/*
- * 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/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();
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();
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
};
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;
// 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);
{
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)) {
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);
}
void
-Ftp::Relay::failed(err_type error, int xerrno)
+Ftp::Relay::failed(err_type error, int xerrno, ErrorState *ftpErr)
{
if (!doneWithServer())
serverState(fssError);
if (entry->isEmpty())
failedErrorMessage(error, xerrno); // as a reply
- Ftp::Client::failed(error, xerrno);
+ Ftp::Client::failed(error, xerrno, ftpErr);
}
void
* 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;
}
void
Ftp::Relay::handleRequestBodyProducerAborted()
{
- ::ServerStateData::handleRequestBodyProducerAborted();
+ ::Client::handleRequestBodyProducerAborted();
failed(ERR_READ_ERROR);
}
EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
HttpReply *const reply = createHttpReply(Http::scNoContent);
+ reply->sources |= HttpMsg::srcFtp;
setVirginReply(reply);
adaptOrFinalizeReply();
// 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));
}
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;
}
" (" << 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();
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;
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 ¶ms = 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 ¶ms = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
if (params.size() > 0)
debugs(9, 5, "command: " << cmd << ", parameters: " << params);
debugs(9, 5, "command: " << cmd << ", no parameters");
if (serverState() == fssHandlePasv ||
- serverState() == fssHandleEpsv ||
- serverState() == fssHandleEprt ||
- serverState() == fssHandlePort) {
+ serverState() == fssHandleEpsv ||
+ serverState() == fssHandleEprt ||
+ serverState() == fssHandlePort) {
sendPassive();
return;
}
- static MemBuf mb;
- mb.reset();
+ SBuf buf;
if (params.size() > 0)
- mb.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
+ buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
else
- mb.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
+ buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
- writeCommand(mb.content());
+ writeCommand(buf.c_str());
state =
serverState() == fssHandleCdup ? SENT_CDUP :
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
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
" 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();
}
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));
}
+