]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
update comments
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
be335c22 1
30a4f2a8 2/*
a15705d0 3 * $Id: ftp.cc,v 1.222 1998/04/25 07:25:41 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 9 File Transfer Protocol (FTP)
6 * AUTHOR: Harvest Derived
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
019dd986 31
44a47c6e 32#include "squid.h"
090089c4 33
3fdadc70 34enum {
35 FTP_ISDIR,
36 FTP_PASV_SUPPORTED,
37 FTP_SKIP_WHITESPACE,
38 FTP_REST_SUPPORTED,
39 FTP_PASV_ONLY,
40 FTP_AUTHENTICATED,
3fdadc70 41 FTP_HTTP_HEADER_SENT,
42 FTP_TRIED_NLST,
43 FTP_USE_BASE,
44 FTP_ROOT_DIR,
13e80e5b 45 FTP_NO_DOTDOT,
2761b2be 46 FTP_HTML_HEADER_SENT,
969c39b9 47 FTP_BINARY,
dbfed404 48 FTP_TRY_SLASH_HACK,
54220df8 49 FTP_PUT,
50 FTP_PUT_MKDIR,
0f169992 51 FTP_LISTFORMAT_UNKNOWN,
52 FTP_DATACHANNEL_HACK
3fdadc70 53};
54
55static const char *const crlf = "\r\n";
56static char cbuf[1024];
57
58typedef enum {
59 BEGIN,
60 SENT_USER,
61 SENT_PASS,
62 SENT_TYPE,
63 SENT_MDTM,
64 SENT_SIZE,
65 SENT_PORT,
66 SENT_PASV,
67 SENT_CWD,
68 SENT_LIST,
69 SENT_NLST,
70 SENT_REST,
71 SENT_RETR,
54220df8 72 SENT_STOR,
3fdadc70 73 SENT_QUIT,
54220df8 74 READING_DATA,
75 WRITING_DATA,
76 SENT_MKDIR
3fdadc70 77} ftp_state_t;
090089c4 78
79typedef struct _Ftpdata {
80 StoreEntry *entry;
983061ed 81 request_t *request;
77a30ebb 82 char user[MAX_URL];
83 char password[MAX_URL];
f0afe435 84 char *reply_hdr;
f0afe435 85 int reply_hdr_state;
3fdadc70 86 char *title_url;
87 int conn_att;
88 int login_att;
89 ftp_state_t state;
3fdadc70 90 time_t mdtm;
91 int size;
92 int flags;
93 wordlist *pathcomps;
94 char *filepath;
95 int restart_offset;
96 int rest_att;
97 char *proxy_host;
98 size_t list_width;
99 wordlist *cwd_message;
969c39b9 100 char *old_request;
101 char *old_reply;
0f169992 102 char *old_filepath;
9e242e02 103 char typecode;
3fdadc70 104 struct {
105 int fd;
106 char *buf;
107 size_t size;
108 off_t offset;
109 FREE *freefunc;
110 wordlist *message;
b9916917 111 char *last_command;
112 char *last_reply;
3fdadc70 113 int replycode;
114 } ctrl;
115 struct {
116 int fd;
117 char *buf;
118 size_t size;
119 off_t offset;
120 FREE *freefunc;
9b312a19 121 char *host;
122 u_short port;
3fdadc70 123 } data;
e5f6c5c2 124} FtpStateData;
090089c4 125
3fdadc70 126typedef struct {
127 char type;
128 int size;
129 char *date;
130 char *name;
131 char *showname;
132 char *link;
133} ftpListParts;
134
ea3a2a69 135typedef void (FTPSM) (FtpStateData *);
0a0bf5db 136
983061ed 137/* Local functions */
9e4ad609 138static CNCB ftpConnectDone;
3fdadc70 139static CNCB ftpPasvCallback;
dbfed404 140static PF ftpDataRead;
9e4ad609 141static PF ftpStateFree;
5c5783a2 142static PF ftpTimeout;
3fdadc70 143static PF ftpReadControlReply;
144static CWCB ftpWriteCommandCallback;
f5b8bbc4 145static void ftpLoginParser(const char *, FtpStateData *);
f5b8bbc4 146static wordlist *ftpParseControlReply(char *buf, size_t len, int *code);
f5b8bbc4 147static void ftpAppendSuccessHeader(FtpStateData * ftpState);
ee1679df 148static void ftpAuthRequired(HttpReply * reply, request_t * request, const char *realm);
bfcaf585 149static STABH ftpAbort;
969c39b9 150static void ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState);
54220df8 151static void ftpPutStart(FtpStateData *);
152static CWCB ftpPutTransferDone;
0f169992 153static void ftpUnhack(FtpStateData * ftpState);
983061ed 154
969c39b9 155/* State machine functions
156 * send == state transition
157 * read == wait for response, and select next state transition
dbfed404 158 * other == Transition logic
969c39b9 159 */
3fdadc70 160static FTPSM ftpReadWelcome;
969c39b9 161static FTPSM ftpSendUser;
3fdadc70 162static FTPSM ftpReadUser;
969c39b9 163static FTPSM ftpSendPass;
3fdadc70 164static FTPSM ftpReadPass;
969c39b9 165static FTPSM ftpSendType;
3fdadc70 166static FTPSM ftpReadType;
969c39b9 167static FTPSM ftpSendMdtm;
3fdadc70 168static FTPSM ftpReadMdtm;
969c39b9 169static FTPSM ftpSendSize;
3fdadc70 170static FTPSM ftpReadSize;
969c39b9 171static FTPSM ftpSendPort;
3fdadc70 172static FTPSM ftpReadPort;
969c39b9 173static FTPSM ftpSendPasv;
3fdadc70 174static FTPSM ftpReadPasv;
dbfed404 175static FTPSM ftpTraverseDirectory;
176static FTPSM ftpListDir;
177static FTPSM ftpGetFile;
969c39b9 178static FTPSM ftpSendCwd;
3fdadc70 179static FTPSM ftpReadCwd;
969c39b9 180static FTPSM ftpSendList;
181static FTPSM ftpSendNlst;
3fdadc70 182static FTPSM ftpReadList;
969c39b9 183static FTPSM ftpSendRest;
3fdadc70 184static FTPSM ftpReadRest;
969c39b9 185static FTPSM ftpSendRetr;
3fdadc70 186static FTPSM ftpReadRetr;
187static FTPSM ftpReadTransferDone;
969c39b9 188static FTPSM ftpSendQuit;
189static FTPSM ftpReadQuit;
190static FTPSM ftpFail;
dbfed404 191static FTPSM ftpDataTransferDone;
192static FTPSM ftpRestOrList;
54220df8 193static FTPSM ftpSendStor;
194static FTPSM ftpReadStor;
195static FTPSM ftpSendReply;
196static FTPSM ftpTryMkdir;
197static FTPSM ftpReadMkdir;
dbfed404 198/************************************************
199** State Machine Description (excluding hacks) **
200*************************************************
201From To
202---------------------------------------
203Welcome User
204User Pass
205Pass Type
206Type TraverseDirectory / GetFile
207TraverseDirectory Cwd / GetFile / ListDir
208Cwd TraverseDirectory
209GetFile Mdtm
210Mdtm Size
211Size Pasv
212ListDir Pasv
213Pasv RestOrList
214RestOrList Rest / Retr / Nlst / List
215Rest Retr
216Retr / Nlst / List (ftpDataRead on datachannel)
217(ftpDataRead) ReadTransferDone
218ReadTransferDone DataTransferDone
219DataTransferDone Quit
220Quit -
221************************************************/
3fdadc70 222
223FTPSM *FTP_SM_FUNCS[] =
224{
225 ftpReadWelcome,
226 ftpReadUser,
227 ftpReadPass,
228 ftpReadType,
229 ftpReadMdtm,
230 ftpReadSize,
231 ftpReadPort,
232 ftpReadPasv,
233 ftpReadCwd,
234 ftpReadList, /* SENT_LIST */
235 ftpReadList, /* SENT_NLST */
236 ftpReadRest,
237 ftpReadRetr,
54220df8 238 ftpReadStor,
3fdadc70 239 ftpReadQuit,
54220df8 240 ftpReadTransferDone,
241 ftpSendReply,
242 ftpReadMkdir
3fdadc70 243};
e381a13d 244
582b6456 245static void
79d39a72 246ftpStateFree(int fdnotused, void *data)
ba718c8f 247{
582b6456 248 FtpStateData *ftpState = data;
51fa90db 249 if (ftpState == NULL)
582b6456 250 return;
c3b838d4 251 debug(9, 3) ("ftpStateFree: %s\n", storeUrl(ftpState->entry));
bfcaf585 252 storeUnregisterAbort(ftpState->entry);
f88211e8 253 storeUnlockObject(ftpState->entry);
51fa90db 254 if (ftpState->reply_hdr) {
3f6c0fb2 255 memFree(MEM_8K_BUF, ftpState->reply_hdr);
51fa90db 256 ftpState->reply_hdr = NULL;
f0afe435 257 }
30a4f2a8 258 requestUnlink(ftpState->request);
3fdadc70 259 if (ftpState->ctrl.buf)
260 ftpState->ctrl.freefunc(ftpState->ctrl.buf);
261 if (ftpState->data.buf)
262 ftpState->data.freefunc(ftpState->data.buf);
263 if (ftpState->pathcomps)
264 wordlistDestroy(&ftpState->pathcomps);
265 if (ftpState->ctrl.message)
266 wordlistDestroy(&ftpState->ctrl.message);
267 if (ftpState->cwd_message)
268 wordlistDestroy(&ftpState->cwd_message);
b9916917 269 safe_free(ftpState->ctrl.last_reply);
270 safe_free(ftpState->ctrl.last_command);
969c39b9 271 safe_free(ftpState->old_request);
272 safe_free(ftpState->old_reply);
0f169992 273 safe_free(ftpState->old_filepath);
b5639035 274 safe_free(ftpState->title_url);
275 safe_free(ftpState->filepath);
9b312a19 276 safe_free(ftpState->data.host);
a0eff6be 277 if (ftpState->data.fd > -1) {
15888cf7 278 comm_close(ftpState->data.fd);
a0eff6be 279 ftpState->data.fd = -1;
280 }
7dd44885 281 cbdataFree(ftpState);
ba718c8f 282}
283
b8d8561b 284static void
9e4ad609 285ftpLoginParser(const char *login, FtpStateData * ftpState)
090089c4 286{
983061ed 287 char *s = NULL;
582b6456 288 xstrncpy(ftpState->user, login, MAX_URL);
289 if ((s = strchr(ftpState->user, ':'))) {
983061ed 290 *s = 0;
582b6456 291 xstrncpy(ftpState->password, s + 1, MAX_URL);
983061ed 292 } else {
582b6456 293 xstrncpy(ftpState->password, null_string, MAX_URL);
983061ed 294 }
582b6456 295 if (ftpState->user[0] || ftpState->password[0])
429fdbec 296 return;
582b6456 297 xstrncpy(ftpState->user, "anonymous", MAX_URL);
e34e0322 298 xstrncpy(ftpState->password, Config.Ftp.anon_user, MAX_URL);
090089c4 299}
300
24382924 301static void
5c5783a2 302ftpTimeout(int fd, void *data)
090089c4 303{
582b6456 304 FtpStateData *ftpState = data;
305 StoreEntry *entry = ftpState->entry;
9b312a19 306 ErrorState *err;
9fb13bb6 307 debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd, storeUrl(entry));
185b9571 308 if (entry->store_status == STORE_PENDING) {
309 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 310 err = errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT);
185b9571 311 err->request = requestLink(ftpState->request);
312 errorAppendEntry(entry, err);
b50179a6 313 } else {
314 storeAbort(entry, 0);
185b9571 315 }
9b312a19 316 }
bd200c90 317 if (ftpState->data.fd > -1) {
3fdadc70 318 comm_close(ftpState->data.fd);
15888cf7 319 ftpState->data.fd = -1;
320 }
3fdadc70 321 comm_close(ftpState->ctrl.fd);
3c30232f 322 /* don't modify ftpState here, it has been freed */
090089c4 323}
324
3fdadc70 325static void
326ftpListingStart(FtpStateData * ftpState)
327{
328 StoreEntry *e = ftpState->entry;
329 wordlist *w;
438fc1e3 330 storeBuffer(e);
3fdadc70 331 storeAppendPrintf(e, "<!-- HTML listing generated by Squid %s -->\n",
332 version_string);
333 storeAppendPrintf(e, "<!-- %s -->\n", mkrfc1123(squid_curtime));
334 storeAppendPrintf(e, "<HTML><HEAD><TITLE>\n");
335 storeAppendPrintf(e, "FTP Directory: %s\n",
336 ftpState->title_url);
337 storeAppendPrintf(e, "</TITLE>\n");
338 if (EBIT_TEST(ftpState->flags, FTP_USE_BASE))
339 storeAppendPrintf(e, "<BASE HREF=\"%s\">\n",
13e80e5b 340 ftpState->title_url);
3fdadc70 341 storeAppendPrintf(e, "</HEAD><BODY>\n");
342 if (ftpState->cwd_message) {
343 storeAppendPrintf(e, "<PRE>\n");
344 for (w = ftpState->cwd_message; w; w = w->next)
345 storeAppendPrintf(e, "%s\n", w->key);
346 storeAppendPrintf(e, "</PRE>\n");
347 storeAppendPrintf(e, "<HR>\n");
348 wordlistDestroy(&ftpState->cwd_message);
349 }
350 storeAppendPrintf(e, "<H2>\n");
351 storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url);
352 storeAppendPrintf(e, "</H2>\n");
353 storeAppendPrintf(e, "<PRE>\n");
438fc1e3 354 storeBufferFlush(e);
3fdadc70 355 EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT);
356}
357
358static void
359ftpListingFinish(FtpStateData * ftpState)
360{
361 StoreEntry *e = ftpState->entry;
438fc1e3 362 storeBuffer(e);
3fdadc70 363 storeAppendPrintf(e, "</PRE>\n");
dbfed404 364 if (EBIT_TEST(ftpState->flags, FTP_LISTFORMAT_UNKNOWN) && !EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
365 storeAppendPrintf(e, "<A HREF=\"./;type=d\">[As plain directory]</A>\n");
0e473d70 366 } else if (ftpState->typecode == 'D') {
dbfed404 367 storeAppendPrintf(e, "<A HREF=\"./\">[As extended directory]</A>\n");
368 }
3fdadc70 369 storeAppendPrintf(e, "<HR>\n");
370 storeAppendPrintf(e, "<ADDRESS>\n");
a15705d0 371 storeAppendPrintf(e, "Generated %s by %s (<a href=\"http://squid.nlanr.net/Squid/\">%s</a>)\n",
3fdadc70 372 mkrfc1123(squid_curtime),
a15705d0 373 getMyHostname(),
374 full_appname_string,
375 version_string);
3fdadc70 376 storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
438fc1e3 377 storeBufferFlush(e);
3fdadc70 378}
379
380static const char *Month[] =
381{
382 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
383 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
384};
385
386static int
387is_month(const char *buf)
388{
389 int i;
390 for (i = 0; i < 12; i++)
391 if (!strcasecmp(buf, Month[i]))
392 return 1;
393 return 0;
394}
395
396
397static void
398ftpListPartsFree(ftpListParts ** parts)
399{
400 safe_free((*parts)->date);
401 safe_free((*parts)->name);
402 safe_free((*parts)->showname);
403 safe_free((*parts)->link);
404 safe_free(*parts);
405}
406
407#define MAX_TOKENS 64
408
75e5a32e 409#define SCAN_FTP1 "%[0123456789]"
410#define SCAN_FTP2 "%[0123456789:]"
411#define SCAN_FTP3 "%[0123456789]-%[0123456789]-%[0123456789]"
412#define SCAN_FTP4 "%[0123456789]:%[0123456789]%[AaPp]%[Mm]"
413
3fdadc70 414static ftpListParts *
415ftpListParseParts(const char *buf, int flags)
416{
417 ftpListParts *p = NULL;
418 char *t = NULL;
419 const char *ct = NULL;
420 char *tokens[MAX_TOKENS];
421 int i;
422 int n_tokens;
423 static char sbuf[128];
424 char *xbuf = NULL;
425 if (buf == NULL)
426 return NULL;
427 if (*buf == '\0')
428 return NULL;
429 p = xcalloc(1, sizeof(ftpListParts));
430 n_tokens = 0;
431 for (i = 0; i < MAX_TOKENS; i++)
432 tokens[i] = (char *) NULL;
433 xbuf = xstrdup(buf);
dbfed404 434 if (EBIT_TEST(flags, FTP_TRIED_NLST)) {
435 /* Machine readable format, one name per line */
0e473d70 436 p->name = xbuf;
437 p->type = '\0';
dbfed404 438 return p;
439 }
3fdadc70 440 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
441 tokens[n_tokens++] = xstrdup(t);
442 xfree(xbuf);
443 /* locate the Month field */
444 for (i = 3; i < n_tokens - 3; i++) {
445 if (!is_month(tokens[i])) /* Month */
446 continue;
75e5a32e 447 if (!sscanf(tokens[i - 1], SCAN_FTP1, sbuf)) /* Size */
3fdadc70 448 continue;
75e5a32e 449 if (!sscanf(tokens[i + 1], SCAN_FTP1, sbuf)) /* Day */
3fdadc70 450 continue;
75e5a32e 451 if (!sscanf(tokens[i + 2], SCAN_FTP2, sbuf)) /* Yr | hh:mm */
3fdadc70 452 continue;
453 p->type = *tokens[0];
454 p->size = atoi(tokens[i - 1]);
042461c3 455 snprintf(sbuf, 128, "%s %2s %5s",
3fdadc70 456 tokens[i], tokens[i + 1], tokens[i + 2]);
457 if (!strstr(buf, sbuf))
042461c3 458 snprintf(sbuf, 128, "%s %2s %-5s",
3fdadc70 459 tokens[i], tokens[i + 1], tokens[i + 2]);
460 if ((t = strstr(buf, sbuf))) {
461 p->date = xstrdup(sbuf);
b9916917 462 if (EBIT_TEST(flags, FTP_SKIP_WHITESPACE)) {
3fdadc70 463 t += strlen(sbuf);
464 while (strchr(w_space, *t))
465 t++;
466 } else {
467 /* XXX assumes a single space between date and filename
468 * suggested by: Nathan.Bailey@cc.monash.edu.au and
469 * Mike Battersby <mike@starbug.bofh.asn.au> */
470 t += strlen(sbuf) + 1;
471 }
472 p->name = xstrdup(t);
473 if ((t = strstr(p->name, " -> "))) {
474 *t = '\0';
475 p->link = xstrdup(t + 4);
476 }
477 }
478 break;
479 }
480 /* try it as a DOS listing */
481 if (n_tokens > 3 && p->name == NULL &&
75e5a32e 482 sscanf(tokens[0], SCAN_FTP3, sbuf, sbuf, sbuf) == 3 &&
3fdadc70 483 /* 04-05-70 */
75e5a32e 484 sscanf(tokens[1], SCAN_FTP4, sbuf, sbuf, sbuf, sbuf) == 4) {
3fdadc70 485 /* 09:33PM */
486 if (!strcasecmp(tokens[2], "<dir>")) {
487 p->type = 'd';
488 } else {
489 p->type = '-';
490 p->size = atoi(tokens[2]);
491 }
042461c3 492 snprintf(sbuf, 128, "%s %s", tokens[0], tokens[1]);
3fdadc70 493 p->date = xstrdup(sbuf);
494 p->name = xstrdup(tokens[3]);
495 }
496 /* Try EPLF format; carson@lehman.com */
497 if (p->name == NULL && buf[0] == '+') {
498 ct = buf + 1;
499 p->type = 0;
500 while (ct && *ct) {
501 switch (*ct) {
502 case '\t':
503 sscanf(ct + 1, "%[^,]", sbuf);
504 p->name = xstrdup(sbuf);
505 break;
506 case 's':
507 sscanf(ct + 1, "%d", &(p->size));
508 break;
509 case 'm':
510 sscanf(ct + 1, "%d", &i);
511 p->date = xstrdup(ctime((time_t *) & i));
512 *(strstr(p->date, "\n")) = '\0';
513 break;
514 case '/':
515 p->type = 'd';
516 break;
517 case 'r':
518 p->type = '-';
519 break;
520 case 'i':
521 break;
522 default:
523 break;
524 }
525 ct = strstr(ct, ",");
526 if (ct) {
527 ct++;
528 }
529 }
530 if (p->type == 0) {
531 p->type = '-';
532 }
533 }
534 for (i = 0; i < n_tokens; i++)
535 xfree(tokens[i]);
b5639035 536 if (p->name == NULL)
537 ftpListPartsFree(&p);
3fdadc70 538 return p;
539}
540
541static const char *
542dots_fill(size_t len)
543{
544 static char buf[256];
545 int i = 0;
546 if (len > Config.Ftp.list_width) {
547 memset(buf, ' ', 256);
548 buf[0] = '\n';
549 buf[Config.Ftp.list_width + 4] = '\0';
550 return buf;
551 }
552 for (i = (int) len; i < Config.Ftp.list_width; i++)
553 buf[i - len] = (i % 2) ? '.' : ' ';
554 buf[i - len] = '\0';
555 return buf;
556}
557
558static char *
dbfed404 559ftpHtmlifyListEntry(char *line, FtpStateData * ftpState)
3fdadc70 560{
13e80e5b 561 LOCAL_ARRAY(char, link, 2048 + 40);
9fc0b4b8 562 LOCAL_ARRAY(char, link2, 2048 + 40);
3fdadc70 563 LOCAL_ARRAY(char, icon, 2048);
564 LOCAL_ARRAY(char, html, 8192);
3fdadc70 565 size_t width = Config.Ftp.list_width;
566 ftpListParts *parts;
dbfed404 567 int flags = ftpState->flags;
69e81830 568 if ((int) strlen(line) > 1024) {
042461c3 569 snprintf(html, 8192, "%s\n", line);
3fdadc70 570 return html;
571 }
572 if ((parts = ftpListParseParts(line, flags)) == NULL) {
0e473d70 573 char *p;
042461c3 574 snprintf(html, 8192, "%s\n", line);
0e473d70 575 for (p = line; *p && isspace(*p); p++);
dbfed404 576 if (*p && !isspace(*p))
577 EBIT_SET(ftpState->flags, FTP_LISTFORMAT_UNKNOWN);
3fdadc70 578 return html;
579 }
13e80e5b 580 /* check .. as special case */
581 if (!strcmp(parts->name, "..")) {
4162ee3b 582 snprintf(icon, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
583 mimeGetIconURL("internal-dirup"),
584 "[DIRUP]");
13e80e5b 585 if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) {
586 /* Normal directory */
587 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
588 "../",
589 "Parent Directory");
590 } else if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && EBIT_TEST(flags, FTP_ROOT_DIR)) {
591 /* "Top level" directory */
592 snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)",
593 "%2e%2e/",
594 "Parent Directory",
595 "%2f/",
596 "Root Directory");
597 } else if (EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) {
598 /* Normal directory where last component is / or .. */
599 snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)",
600 "%2e%2e/",
601 "Parent Directory",
602 "../",
603 "Up");
604 } else { /* NO_DOTDOT && ROOT_DIR */
605 /* "UNIX Root" directory */
606 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
607 "../",
608 "Home Directory");
609 }
610 snprintf(html, 8192, "%s %s\n", icon, link);
611 ftpListPartsFree(&parts);
612 return html;
613 }
3fdadc70 614 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
615 *html = '\0';
b5639035 616 ftpListPartsFree(&parts);
3fdadc70 617 return html;
618 }
619 parts->size += 1023;
620 parts->size >>= 10;
621 parts->showname = xstrdup(parts->name);
622 if (!Config.Ftp.list_wrap) {
623 if (strlen(parts->showname) > width - 1) {
624 *(parts->showname + width - 1) = '>';
625 *(parts->showname + width - 0) = '\0';
626 }
627 }
3fdadc70 628 switch (parts->type) {
629 case 'd':
4162ee3b 630 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
631 mimeGetIconURL("internal-dir"),
3fdadc70 632 "[DIR]");
56878878 633 snprintf(link, 2048, "<A HREF=\"%s/\">%s</A>%s",
9fc0b4b8 634 rfc1738_escape(parts->name),
3fdadc70 635 parts->showname,
636 dots_fill(strlen(parts->showname)));
042461c3 637 snprintf(html, 8192, "%s %s [%s]\n",
3fdadc70 638 icon,
639 link,
640 parts->date);
641 break;
642 case 'l':
4162ee3b 643 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
644 mimeGetIconURL("internal-link"),
3fdadc70 645 "[LINK]");
042461c3 646 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s",
9fc0b4b8 647 rfc1738_escape(parts->name),
3fdadc70 648 parts->showname,
649 dots_fill(strlen(parts->showname)));
7b63416b 650 /* sometimes there is an 'l' flag, but no "->" link */
651 if (parts->link)
652 snprintf(link2, 2048, "<A HREF=\"%s\">%s</A>",
653 rfc1738_escape(parts->link),
654 parts->link);
9fc0b4b8 655 snprintf(html, 8192, "%s %s [%s] -> %s\n",
3fdadc70 656 icon,
657 link,
9fc0b4b8 658 parts->date,
659 link2);
3fdadc70 660 break;
dbfed404 661 case '\0':
4162ee3b 662 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
663 mimeGetIconURL(parts->name),
dbfed404 664 "[UNKNOWN]");
665 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
666 rfc1738_escape(parts->name),
667 parts->name);
668 snprintf(link2, 2048, "(<A HREF=\"%s/;type=d\">chdir</A>)",
669 rfc1738_escape(parts->name));
670 snprintf(html, 8192, "%s %s %s\n",
671 icon,
672 link,
673 link2);
674 break;
3fdadc70 675 case '-':
676 default:
4162ee3b 677 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
678 mimeGetIconURL(parts->name),
3fdadc70 679 "[FILE]");
042461c3 680 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s",
9fc0b4b8 681 rfc1738_escape(parts->name),
3fdadc70 682 parts->showname,
683 dots_fill(strlen(parts->showname)));
042461c3 684 snprintf(html, 8192, "%s %s [%s] %6dk\n",
3fdadc70 685 icon,
686 link,
687 parts->date,
688 parts->size);
689 break;
690 }
691 ftpListPartsFree(&parts);
3fdadc70 692 return html;
693}
694
695static void
696ftpParseListing(FtpStateData * ftpState, int len)
697{
698 char *buf = ftpState->data.buf;
7131112f 699 char *sbuf; /* NULL-terminated copy of buf */
b5639035 700 char *end;
701 char *line;
3fdadc70 702 char *s;
703 char *t;
704 size_t linelen;
b5639035 705 size_t usable;
3fdadc70 706 StoreEntry *e = ftpState->entry;
7131112f 707 /*
708 * There may have been 'data.offset' bytes left over from a previous
709 * call here
710 */
b5639035 711 len += ftpState->data.offset;
7131112f 712 /*
713 * We need a NULL-terminated buffer for scanning, ick
714 */
715 sbuf = xmalloc(len + 1);
716 xstrncpy(sbuf, buf, len + 1);
717 end = sbuf + len - 1;
718 while (*end != '\r' && *end != '\n' && end > sbuf)
3fdadc70 719 end--;
7131112f 720 usable = end - sbuf;
362be274 721 debug(9,3)("ftpParseListing: usable = %d\n", usable);
b5639035 722 if (usable == 0) {
9fb13bb6 723 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e));
7131112f 724 xfree(sbuf);
3fdadc70 725 return;
726 }
7131112f 727 debug(9, 3) ("ftpParseListing: %d bytes to play with\n", len);
7021844c 728 line = memAllocate(MEM_4K_BUF);
3fdadc70 729 end++;
438fc1e3 730 storeBuffer(e);
362be274 731 s = sbuf;
732 s += strspn(s, crlf);
733 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
7131112f 734 debug(9, 3) ("ftpParseListing: s = {%s}\n", s);
3fdadc70 735 linelen = strcspn(s, crlf) + 1;
fc09761c 736 if (linelen < 2)
737 break;
3fdadc70 738 if (linelen > 4096)
739 linelen = 4096;
740 xstrncpy(line, s, linelen);
7131112f 741 debug(9, 7) ("ftpParseListing: {%s}\n", line);
3fdadc70 742 if (!strncmp(line, "total", 5))
743 continue;
dbfed404 744 t = ftpHtmlifyListEntry(line, ftpState);
3fdadc70 745 assert(t != NULL);
746 storeAppend(e, t, strlen(t));
747 }
438fc1e3 748 storeBufferFlush(e);
b5639035 749 assert(usable <= len);
750 if (usable < len) {
751 /* must copy partial line to beginning of buf */
e29f7586 752 linelen = len - usable;
b5639035 753 if (linelen > 4096)
754 linelen = 4096;
755 xstrncpy(line, end, linelen);
756 xstrncpy(ftpState->data.buf, line, ftpState->data.size);
757 ftpState->data.offset = strlen(ftpState->data.buf);
758 }
3f6c0fb2 759 memFree(MEM_4K_BUF, line);
4a2635aa 760 xfree(sbuf);
3fdadc70 761}
090089c4 762
79a15e0a 763static void
764ftpReadComplete(FtpStateData * ftpState)
765{
4cca8b93 766 debug(9, 3) ("ftpReadComplete\n");
79a15e0a 767 /* Connection closed; retrieval done. */
768 if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
769 ftpListingFinish(ftpState);
54220df8 770 if (!EBIT_TEST(ftpState->flags, FTP_PUT)) {
4162ee3b 771 storeTimestampsSet(ftpState->entry);
772 storeComplete(ftpState->entry);
54220df8 773 }
79a15e0a 774 /* expect the "transfer complete" message on the control socket */
775 commSetSelect(ftpState->ctrl.fd,
776 COMM_SELECT_READ,
777 ftpReadControlReply,
778 ftpState,
779 Config.Timeout.read);
780}
781
582b6456 782static void
dbfed404 783ftpDataRead(int fd, void *data)
090089c4 784{
582b6456 785 FtpStateData *ftpState = data;
090089c4 786 int len;
a57512fa 787 int j;
30a4f2a8 788 int bin;
bfcaf585 789 StoreEntry *entry = ftpState->entry;
79a15e0a 790 MemObject *mem = entry->mem_obj;
3fdadc70 791 assert(fd == ftpState->data.fd);
3fdadc70 792 if (protoAbortFetch(entry)) {
9b312a19 793 storeAbort(entry, 0);
a3d5953d 794 ftpDataTransferDone(ftpState);
795 return;
30a4f2a8 796 }
1a6e21ac 797 errno = 0;
3fdadc70 798 memset(ftpState->data.buf + ftpState->data.offset, '\0',
799 ftpState->data.size - ftpState->data.offset);
800 len = read(fd,
801 ftpState->data.buf + ftpState->data.offset,
802 ftpState->data.size - ftpState->data.offset);
ee1679df 803 if (len > 0) {
804 fd_bytes(fd, len, FD_READ);
a0f32775 805 kb_incr(&Counter.server.all.kbytes_in, len);
806 kb_incr(&Counter.server.ftp.kbytes_in, len);
ee1679df 807 }
dbfed404 808 debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd, len);
30a4f2a8 809 if (len > 0) {
4a63c85f 810 IOStats.Ftp.reads++;
a57512fa 811 for (j = len - 1, bin = 0; j; bin++)
812 j >>= 1;
30a4f2a8 813 IOStats.Ftp.read_hist[bin]++;
814 }
ba718c8f 815 if (len < 0) {
dbfed404 816 debug(50, 1) ("ftpDataRead: read error: %s\n", xstrerror());
b224ea98 817 if (ignoreErrno(errno)) {
a57512fa 818 commSetSelect(fd,
819 COMM_SELECT_READ,
dbfed404 820 ftpDataRead,
a57512fa 821 data,
822 Config.Timeout.read);
6fe6313d 823 } else {
a57512fa 824 assert(mem->inmem_hi > 0);
825 storeAbort(entry, 0);
3fdadc70 826 ftpDataTransferDone(ftpState);
6fe6313d 827 }
090089c4 828 } else if (len == 0) {
79a15e0a 829 ftpReadComplete(ftpState);
090089c4 830 } else {
b5639035 831 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
a57512fa 832 if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
833 ftpListingStart(ftpState);
3fdadc70 834 ftpParseListing(ftpState, len);
b5639035 835 } else {
836 assert(ftpState->data.offset == 0);
837 storeAppend(entry, ftpState->data.buf, len);
838 }
48451fb1 839 if (ftpState->size > 0 && mem->inmem_hi >= ftpState->size + mem->reply->hdr_sz)
79a15e0a 840 ftpReadComplete(ftpState);
841 else
842 commSetSelect(fd,
843 COMM_SELECT_READ,
dbfed404 844 ftpDataRead,
79a15e0a 845 data,
846 Config.Timeout.read);
090089c4 847 }
090089c4 848}
849
429fdbec 850/*
851 * ftpCheckAuth
852 *
853 * Return 1 if we have everything needed to complete this request.
854 * Return 0 if something is missing.
855 */
856static int
857ftpCheckAuth(FtpStateData * ftpState, char *req_hdr)
858{
859 char *orig_user;
63259c34 860 const char *auth;
9e4ad609 861 ftpLoginParser(ftpState->request->login, ftpState);
429fdbec 862 if (ftpState->user[0] && ftpState->password[0])
863 return 1; /* name and passwd both in URL */
864 if (!ftpState->user[0] && !ftpState->password[0])
865 return 1; /* no name or passwd */
866 if (ftpState->password[0])
867 return 1; /* passwd with no name? */
868 /* URL has name, but no passwd */
63259c34 869 if (!(auth = mime_get_auth(req_hdr, "Basic", NULL)))
429fdbec 870 return 0; /* need auth header */
871 orig_user = xstrdup(ftpState->user);
9e4ad609 872 ftpLoginParser(auth, ftpState);
429fdbec 873 if (!strcmp(orig_user, ftpState->user)) {
874 xfree(orig_user);
875 return 1; /* same username */
876 }
877 strcpy(ftpState->user, orig_user);
878 xfree(orig_user);
879 return 0; /* different username */
880}
881
13e80e5b 882static void
883ftpCheckUrlpath(FtpStateData * ftpState)
884{
885 request_t *request = ftpState->request;
886 int l;
02922e76 887 const char *t;
888 if ((t = strRChr(request->urlpath, ';')) != NULL) {
dbfed404 889 if (strncasecmp(t + 1, "type=", 5) == 0) {
890 ftpState->typecode = (char) toupper((int) *(t + 6));
02922e76 891 strSet(request->urlpath, t, '\0');
dbfed404 892 }
893 }
02922e76 894 l = strLen(request->urlpath);
13e80e5b 895 EBIT_SET(ftpState->flags, FTP_USE_BASE);
896 /* check for null path */
02922e76 897 if (!l) {
898 stringReset(&request->urlpath, "/");
13e80e5b 899 EBIT_SET(ftpState->flags, FTP_ISDIR);
900 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
02922e76 901 } else if (!strCmp(request->urlpath, "/%2f/")) {
13e80e5b 902 EBIT_SET(ftpState->flags, FTP_ISDIR);
903 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
02922e76 904 } else if ((l >= 1) && (*(strBuf(request->urlpath) + l - 1) == '/')) {
13e80e5b 905 EBIT_SET(ftpState->flags, FTP_ISDIR);
906 EBIT_CLR(ftpState->flags, FTP_USE_BASE);
907 if (l == 1)
908 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
909 }
910}
911
3fdadc70 912static void
913ftpBuildTitleUrl(FtpStateData * ftpState)
914{
915 request_t *request = ftpState->request;
916 size_t len;
917 char *t;
918 len = 64
919 + strlen(ftpState->user)
920 + strlen(ftpState->password)
921 + strlen(request->host)
02922e76 922 + strLen(request->urlpath);
3fdadc70 923 t = ftpState->title_url = xcalloc(len, 1);
924 strcat(t, "ftp://");
925 if (strcmp(ftpState->user, "anonymous")) {
926 strcat(t, ftpState->user);
927 strcat(t, "@");
928 }
929 strcat(t, request->host);
930 if (request->port != urlDefaultPort(PROTO_FTP))
56878878 931 snprintf(&t[strlen(t)], len - strlen(t), ":%d", request->port);
02922e76 932 strcat(t, strBuf(request->urlpath));
3fdadc70 933}
934
770f051d 935void
75e88d56 936ftpStart(request_t * request, StoreEntry * entry)
0a0bf5db 937{
0a0bf5db 938 LOCAL_ARRAY(char, realm, 8192);
9fb13bb6 939 const char *url = storeUrl(entry);
5c5783a2 940 FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData));
3fdadc70 941 int fd;
9b312a19 942 ErrorState *err;
a0f32775 943 HttpReply *reply;
3f6c0fb2 944 cbdataAdd(ftpState, MEM_NONE);
9fb13bb6 945 debug(9, 3) ("FtpStart: '%s'\n", url);
a0f32775 946 Counter.server.all.requests++;
947 Counter.server.ftp.requests++;
770f051d 948 storeLockObject(entry);
5c5783a2 949 ftpState->entry = entry;
5c5783a2 950 ftpState->request = requestLink(request);
3fdadc70 951 ftpState->ctrl.fd = -1;
952 ftpState->data.fd = -1;
a533dc81 953 ftpState->size = -1;
3fdadc70 954 EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED);
955 EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED);
54220df8 956 if (ftpState->request->method == METHOD_PUT)
4162ee3b 957 EBIT_SET(ftpState->flags, FTP_PUT);
62dec5a7 958 if (!ftpCheckAuth(ftpState, request->headers)) {
429fdbec 959 /* This request is not fully authenticated */
960 if (request->port == 21) {
56878878 961 snprintf(realm, 8192, "ftp %s", ftpState->user);
429fdbec 962 } else {
56878878 963 snprintf(realm, 8192, "ftp %s port %d",
5c5783a2 964 ftpState->user, request->port);
e381a13d 965 }
63259c34 966 /* create reply */
a0f32775 967 reply = entry->mem_obj->reply;
968 assert(reply != NULL);
969 /* create appropriate reply */
970 ftpAuthRequired(reply, request, realm);
971 httpReplySwapOut(reply, entry);
429fdbec 972 storeComplete(entry);
5c5783a2 973 ftpStateFree(-1, ftpState);
429fdbec 974 return;
e381a13d 975 }
13e80e5b 976 ftpCheckUrlpath(ftpState);
3fdadc70 977 ftpBuildTitleUrl(ftpState);
a3d5953d 978 debug(9, 5) ("FtpStart: host=%s, path=%s, user=%s, passwd=%s\n",
02922e76 979 ftpState->request->host, strBuf(ftpState->request->urlpath),
5c5783a2 980 ftpState->user, ftpState->password);
3fdadc70 981 fd = comm_open(SOCK_STREAM,
16b204c4 982 0,
3fdadc70 983 Config.Addrs.tcp_outgoing,
30a4f2a8 984 0,
16b204c4 985 COMM_NONBLOCKING,
30a4f2a8 986 url);
3fdadc70 987 if (fd == COMM_ERROR) {
185b9571 988 debug(9, 4) ("ftpStart: Failed to open a socket.\n");
fe40a877 989 err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 990 err->xerrno = errno;
9b312a19 991 err->request = requestLink(ftpState->request);
992 errorAppendEntry(entry, err);
0a0bf5db 993 return;
090089c4 994 }
3fdadc70 995 ftpState->ctrl.fd = fd;
996 comm_add_close_handler(fd, ftpStateFree, ftpState);
5fbef0c9 997 storeRegisterAbort(entry, ftpAbort, ftpState);
3fdadc70 998 commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
3fdadc70 999 commConnectStart(ftpState->ctrl.fd,
1000 request->host,
1001 request->port,
e924600d 1002 ftpConnectDone,
5c5783a2 1003 ftpState);
e5f6c5c2 1004}
090089c4 1005
e5f6c5c2 1006static void
1007ftpConnectDone(int fd, int status, void *data)
1008{
5c5783a2 1009 FtpStateData *ftpState = data;
9e975e4e 1010 request_t *request = ftpState->request;
9b312a19 1011 ErrorState *err;
9e975e4e 1012 debug(9, 3) ("ftpConnectDone, status = %d\n", status);
1013 if (status == COMM_ERR_DNS) {
1014 debug(9, 4) ("ftpConnectDone: Unknown host: %s\n", request->host);
fe40a877 1015 err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE);
9b312a19 1016 err->dnsserver_msg = xstrdup(dns_error_message);
1017 err->request = requestLink(request);
1018 errorAppendEntry(ftpState->entry, err);
ff008d84 1019 comm_close(ftpState->ctrl.fd);
9e975e4e 1020 } else if (status != COMM_OK) {
fe40a877 1021 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1022 err->xerrno = errno;
9b312a19 1023 err->host = xstrdup(request->host);
1024 err->port = request->port;
1025 err->request = requestLink(request);
1026 errorAppendEntry(ftpState->entry, err);
ff008d84 1027 comm_close(ftpState->ctrl.fd);
9e975e4e 1028 } else {
1029 ftpState->state = BEGIN;
7021844c 1030 ftpState->ctrl.buf = memAllocate(MEM_4K_BUF);
3f6c0fb2 1031 ftpState->ctrl.freefunc = memFree4K;
9e975e4e 1032 ftpState->ctrl.size = 4096;
1033 ftpState->ctrl.offset = 0;
1034 ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF);
1035 ftpState->data.size = SQUID_TCP_SO_RCVBUF;
1036 ftpState->data.freefunc = xfree;
86cf9987 1037 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
05e11a8c 1038 commSetTimeout(fd, Config.Timeout.read, ftpTimeout, ftpState);
e5f6c5c2 1039 }
090089c4 1040}
1041
3fdadc70 1042/* ====================================================================== */
1043
b8d8561b 1044static void
3fdadc70 1045ftpWriteCommand(const char *buf, FtpStateData * ftpState)
234967c9 1046{
a3d5953d 1047 debug(9, 5) ("ftpWriteCommand: %s\n", buf);
b9916917 1048 safe_free(ftpState->ctrl.last_command);
1049 ftpState->ctrl.last_command = xstrdup(buf);
3fdadc70 1050 comm_write(ftpState->ctrl.fd,
1051 xstrdup(buf),
1052 strlen(buf),
1053 ftpWriteCommandCallback,
1054 ftpState,
1055 xfree);
1056 commSetSelect(ftpState->ctrl.fd,
1057 COMM_SELECT_READ,
1058 ftpReadControlReply,
1059 ftpState,
2acf4be6 1060 Config.Timeout.read);
3fdadc70 1061}
1062
1063static void
79a15e0a 1064ftpWriteCommandCallback(int fd, char *bufnotused, size_t size, int errflag, void *data)
3fdadc70 1065{
1066 FtpStateData *ftpState = data;
1067 StoreEntry *entry = ftpState->entry;
9b312a19 1068 ErrorState *err;
a3d5953d 1069 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size);
ee1679df 1070 if (size > 0) {
1071 fd_bytes(fd, size, FD_WRITE);
a0f32775 1072 kb_incr(&Counter.server.all.kbytes_out, size);
399e85ea 1073 kb_incr(&Counter.server.ftp.kbytes_out, size);
ee1679df 1074 }
96f1be5d 1075 if (errflag == COMM_ERR_CLOSING)
1076 return;
3fdadc70 1077 if (errflag) {
270b86af 1078 debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd, xstrerror());
73a3014d 1079 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1080 err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1081 err->xerrno = errno;
9b312a19 1082 err->request = requestLink(ftpState->request);
1083 errorAppendEntry(entry, err);
1084 }
43d9de30 1085 if (entry->store_status == STORE_PENDING)
1086 storeAbort(entry, 0);
ff008d84 1087 comm_close(ftpState->ctrl.fd);
3fdadc70 1088 }
1089}
1090
1091static wordlist *
1092ftpParseControlReply(char *buf, size_t len, int *codep)
1093{
1094 char *s;
1095 int complete = 0;
1096 wordlist *head;
1097 wordlist *list;
1098 wordlist **tail = &head;
1099 off_t offset;
1100 size_t linelen;
b5639035 1101 int code = -1;
a3d5953d 1102 debug(9, 5) ("ftpParseControlReply\n");
3fdadc70 1103 if (*(buf + len - 1) != '\n')
1104 return NULL;
1105 for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) {
1106 linelen = strcspn(s, crlf) + 1;
fc09761c 1107 if (linelen < 2)
1108 break;
3fdadc70 1109 if (linelen > 3)
1110 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
1111 if (complete)
1112 code = atoi(s);
1113 offset = 0;
1114 if (linelen > 3)
1115 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
1116 offset = 4;
1117 list = xcalloc(1, sizeof(wordlist));
1118 list->key = xmalloc(linelen - offset);
1119 xstrncpy(list->key, s + offset, linelen - offset);
4939c5da 1120 debug(9, 7) ("%d %s\n", code, list->key);
3fdadc70 1121 *tail = list;
1122 tail = &list->next;
1123 }
1124 if (!complete)
1125 wordlistDestroy(&head);
1126 if (codep)
1127 *codep = code;
1128 return head;
1129}
1130
1131static void
1132ftpReadControlReply(int fd, void *data)
1133{
1134 FtpStateData *ftpState = data;
1135 StoreEntry *entry = ftpState->entry;
1136 char *oldbuf;
1137 wordlist **W;
1138 int len;
9b312a19 1139 ErrorState *err;
a3d5953d 1140 debug(9, 5) ("ftpReadControlReply\n");
3fdadc70 1141 assert(ftpState->ctrl.offset < ftpState->ctrl.size);
1142 len = read(fd,
1143 ftpState->ctrl.buf + ftpState->ctrl.offset,
1144 ftpState->ctrl.size - ftpState->ctrl.offset);
ee1679df 1145 if (len > 0) {
1146 fd_bytes(fd, len, FD_READ);
a0f32775 1147 kb_incr(&Counter.server.all.kbytes_in, len);
399e85ea 1148 kb_incr(&Counter.server.ftp.kbytes_in, len);
ee1679df 1149 }
a3d5953d 1150 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd, len);
3fdadc70 1151 if (len < 0) {
a3d5953d 1152 debug(50, 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
b224ea98 1153 if (ignoreErrno(errno)) {
3fdadc70 1154 commSetSelect(fd,
1155 COMM_SELECT_READ,
1156 ftpReadControlReply,
1157 ftpState,
2acf4be6 1158 Config.Timeout.read);
3fdadc70 1159 } else {
73a3014d 1160 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1161 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 1162 err->xerrno = errno;
9b312a19 1163 err->request = requestLink(ftpState->request);
1164 errorAppendEntry(entry, err);
1165 }
43d9de30 1166 if (entry->store_status == STORE_PENDING)
1167 storeAbort(entry, 0);
ff008d84 1168 comm_close(ftpState->ctrl.fd);
3fdadc70 1169 }
1170 return;
1171 }
1172 if (len == 0) {
cc11e161 1173 if (entry->store_status == STORE_PENDING) {
1174 storeReleaseRequest(entry);
1175 if (entry->mem_obj->inmem_hi == 0) {
1614c049 1176 err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
1177 err->xerrno = 0;
cc11e161 1178 err->request = requestLink(ftpState->request);
7131112f 1179 err->ftp_server_msg = ftpState->ctrl.message;
cc11e161 1180 errorAppendEntry(entry, err);
1181 }
9b312a19 1182 }
ff008d84 1183 comm_close(ftpState->ctrl.fd);
3fdadc70 1184 return;
1185 }
b5639035 1186 len += ftpState->ctrl.offset;
1187 ftpState->ctrl.offset = len;
3fdadc70 1188 assert(len <= ftpState->ctrl.size);
1189 wordlistDestroy(&ftpState->ctrl.message);
1190 ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len,
1191 &ftpState->ctrl.replycode);
1192 if (ftpState->ctrl.message == NULL) {
a3d5953d 1193 debug(9, 5) ("ftpReadControlReply: partial server reply\n");
3fdadc70 1194 if (len == ftpState->ctrl.size) {
1195 oldbuf = ftpState->ctrl.buf;
1196 ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1);
1197 xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size);
1198 ftpState->ctrl.size <<= 1;
1199 ftpState->ctrl.freefunc(oldbuf);
1200 ftpState->ctrl.freefunc = xfree;
1201 }
2acf4be6 1202 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read);
3fdadc70 1203 return;
1204 }
1205 for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next);
b9916917 1206 safe_free(ftpState->ctrl.last_reply);
1207 ftpState->ctrl.last_reply = (*W)->key;
3fdadc70 1208 safe_free(*W);
1209 ftpState->ctrl.offset = 0;
7f74df3a 1210 debug(9, 8) ("ftpReadControlReply: state=%d, code=%d\n", ftpState->state, ftpState->ctrl.replycode);
3fdadc70 1211 FTP_SM_FUNCS[ftpState->state] (ftpState);
234967c9 1212}
1213
3fdadc70 1214/* ====================================================================== */
1215
1216static void
1217ftpReadWelcome(FtpStateData * ftpState)
1218{
1219 int code = ftpState->ctrl.replycode;
a3d5953d 1220 debug(9, 3) ("ftpReadWelcome\n");
3fdadc70 1221 if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY))
1222 ftpState->login_att++;
1223 if (code == 220) {
1224 if (ftpState->ctrl.message)
1225 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1226 EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
969c39b9 1227 ftpSendUser(ftpState);
cdc33f35 1228 } else if (code == 120) {
c3b838d4 1229 if (NULL != ftpState->ctrl.message)
1230 debug(9, 3) ("FTP server is busy: %s\n",
1231 ftpState->ctrl.message->key);
cdc33f35 1232 return;
3fdadc70 1233 } else {
1234 ftpFail(ftpState);
1235 }
1236}
1237
969c39b9 1238static void
1239ftpSendUser(FtpStateData * ftpState)
1240{
1241 if (ftpState->proxy_host != NULL)
1242 snprintf(cbuf, 1024, "USER %s@%s\r\n",
1243 ftpState->user,
1244 ftpState->request->host);
1245 else
1246 snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user);
1247 ftpWriteCommand(cbuf, ftpState);
1248 ftpState->state = SENT_USER;
1249}
1250
3fdadc70 1251static void
1252ftpReadUser(FtpStateData * ftpState)
234967c9 1253{
3fdadc70 1254 int code = ftpState->ctrl.replycode;
a3d5953d 1255 debug(9, 3) ("ftpReadUser\n");
3fdadc70 1256 if (code == 230) {
1257 ftpReadPass(ftpState);
1258 } else if (code == 331) {
969c39b9 1259 ftpSendPass(ftpState);
3fdadc70 1260 } else {
1261 ftpFail(ftpState);
1262 }
1263}
1264
969c39b9 1265static void
1266ftpSendPass(FtpStateData * ftpState)
1267{
1268 snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password);
1269 ftpWriteCommand(cbuf, ftpState);
1270 ftpState->state = SENT_PASS;
1271}
1272
3fdadc70 1273static void
1274ftpReadPass(FtpStateData * ftpState)
1275{
1276 int code = ftpState->ctrl.replycode;
a3d5953d 1277 debug(9, 3) ("ftpReadPass\n");
3fdadc70 1278 if (code == 230) {
969c39b9 1279 ftpSendType(ftpState);
3fdadc70 1280 } else {
1281 ftpFail(ftpState);
1282 }
1283}
1284
969c39b9 1285static void
1286ftpSendType(FtpStateData * ftpState)
1287{
02922e76 1288 const char *t;
1289 const char *filename;
969c39b9 1290 char mode;
9e242e02 1291 /*
1292 * Ref section 3.2.2 of RFC 1738
1293 */
1294 switch (mode = ftpState->typecode) {
1295 case 'D':
1296 mode = 'A';
1297 break;
1298 case 'A':
1299 case 'I':
1300 break;
1301 default:
dbfed404 1302 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1303 mode = 'A';
1304 } else {
02922e76 1305 t = strRChr(ftpState->request->urlpath, '/');
1306 filename = t ? t + 1 : strBuf(ftpState->request->urlpath);
dbfed404 1307 mode = mimeGetTransferMode(filename);
1308 }
9e242e02 1309 break;
1310 }
969c39b9 1311 if (mode == 'I')
1312 EBIT_SET(ftpState->flags, FTP_BINARY);
1313 snprintf(cbuf, 1024, "TYPE %c\r\n", mode);
1314 ftpWriteCommand(cbuf, ftpState);
1315 ftpState->state = SENT_TYPE;
1316}
1317
3fdadc70 1318static void
1319ftpReadType(FtpStateData * ftpState)
1320{
1321 int code = ftpState->ctrl.replycode;
1322 wordlist *w;
1323 wordlist **T;
1324 char *path;
1325 char *d;
a3d5953d 1326 debug(9, 3) ("This is ftpReadType\n");
3fdadc70 1327 if (code == 200) {
02922e76 1328 path = xstrdup(strBuf(ftpState->request->urlpath));
13e80e5b 1329 T = &ftpState->pathcomps;
1330 for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) {
1331 rfc1738_unescape(d);
1332 w = xcalloc(1, sizeof(wordlist));
1333 w->key = xstrdup(d);
1334 *T = w;
1335 T = &w->next;
3fdadc70 1336 }
13e80e5b 1337 xfree(path);
969c39b9 1338 if (ftpState->pathcomps)
1339 ftpTraverseDirectory(ftpState);
1340 else
dbfed404 1341 ftpListDir(ftpState);
3fdadc70 1342 } else {
1343 ftpFail(ftpState);
1344 }
1345}
1346
1347static void
969c39b9 1348ftpTraverseDirectory(FtpStateData * ftpState)
3fdadc70 1349{
1350 wordlist *w;
0f169992 1351 debug(9, 4) ("ftpTraverseDirectory %s\n",
1352 ftpState->filepath ? ftpState->filepath : "<NULL>");
969c39b9 1353
1354 safe_free(ftpState->filepath);
1355 /* Done? */
1356 if (ftpState->pathcomps == NULL) {
a3d5953d 1357 debug(9, 3) ("the final component was a directory\n");
dbfed404 1358 ftpListDir(ftpState);
234967c9 1359 return;
3fdadc70 1360 }
969c39b9 1361 /* Go to next path component */
1362 w = ftpState->pathcomps;
1363 ftpState->filepath = w->key;
1364 ftpState->pathcomps = w->next;
1365 xfree(w);
1366 /* Check if we are to CWD or RETR */
1367 if (ftpState->pathcomps != NULL || EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1368 ftpSendCwd(ftpState);
1369 } else {
1370 debug(9, 3) ("final component is probably a file\n");
dbfed404 1371 ftpGetFile(ftpState);
969c39b9 1372 return;
1373 }
1374}
1375
1376static void
1377ftpSendCwd(FtpStateData * ftpState)
1378{
1379 char *path = ftpState->filepath;
1380 debug(9, 3) ("ftpSendCwd\n");
1381 if (!strcmp(path, "..") || !strcmp(path, "/")) {
13e80e5b 1382 EBIT_SET(ftpState->flags, FTP_NO_DOTDOT);
1383 } else {
1384 EBIT_CLR(ftpState->flags, FTP_NO_DOTDOT);
1385 }
969c39b9 1386 snprintf(cbuf, 1024, "CWD %s\r\n", path);
1387 ftpWriteCommand(cbuf, ftpState);
1388 ftpState->state = SENT_CWD;
3fdadc70 1389}
77a30ebb 1390
3fdadc70 1391static void
1392ftpReadCwd(FtpStateData * ftpState)
1393{
1394 int code = ftpState->ctrl.replycode;
a3d5953d 1395 debug(9, 3) ("This is ftpReadCwd\n");
3fdadc70 1396 if (code >= 200 && code < 300) {
969c39b9 1397 /* CWD OK */
0f169992 1398 ftpUnhack(ftpState);
3fdadc70 1399 if (ftpState->cwd_message)
1400 wordlistDestroy(&ftpState->cwd_message);
1401 ftpState->cwd_message = ftpState->ctrl.message;
1402 ftpState->ctrl.message = NULL;
969c39b9 1403 /* Continue to traverse the path */
1404 ftpTraverseDirectory(ftpState);
3fdadc70 1405 } else {
1406 /* CWD FAILED */
4162ee3b 1407 if (!EBIT_TEST(ftpState->flags, FTP_PUT))
1408 ftpFail(ftpState);
54220df8 1409 else
4162ee3b 1410 ftpTryMkdir(ftpState);
c021888f 1411 }
3fdadc70 1412}
1413
54220df8 1414static void
4162ee3b 1415ftpTryMkdir(FtpStateData * ftpState)
54220df8 1416{
4162ee3b 1417 char *path = ftpState->filepath;
1418 debug(9, 3) ("ftpTryMkdir: with path=%s\n", path);
1419 snprintf(cbuf, 1024, "MKD %s\r\n", path);
1420 ftpWriteCommand(cbuf, ftpState);
1421 ftpState->state = SENT_MKDIR;
54220df8 1422}
1423
1424static void
4162ee3b 1425ftpReadMkdir(FtpStateData * ftpState)
1426{
1427 char *path = ftpState->filepath;
1428 int code = ftpState->ctrl.replycode;
1429
c3b838d4 1430 debug(9, 3) ("ftpReadMkdir: path %s, code %d\n", path, code);
4162ee3b 1431 if (code == 257) { /* success */
1432 ftpSendCwd(ftpState);
1433 } else if (code == 550) { /* dir exists */
1434 if (EBIT_TEST(ftpState->flags, FTP_PUT_MKDIR)) {
1435 EBIT_SET(ftpState->flags, FTP_PUT_MKDIR);
1436 ftpSendCwd(ftpState);
1437 } else
1438 ftpSendReply(ftpState);
1439 } else
1440 ftpSendReply(ftpState);
54220df8 1441}
1442
dbfed404 1443static void
1444ftpGetFile(FtpStateData * ftpState)
1445{
1446 assert(*ftpState->filepath != '\0');
1447 EBIT_CLR(ftpState->flags, FTP_ISDIR);
1448 ftpSendMdtm(ftpState);
1449}
1450
1451static void
1452ftpListDir(FtpStateData * ftpState)
1453{
1454 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1455 debug(9, 3) ("Directory path did not end in /\n");
1456 strcat(ftpState->title_url, "/");
1457 EBIT_SET(ftpState->flags, FTP_ISDIR);
1458 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1459 }
1460 ftpSendPasv(ftpState);
1461}
1462
969c39b9 1463static void
1464ftpSendMdtm(FtpStateData * ftpState)
1465{
1466 assert(*ftpState->filepath != '\0');
1467 snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath);
1468 ftpWriteCommand(cbuf, ftpState);
1469 ftpState->state = SENT_MDTM;
1470}
1471
3fdadc70 1472static void
1473ftpReadMdtm(FtpStateData * ftpState)
1474{
1475 int code = ftpState->ctrl.replycode;
a3d5953d 1476 debug(9, 3) ("This is ftpReadMdtm\n");
3fdadc70 1477 if (code == 213) {
b9916917 1478 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
0f169992 1479 ftpUnhack(ftpState);
3fdadc70 1480 } else if (code < 0) {
1481 ftpFail(ftpState);
77a30ebb 1482 }
969c39b9 1483 ftpSendSize(ftpState);
1484}
1485
1486static void
1487ftpSendSize(FtpStateData * ftpState)
1488{
1489 /* Only send SIZE for binary transfers. The returned size
1490 * is useless on ASCII transfers */
dbfed404 1491 if (EBIT_TEST(ftpState->flags, FTP_BINARY)) {
969c39b9 1492 assert(ftpState->filepath != NULL);
1493 assert(*ftpState->filepath != '\0');
1494 snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
1495 ftpWriteCommand(cbuf, ftpState);
1496 ftpState->state = SENT_SIZE;
1497 } else
1498 /* Skip to next state no non-binary transfers */
1499 ftpSendPasv(ftpState);
3fdadc70 1500}
1501
1502static void
1503ftpReadSize(FtpStateData * ftpState)
1504{
1505 int code = ftpState->ctrl.replycode;
a3d5953d 1506 debug(9, 3) ("This is ftpReadSize\n");
3fdadc70 1507 if (code == 213) {
0f169992 1508 ftpUnhack(ftpState);
b9916917 1509 ftpState->size = atoi(ftpState->ctrl.last_reply);
3fdadc70 1510 } else if (code < 0) {
1511 ftpFail(ftpState);
1512 }
4939c5da 1513 ftpSendPasv(ftpState);
3fdadc70 1514}
1515
1516static void
1517ftpSendPasv(FtpStateData * ftpState)
1518{
1519 int fd;
cdc33f35 1520 struct sockaddr_in addr;
1521 int addr_len;
a57512fa 1522 if (ftpState->data.fd >= 0) {
0f169992 1523 if (!EBIT_TEST(ftpState->flags, FTP_DATACHANNEL_HACK)) {
1524 /* We are already connected, reuse this connection. */
1525 ftpRestOrList(ftpState);
1526 return;
1527 } else {
1528 /* Close old connection */
1529 comm_close(ftpState->data.fd);
1530 ftpState->data.fd = -1;
1531 }
a57512fa 1532 }
3fdadc70 1533 if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
1534 ftpSendPort(ftpState);
1535 return;
30a4f2a8 1536 }
cdc33f35 1537 addr_len = sizeof(addr);
1538 if (getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
1539 debug(9, 0) ("ftpSendPasv: getsockname(%d,..): %s\n",
1540 ftpState->ctrl.fd, xstrerror());
1541 addr.sin_addr = Config.Addrs.tcp_outgoing;
1542 }
1543 /* Open data channel with the same local address as control channel */
3fdadc70 1544 fd = comm_open(SOCK_STREAM,
16b204c4 1545 0,
cdc33f35 1546 addr.sin_addr,
30a4f2a8 1547 0,
3fdadc70 1548 COMM_NONBLOCKING,
9fb13bb6 1549 storeUrl(ftpState->entry));
05e11a8c 1550 debug(9, 3) ("ftpSendPasv: Unconnected data socket created on FD %d\n", fd);
3fdadc70 1551 if (fd < 0) {
1552 ftpFail(ftpState);
1553 return;
1554 }
ff008d84 1555 /*
1556 * No comm_add_close_handler() here. If we have both ctrl and
1557 * data FD's call ftpStateFree() upon close, then we have
1558 * to delete the close handler which did NOT get called
1559 * to prevent ftpStateFree() getting called twice.
1560 * Instead we'll always call comm_close() on the ctrl FD.
1561 */
3fdadc70 1562 ftpState->data.fd = fd;
042461c3 1563 snprintf(cbuf, 1024, "PASV\r\n");
3fdadc70 1564 ftpWriteCommand(cbuf, ftpState);
1565 ftpState->state = SENT_PASV;
1566}
1567
1568static void
1569ftpReadPasv(FtpStateData * ftpState)
1570{
1571 int code = ftpState->ctrl.replycode;
1572 int h1, h2, h3, h4;
1573 int p1, p2;
1574 int n;
1575 u_short port;
1576 int fd = ftpState->data.fd;
b9916917 1577 char *buf = ftpState->ctrl.last_reply;
3fdadc70 1578 LOCAL_ARRAY(char, junk, 1024);
a3d5953d 1579 debug(9, 3) ("This is ftpReadPasv\n");
3fdadc70 1580 if (code != 227) {
a3d5953d 1581 debug(9, 3) ("PASV not supported by remote end\n");
cdc33f35 1582 comm_close(ftpState->data.fd);
1583 ftpState->data.fd = -1;
3fdadc70 1584 ftpSendPort(ftpState);
1585 return;
1586 }
69e81830 1587 if ((int) strlen(buf) > 1024) {
2a1bc30a 1588 debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n");
3fdadc70 1589 ftpSendPort(ftpState);
1590 return;
1591 }
1592 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1593 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a3d5953d 1594 debug(9, 5) ("scanning: %s\n", buf);
3fdadc70 1595 n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1596 junk, &h1, &h2, &h3, &h4, &p1, &p2);
1597 if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a3d5953d 1598 debug(9, 3) ("Bad 227 reply\n");
1599 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n, p1, p2);
3fdadc70 1600 ftpSendPort(ftpState);
1601 return;
1602 }
56878878 1603 snprintf(junk, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
3fdadc70 1604 if (!safe_inet_addr(junk, NULL)) {
a3d5953d 1605 debug(9, 1) ("unsafe address (%s)\n", junk);
3fdadc70 1606 ftpSendPort(ftpState);
1607 return;
1608 }
1609 port = ((p1 << 8) + p2);
7f74df3a 1610 if (0 == port) {
1afe05c5 1611 debug(9, 1) ("ftpReadPasv: Invalid PASV reply: %s\n", buf);
7f74df3a 1612 ftpSendPort(ftpState);
1613 return;
1614 }
a3d5953d 1615 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk, port);
9b312a19 1616 ftpState->data.port = port;
1617 ftpState->data.host = xstrdup(junk);
3fdadc70 1618 commConnectStart(fd, junk, port, ftpPasvCallback, ftpState);
1619}
1620
1621static void
1622ftpPasvCallback(int fd, int status, void *data)
1623{
1624 FtpStateData *ftpState = data;
9b312a19 1625 request_t *request = ftpState->request;
1626 ErrorState *err;
a3d5953d 1627 debug(9, 3) ("ftpPasvCallback\n");
9b312a19 1628 if (status != COMM_OK) {
fe40a877 1629 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1630 err->xerrno = errno;
9b312a19 1631 err->host = xstrdup(ftpState->data.host);
1632 err->port = ftpState->data.port;
1633 err->request = requestLink(request);
1634 errorAppendEntry(ftpState->entry, err);
ff008d84 1635 comm_close(ftpState->ctrl.fd);
3fdadc70 1636 return;
1637 }
1638 ftpRestOrList(ftpState);
1639}
1640
cdc33f35 1641static int
1642ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
1643{
1644 int fd;
1645 struct sockaddr_in addr;
1646 int addr_len;
1647 int on = 1;
1648 u_short port = 0;
1649 /* Set up a listen socket on the same local address as the control connection. */
1650 addr_len = sizeof(addr);
1651 if (getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
1652 debug(9, 0) ("ftpOpenListenSocket: getsockname(%d,..): %s\n",
1653 ftpState->ctrl.fd, xstrerror());
1654 return -1;
1655 }
1656 /* REUSEADDR is needed in fallback mode, since the same port is used for both
1657 * control and data
1658 */
1659 if (fallback) {
1660 setsockopt(ftpState->ctrl.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
1661 port = ntohs(addr.sin_port);
1662 }
1663 fd = comm_open(SOCK_STREAM,
1664 0,
1665 addr.sin_addr,
1666 port,
1667 COMM_NONBLOCKING | (fallback ? COMM_REUSEADDR : 0),
1668 storeUrl(ftpState->entry));
05e11a8c 1669 debug(9, 3) ("ftpOpenListenSocket: Unconnected data socket created on FD %d\n", fd);
cdc33f35 1670 if (fd < 0) {
1671 debug(9, 0) ("ftpOpenListenSocket: comm_open failed\n");
1672 return -1;
1673 }
1674 if (comm_listen(fd) < 0) {
1675 comm_close(fd);
1676 return -1;
1677 }
1678 ftpState->data.fd = fd;
1679 ftpState->data.port = comm_local_port(fd);;
1680 ftpState->data.host = NULL;
1681 return fd;
1682}
1683
3fdadc70 1684static void
1685ftpSendPort(FtpStateData * ftpState)
1686{
cdc33f35 1687 int fd;
1688 struct sockaddr_in addr;
1689 int addr_len;
1690 unsigned char *addrptr;
1691 unsigned char *portptr;
a3d5953d 1692 debug(9, 3) ("This is ftpSendPort\n");
d3f89c29 1693 EBIT_CLR(ftpState->flags, FTP_PASV_SUPPORTED);
cdc33f35 1694 fd = ftpOpenListenSocket(ftpState, 0);
1695 addr_len = sizeof(addr);
1696 if (getsockname(fd, (struct sockaddr *) &addr, &addr_len)) {
1697 debug(9, 0) ("ftpSendPort: getsockname(%d,..): %s\n", fd, xstrerror());
1698 /* XXX Need to set error message */
1699 ftpFail(ftpState);
1700 return;
1701 }
1702 addrptr = (unsigned char *) &addr.sin_addr.s_addr;
1703 portptr = (unsigned char *) &addr.sin_port;
1704 snprintf(cbuf, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
1705 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
1706 portptr[0], portptr[1]);
1707 ftpWriteCommand(cbuf, ftpState);
1708 ftpState->state = SENT_PORT;
3fdadc70 1709}
1710
1711static void
cdc33f35 1712ftpReadPort(FtpStateData * ftpState)
3fdadc70 1713{
cdc33f35 1714 int code = ftpState->ctrl.replycode;
a3d5953d 1715 debug(9, 3) ("This is ftpReadPort\n");
cdc33f35 1716 if (code != 200) {
1717 /* Fall back on using the same port as the control connection */
1718 debug(9, 3) ("PORT not supported by remote end\n");
1719 comm_close(ftpState->data.fd);
1720 ftpOpenListenSocket(ftpState, 1);
1721 }
1722 ftpRestOrList(ftpState);
1723}
1724
1725/* "read" handler to accept data connection */
1726static void
1727ftpAcceptDataConnection(int fd, void *data)
1728{
1729 FtpStateData *ftpState = data;
1730 struct sockaddr_in peer, me;
1731 debug(9, 3) ("ftpAcceptDataConnection\n");
1732
1733 fd = comm_accept(fd, &peer, &me);
1734 if (fd < 0) {
1735 debug(9, 1) ("ftpHandleDataAccept: comm_accept(%d): %s", fd, xstrerror());
1736 /* XXX Need to set error message */
1737 ftpFail(ftpState);
1738 return;
1739 }
c93a9f49 1740 /* Replace the Listen socket with the accepted data socket */
1741 comm_close(ftpState->data.fd);
05e11a8c 1742 debug(9, 3) ("ftpAcceptDataConnection: Connected data socket on FD %d\n", fd);
cdc33f35 1743 ftpState->data.fd = fd;
1744 ftpState->data.port = ntohs(peer.sin_port);
1745 ftpState->data.host = xstrdup(inet_ntoa(peer.sin_addr));
05e11a8c 1746 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1747 ftpState);
cdc33f35 1748 /* XXX We should have a flag to track connect state...
1749 * host NULL -> not connected, port == local port
1750 * host set -> connected, port == remote port
1751 */
1752 /* Restart state (SENT_NLST/LIST/RETR) */
1753 FTP_SM_FUNCS[ftpState->state] (ftpState);
3fdadc70 1754}
1755
1756static void
1757ftpRestOrList(FtpStateData * ftpState)
1758{
54220df8 1759
a3d5953d 1760 debug(9, 3) ("This is ftpRestOrList\n");
54220df8 1761 if (EBIT_TEST(ftpState->flags, FTP_PUT)) {
4162ee3b 1762 debug(9, 3) ("ftpRestOrList: Sending STOR request...\n");
54220df8 1763 ftpSendStor(ftpState);
1764 } else if (ftpState->typecode == 'D') {
dbfed404 1765 /* XXX This should NOT be here */
9e242e02 1766 ftpSendNlst(ftpState); /* sec 3.2.2 of RFC 1738 */
1767 EBIT_SET(ftpState->flags, FTP_ISDIR);
dbfed404 1768 EBIT_SET(ftpState->flags, FTP_USE_BASE);
9e242e02 1769 } else if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
969c39b9 1770 ftpSendList(ftpState);
1771 else if (ftpState->restart_offset > 0)
1772 ftpSendRest(ftpState);
1773 else
1774 ftpSendRetr(ftpState);
1775}
1776
54220df8 1777static void
1778ftpSendStor(FtpStateData * ftpState)
1779{
1780 assert(ftpState->filepath != NULL);
1781 snprintf(cbuf, 1024, "STOR %s\r\n", ftpState->filepath);
1782 ftpWriteCommand(cbuf, ftpState);
1783 ftpState->state = SENT_STOR;
1784}
1785
1786static void
1787ftpReadStor(FtpStateData * ftpState)
1788{
1789 int code = ftpState->ctrl.replycode;
1790 debug(9, 3) ("This is ftpReadStor\n");
1791 if (code >= 100 && code < 200) {
acce49bf 1792 /*
1793 * Cancel the timeout on the Control socket, pumpStart will
1794 * establish one on the data socket.
1795 */
1796 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1797 ftpPutStart(ftpState);
1798 debug(9, 3) ("ftpReadStor: writing data channel\n");
1799 ftpState->state = WRITING_DATA;
1800 } else if (code == 553) {
26970499 1801 /* directory does not exist, have to create, sigh */
1802#if WORK_IN_PROGRESS
54220df8 1803 ftpTraverseDirectory(ftpState);
1804#endif
1805 ftpSendReply(ftpState);
1806 } else {
acce49bf 1807 debug(9, 3) ("ftpReadStor: that's all folks\n");
54220df8 1808 ftpSendReply(ftpState);
1809 }
1810}
1811
969c39b9 1812static void
1813ftpSendRest(FtpStateData * ftpState)
1814{
1815 snprintf(cbuf, 1024, "REST %d\r\n", ftpState->restart_offset);
1816 ftpWriteCommand(cbuf, ftpState);
1817 ftpState->state = SENT_REST;
3fdadc70 1818}
1819
1820static void
1821ftpReadRest(FtpStateData * ftpState)
1822{
1823 int code = ftpState->ctrl.replycode;
a3d5953d 1824 debug(9, 3) ("This is ftpReadRest\n");
3fdadc70 1825 assert(ftpState->restart_offset > 0);
1826 if (code == 350) {
969c39b9 1827 ftpSendRetr(ftpState);
3fdadc70 1828 } else if (code > 0) {
a3d5953d 1829 debug(9, 3) ("ftpReadRest: REST not supported\n");
d3f89c29 1830 EBIT_CLR(ftpState->flags, FTP_REST_SUPPORTED);
3fdadc70 1831 } else {
1832 ftpFail(ftpState);
1833 }
1834}
1835
969c39b9 1836static void
1837ftpSendList(FtpStateData * ftpState)
1838{
dbfed404 1839 if (ftpState->filepath) {
1840 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1841 snprintf(cbuf, 1024, "LIST %s\r\n", ftpState->filepath);
1842 } else {
1843 snprintf(cbuf, 1024, "LIST\r\n");
1844 }
969c39b9 1845 ftpWriteCommand(cbuf, ftpState);
1846 ftpState->state = SENT_LIST;
1847}
1848
dbfed404 1849static void
1850ftpSendNlst(FtpStateData * ftpState)
1851{
1852 EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
1853 if (ftpState->filepath) {
1854 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1855 snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
1856 } else {
1857 snprintf(cbuf, 1024, "NLST\r\n");
1858 }
1859 ftpWriteCommand(cbuf, ftpState);
1860 ftpState->state = SENT_NLST;
1861}
1862
3fdadc70 1863static void
1864ftpReadList(FtpStateData * ftpState)
1865{
1866 int code = ftpState->ctrl.replycode;
a3d5953d 1867 debug(9, 3) ("This is ftpReadList\n");
cdc33f35 1868 if (code == 125 || (code == 150 && ftpState->data.host)) {
1869 /* Begin data transfer */
3fdadc70 1870 ftpAppendSuccessHeader(ftpState);
1871 commSetSelect(ftpState->data.fd,
30a4f2a8 1872 COMM_SELECT_READ,
dbfed404 1873 ftpDataRead,
3fdadc70 1874 ftpState,
2acf4be6 1875 Config.Timeout.read);
70a9dab4 1876 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1877 ftpState->state = READING_DATA;
05e11a8c 1878 /*
1879 * Cancel the timeout on the Control socket and establish one
1880 * on the data socket
1881 */
a0eff6be 1882 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1883 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
3fdadc70 1884 return;
cdc33f35 1885 } else if (code == 150) {
1886 /* Accept data channel */
1887 commSetSelect(ftpState->data.fd,
1888 COMM_SELECT_READ,
1889 ftpAcceptDataConnection,
1890 ftpState,
05e11a8c 1891 0);
1892 /*
1893 * Cancel the timeout on the Control socket and establish one
1894 * on the data socket
1895 */
1896 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1897 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
cdc33f35 1898 return;
1899 } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST) && code > 300) {
969c39b9 1900 ftpSendNlst(ftpState);
3fdadc70 1901 } else {
1902 ftpFail(ftpState);
1903 return;
1904 }
1905}
1906
969c39b9 1907static void
1908ftpSendRetr(FtpStateData * ftpState)
1909{
1910 assert(ftpState->filepath != NULL);
1911 snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath);
1912 ftpWriteCommand(cbuf, ftpState);
1913 ftpState->state = SENT_RETR;
1914}
1915
3fdadc70 1916static void
1917ftpReadRetr(FtpStateData * ftpState)
1918{
1919 int code = ftpState->ctrl.replycode;
a3d5953d 1920 debug(9, 3) ("This is ftpReadRetr\n");
cdc33f35 1921 if (code == 125 || (code == 150 && ftpState->data.host)) {
1922 /* Begin data transfer */
a57512fa 1923 debug(9, 3) ("ftpReadRetr: reading data channel\n");
3fdadc70 1924 ftpAppendSuccessHeader(ftpState);
1925 commSetSelect(ftpState->data.fd,
1926 COMM_SELECT_READ,
dbfed404 1927 ftpDataRead,
3fdadc70 1928 ftpState,
2acf4be6 1929 Config.Timeout.read);
70a9dab4 1930 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1931 ftpState->state = READING_DATA;
bd200c90 1932 /*
1933 * Cancel the timeout on the Control socket and establish one
1934 * on the data socket
1935 */
2acf4be6 1936 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
bd200c90 1937 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1938 ftpState);
cdc33f35 1939 } else if (code == 150) {
1940 /* Accept data channel */
1941 commSetSelect(ftpState->data.fd,
1942 COMM_SELECT_READ,
1943 ftpAcceptDataConnection,
1944 ftpState,
05e11a8c 1945 0);
bd200c90 1946 /*
1947 * Cancel the timeout on the Control socket and establish one
1948 * on the data socket
1949 */
1950 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1951 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1952 ftpState);
cdc33f35 1953 } else if (code >= 300) {
969c39b9 1954 if (!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
1955 /* Try this as a directory missing trailing slash... */
1956 ftpHackShortcut(ftpState, ftpSendCwd);
1957 } else {
1958 ftpFail(ftpState);
1959 }
cdc33f35 1960 } else {
1961 ftpFail(ftpState);
3fdadc70 1962 }
1963}
1964
1965static void
1966ftpReadTransferDone(FtpStateData * ftpState)
1967{
1968 int code = ftpState->ctrl.replycode;
a3d5953d 1969 debug(9, 3) ("This is ftpReadTransferDone\n");
3fdadc70 1970 if (code != 226) {
c3b838d4 1971 debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n",
1972 code);
9fb13bb6 1973 debug(9, 1) ("--> releasing '%s'\n", storeUrl(ftpState->entry));
3fdadc70 1974 storeReleaseRequest(ftpState->entry);
1975 }
02be0294 1976 ftpDataTransferDone(ftpState);
3fdadc70 1977}
1978
1979static void
1980ftpDataTransferDone(FtpStateData * ftpState)
1981{
a3d5953d 1982 debug(9, 3) ("This is ftpDataTransferDone\n");
033fa114 1983 if (ftpState->data.fd > -1) {
a3d5953d 1984 comm_close(ftpState->data.fd);
1985 ftpState->data.fd = -1;
033fa114 1986 }
969c39b9 1987 ftpSendQuit(ftpState);
1988}
1989
1990static void
1991ftpSendQuit(FtpStateData * ftpState)
1992{
033fa114 1993 assert(ftpState->ctrl.fd > -1);
56878878 1994 snprintf(cbuf, 1024, "QUIT\r\n");
3fdadc70 1995 ftpWriteCommand(cbuf, ftpState);
1996 ftpState->state = SENT_QUIT;
1997}
1998
1999static void
2000ftpReadQuit(FtpStateData * ftpState)
2001{
2002 comm_close(ftpState->ctrl.fd);
2003}
2004
969c39b9 2005static void
2006ftpTrySlashHack(FtpStateData * ftpState)
2007{
2008 char *path;
2009 EBIT_SET(ftpState->flags, FTP_TRY_SLASH_HACK);
2010 /* Free old paths */
2011 if (ftpState->pathcomps)
2012 wordlistDestroy(&ftpState->pathcomps);
2013 safe_free(ftpState->filepath);
2014 /* Build the new path (urlpath begins with /) */
02922e76 2015 path = xstrdup(strBuf(ftpState->request->urlpath));
969c39b9 2016 rfc1738_unescape(path);
2017 ftpState->filepath = path;
2018 /* And off we go */
dbfed404 2019 ftpGetFile(ftpState);
969c39b9 2020}
2021
0f169992 2022static void
2023ftpTryDatachannelHack(FtpStateData * ftpState)
2024{
2025 EBIT_SET(ftpState->flags, FTP_DATACHANNEL_HACK);
2026 /* we have to undo some of the slash hack... */
2027 if (ftpState->old_filepath != NULL) {
2028 EBIT_CLR(ftpState->flags, FTP_TRY_SLASH_HACK);
2029 safe_free(ftpState->filepath);
2030 ftpState->filepath = ftpState->old_filepath;
2031 ftpState->old_filepath = NULL;
2032 }
2033 EBIT_CLR(ftpState->flags, FTP_TRIED_NLST);
2034 /* And off we go */
2035 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
2036 safe_free(ftpState->filepath);
2037 ftpListDir(ftpState);
2038 } else {
2039 ftpGetFile(ftpState);
2040 }
2041 return;
2042}
2043
2044/* Forget hack status. Next error is shown to the user */
2045static void
2046ftpUnhack(FtpStateData * ftpState)
2047{
2048 if (ftpState->old_request != NULL) {
2049 safe_free(ftpState->old_request);
2050 safe_free(ftpState->old_reply);
2051 }
2052}
2053
969c39b9 2054static void
2055ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState)
2056{
0f169992 2057 /* Save old error message & some state info */
2058 if (ftpState->old_request == NULL) {
2059 ftpState->old_request = ftpState->ctrl.last_command;
2060 ftpState->ctrl.last_command = NULL;
2061 ftpState->old_reply = ftpState->ctrl.last_reply;
2062 ftpState->ctrl.last_reply = NULL;
2063 if (ftpState->pathcomps == NULL && ftpState->filepath != NULL)
2064 ftpState->old_filepath = xstrdup(ftpState->filepath);
2065 }
969c39b9 2066 /* Jump to the "hack" state */
2067 nextState(ftpState);
2068}
2069
3fdadc70 2070static void
2071ftpFail(FtpStateData * ftpState)
2072{
b9916917 2073 ErrorState *err;
a3d5953d 2074 debug(9, 3) ("ftpFail\n");
dbfed404 2075 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
969c39b9 2076 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR) &&
2077 !EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
2078 switch (ftpState->state) {
2079 case SENT_CWD:
2080 case SENT_RETR:
2081 /* Try the / hack */
2082 ftpHackShortcut(ftpState, ftpTrySlashHack);
2083 return;
2084 default:
2085 break;
2086 }
2087 }
0f169992 2088 /* Try to reopen datachannel */
2089 if (!EBIT_TEST(ftpState->flags, FTP_DATACHANNEL_HACK) &&
2090 ftpState->pathcomps == NULL) {
2091 switch (ftpState->state) {
2092 case SENT_RETR:
2093 case SENT_LIST:
2094 case SENT_NLST:
2095 /* Try to reopen datachannel */
2096 ftpHackShortcut(ftpState, ftpTryDatachannelHack);
2097 return;
2098 default:
2099 break;
2100 }
2101 }
1931735b 2102 err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
b9916917 2103 err->request = requestLink(ftpState->request);
7131112f 2104 err->ftp_server_msg = ftpState->ctrl.message;
969c39b9 2105 if (ftpState->old_request)
2106 err->ftp.request = ftpState->old_request;
2107 else
2108 err->ftp.request = ftpState->ctrl.last_command;
2109 if (ftpState->old_reply)
2110 err->ftp.reply = ftpState->old_reply;
2111 else
2112 err->ftp.reply = ftpState->ctrl.last_reply;
b9916917 2113 errorAppendEntry(ftpState->entry, err);
3fdadc70 2114 comm_close(ftpState->ctrl.fd);
2115}
2116
54220df8 2117static void
2118ftpPutStart(FtpStateData * ftpState)
2119{
2120 debug(9, 3) ("ftpPutStart\n");
4162ee3b 2121 pumpStart(ftpState->data.fd, ftpState->entry,
2122 ftpState->request, ftpPutTransferDone, ftpState);
54220df8 2123}
2124
4162ee3b 2125static void
54220df8 2126ftpPutTransferDone(int fd, char *bufnotused, size_t size, int errflag, void *data)
2127{
4162ee3b 2128 FtpStateData *ftpState = (FtpStateData *) data;
54220df8 2129 if (ftpState->data.fd >= 0) {
4162ee3b 2130 comm_close(ftpState->data.fd);
2131 ftpState->data.fd = -1;
54220df8 2132 }
2133 ftpReadComplete(ftpState);
2134}
2135
2136static void
2137ftpSendReply(FtpStateData * ftpState)
2138{
2139 ErrorState *err;
acce49bf 2140 int code = ftpState->ctrl.replycode;
54220df8 2141 int http_code;
acce49bf 2142 int err_code = ERR_NONE;
c3b838d4 2143 debug(9, 5) ("ftpSendReply: %s, code %d\n",
2144 storeUrl(ftpState->entry), code);
54220df8 2145 if (cbdataValid(ftpState))
acce49bf 2146 debug(9, 5) ("ftpSendReply: ftpState (%p) is valid!\n", ftpState);
2147 if (code == 226) {
2148 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
2149 http_code = (ftpState->mdtm > 0) ? HTTP_ACCEPTED : HTTP_CREATED;
54220df8 2150 } else {
2151 err_code = ERR_FTP_PUT_ERROR;
acce49bf 2152 http_code = HTTP_INTERNAL_SERVER_ERROR;
54220df8 2153 }
54220df8 2154 err = errorCon(err_code, http_code);
2155 err->request = requestLink(ftpState->request);
2156 if (ftpState->old_request)
acce49bf 2157 err->ftp.request = ftpState->old_request;
54220df8 2158 else
acce49bf 2159 err->ftp.request = ftpState->ctrl.last_command;
54220df8 2160 if (ftpState->old_reply)
acce49bf 2161 err->ftp.reply = ftpState->old_reply;
54220df8 2162 else
acce49bf 2163 err->ftp.reply = ftpState->ctrl.last_reply;
54220df8 2164 errorAppendEntry(ftpState->entry, err);
54220df8 2165 storeBufferFlush(ftpState->entry);
2166 comm_close(ftpState->ctrl.fd);
2167}
2168
3fdadc70 2169static void
2170ftpAppendSuccessHeader(FtpStateData * ftpState)
2171{
2172 char *mime_type = NULL;
2173 char *mime_enc = NULL;
02922e76 2174 String urlpath = ftpState->request->urlpath;
2175 const char *filename = NULL;
2176 const char *t = NULL;
3fdadc70 2177 StoreEntry *e = ftpState->entry;
9e975e4e 2178 http_reply *reply = e->mem_obj->reply;
3fdadc70 2179 if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT))
2180 return;
9e975e4e 2181 EBIT_SET(ftpState->flags, FTP_HTTP_HEADER_SENT);
8350fe9b 2182 assert(e->mem_obj->inmem_hi == 0);
02922e76 2183 filename = (t = strRChr(urlpath, '/')) ? t + 1 : strBuf(urlpath);
3fdadc70 2184 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
2185 mime_type = "text/html";
2186 } else {
31b4984a 2187 switch (ftpState->typecode) {
2188 case 'I':
2189 mime_type = "application/octet-stream";
2190 mime_enc = mimeGetContentEncoding(filename);
2191 break;
2192 case 'A':
2193 mime_type = "text/plain";
2194 break;
2195 default:
2196 mime_type = mimeGetContentType(filename);
2197 mime_enc = mimeGetContentEncoding(filename);
2198 break;
2199 }
3fdadc70 2200 }
438fc1e3 2201 storeBuffer(e);
cb69b4c7 2202 httpReplyReset(reply);
2203 /* set standard stuff */
2204 httpReplySetHeaders(reply, 1.0, HTTP_OK, "Gatewaying",
2205 mime_type, ftpState->size, ftpState->mdtm, -2);
2206 /* additional info */
2207 if (mime_enc)
d8b249ef 2208 httpHeaderPutStr(&reply->header, HDR_CONTENT_ENCODING, mime_enc);
cb69b4c7 2209 httpReplySwapOut(reply, e);
438fc1e3 2210 storeBufferFlush(e);
2acf4be6 2211 reply->hdr_sz = e->mem_obj->inmem_hi;
e6b02cfc 2212 storeTimestampsSet(e);
e6b02cfc 2213 storeSetPublicKey(e);
77a30ebb 2214}
bfcaf585 2215
2216static void
2217ftpAbort(void *data)
2218{
2219 FtpStateData *ftpState = data;
9fb13bb6 2220 debug(9, 2) ("ftpAbort: %s\n", storeUrl(ftpState->entry));
15888cf7 2221 if (ftpState->data.fd >= 0) {
270b86af 2222 comm_close(ftpState->data.fd);
15888cf7 2223 ftpState->data.fd = -1;
2224 }
7b2cdd7b 2225 comm_close(ftpState->ctrl.fd);
bfcaf585 2226}
9b312a19 2227
cb69b4c7 2228static void
ee1679df 2229ftpAuthRequired(HttpReply * old_reply, request_t * request, const char *realm)
cb69b4c7 2230{
2231 ErrorState *err = errorCon(ERR_ACCESS_DENIED, HTTP_UNAUTHORIZED);
2232 HttpReply *rep;
2233 err->request = requestLink(request);
2234 rep = errorBuildReply(err);
cb69b4c7 2235 errorStateFree(err);
63259c34 2236 /* add Authenticate header */
d8b249ef 2237 httpHeaderPutAuth(&rep->header, "Basic", realm);
63259c34 2238 /* move new reply to the old one */
2239 httpReplyAbsorb(old_reply, rep);
cb69b4c7 2240}
8f872bb6 2241
2242char *
2243ftpUrlWith2f(const request_t * request)
2244{
2245 LOCAL_ARRAY(char, buf, MAX_URL);
2246 LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1);
2247 LOCAL_ARRAY(char, portbuf, 32);
2248 char *t;
2249 portbuf[0] = '\0';
23d92c64 2250 if (request->protocol != PROTO_FTP)
2251 return NULL;
8f872bb6 2252 if (request->port != urlDefaultPort(request->protocol))
2253 snprintf(portbuf, 32, ":%d", request->port);
2254 loginbuf[0] = '\0';
69e81830 2255 if ((int) strlen(request->login) > 0) {
8f872bb6 2256 strcpy(loginbuf, request->login);
2257 if ((t = strchr(loginbuf, ':')))
2258 *t = '\0';
2259 strcat(loginbuf, "@");
2260 }
2261 snprintf(buf, MAX_URL, "%s://%s%s%s%s%s",
2262 ProtocolStr[request->protocol],
2263 loginbuf,
2264 request->host,
2265 portbuf,
2266 "/%2f",
02922e76 2267 strBuf(request->urlpath));
8f872bb6 2268 if ((t = strchr(buf, '?')))
2269 *t = '\0';
2270 return buf;
2271}