]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/clients/FtpRelay.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / clients / FtpRelay.cc
index e4f7b7b75734637917cd6bd90b982eb505fbed8a..c702ad53ae15772cb69924b950079166e2d7367d 100644 (file)
@@ -1,31 +1,39 @@
 /*
- * 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 "anyp/PortCfg.h"
 #include "client_side.h"
 #include "clients/forward.h"
 #include "clients/FtpClient.h"
+#include "ftp/Elements.h"
 #include "ftp/Parsing.h"
+#include "http/Stream.h"
 #include "HttpHdrCc.h"
 #include "HttpRequest.h"
-#include "SBuf.h"
+#include "sbuf/SBuf.h"
 #include "servers/FtpServer.h"
-#include "Server.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "wordlist.h"
 
-namespace Ftp {
+namespace Ftp
+{
 
 /// An FTP client receiving native FTP commands from our FTP server
 /// (Ftp::Server), forwarding them to the next FTP hop,
 /// and then relaying FTP replies back to our FTP server.
 class Relay: public Ftp::Client
 {
+    CBDATA_CLASS(Relay);
+
 public:
     explicit Relay(FwdState *const fwdState);
     virtual ~Relay();
@@ -37,24 +45,26 @@ protected:
     void serverState(const Ftp::ServerState newState);
 
     /* Ftp::Client API */
-    virtual void failed(err_type error = ERR_NONE, int xerrno = 0);
-    virtual void dataChannelConnected(const Comm::ConnectionPointer &conn, Comm::Flag err, int xerrno);
+    virtual void failed(err_type error = ERR_NONE, int xerrno = 0, ErrorState *ftperr = nullptr);
+    virtual void dataChannelConnected(const CommConnectCbParams &io);
 
-    /* ServerStateData API */
+    /* Client API */
     virtual void serverComplete();
     virtual void handleControlReply();
     virtual void processReplyBody();
     virtual void handleRequestBodyProducerAborted();
     virtual bool mayReadVirginReplyBody() const;
     virtual void completeForwarding();
+    virtual bool abortOnData(const char *reason);
 
     /* AsyncJob API */
     virtual void start();
+    virtual void swanSong();
 
     void forwardReply();
     void forwardError(err_type error = ERR_NONE, int xerrno = 0);
     void failedErrorMessage(err_type error, int xerrno);
-    HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int clen = 0);
+    HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0);
     void handleDataRequest();
     void startDataDownload();
     void startDataUpload();
@@ -82,16 +92,23 @@ protected:
 
     void scheduleReadControlReply();
 
+    /// Inform Ftp::Server that we are done if originWaitInProgress
+    void stopOriginWait(int code);
+
+    static void abort(void *d); // TODO: Capitalize this and FwdState::abort().
+
     bool forwardingCompleted; ///< completeForwarding() has been called
 
+    /// whether we are between Ftp::Server::startWaitingForOrigin() and
+    /// Ftp::Server::stopWaitingForOrigin() calls
+    bool originWaitInProgress;
+
     struct {
         wordlist *message; ///< reply message, one  wordlist entry per message line
         char *lastCommand; ///< the command caused the reply
         char *lastReply; ///< last line of reply: reply status plus message
         int replyCode; ///< the reply status
     } savedReply; ///< set and delayed while we are tracking using PWD
-
-    CBDATA_CLASS2(Relay);
 };
 
 } // namespace Ftp
@@ -130,9 +147,11 @@ const Ftp::Relay::SM_FUNC Ftp::Relay::SM_FUNCS[] = {
 };
 
 Ftp::Relay::Relay(FwdState *const fwdState):
