--- /dev/null
+/*
+ * DEBUG: section 09 File Transfer Protocol (FTP)
+ *
+ */
+
+#include "squid.h"
+
+#include "FtpGatewayServer.h"
+#include "FtpServer.h"
+#include "HttpHdrCc.h"
+#include "HttpRequest.h"
+#include "Server.h"
+#include "SquidTime.h"
+#include "Store.h"
+#include "client_side.h"
+#include "wordlist.h"
+
+namespace Ftp {
+
+namespace Gateway {
+
+class ServerStateData: public Ftp::ServerStateData
+{
+public:
+ ServerStateData(FwdState *const fwdState);
+ ~ServerStateData();
+
+ virtual void processReplyBody();
+
+protected:
+ virtual void start();
+
+ ConnStateData::FtpState clientState() const;
+ void clientState(ConnStateData::FtpState s) const;
+ virtual void failed(err_type error = ERR_NONE, int xerrno = 0);
+ virtual void failedErrorMessage(err_type error, int xerrno);
+ virtual void handleControlReply();
+ virtual void handleRequestBodyProducerAborted();
+ virtual bool doneWithServer() const;
+ void forwardReply();
+ void forwardError(err_type error = ERR_NONE, int xerrno = 0);
+ HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int clen = 0);
+ void handleDataRequest();
+ void startDataTransfer();
+
+ typedef void (ServerStateData::*PreliminaryCb)();
+ void forwardPreliminaryReply(const PreliminaryCb cb);
+ void proceedAfterPreliminaryReply();
+ PreliminaryCb thePreliminaryCb;
+
+ enum {
+ BEGIN,
+ SENT_COMMAND,
+ SENT_PASV,
+ SENT_DATA_REQUEST,
+ READING_DATA,
+ DONE
+ };
+ typedef void (ServerStateData::*SM_FUNC)();
+ static const SM_FUNC SM_FUNCS[];
+ void readWelcome();
+ void sendCommand();
+ void readReply();
+ void readPasvReply();
+ void readDataReply();
+ void readTransferDoneReply();
+
+ virtual void dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t err, int xerrno);
+ void scheduleReadControlReply();
+
+ CBDATA_CLASS2(ServerStateData);
+};
+
+CBDATA_CLASS_INIT(ServerStateData);
+
+const ServerStateData::SM_FUNC ServerStateData::SM_FUNCS[] = {
+ &ServerStateData::readWelcome, // BEGIN
+ &ServerStateData::readReply, // SENT_COMMAND
+ &ServerStateData::readPasvReply, // SENT_PASV
+ &ServerStateData::readDataReply, // SENT_DATA_REQUEST
+ &ServerStateData::readTransferDoneReply, // READING_DATA
+ NULL // DONE
+};
+
+ServerStateData::ServerStateData(FwdState *const fwdState):
+ AsyncJob("Ftp::Gateway::ServerStateData"), Ftp::ServerStateData(fwdState)
+{
+}
+
+ServerStateData::~ServerStateData()
+{
+ if (Comm::IsConnOpen(ctrl.conn)) {
+ fwd->unregister(ctrl.conn);
+ ctrl.forget();
+ }
+}
+
+void
+ServerStateData::start()
+{
+ if (clientState() == ConnStateData::FTP_BEGIN)
+ Ftp::ServerStateData::start();
+ else
+ if (clientState() == ConnStateData::FTP_HANDLE_DATA_REQUEST)
+ handleDataRequest();
+ else
+ sendCommand();
+}
+
+ConnStateData::FtpState
+ServerStateData::clientState() const
+{
+ return fwd->request->clientConnectionManager->ftp.state;
+}
+
+void
+ServerStateData::clientState(ConnStateData::FtpState s) const
+{
+ fwd->request->clientConnectionManager->ftp.state = s;
+}
+
+void
+ServerStateData::failed(err_type error, int xerrno)
+{
+ if (!doneWithServer())
+ clientState(ConnStateData::FTP_ERROR);
+
+ Ftp::ServerStateData::failed(error, xerrno);
+}
+
+void
+ServerStateData::failedErrorMessage(err_type error, int xerrno)
+{
+ const Http::StatusCode httpStatus = failedHttpStatus(error);
+ HttpReply *const reply = createHttpReply(httpStatus);
+ entry->replaceHttpReply(reply);
+ EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+ fwd->request->detailError(error, xerrno);
+}
+
+void
+ServerStateData::processReplyBody()
+{
+ debugs(9, 3, HERE << "starting");
+
+ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+ /*
+ * probably was aborted because content length exceeds one
+ * of the maximum size limits.
+ */
+ abortTransaction("entry aborted after calling appendSuccessHeader()");
+ return;
+ }
+
+#if USE_ADAPTATION
+
+ if (adaptationAccessCheckPending) {
+ debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
+ return;
+ }
+
+#endif
+
+ if (const int csize = data.readBuf->contentSize()) {
+ debugs(9, 5, HERE << "writing " << csize << " bytes to the reply");
+ addVirginReplyBody(data.readBuf->content(), csize);
+ data.readBuf->consume(csize);
+ }
+
+ entry->flush();
+
+ maybeReadVirginBody();
+}
+
+void
+ServerStateData::handleControlReply()
+{
+ Ftp::ServerStateData::handleControlReply();
+ if (ctrl.message == NULL)
+ return; // didn't get complete reply yet
+
+ assert(state < DONE);
+ (this->*SM_FUNCS[state])();
+}
+
+void
+ServerStateData::handleRequestBodyProducerAborted()
+{
+ ::ServerStateData::handleRequestBodyProducerAborted();
+
+ abortTransaction("request body producer aborted");
+}
+
+bool
+ServerStateData::doneWithServer() const
+{
+ return state == DONE || Ftp::ServerStateData::doneWithServer();
+}
+
+void
+ServerStateData::forwardReply()
+{
+ assert(entry->isEmpty());
+ EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+
+ HttpReply *const reply = createHttpReply(Http::scOkay);
+
+ setVirginReply(reply);
+ adaptOrFinalizeReply();
+
+ state = DONE;
+ serverComplete();
+}
+
+void
+ServerStateData::forwardPreliminaryReply(const PreliminaryCb cb)
+{
+ debugs(9, 5, HERE << "Forwarding preliminary reply to client");
+
+ assert(thePreliminaryCb == NULL);
+ thePreliminaryCb = cb;
+
+ const HttpReply::Pointer reply = createHttpReply(Http::scContinue);
+
+ // the Sink will use this to call us back after writing 1xx to the client
+ typedef NullaryMemFunT<ServerStateData> CbDialer;
+ const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this,
+ ServerStateData::proceedAfterPreliminaryReply);
+
+ CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData,
+ ConnStateData::sendControlMsg, HttpControlMsg(reply, call));
+}
+
+void
+ServerStateData::proceedAfterPreliminaryReply()
+{
+ debugs(9, 5, HERE << "Proceeding after preliminary reply to client");
+
+ assert(thePreliminaryCb != NULL);
+ const PreliminaryCb cb = thePreliminaryCb;
+ thePreliminaryCb = NULL;
+ (this->*cb)();
+}
+
+void
+ServerStateData::forwardError(err_type error, int xerrno)
+{
+ state = DONE;
+ failed(error, xerrno);
+}
+
+HttpReply *
+ServerStateData::createHttpReply(const Http::StatusCode httpStatus, const int clen)
+{
+ HttpReply *const reply = new HttpReply;
+ reply->sline.set(Http::ProtocolVersion(1, 1), httpStatus);
+ HttpHeader &header = reply->header;
+ header.putTime(HDR_DATE, squid_curtime);
+ {
+ HttpHdrCc cc;
+ cc.Private();
+ header.putCc(&cc);
+ }
+ if (clen >= 0)
+ header.putInt64(HDR_CONTENT_LENGTH, clen);
+ if (ctrl.replycode > 0)
+ header.putInt(HDR_FTP_STATUS, ctrl.replycode);
+ if (ctrl.message) {
+ for (wordlist *W = ctrl.message; W; W = W->next)
+ header.putStr(HDR_FTP_REASON, W->key);
+ } else if (ctrl.last_command)
+ header.putStr(HDR_FTP_REASON, ctrl.last_command);
+
+ reply->hdrCacheInit();
+
+ return reply;
+}
+
+void
+ServerStateData::handleDataRequest()
+{
+ data.addr = fwd->request->clientConnectionManager->ftp.serverDataAddr;
+
+ if (!data.addr.IsSockAddr()) { // should never happen
+ debugs(9, DBG_IMPORTANT, HERE << "Inconsistent FTP server state: "
+ "data.addr=" << data.addr);
+ failed();
+ return;
+ }
+
+ connectDataChannel();
+}
+
+void
+ServerStateData::startDataTransfer()
+{
+ assert(Comm::IsConnOpen(data.conn));
+
+ debugs(9, 3, HERE << "begin data transfer from " << data.conn->remote <<
+ " (" << data.conn->local << ")");
+
+ HttpReply *const reply = createHttpReply(Http::scOkay, -1);
+ EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+ setVirginReply(reply);
+ adaptOrFinalizeReply();
+
+ switchTimeoutToDataChannel();
+ maybeReadVirginBody();
+ state = READING_DATA;
+}
+
+void
+ServerStateData::readWelcome()
+{
+ assert(clientState() == ConnStateData::FTP_BEGIN);
+
+ switch (ctrl.replycode) {
+ case 220:
+ clientState(ConnStateData::FTP_CONNECTED);
+ ctrl.replycode = 120; // change status for forwarded server greeting
+ forwardPreliminaryReply(&ServerStateData::sendCommand);
+ break;
+ case 120:
+ if (NULL != ctrl.message)
+ debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key);
+ forwardPreliminaryReply(&ServerStateData::scheduleReadControlReply);
+ break;
+ default:
+ failed();
+ break;
+ }
+}
+
+void
+ServerStateData::sendCommand()
+{
+ if (!fwd->request->header.has(HDR_FTP_COMMAND)) {
+ abortTransaction("Internal error: FTP gateway request with no command");
+ return;
+ }
+
+ String cmd = fwd->request->header.findEntry(HDR_FTP_COMMAND)->value;
+ const String *const params = fwd->request->header.has(HDR_FTP_ARGUMENTS) ?
+ &fwd->request->header.findEntry(HDR_FTP_ARGUMENTS)->value : NULL;
+
+ if (params != NULL)
+ debugs(9, 5, HERE << "command: " << cmd << ", parameters: " << *params);
+ else
+ debugs(9, 5, HERE << "command: " << cmd << ", no parameters");
+
+ static MemBuf mb;
+ mb.reset();
+ if (params != NULL)
+ mb.Printf("%s %s%s", cmd.termedBuf(), params->termedBuf(), Ftp::crlf);
+ else
+ mb.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
+
+ writeCommand(mb.content());
+
+ state = clientState() == ConnStateData::FTP_HANDLE_PASV ? SENT_PASV :
+ clientState() == ConnStateData::FTP_HANDLE_DATA_REQUEST ? SENT_DATA_REQUEST :
+ SENT_COMMAND;
+}
+
+void
+ServerStateData::readReply()
+{
+ assert(clientState() == ConnStateData::FTP_CONNECTED);
+
+ if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ forwardPreliminaryReply(&ServerStateData::scheduleReadControlReply);
+ else
+ forwardReply();
+}
+
+void
+ServerStateData::readPasvReply()
+{
+ assert(clientState() == ConnStateData::FTP_HANDLE_PASV);
+
+ if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ return; // ignore preliminary replies
+
+ if (handlePasvReply()) {
+ fwd->request->clientConnectionManager->ftp.serverDataAddr = data.addr;
+ forwardReply();
+ } else
+ forwardError();
+}
+
+void
+ServerStateData::readDataReply()
+{
+ assert(clientState() == ConnStateData::FTP_HANDLE_DATA_REQUEST);
+
+ if (ctrl.replycode == 150)
+ forwardPreliminaryReply(&ServerStateData::startDataTransfer);
+ else
+ forwardReply();
+}
+
+void
+ServerStateData::readTransferDoneReply()
+{
+ debugs(9, 3, HERE);
+
+ if (ctrl.replycode != 226 && ctrl.replycode != 250) {
+ debugs(9, DBG_IMPORTANT, HERE << "Got code " << ctrl.replycode <<
+ " after reading data");
+ }
+
+ state = DONE;
+ serverComplete();
+}
+
+void
+ServerStateData::dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t err, int xerrno)
+{
+ debugs(9, 3, HERE);
+ data.opener = NULL;
+
+ if (err != COMM_OK) {
+ debugs(9, 2, HERE << "Failed to connect FTP server data channel.");
+ forwardError(ERR_CONNECT_FAIL, xerrno);
+ return;
+ }
+
+ debugs(9, 2, HERE << "Connected FTP server data channel: " << conn);
+
+ data.opened(conn, dataCloser());
+
+ sendCommand();
+}
+
+void
+ServerStateData::scheduleReadControlReply()
+{
+ Ftp::ServerStateData::scheduleReadControlReply(0);
+}
+
+}; // namespace Gateway
+
+}; // namespace Ftp
+
+void
+ftpGatewayServerStart(FwdState *const fwdState)
+{
+ AsyncJob::Start(new Ftp::Gateway::ServerStateData(fwdState));
+}
--- /dev/null
+/*
+ * DEBUG: section 09 File Transfer Protocol (FTP)
+ *
+ */
+
+#ifndef SQUID_FTP_GATEWAY_SERVER_H
+#define SQUID_FTP_GATEWAY_SERVER_H
+
+class FwdState;
+
+void ftpGatewayServerStart(FwdState *const);
+
+#endif /* SQUID_FTP_GATEWAY_SERVER_H */
--- /dev/null
+/*
+ * DEBUG: section 09 File Transfer Protocol (FTP)
+ *
+ */
+
+#include "squid.h"
+
+#include "FtpServer.h"
+#include "Mem.h"
+#include "SquidConfig.h"
+#include "StatCounters.h"
+#include "client_side.h"
+#include "comm/ConnOpener.h"
+#include "comm/Write.h"
+#include "errorpage.h"
+#include "fd.h"
+#include "tools.h"
+#include "wordlist.h"
+
+namespace Ftp {
+
+const char *const crlf = "\r\n";
+
+/// \ingroup ServerProtocolFTPInternal
+static char *
+escapeIAC(const char *buf)
+{
+ int n;
+ char *ret;
+ unsigned const char *p;
+ unsigned char *r;
+
+ for (p = (unsigned const char *)buf, n = 1; *p; ++n, ++p)
+ if (*p == 255)
+ ++n;
+
+ ret = (char *)xmalloc(n);
+
+ for (p = (unsigned const char *)buf, r=(unsigned char *)ret; *p; ++p) {
+ *r = *p;
+ ++r;
+
+ if (*p == 255) {
+ *r = 255;
+ ++r;
+ }
+ }
+
+ *r = '\0';
+ ++r;
+ assert((r - (unsigned char *)ret) == n );
+ return ret;
+}
+
+/// configures the channel with a descriptor and registers a close handler
+void
+ServerChannel::opened(const Comm::ConnectionPointer &newConn,
+ const AsyncCall::Pointer &aCloser)
+{
+ assert(!Comm::IsConnOpen(conn));
+ assert(closer == NULL);
+
+ assert(Comm::IsConnOpen(newConn));
+ assert(aCloser != NULL);
+
+ conn = newConn;
+ closer = aCloser;
+ comm_add_close_handler(conn->fd, closer);
+}
+
+/// planned close: removes the close handler and calls comm_close
+void
+ServerChannel::close()
+{
+ // channels with active listeners will be closed when the listener handler dies.
+ if (Comm::IsConnOpen(conn)) {
+ comm_remove_close_handler(conn->fd, closer);
+ conn->close(); // we do not expect to be called back
+ }
+ clear();
+}
+
+void
+ServerChannel::forget()
+{
+ if (Comm::IsConnOpen(conn))
+ comm_remove_close_handler(conn->fd, closer);
+ closer = NULL;
+}
+
+void
+ServerChannel::clear()
+{
+ conn = NULL;
+ closer = NULL;
+}
+
+ServerStateData::ServerStateData(FwdState *fwdState):
+ AsyncJob("Ftp::ServerStateData"), ::ServerStateData(fwdState)
+{
+ ++statCounter.server.all.requests;
+ ++statCounter.server.ftp.requests;
+
+ ctrl.last_command = xstrdup("Connect to server");
+ ctrl.buf = static_cast<char *>(memAllocBuf(4096, &ctrl.size));
+ ctrl.offset = 0;
+
+ typedef CommCbMemFunT<ServerStateData, CommCloseCbParams> Dialer;
+ const AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this,
+ ServerStateData::ctrlClosed);
+ ctrl.opened(fwdState->serverConnection(), closer);
+}
+
+ServerStateData::~ServerStateData()
+{
+ if (data.opener != NULL) {
+ data.opener->cancel("Ftp::ServerStateData destructed");
+ data.opener = NULL;
+ }
+ data.close();
+
+ if (ctrl.buf) {
+ memFreeBuf(ctrl.size, ctrl.buf);
+ ctrl.buf = NULL;
+ }
+ if (ctrl.message)
+ wordlistDestroy(&ctrl.message);
+ safe_free(ctrl.last_command);
+ safe_free(ctrl.last_reply);
+
+ if (data.readBuf) {
+ if (!data.readBuf->isNull())
+ data.readBuf->clean();
+
+ delete data.readBuf;
+ }
+
+ safe_free(old_request);
+
+ safe_free(old_reply);
+
+ fwd = NULL; // refcounted
+}
+
+void
+ServerStateData::start()
+{
+ scheduleReadControlReply(0);
+}
+
+/**
+ * Close the FTP server connection(s). Used by serverComplete().
+ */
+void
+ServerStateData::closeServer()
+{
+ if (Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9,3, HERE << "closing FTP server FD " << ctrl.conn->fd << ", this " << this);
+ fwd->unregister(ctrl.conn);
+ ctrl.close();
+ }
+
+ if (Comm::IsConnOpen(data.conn)) {
+ debugs(9,3, HERE << "closing FTP data FD " << data.conn->fd << ", this " << this);
+ data.close();
+ }
+
+ debugs(9,3, HERE << "FTP ctrl and data connections closed. this " << this);
+}
+
+/**
+ * Did we close all FTP server connection(s)?
+ *
+ \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
+ \retval false Either control channel or data is still active.
+ */
+bool
+ServerStateData::doneWithServer() const
+{
+ return !Comm::IsConnOpen(ctrl.conn) && !Comm::IsConnOpen(data.conn);
+}
+
+void
+ServerStateData::failed(err_type error, int xerrno)
+{
+ debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
+ if (entry->isEmpty())
+ failedErrorMessage(error, xerrno);
+
+ serverComplete();
+}
+
+void
+ServerStateData::failedErrorMessage(err_type error, int xerrno)
+{
+ const char *command, *reply;
+ const Http::StatusCode httpStatus = failedHttpStatus(error);
+ ErrorState *const ftperr = new ErrorState(error, httpStatus, fwd->request);
+ ftperr->xerrno = xerrno;
+
+ ftperr->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)
+ ftperr->ftp.request = xstrdup(command);
+
+ if (reply)
+ ftperr->ftp.reply = xstrdup(reply);
+
+ entry->replaceHttpReply( ftperr->BuildHttpReply() );
+ delete ftperr;
+}
+
+Http::StatusCode
+ServerStateData::failedHttpStatus(err_type &error)
+{
+ if (error == ERR_NONE)
+ error = ERR_FTP_FAILURE;
+ return error == ERR_READ_TIMEOUT ? Http::scGateway_Timeout :
+ Http::scBadGateway;
+}
+
+/**
+ * DPW 2007-04-23
+ * Looks like there are no longer anymore callers that set
+ * buffered_ok=1. Perhaps it can be removed at some point.
+ */
+void
+ServerStateData::scheduleReadControlReply(int buffered_ok)
+{
+ debugs(9, 3, HERE << ctrl.conn);
+
+ if (buffered_ok && ctrl.offset > 0) {
+ /* We've already read some reply data */
+ handleControlReply();
+ } else {
+ /*
+ * Cancel the timeout on the Data socket (if any) and
+ * establish one on the control socket.
+ */
+ if (Comm::IsConnOpen(data.conn)) {
+ commUnsetConnTimeout(data.conn);
+ }
+
+ typedef CommCbMemFunT<ServerStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, ServerStateData::timeout);
+ commSetConnTimeout(ctrl.conn, Config.Timeout.read, timeoutCall);
+
+ typedef CommCbMemFunT<ServerStateData, CommIoCbParams> Dialer;
+ AsyncCall::Pointer reader = JobCallback(9, 5, Dialer, this, ServerStateData::readControlReply);
+ comm_read(ctrl.conn, ctrl.buf + ctrl.offset, ctrl.size - ctrl.offset, reader);
+ }
+}
+
+void
+ServerStateData::readControlReply(const CommIoCbParams &io)
+{
+ debugs(9, 3, HERE << "FD " << io.fd << ", Read " << io.size << " bytes");
+
+ if (io.size > 0) {
+ kb_incr(&(statCounter.server.all.kbytes_in), io.size);
+ kb_incr(&(statCounter.server.ftp.kbytes_in), io.size);
+ }
+
+ if (io.flag == COMM_ERR_CLOSING)
+ return;
+
+ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+ abortTransaction("entry aborted during control reply read");
+ return;
+ }
+
+ assert(ctrl.offset < ctrl.size);
+
+ if (io.flag == COMM_OK && io.size > 0) {
+ fd_bytes(io.fd, io.size, FD_READ);
+ }
+
+ if (io.flag != COMM_OK) {
+ debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
+ "ftpReadControlReply: read error: " << xstrerr(io.xerrno));
+
+ if (ignoreErrno(io.xerrno)) {
+ scheduleReadControlReply(0);
+ } else {
+ failed(ERR_READ_ERROR, io.xerrno);
+ /* failed closes ctrl.conn and frees ftpState */
+ }
+ return;
+ }
+
+ if (io.size == 0) {
+ if (entry->store_status == STORE_PENDING) {
+ failed(ERR_FTP_FAILURE, 0);
+ /* failed closes ctrl.conn and frees ftpState */
+ return;
+ }
+
+ /* XXX this may end up having to be serverComplete() .. */
+ abortTransaction("zero control reply read");
+ return;
+ }
+
+ unsigned int len =io.size + ctrl.offset;
+ ctrl.offset = len;
+ assert(len <= ctrl.size);
+ handleControlReply();
+}
+
+void
+ServerStateData::handleControlReply()
+{
+ debugs(9, 3, HERE);
+
+ size_t bytes_used = 0;
+ wordlistDestroy(&ctrl.message);
+ ctrl.message = parseControlReply(ctrl.buf, ctrl.offset, &ctrl.replycode,
+ &bytes_used);
+
+ if (ctrl.message == NULL) {
+ /* didn't get complete reply yet */
+
+ if (ctrl.offset == ctrl.size) {
+ ctrl.buf = (char *)memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size);
+ }
+
+ scheduleReadControlReply(0);
+ return;
+ } else if (ctrl.offset == bytes_used) {
+ /* used it all up */
+ ctrl.offset = 0;
+ } else {
+ /* Got some data past the complete reply */
+ assert(bytes_used < ctrl.offset);
+ ctrl.offset -= bytes_used;
+ memmove(ctrl.buf, ctrl.buf + bytes_used, ctrl.offset);
+ }
+
+ /* Move the last line of the reply message to ctrl.last_reply */
+ const wordlist *W;
+ for (W = ctrl.message; W && W->next; W = W->next);
+ if (W) {
+ safe_free(ctrl.last_reply);
+ ctrl.last_reply = xstrdup(W->key);
+ }
+
+ debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
+}
+
+bool
+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) {
+ debugs(9, 2, "PASV not supported by remote end");
+ return false;
+ }
+
+ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
+ /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
+ debugs(9, 5, HERE << "scanning: " << ctrl.last_reply);
+
+ 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() ) {
+ 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;
+}
+
+void
+ServerStateData::connectDataChannel()
+{
+ safe_free(ctrl.last_command);
+
+ safe_free(ctrl.last_reply);
+
+ ctrl.last_command = xstrdup("Connect to server data port");
+
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ conn->local = ctrl.conn->local;
+ conn->local.SetPort(0);
+ conn->remote = data.addr;
+
+ debugs(9, 3, HERE << "connecting to " << conn->remote);
+
+ data.opener = commCbCall(9,3, "Ftp::ServerStateData::dataChannelConnected",
+ CommConnectCbPtrFun(ServerStateData::dataChannelConnected, this));
+ Comm::ConnOpener *cs = new Comm::ConnOpener(conn, data.opener, Config.Timeout.connect);
+ char buf[MAX_IPSTRLEN];
+ data.addr.ToHostname(buf, MAX_IPSTRLEN);
+ cs->setHost(buf);
+ AsyncJob::Start(cs);
+}
+
+void
+ServerStateData::dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
+{
+ ServerStateData *ftpState = static_cast<ServerStateData *>(data);
+ ftpState->dataChannelConnected(conn, status, xerrno);
+}
+
+/// creates a data channel Comm close callback
+AsyncCall::Pointer
+ServerStateData::dataCloser()
+{
+ typedef CommCbMemFunT<ServerStateData, CommCloseCbParams> Dialer;
+ return JobCallback(9, 5, Dialer, this, ServerStateData::dataClosed);
+}
+
+/// handler called by Comm when FTP data channel is closed unexpectedly
+void
+ServerStateData::dataClosed(const CommCloseCbParams &io)
+{
+ debugs(9, 4, HERE);
+ if (data.listenConn != NULL) {
+ data.listenConn->close();
+ data.listenConn = NULL;
+ // NP clear() does the: data.fd = -1;
+ }
+ data.clear();
+}
+
+void
+ServerStateData::writeCommand(const char *buf)
+{
+ char *ebuf;
+ /* trace FTP protocol communications at level 2 */
+ debugs(9, 2, "ftp<< " << buf);
+
+ if (Config.Ftp.telnet)
+ ebuf = escapeIAC(buf);
+ else
+ ebuf = xstrdup(buf);
+
+ safe_free(ctrl.last_command);
+
+ safe_free(ctrl.last_reply);
+
+ ctrl.last_command = ebuf;
+
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 2, HERE << "cannot send to closing ctrl " << ctrl.conn);
+ // TODO: assert(ctrl.closer != NULL);
+ return;
+ }
+
+ typedef CommCbMemFunT<ServerStateData, CommIoCbParams> Dialer;
+ AsyncCall::Pointer call = JobCallback(9, 5, Dialer, this,
+ ServerStateData::writeCommandCallback);
+ Comm::Write(ctrl.conn, ctrl.last_command, strlen(ctrl.last_command), call, NULL);
+
+ scheduleReadControlReply(0);
+}
+
+void
+ServerStateData::writeCommandCallback(const CommIoCbParams &io)
+{
+
+ debugs(9, 5, HERE << "wrote " << io.size << " bytes");
+
+ if (io.size > 0) {
+ fd_bytes(io.fd, io.size, FD_WRITE);
+ kb_incr(&(statCounter.server.all.kbytes_out), io.size);
+ kb_incr(&(statCounter.server.ftp.kbytes_out), io.size);
+ }
+
+ if (io.flag == COMM_ERR_CLOSING)
+ return;
+
+ if (io.flag) {
+ debugs(9, DBG_IMPORTANT, "ftpWriteCommandCallback: " << io.conn << ": " << xstrerr(io.xerrno));
+ failed(ERR_WRITE_ERROR, io.xerrno);
+ /* failed closes ctrl.conn and frees ftpState */
+ return;
+ }
+}
+
+/// handler called by Comm when FTP control channel is closed unexpectedly
+void
+ServerStateData::ctrlClosed(const CommCloseCbParams &io)
+{
+ debugs(9, 4, HERE);
+ ctrl.clear();
+ mustStop("Ftp::ServerStateData::ctrlClosed");
+}
+
+void
+ServerStateData::timeout(const CommTimeoutCbParams &io)
+{
+ debugs(9, 4, HERE << io.conn << ": '" << entry->url() << "'" );
+
+ if (abortOnBadEntry("entry went bad while waiting for a timeout"))
+ return;
+
+ failed(ERR_READ_TIMEOUT, 0);
+ /* failed() closes ctrl.conn and frees ftpState */
+}
+
+const Comm::ConnectionPointer &
+ServerStateData::dataConnection() const
+{
+ return data.conn;
+}
+
+void
+ServerStateData::maybeReadVirginBody()
+{
+ // too late to read
+ if (!Comm::IsConnOpen(data.conn) || fd_table[data.conn->fd].closing())
+ return;
+
+ if (data.read_pending)
+ return;
+
+ if (data.readBuf == NULL) {
+ data.readBuf = new MemBuf;
+ data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF);
+ }
+ const int read_sz = replyBodySpace(*data.readBuf, 0);
+
+ debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
+
+ if (read_sz < 2) // see http.cc
+ return;
+
+ data.read_pending = true;
+
+ typedef CommCbMemFunT<ServerStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
+ TimeoutDialer, this, ServerStateData::timeout);
+ commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
+
+ debugs(9,5,HERE << "queueing read on FD " << data.conn->fd);
+
+ typedef CommCbMemFunT<ServerStateData, CommIoCbParams> Dialer;
+ entry->delayAwareRead(data.conn, data.readBuf->space(), read_sz,
+ JobCallback(9, 5, Dialer, this, ServerStateData::dataRead));
+}
+
+void
+ServerStateData::dataRead(const CommIoCbParams &io)
+{
+ int j;
+ int bin;
+
+ data.read_pending = false;
+
+ debugs(9, 3, HERE << "FD " << io.fd << " Read " << io.size << " bytes");
+
+ if (io.size > 0) {
+ kb_incr(&(statCounter.server.all.kbytes_in), io.size);
+ kb_incr(&(statCounter.server.ftp.kbytes_in), io.size);
+ }
+
+ if (io.flag == COMM_ERR_CLOSING)
+ return;
+
+ assert(io.fd == data.conn->fd);
+
+ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+ abortTransaction("entry aborted during dataRead");
+ return;
+ }
+
+ if (io.flag == COMM_OK && io.size > 0) {
+ debugs(9, 5, HERE << "appended " << io.size << " bytes to readBuf");
+ data.readBuf->appended(io.size);
+#if USE_DELAY_POOLS
+ DelayId delayId = entry->mem_obj->mostBytesAllowed();
+ delayId.bytesIn(io.size);
+#endif
+ ++ IOStats.Ftp.reads;
+
+ for (j = io.size - 1, bin = 0; j; ++bin)
+ j >>= 1;
+
+ ++ IOStats.Ftp.read_hist[bin];
+ }
+
+ if (io.flag != COMM_OK) {
+ debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
+ HERE << "read error: " << xstrerr(io.xerrno));
+
+ if (ignoreErrno(io.xerrno)) {
+ typedef CommCbMemFunT<ServerStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer timeoutCall =
+ JobCallback(9, 5, TimeoutDialer, this,
+ ServerStateData::timeout);
+ commSetConnTimeout(io.conn, Config.Timeout.read, timeoutCall);
+
+ maybeReadVirginBody();
+ } else {
+ failed(ERR_READ_ERROR, 0);
+ /* failed closes ctrl.conn and frees ftpState */
+ return;
+ }
+ } else if (io.size == 0) {
+ debugs(9,3, HERE << "Calling dataComplete() because io.size == 0");
+ /*
+ * DPW 2007-04-23
+ * Dangerous curves ahead. This call to dataComplete was
+ * calling scheduleReadControlReply, handleControlReply,
+ * and then ftpReadTransferDone. If ftpReadTransferDone
+ * gets unexpected status code, it closes down the control
+ * socket and our FtpStateData object gets destroyed. As
+ * a workaround we no longer set the 'buffered_ok' flag in
+ * the scheduleReadControlReply call.
+ */
+ dataComplete();
+ }
+
+ processReplyBody();
+}
+
+void
+ServerStateData::dataComplete()
+{
+ debugs(9, 3,HERE);
+
+ /* Connection closed; transfer done. */
+
+ /// Close data channel, if any, to conserve resources while we wait.
+ data.close();
+
+ /* expect the "transfer complete" message on the control socket */
+ /*
+ * DPW 2007-04-23
+ * Previously, this was the only place where we set the
+ * 'buffered_ok' flag when calling scheduleReadControlReply().
+ * It caused some problems if the FTP server returns an unexpected
+ * status code after the data command. FtpStateData was being
+ * deleted in the middle of dataRead().
+ */
+ /* AYJ: 2011-01-13: Bug 2581.
+ * 226 status is possibly waiting in the ctrl buffer.
+ * The connection will hang if we DONT send buffered_ok.
+ * This happens on all transfers which can be completly sent by the
+ * server before the 150 started status message is read in by Squid.
+ * ie all transfers of about one packet hang.
+ */
+ scheduleReadControlReply(1);
+}
+
+/**
+ * Quickly abort the transaction
+ *
+ \todo destruction should be sufficient as the destructor should cleanup,
+ * including canceling close handlers
+ */
+void
+ServerStateData::abortTransaction(const char *reason)
+{
+ debugs(9, 3, HERE << "aborting transaction for " << reason <<
+ "; FD " << (ctrl.conn!=NULL?ctrl.conn->fd:-1) << ", Data FD " << (data.conn!=NULL?data.conn->fd:-1) << ", this " << this);
+ if (Comm::IsConnOpen(ctrl.conn)) {
+ ctrl.conn->close();
+ return;
+ }
+
+ fwd->handleUnregisteredServerEnd();
+ mustStop("ServerStateData::abortTransaction");
+}
+
+/**
+ * Cancel the timeout on the Control socket and establish one
+ * on the data socket
+ */
+void
+ServerStateData::switchTimeoutToDataChannel()
+{
+ commUnsetConnTimeout(ctrl.conn);
+
+ typedef CommCbMemFunT<ServerStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this,
+ ServerStateData::timeout);
+ commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
+}
+
+void
+ServerStateData::sentRequestBody(const CommIoCbParams &io)
+{
+ if (io.size > 0)
+ kb_incr(&(statCounter.server.ftp.kbytes_out), io.size);
+ ::ServerStateData::sentRequestBody(io);
+}
+
+/**
+ * called after we wrote the last byte of the request body
+ */
+void
+ServerStateData::doneSendingRequestBody()
+{
+ ::ServerStateData::doneSendingRequestBody();
+ debugs(9,3, HERE);
+ dataComplete();
+ /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
+ * if transfer type is 'stream' call dataComplete()
+ * otherwise leave open. (reschedule control channel read?)
+ */
+}
+
+wordlist *
+ServerStateData::parseControlReply(char *buf, size_t len, int *codep, size_t *used)
+{
+ char *s;
+ char *sbuf;
+ char *end;
+ int usable;
+ int complete = 0;
+ wordlist *head = NULL;
+ wordlist *list;
+ wordlist **tail = &head;
+ size_t offset;
+ size_t linelen;
+ int code = -1;
+ debugs(9, 3, HERE);
+ /*
+ * We need a NULL-terminated buffer for scanning, ick
+ */
+ sbuf = (char *)xmalloc(len + 1);
+ xstrncpy(sbuf, buf, len + 1);
+ end = sbuf + len - 1;
+
+ while (*end != '\r' && *end != '\n' && end > sbuf)
+ --end;
+
+ usable = end - sbuf;
+
+ debugs(9, 3, HERE << "usable = " << usable);
+
+ if (usable == 0) {
+ debugs(9, 3, HERE << "didn't find end of line");
+ safe_free(sbuf);
+ return NULL;
+ }
+
+ debugs(9, 3, HERE << len << " bytes to play with");
+ ++end;
+ s = sbuf;
+ s += strspn(s, crlf);
+
+ for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
+ if (complete)
+ break;
+
+ debugs(9, 5, HERE << "s = {" << s << "}");
+
+ linelen = strcspn(s, crlf) + 1;
+
+ if (linelen < 2)
+ break;
+
+ if (linelen > 3)
+ complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
+
+ if (complete)
+ code = atoi(s);
+
+ offset = 0;
+
+ if (linelen > 3)
+ if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
+ offset = 4;
+
+ list = new wordlist();
+
+ list->key = (char *)xmalloc(linelen - offset);
+
+ xstrncpy(list->key, s + offset, linelen - offset);
+
+ /* trace the FTP communication chat at level 2 */
+ debugs(9, 2, "ftp>> " << code << " " << list->key);
+
+ *tail = list;
+
+ tail = &list->next;
+ }
+
+ *used = (size_t) (s - sbuf);
+ safe_free(sbuf);
+
+ if (!complete)
+ wordlistDestroy(&head);
+
+ if (codep)
+ *codep = code;
+
+ return head;
+}
+
+}; // namespace Ftp
--- /dev/null
+/*
+ * DEBUG: section 09 File Transfer Protocol (FTP)
+ *
+ */
+
+#ifndef SQUID_FTP_SERVER_H
+#define SQUID_FTP_SERVER_H
+
+#include "Server.h"
+
+namespace Ftp {
+
+extern const char *const crlf;
+
+/// common code for FTP server control and data channels
+/// does not own the channel descriptor, which is managed by FtpStateData
+class ServerChannel
+{
+public:
+ /// called after the socket is opened, sets up close handler
+ void opened(const Comm::ConnectionPointer &conn, const AsyncCall::Pointer &aCloser);
+
+ /** Handles all operations needed to properly close the active channel FD.
+ * clearing the close handler, clearing the listen socket properly, and calling comm_close
+ */
+ void close();
+
+ void forget(); /// remove the close handler, leave connection open
+
+ void clear(); ///< just drops conn and close handler. does not close active connections.
+
+ Comm::ConnectionPointer conn; ///< channel descriptor
+
+ /** A temporary handle to the connection being listened on.
+ * Closing this will also close the waiting Data channel acceptor.
+ * If a data connection has already been accepted but is still waiting in the event queue
+ * the callback will still happen and needs to be handled (usually dropped).
+ */
+ Comm::ConnectionPointer listenConn;
+
+ AsyncCall::Pointer opener; ///< Comm opener handler callback.
+private:
+ AsyncCall::Pointer closer; ///< Comm close handler callback
+};
+
+/// Base class for FTP over HTTP and FTP Gateway server state.
+class ServerStateData: public ::ServerStateData
+{
+public:
+ ServerStateData(FwdState *fwdState);
+ virtual ~ServerStateData();
+
+ virtual void failed(err_type error = ERR_NONE, int xerrno = 0);
+ virtual void timeout(const CommTimeoutCbParams &io);
+ virtual const Comm::ConnectionPointer & dataConnection() const;
+ virtual void abortTransaction(const char *reason);
+ void writeCommand(const char *buf);
+ bool handlePasvReply();
+ void connectDataChannel();
+ virtual void maybeReadVirginBody();
+ void switchTimeoutToDataChannel();
+
+ // \todo: optimize ctrl and data structs member order, to minimize size
+ /// FTP control channel info; the channel is opened once per transaction
+ struct CtrlChannel: public ServerChannel {
+ char *buf;
+ size_t size;
+ size_t offset;
+ wordlist *message;
+ char *last_command;
+ char *last_reply;
+ int replycode;
+ } ctrl;
+
+ /// FTP data channel info; the channel may be opened/closed a few times
+ struct DataChannel: public ServerChannel {
+ MemBuf *readBuf;
+ Ip::Address addr;
+ bool read_pending;
+ } data;
+
+ int state;
+ char *old_request;
+ char *old_reply;
+
+protected:
+ virtual void start();
+
+ virtual void closeServer();
+ virtual bool doneWithServer() const;
+ virtual void failedErrorMessage(err_type error, int xerrno);
+ virtual Http::StatusCode failedHttpStatus(err_type &error);
+ void ctrlClosed(const CommCloseCbParams &io);
+ void scheduleReadControlReply(int buffered_ok);
+ void readControlReply(const CommIoCbParams &io);
+ virtual void handleControlReply();
+ void writeCommandCallback(const CommIoCbParams &io);
+ static CNCB dataChannelConnected;
+ virtual void dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno) = 0;
+ void dataRead(const CommIoCbParams &io);
+ void dataComplete();
+ AsyncCall::Pointer dataCloser();
+ virtual void dataClosed(const CommCloseCbParams &io);
+
+ // sending of the request body to the server
+ virtual void sentRequestBody(const CommIoCbParams &io);
+ virtual void doneSendingRequestBody();
+
+private:
+ static wordlist *parseControlReply(char *buf, size_t len, int *codep, size_t *used);
+
+ CBDATA_CLASS2(ServerStateData);
+};
+
+}; // namespace Ftp
+
+#endif /* SQUID_FTP_SERVER_H */
//manipulation for Cache-Control: private header
bool hasPrivate() const {return isSet(CC_PRIVATE);}
const String &Private() const {return private_;}
- void Private(String &v) {
+ void Private(const String &v = "") {
setMask(CC_PRIVATE,true);
// uses append for multi-line headers
if (private_.defined())
{"Surrogate-Capability", HDR_SURROGATE_CAPABILITY, ftStr},
{"Surrogate-Control", HDR_SURROGATE_CONTROL, ftPSc},
{"Front-End-Https", HDR_FRONT_END_HTTPS, ftStr},
+ {"FTP-Command", HDR_FTP_COMMAND, ftStr},
+ {"FTP-Arguments", HDR_FTP_ARGUMENTS, ftStr},
+ {"FTP-Status", HDR_FTP_STATUS, ftInt},
+ {"FTP-Reason", HDR_FTP_REASON, ftStr},
{"Other:", HDR_OTHER, ftStr} /* ':' will not allow matches */
};
HDR_SURROGATE_CAPABILITY, /**< Edge Side Includes (ESI) header */
HDR_SURROGATE_CONTROL, /**< Edge Side Includes (ESI) header */
HDR_FRONT_END_HTTPS, /**< MS Exchange custom header we may have to add */
+ HDR_FTP_COMMAND, /**< Internal header for FTP command */
+ HDR_FTP_ARGUMENTS, /**< Internal header for FTP command arguments */
+ HDR_FTP_STATUS, /**< Internal header for FTP reply status */
+ HDR_FTP_REASON, /**< Internal header for FTP reply reason */
HDR_OTHER, /**< internal tag value for "unknown" headers */
HDR_ENUM_END
} http_hdr_type;
/// Remove Warnings with warn-date different from Date value
void removeStaleWarnings();
+ virtual void hdrCacheInit();
+
private:
/** initialize */
void init();
virtual void packFirstLineInto(Packer * p, bool) const { sline.packInto(p); }
virtual bool parseFirstLine(const char *start, const char *end);
-
- virtual void hdrCacheInit();
};
MEMPROXY_CLASS_INLINE(HttpReply);
fqdncache.cc \
ftp.h \
ftp.cc \
+ FtpServer.h \
+ FtpServer.cc \
+ FtpGatewayServer.h \
+ FtpGatewayServer.cc \
Generic.h \
globals.h \
gopher.h \
#if USE_SSL
AnyP::PortCfg *https;
#endif
+ AnyP::PortCfg *ftp;
} Sockaddr;
#if SQUID_SNMP
protocol = "http";
else if (strcmp(optionName, "https_port") == 0)
protocol = "https";
+ else if (strcmp(optionName, "ftp_port") == 0)
+ protocol = "ftp";
if (!protocol) {
self_destruct();
return;
debugs(3, DBG_CRITICAL, "FATAL: tproxy/intercept on https_port requires ssl-bump which is missing.");
self_destruct();
}
+ } else if (strcmp(protocol, "ftp") == 0) {
+ /* ftp_port does not support ssl-bump */
+ if (s->flags.tunnelSslBumping) {
+ debugs(3, DBG_CRITICAL, "FATAL: ssl-bump is not supported for ftp_port.");
+ self_destruct();
+ }
}
#endif
See http_port for a list of available options.
DOC_END
+NAME: ftp_port
+TYPE: PortCfg
+DEFAULT: none
+LOC: Config.Sockaddr.ftp
+DOC_START
+ Usage: [ip:]port
+
NAME: tcp_outgoing_tos tcp_outgoing_ds tcp_outgoing_dscp
TYPE: acl_tos
DEFAULT: none
#if USE_SSL
static IOACB httpsAccept;
#endif
+static IOACB ftpAccept;
static CTCB clientLifetimeTimeout;
static ClientSocketContext *parseHttpRequestAbort(ConnStateData * conn, const char *uri);
static ClientSocketContext *parseHttpRequest(ConnStateData *, HttpParser *, HttpRequestMethod *, Http::ProtocolVersion *);
static ConnStateData *connStateCreate(const Comm::ConnectionPointer &client, AnyP::PortCfg *port);
+static IOACB FtpAcceptDataConnection;
+static void FtpCloseDataConnection(ConnStateData *conn);
+static void FtpWriteGreeting(ConnStateData *conn);
+static ClientSocketContext *FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::ProtocolVersion *http_ver);
+
+static void FtpHandleReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data);
+typedef void FtpReplyHandler(ClientSocketContext *context, const HttpReply *reply);
+static FtpReplyHandler FtpHandlePasvReply;
+static FtpReplyHandler FtpHandleErrorReply;
+
+static void FtpHandleReplyData(ClientSocketContext *context, StoreIOBuffer data);
+static IOCB FtpWroteReplyData;
+
+static void FtpWriteEarlyReply(ConnStateData *conn, const int code, const char *msg);
+static void FtpWriteReply(ClientSocketContext *context, MemBuf &mb);
+static void FtpWriteCustomReply(ClientSocketContext *context, const int code, const char *msg, const HttpReply *reply = NULL);
+static void FtpWriteForwardedReply(ClientSocketContext *context, const HttpReply *reply);
+static void FtpWriteForwardedReply(ClientSocketContext *context, const HttpReply *reply, AsyncCall::Pointer call);
+static void FtpPrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = "");
+static IOCB FtpWroteEarlyReply;
+static IOCB FtpWroteReply;
+
+typedef bool FtpRequestHandler(ConnStateData *connState, String &cmd, String ¶ms);
+static FtpRequestHandler FtpHandleRequest;
+static FtpRequestHandler FtpHandleUserRequest;
+static FtpRequestHandler FtpHandlePasvRequest;
+static FtpRequestHandler FtpHandlePortRequest;
+static FtpRequestHandler FtpHandleDataRequest;
+
+static bool FtpCheckDataConnection(ConnStateData *connState);
+
clientStreamNode *
ClientSocketContext::getTail() const
{
return (clientStreamNode *)http->client_stream.tail->prev->data;
}
+ConnStateData *
+ClientSocketContext::getConn() const
+{
+ return http->getConn();
+}
+
/**
* This routine should be called to grow the inbuf and then
* call comm_read().
const HttpReply::Pointer rep(msg.reply);
Must(rep != NULL);
+ // remember the callback
+ cbControlMsgSent = msg.cbSuccess;
+
+ AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg",
+ CommIoCbPtrFun(&WroteControlMsg, this));
+
+ if (http->flags.ftp) {
+ FtpWriteForwardedReply(this, rep.getRaw(), call);
+ return;
+ }
+
// apply selected clientReplyContext::buildReplyHeader() mods
// it is not clear what headers are required for control messages
rep->header.removeHopByHopEntries();
rep->header.putStr(HDR_CONNECTION, "keep-alive");
httpHdrMangleList(&rep->header, http->request, ROR_REPLY);
- // remember the callback
- cbControlMsgSent = msg.cbSuccess;
-
MemBuf *mb = rep->pack();
debugs(11, 2, "HTTP Client " << clientConnection);
debugs(11, 2, "HTTP Client CONTROL MSG:\n---------\n" << mb->buf << "\n----------");
- AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg",
- CommIoCbPtrFun(&WroteControlMsg, this));
Comm::Write(clientConnection, mb, call);
delete mb;
assert(this != NULL);
debugs(33, 3, HERE << clientConnection);
+ FtpCloseDataConnection(this);
+
if (isOpen())
debugs(33, DBG_IMPORTANT, "BUG: ConnStateData did not close " << clientConnection);
clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http,
HttpReply * rep, StoreIOBuffer receivedData)
{
+ debugs(33,7, HERE << "rep->content_length=" << (rep ? rep->content_length : -2) << " receivedData.length=" << receivedData.length);
/* Test preconditions */
assert(node != NULL);
PROF_start(clientSocketRecipient);
return;
}
+ if (http->getConn()->isFtp) {
+ assert(context->http == http);
+ FtpHandleReply(context.getRaw(), rep, receivedData);
+ PROF_stop(clientSocketRecipient);
+ return;
+ }
+
// After sending Transfer-Encoding: chunked (at least), always send
// the last-chunk if there was no error, ignoring responseFinishedOrFailed.
const bool mustSendLastChunk = http->request->flags.chunkedReply &&
goto finish;
}
+ if (conn->isFtp) {
+ assert(http->request);
+ request = http->request;
+ } else
if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
clientStreamNode *node = context->getClientReplyContext();
debugs(33, 5, "Invalid URL: " << http->uri);
/* compile headers */
/* we should skip request line! */
/* XXX should actually know the damned buffer size here */
- if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
+ if (!conn->isFtp && http_ver.major >= 1 &&
+ !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
clientStreamNode *node = context->getClientReplyContext();
debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp));
conn->quitAfterError(request.getRaw());
}
}
- http->request = request.getRaw();
- HTTPMSGLOCK(http->request);
+ if (!conn->isFtp) {
+ http->request = request.getRaw();
+ HTTPMSGLOCK(http->request);
+ }
clientSetKeepaliveFlag(http);
// Let tunneling code be fully responsible for CONNECT requests
static int
connOkToAddRequest(ConnStateData * conn)
{
- int result = conn->getConcurrentRequestCount() < (Config.onoff.pipeline_prefetch ? 2 : 1);
+ const int limit = !conn->isFtp && Config.onoff.pipeline_prefetch ? 2 : 1;
+ const int result = conn->getConcurrentRequestCount() < limit;
if (!result) {
debugs(33, 3, HERE << conn->clientConnection << " max concurrent requests reached");
/* Terminate the string */
in.buf[in.notYetUsed] = '\0';
- /* Begin the parsing */
- PROF_start(parseHttpRequest);
- HttpParserInit(&parser_, in.buf, in.notYetUsed);
-
- /* Process request */
Http::ProtocolVersion http_ver;
- ClientSocketContext *context = parseHttpRequest(this, &parser_, &method, &http_ver);
- PROF_stop(parseHttpRequest);
+ ClientSocketContext *context = NULL;
+ if (!isFtp) {
+ /* Begin the parsing */
+ PROF_start(parseHttpRequest);
+ HttpParserInit(&parser_, in.buf, in.notYetUsed);
+
+ /* Process request */
+ context = parseHttpRequest(this, &parser_, &method, &http_ver);
+ PROF_stop(parseHttpRequest);
+ } else
+ context = FtpParseRequest(this, &method, &http_ver);
/* partial or incomplete request */
if (!context) {
/**
* called when new request body data has been buffered in in.buf
* may close the connection if we were closing and piped everything out
- *
+e *
* \retval false called comm_close or setReplyToError (the caller should bail)
* \retval true we did not call comm_close or setReplyToError
*/
ConnStateData *
connStateCreate(const Comm::ConnectionPointer &client, AnyP::PortCfg *port)
{
- ConnStateData *result = new ConnStateData;
+ ConnStateData *result = new ConnStateData(port->protocol);
result->clientConnection = client;
result->log_addr = client->remote;
clientdbEstablished(client->remote, 1);
- result->flags.readMore = true;
+ if (!result->isFtp)
+ result->flags.readMore = true;
+
return result;
}
}
}
+/** handle a new FTP connection */
+static void
+ftpAccept(const CommAcceptCbParams ¶ms)
+{
+ AnyP::PortCfg *s = static_cast<AnyP::PortCfg *>(params.data);
+
+ if (params.flag != COMM_OK) {
+ // Its possible the call was still queued when the client disconnected
+ debugs(33, 2, "ftpAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno));
+ return;
+ }
+
+ debugs(33, 4, HERE << params.conn << ": accepted");
+ fd_note(params.conn->fd, "client ftp connect");
+
+ if (s->tcp_keepalive.enabled) {
+ commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
+ }
+
+ ++incoming_sockets_accepted;
+
+ // Socket is ready, setup the connection manager to start using it
+ ConnStateData *connState = connStateCreate(params.conn, s);
+
+ FtpWriteGreeting(connState);
+}
+
void
ConnStateData::sslCrtdHandleReplyWrapper(void *data, const HelperReply &reply)
{
}
#endif
+static void
+clientFtpConnectionsOpen(void)
+{
+ AnyP::PortCfg *s;
+
+ for (s = Config.Sockaddr.ftp; s; s = s->next) {
+ if (MAXTCPLISTENPORTS == NHttpSockets) {
+ debugs(1, DBG_IMPORTANT, "Ignoring 'ftp_port' lines exceeding the limit.");
+ debugs(1, DBG_IMPORTANT, "The limit is " << MAXTCPLISTENPORTS << " FTP ports.");
+ continue;
+ }
+
+ // Fill out a Comm::Connection which IPC will open as a listener for us
+ s->listenConn = new Comm::Connection;
+ s->listenConn->local = s->s;
+ s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) |
+ (s->flags.natIntercept ? COMM_INTERCEPTION : 0);
+
+ // setup the subscriptions such that new connections accepted by listenConn are handled by FTP
+ typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
+ RefCount<AcceptCall> subCall = commCbCall(5, 5, "ftpAccept", CommAcceptCbPtrFun(ftpAccept, s));
+ Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
+
+ AsyncCall::Pointer listenCall = asyncCall(33, 2, "clientListenerConnectionOpened",
+ ListeningStartedDialer(&clientListenerConnectionOpened,
+ s, Ipc::fdnFtpSocket, sub));
+ Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnFtpSocket, listenCall);
+ HttpSockets[NHttpSockets] = -1;
+ ++NHttpSockets;
+ }
+}
+
/// process clientHttpConnectionsOpen result
static void
clientListenerConnectionOpened(AnyP::PortCfg *s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub)
#if USE_SSL
clientHttpsConnectionsOpen();
#endif
+ clientFtpConnectionsOpen();
if (NHttpSockets < 1)
- fatal("No HTTP or HTTPS ports configured");
+ fatal("No HTTP, HTTPS or FTP ports configured");
}
void
-clientHttpConnectionsClose(void)
+clientConnectionsClose(void)
{
for (AnyP::PortCfg *s = Config.Sockaddr.http; s; s = s->next) {
if (s->listenConn != NULL) {
}
#endif
+ for (AnyP::PortCfg *s = Config.Sockaddr.ftp; s; s = s->next) {
+ if (s->listenConn != NULL) {
+ debugs(1, DBG_IMPORTANT, "Closing FTP port " << s->listenConn->local);
+ s->listenConn->close();
+ s->listenConn = NULL;
+ }
+ }
+
// TODO see if we can drop HttpSockets array entirely */
for (int i = 0; i < NHttpSockets; ++i) {
HttpSockets[i] = -1;
CBDATA_CLASS_INIT(ConnStateData);
-ConnStateData::ConnStateData() :
+ConnStateData::ConnStateData(const char *protocol) :
AsyncJob("ConnStateData"),
+ isFtp(strcmp(protocol, "ftp") == 0),
#if USE_SSL
sslBumpMode(Ssl::bumpEnd),
switchedToHttps_(false),
debugs(33, 3, "Closing client connection on pinned zero reply.");
clientConnection->close();
}
+ if (isFtp) {
+ // XXX
+ /*
+ debugs(33, 5, HERE << "FTP server connection closed, closing client "
+ "connection.");
+ clientConnection->close();
+ */
+ }
}
void
/* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host
* connection has gone away */
}
+
+
+static void
+FtpAcceptDataConnection(const CommAcceptCbParams ¶ms)
+{
+ ConnStateData *connState = static_cast<ConnStateData *>(params.data);
+
+ if (params.flag != COMM_OK) {
+ // Its possible the call was still queued when the client disconnected
+ debugs(33, 2, HERE << connState->ftp.dataListenConn << ": accept "
+ "failure: " << xstrerr(params.xerrno));
+ return;
+ }
+
+ debugs(33, 4, HERE << params.conn << ": accepted");
+ fd_note(params.conn->fd, "client ftp data connect");
+ ++incoming_sockets_accepted;
+
+ FtpCloseDataConnection(connState);
+ connState->ftp.dataConn = params.conn;
+}
+
+static void
+FtpCloseDataConnection(ConnStateData *conn)
+{
+ if (Comm::IsConnOpen(conn->ftp.dataListenConn)) {
+ debugs(33, 5, HERE << "FTP closing client data listen socket: " <<
+ *conn->ftp.dataListenConn);
+ conn->ftp.dataListenConn->close();
+ }
+ conn->ftp.dataListenConn = NULL;
+
+ if (Comm::IsConnOpen(conn->ftp.dataConn)) {
+ debugs(33, 5, HERE << "FTP closing client data connection: " <<
+ *conn->ftp.dataConn);
+ conn->ftp.dataConn->close();
+ }
+ conn->ftp.dataConn = NULL;
+}
+
+static void
+FtpWriteGreeting(ConnStateData *conn)
+{
+ MemBuf mb;
+ const String msg = "220 Service ready\r\n";
+ mb.init(msg.size(), msg.size());
+ mb.append(msg.rawBuf(), msg.size());
+
+ AsyncCall::Pointer call = commCbCall(33, 5, "FtpWroteEarlyReply",
+ CommIoCbPtrFun(&FtpWroteEarlyReply, conn));
+ Comm::Write(conn->clientConnection, &mb, call);
+}
+
+static void
+FtpWriteEarlyReply(ConnStateData *connState, const int code, const char *msg)
+{
+ debugs(33, 7, HERE << code << ' ' << msg);
+ assert(99 < code && code < 1000);
+
+ MemBuf mb;
+ mb.init();
+ mb.Printf("%i %s\r\n", code, msg);
+
+ AsyncCall::Pointer call = commCbCall(33, 5, "FtpWroteEarlyReply",
+ CommIoCbPtrFun(&FtpWroteEarlyReply, connState));
+ Comm::Write(connState->clientConnection, &mb, call);
+
+ connState->flags.readMore = false;
+}
+
+static void
+FtpWriteReply(ClientSocketContext *context, MemBuf &mb)
+{
+ AsyncCall::Pointer call = commCbCall(33, 5, "FtpWroteReply",
+ CommIoCbPtrFun(&FtpWroteReply, context));
+ Comm::Write(context->clientConnection, &mb, call);
+}
+
+static void
+FtpWriteCustomReply(ClientSocketContext *context, const int code, const char *msg, const HttpReply *reply)
+{
+ debugs(33, 7, HERE << code << ' ' << msg);
+ assert(99 < code && code < 1000);
+
+ const bool sendDetails = reply != NULL &&
+ reply->header.has(HDR_FTP_STATUS) && reply->header.has(HDR_FTP_REASON);
+
+ MemBuf mb;
+ mb.init();
+ if (sendDetails) {
+ mb.Printf("%i-%s\r\n", code, msg);
+ mb.Printf(" Server reply:\r\n");
+ FtpPrintReply(mb, reply, " ");
+ mb.Printf("%i \r\n", code);
+ } else
+ mb.Printf("%i %s\r\n", code, msg);
+
+ FtpWriteReply(context, mb);
+}
+
+/** Parse an FTP request
+ *
+ * \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
+ * to 1 if the request was correctly parsed.
+ * \param[in] connState a ConnStateData. The caller must make sure it is not null
+ * \param[out] mehtod_p will be set as a side-effect of the parsing.
+ * Pointed-to value will be set to Http::METHOD_NONE in case of
+ * parsing failure
+ * \param[out] http_ver will be set as a side-effect of the parsing
+ * \return NULL on incomplete requests,
+ * a ClientSocketContext structure on success or failure.
+ */
+static ClientSocketContext *
+FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::ProtocolVersion *http_ver)
+{
+ *method_p = Http::METHOD_GET;
+ *http_ver = Http::ProtocolVersion(1, 1);
+
+ const char *const eor =
+ static_cast<const char *>(memchr(connState->in.buf, '\n',
+ min(connState->in.notYetUsed, Config.maxRequestHeaderSize)));
+
+ if (eor == NULL && connState->in.notYetUsed >= Config.maxRequestHeaderSize) {
+ connState->ftp.state = ConnStateData::FTP_ERROR;
+ FtpWriteEarlyReply(connState, 421, "Too large request");
+ return NULL;
+ }
+
+ if (eor == NULL) {
+ debugs(33, 5, HERE << "Incomplete request, waiting for end of request");
+ return NULL;
+ }
+
+ // skip leading whitespaces
+ const char *boc = connState->in.buf;
+ while (boc < eor && isspace(*boc)) ++boc;
+ if (boc >= eor) {
+ debugs(33, 5, HERE << "Empty request, ignoring");
+ connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
+ return NULL;
+ }
+
+ const char *eoc = boc;
+ while (eoc < eor && !isspace(*eoc)) ++eoc;
+ connState->in.buf[eoc - connState->in.buf] = '\0';
+
+ const char *bop = eoc + 1;
+ while (bop < eor && isspace(*bop)) ++bop;
+ if (bop < eor) {
+ const char *eop = eor - 1;
+ while (isspace(*eop)) --eop;
+ assert(eop >= bop);
+ connState->in.buf[eop + 1 - connState->in.buf] = '\0';
+ } else
+ bop = NULL;
+
+ debugs(33, 7, HERE << "Parsed FTP command " << boc << " with " <<
+ (bop == NULL ? "no " : "") << "parameters" <<
+ (bop != NULL ? ": " : "") << bop);
+
+ String cmd = boc;
+ String params = bop;
+
+ // the first command must be USER
+ if (connState->ftp.uri.size() == 0 && cmd.caseCmp("USER") != 0) {
+ debugs(33, 5, HERE << "Unexpected FTP command: expected USER, got " <<
+ boc);
+ FtpWriteEarlyReply(connState, 530, "Must login first");
+ connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
+ return NULL;
+ }
+
+ if (!FtpHandleRequest(connState, cmd, params)) {
+ connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
+ return NULL;
+ }
+
+ char *uri = xstrdup(connState->ftp.uri.termedBuf());
+ HttpRequest *const request =
+ HttpRequest::CreateFromUrlAndMethod(uri, *method_p);
+ if (request == NULL) {
+ debugs(33, 5, HERE << "Invalid FTP URL: " << connState->ftp.uri);
+ FtpWriteEarlyReply(connState, 501, "Invalid host");
+ connState->ftp.uri.clean();
+ connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
+ safe_free(uri);
+ return NULL;
+ }
+
+ request->header.putStr(HDR_FTP_COMMAND, cmd.termedBuf());
+ if (params.size() > 0)
+ request->header.putStr(HDR_FTP_ARGUMENTS, params.termedBuf());
+
+ ClientHttpRequest *const http = new ClientHttpRequest(connState);
+ http->request = request;
+ HTTPMSGLOCK(http->request);
+ http->req_sz = eor - connState->in.buf + 1;
+ http->flags.ftp = true;
+ http->uri = xstrdup(connState->ftp.uri.termedBuf());
+
+ ClientSocketContext *const result =
+ ClientSocketContextNew(connState->clientConnection, http);
+
+ StoreIOBuffer tempBuffer;
+ tempBuffer.data = result->reqbuf;
+ tempBuffer.length = HTTP_REQBUF_SZ;
+
+ ClientStreamData newServer = new clientReplyContext(http);
+ ClientStreamData newClient = result;
+ clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
+ clientReplyStatus, newServer, clientSocketRecipient,
+ clientSocketDetach, newClient, tempBuffer);
+
+ result->flags.parsed_ok = 1;
+ connState->flags.readMore = false;
+ return result;
+}
+
+static void
+FtpHandleReply(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data)
+{
+ if (reply == NULL || reply->content_length == -1) {
+ FtpHandleReplyData(context, data);
+ return;
+ }
+
+ assert(reply != NULL);
+ assert(reply->content_length == 0);
+ assert(context->startOfOutput());
+
+ const ConnStateData::FtpState state = context->getConn()->ftp.state;
+ assert(state != ConnStateData::FTP_BEGIN);
+
+ static FtpReplyHandler *handlers[] = {
+ NULL, // FTP_BEGIN
+ NULL, // FTP_CONNECTED
+ FtpHandlePasvReply, // FTP_HANDLE_PASV
+ NULL, // FTP_HANDLE_DATA_REQUEST
+ FtpHandleErrorReply // FTP_ERROR
+ };
+ FtpReplyHandler *const handler = handlers[state];
+ if (handler)
+ (*handler)(context, reply);
+ else
+ FtpWriteForwardedReply(context, reply);
+}
+
+static void
+FtpHandlePasvReply(ClientSocketContext *context, const HttpReply *reply)
+{
+ if (context->http->request->errType != ERR_NONE) {
+ FtpWriteCustomReply(context, 502, "Server does not support PASV", reply);
+ return;
+ }
+
+ FtpCloseDataConnection(context->getConn());
+
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ conn->flags = COMM_NONBLOCKING;
+ conn->local = context->clientConnection->local;
+ conn->local.SetPort(0);
+ ConnStateData *const connState = context->getConn();
+ const char *const note = connState->ftp.uri.termedBuf();
+ comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn, note);
+ if (!Comm::IsConnOpen(conn)) {
+ debugs(5, DBG_CRITICAL, HERE << "comm_open_listener failed:" <<
+ conn->local << " error: " << errno);
+ FtpWriteCustomReply(context, 451, "Internal error");
+ return;
+ }
+
+ typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
+ RefCount<AcceptCall> subCall = commCbCall(5, 5, "FtpAcceptDataConnection",
+ CommAcceptCbPtrFun(FtpAcceptDataConnection, connState));
+ Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
+ AsyncJob::Start(new Comm::TcpAcceptor(conn, note, sub));
+
+ connState->ftp.dataListenConn = conn;
+
+ char addr[MAX_IPSTRLEN];
+ conn->local.NtoA(addr, MAX_IPSTRLEN, AF_INET);
+ addr[MAX_IPSTRLEN - 1] = '\0';
+ for (char *c = addr; *c != '\0'; ++c) {
+ if (*c == '.')
+ *c = ',';
+ }
+
+ const unsigned short port = comm_local_port(conn->fd);
+ conn->local.SetPort(port);
+
+ MemBuf mb;
+ mb.init();
+ mb.Printf("227 =%s,%i,%i\r\n", addr, static_cast<int>(port >> 8),
+ static_cast<int>(port % 256));
+
+ FtpWriteReply(context, mb);
+}
+
+static void
+FtpHandleErrorReply(ClientSocketContext *context, const HttpReply *reply)
+{
+ int code;
+ ConnStateData *const connState = context->getConn();
+ if (!connState->pinning.pinned) { // we failed to connect to server
+ connState->ftp.uri.clean();
+ code = 530;
+ } else
+ code = 421;
+ const char *const msg = err_type_str[context->http->request->errType];
+ FtpWriteCustomReply(context, code, msg, reply);
+}
+
+static void
+FtpHandleReplyData(ClientSocketContext *context, StoreIOBuffer data)
+{
+ debugs(33, 7, HERE << data.length);
+
+ ConnStateData *const conn = context->getConn();
+
+ if (data.length <= 0) {
+ FtpWroteReplyData(conn->clientConnection, NULL, 0, COMM_OK, 0, context);
+ return;
+ }
+
+ if (!Comm::IsConnOpen(conn->ftp.dataConn)) {
+ debugs(33, 3, HERE << "got FTP reply data when client data connection "
+ "is closed, ignoring");
+ return;
+ }
+
+ MemBuf mb;
+ mb.init(data.length, data.length);
+ mb.append(data.data, data.length);
+
+ AsyncCall::Pointer call = commCbCall(33, 5, "FtpWroteReplyData",
+ CommIoCbPtrFun(&FtpWroteReplyData, context));
+ Comm::Write(conn->ftp.dataConn, &mb, call);
+
+ context->noteSentBodyBytes(data.length);
+}
+
+static void
+FtpWroteReplyData(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
+{
+ if (errflag == COMM_ERR_CLOSING)
+ return;
+
+ ClientSocketContext *const context = static_cast<ClientSocketContext*>(data);
+ ConnStateData *const connState = context->getConn();
+
+ if (errflag != COMM_OK) {
+ debugs(33, 3, HERE << "FTP reply data writing failed: " <<
+ xstrerr(xerrno));
+ FtpCloseDataConnection(connState);
+ FtpWriteCustomReply(context, 426, "Data connection error; transfer aborted");
+ return;
+ }
+
+ switch (context->socketState()) {
+ case STREAM_NONE:
+ context->pullData();
+ return;
+ case STREAM_COMPLETE:
+ debugs(33, 3, HERE << "FTP reply data transfer successfully complete");
+ FtpWriteCustomReply(context, 226, "Transfer complete");
+ break;
+ case STREAM_UNPLANNED_COMPLETE:
+ debugs(33, 3, HERE << "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
+ FtpWriteCustomReply(context, 451, "Server error; transfer aborted");
+ break;
+ case STREAM_FAILED:
+ debugs(33, 3, HERE << "FTP reply data transfer failed: STREAM_FAILED");
+ FtpWriteCustomReply(context, 451, "Server error; transfer aborted");
+ break;
+ default:
+ fatal("unreachable code");
+ }
+
+ FtpCloseDataConnection(connState);
+}
+
+static void
+FtpWriteForwardedReply(ClientSocketContext *context, const HttpReply *reply)
+{
+ const AsyncCall::Pointer call = commCbCall(33, 5, "FtpWroteReply",
+ CommIoCbPtrFun(&FtpWroteReply, context));
+ FtpWriteForwardedReply(context, reply, call);
+}
+
+static void
+FtpWriteForwardedReply(ClientSocketContext *context, const HttpReply *reply, AsyncCall::Pointer call)
+{
+ assert(reply != NULL);
+ const HttpHeader &header = reply->header;
+ assert(header.has(HDR_FTP_STATUS));
+ assert(header.has(HDR_FTP_REASON));
+
+ const int status = header.getInt(HDR_FTP_STATUS);
+ debugs(33, 7, HERE << "status: " << status);
+
+ MemBuf mb;
+ mb.init();
+ FtpPrintReply(mb, reply);
+
+ Comm::Write(context->clientConnection, &mb, call);
+}
+
+static void
+FtpPrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix)
+{
+ const HttpHeader &header = reply->header;
+
+ char status[4];
+ if (header.has(HDR_FTP_STATUS))
+ snprintf(status, sizeof(status), "%i", header.getInt(HDR_FTP_STATUS));
+ else
+ status[0] = '\0';
+
+ HttpHeaderPos pos = HttpHeaderInitPos;
+ const HttpHeaderEntry *e = header.getEntry(&pos);
+ while (e) {
+ const HttpHeaderEntry *const next = header.getEntry(&pos);
+ if (e->id == HDR_FTP_REASON) {
+ const bool isLastLine = next == NULL || next->id != HDR_FTP_REASON;
+ const int separator = status[0] == '\0' || isLastLine ? ' ' : '-';
+ mb.Printf("%s%s%c%s\r\n", prefix, status, separator,
+ e->value.termedBuf());
+ }
+ e = next;
+ }
+}
+
+static void
+FtpWroteEarlyReply(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
+{
+ if (errflag == COMM_ERR_CLOSING)
+ return;
+
+ if (errflag != COMM_OK) {
+ debugs(33, 3, HERE << "FTP reply writing failed: " << xstrerr(xerrno));
+ conn->close();
+ return;
+ }
+
+ ConnStateData *const connState = static_cast<ConnStateData*>(data);
+ connState->flags.readMore = true;
+ connState->readSomeData();
+}
+
+static void
+FtpWroteReply(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
+{
+ if (errflag == COMM_ERR_CLOSING)
+ return;
+
+ if (errflag != COMM_OK) {
+ debugs(33, 3, HERE << "FTP reply writing failed: " <<
+ xstrerr(xerrno));
+ conn->close();
+ return;
+ }
+
+ ClientSocketContext *const context =
+ static_cast<ClientSocketContext*>(data);
+ ConnStateData *const connState = context->getConn();
+
+ if (connState->ftp.state == ConnStateData::FTP_ERROR) {
+ conn->close();
+ return;
+ }
+
+ assert(context->socketState() == STREAM_COMPLETE);
+ connState->flags.readMore = true;
+ connState->ftp.state = ConnStateData::FTP_CONNECTED;
+ context->keepaliveNextRequest();
+}
+
+bool
+FtpHandleRequest(ConnStateData *connState, String &cmd, String ¶ms) {
+ static std::pair<const char *, FtpRequestHandler *> handlers[] = {
+ std::make_pair("PASV", FtpHandlePasvRequest),
+ std::make_pair("PORT", FtpHandlePortRequest),
+ std::make_pair("RETR", FtpHandleDataRequest),
+ std::make_pair("LIST", FtpHandleDataRequest),
+ std::make_pair("NLST", FtpHandleDataRequest),
+ std::make_pair("USER", FtpHandleUserRequest)
+ };
+
+ FtpRequestHandler *handler = NULL;
+ for (size_t i = 0; i < sizeof(handlers) / sizeof(*handlers); ++i) {
+ if (cmd.caseCmp(handlers[i].first) == 0) {
+ handler = handlers[i].second;
+ break;
+ }
+ }
+
+ return handler != NULL ? (*handler)(connState, cmd, params) : true;
+}
+
+bool
+FtpHandleUserRequest(ConnStateData *connState, String &cmd, String ¶ms)
+{
+ if (params.size() == 0) {
+ FtpWriteEarlyReply(connState, 501, "Missing username");
+ return false;
+ }
+
+ const String::size_type eou = params.find('@');
+ if (eou == String::npos || eou + 1 >= params.size()) {
+ FtpWriteEarlyReply(connState, 501, "Missing host");
+ return false;
+ }
+
+ static const String scheme = "ftp://";
+ const String host = params.substr(eou + 1, params.size());
+ String uri = scheme;
+ uri.append(host);
+ uri.append("/");
+
+ if (connState->ftp.uri.size() == 0)
+ connState->ftp.uri = uri;
+ else if (uri.caseCmp(connState->ftp.uri) != 0) {
+ FtpWriteEarlyReply(connState, 501, "Cannot change host");
+ return false;
+ }
+
+ params.cut(eou);
+
+ return true;
+}
+
+bool
+FtpHandlePasvRequest(ConnStateData *connState, String &cmd, String ¶ms)
+{
+ if (params.size() > 0) {
+ FtpWriteEarlyReply(connState, 501, "Unexpected parameter");
+ return false;
+ }
+
+ connState->ftp.state = ConnStateData::FTP_HANDLE_PASV;
+
+ return true;
+}
+
+bool
+FtpHandlePortRequest(ConnStateData *connState, String &cmd, String ¶ms)
+{
+ FtpWriteEarlyReply(connState, 502, "Command not supported");
+ return false;
+}
+
+bool
+FtpHandleDataRequest(ConnStateData *connState, String &cmd, String ¶ms)
+{
+ if (!FtpCheckDataConnection(connState))
+ return false;
+
+ connState->ftp.state = ConnStateData::FTP_HANDLE_DATA_REQUEST;
+
+ return true;
+}
+
+bool
+FtpCheckDataConnection(ConnStateData *connState)
+{
+ if (Comm::IsConnOpen(connState->ftp.dataConn))
+ return true;
+
+ if (!Comm::IsConnOpen(connState->ftp.dataListenConn))
+ FtpWriteEarlyReply(connState, 425, "Use PASV first");
+ else
+ FtpWriteEarlyReply(connState, 425, "Data connection is not established");
+ return false;
+}
void buildRangeHeader(HttpReply * rep);
clientStreamNode * getTail() const;
clientStreamNode * getClientReplyContext() const;
+ ConnStateData *getConn() const;
void connIsFinished();
void removeFromConnectionList(ConnStateData * conn);
void deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData);
public:
- ConnStateData();
+ ConnStateData(const char *protocol);
~ConnStateData();
void readSomeData();
/// the client-side-detected error response instead of getting stuck.
void quitAfterError(HttpRequest *request); // meant to be private
+ const bool isFtp;
+ enum FtpState {
+ FTP_BEGIN,
+ FTP_CONNECTED,
+ FTP_HANDLE_PASV,
+ FTP_HANDLE_DATA_REQUEST,
+ FTP_ERROR
+ };
+ struct {
+ String uri;
+ FtpState state;
+ Comm::ConnectionPointer dataListenConn;
+ Comm::ConnectionPointer dataConn;
+ Ip::Address serverDataAddr;
+ } ftp;
+
#if USE_SSL
/// called by FwdState when it is done bumping the server
void httpsPeeked(Comm::ConnectionPointer serverConnection);
int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req);
void clientOpenListenSockets(void);
-void clientHttpConnectionsClose(void);
+void clientConnectionsClose(void);
void httpRequestFree(void *);
#endif /* SQUID_CLIENTSIDE_H */
bool internal;
bool done_copying;
bool purging;
+ bool ftp;
} flags;
struct {
#include "fde.h"
#include "forward.h"
#include "ftp.h"
+#include "FtpGatewayServer.h"
#include "globals.h"
#include "gopher.h"
#include "hier_code.h"
}
#endif
+ const CbcPointer<ConnStateData> &clientConnState =
+ request->clientConnectionManager;
+ if (clientConnState->isFtp) {
+ clientConnState->pinConnection(serverConnection(), request,
+ serverConnection()->getPeer(), false);
+ }
+
dispatch();
}
break;
case AnyP::PROTO_FTP:
- ftpStart(this);
+ if (request->clientConnectionManager->isFtp)
+ ftpGatewayServerStart(this);
+ else
+ ftpStart(this);
break;
case AnyP::PROTO_CACHE_OBJECT:
#include "squid.h"
#include "comm.h"
-#include "comm/ConnOpener.h"
#include "comm/TcpAcceptor.h"
-#include "comm/Write.h"
#include "CommCalls.h"
#include "compat/strtoll.h"
#include "errorpage.h"
#include "fd.h"
#include "fde.h"
#include "forward.h"
+#include "FtpServer.h"
#include "html_quote.h"
#include "HttpHdrContRange.h"
#include "HttpHeader.h"
\ingroup ServerProtocolFTPAPI
*/
-/// \ingroup ServerProtocolFTPInternal
-static const char *const crlf = "\r\n";
-
#define CTRL_BUFLEN 1024
/// \ingroup ServerProtocolFTPInternal
static char cbuf[CTRL_BUFLEN];
/// \ingroup ServerProtocolFTPInternal
typedef void (FTPSM) (FtpStateData *);
-/// common code for FTP control and data channels
-/// does not own the channel descriptor, which is managed by FtpStateData
-class FtpChannel
-{
-public:
- FtpChannel() {};
-
- /// called after the socket is opened, sets up close handler
- void opened(const Comm::ConnectionPointer &conn, const AsyncCall::Pointer &aCloser);
-
- /** Handles all operations needed to properly close the active channel FD.
- * clearing the close handler, clearing the listen socket properly, and calling comm_close
- */
- void close();
-
- void clear(); ///< just drops conn and close handler. does not close active connections.
-
- Comm::ConnectionPointer conn; ///< channel descriptor
-
- /** A temporary handle to the connection being listened on.
- * Closing this will also close the waiting Data channel acceptor.
- * If a data connection has already been accepted but is still waiting in the event queue
- * the callback will still happen and needs to be handled (usually dropped).
- */
- Comm::ConnectionPointer listenConn;
-
- AsyncCall::Pointer opener; ///< Comm opener handler callback.
-private:
- AsyncCall::Pointer closer; ///< Comm close handler callback
-};
-
/// \ingroup ServerProtocolFTPInternal
-class FtpStateData : public ServerStateData
+class FtpStateData : public Ftp::ServerStateData
{
public:
void operator delete (void *);
void *toCbdata() { return this; }
- FtpStateData(FwdState *, const Comm::ConnectionPointer &conn);
- ~FtpStateData();
+ FtpStateData(FwdState *fwdState);
+ virtual ~FtpStateData();
char user[MAX_URL];
char password[MAX_URL];
int password_url;
String base_href;
int conn_att;
int login_att;
- ftp_state_t state;
time_t mdtm;
int64_t theSize;
wordlist *pathcomps;
char *proxy_host;
size_t list_width;
String cwd_message;
- char *old_request;
- char *old_reply;
char *old_filepath;
char typecode;
MemBuf listing; ///< FTP directory listing in HTML format.
- // \todo: optimize ctrl and data structs member order, to minimize size
- /// FTP control channel info; the channel is opened once per transaction
- struct CtrlChannel: public FtpChannel {
- char *buf;
- size_t size;
- size_t offset;
- wordlist *message;
- char *last_command;
- char *last_reply;
- int replycode;
- } ctrl;
-
- /// FTP data channel info; the channel may be opened/closed a few times
- struct DataChannel: public FtpChannel {
- MemBuf *readBuf;
- char *host;
- unsigned short port;
- bool read_pending;
- } data;
-
struct _ftp_flags flags;
private:
public:
// these should all be private
virtual void start();
+ virtual Http::StatusCode failedHttpStatus(err_type &error);
void loginParser(const char *, int escaped);
int restartable();
void appendSuccessHeader();
void hackShortcut(FTPSM * nextState);
- void failed(err_type, int xerrno);
- void failedErrorMessage(err_type, int xerrno);
void unhack();
- void scheduleReadControlReply(int);
- void handleControlReply();
void readStor();
void parseListing();
MemBuf *htmlifyListEntry(const char *line);
void completedListing(void);
- void dataComplete();
- void dataRead(const CommIoCbParams &io);
- /// ignore timeout on CTRL channel. set read timeout on DATA channel.
- void switchTimeoutToDataChannel();
/// create a data channel acceptor and start listening.
- void listenForDataChannel(const Comm::ConnectionPointer &conn, const char *note);
+ void listenForDataChannel(const Comm::ConnectionPointer &conn);
int checkAuth(const HttpHeader * req_hdr);
void checkUrlpath();
void buildTitleUrl();
void writeReplyBody(const char *, size_t len);
void printfReplyBody(const char *fmt, ...);
- virtual const Comm::ConnectionPointer & dataConnection() const;
- virtual void maybeReadVirginBody();
- virtual void closeServer();
virtual void completeForwarding();
- virtual void abortTransaction(const char *reason);
void processHeadResponse();
void processReplyBody();
- void writeCommand(const char *buf);
void setCurrentOffset(int64_t offset) { currentOffset = offset; }
int64_t getCurrentOffset() const { return currentOffset; }
- static CNCB ftpPasvCallback;
+ virtual void dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t err, int xerrno);
static PF ftpDataWrite;
- void ftpTimeout(const CommTimeoutCbParams &io);
- void ctrlClosed(const CommCloseCbParams &io);
- void dataClosed(const CommCloseCbParams &io);
- void ftpReadControlReply(const CommIoCbParams &io);
- void ftpWriteCommandCallback(const CommIoCbParams &io);
+ virtual void timeout(const CommTimeoutCbParams &io);
void ftpAcceptDataConnection(const CommAcceptCbParams &io);
static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
const char *ftpRealm(void);
void loginFailed(void);
- static wordlist *ftpParseControlReply(char *, size_t, int *, size_t *);
-
- // sending of the request body to the server
- virtual void sentRequestBody(const CommIoCbParams&);
- virtual void doneSendingRequestBody();
virtual void haveParsedReplyHeaders();
- virtual bool doneWithServer() const;
virtual bool haveControlChannel(const char *caller_name) const;
- AsyncCall::Pointer dataCloser(); /// creates a Comm close callback
- AsyncCall::Pointer dataOpener(); /// creates a Comm connect callback
+
+protected:
+ virtual void handleControlReply();
+ virtual void dataClosed(const CommCloseCbParams &io);
private:
// BodyConsumer for HTTP: consume request body.
ftpReadMkdir /* SENT_MKDIR */
};
-/// handler called by Comm when FTP control channel is closed unexpectedly
-void
-FtpStateData::ctrlClosed(const CommCloseCbParams &io)
-{
- debugs(9, 4, HERE);
- ctrl.clear();
- mustStop("FtpStateData::ctrlClosed");
-}
-
/// handler called by Comm when FTP data channel is closed unexpectedly
void
FtpStateData::dataClosed(const CommCloseCbParams &io)
{
- debugs(9, 4, HERE);
- if (data.listenConn != NULL) {
- data.listenConn->close();
- data.listenConn = NULL;
- // NP clear() does the: data.fd = -1;
- }
- data.clear();
+ Ftp::ServerStateData::dataClosed(io);
failed(ERR_FTP_FAILURE, 0);
/* failed closes ctrl.conn and frees ftpState */
*/
}
-FtpStateData::FtpStateData(FwdState *theFwdState, const Comm::ConnectionPointer &conn) : AsyncJob("FtpStateData"), ServerStateData(theFwdState)
+FtpStateData::FtpStateData(FwdState *fwdState): AsyncJob("FtpStateData"),
+ Ftp::ServerStateData(fwdState)
{
const char *url = entry->url();
debugs(9, 3, HERE << "'" << url << "'" );
- ++ statCounter.server.all.requests;
- ++ statCounter.server.ftp.requests;
theSize = -1;
mdtm = -1;
flags.rest_supported = 1;
- typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
- AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this, FtpStateData::ctrlClosed);
- ctrl.opened(conn, closer);
-
if (request->method == Http::METHOD_PUT)
flags.put = 1;
}
{
debugs(9, 3, HERE << entry->url() );
- if (reply_hdr) {
- memFree(reply_hdr, MEM_8K_BUF);
- reply_hdr = NULL;
- }
-
- if (data.opener != NULL) {
- data.opener->cancel("FtpStateData destructed");
- data.opener = NULL;
- }
- data.close();
-
if (Comm::IsConnOpen(ctrl.conn)) {
- debugs(9, DBG_IMPORTANT, HERE << "Internal bug: FtpStateData left " <<
- "open control channel " << ctrl.conn);
- }
-
- if (ctrl.buf) {
- memFreeBuf(ctrl.size, ctrl.buf);
- ctrl.buf = NULL;
+ debugs(9, DBG_IMPORTANT, HERE << "Internal bug: Ftp::ServerStateData "
+ "left open control channel " << ctrl.conn);
}
- if (data.readBuf) {
- if (!data.readBuf->isNull())
- data.readBuf->clean();
-
- delete data.readBuf;
+ if (reply_hdr) {
+ memFree(reply_hdr, MEM_8K_BUF);
+ reply_hdr = NULL;
}
if (pathcomps)
wordlistDestroy(&pathcomps);
- if (ctrl.message)
- wordlistDestroy(&ctrl.message);
-
cwd_message.clean();
- safe_free(ctrl.last_reply);
-
- safe_free(ctrl.last_command);
-
- safe_free(old_request);
-
- safe_free(old_reply);
-
safe_free(old_filepath);
title_url.clean();
safe_free(filepath);
safe_free(dirpath);
-
- safe_free(data.host);
-
- fwd = NULL; // refcounted
}
/**
debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
}
-/**
- * Cancel the timeout on the Control socket and establish one
- * on the data socket
- */
-void
-FtpStateData::switchTimeoutToDataChannel()
-{
- commUnsetConnTimeout(ctrl.conn);
-
- typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, FtpStateData::ftpTimeout);
- commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
-}
-
void
-FtpStateData::listenForDataChannel(const Comm::ConnectionPointer &conn, const char *note)
+FtpStateData::listenForDataChannel(const Comm::ConnectionPointer &conn)
{
assert(!Comm::IsConnOpen(data.conn));
typedef AsyncCallT<AcceptDialer> AcceptCall;
RefCount<AcceptCall> call = static_cast<AcceptCall*>(JobCallback(11, 5, AcceptDialer, this, FtpStateData::ftpAcceptDataConnection));
Subscription::Pointer sub = new CallSubscription<AcceptCall>(call);
+ const char *note = entry->url();
/* open the conn if its not already open */
if (!Comm::IsConnOpen(conn)) {
}
void
-FtpStateData::ftpTimeout(const CommTimeoutCbParams &io)
+FtpStateData::timeout(const CommTimeoutCbParams &io)
{
- debugs(9, 4, HERE << io.conn << ": '" << entry->url() << "'" );
-
- if (abortOnBadEntry("entry went bad while waiting for a timeout"))
- return;
-
if (SENT_PASV == state) {
/* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
flags.pasv_supported = false;
- debugs(9, DBG_IMPORTANT, "ftpTimeout: timeout in SENT_PASV state" );
+ debugs(9, DBG_IMPORTANT, HERE << "timeout in SENT_PASV state");
// cancel the data connection setup.
if (data.opener != NULL) {
data.close();
}
- failed(ERR_READ_TIMEOUT, 0);
- /* failed() closes ctrl.conn and frees ftpState */
+ Ftp::ServerStateData::timeout(io);
}
/// \ingroup ServerProtocolFTPInternal
line = (char *)memAllocate(MEM_4K_BUF);
++end;
s = sbuf;
- s += strspn(s, crlf);
+ s += strspn(s, Ftp::crlf);
- for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
+ for (; s < end; s += strcspn(s, Ftp::crlf), s += strspn(s, Ftp::crlf)) {
debugs(9, 7, HERE << "s = {" << s << "}");
- linelen = strcspn(s, crlf) + 1;
+ linelen = strcspn(s, Ftp::crlf) + 1;
if (linelen < 2)
break;
xfree(sbuf);
}
-const Comm::ConnectionPointer &
-FtpStateData::dataConnection() const
-{
- return data.conn;
-}
-
-void
-FtpStateData::dataComplete()
-{
- debugs(9, 3,HERE);
-
- /* Connection closed; transfer done. */
-
- /// Close data channel, if any, to conserve resources while we wait.
- data.close();
-
- /* expect the "transfer complete" message on the control socket */
- /*
- * DPW 2007-04-23
- * Previously, this was the only place where we set the
- * 'buffered_ok' flag when calling scheduleReadControlReply().
- * It caused some problems if the FTP server returns an unexpected
- * status code after the data command. FtpStateData was being
- * deleted in the middle of dataRead().
- */
- /* AYJ: 2011-01-13: Bug 2581.
- * 226 status is possibly waiting in the ctrl buffer.
- * The connection will hang if we DONT send buffered_ok.
- * This happens on all transfers which can be completly sent by the
- * server before the 150 started status message is read in by Squid.
- * ie all transfers of about one packet hang.
- */
- scheduleReadControlReply(1);
-}
-
-void
-FtpStateData::maybeReadVirginBody()
-{
- // too late to read
- if (!Comm::IsConnOpen(data.conn) || fd_table[data.conn->fd].closing())
- return;
-
- if (data.read_pending)
- return;
-
- const int read_sz = replyBodySpace(*data.readBuf, 0);
-
- debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
-
- if (read_sz < 2) // see http.cc
- return;
-
- data.read_pending = true;
-
- typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
- TimeoutDialer, this, FtpStateData::ftpTimeout);
- commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
-
- debugs(9,5,HERE << "queueing read on FD " << data.conn->fd);
-
- typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
- entry->delayAwareRead(data.conn, data.readBuf->space(), read_sz,
- JobCallback(9, 5, Dialer, this, FtpStateData::dataRead));
-}
-
-void
-FtpStateData::dataRead(const CommIoCbParams &io)
-{
- int j;
- int bin;
-
- data.read_pending = false;
-
- debugs(9, 3, HERE << "ftpDataRead: FD " << io.fd << " Read " << io.size << " bytes");
-
- if (io.size > 0) {
- kb_incr(&(statCounter.server.all.kbytes_in), io.size);
- kb_incr(&(statCounter.server.ftp.kbytes_in), io.size);
- }
-
- if (io.flag == COMM_ERR_CLOSING)
- return;
-
- assert(io.fd == data.conn->fd);
-
- if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- abortTransaction("entry aborted during dataRead");
- return;
- }
-
- if (io.flag == COMM_OK && io.size > 0) {
- debugs(9,5,HERE << "appended " << io.size << " bytes to readBuf");
- data.readBuf->appended(io.size);
-#if USE_DELAY_POOLS
- DelayId delayId = entry->mem_obj->mostBytesAllowed();
- delayId.bytesIn(io.size);
-#endif
- ++ IOStats.Ftp.reads;
-
- for (j = io.size - 1, bin = 0; j; ++bin)
- j >>= 1;
-
- ++ IOStats.Ftp.read_hist[bin];
- }
-
- if (io.flag != COMM_OK) {
- debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
- "ftpDataRead: read error: " << xstrerr(io.xerrno));
-
- if (ignoreErrno(io.xerrno)) {
- typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
- TimeoutDialer, this, FtpStateData::ftpTimeout);
- commSetConnTimeout(io.conn, Config.Timeout.read, timeoutCall);
-
- maybeReadVirginBody();
- } else {
- failed(ERR_READ_ERROR, 0);
- /* failed closes ctrl.conn and frees ftpState */
- return;
- }
- } else if (io.size == 0) {
- debugs(9,3, HERE << "Calling dataComplete() because io.size == 0");
- /*
- * DPW 2007-04-23
- * Dangerous curves ahead. This call to dataComplete was
- * calling scheduleReadControlReply, handleControlReply,
- * and then ftpReadTransferDone. If ftpReadTransferDone
- * gets unexpected status code, it closes down the control
- * socket and our FtpStateData object gets destroyed. As
- * a workaround we no longer set the 'buffered_ok' flag in
- * the scheduleReadControlReply call.
- */
- dataComplete();
- }
-
- processReplyBody();
-}
-
void
FtpStateData::processReplyBody()
{
void
ftpStart(FwdState * fwd)
{
- AsyncJob::Start(new FtpStateData(fwd, fwd->serverConnection()));
+ AsyncJob::Start(new FtpStateData(fwd));
}
void
", path=" << request->urlpath << ", user=" << user << ", passwd=" << password);
state = BEGIN;
- ctrl.last_command = xstrdup("Connect to server");
- ctrl.buf = (char *)memAllocBuf(4096, &ctrl.size);
- ctrl.offset = 0;
- data.readBuf = new MemBuf;
- data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF);
- scheduleReadControlReply(0);
-}
-
-/* ====================================================================== */
-
-/// \ingroup ServerProtocolFTPInternal
-static char *
-escapeIAC(const char *buf)
-{
- int n;
- char *ret;
- unsigned const char *p;
- unsigned char *r;
-
- for (p = (unsigned const char *)buf, n = 1; *p; ++n, ++p)
- if (*p == 255)
- ++n;
-
- ret = (char *)xmalloc(n);
-
- for (p = (unsigned const char *)buf, r=(unsigned char *)ret; *p; ++p) {
- *r = *p;
- ++r;
- if (*p == 255) {
- *r = 255;
- ++r;
- }
- }
-
- *r = '\0';
- ++r;
- assert((r - (unsigned char *)ret) == n );
- return ret;
+ Ftp::ServerStateData::start();
}
-void
-FtpStateData::writeCommand(const char *buf)
-{
- char *ebuf;
- /* trace FTP protocol communications at level 2 */
- debugs(9, 2, "ftp<< " << buf);
-
- if (Config.Ftp.telnet)
- ebuf = escapeIAC(buf);
- else
- ebuf = xstrdup(buf);
-
- safe_free(ctrl.last_command);
-
- safe_free(ctrl.last_reply);
-
- ctrl.last_command = ebuf;
-
- if (!Comm::IsConnOpen(ctrl.conn)) {
- debugs(9, 2, HERE << "cannot send to closing ctrl " << ctrl.conn);
- // TODO: assert(ctrl.closer != NULL);
- return;
- }
-
- typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
- AsyncCall::Pointer call = JobCallback(9, 5, Dialer, this, FtpStateData::ftpWriteCommandCallback);
- Comm::Write(ctrl.conn, ctrl.last_command, strlen(ctrl.last_command), call, NULL);
-
- scheduleReadControlReply(0);
-}
-
-void
-FtpStateData::ftpWriteCommandCallback(const CommIoCbParams &io)
-{
-
- debugs(9, 5, "ftpWriteCommandCallback: wrote " << io.size << " bytes");
-
- if (io.size > 0) {
- fd_bytes(io.fd, io.size, FD_WRITE);
- kb_incr(&(statCounter.server.all.kbytes_out), io.size);
- kb_incr(&(statCounter.server.ftp.kbytes_out), io.size);
- }
-
- if (io.flag == COMM_ERR_CLOSING)
- return;
-
- if (io.flag) {
- debugs(9, DBG_IMPORTANT, "ftpWriteCommandCallback: " << io.conn << ": " << xstrerr(io.xerrno));
- failed(ERR_WRITE_ERROR, io.xerrno);
- /* failed closes ctrl.conn and frees ftpState */
- return;
- }
-}
-
-wordlist *
-FtpStateData::ftpParseControlReply(char *buf, size_t len, int *codep, size_t *used)
-{
- char *s;
- char *sbuf;
- char *end;
- int usable;
- int complete = 0;
- wordlist *head = NULL;
- wordlist *list;
- wordlist **tail = &head;
- size_t offset;
- size_t linelen;
- int code = -1;
- debugs(9, 3, HERE);
- /*
- * We need a NULL-terminated buffer for scanning, ick
- */
- sbuf = (char *)xmalloc(len + 1);
- xstrncpy(sbuf, buf, len + 1);
- end = sbuf + len - 1;
-
- while (*end != '\r' && *end != '\n' && end > sbuf)
- --end;
-
- usable = end - sbuf;
-
- debugs(9, 3, HERE << "usable = " << usable);
-
- if (usable == 0) {
- debugs(9, 3, HERE << "didn't find end of line");
- safe_free(sbuf);
- return NULL;
- }
-
- debugs(9, 3, HERE << len << " bytes to play with");
- ++end;
- s = sbuf;
- s += strspn(s, crlf);
-
- for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
- if (complete)
- break;
-
- debugs(9, 5, HERE << "s = {" << s << "}");
-
- linelen = strcspn(s, crlf) + 1;
-
- if (linelen < 2)
- break;
-
- if (linelen > 3)
- complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
-
- if (complete)
- code = atoi(s);
-
- offset = 0;
-
- if (linelen > 3)
- if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
- offset = 4;
-
- list = new wordlist();
-
- list->key = (char *)xmalloc(linelen - offset);
-
- xstrncpy(list->key, s + offset, linelen - offset);
-
- /* trace the FTP communication chat at level 2 */
- debugs(9, 2, "ftp>> " << code << " " << list->key);
-
- *tail = list;
-
- tail = &list->next;
- }
-
- *used = (size_t) (s - sbuf);
- safe_free(sbuf);
-
- if (!complete)
- wordlistDestroy(&head);
-
- if (codep)
- *codep = code;
-
- return head;
-}
-
-/**
- * DPW 2007-04-23
- * Looks like there are no longer anymore callers that set
- * buffered_ok=1. Perhaps it can be removed at some point.
- */
-void
-FtpStateData::scheduleReadControlReply(int buffered_ok)
-{
- debugs(9, 3, HERE << ctrl.conn);
-
- if (buffered_ok && ctrl.offset > 0) {
- /* We've already read some reply data */
- handleControlReply();
- } else {
- /*
- * Cancel the timeout on the Data socket (if any) and
- * establish one on the control socket.
- */
- if (Comm::IsConnOpen(data.conn)) {
- commUnsetConnTimeout(data.conn);
- }
-
- typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, FtpStateData::ftpTimeout);
- commSetConnTimeout(ctrl.conn, Config.Timeout.read, timeoutCall);
-
- typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
- AsyncCall::Pointer reader = JobCallback(9, 5, Dialer, this, FtpStateData::ftpReadControlReply);
- comm_read(ctrl.conn, ctrl.buf + ctrl.offset, ctrl.size - ctrl.offset, reader);
- }
-}
-
-void FtpStateData::ftpReadControlReply(const CommIoCbParams &io)
-{
- debugs(9, 3, "ftpReadControlReply: FD " << io.fd << ", Read " << io.size << " bytes");
-
- if (io.size > 0) {
- kb_incr(&(statCounter.server.all.kbytes_in), io.size);
- kb_incr(&(statCounter.server.ftp.kbytes_in), io.size);
- }
-
- if (io.flag == COMM_ERR_CLOSING)
- return;
-
- if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- abortTransaction("entry aborted during control reply read");
- return;
- }
-
- assert(ctrl.offset < ctrl.size);
-
- if (io.flag == COMM_OK && io.size > 0) {
- fd_bytes(io.fd, io.size, FD_READ);
- }
-
- if (io.flag != COMM_OK) {
- debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
- "ftpReadControlReply: read error: " << xstrerr(io.xerrno));
-
- if (ignoreErrno(io.xerrno)) {
- scheduleReadControlReply(0);
- } else {
- failed(ERR_READ_ERROR, io.xerrno);
- /* failed closes ctrl.conn and frees ftpState */
- }
- return;
- }
-
- if (io.size == 0) {
- if (entry->store_status == STORE_PENDING) {
- failed(ERR_FTP_FAILURE, 0);
- /* failed closes ctrl.conn and frees ftpState */
- return;
- }
-
- /* XXX this may end up having to be serverComplete() .. */
- abortTransaction("zero control reply read");
- return;
- }
-
- unsigned int len =io.size + ctrl.offset;
- ctrl.offset = len;
- assert(len <= ctrl.size);
- handleControlReply();
-}
+/* ====================================================================== */
void
FtpStateData::handleControlReply()
{
- wordlist **W;
- size_t bytes_used = 0;
- wordlistDestroy(&ctrl.message);
- ctrl.message = ftpParseControlReply(ctrl.buf,
- ctrl.offset, &ctrl.replycode, &bytes_used);
-
- if (ctrl.message == NULL) {
- /* didn't get complete reply yet */
-
- if (ctrl.offset == ctrl.size) {
- ctrl.buf = (char *)memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size);
- }
-
- scheduleReadControlReply(0);
- return;
- } else if (ctrl.offset == bytes_used) {
- /* used it all up */
- ctrl.offset = 0;
- } else {
- /* Got some data past the complete reply */
- assert(bytes_used < ctrl.offset);
- ctrl.offset -= bytes_used;
- memmove(ctrl.buf, ctrl.buf + bytes_used, ctrl.offset);
- }
-
- /* Move the last line of the reply message to ctrl.last_reply */
- for (W = &ctrl.message; (*W)->next; W = &(*W)->next);
- safe_free(ctrl.last_reply);
-
- ctrl.last_reply = xstrdup((*W)->key);
+ Ftp::ServerStateData::handleControlReply();
+ if (ctrl.message == NULL)
+ return; // didn't get complete reply yet
- wordlistDestroy(W);
-
- /* Copy the rest of the message to cwd_message to be printed in
- * error messages
+ /* Copy the message except for the last line to cwd_message to be
+ * printed in error messages.
*/
- if (ctrl.message) {
- for (wordlist *w = ctrl.message; w; w = w->next) {
- cwd_message.append('\n');
- cwd_message.append(w->key);
- }
+ for (wordlist *w = ctrl.message; w && w->next; w = w->next) {
+ cwd_message.append('\n');
+ cwd_message.append(w->key);
}
- debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
-
FTP_SM_FUNCS[state] (this);
}
}
}
- ftpState->data.port = port;
-
- safe_free(ftpState->data.host);
- ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.conn->fd].ipaddr);
-
- safe_free(ftpState->ctrl.last_command);
-
- safe_free(ftpState->ctrl.last_reply);
-
- ftpState->ctrl.last_command = xstrdup("Connect to server data port");
+ ftpState->data.addr = fd_table[ftpState->ctrl.conn->fd].ipaddr;
+ ftpState->data.addr.SetPort(port);
- // Generate a new data channel descriptor to be opened.
- Comm::ConnectionPointer conn = new Comm::Connection;
- conn->local = ftpState->ctrl.conn->local;
- conn->local.SetPort(0);
- conn->remote = ftpState->ctrl.conn->remote;
- conn->remote.SetPort(port);
-
- debugs(9, 3, HERE << "connecting to " << conn->remote);
-
- ftpState->data.opener = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
- Comm::ConnOpener *cs = new Comm::ConnOpener(conn, ftpState->data.opener, Config.Timeout.connect);
- cs->setHost(ftpState->data.host);
- AsyncJob::Start(cs);
+ ftpState->connectDataChannel();
}
/** \ingroup ServerProtocolFTPInternal
*/
typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
- TimeoutDialer, ftpState, FtpStateData::ftpTimeout);
+ TimeoutDialer, ftpState, FtpStateData::timeout);
commSetConnTimeout(ftpState->ctrl.conn, Config.Timeout.connect, timeoutCall);
}
/*
* On rare occasions I'm seeing the entry get aborted after
- * ftpReadControlReply() and before here, probably when
+ * readControlReply() and before here, probably when
* trying to write to the client.
*/
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
static void
ftpReadPasv(FtpStateData * ftpState)
{
- int code = ftpState->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) {
- debugs(9, 2, "PASV not supported by remote end");
- ftpSendEPRT(ftpState);
- return;
- }
-
- /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
- /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
- debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
-
- buf = ftpState->ctrl.last_reply + strcspn(ftpState->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 " <<
- ftpState->ctrl.conn->remote << ": " <<
- ftpState->ctrl.last_reply);
-
- ftpSendEPRT(ftpState);
- return;
- }
-
- snprintf(ipaddr, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
-
- ipa_remote = ipaddr;
-
- if ( ipa_remote.IsAnyAddr() ) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ftpState->ctrl.conn->remote << ": " <<
- ftpState->ctrl.last_reply);
-
- ftpSendEPRT(ftpState);
- return;
- }
-
- port = ((p1 << 8) + p2);
-
- if (0 == port) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ftpState->ctrl.conn->remote << ": " <<
- ftpState->ctrl.last_reply);
-
+ if (ftpState->handlePasvReply())
+ ftpState->connectDataChannel();
+ else {
ftpSendEPRT(ftpState);
return;
}
-
- if (Config.Ftp.sanitycheck) {
- if (port < 1024) {
- debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
- ftpState->ctrl.conn->remote << ": " <<
- ftpState->ctrl.last_reply);
-
- ftpSendEPRT(ftpState);
- return;
- }
- }
-
- ftpState->data.port = port;
-
- safe_free(ftpState->data.host);
- if (Config.Ftp.sanitycheck)
- ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.conn->fd].ipaddr);
- else
- ftpState->data.host = xstrdup(ipaddr);
-
- safe_free(ftpState->ctrl.last_command);
-
- safe_free(ftpState->ctrl.last_reply);
-
- ftpState->ctrl.last_command = xstrdup("Connect to server data port");
-
- Comm::ConnectionPointer conn = new Comm::Connection;
- conn->local = ftpState->ctrl.conn->local;
- conn->local.SetPort(0);
- conn->remote = ipaddr;
- conn->remote.SetPort(port);
-
- debugs(9, 3, HERE << "connecting to " << conn->remote);
-
- ftpState->data.opener = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
- Comm::ConnOpener *cs = new Comm::ConnOpener(conn, ftpState->data.opener, Config.Timeout.connect);
- cs->setHost(ftpState->data.host);
- AsyncJob::Start(cs);
}
void
-FtpStateData::ftpPasvCallback(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
+FtpStateData::dataChannelConnected(const Comm::ConnectionPointer &conn, comm_err_t err, int xerrno)
{
- FtpStateData *ftpState = (FtpStateData *)data;
debugs(9, 3, HERE);
- ftpState->data.opener = NULL;
+ data.opener = NULL;
- if (status != COMM_OK) {
+ if (err != COMM_OK) {
debugs(9, 2, HERE << "Failed to connect. Retrying via another method.");
// ABORT on timeouts. server may be waiting on a broken TCP link.
- if (status == COMM_TIMEOUT)
- ftpState->writeCommand("ABOR");
+ if (err == COMM_TIMEOUT)
+ writeCommand("ABOR");
// try another connection attempt with some other method
- ftpSendPassive(ftpState);
+ ftpSendPassive(this);
return;
}
- ftpState->data.opened(conn, ftpState->dataCloser());
- ftpRestOrList(ftpState);
+ data.opened(conn, dataCloser());
+ ftpRestOrList(this);
}
/// \ingroup ServerProtocolFTPInternal
else
ftpState->data.close();
}
- safe_free(ftpState->data.host);
+ ftpState->data.addr.SetEmpty();
/*
* Set up a listen socket on the same local address as the
temp->local.SetPort(0);
}
- ftpState->listenForDataChannel(temp, ftpState->entry->url());
+ ftpState->listenForDataChannel(temp);
}
/// \ingroup ServerProtocolFTPInternal
/** On COMM_OK start using the accepted data socket and discard the temporary listen socket. */
data.close();
data.opened(io.conn, dataCloser());
- static char ntoapeer[MAX_IPSTRLEN];
- io.conn->remote.NtoA(ntoapeer,sizeof(ntoapeer));
- data.host = xstrdup(ntoapeer);
+ data.addr = io.conn->remote;
debugs(9, 3, HERE << "Connected data socket on " <<
io.conn << ". FD table says: " <<
} else if (code == 150) {
/* When client code is 150 with no data channel, Accept data channel. */
debugs(9, 3, "ftpReadStor: accepting data channel");
- listenForDataChannel(data.conn, data.host);
+ listenForDataChannel(data.conn);
} else {
debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
ftpFail(this);
} else if (code == 150) {
/* Accept data channel */
debugs(9, 3, HERE << "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
- ftpState->listenForDataChannel(ftpState->data.conn, ftpState->data.host);
+ ftpState->listenForDataChannel(ftpState->data.conn);
return;
} else if (!ftpState->flags.tried_nlst && code > 300) {
ftpSendNlst(ftpState);
ftpState->state = READING_DATA;
} else if (code == 150) {
/* Accept data channel */
- ftpState->listenForDataChannel(ftpState->data.conn, ftpState->data.host);
+ ftpState->listenForDataChannel(ftpState->data.conn);
} else if (code >= 300) {
if (!ftpState->flags.try_slash_hack) {
/* Try this as a directory missing trailing slash... */
failed(ERR_READ_ERROR, 0);
}
-/**
- * This will be called when the put write is completed
- */
-void
-FtpStateData::sentRequestBody(const CommIoCbParams &io)
-{
- if (io.size > 0)
- kb_incr(&(statCounter.server.ftp.kbytes_out), io.size);
- ServerStateData::sentRequestBody(io);
-}
-
/// \ingroup ServerProtocolFTPInternal
static void
ftpWriteTransferDone(FtpStateData * ftpState)
/* failed() closes ctrl.conn and frees this */
}
-void
-FtpStateData::failed(err_type error, int xerrno)
-{
- debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
- if (entry->isEmpty())
- failedErrorMessage(error, xerrno);
-
- serverComplete();
-}
-
-void
-FtpStateData::failedErrorMessage(err_type error, int xerrno)
+Http::StatusCode
+FtpStateData::failedHttpStatus(err_type &error)
{
- ErrorState *ftperr = NULL;
- const char *command, *reply;
-
- /* Translate FTP errors into HTTP errors */
- switch (error) {
-
- case ERR_NONE:
-
+ if (error == ERR_NONE) {
switch (state) {
-
case SENT_USER:
-
case SENT_PASS:
-
- if (ctrl.replycode > 500)
- if (password_url)
- ftperr = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request);
- else
- ftperr = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
-
- else if (ctrl.replycode == 421)
- ftperr = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request);
-
+ if (ctrl.replycode > 500) {
+ error = ERR_FTP_FORBIDDEN;
+ return password_url ? Http::scForbidden : Http::scUnauthorized;
+ } else if (ctrl.replycode == 421) {
+ error = ERR_FTP_UNAVAILABLE;
+ return Http::scServiceUnavailable;
+ }
break;
-
case SENT_CWD:
-
case SENT_RETR:
- if (ctrl.replycode == 550)
- ftperr = new ErrorState(ERR_FTP_NOT_FOUND, Http::scNotFound, fwd->request);
-
+ if (ctrl.replycode == 550) {
+ error = ERR_FTP_NOT_FOUND;
+ return Http::scNotFound;
+ }
break;
-
default:
break;
}
-
- break;
-
- case ERR_READ_TIMEOUT:
- ftperr = new ErrorState(error, Http::scGateway_Timeout, fwd->request);
- break;
-
- default:
- ftperr = new ErrorState(error, Http::scBadGateway, fwd->request);
- break;
}
-
- if (ftperr == NULL)
- ftperr = new ErrorState(ERR_FTP_FAILURE, Http::scBadGateway, fwd->request);
-
- ftperr->xerrno = xerrno;
-
- ftperr->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)
- ftperr->ftp.request = xstrdup(command);
-
- if (reply)
- ftperr->ftp.reply = xstrdup(reply);
-
- entry->replaceHttpReply( ftperr->BuildHttpReply() );
- delete ftperr;
+ return Ftp::ServerStateData::failedHttpStatus(error);
}
/// \ingroup ServerProtocolFTPInternal
addVirginReplyBody(dataToWrite, dataLength);
}
-/**
- * called after we wrote the last byte of the request body
- */
-void
-FtpStateData::doneSendingRequestBody()
-{
- ServerStateData::doneSendingRequestBody();
- debugs(9,3, HERE);
- dataComplete();
- /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
- * if transfer type is 'stream' call dataComplete()
- * otherwise leave open. (reschedule control channel read?)
- */
-}
-
/**
* A hack to ensure we do not double-complete on the forward entry.
*
ServerStateData::completeForwarding();
}
-/**
- * Close the FTP server connection(s). Used by serverComplete().
- */
-void
-FtpStateData::closeServer()
-{
- if (Comm::IsConnOpen(ctrl.conn)) {
- debugs(9,3, HERE << "closing FTP server FD " << ctrl.conn->fd << ", this " << this);
- fwd->unregister(ctrl.conn);
- ctrl.close();
- }
-
- if (Comm::IsConnOpen(data.conn)) {
- debugs(9,3, HERE << "closing FTP data FD " << data.conn->fd << ", this " << this);
- data.close();
- }
-
- debugs(9,3, HERE << "FTP ctrl and data connections closed. this " << this);
-}
-
-/**
- * Did we close all FTP server connection(s)?
- *
- \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
- \retval false Either control channel or data is still active.
- */
-bool
-FtpStateData::doneWithServer() const
-{
- return !Comm::IsConnOpen(ctrl.conn) && !Comm::IsConnOpen(data.conn);
-}
-
/**
* Have we lost the FTP server control channel?
*
return true;
}
-
-/**
- * Quickly abort the transaction
- *
- \todo destruction should be sufficient as the destructor should cleanup,
- * including canceling close handlers
- */
-void
-FtpStateData::abortTransaction(const char *reason)
-{
- debugs(9, 3, HERE << "aborting transaction for " << reason <<
- "; FD " << (ctrl.conn!=NULL?ctrl.conn->fd:-1) << ", Data FD " << (data.conn!=NULL?data.conn->fd:-1) << ", this " << this);
- if (Comm::IsConnOpen(ctrl.conn)) {
- ctrl.conn->close();
- return;
- }
-
- fwd->handleUnregisteredServerEnd();
- mustStop("FtpStateData::abortTransaction");
-}
-
-/// creates a data channel Comm close callback
-AsyncCall::Pointer
-FtpStateData::dataCloser()
-{
- typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
- return JobCallback(9, 5, Dialer, this, FtpStateData::dataClosed);
-}
-
-/// configures the channel with a descriptor and registers a close handler
-void
-FtpChannel::opened(const Comm::ConnectionPointer &newConn, const AsyncCall::Pointer &aCloser)
-{
- assert(!Comm::IsConnOpen(conn));
- assert(closer == NULL);
-
- assert(Comm::IsConnOpen(newConn));
- assert(aCloser != NULL);
-
- conn = newConn;
- closer = aCloser;
- comm_add_close_handler(conn->fd, closer);
-}
-
-/// planned close: removes the close handler and calls comm_close
-void
-FtpChannel::close()
-{
- // channels with active listeners will be closed when the listener handler dies.
- if (Comm::IsConnOpen(conn)) {
- comm_remove_close_handler(conn->fd, closer);
- conn->close(); // we do not expect to be called back
- }
- clear();
-}
-
-void
-FtpChannel::clear()
-{
- conn = NULL;
- closer = NULL;
-}
"None", // fdnNone
"HTTP Socket", // fdnHttpSocket
"HTTPS Socket", // fdnHttpsSocket
+ "FTP Socket", // fdnFtpSocket
#if SQUID_SNMP
"Incoming SNMP Socket", // fdnInSnmpSocket
"Outgoing SNMP Socket", // fdnOutSnmpSocket
/// We cannot send char* FD notes to other processes. Pass int IDs and convert.
/// fd_note() label ID
-typedef enum { fdnNone, fdnHttpSocket, fdnHttpsSocket,
+typedef enum { fdnNone, fdnHttpSocket, fdnHttpsSocket, fdnFtpSocket,
#if SQUID_SNMP
fdnInSnmpSocket, fdnOutSnmpSocket,
#endif
#endif
}
if (IamWorkerProcess()) {
- clientHttpConnectionsClose();
+ clientConnectionsClose();
icpConnectionShutdown();
#if USE_HTCP
htcpSocketShutdown();
{
// Release the main ports as early as possible
- // clear both http_port and https_port lists.
- clientHttpConnectionsClose();
+ // clear http_port, https_port and ftp_port lists
+ clientConnectionsClose();
// clear icp_port's
icpClosePorts();