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