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