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