continue;
}
switch (e->id) {
- // TODO: When FTP gw command wrappers may end up in error pages and
- // other sensitive places, hide HDR_FTP_ARGUMENTS for FTP PASS command.
+ // TODO: When native FTP commands may end up in error pages and other
+ // sensitive places, hide HDR_FTP_ARGUMENTS for the FTP PASS command.
case HDR_AUTHORIZATION:
case HDR_PROXY_AUTHORIZATION:
packerAppend(p, e->name.rawBuf(), e->name.size());
class HttpHdrSc;
class Packer;
class StoreEntry;
+class SBuf;
/* constant attributes of http header fields */
HDR_FRONT_END_HTTPS, /**< MS Exchange custom header we may have to add */
HDR_FTP_COMMAND, /**< Internal header for FTP command */
HDR_FTP_ARGUMENTS, /**< Internal header for FTP command arguments */
- HDR_FTP_PRE, /**< Custom: Contains leading FTP control response lines */
+ HDR_FTP_PRE, /**< Internal header containing leading FTP control response lines */
HDR_FTP_STATUS, /**< Internal header for FTP reply status */
HDR_FTP_REASON, /**< Internal header for FTP reply reason */
HDR_OTHER, /**< internal tag value for "unknown" headers */
int httpHeaderParseQuotedString(const char *start, const int len, String *val);
-/// quotes string using RFC 2616 quoted-string rules
-String httpHeaderQuoteString(const char *raw);
+/// quotes string using RFC 7230 quoted-string rules
+SBuf httpHeaderQuoteString(const char *raw);
int httpHeaderHasByNameListMember(const HttpHeader * hdr, const char *name, const char *member, const char separator);
void httpHeaderUpdate(HttpHeader * old, const HttpHeader * fresh, const HttpHeaderMask * denied_mask);
return 1;
}
-// TODO: Optimize using SBuf
-String
+SBuf
httpHeaderQuoteString(const char *raw)
{
assert(raw);
- // HTTPbis says Senders SHOULD NOT escape octets in quoted-strings that
- // do not require escaping (i.e., except DQUOTE and the backslash octet).
+ // RFC 7230 says a "sender SHOULD NOT generate a quoted-pair in a
+ // quoted-string except where necessary" (i.e., DQUOTE and backslash)
bool needInnerQuote = false;
for (const char *s = raw; !needInnerQuote && *s; ++s)
needInnerQuote = *s == '"' || *s == '\\';
- static String quotedStr;
- quotedStr.clean();
+ SBuf quotedStr;
quotedStr.append('"');
if (needInnerQuote) {
} else {
quotedStr.append(raw);
}
-
+
quotedStr.append('"');
return quotedStr;
}
return lowAppend(S, Ssize);
}
+SBuf &
+SBuf::append(const char c)
+{
+ return lowAppend(&c, 1);
+}
+
SBuf&
SBuf::Printf(const char *fmt, ...)
{
*/
SBuf& append(const SBuf & S);
+ /// Append a single character. The character may be NUL (\0).
+ SBuf& append(const char c);
+
/** Append operation for C-style strings.
*
* Append the supplied c-string to the SBuf; extend storage
actAsOrigin(false),
ignore_cc(false),
connection_auth_disabled(false),
+ ftp_track_dirs(false),
vport(0),
disable_pmtu_discovery(0),
- listenConn(),
+ listenConn()
#if USE_OPENSSL
- cert(NULL),
+ ,cert(NULL),
key(NULL),
version(0),
cipher(NULL),
dhParams(),
contextMethod(),
sslContextFlags(0),
- sslOptions(0),
+ sslOptions(0)
#endif
- ftp_track_dirs(false)
{
memset(&tcp_keepalive, 0, sizeof(tcp_keepalive));
}
b->vhost = vhost;
b->vport = vport;
b->connection_auth_disabled = connection_auth_disabled;
+ b->ftp_track_dirs = ftp_track_dirs;
b->disable_pmtu_discovery = disable_pmtu_discovery;
b->tcp_keepalive = tcp_keepalive;
- b->ftp_track_dirs = ftp_track_dirs;
#if 0
// TODO: AYJ: 2009-07-18: for now SSL does not clone. Configure separate ports with IPs and SSL settings
transport = AnyP::ProtocolVersion(AnyP::PROTO_HTTPS, 1,1);
else if (strcasecmp("ftp", aProtocol) == 0)
- transport = AnyP::ProtocolVersion(AnyP::PROTO_FTP, 1,0);
+ transport = AnyP::ProtocolVersion(AnyP::PROTO_FTP, 1,1);
else
fatalf("http(s)_port protocol=%s is not supported\n", aProtocol);
/**
* Set this ports transport type from a string representation.
* Unknown transport type representations will halt Squid.
- * Supports: HTTP, HTTP/1.1, HTTPS, HTTPS/1.1.
+ * Supports: HTTP, HTTP/1.1, HTTPS, HTTPS/1.1, and FTP.
*/
void setTransport(const char *aProtocol);
bool connection_auth_disabled; ///< Don't support connection oriented auth
+ bool ftp_track_dirs; ///< whether transactions should track FTP directories
+
int vport; ///< virtual port support. -1 if dynamic, >0 static
int disable_pmtu_discovery;
long sslContextFlags; ///< flags modifying the use of SSL
long sslOptions; ///< SSL engine options
#endif
-
- bool ftp_track_dirs; ///< whether ftp_port should track FTP directories
};
} // namespace AnyP
Options:
- ftp-track-dirs=on|off
+ ftp-track-dirs=on|off
Enables tracking of FTP directories by injecting extra
PWD commands and adjusting Request-URI (in wrapping
HTTP requests) to reflect the current FTP server
#include "clientStream.h"
#include "comm.h"
#include "comm/Connection.h"
-#include "comm/ConnOpener.h"
#include "comm/Loops.h"
#include "comm/Read.h"
#include "comm/TcpAcceptor.h"
#include "FwdState.h"
#include "globals.h"
#include "http.h"
-#include "HttpHdrCc.h"
#include "HttpHdrContRange.h"
#include "HttpHeaderTools.h"
#include "HttpReply.h"
#include "ident/Config.h"
#include "ident/Ident.h"
#include "internal.h"
-#include "ip/tools.h"
#include "ipc/FdNotes.h"
#include "ipc/StartListening.h"
#include "log/access_log.h"
#include <climits>
#include <cmath>
#include <limits>
-#include <set>
#if LINGERING_CLOSE
#define comm_close comm_lingering_close
clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http,
HttpReply * rep, StoreIOBuffer receivedData)
{
- debugs(33,7, HERE << "rep->content_length=" << (rep ? rep->content_length : -2) << " receivedData.length=" << receivedData.length);
/* Test preconditions */
assert(node != NULL);
PROF_start(clientSocketRecipient);
typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
TimeoutDialer, this, ConnStateData::requestTimeout);
- const int timeout = idleTimeout();
- commSetConnTimeout(clientConnection, timeout, timeoutCall);
+ commSetConnTimeout(clientConnection, idleTimeout(), timeoutCall);
readSomeData();
/** Please don't do anything with the FD past here! */
/* compile headers */
/* we should skip request line! */
/* XXX should actually know the damned buffer size here */
- if (http_ver.major >= 1 &&
- !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
+ if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
clientStreamNode *node = context->getClientReplyContext();
debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp));
conn->quitAfterError(request.getRaw());
request->body_pipe = conn->expectRequestBody(
chunked ? -1 : request->content_length);
- if (!notedUseOfBuffer) {
+ if (!isFtp) {
// consume header early so that body pipe gets just the body
connNoteUseOfBuffer(conn, http->req_sz);
notedUseOfBuffer = true;
goto finish;
if (!request->body_pipe->productionEnded()) {
- debugs(33, 5, HERE << "need more request body");
+ debugs(33, 5, "need more request body");
context->mayUseConnection(true);
assert(conn->flags.readMore);
}
io.conn->close();
}
-ConnStateData::ConnStateData(const MasterXaction::Pointer &xact):
+ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
AsyncJob("ConnStateData"), // kids overwrite
#if USE_OPENSSL
sslBumpMode(Ssl::bumpEnd),
// kids must extend to actually start doing something (e.g., reading)
}
-/** Handle a new connection on HTTP socket. */
+/** Handle a new connection on an HTTP socket. */
void
httpAccept(const CommAcceptCbParams ¶ms)
{
if (params.flag != Comm::OK) {
// Its possible the call was still queued when the client disconnected
- debugs(33, 2, "httpAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno));
+ debugs(33, 2, s->listenConn << ": accept failure: " << xstrerr(params.xerrno));
return;
}
- debugs(33, 4, HERE << params.conn << ": accepted");
+ debugs(33, 4, params.conn << ": accepted");
fd_note(params.conn->fd, "client http connect");
- if (s->tcp_keepalive.enabled) {
+ if (s->tcp_keepalive.enabled)
commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
- }
- ++ incoming_sockets_accepted;
+ ++incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
ConnStateData *connState = Http::NewServer(xact);
- AsyncJob::Start(connState);
+ AsyncJob::Start(connState); // usually async-calls readSomeData()
}
#if USE_OPENSSL
// Socket is ready, setup the connection manager to start using it
ConnStateData *connState = Https::NewServer(xact);
- AsyncJob::Start(connState); // will eventually call postHttpsAccept()
+ AsyncJob::Start(connState); // usually async-calls postHttpsAccept()
}
void
const AnyP::PortCfgPointer s = port;
if (s->flags.tunnelSslBumping) {
- debugs(33, 5, "httpsAccept: accept transparent connection: " << clientConnection);
+ debugs(33, 5, "accept transparent connection: " << clientConnection);
if (!Config.accessList.ssl_bump) {
httpsSslBumpAccessCheckDone(ACCESS_DENIED, connState);
// Fill out a Comm::Connection which IPC will open as a listener for us
port->listenConn = new Comm::Connection;
port->listenConn->local = port->s;
- port->listenConn->flags = COMM_NONBLOCKING | (port->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) |
- (port->flags.natIntercept ? COMM_INTERCEPTION : 0);
+ port->listenConn->flags =
+ COMM_NONBLOCKING |
+ (port->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) |
+ (port->flags.natIntercept ? COMM_INTERCEPTION : 0);
// route new connections to subCall
typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
- AsyncCall::Pointer listenCall = asyncCall(33, 2, "clientListenerConnectionOpened",
- ListeningStartedDialer(&clientListenerConnectionOpened,
- port, fdNote, sub));
+ AsyncCall::Pointer listenCall =
+ asyncCall(33, 2, "clientListenerConnectionOpened",
+ ListeningStartedDialer(&clientListenerConnectionOpened,
+ port, fdNote, sub));
Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, port->listenConn, fdNote, listenCall);
assert(NHttpSockets < MAXTCPLISTENPORTS);
Ftp::StartListening();
if (NHttpSockets < 1)
- fatal("No HTTP, HTTPS or FTP ports configured");
+ fatal("No HTTP, HTTPS, or FTP ports configured");
}
void
-clientConnectionsClose(void)
+clientConnectionsClose()
{
for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) {
if (s->listenConn != NULL) {
void
ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth, bool monitor)
{
- if (!Comm::IsConnOpen(pinning.serverConnection) ||
+ if (!Comm::IsConnOpen(pinning.serverConnection) ||
pinning.serverConnection->fd != pinServer->fd)
pinNewConnection(pinServer, request, aPeer, auth);
comm_add_close_handler(pinning.serverConnection->fd, pinning.closeHandler);
}
-/// [re]start monitoring pinned connection for server closures so that we can
-/// propagate them to an _idle_ client pinned to the server
+/// [re]start monitoring pinned connection for peer closures so that we can
+/// propagate them to an _idle_ client pinned to that peer
void
ConnStateData::startPinnedConnectionMonitoring()
{
int port; /* port of pinned connection */
bool pinned; /* this connection was pinned */
bool auth; /* pinned for www authentication */
- bool reading; ///< we are monitoring for server connection closure
+ bool reading; ///< we are monitoring for peer connection closure
bool zeroReply; ///< server closed w/o response (ERR_ZERO_SIZE_OBJECT)
CachePeer *peer; /* CachePeer the connection goes via */
AsyncCall::Pointer readHandler; ///< detects serverConnection closure
bool handleReadData();
bool handleRequestBodyData();
- /// forward future client requests using the given server connection
- /// optionally, monitor pinned server connection for server-side closures
+ /// Forward future client requests using the given server connection.
+ /// Optionally, monitor pinned server connection for server-side closures.
void pinConnection(const Comm::ConnectionPointer &pinServerConn, HttpRequest *request, CachePeer *peer, bool auth, bool monitor = true);
- /// undo pinConnection() and, optionally, close the pinned connection
+ /// Undo pinConnection() and, optionally, close the pinned connection.
void unpinConnection(const bool andClose);
- /// returns validated pinnned server connection (and stops its monitoring)
+ /// Returns validated pinnned server connection (and stops its monitoring).
Comm::ConnectionPointer borrowPinnedConnection(HttpRequest *request, const CachePeer *aPeer);
/**
* Checks if there is pinning info if it is valid. It can close the server side connection
void startPinnedConnectionMonitoring();
void clientPinnedConnectionRead(const CommIoCbParams &io);
- // TODO: document
+ /// parse input buffer prefix into a single transfer protocol request
virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver) = 0;
+
+ /// start processing a freshly parsed request
virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver) = 0;
/// returning N allows a pipeline of 1+N requests (see pipeline_prefetch)
protected:
int connFinishedWithConn(int size);
void clientAfterReadingRequests();
-
bool concurrentRequestQueueFilled() const;
void pinNewConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth);
int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req);
+/// accept requests to a given port and inform subCall about them
void clientStartListeningOn(AnyP::PortCfgPointer &port, const RefCount< CommCbFunPtrCallT<CommAcceptCbPtrFun> > &subCall, const Ipc::FdNoteId noteId);
void clientOpenListenSockets(void);
void clientConnectionsClose(void);
void httpRequestFree(void *);
+
+/// decide whether to expect multiple requests on the corresponding connection
void clientSetKeepaliveFlag(ClientHttpRequest *http);
/* misplaced declaratrions of Stream callbacks provided/used by client side */
return ret;
}
+/* Ftp::Channel */
+
/// configures the channel with a descriptor and registers a close handler
void
Ftp::Channel::opened(const Comm::ConnectionPointer &newConn,
closer = NULL;
}
-Ftp::Client::Client(FwdState *fwdState):
- AsyncJob("Ftp::Client"), ::ServerStateData(fwdState)
+/* Ftp::CtrlChannel */
+
+Ftp::CtrlChannel::CtrlChannel():
+ buf(NULL),
+ size(0),
+ offset(0),
+ message(NULL),
+ last_command(NULL),
+ last_reply(NULL),
+ replycode(0)
{
- ++statCounter.server.all.requests;
- ++statCounter.server.ftp.requests;
+ buf = static_cast<char*>(memAllocBuf(4096, &size));
+}
- ctrl.last_command = xstrdup("Connect to server");
- ctrl.buf = static_cast<char *>(memAllocBuf(4096, &ctrl.size));
- ctrl.offset = 0;
+Ftp::CtrlChannel::~CtrlChannel()
+{
+ memFreeBuf(size, buf);
+ if (message)
+ wordlistDestroy(&message);
+ safe_free(last_command);
+ safe_free(last_reply);
+}
- typedef CommCbMemFunT<Client, CommCloseCbParams> Dialer;
- const AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this,
- Ftp::Client::ctrlClosed);
- ctrl.opened(fwdState->serverConnection(), closer);
+/* Ftp::DataChannel */
+
+Ftp::DataChannel::DataChannel():
+ readBuf(NULL),
+ host(NULL),
+ port(0),
+ read_pending(false)
+{
+}
+
+Ftp::DataChannel::~DataChannel()
+{
+ delete readBuf;
}
void
-Ftp::Client::DataChannel::addr(const Ip::Address &import)
+Ftp::DataChannel::addr(const Ip::Address &import)
{
static char addrBuf[MAX_IPSTRLEN];
import.toStr(addrBuf, sizeof(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)
+{
+ ++statCounter.server.all.requests;
+ ++statCounter.server.ftp.requests;
+
+ ctrl.last_command = xstrdup("Connect to server");
+
+ typedef CommCbMemFunT<Client, CommCloseCbParams> Dialer;
+ const AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this,
+ Ftp::Client::ctrlClosed);
+ ctrl.opened(fwdState->serverConnection(), closer);
+}
+
Ftp::Client::~Client()
{
if (data.opener != NULL) {
}
data.close();
- if (ctrl.buf) {
- memFreeBuf(ctrl.size, ctrl.buf);
- ctrl.buf = NULL;
- }
- if (ctrl.message)
- wordlistDestroy(&ctrl.message);
- safe_free(ctrl.last_command);
- safe_free(ctrl.last_reply);
-
- if (data.readBuf) {
- if (!data.readBuf->isNull())
- data.readBuf->clean();
-
- delete data.readBuf;
- }
-
safe_free(old_request);
-
safe_free(old_reply);
-
fwd = NULL; // refcounted
}
Ftp::Client::closeServer()
{
if (Comm::IsConnOpen(ctrl.conn)) {
- debugs(9,3, HERE << "closing FTP server FD " << ctrl.conn->fd << ", this " << this);
+ debugs(9, 3, "closing FTP server FD " << ctrl.conn->fd << ", this " << this);
fwd->unregister(ctrl.conn);
ctrl.close();
}
if (Comm::IsConnOpen(data.conn)) {
- debugs(9,3, HERE << "closing FTP data FD " << data.conn->fd << ", this " << this);
+ debugs(9, 3, "closing FTP data FD " << data.conn->fd << ", this " << this);
data.close();
}
- debugs(9,3, HERE << "FTP ctrl and data connections closed. this " << this);
+ debugs(9, 3, "FTP ctrl and data connections closed. this " << this);
}
/**
* Did we close all FTP server connection(s)?
*
- \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
- \retval false Either control channel or data is still active.
+ \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
+ \retval false Either control channel or data is still active.
*/
bool
Ftp::Client::doneWithServer() const
void
Ftp::Client::failed(err_type error, int xerrno)
{
- debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
+ debugs(9, 3, "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
const char *command, *reply;
const Http::StatusCode httpStatus = failedHttpStatus(error);
void
Ftp::Client::scheduleReadControlReply(int buffered_ok)
{
- debugs(9, 3, HERE << ctrl.conn);
+ debugs(9, 3, ctrl.conn);
if (buffered_ok && ctrl.offset > 0) {
/* We've already read some reply data */
commUnsetConnTimeout(data.conn);
}
+ const time_t tout = shortenReadTimeout ?
+ min(Config.Timeout.connect, Config.Timeout.read):
+ Config.Timeout.read;
+ shortenReadTimeout = false; // we only need to do this once, after PASV
+
typedef CommCbMemFunT<Client, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, Ftp::Client::timeout);
- commSetConnTimeout(ctrl.conn, Config.Timeout.read, timeoutCall);
+ commSetConnTimeout(ctrl.conn, tout, timeoutCall);
typedef CommCbMemFunT<Client, CommIoCbParams> Dialer;
AsyncCall::Pointer reader = JobCallback(9, 5, Dialer, this, Ftp::Client::readControlReply);
void
Ftp::Client::readControlReply(const CommIoCbParams &io)
{
- debugs(9, 3, HERE << "FD " << io.fd << ", Read " << io.size << " bytes");
+ debugs(9, 3, "FD " << io.fd << ", Read " << io.size << " bytes");
if (io.size > 0) {
kb_incr(&(statCounter.server.all.kbytes_in), io.size);
if (io.flag != Comm::OK) {
debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
- "ftpReadControlReply: read error: " << xstrerr(io.xerrno));
+ "FTP control reply read error: " << xstrerr(io.xerrno));
if (ignoreErrno(io.xerrno)) {
scheduleReadControlReply(0);
void
Ftp::Client::handleControlReply()
{
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
size_t bytes_used = 0;
wordlistDestroy(&ctrl.message);
/* didn't get complete reply yet */
if (ctrl.offset == ctrl.size) {
- ctrl.buf = (char *)memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size);
+ ctrl.buf = static_cast<char*>(memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size));
}
scheduleReadControlReply(0);
return;
- }
+ }
assert(ctrl.message); // the entire FTP server response, line by line
assert(ctrl.replycode >= 0); // FTP status code (from the last line)
memmove(ctrl.buf, ctrl.buf + bytes_used, ctrl.offset);
}
- debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
+ debugs(9, 3, "state=" << state << ", code=" << ctrl.replycode);
}
bool
{
int code = ctrl.replycode;
char *buf;
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
if (code != 227) {
debugs(9, 2, "PASV not supported by remote end");
/* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
/* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
- debugs(9, 5, HERE << "scanning: " << ctrl.last_reply);
+ debugs(9, 5, "scanning: " << ctrl.last_reply);
buf = ctrl.last_reply + strcspn(ctrl.last_reply, "0123456789");
{
int code = ctrl.replycode;
char *buf;
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
if (code != 229 && code != 522) {
if (code == 200) {
}
if (code == 522) {
- /* server response with list of supported methods */
- /* 522 Network protocol not supported, use (1) */
- /* 522 Network protocol not supported, use (1,2) */
- /* 522 Network protocol not supported, use (2) */
- /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
- * which means close data + control without self-destructing and re-open from scratch. */
- debugs(9, 5, HERE << "scanning: " << ctrl.last_reply);
+ /* Peer responded with a list of supported methods:
+ * 522 Network protocol not supported, use (1)
+ * 522 Network protocol not supported, use (1,2)
+ * 522 Network protocol not supported, use (2)
+ * TODO: Handle the (1,2) case which may happen after EPSV ALL. Close
+ * data + control without self-destructing and re-open from scratch.
+ */
+ debugs(9, 5, "scanning: " << ctrl.last_reply);
buf = ctrl.last_reply;
while (buf != NULL && *buf != '\0' && *buf != '\n' && *buf != '(')
++buf;
return true;
}
-// The server-side EPRT and PORT commands are not yet implemented.
+// FTP clients do not support EPRT and PORT commands yet.
// The Ftp::Client::sendEprt() will fail because of the unimplemented
// openListenSocket() or sendPort() methods
bool
return sendPort();
}
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
if (!openListenSocket()) {
failed(ERR_FTP_FAILURE, 0);
debugs(9, 3, "Listening for FTP data connection with FD " << data.conn);
if (!Comm::IsConnOpen(data.conn)) {
- /* XXX Need to set error message */
+ // TODO: Set error message.
failed(ERR_FTP_FAILURE, 0);
return false;
}
bool
Ftp::Client::sendPassive()
{
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
/** \par
* Checks for EPSV ALL special conditions:
switch (state) {
case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
if (ctrl.conn->local.isIPv6()) {
- debugs(9, 5, HERE << "FTP Channel is IPv6 (" << ctrl.conn->remote << ") attempting EPSV 2 after EPSV ALL has failed.");
+ 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);
state = SENT_EPSV_2;
break;
case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
if (ctrl.conn->local.isIPv4()) {
- debugs(9, 5, HERE << "FTP Channel is IPv4 (" << ctrl.conn->remote << ") attempting EPSV 1 after EPSV ALL has failed.");
+ 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);
state = SENT_EPSV_1;
break;
// else fall through to skip EPSV 1
case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
- debugs(9, 5, HERE << "FTP Channel (" << ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead.");
+ debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead.");
mb.Printf("PASV%s", Ftp::crlf);
state = SENT_PASV;
break;
doEpsv = (checklist.fastCheck() == ACCESS_ALLOWED);
}
if (!doEpsv) {
- debugs(9, 5, HERE << "EPSV support manually disabled. Sending PASV for FTP Channel (" << ctrl.conn->remote <<")");
+ debugs(9, 5, "EPSV support manually disabled. Sending PASV for FTP Channel (" << ctrl.conn->remote <<")");
mb.Printf("PASV%s", Ftp::crlf);
state = SENT_PASV;
} else if (Config.Ftp.epsv_all) {
- debugs(9, 5, HERE << "EPSV ALL manually enabled. Attempting with FTP Channel (" << ctrl.conn->remote <<")");
+ debugs(9, 5, "EPSV ALL manually enabled. Attempting with FTP Channel (" << ctrl.conn->remote <<")");
mb.Printf("EPSV ALL%s", Ftp::crlf);
state = SENT_EPSV_ALL;
} else {
if (ctrl.conn->local.isIPv6()) {
- debugs(9, 5, HERE << "FTP Channel (" << ctrl.conn->remote << "). Sending default EPSV 2");
+ debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << "). Sending default EPSV 2");
mb.Printf("EPSV 2%s", Ftp::crlf);
state = SENT_EPSV_2;
}
if (ctrl.conn->local.isIPv4()) {
- debugs(9, 5, HERE << "Channel (" << ctrl.conn->remote <<"). Sending default EPSV 1");
+ debugs(9, 5, "Channel (" << ctrl.conn->remote <<"). Sending default EPSV 1");
mb.Printf("EPSV 1%s", Ftp::crlf);
state = SENT_EPSV_1;
}
}
break;
}
- }
+ }
if (ctrl.message)
wordlistDestroy(&ctrl.message);
writeCommand(mb.content());
- /*
- * ugly hack for ftp servers like ftp.netscape.com that sometimes
- * dont acknowledge PASV commands. Use connect timeout to be faster then read timeout (minutes).
- */
- /* XXX: resurrect or remove
- typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
- TimeoutDialer, this, FtpStateData::timeout);
- commSetConnTimeout(ctrl.conn, Config.Timeout.connect, timeoutCall);
- */
-
+ shortenReadTimeout = true;
return true;
}
conn->tos = ctrl.conn->tos;
conn->nfmark = ctrl.conn->nfmark;
- debugs(9, 3, HERE << "connecting to " << conn->remote);
+ debugs(9, 3, "connecting to " << conn->remote);
- data.opener = commCbCall(9,3, "Ftp::Client::dataChannelConnected",
+ data.opener = commCbCall(9, 3, "Ftp::Client::dataChannelConnected",
CommConnectCbPtrFun(Ftp::Client::dataChannelConnected, this));
Comm::ConnOpener *cs = new Comm::ConnOpener(conn, data.opener, Config.Timeout.connect);
cs->setHost(data.host);
void
Ftp::Client::dataClosed(const CommCloseCbParams &io)
{
- debugs(9, 4, HERE);
+ debugs(9, 4, status());
if (data.listenConn != NULL) {
data.listenConn->close();
data.listenConn = NULL;
ctrl.last_command = ebuf;
if (!Comm::IsConnOpen(ctrl.conn)) {
- debugs(9, 2, HERE << "cannot send to closing ctrl " << ctrl.conn);
+ debugs(9, 2, "cannot send to closing ctrl " << ctrl.conn);
// TODO: assert(ctrl.closer != NULL);
return;
}
Ftp::Client::writeCommandCallback(const CommIoCbParams &io)
{
- debugs(9, 5, HERE << "wrote " << io.size << " bytes");
+ debugs(9, 5, "wrote " << io.size << " bytes");
if (io.size > 0) {
fd_bytes(io.fd, io.size, FD_WRITE);
return;
if (io.flag) {
- debugs(9, DBG_IMPORTANT, "ftpWriteCommandCallback: " << io.conn << ": " << xstrerr(io.xerrno));
+ debugs(9, DBG_IMPORTANT, "FTP command write error: " << io.conn << ": " << xstrerr(io.xerrno));
failed(ERR_WRITE_ERROR, io.xerrno);
/* failed closes ctrl.conn and frees ftpState */
return;
void
Ftp::Client::ctrlClosed(const CommCloseCbParams &io)
{
- debugs(9, 4, HERE);
+ debugs(9, 4, status());
ctrl.clear();
mustStop("Ftp::Client::ctrlClosed");
}
void
Ftp::Client::timeout(const CommTimeoutCbParams &io)
{
- debugs(9, 4, HERE << io.conn << ": '" << entry->url() << "'" );
+ debugs(9, 4, io.conn << ": '" << entry->url() << "'" );
if (abortOnBadEntry("entry went bad while waiting for a timeout"))
return;
const int read_sz = replyBodySpace(*data.readBuf, 0);
- debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
+ debugs(11,9, "FTP may read up to " << read_sz << " bytes");
- if (read_sz < 2) // see http.cc
+ if (read_sz < 2) // see http.cc
return;
data.read_pending = true;
TimeoutDialer, this, Ftp::Client::timeout);
commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
- debugs(9,5,HERE << "queueing read on FD " << data.conn->fd);
+ debugs(9,5,"queueing read on FD " << data.conn->fd);
typedef CommCbMemFunT<Client, CommIoCbParams> Dialer;
entry->delayAwareRead(data.conn, data.readBuf->space(), read_sz,
data.read_pending = false;
- debugs(9, 3, HERE << "FD " << io.fd << " Read " << io.size << " bytes");
+ debugs(9, 3, "FD " << io.fd << " Read " << io.size << " bytes");
if (io.size > 0) {
kb_incr(&(statCounter.server.all.kbytes_in), io.size);
if (io.flag != Comm::OK) {
debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
- HERE << "read error: " << xstrerr(io.xerrno));
+ "FTP data read error: " << xstrerr(io.xerrno));
if (ignoreErrno(io.xerrno)) {
maybeReadVirginBody();
return;
}
} else if (io.size == 0) {
- debugs(9,3, HERE << "Calling dataComplete() because io.size == 0");
+ debugs(9, 3, "Calling dataComplete() because io.size == 0");
/*
* DPW 2007-04-23
* Dangerous curves ahead. This call to dataComplete was
void
Ftp::Client::dataComplete()
{
- debugs(9, 3,HERE);
+ debugs(9, 3,status());
/* Connection closed; transfer done. */
* Quickly abort the transaction
*
\todo destruction should be sufficient as the destructor should cleanup,
- * including canceling close handlers
+ * including canceling close handlers
*/
void
Ftp::Client::abortTransaction(const char *reason)
{
- debugs(9, 3, HERE << "aborting transaction for " << 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();
Ftp::Client::doneSendingRequestBody()
{
::ServerStateData::doneSendingRequestBody();
- debugs(9,3, HERE);
+ debugs(9, 3, status());
dataComplete();
/* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
* if transfer type is 'stream' call dataComplete()
wordlist *list;
wordlist **tail = &head;
size_t linelen;
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
/*
* We need a NULL-terminated buffer for scanning, ick
*/
usable = end - sbuf;
- debugs(9, 3, HERE << "usable = " << usable);
+ debugs(9, 3, "usable = " << usable);
if (usable == 0) {
- debugs(9, 3, HERE << "didn't find end of line");
+ debugs(9, 3, "didn't find end of line");
safe_free(sbuf);
return false;
}
- debugs(9, 3, HERE << len << " bytes to play with");
+ debugs(9, 3, len << " bytes to play with");
++end;
s = sbuf;
s += strspn(s, crlf);
if (complete)
break;
- debugs(9, 5, HERE << "s = {" << s << "}");
+ debugs(9, 5, "s = {" << s << "}");
linelen = strcspn(s, crlf) + 1;
extern const char *const crlf;
-/// common code for FTP server control and data channels
-/// does not own the channel descriptor, which is managed by Ftp::Client
+/// Common code for FTP server control and data channels.
+/// Does not own the channel descriptor, which is managed by Ftp::Client.
class Channel
{
public:
AsyncCall::Pointer closer; ///< Comm close handler callback
};
+/// FTP channel for control commands.
+/// This channel is opened once per transaction.
+class CtrlChannel: public Ftp::Channel
+{
+public:
+ CtrlChannel();
+ ~CtrlChannel();
+
+ char *buf;
+ size_t size;
+ size_t offset;
+ wordlist *message;
+ char *last_command;
+ char *last_reply;
+ int replycode;
+
+private:
+ CtrlChannel(const CtrlChannel &); // not implemented
+ CtrlChannel &operator =(const CtrlChannel &); // not implemented
+};
+
+/// FTP channel for data exchanges.
+/// This channel may be opened/closed a few times.
+class DataChannel: public Ftp::Channel
+{
+public:
+ DataChannel();
+ ~DataChannel();
+
+ void addr(const Ip::Address &addr); ///< import host and port
+
+public:
+ MemBuf *readBuf;
+ char *host;
+ unsigned short port;
+ bool read_pending;
+};
+
/// Base class for FTP Gateway and FTP Native client classes.
class Client: public ::ServerStateData
{
bool openListenSocket();
void switchTimeoutToDataChannel();
- // \todo: optimize ctrl and data structs member order, to minimize size
- /// FTP control channel info; the channel is opened once per transaction
- struct CtrlChannel: public Ftp::Channel {
- char *buf;
- size_t size;
- size_t offset;
- wordlist *message;
- char *last_command;
- char *last_reply;
- int replycode;
- } ctrl;
-
- /// FTP data channel info; the channel may be opened/closed a few times
- struct DataChannel: public Ftp::Channel {
- MemBuf *readBuf;
- char *host;
- unsigned short port;
- bool read_pending;
-
- void addr(const Ip::Address &addr); ///< import host and port
- } data;
+ CtrlChannel ctrl; ///< FTP control channel state
+ DataChannel data; ///< FTP data channel state
enum {
BEGIN,
private:
bool parseControlReply(size_t &bytesUsed);
+ /// XXX: An old hack for FTP servers like ftp.netscape.com that may not
+ /// respond to PASV. Use faster connect timeout instead of read timeout.
+ bool shortenReadTimeout;
+
CBDATA_CLASS2(Client);
};
/// \ingroup ServerProtocolFTPInternal
/// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI,
-/// converts it into one or more FTP commands, and then
+/// converts it into one or more FTP commands, and then
/// converts one or more FTP responses into the final HTTP response.
class Gateway : public Ftp::Client
{
debugs(9, 3, HERE << entry->url() );
if (Comm::IsConnOpen(ctrl.conn)) {
- debugs(9, DBG_IMPORTANT, HERE << "Internal bug: Ftp::ServerStateData "
- "left open control channel " << ctrl.conn);
+ debugs(9, DBG_IMPORTANT, "Internal bug: FTP Gateway left open " <<
+ "control channel " << ctrl.conn);
}
if (reply_hdr) {
if (SENT_PASV == state) {
/* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
flags.pasv_supported = false;
- debugs(9, DBG_IMPORTANT, HERE << "timeout in SENT_PASV state");
+ debugs(9, DBG_IMPORTANT, "FTP Gateway timeout in SENT_PASV state");
// cancel the data connection setup.
if (data.opener != NULL) {
void
Ftp::Gateway::processReplyBody()
{
- debugs(9, 3, HERE << "Ftp::Gateway::processReplyBody starting.");
+ debugs(9, 3, status());
if (request->method == Http::METHOD_HEAD && (flags.isdir || theSize != -1)) {
serverComplete();
#if USE_ADAPTATION
if (adaptationAccessCheckPending) {
- debugs(9,3, HERE << "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
+ debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
return;
}
buildTitleUrl();
debugs(9, 5, HERE << "FD " << ctrl.conn->fd << " : host=" << request->GetHost() <<
", path=" << request->urlpath << ", user=" << user << ", passwd=" << password);
-
state = BEGIN;
-
Ftp::Client::start();
}
{
if (error == ERR_NONE) {
switch (state) {
+
case SENT_USER:
+
case SENT_PASS:
+
if (ctrl.replycode > 500) {
error = ERR_FTP_FORBIDDEN;
return password_url ? Http::scForbidden : Http::scUnauthorized;
return Http::scServiceUnavailable;
}
break;
+
case SENT_CWD:
+
case SENT_RETR:
if (ctrl.replycode == 550) {
error = ERR_FTP_NOT_FOUND;
return Http::scNotFound;
}
break;
+
default:
break;
}
namespace Ftp {
-/// An FTP client receiving native FTP commands from our FTP server
+/// 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
protected:
const Ftp::MasterState &master() const;
Ftp::MasterState &updateMaster();
- Ftp::ServerState clientState() const;
- void clientState(Ftp::ServerState newState);
+ Ftp::ServerState serverState() const { return master().serverState; }
+ 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);
/* ServerStateData API */
virtual void serverComplete();
void readCwdOrCdupReply();
void readUserOrPassReply();
- virtual void dataChannelConnected(const Comm::ConnectionPointer &conn, Comm::Flag err, int xerrno);
void scheduleReadControlReply();
bool forwardingCompleted; ///< completeForwarding() has been called
&Ftp::Relay::readGreeting, // BEGIN
&Ftp::Relay::readUserOrPassReply, // SENT_USER
&Ftp::Relay::readUserOrPassReply, // SENT_PASS
- NULL,/*&Ftp::Relay::readReply*/ // SENT_TYPE
- NULL,/*&Ftp::Relay::readReply*/ // SENT_MDTM
- NULL,/*&Ftp::Relay::readReply*/ // SENT_SIZE
+ NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE
+ NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM
+ NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE
NULL, // SENT_EPRT
NULL, // SENT_PORT
&Ftp::Relay::readEpsvReply, // SENT_EPSV_ALL
&Ftp::Relay::readEpsvReply, // SENT_EPSV_2
&Ftp::Relay::readPasvReply, // SENT_PASV
&Ftp::Relay::readCwdOrCdupReply, // SENT_CWD
- NULL,/*&Ftp::Relay::readDataReply,*/ // SENT_LIST
- NULL,/*&Ftp::Relay::readDataReply,*/ // SENT_NLST
- NULL,/*&Ftp::Relay::readReply*/ // SENT_REST
- NULL,/*&Ftp::Relay::readDataReply*/ // SENT_RETR
- NULL,/*&Ftp::Relay::readReply*/ // SENT_STOR
- NULL,/*&Ftp::Relay::readReply*/ // SENT_QUIT
+ NULL,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
+ NULL,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
+ NULL,/* &Ftp::Relay::readReply */ // SENT_REST
+ NULL,/* &Ftp::Relay::readDataReply */ // SENT_RETR
+ NULL,/* &Ftp::Relay::readReply */ // SENT_STOR
+ NULL,/* &Ftp::Relay::readReply */ // SENT_QUIT
&Ftp::Relay::readTransferDoneReply, // READING_DATA
&Ftp::Relay::readReply, // WRITING_DATA
- NULL,/*&Ftp::Relay::readReply*/ // SENT_MKDIR
+ NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR
&Ftp::Relay::readFeatReply, // SENT_FEAT
- NULL,/*&Ftp::Relay::readPwdReply*/ // SENT_PWD
+ NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
&Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP
&Ftp::Relay::readDataReply,// SENT_DATA_REQUEST
&Ftp::Relay::readReply, // SENT_COMMAND
if (!master().clientReadGreeting)
Ftp::Client::start();
else
- if (clientState() == fssHandleDataRequest ||
- clientState() == fssHandleUploadRequest)
+ if (serverState() == fssHandleDataRequest ||
+ serverState() == fssHandleUploadRequest)
handleDataRequest();
else
sendCommand();
Ftp::Client::serverComplete();
}
+/// Safely returns the master state,
+/// with safety checks in case the Ftp::Server side of the master xact is gone.
Ftp::MasterState &
Ftp::Relay::updateMaster()
{
return Master;
}
+/// A const variant of updateMaster().
const Ftp::MasterState &
Ftp::Relay::master() const
{
- return const_cast<Ftp::Relay*>(this)->updateMaster();
-}
-
-Ftp::ServerState
-Ftp::Relay::clientState() const
-{
- return master().serverState;
+ return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe
}
+/// Changes server state and debugs about that important event.
void
-Ftp::Relay::clientState(Ftp::ServerState newState)
+Ftp::Relay::serverState(const Ftp::ServerState newState)
{
- // XXX: s/client/server/g
Ftp::ServerState &cltState = updateMaster().serverState;
debugs(9, 3, "client state was " << cltState << " now: " << newState);
cltState = newState;
/**
* Ensure we do not double-complete on the forward entry.
- * We complete forwarding when the response adaptation is over
+ * We complete forwarding when the response adaptation is over
* (but we may still be waiting for 226 from the FTP server) and
* also when we get that 226 from the server (and adaptation is done).
*
Ftp::Relay::failed(err_type error, int xerrno)
{
if (!doneWithServer())
- clientState(fssError);
+ serverState(fssError);
// TODO: we need to customize ErrorState instead
if (entry->isEmpty())
void
Ftp::Relay::processReplyBody()
{
- debugs(9, 3, HERE << "starting");
+ debugs(9, 3, status());
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
/*
#if USE_ADAPTATION
if (adaptationAccessCheckPending) {
- debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
+ debugs(9, 3, "returning due to adaptationAccessCheckPending");
return;
}
if (data.readBuf != NULL && data.readBuf->hasContent()) {
const mb_size_t csize = data.readBuf->contentSize();
- debugs(9, 5, HERE << "writing " << csize << " bytes to the reply");
+ debugs(9, 5, "writing " << csize << " bytes to the reply");
addVirginReplyBody(data.readBuf->content(), csize);
data.readBuf->consume(csize);
}
void
Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb)
{
- debugs(9, 5, HERE << "Forwarding preliminary reply to client");
+ debugs(9, 5, "forwarding preliminary reply to client");
// we must prevent concurrent ConnStateData::sendControlMsg() calls
Must(thePreliminaryCb == NULL);
void
Ftp::Relay::proceedAfterPreliminaryReply()
{
- debugs(9, 5, HERE << "Proceeding after preliminary reply to client");
+ debugs(9, 5, "proceeding after preliminary reply to client");
Must(thePreliminaryCb != NULL);
const PreliminaryCb cb = thePreliminaryCb;
if (ctrl.message) {
for (wordlist *W = ctrl.message; W && W->next; W = W->next)
- header.putStr(HDR_FTP_PRE, httpHeaderQuoteString(W->key).termedBuf());
+ header.putStr(HDR_FTP_PRE, httpHeaderQuoteString(W->key).c_str());
}
if (ctrl.replycode > 0)
header.putInt(HDR_FTP_STATUS, ctrl.replycode);
{
assert(Comm::IsConnOpen(data.conn));
- debugs(9, 3, HERE << "begin data transfer from " << data.conn->remote <<
+ debugs(9, 3, "begin data transfer from " << data.conn->remote <<
" (" << data.conn->local << ")");
HttpReply *const reply = createHttpReply(Http::scOkay, -1);
{
assert(Comm::IsConnOpen(data.conn));
- debugs(9, 3, HERE << "begin data transfer to " << data.conn->remote <<
+ debugs(9, 3, "begin data transfer to " << data.conn->remote <<
" (" << data.conn->local << ")");
if (!startRequestBodyFlow()) { // register to receive body data
switch (ctrl.replycode) {
case 220:
updateMaster().clientReadGreeting = true;
- if (clientState() == fssBegin)
- clientState(fssConnected);
+ 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
const String ¶ms = header.findEntry(HDR_FTP_ARGUMENTS)->value;
if (params.size() > 0)
- debugs(9, 5, HERE << "command: " << cmd << ", parameters: " << params);
+ debugs(9, 5, "command: " << cmd << ", parameters: " << params);
else
- debugs(9, 5, HERE << "command: " << cmd << ", no parameters");
+ debugs(9, 5, "command: " << cmd << ", no parameters");
- if (clientState() == fssHandlePasv ||
- clientState() == fssHandleEpsv ||
- clientState() == fssHandleEprt ||
- clientState() == fssHandlePort) {
+ if (serverState() == fssHandlePasv ||
+ serverState() == fssHandleEpsv ||
+ serverState() == fssHandleEprt ||
+ serverState() == fssHandlePort) {
sendPassive();
return;
}
writeCommand(mb.content());
state =
- clientState() == fssHandleCdup ? SENT_CDUP :
- clientState() == fssHandleCwd ? SENT_CWD :
- clientState() == fssHandleFeat ? SENT_FEAT :
- clientState() == fssHandleDataRequest ? SENT_DATA_REQUEST :
- clientState() == fssHandleUploadRequest ? SENT_DATA_REQUEST :
- clientState() == fssConnected ? SENT_USER :
- clientState() == fssHandlePass ? SENT_PASS :
+ serverState() == fssHandleCdup ? SENT_CDUP :
+ serverState() == fssHandleCwd ? SENT_CWD :
+ serverState() == fssHandleFeat ? SENT_FEAT :
+ serverState() == fssHandleDataRequest ? SENT_DATA_REQUEST :
+ serverState() == fssHandleUploadRequest ? SENT_DATA_REQUEST :
+ serverState() == fssConnected ? SENT_USER :
+ serverState() == fssHandlePass ? SENT_PASS :
SENT_COMMAND;
}
void
Ftp::Relay::readReply()
{
- assert(clientState() == fssConnected ||
- clientState() == fssHandleUploadRequest);
+ assert(serverState() == fssConnected ||
+ serverState() == fssHandleUploadRequest);
if (100 <= ctrl.replycode && ctrl.replycode < 200)
forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
void
Ftp::Relay::readFeatReply()
{
- assert(clientState() == fssHandleFeat);
+ assert(serverState() == fssHandleFeat);
if (100 <= ctrl.replycode && ctrl.replycode < 200)
return; // ignore preliminary replies
void
Ftp::Relay::readPasvReply()
{
- assert(clientState() == fssHandlePasv || clientState() == fssHandleEpsv || clientState() == fssHandlePort || clientState() == fssHandleEprt);
+ assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
if (100 <= ctrl.replycode && ctrl.replycode < 200)
return; // ignore preliminary replies
void
Ftp::Relay::readDataReply()
{
- assert(clientState() == fssHandleDataRequest ||
- clientState() == fssHandleUploadRequest);
+ assert(serverState() == fssHandleDataRequest ||
+ serverState() == fssHandleUploadRequest);
if (ctrl.replycode == 125 || ctrl.replycode == 150) {
- if (clientState() == fssHandleDataRequest)
+ if (serverState() == fssHandleDataRequest)
forwardPreliminaryReply(&Ftp::Relay::startDataDownload);
- else // clientState() == fssHandleUploadRequest
+ else // serverState() == fssHandleUploadRequest
forwardPreliminaryReply(&Ftp::Relay::startDataUpload);
} else
forwardReply();
if (!fwd->request->clientConnectionManager->port->ftp_track_dirs)
return false;
- debugs(9, 5, "Start directory tracking");
+ debugs(9, 5, "start directory tracking");
savedReply.message = ctrl.message;
savedReply.lastCommand = ctrl.last_command;
savedReply.lastReply = ctrl.last_reply;
void
Ftp::Relay::stopDirTracking()
{
- debugs(9, 5, "Got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply);
+ debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply);
if (ctrl.replycode == 257)
updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply);
void
Ftp::Relay::readCwdOrCdupReply()
{
- assert(clientState() == fssHandleCwd ||
- clientState() == fssHandleCdup);
+ assert(serverState() == fssHandleCwd ||
+ serverState() == fssHandleCdup);
- debugs(9, 5, HERE << "Got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
+ debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
if (100 <= ctrl.replycode && ctrl.replycode < 200)
return;
void
Ftp::Relay::readTransferDoneReply()
{
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
if (ctrl.replycode != 226 && ctrl.replycode != 250) {
- debugs(9, DBG_IMPORTANT, HERE << "Got code " << ctrl.replycode <<
- " after reading data");
+ debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode <<
+ " after reading response data");
}
serverComplete();
void
Ftp::Relay::dataChannelConnected(const Comm::ConnectionPointer &conn, Comm::Flag err, int xerrno)
{
- debugs(9, 3, HERE);
+ debugs(9, 3, status());
data.opener = NULL;
if (err != Comm::OK) {
- debugs(9, 2, HERE << "Failed to connect FTP server data channel.");
+ debugs(9, 2, "failed to connect FTP server data channel");
forwardError(ERR_CONNECT_FAIL, xerrno);
return;
}
- debugs(9, 2, HERE << "Connected FTP server data channel: " << conn);
+ debugs(9, 2, "connected FTP server data channel: " << conn);
data.opened(conn, dataCloser());
namespace Ftp {
static void PrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = "");
static bool SupportedCommand(const String &name);
+static bool CommandHasPathParameter(const String &cmd);
};
Ftp::Server::Server(const MasterXaction::Pointer &xact):
clientConnection->local.toUrl(buf, MAX_IPSTRLEN);
host = buf;
calcUri();
- debugs(33, 5, HERE << "FTP transparent URL: " << uri);
+ debugs(33, 5, "FTP transparent URL: " << uri);
}
writeEarlyReply(220, "Service ready");
if (availSpace <= 0)
return;
- debugs(33, 4, HERE << dataConn << ": reading FTP data...");
+ debugs(33, 4, dataConn << ": reading FTP data...");
typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
reader = JobCallback(33, 5, Dialer, this, Ftp::Server::readUploadData);
void
Ftp::Server::readUploadData(const CommIoCbParams &io)
{
- debugs(33,5,HERE << io.conn << " size " << io.size);
+ debugs(33, 5, io.conn << " size " << io.size);
Must(reader != NULL);
reader = NULL;
uploadAvailSize += io.size;
shovelUploadData();
} else if (io.size == 0) {
- debugs(33, 5, HERE << io.conn << " closed");
+ debugs(33, 5, io.conn << " closed");
closeDataConnection();
if (uploadAvailSize <= 0)
finishDechunkingRequest(true);
}
} else { // not Comm::Flags::OK or unexpected read
- debugs(33, 5, HERE << io.conn << " closed");
+ debugs(33, 5, io.conn << " closed");
closeDataConnection();
finishDechunkingRequest(false);
}
{
assert(bodyPipe != NULL);
- debugs(33,5, HERE << "handling FTP request data for " << clientConnection);
+ debugs(33, 5, "handling FTP request data for " << clientConnection);
const size_t putSize = bodyPipe->putMoreData(uploadBuf,
uploadAvailSize);
if (putSize > 0) {
if (params.flag != Comm::OK) {
// Its possible the call was still queued when the client disconnected
- debugs(33, 2, "ftpAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno));
+ debugs(33, 2, s->listenConn << ": FTP accept failure: " << xstrerr(params.xerrno));
return;
}
- debugs(33, 4, HERE << params.conn << ": accepted");
+ debugs(33, 4, params.conn << ": accepted");
fd_note(params.conn->fd, "client ftp connect");
if (s->tcp_keepalive.enabled)
ConnStateData::clientPinnedConnectionClosed(io);
// if the server control connection is gone, reset state to login again
- // TODO: merge with similar code in ftpHandleUserRequest()
- debugs(33, 5, "will need to re-login due to FTP server closure");
- master.clientReadGreeting = false;
- changeState(fssBegin, "server closure");
+ resetLogin("control connection closure");
+
// XXX: Not enough. Gateway::ServerStateData::sendCommand() will not
// re-login because clientState() is not ConnStateData::FTP_CONNECTED.
}
+/// clear client and server login-related state after the old login is gone
+void
+Ftp::Server::resetLogin(const char *reason)
+{
+ debugs(33, 5, "will need to re-login due to " << reason);
+ master.clientReadGreeting = false;
+ changeState(fssBegin, reason);
+}
+
/// computes uri member from host and, if tracked, working dir with file name
void
Ftp::Server::calcUri(const char *file)
}
if (Comm::IsConnOpen(dataListenConn)) {
- debugs(33, 5, HERE << "FTP closing client data listen socket: " <<
+ debugs(33, 5, "FTP closing client data listen socket: " <<
*dataListenConn);
dataListenConn->close();
}
}
if (Comm::IsConnOpen(dataConn)) {
- debugs(33, 5, HERE << "FTP closing client data connection: " <<
+ debugs(33, 5, "FTP closing client data connection: " <<
*dataConn);
dataConn->close();
}
void
Ftp::Server::writeEarlyReply(const int code, const char *msg)
{
- debugs(33, 7, HERE << code << ' ' << msg);
+ debugs(33, 7, code << ' ' << msg);
assert(99 < code && code < 1000);
MemBuf mb;
void
Ftp::Server::writeCustomReply(const int code, const char *msg, const HttpReply *reply)
{
- debugs(33, 7, HERE << code << ' ' << msg);
+ debugs(33, 7, code << ' ' << msg);
assert(99 < code && code < 1000);
const bool sendDetails = reply != NULL &&
/// whether the given FTP command has a pathname parameter
static bool
-ftpHasPathParameter(const String &cmd)
+Ftp::CommandHasPathParameter(const String &cmd)
{
static const char *pathCommandsStr[]= {"CWD","SMNT", "RETR", "STOR", "APPE",
"RNFR", "RNTO", "DELE", "RMD", "MKD",
}
if (eor == NULL) {
- debugs(33, 5, HERE << "Incomplete request, waiting for end of request");
+ debugs(33, 5, "Incomplete request, waiting for end of request");
return NULL;
}
const char *boc = inBuf; // beginning of command
while (boc < eor && isspace(*boc)) ++boc;
if (boc >= eor) {
- debugs(33, 5, HERE << "Empty request, ignoring");
+ debugs(33, 5, "Empty request, ignoring");
consumeInput(req_sz);
return NULL;
}
} else
bop = NULL;
- debugs(33, 7, HERE << "Parsed FTP command " << boc << " with " <<
+ debugs(33, 7, "Parsed FTP command " << boc << " with " <<
(bop == NULL ? "no " : "") << "parameters" <<
(bop != NULL ? ": " : "") << bop);
consumeInput(req_sz);
- if (!master.clientReadGreeting) {
- // the first command must be USER
- if (!pinning.pinned && cmd.caseCmp("USER") != 0) {
- writeEarlyReply(530, "Must login first");
- return NULL;
+ // interception cases do not need USER to calculate the uri
+ if (!transparent()) {
+ if (!master.clientReadGreeting) {
+ // the first command must be USER
+ if (!pinning.pinned && cmd.caseCmp("USER") != 0) {
+ writeEarlyReply(530, "Must login first");
+ return NULL;
+ }
}
- }
- // We need to process USER request now because it sets ftp server Hostname.
- if (cmd.caseCmp("USER") == 0 && !handleUserRequest(cmd, params))
- return NULL;
+ // process USER request now because it sets FTP peer host name
+ if (cmd.caseCmp("USER") == 0 && !handleUserRequest(cmd, params))
+ return NULL;
+ }
if (!Ftp::SupportedCommand(cmd)) {
writeEarlyReply(502, "Unknown or unsupported command");
!cmd.caseCmp("APPE") || !cmd.caseCmp("STOR") || !cmd.caseCmp("STOU") ?
Http::METHOD_PUT : Http::METHOD_GET;
- const char *aPath = params.size() > 0 && ftpHasPathParameter(cmd) ?
+ const char *aPath = params.size() > 0 && CommandHasPathParameter(cmd) ?
params.termedBuf() : NULL;
calcUri(aPath);
char *newUri = xstrdup(uri.termedBuf());
HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method);
if (!request) {
- debugs(33, 5, HERE << "Invalid FTP URL: " << uri);
+ debugs(33, 5, "Invalid FTP URL: " << uri);
writeEarlyReply(501, "Invalid host");
uri.clean();
safe_free(newUri);
return;
}
- debugs(33, 7, HERE << data.length);
+ debugs(33, 7, data.length);
if (data.length <= 0) {
replyDataWritingCheckpoint(); // skip the actual write call
return;
if (io.flag != Comm::OK) {
- debugs(33, 3, HERE << "FTP reply data writing failed: " <<
+ debugs(33, 3, "FTP reply data writing failed: " <<
xstrerr(io.xerrno));
closeDataConnection();
writeCustomReply(426, "Data connection error; transfer aborted");
getCurrentContext()->pullData();
return;
case STREAM_COMPLETE:
- debugs(33, 3, HERE << "FTP reply data transfer successfully complete");
+ debugs(33, 3, "FTP reply data transfer successfully complete");
writeCustomReply(226, "Transfer complete");
break;
case STREAM_UNPLANNED_COMPLETE:
- debugs(33, 3, HERE << "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
+ debugs(33, 3, "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
writeCustomReply(451, "Server error; transfer aborted");
break;
case STREAM_FAILED:
- debugs(33, 3, HERE << "FTP reply data transfer failed: STREAM_FAILED");
+ debugs(33, 3, "FTP reply data transfer failed: STREAM_FAILED");
writeCustomReply(451, "Server error; transfer aborted");
break;
default:
Must(header.has(HDR_FTP_STATUS));
Must(header.has(HDR_FTP_REASON));
const int scode = header.getInt(HDR_FTP_STATUS);
- debugs(33, 7, HERE << "scode: " << scode);
+ debugs(33, 7, "scode: " << scode);
// Status 125 or 150 implies upload or data request, but we still check
// the state in case the server is buggy.
return false;
}
+ // find the [end of] user name
const String::size_type eou = params.rfind('@');
if (eou == String::npos || eou + 1 >= params.size()) {
writeEarlyReply(501, "Missing host");
return false;
}
+ // const String login = params.substr(0, eou);
- const String login = params.substr(0, eou);
+ // Determine the intended destination.
host = params.substr(eou + 1, params.size());
// If we can parse it as raw IPv6 address, then surround with "[]".
// Otherwise (domain, IPv4, [bracketed] IPv6, garbage, etc), use as is.
if (!master.clientReadGreeting) {
debugs(11, 3, "set URI to " << uri);
} else if (oldUri.caseCmp(uri) == 0) {
- debugs(11, 5, "keep URI as " << oldUri);
+ debugs(11, 5, "kept URI as " << oldUri);
} else {
debugs(11, 3, "reset URI from " << oldUri << " to " << uri);
closeDataConnection();
- master.clientReadGreeting = false;
unpinConnection(true); // close control connection to peer
- changeState(fssBegin, "URI reset");
+ resetLogin("URI reset");
}
params.cut(eou);
bool
Ftp::Server::handleFeatRequest(String &cmd, String ¶ms)
{
- changeState(fssHandleFeat, "ftpHandleFeatRequest");
+ changeState(fssHandleFeat, "handleFeatRequest");
return true;
}
return false;
}
- changeState(fssHandlePasv, "ftpHandlePasvRequest");
+ changeState(fssHandlePasv, "handlePasvRequest");
// no need to fake PASV request via setDataCommand() in true PASV case
return true;
}
if (!createDataConnection(cltAddr))
return false;
- changeState(fssHandlePort, "ftpHandlePortRequest");
+ changeState(fssHandlePort, "handlePortRequest");
setDataCommand();
return true; // forward our fake PASV request
}
if (!checkDataConnPre())
return false;
- changeState(fssHandleDataRequest, "ftpHandleDataRequest");
+ changeState(fssHandleDataRequest, "handleDataRequest");
return true;
}
if (!checkDataConnPre())
return false;
- changeState(fssHandleUploadRequest, "ftpHandleDataRequest");
+ changeState(fssHandleUploadRequest, "handleDataRequest");
return true;
}
if (!createDataConnection(cltAddr))
return false;
- changeState(fssHandleEprt, "ftpHandleEprtRequest");
+ changeState(fssHandleEprt, "handleEprtRequest");
setDataCommand();
return true; // forward our fake PASV request
}
return false;
}
- changeState(fssHandleEpsv, "ftpHandleEpsvRequest");
+ changeState(fssHandleEpsv, "handleEpsvRequest");
setDataCommand();
return true; // forward our fake PASV request
}
bool
Ftp::Server::handleCwdRequest(String &cmd, String ¶ms)
{
- changeState(fssHandleCwd, "ftpHandleCwdRequest");
+ changeState(fssHandleCwd, "handleCwdRequest");
return true;
}
bool
Ftp::Server::handlePassRequest(String &cmd, String ¶ms)
{
- changeState(fssHandlePass, "ftpHandlePassRequest");
+ changeState(fssHandlePass, "handlePassRequest");
return true;
}
bool
Ftp::Server::handleCdupRequest(String &cmd, String ¶ms)
{
- changeState(fssHandleCdup, "ftpHandleCdupRequest");
+ changeState(fssHandleCdup, "handleCdupRequest");
return true;
}
private:
void doProcessRequest();
void shovelUploadData();
+ void resetLogin(const char *reason);
String uri; ///< a URI reconstructed from various FTP message details
String host; ///< intended dest. of a transparently intercepted FTP conn
{
// Release the main ports as early as possible
- // clear http_port, https_port and ftp_port lists
+ // clear http_port, https_port, and ftp_port lists
clientConnectionsClose();
// clear icp_port's