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