]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
ef57eb7b | 2 | * Copyright (C) 1996-2016 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
30a4f2a8 | 7 | */ |
019dd986 | 8 | |
bbc27441 AJ |
9 | /* DEBUG: section 09 File Transfer Protocol (FTP) */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
ddf5aa2b | 12 | #include "acl/FilledChecklist.h" |
5517260a | 13 | #include "clients/forward.h" |
92ae4c86 | 14 | #include "clients/FtpClient.h" |
04f55905 | 15 | #include "comm.h" |
aed188fd | 16 | #include "comm/ConnOpener.h" |
7e66d5e2 | 17 | #include "comm/Read.h" |
cbff89ba | 18 | #include "comm/TcpAcceptor.h" |
582c2af2 | 19 | #include "CommCalls.h" |
27bc2077 | 20 | #include "compat/strtoll.h" |
aa839030 | 21 | #include "errorpage.h" |
c4ad1349 | 22 | #include "fd.h" |
528b2c61 | 23 | #include "fde.h" |
eb13c21e | 24 | #include "FwdState.h" |
25f98340 | 25 | #include "html_quote.h" |
528b2c61 | 26 | #include "HttpHdrContRange.h" |
27 | #include "HttpHeader.h" | |
582c2af2 | 28 | #include "HttpHeaderRange.h" |
04f55905 | 29 | #include "HttpReply.h" |
582c2af2 | 30 | #include "HttpRequest.h" |
055421ee | 31 | #include "ip/tools.h" |
04f55905 | 32 | #include "MemBuf.h" |
b65ce00c | 33 | #include "mime.h" |
056b24a1 | 34 | #include "rfc1738.h" |
4d5904f7 | 35 | #include "SquidConfig.h" |
056b24a1 | 36 | #include "SquidString.h" |
04f55905 | 37 | #include "SquidTime.h" |
e4f1fdae | 38 | #include "StatCounters.h" |
04f55905 | 39 | #include "Store.h" |
4e540555 | 40 | #include "tools.h" |
b1bd952a | 41 | #include "URL.h" |
ed6e9fb9 | 42 | #include "util.h" |
04f55905 AJ |
43 | #include "wordlist.h" |
44 | ||
9a0a18de | 45 | #if USE_DELAY_POOLS |
b67e2c8c | 46 | #include "DelayPools.h" |
86a2f789 | 47 | #include "MemObject.h" |
b67e2c8c | 48 | #endif |
253caccb | 49 | |
1a30fdf5 | 50 | #include <cerrno> |
21d845b1 | 51 | |
27c841f6 AR |
52 | namespace Ftp |
53 | { | |
5517260a | 54 | |
5517260a | 55 | struct GatewayFlags { |
4e2da309 AJ |
56 | |
57 | /* passive mode */ | |
58 | bool pasv_supported; ///< PASV command is allowed | |
59 | bool epsv_all_sent; ///< EPSV ALL has been used. Must abort on failures. | |
60 | bool pasv_only; | |
6200d3d6 | 61 | bool pasv_failed; // was FwdState::flags.ftp_pasv_failed |
4e2da309 AJ |
62 | |
63 | /* authentication */ | |
64 | bool authenticated; ///< authentication success | |
65 | bool tried_auth_anonymous; ///< auth has tried to use anonymous credentials already. | |
66 | bool tried_auth_nopass; ///< auth tried username with no password already. | |
67 | ||
68 | /* other */ | |
3977372c | 69 | bool isdir; |
3977372c | 70 | bool skip_whitespace; |
71 | bool rest_supported; | |
3977372c | 72 | bool http_header_sent; |
73 | bool tried_nlst; | |
74 | bool need_base_href; | |
75 | bool dir_slash; | |
76 | bool root_dir; | |
77 | bool no_dotdot; | |
3977372c | 78 | bool binary; |
79 | bool try_slash_hack; | |
80 | bool put; | |
81 | bool put_mkdir; | |
82 | bool listformat_unknown; | |
0477a072 | 83 | bool listing; |
5f8252d2 | 84 | bool completed_forwarding; |
e55f0142 | 85 | }; |
86 | ||
5517260a | 87 | class Gateway; |
5517260a | 88 | typedef void (StateMethod)(Ftp::Gateway *); |
6f0aab86 | 89 | |
5517260a | 90 | /// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI, |
e7ce227f | 91 | /// converts it into one or more FTP commands, and then |
5517260a AR |
92 | /// converts one or more FTP responses into the final HTTP response. |
93 | class Gateway : public Ftp::Client | |
62e76326 | 94 | { |
5c2f68b7 AJ |
95 | CBDATA_CLASS(Gateway); |
96 | ||
0353e724 | 97 | public: |
5517260a AR |
98 | Gateway(FwdState *); |
99 | virtual ~Gateway(); | |
77a30ebb | 100 | char user[MAX_URL]; |
101 | char password[MAX_URL]; | |
9bc73deb | 102 | int password_url; |
f0afe435 | 103 | char *reply_hdr; |
f0afe435 | 104 | int reply_hdr_state; |
30abd221 | 105 | String clean_url; |
106 | String title_url; | |
107 | String base_href; | |
3fdadc70 | 108 | int conn_att; |
109 | int login_att; | |
3fdadc70 | 110 | time_t mdtm; |
47f6e231 | 111 | int64_t theSize; |
3fdadc70 | 112 | wordlist *pathcomps; |
113 | char *filepath; | |
5627a633 | 114 | char *dirpath; |
47f6e231 | 115 | int64_t restart_offset; |
3fdadc70 | 116 | char *proxy_host; |
117 | size_t list_width; | |
0477a072 | 118 | String cwd_message; |
0f169992 | 119 | char *old_filepath; |
9e242e02 | 120 | char typecode; |
f53969cc | 121 | MemBuf listing; ///< FTP directory listing in HTML format. |
62e76326 | 122 | |
5517260a | 123 | GatewayFlags flags; |
0353e724 | 124 | |
6f0aab86 | 125 | public: |
253caccb | 126 | // these should all be private |
79628299 | 127 | virtual void start(); |
434a79b0 | 128 | virtual Http::StatusCode failedHttpStatus(err_type &error); |
6f0aab86 | 129 | int restartable(); |
130 | void appendSuccessHeader(); | |
5517260a | 131 | void hackShortcut(StateMethod *nextState); |
6f0aab86 | 132 | void unhack(); |
5f8252d2 | 133 | void readStor(); |
f1a83a7d | 134 | void parseListing(); |
0477a072 AJ |
135 | MemBuf *htmlifyListEntry(const char *line); |
136 | void completedListing(void); | |
00406b24 | 137 | |
00406b24 | 138 | /// create a data channel acceptor and start listening. |
434a79b0 | 139 | void listenForDataChannel(const Comm::ConnectionPointer &conn); |
00406b24 | 140 | |
f1a83a7d | 141 | int checkAuth(const HttpHeader * req_hdr); |
142 | void checkUrlpath(); | |
143 | void buildTitleUrl(); | |
47f6e231 | 144 | void writeReplyBody(const char *, size_t len); |
253caccb | 145 | void printfReplyBody(const char *fmt, ...); |
5f8252d2 | 146 | virtual void completeForwarding(); |
063a47b5 | 147 | void processHeadResponse(); |
253caccb | 148 | void processReplyBody(); |
9fa2e208 CT |
149 | void setCurrentOffset(int64_t offset) { currentOffset = offset; } |
150 | int64_t getCurrentOffset() const { return currentOffset; } | |
6f0aab86 | 151 | |
43446566 | 152 | virtual void dataChannelConnected(const CommConnectCbParams &io); |
6f0aab86 | 153 | static PF ftpDataWrite; |
434a79b0 | 154 | virtual void timeout(const CommTimeoutCbParams &io); |
dc56a9b1 | 155 | void ftpAcceptDataConnection(const CommAcceptCbParams &io); |
156 | ||
6f0aab86 | 157 | static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm); |
f381d699 AJ |
158 | const char *ftpRealm(void); |
159 | void loginFailed(void); | |
5f8252d2 | 160 | |
063a47b5 | 161 | virtual void haveParsedReplyHeaders(); |
162 | ||
aa190430 | 163 | virtual bool haveControlChannel(const char *caller_name) const; |
434a79b0 DK |
164 | |
165 | protected: | |
166 | virtual void handleControlReply(); | |
167 | virtual void dataClosed(const CommCloseCbParams &io); | |
253caccb | 168 | |
5f8252d2 | 169 | private: |
aea65fec | 170 | virtual bool mayReadVirginReplyBody() const; |
5f8252d2 | 171 | // BodyConsumer for HTTP: consume request body. |
172 | virtual void handleRequestBodyProducerAborted(); | |
92d6986d AJ |
173 | |
174 | void loginParser(const SBuf &login, bool escaped); | |
0353e724 | 175 | }; |
176 | ||
5517260a AR |
177 | } // namespace Ftp |
178 | ||
179 | typedef Ftp::StateMethod FTPSM; // to avoid lots of non-changes | |
180 | ||
181 | CBDATA_NAMESPACED_CLASS_INIT(Ftp, Gateway); | |
0353e724 | 182 | |
9e008dda | 183 | typedef struct { |
3fdadc70 | 184 | char type; |
47f6e231 | 185 | int64_t size; |
3fdadc70 | 186 | char *date; |
187 | char *name; | |
188 | char *showname; | |
189 | char *link; | |
2fadd50d | 190 | } ftpListParts; |
3fdadc70 | 191 | |
5517260a | 192 | #define CTRL_BUFLEN 1024 |
5517260a AR |
193 | static char cbuf[CTRL_BUFLEN]; |
194 | ||
7e3ce7b9 | 195 | /* |
196 | * State machine functions | |
969c39b9 | 197 | * send == state transition |
198 | * read == wait for response, and select next state transition | |
dbfed404 | 199 | * other == Transition logic |
969c39b9 | 200 | */ |
3fdadc70 | 201 | static FTPSM ftpReadWelcome; |
969c39b9 | 202 | static FTPSM ftpSendUser; |
3fdadc70 | 203 | static FTPSM ftpReadUser; |
969c39b9 | 204 | static FTPSM ftpSendPass; |
3fdadc70 | 205 | static FTPSM ftpReadPass; |
969c39b9 | 206 | static FTPSM ftpSendType; |
3fdadc70 | 207 | static FTPSM ftpReadType; |
969c39b9 | 208 | static FTPSM ftpSendMdtm; |
3fdadc70 | 209 | static FTPSM ftpReadMdtm; |
969c39b9 | 210 | static FTPSM ftpSendSize; |
3fdadc70 | 211 | static FTPSM ftpReadSize; |
cc192b50 | 212 | static FTPSM ftpSendEPRT; |
213 | static FTPSM ftpReadEPRT; | |
214 | static FTPSM ftpSendPORT; | |
215 | static FTPSM ftpReadPORT; | |
a689bd4e | 216 | static FTPSM ftpSendPassive; |
cc192b50 | 217 | static FTPSM ftpReadEPSV; |
3fdadc70 | 218 | static FTPSM ftpReadPasv; |
dbfed404 | 219 | static FTPSM ftpTraverseDirectory; |
220 | static FTPSM ftpListDir; | |
221 | static FTPSM ftpGetFile; | |
969c39b9 | 222 | static FTPSM ftpSendCwd; |
3fdadc70 | 223 | static FTPSM ftpReadCwd; |
94439e4e | 224 | static FTPSM ftpRestOrList; |
969c39b9 | 225 | static FTPSM ftpSendList; |
226 | static FTPSM ftpSendNlst; | |
3fdadc70 | 227 | static FTPSM ftpReadList; |
969c39b9 | 228 | static FTPSM ftpSendRest; |
3fdadc70 | 229 | static FTPSM ftpReadRest; |
969c39b9 | 230 | static FTPSM ftpSendRetr; |
3fdadc70 | 231 | static FTPSM ftpReadRetr; |
232 | static FTPSM ftpReadTransferDone; | |
54220df8 | 233 | static FTPSM ftpSendStor; |
234 | static FTPSM ftpReadStor; | |
94439e4e | 235 | static FTPSM ftpWriteTransferDone; |
54220df8 | 236 | static FTPSM ftpSendReply; |
94439e4e | 237 | static FTPSM ftpSendMkdir; |
54220df8 | 238 | static FTPSM ftpReadMkdir; |
94439e4e | 239 | static FTPSM ftpFail; |
240 | static FTPSM ftpSendQuit; | |
241 | static FTPSM ftpReadQuit; | |
a689bd4e | 242 | |
243 | /************************************************ | |
244 | ** Debugs Levels used here ** | |
245 | ************************************************* | |
f53969cc SM |
246 | 0 CRITICAL Events |
247 | 1 IMPORTANT Events | |
248 | Protocol and Transmission failures. | |
249 | 2 FTP Protocol Chatter | |
250 | 3 Logic Flows | |
251 | 4 Data Parsing Flows | |
252 | 5 Data Dumps | |
253 | 7 ?? | |
a689bd4e | 254 | ************************************************/ |
255 | ||
dbfed404 | 256 | /************************************************ |
257 | ** State Machine Description (excluding hacks) ** | |
258 | ************************************************* | |
f53969cc | 259 | From To |
dbfed404 | 260 | --------------------------------------- |
f53969cc SM |
261 | Welcome User |
262 | User Pass | |
263 | Pass Type | |
264 | Type TraverseDirectory / GetFile | |
265 | TraverseDirectory Cwd / GetFile / ListDir | |
266 | Cwd TraverseDirectory / Mkdir | |
267 | GetFile Mdtm | |
268 | Mdtm Size | |
269 | Size Epsv | |
270 | ListDir Epsv | |
271 | Epsv FileOrList | |
272 | FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d) | |
273 | Rest Retr | |
274 | Retr / Nlst / List DataRead* (on datachannel) | |
275 | DataRead* ReadTransferDone | |
276 | ReadTransferDone DataTransferDone | |
277 | Stor DataWrite* (on datachannel) | |
278 | DataWrite* RequestPutBody** (from client) | |
279 | RequestPutBody** DataWrite* / WriteTransferDone | |
280 | WriteTransferDone DataTransferDone | |
281 | DataTransferDone Quit | |
282 | Quit - | |
dbfed404 | 283 | ************************************************/ |
3fdadc70 | 284 | |
9e008dda | 285 | FTPSM *FTP_SM_FUNCS[] = { |
f53969cc SM |
286 | ftpReadWelcome, /* BEGIN */ |
287 | ftpReadUser, /* SENT_USER */ | |
288 | ftpReadPass, /* SENT_PASS */ | |
289 | ftpReadType, /* SENT_TYPE */ | |
290 | ftpReadMdtm, /* SENT_MDTM */ | |
291 | ftpReadSize, /* SENT_SIZE */ | |
292 | ftpReadEPRT, /* SENT_EPRT */ | |
293 | ftpReadPORT, /* SENT_PORT */ | |
294 | ftpReadEPSV, /* SENT_EPSV_ALL */ | |
295 | ftpReadEPSV, /* SENT_EPSV_1 */ | |
296 | ftpReadEPSV, /* SENT_EPSV_2 */ | |
297 | ftpReadPasv, /* SENT_PASV */ | |
298 | ftpReadCwd, /* SENT_CWD */ | |
299 | ftpReadList, /* SENT_LIST */ | |
300 | ftpReadList, /* SENT_NLST */ | |
301 | ftpReadRest, /* SENT_REST */ | |
302 | ftpReadRetr, /* SENT_RETR */ | |
303 | ftpReadStor, /* SENT_STOR */ | |
304 | ftpReadQuit, /* SENT_QUIT */ | |
305 | ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */ | |
306 | ftpWriteTransferDone, /* WRITING_DATA (STOR) */ | |
307 | ftpReadMkdir, /* SENT_MKDIR */ | |
308 | NULL, /* SENT_FEAT */ | |
309 | NULL, /* SENT_PWD */ | |
310 | NULL, /* SENT_CDUP*/ | |
311 | NULL, /* SENT_DATA_REQUEST */ | |
312 | NULL /* SENT_COMMAND */ | |
9e008dda | 313 | }; |
e381a13d | 314 | |
6e92b048 | 315 | /// handler called by Comm when FTP data channel is closed unexpectedly |
9e008dda | 316 | void |
5517260a | 317 | Ftp::Gateway::dataClosed(const CommCloseCbParams &io) |
518a192e | 318 | { |
5517260a | 319 | Ftp::Client::dataClosed(io); |
5a5b4ce1 | 320 | failed(ERR_FTP_FAILURE, 0); |
6b679a01 | 321 | /* failed closes ctrl.conn and frees ftpState */ |
5a5b4ce1 | 322 | |
6b679a01 | 323 | /* NP: failure recovery may be possible when its only a data.conn failure. |
6200d3d6 | 324 | * if the ctrl.conn is still fine, we can send ABOR down it and retry. |
5a5b4ce1 AJ |
325 | * Just need to watch out for wider Squid states like shutting down or reconfigure. |
326 | */ | |
6f0aab86 | 327 | } |
1be4874e | 328 | |
5517260a | 329 | Ftp::Gateway::Gateway(FwdState *fwdState): |
f53969cc SM |
330 | AsyncJob("FtpStateData"), |
331 | Ftp::Client(fwdState), | |
332 | password_url(0), | |
333 | reply_hdr(NULL), | |
334 | reply_hdr_state(0), | |
335 | conn_att(0), | |
336 | login_att(0), | |
337 | mdtm(-1), | |
338 | theSize(-1), | |
339 | pathcomps(NULL), | |
340 | filepath(NULL), | |
341 | dirpath(NULL), | |
342 | restart_offset(0), | |
343 | proxy_host(NULL), | |
344 | list_width(0), | |
345 | old_filepath(NULL), | |
346 | typecode('\0') | |
6f0aab86 | 347 | { |
4579a6d0 AJ |
348 | debugs(9, 3, entry->url()); |
349 | ||
350 | *user = 0; | |
351 | *password = 0; | |
352 | memset(&flags, 0, sizeof(flags)); | |
6f0aab86 | 353 | |
6200d3d6 | 354 | if (Config.Ftp.passive && !flags.pasv_failed) |
fe6b2914 | 355 | flags.pasv_supported = 1; |
6f0aab86 | 356 | |
fe6b2914 | 357 | flags.rest_supported = 1; |
6f0aab86 | 358 | |
c2a7cefd | 359 | if (request->method == Http::METHOD_PUT) |
fe6b2914 | 360 | flags.put = 1; |
adaff124 DK |
361 | |
362 | initReadBuf(); | |
518a192e | 363 | } |
364 | ||
5517260a | 365 | Ftp::Gateway::~Gateway() |
ba718c8f | 366 | { |
4579a6d0 | 367 | debugs(9, 3, entry->url()); |
62e76326 | 368 | |
6b679a01 | 369 | if (Comm::IsConnOpen(ctrl.conn)) { |
e7ce227f AR |
370 | debugs(9, DBG_IMPORTANT, "Internal bug: FTP Gateway left open " << |
371 | "control channel " << ctrl.conn); | |
1ecaa0a0 | 372 | } |
62e76326 | 373 | |
434a79b0 DK |
374 | if (reply_hdr) { |
375 | memFree(reply_hdr, MEM_8K_BUF); | |
376 | reply_hdr = NULL; | |
968d691c | 377 | } |
62e76326 | 378 | |
6f0aab86 | 379 | if (pathcomps) |
380 | wordlistDestroy(&pathcomps); | |
62e76326 | 381 | |
0477a072 | 382 | cwd_message.clean(); |
4579a6d0 | 383 | xfree(old_filepath); |
30abd221 | 384 | title_url.clean(); |
30abd221 | 385 | base_href.clean(); |
4579a6d0 AJ |
386 | xfree(filepath); |
387 | xfree(dirpath); | |
ba718c8f | 388 | } |
389 | ||
f381d699 AJ |
390 | /** |
391 | * Parse a possible login username:password pair. | |
cd0b63ba | 392 | * Produces filled member variables user, password, password_url if anything found. |
92d6986d AJ |
393 | * |
394 | * \param login a decoded Basic authentication credential token or URI user-info token | |
395 | * \param escaped whether to URL-decode the token after extracting user and password | |
f381d699 | 396 | */ |
6f0aab86 | 397 | void |
92d6986d | 398 | Ftp::Gateway::loginParser(const SBuf &login, bool escaped) |
090089c4 | 399 | { |
92d6986d AJ |
400 | debugs(9, 4, "login=" << login << ", escaped=" << escaped); |
401 | debugs(9, 9, "IN : login=" << login << ", escaped=" << escaped << ", user=" << user << ", password=" << password); | |
62e76326 | 402 | |
92d6986d AJ |
403 | if (login.isEmpty()) |
404 | return; | |
405 | ||
406 | const SBuf::size_type colonPos = login.find(':'); | |
407 | ||
408 | /* If there was a username part with at least one character use it. | |
409 | * Ignore 0-length username portion, retain what we have already. | |
410 | */ | |
411 | if (colonPos == SBuf::npos || colonPos > 0) { | |
412 | const SBuf userName = login.substr(0, colonPos); | |
413 | SBuf::size_type upto = userName.copy(user, sizeof(user)-1); | |
414 | user[upto]='\0'; | |
415 | debugs(9, 9, "found user=" << userName << ' ' << | |
416 | (upto != userName.length() ? ", truncated-to=" : ", length=") << upto << | |
417 | ", escaped=" << escaped); | |
f381d699 AJ |
418 | if (escaped) |
419 | rfc1738_unescape(user); | |
92d6986d AJ |
420 | debugs(9, 9, "found user=" << user << " (" << strlen(user) << ") unescaped."); |
421 | } | |
422 | ||
423 | /* If there was a password part. | |
424 | * For 0-length password clobber what we have already, this means explicitly none | |
425 | */ | |
426 | if (colonPos != SBuf::npos) { | |
427 | const SBuf pass = login.substr(colonPos+1, SBuf::npos); | |
428 | SBuf::size_type upto = pass.copy(password, sizeof(password)-1); | |
429 | password[upto]='\0'; | |
430 | debugs(9, 9, "found password=" << pass << " " << | |
431 | (upto != pass.length() ? ", truncated-to=" : ", length=") << upto << | |
432 | ", escaped=" << escaped); | |
433 | if (escaped) { | |
434 | rfc1738_unescape(password); | |
435 | password_url = 1; | |
436 | } | |
437 | debugs(9, 9, "found password=" << password << " (" << strlen(password) << ") unescaped."); | |
f381d699 | 438 | } |
62e76326 | 439 | |
92d6986d | 440 | debugs(9, 9, "OUT: login=" << login << ", escaped=" << escaped << ", user=" << user << ", password=" << password); |
090089c4 | 441 | } |
442 | ||
00406b24 | 443 | void |
5517260a | 444 | Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer &conn) |
00406b24 | 445 | { |
8bbb16e3 | 446 | assert(!Comm::IsConnOpen(data.conn)); |
00406b24 | 447 | |
5517260a | 448 | typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer; |
00406b24 | 449 | typedef AsyncCallT<AcceptDialer> AcceptCall; |
5517260a | 450 | RefCount<AcceptCall> call = static_cast<AcceptCall*>(JobCallback(11, 5, AcceptDialer, this, Ftp::Gateway::ftpAcceptDataConnection)); |
00406b24 | 451 | Subscription::Pointer sub = new CallSubscription<AcceptCall>(call); |
434a79b0 | 452 | const char *note = entry->url(); |
cbff89ba AJ |
453 | |
454 | /* open the conn if its not already open */ | |
8bbb16e3 AJ |
455 | if (!Comm::IsConnOpen(conn)) { |
456 | conn->fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn->local, conn->flags, note); | |
457 | if (!Comm::IsConnOpen(conn)) { | |
458 | debugs(5, DBG_CRITICAL, HERE << "comm_open_listener failed:" << conn->local << " error: " << errno); | |
cbff89ba AJ |
459 | return; |
460 | } | |
8bbb16e3 | 461 | debugs(9, 3, HERE << "Unconnected data socket created on " << conn); |
cbff89ba AJ |
462 | } |
463 | ||
c6f168c1 CT |
464 | conn->tos = ctrl.conn->tos; |
465 | conn->nfmark = ctrl.conn->nfmark; | |
466 | ||
8bbb16e3 AJ |
467 | assert(Comm::IsConnOpen(conn)); |
468 | AsyncJob::Start(new Comm::TcpAcceptor(conn, note, sub)); | |
cbff89ba AJ |
469 | |
470 | // Ensure we have a copy of the FD opened for listening and a close handler on it. | |
8bbb16e3 | 471 | data.opened(conn, dataCloser()); |
cbff89ba | 472 | switchTimeoutToDataChannel(); |
00406b24 AJ |
473 | } |
474 | ||
6f0aab86 | 475 | void |
5517260a | 476 | Ftp::Gateway::timeout(const CommTimeoutCbParams &io) |
090089c4 | 477 | { |
05cb9a45 AJ |
478 | if (SENT_PASV == state) { |
479 | /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */ | |
d890b809 | 480 | flags.pasv_supported = false; |
e7ce227f | 481 | debugs(9, DBG_IMPORTANT, "FTP Gateway timeout in SENT_PASV state"); |
05cb9a45 AJ |
482 | |
483 | // cancel the data connection setup. | |
484 | if (data.opener != NULL) { | |
485 | data.opener->cancel("timeout"); | |
486 | data.opener = NULL; | |
487 | } | |
488 | data.close(); | |
7e3ce7b9 | 489 | } |
62e76326 | 490 | |
5517260a | 491 | Ftp::Client::timeout(io); |
090089c4 | 492 | } |
493 | ||
9e008dda AJ |
494 | static const char *Month[] = { |
495 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
496 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" | |
497 | }; | |
3fdadc70 | 498 | |
499 | static int | |
500 | is_month(const char *buf) | |
501 | { | |
502 | int i; | |
62e76326 | 503 | |
95dc7ff4 | 504 | for (i = 0; i < 12; ++i) |
62e76326 | 505 | if (!strcasecmp(buf, Month[i])) |
506 | return 1; | |
507 | ||
3fdadc70 | 508 | return 0; |
509 | } | |
510 | ||
3fdadc70 | 511 | static void |
512 | ftpListPartsFree(ftpListParts ** parts) | |
513 | { | |
514 | safe_free((*parts)->date); | |
515 | safe_free((*parts)->name); | |
516 | safe_free((*parts)->showname); | |
517 | safe_free((*parts)->link); | |
518 | safe_free(*parts); | |
519 | } | |
520 | ||
521 | #define MAX_TOKENS 64 | |
522 | ||
523 | static ftpListParts * | |
5517260a | 524 | ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags) |
3fdadc70 | 525 | { |
526 | ftpListParts *p = NULL; | |
527 | char *t = NULL; | |
528 | const char *ct = NULL; | |
529 | char *tokens[MAX_TOKENS]; | |
530 | int i; | |
531 | int n_tokens; | |
748f6a15 | 532 | static char tbuf[128]; |
3fdadc70 | 533 | char *xbuf = NULL; |
748f6a15 | 534 | static int scan_ftp_initialized = 0; |
535 | static regex_t scan_ftp_integer; | |
536 | static regex_t scan_ftp_time; | |
537 | static regex_t scan_ftp_dostime; | |
538 | static regex_t scan_ftp_dosdate; | |
539 | ||
9e008dda | 540 | if (!scan_ftp_initialized) { |
62e76326 | 541 | scan_ftp_initialized = 1; |
542 | regcomp(&scan_ftp_integer, "^[0123456789]+$", REG_EXTENDED | REG_NOSUB); | |
543 | regcomp(&scan_ftp_time, "^[0123456789:]+$", REG_EXTENDED | REG_NOSUB); | |
544 | regcomp(&scan_ftp_dosdate, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED | REG_NOSUB); | |
545 | regcomp(&scan_ftp_dostime, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED | REG_NOSUB | REG_ICASE); | |
748f6a15 | 546 | } |
62e76326 | 547 | |
3fdadc70 | 548 | if (buf == NULL) |
62e76326 | 549 | return NULL; |
550 | ||
3fdadc70 | 551 | if (*buf == '\0') |
62e76326 | 552 | return NULL; |
553 | ||
e6ccf245 | 554 | p = (ftpListParts *)xcalloc(1, sizeof(ftpListParts)); |
62e76326 | 555 | |
3fdadc70 | 556 | n_tokens = 0; |
62e76326 | 557 | |
748f6a15 | 558 | memset(tokens, 0, sizeof(tokens)); |
62e76326 | 559 | |
3fdadc70 | 560 | xbuf = xstrdup(buf); |
62e76326 | 561 | |
9e008dda | 562 | if (flags.tried_nlst) { |
62e76326 | 563 | /* Machine readable format, one name per line */ |
564 | p->name = xbuf; | |
565 | p->type = '\0'; | |
566 | return p; | |
dbfed404 | 567 | } |
62e76326 | 568 | |
a38ec4b1 FC |
569 | for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) { |
570 | tokens[n_tokens] = xstrdup(t); | |
571 | ++n_tokens; | |
572 | } | |
62e76326 | 573 | |
3fdadc70 | 574 | xfree(xbuf); |
62e76326 | 575 | |
3fdadc70 | 576 | /* locate the Month field */ |
95dc7ff4 | 577 | for (i = 3; i < n_tokens - 2; ++i) { |
62e76326 | 578 | char *size = tokens[i - 1]; |
579 | char *month = tokens[i]; | |
580 | char *day = tokens[i + 1]; | |
581 | char *year = tokens[i + 2]; | |
582 | ||
583 | if (!is_month(month)) | |
584 | continue; | |
585 | ||
586 | if (regexec(&scan_ftp_integer, size, 0, NULL, 0) != 0) | |
587 | continue; | |
588 | ||
589 | if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0) | |
590 | continue; | |
591 | ||
f53969cc | 592 | if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */ |
62e76326 | 593 | continue; |
594 | ||
595 | snprintf(tbuf, 128, "%s %2s %5s", | |
596 | month, day, year); | |
597 | ||
598 | if (!strstr(buf, tbuf)) | |
599 | snprintf(tbuf, 128, "%s %2s %-5s", | |
600 | month, day, year); | |
601 | ||
65bfb9ab | 602 | char const *copyFrom = NULL; |
603 | ||
604 | if ((copyFrom = strstr(buf, tbuf))) { | |
62e76326 | 605 | p->type = *tokens[0]; |
47f6e231 | 606 | p->size = strtoll(size, NULL, 10); |
62e76326 | 607 | p->date = xstrdup(tbuf); |
608 | ||
609 | if (flags.skip_whitespace) { | |
65bfb9ab | 610 | copyFrom += strlen(tbuf); |
62e76326 | 611 | |
65bfb9ab | 612 | while (strchr(w_space, *copyFrom)) |
95dc7ff4 | 613 | ++copyFrom; |
62e76326 | 614 | } else { |
615 | /* XXX assumes a single space between date and filename | |
616 | * suggested by: Nathan.Bailey@cc.monash.edu.au and | |
617 | * Mike Battersby <mike@starbug.bofh.asn.au> */ | |
65bfb9ab | 618 | copyFrom += strlen(tbuf) + 1; |
62e76326 | 619 | } |
620 | ||
65bfb9ab | 621 | p->name = xstrdup(copyFrom); |
62e76326 | 622 | |
38ebaefc | 623 | if (p->type == 'l' && (t = strstr(p->name, " -> "))) { |
62e76326 | 624 | *t = '\0'; |
625 | p->link = xstrdup(t + 4); | |
626 | } | |
627 | ||
628 | goto found; | |
629 | } | |
630 | ||
631 | break; | |
3fdadc70 | 632 | } |
62e76326 | 633 | |
748f6a15 | 634 | /* try it as a DOS listing, 04-05-70 09:33PM ... */ |
635 | if (n_tokens > 3 && | |
62e76326 | 636 | regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 && |
9e008dda | 637 | regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) { |
62e76326 | 638 | if (!strcasecmp(tokens[2], "<dir>")) { |
639 | p->type = 'd'; | |
640 | } else { | |
641 | p->type = '-'; | |
47f6e231 | 642 | p->size = strtoll(tokens[2], NULL, 10); |
62e76326 | 643 | } |
644 | ||
645 | snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]); | |
646 | p->date = xstrdup(tbuf); | |
647 | ||
648 | if (p->type == 'd') { | |
649 | /* Directory.. name begins with first printable after <dir> */ | |
650 | ct = strstr(buf, tokens[2]); | |
651 | ct += strlen(tokens[2]); | |
652 | ||
653 | while (xisspace(*ct)) | |
95dc7ff4 | 654 | ++ct; |
62e76326 | 655 | |
656 | if (!*ct) | |
657 | ct = NULL; | |
658 | } else { | |
659 | /* A file. Name begins after size, with a space in between */ | |
660 | snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]); | |
661 | ct = strstr(buf, tbuf); | |
662 | ||
663 | if (ct) { | |
664 | ct += strlen(tokens[2]) + 2; | |
665 | } | |
666 | } | |
667 | ||
668 | p->name = xstrdup(ct ? ct : tokens[3]); | |
669 | goto found; | |
3fdadc70 | 670 | } |
62e76326 | 671 | |
3fdadc70 | 672 | /* Try EPLF format; carson@lehman.com */ |
9e008dda | 673 | if (buf[0] == '+') { |
62e76326 | 674 | ct = buf + 1; |
675 | p->type = 0; | |
676 | ||
677 | while (ct && *ct) { | |
d5f8d05f | 678 | time_t tm; |
3f9d0b2d | 679 | int l = strcspn(ct, ","); |
62e76326 | 680 | char *tmp; |
681 | ||
682 | if (l < 1) | |
683 | goto blank; | |
684 | ||
685 | switch (*ct) { | |
686 | ||
687 | case '\t': | |
688 | p->name = xstrndup(ct + 1, l + 1); | |
689 | break; | |
690 | ||
691 | case 's': | |
692 | p->size = atoi(ct + 1); | |
693 | break; | |
694 | ||
695 | case 'm': | |
d5f8d05f | 696 | tm = (time_t) strtol(ct + 1, &tmp, 0); |
62e76326 | 697 | |
3f9d0b2d | 698 | if (tmp != ct + 1) |
f53969cc | 699 | break; /* not a valid integer */ |
62e76326 | 700 | |
d5f8d05f | 701 | p->date = xstrdup(ctime(&tm)); |
62e76326 | 702 | |
703 | *(strstr(p->date, "\n")) = '\0'; | |
704 | ||
705 | break; | |
706 | ||
707 | case '/': | |
708 | p->type = 'd'; | |
709 | ||
710 | break; | |
711 | ||
712 | case 'r': | |
713 | p->type = '-'; | |
714 | ||
715 | break; | |
716 | ||
717 | case 'i': | |
718 | break; | |
719 | ||
720 | default: | |
721 | break; | |
722 | } | |
723 | ||
724 | blank: | |
725 | ct = strstr(ct, ","); | |
726 | ||
727 | if (ct) { | |
95dc7ff4 | 728 | ++ct; |
62e76326 | 729 | } |
730 | } | |
731 | ||
732 | if (p->type == 0) { | |
733 | p->type = '-'; | |
734 | } | |
735 | ||
736 | if (p->name) | |
737 | goto found; | |
738 | else | |
739 | safe_free(p->date); | |
740 | } | |
741 | ||
742 | found: | |
743 | ||
95dc7ff4 | 744 | for (i = 0; i < n_tokens; ++i) |
62e76326 | 745 | xfree(tokens[i]); |
746 | ||
748f6a15 | 747 | if (!p->name) |
f53969cc | 748 | ftpListPartsFree(&p); /* cleanup */ |
62e76326 | 749 | |
3fdadc70 | 750 | return p; |
751 | } | |
752 | ||
0477a072 | 753 | MemBuf * |
5517260a | 754 | Ftp::Gateway::htmlifyListEntry(const char *line) |
3fdadc70 | 755 | { |
0477a072 AJ |
756 | char icon[2048]; |
757 | char href[2048 + 40]; | |
758 | char text[ 2048]; | |
759 | char size[ 2048]; | |
760 | char chdir[ 2048 + 40]; | |
761 | char view[ 2048 + 40]; | |
762 | char download[ 2048 + 40]; | |
763 | char link[ 2048 + 40]; | |
764 | MemBuf *html; | |
765 | char prefix[2048]; | |
3fdadc70 | 766 | ftpListParts *parts; |
0477a072 | 767 | *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0'; |
62e76326 | 768 | |
0477a072 | 769 | debugs(9, 7, HERE << " line ={" << line << "}"); |
62e76326 | 770 | |
0477a072 AJ |
771 | if (strlen(line) > 1024) { |
772 | html = new MemBuf(); | |
773 | html->init(); | |
4391cd15 | 774 | html->appendf("<tr><td colspan=\"5\">%s</td></tr>\n", line); |
62e76326 | 775 | return html; |
3fdadc70 | 776 | } |
62e76326 | 777 | |
45289935 | 778 | if (flags.dir_slash && dirpath && typecode != 'D') |
779 | snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath)); | |
5627a633 | 780 | else |
781 | prefix[0] = '\0'; | |
782 | ||
6f0aab86 | 783 | if ((parts = ftpListParseParts(line, flags)) == NULL) { |
62e76326 | 784 | const char *p; |
0477a072 AJ |
785 | |
786 | html = new MemBuf(); | |
787 | html->init(); | |
4391cd15 | 788 | html->appendf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line); |
62e76326 | 789 | |
95dc7ff4 | 790 | for (p = line; *p && xisspace(*p); ++p); |
62e76326 | 791 | if (*p && !xisspace(*p)) |
6f0aab86 | 792 | flags.listformat_unknown = 1; |
62e76326 | 793 | |
794 | return html; | |
13e80e5b | 795 | } |
62e76326 | 796 | |
3fdadc70 | 797 | if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) { |
62e76326 | 798 | ftpListPartsFree(&parts); |
0477a072 | 799 | return NULL; |
3fdadc70 | 800 | } |
62e76326 | 801 | |
3fdadc70 | 802 | parts->size += 1023; |
803 | parts->size >>= 10; | |
804 | parts->showname = xstrdup(parts->name); | |
62e76326 | 805 | |
2a1ca944 | 806 | /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */ |
9bc73deb | 807 | xstrncpy(href, rfc1738_escape_part(parts->name), 2048); |
62e76326 | 808 | |
2a1ca944 | 809 | xstrncpy(text, parts->showname, 2048); |
62e76326 | 810 | |
3fdadc70 | 811 | switch (parts->type) { |
62e76326 | 812 | |
3fdadc70 | 813 | case 'd': |
0477a072 | 814 | snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">", |
62e76326 | 815 | mimeGetIconURL("internal-dir"), |
816 | "[DIR]"); | |
f53969cc | 817 | strcat(href, "/"); /* margin is allocated above */ |
62e76326 | 818 | break; |
819 | ||
3fdadc70 | 820 | case 'l': |
0477a072 | 821 | snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">", |
62e76326 | 822 | mimeGetIconURL("internal-link"), |
823 | "[LINK]"); | |
824 | /* sometimes there is an 'l' flag, but no "->" link */ | |
825 | ||
826 | if (parts->link) { | |
827 | char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link))); | |
0477a072 | 828 | snprintf(link, 2048, " -> <a href=\"%s%s\">%s</a>", |
5627a633 | 829 | *link2 != '/' ? prefix : "", link2, |
62e76326 | 830 | html_quote(parts->link)); |
831 | safe_free(link2); | |
832 | } | |
833 | ||
834 | break; | |
835 | ||
dbfed404 | 836 | case '\0': |
0477a072 | 837 | snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">", |
62e76326 | 838 | mimeGetIconURL(parts->name), |
839 | "[UNKNOWN]"); | |
0477a072 AJ |
840 | snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" " |
841 | "alt=\"[DIR]\"></a>", | |
62e76326 | 842 | rfc1738_escape_part(parts->name), |
843 | mimeGetIconURL("internal-dir")); | |
844 | break; | |
845 | ||
3fdadc70 | 846 | case '-': |
62e76326 | 847 | |
3fdadc70 | 848 | default: |
0477a072 | 849 | snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">", |
62e76326 | 850 | mimeGetIconURL(parts->name), |
851 | "[FILE]"); | |
c91ca3ce | 852 | snprintf(size, 2048, " %6" PRId64 "k", parts->size); |
62e76326 | 853 | break; |
3fdadc70 | 854 | } |
62e76326 | 855 | |
2a1ca944 | 856 | if (parts->type != 'd') { |
62e76326 | 857 | if (mimeGetViewOption(parts->name)) { |
0477a072 AJ |
858 | snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" " |
859 | "alt=\"[VIEW]\"></a>", | |
5627a633 | 860 | prefix, href, mimeGetIconURL("internal-view")); |
62e76326 | 861 | } |
862 | ||
863 | if (mimeGetDownloadOption(parts->name)) { | |
0477a072 AJ |
864 | snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" " |
865 | "alt=\"[DOWNLOAD]\"></a>", | |
5627a633 | 866 | prefix, href, mimeGetIconURL("internal-download")); |
62e76326 | 867 | } |
2a1ca944 | 868 | } |
62e76326 | 869 | |
0477a072 AJ |
870 | /* construct the table row from parts. */ |
871 | html = new MemBuf(); | |
872 | html->init(); | |
4391cd15 | 873 | html->appendf("<tr class=\"entry\">" |
f680026f SM |
874 | "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>" |
875 | "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>" | |
876 | "<td class=\"date\">%s</td>" | |
877 | "<td class=\"size\">%s</td>" | |
878 | "<td class=\"actions\">%s%s%s%s</td>" | |
879 | "</tr>\n", | |
880 | prefix, href, icon, | |
881 | prefix, href, html_quote(text), | |
882 | parts->date, | |
883 | size, | |
884 | chdir, view, download, link); | |
62e76326 | 885 | |
3fdadc70 | 886 | ftpListPartsFree(&parts); |
3fdadc70 | 887 | return html; |
888 | } | |
889 | ||
f1a83a7d | 890 | void |
5517260a | 891 | Ftp::Gateway::parseListing() |
3fdadc70 | 892 | { |
253caccb | 893 | char *buf = data.readBuf->content(); |
f53969cc | 894 | char *sbuf; /* NULL-terminated copy of termedBuf */ |
b5639035 | 895 | char *end; |
896 | char *line; | |
3fdadc70 | 897 | char *s; |
0477a072 | 898 | MemBuf *t; |
3fdadc70 | 899 | size_t linelen; |
b5639035 | 900 | size_t usable; |
253caccb | 901 | size_t len = data.readBuf->contentSize(); |
5f8252d2 | 902 | |
903 | if (!len) { | |
0477a072 | 904 | debugs(9, 3, HERE << "no content to parse for " << entry->url() ); |
5f8252d2 | 905 | return; |
906 | } | |
907 | ||
7131112f | 908 | /* |
909 | * We need a NULL-terminated buffer for scanning, ick | |
910 | */ | |
e6ccf245 | 911 | sbuf = (char *)xmalloc(len + 1); |
7131112f | 912 | xstrncpy(sbuf, buf, len + 1); |
913 | end = sbuf + len - 1; | |
62e76326 | 914 | |
7131112f | 915 | while (*end != '\r' && *end != '\n' && end > sbuf) |
5e263176 | 916 | --end; |
62e76326 | 917 | |
7131112f | 918 | usable = end - sbuf; |
62e76326 | 919 | |
cbff89ba | 920 | debugs(9, 3, HERE << "usable = " << usable << " of " << len << " bytes."); |
62e76326 | 921 | |
b5639035 | 922 | if (usable == 0) { |
cbff89ba AJ |
923 | if (buf[0] == '\0' && len == 1) { |
924 | debugs(9, 3, HERE << "NIL ends data from " << entry->url() << " transfer problem?"); | |
925 | data.readBuf->consume(len); | |
926 | } else { | |
927 | debugs(9, 3, HERE << "didn't find end for " << entry->url()); | |
928 | debugs(9, 3, HERE << "buffer remains (" << len << " bytes) '" << rfc1738_do_escape(buf,0) << "'"); | |
929 | } | |
62e76326 | 930 | xfree(sbuf); |
931 | return; | |
3fdadc70 | 932 | } |
62e76326 | 933 | |
a689bd4e | 934 | debugs(9, 3, HERE << (unsigned long int)len << " bytes to play with"); |
bf8fe701 | 935 | |
e6ccf245 | 936 | line = (char *)memAllocate(MEM_4K_BUF); |
95dc7ff4 | 937 | ++end; |
362be274 | 938 | s = sbuf; |
43446566 | 939 | s += strspn(s, crlf); |
62e76326 | 940 | |
43446566 | 941 | for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) { |
a689bd4e | 942 | debugs(9, 7, HERE << "s = {" << s << "}"); |
43446566 | 943 | linelen = strcspn(s, crlf) + 1; |
62e76326 | 944 | |
945 | if (linelen < 2) | |
946 | break; | |
947 | ||
948 | if (linelen > 4096) | |
949 | linelen = 4096; | |
950 | ||
951 | xstrncpy(line, s, linelen); | |
952 | ||
a689bd4e | 953 | debugs(9, 7, HERE << "{" << line << "}"); |
62e76326 | 954 | |
955 | if (!strncmp(line, "total", 5)) | |
956 | continue; | |
957 | ||
f1a83a7d | 958 | t = htmlifyListEntry(line); |
62e76326 | 959 | |
05320519 | 960 | if ( t != NULL) { |
0477a072 AJ |
961 | debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}"); |
962 | listing.append(t->content(), t->contentSize()); | |
6175a21f | 963 | delete t; |
0477a072 | 964 | } |
b5639035 | 965 | } |
62e76326 | 966 | |
0477a072 | 967 | debugs(9, 7, HERE << "Done."); |
253caccb | 968 | data.readBuf->consume(usable); |
db1cd23c | 969 | memFree(line, MEM_4K_BUF); |
4a2635aa | 970 | xfree(sbuf); |
3fdadc70 | 971 | } |
090089c4 | 972 | |
253caccb | 973 | void |
5517260a | 974 | Ftp::Gateway::processReplyBody() |
253caccb | 975 | { |
e7ce227f | 976 | debugs(9, 3, status()); |
063a47b5 | 977 | |
c2a7cefd | 978 | if (request->method == Http::METHOD_HEAD && (flags.isdir || theSize != -1)) { |
063a47b5 | 979 | serverComplete(); |
980 | return; | |
981 | } | |
982 | ||
0477a072 AJ |
983 | /* Directory listings are special. They write ther own headers via the error objects */ |
984 | if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir) | |
253caccb | 985 | appendSuccessHeader(); |
62e76326 | 986 | |
35f26998 | 987 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
9e008dda AJ |
988 | /* |
989 | * probably was aborted because content length exceeds one | |
990 | * of the maximum size limits. | |
991 | */ | |
35f26998 | 992 | abortTransaction("entry aborted after calling appendSuccessHeader()"); |
993 | return; | |
994 | } | |
995 | ||
a83c6ed6 | 996 | #if USE_ADAPTATION |
62e76326 | 997 | |
a83c6ed6 | 998 | if (adaptationAccessCheckPending) { |
e7ce227f | 999 | debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending"); |
253caccb | 1000 | return; |
1001 | } | |
62e76326 | 1002 | |
c4b7a5a9 | 1003 | #endif |
62e76326 | 1004 | |
253caccb | 1005 | if (flags.isdir) { |
0477a072 AJ |
1006 | if (!flags.listing) { |
1007 | flags.listing = 1; | |
1008 | listing.reset(); | |
1009 | } | |
253caccb | 1010 | parseListing(); |
0477a072 AJ |
1011 | maybeReadVirginBody(); |
1012 | return; | |
e1381638 AJ |
1013 | } else if (const int csize = data.readBuf->contentSize()) { |
1014 | writeReplyBody(data.readBuf->content(), csize); | |
1015 | debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf"); | |
1016 | data.readBuf->consume(csize); | |
1017 | } | |
253caccb | 1018 | |
3900307b | 1019 | entry->flush(); |
253caccb | 1020 | |
5f8252d2 | 1021 | maybeReadVirginBody(); |
090089c4 | 1022 | } |
1023 | ||
63be0a78 | 1024 | /** |
f381d699 AJ |
1025 | * Locates the FTP user:password login. |
1026 | * | |
1027 | * Highest to lowest priority: | |
1028 | * - Checks URL (ftp://user:pass@domain) | |
1029 | * - Authorization: Basic header | |
1030 | * - squid.conf anonymous-FTP settings (default: anonymous:Squid@). | |
1031 | * | |
1032 | * Special Case: A username-only may be provided in the URL and password in the HTTP headers. | |
1033 | * | |
4e2da309 AJ |
1034 | * TODO: we might be able to do something about locating username from other sources: |
1035 | * ie, external ACL user=* tag or ident lookup | |
1036 | * | |
f53969cc SM |
1037 | \retval 1 if we have everything needed to complete this request. |
1038 | \retval 0 if something is missing. | |
429fdbec | 1039 | */ |
f1a83a7d | 1040 | int |
5517260a | 1041 | Ftp::Gateway::checkAuth(const HttpHeader * req_hdr) |
429fdbec | 1042 | { |
f381d699 AJ |
1043 | /* default username */ |
1044 | xstrncpy(user, "anonymous", MAX_URL); | |
62e76326 | 1045 | |
9da6b594 | 1046 | #if HAVE_AUTH_MODULE_BASIC |
f381d699 | 1047 | /* Check HTTP Authorization: headers (better than defaults, but less than URL) */ |
789217a2 | 1048 | const SBuf auth(req_hdr->getAuth(Http::HdrType::AUTHORIZATION, "Basic")); |
92d6986d | 1049 | if (!auth.isEmpty()) { |
f381d699 | 1050 | flags.authenticated = 1; |
92d6986d | 1051 | loginParser(auth, false); |
f381d699 AJ |
1052 | } |
1053 | /* we fail with authorization-required error later IFF the FTP server requests it */ | |
9da6b594 | 1054 | #endif |
62e76326 | 1055 | |
f381d699 | 1056 | /* Test URL login syntax. Overrides any headers received. */ |
92d6986d | 1057 | loginParser(request->url.userInfo(), true); |
62e76326 | 1058 | |
f381d699 AJ |
1059 | /* name is missing. thats fatal. */ |
1060 | if (!user[0]) | |
1061 | fatal("FTP login parsing destroyed username info"); | |
62e76326 | 1062 | |
f381d699 AJ |
1063 | /* name + password == success */ |
1064 | if (password[0]) | |
1065 | return 1; | |
62e76326 | 1066 | |
f381d699 AJ |
1067 | /* Setup default FTP password settings */ |
1068 | /* this has to be done last so that we can have a no-password case above. */ | |
1069 | if (!password[0]) { | |
4e2da309 | 1070 | if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) { |
f381d699 | 1071 | xstrncpy(password, Config.Ftp.anon_user, MAX_URL); |
4e2da309 AJ |
1072 | flags.tried_auth_anonymous=1; |
1073 | return 1; | |
e1381638 | 1074 | } else if (!flags.tried_auth_nopass) { |
f381d699 | 1075 | xstrncpy(password, null_string, MAX_URL); |
4e2da309 AJ |
1076 | flags.tried_auth_nopass=1; |
1077 | return 1; | |
1078 | } | |
429fdbec | 1079 | } |
62e76326 | 1080 | |
f53969cc | 1081 | return 0; /* different username */ |
429fdbec | 1082 | } |
1083 | ||
f1a83a7d | 1084 | void |
5517260a | 1085 | Ftp::Gateway::checkUrlpath() |
13e80e5b | 1086 | { |
51b5dcf5 AJ |
1087 | static SBuf str_type_eq("type="); |
1088 | auto t = request->url.path().rfind(';'); | |
62e76326 | 1089 | |
51b5dcf5 AJ |
1090 | if (t != SBuf::npos) { |
1091 | auto filenameEnd = t-1; | |
1092 | if (request->url.path().substr(++t).cmp(str_type_eq, str_type_eq.length()) == 0) { | |
1093 | t += str_type_eq.length(); | |
1094 | typecode = (char)xtoupper(request->url.path()[t]); | |
1095 | request->url.path(request->url.path().substr(0,filenameEnd)); | |
62e76326 | 1096 | } |
dbfed404 | 1097 | } |
62e76326 | 1098 | |
51b5dcf5 | 1099 | int l = request->url.path().length(); |
13e80e5b | 1100 | /* check for null path */ |
62e76326 | 1101 | |
02922e76 | 1102 | if (!l) { |
f1a83a7d | 1103 | flags.isdir = 1; |
1104 | flags.root_dir = 1; | |
f53969cc | 1105 | flags.need_base_href = 1; /* Work around broken browsers */ |
51b5dcf5 | 1106 | } else if (!request->url.path().cmp("/%2f/")) { |
62e76326 | 1107 | /* UNIX root directory */ |
f1a83a7d | 1108 | flags.isdir = 1; |
1109 | flags.root_dir = 1; | |
51b5dcf5 | 1110 | } else if ((l >= 1) && (request->url.path()[l-1] == '/')) { |
62e76326 | 1111 | /* Directory URL, ending in / */ |
f1a83a7d | 1112 | flags.isdir = 1; |
62e76326 | 1113 | |
1114 | if (l == 1) | |
f1a83a7d | 1115 | flags.root_dir = 1; |
d5f80edc | 1116 | } else { |
f1a83a7d | 1117 | flags.dir_slash = 1; |
13e80e5b | 1118 | } |
1119 | } | |
1120 | ||
f1a83a7d | 1121 | void |
5517260a | 1122 | Ftp::Gateway::buildTitleUrl() |
3fdadc70 | 1123 | { |
f1a83a7d | 1124 | title_url = "ftp://"; |
62e76326 | 1125 | |
f1a83a7d | 1126 | if (strcmp(user, "anonymous")) { |
1127 | title_url.append(user); | |
1128 | title_url.append("@"); | |
06a5d871 | 1129 | } |
62e76326 | 1130 | |
5c51bffb | 1131 | SBuf authority = request->url.authority(request->url.getScheme() != AnyP::PROTO_FTP); |
62e76326 | 1132 | |
5c51bffb | 1133 | title_url.append(authority.rawContent(), authority.length()); |
51b5dcf5 | 1134 | title_url.append(request->url.path().rawContent(), request->url.path().length()); |
06a5d871 | 1135 | |
f1a83a7d | 1136 | base_href = "ftp://"; |
62e76326 | 1137 | |
f1a83a7d | 1138 | if (strcmp(user, "anonymous") != 0) { |
1139 | base_href.append(rfc1738_escape_part(user)); | |
62e76326 | 1140 | |
f1a83a7d | 1141 | if (password_url) { |
5c51bffb | 1142 | base_href.append(":"); |
f1a83a7d | 1143 | base_href.append(rfc1738_escape_part(password)); |
62e76326 | 1144 | } |
1145 | ||
f1a83a7d | 1146 | base_href.append("@"); |
06a5d871 | 1147 | } |
62e76326 | 1148 | |
5c51bffb | 1149 | base_href.append(authority.rawContent(), authority.length()); |
51b5dcf5 | 1150 | base_href.append(request->url.path().rawContent(), request->url.path().length()); |
f1a83a7d | 1151 | base_href.append("/"); |
3fdadc70 | 1152 | } |
1153 | ||
770f051d | 1154 | void |
5517260a | 1155 | Ftp::Gateway::start() |
6f0aab86 | 1156 | { |
f1a83a7d | 1157 | if (!checkAuth(&request->header)) { |
62e76326 | 1158 | /* create appropriate reply */ |
f381d699 | 1159 | HttpReply *reply = ftpAuthRequired(request, ftpRealm()); |
db237875 | 1160 | entry->replaceHttpReply(reply); |
5f8252d2 | 1161 | serverComplete(); |
62e76326 | 1162 | return; |
e381a13d | 1163 | } |
62e76326 | 1164 | |
f1a83a7d | 1165 | checkUrlpath(); |
1166 | buildTitleUrl(); | |
51b5dcf5 AJ |
1167 | debugs(9, 5, "FD " << ctrl.conn->fd << " : host=" << request->url.host() << |
1168 | ", path=" << request->url.path() << ", user=" << user << ", passwd=" << password); | |
6f0aab86 | 1169 | state = BEGIN; |
5517260a | 1170 | Ftp::Client::start(); |
dad0fe12 | 1171 | } |
1172 | ||
434a79b0 | 1173 | /* ====================================================================== */ |
4f310655 | 1174 | |
6f0aab86 | 1175 | void |
5517260a | 1176 | Ftp::Gateway::handleControlReply() |
4f310655 | 1177 | { |
5517260a | 1178 | Ftp::Client::handleControlReply(); |
434a79b0 DK |
1179 | if (ctrl.message == NULL) |
1180 | return; // didn't get complete reply yet | |
62e76326 | 1181 | |
434a79b0 DK |
1182 | /* Copy the message except for the last line to cwd_message to be |
1183 | * printed in error messages. | |
858783c9 | 1184 | */ |
434a79b0 DK |
1185 | for (wordlist *w = ctrl.message; w && w->next; w = w->next) { |
1186 | cwd_message.append('\n'); | |
1187 | cwd_message.append(w->key); | |
0477a072 | 1188 | } |
62e76326 | 1189 | |
6f0aab86 | 1190 | FTP_SM_FUNCS[state] (this); |
234967c9 | 1191 | } |
1192 | ||
3fdadc70 | 1193 | /* ====================================================================== */ |
1194 | ||
1195 | static void | |
5517260a | 1196 | ftpReadWelcome(Ftp::Gateway * ftpState) |
3fdadc70 | 1197 | { |
1198 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 1199 | debugs(9, 3, HERE); |
62e76326 | 1200 | |
e55f0142 | 1201 | if (ftpState->flags.pasv_only) |
95dc7ff4 | 1202 | ++ ftpState->login_att; |
62e76326 | 1203 | |
3fdadc70 | 1204 | if (code == 220) { |
62e76326 | 1205 | if (ftpState->ctrl.message) { |
1206 | if (strstr(ftpState->ctrl.message->key, "NetWare")) | |
1207 | ftpState->flags.skip_whitespace = 1; | |
1208 | } | |
1209 | ||
1210 | ftpSendUser(ftpState); | |
cdc33f35 | 1211 | } else if (code == 120) { |
62e76326 | 1212 | if (NULL != ftpState->ctrl.message) |
a689bd4e | 1213 | debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key); |
62e76326 | 1214 | |
1215 | return; | |
3fdadc70 | 1216 | } else { |
62e76326 | 1217 | ftpFail(ftpState); |
3fdadc70 | 1218 | } |
1219 | } | |
1220 | ||
f381d699 AJ |
1221 | /** |
1222 | * Translate FTP login failure into HTTP error | |
1223 | * this is an attmpt to get the 407 message to show up outside Squid. | |
1224 | * its NOT a general failure. But a correct FTP response type. | |
1225 | */ | |
1226 | void | |
5517260a | 1227 | Ftp::Gateway::loginFailed() |
f381d699 AJ |
1228 | { |
1229 | ErrorState *err = NULL; | |
f381d699 | 1230 | |
493e0ad0 AJ |
1231 | if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) { |
1232 | if (ctrl.replycode == 421 || ctrl.replycode == 426) { | |
1233 | // 421/426 - Service Overload - retry permitted. | |
955394ce | 1234 | err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request); |
493e0ad0 AJ |
1235 | } else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) { |
1236 | // 43x - Invalid or Credential Error - retry challenge required. | |
955394ce | 1237 | err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request); |
493e0ad0 AJ |
1238 | } else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) { |
1239 | // 53x - Credentials Missing - retry challenge required | |
1240 | if (password_url) // but they were in the URI! major fail. | |
955394ce | 1241 | err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request); |
f381d699 | 1242 | else |
955394ce | 1243 | err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request); |
f381d699 | 1244 | } |
b115733c AJ |
1245 | } |
1246 | ||
493e0ad0 | 1247 | if (!err) { |
f381d699 AJ |
1248 | ftpFail(this); |
1249 | return; | |
1250 | } | |
1251 | ||
cd4d5d45 VL |
1252 | failed(ERR_NONE, ctrl.replycode, err); |
1253 | // any other problems are general falures. | |
f381d699 | 1254 | |
f381d699 | 1255 | HttpReply *newrep = err->BuildHttpReply(); |
913524f0 | 1256 | delete err; |
9da6b594 AJ |
1257 | |
1258 | #if HAVE_AUTH_MODULE_BASIC | |
f381d699 AJ |
1259 | /* add Authenticate header */ |
1260 | newrep->header.putAuth("Basic", ftpRealm()); | |
9da6b594 | 1261 | #endif |
f381d699 AJ |
1262 | |
1263 | // add it to the store entry for response.... | |
1264 | entry->replaceHttpReply(newrep); | |
1265 | serverComplete(); | |
1266 | } | |
1267 | ||
1268 | const char * | |
5517260a | 1269 | Ftp::Gateway::ftpRealm() |
f381d699 AJ |
1270 | { |
1271 | static char realm[8192]; | |
1272 | ||
1273 | /* This request is not fully authenticated */ | |
1274 | if (!request) { | |
1275 | snprintf(realm, 8192, "FTP %s unknown", user); | |
5c51bffb AJ |
1276 | } else if (request->url.port() == 21) { |
1277 | snprintf(realm, 8192, "FTP %s %s", user, request->url.host()); | |
f381d699 | 1278 | } else { |
5c51bffb | 1279 | snprintf(realm, 8192, "FTP %s %s port %d", user, request->url.host(), request->url.port()); |
f381d699 AJ |
1280 | } |
1281 | return realm; | |
1282 | } | |
1283 | ||
969c39b9 | 1284 | static void |
5517260a | 1285 | ftpSendUser(Ftp::Gateway * ftpState) |
969c39b9 | 1286 | { |
a11382aa | 1287 | /* check the server control channel is still available */ |
9e008dda | 1288 | if (!ftpState || !ftpState->haveControlChannel("ftpSendUser")) |
a11382aa | 1289 | return; |
1290 | ||
969c39b9 | 1291 | if (ftpState->proxy_host != NULL) |
5c51bffb | 1292 | snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n", ftpState->user, ftpState->request->url.host()); |
969c39b9 | 1293 | else |
903198a7 | 1294 | snprintf(cbuf, CTRL_BUFLEN, "USER %s\r\n", ftpState->user); |
62e76326 | 1295 | |
2f47fadf | 1296 | ftpState->writeCommand(cbuf); |
62e76326 | 1297 | |
5517260a | 1298 | ftpState->state = Ftp::Client::SENT_USER; |
969c39b9 | 1299 | } |
1300 | ||
3fdadc70 | 1301 | static void |
5517260a | 1302 | ftpReadUser(Ftp::Gateway * ftpState) |
234967c9 | 1303 | { |
3fdadc70 | 1304 | int code = ftpState->ctrl.replycode; |
a689bd4e | 1305 | debugs(9, 3, HERE); |
62e76326 | 1306 | |
3fdadc70 | 1307 | if (code == 230) { |
62e76326 | 1308 | ftpReadPass(ftpState); |
3fdadc70 | 1309 | } else if (code == 331) { |
62e76326 | 1310 | ftpSendPass(ftpState); |
3fdadc70 | 1311 | } else { |
f381d699 | 1312 | ftpState->loginFailed(); |
3fdadc70 | 1313 | } |
1314 | } | |
1315 | ||
969c39b9 | 1316 | static void |
5517260a | 1317 | ftpSendPass(Ftp::Gateway * ftpState) |
969c39b9 | 1318 | { |
a11382aa | 1319 | /* check the server control channel is still available */ |
9e008dda | 1320 | if (!ftpState || !ftpState->haveControlChannel("ftpSendPass")) |
a11382aa | 1321 | return; |
1322 | ||
903198a7 | 1323 | snprintf(cbuf, CTRL_BUFLEN, "PASS %s\r\n", ftpState->password); |
2f47fadf | 1324 | ftpState->writeCommand(cbuf); |
5517260a | 1325 | ftpState->state = Ftp::Client::SENT_PASS; |
969c39b9 | 1326 | } |
1327 | ||
3fdadc70 | 1328 | static void |
5517260a | 1329 | ftpReadPass(Ftp::Gateway * ftpState) |
3fdadc70 | 1330 | { |
1331 | int code = ftpState->ctrl.replycode; | |
4d6c56a6 | 1332 | debugs(9, 3, HERE << "code=" << code); |
62e76326 | 1333 | |
3fdadc70 | 1334 | if (code == 230) { |
62e76326 | 1335 | ftpSendType(ftpState); |
3fdadc70 | 1336 | } else { |
f381d699 | 1337 | ftpState->loginFailed(); |
3fdadc70 | 1338 | } |
1339 | } | |
1340 | ||
969c39b9 | 1341 | static void |
5517260a | 1342 | ftpSendType(Ftp::Gateway * ftpState) |
969c39b9 | 1343 | { |
a11382aa | 1344 | /* check the server control channel is still available */ |
9e008dda | 1345 | if (!ftpState || !ftpState->haveControlChannel("ftpSendType")) |
a11382aa | 1346 | return; |
1347 | ||
9e242e02 | 1348 | /* |
1349 | * Ref section 3.2.2 of RFC 1738 | |
1350 | */ | |
51b5dcf5 | 1351 | char mode = ftpState->typecode; |
62e76326 | 1352 | |
b02bfc2d | 1353 | switch (mode) { |
62e76326 | 1354 | |
9e242e02 | 1355 | case 'D': |
62e76326 | 1356 | mode = 'A'; |
1357 | break; | |
1358 | ||
9e242e02 | 1359 | case 'A': |
62e76326 | 1360 | |
9e242e02 | 1361 | case 'I': |
62e76326 | 1362 | break; |
1363 | ||
9e242e02 | 1364 | default: |
62e76326 | 1365 | |
1366 | if (ftpState->flags.isdir) { | |
1367 | mode = 'A'; | |
1368 | } else { | |
51b5dcf5 AJ |
1369 | auto t = ftpState->request->url.path().rfind('/'); |
1370 | // XXX: performance regression, c_str() may reallocate | |
1371 | SBuf filename = ftpState->request->url.path().substr(t != SBuf::npos ? t + 1 : 0); | |
1372 | mode = mimeGetTransferMode(filename.c_str()); | |
62e76326 | 1373 | } |
1374 | ||
1375 | break; | |
9e242e02 | 1376 | } |
62e76326 | 1377 | |
969c39b9 | 1378 | if (mode == 'I') |
62e76326 | 1379 | ftpState->flags.binary = 1; |
cfbf5373 | 1380 | else |
62e76326 | 1381 | ftpState->flags.binary = 0; |
1382 | ||
903198a7 | 1383 | snprintf(cbuf, CTRL_BUFLEN, "TYPE %c\r\n", mode); |
62e76326 | 1384 | |
2f47fadf | 1385 | ftpState->writeCommand(cbuf); |
62e76326 | 1386 | |
5517260a | 1387 | ftpState->state = Ftp::Client::SENT_TYPE; |
969c39b9 | 1388 | } |
1389 | ||
3fdadc70 | 1390 | static void |
5517260a | 1391 | ftpReadType(Ftp::Gateway * ftpState) |
3fdadc70 | 1392 | { |
1393 | int code = ftpState->ctrl.replycode; | |
3fdadc70 | 1394 | char *path; |
6e5ae4a4 | 1395 | char *d, *p; |
6200d3d6 | 1396 | debugs(9, 3, HERE << "code=" << code); |
62e76326 | 1397 | |
3fdadc70 | 1398 | if (code == 200) { |
3f0e38d6 | 1399 | p = path = SBufToCstring(ftpState->request->url.path()); |
62e76326 | 1400 | |
1401 | if (*p == '/') | |
95dc7ff4 | 1402 | ++p; |
62e76326 | 1403 | |
1404 | while (*p) { | |
1405 | d = p; | |
1406 | p += strcspn(p, "/"); | |
1407 | ||
a38ec4b1 FC |
1408 | if (*p) { |
1409 | *p = '\0'; | |
1410 | ++p; | |
1411 | } | |
62e76326 | 1412 | |
1413 | rfc1738_unescape(d); | |
1414 | ||
80dc929d | 1415 | if (*d) |
1416 | wordlistAdd(&ftpState->pathcomps, d); | |
62e76326 | 1417 | } |
1418 | ||
1419 | xfree(path); | |
1420 | ||
1421 | if (ftpState->pathcomps) | |
1422 | ftpTraverseDirectory(ftpState); | |
1423 | else | |
1424 | ftpListDir(ftpState); | |
3fdadc70 | 1425 | } else { |
62e76326 | 1426 | ftpFail(ftpState); |
3fdadc70 | 1427 | } |
1428 | } | |
1429 | ||
1430 | static void | |
5517260a | 1431 | ftpTraverseDirectory(Ftp::Gateway * ftpState) |
3fdadc70 | 1432 | { |
a689bd4e | 1433 | debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>")); |
969c39b9 | 1434 | |
5627a633 | 1435 | safe_free(ftpState->dirpath); |
1436 | ftpState->dirpath = ftpState->filepath; | |
1437 | ftpState->filepath = NULL; | |
1438 | ||
969c39b9 | 1439 | /* Done? */ |
62e76326 | 1440 | |
969c39b9 | 1441 | if (ftpState->pathcomps == NULL) { |
a689bd4e | 1442 | debugs(9, 3, HERE << "the final component was a directory"); |
62e76326 | 1443 | ftpListDir(ftpState); |
1444 | return; | |
3fdadc70 | 1445 | } |
62e76326 | 1446 | |
969c39b9 | 1447 | /* Go to next path component */ |
0d225c05 | 1448 | ftpState->filepath = wordlistChopHead(& ftpState->pathcomps); |
62e76326 | 1449 | |
969c39b9 | 1450 | /* Check if we are to CWD or RETR */ |
e55f0142 | 1451 | if (ftpState->pathcomps != NULL || ftpState->flags.isdir) { |
62e76326 | 1452 | ftpSendCwd(ftpState); |
969c39b9 | 1453 | } else { |
a689bd4e | 1454 | debugs(9, 3, HERE << "final component is probably a file"); |
62e76326 | 1455 | ftpGetFile(ftpState); |
1456 | return; | |
969c39b9 | 1457 | } |
1458 | } | |
1459 | ||
1460 | static void | |
5517260a | 1461 | ftpSendCwd(Ftp::Gateway * ftpState) |
969c39b9 | 1462 | { |
9ededfe2 | 1463 | char *path = NULL; |
a11382aa | 1464 | |
1465 | /* check the server control channel is still available */ | |
9e008dda | 1466 | if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd")) |
a11382aa | 1467 | return; |
1468 | ||
a689bd4e | 1469 | debugs(9, 3, HERE); |
62e76326 | 1470 | |
9ededfe2 | 1471 | path = ftpState->filepath; |
1472 | ||
969c39b9 | 1473 | if (!strcmp(path, "..") || !strcmp(path, "/")) { |
62e76326 | 1474 | ftpState->flags.no_dotdot = 1; |
13e80e5b | 1475 | } else { |
62e76326 | 1476 | ftpState->flags.no_dotdot = 0; |
13e80e5b | 1477 | } |
62e76326 | 1478 | |
903198a7 | 1479 | snprintf(cbuf, CTRL_BUFLEN, "CWD %s\r\n", path); |
62e76326 | 1480 | |
2f47fadf | 1481 | ftpState->writeCommand(cbuf); |
62e76326 | 1482 | |
5517260a | 1483 | ftpState->state = Ftp::Client::SENT_CWD; |
3fdadc70 | 1484 | } |
77a30ebb | 1485 | |
3fdadc70 | 1486 | static void |
5517260a | 1487 | ftpReadCwd(Ftp::Gateway * ftpState) |
3fdadc70 | 1488 | { |
1489 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 1490 | debugs(9, 3, HERE); |
62e76326 | 1491 | |
3fdadc70 | 1492 | if (code >= 200 && code < 300) { |
62e76326 | 1493 | /* CWD OK */ |
6f0aab86 | 1494 | ftpState->unhack(); |
62e76326 | 1495 | |
0477a072 AJ |
1496 | /* Reset cwd_message to only include the last message */ |
1497 | ftpState->cwd_message.reset(""); | |
1498 | for (wordlist *w = ftpState->ctrl.message; w; w = w->next) { | |
1499 | ftpState->cwd_message.append(' '); | |
1500 | ftpState->cwd_message.append(w->key); | |
1501 | } | |
62e76326 | 1502 | ftpState->ctrl.message = NULL; |
1503 | ||
1504 | /* Continue to traverse the path */ | |
1505 | ftpTraverseDirectory(ftpState); | |
3fdadc70 | 1506 | } else { |
62e76326 | 1507 | /* CWD FAILED */ |
1508 | ||
1509 | if (!ftpState->flags.put) | |
1510 | ftpFail(ftpState); | |
1511 | else | |
1512 | ftpSendMkdir(ftpState); | |
c021888f | 1513 | } |
3fdadc70 | 1514 | } |
1515 | ||
54220df8 | 1516 | static void |
5517260a | 1517 | ftpSendMkdir(Ftp::Gateway * ftpState) |
54220df8 | 1518 | { |
9ededfe2 | 1519 | char *path = NULL; |
a11382aa | 1520 | |
1521 | /* check the server control channel is still available */ | |
9e008dda | 1522 | if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir")) |
a11382aa | 1523 | return; |
1524 | ||
9ededfe2 | 1525 | path = ftpState->filepath; |
a689bd4e | 1526 | debugs(9, 3, HERE << "with path=" << path); |
903198a7 | 1527 | snprintf(cbuf, CTRL_BUFLEN, "MKD %s\r\n", path); |
2f47fadf | 1528 | ftpState->writeCommand(cbuf); |
5517260a | 1529 | ftpState->state = Ftp::Client::SENT_MKDIR; |
54220df8 | 1530 | } |
1531 | ||
1532 | static void | |
5517260a | 1533 | ftpReadMkdir(Ftp::Gateway * ftpState) |
4162ee3b | 1534 | { |
1535 | char *path = ftpState->filepath; | |
1536 | int code = ftpState->ctrl.replycode; | |
1537 | ||
a689bd4e | 1538 | debugs(9, 3, HERE << "path " << path << ", code " << code); |
62e76326 | 1539 | |
f53969cc | 1540 | if (code == 257) { /* success */ |
62e76326 | 1541 | ftpSendCwd(ftpState); |
f53969cc | 1542 | } else if (code == 550) { /* dir exists */ |
62e76326 | 1543 | |
1544 | if (ftpState->flags.put_mkdir) { | |
1545 | ftpState->flags.put_mkdir = 1; | |
1546 | ftpSendCwd(ftpState); | |
1547 | } else | |
1548 | ftpSendReply(ftpState); | |
4162ee3b | 1549 | } else |
62e76326 | 1550 | ftpSendReply(ftpState); |
54220df8 | 1551 | } |
1552 | ||
dbfed404 | 1553 | static void |
5517260a | 1554 | ftpGetFile(Ftp::Gateway * ftpState) |
dbfed404 | 1555 | { |
1556 | assert(*ftpState->filepath != '\0'); | |
e55f0142 | 1557 | ftpState->flags.isdir = 0; |
dbfed404 | 1558 | ftpSendMdtm(ftpState); |
1559 | } | |
1560 | ||
1561 | static void | |
5517260a | 1562 | ftpListDir(Ftp::Gateway * ftpState) |
dbfed404 | 1563 | { |
d5f80edc | 1564 | if (ftpState->flags.dir_slash) { |
a689bd4e | 1565 | debugs(9, 3, HERE << "Directory path did not end in /"); |
62e76326 | 1566 | ftpState->title_url.append("/"); |
1567 | ftpState->flags.isdir = 1; | |
dbfed404 | 1568 | } |
62e76326 | 1569 | |
a689bd4e | 1570 | ftpSendPassive(ftpState); |
dbfed404 | 1571 | } |
1572 | ||
969c39b9 | 1573 | static void |
5517260a | 1574 | ftpSendMdtm(Ftp::Gateway * ftpState) |
969c39b9 | 1575 | { |
a11382aa | 1576 | /* check the server control channel is still available */ |
9e008dda | 1577 | if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm")) |
a11382aa | 1578 | return; |
1579 | ||
969c39b9 | 1580 | assert(*ftpState->filepath != '\0'); |
903198a7 | 1581 | snprintf(cbuf, CTRL_BUFLEN, "MDTM %s\r\n", ftpState->filepath); |
2f47fadf | 1582 | ftpState->writeCommand(cbuf); |
5517260a | 1583 | ftpState->state = Ftp::Client::SENT_MDTM; |
969c39b9 | 1584 | } |
1585 | ||
3fdadc70 | 1586 | static void |
5517260a | 1587 | ftpReadMdtm(Ftp::Gateway * ftpState) |
3fdadc70 | 1588 | { |
1589 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 1590 | debugs(9, 3, HERE); |
62e76326 | 1591 | |
3fdadc70 | 1592 | if (code == 213) { |
62e76326 | 1593 | ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply); |
6f0aab86 | 1594 | ftpState->unhack(); |
3fdadc70 | 1595 | } else if (code < 0) { |
62e76326 | 1596 | ftpFail(ftpState); |
9e008dda | 1597 | return; |
77a30ebb | 1598 | } |
62e76326 | 1599 | |
969c39b9 | 1600 | ftpSendSize(ftpState); |
1601 | } | |
1602 | ||
1603 | static void | |
5517260a | 1604 | ftpSendSize(Ftp::Gateway * ftpState) |
969c39b9 | 1605 | { |
a11382aa | 1606 | /* check the server control channel is still available */ |
9e008dda | 1607 | if (!ftpState || !ftpState->haveControlChannel("ftpSendSize")) |
a11382aa | 1608 | return; |
1609 | ||
969c39b9 | 1610 | /* Only send SIZE for binary transfers. The returned size |
1611 | * is useless on ASCII transfers */ | |
62e76326 | 1612 | |
e55f0142 | 1613 | if (ftpState->flags.binary) { |
62e76326 | 1614 | assert(ftpState->filepath != NULL); |
1615 | assert(*ftpState->filepath != '\0'); | |
903198a7 | 1616 | snprintf(cbuf, CTRL_BUFLEN, "SIZE %s\r\n", ftpState->filepath); |
2f47fadf | 1617 | ftpState->writeCommand(cbuf); |
5517260a | 1618 | ftpState->state = Ftp::Client::SENT_SIZE; |
969c39b9 | 1619 | } else |
62e76326 | 1620 | /* Skip to next state no non-binary transfers */ |
a689bd4e | 1621 | ftpSendPassive(ftpState); |
3fdadc70 | 1622 | } |
1623 | ||
1624 | static void | |
5517260a | 1625 | ftpReadSize(Ftp::Gateway * ftpState) |
3fdadc70 | 1626 | { |
1627 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 1628 | debugs(9, 3, HERE); |
62e76326 | 1629 | |
3fdadc70 | 1630 | if (code == 213) { |
6f0aab86 | 1631 | ftpState->unhack(); |
47f6e231 | 1632 | ftpState->theSize = strtoll(ftpState->ctrl.last_reply, NULL, 10); |
62e76326 | 1633 | |
47f6e231 | 1634 | if (ftpState->theSize == 0) { |
a689bd4e | 1635 | debugs(9, 2, "SIZE reported " << |
9e008dda | 1636 | ftpState->ctrl.last_reply << " on " << |
d53b3f6d | 1637 | ftpState->title_url); |
47f6e231 | 1638 | ftpState->theSize = -1; |
62e76326 | 1639 | } |
3fdadc70 | 1640 | } else if (code < 0) { |
62e76326 | 1641 | ftpFail(ftpState); |
9e008dda | 1642 | return; |
3fdadc70 | 1643 | } |
62e76326 | 1644 | |
a689bd4e | 1645 | ftpSendPassive(ftpState); |
3fdadc70 | 1646 | } |
1647 | ||
cc192b50 | 1648 | static void |
5517260a | 1649 | ftpReadEPSV(Ftp::Gateway* ftpState) |
cc192b50 | 1650 | { |
000e664b AR |
1651 | Ip::Address srvAddr; // unused |
1652 | if (ftpState->handleEpsvReply(srvAddr)) { | |
1653 | if (ftpState->ctrl.message == NULL) | |
1654 | return; // didn't get complete reply yet | |
a689bd4e | 1655 | |
000e664b | 1656 | ftpState->connectDataChannel(); |
a689bd4e | 1657 | } |
a689bd4e | 1658 | } |
cc192b50 | 1659 | |
3cc0f4e7 | 1660 | /** Send Passive connection request. |
a689bd4e | 1661 | * Default method is to use modern EPSV request. |
1662 | * The failover mechanism should check for previous state and re-call with alternates on failure. | |
1663 | */ | |
3fdadc70 | 1664 | static void |
5517260a | 1665 | ftpSendPassive(Ftp::Gateway * ftpState) |
3fdadc70 | 1666 | { |
a689bd4e | 1667 | /** Checks the server control channel is still available before running. */ |
9e008dda | 1668 | if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive")) |
a11382aa | 1669 | return; |
1670 | ||
a689bd4e | 1671 | debugs(9, 3, HERE); |
1672 | ||
a689bd4e | 1673 | /** \par |
5517260a | 1674 | * Checks for 'HEAD' method request and passes off for special handling by Ftp::Gateway::processHeadResponse(). */ |
c2a7cefd | 1675 | if (ftpState->request->method == Http::METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) { |
063a47b5 | 1676 | ftpState->processHeadResponse(); // may call serverComplete |
62e76326 | 1677 | return; |
7497ceef | 1678 | } |
62e76326 | 1679 | |
f8e4867b AR |
1680 | if (ftpState->sendPassive()) { |
1681 | // SENT_EPSV_ALL blocks other non-EPSV connections being attempted | |
5517260a | 1682 | if (ftpState->state == Ftp::Client::SENT_EPSV_ALL) |
a689bd4e | 1683 | ftpState->flags.epsv_all_sent = true; |
a689bd4e | 1684 | } |
3fdadc70 | 1685 | } |
1686 | ||
063a47b5 | 1687 | void |
5517260a | 1688 | Ftp::Gateway::processHeadResponse() |
063a47b5 | 1689 | { |
1690 | debugs(9, 5, HERE << "handling HEAD response"); | |
1691 | ftpSendQuit(this); | |
1692 | appendSuccessHeader(); | |
1693 | ||
1694 | /* | |
1695 | * On rare occasions I'm seeing the entry get aborted after | |
434a79b0 | 1696 | * readControlReply() and before here, probably when |
063a47b5 | 1697 | * trying to write to the client. |
1698 | */ | |
1699 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { | |
1700 | abortTransaction("entry aborted while processing HEAD"); | |
1701 | return; | |
1702 | } | |
1703 | ||
a83c6ed6 AR |
1704 | #if USE_ADAPTATION |
1705 | if (adaptationAccessCheckPending) { | |
1706 | debugs(9,3, HERE << "returning due to adaptationAccessCheckPending"); | |
063a47b5 | 1707 | return; |
1708 | } | |
1709 | #endif | |
1710 | ||
1711 | // processReplyBody calls serverComplete() since there is no body | |
9e008dda | 1712 | processReplyBody(); |
063a47b5 | 1713 | } |
1714 | ||
3fdadc70 | 1715 | static void |
5517260a | 1716 | ftpReadPasv(Ftp::Gateway * ftpState) |
3fdadc70 | 1717 | { |
73950ceb AR |
1718 | Ip::Address srvAddr; // unused |
1719 | if (ftpState->handlePasvReply(srvAddr)) | |
434a79b0 DK |
1720 | ftpState->connectDataChannel(); |
1721 | else { | |
cc192b50 | 1722 | ftpSendEPRT(ftpState); |
62e76326 | 1723 | return; |
7f74df3a | 1724 | } |
3fdadc70 | 1725 | } |
1726 | ||
6f0aab86 | 1727 | void |
43446566 | 1728 | Ftp::Gateway::dataChannelConnected(const CommConnectCbParams &io) |
3fdadc70 | 1729 | { |
a689bd4e | 1730 | debugs(9, 3, HERE); |
434a79b0 | 1731 | data.opener = NULL; |
62e76326 | 1732 | |
43446566 | 1733 | if (io.flag != Comm::OK) { |
6200d3d6 | 1734 | debugs(9, 2, HERE << "Failed to connect. Retrying via another method."); |
6200d3d6 AJ |
1735 | |
1736 | // ABORT on timeouts. server may be waiting on a broken TCP link. | |
43446566 | 1737 | if (io.xerrno == Comm::TIMEOUT) |
434a79b0 | 1738 | writeCommand("ABOR"); |
6200d3d6 AJ |
1739 | |
1740 | // try another connection attempt with some other method | |
434a79b0 | 1741 | ftpSendPassive(this); |
62e76326 | 1742 | return; |
3fdadc70 | 1743 | } |
62e76326 | 1744 | |
43446566 | 1745 | data.opened(io.conn, dataCloser()); |
434a79b0 | 1746 | ftpRestOrList(this); |
3fdadc70 | 1747 | } |
1748 | ||
903198a7 | 1749 | static void |
5517260a | 1750 | ftpOpenListenSocket(Ftp::Gateway * ftpState, int fallback) |
cdc33f35 | 1751 | { |
04f55905 | 1752 | /// Close old data channels, if any. We may open a new one below. |
b2e3fa62 AJ |
1753 | if (ftpState->data.conn != NULL) { |
1754 | if ((ftpState->data.conn->flags & COMM_REUSEADDR)) | |
1755 | // NP: in fact it points to the control channel. just clear it. | |
1756 | ftpState->data.clear(); | |
1757 | else | |
1758 | ftpState->data.close(); | |
1759 | } | |
a9de4717 | 1760 | safe_free(ftpState->data.host); |
62e76326 | 1761 | |
4f310655 | 1762 | /* |
1763 | * Set up a listen socket on the same local address as the | |
1764 | * control connection. | |
1765 | */ | |
8bbb16e3 AJ |
1766 | Comm::ConnectionPointer temp = new Comm::Connection; |
1767 | temp->local = ftpState->ctrl.conn->local; | |
62e76326 | 1768 | |
4f310655 | 1769 | /* |
1770 | * REUSEADDR is needed in fallback mode, since the same port is | |
1771 | * used for both control and data. | |
cdc33f35 | 1772 | */ |
1773 | if (fallback) { | |
6200d3d6 | 1774 | int on = 1; |
6cf3e435 FC |
1775 | errno = 0; |
1776 | if (setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR, | |
203fdebd | 1777 | (char *) &on, sizeof(on)) == -1) { |
6cf3e435 FC |
1778 | // SO_REUSEADDR is only an optimization, no need to be verbose about error |
1779 | debugs(9, 4, "setsockopt failed: " << xstrerror()); | |
1780 | } | |
6b679a01 | 1781 | ftpState->ctrl.conn->flags |= COMM_REUSEADDR; |
8bbb16e3 | 1782 | temp->flags |= COMM_REUSEADDR; |
9e008dda | 1783 | } else { |
a689bd4e | 1784 | /* if not running in fallback mode a new port needs to be retrieved */ |
4dd643d5 | 1785 | temp->local.port(0); |
cdc33f35 | 1786 | } |
62e76326 | 1787 | |
434a79b0 | 1788 | ftpState->listenForDataChannel(temp); |
cdc33f35 | 1789 | } |
1790 | ||
3fdadc70 | 1791 | static void |
5517260a | 1792 | ftpSendPORT(Ftp::Gateway * ftpState) |
3fdadc70 | 1793 | { |
a11382aa | 1794 | /* check the server control channel is still available */ |
9e008dda | 1795 | if (!ftpState || !ftpState->haveControlChannel("ftpSendPort")) |
a11382aa | 1796 | return; |
1797 | ||
9e008dda | 1798 | if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) { |
a689bd4e | 1799 | debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent."); |
1800 | return; | |
1801 | } | |
1802 | ||
1803 | debugs(9, 3, HERE); | |
e55f0142 | 1804 | ftpState->flags.pasv_supported = 0; |
903198a7 | 1805 | ftpOpenListenSocket(ftpState, 0); |
6b679a01 | 1806 | |
00406b24 | 1807 | if (!Comm::IsConnOpen(ftpState->data.listenConn)) { |
4dd643d5 | 1808 | if ( ftpState->data.listenConn != NULL && !ftpState->data.listenConn->local.isIPv4() ) { |
6b679a01 AJ |
1809 | /* non-IPv4 CANNOT send PORT command. */ |
1810 | /* we got here by attempting and failing an EPRT */ | |
1811 | /* using the same reply code should simulate a PORT failure */ | |
1812 | ftpReadPORT(ftpState); | |
1813 | return; | |
1814 | } | |
bf8fe701 | 1815 | |
62e76326 | 1816 | /* XXX Need to set error message */ |
1817 | ftpFail(ftpState); | |
1818 | return; | |
cdc33f35 | 1819 | } |
62e76326 | 1820 | |
6200d3d6 AJ |
1821 | // pull out the internal IP address bytes to send in PORT command... |
1822 | // source them from the listen_conn->local | |
cc192b50 | 1823 | |
6200d3d6 | 1824 | struct addrinfo *AI = NULL; |
4dd643d5 | 1825 | ftpState->data.listenConn->local.getAddrInfo(AI, AF_INET); |
6200d3d6 AJ |
1826 | unsigned char *addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr; |
1827 | unsigned char *portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port; | |
903198a7 | 1828 | snprintf(cbuf, CTRL_BUFLEN, "PORT %d,%d,%d,%d,%d,%d\r\n", |
62e76326 | 1829 | addrptr[0], addrptr[1], addrptr[2], addrptr[3], |
1830 | portptr[0], portptr[1]); | |
2f47fadf | 1831 | ftpState->writeCommand(cbuf); |
5517260a | 1832 | ftpState->state = Ftp::Client::SENT_PORT; |
cc192b50 | 1833 | |
851614a8 | 1834 | Ip::Address::FreeAddr(AI); |
3fdadc70 | 1835 | } |
1836 | ||
1837 | static void | |
5517260a | 1838 | ftpReadPORT(Ftp::Gateway * ftpState) |
3fdadc70 | 1839 | { |
cdc33f35 | 1840 | int code = ftpState->ctrl.replycode; |
a689bd4e | 1841 | debugs(9, 3, HERE); |
62e76326 | 1842 | |
cdc33f35 | 1843 | if (code != 200) { |
62e76326 | 1844 | /* Fall back on using the same port as the control connection */ |
bf8fe701 | 1845 | debugs(9, 3, "PORT not supported by remote end"); |
62e76326 | 1846 | ftpOpenListenSocket(ftpState, 1); |
cdc33f35 | 1847 | } |
62e76326 | 1848 | |
cdc33f35 | 1849 | ftpRestOrList(ftpState); |
1850 | } | |
1851 | ||
cc192b50 | 1852 | static void |
5517260a | 1853 | ftpSendEPRT(Ftp::Gateway * ftpState) |
cc192b50 | 1854 | { |
9e008dda | 1855 | if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) { |
a689bd4e | 1856 | debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent."); |
1857 | return; | |
1858 | } | |
1859 | ||
63ee5443 AJ |
1860 | if (!Config.Ftp.eprt) { |
1861 | /* Disabled. Switch immediately to attempting old PORT command. */ | |
1862 | debugs(9, 3, "EPRT disabled by local administrator"); | |
1863 | ftpSendPORT(ftpState); | |
1864 | return; | |
1865 | } | |
1866 | ||
a689bd4e | 1867 | debugs(9, 3, HERE); |
cc192b50 | 1868 | ftpState->flags.pasv_supported = 0; |
903198a7 AJ |
1869 | |
1870 | ftpOpenListenSocket(ftpState, 0); | |
8bbb16e3 AJ |
1871 | debugs(9, 3, "Listening for FTP data connection with FD " << ftpState->data.conn); |
1872 | if (!Comm::IsConnOpen(ftpState->data.conn)) { | |
903198a7 AJ |
1873 | /* XXX Need to set error message */ |
1874 | ftpFail(ftpState); | |
1875 | return; | |
1876 | } | |
cc192b50 | 1877 | |
8bbb16e3 | 1878 | char buf[MAX_IPSTRLEN]; |
cc192b50 | 1879 | |
1880 | /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */ | |
1881 | /* Which can be used by EITHER protocol. */ | |
903198a7 | 1882 | snprintf(cbuf, CTRL_BUFLEN, "EPRT |%d|%s|%d|\r\n", |
4dd643d5 AJ |
1883 | ( ftpState->data.listenConn->local.isIPv6() ? 2 : 1 ), |
1884 | ftpState->data.listenConn->local.toStr(buf,MAX_IPSTRLEN), | |
1885 | ftpState->data.listenConn->local.port() ); | |
cc192b50 | 1886 | |
1887 | ftpState->writeCommand(cbuf); | |
5517260a | 1888 | ftpState->state = Ftp::Client::SENT_EPRT; |
cc192b50 | 1889 | } |
1890 | ||
1891 | static void | |
5517260a | 1892 | ftpReadEPRT(Ftp::Gateway * ftpState) |
cc192b50 | 1893 | { |
1894 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 1895 | debugs(9, 3, HERE); |
cc192b50 | 1896 | |
1897 | if (code != 200) { | |
1898 | /* Failover to attempting old PORT command. */ | |
1899 | debugs(9, 3, "EPRT not supported by remote end"); | |
1900 | ftpSendPORT(ftpState); | |
1901 | return; | |
1902 | } | |
1903 | ||
1904 | ftpRestOrList(ftpState); | |
1905 | } | |
1906 | ||
3cc0f4e7 | 1907 | /** "read" handler to accept FTP data connections. |
a689bd4e | 1908 | * |
63be0a78 | 1909 | \param io comm accept(2) callback parameters |
a689bd4e | 1910 | */ |
6200d3d6 | 1911 | void |
5517260a | 1912 | Ftp::Gateway::ftpAcceptDataConnection(const CommAcceptCbParams &io) |
cdc33f35 | 1913 | { |
cbff89ba | 1914 | debugs(9, 3, HERE); |
cdc33f35 | 1915 | |
dc56a9b1 | 1916 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
1917 | abortTransaction("entry aborted when accepting data conn"); | |
00406b24 AJ |
1918 | data.listenConn->close(); |
1919 | data.listenConn = NULL; | |
62e76326 | 1920 | return; |
b044a7eb | 1921 | } |
c4b7a5a9 | 1922 | |
c8407295 | 1923 | if (io.flag != Comm::OK) { |
00406b24 AJ |
1924 | data.listenConn->close(); |
1925 | data.listenConn = NULL; | |
1926 | debugs(9, DBG_IMPORTANT, "FTP AcceptDataConnection: " << io.conn << ": " << xstrerr(io.xerrno)); | |
1927 | /** \todo Need to send error message on control channel*/ | |
903198a7 AJ |
1928 | ftpFail(this); |
1929 | return; | |
1930 | } | |
1931 | ||
00406b24 AJ |
1932 | /* data listening conn is no longer even open. abort. */ |
1933 | if (!Comm::IsConnOpen(data.listenConn)) { | |
1934 | data.listenConn = NULL; // ensure that it's cleared and not just closed. | |
1935 | return; | |
1936 | } | |
1937 | ||
cbff89ba | 1938 | /* data listening conn is no longer even open. abort. */ |
8bbb16e3 | 1939 | if (!Comm::IsConnOpen(data.conn)) { |
cbff89ba AJ |
1940 | data.clear(); // ensure that it's cleared and not just closed. |
1941 | return; | |
1942 | } | |
1943 | ||
a689bd4e | 1944 | /** \par |
1945 | * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being | |
1946 | * made by the remote client which is connected to the FTP control socket. | |
00406b24 | 1947 | * Or the one which we were told to listen for by control channel messages (may differ under NAT). |
a689bd4e | 1948 | * This prevents third-party hacks, but also third-party load balancing handshakes. |
1949 | */ | |
00c5afca | 1950 | if (Config.Ftp.sanitycheck) { |
6200d3d6 | 1951 | // accept if either our data or ctrl connection is talking to this remote peer. |
8bbb16e3 | 1952 | if (data.conn->remote != io.conn->remote && ctrl.conn->remote != io.conn->remote) { |
9e008dda AJ |
1953 | debugs(9, DBG_IMPORTANT, |
1954 | "FTP data connection from unexpected server (" << | |
00406b24 | 1955 | io.conn->remote << "), expecting " << |
8bbb16e3 | 1956 | data.conn->remote << " or " << ctrl.conn->remote); |
dc56a9b1 | 1957 | |
cbff89ba | 1958 | /* close the bad sources connection down ASAP. */ |
8bbb16e3 | 1959 | io.conn->close(); |
04f55905 | 1960 | |
00406b24 | 1961 | /* drop the bad connection (io) by ignoring the attempt. */ |
62e76326 | 1962 | return; |
1963 | } | |
00c5afca | 1964 | } |
62e76326 | 1965 | |
c8407295 | 1966 | /** On Comm::OK start using the accepted data socket and discard the temporary listen socket. */ |
94b88585 | 1967 | data.close(); |
00406b24 | 1968 | data.opened(io.conn, dataCloser()); |
73950ceb | 1969 | data.addr(io.conn->remote); |
a689bd4e | 1970 | |
903198a7 | 1971 | debugs(9, 3, HERE << "Connected data socket on " << |
00406b24 | 1972 | io.conn << ". FD table says: " << |
6b679a01 AJ |
1973 | "ctrl-peer= " << fd_table[ctrl.conn->fd].ipaddr << ", " << |
1974 | "data-peer= " << fd_table[data.conn->fd].ipaddr); | |
a689bd4e | 1975 | |
903198a7 AJ |
1976 | assert(haveControlChannel("ftpAcceptDataConnection")); |
1977 | assert(ctrl.message == NULL); | |
a689bd4e | 1978 | |
903198a7 | 1979 | // Ctrl channel operations will determine what happens to this data connection |
3fdadc70 | 1980 | } |
1981 | ||
1982 | static void | |
5517260a | 1983 | ftpRestOrList(Ftp::Gateway * ftpState) |
3fdadc70 | 1984 | { |
a689bd4e | 1985 | debugs(9, 3, HERE); |
62e76326 | 1986 | |
94439e4e | 1987 | if (ftpState->typecode == 'D') { |
62e76326 | 1988 | ftpState->flags.isdir = 1; |
62e76326 | 1989 | |
1990 | if (ftpState->flags.put) { | |
f53969cc | 1991 | ftpSendMkdir(ftpState); /* PUT name;type=d */ |
62e76326 | 1992 | } else { |
f53969cc | 1993 | ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */ |
62e76326 | 1994 | } |
94439e4e | 1995 | } else if (ftpState->flags.put) { |
62e76326 | 1996 | ftpSendStor(ftpState); |
e55f0142 | 1997 | } else if (ftpState->flags.isdir) |
62e76326 | 1998 | ftpSendList(ftpState); |
6f0aab86 | 1999 | else if (ftpState->restartable()) |
62e76326 | 2000 | ftpSendRest(ftpState); |
969c39b9 | 2001 | else |
62e76326 | 2002 | ftpSendRetr(ftpState); |
969c39b9 | 2003 | } |
2004 | ||
54220df8 | 2005 | static void |
5517260a | 2006 | ftpSendStor(Ftp::Gateway * ftpState) |
54220df8 | 2007 | { |
a11382aa | 2008 | /* check the server control channel is still available */ |
9e008dda | 2009 | if (!ftpState || !ftpState->haveControlChannel("ftpSendStor")) |
a11382aa | 2010 | return; |
2011 | ||
a689bd4e | 2012 | debugs(9, 3, HERE); |
2013 | ||
9bc73deb | 2014 | if (ftpState->filepath != NULL) { |
62e76326 | 2015 | /* Plain file upload */ |
903198a7 | 2016 | snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath); |
2f47fadf | 2017 | ftpState->writeCommand(cbuf); |
5517260a | 2018 | ftpState->state = Ftp::Client::SENT_STOR; |
789217a2 | 2019 | } else if (ftpState->request->header.getInt64(Http::HdrType::CONTENT_LENGTH) > 0) { |
62e76326 | 2020 | /* File upload without a filename. use STOU to generate one */ |
903198a7 | 2021 | snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n"); |
2f47fadf | 2022 | ftpState->writeCommand(cbuf); |
5517260a | 2023 | ftpState->state = Ftp::Client::SENT_STOR; |
9bc73deb | 2024 | } else { |
62e76326 | 2025 | /* No file to transfer. Only create directories if needed */ |
2026 | ftpSendReply(ftpState); | |
9bc73deb | 2027 | } |
54220df8 | 2028 | } |
2029 | ||
63be0a78 | 2030 | /// \deprecated use ftpState->readStor() instead. |
54220df8 | 2031 | static void |
5517260a | 2032 | ftpReadStor(Ftp::Gateway * ftpState) |
54220df8 | 2033 | { |
5f8252d2 | 2034 | ftpState->readStor(); |
2035 | } | |
2036 | ||
5517260a | 2037 | void Ftp::Gateway::readStor() |
9e008dda | 2038 | { |
5f8252d2 | 2039 | int code = ctrl.replycode; |
a689bd4e | 2040 | debugs(9, 3, HERE); |
62e76326 | 2041 | |
d890b809 | 2042 | if (code == 125 || (code == 150 && Comm::IsConnOpen(data.conn))) { |
ccc927f9 AR |
2043 | if (!originalRequest()->body_pipe) { |
2044 | debugs(9, 3, "zero-size STOR?"); | |
2045 | state = WRITING_DATA; // make ftpWriteTransferDone() responsible | |
2046 | dataComplete(); // XXX: keep in sync with doneSendingRequestBody() | |
2047 | return; | |
2048 | } | |
2049 | ||
123ec4de | 2050 | if (!startRequestBodyFlow()) { // register to receive body data |
5f8252d2 | 2051 | ftpFail(this); |
2052 | return; | |
2053 | } | |
2054 | ||
d890b809 | 2055 | /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */ |
a689bd4e | 2056 | debugs(9, 3, HERE << "starting data transfer"); |
d890b809 | 2057 | switchTimeoutToDataChannel(); |
5f8252d2 | 2058 | sendMoreRequestBody(); |
d890b809 | 2059 | fwd->dontRetry(true); // dont permit re-trying if the body was sent. |
5f8252d2 | 2060 | state = WRITING_DATA; |
a689bd4e | 2061 | debugs(9, 3, HERE << "writing data channel"); |
9bc73deb | 2062 | } else if (code == 150) { |
d890b809 | 2063 | /* When client code is 150 with no data channel, Accept data channel. */ |
dc56a9b1 | 2064 | debugs(9, 3, "ftpReadStor: accepting data channel"); |
434a79b0 | 2065 | listenForDataChannel(data.conn); |
54220df8 | 2066 | } else { |
a689bd4e | 2067 | debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code); |
5f8252d2 | 2068 | ftpFail(this); |
54220df8 | 2069 | } |
2070 | } | |
2071 | ||
969c39b9 | 2072 | static void |
5517260a | 2073 | ftpSendRest(Ftp::Gateway * ftpState) |
969c39b9 | 2074 | { |
a11382aa | 2075 | /* check the server control channel is still available */ |
9e008dda | 2076 | if (!ftpState || !ftpState->haveControlChannel("ftpSendRest")) |
a11382aa | 2077 | return; |
2078 | ||
a689bd4e | 2079 | debugs(9, 3, HERE); |
2080 | ||
c91ca3ce | 2081 | snprintf(cbuf, CTRL_BUFLEN, "REST %" PRId64 "\r\n", ftpState->restart_offset); |
2f47fadf | 2082 | ftpState->writeCommand(cbuf); |
5517260a | 2083 | ftpState->state = Ftp::Client::SENT_REST; |
3fdadc70 | 2084 | } |
2085 | ||
6f0aab86 | 2086 | int |
5517260a | 2087 | Ftp::Gateway::restartable() |
cfbf5373 | 2088 | { |
6f0aab86 | 2089 | if (restart_offset > 0) |
62e76326 | 2090 | return 1; |
2091 | ||
6f0aab86 | 2092 | if (!request->range) |
62e76326 | 2093 | return 0; |
2094 | ||
6f0aab86 | 2095 | if (!flags.binary) |
62e76326 | 2096 | return 0; |
2097 | ||
47f6e231 | 2098 | if (theSize <= 0) |
62e76326 | 2099 | return 0; |
cfbf5373 | 2100 | |
47f6e231 | 2101 | int64_t desired_offset = request->range->lowestOffset(theSize); |
62e76326 | 2102 | |
b0e52cb9 | 2103 | if (desired_offset <= 0) |
62e76326 | 2104 | return 0; |
2105 | ||
47f6e231 | 2106 | if (desired_offset >= theSize) |
9e008dda | 2107 | return 0; |
b0e52cb9 | 2108 | |
2109 | restart_offset = desired_offset; | |
cfbf5373 | 2110 | return 1; |
2111 | } | |
2112 | ||
3fdadc70 | 2113 | static void |
5517260a | 2114 | ftpReadRest(Ftp::Gateway * ftpState) |
3fdadc70 | 2115 | { |
2116 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 2117 | debugs(9, 3, HERE); |
3fdadc70 | 2118 | assert(ftpState->restart_offset > 0); |
62e76326 | 2119 | |
3fdadc70 | 2120 | if (code == 350) { |
04f7fd38 | 2121 | ftpState->setCurrentOffset(ftpState->restart_offset); |
62e76326 | 2122 | ftpSendRetr(ftpState); |
3fdadc70 | 2123 | } else if (code > 0) { |
a689bd4e | 2124 | debugs(9, 3, HERE << "REST not supported"); |
62e76326 | 2125 | ftpState->flags.rest_supported = 0; |
2126 | ftpSendRetr(ftpState); | |
3fdadc70 | 2127 | } else { |
62e76326 | 2128 | ftpFail(ftpState); |
3fdadc70 | 2129 | } |
2130 | } | |
2131 | ||
969c39b9 | 2132 | static void |
5517260a | 2133 | ftpSendList(Ftp::Gateway * ftpState) |
969c39b9 | 2134 | { |
a11382aa | 2135 | /* check the server control channel is still available */ |
9e008dda | 2136 | if (!ftpState || !ftpState->haveControlChannel("ftpSendList")) |
a11382aa | 2137 | return; |
2138 | ||
a689bd4e | 2139 | debugs(9, 3, HERE); |
2140 | ||
dbfed404 | 2141 | if (ftpState->filepath) { |
903198a7 | 2142 | snprintf(cbuf, CTRL_BUFLEN, "LIST %s\r\n", ftpState->filepath); |
dbfed404 | 2143 | } else { |
903198a7 | 2144 | snprintf(cbuf, CTRL_BUFLEN, "LIST\r\n"); |
dbfed404 | 2145 | } |
62e76326 | 2146 | |
2f47fadf | 2147 | ftpState->writeCommand(cbuf); |
5517260a | 2148 | ftpState->state = Ftp::Client::SENT_LIST; |
969c39b9 | 2149 | } |
2150 | ||
dbfed404 | 2151 | static void |
5517260a | 2152 | ftpSendNlst(Ftp::Gateway * ftpState) |
dbfed404 | 2153 | { |
a11382aa | 2154 | /* check the server control channel is still available */ |
9e008dda | 2155 | if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst")) |
a11382aa | 2156 | return; |
2157 | ||
a689bd4e | 2158 | debugs(9, 3, HERE); |
2159 | ||
e55f0142 | 2160 | ftpState->flags.tried_nlst = 1; |
62e76326 | 2161 | |
dbfed404 | 2162 | if (ftpState->filepath) { |
903198a7 | 2163 | snprintf(cbuf, CTRL_BUFLEN, "NLST %s\r\n", ftpState->filepath); |
dbfed404 | 2164 | } else { |
903198a7 | 2165 | snprintf(cbuf, CTRL_BUFLEN, "NLST\r\n"); |
dbfed404 | 2166 | } |
62e76326 | 2167 | |
2f47fadf | 2168 | ftpState->writeCommand(cbuf); |
5517260a | 2169 | ftpState->state = Ftp::Client::SENT_NLST; |
dbfed404 | 2170 | } |
2171 | ||
3fdadc70 | 2172 | static void |
5517260a | 2173 | ftpReadList(Ftp::Gateway * ftpState) |
3fdadc70 | 2174 | { |
2175 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 2176 | debugs(9, 3, HERE); |
62e76326 | 2177 | |
6200d3d6 | 2178 | if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) { |
62e76326 | 2179 | /* Begin data transfer */ |
8bbb16e3 | 2180 | debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")"); |
6200d3d6 | 2181 | ftpState->switchTimeoutToDataChannel(); |
5f8252d2 | 2182 | ftpState->maybeReadVirginBody(); |
5517260a | 2183 | ftpState->state = Ftp::Client::READING_DATA; |
62e76326 | 2184 | return; |
cdc33f35 | 2185 | } else if (code == 150) { |
62e76326 | 2186 | /* Accept data channel */ |
8bbb16e3 | 2187 | debugs(9, 3, HERE << "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")"); |
434a79b0 | 2188 | ftpState->listenForDataChannel(ftpState->data.conn); |
62e76326 | 2189 | return; |
e55f0142 | 2190 | } else if (!ftpState->flags.tried_nlst && code > 300) { |
62e76326 | 2191 | ftpSendNlst(ftpState); |
3fdadc70 | 2192 | } else { |
62e76326 | 2193 | ftpFail(ftpState); |
2194 | return; | |
3fdadc70 | 2195 | } |
2196 | } | |
2197 | ||
969c39b9 | 2198 | static void |
5517260a | 2199 | ftpSendRetr(Ftp::Gateway * ftpState) |
969c39b9 | 2200 | { |
a11382aa | 2201 | /* check the server control channel is still available */ |
9e008dda | 2202 | if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr")) |
a11382aa | 2203 | return; |
2204 | ||
a689bd4e | 2205 | debugs(9, 3, HERE); |
2206 | ||
969c39b9 | 2207 | assert(ftpState->filepath != NULL); |
903198a7 | 2208 | snprintf(cbuf, CTRL_BUFLEN, "RETR %s\r\n", ftpState->filepath); |
2f47fadf | 2209 | ftpState->writeCommand(cbuf); |
5517260a | 2210 | ftpState->state = Ftp::Client::SENT_RETR; |
969c39b9 | 2211 | } |
2212 | ||
3fdadc70 | 2213 | static void |
5517260a | 2214 | ftpReadRetr(Ftp::Gateway * ftpState) |
3fdadc70 | 2215 | { |
2216 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 2217 | debugs(9, 3, HERE); |
62e76326 | 2218 | |
6200d3d6 | 2219 | if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) { |
62e76326 | 2220 | /* Begin data transfer */ |
48e7baac | 2221 | debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")"); |
6200d3d6 | 2222 | ftpState->switchTimeoutToDataChannel(); |
5f8252d2 | 2223 | ftpState->maybeReadVirginBody(); |
5517260a | 2224 | ftpState->state = Ftp::Client::READING_DATA; |
cdc33f35 | 2225 | } else if (code == 150) { |
62e76326 | 2226 | /* Accept data channel */ |
434a79b0 | 2227 | ftpState->listenForDataChannel(ftpState->data.conn); |
cdc33f35 | 2228 | } else if (code >= 300) { |
62e76326 | 2229 | if (!ftpState->flags.try_slash_hack) { |
2230 | /* Try this as a directory missing trailing slash... */ | |
6f0aab86 | 2231 | ftpState->hackShortcut(ftpSendCwd); |
62e76326 | 2232 | } else { |
2233 | ftpFail(ftpState); | |
2234 | } | |
cdc33f35 | 2235 | } else { |
62e76326 | 2236 | ftpFail(ftpState); |
3fdadc70 | 2237 | } |
2238 | } | |
2239 | ||
0477a072 AJ |
2240 | /** |
2241 | * Generate the HTTP headers and template fluff around an FTP | |
2242 | * directory listing display. | |
2243 | */ | |
2244 | void | |
5517260a | 2245 | Ftp::Gateway::completedListing() |
0477a072 AJ |
2246 | { |
2247 | assert(entry); | |
5517260a | 2248 | entry->lock("Ftp::Gateway"); |
955394ce | 2249 | ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request); |
913524f0 AJ |
2250 | ferr.ftp.listing = &listing; |
2251 | ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : ""); | |
2252 | ferr.ftp.server_msg = ctrl.message; | |
0477a072 | 2253 | ctrl.message = NULL; |
913524f0 | 2254 | entry->replaceHttpReply( ferr.BuildHttpReply() ); |
0477a072 AJ |
2255 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); |
2256 | entry->flush(); | |
5517260a | 2257 | entry->unlock("Ftp::Gateway"); |
0477a072 AJ |
2258 | } |
2259 | ||
3fdadc70 | 2260 | static void |
5517260a | 2261 | ftpReadTransferDone(Ftp::Gateway * ftpState) |
3fdadc70 | 2262 | { |
2263 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 2264 | debugs(9, 3, HERE); |
62e76326 | 2265 | |
bbf07a94 | 2266 | if (code == 226 || code == 250) { |
62e76326 | 2267 | /* Connection closed; retrieval done. */ |
0477a072 AJ |
2268 | if (ftpState->flags.listing) { |
2269 | ftpState->completedListing(); | |
2270 | /* QUIT operation handles sending the reply to client */ | |
2271 | } | |
c0d75e74 | 2272 | ftpSendQuit(ftpState); |
f53969cc | 2273 | } else { /* != 226 */ |
a689bd4e | 2274 | debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data"); |
8043ea13 | 2275 | ftpState->failed(ERR_FTP_FAILURE, 0); |
6b679a01 | 2276 | /* failed closes ctrl.conn and frees ftpState */ |
62e76326 | 2277 | return; |
3fdadc70 | 2278 | } |
2279 | } | |
2280 | ||
5f8252d2 | 2281 | // premature end of the request body |
6f0aab86 | 2282 | void |
5517260a | 2283 | Ftp::Gateway::handleRequestBodyProducerAborted() |
3fdadc70 | 2284 | { |
fccd4a86 | 2285 | Client::handleRequestBodyProducerAborted(); |
a689bd4e | 2286 | debugs(9, 3, HERE << "ftpState=" << this); |
5f8252d2 | 2287 | failed(ERR_READ_ERROR, 0); |
94439e4e | 2288 | } |
2289 | ||
94439e4e | 2290 | static void |
5517260a | 2291 | ftpWriteTransferDone(Ftp::Gateway * ftpState) |
94439e4e | 2292 | { |
2293 | int code = ftpState->ctrl.replycode; | |
a689bd4e | 2294 | debugs(9, 3, HERE); |
62e76326 | 2295 | |
bbf07a94 | 2296 | if (!(code == 226 || code == 250)) { |
a689bd4e | 2297 | debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data"); |
8043ea13 | 2298 | ftpState->failed(ERR_FTP_PUT_ERROR, 0); |
62e76326 | 2299 | return; |
94439e4e | 2300 | } |
62e76326 | 2301 | |
f53969cc | 2302 | ftpState->entry->timestampsSet(); /* XXX Is this needed? */ |
94439e4e | 2303 | ftpSendReply(ftpState); |
969c39b9 | 2304 | } |
2305 | ||
2306 | static void | |
5517260a | 2307 | ftpSendQuit(Ftp::Gateway * ftpState) |
969c39b9 | 2308 | { |
a11382aa | 2309 | /* check the server control channel is still available */ |
9e008dda | 2310 | if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit")) |
a11382aa | 2311 | return; |
2312 | ||
903198a7 | 2313 | snprintf(cbuf, CTRL_BUFLEN, "QUIT\r\n"); |
2f47fadf | 2314 | ftpState->writeCommand(cbuf); |
5517260a | 2315 | ftpState->state = Ftp::Client::SENT_QUIT; |
3fdadc70 | 2316 | } |
2317 | ||
3cc0f4e7 | 2318 | /** Completes a client FTP operation with success or other page |
0477a072 AJ |
2319 | * generated and stored in the entry field by the code issuing QUIT. |
2320 | */ | |
3fdadc70 | 2321 | static void |
5517260a | 2322 | ftpReadQuit(Ftp::Gateway * ftpState) |
3fdadc70 | 2323 | { |
5f8252d2 | 2324 | ftpState->serverComplete(); |
3fdadc70 | 2325 | } |
2326 | ||
969c39b9 | 2327 | static void |
5517260a | 2328 | ftpTrySlashHack(Ftp::Gateway * ftpState) |
969c39b9 | 2329 | { |
2330 | char *path; | |
e55f0142 | 2331 | ftpState->flags.try_slash_hack = 1; |
969c39b9 | 2332 | /* Free old paths */ |
62e76326 | 2333 | |
a689bd4e | 2334 | debugs(9, 3, HERE); |
2335 | ||
969c39b9 | 2336 | if (ftpState->pathcomps) |
62e76326 | 2337 | wordlistDestroy(&ftpState->pathcomps); |
2338 | ||
969c39b9 | 2339 | safe_free(ftpState->filepath); |
62e76326 | 2340 | |
969c39b9 | 2341 | /* Build the new path (urlpath begins with /) */ |
3f0e38d6 | 2342 | path = SBufToCstring(ftpState->request->url.path()); |
62e76326 | 2343 | |
969c39b9 | 2344 | rfc1738_unescape(path); |
62e76326 | 2345 | |
969c39b9 | 2346 | ftpState->filepath = path; |
62e76326 | 2347 | |
969c39b9 | 2348 | /* And off we go */ |
dbfed404 | 2349 | ftpGetFile(ftpState); |
969c39b9 | 2350 | } |
2351 | ||
63be0a78 | 2352 | /** |
2353 | * Forget hack status. Next error is shown to the user | |
2354 | */ | |
6f0aab86 | 2355 | void |
5517260a | 2356 | Ftp::Gateway::unhack() |
0f169992 | 2357 | { |
a689bd4e | 2358 | debugs(9, 3, HERE); |
2359 | ||
6f0aab86 | 2360 | if (old_request != NULL) { |
2361 | safe_free(old_request); | |
2362 | safe_free(old_reply); | |
0f169992 | 2363 | } |
2364 | } | |
2365 | ||
6f0aab86 | 2366 | void |
5517260a | 2367 | Ftp::Gateway::hackShortcut(FTPSM * nextState) |
969c39b9 | 2368 | { |
c7e0305b | 2369 | /* Clear some unwanted state */ |
9fa2e208 | 2370 | setCurrentOffset(0); |
6f0aab86 | 2371 | restart_offset = 0; |
0f169992 | 2372 | /* Save old error message & some state info */ |
62e76326 | 2373 | |
a689bd4e | 2374 | debugs(9, 3, HERE); |
2375 | ||
6f0aab86 | 2376 | if (old_request == NULL) { |
2377 | old_request = ctrl.last_command; | |
2378 | ctrl.last_command = NULL; | |
2379 | old_reply = ctrl.last_reply; | |
2380 | ctrl.last_reply = NULL; | |
62e76326 | 2381 | |
6f0aab86 | 2382 | if (pathcomps == NULL && filepath != NULL) |
2383 | old_filepath = xstrdup(filepath); | |
0f169992 | 2384 | } |
62e76326 | 2385 | |
969c39b9 | 2386 | /* Jump to the "hack" state */ |
6f0aab86 | 2387 | nextState(this); |
969c39b9 | 2388 | } |
2389 | ||
3fdadc70 | 2390 | static void |
5517260a | 2391 | ftpFail(Ftp::Gateway *ftpState) |
3fdadc70 | 2392 | { |
51b5dcf5 | 2393 | const bool slashHack = ftpState->request->url.path().caseCmp("/%2f", 4)==0; |
cd4d5d45 VL |
2394 | int code = ftpState->ctrl.replycode; |
2395 | err_type error_code = ERR_NONE; | |
2396 | ||
2397 | debugs(9, 6, "state " << ftpState->state << | |
2398 | " reply code " << code << "flags(" << | |
9e008dda AJ |
2399 | (ftpState->flags.isdir?"IS_DIR,":"") << |
2400 | (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " << | |
2401 | "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize << | |
51b5dcf5 | 2402 | "slashhack=" << (slashHack? "T":"F")); |
62e76326 | 2403 | |
a689bd4e | 2404 | /* Try the / hack to support "Netscape" FTP URL's for retreiving files */ |
f53969cc | 2405 | if (!ftpState->flags.isdir && /* Not a directory */ |
51b5dcf5 AJ |
2406 | !ftpState->flags.try_slash_hack && !slashHack && /* Not doing slash hack */ |
2407 | ftpState->mdtm <= 0 && ftpState->theSize < 0) { /* Not known as a file */ | |
62e76326 | 2408 | |
2409 | switch (ftpState->state) { | |
2410 | ||
5517260a | 2411 | case Ftp::Client::SENT_CWD: |
62e76326 | 2412 | |
5517260a | 2413 | case Ftp::Client::SENT_RETR: |
62e76326 | 2414 | /* Try the / hack */ |
6f0aab86 | 2415 | ftpState->hackShortcut(ftpTrySlashHack); |
62e76326 | 2416 | return; |
2417 | ||
2418 | default: | |
2419 | break; | |
2420 | } | |
969c39b9 | 2421 | } |
62e76326 | 2422 | |
cd4d5d45 VL |
2423 | Http::StatusCode sc = ftpState->failedHttpStatus(error_code); |
2424 | ErrorState *ftperr = new ErrorState(error_code, sc, ftpState->fwd->request); | |
2425 | ftpState->failed(error_code, code, ftperr); | |
2426 | ftperr->detailError(code); | |
2427 | HttpReply *newrep = ftperr->BuildHttpReply(); | |
2428 | delete ftperr; | |
2429 | ||
2430 | ftpState->entry->replaceHttpReply(newrep); | |
2431 | ftpSendQuit(ftpState); | |
9bc73deb | 2432 | } |
2433 | ||
434a79b0 | 2434 | Http::StatusCode |
5517260a | 2435 | Ftp::Gateway::failedHttpStatus(err_type &error) |
9bc73deb | 2436 | { |
434a79b0 | 2437 | if (error == ERR_NONE) { |
6f0aab86 | 2438 | switch (state) { |
e7ce227f | 2439 | |
62e76326 | 2440 | case SENT_USER: |
e7ce227f | 2441 | |
62e76326 | 2442 | case SENT_PASS: |
e7ce227f | 2443 | |
434a79b0 DK |
2444 | if (ctrl.replycode > 500) { |
2445 | error = ERR_FTP_FORBIDDEN; | |
2446 | return password_url ? Http::scForbidden : Http::scUnauthorized; | |
2447 | } else if (ctrl.replycode == 421) { | |
2448 | error = ERR_FTP_UNAVAILABLE; | |
2449 | return Http::scServiceUnavailable; | |
2450 | } | |
62e76326 | 2451 | break; |
e7ce227f | 2452 | |
62e76326 | 2453 | case SENT_CWD: |
e7ce227f | 2454 | |
62e76326 | 2455 | case SENT_RETR: |
434a79b0 DK |
2456 | if (ctrl.replycode == 550) { |
2457 | error = ERR_FTP_NOT_FOUND; | |
2458 | return Http::scNotFound; | |
2459 | } | |
62e76326 | 2460 | break; |
e7ce227f | 2461 | |
62e76326 | 2462 | default: |
2463 | break; | |
2464 | } | |
b6a2f15e | 2465 | } |
5517260a | 2466 | return Ftp::Client::failedHttpStatus(error); |
3fdadc70 | 2467 | } |
2468 | ||
54220df8 | 2469 | static void |
5517260a | 2470 | ftpSendReply(Ftp::Gateway * ftpState) |
54220df8 | 2471 | { |
acce49bf | 2472 | int code = ftpState->ctrl.replycode; |
955394ce | 2473 | Http::StatusCode http_code; |
728da2ee | 2474 | err_type err_code = ERR_NONE; |
a11382aa | 2475 | |
a689bd4e | 2476 | debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code); |
62e76326 | 2477 | |
fa80a8ef | 2478 | if (cbdataReferenceValid(ftpState)) |
a689bd4e | 2479 | debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!"); |
62e76326 | 2480 | |
bbf07a94 | 2481 | if (code == 226 || code == 250) { |
62e76326 | 2482 | err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED; |
955394ce | 2483 | http_code = (ftpState->mdtm > 0) ? Http::scAccepted : Http::scCreated; |
9bc73deb | 2484 | } else if (code == 227) { |
62e76326 | 2485 | err_code = ERR_FTP_PUT_CREATED; |
955394ce | 2486 | http_code = Http::scCreated; |
54220df8 | 2487 | } else { |
62e76326 | 2488 | err_code = ERR_FTP_PUT_ERROR; |
955394ce | 2489 | http_code = Http::scInternalServerError; |
54220df8 | 2490 | } |
62e76326 | 2491 | |
913524f0 | 2492 | ErrorState err(err_code, http_code, ftpState->request); |
62e76326 | 2493 | |
54220df8 | 2494 | if (ftpState->old_request) |
913524f0 | 2495 | err.ftp.request = xstrdup(ftpState->old_request); |
54220df8 | 2496 | else |
913524f0 | 2497 | err.ftp.request = xstrdup(ftpState->ctrl.last_command); |
62e76326 | 2498 | |
54220df8 | 2499 | if (ftpState->old_reply) |
913524f0 | 2500 | err.ftp.reply = xstrdup(ftpState->old_reply); |
0dfb298f | 2501 | else if (ftpState->ctrl.last_reply) |
913524f0 | 2502 | err.ftp.reply = xstrdup(ftpState->ctrl.last_reply); |
0dfb298f | 2503 | else |
913524f0 | 2504 | err.ftp.reply = xstrdup(""); |
62e76326 | 2505 | |
129fe2a1 CT |
2506 | // TODO: interpret as FTP-specific error code |
2507 | err.detailError(code); | |
2508 | ||
913524f0 | 2509 | ftpState->entry->replaceHttpReply( err.BuildHttpReply() ); |
62e76326 | 2510 | |
9bc73deb | 2511 | ftpSendQuit(ftpState); |
54220df8 | 2512 | } |
2513 | ||
6f0aab86 | 2514 | void |
5517260a | 2515 | Ftp::Gateway::appendSuccessHeader() |
3fdadc70 | 2516 | { |
a689bd4e | 2517 | debugs(9, 3, HERE); |
ee65546f | 2518 | |
6f0aab86 | 2519 | if (flags.http_header_sent) |
62e76326 | 2520 | return; |
2521 | ||
585ab260 | 2522 | HttpReply *reply = new HttpReply; |
2523 | ||
6f0aab86 | 2524 | flags.http_header_sent = 1; |
62e76326 | 2525 | |
0477a072 | 2526 | assert(entry->isEmpty()); |
62e76326 | 2527 | |
0477a072 | 2528 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); |
62e76326 | 2529 | |
f53969cc | 2530 | entry->buffer(); /* released when done processing current data payload */ |
b66315e4 | 2531 | |
51b5dcf5 AJ |
2532 | SBuf urlPath = request->url.path(); |
2533 | auto t = urlPath.rfind('/'); | |
2534 | SBuf filename = urlPath.substr(t != SBuf::npos ? t : 0); | |
2535 | ||
2536 | const char *mime_type = NULL; | |
2537 | const char *mime_enc = NULL; | |
62e76326 | 2538 | |
6f0aab86 | 2539 | if (flags.isdir) { |
62e76326 | 2540 | mime_type = "text/html"; |
3fdadc70 | 2541 | } else { |
6f0aab86 | 2542 | switch (typecode) { |
62e76326 | 2543 | |
2544 | case 'I': | |
2545 | mime_type = "application/octet-stream"; | |
51b5dcf5 AJ |
2546 | // XXX: performance regression, c_str() may reallocate |
2547 | mime_enc = mimeGetContentEncoding(filename.c_str()); | |
62e76326 | 2548 | break; |
2549 | ||
2550 | case 'A': | |
2551 | mime_type = "text/plain"; | |
2552 | break; | |
2553 | ||
2554 | default: | |
51b5dcf5 AJ |
2555 | // XXX: performance regression, c_str() may reallocate |
2556 | mime_type = mimeGetContentType(filename.c_str()); | |
2557 | mime_enc = mimeGetContentEncoding(filename.c_str()); | |
62e76326 | 2558 | break; |
2559 | } | |
3fdadc70 | 2560 | } |
62e76326 | 2561 | |
cb69b4c7 | 2562 | /* set standard stuff */ |
62e76326 | 2563 | |
9fa2e208 | 2564 | if (0 == getCurrentOffset()) { |
690b22c6 | 2565 | /* Full reply */ |
955394ce | 2566 | reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2); |
9fa2e208 | 2567 | } else if (theSize < getCurrentOffset()) { |
9e008dda AJ |
2568 | /* |
2569 | * DPW 2007-05-04 | |
2570 | * offset should not be larger than theSize. We should | |
2571 | * not be seeing this condition any more because we'll only | |
2572 | * send REST if we know the theSize and if it is less than theSize. | |
2573 | */ | |
2574 | debugs(0,DBG_CRITICAL,HERE << "Whoops! " << | |
9fa2e208 | 2575 | " current offset=" << getCurrentOffset() << |
9e008dda AJ |
2576 | ", but theSize=" << theSize << |
2577 | ". assuming full content response"); | |
955394ce | 2578 | reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2); |
690b22c6 | 2579 | } else { |
62e76326 | 2580 | /* Partial reply */ |
2581 | HttpHdrRangeSpec range_spec; | |
9fa2e208 CT |
2582 | range_spec.offset = getCurrentOffset(); |
2583 | range_spec.length = theSize - getCurrentOffset(); | |
955394ce | 2584 | reply->setHeaders(Http::scPartialContent, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2); |
47f6e231 | 2585 | httpHeaderAddContRange(&reply->header, range_spec, theSize); |
cfbf5373 | 2586 | } |
62e76326 | 2587 | |
cb69b4c7 | 2588 | /* additional info */ |
2589 | if (mime_enc) | |
789217a2 | 2590 | reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc); |
62e76326 | 2591 | |
585ab260 | 2592 | setVirginReply(reply); |
2593 | adaptOrFinalizeReply(); | |
063a47b5 | 2594 | } |
2595 | ||
2596 | void | |
5517260a | 2597 | Ftp::Gateway::haveParsedReplyHeaders() |
063a47b5 | 2598 | { |
fccd4a86 | 2599 | Client::haveParsedReplyHeaders(); |
c1520b67 | 2600 | |
063a47b5 | 2601 | StoreEntry *e = entry; |
62e76326 | 2602 | |
3900307b | 2603 | e->timestampsSet(); |
62e76326 | 2604 | |
6f0aab86 | 2605 | if (flags.authenticated) { |
62e76326 | 2606 | /* |
2607 | * Authenticated requests can't be cached. | |
2608 | */ | |
5f33b71d | 2609 | e->release(); |
6919be24 | 2610 | } else if (!EBIT_TEST(e->flags, RELEASE_REQUEST) && !getCurrentOffset()) { |
d88e3c49 | 2611 | e->setPublicKey(); |
c68e9c6b | 2612 | } else { |
5f33b71d | 2613 | e->release(); |
c68e9c6b | 2614 | } |
77a30ebb | 2615 | } |
bfcaf585 | 2616 | |
6f0aab86 | 2617 | HttpReply * |
5517260a | 2618 | Ftp::Gateway::ftpAuthRequired(HttpRequest * request, const char *realm) |
cb69b4c7 | 2619 | { |
955394ce | 2620 | ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request); |
913524f0 | 2621 | HttpReply *newrep = err.BuildHttpReply(); |
9da6b594 | 2622 | #if HAVE_AUTH_MODULE_BASIC |
63259c34 | 2623 | /* add Authenticate header */ |
a9925b40 | 2624 | newrep->header.putAuth("Basic", realm); |
9da6b594 | 2625 | #endif |
4a56ee8d | 2626 | return newrep; |
cb69b4c7 | 2627 | } |
8f872bb6 | 2628 | |
851feda6 | 2629 | const SBuf & |
5517260a | 2630 | Ftp::UrlWith2f(HttpRequest * request) |
8f872bb6 | 2631 | { |
51b5dcf5 | 2632 | SBuf newbuf("%2f"); |
62e76326 | 2633 | |
851feda6 AJ |
2634 | if (request->url.getScheme() != AnyP::PROTO_FTP) { |
2635 | static const SBuf nil; | |
2636 | return nil; | |
2637 | } | |
62e76326 | 2638 | |
51b5dcf5 AJ |
2639 | if (request->url.path()[0] == '/') { |
2640 | newbuf.append(request->url.path()); | |
2641 | request->url.path(newbuf); | |
51b5dcf5 AJ |
2642 | } else if (!request->url.path().startsWith(newbuf)) { |
2643 | newbuf.append(request->url.path().substr(1)); | |
2644 | request->url.path(newbuf); | |
8f872bb6 | 2645 | } |
62e76326 | 2646 | |
851feda6 | 2647 | return request->effectiveRequestUri(); |
8f872bb6 | 2648 | } |
253caccb | 2649 | |
2650 | void | |
5517260a | 2651 | Ftp::Gateway::printfReplyBody(const char *fmt, ...) |
253caccb | 2652 | { |
2653 | va_list args; | |
2654 | va_start (args, fmt); | |
2655 | static char buf[4096]; | |
2656 | buf[0] = '\0'; | |
2657 | vsnprintf(buf, 4096, fmt, args); | |
2658 | writeReplyBody(buf, strlen(buf)); | |
810635e3 | 2659 | va_end(args); |
253caccb | 2660 | } |
2661 | ||
63be0a78 | 2662 | /** |
253caccb | 2663 | * Call this when there is data from the origin server |
2664 | * which should be sent to either StoreEntry, or to ICAP... | |
2665 | */ | |
2666 | void | |
5517260a | 2667 | Ftp::Gateway::writeReplyBody(const char *dataToWrite, size_t dataLength) |
253caccb | 2668 | { |
e053c141 FC |
2669 | debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply"); |
2670 | addVirginReplyBody(dataToWrite, dataLength); | |
253caccb | 2671 | } |
2672 | ||
63be0a78 | 2673 | /** |
2674 | * A hack to ensure we do not double-complete on the forward entry. | |
2675 | * | |
5517260a | 2676 | \todo Ftp::Gateway logic should probably be rewritten to avoid |
f53969cc | 2677 | * double-completion or FwdState should be rewritten to allow it. |
63be0a78 | 2678 | */ |
16846d00 | 2679 | void |
5517260a | 2680 | Ftp::Gateway::completeForwarding() |
16846d00 | 2681 | { |
5f8252d2 | 2682 | if (fwd == NULL || flags.completed_forwarding) { |
a689bd4e | 2683 | debugs(9, 3, HERE << "completeForwarding avoids " << |
6b679a01 | 2684 | "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd << |
9e008dda | 2685 | ", this " << this << ", fwd " << fwd); |
5f8252d2 | 2686 | return; |
2687 | } | |
16846d00 | 2688 | |
5f8252d2 | 2689 | flags.completed_forwarding = true; |
fccd4a86 | 2690 | Client::completeForwarding(); |
16846d00 | 2691 | } |
2692 | ||
63be0a78 | 2693 | /** |
2694 | * Have we lost the FTP server control channel? | |
2695 | * | |
f53969cc SM |
2696 | \retval true The server control channel is available. |
2697 | \retval false The server control channel is not available. | |
63be0a78 | 2698 | */ |
a11382aa | 2699 | bool |
5517260a | 2700 | Ftp::Gateway::haveControlChannel(const char *caller_name) const |
a11382aa | 2701 | { |
9e008dda | 2702 | if (doneWithServer()) |
33ed5343 | 2703 | return false; |
a11382aa | 2704 | |
33ed5343 | 2705 | /* doneWithServer() only checks BOTH channels are closed. */ |
6200d3d6 | 2706 | if (!Comm::IsConnOpen(ctrl.conn)) { |
a689bd4e | 2707 | debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active."); |
33ed5343 | 2708 | debugs(9, 2, caller_name << ": attempted on a closed FTP channel."); |
2709 | return false; | |
2710 | } | |
2711 | ||
2712 | return true; | |
a11382aa | 2713 | } |
5517260a | 2714 | |
aea65fec AR |
2715 | bool |
2716 | Ftp::Gateway::mayReadVirginReplyBody() const | |
2717 | { | |
2718 | // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead? | |
2719 | return !doneWithServer(); | |
2720 | } | |
2721 | ||
5517260a AR |
2722 | AsyncJob::Pointer |
2723 | Ftp::StartGateway(FwdState *const fwdState) | |
2724 | { | |
2725 | return AsyncJob::Start(new Ftp::Gateway(fwdState)); | |
2726 | } | |
f53969cc | 2727 |