2 * DEBUG: section 09 File Transfer Protocol (FTP)
3 * AUTHOR: Harvest Derived
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
35 #include "comm/ConnOpener.h"
36 #include "comm/TcpAcceptor.h"
37 #include "comm/Write.h"
38 #include "CommCalls.h"
39 #include "compat/strtoll.h"
40 #include "errorpage.h"
44 #include "html_quote.h"
45 #include "HttpHdrContRange.h"
46 #include "HttpHeader.h"
47 #include "HttpHeaderRange.h"
48 #include "HttpReply.h"
49 #include "HttpRequest.h"
56 #include "SquidConfig.h"
57 #include "SquidString.h"
58 #include "SquidTime.h"
59 #include "StatCounters.h"
63 #include "URLScheme.h"
67 #include "DelayPools.h"
68 #include "MemObject.h"
76 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
77 \ingroup ServerProtocolFTPAPI
80 /// \ingroup ServerProtocolFTPInternal
81 static const char *const crlf
= "\r\n";
83 #define CTRL_BUFLEN 1024
84 /// \ingroup ServerProtocolFTPInternal
85 static char cbuf
[CTRL_BUFLEN
];
87 /// \ingroup ServerProtocolFTPInternal
113 /// \ingroup ServerProtocolFTPInternal
117 bool pasv_supported
; ///< PASV command is allowed
118 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
120 bool pasv_failed
; // was FwdState::flags.ftp_pasv_failed
123 bool authenticated
; ///< authentication success
124 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
125 bool tried_auth_nopass
; ///< auth tried username with no password already.
129 bool skip_whitespace
;
131 bool http_header_sent
;
141 bool listformat_unknown
;
143 bool completed_forwarding
;
148 /// \ingroup ServerProtocolFTPInternal
149 typedef void (FTPSM
) (FtpStateData
*);
151 /// common code for FTP control and data channels
152 /// does not own the channel descriptor, which is managed by FtpStateData
158 /// called after the socket is opened, sets up close handler
159 void opened(const Comm::ConnectionPointer
&conn
, const AsyncCall::Pointer
&aCloser
);
161 /** Handles all operations needed to properly close the active channel FD.
162 * clearing the close handler, clearing the listen socket properly, and calling comm_close
166 void clear(); ///< just drops conn and close handler. does not close active connections.
168 Comm::ConnectionPointer conn
; ///< channel descriptor
170 /** A temporary handle to the connection being listened on.
171 * Closing this will also close the waiting Data channel acceptor.
172 * If a data connection has already been accepted but is still waiting in the event queue
173 * the callback will still happen and needs to be handled (usually dropped).
175 Comm::ConnectionPointer listenConn
;
177 AsyncCall::Pointer opener
; ///< Comm opener handler callback.
179 AsyncCall::Pointer closer
; ///< Comm close handler callback
182 /// \ingroup ServerProtocolFTPInternal
183 class FtpStateData
: public ServerStateData
187 void *operator new (size_t);
188 void operator delete (void *);
189 void *toCbdata() { return this; }
191 FtpStateData(FwdState
*, const Comm::ConnectionPointer
&conn
);
194 char password
[MAX_URL
];
209 int64_t restart_offset
;
217 MemBuf listing
; ///< FTP directory listing in HTML format.
219 // \todo: optimize ctrl and data structs member order, to minimize size
220 /// FTP control channel info; the channel is opened once per transaction
221 struct CtrlChannel
: public FtpChannel
{
231 /// FTP data channel info; the channel may be opened/closed a few times
232 struct DataChannel
: public FtpChannel
{
239 struct _ftp_flags flags
;
242 CBDATA_CLASS(FtpStateData
);
245 // these should all be private
246 virtual void start();
247 void loginParser(const char *, int escaped
);
249 void appendSuccessHeader();
250 void hackShortcut(FTPSM
* nextState
);
251 void failed(err_type
, int xerrno
);
252 void failedErrorMessage(err_type
, int xerrno
);
254 void scheduleReadControlReply(int);
255 void handleControlReply();
258 MemBuf
*htmlifyListEntry(const char *line
);
259 void completedListing(void);
261 void dataRead(const CommIoCbParams
&io
);
263 /// ignore timeout on CTRL channel. set read timeout on DATA channel.
264 void switchTimeoutToDataChannel();
265 /// create a data channel acceptor and start listening.
266 void listenForDataChannel(const Comm::ConnectionPointer
&conn
, const char *note
);
268 int checkAuth(const HttpHeader
* req_hdr
);
270 void buildTitleUrl();
271 void writeReplyBody(const char *, size_t len
);
272 void printfReplyBody(const char *fmt
, ...);
273 virtual const Comm::ConnectionPointer
& dataConnection() const;
274 virtual void maybeReadVirginBody();
275 virtual void closeServer();
276 virtual void completeForwarding();
277 virtual void abortTransaction(const char *reason
);
278 void processHeadResponse();
279 void processReplyBody();
280 void writeCommand(const char *buf
);
281 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
282 int64_t getCurrentOffset() const { return currentOffset
; }
284 static CNCB ftpPasvCallback
;
285 static PF ftpDataWrite
;
286 void ftpTimeout(const CommTimeoutCbParams
&io
);
287 void ctrlClosed(const CommCloseCbParams
&io
);
288 void dataClosed(const CommCloseCbParams
&io
);
289 void ftpReadControlReply(const CommIoCbParams
&io
);
290 void ftpWriteCommandCallback(const CommIoCbParams
&io
);
291 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
293 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, const char *realm
);
294 const char *ftpRealm(void);
295 void loginFailed(void);
296 static wordlist
*ftpParseControlReply(char *, size_t, int *, size_t *);
298 // sending of the request body to the server
299 virtual void sentRequestBody(const CommIoCbParams
&);
300 virtual void doneSendingRequestBody();
302 virtual void haveParsedReplyHeaders();
304 virtual bool doneWithServer() const;
305 virtual bool haveControlChannel(const char *caller_name
) const;
306 AsyncCall::Pointer
dataCloser(); /// creates a Comm close callback
307 AsyncCall::Pointer
dataOpener(); /// creates a Comm connect callback
310 // BodyConsumer for HTTP: consume request body.
311 virtual void handleRequestBodyProducerAborted();
314 CBDATA_CLASS_INIT(FtpStateData
);
317 FtpStateData::operator new (size_t)
319 CBDATA_INIT_TYPE(FtpStateData
);
320 FtpStateData
*result
= cbdataAlloc(FtpStateData
);
325 FtpStateData::operator delete (void *address
)
327 FtpStateData
*t
= static_cast<FtpStateData
*>(address
);
331 /// \ingroup ServerProtocolFTPInternal
341 /// \ingroup ServerProtocolFTPInternal
342 #define FTP_LOGIN_ESCAPED 1
344 /// \ingroup ServerProtocolFTPInternal
345 #define FTP_LOGIN_NOT_ESCAPED 0
348 * State machine functions
349 * send == state transition
350 * read == wait for response, and select next state transition
351 * other == Transition logic
353 static FTPSM ftpReadWelcome
;
354 static FTPSM ftpSendUser
;
355 static FTPSM ftpReadUser
;
356 static FTPSM ftpSendPass
;
357 static FTPSM ftpReadPass
;
358 static FTPSM ftpSendType
;
359 static FTPSM ftpReadType
;
360 static FTPSM ftpSendMdtm
;
361 static FTPSM ftpReadMdtm
;
362 static FTPSM ftpSendSize
;
363 static FTPSM ftpReadSize
;
364 static FTPSM ftpSendEPRT
;
365 static FTPSM ftpReadEPRT
;
366 static FTPSM ftpSendPORT
;
367 static FTPSM ftpReadPORT
;
368 static FTPSM ftpSendPassive
;
369 static FTPSM ftpReadEPSV
;
370 static FTPSM ftpReadPasv
;
371 static FTPSM ftpTraverseDirectory
;
372 static FTPSM ftpListDir
;
373 static FTPSM ftpGetFile
;
374 static FTPSM ftpSendCwd
;
375 static FTPSM ftpReadCwd
;
376 static FTPSM ftpRestOrList
;
377 static FTPSM ftpSendList
;
378 static FTPSM ftpSendNlst
;
379 static FTPSM ftpReadList
;
380 static FTPSM ftpSendRest
;
381 static FTPSM ftpReadRest
;
382 static FTPSM ftpSendRetr
;
383 static FTPSM ftpReadRetr
;
384 static FTPSM ftpReadTransferDone
;
385 static FTPSM ftpSendStor
;
386 static FTPSM ftpReadStor
;
387 static FTPSM ftpWriteTransferDone
;
388 static FTPSM ftpSendReply
;
389 static FTPSM ftpSendMkdir
;
390 static FTPSM ftpReadMkdir
;
391 static FTPSM ftpFail
;
392 static FTPSM ftpSendQuit
;
393 static FTPSM ftpReadQuit
;
395 /************************************************
396 ** Debugs Levels used here **
397 *************************************************
400 Protocol and Transmission failures.
401 2 FTP Protocol Chatter
406 ************************************************/
408 /************************************************
409 ** State Machine Description (excluding hacks) **
410 *************************************************
412 ---------------------------------------
416 Type TraverseDirectory / GetFile
417 TraverseDirectory Cwd / GetFile / ListDir
418 Cwd TraverseDirectory / Mkdir
424 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
426 Retr / Nlst / List DataRead* (on datachannel)
427 DataRead* ReadTransferDone
428 ReadTransferDone DataTransferDone
429 Stor DataWrite* (on datachannel)
430 DataWrite* RequestPutBody** (from client)
431 RequestPutBody** DataWrite* / WriteTransferDone
432 WriteTransferDone DataTransferDone
433 DataTransferDone Quit
435 ************************************************/
437 /// \ingroup ServerProtocolFTPInternal
438 FTPSM
*FTP_SM_FUNCS
[] = {
439 ftpReadWelcome
, /* BEGIN */
440 ftpReadUser
, /* SENT_USER */
441 ftpReadPass
, /* SENT_PASS */
442 ftpReadType
, /* SENT_TYPE */
443 ftpReadMdtm
, /* SENT_MDTM */
444 ftpReadSize
, /* SENT_SIZE */
445 ftpReadEPRT
, /* SENT_EPRT */
446 ftpReadPORT
, /* SENT_PORT */
447 ftpReadEPSV
, /* SENT_EPSV_ALL */
448 ftpReadEPSV
, /* SENT_EPSV_1 */
449 ftpReadEPSV
, /* SENT_EPSV_2 */
450 ftpReadPasv
, /* SENT_PASV */
451 ftpReadCwd
, /* SENT_CWD */
452 ftpReadList
, /* SENT_LIST */
453 ftpReadList
, /* SENT_NLST */
454 ftpReadRest
, /* SENT_REST */
455 ftpReadRetr
, /* SENT_RETR */
456 ftpReadStor
, /* SENT_STOR */
457 ftpReadQuit
, /* SENT_QUIT */
458 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
459 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
460 ftpReadMkdir
/* SENT_MKDIR */
463 /// handler called by Comm when FTP control channel is closed unexpectedly
465 FtpStateData::ctrlClosed(const CommCloseCbParams
&io
)
469 mustStop("FtpStateData::ctrlClosed");
472 /// handler called by Comm when FTP data channel is closed unexpectedly
474 FtpStateData::dataClosed(const CommCloseCbParams
&io
)
477 if (data
.listenConn
!= NULL
) {
478 data
.listenConn
->close();
479 data
.listenConn
= NULL
;
480 // NP clear() does the: data.fd = -1;
483 failed(ERR_FTP_FAILURE
, 0);
484 /* failed closes ctrl.conn and frees ftpState */
486 /* NP: failure recovery may be possible when its only a data.conn failure.
487 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
488 * Just need to watch out for wider Squid states like shutting down or reconfigure.
492 FtpStateData::FtpStateData(FwdState
*theFwdState
, const Comm::ConnectionPointer
&conn
) : AsyncJob("FtpStateData"), ServerStateData(theFwdState
)
494 const char *url
= entry
->url();
495 debugs(9, 3, HERE
<< "'" << url
<< "'" );
496 ++ statCounter
.server
.all
.requests
;
497 ++ statCounter
.server
.ftp
.requests
;
501 if (Config
.Ftp
.passive
&& !flags
.pasv_failed
)
502 flags
.pasv_supported
= 1;
504 flags
.rest_supported
= 1;
506 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
507 AsyncCall::Pointer closer
= JobCallback(9, 5, Dialer
, this, FtpStateData::ctrlClosed
);
508 ctrl
.opened(conn
, closer
);
510 if (request
->method
== Http::METHOD_PUT
)
514 FtpStateData::~FtpStateData()
516 debugs(9, 3, HERE
<< entry
->url() );
519 memFree(reply_hdr
, MEM_8K_BUF
);
523 if (data
.opener
!= NULL
) {
524 data
.opener
->cancel("FtpStateData destructed");
529 if (Comm::IsConnOpen(ctrl
.conn
)) {
530 debugs(9, DBG_IMPORTANT
, HERE
<< "Internal bug: FtpStateData left " <<
531 "open control channel " << ctrl
.conn
);
535 memFreeBuf(ctrl
.size
, ctrl
.buf
);
540 if (!data
.readBuf
->isNull())
541 data
.readBuf
->clean();
547 wordlistDestroy(&pathcomps
);
550 wordlistDestroy(&ctrl
.message
);
554 safe_free(ctrl
.last_reply
);
556 safe_free(ctrl
.last_command
);
558 safe_free(old_request
);
560 safe_free(old_reply
);
562 safe_free(old_filepath
);
572 safe_free(data
.host
);
574 fwd
= NULL
; // refcounted
578 * Parse a possible login username:password pair.
579 * Produces filled member variables user, password, password_url if anything found.
582 FtpStateData::loginParser(const char *login
, int escaped
)
584 const char *u
= NULL
; // end of the username sub-string
585 int len
; // length of the current sub-string to handle.
587 int total_len
= strlen(login
);
589 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
590 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
592 if ((u
= strchr(login
, ':'))) {
594 /* if there was a username part */
597 ++u
; // jump off the delimiter.
600 xstrncpy(user
, login
, len
+1);
601 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
603 rfc1738_unescape(user
);
604 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
607 /* if there was a password part */
608 len
= login
+ total_len
- u
;
612 xstrncpy(password
, u
, len
+1);
613 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
615 rfc1738_unescape(password
);
618 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
620 } else if (login
[0]) {
621 /* no password, just username */
622 if (total_len
> MAX_URL
)
623 total_len
= MAX_URL
-1;
624 xstrncpy(user
, login
, total_len
+1);
625 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
627 rfc1738_unescape(user
);
628 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
631 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
635 * Cancel the timeout on the Control socket and establish one
639 FtpStateData::switchTimeoutToDataChannel()
641 commUnsetConnTimeout(ctrl
.conn
);
643 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
644 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5, TimeoutDialer
, this, FtpStateData::ftpTimeout
);
645 commSetConnTimeout(data
.conn
, Config
.Timeout
.read
, timeoutCall
);
649 FtpStateData::listenForDataChannel(const Comm::ConnectionPointer
&conn
, const char *note
)
651 assert(!Comm::IsConnOpen(data
.conn
));
653 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> AcceptDialer
;
654 typedef AsyncCallT
<AcceptDialer
> AcceptCall
;
655 RefCount
<AcceptCall
> call
= static_cast<AcceptCall
*>(JobCallback(11, 5, AcceptDialer
, this, FtpStateData::ftpAcceptDataConnection
));
656 Subscription::Pointer sub
= new CallSubscription
<AcceptCall
>(call
);
658 /* open the conn if its not already open */
659 if (!Comm::IsConnOpen(conn
)) {
660 conn
->fd
= comm_open_listener(SOCK_STREAM
, IPPROTO_TCP
, conn
->local
, conn
->flags
, note
);
661 if (!Comm::IsConnOpen(conn
)) {
662 debugs(5, DBG_CRITICAL
, HERE
<< "comm_open_listener failed:" << conn
->local
<< " error: " << errno
);
665 debugs(9, 3, HERE
<< "Unconnected data socket created on " << conn
);
668 assert(Comm::IsConnOpen(conn
));
669 AsyncJob::Start(new Comm::TcpAcceptor(conn
, note
, sub
));
671 // Ensure we have a copy of the FD opened for listening and a close handler on it.
672 data
.opened(conn
, dataCloser());
673 switchTimeoutToDataChannel();
677 FtpStateData::ftpTimeout(const CommTimeoutCbParams
&io
)
679 debugs(9, 4, HERE
<< io
.conn
<< ": '" << entry
->url() << "'" );
681 if (abortOnBadEntry("entry went bad while waiting for a timeout"))
684 if (SENT_PASV
== state
) {
685 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
686 flags
.pasv_supported
= false;
687 debugs(9, DBG_IMPORTANT
, "ftpTimeout: timeout in SENT_PASV state" );
689 // cancel the data connection setup.
690 if (data
.opener
!= NULL
) {
691 data
.opener
->cancel("timeout");
697 failed(ERR_READ_TIMEOUT
, 0);
698 /* failed() closes ctrl.conn and frees ftpState */
701 /// \ingroup ServerProtocolFTPInternal
702 static const char *Month
[] = {
703 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
704 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
707 /// \ingroup ServerProtocolFTPInternal
709 is_month(const char *buf
)
713 for (i
= 0; i
< 12; ++i
)
714 if (!strcasecmp(buf
, Month
[i
]))
720 /// \ingroup ServerProtocolFTPInternal
722 ftpListPartsFree(ftpListParts
** parts
)
724 safe_free((*parts
)->date
);
725 safe_free((*parts
)->name
);
726 safe_free((*parts
)->showname
);
727 safe_free((*parts
)->link
);
731 /// \ingroup ServerProtocolFTPInternal
732 #define MAX_TOKENS 64
734 /// \ingroup ServerProtocolFTPInternal
735 static ftpListParts
*
736 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
738 ftpListParts
*p
= NULL
;
740 const char *ct
= NULL
;
741 char *tokens
[MAX_TOKENS
];
744 static char tbuf
[128];
746 static int scan_ftp_initialized
= 0;
747 static regex_t scan_ftp_integer
;
748 static regex_t scan_ftp_time
;
749 static regex_t scan_ftp_dostime
;
750 static regex_t scan_ftp_dosdate
;
752 if (!scan_ftp_initialized
) {
753 scan_ftp_initialized
= 1;
754 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
755 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
756 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
757 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
766 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
770 memset(tokens
, 0, sizeof(tokens
));
774 if (flags
.tried_nlst
) {
775 /* Machine readable format, one name per line */
781 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
)) {
782 tokens
[n_tokens
] = xstrdup(t
);
788 /* locate the Month field */
789 for (i
= 3; i
< n_tokens
- 2; ++i
) {
790 char *size
= tokens
[i
- 1];
791 char *month
= tokens
[i
];
792 char *day
= tokens
[i
+ 1];
793 char *year
= tokens
[i
+ 2];
795 if (!is_month(month
))
798 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
801 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
804 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
807 snprintf(tbuf
, 128, "%s %2s %5s",
810 if (!strstr(buf
, tbuf
))
811 snprintf(tbuf
, 128, "%s %2s %-5s",
814 char const *copyFrom
= NULL
;
816 if ((copyFrom
= strstr(buf
, tbuf
))) {
817 p
->type
= *tokens
[0];
818 p
->size
= strtoll(size
, NULL
, 10);
819 p
->date
= xstrdup(tbuf
);
821 if (flags
.skip_whitespace
) {
822 copyFrom
+= strlen(tbuf
);
824 while (strchr(w_space
, *copyFrom
))
827 /* XXX assumes a single space between date and filename
828 * suggested by: Nathan.Bailey@cc.monash.edu.au and
829 * Mike Battersby <mike@starbug.bofh.asn.au> */
830 copyFrom
+= strlen(tbuf
) + 1;
833 p
->name
= xstrdup(copyFrom
);
835 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
837 p
->link
= xstrdup(t
+ 4);
846 /* try it as a DOS listing, 04-05-70 09:33PM ... */
848 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
849 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
850 if (!strcasecmp(tokens
[2], "<dir>")) {
854 p
->size
= strtoll(tokens
[2], NULL
, 10);
857 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
858 p
->date
= xstrdup(tbuf
);
860 if (p
->type
== 'd') {
861 /* Directory.. name begins with first printable after <dir> */
862 ct
= strstr(buf
, tokens
[2]);
863 ct
+= strlen(tokens
[2]);
865 while (xisspace(*ct
))
871 /* A file. Name begins after size, with a space in between */
872 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
873 ct
= strstr(buf
, tbuf
);
876 ct
+= strlen(tokens
[2]) + 2;
880 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
884 /* Try EPLF format; carson@lehman.com */
891 int l
= strcspn(ct
, ",");
900 p
->name
= xstrndup(ct
+ 1, l
+ 1);
904 p
->size
= atoi(ct
+ 1);
908 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
911 break; /* not a valid integer */
913 p
->date
= xstrdup(ctime(&tm
));
915 *(strstr(p
->date
, "\n")) = '\0';
937 ct
= strstr(ct
, ",");
956 for (i
= 0; i
< n_tokens
; ++i
)
960 ftpListPartsFree(&p
); /* cleanup */
966 FtpStateData::htmlifyListEntry(const char *line
)
969 char href
[2048 + 40];
972 char chdir
[ 2048 + 40];
973 char view
[ 2048 + 40];
974 char download
[ 2048 + 40];
975 char link
[ 2048 + 40];
979 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
981 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
983 if (strlen(line
) > 1024) {
986 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
990 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
991 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
995 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
1000 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
1002 for (p
= line
; *p
&& xisspace(*p
); ++p
);
1003 if (*p
&& !xisspace(*p
))
1004 flags
.listformat_unknown
= 1;
1009 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
1010 ftpListPartsFree(&parts
);
1014 parts
->size
+= 1023;
1016 parts
->showname
= xstrdup(parts
->name
);
1018 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
1019 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
1021 xstrncpy(text
, parts
->showname
, 2048);
1023 switch (parts
->type
) {
1026 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1027 mimeGetIconURL("internal-dir"),
1029 strcat(href
, "/"); /* margin is allocated above */
1033 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1034 mimeGetIconURL("internal-link"),
1036 /* sometimes there is an 'l' flag, but no "->" link */
1039 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
1040 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
1041 *link2
!= '/' ? prefix
: "", link2
,
1042 html_quote(parts
->link
));
1049 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1050 mimeGetIconURL(parts
->name
),
1052 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
1053 "alt=\"[DIR]\"></a>",
1054 rfc1738_escape_part(parts
->name
),
1055 mimeGetIconURL("internal-dir"));
1061 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
1062 mimeGetIconURL(parts
->name
),
1064 snprintf(size
, 2048, " %6" PRId64
"k", parts
->size
);
1068 if (parts
->type
!= 'd') {
1069 if (mimeGetViewOption(parts
->name
)) {
1070 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1071 "alt=\"[VIEW]\"></a>",
1072 prefix
, href
, mimeGetIconURL("internal-view"));
1075 if (mimeGetDownloadOption(parts
->name
)) {
1076 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1077 "alt=\"[DOWNLOAD]\"></a>",
1078 prefix
, href
, mimeGetIconURL("internal-download"));
1082 /* construct the table row from parts. */
1083 html
= new MemBuf();
1085 html
->Printf("<tr class=\"entry\">"
1086 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1087 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1088 "<td class=\"date\">%s</td>"
1089 "<td class=\"size\">%s</td>"
1090 "<td class=\"actions\">%s%s%s%s</td>"
1093 prefix
, href
, html_quote(text
),
1096 chdir
, view
, download
, link
);
1098 ftpListPartsFree(&parts
);
1103 FtpStateData::parseListing()
1105 char *buf
= data
.readBuf
->content();
1106 char *sbuf
; /* NULL-terminated copy of termedBuf */
1113 size_t len
= data
.readBuf
->contentSize();
1116 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
1121 * We need a NULL-terminated buffer for scanning, ick
1123 sbuf
= (char *)xmalloc(len
+ 1);
1124 xstrncpy(sbuf
, buf
, len
+ 1);
1125 end
= sbuf
+ len
- 1;
1127 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1130 usable
= end
- sbuf
;
1132 debugs(9, 3, HERE
<< "usable = " << usable
<< " of " << len
<< " bytes.");
1135 if (buf
[0] == '\0' && len
== 1) {
1136 debugs(9, 3, HERE
<< "NIL ends data from " << entry
->url() << " transfer problem?");
1137 data
.readBuf
->consume(len
);
1139 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url());
1140 debugs(9, 3, HERE
<< "buffer remains (" << len
<< " bytes) '" << rfc1738_do_escape(buf
,0) << "'");
1146 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
1148 line
= (char *)memAllocate(MEM_4K_BUF
);
1151 s
+= strspn(s
, crlf
);
1153 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1154 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
1155 linelen
= strcspn(s
, crlf
) + 1;
1163 xstrncpy(line
, s
, linelen
);
1165 debugs(9, 7, HERE
<< "{" << line
<< "}");
1167 if (!strncmp(line
, "total", 5))
1170 t
= htmlifyListEntry(line
);
1173 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
1174 listing
.append(t
->content(), t
->contentSize());
1179 debugs(9, 7, HERE
<< "Done.");
1180 data
.readBuf
->consume(usable
);
1181 memFree(line
, MEM_4K_BUF
);
1185 const Comm::ConnectionPointer
&
1186 FtpStateData::dataConnection() const
1192 FtpStateData::dataComplete()
1196 /* Connection closed; transfer done. */
1198 /// Close data channel, if any, to conserve resources while we wait.
1201 /* expect the "transfer complete" message on the control socket */
1204 * Previously, this was the only place where we set the
1205 * 'buffered_ok' flag when calling scheduleReadControlReply().
1206 * It caused some problems if the FTP server returns an unexpected
1207 * status code after the data command. FtpStateData was being
1208 * deleted in the middle of dataRead().
1210 /* AYJ: 2011-01-13: Bug 2581.
1211 * 226 status is possibly waiting in the ctrl buffer.
1212 * The connection will hang if we DONT send buffered_ok.
1213 * This happens on all transfers which can be completly sent by the
1214 * server before the 150 started status message is read in by Squid.
1215 * ie all transfers of about one packet hang.
1217 scheduleReadControlReply(1);
1221 FtpStateData::maybeReadVirginBody()
1224 if (!Comm::IsConnOpen(data
.conn
) || fd_table
[data
.conn
->fd
].closing())
1227 if (data
.read_pending
)
1230 const int read_sz
= replyBodySpace(*data
.readBuf
, 0);
1232 debugs(11,9, HERE
<< "FTP may read up to " << read_sz
<< " bytes");
1234 if (read_sz
< 2) // see http.cc
1237 data
.read_pending
= true;
1239 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1240 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1241 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1242 commSetConnTimeout(data
.conn
, Config
.Timeout
.read
, timeoutCall
);
1244 debugs(9,5,HERE
<< "queueing read on FD " << data
.conn
->fd
);
1246 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1247 entry
->delayAwareRead(data
.conn
, data
.readBuf
->space(), read_sz
,
1248 JobCallback(9, 5, Dialer
, this, FtpStateData::dataRead
));
1252 FtpStateData::dataRead(const CommIoCbParams
&io
)
1257 data
.read_pending
= false;
1259 debugs(9, 3, HERE
<< "ftpDataRead: FD " << io
.fd
<< " Read " << io
.size
<< " bytes");
1262 kb_incr(&(statCounter
.server
.all
.kbytes_in
), io
.size
);
1263 kb_incr(&(statCounter
.server
.ftp
.kbytes_in
), io
.size
);
1266 if (io
.flag
== COMM_ERR_CLOSING
)
1269 assert(io
.fd
== data
.conn
->fd
);
1271 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1272 abortTransaction("entry aborted during dataRead");
1276 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1277 debugs(9,5,HERE
<< "appended " << io
.size
<< " bytes to readBuf");
1278 data
.readBuf
->appended(io
.size
);
1280 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
1281 delayId
.bytesIn(io
.size
);
1283 ++ IOStats
.Ftp
.reads
;
1285 for (j
= io
.size
- 1, bin
= 0; j
; ++bin
)
1288 ++ IOStats
.Ftp
.read_hist
[bin
];
1291 if (io
.flag
!= COMM_OK
) {
1292 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1293 "ftpDataRead: read error: " << xstrerr(io
.xerrno
));
1295 if (ignoreErrno(io
.xerrno
)) {
1296 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1297 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1298 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1299 commSetConnTimeout(io
.conn
, Config
.Timeout
.read
, timeoutCall
);
1301 maybeReadVirginBody();
1303 failed(ERR_READ_ERROR
, 0);
1304 /* failed closes ctrl.conn and frees ftpState */
1307 } else if (io
.size
== 0) {
1308 debugs(9,3, HERE
<< "Calling dataComplete() because io.size == 0");
1311 * Dangerous curves ahead. This call to dataComplete was
1312 * calling scheduleReadControlReply, handleControlReply,
1313 * and then ftpReadTransferDone. If ftpReadTransferDone
1314 * gets unexpected status code, it closes down the control
1315 * socket and our FtpStateData object gets destroyed. As
1316 * a workaround we no longer set the 'buffered_ok' flag in
1317 * the scheduleReadControlReply call.
1326 FtpStateData::processReplyBody()
1328 debugs(9, 3, HERE
<< "FtpStateData::processReplyBody starting.");
1330 if (request
->method
== Http::METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
1335 /* Directory listings are special. They write ther own headers via the error objects */
1336 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
1337 appendSuccessHeader();
1339 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1341 * probably was aborted because content length exceeds one
1342 * of the maximum size limits.
1344 abortTransaction("entry aborted after calling appendSuccessHeader()");
1350 if (adaptationAccessCheckPending
) {
1351 debugs(9,3, HERE
<< "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1358 if (!flags
.listing
) {
1363 maybeReadVirginBody();
1365 } else if (const int csize
= data
.readBuf
->contentSize()) {
1366 writeReplyBody(data
.readBuf
->content(), csize
);
1367 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1368 data
.readBuf
->consume(csize
);
1373 maybeReadVirginBody();
1377 * Locates the FTP user:password login.
1379 * Highest to lowest priority:
1380 * - Checks URL (ftp://user:pass@domain)
1381 * - Authorization: Basic header
1382 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1384 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1386 * TODO: we might be able to do something about locating username from other sources:
1387 * ie, external ACL user=* tag or ident lookup
1389 \retval 1 if we have everything needed to complete this request.
1390 \retval 0 if something is missing.
1393 FtpStateData::checkAuth(const HttpHeader
* req_hdr
)
1395 /* default username */
1396 xstrncpy(user
, "anonymous", MAX_URL
);
1398 #if HAVE_AUTH_MODULE_BASIC
1399 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1401 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1402 flags
.authenticated
= 1;
1403 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1405 /* we fail with authorization-required error later IFF the FTP server requests it */
1408 /* Test URL login syntax. Overrides any headers received. */
1409 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1411 /* name is missing. thats fatal. */
1413 fatal("FTP login parsing destroyed username info");
1415 /* name + password == success */
1419 /* Setup default FTP password settings */
1420 /* this has to be done last so that we can have a no-password case above. */
1422 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1423 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1424 flags
.tried_auth_anonymous
=1;
1426 } else if (!flags
.tried_auth_nopass
) {
1427 xstrncpy(password
, null_string
, MAX_URL
);
1428 flags
.tried_auth_nopass
=1;
1433 return 0; /* different username */
1436 static String str_type_eq
;
1438 FtpStateData::checkUrlpath()
1443 if (str_type_eq
.undefined()) //hack. String doesn't support global-static
1444 str_type_eq
="type=";
1446 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1447 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1448 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1449 request
->urlpath
.cut(t
);
1453 l
= request
->urlpath
.size();
1454 /* check for null path */
1459 flags
.need_base_href
= 1; /* Work around broken browsers */
1460 } else if (!request
->urlpath
.cmp("/%2f/")) {
1461 /* UNIX root directory */
1464 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1465 /* Directory URL, ending in / */
1471 flags
.dir_slash
= 1;
1476 FtpStateData::buildTitleUrl()
1478 title_url
= "ftp://";
1480 if (strcmp(user
, "anonymous")) {
1481 title_url
.append(user
);
1482 title_url
.append("@");
1485 title_url
.append(request
->GetHost());
1487 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1488 title_url
.append(":");
1489 title_url
.append(xitoa(request
->port
));
1492 title_url
.append (request
->urlpath
);
1494 base_href
= "ftp://";
1496 if (strcmp(user
, "anonymous") != 0) {
1497 base_href
.append(rfc1738_escape_part(user
));
1500 base_href
.append (":");
1501 base_href
.append(rfc1738_escape_part(password
));
1504 base_href
.append("@");
1507 base_href
.append(request
->GetHost());
1509 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1510 base_href
.append(":");
1511 base_href
.append(xitoa(request
->port
));
1514 base_href
.append(request
->urlpath
);
1515 base_href
.append("/");
1518 /// \ingroup ServerProtocolFTPAPI
1520 ftpStart(FwdState
* fwd
)
1522 AsyncJob::Start(new FtpStateData(fwd
, fwd
->serverConnection()));
1526 FtpStateData::start()
1528 if (!checkAuth(&request
->header
)) {
1529 /* create appropriate reply */
1530 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1531 entry
->replaceHttpReply(reply
);
1538 debugs(9, 5, HERE
<< "FD " << ctrl
.conn
->fd
<< " : host=" << request
->GetHost() <<
1539 ", path=" << request
->urlpath
<< ", user=" << user
<< ", passwd=" << password
);
1542 ctrl
.last_command
= xstrdup("Connect to server");
1543 ctrl
.buf
= (char *)memAllocBuf(4096, &ctrl
.size
);
1545 data
.readBuf
= new MemBuf
;
1546 data
.readBuf
->init(4096, SQUID_TCP_SO_RCVBUF
);
1547 scheduleReadControlReply(0);
1550 /* ====================================================================== */
1552 /// \ingroup ServerProtocolFTPInternal
1554 escapeIAC(const char *buf
)
1558 unsigned const char *p
;
1561 for (p
= (unsigned const char *)buf
, n
= 1; *p
; ++n
, ++p
)
1565 ret
= (char *)xmalloc(n
);
1567 for (p
= (unsigned const char *)buf
, r
=(unsigned char *)ret
; *p
; ++p
) {
1579 assert((r
- (unsigned char *)ret
) == n
);
1584 FtpStateData::writeCommand(const char *buf
)
1587 /* trace FTP protocol communications at level 2 */
1588 debugs(9, 2, "ftp<< " << buf
);
1590 if (Config
.Ftp
.telnet
)
1591 ebuf
= escapeIAC(buf
);
1593 ebuf
= xstrdup(buf
);
1595 safe_free(ctrl
.last_command
);
1597 safe_free(ctrl
.last_reply
);
1599 ctrl
.last_command
= ebuf
;
1601 if (!Comm::IsConnOpen(ctrl
.conn
)) {
1602 debugs(9, 2, HERE
<< "cannot send to closing ctrl " << ctrl
.conn
);
1603 // TODO: assert(ctrl.closer != NULL);
1607 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1608 AsyncCall::Pointer call
= JobCallback(9, 5, Dialer
, this, FtpStateData::ftpWriteCommandCallback
);
1609 Comm::Write(ctrl
.conn
, ctrl
.last_command
, strlen(ctrl
.last_command
), call
, NULL
);
1611 scheduleReadControlReply(0);
1615 FtpStateData::ftpWriteCommandCallback(const CommIoCbParams
&io
)
1618 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io
.size
<< " bytes");
1621 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
1622 kb_incr(&(statCounter
.server
.all
.kbytes_out
), io
.size
);
1623 kb_incr(&(statCounter
.server
.ftp
.kbytes_out
), io
.size
);
1626 if (io
.flag
== COMM_ERR_CLOSING
)
1630 debugs(9, DBG_IMPORTANT
, "ftpWriteCommandCallback: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
1631 failed(ERR_WRITE_ERROR
, io
.xerrno
);
1632 /* failed closes ctrl.conn and frees ftpState */
1638 FtpStateData::ftpParseControlReply(char *buf
, size_t len
, int *codep
, size_t *used
)
1645 wordlist
*head
= NULL
;
1647 wordlist
**tail
= &head
;
1653 * We need a NULL-terminated buffer for scanning, ick
1655 sbuf
= (char *)xmalloc(len
+ 1);
1656 xstrncpy(sbuf
, buf
, len
+ 1);
1657 end
= sbuf
+ len
- 1;
1659 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1662 usable
= end
- sbuf
;
1664 debugs(9, 3, HERE
<< "usable = " << usable
);
1667 debugs(9, 3, HERE
<< "didn't find end of line");
1672 debugs(9, 3, HERE
<< len
<< " bytes to play with");
1675 s
+= strspn(s
, crlf
);
1677 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1681 debugs(9, 5, HERE
<< "s = {" << s
<< "}");
1683 linelen
= strcspn(s
, crlf
) + 1;
1689 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1697 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1700 list
= new wordlist();
1702 list
->key
= (char *)xmalloc(linelen
- offset
);
1704 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1706 /* trace the FTP communication chat at level 2 */
1707 debugs(9, 2, "ftp>> " << code
<< " " << list
->key
);
1714 *used
= (size_t) (s
- sbuf
);
1718 wordlistDestroy(&head
);
1728 * Looks like there are no longer anymore callers that set
1729 * buffered_ok=1. Perhaps it can be removed at some point.
1732 FtpStateData::scheduleReadControlReply(int buffered_ok
)
1734 debugs(9, 3, HERE
<< ctrl
.conn
);
1736 if (buffered_ok
&& ctrl
.offset
> 0) {
1737 /* We've already read some reply data */
1738 handleControlReply();
1741 * Cancel the timeout on the Data socket (if any) and
1742 * establish one on the control socket.
1744 if (Comm::IsConnOpen(data
.conn
)) {
1745 commUnsetConnTimeout(data
.conn
);
1748 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1749 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5, TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1750 commSetConnTimeout(ctrl
.conn
, Config
.Timeout
.read
, timeoutCall
);
1752 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1753 AsyncCall::Pointer reader
= JobCallback(9, 5, Dialer
, this, FtpStateData::ftpReadControlReply
);
1754 comm_read(ctrl
.conn
, ctrl
.buf
+ ctrl
.offset
, ctrl
.size
- ctrl
.offset
, reader
);
1758 void FtpStateData::ftpReadControlReply(const CommIoCbParams
&io
)
1760 debugs(9, 3, "ftpReadControlReply: FD " << io
.fd
<< ", Read " << io
.size
<< " bytes");
1763 kb_incr(&(statCounter
.server
.all
.kbytes_in
), io
.size
);
1764 kb_incr(&(statCounter
.server
.ftp
.kbytes_in
), io
.size
);
1767 if (io
.flag
== COMM_ERR_CLOSING
)
1770 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1771 abortTransaction("entry aborted during control reply read");
1775 assert(ctrl
.offset
< ctrl
.size
);
1777 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1778 fd_bytes(io
.fd
, io
.size
, FD_READ
);
1781 if (io
.flag
!= COMM_OK
) {
1782 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1783 "ftpReadControlReply: read error: " << xstrerr(io
.xerrno
));
1785 if (ignoreErrno(io
.xerrno
)) {
1786 scheduleReadControlReply(0);
1788 failed(ERR_READ_ERROR
, io
.xerrno
);
1789 /* failed closes ctrl.conn and frees ftpState */
1795 if (entry
->store_status
== STORE_PENDING
) {
1796 failed(ERR_FTP_FAILURE
, 0);
1797 /* failed closes ctrl.conn and frees ftpState */
1801 /* XXX this may end up having to be serverComplete() .. */
1802 abortTransaction("zero control reply read");
1806 unsigned int len
=io
.size
+ ctrl
.offset
;
1808 assert(len
<= ctrl
.size
);
1809 handleControlReply();
1813 FtpStateData::handleControlReply()
1816 size_t bytes_used
= 0;
1817 wordlistDestroy(&ctrl
.message
);
1818 ctrl
.message
= ftpParseControlReply(ctrl
.buf
,
1819 ctrl
.offset
, &ctrl
.replycode
, &bytes_used
);
1821 if (ctrl
.message
== NULL
) {
1822 /* didn't get complete reply yet */
1824 if (ctrl
.offset
== ctrl
.size
) {
1825 ctrl
.buf
= (char *)memReallocBuf(ctrl
.buf
, ctrl
.size
<< 1, &ctrl
.size
);
1828 scheduleReadControlReply(0);
1830 } else if (ctrl
.offset
== bytes_used
) {
1831 /* used it all up */
1834 /* Got some data past the complete reply */
1835 assert(bytes_used
< ctrl
.offset
);
1836 ctrl
.offset
-= bytes_used
;
1837 memmove(ctrl
.buf
, ctrl
.buf
+ bytes_used
, ctrl
.offset
);
1840 /* Move the last line of the reply message to ctrl.last_reply */
1841 for (W
= &ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1842 safe_free(ctrl
.last_reply
);
1844 ctrl
.last_reply
= xstrdup((*W
)->key
);
1848 /* Copy the rest of the message to cwd_message to be printed in
1852 for (wordlist
*w
= ctrl
.message
; w
; w
= w
->next
) {
1853 cwd_message
.append('\n');
1854 cwd_message
.append(w
->key
);
1858 debugs(9, 3, HERE
<< "state=" << state
<< ", code=" << ctrl
.replycode
);
1860 FTP_SM_FUNCS
[state
] (this);
1863 /* ====================================================================== */
1865 /// \ingroup ServerProtocolFTPInternal
1867 ftpReadWelcome(FtpStateData
* ftpState
)
1869 int code
= ftpState
->ctrl
.replycode
;
1872 if (ftpState
->flags
.pasv_only
)
1873 ++ ftpState
->login_att
;
1876 if (ftpState
->ctrl
.message
) {
1877 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1878 ftpState
->flags
.skip_whitespace
= 1;
1881 ftpSendUser(ftpState
);
1882 } else if (code
== 120) {
1883 if (NULL
!= ftpState
->ctrl
.message
)
1884 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1893 * Translate FTP login failure into HTTP error
1894 * this is an attmpt to get the 407 message to show up outside Squid.
1895 * its NOT a general failure. But a correct FTP response type.
1898 FtpStateData::loginFailed()
1900 ErrorState
*err
= NULL
;
1901 const char *command
, *reply
;
1903 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1904 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1905 // 421/426 - Service Overload - retry permitted.
1906 err
= new ErrorState(ERR_FTP_UNAVAILABLE
, Http::scServiceUnavailable
, fwd
->request
);
1907 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1908 // 43x - Invalid or Credential Error - retry challenge required.
1909 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1910 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1911 // 53x - Credentials Missing - retry challenge required
1912 if (password_url
) // but they were in the URI! major fail.
1913 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scForbidden
, fwd
->request
);
1915 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1919 // any other problems are general falures.
1925 err
->ftp
.server_msg
= ctrl
.message
;
1927 ctrl
.message
= NULL
;
1930 command
= old_request
;
1932 command
= ctrl
.last_command
;
1934 if (command
&& strncmp(command
, "PASS", 4) == 0)
1935 command
= "PASS <yourpassword>";
1940 reply
= ctrl
.last_reply
;
1943 err
->ftp
.request
= xstrdup(command
);
1946 err
->ftp
.reply
= xstrdup(reply
);
1948 HttpReply
*newrep
= err
->BuildHttpReply();
1951 #if HAVE_AUTH_MODULE_BASIC
1952 /* add Authenticate header */
1953 newrep
->header
.putAuth("Basic", ftpRealm());
1956 // add it to the store entry for response....
1957 entry
->replaceHttpReply(newrep
);
1962 FtpStateData::ftpRealm()
1964 static char realm
[8192];
1966 /* This request is not fully authenticated */
1968 snprintf(realm
, 8192, "FTP %s unknown", user
);
1969 } else if (request
->port
== 21) {
1970 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1972 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1977 /// \ingroup ServerProtocolFTPInternal
1979 ftpSendUser(FtpStateData
* ftpState
)
1981 /* check the server control channel is still available */
1982 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1985 if (ftpState
->proxy_host
!= NULL
)
1986 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s@%s\r\n",
1988 ftpState
->request
->GetHost());
1990 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s\r\n", ftpState
->user
);
1992 ftpState
->writeCommand(cbuf
);
1994 ftpState
->state
= SENT_USER
;
1997 /// \ingroup ServerProtocolFTPInternal
1999 ftpReadUser(FtpStateData
* ftpState
)
2001 int code
= ftpState
->ctrl
.replycode
;
2005 ftpReadPass(ftpState
);
2006 } else if (code
== 331) {
2007 ftpSendPass(ftpState
);
2009 ftpState
->loginFailed();
2013 /// \ingroup ServerProtocolFTPInternal
2015 ftpSendPass(FtpStateData
* ftpState
)
2017 /* check the server control channel is still available */
2018 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
2021 snprintf(cbuf
, CTRL_BUFLEN
, "PASS %s\r\n", ftpState
->password
);
2022 ftpState
->writeCommand(cbuf
);
2023 ftpState
->state
= SENT_PASS
;
2026 /// \ingroup ServerProtocolFTPInternal
2028 ftpReadPass(FtpStateData
* ftpState
)
2030 int code
= ftpState
->ctrl
.replycode
;
2031 debugs(9, 3, HERE
<< "code=" << code
);
2034 ftpSendType(ftpState
);
2036 ftpState
->loginFailed();
2040 /// \ingroup ServerProtocolFTPInternal
2042 ftpSendType(FtpStateData
* ftpState
)
2045 const char *filename
;
2048 /* check the server control channel is still available */
2049 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
2053 * Ref section 3.2.2 of RFC 1738
2055 mode
= ftpState
->typecode
;
2070 if (ftpState
->flags
.isdir
) {
2073 t
= ftpState
->request
->urlpath
.rpos('/');
2074 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
2075 mode
= mimeGetTransferMode(filename
);
2082 ftpState
->flags
.binary
= 1;
2084 ftpState
->flags
.binary
= 0;
2086 snprintf(cbuf
, CTRL_BUFLEN
, "TYPE %c\r\n", mode
);
2088 ftpState
->writeCommand(cbuf
);
2090 ftpState
->state
= SENT_TYPE
;
2093 /// \ingroup ServerProtocolFTPInternal
2095 ftpReadType(FtpStateData
* ftpState
)
2097 int code
= ftpState
->ctrl
.replycode
;
2100 debugs(9, 3, HERE
<< "code=" << code
);
2103 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2110 p
+= strcspn(p
, "/");
2117 rfc1738_unescape(d
);
2120 wordlistAdd(&ftpState
->pathcomps
, d
);
2125 if (ftpState
->pathcomps
)
2126 ftpTraverseDirectory(ftpState
);
2128 ftpListDir(ftpState
);
2134 /// \ingroup ServerProtocolFTPInternal
2136 ftpTraverseDirectory(FtpStateData
* ftpState
)
2139 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
2141 safe_free(ftpState
->dirpath
);
2142 ftpState
->dirpath
= ftpState
->filepath
;
2143 ftpState
->filepath
= NULL
;
2147 if (ftpState
->pathcomps
== NULL
) {
2148 debugs(9, 3, HERE
<< "the final component was a directory");
2149 ftpListDir(ftpState
);
2153 /* Go to next path component */
2154 w
= ftpState
->pathcomps
;
2156 ftpState
->filepath
= w
->key
;
2158 ftpState
->pathcomps
= w
->next
;
2162 /* Check if we are to CWD or RETR */
2163 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
2164 ftpSendCwd(ftpState
);
2166 debugs(9, 3, HERE
<< "final component is probably a file");
2167 ftpGetFile(ftpState
);
2172 /// \ingroup ServerProtocolFTPInternal
2174 ftpSendCwd(FtpStateData
* ftpState
)
2178 /* check the server control channel is still available */
2179 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
2184 path
= ftpState
->filepath
;
2186 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
2187 ftpState
->flags
.no_dotdot
= 1;
2189 ftpState
->flags
.no_dotdot
= 0;
2192 snprintf(cbuf
, CTRL_BUFLEN
, "CWD %s\r\n", path
);
2194 ftpState
->writeCommand(cbuf
);
2196 ftpState
->state
= SENT_CWD
;
2199 /// \ingroup ServerProtocolFTPInternal
2201 ftpReadCwd(FtpStateData
* ftpState
)
2203 int code
= ftpState
->ctrl
.replycode
;
2206 if (code
>= 200 && code
< 300) {
2210 /* Reset cwd_message to only include the last message */
2211 ftpState
->cwd_message
.reset("");
2212 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
2213 ftpState
->cwd_message
.append(' ');
2214 ftpState
->cwd_message
.append(w
->key
);
2216 ftpState
->ctrl
.message
= NULL
;
2218 /* Continue to traverse the path */
2219 ftpTraverseDirectory(ftpState
);
2223 if (!ftpState
->flags
.put
)
2226 ftpSendMkdir(ftpState
);
2230 /// \ingroup ServerProtocolFTPInternal
2232 ftpSendMkdir(FtpStateData
* ftpState
)
2236 /* check the server control channel is still available */
2237 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
2240 path
= ftpState
->filepath
;
2241 debugs(9, 3, HERE
<< "with path=" << path
);
2242 snprintf(cbuf
, CTRL_BUFLEN
, "MKD %s\r\n", path
);
2243 ftpState
->writeCommand(cbuf
);
2244 ftpState
->state
= SENT_MKDIR
;
2247 /// \ingroup ServerProtocolFTPInternal
2249 ftpReadMkdir(FtpStateData
* ftpState
)
2251 char *path
= ftpState
->filepath
;
2252 int code
= ftpState
->ctrl
.replycode
;
2254 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
2256 if (code
== 257) { /* success */
2257 ftpSendCwd(ftpState
);
2258 } else if (code
== 550) { /* dir exists */
2260 if (ftpState
->flags
.put_mkdir
) {
2261 ftpState
->flags
.put_mkdir
= 1;
2262 ftpSendCwd(ftpState
);
2264 ftpSendReply(ftpState
);
2266 ftpSendReply(ftpState
);
2269 /// \ingroup ServerProtocolFTPInternal
2271 ftpGetFile(FtpStateData
* ftpState
)
2273 assert(*ftpState
->filepath
!= '\0');
2274 ftpState
->flags
.isdir
= 0;
2275 ftpSendMdtm(ftpState
);
2278 /// \ingroup ServerProtocolFTPInternal
2280 ftpListDir(FtpStateData
* ftpState
)
2282 if (ftpState
->flags
.dir_slash
) {
2283 debugs(9, 3, HERE
<< "Directory path did not end in /");
2284 ftpState
->title_url
.append("/");
2285 ftpState
->flags
.isdir
= 1;
2288 ftpSendPassive(ftpState
);
2291 /// \ingroup ServerProtocolFTPInternal
2293 ftpSendMdtm(FtpStateData
* ftpState
)
2295 /* check the server control channel is still available */
2296 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
2299 assert(*ftpState
->filepath
!= '\0');
2300 snprintf(cbuf
, CTRL_BUFLEN
, "MDTM %s\r\n", ftpState
->filepath
);
2301 ftpState
->writeCommand(cbuf
);
2302 ftpState
->state
= SENT_MDTM
;
2305 /// \ingroup ServerProtocolFTPInternal
2307 ftpReadMdtm(FtpStateData
* ftpState
)
2309 int code
= ftpState
->ctrl
.replycode
;
2313 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
2315 } else if (code
< 0) {
2320 ftpSendSize(ftpState
);
2323 /// \ingroup ServerProtocolFTPInternal
2325 ftpSendSize(FtpStateData
* ftpState
)
2327 /* check the server control channel is still available */
2328 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
2331 /* Only send SIZE for binary transfers. The returned size
2332 * is useless on ASCII transfers */
2334 if (ftpState
->flags
.binary
) {
2335 assert(ftpState
->filepath
!= NULL
);
2336 assert(*ftpState
->filepath
!= '\0');
2337 snprintf(cbuf
, CTRL_BUFLEN
, "SIZE %s\r\n", ftpState
->filepath
);
2338 ftpState
->writeCommand(cbuf
);
2339 ftpState
->state
= SENT_SIZE
;
2341 /* Skip to next state no non-binary transfers */
2342 ftpSendPassive(ftpState
);
2345 /// \ingroup ServerProtocolFTPInternal
2347 ftpReadSize(FtpStateData
* ftpState
)
2349 int code
= ftpState
->ctrl
.replycode
;
2354 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
2356 if (ftpState
->theSize
== 0) {
2357 debugs(9, 2, "SIZE reported " <<
2358 ftpState
->ctrl
.last_reply
<< " on " <<
2359 ftpState
->title_url
);
2360 ftpState
->theSize
= -1;
2362 } else if (code
< 0) {
2367 ftpSendPassive(ftpState
);
2371 \ingroup ServerProtocolFTPInternal
2374 ftpReadEPSV(FtpStateData
* ftpState
)
2376 int code
= ftpState
->ctrl
.replycode
;
2377 Ip::Address ipa_remote
;
2381 if (code
!= 229 && code
!= 522) {
2383 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2384 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2385 * Its okay to re-send EPSV 1/2 but nothing else. */
2386 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << ftpState
->ctrl
.conn
->remote
<< ". Wrong accept code for EPSV");
2388 debugs(9, 2, "EPSV not supported by remote end");
2389 ftpState
->state
= SENT_EPSV_1
; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2391 ftpSendPassive(ftpState
);
2396 /* server response with list of supported methods */
2397 /* 522 Network protocol not supported, use (1) */
2398 /* 522 Network protocol not supported, use (1,2) */
2399 /* 522 Network protocol not supported, use (2) */
2400 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
2401 * which means close data + control without self-destructing and re-open from scratch. */
2402 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2403 buf
= ftpState
->ctrl
.last_reply
;
2404 while (buf
!= NULL
&& *buf
!= '\0' && *buf
!= '\n' && *buf
!= '(')
2406 if (buf
!= NULL
&& *buf
== '\n')
2409 if (buf
== NULL
|| *buf
== '\0') {
2410 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2411 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << ftpState
->ctrl
.conn
->remote
<< ". 522 error missing protocol negotiation hints");
2412 ftpSendPassive(ftpState
);
2413 } else if (strcmp(buf
, "(1)") == 0) {
2414 ftpState
->state
= SENT_EPSV_2
; /* simulate having sent and failed EPSV 2 */
2415 ftpSendPassive(ftpState
);
2416 } else if (strcmp(buf
, "(2)") == 0) {
2417 if (Ip::EnableIpv6
) {
2418 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2419 if (ftpState
->state
== SENT_EPSV_2
) {
2420 ftpSendEPRT(ftpState
);
2422 /* or try the next Passive mode down the chain. */
2423 ftpSendPassive(ftpState
);
2426 /* Server only accept EPSV in IPv6 traffic. */
2427 ftpState
->state
= SENT_EPSV_1
; /* simulate having sent and failed EPSV 1 */
2428 ftpSendPassive(ftpState
);
2431 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2432 debugs(9, DBG_IMPORTANT
, "WARNING: Server at " << ftpState
->ctrl
.conn
->remote
<< " sent unknown protocol negotiation hint: " << buf
);
2433 ftpSendPassive(ftpState
);
2438 /* 229 Entering Extended Passive Mode (|||port|) */
2439 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2440 debugs(9, 5, "scanning: " << ftpState
->ctrl
.last_reply
);
2442 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "(");
2444 char h1
, h2
, h3
, h4
;
2445 unsigned short port
;
2446 int n
= sscanf(buf
, "(%c%c%c%hu%c)", &h1
, &h2
, &h3
, &port
, &h4
);
2448 if (n
< 4 || h1
!= h2
|| h1
!= h3
|| h1
!= h4
) {
2449 debugs(9, DBG_IMPORTANT
, "Invalid EPSV reply from " <<
2450 ftpState
->ctrl
.conn
->remote
<< ": " <<
2451 ftpState
->ctrl
.last_reply
);
2453 ftpSendPassive(ftpState
);
2458 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2459 ftpState
->ctrl
.conn
->remote
<< ": " <<
2460 ftpState
->ctrl
.last_reply
);
2462 ftpSendPassive(ftpState
);
2466 if (Config
.Ftp
.sanitycheck
) {
2468 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2469 ftpState
->ctrl
.conn
->remote
<< ": " <<
2470 ftpState
->ctrl
.last_reply
);
2472 ftpSendPassive(ftpState
);
2477 ftpState
->data
.port
= port
;
2479 safe_free(ftpState
->data
.host
);
2480 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.conn
->fd
].ipaddr
);
2482 safe_free(ftpState
->ctrl
.last_command
);
2484 safe_free(ftpState
->ctrl
.last_reply
);
2486 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2488 // Generate a new data channel descriptor to be opened.
2489 Comm::ConnectionPointer conn
= new Comm::Connection
;
2490 conn
->local
= ftpState
->ctrl
.conn
->local
;
2491 conn
->local
.SetPort(0);
2492 conn
->remote
= ftpState
->ctrl
.conn
->remote
;
2493 conn
->remote
.SetPort(port
);
2495 debugs(9, 3, HERE
<< "connecting to " << conn
->remote
);
2497 ftpState
->data
.opener
= commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback
, ftpState
));
2498 Comm::ConnOpener
*cs
= new Comm::ConnOpener(conn
, ftpState
->data
.opener
, Config
.Timeout
.connect
);
2499 cs
->setHost(ftpState
->data
.host
);
2500 AsyncJob::Start(cs
);
2503 /** \ingroup ServerProtocolFTPInternal
2505 * Send Passive connection request.
2506 * Default method is to use modern EPSV request.
2507 * The failover mechanism should check for previous state and re-call with alternates on failure.
2510 ftpSendPassive(FtpStateData
* ftpState
)
2512 /** Checks the server control channel is still available before running. */
2513 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
2519 * Checks for EPSV ALL special conditions:
2520 * If enabled to be sent, squid MUST NOT request any other connect methods.
2521 * If 'ALL' is sent and fails the entire FTP Session fails.
2522 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
2523 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
&& ftpState
->state
== SENT_EPSV_1
) {
2524 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2530 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
2531 if (ftpState
->request
->method
== Http::METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
2532 ftpState
->processHeadResponse(); // may call serverComplete
2536 /// Closes any old FTP-Data connection which may exist. */
2537 ftpState
->data
.close();
2540 * Checks for previous EPSV/PASV failures on this server/session.
2541 * Diverts to EPRT immediately if they are not working. */
2542 if (!ftpState
->flags
.pasv_supported
) {
2543 ftpSendEPRT(ftpState
);
2548 * Send EPSV (ALL,2,1) or PASV on the control channel.
2550 * - EPSV ALL is used if enabled.
2551 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2552 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
2553 * - PASV is used if EPSV 1 fails.
2555 switch (ftpState
->state
) {
2556 case SENT_EPSV_ALL
: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2557 ftpState
->flags
.epsv_all_sent
= true;
2558 if (ftpState
->ctrl
.conn
->local
.IsIPv6()) {
2559 debugs(9, 5, HERE
<< "FTP Channel is IPv6 (" << ftpState
->ctrl
.conn
->remote
<< ") attempting EPSV 2 after EPSV ALL has failed.");
2560 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 2\r\n");
2561 ftpState
->state
= SENT_EPSV_2
;
2564 // else fall through to skip EPSV 2
2566 case SENT_EPSV_2
: /* EPSV IPv6 failed. Try EPSV IPv4 */
2567 if (ftpState
->ctrl
.conn
->local
.IsIPv4()) {
2568 debugs(9, 5, HERE
<< "FTP Channel is IPv4 (" << ftpState
->ctrl
.conn
->remote
<< ") attempting EPSV 1 after EPSV ALL has failed.");
2569 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 1\r\n");
2570 ftpState
->state
= SENT_EPSV_1
;
2572 } else if (ftpState
->flags
.epsv_all_sent
) {
2573 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2577 // else fall through to skip EPSV 1
2579 case SENT_EPSV_1
: /* EPSV options exhausted. Try PASV now. */
2580 debugs(9, 5, HERE
<< "FTP Channel (" << ftpState
->ctrl
.conn
->remote
<< ") rejects EPSV connection attempts. Trying PASV instead.");
2581 snprintf(cbuf
, CTRL_BUFLEN
, "PASV\r\n");
2582 ftpState
->state
= SENT_PASV
;
2586 if (!Config
.Ftp
.epsv
) {
2587 debugs(9, 5, HERE
<< "EPSV support manually disabled. Sending PASV for FTP Channel (" << ftpState
->ctrl
.conn
->remote
<<")");
2588 snprintf(cbuf
, CTRL_BUFLEN
, "PASV\r\n");
2589 ftpState
->state
= SENT_PASV
;
2590 } else if (Config
.Ftp
.epsv_all
) {
2591 debugs(9, 5, HERE
<< "EPSV ALL manually enabled. Attempting with FTP Channel (" << ftpState
->ctrl
.conn
->remote
<<")");
2592 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV ALL\r\n");
2593 ftpState
->state
= SENT_EPSV_ALL
;
2594 /* block other non-EPSV connections being attempted */
2595 ftpState
->flags
.epsv_all_sent
= true;
2597 if (ftpState
->ctrl
.conn
->local
.IsIPv6()) {
2598 debugs(9, 5, HERE
<< "FTP Channel (" << ftpState
->ctrl
.conn
->remote
<< "). Sending default EPSV 2");
2599 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 2\r\n");
2600 ftpState
->state
= SENT_EPSV_2
;
2602 if (ftpState
->ctrl
.conn
->local
.IsIPv4()) {
2603 debugs(9, 5, HERE
<< "Channel (" << ftpState
->ctrl
.conn
->remote
<<"). Sending default EPSV 1");
2604 snprintf(cbuf
, CTRL_BUFLEN
, "EPSV 1\r\n");
2605 ftpState
->state
= SENT_EPSV_1
;
2611 ftpState
->writeCommand(cbuf
);
2614 * ugly hack for ftp servers like ftp.netscape.com that sometimes
2615 * dont acknowledge PASV commands. Use connect timeout to be faster then read timeout (minutes).
2617 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2618 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
2619 TimeoutDialer
, ftpState
, FtpStateData::ftpTimeout
);
2620 commSetConnTimeout(ftpState
->ctrl
.conn
, Config
.Timeout
.connect
, timeoutCall
);
2624 FtpStateData::processHeadResponse()
2626 debugs(9, 5, HERE
<< "handling HEAD response");
2628 appendSuccessHeader();
2631 * On rare occasions I'm seeing the entry get aborted after
2632 * ftpReadControlReply() and before here, probably when
2633 * trying to write to the client.
2635 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2636 abortTransaction("entry aborted while processing HEAD");
2641 if (adaptationAccessCheckPending
) {
2642 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
2647 // processReplyBody calls serverComplete() since there is no body
2651 /// \ingroup ServerProtocolFTPInternal
2653 ftpReadPasv(FtpStateData
* ftpState
)
2655 int code
= ftpState
->ctrl
.replycode
;
2659 unsigned short port
;
2660 Ip::Address ipa_remote
;
2662 LOCAL_ARRAY(char, ipaddr
, 1024);
2666 debugs(9, 2, "PASV not supported by remote end");
2667 ftpSendEPRT(ftpState
);
2671 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2672 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2673 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2675 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "0123456789");
2677 n
= sscanf(buf
, "%d,%d,%d,%d,%d,%d", &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
2679 if (n
!= 6 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
2680 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2681 ftpState
->ctrl
.conn
->remote
<< ": " <<
2682 ftpState
->ctrl
.last_reply
);
2684 ftpSendEPRT(ftpState
);
2688 snprintf(ipaddr
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
2690 ipa_remote
= ipaddr
;
2692 if ( ipa_remote
.IsAnyAddr() ) {
2693 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2694 ftpState
->ctrl
.conn
->remote
<< ": " <<
2695 ftpState
->ctrl
.last_reply
);
2697 ftpSendEPRT(ftpState
);
2701 port
= ((p1
<< 8) + p2
);
2704 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2705 ftpState
->ctrl
.conn
->remote
<< ": " <<
2706 ftpState
->ctrl
.last_reply
);
2708 ftpSendEPRT(ftpState
);
2712 if (Config
.Ftp
.sanitycheck
) {
2714 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2715 ftpState
->ctrl
.conn
->remote
<< ": " <<
2716 ftpState
->ctrl
.last_reply
);
2718 ftpSendEPRT(ftpState
);
2723 ftpState
->data
.port
= port
;
2725 safe_free(ftpState
->data
.host
);
2726 if (Config
.Ftp
.sanitycheck
)
2727 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.conn
->fd
].ipaddr
);
2729 ftpState
->data
.host
= xstrdup(ipaddr
);
2731 safe_free(ftpState
->ctrl
.last_command
);
2733 safe_free(ftpState
->ctrl
.last_reply
);
2735 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2737 Comm::ConnectionPointer conn
= new Comm::Connection
;
2738 conn
->local
= ftpState
->ctrl
.conn
->local
;
2739 conn
->local
.SetPort(0);
2740 conn
->remote
= ipaddr
;
2741 conn
->remote
.SetPort(port
);
2743 debugs(9, 3, HERE
<< "connecting to " << conn
->remote
);
2745 ftpState
->data
.opener
= commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback
, ftpState
));
2746 Comm::ConnOpener
*cs
= new Comm::ConnOpener(conn
, ftpState
->data
.opener
, Config
.Timeout
.connect
);
2747 cs
->setHost(ftpState
->data
.host
);
2748 AsyncJob::Start(cs
);
2752 FtpStateData::ftpPasvCallback(const Comm::ConnectionPointer
&conn
, comm_err_t status
, int xerrno
, void *data
)
2754 FtpStateData
*ftpState
= (FtpStateData
*)data
;
2756 ftpState
->data
.opener
= NULL
;
2758 if (status
!= COMM_OK
) {
2759 debugs(9, 2, HERE
<< "Failed to connect. Retrying via another method.");
2761 // ABORT on timeouts. server may be waiting on a broken TCP link.
2762 if (status
== COMM_TIMEOUT
)
2763 ftpState
->writeCommand("ABOR");
2765 // try another connection attempt with some other method
2766 ftpSendPassive(ftpState
);
2770 ftpState
->data
.opened(conn
, ftpState
->dataCloser());
2771 ftpRestOrList(ftpState
);
2774 /// \ingroup ServerProtocolFTPInternal
2776 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
2778 /// Close old data channels, if any. We may open a new one below.
2779 if (ftpState
->data
.conn
!= NULL
) {
2780 if ((ftpState
->data
.conn
->flags
& COMM_REUSEADDR
))
2781 // NP: in fact it points to the control channel. just clear it.
2782 ftpState
->data
.clear();
2784 ftpState
->data
.close();
2786 safe_free(ftpState
->data
.host
);
2789 * Set up a listen socket on the same local address as the
2790 * control connection.
2792 Comm::ConnectionPointer temp
= new Comm::Connection
;
2793 temp
->local
= ftpState
->ctrl
.conn
->local
;
2796 * REUSEADDR is needed in fallback mode, since the same port is
2797 * used for both control and data.
2801 setsockopt(ftpState
->ctrl
.conn
->fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
2802 ftpState
->ctrl
.conn
->flags
|= COMM_REUSEADDR
;
2803 temp
->flags
|= COMM_REUSEADDR
;
2805 /* if not running in fallback mode a new port needs to be retrieved */
2806 temp
->local
.SetPort(0);
2809 ftpState
->listenForDataChannel(temp
, ftpState
->entry
->url());
2812 /// \ingroup ServerProtocolFTPInternal
2814 ftpSendPORT(FtpStateData
* ftpState
)
2816 /* check the server control channel is still available */
2817 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
2820 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2821 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2826 ftpState
->flags
.pasv_supported
= 0;
2827 ftpOpenListenSocket(ftpState
, 0);
2829 if (!Comm::IsConnOpen(ftpState
->data
.listenConn
)) {
2830 if ( ftpState
->data
.listenConn
!= NULL
&& !ftpState
->data
.listenConn
->local
.IsIPv4() ) {
2831 /* non-IPv4 CANNOT send PORT command. */
2832 /* we got here by attempting and failing an EPRT */
2833 /* using the same reply code should simulate a PORT failure */
2834 ftpReadPORT(ftpState
);
2838 /* XXX Need to set error message */
2843 // pull out the internal IP address bytes to send in PORT command...
2844 // source them from the listen_conn->local
2846 struct addrinfo
*AI
= NULL
;
2847 ftpState
->data
.listenConn
->local
.GetAddrInfo(AI
, AF_INET
);
2848 unsigned char *addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
2849 unsigned char *portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
2850 snprintf(cbuf
, CTRL_BUFLEN
, "PORT %d,%d,%d,%d,%d,%d\r\n",
2851 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
2852 portptr
[0], portptr
[1]);
2853 ftpState
->writeCommand(cbuf
);
2854 ftpState
->state
= SENT_PORT
;
2856 ftpState
->data
.listenConn
->local
.FreeAddrInfo(AI
);
2859 /// \ingroup ServerProtocolFTPInternal
2861 ftpReadPORT(FtpStateData
* ftpState
)
2863 int code
= ftpState
->ctrl
.replycode
;
2867 /* Fall back on using the same port as the control connection */
2868 debugs(9, 3, "PORT not supported by remote end");
2869 ftpOpenListenSocket(ftpState
, 1);
2872 ftpRestOrList(ftpState
);
2875 /// \ingroup ServerProtocolFTPInternal
2877 ftpSendEPRT(FtpStateData
* ftpState
)
2879 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2880 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2884 if (!Config
.Ftp
.eprt
) {
2885 /* Disabled. Switch immediately to attempting old PORT command. */
2886 debugs(9, 3, "EPRT disabled by local administrator");
2887 ftpSendPORT(ftpState
);
2892 ftpState
->flags
.pasv_supported
= 0;
2894 ftpOpenListenSocket(ftpState
, 0);
2895 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState
->data
.conn
);
2896 if (!Comm::IsConnOpen(ftpState
->data
.conn
)) {
2897 /* XXX Need to set error message */
2902 char buf
[MAX_IPSTRLEN
];
2904 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2905 /* Which can be used by EITHER protocol. */
2906 snprintf(cbuf
, CTRL_BUFLEN
, "EPRT |%d|%s|%d|\r\n",
2907 ( ftpState
->data
.listenConn
->local
.IsIPv6() ? 2 : 1 ),
2908 ftpState
->data
.listenConn
->local
.NtoA(buf
,MAX_IPSTRLEN
),
2909 ftpState
->data
.listenConn
->local
.GetPort() );
2911 ftpState
->writeCommand(cbuf
);
2912 ftpState
->state
= SENT_EPRT
;
2916 ftpReadEPRT(FtpStateData
* ftpState
)
2918 int code
= ftpState
->ctrl
.replycode
;
2922 /* Failover to attempting old PORT command. */
2923 debugs(9, 3, "EPRT not supported by remote end");
2924 ftpSendPORT(ftpState
);
2928 ftpRestOrList(ftpState
);
2932 \ingroup ServerProtocolFTPInternal
2934 * "read" handler to accept FTP data connections.
2936 \param io comm accept(2) callback parameters
2939 FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
2943 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2944 abortTransaction("entry aborted when accepting data conn");
2945 data
.listenConn
->close();
2946 data
.listenConn
= NULL
;
2950 if (io
.flag
!= COMM_OK
) {
2951 data
.listenConn
->close();
2952 data
.listenConn
= NULL
;
2953 debugs(9, DBG_IMPORTANT
, "FTP AcceptDataConnection: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
2954 /** \todo Need to send error message on control channel*/
2959 /* data listening conn is no longer even open. abort. */
2960 if (!Comm::IsConnOpen(data
.listenConn
)) {
2961 data
.listenConn
= NULL
; // ensure that it's cleared and not just closed.
2965 /* data listening conn is no longer even open. abort. */
2966 if (!Comm::IsConnOpen(data
.conn
)) {
2967 data
.clear(); // ensure that it's cleared and not just closed.
2972 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2973 * made by the remote client which is connected to the FTP control socket.
2974 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
2975 * This prevents third-party hacks, but also third-party load balancing handshakes.
2977 if (Config
.Ftp
.sanitycheck
) {
2978 // accept if either our data or ctrl connection is talking to this remote peer.
2979 if (data
.conn
->remote
!= io
.conn
->remote
&& ctrl
.conn
->remote
!= io
.conn
->remote
) {
2980 debugs(9, DBG_IMPORTANT
,
2981 "FTP data connection from unexpected server (" <<
2982 io
.conn
->remote
<< "), expecting " <<
2983 data
.conn
->remote
<< " or " << ctrl
.conn
->remote
);
2985 /* close the bad sources connection down ASAP. */
2988 /* drop the bad connection (io) by ignoring the attempt. */
2993 /** On COMM_OK start using the accepted data socket and discard the temporary listen socket. */
2995 data
.opened(io
.conn
, dataCloser());
2996 static char ntoapeer
[MAX_IPSTRLEN
];
2997 io
.conn
->remote
.NtoA(ntoapeer
,sizeof(ntoapeer
));
2998 data
.host
= xstrdup(ntoapeer
);
3000 debugs(9, 3, HERE
<< "Connected data socket on " <<
3001 io
.conn
<< ". FD table says: " <<
3002 "ctrl-peer= " << fd_table
[ctrl
.conn
->fd
].ipaddr
<< ", " <<
3003 "data-peer= " << fd_table
[data
.conn
->fd
].ipaddr
);
3005 assert(haveControlChannel("ftpAcceptDataConnection"));
3006 assert(ctrl
.message
== NULL
);
3008 // Ctrl channel operations will determine what happens to this data connection
3011 /// \ingroup ServerProtocolFTPInternal
3013 ftpRestOrList(FtpStateData
* ftpState
)
3017 if (ftpState
->typecode
== 'D') {
3018 ftpState
->flags
.isdir
= 1;
3020 if (ftpState
->flags
.put
) {
3021 ftpSendMkdir(ftpState
); /* PUT name;type=d */
3023 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3025 } else if (ftpState
->flags
.put
) {
3026 ftpSendStor(ftpState
);
3027 } else if (ftpState
->flags
.isdir
)
3028 ftpSendList(ftpState
);
3029 else if (ftpState
->restartable())
3030 ftpSendRest(ftpState
);
3032 ftpSendRetr(ftpState
);
3035 /// \ingroup ServerProtocolFTPInternal
3037 ftpSendStor(FtpStateData
* ftpState
)
3039 /* check the server control channel is still available */
3040 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
3045 if (ftpState
->filepath
!= NULL
) {
3046 /* Plain file upload */
3047 snprintf(cbuf
, CTRL_BUFLEN
, "STOR %s\r\n", ftpState
->filepath
);
3048 ftpState
->writeCommand(cbuf
);
3049 ftpState
->state
= SENT_STOR
;
3050 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
3051 /* File upload without a filename. use STOU to generate one */
3052 snprintf(cbuf
, CTRL_BUFLEN
, "STOU\r\n");
3053 ftpState
->writeCommand(cbuf
);
3054 ftpState
->state
= SENT_STOR
;
3056 /* No file to transfer. Only create directories if needed */
3057 ftpSendReply(ftpState
);
3061 /// \ingroup ServerProtocolFTPInternal
3062 /// \deprecated use ftpState->readStor() instead.
3064 ftpReadStor(FtpStateData
* ftpState
)
3066 ftpState
->readStor();
3069 void FtpStateData::readStor()
3071 int code
= ctrl
.replycode
;
3074 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(data
.conn
))) {
3075 if (!startRequestBodyFlow()) { // register to receive body data
3080 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
3081 debugs(9, 3, HERE
<< "starting data transfer");
3082 switchTimeoutToDataChannel();
3083 sendMoreRequestBody();
3084 fwd
->dontRetry(true); // dont permit re-trying if the body was sent.
3085 state
= WRITING_DATA
;
3086 debugs(9, 3, HERE
<< "writing data channel");
3087 } else if (code
== 150) {
3088 /* When client code is 150 with no data channel, Accept data channel. */
3089 debugs(9, 3, "ftpReadStor: accepting data channel");
3090 listenForDataChannel(data
.conn
, data
.host
);
3092 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
3097 /// \ingroup ServerProtocolFTPInternal
3099 ftpSendRest(FtpStateData
* ftpState
)
3101 /* check the server control channel is still available */
3102 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
3107 snprintf(cbuf
, CTRL_BUFLEN
, "REST %" PRId64
"\r\n", ftpState
->restart_offset
);
3108 ftpState
->writeCommand(cbuf
);
3109 ftpState
->state
= SENT_REST
;
3113 FtpStateData::restartable()
3115 if (restart_offset
> 0)
3118 if (!request
->range
)
3127 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
3129 if (desired_offset
<= 0)
3132 if (desired_offset
>= theSize
)
3135 restart_offset
= desired_offset
;
3139 /// \ingroup ServerProtocolFTPInternal
3141 ftpReadRest(FtpStateData
* ftpState
)
3143 int code
= ftpState
->ctrl
.replycode
;
3145 assert(ftpState
->restart_offset
> 0);
3148 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
3149 ftpSendRetr(ftpState
);
3150 } else if (code
> 0) {
3151 debugs(9, 3, HERE
<< "REST not supported");
3152 ftpState
->flags
.rest_supported
= 0;
3153 ftpSendRetr(ftpState
);
3159 /// \ingroup ServerProtocolFTPInternal
3161 ftpSendList(FtpStateData
* ftpState
)
3163 /* check the server control channel is still available */
3164 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
3169 if (ftpState
->filepath
) {
3170 snprintf(cbuf
, CTRL_BUFLEN
, "LIST %s\r\n", ftpState
->filepath
);
3172 snprintf(cbuf
, CTRL_BUFLEN
, "LIST\r\n");
3175 ftpState
->writeCommand(cbuf
);
3176 ftpState
->state
= SENT_LIST
;
3179 /// \ingroup ServerProtocolFTPInternal
3181 ftpSendNlst(FtpStateData
* ftpState
)
3183 /* check the server control channel is still available */
3184 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
3189 ftpState
->flags
.tried_nlst
= 1;
3191 if (ftpState
->filepath
) {
3192 snprintf(cbuf
, CTRL_BUFLEN
, "NLST %s\r\n", ftpState
->filepath
);
3194 snprintf(cbuf
, CTRL_BUFLEN
, "NLST\r\n");
3197 ftpState
->writeCommand(cbuf
);
3198 ftpState
->state
= SENT_NLST
;
3201 /// \ingroup ServerProtocolFTPInternal
3203 ftpReadList(FtpStateData
* ftpState
)
3205 int code
= ftpState
->ctrl
.replycode
;
3208 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
3209 /* Begin data transfer */
3210 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3211 ftpState
->switchTimeoutToDataChannel();
3212 ftpState
->maybeReadVirginBody();
3213 ftpState
->state
= READING_DATA
;
3215 } else if (code
== 150) {
3216 /* Accept data channel */
3217 debugs(9, 3, HERE
<< "accept data channel from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3218 ftpState
->listenForDataChannel(ftpState
->data
.conn
, ftpState
->data
.host
);
3220 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
3221 ftpSendNlst(ftpState
);
3228 /// \ingroup ServerProtocolFTPInternal
3230 ftpSendRetr(FtpStateData
* ftpState
)
3232 /* check the server control channel is still available */
3233 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
3238 assert(ftpState
->filepath
!= NULL
);
3239 snprintf(cbuf
, CTRL_BUFLEN
, "RETR %s\r\n", ftpState
->filepath
);
3240 ftpState
->writeCommand(cbuf
);
3241 ftpState
->state
= SENT_RETR
;
3244 /// \ingroup ServerProtocolFTPInternal
3246 ftpReadRetr(FtpStateData
* ftpState
)
3248 int code
= ftpState
->ctrl
.replycode
;
3251 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
3252 /* Begin data transfer */
3253 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
3254 ftpState
->switchTimeoutToDataChannel();
3255 ftpState
->maybeReadVirginBody();
3256 ftpState
->state
= READING_DATA
;
3257 } else if (code
== 150) {
3258 /* Accept data channel */
3259 ftpState
->listenForDataChannel(ftpState
->data
.conn
, ftpState
->data
.host
);
3260 } else if (code
>= 300) {
3261 if (!ftpState
->flags
.try_slash_hack
) {
3262 /* Try this as a directory missing trailing slash... */
3263 ftpState
->hackShortcut(ftpSendCwd
);
3273 * Generate the HTTP headers and template fluff around an FTP
3274 * directory listing display.
3277 FtpStateData::completedListing()
3281 ErrorState
ferr(ERR_DIR_LISTING
, Http::scOkay
, request
);
3282 ferr
.ftp
.listing
= &listing
;
3283 ferr
.ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
3284 ferr
.ftp
.server_msg
= ctrl
.message
;
3285 ctrl
.message
= NULL
;
3286 entry
->replaceHttpReply( ferr
.BuildHttpReply() );
3287 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3292 /// \ingroup ServerProtocolFTPInternal
3294 ftpReadTransferDone(FtpStateData
* ftpState
)
3296 int code
= ftpState
->ctrl
.replycode
;
3299 if (code
== 226 || code
== 250) {
3300 /* Connection closed; retrieval done. */
3301 if (ftpState
->flags
.listing
) {
3302 ftpState
->completedListing();
3303 /* QUIT operation handles sending the reply to client */
3305 ftpSendQuit(ftpState
);
3306 } else { /* != 226 */
3307 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
3308 ftpState
->failed(ERR_FTP_FAILURE
, 0);
3309 /* failed closes ctrl.conn and frees ftpState */
3314 // premature end of the request body
3316 FtpStateData::handleRequestBodyProducerAborted()
3318 ServerStateData::handleRequestBodyProducerAborted();
3319 debugs(9, 3, HERE
<< "ftpState=" << this);
3320 failed(ERR_READ_ERROR
, 0);
3324 * This will be called when the put write is completed
3327 FtpStateData::sentRequestBody(const CommIoCbParams
&io
)
3330 kb_incr(&(statCounter
.server
.ftp
.kbytes_out
), io
.size
);
3331 ServerStateData::sentRequestBody(io
);
3334 /// \ingroup ServerProtocolFTPInternal
3336 ftpWriteTransferDone(FtpStateData
* ftpState
)
3338 int code
= ftpState
->ctrl
.replycode
;
3341 if (!(code
== 226 || code
== 250)) {
3342 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
3343 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
3347 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
3348 ftpSendReply(ftpState
);
3351 /// \ingroup ServerProtocolFTPInternal
3353 ftpSendQuit(FtpStateData
* ftpState
)
3355 /* check the server control channel is still available */
3356 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
3359 snprintf(cbuf
, CTRL_BUFLEN
, "QUIT\r\n");
3360 ftpState
->writeCommand(cbuf
);
3361 ftpState
->state
= SENT_QUIT
;
3365 * \ingroup ServerProtocolFTPInternal
3367 * This completes a client FTP operation with success or other page
3368 * generated and stored in the entry field by the code issuing QUIT.
3371 ftpReadQuit(FtpStateData
* ftpState
)
3373 ftpState
->serverComplete();
3376 /// \ingroup ServerProtocolFTPInternal
3378 ftpTrySlashHack(FtpStateData
* ftpState
)
3381 ftpState
->flags
.try_slash_hack
= 1;
3382 /* Free old paths */
3386 if (ftpState
->pathcomps
)
3387 wordlistDestroy(&ftpState
->pathcomps
);
3389 safe_free(ftpState
->filepath
);
3391 /* Build the new path (urlpath begins with /) */
3392 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
3394 rfc1738_unescape(path
);
3396 ftpState
->filepath
= path
;
3399 ftpGetFile(ftpState
);
3403 * Forget hack status. Next error is shown to the user
3406 FtpStateData::unhack()
3410 if (old_request
!= NULL
) {
3411 safe_free(old_request
);
3412 safe_free(old_reply
);
3417 FtpStateData::hackShortcut(FTPSM
* nextState
)
3419 /* Clear some unwanted state */
3420 setCurrentOffset(0);
3422 /* Save old error message & some state info */
3426 if (old_request
== NULL
) {
3427 old_request
= ctrl
.last_command
;
3428 ctrl
.last_command
= NULL
;
3429 old_reply
= ctrl
.last_reply
;
3430 ctrl
.last_reply
= NULL
;
3432 if (pathcomps
== NULL
&& filepath
!= NULL
)
3433 old_filepath
= xstrdup(filepath
);
3436 /* Jump to the "hack" state */
3440 /// \ingroup ServerProtocolFTPInternal
3442 ftpFail(FtpStateData
*ftpState
)
3444 debugs(9, 6, HERE
<< "flags(" <<
3445 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
3446 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
3447 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
3448 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
3450 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
3451 if (!ftpState
->flags
.isdir
&& /* Not a directory */
3452 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
3453 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
3454 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
3456 switch (ftpState
->state
) {
3461 /* Try the / hack */
3462 ftpState
->hackShortcut(ftpTrySlashHack
);
3470 ftpState
->failed(ERR_NONE
, 0);
3471 /* failed() closes ctrl.conn and frees this */
3475 FtpStateData::failed(err_type error
, int xerrno
)
3477 debugs(9,3,HERE
<< "entry-null=" << (entry
?entry
->isEmpty():0) << ", entry=" << entry
);
3478 if (entry
->isEmpty())
3479 failedErrorMessage(error
, xerrno
);
3485 FtpStateData::failedErrorMessage(err_type error
, int xerrno
)
3487 ErrorState
*ftperr
= NULL
;
3488 const char *command
, *reply
;
3490 /* Translate FTP errors into HTTP errors */
3501 if (ctrl
.replycode
> 500)
3503 ftperr
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scForbidden
, fwd
->request
);
3505 ftperr
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
3507 else if (ctrl
.replycode
== 421)
3508 ftperr
= new ErrorState(ERR_FTP_UNAVAILABLE
, Http::scServiceUnavailable
, fwd
->request
);
3515 if (ctrl
.replycode
== 550)
3516 ftperr
= new ErrorState(ERR_FTP_NOT_FOUND
, Http::scNotFound
, fwd
->request
);
3526 case ERR_READ_TIMEOUT
:
3527 ftperr
= new ErrorState(error
, Http::scGateway_Timeout
, fwd
->request
);
3531 ftperr
= new ErrorState(error
, Http::scBadGateway
, fwd
->request
);
3536 ftperr
= new ErrorState(ERR_FTP_FAILURE
, Http::scBadGateway
, fwd
->request
);
3538 ftperr
->xerrno
= xerrno
;
3540 ftperr
->ftp
.server_msg
= ctrl
.message
;
3541 ctrl
.message
= NULL
;
3544 command
= old_request
;
3546 command
= ctrl
.last_command
;
3548 if (command
&& strncmp(command
, "PASS", 4) == 0)
3549 command
= "PASS <yourpassword>";
3554 reply
= ctrl
.last_reply
;
3557 ftperr
->ftp
.request
= xstrdup(command
);
3560 ftperr
->ftp
.reply
= xstrdup(reply
);
3562 entry
->replaceHttpReply( ftperr
->BuildHttpReply() );
3566 /// \ingroup ServerProtocolFTPInternal
3568 ftpSendReply(FtpStateData
* ftpState
)
3570 int code
= ftpState
->ctrl
.replycode
;
3571 Http::StatusCode http_code
;
3572 err_type err_code
= ERR_NONE
;
3574 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
3576 if (cbdataReferenceValid(ftpState
))
3577 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
3579 if (code
== 226 || code
== 250) {
3580 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
3581 http_code
= (ftpState
->mdtm
> 0) ? Http::scAccepted
: Http::scCreated
;
3582 } else if (code
== 227) {
3583 err_code
= ERR_FTP_PUT_CREATED
;
3584 http_code
= Http::scCreated
;
3586 err_code
= ERR_FTP_PUT_ERROR
;
3587 http_code
= Http::scInternalServerError
;
3590 ErrorState
err(err_code
, http_code
, ftpState
->request
);
3592 if (ftpState
->old_request
)
3593 err
.ftp
.request
= xstrdup(ftpState
->old_request
);
3595 err
.ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
3597 if (ftpState
->old_reply
)
3598 err
.ftp
.reply
= xstrdup(ftpState
->old_reply
);
3599 else if (ftpState
->ctrl
.last_reply
)
3600 err
.ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
3602 err
.ftp
.reply
= xstrdup("");
3604 // TODO: interpret as FTP-specific error code
3605 err
.detailError(code
);
3607 ftpState
->entry
->replaceHttpReply( err
.BuildHttpReply() );
3609 ftpSendQuit(ftpState
);
3613 FtpStateData::appendSuccessHeader()
3615 const char *mime_type
= NULL
;
3616 const char *mime_enc
= NULL
;
3617 String urlpath
= request
->urlpath
;
3618 const char *filename
= NULL
;
3619 const char *t
= NULL
;
3623 if (flags
.http_header_sent
)
3626 HttpReply
*reply
= new HttpReply
;
3628 flags
.http_header_sent
= 1;
3630 assert(entry
->isEmpty());
3632 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3634 entry
->buffer(); /* released when done processing current data payload */
3636 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
3639 mime_type
= "text/html";
3644 mime_type
= "application/octet-stream";
3645 mime_enc
= mimeGetContentEncoding(filename
);
3649 mime_type
= "text/plain";
3653 mime_type
= mimeGetContentType(filename
);
3654 mime_enc
= mimeGetContentEncoding(filename
);
3659 /* set standard stuff */
3661 if (0 == getCurrentOffset()) {
3663 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3664 } else if (theSize
< getCurrentOffset()) {
3667 * offset should not be larger than theSize. We should
3668 * not be seeing this condition any more because we'll only
3669 * send REST if we know the theSize and if it is less than theSize.
3671 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
3672 " current offset=" << getCurrentOffset() <<
3673 ", but theSize=" << theSize
<<
3674 ". assuming full content response");
3675 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3678 HttpHdrRangeSpec range_spec
;
3679 range_spec
.offset
= getCurrentOffset();
3680 range_spec
.length
= theSize
- getCurrentOffset();
3681 reply
->setHeaders(Http::scPartialContent
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
3682 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
3685 /* additional info */
3687 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
3689 setVirginReply(reply
);
3690 adaptOrFinalizeReply();
3694 FtpStateData::haveParsedReplyHeaders()
3696 ServerStateData::haveParsedReplyHeaders();
3698 StoreEntry
*e
= entry
;
3702 if (flags
.authenticated
) {
3704 * Authenticated requests can't be cached.
3707 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !getCurrentOffset()) {
3715 FtpStateData::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
3717 ErrorState
err(ERR_CACHE_ACCESS_DENIED
, Http::scUnauthorized
, request
);
3718 HttpReply
*newrep
= err
.BuildHttpReply();
3719 #if HAVE_AUTH_MODULE_BASIC
3720 /* add Authenticate header */
3721 newrep
->header
.putAuth("Basic", realm
);
3727 \ingroup ServerProtocolFTPAPI
3728 \todo Should be a URL class API call.
3730 * Construct an URI with leading / in PATH portion for use by CWD command
3731 * possibly others. FTP encodes absolute paths as beginning with '/'
3732 * after the initial URI path delimiter, which happens to be / itself.
3733 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3734 * To encompass older software which compacts multiple // to / in transit
3735 * We use standard URI-encoding on the second / making it
3736 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3739 ftpUrlWith2f(HttpRequest
* request
)
3741 String newbuf
= "%2f";
3743 if (request
->protocol
!= AnyP::PROTO_FTP
)
3746 if ( request
->urlpath
[0]=='/' ) {
3747 newbuf
.append(request
->urlpath
);
3748 request
->urlpath
.absorb(newbuf
);
3749 safe_free(request
->canonical
);
3750 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
3751 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
3752 request
->urlpath
.absorb(newbuf
);
3753 safe_free(request
->canonical
);
3756 return urlCanonical(request
);
3760 FtpStateData::printfReplyBody(const char *fmt
, ...)
3763 va_start (args
, fmt
);
3764 static char buf
[4096];
3766 vsnprintf(buf
, 4096, fmt
, args
);
3767 writeReplyBody(buf
, strlen(buf
));
3772 * Call this when there is data from the origin server
3773 * which should be sent to either StoreEntry, or to ICAP...
3776 FtpStateData::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
3778 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
3779 addVirginReplyBody(dataToWrite
, dataLength
);
3783 * called after we wrote the last byte of the request body
3786 FtpStateData::doneSendingRequestBody()
3788 ServerStateData::doneSendingRequestBody();
3791 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3792 * if transfer type is 'stream' call dataComplete()
3793 * otherwise leave open. (reschedule control channel read?)
3798 * A hack to ensure we do not double-complete on the forward entry.
3800 \todo FtpStateData logic should probably be rewritten to avoid
3801 * double-completion or FwdState should be rewritten to allow it.
3804 FtpStateData::completeForwarding()
3806 if (fwd
== NULL
|| flags
.completed_forwarding
) {
3807 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
3808 "double-complete on FD " << ctrl
.conn
->fd
<< ", Data FD " << data
.conn
->fd
<<
3809 ", this " << this << ", fwd " << fwd
);
3813 flags
.completed_forwarding
= true;
3814 ServerStateData::completeForwarding();
3818 * Close the FTP server connection(s). Used by serverComplete().
3821 FtpStateData::closeServer()
3823 if (Comm::IsConnOpen(ctrl
.conn
)) {
3824 debugs(9,3, HERE
<< "closing FTP server FD " << ctrl
.conn
->fd
<< ", this " << this);
3825 fwd
->unregister(ctrl
.conn
);
3829 if (Comm::IsConnOpen(data
.conn
)) {
3830 debugs(9,3, HERE
<< "closing FTP data FD " << data
.conn
->fd
<< ", this " << this);
3834 debugs(9,3, HERE
<< "FTP ctrl and data connections closed. this " << this);
3838 * Did we close all FTP server connection(s)?
3840 \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
3841 \retval false Either control channel or data is still active.
3844 FtpStateData::doneWithServer() const
3846 return !Comm::IsConnOpen(ctrl
.conn
) && !Comm::IsConnOpen(data
.conn
);
3850 * Have we lost the FTP server control channel?
3852 \retval true The server control channel is available.
3853 \retval false The server control channel is not available.
3856 FtpStateData::haveControlChannel(const char *caller_name
) const
3858 if (doneWithServer())
3861 /* doneWithServer() only checks BOTH channels are closed. */
3862 if (!Comm::IsConnOpen(ctrl
.conn
)) {
3863 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
3864 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
3872 * Quickly abort the transaction
3874 \todo destruction should be sufficient as the destructor should cleanup,
3875 * including canceling close handlers
3878 FtpStateData::abortTransaction(const char *reason
)
3880 debugs(9, 3, HERE
<< "aborting transaction for " << reason
<<
3881 "; FD " << (ctrl
.conn
!=NULL
?ctrl
.conn
->fd
:-1) << ", Data FD " << (data
.conn
!=NULL
?data
.conn
->fd
:-1) << ", this " << this);
3882 if (Comm::IsConnOpen(ctrl
.conn
)) {
3887 fwd
->handleUnregisteredServerEnd();
3888 mustStop("FtpStateData::abortTransaction");
3891 /// creates a data channel Comm close callback
3893 FtpStateData::dataCloser()
3895 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
3896 return JobCallback(9, 5, Dialer
, this, FtpStateData::dataClosed
);
3899 /// configures the channel with a descriptor and registers a close handler
3901 FtpChannel::opened(const Comm::ConnectionPointer
&newConn
, const AsyncCall::Pointer
&aCloser
)
3903 assert(!Comm::IsConnOpen(conn
));
3904 assert(closer
== NULL
);
3906 assert(Comm::IsConnOpen(newConn
));
3907 assert(aCloser
!= NULL
);
3911 comm_add_close_handler(conn
->fd
, closer
);
3914 /// planned close: removes the close handler and calls comm_close
3918 // channels with active listeners will be closed when the listener handler dies.
3919 if (Comm::IsConnOpen(conn
)) {
3920 comm_remove_close_handler(conn
->fd
, closer
);
3921 conn
->close(); // we do not expect to be called back