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