From: Alex Rousskov Date: Thu, 29 Aug 2013 16:38:39 +0000 (-0600) Subject: Disclaim support for FTP EPRT and EPSV commands. Exclude them from FEAT X-Git-Tag: SQUID_3_5_0_1~117^2~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5f3898e21f64067dc56a8d31ec92fb28cd09032a;p=thirdparty%2Fsquid.git Disclaim support for FTP EPRT and EPSV commands. Exclude them from FEAT responses. Squid still assumes that it can support all other FTP commands. TODO: We probably want to add ftp_support or a similar ACL-driven option to give admin more control over which commands are supported. This is different from access control because we want to filter unsupported commands from FEAT responses as well. --- diff --git a/src/FtpGatewayServer.cc b/src/FtpGatewayServer.cc index 1a055bc7af..d570c61167 100644 --- a/src/FtpGatewayServer.cc +++ b/src/FtpGatewayServer.cc @@ -54,6 +54,7 @@ protected: enum { BEGIN, SENT_COMMAND, + SENT_FEAT, SENT_PASV, SENT_PORT, SENT_DATA_REQUEST, @@ -66,6 +67,7 @@ protected: void readGreeting(); void sendCommand(); void readReply(); + void readFeatReply(); void readPasvReply(); void readPortReply(); void readDataReply(); @@ -84,6 +86,7 @@ CBDATA_CLASS_INIT(ServerStateData); const ServerStateData::SM_FUNC ServerStateData::SM_FUNCS[] = { &ServerStateData::readGreeting, // BEGIN &ServerStateData::readReply, // SENT_COMMAND + &ServerStateData::readFeatReply, // SENT_FEAT &ServerStateData::readPasvReply, // SENT_PASV &ServerStateData::readPortReply, // SENT_PORT &ServerStateData::readDataReply, // SENT_DATA_REQUEST @@ -429,6 +432,7 @@ ServerStateData::sendCommand() writeCommand(mb.content()); state = + clientState() == ConnStateData::FTP_HANDLE_FEAT ? SENT_FEAT : clientState() == ConnStateData::FTP_HANDLE_PASV ? SENT_PASV : clientState() == ConnStateData::FTP_HANDLE_PORT ? SENT_PORT : clientState() == ConnStateData::FTP_HANDLE_DATA_REQUEST ? SENT_DATA_REQUEST : @@ -448,6 +452,17 @@ ServerStateData::readReply() forwardReply(); } +void +ServerStateData::readFeatReply() +{ + assert(clientState() == ConnStateData::FTP_HANDLE_FEAT); + + if (100 <= ctrl.replycode && ctrl.replycode < 200) + return; // ignore preliminary replies + + forwardReply(); +} + void ServerStateData::readPasvReply() { diff --git a/src/client_side.cc b/src/client_side.cc index 0bbde9ee15..c2d6cd52f3 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -241,6 +241,7 @@ static CNCB FtpHandleConnectDone; static void FtpHandleReply(ClientSocketContext *context, HttpReply *reply, StoreIOBuffer data); typedef void FtpReplyHandler(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data); +static FtpReplyHandler FtpHandleFeatReply; static FtpReplyHandler FtpHandlePasvReply; static FtpReplyHandler FtpHandlePortReply; static FtpReplyHandler FtpHandleErrorReply; @@ -261,6 +262,7 @@ static IOCB FtpWroteReplyData; typedef bool FtpRequestHandler(ClientSocketContext *context, String &cmd, String ¶ms); static FtpRequestHandler FtpHandleRequest; +static FtpRequestHandler FtpHandleFeatRequest; static FtpRequestHandler FtpHandlePasvRequest; static FtpRequestHandler FtpHandlePortRequest; static FtpRequestHandler FtpHandleDataRequest; @@ -268,6 +270,8 @@ static FtpRequestHandler FtpHandleUploadRequest; static bool FtpCheckDataConnection(ClientSocketContext *context); static void FtpSetReply(ClientSocketContext *context, const int code, const char *msg); +static bool FtpSupportedCommand(const String &name); + clientStreamNode * ClientSocketContext::getTail() const @@ -5065,9 +5069,6 @@ FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::Pro const String cmd = boc; String params = bop; - *method_p = !cmd.caseCmp("APPE") || !cmd.caseCmp("STOR") || - !cmd.caseCmp("STOU") ? Http::METHOD_PUT : Http::METHOD_GET; - if (connState->ftp.uri.size() == 0) { // the first command must be USER if (cmd.caseCmp("USER") != 0) { @@ -5086,6 +5087,14 @@ FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::Pro !FtpHandleUserRequest(connState, cmd, params)) return NULL; + if (!FtpSupportedCommand(cmd)) { + FtpWriteEarlyReply(connState, 502, "Unknown or unsupported command"); + return NULL; + } + + *method_p = !cmd.caseCmp("APPE") || !cmd.caseCmp("STOR") || + !cmd.caseCmp("STOU") ? Http::METHOD_PUT : Http::METHOD_GET; + assert(connState->ftp.uri.size() > 0); char *uri = xstrdup(connState->ftp.uri.termedBuf()); HttpRequest *const request = @@ -5149,6 +5158,7 @@ FtpHandleReply(ClientSocketContext *context, HttpReply *reply, StoreIOBuffer dat static FtpReplyHandler *handlers[] = { NULL, // FTP_BEGIN NULL, // FTP_CONNECTED + FtpHandleFeatReply, // FTP_HANDLE_FEAT FtpHandlePasvReply, // FTP_HANDLE_PASV FtpHandlePortReply, // FTP_HANDLE_PORT FtpHandleDataReply, // FTP_HANDLE_DATA_REQUEST @@ -5163,6 +5173,47 @@ FtpHandleReply(ClientSocketContext *context, HttpReply *reply, StoreIOBuffer dat FtpWriteForwardedReply(context, reply); } +static void +FtpHandleFeatReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data) +{ + if (context->http->request->errType != ERR_NONE) { + FtpWriteCustomReply(context, 502, "Server does not support FEAT", reply); + return; + } + + HttpReply *filteredReply = reply->clone(); + HttpHeader &filteredHeader = filteredReply->header; + + // Remove all unsupported commands from the response wrapper. + int deletedCount = 0; + HttpHeaderPos pos = HttpHeaderInitPos; + while (const HttpHeaderEntry *e = filteredHeader.getEntry(&pos)) { + if (e->id == HDR_FTP_PRE) { + // assume RFC 2389 FEAT response format, quoted by Squid: + // <"> SP NAME [SP PARAMS] <"> + if (e->value.size() < 4) + continue; + const char *raw = e->value.termedBuf(); + if (raw[0] != '"' && raw[1] != ' ') + continue; + const char *beg = raw + 2; // after quote and space + // command name ends with (SP parameter) or quote + const char *end = beg + strcspn(beg, " \""); + const String cmd = e->value.substr(beg-raw, end-raw); + + if (!FtpSupportedCommand(cmd)) + filteredHeader.delAt(pos, deletedCount); + } + } + + if (deletedCount) { + filteredHeader.refreshMask(); + debugs(33, 5, "deleted " << deletedCount); + } + + FtpWriteForwardedReply(context, filteredReply); +} + static void FtpHandlePasvReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data) { @@ -5543,6 +5594,7 @@ FtpHandleRequest(ClientSocketContext *context, String &cmd, String ¶ms) { static std::pair handlers[] = { std::make_pair("LIST", FtpHandleDataRequest), std::make_pair("NLST", FtpHandleDataRequest), + std::make_pair("FEAT", FtpHandleFeatRequest), std::make_pair("PASV", FtpHandlePasvRequest), std::make_pair("PORT", FtpHandlePortRequest), std::make_pair("RETR", FtpHandleDataRequest) @@ -5601,6 +5653,14 @@ FtpHandleUserRequest(ConnStateData *connState, const String &cmd, String ¶ms return true; } +bool +FtpHandleFeatRequest(ClientSocketContext *context, String &cmd, String ¶ms) +{ + FtpChangeState(context->getConn(), ConnStateData::FTP_HANDLE_FEAT, "FtpHandleFeatRequest"); + + return true; +} + bool FtpHandlePasvRequest(ClientSocketContext *context, String &cmd, String ¶ms) { @@ -5768,3 +5828,17 @@ FtpSetReply(ClientSocketContext *context, const int code, const char *msg) repContext->createStoreEntry(http->request->method, flags); http->storeEntry()->replaceHttpReply(reply); } + +static bool +FtpSupportedCommand(const String &name) +{ + static std::set BlackList; + if (BlackList.empty()) { + // FTP commands that Squid cannot gateway correctly: + BlackList.insert("EPRT"); + BlackList.insert("EPSV"); + } + + // we claim support for all commands that we do not know about + return BlackList.find(name.termedBuf()) == BlackList.end(); +} diff --git a/src/client_side.h b/src/client_side.h index 88a0524007..c771d1428d 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -340,6 +340,7 @@ public: enum FtpState { FTP_BEGIN, FTP_CONNECTED, + FTP_HANDLE_FEAT, FTP_HANDLE_PASV, FTP_HANDLE_PORT, FTP_HANDLE_DATA_REQUEST,