enum {
BEGIN,
SENT_COMMAND,
+ SENT_FEAT,
SENT_PASV,
SENT_PORT,
SENT_DATA_REQUEST,
void readGreeting();
void sendCommand();
void readReply();
+ void readFeatReply();
void readPasvReply();
void readPortReply();
void readDataReply();
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
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 :
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()
{
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;
typedef bool FtpRequestHandler(ClientSocketContext *context, String &cmd, String ¶ms);
static FtpRequestHandler FtpHandleRequest;
+static FtpRequestHandler FtpHandleFeatRequest;
static FtpRequestHandler FtpHandlePasvRequest;
static FtpRequestHandler FtpHandlePortRequest;
static FtpRequestHandler FtpHandleDataRequest;
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
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) {
!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 =
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
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)
{
static std::pair<const char *, FtpRequestHandler *> 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)
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)
{
repContext->createStoreEntry(http->request->method, flags);
http->storeEntry()->replaceHttpReply(reply);
}
+
+static bool
+FtpSupportedCommand(const String &name)
+{
+ static std::set<std::string> 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();
+}