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