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