From: Alex Rousskov Date: Thu, 7 Aug 2014 17:31:05 +0000 (-0600) Subject: Converted some of the new FTP code to use SBuf and Tokenizer X-Git-Tag: SQUID_3_5_0_1~117^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1ab04517e705e2aaa2afc9d45bb3dcb45e6030da;p=thirdparty%2Fsquid.git Converted some of the new FTP code to use SBuf and Tokenizer instead of MemBuf, String, and c-string manipulations. --- diff --git a/src/Makefile.am b/src/Makefile.am index 0fa15d2f07..f42403cd44 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -673,15 +673,15 @@ squid_DEPENDENCIES = \ $(AUTH_LIBS) \ acl/libapi.la \ base/libbase.la \ - ftp/libftp.la \ clients/libclients.la \ - servers/libservers.la \ + ftp/libftp.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ format/libformat.la \ ipc/libipc.la \ - mgr/libmgr.la + mgr/libmgr.la \ + servers/libservers.la if ENABLE_LOADABLE_MODULES squid_SOURCES += $(LOADABLE_MODULES_SOURCES) @@ -1615,6 +1615,7 @@ tests_testCacheManager_LDADD = \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ @@ -2050,6 +2051,7 @@ tests_testEvent_LDADD = \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ @@ -2301,6 +2303,7 @@ tests_testEventLoop_LDADD = \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ @@ -2545,6 +2548,7 @@ tests_test_http_range_LDADD = \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ @@ -2837,6 +2841,7 @@ tests_testHttpRequest_LDADD = \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ @@ -3682,6 +3687,7 @@ tests_testURL_LDADD = \ eui/libeui.la \ acl/libstate.la \ acl/libapi.la \ + parser/libsquid-parser.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ diff --git a/src/clients/FtpRelay.cc b/src/clients/FtpRelay.cc index 4163686870..e4f7b7b757 100644 --- a/src/clients/FtpRelay.cc +++ b/src/clients/FtpRelay.cc @@ -12,6 +12,7 @@ #include "ftp/Parsing.h" #include "HttpHdrCc.h" #include "HttpRequest.h" +#include "SBuf.h" #include "servers/FtpServer.h" #include "Server.h" #include "SquidTime.h" @@ -508,14 +509,13 @@ Ftp::Relay::sendCommand() return; } - static MemBuf mb; - mb.reset(); + SBuf buf; if (params.size() > 0) - mb.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); + buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); else - mb.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); + buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); - writeCommand(mb.content()); + writeCommand(buf.c_str()); state = serverState() == fssHandleCdup ? SENT_CDUP : diff --git a/src/ftp/Elements.cc b/src/ftp/Elements.cc new file mode 100644 index 0000000000..6e053706c8 --- /dev/null +++ b/src/ftp/Elements.cc @@ -0,0 +1,147 @@ +/* + * DEBUG: section 09 File Transfer Protocol (FTP) + */ + +#include "squid.h" +#include "ftp/Elements.h" +#include "SBuf.h" + +const SBuf & +Ftp::cmdAppe() +{ + static const SBuf cmd("APPE"); + return cmd; +} + +const SBuf & +Ftp::cmdAuth() +{ + static const SBuf cmd("AUTH"); + return cmd; +} + +const SBuf & +Ftp::cmdCwd() +{ + static const SBuf cmd("CWD"); + return cmd; +} + +const SBuf & +Ftp::cmdDele() +{ + static const SBuf cmd("DELE"); + return cmd; +} + +const SBuf & +Ftp::cmdEprt() +{ + static const SBuf cmd("EPRT"); + return cmd; +} + +const SBuf & +Ftp::cmdEpsv() +{ + static const SBuf cmd("EPSV"); + return cmd; +} + +const SBuf & +Ftp::cmdList() +{ + static const SBuf cmd("LIST"); + return cmd; +} + +const SBuf & +Ftp::cmdMkd() +{ + static const SBuf cmd("MKD"); + return cmd; +} + +const SBuf & +Ftp::cmdMlsd() +{ + static const SBuf cmd("MLSD"); + return cmd; +} + +const SBuf & +Ftp::cmdMlst() +{ + static const SBuf cmd("MLST"); + return cmd; +} + +const SBuf & +Ftp::cmdNlst() +{ + static const SBuf cmd("NLST"); + return cmd; +} + +const SBuf & +Ftp::cmdRetr() +{ + static const SBuf cmd("RETR"); + return cmd; +} + +const SBuf & +Ftp::cmdRmd() +{ + static const SBuf cmd("RMD"); + return cmd; +} + +const SBuf & +Ftp::cmdRnfr() +{ + static const SBuf cmd("RNFR"); + return cmd; +} + +const SBuf & +Ftp::cmdRnto() +{ + static const SBuf cmd("RNTO"); + return cmd; +} + +const SBuf & +Ftp::cmdSmnt() +{ + static const SBuf cmd("SMNT"); + return cmd; +} + +const SBuf & +Ftp::cmdStat() +{ + static const SBuf cmd("STAT"); + return cmd; +} + +const SBuf & +Ftp::cmdStor() +{ + static const SBuf cmd("STOR"); + return cmd; +} + +const SBuf & +Ftp::cmdStou() +{ + static const SBuf cmd("STOU"); + return cmd; +} + +const SBuf & +Ftp::cmdUser() +{ + static const SBuf cmd("USER"); + return cmd; +} diff --git a/src/ftp/Elements.h b/src/ftp/Elements.h new file mode 100644 index 0000000000..6d77e86de9 --- /dev/null +++ b/src/ftp/Elements.h @@ -0,0 +1,32 @@ +#ifndef SQUID_FTP_ELEMENTS_H +#define SQUID_FTP_ELEMENTS_H + +class SBuf; + +namespace Ftp { + +/* FTP Commands used by Squid. ALLCAPS case. Safe for static initializaton. */ +const SBuf &cmdAppe(); +const SBuf &cmdAuth(); +const SBuf &cmdCwd(); +const SBuf &cmdDele(); +const SBuf &cmdEprt(); +const SBuf &cmdEpsv(); +const SBuf &cmdList(); +const SBuf &cmdMkd(); +const SBuf &cmdMlsd(); +const SBuf &cmdMlst(); +const SBuf &cmdNlst(); +const SBuf &cmdRetr(); +const SBuf &cmdRmd(); +const SBuf &cmdRnfr(); +const SBuf &cmdRnto(); +const SBuf &cmdSmnt(); +const SBuf &cmdStat(); +const SBuf &cmdStor(); +const SBuf &cmdStou(); +const SBuf &cmdUser(); + +} // namespace Ftp + +#endif /* SQUID_FTP_ELEMENTS_H */ diff --git a/src/ftp/Makefile.am b/src/ftp/Makefile.am index 4c4d083c0f..6117116d9c 100644 --- a/src/ftp/Makefile.am +++ b/src/ftp/Makefile.am @@ -3,5 +3,7 @@ include $(top_srcdir)/src/Common.am noinst_LTLIBRARIES = libftp.la libftp_la_SOURCES = \ + Elements.cc \ + Elements.h \ Parsing.cc \ Parsing.h diff --git a/src/parser/testTokenizer.cc b/src/parser/testTokenizer.cc index ac4dec4bf0..7443e07eeb 100644 --- a/src/parser/testTokenizer.cc +++ b/src/parser/testTokenizer.cc @@ -60,8 +60,8 @@ testTokenizer::testTokenizerSkip() CPPUNIT_ASSERT(t.prefix(s,alpha)); CPPUNIT_ASSERT_EQUAL(SBuf("GET"),s); - // test skip testing character set - CPPUNIT_ASSERT(t.skip(whitespace)); + // test skipping one character from a character set + CPPUNIT_ASSERT(t.skipOne(whitespace)); // check that skip was right CPPUNIT_ASSERT(t.prefix(s,alpha)); CPPUNIT_ASSERT_EQUAL(SBuf("http"),s); @@ -73,10 +73,14 @@ testTokenizer::testTokenizerSkip() CPPUNIT_ASSERT_EQUAL(SBuf("resource"),s); // no skip - CPPUNIT_ASSERT(!t.skip(alpha)); + CPPUNIT_ASSERT(!t.skipOne(alpha)); CPPUNIT_ASSERT(!t.skip(SBuf("://"))); CPPUNIT_ASSERT(!t.skip('a')); + // test skipping all characters from a character set while looking at .com + CPPUNIT_ASSERT(t.skip('.')); + CPPUNIT_ASSERT_EQUAL(static_cast(3), t.skipAll(alpha)); + CPPUNIT_ASSERT(t.remaining().startsWith(SBuf("/path"))); } void diff --git a/src/servers/FtpServer.cc b/src/servers/FtpServer.cc index effd90e8cd..37130c2a1c 100644 --- a/src/servers/FtpServer.cc +++ b/src/servers/FtpServer.cc @@ -3,6 +3,7 @@ */ #include "squid.h" +#include "base/CharacterSet.h" #include "base/Subscription.h" #include "clientStream.h" #include "comm/ConnOpener.h" @@ -13,23 +14,28 @@ #include "client_side_request.h" #include "errorpage.h" #include "fd.h" +#include "ftp/Elements.h" #include "ftp/Parsing.h" #include "globals.h" #include "HttpHdrCc.h" #include "ip/tools.h" #include "ipc/FdNotes.h" +#include "parser/Tokenizer.h" #include "servers/forward.h" #include "servers/FtpServer.h" #include "SquidConfig.h" #include "StatCounters.h" #include "tools.h" +#include +#include + CBDATA_NAMESPACED_CLASS_INIT(Ftp, Server); namespace Ftp { static void PrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = ""); -static bool SupportedCommand(const String &name); -static bool CommandHasPathParameter(const String &cmd); +static bool SupportedCommand(const SBuf &name); +static bool CommandHasPathParameter(const SBuf &cmd); }; Ftp::Server::Server(const MasterXaction::Pointer &xact): @@ -76,7 +82,7 @@ Ftp::Server::start() char buf[MAX_IPSTRLEN]; clientConnection->local.toUrl(buf, MAX_IPSTRLEN); host = buf; - calcUri(); + calcUri(NULL); debugs(33, 5, "FTP transparent URL: " << uri); } @@ -315,24 +321,24 @@ Ftp::Server::resetLogin(const char *reason) /// computes uri member from host and, if tracked, working dir with file name void -Ftp::Server::calcUri(const char *file) +Ftp::Server::calcUri(const SBuf *file) { uri = "ftp://"; uri.append(host); - if (port->ftp_track_dirs && master.workingDir.size()) { + if (port->ftp_track_dirs && master.workingDir.length()) { if (master.workingDir[0] != '/') uri.append("/"); uri.append(master.workingDir); } - if (uri[uri.size() - 1] != '/') + if (uri[uri.length() - 1] != '/') uri.append("/"); if (port->ftp_track_dirs && file) { - // remove any '/' from the beginning of path - while (*file == '/') - ++file; - uri.append(file); + static const CharacterSet Slash("/", "/"); + Parser::Tokenizer tok(*file); + tok.skipAll(Slash); + uri.append(tok.remaining()); } } @@ -347,7 +353,7 @@ Ftp::Server::listenForDataConnection() conn->flags = COMM_NONBLOCKING; conn->local = transparent() ? port->s : clientConnection->local; conn->local.port(0); - const char *const note = uri.termedBuf(); + const char *const note = uri.c_str(); comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn, note); if (!Comm::IsConnOpen(conn)) { debugs(5, DBG_CRITICAL, "comm_open_listener failed for FTP data: " << @@ -514,13 +520,28 @@ Ftp::Server::changeState(const ServerState newState, const char *reason) /// whether the given FTP command has a pathname parameter static bool -Ftp::CommandHasPathParameter(const String &cmd) -{ - static const char *pathCommandsStr[]= {"CWD","SMNT", "RETR", "STOR", "APPE", - "RNFR", "RNTO", "DELE", "RMD", "MKD", - "LIST", "NLST", "STAT", "MLSD", "MLST"}; - static const std::set pathCommands(pathCommandsStr, pathCommandsStr + sizeof(pathCommandsStr)/sizeof(pathCommandsStr[0])); - return pathCommands.find(cmd) != pathCommands.end(); +Ftp::CommandHasPathParameter(const SBuf &cmd) +{ + static std::set PathedCommands; + if (!PathedCommands.size()) { + PathedCommands.insert(cmdMlst()); + PathedCommands.insert(cmdMlsd()); + PathedCommands.insert(cmdStat()); + PathedCommands.insert(cmdNlst()); + PathedCommands.insert(cmdList()); + PathedCommands.insert(cmdMkd()); + PathedCommands.insert(cmdRmd()); + PathedCommands.insert(cmdDele()); + PathedCommands.insert(cmdRnto()); + PathedCommands.insert(cmdRnfr()); + PathedCommands.insert(cmdAppe()); + PathedCommands.insert(cmdStor()); + PathedCommands.insert(cmdRetr()); + PathedCommands.insert(cmdSmnt()); + PathedCommands.insert(cmdCwd()); + } + + return PathedCommands.find(cmd) != PathedCommands.end(); } /// Parses a single FTP request on the control connection. @@ -528,73 +549,65 @@ Ftp::CommandHasPathParameter(const String &cmd) ClientSocketContext * Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver) { - ver = Http::ProtocolVersion(1, 1); - - // TODO: Use tokenizer for parsing instead of raw pointer manipulation. - const char *inBuf = in.buf.rawContent(); + // OWS [ RWS ] OWS LF + static const CharacterSet WhiteSpace = CharacterSet("Ftp::WS", " \f\r\t\v"); + static const CharacterSet BlackSpace = WhiteSpace.complement("!Ftp::WS"); + static const CharacterSet NewLine = CharacterSet("NL", "\n"); + static const CharacterSet OldLine = NewLine.complement("!NL"); - const char *const eor = - static_cast(memchr(inBuf, '\n', - min(static_cast(in.buf.length()), Config.maxRequestHeaderSize))); + // This set is used to ignore empty commands without allowing an attacker + // to keep us endlessly busy by feeding us whitespace or empty commands. + static const CharacterSet LeadingSpace = (WhiteSpace + NewLine).rename("Ftp::LeadingSpace"); - if (eor == NULL && in.buf.length() >= Config.maxRequestHeaderSize) { - changeState(fssError, "huge req"); - writeEarlyReply(421, "Too large request"); - return NULL; - } + SBuf cmd; + SBuf params; - if (eor == NULL) { - debugs(33, 5, "Incomplete request, waiting for end of request"); - return NULL; - } + Parser::Tokenizer tok(in.buf); - const size_t req_sz = eor + 1 - inBuf; + (void)tok.skipAll(LeadingSpace); // leading OWS and empty commands + const bool parsed = tok.prefix(cmd, BlackSpace); // required command - // skip leading whitespaces - const char *boc = inBuf; // beginning of command - while (boc < eor && isspace(*boc)) ++boc; - if (boc >= eor) { - debugs(33, 5, "Empty request, ignoring"); - consumeInput(req_sz); - return NULL; + // note that the condition below will eat either RWS or trailing OWS + if (parsed && tok.skipAll(WhiteSpace) && tok.prefix(params, OldLine)) { + // now params may include trailing OWS + // TODO: Support right-trimming using CharacterSet in Tokenizer instead + static const SBuf bufWhiteSpace("\r\t "); + params.trim(bufWhiteSpace, false, true); } - const char *eoc = boc; // end of command - while (eoc < eor && !isspace(*eoc)) ++eoc; - in.buf.setAt(eoc - inBuf, '\0'); - - const char *bop = eoc + 1; // beginning of parameter - while (bop < eor && isspace(*bop)) ++bop; - if (bop < eor) { - const char *eop = eor - 1; - while (isspace(*eop)) --eop; - assert(eop >= bop); - in.buf.setAt(eop + 1 - inBuf, '\0'); - } else - bop = NULL; + // technically, we may skip multiple NLs below, but that is OK + if (!parsed || !tok.skipAll(NewLine)) { // did not find terminating LF yet + // we need more data, but can we buffer more? + if (in.buf.length() >= Config.maxRequestHeaderSize) { + changeState(fssError, "huge req"); + writeEarlyReply(421, "Huge request"); + return NULL; + } else { + debugs(33, 5, "Waiting for more, up to " << + (Config.maxRequestHeaderSize - in.buf.length())); + return NULL; + } + } - debugs(33, 7, "Parsed FTP command " << boc << " with " << - (bop == NULL ? "no " : "") << "parameters" << - (bop != NULL ? ": " : "") << bop); + Must(parsed && cmd.length()); + consumeInput(tok.parsedSize()); // TODO: Would delaying optimize copying? - // TODO: Use SBuf instead of String - const String cmd = boc; - String params = bop; + debugs(33, 2, ">>ftp " << cmd << (params.isEmpty() ? "" : " ") << params); - consumeInput(req_sz); + cmd.toUpper(); // this should speed up and simplify future comparisons // interception cases do not need USER to calculate the uri if (!transparent()) { if (!master.clientReadGreeting) { // the first command must be USER - if (!pinning.pinned && cmd.caseCmp("USER") != 0) { + if (!pinning.pinned && cmd != cmdUser()) { writeEarlyReply(530, "Must login first"); return NULL; } } // process USER request now because it sets FTP peer host name - if (cmd.caseCmp("USER") == 0 && !handleUserRequest(cmd, params)) + if (cmd == cmdUser() && !handleUserRequest(cmd, params)) return NULL; } @@ -604,22 +617,23 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver) } const HttpRequestMethod method = - !cmd.caseCmp("APPE") || !cmd.caseCmp("STOR") || !cmd.caseCmp("STOU") ? + cmd == cmdAppe() || cmd == cmdStor() || cmd == cmdStou() ? Http::METHOD_PUT : Http::METHOD_GET; - const char *aPath = params.size() > 0 && CommandHasPathParameter(cmd) ? - params.termedBuf() : NULL; - calcUri(aPath); - char *newUri = xstrdup(uri.termedBuf()); + const SBuf *path = params.length() && CommandHasPathParameter(cmd) ? + ¶ms : NULL; + calcUri(path); + char *newUri = xstrdup(uri.c_str()); HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method); if (!request) { debugs(33, 5, "Invalid FTP URL: " << uri); writeEarlyReply(501, "Invalid host"); - uri.clean(); + uri.clear(); safe_free(newUri); return NULL; } + ver = Http::ProtocolVersion(1, 1); request->flags.ftpNative = true; request->http_ver = ver; @@ -627,9 +641,8 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver) request->flags.cachable = false; // XXX: reset later by maybeCacheable() request->flags.noCache = true; - request->header.putStr(HDR_FTP_COMMAND, cmd.termedBuf()); - request->header.putStr(HDR_FTP_ARGUMENTS, params.termedBuf() != NULL ? - params.termedBuf() : ""); + request->header.putStr(HDR_FTP_COMMAND, cmd.c_str()); + request->header.putStr(HDR_FTP_ARGUMENTS, params.c_str()); // may be "" if (method == Http::METHOD_PUT) { request->header.putStr(HDR_EXPECT, "100-continue"); request->header.putStr(HDR_TRANSFER_ENCODING, "chunked"); @@ -638,7 +651,7 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver) ClientHttpRequest *const http = new ClientHttpRequest(this); http->request = request; HTTPMSGLOCK(http->request); - http->req_sz = req_sz; + http->req_sz = tok.parsedSize(); http->uri = newUri; ClientSocketContext *const result = @@ -718,29 +731,29 @@ Ftp::Server::handleFeatReply(const HttpReply *reply, StoreIOBuffer data) // assume RFC 2389 FEAT response format, quoted by Squid: // <"> SP NAME [SP PARAMS] <"> // but accommodate MS servers sending four SPs before NAME - if (e->value.size() < 4) - continue; - const char *raw = e->value.termedBuf(); - if (raw[0] != '"' || raw[1] != ' ') - continue; - const char *beg = raw + 1 + strspn(raw + 1, " "); // after quote and spaces + // command name ends with (SP parameter) or quote - const char *end = beg + strcspn(beg, " \""); + static const CharacterSet AfterFeatNameChars("AfterFeatName", " \""); + static const CharacterSet FeatNameChars = AfterFeatNameChars.complement("FeatName"); - if (end <= beg) + Parser::Tokenizer tok(SBuf(e->value.termedBuf())); + if (!tok.skip('"') && !tok.skip(' ')) continue; - // compute the number of spaces before the command - prependSpaces = beg - raw - 1; + // optional spaces; remember their number to accomodate MS servers + prependSpaces = 1 + tok.skipAll(CharacterSet::SP); - const String cmd = e->value.substr(beg-raw, end-raw); + SBuf cmd; + if (!tok.prefix(cmd, FeatNameChars)) + continue; + cmd.toUpper(); if (!Ftp::SupportedCommand(cmd)) filteredHeader.delAt(pos, deletedCount); - if (cmd == "EPRT") + if (cmd == cmdEprt()) hasEPRT = true; - else if (cmd == "EPSV") + else if (cmd == cmdEpsv()) hasEPSV = true; } } @@ -823,7 +836,7 @@ void Ftp::Server::handleErrorReply(const HttpReply *reply, StoreIOBuffer data) { if (!pinning.pinned) // we failed to connect to server - uri.clean(); + uri.clear(); // 421: we will close due to fssError writeErrorReply(reply, 421); } @@ -1193,76 +1206,80 @@ Ftp::Server::handleRequest(String &cmd, String ¶ms) { "\n----------"); } - // TODO: optimize using a static map with case-insensitive lookup - static std::pair handlers[] = { - std::make_pair("LIST", &Ftp::Server::handleDataRequest), - std::make_pair("NLST", &Ftp::Server::handleDataRequest), - std::make_pair("MLSD", &Ftp::Server::handleDataRequest), - std::make_pair("FEAT", &Ftp::Server::handleFeatRequest), - std::make_pair("PASV", &Ftp::Server::handlePasvRequest), - std::make_pair("PORT", &Ftp::Server::handlePortRequest), - std::make_pair("RETR", &Ftp::Server::handleDataRequest), - std::make_pair("EPRT", &Ftp::Server::handleEprtRequest), - std::make_pair("EPSV", &Ftp::Server::handleEpsvRequest), - std::make_pair("CWD", &Ftp::Server::handleCwdRequest), - std::make_pair("PASS", &Ftp::Server::handlePassRequest), - std::make_pair("CDUP", &Ftp::Server::handleCdupRequest) - }; + // TODO: When HttpHeader uses SBuf, change keys to SBuf + typedef std::map RequestHandlers; + static RequestHandlers handlers; + if (!handlers.size()) { + handlers["LIST"] = &Ftp::Server::handleDataRequest; + handlers["NLST"] = &Ftp::Server::handleDataRequest; + handlers["MLSD"] = &Ftp::Server::handleDataRequest; + handlers["FEAT"] = &Ftp::Server::handleFeatRequest; + handlers["PASV"] = &Ftp::Server::handlePasvRequest; + handlers["PORT"] = &Ftp::Server::handlePortRequest; + handlers["RETR"] = &Ftp::Server::handleDataRequest; + handlers["EPRT"] = &Ftp::Server::handleEprtRequest; + handlers["EPSV"] = &Ftp::Server::handleEpsvRequest; + handlers["CWD"] = &Ftp::Server::handleCwdRequest; + handlers["PASS"] = &Ftp::Server::handlePassRequest; + handlers["CDUP"] = &Ftp::Server::handleCdupRequest; + } RequestHandler handler = NULL; if (request->method == Http::METHOD_PUT) handler = &Ftp::Server::handleUploadRequest; else { - for (size_t i = 0; i < sizeof(handlers) / sizeof(*handlers); ++i) { - if (cmd.caseCmp(handlers[i].first) == 0) { - handler = handlers[i].second; - break; - } - } + const RequestHandlers::const_iterator hi = handlers.find(cmd.termedBuf()); + if (hi != handlers.end()) + handler = hi->second; } - // TODO: complain about unknown commands - return handler != NULL ? (this->*handler)(cmd, params) : true; + if (!handler) { + debugs(11, 7, "forwarding " << cmd << " as is, no post-processing"); + return true; + } + + return (this->*handler)(cmd, params); } /// Called to parse USER command, which is required to create an HTTP request /// wrapper. Thus, errors are handled with writeEarlyReply() here. bool -Ftp::Server::handleUserRequest(const String &cmd, String ¶ms) +Ftp::Server::handleUserRequest(const SBuf &cmd, SBuf ¶ms) { - if (params.size() == 0) { + if (params.isEmpty()) { writeEarlyReply(501, "Missing username"); return false; } // find the [end of] user name - const String::size_type eou = params.rfind('@'); - if (eou == String::npos || eou + 1 >= params.size()) { + const SBuf::size_type eou = params.rfind('@'); + if (eou == SBuf::npos || eou + 1 >= params.length()) { writeEarlyReply(501, "Missing host"); return false; } - // const String login = params.substr(0, eou); // Determine the intended destination. - host = params.substr(eou + 1, params.size()); + host = params.substr(eou + 1, params.length()); // If we can parse it as raw IPv6 address, then surround with "[]". // Otherwise (domain, IPv4, [bracketed] IPv6, garbage, etc), use as is. - if (host.pos(":")) { - char ipBuf[MAX_IPSTRLEN]; - Ip::Address ipa; - ipa = host.termedBuf(); + if (host.find(':') != SBuf::npos) { + const Ip::Address ipa(host.c_str()); if (!ipa.isAnyAddr()) { + char ipBuf[MAX_IPSTRLEN]; ipa.toHostStr(ipBuf, MAX_IPSTRLEN); host = ipBuf; } } - String oldUri; + // const SBuf login = params.substr(0, eou); + params.chop(0, eou); // leave just the login part for the peer + + SBuf oldUri; if (master.clientReadGreeting) oldUri = uri; master.workingDir = NULL; - calcUri(); + calcUri(NULL); if (!master.clientReadGreeting) { debugs(11, 3, "set URI to " << uri); @@ -1275,8 +1292,6 @@ Ftp::Server::handleUserRequest(const String &cmd, String ¶ms) resetLogin("URI reset"); } - params.cut(eou); - return true; } @@ -1589,18 +1604,18 @@ Ftp::Server::setReply(const int code, const char *msg) /// Whether Squid FTP gateway supports a given feature (e.g., a command). static bool -Ftp::SupportedCommand(const String &name) +Ftp::SupportedCommand(const SBuf &name) { - static std::set BlackList; + static std::set BlackList; if (BlackList.empty()) { /* Add FTP commands that Squid cannot gateway correctly */ // we probably do not support AUTH TLS.* and AUTH SSL, // but let's disclaim all AUTH support to KISS, for now - BlackList.insert("AUTH"); + BlackList.insert(cmdAuth()); } // we claim support for all commands that we do not know about - return BlackList.find(name.termedBuf()) == BlackList.end(); + return BlackList.find(name) == BlackList.end(); } diff --git a/src/servers/FtpServer.h b/src/servers/FtpServer.h index 9550c04517..635321ba53 100644 --- a/src/servers/FtpServer.h +++ b/src/servers/FtpServer.h @@ -32,7 +32,7 @@ class MasterState { public: Ip::Address clientDataAddr; ///< address of our FTP client data connection - String workingDir; + SBuf workingDir; ServerState serverState; ///< what our FTP server is doing bool clientReadGreeting; ///< whether our FTP client read their FTP server greeting @@ -81,9 +81,9 @@ protected: bool createDataConnection(Ip::Address cltAddr); void closeDataConnection(); - void calcUri(const char *file = NULL); + void calcUri(const SBuf *file); void changeState(const Ftp::ServerState newState, const char *reason); - bool handleUserRequest(const String &cmd, String ¶ms); + bool handleUserRequest(const SBuf &cmd, SBuf ¶ms); bool checkDataConnPost() const; void replyDataWritingCheckpoint(); void maybeReadUploadData(); @@ -130,8 +130,8 @@ private: void shovelUploadData(); void resetLogin(const char *reason); - String uri; ///< a URI reconstructed from various FTP message details - String host; ///< intended dest. of a transparently intercepted FTP conn + SBuf uri; ///< a URI reconstructed from various FTP message details + SBuf host; ///< intended dest. of a transparently intercepted FTP conn bool gotEpsvAll; ///< restrict data conn setup commands to just EPSV AsyncCall::Pointer onDataAcceptCall; ///< who to call upon data conn acceptance Comm::ConnectionPointer dataListenConn; ///< data connection listening socket