/*
- * DEBUG: section 09 File Transfer Protocol (FTP)
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
*
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
*/
-#include "squid.h"
+/* DEBUG: section 09 File Transfer Protocol (FTP) */
+#include "squid.h"
#include "acl/FilledChecklist.h"
-#include "clients/FtpClient.h"
-#include "Mem.h"
-#include "SquidConfig.h"
-#include "StatCounters.h"
#include "client_side.h"
+#include "clients/FtpClient.h"
#include "comm/ConnOpener.h"
#include "comm/Read.h"
#include "comm/TcpAcceptor.h"
#include "errorpage.h"
#include "fd.h"
#include "ftp/Parsing.h"
+#include "http/Stream.h"
#include "ip/tools.h"
+#include "SquidConfig.h"
#include "SquidString.h"
+#include "StatCounters.h"
#include "tools.h"
#include "wordlist.h"
+
#include <set>
-namespace Ftp {
+namespace Ftp
+{
const char *const crlf = "\r\n";
-/// \ingroup ServerProtocolFTPInternal
static char *
escapeIAC(const char *buf)
{
/// configures the channel with a descriptor and registers a close handler
void
Ftp::Channel::opened(const Comm::ConnectionPointer &newConn,
- const AsyncCall::Pointer &aCloser)
+ const AsyncCall::Pointer &aCloser)
{
assert(!Comm::IsConnOpen(conn));
assert(closer == NULL);
/* Ftp::CtrlChannel */
Ftp::CtrlChannel::CtrlChannel():
- buf(NULL),
- size(0),
- offset(0),
- message(NULL),
- last_command(NULL),
- last_reply(NULL),
- replycode(0)
+ buf(NULL),
+ size(0),
+ offset(0),
+ message(NULL),
+ last_command(NULL),
+ last_reply(NULL),
+ replycode(0)
{
buf = static_cast<char*>(memAllocBuf(4096, &size));
}
/* Ftp::DataChannel */
Ftp::DataChannel::DataChannel():
- readBuf(NULL),
- host(NULL),
- port(0),
- read_pending(false)
+ readBuf(NULL),
+ host(NULL),
+ port(0),
+ read_pending(false)
{
}
void
Ftp::DataChannel::addr(const Ip::Address &import)
{
- static char addrBuf[MAX_IPSTRLEN];
- import.toStr(addrBuf, sizeof(addrBuf));
- xfree(host);
- host = xstrdup(addrBuf);
- port = import.port();
+ static char addrBuf[MAX_IPSTRLEN];
+ import.toStr(addrBuf, sizeof(addrBuf));
+ xfree(host);
+ host = xstrdup(addrBuf);
+ port = import.port();
}
/* Ftp::Client */
Ftp::Client::Client(FwdState *fwdState):
- AsyncJob("Ftp::Client"),
- ::ServerStateData(fwdState),
- ctrl(),
- data(),
- state(BEGIN),
- old_request(NULL),
- old_reply(NULL),
- shortenReadTimeout(false)
+ AsyncJob("Ftp::Client"),
+ ::Client(fwdState),
+ ctrl(),
+ data(),
+ state(BEGIN),
+ old_request(NULL),
+ old_reply(NULL),
+ shortenReadTimeout(false)
{
++statCounter.server.all.requests;
++statCounter.server.ftp.requests;
typedef CommCbMemFunT<Client, CommCloseCbParams> Dialer;
const AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this,
- Ftp::Client::ctrlClosed);
+ Ftp::Client::ctrlClosed);
ctrl.opened(fwdState->serverConnection(), closer);
}
}
void
-Ftp::Client::failed(err_type error, int xerrno)
+Ftp::Client::failed(err_type error, int xerrno, ErrorState *err)
{
debugs(9, 3, "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
const char *command, *reply;
- const Http::StatusCode httpStatus = failedHttpStatus(error);
- ErrorState *const ftperr = new ErrorState(error, httpStatus, fwd->request);
+ ErrorState *ftperr;
+
+ if (err) {
+ debugs(9, 6, "error=" << err->type << ", code=" << xerrno <<
+ ", status=" << err->httpStatus);
+ error = err->type;
+ ftperr = err;
+ } else {
+ Http::StatusCode httpStatus = failedHttpStatus(error);
+ ftperr = new ErrorState(error, httpStatus, fwd->request);
+ }
+
ftperr->xerrno = xerrno;
ftperr->ftp.server_msg = ctrl.message;
if (reply)
ftperr->ftp.reply = xstrdup(reply);
- fwd->request->detailError(error, xerrno);
- fwd->fail(ftperr);
-
- closeServer(); // we failed, so no serverComplete()
+ if (!err) {
+ fwd->request->detailError(error, xerrno);
+ fwd->fail(ftperr);
+ closeServer(); // we failed, so no serverComplete()
+ }
}
Http::StatusCode
if (error == ERR_NONE)
error = ERR_FTP_FAILURE;
return error == ERR_READ_TIMEOUT ? Http::scGatewayTimeout :
- Http::scBadGateway;
+ Http::scBadGateway;
}
/**
/* We've already read some reply data */
handleControlReply();
} else {
+
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 3, "cannot read without ctrl " << ctrl.conn);
+ return;
+ }
/*
* Cancel the timeout on the Data socket (if any) and
* establish one on the control socket.
debugs(9, 3, "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);
+ statCounter.server.all.kbytes_in += io.size;
+ 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;
+ if (abortOnData("entry aborted during control reply read"))
+ return;
}
assert(ctrl.offset < ctrl.size);
}
/* XXX this may end up having to be serverComplete() .. */
- abortTransaction("zero control reply read");
+ abortAll("zero control reply read");
return;
}
char *buf;
debugs(9, 3, status());
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 5, "The control connection to the remote end is closed");
+ return false;
+ }
+
if (code != 227) {
debugs(9, 2, "PASV not supported by remote end");
return false;
char *buf;
debugs(9, 3, status());
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 5, "The control connection to the remote end is closed");
+ return false;
+ }
+
if (code != 229 && code != 522) {
if (code == 200) {
/* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
debugs(9, DBG_IMPORTANT, "WARNING: Server at " << ctrl.conn->remote << " sent unknown protocol negotiation hint: " << buf);
return sendPassive();
}
+ /* coverity[unreachable] */
+ /* safeguard against possible future bugs in above conditions */
failed(ERR_FTP_FAILURE, 0);
return false;
}
/* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
/* Which can be used by EITHER protocol. */
debugs(9, 3, "Listening for FTP data connection on port" << comm_local_port(data.conn->fd) << " or port?" << data.conn->local.port());
- mb.Printf("EPRT |%d|%s|%d|%s",
- ( data.conn->local.isIPv6() ? 2 : 1 ),
- data.conn->local.toStr(buf,MAX_IPSTRLEN),
- comm_local_port(data.conn->fd), Ftp::crlf );
+ mb.appendf("EPRT |%d|%s|%d|%s",
+ ( data.conn->local.isIPv6() ? 2 : 1 ),
+ data.conn->local.toStr(buf,MAX_IPSTRLEN),
+ comm_local_port(data.conn->fd), Ftp::crlf );
state = SENT_EPRT;
writeCommand(mb.content());
return false;
}
-
/// Closes any old FTP-Data connection which may exist. */
data.close();
case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
if (ctrl.conn->local.isIPv6()) {
debugs(9, 5, "FTP Channel is IPv6 (" << ctrl.conn->remote << ") attempting EPSV 2 after EPSV ALL has failed.");
- mb.Printf("EPSV 2%s", Ftp::crlf);
+ mb.appendf("EPSV 2%s", Ftp::crlf);
state = SENT_EPSV_2;
break;
}
- // else fall through to skip EPSV 2
+ // else fall through to skip EPSV 2
case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
if (ctrl.conn->local.isIPv4()) {
debugs(9, 5, "FTP Channel is IPv4 (" << ctrl.conn->remote << ") attempting EPSV 1 after EPSV ALL has failed.");
- mb.Printf("EPSV 1%s", Ftp::crlf);
+ mb.appendf("EPSV 1%s", Ftp::crlf);
state = SENT_EPSV_1;
break;
} else if (Config.Ftp.epsv_all) {
failed(ERR_FTP_FAILURE, 0);
return false;
}
- // else fall through to skip EPSV 1
+ // else fall through to skip EPSV 1
case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead.");
- mb.Printf("PASV%s", Ftp::crlf);
+ mb.appendf("PASV%s", Ftp::crlf);
state = SENT_PASV;
break;
}
if (!doEpsv) {
debugs(9, 5, "EPSV support manually disabled. Sending PASV for FTP Channel (" << ctrl.conn->remote <<")");
- mb.Printf("PASV%s", Ftp::crlf);
+ mb.appendf("PASV%s", Ftp::crlf);
state = SENT_PASV;
} else if (Config.Ftp.epsv_all) {
debugs(9, 5, "EPSV ALL manually enabled. Attempting with FTP Channel (" << ctrl.conn->remote <<")");
- mb.Printf("EPSV ALL%s", Ftp::crlf);
+ mb.appendf("EPSV ALL%s", Ftp::crlf);
state = SENT_EPSV_ALL;
} else {
if (ctrl.conn->local.isIPv6()) {
debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << "). Sending default EPSV 2");
- mb.Printf("EPSV 2%s", Ftp::crlf);
+ mb.appendf("EPSV 2%s", Ftp::crlf);
state = SENT_EPSV_2;
}
if (ctrl.conn->local.isIPv4()) {
debugs(9, 5, "Channel (" << ctrl.conn->remote <<"). Sending default EPSV 1");
- mb.Printf("EPSV 1%s", Ftp::crlf);
+ mb.appendf("EPSV 1%s", Ftp::crlf);
state = SENT_EPSV_1;
}
}
return true;
}
-
void
Ftp::Client::connectDataChannel()
{
+ if (!Comm::IsConnOpen(ctrl.conn)) {
+ debugs(9, 5, "The control connection to the remote end is closed");
+ return;
+ }
+
safe_free(ctrl.last_command);
safe_free(ctrl.last_reply);
debugs(9, 3, "connecting to " << conn->remote);
- data.opener = commCbCall(9, 3, "Ftp::Client::dataChannelConnected",
- CommConnectCbPtrFun(Ftp::Client::dataChannelConnected, this));
+ typedef CommCbMemFunT<Client, CommConnectCbParams> Dialer;
+ data.opener = JobCallback(9, 3, Dialer, this, Ftp::Client::dataChannelConnected);
Comm::ConnOpener *cs = new Comm::ConnOpener(conn, data.opener, Config.Timeout.connect);
cs->setHost(data.host);
AsyncJob::Start(cs);
}
-void
-Ftp::Client::dataChannelConnected(const Comm::ConnectionPointer &conn, Comm::Flag status, int xerrno, void *data)
-{
- Client *ftpState = static_cast<Client *>(data);
- ftpState->dataChannelConnected(conn, status, xerrno);
-}
-
bool
Ftp::Client::openListenSocket()
{
/// handler called by Comm when FTP data channel is closed unexpectedly
void
-Ftp::Client::dataClosed(const CommCloseCbParams &io)
+Ftp::Client::dataClosed(const CommCloseCbParams &)
{
debugs(9, 4, status());
if (data.listenConn != NULL) {
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);
+ statCounter.server.all.kbytes_out += io.size;
+ statCounter.server.ftp.kbytes_out += io.size;
}
if (io.flag == Comm::ERR_CLOSING)
/// handler called by Comm when FTP control channel is closed unexpectedly
void
-Ftp::Client::ctrlClosed(const CommCloseCbParams &io)
+Ftp::Client::ctrlClosed(const CommCloseCbParams &)
{
debugs(9, 4, status());
ctrl.clear();
+ doneWithFwd = "ctrlClosed()"; // assume FwdState is monitoring too
mustStop("Ftp::Client::ctrlClosed");
}
const int read_sz = replyBodySpace(*data.readBuf, 0);
- debugs(11,9, "FTP may read up to " << read_sz << " bytes");
+ debugs(9, 9, "FTP may read up to " << read_sz << " bytes");
if (read_sz < 2) // see http.cc
return;
debugs(9, 3, "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);
+ statCounter.server.all.kbytes_in += io.size;
+ statCounter.server.ftp.kbytes_in += io.size;
}
if (io.flag == Comm::ERR_CLOSING)
assert(io.fd == data.conn->fd);
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- abortTransaction("entry aborted during dataRead");
+ abortOnData("entry aborted during dataRead");
return;
}
scheduleReadControlReply(1);
}
-/**
- * Quickly abort the transaction
- *
- \todo destruction should be sufficient as the destructor should cleanup,
- * including canceling close handlers
- */
void
-Ftp::Client::abortTransaction(const char *reason)
+Ftp::Client::abortAll(const char *reason)
{
debugs(9, 3, "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("Ftp::Client::abortTransaction");
+ mustStop(reason);
}
/**
typedef CommCbMemFunT<Client, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this,
- Ftp::Client::timeout);
+ Ftp::Client::timeout);
commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
}
Ftp::Client::sentRequestBody(const CommIoCbParams &io)
{
if (io.size > 0)
- kb_incr(&(statCounter.server.ftp.kbytes_out), io.size);
- ::ServerStateData::sentRequestBody(io);
+ statCounter.server.ftp.kbytes_out += io.size;
+ ::Client::sentRequestBody(io);
}
/**
void
Ftp::Client::doneSendingRequestBody()
{
- ::ServerStateData::doneSendingRequestBody();
+ ::Client::doneSendingRequestBody();
debugs(9, 3, status());
dataComplete();
/* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
}
}; // namespace Ftp
+