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