2 * Copyright (C) 1996-2014 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 "clients/forward.h"
14 #include "clients/FtpClient.h"
16 #include "comm/ConnOpener.h"
17 #include "comm/Read.h"
18 #include "comm/TcpAcceptor.h"
19 #include "CommCalls.h"
20 #include "compat/strtoll.h"
21 #include "errorpage.h"
25 #include "html_quote.h"
26 #include "HttpHdrContRange.h"
27 #include "HttpHeader.h"
28 #include "HttpHeaderRange.h"
29 #include "HttpReply.h"
30 #include "HttpRequest.h"
36 #include "SquidConfig.h"
37 #include "SquidString.h"
38 #include "SquidTime.h"
39 #include "StatCounters.h"
46 #include "DelayPools.h"
47 #include "MemObject.h"
58 bool pasv_supported
; ///< PASV command is allowed
59 bool epsv_all_sent
; ///< EPSV ALL has been used. Must abort on failures.
61 bool pasv_failed
; // was FwdState::flags.ftp_pasv_failed
64 bool authenticated
; ///< authentication success
65 bool tried_auth_anonymous
; ///< auth has tried to use anonymous credentials already.
66 bool tried_auth_nopass
; ///< auth tried username with no password already.
72 bool http_header_sent
;
82 bool listformat_unknown
;
84 bool completed_forwarding
;
88 typedef void (StateMethod
)(Ftp::Gateway
*);
90 /// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI,
91 /// converts it into one or more FTP commands, and then
92 /// converts one or more FTP responses into the final HTTP response.
93 class Gateway
: public Ftp::Client
99 char password
[MAX_URL
];
113 int64_t restart_offset
;
119 MemBuf listing
; ///< FTP directory listing in HTML format.
124 // these should all be private
125 virtual void start();
126 virtual Http::StatusCode
failedHttpStatus(err_type
&error
);
127 void loginParser(const char *, int escaped
);
129 void appendSuccessHeader();
130 void hackShortcut(StateMethod
*nextState
);
134 MemBuf
*htmlifyListEntry(const char *line
);
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
, const char *realm
);
157 const char *ftpRealm(void);
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 CBDATA_CLASS2(Gateway
);
178 typedef Ftp::StateMethod FTPSM
; // to avoid lots of non-changes
180 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Gateway
);
191 #define FTP_LOGIN_ESCAPED 1
193 #define FTP_LOGIN_NOT_ESCAPED 0
195 #define CTRL_BUFLEN 1024
196 static char cbuf
[CTRL_BUFLEN
];
199 * State machine functions
200 * send == state transition
201 * read == wait for response, and select next state transition
202 * other == Transition logic
204 static FTPSM ftpReadWelcome
;
205 static FTPSM ftpSendUser
;
206 static FTPSM ftpReadUser
;
207 static FTPSM ftpSendPass
;
208 static FTPSM ftpReadPass
;
209 static FTPSM ftpSendType
;
210 static FTPSM ftpReadType
;
211 static FTPSM ftpSendMdtm
;
212 static FTPSM ftpReadMdtm
;
213 static FTPSM ftpSendSize
;
214 static FTPSM ftpReadSize
;
215 static FTPSM ftpSendEPRT
;
216 static FTPSM ftpReadEPRT
;
217 static FTPSM ftpSendPORT
;
218 static FTPSM ftpReadPORT
;
219 static FTPSM ftpSendPassive
;
220 static FTPSM ftpReadEPSV
;
221 static FTPSM ftpReadPasv
;
222 static FTPSM ftpTraverseDirectory
;
223 static FTPSM ftpListDir
;
224 static FTPSM ftpGetFile
;
225 static FTPSM ftpSendCwd
;
226 static FTPSM ftpReadCwd
;
227 static FTPSM ftpRestOrList
;
228 static FTPSM ftpSendList
;
229 static FTPSM ftpSendNlst
;
230 static FTPSM ftpReadList
;
231 static FTPSM ftpSendRest
;
232 static FTPSM ftpReadRest
;
233 static FTPSM ftpSendRetr
;
234 static FTPSM ftpReadRetr
;
235 static FTPSM ftpReadTransferDone
;
236 static FTPSM ftpSendStor
;
237 static FTPSM ftpReadStor
;
238 static FTPSM ftpWriteTransferDone
;
239 static FTPSM ftpSendReply
;
240 static FTPSM ftpSendMkdir
;
241 static FTPSM ftpReadMkdir
;
242 static FTPSM ftpFail
;
243 static FTPSM ftpSendQuit
;
244 static FTPSM ftpReadQuit
;
246 /************************************************
247 ** Debugs Levels used here **
248 *************************************************
251 Protocol and Transmission failures.
252 2 FTP Protocol Chatter
257 ************************************************/
259 /************************************************
260 ** State Machine Description (excluding hacks) **
261 *************************************************
263 ---------------------------------------
267 Type TraverseDirectory / GetFile
268 TraverseDirectory Cwd / GetFile / ListDir
269 Cwd TraverseDirectory / Mkdir
275 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
277 Retr / Nlst / List DataRead* (on datachannel)
278 DataRead* ReadTransferDone
279 ReadTransferDone DataTransferDone
280 Stor DataWrite* (on datachannel)
281 DataWrite* RequestPutBody** (from client)
282 RequestPutBody** DataWrite* / WriteTransferDone
283 WriteTransferDone DataTransferDone
284 DataTransferDone Quit
286 ************************************************/
288 FTPSM
*FTP_SM_FUNCS
[] = {
289 ftpReadWelcome
, /* BEGIN */
290 ftpReadUser
, /* SENT_USER */
291 ftpReadPass
, /* SENT_PASS */
292 ftpReadType
, /* SENT_TYPE */
293 ftpReadMdtm
, /* SENT_MDTM */
294 ftpReadSize
, /* SENT_SIZE */
295 ftpReadEPRT
, /* SENT_EPRT */
296 ftpReadPORT
, /* SENT_PORT */
297 ftpReadEPSV
, /* SENT_EPSV_ALL */
298 ftpReadEPSV
, /* SENT_EPSV_1 */
299 ftpReadEPSV
, /* SENT_EPSV_2 */
300 ftpReadPasv
, /* SENT_PASV */
301 ftpReadCwd
, /* SENT_CWD */
302 ftpReadList
, /* SENT_LIST */
303 ftpReadList
, /* SENT_NLST */
304 ftpReadRest
, /* SENT_REST */
305 ftpReadRetr
, /* SENT_RETR */
306 ftpReadStor
, /* SENT_STOR */
307 ftpReadQuit
, /* SENT_QUIT */
308 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
309 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
310 ftpReadMkdir
, /* SENT_MKDIR */
311 NULL
, /* SENT_FEAT */
314 NULL
, /* SENT_DATA_REQUEST */
315 NULL
/* SENT_COMMAND */
318 /// handler called by Comm when FTP data channel is closed unexpectedly
320 Ftp::Gateway::dataClosed(const CommCloseCbParams
&io
)
322 Ftp::Client::dataClosed(io
);
323 failed(ERR_FTP_FAILURE
, 0);
324 /* failed closes ctrl.conn and frees ftpState */
326 /* NP: failure recovery may be possible when its only a data.conn failure.
327 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
328 * Just need to watch out for wider Squid states like shutting down or reconfigure.
332 Ftp::Gateway::Gateway(FwdState
*fwdState
):
333 AsyncJob("FtpStateData"),
334 Ftp::Client(fwdState
)
336 const char *url
= entry
->url();
337 debugs(9, 3, HERE
<< "'" << url
<< "'" );
341 if (Config
.Ftp
.passive
&& !flags
.pasv_failed
)
342 flags
.pasv_supported
= 1;
344 flags
.rest_supported
= 1;
346 if (request
->method
== Http::METHOD_PUT
)
352 Ftp::Gateway::~Gateway()
354 debugs(9, 3, HERE
<< entry
->url() );
356 if (Comm::IsConnOpen(ctrl
.conn
)) {
357 debugs(9, DBG_IMPORTANT
, "Internal bug: FTP Gateway left open " <<
358 "control channel " << ctrl
.conn
);
362 memFree(reply_hdr
, MEM_8K_BUF
);
367 wordlistDestroy(&pathcomps
);
371 safe_free(old_filepath
);
383 * Parse a possible login username:password pair.
384 * Produces filled member variables user, password, password_url if anything found.
387 Ftp::Gateway::loginParser(const char *login
, int escaped
)
389 const char *u
= NULL
; // end of the username sub-string
390 int len
; // length of the current sub-string to handle.
392 int total_len
= strlen(login
);
394 debugs(9, 4, HERE
<< ": login='" << login
<< "', escaped=" << escaped
);
395 debugs(9, 9, HERE
<< ": IN : login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
397 if ((u
= strchr(login
, ':'))) {
399 /* if there was a username part */
402 ++u
; // jump off the delimiter.
405 xstrncpy(user
, login
, len
+1);
406 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<"), escaped=" << escaped
);
408 rfc1738_unescape(user
);
409 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << len
<<") unescaped.");
412 /* if there was a password part */
413 len
= login
+ total_len
- u
;
417 xstrncpy(password
, u
, len
+1);
418 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<"), escaped=" << escaped
);
420 rfc1738_unescape(password
);
423 debugs(9, 9, HERE
<< ": found password='" << password
<< "'(" << len
<<") unescaped.");
425 } else if (login
[0]) {
426 /* no password, just username */
427 if (total_len
> MAX_URL
)
428 total_len
= MAX_URL
-1;
429 xstrncpy(user
, login
, total_len
+1);
430 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<"), escaped=" << escaped
);
432 rfc1738_unescape(user
);
433 debugs(9, 9, HERE
<< ": found user='" << user
<< "'(" << total_len
<<") unescaped.");
436 debugs(9, 9, HERE
<< ": OUT: login='" << login
<< "', escaped=" << escaped
<< ", user=" << user
<< ", password=" << password
);
440 Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer
&conn
)
442 assert(!Comm::IsConnOpen(data
.conn
));
444 typedef CommCbMemFunT
<Gateway
, CommAcceptCbParams
> AcceptDialer
;
445 typedef AsyncCallT
<AcceptDialer
> AcceptCall
;
446 RefCount
<AcceptCall
> call
= static_cast<AcceptCall
*>(JobCallback(11, 5, AcceptDialer
, this, Ftp::Gateway::ftpAcceptDataConnection
));
447 Subscription::Pointer sub
= new CallSubscription
<AcceptCall
>(call
);
448 const char *note
= entry
->url();
450 /* open the conn if its not already open */
451 if (!Comm::IsConnOpen(conn
)) {
452 conn
->fd
= comm_open_listener(SOCK_STREAM
, IPPROTO_TCP
, conn
->local
, conn
->flags
, note
);
453 if (!Comm::IsConnOpen(conn
)) {
454 debugs(5, DBG_CRITICAL
, HERE
<< "comm_open_listener failed:" << conn
->local
<< " error: " << errno
);
457 debugs(9, 3, HERE
<< "Unconnected data socket created on " << conn
);
460 conn
->tos
= ctrl
.conn
->tos
;
461 conn
->nfmark
= ctrl
.conn
->nfmark
;
463 assert(Comm::IsConnOpen(conn
));
464 AsyncJob::Start(new Comm::TcpAcceptor(conn
, note
, sub
));
466 // Ensure we have a copy of the FD opened for listening and a close handler on it.
467 data
.opened(conn
, dataCloser());
468 switchTimeoutToDataChannel();
472 Ftp::Gateway::timeout(const CommTimeoutCbParams
&io
)
474 if (SENT_PASV
== state
) {
475 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
476 flags
.pasv_supported
= false;
477 debugs(9, DBG_IMPORTANT
, "FTP Gateway timeout in SENT_PASV state");
479 // cancel the data connection setup.
480 if (data
.opener
!= NULL
) {
481 data
.opener
->cancel("timeout");
487 Ftp::Client::timeout(io
);
490 static const char *Month
[] = {
491 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
492 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
496 is_month(const char *buf
)
500 for (i
= 0; i
< 12; ++i
)
501 if (!strcasecmp(buf
, Month
[i
]))
508 ftpListPartsFree(ftpListParts
** parts
)
510 safe_free((*parts
)->date
);
511 safe_free((*parts
)->name
);
512 safe_free((*parts
)->showname
);
513 safe_free((*parts
)->link
);
517 #define MAX_TOKENS 64
519 static ftpListParts
*
520 ftpListParseParts(const char *buf
, struct Ftp::GatewayFlags flags
)
522 ftpListParts
*p
= NULL
;
524 const char *ct
= NULL
;
525 char *tokens
[MAX_TOKENS
];
528 static char tbuf
[128];
530 static int scan_ftp_initialized
= 0;
531 static regex_t scan_ftp_integer
;
532 static regex_t scan_ftp_time
;
533 static regex_t scan_ftp_dostime
;
534 static regex_t scan_ftp_dosdate
;
536 if (!scan_ftp_initialized
) {
537 scan_ftp_initialized
= 1;
538 regcomp(&scan_ftp_integer
, "^[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
539 regcomp(&scan_ftp_time
, "^[0123456789:]+$", REG_EXTENDED
| REG_NOSUB
);
540 regcomp(&scan_ftp_dosdate
, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED
| REG_NOSUB
);
541 regcomp(&scan_ftp_dostime
, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED
| REG_NOSUB
| REG_ICASE
);
550 p
= (ftpListParts
*)xcalloc(1, sizeof(ftpListParts
));
554 memset(tokens
, 0, sizeof(tokens
));
558 if (flags
.tried_nlst
) {
559 /* Machine readable format, one name per line */
565 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
)) {
566 tokens
[n_tokens
] = xstrdup(t
);
572 /* locate the Month field */
573 for (i
= 3; i
< n_tokens
- 2; ++i
) {
574 char *size
= tokens
[i
- 1];
575 char *month
= tokens
[i
];
576 char *day
= tokens
[i
+ 1];
577 char *year
= tokens
[i
+ 2];
579 if (!is_month(month
))
582 if (regexec(&scan_ftp_integer
, size
, 0, NULL
, 0) != 0)
585 if (regexec(&scan_ftp_integer
, day
, 0, NULL
, 0) != 0)
588 if (regexec(&scan_ftp_time
, year
, 0, NULL
, 0) != 0) /* Yr | hh:mm */
591 snprintf(tbuf
, 128, "%s %2s %5s",
594 if (!strstr(buf
, tbuf
))
595 snprintf(tbuf
, 128, "%s %2s %-5s",
598 char const *copyFrom
= NULL
;
600 if ((copyFrom
= strstr(buf
, tbuf
))) {
601 p
->type
= *tokens
[0];
602 p
->size
= strtoll(size
, NULL
, 10);
603 p
->date
= xstrdup(tbuf
);
605 if (flags
.skip_whitespace
) {
606 copyFrom
+= strlen(tbuf
);
608 while (strchr(w_space
, *copyFrom
))
611 /* XXX assumes a single space between date and filename
612 * suggested by: Nathan.Bailey@cc.monash.edu.au and
613 * Mike Battersby <mike@starbug.bofh.asn.au> */
614 copyFrom
+= strlen(tbuf
) + 1;
617 p
->name
= xstrdup(copyFrom
);
619 if (p
->type
== 'l' && (t
= strstr(p
->name
, " -> "))) {
621 p
->link
= xstrdup(t
+ 4);
630 /* try it as a DOS listing, 04-05-70 09:33PM ... */
632 regexec(&scan_ftp_dosdate
, tokens
[0], 0, NULL
, 0) == 0 &&
633 regexec(&scan_ftp_dostime
, tokens
[1], 0, NULL
, 0) == 0) {
634 if (!strcasecmp(tokens
[2], "<dir>")) {
638 p
->size
= strtoll(tokens
[2], NULL
, 10);
641 snprintf(tbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
642 p
->date
= xstrdup(tbuf
);
644 if (p
->type
== 'd') {
645 /* Directory.. name begins with first printable after <dir> */
646 ct
= strstr(buf
, tokens
[2]);
647 ct
+= strlen(tokens
[2]);
649 while (xisspace(*ct
))
655 /* A file. Name begins after size, with a space in between */
656 snprintf(tbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
657 ct
= strstr(buf
, tbuf
);
660 ct
+= strlen(tokens
[2]) + 2;
664 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
668 /* Try EPLF format; carson@lehman.com */
675 int l
= strcspn(ct
, ",");
684 p
->name
= xstrndup(ct
+ 1, l
+ 1);
688 p
->size
= atoi(ct
+ 1);
692 tm
= (time_t) strtol(ct
+ 1, &tmp
, 0);
695 break; /* not a valid integer */
697 p
->date
= xstrdup(ctime(&tm
));
699 *(strstr(p
->date
, "\n")) = '\0';
721 ct
= strstr(ct
, ",");
740 for (i
= 0; i
< n_tokens
; ++i
)
744 ftpListPartsFree(&p
); /* cleanup */
750 Ftp::Gateway::htmlifyListEntry(const char *line
)
753 char href
[2048 + 40];
756 char chdir
[ 2048 + 40];
757 char view
[ 2048 + 40];
758 char download
[ 2048 + 40];
759 char link
[ 2048 + 40];
763 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= '\0';
765 debugs(9, 7, HERE
<< " line ={" << line
<< "}");
767 if (strlen(line
) > 1024) {
770 html
->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line
);
774 if (flags
.dir_slash
&& dirpath
&& typecode
!= 'D')
775 snprintf(prefix
, 2048, "%s/", rfc1738_escape_part(dirpath
));
779 if ((parts
= ftpListParseParts(line
, flags
)) == NULL
) {
784 html
->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line
);
786 for (p
= line
; *p
&& xisspace(*p
); ++p
);
787 if (*p
&& !xisspace(*p
))
788 flags
.listformat_unknown
= 1;
793 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
794 ftpListPartsFree(&parts
);
800 parts
->showname
= xstrdup(parts
->name
);
802 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
803 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
805 xstrncpy(text
, parts
->showname
, 2048);
807 switch (parts
->type
) {
810 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
811 mimeGetIconURL("internal-dir"),
813 strcat(href
, "/"); /* margin is allocated above */
817 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
818 mimeGetIconURL("internal-link"),
820 /* sometimes there is an 'l' flag, but no "->" link */
823 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
824 snprintf(link
, 2048, " -> <a href=\"%s%s\">%s</a>",
825 *link2
!= '/' ? prefix
: "", link2
,
826 html_quote(parts
->link
));
833 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
834 mimeGetIconURL(parts
->name
),
836 snprintf(chdir
, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
837 "alt=\"[DIR]\"></a>",
838 rfc1738_escape_part(parts
->name
),
839 mimeGetIconURL("internal-dir"));
845 snprintf(icon
, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
846 mimeGetIconURL(parts
->name
),
848 snprintf(size
, 2048, " %6" PRId64
"k", parts
->size
);
852 if (parts
->type
!= 'd') {
853 if (mimeGetViewOption(parts
->name
)) {
854 snprintf(view
, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
855 "alt=\"[VIEW]\"></a>",
856 prefix
, href
, mimeGetIconURL("internal-view"));
859 if (mimeGetDownloadOption(parts
->name
)) {
860 snprintf(download
, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
861 "alt=\"[DOWNLOAD]\"></a>",
862 prefix
, href
, mimeGetIconURL("internal-download"));
866 /* construct the table row from parts. */
869 html
->Printf("<tr class=\"entry\">"
870 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
871 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
872 "<td class=\"date\">%s</td>"
873 "<td class=\"size\">%s</td>"
874 "<td class=\"actions\">%s%s%s%s</td>"
877 prefix
, href
, html_quote(text
),
880 chdir
, view
, download
, link
);
882 ftpListPartsFree(&parts
);
887 Ftp::Gateway::parseListing()
889 char *buf
= data
.readBuf
->content();
890 char *sbuf
; /* NULL-terminated copy of termedBuf */
897 size_t len
= data
.readBuf
->contentSize();
900 debugs(9, 3, HERE
<< "no content to parse for " << entry
->url() );
905 * We need a NULL-terminated buffer for scanning, ick
907 sbuf
= (char *)xmalloc(len
+ 1);
908 xstrncpy(sbuf
, buf
, len
+ 1);
909 end
= sbuf
+ len
- 1;
911 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
916 debugs(9, 3, HERE
<< "usable = " << usable
<< " of " << len
<< " bytes.");
919 if (buf
[0] == '\0' && len
== 1) {
920 debugs(9, 3, HERE
<< "NIL ends data from " << entry
->url() << " transfer problem?");
921 data
.readBuf
->consume(len
);
923 debugs(9, 3, HERE
<< "didn't find end for " << entry
->url());
924 debugs(9, 3, HERE
<< "buffer remains (" << len
<< " bytes) '" << rfc1738_do_escape(buf
,0) << "'");
930 debugs(9, 3, HERE
<< (unsigned long int)len
<< " bytes to play with");
932 line
= (char *)memAllocate(MEM_4K_BUF
);
935 s
+= strspn(s
, crlf
);
937 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
938 debugs(9, 7, HERE
<< "s = {" << s
<< "}");
939 linelen
= strcspn(s
, crlf
) + 1;
947 xstrncpy(line
, s
, linelen
);
949 debugs(9, 7, HERE
<< "{" << line
<< "}");
951 if (!strncmp(line
, "total", 5))
954 t
= htmlifyListEntry(line
);
957 debugs(9, 7, HERE
<< "listing append: t = {" << t
->contentSize() << ", '" << t
->content() << "'}");
958 listing
.append(t
->content(), t
->contentSize());
963 debugs(9, 7, HERE
<< "Done.");
964 data
.readBuf
->consume(usable
);
965 memFree(line
, MEM_4K_BUF
);
970 Ftp::Gateway::processReplyBody()
972 debugs(9, 3, status());
974 if (request
->method
== Http::METHOD_HEAD
&& (flags
.isdir
|| theSize
!= -1)) {
979 /* Directory listings are special. They write ther own headers via the error objects */
980 if (!flags
.http_header_sent
&& data
.readBuf
->contentSize() >= 0 && !flags
.isdir
)
981 appendSuccessHeader();
983 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
985 * probably was aborted because content length exceeds one
986 * of the maximum size limits.
988 abortTransaction("entry aborted after calling appendSuccessHeader()");
994 if (adaptationAccessCheckPending
) {
995 debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
1002 if (!flags
.listing
) {
1007 maybeReadVirginBody();
1009 } else if (const int csize
= data
.readBuf
->contentSize()) {
1010 writeReplyBody(data
.readBuf
->content(), csize
);
1011 debugs(9, 5, HERE
<< "consuming " << csize
<< " bytes of readBuf");
1012 data
.readBuf
->consume(csize
);
1017 maybeReadVirginBody();
1021 * Locates the FTP user:password login.
1023 * Highest to lowest priority:
1024 * - Checks URL (ftp://user:pass@domain)
1025 * - Authorization: Basic header
1026 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1028 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1030 * TODO: we might be able to do something about locating username from other sources:
1031 * ie, external ACL user=* tag or ident lookup
1033 \retval 1 if we have everything needed to complete this request.
1034 \retval 0 if something is missing.
1037 Ftp::Gateway::checkAuth(const HttpHeader
* req_hdr
)
1039 /* default username */
1040 xstrncpy(user
, "anonymous", MAX_URL
);
1042 #if HAVE_AUTH_MODULE_BASIC
1043 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1045 if ( (auth
= req_hdr
->getAuth(HDR_AUTHORIZATION
, "Basic")) ) {
1046 flags
.authenticated
= 1;
1047 loginParser(auth
, FTP_LOGIN_NOT_ESCAPED
);
1049 /* we fail with authorization-required error later IFF the FTP server requests it */
1052 /* Test URL login syntax. Overrides any headers received. */
1053 loginParser(request
->login
, FTP_LOGIN_ESCAPED
);
1055 /* name is missing. thats fatal. */
1057 fatal("FTP login parsing destroyed username info");
1059 /* name + password == success */
1063 /* Setup default FTP password settings */
1064 /* this has to be done last so that we can have a no-password case above. */
1066 if (strcmp(user
, "anonymous") == 0 && !flags
.tried_auth_anonymous
) {
1067 xstrncpy(password
, Config
.Ftp
.anon_user
, MAX_URL
);
1068 flags
.tried_auth_anonymous
=1;
1070 } else if (!flags
.tried_auth_nopass
) {
1071 xstrncpy(password
, null_string
, MAX_URL
);
1072 flags
.tried_auth_nopass
=1;
1077 return 0; /* different username */
1080 static String str_type_eq
;
1082 Ftp::Gateway::checkUrlpath()
1087 if (str_type_eq
.size()==0) //hack. String doesn't support global-static
1088 str_type_eq
="type=";
1090 if ((t
= request
->urlpath
.rfind(';')) != String::npos
) {
1091 if (request
->urlpath
.substr(t
+1,t
+1+str_type_eq
.size())==str_type_eq
) {
1092 typecode
= (char)xtoupper(request
->urlpath
[t
+str_type_eq
.size()+1]);
1093 request
->urlpath
.cut(t
);
1097 l
= request
->urlpath
.size();
1098 /* check for null path */
1103 flags
.need_base_href
= 1; /* Work around broken browsers */
1104 } else if (!request
->urlpath
.cmp("/%2f/")) {
1105 /* UNIX root directory */
1108 } else if ((l
>= 1) && (request
->urlpath
[l
- 1] == '/')) {
1109 /* Directory URL, ending in / */
1115 flags
.dir_slash
= 1;
1120 Ftp::Gateway::buildTitleUrl()
1122 title_url
= "ftp://";
1124 if (strcmp(user
, "anonymous")) {
1125 title_url
.append(user
);
1126 title_url
.append("@");
1129 title_url
.append(request
->GetHost());
1131 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1132 title_url
.append(":");
1133 title_url
.append(xitoa(request
->port
));
1136 title_url
.append (request
->urlpath
);
1138 base_href
= "ftp://";
1140 if (strcmp(user
, "anonymous") != 0) {
1141 base_href
.append(rfc1738_escape_part(user
));
1144 base_href
.append (":");
1145 base_href
.append(rfc1738_escape_part(password
));
1148 base_href
.append("@");
1151 base_href
.append(request
->GetHost());
1153 if (request
->port
!= urlDefaultPort(AnyP::PROTO_FTP
)) {
1154 base_href
.append(":");
1155 base_href
.append(xitoa(request
->port
));
1158 base_href
.append(request
->urlpath
);
1159 base_href
.append("/");
1163 Ftp::Gateway::start()
1165 if (!checkAuth(&request
->header
)) {
1166 /* create appropriate reply */
1167 HttpReply
*reply
= ftpAuthRequired(request
, ftpRealm());
1168 entry
->replaceHttpReply(reply
);
1175 debugs(9, 5, HERE
<< "FD " << ctrl
.conn
->fd
<< " : host=" << request
->GetHost() <<
1176 ", path=" << request
->urlpath
<< ", user=" << user
<< ", passwd=" << password
);
1178 Ftp::Client::start();
1181 /* ====================================================================== */
1184 Ftp::Gateway::handleControlReply()
1186 Ftp::Client::handleControlReply();
1187 if (ctrl
.message
== NULL
)
1188 return; // didn't get complete reply yet
1190 /* Copy the message except for the last line to cwd_message to be
1191 * printed in error messages.
1193 for (wordlist
*w
= ctrl
.message
; w
&& w
->next
; w
= w
->next
) {
1194 cwd_message
.append('\n');
1195 cwd_message
.append(w
->key
);
1198 FTP_SM_FUNCS
[state
] (this);
1201 /* ====================================================================== */
1204 ftpReadWelcome(Ftp::Gateway
* ftpState
)
1206 int code
= ftpState
->ctrl
.replycode
;
1209 if (ftpState
->flags
.pasv_only
)
1210 ++ ftpState
->login_att
;
1213 if (ftpState
->ctrl
.message
) {
1214 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1215 ftpState
->flags
.skip_whitespace
= 1;
1218 ftpSendUser(ftpState
);
1219 } else if (code
== 120) {
1220 if (NULL
!= ftpState
->ctrl
.message
)
1221 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ftpState
->ctrl
.message
->key
);
1230 * Translate FTP login failure into HTTP error
1231 * this is an attmpt to get the 407 message to show up outside Squid.
1232 * its NOT a general failure. But a correct FTP response type.
1235 Ftp::Gateway::loginFailed()
1237 ErrorState
*err
= NULL
;
1238 const char *command
, *reply
;
1240 if ((state
== SENT_USER
|| state
== SENT_PASS
) && ctrl
.replycode
>= 400) {
1241 if (ctrl
.replycode
== 421 || ctrl
.replycode
== 426) {
1242 // 421/426 - Service Overload - retry permitted.
1243 err
= new ErrorState(ERR_FTP_UNAVAILABLE
, Http::scServiceUnavailable
, fwd
->request
);
1244 } else if (ctrl
.replycode
>= 430 && ctrl
.replycode
<= 439) {
1245 // 43x - Invalid or Credential Error - retry challenge required.
1246 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1247 } else if (ctrl
.replycode
>= 530 && ctrl
.replycode
<= 539) {
1248 // 53x - Credentials Missing - retry challenge required
1249 if (password_url
) // but they were in the URI! major fail.
1250 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scForbidden
, fwd
->request
);
1252 err
= new ErrorState(ERR_FTP_FORBIDDEN
, Http::scUnauthorized
, fwd
->request
);
1256 // any other problems are general falures.
1262 err
->ftp
.server_msg
= ctrl
.message
;
1264 ctrl
.message
= NULL
;
1267 command
= old_request
;
1269 command
= ctrl
.last_command
;
1271 if (command
&& strncmp(command
, "PASS", 4) == 0)
1272 command
= "PASS <yourpassword>";
1277 reply
= ctrl
.last_reply
;
1280 err
->ftp
.request
= xstrdup(command
);
1283 err
->ftp
.reply
= xstrdup(reply
);
1285 HttpReply
*newrep
= err
->BuildHttpReply();
1288 #if HAVE_AUTH_MODULE_BASIC
1289 /* add Authenticate header */
1290 newrep
->header
.putAuth("Basic", ftpRealm());
1293 // add it to the store entry for response....
1294 entry
->replaceHttpReply(newrep
);
1299 Ftp::Gateway::ftpRealm()
1301 static char realm
[8192];
1303 /* This request is not fully authenticated */
1305 snprintf(realm
, 8192, "FTP %s unknown", user
);
1306 } else if (request
->port
== 21) {
1307 snprintf(realm
, 8192, "FTP %s %s", user
, request
->GetHost());
1309 snprintf(realm
, 8192, "FTP %s %s port %d", user
, request
->GetHost(), request
->port
);
1315 ftpSendUser(Ftp::Gateway
* ftpState
)
1317 /* check the server control channel is still available */
1318 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendUser"))
1321 if (ftpState
->proxy_host
!= NULL
)
1322 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s@%s\r\n",
1324 ftpState
->request
->GetHost());
1326 snprintf(cbuf
, CTRL_BUFLEN
, "USER %s\r\n", ftpState
->user
);
1328 ftpState
->writeCommand(cbuf
);
1330 ftpState
->state
= Ftp::Client::SENT_USER
;
1334 ftpReadUser(Ftp::Gateway
* ftpState
)
1336 int code
= ftpState
->ctrl
.replycode
;
1340 ftpReadPass(ftpState
);
1341 } else if (code
== 331) {
1342 ftpSendPass(ftpState
);
1344 ftpState
->loginFailed();
1349 ftpSendPass(Ftp::Gateway
* ftpState
)
1351 /* check the server control channel is still available */
1352 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPass"))
1355 snprintf(cbuf
, CTRL_BUFLEN
, "PASS %s\r\n", ftpState
->password
);
1356 ftpState
->writeCommand(cbuf
);
1357 ftpState
->state
= Ftp::Client::SENT_PASS
;
1361 ftpReadPass(Ftp::Gateway
* ftpState
)
1363 int code
= ftpState
->ctrl
.replycode
;
1364 debugs(9, 3, HERE
<< "code=" << code
);
1367 ftpSendType(ftpState
);
1369 ftpState
->loginFailed();
1374 ftpSendType(Ftp::Gateway
* ftpState
)
1377 const char *filename
;
1380 /* check the server control channel is still available */
1381 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendType"))
1385 * Ref section 3.2.2 of RFC 1738
1387 mode
= ftpState
->typecode
;
1402 if (ftpState
->flags
.isdir
) {
1405 t
= ftpState
->request
->urlpath
.rpos('/');
1406 filename
= t
? t
+ 1 : ftpState
->request
->urlpath
.termedBuf();
1407 mode
= mimeGetTransferMode(filename
);
1414 ftpState
->flags
.binary
= 1;
1416 ftpState
->flags
.binary
= 0;
1418 snprintf(cbuf
, CTRL_BUFLEN
, "TYPE %c\r\n", mode
);
1420 ftpState
->writeCommand(cbuf
);
1422 ftpState
->state
= Ftp::Client::SENT_TYPE
;
1426 ftpReadType(Ftp::Gateway
* ftpState
)
1428 int code
= ftpState
->ctrl
.replycode
;
1431 debugs(9, 3, HERE
<< "code=" << code
);
1434 p
= path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
1441 p
+= strcspn(p
, "/");
1448 rfc1738_unescape(d
);
1451 wordlistAdd(&ftpState
->pathcomps
, d
);
1456 if (ftpState
->pathcomps
)
1457 ftpTraverseDirectory(ftpState
);
1459 ftpListDir(ftpState
);
1466 ftpTraverseDirectory(Ftp::Gateway
* ftpState
)
1469 debugs(9, 4, HERE
<< (ftpState
->filepath
? ftpState
->filepath
: "<NULL>"));
1471 safe_free(ftpState
->dirpath
);
1472 ftpState
->dirpath
= ftpState
->filepath
;
1473 ftpState
->filepath
= NULL
;
1477 if (ftpState
->pathcomps
== NULL
) {
1478 debugs(9, 3, HERE
<< "the final component was a directory");
1479 ftpListDir(ftpState
);
1483 /* Go to next path component */
1484 w
= ftpState
->pathcomps
;
1486 ftpState
->filepath
= w
->key
;
1488 ftpState
->pathcomps
= w
->next
;
1492 /* Check if we are to CWD or RETR */
1493 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
1494 ftpSendCwd(ftpState
);
1496 debugs(9, 3, HERE
<< "final component is probably a file");
1497 ftpGetFile(ftpState
);
1503 ftpSendCwd(Ftp::Gateway
* ftpState
)
1507 /* check the server control channel is still available */
1508 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendCwd"))
1513 path
= ftpState
->filepath
;
1515 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
1516 ftpState
->flags
.no_dotdot
= 1;
1518 ftpState
->flags
.no_dotdot
= 0;
1521 snprintf(cbuf
, CTRL_BUFLEN
, "CWD %s\r\n", path
);
1523 ftpState
->writeCommand(cbuf
);
1525 ftpState
->state
= Ftp::Client::SENT_CWD
;
1529 ftpReadCwd(Ftp::Gateway
* ftpState
)
1531 int code
= ftpState
->ctrl
.replycode
;
1534 if (code
>= 200 && code
< 300) {
1538 /* Reset cwd_message to only include the last message */
1539 ftpState
->cwd_message
.reset("");
1540 for (wordlist
*w
= ftpState
->ctrl
.message
; w
; w
= w
->next
) {
1541 ftpState
->cwd_message
.append(' ');
1542 ftpState
->cwd_message
.append(w
->key
);
1544 ftpState
->ctrl
.message
= NULL
;
1546 /* Continue to traverse the path */
1547 ftpTraverseDirectory(ftpState
);
1551 if (!ftpState
->flags
.put
)
1554 ftpSendMkdir(ftpState
);
1559 ftpSendMkdir(Ftp::Gateway
* ftpState
)
1563 /* check the server control channel is still available */
1564 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMkdir"))
1567 path
= ftpState
->filepath
;
1568 debugs(9, 3, HERE
<< "with path=" << path
);
1569 snprintf(cbuf
, CTRL_BUFLEN
, "MKD %s\r\n", path
);
1570 ftpState
->writeCommand(cbuf
);
1571 ftpState
->state
= Ftp::Client::SENT_MKDIR
;
1575 ftpReadMkdir(Ftp::Gateway
* ftpState
)
1577 char *path
= ftpState
->filepath
;
1578 int code
= ftpState
->ctrl
.replycode
;
1580 debugs(9, 3, HERE
<< "path " << path
<< ", code " << code
);
1582 if (code
== 257) { /* success */
1583 ftpSendCwd(ftpState
);
1584 } else if (code
== 550) { /* dir exists */
1586 if (ftpState
->flags
.put_mkdir
) {
1587 ftpState
->flags
.put_mkdir
= 1;
1588 ftpSendCwd(ftpState
);
1590 ftpSendReply(ftpState
);
1592 ftpSendReply(ftpState
);
1596 ftpGetFile(Ftp::Gateway
* ftpState
)
1598 assert(*ftpState
->filepath
!= '\0');
1599 ftpState
->flags
.isdir
= 0;
1600 ftpSendMdtm(ftpState
);
1604 ftpListDir(Ftp::Gateway
* ftpState
)
1606 if (ftpState
->flags
.dir_slash
) {
1607 debugs(9, 3, HERE
<< "Directory path did not end in /");
1608 ftpState
->title_url
.append("/");
1609 ftpState
->flags
.isdir
= 1;
1612 ftpSendPassive(ftpState
);
1616 ftpSendMdtm(Ftp::Gateway
* ftpState
)
1618 /* check the server control channel is still available */
1619 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendMdtm"))
1622 assert(*ftpState
->filepath
!= '\0');
1623 snprintf(cbuf
, CTRL_BUFLEN
, "MDTM %s\r\n", ftpState
->filepath
);
1624 ftpState
->writeCommand(cbuf
);
1625 ftpState
->state
= Ftp::Client::SENT_MDTM
;
1629 ftpReadMdtm(Ftp::Gateway
* ftpState
)
1631 int code
= ftpState
->ctrl
.replycode
;
1635 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
1637 } else if (code
< 0) {
1642 ftpSendSize(ftpState
);
1646 ftpSendSize(Ftp::Gateway
* ftpState
)
1648 /* check the server control channel is still available */
1649 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendSize"))
1652 /* Only send SIZE for binary transfers. The returned size
1653 * is useless on ASCII transfers */
1655 if (ftpState
->flags
.binary
) {
1656 assert(ftpState
->filepath
!= NULL
);
1657 assert(*ftpState
->filepath
!= '\0');
1658 snprintf(cbuf
, CTRL_BUFLEN
, "SIZE %s\r\n", ftpState
->filepath
);
1659 ftpState
->writeCommand(cbuf
);
1660 ftpState
->state
= Ftp::Client::SENT_SIZE
;
1662 /* Skip to next state no non-binary transfers */
1663 ftpSendPassive(ftpState
);
1667 ftpReadSize(Ftp::Gateway
* ftpState
)
1669 int code
= ftpState
->ctrl
.replycode
;
1674 ftpState
->theSize
= strtoll(ftpState
->ctrl
.last_reply
, NULL
, 10);
1676 if (ftpState
->theSize
== 0) {
1677 debugs(9, 2, "SIZE reported " <<
1678 ftpState
->ctrl
.last_reply
<< " on " <<
1679 ftpState
->title_url
);
1680 ftpState
->theSize
= -1;
1682 } else if (code
< 0) {
1687 ftpSendPassive(ftpState
);
1691 ftpReadEPSV(Ftp::Gateway
* ftpState
)
1693 Ip::Address srvAddr
; // unused
1694 if (ftpState
->handleEpsvReply(srvAddr
)) {
1695 if (ftpState
->ctrl
.message
== NULL
)
1696 return; // didn't get complete reply yet
1698 ftpState
->connectDataChannel();
1702 /** Send Passive connection request.
1703 * Default method is to use modern EPSV request.
1704 * The failover mechanism should check for previous state and re-call with alternates on failure.
1707 ftpSendPassive(Ftp::Gateway
* ftpState
)
1709 /** Checks the server control channel is still available before running. */
1710 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPassive"))
1716 * Checks for 'HEAD' method request and passes off for special handling by Ftp::Gateway::processHeadResponse(). */
1717 if (ftpState
->request
->method
== Http::METHOD_HEAD
&& (ftpState
->flags
.isdir
|| ftpState
->theSize
!= -1)) {
1718 ftpState
->processHeadResponse(); // may call serverComplete
1722 if (ftpState
->sendPassive()) {
1723 // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
1724 if (ftpState
->state
== Ftp::Client::SENT_EPSV_ALL
)
1725 ftpState
->flags
.epsv_all_sent
= true;
1730 Ftp::Gateway::processHeadResponse()
1732 debugs(9, 5, HERE
<< "handling HEAD response");
1734 appendSuccessHeader();
1737 * On rare occasions I'm seeing the entry get aborted after
1738 * readControlReply() and before here, probably when
1739 * trying to write to the client.
1741 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1742 abortTransaction("entry aborted while processing HEAD");
1747 if (adaptationAccessCheckPending
) {
1748 debugs(9,3, HERE
<< "returning due to adaptationAccessCheckPending");
1753 // processReplyBody calls serverComplete() since there is no body
1758 ftpReadPasv(Ftp::Gateway
* ftpState
)
1760 Ip::Address srvAddr
; // unused
1761 if (ftpState
->handlePasvReply(srvAddr
))
1762 ftpState
->connectDataChannel();
1764 ftpSendEPRT(ftpState
);
1770 Ftp::Gateway::dataChannelConnected(const CommConnectCbParams
&io
)
1775 if (io
.flag
!= Comm::OK
) {
1776 debugs(9, 2, HERE
<< "Failed to connect. Retrying via another method.");
1778 // ABORT on timeouts. server may be waiting on a broken TCP link.
1779 if (io
.xerrno
== Comm::TIMEOUT
)
1780 writeCommand("ABOR");
1782 // try another connection attempt with some other method
1783 ftpSendPassive(this);
1787 data
.opened(io
.conn
, dataCloser());
1788 ftpRestOrList(this);
1792 ftpOpenListenSocket(Ftp::Gateway
* ftpState
, int fallback
)
1794 /// Close old data channels, if any. We may open a new one below.
1795 if (ftpState
->data
.conn
!= NULL
) {
1796 if ((ftpState
->data
.conn
->flags
& COMM_REUSEADDR
))
1797 // NP: in fact it points to the control channel. just clear it.
1798 ftpState
->data
.clear();
1800 ftpState
->data
.close();
1802 safe_free(ftpState
->data
.host
);
1805 * Set up a listen socket on the same local address as the
1806 * control connection.
1808 Comm::ConnectionPointer temp
= new Comm::Connection
;
1809 temp
->local
= ftpState
->ctrl
.conn
->local
;
1812 * REUSEADDR is needed in fallback mode, since the same port is
1813 * used for both control and data.
1817 setsockopt(ftpState
->ctrl
.conn
->fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
1818 ftpState
->ctrl
.conn
->flags
|= COMM_REUSEADDR
;
1819 temp
->flags
|= COMM_REUSEADDR
;
1821 /* if not running in fallback mode a new port needs to be retrieved */
1822 temp
->local
.port(0);
1825 ftpState
->listenForDataChannel(temp
);
1829 ftpSendPORT(Ftp::Gateway
* ftpState
)
1831 /* check the server control channel is still available */
1832 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendPort"))
1835 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1836 debugs(9, DBG_IMPORTANT
, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1841 ftpState
->flags
.pasv_supported
= 0;
1842 ftpOpenListenSocket(ftpState
, 0);
1844 if (!Comm::IsConnOpen(ftpState
->data
.listenConn
)) {
1845 if ( ftpState
->data
.listenConn
!= NULL
&& !ftpState
->data
.listenConn
->local
.isIPv4() ) {
1846 /* non-IPv4 CANNOT send PORT command. */
1847 /* we got here by attempting and failing an EPRT */
1848 /* using the same reply code should simulate a PORT failure */
1849 ftpReadPORT(ftpState
);
1853 /* XXX Need to set error message */
1858 // pull out the internal IP address bytes to send in PORT command...
1859 // source them from the listen_conn->local
1861 struct addrinfo
*AI
= NULL
;
1862 ftpState
->data
.listenConn
->local
.getAddrInfo(AI
, AF_INET
);
1863 unsigned char *addrptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_addr
;
1864 unsigned char *portptr
= (unsigned char *) &((struct sockaddr_in
*)AI
->ai_addr
)->sin_port
;
1865 snprintf(cbuf
, CTRL_BUFLEN
, "PORT %d,%d,%d,%d,%d,%d\r\n",
1866 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
1867 portptr
[0], portptr
[1]);
1868 ftpState
->writeCommand(cbuf
);
1869 ftpState
->state
= Ftp::Client::SENT_PORT
;
1871 Ip::Address::FreeAddrInfo(AI
);
1875 ftpReadPORT(Ftp::Gateway
* ftpState
)
1877 int code
= ftpState
->ctrl
.replycode
;
1881 /* Fall back on using the same port as the control connection */
1882 debugs(9, 3, "PORT not supported by remote end");
1883 ftpOpenListenSocket(ftpState
, 1);
1886 ftpRestOrList(ftpState
);
1890 ftpSendEPRT(Ftp::Gateway
* ftpState
)
1892 if (Config
.Ftp
.epsv_all
&& ftpState
->flags
.epsv_all_sent
) {
1893 debugs(9, DBG_IMPORTANT
, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
1897 if (!Config
.Ftp
.eprt
) {
1898 /* Disabled. Switch immediately to attempting old PORT command. */
1899 debugs(9, 3, "EPRT disabled by local administrator");
1900 ftpSendPORT(ftpState
);
1905 ftpState
->flags
.pasv_supported
= 0;
1907 ftpOpenListenSocket(ftpState
, 0);
1908 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState
->data
.conn
);
1909 if (!Comm::IsConnOpen(ftpState
->data
.conn
)) {
1910 /* XXX Need to set error message */
1915 char buf
[MAX_IPSTRLEN
];
1917 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
1918 /* Which can be used by EITHER protocol. */
1919 snprintf(cbuf
, CTRL_BUFLEN
, "EPRT |%d|%s|%d|\r\n",
1920 ( ftpState
->data
.listenConn
->local
.isIPv6() ? 2 : 1 ),
1921 ftpState
->data
.listenConn
->local
.toStr(buf
,MAX_IPSTRLEN
),
1922 ftpState
->data
.listenConn
->local
.port() );
1924 ftpState
->writeCommand(cbuf
);
1925 ftpState
->state
= Ftp::Client::SENT_EPRT
;
1929 ftpReadEPRT(Ftp::Gateway
* ftpState
)
1931 int code
= ftpState
->ctrl
.replycode
;
1935 /* Failover to attempting old PORT command. */
1936 debugs(9, 3, "EPRT not supported by remote end");
1937 ftpSendPORT(ftpState
);
1941 ftpRestOrList(ftpState
);
1944 /** "read" handler to accept FTP data connections.
1946 \param io comm accept(2) callback parameters
1949 Ftp::Gateway::ftpAcceptDataConnection(const CommAcceptCbParams
&io
)
1953 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1954 abortTransaction("entry aborted when accepting data conn");
1955 data
.listenConn
->close();
1956 data
.listenConn
= NULL
;
1960 if (io
.flag
!= Comm::OK
) {
1961 data
.listenConn
->close();
1962 data
.listenConn
= NULL
;
1963 debugs(9, DBG_IMPORTANT
, "FTP AcceptDataConnection: " << io
.conn
<< ": " << xstrerr(io
.xerrno
));
1964 /** \todo Need to send error message on control channel*/
1969 /* data listening conn is no longer even open. abort. */
1970 if (!Comm::IsConnOpen(data
.listenConn
)) {
1971 data
.listenConn
= NULL
; // ensure that it's cleared and not just closed.
1975 /* data listening conn is no longer even open. abort. */
1976 if (!Comm::IsConnOpen(data
.conn
)) {
1977 data
.clear(); // ensure that it's cleared and not just closed.
1982 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
1983 * made by the remote client which is connected to the FTP control socket.
1984 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
1985 * This prevents third-party hacks, but also third-party load balancing handshakes.
1987 if (Config
.Ftp
.sanitycheck
) {
1988 // accept if either our data or ctrl connection is talking to this remote peer.
1989 if (data
.conn
->remote
!= io
.conn
->remote
&& ctrl
.conn
->remote
!= io
.conn
->remote
) {
1990 debugs(9, DBG_IMPORTANT
,
1991 "FTP data connection from unexpected server (" <<
1992 io
.conn
->remote
<< "), expecting " <<
1993 data
.conn
->remote
<< " or " << ctrl
.conn
->remote
);
1995 /* close the bad sources connection down ASAP. */
1998 /* drop the bad connection (io) by ignoring the attempt. */
2003 /** On Comm::OK start using the accepted data socket and discard the temporary listen socket. */
2005 data
.opened(io
.conn
, dataCloser());
2006 data
.addr(io
.conn
->remote
);
2008 debugs(9, 3, HERE
<< "Connected data socket on " <<
2009 io
.conn
<< ". FD table says: " <<
2010 "ctrl-peer= " << fd_table
[ctrl
.conn
->fd
].ipaddr
<< ", " <<
2011 "data-peer= " << fd_table
[data
.conn
->fd
].ipaddr
);
2013 assert(haveControlChannel("ftpAcceptDataConnection"));
2014 assert(ctrl
.message
== NULL
);
2016 // Ctrl channel operations will determine what happens to this data connection
2020 ftpRestOrList(Ftp::Gateway
* ftpState
)
2024 if (ftpState
->typecode
== 'D') {
2025 ftpState
->flags
.isdir
= 1;
2027 if (ftpState
->flags
.put
) {
2028 ftpSendMkdir(ftpState
); /* PUT name;type=d */
2030 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
2032 } else if (ftpState
->flags
.put
) {
2033 ftpSendStor(ftpState
);
2034 } else if (ftpState
->flags
.isdir
)
2035 ftpSendList(ftpState
);
2036 else if (ftpState
->restartable())
2037 ftpSendRest(ftpState
);
2039 ftpSendRetr(ftpState
);
2043 ftpSendStor(Ftp::Gateway
* ftpState
)
2045 /* check the server control channel is still available */
2046 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendStor"))
2051 if (ftpState
->filepath
!= NULL
) {
2052 /* Plain file upload */
2053 snprintf(cbuf
, CTRL_BUFLEN
, "STOR %s\r\n", ftpState
->filepath
);
2054 ftpState
->writeCommand(cbuf
);
2055 ftpState
->state
= Ftp::Client::SENT_STOR
;
2056 } else if (ftpState
->request
->header
.getInt64(HDR_CONTENT_LENGTH
) > 0) {
2057 /* File upload without a filename. use STOU to generate one */
2058 snprintf(cbuf
, CTRL_BUFLEN
, "STOU\r\n");
2059 ftpState
->writeCommand(cbuf
);
2060 ftpState
->state
= Ftp::Client::SENT_STOR
;
2062 /* No file to transfer. Only create directories if needed */
2063 ftpSendReply(ftpState
);
2067 /// \deprecated use ftpState->readStor() instead.
2069 ftpReadStor(Ftp::Gateway
* ftpState
)
2071 ftpState
->readStor();
2074 void Ftp::Gateway::readStor()
2076 int code
= ctrl
.replycode
;
2079 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(data
.conn
))) {
2080 if (!originalRequest()->body_pipe
) {
2081 debugs(9, 3, "zero-size STOR?");
2082 state
= WRITING_DATA
; // make ftpWriteTransferDone() responsible
2083 dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2087 if (!startRequestBodyFlow()) { // register to receive body data
2092 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
2093 debugs(9, 3, HERE
<< "starting data transfer");
2094 switchTimeoutToDataChannel();
2095 sendMoreRequestBody();
2096 fwd
->dontRetry(true); // dont permit re-trying if the body was sent.
2097 state
= WRITING_DATA
;
2098 debugs(9, 3, HERE
<< "writing data channel");
2099 } else if (code
== 150) {
2100 /* When client code is 150 with no data channel, Accept data channel. */
2101 debugs(9, 3, "ftpReadStor: accepting data channel");
2102 listenForDataChannel(data
.conn
);
2104 debugs(9, DBG_IMPORTANT
, HERE
<< "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code
);
2110 ftpSendRest(Ftp::Gateway
* ftpState
)
2112 /* check the server control channel is still available */
2113 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRest"))
2118 snprintf(cbuf
, CTRL_BUFLEN
, "REST %" PRId64
"\r\n", ftpState
->restart_offset
);
2119 ftpState
->writeCommand(cbuf
);
2120 ftpState
->state
= Ftp::Client::SENT_REST
;
2124 Ftp::Gateway::restartable()
2126 if (restart_offset
> 0)
2129 if (!request
->range
)
2138 int64_t desired_offset
= request
->range
->lowestOffset(theSize
);
2140 if (desired_offset
<= 0)
2143 if (desired_offset
>= theSize
)
2146 restart_offset
= desired_offset
;
2151 ftpReadRest(Ftp::Gateway
* ftpState
)
2153 int code
= ftpState
->ctrl
.replycode
;
2155 assert(ftpState
->restart_offset
> 0);
2158 ftpState
->setCurrentOffset(ftpState
->restart_offset
);
2159 ftpSendRetr(ftpState
);
2160 } else if (code
> 0) {
2161 debugs(9, 3, HERE
<< "REST not supported");
2162 ftpState
->flags
.rest_supported
= 0;
2163 ftpSendRetr(ftpState
);
2170 ftpSendList(Ftp::Gateway
* ftpState
)
2172 /* check the server control channel is still available */
2173 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendList"))
2178 if (ftpState
->filepath
) {
2179 snprintf(cbuf
, CTRL_BUFLEN
, "LIST %s\r\n", ftpState
->filepath
);
2181 snprintf(cbuf
, CTRL_BUFLEN
, "LIST\r\n");
2184 ftpState
->writeCommand(cbuf
);
2185 ftpState
->state
= Ftp::Client::SENT_LIST
;
2189 ftpSendNlst(Ftp::Gateway
* ftpState
)
2191 /* check the server control channel is still available */
2192 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendNlst"))
2197 ftpState
->flags
.tried_nlst
= 1;
2199 if (ftpState
->filepath
) {
2200 snprintf(cbuf
, CTRL_BUFLEN
, "NLST %s\r\n", ftpState
->filepath
);
2202 snprintf(cbuf
, CTRL_BUFLEN
, "NLST\r\n");
2205 ftpState
->writeCommand(cbuf
);
2206 ftpState
->state
= Ftp::Client::SENT_NLST
;
2210 ftpReadList(Ftp::Gateway
* ftpState
)
2212 int code
= ftpState
->ctrl
.replycode
;
2215 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2216 /* Begin data transfer */
2217 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2218 ftpState
->switchTimeoutToDataChannel();
2219 ftpState
->maybeReadVirginBody();
2220 ftpState
->state
= Ftp::Client::READING_DATA
;
2222 } else if (code
== 150) {
2223 /* Accept data channel */
2224 debugs(9, 3, HERE
<< "accept data channel from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2225 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2227 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
2228 ftpSendNlst(ftpState
);
2236 ftpSendRetr(Ftp::Gateway
* ftpState
)
2238 /* check the server control channel is still available */
2239 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendRetr"))
2244 assert(ftpState
->filepath
!= NULL
);
2245 snprintf(cbuf
, CTRL_BUFLEN
, "RETR %s\r\n", ftpState
->filepath
);
2246 ftpState
->writeCommand(cbuf
);
2247 ftpState
->state
= Ftp::Client::SENT_RETR
;
2251 ftpReadRetr(Ftp::Gateway
* ftpState
)
2253 int code
= ftpState
->ctrl
.replycode
;
2256 if (code
== 125 || (code
== 150 && Comm::IsConnOpen(ftpState
->data
.conn
))) {
2257 /* Begin data transfer */
2258 debugs(9, 3, HERE
<< "begin data transfer from " << ftpState
->data
.conn
->remote
<< " (" << ftpState
->data
.conn
->local
<< ")");
2259 ftpState
->switchTimeoutToDataChannel();
2260 ftpState
->maybeReadVirginBody();
2261 ftpState
->state
= Ftp::Client::READING_DATA
;
2262 } else if (code
== 150) {
2263 /* Accept data channel */
2264 ftpState
->listenForDataChannel(ftpState
->data
.conn
);
2265 } else if (code
>= 300) {
2266 if (!ftpState
->flags
.try_slash_hack
) {
2267 /* Try this as a directory missing trailing slash... */
2268 ftpState
->hackShortcut(ftpSendCwd
);
2278 * Generate the HTTP headers and template fluff around an FTP
2279 * directory listing display.
2282 Ftp::Gateway::completedListing()
2285 entry
->lock("Ftp::Gateway");
2286 ErrorState
ferr(ERR_DIR_LISTING
, Http::scOkay
, request
);
2287 ferr
.ftp
.listing
= &listing
;
2288 ferr
.ftp
.cwd_msg
= xstrdup(cwd_message
.size()? cwd_message
.termedBuf() : "");
2289 ferr
.ftp
.server_msg
= ctrl
.message
;
2290 ctrl
.message
= NULL
;
2291 entry
->replaceHttpReply( ferr
.BuildHttpReply() );
2292 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
2294 entry
->unlock("Ftp::Gateway");
2298 ftpReadTransferDone(Ftp::Gateway
* ftpState
)
2300 int code
= ftpState
->ctrl
.replycode
;
2303 if (code
== 226 || code
== 250) {
2304 /* Connection closed; retrieval done. */
2305 if (ftpState
->flags
.listing
) {
2306 ftpState
->completedListing();
2307 /* QUIT operation handles sending the reply to client */
2309 ftpSendQuit(ftpState
);
2310 } else { /* != 226 */
2311 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after reading data");
2312 ftpState
->failed(ERR_FTP_FAILURE
, 0);
2313 /* failed closes ctrl.conn and frees ftpState */
2318 // premature end of the request body
2320 Ftp::Gateway::handleRequestBodyProducerAborted()
2322 Client::handleRequestBodyProducerAborted();
2323 debugs(9, 3, HERE
<< "ftpState=" << this);
2324 failed(ERR_READ_ERROR
, 0);
2328 ftpWriteTransferDone(Ftp::Gateway
* ftpState
)
2330 int code
= ftpState
->ctrl
.replycode
;
2333 if (!(code
== 226 || code
== 250)) {
2334 debugs(9, DBG_IMPORTANT
, HERE
<< "Got code " << code
<< " after sending data");
2335 ftpState
->failed(ERR_FTP_PUT_ERROR
, 0);
2339 ftpState
->entry
->timestampsSet(); /* XXX Is this needed? */
2340 ftpSendReply(ftpState
);
2344 ftpSendQuit(Ftp::Gateway
* ftpState
)
2346 /* check the server control channel is still available */
2347 if (!ftpState
|| !ftpState
->haveControlChannel("ftpSendQuit"))
2350 snprintf(cbuf
, CTRL_BUFLEN
, "QUIT\r\n");
2351 ftpState
->writeCommand(cbuf
);
2352 ftpState
->state
= Ftp::Client::SENT_QUIT
;
2355 /** Completes a client FTP operation with success or other page
2356 * generated and stored in the entry field by the code issuing QUIT.
2359 ftpReadQuit(Ftp::Gateway
* ftpState
)
2361 ftpState
->serverComplete();
2365 ftpTrySlashHack(Ftp::Gateway
* ftpState
)
2368 ftpState
->flags
.try_slash_hack
= 1;
2369 /* Free old paths */
2373 if (ftpState
->pathcomps
)
2374 wordlistDestroy(&ftpState
->pathcomps
);
2376 safe_free(ftpState
->filepath
);
2378 /* Build the new path (urlpath begins with /) */
2379 path
= xstrdup(ftpState
->request
->urlpath
.termedBuf());
2381 rfc1738_unescape(path
);
2383 ftpState
->filepath
= path
;
2386 ftpGetFile(ftpState
);
2390 * Forget hack status. Next error is shown to the user
2393 Ftp::Gateway::unhack()
2397 if (old_request
!= NULL
) {
2398 safe_free(old_request
);
2399 safe_free(old_reply
);
2404 Ftp::Gateway::hackShortcut(FTPSM
* nextState
)
2406 /* Clear some unwanted state */
2407 setCurrentOffset(0);
2409 /* Save old error message & some state info */
2413 if (old_request
== NULL
) {
2414 old_request
= ctrl
.last_command
;
2415 ctrl
.last_command
= NULL
;
2416 old_reply
= ctrl
.last_reply
;
2417 ctrl
.last_reply
= NULL
;
2419 if (pathcomps
== NULL
&& filepath
!= NULL
)
2420 old_filepath
= xstrdup(filepath
);
2423 /* Jump to the "hack" state */
2428 ftpFail(Ftp::Gateway
*ftpState
)
2430 debugs(9, 6, HERE
<< "flags(" <<
2431 (ftpState
->flags
.isdir
?"IS_DIR,":"") <<
2432 (ftpState
->flags
.try_slash_hack
?"TRY_SLASH_HACK":"") << "), " <<
2433 "mdtm=" << ftpState
->mdtm
<< ", size=" << ftpState
->theSize
<<
2434 "slashhack=" << (ftpState
->request
->urlpath
.caseCmp("/%2f", 4)==0? "T":"F") );
2436 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2437 if (!ftpState
->flags
.isdir
&& /* Not a directory */
2438 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
2439 ftpState
->mdtm
<= 0 && ftpState
->theSize
< 0 && /* Not known as a file */
2440 ftpState
->request
->urlpath
.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
2442 switch (ftpState
->state
) {
2444 case Ftp::Client::SENT_CWD
:
2446 case Ftp::Client::SENT_RETR
:
2447 /* Try the / hack */
2448 ftpState
->hackShortcut(ftpTrySlashHack
);
2456 ftpState
->failed(ERR_NONE
, 0);
2457 /* failed() closes ctrl.conn and frees this */
2461 Ftp::Gateway::failedHttpStatus(err_type
&error
)
2463 if (error
== ERR_NONE
) {
2470 if (ctrl
.replycode
> 500) {
2471 error
= ERR_FTP_FORBIDDEN
;
2472 return password_url
? Http::scForbidden
: Http::scUnauthorized
;
2473 } else if (ctrl
.replycode
== 421) {
2474 error
= ERR_FTP_UNAVAILABLE
;
2475 return Http::scServiceUnavailable
;
2482 if (ctrl
.replycode
== 550) {
2483 error
= ERR_FTP_NOT_FOUND
;
2484 return Http::scNotFound
;
2492 return Ftp::Client::failedHttpStatus(error
);
2496 ftpSendReply(Ftp::Gateway
* ftpState
)
2498 int code
= ftpState
->ctrl
.replycode
;
2499 Http::StatusCode http_code
;
2500 err_type err_code
= ERR_NONE
;
2502 debugs(9, 3, HERE
<< ftpState
->entry
->url() << ", code " << code
);
2504 if (cbdataReferenceValid(ftpState
))
2505 debugs(9, 5, HERE
<< "ftpState (" << ftpState
<< ") is valid!");
2507 if (code
== 226 || code
== 250) {
2508 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
2509 http_code
= (ftpState
->mdtm
> 0) ? Http::scAccepted
: Http::scCreated
;
2510 } else if (code
== 227) {
2511 err_code
= ERR_FTP_PUT_CREATED
;
2512 http_code
= Http::scCreated
;
2514 err_code
= ERR_FTP_PUT_ERROR
;
2515 http_code
= Http::scInternalServerError
;
2518 ErrorState
err(err_code
, http_code
, ftpState
->request
);
2520 if (ftpState
->old_request
)
2521 err
.ftp
.request
= xstrdup(ftpState
->old_request
);
2523 err
.ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
2525 if (ftpState
->old_reply
)
2526 err
.ftp
.reply
= xstrdup(ftpState
->old_reply
);
2527 else if (ftpState
->ctrl
.last_reply
)
2528 err
.ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
2530 err
.ftp
.reply
= xstrdup("");
2532 // TODO: interpret as FTP-specific error code
2533 err
.detailError(code
);
2535 ftpState
->entry
->replaceHttpReply( err
.BuildHttpReply() );
2537 ftpSendQuit(ftpState
);
2541 Ftp::Gateway::appendSuccessHeader()
2543 const char *mime_type
= NULL
;
2544 const char *mime_enc
= NULL
;
2545 String urlpath
= request
->urlpath
;
2546 const char *filename
= NULL
;
2547 const char *t
= NULL
;
2551 if (flags
.http_header_sent
)
2554 HttpReply
*reply
= new HttpReply
;
2556 flags
.http_header_sent
= 1;
2558 assert(entry
->isEmpty());
2560 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
2562 entry
->buffer(); /* released when done processing current data payload */
2564 filename
= (t
= urlpath
.rpos('/')) ? t
+ 1 : urlpath
.termedBuf();
2567 mime_type
= "text/html";
2572 mime_type
= "application/octet-stream";
2573 mime_enc
= mimeGetContentEncoding(filename
);
2577 mime_type
= "text/plain";
2581 mime_type
= mimeGetContentType(filename
);
2582 mime_enc
= mimeGetContentEncoding(filename
);
2587 /* set standard stuff */
2589 if (0 == getCurrentOffset()) {
2591 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2592 } else if (theSize
< getCurrentOffset()) {
2595 * offset should not be larger than theSize. We should
2596 * not be seeing this condition any more because we'll only
2597 * send REST if we know the theSize and if it is less than theSize.
2599 debugs(0,DBG_CRITICAL
,HERE
<< "Whoops! " <<
2600 " current offset=" << getCurrentOffset() <<
2601 ", but theSize=" << theSize
<<
2602 ". assuming full content response");
2603 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, theSize
, mdtm
, -2);
2606 HttpHdrRangeSpec range_spec
;
2607 range_spec
.offset
= getCurrentOffset();
2608 range_spec
.length
= theSize
- getCurrentOffset();
2609 reply
->setHeaders(Http::scPartialContent
, "Gatewaying", mime_type
, theSize
- getCurrentOffset(), mdtm
, -2);
2610 httpHeaderAddContRange(&reply
->header
, range_spec
, theSize
);
2613 /* additional info */
2615 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
2617 setVirginReply(reply
);
2618 adaptOrFinalizeReply();
2622 Ftp::Gateway::haveParsedReplyHeaders()
2624 Client::haveParsedReplyHeaders();
2626 StoreEntry
*e
= entry
;
2630 if (flags
.authenticated
) {
2632 * Authenticated requests can't be cached.
2635 } else if (!EBIT_TEST(e
->flags
, RELEASE_REQUEST
) && !getCurrentOffset()) {
2643 Ftp::Gateway::ftpAuthRequired(HttpRequest
* request
, const char *realm
)
2645 ErrorState
err(ERR_CACHE_ACCESS_DENIED
, Http::scUnauthorized
, request
);
2646 HttpReply
*newrep
= err
.BuildHttpReply();
2647 #if HAVE_AUTH_MODULE_BASIC
2648 /* add Authenticate header */
2649 newrep
->header
.putAuth("Basic", realm
);
2655 Ftp::UrlWith2f(HttpRequest
* request
)
2657 String newbuf
= "%2f";
2659 if (request
->url
.getScheme() != AnyP::PROTO_FTP
)
2662 if ( request
->urlpath
[0]=='/' ) {
2663 newbuf
.append(request
->urlpath
);
2664 request
->urlpath
.absorb(newbuf
);
2665 safe_free(request
->canonical
);
2666 } else if ( !strncmp(request
->urlpath
.termedBuf(), "%2f", 3) ) {
2667 newbuf
.append(request
->urlpath
.substr(1,request
->urlpath
.size()));
2668 request
->urlpath
.absorb(newbuf
);
2669 safe_free(request
->canonical
);
2672 return urlCanonical(request
);
2676 Ftp::Gateway::printfReplyBody(const char *fmt
, ...)
2679 va_start (args
, fmt
);
2680 static char buf
[4096];
2682 vsnprintf(buf
, 4096, fmt
, args
);
2683 writeReplyBody(buf
, strlen(buf
));
2688 * Call this when there is data from the origin server
2689 * which should be sent to either StoreEntry, or to ICAP...
2692 Ftp::Gateway::writeReplyBody(const char *dataToWrite
, size_t dataLength
)
2694 debugs(9, 5, HERE
<< "writing " << dataLength
<< " bytes to the reply");
2695 addVirginReplyBody(dataToWrite
, dataLength
);
2699 * A hack to ensure we do not double-complete on the forward entry.
2701 \todo Ftp::Gateway logic should probably be rewritten to avoid
2702 * double-completion or FwdState should be rewritten to allow it.
2705 Ftp::Gateway::completeForwarding()
2707 if (fwd
== NULL
|| flags
.completed_forwarding
) {
2708 debugs(9, 3, HERE
<< "completeForwarding avoids " <<
2709 "double-complete on FD " << ctrl
.conn
->fd
<< ", Data FD " << data
.conn
->fd
<<
2710 ", this " << this << ", fwd " << fwd
);
2714 flags
.completed_forwarding
= true;
2715 Client::completeForwarding();
2719 * Have we lost the FTP server control channel?
2721 \retval true The server control channel is available.
2722 \retval false The server control channel is not available.
2725 Ftp::Gateway::haveControlChannel(const char *caller_name
) const
2727 if (doneWithServer())
2730 /* doneWithServer() only checks BOTH channels are closed. */
2731 if (!Comm::IsConnOpen(ctrl
.conn
)) {
2732 debugs(9, DBG_IMPORTANT
, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
2733 debugs(9, 2, caller_name
<< ": attempted on a closed FTP channel.");
2741 Ftp::Gateway::mayReadVirginReplyBody() const
2743 // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2744 return !doneWithServer();
2748 Ftp::StartGateway(FwdState
*const fwdState
)
2750 return AsyncJob::Start(new Ftp::Gateway(fwdState
));