-        AsyncJob("Ftp::Relay"),
-        Ftp::Client(fwdState),
-        forwardingCompleted(false)
+    AsyncJob("Ftp::Relay"),
+    Ftp::Client(fwdState),
+    thePreliminaryCb(NULL),
+    forwardingCompleted(false),
+    originWaitInProgress(false)
 {
     savedReply.message = NULL;
     savedReply.lastCommand = NULL;
@@ -142,11 +161,13 @@ Ftp::Relay::Relay(FwdState *const fwdState):
     // Nothing we can do at request creation time can mark the response as
     // uncachable, unfortunately. This prevents "found KEY_PRIVATE" WARNINGs.
     entry->releaseRequest();
+    // TODO: Convert registerAbort() to use AsyncCall
+    entry->registerAbort(Ftp::Relay::abort, this);
 }
 
 Ftp::Relay::~Relay()
 {
-    closeServer(); // TODO: move to Server.cc?
+    closeServer(); // TODO: move to clients/Client.cc?
     if (savedReply.message)
         wordlistDestroy(&savedReply.message);
 
@@ -159,19 +180,27 @@ Ftp::Relay::start()
 {
     if (!master().clientReadGreeting)
         Ftp::Client::start();
-    else
-    if (serverState() == fssHandleDataRequest ||
-        serverState() == fssHandleUploadRequest)
+    else if (serverState() == fssHandleDataRequest ||
+             serverState() == fssHandleUploadRequest)
         handleDataRequest();
     else
         sendCommand();
 }
 
+void
+Ftp::Relay::swanSong()
+{
+    stopOriginWait(0);
+    Ftp::Client::swanSong();
+}
+
 /// Keep control connection for future requests, after we are done with it.
 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
 void
 Ftp::Relay::serverComplete()
 {
+    stopOriginWait(ctrl.replycode);
+
     CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
     if (mgr.valid()) {
         if (Comm::IsConnOpen(ctrl.conn)) {
@@ -200,7 +229,7 @@ Ftp::Relay::updateMaster()
     CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
     if (mgr.valid()) {
         if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get()))
-            return srv->master;
+            return *srv->master;
     }
     // this code will not be necessary once the master is inside MasterXaction
     debugs(9, 3, "our server side is gone: " << mgr);
@@ -244,7 +273,7 @@ Ftp::Relay::completeForwarding()
 }
 
 void
-Ftp::Relay::failed(err_type error, int xerrno)
+Ftp::Relay::failed(err_type error, int xerrno, ErrorState *ftpErr)
 {
     if (!doneWithServer())
         serverState(fssError);
@@ -253,7 +282,7 @@ Ftp::Relay::failed(err_type error, int xerrno)
     if (entry->isEmpty())
         failedErrorMessage(error, xerrno); // as a reply
 
-    Ftp::Client::failed(error, xerrno);
+    Ftp::Client::failed(error, xerrno, ftpErr);
 }
 
 void
@@ -276,7 +305,14 @@ Ftp::Relay::processReplyBody()
          * probably was aborted because content length exceeds one
          * of the maximum size limits.
          */
-        abortTransaction("entry aborted after calling appendSuccessHeader()");
+        abortOnData("entry aborted after calling appendSuccessHeader()");
+        return;
+    }
+
+    if (master().userDataDone) {
+        // Squid-to-client data transfer done. Abort data transfer on our
+        // side to allow new commands from ftp client
+        abortOnData("Squid-to-client data connection is closed");
         return;
     }
 
@@ -322,7 +358,7 @@ Ftp::Relay::handleControlReply()
 void
 Ftp::Relay::handleRequestBodyProducerAborted()
 {
-    ::ServerStateData::handleRequestBodyProducerAborted();
+    ::Client::handleRequestBodyProducerAborted();
 
     failed(ERR_READ_ERROR);
 }
@@ -341,6 +377,7 @@ Ftp::Relay::forwardReply()
     EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
 
     HttpReply *const reply = createHttpReply(Http::scNoContent);
+    reply->sources |= HttpMsg::srcFtp;
 
     setVirginReply(reply);
     adaptOrFinalizeReply();
@@ -362,7 +399,7 @@ Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb)
     // the Sink will use this to call us back after writing 1xx to the client
     typedef NullaryMemFunT<Relay> CbDialer;
     const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this,
