ServerStateData::handlePasvReply()
{
int code = ctrl.replycode;
- int h1, h2, h3, h4;
- int p1, p2;
- int n;
- unsigned short port;
- Ip::Address ipa_remote;
char *buf;
- LOCAL_ARRAY(char, ipaddr, 1024);
debugs(9, 3, HERE);
if (code != 227) {
buf = ctrl.last_reply + strcspn(ctrl.last_reply, "0123456789");
- n = sscanf(buf, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2);
-
- if (n != 6 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ctrl.conn->remote << ": " << ctrl.last_reply);
- return false;
- }
-
- snprintf(ipaddr, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
-
- ipa_remote = ipaddr;
-
- if ( ipa_remote.IsAnyAddr() ) {
+ const char *forceIp = Config.Ftp.sanitycheck ?
+ fd_table[ctrl.conn->fd].ipaddr : NULL;
+ if (!Ftp::ParseIpPort(buf, forceIp, data.addr)) {
debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
ctrl.conn->remote << ": " << ctrl.last_reply);
return false;
}
- port = ((p1 << 8) + p2);
-
- if (0 == port) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ctrl.conn->remote << ": " << ctrl.last_reply);
- return false;
- }
-
- if (Config.Ftp.sanitycheck) {
- if (port < 1024) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ctrl.conn->remote << ": " << ctrl.last_reply);
- return false;
- }
- }
-
- data.addr = Config.Ftp.sanitycheck ? fd_table[ctrl.conn->fd].ipaddr : ipaddr;
- data.addr.SetPort(port);
-
return true;
}
}
}; // namespace Ftp
+
+
+bool
+Ftp::ParseIpPort(const char *buf, const char *forceIp, Ip::Address &addr)
+{
+ int h1, h2, h3, h4;
+ int p1, p2;
+ const int n = sscanf(buf, "%d,%d,%d,%d,%d,%d",
+ &h1, &h2, &h3, &h4, &p1, &p2);
+
+ if (n != 6 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255)
+ return false;
+
+ if (forceIp) {
+ addr = forceIp; // but the above code still validates the IP we got
+ } else {
+ static char ipBuf[1024];
+ snprintf(ipBuf, sizeof(ipBuf), "%d.%d.%d.%d", h1, h2, h3, h4);
+ addr = ipBuf;
+
+ if (addr.IsAnyAddr())
+ return false;
+ }
+
+ const int port = ((p1 << 8) + p2);
+
+ if (port <= 0)
+ return false;
+
+ if (Config.Ftp.sanitycheck && port < 1024)
+ return false;
+
+ addr.SetPort(port);
+ return true;
+}
#include "clientStream.h"
#include "comm.h"
#include "comm/Connection.h"
+#include "comm/ConnOpener.h"
#include "comm/Loops.h"
#include "comm/TcpAcceptor.h"
#include "comm/Write.h"
static void FtpWriteGreeting(ConnStateData *conn);
static ClientSocketContext *FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::ProtocolVersion *http_ver);
static bool FtpHandleUserRequest(ConnStateData *connState, const String &cmd, String ¶ms);
+static CNCB FtpHandleConnectDone;
static void FtpHandleReply(ClientSocketContext *context, HttpReply *reply, StoreIOBuffer data);
typedef void FtpReplyHandler(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data);
static FtpReplyHandler FtpHandlePasvReply;
+static FtpReplyHandler FtpHandlePortReply;
static FtpReplyHandler FtpHandleErrorReply;
static FtpReplyHandler FtpHandleDataReply;
static FtpReplyHandler FtpHandleUploadReply;
assert(http != NULL);
HttpRequest *const request = http->request;
assert(request != NULL);
+ debugs(33, 9, request);
+
HttpHeader &header = request->header;
assert(header.has(HDR_FTP_COMMAND));
String &cmd = header.findEntry(HDR_FTP_COMMAND)->value;
assert(header.has(HDR_FTP_ARGUMENTS));
String ¶ms = header.findEntry(HDR_FTP_ARGUMENTS)->value;
- if (!FtpHandleRequest(context, cmd, params)) {
+ const bool fwd = !http->storeEntry() &&
+ FtpHandleRequest(context, cmd, params);
+
+ if (http->storeEntry() != NULL) {
+ debugs(33, 4, "got an immediate response");
assert(http->storeEntry() != NULL);
clientSetKeepaliveFlag(http);
context->pullData();
- return;
+ } else if (fwd) {
+ debugs(33, 4, "forwarding request to server side");
+ assert(http->storeEntry() == NULL);
+ clientProcessRequest(this, &parser_, context, request->method,
+ request->http_ver);
+ } else {
+ debugs(33, 4, "will resume processing later");
}
+}
- assert(http->storeEntry() == NULL);
- clientProcessRequest(this, &parser_, context, request->method,
- request->http_ver);
+void
+ConnStateData::resumeFtpRequest(ClientSocketContext *const context)
+{
+ debugs(33, 4, "resuming");
+ processFtpRequest(context);
}
static void
NULL, // FTP_BEGIN
NULL, // FTP_CONNECTED
FtpHandlePasvReply, // FTP_HANDLE_PASV
+ FtpHandlePortReply, // FTP_HANDLE_PORT
FtpHandleDataReply, // FTP_HANDLE_DATA_REQUEST
FtpHandleUploadReply, // FTP_HANDLE_DATA_REQUEST
FtpHandleErrorReply // FTP_ERROR
FtpWriteReply(context, mb);
}
+static void
+FtpHandlePortReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data)
+{
+ if (context->http->request->errType != ERR_NONE) {
+ FtpWriteCustomReply(context, 502, "Server does not support PASV (converted from PORT)", reply);
+ return;
+ }
+
+ FtpWriteCustomReply(context, 200, "PORT successfully converted to PASV.");
+
+ // and wait for RETR
+}
+
static void
FtpHandleErrorReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data)
{
return true;
}
+#include "FtpServer.h" /* XXX: For Ftp::ParseIpPort() */
+
bool
FtpHandlePortRequest(ClientSocketContext *context, String &cmd, String ¶ms)
{
- FtpSetReply(context, 502, "Command not supported");
- return false;
+ if (!params.size()) {
+ FtpSetReply(context, 501, "Missing parameter");
+ return false;
+ }
+
+ Ip::Address cltAddr;
+ if (!Ftp::ParseIpPort(params.termedBuf(), NULL, cltAddr)) {
+ FtpSetReply(context, 501, "Invalid parameter");
+ return false;
+ }
+
+ FtpCloseDataConnection(context->getConn());
+ debugs(11, 3, "will actively connect to " << cltAddr);
+
+ Comm::ConnectionPointer conn = new Comm::Connection();
+ conn->remote = cltAddr;
+
+ // TODO: should we use getOutgoingAddress() here instead?
+ if (conn->remote.IsIPv4())
+ conn->local.SetIPv4();
+
+ // RFC 959 requires active FTP connections to originate from port 20
+ // but that would preclude us from supporting concurrent transfers! (XXX?)
+ // conn->flags |= COMM_DOBIND;
+ // conn->local.SetPort(20);
+
+ context->getConn()->ftp.dataConn = conn;
+ context->getConn()->ftp.uploadAvailSize = 0; // XXX: FtpCloseDataConnection should do that
+
+ context->getConn()->ftp.state = ConnStateData::FTP_HANDLE_PORT;
+
+ // convert client PORT command to Squid PASV command because Squid
+ // does not support active FTP transfers on the server side (yet?)
+ ClientHttpRequest *const http = context->http;
+ assert(http != NULL);
+ HttpRequest *const request = http->request;
+ assert(request != NULL);
+ HttpHeader &header = request->header;
+ header.delById(HDR_FTP_COMMAND);
+ header.putStr(HDR_FTP_COMMAND, "PASV");
+ header.delById(HDR_FTP_ARGUMENTS);
+ header.putStr(HDR_FTP_ARGUMENTS, "");
+ return true; // forward our fake PASV request
}
bool
bool
FtpCheckDataConnection(ClientSocketContext *context)
{
- const ConnStateData *const connState = context->getConn();
+ ConnStateData *const connState = context->getConn();
if (Comm::IsConnOpen(connState->ftp.dataConn))
return true;
- if (!Comm::IsConnOpen(connState->ftp.dataListenConn))
- FtpSetReply(context, 425, "Use PASV first");
- else
+ if (Comm::IsConnOpen(connState->ftp.dataListenConn)) {
FtpSetReply(context, 425, "Data connection is not established");
- return false;
+ return false;
+ }
+
+ if (connState->ftp.dataConn->remote.IsAnyAddr()) {
+ // XXX: use client address and default port instead.
+ FtpSetReply(context, 425, "Use PORT first");
+ return false;
+ }
+
+ // active transfer: open a connection from Squid to client
+ AsyncCall::Pointer connector = context->getConn()->ftp.connector =
+ commCbCall(17, 3, "FtpConnectDoneWrapper",
+ CommConnectCbPtrFun(FtpHandleConnectDone, context));
+
+ Comm::ConnOpener *cs = new Comm::ConnOpener(connState->ftp.dataConn,
+ connector,
+ Config.Timeout.connect);
+ AsyncJob::Start(cs);
+ return false; // ConnStateData::processFtpRequest waits FtpHandleConnectDone
+}
+
+void
+FtpHandleConnectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
+{
+ ClientSocketContext *context = static_cast<ClientSocketContext*>(data);
+ context->getConn()->ftp.connector = NULL;
+
+ if (status != COMM_OK) {
+ conn->close();
+ FtpSetReply(context, 425, "Cannot open data connection.");
+ assert(context->http && context->http->storeEntry() != NULL);
+ } else {
+ context->getConn()->ftp.dataConn = conn;
+ context->getConn()->ftp.uploadAvailSize = 0;
+ assert(Comm::IsConnOpen(context->getConn()->ftp.dataConn));
+ }
+ context->getConn()->resumeFtpRequest(context);
}
void