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