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