]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/clients/FtpClient.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / clients / FtpClient.cc
index e42759665b266b9829498c3e33f66fc362c6a27b..aa8e1ca5428ca66466b95c26f4818b5d0cd2db5a 100644 (file)
@@ -1,16 +1,17 @@
 /*
- * 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)
 {
@@ -64,7 +69,7 @@ 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);
@@ -109,13 +114,13 @@ Ftp::Channel::clear()
 /* 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));
 }
@@ -132,10 +137,10 @@ Ftp::CtrlChannel::~CtrlChannel()
 /* Ftp::DataChannel */
 
 Ftp::DataChannel::DataChannel():
-        readBuf(NULL),
-        host(NULL),
-        port(0),
-        read_pending(false)
+    readBuf(NULL),
+    host(NULL),
+    port(0),
+    read_pending(false)
 {
 }
 
@@ -147,24 +152,24 @@ Ftp::DataChannel::~DataChannel()
 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;
@@ -173,7 +178,7 @@ Ftp::Client::Client(FwdState *fwdState):
 
     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);
 }
 
@@ -238,13 +243,23 @@ Ftp::Client::doneWithServer() const
 }
 
 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;
@@ -269,10 +284,11 @@ Ftp::Client::failed(err_type error, int xerrno)
     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
@@ -281,7 +297,7 @@ Ftp::Client::failedHttpStatus(err_type &error)
     if (error == ERR_NONE)
         error = ERR_FTP_FAILURE;
     return error == ERR_READ_TIMEOUT ? Http::scGatewayTimeout :
-        Http::scBadGateway;
+           Http::scBadGateway;
 }
 
 /**
@@ -298,6 +314,11 @@ Ftp::Client::scheduleReadControlReply(int buffered_ok)
         /* 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.
@@ -327,16 +348,16 @@ Ftp::Client::readControlReply(const CommIoCbParams &io)
     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);
@@ -366,7 +387,7 @@ Ftp::Client::readControlReply(const CommIoCbParams &io)
         }
 
         /* XXX this may end up having to be serverComplete() .. */
-        abortTransaction("zero control reply read");
+        abortAll("zero control reply read");
         return;
     }
 
@@ -421,6 +442,11 @@ Ftp::Client::handlePasvReply(Ip::Address &srvAddr)
     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;
@@ -452,6 +478,11 @@ Ftp::Client::handleEpsvReply(Ip::Address &remoteAddr)
     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) */
@@ -505,6 +536,8 @@ Ftp::Client::handleEpsvReply(Ip::Address &remoteAddr)
             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;
     }
@@ -583,10 +616,10 @@ Ftp::Client::sendEprt()
     /* 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());
@@ -618,7 +651,6 @@ Ftp::Client::sendPassive()
         return false;
     }
 
-
     /// Closes any old FTP-Data connection which may exist. */
     data.close();
 
@@ -644,16 +676,16 @@ Ftp::Client::sendPassive()
     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) {
@@ -661,11 +693,11 @@ Ftp::Client::sendPassive()
             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;
 
@@ -677,21 +709,21 @@ Ftp::Client::sendPassive()
         }
         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;
             }
         }
@@ -710,10 +742,14 @@ Ftp::Client::sendPassive()
     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);
@@ -730,20 +766,13 @@ Ftp::Client::connectDataChannel()
 
     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()
 {
@@ -760,7 +789,7 @@ Ftp::Client::dataCloser()
 
 /// 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) {
@@ -811,8 +840,8 @@ Ftp::Client::writeCommandCallback(const CommIoCbParams &io)
 
     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)
@@ -828,10 +857,11 @@ Ftp::Client::writeCommandCallback(const CommIoCbParams &io)
 
 /// 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");
 }
 
@@ -867,7 +897,7 @@ Ftp::Client::maybeReadVirginBody()
 
     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;
@@ -897,8 +927,8 @@ Ftp::Client::dataRead(const CommIoCbParams &io)
     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)
@@ -907,7 +937,7 @@ Ftp::Client::dataRead(const CommIoCbParams &io)
     assert(io.fd == data.conn->fd);
 
     if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
-        abortTransaction("entry aborted during dataRead");
+        abortOnData("entry aborted during dataRead");
         return;
     }
 
@@ -984,24 +1014,12 @@ Ftp::Client::dataComplete()
     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);
 }
 
 /**
@@ -1015,7 +1033,7 @@ Ftp::Client::switchTimeoutToDataChannel()
 
     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);
 }
 
@@ -1023,8 +1041,8 @@ void
 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);
 }
 
 /**
@@ -1033,7 +1051,7 @@ Ftp::Client::sentRequestBody(const CommIoCbParams &io)
 void
 Ftp::Client::doneSendingRequestBody()
 {
-    ::ServerStateData::doneSendingRequestBody();
+    ::Client::doneSendingRequestBody();
     debugs(9, 3, status());
     dataComplete();
     /* NP: RFC 959  3.3.  DATA CONNECTION MANAGEMENT
@@ -1133,3 +1151,4 @@ Ftp::Client::parseControlReply(size_t &bytesUsed)
 }
 
 }; // namespace Ftp
+