]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ftp.cc
Add source-maintenance.sh to perform automated Maintenance Updates
[thirdparty/squid.git] / src / ftp.cc
1 /*
2 * $Id$
3 *
4 * DEBUG: section 09 File Transfer Protocol (FTP)
5 * AUTHOR: Harvest Derived
6 *
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 *
33 */
34
35 #include "squid.h"
36 #include "comm.h"
37 #include "comm/ListenStateData.h"
38 #include "compat/strtoll.h"
39 #include "ConnectionDetail.h"
40 #include "errorpage.h"
41 #include "fde.h"
42 #include "forward.h"
43 #include "HttpHdrContRange.h"
44 #include "HttpHeaderRange.h"
45 #include "HttpHeader.h"
46 #include "HttpRequest.h"
47 #include "HttpReply.h"
48 #include "MemBuf.h"
49 #include "rfc1738.h"
50 #include "Server.h"
51 #include "SquidString.h"
52 #include "SquidTime.h"
53 #include "Store.h"
54 #include "URLScheme.h"
55 #include "wordlist.h"
56
57 #if DELAY_POOLS
58 #include "DelayPools.h"
59 #include "MemObject.h"
60 #endif
61
62 /**
63 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
64 \ingroup ServerProtocolFTPAPI
65 */
66
67 /// \ingroup ServerProtocolFTPInternal
68 static const char *const crlf = "\r\n";
69
70 /// \ingroup ServerProtocolFTPInternal
71 static char cbuf[1024];
72
73 /// \ingroup ServerProtocolFTPInternal
74 typedef enum {
75 BEGIN,
76 SENT_USER,
77 SENT_PASS,
78 SENT_TYPE,
79 SENT_MDTM,
80 SENT_SIZE,
81 SENT_EPRT,
82 SENT_PORT,
83 SENT_EPSV_ALL,
84 SENT_EPSV_1,
85 SENT_EPSV_2,
86 SENT_PASV,
87 SENT_CWD,
88 SENT_LIST,
89 SENT_NLST,
90 SENT_REST,
91 SENT_RETR,
92 SENT_STOR,
93 SENT_QUIT,
94 READING_DATA,
95 WRITING_DATA,
96 SENT_MKDIR
97 } ftp_state_t;
98
99 /// \ingroup ServerProtocolFTPInternal
100 struct _ftp_flags {
101
102 /* passive mode */
103 bool pasv_supported; ///< PASV command is allowed
104 bool epsv_all_sent; ///< EPSV ALL has been used. Must abort on failures.
105 bool pasv_only;
106
107 /* authentication */
108 bool authenticated; ///< authentication success
109 bool tried_auth_anonymous; ///< auth has tried to use anonymous credentials already.
110 bool tried_auth_nopass; ///< auth tried username with no password already.
111
112 /* other */
113 bool isdir;
114 bool skip_whitespace;
115 bool rest_supported;
116 bool http_header_sent;
117 bool tried_nlst;
118 bool need_base_href;
119 bool dir_slash;
120 bool root_dir;
121 bool no_dotdot;
122 bool binary;
123 bool try_slash_hack;
124 bool put;
125 bool put_mkdir;
126 bool listformat_unknown;
127 bool listing;
128 bool completed_forwarding;
129 };
130
131 class FtpStateData;
132
133 /// \ingroup ServerProtocolFTPInternal
134 typedef void (FTPSM) (FtpStateData *);
135
136 /// common code for FTP control and data channels
137 // does not own the channel descriptor, which is managed by FtpStateData
138 class FtpChannel
139 {
140 public:
141 FtpChannel(): fd(-1) {}
142
143 /// called after the socket is opened, sets up close handler
144 void opened(int aFd, const AsyncCall::Pointer &aCloser);
145
146 /** Handles all operations needed to properly close the active channel FD.
147 * clearing the close handler, clearing the listen socket properly, and calling comm_close
148 */
149 void close();
150
151 void clear(); /// just resets fd and close handler. does not close active connections.
152
153 int fd; /// channel descriptor; \todo: remove because the closer has it
154
155 /** Current listening socket handler. delete on shutdown or abort.
156 * FTP stores a copy of the FD in the field fd above.
157 * Use close() to properly close the channel.
158 */
159 Comm::ListenStateData *listener;
160
161 private:
162 AsyncCall::Pointer closer; /// Comm close handler callback
163 };
164
165 /// \ingroup ServerProtocolFTPInternal
166 class FtpStateData : public ServerStateData
167 {
168
169 public:
170 void *operator new (size_t);
171 void operator delete (void *);
172 void *toCbdata() { return this; }
173
174 FtpStateData(FwdState *);
175 ~FtpStateData();
176 char user[MAX_URL];
177 char password[MAX_URL];
178 int password_url;
179 char *reply_hdr;
180 int reply_hdr_state;
181 String clean_url;
182 String title_url;
183 String base_href;
184 int conn_att;
185 int login_att;
186 ftp_state_t state;
187 time_t mdtm;
188 int64_t theSize;
189 wordlist *pathcomps;
190 char *filepath;
191 char *dirpath;
192 int64_t restart_offset;
193 char *proxy_host;
194 size_t list_width;
195 String cwd_message;
196 char *old_request;
197 char *old_reply;
198 char *old_filepath;
199 char typecode;
200 MemBuf listing; ///< FTP directory listing in HTML format.
201
202 // \todo: optimize ctrl and data structs member order, to minimize size
203 /// FTP control channel info; the channel is opened once per transaction
204 struct CtrlChannel: public FtpChannel {
205 char *buf;
206 size_t size;
207 size_t offset;
208 wordlist *message;
209 char *last_command;
210 char *last_reply;
211 int replycode;
212 } ctrl;
213
214 /// FTP data channel info; the channel may be opened/closed a few times
215 struct DataChannel: public FtpChannel {
216 MemBuf *readBuf;
217 char *host;
218 u_short port;
219 bool read_pending;
220 } data;
221
222 struct _ftp_flags flags;
223
224 private:
225 CBDATA_CLASS(FtpStateData);
226
227 public:
228 // these should all be private
229 void start();
230 void loginParser(const char *, int escaped);
231 int restartable();
232 void appendSuccessHeader();
233 void hackShortcut(FTPSM * nextState);
234 void failed(err_type, int xerrno);
235 void failedErrorMessage(err_type, int xerrno);
236 void unhack();
237 void scheduleReadControlReply(int);
238 void handleControlReply();
239 void readStor();
240 void parseListing();
241 MemBuf *htmlifyListEntry(const char *line);
242 void completedListing(void);
243 void dataComplete();
244 void dataRead(const CommIoCbParams &io);
245 int checkAuth(const HttpHeader * req_hdr);
246 void checkUrlpath();
247 void buildTitleUrl();
248 void writeReplyBody(const char *, size_t len);
249 void printfReplyBody(const char *fmt, ...);
250 virtual int dataDescriptor() const;
251 virtual void maybeReadVirginBody();
252 virtual void closeServer();
253 virtual void completeForwarding();
254 virtual void abortTransaction(const char *reason);
255 void processHeadResponse();
256 void processReplyBody();
257 void writeCommand(const char *buf);
258 void setCurrentOffset(int64_t offset) { currentOffset = offset; }
259 int64_t getCurrentOffset() const { return currentOffset; }
260
261 static CNCB ftpPasvCallback;
262 static PF ftpDataWrite;
263 void ftpTimeout(const CommTimeoutCbParams &io);
264 void ctrlClosed(const CommCloseCbParams &io);
265 void dataClosed(const CommCloseCbParams &io);
266 void ftpReadControlReply(const CommIoCbParams &io);
267 void ftpWriteCommandCallback(const CommIoCbParams &io);
268 void ftpAcceptDataConnection(const CommAcceptCbParams &io);
269
270 static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
271 const char *ftpRealm(void);
272 void loginFailed(void);
273 static wordlist *ftpParseControlReply(char *, size_t, int *, size_t *);
274
275 // sending of the request body to the server
276 virtual void sentRequestBody(const CommIoCbParams&);
277 virtual void doneSendingRequestBody();
278
279 virtual void haveParsedReplyHeaders();
280
281 virtual bool doneWithServer() const;
282 virtual bool haveControlChannel(const char *caller_name) const;
283 AsyncCall::Pointer dataCloser(); /// creates a Comm close callback
284
285 private:
286 // BodyConsumer for HTTP: consume request body.
287 virtual void handleRequestBodyProducerAborted();
288 };
289
290 CBDATA_CLASS_INIT(FtpStateData);
291
292 void *
293 FtpStateData::operator new (size_t)
294 {
295 CBDATA_INIT_TYPE(FtpStateData);
296 FtpStateData *result = cbdataAlloc(FtpStateData);
297 return result;
298 }
299
300 void
301 FtpStateData::operator delete (void *address)
302 {
303 FtpStateData *t = static_cast<FtpStateData *>(address);
304 cbdataFree(t);
305 }
306
307 /// \ingroup ServerProtocolFTPInternal
308 typedef struct {
309 char type;
310 int64_t size;
311 char *date;
312 char *name;
313 char *showname;
314 char *link;
315 } ftpListParts;
316
317 /// \ingroup ServerProtocolFTPInternal
318 #define FTP_LOGIN_ESCAPED 1
319
320 /// \ingroup ServerProtocolFTPInternal
321 #define FTP_LOGIN_NOT_ESCAPED 0
322
323 /*
324 * State machine functions
325 * send == state transition
326 * read == wait for response, and select next state transition
327 * other == Transition logic
328 */
329 static FTPSM ftpReadWelcome;
330 static FTPSM ftpSendUser;
331 static FTPSM ftpReadUser;
332 static FTPSM ftpSendPass;
333 static FTPSM ftpReadPass;
334 static FTPSM ftpSendType;
335 static FTPSM ftpReadType;
336 static FTPSM ftpSendMdtm;
337 static FTPSM ftpReadMdtm;
338 static FTPSM ftpSendSize;
339 static FTPSM ftpReadSize;
340 static FTPSM ftpSendEPRT;
341 static FTPSM ftpReadEPRT;
342 static FTPSM ftpSendPORT;
343 static FTPSM ftpReadPORT;
344 static FTPSM ftpSendPassive;
345 static FTPSM ftpReadEPSV;
346 static FTPSM ftpReadPasv;
347 static FTPSM ftpTraverseDirectory;
348 static FTPSM ftpListDir;
349 static FTPSM ftpGetFile;
350 static FTPSM ftpSendCwd;
351 static FTPSM ftpReadCwd;
352 static FTPSM ftpRestOrList;
353 static FTPSM ftpSendList;
354 static FTPSM ftpSendNlst;
355 static FTPSM ftpReadList;
356 static FTPSM ftpSendRest;
357 static FTPSM ftpReadRest;
358 static FTPSM ftpSendRetr;
359 static FTPSM ftpReadRetr;
360 static FTPSM ftpReadTransferDone;
361 static FTPSM ftpSendStor;
362 static FTPSM ftpReadStor;
363 static FTPSM ftpWriteTransferDone;
364 static FTPSM ftpSendReply;
365 static FTPSM ftpSendMkdir;
366 static FTPSM ftpReadMkdir;
367 static FTPSM ftpFail;
368 static FTPSM ftpSendQuit;
369 static FTPSM ftpReadQuit;
370
371 /************************************************
372 ** Debugs Levels used here **
373 *************************************************
374 0 CRITICAL Events
375 1 IMPORTANT Events
376 Protocol and Transmission failures.
377 2 FTP Protocol Chatter
378 3 Logic Flows
379 4 Data Parsing Flows
380 5 Data Dumps
381 7 ??
382 ************************************************/
383
384 /************************************************
385 ** State Machine Description (excluding hacks) **
386 *************************************************
387 From To
388 ---------------------------------------
389 Welcome User
390 User Pass
391 Pass Type
392 Type TraverseDirectory / GetFile
393 TraverseDirectory Cwd / GetFile / ListDir
394 Cwd TraverseDirectory / Mkdir
395 GetFile Mdtm
396 Mdtm Size
397 Size Epsv
398 ListDir Epsv
399 Epsv FileOrList
400 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
401 Rest Retr
402 Retr / Nlst / List DataRead* (on datachannel)
403 DataRead* ReadTransferDone
404 ReadTransferDone DataTransferDone
405 Stor DataWrite* (on datachannel)
406 DataWrite* RequestPutBody** (from client)
407 RequestPutBody** DataWrite* / WriteTransferDone
408 WriteTransferDone DataTransferDone
409 DataTransferDone Quit
410 Quit -
411 ************************************************/
412
413 /// \ingroup ServerProtocolFTPInternal
414 FTPSM *FTP_SM_FUNCS[] = {
415 ftpReadWelcome, /* BEGIN */
416 ftpReadUser, /* SENT_USER */
417 ftpReadPass, /* SENT_PASS */
418 ftpReadType, /* SENT_TYPE */
419 ftpReadMdtm, /* SENT_MDTM */
420 ftpReadSize, /* SENT_SIZE */
421 ftpReadEPRT, /* SENT_EPRT */
422 ftpReadPORT, /* SENT_PORT */
423 ftpReadEPSV, /* SENT_EPSV_ALL */
424 ftpReadEPSV, /* SENT_EPSV_1 */
425 ftpReadEPSV, /* SENT_EPSV_2 */
426 ftpReadPasv, /* SENT_PASV */
427 ftpReadCwd, /* SENT_CWD */
428 ftpReadList, /* SENT_LIST */
429 ftpReadList, /* SENT_NLST */
430 ftpReadRest, /* SENT_REST */
431 ftpReadRetr, /* SENT_RETR */
432 ftpReadStor, /* SENT_STOR */
433 ftpReadQuit, /* SENT_QUIT */
434 ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
435 ftpWriteTransferDone, /* WRITING_DATA (STOR) */
436 ftpReadMkdir /* SENT_MKDIR */
437 };
438
439 /// handler called by Comm when FTP control channel is closed unexpectedly
440 void
441 FtpStateData::ctrlClosed(const CommCloseCbParams &io)
442 {
443 ctrl.clear();
444 deleteThis("FtpStateData::ctrlClosed");
445 }
446
447 /// handler called by Comm when FTP data channel is closed unexpectedly
448 void
449 FtpStateData::dataClosed(const CommCloseCbParams &io)
450 {
451 if (data.listener) {
452 delete data.listener;
453 data.listener = NULL;
454 data.fd = -1;
455 }
456 data.clear();
457 failed(ERR_FTP_FAILURE, 0);
458 /* failed closes ctrl.fd and frees ftpState */
459
460 /* NP: failure recovery may be possible when its only a data.fd failure.
461 * is the ctrl.fd is still fine, we can send ABOR down it and retry.
462 * Just need to watch out for wider Squid states like shutting down or reconfigure.
463 */
464 }
465
466 FtpStateData::FtpStateData(FwdState *theFwdState) : AsyncJob("FtpStateData"), ServerStateData(theFwdState)
467 {
468 const char *url = entry->url();
469 debugs(9, 3, HERE << "'" << url << "'" );
470 statCounter.server.all.requests++;
471 statCounter.server.ftp.requests++;
472 theSize = -1;
473 mdtm = -1;
474
475 if (Config.Ftp.passive && !theFwdState->ftpPasvFailed())
476 flags.pasv_supported = 1;
477
478 flags.rest_supported = 1;
479
480 typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
481 AsyncCall::Pointer closer = asyncCall(9, 5, "FtpStateData::ctrlClosed",
482 Dialer(this, &FtpStateData::ctrlClosed));
483 ctrl.opened(theFwdState->server_fd, closer);
484
485 if (request->method == METHOD_PUT)
486 flags.put = 1;
487 }
488
489 FtpStateData::~FtpStateData()
490 {
491 debugs(9, 3, HERE << entry->url() );
492
493 if (reply_hdr) {
494 memFree(reply_hdr, MEM_8K_BUF);
495 reply_hdr = NULL;
496 }
497
498 data.close();
499
500 if (ctrl.fd >= 0) {
501 debugs(9, DBG_IMPORTANT, HERE << "Internal bug: FtpStateData left " <<
502 "control FD " << ctrl.fd << " open");
503 }
504
505 if (ctrl.buf) {
506 memFreeBuf(ctrl.size, ctrl.buf);
507 ctrl.buf = NULL;
508 }
509
510 if (data.readBuf) {
511 if (!data.readBuf->isNull())
512 data.readBuf->clean();
513
514 delete data.readBuf;
515 }
516
517 if (pathcomps)
518 wordlistDestroy(&pathcomps);
519
520 if (ctrl.message)
521 wordlistDestroy(&ctrl.message);
522
523 cwd_message.clean();
524
525 safe_free(ctrl.last_reply);
526
527 safe_free(ctrl.last_command);
528
529 safe_free(old_request);
530
531 safe_free(old_reply);
532
533 safe_free(old_filepath);
534
535 title_url.clean();
536
537 base_href.clean();
538
539 safe_free(filepath);
540
541 safe_free(dirpath);
542
543 safe_free(data.host);
544
545 fwd = NULL; // refcounted
546 }
547
548 /**
549 * Parse a possible login username:password pair.
550 * Produces filled member variables user, password, password_url if anything found.
551 */
552 void
553 FtpStateData::loginParser(const char *login, int escaped)
554 {
555 const char *u = NULL; // end of the username sub-string
556 int len; // length of the current sub-string to handle.
557
558 int total_len = strlen(login);
559
560 debugs(9, 4, HERE << ": login='" << login << "', escaped=" << escaped);
561 debugs(9, 9, HERE << ": IN : login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
562
563 if ((u = strchr(login, ':'))) {
564
565 /* if there was a username part */
566 if (u > login) {
567 len = u - login;
568 ++u; // jump off the delimiter.
569 if (len > MAX_URL)
570 len = MAX_URL-1;
571 xstrncpy(user, login, len +1);
572 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<"), escaped=" << escaped);
573 if (escaped)
574 rfc1738_unescape(user);
575 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<") unescaped.");
576 }
577
578 /* if there was a password part */
579 len = login + total_len - u;
580 if ( len > 0) {
581 if (len > MAX_URL)
582 len = MAX_URL -1;
583 xstrncpy(password, u, len +1);
584 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<"), escaped=" << escaped);
585 if (escaped) {
586 rfc1738_unescape(password);
587 password_url = 1;
588 }
589 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<") unescaped.");
590 }
591 } else if (login[0]) {
592 /* no password, just username */
593 if (total_len > MAX_URL)
594 total_len = MAX_URL -1;
595 xstrncpy(user, login, total_len +1);
596 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<"), escaped=" << escaped);
597 if (escaped)
598 rfc1738_unescape(user);
599 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<") unescaped.");
600 }
601
602 debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
603 }
604
605 void
606 FtpStateData::ftpTimeout(const CommTimeoutCbParams &io)
607 {
608 debugs(9, 4, "ftpTimeout: FD " << io.fd << ": '" << entry->url() << "'" );
609
610 if (SENT_PASV == state && io.fd == data.fd) {
611 /* stupid ftp.netscape.com */
612 fwd->dontRetry(false);
613 fwd->ftpPasvFailed(true);
614 debugs(9, DBG_IMPORTANT, "ftpTimeout: timeout in SENT_PASV state" );
615 }
616
617 failed(ERR_READ_TIMEOUT, 0);
618 /* failed() closes ctrl.fd and frees ftpState */
619 }
620
621 #if DEAD_CODE // obsoleted by ERR_DIR_LISTING
622 void
623 FtpStateData::listingFinish()
624 {
625 // TODO: figure out what this means and how to show it ...
626
627 if (flags.listformat_unknown && !flags.tried_nlst) {
628 printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
629 flags.dir_slash ? rfc1738_escape_part(old_filepath) : ".");
630 } else if (typecode == 'D') {
631 const char *path = flags.dir_slash ? filepath : ".";
632 printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path));
633 }
634 }
635 #endif /* DEAD_CODE */
636
637 /// \ingroup ServerProtocolFTPInternal
638 static const char *Month[] = {
639 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
640 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
641 };
642
643 /// \ingroup ServerProtocolFTPInternal
644 static int
645 is_month(const char *buf)
646 {
647 int i;
648
649 for (i = 0; i < 12; i++)
650 if (!strcasecmp(buf, Month[i]))
651 return 1;
652
653 return 0;
654 }
655
656 /// \ingroup ServerProtocolFTPInternal
657 static void
658 ftpListPartsFree(ftpListParts ** parts)
659 {
660 safe_free((*parts)->date);
661 safe_free((*parts)->name);
662 safe_free((*parts)->showname);
663 safe_free((*parts)->link);
664 safe_free(*parts);
665 }
666
667 /// \ingroup ServerProtocolFTPInternal
668 #define MAX_TOKENS 64
669
670 /// \ingroup ServerProtocolFTPInternal
671 static ftpListParts *
672 ftpListParseParts(const char *buf, struct _ftp_flags flags)
673 {
674 ftpListParts *p = NULL;
675 char *t = NULL;
676 const char *ct = NULL;
677 char *tokens[MAX_TOKENS];
678 int i;
679 int n_tokens;
680 static char tbuf[128];
681 char *xbuf = NULL;
682 static int scan_ftp_initialized = 0;
683 static regex_t scan_ftp_integer;
684 static regex_t scan_ftp_time;
685 static regex_t scan_ftp_dostime;
686 static regex_t scan_ftp_dosdate;
687
688 if (!scan_ftp_initialized) {
689 scan_ftp_initialized = 1;
690 regcomp(&scan_ftp_integer, "^[0123456789]+$", REG_EXTENDED | REG_NOSUB);
691 regcomp(&scan_ftp_time, "^[0123456789:]+$", REG_EXTENDED | REG_NOSUB);
692 regcomp(&scan_ftp_dosdate, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED | REG_NOSUB);
693 regcomp(&scan_ftp_dostime, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED | REG_NOSUB | REG_ICASE);
694 }
695
696 if (buf == NULL)
697 return NULL;
698
699 if (*buf == '\0')
700 return NULL;
701
702 p = (ftpListParts *)xcalloc(1, sizeof(ftpListParts));
703
704 n_tokens = 0;
705
706 memset(tokens, 0, sizeof(tokens));
707
708 xbuf = xstrdup(buf);
709
710 if (flags.tried_nlst) {
711 /* Machine readable format, one name per line */
712 p->name = xbuf;
713 p->type = '\0';
714 return p;
715 }
716
717 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
718 tokens[n_tokens++] = xstrdup(t);
719
720 xfree(xbuf);
721
722 /* locate the Month field */
723 for (i = 3; i < n_tokens - 2; i++) {
724 char *size = tokens[i - 1];
725 char *month = tokens[i];
726 char *day = tokens[i + 1];
727 char *year = tokens[i + 2];
728
729 if (!is_month(month))
730 continue;
731
732 if (regexec(&scan_ftp_integer, size, 0, NULL, 0) != 0)
733 continue;
734
735 if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0)
736 continue;
737
738 if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
739 continue;
740
741 snprintf(tbuf, 128, "%s %2s %5s",
742 month, day, year);
743
744 if (!strstr(buf, tbuf))
745 snprintf(tbuf, 128, "%s %2s %-5s",
746 month, day, year);
747
748 char const *copyFrom = NULL;
749
750 if ((copyFrom = strstr(buf, tbuf))) {
751 p->type = *tokens[0];
752 p->size = strtoll(size, NULL, 10);
753 p->date = xstrdup(tbuf);
754
755 if (flags.skip_whitespace) {
756 copyFrom += strlen(tbuf);
757
758 while (strchr(w_space, *copyFrom))
759 copyFrom++;
760 } else {
761 /* XXX assumes a single space between date and filename
762 * suggested by: Nathan.Bailey@cc.monash.edu.au and
763 * Mike Battersby <mike@starbug.bofh.asn.au> */
764 copyFrom += strlen(tbuf) + 1;
765 }
766
767 p->name = xstrdup(copyFrom);
768
769 if (p->type == 'l' && (t = strstr(p->name, " -> "))) {
770 *t = '\0';
771 p->link = xstrdup(t + 4);
772 }
773
774 goto found;
775 }
776
777 break;
778 }
779
780 /* try it as a DOS listing, 04-05-70 09:33PM ... */
781 if (n_tokens > 3 &&
782 regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 &&
783 regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) {
784 if (!strcasecmp(tokens[2], "<dir>")) {
785 p->type = 'd';
786 } else {
787 p->type = '-';
788 p->size = strtoll(tokens[2], NULL, 10);
789 }
790
791 snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]);
792 p->date = xstrdup(tbuf);
793
794 if (p->type == 'd') {
795 /* Directory.. name begins with first printable after <dir> */
796 ct = strstr(buf, tokens[2]);
797 ct += strlen(tokens[2]);
798
799 while (xisspace(*ct))
800 ct++;
801
802 if (!*ct)
803 ct = NULL;
804 } else {
805 /* A file. Name begins after size, with a space in between */
806 snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]);
807 ct = strstr(buf, tbuf);
808
809 if (ct) {
810 ct += strlen(tokens[2]) + 2;
811 }
812 }
813
814 p->name = xstrdup(ct ? ct : tokens[3]);
815 goto found;
816 }
817
818 /* Try EPLF format; carson@lehman.com */
819 if (buf[0] == '+') {
820 ct = buf + 1;
821 p->type = 0;
822
823 while (ct && *ct) {
824 time_t tm;
825 int l = strcspn(ct, ",");
826 char *tmp;
827
828 if (l < 1)
829 goto blank;
830
831 switch (*ct) {
832
833 case '\t':
834 p->name = xstrndup(ct + 1, l + 1);
835 break;
836
837 case 's':
838 p->size = atoi(ct + 1);
839 break;
840
841 case 'm':
842 tm = (time_t) strtol(ct + 1, &tmp, 0);
843
844 if (tmp != ct + 1)
845 break; /* not a valid integer */
846
847 p->date = xstrdup(ctime(&tm));
848
849 *(strstr(p->date, "\n")) = '\0';
850
851 break;
852
853 case '/':
854 p->type = 'd';
855
856 break;
857
858 case 'r':
859 p->type = '-';
860
861 break;
862
863 case 'i':
864 break;
865
866 default:
867 break;
868 }
869
870 blank:
871 ct = strstr(ct, ",");
872
873 if (ct) {
874 ct++;
875 }
876 }
877
878 if (p->type == 0) {
879 p->type = '-';
880 }
881
882 if (p->name)
883 goto found;
884 else
885 safe_free(p->date);
886 }
887
888 found:
889
890 for (i = 0; i < n_tokens; i++)
891 xfree(tokens[i]);
892
893 if (!p->name)
894 ftpListPartsFree(&p); /* cleanup */
895
896 return p;
897 }
898
899 MemBuf *
900 FtpStateData::htmlifyListEntry(const char *line)
901 {
902 char icon[2048];
903 char href[2048 + 40];
904 char text[ 2048];
905 char size[ 2048];
906 char chdir[ 2048 + 40];
907 char view[ 2048 + 40];
908 char download[ 2048 + 40];
909 char link[ 2048 + 40];
910 MemBuf *html;
911 char prefix[2048];
912 ftpListParts *parts;
913 *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
914
915 debugs(9, 7, HERE << " line ={" << line << "}");
916
917 if (strlen(line) > 1024) {
918 html = new MemBuf();
919 html->init();
920 html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
921 return html;
922 }
923
924 if (flags.dir_slash && dirpath && typecode != 'D')
925 snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath));
926 else
927 prefix[0] = '\0';
928
929 if ((parts = ftpListParseParts(line, flags)) == NULL) {
930 const char *p;
931
932 html = new MemBuf();
933 html->init();
934 html->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
935
936 for (p = line; *p && xisspace(*p); p++);
937 if (*p && !xisspace(*p))
938 flags.listformat_unknown = 1;
939
940 return html;
941 }
942
943 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
944 ftpListPartsFree(&parts);
945 return NULL;
946 }
947
948 parts->size += 1023;
949 parts->size >>= 10;
950 parts->showname = xstrdup(parts->name);
951
952 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
953 xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
954
955 xstrncpy(text, parts->showname, 2048);
956
957 switch (parts->type) {
958
959 case 'd':
960 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
961 mimeGetIconURL("internal-dir"),
962 "[DIR]");
963 strcat(href, "/"); /* margin is allocated above */
964 break;
965
966 case 'l':
967 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
968 mimeGetIconURL("internal-link"),
969 "[LINK]");
970 /* sometimes there is an 'l' flag, but no "->" link */
971
972 if (parts->link) {
973 char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
974 snprintf(link, 2048, " -&gt; <a href=\"%s%s\">%s</a>",
975 *link2 != '/' ? prefix : "", link2,
976 html_quote(parts->link));
977 safe_free(link2);
978 }
979
980 break;
981
982 case '\0':
983 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
984 mimeGetIconURL(parts->name),
985 "[UNKNOWN]");
986 snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
987 "alt=\"[DIR]\"></a>",
988 rfc1738_escape_part(parts->name),
989 mimeGetIconURL("internal-dir"));
990 break;
991
992 case '-':
993
994 default:
995 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
996 mimeGetIconURL(parts->name),
997 "[FILE]");
998 snprintf(size, 2048, " %6"PRId64"k", parts->size);
999 break;
1000 }
1001
1002 if (parts->type != 'd') {
1003 if (mimeGetViewOption(parts->name)) {
1004 snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1005 "alt=\"[VIEW]\"></a>",
1006 prefix, href, mimeGetIconURL("internal-view"));
1007 }
1008
1009 if (mimeGetDownloadOption(parts->name)) {
1010 snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1011 "alt=\"[DOWNLOAD]\"></a>",
1012 prefix, href, mimeGetIconURL("internal-download"));
1013 }
1014 }
1015
1016 /* construct the table row from parts. */
1017 html = new MemBuf();
1018 html->init();
1019 html->Printf("<tr class=\"entry\">"
1020 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1021 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1022 "<td class=\"date\">%s</td>"
1023 "<td class=\"size\">%s</td>"
1024 "<td class=\"actions\">%s%s%s%s</td>"
1025 "</tr>\n",
1026 prefix, href, icon,
1027 prefix, href, html_quote(text),
1028 parts->date,
1029 size,
1030 chdir, view, download, link);
1031
1032 ftpListPartsFree(&parts);
1033 return html;
1034 }
1035
1036 void
1037 FtpStateData::parseListing()
1038 {
1039 char *buf = data.readBuf->content();
1040 char *sbuf; /* NULL-terminated copy of termedBuf */
1041 char *end;
1042 char *line;
1043 char *s;
1044 MemBuf *t;
1045 size_t linelen;
1046 size_t usable;
1047 size_t len = data.readBuf->contentSize();
1048
1049 if (!len) {
1050 debugs(9, 3, HERE << "no content to parse for " << entry->url() );
1051 return;
1052 }
1053
1054 /*
1055 * We need a NULL-terminated buffer for scanning, ick
1056 */
1057 sbuf = (char *)xmalloc(len + 1);
1058 xstrncpy(sbuf, buf, len + 1);
1059 end = sbuf + len - 1;
1060
1061 while (*end != '\r' && *end != '\n' && end > sbuf)
1062 end--;
1063
1064 usable = end - sbuf;
1065
1066 debugs(9, 3, HERE << "usable = " << usable);
1067
1068 if (usable == 0) {
1069 debugs(9, 3, HERE << "didn't find end for " << entry->url() );
1070 xfree(sbuf);
1071 return;
1072 }
1073
1074 debugs(9, 3, HERE << (unsigned long int)len << " bytes to play with");
1075
1076 line = (char *)memAllocate(MEM_4K_BUF);
1077 end++;
1078 s = sbuf;
1079 s += strspn(s, crlf);
1080
1081 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
1082 debugs(9, 7, HERE << "s = {" << s << "}");
1083 linelen = strcspn(s, crlf) + 1;
1084
1085 if (linelen < 2)
1086 break;
1087
1088 if (linelen > 4096)
1089 linelen = 4096;
1090
1091 xstrncpy(line, s, linelen);
1092
1093 debugs(9, 7, HERE << "{" << line << "}");
1094
1095 if (!strncmp(line, "total", 5))
1096 continue;
1097
1098 t = htmlifyListEntry(line);
1099
1100 if ( t != NULL) {
1101 debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
1102 listing.append(t->content(), t->contentSize());
1103 //leak? delete t;
1104 }
1105 }
1106
1107 debugs(9, 7, HERE << "Done.");
1108 data.readBuf->consume(usable);
1109 memFree(line, MEM_4K_BUF);
1110 xfree(sbuf);
1111 }
1112
1113 int
1114 FtpStateData::dataDescriptor() const
1115 {
1116 return data.fd;
1117 }
1118
1119 void
1120 FtpStateData::dataComplete()
1121 {
1122 debugs(9, 3,HERE);
1123
1124 /* Connection closed; transfer done. */
1125
1126 /// Close data channel, if any, to conserve resources while we wait.
1127 data.close();
1128
1129 /* expect the "transfer complete" message on the control socket */
1130 /*
1131 * DPW 2007-04-23
1132 * Previously, this was the only place where we set the
1133 * 'buffered_ok' flag when calling scheduleReadControlReply().
1134 * It caused some problems if the FTP server returns an unexpected
1135 * status code after the data command. FtpStateData was being
1136 * deleted in the middle of dataRead().
1137 */
1138 scheduleReadControlReply(0);
1139 }
1140
1141 void
1142 FtpStateData::maybeReadVirginBody()
1143 {
1144 if (data.fd < 0)
1145 return;
1146
1147 if (data.read_pending)
1148 return;
1149
1150 const int read_sz = replyBodySpace(*data.readBuf, 0);
1151
1152 debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
1153
1154 if (read_sz < 2) // see http.cc
1155 return;
1156
1157 data.read_pending = true;
1158
1159 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
1160 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
1161 TimeoutDialer(this,&FtpStateData::ftpTimeout));
1162 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
1163
1164 debugs(9,5,HERE << "queueing read on FD " << data.fd);
1165
1166 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
1167 entry->delayAwareRead(data.fd, data.readBuf->space(), read_sz,
1168 asyncCall(9, 5, "FtpStateData::dataRead",
1169 Dialer(this, &FtpStateData::dataRead)));
1170 }
1171
1172 void
1173 FtpStateData::dataRead(const CommIoCbParams &io)
1174 {
1175 int j;
1176 int bin;
1177
1178 data.read_pending = false;
1179
1180 debugs(9, 3, HERE << "ftpDataRead: FD " << io.fd << " Read " << io.size << " bytes");
1181
1182 if (io.size > 0) {
1183 kb_incr(&statCounter.server.all.kbytes_in, io.size);
1184 kb_incr(&statCounter.server.ftp.kbytes_in, io.size);
1185 }
1186
1187 if (io.flag == COMM_ERR_CLOSING)
1188 return;
1189
1190 assert(io.fd == data.fd);
1191
1192 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1193 abortTransaction("entry aborted during dataRead");
1194 return;
1195 }
1196
1197 if (io.flag == COMM_OK && io.size > 0) {
1198 debugs(9,5,HERE << "appended " << io.size << " bytes to readBuf");
1199 data.readBuf->appended(io.size);
1200 #if DELAY_POOLS
1201 DelayId delayId = entry->mem_obj->mostBytesAllowed();
1202 delayId.bytesIn(io.size);
1203 #endif
1204 IOStats.Ftp.reads++;
1205
1206 for (j = io.size - 1, bin = 0; j; bin++)
1207 j >>= 1;
1208
1209 IOStats.Ftp.read_hist[bin]++;
1210 }
1211
1212 if (io.flag != COMM_OK || io.size < 0) {
1213 debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
1214 "ftpDataRead: read error: " << xstrerr(io.xerrno));
1215
1216 if (ignoreErrno(io.xerrno)) {
1217 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
1218 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
1219 TimeoutDialer(this,&FtpStateData::ftpTimeout));
1220 commSetTimeout(io.fd, Config.Timeout.read, timeoutCall);
1221
1222 maybeReadVirginBody();
1223 } else {
1224 if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported && !flags.listing) {
1225 fwd->dontRetry(false); /* this is a retryable error */
1226 fwd->ftpPasvFailed(true);
1227 }
1228
1229 failed(ERR_READ_ERROR, 0);
1230 /* failed closes ctrl.fd and frees ftpState */
1231 return;
1232 }
1233 } else if (io.size == 0) {
1234 debugs(9,3, HERE << "Calling dataComplete() because io.size == 0");
1235 /*
1236 * DPW 2007-04-23
1237 * Dangerous curves ahead. This call to dataComplete was
1238 * calling scheduleReadControlReply, handleControlReply,
1239 * and then ftpReadTransferDone. If ftpReadTransferDone
1240 * gets unexpected status code, it closes down the control
1241 * socket and our FtpStateData object gets destroyed. As
1242 * a workaround we no longer set the 'buffered_ok' flag in
1243 * the scheduleReadControlReply call.
1244 */
1245 dataComplete();
1246 }
1247
1248 processReplyBody();
1249 }
1250
1251 void
1252 FtpStateData::processReplyBody()
1253 {
1254 debugs(9, 3, HERE << "FtpStateData::processReplyBody starting.");
1255
1256 if (request->method == METHOD_HEAD && (flags.isdir || theSize != -1)) {
1257 serverComplete();
1258 return;
1259 }
1260
1261 /* Directory listings are special. They write ther own headers via the error objects */
1262 if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
1263 appendSuccessHeader();
1264
1265 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1266 /*
1267 * probably was aborted because content length exceeds one
1268 * of the maximum size limits.
1269 */
1270 abortTransaction("entry aborted after calling appendSuccessHeader()");
1271 return;
1272 }
1273
1274 #if USE_ADAPTATION
1275
1276 if (adaptationAccessCheckPending) {
1277 debugs(9,3, HERE << "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
1278 return;
1279 }
1280
1281 #endif
1282
1283 if (flags.isdir) {
1284 if (!flags.listing) {
1285 flags.listing = 1;
1286 listing.reset();
1287 }
1288 parseListing();
1289 maybeReadVirginBody();
1290 return;
1291 } else if (const int csize = data.readBuf->contentSize()) {
1292 writeReplyBody(data.readBuf->content(), csize);
1293 debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
1294 data.readBuf->consume(csize);
1295 }
1296
1297 entry->flush();
1298
1299 maybeReadVirginBody();
1300 }
1301
1302 /**
1303 * Locates the FTP user:password login.
1304 *
1305 * Highest to lowest priority:
1306 * - Checks URL (ftp://user:pass@domain)
1307 * - Authorization: Basic header
1308 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1309 *
1310 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1311 *
1312 * TODO: we might be able to do something about locating username from other sources:
1313 * ie, external ACL user=* tag or ident lookup
1314 *
1315 \retval 1 if we have everything needed to complete this request.
1316 \retval 0 if something is missing.
1317 */
1318 int
1319 FtpStateData::checkAuth(const HttpHeader * req_hdr)
1320 {
1321 const char *auth;
1322
1323 /* default username */
1324 xstrncpy(user, "anonymous", MAX_URL);
1325
1326 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1327 if ( (auth = req_hdr->getAuth(HDR_AUTHORIZATION, "Basic")) ) {
1328 flags.authenticated = 1;
1329 loginParser(auth, FTP_LOGIN_NOT_ESCAPED);
1330 }
1331 /* we fail with authorization-required error later IFF the FTP server requests it */
1332
1333 /* Test URL login syntax. Overrides any headers received. */
1334 loginParser(request->login, FTP_LOGIN_ESCAPED);
1335
1336 /* name is missing. thats fatal. */
1337 if (!user[0])
1338 fatal("FTP login parsing destroyed username info");
1339
1340 /* name + password == success */
1341 if (password[0])
1342 return 1;
1343
1344 /* Setup default FTP password settings */
1345 /* this has to be done last so that we can have a no-password case above. */
1346 if (!password[0]) {
1347 if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
1348 xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
1349 flags.tried_auth_anonymous=1;
1350 return 1;
1351 } else if (!flags.tried_auth_nopass) {
1352 xstrncpy(password, null_string, MAX_URL);
1353 flags.tried_auth_nopass=1;
1354 return 1;
1355 }
1356 }
1357
1358 return 0; /* different username */
1359 }
1360
1361 static String str_type_eq;
1362 void
1363 FtpStateData::checkUrlpath()
1364 {
1365 int l;
1366 size_t t;
1367
1368 if (str_type_eq.undefined()) //hack. String doesn't support global-static
1369 str_type_eq="type=";
1370
1371 if ((t = request->urlpath.rfind(';')) != String::npos) {
1372 if (request->urlpath.substr(t+1,t+1+str_type_eq.size())==str_type_eq) {
1373 typecode = (char)xtoupper(request->urlpath[t+str_type_eq.size()+1]);
1374 request->urlpath.cut(t);
1375 }
1376 }
1377
1378 l = request->urlpath.size();
1379 /* check for null path */
1380
1381 if (!l) {
1382 flags.isdir = 1;
1383 flags.root_dir = 1;
1384 flags.need_base_href = 1; /* Work around broken browsers */
1385 } else if (!request->urlpath.cmp("/%2f/")) {
1386 /* UNIX root directory */
1387 flags.isdir = 1;
1388 flags.root_dir = 1;
1389 } else if ((l >= 1) && (request->urlpath[l - 1] == '/')) {
1390 /* Directory URL, ending in / */
1391 flags.isdir = 1;
1392
1393 if (l == 1)
1394 flags.root_dir = 1;
1395 } else {
1396 flags.dir_slash = 1;
1397 }
1398 }
1399
1400 void
1401 FtpStateData::buildTitleUrl()
1402 {
1403 title_url = "ftp://";
1404
1405 if (strcmp(user, "anonymous")) {
1406 title_url.append(user);
1407 title_url.append("@");
1408 }
1409
1410 title_url.append(request->GetHost());
1411
1412 if (request->port != urlDefaultPort(PROTO_FTP)) {
1413 title_url.append(":");
1414 title_url.append(xitoa(request->port));
1415 }
1416
1417 title_url.append (request->urlpath);
1418
1419 base_href = "ftp://";
1420
1421 if (strcmp(user, "anonymous") != 0) {
1422 base_href.append(rfc1738_escape_part(user));
1423
1424 if (password_url) {
1425 base_href.append (":");
1426 base_href.append(rfc1738_escape_part(password));
1427 }
1428
1429 base_href.append("@");
1430 }
1431
1432 base_href.append(request->GetHost());
1433
1434 if (request->port != urlDefaultPort(PROTO_FTP)) {
1435 base_href.append(":");
1436 base_href.append(xitoa(request->port));
1437 }
1438
1439 base_href.append(request->urlpath);
1440 base_href.append("/");
1441 }
1442
1443 /// \ingroup ServerProtocolFTPAPI
1444 void
1445 ftpStart(FwdState * fwd)
1446 {
1447 FtpStateData *ftpState = new FtpStateData(fwd);
1448 ftpState->start();
1449 }
1450
1451 void
1452 FtpStateData::start()
1453 {
1454 if (!checkAuth(&request->header)) {
1455 /* create appropriate reply */
1456 HttpReply *reply = ftpAuthRequired(request, ftpRealm());
1457 entry->replaceHttpReply(reply);
1458 serverComplete();
1459 return;
1460 }
1461
1462 checkUrlpath();
1463 buildTitleUrl();
1464 debugs(9, 5, HERE << "host=" << request->GetHost() << ", path=" <<
1465 request->urlpath << ", user=" << user << ", passwd=" <<
1466 password);
1467
1468 state = BEGIN;
1469 ctrl.last_command = xstrdup("Connect to server");
1470 ctrl.buf = (char *)memAllocBuf(4096, &ctrl.size);
1471 ctrl.offset = 0;
1472 data.readBuf = new MemBuf;
1473 data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF);
1474 scheduleReadControlReply(0);
1475 }
1476
1477 /* ====================================================================== */
1478
1479 /// \ingroup ServerProtocolFTPInternal
1480 static char *
1481 escapeIAC(const char *buf)
1482 {
1483 int n;
1484 char *ret;
1485 unsigned const char *p;
1486 unsigned char *r;
1487
1488 for (p = (unsigned const char *)buf, n = 1; *p; n++, p++)
1489 if (*p == 255)
1490 n++;
1491
1492 ret = (char *)xmalloc(n);
1493
1494 for (p = (unsigned const char *)buf, r=(unsigned char *)ret; *p; p++) {
1495 *r++ = *p;
1496
1497 if (*p == 255)
1498 *r++ = 255;
1499 }
1500
1501 *r++ = '\0';
1502 assert((r - (unsigned char *)ret) == n );
1503 return ret;
1504 }
1505
1506 void
1507 FtpStateData::writeCommand(const char *buf)
1508 {
1509 char *ebuf;
1510 /* trace FTP protocol communications at level 2 */
1511 debugs(9, 2, "ftp<< " << buf);
1512
1513 if (Config.Ftp.telnet)
1514 ebuf = escapeIAC(buf);
1515 else
1516 ebuf = xstrdup(buf);
1517
1518 safe_free(ctrl.last_command);
1519
1520 safe_free(ctrl.last_reply);
1521
1522 ctrl.last_command = ebuf;
1523
1524 if (!canSend(ctrl.fd)) {
1525 debugs(9, 2, HERE << "cannot send to closing ctrl FD " << ctrl.fd);
1526 // TODO: assert(ctrl.closer != NULL);
1527 return;
1528 }
1529
1530 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
1531 AsyncCall::Pointer call = asyncCall(9, 5, "FtpStateData::ftpWriteCommandCallback",
1532 Dialer(this, &FtpStateData::ftpWriteCommandCallback));
1533 comm_write(ctrl.fd,
1534 ctrl.last_command,
1535 strlen(ctrl.last_command),
1536 call);
1537
1538 scheduleReadControlReply(0);
1539 }
1540
1541 void
1542 FtpStateData::ftpWriteCommandCallback(const CommIoCbParams &io)
1543 {
1544
1545 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io.size << " bytes");
1546
1547 if (io.size > 0) {
1548 fd_bytes(io.fd, io.size, FD_WRITE);
1549 kb_incr(&statCounter.server.all.kbytes_out, io.size);
1550 kb_incr(&statCounter.server.ftp.kbytes_out, io.size);
1551 }
1552
1553 if (io.flag == COMM_ERR_CLOSING)
1554 return;
1555
1556 if (io.flag) {
1557 debugs(9, DBG_IMPORTANT, "ftpWriteCommandCallback: FD " << io.fd << ": " << xstrerr(io.xerrno));
1558 failed(ERR_WRITE_ERROR, io.xerrno);
1559 /* failed closes ctrl.fd and frees ftpState */
1560 return;
1561 }
1562 }
1563
1564 wordlist *
1565 FtpStateData::ftpParseControlReply(char *buf, size_t len, int *codep, size_t *used)
1566 {
1567 char *s;
1568 char *sbuf;
1569 char *end;
1570 int usable;
1571 int complete = 0;
1572 wordlist *head = NULL;
1573 wordlist *list;
1574 wordlist **tail = &head;
1575 size_t offset;
1576 size_t linelen;
1577 int code = -1;
1578 debugs(9, 3, HERE);
1579 /*
1580 * We need a NULL-terminated buffer for scanning, ick
1581 */
1582 sbuf = (char *)xmalloc(len + 1);
1583 xstrncpy(sbuf, buf, len + 1);
1584 end = sbuf + len - 1;
1585
1586 while (*end != '\r' && *end != '\n' && end > sbuf)
1587 end--;
1588
1589 usable = end - sbuf;
1590
1591 debugs(9, 3, HERE << "usable = " << usable);
1592
1593 if (usable == 0) {
1594 debugs(9, 3, HERE << "didn't find end of line");
1595 safe_free(sbuf);
1596 return NULL;
1597 }
1598
1599 debugs(9, 3, HERE << len << " bytes to play with");
1600 end++;
1601 s = sbuf;
1602 s += strspn(s, crlf);
1603
1604 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
1605 if (complete)
1606 break;
1607
1608 debugs(9, 5, HERE << "s = {" << s << "}");
1609
1610 linelen = strcspn(s, crlf) + 1;
1611
1612 if (linelen < 2)
1613 break;
1614
1615 if (linelen > 3)
1616 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
1617
1618 if (complete)
1619 code = atoi(s);
1620
1621 offset = 0;
1622
1623 if (linelen > 3)
1624 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
1625 offset = 4;
1626
1627 list = new wordlist();
1628
1629 list->key = (char *)xmalloc(linelen - offset);
1630
1631 xstrncpy(list->key, s + offset, linelen - offset);
1632
1633 /* trace the FTP communication chat at level 2 */
1634 debugs(9, 2, "ftp>> " << code << " " << list->key);
1635
1636 *tail = list;
1637
1638 tail = &list->next;
1639 }
1640
1641 *used = (size_t) (s - sbuf);
1642 safe_free(sbuf);
1643
1644 if (!complete)
1645 wordlistDestroy(&head);
1646
1647 if (codep)
1648 *codep = code;
1649
1650 return head;
1651 }
1652
1653 /**
1654 * DPW 2007-04-23
1655 * Looks like there are no longer anymore callers that set
1656 * buffered_ok=1. Perhaps it can be removed at some point.
1657 */
1658 void
1659 FtpStateData::scheduleReadControlReply(int buffered_ok)
1660 {
1661 debugs(9, 3, HERE << "FD " << ctrl.fd);
1662
1663 if (buffered_ok && ctrl.offset > 0) {
1664 /* We've already read some reply data */
1665 handleControlReply();
1666 } else {
1667 /* XXX What about Config.Timeout.read? */
1668 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
1669 AsyncCall::Pointer reader=asyncCall(9, 5, "FtpStateData::ftpReadControlReply",
1670 Dialer(this, &FtpStateData::ftpReadControlReply));
1671 comm_read(ctrl.fd, ctrl.buf + ctrl.offset, ctrl.size - ctrl.offset, reader);
1672 /*
1673 * Cancel the timeout on the Data socket (if any) and
1674 * establish one on the control socket.
1675 */
1676
1677 if (data.fd > -1) {
1678 AsyncCall::Pointer nullCall = NULL;
1679 commSetTimeout(data.fd, -1, nullCall);
1680 }
1681
1682 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
1683 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
1684 TimeoutDialer(this,&FtpStateData::ftpTimeout));
1685
1686 commSetTimeout(ctrl.fd, Config.Timeout.read, timeoutCall);
1687 }
1688 }
1689
1690 void FtpStateData::ftpReadControlReply(const CommIoCbParams &io)
1691 {
1692 debugs(9, 3, "ftpReadControlReply: FD " << io.fd << ", Read " << io.size << " bytes");
1693
1694 if (io.size > 0) {
1695 kb_incr(&statCounter.server.all.kbytes_in, io.size);
1696 kb_incr(&statCounter.server.ftp.kbytes_in, io.size);
1697 }
1698
1699 if (io.flag == COMM_ERR_CLOSING)
1700 return;
1701
1702 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1703 abortTransaction("entry aborted during control reply read");
1704 return;
1705 }
1706
1707 assert(ctrl.offset < ctrl.size);
1708
1709 if (io.flag == COMM_OK && io.size > 0) {
1710 fd_bytes(io.fd, io.size, FD_READ);
1711 }
1712
1713 if (io.flag != COMM_OK || io.size < 0) {
1714 debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
1715 "ftpReadControlReply: read error: " << xstrerr(io.xerrno));
1716
1717 if (ignoreErrno(io.xerrno)) {
1718 scheduleReadControlReply(0);
1719 } else {
1720 failed(ERR_READ_ERROR, io.xerrno);
1721 /* failed closes ctrl.fd and frees ftpState */
1722 return;
1723 }
1724
1725 return;
1726 }
1727
1728 if (io.size == 0) {
1729 if (entry->store_status == STORE_PENDING) {
1730 failed(ERR_FTP_FAILURE, 0);
1731 /* failed closes ctrl.fd and frees ftpState */
1732 return;
1733 }
1734
1735 /* XXX this may end up having to be serverComplete() .. */
1736 abortTransaction("zero control reply read");
1737 return;
1738 }
1739
1740 unsigned int len =io.size + ctrl.offset;
1741 ctrl.offset = len;
1742 assert(len <= ctrl.size);
1743 handleControlReply();
1744 }
1745
1746 void
1747 FtpStateData::handleControlReply()
1748 {
1749 wordlist **W;
1750 size_t bytes_used = 0;
1751 wordlistDestroy(&ctrl.message);
1752 ctrl.message = ftpParseControlReply(ctrl.buf,
1753 ctrl.offset, &ctrl.replycode, &bytes_used);
1754
1755 if (ctrl.message == NULL) {
1756 /* didn't get complete reply yet */
1757
1758 if (ctrl.offset == ctrl.size) {
1759 ctrl.buf = (char *)memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size);
1760 }
1761
1762 scheduleReadControlReply(0);
1763 return;
1764 } else if (ctrl.offset == bytes_used) {
1765 /* used it all up */
1766 ctrl.offset = 0;
1767 } else {
1768 /* Got some data past the complete reply */
1769 assert(bytes_used < ctrl.offset);
1770 ctrl.offset -= bytes_used;
1771 xmemmove(ctrl.buf, ctrl.buf + bytes_used,
1772 ctrl.offset);
1773 }
1774
1775 /* Move the last line of the reply message to ctrl.last_reply */
1776 for (W = &ctrl.message; (*W)->next; W = &(*W)->next);
1777 safe_free(ctrl.last_reply);
1778
1779 ctrl.last_reply = xstrdup((*W)->key);
1780
1781 wordlistDestroy(W);
1782
1783 /* Copy the rest of the message to cwd_message to be printed in
1784 * error messages
1785 */
1786 if (ctrl.message) {
1787 for (wordlist *w = ctrl.message; w; w = w->next) {
1788 cwd_message.append('\n');
1789 cwd_message.append(w->key);
1790 }
1791 }
1792
1793 debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
1794
1795 FTP_SM_FUNCS[state] (this);
1796 }
1797
1798 /* ====================================================================== */
1799
1800 /// \ingroup ServerProtocolFTPInternal
1801 static void
1802 ftpReadWelcome(FtpStateData * ftpState)
1803 {
1804 int code = ftpState->ctrl.replycode;
1805 debugs(9, 3, HERE);
1806
1807 if (ftpState->flags.pasv_only)
1808 ftpState->login_att++;
1809
1810 /* Dont retry if the FTP server accepted the connection */
1811 ftpState->fwd->dontRetry(true);
1812
1813 if (code == 220) {
1814 if (ftpState->ctrl.message) {
1815 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1816 ftpState->flags.skip_whitespace = 1;
1817 }
1818
1819 ftpSendUser(ftpState);
1820 } else if (code == 120) {
1821 if (NULL != ftpState->ctrl.message)
1822 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key);
1823
1824 return;
1825 } else {
1826 ftpFail(ftpState);
1827 }
1828 }
1829
1830 /**
1831 * Translate FTP login failure into HTTP error
1832 * this is an attmpt to get the 407 message to show up outside Squid.
1833 * its NOT a general failure. But a correct FTP response type.
1834 */
1835 void
1836 FtpStateData::loginFailed()
1837 {
1838 ErrorState *err = NULL;
1839 const char *command, *reply;
1840
1841 if (state == SENT_USER || state == SENT_PASS) {
1842 if (ctrl.replycode > 500) {
1843 if (password_url)
1844 err = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
1845 else
1846 err = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
1847 } else if (ctrl.replycode == 421) {
1848 err = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
1849 }
1850 } else {
1851 ftpFail(this);
1852 return;
1853 }
1854
1855 err->ftp.server_msg = ctrl.message;
1856
1857 ctrl.message = NULL;
1858
1859 if (old_request)
1860 command = old_request;
1861 else
1862 command = ctrl.last_command;
1863
1864 if (command && strncmp(command, "PASS", 4) == 0)
1865 command = "PASS <yourpassword>";
1866
1867 if (old_reply)
1868 reply = old_reply;
1869 else
1870 reply = ctrl.last_reply;
1871
1872 if (command)
1873 err->ftp.request = xstrdup(command);
1874
1875 if (reply)
1876 err->ftp.reply = xstrdup(reply);
1877
1878
1879 HttpReply *newrep = err->BuildHttpReply();
1880 errorStateFree(err);
1881 /* add Authenticate header */
1882 newrep->header.putAuth("Basic", ftpRealm());
1883
1884 // add it to the store entry for response....
1885 entry->replaceHttpReply(newrep);
1886 serverComplete();
1887 }
1888
1889 const char *
1890 FtpStateData::ftpRealm()
1891 {
1892 static char realm[8192];
1893
1894 /* This request is not fully authenticated */
1895 if (!request) {
1896 snprintf(realm, 8192, "FTP %s unknown", user);
1897 } else if (request->port == 21) {
1898 snprintf(realm, 8192, "FTP %s %s", user, request->GetHost());
1899 } else {
1900 snprintf(realm, 8192, "FTP %s %s port %d", user, request->GetHost(), request->port);
1901 }
1902 return realm;
1903 }
1904
1905 /// \ingroup ServerProtocolFTPInternal
1906 static void
1907 ftpSendUser(FtpStateData * ftpState)
1908 {
1909 /* check the server control channel is still available */
1910 if (!ftpState || !ftpState->haveControlChannel("ftpSendUser"))
1911 return;
1912
1913 if (ftpState->proxy_host != NULL)
1914 snprintf(cbuf, 1024, "USER %s@%s\r\n",
1915 ftpState->user,
1916 ftpState->request->GetHost());
1917 else
1918 snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user);
1919
1920 ftpState->writeCommand(cbuf);
1921
1922 ftpState->state = SENT_USER;
1923 }
1924
1925 /// \ingroup ServerProtocolFTPInternal
1926 static void
1927 ftpReadUser(FtpStateData * ftpState)
1928 {
1929 int code = ftpState->ctrl.replycode;
1930 debugs(9, 3, HERE);
1931
1932 if (code == 230) {
1933 ftpReadPass(ftpState);
1934 } else if (code == 331) {
1935 ftpSendPass(ftpState);
1936 } else {
1937 ftpState->loginFailed();
1938 }
1939 }
1940
1941 /// \ingroup ServerProtocolFTPInternal
1942 static void
1943 ftpSendPass(FtpStateData * ftpState)
1944 {
1945 /* check the server control channel is still available */
1946 if (!ftpState || !ftpState->haveControlChannel("ftpSendPass"))
1947 return;
1948
1949 snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password);
1950 ftpState->writeCommand(cbuf);
1951 ftpState->state = SENT_PASS;
1952 }
1953
1954 /// \ingroup ServerProtocolFTPInternal
1955 static void
1956 ftpReadPass(FtpStateData * ftpState)
1957 {
1958 int code = ftpState->ctrl.replycode;
1959 debugs(9, 3, HERE << "code=" << code);
1960
1961 if (code == 230) {
1962 ftpSendType(ftpState);
1963 } else {
1964 ftpState->loginFailed();
1965 }
1966 }
1967
1968 /// \ingroup ServerProtocolFTPInternal
1969 static void
1970 ftpSendType(FtpStateData * ftpState)
1971 {
1972 const char *t;
1973 const char *filename;
1974 char mode;
1975
1976 /* check the server control channel is still available */
1977 if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
1978 return;
1979
1980 /*
1981 * Ref section 3.2.2 of RFC 1738
1982 */
1983 mode = ftpState->typecode;
1984
1985 switch (mode) {
1986
1987 case 'D':
1988 mode = 'A';
1989 break;
1990
1991 case 'A':
1992
1993 case 'I':
1994 break;
1995
1996 default:
1997
1998 if (ftpState->flags.isdir) {
1999 mode = 'A';
2000 } else {
2001 t = ftpState->request->urlpath.rpos('/');
2002 filename = t ? t + 1 : ftpState->request->urlpath.termedBuf();
2003 mode = mimeGetTransferMode(filename);
2004 }
2005
2006 break;
2007 }
2008
2009 if (mode == 'I')
2010 ftpState->flags.binary = 1;
2011 else
2012 ftpState->flags.binary = 0;
2013
2014 snprintf(cbuf, 1024, "TYPE %c\r\n", mode);
2015
2016 ftpState->writeCommand(cbuf);
2017
2018 ftpState->state = SENT_TYPE;
2019 }
2020
2021 /// \ingroup ServerProtocolFTPInternal
2022 static void
2023 ftpReadType(FtpStateData * ftpState)
2024 {
2025 int code = ftpState->ctrl.replycode;
2026 char *path;
2027 char *d, *p;
2028 debugs(9, 3, HERE);
2029
2030 if (code == 200) {
2031 p = path = xstrdup(ftpState->request->urlpath.termedBuf());
2032
2033 if (*p == '/')
2034 p++;
2035
2036 while (*p) {
2037 d = p;
2038 p += strcspn(p, "/");
2039
2040 if (*p)
2041 *p++ = '\0';
2042
2043 rfc1738_unescape(d);
2044
2045 if (*d)
2046 wordlistAdd(&ftpState->pathcomps, d);
2047 }
2048
2049 xfree(path);
2050
2051 if (ftpState->pathcomps)
2052 ftpTraverseDirectory(ftpState);
2053 else
2054 ftpListDir(ftpState);
2055 } else {
2056 ftpFail(ftpState);
2057 }
2058 }
2059
2060 /// \ingroup ServerProtocolFTPInternal
2061 static void
2062 ftpTraverseDirectory(FtpStateData * ftpState)
2063 {
2064 wordlist *w;
2065 debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>"));
2066
2067 safe_free(ftpState->dirpath);
2068 ftpState->dirpath = ftpState->filepath;
2069 ftpState->filepath = NULL;
2070
2071 /* Done? */
2072
2073 if (ftpState->pathcomps == NULL) {
2074 debugs(9, 3, HERE << "the final component was a directory");
2075 ftpListDir(ftpState);
2076 return;
2077 }
2078
2079 /* Go to next path component */
2080 w = ftpState->pathcomps;
2081
2082 ftpState->filepath = w->key;
2083
2084 ftpState->pathcomps = w->next;
2085
2086 delete w;
2087
2088 /* Check if we are to CWD or RETR */
2089 if (ftpState->pathcomps != NULL || ftpState->flags.isdir) {
2090 ftpSendCwd(ftpState);
2091 } else {
2092 debugs(9, 3, HERE << "final component is probably a file");
2093 ftpGetFile(ftpState);
2094 return;
2095 }
2096 }
2097
2098 /// \ingroup ServerProtocolFTPInternal
2099 static void
2100 ftpSendCwd(FtpStateData * ftpState)
2101 {
2102 char *path = NULL;
2103
2104 /* check the server control channel is still available */
2105 if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd"))
2106 return;
2107
2108 debugs(9, 3, HERE);
2109
2110 path = ftpState->filepath;
2111
2112 if (!strcmp(path, "..") || !strcmp(path, "/")) {
2113 ftpState->flags.no_dotdot = 1;
2114 } else {
2115 ftpState->flags.no_dotdot = 0;
2116 }
2117
2118 snprintf(cbuf, 1024, "CWD %s\r\n", path);
2119
2120 ftpState->writeCommand(cbuf);
2121
2122 ftpState->state = SENT_CWD;
2123 }
2124
2125 /// \ingroup ServerProtocolFTPInternal
2126 static void
2127 ftpReadCwd(FtpStateData * ftpState)
2128 {
2129 int code = ftpState->ctrl.replycode;
2130 debugs(9, 3, HERE);
2131
2132 if (code >= 200 && code < 300) {
2133 /* CWD OK */
2134 ftpState->unhack();
2135
2136 /* Reset cwd_message to only include the last message */
2137 ftpState->cwd_message.reset("");
2138 for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
2139 ftpState->cwd_message.append(' ');
2140 ftpState->cwd_message.append(w->key);
2141 }
2142 ftpState->ctrl.message = NULL;
2143
2144 /* Continue to traverse the path */
2145 ftpTraverseDirectory(ftpState);
2146 } else {
2147 /* CWD FAILED */
2148
2149 if (!ftpState->flags.put)
2150 ftpFail(ftpState);
2151 else
2152 ftpSendMkdir(ftpState);
2153 }
2154 }
2155
2156 /// \ingroup ServerProtocolFTPInternal
2157 static void
2158 ftpSendMkdir(FtpStateData * ftpState)
2159 {
2160 char *path = NULL;
2161
2162 /* check the server control channel is still available */
2163 if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir"))
2164 return;
2165
2166 path = ftpState->filepath;
2167 debugs(9, 3, HERE << "with path=" << path);
2168 snprintf(cbuf, 1024, "MKD %s\r\n", path);
2169 ftpState->writeCommand(cbuf);
2170 ftpState->state = SENT_MKDIR;
2171 }
2172
2173 /// \ingroup ServerProtocolFTPInternal
2174 static void
2175 ftpReadMkdir(FtpStateData * ftpState)
2176 {
2177 char *path = ftpState->filepath;
2178 int code = ftpState->ctrl.replycode;
2179
2180 debugs(9, 3, HERE << "path " << path << ", code " << code);
2181
2182 if (code == 257) { /* success */
2183 ftpSendCwd(ftpState);
2184 } else if (code == 550) { /* dir exists */
2185
2186 if (ftpState->flags.put_mkdir) {
2187 ftpState->flags.put_mkdir = 1;
2188 ftpSendCwd(ftpState);
2189 } else
2190 ftpSendReply(ftpState);
2191 } else
2192 ftpSendReply(ftpState);
2193 }
2194
2195 /// \ingroup ServerProtocolFTPInternal
2196 static void
2197 ftpGetFile(FtpStateData * ftpState)
2198 {
2199 assert(*ftpState->filepath != '\0');
2200 ftpState->flags.isdir = 0;
2201 ftpSendMdtm(ftpState);
2202 }
2203
2204 /// \ingroup ServerProtocolFTPInternal
2205 static void
2206 ftpListDir(FtpStateData * ftpState)
2207 {
2208 if (ftpState->flags.dir_slash) {
2209 debugs(9, 3, HERE << "Directory path did not end in /");
2210 ftpState->title_url.append("/");
2211 ftpState->flags.isdir = 1;
2212 }
2213
2214 ftpSendPassive(ftpState);
2215 }
2216
2217 /// \ingroup ServerProtocolFTPInternal
2218 static void
2219 ftpSendMdtm(FtpStateData * ftpState)
2220 {
2221 /* check the server control channel is still available */
2222 if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm"))
2223 return;
2224
2225 assert(*ftpState->filepath != '\0');
2226 snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath);
2227 ftpState->writeCommand(cbuf);
2228 ftpState->state = SENT_MDTM;
2229 }
2230
2231 /// \ingroup ServerProtocolFTPInternal
2232 static void
2233 ftpReadMdtm(FtpStateData * ftpState)
2234 {
2235 int code = ftpState->ctrl.replycode;
2236 debugs(9, 3, HERE);
2237
2238 if (code == 213) {
2239 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
2240 ftpState->unhack();
2241 } else if (code < 0) {
2242 ftpFail(ftpState);
2243 return;
2244 }
2245
2246 ftpSendSize(ftpState);
2247 }
2248
2249 /// \ingroup ServerProtocolFTPInternal
2250 static void
2251 ftpSendSize(FtpStateData * ftpState)
2252 {
2253 /* check the server control channel is still available */
2254 if (!ftpState || !ftpState->haveControlChannel("ftpSendSize"))
2255 return;
2256
2257 /* Only send SIZE for binary transfers. The returned size
2258 * is useless on ASCII transfers */
2259
2260 if (ftpState->flags.binary) {
2261 assert(ftpState->filepath != NULL);
2262 assert(*ftpState->filepath != '\0');
2263 snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
2264 ftpState->writeCommand(cbuf);
2265 ftpState->state = SENT_SIZE;
2266 } else
2267 /* Skip to next state no non-binary transfers */
2268 ftpSendPassive(ftpState);
2269 }
2270
2271 /// \ingroup ServerProtocolFTPInternal
2272 static void
2273 ftpReadSize(FtpStateData * ftpState)
2274 {
2275 int code = ftpState->ctrl.replycode;
2276 debugs(9, 3, HERE);
2277
2278 if (code == 213) {
2279 ftpState->unhack();
2280 ftpState->theSize = strtoll(ftpState->ctrl.last_reply, NULL, 10);
2281
2282 if (ftpState->theSize == 0) {
2283 debugs(9, 2, "SIZE reported " <<
2284 ftpState->ctrl.last_reply << " on " <<
2285 ftpState->title_url);
2286 ftpState->theSize = -1;
2287 }
2288 } else if (code < 0) {
2289 ftpFail(ftpState);
2290 return;
2291 }
2292
2293 ftpSendPassive(ftpState);
2294 }
2295
2296 /**
2297 \ingroup ServerProtocolFTPInternal
2298 */
2299 static void
2300 ftpReadEPSV(FtpStateData* ftpState)
2301 {
2302 int code = ftpState->ctrl.replycode;
2303 char h1, h2, h3, h4;
2304 int n;
2305 u_short port;
2306 Ip::Address ipa_remote;
2307 int fd = ftpState->data.fd;
2308 char *buf;
2309 debugs(9, 3, HERE);
2310
2311 if (code != 229 && code != 522) {
2312 if (code == 200) {
2313 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2314 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2315 * Its okay to re-send EPSV 1/2 but nothing else. */
2316 debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << fd_table[ftpState->ctrl.fd].ipaddr << ". Wrong accept code for EPSV");
2317 } else {
2318 debugs(9, 2, "EPSV not supported by remote end");
2319 ftpState->state = SENT_EPSV_1; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2320 }
2321 ftpSendPassive(ftpState);
2322 return;
2323 }
2324
2325 if (code == 522) {
2326 /* server response with list of supported methods */
2327 /* 522 Network protocol not supported, use (1) */
2328 /* 522 Network protocol not supported, use (1,2) */
2329 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
2330 * which means close data + control without self-destructing and re-open from scratch. */
2331 debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
2332 buf = ftpState->ctrl.last_reply;
2333 while (buf != NULL && *buf != '\0' && *buf != '\n' && *buf != '(') ++buf;
2334 if (buf != NULL && *buf == '\n') ++buf;
2335
2336 if (buf == NULL || *buf == '\0') {
2337 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2338 debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << fd_table[ftpState->ctrl.fd].ipaddr << ". 522 error missing protocol negotiation hints");
2339 ftpSendPassive(ftpState);
2340 } else if (strcmp(buf, "(1)") == 0) {
2341 ftpState->state = SENT_EPSV_2; /* simulate having sent and failed EPSV 2 */
2342 ftpSendPassive(ftpState);
2343 } else if (strcmp(buf, "(2)") == 0) {
2344 #if USE_IPV6
2345 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2346 if (ftpState->state == SENT_EPSV_2) {
2347 ftpSendEPRT(ftpState);
2348 } else {
2349 /* or try the next Passive mode down the chain. */
2350 ftpSendPassive(ftpState);
2351 }
2352 #else
2353 /* We do not support IPv6. Remote server requires it.
2354 So we must simulate having failed all EPSV methods. */
2355 ftpState->state = SENT_EPSV_1;
2356 ftpSendPassive(ftpState);
2357 #endif
2358 } else {
2359 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2360 debugs(9, DBG_IMPORTANT, "WARNING: Server at " << fd_table[ftpState->ctrl.fd].ipaddr << " sent unknown protocol negotiation hint: " << buf);
2361 ftpSendPassive(ftpState);
2362 }
2363 return;
2364 }
2365
2366 /* 229 Entering Extended Passive Mode (|||port|) */
2367 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2368 debugs(9, 5, "scanning: " << ftpState->ctrl.last_reply);
2369
2370 buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "(");
2371
2372 n = sscanf(buf, "(%c%c%c%hu%c)", &h1, &h2, &h3, &port, &h4);
2373
2374 if (h1 != h2 || h1 != h3 || h1 != h4) {
2375 debugs(9, DBG_IMPORTANT, "Invalid EPSV reply from " <<
2376 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2377 ftpState->ctrl.last_reply);
2378
2379 ftpSendPassive(ftpState);
2380 return;
2381 }
2382
2383 if (0 == port) {
2384 debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " <<
2385 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2386 ftpState->ctrl.last_reply);
2387
2388 ftpSendPassive(ftpState);
2389 return;
2390 }
2391
2392 if (Config.Ftp.sanitycheck) {
2393 if (port < 1024) {
2394 debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " <<
2395 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2396 ftpState->ctrl.last_reply);
2397
2398 ftpSendPassive(ftpState);
2399 return;
2400 }
2401 }
2402
2403 ftpState->data.port = port;
2404
2405 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr);
2406
2407 safe_free(ftpState->ctrl.last_command);
2408
2409 safe_free(ftpState->ctrl.last_reply);
2410
2411 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
2412
2413 debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
2414
2415 commConnectStart(fd, ftpState->data.host, port, FtpStateData::ftpPasvCallback, ftpState);
2416 }
2417
2418 /** \ingroup ServerProtocolFTPInternal
2419 *
2420 * Send Passive connection request.
2421 * Default method is to use modern EPSV request.
2422 * The failover mechanism should check for previous state and re-call with alternates on failure.
2423 */
2424 static void
2425 ftpSendPassive(FtpStateData * ftpState)
2426 {
2427 Ip::Address addr;
2428 struct addrinfo *AI = NULL;
2429
2430 /** Checks the server control channel is still available before running. */
2431 if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
2432 return;
2433
2434 debugs(9, 3, HERE);
2435
2436 /** \par
2437 * Checks for EPSV ALL special conditions:
2438 * If enabled to be sent, squid MUST NOT request any other connect methods.
2439 * If 'ALL' is sent and fails the entire FTP Session fails.
2440 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
2441 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent && ftpState->state == SENT_EPSV_1 ) {
2442 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2443 ftpFail(ftpState);
2444 return;
2445 }
2446
2447 /** \par
2448 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
2449 if (ftpState->request->method == METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
2450 ftpState->processHeadResponse(); // may call serverComplete
2451 return;
2452 }
2453
2454 /// Closes any old FTP-Data connection which may exist. */
2455 ftpState->data.close();
2456
2457 /** \par
2458 * Checks for previous EPSV/PASV failures on this server/session.
2459 * Diverts to EPRT immediately if they are not working. */
2460 if (!ftpState->flags.pasv_supported) {
2461 ftpSendEPRT(ftpState);
2462 return;
2463 }
2464
2465 /** \par
2466 * Locates the Address of the remote server. */
2467 addr.InitAddrInfo(AI);
2468
2469 if (getsockname(ftpState->ctrl.fd, AI->ai_addr, &AI->ai_addrlen)) {
2470 /** If it cannot be located the FTP Session is killed. */
2471 addr.FreeAddrInfo(AI);
2472 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << ftpState->ctrl.fd << ",'" << addr << "',...): " << xstrerror());
2473 ftpFail(ftpState);
2474 return;
2475 }
2476
2477 addr = *AI;
2478 addr.FreeAddrInfo(AI);
2479
2480 /** \par
2481 * Send EPSV (ALL,2,1) or PASV on the control channel.
2482 *
2483 * - EPSV ALL is used if enabled.
2484 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2485 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
2486 * - PASV is used if EPSV 1 fails.
2487 */
2488 switch (ftpState->state) {
2489 case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2490 ftpState->flags.epsv_all_sent = true;
2491 if (addr.IsIPv6()) {
2492 debugs(9, 5, HERE << "FTP Channel is IPv6 (" << addr << ") attempting EPSV 2 after EPSV ALL has failed.");
2493 snprintf(cbuf, 1024, "EPSV 2\r\n");
2494 ftpState->state = SENT_EPSV_2;
2495 break;
2496 }
2497 // else fall through to skip EPSV 2
2498
2499 case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
2500 if (addr.IsIPv4()) {
2501 debugs(9, 5, HERE << "FTP Channel is IPv4 (" << addr << ") attempting EPSV 1 after EPSV ALL has failed.");
2502 snprintf(cbuf, 1024, "EPSV 1\r\n");
2503 ftpState->state = SENT_EPSV_1;
2504 break;
2505 } else if (ftpState->flags.epsv_all_sent) {
2506 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2507 ftpFail(ftpState);
2508 return;
2509 }
2510 // else fall through to skip EPSV 1
2511
2512 case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
2513 debugs(9, 5, HERE << "FTP Channel (" << addr << ") rejects EPSV connection attempts. Trying PASV instead.");
2514 snprintf(cbuf, 1024, "PASV\r\n");
2515 ftpState->state = SENT_PASV;
2516 break;
2517
2518 default:
2519 if (!Config.Ftp.epsv) {
2520 debugs(9, 5, HERE << "EPSV support manually disabled. Sending PASV for FTP Channel (" << addr <<")");
2521 snprintf(cbuf, 1024, "PASV\r\n");
2522 ftpState->state = SENT_PASV;
2523 } else if (Config.Ftp.epsv_all) {
2524 debugs(9, 5, HERE << "EPSV ALL manually enabled. Attempting with FTP Channel (" << addr <<")");
2525 snprintf(cbuf, 1024, "EPSV ALL\r\n");
2526 ftpState->state = SENT_EPSV_ALL;
2527 /* block other non-EPSV connections being attempted */
2528 ftpState->flags.epsv_all_sent = true;
2529 } else {
2530 #if USE_IPV6
2531 if (addr.IsIPv6()) {
2532 debugs(9, 5, HERE << "FTP Channel (" << addr << "). Sending default EPSV 2");
2533 snprintf(cbuf, 1024, "EPSV 2\r\n");
2534 ftpState->state = SENT_EPSV_2;
2535 }
2536 #endif
2537 if (addr.IsIPv4()) {
2538 debugs(9, 5, HERE << "Channel (" << addr <<"). Sending default EPSV 1");
2539 snprintf(cbuf, 1024, "EPSV 1\r\n");
2540 ftpState->state = SENT_EPSV_1;
2541 }
2542 }
2543 break;
2544 }
2545
2546 /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */
2547 addr.SetPort(0);
2548 int fd = comm_open(SOCK_STREAM,
2549 IPPROTO_TCP,
2550 addr,
2551 COMM_NONBLOCKING,
2552 ftpState->entry->url());
2553
2554 debugs(9, 3, HERE << "Unconnected data socket created on FD " << fd << " from " << addr);
2555
2556 if (fd < 0) {
2557 ftpFail(ftpState);
2558 return;
2559 }
2560
2561 ftpState->data.opened(fd, ftpState->dataCloser());
2562 ftpState->writeCommand(cbuf);
2563
2564 /*
2565 * ugly hack for ftp servers like ftp.netscape.com that sometimes
2566 * dont acknowledge PASV commands.
2567 */
2568 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
2569 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
2570 TimeoutDialer(ftpState,&FtpStateData::ftpTimeout));
2571
2572 commSetTimeout(ftpState->data.fd, 15, timeoutCall);
2573 }
2574
2575 void
2576 FtpStateData::processHeadResponse()
2577 {
2578 debugs(9, 5, HERE << "handling HEAD response");
2579 ftpSendQuit(this);
2580 appendSuccessHeader();
2581
2582 /*
2583 * On rare occasions I'm seeing the entry get aborted after
2584 * ftpReadControlReply() and before here, probably when
2585 * trying to write to the client.
2586 */
2587 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2588 abortTransaction("entry aborted while processing HEAD");
2589 return;
2590 }
2591
2592 #if USE_ADAPTATION
2593 if (adaptationAccessCheckPending) {
2594 debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
2595 return;
2596 }
2597 #endif
2598
2599 // processReplyBody calls serverComplete() since there is no body
2600 processReplyBody();
2601 }
2602
2603 /// \ingroup ServerProtocolFTPInternal
2604 static void
2605 ftpReadPasv(FtpStateData * ftpState)
2606 {
2607 int code = ftpState->ctrl.replycode;
2608 int h1, h2, h3, h4;
2609 int p1, p2;
2610 int n;
2611 u_short port;
2612 Ip::Address ipa_remote;
2613 int fd = ftpState->data.fd;
2614 char *buf;
2615 LOCAL_ARRAY(char, ipaddr, 1024);
2616 debugs(9, 3, HERE);
2617
2618 if (code != 227) {
2619 debugs(9, 2, "PASV not supported by remote end");
2620 ftpSendEPRT(ftpState);
2621 return;
2622 }
2623
2624 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2625 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2626 debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
2627
2628 buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "0123456789");
2629
2630 n = sscanf(buf, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2);
2631
2632 if (n != 6 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
2633 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
2634 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2635 ftpState->ctrl.last_reply);
2636
2637 ftpSendEPRT(ftpState);
2638 return;
2639 }
2640
2641 snprintf(ipaddr, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
2642
2643 ipa_remote = ipaddr;
2644
2645 if ( ipa_remote.IsAnyAddr() ) {
2646 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
2647 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2648 ftpState->ctrl.last_reply);
2649
2650 ftpSendEPRT(ftpState);
2651 return;
2652 }
2653
2654 port = ((p1 << 8) + p2);
2655
2656 if (0 == port) {
2657 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
2658 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2659 ftpState->ctrl.last_reply);
2660
2661 ftpSendEPRT(ftpState);
2662 return;
2663 }
2664
2665 if (Config.Ftp.sanitycheck) {
2666 if (port < 1024) {
2667 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
2668 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2669 ftpState->ctrl.last_reply);
2670
2671 ftpSendEPRT(ftpState);
2672 return;
2673 }
2674 }
2675
2676 ftpState->data.port = port;
2677
2678 if (Config.Ftp.sanitycheck)
2679 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr);
2680 else
2681 ftpState->data.host = xstrdup(ipaddr);
2682
2683 safe_free(ftpState->ctrl.last_command);
2684
2685 safe_free(ftpState->ctrl.last_reply);
2686
2687 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
2688
2689 debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
2690
2691 commConnectStart(fd, ipaddr, port, FtpStateData::ftpPasvCallback, ftpState);
2692 }
2693
2694 void
2695 FtpStateData::ftpPasvCallback(int fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
2696 {
2697 FtpStateData *ftpState = (FtpStateData *)data;
2698 debugs(9, 3, HERE);
2699 ftpState->request->recordLookup(dns);
2700
2701 if (status != COMM_OK) {
2702 debugs(9, 2, HERE << "Failed to connect. Retrying without PASV.");
2703 ftpState->fwd->dontRetry(false); /* this is a retryable error */
2704 ftpState->fwd->ftpPasvFailed(true);
2705 ftpState->failed(ERR_NONE, 0);
2706 /* failed closes ctrl.fd and frees ftpState */
2707 return;
2708 }
2709
2710 ftpRestOrList(ftpState);
2711 }
2712
2713 /// \ingroup ServerProtocolFTPInternal
2714 static int
2715 ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
2716 {
2717 int fd;
2718 Ip::Address addr;
2719 struct addrinfo *AI = NULL;
2720 int on = 1;
2721 int x = 0;
2722
2723 /// Close old data channels, if any. We may open a new one below.
2724 ftpState->data.close();
2725
2726 /*
2727 * Set up a listen socket on the same local address as the
2728 * control connection.
2729 */
2730
2731 addr.InitAddrInfo(AI);
2732
2733 x = getsockname(ftpState->ctrl.fd, AI->ai_addr, &AI->ai_addrlen);
2734
2735 addr = *AI;
2736
2737 addr.FreeAddrInfo(AI);
2738
2739 if (x) {
2740 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << ftpState->ctrl.fd << ",..): " << xstrerror());
2741 return -1;
2742 }
2743
2744 /*
2745 * REUSEADDR is needed in fallback mode, since the same port is
2746 * used for both control and data.
2747 */
2748 if (fallback) {
2749 setsockopt(ftpState->ctrl.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
2750 } else {
2751 /* if not running in fallback mode a new port needs to be retrieved */
2752 addr.SetPort(0);
2753 }
2754
2755 fd = comm_open(SOCK_STREAM,
2756 IPPROTO_TCP,
2757 addr,
2758 COMM_NONBLOCKING | (fallback ? COMM_REUSEADDR : 0),
2759 ftpState->entry->url());
2760 debugs(9, 3, HERE << "Unconnected data socket created on FD " << fd );
2761
2762 if (fd < 0) {
2763 debugs(9, DBG_CRITICAL, HERE << "comm_open failed");
2764 return -1;
2765 }
2766
2767 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
2768 AsyncCall::Pointer acceptCall = asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
2769 acceptDialer(ftpState, &FtpStateData::ftpAcceptDataConnection));
2770 ftpState->data.listener = new Comm::ListenStateData(fd, acceptCall, false);
2771
2772 if (!ftpState->data.listener || ftpState->data.listener->errcode < 0) {
2773 comm_close(fd);
2774 return -1;
2775 }
2776
2777 ftpState->data.opened(fd, ftpState->dataCloser());
2778 ftpState->data.port = comm_local_port(fd);
2779 ftpState->data.host = NULL;
2780 return fd;
2781 }
2782
2783 /// \ingroup ServerProtocolFTPInternal
2784 static void
2785 ftpSendPORT(FtpStateData * ftpState)
2786 {
2787 int fd;
2788 Ip::Address ipa;
2789 struct addrinfo *AI = NULL;
2790 unsigned char *addrptr;
2791 unsigned char *portptr;
2792
2793 /* check the server control channel is still available */
2794 if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
2795 return;
2796
2797 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
2798 debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2799 return;
2800 }
2801
2802 debugs(9, 3, HERE);
2803 ftpState->flags.pasv_supported = 0;
2804 fd = ftpOpenListenSocket(ftpState, 0);
2805 ipa.InitAddrInfo(AI);
2806
2807 if (getsockname(fd, AI->ai_addr, &AI->ai_addrlen)) {
2808 ipa.FreeAddrInfo(AI);
2809 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << fd << ",..): " << xstrerror());
2810
2811 /* XXX Need to set error message */
2812 ftpFail(ftpState);
2813 return;
2814 }
2815
2816 #if USE_IPV6
2817 if ( AI->ai_addrlen != sizeof(struct sockaddr_in) ) {
2818 ipa.FreeAddrInfo(AI);
2819 /* IPv6 CANNOT send PORT command. */
2820 /* we got here by attempting and failing an EPRT */
2821 /* using the same reply code should simulate a PORT failure */
2822 ftpReadPORT(ftpState);
2823 return;
2824 }
2825 #endif
2826
2827 addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
2828 portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
2829 snprintf(cbuf, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
2830 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
2831 portptr[0], portptr[1]);
2832 ftpState->writeCommand(cbuf);
2833 ftpState->state = SENT_PORT;
2834
2835 ipa.FreeAddrInfo(AI);
2836 }
2837
2838 /// \ingroup ServerProtocolFTPInternal
2839 static void
2840 ftpReadPORT(FtpStateData * ftpState)
2841 {
2842 int code = ftpState->ctrl.replycode;
2843 debugs(9, 3, HERE);
2844
2845 if (code != 200) {
2846 /* Fall back on using the same port as the control connection */
2847 debugs(9, 3, "PORT not supported by remote end");
2848 ftpOpenListenSocket(ftpState, 1);
2849 }
2850
2851 ftpRestOrList(ftpState);
2852 }
2853
2854 /// \ingroup ServerProtocolFTPInternal
2855 static void
2856 ftpSendEPRT(FtpStateData * ftpState)
2857 {
2858 int fd;
2859 Ip::Address addr;
2860 struct addrinfo *AI = NULL;
2861 char buf[MAX_IPSTRLEN];
2862
2863 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
2864 debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2865 return;
2866 }
2867
2868 debugs(9, 3, HERE);
2869 ftpState->flags.pasv_supported = 0;
2870 fd = ftpOpenListenSocket(ftpState, 0);
2871
2872 Ip::Address::InitAddrInfo(AI);
2873
2874 if (getsockname(fd, AI->ai_addr, &AI->ai_addrlen)) {
2875 Ip::Address::FreeAddrInfo(AI);
2876 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << fd << ",..): " << xstrerror());
2877
2878 /* XXX Need to set error message */
2879 ftpFail(ftpState);
2880 return;
2881 }
2882
2883 addr = *AI;
2884
2885 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2886 /* Which can be used by EITHER protocol. */
2887 snprintf(cbuf, 1024, "EPRT |%d|%s|%d|\r\n",
2888 ( addr.IsIPv6() ? 2 : 1 ),
2889 addr.NtoA(buf,MAX_IPSTRLEN),
2890 addr.GetPort() );
2891
2892 ftpState->writeCommand(cbuf);
2893 ftpState->state = SENT_EPRT;
2894
2895 Ip::Address::FreeAddrInfo(AI);
2896 }
2897
2898 static void
2899 ftpReadEPRT(FtpStateData * ftpState)
2900 {
2901 int code = ftpState->ctrl.replycode;
2902 debugs(9, 3, HERE);
2903
2904 if (code != 200) {
2905 /* Failover to attempting old PORT command. */
2906 debugs(9, 3, "EPRT not supported by remote end");
2907 ftpSendPORT(ftpState);
2908 return;
2909 }
2910
2911 ftpRestOrList(ftpState);
2912 }
2913
2914 /**
2915 \ingroup ServerProtocolFTPInternal
2916 \par
2917 * "read" handler to accept FTP data connections.
2918 *
2919 \param io comm accept(2) callback parameters
2920 */
2921 void FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams &io)
2922 {
2923 char ntoapeer[MAX_IPSTRLEN];
2924 debugs(9, 3, "ftpAcceptDataConnection");
2925
2926 // one connection accepted. the handler has stopped listening. drop our local pointer to it.
2927 data.listener = NULL;
2928
2929 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2930 abortTransaction("entry aborted when accepting data conn");
2931 return;
2932 }
2933
2934 /** \par
2935 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2936 * made by the remote client which is connected to the FTP control socket.
2937 * This prevents third-party hacks, but also third-party load balancing handshakes.
2938 */
2939 if (Config.Ftp.sanitycheck) {
2940 io.details.peer.NtoA(ntoapeer,MAX_IPSTRLEN);
2941
2942 if (strcmp(fd_table[ctrl.fd].ipaddr, ntoapeer) != 0) {
2943 debugs(9, DBG_IMPORTANT,
2944 "FTP data connection from unexpected server (" <<
2945 io.details.peer << "), expecting " <<
2946 fd_table[ctrl.fd].ipaddr);
2947
2948 /* close the bad soures connection down ASAP. */
2949 comm_close(io.nfd);
2950
2951 /* we are ony accepting once, so need to re-open the listener socket. */
2952 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
2953 AsyncCall::Pointer acceptCall = asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
2954 acceptDialer(this, &FtpStateData::ftpAcceptDataConnection));
2955 data.listener = new Comm::ListenStateData(data.fd, acceptCall, false);
2956 return;
2957 }
2958 }
2959
2960 if (io.flag != COMM_OK) {
2961 debugs(9, DBG_IMPORTANT, "ftpHandleDataAccept: FD " << io.nfd << ": " << xstrerr(io.xerrno));
2962 /** \todo XXX Need to set error message */
2963 ftpFail(this);
2964 return;
2965 }
2966
2967 /**\par
2968 * Replace the Listen socket with the accepted data socket */
2969 data.close();
2970 data.opened(io.nfd, dataCloser());
2971 data.port = io.details.peer.GetPort();
2972 io.details.peer.NtoA(data.host,SQUIDHOSTNAMELEN);
2973
2974 debugs(9, 3, "ftpAcceptDataConnection: Connected data socket on " <<
2975 "FD " << io.nfd << " to " << io.details.peer << " FD table says: " <<
2976 "ctrl-peer= " << fd_table[ctrl.fd].ipaddr << ", " <<
2977 "data-peer= " << fd_table[data.fd].ipaddr);
2978
2979
2980 AsyncCall::Pointer nullCall = NULL;
2981 commSetTimeout(ctrl.fd, -1, nullCall);
2982
2983 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
2984 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
2985 TimeoutDialer(this,&FtpStateData::ftpTimeout));
2986 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
2987
2988 /*\todo XXX We should have a flag to track connect state...
2989 * host NULL -> not connected, port == local port
2990 * host set -> connected, port == remote port
2991 */
2992 /* Restart state (SENT_NLST/LIST/RETR) */
2993 FTP_SM_FUNCS[state] (this);
2994 }
2995
2996 /// \ingroup ServerProtocolFTPInternal
2997 static void
2998 ftpRestOrList(FtpStateData * ftpState)
2999 {
3000 debugs(9, 3, HERE);
3001
3002 if (ftpState->typecode == 'D') {
3003 ftpState->flags.isdir = 1;
3004
3005 if (ftpState->flags.put) {
3006 ftpSendMkdir(ftpState); /* PUT name;type=d */
3007 } else {
3008 ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3009 }
3010 } else if (ftpState->flags.put) {
3011 ftpSendStor(ftpState);
3012 } else if (ftpState->flags.isdir)
3013 ftpSendList(ftpState);
3014 else if (ftpState->restartable())
3015 ftpSendRest(ftpState);
3016 else
3017 ftpSendRetr(ftpState);
3018 }
3019
3020 /// \ingroup ServerProtocolFTPInternal
3021 static void
3022 ftpSendStor(FtpStateData * ftpState)
3023 {
3024 /* check the server control channel is still available */
3025 if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
3026 return;
3027
3028 debugs(9, 3, HERE);
3029
3030 if (ftpState->filepath != NULL) {
3031 /* Plain file upload */
3032 snprintf(cbuf, 1024, "STOR %s\r\n", ftpState->filepath);
3033 ftpState->writeCommand(cbuf);
3034 ftpState->state = SENT_STOR;
3035 } else if (ftpState->request->header.getInt64(HDR_CONTENT_LENGTH) > 0) {
3036 /* File upload without a filename. use STOU to generate one */
3037 snprintf(cbuf, 1024, "STOU\r\n");
3038 ftpState->writeCommand(cbuf);
3039 ftpState->state = SENT_STOR;
3040 } else {
3041 /* No file to transfer. Only create directories if needed */
3042 ftpSendReply(ftpState);
3043 }
3044 }
3045
3046 /// \ingroup ServerProtocolFTPInternal
3047 /// \deprecated use ftpState->readStor() instead.
3048 static void
3049 ftpReadStor(FtpStateData * ftpState)
3050 {
3051 ftpState->readStor();
3052 }
3053
3054 void FtpStateData::readStor()
3055 {
3056 int code = ctrl.replycode;
3057 debugs(9, 3, HERE);
3058
3059 if (code == 125 || (code == 150 && data.host)) {
3060 if (!startRequestBodyFlow()) { // register to receive body data
3061 ftpFail(this);
3062 return;
3063 }
3064
3065 /*\par
3066 * When client status is 125, or 150 without a hostname, Begin data transfer. */
3067 debugs(9, 3, HERE << "starting data transfer");
3068 sendMoreRequestBody();
3069 /** \par
3070 * Cancel the timeout on the Control socket and
3071 * establish one on the data socket.
3072 */
3073 AsyncCall::Pointer nullCall = NULL;
3074 commSetTimeout(ctrl.fd, -1, nullCall);
3075
3076 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
3077 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
3078 TimeoutDialer(this,&FtpStateData::ftpTimeout));
3079
3080 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
3081
3082 state = WRITING_DATA;
3083 debugs(9, 3, HERE << "writing data channel");
3084 } else if (code == 150) {
3085 /*\par
3086 * When client code is 150 with a hostname, Accept data channel. */
3087 debugs(9, 3, "ftpReadStor: accepting data channel");
3088 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
3089 AsyncCall::Pointer acceptCall = asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3090 acceptDialer(this, &FtpStateData::ftpAcceptDataConnection));
3091
3092 data.listener = new Comm::ListenStateData(data.fd, acceptCall, false);
3093 } else {
3094 debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
3095 ftpFail(this);
3096 }
3097 }
3098
3099 /// \ingroup ServerProtocolFTPInternal
3100 static void
3101 ftpSendRest(FtpStateData * ftpState)
3102 {
3103 /* check the server control channel is still available */
3104 if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
3105 return;
3106
3107 debugs(9, 3, HERE);
3108
3109 snprintf(cbuf, 1024, "REST %"PRId64"\r\n", ftpState->restart_offset);
3110 ftpState->writeCommand(cbuf);
3111 ftpState->state = SENT_REST;
3112 }
3113
3114 int
3115 FtpStateData::restartable()
3116 {
3117 if (restart_offset > 0)
3118 return 1;
3119
3120 if (!request->range)
3121 return 0;
3122
3123 if (!flags.binary)
3124 return 0;
3125
3126 if (theSize <= 0)
3127 return 0;
3128
3129 int64_t desired_offset = request->range->lowestOffset(theSize);
3130
3131 if (desired_offset <= 0)
3132 return 0;
3133
3134 if (desired_offset >= theSize)
3135 return 0;
3136
3137 restart_offset = desired_offset;
3138 return 1;
3139 }
3140
3141 /// \ingroup ServerProtocolFTPInternal
3142 static void
3143 ftpReadRest(FtpStateData * ftpState)
3144 {
3145 int code = ftpState->ctrl.replycode;
3146 debugs(9, 3, HERE);
3147 assert(ftpState->restart_offset > 0);
3148
3149 if (code == 350) {
3150 ftpState->setCurrentOffset(ftpState->restart_offset);
3151 ftpSendRetr(ftpState);
3152 } else if (code > 0) {
3153 debugs(9, 3, HERE << "REST not supported");
3154 ftpState->flags.rest_supported = 0;
3155 ftpSendRetr(ftpState);
3156 } else {
3157 ftpFail(ftpState);
3158 }
3159 }
3160
3161 /// \ingroup ServerProtocolFTPInternal
3162 static void
3163 ftpSendList(FtpStateData * ftpState)
3164 {
3165 /* check the server control channel is still available */
3166 if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
3167 return;
3168
3169 debugs(9, 3, HERE);
3170
3171 if (ftpState->filepath) {
3172 snprintf(cbuf, 1024, "LIST %s\r\n", ftpState->filepath);
3173 } else {
3174 snprintf(cbuf, 1024, "LIST\r\n");
3175 }
3176
3177 ftpState->writeCommand(cbuf);
3178 ftpState->state = SENT_LIST;
3179 }
3180
3181 /// \ingroup ServerProtocolFTPInternal
3182 static void
3183 ftpSendNlst(FtpStateData * ftpState)
3184 {
3185 /* check the server control channel is still available */
3186 if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
3187 return;
3188
3189 debugs(9, 3, HERE);
3190
3191 ftpState->flags.tried_nlst = 1;
3192
3193 if (ftpState->filepath) {
3194 snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
3195 } else {
3196 snprintf(cbuf, 1024, "NLST\r\n");
3197 }
3198
3199 ftpState->writeCommand(cbuf);
3200 ftpState->state = SENT_NLST;
3201 }
3202
3203 /// \ingroup ServerProtocolFTPInternal
3204 static void
3205 ftpReadList(FtpStateData * ftpState)
3206 {
3207 int code = ftpState->ctrl.replycode;
3208 debugs(9, 3, HERE);
3209
3210 if (code == 125 || (code == 150 && ftpState->data.host)) {
3211 /* Begin data transfer */
3212 /* XXX what about Config.Timeout.read? */
3213 ftpState->maybeReadVirginBody();
3214 ftpState->state = READING_DATA;
3215 /*
3216 * Cancel the timeout on the Control socket and establish one
3217 * on the data socket
3218 */
3219 AsyncCall::Pointer nullCall = NULL;
3220 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
3221 return;
3222 } else if (code == 150) {
3223 /* Accept data channel */
3224 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
3225 AsyncCall::Pointer acceptCall = asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3226 acceptDialer(ftpState, &FtpStateData::ftpAcceptDataConnection));
3227
3228 ftpState->data.listener = new Comm::ListenStateData(ftpState->data.fd, acceptCall, false);
3229 /*
3230 * Cancel the timeout on the Control socket and establish one
3231 * on the data socket
3232 */
3233 AsyncCall::Pointer nullCall = NULL;
3234 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
3235
3236 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
3237 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
3238 TimeoutDialer(ftpState,&FtpStateData::ftpTimeout));
3239 commSetTimeout(ftpState->data.fd, Config.Timeout.read, timeoutCall);
3240 return;
3241 } else if (!ftpState->flags.tried_nlst && code > 300) {
3242 ftpSendNlst(ftpState);
3243 } else {
3244 ftpFail(ftpState);
3245 return;
3246 }
3247 }
3248
3249 /// \ingroup ServerProtocolFTPInternal
3250 static void
3251 ftpSendRetr(FtpStateData * ftpState)
3252 {
3253 /* check the server control channel is still available */
3254 if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
3255 return;
3256
3257 debugs(9, 3, HERE);
3258
3259 assert(ftpState->filepath != NULL);
3260 snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath);
3261 ftpState->writeCommand(cbuf);
3262 ftpState->state = SENT_RETR;
3263 }
3264
3265 /// \ingroup ServerProtocolFTPInternal
3266 static void
3267 ftpReadRetr(FtpStateData * ftpState)
3268 {
3269 int code = ftpState->ctrl.replycode;
3270 debugs(9, 3, HERE);
3271
3272 if (code == 125 || (code == 150 && ftpState->data.host)) {
3273 /* Begin data transfer */
3274 debugs(9, 3, HERE << "reading data channel");
3275 /* XXX what about Config.Timeout.read? */
3276 ftpState->maybeReadVirginBody();
3277 ftpState->state = READING_DATA;
3278 /*
3279 * Cancel the timeout on the Control socket and establish one
3280 * on the data socket
3281 */
3282 AsyncCall::Pointer nullCall = NULL;
3283 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
3284 } else if (code == 150) {
3285 /* Accept data channel */
3286 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
3287 AsyncCall::Pointer acceptCall = asyncCall(11, 5, "FtpStateData::ftpAcceptDataConnection",
3288 acceptDialer(ftpState, &FtpStateData::ftpAcceptDataConnection));
3289 ftpState->data.listener = new Comm::ListenStateData(ftpState->data.fd, acceptCall, false);
3290 /*
3291 * Cancel the timeout on the Control socket and establish one
3292 * on the data socket
3293 */
3294 AsyncCall::Pointer nullCall = NULL;
3295 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
3296
3297 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
3298 AsyncCall::Pointer timeoutCall = asyncCall(9, 5, "FtpStateData::ftpTimeout",
3299 TimeoutDialer(ftpState,&FtpStateData::ftpTimeout));
3300 commSetTimeout(ftpState->data.fd, Config.Timeout.read, timeoutCall);
3301 } else if (code >= 300) {
3302 if (!ftpState->flags.try_slash_hack) {
3303 /* Try this as a directory missing trailing slash... */
3304 ftpState->hackShortcut(ftpSendCwd);
3305 } else {
3306 ftpFail(ftpState);
3307 }
3308 } else {
3309 ftpFail(ftpState);
3310 }
3311 }
3312
3313 /**
3314 * Generate the HTTP headers and template fluff around an FTP
3315 * directory listing display.
3316 */
3317 void
3318 FtpStateData::completedListing()
3319 {
3320 assert(entry);
3321 entry->lock();
3322 ErrorState *ferr = errorCon(ERR_DIR_LISTING, HTTP_OK, request);
3323 ferr->ftp.listing = &listing;
3324 ferr->ftp.cwd_msg = xstrdup(cwd_message.termedBuf());
3325 ferr->ftp.server_msg = ctrl.message;
3326 ctrl.message = NULL;
3327 entry->replaceHttpReply( ferr->BuildHttpReply() );
3328 errorStateFree(ferr);
3329 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
3330 entry->flush();
3331 entry->unlock();
3332 }
3333
3334
3335 /// \ingroup ServerProtocolFTPInternal
3336 static void
3337 ftpReadTransferDone(FtpStateData * ftpState)
3338 {
3339 int code = ftpState->ctrl.replycode;
3340 debugs(9, 3, HERE);
3341
3342 if (code == 226 || code == 250) {
3343 /* Connection closed; retrieval done. */
3344 if (ftpState->flags.listing) {
3345 ftpState->completedListing();
3346 /* QUIT operation handles sending the reply to client */
3347 }
3348 ftpSendQuit(ftpState);
3349 } else { /* != 226 */
3350 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
3351 ftpState->failed(ERR_FTP_FAILURE, 0);
3352 /* failed closes ctrl.fd and frees ftpState */
3353 return;
3354 }
3355 }
3356
3357 // premature end of the request body
3358 void
3359 FtpStateData::handleRequestBodyProducerAborted()
3360 {
3361 ServerStateData::handleRequestBodyProducerAborted();
3362 debugs(9, 3, HERE << "ftpState=" << this);
3363 failed(ERR_READ_ERROR, 0);
3364 }
3365
3366 /**
3367 * This will be called when the put write is completed
3368 */
3369 void
3370 FtpStateData::sentRequestBody(const CommIoCbParams &io)
3371 {
3372 if (io.size > 0)
3373 kb_incr(&statCounter.server.ftp.kbytes_out, io.size);
3374 ServerStateData::sentRequestBody(io);
3375 }
3376
3377 /// \ingroup ServerProtocolFTPInternal
3378 static void
3379 ftpWriteTransferDone(FtpStateData * ftpState)
3380 {
3381 int code = ftpState->ctrl.replycode;
3382 debugs(9, 3, HERE);
3383
3384 if (!(code == 226 || code == 250)) {
3385 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data");
3386 ftpState->failed(ERR_FTP_PUT_ERROR, 0);
3387 return;
3388 }
3389
3390 ftpState->entry->timestampsSet(); /* XXX Is this needed? */
3391 ftpSendReply(ftpState);
3392 }
3393
3394 /// \ingroup ServerProtocolFTPInternal
3395 static void
3396 ftpSendQuit(FtpStateData * ftpState)
3397 {
3398 /* check the server control channel is still available */
3399 if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
3400 return;
3401
3402 snprintf(cbuf, 1024, "QUIT\r\n");
3403 ftpState->writeCommand(cbuf);
3404 ftpState->state = SENT_QUIT;
3405 }
3406
3407 /**
3408 * \ingroup ServerProtocolFTPInternal
3409 *
3410 * This completes a client FTP operation with success or other page
3411 * generated and stored in the entry field by the code issuing QUIT.
3412 */
3413 static void
3414 ftpReadQuit(FtpStateData * ftpState)
3415 {
3416 ftpState->serverComplete();
3417 }
3418
3419 /// \ingroup ServerProtocolFTPInternal
3420 static void
3421 ftpTrySlashHack(FtpStateData * ftpState)
3422 {
3423 char *path;
3424 ftpState->flags.try_slash_hack = 1;
3425 /* Free old paths */
3426
3427 debugs(9, 3, HERE);
3428
3429 if (ftpState->pathcomps)
3430 wordlistDestroy(&ftpState->pathcomps);
3431
3432 safe_free(ftpState->filepath);
3433
3434 /* Build the new path (urlpath begins with /) */
3435 path = xstrdup(ftpState->request->urlpath.termedBuf());
3436
3437 rfc1738_unescape(path);
3438
3439 ftpState->filepath = path;
3440
3441 /* And off we go */
3442 ftpGetFile(ftpState);
3443 }
3444
3445 /**
3446 * Forget hack status. Next error is shown to the user
3447 */
3448 void
3449 FtpStateData::unhack()
3450 {
3451 debugs(9, 3, HERE);
3452
3453 if (old_request != NULL) {
3454 safe_free(old_request);
3455 safe_free(old_reply);
3456 }
3457 }
3458
3459 void
3460 FtpStateData::hackShortcut(FTPSM * nextState)
3461 {
3462 /* Clear some unwanted state */
3463 setCurrentOffset(0);
3464 restart_offset = 0;
3465 /* Save old error message & some state info */
3466
3467 debugs(9, 3, HERE);
3468
3469 if (old_request == NULL) {
3470 old_request = ctrl.last_command;
3471 ctrl.last_command = NULL;
3472 old_reply = ctrl.last_reply;
3473 ctrl.last_reply = NULL;
3474
3475 if (pathcomps == NULL && filepath != NULL)
3476 old_filepath = xstrdup(filepath);
3477 }
3478
3479 /* Jump to the "hack" state */
3480 nextState(this);
3481 }
3482
3483 /// \ingroup ServerProtocolFTPInternal
3484 static void
3485 ftpFail(FtpStateData *ftpState)
3486 {
3487 debugs(9, 6, HERE << "flags(" <<
3488 (ftpState->flags.isdir?"IS_DIR,":"") <<
3489 (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
3490 "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
3491 "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
3492
3493 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
3494 if (!ftpState->flags.isdir && /* Not a directory */
3495 !ftpState->flags.try_slash_hack && /* Not in slash hack */
3496 ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
3497 ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
3498
3499 switch (ftpState->state) {
3500
3501 case SENT_CWD:
3502
3503 case SENT_RETR:
3504 /* Try the / hack */
3505 ftpState->hackShortcut(ftpTrySlashHack);
3506 return;
3507
3508 default:
3509 break;
3510 }
3511 }
3512
3513 ftpState->failed(ERR_NONE, 0);
3514 /* failed() closes ctrl.fd and frees this */
3515 }
3516
3517 void
3518 FtpStateData::failed(err_type error, int xerrno)
3519 {
3520 debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
3521 if (entry->isEmpty())
3522 failedErrorMessage(error, xerrno);
3523
3524 serverComplete();
3525 }
3526
3527 void
3528 FtpStateData::failedErrorMessage(err_type error, int xerrno)
3529 {
3530 ErrorState *ftperr;
3531 const char *command, *reply;
3532
3533 /* Translate FTP errors into HTTP errors */
3534 ftperr = NULL;
3535
3536 switch (error) {
3537
3538 case ERR_NONE:
3539
3540 switch (state) {
3541
3542 case SENT_USER:
3543
3544 case SENT_PASS:
3545
3546 if (ctrl.replycode > 500)
3547 if (password_url)
3548 ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
3549 else
3550 ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
3551
3552 else if (ctrl.replycode == 421)
3553 ftperr = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
3554
3555 break;
3556
3557 case SENT_CWD:
3558
3559 case SENT_RETR:
3560 if (ctrl.replycode == 550)
3561 ftperr = errorCon(ERR_FTP_NOT_FOUND, HTTP_NOT_FOUND, fwd->request);
3562
3563 break;
3564
3565 default:
3566 break;
3567 }
3568
3569 break;
3570
3571 case ERR_READ_TIMEOUT:
3572 ftperr = errorCon(error, HTTP_GATEWAY_TIMEOUT, fwd->request);
3573 break;
3574
3575 default:
3576 ftperr = errorCon(error, HTTP_BAD_GATEWAY, fwd->request);
3577 break;
3578 }
3579
3580 if (ftperr == NULL)
3581 ftperr = errorCon(ERR_FTP_FAILURE, HTTP_BAD_GATEWAY, fwd->request);
3582
3583 ftperr->xerrno = xerrno;
3584
3585 ftperr->ftp.server_msg = ctrl.message;
3586 ctrl.message = NULL;
3587
3588 if (old_request)
3589 command = old_request;
3590 else
3591 command = ctrl.last_command;
3592
3593 if (command && strncmp(command, "PASS", 4) == 0)
3594 command = "PASS <yourpassword>";
3595
3596 if (old_reply)
3597 reply = old_reply;
3598 else
3599 reply = ctrl.last_reply;
3600
3601 if (command)
3602 ftperr->ftp.request = xstrdup(command);
3603
3604 if (reply)
3605 ftperr->ftp.reply = xstrdup(reply);
3606
3607 entry->replaceHttpReply( ftperr->BuildHttpReply() );
3608 errorStateFree(ftperr);
3609 }
3610
3611 /// \ingroup ServerProtocolFTPInternal
3612 static void
3613 ftpSendReply(FtpStateData * ftpState)
3614 {
3615 ErrorState *err;
3616 int code = ftpState->ctrl.replycode;
3617 http_status http_code;
3618 err_type err_code = ERR_NONE;
3619
3620 debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code);
3621
3622 if (cbdataReferenceValid(ftpState))
3623 debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!");
3624
3625 if (code == 226 || code == 250) {
3626 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
3627 http_code = (ftpState->mdtm > 0) ? HTTP_ACCEPTED : HTTP_CREATED;
3628 } else if (code == 227) {
3629 err_code = ERR_FTP_PUT_CREATED;
3630 http_code = HTTP_CREATED;
3631 } else {
3632 err_code = ERR_FTP_PUT_ERROR;
3633 http_code = HTTP_INTERNAL_SERVER_ERROR;
3634 }
3635
3636 err = errorCon(err_code, http_code, ftpState->request);
3637
3638 if (ftpState->old_request)
3639 err->ftp.request = xstrdup(ftpState->old_request);
3640 else
3641 err->ftp.request = xstrdup(ftpState->ctrl.last_command);
3642
3643 if (ftpState->old_reply)
3644 err->ftp.reply = xstrdup(ftpState->old_reply);
3645 else if (ftpState->ctrl.last_reply)
3646 err->ftp.reply = xstrdup(ftpState->ctrl.last_reply);
3647 else
3648 err->ftp.reply = xstrdup("");
3649
3650 ftpState->entry->replaceHttpReply( err->BuildHttpReply() );
3651 errorStateFree(err);
3652
3653 ftpSendQuit(ftpState);
3654 }
3655
3656 void
3657 FtpStateData::appendSuccessHeader()
3658 {
3659 const char *mime_type = NULL;
3660 const char *mime_enc = NULL;
3661 String urlpath = request->urlpath;
3662 const char *filename = NULL;
3663 const char *t = NULL;
3664
3665 debugs(9, 3, HERE);
3666
3667 if (flags.http_header_sent)
3668 return;
3669
3670 HttpReply *reply = new HttpReply;
3671
3672 flags.http_header_sent = 1;
3673
3674 assert(entry->isEmpty());
3675
3676 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
3677
3678 entry->buffer(); /* released when done processing current data payload */
3679
3680 filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
3681
3682 if (flags.isdir) {
3683 mime_type = "text/html";
3684 } else {
3685 switch (typecode) {
3686
3687 case 'I':
3688 mime_type = "application/octet-stream";
3689 mime_enc = mimeGetContentEncoding(filename);
3690 break;
3691
3692 case 'A':
3693 mime_type = "text/plain";
3694 break;
3695
3696 default:
3697 mime_type = mimeGetContentType(filename);
3698 mime_enc = mimeGetContentEncoding(filename);
3699 break;
3700 }
3701 }
3702
3703 /* set standard stuff */
3704
3705 if (0 == getCurrentOffset()) {
3706 /* Full reply */
3707 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
3708 } else if (theSize < getCurrentOffset()) {
3709 /*
3710 * DPW 2007-05-04
3711 * offset should not be larger than theSize. We should
3712 * not be seeing this condition any more because we'll only
3713 * send REST if we know the theSize and if it is less than theSize.
3714 */
3715 debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
3716 " current offset=" << getCurrentOffset() <<
3717 ", but theSize=" << theSize <<
3718 ". assuming full content response");
3719 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
3720 } else {
3721 /* Partial reply */
3722 HttpHdrRangeSpec range_spec;
3723 range_spec.offset = getCurrentOffset();
3724 range_spec.length = theSize - getCurrentOffset();
3725 reply->setHeaders(HTTP_PARTIAL_CONTENT, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
3726 httpHeaderAddContRange(&reply->header, range_spec, theSize);
3727 }
3728
3729 /* additional info */
3730 if (mime_enc)
3731 reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
3732
3733 setVirginReply(reply);
3734 adaptOrFinalizeReply();
3735 }
3736
3737 void
3738 FtpStateData::haveParsedReplyHeaders()
3739 {
3740 ServerStateData::haveParsedReplyHeaders();
3741
3742 StoreEntry *e = entry;
3743
3744 e->timestampsSet();
3745
3746 if (flags.authenticated) {
3747 /*
3748 * Authenticated requests can't be cached.
3749 */
3750 e->release();
3751 } else if (EBIT_TEST(e->flags, ENTRY_CACHABLE) && !getCurrentOffset()) {
3752 e->setPublicKey();
3753 } else {
3754 e->release();
3755 }
3756 }
3757
3758 HttpReply *
3759 FtpStateData::ftpAuthRequired(HttpRequest * request, const char *realm)
3760 {
3761 ErrorState *err = errorCon(ERR_CACHE_ACCESS_DENIED, HTTP_UNAUTHORIZED, request);
3762 HttpReply *newrep = err->BuildHttpReply();
3763 errorStateFree(err);
3764 /* add Authenticate header */
3765 newrep->header.putAuth("Basic", realm);
3766 return newrep;
3767 }
3768
3769 /**
3770 \ingroup ServerProtocolFTPAPI
3771 \todo Should be a URL class API call.
3772 *
3773 * Construct an URI with leading / in PATH portion for use by CWD command
3774 * possibly others. FTP encodes absolute paths as beginning with '/'
3775 * after the initial URI path delimiter, which happens to be / itself.
3776 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3777 * To encompass older software which compacts multiple // to / in transit
3778 * We use standard URI-encoding on the second / making it
3779 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3780 */
3781 const char *
3782 ftpUrlWith2f(HttpRequest * request)
3783 {
3784 String newbuf = "%2f";
3785
3786 if (request->protocol != PROTO_FTP)
3787 return NULL;
3788
3789 if ( request->urlpath[0]=='/' ) {
3790 newbuf.append(request->urlpath);
3791 request->urlpath.absorb(newbuf);
3792 safe_free(request->canonical);
3793 } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
3794 newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
3795 request->urlpath.absorb(newbuf);
3796 safe_free(request->canonical);
3797 }
3798
3799 return urlCanonical(request);
3800 }
3801
3802 void
3803 FtpStateData::printfReplyBody(const char *fmt, ...)
3804 {
3805 va_list args;
3806 va_start (args, fmt);
3807 static char buf[4096];
3808 buf[0] = '\0';
3809 vsnprintf(buf, 4096, fmt, args);
3810 writeReplyBody(buf, strlen(buf));
3811 }
3812
3813 /**
3814 * Call this when there is data from the origin server
3815 * which should be sent to either StoreEntry, or to ICAP...
3816 */
3817 void
3818 FtpStateData::writeReplyBody(const char *dataToWrite, size_t dataLength)
3819 {
3820 debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
3821 addVirginReplyBody(dataToWrite, dataLength);
3822 }
3823
3824 /**
3825 * called after we wrote the last byte of the request body
3826 */
3827 void
3828 FtpStateData::doneSendingRequestBody()
3829 {
3830 debugs(9,3, HERE);
3831 dataComplete();
3832 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3833 * if transfer type is 'stream' call dataComplete()
3834 * otherwise leave open. (reschedule control channel read?)
3835 */
3836 }
3837
3838 /**
3839 * A hack to ensure we do not double-complete on the forward entry.
3840 *
3841 \todo FtpStateData logic should probably be rewritten to avoid
3842 * double-completion or FwdState should be rewritten to allow it.
3843 */
3844 void
3845 FtpStateData::completeForwarding()
3846 {
3847 if (fwd == NULL || flags.completed_forwarding) {
3848 debugs(9, 3, HERE << "completeForwarding avoids " <<
3849 "double-complete on FD " << ctrl.fd << ", Data FD " << data.fd <<
3850 ", this " << this << ", fwd " << fwd);
3851 return;
3852 }
3853
3854 flags.completed_forwarding = true;
3855 ServerStateData::completeForwarding();
3856 }
3857
3858 /**
3859 * Close the FTP server connection(s). Used by serverComplete().
3860 */
3861 void
3862 FtpStateData::closeServer()
3863 {
3864 debugs(9,3, HERE << "closing FTP server FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
3865
3866 if (ctrl.fd > -1) {
3867 fwd->unregister(ctrl.fd);
3868 ctrl.close();
3869 }
3870
3871 data.close();
3872 }
3873
3874 /**
3875 * Did we close all FTP server connection(s)?
3876 *
3877 \retval true Both server control and data channels are closed. And not waitigng for a new data connection to open.
3878 \retval false Either control channel or data is still active.
3879 */
3880 bool
3881 FtpStateData::doneWithServer() const
3882 {
3883 return ctrl.fd < 0 && data.fd < 0;
3884 }
3885
3886 /**
3887 * Have we lost the FTP server control channel?
3888 *
3889 \retval true The server control channel is available.
3890 \retval false The server control channel is not available.
3891 */
3892 bool
3893 FtpStateData::haveControlChannel(const char *caller_name) const
3894 {
3895 if (doneWithServer())
3896 return false;
3897
3898 /* doneWithServer() only checks BOTH channels are closed. */
3899 if (ctrl.fd < 0) {
3900 debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
3901 debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
3902 return false;
3903 }
3904
3905 return true;
3906 }
3907
3908 /**
3909 * Quickly abort the transaction
3910 *
3911 \todo destruction should be sufficient as the destructor should cleanup,
3912 * including canceling close handlers
3913 */
3914 void
3915 FtpStateData::abortTransaction(const char *reason)
3916 {
3917 debugs(9, 3, HERE << "aborting transaction for " << reason <<
3918 "; FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
3919 if (ctrl.fd >= 0) {
3920 comm_close(ctrl.fd);
3921 return;
3922 }
3923
3924 fwd->handleUnregisteredServerEnd();
3925 deleteThis("FtpStateData::abortTransaction");
3926 }
3927
3928 /// creates a data channel Comm close callback
3929 AsyncCall::Pointer
3930 FtpStateData::dataCloser()
3931 {
3932 typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
3933 return asyncCall(9, 5, "FtpStateData::dataClosed",
3934 Dialer(this, &FtpStateData::dataClosed));
3935 }
3936
3937 /// configures the channel with a descriptor and registers a close handler
3938 void
3939 FtpChannel::opened(int aFd, const AsyncCall::Pointer &aCloser)
3940 {
3941 assert(fd < 0);
3942 assert(closer == NULL);
3943
3944 assert(aFd >= 0);
3945 assert(aCloser != NULL);
3946
3947 fd = aFd;
3948 closer = aCloser;
3949 comm_add_close_handler(fd, closer);
3950 }
3951
3952 /// planned close: removes the close handler and calls comm_close
3953 void
3954 FtpChannel::close()
3955 {
3956 // channels with active listeners will be closed when the listener handler dies.
3957 if (listener) {
3958 delete listener;
3959 listener = NULL;
3960 comm_remove_close_handler(fd, closer);
3961 closer = NULL;
3962 fd = -1;
3963 } else if (fd >= 0) {
3964 comm_remove_close_handler(fd, closer);
3965 closer = NULL;
3966 comm_close(fd); // we do not expect to be called back
3967 fd = -1;
3968 }
3969 }
3970
3971 /// just resets fd and close handler
3972 void
3973 FtpChannel::clear()
3974 {
3975 fd = -1;
3976 closer = NULL;
3977 }