FtpGateway.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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/strtoll.h"
22 #include "errorpage.h"
23 #include "fd.h"
24 #include "fde.h"
25 #include "FwdState.h"
26 #include "html/Quoting.h"
27 #include "HttpHdrContRange.h"
28 #include "HttpHeader.h"
29 #include "HttpHeaderRange.h"
30 #include "HttpReply.h"
31 #include "ip/tools.h"
32 #include "MemBuf.h"
33 #include "mime.h"
34 #include "rfc1738.h"
35 #include "SquidConfig.h"
36 #include "SquidString.h"
37 #include "StatCounters.h"
38 #include "Store.h"
39 #include "tools.h"
40 #include "util.h"
41 #include "wordlist.h"
42 
43 #if USE_DELAY_POOLS
44 #include "DelayPools.h"
45 #include "MemObject.h"
46 #endif
47 
48 #include <cerrno>
49 #if HAVE_REGEX_H
50 #include <regex.h>
51 #endif
52 
53 namespace Ftp
54 {
55 
56 struct GatewayFlags {
57 
58  /* passive mode */
61  bool pasv_only;
62  bool pasv_failed; // was FwdState::flags.ftp_pasv_failed
63 
64  /* authentication */
68 
69  /* other */
70  bool isdir;
74  bool tried_nlst;
76  bool dir_slash;
77  bool root_dir;
78  bool no_dotdot;
79  bool binary;
81  bool put;
82  bool put_mkdir;
84  bool listing;
86 };
87 
88 class Gateway;
89 typedef void (StateMethod)(Ftp::Gateway *);
90 
94 class Gateway : public Ftp::Client
95 {
97 
98 public:
99  Gateway(FwdState *);
100  ~Gateway() override;
101  char user[MAX_URL];
104  char *reply_hdr;
109  int conn_att;
111  time_t mdtm;
112  int64_t theSize;
114  char *filepath;
115  char *dirpath;
116  int64_t restart_offset;
117  char *proxy_host;
118  size_t list_width;
121  char typecode;
123 
125 
126 public:
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  void buildTitleUrl();
145  void writeReplyBody(const char *, size_t len);
146  void completeForwarding() override;
147  void processHeadResponse();
148  void processReplyBody() override;
149  void setCurrentOffset(int64_t offset) { currentOffset = offset; }
150  int64_t getCurrentOffset() const { return currentOffset; }
151 
152  void dataChannelConnected(const CommConnectCbParams &io) override;
153  static PF ftpDataWrite;
154  void timeout(const CommTimeoutCbParams &io) override;
156 
158  SBuf ftpRealm();
159  void loginFailed(void);
160 
161  void haveParsedReplyHeaders() override;
162 
163  virtual bool haveControlChannel(const char *caller_name) const;
164 
165 protected:
166  void handleControlReply() override;
167  void dataClosed(const CommCloseCbParams &io) override;
168 
169 private:
170  bool mayReadVirginReplyBody() const override;
171  // BodyConsumer for HTTP: consume request body.
172  void handleRequestBodyProducerAborted() override;
173 
174  void loginParser(const SBuf &login, bool escaped);
175 };
176 
177 } // namespace Ftp
178 
179 typedef Ftp::StateMethod FTPSM; // to avoid lots of non-changes
180 
182 
183 typedef struct {
184  char type;
185  int64_t size;
186  char *date;
187  char *name;
188  char *showname;
189  char *link;
190 } ftpListParts;
191 
192 #define CTRL_BUFLEN 16*1024
193 static char cbuf[CTRL_BUFLEN];
194 
195 /*
196  * State machine functions
197  * send == state transition
198  * read == wait for response, and select next state transition
199  * other == Transition logic
200  */
238 static FTPSM ftpFail;
241 
242 /************************************************
243 ** Debugs Levels used here **
244 *************************************************
245 0 CRITICAL Events
246 1 IMPORTANT Events
247  Protocol and Transmission failures.
248 2 FTP Protocol Chatter
249 3 Logic Flows
250 4 Data Parsing Flows
251 5 Data Dumps
252 7 ??
253 ************************************************/
254 
255 /************************************************
256 ** State Machine Description (excluding hacks) **
257 *************************************************
258 From To
259 ---------------------------------------
260 Welcome User
261 User Pass
262 Pass Type
263 Type TraverseDirectory / GetFile
264 TraverseDirectory Cwd / GetFile / ListDir
265 Cwd TraverseDirectory / Mkdir
266 GetFile Mdtm
267 Mdtm Size
268 Size Epsv
269 ListDir Epsv
270 Epsv FileOrList
271 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
272 Rest Retr
273 Retr / Nlst / List DataRead* (on datachannel)
274 DataRead* ReadTransferDone
275 ReadTransferDone DataTransferDone
276 Stor DataWrite* (on datachannel)
277 DataWrite* RequestPutBody** (from client)
278 RequestPutBody** DataWrite* / WriteTransferDone
279 WriteTransferDone DataTransferDone
280 DataTransferDone Quit
281 Quit -
282 ************************************************/
283 
285  ftpReadWelcome, /* BEGIN */
286  ftpReadUser, /* SENT_USER */
287  ftpReadPass, /* SENT_PASS */
288  ftpReadType, /* SENT_TYPE */
289  ftpReadMdtm, /* SENT_MDTM */
290  ftpReadSize, /* SENT_SIZE */
291  ftpReadEPRT, /* SENT_EPRT */
292  ftpReadPORT, /* SENT_PORT */
293  ftpReadEPSV, /* SENT_EPSV_ALL */
294  ftpReadEPSV, /* SENT_EPSV_1 */
295  ftpReadEPSV, /* SENT_EPSV_2 */
296  ftpReadPasv, /* SENT_PASV */
297  ftpReadCwd, /* SENT_CWD */
298  ftpReadList, /* SENT_LIST */
299  ftpReadList, /* SENT_NLST */
300  ftpReadRest, /* SENT_REST */
301  ftpReadRetr, /* SENT_RETR */
302  ftpReadStor, /* SENT_STOR */
303  ftpReadQuit, /* SENT_QUIT */
304  ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
305  ftpWriteTransferDone, /* WRITING_DATA (STOR) */
306  ftpReadMkdir, /* SENT_MKDIR */
307  nullptr, /* SENT_FEAT */
308  nullptr, /* SENT_PWD */
309  nullptr, /* SENT_CDUP*/
310  nullptr, /* SENT_DATA_REQUEST */
311  nullptr /* SENT_COMMAND */
312 };
313 
315 void
317 {
320  /* failed closes ctrl.conn and frees ftpState */
321 
322  /* NP: failure recovery may be possible when its only a data.conn failure.
323  * if the ctrl.conn is still fine, we can send ABOR down it and retry.
324  * Just need to watch out for wider Squid states like shutting down or reconfigure.
325  */
326 }
327 
329  AsyncJob("FtpStateData"),
330  Ftp::Client(fwdState),
331  password_url(0),
332  reply_hdr(nullptr),
333  reply_hdr_state(0),
334  conn_att(0),
335  login_att(0),
336  mdtm(-1),
337  theSize(-1),
338  pathcomps(nullptr),
339  filepath(nullptr),
340  dirpath(nullptr),
341  restart_offset(0),
342  proxy_host(nullptr),
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 
354  flags.pasv_supported = 1;
355 
356  flags.rest_supported = 1;
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 
396 void
397 Ftp::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 
448 void
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 
458  typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer;
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 
477  assert(Comm::IsConnOpen(conn));
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 
485 void
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 
502 static const char *Month[] = {
503  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
504  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
505 };
506 
507 static int
508 is_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 
519 static 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 
531 static ftpListParts *
532 ftpListParseParts(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;
539  } tokens[MAX_TOKENS];
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 (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 (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  p->name = xstrndup(ct + 1, l + 1);
699  break;
700 
701  case 's':
702  p->size = atoi(ct + 1);
703  break;
704 
705  case 'm':
706  tm = (time_t) strtol(ct + 1, &tmp, 0);
707 
708  if (tmp != ct + 1)
709  break; /* not a valid integer */
710 
711  p->date = xstrdup(ctime(&tm));
712 
713  *(strstr(p->date, "\n")) = '\0';
714 
715  break;
716 
717  case '/':
718  p->type = 'd';
719 
720  break;
721 
722  case 'r':
723  p->type = '-';
724 
725  break;
726 
727  case 'i':
728  break;
729 
730  default:
731  break;
732  }
733 
734 blank:
735  ct = strstr(ct, ",");
736 
737  if (ct) {
738  ++ct;
739  }
740  }
741 
742  if (p->type == 0) {
743  p->type = '-';
744  }
745 
746  if (p->name)
747  goto found;
748  else
749  safe_free(p->date);
750  }
751 
752 found:
753 
754  for (i = 0; i < n_tokens; ++i)
755  xfree(tokens[i].token);
756 
757  if (!p->name)
758  ftpListPartsFree(&p); /* cleanup */
759 
760  return p;
761 }
762 
763 bool
765 {
766  debugs(9, 7, "line={" << line << "}");
767 
768  if (strlen(line) > 1024) {
769  html << "<tr><td colspan=\"5\">" << line << "</td></tr>\n";
770  return true;
771  }
772 
773  SBuf prefix;
774  if (flags.dir_slash && dirpath && typecode != 'D') {
775  prefix.append(rfc1738_escape_part(dirpath));
776  prefix.append("/", 1);
777  }
778 
779  ftpListParts *parts = ftpListParseParts(line, flags);
780  if (!parts) {
781  html << "<tr class=\"entry\"><td colspan=\"5\">" << line << "</td></tr>\n";
782 
783  const char *p;
784  for (p = line; *p && xisspace(*p); ++p);
785  if (*p && !xisspace(*p))
786  flags.listformat_unknown = 1;
787 
788  return true;
789  }
790 
791  if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
792  ftpListPartsFree(&parts);
793  return false;
794  }
795 
796  parts->size += 1023;
797  parts->size >>= 10;
798  parts->showname = xstrdup(parts->name);
799 
800  /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
801  SBuf href(prefix);
802  href.append(rfc1738_escape_part(parts->name));
803 
804  SBuf text(parts->showname);
805 
806  SBuf icon, size, chdir, link;
807  switch (parts->type) {
808 
809  case 'd':
810  icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
811  mimeGetIconURL("internal-dir"),
812  "[DIR]");
813  href.append("/", 1); /* margin is allocated above */
814  break;
815 
816  case 'l':
817  icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
818  mimeGetIconURL("internal-link"),
819  "[LINK]");
820  /* sometimes there is an 'l' flag, but no "->" link */
821 
822  if (parts->link) {
823  SBuf link2(html_quote(rfc1738_escape(parts->link)));
824  link.appendf(" -&gt; <a href=\"%s" SQUIDSBUFPH "\">%s</a>",
825  link2[0] != '/' ? prefix.c_str() : "", SQUIDSBUFPRINT(link2),
826  html_quote(parts->link));
827  }
828 
829  break;
830 
831  case '\0':
832  icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
833  mimeGetIconURL(parts->name),
834  "[UNKNOWN]");
835  chdir.appendf("<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
836  "alt=\"[DIR]\"></a>",
837  rfc1738_escape_part(parts->name),
838  mimeGetIconURL("internal-dir"));
839  break;
840 
841  case '-':
842 
843  default:
844  icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
845  mimeGetIconURL(parts->name),
846  "[FILE]");
847  size.appendf(" %6" PRId64 "k", parts->size);
848  break;
849  }
850 
851  SBuf view, download;
852  if (parts->type != 'd') {
853  if (mimeGetViewOption(parts->name)) {
854  view.appendf("<a href=\"" SQUIDSBUFPH ";type=a\"><img border=\"0\" src=\"%s\" "
855  "alt=\"[VIEW]\"></a>",
856  SQUIDSBUFPRINT(href), mimeGetIconURL("internal-view"));
857  }
858 
859  if (mimeGetDownloadOption(parts->name)) {
860  download.appendf("<a href=\"" SQUIDSBUFPH ";type=i\"><img border=\"0\" src=\"%s\" "
861  "alt=\"[DOWNLOAD]\"></a>",
862  SQUIDSBUFPRINT(href), mimeGetIconURL("internal-download"));
863  }
864  }
865 
866  /* construct the table row from parts. */
867  html << "<tr class=\"entry\">"
868  "<td class=\"icon\"><a href=\"" << href << "\">" << icon << "</a></td>"
869  "<td class=\"filename\"><a href=\"" << href << "\">" << html_quote(text.c_str()) << "</a></td>"
870  "<td class=\"date\">" << parts->date << "</td>"
871  "<td class=\"size\">" << size << "</td>"
872  "<td class=\"actions\">" << chdir << view << download << link << "</td>"
873  "</tr>\n";
874 
875  ftpListPartsFree(&parts);
876  return true;
877 }
878 
879 void
881 {
882  char *buf = data.readBuf->content();
883  char *sbuf; /* NULL-terminated copy of termedBuf */
884  char *end;
885  char *line;
886  char *s;
887  size_t linelen;
888  size_t usable;
889  size_t len = data.readBuf->contentSize();
890 
891  if (!len) {
892  debugs(9, 3, "no content to parse for " << entry->url() );
893  return;
894  }
895 
896  /*
897  * We need a NULL-terminated buffer for scanning, ick
898  */
899  sbuf = (char *)xmalloc(len + 1);
900  xstrncpy(sbuf, buf, len + 1);
901  end = sbuf + len - 1;
902 
903  while (*end != '\r' && *end != '\n' && end > sbuf)
904  --end;
905 
906  usable = end - sbuf;
907 
908  debugs(9, 3, "usable = " << usable << " of " << len << " bytes.");
909 
910  if (usable == 0) {
911  if (buf[0] == '\0' && len == 1) {
912  debugs(9, 3, "NIL ends data from " << entry->url() << " transfer problem?");
913  data.readBuf->consume(len);
914  } else {
915  debugs(9, 3, "didn't find end for " << entry->url());
916  debugs(9, 3, "buffer remains (" << len << " bytes) '" << rfc1738_do_escape(buf,0) << "'");
917  }
918  xfree(sbuf);
919  return;
920  }
921 
922  debugs(9, 3, (unsigned long int)len << " bytes to play with");
923 
924  line = (char *)memAllocate(MEM_4K_BUF);
925  ++end;
926  s = sbuf;
927  s += strspn(s, crlf);
928 
929  for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
930  debugs(9, 7, "s = {" << s << "}");
931  linelen = strcspn(s, crlf) + 1;
932 
933  if (linelen < 2)
934  break;
935 
936  if (linelen > 4096)
937  linelen = 4096;
938 
939  xstrncpy(line, s, linelen);
940 
941  debugs(9, 7, "{" << line << "}");
942 
943  if (!strncmp(line, "total", 5))
944  continue;
945 
946  MemBuf htmlPage;
947  htmlPage.init();
948  PackableStream html(htmlPage);
949 
950  if (htmlifyListEntry(line, html)) {
951  html.flush();
952  debugs(9, 7, "listing append: t = {" << htmlPage.contentSize() << ", '" << htmlPage.content() << "'}");
953  listing.append(htmlPage.content(), htmlPage.contentSize());
954  }
955  }
956 
957  debugs(9, 7, "Done.");
958  data.readBuf->consume(usable);
959  memFree(line, MEM_4K_BUF);
960  xfree(sbuf);
961 }
962 
963 void
965 {
966  debugs(9, 3, status());
967 
968  if (request->method == Http::METHOD_HEAD && (flags.isdir || theSize != -1)) {
969  serverComplete();
970  return;
971  }
972 
973  /* Directory listings are special. They write ther own headers via the error objects */
974  if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
975  appendSuccessHeader();
976 
977  if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
978  /*
979  * probably was aborted because content length exceeds one
980  * of the maximum size limits.
981  */
982  abortAll("entry aborted after calling appendSuccessHeader()");
983  return;
984  }
985 
986 #if USE_ADAPTATION
987 
988  if (adaptationAccessCheckPending) {
989  debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
990  return;
991  }
992 
993 #endif
994 
995  if (flags.isdir) {
996  if (!flags.listing) {
997  flags.listing = 1;
998  listing.reset();
999  }
1000  parseListing();
1001  maybeReadVirginBody();
1002  return;
1003  } else if (const auto csize = data.readBuf->contentSize()) {
1004  writeReplyBody(data.readBuf->content(), csize);
1005  debugs(9, 5, "consuming " << csize << " bytes of readBuf");
1006  data.readBuf->consume(csize);
1007  }
1008 
1009  entry->flush();
1010 
1011  maybeReadVirginBody();
1012 }
1013 
1030 int
1032 {
1033  /* default username */
1034  xstrncpy(user, "anonymous", MAX_URL);
1035 
1036 #if HAVE_AUTH_MODULE_BASIC
1037  /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1038  const auto auth(req_hdr->getAuthToken(Http::HdrType::AUTHORIZATION, "Basic"));
1039  if (!auth.isEmpty()) {
1040  flags.authenticated = 1;
1041  loginParser(auth, false);
1042  }
1043  /* we fail with authorization-required error later IFF the FTP server requests it */
1044 #else
1045  (void)req_hdr;
1046 #endif
1047 
1048  /* Test URL login syntax. Overrides any headers received. */
1049  loginParser(request->url.userInfo(), true);
1050 
1051  // XXX: We we keep default "anonymous" instead of properly supporting empty usernames.
1052  Assure(user[0]);
1053 
1054  /* name + password == success */
1055  if (password[0])
1056  return 1;
1057 
1058  /* Setup default FTP password settings */
1059  /* this has to be done last so that we can have a no-password case above. */
1060  if (!password[0]) {
1061  if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
1062  xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
1063  flags.tried_auth_anonymous=1;
1064  return 1;
1065  } else if (!flags.tried_auth_nopass) {
1066  xstrncpy(password, null_string, MAX_URL);
1067  flags.tried_auth_nopass=1;
1068  return 1;
1069  }
1070  }
1071 
1072  return 0; /* different username */
1073 }
1074 
1075 void
1077 {
1078  // If typecode was specified, extract it and leave just the filename in
1079  // url.path. Tolerate trailing garbage or missing typecode value. Roughly:
1080  // [filename] ;type=[typecode char] [trailing garbage]
1081  static const SBuf middle(";type=");
1082  const auto typeSpecStart = request->url.path().find(middle);
1083  if (typeSpecStart != SBuf::npos) {
1084  const auto fullPath = request->url.path();
1085  const auto typecodePos = typeSpecStart + middle.length();
1086  typecode = (typecodePos < fullPath.length()) ?
1087  static_cast<char>(xtoupper(fullPath[typecodePos])) : '\0';
1088  request->url.path(fullPath.substr(0, typeSpecStart));
1089  }
1090 
1091  int l = request->url.path().length();
1092  /* check for null path */
1093 
1094  if (!l) {
1095  flags.isdir = 1;
1096  flags.root_dir = 1;
1097  flags.need_base_href = 1; /* Work around broken browsers */
1098  } else if (!request->url.path().cmp("/%2f/")) {
1099  /* UNIX root directory */
1100  flags.isdir = 1;
1101  flags.root_dir = 1;
1102  } else if ((l >= 1) && (request->url.path()[l-1] == '/')) {
1103  /* Directory URL, ending in / */
1104  flags.isdir = 1;
1105 
1106  if (l == 1)
1107  flags.root_dir = 1;
1108  } else {
1109  flags.dir_slash = 1;
1110  }
1111 }
1112 
1113 void
1115 {
1116  title_url = "ftp://";
1117 
1118  if (strcmp(user, "anonymous")) {
1119  title_url.append(user);
1120  title_url.append("@");
1121  }
1122 
1123  SBuf authority = request->url.authority(request->url.getScheme() != AnyP::PROTO_FTP);
1124 
1125  title_url.append(authority.rawContent(), authority.length());
1126  title_url.append(request->url.path().rawContent(), request->url.path().length());
1127 
1128  base_href = "ftp://";
1129 
1130  if (strcmp(user, "anonymous") != 0) {
1131  base_href.append(rfc1738_escape_part(user));
1132 
1133  if (password_url) {
1134  base_href.append(":");
1135  base_href.append(rfc1738_escape_part(password));
1136  }
1137 
1138  base_href.append("@");
1139  }
1140 
1141  base_href.append(authority.rawContent(), authority.length());
1142  base_href.append(request->url.path().rawContent(), request->url.path().length());
1143  base_href.append("/");
1144 }
1145 
1146 void
1148 {
1149  if (!checkAuth(&request->header)) {
1150  /* create appropriate reply */
1151  SBuf realm(ftpRealm()); // local copy so SBuf will not disappear too early
1152  const auto reply = ftpAuthRequired(request.getRaw(), realm, fwd->al);
1153  entry->replaceHttpReply(reply);
1154  serverComplete();
1155  return;
1156  }
1157 
1158  checkUrlpath();
1159  buildTitleUrl();
1160  debugs(9, 5, "FD " << (ctrl.conn ? ctrl.conn->fd : -1) << " : host=" << request->url.host() <<
1161  ", path=" << request->url.path() << ", user=" << user << ", passwd=" << password);
1162  state = BEGIN;
1164 }
1165 
1166 /* ====================================================================== */
1167 
1168 void
1170 {
1172  if (ctrl.message == nullptr)
1173  return; // didn't get complete reply yet
1174 
1175  /* Copy the message except for the last line to cwd_message to be
1176  * printed in error messages.
1177  */
1178  for (wordlist *w = ctrl.message; w && w->next; w = w->next) {
1179  cwd_message.append('\n');
1180  cwd_message.append(w->key);
1181  }
1182 
1183  FTP_SM_FUNCS[state] (this);
1184 }
1185 
1186 /* ====================================================================== */
1187 
1188 static void
1190 {
1191  int code = ftpState->ctrl.replycode;
1192  debugs(9, 3, MYNAME);
1193 
1194  if (ftpState->flags.pasv_only)
1195  ++ ftpState->login_att;
1196 
1197  if (code == 220) {
1198  if (ftpState->ctrl.message) {
1199  if (strstr(ftpState->ctrl.message->key, "NetWare"))
1200  ftpState->flags.skip_whitespace = 1;
1201  }
1202 
1203  ftpSendUser(ftpState);
1204  } else if (code == 120) {
1205  if (nullptr != ftpState->ctrl.message)
1206  debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key);
1207 
1208  return;
1209  } else {
1210  ftpFail(ftpState);
1211  }
1212 }
1213 
1219 void
1221 {
1222  ErrorState *err = nullptr;
1223 
1224  if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) {
1225  if (ctrl.replycode == 421 || ctrl.replycode == 426) {
1226  // 421/426 - Service Overload - retry permitted.
1227  err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request, fwd->al);
1228  } else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) {
1229  // 43x - Invalid or Credential Error - retry challenge required.
1230  err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
1231  } else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) {
1232  // 53x - Credentials Missing - retry challenge required
1233  if (password_url) // but they were in the URI! major fail.
1234  err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request, fwd->al);
1235  else
1236  err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
1237  }
1238  }
1239 
1240  if (!err) {
1241  ftpFail(this);
1242  return;
1243  }
1244 
1245  failed(ERR_NONE, ctrl.replycode, err);
1246  // any other problems are general falures.
1247 
1248  HttpReply *newrep = err->BuildHttpReply();
1249  delete err;
1250 
1251 #if HAVE_AUTH_MODULE_BASIC
1252  /* add Authenticate header */
1253  // XXX: performance regression. c_str() may reallocate
1254  SBuf realm(ftpRealm()); // local copy so SBuf will not disappear too early
1255  newrep->header.putAuth("Basic", realm.c_str());
1256 #endif
1257 
1258  // add it to the store entry for response....
1259  entry->replaceHttpReply(newrep);
1260  serverComplete();
1261 }
1262 
1263 SBuf
1265 {
1266  SBuf realm;
1267 
1268  /* This request is not fully authenticated */
1269  realm.appendf("FTP %s ", user);
1270  if (!request)
1271  realm.append("unknown", 7);
1272  else {
1273  realm.append(request->url.host());
1274  const auto &rport = request->url.port();
1275  if (rport && *rport != 21)
1276  realm.appendf(" port %hu", *rport);
1277  }
1278  return realm;
1279 }
1280 
1281 static void
1283 {
1284  /* check the server control channel is still available */
1285  if (!ftpState || !ftpState->haveControlChannel("ftpSendUser"))
1286  return;
1287 
1288  if (ftpState->proxy_host != nullptr)
1289  snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n", ftpState->user, ftpState->request->url.host());
1290  else
1291  snprintf(cbuf, CTRL_BUFLEN, "USER %s\r\n", ftpState->user);
1292 
1293  ftpState->writeCommand(cbuf);
1294 
1295  ftpState->state = Ftp::Client::SENT_USER;
1296 }
1297 
1298 static void
1300 {
1301  int code = ftpState->ctrl.replycode;
1302  debugs(9, 3, MYNAME);
1303 
1304  if (code == 230) {
1305  ftpReadPass(ftpState);
1306  } else if (code == 331) {
1307  ftpSendPass(ftpState);
1308  } else {
1309  ftpState->loginFailed();
1310  }
1311 }
1312 
1313 static void
1315 {
1316  /* check the server control channel is still available */
1317  if (!ftpState || !ftpState->haveControlChannel("ftpSendPass"))
1318  return;
1319 
1320  snprintf(cbuf, CTRL_BUFLEN, "PASS %s\r\n", ftpState->password);
1321  ftpState->writeCommand(cbuf);
1322  ftpState->state = Ftp::Client::SENT_PASS;
1323 }
1324 
1325 static void
1327 {
1328  int code = ftpState->ctrl.replycode;
1329  debugs(9, 3, "code=" << code);
1330 
1331  if (code == 230) {
1332  ftpSendType(ftpState);
1333  } else {
1334  ftpState->loginFailed();
1335  }
1336 }
1337 
1338 static void
1340 {
1341  /* check the server control channel is still available */
1342  if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
1343  return;
1344 
1345  /*
1346  * Ref section 3.2.2 of RFC 1738
1347  */
1348  char mode = ftpState->typecode;
1349 
1350  switch (mode) {
1351 
1352  case 'D':
1353  mode = 'A';
1354  break;
1355 
1356  case 'A':
1357 
1358  case 'I':
1359  break;
1360 
1361  default:
1362 
1363  if (ftpState->flags.isdir) {
1364  mode = 'A';
1365  } else {
1366  auto t = ftpState->request->url.path().rfind('/');
1367  // XXX: performance regression, c_str() may reallocate
1368  SBuf filename = ftpState->request->url.path().substr(t != SBuf::npos ? t + 1 : 0);
1369  mode = mimeGetTransferMode(filename.c_str());
1370  }
1371 
1372  break;
1373  }
1374 
1375  if (mode == 'I')
1376  ftpState->flags.binary = 1;
1377  else
1378  ftpState->flags.binary = 0;
1379 
1380  snprintf(cbuf, CTRL_BUFLEN, "TYPE %c\r\n", mode);
1381 
1382  ftpState->writeCommand(cbuf);
1383 
1384  ftpState->state = Ftp::Client::SENT_TYPE;
1385 }
1386 
1387 static void
1389 {
1390  int code = ftpState->ctrl.replycode;
1391  char *path;
1392  char *d, *p;
1393  debugs(9, 3, "code=" << code);
1394 
1395  if (code == 200) {
1396  p = path = SBufToCstring(ftpState->request->url.path());
1397 
1398  if (*p == '/')
1399  ++p;
1400 
1401  while (*p) {
1402  d = p;
1403  p += strcspn(p, "/");
1404 
1405  if (*p) {
1406  *p = '\0';
1407  ++p;
1408  }
1409 
1410  rfc1738_unescape(d);
1411 
1412  if (*d)
1413  wordlistAdd(&ftpState->pathcomps, d);
1414  }
1415 
1416  xfree(path);
1417 
1418  if (ftpState->pathcomps)
1419  ftpTraverseDirectory(ftpState);
1420  else
1421  ftpListDir(ftpState);
1422  } else {
1423  ftpFail(ftpState);
1424  }
1425 }
1426 
1427 static void
1429 {
1430  debugs(9, 4, (ftpState->filepath ? ftpState->filepath : "<NULL>"));
1431 
1432  safe_free(ftpState->dirpath);
1433  ftpState->dirpath = ftpState->filepath;
1434  ftpState->filepath = nullptr;
1435 
1436  /* Done? */
1437 
1438  if (ftpState->pathcomps == nullptr) {
1439  debugs(9, 3, "the final component was a directory");
1440  ftpListDir(ftpState);
1441  return;
1442  }
1443 
1444  /* Go to next path component */
1445  ftpState->filepath = wordlistChopHead(& ftpState->pathcomps);
1446 
1447  /* Check if we are to CWD or RETR */
1448  if (ftpState->pathcomps != nullptr || ftpState->flags.isdir) {
1449  ftpSendCwd(ftpState);
1450  } else {
1451  debugs(9, 3, "final component is probably a file");
1452  ftpGetFile(ftpState);
1453  return;
1454  }
1455 }
1456 
1457 static void
1459 {
1460  char *path = nullptr;
1461 
1462  /* check the server control channel is still available */
1463  if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd"))
1464  return;
1465 
1466  debugs(9, 3, MYNAME);
1467 
1468  path = ftpState->filepath;
1469 
1470  if (!strcmp(path, "..") || !strcmp(path, "/")) {
1471  ftpState->flags.no_dotdot = 1;
1472  } else {
1473  ftpState->flags.no_dotdot = 0;
1474  }
1475 
1476  snprintf(cbuf, CTRL_BUFLEN, "CWD %s\r\n", path);
1477 
1478  ftpState->writeCommand(cbuf);
1479 
1480  ftpState->state = Ftp::Client::SENT_CWD;
1481 }
1482 
1483 static void
1485 {
1486  int code = ftpState->ctrl.replycode;
1487  debugs(9, 3, MYNAME);
1488 
1489  if (code >= 200 && code < 300) {
1490  /* CWD OK */
1491  ftpState->unhack();
1492 
1493  /* Reset cwd_message to only include the last message */
1494  ftpState->cwd_message.reset("");
1495  for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
1496  ftpState->cwd_message.append('\n');
1497  ftpState->cwd_message.append(w->key);
1498  }
1499  ftpState->ctrl.message = nullptr;
1500 
1501  /* Continue to traverse the path */
1502  ftpTraverseDirectory(ftpState);
1503  } else {
1504  /* CWD FAILED */
1505 
1506  if (!ftpState->flags.put)
1507  ftpFail(ftpState);
1508  else
1509  ftpSendMkdir(ftpState);
1510  }
1511 }
1512 
1513 static void
1515 {
1516  char *path = nullptr;
1517 
1518  /* check the server control channel is still available */
1519  if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir"))
1520  return;
1521 
1522  path = ftpState->filepath;
1523  debugs(9, 3, "with path=" << path);
1524  snprintf(cbuf, CTRL_BUFLEN, "MKD %s\r\n", path);
1525  ftpState->writeCommand(cbuf);
1526  ftpState->state = Ftp::Client::SENT_MKDIR;
1527 }
1528 
1529 static void
1531 {
1532  char *path = ftpState->filepath;
1533  int code = ftpState->ctrl.replycode;
1534 
1535  debugs(9, 3, "path " << path << ", code " << code);
1536 
1537  if (code == 257) { /* success */
1538  ftpSendCwd(ftpState);
1539  } else if (code == 550) { /* dir exists */
1540 
1541  if (ftpState->flags.put_mkdir) {
1542  ftpState->flags.put_mkdir = 1;
1543  ftpSendCwd(ftpState);
1544  } else
1545  ftpSendReply(ftpState);
1546  } else
1547  ftpSendReply(ftpState);
1548 }
1549 
1550 static void
1552 {
1553  assert(*ftpState->filepath != '\0');
1554  ftpState->flags.isdir = 0;
1555  ftpSendMdtm(ftpState);
1556 }
1557 
1558 static void
1560 {
1561  if (ftpState->flags.dir_slash) {
1562  debugs(9, 3, "Directory path did not end in /");
1563  ftpState->title_url.append("/");
1564  ftpState->flags.isdir = 1;
1565  }
1566 
1567  ftpSendPassive(ftpState);
1568 }
1569 
1570 static void
1572 {
1573  /* check the server control channel is still available */
1574  if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm"))
1575  return;
1576 
1577  assert(*ftpState->filepath != '\0');
1578  snprintf(cbuf, CTRL_BUFLEN, "MDTM %s\r\n", ftpState->filepath);
1579  ftpState->writeCommand(cbuf);
1580  ftpState->state = Ftp::Client::SENT_MDTM;
1581 }
1582 
1583 static void
1585 {
1586  int code = ftpState->ctrl.replycode;
1587  debugs(9, 3, MYNAME);
1588 
1589  if (code == 213) {
1590  ftpState->mdtm = Time::ParseIso3307(ftpState->ctrl.last_reply);
1591  ftpState->unhack();
1592  } else if (code < 0) {
1593  ftpFail(ftpState);
1594  return;
1595  }
1596 
1597  ftpSendSize(ftpState);
1598 }
1599 
1600 static void
1602 {
1603  /* check the server control channel is still available */
1604  if (!ftpState || !ftpState->haveControlChannel("ftpSendSize"))
1605  return;
1606 
1607  /* Only send SIZE for binary transfers. The returned size
1608  * is useless on ASCII transfers */
1609 
1610  if (ftpState->flags.binary) {
1611  assert(ftpState->filepath != nullptr);
1612  assert(*ftpState->filepath != '\0');
1613  snprintf(cbuf, CTRL_BUFLEN, "SIZE %s\r\n", ftpState->filepath);
1614  ftpState->writeCommand(cbuf);
1615  ftpState->state = Ftp::Client::SENT_SIZE;
1616  } else
1617  /* Skip to next state no non-binary transfers */
1618  ftpSendPassive(ftpState);
1619 }
1620 
1621 static void
1623 {
1624  int code = ftpState->ctrl.replycode;
1625  debugs(9, 3, MYNAME);
1626 
1627  if (code == 213) {
1628  ftpState->unhack();
1629  ftpState->theSize = strtoll(ftpState->ctrl.last_reply, nullptr, 10);
1630 
1631  if (ftpState->theSize == 0) {
1632  debugs(9, 2, "SIZE reported " <<
1633  ftpState->ctrl.last_reply << " on " <<
1634  ftpState->title_url);
1635  ftpState->theSize = -1;
1636  }
1637  } else if (code < 0) {
1638  ftpFail(ftpState);
1639  return;
1640  }
1641 
1642  ftpSendPassive(ftpState);
1643 }
1644 
1645 static void
1647 {
1648  Ip::Address srvAddr; // unused
1649  if (ftpState->handleEpsvReply(srvAddr)) {
1650  if (ftpState->ctrl.message == nullptr)
1651  return; // didn't get complete reply yet
1652 
1653  ftpState->connectDataChannel();
1654  }
1655 }
1656 
1661 static void
1663 {
1665  if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
1666  return;
1667 
1668  debugs(9, 3, MYNAME);
1669 
1672  if (ftpState->request->method == Http::METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
1673  ftpState->processHeadResponse(); // may call serverComplete
1674  return;
1675  }
1676 
1677  if (ftpState->sendPassive()) {
1678  // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
1679  if (ftpState->state == Ftp::Client::SENT_EPSV_ALL)
1680  ftpState->flags.epsv_all_sent = true;
1681  }
1682 }
1683 
1684 void
1686 {
1687  debugs(9, 5, "handling HEAD response");
1688  ftpSendQuit(this);
1689  appendSuccessHeader();
1690 
1691  /*
1692  * On rare occasions I'm seeing the entry get aborted after
1693  * readControlReply() and before here, probably when
1694  * trying to write to the client.
1695  */
1696  if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1697  abortAll("entry aborted while processing HEAD");
1698  return;
1699  }
1700 
1701 #if USE_ADAPTATION
1702  if (adaptationAccessCheckPending) {
1703  debugs(9,3, "returning due to adaptationAccessCheckPending");
1704  return;
1705  }
1706 #endif
1707 
1708  // processReplyBody calls serverComplete() since there is no body
1709  processReplyBody();
1710 }
1711 
1712 static void
1714 {
1715  Ip::Address srvAddr; // unused
1716  if (ftpState->handlePasvReply(srvAddr))
1717  ftpState->connectDataChannel();
1718  else {
1719  ftpFail(ftpState);
1720  // Currently disabled, does not work correctly:
1721  // ftpSendEPRT(ftpState);
1722  return;
1723  }
1724 }
1725 
1726 void
1728 {
1729  debugs(9, 3, MYNAME);
1730  dataConnWait.finish();
1731 
1732  if (io.flag != Comm::OK) {
1733  debugs(9, 2, "Failed to connect. Retrying via another method.");
1734 
1735  // ABORT on timeouts. server may be waiting on a broken TCP link.
1736  if (io.xerrno == Comm::TIMEOUT)
1737  writeCommand("ABOR\r\n");
1738 
1739  // try another connection attempt with some other method
1740  ftpSendPassive(this);
1741  return;
1742  }
1743 
1744  data.opened(io.conn, dataCloser());
1745  ftpRestOrList(this);
1746 }
1747 
1748 static void
1749 ftpOpenListenSocket(Ftp::Gateway * ftpState, int fallback)
1750 {
1752  if (ftpState->data.conn != nullptr) {
1753  if ((ftpState->data.conn->flags & COMM_REUSEADDR))
1754  // NP: in fact it points to the control channel. just clear it.
1755  ftpState->data.clear();
1756  else
1757  ftpState->data.close();
1758  }
1759  safe_free(ftpState->data.host);
1760 
1761  if (!Comm::IsConnOpen(ftpState->ctrl.conn)) {
1762  debugs(9, 5, "The control connection to the remote end is closed");
1763  return;
1764  }
1765 
1766  /*
1767  * Set up a listen socket on the same local address as the
1768  * control connection.
1769  */
1771  temp->local = ftpState->ctrl.conn->local;
1772 
1773  /*
1774  * REUSEADDR is needed in fallback mode, since the same port is
1775  * used for both control and data.
1776  */
1777  if (fallback) {
1778  int on = 1;
1779  errno = 0;
1780  if (setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR,
1781  (char *) &on, sizeof(on)) == -1) {
1782  int xerrno = errno;
1783  // SO_REUSEADDR is only an optimization, no need to be verbose about error
1784  debugs(9, 4, "setsockopt failed: " << xstrerr(xerrno));
1785  }
1786  ftpState->ctrl.conn->flags |= COMM_REUSEADDR;
1787  temp->flags |= COMM_REUSEADDR;
1788  } else {
1789  /* if not running in fallback mode a new port needs to be retrieved */
1790  temp->local.port(0);
1791  }
1792 
1793  ftpState->listenForDataChannel(temp);
1794 }
1795 
1796 static void
1798 {
1799  /* check the server control channel is still available */
1800  if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
1801  return;
1802 
1803  if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
1804  debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1805  return;
1806  }
1807 
1808  debugs(9, 3, MYNAME);
1809  ftpState->flags.pasv_supported = 0;
1810  ftpOpenListenSocket(ftpState, 0);
1811 
1812  if (!Comm::IsConnOpen(ftpState->data.listenConn)) {
1813  if ( ftpState->data.listenConn != nullptr && !ftpState->data.listenConn->local.isIPv4() ) {
1814  /* non-IPv4 CANNOT send PORT command. */
1815  /* we got here by attempting and failing an EPRT */
1816  /* using the same reply code should simulate a PORT failure */
1817  ftpReadPORT(ftpState);
1818  return;
1819  }
1820 
1821  /* XXX Need to set error message */
1822  ftpFail(ftpState);
1823  return;
1824  }
1825 
1826  // pull out the internal IP address bytes to send in PORT command...
1827  // source them from the listen_conn->local
1828 
1829  struct addrinfo *AI = nullptr;
1830  ftpState->data.listenConn->local.getAddrInfo(AI, AF_INET);
1831  unsigned char *addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
1832  unsigned char *portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
1833  snprintf(cbuf, CTRL_BUFLEN, "PORT %d,%d,%d,%d,%d,%d\r\n",
1834  addrptr[0], addrptr[1], addrptr[2], addrptr[3],
1835  portptr[0], portptr[1]);
1836  ftpState->writeCommand(cbuf);
1837  ftpState->state = Ftp::Client::SENT_PORT;
1838 
1840 }
1841 
1842 static void
1844 {
1845  int code = ftpState->ctrl.replycode;
1846  debugs(9, 3, MYNAME);
1847 
1848  if (code != 200) {
1849  /* Fall back on using the same port as the control connection */
1850  debugs(9, 3, "PORT not supported by remote end");
1851  ftpOpenListenSocket(ftpState, 1);
1852  }
1853 
1854  ftpRestOrList(ftpState);
1855 }
1856 
1857 static void
1859 {
1860  int code = ftpState->ctrl.replycode;
1861  debugs(9, 3, MYNAME);
1862 
1863  if (code != 200) {
1864  /* Failover to attempting old PORT command. */
1865  debugs(9, 3, "EPRT not supported by remote end");
1866  ftpSendPORT(ftpState);
1867  return;
1868  }
1869 
1870  ftpRestOrList(ftpState);
1871 }
1872 
1877 void
1879 {
1880  debugs(9, 3, MYNAME);
1881 
1882  if (!Comm::IsConnOpen(ctrl.conn)) { /*Close handlers will cleanup*/
1883  debugs(9, 5, "The control connection to the remote end is closed");
1884  return;
1885  }
1886 
1887  if (io.flag != Comm::OK) {
1888  data.listenConn->close();
1889  data.listenConn = nullptr;
1890  debugs(9, DBG_IMPORTANT, "FTP AcceptDataConnection: " << io.conn << ": " << xstrerr(io.xerrno));
1891  // TODO: need to send error message on control channel
1892  ftpFail(this);
1893  return;
1894  }
1895 
1896  if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1897  abortAll("entry aborted when accepting data conn");
1898  data.listenConn->close();
1899  data.listenConn = nullptr;
1900  io.conn->close();
1901  return;
1902  }
1903 
1904  /* data listening conn is no longer even open. abort. */
1905  if (!Comm::IsConnOpen(data.listenConn)) {
1906  data.listenConn = nullptr; // ensure that it's cleared and not just closed.
1907  return;
1908  }
1909 
1910  /* data listening conn is no longer even open. abort. */
1911  if (!Comm::IsConnOpen(data.conn)) {
1912  data.clear(); // ensure that it's cleared and not just closed.
1913  return;
1914  }
1915 
1922  if (Config.Ftp.sanitycheck) {
1923  // accept if either our data or ctrl connection is talking to this remote peer.
1924  if (data.conn->remote != io.conn->remote && ctrl.conn->remote != io.conn->remote) {
1925  debugs(9, DBG_IMPORTANT,
1926  "ERROR: FTP data connection from unexpected server (" <<
1927  io.conn->remote << "), expecting " <<
1928  data.conn->remote << " or " << ctrl.conn->remote);
1929 
1930  /* close the bad sources connection down ASAP. */
1931  io.conn->close();
1932 
1933  /* drop the bad connection (io) by ignoring the attempt. */
1934  return;
1935  }
1936  }
1937 
1939  data.close();
1940  data.opened(io.conn, dataCloser());
1941  data.addr(io.conn->remote);
1942 
1943  debugs(9, 3, "Connected data socket on " <<
1944  io.conn << ". FD table says: " <<
1945  "ctrl-peer= " << fd_table[ctrl.conn->fd].ipaddr << ", " <<
1946  "data-peer= " << fd_table[data.conn->fd].ipaddr);
1947 
1948  assert(haveControlChannel("ftpAcceptDataConnection"));
1949  assert(ctrl.message == nullptr);
1950 
1951  // Ctrl channel operations will determine what happens to this data connection
1952 }
1953 
1954 static void
1956 {
1957  debugs(9, 3, MYNAME);
1958 
1959  if (ftpState->typecode == 'D') {
1960  ftpState->flags.isdir = 1;
1961 
1962  if (ftpState->flags.put) {
1963  ftpSendMkdir(ftpState); /* PUT name;type=d */
1964  } else {
1965  ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
1966  }
1967  } else if (ftpState->flags.put) {
1968  ftpSendStor(ftpState);
1969  } else if (ftpState->flags.isdir)
1970  ftpSendList(ftpState);
1971  else if (ftpState->restartable())
1972  ftpSendRest(ftpState);
1973  else
1974  ftpSendRetr(ftpState);
1975 }
1976 
1977 static void
1979 {
1980  /* check the server control channel is still available */
1981  if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
1982  return;
1983 
1984  debugs(9, 3, MYNAME);
1985 
1986  if (ftpState->filepath != nullptr) {
1987  /* Plain file upload */
1988  snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath);
1989  ftpState->writeCommand(cbuf);
1990  ftpState->state = Ftp::Client::SENT_STOR;
1991  } else if (ftpState->request->header.getInt64(Http::HdrType::CONTENT_LENGTH) > 0) {
1992  /* File upload without a filename. use STOU to generate one */
1993  snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n");
1994  ftpState->writeCommand(cbuf);
1995  ftpState->state = Ftp::Client::SENT_STOR;
1996  } else {
1997  /* No file to transfer. Only create directories if needed */
1998  ftpSendReply(ftpState);
1999  }
2000 }
2001 
2003 static void
2005 {
2006  ftpState->readStor();
2007 }
2008 
2010 {
2011  int code = ctrl.replycode;
2012  debugs(9, 3, MYNAME);
2013 
2014  if (code == 125 || (code == 150 && Comm::IsConnOpen(data.conn))) {
2015  if (!originalRequest()->body_pipe) {
2016  debugs(9, 3, "zero-size STOR?");
2017  state = WRITING_DATA; // make ftpWriteTransferDone() responsible
2018  dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2019  return;
2020  }
2021 
2022  if (!startRequestBodyFlow()) { // register to receive body data
2023  ftpFail(this);
2024  return;
2025  }
2026 
2027  /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
2028  debugs(9, 3, "starting data transfer");
2029  switchTimeoutToDataChannel();
2030  sendMoreRequestBody();
2031  fwd->dontRetry(true); // do not permit re-trying if the body was sent.
2032  state = WRITING_DATA;
2033  debugs(9, 3, "writing data channel");
2034  } else if (code == 150) {
2035  /* When client code is 150 with no data channel, Accept data channel. */
2036  debugs(9, 3, "ftpReadStor: accepting data channel");
2037  listenForDataChannel(data.conn);
2038  } else {
2039  debugs(9, DBG_IMPORTANT, "ERROR: Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
2040  ftpFail(this);
2041  }
2042 }
2043 
2044 static void
2046 {
2047  /* check the server control channel is still available */
2048  if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
2049  return;
2050 
2051  debugs(9, 3, MYNAME);
2052 
2053  snprintf(cbuf, CTRL_BUFLEN, "REST %" PRId64 "\r\n", ftpState->restart_offset);
2054  ftpState->writeCommand(cbuf);
2055  ftpState->state = Ftp::Client::SENT_REST;
2056 }
2057 
2058 int
2060 {
2061  if (restart_offset > 0)
2062  return 1;
2063 
2064  if (!request->range)
2065  return 0;
2066 
2067  if (!flags.binary)
2068  return 0;
2069 
2070  if (theSize <= 0)
2071  return 0;
2072 
2073  int64_t desired_offset = request->range->lowestOffset(theSize);
2074 
2075  if (desired_offset <= 0)
2076  return 0;
2077 
2078  if (desired_offset >= theSize)
2079  return 0;
2080 
2081  restart_offset = desired_offset;
2082  return 1;
2083 }
2084 
2085 static void
2087 {
2088  int code = ftpState->ctrl.replycode;
2089  debugs(9, 3, MYNAME);
2090  assert(ftpState->restart_offset > 0);
2091 
2092  if (code == 350) {
2093  ftpState->setCurrentOffset(ftpState->restart_offset);
2094  ftpSendRetr(ftpState);
2095  } else if (code > 0) {
2096  debugs(9, 3, "REST not supported");
2097  ftpState->flags.rest_supported = 0;
2098  ftpSendRetr(ftpState);
2099  } else {
2100  ftpFail(ftpState);
2101  }
2102 }
2103 
2104 static void
2106 {
2107  /* check the server control channel is still available */
2108  if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
2109  return;
2110 
2111  debugs(9, 3, MYNAME);
2112 
2113  if (ftpState->filepath) {
2114  snprintf(cbuf, CTRL_BUFLEN, "LIST %s\r\n", ftpState->filepath);
2115  } else {
2116  snprintf(cbuf, CTRL_BUFLEN, "LIST\r\n");
2117  }
2118 
2119  ftpState->writeCommand(cbuf);
2120  ftpState->state = Ftp::Client::SENT_LIST;
2121 }
2122 
2123 static void
2125 {
2126  /* check the server control channel is still available */
2127  if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
2128  return;
2129 
2130  debugs(9, 3, MYNAME);
2131 
2132  ftpState->flags.tried_nlst = 1;
2133 
2134  if (ftpState->filepath) {
2135  snprintf(cbuf, CTRL_BUFLEN, "NLST %s\r\n", ftpState->filepath);
2136  } else {
2137  snprintf(cbuf, CTRL_BUFLEN, "NLST\r\n");
2138  }
2139 
2140  ftpState->writeCommand(cbuf);
2141  ftpState->state = Ftp::Client::SENT_NLST;
2142 }
2143 
2144 static void
2146 {
2147  int code = ftpState->ctrl.replycode;
2148  debugs(9, 3, MYNAME);
2149 
2150  if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
2151  /* Begin data transfer */
2152  debugs(9, 3, "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2153  ftpState->switchTimeoutToDataChannel();
2154  ftpState->maybeReadVirginBody();
2155  ftpState->state = Ftp::Client::READING_DATA;
2156  return;
2157  } else if (code == 150) {
2158  /* Accept data channel */
2159  debugs(9, 3, "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2160  ftpState->listenForDataChannel(ftpState->data.conn);
2161  return;
2162  } else if (!ftpState->flags.tried_nlst && code > 300) {
2163  ftpSendNlst(ftpState);
2164  } else {
2165  ftpFail(ftpState);
2166  return;
2167  }
2168 }
2169 
2170 static void
2172 {
2173  /* check the server control channel is still available */
2174  if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
2175  return;
2176 
2177  debugs(9, 3, MYNAME);
2178 
2179  assert(ftpState->filepath != nullptr);
2180  snprintf(cbuf, CTRL_BUFLEN, "RETR %s\r\n", ftpState->filepath);
2181  ftpState->writeCommand(cbuf);
2182  ftpState->state = Ftp::Client::SENT_RETR;
2183 }
2184 
2185 static void
2187 {
2188  int code = ftpState->ctrl.replycode;
2189  debugs(9, 3, MYNAME);
2190 
2191  if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
2192  /* Begin data transfer */
2193  debugs(9, 3, "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2194  ftpState->switchTimeoutToDataChannel();
2195  ftpState->maybeReadVirginBody();
2196  ftpState->state = Ftp::Client::READING_DATA;
2197  } else if (code == 150) {
2198  /* Accept data channel */
2199  ftpState->listenForDataChannel(ftpState->data.conn);
2200  } else if (code >= 300) {
2201  if (!ftpState->flags.try_slash_hack) {
2202  /* Try this as a directory missing trailing slash... */
2203  ftpState->hackShortcut(ftpSendCwd);
2204  } else {
2205  ftpFail(ftpState);
2206  }
2207  } else {
2208  ftpFail(ftpState);
2209  }
2210 }
2211 
2216 void
2218 {
2219  assert(entry);
2220  entry->lock("Ftp::Gateway");
2221  ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request.getRaw(), fwd->al);
2222  ferr.ftp.listing = &listing;
2223  ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
2224  ferr.ftp.server_msg = ctrl.message;
2225  ctrl.message = nullptr;
2226  entry->replaceHttpReply(ferr.BuildHttpReply());
2227  entry->flush();
2228  entry->unlock("Ftp::Gateway");
2229 }
2230 
2231 static void
2233 {
2234  int code = ftpState->ctrl.replycode;
2235  debugs(9, 3, MYNAME);
2236 
2237  if (code == 226 || code == 250) {
2238  /* Connection closed; retrieval done. */
2239  if (ftpState->flags.listing) {
2240  ftpState->completedListing();
2241  /* QUIT operation handles sending the reply to client */
2242  }
2243  ftpState->markParsedVirginReplyAsWhole("ftpReadTransferDone code 226 or 250");
2244  ftpSendQuit(ftpState);
2245  } else { /* != 226 */
2246  debugs(9, DBG_IMPORTANT, "Got code " << code << " after reading data");
2247  ftpState->failed(ERR_FTP_FAILURE, 0);
2248  /* failed closes ctrl.conn and frees ftpState */
2249  return;
2250  }
2251 }
2252 
2253 // premature end of the request body
2254 void
2256 {
2258  debugs(9, 3, "ftpState=" << this);
2259  failed(ERR_READ_ERROR, 0);
2260 }
2261 
2262 static void
2264 {
2265  int code = ftpState->ctrl.replycode;
2266  debugs(9, 3, MYNAME);
2267 
2268  if (!(code == 226 || code == 250)) {
2269  debugs(9, DBG_IMPORTANT, "Got code " << code << " after sending data");
2270  ftpState->failed(ERR_FTP_PUT_ERROR, 0);
2271  return;
2272  }
2273 
2274  ftpState->entry->timestampsSet(); /* XXX Is this needed? */
2275  ftpState->markParsedVirginReplyAsWhole("ftpWriteTransferDone code 226 or 250");
2276  ftpSendReply(ftpState);
2277 }
2278 
2279 static void
2281 {
2282  /* check the server control channel is still available */
2283  if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
2284  return;
2285 
2286  snprintf(cbuf, CTRL_BUFLEN, "QUIT\r\n");
2287  ftpState->writeCommand(cbuf);
2288  ftpState->state = Ftp::Client::SENT_QUIT;
2289 }
2290 
2294 static void
2296 {
2297  ftpState->serverComplete();
2298 }
2299 
2300 static void
2302 {
2303  char *path;
2304  ftpState->flags.try_slash_hack = 1;
2305  /* Free old paths */
2306 
2307  debugs(9, 3, MYNAME);
2308 
2309  if (ftpState->pathcomps)
2310  wordlistDestroy(&ftpState->pathcomps);
2311 
2312  safe_free(ftpState->filepath);
2313 
2314  /* Build the new path (urlpath begins with /) */
2315  path = SBufToCstring(ftpState->request->url.path());
2316 
2317  rfc1738_unescape(path);
2318 
2319  ftpState->filepath = path;
2320 
2321  /* And off we go */
2322  ftpGetFile(ftpState);
2323 }
2324 
2328 void
2330 {
2331  debugs(9, 3, MYNAME);
2332 
2333  if (old_request != nullptr) {
2334  safe_free(old_request);
2335  safe_free(old_reply);
2336  }
2337 }
2338 
2339 void
2341 {
2342  /* Clear some unwanted state */
2343  setCurrentOffset(0);
2344  restart_offset = 0;
2345  /* Save old error message & some state info */
2346 
2347  debugs(9, 3, MYNAME);
2348 
2349  if (old_request == nullptr) {
2350  old_request = ctrl.last_command;
2351  ctrl.last_command = nullptr;
2352  old_reply = ctrl.last_reply;
2353  ctrl.last_reply = nullptr;
2354 
2355  if (pathcomps == nullptr && filepath != nullptr)
2356  old_filepath = xstrdup(filepath);
2357  }
2358 
2359  /* Jump to the "hack" state */
2360  nextState(this);
2361 }
2362 
2363 static void
2365 {
2366  const bool slashHack = ftpState->request->url.path().caseCmp("/%2f", 4)==0;
2367  int code = ftpState->ctrl.replycode;
2368  err_type error_code = ERR_NONE;
2369 
2370  debugs(9, 6, "state " << ftpState->state <<
2371  " reply code " << code << "flags(" <<
2372  (ftpState->flags.isdir?"IS_DIR,":"") <<
2373  (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
2374  "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
2375  "slashhack=" << (slashHack? "T":"F"));
2376 
2377  /* Try the / hack to support "Netscape" FTP URL's for retrieving files */
2378  if (!ftpState->flags.isdir && /* Not a directory */
2379  !ftpState->flags.try_slash_hack && !slashHack && /* Not doing slash hack */
2380  ftpState->mdtm <= 0 && ftpState->theSize < 0) { /* Not known as a file */
2381 
2382  switch (ftpState->state) {
2383 
2384  case Ftp::Client::SENT_CWD:
2385 
2387  /* Try the / hack */
2388  ftpState->hackShortcut(ftpTrySlashHack);
2389  return;
2390 
2391  default:
2392  break;
2393  }
2394  }
2395 
2396  Http::StatusCode sc = ftpState->failedHttpStatus(error_code);
2397  const auto ftperr = new ErrorState(error_code, sc, ftpState->fwd->request, ftpState->fwd->al);
2398  ftpState->failed(error_code, 0, ftperr);
2399  ftperr->detailError(new Ftp::ErrorDetail(code));
2400  HttpReply *newrep = ftperr->BuildHttpReply();
2401  delete ftperr;
2402 
2403  ftpState->entry->replaceHttpReply(newrep);
2404  ftpSendQuit(ftpState);
2405 }
2406 
2409 {
2410  if (error == ERR_NONE) {
2411  switch (state) {
2412 
2413  case SENT_USER:
2414 
2415  case SENT_PASS:
2416 
2417  if (ctrl.replycode > 500) {
2419  return password_url ? Http::scForbidden : Http::scUnauthorized;
2420  } else if (ctrl.replycode == 421) {
2423  }
2424  break;
2425 
2426  case SENT_CWD:
2427 
2428  case SENT_RETR:
2429  if (ctrl.replycode == 550) {
2431  return Http::scNotFound;
2432  }
2433  break;
2434 
2435  default:
2436  break;
2437  }
2438  }
2440 }
2441 
2442 static void
2444 {
2445  int code = ftpState->ctrl.replycode;
2446  Http::StatusCode http_code;
2447  err_type err_code = ERR_NONE;
2448 
2449  debugs(9, 3, ftpState->entry->url() << ", code " << code);
2450 
2451  if (cbdataReferenceValid(ftpState))
2452  debugs(9, 5, "ftpState (" << ftpState << ") is valid!");
2453 
2454  if (code == 226 || code == 250) {
2455  err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
2456  http_code = (ftpState->mdtm > 0) ? Http::scAccepted : Http::scCreated;
2457  } else if (code == 227) {
2458  err_code = ERR_FTP_PUT_CREATED;
2459  http_code = Http::scCreated;
2460  } else {
2461  err_code = ERR_FTP_PUT_ERROR;
2462  http_code = Http::scInternalServerError;
2463  }
2464 
2465  ErrorState err(err_code, http_code, ftpState->request.getRaw(), ftpState->fwd->al);
2466 
2467  if (ftpState->old_request)
2468  err.ftp.request = xstrdup(ftpState->old_request);
2469  else
2470  err.ftp.request = xstrdup(ftpState->ctrl.last_command);
2471 
2472  if (ftpState->old_reply)
2473  err.ftp.reply = xstrdup(ftpState->old_reply);
2474  else if (ftpState->ctrl.last_reply)
2475  err.ftp.reply = xstrdup(ftpState->ctrl.last_reply);
2476  else
2477  err.ftp.reply = xstrdup("");
2478 
2479  err.detailError(new Ftp::ErrorDetail(code));
2480 
2481  ftpState->entry->replaceHttpReply(err.BuildHttpReply());
2482 
2483  ftpSendQuit(ftpState);
2484 }
2485 
2486 void
2488 {
2489  debugs(9, 3, MYNAME);
2490 
2491  if (flags.http_header_sent)
2492  return;
2493 
2494  HttpReply *reply = new HttpReply;
2495 
2496  flags.http_header_sent = 1;
2497 
2498  assert(entry->isEmpty());
2499 
2500  entry->buffer(); /* released when done processing current data payload */
2501 
2502  SBuf urlPath = request->url.path();
2503  auto t = urlPath.rfind('/');
2504  SBuf filename = urlPath.substr(t != SBuf::npos ? t : 0);
2505 
2506  const char *mime_type = nullptr;
2507  const char *mime_enc = nullptr;
2508 
2509  if (flags.isdir) {
2510  mime_type = "text/html";
2511  } else {
2512  switch (typecode) {
2513 
2514  case 'I':
2515  mime_type = "application/octet-stream";
2516  // XXX: performance regression, c_str() may reallocate
2517  mime_enc = mimeGetContentEncoding(filename.c_str());
2518  break;
2519 
2520  case 'A':
2521  mime_type = "text/plain";
2522  break;
2523 
2524  default:
2525  // XXX: performance regression, c_str() may reallocate
2526  mime_type = mimeGetContentType(filename.c_str());
2527  mime_enc = mimeGetContentEncoding(filename.c_str());
2528  break;
2529  }
2530  }
2531 
2532  /* set standard stuff */
2533 
2534  if (0 == getCurrentOffset()) {
2535  /* Full reply */
2536  reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
2537  } else if (theSize < getCurrentOffset()) {
2538  /*
2539  * DPW 2007-05-04
2540  * offset should not be larger than theSize. We should
2541  * not be seeing this condition any more because we'll only
2542  * send REST if we know the theSize and if it is less than theSize.
2543  */
2544  debugs(0, DBG_CRITICAL, "ERROR: " <<
2545  " current offset=" << getCurrentOffset() <<
2546  ", but theSize=" << theSize <<
2547  ". assuming full content response");
2548  reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
2549  } else {
2550  /* Partial reply */
2551  HttpHdrRangeSpec range_spec;
2552  range_spec.offset = getCurrentOffset();
2553  range_spec.length = theSize - getCurrentOffset();
2554  reply->setHeaders(Http::scPartialContent, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
2555  httpHeaderAddContRange(&reply->header, range_spec, theSize);
2556  }
2557 
2558  /* additional info */
2559  if (mime_enc)
2560  reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc);
2561 
2562  reply->sources |= Http::Message::srcFtp;
2563  setVirginReply(reply);
2564  adaptOrFinalizeReply();
2565 }
2566 
2567 void
2569 {
2571 
2572  StoreEntry *e = entry;
2573 
2574  e->timestampsSet();
2575 
2576  // makePublic() if allowed/possible or release() otherwise
2577  if (flags.authenticated || // authenticated requests can't be cached
2578  getCurrentOffset() ||
2579  !e->makePublic()) {
2580  e->release();
2581  }
2582 }
2583 
2584 HttpReply *
2586 {
2588  HttpReply *newrep = err.BuildHttpReply();
2589 #if HAVE_AUTH_MODULE_BASIC
2590  /* add Authenticate header */
2591  // XXX: performance regression. c_str() may reallocate
2592  newrep->header.putAuth("Basic", realm.c_str());
2593 #else
2594  (void)realm;
2595 #endif
2596  return newrep;
2597 }
2598 
2599 const SBuf &
2601 {
2602  SBuf newbuf("%2f");
2603 
2604  if (request->url.getScheme() != AnyP::PROTO_FTP) {
2605  static const SBuf nil;
2606  return nil;
2607  }
2608 
2609  if (request->url.path()[0] == '/') {
2610  newbuf.append(request->url.path());
2611  request->url.path(newbuf);
2612  } else if (!request->url.path().startsWith(newbuf)) {
2613  newbuf.append(request->url.path().substr(1));
2614  request->url.path(newbuf);
2615  }
2616 
2617  return request->effectiveRequestUri();
2618 }
2619 
2624 void
2625 Ftp::Gateway::writeReplyBody(const char *dataToWrite, size_t dataLength)
2626 {
2627  debugs(9, 5, "writing " << dataLength << " bytes to the reply");
2628  addVirginReplyBody(dataToWrite, dataLength);
2629 }
2630 
2637 void
2639 {
2640  if (fwd == nullptr || flags.completed_forwarding) {
2641  debugs(9, 3, "avoid double-complete on FD " <<
2642  (ctrl.conn ? ctrl.conn->fd : -1) << ", Data FD " << (data.conn ? data.conn->fd : -1) <<
2643  ", this " << this << ", fwd " << fwd);
2644  return;
2645  }
2646 
2647  flags.completed_forwarding = true;
2649 }
2650 
2657 bool
2658 Ftp::Gateway::haveControlChannel(const char *caller_name) const
2659 {
2660  if (doneWithServer())
2661  return false;
2662 
2663  /* doneWithServer() only checks BOTH channels are closed. */
2664  if (!Comm::IsConnOpen(ctrl.conn)) {
2665  debugs(9, DBG_IMPORTANT, "WARNING: FTP Server Control channel is closed, but Data channel still active.");
2666  debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
2667  return false;
2668  }
2669 
2670  return true;
2671 }
2672 
2673 bool
2675 {
2676  // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2677  return !doneWithServer();
2678 }
2679 
2680 void
2681 Ftp::StartGateway(FwdState *const fwdState)
2682 {
2683  AsyncJob::Start(new Ftp::Gateway(fwdState));
2684 }
2685 
static void ftpTrySlashHack(Ftp::Gateway *ftpState)
Definition: FtpGateway.cc:2301
const char * xstrerr(int error)
Definition: xstrerror.cc:83
static FTPSM ftpSendCwd
Definition: FtpGateway.cc:221
void processReplyBody() override
Definition: FtpGateway.cc:964
static FTPSM ftpReadQuit
Definition: FtpGateway.cc:240
CBDATA_CHILD(Gateway)
void handleRequestBodyProducerAborted() override
Definition: FtpGateway.cc:2255
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
StoreEntry * entry
Definition: Client.h:176
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:328
@ 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:364
@ ERR_FTP_FORBIDDEN
Definition: forward.h:56
static PF ftpDataWrite
Definition: FtpGateway.cc:153
MemBuf * listing
Definition: errorpage.h:193
SBuf ftpRealm()
Definition: FtpGateway.cc:1264
bool makePublic(const KeyScope keyScope=ksDefault)
Definition: store.cc:167
HttpHeader header
Definition: Message.h:74
char * old_filepath
Definition: FtpGateway.cc:120
static FTPSM ftpReadTransferDone
Definition: FtpGateway.cc:231
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:917
char * reply
Definition: errorpage.h:191
const char * url() const
Definition: store.cc:1566
void connectDataChannel()
Definition: FtpClient.cc:762
const char *const crlf
Definition: FtpClient.cc:40
void writeCommand(const char *buf)
Definition: FtpClient.cc:823
bool pasv_supported
PASV command is allowed.
Definition: FtpGateway.cc:59
static void ftpOpenListenSocket(Ftp::Gateway *ftpState, int fallback)
Definition: FtpGateway.cc:1749
HttpReply * BuildHttpReply(void)
Definition: errorpage.cc:1315
virtual void handleRequestBodyProducerAborted()=0
Definition: Client.cc:350
@ ENTRY_ABORTED
Definition: enums.h:110
void error(char *format,...)
AccessLogEntryPointer al
info for the future access.log entry
Definition: FwdState.h:204
static FTPSM ftpSendRest
Definition: FtpGateway.cc:227
@ ERR_CACHE_ACCESS_DENIED
Definition: forward.h:19
bool tried_auth_nopass
auth tried username with no password already.
Definition: FtpGateway.cc:67
void dataClosed(const CommCloseCbParams &io) override
handler called by Comm when FTP data channel is closed unexpectedly
Definition: FtpGateway.cc:316
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:2585
bool sendPassive()
Definition: FtpClient.cc:653
bool htmlifyListEntry(const char *line, PackableStream &)
Definition: FtpGateway.cc:764
static FTPSM ftpReadSize
Definition: FtpGateway.cc:211
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:1169
@ TIMEOUT
Definition: Flag.h:18
static FTPSM ftpSendRetr
Definition: FtpGateway.cc:229
@ CONTENT_ENCODING
int checkAuth(const HttpHeader *req_hdr)
Definition: FtpGateway.cc:1031
static void FreeAddr(struct addrinfo *&ai)
Definition: Address.cc:706
C * getRaw() const
Definition: RefCount.h:89
void initReadBuf()
Definition: FtpClient.cc:221
void loginParser(const SBuf &login, bool escaped)
Definition: FtpGateway.cc:397
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:202
#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:262
static FTPSM ftpReadRest
Definition: FtpGateway.cc:228
static FTPSM ftpReadList
Definition: FtpGateway.cc:226
@ 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:225
bool isIPv4() const
Definition: Address.cc:178
StatusCode
Definition: StatusCode.h:20
static FTPSM ftpSendMkdir
Definition: FtpGateway.cc:236
err_type
Definition: forward.h:14
static FTPSM ftpRestOrList
Definition: FtpGateway.cc:223
virtual void haveParsedReplyHeaders()
called when we have final (possibly adapted) reply headers; kids extend
Definition: Client.cc:536
int64_t currentOffset
Definition: Client.h:172
static FTPSM ftpSendType
Definition: FtpGateway.cc:206
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:112
SBuf substr(size_type pos, size_type n=npos) const
Definition: SBuf.cc:576
void serverComplete()
Definition: Client.cc:180
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:229
void() StateMethod(Ftp::Gateway *)
Definition: FtpGateway.cc:89
CBDATA_NAMESPACED_CLASS_INIT(Ftp, Gateway)
static FTPSM ftpListDir
Definition: FtpGateway.cc:219
char * showname
Definition: FtpGateway.cc:188
void comm_open_listener(int sock_type, int proto, Comm::ConnectionPointer &conn, const char *note)
Definition: comm.cc:257
virtual bool haveControlChannel(const char *caller_name) const
Definition: FtpGateway.cc:2658
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:455
static FTPSM ftpSendQuit
Definition: FtpGateway.cc:239
struct tok tokens[]
Definition: parse.c:168
static FTPSM ftpReadCwd
Definition: FtpGateway.cc:222
static FTPSM ftpReadUser
Definition: FtpGateway.cc:203
char * proxy_host
Definition: FtpGateway.cc:117
mb_size_t contentSize() const
available data size
Definition: MemBuf.h:47
wordlist * server_msg
Definition: errorpage.h:189
int size
Definition: ModDevPoll.cc:69
wordlist * message
Definition: FtpClient.h:82
static FTPSM ftpReadMkdir
Definition: FtpGateway.cc:237
void start() override
called by AsyncStart; do not call directly
Definition: FtpClient.cc:215
static char cbuf[CTRL_BUFLEN]
Definition: FtpGateway.cc:193
const char * rawContent() const
Definition: SBuf.cc:509
bool completed_forwarding
Definition: FtpGateway.cc:85
String base_href
Definition: FtpGateway.cc:108
static FTPSM ftpSendSize
Definition: FtpGateway.cc:210
@ 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:419
char * last_command
Definition: FtpClient.h:83
static FTPSM ftpSendPass
Definition: FtpGateway.cc:204
virtual void dataClosed(const CommCloseCbParams &io)
handler called by Comm when FTP data channel is closed unexpectedly
Definition: FtpClient.cc:810
void buildTitleUrl()
Definition: FtpGateway.cc:1114
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:130
static FTPSM ftpReadWelcome
Definition: FtpGateway.cc:201
String clean_url
Definition: FtpGateway.cc:106
int64_t size
Definition: FtpGateway.cc:185
HttpRequestPointer request
Definition: errorpage.h:177
void putAuth(const char *auth_scheme, const char *realm)
Definition: HttpHeader.cc:1004
char * html_quote(const char *string)
Definition: Quoting.cc:42
void appendSuccessHeader()
Definition: FtpGateway.cc:2487
unsigned short port() const
Definition: Address.cc:798
Definition: MemBuf.h:23
Ip::Address local
Definition: Connection.h:146
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:491
String cwd_message
Definition: FtpGateway.cc:119
@ scPartialContent
Definition: StatusCode.h:33
size_t list_width
Definition: FtpGateway.cc:118
static FTPSM ftpReadEPRT
Definition: FtpGateway.cc:212
FTPSM * FTP_SM_FUNCS[]
Definition: FtpGateway.cc:284
void timeout(const CommTimeoutCbParams &io) override
read timeout handler
Definition: FtpGateway.cc:486
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
@ scCreated
Definition: StatusCode.h:28
int64_t getInt64(Http::HdrType id) const
Definition: HttpHeader.cc:1133
#define safe_free(x)
Definition: xalloc.h:73
@ srcFtp
ftp_port or FTP server
Definition: Message.h:40
Ip::Address remote
Definition: Connection.h:149
void close()
planned close: removes the close handler and calls comm_close
Definition: FtpClient.cc:107
void ftpAcceptDataConnection(const CommAcceptCbParams &io)
Definition: FtpGateway.cc:1878
SBuf getAuthToken(Http::HdrType id, const char *auth_scheme) const
Definition: HttpHeader.cc:1275
#define assert(EX)
Definition: assert.h:17
bool tried_auth_anonymous
auth has tried to use anonymous credentials already.
Definition: FtpGateway.cc:66
bool authenticated
authentication success
Definition: FtpGateway.cc:65
SSL Connection
Definition: Session.h:49
FwdState::Pointer fwd
Definition: Client.h:177
bool mayReadVirginReplyBody() const override
whether we may receive more virgin response body bytes
Definition: FtpGateway.cc:2674
static FTPSM ftpSendList
Definition: FtpGateway.cc:224
@ METHOD_PUT
Definition: MethodType.h:27
Comm::Flag flag
comm layer result status.
Definition: CommCalls.h:82
void parseListing()
Definition: FtpGateway.cc:880
static FTPSM ftpTraverseDirectory
Definition: FtpGateway.cc:218
String title_url
Definition: FtpGateway.cc:107
@ scServiceUnavailable
Definition: StatusCode.h:76
const AnyP::UriScheme & getScheme() const
Definition: Uri.h:58
static FTPSM ftpReadEPSV
Definition: FtpGateway.cc:216
#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:114
@ scInternalServerError
Definition: StatusCode.h:73
Comm::ConnectionPointer conn
channel descriptor
Definition: FtpClient.h:58
static int sc[16]
Definition: smbdes.c:121
wordlist * pathcomps
Definition: FtpGateway.cc:113
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
struct SquidConfig::@99 Ftp
int reply_hdr_state
Definition: FtpGateway.cc:105
#define xfree
void completeForwarding() override
Definition: FtpGateway.cc:2638
int code
Definition: smb-errors.c:145
char * anon_user
Definition: SquidConfig.h:413
char * reply_hdr
Definition: FtpGateway.cc:104
DataChannel data
FTP data channel state.
Definition: FtpClient.h:143
static FTPSM ftpSendPassive
Definition: FtpGateway.cc:215
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:2625
static void ftpListPartsFree(ftpListParts **parts)
Definition: FtpGateway.cc:520
static const char * Month[]
Definition: FtpGateway.cc:502
void hackShortcut(StateMethod *nextState)
Definition: FtpGateway.cc:2340
#define fd_table
Definition: fde.h:189
static FTPSM ftpReadPasv
Definition: FtpGateway.cc:217
void clear()
remove the close handler, leave connection open
Definition: FtpClient.cc:128
char user[MAX_URL]
Definition: FtpGateway.cc:101
@ MEM_4K_BUF
Definition: forward.h:49
void checkUrlpath()
Definition: FtpGateway.cc:1076
HttpRequestMethod method
Definition: HttpRequest.h:114
void path(const char *p)
Definition: Uri.h:96
static FTPSM ftpReadType
Definition: FtpGateway.cc:207
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:311
MemBuf listing
FTP directory listing in HTML format.
Definition: FtpGateway.cc:122
void dataChannelConnected(const CommConnectCbParams &io) override
Definition: FtpGateway.cc:1727
@ 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:2600
static ftpListParts * ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags)
Definition: FtpGateway.cc:532
int64_t restart_offset
Definition: FtpGateway.cc:116
static FTPSM ftpReadMdtm
Definition: FtpGateway.cc:209
void processHeadResponse()
Definition: FtpGateway.cc:1685
void httpHeaderAddContRange(HttpHeader *, HttpHdrRangeSpec, int64_t)
bool timestampsSet()
Definition: store.cc:1387
GatewayFlags flags
Definition: FtpGateway.cc:124
char * content()
start of the added data
Definition: MemBuf.h:41
static int is_month(const char *buf)
Definition: FtpGateway.cc:508
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:995
static FTPSM ftpFail
Definition: FtpGateway.cc:238
Ftp::StateMethod FTPSM
Definition: FtpGateway.cc:179
static FTPSM ftpReadStor
Definition: FtpGateway.cc:233
void memFree(void *, int type)
Free a element allocated by memAllocate()
Definition: minimal.cc:61
void readStor()
Definition: FtpGateway.cc:2009
char * old_request
Definition: FtpClient.h:177
void loginFailed(void)
Definition: FtpGateway.cc:1220
void StartGateway(FwdState *const fwdState)
A new FTP Gateway job.
Definition: FtpGateway.cc:2681
static FTPSM ftpSendMdtm
Definition: FtpGateway.cc:208
const char * mimeGetContentEncoding(const char *fn)
Definition: mime.cc:195
void reset(char const *str)
Definition: String.cc:122
#define DBG_IMPORTANT
Definition: Stream.h:38
void listenForDataChannel(const Comm::ConnectionPointer &conn)
create a data channel acceptor and start listening.
Definition: FtpGateway.cc:449
char password[MAX_URL]
Definition: FtpGateway.cc:102
int restartable()
Definition: FtpGateway.cc:2059
Http::StatusCode failedHttpStatus(err_type &error) override
Definition: FtpGateway.cc:2408
#define MYNAME
Definition: Stream.h:219
void completedListing(void)
Definition: FtpGateway.cc:2217
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:2568
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:150
CtrlChannel ctrl
FTP control channel state.
Definition: FtpClient.h:142
static FTPSM ftpReadPORT
Definition: FtpGateway.cc:214
int token
Definition: parse.c:163
@ ERR_FTP_FAILURE
Definition: forward.h:53
char * dirpath
Definition: FtpGateway.cc:115
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:213
#define CTRL_BUFLEN
Definition: FtpGateway.cc:192
@ 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:232
const SBuf & effectiveRequestUri() const
RFC 7230 section 5.5 - Effective Request URI.
Definition: HttpRequest.cc:744
virtual void timeout(const CommTimeoutCbParams &io)
read timeout handler
Definition: FtpClient.cc:890
struct ErrorState::@54 ftp
HttpRequest * request
Definition: FwdState.h:203
void host(const char *src)
Definition: Uri.cc:123
@ ERR_FTP_UNAVAILABLE
Definition: forward.h:52
#define MAX_TOKENS
Definition: FtpGateway.cc:529
static FTPSM ftpSendReply
Definition: FtpGateway.cc:235
HttpRequestPointer request
Definition: Client.h:178
@ 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:230
char * old_reply
Definition: FtpClient.h:178
@ ERR_DIR_LISTING
Definition: forward.h:70
static FTPSM ftpReadPass
Definition: FtpGateway.cc:205
void setCurrentOffset(int64_t offset)
Definition: FtpGateway.cc:149
void start() override
called by AsyncStart; do not call directly
Definition: FtpGateway.cc:1147
void switchTimeoutToDataChannel()
Definition: FtpClient.cc:1067
size_type copy(char *dest, size_type n) const
Definition: SBuf.cc:500
nfmark_t nfmark
Definition: Connection.h:163
#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:234
class SquidConfig Config
Definition: SquidConfig.cc:12
static FTPSM ftpGetFile
Definition: FtpGateway.cc:220
bool epsv_all_sent
EPSV ALL has been used. Must abort on failures.
Definition: FtpGateway.cc:60
static void Start(const Pointer &job)
Definition: AsyncJob.cc:37

 

Introduction

Documentation

Support

Miscellaneous