-        Ftp::Relay::proceedAfterPreliminaryReply);
+                                    Ftp::Relay::proceedAfterPreliminaryReply);
 
     CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData,
                  ConnStateData::sendControlMsg, HttpControlMsg(reply, call));
@@ -386,31 +423,14 @@ Ftp::Relay::forwardError(err_type error, int xerrno)
 }
 
 HttpReply *
-Ftp::Relay::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);
-
+Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen)
+{
+    HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen);
     if (ctrl.message) {
         for (wordlist *W = ctrl.message; W && W->next; W = W->next)
-            header.putStr(HDR_FTP_PRE, httpHeaderQuoteString(W->key).c_str());
+            reply->header.putStr(Http::HdrType::FTP_PRE, httpHeaderQuoteString(W->key).c_str());
+        // no hdrCacheInit() is needed for after Http::HdrType::FTP_PRE addition
     }
-    if (ctrl.replycode > 0)
-        header.putInt(HDR_FTP_STATUS, ctrl.replycode);
-    if (ctrl.last_reply)
-        header.putStr(HDR_FTP_REASON, ctrl.last_reply);
-
-    reply->hdrCacheInit();
-
     return reply;
 }
 
@@ -430,6 +450,8 @@ Ftp::Relay::startDataDownload()
            " (" << data.conn->local << ")");
 
     HttpReply *const reply = createHttpReply(Http::scOkay, -1);
+    reply->sources |= HttpMsg::srcFtp;
+
     EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
     setVirginReply(reply);
     adaptOrFinalizeReply();
@@ -465,9 +487,9 @@ Ftp::Relay::readGreeting()
         if (serverState() == fssBegin)
             serverState(fssConnected);
 
-        // Do not forward server greeting to the client because our client
-        // side code has greeted the client already. Also, a greeting may
-        // confuse a client that has changed the gateway destination mid-air.
+        // Do not forward server greeting to the user because our FTP Server
+        // has greeted the user already. Also, an original origin greeting may
+        // confuse a user that has changed the origin mid-air.
 
         start();
         break;
@@ -485,16 +507,16 @@ Ftp::Relay::readGreeting()
 void
 Ftp::Relay::sendCommand()
 {
-    if (!fwd->request->header.has(HDR_FTP_COMMAND)) {
-        abortTransaction("Internal error: FTP gateway request with no command");
+    if (!fwd->request->header.has(Http::HdrType::FTP_COMMAND)) {
+        abortAll("Internal error: FTP relay request with no command");
         return;
     }
 
     HttpHeader &header = fwd->request->header;
-    assert(header.has(HDR_FTP_COMMAND));
-    const String &cmd = header.findEntry(HDR_FTP_COMMAND)->value;
-    assert(header.has(HDR_FTP_ARGUMENTS));
-    const String &params = header.findEntry(HDR_FTP_ARGUMENTS)->value;
+    assert(header.has(Http::HdrType::FTP_COMMAND));
+    const String &cmd = header.findEntry(Http::HdrType::FTP_COMMAND)->value;
+    assert(header.has(Http::HdrType::FTP_ARGUMENTS));
+    const String &params = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
 
     if (params.size() > 0)
         debugs(9, 5, "command: " << cmd << ", parameters: " << params);
@@ -502,9 +524,9 @@ Ftp::Relay::sendCommand()
         debugs(9, 5, "command: " << cmd << ", no parameters");
 
     if (serverState() == fssHandlePasv ||
-        serverState() == fssHandleEpsv ||
-        serverState() == fssHandleEprt ||
-        serverState() == fssHandlePort) {
+            serverState() == fssHandleEpsv ||
+            serverState() == fssHandleEprt ||
+            serverState() == fssHandlePort) {
         sendPassive();
         return;
     }
@@ -526,6 +548,19 @@ Ftp::Relay::sendCommand()
         serverState() == fssConnected ? SENT_USER :
         serverState() == fssHandlePass ? SENT_PASS :
         SENT_COMMAND;
+
+    if (state == SENT_DATA_REQUEST) {
+        CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
+        if (mgr.valid()) {
+            if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) {
+                typedef NullaryMemFunT<Ftp::Server> CbDialer;
+                AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, srv,
+                                                      Ftp::Server::startWaitingForOrigin);
+                ScheduleCallHere(call);
+                originWaitInProgress = true;
+            }
+        }
+    }
 }
 
 void
