]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/FtpGateway.cc
Fixes and polishing in response to Amos' squid-dev review dated 2014/08/10.
[thirdparty/squid.git] / src / clients / FtpGateway.cc
1 /*
2 * DEBUG: section 09 File Transfer Protocol (FTP)
3 * AUTHOR: Harvest Derived
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 */
32
33 #include "squid.h"
34 #include "acl/FilledChecklist.h"
35 #include "clients/forward.h"
36 #include "clients/FtpClient.h"
37 #include "comm.h"
38 #include "comm/ConnOpener.h"
39 #include "comm/Read.h"
40 #include "comm/TcpAcceptor.h"
41 #include "CommCalls.h"
42 #include "compat/strtoll.h"
43 #include "errorpage.h"
44 #include "fd.h"
45 #include "fde.h"
46 #include "FwdState.h"
47 #include "html_quote.h"
48 #include "HttpHdrContRange.h"
49 #include "HttpHeader.h"
50 #include "HttpHeaderRange.h"
51 #include "HttpReply.h"
52 #include "HttpRequest.h"
53 #include "ip/tools.h"
54 #include "Mem.h"
55 #include "MemBuf.h"
56 #include "mime.h"
57 #include "rfc1738.h"
58 #include "Server.h"
59 #include "SquidConfig.h"
60 #include "SquidString.h"
61 #include "SquidTime.h"
62 #include "StatCounters.h"
63 #include "Store.h"
64 #include "tools.h"
65 #include "URL.h"
66 #include "wordlist.h"
67
68 #if USE_DELAY_POOLS
69 #include "DelayPools.h"
70 #include "MemObject.h"
71 #endif
72
73 #include <cerrno>
74
75 namespace Ftp
76 {
77
78 struct GatewayFlags {
79
80 /* passive mode */
81 bool pasv_supported; ///< PASV command is allowed
82 bool epsv_all_sent; ///< EPSV ALL has been used. Must abort on failures.
83 bool pasv_only;
84 bool pasv_failed; // was FwdState::flags.ftp_pasv_failed
85
86 /* authentication */
87 bool authenticated; ///< authentication success
88 bool tried_auth_anonymous; ///< auth has tried to use anonymous credentials already.
89 bool tried_auth_nopass; ///< auth tried username with no password already.
90
91 /* other */
92 bool isdir;
93 bool skip_whitespace;
94 bool rest_supported;
95 bool http_header_sent;
96 bool tried_nlst;
97 bool need_base_href;
98 bool dir_slash;
99 bool root_dir;
100 bool no_dotdot;
101 bool binary;
102 bool try_slash_hack;
103 bool put;
104 bool put_mkdir;
105 bool listformat_unknown;
106 bool listing;
107 bool completed_forwarding;
108 };
109
110 class Gateway;
111 typedef void (StateMethod)(Ftp::Gateway *);
112
113 /// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI,
114 /// converts it into one or more FTP commands, and then
115 /// converts one or more FTP responses into the final HTTP response.
116 class Gateway : public Ftp::Client
117 {
118 public:
119 Gateway(FwdState *);
120 virtual ~Gateway();
121 char user[MAX_URL];
122 char password[MAX_URL];
123 int password_url;
124 char *reply_hdr;
125 int reply_hdr_state;
126 String clean_url;
127 String title_url;
128 String base_href;
129 int conn_att;
130 int login_att;
131 time_t mdtm;
132 int64_t theSize;
133 wordlist *pathcomps;
134 char *filepath;
135 char *dirpath;
136 int64_t restart_offset;
137 char *proxy_host;
138 size_t list_width;
139 String cwd_message;
140 char *old_filepath;
141 char typecode;
142 MemBuf listing; ///< FTP directory listing in HTML format.
143
144 GatewayFlags flags;
145
146 public:
147 // these should all be private
148 virtual void start();
149 virtual Http::StatusCode failedHttpStatus(err_type &error);
150 void loginParser(const char *, int escaped);
151 int restartable();
152 void appendSuccessHeader();
153 void hackShortcut(StateMethod *nextState);
154 void unhack();
155 void readStor();
156 void parseListing();
157 MemBuf *htmlifyListEntry(const char *line);
158 void completedListing(void);
159
160 /// create a data channel acceptor and start listening.
161 void listenForDataChannel(const Comm::ConnectionPointer &conn);
162
163 int checkAuth(const HttpHeader * req_hdr);
164 void checkUrlpath();
165 void buildTitleUrl();
166 void writeReplyBody(const char *, size_t len);
167 void printfReplyBody(const char *fmt, ...);
168 virtual void completeForwarding();
169 void processHeadResponse();
170 void processReplyBody();
171 void setCurrentOffset(int64_t offset) { currentOffset = offset; }
172 int64_t getCurrentOffset() const { return currentOffset; }
173
174 virtual void dataChannelConnected(const CommConnectCbParams &io);
175 static PF ftpDataWrite;
176 virtual void timeout(const CommTimeoutCbParams &io);
177 void ftpAcceptDataConnection(const CommAcceptCbParams &io);
178
179 static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
180 const char *ftpRealm(void);
181 void loginFailed(void);
182
183 virtual void haveParsedReplyHeaders();
184
185 virtual bool haveControlChannel(const char *caller_name) const;
186
187 protected:
188 virtual void handleControlReply();
189 virtual void dataClosed(const CommCloseCbParams &io);
190
191 private:
192 virtual bool mayReadVirginReplyBody() const;
193 // BodyConsumer for HTTP: consume request body.
194 virtual void handleRequestBodyProducerAborted();
195
196 CBDATA_CLASS2(Gateway);
197 };
198
199 } // namespace Ftp
200
201 typedef Ftp::StateMethod FTPSM; // to avoid lots of non-changes
202
203 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Gateway);
204
205 typedef struct {
206 char type;
207 int64_t size;
208 char *date;
209 char *name;
210 char *showname;
211 char *link;
212 } ftpListParts;
213
214 #define FTP_LOGIN_ESCAPED 1
215
216 #define FTP_LOGIN_NOT_ESCAPED 0
217
218 #define CTRL_BUFLEN 1024
219 static char cbuf[CTRL_BUFLEN];
220
221 /*
222 * State machine functions
223 * send == state transition
224 * read == wait for response, and select next state transition
225 * other == Transition logic
226 */
227 static FTPSM ftpReadWelcome;
228 static FTPSM ftpSendUser;
229 static FTPSM ftpReadUser;
230 static FTPSM ftpSendPass;
231 static FTPSM ftpReadPass;
232 static FTPSM ftpSendType;
233 static FTPSM ftpReadType;
234 static FTPSM ftpSendMdtm;
235 static FTPSM ftpReadMdtm;
236 static FTPSM ftpSendSize;
237 static FTPSM ftpReadSize;
238 static FTPSM ftpSendEPRT;
239 static FTPSM ftpReadEPRT;
240 static FTPSM ftpSendPORT;
241 static FTPSM ftpReadPORT;
242 static FTPSM ftpSendPassive;
243 static FTPSM ftpReadEPSV;
244 static FTPSM ftpReadPasv;
245 static FTPSM ftpTraverseDirectory;
246 static FTPSM ftpListDir;
247 static FTPSM ftpGetFile;
248 static FTPSM ftpSendCwd;
249 static FTPSM ftpReadCwd;
250 static FTPSM ftpRestOrList;
251 static FTPSM ftpSendList;
252 static FTPSM ftpSendNlst;
253 static FTPSM ftpReadList;
254 static FTPSM ftpSendRest;
255 static FTPSM ftpReadRest;
256 static FTPSM ftpSendRetr;
257 static FTPSM ftpReadRetr;
258 static FTPSM ftpReadTransferDone;
259 static FTPSM ftpSendStor;
260 static FTPSM ftpReadStor;
261 static FTPSM ftpWriteTransferDone;
262 static FTPSM ftpSendReply;
263 static FTPSM ftpSendMkdir;
264 static FTPSM ftpReadMkdir;
265 static FTPSM ftpFail;
266 static FTPSM ftpSendQuit;
267 static FTPSM ftpReadQuit;
268
269 /************************************************
270 ** Debugs Levels used here **
271 *************************************************
272 0 CRITICAL Events
273 1 IMPORTANT Events
274 Protocol and Transmission failures.
275 2 FTP Protocol Chatter
276 3 Logic Flows
277 4 Data Parsing Flows
278 5 Data Dumps
279 7 ??
280 ************************************************/
281
282 /************************************************
283 ** State Machine Description (excluding hacks) **
284 *************************************************
285 From To
286 ---------------------------------------
287 Welcome User
288 User Pass
289 Pass Type
290 Type TraverseDirectory / GetFile
291 TraverseDirectory Cwd / GetFile / ListDir
292 Cwd TraverseDirectory / Mkdir
293 GetFile Mdtm
294 Mdtm Size
295 Size Epsv
296 ListDir Epsv
297 Epsv FileOrList
298 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
299 Rest Retr
300 Retr / Nlst / List DataRead* (on datachannel)
301 DataRead* ReadTransferDone
302 ReadTransferDone DataTransferDone
303 Stor DataWrite* (on datachannel)
304 DataWrite* RequestPutBody** (from client)
305 RequestPutBody** DataWrite* / WriteTransferDone
306 WriteTransferDone DataTransferDone
307 DataTransferDone Quit
308 Quit -
309 ************************************************/
310
311 FTPSM *FTP_SM_FUNCS[] = {
312 ftpReadWelcome, /* BEGIN */
313 ftpReadUser, /* SENT_USER */
314 ftpReadPass, /* SENT_PASS */
315 ftpReadType, /* SENT_TYPE */
316 ftpReadMdtm, /* SENT_MDTM */
317 ftpReadSize, /* SENT_SIZE */
318 ftpReadEPRT, /* SENT_EPRT */
319 ftpReadPORT, /* SENT_PORT */
320 ftpReadEPSV, /* SENT_EPSV_ALL */
321 ftpReadEPSV, /* SENT_EPSV_1 */
322 ftpReadEPSV, /* SENT_EPSV_2 */
323 ftpReadPasv, /* SENT_PASV */
324 ftpReadCwd, /* SENT_CWD */
325 ftpReadList, /* SENT_LIST */
326 ftpReadList, /* SENT_NLST */
327 ftpReadRest, /* SENT_REST */
328 ftpReadRetr, /* SENT_RETR */
329 ftpReadStor, /* SENT_STOR */
330 ftpReadQuit, /* SENT_QUIT */
331 ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
332 ftpWriteTransferDone, /* WRITING_DATA (STOR) */
333 ftpReadMkdir, /* SENT_MKDIR */
334 NULL, /* SENT_FEAT */
335 NULL, /* SENT_PWD */
336 NULL, /* SENT_CDUP*/
337 NULL, /* SENT_DATA_REQUEST */
338 NULL /* SENT_COMMAND */
339 };
340
341 /// handler called by Comm when FTP data channel is closed unexpectedly
342 void
343 Ftp::Gateway::dataClosed(const CommCloseCbParams &io)
344 {
345 Ftp::Client::dataClosed(io);
346 failed(ERR_FTP_FAILURE, 0);
347 /* failed closes ctrl.conn and frees ftpState */
348
349 /* NP: failure recovery may be possible when its only a data.conn failure.
350 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
351 * Just need to watch out for wider Squid states like shutting down or reconfigure.
352 */
353 }
354
355 Ftp::Gateway::Gateway(FwdState *fwdState):
356 AsyncJob("FtpStateData"),
357 Ftp::Client(fwdState)
358 {
359 const char *url = entry->url();
360 debugs(9, 3, HERE << "'" << url << "'" );
361 theSize = -1;
362 mdtm = -1;
363
364 if (Config.Ftp.passive && !flags.pasv_failed)
365 flags.pasv_supported = 1;
366
367 flags.rest_supported = 1;
368
369 if (request->method == Http::METHOD_PUT)
370 flags.put = 1;
371
372 initReadBuf();
373 }
374
375 Ftp::Gateway::~Gateway()
376 {
377 debugs(9, 3, HERE << entry->url() );
378
379 if (Comm::IsConnOpen(ctrl.conn)) {
380 debugs(9, DBG_IMPORTANT, "Internal bug: FTP Gateway left open " <<
381 "control channel " << ctrl.conn);
382 }
383
384 if (reply_hdr) {
385 memFree(reply_hdr, MEM_8K_BUF);
386 reply_hdr = NULL;
387 }
388
389 if (pathcomps)
390 wordlistDestroy(&pathcomps);
391
392 cwd_message.clean();
393
394 safe_free(old_filepath);
395
396 title_url.clean();
397
398 base_href.clean();
399
400 safe_free(filepath);
401
402 safe_free(dirpath);
403 }
404
405 /**
406 * Parse a possible login username:password pair.
407 * Produces filled member variables user, password, password_url if anything found.
408 */
409 void
410 Ftp::Gateway::loginParser(const char *login, int escaped)
411 {
412 const char *u = NULL; // end of the username sub-string
413 int len; // length of the current sub-string to handle.
414
415 int total_len = strlen(login);
416
417 debugs(9, 4, HERE << ": login='" << login << "', escaped=" << escaped);
418 debugs(9, 9, HERE << ": IN : login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
419
420 if ((u = strchr(login, ':'))) {
421
422 /* if there was a username part */
423 if (u > login) {
424 len = u - login;
425 ++u; // jump off the delimiter.
426 if (len > MAX_URL)
427 len = MAX_URL-1;
428 xstrncpy(user, login, len +1);
429 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<"), escaped=" << escaped);
430 if (escaped)
431 rfc1738_unescape(user);
432 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<") unescaped.");
433 }
434
435 /* if there was a password part */
436 len = login + total_len - u;
437 if ( len > 0) {
438 if (len > MAX_URL)
439 len = MAX_URL -1;
440 xstrncpy(password, u, len +1);
441 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<"), escaped=" << escaped);
442 if (escaped) {
443 rfc1738_unescape(password);
444 password_url = 1;
445 }
446 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<") unescaped.");
447 }
448 } else if (login[0]) {
449 /* no password, just username */
450 if (total_len > MAX_URL)
451 total_len = MAX_URL -1;
452 xstrncpy(user, login, total_len +1);
453 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<"), escaped=" << escaped);
454 if (escaped)
455 rfc1738_unescape(user);
456 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<") unescaped.");
457 }
458
459 debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
460 }
461
462 void
463 Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer &conn)
464 {
465 assert(!Comm::IsConnOpen(data.conn));
466
467 typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer;
468 typedef AsyncCallT<AcceptDialer> AcceptCall;
469 RefCount<AcceptCall> call = static_cast<AcceptCall*>(JobCallback(11, 5, AcceptDialer, this, Ftp::Gateway::ftpAcceptDataConnection));
470 Subscription::Pointer sub = new CallSubscription<AcceptCall>(call);
471 const char *note = entry->url();
472
473 /* open the conn if its not already open */
474 if (!Comm::IsConnOpen(conn)) {
475 conn->fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn->local, conn->flags, note);
476 if (!Comm::IsConnOpen(conn)) {
477 debugs(5, DBG_CRITICAL, HERE << "comm_open_listener failed:" << conn->local << " error: " << errno);
478 return;
479 }
480 debugs(9, 3, HERE << "Unconnected data socket created on " << conn);
481 }
482
483 conn->tos = ctrl.conn->tos;
484 conn->nfmark = ctrl.conn->nfmark;
485
486 assert(Comm::IsConnOpen(conn));
487 AsyncJob::Start(new Comm::TcpAcceptor(conn, note, sub));
488
489 // Ensure we have a copy of the FD opened for listening and a close handler on it.
490 data.opened(conn, dataCloser());
491 switchTimeoutToDataChannel();
492 }
493
494 void
495 Ftp::Gateway::timeout(const CommTimeoutCbParams &io)
496 {
497 if (SENT_PASV == state) {
498 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
499 flags.pasv_supported = false;
500 debugs(9, DBG_IMPORTANT, "FTP Gateway timeout in SENT_PASV state");
501
502 // cancel the data connection setup.
503 if (data.opener != NULL) {
504 data.opener->cancel("timeout");
505 data.opener = NULL;
506 }
507 data.close();
508 }
509
510 Ftp::Client::timeout(io);
511 }
512
513 static const char *Month[] = {
514 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
515 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
516 };
517
518 static int
519 is_month(const char *buf)
520 {
521 int i;
522
523 for (i = 0; i < 12; ++i)
524 if (!strcasecmp(buf, Month[i]))
525 return 1;
526
527 return 0;
528 }
529
530 static void
531 ftpListPartsFree(ftpListParts ** parts)
532 {
533 safe_free((*parts)->date);
534 safe_free((*parts)->name);
535 safe_free((*parts)->showname);
536 safe_free((*parts)->link);
537 safe_free(*parts);
538 }
539
540 #define MAX_TOKENS 64
541
542 static ftpListParts *
543 ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags)
544 {
545 ftpListParts *p = NULL;
546 char *t = NULL;
547 const char *ct = NULL;
548 char *tokens[MAX_TOKENS];
549 int i;
550 int n_tokens;
551 static char tbuf[128];
552 char *xbuf = NULL;
553 static int scan_ftp_initialized = 0;
554 static regex_t scan_ftp_integer;
555 static regex_t scan_ftp_time;
556 static regex_t scan_ftp_dostime;
557 static regex_t scan_ftp_dosdate;
558
559 if (!scan_ftp_initialized) {
560 scan_ftp_initialized = 1;
561 regcomp(&scan_ftp_integer, "^[0123456789]+$", REG_EXTENDED | REG_NOSUB);
562 regcomp(&scan_ftp_time, "^[0123456789:]+$", REG_EXTENDED | REG_NOSUB);
563 regcomp(&scan_ftp_dosdate, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED | REG_NOSUB);
564 regcomp(&scan_ftp_dostime, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED | REG_NOSUB | REG_ICASE);
565 }
566
567 if (buf == NULL)
568 return NULL;
569
570 if (*buf == '\0')
571 return NULL;
572
573 p = (ftpListParts *)xcalloc(1, sizeof(ftpListParts));
574
575 n_tokens = 0;
576
577 memset(tokens, 0, sizeof(tokens));
578
579 xbuf = xstrdup(buf);
580
581 if (flags.tried_nlst) {
582 /* Machine readable format, one name per line */
583 p->name = xbuf;
584 p->type = '\0';
585 return p;
586 }
587
588 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) {
589 tokens[n_tokens] = xstrdup(t);
590 ++n_tokens;
591 }
592
593 xfree(xbuf);
594
595 /* locate the Month field */
596 for (i = 3; i < n_tokens - 2; ++i) {
597 char *size = tokens[i - 1];
598 char *month = tokens[i];
599 char *day = tokens[i + 1];
600 char *year = tokens[i + 2];
601
602 if (!is_month(month))
603 continue;
604
605 if (regexec(&scan_ftp_integer, size, 0, NULL, 0) != 0)
606 continue;
607
608 if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0)
609 continue;
610
611 if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
612 continue;
613
614 snprintf(tbuf, 128, "%s %2s %5s",
615 month, day, year);
616
617 if (!strstr(buf, tbuf))
618 snprintf(tbuf, 128, "%s %2s %-5s",
619 month, day, year);
620
621 char const *copyFrom = NULL;
622
623 if ((copyFrom = strstr(buf, tbuf))) {
624 p->type = *tokens[0];
625 p->size = strtoll(size, NULL, 10);
626 p->date = xstrdup(tbuf);
627
628 if (flags.skip_whitespace) {
629 copyFrom += strlen(tbuf);
630
631 while (strchr(w_space, *copyFrom))
632 ++copyFrom;
633 } else {
634 /* XXX assumes a single space between date and filename
635 * suggested by: Nathan.Bailey@cc.monash.edu.au and
636 * Mike Battersby <mike@starbug.bofh.asn.au> */
637 copyFrom += strlen(tbuf) + 1;
638 }
639
640 p->name = xstrdup(copyFrom);
641
642 if (p->type == 'l' && (t = strstr(p->name, " -> "))) {
643 *t = '\0';
644 p->link = xstrdup(t + 4);
645 }
646
647 goto found;
648 }
649
650 break;
651 }
652
653 /* try it as a DOS listing, 04-05-70 09:33PM ... */
654 if (n_tokens > 3 &&
655 regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 &&
656 regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) {
657 if (!strcasecmp(tokens[2], "<dir>")) {
658 p->type = 'd';
659 } else {
660 p->type = '-';
661 p->size = strtoll(tokens[2], NULL, 10);
662 }
663
664 snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]);
665 p->date = xstrdup(tbuf);
666
667 if (p->type == 'd') {
668 /* Directory.. name begins with first printable after <dir> */
669 ct = strstr(buf, tokens[2]);
670 ct += strlen(tokens[2]);
671
672 while (xisspace(*ct))
673 ++ct;
674
675 if (!*ct)
676 ct = NULL;
677 } else {
678 /* A file. Name begins after size, with a space in between */
679 snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]);
680 ct = strstr(buf, tbuf);
681
682 if (ct) {
683 ct += strlen(tokens[2]) + 2;
684 }
685 }
686
687 p->name = xstrdup(ct ? ct : tokens[3]);
688 goto found;
689 }
690
691 /* Try EPLF format; carson@lehman.com */
692 if (buf[0] == '+') {
693 ct = buf + 1;
694 p->type = 0;
695
696 while (ct && *ct) {
697 time_t tm;
698 int l = strcspn(ct, ",");
699 char *tmp;
700
701 if (l < 1)
702 goto blank;
703
704 switch (*ct) {
705
706 case '\t':
707 p->name = xstrndup(ct + 1, l + 1);
708 break;
709
710 case 's':
711 p->size = atoi(ct + 1);
712 break;
713
714 case 'm':
715 tm = (time_t) strtol(ct + 1, &tmp, 0);
716
717 if (tmp != ct + 1)
718 break; /* not a valid integer */
719
720 p->date = xstrdup(ctime(&tm));
721
722 *(strstr(p->date, "\n")) = '\0';
723
724 break;
725
726 case '/':
727 p->type = 'd';
728
729 break;
730
731 case 'r':
732 p->type = '-';
733
734 break;
735
736 case 'i':
737 break;
738
739 default:
740 break;
741 }
742
743 blank:
744 ct = strstr(ct, ",");
745
746 if (ct) {
747 ++ct;
748 }
749 }
750
751 if (p->type == 0) {
752 p->type = '-';
753 }
754
755 if (p->name)
756 goto found;
757 else
758 safe_free(p->date);
759 }
760
761 found:
762
763 for (i = 0; i < n_tokens; ++i)
764 xfree(tokens[i]);
765
766 if (!p->name)
767 ftpListPartsFree(&p); /* cleanup */
768
769 return p;
770 }
771
772 MemBuf *
773 Ftp::Gateway::htmlifyListEntry(const char *line)
774 {
775 char icon[2048];
776 char href[2048 + 40];
777 char text[ 2048];
778 char size[ 2048];
779 char chdir[ 2048 + 40];
780 char view[ 2048 + 40];
781 char download[ 2048 + 40];
782 char link[ 2048 + 40];
783 MemBuf *html;
784 char prefix[2048];
785 ftpListParts *parts;
786 *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
787
788 debugs(9, 7, HERE << " line ={" << line << "}");
789
790 if (strlen(line) > 1024) {
791 html = new MemBuf();
792 html->init();
793 html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
794 return html;
795 }
796
797 if (flags.dir_slash && dirpath && typecode != 'D')
798 snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath));
799 else
800 prefix[0] = '\0';
801
802 if ((parts = ftpListParseParts(line, flags)) == NULL) {
803 const char *p;
804
805 html = new MemBuf();
806 html->init();
807 html->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
808
809 for (p = line; *p && xisspace(*p); ++p);
810 if (*p && !xisspace(*p))
811 flags.listformat_unknown = 1;
812
813 return html;
814 }
815
816 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
817 ftpListPartsFree(&parts);
818 return NULL;
819 }
820
821 parts->size += 1023;
822 parts->size >>= 10;
823 parts->showname = xstrdup(parts->name);
824
825 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
826 xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
827
828 xstrncpy(text, parts->showname, 2048);
829
830 switch (parts->type) {
831
832 case 'd':
833 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
834 mimeGetIconURL("internal-dir"),
835 "[DIR]");
836 strcat(href, "/"); /* margin is allocated above */
837 break;
838
839 case 'l':
840 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
841 mimeGetIconURL("internal-link"),
842 "[LINK]");
843 /* sometimes there is an 'l' flag, but no "->" link */
844
845 if (parts->link) {
846 char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
847 snprintf(link, 2048, " -&gt; <a href=\"%s%s\">%s</a>",
848 *link2 != '/' ? prefix : "", link2,
849 html_quote(parts->link));
850 safe_free(link2);
851 }
852
853 break;
854
855 case '\0':
856 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
857 mimeGetIconURL(parts->name),
858 "[UNKNOWN]");
859 snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
860 "alt=\"[DIR]\"></a>",
861 rfc1738_escape_part(parts->name),
862 mimeGetIconURL("internal-dir"));
863 break;
864
865 case '-':
866
867 default:
868 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
869 mimeGetIconURL(parts->name),
870 "[FILE]");
871 snprintf(size, 2048, " %6" PRId64 "k", parts->size);
872 break;
873 }
874
875 if (parts->type != 'd') {
876 if (mimeGetViewOption(parts->name)) {
877 snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
878 "alt=\"[VIEW]\"></a>",
879 prefix, href, mimeGetIconURL("internal-view"));
880 }
881
882 if (mimeGetDownloadOption(parts->name)) {
883 snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
884 "alt=\"[DOWNLOAD]\"></a>",
885 prefix, href, mimeGetIconURL("internal-download"));
886 }
887 }
888
889 /* construct the table row from parts. */
890 html = new MemBuf();
891 html->init();
892 html->Printf("<tr class=\"entry\">"
893 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
894 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
895 "<td class=\"date\">%s</td>"
896 "<td class=\"size\">%s</td>"
897 "<td class=\"actions\">%s%s%s%s</td>"
898 "</tr>\n",
899 prefix, href, icon,
900 prefix, href, html_quote(text),
901 parts->date,
902 size,
903 chdir, view, download, link);
904
905 ftpListPartsFree(&parts);
906 return html;
907 }
908
909 void
910 Ftp::Gateway::parseListing()
911 {
912 char *buf = data.readBuf->content();
913 char *sbuf; /* NULL-terminated copy of termedBuf */
914 char *end;
915 char *line;
916 char *s;
917 MemBuf *t;
918 size_t linelen;
919 size_t usable;
920 size_t len = data.readBuf->contentSize();
921
922 if (!len) {
923 debugs(9, 3, HERE << "no content to parse for " << entry->url() );
924 return;
925 }
926
927 /*
928 * We need a NULL-terminated buffer for scanning, ick
929 */
930 sbuf = (char *)xmalloc(len + 1);
931 xstrncpy(sbuf, buf, len + 1);
932 end = sbuf + len - 1;
933
934 while (*end != '\r' && *end != '\n' && end > sbuf)
935 --end;
936
937 usable = end - sbuf;
938
939 debugs(9, 3, HERE << "usable = " << usable << " of " << len << " bytes.");
940
941 if (usable == 0) {
942 if (buf[0] == '\0' && len == 1) {
943 debugs(9, 3, HERE << "NIL ends data from " << entry->url() << " transfer problem?");
944 data.readBuf->consume(len);
945 } else {
946 debugs(9, 3, HERE << "didn't find end for " << entry->url());
947 debugs(9, 3, HERE << "buffer remains (" << len << " bytes) '" << rfc1738_do_escape(buf,0) << "'");
948 }
949 xfree(sbuf);
950 return;
951 }
952
953 debugs(9, 3, HERE << (unsigned long int)len << " bytes to play with");
954
955 line = (char *)memAllocate(MEM_4K_BUF);
956 ++end;
957 s = sbuf;
958 s += strspn(s, crlf);
959
960 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
961 debugs(9, 7, HERE << "s = {" << s << "}");
962 linelen = strcspn(s, crlf) + 1;
963
964 if (linelen < 2)
965 break;
966
967 if (linelen > 4096)
968 linelen = 4096;
969
970 xstrncpy(line, s, linelen);
971
972 debugs(9, 7, HERE << "{" << line << "}");
973
974 if (!strncmp(line, "total", 5))
975 continue;
976
977 t = htmlifyListEntry(line);
978
979 if ( t != NULL) {
980 debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
981 listing.append(t->content(), t->contentSize());
982 //leak? delete t;
983 }
984 }
985
986 debugs(9, 7, HERE << "Done.");
987 data.readBuf->consume(usable);
988 memFree(line, MEM_4K_BUF);
989 xfree(sbuf);
990 }
991
992 void
993 Ftp::Gateway::processReplyBody()
994 {
995 debugs(9, 3, status());
996
997 if (request->method == Http::METHOD_HEAD && (flags.isdir || theSize != -1)) {
998 serverComplete();
999 return;
1000 }
1001
1002 /* Directory listings are special. They write ther own headers via the error objects */
1003 if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
1004 appendSuccessHeader();
1005
1006 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1007 /*
1008 * probably was aborted because content length exceeds one
1009 * of the maximum size limits.
1010 */
1011 abortTransaction("entry aborted after calling appendSuccessHeader()");
1012 return;
1013 }
1014
1015 #if USE_ADAPTATION
1016
1017 if (adaptationAccessCheckPending) {
1018 debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
1019 return;
1020 }
1021
1022 #endif
1023
1024 if (flags.isdir) {
1025 if (!flags.listing) {
1026 flags.listing = 1;
1027 listing.reset();
1028 }
1029 parseListing();
1030 maybeReadVirginBody();
1031 return;
1032 } else if (const int csize = data.readBuf->contentSize()) {
1033 writeReplyBody(data.readBuf->content(), csize);
1034 debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
1035 data.readBuf->consume(csize);
1036 }
1037
1038 entry->flush();
1039
1040 maybeReadVirginBody();
1041 }
1042
1043 /**
1044 * Locates the FTP user:password login.
1045 *
1046 * Highest to lowest priority:
1047 * - Checks URL (ftp://user:pass@domain)
1048 * - Authorization: Basic header
1049 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1050 *
1051 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1052 *
1053 * TODO: we might be able to do something about locating username from other sources:
1054 * ie, external ACL user=* tag or ident lookup
1055 *
1056 \retval 1 if we have everything needed to complete this request.
1057 \retval 0 if something is missing.
1058 */
1059 int
1060 Ftp::Gateway::checkAuth(const HttpHeader * req_hdr)
1061 {
1062 /* default username */
1063 xstrncpy(user, "anonymous", MAX_URL);
1064
1065 #if HAVE_AUTH_MODULE_BASIC
1066 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1067 const char *auth;
1068 if ( (auth = req_hdr->getAuth(HDR_AUTHORIZATION, "Basic")) ) {
1069 flags.authenticated = 1;
1070 loginParser(auth, FTP_LOGIN_NOT_ESCAPED);
1071 }
1072 /* we fail with authorization-required error later IFF the FTP server requests it */
1073 #endif
1074
1075 /* Test URL login syntax. Overrides any headers received. */
1076 loginParser(request->login, FTP_LOGIN_ESCAPED);
1077
1078 /* name is missing. thats fatal. */
1079 if (!user[0])
1080 fatal("FTP login parsing destroyed username info");
1081
1082 /* name + password == success */
1083 if (password[0])
1084 return 1;
1085
1086 /* Setup default FTP password settings */
1087 /* this has to be done last so that we can have a no-password case above. */
1088 if (!password[0]) {
1089 if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
1090 xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
1091 flags.tried_auth_anonymous=1;
1092 return 1;
1093 } else if (!flags.tried_auth_nopass) {
1094 xstrncpy(password, null_string, MAX_URL);
1095 flags.tried_auth_nopass=1;
1096 return 1;
1097 }
1098 }
1099
1100 return 0; /* different username */
1101 }
1102
1103 static String str_type_eq;
1104 void
1105 Ftp::Gateway::checkUrlpath()
1106 {
1107 int l;
1108 size_t t;
1109
1110 if (str_type_eq.size()==0) //hack. String doesn't support global-static
1111 str_type_eq="type=";
1112
1113 if ((t = request->urlpath.rfind(';')) != String::npos) {
1114 if (request->urlpath.substr(t+1,t+1+str_type_eq.size())==str_type_eq) {
1115 typecode = (char)xtoupper(request->urlpath[t+str_type_eq.size()+1]);
1116 request->urlpath.cut(t);
1117 }
1118 }
1119
1120 l = request->urlpath.size();
1121 /* check for null path */
1122
1123 if (!l) {
1124 flags.isdir = 1;
1125 flags.root_dir = 1;
1126 flags.need_base_href = 1; /* Work around broken browsers */
1127 } else if (!request->urlpath.cmp("/%2f/")) {
1128 /* UNIX root directory */
1129 flags.isdir = 1;
1130 flags.root_dir = 1;
1131 } else if ((l >= 1) && (request->urlpath[l - 1] == '/')) {
1132 /* Directory URL, ending in / */
1133 flags.isdir = 1;
1134
1135 if (l == 1)
1136 flags.root_dir = 1;
1137 } else {
1138 flags.dir_slash = 1;
1139 }
1140 }
1141
1142 void
1143 Ftp::Gateway::buildTitleUrl()
1144 {
1145 title_url = "ftp://";
1146
1147 if (strcmp(user, "anonymous")) {
1148 title_url.append(user);
1149 title_url.append("@");
1150 }
1151
1152 title_url.append(request->GetHost());
1153
1154 if (request->port != urlDefaultPort(AnyP::PROTO_FTP)) {
1155 title_url.append(":");
1156 title_url.append(xitoa(request->port));
1157 }
1158
1159 title_url.append (request->urlpath);
1160
1161 base_href = "ftp://";
1162
1163 if (strcmp(user, "anonymous") != 0) {
1164 base_href.append(rfc1738_escape_part(user));
1165
1166 if (password_url) {
1167 base_href.append (":");
1168 base_href.append(rfc1738_escape_part(password));
1169 }
1170
1171 base_href.append("@");
1172 }
1173
1174 base_href.append(request->GetHost());
1175
1176 if (request->port != urlDefaultPort(AnyP::PROTO_FTP)) {
1177 base_href.append(":");
1178 base_href.append(xitoa(request->port));
1179 }
1180
1181 base_href.append(request->urlpath);
1182 base_href.append("/");
1183 }
1184
1185 void
1186 Ftp::Gateway::start()
1187 {
1188 if (!checkAuth(&request->header)) {
1189 /* create appropriate reply */
1190 HttpReply *reply = ftpAuthRequired(request, ftpRealm());
1191 entry->replaceHttpReply(reply);
1192 serverComplete();
1193 return;
1194 }
1195
1196 checkUrlpath();
1197 buildTitleUrl();
1198 debugs(9, 5, HERE << "FD " << ctrl.conn->fd << " : host=" << request->GetHost() <<
1199 ", path=" << request->urlpath << ", user=" << user << ", passwd=" << password);
1200 state = BEGIN;
1201 Ftp::Client::start();
1202 }
1203
1204 /* ====================================================================== */
1205
1206 void
1207 Ftp::Gateway::handleControlReply()
1208 {
1209 Ftp::Client::handleControlReply();
1210 if (ctrl.message == NULL)
1211 return; // didn't get complete reply yet
1212
1213 /* Copy the message except for the last line to cwd_message to be
1214 * printed in error messages.
1215 */
1216 for (wordlist *w = ctrl.message; w && w->next; w = w->next) {
1217 cwd_message.append('\n');
1218 cwd_message.append(w->key);
1219 }
1220
1221 FTP_SM_FUNCS[state] (this);
1222 }
1223
1224 /* ====================================================================== */
1225
1226 static void
1227 ftpReadWelcome(Ftp::Gateway * ftpState)
1228 {
1229 int code = ftpState->ctrl.replycode;
1230 debugs(9, 3, HERE);
1231
1232 if (ftpState->flags.pasv_only)
1233 ++ ftpState->login_att;
1234
1235 if (code == 220) {
1236 if (ftpState->ctrl.message) {
1237 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1238 ftpState->flags.skip_whitespace = 1;
1239 }
1240
1241 ftpSendUser(ftpState);
1242 } else if (code == 120) {
1243 if (NULL != ftpState->ctrl.message)
1244 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key);
1245
1246 return;
1247 } else {
1248 ftpFail(ftpState);
1249 }
1250 }
1251
1252 /**
1253 * Translate FTP login failure into HTTP error
1254 * this is an attmpt to get the 407 message to show up outside Squid.
1255 * its NOT a general failure. But a correct FTP response type.
1256 */
1257 void
1258 Ftp::Gateway::loginFailed()
1259 {
1260 ErrorState *err = NULL;
1261 const char *command, *reply;
1262
1263 if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) {
1264 if (ctrl.replycode == 421 || ctrl.replycode == 426) {
1265 // 421/426 - Service Overload - retry permitted.
1266 err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request);
1267 } else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) {
1268 // 43x - Invalid or Credential Error - retry challenge required.
1269 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
1270 } else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) {
1271 // 53x - Credentials Missing - retry challenge required
1272 if (password_url) // but they were in the URI! major fail.
1273 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request);
1274 else
1275 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
1276 }
1277 }
1278
1279 // any other problems are general falures.
1280 if (!err) {
1281 ftpFail(this);
1282 return;
1283 }
1284
1285 err->ftp.server_msg = ctrl.message;
1286
1287 ctrl.message = NULL;
1288
1289 if (old_request)
1290 command = old_request;
1291 else
1292 command = ctrl.last_command;
1293
1294 if (command && strncmp(command, "PASS", 4) == 0)
1295 command = "PASS <yourpassword>";
1296
1297 if (old_reply)
1298 reply = old_reply;
1299 else
1300 reply = ctrl.last_reply;
1301
1302 if (command)
1303 err->ftp.request = xstrdup(command);
1304
1305 if (reply)
1306 err->ftp.reply = xstrdup(reply);
1307
1308 HttpReply *newrep = err->BuildHttpReply();
1309 delete err;
1310
1311 #if HAVE_AUTH_MODULE_BASIC
1312 /* add Authenticate header */
1313 newrep->header.putAuth("Basic", ftpRealm());
1314 #endif
1315
1316 // add it to the store entry for response....
1317 entry->replaceHttpReply(newrep);
1318 serverComplete();
1319 }
1320
1321 const char *
1322 Ftp::Gateway::ftpRealm()
1323 {
1324 static char realm[8192];
1325
1326 /* This request is not fully authenticated */
1327 if (!request) {
1328 snprintf(realm, 8192, "FTP %s unknown", user);
1329 } else if (request->port == 21) {
1330 snprintf(realm, 8192, "FTP %s %s", user, request->GetHost());
1331 } else {
1332 snprintf(realm, 8192, "FTP %s %s port %d", user, request->GetHost(), request->port);
1333 }
1334 return realm;
1335 }
1336
1337 static void
1338 ftpSendUser(Ftp::Gateway * ftpState)
1339 {
1340 /* check the server control channel is still available */
1341 if (!ftpState || !ftpState->haveControlChannel("ftpSendUser"))
1342 return;
1343
1344 if (ftpState->proxy_host != NULL)
1345 snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n",
1346 ftpState->user,
1347 ftpState->request->GetHost());
1348 else
1349 snprintf(cbuf, CTRL_BUFLEN, "USER %s\r\n", ftpState->user);
1350
1351 ftpState->writeCommand(cbuf);
1352
1353 ftpState->state = Ftp::Client::SENT_USER;
1354 }
1355
1356 static void
1357 ftpReadUser(Ftp::Gateway * ftpState)
1358 {
1359 int code = ftpState->ctrl.replycode;
1360 debugs(9, 3, HERE);
1361
1362 if (code == 230) {
1363 ftpReadPass(ftpState);
1364 } else if (code == 331) {
1365 ftpSendPass(ftpState);
1366 } else {
1367 ftpState->loginFailed();
1368 }
1369 }
1370
1371 static void
1372 ftpSendPass(Ftp::Gateway * ftpState)
1373 {
1374 /* check the server control channel is still available */
1375 if (!ftpState || !ftpState->haveControlChannel("ftpSendPass"))
1376 return;
1377
1378 snprintf(cbuf, CTRL_BUFLEN, "PASS %s\r\n", ftpState->password);
1379 ftpState->writeCommand(cbuf);
1380 ftpState->state = Ftp::Client::SENT_PASS;
1381 }
1382
1383 static void
1384 ftpReadPass(Ftp::Gateway * ftpState)
1385 {
1386 int code = ftpState->ctrl.replycode;
1387 debugs(9, 3, HERE << "code=" << code);
1388
1389 if (code == 230) {
1390 ftpSendType(ftpState);
1391 } else {
1392 ftpState->loginFailed();
1393 }
1394 }
1395
1396 static void
1397 ftpSendType(Ftp::Gateway * ftpState)
1398 {
1399 const char *t;
1400 const char *filename;
1401 char mode;
1402
1403 /* check the server control channel is still available */
1404 if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
1405 return;
1406
1407 /*
1408 * Ref section 3.2.2 of RFC 1738
1409 */
1410 mode = ftpState->typecode;
1411
1412 switch (mode) {
1413
1414 case 'D':
1415 mode = 'A';
1416 break;
1417
1418 case 'A':
1419
1420 case 'I':
1421 break;
1422
1423 default:
1424
1425 if (ftpState->flags.isdir) {
1426 mode = 'A';
1427 } else {
1428 t = ftpState->request->urlpath.rpos('/');
1429 filename = t ? t + 1 : ftpState->request->urlpath.termedBuf();
1430 mode = mimeGetTransferMode(filename);
1431 }
1432
1433 break;
1434 }
1435
1436 if (mode == 'I')
1437 ftpState->flags.binary = 1;
1438 else
1439 ftpState->flags.binary = 0;
1440
1441 snprintf(cbuf, CTRL_BUFLEN, "TYPE %c\r\n", mode);
1442
1443 ftpState->writeCommand(cbuf);
1444
1445 ftpState->state = Ftp::Client::SENT_TYPE;
1446 }
1447
1448 static void
1449 ftpReadType(Ftp::Gateway * ftpState)
1450 {
1451 int code = ftpState->ctrl.replycode;
1452 char *path;
1453 char *d, *p;
1454 debugs(9, 3, HERE << "code=" << code);
1455
1456 if (code == 200) {
1457 p = path = xstrdup(ftpState->request->urlpath.termedBuf());
1458
1459 if (*p == '/')
1460 ++p;
1461
1462 while (*p) {
1463 d = p;
1464 p += strcspn(p, "/");
1465
1466 if (*p) {
1467 *p = '\0';
1468 ++p;
1469 }
1470
1471 rfc1738_unescape(d);
1472
1473 if (*d)
1474 wordlistAdd(&ftpState->pathcomps, d);
1475 }
1476
1477 xfree(path);
1478
1479 if (ftpState->pathcomps)
1480 ftpTraverseDirectory(ftpState);
1481 else
1482 ftpListDir(ftpState);
1483 } else {
1484 ftpFail(ftpState);
1485 }
1486 }
1487
1488 static void
1489 ftpTraverseDirectory(Ftp::Gateway * ftpState)
1490 {
1491 wordlist *w;
1492 debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>"));
1493
1494 safe_free(ftpState->dirpath);
1495 ftpState->dirpath = ftpState->filepath;
1496 ftpState->filepath = NULL;
1497
1498 /* Done? */
1499
1500 if (ftpState->pathcomps == NULL) {
1501 debugs(9, 3, HERE << "the final component was a directory");
1502 ftpListDir(ftpState);
1503 return;
1504 }
1505
1506 /* Go to next path component */
1507 w = ftpState->pathcomps;
1508
1509 ftpState->filepath = w->key;
1510
1511 ftpState->pathcomps = w->next;
1512
1513 delete w;
1514
1515 /* Check if we are to CWD or RETR */
1516 if (ftpState->pathcomps != NULL || ftpState->flags.isdir) {
1517 ftpSendCwd(ftpState);
1518 } else {
1519 debugs(9, 3, HERE << "final component is probably a file");
1520 ftpGetFile(ftpState);
1521 return;
1522 }
1523 }
1524
1525 static void
1526 ftpSendCwd(Ftp::Gateway * ftpState)
1527 {
1528 char *path = NULL;
1529
1530 /* check the server control channel is still available */
1531 if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd"))
1532 return;
1533
1534 debugs(9, 3, HERE);
1535
1536 path = ftpState->filepath;
1537
1538 if (!strcmp(path, "..") || !strcmp(path, "/")) {
1539 ftpState->flags.no_dotdot = 1;
1540 } else {
1541 ftpState->flags.no_dotdot = 0;
1542 }
1543
1544 snprintf(cbuf, CTRL_BUFLEN, "CWD %s\r\n", path);
1545
1546 ftpState->writeCommand(cbuf);
1547
1548 ftpState->state = Ftp::Client::SENT_CWD;
1549 }
1550
1551 static void
1552 ftpReadCwd(Ftp::Gateway * ftpState)
1553 {
1554 int code = ftpState->ctrl.replycode;
1555 debugs(9, 3, HERE);
1556
1557 if (code >= 200 && code < 300) {
1558 /* CWD OK */
1559 ftpState->unhack();
1560
1561 /* Reset cwd_message to only include the last message */
1562 ftpState->cwd_message.reset("");
1563 for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
1564 ftpState->cwd_message.append(' ');
1565 ftpState->cwd_message.append(w->key);
1566 }
1567 ftpState->ctrl.message = NULL;
1568
1569 /* Continue to traverse the path */
1570 ftpTraverseDirectory(ftpState);
1571 } else {
1572 /* CWD FAILED */
1573
1574 if (!ftpState->flags.put)
1575 ftpFail(ftpState);
1576 else
1577 ftpSendMkdir(ftpState);
1578 }
1579 }
1580
1581 static void
1582 ftpSendMkdir(Ftp::Gateway * ftpState)
1583 {
1584 char *path = NULL;
1585
1586 /* check the server control channel is still available */
1587 if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir"))
1588 return;
1589
1590 path = ftpState->filepath;
1591 debugs(9, 3, HERE << "with path=" << path);
1592 snprintf(cbuf, CTRL_BUFLEN, "MKD %s\r\n", path);
1593 ftpState->writeCommand(cbuf);
1594 ftpState->state = Ftp::Client::SENT_MKDIR;
1595 }
1596
1597 static void
1598 ftpReadMkdir(Ftp::Gateway * ftpState)
1599 {
1600 char *path = ftpState->filepath;
1601 int code = ftpState->ctrl.replycode;
1602
1603 debugs(9, 3, HERE << "path " << path << ", code " << code);
1604
1605 if (code == 257) { /* success */
1606 ftpSendCwd(ftpState);
1607 } else if (code == 550) { /* dir exists */
1608
1609 if (ftpState->flags.put_mkdir) {
1610 ftpState->flags.put_mkdir = 1;
1611 ftpSendCwd(ftpState);
1612 } else
1613 ftpSendReply(ftpState);
1614 } else
1615 ftpSendReply(ftpState);
1616 }
1617
1618 static void
1619 ftpGetFile(Ftp::Gateway * ftpState)
1620 {
1621 assert(*ftpState->filepath != '\0');
1622 ftpState->flags.isdir = 0;
1623 ftpSendMdtm(ftpState);
1624 }
1625
1626 static void
1627 ftpListDir(Ftp::Gateway * ftpState)
1628 {
1629 if (ftpState->flags.dir_slash) {
1630 debugs(9, 3, HERE << "Directory path did not end in /");
1631 ftpState->title_url.append("/");
1632 ftpState->flags.isdir = 1;
1633 }
1634
1635 ftpSendPassive(ftpState);
1636 }
1637
1638 static void
1639 ftpSendMdtm(Ftp::Gateway * ftpState)
1640 {
1641 /* check the server control channel is still available */
1642 if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm"))
1643 return;
1644
1645 assert(*ftpState->filepath != '\0');
1646 snprintf(cbuf, CTRL_BUFLEN, "MDTM %s\r\n", ftpState->filepath);
1647 ftpState->writeCommand(cbuf);
1648 ftpState->state = Ftp::Client::SENT_MDTM;
1649 }
1650
1651 static void
1652 ftpReadMdtm(Ftp::Gateway * ftpState)
1653 {
1654 int code = ftpState->ctrl.replycode;
1655 debugs(9, 3, HERE);
1656
1657 if (code == 213) {
1658 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
1659 ftpState->unhack();
1660 } else if (code < 0) {
1661 ftpFail(ftpState);
1662 return;
1663 }
1664
1665 ftpSendSize(ftpState);
1666 }
1667
1668 static void
1669 ftpSendSize(Ftp::Gateway * ftpState)
1670 {
1671 /* check the server control channel is still available */
1672 if (!ftpState || !ftpState->haveControlChannel("ftpSendSize"))
1673 return;
1674
1675 /* Only send SIZE for binary transfers. The returned size
1676 * is useless on ASCII transfers */
1677
1678 if (ftpState->flags.binary) {
1679 assert(ftpState->filepath != NULL);
1680 assert(*ftpState->filepath != '\0');
1681 snprintf(cbuf, CTRL_BUFLEN, "SIZE %s\r\n", ftpState->filepath);
1682 ftpState->writeCommand(cbuf);
1683 ftpState->state = Ftp::Client::SENT_SIZE;
1684 } else
1685 /* Skip to next state no non-binary transfers */
1686 ftpSendPassive(ftpState);
1687 }
1688
1689 static void
1690 ftpReadSize(Ftp::Gateway * ftpState)
1691 {
1692 int code = ftpState->ctrl.replycode;
1693 debugs(9, 3, HERE);
1694
1695 if (code == 213) {
1696 ftpState->unhack();
1697 ftpState->theSize = strtoll(ftpState->ctrl.last_reply, NULL, 10);
1698
1699 if (ftpState->theSize == 0) {
1700 debugs(9, 2, "SIZE reported " <<
1701 ftpState->ctrl.last_reply << " on " <<
1702 ftpState->title_url);
1703 ftpState->theSize = -1;
1704 }
1705 } else if (code < 0) {
1706 ftpFail(ftpState);
1707 return;
1708 }
1709
1710 ftpSendPassive(ftpState);
1711 }
1712
1713 static void
1714 ftpReadEPSV(Ftp::Gateway* ftpState)
1715 {
1716 Ip::Address srvAddr; // unused
1717 if (ftpState->handleEpsvReply(srvAddr)) {
1718 if (ftpState->ctrl.message == NULL)
1719 return; // didn't get complete reply yet
1720
1721 ftpState->connectDataChannel();
1722 }
1723 }
1724
1725 /** Send Passive connection request.
1726 * Default method is to use modern EPSV request.
1727 * The failover mechanism should check for previous state and re-call with alternates on failure.
1728 */
1729 static void
1730 ftpSendPassive(Ftp::Gateway * ftpState)
1731 {
1732 /** Checks the server control channel is still available before running. */
1733 if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
1734 return;
1735
1736 debugs(9, 3, HERE);
1737
1738 /** \par
1739 * Checks for 'HEAD' method request and passes off for special handling by Ftp::Gateway::processHeadResponse(). */
1740 if (ftpState->request->method == Http::METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
1741 ftpState->processHeadResponse(); // may call serverComplete
1742 return;
1743 }
1744
1745 if (ftpState->sendPassive()) {
1746 // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
1747 if (ftpState->state == Ftp::Client::SENT_EPSV_ALL)
1748 ftpState->flags.epsv_all_sent = true;
1749 }
1750 }
1751
1752 void
1753 Ftp::Gateway::processHeadResponse()
1754 {
1755 debugs(9, 5, HERE << "handling HEAD response");
1756 ftpSendQuit(this);
1757 appendSuccessHeader();
1758
1759 /*
1760 * On rare occasions I'm seeing the entry get aborted after
1761 * readControlReply() and before here, probably when
1762 * trying to write to the client.
1763 */
1764 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1765 abortTransaction("entry aborted while processing HEAD");
1766 return;
1767 }
1768
1769 #if USE_ADAPTATION
1770 if (adaptationAccessCheckPending) {
1771 debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
1772 return;
1773 }
1774 #endif
1775
1776 // processReplyBody calls serverComplete() since there is no body
1777 processReplyBody();
1778 }
1779
1780 static void
1781 ftpReadPasv(Ftp::Gateway * ftpState)
1782 {
1783 Ip::Address srvAddr; // unused
1784 if (ftpState->handlePasvReply(srvAddr))
1785 ftpState->connectDataChannel();
1786 else {
1787 ftpSendEPRT(ftpState);
1788 return;
1789 }
1790 }
1791
1792 void
1793 Ftp::Gateway::dataChannelConnected(const CommConnectCbParams &io)
1794 {
1795 debugs(9, 3, HERE);
1796 data.opener = NULL;
1797
1798 if (io.flag != Comm::OK) {
1799 debugs(9, 2, HERE << "Failed to connect. Retrying via another method.");
1800
1801 // ABORT on timeouts. server may be waiting on a broken TCP link.
1802 if (io.xerrno == Comm::TIMEOUT)
1803 writeCommand("ABOR");
1804
1805 // try another connection attempt with some other method
1806 ftpSendPassive(this);
1807 return;
1808 }
1809
1810 data.opened(io.conn, dataCloser());
1811 ftpRestOrList(this);
1812 }
1813
1814 static void
1815 ftpOpenListenSocket(Ftp::Gateway * ftpState, int fallback)
1816 {
1817 /// Close old data channels, if any. We may open a new one below.
1818 if (ftpState->data.conn != NULL) {
1819 if ((ftpState->data.conn->flags & COMM_REUSEADDR))
1820 // NP: in fact it points to the control channel. just clear it.
1821 ftpState->data.clear();
1822 else
1823 ftpState->data.close();
1824 }
1825 safe_free(ftpState->data.host);
1826
1827 /*
1828 * Set up a listen socket on the same local address as the
1829 * control connection.
1830 */
1831 Comm::ConnectionPointer temp = new Comm::Connection;
1832 temp->local = ftpState->ctrl.conn->local;
1833
1834 /*
1835 * REUSEADDR is needed in fallback mode, since the same port is
1836 * used for both control and data.
1837 */
1838 if (fallback) {
1839 int on = 1;
1840 setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
1841 ftpState->ctrl.conn->flags |= COMM_REUSEADDR;
1842 temp->flags |= COMM_REUSEADDR;
1843 } else {
1844 /* if not running in fallback mode a new port needs to be retrieved */
1845 temp->local.port(0);
1846 }
1847
1848 ftpState->listenForDataChannel(temp);
1849 }
1850
1851 static void
1852 ftpSendPORT(Ftp::Gateway * ftpState)
1853 {
1854 /* check the server control channel is still available */
1855 if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
1856 return;
1857
1858 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
1859 debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1860 return;
1861 }
1862
1863 debugs(9, 3, HERE);
1864 ftpState->flags.pasv_supported = 0;
1865 ftpOpenListenSocket(ftpState, 0);
1866
1867 if (!Comm::IsConnOpen(ftpState->data.listenConn)) {
1868 if ( ftpState->data.listenConn != NULL && !ftpState->data.listenConn->local.isIPv4() ) {
1869 /* non-IPv4 CANNOT send PORT command. */
1870 /* we got here by attempting and failing an EPRT */
1871 /* using the same reply code should simulate a PORT failure */
1872 ftpReadPORT(ftpState);
1873 return;
1874 }
1875
1876 /* XXX Need to set error message */
1877 ftpFail(ftpState);
1878 return;
1879 }
1880
1881 // pull out the internal IP address bytes to send in PORT command...
1882 // source them from the listen_conn->local
1883
1884 struct addrinfo *AI = NULL;
1885 ftpState->data.listenConn->local.getAddrInfo(AI, AF_INET);
1886 unsigned char *addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
1887 unsigned char *portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
1888 snprintf(cbuf, CTRL_BUFLEN, "PORT %d,%d,%d,%d,%d,%d\r\n",
1889 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
1890 portptr[0], portptr[1]);
1891 ftpState->writeCommand(cbuf);
1892 ftpState->state = Ftp::Client::SENT_PORT;
1893
1894 Ip::Address::FreeAddrInfo(AI);
1895 }
1896
1897 static void
1898 ftpReadPORT(Ftp::Gateway * ftpState)
1899 {
1900 int code = ftpState->ctrl.replycode;
1901 debugs(9, 3, HERE);
1902
1903 if (code != 200) {
1904 /* Fall back on using the same port as the control connection */
1905 debugs(9, 3, "PORT not supported by remote end");
1906 ftpOpenListenSocket(ftpState, 1);
1907 }
1908
1909 ftpRestOrList(ftpState);
1910 }
1911
1912 static void
1913 ftpSendEPRT(Ftp::Gateway * ftpState)
1914 {
1915 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
1916 debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
1917 return;
1918 }
1919
1920 if (!Config.Ftp.eprt) {
1921 /* Disabled. Switch immediately to attempting old PORT command. */
1922 debugs(9, 3, "EPRT disabled by local administrator");
1923 ftpSendPORT(ftpState);
1924 return;
1925 }
1926
1927 debugs(9, 3, HERE);
1928 ftpState->flags.pasv_supported = 0;
1929
1930 ftpOpenListenSocket(ftpState, 0);
1931 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState->data.conn);
1932 if (!Comm::IsConnOpen(ftpState->data.conn)) {
1933 /* XXX Need to set error message */
1934 ftpFail(ftpState);
1935 return;
1936 }
1937
1938 char buf[MAX_IPSTRLEN];
1939
1940 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
1941 /* Which can be used by EITHER protocol. */
1942 snprintf(cbuf, CTRL_BUFLEN, "EPRT |%d|%s|%d|\r\n",
1943 ( ftpState->data.listenConn->local.isIPv6() ? 2 : 1 ),
1944 ftpState->data.listenConn->local.toStr(buf,MAX_IPSTRLEN),
1945 ftpState->data.listenConn->local.port() );
1946
1947 ftpState->writeCommand(cbuf);
1948 ftpState->state = Ftp::Client::SENT_EPRT;
1949 }
1950
1951 static void
1952 ftpReadEPRT(Ftp::Gateway * ftpState)
1953 {
1954 int code = ftpState->ctrl.replycode;
1955 debugs(9, 3, HERE);
1956
1957 if (code != 200) {
1958 /* Failover to attempting old PORT command. */
1959 debugs(9, 3, "EPRT not supported by remote end");
1960 ftpSendPORT(ftpState);
1961 return;
1962 }
1963
1964 ftpRestOrList(ftpState);
1965 }
1966
1967 /** "read" handler to accept FTP data connections.
1968 *
1969 \param io comm accept(2) callback parameters
1970 */
1971 void
1972 Ftp::Gateway::ftpAcceptDataConnection(const CommAcceptCbParams &io)
1973 {
1974 debugs(9, 3, HERE);
1975
1976 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1977 abortTransaction("entry aborted when accepting data conn");
1978 data.listenConn->close();
1979 data.listenConn = NULL;
1980 return;
1981 }
1982
1983 if (io.flag != Comm::OK) {
1984 data.listenConn->close();
1985 data.listenConn = NULL;
1986 debugs(9, DBG_IMPORTANT, "FTP AcceptDataConnection: " << io.conn << ": " << xstrerr(io.xerrno));
1987 /** \todo Need to send error message on control channel*/
1988 ftpFail(this);
1989 return;
1990 }
1991
1992 /* data listening conn is no longer even open. abort. */
1993 if (!Comm::IsConnOpen(data.listenConn)) {
1994 data.listenConn = NULL; // ensure that it's cleared and not just closed.
1995 return;
1996 }
1997
1998 /* data listening conn is no longer even open. abort. */
1999 if (!Comm::IsConnOpen(data.conn)) {
2000 data.clear(); // ensure that it's cleared and not just closed.
2001 return;
2002 }
2003
2004 /** \par
2005 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2006 * made by the remote client which is connected to the FTP control socket.
2007 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
2008 * This prevents third-party hacks, but also third-party load balancing handshakes.
2009 */
2010 if (Config.Ftp.sanitycheck) {
2011 // accept if either our data or ctrl connection is talking to this remote peer.
2012 if (data.conn->remote != io.conn->remote && ctrl.conn->remote != io.conn->remote) {
2013 debugs(9, DBG_IMPORTANT,
2014 "FTP data connection from unexpected server (" <<
2015 io.conn->remote << "), expecting " <<
2016 data.conn->remote << " or " << ctrl.conn->remote);
2017
2018 /* close the bad sources connection down ASAP. */
2019 io.conn->close();
2020
2021 /* drop the bad connection (io) by ignoring the attempt. */
2022 return;
2023 }
2024 }
2025
2026 /** On Comm::OK start using the accepted data socket and discard the temporary listen socket. */
2027 data.close();
2028 data.opened(io.conn, dataCloser());
2029 data.addr(io.conn->remote);
2030
2031 debugs(9, 3, HERE << "Connected data socket on " <<
2032 io.conn << ". FD table says: " <<
2033 "ctrl-peer= " << fd_table[ctrl.conn->fd].ipaddr << ", " <<
2034 "data-peer= " << fd_table[data.conn->fd].ipaddr);
2035
2036 assert(haveControlChannel("ftpAcceptDataConnection"));
2037 assert(ctrl.message == NULL);
2038
2039 // Ctrl channel operations will determine what happens to this data connection
2040 }
2041
2042 static void
2043 ftpRestOrList(Ftp::Gateway * ftpState)
2044 {
2045 debugs(9, 3, HERE);
2046
2047 if (ftpState->typecode == 'D') {
2048 ftpState->flags.isdir = 1;
2049
2050 if (ftpState->flags.put) {
2051 ftpSendMkdir(ftpState); /* PUT name;type=d */
2052 } else {
2053 ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
2054 }
2055 } else if (ftpState->flags.put) {
2056 ftpSendStor(ftpState);
2057 } else if (ftpState->flags.isdir)
2058 ftpSendList(ftpState);
2059 else if (ftpState->restartable())
2060 ftpSendRest(ftpState);
2061 else
2062 ftpSendRetr(ftpState);
2063 }
2064
2065 static void
2066 ftpSendStor(Ftp::Gateway * ftpState)
2067 {
2068 /* check the server control channel is still available */
2069 if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
2070 return;
2071
2072 debugs(9, 3, HERE);
2073
2074 if (ftpState->filepath != NULL) {
2075 /* Plain file upload */
2076 snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath);
2077 ftpState->writeCommand(cbuf);
2078 ftpState->state = Ftp::Client::SENT_STOR;
2079 } else if (ftpState->request->header.getInt64(HDR_CONTENT_LENGTH) > 0) {
2080 /* File upload without a filename. use STOU to generate one */
2081 snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n");
2082 ftpState->writeCommand(cbuf);
2083 ftpState->state = Ftp::Client::SENT_STOR;
2084 } else {
2085 /* No file to transfer. Only create directories if needed */
2086 ftpSendReply(ftpState);
2087 }
2088 }
2089
2090 /// \deprecated use ftpState->readStor() instead.
2091 static void
2092 ftpReadStor(Ftp::Gateway * ftpState)
2093 {
2094 ftpState->readStor();
2095 }
2096
2097 void Ftp::Gateway::readStor()
2098 {
2099 int code = ctrl.replycode;
2100 debugs(9, 3, HERE);
2101
2102 if (code == 125 || (code == 150 && Comm::IsConnOpen(data.conn))) {
2103 if (!originalRequest()->body_pipe) {
2104 debugs(9, 3, "zero-size STOR?");
2105 state = WRITING_DATA; // make ftpWriteTransferDone() responsible
2106 dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2107 return;
2108 }
2109
2110 if (!startRequestBodyFlow()) { // register to receive body data
2111 ftpFail(this);
2112 return;
2113 }
2114
2115 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
2116 debugs(9, 3, HERE << "starting data transfer");
2117 switchTimeoutToDataChannel();
2118 sendMoreRequestBody();
2119 fwd->dontRetry(true); // dont permit re-trying if the body was sent.
2120 state = WRITING_DATA;
2121 debugs(9, 3, HERE << "writing data channel");
2122 } else if (code == 150) {
2123 /* When client code is 150 with no data channel, Accept data channel. */
2124 debugs(9, 3, "ftpReadStor: accepting data channel");
2125 listenForDataChannel(data.conn);
2126 } else {
2127 debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
2128 ftpFail(this);
2129 }
2130 }
2131
2132 static void
2133 ftpSendRest(Ftp::Gateway * ftpState)
2134 {
2135 /* check the server control channel is still available */
2136 if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
2137 return;
2138
2139 debugs(9, 3, HERE);
2140
2141 snprintf(cbuf, CTRL_BUFLEN, "REST %" PRId64 "\r\n", ftpState->restart_offset);
2142 ftpState->writeCommand(cbuf);
2143 ftpState->state = Ftp::Client::SENT_REST;
2144 }
2145
2146 int
2147 Ftp::Gateway::restartable()
2148 {
2149 if (restart_offset > 0)
2150 return 1;
2151
2152 if (!request->range)
2153 return 0;
2154
2155 if (!flags.binary)
2156 return 0;
2157
2158 if (theSize <= 0)
2159 return 0;
2160
2161 int64_t desired_offset = request->range->lowestOffset(theSize);
2162
2163 if (desired_offset <= 0)
2164 return 0;
2165
2166 if (desired_offset >= theSize)
2167 return 0;
2168
2169 restart_offset = desired_offset;
2170 return 1;
2171 }
2172
2173 static void
2174 ftpReadRest(Ftp::Gateway * ftpState)
2175 {
2176 int code = ftpState->ctrl.replycode;
2177 debugs(9, 3, HERE);
2178 assert(ftpState->restart_offset > 0);
2179
2180 if (code == 350) {
2181 ftpState->setCurrentOffset(ftpState->restart_offset);
2182 ftpSendRetr(ftpState);
2183 } else if (code > 0) {
2184 debugs(9, 3, HERE << "REST not supported");
2185 ftpState->flags.rest_supported = 0;
2186 ftpSendRetr(ftpState);
2187 } else {
2188 ftpFail(ftpState);
2189 }
2190 }
2191
2192 static void
2193 ftpSendList(Ftp::Gateway * ftpState)
2194 {
2195 /* check the server control channel is still available */
2196 if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
2197 return;
2198
2199 debugs(9, 3, HERE);
2200
2201 if (ftpState->filepath) {
2202 snprintf(cbuf, CTRL_BUFLEN, "LIST %s\r\n", ftpState->filepath);
2203 } else {
2204 snprintf(cbuf, CTRL_BUFLEN, "LIST\r\n");
2205 }
2206
2207 ftpState->writeCommand(cbuf);
2208 ftpState->state = Ftp::Client::SENT_LIST;
2209 }
2210
2211 static void
2212 ftpSendNlst(Ftp::Gateway * ftpState)
2213 {
2214 /* check the server control channel is still available */
2215 if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
2216 return;
2217
2218 debugs(9, 3, HERE);
2219
2220 ftpState->flags.tried_nlst = 1;
2221
2222 if (ftpState->filepath) {
2223 snprintf(cbuf, CTRL_BUFLEN, "NLST %s\r\n", ftpState->filepath);
2224 } else {
2225 snprintf(cbuf, CTRL_BUFLEN, "NLST\r\n");
2226 }
2227
2228 ftpState->writeCommand(cbuf);
2229 ftpState->state = Ftp::Client::SENT_NLST;
2230 }
2231
2232 static void
2233 ftpReadList(Ftp::Gateway * ftpState)
2234 {
2235 int code = ftpState->ctrl.replycode;
2236 debugs(9, 3, HERE);
2237
2238 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
2239 /* Begin data transfer */
2240 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2241 ftpState->switchTimeoutToDataChannel();
2242 ftpState->maybeReadVirginBody();
2243 ftpState->state = Ftp::Client::READING_DATA;
2244 return;
2245 } else if (code == 150) {
2246 /* Accept data channel */
2247 debugs(9, 3, HERE << "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2248 ftpState->listenForDataChannel(ftpState->data.conn);
2249 return;
2250 } else if (!ftpState->flags.tried_nlst && code > 300) {
2251 ftpSendNlst(ftpState);
2252 } else {
2253 ftpFail(ftpState);
2254 return;
2255 }
2256 }
2257
2258 static void
2259 ftpSendRetr(Ftp::Gateway * ftpState)
2260 {
2261 /* check the server control channel is still available */
2262 if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
2263 return;
2264
2265 debugs(9, 3, HERE);
2266
2267 assert(ftpState->filepath != NULL);
2268 snprintf(cbuf, CTRL_BUFLEN, "RETR %s\r\n", ftpState->filepath);
2269 ftpState->writeCommand(cbuf);
2270 ftpState->state = Ftp::Client::SENT_RETR;
2271 }
2272
2273 static void
2274 ftpReadRetr(Ftp::Gateway * ftpState)
2275 {
2276 int code = ftpState->ctrl.replycode;
2277 debugs(9, 3, HERE);
2278
2279 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
2280 /* Begin data transfer */
2281 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
2282 ftpState->switchTimeoutToDataChannel();
2283 ftpState->maybeReadVirginBody();
2284 ftpState->state = Ftp::Client::READING_DATA;
2285 } else if (code == 150) {
2286 /* Accept data channel */
2287 ftpState->listenForDataChannel(ftpState->data.conn);
2288 } else if (code >= 300) {
2289 if (!ftpState->flags.try_slash_hack) {
2290 /* Try this as a directory missing trailing slash... */
2291 ftpState->hackShortcut(ftpSendCwd);
2292 } else {
2293 ftpFail(ftpState);
2294 }
2295 } else {
2296 ftpFail(ftpState);
2297 }
2298 }
2299
2300 /**
2301 * Generate the HTTP headers and template fluff around an FTP
2302 * directory listing display.
2303 */
2304 void
2305 Ftp::Gateway::completedListing()
2306 {
2307 assert(entry);
2308 entry->lock("Ftp::Gateway");
2309 ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request);
2310 ferr.ftp.listing = &listing;
2311 ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
2312 ferr.ftp.server_msg = ctrl.message;
2313 ctrl.message = NULL;
2314 entry->replaceHttpReply( ferr.BuildHttpReply() );
2315 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
2316 entry->flush();
2317 entry->unlock("Ftp::Gateway");
2318 }
2319
2320 static void
2321 ftpReadTransferDone(Ftp::Gateway * ftpState)
2322 {
2323 int code = ftpState->ctrl.replycode;
2324 debugs(9, 3, HERE);
2325
2326 if (code == 226 || code == 250) {
2327 /* Connection closed; retrieval done. */
2328 if (ftpState->flags.listing) {
2329 ftpState->completedListing();
2330 /* QUIT operation handles sending the reply to client */
2331 }
2332 ftpSendQuit(ftpState);
2333 } else { /* != 226 */
2334 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
2335 ftpState->failed(ERR_FTP_FAILURE, 0);
2336 /* failed closes ctrl.conn and frees ftpState */
2337 return;
2338 }
2339 }
2340
2341 // premature end of the request body
2342 void
2343 Ftp::Gateway::handleRequestBodyProducerAborted()
2344 {
2345 ServerStateData::handleRequestBodyProducerAborted();
2346 debugs(9, 3, HERE << "ftpState=" << this);
2347 failed(ERR_READ_ERROR, 0);
2348 }
2349
2350 static void
2351 ftpWriteTransferDone(Ftp::Gateway * ftpState)
2352 {
2353 int code = ftpState->ctrl.replycode;
2354 debugs(9, 3, HERE);
2355
2356 if (!(code == 226 || code == 250)) {
2357 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data");
2358 ftpState->failed(ERR_FTP_PUT_ERROR, 0);
2359 return;
2360 }
2361
2362 ftpState->entry->timestampsSet(); /* XXX Is this needed? */
2363 ftpSendReply(ftpState);
2364 }
2365
2366 static void
2367 ftpSendQuit(Ftp::Gateway * ftpState)
2368 {
2369 /* check the server control channel is still available */
2370 if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
2371 return;
2372
2373 snprintf(cbuf, CTRL_BUFLEN, "QUIT\r\n");
2374 ftpState->writeCommand(cbuf);
2375 ftpState->state = Ftp::Client::SENT_QUIT;
2376 }
2377
2378 /** Completes a client FTP operation with success or other page
2379 * generated and stored in the entry field by the code issuing QUIT.
2380 */
2381 static void
2382 ftpReadQuit(Ftp::Gateway * ftpState)
2383 {
2384 ftpState->serverComplete();
2385 }
2386
2387 static void
2388 ftpTrySlashHack(Ftp::Gateway * ftpState)
2389 {
2390 char *path;
2391 ftpState->flags.try_slash_hack = 1;
2392 /* Free old paths */
2393
2394 debugs(9, 3, HERE);
2395
2396 if (ftpState->pathcomps)
2397 wordlistDestroy(&ftpState->pathcomps);
2398
2399 safe_free(ftpState->filepath);
2400
2401 /* Build the new path (urlpath begins with /) */
2402 path = xstrdup(ftpState->request->urlpath.termedBuf());
2403
2404 rfc1738_unescape(path);
2405
2406 ftpState->filepath = path;
2407
2408 /* And off we go */
2409 ftpGetFile(ftpState);
2410 }
2411
2412 /**
2413 * Forget hack status. Next error is shown to the user
2414 */
2415 void
2416 Ftp::Gateway::unhack()
2417 {
2418 debugs(9, 3, HERE);
2419
2420 if (old_request != NULL) {
2421 safe_free(old_request);
2422 safe_free(old_reply);
2423 }
2424 }
2425
2426 void
2427 Ftp::Gateway::hackShortcut(FTPSM * nextState)
2428 {
2429 /* Clear some unwanted state */
2430 setCurrentOffset(0);
2431 restart_offset = 0;
2432 /* Save old error message & some state info */
2433
2434 debugs(9, 3, HERE);
2435
2436 if (old_request == NULL) {
2437 old_request = ctrl.last_command;
2438 ctrl.last_command = NULL;
2439 old_reply = ctrl.last_reply;
2440 ctrl.last_reply = NULL;
2441
2442 if (pathcomps == NULL && filepath != NULL)
2443 old_filepath = xstrdup(filepath);
2444 }
2445
2446 /* Jump to the "hack" state */
2447 nextState(this);
2448 }
2449
2450 static void
2451 ftpFail(Ftp::Gateway *ftpState)
2452 {
2453 debugs(9, 6, HERE << "flags(" <<
2454 (ftpState->flags.isdir?"IS_DIR,":"") <<
2455 (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
2456 "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
2457 "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
2458
2459 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2460 if (!ftpState->flags.isdir && /* Not a directory */
2461 !ftpState->flags.try_slash_hack && /* Not in slash hack */
2462 ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
2463 ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
2464
2465 switch (ftpState->state) {
2466
2467 case Ftp::Client::SENT_CWD:
2468
2469 case Ftp::Client::SENT_RETR:
2470 /* Try the / hack */
2471 ftpState->hackShortcut(ftpTrySlashHack);
2472 return;
2473
2474 default:
2475 break;
2476 }
2477 }
2478
2479 ftpState->failed(ERR_NONE, 0);
2480 /* failed() closes ctrl.conn and frees this */
2481 }
2482
2483 Http::StatusCode
2484 Ftp::Gateway::failedHttpStatus(err_type &error)
2485 {
2486 if (error == ERR_NONE) {
2487 switch (state) {
2488
2489 case SENT_USER:
2490
2491 case SENT_PASS:
2492
2493 if (ctrl.replycode > 500) {
2494 error = ERR_FTP_FORBIDDEN;
2495 return password_url ? Http::scForbidden : Http::scUnauthorized;
2496 } else if (ctrl.replycode == 421) {
2497 error = ERR_FTP_UNAVAILABLE;
2498 return Http::scServiceUnavailable;
2499 }
2500 break;
2501
2502 case SENT_CWD:
2503
2504 case SENT_RETR:
2505 if (ctrl.replycode == 550) {
2506 error = ERR_FTP_NOT_FOUND;
2507 return Http::scNotFound;
2508 }
2509 break;
2510
2511 default:
2512 break;
2513 }
2514 }
2515 return Ftp::Client::failedHttpStatus(error);
2516 }
2517
2518 static void
2519 ftpSendReply(Ftp::Gateway * ftpState)
2520 {
2521 int code = ftpState->ctrl.replycode;
2522 Http::StatusCode http_code;
2523 err_type err_code = ERR_NONE;
2524
2525 debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code);
2526
2527 if (cbdataReferenceValid(ftpState))
2528 debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!");
2529
2530 if (code == 226 || code == 250) {
2531 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
2532 http_code = (ftpState->mdtm > 0) ? Http::scAccepted : Http::scCreated;
2533 } else if (code == 227) {
2534 err_code = ERR_FTP_PUT_CREATED;
2535 http_code = Http::scCreated;
2536 } else {
2537 err_code = ERR_FTP_PUT_ERROR;
2538 http_code = Http::scInternalServerError;
2539 }
2540
2541 ErrorState err(err_code, http_code, ftpState->request);
2542
2543 if (ftpState->old_request)
2544 err.ftp.request = xstrdup(ftpState->old_request);
2545 else
2546 err.ftp.request = xstrdup(ftpState->ctrl.last_command);
2547
2548 if (ftpState->old_reply)
2549 err.ftp.reply = xstrdup(ftpState->old_reply);
2550 else if (ftpState->ctrl.last_reply)
2551 err.ftp.reply = xstrdup(ftpState->ctrl.last_reply);
2552 else
2553 err.ftp.reply = xstrdup("");
2554
2555 // TODO: interpret as FTP-specific error code
2556 err.detailError(code);
2557
2558 ftpState->entry->replaceHttpReply( err.BuildHttpReply() );
2559
2560 ftpSendQuit(ftpState);
2561 }
2562
2563 void
2564 Ftp::Gateway::appendSuccessHeader()
2565 {
2566 const char *mime_type = NULL;
2567 const char *mime_enc = NULL;
2568 String urlpath = request->urlpath;
2569 const char *filename = NULL;
2570 const char *t = NULL;
2571
2572 debugs(9, 3, HERE);
2573
2574 if (flags.http_header_sent)
2575 return;
2576
2577 HttpReply *reply = new HttpReply;
2578
2579 flags.http_header_sent = 1;
2580
2581 assert(entry->isEmpty());
2582
2583 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
2584
2585 entry->buffer(); /* released when done processing current data payload */
2586
2587 filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
2588
2589 if (flags.isdir) {
2590 mime_type = "text/html";
2591 } else {
2592 switch (typecode) {
2593
2594 case 'I':
2595 mime_type = "application/octet-stream";
2596 mime_enc = mimeGetContentEncoding(filename);
2597 break;
2598
2599 case 'A':
2600 mime_type = "text/plain";
2601 break;
2602
2603 default:
2604 mime_type = mimeGetContentType(filename);
2605 mime_enc = mimeGetContentEncoding(filename);
2606 break;
2607 }
2608 }
2609
2610 /* set standard stuff */
2611
2612 if (0 == getCurrentOffset()) {
2613 /* Full reply */
2614 reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
2615 } else if (theSize < getCurrentOffset()) {
2616 /*
2617 * DPW 2007-05-04
2618 * offset should not be larger than theSize. We should
2619 * not be seeing this condition any more because we'll only
2620 * send REST if we know the theSize and if it is less than theSize.
2621 */
2622 debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
2623 " current offset=" << getCurrentOffset() <<
2624 ", but theSize=" << theSize <<
2625 ". assuming full content response");
2626 reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
2627 } else {
2628 /* Partial reply */
2629 HttpHdrRangeSpec range_spec;
2630 range_spec.offset = getCurrentOffset();
2631 range_spec.length = theSize - getCurrentOffset();
2632 reply->setHeaders(Http::scPartialContent, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
2633 httpHeaderAddContRange(&reply->header, range_spec, theSize);
2634 }
2635
2636 /* additional info */
2637 if (mime_enc)
2638 reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
2639
2640 setVirginReply(reply);
2641 adaptOrFinalizeReply();
2642 }
2643
2644 void
2645 Ftp::Gateway::haveParsedReplyHeaders()
2646 {
2647 ServerStateData::haveParsedReplyHeaders();
2648
2649 StoreEntry *e = entry;
2650
2651 e->timestampsSet();
2652
2653 if (flags.authenticated) {
2654 /*
2655 * Authenticated requests can't be cached.
2656 */
2657 e->release();
2658 } else if (!EBIT_TEST(e->flags, RELEASE_REQUEST) && !getCurrentOffset()) {
2659 e->setPublicKey();
2660 } else {
2661 e->release();
2662 }
2663 }
2664
2665 HttpReply *
2666 Ftp::Gateway::ftpAuthRequired(HttpRequest * request, const char *realm)
2667 {
2668 ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request);
2669 HttpReply *newrep = err.BuildHttpReply();
2670 #if HAVE_AUTH_MODULE_BASIC
2671 /* add Authenticate header */
2672 newrep->header.putAuth("Basic", realm);
2673 #endif
2674 return newrep;
2675 }
2676
2677 const char *
2678 Ftp::UrlWith2f(HttpRequest * request)
2679 {
2680 String newbuf = "%2f";
2681
2682 if (request->url.getScheme() != AnyP::PROTO_FTP)
2683 return NULL;
2684
2685 if ( request->urlpath[0]=='/' ) {
2686 newbuf.append(request->urlpath);
2687 request->urlpath.absorb(newbuf);
2688 safe_free(request->canonical);
2689 } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
2690 newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
2691 request->urlpath.absorb(newbuf);
2692 safe_free(request->canonical);
2693 }
2694
2695 return urlCanonical(request);
2696 }
2697
2698 void
2699 Ftp::Gateway::printfReplyBody(const char *fmt, ...)
2700 {
2701 va_list args;
2702 va_start (args, fmt);
2703 static char buf[4096];
2704 buf[0] = '\0';
2705 vsnprintf(buf, 4096, fmt, args);
2706 writeReplyBody(buf, strlen(buf));
2707 va_end(args);
2708 }
2709
2710 /**
2711 * Call this when there is data from the origin server
2712 * which should be sent to either StoreEntry, or to ICAP...
2713 */
2714 void
2715 Ftp::Gateway::writeReplyBody(const char *dataToWrite, size_t dataLength)
2716 {
2717 debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
2718 addVirginReplyBody(dataToWrite, dataLength);
2719 }
2720
2721 /**
2722 * A hack to ensure we do not double-complete on the forward entry.
2723 *
2724 \todo Ftp::Gateway logic should probably be rewritten to avoid
2725 * double-completion or FwdState should be rewritten to allow it.
2726 */
2727 void
2728 Ftp::Gateway::completeForwarding()
2729 {
2730 if (fwd == NULL || flags.completed_forwarding) {
2731 debugs(9, 3, HERE << "completeForwarding avoids " <<
2732 "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd <<
2733 ", this " << this << ", fwd " << fwd);
2734 return;
2735 }
2736
2737 flags.completed_forwarding = true;
2738 ServerStateData::completeForwarding();
2739 }
2740
2741 /**
2742 * Have we lost the FTP server control channel?
2743 *
2744 \retval true The server control channel is available.
2745 \retval false The server control channel is not available.
2746 */
2747 bool
2748 Ftp::Gateway::haveControlChannel(const char *caller_name) const
2749 {
2750 if (doneWithServer())
2751 return false;
2752
2753 /* doneWithServer() only checks BOTH channels are closed. */
2754 if (!Comm::IsConnOpen(ctrl.conn)) {
2755 debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
2756 debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
2757 return false;
2758 }
2759
2760 return true;
2761 }
2762
2763 bool
2764 Ftp::Gateway::mayReadVirginReplyBody() const
2765 {
2766 // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2767 return !doneWithServer();
2768 }
2769
2770 AsyncJob::Pointer
2771 Ftp::StartGateway(FwdState *const fwdState)
2772 {
2773 return AsyncJob::Start(new Ftp::Gateway(fwdState));
2774 }