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