@@ -589,6 +624,8 @@ Ftp::Relay::readDataReply()
     if (ctrl.replycode == 125 || ctrl.replycode == 150) {
         if (serverState() == fssHandleDataRequest)
             forwardPreliminaryReply(&Ftp::Relay::startDataDownload);
+        else if (fwd->request->forcedBodyContinuation /*&& serverState() == fssHandleUploadRequest*/)
+            startDataUpload();
         else // serverState() == fssHandleUploadRequest
             forwardPreliminaryReply(&Ftp::Relay::startDataUpload);
     } else
@@ -682,24 +719,26 @@ Ftp::Relay::readTransferDoneReply()
                " after reading response data");
     }
 
+    debugs(9, 2, "Complete data downloading");
+
     serverComplete();
 }
 
 void
-Ftp::Relay::dataChannelConnected(const Comm::ConnectionPointer &conn, Comm::Flag err, int xerrno)
+Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io)
 {
     debugs(9, 3, status());
     data.opener = NULL;
 
-    if (err != Comm::OK) {
+    if (io.flag != Comm::OK) {
         debugs(9, 2, "failed to connect FTP server data channel");
-        forwardError(ERR_CONNECT_FAIL, xerrno);
+        forwardError(ERR_CONNECT_FAIL, io.xerrno);
         return;
     }
 
-    debugs(9, 2, "connected FTP server data channel: " << conn);
+    debugs(9, 2, "connected FTP server data channel: " << io.conn);
 
-    data.opened(conn, dataCloser());
+    data.opened(io.conn, dataCloser());
 
     sendCommand();
 }
@@ -710,8 +749,56 @@ Ftp::Relay::scheduleReadControlReply()
     Ftp::Client::scheduleReadControlReply(0);
 }
 
+bool
+Ftp::Relay::abortOnData(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);
+    // this method is only called to handle data connection problems
+    // the control connection should keep going
+
+#if USE_ADAPTATION
+    if (adaptedBodySource != NULL)
+        stopConsumingFrom(adaptedBodySource);
+#endif
+
+    if (Comm::IsConnOpen(data.conn))
+        dataComplete();
+
+    return !Comm::IsConnOpen(ctrl.conn);
+}
+
+void
+Ftp::Relay::stopOriginWait(int code)
+{
+    if (originWaitInProgress) {
+        CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
+        if (mgr.valid()) {
+            if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) {
+                typedef UnaryMemFunT<Ftp::Server, int> CbDialer;
+                AsyncCall::Pointer call = asyncCall(11, 3, "Ftp::Server::stopWaitingForOrigin",
+                                                    CbDialer(srv, &Ftp::Server::stopWaitingForOrigin, code));
+                ScheduleCallHere(call);
+            }
+        }
+        originWaitInProgress = false;
+    }
+}
+
+void
+Ftp::Relay::abort(void *d)
+{
+    Ftp::Relay *ftpClient = (Ftp::Relay *)d;
+    debugs(9, 2, "Client Data connection closed!");
+    if (!cbdataReferenceValid(ftpClient))
+        return;
+    if (Comm::IsConnOpen(ftpClient->data.conn))
+        ftpClient->dataComplete();
+}
+
 AsyncJob::Pointer
 Ftp::StartRelay(FwdState *const fwdState)
 {
     return AsyncJob::Start(new Ftp::Relay(fwdState));
 }
+