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