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