2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 09 File Transfer Protocol (FTP) */
12 #include "acl/FilledChecklist.h"
13 #include "base/PackableStream.h"
14 #include "clients/forward.h"
15 #include "clients/FtpClient.h"
17 #include "comm/ConnOpener.h"
18 #include "comm/Read.h"
19 #include "comm/TcpAcceptor.h"
20 #include "CommCalls.h"
21 #include "compat/strtoll.h"
22 #include "errorpage.h"
26 #include "html_quote.h"
27 #include "HttpHdrContRange.h"
28 #include "HttpHeader.h"
29 #include "HttpHeaderRange.h"
30 #include "HttpReply.h"
35 #include "SquidConfig.h"
36 #include "SquidString.h"
37 #include "SquidTime.h"
38 #include "StatCounters.h"
45 #include "DelayPools.h"
46 #include "MemObject.h"
57 bool pasv_supported
; ///< PASV command is allowed
58 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
60 bool pasv_failed
; // was FwdState::flags.ftp_pasv_failed
63 bool authenticated
; ///< authentication success
64 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
65 bool tried_auth_nopass
; ///< auth tried username with no password already.
71 bool http_header_sent
;
81 bool listformat_unknown
;
83 bool completed_forwarding
;
87 typedef void (StateMethod
)(Ftp::Gateway
*);
89 /// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI,
90 /// converts it into one or more FTP commands, and then
91 /// converts one or more FTP responses into the final HTTP response.
92 class Gateway
: public Ftp::Client
94 CBDATA_CLASS(Gateway
);
100 char password
[MAX_URL
];
114 int64_t restart_offset
;
120 MemBuf listing
; ///< FTP directory listing in HTML format.
125 // these should all be private
126 virtual void start();
127 virtual Http::StatusCode
failedHttpStatus(err_type
&error
);
129 void appendSuccessHeader();
130 void hackShortcut(StateMethod
*nextState
);
134 bool htmlifyListEntry(const char *line
, PackableStream
&);
135 void completedListing(void);
137 /// create a data channel acceptor and start listening.
138 void listenForDataChannel(const Comm::ConnectionPointer
&conn
);
140 int checkAuth(const HttpHeader
* req_hdr
);
142 void buildTitleUrl();
143 void writeReplyBody(const char *, size_t len
);
144 void printfReplyBody(const char *fmt
, ...);
145 virtual void completeForwarding();
146 void processHeadResponse();
147 void processReplyBody();
148 void setCurrentOffset(int64_t offset
) { currentOffset
= offset
; }
149 int64_t getCurrentOffset() const { return currentOffset
; }
151 virtual void dataChannelConnected(const CommConnectCbParams
&io
);
152 static PF ftpDataWrite
;
153 virtual void timeout(const CommTimeoutCbParams
&io
);
154 void ftpAcceptDataConnection(const CommAcceptCbParams
&io
);
156 static HttpReply
*ftpAuthRequired(HttpRequest
* request
, SBuf
&realm
);
158 void loginFailed(void);
160 virtual void haveParsedReplyHeaders();
162 virtual bool haveControlChannel(const char *caller_name
) const;
165 virtual void handleControlReply();
166 virtual void dataClosed(const CommCloseCbParams
&io
);
169 virtual bool mayReadVirginReplyBody() const;
170 // BodyConsumer for HTTP: consume request body.
171 virtual void handleRequestBodyProducerAborted();
173 void loginParser(const SBuf
&login
, bool escaped
);
178 typedef Ftp::StateMethod FTPSM
; // to avoid lots of non-changes
180 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Gateway
);
191 #define CTRL_BUFLEN 16*1024
192 static char cbuf
[CTRL_BUFLEN
];
195 * State machine functions
196 * send == state transition
197 * read == wait for response, and select next state transition
198 * other == Transition logic
200 static FTPSM ftpReadWelcome
;
201 static FTPSM ftpSendUser
;
202 static FTPSM ftpReadUser
;
203 static FTPSM ftpSendPass
;
204 static FTPSM ftpReadPass
;
205 static FTPSM ftpSendType
;
206 static FTPSM ftpReadType
;
207 static FTPSM ftpSendMdtm
;
208 static FTPSM ftpReadMdtm
;
209 static FTPSM ftpSendSize
;
210 static FTPSM ftpReadSize
;
212 static FTPSM ftpSendEPRT
;
214 static FTPSM ftpReadEPRT
;
215 static FTPSM ftpSendPORT
;
216 static FTPSM ftpReadPORT
;
217 static FTPSM ftpSendPassive
;
218 static FTPSM ftpReadEPSV
;
219 static FTPSM ftpReadPasv
;
220 static FTPSM ftpTraverseDirectory
;
221 static FTPSM ftpListDir
;
222 static FTPSM ftpGetFile
;
223 static FTPSM ftpSendCwd
;
224 static FTPSM ftpReadCwd
;
225 static FTPSM ftpRestOrList
;
226 static FTPSM ftpSendList
;
227 static FTPSM ftpSendNlst
;
228 static FTPSM ftpReadList
;
229 static FTPSM ftpSendRest
;
230 static FTPSM ftpReadRest
;
231 static FTPSM ftpSendRetr
;
232 static FTPSM ftpReadRetr
;
233 static FTPSM ftpReadTransferDone
;
234 static FTPSM ftpSendStor
;
235 static FTPSM ftpReadStor
;
236 static FTPSM ftpWriteTransferDone
;
237 static FTPSM ftpSendReply
;
238 static FTPSM ftpSendMkdir
;
239 static FTPSM ftpReadMkdir
;
240 static FTPSM ftpFail
;
241 static FTPSM ftpSendQuit
;
242 static FTPSM ftpReadQuit
;
244 /************************************************
245 ** Debugs Levels used here **
246 *************************************************
249 Protocol and Transmission failures.
250 2 FTP Protocol Chatter
255 ************************************************/
257 /************************************************
258 ** State Machine Description (excluding hacks) **
259 *************************************************
261 ---------------------------------------
265 Type TraverseDirectory / GetFile
266 TraverseDirectory Cwd / GetFile / ListDir
267 Cwd TraverseDirectory / Mkdir
273 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
275 Retr / Nlst / List DataRead* (on datachannel)
276 DataRead* ReadTransferDone
277 ReadTransferDone DataTransferDone
278 Stor DataWrite* (on datachannel)
279 DataWrite* RequestPutBody** (from client)
280 RequestPutBody** DataWrite* / WriteTransferDone
281 WriteTransferDone DataTransferDone
282 DataTransferDone Quit
284 ************************************************/
286 FTPSM
*FTP_SM_FUNCS
[] = {
287 ftpReadWelcome
, /* BEGIN */
288 ftpReadUser
, /* SENT_USER */
289 ftpReadPass
, /* SENT_PASS */
290 ftpReadType
, /* SENT_TYPE */
291 ftpReadMdtm
, /* SENT_MDTM */
292 ftpReadSize
, /* SENT_SIZE */
293 ftpReadEPRT
, /* SENT_EPRT */
294 ftpReadPORT
, /* SENT_PORT */
295 ftpReadEPSV
, /* SENT_EPSV_ALL */
296 ftpReadEPSV
, /* SENT_EPSV_1 */
297 ftpReadEPSV
, /* SENT_EPSV_2 */
298 ftpReadPasv
, /* SENT_PASV */
299 ftpReadCwd
, /* SENT_CWD */
300 ftpReadList
, /* SENT_LIST */
301 ftpReadList
, /* SENT_NLST */
302 ftpReadRest
, /* SENT_REST */
303 ftpReadRetr
, /* SENT_RETR */
304 ftpReadStor
, /* SENT_STOR */
305 ftpReadQuit
, /* SENT_QUIT */
306 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
307 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
308 ftpReadMkdir
, /* SENT_MKDIR */
309 NULL
, /* SENT_FEAT */
312 NULL
, /* SENT_DATA_REQUEST */
313 NULL
/* SENT_COMMAND */
316 /// handler called by Comm when FTP data channel is closed unexpectedly
318 Ftp::Gateway::dataClosed(const CommCloseCbParams
&io
)
320 Ftp::Client::dataClosed(io
);
321 failed(ERR_FTP_FAILURE
, 0);
322 /* failed closes ctrl.conn and frees ftpState */
324 /* NP: failure recovery may be possible when its only a data.conn failure.
325 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
326 * Just need to watch out for wider Squid states like shutting down or reconfigure.
330 Ftp::Gateway::Gateway(FwdState
*fwdState
):
331 AsyncJob("FtpStateData"),
332 Ftp::Client(fwdState
),
349 debugs(9, 3, entry
->url());
353 memset(&flags
, 0, sizeof(flags
));
355 if (Config
.Ftp
.passive
&& !flags
.pasv_failed
)
356 flags
.pasv_supported
= 1;
358 flags
.rest_supported
= 1;
360 if (request
->method
== Http::METHOD_PUT
)
366 Ftp::Gateway::~Gateway()
368 debugs(9, 3, entry
->url());
370 if (Comm::IsConnOpen(ctrl
.conn
)) {
371 debugs(9, DBG_IMPORTANT
, "Internal bug: FTP Gateway left open " <<
372 "control channel " << ctrl
.conn
);
376 memFree(reply_hdr
, MEM_8K_BUF
);
381 wordlistDestroy(&pathcomps
);
392 * Parse a possible login username:password pair.
393 * Produces filled member variables user, password, password_url if anything found.
395 * \param login a decoded Basic authentication credential token or URI user-info token
396 * \param escaped whether to URL-decode the token after extracting user and password
399 Ftp::Gateway::loginParser(const SBuf
&login
, bool escaped
)
401 debugs(9, 4, "login=" << login
<< ", escaped=" << escaped
);
402 debugs(9, 9, "IN : login=" << login
<< ", escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
407 const SBuf::size_type colonPos
= login
.find(':');
409 /* If there was a username part with at least one character use it.
410 * Ignore 0-length username portion, retain what we have already.
412 if (colonPos
== SBuf::npos
|| colonPos
> 0) {
413 const SBuf userName
= login
.substr(0, colonPos
);
414 SBuf::size_type upto
= userName
.copy(user
, sizeof(user
)-1);
416 debugs(9, 9, "found user=" << userName
<< ' ' <<
417 (upto
!= userName
.length() ? ", truncated-to=" : ", length=") << upto
<<
418 ", escaped=" << escaped
);
420 rfc1738_unescape(user
);
421 debugs(9, 9, "found user=" << user
<< " (" << strlen(user
) << ") unescaped.");
424 /* If there was a password part.
425 * For 0-length password clobber what we have already, this means explicitly none
427 if (colonPos
!= SBuf::npos
) {
428 const SBuf pass
= login
.substr(colonPos
+1, SBuf::npos
);
429 SBuf::size_type upto
= pass
.copy(password
, sizeof(password
)-1);
431 debugs(9, 9, "found password=" << pass
<< " " <<
432 (upto
!= pass
.length() ? ", truncated-to=" : ", length=") << upto
<<
433 ", escaped=" << escaped
);
435 rfc1738_unescape(password
);
438 debugs(9, 9, "found password=" << password
<< " (" << strlen(password
) << ") unescaped.");
441 debugs(9, 9, "OUT: login=" << login
<< ", escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
445 Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer
&conn
)
447 if (!Comm::IsConnOpen(ctrl
.conn
)) {
448 debugs(9, 5, "The control connection to the remote end is closed");
452 assert(!Comm::IsConnOpen(data
.conn
));
454 typedef CommCbMemFunT
<Gateway
, CommAcceptCbParams
> AcceptDialer
;
455 typedef AsyncCallT
<AcceptDialer
> AcceptCall
;
456 RefCount
<AcceptCall
> call
= static_cast<AcceptCall
*>(JobCallback(11, 5, AcceptDialer
, this, Ftp::Gateway::ftpAcceptDataConnection
));
457 Subscription::Pointer sub
= new CallSubscription
<AcceptCall
>(call
);
458 const char *note
= entry
->url();
460 /* open the conn if its not already open */
461 if (!Comm::IsConnOpen(conn
)) {
462 conn
->fd
= comm_open_listener(SOCK_STREAM
, IPPROTO_TCP
, conn
->local
, conn
->flags
, note
);
463 if (!Comm::IsConnOpen(conn
)) {
464 debugs(5, DBG_CRITICAL
, HERE
<< "comm_open_listener failed:" << conn
->local
<< " error: " << errno
);
467 debugs(9, 3, HERE
<< "Unconnected data socket created on " << conn
);
470 conn
->tos
= ctrl
.conn
->tos
;
471 conn
->nfmark
= ctrl
.conn
->nfmark
;
473 assert(Comm::IsConnOpen(conn
));
474 AsyncJob::Start(new Comm::TcpAcceptor(conn
, note
, sub
));
476 // Ensure we have a copy of the FD opened for listening and a close handler on it.
477 data
.opened(conn
, dataCloser());
478 switchTimeoutToDataChannel();
482 Ftp::Gateway::timeout(const CommTimeoutCbParams
&io
)
484 if (SENT_PASV
== state
) {
485 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
486 flags
.pasv_supported
= false;
487 debugs(9, DBG_IMPORTANT
, "FTP Gateway timeout in SENT_PASV state");
489 // cancel the data connection setup.
490 if (data
.opener
!= NULL
) {
491 data
.opener
->cancel("timeout");
497 Ftp::Client::timeout(io
);
500 static const char *Month
[] = {
501 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
502 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
506 is_month(const char *buf
)
510 for (i
= 0; i
< 12; ++i
)
511 if (!strcasecmp(buf
, Month
[i
]))
518 ftpListPartsFree(ftpListParts
** parts
)
520 safe_free((*parts
)->date
);
521 safe_free((*parts
)->name
);
522 safe_free((*parts
)->showname
);
523 safe_free((*parts
)->link
);
527 #define MAX_TOKENS 64
529 static ftpListParts
*
530 ftpListParseParts(const char *buf
, struct Ftp::GatewayFlags flags
)
532 ftpListParts
*p
= NULL
;
534 const char *ct
= NULL
;
535 char *tokens
[MAX_TOKENS
];
538 static char tbuf
[128];
540 static int scan_ftp_initialized
= 0;
541 static regex_t scan_ftp_integer
;
542 static regex_t scan_ftp_time
;
543 static regex_t scan_ftp_dostime
;
544 static regex_t scan_ftp_dosdate
;
546 if (!scan_ftp_initialized
) {
547 scan_ftp_initialized
= 1;
548 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
549 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
550 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
551 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
560 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
564 memset(tokens
, 0, sizeof(tokens
));
568 if (flags
.tried_nlst
) {
569 /* Machine readable format, one name per line */
575 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
)) {
576 tokens
[n_tokens
] = xstrdup(t
);
582 /* locate the Month field */
583 for (i
= 3; i
< n_tokens
- 2; ++i
) {
584 char *size
= tokens
[i
- 1];
585 char *month
= tokens
[i
];
586 char *day
= tokens
[i
+ 1];
587 char *year
= tokens
[i
+ 2];
589 if (!is_month(month
))
592 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
595 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
598 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
601 snprintf(tbuf
, 128, "%s %2s %5s",
604 if (!strstr(buf
, tbuf
))
605 snprintf(tbuf
, 128, "%s %2s %-5s",
608 char const *copyFrom
= NULL
;
610 if ((copyFrom
= strstr(buf
, tbuf
))) {
611 p
->type
= *tokens
[0];
612 p
->size
= strtoll(size
, NULL
, 10);
613 p
->date
= xstrdup(tbuf
);
615 if (flags
.skip_whitespace
) {
616 copyFrom
+= strlen(tbuf
);
618 while (strchr(w_space
, *copyFrom
))
621 /* Handle the following four formats:
626 * Assuming a single space between date and filename
627 * suggested by: Nathan.Bailey@cc.monash.edu.au and
628 * Mike Battersby <mike@starbug.bofh.asn.au> */
629 copyFrom
+= strlen(tbuf
);
630 if (strchr(w_space
, *copyFrom
))
634 p
->name
= xstrdup(copyFrom
);
636 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
638 p
->link
= xstrdup(t
+ 4);
647 /* try it as a DOS listing, 04-05-70 09:33PM ... */
649 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
650 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
651 if (!strcasecmp(tokens
[2], "<dir>")) {
655 p
->size
= strtoll(tokens
[2], NULL
, 10);
658 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
659 p
->date
= xstrdup(tbuf
);
661 if (p
->type
== 'd') {
662 /* Directory.. name begins with first printable after <dir> */
663 ct
= strstr(buf
, tokens
[2]);
664 ct
+= strlen(tokens
[2]);
666 while (xisspace(*ct
))
672 /* A file. Name begins after size, with a space in between */
673 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
674 ct
= strstr(buf
, tbuf
);
677 ct
+= strlen(tokens
[2]) + 2;
681 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
685 /* Try EPLF format; carson@lehman.com */
692 int l
= strcspn(ct
, ",");
701 p
->name
= xstrndup(ct
+ 1, l
+ 1);
705 p
->size
= atoi(ct
+ 1);
709 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
712 break; /* not a valid integer */
714 p
->date
= xstrdup(ctime(&tm
));
716 *(strstr(p
->date
, "\n")) = '\0';
738 ct
= strstr(ct
, ",");
757 for (i
= 0; i
< n_tokens
; ++i
)
761 ftpListPartsFree(&p
); /* cleanup */
767 Ftp::Gateway::htmlifyListEntry(const char *line
, PackableStream
&html
)
769 debugs(9, 7, "line={" << line
<< "}");
771 if (strlen(line
) > 1024) {
772 html
<< "<tr><td colspan=\"5\">" << line
<< "</td></tr>\n";
777 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D') {
778 prefix
.append(rfc1738_escape_part(dirpath
));
779 prefix
.append("/", 1);
782 ftpListParts
*parts
= ftpListParseParts(line
, flags
);
784 html
<< "<tr class=\"entry\"><td colspan=\"5\">" << line
<< "</td></tr>\n";
787 for (p
= line
; *p
&& xisspace(*p
); ++p
);
788 if (*p
&& !xisspace(*p
))
789 flags
.listformat_unknown
= 1;
794 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
795 ftpListPartsFree(&parts
);
801 parts
->showname
= xstrdup(parts
->name
);
803 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
805 href
.append(rfc1738_escape_part(parts
->name
));
807 SBuf
text(parts
->showname
);
809 SBuf icon
, size
, chdir
, link
;
810 switch (parts
->type
) {
813 icon
.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
814 mimeGetIconURL("internal-dir"),
816 href
.append("/", 1); /* margin is allocated above */
820 icon
.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
821 mimeGetIconURL("internal-link"),
823 /* sometimes there is an 'l' flag, but no "->" link */
826 SBuf
link2(html_quote(rfc1738_escape(parts
->link
)));
827 link
.appendf(" -> <a href=\"%s" SQUIDSBUFPH
"\">%s</a>",
828 link2
[0] != '/' ? prefix
.c_str() : "", SQUIDSBUFPRINT(link2
),
829 html_quote(parts
->link
));
835 icon
.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
836 mimeGetIconURL(parts
->name
),
838 chdir
.appendf("<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
839 "alt=\"[DIR]\"></a>",
840 rfc1738_escape_part(parts
->name
),
841 mimeGetIconURL("internal-dir"));
847 icon
.appendf("<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
848 mimeGetIconURL(parts
->name
),
850 size
.appendf(" %6" PRId64
"k", parts
->size
);
855 if (parts
->type
!= 'd') {
856 if (mimeGetViewOption(parts
->name
)) {
857 view
.appendf("<a href=\"" SQUIDSBUFPH
";type=a\"><img border=\"0\" src=\"%s\" "
858 "alt=\"[VIEW]\"></a>",
859 SQUIDSBUFPRINT(href
), mimeGetIconURL("internal-view"));
862 if (mimeGetDownloadOption(parts
->name
)) {
863 download
.appendf("<a href=\"" SQUIDSBUFPH
";type=i\"><img border=\"0\" src=\"%s\" "
864 "alt=\"[DOWNLOAD]\"></a>",
865 SQUIDSBUFPRINT(href
), mimeGetIconURL("internal-download"));
869 /* construct the table row from parts. */
870 html
<< "<tr class=\"entry\">"
871 "<td class=\"icon\"><a href=\"" << href
<< "\">" << icon
<< "</a></td>"
872 "<td class=\"filename\"><a href=\"" << href
<< "\">" << html_quote(text
.c_str()) << "</a></td>"
873 "<td class=\"date\">" << parts
->date
<< "</td>"
874 "<td class=\"size\">" << size
<< "</td>"
875 "<td class=\"actions\">" << chdir
<< view
<< download
<< link
<< "</td>"
878 ftpListPartsFree(&parts
);
883 Ftp::Gateway::parseListing()
885 char *buf
= data
.readBuf
->content();
886 char *sbuf
; /* NULL-terminated copy of termedBuf */
892 size_t len
= data
.readBuf
->contentSize();
895 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
900 * We need a NULL-terminated buffer for scanning, ick
902 sbuf
= (char *)xmalloc(len
+ 1);
903 xstrncpy(sbuf
, buf
, len
+ 1);
904 end
= sbuf
+ len
- 1;
906 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
911 debugs(9, 3, HERE
<< "usable = " << usable
<< " of " << len
<< " bytes.");
914 if (buf
[0] == '\0' && len
== 1) {
915 debugs(9, 3, HERE
<< "NIL ends data from " << entry
->url() << " transfer problem?");
916 data
.readBuf
->consume(len
);
918 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url());
919 debugs(9, 3, HERE
<< "buffer remains (" << len
<< " bytes) '" << rfc1738_do_escape(buf
,0) << "'");
925 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
927 line
= (char *)memAllocate(MEM_4K_BUF
);
930 s
+= strspn(s
, crlf
);
932 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
933 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
934 linelen
= strcspn(s
, crlf
) + 1;
942 xstrncpy(line
, s
, linelen
);
944 debugs(9, 7, HERE
<< "{" << line
<< "}");
946 if (!strncmp(line
, "total", 5))
951 PackableStream
html(htmlPage
);
953 if (htmlifyListEntry(line
, html
)) {
955 debugs(9, 7, "listing append: t = {" << htmlPage
.contentSize() << ", '" << htmlPage
.content() << "'}");
956 listing
.append(htmlPage
.content(), htmlPage
.contentSize());
960 debugs(9, 7, HERE
<< "Done.");
961 data
.readBuf
->consume(usable
);
962 memFree(line
, MEM_4K_BUF
);
967 Ftp::Gateway::processReplyBody()
969 debugs(9, 3, status());
971 if (request
->method
== Http::METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
976 /* Directory listings are special. They write ther own headers via the error objects */
977 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
978 appendSuccessHeader();
980 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
982 * probably was aborted because content length exceeds one
983 * of the maximum size limits.
985 abortAll("entry aborted after calling appendSuccessHeader()");
991 if (adaptationAccessCheckPending
) {
992 debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
999 if (!flags
.listing
) {
1004 maybeReadVirginBody();
1006 } else if (const int csize
= data
.readBuf
->contentSize()) {
1007 writeReplyBody(data
.readBuf
->content(), csize
);
1008 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1009 data
.readBuf
->consume(csize
);
1014 maybeReadVirginBody();
1018 * Locates the FTP user:password login.
1020 * Highest to lowest priority:
1021 * - Checks URL (ftp://user:pass@domain)
1022 * - Authorization: Basic header
1023 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1025 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1027 * TODO: we might be able to do something about locating username from other sources:
1028 * ie, external ACL user=* tag or ident lookup
1030 \retval 1 if we have everything needed to complete this request.
1031 \retval 0 if something is missing.
1034 Ftp::Gateway::checkAuth(const HttpHeader
* req_hdr
)
1036 /* default username */
1037 xstrncpy(user
, "anonymous", MAX_URL
);
1039 #if HAVE_AUTH_MODULE_BASIC
1040 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1041 const SBuf
auth(req_hdr
->getAuth(Http::HdrType::AUTHORIZATION
, "Basic"));
1042 if (!auth
.isEmpty()) {
1043 flags
.authenticated
= 1;
1044 loginParser(auth
, false);
1046 /* we fail with authorization-required error later IFF the FTP server requests it */
1049 /* Test URL login syntax. Overrides any headers received. */
1050 loginParser(request
->url
.userInfo(), true);
1052 /* name is missing. thats fatal. */
1054 fatal("FTP login parsing destroyed username info");
1056 /* name + password == success */
1060 /* Setup default FTP password settings */
1061 /* this has to be done last so that we can have a no-password case above. */
1063 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1064 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1065 flags
.tried_auth_anonymous
=1;
1067 } else if (!flags
.tried_auth_nopass
) {
1068 xstrncpy(password
, null_string
, MAX_URL
);
1069 flags
.tried_auth_nopass
=1;
1074 return 0; /* different username */
1078 Ftp::Gateway::checkUrlpath()
1080 static SBuf
str_type_eq("type=");
1081 auto t
= request
->url
.path().rfind(';');
1083 if (t
!= SBuf::npos
) {
1084 auto filenameEnd
= t
-1;
1085 if (request
->url
.path().substr(++t
).cmp(str_type_eq
, str_type_eq
.length()) == 0) {
1086 t
+= str_type_eq
.length();
1087 typecode
= (char)xtoupper(request
->url
.path()[t
]);
1088 request
->url
.path(request
->url
.path().substr(0,filenameEnd
));
1092 int l
= request
->url
.path().length();
1093 /* check for null path */
1098 flags
.need_base_href
= 1; /* Work around broken browsers */
1099 } else if (!request
->url
.path().cmp("/%2f/")) {
1100 /* UNIX root directory */
1103 } else if ((l
>= 1) && (request
->url
.path()[l
-1] == '/')) {
1104 /* Directory URL, ending in / */
1110 flags
.dir_slash
= 1;
1115 Ftp::Gateway::buildTitleUrl()
1117 title_url
= "ftp://";
1119 if (strcmp(user
, "anonymous")) {
1120 title_url
.append(user
);
1121 title_url
.append("@");
1124 SBuf authority
= request
->url
.authority(request
->url
.getScheme() != AnyP::PROTO_FTP
);
1126 title_url
.append(authority
.rawContent(), authority
.length());
1127 title_url
.append(request
->url
.path().rawContent(), request
->url
.path().length());
1129 base_href
= "ftp://";
1131 if (strcmp(user
, "anonymous") != 0) {
1132 base_href
.append(rfc1738_escape_part(user
));
1135 base_href
.append(":");
1136 base_href
.append(rfc1738_escape_part(password
));
1139 base_href
.append("@");
1142 base_href
.append(authority
.rawContent(), authority
.length());
1143 base_href
.append(request
->url
.path().rawContent(), request
->url
.path().length());
1144 base_href
.append("/");
1148 Ftp::Gateway::start()
1150 if (!checkAuth(&request
->header
)) {
1151 /* create appropriate reply */
1152 SBuf
realm(ftpRealm()); // local copy so SBuf will not disappear too early
1153 HttpReply
*reply
= ftpAuthRequired(request
.getRaw(), realm
);
1154 entry
->replaceHttpReply(reply
);
1161 debugs(9, 5, "FD " << (ctrl
.conn
? ctrl
.conn
->fd
: -1) << " : host=" << request
->url
.host() <<
1162 ", path=" << request
->url
.path() << ", user=" << user
<< ", passwd=" << password
);
1164 Ftp::Client::start();
1167 /* ====================================================================== */
1170 Ftp::Gateway::handleControlReply()
1172 Ftp::Client::handleControlReply();
1173 if (ctrl
.message
== NULL
)
1174 return; // didn't get complete reply yet
1176 /* Copy the message except for the last line to cwd_message to be
1177 * printed in error messages.
1179 for (wordlist
*w
= ctrl
.message
; w
&& w
->next
; w
= w
->next
) {
1180 cwd_message
.append('\n');
1181 cwd_message
.append(w
->key
);
1184 FTP_SM_FUNCS
[state
] (this);
1187 /* ====================================================================== */
1190 ftpReadWelcome(Ftp::Gateway
* ftpState
)
1192 int code
= ftpState
->ctrl
.replycode
;
1195 if (ftpState
->flags
.pasv_only
)
1196 ++ ftpState
->login_att
;
1199 if (ftpState
->ctrl
.message
) {
1200 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1201 ftpState
->flags
.skip_whitespace
= 1;
1204 ftpSendUser(ftpState
);
1205 } else if (code
== 120) {
1206 if (NULL
!= ftpState
->ctrl
.message
)
1207 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1216 * Translate FTP login failure into HTTP error
1217 * this is an attmpt to get the 407 message to show up outside Squid.
1218 * its NOT a general failure. But a correct FTP response type.
1221 Ftp::Gateway::loginFailed()
1223 ErrorState
*err
= NULL
;
1225 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1226 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1227 // 421/426 - Service Overload - retry permitted.
1228 err
= new ErrorState(ERR_FTP_UNAVAILABLE
, Http::scServiceUnavailable
, fwd
->request
);
1229 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1230 // 43x - Invalid or Credential Error - retry challenge required.
1231 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1232 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1233 // 53x - Credentials Missing - retry challenge required
1234 if (password_url
) // but they were in the URI! major fail.
1235 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scForbidden
, fwd
->request
);
1237 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1246 failed(ERR_NONE
, ctrl
.replycode
, err
);
1247 // any other problems are general falures.
1249 HttpReply
*newrep
= err
->BuildHttpReply();
1252 #if HAVE_AUTH_MODULE_BASIC
1253 /* add Authenticate header */
1254 // XXX: performance regression. c_str() may reallocate
1255 SBuf
realm(ftpRealm()); // local copy so SBuf will not disappear too early
1256 newrep
->header
.putAuth("Basic", realm
.c_str());
1259 // add it to the store entry for response....
1260 entry
->replaceHttpReply(newrep
);
1265 Ftp::Gateway::ftpRealm()
1269 /* This request is not fully authenticated */
1270 realm
.appendf("FTP %s ", user
);
1272 realm
.append("unknown", 7);
1274 realm
.append(request
->url
.host());
1275 if (request
->url
.port() != 21)
1276 realm
.appendf(" port %d", request
->url
.port());
1282 ftpSendUser(Ftp::Gateway
* ftpState
)
1284 /* check the server control channel is still available */
1285 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1288 if (ftpState
->proxy_host
!= NULL
)
1289 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s@%s\r\n", ftpState
->user
, ftpState
->request
->url
.host());
1291 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s\r\n", ftpState
->user
);
1293 ftpState
->writeCommand(cbuf
);
1295 ftpState
->state
= Ftp::Client::SENT_USER
;
1299 ftpReadUser(Ftp::Gateway
* ftpState
)
1301 int code
= ftpState
->ctrl
.replycode
;
1305 ftpReadPass(ftpState
);
1306 } else if (code
== 331) {
1307 ftpSendPass(ftpState
);
1309 ftpState
->loginFailed();
1314 ftpSendPass(Ftp::Gateway
* ftpState
)
1316 /* check the server control channel is still available */
1317 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
1320 snprintf(cbuf
, CTRL_BUFLEN
, "PASS %s\r\n", ftpState
->password
);
1321 ftpState
->writeCommand(cbuf
);
1322 ftpState
->state
= Ftp::Client::SENT_PASS
;
1326 ftpReadPass(Ftp::Gateway
* ftpState
)
1328 int code
= ftpState
->ctrl
.replycode
;
1329 debugs(9, 3, HERE
<< "code=" << code
);
1332 ftpSendType(ftpState
);
1334 ftpState
->loginFailed();
1339 ftpSendType(Ftp::Gateway
* ftpState
)
1341 /* check the server control channel is still available */
1342 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
1346 * Ref section 3.2.2 of RFC 1738
1348 char mode
= ftpState
->typecode
;
1363 if (ftpState
->flags
.isdir
) {
1366 auto t
= ftpState
->request
->url
.path().rfind('/');
1367 // XXX: performance regression, c_str() may reallocate
1368 SBuf filename
= ftpState
->request
->url
.path().substr(t
!= SBuf::npos
? t
+ 1 : 0);
1369 mode
= mimeGetTransferMode(filename
.c_str());
1376 ftpState
->flags
.binary
= 1;
1378 ftpState
->flags
.binary
= 0;
1380 snprintf(cbuf
, CTRL_BUFLEN
, "TYPE %c\r\n", mode
);
1382 ftpState
->writeCommand(cbuf
);
1384 ftpState
->state
= Ftp::Client::SENT_TYPE
;
1388 ftpReadType(Ftp::Gateway
* ftpState
)
1390 int code
= ftpState
->ctrl
.replycode
;
1393 debugs(9, 3, HERE
<< "code=" << code
);
1396 p
= path
= SBufToCstring(ftpState
->request
->url
.path());
1403 p
+= strcspn(p
, "/");
1410 rfc1738_unescape(d
);
1413 wordlistAdd(&ftpState
->pathcomps
, d
);
1418 if (ftpState
->pathcomps
)
1419 ftpTraverseDirectory(ftpState
);
1421 ftpListDir(ftpState
);
1428 ftpTraverseDirectory(Ftp::Gateway
* ftpState
)
1430 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
1432 safe_free(ftpState
->dirpath
);
1433 ftpState
->dirpath
= ftpState
->filepath
;
1434 ftpState
->filepath
= NULL
;
1438 if (ftpState
->pathcomps
== NULL
) {
1439 debugs(9, 3, HERE
<< "the final component was a directory");
1440 ftpListDir(ftpState
);
1444 /* Go to next path component */
1445 ftpState
->filepath
= wordlistChopHead(& ftpState
->pathcomps
);
1447 /* Check if we are to CWD or RETR */
1448 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
1449 ftpSendCwd(ftpState
);
1451 debugs(9, 3, HERE
<< "final component is probably a file");
1452 ftpGetFile(ftpState
);
1458 ftpSendCwd(Ftp::Gateway
* ftpState
)
1462 /* check the server control channel is still available */
1463 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
1468 path
= ftpState
->filepath
;
1470 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
1471 ftpState
->flags
.no_dotdot
= 1;
1473 ftpState
->flags
.no_dotdot
= 0;
1476 snprintf(cbuf
, CTRL_BUFLEN
, "CWD %s\r\n", path
);
1478 ftpState
->writeCommand(cbuf
);
1480 ftpState
->state
= Ftp::Client::SENT_CWD
;
1484 ftpReadCwd(Ftp::Gateway
* ftpState
)
1486 int code
= ftpState
->ctrl
.replycode
;
1489 if (code
>= 200 && code
< 300) {
1493 /* Reset cwd_message to only include the last message */
1494 ftpState
->cwd_message
.reset("");
1495 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
1496 ftpState
->cwd_message
.append('\n');
1497 ftpState
->cwd_message
.append(w
->key
);
1499 ftpState
->ctrl
.message
= NULL
;
1501 /* Continue to traverse the path */
1502 ftpTraverseDirectory(ftpState
);
1506 if (!ftpState
->flags
.put
)
1509 ftpSendMkdir(ftpState
);
1514 ftpSendMkdir(Ftp::Gateway
* ftpState
)
1518 /* check the server control channel is still available */
1519 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
1522 path
= ftpState
->filepath
;
1523 debugs(9, 3, HERE
<< "with path=" << path
);
1524 snprintf(cbuf
, CTRL_BUFLEN
, "MKD %s\r\n", path
);
1525 ftpState
->writeCommand(cbuf
);
1526 ftpState
->state
= Ftp::Client::SENT_MKDIR
;
1530 ftpReadMkdir(Ftp::Gateway
* ftpState
)
1532 char *path
= ftpState
->filepath
;
1533 int code
= ftpState
->ctrl
.replycode
;
1535 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
1537 if (code
== 257) { /* success */
1538 ftpSendCwd(ftpState
);
1539 } else if (code
== 550) { /* dir exists */
1541 if (ftpState
->flags
.put_mkdir
) {
1542 ftpState
->flags
.put_mkdir
= 1;
1543 ftpSendCwd(ftpState
);
1545 ftpSendReply(ftpState
);
1547 ftpSendReply(ftpState
);
1551 ftpGetFile(Ftp::Gateway
* ftpState
)
1553 assert(*ftpState
->filepath
!= '\0');
1554 ftpState
->flags
.isdir
= 0;
1555 ftpSendMdtm(ftpState
);
1559 ftpListDir(Ftp::Gateway
* ftpState
)
1561 if (ftpState
->flags
.dir_slash
) {
1562 debugs(9, 3, HERE
<< "Directory path did not end in /");
1563 ftpState
->title_url
.append("/");
1564 ftpState
->flags
.isdir
= 1;
1567 ftpSendPassive(ftpState
);
1571 ftpSendMdtm(Ftp::Gateway
* ftpState
)
1573 /* check the server control channel is still available */
1574 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
1577 assert(*ftpState
->filepath
!= '\0');
1578 snprintf(cbuf
, CTRL_BUFLEN
, "MDTM %s\r\n", ftpState
->filepath
);
1579 ftpState
->writeCommand(cbuf
);
1580 ftpState
->state
= Ftp::Client::SENT_MDTM
;
1584 ftpReadMdtm(Ftp::Gateway
* ftpState
)
1586 int code
= ftpState
->ctrl
.replycode
;
1590 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
1592 } else if (code
< 0) {
1597 ftpSendSize(ftpState
);
1601 ftpSendSize(Ftp::Gateway
* ftpState
)
1603 /* check the server control channel is still available */
1604 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
1607 /* Only send SIZE for binary transfers. The returned size
1608 * is useless on ASCII transfers */
1610 if (ftpState
->flags
.binary
) {
1611 assert(ftpState
->filepath
!= NULL
);
1612 assert(*ftpState
->filepath
!= '\0');
1613 snprintf(cbuf
, CTRL_BUFLEN
, "SIZE %s\r\n", ftpState
->filepath
);
1614 ftpState
->writeCommand(cbuf
);
1615 ftpState
->state
= Ftp::Client::SENT_SIZE
;
1617 /* Skip to next state no non-binary transfers */
1618 ftpSendPassive(ftpState
);
1622 ftpReadSize(Ftp::Gateway
* ftpState
)
1624 int code
= ftpState
->ctrl
.replycode
;
1629 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
1631 if (ftpState
->theSize
== 0) {
1632 debugs(9, 2, "SIZE reported " <<
1633 ftpState
->ctrl
.last_reply
<< " on " <<
1634 ftpState
->title_url
);
1635 ftpState
->theSize
= -1;
1637 } else if (code
< 0) {
1642 ftpSendPassive(ftpState
);
1646 ftpReadEPSV(Ftp::Gateway
* ftpState
)
1648 Ip::Address srvAddr
; // unused
1649 if (ftpState
->handleEpsvReply(srvAddr
)) {
1650 if (ftpState
->ctrl
.message
== NULL
)
1651 return; // didn't get complete reply yet
1653 ftpState
->connectDataChannel();
1657 /** Send Passive connection request.
1658 * Default method is to use modern EPSV request.
1659 * The failover mechanism should check for previous state and re-call with alternates on failure.
1662 ftpSendPassive(Ftp::Gateway
* ftpState
)
1664 /** Checks the server control channel is still available before running. */
1665 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
1671 * Checks for 'HEAD' method request and passes off for special handling by Ftp::Gateway::processHeadResponse(). */
1672 if (ftpState
->request
->method
== Http::METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
1673 ftpState
->processHeadResponse(); // may call serverComplete
1677 if (ftpState
->sendPassive()) {
1678 // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
1679 if (ftpState
->state
== Ftp::Client::SENT_EPSV_ALL
)
1680 ftpState
->flags
.epsv_all_sent
= true;
1685 Ftp::Gateway::processHeadResponse()
1687 debugs(9, 5, HERE
<< "handling HEAD response");
1689 appendSuccessHeader();
1692 * On rare occasions I'm seeing the entry get aborted after
1693 * readControlReply() and before here, probably when
1694 * trying to write to the client.
1696 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1697 abortAll("entry aborted while processing HEAD");
1702 if (adaptationAccessCheckPending
) {
1703 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
1708 // processReplyBody calls serverComplete() since there is no body
1713 ftpReadPasv(Ftp::Gateway
* ftpState
)
1715 Ip::Address srvAddr
; // unused
1716 if (ftpState
->handlePasvReply(srvAddr
))
1717 ftpState
->connectDataChannel();
1720 // Currently disabled, does not work correctly:
1721 // ftpSendEPRT(ftpState);
1727 Ftp::Gateway::dataChannelConnected(const CommConnectCbParams
&io
)
1732 if (io
.flag
!= Comm::OK
) {
1733 debugs(9, 2, HERE
<< "Failed to connect. Retrying via another method.");
1735 // ABORT on timeouts. server may be waiting on a broken TCP link.
1736 if (io
.xerrno
== Comm::TIMEOUT
)
1737 writeCommand("ABOR\r\n");
1739 // try another connection attempt with some other method
1740 ftpSendPassive(this);
1744 data
.opened(io
.conn
, dataCloser());
1745 ftpRestOrList(this);
1749 ftpOpenListenSocket(Ftp::Gateway
* ftpState
, int fallback
)
1751 /// Close old data channels, if any. We may open a new one below.
1752 if (ftpState
->data
.conn
!= NULL
) {
1753 if ((ftpState
->data
.conn
->flags
& COMM_REUSEADDR
))
1754 // NP: in fact it points to the control channel. just clear it.
1755 ftpState
->data
.clear();
1757 ftpState
->data
.close();
1759 safe_free(ftpState
->data
.host
);
1761 if (!Comm::IsConnOpen(ftpState
->ctrl
.conn
)) {
1762 debugs(9, 5, "The control connection to the remote end is closed");
1767 * Set up a listen socket on the same local address as the
1768 * control connection.
1770 Comm::ConnectionPointer temp
= new Comm::Connection
;
1771 temp
->local
= ftpState
->ctrl
.conn
->local
;
1774 * REUSEADDR is needed in fallback mode, since the same port is
1775 * used for both control and data.
1780 if (setsockopt(ftpState
->ctrl
.conn
->fd
, SOL_SOCKET
, SO_REUSEADDR
,
1781 (char *) &on
, sizeof(on
)) == -1) {
1783 // SO_REUSEADDR is only an optimization, no need to be verbose about error
1784 debugs(9, 4, "setsockopt failed: " << xstrerr(xerrno
));
1786 ftpState
->ctrl
.conn
->flags
|= COMM_REUSEADDR
;
1787 temp
->flags
|= COMM_REUSEADDR
;
1789 /* if not running in fallback mode a new port needs to be retrieved */
1790 temp
->local
.port(0);
1793 ftpState
->listenForDataChannel(temp
);
1797 ftpSendPORT(Ftp::Gateway
* ftpState
)
1799 /* check the server control channel is still available */
1800 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
1803 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1804 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1809 ftpState
->flags
.pasv_supported
= 0;
1810 ftpOpenListenSocket(ftpState
, 0);
1812 if (!Comm::IsConnOpen(ftpState
->data
.listenConn
)) {
1813 if ( ftpState
->data
.listenConn
!= NULL
&& !ftpState
->data
.listenConn
->local
.isIPv4() ) {
1814 /* non-IPv4 CANNOT send PORT command. */
1815 /* we got here by attempting and failing an EPRT */
1816 /* using the same reply code should simulate a PORT failure */
1817 ftpReadPORT(ftpState
);
1821 /* XXX Need to set error message */
1826 // pull out the internal IP address bytes to send in PORT command...
1827 // source them from the listen_conn->local
1829 struct addrinfo
*AI
= NULL
;
1830 ftpState
->data
.listenConn
->local
.getAddrInfo(AI
, AF_INET
);
1831 unsigned char *addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
1832 unsigned char *portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
1833 snprintf(cbuf
, CTRL_BUFLEN
, "PORT %d,%d,%d,%d,%d,%d\r\n",
1834 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
1835 portptr
[0], portptr
[1]);
1836 ftpState
->writeCommand(cbuf
);
1837 ftpState
->state
= Ftp::Client::SENT_PORT
;
1839 Ip::Address::FreeAddr(AI
);
1843 ftpReadPORT(Ftp::Gateway
* ftpState
)
1845 int code
= ftpState
->ctrl
.replycode
;
1849 /* Fall back on using the same port as the control connection */
1850 debugs(9, 3, "PORT not supported by remote end");
1851 ftpOpenListenSocket(ftpState
, 1);
1854 ftpRestOrList(ftpState
);
1859 ftpSendEPRT(Ftp::Gateway
* ftpState
)
1861 /* check the server control channel is still available */
1862 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendEPRT"))
1865 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1866 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
1870 if (!Config
.Ftp
.eprt
) {
1871 /* Disabled. Switch immediately to attempting old PORT command. */
1872 debugs(9, 3, "EPRT disabled by local administrator");
1873 ftpSendPORT(ftpState
);
1878 ftpState
->flags
.pasv_supported
= 0;
1880 ftpOpenListenSocket(ftpState
, 0);
1881 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState
->data
.conn
);
1882 if (!Comm::IsConnOpen(ftpState
->data
.conn
)) {
1883 /* XXX Need to set error message */
1888 char buf
[MAX_IPSTRLEN
];
1890 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
1891 /* Which can be used by EITHER protocol. */
1892 snprintf(cbuf
, CTRL_BUFLEN
, "EPRT |%d|%s|%d|\r\n",
1893 ( ftpState
->data
.listenConn
->local
.isIPv6() ? 2 : 1 ),
1894 ftpState
->data
.listenConn
->local
.toStr(buf
,MAX_IPSTRLEN
),
1895 ftpState
->data
.listenConn
->local
.port() );
1897 ftpState
->writeCommand(cbuf
);
1898 ftpState
->state
= Ftp::Client::SENT_EPRT
;
1903 ftpReadEPRT(Ftp::Gateway
* ftpState
)
1905 int code
= ftpState
->ctrl
.replycode
;
1909 /* Failover to attempting old PORT command. */
1910 debugs(9, 3, "EPRT not supported by remote end");
1911 ftpSendPORT(ftpState
);
1915 ftpRestOrList(ftpState
);
1918 /** "read" handler to accept FTP data connections.
1920 \param io comm accept(2) callback parameters
1923 Ftp::Gateway::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
1927 if (!Comm::IsConnOpen(ctrl
.conn
)) { /*Close handlers will cleanup*/
1928 debugs(9, 5, "The control connection to the remote end is closed");
1932 if (io
.flag
!= Comm::OK
) {
1933 data
.listenConn
->close();
1934 data
.listenConn
= NULL
;
1935 debugs(9, DBG_IMPORTANT
, "FTP AcceptDataConnection: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
1936 /** \todo Need to send error message on control channel*/
1941 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1942 abortAll("entry aborted when accepting data conn");
1943 data
.listenConn
->close();
1944 data
.listenConn
= NULL
;
1949 /* data listening conn is no longer even open. abort. */
1950 if (!Comm::IsConnOpen(data
.listenConn
)) {
1951 data
.listenConn
= NULL
; // ensure that it's cleared and not just closed.
1955 /* data listening conn is no longer even open. abort. */
1956 if (!Comm::IsConnOpen(data
.conn
)) {
1957 data
.clear(); // ensure that it's cleared and not just closed.
1962 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
1963 * made by the remote client which is connected to the FTP control socket.
1964 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
1965 * This prevents third-party hacks, but also third-party load balancing handshakes.
1967 if (Config
.Ftp
.sanitycheck
) {
1968 // accept if either our data or ctrl connection is talking to this remote peer.
1969 if (data
.conn
->remote
!= io
.conn
->remote
&& ctrl
.conn
->remote
!= io
.conn
->remote
) {
1970 debugs(9, DBG_IMPORTANT
,
1971 "FTP data connection from unexpected server (" <<
1972 io
.conn
->remote
<< "), expecting " <<
1973 data
.conn
->remote
<< " or " << ctrl
.conn
->remote
);
1975 /* close the bad sources connection down ASAP. */
1978 /* drop the bad connection (io) by ignoring the attempt. */
1983 /** On Comm::OK start using the accepted data socket and discard the temporary listen socket. */
1985 data
.opened(io
.conn
, dataCloser());
1986 data
.addr(io
.conn
->remote
);
1988 debugs(9, 3, HERE
<< "Connected data socket on " <<
1989 io
.conn
<< ". FD table says: " <<
1990 "ctrl-peer= " << fd_table
[ctrl
.conn
->fd
].ipaddr
<< ", " <<
1991 "data-peer= " << fd_table
[data
.conn
->fd
].ipaddr
);
1993 assert(haveControlChannel("ftpAcceptDataConnection"));
1994 assert(ctrl
.message
== NULL
);
1996 // Ctrl channel operations will determine what happens to this data connection
2000 ftpRestOrList(Ftp::Gateway
* ftpState
)
2004 if (ftpState
->typecode
== 'D') {
2005 ftpState
->flags
.isdir
= 1;
2007 if (ftpState
->flags
.put
) {
2008 ftpSendMkdir(ftpState
); /* PUT name;type=d */
2010 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
2012 } else if (ftpState
->flags
.put
) {
2013 ftpSendStor(ftpState
);
2014 } else if (ftpState
->flags
.isdir
)
2015 ftpSendList(ftpState
);
2016 else if (ftpState
->restartable())
2017 ftpSendRest(ftpState
);
2019 ftpSendRetr(ftpState
);
2023 ftpSendStor(Ftp::Gateway
* ftpState
)
2025 /* check the server control channel is still available */
2026 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
2031 if (ftpState
->filepath
!= NULL
) {
2032 /* Plain file upload */
2033 snprintf(cbuf
, CTRL_BUFLEN
, "STOR %s\r\n", ftpState
->filepath
);
2034 ftpState
->writeCommand(cbuf
);
2035 ftpState
->state
= Ftp::Client::SENT_STOR
;
2036 } else if (ftpState
->request
->header
.getInt64(Http::HdrType::CONTENT_LENGTH
) > 0) {
2037 /* File upload without a filename. use STOU to generate one */
2038 snprintf(cbuf
, CTRL_BUFLEN
, "STOU\r\n");
2039 ftpState
->writeCommand(cbuf
);
2040 ftpState
->state
= Ftp::Client::SENT_STOR
;
2042 /* No file to transfer. Only create directories if needed */
2043 ftpSendReply(ftpState
);
2047 /// \deprecated use ftpState->readStor() instead.
2049 ftpReadStor(Ftp::Gateway
* ftpState
)
2051 ftpState
->readStor();
2054 void Ftp::Gateway::readStor()
2056 int code
= ctrl
.replycode
;
2059 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(data
.conn
))) {
2060 if (!originalRequest()->body_pipe
) {
2061 debugs(9, 3, "zero-size STOR?");
2062 state
= WRITING_DATA
; // make ftpWriteTransferDone() responsible
2063 dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2067 if (!startRequestBodyFlow()) { // register to receive body data
2072 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
2073 debugs(9, 3, HERE
<< "starting data transfer");
2074 switchTimeoutToDataChannel();
2075 sendMoreRequestBody();
2076 fwd
->dontRetry(true); // do not permit re-trying if the body was sent.
2077 state
= WRITING_DATA
;
2078 debugs(9, 3, HERE
<< "writing data channel");
2079 } else if (code
== 150) {
2080 /* When client code is 150 with no data channel, Accept data channel. */
2081 debugs(9, 3, "ftpReadStor: accepting data channel");
2082 listenForDataChannel(data
.conn
);
2084 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
2090 ftpSendRest(Ftp::Gateway
* ftpState
)
2092 /* check the server control channel is still available */
2093 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
2098 snprintf(cbuf
, CTRL_BUFLEN
, "REST %" PRId64
"\r\n", ftpState
->restart_offset
);
2099 ftpState
->writeCommand(cbuf
);
2100 ftpState
->state
= Ftp::Client::SENT_REST
;
2104 Ftp::Gateway::restartable()
2106 if (restart_offset
> 0)
2109 if (!request
->range
)
2118 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
2120 if (desired_offset
<= 0)
2123 if (desired_offset
>= theSize
)
2126 restart_offset
= desired_offset
;
2131 ftpReadRest(Ftp::Gateway
* ftpState
)
2133 int code
= ftpState
->ctrl
.replycode
;
2135 assert(ftpState
->restart_offset
> 0);
2138 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
2139 ftpSendRetr(ftpState
);
2140 } else if (code
> 0) {
2141 debugs(9, 3, HERE
<< "REST not supported");
2142 ftpState
->flags
.rest_supported
= 0;
2143 ftpSendRetr(ftpState
);
2150 ftpSendList(Ftp::Gateway
* ftpState
)
2152 /* check the server control channel is still available */
2153 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
2158 if (ftpState
->filepath
) {
2159 snprintf(cbuf
, CTRL_BUFLEN
, "LIST %s\r\n", ftpState
->filepath
);
2161 snprintf(cbuf
, CTRL_BUFLEN
, "LIST\r\n");
2164 ftpState
->writeCommand(cbuf
);
2165 ftpState
->state
= Ftp::Client::SENT_LIST
;
2169 ftpSendNlst(Ftp::Gateway
* ftpState
)
2171 /* check the server control channel is still available */
2172 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
2177 ftpState
->flags
.tried_nlst
= 1;
2179 if (ftpState
->filepath
) {
2180 snprintf(cbuf
, CTRL_BUFLEN
, "NLST %s\r\n", ftpState
->filepath
);
2182 snprintf(cbuf
, CTRL_BUFLEN
, "NLST\r\n");
2185 ftpState
->writeCommand(cbuf
);
2186 ftpState
->state
= Ftp::Client::SENT_NLST
;
2190 ftpReadList(Ftp::Gateway
* ftpState
)
2192 int code
= ftpState
->ctrl
.replycode
;
2195 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2196 /* Begin data transfer */
2197 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2198 ftpState
->switchTimeoutToDataChannel();
2199 ftpState
->maybeReadVirginBody();
2200 ftpState
->state
= Ftp::Client::READING_DATA
;
2202 } else if (code
== 150) {
2203 /* Accept data channel */
2204 debugs(9, 3, HERE
<< "accept data channel from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2205 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2207 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
2208 ftpSendNlst(ftpState
);
2216 ftpSendRetr(Ftp::Gateway
* ftpState
)
2218 /* check the server control channel is still available */
2219 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
2224 assert(ftpState
->filepath
!= NULL
);
2225 snprintf(cbuf
, CTRL_BUFLEN
, "RETR %s\r\n", ftpState
->filepath
);
2226 ftpState
->writeCommand(cbuf
);
2227 ftpState
->state
= Ftp::Client::SENT_RETR
;
2231 ftpReadRetr(Ftp::Gateway
* ftpState
)
2233 int code
= ftpState
->ctrl
.replycode
;
2236 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2237 /* Begin data transfer */
2238 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2239 ftpState
->switchTimeoutToDataChannel();
2240 ftpState
->maybeReadVirginBody();
2241 ftpState
->state
= Ftp::Client::READING_DATA
;
2242 } else if (code
== 150) {
2243 /* Accept data channel */
2244 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2245 } else if (code
>= 300) {
2246 if (!ftpState
->flags
.try_slash_hack
) {
2247 /* Try this as a directory missing trailing slash... */
2248 ftpState
->hackShortcut(ftpSendCwd
);
2258 * Generate the HTTP headers and template fluff around an FTP
2259 * directory listing display.
2262 Ftp::Gateway::completedListing()
2265 entry
->lock("Ftp::Gateway");
2266 ErrorState
ferr(ERR_DIR_LISTING
, Http::scOkay
, request
.getRaw());
2267 ferr
.ftp
.listing
= &listing
;
2268 ferr
.ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
2269 ferr
.ftp
.server_msg
= ctrl
.message
;
2270 ctrl
.message
= NULL
;
2271 entry
->replaceHttpReply(ferr
.BuildHttpReply());
2273 entry
->unlock("Ftp::Gateway");
2277 ftpReadTransferDone(Ftp::Gateway
* ftpState
)
2279 int code
= ftpState
->ctrl
.replycode
;
2282 if (code
== 226 || code
== 250) {
2283 /* Connection closed; retrieval done. */
2284 if (ftpState
->flags
.listing
) {
2285 ftpState
->completedListing();
2286 /* QUIT operation handles sending the reply to client */
2288 ftpSendQuit(ftpState
);
2289 } else { /* != 226 */
2290 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
2291 ftpState
->failed(ERR_FTP_FAILURE
, 0);
2292 /* failed closes ctrl.conn and frees ftpState */
2297 // premature end of the request body
2299 Ftp::Gateway::handleRequestBodyProducerAborted()
2301 Client::handleRequestBodyProducerAborted();
2302 debugs(9, 3, HERE
<< "ftpState=" << this);
2303 failed(ERR_READ_ERROR
, 0);
2307 ftpWriteTransferDone(Ftp::Gateway
* ftpState
)
2309 int code
= ftpState
->ctrl
.replycode
;
2312 if (!(code
== 226 || code
== 250)) {
2313 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
2314 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
2318 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
2319 ftpSendReply(ftpState
);
2323 ftpSendQuit(Ftp::Gateway
* ftpState
)
2325 /* check the server control channel is still available */
2326 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
2329 snprintf(cbuf
, CTRL_BUFLEN
, "QUIT\r\n");
2330 ftpState
->writeCommand(cbuf
);
2331 ftpState
->state
= Ftp::Client::SENT_QUIT
;
2334 /** Completes a client FTP operation with success or other page
2335 * generated and stored in the entry field by the code issuing QUIT.
2338 ftpReadQuit(Ftp::Gateway
* ftpState
)
2340 ftpState
->serverComplete();
2344 ftpTrySlashHack(Ftp::Gateway
* ftpState
)
2347 ftpState
->flags
.try_slash_hack
= 1;
2348 /* Free old paths */
2352 if (ftpState
->pathcomps
)
2353 wordlistDestroy(&ftpState
->pathcomps
);
2355 safe_free(ftpState
->filepath
);
2357 /* Build the new path (urlpath begins with /) */
2358 path
= SBufToCstring(ftpState
->request
->url
.path());
2360 rfc1738_unescape(path
);
2362 ftpState
->filepath
= path
;
2365 ftpGetFile(ftpState
);
2369 * Forget hack status. Next error is shown to the user
2372 Ftp::Gateway::unhack()
2376 if (old_request
!= NULL
) {
2377 safe_free(old_request
);
2378 safe_free(old_reply
);
2383 Ftp::Gateway::hackShortcut(FTPSM
* nextState
)
2385 /* Clear some unwanted state */
2386 setCurrentOffset(0);
2388 /* Save old error message & some state info */
2392 if (old_request
== NULL
) {
2393 old_request
= ctrl
.last_command
;
2394 ctrl
.last_command
= NULL
;
2395 old_reply
= ctrl
.last_reply
;
2396 ctrl
.last_reply
= NULL
;
2398 if (pathcomps
== NULL
&& filepath
!= NULL
)
2399 old_filepath
= xstrdup(filepath
);
2402 /* Jump to the "hack" state */
2407 ftpFail(Ftp::Gateway
*ftpState
)
2409 const bool slashHack
= ftpState
->request
->url
.path().caseCmp("/%2f", 4)==0;
2410 int code
= ftpState
->ctrl
.replycode
;
2411 err_type error_code
= ERR_NONE
;
2413 debugs(9, 6, "state " << ftpState
->state
<<
2414 " reply code " << code
<< "flags(" <<
2415 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
2416 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
2417 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
2418 "slashhack=" << (slashHack
? "T":"F"));
2420 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2421 if (!ftpState
->flags
.isdir
&& /* Not a directory */
2422 !ftpState
->flags
.try_slash_hack
&& !slashHack
&& /* Not doing slash hack */
2423 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0) { /* Not known as a file */
2425 switch (ftpState
->state
) {
2427 case Ftp::Client::SENT_CWD
:
2429 case Ftp::Client::SENT_RETR
:
2430 /* Try the / hack */
2431 ftpState
->hackShortcut(ftpTrySlashHack
);
2439 Http::StatusCode sc
= ftpState
->failedHttpStatus(error_code
);
2440 ErrorState
*ftperr
= new ErrorState(error_code
, sc
, ftpState
->fwd
->request
);
2441 ftpState
->failed(error_code
, code
, ftperr
);
2442 ftperr
->detailError(code
);
2443 HttpReply
*newrep
= ftperr
->BuildHttpReply();
2446 ftpState
->entry
->replaceHttpReply(newrep
);
2447 ftpSendQuit(ftpState
);
2451 Ftp::Gateway::failedHttpStatus(err_type
&error
)
2453 if (error
== ERR_NONE
) {
2460 if (ctrl
.replycode
> 500) {
2461 error
= ERR_FTP_FORBIDDEN
;
2462 return password_url
? Http::scForbidden
: Http::scUnauthorized
;
2463 } else if (ctrl
.replycode
== 421) {
2464 error
= ERR_FTP_UNAVAILABLE
;
2465 return Http::scServiceUnavailable
;
2472 if (ctrl
.replycode
== 550) {
2473 error
= ERR_FTP_NOT_FOUND
;
2474 return Http::scNotFound
;
2482 return Ftp::Client::failedHttpStatus(error
);
2486 ftpSendReply(Ftp::Gateway
* ftpState
)
2488 int code
= ftpState
->ctrl
.replycode
;
2489 Http::StatusCode http_code
;
2490 err_type err_code
= ERR_NONE
;
2492 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
2494 if (cbdataReferenceValid(ftpState
))
2495 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
2497 if (code
== 226 || code
== 250) {
2498 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
2499 http_code
= (ftpState
->mdtm
> 0) ? Http::scAccepted
: Http::scCreated
;
2500 } else if (code
== 227) {
2501 err_code
= ERR_FTP_PUT_CREATED
;
2502 http_code
= Http::scCreated
;
2504 err_code
= ERR_FTP_PUT_ERROR
;
2505 http_code
= Http::scInternalServerError
;
2508 ErrorState
err(err_code
, http_code
, ftpState
->request
.getRaw());
2510 if (ftpState
->old_request
)
2511 err
.ftp
.request
= xstrdup(ftpState
->old_request
);
2513 err
.ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
2515 if (ftpState
->old_reply
)
2516 err
.ftp
.reply
= xstrdup(ftpState
->old_reply
);
2517 else if (ftpState
->ctrl
.last_reply
)
2518 err
.ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
2520 err
.ftp
.reply
= xstrdup("");
2522 // TODO: interpret as FTP-specific error code
2523 err
.detailError(code
);
2525 ftpState
->entry
->replaceHttpReply(err
.BuildHttpReply());
2527 ftpSendQuit(ftpState
);
2531 Ftp::Gateway::appendSuccessHeader()
2535 if (flags
.http_header_sent
)
2538 HttpReply
*reply
= new HttpReply
;
2540 flags
.http_header_sent
= 1;
2542 assert(entry
->isEmpty());
2544 entry
->buffer(); /* released when done processing current data payload */
2546 SBuf urlPath
= request
->url
.path();
2547 auto t
= urlPath
.rfind('/');
2548 SBuf filename
= urlPath
.substr(t
!= SBuf::npos
? t
: 0);
2550 const char *mime_type
= NULL
;
2551 const char *mime_enc
= NULL
;
2554 mime_type
= "text/html";
2559 mime_type
= "application/octet-stream";
2560 // XXX: performance regression, c_str() may reallocate
2561 mime_enc
= mimeGetContentEncoding(filename
.c_str());
2565 mime_type
= "text/plain";
2569 // XXX: performance regression, c_str() may reallocate
2570 mime_type
= mimeGetContentType(filename
.c_str());
2571 mime_enc
= mimeGetContentEncoding(filename
.c_str());
2576 /* set standard stuff */
2578 if (0 == getCurrentOffset()) {
2580 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2581 } else if (theSize
< getCurrentOffset()) {
2584 * offset should not be larger than theSize. We should
2585 * not be seeing this condition any more because we'll only
2586 * send REST if we know the theSize and if it is less than theSize.
2588 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
2589 " current offset=" << getCurrentOffset() <<
2590 ", but theSize=" << theSize
<<
2591 ". assuming full content response");
2592 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2595 HttpHdrRangeSpec range_spec
;
2596 range_spec
.offset
= getCurrentOffset();
2597 range_spec
.length
= theSize
- getCurrentOffset();
2598 reply
->setHeaders(Http::scPartialContent
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
2599 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
2602 /* additional info */
2604 reply
->header
.putStr(Http::HdrType::CONTENT_ENCODING
, mime_enc
);
2606 reply
->sources
|= Http::Message::srcFtp
;
2607 setVirginReply(reply
);
2608 adaptOrFinalizeReply();
2612 Ftp::Gateway::haveParsedReplyHeaders()
2614 Client::haveParsedReplyHeaders();
2616 StoreEntry
*e
= entry
;
2620 // makePublic() if allowed/possible or release() otherwise
2621 if (flags
.authenticated
|| // authenticated requests can't be cached
2622 getCurrentOffset() ||
2629 Ftp::Gateway::ftpAuthRequired(HttpRequest
* request
, SBuf
&realm
)
2631 ErrorState
err(ERR_CACHE_ACCESS_DENIED
, Http::scUnauthorized
, request
);
2632 HttpReply
*newrep
= err
.BuildHttpReply();
2633 #if HAVE_AUTH_MODULE_BASIC
2634 /* add Authenticate header */
2635 // XXX: performance regression. c_str() may reallocate
2636 newrep
->header
.putAuth("Basic", realm
.c_str());
2642 Ftp::UrlWith2f(HttpRequest
* request
)
2646 if (request
->url
.getScheme() != AnyP::PROTO_FTP
) {
2647 static const SBuf nil
;
2651 if (request
->url
.path()[0] == '/') {
2652 newbuf
.append(request
->url
.path());
2653 request
->url
.path(newbuf
);
2654 } else if (!request
->url
.path().startsWith(newbuf
)) {
2655 newbuf
.append(request
->url
.path().substr(1));
2656 request
->url
.path(newbuf
);
2659 return request
->effectiveRequestUri();
2663 Ftp::Gateway::printfReplyBody(const char *fmt
, ...)
2666 va_start (args
, fmt
);
2667 static char buf
[4096];
2669 vsnprintf(buf
, 4096, fmt
, args
);
2670 writeReplyBody(buf
, strlen(buf
));
2675 * Call this when there is data from the origin server
2676 * which should be sent to either StoreEntry, or to ICAP...
2679 Ftp::Gateway::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
2681 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
2682 addVirginReplyBody(dataToWrite
, dataLength
);
2686 * A hack to ensure we do not double-complete on the forward entry.
2688 \todo Ftp::Gateway logic should probably be rewritten to avoid
2689 * double-completion or FwdState should be rewritten to allow it.
2692 Ftp::Gateway::completeForwarding()
2694 if (fwd
== NULL
|| flags
.completed_forwarding
) {
2695 debugs(9, 3, "avoid double-complete on FD " <<
2696 (ctrl
.conn
? ctrl
.conn
->fd
: -1) << ", Data FD " << data
.conn
->fd
<<
2697 ", this " << this << ", fwd " << fwd
);
2701 flags
.completed_forwarding
= true;
2702 Client::completeForwarding();
2706 * Have we lost the FTP server control channel?
2708 \retval true The server control channel is available.
2709 \retval false The server control channel is not available.
2712 Ftp::Gateway::haveControlChannel(const char *caller_name
) const
2714 if (doneWithServer())
2717 /* doneWithServer() only checks BOTH channels are closed. */
2718 if (!Comm::IsConnOpen(ctrl
.conn
)) {
2719 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
2720 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
2728 Ftp::Gateway::mayReadVirginReplyBody() const
2730 // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2731 return !doneWithServer();
2735 Ftp::StartGateway(FwdState
*const fwdState
)
2737 return AsyncJob::Start(new Ftp::Gateway(fwdState
));