4 * DEBUG: section 09 File Transfer Protocol (FTP)
5 * AUTHOR: Harvest Derived
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "comm/Write.h"
38 #include "comm/ListenStateData.h"
39 #include "compat/strtoll.h"
40 #include "ConnectionDetail.h"
41 #include "errorpage.h"
44 #include "html_quote.h"
45 #include "HttpHdrContRange.h"
46 #include "HttpHeaderRange.h"
47 #include "HttpHeader.h"
48 #include "HttpRequest.h"
49 #include "HttpReply.h"
54 #include "SquidString.h"
55 #include "SquidTime.h"
57 #include "URLScheme.h"
61 #include "DelayPools.h"
62 #include "MemObject.h"
66 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
67 \ingroup ServerProtocolFTPAPI
70 /// \ingroup ServerProtocolFTPInternal
71 static const char *const crlf
= "\r\n";
73 /// \ingroup ServerProtocolFTPInternal
74 static char cbuf
[1024];
76 /// \ingroup ServerProtocolFTPInternal
102 /// \ingroup ServerProtocolFTPInternal
106 bool pasv_supported
; ///< PASV command is allowed
107 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
111 bool authenticated
; ///< authentication success
112 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
113 bool tried_auth_nopass
; ///< auth tried username with no password already.
117 bool skip_whitespace
;
119 bool http_header_sent
;
129 bool listformat_unknown
;
131 bool completed_forwarding
;
136 /// \ingroup ServerProtocolFTPInternal
137 typedef void (FTPSM
) (FtpStateData
*);
139 /// common code for FTP control and data channels
140 // does not own the channel descriptor, which is managed by FtpStateData
144 FtpChannel(): fd(-1) {}
146 /// called after the socket is opened, sets up close handler
147 void opened(int aFd
, const AsyncCall::Pointer
&aCloser
);
149 /** Handles all operations needed to properly close the active channel FD.
150 * clearing the close handler, clearing the listen socket properly, and calling comm_close
154 void clear(); /// just resets fd and close handler. does not close active connections.
156 int fd
; /// channel descriptor; \todo: remove because the closer has it
158 /** Current listening socket handler. delete on shutdown or abort.
159 * FTP stores a copy of the FD in the field fd above.
160 * Use close() to properly close the channel.
162 Comm::ListenStateData
*listener
;
165 AsyncCall::Pointer closer
; /// Comm close handler callback
168 /// \ingroup ServerProtocolFTPInternal
169 class FtpStateData
: public ServerStateData
173 void *operator new (size_t);
174 void operator delete (void *);
175 void *toCbdata() { return this; }
177 FtpStateData(FwdState
*);
180 char password
[MAX_URL
];
195 int64_t restart_offset
;
203 MemBuf listing
; ///< FTP directory listing in HTML format.
205 // \todo: optimize ctrl and data structs member order, to minimize size
206 /// FTP control channel info; the channel is opened once per transaction
207 struct CtrlChannel
: public FtpChannel
{
217 /// FTP data channel info; the channel may be opened/closed a few times
218 struct DataChannel
: public FtpChannel
{
225 struct _ftp_flags flags
;
228 CBDATA_CLASS(FtpStateData
);
231 // these should all be private
233 void loginParser(const char *, int escaped
);
235 void appendSuccessHeader();
236 void hackShortcut(FTPSM
* nextState
);
237 void failed(err_type
, int xerrno
);
238 void failedErrorMessage(err_type
, int xerrno
);
240 void scheduleReadControlReply(int);
241 void handleControlReply();
244 MemBuf
*htmlifyListEntry(const char *line
);
245 void completedListing(void);
247 void dataRead(const CommIoCbParams
&io
);
248 int checkAuth(const HttpHeader
* req_hdr
);
250 void buildTitleUrl();
251 void writeReplyBody(const char *, size_t len
);
252 void printfReplyBody(const char *fmt
, ...);
253 virtual int dataDescriptor() const;
254 virtual void maybeReadVirginBody();
255 virtual void closeServer();
256 virtual void completeForwarding();
257 virtual void abortTransaction(const char *reason
);
258 void processHeadResponse();
259 void processReplyBody();
260 void writeCommand(const char *buf
);
261 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
262 int64_t getCurrentOffset() const { return currentOffset
; }
264 static CNCB ftpPasvCallback
;
265 static PF ftpDataWrite
;
266 void ftpTimeout(const CommTimeoutCbParams
&io
);
267 void ctrlClosed(const CommCloseCbParams
&io
);
268 void dataClosed(const CommCloseCbParams
&io
);
269 void ftpReadControlReply(const CommIoCbParams
&io
);
270 void ftpWriteCommandCallback(const CommIoCbParams
&io
);
271 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
273 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, const char *realm
);
274 const char *ftpRealm(void);
275 void loginFailed(void);
276 static wordlist
*ftpParseControlReply(char *, size_t, int *, size_t *);
278 // sending of the request body to the server
279 virtual void sentRequestBody(const CommIoCbParams
&);
280 virtual void doneSendingRequestBody();
282 virtual void haveParsedReplyHeaders();
284 virtual bool doneWithServer() const;
285 virtual bool haveControlChannel(const char *caller_name
) const;
286 AsyncCall::Pointer
dataCloser(); /// creates a Comm close callback
289 // BodyConsumer for HTTP: consume request body.
290 virtual void handleRequestBodyProducerAborted();
293 CBDATA_CLASS_INIT(FtpStateData
);
296 FtpStateData::operator new (size_t)
298 CBDATA_INIT_TYPE(FtpStateData
);
299 FtpStateData
*result
= cbdataAlloc(FtpStateData
);
304 FtpStateData::operator delete (void *address
)
306 FtpStateData
*t
= static_cast<FtpStateData
*>(address
);
310 /// \ingroup ServerProtocolFTPInternal
320 /// \ingroup ServerProtocolFTPInternal
321 #define FTP_LOGIN_ESCAPED 1
323 /// \ingroup ServerProtocolFTPInternal
324 #define FTP_LOGIN_NOT_ESCAPED 0
327 * State machine functions
328 * send == state transition
329 * read == wait for response, and select next state transition
330 * other == Transition logic
332 static FTPSM ftpReadWelcome
;
333 static FTPSM ftpSendUser
;
334 static FTPSM ftpReadUser
;
335 static FTPSM ftpSendPass
;
336 static FTPSM ftpReadPass
;
337 static FTPSM ftpSendType
;
338 static FTPSM ftpReadType
;
339 static FTPSM ftpSendMdtm
;
340 static FTPSM ftpReadMdtm
;
341 static FTPSM ftpSendSize
;
342 static FTPSM ftpReadSize
;
343 static FTPSM ftpSendEPRT
;
344 static FTPSM ftpReadEPRT
;
345 static FTPSM ftpSendPORT
;
346 static FTPSM ftpReadPORT
;
347 static FTPSM ftpSendPassive
;
348 static FTPSM ftpReadEPSV
;
349 static FTPSM ftpReadPasv
;
350 static FTPSM ftpTraverseDirectory
;
351 static FTPSM ftpListDir
;
352 static FTPSM ftpGetFile
;
353 static FTPSM ftpSendCwd
;
354 static FTPSM ftpReadCwd
;
355 static FTPSM ftpRestOrList
;
356 static FTPSM ftpSendList
;
357 static FTPSM ftpSendNlst
;
358 static FTPSM ftpReadList
;
359 static FTPSM ftpSendRest
;
360 static FTPSM ftpReadRest
;
361 static FTPSM ftpSendRetr
;
362 static FTPSM ftpReadRetr
;
363 static FTPSM ftpReadTransferDone
;
364 static FTPSM ftpSendStor
;
365 static FTPSM ftpReadStor
;
366 static FTPSM ftpWriteTransferDone
;
367 static FTPSM ftpSendReply
;
368 static FTPSM ftpSendMkdir
;
369 static FTPSM ftpReadMkdir
;
370 static FTPSM ftpFail
;
371 static FTPSM ftpSendQuit
;
372 static FTPSM ftpReadQuit
;
374 /************************************************
375 ** Debugs Levels used here **
376 *************************************************
379 Protocol and Transmission failures.
380 2 FTP Protocol Chatter
385 ************************************************/
387 /************************************************
388 ** State Machine Description (excluding hacks) **
389 *************************************************
391 ---------------------------------------
395 Type TraverseDirectory / GetFile
396 TraverseDirectory Cwd / GetFile / ListDir
397 Cwd TraverseDirectory / Mkdir
403 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
405 Retr / Nlst / List DataRead* (on datachannel)
406 DataRead* ReadTransferDone
407 ReadTransferDone DataTransferDone
408 Stor DataWrite* (on datachannel)
409 DataWrite* RequestPutBody** (from client)
410 RequestPutBody** DataWrite* / WriteTransferDone
411 WriteTransferDone DataTransferDone
412 DataTransferDone Quit
414 ************************************************/
416 /// \ingroup ServerProtocolFTPInternal
417 FTPSM
*FTP_SM_FUNCS
[] = {
418 ftpReadWelcome
, /* BEGIN */
419 ftpReadUser
, /* SENT_USER */
420 ftpReadPass
, /* SENT_PASS */
421 ftpReadType
, /* SENT_TYPE */
422 ftpReadMdtm
, /* SENT_MDTM */
423 ftpReadSize
, /* SENT_SIZE */
424 ftpReadEPRT
, /* SENT_EPRT */
425 ftpReadPORT
, /* SENT_PORT */
426 ftpReadEPSV
, /* SENT_EPSV_ALL */
427 ftpReadEPSV
, /* SENT_EPSV_1 */
428 ftpReadEPSV
, /* SENT_EPSV_2 */
429 ftpReadPasv
, /* SENT_PASV */
430 ftpReadCwd
, /* SENT_CWD */
431 ftpReadList
, /* SENT_LIST */
432 ftpReadList
, /* SENT_NLST */
433 ftpReadRest
, /* SENT_REST */
434 ftpReadRetr
, /* SENT_RETR */
435 ftpReadStor
, /* SENT_STOR */
436 ftpReadQuit
, /* SENT_QUIT */
437 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
438 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
439 ftpReadMkdir
/* SENT_MKDIR */
442 /// handler called by Comm when FTP control channel is closed unexpectedly
444 FtpStateData::ctrlClosed(const CommCloseCbParams
&io
)
447 deleteThis("FtpStateData::ctrlClosed");
450 /// handler called by Comm when FTP data channel is closed unexpectedly
452 FtpStateData::dataClosed(const CommCloseCbParams
&io
)
455 delete data
.listener
;
456 data
.listener
= NULL
;
460 failed(ERR_FTP_FAILURE
, 0);
461 /* failed closes ctrl.fd and frees ftpState */
463 /* NP: failure recovery may be possible when its only a data.fd failure.
464 * is the ctrl.fd is still fine, we can send ABOR down it and retry.
465 * Just need to watch out for wider Squid states like shutting down or reconfigure.
469 FtpStateData::FtpStateData(FwdState
*theFwdState
) : AsyncJob("FtpStateData"), ServerStateData(theFwdState
)
471 const char *url
= entry
->url();
472 debugs(9, 3, HERE
<< "'" << url
<< "'" );
473 statCounter
.server
.all
.requests
++;
474 statCounter
.server
.ftp
.requests
++;
478 if (Config
.Ftp
.passive
&& !theFwdState
->ftpPasvFailed())
479 flags
.pasv_supported
= 1;
481 flags
.rest_supported
= 1;
483 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
484 AsyncCall::Pointer closer
= JobCallback(9, 5,
485 Dialer
, this, FtpStateData::ctrlClosed
);
486 ctrl
.opened(theFwdState
->server_fd
, closer
);
488 if (request
->method
== METHOD_PUT
)
492 FtpStateData::~FtpStateData()
494 debugs(9, 3, HERE
<< entry
->url() );
497 memFree(reply_hdr
, MEM_8K_BUF
);
504 debugs(9, DBG_IMPORTANT
, HERE
<< "Internal bug: FtpStateData left " <<
505 "control FD " << ctrl
.fd
<< " open");
509 memFreeBuf(ctrl
.size
, ctrl
.buf
);
514 if (!data
.readBuf
->isNull())
515 data
.readBuf
->clean();
521 wordlistDestroy(&pathcomps
);
524 wordlistDestroy(&ctrl
.message
);
528 safe_free(ctrl
.last_reply
);
530 safe_free(ctrl
.last_command
);
532 safe_free(old_request
);
534 safe_free(old_reply
);
536 safe_free(old_filepath
);
546 safe_free(data
.host
);
548 fwd
= NULL
; // refcounted
552 * Parse a possible login username:password pair.
553 * Produces filled member variables user, password, password_url if anything found.
556 FtpStateData::loginParser(const char *login
, int escaped
)
558 const char *u
= NULL
; // end of the username sub-string
559 int len
; // length of the current sub-string to handle.
561 int total_len
= strlen(login
);
563 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
564 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
566 if ((u
= strchr(login
, ':'))) {
568 /* if there was a username part */
571 ++u
; // jump off the delimiter.
574 xstrncpy(user
, login
, len
+1);
575 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
577 rfc1738_unescape(user
);
578 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
581 /* if there was a password part */
582 len
= login
+ total_len
- u
;
586 xstrncpy(password
, u
, len
+1);
587 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
589 rfc1738_unescape(password
);
592 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
594 } else if (login
[0]) {
595 /* no password, just username */
596 if (total_len
> MAX_URL
)
597 total_len
= MAX_URL
-1;
598 xstrncpy(user
, login
, total_len
+1);
599 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
601 rfc1738_unescape(user
);
602 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
605 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
609 FtpStateData::ftpTimeout(const CommTimeoutCbParams
&io
)
611 debugs(9, 4, "ftpTimeout: FD " << io
.fd
<< ": '" << entry
->url() << "'" );
613 if (SENT_PASV
== state
&& io
.fd
== data
.fd
) {
614 /* stupid ftp.netscape.com */
615 fwd
->dontRetry(false);
616 fwd
->ftpPasvFailed(true);
617 debugs(9, DBG_IMPORTANT
, "ftpTimeout: timeout in SENT_PASV state" );
620 failed(ERR_READ_TIMEOUT
, 0);
621 /* failed() closes ctrl.fd and frees ftpState */
624 #if DEAD_CODE // obsoleted by ERR_DIR_LISTING
626 FtpStateData::listingFinish()
628 // TODO: figure out what this means and how to show it ...
630 if (flags
.listformat_unknown
&& !flags
.tried_nlst
) {
631 printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
632 flags
.dir_slash
? rfc1738_escape_part(old_filepath
) : ".");
633 } else if (typecode
== 'D') {
634 const char *path
= flags
.dir_slash
? filepath
: ".";
635 printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path
));
638 #endif /* DEAD_CODE */
640 /// \ingroup ServerProtocolFTPInternal
641 static const char *Month
[] = {
642 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
643 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
646 /// \ingroup ServerProtocolFTPInternal
648 is_month(const char *buf
)
652 for (i
= 0; i
< 12; i
++)
653 if (!strcasecmp(buf
, Month
[i
]))
659 /// \ingroup ServerProtocolFTPInternal
661 ftpListPartsFree(ftpListParts
** parts
)
663 safe_free((*parts
)->date
);
664 safe_free((*parts
)->name
);
665 safe_free((*parts
)->showname
);
666 safe_free((*parts
)->link
);
670 /// \ingroup ServerProtocolFTPInternal
671 #define MAX_TOKENS 64
673 /// \ingroup ServerProtocolFTPInternal
674 static ftpListParts
*
675 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
677 ftpListParts
*p
= NULL
;
679 const char *ct
= NULL
;
680 char *tokens
[MAX_TOKENS
];
683 static char tbuf
[128];
685 static int scan_ftp_initialized
= 0;
686 static regex_t scan_ftp_integer
;
687 static regex_t scan_ftp_time
;
688 static regex_t scan_ftp_dostime
;
689 static regex_t scan_ftp_dosdate
;
691 if (!scan_ftp_initialized
) {
692 scan_ftp_initialized
= 1;
693 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
694 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
695 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
696 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
705 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
709 memset(tokens
, 0, sizeof(tokens
));
713 if (flags
.tried_nlst
) {
714 /* Machine readable format, one name per line */
720 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
))
721 tokens
[n_tokens
++] = xstrdup(t
);
725 /* locate the Month field */
726 for (i
= 3; i
< n_tokens
- 2; i
++) {
727 char *size
= tokens
[i
- 1];
728 char *month
= tokens
[i
];
729 char *day
= tokens
[i
+ 1];
730 char *year
= tokens
[i
+ 2];
732 if (!is_month(month
))
735 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
738 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
741 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
744 snprintf(tbuf
, 128, "%s %2s %5s",
747 if (!strstr(buf
, tbuf
))
748 snprintf(tbuf
, 128, "%s %2s %-5s",
751 char const *copyFrom
= NULL
;
753 if ((copyFrom
= strstr(buf
, tbuf
))) {
754 p
->type
= *tokens
[0];
755 p
->size
= strtoll(size
, NULL
, 10);
756 p
->date
= xstrdup(tbuf
);
758 if (flags
.skip_whitespace
) {
759 copyFrom
+= strlen(tbuf
);
761 while (strchr(w_space
, *copyFrom
))
764 /* XXX assumes a single space between date and filename
765 * suggested by: Nathan.Bailey@cc.monash.edu.au and
766 * Mike Battersby <mike@starbug.bofh.asn.au> */
767 copyFrom
+= strlen(tbuf
) + 1;
770 p
->name
= xstrdup(copyFrom
);
772 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
774 p
->link
= xstrdup(t
+ 4);
783 /* try it as a DOS listing, 04-05-70 09:33PM ... */
785 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
786 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
787 if (!strcasecmp(tokens
[2], "<dir>")) {
791 p
->size
= strtoll(tokens
[2], NULL
, 10);
794 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
795 p
->date
= xstrdup(tbuf
);
797 if (p
->type
== 'd') {
798 /* Directory.. name begins with first printable after <dir> */
799 ct
= strstr(buf
, tokens
[2]);
800 ct
+= strlen(tokens
[2]);
802 while (xisspace(*ct
))
808 /* A file. Name begins after size, with a space in between */
809 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
810 ct
= strstr(buf
, tbuf
);
813 ct
+= strlen(tokens
[2]) + 2;
817 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
821 /* Try EPLF format; carson@lehman.com */
828 int l
= strcspn(ct
, ",");
837 p
->name
= xstrndup(ct
+ 1, l
+ 1);
841 p
->size
= atoi(ct
+ 1);
845 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
848 break; /* not a valid integer */
850 p
->date
= xstrdup(ctime(&tm
));
852 *(strstr(p
->date
, "\n")) = '\0';
874 ct
= strstr(ct
, ",");
893 for (i
= 0; i
< n_tokens
; i
++)
897 ftpListPartsFree(&p
); /* cleanup */
903 FtpStateData::htmlifyListEntry(const char *line
)
906 char href
[2048 + 40];
909 char chdir
[ 2048 + 40];
910 char view
[ 2048 + 40];
911 char download
[ 2048 + 40];
912 char link
[ 2048 + 40];
916 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
918 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
920 if (strlen(line
) > 1024) {
923 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
927 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
928 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
932 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
937 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
939 for (p
= line
; *p
&& xisspace(*p
); p
++);
940 if (*p
&& !xisspace(*p
))
941 flags
.listformat_unknown
= 1;
946 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
947 ftpListPartsFree(&parts
);
953 parts
->showname
= xstrdup(parts
->name
);
955 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
956 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
958 xstrncpy(text
, parts
->showname
, 2048);
960 switch (parts
->type
) {
963 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
964 mimeGetIconURL("internal-dir"),
966 strcat(href
, "/"); /* margin is allocated above */
970 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
971 mimeGetIconURL("internal-link"),
973 /* sometimes there is an 'l' flag, but no "->" link */
976 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
977 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
978 *link2
!= '/' ? prefix
: "", link2
,
979 html_quote(parts
->link
));
986 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
987 mimeGetIconURL(parts
->name
),
989 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
990 "alt=\"[DIR]\"></a>",
991 rfc1738_escape_part(parts
->name
),
992 mimeGetIconURL("internal-dir"));
998 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
999 mimeGetIconURL(parts
->name
),
1001 snprintf(size
, 2048, " %6"PRId64
"k", parts
->size
);
1005 if (parts
->type
!= 'd') {
1006 if (mimeGetViewOption(parts
->name
)) {
1007 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1008 "alt=\"[VIEW]\"></a>",
1009 prefix
, href
, mimeGetIconURL("internal-view"));
1012 if (mimeGetDownloadOption(parts
->name
)) {
1013 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1014 "alt=\"[DOWNLOAD]\"></a>",
1015 prefix
, href
, mimeGetIconURL("internal-download"));
1019 /* construct the table row from parts. */
1020 html
= new MemBuf();
1022 html
->Printf("<tr class=\"entry\">"
1023 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1024 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1025 "<td class=\"date\">%s</td>"
1026 "<td class=\"size\">%s</td>"
1027 "<td class=\"actions\">%s%s%s%s</td>"
1030 prefix
, href
, html_quote(text
),
1033 chdir
, view
, download
, link
);
1035 ftpListPartsFree(&parts
);
1040 FtpStateData::parseListing()
1042 char *buf
= data
.readBuf
->content();
1043 char *sbuf
; /* NULL-terminated copy of termedBuf */
1050 size_t len
= data
.readBuf
->contentSize();
1053 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
1058 * We need a NULL-terminated buffer for scanning, ick
1060 sbuf
= (char *)xmalloc(len
+ 1);
1061 xstrncpy(sbuf
, buf
, len
+ 1);
1062 end
= sbuf
+ len
- 1;
1064 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1067 usable
= end
- sbuf
;
1069 debugs(9, 3, HERE
<< "usable = " << usable
);
1072 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url() );
1077 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
1079 line
= (char *)memAllocate(MEM_4K_BUF
);
1082 s
+= strspn(s
, crlf
);
1084 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1085 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
1086 linelen
= strcspn(s
, crlf
) + 1;
1094 xstrncpy(line
, s
, linelen
);
1096 debugs(9, 7, HERE
<< "{" << line
<< "}");
1098 if (!strncmp(line
, "total", 5))
1101 t
= htmlifyListEntry(line
);
1104 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
1105 listing
.append(t
->content(), t
->contentSize());
1110 debugs(9, 7, HERE
<< "Done.");
1111 data
.readBuf
->consume(usable
);
1112 memFree(line
, MEM_4K_BUF
);
1117 FtpStateData::dataDescriptor() const
1123 FtpStateData::dataComplete()
1127 /* Connection closed; transfer done. */
1129 /// Close data channel, if any, to conserve resources while we wait.
1132 /* expect the "transfer complete" message on the control socket */
1135 * Previously, this was the only place where we set the
1136 * 'buffered_ok' flag when calling scheduleReadControlReply().
1137 * It caused some problems if the FTP server returns an unexpected
1138 * status code after the data command. FtpStateData was being
1139 * deleted in the middle of dataRead().
1141 scheduleReadControlReply(0);
1145 FtpStateData::maybeReadVirginBody()
1150 if (data
.read_pending
)
1153 const int read_sz
= replyBodySpace(*data
.readBuf
, 0);
1155 debugs(11,9, HERE
<< "FTP may read up to " << read_sz
<< " bytes");
1157 if (read_sz
< 2) // see http.cc
1160 data
.read_pending
= true;
1162 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1163 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1164 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1165 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
1167 debugs(9,5,HERE
<< "queueing read on FD " << data
.fd
);
1169 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1170 entry
->delayAwareRead(data
.fd
, data
.readBuf
->space(), read_sz
,
1171 JobCallback(9, 5, Dialer
, this, FtpStateData::dataRead
));
1175 FtpStateData::dataRead(const CommIoCbParams
&io
)
1180 data
.read_pending
= false;
1182 debugs(9, 3, HERE
<< "ftpDataRead: FD " << io
.fd
<< " Read " << io
.size
<< " bytes");
1185 kb_incr(&statCounter
.server
.all
.kbytes_in
, io
.size
);
1186 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, io
.size
);
1189 if (io
.flag
== COMM_ERR_CLOSING
)
1192 assert(io
.fd
== data
.fd
);
1194 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1195 abortTransaction("entry aborted during dataRead");
1199 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1200 debugs(9,5,HERE
<< "appended " << io
.size
<< " bytes to readBuf");
1201 data
.readBuf
->appended(io
.size
);
1203 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
1204 delayId
.bytesIn(io
.size
);
1206 IOStats
.Ftp
.reads
++;
1208 for (j
= io
.size
- 1, bin
= 0; j
; bin
++)
1211 IOStats
.Ftp
.read_hist
[bin
]++;
1214 if (io
.flag
!= COMM_OK
|| io
.size
< 0) {
1215 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1216 "ftpDataRead: read error: " << xstrerr(io
.xerrno
));
1218 if (ignoreErrno(io
.xerrno
)) {
1219 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1220 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1221 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1222 commSetTimeout(io
.fd
, Config
.Timeout
.read
, timeoutCall
);
1224 maybeReadVirginBody();
1226 if (!flags
.http_header_sent
&& !fwd
->ftpPasvFailed() && flags
.pasv_supported
&& !flags
.listing
) {
1227 fwd
->dontRetry(false); /* this is a retryable error */
1228 fwd
->ftpPasvFailed(true);
1231 failed(ERR_READ_ERROR
, 0);
1232 /* failed closes ctrl.fd and frees ftpState */
1235 } else if (io
.size
== 0) {
1236 debugs(9,3, HERE
<< "Calling dataComplete() because io.size == 0");
1239 * Dangerous curves ahead. This call to dataComplete was
1240 * calling scheduleReadControlReply, handleControlReply,
1241 * and then ftpReadTransferDone. If ftpReadTransferDone
1242 * gets unexpected status code, it closes down the control
1243 * socket and our FtpStateData object gets destroyed. As
1244 * a workaround we no longer set the 'buffered_ok' flag in
1245 * the scheduleReadControlReply call.
1254 FtpStateData::processReplyBody()
1256 debugs(9, 3, HERE
<< "FtpStateData::processReplyBody starting.");
1258 if (request
->method
== METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
1263 /* Directory listings are special. They write ther own headers via the error objects */
1264 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
1265 appendSuccessHeader();
1267 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1269 * probably was aborted because content length exceeds one
1270 * of the maximum size limits.
1272 abortTransaction("entry aborted after calling appendSuccessHeader()");
1278 if (adaptationAccessCheckPending
) {
1279 debugs(9,3, HERE
<< "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1286 if (!flags
.listing
) {
1291 maybeReadVirginBody();
1293 } else if (const int csize
= data
.readBuf
->contentSize()) {
1294 writeReplyBody(data
.readBuf
->content(), csize
);
1295 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1296 data
.readBuf
->consume(csize
);
1301 maybeReadVirginBody();
1305 * Locates the FTP user:password login.
1307 * Highest to lowest priority:
1308 * - Checks URL (ftp://user:pass@domain)
1309 * - Authorization: Basic header
1310 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1312 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1314 * TODO: we might be able to do something about locating username from other sources:
1315 * ie, external ACL user=* tag or ident lookup
1317 \retval 1 if we have everything needed to complete this request.
1318 \retval 0 if something is missing.
1321 FtpStateData::checkAuth(const HttpHeader
* req_hdr
)
1325 /* default username */
1326 xstrncpy(user
, "anonymous", MAX_URL
);
1328 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1329 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1330 flags
.authenticated
= 1;
1331 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1333 /* we fail with authorization-required error later IFF the FTP server requests it */
1335 /* Test URL login syntax. Overrides any headers received. */
1336 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1338 /* name is missing. thats fatal. */
1340 fatal("FTP login parsing destroyed username info");
1342 /* name + password == success */
1346 /* Setup default FTP password settings */
1347 /* this has to be done last so that we can have a no-password case above. */
1349 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1350 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1351 flags
.tried_auth_anonymous
=1;
1353 } else if (!flags
.tried_auth_nopass
) {
1354 xstrncpy(password
, null_string
, MAX_URL
);
1355 flags
.tried_auth_nopass
=1;
1360 return 0; /* different username */
1363 static String str_type_eq
;
1365 FtpStateData::checkUrlpath()
1370 if (str_type_eq
.undefined()) //hack. String doesn't support global-static
1371 str_type_eq
="type=";
1373 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1374 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1375 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1376 request
->urlpath
.cut(t
);
1380 l
= request
->urlpath
.size();
1381 /* check for null path */
1386 flags
.need_base_href
= 1; /* Work around broken browsers */
1387 } else if (!request
->urlpath
.cmp("/%2f/")) {
1388 /* UNIX root directory */
1391 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1392 /* Directory URL, ending in / */
1398 flags
.dir_slash
= 1;
1403 FtpStateData::buildTitleUrl()
1405 title_url
= "ftp://";
1407 if (strcmp(user
, "anonymous")) {
1408 title_url
.append(user
);
1409 title_url
.append("@");
1412 title_url
.append(request
->GetHost());
1414 if (request
->port
!= urlDefaultPort(PROTO_FTP
)) {
1415 title_url
.append(":");
1416 title_url
.append(xitoa(request
->port
));
1419 title_url
.append (request
->urlpath
);
1421 base_href
= "ftp://";
1423 if (strcmp(user
, "anonymous") != 0) {
1424 base_href
.append(rfc1738_escape_part(user
));
1427 base_href
.append (":");
1428 base_href
.append(rfc1738_escape_part(password
));
1431 base_href
.append("@");
1434 base_href
.append(request
->GetHost());
1436 if (request
->port
!= urlDefaultPort(PROTO_FTP
)) {
1437 base_href
.append(":");
1438 base_href
.append(xitoa(request
->port
));
1441 base_href
.append(request
->urlpath
);
1442 base_href
.append("/");
1445 /// \ingroup ServerProtocolFTPAPI
1447 ftpStart(FwdState
* fwd
)
1449 FtpStateData
*ftpState
= new FtpStateData(fwd
);
1454 FtpStateData::start()
1456 if (!checkAuth(&request
->header
)) {
1457 /* create appropriate reply */
1458 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1459 entry
->replaceHttpReply(reply
);
1466 debugs(9, 5, HERE
<< "host=" << request
->GetHost() << ", path=" <<
1467 request
->urlpath
<< ", user=" << user
<< ", passwd=" <<
1471 ctrl
.last_command
= xstrdup("Connect to server");
1472 ctrl
.buf
= (char *)memAllocBuf(4096, &ctrl
.size
);
1474 data
.readBuf
= new MemBuf
;
1475 data
.readBuf
->init(4096, SQUID_TCP_SO_RCVBUF
);
1476 scheduleReadControlReply(0);
1479 /* ====================================================================== */
1481 /// \ingroup ServerProtocolFTPInternal
1483 escapeIAC(const char *buf
)
1487 unsigned const char *p
;
1490 for (p
= (unsigned const char *)buf
, n
= 1; *p
; n
++, p
++)
1494 ret
= (char *)xmalloc(n
);
1496 for (p
= (unsigned const char *)buf
, r
=(unsigned char *)ret
; *p
; p
++) {
1504 assert((r
- (unsigned char *)ret
) == n
);
1509 FtpStateData::writeCommand(const char *buf
)
1512 /* trace FTP protocol communications at level 2 */
1513 debugs(9, 2, "ftp<< " << buf
);
1515 if (Config
.Ftp
.telnet
)
1516 ebuf
= escapeIAC(buf
);
1518 ebuf
= xstrdup(buf
);
1520 safe_free(ctrl
.last_command
);
1522 safe_free(ctrl
.last_reply
);
1524 ctrl
.last_command
= ebuf
;
1526 if (!canSend(ctrl
.fd
)) {
1527 debugs(9, 2, HERE
<< "cannot send to closing ctrl FD " << ctrl
.fd
);
1528 // TODO: assert(ctrl.closer != NULL);
1532 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1533 AsyncCall::Pointer call
= JobCallback(9, 5,
1534 Dialer
, this, FtpStateData::ftpWriteCommandCallback
);
1535 Comm::Write(ctrl
.fd
, ctrl
.last_command
, strlen(ctrl
.last_command
), call
, NULL
);
1537 scheduleReadControlReply(0);
1541 FtpStateData::ftpWriteCommandCallback(const CommIoCbParams
&io
)
1544 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io
.size
<< " bytes");
1547 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
1548 kb_incr(&statCounter
.server
.all
.kbytes_out
, io
.size
);
1549 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, io
.size
);
1552 if (io
.flag
== COMM_ERR_CLOSING
)
1556 debugs(9, DBG_IMPORTANT
, "ftpWriteCommandCallback: FD " << io
.fd
<< ": " << xstrerr(io
.xerrno
));
1557 failed(ERR_WRITE_ERROR
, io
.xerrno
);
1558 /* failed closes ctrl.fd and frees ftpState */
1564 FtpStateData::ftpParseControlReply(char *buf
, size_t len
, int *codep
, size_t *used
)
1571 wordlist
*head
= NULL
;
1573 wordlist
**tail
= &head
;
1579 * We need a NULL-terminated buffer for scanning, ick
1581 sbuf
= (char *)xmalloc(len
+ 1);
1582 xstrncpy(sbuf
, buf
, len
+ 1);
1583 end
= sbuf
+ len
- 1;
1585 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1588 usable
= end
- sbuf
;
1590 debugs(9, 3, HERE
<< "usable = " << usable
);
1593 debugs(9, 3, HERE
<< "didn't find end of line");
1598 debugs(9, 3, HERE
<< len
<< " bytes to play with");
1601 s
+= strspn(s
, crlf
);
1603 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1607 debugs(9, 5, HERE
<< "s = {" << s
<< "}");
1609 linelen
= strcspn(s
, crlf
) + 1;
1615 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1623 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1626 list
= new wordlist();
1628 list
->key
= (char *)xmalloc(linelen
- offset
);
1630 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1632 /* trace the FTP communication chat at level 2 */
1633 debugs(9, 2, "ftp>> " << code
<< " " << list
->key
);
1640 *used
= (size_t) (s
- sbuf
);
1644 wordlistDestroy(&head
);
1654 * Looks like there are no longer anymore callers that set
1655 * buffered_ok=1. Perhaps it can be removed at some point.
1658 FtpStateData::scheduleReadControlReply(int buffered_ok
)
1660 debugs(9, 3, HERE
<< "FD " << ctrl
.fd
);
1662 if (buffered_ok
&& ctrl
.offset
> 0) {
1663 /* We've already read some reply data */
1664 handleControlReply();
1666 /* XXX What about Config.Timeout.read? */
1667 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1668 AsyncCall::Pointer reader
= JobCallback(9, 5,
1669 Dialer
, this, FtpStateData::ftpReadControlReply
);
1670 comm_read(ctrl
.fd
, ctrl
.buf
+ ctrl
.offset
, ctrl
.size
- ctrl
.offset
, reader
);
1672 * Cancel the timeout on the Data socket (if any) and
1673 * establish one on the control socket.
1677 AsyncCall::Pointer nullCall
= NULL
;
1678 commSetTimeout(data
.fd
, -1, nullCall
);
1681 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1682 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
1683 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
1685 commSetTimeout(ctrl
.fd
, Config
.Timeout
.read
, timeoutCall
);
1689 void FtpStateData::ftpReadControlReply(const CommIoCbParams
&io
)
1691 debugs(9, 3, "ftpReadControlReply: FD " << io
.fd
<< ", Read " << io
.size
<< " bytes");
1694 kb_incr(&statCounter
.server
.all
.kbytes_in
, io
.size
);
1695 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, io
.size
);
1698 if (io
.flag
== COMM_ERR_CLOSING
)
1701 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1702 abortTransaction("entry aborted during control reply read");
1706 assert(ctrl
.offset
< ctrl
.size
);
1708 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1709 fd_bytes(io
.fd
, io
.size
, FD_READ
);
1712 if (io
.flag
!= COMM_OK
|| io
.size
< 0) {
1713 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1714 "ftpReadControlReply: read error: " << xstrerr(io
.xerrno
));
1716 if (ignoreErrno(io
.xerrno
)) {
1717 scheduleReadControlReply(0);
1719 failed(ERR_READ_ERROR
, io
.xerrno
);
1720 /* failed closes ctrl.fd and frees ftpState */
1728 if (entry
->store_status
== STORE_PENDING
) {
1729 failed(ERR_FTP_FAILURE
, 0);
1730 /* failed closes ctrl.fd and frees ftpState */
1734 /* XXX this may end up having to be serverComplete() .. */
1735 abortTransaction("zero control reply read");
1739 unsigned int len
=io
.size
+ ctrl
.offset
;
1741 assert(len
<= ctrl
.size
);
1742 handleControlReply();
1746 FtpStateData::handleControlReply()
1749 size_t bytes_used
= 0;
1750 wordlistDestroy(&ctrl
.message
);
1751 ctrl
.message
= ftpParseControlReply(ctrl
.buf
,
1752 ctrl
.offset
, &ctrl
.replycode
, &bytes_used
);
1754 if (ctrl
.message
== NULL
) {
1755 /* didn't get complete reply yet */
1757 if (ctrl
.offset
== ctrl
.size
) {
1758 ctrl
.buf
= (char *)memReallocBuf(ctrl
.buf
, ctrl
.size
<< 1, &ctrl
.size
);
1761 scheduleReadControlReply(0);
1763 } else if (ctrl
.offset
== bytes_used
) {
1764 /* used it all up */
1767 /* Got some data past the complete reply */
1768 assert(bytes_used
< ctrl
.offset
);
1769 ctrl
.offset
-= bytes_used
;
1770 xmemmove(ctrl
.buf
, ctrl
.buf
+ bytes_used
,
1774 /* Move the last line of the reply message to ctrl.last_reply */
1775 for (W
= &ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1776 safe_free(ctrl
.last_reply
);
1778 ctrl
.last_reply
= xstrdup((*W
)->key
);
1782 /* Copy the rest of the message to cwd_message to be printed in
1786 for (wordlist
*w
= ctrl
.message
; w
; w
= w
->next
) {
1787 cwd_message
.append('\n');
1788 cwd_message
.append(w
->key
);
1792 debugs(9, 3, HERE
<< "state=" << state
<< ", code=" << ctrl
.replycode
);
1794 FTP_SM_FUNCS
[state
] (this);
1797 /* ====================================================================== */
1799 /// \ingroup ServerProtocolFTPInternal
1801 ftpReadWelcome(FtpStateData
* ftpState
)
1803 int code
= ftpState
->ctrl
.replycode
;
1806 if (ftpState
->flags
.pasv_only
)
1807 ftpState
->login_att
++;
1809 /* Dont retry if the FTP server accepted the connection */
1810 ftpState
->fwd
->dontRetry(true);
1813 if (ftpState
->ctrl
.message
) {
1814 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1815 ftpState
->flags
.skip_whitespace
= 1;
1818 ftpSendUser(ftpState
);
1819 } else if (code
== 120) {
1820 if (NULL
!= ftpState
->ctrl
.message
)
1821 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1830 * Translate FTP login failure into HTTP error
1831 * this is an attmpt to get the 407 message to show up outside Squid.
1832 * its NOT a general failure. But a correct FTP response type.
1835 FtpStateData::loginFailed()
1837 ErrorState
*err
= NULL
;
1838 const char *command
, *reply
;
1840 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1841 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1842 // 421/426 - Service Overload - retry permitted.
1843 err
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
1844 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1845 // 43x - Invalid or Credential Error - retry challenge required.
1846 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
1847 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1848 // 53x - Credentials Missing - retry challenge required
1849 if (password_url
) // but they were in the URI! major fail.
1850 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
1852 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
1856 // any other problems are general falures.
1862 err
->ftp
.server_msg
= ctrl
.message
;
1864 ctrl
.message
= NULL
;
1867 command
= old_request
;
1869 command
= ctrl
.last_command
;
1871 if (command
&& strncmp(command
, "PASS", 4) == 0)
1872 command
= "PASS <yourpassword>";
1877 reply
= ctrl
.last_reply
;
1880 err
->ftp
.request
= xstrdup(command
);
1883 err
->ftp
.reply
= xstrdup(reply
);
1886 HttpReply
*newrep
= err
->BuildHttpReply();
1887 errorStateFree(err
);
1888 /* add Authenticate header */
1889 newrep
->header
.putAuth("Basic", ftpRealm());
1891 // add it to the store entry for response....
1892 entry
->replaceHttpReply(newrep
);
1897 FtpStateData::ftpRealm()
1899 static char realm
[8192];
1901 /* This request is not fully authenticated */
1903 snprintf(realm
, 8192, "FTP %s unknown", user
);
1904 } else if (request
->port
== 21) {
1905 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1907 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1912 /// \ingroup ServerProtocolFTPInternal
1914 ftpSendUser(FtpStateData
* ftpState
)
1916 /* check the server control channel is still available */
1917 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1920 if (ftpState
->proxy_host
!= NULL
)
1921 snprintf(cbuf
, 1024, "USER %s@%s\r\n",
1923 ftpState
->request
->GetHost());
1925 snprintf(cbuf
, 1024, "USER %s\r\n", ftpState
->user
);
1927 ftpState
->writeCommand(cbuf
);
1929 ftpState
->state
= SENT_USER
;
1932 /// \ingroup ServerProtocolFTPInternal
1934 ftpReadUser(FtpStateData
* ftpState
)
1936 int code
= ftpState
->ctrl
.replycode
;
1940 ftpReadPass(ftpState
);
1941 } else if (code
== 331) {
1942 ftpSendPass(ftpState
);
1944 ftpState
->loginFailed();
1948 /// \ingroup ServerProtocolFTPInternal
1950 ftpSendPass(FtpStateData
* ftpState
)
1952 /* check the server control channel is still available */
1953 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
1956 snprintf(cbuf
, 1024, "PASS %s\r\n", ftpState
->password
);
1957 ftpState
->writeCommand(cbuf
);
1958 ftpState
->state
= SENT_PASS
;
1961 /// \ingroup ServerProtocolFTPInternal
1963 ftpReadPass(FtpStateData
* ftpState
)
1965 int code
= ftpState
->ctrl
.replycode
;
1966 debugs(9, 3, HERE
<< "code=" << code
);
1969 ftpSendType(ftpState
);
1971 ftpState
->loginFailed();
1975 /// \ingroup ServerProtocolFTPInternal
1977 ftpSendType(FtpStateData
* ftpState
)
1980 const char *filename
;
1983 /* check the server control channel is still available */
1984 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
1988 * Ref section 3.2.2 of RFC 1738
1990 mode
= ftpState
->typecode
;
2005 if (ftpState
->flags
.isdir
) {
2008 t
= ftpState
->request
->urlpath
.rpos('/');
2009 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
2010 mode
= mimeGetTransferMode(filename
);
2017 ftpState
->flags
.binary
= 1;
2019 ftpState
->flags
.binary
= 0;
2021 snprintf(cbuf
, 1024, "TYPE %c\r\n", mode
);
2023 ftpState
->writeCommand(cbuf
);
2025 ftpState
->state
= SENT_TYPE
;
2028 /// \ingroup ServerProtocolFTPInternal
2030 ftpReadType(FtpStateData
* ftpState
)
2032 int code
= ftpState
->ctrl
.replycode
;
2038 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2045 p
+= strcspn(p
, "/");
2050 rfc1738_unescape(d
);
2053 wordlistAdd(&ftpState
->pathcomps
, d
);
2058 if (ftpState
->pathcomps
)
2059 ftpTraverseDirectory(ftpState
);
2061 ftpListDir(ftpState
);
2067 /// \ingroup ServerProtocolFTPInternal
2069 ftpTraverseDirectory(FtpStateData
* ftpState
)
2072 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
2074 safe_free(ftpState
->dirpath
);
2075 ftpState
->dirpath
= ftpState
->filepath
;
2076 ftpState
->filepath
= NULL
;
2080 if (ftpState
->pathcomps
== NULL
) {
2081 debugs(9, 3, HERE
<< "the final component was a directory");
2082 ftpListDir(ftpState
);
2086 /* Go to next path component */
2087 w
= ftpState
->pathcomps
;
2089 ftpState
->filepath
= w
->key
;
2091 ftpState
->pathcomps
= w
->next
;
2095 /* Check if we are to CWD or RETR */
2096 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
2097 ftpSendCwd(ftpState
);
2099 debugs(9, 3, HERE
<< "final component is probably a file");
2100 ftpGetFile(ftpState
);
2105 /// \ingroup ServerProtocolFTPInternal
2107 ftpSendCwd(FtpStateData
* ftpState
)
2111 /* check the server control channel is still available */
2112 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
2117 path
= ftpState
->filepath
;
2119 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
2120 ftpState
->flags
.no_dotdot
= 1;
2122 ftpState
->flags
.no_dotdot
= 0;
2125 snprintf(cbuf
, 1024, "CWD %s\r\n", path
);
2127 ftpState
->writeCommand(cbuf
);
2129 ftpState
->state
= SENT_CWD
;
2132 /// \ingroup ServerProtocolFTPInternal
2134 ftpReadCwd(FtpStateData
* ftpState
)
2136 int code
= ftpState
->ctrl
.replycode
;
2139 if (code
>= 200 && code
< 300) {
2143 /* Reset cwd_message to only include the last message */
2144 ftpState
->cwd_message
.reset("");
2145 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
2146 ftpState
->cwd_message
.append(' ');
2147 ftpState
->cwd_message
.append(w
->key
);
2149 ftpState
->ctrl
.message
= NULL
;
2151 /* Continue to traverse the path */
2152 ftpTraverseDirectory(ftpState
);
2156 if (!ftpState
->flags
.put
)
2159 ftpSendMkdir(ftpState
);
2163 /// \ingroup ServerProtocolFTPInternal
2165 ftpSendMkdir(FtpStateData
* ftpState
)
2169 /* check the server control channel is still available */
2170 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
2173 path
= ftpState
->filepath
;
2174 debugs(9, 3, HERE
<< "with path=" << path
);
2175 snprintf(cbuf
, 1024, "MKD %s\r\n", path
);
2176 ftpState
->writeCommand(cbuf
);
2177 ftpState
->state
= SENT_MKDIR
;
2180 /// \ingroup ServerProtocolFTPInternal
2182 ftpReadMkdir(FtpStateData
* ftpState
)
2184 char *path
= ftpState
->filepath
;
2185 int code
= ftpState
->ctrl
.replycode
;
2187 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
2189 if (code
== 257) { /* success */
2190 ftpSendCwd(ftpState
);
2191 } else if (code
== 550) { /* dir exists */
2193 if (ftpState
->flags
.put_mkdir
) {
2194 ftpState
->flags
.put_mkdir
= 1;
2195 ftpSendCwd(ftpState
);
2197 ftpSendReply(ftpState
);
2199 ftpSendReply(ftpState
);
2202 /// \ingroup ServerProtocolFTPInternal
2204 ftpGetFile(FtpStateData
* ftpState
)
2206 assert(*ftpState
->filepath
!= '\0');
2207 ftpState
->flags
.isdir
= 0;
2208 ftpSendMdtm(ftpState
);
2211 /// \ingroup ServerProtocolFTPInternal
2213 ftpListDir(FtpStateData
* ftpState
)
2215 if (ftpState
->flags
.dir_slash
) {
2216 debugs(9, 3, HERE
<< "Directory path did not end in /");
2217 ftpState
->title_url
.append("/");
2218 ftpState
->flags
.isdir
= 1;
2221 ftpSendPassive(ftpState
);
2224 /// \ingroup ServerProtocolFTPInternal
2226 ftpSendMdtm(FtpStateData
* ftpState
)
2228 /* check the server control channel is still available */
2229 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
2232 assert(*ftpState
->filepath
!= '\0');
2233 snprintf(cbuf
, 1024, "MDTM %s\r\n", ftpState
->filepath
);
2234 ftpState
->writeCommand(cbuf
);
2235 ftpState
->state
= SENT_MDTM
;
2238 /// \ingroup ServerProtocolFTPInternal
2240 ftpReadMdtm(FtpStateData
* ftpState
)
2242 int code
= ftpState
->ctrl
.replycode
;
2246 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
2248 } else if (code
< 0) {
2253 ftpSendSize(ftpState
);
2256 /// \ingroup ServerProtocolFTPInternal
2258 ftpSendSize(FtpStateData
* ftpState
)
2260 /* check the server control channel is still available */
2261 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
2264 /* Only send SIZE for binary transfers. The returned size
2265 * is useless on ASCII transfers */
2267 if (ftpState
->flags
.binary
) {
2268 assert(ftpState
->filepath
!= NULL
);
2269 assert(*ftpState
->filepath
!= '\0');
2270 snprintf(cbuf
, 1024, "SIZE %s\r\n", ftpState
->filepath
);
2271 ftpState
->writeCommand(cbuf
);
2272 ftpState
->state
= SENT_SIZE
;
2274 /* Skip to next state no non-binary transfers */
2275 ftpSendPassive(ftpState
);
2278 /// \ingroup ServerProtocolFTPInternal
2280 ftpReadSize(FtpStateData
* ftpState
)
2282 int code
= ftpState
->ctrl
.replycode
;
2287 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
2289 if (ftpState
->theSize
== 0) {
2290 debugs(9, 2, "SIZE reported " <<
2291 ftpState
->ctrl
.last_reply
<< " on " <<
2292 ftpState
->title_url
);
2293 ftpState
->theSize
= -1;
2295 } else if (code
< 0) {
2300 ftpSendPassive(ftpState
);
2304 \ingroup ServerProtocolFTPInternal
2307 ftpReadEPSV(FtpStateData
* ftpState
)
2309 int code
= ftpState
->ctrl
.replycode
;
2310 char h1
, h2
, h3
, h4
;
2313 Ip::Address ipa_remote
;
2314 int fd
= ftpState
->data
.fd
;
2318 if (code
!= 229 && code
!= 522) {
2320 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2321 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2322 * Its okay to re-send EPSV 1/2 but nothing else. */
2323 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ". Wrong accept code for EPSV");
2325 debugs(9, 2, "EPSV not supported by remote end");
2326 ftpState
->state
= SENT_EPSV_1
; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2328 ftpSendPassive(ftpState
);
2333 /* server response with list of supported methods */
2334 /* 522 Network protocol not supported, use (1) */
2335 /* 522 Network protocol not supported, use (1,2) */
2336 /* 522 Network protocol not supported, use (2) */
2337 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
2338 * which means close data + control without self-destructing and re-open from scratch. */
2339 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2340 buf
= ftpState
->ctrl
.last_reply
;
2341 while (buf
!= NULL
&& *buf
!= '\0' && *buf
!= '\n' && *buf
!= '(') ++buf
;
2342 if (buf
!= NULL
&& *buf
== '\n') ++buf
;
2344 if (buf
== NULL
|| *buf
== '\0') {
2345 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2346 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ". 522 error missing protocol negotiation hints");
2347 ftpSendPassive(ftpState
);
2348 } else if (strcmp(buf
, "(1)") == 0) {
2349 ftpState
->state
= SENT_EPSV_2
; /* simulate having sent and failed EPSV 2 */
2350 ftpSendPassive(ftpState
);
2351 } else if (strcmp(buf
, "(2)") == 0) {
2352 if (Ip::EnableIpv6
) {
2353 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2354 if (ftpState
->state
== SENT_EPSV_2
) {
2355 ftpSendEPRT(ftpState
);
2357 /* or try the next Passive mode down the chain. */
2358 ftpSendPassive(ftpState
);
2361 /* Server only accept EPSV in IPv6 traffic. */
2362 ftpState
->state
= SENT_EPSV_1
; /* simulate having sent and failed EPSV 1 */
2363 ftpSendPassive(ftpState
);
2366 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2367 debugs(9, DBG_IMPORTANT
, "WARNING: Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< " sent unknown protocol negotiation hint: " << buf
);
2368 ftpSendPassive(ftpState
);
2373 /* 229 Entering Extended Passive Mode (|||port|) */
2374 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2375 debugs(9, 5, "scanning: " << ftpState
->ctrl
.last_reply
);
2377 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "(");
2379 n
= sscanf(buf
, "(%c%c%c%hu%c)", &h1
, &h2
, &h3
, &port
, &h4
);
2381 if (h1
!= h2
|| h1
!= h3
|| h1
!= h4
) {
2382 debugs(9, DBG_IMPORTANT
, "Invalid EPSV reply from " <<
2383 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2384 ftpState
->ctrl
.last_reply
);
2386 ftpSendPassive(ftpState
);
2391 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2392 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2393 ftpState
->ctrl
.last_reply
);
2395 ftpSendPassive(ftpState
);
2399 if (Config
.Ftp
.sanitycheck
) {
2401 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2402 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2403 ftpState
->ctrl
.last_reply
);
2405 ftpSendPassive(ftpState
);
2410 ftpState
->data
.port
= port
;
2412 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.fd
].ipaddr
);
2414 safe_free(ftpState
->ctrl
.last_command
);
2416 safe_free(ftpState
->ctrl
.last_reply
);
2418 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2420 debugs(9, 3, HERE
<< "connecting to " << ftpState
->data
.host
<< ", port " << ftpState
->data
.port
);
2422 commConnectStart(fd
, ftpState
->data
.host
, port
, FtpStateData::ftpPasvCallback
, ftpState
);
2425 /** \ingroup ServerProtocolFTPInternal
2427 * Send Passive connection request.
2428 * Default method is to use modern EPSV request.
2429 * The failover mechanism should check for previous state and re-call with alternates on failure.
2432 ftpSendPassive(FtpStateData
* ftpState
)
2435 struct addrinfo
*AI
= NULL
;
2437 /** Checks the server control channel is still available before running. */
2438 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
2444 * Checks for EPSV ALL special conditions:
2445 * If enabled to be sent, squid MUST NOT request any other connect methods.
2446 * If 'ALL' is sent and fails the entire FTP Session fails.
2447 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
2448 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
&& ftpState
->state
== SENT_EPSV_1
) {
2449 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2455 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
2456 if (ftpState
->request
->method
== METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
2457 ftpState
->processHeadResponse(); // may call serverComplete
2461 /// Closes any old FTP-Data connection which may exist. */
2462 ftpState
->data
.close();
2465 * Checks for previous EPSV/PASV failures on this server/session.
2466 * Diverts to EPRT immediately if they are not working. */
2467 if (!ftpState
->flags
.pasv_supported
) {
2468 ftpSendEPRT(ftpState
);
2473 * Locates the Address of the remote server. */
2474 addr
.InitAddrInfo(AI
);
2476 if (getsockname(ftpState
->ctrl
.fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2477 /** If it cannot be located the FTP Session is killed. */
2478 addr
.FreeAddrInfo(AI
);
2479 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << ftpState
->ctrl
.fd
<< ",'" << addr
<< "',...): " << xstrerror());
2485 addr
.FreeAddrInfo(AI
);
2488 * Send EPSV (ALL,2,1) or PASV on the control channel.
2490 * - EPSV ALL is used if enabled.
2491 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2492 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
2493 * - PASV is used if EPSV 1 fails.
2495 switch (ftpState
->state
) {
2496 case SENT_EPSV_ALL
: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2497 ftpState
->flags
.epsv_all_sent
= true;
2498 if (addr
.IsIPv6()) {
2499 debugs(9, 5, HERE
<< "FTP Channel is IPv6 (" << addr
<< ") attempting EPSV 2 after EPSV ALL has failed.");
2500 snprintf(cbuf
, 1024, "EPSV 2\r\n");
2501 ftpState
->state
= SENT_EPSV_2
;
2504 // else fall through to skip EPSV 2
2506 case SENT_EPSV_2
: /* EPSV IPv6 failed. Try EPSV IPv4 */
2507 if (addr
.IsIPv4()) {
2508 debugs(9, 5, HERE
<< "FTP Channel is IPv4 (" << addr
<< ") attempting EPSV 1 after EPSV ALL has failed.");
2509 snprintf(cbuf
, 1024, "EPSV 1\r\n");
2510 ftpState
->state
= SENT_EPSV_1
;
2512 } else if (ftpState
->flags
.epsv_all_sent
) {
2513 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2517 // else fall through to skip EPSV 1
2519 case SENT_EPSV_1
: /* EPSV options exhausted. Try PASV now. */
2520 debugs(9, 5, HERE
<< "FTP Channel (" << addr
<< ") rejects EPSV connection attempts. Trying PASV instead.");
2521 snprintf(cbuf
, 1024, "PASV\r\n");
2522 ftpState
->state
= SENT_PASV
;
2526 if (!Config
.Ftp
.epsv
) {
2527 debugs(9, 5, HERE
<< "EPSV support manually disabled. Sending PASV for FTP Channel (" << addr
<<")");
2528 snprintf(cbuf
, 1024, "PASV\r\n");
2529 ftpState
->state
= SENT_PASV
;
2530 } else if (Config
.Ftp
.epsv_all
) {
2531 debugs(9, 5, HERE
<< "EPSV ALL manually enabled. Attempting with FTP Channel (" << addr
<<")");
2532 snprintf(cbuf
, 1024, "EPSV ALL\r\n");
2533 ftpState
->state
= SENT_EPSV_ALL
;
2534 /* block other non-EPSV connections being attempted */
2535 ftpState
->flags
.epsv_all_sent
= true;
2537 if (addr
.IsIPv6()) {
2538 debugs(9, 5, HERE
<< "FTP Channel (" << addr
<< "). Sending default EPSV 2");
2539 snprintf(cbuf
, 1024, "EPSV 2\r\n");
2540 ftpState
->state
= SENT_EPSV_2
;
2542 if (addr
.IsIPv4()) {
2543 debugs(9, 5, HERE
<< "Channel (" << addr
<<"). Sending default EPSV 1");
2544 snprintf(cbuf
, 1024, "EPSV 1\r\n");
2545 ftpState
->state
= SENT_EPSV_1
;
2551 /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */
2553 int fd
= comm_open(SOCK_STREAM
,
2557 ftpState
->entry
->url());
2559 debugs(9, 3, HERE
<< "Unconnected data socket created on FD " << fd
<< " from " << addr
);
2566 ftpState
->data
.opened(fd
, ftpState
->dataCloser());
2567 ftpState
->writeCommand(cbuf
);
2570 * ugly hack for ftp servers like ftp.netscape.com that sometimes
2571 * dont acknowledge PASV commands.
2573 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2574 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
2575 TimeoutDialer
, ftpState
, FtpStateData::ftpTimeout
);
2577 commSetTimeout(ftpState
->data
.fd
, 15, timeoutCall
);
2581 FtpStateData::processHeadResponse()
2583 debugs(9, 5, HERE
<< "handling HEAD response");
2585 appendSuccessHeader();
2588 * On rare occasions I'm seeing the entry get aborted after
2589 * ftpReadControlReply() and before here, probably when
2590 * trying to write to the client.
2592 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2593 abortTransaction("entry aborted while processing HEAD");
2598 if (adaptationAccessCheckPending
) {
2599 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
2604 // processReplyBody calls serverComplete() since there is no body
2608 /// \ingroup ServerProtocolFTPInternal
2610 ftpReadPasv(FtpStateData
* ftpState
)
2612 int code
= ftpState
->ctrl
.replycode
;
2617 Ip::Address ipa_remote
;
2618 int fd
= ftpState
->data
.fd
;
2620 LOCAL_ARRAY(char, ipaddr
, 1024);
2624 debugs(9, 2, "PASV not supported by remote end");
2625 ftpSendEPRT(ftpState
);
2629 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2630 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2631 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2633 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "0123456789");
2635 n
= sscanf(buf
, "%d,%d,%d,%d,%d,%d", &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
2637 if (n
!= 6 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
2638 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2639 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2640 ftpState
->ctrl
.last_reply
);
2642 ftpSendEPRT(ftpState
);
2646 snprintf(ipaddr
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
2648 ipa_remote
= ipaddr
;
2650 if ( ipa_remote
.IsAnyAddr() ) {
2651 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2652 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2653 ftpState
->ctrl
.last_reply
);
2655 ftpSendEPRT(ftpState
);
2659 port
= ((p1
<< 8) + p2
);
2662 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2663 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2664 ftpState
->ctrl
.last_reply
);
2666 ftpSendEPRT(ftpState
);
2670 if (Config
.Ftp
.sanitycheck
) {
2672 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2673 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2674 ftpState
->ctrl
.last_reply
);
2676 ftpSendEPRT(ftpState
);
2681 ftpState
->data
.port
= port
;
2683 if (Config
.Ftp
.sanitycheck
)
2684 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.fd
].ipaddr
);
2686 ftpState
->data
.host
= xstrdup(ipaddr
);
2688 safe_free(ftpState
->ctrl
.last_command
);
2690 safe_free(ftpState
->ctrl
.last_reply
);
2692 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2694 debugs(9, 3, HERE
<< "connecting to " << ftpState
->data
.host
<< ", port " << ftpState
->data
.port
);
2696 commConnectStart(fd
, ipaddr
, port
, FtpStateData::ftpPasvCallback
, ftpState
);
2700 FtpStateData::ftpPasvCallback(int fd
, const DnsLookupDetails
&dns
, comm_err_t status
, int xerrno
, void *data
)
2702 FtpStateData
*ftpState
= (FtpStateData
*)data
;
2704 ftpState
->request
->recordLookup(dns
);
2706 if (status
!= COMM_OK
) {
2707 debugs(9, 2, HERE
<< "Failed to connect. Retrying without PASV.");
2708 ftpState
->fwd
->dontRetry(false); /* this is a retryable error */
2709 ftpState
->fwd
->ftpPasvFailed(true);
2710 ftpState
->failed(ERR_NONE
, 0);
2711 /* failed closes ctrl.fd and frees ftpState */
2715 ftpRestOrList(ftpState
);
2718 /// \ingroup ServerProtocolFTPInternal
2720 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
2724 struct addrinfo
*AI
= NULL
;
2728 /// Close old data channels, if any. We may open a new one below.
2729 ftpState
->data
.close();
2732 * Set up a listen socket on the same local address as the
2733 * control connection.
2736 addr
.InitAddrInfo(AI
);
2738 x
= getsockname(ftpState
->ctrl
.fd
, AI
->ai_addr
, &AI
->ai_addrlen
);
2742 addr
.FreeAddrInfo(AI
);
2745 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << ftpState
->ctrl
.fd
<< ",..): " << xstrerror());
2750 * REUSEADDR is needed in fallback mode, since the same port is
2751 * used for both control and data.
2754 setsockopt(ftpState
->ctrl
.fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
2756 /* if not running in fallback mode a new port needs to be retrieved */
2760 fd
= comm_open(SOCK_STREAM
,
2763 COMM_NONBLOCKING
| (fallback
? COMM_REUSEADDR
: 0),
2764 ftpState
->entry
->url());
2765 debugs(9, 3, HERE
<< "Unconnected data socket created on FD " << fd
);
2768 debugs(9, DBG_CRITICAL
, HERE
<< "comm_open failed");
2772 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
2773 AsyncCall::Pointer acceptCall
= JobCallback(11, 5,
2774 acceptDialer
, ftpState
, FtpStateData::ftpAcceptDataConnection
);
2775 ftpState
->data
.listener
= new Comm::ListenStateData(fd
, acceptCall
, false);
2777 if (!ftpState
->data
.listener
|| ftpState
->data
.listener
->errcode
!= 0) {
2782 ftpState
->data
.opened(fd
, ftpState
->dataCloser());
2783 ftpState
->data
.port
= comm_local_port(fd
);
2784 ftpState
->data
.host
= NULL
;
2788 /// \ingroup ServerProtocolFTPInternal
2790 ftpSendPORT(FtpStateData
* ftpState
)
2794 struct addrinfo
*AI
= NULL
;
2795 unsigned char *addrptr
;
2796 unsigned char *portptr
;
2798 /* check the server control channel is still available */
2799 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
2802 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2803 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2808 ftpState
->flags
.pasv_supported
= 0;
2809 fd
= ftpOpenListenSocket(ftpState
, 0);
2810 ipa
.InitAddrInfo(AI
);
2812 if (getsockname(fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2813 ipa
.FreeAddrInfo(AI
);
2814 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << fd
<< ",..): " << xstrerror());
2816 /* XXX Need to set error message */
2821 if ( AI
->ai_addrlen
!= sizeof(struct sockaddr_in
) ) {
2822 ipa
.FreeAddrInfo(AI
);
2823 /* IPv6 CANNOT send PORT command. */
2824 /* we got here by attempting and failing an EPRT */
2825 /* using the same reply code should simulate a PORT failure */
2826 ftpReadPORT(ftpState
);
2830 addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
2831 portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
2832 snprintf(cbuf
, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
2833 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
2834 portptr
[0], portptr
[1]);
2835 ftpState
->writeCommand(cbuf
);
2836 ftpState
->state
= SENT_PORT
;
2838 ipa
.FreeAddrInfo(AI
);
2841 /// \ingroup ServerProtocolFTPInternal
2843 ftpReadPORT(FtpStateData
* ftpState
)
2845 int code
= ftpState
->ctrl
.replycode
;
2849 /* Fall back on using the same port as the control connection */
2850 debugs(9, 3, "PORT not supported by remote end");
2851 ftpOpenListenSocket(ftpState
, 1);
2854 ftpRestOrList(ftpState
);
2857 /// \ingroup ServerProtocolFTPInternal
2859 ftpSendEPRT(FtpStateData
* ftpState
)
2863 struct addrinfo
*AI
= NULL
;
2864 char buf
[MAX_IPSTRLEN
];
2866 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2867 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2872 ftpState
->flags
.pasv_supported
= 0;
2873 fd
= ftpOpenListenSocket(ftpState
, 0);
2875 Ip::Address::InitAddrInfo(AI
);
2877 if (getsockname(fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2878 Ip::Address::FreeAddrInfo(AI
);
2879 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << fd
<< ",..): " << xstrerror());
2881 /* XXX Need to set error message */
2888 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2889 /* Which can be used by EITHER protocol. */
2890 snprintf(cbuf
, 1024, "EPRT |%d|%s|%d|\r\n",
2891 ( addr
.IsIPv6() ? 2 : 1 ),
2892 addr
.NtoA(buf
,MAX_IPSTRLEN
),
2895 ftpState
->writeCommand(cbuf
);
2896 ftpState
->state
= SENT_EPRT
;
2898 Ip::Address::FreeAddrInfo(AI
);
2902 ftpReadEPRT(FtpStateData
* ftpState
)
2904 int code
= ftpState
->ctrl
.replycode
;
2908 /* Failover to attempting old PORT command. */
2909 debugs(9, 3, "EPRT not supported by remote end");
2910 ftpSendPORT(ftpState
);
2914 ftpRestOrList(ftpState
);
2918 \ingroup ServerProtocolFTPInternal
2920 * "read" handler to accept FTP data connections.
2922 \param io comm accept(2) callback parameters
2924 void FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
2926 char ntoapeer
[MAX_IPSTRLEN
];
2927 debugs(9, 3, "ftpAcceptDataConnection");
2929 // one connection accepted. the handler has stopped listening. drop our local pointer to it.
2930 data
.listener
= NULL
;
2932 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2933 abortTransaction("entry aborted when accepting data conn");
2938 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2939 * made by the remote client which is connected to the FTP control socket.
2940 * This prevents third-party hacks, but also third-party load balancing handshakes.
2942 if (Config
.Ftp
.sanitycheck
) {
2943 io
.details
.peer
.NtoA(ntoapeer
,MAX_IPSTRLEN
);
2945 if (strcmp(fd_table
[ctrl
.fd
].ipaddr
, ntoapeer
) != 0) {
2946 debugs(9, DBG_IMPORTANT
,
2947 "FTP data connection from unexpected server (" <<
2948 io
.details
.peer
<< "), expecting " <<
2949 fd_table
[ctrl
.fd
].ipaddr
);
2951 /* close the bad soures connection down ASAP. */
2954 /* we are ony accepting once, so need to re-open the listener socket. */
2955 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
2956 AsyncCall::Pointer acceptCall
= JobCallback(11, 5,
2957 acceptDialer
, this, FtpStateData::ftpAcceptDataConnection
);
2958 data
.listener
= new Comm::ListenStateData(data
.fd
, acceptCall
, false);
2963 if (io
.flag
!= COMM_OK
) {
2964 debugs(9, DBG_IMPORTANT
, "ftpHandleDataAccept: FD " << io
.nfd
<< ": " << xstrerr(io
.xerrno
));
2965 /** \todo XXX Need to set error message */
2971 * Replace the Listen socket with the accepted data socket */
2973 data
.opened(io
.nfd
, dataCloser());
2974 data
.port
= io
.details
.peer
.GetPort();
2975 io
.details
.peer
.NtoA(data
.host
,SQUIDHOSTNAMELEN
);
2977 debugs(9, 3, "ftpAcceptDataConnection: Connected data socket on " <<
2978 "FD " << io
.nfd
<< " to " << io
.details
.peer
<< " FD table says: " <<
2979 "ctrl-peer= " << fd_table
[ctrl
.fd
].ipaddr
<< ", " <<
2980 "data-peer= " << fd_table
[data
.fd
].ipaddr
);
2983 AsyncCall::Pointer nullCall
= NULL
;
2984 commSetTimeout(ctrl
.fd
, -1, nullCall
);
2986 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2987 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
2988 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
2989 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
2991 /*\todo XXX We should have a flag to track connect state...
2992 * host NULL -> not connected, port == local port
2993 * host set -> connected, port == remote port
2995 /* Restart state (SENT_NLST/LIST/RETR) */
2996 FTP_SM_FUNCS
[state
] (this);
2999 /// \ingroup ServerProtocolFTPInternal
3001 ftpRestOrList(FtpStateData
* ftpState
)
3005 if (ftpState
->typecode
== 'D') {
3006 ftpState
->flags
.isdir
= 1;
3008 if (ftpState
->flags
.put
) {
3009 ftpSendMkdir(ftpState
); /* PUT name;type=d */
3011 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3013 } else if (ftpState
->flags
.put
) {
3014 ftpSendStor(ftpState
);
3015 } else if (ftpState
->flags
.isdir
)
3016 ftpSendList(ftpState
);
3017 else if (ftpState
->restartable())
3018 ftpSendRest(ftpState
);
3020 ftpSendRetr(ftpState
);
3023 /// \ingroup ServerProtocolFTPInternal
3025 ftpSendStor(FtpStateData
* ftpState
)
3027 /* check the server control channel is still available */
3028 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
3033 if (ftpState
->filepath
!= NULL
) {
3034 /* Plain file upload */
3035 snprintf(cbuf
, 1024, "STOR %s\r\n", ftpState
->filepath
);
3036 ftpState
->writeCommand(cbuf
);
3037 ftpState
->state
= SENT_STOR
;
3038 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
3039 /* File upload without a filename. use STOU to generate one */
3040 snprintf(cbuf
, 1024, "STOU\r\n");
3041 ftpState
->writeCommand(cbuf
);
3042 ftpState
->state
= SENT_STOR
;
3044 /* No file to transfer. Only create directories if needed */
3045 ftpSendReply(ftpState
);
3049 /// \ingroup ServerProtocolFTPInternal
3050 /// \deprecated use ftpState->readStor() instead.
3052 ftpReadStor(FtpStateData
* ftpState
)
3054 ftpState
->readStor();
3057 void FtpStateData::readStor()
3059 int code
= ctrl
.replycode
;
3062 if (code
== 125 || (code
== 150 && data
.host
)) {
3063 if (!startRequestBodyFlow()) { // register to receive body data
3069 * When client status is 125, or 150 without a hostname, Begin data transfer. */
3070 debugs(9, 3, HERE
<< "starting data transfer");
3071 sendMoreRequestBody();
3073 * Cancel the timeout on the Control socket and
3074 * establish one on the data socket.
3076 AsyncCall::Pointer nullCall
= NULL
;
3077 commSetTimeout(ctrl
.fd
, -1, nullCall
);
3079 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3080 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
3081 TimeoutDialer
, this, FtpStateData::ftpTimeout
);
3083 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3085 state
= WRITING_DATA
;
3086 debugs(9, 3, HERE
<< "writing data channel");
3087 } else if (code
== 150) {
3089 * When client code is 150 with a hostname, Accept data channel. */
3090 debugs(9, 3, "ftpReadStor: accepting data channel");
3091 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3092 AsyncCall::Pointer acceptCall
= JobCallback(11, 5,
3093 acceptDialer
, this, FtpStateData::ftpAcceptDataConnection
);
3095 data
.listener
= new Comm::ListenStateData(data
.fd
, acceptCall
, false);
3097 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
3102 /// \ingroup ServerProtocolFTPInternal
3104 ftpSendRest(FtpStateData
* ftpState
)
3106 /* check the server control channel is still available */
3107 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
3112 snprintf(cbuf
, 1024, "REST %"PRId64
"\r\n", ftpState
->restart_offset
);
3113 ftpState
->writeCommand(cbuf
);
3114 ftpState
->state
= SENT_REST
;
3118 FtpStateData::restartable()
3120 if (restart_offset
> 0)
3123 if (!request
->range
)
3132 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
3134 if (desired_offset
<= 0)
3137 if (desired_offset
>= theSize
)
3140 restart_offset
= desired_offset
;
3144 /// \ingroup ServerProtocolFTPInternal
3146 ftpReadRest(FtpStateData
* ftpState
)
3148 int code
= ftpState
->ctrl
.replycode
;
3150 assert(ftpState
->restart_offset
> 0);
3153 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
3154 ftpSendRetr(ftpState
);
3155 } else if (code
> 0) {
3156 debugs(9, 3, HERE
<< "REST not supported");
3157 ftpState
->flags
.rest_supported
= 0;
3158 ftpSendRetr(ftpState
);
3164 /// \ingroup ServerProtocolFTPInternal
3166 ftpSendList(FtpStateData
* ftpState
)
3168 /* check the server control channel is still available */
3169 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
3174 if (ftpState
->filepath
) {
3175 snprintf(cbuf
, 1024, "LIST %s\r\n", ftpState
->filepath
);
3177 snprintf(cbuf
, 1024, "LIST\r\n");
3180 ftpState
->writeCommand(cbuf
);
3181 ftpState
->state
= SENT_LIST
;
3184 /// \ingroup ServerProtocolFTPInternal
3186 ftpSendNlst(FtpStateData
* ftpState
)
3188 /* check the server control channel is still available */
3189 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
3194 ftpState
->flags
.tried_nlst
= 1;
3196 if (ftpState
->filepath
) {
3197 snprintf(cbuf
, 1024, "NLST %s\r\n", ftpState
->filepath
);
3199 snprintf(cbuf
, 1024, "NLST\r\n");
3202 ftpState
->writeCommand(cbuf
);
3203 ftpState
->state
= SENT_NLST
;
3206 /// \ingroup ServerProtocolFTPInternal
3208 ftpReadList(FtpStateData
* ftpState
)
3210 int code
= ftpState
->ctrl
.replycode
;
3213 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
3214 /* Begin data transfer */
3215 /* XXX what about Config.Timeout.read? */
3216 ftpState
->maybeReadVirginBody();
3217 ftpState
->state
= READING_DATA
;
3219 * Cancel the timeout on the Control socket and establish one
3220 * on the data socket
3222 AsyncCall::Pointer nullCall
= NULL
;
3223 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3225 } else if (code
== 150) {
3226 /* Accept data channel */
3227 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3228 AsyncCall::Pointer acceptCall
= JobCallback(11, 5,
3229 acceptDialer
, ftpState
, FtpStateData::ftpAcceptDataConnection
);
3231 ftpState
->data
.listener
= new Comm::ListenStateData(ftpState
->data
.fd
, acceptCall
, false);
3233 * Cancel the timeout on the Control socket and establish one
3234 * on the data socket
3236 AsyncCall::Pointer nullCall
= NULL
;
3237 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3239 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3240 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
3241 TimeoutDialer
, ftpState
,FtpStateData::ftpTimeout
);
3242 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3244 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
3245 ftpSendNlst(ftpState
);
3252 /// \ingroup ServerProtocolFTPInternal
3254 ftpSendRetr(FtpStateData
* ftpState
)
3256 /* check the server control channel is still available */
3257 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
3262 assert(ftpState
->filepath
!= NULL
);
3263 snprintf(cbuf
, 1024, "RETR %s\r\n", ftpState
->filepath
);
3264 ftpState
->writeCommand(cbuf
);
3265 ftpState
->state
= SENT_RETR
;
3268 /// \ingroup ServerProtocolFTPInternal
3270 ftpReadRetr(FtpStateData
* ftpState
)
3272 int code
= ftpState
->ctrl
.replycode
;
3275 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
3276 /* Begin data transfer */
3277 debugs(9, 3, HERE
<< "reading data channel");
3278 /* XXX what about Config.Timeout.read? */
3279 ftpState
->maybeReadVirginBody();
3280 ftpState
->state
= READING_DATA
;
3282 * Cancel the timeout on the Control socket and establish one
3283 * on the data socket
3285 AsyncCall::Pointer nullCall
= NULL
;
3286 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3287 } else if (code
== 150) {
3288 /* Accept data channel */
3289 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3290 AsyncCall::Pointer acceptCall
= JobCallback(11, 5,
3291 acceptDialer
, ftpState
, FtpStateData::ftpAcceptDataConnection
);
3292 ftpState
->data
.listener
= new Comm::ListenStateData(ftpState
->data
.fd
, acceptCall
, false);
3294 * Cancel the timeout on the Control socket and establish one
3295 * on the data socket
3297 AsyncCall::Pointer nullCall
= NULL
;
3298 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3300 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3301 AsyncCall::Pointer timeoutCall
= JobCallback(9, 5,
3302 TimeoutDialer
, ftpState
,FtpStateData::ftpTimeout
);
3303 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3304 } else if (code
>= 300) {
3305 if (!ftpState
->flags
.try_slash_hack
) {
3306 /* Try this as a directory missing trailing slash... */
3307 ftpState
->hackShortcut(ftpSendCwd
);
3317 * Generate the HTTP headers and template fluff around an FTP
3318 * directory listing display.
3321 FtpStateData::completedListing()
3325 ErrorState
*ferr
= errorCon(ERR_DIR_LISTING
, HTTP_OK
, request
);
3326 ferr
->ftp
.listing
= &listing
;
3327 ferr
->ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
3328 ferr
->ftp
.server_msg
= ctrl
.message
;
3329 ctrl
.message
= NULL
;
3330 entry
->replaceHttpReply( ferr
->BuildHttpReply() );
3331 errorStateFree(ferr
);
3332 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3338 /// \ingroup ServerProtocolFTPInternal
3340 ftpReadTransferDone(FtpStateData
* ftpState
)
3342 int code
= ftpState
->ctrl
.replycode
;
3345 if (code
== 226 || code
== 250) {
3346 /* Connection closed; retrieval done. */
3347 if (ftpState
->flags
.listing
) {
3348 ftpState
->completedListing();
3349 /* QUIT operation handles sending the reply to client */
3351 ftpSendQuit(ftpState
);
3352 } else { /* != 226 */
3353 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
3354 ftpState
->failed(ERR_FTP_FAILURE
, 0);
3355 /* failed closes ctrl.fd and frees ftpState */
3360 // premature end of the request body
3362 FtpStateData::handleRequestBodyProducerAborted()
3364 ServerStateData::handleRequestBodyProducerAborted();
3365 debugs(9, 3, HERE
<< "ftpState=" << this);
3366 failed(ERR_READ_ERROR
, 0);
3370 * This will be called when the put write is completed
3373 FtpStateData::sentRequestBody(const CommIoCbParams
&io
)
3376 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, io
.size
);
3377 ServerStateData::sentRequestBody(io
);
3380 /// \ingroup ServerProtocolFTPInternal
3382 ftpWriteTransferDone(FtpStateData
* ftpState
)
3384 int code
= ftpState
->ctrl
.replycode
;
3387 if (!(code
== 226 || code
== 250)) {
3388 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
3389 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
3393 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
3394 ftpSendReply(ftpState
);
3397 /// \ingroup ServerProtocolFTPInternal
3399 ftpSendQuit(FtpStateData
* ftpState
)
3401 /* check the server control channel is still available */
3402 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
3405 snprintf(cbuf
, 1024, "QUIT\r\n");
3406 ftpState
->writeCommand(cbuf
);
3407 ftpState
->state
= SENT_QUIT
;
3411 * \ingroup ServerProtocolFTPInternal
3413 * This completes a client FTP operation with success or other page
3414 * generated and stored in the entry field by the code issuing QUIT.
3417 ftpReadQuit(FtpStateData
* ftpState
)
3419 ftpState
->serverComplete();
3422 /// \ingroup ServerProtocolFTPInternal
3424 ftpTrySlashHack(FtpStateData
* ftpState
)
3427 ftpState
->flags
.try_slash_hack
= 1;
3428 /* Free old paths */
3432 if (ftpState
->pathcomps
)
3433 wordlistDestroy(&ftpState
->pathcomps
);
3435 safe_free(ftpState
->filepath
);
3437 /* Build the new path (urlpath begins with /) */
3438 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
3440 rfc1738_unescape(path
);
3442 ftpState
->filepath
= path
;
3445 ftpGetFile(ftpState
);
3449 * Forget hack status. Next error is shown to the user
3452 FtpStateData::unhack()
3456 if (old_request
!= NULL
) {
3457 safe_free(old_request
);
3458 safe_free(old_reply
);
3463 FtpStateData::hackShortcut(FTPSM
* nextState
)
3465 /* Clear some unwanted state */
3466 setCurrentOffset(0);
3468 /* Save old error message & some state info */
3472 if (old_request
== NULL
) {
3473 old_request
= ctrl
.last_command
;
3474 ctrl
.last_command
= NULL
;
3475 old_reply
= ctrl
.last_reply
;
3476 ctrl
.last_reply
= NULL
;
3478 if (pathcomps
== NULL
&& filepath
!= NULL
)
3479 old_filepath
= xstrdup(filepath
);
3482 /* Jump to the "hack" state */
3486 /// \ingroup ServerProtocolFTPInternal
3488 ftpFail(FtpStateData
*ftpState
)
3490 debugs(9, 6, HERE
<< "flags(" <<
3491 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
3492 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
3493 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
3494 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
3496 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
3497 if (!ftpState
->flags
.isdir
&& /* Not a directory */
3498 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
3499 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
3500 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
3502 switch (ftpState
->state
) {
3507 /* Try the / hack */
3508 ftpState
->hackShortcut(ftpTrySlashHack
);
3516 ftpState
->failed(ERR_NONE
, 0);
3517 /* failed() closes ctrl.fd and frees this */
3521 FtpStateData::failed(err_type error
, int xerrno
)
3523 debugs(9,3,HERE
<< "entry-null=" << (entry
?entry
->isEmpty():0) << ", entry=" << entry
);
3524 if (entry
->isEmpty())
3525 failedErrorMessage(error
, xerrno
);
3531 FtpStateData::failedErrorMessage(err_type error
, int xerrno
)
3534 const char *command
, *reply
;
3536 /* Translate FTP errors into HTTP errors */
3549 if (ctrl
.replycode
> 500)
3551 ftperr
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
3553 ftperr
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
3555 else if (ctrl
.replycode
== 421)
3556 ftperr
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
3563 if (ctrl
.replycode
== 550)
3564 ftperr
= errorCon(ERR_FTP_NOT_FOUND
, HTTP_NOT_FOUND
, fwd
->request
);
3574 case ERR_READ_TIMEOUT
:
3575 ftperr
= errorCon(error
, HTTP_GATEWAY_TIMEOUT
, fwd
->request
);
3579 ftperr
= errorCon(error
, HTTP_BAD_GATEWAY
, fwd
->request
);
3584 ftperr
= errorCon(ERR_FTP_FAILURE
, HTTP_BAD_GATEWAY
, fwd
->request
);
3586 ftperr
->xerrno
= xerrno
;
3588 ftperr
->ftp
.server_msg
= ctrl
.message
;
3589 ctrl
.message
= NULL
;
3592 command
= old_request
;
3594 command
= ctrl
.last_command
;
3596 if (command
&& strncmp(command
, "PASS", 4) == 0)
3597 command
= "PASS <yourpassword>";
3602 reply
= ctrl
.last_reply
;
3605 ftperr
->ftp
.request
= xstrdup(command
);
3608 ftperr
->ftp
.reply
= xstrdup(reply
);
3610 entry
->replaceHttpReply( ftperr
->BuildHttpReply() );
3611 errorStateFree(ftperr
);
3614 /// \ingroup ServerProtocolFTPInternal
3616 ftpSendReply(FtpStateData
* ftpState
)
3619 int code
= ftpState
->ctrl
.replycode
;
3620 http_status http_code
;
3621 err_type err_code
= ERR_NONE
;
3623 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
3625 if (cbdataReferenceValid(ftpState
))
3626 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
3628 if (code
== 226 || code
== 250) {
3629 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
3630 http_code
= (ftpState
->mdtm
> 0) ? HTTP_ACCEPTED
: HTTP_CREATED
;
3631 } else if (code
== 227) {
3632 err_code
= ERR_FTP_PUT_CREATED
;
3633 http_code
= HTTP_CREATED
;
3635 err_code
= ERR_FTP_PUT_ERROR
;
3636 http_code
= HTTP_INTERNAL_SERVER_ERROR
;
3639 if (ftpState
->request
)
3640 ftpState
->request
->detailError(err_code
, code
);
3642 err
= errorCon(err_code
, http_code
, ftpState
->request
);
3644 if (ftpState
->old_request
)
3645 err
->ftp
.request
= xstrdup(ftpState
->old_request
);
3647 err
->ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
3649 if (ftpState
->old_reply
)
3650 err
->ftp
.reply
= xstrdup(ftpState
->old_reply
);
3651 else if (ftpState
->ctrl
.last_reply
)
3652 err
->ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
3654 err
->ftp
.reply
= xstrdup("");
3656 ftpState
->entry
->replaceHttpReply( err
->BuildHttpReply() );
3657 errorStateFree(err
);
3659 ftpSendQuit(ftpState
);
3663 FtpStateData::appendSuccessHeader()
3665 const char *mime_type
= NULL
;
3666 const char *mime_enc
= NULL
;
3667 String urlpath
= request
->urlpath
;
3668 const char *filename
= NULL
;
3669 const char *t
= NULL
;
3673 if (flags
.http_header_sent
)
3676 HttpReply
*reply
= new HttpReply
;
3678 flags
.http_header_sent
= 1;
3680 assert(entry
->isEmpty());
3682 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3684 entry
->buffer(); /* released when done processing current data payload */
3686 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
3689 mime_type
= "text/html";
3694 mime_type
= "application/octet-stream";
3695 mime_enc
= mimeGetContentEncoding(filename
);
3699 mime_type
= "text/plain";
3703 mime_type
= mimeGetContentType(filename
);
3704 mime_enc
= mimeGetContentEncoding(filename
);
3709 /* set standard stuff */
3711 if (0 == getCurrentOffset()) {
3713 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3714 } else if (theSize
< getCurrentOffset()) {
3717 * offset should not be larger than theSize. We should
3718 * not be seeing this condition any more because we'll only
3719 * send REST if we know the theSize and if it is less than theSize.
3721 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
3722 " current offset=" << getCurrentOffset() <<
3723 ", but theSize=" << theSize
<<
3724 ". assuming full content response");
3725 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3728 HttpHdrRangeSpec range_spec
;
3729 range_spec
.offset
= getCurrentOffset();
3730 range_spec
.length
= theSize
- getCurrentOffset();
3731 reply
->setHeaders(HTTP_PARTIAL_CONTENT
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
3732 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
3735 /* additional info */
3737 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
3739 setVirginReply(reply
);
3740 adaptOrFinalizeReply();
3744 FtpStateData::haveParsedReplyHeaders()
3746 ServerStateData::haveParsedReplyHeaders();
3748 StoreEntry
*e
= entry
;
3752 if (flags
.authenticated
) {
3754 * Authenticated requests can't be cached.
3757 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !getCurrentOffset()) {
3765 FtpStateData::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
3767 ErrorState
*err
= errorCon(ERR_CACHE_ACCESS_DENIED
, HTTP_UNAUTHORIZED
, request
);
3768 HttpReply
*newrep
= err
->BuildHttpReply();
3769 errorStateFree(err
);
3770 /* add Authenticate header */
3771 newrep
->header
.putAuth("Basic", realm
);
3776 \ingroup ServerProtocolFTPAPI
3777 \todo Should be a URL class API call.
3779 * Construct an URI with leading / in PATH portion for use by CWD command
3780 * possibly others. FTP encodes absolute paths as beginning with '/'
3781 * after the initial URI path delimiter, which happens to be / itself.
3782 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3783 * To encompass older software which compacts multiple // to / in transit
3784 * We use standard URI-encoding on the second / making it
3785 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3788 ftpUrlWith2f(HttpRequest
* request
)
3790 String newbuf
= "%2f";
3792 if (request
->protocol
!= PROTO_FTP
)
3795 if ( request
->urlpath
[0]=='/' ) {
3796 newbuf
.append(request
->urlpath
);
3797 request
->urlpath
.absorb(newbuf
);
3798 safe_free(request
->canonical
);
3799 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
3800 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
3801 request
->urlpath
.absorb(newbuf
);
3802 safe_free(request
->canonical
);
3805 return urlCanonical(request
);
3809 FtpStateData::printfReplyBody(const char *fmt
, ...)
3812 va_start (args
, fmt
);
3813 static char buf
[4096];
3815 vsnprintf(buf
, 4096, fmt
, args
);
3816 writeReplyBody(buf
, strlen(buf
));
3820 * Call this when there is data from the origin server
3821 * which should be sent to either StoreEntry, or to ICAP...
3824 FtpStateData::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
3826 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
3827 addVirginReplyBody(dataToWrite
, dataLength
);
3831 * called after we wrote the last byte of the request body
3834 FtpStateData::doneSendingRequestBody()
3836 ServerStateData::doneSendingRequestBody();
3839 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3840 * if transfer type is 'stream' call dataComplete()
3841 * otherwise leave open. (reschedule control channel read?)
3846 * A hack to ensure we do not double-complete on the forward entry.
3848 \todo FtpStateData logic should probably be rewritten to avoid
3849 * double-completion or FwdState should be rewritten to allow it.
3852 FtpStateData::completeForwarding()
3854 if (fwd
== NULL
|| flags
.completed_forwarding
) {
3855 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
3856 "double-complete on FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<<
3857 ", this " << this << ", fwd " << fwd
);
3861 flags
.completed_forwarding
= true;
3862 ServerStateData::completeForwarding();
3866 * Close the FTP server connection(s). Used by serverComplete().
3869 FtpStateData::closeServer()
3871 debugs(9,3, HERE
<< "closing FTP server FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<< ", this " << this);
3874 fwd
->unregister(ctrl
.fd
);
3882 * Did we close all FTP server connection(s)?
3884 \retval true Both server control and data channels are closed. And not waitigng for a new data connection to open.
3885 \retval false Either control channel or data is still active.
3888 FtpStateData::doneWithServer() const
3890 return ctrl
.fd
< 0 && data
.fd
< 0;
3894 * Have we lost the FTP server control channel?
3896 \retval true The server control channel is available.
3897 \retval false The server control channel is not available.
3900 FtpStateData::haveControlChannel(const char *caller_name
) const
3902 if (doneWithServer())
3905 /* doneWithServer() only checks BOTH channels are closed. */
3907 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
3908 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
3916 * Quickly abort the transaction
3918 \todo destruction should be sufficient as the destructor should cleanup,
3919 * including canceling close handlers
3922 FtpStateData::abortTransaction(const char *reason
)
3924 debugs(9, 3, HERE
<< "aborting transaction for " << reason
<<
3925 "; FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<< ", this " << this);
3927 comm_close(ctrl
.fd
);
3931 fwd
->handleUnregisteredServerEnd();
3932 deleteThis("FtpStateData::abortTransaction");
3935 /// creates a data channel Comm close callback
3937 FtpStateData::dataCloser()
3939 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
3940 return JobCallback(9, 5, Dialer
, this, FtpStateData::dataClosed
);
3943 /// configures the channel with a descriptor and registers a close handler
3945 FtpChannel::opened(int aFd
, const AsyncCall::Pointer
&aCloser
)
3948 assert(closer
== NULL
);
3951 assert(aCloser
!= NULL
);
3955 comm_add_close_handler(fd
, closer
);
3958 /// planned close: removes the close handler and calls comm_close
3962 // channels with active listeners will be closed when the listener handler dies.
3966 comm_remove_close_handler(fd
, closer
);
3969 } else if (fd
>= 0) {
3970 comm_remove_close_handler(fd
, closer
);
3972 comm_close(fd
); // we do not expect to be called back
3977 /// just resets fd and close handler