/*
- * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "squid.h"
#include "acl/FilledChecklist.h"
+#include "base/PackableStream.h"
#include "clients/forward.h"
#include "clients/FtpClient.h"
#include "comm.h"
#include "HttpHeader.h"
#include "HttpHeaderRange.h"
#include "HttpReply.h"
-#include "HttpRequest.h"
#include "ip/tools.h"
-#include "Mem.h"
#include "MemBuf.h"
#include "mime.h"
#include "rfc1738.h"
#include "StatCounters.h"
#include "Store.h"
#include "tools.h"
-#include "URL.h"
+#include "util.h"
#include "wordlist.h"
#if USE_DELAY_POOLS
String cwd_message;
char *old_filepath;
char typecode;
- MemBuf listing; ///< FTP directory listing in HTML format.
+ MemBuf listing; ///< FTP directory listing in HTML format.
GatewayFlags flags;
void unhack();
void readStor();
void parseListing();
- MemBuf *htmlifyListEntry(const char *line);
+ bool htmlifyListEntry(const char *line, PackableStream &);
void completedListing(void);
/// create a data channel acceptor and start listening.
virtual void timeout(const CommTimeoutCbParams &io);
void ftpAcceptDataConnection(const CommAcceptCbParams &io);
- static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
- const char *ftpRealm(void);
+ static HttpReply *ftpAuthRequired(HttpRequest * request, SBuf &realm, AccessLogEntry::Pointer &);
+ SBuf ftpRealm();
void loginFailed(void);
virtual void haveParsedReplyHeaders();
char *link;
} ftpListParts;
-#define CTRL_BUFLEN 1024
+#define CTRL_BUFLEN 16*1024
static char cbuf[CTRL_BUFLEN];
/*
static FTPSM ftpReadMdtm;
static FTPSM ftpSendSize;
static FTPSM ftpReadSize;
+#if 0
static FTPSM ftpSendEPRT;
+#endif
static FTPSM ftpReadEPRT;
static FTPSM ftpSendPORT;
static FTPSM ftpReadPORT;
/************************************************
** Debugs Levels used here **
*************************************************
-0 CRITICAL Events
-1 IMPORTANT Events
- Protocol and Transmission failures.
-2 FTP Protocol Chatter
-3 Logic Flows
-4 Data Parsing Flows
-5 Data Dumps
-7 ??
+0 CRITICAL Events
+1 IMPORTANT Events
+ Protocol and Transmission failures.
+2 FTP Protocol Chatter
+3 Logic Flows
+4 Data Parsing Flows
+5 Data Dumps
+7 ??
************************************************/
/************************************************
** State Machine Description (excluding hacks) **
*************************************************
-From To
+From To
---------------------------------------
-Welcome User
-User Pass
-Pass Type
-Type TraverseDirectory / GetFile
-TraverseDirectory Cwd / GetFile / ListDir
-Cwd TraverseDirectory / Mkdir
-GetFile Mdtm
-Mdtm Size
-Size Epsv
-ListDir Epsv
-Epsv FileOrList
-FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
-Rest Retr
-Retr / Nlst / List DataRead* (on datachannel)
-DataRead* ReadTransferDone
-ReadTransferDone DataTransferDone
-Stor DataWrite* (on datachannel)
-DataWrite* RequestPutBody** (from client)
-RequestPutBody** DataWrite* / WriteTransferDone
-WriteTransferDone DataTransferDone
-DataTransferDone Quit
-Quit -
+Welcome User
+User Pass
+Pass Type
+Type TraverseDirectory / GetFile
+TraverseDirectory Cwd / GetFile / ListDir
+Cwd TraverseDirectory / Mkdir
+GetFile Mdtm
+Mdtm Size
+Size Epsv
+ListDir Epsv
+Epsv FileOrList
+FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
+Rest Retr
+Retr / Nlst / List DataRead* (on datachannel)
+DataRead* ReadTransferDone
+ReadTransferDone DataTransferDone
+Stor DataWrite* (on datachannel)
+DataWrite* RequestPutBody** (from client)
+RequestPutBody** DataWrite* / WriteTransferDone
+WriteTransferDone DataTransferDone
+DataTransferDone Quit
+Quit -
************************************************/
FTPSM *FTP_SM_FUNCS[] = {
- ftpReadWelcome, /* BEGIN */
- ftpReadUser, /* SENT_USER */
- ftpReadPass, /* SENT_PASS */
- ftpReadType, /* SENT_TYPE */
- ftpReadMdtm, /* SENT_MDTM */
- ftpReadSize, /* SENT_SIZE */
- ftpReadEPRT, /* SENT_EPRT */
- ftpReadPORT, /* SENT_PORT */
- ftpReadEPSV, /* SENT_EPSV_ALL */
- ftpReadEPSV, /* SENT_EPSV_1 */
- ftpReadEPSV, /* SENT_EPSV_2 */
- ftpReadPasv, /* SENT_PASV */
- ftpReadCwd, /* SENT_CWD */
- ftpReadList, /* SENT_LIST */
- ftpReadList, /* SENT_NLST */
- ftpReadRest, /* SENT_REST */
- ftpReadRetr, /* SENT_RETR */
- ftpReadStor, /* SENT_STOR */
- ftpReadQuit, /* SENT_QUIT */
- ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
- ftpWriteTransferDone, /* WRITING_DATA (STOR) */
- ftpReadMkdir, /* SENT_MKDIR */
- NULL, /* SENT_FEAT */
- NULL, /* SENT_PWD */
- NULL, /* SENT_CDUP*/
- NULL, /* SENT_DATA_REQUEST */
- NULL /* SENT_COMMAND */
+ ftpReadWelcome, /* BEGIN */
+ ftpReadUser, /* SENT_USER */
+ ftpReadPass, /* SENT_PASS */
+ ftpReadType, /* SENT_TYPE */
+ ftpReadMdtm, /* SENT_MDTM */
+ ftpReadSize, /* SENT_SIZE */
+ ftpReadEPRT, /* SENT_EPRT */
+ ftpReadPORT, /* SENT_PORT */
+ ftpReadEPSV, /* SENT_EPSV_ALL */
+ ftpReadEPSV, /* SENT_EPSV_1 */
+ ftpReadEPSV, /* SENT_EPSV_2 */
+ ftpReadPasv, /* SENT_PASV */
+ ftpReadCwd, /* SENT_CWD */
+ ftpReadList, /* SENT_LIST */
+ ftpReadList, /* SENT_NLST */
+ ftpReadRest, /* SENT_REST */
+ ftpReadRetr, /* SENT_RETR */
+ ftpReadStor, /* SENT_STOR */
+ ftpReadQuit, /* SENT_QUIT */
+ ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
+ ftpWriteTransferDone, /* WRITING_DATA (STOR) */
+ ftpReadMkdir, /* SENT_MKDIR */
+ NULL, /* SENT_FEAT */
+ NULL, /* SENT_PWD */
+ NULL, /* SENT_CDUP*/
+ NULL, /* SENT_DATA_REQUEST */
+ NULL /* SENT_COMMAND */
};
/// handler called by Comm when FTP data channel is closed unexpectedly
}
Ftp::Gateway::Gateway(FwdState *fwdState):
- AsyncJob("FtpStateData"),
- Ftp::Client(fwdState),
- password_url(0),
- reply_hdr(NULL),
- reply_hdr_state(0),
- conn_att(0),
- login_att(0),
- mdtm(-1),
- theSize(-1),
- pathcomps(NULL),
- filepath(NULL),
- dirpath(NULL),
- restart_offset(0),
- proxy_host(NULL),
- list_width(0),
- old_filepath(NULL),
- typecode('\0')
+ AsyncJob("FtpStateData"),
+ Ftp::Client(fwdState),
+ password_url(0),
+ reply_hdr(NULL),
+ reply_hdr_state(0),
+ conn_att(0),
+ login_att(0),
+ mdtm(-1),
+ theSize(-1),
+ pathcomps(NULL),
+ filepath(NULL),
+ dirpath(NULL),
+ restart_offset(0),
+ proxy_host(NULL),
+ list_width(0),
+ old_filepath(NULL),
+ typecode('\0')
{
debugs(9, 3, entry->url());
void
Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer &conn)
{
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 5, "The control connection to the remote end is closed");
+ return;
+ }
+
assert(!Comm::IsConnOpen(data.conn));
typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer;
{
ftpListParts *p = NULL;
char *t = NULL;
- const char *ct = NULL;
- char *tokens[MAX_TOKENS];
+ struct FtpLineToken {
+ char *token = nullptr; ///< token image copied from the received line
+ size_t pos = 0; ///< token offset on the received line
+ } tokens[MAX_TOKENS];
int i;
int n_tokens;
static char tbuf[128];
}
for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) {
- tokens[n_tokens] = xstrdup(t);
+ tokens[n_tokens].token = xstrdup(t);
+ tokens[n_tokens].pos = t - xbuf;
++n_tokens;
}
/* locate the Month field */
for (i = 3; i < n_tokens - 2; ++i) {
- char *size = tokens[i - 1];
- char *month = tokens[i];
- char *day = tokens[i + 1];
- char *year = tokens[i + 2];
+ const auto size = tokens[i - 1].token;
+ char *month = tokens[i].token;
+ char *day = tokens[i + 1].token;
+ char *year = tokens[i + 2].token;
if (!is_month(month))
continue;
if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0)
continue;
- if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
+ if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
continue;
- snprintf(tbuf, 128, "%s %2s %5s",
- month, day, year);
+ const auto *copyFrom = buf + tokens[i].pos;
- if (!strstr(buf, tbuf))
- snprintf(tbuf, 128, "%s %2s %-5s",
- month, day, year);
+ // "MMM DD [ YYYY|hh:mm]" with at most two spaces between DD and YYYY
+ auto dateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %5s", month, day, year);
+ bool isTypeA = (dateSize == 12) && (strncmp(copyFrom, tbuf, dateSize) == 0);
- char const *copyFrom = NULL;
+ // "MMM DD [YYYY|hh:mm]" with one space between DD and YYYY
+ dateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %-5s", month, day, year);
+ bool isTypeB = (dateSize == 12 || dateSize == 11) && (strncmp(copyFrom, tbuf, dateSize) == 0);
- if ((copyFrom = strstr(buf, tbuf))) {
- p->type = *tokens[0];
+ // TODO: replace isTypeA and isTypeB with a regex.
+ if (isTypeA || isTypeB) {
+ p->type = *tokens[0].token;
p->size = strtoll(size, NULL, 10);
+ const auto finalDateSize = snprintf(tbuf, sizeof(tbuf), "%s %2s %5s", month, day, year);
+ assert(finalDateSize >= 0);
p->date = xstrdup(tbuf);
+ // point after tokens[i+2] :
+ copyFrom = buf + tokens[i + 2].pos + strlen(tokens[i + 2].token);
if (flags.skip_whitespace) {
- copyFrom += strlen(tbuf);
-
while (strchr(w_space, *copyFrom))
++copyFrom;
} else {
- /* XXX assumes a single space between date and filename
+ /* Handle the following four formats:
+ * "MMM DD YYYY Name"
+ * "MMM DD YYYYName"
+ * "MMM DD YYYY Name"
+ * "MMM DD YYYY Name"
+ * Assuming a single space between date and filename
* suggested by: Nathan.Bailey@cc.monash.edu.au and
* Mike Battersby <mike@starbug.bofh.asn.au> */
- copyFrom += strlen(tbuf) + 1;
+ if (strchr(w_space, *copyFrom))
+ ++copyFrom;
}
p->name = xstrdup(copyFrom);
/* try it as a DOS listing, 04-05-70 09:33PM ... */
if (n_tokens > 3 &&
- regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 &&
- regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) {
- if (!strcasecmp(tokens[2], "<dir>")) {
+ regexec(&scan_ftp_dosdate, tokens[0].token, 0, NULL, 0) == 0 &&
+ regexec(&scan_ftp_dostime, tokens[1].token, 0, NULL, 0) == 0) {
+ if (!strcasecmp(tokens[2].token, "<dir>")) {
p->type = 'd';
} else {
p->type = '-';
- p->size = strtoll(tokens[2], NULL, 10);
+ p->size = strtoll(tokens[2].token, NULL, 10);
}
- snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]);
+ snprintf(tbuf, sizeof(tbuf), "%s %s", tokens[0].token, tokens[1].token);
p->date = xstrdup(tbuf);
if (p->type == 'd') {
- /* Directory.. name begins with first printable after <dir> */
- ct = strstr(buf, tokens[2]);
- ct += strlen(tokens[2]);
-
- while (xisspace(*ct))
- ++ct;
-
- if (!*ct)
- ct = NULL;
+ // Directory.. name begins with first printable after <dir>
+ // Because of the "n_tokens > 3", the next printable after <dir>
+ // is stored at token[3]. No need for more checks here.
} else {
- /* A file. Name begins after size, with a space in between */
- snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]);
- ct = strstr(buf, tbuf);
-
- if (ct) {
- ct += strlen(tokens[2]) + 2;
- }
+ // A file. Name begins after size, with a space in between.
+ // Also a space should exist before size.
+ // But there is not needed to be very strict with spaces.
+ // The name is stored at token[3], take it from here.
}
- p->name = xstrdup(ct ? ct : tokens[3]);
+ p->name = xstrdup(tokens[3].token);
goto found;
}
/* Try EPLF format; carson@lehman.com */
if (buf[0] == '+') {
- ct = buf + 1;
+ const char *ct = buf + 1;
p->type = 0;
while (ct && *ct) {
tm = (time_t) strtol(ct + 1, &tmp, 0);
if (tmp != ct + 1)
- break; /* not a valid integer */
+ break; /* not a valid integer */
p->date = xstrdup(ctime(&tm));
found:
for (i = 0; i < n_tokens; ++i)
- xfree(tokens[i]);
+ xfree(tokens[i].token);
if (!p->name)
- ftpListPartsFree(&p); /* cleanup */
+ ftpListPartsFree(&p); /* cleanup */
return p;
}
-MemBuf *
-Ftp::Gateway::htmlifyListEntry(const char *line)
+bool
+Ftp::Gateway::htmlifyListEntry(const char *line, PackableStream &html)
{
- char icon[2048];
- char href[2048 + 40];
- char text[ 2048];
- char size[ 2048];
- char chdir[ 2048 + 40];
- char view[ 2048 + 40];
- char download[ 2048 + 40];
- char link[ 2048 + 40];
- MemBuf *html;
- char prefix[2048];
- ftpListParts *parts;
- *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
-
- debugs(9, 7, HERE << " line ={" << line << "}");
+ debugs(9, 7, "line={" << line << "}");
if (strlen(line) > 1024) {
- html = new MemBuf();
- html->init();
- html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
- return html;
+ html << "<tr><td colspan=\"5\">" << line << "</td></tr>\n";
+ return true;
}
- if (flags.dir_slash && dirpath && typecode != 'D')
- snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath));
- else
- prefix[0] = '\0';
-
- if ((parts = ftpListParseParts(line, flags)) == NULL) {
- const char *p;
+ SBuf prefix;
+ if (flags.dir_slash && dirpath && typecode != 'D') {
+ prefix.append(rfc1738_escape_part(dirpath));
+ prefix.append("/", 1);
+ }
- html = new MemBuf();
- html->init();
- html->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
+ ftpListParts *parts = ftpListParseParts(line, flags);
+ if (!parts) {
+ html << "<tr class=\"entry\"><td colspan=\"5\">" << line << "</td></tr>\n";
+ const char *p;
for (p = line; *p && xisspace(*p); ++p);
if (*p && !xisspace(*p))
flags.listformat_unknown = 1;
- return html;
+ return true;
}
if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
ftpListPartsFree(&parts);
- return NULL;
+ return false;
}
parts->size += 1023;
parts->showname = xstrdup(parts->name);
/* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
- xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
+ SBuf href(prefix);
+ href.append(rfc1738_escape_part(parts->name));
- xstrncpy(text, parts->showname, 2048);
+ SBuf text(parts->showname);
+ SBuf icon, size, chdir, link;
switch (parts->type) {
case 'd':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
- mimeGetIconURL("internal-dir"),
- "[DIR]");
- strcat(href, "/"); /* margin is allocated above */
+ icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ mimeGetIconURL("internal-dir"),
+ "[DIR]");
+ href.append("/", 1); /* margin is allocated above */
break;
case 'l':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
- mimeGetIconURL("internal-link"),
- "[LINK]");
+ icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ mimeGetIconURL("internal-link"),
+ "[LINK]");
/* sometimes there is an 'l' flag, but no "->" link */
if (parts->link) {
- char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
- snprintf(link, 2048, " -> <a href=\"%s%s\">%s</a>",
- *link2 != '/' ? prefix : "", link2,
- html_quote(parts->link));
- safe_free(link2);
+ SBuf link2(html_quote(rfc1738_escape(parts->link)));
+ link.appendf(" -> <a href=\"%s" SQUIDSBUFPH "\">%s</a>",
+ link2[0] != '/' ? prefix.c_str() : "", SQUIDSBUFPRINT(link2),
+ html_quote(parts->link));
}
break;
case '\0':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
- mimeGetIconURL(parts->name),
- "[UNKNOWN]");
- snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
- "alt=\"[DIR]\"></a>",
- rfc1738_escape_part(parts->name),
- mimeGetIconURL("internal-dir"));
+ icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ mimeGetIconURL(parts->name),
+ "[UNKNOWN]");
+ chdir.appendf("<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
+ "alt=\"[DIR]\"></a>",
+ rfc1738_escape_part(parts->name),
+ mimeGetIconURL("internal-dir"));
break;
case '-':
default:
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
- mimeGetIconURL(parts->name),
- "[FILE]");
- snprintf(size, 2048, " %6" PRId64 "k", parts->size);
+ icon.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ mimeGetIconURL(parts->name),
+ "[FILE]");
+ size.appendf(" %6" PRId64 "k", parts->size);
break;
}
+ SBuf view, download;
if (parts->type != 'd') {
if (mimeGetViewOption(parts->name)) {
- snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
- "alt=\"[VIEW]\"></a>",
- prefix, href, mimeGetIconURL("internal-view"));
+ view.appendf("<a href=\"" SQUIDSBUFPH ";type=a\"><img border=\"0\" src=\"%s\" "
+ "alt=\"[VIEW]\"></a>",
+ SQUIDSBUFPRINT(href), mimeGetIconURL("internal-view"));
}
if (mimeGetDownloadOption(parts->name)) {
- snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
- "alt=\"[DOWNLOAD]\"></a>",
- prefix, href, mimeGetIconURL("internal-download"));
+ download.appendf("<a href=\"" SQUIDSBUFPH ";type=i\"><img border=\"0\" src=\"%s\" "
+ "alt=\"[DOWNLOAD]\"></a>",
+ SQUIDSBUFPRINT(href), mimeGetIconURL("internal-download"));
}
}
/* construct the table row from parts. */
- html = new MemBuf();
- html->init();
- html->Printf("<tr class=\"entry\">"
- "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
- "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
- "<td class=\"date\">%s</td>"
- "<td class=\"size\">%s</td>"
- "<td class=\"actions\">%s%s%s%s</td>"
- "</tr>\n",
- prefix, href, icon,
- prefix, href, html_quote(text),
- parts->date,
- size,
- chdir, view, download, link);
+ html << "<tr class=\"entry\">"
+ "<td class=\"icon\"><a href=\"" << href << "\">" << icon << "</a></td>"
+ "<td class=\"filename\"><a href=\"" << href << "\">" << html_quote(text.c_str()) << "</a></td>"
+ "<td class=\"date\">" << parts->date << "</td>"
+ "<td class=\"size\">" << size << "</td>"
+ "<td class=\"actions\">" << chdir << view << download << link << "</td>"
+ "</tr>\n";
ftpListPartsFree(&parts);
- return html;
+ return true;
}
void
Ftp::Gateway::parseListing()
{
char *buf = data.readBuf->content();
- char *sbuf; /* NULL-terminated copy of termedBuf */
+ char *sbuf; /* NULL-terminated copy of termedBuf */
char *end;
char *line;
char *s;
- MemBuf *t;
size_t linelen;
size_t usable;
size_t len = data.readBuf->contentSize();
if (!strncmp(line, "total", 5))
continue;
- t = htmlifyListEntry(line);
+ MemBuf htmlPage;
+ htmlPage.init();
+ PackableStream html(htmlPage);
- if ( t != NULL) {
- debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
- listing.append(t->content(), t->contentSize());
-//leak? delete t;
+ if (htmlifyListEntry(line, html)) {
+ html.flush();
+ debugs(9, 7, "listing append: t = {" << htmlPage.contentSize() << ", '" << htmlPage.content() << "'}");
+ listing.append(htmlPage.content(), htmlPage.contentSize());
}
}
* probably was aborted because content length exceeds one
* of the maximum size limits.
*/
- abortTransaction("entry aborted after calling appendSuccessHeader()");
+ abortAll("entry aborted after calling appendSuccessHeader()");
return;
}
* TODO: we might be able to do something about locating username from other sources:
* ie, external ACL user=* tag or ident lookup
*
- \retval 1 if we have everything needed to complete this request.
- \retval 0 if something is missing.
+ \retval 1 if we have everything needed to complete this request.
+ \retval 0 if something is missing.
*/
int
Ftp::Gateway::checkAuth(const HttpHeader * req_hdr)
#if HAVE_AUTH_MODULE_BASIC
/* Check HTTP Authorization: headers (better than defaults, but less than URL) */
- const SBuf auth(req_hdr->getAuth(HDR_AUTHORIZATION, "Basic"));
+ const auto auth(req_hdr->getAuthToken(Http::HdrType::AUTHORIZATION, "Basic"));
if (!auth.isEmpty()) {
flags.authenticated = 1;
loginParser(auth, false);
}
}
- return 0; /* different username */
+ return 0; /* different username */
}
-static String str_type_eq;
void
Ftp::Gateway::checkUrlpath()
{
- int l;
- size_t t;
+ static SBuf str_type_eq("type=");
+ auto t = request->url.path().rfind(';');
- if (str_type_eq.size()==0) //hack. String doesn't support global-static
- str_type_eq="type=";
-
- if ((t = request->urlpath.rfind(';')) != String::npos) {
- if (request->urlpath.substr(t+1,t+1+str_type_eq.size())==str_type_eq) {
- typecode = (char)xtoupper(request->urlpath[t+str_type_eq.size()+1]);
- request->urlpath.cut(t);
+ if (t != SBuf::npos) {
+ auto filenameEnd = t-1;
+ if (request->url.path().substr(++t).cmp(str_type_eq, str_type_eq.length()) == 0) {
+ t += str_type_eq.length();
+ typecode = (char)xtoupper(request->url.path()[t]);
+ request->url.path(request->url.path().substr(0,filenameEnd));
}
}
- l = request->urlpath.size();
+ int l = request->url.path().length();
/* check for null path */
if (!l) {
flags.isdir = 1;
flags.root_dir = 1;
- flags.need_base_href = 1; /* Work around broken browsers */
- } else if (!request->urlpath.cmp("/%2f/")) {
+ flags.need_base_href = 1; /* Work around broken browsers */
+ } else if (!request->url.path().cmp("/%2f/")) {
/* UNIX root directory */
flags.isdir = 1;
flags.root_dir = 1;
- } else if ((l >= 1) && (request->urlpath[l - 1] == '/')) {
+ } else if ((l >= 1) && (request->url.path()[l-1] == '/')) {
/* Directory URL, ending in / */
flags.isdir = 1;
title_url.append("@");
}
- title_url.append(request->GetHost());
-
- if (request->port != urlDefaultPort(AnyP::PROTO_FTP)) {
- title_url.append(":");
- title_url.append(xitoa(request->port));
- }
+ SBuf authority = request->url.authority(request->url.getScheme() != AnyP::PROTO_FTP);
- title_url.append (request->urlpath);
+ title_url.append(authority.rawContent(), authority.length());
+ title_url.append(request->url.path().rawContent(), request->url.path().length());
base_href = "ftp://";
base_href.append(rfc1738_escape_part(user));
if (password_url) {
- base_href.append (":");
+ base_href.append(":");
base_href.append(rfc1738_escape_part(password));
}
base_href.append("@");
}
- base_href.append(request->GetHost());
-
- if (request->port != urlDefaultPort(AnyP::PROTO_FTP)) {
- base_href.append(":");
- base_href.append(xitoa(request->port));
- }
-
- base_href.append(request->urlpath);
+ base_href.append(authority.rawContent(), authority.length());
+ base_href.append(request->url.path().rawContent(), request->url.path().length());
base_href.append("/");
}
{
if (!checkAuth(&request->header)) {
/* create appropriate reply */
- HttpReply *reply = ftpAuthRequired(request, ftpRealm());
+ SBuf realm(ftpRealm()); // local copy so SBuf will not disappear too early
+ const auto reply = ftpAuthRequired(request.getRaw(), realm, fwd->al);
entry->replaceHttpReply(reply);
serverComplete();
return;
checkUrlpath();
buildTitleUrl();
- debugs(9, 5, HERE << "FD " << ctrl.conn->fd << " : host=" << request->GetHost() <<
- ", path=" << request->urlpath << ", user=" << user << ", passwd=" << password);
+ debugs(9, 5, "FD " << (ctrl.conn ? ctrl.conn->fd : -1) << " : host=" << request->url.host() <<
+ ", path=" << request->url.path() << ", user=" << user << ", passwd=" << password);
state = BEGIN;
Ftp::Client::start();
}
Ftp::Gateway::loginFailed()
{
ErrorState *err = NULL;
- const char *command, *reply;
if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) {
if (ctrl.replycode == 421 || ctrl.replycode == 426) {
// 421/426 - Service Overload - retry permitted.
- err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request);
+ err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request, fwd->al);
} else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) {
// 43x - Invalid or Credential Error - retry challenge required.
- err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
+ err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
} else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) {
// 53x - Credentials Missing - retry challenge required
if (password_url) // but they were in the URI! major fail.
- err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request);
+ err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request, fwd->al);
else
- err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
+ err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
}
}
- // any other problems are general falures.
if (!err) {
ftpFail(this);
return;
}
- err->ftp.server_msg = ctrl.message;
-
- ctrl.message = NULL;
-
- if (old_request)
- command = old_request;
- else
- command = ctrl.last_command;
-
- if (command && strncmp(command, "PASS", 4) == 0)
- command = "PASS <yourpassword>";
-
- if (old_reply)
- reply = old_reply;
- else
- reply = ctrl.last_reply;
-
- if (command)
- err->ftp.request = xstrdup(command);
-
- if (reply)
- err->ftp.reply = xstrdup(reply);
+ failed(ERR_NONE, ctrl.replycode, err);
+ // any other problems are general falures.
HttpReply *newrep = err->BuildHttpReply();
delete err;
#if HAVE_AUTH_MODULE_BASIC
/* add Authenticate header */
- newrep->header.putAuth("Basic", ftpRealm());
+ // XXX: performance regression. c_str() may reallocate
+ SBuf realm(ftpRealm()); // local copy so SBuf will not disappear too early
+ newrep->header.putAuth("Basic", realm.c_str());
#endif
// add it to the store entry for response....
serverComplete();
}
-const char *
+SBuf
Ftp::Gateway::ftpRealm()
{
- static char realm[8192];
+ SBuf realm;
/* This request is not fully authenticated */
- if (!request) {
- snprintf(realm, 8192, "FTP %s unknown", user);
- } else if (request->port == 21) {
- snprintf(realm, 8192, "FTP %s %s", user, request->GetHost());
- } else {
- snprintf(realm, 8192, "FTP %s %s port %d", user, request->GetHost(), request->port);
+ realm.appendf("FTP %s ", user);
+ if (!request)
+ realm.append("unknown", 7);
+ else {
+ realm.append(request->url.host());
+ if (request->url.port() != 21)
+ realm.appendf(" port %d", request->url.port());
}
return realm;
}
return;
if (ftpState->proxy_host != NULL)
- snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n",
- ftpState->user,
- ftpState->request->GetHost());
+ snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n", ftpState->user, ftpState->request->url.host());
else
snprintf(cbuf, CTRL_BUFLEN, "USER %s\r\n", ftpState->user);
static void
ftpSendType(Ftp::Gateway * ftpState)
{
- const char *t;
- const char *filename;
- char mode;
-
/* check the server control channel is still available */
if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
return;
/*
* Ref section 3.2.2 of RFC 1738
*/
- mode = ftpState->typecode;
+ char mode = ftpState->typecode;
switch (mode) {
if (ftpState->flags.isdir) {
mode = 'A';
} else {
- t = ftpState->request->urlpath.rpos('/');
- filename = t ? t + 1 : ftpState->request->urlpath.termedBuf();
- mode = mimeGetTransferMode(filename);
+ auto t = ftpState->request->url.path().rfind('/');
+ // XXX: performance regression, c_str() may reallocate
+ SBuf filename = ftpState->request->url.path().substr(t != SBuf::npos ? t + 1 : 0);
+ mode = mimeGetTransferMode(filename.c_str());
}
break;
debugs(9, 3, HERE << "code=" << code);
if (code == 200) {
- p = path = xstrdup(ftpState->request->urlpath.termedBuf());
+ p = path = SBufToCstring(ftpState->request->url.path());
if (*p == '/')
++p;
static void
ftpTraverseDirectory(Ftp::Gateway * ftpState)
{
- wordlist *w;
debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>"));
safe_free(ftpState->dirpath);
}
/* Go to next path component */
- w = ftpState->pathcomps;
-
- ftpState->filepath = w->key;
-
- ftpState->pathcomps = w->next;
-
- delete w;
+ ftpState->filepath = wordlistChopHead(& ftpState->pathcomps);
/* Check if we are to CWD or RETR */
if (ftpState->pathcomps != NULL || ftpState->flags.isdir) {
/* Reset cwd_message to only include the last message */
ftpState->cwd_message.reset("");
for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
- ftpState->cwd_message.append(' ');
+ ftpState->cwd_message.append('\n');
ftpState->cwd_message.append(w->key);
}
ftpState->ctrl.message = NULL;
debugs(9, 3, HERE << "path " << path << ", code " << code);
- if (code == 257) { /* success */
+ if (code == 257) { /* success */
ftpSendCwd(ftpState);
- } else if (code == 550) { /* dir exists */
+ } else if (code == 550) { /* dir exists */
if (ftpState->flags.put_mkdir) {
ftpState->flags.put_mkdir = 1;
* trying to write to the client.
*/
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- abortTransaction("entry aborted while processing HEAD");
+ abortAll("entry aborted while processing HEAD");
return;
}
if (ftpState->handlePasvReply(srvAddr))
ftpState->connectDataChannel();
else {
- ftpSendEPRT(ftpState);
+ ftpFail(ftpState);
+ // Currently disabled, does not work correctly:
+ // ftpSendEPRT(ftpState);
return;
}
}
// ABORT on timeouts. server may be waiting on a broken TCP link.
if (io.xerrno == Comm::TIMEOUT)
- writeCommand("ABOR");
+ writeCommand("ABOR\r\n");
// try another connection attempt with some other method
ftpSendPassive(this);
}
safe_free(ftpState->data.host);
+ if (!Comm::IsConnOpen(ftpState->ctrl.conn)) {
+ debugs(9, 5, "The control connection to the remote end is closed");
+ return;
+ }
+
/*
* Set up a listen socket on the same local address as the
* control connection.
*/
if (fallback) {
int on = 1;
- setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
+ errno = 0;
+ if (setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &on, sizeof(on)) == -1) {
+ int xerrno = errno;
+ // SO_REUSEADDR is only an optimization, no need to be verbose about error
+ debugs(9, 4, "setsockopt failed: " << xstrerr(xerrno));
+ }
ftpState->ctrl.conn->flags |= COMM_REUSEADDR;
temp->flags |= COMM_REUSEADDR;
} else {
ftpRestOrList(ftpState);
}
+#if 0
static void
ftpSendEPRT(Ftp::Gateway * ftpState)
{
+ /* check the server control channel is still available */
+ if (!ftpState || !ftpState->haveControlChannel("ftpSendEPRT"))
+ return;
+
if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
return;
ftpState->writeCommand(cbuf);
ftpState->state = Ftp::Client::SENT_EPRT;
}
+#endif
static void
ftpReadEPRT(Ftp::Gateway * ftpState)
{
debugs(9, 3, HERE);
- if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- abortTransaction("entry aborted when accepting data conn");
- data.listenConn->close();
- data.listenConn = NULL;
+ if (!Comm::IsConnOpen(ctrl.conn)) { /*Close handlers will cleanup*/
+ debugs(9, 5, "The control connection to the remote end is closed");
return;
}
return;
}
+ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+ abortAll("entry aborted when accepting data conn");
+ data.listenConn->close();
+ data.listenConn = NULL;
+ io.conn->close();
+ return;
+ }
+
/* data listening conn is no longer even open. abort. */
if (!Comm::IsConnOpen(data.listenConn)) {
data.listenConn = NULL; // ensure that it's cleared and not just closed.
ftpState->flags.isdir = 1;
if (ftpState->flags.put) {
- ftpSendMkdir(ftpState); /* PUT name;type=d */
+ ftpSendMkdir(ftpState); /* PUT name;type=d */
} else {
- ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
+ ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
}
} else if (ftpState->flags.put) {
ftpSendStor(ftpState);
snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath);
ftpState->writeCommand(cbuf);
ftpState->state = Ftp::Client::SENT_STOR;
- } else if (ftpState->request->header.getInt64(HDR_CONTENT_LENGTH) > 0) {
+ } else if (ftpState->request->header.getInt64(Http::HdrType::CONTENT_LENGTH) > 0) {
/* File upload without a filename. use STOU to generate one */
snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n");
ftpState->writeCommand(cbuf);
debugs(9, 3, HERE << "starting data transfer");
switchTimeoutToDataChannel();
sendMoreRequestBody();
- fwd->dontRetry(true); // dont permit re-trying if the body was sent.
+ fwd->dontRetry(true); // do not permit re-trying if the body was sent.
state = WRITING_DATA;
debugs(9, 3, HERE << "writing data channel");
} else if (code == 150) {
{
assert(entry);
entry->lock("Ftp::Gateway");
- ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request);
+ ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request.getRaw(), fwd->al);
ferr.ftp.listing = &listing;
ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
ferr.ftp.server_msg = ctrl.message;
ctrl.message = NULL;
- entry->replaceHttpReply( ferr.BuildHttpReply() );
- EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+ entry->replaceHttpReply(ferr.BuildHttpReply());
entry->flush();
entry->unlock("Ftp::Gateway");
}
/* QUIT operation handles sending the reply to client */
}
ftpSendQuit(ftpState);
- } else { /* != 226 */
+ } else { /* != 226 */
debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
ftpState->failed(ERR_FTP_FAILURE, 0);
/* failed closes ctrl.conn and frees ftpState */
return;
}
- ftpState->entry->timestampsSet(); /* XXX Is this needed? */
+ ftpState->entry->timestampsSet(); /* XXX Is this needed? */
ftpSendReply(ftpState);
}
safe_free(ftpState->filepath);
/* Build the new path (urlpath begins with /) */
- path = xstrdup(ftpState->request->urlpath.termedBuf());
+ path = SBufToCstring(ftpState->request->url.path());
rfc1738_unescape(path);
static void
ftpFail(Ftp::Gateway *ftpState)
{
- debugs(9, 6, HERE << "flags(" <<
+ const bool slashHack = ftpState->request->url.path().caseCmp("/%2f", 4)==0;
+ int code = ftpState->ctrl.replycode;
+ err_type error_code = ERR_NONE;
+
+ debugs(9, 6, "state " << ftpState->state <<
+ " reply code " << code << "flags(" <<
(ftpState->flags.isdir?"IS_DIR,":"") <<
(ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
"mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
- "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
+ "slashhack=" << (slashHack? "T":"F"));
/* Try the / hack to support "Netscape" FTP URL's for retreiving files */
- if (!ftpState->flags.isdir && /* Not a directory */
- !ftpState->flags.try_slash_hack && /* Not in slash hack */
- ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
- ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
+ if (!ftpState->flags.isdir && /* Not a directory */
+ !ftpState->flags.try_slash_hack && !slashHack && /* Not doing slash hack */
+ ftpState->mdtm <= 0 && ftpState->theSize < 0) { /* Not known as a file */
switch (ftpState->state) {
}
}
- ftpState->failed(ERR_NONE, 0);
- /* failed() closes ctrl.conn and frees this */
+ Http::StatusCode sc = ftpState->failedHttpStatus(error_code);
+ const auto ftperr = new ErrorState(error_code, sc, ftpState->fwd->request, ftpState->fwd->al);
+ ftpState->failed(error_code, code, ftperr);
+ ftperr->detailError(code);
+ HttpReply *newrep = ftperr->BuildHttpReply();
+ delete ftperr;
+
+ ftpState->entry->replaceHttpReply(newrep);
+ ftpSendQuit(ftpState);
}
Http::StatusCode
http_code = Http::scInternalServerError;
}
- ErrorState err(err_code, http_code, ftpState->request);
+ ErrorState err(err_code, http_code, ftpState->request.getRaw(), ftpState->fwd->al);
if (ftpState->old_request)
err.ftp.request = xstrdup(ftpState->old_request);
// TODO: interpret as FTP-specific error code
err.detailError(code);
- ftpState->entry->replaceHttpReply( err.BuildHttpReply() );
+ ftpState->entry->replaceHttpReply(err.BuildHttpReply());
ftpSendQuit(ftpState);
}
void
Ftp::Gateway::appendSuccessHeader()
{
- const char *mime_type = NULL;
- const char *mime_enc = NULL;
- String urlpath = request->urlpath;
- const char *filename = NULL;
- const char *t = NULL;
-
debugs(9, 3, HERE);
if (flags.http_header_sent)
assert(entry->isEmpty());
- EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+ entry->buffer(); /* released when done processing current data payload */
- entry->buffer(); /* released when done processing current data payload */
+ SBuf urlPath = request->url.path();
+ auto t = urlPath.rfind('/');
+ SBuf filename = urlPath.substr(t != SBuf::npos ? t : 0);
- filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
+ const char *mime_type = NULL;
+ const char *mime_enc = NULL;
if (flags.isdir) {
mime_type = "text/html";
case 'I':
mime_type = "application/octet-stream";
- mime_enc = mimeGetContentEncoding(filename);
+ // XXX: performance regression, c_str() may reallocate
+ mime_enc = mimeGetContentEncoding(filename.c_str());
break;
case 'A':
break;
default:
- mime_type = mimeGetContentType(filename);
- mime_enc = mimeGetContentEncoding(filename);
+ // XXX: performance regression, c_str() may reallocate
+ mime_type = mimeGetContentType(filename.c_str());
+ mime_enc = mimeGetContentEncoding(filename.c_str());
break;
}
}
/* additional info */
if (mime_enc)
- reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
+ reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc);
+ reply->sources |= Http::Message::srcFtp;
setVirginReply(reply);
adaptOrFinalizeReply();
}
e->timestampsSet();
- if (flags.authenticated) {
- /*
- * Authenticated requests can't be cached.
- */
- e->release();
- } else if (!EBIT_TEST(e->flags, RELEASE_REQUEST) && !getCurrentOffset()) {
- e->setPublicKey();
- } else {
+ // makePublic() if allowed/possible or release() otherwise
+ if (flags.authenticated || // authenticated requests can't be cached
+ getCurrentOffset() ||
+ !e->makePublic()) {
e->release();
}
}
HttpReply *
-Ftp::Gateway::ftpAuthRequired(HttpRequest * request, const char *realm)
+Ftp::Gateway::ftpAuthRequired(HttpRequest * request, SBuf &realm, AccessLogEntry::Pointer &ale)
{
- ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request);
+ ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request, ale);
HttpReply *newrep = err.BuildHttpReply();
#if HAVE_AUTH_MODULE_BASIC
/* add Authenticate header */
- newrep->header.putAuth("Basic", realm);
+ // XXX: performance regression. c_str() may reallocate
+ newrep->header.putAuth("Basic", realm.c_str());
#endif
return newrep;
}
-const char *
+const SBuf &
Ftp::UrlWith2f(HttpRequest * request)
{
- String newbuf = "%2f";
+ SBuf newbuf("%2f");
- if (request->url.getScheme() != AnyP::PROTO_FTP)
- return NULL;
+ if (request->url.getScheme() != AnyP::PROTO_FTP) {
+ static const SBuf nil;
+ return nil;
+ }
- if ( request->urlpath[0]=='/' ) {
- newbuf.append(request->urlpath);
- request->urlpath.absorb(newbuf);
- safe_free(request->canonical);
- } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
- newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
- request->urlpath.absorb(newbuf);
- safe_free(request->canonical);
+ if (request->url.path()[0] == '/') {
+ newbuf.append(request->url.path());
+ request->url.path(newbuf);
+ } else if (!request->url.path().startsWith(newbuf)) {
+ newbuf.append(request->url.path().substr(1));
+ request->url.path(newbuf);
}
- return urlCanonical(request);
+ return request->effectiveRequestUri();
}
void
* A hack to ensure we do not double-complete on the forward entry.
*
\todo Ftp::Gateway logic should probably be rewritten to avoid
- * double-completion or FwdState should be rewritten to allow it.
+ * double-completion or FwdState should be rewritten to allow it.
*/
void
Ftp::Gateway::completeForwarding()
{
if (fwd == NULL || flags.completed_forwarding) {
- debugs(9, 3, HERE << "completeForwarding avoids " <<
- "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd <<
+ debugs(9, 3, "avoid double-complete on FD " <<
+ (ctrl.conn ? ctrl.conn->fd : -1) << ", Data FD " << data.conn->fd <<
", this " << this << ", fwd " << fwd);
return;
}
/**
* Have we lost the FTP server control channel?
*
- \retval true The server control channel is available.
- \retval false The server control channel is not available.
+ \retval true The server control channel is available.
+ \retval false The server control channel is not available.
*/
bool
Ftp::Gateway::haveControlChannel(const char *caller_name) const
{
return AsyncJob::Start(new Ftp::Gateway(fwdState));
}
+