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