4 * DEBUG: section 09 File Transfer Protocol (FTP)
5 * AUTHOR: Harvest Derived
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "comm/ConnOpener.h"
38 #include "comm/TcpAcceptor.h"
39 #include "comm/Write.h"
40 #include "CommCalls.h"
41 #include "compat/strtoll.h"
42 #include "errorpage.h"
45 #include "html_quote.h"
46 #include "HttpHdrContRange.h"
47 #include "HttpHeader.h"
48 #include "HttpHeaderRange.h"
49 #include "HttpReply.h"
50 #include "HttpRequest.h"
56 #include "SquidString.h"
57 #include "SquidTime.h"
58 #include "StatCounters.h"
60 #include "URLScheme.h"
64 #include "DelayPools.h"
65 #include "MemObject.h"
73 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
74 \ingroup ServerProtocolFTPAPI
77 /// \ingroup ServerProtocolFTPInternal
78 static const char *const crlf
= "\r\n";
80 #define CTRL_BUFLEN 1024
81 /// \ingroup ServerProtocolFTPInternal
82 static char cbuf
[CTRL_BUFLEN
];
84 /// \ingroup ServerProtocolFTPInternal
110 /// \ingroup ServerProtocolFTPInternal
114 bool pasv_supported
; ///< PASV command is allowed
115 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
117 bool pasv_failed
; // was FwdState::flags.ftp_pasv_failed
120 bool authenticated
; ///< authentication success
121 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
122 bool tried_auth_nopass
; ///< auth tried username with no password already.
126 bool skip_whitespace
;
128 bool http_header_sent
;
138 bool listformat_unknown
;
140 bool completed_forwarding
;
145 /// \ingroup ServerProtocolFTPInternal
146 typedef void (FTPSM
) (FtpStateData
*);
148 /// common code for FTP control and data channels
149 /// does not own the channel descriptor, which is managed by FtpStateData
155 /// called after the socket is opened, sets up close handler
156 void opened(const Comm::ConnectionPointer
&conn
, const AsyncCall::Pointer
&aCloser
);
158 /** Handles all operations needed to properly close the active channel FD.
159 * clearing the close handler, clearing the listen socket properly, and calling comm_close
163 void clear(); ///< just drops conn and close handler. does not close active connections.
165 Comm::ConnectionPointer conn
; ///< channel descriptor
167 /** A temporary handle to the connection being listened on.
168 * Closing this will also close the waiting Data channel acceptor.
169 * If a data connection has already been accepted but is still waiting in the event queue
170 * the callback will still happen and needs to be handled (usually dropped).
172 Comm::ConnectionPointer listenConn
;
174 AsyncCall::Pointer opener
; ///< Comm opener handler callback.
176 AsyncCall::Pointer closer
; ///< Comm close handler callback
179 /// \ingroup ServerProtocolFTPInternal
180 class FtpStateData
: public ServerStateData
184 void *operator new (size_t);
185 void operator delete (void *);
186 void *toCbdata() { return this; }
188 FtpStateData(FwdState
*, const Comm::ConnectionPointer
&conn
);
191 char password
[MAX_URL
];
206 int64_t restart_offset
;
214 MemBuf listing
; ///< FTP directory listing in HTML format.
216 // \todo: optimize ctrl and data structs member order, to minimize size
217 /// FTP control channel info; the channel is opened once per transaction
218 struct CtrlChannel
: public FtpChannel
{
228 /// FTP data channel info; the channel may be opened/closed a few times
229 struct DataChannel
: public FtpChannel
{
236 struct _ftp_flags flags
;
239 CBDATA_CLASS(FtpStateData
);
242 // these should all be private
243 virtual void start();
244 void loginParser(const char *, int escaped
);
246 void appendSuccessHeader();
247 void hackShortcut(FTPSM
* nextState
);
248 void failed(err_type
, int xerrno
);
249 void failedErrorMessage(err_type
, int xerrno
);
251 void scheduleReadControlReply(int);
252 void handleControlReply();
255 MemBuf
*htmlifyListEntry(const char *line
);
256 void completedListing(void);
258 void dataRead(const CommIoCbParams
&io
);
260 /// ignore timeout on CTRL channel. set read timeout on DATA channel.
261 void switchTimeoutToDataChannel();
262 /// create a data channel acceptor and start listening.
263 void listenForDataChannel(const Comm::ConnectionPointer
&conn
, const char *note
);
265 int checkAuth(const HttpHeader
* req_hdr
);
267 void buildTitleUrl();
268 void writeReplyBody(const char *, size_t len
);
269 void printfReplyBody(const char *fmt
, ...);
270 virtual const Comm::ConnectionPointer
& dataConnection() const;
271 virtual void maybeReadVirginBody();
272 virtual void closeServer();
273 virtual void completeForwarding();
274 virtual void abortTransaction(const char *reason
);
275 void processHeadResponse();
276 void processReplyBody();
277 void writeCommand(const char *buf
);
278 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
279 int64_t getCurrentOffset() const { return currentOffset
; }
281 static CNCB ftpPasvCallback
;
282 static PF ftpDataWrite
;
283 void ftpTimeout(const CommTimeoutCbParams
&io
);
284 void ctrlClosed(const CommCloseCbParams
&io
);
285 void dataClosed(const CommCloseCbParams
&io
);
286 void ftpReadControlReply(const CommIoCbParams
&io
);
287 void ftpWriteCommandCallback(const CommIoCbParams
&io
);
288 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
290 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, const char *realm
);
291 const char *ftpRealm(void);
292 void loginFailed(void);
293 static wordlist
*ftpParseControlReply(char *, size_t, int *, size_t *);
295 // sending of the request body to the server
296 virtual void sentRequestBody(const CommIoCbParams
&);
297 virtual void doneSendingRequestBody();
299 virtual void haveParsedReplyHeaders();
301 virtual bool doneWithServer() const;
302 virtual bool haveControlChannel(const char *caller_name
) const;
303 AsyncCall::Pointer
dataCloser(); /// creates a Comm close callback
304 AsyncCall::Pointer
dataOpener(); /// creates a Comm connect callback
307 // BodyConsumer for HTTP: consume request body.
308 virtual void handleRequestBodyProducerAborted();
311 CBDATA_CLASS_INIT(FtpStateData
);
314 FtpStateData::operator new (size_t)
316 CBDATA_INIT_TYPE(FtpStateData
);
317 FtpStateData
*result
= cbdataAlloc(FtpStateData
);
322 FtpStateData::operator delete (void *address
)
324 FtpStateData
*t
= static_cast<FtpStateData
*>(address
);
328 /// \ingroup ServerProtocolFTPInternal
338 /// \ingroup ServerProtocolFTPInternal
339 #define FTP_LOGIN_ESCAPED 1
341 /// \ingroup ServerProtocolFTPInternal
342 #define FTP_LOGIN_NOT_ESCAPED 0
345 * State machine functions
346 * send == state transition
347 * read == wait for response, and select next state transition
348 * other == Transition logic
350 static FTPSM ftpReadWelcome
;
351 static FTPSM ftpSendUser
;
352 static FTPSM ftpReadUser
;
353 static FTPSM ftpSendPass
;
354 static FTPSM ftpReadPass
;
355 static FTPSM ftpSendType
;
356 static FTPSM ftpReadType
;
357 static FTPSM ftpSendMdtm
;
358 static FTPSM ftpReadMdtm
;
359 static FTPSM ftpSendSize
;
360 static FTPSM ftpReadSize
;
361 static FTPSM ftpSendEPRT
;
362 static FTPSM ftpReadEPRT
;
363 static FTPSM ftpSendPORT
;
364 static FTPSM ftpReadPORT
;
365 static FTPSM ftpSendPassive
;
366 static FTPSM ftpReadEPSV
;
367 static FTPSM ftpReadPasv
;
368 static FTPSM ftpTraverseDirectory
;
369 static FTPSM ftpListDir
;
370 static FTPSM ftpGetFile
;
371 static FTPSM ftpSendCwd
;
372 static FTPSM ftpReadCwd
;
373 static FTPSM ftpRestOrList
;
374 static FTPSM ftpSendList
;
375 static FTPSM ftpSendNlst
;
376 static FTPSM ftpReadList
;
377 static FTPSM ftpSendRest
;
378 static FTPSM ftpReadRest
;
379 static FTPSM ftpSendRetr
;
380 static FTPSM ftpReadRetr
;
381 static FTPSM ftpReadTransferDone
;
382 static FTPSM ftpSendStor
;
383 static FTPSM ftpReadStor
;
384 static FTPSM ftpWriteTransferDone
;
385 static FTPSM ftpSendReply
;
386 static FTPSM ftpSendMkdir
;
387 static FTPSM ftpReadMkdir
;
388 static FTPSM ftpFail
;
389 static FTPSM ftpSendQuit
;
390 static FTPSM ftpReadQuit
;
392 /************************************************
393 ** Debugs Levels used here **
394 *************************************************
397 Protocol and Transmission failures.
398 2 FTP Protocol Chatter
403 ************************************************/
405 /************************************************
406 ** State Machine Description (excluding hacks) **
407 *************************************************
409 ---------------------------------------
413 Type TraverseDirectory / GetFile
414 TraverseDirectory Cwd / GetFile / ListDir
415 Cwd TraverseDirectory / Mkdir
421 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
423 Retr / Nlst / List DataRead* (on datachannel)
424 DataRead* ReadTransferDone
425 ReadTransferDone DataTransferDone
426 Stor DataWrite* (on datachannel)
427 DataWrite* RequestPutBody** (from client)
428 RequestPutBody** DataWrite* / WriteTransferDone
429 WriteTransferDone DataTransferDone
430 DataTransferDone Quit
432 ************************************************/
434 /// \ingroup ServerProtocolFTPInternal
435 FTPSM
*FTP_SM_FUNCS
[] = {
436 ftpReadWelcome
, /* BEGIN */
437 ftpReadUser
, /* SENT_USER */
438 ftpReadPass
, /* SENT_PASS */
439 ftpReadType
, /* SENT_TYPE */
440 ftpReadMdtm
, /* SENT_MDTM */
441 ftpReadSize
, /* SENT_SIZE */
442 ftpReadEPRT
, /* SENT_EPRT */
443 ftpReadPORT
, /* SENT_PORT */
444 ftpReadEPSV
, /* SENT_EPSV_ALL */
445 ftpReadEPSV
, /* SENT_EPSV_1 */
446 ftpReadEPSV
, /* SENT_EPSV_2 */
447 ftpReadPasv
, /* SENT_PASV */
448 ftpReadCwd
, /* SENT_CWD */
449 ftpReadList
, /* SENT_LIST */
450 ftpReadList
, /* SENT_NLST */
451 ftpReadRest
, /* SENT_REST */
452 ftpReadRetr
, /* SENT_RETR */
453 ftpReadStor
, /* SENT_STOR */
454 ftpReadQuit
, /* SENT_QUIT */
455 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
456 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
457 ftpReadMkdir
/* SENT_MKDIR */
460 /// handler called by Comm when FTP control channel is closed unexpectedly
462 FtpStateData::ctrlClosed(const CommCloseCbParams
&io
)
466 mustStop("FtpStateData::ctrlClosed");
469 /// handler called by Comm when FTP data channel is closed unexpectedly
471 FtpStateData::dataClosed(const CommCloseCbParams
&io
)
474 if (data
.listenConn
!= NULL
) {
475 data
.listenConn
->close();
476 data
.listenConn
= NULL
;
477 // NP clear() does the: data.fd = -1;
480 failed(ERR_FTP_FAILURE
, 0);
481 /* failed closes ctrl.conn and frees ftpState */
483 /* NP: failure recovery may be possible when its only a data.conn failure.
484 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
485 * Just need to watch out for wider Squid states like shutting down or reconfigure.
489 FtpStateData::FtpStateData(FwdState
*theFwdState
, const Comm::ConnectionPointer
&conn
) : AsyncJob("FtpStateData"), ServerStateData(theFwdState
)
491 const char *url
= entry
->url();
492 debugs(9, 3, HERE
<< "'" << url
<< "'" );
493 ++ statCounter
.server
.all
.requests
;
494 ++ statCounter
.server
.ftp
.requests
;
498 if (Config
.Ftp
.passive
&& !flags
.pasv_failed
)
499 flags
.pasv_supported
= 1;
501 flags
.rest_supported
= 1;
503 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
504 AsyncCall::Pointer closer
= JobCallback(9, 5, Dialer
, this, FtpStateData::ctrlClosed
);
505 ctrl
.opened(conn
, closer
);
507 if (request
->method
== METHOD_PUT
)
511 FtpStateData::~FtpStateData()
513 debugs(9, 3, HERE
<< entry
->url() );
516 memFree(reply_hdr
, MEM_8K_BUF
);
520 if (data
.opener
!= NULL
) {
521 data
.opener
->cancel("FtpStateData destructed");
526 if (Comm::IsConnOpen(ctrl
.conn
)) {
527 debugs(9, DBG_IMPORTANT
, HERE
<< "Internal bug: FtpStateData left " <<
528 "open control channel " << ctrl
.conn
);
532 memFreeBuf(ctrl
.size
, ctrl
.buf
);
537 if (!data
.readBuf
->isNull())
538 data
.readBuf
->clean();
544 wordlistDestroy(&pathcomps
);
547 wordlistDestroy(&ctrl
.message
);
551 safe_free(ctrl
.last_reply
);
553 safe_free(ctrl
.last_command
);
555 safe_free(old_request
);
557 safe_free(old_reply
);
559 safe_free(old_filepath
);
569 safe_free(data
.host
);
571 fwd
= NULL
; // refcounted
575 * Parse a possible login username:password pair.
576 * Produces filled member variables user, password, password_url if anything found.
579 FtpStateData::loginParser(const char *login
, int escaped
)
581 const char *u
= NULL
; // end of the username sub-string
582 int len
; // length of the current sub-string to handle.
584 int total_len
= strlen(login
);
586 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
587 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
589 if ((u
= strchr(login
, ':'))) {
591 /* if there was a username part */
594 ++u
; // jump off the delimiter.
597 xstrncpy(user
, login
, len
+1);
598 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
600 rfc1738_unescape(user
);
601 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
604 /* if there was a password part */
605 len
= login
+ total_len
- u
;
609 xstrncpy(password
, u
, len
+1);
610 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
612 rfc1738_unescape(password
);
615 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
617 } else if (login
[0]) {
618 /* no password, just username */
619 if (total_len
> MAX_URL
)
620 total_len
= MAX_URL
-1;
621 xstrncpy(user
, login
, total_len
+1);
622 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
624 rfc1738_unescape(user
);
625 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
628 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
632 * Cancel the timeout on the Control socket and establish one
636 FtpStateData::switchTimeoutToDataChannel()
638 commUnsetConnTimeout(ctrl
.conn
);
640 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
641 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5, TimeoutDialer
, this, FtpStateData::ftpTimeout
);
642 commSetConnTimeout(data
.conn
, Config
.Timeout
.read
, timeoutCall
);
646 FtpStateData::listenForDataChannel(const Comm::ConnectionPointer
&conn
, const char *note
)
648 assert(!Comm::IsConnOpen(data
.conn
));
650 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> AcceptDialer
;
651 typedef AsyncCallT
<AcceptDialer
> AcceptCall
;
652 RefCount
<AcceptCall
> call
= static_cast<AcceptCall
*>(JobCallback(11, 5, AcceptDialer
, this, FtpStateData::ftpAcceptDataConnection
));
653 Subscription::Pointer sub
= new CallSubscription
<AcceptCall
>(call
);
655 /* open the conn if its not already open */
656 if (!Comm::IsConnOpen(conn
)) {
657 conn
->fd
= comm_open_listener(SOCK_STREAM
, IPPROTO_TCP
, conn
->local
, conn
->flags
, note
);
658 if (!Comm::IsConnOpen(conn
)) {
659 debugs(5, DBG_CRITICAL
, HERE
<< "comm_open_listener failed:" << conn
->local
<< " error: " << errno
);
662 debugs(9, 3, HERE
<< "Unconnected data socket created on " << conn
);
665 assert(Comm::IsConnOpen(conn
));
666 AsyncJob::Start(new Comm::TcpAcceptor(conn
, note
, sub
));
668 // Ensure we have a copy of the FD opened for listening and a close handler on it.
669 data
.opened(conn
, dataCloser());
670 switchTimeoutToDataChannel();
674 FtpStateData::ftpTimeout(const CommTimeoutCbParams
&io
)
676 debugs(9, 4, HERE
<< io
.conn
<< ": '" << entry
->url() << "'" );
678 if (abortOnBadEntry("entry went bad while waiting for a timeout"))
681 if (SENT_PASV
== state
&& io
.conn
->fd
== data
.conn
->fd
) {
682 /* stupid ftp.netscape.com */
683 flags
.pasv_supported
= false;
684 debugs(9, DBG_IMPORTANT
, "ftpTimeout: timeout in SENT_PASV state" );
687 failed(ERR_READ_TIMEOUT
, 0);
688 /* failed() closes ctrl.conn and frees ftpState */
691 #if DEAD_CODE // obsoleted by ERR_DIR_LISTING
693 FtpStateData::listingFinish()
695 // TODO: figure out what this means and how to show it ...
697 if (flags
.listformat_unknown
&& !flags
.tried_nlst
) {
698 printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
699 flags
.dir_slash
? rfc1738_escape_part(old_filepath
) : ".");
700 } else if (typecode
== 'D') {
701 const char *path
= flags
.dir_slash
? filepath
: ".";
702 printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path
));
705 #endif /* DEAD_CODE */
707 /// \ingroup ServerProtocolFTPInternal
708 static const char *Month
[] = {
709 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
710 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
713 /// \ingroup ServerProtocolFTPInternal
715 is_month(const char *buf
)
719 for (i
= 0; i
< 12; ++i
)
720 if (!strcasecmp(buf
, Month
[i
]))
726 /// \ingroup ServerProtocolFTPInternal
728 ftpListPartsFree(ftpListParts
** parts
)
730 safe_free((*parts
)->date
);
731 safe_free((*parts
)->name
);
732 safe_free((*parts
)->showname
);
733 safe_free((*parts
)->link
);
737 /// \ingroup ServerProtocolFTPInternal
738 #define MAX_TOKENS 64
740 /// \ingroup ServerProtocolFTPInternal
741 static ftpListParts
*
742 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
744 ftpListParts
*p
= NULL
;
746 const char *ct
= NULL
;
747 char *tokens
[MAX_TOKENS
];
750 static char tbuf
[128];
752 static int scan_ftp_initialized
= 0;
753 static regex_t scan_ftp_integer
;
754 static regex_t scan_ftp_time
;
755 static regex_t scan_ftp_dostime
;
756 static regex_t scan_ftp_dosdate
;
758 if (!scan_ftp_initialized
) {
759 scan_ftp_initialized
= 1;
760 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
761 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
762 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
763 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
772 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
776 memset(tokens
, 0, sizeof(tokens
));
780 if (flags
.tried_nlst
) {
781 /* Machine readable format, one name per line */
787 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
)) {
788 tokens
[n_tokens
] = xstrdup(t
);
794 /* locate the Month field */
795 for (i
= 3; i
< n_tokens
- 2; ++i
) {
796 char *size
= tokens
[i
- 1];
797 char *month
= tokens
[i
];
798 char *day
= tokens
[i
+ 1];
799 char *year
= tokens
[i
+ 2];
801 if (!is_month(month
))
804 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
807 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
810 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
813 snprintf(tbuf
, 128, "%s %2s %5s",
816 if (!strstr(buf
, tbuf
))
817 snprintf(tbuf
, 128, "%s %2s %-5s",
820 char const *copyFrom
= NULL
;
822 if ((copyFrom
= strstr(buf
, tbuf
))) {
823 p
->type
= *tokens
[0];
824 p
->size
= strtoll(size
, NULL
, 10);
825 p
->date
= xstrdup(tbuf
);
827 if (flags
.skip_whitespace
) {
828 copyFrom
+= strlen(tbuf
);
830 while (strchr(w_space
, *copyFrom
))
833 /* XXX assumes a single space between date and filename
834 * suggested by: Nathan.Bailey@cc.monash.edu.au and
835 * Mike Battersby <mike@starbug.bofh.asn.au> */
836 copyFrom
+= strlen(tbuf
) + 1;
839 p
->name
= xstrdup(copyFrom
);
841 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
843 p
->link
= xstrdup(t
+ 4);
852 /* try it as a DOS listing, 04-05-70 09:33PM ... */
854 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
855 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
856 if (!strcasecmp(tokens
[2], "<dir>")) {
860 p
->size
= strtoll(tokens
[2], NULL
, 10);
863 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
864 p
->date
= xstrdup(tbuf
);
866 if (p
->type
== 'd') {
867 /* Directory.. name begins with first printable after <dir> */
868 ct
= strstr(buf
, tokens
[2]);
869 ct
+= strlen(tokens
[2]);
871 while (xisspace(*ct
))
877 /* A file. Name begins after size, with a space in between */
878 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
879 ct
= strstr(buf
, tbuf
);
882 ct
+= strlen(tokens
[2]) + 2;
886 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
890 /* Try EPLF format; carson@lehman.com */
897 int l
= strcspn(ct
, ",");
906 p
->name
= xstrndup(ct
+ 1, l
+ 1);
910 p
->size
= atoi(ct
+ 1);
914 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
917 break; /* not a valid integer */
919 p
->date
= xstrdup(ctime(&tm
));
921 *(strstr(p
->date
, "\n")) = '\0';
943 ct
= strstr(ct
, ",");
962 for (i
= 0; i
< n_tokens
; ++i
)
966 ftpListPartsFree(&p
); /* cleanup */
972 FtpStateData::htmlifyListEntry(const char *line
)
975 char href
[2048 + 40];
978 char chdir
[ 2048 + 40];
979 char view
[ 2048 + 40];
980 char download
[ 2048 + 40];
981 char link
[ 2048 + 40];
985 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
987 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
989 if (strlen(line
) > 1024) {
992 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
996 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
997 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
1001 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
1004 html
= new MemBuf();
1006 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
1008 for (p
= line
; *p
&& xisspace(*p
); ++p
);
1009 if (*p
&& !xisspace(*p
))
1010 flags
.listformat_unknown
= 1;
1015 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
1016 ftpListPartsFree(&parts
);
1020 parts
->size
+= 1023;
1022 parts
->showname
= xstrdup(parts
->name
);
1024 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
1025 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
1027 xstrncpy(text
, parts
->showname
, 2048);
1029 switch (parts
->type
) {
1032 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1033 mimeGetIconURL("internal-dir"),
1035 strcat(href
, "/"); /* margin is allocated above */
1039 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1040 mimeGetIconURL("internal-link"),
1042 /* sometimes there is an 'l' flag, but no "->" link */
1045 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
1046 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
1047 *link2
!= '/' ? prefix
: "", link2
,
1048 html_quote(parts
->link
));
1055 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1056 mimeGetIconURL(parts
->name
),
1058 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
1059 "alt=\"[DIR]\"></a>",
1060 rfc1738_escape_part(parts
->name
),
1061 mimeGetIconURL("internal-dir"));
1067 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1068 mimeGetIconURL(parts
->name
),
1070 snprintf(size
, 2048, " %6" PRId64
"k", parts
->size
);
1074 if (parts
->type
!= 'd') {
1075 if (mimeGetViewOption(parts
->name
)) {
1076 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1077 "alt=\"[VIEW]\"></a>",
1078 prefix
, href
, mimeGetIconURL("internal-view"));
1081 if (mimeGetDownloadOption(parts
->name
)) {
1082 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1083 "alt=\"[DOWNLOAD]\"></a>",
1084 prefix
, href
, mimeGetIconURL("internal-download"));
1088 /* construct the table row from parts. */
1089 html
= new MemBuf();
1091 html
->Printf("<tr class=\"entry\">"
1092 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1093 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1094 "<td class=\"date\">%s</td>"
1095 "<td class=\"size\">%s</td>"
1096 "<td class=\"actions\">%s%s%s%s</td>"
1099 prefix
, href
, html_quote(text
),
1102 chdir
, view
, download
, link
);
1104 ftpListPartsFree(&parts
);
1109 FtpStateData::parseListing()
1111 char *buf
= data
.readBuf
->content();
1112 char *sbuf
; /* NULL-terminated copy of termedBuf */
1119 size_t len
= data
.readBuf
->contentSize();
1122 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
1127 * We need a NULL-terminated buffer for scanning, ick
1129 sbuf
= (char *)xmalloc(len
+ 1);
1130 xstrncpy(sbuf
, buf
, len
+ 1);
1131 end
= sbuf
+ len
- 1;
1133 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1136 usable
= end
- sbuf
;
1138 debugs(9, 3, HERE
<< "usable = " << usable
<< " of " << len
<< " bytes.");
1141 if (buf
[0] == '\0' && len
== 1) {
1142 debugs(9, 3, HERE
<< "NIL ends data from " << entry
->url() << " transfer problem?");
1143 data
.readBuf
->consume(len
);
1145 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url());
1146 debugs(9, 3, HERE
<< "buffer remains (" << len
<< " bytes) '" << rfc1738_do_escape(buf
,0) << "'");
1152 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
1154 line
= (char *)memAllocate(MEM_4K_BUF
);
1157 s
+= strspn(s
, crlf
);
1159 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1160 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
1161 linelen
= strcspn(s
, crlf
) + 1;
1169 xstrncpy(line
, s
, linelen
);
1171 debugs(9, 7, HERE
<< "{" << line
<< "}");
1173 if (!strncmp(line
, "total", 5))
1176 t
= htmlifyListEntry(line
);
1179 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
1180 listing
.append(t
->content(), t
->contentSize());
1185 debugs(9, 7, HERE
<< "Done.");
1186 data
.readBuf
->consume(usable
);
1187 memFree(line
, MEM_4K_BUF
);
1191 const Comm::ConnectionPointer
&
1192 FtpStateData::dataConnection() const
1198 FtpStateData::dataComplete()
1202 /* Connection closed; transfer done. */
1204 /// Close data channel, if any, to conserve resources while we wait.
1207 /* expect the "transfer complete" message on the control socket */
1210 * Previously, this was the only place where we set the
1211 * 'buffered_ok' flag when calling scheduleReadControlReply().
1212 * It caused some problems if the FTP server returns an unexpected
1213 * status code after the data command. FtpStateData was being
1214 * deleted in the middle of dataRead().
1216 /* AYJ: 2011-01-13: Bug 2581.
1217 * 226 status is possibly waiting in the ctrl buffer.
1218 * The connection will hang if we DONT send buffered_ok.
1219 * This happens on all transfers which can be completly sent by the
1220 * server before the 150 started status message is read in by Squid.
1221 * ie all transfers of about one packet hang.
1223 scheduleReadControlReply(1);
1227 FtpStateData::maybeReadVirginBody()
1230 if (!Comm::IsConnOpen(data
.conn
) || fd_table
[data
.conn
->fd
].closing())
1233 if (data
.read_pending
)
1236 const int read_sz
= replyBodySpace(*data
.readBuf
, 0);
1238 debugs(11,9, HERE
<< "FTP may read up to " << read_sz
<< " bytes");
1240 if (read_sz
< 2) // see http.cc
1243 data
.read_pending
= true;
1245 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1246 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1247 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1248 commSetConnTimeout(data
.conn
, Config
.Timeout
.read
, timeoutCall
);
1250 debugs(9,5,HERE
<< "queueing read on FD " << data
.conn
->fd
);
1252 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1253 entry
->delayAwareRead(data
.conn
, data
.readBuf
->space(), read_sz
,
1254 JobCallback(9, 5, Dialer
, this, FtpStateData::dataRead
));
1258 FtpStateData::dataRead(const CommIoCbParams
&io
)
1263 data
.read_pending
= false;
1265 debugs(9, 3, HERE
<< "ftpDataRead: FD " << io
.fd
<< " Read " << io
.size
<< " bytes");
1268 kb_incr(&(statCounter
.server
.all
.kbytes_in
), io
.size
);
1269 kb_incr(&(statCounter
.server
.ftp
.kbytes_in
), io
.size
);
1272 if (io
.flag
== COMM_ERR_CLOSING
)
1275 assert(io
.fd
== data
.conn
->fd
);
1277 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1278 abortTransaction("entry aborted during dataRead");
1282 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1283 debugs(9,5,HERE
<< "appended " << io
.size
<< " bytes to readBuf");
1284 data
.readBuf
->appended(io
.size
);
1286 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
1287 delayId
.bytesIn(io
.size
);
1289 ++ IOStats
.Ftp
.reads
;
1291 for (j
= io
.size
- 1, bin
= 0; j
; ++bin
)
1294 ++ IOStats
.Ftp
.read_hist
[bin
];
1297 if (io
.flag
!= COMM_OK
) {
1298 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1299 "ftpDataRead: read error: " << xstrerr(io
.xerrno
));
1301 if (ignoreErrno(io
.xerrno
)) {
1302 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1303 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1304 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1305 commSetConnTimeout(io
.conn
, Config
.Timeout
.read
, timeoutCall
);
1307 maybeReadVirginBody();
1309 failed(ERR_READ_ERROR
, 0);
1310 /* failed closes ctrl.conn and frees ftpState */
1313 } else if (io
.size
== 0) {
1314 debugs(9,3, HERE
<< "Calling dataComplete() because io.size == 0");
1317 * Dangerous curves ahead. This call to dataComplete was
1318 * calling scheduleReadControlReply, handleControlReply,
1319 * and then ftpReadTransferDone. If ftpReadTransferDone
1320 * gets unexpected status code, it closes down the control
1321 * socket and our FtpStateData object gets destroyed. As
1322 * a workaround we no longer set the 'buffered_ok' flag in
1323 * the scheduleReadControlReply call.
1332 FtpStateData::processReplyBody()
1334 debugs(9, 3, HERE
<< "FtpStateData::processReplyBody starting.");
1336 if (request
->method
== METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
1341 /* Directory listings are special. They write ther own headers via the error objects */
1342 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
1343 appendSuccessHeader();
1345 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1347 * probably was aborted because content length exceeds one
1348 * of the maximum size limits.
1350 abortTransaction("entry aborted after calling appendSuccessHeader()");
1356 if (adaptationAccessCheckPending
) {
1357 debugs(9,3, HERE
<< "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1364 if (!flags
.listing
) {
1369 maybeReadVirginBody();
1371 } else if (const int csize
= data
.readBuf
->contentSize()) {
1372 writeReplyBody(data
.readBuf
->content(), csize
);
1373 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1374 data
.readBuf
->consume(csize
);
1379 maybeReadVirginBody();
1383 * Locates the FTP user:password login.
1385 * Highest to lowest priority:
1386 * - Checks URL (ftp://user:pass@domain)
1387 * - Authorization: Basic header
1388 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1390 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1392 * TODO: we might be able to do something about locating username from other sources:
1393 * ie, external ACL user=* tag or ident lookup
1395 \retval 1 if we have everything needed to complete this request.
1396 \retval 0 if something is missing.
1399 FtpStateData::checkAuth(const HttpHeader
* req_hdr
)
1401 /* default username */
1402 xstrncpy(user
, "anonymous", MAX_URL
);
1404 #if HAVE_AUTH_MODULE_BASIC
1405 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1407 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1408 flags
.authenticated
= 1;
1409 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1411 /* we fail with authorization-required error later IFF the FTP server requests it */
1414 /* Test URL login syntax. Overrides any headers received. */
1415 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1417 /* name is missing. thats fatal. */
1419 fatal("FTP login parsing destroyed username info");
1421 /* name + password == success */
1425 /* Setup default FTP password settings */
1426 /* this has to be done last so that we can have a no-password case above. */
1428 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1429 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1430 flags
.tried_auth_anonymous
=1;
1432 } else if (!flags
.tried_auth_nopass
) {
1433 xstrncpy(password
, null_string
, MAX_URL
);
1434 flags
.tried_auth_nopass
=1;
1439 return 0; /* different username */
1442 static String str_type_eq
;
1444 FtpStateData::checkUrlpath()
1449 if (str_type_eq
.undefined()) //hack. String doesn't support global-static
1450 str_type_eq
="type=";
1452 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1453 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1454 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1455 request
->urlpath
.cut(t
);
1459 l
= request
->urlpath
.size();
1460 /* check for null path */
1465 flags
.need_base_href
= 1; /* Work around broken browsers */
1466 } else if (!request
->urlpath
.cmp("/%2f/")) {
1467 /* UNIX root directory */
1470 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1471 /* Directory URL, ending in / */
1477 flags
.dir_slash
= 1;
1482 FtpStateData::buildTitleUrl()
1484 title_url
= "ftp://";
1486 if (strcmp(user
, "anonymous")) {
1487 title_url
.append(user
);
1488 title_url
.append("@");
1491 title_url
.append(request
->GetHost());
1493 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1494 title_url
.append(":");
1495 title_url
.append(xitoa(request
->port
));
1498 title_url
.append (request
->urlpath
);
1500 base_href
= "ftp://";
1502 if (strcmp(user
, "anonymous") != 0) {
1503 base_href
.append(rfc1738_escape_part(user
));
1506 base_href
.append (":");
1507 base_href
.append(rfc1738_escape_part(password
));
1510 base_href
.append("@");
1513 base_href
.append(request
->GetHost());
1515 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1516 base_href
.append(":");
1517 base_href
.append(xitoa(request
->port
));
1520 base_href
.append(request
->urlpath
);
1521 base_href
.append("/");
1524 /// \ingroup ServerProtocolFTPAPI
1526 ftpStart(FwdState
* fwd
)
1528 AsyncJob::Start(new FtpStateData(fwd
, fwd
->serverConnection()));
1532 FtpStateData::start()
1534 if (!checkAuth(&request
->header
)) {
1535 /* create appropriate reply */
1536 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1537 entry
->replaceHttpReply(reply
);
1544 debugs(9, 5, HERE
<< "FD " << ctrl
.conn
->fd
<< " : host=" << request
->GetHost() <<
1545 ", path=" << request
->urlpath
<< ", user=" << user
<< ", passwd=" << password
);
1548 ctrl
.last_command
= xstrdup("Connect to server");
1549 ctrl
.buf
= (char *)memAllocBuf(4096, &ctrl
.size
);
1551 data
.readBuf
= new MemBuf
;
1552 data
.readBuf
->init(4096, SQUID_TCP_SO_RCVBUF
);
1553 scheduleReadControlReply(0);
1556 /* ====================================================================== */
1558 /// \ingroup ServerProtocolFTPInternal
1560 escapeIAC(const char *buf
)
1564 unsigned const char *p
;
1567 for (p
= (unsigned const char *)buf
, n
= 1; *p
; ++n
, ++p
)
1571 ret
= (char *)xmalloc(n
);
1573 for (p
= (unsigned const char *)buf
, r
=(unsigned char *)ret
; *p
; ++p
) {
1585 assert((r
- (unsigned char *)ret
) == n
);
1590 FtpStateData::writeCommand(const char *buf
)
1593 /* trace FTP protocol communications at level 2 */
1594 debugs(9, 2, "ftp<< " << buf
);
1596 if (Config
.Ftp
.telnet
)
1597 ebuf
= escapeIAC(buf
);
1599 ebuf
= xstrdup(buf
);
1601 safe_free(ctrl
.last_command
);
1603 safe_free(ctrl
.last_reply
);
1605 ctrl
.last_command
= ebuf
;
1607 if (!Comm::IsConnOpen(ctrl
.conn
)) {
1608 debugs(9, 2, HERE
<< "cannot send to closing ctrl " << ctrl
.conn
);
1609 // TODO: assert(ctrl.closer != NULL);
1613 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1614 AsyncCall::Pointer call
= JobCallback(9, 5, Dialer
, this, FtpStateData::ftpWriteCommandCallback
);
1615 Comm::Write(ctrl
.conn
, ctrl
.last_command
, strlen(ctrl
.last_command
), call
, NULL
);
1617 scheduleReadControlReply(0);
1621 FtpStateData::ftpWriteCommandCallback(const CommIoCbParams
&io
)
1624 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io
.size
<< " bytes");
1627 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
1628 kb_incr(&(statCounter
.server
.all
.kbytes_out
), io
.size
);
1629 kb_incr(&(statCounter
.server
.ftp
.kbytes_out
), io
.size
);
1632 if (io
.flag
== COMM_ERR_CLOSING
)
1636 debugs(9, DBG_IMPORTANT
, "ftpWriteCommandCallback: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
1637 failed(ERR_WRITE_ERROR
, io
.xerrno
);
1638 /* failed closes ctrl.conn and frees ftpState */
1644 FtpStateData::ftpParseControlReply(char *buf
, size_t len
, int *codep
, size_t *used
)
1651 wordlist
*head
= NULL
;
1653 wordlist
**tail
= &head
;
1659 * We need a NULL-terminated buffer for scanning, ick
1661 sbuf
= (char *)xmalloc(len
+ 1);
1662 xstrncpy(sbuf
, buf
, len
+ 1);
1663 end
= sbuf
+ len
- 1;
1665 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1668 usable
= end
- sbuf
;
1670 debugs(9, 3, HERE
<< "usable = " << usable
);
1673 debugs(9, 3, HERE
<< "didn't find end of line");
1678 debugs(9, 3, HERE
<< len
<< " bytes to play with");
1681 s
+= strspn(s
, crlf
);
1683 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1687 debugs(9, 5, HERE
<< "s = {" << s
<< "}");
1689 linelen
= strcspn(s
, crlf
) + 1;
1695 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1703 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1706 list
= new wordlist();
1708 list
->key
= (char *)xmalloc(linelen
- offset
);
1710 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1712 /* trace the FTP communication chat at level 2 */
1713 debugs(9, 2, "ftp>> " << code
<< " " << list
->key
);
1720 *used
= (size_t) (s
- sbuf
);
1724 wordlistDestroy(&head
);
1734 * Looks like there are no longer anymore callers that set
1735 * buffered_ok=1. Perhaps it can be removed at some point.
1738 FtpStateData::scheduleReadControlReply(int buffered_ok
)
1740 debugs(9, 3, HERE
<< ctrl
.conn
);
1742 if (buffered_ok
&& ctrl
.offset
> 0) {
1743 /* We've already read some reply data */
1744 handleControlReply();
1747 * Cancel the timeout on the Data socket (if any) and
1748 * establish one on the control socket.
1750 if (Comm::IsConnOpen(data
.conn
)) {
1751 commUnsetConnTimeout(data
.conn
);
1754 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1755 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5, TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1756 commSetConnTimeout(ctrl
.conn
, Config
.Timeout
.read
, timeoutCall
);
1758 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1759 AsyncCall::Pointer reader
= JobCallback(9, 5, Dialer
, this, FtpStateData::ftpReadControlReply
);
1760 comm_read(ctrl
.conn
, ctrl
.buf
+ ctrl
.offset
, ctrl
.size
- ctrl
.offset
, reader
);
1764 void FtpStateData::ftpReadControlReply(const CommIoCbParams
&io
)
1766 debugs(9, 3, "ftpReadControlReply: FD " << io
.fd
<< ", Read " << io
.size
<< " bytes");
1769 kb_incr(&(statCounter
.server
.all
.kbytes_in
), io
.size
);
1770 kb_incr(&(statCounter
.server
.ftp
.kbytes_in
), io
.size
);
1773 if (io
.flag
== COMM_ERR_CLOSING
)
1776 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1777 abortTransaction("entry aborted during control reply read");
1781 assert(ctrl
.offset
< ctrl
.size
);
1783 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1784 fd_bytes(io
.fd
, io
.size
, FD_READ
);
1787 if (io
.flag
!= COMM_OK
) {
1788 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1789 "ftpReadControlReply: read error: " << xstrerr(io
.xerrno
));
1791 if (ignoreErrno(io
.xerrno
)) {
1792 scheduleReadControlReply(0);
1794 failed(ERR_READ_ERROR
, io
.xerrno
);
1795 /* failed closes ctrl.conn and frees ftpState */
1801 if (entry
->store_status
== STORE_PENDING
) {
1802 failed(ERR_FTP_FAILURE
, 0);
1803 /* failed closes ctrl.conn and frees ftpState */
1807 /* XXX this may end up having to be serverComplete() .. */
1808 abortTransaction("zero control reply read");
1812 unsigned int len
=io
.size
+ ctrl
.offset
;
1814 assert(len
<= ctrl
.size
);
1815 handleControlReply();
1819 FtpStateData::handleControlReply()
1822 size_t bytes_used
= 0;
1823 wordlistDestroy(&ctrl
.message
);
1824 ctrl
.message
= ftpParseControlReply(ctrl
.buf
,
1825 ctrl
.offset
, &ctrl
.replycode
, &bytes_used
);
1827 if (ctrl
.message
== NULL
) {
1828 /* didn't get complete reply yet */
1830 if (ctrl
.offset
== ctrl
.size
) {
1831 ctrl
.buf
= (char *)memReallocBuf(ctrl
.buf
, ctrl
.size
<< 1, &ctrl
.size
);
1834 scheduleReadControlReply(0);
1836 } else if (ctrl
.offset
== bytes_used
) {
1837 /* used it all up */
1840 /* Got some data past the complete reply */
1841 assert(bytes_used
< ctrl
.offset
);
1842 ctrl
.offset
-= bytes_used
;
1843 memmove(ctrl
.buf
, ctrl
.buf
+ bytes_used
, ctrl
.offset
);
1846 /* Move the last line of the reply message to ctrl.last_reply */
1847 for (W
= &ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1848 safe_free(ctrl
.last_reply
);
1850 ctrl
.last_reply
= xstrdup((*W
)->key
);
1854 /* Copy the rest of the message to cwd_message to be printed in
1858 for (wordlist
*w
= ctrl
.message
; w
; w
= w
->next
) {
1859 cwd_message
.append('\n');
1860 cwd_message
.append(w
->key
);
1864 debugs(9, 3, HERE
<< "state=" << state
<< ", code=" << ctrl
.replycode
);
1866 FTP_SM_FUNCS
[state
] (this);
1869 /* ====================================================================== */
1871 /// \ingroup ServerProtocolFTPInternal
1873 ftpReadWelcome(FtpStateData
* ftpState
)
1875 int code
= ftpState
->ctrl
.replycode
;
1878 if (ftpState
->flags
.pasv_only
)
1879 ++ ftpState
->login_att
;
1882 if (ftpState
->ctrl
.message
) {
1883 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1884 ftpState
->flags
.skip_whitespace
= 1;
1887 ftpSendUser(ftpState
);
1888 } else if (code
== 120) {
1889 if (NULL
!= ftpState
->ctrl
.message
)
1890 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1899 * Translate FTP login failure into HTTP error
1900 * this is an attmpt to get the 407 message to show up outside Squid.
1901 * its NOT a general failure. But a correct FTP response type.
1904 FtpStateData::loginFailed()
1906 ErrorState
*err
= NULL
;
1907 const char *command
, *reply
;
1909 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1910 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1911 // 421/426 - Service Overload - retry permitted.
1912 err
= new ErrorState(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
1913 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1914 // 43x - Invalid or Credential Error - retry challenge required.
1915 err
= new ErrorState(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
1916 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1917 // 53x - Credentials Missing - retry challenge required
1918 if (password_url
) // but they were in the URI! major fail.
1919 err
= new ErrorState(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
1921 err
= new ErrorState(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
1925 // any other problems are general falures.
1931 err
->ftp
.server_msg
= ctrl
.message
;
1933 ctrl
.message
= NULL
;
1936 command
= old_request
;
1938 command
= ctrl
.last_command
;
1940 if (command
&& strncmp(command
, "PASS", 4) == 0)
1941 command
= "PASS <yourpassword>";
1946 reply
= ctrl
.last_reply
;
1949 err
->ftp
.request
= xstrdup(command
);
1952 err
->ftp
.reply
= xstrdup(reply
);
1954 HttpReply
*newrep
= err
->BuildHttpReply();
1957 #if HAVE_AUTH_MODULE_BASIC
1958 /* add Authenticate header */
1959 newrep
->header
.putAuth("Basic", ftpRealm());
1962 // add it to the store entry for response....
1963 entry
->replaceHttpReply(newrep
);
1968 FtpStateData::ftpRealm()
1970 static char realm
[8192];
1972 /* This request is not fully authenticated */
1974 snprintf(realm
, 8192, "FTP %s unknown", user
);
1975 } else if (request
->port
== 21) {
1976 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1978 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1983 /// \ingroup ServerProtocolFTPInternal
1985 ftpSendUser(FtpStateData
* ftpState
)
1987 /* check the server control channel is still available */
1988 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1991 if (ftpState
->proxy_host
!= NULL
)
1992 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s@%s\r\n",
1994 ftpState
->request
->GetHost());
1996 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s\r\n", ftpState
->user
);
1998 ftpState
->writeCommand(cbuf
);
2000 ftpState
->state
= SENT_USER
;
2003 /// \ingroup ServerProtocolFTPInternal
2005 ftpReadUser(FtpStateData
* ftpState
)
2007 int code
= ftpState
->ctrl
.replycode
;
2011 ftpReadPass(ftpState
);
2012 } else if (code
== 331) {
2013 ftpSendPass(ftpState
);
2015 ftpState
->loginFailed();
2019 /// \ingroup ServerProtocolFTPInternal
2021 ftpSendPass(FtpStateData
* ftpState
)
2023 /* check the server control channel is still available */
2024 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
2027 snprintf(cbuf
, CTRL_BUFLEN
, "PASS %s\r\n", ftpState
->password
);
2028 ftpState
->writeCommand(cbuf
);
2029 ftpState
->state
= SENT_PASS
;
2032 /// \ingroup ServerProtocolFTPInternal
2034 ftpReadPass(FtpStateData
* ftpState
)
2036 int code
= ftpState
->ctrl
.replycode
;
2037 debugs(9, 3, HERE
<< "code=" << code
);
2040 ftpSendType(ftpState
);
2042 ftpState
->loginFailed();
2046 /// \ingroup ServerProtocolFTPInternal
2048 ftpSendType(FtpStateData
* ftpState
)
2051 const char *filename
;
2054 /* check the server control channel is still available */
2055 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
2059 * Ref section 3.2.2 of RFC 1738
2061 mode
= ftpState
->typecode
;
2076 if (ftpState
->flags
.isdir
) {
2079 t
= ftpState
->request
->urlpath
.rpos('/');
2080 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
2081 mode
= mimeGetTransferMode(filename
);
2088 ftpState
->flags
.binary
= 1;
2090 ftpState
->flags
.binary
= 0;
2092 snprintf(cbuf
, CTRL_BUFLEN
, "TYPE %c\r\n", mode
);
2094 ftpState
->writeCommand(cbuf
);
2096 ftpState
->state
= SENT_TYPE
;
2099 /// \ingroup ServerProtocolFTPInternal
2101 ftpReadType(FtpStateData
* ftpState
)
2103 int code
= ftpState
->ctrl
.replycode
;
2106 debugs(9, 3, HERE
<< "code=" << code
);
2109 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2116 p
+= strcspn(p
, "/");
2123 rfc1738_unescape(d
);
2126 wordlistAdd(&ftpState
->pathcomps
, d
);
2131 if (ftpState
->pathcomps
)
2132 ftpTraverseDirectory(ftpState
);
2134 ftpListDir(ftpState
);
2140 /// \ingroup ServerProtocolFTPInternal
2142 ftpTraverseDirectory(FtpStateData
* ftpState
)
2145 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
2147 safe_free(ftpState
->dirpath
);
2148 ftpState
->dirpath
= ftpState
->filepath
;
2149 ftpState
->filepath
= NULL
;
2153 if (ftpState
->pathcomps
== NULL
) {
2154 debugs(9, 3, HERE
<< "the final component was a directory");
2155 ftpListDir(ftpState
);
2159 /* Go to next path component */
2160 w
= ftpState
->pathcomps
;
2162 ftpState
->filepath
= w
->key
;
2164 ftpState
->pathcomps
= w
->next
;
2168 /* Check if we are to CWD or RETR */
2169 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
2170 ftpSendCwd(ftpState
);
2172 debugs(9, 3, HERE
<< "final component is probably a file");
2173 ftpGetFile(ftpState
);
2178 /// \ingroup ServerProtocolFTPInternal
2180 ftpSendCwd(FtpStateData
* ftpState
)
2184 /* check the server control channel is still available */
2185 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
2190 path
= ftpState
->filepath
;
2192 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
2193 ftpState
->flags
.no_dotdot
= 1;
2195 ftpState
->flags
.no_dotdot
= 0;
2198 snprintf(cbuf
, CTRL_BUFLEN
, "CWD %s\r\n", path
);
2200 ftpState
->writeCommand(cbuf
);
2202 ftpState
->state
= SENT_CWD
;
2205 /// \ingroup ServerProtocolFTPInternal
2207 ftpReadCwd(FtpStateData
* ftpState
)
2209 int code
= ftpState
->ctrl
.replycode
;
2212 if (code
>= 200 && code
< 300) {
2216 /* Reset cwd_message to only include the last message */
2217 ftpState
->cwd_message
.reset("");
2218 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
2219 ftpState
->cwd_message
.append(' ');
2220 ftpState
->cwd_message
.append(w
->key
);
2222 ftpState
->ctrl
.message
= NULL
;
2224 /* Continue to traverse the path */
2225 ftpTraverseDirectory(ftpState
);
2229 if (!ftpState
->flags
.put
)
2232 ftpSendMkdir(ftpState
);
2236 /// \ingroup ServerProtocolFTPInternal
2238 ftpSendMkdir(FtpStateData
* ftpState
)
2242 /* check the server control channel is still available */
2243 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
2246 path
= ftpState
->filepath
;
2247 debugs(9, 3, HERE
<< "with path=" << path
);
2248 snprintf(cbuf
, CTRL_BUFLEN
, "MKD %s\r\n", path
);
2249 ftpState
->writeCommand(cbuf
);
2250 ftpState
->state
= SENT_MKDIR
;
2253 /// \ingroup ServerProtocolFTPInternal
2255 ftpReadMkdir(FtpStateData
* ftpState
)
2257 char *path
= ftpState
->filepath
;
2258 int code
= ftpState
->ctrl
.replycode
;
2260 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
2262 if (code
== 257) { /* success */
2263 ftpSendCwd(ftpState
);
2264 } else if (code
== 550) { /* dir exists */
2266 if (ftpState
->flags
.put_mkdir
) {
2267 ftpState
->flags
.put_mkdir
= 1;
2268 ftpSendCwd(ftpState
);
2270 ftpSendReply(ftpState
);
2272 ftpSendReply(ftpState
);
2275 /// \ingroup ServerProtocolFTPInternal
2277 ftpGetFile(FtpStateData
* ftpState
)
2279 assert(*ftpState
->filepath
!= '\0');
2280 ftpState
->flags
.isdir
= 0;
2281 ftpSendMdtm(ftpState
);
2284 /// \ingroup ServerProtocolFTPInternal
2286 ftpListDir(FtpStateData
* ftpState
)
2288 if (ftpState
->flags
.dir_slash
) {
2289 debugs(9, 3, HERE
<< "Directory path did not end in /");
2290 ftpState
->title_url
.append("/");
2291 ftpState
->flags
.isdir
= 1;
2294 ftpSendPassive(ftpState
);
2297 /// \ingroup ServerProtocolFTPInternal
2299 ftpSendMdtm(FtpStateData
* ftpState
)
2301 /* check the server control channel is still available */
2302 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
2305 assert(*ftpState
->filepath
!= '\0');
2306 snprintf(cbuf
, CTRL_BUFLEN
, "MDTM %s\r\n", ftpState
->filepath
);
2307 ftpState
->writeCommand(cbuf
);
2308 ftpState
->state
= SENT_MDTM
;
2311 /// \ingroup ServerProtocolFTPInternal
2313 ftpReadMdtm(FtpStateData
* ftpState
)
2315 int code
= ftpState
->ctrl
.replycode
;
2319 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
2321 } else if (code
< 0) {
2326 ftpSendSize(ftpState
);
2329 /// \ingroup ServerProtocolFTPInternal
2331 ftpSendSize(FtpStateData
* ftpState
)
2333 /* check the server control channel is still available */
2334 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
2337 /* Only send SIZE for binary transfers. The returned size
2338 * is useless on ASCII transfers */
2340 if (ftpState
->flags
.binary
) {
2341 assert(ftpState
->filepath
!= NULL
);
2342 assert(*ftpState
->filepath
!= '\0');
2343 snprintf(cbuf
, CTRL_BUFLEN
, "SIZE %s\r\n", ftpState
->filepath
);
2344 ftpState
->writeCommand(cbuf
);
2345 ftpState
->state
= SENT_SIZE
;
2347 /* Skip to next state no non-binary transfers */
2348 ftpSendPassive(ftpState
);
2351 /// \ingroup ServerProtocolFTPInternal
2353 ftpReadSize(FtpStateData
* ftpState
)
2355 int code
= ftpState
->ctrl
.replycode
;
2360 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
2362 if (ftpState
->theSize
== 0) {
2363 debugs(9, 2, "SIZE reported " <<
2364 ftpState
->ctrl
.last_reply
<< " on " <<
2365 ftpState
->title_url
);
2366 ftpState
->theSize
= -1;
2368 } else if (code
< 0) {
2373 ftpSendPassive(ftpState
);
2377 \ingroup ServerProtocolFTPInternal
2380 ftpReadEPSV(FtpStateData
* ftpState
)
2382 int code
= ftpState
->ctrl
.replycode
;
2383 Ip::Address ipa_remote
;
2387 if (code
!= 229 && code
!= 522) {
2389 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2390 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2391 * Its okay to re-send EPSV 1/2 but nothing else. */
2392 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << ftpState
->ctrl
.conn
->remote
<< ". Wrong accept code for EPSV");
2394 debugs(9, 2, "EPSV not supported by remote end");
2395 ftpState
->state
= SENT_EPSV_1
; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2397 ftpSendPassive(ftpState
);
2402 /* server response with list of supported methods */
2403 /* 522 Network protocol not supported, use (1) */
2404 /* 522 Network protocol not supported, use (1,2) */
2405 /* 522 Network protocol not supported, use (2) */
2406 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
2407 * which means close data + control without self-destructing and re-open from scratch. */
2408 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2409 buf
= ftpState
->ctrl
.last_reply
;
2410 while (buf
!= NULL
&& *buf
!= '\0' && *buf
!= '\n' && *buf
!= '(')
2412 if (buf
!= NULL
&& *buf
== '\n')
2415 if (buf
== NULL
|| *buf
== '\0') {
2416 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2417 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << ftpState
->ctrl
.conn
->remote
<< ". 522 error missing protocol negotiation hints");
2418 ftpSendPassive(ftpState
);
2419 } else if (strcmp(buf
, "(1)") == 0) {
2420 ftpState
->state
= SENT_EPSV_2
; /* simulate having sent and failed EPSV 2 */
2421 ftpSendPassive(ftpState
);
2422 } else if (strcmp(buf
, "(2)") == 0) {
2423 if (Ip::EnableIpv6
) {
2424 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2425 if (ftpState
->state
== SENT_EPSV_2
) {
2426 ftpSendEPRT(ftpState
);
2428 /* or try the next Passive mode down the chain. */
2429 ftpSendPassive(ftpState
);
2432 /* Server only accept EPSV in IPv6 traffic. */
2433 ftpState
->state
= SENT_EPSV_1
; /* simulate having sent and failed EPSV 1 */
2434 ftpSendPassive(ftpState
);
2437 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2438 debugs(9, DBG_IMPORTANT
, "WARNING: Server at " << ftpState
->ctrl
.conn
->remote
<< " sent unknown protocol negotiation hint: " << buf
);
2439 ftpSendPassive(ftpState
);
2444 /* 229 Entering Extended Passive Mode (|||port|) */
2445 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2446 debugs(9, 5, "scanning: " << ftpState
->ctrl
.last_reply
);
2448 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "(");
2450 char h1
, h2
, h3
, h4
;
2451 unsigned short port
;
2452 int n
= sscanf(buf
, "(%c%c%c%hu%c)", &h1
, &h2
, &h3
, &port
, &h4
);
2454 if (n
< 4 || h1
!= h2
|| h1
!= h3
|| h1
!= h4
) {
2455 debugs(9, DBG_IMPORTANT
, "Invalid EPSV reply from " <<
2456 ftpState
->ctrl
.conn
->remote
<< ": " <<
2457 ftpState
->ctrl
.last_reply
);
2459 ftpSendPassive(ftpState
);
2464 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2465 ftpState
->ctrl
.conn
->remote
<< ": " <<
2466 ftpState
->ctrl
.last_reply
);
2468 ftpSendPassive(ftpState
);
2472 if (Config
.Ftp
.sanitycheck
) {
2474 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2475 ftpState
->ctrl
.conn
->remote
<< ": " <<
2476 ftpState
->ctrl
.last_reply
);
2478 ftpSendPassive(ftpState
);
2483 ftpState
->data
.port
= port
;
2485 safe_free(ftpState
->data
.host
);
2486 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.conn
->fd
].ipaddr
);
2488 safe_free(ftpState
->ctrl
.last_command
);
2490 safe_free(ftpState
->ctrl
.last_reply
);
2492 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2494 // Generate a new data channel descriptor to be opened.
2495 Comm::ConnectionPointer conn
= new Comm::Connection
;
2496 conn
->local
= ftpState
->ctrl
.conn
->local
;
2497 conn
->local
.SetPort(0);
2498 conn
->remote
= ftpState
->ctrl
.conn
->remote
;
2499 conn
->remote
.SetPort(port
);
2501 debugs(9, 3, HERE
<< "connecting to " << conn
->remote
);
2503 ftpState
->data
.opener
= commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback
, ftpState
));
2504 Comm::ConnOpener
*cs
= new Comm::ConnOpener(conn
, ftpState
->data
.opener
, Config
.Timeout
.connect
);
2505 cs
->setHost(ftpState
->data
.host
);
2506 AsyncJob::Start(cs
);
2509 /** \ingroup ServerProtocolFTPInternal
2511 * Send Passive connection request.
2512 * Default method is to use modern EPSV request.
2513 * The failover mechanism should check for previous state and re-call with alternates on failure.
2516 ftpSendPassive(FtpStateData
* ftpState
)
2518 /** Checks the server control channel is still available before running. */
2519 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
2525 * Checks for EPSV ALL special conditions:
2526 * If enabled to be sent, squid MUST NOT request any other connect methods.
2527 * If 'ALL' is sent and fails the entire FTP Session fails.
2528 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
2529 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
&& ftpState
->state
== SENT_EPSV_1
) {
2530 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2536 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
2537 if (ftpState
->request
->method
== METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
2538 ftpState
->processHeadResponse(); // may call serverComplete
2542 /// Closes any old FTP-Data connection which may exist. */
2543 ftpState
->data
.close();
2546 * Checks for previous EPSV/PASV failures on this server/session.
2547 * Diverts to EPRT immediately if they are not working. */
2548 if (!ftpState
->flags
.pasv_supported
) {
2549 ftpSendEPRT(ftpState
);
2554 * Send EPSV (ALL,2,1) or PASV on the control channel.
2556 * - EPSV ALL is used if enabled.
2557 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2558 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
2559 * - PASV is used if EPSV 1 fails.
2561 switch (ftpState
->state
) {
2562 case SENT_EPSV_ALL
: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2563 ftpState
->flags
.epsv_all_sent
= true;
2564 if (ftpState
->ctrl
.conn
->local
.IsIPv6()) {
2565 debugs(9, 5, HERE
<< "FTP Channel is IPv6 (" << ftpState
->ctrl
.conn
->remote
<< ") attempting EPSV 2 after EPSV ALL has failed.");
2566 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 2\r\n");
2567 ftpState
->state
= SENT_EPSV_2
;
2570 // else fall through to skip EPSV 2
2572 case SENT_EPSV_2
: /* EPSV IPv6 failed. Try EPSV IPv4 */
2573 if (ftpState
->ctrl
.conn
->local
.IsIPv4()) {
2574 debugs(9, 5, HERE
<< "FTP Channel is IPv4 (" << ftpState
->ctrl
.conn
->remote
<< ") attempting EPSV 1 after EPSV ALL has failed.");
2575 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 1\r\n");
2576 ftpState
->state
= SENT_EPSV_1
;
2578 } else if (ftpState
->flags
.epsv_all_sent
) {
2579 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2583 // else fall through to skip EPSV 1
2585 case SENT_EPSV_1
: /* EPSV options exhausted. Try PASV now. */
2586 debugs(9, 5, HERE
<< "FTP Channel (" << ftpState
->ctrl
.conn
->remote
<< ") rejects EPSV connection attempts. Trying PASV instead.");
2587 snprintf(cbuf
, CTRL_BUFLEN
, "PASV\r\n");
2588 ftpState
->state
= SENT_PASV
;
2592 if (!Config
.Ftp
.epsv
) {
2593 debugs(9, 5, HERE
<< "EPSV support manually disabled. Sending PASV for FTP Channel (" << ftpState
->ctrl
.conn
->remote
<<")");
2594 snprintf(cbuf
, CTRL_BUFLEN
, "PASV\r\n");
2595 ftpState
->state
= SENT_PASV
;
2596 } else if (Config
.Ftp
.epsv_all
) {
2597 debugs(9, 5, HERE
<< "EPSV ALL manually enabled. Attempting with FTP Channel (" << ftpState
->ctrl
.conn
->remote
<<")");
2598 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV ALL\r\n");
2599 ftpState
->state
= SENT_EPSV_ALL
;
2600 /* block other non-EPSV connections being attempted */
2601 ftpState
->flags
.epsv_all_sent
= true;
2603 if (ftpState
->ctrl
.conn
->local
.IsIPv6()) {
2604 debugs(9, 5, HERE
<< "FTP Channel (" << ftpState
->ctrl
.conn
->remote
<< "). Sending default EPSV 2");
2605 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 2\r\n");
2606 ftpState
->state
= SENT_EPSV_2
;
2608 if (ftpState
->ctrl
.conn
->local
.IsIPv4()) {
2609 debugs(9, 5, HERE
<< "Channel (" << ftpState
->ctrl
.conn
->remote
<<"). Sending default EPSV 1");
2610 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 1\r\n");
2611 ftpState
->state
= SENT_EPSV_1
;
2617 ftpState
->writeCommand(cbuf
);
2620 * ugly hack for ftp servers like ftp.netscape.com that sometimes
2621 * dont acknowledge PASV commands. Use connect timeout to be faster then read timeout (minutes).
2623 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2624 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
2625 TimeoutDialer
, ftpState
, FtpStateData::ftpTimeout
);
2626 commSetConnTimeout(ftpState
->ctrl
.conn
, Config
.Timeout
.connect
, timeoutCall
);
2630 FtpStateData::processHeadResponse()
2632 debugs(9, 5, HERE
<< "handling HEAD response");
2634 appendSuccessHeader();
2637 * On rare occasions I'm seeing the entry get aborted after
2638 * ftpReadControlReply() and before here, probably when
2639 * trying to write to the client.
2641 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2642 abortTransaction("entry aborted while processing HEAD");
2647 if (adaptationAccessCheckPending
) {
2648 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
2653 // processReplyBody calls serverComplete() since there is no body
2657 /// \ingroup ServerProtocolFTPInternal
2659 ftpReadPasv(FtpStateData
* ftpState
)
2661 int code
= ftpState
->ctrl
.replycode
;
2665 unsigned short port
;
2666 Ip::Address ipa_remote
;
2668 LOCAL_ARRAY(char, ipaddr
, 1024);
2672 debugs(9, 2, "PASV not supported by remote end");
2673 ftpSendEPRT(ftpState
);
2677 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2678 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2679 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2681 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "0123456789");
2683 n
= sscanf(buf
, "%d,%d,%d,%d,%d,%d", &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
2685 if (n
!= 6 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
2686 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2687 ftpState
->ctrl
.conn
->remote
<< ": " <<
2688 ftpState
->ctrl
.last_reply
);
2690 ftpSendEPRT(ftpState
);
2694 snprintf(ipaddr
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
2696 ipa_remote
= ipaddr
;
2698 if ( ipa_remote
.IsAnyAddr() ) {
2699 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2700 ftpState
->ctrl
.conn
->remote
<< ": " <<
2701 ftpState
->ctrl
.last_reply
);
2703 ftpSendEPRT(ftpState
);
2707 port
= ((p1
<< 8) + p2
);
2710 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2711 ftpState
->ctrl
.conn
->remote
<< ": " <<
2712 ftpState
->ctrl
.last_reply
);
2714 ftpSendEPRT(ftpState
);
2718 if (Config
.Ftp
.sanitycheck
) {
2720 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2721 ftpState
->ctrl
.conn
->remote
<< ": " <<
2722 ftpState
->ctrl
.last_reply
);
2724 ftpSendEPRT(ftpState
);
2729 ftpState
->data
.port
= port
;
2731 safe_free(ftpState
->data
.host
);
2732 if (Config
.Ftp
.sanitycheck
)
2733 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.conn
->fd
].ipaddr
);
2735 ftpState
->data
.host
= xstrdup(ipaddr
);
2737 safe_free(ftpState
->ctrl
.last_command
);
2739 safe_free(ftpState
->ctrl
.last_reply
);
2741 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2743 Comm::ConnectionPointer conn
= new Comm::Connection
;
2744 conn
->local
= ftpState
->ctrl
.conn
->local
;
2745 conn
->local
.SetPort(0);
2746 conn
->remote
= ipaddr
;
2747 conn
->remote
.SetPort(port
);
2749 debugs(9, 3, HERE
<< "connecting to " << conn
->remote
);
2751 ftpState
->data
.opener
= commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback
, ftpState
));
2752 Comm::ConnOpener
*cs
= new Comm::ConnOpener(conn
, ftpState
->data
.opener
, Config
.Timeout
.connect
);
2753 cs
->setHost(ftpState
->data
.host
);
2754 AsyncJob::Start(cs
);
2758 FtpStateData::ftpPasvCallback(const Comm::ConnectionPointer
&conn
, comm_err_t status
, int xerrno
, void *data
)
2760 FtpStateData
*ftpState
= (FtpStateData
*)data
;
2762 ftpState
->data
.opener
= NULL
;
2764 if (status
!= COMM_OK
) {
2765 debugs(9, 2, HERE
<< "Failed to connect. Retrying via another method.");
2767 // ABORT on timeouts. server may be waiting on a broken TCP link.
2768 if (status
== COMM_TIMEOUT
)
2769 ftpState
->writeCommand("ABOR");
2771 // try another connection attempt with some other method
2772 ftpSendPassive(ftpState
);
2776 ftpState
->data
.opened(conn
, ftpState
->dataCloser());
2777 ftpRestOrList(ftpState
);
2780 /// \ingroup ServerProtocolFTPInternal
2782 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
2784 /// Close old data channels, if any. We may open a new one below.
2785 if (ftpState
->data
.conn
!= NULL
) {
2786 if ((ftpState
->data
.conn
->flags
& COMM_REUSEADDR
))
2787 // NP: in fact it points to the control channel. just clear it.
2788 ftpState
->data
.clear();
2790 ftpState
->data
.close();
2792 safe_free(ftpState
->data
.host
);
2795 * Set up a listen socket on the same local address as the
2796 * control connection.
2798 Comm::ConnectionPointer temp
= new Comm::Connection
;
2799 temp
->local
= ftpState
->ctrl
.conn
->local
;
2802 * REUSEADDR is needed in fallback mode, since the same port is
2803 * used for both control and data.
2807 setsockopt(ftpState
->ctrl
.conn
->fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
2808 ftpState
->ctrl
.conn
->flags
|= COMM_REUSEADDR
;
2809 temp
->flags
|= COMM_REUSEADDR
;
2811 /* if not running in fallback mode a new port needs to be retrieved */
2812 temp
->local
.SetPort(0);
2815 ftpState
->listenForDataChannel(temp
, ftpState
->entry
->url());
2818 /// \ingroup ServerProtocolFTPInternal
2820 ftpSendPORT(FtpStateData
* ftpState
)
2822 /* check the server control channel is still available */
2823 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
2826 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2827 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2832 ftpState
->flags
.pasv_supported
= 0;
2833 ftpOpenListenSocket(ftpState
, 0);
2835 if (!Comm::IsConnOpen(ftpState
->data
.listenConn
)) {
2836 if ( ftpState
->data
.listenConn
!= NULL
&& !ftpState
->data
.listenConn
->local
.IsIPv4() ) {
2837 /* non-IPv4 CANNOT send PORT command. */
2838 /* we got here by attempting and failing an EPRT */
2839 /* using the same reply code should simulate a PORT failure */
2840 ftpReadPORT(ftpState
);
2844 /* XXX Need to set error message */
2849 // pull out the internal IP address bytes to send in PORT command...
2850 // source them from the listen_conn->local
2852 struct addrinfo
*AI
= NULL
;
2853 ftpState
->data
.listenConn
->local
.GetAddrInfo(AI
, AF_INET
);
2854 unsigned char *addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
2855 unsigned char *portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
2856 snprintf(cbuf
, CTRL_BUFLEN
, "PORT %d,%d,%d,%d,%d,%d\r\n",
2857 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
2858 portptr
[0], portptr
[1]);
2859 ftpState
->writeCommand(cbuf
);
2860 ftpState
->state
= SENT_PORT
;
2862 ftpState
->data
.listenConn
->local
.FreeAddrInfo(AI
);
2865 /// \ingroup ServerProtocolFTPInternal
2867 ftpReadPORT(FtpStateData
* ftpState
)
2869 int code
= ftpState
->ctrl
.replycode
;
2873 /* Fall back on using the same port as the control connection */
2874 debugs(9, 3, "PORT not supported by remote end");
2875 ftpOpenListenSocket(ftpState
, 1);
2878 ftpRestOrList(ftpState
);
2881 /// \ingroup ServerProtocolFTPInternal
2883 ftpSendEPRT(FtpStateData
* ftpState
)
2885 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2886 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2890 if (!Config
.Ftp
.eprt
) {
2891 /* Disabled. Switch immediately to attempting old PORT command. */
2892 debugs(9, 3, "EPRT disabled by local administrator");
2893 ftpSendPORT(ftpState
);
2898 ftpState
->flags
.pasv_supported
= 0;
2900 ftpOpenListenSocket(ftpState
, 0);
2901 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState
->data
.conn
);
2902 if (!Comm::IsConnOpen(ftpState
->data
.conn
)) {
2903 /* XXX Need to set error message */
2908 char buf
[MAX_IPSTRLEN
];
2910 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2911 /* Which can be used by EITHER protocol. */
2912 snprintf(cbuf
, CTRL_BUFLEN
, "EPRT |%d|%s|%d|\r\n",
2913 ( ftpState
->data
.listenConn
->local
.IsIPv6() ? 2 : 1 ),
2914 ftpState
->data
.listenConn
->local
.NtoA(buf
,MAX_IPSTRLEN
),
2915 ftpState
->data
.listenConn
->local
.GetPort() );
2917 ftpState
->writeCommand(cbuf
);
2918 ftpState
->state
= SENT_EPRT
;
2922 ftpReadEPRT(FtpStateData
* ftpState
)
2924 int code
= ftpState
->ctrl
.replycode
;
2928 /* Failover to attempting old PORT command. */
2929 debugs(9, 3, "EPRT not supported by remote end");
2930 ftpSendPORT(ftpState
);
2934 ftpRestOrList(ftpState
);
2938 \ingroup ServerProtocolFTPInternal
2940 * "read" handler to accept FTP data connections.
2942 \param io comm accept(2) callback parameters
2945 FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
2949 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2950 abortTransaction("entry aborted when accepting data conn");
2951 data
.listenConn
->close();
2952 data
.listenConn
= NULL
;
2956 if (io
.flag
!= COMM_OK
) {
2957 data
.listenConn
->close();
2958 data
.listenConn
= NULL
;
2959 debugs(9, DBG_IMPORTANT
, "FTP AcceptDataConnection: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
2960 /** \todo Need to send error message on control channel*/
2965 /* data listening conn is no longer even open. abort. */
2966 if (!Comm::IsConnOpen(data
.listenConn
)) {
2967 data
.listenConn
= NULL
; // ensure that it's cleared and not just closed.
2971 /* data listening conn is no longer even open. abort. */
2972 if (!Comm::IsConnOpen(data
.conn
)) {
2973 data
.clear(); // ensure that it's cleared and not just closed.
2978 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2979 * made by the remote client which is connected to the FTP control socket.
2980 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
2981 * This prevents third-party hacks, but also third-party load balancing handshakes.
2983 if (Config
.Ftp
.sanitycheck
) {
2984 // accept if either our data or ctrl connection is talking to this remote peer.
2985 if (data
.conn
->remote
!= io
.conn
->remote
&& ctrl
.conn
->remote
!= io
.conn
->remote
) {
2986 debugs(9, DBG_IMPORTANT
,
2987 "FTP data connection from unexpected server (" <<
2988 io
.conn
->remote
<< "), expecting " <<
2989 data
.conn
->remote
<< " or " << ctrl
.conn
->remote
);
2991 /* close the bad sources connection down ASAP. */
2994 /* drop the bad connection (io) by ignoring the attempt. */
2999 /** On COMM_OK start using the accepted data socket and discard the temporary listen socket. */
3001 data
.opened(io
.conn
, dataCloser());
3002 static char ntoapeer
[MAX_IPSTRLEN
];
3003 io
.conn
->remote
.NtoA(ntoapeer
,sizeof(ntoapeer
));
3004 data
.host
= xstrdup(ntoapeer
);
3006 debugs(9, 3, HERE
<< "Connected data socket on " <<
3007 io
.conn
<< ". FD table says: " <<
3008 "ctrl-peer= " << fd_table
[ctrl
.conn
->fd
].ipaddr
<< ", " <<
3009 "data-peer= " << fd_table
[data
.conn
->fd
].ipaddr
);
3011 assert(haveControlChannel("ftpAcceptDataConnection"));
3012 assert(ctrl
.message
== NULL
);
3014 // Ctrl channel operations will determine what happens to this data connection
3017 /// \ingroup ServerProtocolFTPInternal
3019 ftpRestOrList(FtpStateData
* ftpState
)
3023 if (ftpState
->typecode
== 'D') {
3024 ftpState
->flags
.isdir
= 1;
3026 if (ftpState
->flags
.put
) {
3027 ftpSendMkdir(ftpState
); /* PUT name;type=d */
3029 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3031 } else if (ftpState
->flags
.put
) {
3032 ftpSendStor(ftpState
);
3033 } else if (ftpState
->flags
.isdir
)
3034 ftpSendList(ftpState
);
3035 else if (ftpState
->restartable())
3036 ftpSendRest(ftpState
);
3038 ftpSendRetr(ftpState
);
3041 /// \ingroup ServerProtocolFTPInternal
3043 ftpSendStor(FtpStateData
* ftpState
)
3045 /* check the server control channel is still available */
3046 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
3051 if (ftpState
->filepath
!= NULL
) {
3052 /* Plain file upload */
3053 snprintf(cbuf
, CTRL_BUFLEN
, "STOR %s\r\n", ftpState
->filepath
);
3054 ftpState
->writeCommand(cbuf
);
3055 ftpState
->state
= SENT_STOR
;
3056 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
3057 /* File upload without a filename. use STOU to generate one */
3058 snprintf(cbuf
, CTRL_BUFLEN
, "STOU\r\n");
3059 ftpState
->writeCommand(cbuf
);
3060 ftpState
->state
= SENT_STOR
;
3062 /* No file to transfer. Only create directories if needed */
3063 ftpSendReply(ftpState
);
3067 /// \ingroup ServerProtocolFTPInternal
3068 /// \deprecated use ftpState->readStor() instead.
3070 ftpReadStor(FtpStateData
* ftpState
)
3072 ftpState
->readStor();
3075 void FtpStateData::readStor()
3077 int code
= ctrl
.replycode
;
3080 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(data
.conn
))) {
3081 if (!startRequestBodyFlow()) { // register to receive body data
3086 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
3087 debugs(9, 3, HERE
<< "starting data transfer");
3088 switchTimeoutToDataChannel();
3089 sendMoreRequestBody();
3090 fwd
->dontRetry(true); // dont permit re-trying if the body was sent.
3091 state
= WRITING_DATA
;
3092 debugs(9, 3, HERE
<< "writing data channel");
3093 } else if (code
== 150) {
3094 /* When client code is 150 with no data channel, Accept data channel. */
3095 debugs(9, 3, "ftpReadStor: accepting data channel");
3096 listenForDataChannel(data
.conn
, data
.host
);
3098 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
3103 /// \ingroup ServerProtocolFTPInternal
3105 ftpSendRest(FtpStateData
* ftpState
)
3107 /* check the server control channel is still available */
3108 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
3113 snprintf(cbuf
, CTRL_BUFLEN
, "REST %" PRId64
"\r\n", ftpState
->restart_offset
);
3114 ftpState
->writeCommand(cbuf
);
3115 ftpState
->state
= SENT_REST
;
3119 FtpStateData::restartable()
3121 if (restart_offset
> 0)
3124 if (!request
->range
)
3133 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
3135 if (desired_offset
<= 0)
3138 if (desired_offset
>= theSize
)
3141 restart_offset
= desired_offset
;
3145 /// \ingroup ServerProtocolFTPInternal
3147 ftpReadRest(FtpStateData
* ftpState
)
3149 int code
= ftpState
->ctrl
.replycode
;
3151 assert(ftpState
->restart_offset
> 0);
3154 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
3155 ftpSendRetr(ftpState
);
3156 } else if (code
> 0) {
3157 debugs(9, 3, HERE
<< "REST not supported");
3158 ftpState
->flags
.rest_supported
= 0;
3159 ftpSendRetr(ftpState
);
3165 /// \ingroup ServerProtocolFTPInternal
3167 ftpSendList(FtpStateData
* ftpState
)
3169 /* check the server control channel is still available */
3170 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
3175 if (ftpState
->filepath
) {
3176 snprintf(cbuf
, CTRL_BUFLEN
, "LIST %s\r\n", ftpState
->filepath
);
3178 snprintf(cbuf
, CTRL_BUFLEN
, "LIST\r\n");
3181 ftpState
->writeCommand(cbuf
);
3182 ftpState
->state
= SENT_LIST
;
3185 /// \ingroup ServerProtocolFTPInternal
3187 ftpSendNlst(FtpStateData
* ftpState
)
3189 /* check the server control channel is still available */
3190 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
3195 ftpState
->flags
.tried_nlst
= 1;
3197 if (ftpState
->filepath
) {
3198 snprintf(cbuf
, CTRL_BUFLEN
, "NLST %s\r\n", ftpState
->filepath
);
3200 snprintf(cbuf
, CTRL_BUFLEN
, "NLST\r\n");
3203 ftpState
->writeCommand(cbuf
);
3204 ftpState
->state
= SENT_NLST
;
3207 /// \ingroup ServerProtocolFTPInternal
3209 ftpReadList(FtpStateData
* ftpState
)
3211 int code
= ftpState
->ctrl
.replycode
;
3214 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
3215 /* Begin data transfer */
3216 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3217 ftpState
->switchTimeoutToDataChannel();
3218 ftpState
->maybeReadVirginBody();
3219 ftpState
->state
= READING_DATA
;
3221 } else if (code
== 150) {
3222 /* Accept data channel */
3223 debugs(9, 3, HERE
<< "accept data channel from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3224 ftpState
->listenForDataChannel(ftpState
->data
.conn
, ftpState
->data
.host
);
3226 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
3227 ftpSendNlst(ftpState
);
3234 /// \ingroup ServerProtocolFTPInternal
3236 ftpSendRetr(FtpStateData
* ftpState
)
3238 /* check the server control channel is still available */
3239 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
3244 assert(ftpState
->filepath
!= NULL
);
3245 snprintf(cbuf
, CTRL_BUFLEN
, "RETR %s\r\n", ftpState
->filepath
);
3246 ftpState
->writeCommand(cbuf
);
3247 ftpState
->state
= SENT_RETR
;
3250 /// \ingroup ServerProtocolFTPInternal
3252 ftpReadRetr(FtpStateData
* ftpState
)
3254 int code
= ftpState
->ctrl
.replycode
;
3257 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
3258 /* Begin data transfer */
3259 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3260 ftpState
->switchTimeoutToDataChannel();
3261 ftpState
->maybeReadVirginBody();
3262 ftpState
->state
= READING_DATA
;
3263 } else if (code
== 150) {
3264 /* Accept data channel */
3265 ftpState
->listenForDataChannel(ftpState
->data
.conn
, ftpState
->data
.host
);
3266 } else if (code
>= 300) {
3267 if (!ftpState
->flags
.try_slash_hack
) {
3268 /* Try this as a directory missing trailing slash... */
3269 ftpState
->hackShortcut(ftpSendCwd
);
3279 * Generate the HTTP headers and template fluff around an FTP
3280 * directory listing display.
3283 FtpStateData::completedListing()
3287 ErrorState
ferr(ERR_DIR_LISTING
, HTTP_OK
, request
);
3288 ferr
.ftp
.listing
= &listing
;
3289 ferr
.ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
3290 ferr
.ftp
.server_msg
= ctrl
.message
;
3291 ctrl
.message
= NULL
;
3292 entry
->replaceHttpReply( ferr
.BuildHttpReply() );
3293 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3298 /// \ingroup ServerProtocolFTPInternal
3300 ftpReadTransferDone(FtpStateData
* ftpState
)
3302 int code
= ftpState
->ctrl
.replycode
;
3305 if (code
== 226 || code
== 250) {
3306 /* Connection closed; retrieval done. */
3307 if (ftpState
->flags
.listing
) {
3308 ftpState
->completedListing();
3309 /* QUIT operation handles sending the reply to client */
3311 ftpSendQuit(ftpState
);
3312 } else { /* != 226 */
3313 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
3314 ftpState
->failed(ERR_FTP_FAILURE
, 0);
3315 /* failed closes ctrl.conn and frees ftpState */
3320 // premature end of the request body
3322 FtpStateData::handleRequestBodyProducerAborted()
3324 ServerStateData::handleRequestBodyProducerAborted();
3325 debugs(9, 3, HERE
<< "ftpState=" << this);
3326 failed(ERR_READ_ERROR
, 0);
3330 * This will be called when the put write is completed
3333 FtpStateData::sentRequestBody(const CommIoCbParams
&io
)
3336 kb_incr(&(statCounter
.server
.ftp
.kbytes_out
), io
.size
);
3337 ServerStateData::sentRequestBody(io
);
3340 /// \ingroup ServerProtocolFTPInternal
3342 ftpWriteTransferDone(FtpStateData
* ftpState
)
3344 int code
= ftpState
->ctrl
.replycode
;
3347 if (!(code
== 226 || code
== 250)) {
3348 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
3349 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
3353 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
3354 ftpSendReply(ftpState
);
3357 /// \ingroup ServerProtocolFTPInternal
3359 ftpSendQuit(FtpStateData
* ftpState
)
3361 /* check the server control channel is still available */
3362 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
3365 snprintf(cbuf
, CTRL_BUFLEN
, "QUIT\r\n");
3366 ftpState
->writeCommand(cbuf
);
3367 ftpState
->state
= SENT_QUIT
;
3371 * \ingroup ServerProtocolFTPInternal
3373 * This completes a client FTP operation with success or other page
3374 * generated and stored in the entry field by the code issuing QUIT.
3377 ftpReadQuit(FtpStateData
* ftpState
)
3379 ftpState
->serverComplete();
3382 /// \ingroup ServerProtocolFTPInternal
3384 ftpTrySlashHack(FtpStateData
* ftpState
)
3387 ftpState
->flags
.try_slash_hack
= 1;
3388 /* Free old paths */
3392 if (ftpState
->pathcomps
)
3393 wordlistDestroy(&ftpState
->pathcomps
);
3395 safe_free(ftpState
->filepath
);
3397 /* Build the new path (urlpath begins with /) */
3398 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
3400 rfc1738_unescape(path
);
3402 ftpState
->filepath
= path
;
3405 ftpGetFile(ftpState
);
3409 * Forget hack status. Next error is shown to the user
3412 FtpStateData::unhack()
3416 if (old_request
!= NULL
) {
3417 safe_free(old_request
);
3418 safe_free(old_reply
);
3423 FtpStateData::hackShortcut(FTPSM
* nextState
)
3425 /* Clear some unwanted state */
3426 setCurrentOffset(0);
3428 /* Save old error message & some state info */
3432 if (old_request
== NULL
) {
3433 old_request
= ctrl
.last_command
;
3434 ctrl
.last_command
= NULL
;
3435 old_reply
= ctrl
.last_reply
;
3436 ctrl
.last_reply
= NULL
;
3438 if (pathcomps
== NULL
&& filepath
!= NULL
)
3439 old_filepath
= xstrdup(filepath
);
3442 /* Jump to the "hack" state */
3446 /// \ingroup ServerProtocolFTPInternal
3448 ftpFail(FtpStateData
*ftpState
)
3450 debugs(9, 6, HERE
<< "flags(" <<
3451 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
3452 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
3453 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
3454 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
3456 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
3457 if (!ftpState
->flags
.isdir
&& /* Not a directory */
3458 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
3459 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
3460 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
3462 switch (ftpState
->state
) {
3467 /* Try the / hack */
3468 ftpState
->hackShortcut(ftpTrySlashHack
);
3476 ftpState
->failed(ERR_NONE
, 0);
3477 /* failed() closes ctrl.conn and frees this */
3481 FtpStateData::failed(err_type error
, int xerrno
)
3483 debugs(9,3,HERE
<< "entry-null=" << (entry
?entry
->isEmpty():0) << ", entry=" << entry
);
3484 if (entry
->isEmpty())
3485 failedErrorMessage(error
, xerrno
);
3491 FtpStateData::failedErrorMessage(err_type error
, int xerrno
)
3493 ErrorState
*ftperr
= NULL
;
3494 const char *command
, *reply
;
3496 /* Translate FTP errors into HTTP errors */
3507 if (ctrl
.replycode
> 500)
3509 ftperr
= new ErrorState(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
3511 ftperr
= new ErrorState(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
3513 else if (ctrl
.replycode
== 421)
3514 ftperr
= new ErrorState(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
3521 if (ctrl
.replycode
== 550)
3522 ftperr
= new ErrorState(ERR_FTP_NOT_FOUND
, HTTP_NOT_FOUND
, fwd
->request
);
3532 case ERR_READ_TIMEOUT
:
3533 ftperr
= new ErrorState(error
, HTTP_GATEWAY_TIMEOUT
, fwd
->request
);
3537 ftperr
= new ErrorState(error
, HTTP_BAD_GATEWAY
, fwd
->request
);
3542 ftperr
= new ErrorState(ERR_FTP_FAILURE
, HTTP_BAD_GATEWAY
, fwd
->request
);
3544 ftperr
->xerrno
= xerrno
;
3546 ftperr
->ftp
.server_msg
= ctrl
.message
;
3547 ctrl
.message
= NULL
;
3550 command
= old_request
;
3552 command
= ctrl
.last_command
;
3554 if (command
&& strncmp(command
, "PASS", 4) == 0)
3555 command
= "PASS <yourpassword>";
3560 reply
= ctrl
.last_reply
;
3563 ftperr
->ftp
.request
= xstrdup(command
);
3566 ftperr
->ftp
.reply
= xstrdup(reply
);
3568 entry
->replaceHttpReply( ftperr
->BuildHttpReply() );
3572 /// \ingroup ServerProtocolFTPInternal
3574 ftpSendReply(FtpStateData
* ftpState
)
3576 int code
= ftpState
->ctrl
.replycode
;
3577 http_status http_code
;
3578 err_type err_code
= ERR_NONE
;
3580 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
3582 if (cbdataReferenceValid(ftpState
))
3583 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
3585 if (code
== 226 || code
== 250) {
3586 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
3587 http_code
= (ftpState
->mdtm
> 0) ? HTTP_ACCEPTED
: HTTP_CREATED
;
3588 } else if (code
== 227) {
3589 err_code
= ERR_FTP_PUT_CREATED
;
3590 http_code
= HTTP_CREATED
;
3592 err_code
= ERR_FTP_PUT_ERROR
;
3593 http_code
= HTTP_INTERNAL_SERVER_ERROR
;
3596 ErrorState
err(err_code
, http_code
, ftpState
->request
);
3598 if (ftpState
->old_request
)
3599 err
.ftp
.request
= xstrdup(ftpState
->old_request
);
3601 err
.ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
3603 if (ftpState
->old_reply
)
3604 err
.ftp
.reply
= xstrdup(ftpState
->old_reply
);
3605 else if (ftpState
->ctrl
.last_reply
)
3606 err
.ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
3608 err
.ftp
.reply
= xstrdup("");
3610 // TODO: interpret as FTP-specific error code
3611 err
.detailError(code
);
3613 ftpState
->entry
->replaceHttpReply( err
.BuildHttpReply() );
3615 ftpSendQuit(ftpState
);
3619 FtpStateData::appendSuccessHeader()
3621 const char *mime_type
= NULL
;
3622 const char *mime_enc
= NULL
;
3623 String urlpath
= request
->urlpath
;
3624 const char *filename
= NULL
;
3625 const char *t
= NULL
;
3629 if (flags
.http_header_sent
)
3632 HttpReply
*reply
= new HttpReply
;
3634 flags
.http_header_sent
= 1;
3636 assert(entry
->isEmpty());
3638 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3640 entry
->buffer(); /* released when done processing current data payload */
3642 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
3645 mime_type
= "text/html";
3650 mime_type
= "application/octet-stream";
3651 mime_enc
= mimeGetContentEncoding(filename
);
3655 mime_type
= "text/plain";
3659 mime_type
= mimeGetContentType(filename
);
3660 mime_enc
= mimeGetContentEncoding(filename
);
3665 /* set standard stuff */
3667 if (0 == getCurrentOffset()) {
3669 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3670 } else if (theSize
< getCurrentOffset()) {
3673 * offset should not be larger than theSize. We should
3674 * not be seeing this condition any more because we'll only
3675 * send REST if we know the theSize and if it is less than theSize.
3677 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
3678 " current offset=" << getCurrentOffset() <<
3679 ", but theSize=" << theSize
<<
3680 ". assuming full content response");
3681 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3684 HttpHdrRangeSpec range_spec
;
3685 range_spec
.offset
= getCurrentOffset();
3686 range_spec
.length
= theSize
- getCurrentOffset();
3687 reply
->setHeaders(HTTP_PARTIAL_CONTENT
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
3688 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
3691 /* additional info */
3693 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
3695 setVirginReply(reply
);
3696 adaptOrFinalizeReply();
3700 FtpStateData::haveParsedReplyHeaders()
3702 ServerStateData::haveParsedReplyHeaders();
3704 StoreEntry
*e
= entry
;
3708 if (flags
.authenticated
) {
3710 * Authenticated requests can't be cached.
3713 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !getCurrentOffset()) {
3721 FtpStateData::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
3723 ErrorState
err(ERR_CACHE_ACCESS_DENIED
, HTTP_UNAUTHORIZED
, request
);
3724 HttpReply
*newrep
= err
.BuildHttpReply();
3725 #if HAVE_AUTH_MODULE_BASIC
3726 /* add Authenticate header */
3727 newrep
->header
.putAuth("Basic", realm
);
3733 \ingroup ServerProtocolFTPAPI
3734 \todo Should be a URL class API call.
3736 * Construct an URI with leading / in PATH portion for use by CWD command
3737 * possibly others. FTP encodes absolute paths as beginning with '/'
3738 * after the initial URI path delimiter, which happens to be / itself.
3739 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3740 * To encompass older software which compacts multiple // to / in transit
3741 * We use standard URI-encoding on the second / making it
3742 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3745 ftpUrlWith2f(HttpRequest
* request
)
3747 String newbuf
= "%2f";
3749 if (request
->protocol
!= AnyP::PROTO_FTP
)
3752 if ( request
->urlpath
[0]=='/' ) {
3753 newbuf
.append(request
->urlpath
);
3754 request
->urlpath
.absorb(newbuf
);
3755 safe_free(request
->canonical
);
3756 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
3757 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
3758 request
->urlpath
.absorb(newbuf
);
3759 safe_free(request
->canonical
);
3762 return urlCanonical(request
);
3766 FtpStateData::printfReplyBody(const char *fmt
, ...)
3769 va_start (args
, fmt
);
3770 static char buf
[4096];
3772 vsnprintf(buf
, 4096, fmt
, args
);
3773 writeReplyBody(buf
, strlen(buf
));
3777 * Call this when there is data from the origin server
3778 * which should be sent to either StoreEntry, or to ICAP...
3781 FtpStateData::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
3783 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
3784 addVirginReplyBody(dataToWrite
, dataLength
);
3788 * called after we wrote the last byte of the request body
3791 FtpStateData::doneSendingRequestBody()
3793 ServerStateData::doneSendingRequestBody();
3796 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3797 * if transfer type is 'stream' call dataComplete()
3798 * otherwise leave open. (reschedule control channel read?)
3803 * A hack to ensure we do not double-complete on the forward entry.
3805 \todo FtpStateData logic should probably be rewritten to avoid
3806 * double-completion or FwdState should be rewritten to allow it.
3809 FtpStateData::completeForwarding()
3811 if (fwd
== NULL
|| flags
.completed_forwarding
) {
3812 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
3813 "double-complete on FD " << ctrl
.conn
->fd
<< ", Data FD " << data
.conn
->fd
<<
3814 ", this " << this << ", fwd " << fwd
);
3818 flags
.completed_forwarding
= true;
3819 ServerStateData::completeForwarding();
3823 * Close the FTP server connection(s). Used by serverComplete().
3826 FtpStateData::closeServer()
3828 if (Comm::IsConnOpen(ctrl
.conn
)) {
3829 debugs(9,3, HERE
<< "closing FTP server FD " << ctrl
.conn
->fd
<< ", this " << this);
3830 fwd
->unregister(ctrl
.conn
);
3834 if (Comm::IsConnOpen(data
.conn
)) {
3835 debugs(9,3, HERE
<< "closing FTP data FD " << data
.conn
->fd
<< ", this " << this);
3839 debugs(9,3, HERE
<< "FTP ctrl and data connections closed. this " << this);
3843 * Did we close all FTP server connection(s)?
3845 \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
3846 \retval false Either control channel or data is still active.
3849 FtpStateData::doneWithServer() const
3851 return !Comm::IsConnOpen(ctrl
.conn
) && !Comm::IsConnOpen(data
.conn
);
3855 * Have we lost the FTP server control channel?
3857 \retval true The server control channel is available.
3858 \retval false The server control channel is not available.
3861 FtpStateData::haveControlChannel(const char *caller_name
) const
3863 if (doneWithServer())
3866 /* doneWithServer() only checks BOTH channels are closed. */
3867 if (!Comm::IsConnOpen(ctrl
.conn
)) {
3868 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
3869 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
3877 * Quickly abort the transaction
3879 \todo destruction should be sufficient as the destructor should cleanup,
3880 * including canceling close handlers
3883 FtpStateData::abortTransaction(const char *reason
)
3885 debugs(9, 3, HERE
<< "aborting transaction for " << reason
<<
3886 "; FD " << (ctrl
.conn
!=NULL
?ctrl
.conn
->fd
:-1) << ", Data FD " << (data
.conn
!=NULL
?data
.conn
->fd
:-1) << ", this " << this);
3887 if (Comm::IsConnOpen(ctrl
.conn
)) {
3892 fwd
->handleUnregisteredServerEnd();
3893 mustStop("FtpStateData::abortTransaction");
3896 /// creates a data channel Comm close callback
3898 FtpStateData::dataCloser()
3900 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
3901 return JobCallback(9, 5, Dialer
, this, FtpStateData::dataClosed
);
3904 /// configures the channel with a descriptor and registers a close handler
3906 FtpChannel::opened(const Comm::ConnectionPointer
&newConn
, const AsyncCall::Pointer
&aCloser
)
3908 assert(!Comm::IsConnOpen(conn
));
3909 assert(closer
== NULL
);
3911 assert(Comm::IsConnOpen(newConn
));
3912 assert(aCloser
!= NULL
);
3916 comm_add_close_handler(conn
->fd
, closer
);
3919 /// planned close: removes the close handler and calls comm_close
3923 // channels with active listeners will be closed when the listener handler dies.
3924 if (Comm::IsConnOpen(conn
)) {
3925 comm_remove_close_handler(conn
->fd
, closer
);
3926 conn
->close(); // we do not expect to be called back