2 * DEBUG: section 09 File Transfer Protocol (FTP)
3 * AUTHOR: Harvest Derived
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 #include "acl/FilledChecklist.h"
36 #include "comm/TcpAcceptor.h"
37 #include "CommCalls.h"
38 #include "compat/strtoll.h"
39 #include "errorpage.h"
42 #include "FtpServer.h"
44 #include "html_quote.h"
45 #include "HttpHdrContRange.h"
46 #include "HttpHeader.h"
47 #include "HttpHeaderRange.h"
48 #include "HttpReply.h"
49 #include "HttpRequest.h"
56 #include "SquidConfig.h"
57 #include "SquidString.h"
58 #include "SquidTime.h"
59 #include "StatCounters.h"
66 #include "DelayPools.h"
67 #include "MemObject.h"
75 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
76 \ingroup ServerProtocolFTPAPI
79 #define CTRL_BUFLEN 1024
80 /// \ingroup ServerProtocolFTPInternal
81 static char cbuf
[CTRL_BUFLEN
];
83 /// \ingroup ServerProtocolFTPInternal
87 bool pasv_supported
; ///< PASV command is allowed
88 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
90 bool pasv_failed
; // was FwdState::flags.ftp_pasv_failed
93 bool authenticated
; ///< authentication success
94 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
95 bool tried_auth_nopass
; ///< auth tried username with no password already.
101 bool http_header_sent
;
111 bool listformat_unknown
;
113 bool completed_forwarding
;
118 /// \ingroup ServerProtocolFTPInternal
119 typedef void (FTPSM
) (FtpStateData
*);
121 /// \ingroup ServerProtocolFTPInternal
122 class FtpStateData
: public Ftp::ServerStateData
125 FtpStateData(FwdState
*);
126 virtual ~FtpStateData();
128 char password
[MAX_URL
];
142 int64_t restart_offset
;
148 MemBuf listing
; ///< FTP directory listing in HTML format.
150 struct _ftp_flags flags
;
153 // these should all be private
154 virtual void start();
155 virtual Http::StatusCode
failedHttpStatus(err_type
&error
);
156 void loginParser(const char *, int escaped
);
158 void appendSuccessHeader();
159 void hackShortcut(FTPSM
* nextState
);
163 MemBuf
*htmlifyListEntry(const char *line
);
164 void completedListing(void);
166 /// create a data channel acceptor and start listening.
167 void listenForDataChannel(const Comm::ConnectionPointer
&conn
);
169 int checkAuth(const HttpHeader
* req_hdr
);
171 void buildTitleUrl();
172 void writeReplyBody(const char *, size_t len
);
173 void printfReplyBody(const char *fmt
, ...);
174 virtual void completeForwarding();
175 void processHeadResponse();
176 void processReplyBody();
177 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
178 int64_t getCurrentOffset() const { return currentOffset
; }
180 virtual void dataChannelConnected(const Comm::ConnectionPointer
&conn
, comm_err_t err
, int xerrno
);
181 static PF ftpDataWrite
;
182 virtual void timeout(const CommTimeoutCbParams
&io
);
183 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
185 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, const char *realm
);
186 const char *ftpRealm(void);
187 void loginFailed(void);
189 virtual void haveParsedReplyHeaders();
191 virtual bool haveControlChannel(const char *caller_name
) const;
194 virtual void handleControlReply();
195 virtual void dataClosed(const CommCloseCbParams
&io
);
198 // BodyConsumer for HTTP: consume request body.
199 virtual void handleRequestBodyProducerAborted();
201 CBDATA_CLASS2(FtpStateData
);
204 CBDATA_CLASS_INIT(FtpStateData
);
206 /// \ingroup ServerProtocolFTPInternal
216 /// \ingroup ServerProtocolFTPInternal
217 #define FTP_LOGIN_ESCAPED 1
219 /// \ingroup ServerProtocolFTPInternal
220 #define FTP_LOGIN_NOT_ESCAPED 0
223 * State machine functions
224 * send == state transition
225 * read == wait for response, and select next state transition
226 * other == Transition logic
228 static FTPSM ftpReadWelcome
;
229 static FTPSM ftpSendUser
;
230 static FTPSM ftpReadUser
;
231 static FTPSM ftpSendPass
;
232 static FTPSM ftpReadPass
;
233 static FTPSM ftpSendType
;
234 static FTPSM ftpReadType
;
235 static FTPSM ftpSendMdtm
;
236 static FTPSM ftpReadMdtm
;
237 static FTPSM ftpSendSize
;
238 static FTPSM ftpReadSize
;
239 static FTPSM ftpSendEPRT
;
240 static FTPSM ftpReadEPRT
;
241 static FTPSM ftpSendPORT
;
242 static FTPSM ftpReadPORT
;
243 static FTPSM ftpSendPassive
;
244 static FTPSM ftpReadEPSV
;
245 static FTPSM ftpReadPasv
;
246 static FTPSM ftpTraverseDirectory
;
247 static FTPSM ftpListDir
;
248 static FTPSM ftpGetFile
;
249 static FTPSM ftpSendCwd
;
250 static FTPSM ftpReadCwd
;
251 static FTPSM ftpRestOrList
;
252 static FTPSM ftpSendList
;
253 static FTPSM ftpSendNlst
;
254 static FTPSM ftpReadList
;
255 static FTPSM ftpSendRest
;
256 static FTPSM ftpReadRest
;
257 static FTPSM ftpSendRetr
;
258 static FTPSM ftpReadRetr
;
259 static FTPSM ftpReadTransferDone
;
260 static FTPSM ftpSendStor
;
261 static FTPSM ftpReadStor
;
262 static FTPSM ftpWriteTransferDone
;
263 static FTPSM ftpSendReply
;
264 static FTPSM ftpSendMkdir
;
265 static FTPSM ftpReadMkdir
;
266 static FTPSM ftpFail
;
267 static FTPSM ftpSendQuit
;
268 static FTPSM ftpReadQuit
;
270 /************************************************
271 ** Debugs Levels used here **
272 *************************************************
275 Protocol and Transmission failures.
276 2 FTP Protocol Chatter
281 ************************************************/
283 /************************************************
284 ** State Machine Description (excluding hacks) **
285 *************************************************
287 ---------------------------------------
291 Type TraverseDirectory / GetFile
292 TraverseDirectory Cwd / GetFile / ListDir
293 Cwd TraverseDirectory / Mkdir
299 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
301 Retr / Nlst / List DataRead* (on datachannel)
302 DataRead* ReadTransferDone
303 ReadTransferDone DataTransferDone
304 Stor DataWrite* (on datachannel)
305 DataWrite* RequestPutBody** (from client)
306 RequestPutBody** DataWrite* / WriteTransferDone
307 WriteTransferDone DataTransferDone
308 DataTransferDone Quit
310 ************************************************/
312 /// \ingroup ServerProtocolFTPInternal
313 FTPSM
*FTP_SM_FUNCS
[] = {
314 ftpReadWelcome
, /* BEGIN */
315 ftpReadUser
, /* SENT_USER */
316 ftpReadPass
, /* SENT_PASS */
317 ftpReadType
, /* SENT_TYPE */
318 ftpReadMdtm
, /* SENT_MDTM */
319 ftpReadSize
, /* SENT_SIZE */
320 ftpReadEPRT
, /* SENT_EPRT */
321 ftpReadPORT
, /* SENT_PORT */
322 ftpReadEPSV
, /* SENT_EPSV_ALL */
323 ftpReadEPSV
, /* SENT_EPSV_1 */
324 ftpReadEPSV
, /* SENT_EPSV_2 */
325 ftpReadPasv
, /* SENT_PASV */
326 ftpReadCwd
, /* SENT_CWD */
327 ftpReadList
, /* SENT_LIST */
328 ftpReadList
, /* SENT_NLST */
329 ftpReadRest
, /* SENT_REST */
330 ftpReadRetr
, /* SENT_RETR */
331 ftpReadStor
, /* SENT_STOR */
332 ftpReadQuit
, /* SENT_QUIT */
333 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
334 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
335 ftpReadMkdir
, /* SENT_MKDIR */
336 NULL
, /* SENT_FEAT */
339 NULL
, /* SENT_DATA_REQUEST */
340 NULL
/* SENT_COMMAND */
343 /// handler called by Comm when FTP data channel is closed unexpectedly
345 FtpStateData::dataClosed(const CommCloseCbParams
&io
)
347 Ftp::ServerStateData::dataClosed(io
);
348 failed(ERR_FTP_FAILURE
, 0);
349 /* failed closes ctrl.conn and frees ftpState */
351 /* NP: failure recovery may be possible when its only a data.conn failure.
352 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
353 * Just need to watch out for wider Squid states like shutting down or reconfigure.
357 FtpStateData::FtpStateData(FwdState
*fwdState
): AsyncJob("FtpStateData"),
358 Ftp::ServerStateData(fwdState
)
360 const char *url
= entry
->url();
361 debugs(9, 3, HERE
<< "'" << url
<< "'" );
365 if (Config
.Ftp
.passive
&& !flags
.pasv_failed
)
366 flags
.pasv_supported
= 1;
368 flags
.rest_supported
= 1;
370 if (request
->method
== Http::METHOD_PUT
)
376 FtpStateData::~FtpStateData()
378 debugs(9, 3, HERE
<< entry
->url() );
380 if (Comm::IsConnOpen(ctrl
.conn
)) {
381 debugs(9, DBG_IMPORTANT
, HERE
<< "Internal bug: Ftp::ServerStateData "
382 "left open control channel " << ctrl
.conn
);
386 memFree(reply_hdr
, MEM_8K_BUF
);
391 wordlistDestroy(&pathcomps
);
395 safe_free(old_filepath
);
407 * Parse a possible login username:password pair.
408 * Produces filled member variables user, password, password_url if anything found.
411 FtpStateData::loginParser(const char *login
, int escaped
)
413 const char *u
= NULL
; // end of the username sub-string
414 int len
; // length of the current sub-string to handle.
416 int total_len
= strlen(login
);
418 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
419 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
421 if ((u
= strchr(login
, ':'))) {
423 /* if there was a username part */
426 ++u
; // jump off the delimiter.
429 xstrncpy(user
, login
, len
+1);
430 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
432 rfc1738_unescape(user
);
433 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
436 /* if there was a password part */
437 len
= login
+ total_len
- u
;
441 xstrncpy(password
, u
, len
+1);
442 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
444 rfc1738_unescape(password
);
447 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
449 } else if (login
[0]) {
450 /* no password, just username */
451 if (total_len
> MAX_URL
)
452 total_len
= MAX_URL
-1;
453 xstrncpy(user
, login
, total_len
+1);
454 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
456 rfc1738_unescape(user
);
457 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
460 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
464 FtpStateData::listenForDataChannel(const Comm::ConnectionPointer
&conn
)
466 assert(!Comm::IsConnOpen(data
.conn
));
468 typedef CommCbMemFunT
<FtpStateData
, CommAcceptCbParams
> AcceptDialer
;
469 typedef AsyncCallT
<AcceptDialer
> AcceptCall
;
470 RefCount
<AcceptCall
> call
= static_cast<AcceptCall
*>(JobCallback(11, 5, AcceptDialer
, this, FtpStateData::ftpAcceptDataConnection
));
471 Subscription::Pointer sub
= new CallSubscription
<AcceptCall
>(call
);
472 const char *note
= entry
->url();
474 /* open the conn if its not already open */
475 if (!Comm::IsConnOpen(conn
)) {
476 conn
->fd
= comm_open_listener(SOCK_STREAM
, IPPROTO_TCP
, conn
->local
, conn
->flags
, note
);
477 if (!Comm::IsConnOpen(conn
)) {
478 debugs(5, DBG_CRITICAL
, HERE
<< "comm_open_listener failed:" << conn
->local
<< " error: " << errno
);
481 debugs(9, 3, HERE
<< "Unconnected data socket created on " << conn
);
484 assert(Comm::IsConnOpen(conn
));
485 AsyncJob::Start(new Comm::TcpAcceptor(conn
, note
, sub
));
487 // Ensure we have a copy of the FD opened for listening and a close handler on it.
488 data
.opened(conn
, dataCloser());
489 switchTimeoutToDataChannel();
493 FtpStateData::timeout(const CommTimeoutCbParams
&io
)
495 if (SENT_PASV
== state
) {
496 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
497 flags
.pasv_supported
= false;
498 debugs(9, DBG_IMPORTANT
, HERE
<< "timeout in SENT_PASV state");
500 // cancel the data connection setup.
501 if (data
.opener
!= NULL
) {
502 data
.opener
->cancel("timeout");
508 Ftp::ServerStateData::timeout(io
);
511 /// \ingroup ServerProtocolFTPInternal
512 static const char *Month
[] = {
513 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
514 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
517 /// \ingroup ServerProtocolFTPInternal
519 is_month(const char *buf
)
523 for (i
= 0; i
< 12; ++i
)
524 if (!strcasecmp(buf
, Month
[i
]))
530 /// \ingroup ServerProtocolFTPInternal
532 ftpListPartsFree(ftpListParts
** parts
)
534 safe_free((*parts
)->date
);
535 safe_free((*parts
)->name
);
536 safe_free((*parts
)->showname
);
537 safe_free((*parts
)->link
);
541 /// \ingroup ServerProtocolFTPInternal
542 #define MAX_TOKENS 64
544 /// \ingroup ServerProtocolFTPInternal
545 static ftpListParts
*
546 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
548 ftpListParts
*p
= NULL
;
550 const char *ct
= NULL
;
551 char *tokens
[MAX_TOKENS
];
554 static char tbuf
[128];
556 static int scan_ftp_initialized
= 0;
557 static regex_t scan_ftp_integer
;
558 static regex_t scan_ftp_time
;
559 static regex_t scan_ftp_dostime
;
560 static regex_t scan_ftp_dosdate
;
562 if (!scan_ftp_initialized
) {
563 scan_ftp_initialized
= 1;
564 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
565 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
566 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
567 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
576 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
580 memset(tokens
, 0, sizeof(tokens
));
584 if (flags
.tried_nlst
) {
585 /* Machine readable format, one name per line */
591 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
)) {
592 tokens
[n_tokens
] = xstrdup(t
);
598 /* locate the Month field */
599 for (i
= 3; i
< n_tokens
- 2; ++i
) {
600 char *size
= tokens
[i
- 1];
601 char *month
= tokens
[i
];
602 char *day
= tokens
[i
+ 1];
603 char *year
= tokens
[i
+ 2];
605 if (!is_month(month
))
608 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
611 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
614 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
617 snprintf(tbuf
, 128, "%s %2s %5s",
620 if (!strstr(buf
, tbuf
))
621 snprintf(tbuf
, 128, "%s %2s %-5s",
624 char const *copyFrom
= NULL
;
626 if ((copyFrom
= strstr(buf
, tbuf
))) {
627 p
->type
= *tokens
[0];
628 p
->size
= strtoll(size
, NULL
, 10);
629 p
->date
= xstrdup(tbuf
);
631 if (flags
.skip_whitespace
) {
632 copyFrom
+= strlen(tbuf
);
634 while (strchr(w_space
, *copyFrom
))
637 /* XXX assumes a single space between date and filename
638 * suggested by: Nathan.Bailey@cc.monash.edu.au and
639 * Mike Battersby <mike@starbug.bofh.asn.au> */
640 copyFrom
+= strlen(tbuf
) + 1;
643 p
->name
= xstrdup(copyFrom
);
645 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
647 p
->link
= xstrdup(t
+ 4);
656 /* try it as a DOS listing, 04-05-70 09:33PM ... */
658 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
659 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
660 if (!strcasecmp(tokens
[2], "<dir>")) {
664 p
->size
= strtoll(tokens
[2], NULL
, 10);
667 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
668 p
->date
= xstrdup(tbuf
);
670 if (p
->type
== 'd') {
671 /* Directory.. name begins with first printable after <dir> */
672 ct
= strstr(buf
, tokens
[2]);
673 ct
+= strlen(tokens
[2]);
675 while (xisspace(*ct
))
681 /* A file. Name begins after size, with a space in between */
682 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
683 ct
= strstr(buf
, tbuf
);
686 ct
+= strlen(tokens
[2]) + 2;
690 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
694 /* Try EPLF format; carson@lehman.com */
701 int l
= strcspn(ct
, ",");
710 p
->name
= xstrndup(ct
+ 1, l
+ 1);
714 p
->size
= atoi(ct
+ 1);
718 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
721 break; /* not a valid integer */
723 p
->date
= xstrdup(ctime(&tm
));
725 *(strstr(p
->date
, "\n")) = '\0';
747 ct
= strstr(ct
, ",");
766 for (i
= 0; i
< n_tokens
; ++i
)
770 ftpListPartsFree(&p
); /* cleanup */
776 FtpStateData::htmlifyListEntry(const char *line
)
779 char href
[2048 + 40];
782 char chdir
[ 2048 + 40];
783 char view
[ 2048 + 40];
784 char download
[ 2048 + 40];
785 char link
[ 2048 + 40];
789 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
791 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
793 if (strlen(line
) > 1024) {
796 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
800 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
801 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
805 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
810 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
812 for (p
= line
; *p
&& xisspace(*p
); ++p
);
813 if (*p
&& !xisspace(*p
))
814 flags
.listformat_unknown
= 1;
819 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
820 ftpListPartsFree(&parts
);
826 parts
->showname
= xstrdup(parts
->name
);
828 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
829 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
831 xstrncpy(text
, parts
->showname
, 2048);
833 switch (parts
->type
) {
836 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
837 mimeGetIconURL("internal-dir"),
839 strcat(href
, "/"); /* margin is allocated above */
843 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
844 mimeGetIconURL("internal-link"),
846 /* sometimes there is an 'l' flag, but no "->" link */
849 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
850 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
851 *link2
!= '/' ? prefix
: "", link2
,
852 html_quote(parts
->link
));
859 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
860 mimeGetIconURL(parts
->name
),
862 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
863 "alt=\"[DIR]\"></a>",
864 rfc1738_escape_part(parts
->name
),
865 mimeGetIconURL("internal-dir"));
871 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
872 mimeGetIconURL(parts
->name
),
874 snprintf(size
, 2048, " %6" PRId64
"k", parts
->size
);
878 if (parts
->type
!= 'd') {
879 if (mimeGetViewOption(parts
->name
)) {
880 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
881 "alt=\"[VIEW]\"></a>",
882 prefix
, href
, mimeGetIconURL("internal-view"));
885 if (mimeGetDownloadOption(parts
->name
)) {
886 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
887 "alt=\"[DOWNLOAD]\"></a>",
888 prefix
, href
, mimeGetIconURL("internal-download"));
892 /* construct the table row from parts. */
895 html
->Printf("<tr class=\"entry\">"
896 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
897 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
898 "<td class=\"date\">%s</td>"
899 "<td class=\"size\">%s</td>"
900 "<td class=\"actions\">%s%s%s%s</td>"
903 prefix
, href
, html_quote(text
),
906 chdir
, view
, download
, link
);
908 ftpListPartsFree(&parts
);
913 FtpStateData::parseListing()
915 char *buf
= data
.readBuf
->content();
916 char *sbuf
; /* NULL-terminated copy of termedBuf */
923 size_t len
= data
.readBuf
->contentSize();
926 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
931 * We need a NULL-terminated buffer for scanning, ick
933 sbuf
= (char *)xmalloc(len
+ 1);
934 xstrncpy(sbuf
, buf
, len
+ 1);
935 end
= sbuf
+ len
- 1;
937 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
942 debugs(9, 3, HERE
<< "usable = " << usable
<< " of " << len
<< " bytes.");
945 if (buf
[0] == '\0' && len
== 1) {
946 debugs(9, 3, HERE
<< "NIL ends data from " << entry
->url() << " transfer problem?");
947 data
.readBuf
->consume(len
);
949 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url());
950 debugs(9, 3, HERE
<< "buffer remains (" << len
<< " bytes) '" << rfc1738_do_escape(buf
,0) << "'");
956 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
958 line
= (char *)memAllocate(MEM_4K_BUF
);
961 s
+= strspn(s
, Ftp::crlf
);
963 for (; s
< end
; s
+= strcspn(s
, Ftp::crlf
), s
+= strspn(s
, Ftp::crlf
)) {
964 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
965 linelen
= strcspn(s
, Ftp::crlf
) + 1;
973 xstrncpy(line
, s
, linelen
);
975 debugs(9, 7, HERE
<< "{" << line
<< "}");
977 if (!strncmp(line
, "total", 5))
980 t
= htmlifyListEntry(line
);
983 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
984 listing
.append(t
->content(), t
->contentSize());
989 debugs(9, 7, HERE
<< "Done.");
990 data
.readBuf
->consume(usable
);
991 memFree(line
, MEM_4K_BUF
);
996 FtpStateData::processReplyBody()
998 debugs(9, 3, HERE
<< "FtpStateData::processReplyBody starting.");
1000 if (request
->method
== Http::METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
1005 /* Directory listings are special. They write ther own headers via the error objects */
1006 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
1007 appendSuccessHeader();
1009 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1011 * probably was aborted because content length exceeds one
1012 * of the maximum size limits.
1014 abortTransaction("entry aborted after calling appendSuccessHeader()");
1020 if (adaptationAccessCheckPending
) {
1021 debugs(9,3, HERE
<< "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1028 if (!flags
.listing
) {
1033 maybeReadVirginBody();
1035 } else if (const int csize
= data
.readBuf
->contentSize()) {
1036 writeReplyBody(data
.readBuf
->content(), csize
);
1037 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1038 data
.readBuf
->consume(csize
);
1043 maybeReadVirginBody();
1047 * Locates the FTP user:password login.
1049 * Highest to lowest priority:
1050 * - Checks URL (ftp://user:pass@domain)
1051 * - Authorization: Basic header
1052 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1054 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1056 * TODO: we might be able to do something about locating username from other sources:
1057 * ie, external ACL user=* tag or ident lookup
1059 \retval 1 if we have everything needed to complete this request.
1060 \retval 0 if something is missing.
1063 FtpStateData::checkAuth(const HttpHeader
* req_hdr
)
1065 /* default username */
1066 xstrncpy(user
, "anonymous", MAX_URL
);
1068 #if HAVE_AUTH_MODULE_BASIC
1069 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1071 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1072 flags
.authenticated
= 1;
1073 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1075 /* we fail with authorization-required error later IFF the FTP server requests it */
1078 /* Test URL login syntax. Overrides any headers received. */
1079 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1081 /* name is missing. thats fatal. */
1083 fatal("FTP login parsing destroyed username info");
1085 /* name + password == success */
1089 /* Setup default FTP password settings */
1090 /* this has to be done last so that we can have a no-password case above. */
1092 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1093 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1094 flags
.tried_auth_anonymous
=1;
1096 } else if (!flags
.tried_auth_nopass
) {
1097 xstrncpy(password
, null_string
, MAX_URL
);
1098 flags
.tried_auth_nopass
=1;
1103 return 0; /* different username */
1106 static String str_type_eq
;
1108 FtpStateData::checkUrlpath()
1113 if (str_type_eq
.size()==0) //hack. String doesn't support global-static
1114 str_type_eq
="type=";
1116 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1117 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1118 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1119 request
->urlpath
.cut(t
);
1123 l
= request
->urlpath
.size();
1124 /* check for null path */
1129 flags
.need_base_href
= 1; /* Work around broken browsers */
1130 } else if (!request
->urlpath
.cmp("/%2f/")) {
1131 /* UNIX root directory */
1134 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1135 /* Directory URL, ending in / */
1141 flags
.dir_slash
= 1;
1146 FtpStateData::buildTitleUrl()
1148 title_url
= "ftp://";
1150 if (strcmp(user
, "anonymous")) {
1151 title_url
.append(user
);
1152 title_url
.append("@");
1155 title_url
.append(request
->GetHost());
1157 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1158 title_url
.append(":");
1159 title_url
.append(xitoa(request
->port
));
1162 title_url
.append (request
->urlpath
);
1164 base_href
= "ftp://";
1166 if (strcmp(user
, "anonymous") != 0) {
1167 base_href
.append(rfc1738_escape_part(user
));
1170 base_href
.append (":");
1171 base_href
.append(rfc1738_escape_part(password
));
1174 base_href
.append("@");
1177 base_href
.append(request
->GetHost());
1179 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1180 base_href
.append(":");
1181 base_href
.append(xitoa(request
->port
));
1184 base_href
.append(request
->urlpath
);
1185 base_href
.append("/");
1188 /// \ingroup ServerProtocolFTPAPI
1190 ftpStart(FwdState
* fwd
)
1192 AsyncJob::Start(new FtpStateData(fwd
));
1196 FtpStateData::start()
1198 if (!checkAuth(&request
->header
)) {
1199 /* create appropriate reply */
1200 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1201 entry
->replaceHttpReply(reply
);
1208 debugs(9, 5, HERE
<< "FD " << ctrl
.conn
->fd
<< " : host=" << request
->GetHost() <<
1209 ", path=" << request
->urlpath
<< ", user=" << user
<< ", passwd=" << password
);
1213 Ftp::ServerStateData::start();
1216 /* ====================================================================== */
1219 FtpStateData::handleControlReply()
1221 Ftp::ServerStateData::handleControlReply();
1222 if (ctrl
.message
== NULL
)
1223 return; // didn't get complete reply yet
1225 /* Copy the message except for the last line to cwd_message to be
1226 * printed in error messages.
1228 for (wordlist
*w
= ctrl
.message
; w
&& w
->next
; w
= w
->next
) {
1229 cwd_message
.append('\n');
1230 cwd_message
.append(w
->key
);
1233 FTP_SM_FUNCS
[state
] (this);
1236 /* ====================================================================== */
1238 /// \ingroup ServerProtocolFTPInternal
1240 ftpReadWelcome(FtpStateData
* ftpState
)
1242 int code
= ftpState
->ctrl
.replycode
;
1245 if (ftpState
->flags
.pasv_only
)
1246 ++ ftpState
->login_att
;
1249 if (ftpState
->ctrl
.message
) {
1250 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1251 ftpState
->flags
.skip_whitespace
= 1;
1254 ftpSendUser(ftpState
);
1255 } else if (code
== 120) {
1256 if (NULL
!= ftpState
->ctrl
.message
)
1257 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1266 * Translate FTP login failure into HTTP error
1267 * this is an attmpt to get the 407 message to show up outside Squid.
1268 * its NOT a general failure. But a correct FTP response type.
1271 FtpStateData::loginFailed()
1273 ErrorState
*err
= NULL
;
1274 const char *command
, *reply
;
1276 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1277 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1278 // 421/426 - Service Overload - retry permitted.
1279 err
= new ErrorState(ERR_FTP_UNAVAILABLE
, Http::scServiceUnavailable
, fwd
->request
);
1280 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1281 // 43x - Invalid or Credential Error - retry challenge required.
1282 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1283 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1284 // 53x - Credentials Missing - retry challenge required
1285 if (password_url
) // but they were in the URI! major fail.
1286 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scForbidden
, fwd
->request
);
1288 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1292 // any other problems are general falures.
1298 err
->ftp
.server_msg
= ctrl
.message
;
1300 ctrl
.message
= NULL
;
1303 command
= old_request
;
1305 command
= ctrl
.last_command
;
1307 if (command
&& strncmp(command
, "PASS", 4) == 0)
1308 command
= "PASS <yourpassword>";
1313 reply
= ctrl
.last_reply
;
1316 err
->ftp
.request
= xstrdup(command
);
1319 err
->ftp
.reply
= xstrdup(reply
);
1321 HttpReply
*newrep
= err
->BuildHttpReply();
1324 #if HAVE_AUTH_MODULE_BASIC
1325 /* add Authenticate header */
1326 newrep
->header
.putAuth("Basic", ftpRealm());
1329 // add it to the store entry for response....
1330 entry
->replaceHttpReply(newrep
);
1335 FtpStateData::ftpRealm()
1337 static char realm
[8192];
1339 /* This request is not fully authenticated */
1341 snprintf(realm
, 8192, "FTP %s unknown", user
);
1342 } else if (request
->port
== 21) {
1343 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1345 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1350 /// \ingroup ServerProtocolFTPInternal
1352 ftpSendUser(FtpStateData
* ftpState
)
1354 /* check the server control channel is still available */
1355 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1358 if (ftpState
->proxy_host
!= NULL
)
1359 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s@%s\r\n",
1361 ftpState
->request
->GetHost());
1363 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s\r\n", ftpState
->user
);
1365 ftpState
->writeCommand(cbuf
);
1367 ftpState
->state
= Ftp::ServerStateData::SENT_USER
;
1370 /// \ingroup ServerProtocolFTPInternal
1372 ftpReadUser(FtpStateData
* ftpState
)
1374 int code
= ftpState
->ctrl
.replycode
;
1378 ftpReadPass(ftpState
);
1379 } else if (code
== 331) {
1380 ftpSendPass(ftpState
);
1382 ftpState
->loginFailed();
1386 /// \ingroup ServerProtocolFTPInternal
1388 ftpSendPass(FtpStateData
* ftpState
)
1390 /* check the server control channel is still available */
1391 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
1394 snprintf(cbuf
, CTRL_BUFLEN
, "PASS %s\r\n", ftpState
->password
);
1395 ftpState
->writeCommand(cbuf
);
1396 ftpState
->state
= Ftp::ServerStateData::SENT_PASS
;
1399 /// \ingroup ServerProtocolFTPInternal
1401 ftpReadPass(FtpStateData
* ftpState
)
1403 int code
= ftpState
->ctrl
.replycode
;
1404 debugs(9, 3, HERE
<< "code=" << code
);
1407 ftpSendType(ftpState
);
1409 ftpState
->loginFailed();
1413 /// \ingroup ServerProtocolFTPInternal
1415 ftpSendType(FtpStateData
* ftpState
)
1418 const char *filename
;
1421 /* check the server control channel is still available */
1422 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
1426 * Ref section 3.2.2 of RFC 1738
1428 mode
= ftpState
->typecode
;
1443 if (ftpState
->flags
.isdir
) {
1446 t
= ftpState
->request
->urlpath
.rpos('/');
1447 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
1448 mode
= mimeGetTransferMode(filename
);
1455 ftpState
->flags
.binary
= 1;
1457 ftpState
->flags
.binary
= 0;
1459 snprintf(cbuf
, CTRL_BUFLEN
, "TYPE %c\r\n", mode
);
1461 ftpState
->writeCommand(cbuf
);
1463 ftpState
->state
= Ftp::ServerStateData::SENT_TYPE
;
1466 /// \ingroup ServerProtocolFTPInternal
1468 ftpReadType(FtpStateData
* ftpState
)
1470 int code
= ftpState
->ctrl
.replycode
;
1473 debugs(9, 3, HERE
<< "code=" << code
);
1476 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
1483 p
+= strcspn(p
, "/");
1490 rfc1738_unescape(d
);
1493 wordlistAdd(&ftpState
->pathcomps
, d
);
1498 if (ftpState
->pathcomps
)
1499 ftpTraverseDirectory(ftpState
);
1501 ftpListDir(ftpState
);
1507 /// \ingroup ServerProtocolFTPInternal
1509 ftpTraverseDirectory(FtpStateData
* ftpState
)
1512 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
1514 safe_free(ftpState
->dirpath
);
1515 ftpState
->dirpath
= ftpState
->filepath
;
1516 ftpState
->filepath
= NULL
;
1520 if (ftpState
->pathcomps
== NULL
) {
1521 debugs(9, 3, HERE
<< "the final component was a directory");
1522 ftpListDir(ftpState
);
1526 /* Go to next path component */
1527 w
= ftpState
->pathcomps
;
1529 ftpState
->filepath
= w
->key
;
1531 ftpState
->pathcomps
= w
->next
;
1535 /* Check if we are to CWD or RETR */
1536 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
1537 ftpSendCwd(ftpState
);
1539 debugs(9, 3, HERE
<< "final component is probably a file");
1540 ftpGetFile(ftpState
);
1545 /// \ingroup ServerProtocolFTPInternal
1547 ftpSendCwd(FtpStateData
* ftpState
)
1551 /* check the server control channel is still available */
1552 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
1557 path
= ftpState
->filepath
;
1559 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
1560 ftpState
->flags
.no_dotdot
= 1;
1562 ftpState
->flags
.no_dotdot
= 0;
1565 snprintf(cbuf
, CTRL_BUFLEN
, "CWD %s\r\n", path
);
1567 ftpState
->writeCommand(cbuf
);
1569 ftpState
->state
= Ftp::ServerStateData::SENT_CWD
;
1572 /// \ingroup ServerProtocolFTPInternal
1574 ftpReadCwd(FtpStateData
* ftpState
)
1576 int code
= ftpState
->ctrl
.replycode
;
1579 if (code
>= 200 && code
< 300) {
1583 /* Reset cwd_message to only include the last message */
1584 ftpState
->cwd_message
.reset("");
1585 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
1586 ftpState
->cwd_message
.append(' ');
1587 ftpState
->cwd_message
.append(w
->key
);
1589 ftpState
->ctrl
.message
= NULL
;
1591 /* Continue to traverse the path */
1592 ftpTraverseDirectory(ftpState
);
1596 if (!ftpState
->flags
.put
)
1599 ftpSendMkdir(ftpState
);
1603 /// \ingroup ServerProtocolFTPInternal
1605 ftpSendMkdir(FtpStateData
* ftpState
)
1609 /* check the server control channel is still available */
1610 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
1613 path
= ftpState
->filepath
;
1614 debugs(9, 3, HERE
<< "with path=" << path
);
1615 snprintf(cbuf
, CTRL_BUFLEN
, "MKD %s\r\n", path
);
1616 ftpState
->writeCommand(cbuf
);
1617 ftpState
->state
= Ftp::ServerStateData::SENT_MKDIR
;
1620 /// \ingroup ServerProtocolFTPInternal
1622 ftpReadMkdir(FtpStateData
* ftpState
)
1624 char *path
= ftpState
->filepath
;
1625 int code
= ftpState
->ctrl
.replycode
;
1627 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
1629 if (code
== 257) { /* success */
1630 ftpSendCwd(ftpState
);
1631 } else if (code
== 550) { /* dir exists */
1633 if (ftpState
->flags
.put_mkdir
) {
1634 ftpState
->flags
.put_mkdir
= 1;
1635 ftpSendCwd(ftpState
);
1637 ftpSendReply(ftpState
);
1639 ftpSendReply(ftpState
);
1642 /// \ingroup ServerProtocolFTPInternal
1644 ftpGetFile(FtpStateData
* ftpState
)
1646 assert(*ftpState
->filepath
!= '\0');
1647 ftpState
->flags
.isdir
= 0;
1648 ftpSendMdtm(ftpState
);
1651 /// \ingroup ServerProtocolFTPInternal
1653 ftpListDir(FtpStateData
* ftpState
)
1655 if (ftpState
->flags
.dir_slash
) {
1656 debugs(9, 3, HERE
<< "Directory path did not end in /");
1657 ftpState
->title_url
.append("/");
1658 ftpState
->flags
.isdir
= 1;
1661 ftpSendPassive(ftpState
);
1664 /// \ingroup ServerProtocolFTPInternal
1666 ftpSendMdtm(FtpStateData
* ftpState
)
1668 /* check the server control channel is still available */
1669 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
1672 assert(*ftpState
->filepath
!= '\0');
1673 snprintf(cbuf
, CTRL_BUFLEN
, "MDTM %s\r\n", ftpState
->filepath
);
1674 ftpState
->writeCommand(cbuf
);
1675 ftpState
->state
= Ftp::ServerStateData::SENT_MDTM
;
1678 /// \ingroup ServerProtocolFTPInternal
1680 ftpReadMdtm(FtpStateData
* ftpState
)
1682 int code
= ftpState
->ctrl
.replycode
;
1686 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
1688 } else if (code
< 0) {
1693 ftpSendSize(ftpState
);
1696 /// \ingroup ServerProtocolFTPInternal
1698 ftpSendSize(FtpStateData
* ftpState
)
1700 /* check the server control channel is still available */
1701 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
1704 /* Only send SIZE for binary transfers. The returned size
1705 * is useless on ASCII transfers */
1707 if (ftpState
->flags
.binary
) {
1708 assert(ftpState
->filepath
!= NULL
);
1709 assert(*ftpState
->filepath
!= '\0');
1710 snprintf(cbuf
, CTRL_BUFLEN
, "SIZE %s\r\n", ftpState
->filepath
);
1711 ftpState
->writeCommand(cbuf
);
1712 ftpState
->state
= Ftp::ServerStateData::SENT_SIZE
;
1714 /* Skip to next state no non-binary transfers */
1715 ftpSendPassive(ftpState
);
1718 /// \ingroup ServerProtocolFTPInternal
1720 ftpReadSize(FtpStateData
* ftpState
)
1722 int code
= ftpState
->ctrl
.replycode
;
1727 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
1729 if (ftpState
->theSize
== 0) {
1730 debugs(9, 2, "SIZE reported " <<
1731 ftpState
->ctrl
.last_reply
<< " on " <<
1732 ftpState
->title_url
);
1733 ftpState
->theSize
= -1;
1735 } else if (code
< 0) {
1740 ftpSendPassive(ftpState
);
1744 \ingroup ServerProtocolFTPInternal
1747 ftpReadEPSV(FtpStateData
* ftpState
)
1749 Ip::Address srvAddr
; // unused
1750 if (ftpState
->handleEpsvReply(srvAddr
)) {
1751 if (ftpState
->ctrl
.message
== NULL
)
1752 return; // didn't get complete reply yet
1754 ftpState
->connectDataChannel();
1758 /** \ingroup ServerProtocolFTPInternal
1760 * Send Passive connection request.
1761 * Default method is to use modern EPSV request.
1762 * The failover mechanism should check for previous state and re-call with alternates on failure.
1765 ftpSendPassive(FtpStateData
* ftpState
)
1767 /** Checks the server control channel is still available before running. */
1768 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
1774 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
1775 if (ftpState
->request
->method
== Http::METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
1776 ftpState
->processHeadResponse(); // may call serverComplete
1780 if (ftpState
->sendPassive()) {
1781 // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
1782 if (ftpState
->state
== Ftp::ServerStateData::SENT_EPSV_ALL
)
1783 ftpState
->flags
.epsv_all_sent
= true;
1788 FtpStateData::processHeadResponse()
1790 debugs(9, 5, HERE
<< "handling HEAD response");
1792 appendSuccessHeader();
1795 * On rare occasions I'm seeing the entry get aborted after
1796 * readControlReply() and before here, probably when
1797 * trying to write to the client.
1799 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1800 abortTransaction("entry aborted while processing HEAD");
1805 if (adaptationAccessCheckPending
) {
1806 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
1811 // processReplyBody calls serverComplete() since there is no body
1815 /// \ingroup ServerProtocolFTPInternal
1817 ftpReadPasv(FtpStateData
* ftpState
)
1819 Ip::Address srvAddr
; // unused
1820 if (ftpState
->handlePasvReply(srvAddr
))
1821 ftpState
->connectDataChannel();
1823 ftpSendEPRT(ftpState
);
1829 FtpStateData::dataChannelConnected(const Comm::ConnectionPointer
&conn
, comm_err_t err
, int xerrno
)
1834 if (err
!= COMM_OK
) {
1835 debugs(9, 2, HERE
<< "Failed to connect. Retrying via another method.");
1837 // ABORT on timeouts. server may be waiting on a broken TCP link.
1838 if (err
== COMM_TIMEOUT
)
1839 writeCommand("ABOR");
1841 // try another connection attempt with some other method
1842 ftpSendPassive(this);
1846 data
.opened(conn
, dataCloser());
1847 ftpRestOrList(this);
1850 /// \ingroup ServerProtocolFTPInternal
1852 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
1854 /// Close old data channels, if any. We may open a new one below.
1855 if (ftpState
->data
.conn
!= NULL
) {
1856 if ((ftpState
->data
.conn
->flags
& COMM_REUSEADDR
))
1857 // NP: in fact it points to the control channel. just clear it.
1858 ftpState
->data
.clear();
1860 ftpState
->data
.close();
1862 safe_free(ftpState
->data
.host
);
1865 * Set up a listen socket on the same local address as the
1866 * control connection.
1868 Comm::ConnectionPointer temp
= new Comm::Connection
;
1869 temp
->local
= ftpState
->ctrl
.conn
->local
;
1872 * REUSEADDR is needed in fallback mode, since the same port is
1873 * used for both control and data.
1877 setsockopt(ftpState
->ctrl
.conn
->fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
1878 ftpState
->ctrl
.conn
->flags
|= COMM_REUSEADDR
;
1879 temp
->flags
|= COMM_REUSEADDR
;
1881 /* if not running in fallback mode a new port needs to be retrieved */
1882 temp
->local
.port(0);
1885 ftpState
->listenForDataChannel(temp
);
1888 /// \ingroup ServerProtocolFTPInternal
1890 ftpSendPORT(FtpStateData
* ftpState
)
1892 /* check the server control channel is still available */
1893 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
1896 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1897 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1902 ftpState
->flags
.pasv_supported
= 0;
1903 ftpOpenListenSocket(ftpState
, 0);
1905 if (!Comm::IsConnOpen(ftpState
->data
.listenConn
)) {
1906 if ( ftpState
->data
.listenConn
!= NULL
&& !ftpState
->data
.listenConn
->local
.isIPv4() ) {
1907 /* non-IPv4 CANNOT send PORT command. */
1908 /* we got here by attempting and failing an EPRT */
1909 /* using the same reply code should simulate a PORT failure */
1910 ftpReadPORT(ftpState
);
1914 /* XXX Need to set error message */
1919 // pull out the internal IP address bytes to send in PORT command...
1920 // source them from the listen_conn->local
1922 struct addrinfo
*AI
= NULL
;
1923 ftpState
->data
.listenConn
->local
.getAddrInfo(AI
, AF_INET
);
1924 unsigned char *addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
1925 unsigned char *portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
1926 snprintf(cbuf
, CTRL_BUFLEN
, "PORT %d,%d,%d,%d,%d,%d\r\n",
1927 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
1928 portptr
[0], portptr
[1]);
1929 ftpState
->writeCommand(cbuf
);
1930 ftpState
->state
= Ftp::ServerStateData::SENT_PORT
;
1932 Ip::Address::FreeAddrInfo(AI
);
1935 /// \ingroup ServerProtocolFTPInternal
1937 ftpReadPORT(FtpStateData
* ftpState
)
1939 int code
= ftpState
->ctrl
.replycode
;
1943 /* Fall back on using the same port as the control connection */
1944 debugs(9, 3, "PORT not supported by remote end");
1945 ftpOpenListenSocket(ftpState
, 1);
1948 ftpRestOrList(ftpState
);
1951 /// \ingroup ServerProtocolFTPInternal
1953 ftpSendEPRT(FtpStateData
* ftpState
)
1955 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1956 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
1960 if (!Config
.Ftp
.eprt
) {
1961 /* Disabled. Switch immediately to attempting old PORT command. */
1962 debugs(9, 3, "EPRT disabled by local administrator");
1963 ftpSendPORT(ftpState
);
1968 ftpState
->flags
.pasv_supported
= 0;
1970 ftpOpenListenSocket(ftpState
, 0);
1971 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState
->data
.conn
);
1972 if (!Comm::IsConnOpen(ftpState
->data
.conn
)) {
1973 /* XXX Need to set error message */
1978 char buf
[MAX_IPSTRLEN
];
1980 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
1981 /* Which can be used by EITHER protocol. */
1982 snprintf(cbuf
, CTRL_BUFLEN
, "EPRT |%d|%s|%d|\r\n",
1983 ( ftpState
->data
.listenConn
->local
.isIPv6() ? 2 : 1 ),
1984 ftpState
->data
.listenConn
->local
.toStr(buf
,MAX_IPSTRLEN
),
1985 ftpState
->data
.listenConn
->local
.port() );
1987 ftpState
->writeCommand(cbuf
);
1988 ftpState
->state
= Ftp::ServerStateData::SENT_EPRT
;
1992 ftpReadEPRT(FtpStateData
* ftpState
)
1994 int code
= ftpState
->ctrl
.replycode
;
1998 /* Failover to attempting old PORT command. */
1999 debugs(9, 3, "EPRT not supported by remote end");
2000 ftpSendPORT(ftpState
);
2004 ftpRestOrList(ftpState
);
2008 \ingroup ServerProtocolFTPInternal
2010 * "read" handler to accept FTP data connections.
2012 \param io comm accept(2) callback parameters
2015 FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
2019 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
2020 abortTransaction("entry aborted when accepting data conn");
2021 data
.listenConn
->close();
2022 data
.listenConn
= NULL
;
2026 if (io
.flag
!= COMM_OK
) {
2027 data
.listenConn
->close();
2028 data
.listenConn
= NULL
;
2029 debugs(9, DBG_IMPORTANT
, "FTP AcceptDataConnection: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
2030 /** \todo Need to send error message on control channel*/
2035 /* data listening conn is no longer even open. abort. */
2036 if (!Comm::IsConnOpen(data
.listenConn
)) {
2037 data
.listenConn
= NULL
; // ensure that it's cleared and not just closed.
2041 /* data listening conn is no longer even open. abort. */
2042 if (!Comm::IsConnOpen(data
.conn
)) {
2043 data
.clear(); // ensure that it's cleared and not just closed.
2048 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2049 * made by the remote client which is connected to the FTP control socket.
2050 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
2051 * This prevents third-party hacks, but also third-party load balancing handshakes.
2053 if (Config
.Ftp
.sanitycheck
) {
2054 // accept if either our data or ctrl connection is talking to this remote peer.
2055 if (data
.conn
->remote
!= io
.conn
->remote
&& ctrl
.conn
->remote
!= io
.conn
->remote
) {
2056 debugs(9, DBG_IMPORTANT
,
2057 "FTP data connection from unexpected server (" <<
2058 io
.conn
->remote
<< "), expecting " <<
2059 data
.conn
->remote
<< " or " << ctrl
.conn
->remote
);
2061 /* close the bad sources connection down ASAP. */
2064 /* drop the bad connection (io) by ignoring the attempt. */
2069 /** On COMM_OK start using the accepted data socket and discard the temporary listen socket. */
2071 data
.opened(io
.conn
, dataCloser());
2072 data
.addr(io
.conn
->remote
);
2074 debugs(9, 3, HERE
<< "Connected data socket on " <<
2075 io
.conn
<< ". FD table says: " <<
2076 "ctrl-peer= " << fd_table
[ctrl
.conn
->fd
].ipaddr
<< ", " <<
2077 "data-peer= " << fd_table
[data
.conn
->fd
].ipaddr
);
2079 assert(haveControlChannel("ftpAcceptDataConnection"));
2080 assert(ctrl
.message
== NULL
);
2082 // Ctrl channel operations will determine what happens to this data connection
2085 /// \ingroup ServerProtocolFTPInternal
2087 ftpRestOrList(FtpStateData
* ftpState
)
2091 if (ftpState
->typecode
== 'D') {
2092 ftpState
->flags
.isdir
= 1;
2094 if (ftpState
->flags
.put
) {
2095 ftpSendMkdir(ftpState
); /* PUT name;type=d */
2097 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
2099 } else if (ftpState
->flags
.put
) {
2100 ftpSendStor(ftpState
);
2101 } else if (ftpState
->flags
.isdir
)
2102 ftpSendList(ftpState
);
2103 else if (ftpState
->restartable())
2104 ftpSendRest(ftpState
);
2106 ftpSendRetr(ftpState
);
2109 /// \ingroup ServerProtocolFTPInternal
2111 ftpSendStor(FtpStateData
* ftpState
)
2113 /* check the server control channel is still available */
2114 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
2119 if (ftpState
->filepath
!= NULL
) {
2120 /* Plain file upload */
2121 snprintf(cbuf
, CTRL_BUFLEN
, "STOR %s\r\n", ftpState
->filepath
);
2122 ftpState
->writeCommand(cbuf
);
2123 ftpState
->state
= Ftp::ServerStateData::SENT_STOR
;
2124 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
2125 /* File upload without a filename. use STOU to generate one */
2126 snprintf(cbuf
, CTRL_BUFLEN
, "STOU\r\n");
2127 ftpState
->writeCommand(cbuf
);
2128 ftpState
->state
= Ftp::ServerStateData::SENT_STOR
;
2130 /* No file to transfer. Only create directories if needed */
2131 ftpSendReply(ftpState
);
2135 /// \ingroup ServerProtocolFTPInternal
2136 /// \deprecated use ftpState->readStor() instead.
2138 ftpReadStor(FtpStateData
* ftpState
)
2140 ftpState
->readStor();
2143 void FtpStateData::readStor()
2145 int code
= ctrl
.replycode
;
2148 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(data
.conn
))) {
2149 if (!originalRequest()->body_pipe
) {
2150 debugs(9, 3, "zero-size STOR?");
2151 state
= WRITING_DATA
; // make ftpWriteTransferDone() responsible
2152 dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2156 if (!startRequestBodyFlow()) { // register to receive body data
2161 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
2162 debugs(9, 3, HERE
<< "starting data transfer");
2163 switchTimeoutToDataChannel();
2164 sendMoreRequestBody();
2165 fwd
->dontRetry(true); // dont permit re-trying if the body was sent.
2166 state
= WRITING_DATA
;
2167 debugs(9, 3, HERE
<< "writing data channel");
2168 } else if (code
== 150) {
2169 /* When client code is 150 with no data channel, Accept data channel. */
2170 debugs(9, 3, "ftpReadStor: accepting data channel");
2171 listenForDataChannel(data
.conn
);
2173 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
2178 /// \ingroup ServerProtocolFTPInternal
2180 ftpSendRest(FtpStateData
* ftpState
)
2182 /* check the server control channel is still available */
2183 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
2188 snprintf(cbuf
, CTRL_BUFLEN
, "REST %" PRId64
"\r\n", ftpState
->restart_offset
);
2189 ftpState
->writeCommand(cbuf
);
2190 ftpState
->state
= Ftp::ServerStateData::SENT_REST
;
2194 FtpStateData::restartable()
2196 if (restart_offset
> 0)
2199 if (!request
->range
)
2208 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
2210 if (desired_offset
<= 0)
2213 if (desired_offset
>= theSize
)
2216 restart_offset
= desired_offset
;
2220 /// \ingroup ServerProtocolFTPInternal
2222 ftpReadRest(FtpStateData
* ftpState
)
2224 int code
= ftpState
->ctrl
.replycode
;
2226 assert(ftpState
->restart_offset
> 0);
2229 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
2230 ftpSendRetr(ftpState
);
2231 } else if (code
> 0) {
2232 debugs(9, 3, HERE
<< "REST not supported");
2233 ftpState
->flags
.rest_supported
= 0;
2234 ftpSendRetr(ftpState
);
2240 /// \ingroup ServerProtocolFTPInternal
2242 ftpSendList(FtpStateData
* ftpState
)
2244 /* check the server control channel is still available */
2245 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
2250 if (ftpState
->filepath
) {
2251 snprintf(cbuf
, CTRL_BUFLEN
, "LIST %s\r\n", ftpState
->filepath
);
2253 snprintf(cbuf
, CTRL_BUFLEN
, "LIST\r\n");
2256 ftpState
->writeCommand(cbuf
);
2257 ftpState
->state
= Ftp::ServerStateData::SENT_LIST
;
2260 /// \ingroup ServerProtocolFTPInternal
2262 ftpSendNlst(FtpStateData
* ftpState
)
2264 /* check the server control channel is still available */
2265 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
2270 ftpState
->flags
.tried_nlst
= 1;
2272 if (ftpState
->filepath
) {
2273 snprintf(cbuf
, CTRL_BUFLEN
, "NLST %s\r\n", ftpState
->filepath
);
2275 snprintf(cbuf
, CTRL_BUFLEN
, "NLST\r\n");
2278 ftpState
->writeCommand(cbuf
);
2279 ftpState
->state
= Ftp::ServerStateData::SENT_NLST
;
2282 /// \ingroup ServerProtocolFTPInternal
2284 ftpReadList(FtpStateData
* ftpState
)
2286 int code
= ftpState
->ctrl
.replycode
;
2289 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2290 /* Begin data transfer */
2291 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2292 ftpState
->switchTimeoutToDataChannel();
2293 ftpState
->maybeReadVirginBody();
2294 ftpState
->state
= Ftp::ServerStateData::READING_DATA
;
2296 } else if (code
== 150) {
2297 /* Accept data channel */
2298 debugs(9, 3, HERE
<< "accept data channel from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2299 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2301 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
2302 ftpSendNlst(ftpState
);
2309 /// \ingroup ServerProtocolFTPInternal
2311 ftpSendRetr(FtpStateData
* ftpState
)
2313 /* check the server control channel is still available */
2314 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
2319 assert(ftpState
->filepath
!= NULL
);
2320 snprintf(cbuf
, CTRL_BUFLEN
, "RETR %s\r\n", ftpState
->filepath
);
2321 ftpState
->writeCommand(cbuf
);
2322 ftpState
->state
= Ftp::ServerStateData::SENT_RETR
;
2325 /// \ingroup ServerProtocolFTPInternal
2327 ftpReadRetr(FtpStateData
* ftpState
)
2329 int code
= ftpState
->ctrl
.replycode
;
2332 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2333 /* Begin data transfer */
2334 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2335 ftpState
->switchTimeoutToDataChannel();
2336 ftpState
->maybeReadVirginBody();
2337 ftpState
->state
= Ftp::ServerStateData::READING_DATA
;
2338 } else if (code
== 150) {
2339 /* Accept data channel */
2340 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2341 } else if (code
>= 300) {
2342 if (!ftpState
->flags
.try_slash_hack
) {
2343 /* Try this as a directory missing trailing slash... */
2344 ftpState
->hackShortcut(ftpSendCwd
);
2354 * Generate the HTTP headers and template fluff around an FTP
2355 * directory listing display.
2358 FtpStateData::completedListing()
2361 entry
->lock("FtpStateData");
2362 ErrorState
ferr(ERR_DIR_LISTING
, Http::scOkay
, request
);
2363 ferr
.ftp
.listing
= &listing
;
2364 ferr
.ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
2365 ferr
.ftp
.server_msg
= ctrl
.message
;
2366 ctrl
.message
= NULL
;
2367 entry
->replaceHttpReply( ferr
.BuildHttpReply() );
2368 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
2370 entry
->unlock("FtpStateData");
2373 /// \ingroup ServerProtocolFTPInternal
2375 ftpReadTransferDone(FtpStateData
* ftpState
)
2377 int code
= ftpState
->ctrl
.replycode
;
2380 if (code
== 226 || code
== 250) {
2381 /* Connection closed; retrieval done. */
2382 if (ftpState
->flags
.listing
) {
2383 ftpState
->completedListing();
2384 /* QUIT operation handles sending the reply to client */
2386 ftpSendQuit(ftpState
);
2387 } else { /* != 226 */
2388 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
2389 ftpState
->failed(ERR_FTP_FAILURE
, 0);
2390 /* failed closes ctrl.conn and frees ftpState */
2395 // premature end of the request body
2397 FtpStateData::handleRequestBodyProducerAborted()
2399 ServerStateData::handleRequestBodyProducerAborted();
2400 debugs(9, 3, HERE
<< "ftpState=" << this);
2401 failed(ERR_READ_ERROR
, 0);
2404 /// \ingroup ServerProtocolFTPInternal
2406 ftpWriteTransferDone(FtpStateData
* ftpState
)
2408 int code
= ftpState
->ctrl
.replycode
;
2411 if (!(code
== 226 || code
== 250)) {
2412 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
2413 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
2417 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
2418 ftpSendReply(ftpState
);
2421 /// \ingroup ServerProtocolFTPInternal
2423 ftpSendQuit(FtpStateData
* ftpState
)
2425 /* check the server control channel is still available */
2426 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
2429 snprintf(cbuf
, CTRL_BUFLEN
, "QUIT\r\n");
2430 ftpState
->writeCommand(cbuf
);
2431 ftpState
->state
= Ftp::ServerStateData::SENT_QUIT
;
2435 * \ingroup ServerProtocolFTPInternal
2437 * This completes a client FTP operation with success or other page
2438 * generated and stored in the entry field by the code issuing QUIT.
2441 ftpReadQuit(FtpStateData
* ftpState
)
2443 ftpState
->serverComplete();
2446 /// \ingroup ServerProtocolFTPInternal
2448 ftpTrySlashHack(FtpStateData
* ftpState
)
2451 ftpState
->flags
.try_slash_hack
= 1;
2452 /* Free old paths */
2456 if (ftpState
->pathcomps
)
2457 wordlistDestroy(&ftpState
->pathcomps
);
2459 safe_free(ftpState
->filepath
);
2461 /* Build the new path (urlpath begins with /) */
2462 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2464 rfc1738_unescape(path
);
2466 ftpState
->filepath
= path
;
2469 ftpGetFile(ftpState
);
2473 * Forget hack status. Next error is shown to the user
2476 FtpStateData::unhack()
2480 if (old_request
!= NULL
) {
2481 safe_free(old_request
);
2482 safe_free(old_reply
);
2487 FtpStateData::hackShortcut(FTPSM
* nextState
)
2489 /* Clear some unwanted state */
2490 setCurrentOffset(0);
2492 /* Save old error message & some state info */
2496 if (old_request
== NULL
) {
2497 old_request
= ctrl
.last_command
;
2498 ctrl
.last_command
= NULL
;
2499 old_reply
= ctrl
.last_reply
;
2500 ctrl
.last_reply
= NULL
;
2502 if (pathcomps
== NULL
&& filepath
!= NULL
)
2503 old_filepath
= xstrdup(filepath
);
2506 /* Jump to the "hack" state */
2510 /// \ingroup ServerProtocolFTPInternal
2512 ftpFail(FtpStateData
*ftpState
)
2514 debugs(9, 6, HERE
<< "flags(" <<
2515 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
2516 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
2517 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
2518 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
2520 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2521 if (!ftpState
->flags
.isdir
&& /* Not a directory */
2522 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
2523 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
2524 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
2526 switch (ftpState
->state
) {
2528 case Ftp::ServerStateData::SENT_CWD
:
2530 case Ftp::ServerStateData::SENT_RETR
:
2531 /* Try the / hack */
2532 ftpState
->hackShortcut(ftpTrySlashHack
);
2540 ftpState
->failed(ERR_NONE
, 0);
2541 /* failed() closes ctrl.conn and frees this */
2545 FtpStateData::failedHttpStatus(err_type
&error
)
2547 if (error
== ERR_NONE
) {
2551 if (ctrl
.replycode
> 500) {
2552 error
= ERR_FTP_FORBIDDEN
;
2553 return password_url
? Http::scForbidden
: Http::scUnauthorized
;
2554 } else if (ctrl
.replycode
== 421) {
2555 error
= ERR_FTP_UNAVAILABLE
;
2556 return Http::scServiceUnavailable
;
2561 if (ctrl
.replycode
== 550) {
2562 error
= ERR_FTP_NOT_FOUND
;
2563 return Http::scNotFound
;
2570 return Ftp::ServerStateData::failedHttpStatus(error
);
2573 /// \ingroup ServerProtocolFTPInternal
2575 ftpSendReply(FtpStateData
* ftpState
)
2577 int code
= ftpState
->ctrl
.replycode
;
2578 Http::StatusCode http_code
;
2579 err_type err_code
= ERR_NONE
;
2581 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
2583 if (cbdataReferenceValid(ftpState
))
2584 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
2586 if (code
== 226 || code
== 250) {
2587 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
2588 http_code
= (ftpState
->mdtm
> 0) ? Http::scAccepted
: Http::scCreated
;
2589 } else if (code
== 227) {
2590 err_code
= ERR_FTP_PUT_CREATED
;
2591 http_code
= Http::scCreated
;
2593 err_code
= ERR_FTP_PUT_ERROR
;
2594 http_code
= Http::scInternalServerError
;
2597 ErrorState
err(err_code
, http_code
, ftpState
->request
);
2599 if (ftpState
->old_request
)
2600 err
.ftp
.request
= xstrdup(ftpState
->old_request
);
2602 err
.ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
2604 if (ftpState
->old_reply
)
2605 err
.ftp
.reply
= xstrdup(ftpState
->old_reply
);
2606 else if (ftpState
->ctrl
.last_reply
)
2607 err
.ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
2609 err
.ftp
.reply
= xstrdup("");
2611 // TODO: interpret as FTP-specific error code
2612 err
.detailError(code
);
2614 ftpState
->entry
->replaceHttpReply( err
.BuildHttpReply() );
2616 ftpSendQuit(ftpState
);
2620 FtpStateData::appendSuccessHeader()
2622 const char *mime_type
= NULL
;
2623 const char *mime_enc
= NULL
;
2624 String urlpath
= request
->urlpath
;
2625 const char *filename
= NULL
;
2626 const char *t
= NULL
;
2630 if (flags
.http_header_sent
)
2633 HttpReply
*reply
= new HttpReply
;
2635 flags
.http_header_sent
= 1;
2637 assert(entry
->isEmpty());
2639 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
2641 entry
->buffer(); /* released when done processing current data payload */
2643 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
2646 mime_type
= "text/html";
2651 mime_type
= "application/octet-stream";
2652 mime_enc
= mimeGetContentEncoding(filename
);
2656 mime_type
= "text/plain";
2660 mime_type
= mimeGetContentType(filename
);
2661 mime_enc
= mimeGetContentEncoding(filename
);
2666 /* set standard stuff */
2668 if (0 == getCurrentOffset()) {
2670 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2671 } else if (theSize
< getCurrentOffset()) {
2674 * offset should not be larger than theSize. We should
2675 * not be seeing this condition any more because we'll only
2676 * send REST if we know the theSize and if it is less than theSize.
2678 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
2679 " current offset=" << getCurrentOffset() <<
2680 ", but theSize=" << theSize
<<
2681 ". assuming full content response");
2682 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2685 HttpHdrRangeSpec range_spec
;
2686 range_spec
.offset
= getCurrentOffset();
2687 range_spec
.length
= theSize
- getCurrentOffset();
2688 reply
->setHeaders(Http::scPartialContent
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
2689 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
2692 /* additional info */
2694 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
2696 setVirginReply(reply
);
2697 adaptOrFinalizeReply();
2701 FtpStateData::haveParsedReplyHeaders()
2703 ServerStateData::haveParsedReplyHeaders();
2705 StoreEntry
*e
= entry
;
2709 if (flags
.authenticated
) {
2711 * Authenticated requests can't be cached.
2714 } else if (!EBIT_TEST(e
->flags
, RELEASE_REQUEST
) && !getCurrentOffset()) {
2722 FtpStateData::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
2724 ErrorState
err(ERR_CACHE_ACCESS_DENIED
, Http::scUnauthorized
, request
);
2725 HttpReply
*newrep
= err
.BuildHttpReply();
2726 #if HAVE_AUTH_MODULE_BASIC
2727 /* add Authenticate header */
2728 newrep
->header
.putAuth("Basic", realm
);
2734 \ingroup ServerProtocolFTPAPI
2735 \todo Should be a URL class API call.
2737 * Construct an URI with leading / in PATH portion for use by CWD command
2738 * possibly others. FTP encodes absolute paths as beginning with '/'
2739 * after the initial URI path delimiter, which happens to be / itself.
2740 * This makes FTP absolute URI appear as: ftp:host:port//root/path
2741 * To encompass older software which compacts multiple // to / in transit
2742 * We use standard URI-encoding on the second / making it
2743 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
2746 ftpUrlWith2f(HttpRequest
* request
)
2748 String newbuf
= "%2f";
2750 if (request
->protocol
!= AnyP::PROTO_FTP
)
2753 if ( request
->urlpath
[0]=='/' ) {
2754 newbuf
.append(request
->urlpath
);
2755 request
->urlpath
.absorb(newbuf
);
2756 safe_free(request
->canonical
);
2757 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
2758 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
2759 request
->urlpath
.absorb(newbuf
);
2760 safe_free(request
->canonical
);
2763 return urlCanonical(request
);
2767 FtpStateData::printfReplyBody(const char *fmt
, ...)
2770 va_start (args
, fmt
);
2771 static char buf
[4096];
2773 vsnprintf(buf
, 4096, fmt
, args
);
2774 writeReplyBody(buf
, strlen(buf
));
2779 * Call this when there is data from the origin server
2780 * which should be sent to either StoreEntry, or to ICAP...
2783 FtpStateData::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
2785 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
2786 addVirginReplyBody(dataToWrite
, dataLength
);
2790 * A hack to ensure we do not double-complete on the forward entry.
2792 \todo FtpStateData logic should probably be rewritten to avoid
2793 * double-completion or FwdState should be rewritten to allow it.
2796 FtpStateData::completeForwarding()
2798 if (fwd
== NULL
|| flags
.completed_forwarding
) {
2799 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
2800 "double-complete on FD " << ctrl
.conn
->fd
<< ", Data FD " << data
.conn
->fd
<<
2801 ", this " << this << ", fwd " << fwd
);
2805 flags
.completed_forwarding
= true;
2806 ServerStateData::completeForwarding();
2810 * Have we lost the FTP server control channel?
2812 \retval true The server control channel is available.
2813 \retval false The server control channel is not available.
2816 FtpStateData::haveControlChannel(const char *caller_name
) const
2818 if (doneWithServer())
2821 /* doneWithServer() only checks BOTH channels are closed. */
2822 if (!Comm::IsConnOpen(ctrl
.conn
)) {
2823 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
2824 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");