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 
54 namespace Ftp
55 {
56 
57 struct GatewayFlags {
58 
59  /* passive mode */
62  bool pasv_only;
63  bool pasv_failed; // was FwdState::flags.ftp_pasv_failed
64 
65  /* authentication */
69 
70  /* other */
71  bool isdir;
75  bool tried_nlst;
77  bool dir_slash;
78  bool root_dir;
79  bool no_dotdot;
80  bool binary;
82  bool put;
83  bool put_mkdir;
85  bool listing;
87 };
88 
89 class Gateway;
90 typedef void (StateMethod)(Ftp::Gateway *);
91 
95 class Gateway : public Ftp::Client
96 {
98 
99 public:
100  Gateway(FwdState *);
101  ~Gateway() override;
102  char user[MAX_URL];
105  char *reply_hdr;
110  int conn_att;
112  time_t mdtm;
113  int64_t theSize;
115  char *filepath;
116  char *dirpath;
117  int64_t restart_offset;
118  char *proxy_host;
119  size_t list_width;
122  char typecode;
124 
126 
127 public:
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;
155  static PF ftpDataWrite;
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 
167 protected:
168  void handleControlReply() override;
169  void dataClosed(const CommCloseCbParams &io) override;
170 
171 private:
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 
181 typedef Ftp::StateMethod FTPSM; // to avoid lots of non-changes
182 
184 
185 typedef struct {
186  char type;
187  int64_t size;
188  char *date;
189  char *name;
190  char *showname;
191  char *link;
192 } ftpListParts;
193 
194 #define CTRL_BUFLEN 16*1024
195 static 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  */
240 static FTPSM ftpFail;
243 
244 /************************************************
245 ** Debugs Levels used here **
246 *************************************************
247 0 CRITICAL Events
248 1 IMPORTANT Events
249  Protocol and Transmission failures.
250 2 FTP Protocol Chatter
251 3 Logic Flows
252 4 Data Parsing Flows
253 5 Data Dumps
254 7 ??
255 ************************************************/
256 
257 /************************************************
258 ** State Machine Description (excluding hacks) **
259 *************************************************
260 From To
261 ---------------------------------------
262 Welcome User
263 User Pass
264 Pass Type
265 Type TraverseDirectory / GetFile
266 TraverseDirectory Cwd / GetFile / ListDir
267 Cwd TraverseDirectory / Mkdir
268 GetFile Mdtm
269 Mdtm Size
270 Size Epsv
271 ListDir Epsv
272 Epsv FileOrList
273 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
274 Rest Retr
275 Retr / Nlst / List DataRead* (on datachannel)
276 DataRead* ReadTransferDone
277 ReadTransferDone DataTransferDone
278 Stor DataWrite* (on datachannel)
279 DataWrite* RequestPutBody** (from client)
280 RequestPutBody** DataWrite* / WriteTransferDone
281 WriteTransferDone DataTransferDone
282 DataTransferDone Quit
283 Quit -
284 ************************************************/
285 
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 
317 void
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 
356  flags.pasv_supported = 1;
357 
358  flags.rest_supported = 1;
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 
398 void
399 Ftp::Gateway::loginParser(const SBuf &login, bool escaped)
400 {
401  debugs(9, 4, "login=" << login << ", escaped=" << escaped);
402  debugs(9, 9, "IN : login=" << login << ", escaped=" << escaped << ", user=" << user << ", password=" << password);
403 
404  if (login.isEmpty())
405  return;
406 
407  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 
450 void
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 
460  typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer;
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 
479  assert(Comm::IsConnOpen(conn));
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 
487 void
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 
504 static const char *Month[] = {
505  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
506  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
507 };
508 
509 static int
510 is_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 
521 static 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 
533 static ftpListParts *
534 ftpListParseParts(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;
541  } tokens[MAX_TOKENS];
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 
738 blank:
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 
756 found:
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 
767 bool
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>",
841  rfc1738_escape_part(parts->name),
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 
883 void
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 
967 void
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 
1034 int
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 
1079 void
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 
1119 void
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 
1152 void
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 
1174 void
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 
1194 static 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 
1225 void
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 
1269 SBuf
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 
1287 static 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 
1304 static 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 
1319 static 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 
1331 static 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 
1344 static 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 
1393 static 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 
1416  rfc1738_unescape(d);
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 
1433 static 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 
1463 static 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 
1489 static 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 
1519 static 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 
1535 static 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 
1556 static void
1558 {
1559  assert(*ftpState->filepath != '\0');
1560  ftpState->flags.isdir = 0;
1561  ftpSendMdtm(ftpState);
1562 }
1563 
1564 static 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 
1576 static 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 
1589 static 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 
1606 static 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 
1627 static 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 
1651 static 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 
1667 static 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 
1690 void
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 
1718 static 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 
1732 void
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 
1754 static void
1755 ftpOpenListenSocket(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 
1802 static 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 
1848 static 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 
1863 static 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 
1883 void
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) {
1931  debugs(9, DBG_IMPORTANT,
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 
1960 static 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 
1983 static 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 
2009 static 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 
2050 static 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 
2064 int
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 
2091 static 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 
2110 static 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 
2129 static 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 
2150 static 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 
2176 static 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 
2191 static 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 
2222 void
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 
2238 static 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
2261 void
2263 {
2265  debugs(9, 3, "ftpState=" << this);
2266  failed(ERR_READ_ERROR, 0);
2267 }
2268 
2269 static 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 
2286 static 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 
2301 static void
2303 {
2304  ftpState->serverComplete();
2305 }
2306 
2308 std::optional<SBuf>
2310 {
2311  return AnyP::Uri::Decode(request->url.absolutePath());
2312 }
2313 
2316 static 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 
2339 void
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 
2350 void
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 
2374 static 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 
2397  case Ftp::Client::SENT_CWD:
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 
2455 static 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 
2499 void
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)
2573  reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc);
2574 
2575  reply->sources |= Http::Message::srcFtp;
2576  setVirginReply(reply);
2577  adaptOrFinalizeReply();
2578 }
2579 
2580 void
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 
2597 HttpReply *
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 
2612 const 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 
2637 void
2638 Ftp::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 
2650 void
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 
2670 bool
2671 Ftp::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 
2686 bool
2688 {
2689  // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2690  return !doneWithServer();
2691 }
2692 
2693 void
2694 Ftp::StartGateway(FwdState *const fwdState)
2695 {
2696  AsyncJob::Start(new Ftp::Gateway(fwdState));
2697 }
2698 
static void ftpTrySlashHack(Ftp::Gateway *ftpState)
Definition: FtpGateway.cc:2317
const char * xstrerr(int error)
Definition: xstrerror.cc:83
static FTPSM ftpSendCwd
Definition: FtpGateway.cc:223
void processReplyBody() override
Definition: FtpGateway.cc:968
static FTPSM ftpReadQuit
Definition: FtpGateway.cc:242
CBDATA_CHILD(Gateway)
void handleRequestBodyProducerAborted() override
Definition: FtpGateway.cc:2262
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
StoreEntry * entry
Definition: Client.h:177
size_type find(char c, size_type startPos=0) const
Definition: SBuf.cc:584
void wordlistDestroy(wordlist **list)
destroy a wordlist
Definition: wordlist.cc:16
Gateway(FwdState *)
Definition: FtpGateway.cc:330
@ scAccepted
Definition: StatusCode.h:29
char * cwd_msg
Definition: errorpage.h:192
@ scUnauthorized
Definition: StatusCode.h:46
@ METHOD_HEAD
Definition: MethodType.h:28
#define DBG_CRITICAL
Definition: Stream.h:37
AnyP::Uri url
the request URI
Definition: HttpRequest.h:115
@ ERR_READ_ERROR
Definition: forward.h:28
#define xmalloc
~Gateway() override
Definition: FtpGateway.cc:366
@ ERR_FTP_FORBIDDEN
Definition: forward.h:56
static PF ftpDataWrite
Definition: FtpGateway.cc:155
MemBuf * listing
Definition: errorpage.h:193
SBuf ftpRealm()
Definition: FtpGateway.cc:1270
bool makePublic(const KeyScope keyScope=ksDefault)
Definition: store.cc:167
HttpHeader header
Definition: Message.h:74
char * old_filepath
Definition: FtpGateway.cc:121
static FTPSM ftpReadTransferDone
Definition: FtpGateway.cc:233
bool isEmpty() const
Definition: SBuf.h:435
@ ERR_FTP_PUT_ERROR
Definition: forward.h:54
void maybeReadVirginBody() override
read response data from the network
Definition: FtpClient.cc:918
char * reply
Definition: errorpage.h:191
const char * url() const
Definition: store.cc:1566
void connectDataChannel()
Definition: FtpClient.cc:763
const char *const crlf
Definition: FtpClient.cc:40
void writeCommand(const char *buf)
Definition: FtpClient.cc:824
std::optional< SBuf > decodedRequestUriPath() const
absolute request URI path after successful decoding of all pct-encoding sequences
Definition: FtpGateway.cc:2309
bool pasv_supported
PASV command is allowed.
Definition: FtpGateway.cc:60
static void ftpOpenListenSocket(Ftp::Gateway *ftpState, int fallback)
Definition: FtpGateway.cc:1755
@ ENTRY_ABORTED
Definition: enums.h:110
HttpReply * BuildHttpReply(void)
Definition: errorpage.cc:1307
virtual void handleRequestBodyProducerAborted()=0
Definition: Client.cc:355
void error(char *format,...)
AccessLogEntryPointer al
info for the future access.log entry
Definition: FwdState.h:204
static FTPSM ftpSendRest
Definition: FtpGateway.cc:229
@ ERR_CACHE_ACCESS_DENIED
Definition: forward.h:19
bool tried_auth_nopass
auth tried username with no password already.
Definition: FtpGateway.cc:68
void dataClosed(const CommCloseCbParams &io) override
handler called by Comm when FTP data channel is closed unexpectedly
Definition: FtpGateway.cc:318
struct ErrorState::@47 ftp
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
Definition: SBuf.h:93
static HttpReply * ftpAuthRequired(HttpRequest *request, SBuf &realm, AccessLogEntry::Pointer &)
Definition: FtpGateway.cc:2598
bool sendPassive()
Definition: FtpClient.cc:654
bool htmlifyListEntry(const char *line, PackableStream &)
Definition: FtpGateway.cc:768
static FTPSM ftpReadSize
Definition: FtpGateway.cc:213
void SBufToCstring(char *d, const SBuf &s)
Definition: SBuf.h:756
#define xtoupper(x)
Definition: xis.h:16
#define xstrdup
void handleControlReply() override
Definition: FtpGateway.cc:1175
@ TIMEOUT
Definition: Flag.h:18
static FTPSM ftpSendRetr
Definition: FtpGateway.cc:231
@ CONTENT_ENCODING
int checkAuth(const HttpHeader *req_hdr)
Definition: FtpGateway.cc:1035
static void FreeAddr(struct addrinfo *&ai)
Definition: Address.cc:698
C * getRaw() const
Definition: RefCount.h:89
void initReadBuf()
Definition: FtpClient.cc:222
void loginParser(const SBuf &login, bool escaped)
Definition: FtpGateway.cc:399
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
void detailError(const ErrorDetail::Pointer &dCode)
set error type-specific detail code
Definition: errorpage.h:111
int cbdataReferenceValid(const void *p)
Definition: cbdata.cc:270
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
@ OK
Definition: Flag.h:16
static FTPSM ftpSendUser
Definition: FtpGateway.cc:204
#define rfc1738_escape(x)
Definition: rfc1738.h:52
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
static FTPSM ftpReadRest
Definition: FtpGateway.cc:230
static FTPSM ftpReadList
Definition: FtpGateway.cc:228
@ ERR_NONE
Definition: forward.h:15
void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting=true)
Definition: store.cc:1705
void * memAllocate(mem_type)
Allocate one element from the typed pool.
Definition: old_api.cc:122
static FTPSM ftpSendNlst
Definition: FtpGateway.cc:227
bool isIPv4() const
Definition: Address.cc:178
StatusCode
Definition: StatusCode.h:20
static FTPSM ftpSendMkdir
Definition: FtpGateway.cc:238
err_type
Definition: forward.h:14
static FTPSM ftpRestOrList
Definition: FtpGateway.cc:225
virtual void haveParsedReplyHeaders()
called when we have final (possibly adapted) reply headers; kids extend
Definition: Client.cc:541
int64_t currentOffset
Definition: Client.h:173
static FTPSM ftpSendType
Definition: FtpGateway.cc:208
char * wordlistChopHead(wordlist **wl)
Definition: wordlist.cc:42
#define w_space
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition: CommCalls.h:83
int64_t theSize
Definition: FtpGateway.cc:113
SBuf substr(size_type pos, size_type n=npos) const
Definition: SBuf.cc:576
void serverComplete()
Definition: Client.cc:167
FTP client functionality shared among FTP Gateway and Relay clients.
Definition: FtpClient.h:110
@ CONTENT_LENGTH
Definition: forward.h:23
virtual void completeForwarding()
Definition: Client.cc:216
void() StateMethod(Ftp::Gateway *)
Definition: FtpGateway.cc:90
CBDATA_NAMESPACED_CLASS_INIT(Ftp, Gateway)
static FTPSM ftpListDir
Definition: FtpGateway.cc:221
char * showname
Definition: FtpGateway.cc:190
void comm_open_listener(int sock_type, int proto, Comm::ConnectionPointer &conn, const char *note)
Definition: comm.cc:259
virtual bool haveControlChannel(const char *caller_name) const
Definition: FtpGateway.cc:2671
size_type rfind(char c, size_type endPos=npos) const
Definition: SBuf.cc:692
#define MAX_URL
Definition: defines.h:76
bool handlePasvReply(Ip::Address &remoteAddr)
Definition: FtpClient.cc:456
static FTPSM ftpSendQuit
Definition: FtpGateway.cc:241
struct tok tokens[]
Definition: parse.c:168
static FTPSM ftpReadCwd
Definition: FtpGateway.cc:224
static FTPSM ftpReadUser
Definition: FtpGateway.cc:205
char * proxy_host
Definition: FtpGateway.cc:118
mb_size_t contentSize() const
available data size
Definition: MemBuf.h:47
wordlist * server_msg
Definition: errorpage.h:189
int size
Definition: ModDevPoll.cc:70
wordlist * message
Definition: FtpClient.h:82
static FTPSM ftpReadMkdir
Definition: FtpGateway.cc:239
void start() override
called by AsyncStart; do not call directly
Definition: FtpClient.cc:216
static char cbuf[CTRL_BUFLEN]
Definition: FtpGateway.cc:195
bool completed_forwarding
Definition: FtpGateway.cc:86
String base_href
Definition: FtpGateway.cc:109
static FTPSM ftpSendSize
Definition: FtpGateway.cc:212
@ ERR_FTP_PUT_MODIFIED
Definition: forward.h:58
void rfc1738_unescape(char *url)
Definition: rfc1738.c:146
@ scForbidden
Definition: StatusCode.h:48
#define SQUIDSBUFPRINT(s)
Definition: SBuf.h:32
virtual void handleControlReply()
Definition: FtpClient.cc:420
char * last_command
Definition: FtpClient.h:83
static FTPSM ftpSendPass
Definition: FtpGateway.cc:206
virtual void dataClosed(const CommCloseCbParams &io)
handler called by Comm when FTP data channel is closed unexpectedly
Definition: FtpClient.cc:811
void buildTitleUrl()
Definition: FtpGateway.cc:1120
MemBlob::size_type size_type
Definition: SBuf.h:96
#define COMM_REUSEADDR
Definition: Connection.h:48
void append(char const *buf, int len)
Definition: String.cc:131
static FTPSM ftpReadWelcome
Definition: FtpGateway.cc:203
String clean_url
Definition: FtpGateway.cc:107
int64_t size
Definition: FtpGateway.cc:187
HttpRequestPointer request
Definition: errorpage.h:177
void putAuth(const char *auth_scheme, const char *realm)
Definition: HttpHeader.cc:1137
char * html_quote(const char *string)
Definition: Quoting.cc:42
void appendSuccessHeader()
Definition: FtpGateway.cc:2500
Definition: MemBuf.h:23
Ip::Address local
Definition: Connection.h:149
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 EBIT_TEST(flag, bit)
Definition: defines.h:67
bool handleEpsvReply(Ip::Address &remoteAddr)
Definition: FtpClient.cc:492
String cwd_message
Definition: FtpGateway.cc:120
@ scPartialContent
Definition: StatusCode.h:33
size_t list_width
Definition: FtpGateway.cc:119
static FTPSM ftpReadEPRT
Definition: FtpGateway.cc:214
int xsetsockopt(int socketFd, int level, int option, const void *value, socklen_t valueLength)
POSIX setsockopt(2) equivalent.
Definition: socket.h:122
FTPSM * FTP_SM_FUNCS[]
Definition: FtpGateway.cc:286
void timeout(const CommTimeoutCbParams &io) override
read timeout handler
Definition: FtpGateway.cc:488
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
@ scCreated
Definition: StatusCode.h:28
int64_t getInt64(Http::HdrType id) const
Definition: HttpHeader.cc:1266
#define safe_free(x)
Definition: xalloc.h:73
@ srcFtp
ftp_port or FTP server
Definition: Message.h:40
Ip::Address remote
Definition: Connection.h:152
void close()
planned close: removes the close handler and calls comm_close
Definition: FtpClient.cc:107
void ftpAcceptDataConnection(const CommAcceptCbParams &io)
Definition: FtpGateway.cc:1884
SBuf getAuthToken(Http::HdrType id, const char *auth_scheme) const
Definition: HttpHeader.cc:1408
static std::optional< SBuf > Decode(const SBuf &)
Definition: Uri.cc:105
#define assert(EX)
Definition: assert.h:17
bool tried_auth_anonymous
auth has tried to use anonymous credentials already.
Definition: FtpGateway.cc:67
bool authenticated
authentication success
Definition: FtpGateway.cc:66
SSL Connection
Definition: Session.h:49
FwdState::Pointer fwd
Definition: Client.h:178
bool mayReadVirginReplyBody() const override
whether we may receive more virgin response body bytes
Definition: FtpGateway.cc:2687
static FTPSM ftpSendList
Definition: FtpGateway.cc:226
@ METHOD_PUT
Definition: MethodType.h:27
Comm::Flag flag
comm layer result status.
Definition: CommCalls.h:82
void parseListing()
Definition: FtpGateway.cc:884
static FTPSM ftpTraverseDirectory
Definition: FtpGateway.cc:220
String title_url
Definition: FtpGateway.cc:108
@ scServiceUnavailable
Definition: StatusCode.h:76
const AnyP::UriScheme & getScheme() const
Definition: Uri.h:58
static FTPSM ftpReadEPSV
Definition: FtpGateway.cc:218
#define Assure(condition)
Definition: Assure.h:35
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:70
char * filepath
Definition: FtpGateway.cc:115
@ scInternalServerError
Definition: StatusCode.h:73
Comm::ConnectionPointer conn
channel descriptor
Definition: FtpClient.h:58
wordlist * pathcomps
Definition: FtpGateway.cc:114
const char * null_string
const char * c_str()
Definition: SBuf.cc:516
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition: strtoll.c:61
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:419
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56
SBuf & append(const SBuf &S)
Definition: SBuf.cc:185
int reply_hdr_state
Definition: FtpGateway.cc:106
#define xfree
void completeForwarding() override
Definition: FtpGateway.cc:2651
char * anon_user
Definition: SquidConfig.h:413
char * reply_hdr
Definition: FtpGateway.cc:105
DataChannel data
FTP data channel state.
Definition: FtpClient.h:143
static FTPSM ftpSendPassive
Definition: FtpGateway.cc:217
wordlist * next
Definition: wordlist.h:60
bool mimeGetViewOption(const char *fn)
Definition: mime.cc:223
static const size_type npos
Definition: SBuf.h:100
void markParsedVirginReplyAsWhole(const char *reasonWeAreSure)
Definition: Client.cc:158
uint32_t sources
The message sources.
Definition: Message.h:99
void writeReplyBody(const char *, size_t len)
Definition: FtpGateway.cc:2638
static void ftpListPartsFree(ftpListParts **parts)
Definition: FtpGateway.cc:522
static const char * Month[]
Definition: FtpGateway.cc:504
void hackShortcut(StateMethod *nextState)
Definition: FtpGateway.cc:2351
#define fd_table
Definition: fde.h:189
static FTPSM ftpReadPasv
Definition: FtpGateway.cc:219
void clear()
remove the close handler, leave connection open
Definition: FtpClient.cc:128
static const SBuf & SlashPath()
the static '/' default URL-path
Definition: Uri.cc:147
char user[MAX_URL]
Definition: FtpGateway.cc:102
@ MEM_4K_BUF
Definition: forward.h:49
void checkUrlpath()
Definition: FtpGateway.cc:1080
HttpRequestMethod method
Definition: HttpRequest.h:114
void path(const char *p)
Definition: Uri.h:96
static FTPSM ftpReadType
Definition: FtpGateway.cc:209
Comm::ConnectionPointer listenConn
Definition: FtpClient.h:65
@ scNotFound
Definition: StatusCode.h:49
const char * mimeGetContentType(const char *fn)
Definition: mime.cc:181
virtual Http::StatusCode failedHttpStatus(err_type &error)
Definition: FtpClient.cc:312
MemBuf listing
FTP directory listing in HTML format.
Definition: FtpGateway.cc:123
void dataChannelConnected(const CommConnectCbParams &io) override
Definition: FtpGateway.cc:1733
@ PROTO_FTP
Definition: ProtocolType.h:26
void getAddrInfo(struct addrinfo *&ai, int force=AF_UNSPEC) const
Definition: Address.cc:619
char * key
Definition: wordlist.h:59
@ MEM_8K_BUF
Definition: forward.h:50
char * last_reply
Definition: FtpClient.h:84
const SBuf & UrlWith2f(HttpRequest *)
Definition: FtpGateway.cc:2613
static ftpListParts * ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags)
Definition: FtpGateway.cc:534
int64_t restart_offset
Definition: FtpGateway.cc:117
static FTPSM ftpReadMdtm
Definition: FtpGateway.cc:211
void processHeadResponse()
Definition: FtpGateway.cc:1691
void httpHeaderAddContRange(HttpHeader *, HttpHdrRangeSpec, int64_t)
bool timestampsSet()
Definition: store.cc:1387
GatewayFlags flags
Definition: FtpGateway.cc:125
char * content()
start of the added data
Definition: MemBuf.h:41
static int is_month(const char *buf)
Definition: FtpGateway.cc:510
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:1128
static FTPSM ftpFail
Definition: FtpGateway.cc:240
Ftp::StateMethod FTPSM
Definition: FtpGateway.cc:181
static FTPSM ftpReadStor
Definition: FtpGateway.cc:235
void memFree(void *, int type)
Free a element allocated by memAllocate()
Definition: minimal.cc:61
void readStor()
Definition: FtpGateway.cc:2015
char * old_request
Definition: FtpClient.h:177
void loginFailed(void)
Definition: FtpGateway.cc:1226
void StartGateway(FwdState *const fwdState)
A new FTP Gateway job.
Definition: FtpGateway.cc:2694
static FTPSM ftpSendMdtm
Definition: FtpGateway.cc:210
const char * mimeGetContentEncoding(const char *fn)
Definition: mime.cc:195
void reset(char const *str)
Definition: String.cc:123
struct SquidConfig::@92 Ftp
#define DBG_IMPORTANT
Definition: Stream.h:38
void listenForDataChannel(const Comm::ConnectionPointer &conn)
create a data channel acceptor and start listening.
Definition: FtpGateway.cc:451
char password[MAX_URL]
Definition: FtpGateway.cc:103
int restartable()
Definition: FtpGateway.cc:2065
Http::StatusCode failedHttpStatus(err_type &error) override
Definition: FtpGateway.cc:2421
#define MYNAME
Definition: Stream.h:219
void completedListing(void)
Definition: FtpGateway.cc:2223
void release(const bool shareable=false)
Definition: store.cc:1146
#define PRId64
Definition: types.h:104
void haveParsedReplyHeaders() override
called when we have final (possibly adapted) reply headers; kids extend
Definition: FtpGateway.cc:2581
time_t ParseIso3307(const char *)
Convert from ISO 3307 style time: YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx.
Definition: iso3307.cc:18
char * rfc1738_do_escape(const char *url, int flags)
Definition: rfc1738.c:56
int64_t getCurrentOffset() const
Definition: FtpGateway.cc:152
CtrlChannel ctrl
FTP control channel state.
Definition: FtpClient.h:142
static FTPSM ftpReadPORT
Definition: FtpGateway.cc:216
int token
Definition: parse.c:163
@ ERR_FTP_FAILURE
Definition: forward.h:53
char * dirpath
Definition: FtpGateway.cc:116
const char * mimeGetIconURL(const char *fn)
Definition: mime.cc:162
void setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires)
Definition: HttpReply.cc:170
#define xisspace(x)
Definition: xis.h:15
static FTPSM ftpSendPORT
Definition: FtpGateway.cc:215
#define CTRL_BUFLEN
Definition: FtpGateway.cc:194
@ scOkay
Definition: StatusCode.h:27
bool mimeGetDownloadOption(const char *fn)
Definition: mime.cc:216
SBuf & appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Definition: SBuf.cc:229
#define rfc1738_escape_part(x)
Definition: rfc1738.h:55
const char * wordlistAdd(wordlist **list, const char *key)
Definition: wordlist.cc:25
static FTPSM ftpSendStor
Definition: FtpGateway.cc:234
const SBuf & effectiveRequestUri() const
RFC 7230 section 5.5 - Effective Request URI.
Definition: HttpRequest.cc:741
virtual void timeout(const CommTimeoutCbParams &io)
read timeout handler
Definition: FtpClient.cc:891
HttpRequest * request
Definition: FwdState.h:203
void host(const char *src)
Definition: Uri.cc:154
@ ERR_FTP_UNAVAILABLE
Definition: forward.h:52
#define MAX_TOKENS
Definition: FtpGateway.cc:531
static FTPSM ftpSendReply
Definition: FtpGateway.cc:237
HttpRequestPointer request
Definition: Client.h:179
@ ERR_FTP_NOT_FOUND
Definition: forward.h:55
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
@ ERR_FTP_PUT_CREATED
Definition: forward.h:57
static FTPSM ftpReadRetr
Definition: FtpGateway.cc:232
char * old_reply
Definition: FtpClient.h:178
@ ERR_DIR_LISTING
Definition: forward.h:70
static FTPSM ftpReadPass
Definition: FtpGateway.cc:207
void setCurrentOffset(int64_t offset)
Definition: FtpGateway.cc:151
void start() override
called by AsyncStart; do not call directly
Definition: FtpGateway.cc:1153
void switchTimeoutToDataChannel()
Definition: FtpClient.cc:1070
size_type copy(char *dest, size_type n) const
Definition: SBuf.cc:500
nfmark_t nfmark
Definition: Connection.h:166
#define SQUIDSBUFPH
Definition: SBuf.h:31
char mimeGetTransferMode(const char *fn)
Definition: mime.cc:209
void PF(int, void *)
Definition: forward.h:18
static FTPSM ftpWriteTransferDone
Definition: FtpGateway.cc:236
class SquidConfig Config
Definition: SquidConfig.cc:12
static FTPSM ftpGetFile
Definition: FtpGateway.cc:222
bool epsv_all_sent
EPSV ALL has been used. Must abort on failures.
Definition: FtpGateway.cc:61
static void Start(const Pointer &job)
Definition: AsyncJob.cc:37

 

Introduction

Documentation

Support

Miscellaneous