40const char *
const crlf =
"\r\n";
47 unsigned const char *p;
50 for (p = (
unsigned const char *)buf, n = 1; *p; ++n, ++p)
56 for (p = (
unsigned const char *)buf, r=(
unsigned char *)ret; *p; ++p) {
68 assert((r - (
unsigned char *)ret) == n );
77 return ToSBuf(
"FTP_REPLY_CODE=", completionCode);
83 return ToSBuf(
"FTP reply with completion code ", completionCode);
97 assert(aCloser !=
nullptr);
141 last_command(nullptr),
178 import.toStr(addrBuf,
sizeof(addrBuf));
181 port =
import.port();
192 old_request(nullptr),
194 shortenReadTimeout(false)
219 scheduleReadControlReply(0);
225 if (data.readBuf ==
nullptr) {
226 data.readBuf =
new MemBuf;
238 debugs(9, 3,
"closing FTP server FD " << ctrl.conn->fd <<
", this " <<
this);
239 fwd->unregister(ctrl.conn);
244 debugs(9, 3,
"closing FTP data FD " << data.conn->fd <<
", this " <<
this);
248 debugs(9, 3,
"FTP ctrl and data connections closed. this " <<
this);
266 debugs(9, 3,
"entry-null=" << (entry?entry->isEmpty():0) <<
", entry=" << entry);
268 const char *command, *reply;
272 debugs(9, 6,
"error=" << err->
type <<
", code=" << xerrno <<
284 ctrl.message =
nullptr;
287 command = old_request;
289 command = ctrl.last_command;
291 if (command && strncmp(command,
"PASS", 4) == 0)
292 command =
"PASS <yourpassword>";
297 reply = ctrl.last_reply;
331 if (buffered_ok && ctrl.offset > 0) {
333 handleControlReply();
337 debugs(9, 3,
"cannot read without ctrl " << ctrl.conn);
349 if (ctrl.offset >= maxSize) {
355 if (ctrl.offset == ctrl.size) {
356 const auto newSize = (ctrl.size <= maxSize/2) ? (ctrl.size*2) : maxSize;
357 Assure(newSize > ctrl.size);
358 ctrl.buf =
static_cast<char*
>(
memReallocBuf(ctrl.buf, newSize, &ctrl.size));
359 Assure(ctrl.offset < ctrl.size);
362 const time_t tout = shortenReadTimeout ?
365 shortenReadTimeout =
false;
376 Assure(maxOffset > ctrl.offset);
377 Assure(maxOffset <= ctrl.size);
378 const auto maxReadSize = maxOffset - ctrl.offset;
379 comm_read(ctrl.conn, ctrl.buf + ctrl.offset, maxReadSize, reader);
386 debugs(9, 3,
"FD " << io.
fd <<
", Read " << io.
size <<
" bytes");
397 if (abortOnData(
"entry aborted during control reply read"))
401 assert(ctrl.offset < ctrl.size);
409 "ERROR: FTP control reply read failure: " <<
xstrerr(io.
xerrno));
412 scheduleReadControlReply(0);
428 abortAll(
"zero control reply read");
432 unsigned int len =io.
size + ctrl.offset;
437 handleControlReply();
445 size_t bytes_used = 0;
448 if (!parseControlReply(bytes_used)) {
450 scheduleReadControlReply(0);
460 assert(ctrl.replycode >= 0);
463 if (ctrl.offset == bytes_used) {
468 assert(bytes_used < ctrl.offset);
469 ctrl.offset -= bytes_used;
470 memmove(ctrl.buf, ctrl.buf + bytes_used, ctrl.offset);
473 debugs(9, 3,
"state=" << state <<
", code=" << ctrl.replycode);
479 int code = ctrl.replycode;
484 debugs(9, 5,
"The control connection to the remote end is closed");
489 debugs(9, 2,
"PASV not supported by remote end");
495 debugs(9, 5,
"scanning: " << ctrl.last_reply);
497 buf = ctrl.last_reply + strcspn(ctrl.last_reply,
"0123456789");
500 fd_table[ctrl.conn->fd].ipaddr :
nullptr;
503 ctrl.conn->remote <<
": " << ctrl.last_reply);
515 int code = ctrl.replycode;
520 debugs(9, 5,
"The control connection to the remote end is closed");
524 if (code != 229 && code != 522) {
529 debugs(9,
DBG_IMPORTANT,
"ERROR: Broken FTP Server at " << ctrl.conn->remote <<
". Wrong accept code for EPSV");
531 debugs(9, 2,
"EPSV not supported by remote end");
533 return sendPassive();
544 debugs(9, 5,
"scanning: " << ctrl.last_reply);
545 buf = ctrl.last_reply;
546 while (buf !=
nullptr && *buf !=
'\0' && *buf !=
'\n' && *buf !=
'(')
548 if (buf !=
nullptr && *buf ==
'\n')
551 if (buf ==
nullptr || *buf ==
'\0') {
553 debugs(9,
DBG_IMPORTANT,
"ERROR: Broken FTP Server at " << ctrl.conn->remote <<
". 522 error missing protocol negotiation hints");
554 return sendPassive();
555 }
else if (strcmp(buf,
"(1)") == 0) {
557 return sendPassive();
558 }
else if (strcmp(buf,
"(2)") == 0) {
559 if (Ip::EnableIpv6) {
561 if (state == SENT_EPSV_2) {
565 return sendPassive();
570 return sendPassive();
574 debugs(9,
DBG_IMPORTANT,
"WARNING: Server at " << ctrl.conn->remote <<
" sent unknown protocol negotiation hint: " << buf);
575 return sendPassive();
585 debugs(9, 5,
"scanning: " << ctrl.last_reply);
587 buf = ctrl.last_reply + strcspn(ctrl.last_reply,
"(");
591 int n = sscanf(buf,
"(%c%c%c%hu%c)", &h1, &h2, &h3, &
port, &h4);
593 if (n < 4 || h1 != h2 || h1 != h3 || h1 != h4) {
595 ctrl.conn->remote <<
": " <<
598 return sendPassive();
603 ctrl.conn->remote <<
": " <<
606 return sendPassive();
612 ctrl.conn->remote <<
": " <<
615 return sendPassive();
619 remoteAddr = ctrl.conn->remote;
621 data.addr(remoteAddr);
633 debugs(9, 3,
"EPRT disabled by local administrator");
639 if (!openListenSocket()) {
644 debugs(9, 3,
"Listening for FTP data connection with FD " << data.conn);
656 debugs(9, 3,
"Listening for FTP data connection on port" <<
comm_local_port(data.conn->fd) <<
" or port?" << data.conn->local.port());
657 mb.
appendf(
"EPRT |%d|%s|%d|%s",
658 ( data.conn->local.isIPv6() ? 2 : 1 ),
687 debugs(9,
DBG_IMPORTANT,
"FTP does not allow PASV method after 'EPSV ALL' has been sent.");
715 if (ctrl.conn->local.isIPv6()) {
716 debugs(9, 5,
"FTP Channel is IPv6 (" << ctrl.conn->remote <<
") attempting EPSV 2 after EPSV ALL has failed.");
724 if (ctrl.conn->local.isIPv4()) {
725 debugs(9, 5,
"FTP Channel is IPv4 (" << ctrl.conn->remote <<
") attempting EPSV 1 after EPSV ALL has failed.");
730 debugs(9,
DBG_IMPORTANT,
"FTP does not allow PASV method after 'EPSV ALL' has been sent.");
737 debugs(9, 5,
"FTP Channel (" << ctrl.conn->remote <<
") rejects EPSV connection attempts. Trying PASV instead.");
749 debugs(9, 5,
"EPSV support manually disabled. Sending PASV for FTP Channel (" << ctrl.conn->remote <<
")");
753 debugs(9, 5,
"EPSV ALL manually enabled. Attempting with FTP Channel (" << ctrl.conn->remote <<
")");
755 state = SENT_EPSV_ALL;
757 if (ctrl.conn->local.isIPv6()) {
758 debugs(9, 5,
"FTP Channel (" << ctrl.conn->remote <<
"). Sending default EPSV 2");
762 if (ctrl.conn->local.isIPv4()) {
763 debugs(9, 5,
"Channel (" << ctrl.conn->remote <<
"). Sending default EPSV 1");
774 ctrl.message =
nullptr;
779 shortenReadTimeout =
true;
787 debugs(9, 5,
"The control connection to the remote end is closed");
795 ctrl.last_command =
xstrdup(
"Connect to server data port");
799 conn->
setAddrs(ctrl.conn->local, data.host);
802 conn->
tos = ctrl.conn->tos;
803 conn->
nfmark = ctrl.conn->nfmark;
812 cs->setHost(data.host);
813 dataConnWait.start(cs, callback);
836 data.conn->noteClosure();
837 if (data.listenConn !=
nullptr) {
838 data.listenConn->close();
839 data.listenConn =
nullptr;
849 debugs(9, 2,
"ftp<< " << buf);
860 ctrl.last_command = ebuf;
863 debugs(9, 2,
"cannot send to closing ctrl " << ctrl.conn);
871 Comm::Write(ctrl.conn, ctrl.last_command, strlen(ctrl.last_command), call,
nullptr);
873 scheduleReadControlReply(0);
880 debugs(9, 5,
"wrote " << io.
size <<
" bytes");
905 ctrl.conn->noteClosure();
907 doneWithFwd =
"ctrlClosed()";
908 mustStop(
"Ftp::Client::ctrlClosed");
914 debugs(9, 4, io.
conn <<
": '" << entry->url() <<
"'" );
916 if (abortOnBadEntry(
"entry went bad while waiting for a timeout"))
933 waitingForDelayAwareReadChance =
false;
934 data.read_pending =
false;
935 maybeReadVirginBody();
945 if (data.read_pending)
952 const auto read_sz = calcBufferSpaceToReserve(data.readBuf->spaceSize(), data.readBuf->spaceSize());
954 debugs(9, 9,
"FTP may read up to " << read_sz <<
" bytes");
959 data.read_pending =
true;
966 debugs(9,5,
"queueing read on FD " << data.conn->fd);
968 const auto amountToRead = entry->bytesWanted(
Range<size_t>(0, read_sz));
970 if (amountToRead <= 0) {
977 comm_read(data.conn, data.readBuf->space(), amountToRead, readCallback);
986 data.read_pending =
false;
988 debugs(9, 3,
"FD " << io.
fd <<
" Read " << io.
size <<
" bytes");
1001 abortOnData(
"entry aborted during dataRead");
1006 debugs(9, 5,
"appended " << io.
size <<
" bytes to readBuf");
1007 data.readBuf->appended(io.
size);
1009 DelayId delayId = entry->mem_obj->mostBytesAllowed();
1014 for (j = io.
size - 1, bin = 0; j; ++bin)
1025 maybeReadVirginBody();
1031 }
else if (io.
size == 0) {
1032 debugs(9, 3,
"Calling dataComplete() because io.size == 0");
1075 scheduleReadControlReply(1);
1081 debugs(9, 3,
"aborting transaction for " << reason <<
1082 "; FD " << (ctrl.conn!=
nullptr?ctrl.conn->fd:-1) <<
", Data FD " << (data.conn!=
nullptr?data.conn->fd:-1) <<
", this " <<
this);
1135 auto headGuard = std::unique_ptr<wordlist, decltype(headDeleter)>(
head, headDeleter);
1143 const size_t len = ctrl.offset;
1144 const auto sbufOwner = std::unique_ptr<void, decltype(&xfree)>(
xmalloc(len + 1),
xfree);
1145 const auto sbuf =
static_cast<char*
>(sbufOwner.get());
1147 end = sbuf + len - 1;
1149 while (*end !=
'\r' && *end !=
'\n' && end > sbuf)
1152 usable = end - sbuf;
1154 debugs(9, 3,
"usable = " << usable);
1157 debugs(9, 3,
"didn't find end of line");
1161 debugs(9, 3, len <<
" bytes to play with");
1164 s += strspn(s,
crlf);
1167 size_t replyLength = 0;
1169 for (; s < end; s += strcspn(s,
crlf), s += strspn(s,
crlf)) {
1173 debugs(9, 5,
"s = {" << s <<
"}");
1175 linelen = strcspn(s,
crlf) + 1;
1176 replyLength += linelen;
1185 complete = (*s >=
'0' && *s <=
'9' && *(s + 3) ==
' ');
1189 headGuard.reset(list);
1202 ctrl.replycode = atoi(list->
key);
1210 bytesUsed =
static_cast<size_t>(s - sbuf);
1216 ctrl.message = headGuard.release();
1217 assert(ctrl.replycode >= 0);
#define Assure(condition)
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
#define Here()
source code location of the caller
void comm_read(const Comm::ConnectionPointer &conn, char *buf, int len, AsyncCall::Pointer &callback)
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
void error(char *format,...)
squidaio_request_t * head
#define SQUID_TCP_SO_RCVBUF
Acl::Answer const & fastCheck()
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
int fd
FD which the call was about. Set by the async call creator.
Comm::Flag flag
comm layer result status.
Comm::ConnectionPointer conn
void setAddrs(const Ip::Address &aLocal, const Ip::Address &aRemote)
void leaveOrphanage()
resume relying on owner(s) to initiate an explicit connection closure
HttpRequestPointer request
struct ErrorState::@47 ftp
Http::StatusCode httpStatus
void close()
planned close: removes the close handler and calls comm_close
void opened(const Comm::ConnectionPointer &conn, const AsyncCall::Pointer &aCloser)
called after the socket is opened, sets up close handler
void clear()
remove the close handler, leave connection open
FTP client functionality shared among FTP Gateway and Relay clients.
virtual Http::StatusCode failedHttpStatus(err_type &error)
void start() override
called by AsyncStart; do not call directly
bool handleEpsvReply(Ip::Address &remoteAddr)
virtual void handleControlReply()
void dataRead(const CommIoCbParams &io)
void closeServer() override
void scheduleReadControlReply(int buffered_ok)
void sentRequestBody(const CommIoCbParams &io) override
void writeCommand(const char *buf)
virtual void timeout(const CommTimeoutCbParams &io)
read timeout handler
bool handlePasvReply(Ip::Address &remoteAddr)
void switchTimeoutToDataChannel()
void connectDataChannel()
bool doneWithServer() const override
void writeCommandCallback(const CommIoCbParams &io)
Client(FwdState *fwdState)
void readControlReply(const CommIoCbParams &io)
void doneSendingRequestBody() override
void maybeReadVirginBody() override
read response data from the network
const Comm::ConnectionPointer & dataConnection() const override
virtual void failed(err_type error=ERR_NONE, int xerrno=0, ErrorState *ftperr=nullptr)
handle a fatal transaction error, closing the control connection
CtrlChannel ctrl
FTP control channel state.
bool parseControlReply(size_t &bytesUsed)
AsyncCall::Pointer dataCloser()
creates a data channel Comm close callback
void ctrlClosed(const CommCloseCbParams &io)
handler called by Comm when FTP control channel is closed unexpectedly
virtual void dataChannelConnected(const CommConnectCbParams &io)=0
void abortAll(const char *reason) override
abnormal transaction termination; reason is for debugging only
virtual void dataClosed(const CommCloseCbParams &io)
handler called by Comm when FTP data channel is closed unexpectedly
void noteDelayAwareReadChance() override
void addr(const Ip::Address &addr)
import host and port
SBuf verbose(const HttpRequestPointer &) const override
SBuf brief() const override
Comm::ConnectionPointer const & serverConnection() const
unsigned short port() const
void init(mb_size_t szInit, mb_size_t szMax)
char * content()
start of the added data
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
size_t maxReplyHeaderSize
struct SquidConfig::@77 Timeout
struct SquidConfig::@92 Ftp
struct SquidConfig::@91 accessList
struct StatCounters::@105::@115 ftp
struct StatCounters::@105 server
struct StatCounters::@105::@115 all
static size_type RawSizeMaxXXX()
static ErrorDetail::Pointer NewIfAny(const int errorNo)
an std::runtime_error with thrower location info
void commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
unsigned short comm_local_port(int fd)
void commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
int ignoreErrno(int ierrno)
A const & max(A const &lhs, A const &rhs)
A const & min(A const &lhs, A const &rhs)
#define debugs(SECTION, LEVEL, CONTENT)
#define EBIT_TEST(flag, bit)
void fd_bytes(const int fd, const int len, const IoDirection direction)
#define MAX_IPSTRLEN
Length of buffer that needs to be allocated to old a null-terminated IP-string.
void memFreeBuf(size_t size, void *)
void * memAllocBuf(size_t net_size, size_t *gross_size)
void * memReallocBuf(void *buf, size_t net_size, size_t *gross_size)
bool IsConnOpen(const Comm::ConnectionPointer &conn)
void Write(const Comm::ConnectionPointer &conn, const char *buf, int size, AsyncCall::Pointer &callback, FREE *free_func)
static char * escapeIAC(const char *buf)
bool ParseIpPort(const char *buf, const char *forceIp, Ip::Address &addr)
parses and validates "A1,A2,A3,A4,P1,P2" IP,port sequence
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
void wordlistDestroy(wordlist **list)
destroy a wordlist
const char * xstrerr(int error)
char * xstrncpy(char *dst, const char *src, size_t n)