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/ListenStateData.h"
38 #include "compat/strtoll.h"
39 #include "ConnectionDetail.h"
40 #include "errorpage.h"
43 #include "HttpHdrContRange.h"
44 #include "HttpHeaderRange.h"
45 #include "HttpHeader.h"
46 #include "HttpRequest.h"
47 #include "HttpReply.h"
51 #include "SquidString.h"
52 #include "SquidTime.h"
54 #include "URLScheme.h"
58 #include "DelayPools.h"
59 #include "MemObject.h"
63 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
64 \ingroup ServerProtocolFTPAPI
67 /// \ingroup ServerProtocolFTPInternal
68 static const char *const crlf
= "\r\n";
70 /// \ingroup ServerProtocolFTPInternal
71 static char cbuf
[1024];
73 /// \ingroup ServerProtocolFTPInternal
99 /// \ingroup ServerProtocolFTPInternal
103 bool pasv_supported
; ///< PASV command is allowed
104 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
108 bool authenticated
; ///< authentication success
109 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
110 bool tried_auth_nopass
; ///< auth tried username with no password already.
114 bool skip_whitespace
;
116 bool http_header_sent
;
126 bool listformat_unknown
;
128 bool completed_forwarding
;
133 /// \ingroup ServerProtocolFTPInternal
134 typedef void (FTPSM
) (FtpStateData
*);
136 /// common code for FTP control and data channels
137 // does not own the channel descriptor, which is managed by FtpStateData
141 FtpChannel(): fd(-1) {}
143 /// called after the socket is opened, sets up close handler
144 void opened(int aFd
, const AsyncCall::Pointer
&aCloser
);
146 /** Handles all operations needed to properly close the active channel FD.
147 * clearing the close handler, clearing the listen socket properly, and calling comm_close
151 void clear(); /// just resets fd and close handler. does not close active connections.
153 int fd
; /// channel descriptor; \todo: remove because the closer has it
155 /** Current listening socket handler. delete on shutdown or abort.
156 * FTP stores a copy of the FD in the field fd above.
157 * Use close() to properly close the channel.
159 Comm::ListenStateData
*listener
;
162 AsyncCall::Pointer closer
; /// Comm close handler callback
165 /// \ingroup ServerProtocolFTPInternal
166 class FtpStateData
: public ServerStateData
170 void *operator new (size_t);
171 void operator delete (void *);
172 void *toCbdata() { return this; }
174 FtpStateData(FwdState
*);
177 char password
[MAX_URL
];
192 int64_t restart_offset
;
200 MemBuf listing
; ///< FTP directory listing in HTML format.
202 // \todo: optimize ctrl and data structs member order, to minimize size
203 /// FTP control channel info; the channel is opened once per transaction
204 struct CtrlChannel
: public FtpChannel
{
214 /// FTP data channel info; the channel may be opened/closed a few times
215 struct DataChannel
: public FtpChannel
{
222 struct _ftp_flags flags
;
225 CBDATA_CLASS(FtpStateData
);
228 // these should all be private
230 void loginParser(const char *, int escaped
);
232 void appendSuccessHeader();
233 void hackShortcut(FTPSM
* nextState
);
234 void failed(err_type
, int xerrno
);
235 void failedErrorMessage(err_type
, int xerrno
);
237 void scheduleReadControlReply(int);
238 void handleControlReply();
241 MemBuf
*htmlifyListEntry(const char *line
);
242 void completedListing(void);
244 void dataRead(const CommIoCbParams
&io
);
245 int checkAuth(const HttpHeader
* req_hdr
);
247 void buildTitleUrl();
248 void writeReplyBody(const char *, size_t len
);
249 void printfReplyBody(const char *fmt
, ...);
250 virtual int dataDescriptor() const;
251 virtual void maybeReadVirginBody();
252 virtual void closeServer();
253 virtual void completeForwarding();
254 virtual void abortTransaction(const char *reason
);
255 void processHeadResponse();
256 void processReplyBody();
257 void writeCommand(const char *buf
);
258 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
259 int64_t getCurrentOffset() const { return currentOffset
; }
261 static CNCB ftpPasvCallback
;
262 static PF ftpDataWrite
;
263 void ftpTimeout(const CommTimeoutCbParams
&io
);
264 void ctrlClosed(const CommCloseCbParams
&io
);
265 void dataClosed(const CommCloseCbParams
&io
);
266 void ftpReadControlReply(const CommIoCbParams
&io
);
267 void ftpWriteCommandCallback(const CommIoCbParams
&io
);
268 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
270 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, const char *realm
);
271 const char *ftpRealm(void);
272 void loginFailed(void);
273 static wordlist
*ftpParseControlReply(char *, size_t, int *, size_t *);
275 // sending of the request body to the server
276 virtual void sentRequestBody(const CommIoCbParams
&);
277 virtual void doneSendingRequestBody();
279 virtual void haveParsedReplyHeaders();
281 virtual bool doneWithServer() const;
282 virtual bool haveControlChannel(const char *caller_name
) const;
283 AsyncCall::Pointer
dataCloser(); /// creates a Comm close callback
286 // BodyConsumer for HTTP: consume request body.
287 virtual void handleRequestBodyProducerAborted();
290 CBDATA_CLASS_INIT(FtpStateData
);
293 FtpStateData::operator new (size_t)
295 CBDATA_INIT_TYPE(FtpStateData
);
296 FtpStateData
*result
= cbdataAlloc(FtpStateData
);
301 FtpStateData::operator delete (void *address
)
303 FtpStateData
*t
= static_cast<FtpStateData
*>(address
);
307 /// \ingroup ServerProtocolFTPInternal
317 /// \ingroup ServerProtocolFTPInternal
318 #define FTP_LOGIN_ESCAPED 1
320 /// \ingroup ServerProtocolFTPInternal
321 #define FTP_LOGIN_NOT_ESCAPED 0
324 * State machine functions
325 * send == state transition
326 * read == wait for response, and select next state transition
327 * other == Transition logic
329 static FTPSM ftpReadWelcome
;
330 static FTPSM ftpSendUser
;
331 static FTPSM ftpReadUser
;
332 static FTPSM ftpSendPass
;
333 static FTPSM ftpReadPass
;
334 static FTPSM ftpSendType
;
335 static FTPSM ftpReadType
;
336 static FTPSM ftpSendMdtm
;
337 static FTPSM ftpReadMdtm
;
338 static FTPSM ftpSendSize
;
339 static FTPSM ftpReadSize
;
340 static FTPSM ftpSendEPRT
;
341 static FTPSM ftpReadEPRT
;
342 static FTPSM ftpSendPORT
;
343 static FTPSM ftpReadPORT
;
344 static FTPSM ftpSendPassive
;
345 static FTPSM ftpReadEPSV
;
346 static FTPSM ftpReadPasv
;
347 static FTPSM ftpTraverseDirectory
;
348 static FTPSM ftpListDir
;
349 static FTPSM ftpGetFile
;
350 static FTPSM ftpSendCwd
;
351 static FTPSM ftpReadCwd
;
352 static FTPSM ftpRestOrList
;
353 static FTPSM ftpSendList
;
354 static FTPSM ftpSendNlst
;
355 static FTPSM ftpReadList
;
356 static FTPSM ftpSendRest
;
357 static FTPSM ftpReadRest
;
358 static FTPSM ftpSendRetr
;
359 static FTPSM ftpReadRetr
;
360 static FTPSM ftpReadTransferDone
;
361 static FTPSM ftpSendStor
;
362 static FTPSM ftpReadStor
;
363 static FTPSM ftpWriteTransferDone
;
364 static FTPSM ftpSendReply
;
365 static FTPSM ftpSendMkdir
;
366 static FTPSM ftpReadMkdir
;
367 static FTPSM ftpFail
;
368 static FTPSM ftpSendQuit
;
369 static FTPSM ftpReadQuit
;
371 /************************************************
372 ** Debugs Levels used here **
373 *************************************************
376 Protocol and Transmission failures.
377 2 FTP Protocol Chatter
382 ************************************************/
384 /************************************************
385 ** State Machine Description (excluding hacks) **
386 *************************************************
388 ---------------------------------------
392 Type TraverseDirectory / GetFile
393 TraverseDirectory Cwd / GetFile / ListDir
394 Cwd TraverseDirectory / Mkdir
400 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
402 Retr / Nlst / List DataRead* (on datachannel)
403 DataRead* ReadTransferDone
404 ReadTransferDone DataTransferDone
405 Stor DataWrite* (on datachannel)
406 DataWrite* RequestPutBody** (from client)
407 RequestPutBody** DataWrite* / WriteTransferDone
408 WriteTransferDone DataTransferDone
409 DataTransferDone Quit
411 ************************************************/
413 /// \ingroup ServerProtocolFTPInternal
414 FTPSM
*FTP_SM_FUNCS
[] = {
415 ftpReadWelcome
, /* BEGIN */
416 ftpReadUser
, /* SENT_USER */
417 ftpReadPass
, /* SENT_PASS */
418 ftpReadType
, /* SENT_TYPE */
419 ftpReadMdtm
, /* SENT_MDTM */
420 ftpReadSize
, /* SENT_SIZE */
421 ftpReadEPRT
, /* SENT_EPRT */
422 ftpReadPORT
, /* SENT_PORT */
423 ftpReadEPSV
, /* SENT_EPSV_ALL */
424 ftpReadEPSV
, /* SENT_EPSV_1 */
425 ftpReadEPSV
, /* SENT_EPSV_2 */
426 ftpReadPasv
, /* SENT_PASV */
427 ftpReadCwd
, /* SENT_CWD */
428 ftpReadList
, /* SENT_LIST */
429 ftpReadList
, /* SENT_NLST */
430 ftpReadRest
, /* SENT_REST */
431 ftpReadRetr
, /* SENT_RETR */
432 ftpReadStor
, /* SENT_STOR */
433 ftpReadQuit
, /* SENT_QUIT */
434 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
435 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
436 ftpReadMkdir
/* SENT_MKDIR */
439 /// handler called by Comm when FTP control channel is closed unexpectedly
441 FtpStateData::ctrlClosed(const CommCloseCbParams
&io
)
444 deleteThis("FtpStateData::ctrlClosed");
447 /// handler called by Comm when FTP data channel is closed unexpectedly
449 FtpStateData::dataClosed(const CommCloseCbParams
&io
)
452 delete data
.listener
;
453 data
.listener
= NULL
;
457 failed(ERR_FTP_FAILURE
, 0);
458 /* failed closes ctrl.fd and frees ftpState */
460 /* NP: failure recovery may be possible when its only a data.fd failure.
461 * is the ctrl.fd is still fine, we can send ABOR down it and retry.
462 * Just need to watch out for wider Squid states like shutting down or reconfigure.
466 FtpStateData::FtpStateData(FwdState
*theFwdState
) : AsyncJob("FtpStateData"), ServerStateData(theFwdState
)
468 const char *url
= entry
->url();
469 debugs(9, 3, HERE
<< "'" << url
<< "'" );
470 statCounter
.server
.all
.requests
++;
471 statCounter
.server
.ftp
.requests
++;
475 if (Config
.Ftp
.passive
&& !theFwdState
->ftpPasvFailed())
476 flags
.pasv_supported
= 1;
478 flags
.rest_supported
= 1;
480 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
481 AsyncCall::Pointer closer
= asyncCall(9, 5, "FtpStateData::ctrlClosed",
482 Dialer(this, &FtpStateData::ctrlClosed
));
483 ctrl
.opened(theFwdState
->server_fd
, closer
);
485 if (request
->method
== METHOD_PUT
)
489 FtpStateData::~FtpStateData()
491 debugs(9, 3, HERE
<< entry
->url() );
494 memFree(reply_hdr
, MEM_8K_BUF
);
501 debugs(9, DBG_IMPORTANT
, HERE
<< "Internal bug: FtpStateData left " <<
502 "control FD " << ctrl
.fd
<< " open");
506 memFreeBuf(ctrl
.size
, ctrl
.buf
);
511 if (!data
.readBuf
->isNull())
512 data
.readBuf
->clean();
518 wordlistDestroy(&pathcomps
);
521 wordlistDestroy(&ctrl
.message
);
525 safe_free(ctrl
.last_reply
);
527 safe_free(ctrl
.last_command
);
529 safe_free(old_request
);
531 safe_free(old_reply
);
533 safe_free(old_filepath
);
543 safe_free(data
.host
);
545 fwd
= NULL
; // refcounted
549 * Parse a possible login username:password pair.
550 * Produces filled member variables user, password, password_url if anything found.
553 FtpStateData::loginParser(const char *login
, int escaped
)
555 const char *u
= NULL
; // end of the username sub-string
556 int len
; // length of the current sub-string to handle.
558 int total_len
= strlen(login
);
560 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
561 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
563 if ((u
= strchr(login
, ':'))) {
565 /* if there was a username part */
568 ++u
; // jump off the delimiter.
571 xstrncpy(user
, login
, len
+1);
572 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
574 rfc1738_unescape(user
);
575 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
578 /* if there was a password part */
579 len
= login
+ total_len
- u
;
583 xstrncpy(password
, u
, len
+1);
584 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
586 rfc1738_unescape(password
);
589 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
591 } else if (login
[0]) {
592 /* no password, just username */
593 if (total_len
> MAX_URL
)
594 total_len
= MAX_URL
-1;
595 xstrncpy(user
, login
, total_len
+1);
596 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
598 rfc1738_unescape(user
);
599 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
602 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
606 FtpStateData::ftpTimeout(const CommTimeoutCbParams
&io
)
608 debugs(9, 4, "ftpTimeout: FD " << io
.fd
<< ": '" << entry
->url() << "'" );
610 if (SENT_PASV
== state
&& io
.fd
== data
.fd
) {
611 /* stupid ftp.netscape.com */
612 fwd
->dontRetry(false);
613 fwd
->ftpPasvFailed(true);
614 debugs(9, DBG_IMPORTANT
, "ftpTimeout: timeout in SENT_PASV state" );
617 failed(ERR_READ_TIMEOUT
, 0);
618 /* failed() closes ctrl.fd and frees ftpState */
621 #if DEAD_CODE // obsoleted by ERR_DIR_LISTING
623 FtpStateData::listingFinish()
625 // TODO: figure out what this means and how to show it ...
627 if (flags
.listformat_unknown
&& !flags
.tried_nlst
) {
628 printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
629 flags
.dir_slash
? rfc1738_escape_part(old_filepath
) : ".");
630 } else if (typecode
== 'D') {
631 const char *path
= flags
.dir_slash
? filepath
: ".";
632 printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path
));
635 #endif /* DEAD_CODE */
637 /// \ingroup ServerProtocolFTPInternal
638 static const char *Month
[] = {
639 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
640 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
643 /// \ingroup ServerProtocolFTPInternal
645 is_month(const char *buf
)
649 for (i
= 0; i
< 12; i
++)
650 if (!strcasecmp(buf
, Month
[i
]))
656 /// \ingroup ServerProtocolFTPInternal
658 ftpListPartsFree(ftpListParts
** parts
)
660 safe_free((*parts
)->date
);
661 safe_free((*parts
)->name
);
662 safe_free((*parts
)->showname
);
663 safe_free((*parts
)->link
);
667 /// \ingroup ServerProtocolFTPInternal
668 #define MAX_TOKENS 64
670 /// \ingroup ServerProtocolFTPInternal
671 static ftpListParts
*
672 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
674 ftpListParts
*p
= NULL
;
676 const char *ct
= NULL
;
677 char *tokens
[MAX_TOKENS
];
680 static char tbuf
[128];
682 static int scan_ftp_initialized
= 0;
683 static regex_t scan_ftp_integer
;
684 static regex_t scan_ftp_time
;
685 static regex_t scan_ftp_dostime
;
686 static regex_t scan_ftp_dosdate
;
688 if (!scan_ftp_initialized
) {
689 scan_ftp_initialized
= 1;
690 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
691 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
692 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
693 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
702 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
706 memset(tokens
, 0, sizeof(tokens
));
710 if (flags
.tried_nlst
) {
711 /* Machine readable format, one name per line */
717 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
))
718 tokens
[n_tokens
++] = xstrdup(t
);
722 /* locate the Month field */
723 for (i
= 3; i
< n_tokens
- 2; i
++) {
724 char *size
= tokens
[i
- 1];
725 char *month
= tokens
[i
];
726 char *day
= tokens
[i
+ 1];
727 char *year
= tokens
[i
+ 2];
729 if (!is_month(month
))
732 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
735 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
738 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
741 snprintf(tbuf
, 128, "%s %2s %5s",
744 if (!strstr(buf
, tbuf
))
745 snprintf(tbuf
, 128, "%s %2s %-5s",
748 char const *copyFrom
= NULL
;
750 if ((copyFrom
= strstr(buf
, tbuf
))) {
751 p
->type
= *tokens
[0];
752 p
->size
= strtoll(size
, NULL
, 10);
753 p
->date
= xstrdup(tbuf
);
755 if (flags
.skip_whitespace
) {
756 copyFrom
+= strlen(tbuf
);
758 while (strchr(w_space
, *copyFrom
))
761 /* XXX assumes a single space between date and filename
762 * suggested by: Nathan.Bailey@cc.monash.edu.au and
763 * Mike Battersby <mike@starbug.bofh.asn.au> */
764 copyFrom
+= strlen(tbuf
) + 1;
767 p
->name
= xstrdup(copyFrom
);
769 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
771 p
->link
= xstrdup(t
+ 4);
780 /* try it as a DOS listing, 04-05-70 09:33PM ... */
782 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
783 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
784 if (!strcasecmp(tokens
[2], "<dir>")) {
788 p
->size
= strtoll(tokens
[2], NULL
, 10);
791 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
792 p
->date
= xstrdup(tbuf
);
794 if (p
->type
== 'd') {
795 /* Directory.. name begins with first printable after <dir> */
796 ct
= strstr(buf
, tokens
[2]);
797 ct
+= strlen(tokens
[2]);
799 while (xisspace(*ct
))
805 /* A file. Name begins after size, with a space in between */
806 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
807 ct
= strstr(buf
, tbuf
);
810 ct
+= strlen(tokens
[2]) + 2;
814 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
818 /* Try EPLF format; carson@lehman.com */
825 int l
= strcspn(ct
, ",");
834 p
->name
= xstrndup(ct
+ 1, l
+ 1);
838 p
->size
= atoi(ct
+ 1);
842 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
845 break; /* not a valid integer */
847 p
->date
= xstrdup(ctime(&tm
));
849 *(strstr(p
->date
, "\n")) = '\0';
871 ct
= strstr(ct
, ",");
890 for (i
= 0; i
< n_tokens
; i
++)
894 ftpListPartsFree(&p
); /* cleanup */
900 FtpStateData::htmlifyListEntry(const char *line
)
903 char href
[2048 + 40];
906 char chdir
[ 2048 + 40];
907 char view
[ 2048 + 40];
908 char download
[ 2048 + 40];
909 char link
[ 2048 + 40];
913 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
915 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
917 if (strlen(line
) > 1024) {
920 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
924 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
925 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
929 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
934 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
936 for (p
= line
; *p
&& xisspace(*p
); p
++);
937 if (*p
&& !xisspace(*p
))
938 flags
.listformat_unknown
= 1;
943 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
944 ftpListPartsFree(&parts
);
950 parts
->showname
= xstrdup(parts
->name
);
952 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
953 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
955 xstrncpy(text
, parts
->showname
, 2048);
957 switch (parts
->type
) {
960 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
961 mimeGetIconURL("internal-dir"),
963 strcat(href
, "/"); /* margin is allocated above */
967 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
968 mimeGetIconURL("internal-link"),
970 /* sometimes there is an 'l' flag, but no "->" link */
973 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
974 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
975 *link2
!= '/' ? prefix
: "", link2
,
976 html_quote(parts
->link
));
983 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
984 mimeGetIconURL(parts
->name
),
986 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
987 "alt=\"[DIR]\"></a>",
988 rfc1738_escape_part(parts
->name
),
989 mimeGetIconURL("internal-dir"));
995 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
996 mimeGetIconURL(parts
->name
),
998 snprintf(size
, 2048, " %6"PRId64
"k", parts
->size
);
1002 if (parts
->type
!= 'd') {
1003 if (mimeGetViewOption(parts
->name
)) {
1004 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1005 "alt=\"[VIEW]\"></a>",
1006 prefix
, href
, mimeGetIconURL("internal-view"));
1009 if (mimeGetDownloadOption(parts
->name
)) {
1010 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1011 "alt=\"[DOWNLOAD]\"></a>",
1012 prefix
, href
, mimeGetIconURL("internal-download"));
1016 /* construct the table row from parts. */
1017 html
= new MemBuf();
1019 html
->Printf("<tr class=\"entry\">"
1020 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1021 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1022 "<td class=\"date\">%s</td>"
1023 "<td class=\"size\">%s</td>"
1024 "<td class=\"actions\">%s%s%s%s</td>"
1027 prefix
, href
, html_quote(text
),
1030 chdir
, view
, download
, link
);
1032 ftpListPartsFree(&parts
);
1037 FtpStateData::parseListing()
1039 char *buf
= data
.readBuf
->content();
1040 char *sbuf
; /* NULL-terminated copy of termedBuf */
1047 size_t len
= data
.readBuf
->contentSize();
1050 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
1055 * We need a NULL-terminated buffer for scanning, ick
1057 sbuf
= (char *)xmalloc(len
+ 1);
1058 xstrncpy(sbuf
, buf
, len
+ 1);
1059 end
= sbuf
+ len
- 1;
1061 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1064 usable
= end
- sbuf
;
1066 debugs(9, 3, HERE
<< "usable = " << usable
);
1069 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url() );
1074 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
1076 line
= (char *)memAllocate(MEM_4K_BUF
);
1079 s
+= strspn(s
, crlf
);
1081 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1082 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
1083 linelen
= strcspn(s
, crlf
) + 1;
1091 xstrncpy(line
, s
, linelen
);
1093 debugs(9, 7, HERE
<< "{" << line
<< "}");
1095 if (!strncmp(line
, "total", 5))
1098 t
= htmlifyListEntry(line
);
1101 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
1102 listing
.append(t
->content(), t
->contentSize());
1107 debugs(9, 7, HERE
<< "Done.");
1108 data
.readBuf
->consume(usable
);
1109 memFree(line
, MEM_4K_BUF
);
1114 FtpStateData::dataDescriptor() const
1120 FtpStateData::dataComplete()
1124 /* Connection closed; transfer done. */
1126 /// Close data channel, if any, to conserve resources while we wait.
1129 /* expect the "transfer complete" message on the control socket */
1132 * Previously, this was the only place where we set the
1133 * 'buffered_ok' flag when calling scheduleReadControlReply().
1134 * It caused some problems if the FTP server returns an unexpected
1135 * status code after the data command. FtpStateData was being
1136 * deleted in the middle of dataRead().
1138 scheduleReadControlReply(0);
1142 FtpStateData::maybeReadVirginBody()
1147 if (data
.read_pending
)
1150 const int read_sz
= replyBodySpace(*data
.readBuf
, 0);
1152 debugs(11,9, HERE
<< "FTP may read up to " << read_sz
<< " bytes");
1154 if (read_sz
< 2) // see http.cc
1157 data
.read_pending
= true;
1159 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1160 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
1161 TimeoutDialer(this,&FtpStateData::ftpTimeout
));
1162 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
1164 debugs(9,5,HERE
<< "queueing read on FD " << data
.fd
);
1166 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1167 entry
->delayAwareRead(data
.fd
, data
.readBuf
->space(), read_sz
,
1168 asyncCall(9, 5, "FtpStateData::dataRead",
1169 Dialer(this, &FtpStateData::dataRead
)));
1173 FtpStateData::dataRead(const CommIoCbParams
&io
)
1178 data
.read_pending
= false;
1180 debugs(9, 3, HERE
<< "ftpDataRead: FD " << io
.fd
<< " Read " << io
.size
<< " bytes");
1183 kb_incr(&statCounter
.server
.all
.kbytes_in
, io
.size
);
1184 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, io
.size
);
1187 if (io
.flag
== COMM_ERR_CLOSING
)
1190 assert(io
.fd
== data
.fd
);
1192 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1193 abortTransaction("entry aborted during dataRead");
1197 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1198 debugs(9,5,HERE
<< "appended " << io
.size
<< " bytes to readBuf");
1199 data
.readBuf
->appended(io
.size
);
1201 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
1202 delayId
.bytesIn(io
.size
);
1204 IOStats
.Ftp
.reads
++;
1206 for (j
= io
.size
- 1, bin
= 0; j
; bin
++)
1209 IOStats
.Ftp
.read_hist
[bin
]++;
1212 if (io
.flag
!= COMM_OK
|| io
.size
< 0) {
1213 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1214 "ftpDataRead: read error: " << xstrerr(io
.xerrno
));
1216 if (ignoreErrno(io
.xerrno
)) {
1217 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1218 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
1219 TimeoutDialer(this,&FtpStateData::ftpTimeout
));
1220 commSetTimeout(io
.fd
, Config
.Timeout
.read
, timeoutCall
);
1222 maybeReadVirginBody();
1224 if (!flags
.http_header_sent
&& !fwd
->ftpPasvFailed() && flags
.pasv_supported
&& !flags
.listing
) {
1225 fwd
->dontRetry(false); /* this is a retryable error */
1226 fwd
->ftpPasvFailed(true);
1229 failed(ERR_READ_ERROR
, 0);
1230 /* failed closes ctrl.fd and frees ftpState */
1233 } else if (io
.size
== 0) {
1234 debugs(9,3, HERE
<< "Calling dataComplete() because io.size == 0");
1237 * Dangerous curves ahead. This call to dataComplete was
1238 * calling scheduleReadControlReply, handleControlReply,
1239 * and then ftpReadTransferDone. If ftpReadTransferDone
1240 * gets unexpected status code, it closes down the control
1241 * socket and our FtpStateData object gets destroyed. As
1242 * a workaround we no longer set the 'buffered_ok' flag in
1243 * the scheduleReadControlReply call.
1252 FtpStateData::processReplyBody()
1254 debugs(9, 3, HERE
<< "FtpStateData::processReplyBody starting.");
1256 if (request
->method
== METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
1261 /* Directory listings are special. They write ther own headers via the error objects */
1262 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
1263 appendSuccessHeader();
1265 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1267 * probably was aborted because content length exceeds one
1268 * of the maximum size limits.
1270 abortTransaction("entry aborted after calling appendSuccessHeader()");
1276 if (adaptationAccessCheckPending
) {
1277 debugs(9,3, HERE
<< "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1284 if (!flags
.listing
) {
1289 maybeReadVirginBody();
1291 } else if (const int csize
= data
.readBuf
->contentSize()) {
1292 writeReplyBody(data
.readBuf
->content(), csize
);
1293 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1294 data
.readBuf
->consume(csize
);
1299 maybeReadVirginBody();
1303 * Locates the FTP user:password login.
1305 * Highest to lowest priority:
1306 * - Checks URL (ftp://user:pass@domain)
1307 * - Authorization: Basic header
1308 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1310 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1312 * TODO: we might be able to do something about locating username from other sources:
1313 * ie, external ACL user=* tag or ident lookup
1315 \retval 1 if we have everything needed to complete this request.
1316 \retval 0 if something is missing.
1319 FtpStateData::checkAuth(const HttpHeader
* req_hdr
)
1323 /* default username */
1324 xstrncpy(user
, "anonymous", MAX_URL
);
1326 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1327 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1328 flags
.authenticated
= 1;
1329 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1331 /* we fail with authorization-required error later IFF the FTP server requests it */
1333 /* Test URL login syntax. Overrides any headers received. */
1334 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1336 /* name is missing. thats fatal. */
1338 fatal("FTP login parsing destroyed username info");
1340 /* name + password == success */
1344 /* Setup default FTP password settings */
1345 /* this has to be done last so that we can have a no-password case above. */
1347 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1348 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1349 flags
.tried_auth_anonymous
=1;
1351 } else if (!flags
.tried_auth_nopass
) {
1352 xstrncpy(password
, null_string
, MAX_URL
);
1353 flags
.tried_auth_nopass
=1;
1358 return 0; /* different username */
1361 static String str_type_eq
;
1363 FtpStateData::checkUrlpath()
1368 if (str_type_eq
.undefined()) //hack. String doesn't support global-static
1369 str_type_eq
="type=";
1371 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1372 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1373 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1374 request
->urlpath
.cut(t
);
1378 l
= request
->urlpath
.size();
1379 /* check for null path */
1384 flags
.need_base_href
= 1; /* Work around broken browsers */
1385 } else if (!request
->urlpath
.cmp("/%2f/")) {
1386 /* UNIX root directory */
1389 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1390 /* Directory URL, ending in / */
1396 flags
.dir_slash
= 1;
1401 FtpStateData::buildTitleUrl()
1403 title_url
= "ftp://";
1405 if (strcmp(user
, "anonymous")) {
1406 title_url
.append(user
);
1407 title_url
.append("@");
1410 title_url
.append(request
->GetHost());
1412 if (request
->port
!= urlDefaultPort(PROTO_FTP
)) {
1413 title_url
.append(":");
1414 title_url
.append(xitoa(request
->port
));
1417 title_url
.append (request
->urlpath
);
1419 base_href
= "ftp://";
1421 if (strcmp(user
, "anonymous") != 0) {
1422 base_href
.append(rfc1738_escape_part(user
));
1425 base_href
.append (":");
1426 base_href
.append(rfc1738_escape_part(password
));
1429 base_href
.append("@");
1432 base_href
.append(request
->GetHost());
1434 if (request
->port
!= urlDefaultPort(PROTO_FTP
)) {
1435 base_href
.append(":");
1436 base_href
.append(xitoa(request
->port
));
1439 base_href
.append(request
->urlpath
);
1440 base_href
.append("/");
1443 /// \ingroup ServerProtocolFTPAPI
1445 ftpStart(FwdState
* fwd
)
1447 FtpStateData
*ftpState
= new FtpStateData(fwd
);
1452 FtpStateData::start()
1454 if (!checkAuth(&request
->header
)) {
1455 /* create appropriate reply */
1456 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1457 entry
->replaceHttpReply(reply
);
1464 debugs(9, 5, HERE
<< "host=" << request
->GetHost() << ", path=" <<
1465 request
->urlpath
<< ", user=" << user
<< ", passwd=" <<
1469 ctrl
.last_command
= xstrdup("Connect to server");
1470 ctrl
.buf
= (char *)memAllocBuf(4096, &ctrl
.size
);
1472 data
.readBuf
= new MemBuf
;
1473 data
.readBuf
->init(4096, SQUID_TCP_SO_RCVBUF
);
1474 scheduleReadControlReply(0);
1477 /* ====================================================================== */
1479 /// \ingroup ServerProtocolFTPInternal
1481 escapeIAC(const char *buf
)
1485 unsigned const char *p
;
1488 for (p
= (unsigned const char *)buf
, n
= 1; *p
; n
++, p
++)
1492 ret
= (char *)xmalloc(n
);
1494 for (p
= (unsigned const char *)buf
, r
=(unsigned char *)ret
; *p
; p
++) {
1502 assert((r
- (unsigned char *)ret
) == n
);
1507 FtpStateData::writeCommand(const char *buf
)
1510 /* trace FTP protocol communications at level 2 */
1511 debugs(9, 2, "ftp<< " << buf
);
1513 if (Config
.Ftp
.telnet
)
1514 ebuf
= escapeIAC(buf
);
1516 ebuf
= xstrdup(buf
);
1518 safe_free(ctrl
.last_command
);
1520 safe_free(ctrl
.last_reply
);
1522 ctrl
.last_command
= ebuf
;
1524 if (!canSend(ctrl
.fd
)) {
1525 debugs(9, 2, HERE
<< "cannot send to closing ctrl FD " << ctrl
.fd
);
1526 // TODO: assert(ctrl.closer != NULL);
1530 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1531 AsyncCall::Pointer call
= asyncCall(9, 5, "FtpStateData::ftpWriteCommandCallback",
1532 Dialer(this, &FtpStateData::ftpWriteCommandCallback
));
1535 strlen(ctrl
.last_command
),
1538 scheduleReadControlReply(0);
1542 FtpStateData::ftpWriteCommandCallback(const CommIoCbParams
&io
)
1545 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io
.size
<< " bytes");
1548 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
1549 kb_incr(&statCounter
.server
.all
.kbytes_out
, io
.size
);
1550 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, io
.size
);
1553 if (io
.flag
== COMM_ERR_CLOSING
)
1557 debugs(9, DBG_IMPORTANT
, "ftpWriteCommandCallback: FD " << io
.fd
<< ": " << xstrerr(io
.xerrno
));
1558 failed(ERR_WRITE_ERROR
, io
.xerrno
);
1559 /* failed closes ctrl.fd and frees ftpState */
1565 FtpStateData::ftpParseControlReply(char *buf
, size_t len
, int *codep
, size_t *used
)
1572 wordlist
*head
= NULL
;
1574 wordlist
**tail
= &head
;
1580 * We need a NULL-terminated buffer for scanning, ick
1582 sbuf
= (char *)xmalloc(len
+ 1);
1583 xstrncpy(sbuf
, buf
, len
+ 1);
1584 end
= sbuf
+ len
- 1;
1586 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1589 usable
= end
- sbuf
;
1591 debugs(9, 3, HERE
<< "usable = " << usable
);
1594 debugs(9, 3, HERE
<< "didn't find end of line");
1599 debugs(9, 3, HERE
<< len
<< " bytes to play with");
1602 s
+= strspn(s
, crlf
);
1604 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1608 debugs(9, 5, HERE
<< "s = {" << s
<< "}");
1610 linelen
= strcspn(s
, crlf
) + 1;
1616 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1624 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1627 list
= new wordlist();
1629 list
->key
= (char *)xmalloc(linelen
- offset
);
1631 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1633 /* trace the FTP communication chat at level 2 */
1634 debugs(9, 2, "ftp>> " << code
<< " " << list
->key
);
1641 *used
= (size_t) (s
- sbuf
);
1645 wordlistDestroy(&head
);
1655 * Looks like there are no longer anymore callers that set
1656 * buffered_ok=1. Perhaps it can be removed at some point.
1659 FtpStateData::scheduleReadControlReply(int buffered_ok
)
1661 debugs(9, 3, HERE
<< "FD " << ctrl
.fd
);
1663 if (buffered_ok
&& ctrl
.offset
> 0) {
1664 /* We've already read some reply data */
1665 handleControlReply();
1667 /* XXX What about Config.Timeout.read? */
1668 typedef CommCbMemFunT
<FtpStateData
, CommIoCbParams
> Dialer
;
1669 AsyncCall::Pointer reader
=asyncCall(9, 5, "FtpStateData::ftpReadControlReply",
1670 Dialer(this, &FtpStateData::ftpReadControlReply
));
1671 comm_read(ctrl
.fd
, ctrl
.buf
+ ctrl
.offset
, ctrl
.size
- ctrl
.offset
, reader
);
1673 * Cancel the timeout on the Data socket (if any) and
1674 * establish one on the control socket.
1678 AsyncCall::Pointer nullCall
= NULL
;
1679 commSetTimeout(data
.fd
, -1, nullCall
);
1682 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
1683 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
1684 TimeoutDialer(this,&FtpStateData::ftpTimeout
));
1686 commSetTimeout(ctrl
.fd
, Config
.Timeout
.read
, timeoutCall
);
1690 void FtpStateData::ftpReadControlReply(const CommIoCbParams
&io
)
1692 debugs(9, 3, "ftpReadControlReply: FD " << io
.fd
<< ", Read " << io
.size
<< " bytes");
1695 kb_incr(&statCounter
.server
.all
.kbytes_in
, io
.size
);
1696 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, io
.size
);
1699 if (io
.flag
== COMM_ERR_CLOSING
)
1702 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1703 abortTransaction("entry aborted during control reply read");
1707 assert(ctrl
.offset
< ctrl
.size
);
1709 if (io
.flag
== COMM_OK
&& io
.size
> 0) {
1710 fd_bytes(io
.fd
, io
.size
, FD_READ
);
1713 if (io
.flag
!= COMM_OK
|| io
.size
< 0) {
1714 debugs(50, ignoreErrno(io
.xerrno
) ? 3 : DBG_IMPORTANT
,
1715 "ftpReadControlReply: read error: " << xstrerr(io
.xerrno
));
1717 if (ignoreErrno(io
.xerrno
)) {
1718 scheduleReadControlReply(0);
1720 failed(ERR_READ_ERROR
, io
.xerrno
);
1721 /* failed closes ctrl.fd and frees ftpState */
1729 if (entry
->store_status
== STORE_PENDING
) {
1730 failed(ERR_FTP_FAILURE
, 0);
1731 /* failed closes ctrl.fd and frees ftpState */
1735 /* XXX this may end up having to be serverComplete() .. */
1736 abortTransaction("zero control reply read");
1740 unsigned int len
=io
.size
+ ctrl
.offset
;
1742 assert(len
<= ctrl
.size
);
1743 handleControlReply();
1747 FtpStateData::handleControlReply()
1750 size_t bytes_used
= 0;
1751 wordlistDestroy(&ctrl
.message
);
1752 ctrl
.message
= ftpParseControlReply(ctrl
.buf
,
1753 ctrl
.offset
, &ctrl
.replycode
, &bytes_used
);
1755 if (ctrl
.message
== NULL
) {
1756 /* didn't get complete reply yet */
1758 if (ctrl
.offset
== ctrl
.size
) {
1759 ctrl
.buf
= (char *)memReallocBuf(ctrl
.buf
, ctrl
.size
<< 1, &ctrl
.size
);
1762 scheduleReadControlReply(0);
1764 } else if (ctrl
.offset
== bytes_used
) {
1765 /* used it all up */
1768 /* Got some data past the complete reply */
1769 assert(bytes_used
< ctrl
.offset
);
1770 ctrl
.offset
-= bytes_used
;
1771 xmemmove(ctrl
.buf
, ctrl
.buf
+ bytes_used
,
1775 /* Move the last line of the reply message to ctrl.last_reply */
1776 for (W
= &ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1777 safe_free(ctrl
.last_reply
);
1779 ctrl
.last_reply
= xstrdup((*W
)->key
);
1783 /* Copy the rest of the message to cwd_message to be printed in
1787 for (wordlist
*w
= ctrl
.message
; w
; w
= w
->next
) {
1788 cwd_message
.append('\n');
1789 cwd_message
.append(w
->key
);
1793 debugs(9, 3, HERE
<< "state=" << state
<< ", code=" << ctrl
.replycode
);
1795 FTP_SM_FUNCS
[state
] (this);
1798 /* ====================================================================== */
1800 /// \ingroup ServerProtocolFTPInternal
1802 ftpReadWelcome(FtpStateData
* ftpState
)
1804 int code
= ftpState
->ctrl
.replycode
;
1807 if (ftpState
->flags
.pasv_only
)
1808 ftpState
->login_att
++;
1810 /* Dont retry if the FTP server accepted the connection */
1811 ftpState
->fwd
->dontRetry(true);
1814 if (ftpState
->ctrl
.message
) {
1815 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1816 ftpState
->flags
.skip_whitespace
= 1;
1819 ftpSendUser(ftpState
);
1820 } else if (code
== 120) {
1821 if (NULL
!= ftpState
->ctrl
.message
)
1822 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1831 * Translate FTP login failure into HTTP error
1832 * this is an attmpt to get the 407 message to show up outside Squid.
1833 * its NOT a general failure. But a correct FTP response type.
1836 FtpStateData::loginFailed()
1838 ErrorState
*err
= NULL
;
1839 const char *command
, *reply
;
1841 if (state
== SENT_USER
|| state
== SENT_PASS
) {
1842 if (ctrl
.replycode
> 500) {
1844 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
1846 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
1847 } else if (ctrl
.replycode
== 421) {
1848 err
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
1855 err
->ftp
.server_msg
= ctrl
.message
;
1857 ctrl
.message
= NULL
;
1860 command
= old_request
;
1862 command
= ctrl
.last_command
;
1864 if (command
&& strncmp(command
, "PASS", 4) == 0)
1865 command
= "PASS <yourpassword>";
1870 reply
= ctrl
.last_reply
;
1873 err
->ftp
.request
= xstrdup(command
);
1876 err
->ftp
.reply
= xstrdup(reply
);
1879 HttpReply
*newrep
= err
->BuildHttpReply();
1880 errorStateFree(err
);
1881 /* add Authenticate header */
1882 newrep
->header
.putAuth("Basic", ftpRealm());
1884 // add it to the store entry for response....
1885 entry
->replaceHttpReply(newrep
);
1890 FtpStateData::ftpRealm()
1892 static char realm
[8192];
1894 /* This request is not fully authenticated */
1896 snprintf(realm
, 8192, "FTP %s unknown", user
);
1897 } else if (request
->port
== 21) {
1898 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1900 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1905 /// \ingroup ServerProtocolFTPInternal
1907 ftpSendUser(FtpStateData
* ftpState
)
1909 /* check the server control channel is still available */
1910 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1913 if (ftpState
->proxy_host
!= NULL
)
1914 snprintf(cbuf
, 1024, "USER %s@%s\r\n",
1916 ftpState
->request
->GetHost());
1918 snprintf(cbuf
, 1024, "USER %s\r\n", ftpState
->user
);
1920 ftpState
->writeCommand(cbuf
);
1922 ftpState
->state
= SENT_USER
;
1925 /// \ingroup ServerProtocolFTPInternal
1927 ftpReadUser(FtpStateData
* ftpState
)
1929 int code
= ftpState
->ctrl
.replycode
;
1933 ftpReadPass(ftpState
);
1934 } else if (code
== 331) {
1935 ftpSendPass(ftpState
);
1937 ftpState
->loginFailed();
1941 /// \ingroup ServerProtocolFTPInternal
1943 ftpSendPass(FtpStateData
* ftpState
)
1945 /* check the server control channel is still available */
1946 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
1949 snprintf(cbuf
, 1024, "PASS %s\r\n", ftpState
->password
);
1950 ftpState
->writeCommand(cbuf
);
1951 ftpState
->state
= SENT_PASS
;
1954 /// \ingroup ServerProtocolFTPInternal
1956 ftpReadPass(FtpStateData
* ftpState
)
1958 int code
= ftpState
->ctrl
.replycode
;
1959 debugs(9, 3, HERE
<< "code=" << code
);
1962 ftpSendType(ftpState
);
1964 ftpState
->loginFailed();
1968 /// \ingroup ServerProtocolFTPInternal
1970 ftpSendType(FtpStateData
* ftpState
)
1973 const char *filename
;
1976 /* check the server control channel is still available */
1977 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
1981 * Ref section 3.2.2 of RFC 1738
1983 mode
= ftpState
->typecode
;
1998 if (ftpState
->flags
.isdir
) {
2001 t
= ftpState
->request
->urlpath
.rpos('/');
2002 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
2003 mode
= mimeGetTransferMode(filename
);
2010 ftpState
->flags
.binary
= 1;
2012 ftpState
->flags
.binary
= 0;
2014 snprintf(cbuf
, 1024, "TYPE %c\r\n", mode
);
2016 ftpState
->writeCommand(cbuf
);
2018 ftpState
->state
= SENT_TYPE
;
2021 /// \ingroup ServerProtocolFTPInternal
2023 ftpReadType(FtpStateData
* ftpState
)
2025 int code
= ftpState
->ctrl
.replycode
;
2031 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2038 p
+= strcspn(p
, "/");
2043 rfc1738_unescape(d
);
2046 wordlistAdd(&ftpState
->pathcomps
, d
);
2051 if (ftpState
->pathcomps
)
2052 ftpTraverseDirectory(ftpState
);
2054 ftpListDir(ftpState
);
2060 /// \ingroup ServerProtocolFTPInternal
2062 ftpTraverseDirectory(FtpStateData
* ftpState
)
2065 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
2067 safe_free(ftpState
->dirpath
);
2068 ftpState
->dirpath
= ftpState
->filepath
;
2069 ftpState
->filepath
= NULL
;
2073 if (ftpState
->pathcomps
== NULL
) {
2074 debugs(9, 3, HERE
<< "the final component was a directory");
2075 ftpListDir(ftpState
);
2079 /* Go to next path component */
2080 w
= ftpState
->pathcomps
;
2082 ftpState
->filepath
= w
->key
;
2084 ftpState
->pathcomps
= w
->next
;
2088 /* Check if we are to CWD or RETR */
2089 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
2090 ftpSendCwd(ftpState
);
2092 debugs(9, 3, HERE
<< "final component is probably a file");
2093 ftpGetFile(ftpState
);
2098 /// \ingroup ServerProtocolFTPInternal
2100 ftpSendCwd(FtpStateData
* ftpState
)
2104 /* check the server control channel is still available */
2105 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
2110 path
= ftpState
->filepath
;
2112 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
2113 ftpState
->flags
.no_dotdot
= 1;
2115 ftpState
->flags
.no_dotdot
= 0;
2118 snprintf(cbuf
, 1024, "CWD %s\r\n", path
);
2120 ftpState
->writeCommand(cbuf
);
2122 ftpState
->state
= SENT_CWD
;
2125 /// \ingroup ServerProtocolFTPInternal
2127 ftpReadCwd(FtpStateData
* ftpState
)
2129 int code
= ftpState
->ctrl
.replycode
;
2132 if (code
>= 200 && code
< 300) {
2136 /* Reset cwd_message to only include the last message */
2137 ftpState
->cwd_message
.reset("");
2138 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
2139 ftpState
->cwd_message
.append(' ');
2140 ftpState
->cwd_message
.append(w
->key
);
2142 ftpState
->ctrl
.message
= NULL
;
2144 /* Continue to traverse the path */
2145 ftpTraverseDirectory(ftpState
);
2149 if (!ftpState
->flags
.put
)
2152 ftpSendMkdir(ftpState
);
2156 /// \ingroup ServerProtocolFTPInternal
2158 ftpSendMkdir(FtpStateData
* ftpState
)
2162 /* check the server control channel is still available */
2163 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
2166 path
= ftpState
->filepath
;
2167 debugs(9, 3, HERE
<< "with path=" << path
);
2168 snprintf(cbuf
, 1024, "MKD %s\r\n", path
);
2169 ftpState
->writeCommand(cbuf
);
2170 ftpState
->state
= SENT_MKDIR
;
2173 /// \ingroup ServerProtocolFTPInternal
2175 ftpReadMkdir(FtpStateData
* ftpState
)
2177 char *path
= ftpState
->filepath
;
2178 int code
= ftpState
->ctrl
.replycode
;
2180 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
2182 if (code
== 257) { /* success */
2183 ftpSendCwd(ftpState
);
2184 } else if (code
== 550) { /* dir exists */
2186 if (ftpState
->flags
.put_mkdir
) {
2187 ftpState
->flags
.put_mkdir
= 1;
2188 ftpSendCwd(ftpState
);
2190 ftpSendReply(ftpState
);
2192 ftpSendReply(ftpState
);
2195 /// \ingroup ServerProtocolFTPInternal
2197 ftpGetFile(FtpStateData
* ftpState
)
2199 assert(*ftpState
->filepath
!= '\0');
2200 ftpState
->flags
.isdir
= 0;
2201 ftpSendMdtm(ftpState
);
2204 /// \ingroup ServerProtocolFTPInternal
2206 ftpListDir(FtpStateData
* ftpState
)
2208 if (ftpState
->flags
.dir_slash
) {
2209 debugs(9, 3, HERE
<< "Directory path did not end in /");
2210 ftpState
->title_url
.append("/");
2211 ftpState
->flags
.isdir
= 1;
2214 ftpSendPassive(ftpState
);
2217 /// \ingroup ServerProtocolFTPInternal
2219 ftpSendMdtm(FtpStateData
* ftpState
)
2221 /* check the server control channel is still available */
2222 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
2225 assert(*ftpState
->filepath
!= '\0');
2226 snprintf(cbuf
, 1024, "MDTM %s\r\n", ftpState
->filepath
);
2227 ftpState
->writeCommand(cbuf
);
2228 ftpState
->state
= SENT_MDTM
;
2231 /// \ingroup ServerProtocolFTPInternal
2233 ftpReadMdtm(FtpStateData
* ftpState
)
2235 int code
= ftpState
->ctrl
.replycode
;
2239 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
2241 } else if (code
< 0) {
2246 ftpSendSize(ftpState
);
2249 /// \ingroup ServerProtocolFTPInternal
2251 ftpSendSize(FtpStateData
* ftpState
)
2253 /* check the server control channel is still available */
2254 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
2257 /* Only send SIZE for binary transfers. The returned size
2258 * is useless on ASCII transfers */
2260 if (ftpState
->flags
.binary
) {
2261 assert(ftpState
->filepath
!= NULL
);
2262 assert(*ftpState
->filepath
!= '\0');
2263 snprintf(cbuf
, 1024, "SIZE %s\r\n", ftpState
->filepath
);
2264 ftpState
->writeCommand(cbuf
);
2265 ftpState
->state
= SENT_SIZE
;
2267 /* Skip to next state no non-binary transfers */
2268 ftpSendPassive(ftpState
);
2271 /// \ingroup ServerProtocolFTPInternal
2273 ftpReadSize(FtpStateData
* ftpState
)
2275 int code
= ftpState
->ctrl
.replycode
;
2280 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
2282 if (ftpState
->theSize
== 0) {
2283 debugs(9, 2, "SIZE reported " <<
2284 ftpState
->ctrl
.last_reply
<< " on " <<
2285 ftpState
->title_url
);
2286 ftpState
->theSize
= -1;
2288 } else if (code
< 0) {
2293 ftpSendPassive(ftpState
);
2297 \ingroup ServerProtocolFTPInternal
2300 ftpReadEPSV(FtpStateData
* ftpState
)
2302 int code
= ftpState
->ctrl
.replycode
;
2303 char h1
, h2
, h3
, h4
;
2306 Ip::Address ipa_remote
;
2307 int fd
= ftpState
->data
.fd
;
2311 if (code
!= 229 && code
!= 522) {
2313 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2314 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2315 * Its okay to re-send EPSV 1/2 but nothing else. */
2316 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ". Wrong accept code for EPSV");
2318 debugs(9, 2, "EPSV not supported by remote end");
2319 ftpState
->state
= SENT_EPSV_1
; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2321 ftpSendPassive(ftpState
);
2326 /* server response with list of supported methods */
2327 /* 522 Network protocol not supported, use (1) */
2328 /* 522 Network protocol not supported, use (1,2) */
2329 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
2330 * which means close data + control without self-destructing and re-open from scratch. */
2331 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2332 buf
= ftpState
->ctrl
.last_reply
;
2333 while (buf
!= NULL
&& *buf
!= '\0' && *buf
!= '\n' && *buf
!= '(') ++buf
;
2334 if (buf
!= NULL
&& *buf
== '\n') ++buf
;
2336 if (buf
== NULL
|| *buf
== '\0') {
2337 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2338 debugs(9, DBG_IMPORTANT
, "Broken FTP Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ". 522 error missing protocol negotiation hints");
2339 ftpSendPassive(ftpState
);
2340 } else if (strcmp(buf
, "(1)") == 0) {
2341 ftpState
->state
= SENT_EPSV_2
; /* simulate having sent and failed EPSV 2 */
2342 ftpSendPassive(ftpState
);
2343 } else if (strcmp(buf
, "(2)") == 0) {
2345 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2346 if (ftpState
->state
== SENT_EPSV_2
) {
2347 ftpSendEPRT(ftpState
);
2349 /* or try the next Passive mode down the chain. */
2350 ftpSendPassive(ftpState
);
2353 /* We do not support IPv6. Remote server requires it.
2354 So we must simulate having failed all EPSV methods. */
2355 ftpState
->state
= SENT_EPSV_1
;
2356 ftpSendPassive(ftpState
);
2359 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2360 debugs(9, DBG_IMPORTANT
, "WARNING: Server at " << fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< " sent unknown protocol negotiation hint: " << buf
);
2361 ftpSendPassive(ftpState
);
2366 /* 229 Entering Extended Passive Mode (|||port|) */
2367 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2368 debugs(9, 5, "scanning: " << ftpState
->ctrl
.last_reply
);
2370 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "(");
2372 n
= sscanf(buf
, "(%c%c%c%hu%c)", &h1
, &h2
, &h3
, &port
, &h4
);
2374 if (h1
!= h2
|| h1
!= h3
|| h1
!= h4
) {
2375 debugs(9, DBG_IMPORTANT
, "Invalid EPSV reply from " <<
2376 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2377 ftpState
->ctrl
.last_reply
);
2379 ftpSendPassive(ftpState
);
2384 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2385 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2386 ftpState
->ctrl
.last_reply
);
2388 ftpSendPassive(ftpState
);
2392 if (Config
.Ftp
.sanitycheck
) {
2394 debugs(9, DBG_IMPORTANT
, "Unsafe EPSV reply from " <<
2395 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2396 ftpState
->ctrl
.last_reply
);
2398 ftpSendPassive(ftpState
);
2403 ftpState
->data
.port
= port
;
2405 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.fd
].ipaddr
);
2407 safe_free(ftpState
->ctrl
.last_command
);
2409 safe_free(ftpState
->ctrl
.last_reply
);
2411 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2413 debugs(9, 3, HERE
<< "connecting to " << ftpState
->data
.host
<< ", port " << ftpState
->data
.port
);
2415 commConnectStart(fd
, ftpState
->data
.host
, port
, FtpStateData::ftpPasvCallback
, ftpState
);
2418 /** \ingroup ServerProtocolFTPInternal
2420 * Send Passive connection request.
2421 * Default method is to use modern EPSV request.
2422 * The failover mechanism should check for previous state and re-call with alternates on failure.
2425 ftpSendPassive(FtpStateData
* ftpState
)
2428 struct addrinfo
*AI
= NULL
;
2430 /** Checks the server control channel is still available before running. */
2431 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
2437 * Checks for EPSV ALL special conditions:
2438 * If enabled to be sent, squid MUST NOT request any other connect methods.
2439 * If 'ALL' is sent and fails the entire FTP Session fails.
2440 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
2441 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
&& ftpState
->state
== SENT_EPSV_1
) {
2442 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2448 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
2449 if (ftpState
->request
->method
== METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
2450 ftpState
->processHeadResponse(); // may call serverComplete
2454 /// Closes any old FTP-Data connection which may exist. */
2455 ftpState
->data
.close();
2458 * Checks for previous EPSV/PASV failures on this server/session.
2459 * Diverts to EPRT immediately if they are not working. */
2460 if (!ftpState
->flags
.pasv_supported
) {
2461 ftpSendEPRT(ftpState
);
2466 * Locates the Address of the remote server. */
2467 addr
.InitAddrInfo(AI
);
2469 if (getsockname(ftpState
->ctrl
.fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2470 /** If it cannot be located the FTP Session is killed. */
2471 addr
.FreeAddrInfo(AI
);
2472 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << ftpState
->ctrl
.fd
<< ",'" << addr
<< "',...): " << xstrerror());
2478 addr
.FreeAddrInfo(AI
);
2481 * Send EPSV (ALL,2,1) or PASV on the control channel.
2483 * - EPSV ALL is used if enabled.
2484 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2485 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
2486 * - PASV is used if EPSV 1 fails.
2488 switch (ftpState
->state
) {
2489 case SENT_EPSV_ALL
: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2490 ftpState
->flags
.epsv_all_sent
= true;
2491 if (addr
.IsIPv6()) {
2492 debugs(9, 5, HERE
<< "FTP Channel is IPv6 (" << addr
<< ") attempting EPSV 2 after EPSV ALL has failed.");
2493 snprintf(cbuf
, 1024, "EPSV 2\r\n");
2494 ftpState
->state
= SENT_EPSV_2
;
2497 // else fall through to skip EPSV 2
2499 case SENT_EPSV_2
: /* EPSV IPv6 failed. Try EPSV IPv4 */
2500 if (addr
.IsIPv4()) {
2501 debugs(9, 5, HERE
<< "FTP Channel is IPv4 (" << addr
<< ") attempting EPSV 1 after EPSV ALL has failed.");
2502 snprintf(cbuf
, 1024, "EPSV 1\r\n");
2503 ftpState
->state
= SENT_EPSV_1
;
2505 } else if (ftpState
->flags
.epsv_all_sent
) {
2506 debugs(9, DBG_IMPORTANT
, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2510 // else fall through to skip EPSV 1
2512 case SENT_EPSV_1
: /* EPSV options exhausted. Try PASV now. */
2513 debugs(9, 5, HERE
<< "FTP Channel (" << addr
<< ") rejects EPSV connection attempts. Trying PASV instead.");
2514 snprintf(cbuf
, 1024, "PASV\r\n");
2515 ftpState
->state
= SENT_PASV
;
2519 if (!Config
.Ftp
.epsv
) {
2520 debugs(9, 5, HERE
<< "EPSV support manually disabled. Sending PASV for FTP Channel (" << addr
<<")");
2521 snprintf(cbuf
, 1024, "PASV\r\n");
2522 ftpState
->state
= SENT_PASV
;
2523 } else if (Config
.Ftp
.epsv_all
) {
2524 debugs(9, 5, HERE
<< "EPSV ALL manually enabled. Attempting with FTP Channel (" << addr
<<")");
2525 snprintf(cbuf
, 1024, "EPSV ALL\r\n");
2526 ftpState
->state
= SENT_EPSV_ALL
;
2527 /* block other non-EPSV connections being attempted */
2528 ftpState
->flags
.epsv_all_sent
= true;
2531 if (addr
.IsIPv6()) {
2532 debugs(9, 5, HERE
<< "FTP Channel (" << addr
<< "). Sending default EPSV 2");
2533 snprintf(cbuf
, 1024, "EPSV 2\r\n");
2534 ftpState
->state
= SENT_EPSV_2
;
2537 if (addr
.IsIPv4()) {
2538 debugs(9, 5, HERE
<< "Channel (" << addr
<<"). Sending default EPSV 1");
2539 snprintf(cbuf
, 1024, "EPSV 1\r\n");
2540 ftpState
->state
= SENT_EPSV_1
;
2546 /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */
2548 int fd
= comm_open(SOCK_STREAM
,
2552 ftpState
->entry
->url());
2554 debugs(9, 3, HERE
<< "Unconnected data socket created on FD " << fd
<< " from " << addr
);
2561 ftpState
->data
.opened(fd
, ftpState
->dataCloser());
2562 ftpState
->writeCommand(cbuf
);
2565 * ugly hack for ftp servers like ftp.netscape.com that sometimes
2566 * dont acknowledge PASV commands.
2568 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2569 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
2570 TimeoutDialer(ftpState
,&FtpStateData::ftpTimeout
));
2572 commSetTimeout(ftpState
->data
.fd
, 15, timeoutCall
);
2576 FtpStateData::processHeadResponse()
2578 debugs(9, 5, HERE
<< "handling HEAD response");
2580 appendSuccessHeader();
2583 * On rare occasions I'm seeing the entry get aborted after
2584 * ftpReadControlReply() and before here, probably when
2585 * trying to write to the client.
2587 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2588 abortTransaction("entry aborted while processing HEAD");
2593 if (adaptationAccessCheckPending
) {
2594 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
2599 // processReplyBody calls serverComplete() since there is no body
2603 /// \ingroup ServerProtocolFTPInternal
2605 ftpReadPasv(FtpStateData
* ftpState
)
2607 int code
= ftpState
->ctrl
.replycode
;
2612 Ip::Address ipa_remote
;
2613 int fd
= ftpState
->data
.fd
;
2615 LOCAL_ARRAY(char, ipaddr
, 1024);
2619 debugs(9, 2, "PASV not supported by remote end");
2620 ftpSendEPRT(ftpState
);
2624 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2625 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2626 debugs(9, 5, HERE
<< "scanning: " << ftpState
->ctrl
.last_reply
);
2628 buf
= ftpState
->ctrl
.last_reply
+ strcspn(ftpState
->ctrl
.last_reply
, "0123456789");
2630 n
= sscanf(buf
, "%d,%d,%d,%d,%d,%d", &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
2632 if (n
!= 6 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
2633 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2634 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2635 ftpState
->ctrl
.last_reply
);
2637 ftpSendEPRT(ftpState
);
2641 snprintf(ipaddr
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
2643 ipa_remote
= ipaddr
;
2645 if ( ipa_remote
.IsAnyAddr() ) {
2646 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2647 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2648 ftpState
->ctrl
.last_reply
);
2650 ftpSendEPRT(ftpState
);
2654 port
= ((p1
<< 8) + p2
);
2657 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2658 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2659 ftpState
->ctrl
.last_reply
);
2661 ftpSendEPRT(ftpState
);
2665 if (Config
.Ftp
.sanitycheck
) {
2667 debugs(9, DBG_IMPORTANT
, "Unsafe PASV reply from " <<
2668 fd_table
[ftpState
->ctrl
.fd
].ipaddr
<< ": " <<
2669 ftpState
->ctrl
.last_reply
);
2671 ftpSendEPRT(ftpState
);
2676 ftpState
->data
.port
= port
;
2678 if (Config
.Ftp
.sanitycheck
)
2679 ftpState
->data
.host
= xstrdup(fd_table
[ftpState
->ctrl
.fd
].ipaddr
);
2681 ftpState
->data
.host
= xstrdup(ipaddr
);
2683 safe_free(ftpState
->ctrl
.last_command
);
2685 safe_free(ftpState
->ctrl
.last_reply
);
2687 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
2689 debugs(9, 3, HERE
<< "connecting to " << ftpState
->data
.host
<< ", port " << ftpState
->data
.port
);
2691 commConnectStart(fd
, ipaddr
, port
, FtpStateData::ftpPasvCallback
, ftpState
);
2695 FtpStateData::ftpPasvCallback(int fd
, const DnsLookupDetails
&dns
, comm_err_t status
, int xerrno
, void *data
)
2697 FtpStateData
*ftpState
= (FtpStateData
*)data
;
2699 ftpState
->request
->recordLookup(dns
);
2701 if (status
!= COMM_OK
) {
2702 debugs(9, 2, HERE
<< "Failed to connect. Retrying without PASV.");
2703 ftpState
->fwd
->dontRetry(false); /* this is a retryable error */
2704 ftpState
->fwd
->ftpPasvFailed(true);
2705 ftpState
->failed(ERR_NONE
, 0);
2706 /* failed closes ctrl.fd and frees ftpState */
2710 ftpRestOrList(ftpState
);
2713 /// \ingroup ServerProtocolFTPInternal
2715 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
2719 struct addrinfo
*AI
= NULL
;
2723 /// Close old data channels, if any. We may open a new one below.
2724 ftpState
->data
.close();
2727 * Set up a listen socket on the same local address as the
2728 * control connection.
2731 addr
.InitAddrInfo(AI
);
2733 x
= getsockname(ftpState
->ctrl
.fd
, AI
->ai_addr
, &AI
->ai_addrlen
);
2737 addr
.FreeAddrInfo(AI
);
2740 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << ftpState
->ctrl
.fd
<< ",..): " << xstrerror());
2745 * REUSEADDR is needed in fallback mode, since the same port is
2746 * used for both control and data.
2749 setsockopt(ftpState
->ctrl
.fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
2751 /* if not running in fallback mode a new port needs to be retrieved */
2755 fd
= comm_open(SOCK_STREAM
,
2758 COMM_NONBLOCKING
| (fallback
? COMM_REUSEADDR
: 0),
2759 ftpState
->entry
->url());
2760 debugs(9, 3, HERE
<< "Unconnected data socket created on FD " << fd
);
2763 debugs(9, DBG_CRITICAL
, HERE
<< "comm_open failed");
2767 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
2768 AsyncCall::Pointer acceptCall
= asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
2769 acceptDialer(ftpState
, &FtpStateData::ftpAcceptDataConnection
));
2770 ftpState
->data
.listener
= new Comm::ListenStateData(fd
, acceptCall
, false);
2772 if (!ftpState
->data
.listener
|| ftpState
->data
.listener
->errcode
< 0) {
2777 ftpState
->data
.opened(fd
, ftpState
->dataCloser());
2778 ftpState
->data
.port
= comm_local_port(fd
);
2779 ftpState
->data
.host
= NULL
;
2783 /// \ingroup ServerProtocolFTPInternal
2785 ftpSendPORT(FtpStateData
* ftpState
)
2789 struct addrinfo
*AI
= NULL
;
2790 unsigned char *addrptr
;
2791 unsigned char *portptr
;
2793 /* check the server control channel is still available */
2794 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
2797 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2798 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2803 ftpState
->flags
.pasv_supported
= 0;
2804 fd
= ftpOpenListenSocket(ftpState
, 0);
2805 ipa
.InitAddrInfo(AI
);
2807 if (getsockname(fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2808 ipa
.FreeAddrInfo(AI
);
2809 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << fd
<< ",..): " << xstrerror());
2811 /* XXX Need to set error message */
2817 if ( AI
->ai_addrlen
!= sizeof(struct sockaddr_in
) ) {
2818 ipa
.FreeAddrInfo(AI
);
2819 /* IPv6 CANNOT send PORT command. */
2820 /* we got here by attempting and failing an EPRT */
2821 /* using the same reply code should simulate a PORT failure */
2822 ftpReadPORT(ftpState
);
2827 addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
2828 portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
2829 snprintf(cbuf
, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
2830 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
2831 portptr
[0], portptr
[1]);
2832 ftpState
->writeCommand(cbuf
);
2833 ftpState
->state
= SENT_PORT
;
2835 ipa
.FreeAddrInfo(AI
);
2838 /// \ingroup ServerProtocolFTPInternal
2840 ftpReadPORT(FtpStateData
* ftpState
)
2842 int code
= ftpState
->ctrl
.replycode
;
2846 /* Fall back on using the same port as the control connection */
2847 debugs(9, 3, "PORT not supported by remote end");
2848 ftpOpenListenSocket(ftpState
, 1);
2851 ftpRestOrList(ftpState
);
2854 /// \ingroup ServerProtocolFTPInternal
2856 ftpSendEPRT(FtpStateData
* ftpState
)
2860 struct addrinfo
*AI
= NULL
;
2861 char buf
[MAX_IPSTRLEN
];
2863 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
2864 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2869 ftpState
->flags
.pasv_supported
= 0;
2870 fd
= ftpOpenListenSocket(ftpState
, 0);
2872 Ip::Address::InitAddrInfo(AI
);
2874 if (getsockname(fd
, AI
->ai_addr
, &AI
->ai_addrlen
)) {
2875 Ip::Address::FreeAddrInfo(AI
);
2876 debugs(9, DBG_CRITICAL
, HERE
<< "getsockname(" << fd
<< ",..): " << xstrerror());
2878 /* XXX Need to set error message */
2885 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2886 /* Which can be used by EITHER protocol. */
2887 snprintf(cbuf
, 1024, "EPRT |%d|%s|%d|\r\n",
2888 ( addr
.IsIPv6() ? 2 : 1 ),
2889 addr
.NtoA(buf
,MAX_IPSTRLEN
),
2892 ftpState
->writeCommand(cbuf
);
2893 ftpState
->state
= SENT_EPRT
;
2895 Ip::Address::FreeAddrInfo(AI
);
2899 ftpReadEPRT(FtpStateData
* ftpState
)
2901 int code
= ftpState
->ctrl
.replycode
;
2905 /* Failover to attempting old PORT command. */
2906 debugs(9, 3, "EPRT not supported by remote end");
2907 ftpSendPORT(ftpState
);
2911 ftpRestOrList(ftpState
);
2915 \ingroup ServerProtocolFTPInternal
2917 * "read" handler to accept FTP data connections.
2919 \param io comm accept(2) callback parameters
2921 void FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
2923 char ntoapeer
[MAX_IPSTRLEN
];
2924 debugs(9, 3, "ftpAcceptDataConnection");
2926 // one connection accepted. the handler has stopped listening. drop our local pointer to it.
2927 data
.listener
= NULL
;
2929 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2930 abortTransaction("entry aborted when accepting data conn");
2935 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2936 * made by the remote client which is connected to the FTP control socket.
2937 * This prevents third-party hacks, but also third-party load balancing handshakes.
2939 if (Config
.Ftp
.sanitycheck
) {
2940 io
.details
.peer
.NtoA(ntoapeer
,MAX_IPSTRLEN
);
2942 if (strcmp(fd_table
[ctrl
.fd
].ipaddr
, ntoapeer
) != 0) {
2943 debugs(9, DBG_IMPORTANT
,
2944 "FTP data connection from unexpected server (" <<
2945 io
.details
.peer
<< "), expecting " <<
2946 fd_table
[ctrl
.fd
].ipaddr
);
2948 /* close the bad soures connection down ASAP. */
2951 /* we are ony accepting once, so need to re-open the listener socket. */
2952 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
2953 AsyncCall::Pointer acceptCall
= asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
2954 acceptDialer(this, &FtpStateData::ftpAcceptDataConnection
));
2955 data
.listener
= new Comm::ListenStateData(data
.fd
, acceptCall
, false);
2960 if (io
.flag
!= COMM_OK
) {
2961 debugs(9, DBG_IMPORTANT
, "ftpHandleDataAccept: FD " << io
.nfd
<< ": " << xstrerr(io
.xerrno
));
2962 /** \todo XXX Need to set error message */
2968 * Replace the Listen socket with the accepted data socket */
2970 data
.opened(io
.nfd
, dataCloser());
2971 data
.port
= io
.details
.peer
.GetPort();
2972 io
.details
.peer
.NtoA(data
.host
,SQUIDHOSTNAMELEN
);
2974 debugs(9, 3, "ftpAcceptDataConnection: Connected data socket on " <<
2975 "FD " << io
.nfd
<< " to " << io
.details
.peer
<< " FD table says: " <<
2976 "ctrl-peer= " << fd_table
[ctrl
.fd
].ipaddr
<< ", " <<
2977 "data-peer= " << fd_table
[data
.fd
].ipaddr
);
2980 AsyncCall::Pointer nullCall
= NULL
;
2981 commSetTimeout(ctrl
.fd
, -1, nullCall
);
2983 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
2984 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
2985 TimeoutDialer(this,&FtpStateData::ftpTimeout
));
2986 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
2988 /*\todo XXX We should have a flag to track connect state...
2989 * host NULL -> not connected, port == local port
2990 * host set -> connected, port == remote port
2992 /* Restart state (SENT_NLST/LIST/RETR) */
2993 FTP_SM_FUNCS
[state
] (this);
2996 /// \ingroup ServerProtocolFTPInternal
2998 ftpRestOrList(FtpStateData
* ftpState
)
3002 if (ftpState
->typecode
== 'D') {
3003 ftpState
->flags
.isdir
= 1;
3005 if (ftpState
->flags
.put
) {
3006 ftpSendMkdir(ftpState
); /* PUT name;type=d */
3008 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3010 } else if (ftpState
->flags
.put
) {
3011 ftpSendStor(ftpState
);
3012 } else if (ftpState
->flags
.isdir
)
3013 ftpSendList(ftpState
);
3014 else if (ftpState
->restartable())
3015 ftpSendRest(ftpState
);
3017 ftpSendRetr(ftpState
);
3020 /// \ingroup ServerProtocolFTPInternal
3022 ftpSendStor(FtpStateData
* ftpState
)
3024 /* check the server control channel is still available */
3025 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
3030 if (ftpState
->filepath
!= NULL
) {
3031 /* Plain file upload */
3032 snprintf(cbuf
, 1024, "STOR %s\r\n", ftpState
->filepath
);
3033 ftpState
->writeCommand(cbuf
);
3034 ftpState
->state
= SENT_STOR
;
3035 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
3036 /* File upload without a filename. use STOU to generate one */
3037 snprintf(cbuf
, 1024, "STOU\r\n");
3038 ftpState
->writeCommand(cbuf
);
3039 ftpState
->state
= SENT_STOR
;
3041 /* No file to transfer. Only create directories if needed */
3042 ftpSendReply(ftpState
);
3046 /// \ingroup ServerProtocolFTPInternal
3047 /// \deprecated use ftpState->readStor() instead.
3049 ftpReadStor(FtpStateData
* ftpState
)
3051 ftpState
->readStor();
3054 void FtpStateData::readStor()
3056 int code
= ctrl
.replycode
;
3059 if (code
== 125 || (code
== 150 && data
.host
)) {
3060 if (!startRequestBodyFlow()) { // register to receive body data
3066 * When client status is 125, or 150 without a hostname, Begin data transfer. */
3067 debugs(9, 3, HERE
<< "starting data transfer");
3068 sendMoreRequestBody();
3070 * Cancel the timeout on the Control socket and
3071 * establish one on the data socket.
3073 AsyncCall::Pointer nullCall
= NULL
;
3074 commSetTimeout(ctrl
.fd
, -1, nullCall
);
3076 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3077 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
3078 TimeoutDialer(this,&FtpStateData::ftpTimeout
));
3080 commSetTimeout(data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3082 state
= WRITING_DATA
;
3083 debugs(9, 3, HERE
<< "writing data channel");
3084 } else if (code
== 150) {
3086 * When client code is 150 with a hostname, Accept data channel. */
3087 debugs(9, 3, "ftpReadStor: accepting data channel");
3088 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3089 AsyncCall::Pointer acceptCall
= asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3090 acceptDialer(this, &FtpStateData::ftpAcceptDataConnection
));
3092 data
.listener
= new Comm::ListenStateData(data
.fd
, acceptCall
, false);
3094 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
3099 /// \ingroup ServerProtocolFTPInternal
3101 ftpSendRest(FtpStateData
* ftpState
)
3103 /* check the server control channel is still available */
3104 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
3109 snprintf(cbuf
, 1024, "REST %"PRId64
"\r\n", ftpState
->restart_offset
);
3110 ftpState
->writeCommand(cbuf
);
3111 ftpState
->state
= SENT_REST
;
3115 FtpStateData::restartable()
3117 if (restart_offset
> 0)
3120 if (!request
->range
)
3129 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
3131 if (desired_offset
<= 0)
3134 if (desired_offset
>= theSize
)
3137 restart_offset
= desired_offset
;
3141 /// \ingroup ServerProtocolFTPInternal
3143 ftpReadRest(FtpStateData
* ftpState
)
3145 int code
= ftpState
->ctrl
.replycode
;
3147 assert(ftpState
->restart_offset
> 0);
3150 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
3151 ftpSendRetr(ftpState
);
3152 } else if (code
> 0) {
3153 debugs(9, 3, HERE
<< "REST not supported");
3154 ftpState
->flags
.rest_supported
= 0;
3155 ftpSendRetr(ftpState
);
3161 /// \ingroup ServerProtocolFTPInternal
3163 ftpSendList(FtpStateData
* ftpState
)
3165 /* check the server control channel is still available */
3166 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
3171 if (ftpState
->filepath
) {
3172 snprintf(cbuf
, 1024, "LIST %s\r\n", ftpState
->filepath
);
3174 snprintf(cbuf
, 1024, "LIST\r\n");
3177 ftpState
->writeCommand(cbuf
);
3178 ftpState
->state
= SENT_LIST
;
3181 /// \ingroup ServerProtocolFTPInternal
3183 ftpSendNlst(FtpStateData
* ftpState
)
3185 /* check the server control channel is still available */
3186 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
3191 ftpState
->flags
.tried_nlst
= 1;
3193 if (ftpState
->filepath
) {
3194 snprintf(cbuf
, 1024, "NLST %s\r\n", ftpState
->filepath
);
3196 snprintf(cbuf
, 1024, "NLST\r\n");
3199 ftpState
->writeCommand(cbuf
);
3200 ftpState
->state
= SENT_NLST
;
3203 /// \ingroup ServerProtocolFTPInternal
3205 ftpReadList(FtpStateData
* ftpState
)
3207 int code
= ftpState
->ctrl
.replycode
;
3210 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
3211 /* Begin data transfer */
3212 /* XXX what about Config.Timeout.read? */
3213 ftpState
->maybeReadVirginBody();
3214 ftpState
->state
= READING_DATA
;
3216 * Cancel the timeout on the Control socket and establish one
3217 * on the data socket
3219 AsyncCall::Pointer nullCall
= NULL
;
3220 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3222 } else if (code
== 150) {
3223 /* Accept data channel */
3224 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3225 AsyncCall::Pointer acceptCall
= asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3226 acceptDialer(ftpState
, &FtpStateData::ftpAcceptDataConnection
));
3228 ftpState
->data
.listener
= new Comm::ListenStateData(ftpState
->data
.fd
, acceptCall
, false);
3230 * Cancel the timeout on the Control socket and establish one
3231 * on the data socket
3233 AsyncCall::Pointer nullCall
= NULL
;
3234 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3236 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3237 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
3238 TimeoutDialer(ftpState
,&FtpStateData::ftpTimeout
));
3239 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3241 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
3242 ftpSendNlst(ftpState
);
3249 /// \ingroup ServerProtocolFTPInternal
3251 ftpSendRetr(FtpStateData
* ftpState
)
3253 /* check the server control channel is still available */
3254 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
3259 assert(ftpState
->filepath
!= NULL
);
3260 snprintf(cbuf
, 1024, "RETR %s\r\n", ftpState
->filepath
);
3261 ftpState
->writeCommand(cbuf
);
3262 ftpState
->state
= SENT_RETR
;
3265 /// \ingroup ServerProtocolFTPInternal
3267 ftpReadRetr(FtpStateData
* ftpState
)
3269 int code
= ftpState
->ctrl
.replycode
;
3272 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
3273 /* Begin data transfer */
3274 debugs(9, 3, HERE
<< "reading data channel");
3275 /* XXX what about Config.Timeout.read? */
3276 ftpState
->maybeReadVirginBody();
3277 ftpState
->state
= READING_DATA
;
3279 * Cancel the timeout on the Control socket and establish one
3280 * on the data socket
3282 AsyncCall::Pointer nullCall
= NULL
;
3283 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3284 } else if (code
== 150) {
3285 /* Accept data channel */
3286 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> acceptDialer
;
3287 AsyncCall::Pointer acceptCall
= asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3288 acceptDialer(ftpState
, &FtpStateData::ftpAcceptDataConnection
));
3289 ftpState
->data
.listener
= new Comm::ListenStateData(ftpState
->data
.fd
, acceptCall
, false);
3291 * Cancel the timeout on the Control socket and establish one
3292 * on the data socket
3294 AsyncCall::Pointer nullCall
= NULL
;
3295 commSetTimeout(ftpState
->ctrl
.fd
, -1, nullCall
);
3297 typedef CommCbMemFunT
<FtpStateData
, CommTimeoutCbParams
> TimeoutDialer
;
3298 AsyncCall::Pointer timeoutCall
= asyncCall(9, 5, "FtpStateData::ftpTimeout",
3299 TimeoutDialer(ftpState
,&FtpStateData::ftpTimeout
));
3300 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, timeoutCall
);
3301 } else if (code
>= 300) {
3302 if (!ftpState
->flags
.try_slash_hack
) {
3303 /* Try this as a directory missing trailing slash... */
3304 ftpState
->hackShortcut(ftpSendCwd
);
3314 * Generate the HTTP headers and template fluff around an FTP
3315 * directory listing display.
3318 FtpStateData::completedListing()
3322 ErrorState
*ferr
= errorCon(ERR_DIR_LISTING
, HTTP_OK
, request
);
3323 ferr
->ftp
.listing
= &listing
;
3324 ferr
->ftp
.cwd_msg
= xstrdup(cwd_message
.termedBuf());
3325 ferr
->ftp
.server_msg
= ctrl
.message
;
3326 ctrl
.message
= NULL
;
3327 entry
->replaceHttpReply( ferr
->BuildHttpReply() );
3328 errorStateFree(ferr
);
3329 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3335 /// \ingroup ServerProtocolFTPInternal
3337 ftpReadTransferDone(FtpStateData
* ftpState
)
3339 int code
= ftpState
->ctrl
.replycode
;
3342 if (code
== 226 || code
== 250) {
3343 /* Connection closed; retrieval done. */
3344 if (ftpState
->flags
.listing
) {
3345 ftpState
->completedListing();
3346 /* QUIT operation handles sending the reply to client */
3348 ftpSendQuit(ftpState
);
3349 } else { /* != 226 */
3350 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
3351 ftpState
->failed(ERR_FTP_FAILURE
, 0);
3352 /* failed closes ctrl.fd and frees ftpState */
3357 // premature end of the request body
3359 FtpStateData::handleRequestBodyProducerAborted()
3361 ServerStateData::handleRequestBodyProducerAborted();
3362 debugs(9, 3, HERE
<< "ftpState=" << this);
3363 failed(ERR_READ_ERROR
, 0);
3367 * This will be called when the put write is completed
3370 FtpStateData::sentRequestBody(const CommIoCbParams
&io
)
3373 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, io
.size
);
3374 ServerStateData::sentRequestBody(io
);
3377 /// \ingroup ServerProtocolFTPInternal
3379 ftpWriteTransferDone(FtpStateData
* ftpState
)
3381 int code
= ftpState
->ctrl
.replycode
;
3384 if (!(code
== 226 || code
== 250)) {
3385 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
3386 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
3390 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
3391 ftpSendReply(ftpState
);
3394 /// \ingroup ServerProtocolFTPInternal
3396 ftpSendQuit(FtpStateData
* ftpState
)
3398 /* check the server control channel is still available */
3399 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
3402 snprintf(cbuf
, 1024, "QUIT\r\n");
3403 ftpState
->writeCommand(cbuf
);
3404 ftpState
->state
= SENT_QUIT
;
3408 * \ingroup ServerProtocolFTPInternal
3410 * This completes a client FTP operation with success or other page
3411 * generated and stored in the entry field by the code issuing QUIT.
3414 ftpReadQuit(FtpStateData
* ftpState
)
3416 ftpState
->serverComplete();
3419 /// \ingroup ServerProtocolFTPInternal
3421 ftpTrySlashHack(FtpStateData
* ftpState
)
3424 ftpState
->flags
.try_slash_hack
= 1;
3425 /* Free old paths */
3429 if (ftpState
->pathcomps
)
3430 wordlistDestroy(&ftpState
->pathcomps
);
3432 safe_free(ftpState
->filepath
);
3434 /* Build the new path (urlpath begins with /) */
3435 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
3437 rfc1738_unescape(path
);
3439 ftpState
->filepath
= path
;
3442 ftpGetFile(ftpState
);
3446 * Forget hack status. Next error is shown to the user
3449 FtpStateData::unhack()
3453 if (old_request
!= NULL
) {
3454 safe_free(old_request
);
3455 safe_free(old_reply
);
3460 FtpStateData::hackShortcut(FTPSM
* nextState
)
3462 /* Clear some unwanted state */
3463 setCurrentOffset(0);
3465 /* Save old error message & some state info */
3469 if (old_request
== NULL
) {
3470 old_request
= ctrl
.last_command
;
3471 ctrl
.last_command
= NULL
;
3472 old_reply
= ctrl
.last_reply
;
3473 ctrl
.last_reply
= NULL
;
3475 if (pathcomps
== NULL
&& filepath
!= NULL
)
3476 old_filepath
= xstrdup(filepath
);
3479 /* Jump to the "hack" state */
3483 /// \ingroup ServerProtocolFTPInternal
3485 ftpFail(FtpStateData
*ftpState
)
3487 debugs(9, 6, HERE
<< "flags(" <<
3488 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
3489 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
3490 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
3491 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
3493 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
3494 if (!ftpState
->flags
.isdir
&& /* Not a directory */
3495 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
3496 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
3497 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
3499 switch (ftpState
->state
) {
3504 /* Try the / hack */
3505 ftpState
->hackShortcut(ftpTrySlashHack
);
3513 ftpState
->failed(ERR_NONE
, 0);
3514 /* failed() closes ctrl.fd and frees this */
3518 FtpStateData::failed(err_type error
, int xerrno
)
3520 debugs(9,3,HERE
<< "entry-null=" << (entry
?entry
->isEmpty():0) << ", entry=" << entry
);
3521 if (entry
->isEmpty())
3522 failedErrorMessage(error
, xerrno
);
3528 FtpStateData::failedErrorMessage(err_type error
, int xerrno
)
3531 const char *command
, *reply
;
3533 /* Translate FTP errors into HTTP errors */
3546 if (ctrl
.replycode
> 500)
3548 ftperr
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
, fwd
->request
);
3550 ftperr
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_UNAUTHORIZED
, fwd
->request
);
3552 else if (ctrl
.replycode
== 421)
3553 ftperr
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
, fwd
->request
);
3560 if (ctrl
.replycode
== 550)
3561 ftperr
= errorCon(ERR_FTP_NOT_FOUND
, HTTP_NOT_FOUND
, fwd
->request
);
3571 case ERR_READ_TIMEOUT
:
3572 ftperr
= errorCon(error
, HTTP_GATEWAY_TIMEOUT
, fwd
->request
);
3576 ftperr
= errorCon(error
, HTTP_BAD_GATEWAY
, fwd
->request
);
3581 ftperr
= errorCon(ERR_FTP_FAILURE
, HTTP_BAD_GATEWAY
, fwd
->request
);
3583 ftperr
->xerrno
= xerrno
;
3585 ftperr
->ftp
.server_msg
= ctrl
.message
;
3586 ctrl
.message
= NULL
;
3589 command
= old_request
;
3591 command
= ctrl
.last_command
;
3593 if (command
&& strncmp(command
, "PASS", 4) == 0)
3594 command
= "PASS <yourpassword>";
3599 reply
= ctrl
.last_reply
;
3602 ftperr
->ftp
.request
= xstrdup(command
);
3605 ftperr
->ftp
.reply
= xstrdup(reply
);
3607 entry
->replaceHttpReply( ftperr
->BuildHttpReply() );
3608 errorStateFree(ftperr
);
3611 /// \ingroup ServerProtocolFTPInternal
3613 ftpSendReply(FtpStateData
* ftpState
)
3616 int code
= ftpState
->ctrl
.replycode
;
3617 http_status http_code
;
3618 err_type err_code
= ERR_NONE
;
3620 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
3622 if (cbdataReferenceValid(ftpState
))
3623 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
3625 if (code
== 226 || code
== 250) {
3626 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
3627 http_code
= (ftpState
->mdtm
> 0) ? HTTP_ACCEPTED
: HTTP_CREATED
;
3628 } else if (code
== 227) {
3629 err_code
= ERR_FTP_PUT_CREATED
;
3630 http_code
= HTTP_CREATED
;
3632 err_code
= ERR_FTP_PUT_ERROR
;
3633 http_code
= HTTP_INTERNAL_SERVER_ERROR
;
3636 err
= errorCon(err_code
, http_code
, ftpState
->request
);
3638 if (ftpState
->old_request
)
3639 err
->ftp
.request
= xstrdup(ftpState
->old_request
);
3641 err
->ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
3643 if (ftpState
->old_reply
)
3644 err
->ftp
.reply
= xstrdup(ftpState
->old_reply
);
3645 else if (ftpState
->ctrl
.last_reply
)
3646 err
->ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
3648 err
->ftp
.reply
= xstrdup("");
3650 ftpState
->entry
->replaceHttpReply( err
->BuildHttpReply() );
3651 errorStateFree(err
);
3653 ftpSendQuit(ftpState
);
3657 FtpStateData::appendSuccessHeader()
3659 const char *mime_type
= NULL
;
3660 const char *mime_enc
= NULL
;
3661 String urlpath
= request
->urlpath
;
3662 const char *filename
= NULL
;
3663 const char *t
= NULL
;
3667 if (flags
.http_header_sent
)
3670 HttpReply
*reply
= new HttpReply
;
3672 flags
.http_header_sent
= 1;
3674 assert(entry
->isEmpty());
3676 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
3678 entry
->buffer(); /* released when done processing current data payload */
3680 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
3683 mime_type
= "text/html";
3688 mime_type
= "application/octet-stream";
3689 mime_enc
= mimeGetContentEncoding(filename
);
3693 mime_type
= "text/plain";
3697 mime_type
= mimeGetContentType(filename
);
3698 mime_enc
= mimeGetContentEncoding(filename
);
3703 /* set standard stuff */
3705 if (0 == getCurrentOffset()) {
3707 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3708 } else if (theSize
< getCurrentOffset()) {
3711 * offset should not be larger than theSize. We should
3712 * not be seeing this condition any more because we'll only
3713 * send REST if we know the theSize and if it is less than theSize.
3715 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
3716 " current offset=" << getCurrentOffset() <<
3717 ", but theSize=" << theSize
<<
3718 ". assuming full content response");
3719 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
3722 HttpHdrRangeSpec range_spec
;
3723 range_spec
.offset
= getCurrentOffset();
3724 range_spec
.length
= theSize
- getCurrentOffset();
3725 reply
->setHeaders(HTTP_PARTIAL_CONTENT
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
3726 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
3729 /* additional info */
3731 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
3733 setVirginReply(reply
);
3734 adaptOrFinalizeReply();
3738 FtpStateData::haveParsedReplyHeaders()
3740 ServerStateData::haveParsedReplyHeaders();
3742 StoreEntry
*e
= entry
;
3746 if (flags
.authenticated
) {
3748 * Authenticated requests can't be cached.
3751 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !getCurrentOffset()) {
3759 FtpStateData::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
3761 ErrorState
*err
= errorCon(ERR_CACHE_ACCESS_DENIED
, HTTP_UNAUTHORIZED
, request
);
3762 HttpReply
*newrep
= err
->BuildHttpReply();
3763 errorStateFree(err
);
3764 /* add Authenticate header */
3765 newrep
->header
.putAuth("Basic", realm
);
3770 \ingroup ServerProtocolFTPAPI
3771 \todo Should be a URL class API call.
3773 * Construct an URI with leading / in PATH portion for use by CWD command
3774 * possibly others. FTP encodes absolute paths as beginning with '/'
3775 * after the initial URI path delimiter, which happens to be / itself.
3776 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3777 * To encompass older software which compacts multiple // to / in transit
3778 * We use standard URI-encoding on the second / making it
3779 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3782 ftpUrlWith2f(HttpRequest
* request
)
3784 String newbuf
= "%2f";
3786 if (request
->protocol
!= PROTO_FTP
)
3789 if ( request
->urlpath
[0]=='/' ) {
3790 newbuf
.append(request
->urlpath
);
3791 request
->urlpath
.absorb(newbuf
);
3792 safe_free(request
->canonical
);
3793 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
3794 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
3795 request
->urlpath
.absorb(newbuf
);
3796 safe_free(request
->canonical
);
3799 return urlCanonical(request
);
3803 FtpStateData::printfReplyBody(const char *fmt
, ...)
3806 va_start (args
, fmt
);
3807 static char buf
[4096];
3809 vsnprintf(buf
, 4096, fmt
, args
);
3810 writeReplyBody(buf
, strlen(buf
));
3814 * Call this when there is data from the origin server
3815 * which should be sent to either StoreEntry, or to ICAP...
3818 FtpStateData::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
3820 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
3821 addVirginReplyBody(dataToWrite
, dataLength
);
3825 * called after we wrote the last byte of the request body
3828 FtpStateData::doneSendingRequestBody()
3832 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3833 * if transfer type is 'stream' call dataComplete()
3834 * otherwise leave open. (reschedule control channel read?)
3839 * A hack to ensure we do not double-complete on the forward entry.
3841 \todo FtpStateData logic should probably be rewritten to avoid
3842 * double-completion or FwdState should be rewritten to allow it.
3845 FtpStateData::completeForwarding()
3847 if (fwd
== NULL
|| flags
.completed_forwarding
) {
3848 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
3849 "double-complete on FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<<
3850 ", this " << this << ", fwd " << fwd
);
3854 flags
.completed_forwarding
= true;
3855 ServerStateData::completeForwarding();
3859 * Close the FTP server connection(s). Used by serverComplete().
3862 FtpStateData::closeServer()
3864 debugs(9,3, HERE
<< "closing FTP server FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<< ", this " << this);
3867 fwd
->unregister(ctrl
.fd
);
3875 * Did we close all FTP server connection(s)?
3877 \retval true Both server control and data channels are closed. And not waitigng for a new data connection to open.
3878 \retval false Either control channel or data is still active.
3881 FtpStateData::doneWithServer() const
3883 return ctrl
.fd
< 0 && data
.fd
< 0;
3887 * Have we lost the FTP server control channel?
3889 \retval true The server control channel is available.
3890 \retval false The server control channel is not available.
3893 FtpStateData::haveControlChannel(const char *caller_name
) const
3895 if (doneWithServer())
3898 /* doneWithServer() only checks BOTH channels are closed. */
3900 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
3901 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
3909 * Quickly abort the transaction
3911 \todo destruction should be sufficient as the destructor should cleanup,
3912 * including canceling close handlers
3915 FtpStateData::abortTransaction(const char *reason
)
3917 debugs(9, 3, HERE
<< "aborting transaction for " << reason
<<
3918 "; FD " << ctrl
.fd
<< ", Data FD " << data
.fd
<< ", this " << this);
3920 comm_close(ctrl
.fd
);
3924 fwd
->handleUnregisteredServerEnd();
3925 deleteThis("FtpStateData::abortTransaction");
3928 /// creates a data channel Comm close callback
3930 FtpStateData::dataCloser()
3932 typedef CommCbMemFunT
<FtpStateData
, CommCloseCbParams
> Dialer
;
3933 return asyncCall(9, 5, "FtpStateData::dataClosed",
3934 Dialer(this, &FtpStateData::dataClosed
));
3937 /// configures the channel with a descriptor and registers a close handler
3939 FtpChannel::opened(int aFd
, const AsyncCall::Pointer
&aCloser
)
3942 assert(closer
== NULL
);
3945 assert(aCloser
!= NULL
);
3949 comm_add_close_handler(fd
, closer
);
3952 /// planned close: removes the close handler and calls comm_close
3956 // channels with active listeners will be closed when the listener handler dies.
3960 comm_remove_close_handler(fd
, closer
);
3963 } else if (fd
>= 0) {
3964 comm_remove_close_handler(fd
, closer
);
3966 comm_close(fd
); // we do not expect to be called back
3971 /// just resets fd and close handler