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