/*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * 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.
#include "ftp/Parsing.h"
#include "globals.h"
#include "http/one/RequestParser.h"
-#include "http/StreamContext.h"
+#include "http/Stream.h"
#include "HttpHdrCc.h"
#include "ip/tools.h"
#include "ipc/FdNotes.h"
uploadAvailSize(0),
listener(),
connector(),
- reader()
+ reader(),
+ waitingForOrigin(false),
+ originDataDownloadAbortedOnError(false)
{
flags.readMore = false; // we need to announce ourselves first
*uploadBuf = 0;
{
// zero pipelinePrefetchMax() ensures that there is only parsed request
Must(pipeline.count() == 1);
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
Must(context != nullptr);
ClientHttpRequest *const http = context->http;
}
void
-Ftp::Server::processParsedRequest(Http::StreamContext *)
+Ftp::Server::processParsedRequest(Http::StreamPointer &)
{
Must(pipeline.count() == 1);
void
Ftp::Server::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
{
+ if (!isOpen()) // if we are closing, nothing to do
+ return;
+
shovelUploadData();
}
void
Ftp::Server::noteBodyConsumerAborted(BodyPipe::Pointer ptr)
{
+ if (!isOpen()) // if we are closing, nothing to do
+ return;
+
ConnStateData::noteBodyConsumerAborted(ptr);
closeDataConnection();
}
Ftp::Server::notePeerConnection(Comm::ConnectionPointer conn)
{
// find request
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
Must(context != nullptr);
ClientHttpRequest *const http = context->http;
Must(http != NULL);
{
ConnStateData::clientPinnedConnectionClosed(io);
- // if the server control connection is gone, reset state to login again
- resetLogin("control connection closure");
+ // TODO: Keep the control connection open after fixing the reset
+ // problem below
+ if (Comm::IsConnOpen(clientConnection))
+ clientConnection->close();
- // XXX: Reseting is not enough. FtpRelay::sendCommand() will not re-login
- // because FtpRelay::serverState() is not going to be fssConnected.
+ // TODO: If the server control connection is gone, reset state to login
+ // again. Reseting login alone is not enough: FtpRelay::sendCommand() will
+ // not re-login because FtpRelay::serverState() is not going to be
+ // fssConnected. Calling resetLogin() alone is also harmful because
+ // it does not reset correctly the client-to-squid control connection (eg
+ // respond if required with an error code, in all cases)
+ // resetLogin("control connection closure");
}
/// clear client and server login-related state after the old login is gone
}
/// creates a context filled with an error message for a given early error
-Http::StreamContext *
+Http::Stream *
Ftp::Server::earlyError(const EarlyErrorKind eek)
{
/* Default values, to be updated by the switch statement below */
// no default so that a compiler can check that we have covered all cases
}
- Http::StreamContext *context = abortRequestParsing(errUri);
+ Http::Stream *context = abortRequestParsing(errUri);
clientStreamNode *node = context->getClientReplyContext();
Must(node);
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
}
/// Parses a single FTP request on the control connection.
-/// Returns a new Http::StreamContext on valid requests and all errors.
+/// Returns a new Http::Stream on valid requests and all errors.
/// Returns NULL on incomplete requests that may still succeed given more data.
-Http::StreamContext *
+Http::Stream *
Ftp::Server::parseOneRequest()
{
flags.readMore = false; // common for all but one case below
// process USER request now because it sets FTP peer host name
if (cmd == cmdUser()) {
- if (Http::StreamContext *errCtx = handleUserRequest(cmd, params))
+ if (Http::Stream *errCtx = handleUserRequest(cmd, params))
return errCtx;
}
}
¶ms : NULL;
calcUri(path);
char *newUri = xstrdup(uri.c_str());
- HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method);
+ MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initClient);
+ mx->tcpClient = clientConnection;
+ HttpRequest *const request = HttpRequest::FromUrl(newUri, mx, method);
if (!request) {
debugs(33, 5, "Invalid FTP URL: " << uri);
uri.clear();
http->req_sz = tok.parsedSize();
http->uri = newUri;
- Http::StreamContext *const result =
- new Http::StreamContext(nextStreamId(), clientConnection, http);
+ Http::Stream *const result =
+ new Http::Stream(clientConnection, http);
StoreIOBuffer tempBuffer;
tempBuffer.data = result->reqbuf;
Ftp::Server::handleReply(HttpReply *reply, StoreIOBuffer data)
{
// the caller guarantees that we are dealing with the current context only
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
assert(context != nullptr);
if (context->http && context->http->al != NULL &&
void
Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
{
- const Http::StreamContextPointer context(pipeline.front());
+ const Http::StreamPointer context(pipeline.front());
assert(context != nullptr);
if (context->http->request->errType != ERR_NONE) {
if (io.flag != Comm::OK) {
debugs(33, 3, "FTP reply data writing failed: " << xstrerr(io.xerrno));
- closeDataConnection();
- writeCustomReply(426, "Data connection error; transfer aborted");
+ userDataCompletionCheckpoint(426);
return;
}
return;
case STREAM_COMPLETE:
debugs(33, 3, "FTP reply data transfer successfully complete");
- writeCustomReply(226, "Transfer complete");
+ userDataCompletionCheckpoint(226);
break;
case STREAM_UNPLANNED_COMPLETE:
debugs(33, 3, "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
- writeCustomReply(451, "Server error; transfer aborted");
+ userDataCompletionCheckpoint(451);
break;
case STREAM_FAILED:
+ userDataCompletionCheckpoint(451);
debugs(33, 3, "FTP reply data transfer failed: STREAM_FAILED");
- writeCustomReply(451, "Server error; transfer aborted");
break;
default:
fatal("unreachable code");
}
-
- closeDataConnection();
}
void
{
Must(reply);
+ if (waitingForOrigin) {
+ Must(delayedReply == NULL);
+ delayedReply = reply;
+ return;
+ }
+
const HttpHeader &header = reply->header;
// adaptation and forwarding errors lack Http::HdrType::FTP_STATUS
if (!header.has(Http::HdrType::FTP_STATUS)) {
writeErrorReply(reply, 451);
}
-void
+bool
Ftp::Server::writeControlMsgAndCall(HttpReply *reply, AsyncCall::Pointer &call)
{
// the caller guarantees that we are dealing with the current context only
// the caller should also make sure reply->header.has(Http::HdrType::FTP_STATUS)
writeForwardedReplyAndCall(reply, call);
+ return true;
}
void
return;
}
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
if (context != nullptr && context->http) {
context->http->out.size += io.size;
context->http->out.headers_sz += io.size;
return;
}
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
assert(context->http);
context->http->out.size += io.size;
context->http->out.headers_sz += io.size;
Must(header.has(Http::HdrType::FTP_ARGUMENTS));
String ¶ms = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
- if (do_debug(9, 2)) {
+ if (Debug::Enabled(9, 2)) {
MemBuf mb;
mb.init();
request->pack(&mb);
/// Called to parse USER command, which is required to create an HTTP request
/// wrapper. W/o request, the errors are handled by returning earlyError().
-Http::StreamContext *
+Http::Stream *
Ftp::Server::handleUserRequest(const SBuf &, SBuf ¶ms)
{
if (params.isEmpty())
Comm::ConnectionPointer conn = new Comm::Connection();
conn->flags |= COMM_DOBIND;
- // Use local IP address of the control connection as the source address
- // of the active data connection, or some clients will refuse to accept.
- conn->setAddrs(clientConnection->local, cltAddr);
+ if (clientConnection->flags & COMM_INTERCEPTION) {
+ // In the case of NAT interception conn->local value is not set
+ // because the TCP stack will automatically pick correct source
+ // address for the data connection. We must only ensure that IP
+ // version matches client's address.
+ conn->local.setAnyAddr();
+
+ if (cltAddr.isIPv4())
+ conn->local.setIPv4();
+
+ conn->remote = cltAddr;
+ } else {
+ // In the case of explicit-proxy the local IP of the control connection
+ // is the Squid IP the client is knowingly talking to.
+ //
+ // In the case of TPROXY the IP address of the control connection is
+ // server IP the client is connecting to, it can be spoofed by Squid.
+ //
+ // In both cases some clients may refuse to accept data connections if
+ // these control connectin local-IP's are not used.
+ conn->setAddrs(clientConnection->local, cltAddr);
+
+ // Using non-local addresses in TPROXY mode requires appropriate socket option.
+ if (clientConnection->flags & COMM_TRANSPARENT)
+ conn->flags |= COMM_TRANSPARENT;
+ }
+
// RFC 959 requires active FTP connections to originate from port 20
// but that would preclude us from supporting concurrent transfers! (XXX?)
conn->local.port(0);
if (!checkDataConnPre())
return false;
+ master->userDataDone = 0;
+ originDataDownloadAbortedOnError = false;
+
changeState(fssHandleDataRequest, "handleDataRequest");
return true;
ClientHttpRequest *http = pipeline.front()->http;
HttpRequest *request = http->request;
ACLFilledChecklist bodyContinuationCheck(Config.accessList.forceRequestBodyContinuation, request, NULL);
- if (bodyContinuationCheck.fastCheck() == ACCESS_ALLOWED) {
+ if (bodyContinuationCheck.fastCheck().allowed()) {
request->forcedBodyContinuation = true;
if (checkDataConnPost()) {
// Write control Msg
if (params.conn != NULL)
params.conn->close();
setReply(425, "Cannot open data connection.");
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
Must(context->http);
Must(context->http->storeEntry() != NULL);
} else {
void
Ftp::Server::setReply(const int code, const char *msg)
{
- Http::StreamContextPointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
ClientHttpRequest *const http = context->http;
assert(http != NULL);
assert(http->storeEntry() == NULL);
AsyncJob::callException(e);
}
+void
+Ftp::Server::startWaitingForOrigin()
+{
+ if (!isOpen()) // if we are closing, nothing to do
+ return;
+
+ debugs(33, 5, "waiting for Ftp::Client data transfer to end");
+ waitingForOrigin = true;
+}
+
+void
+Ftp::Server::stopWaitingForOrigin(int originStatus)
+{
+ Must(waitingForOrigin);
+ waitingForOrigin = false;
+
+ if (!isOpen()) // if we are closing, nothing to do
+ return;
+
+ // if we have already decided how to respond, respond now
+ if (delayedReply) {
+ HttpReply::Pointer reply = delayedReply;
+ delayedReply = nullptr;
+ writeForwardedReply(reply.getRaw());
+ return; // do not completeDataDownload() after an earlier response
+ }
+
+ if (master->serverState != fssHandleDataRequest)
+ return;
+
+ // completeDataDownload() could be waitingForOrigin in fssHandleDataRequest
+ // Depending on which side has finished downloading first, either trust
+ // master->userDataDone status or set originDataDownloadAbortedOnError:
+ if (master->userDataDone) {
+ // We finished downloading before Ftp::Client. Most likely, the
+ // adaptation shortened the origin response or we hit an error.
+ // Our status (stored in master->userDataDone) is more informative.
+ // Use master->userDataDone; avoid originDataDownloadAbortedOnError.
+ completeDataDownload();
+ } else {
+ debugs(33, 5, "too early to write the response");
+ // Ftp::Client naturally finished downloading before us. Set
+ // originDataDownloadAbortedOnError to overwrite future
+ // master->userDataDone and relay Ftp::Client error, if there was
+ // any, to the user.
+ originDataDownloadAbortedOnError = (originStatus >= 400);
+ }
+}
+
+void Ftp::Server::userDataCompletionCheckpoint(int finalStatusCode)
+{
+ Must(!master->userDataDone);
+ master->userDataDone = finalStatusCode;
+
+ if (bodyParser)
+ finishDechunkingRequest(false);
+
+ if (waitingForOrigin) {
+ // The completeDataDownload() is not called here unconditionally
+ // because we want to signal the FTP user that we are not fully
+ // done processing its data stream, even though all data bytes
+ // have been sent or received already.
+ debugs(33, 5, "Transfering from FTP server is not complete");
+ return;
+ }
+
+ // Adjust our reply if the server aborted with an error before we are done.
+ if (master->userDataDone == 226 && originDataDownloadAbortedOnError) {
+ debugs(33, 5, "Transfering from FTP server terminated with an error, adjust status code");
+ master->userDataDone = 451;
+ }
+ completeDataDownload();
+}
+
+void Ftp::Server::completeDataDownload()
+{
+ writeCustomReply(master->userDataDone, master->userDataDone == 226 ? "Transfer complete" : "Server error; transfer aborted");
+ closeDataConnection();
+}
+
/// Whether Squid FTP Relay supports a named feature (e.g., a command).
static bool
Ftp::SupportedCommand(const SBuf &name)