]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
move bunch of options to Config.onoff
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
95d659f0 1
30a4f2a8 2/*
4f117cb4 3 * $Id: ftp.cc,v 1.135 1997/07/19 07:18:00 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
30a4f2a8 34#define FTP_DELETE_GAP (1<<18)
090089c4 35
3fdadc70 36enum {
37 FTP_ISDIR,
38 FTP_PASV_SUPPORTED,
39 FTP_SKIP_WHITESPACE,
40 FTP_REST_SUPPORTED,
41 FTP_PASV_ONLY,
42 FTP_AUTHENTICATED,
3fdadc70 43 FTP_HTTP_HEADER_SENT,
44 FTP_TRIED_NLST,
45 FTP_USE_BASE,
46 FTP_ROOT_DIR,
47 FTP_HTML_HEADER_SENT
48};
49
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,
67 SENT_QUIT,
68 READING_DATA
69} ftp_state_t;
090089c4 70
71typedef struct _Ftpdata {
72 StoreEntry *entry;
983061ed 73 request_t *request;
77a30ebb 74 char user[MAX_URL];
75 char password[MAX_URL];
f0afe435 76 char *reply_hdr;
f0afe435 77 int reply_hdr_state;
3fdadc70 78 char *title_url;
79 int conn_att;
80 int login_att;
81 ftp_state_t state;
82 char *errmsg;
83 time_t mdtm;
84 int size;
85 int flags;
86 wordlist *pathcomps;
87 char *filepath;
88 int restart_offset;
89 int rest_att;
90 char *proxy_host;
91 size_t list_width;
92 wordlist *cwd_message;
93 struct {
94 int fd;
95 char *buf;
96 size_t size;
97 off_t offset;
98 FREE *freefunc;
99 wordlist *message;
100 char *last_message;
101 int replycode;
102 } ctrl;
103 struct {
104 int fd;
105 char *buf;
106 size_t size;
107 off_t offset;
108 FREE *freefunc;
109 } data;
e5f6c5c2 110} FtpStateData;
090089c4 111
3fdadc70 112typedef struct {
113 char type;
114 int size;
115 char *date;
116 char *name;
117 char *showname;
118 char *link;
119} ftpListParts;
120
121typedef void (FTPSM) _PARAMS((FtpStateData *));
0a0bf5db 122
983061ed 123/* Local functions */
9e4ad609 124static CNCB ftpConnectDone;
3fdadc70 125static CNCB ftpPasvCallback;
3fdadc70 126static PF ftpReadData;
9e4ad609 127static PF ftpStateFree;
5c5783a2 128static PF ftpTimeout;
3fdadc70 129static PF ftpReadControlReply;
130static CWCB ftpWriteCommandCallback;
9e4ad609 131static char *ftpGetBasicAuth _PARAMS((const char *));
9e4ad609 132static void ftpLoginParser _PARAMS((const char *, FtpStateData *));
3fdadc70 133static void ftpFail _PARAMS((FtpStateData * ftpState));
134static wordlist *ftpParseControlReply _PARAMS((char *buf, size_t len, int *code));
135static void ftpSendPasv _PARAMS((FtpStateData * ftpState));
136static void ftpSendCwd _PARAMS((FtpStateData * ftpState));
137static void ftpSendPort _PARAMS((FtpStateData * ftpState));
138static void ftpRestOrList _PARAMS((FtpStateData * ftpState));
139static void ftpReadQuit _PARAMS((FtpStateData * ftpState));
140static void ftpDataTransferDone _PARAMS((FtpStateData * ftpState));
141static void ftpAppendSuccessHeader _PARAMS((FtpStateData * ftpState));
bfcaf585 142static STABH ftpAbort;
983061ed 143
3fdadc70 144static FTPSM ftpReadWelcome;
145static FTPSM ftpReadUser;
146static FTPSM ftpReadPass;
147static FTPSM ftpReadType;
148static FTPSM ftpReadMdtm;
149static FTPSM ftpReadSize;
150static FTPSM ftpReadPort;
151static FTPSM ftpReadPasv;
152static FTPSM ftpReadCwd;
153static FTPSM ftpReadList;
154static FTPSM ftpReadRest;
155static FTPSM ftpReadRetr;
156static FTPSM ftpReadTransferDone;
157
158FTPSM *FTP_SM_FUNCS[] =
159{
160 ftpReadWelcome,
161 ftpReadUser,
162 ftpReadPass,
163 ftpReadType,
164 ftpReadMdtm,
165 ftpReadSize,
166 ftpReadPort,
167 ftpReadPasv,
168 ftpReadCwd,
169 ftpReadList, /* SENT_LIST */
170 ftpReadList, /* SENT_NLST */
171 ftpReadRest,
172 ftpReadRetr,
173 ftpReadQuit,
174 ftpReadTransferDone
175};
e381a13d 176
582b6456 177static void
178ftpStateFree(int fd, void *data)
ba718c8f 179{
582b6456 180 FtpStateData *ftpState = data;
51fa90db 181 if (ftpState == NULL)
582b6456 182 return;
270b86af 183 debug(9, 3) ("ftpStateFree: %s\n", ftpState->entry->url);
bfcaf585 184 storeUnregisterAbort(ftpState->entry);
f88211e8 185 storeUnlockObject(ftpState->entry);
51fa90db 186 if (ftpState->reply_hdr) {
187 put_free_8k_page(ftpState->reply_hdr);
188 ftpState->reply_hdr = NULL;
f0afe435 189 }
30a4f2a8 190 requestUnlink(ftpState->request);
3fdadc70 191 if (ftpState->ctrl.buf)
192 ftpState->ctrl.freefunc(ftpState->ctrl.buf);
193 if (ftpState->data.buf)
194 ftpState->data.freefunc(ftpState->data.buf);
195 if (ftpState->pathcomps)
196 wordlistDestroy(&ftpState->pathcomps);
197 if (ftpState->ctrl.message)
198 wordlistDestroy(&ftpState->ctrl.message);
199 if (ftpState->cwd_message)
200 wordlistDestroy(&ftpState->cwd_message);
b5639035 201 safe_free(ftpState->ctrl.last_message);
202 safe_free(ftpState->title_url);
203 safe_free(ftpState->filepath);
15888cf7 204 if (ftpState->data.fd > -1)
205 comm_close(ftpState->data.fd);
7dd44885 206 cbdataFree(ftpState);
ba718c8f 207}
208
b8d8561b 209static void
9e4ad609 210ftpLoginParser(const char *login, FtpStateData * ftpState)
090089c4 211{
983061ed 212 char *s = NULL;
582b6456 213 xstrncpy(ftpState->user, login, MAX_URL);
214 if ((s = strchr(ftpState->user, ':'))) {
983061ed 215 *s = 0;
582b6456 216 xstrncpy(ftpState->password, s + 1, MAX_URL);
983061ed 217 } else {
582b6456 218 xstrncpy(ftpState->password, null_string, MAX_URL);
983061ed 219 }
582b6456 220 if (ftpState->user[0] || ftpState->password[0])
429fdbec 221 return;
582b6456 222 xstrncpy(ftpState->user, "anonymous", MAX_URL);
e34e0322 223 xstrncpy(ftpState->password, Config.Ftp.anon_user, MAX_URL);
090089c4 224}
225
24382924 226static void
5c5783a2 227ftpTimeout(int fd, void *data)
090089c4 228{
582b6456 229 FtpStateData *ftpState = data;
230 StoreEntry *entry = ftpState->entry;
f52826a1 231 debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd, entry->url);
474cac1b 232 storeAbort(entry, ERR_READ_TIMEOUT, NULL, 0);
15888cf7 233 if (ftpState->data.fd >= 0) {
3fdadc70 234 comm_close(ftpState->data.fd);
15888cf7 235 ftpState->data.fd = -1;
236 }
3fdadc70 237 comm_close(ftpState->ctrl.fd);
090089c4 238}
239
3fdadc70 240static void
241ftpListingStart(FtpStateData * ftpState)
242{
243 StoreEntry *e = ftpState->entry;
244 wordlist *w;
245 storeAppendPrintf(e, "<!-- HTML listing generated by Squid %s -->\n",
246 version_string);
247 storeAppendPrintf(e, "<!-- %s -->\n", mkrfc1123(squid_curtime));
248 storeAppendPrintf(e, "<HTML><HEAD><TITLE>\n");
249 storeAppendPrintf(e, "FTP Directory: %s\n",
250 ftpState->title_url);
251 storeAppendPrintf(e, "</TITLE>\n");
252 if (EBIT_TEST(ftpState->flags, FTP_USE_BASE))
253 storeAppendPrintf(e, "<BASE HREF=\"%s\">\n",
254 rfc1738_escape(ftpState->title_url));
255 storeAppendPrintf(e, "</HEAD><BODY>\n");
256 if (ftpState->cwd_message) {
257 storeAppendPrintf(e, "<PRE>\n");
258 for (w = ftpState->cwd_message; w; w = w->next)
259 storeAppendPrintf(e, "%s\n", w->key);
260 storeAppendPrintf(e, "</PRE>\n");
261 storeAppendPrintf(e, "<HR>\n");
262 wordlistDestroy(&ftpState->cwd_message);
263 }
264 storeAppendPrintf(e, "<H2>\n");
265 storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url);
266 storeAppendPrintf(e, "</H2>\n");
267 storeAppendPrintf(e, "<PRE>\n");
268 EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT);
269}
270
271static void
272ftpListingFinish(FtpStateData * ftpState)
273{
274 StoreEntry *e = ftpState->entry;
275 storeAppendPrintf(e, "</PRE>\n");
276 storeAppendPrintf(e, "<HR>\n");
277 storeAppendPrintf(e, "<ADDRESS>\n");
278 storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n",
279 mkrfc1123(squid_curtime),
280 appname,
281 version_string,
282 getMyHostname());
283 storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
284}
285
286static const char *Month[] =
287{
288 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
289 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
290};
291
292static int
293is_month(const char *buf)
294{
295 int i;
296 for (i = 0; i < 12; i++)
297 if (!strcasecmp(buf, Month[i]))
298 return 1;
299 return 0;
300}
301
302
303static void
304ftpListPartsFree(ftpListParts ** parts)
305{
306 safe_free((*parts)->date);
307 safe_free((*parts)->name);
308 safe_free((*parts)->showname);
309 safe_free((*parts)->link);
310 safe_free(*parts);
311}
312
313#define MAX_TOKENS 64
314
315static ftpListParts *
316ftpListParseParts(const char *buf, int flags)
317{
318 ftpListParts *p = NULL;
319 char *t = NULL;
320 const char *ct = NULL;
321 char *tokens[MAX_TOKENS];
322 int i;
323 int n_tokens;
324 static char sbuf[128];
325 char *xbuf = NULL;
326 if (buf == NULL)
327 return NULL;
328 if (*buf == '\0')
329 return NULL;
330 p = xcalloc(1, sizeof(ftpListParts));
331 n_tokens = 0;
332 for (i = 0; i < MAX_TOKENS; i++)
333 tokens[i] = (char *) NULL;
334 xbuf = xstrdup(buf);
335 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
336 tokens[n_tokens++] = xstrdup(t);
337 xfree(xbuf);
338 /* locate the Month field */
339 for (i = 3; i < n_tokens - 3; i++) {
340 if (!is_month(tokens[i])) /* Month */
341 continue;
342 if (!sscanf(tokens[i - 1], "%[0-9]", sbuf)) /* Size */
343 continue;
344 if (!sscanf(tokens[i + 1], "%[0-9]", sbuf)) /* Day */
345 continue;
346 if (!sscanf(tokens[i + 2], "%[0-9:]", sbuf)) /* Yr | hh:mm */
347 continue;
348 p->type = *tokens[0];
349 p->size = atoi(tokens[i - 1]);
350 sprintf(sbuf, "%s %2s %5s",
351 tokens[i], tokens[i + 1], tokens[i + 2]);
352 if (!strstr(buf, sbuf))
353 sprintf(sbuf, "%s %2s %-5s",
354 tokens[i], tokens[i + 1], tokens[i + 2]);
355 if ((t = strstr(buf, sbuf))) {
356 p->date = xstrdup(sbuf);
357 if (BIT_TEST(flags, FTP_SKIP_WHITESPACE)) {
358 t += strlen(sbuf);
359 while (strchr(w_space, *t))
360 t++;
361 } else {
362 /* XXX assumes a single space between date and filename
363 * suggested by: Nathan.Bailey@cc.monash.edu.au and
364 * Mike Battersby <mike@starbug.bofh.asn.au> */
365 t += strlen(sbuf) + 1;
366 }
367 p->name = xstrdup(t);
368 if ((t = strstr(p->name, " -> "))) {
369 *t = '\0';
370 p->link = xstrdup(t + 4);
371 }
372 }
373 break;
374 }
375 /* try it as a DOS listing */
376 if (n_tokens > 3 && p->name == NULL &&
377 sscanf(tokens[0], "%[0-9]-%[0-9]-%[0-9]", sbuf, sbuf, sbuf) == 3 &&
378 /* 04-05-70 */
379 sscanf(tokens[1], "%[0-9]:%[0-9]%[AaPp]%[Mm]", sbuf, sbuf, sbuf, sbuf) == 4) {
380 /* 09:33PM */
381 if (!strcasecmp(tokens[2], "<dir>")) {
382 p->type = 'd';
383 } else {
384 p->type = '-';
385 p->size = atoi(tokens[2]);
386 }
387 sprintf(sbuf, "%s %s", tokens[0], tokens[1]);
388 p->date = xstrdup(sbuf);
389 p->name = xstrdup(tokens[3]);
390 }
391 /* Try EPLF format; carson@lehman.com */
392 if (p->name == NULL && buf[0] == '+') {
393 ct = buf + 1;
394 p->type = 0;
395 while (ct && *ct) {
396 switch (*ct) {
397 case '\t':
398 sscanf(ct + 1, "%[^,]", sbuf);
399 p->name = xstrdup(sbuf);
400 break;
401 case 's':
402 sscanf(ct + 1, "%d", &(p->size));
403 break;
404 case 'm':
405 sscanf(ct + 1, "%d", &i);
406 p->date = xstrdup(ctime((time_t *) & i));
407 *(strstr(p->date, "\n")) = '\0';
408 break;
409 case '/':
410 p->type = 'd';
411 break;
412 case 'r':
413 p->type = '-';
414 break;
415 case 'i':
416 break;
417 default:
418 break;
419 }
420 ct = strstr(ct, ",");
421 if (ct) {
422 ct++;
423 }
424 }
425 if (p->type == 0) {
426 p->type = '-';
427 }
428 }
429 for (i = 0; i < n_tokens; i++)
430 xfree(tokens[i]);
b5639035 431 if (p->name == NULL)
432 ftpListPartsFree(&p);
3fdadc70 433 return p;
434}
435
436static const char *
437dots_fill(size_t len)
438{
439 static char buf[256];
440 int i = 0;
441 if (len > Config.Ftp.list_width) {
442 memset(buf, ' ', 256);
443 buf[0] = '\n';
444 buf[Config.Ftp.list_width + 4] = '\0';
445 return buf;
446 }
447 for (i = (int) len; i < Config.Ftp.list_width; i++)
448 buf[i - len] = (i % 2) ? '.' : ' ';
449 buf[i - len] = '\0';
450 return buf;
451}
452
453static char *
454ftpHtmlifyListEntry(char *line, int flags)
455{
456 LOCAL_ARRAY(char, link, 2048);
457 LOCAL_ARRAY(char, icon, 2048);
458 LOCAL_ARRAY(char, html, 8192);
459 char *ename = NULL;
460 size_t width = Config.Ftp.list_width;
461 ftpListParts *parts;
462 /* check .. as special case */
463 if (!strcmp(line, "..")) {
365cb147 464 sprintf(icon, "<IMG BORDER=0 SRC=\"%s%s\" ALT=\"%-6s\">",
465 "http://internal.squid/icons/",
4f117cb4 466 ICON_DIRUP,
3fdadc70 467 "[DIR]");
468 sprintf(link, "<A HREF=\"%s\">%s</A>", "../", "Parent Directory");
469 sprintf(html, "%s %s\n", icon, link);
470 return html;
471 }
472 if (strlen(line) > 1024) {
473 sprintf(html, "%s\n", line);
474 return html;
475 }
476 if ((parts = ftpListParseParts(line, flags)) == NULL) {
477 sprintf(html, "%s\n", line);
478 return html;
479 }
480 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
481 *html = '\0';
b5639035 482 ftpListPartsFree(&parts);
3fdadc70 483 return html;
484 }
485 parts->size += 1023;
486 parts->size >>= 10;
487 parts->showname = xstrdup(parts->name);
488 if (!Config.Ftp.list_wrap) {
489 if (strlen(parts->showname) > width - 1) {
490 *(parts->showname + width - 1) = '>';
491 *(parts->showname + width - 0) = '\0';
492 }
493 }
494 ename = xstrdup(rfc1738_escape(parts->name));
495 switch (parts->type) {
496 case 'd':
365cb147 497 sprintf(icon, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
498 "http://internal.squid/icons/",
4f117cb4 499 ICON_MENU,
3fdadc70 500 "[DIR]");
501 sprintf(link, "<A HREF=\"%s/\">%s</A>%s",
502 ename,
503 parts->showname,
504 dots_fill(strlen(parts->showname)));
505 sprintf(html, "%s %s [%s]\n",
506 icon,
507 link,
508 parts->date);
509 break;
510 case 'l':
365cb147 511 sprintf(icon, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
512 "http://internal.squid/icons/",
4f117cb4 513 ICON_LINK,
3fdadc70 514 "[LINK]");
515 sprintf(link, "<A HREF=\"%s\">%s</A>%s",
516 ename,
517 parts->showname,
518 dots_fill(strlen(parts->showname)));
519 sprintf(html, "%s %s [%s]\n",
520 icon,
521 link,
522 parts->date);
523 break;
524 case '-':
525 default:
365cb147 526 sprintf(icon, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
527 "http://internal.squid/icons/",
3fdadc70 528 mimeGetIcon(parts->name),
3fdadc70 529 "[FILE]");
530 sprintf(link, "<A HREF=\"%s\">%s</A>%s",
531 ename,
532 parts->showname,
533 dots_fill(strlen(parts->showname)));
534 sprintf(html, "%s %s [%s] %6dk\n",
535 icon,
536 link,
537 parts->date,
538 parts->size);
539 break;
540 }
541 ftpListPartsFree(&parts);
542 xfree(ename);
543 return html;
544}
545
546static void
547ftpParseListing(FtpStateData * ftpState, int len)
548{
549 char *buf = ftpState->data.buf;
b5639035 550 char *end;
551 char *line;
3fdadc70 552 char *s;
553 char *t;
554 size_t linelen;
b5639035 555 size_t usable;
3fdadc70 556 StoreEntry *e = ftpState->entry;
b5639035 557 len += ftpState->data.offset;
558 end = buf + len - 1;
3fdadc70 559 while (*end != '\r' && *end != '\n' && end > buf)
560 end--;
b5639035 561 usable = end - buf;
562 if (usable == 0) {
a3d5953d 563 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", e->url);
3fdadc70 564 return;
565 }
b5639035 566 line = get_free_4k_page();
3fdadc70 567 end++;
568 for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
569 linelen = strcspn(s, crlf) + 1;
570 if (linelen > 4096)
571 linelen = 4096;
572 xstrncpy(line, s, linelen);
a3d5953d 573 debug(9, 7) ("%s\n", line);
3fdadc70 574 if (!strncmp(line, "total", 5))
575 continue;
576 t = ftpHtmlifyListEntry(line, ftpState->flags);
577 assert(t != NULL);
578 storeAppend(e, t, strlen(t));
579 }
b5639035 580 assert(usable <= len);
581 if (usable < len) {
582 /* must copy partial line to beginning of buf */
583 linelen = len - usable + 1;
584 assert(linelen > 0);
585 if (linelen > 4096)
586 linelen = 4096;
587 xstrncpy(line, end, linelen);
588 xstrncpy(ftpState->data.buf, line, ftpState->data.size);
589 ftpState->data.offset = strlen(ftpState->data.buf);
590 }
591 put_free_4k_page(line);
3fdadc70 592}
090089c4 593
582b6456 594static void
3fdadc70 595ftpReadData(int fd, void *data)
090089c4 596{
582b6456 597 FtpStateData *ftpState = data;
090089c4 598 int len;
599 int clen;
600 int off;
30a4f2a8 601 int bin;
bfcaf585 602 StoreEntry *entry = ftpState->entry;
3fdadc70 603 assert(fd == ftpState->data.fd);
3fdadc70 604 if (protoAbortFetch(entry)) {
474cac1b 605 storeAbort(entry, ERR_CLIENT_ABORT, NULL, 0);
a3d5953d 606 ftpDataTransferDone(ftpState);
607 return;
30a4f2a8 608 }
609 /* check if we want to defer reading */
610 clen = entry->mem_obj->e_current_len;
611 off = storeGetLowestReaderOffset(entry);
612 if ((clen - off) > FTP_DELETE_GAP) {
30a4f2a8 613 IOStats.Ftp.reads_deferred++;
a3d5953d 614 debug(9, 3) ("ftpReadData: Read deferred for Object: %s\n",
30a4f2a8 615 entry->url);
3fdadc70 616 commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0);
56fa4cad 617 if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
5c5783a2 618 commSetTimeout(fd, Config.Timeout.defer, NULL, NULL);
56fa4cad 619 BIT_SET(entry->flag, READ_DEFERRED);
620 }
30a4f2a8 621 /* dont try reading again for a while */
b6f794d6 622 comm_set_stall(fd, Config.stallDelay);
582b6456 623 return;
56fa4cad 624 } else {
625 BIT_RESET(entry->flag, READ_DEFERRED);
090089c4 626 }
3fdadc70 627 if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
628 if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
629 ftpListingStart(ftpState);
1a6e21ac 630 errno = 0;
3fdadc70 631 memset(ftpState->data.buf + ftpState->data.offset, '\0',
632 ftpState->data.size - ftpState->data.offset);
633 len = read(fd,
634 ftpState->data.buf + ftpState->data.offset,
635 ftpState->data.size - ftpState->data.offset);
4f92c80c 636 fd_bytes(fd, len, FD_READ);
a3d5953d 637 debug(9, 5) ("ftpReadData: FD %d, Read %d bytes\n", fd, len);
30a4f2a8 638 if (len > 0) {
5c5783a2 639 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
4a63c85f 640 IOStats.Ftp.reads++;
30a4f2a8 641 for (clen = len - 1, bin = 0; clen; bin++)
642 clen >>= 1;
643 IOStats.Ftp.read_hist[bin]++;
644 }
ba718c8f 645 if (len < 0) {
a3d5953d 646 debug(50, 1) ("ftpReadData: read error: %s\n", xstrerror());
0a0bf5db 647 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
02be0294 648 commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0);
6fe6313d 649 } else {
1c481e00 650 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 651 storeReleaseRequest(entry);
474cac1b 652 storeAbort(entry, ERR_READ_ERROR, xstrerror(), 0);
3fdadc70 653 ftpDataTransferDone(ftpState);
6fe6313d 654 }
ba718c8f 655 } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
474cac1b 656 storeAbort(entry, ERR_ZERO_SIZE_OBJECT, errno ? xstrerror() : NULL, 0);
3fdadc70 657 ftpDataTransferDone(ftpState);
090089c4 658 } else if (len == 0) {
659 /* Connection closed; retrieval done. */
3fdadc70 660 if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
661 ftpListingFinish(ftpState);
662 storeTimestampsSet(entry);
090089c4 663 storeComplete(entry);
02be0294 664 /* expect the "transfer complete" message on the control socket */
270b86af 665 commSetSelect(ftpState->ctrl.fd,
666 COMM_SELECT_READ,
667 ftpReadControlReply,
668 ftpState,
669 0);
090089c4 670 } else {
b5639035 671 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
3fdadc70 672 ftpParseListing(ftpState, len);
b5639035 673 } else {
674 assert(ftpState->data.offset == 0);
675 storeAppend(entry, ftpState->data.buf, len);
676 }
3fdadc70 677 commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0);
090089c4 678 }
090089c4 679}
680
0f9306d9 681static char *
0ee4272b 682ftpGetBasicAuth(const char *req_hdr)
0f9306d9 683{
684 char *auth_hdr;
685 char *t;
686 if (req_hdr == NULL)
687 return NULL;
688 if ((auth_hdr = mime_get_header(req_hdr, "Authorization")) == NULL)
689 return NULL;
690 if ((t = strtok(auth_hdr, " \t")) == NULL)
691 return NULL;
692 if (strcasecmp(t, "Basic") != 0)
693 return NULL;
694 if ((t = strtok(NULL, " \t")) == NULL)
695 return NULL;
696 return base64_decode(t);
697}
698
429fdbec 699/*
700 * ftpCheckAuth
701 *
702 * Return 1 if we have everything needed to complete this request.
703 * Return 0 if something is missing.
704 */
705static int
706ftpCheckAuth(FtpStateData * ftpState, char *req_hdr)
707{
708 char *orig_user;
709 char *auth;
9e4ad609 710 ftpLoginParser(ftpState->request->login, ftpState);
429fdbec 711 if (ftpState->user[0] && ftpState->password[0])
712 return 1; /* name and passwd both in URL */
713 if (!ftpState->user[0] && !ftpState->password[0])
714 return 1; /* no name or passwd */
715 if (ftpState->password[0])
716 return 1; /* passwd with no name? */
717 /* URL has name, but no passwd */
718 if ((auth = ftpGetBasicAuth(req_hdr)) == NULL)
719 return 0; /* need auth header */
720 orig_user = xstrdup(ftpState->user);
9e4ad609 721 ftpLoginParser(auth, ftpState);
429fdbec 722 if (!strcmp(orig_user, ftpState->user)) {
723 xfree(orig_user);
724 return 1; /* same username */
725 }
726 strcpy(ftpState->user, orig_user);
727 xfree(orig_user);
728 return 0; /* different username */
729}
730
3fdadc70 731static void
732ftpCleanupUrlpath(FtpStateData * ftpState)
733{
734 request_t *request = ftpState->request;
735 int again;
736 int l;
737 char *t = NULL;
738 char *s = NULL;
739 do {
740 again = 0;
741 l = strlen(request->urlpath);
742 /* check for null path */
743 if (*request->urlpath == '\0') {
744 xstrncpy(request->urlpath, ".", MAX_URL);
745 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
746 again = 1;
747 } else if ((l >= 1) && (*(request->urlpath + l - 1) == '/')) {
748 /* remove any trailing slashes from path */
749 *(request->urlpath + l - 1) = '\0';
750 EBIT_SET(ftpState->flags, FTP_ISDIR);
751 EBIT_RESET(ftpState->flags, FTP_USE_BASE);
752 again = 1;
753 } else if ((l >= 2) && (!strcmp(request->urlpath + l - 2, "/."))) {
754 /* remove trailing /. */
755 *(request->urlpath + l - 2) = '\0';
756 EBIT_SET(ftpState->flags, FTP_ISDIR);
757 EBIT_RESET(ftpState->flags, FTP_USE_BASE);
758 again = 1;
759 } else if (*request->urlpath == '/') {
760 /* remove any leading slashes from path */
761 t = xstrdup(request->urlpath + 1);
762 xstrncpy(request->urlpath, t, MAX_URL);
763 xfree(t);
764 again = 1;
765 } else if (!strncmp(request->urlpath, "./", 2)) {
766 /* remove leading ./ */
767 t = xstrdup(request->urlpath + 2);
768 xstrncpy(request->urlpath, t, MAX_URL);
769 xfree(t);
770 again = 1;
771 } else if ((t = strstr(request->urlpath, "/./"))) {
772 /* remove /./ */
773 s = xstrdup(t + 2);
774 xstrncpy(t, s, strlen(s));
775 xfree(s);
776 again = 1;
777 } else if ((t = strstr(request->urlpath, "//"))) {
778 /* remove // */
779 s = xstrdup(t + 1);
780 xstrncpy(t, s, strlen(s));
781 xfree(s);
782 again = 1;
783 }
784 } while (again);
785}
786
787
788static void
789ftpBuildTitleUrl(FtpStateData * ftpState)
790{
791 request_t *request = ftpState->request;
792 size_t len;
793 char *t;
794 len = 64
795 + strlen(ftpState->user)
796 + strlen(ftpState->password)
797 + strlen(request->host)
798 + strlen(request->urlpath);
799 t = ftpState->title_url = xcalloc(len, 1);
800 strcat(t, "ftp://");
801 if (strcmp(ftpState->user, "anonymous")) {
802 strcat(t, ftpState->user);
803 strcat(t, "@");
804 }
805 strcat(t, request->host);
806 if (request->port != urlDefaultPort(PROTO_FTP))
807 sprintf(&t[strlen(t)], ":%d", request->port);
808 strcat(t, "/");
809 if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR))
810 strcat(t, request->urlpath);
811}
812
813
770f051d 814void
75e88d56 815ftpStart(request_t * request, StoreEntry * entry)
0a0bf5db 816{
0a0bf5db 817 LOCAL_ARRAY(char, realm, 8192);
75e88d56 818 char *url = entry->url;
5c5783a2 819 FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData));
0a0bf5db 820 char *response;
3fdadc70 821 int fd;
7dd44885 822 cbdataAdd(ftpState);
a3d5953d 823 debug(9, 3) ("FtpStart: '%s'\n", entry->url);
770f051d 824 storeLockObject(entry);
5c5783a2 825 ftpState->entry = entry;
5c5783a2 826 ftpState->request = requestLink(request);
3fdadc70 827 ftpState->ctrl.fd = -1;
828 ftpState->data.fd = -1;
829 EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED);
830 EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED);
62dec5a7 831 if (!ftpCheckAuth(ftpState, request->headers)) {
429fdbec 832 /* This request is not fully authenticated */
833 if (request->port == 21) {
5c5783a2 834 sprintf(realm, "ftp %s", ftpState->user);
429fdbec 835 } else {
836 sprintf(realm, "ftp %s port %d",
5c5783a2 837 ftpState->user, request->port);
e381a13d 838 }
429fdbec 839 response = authorization_needed_msg(request, realm);
840 storeAppend(entry, response, strlen(response));
841 httpParseReplyHeaders(response, entry->mem_obj->reply);
842 storeComplete(entry);
5c5783a2 843 ftpStateFree(-1, ftpState);
429fdbec 844 return;
e381a13d 845 }
3fdadc70 846 ftpCleanupUrlpath(ftpState);
847 ftpBuildTitleUrl(ftpState);
a3d5953d 848 debug(9, 5) ("FtpStart: host=%s, path=%s, user=%s, passwd=%s\n",
5c5783a2 849 ftpState->request->host, ftpState->request->urlpath,
850 ftpState->user, ftpState->password);
3fdadc70 851 fd = comm_open(SOCK_STREAM,
16b204c4 852 0,
3fdadc70 853 Config.Addrs.tcp_outgoing,
30a4f2a8 854 0,
16b204c4 855 COMM_NONBLOCKING,
30a4f2a8 856 url);
3fdadc70 857 if (fd == COMM_ERROR) {
a3d5953d 858 debug(9, 4) ("ftpStart: Failed because we're out of sockets.\n");
474cac1b 859 storeAbort(entry, ERR_NO_FDS, xstrerror(), 0);
0a0bf5db 860 return;
090089c4 861 }
3fdadc70 862 ftpState->ctrl.fd = fd;
863 comm_add_close_handler(fd, ftpStateFree, ftpState);
5fbef0c9 864 storeRegisterAbort(entry, ftpAbort, ftpState);
3fdadc70 865 commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
3fdadc70 866 commConnectStart(ftpState->ctrl.fd,
867 request->host,
868 request->port,
e924600d 869 ftpConnectDone,
5c5783a2 870 ftpState);
e5f6c5c2 871}
090089c4 872
e5f6c5c2 873static void
874ftpConnectDone(int fd, int status, void *data)
875{
5c5783a2 876 FtpStateData *ftpState = data;
9e975e4e 877 request_t *request = ftpState->request;
878 debug(9, 3) ("ftpConnectDone, status = %d\n", status);
879 if (status == COMM_ERR_DNS) {
880 debug(9, 4) ("ftpConnectDone: Unknown host: %s\n", request->host);
881 storeAbort(ftpState->entry, ERR_DNS_FAIL, dns_error_message, 0);
882 comm_close(fd);
883 } else if (status != COMM_OK) {
474cac1b 884 storeAbort(ftpState->entry, ERR_CONNECT_FAIL, xstrerror(), 0);
e5f6c5c2 885 comm_close(fd);
9e975e4e 886 } else {
887 ftpState->state = BEGIN;
888 ftpState->ctrl.buf = get_free_4k_page();
889 ftpState->ctrl.freefunc = put_free_4k_page;
890 ftpState->ctrl.size = 4096;
891 ftpState->ctrl.offset = 0;
892 ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF);
893 ftpState->data.size = SQUID_TCP_SO_RCVBUF;
894 ftpState->data.freefunc = xfree;
895 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
e5f6c5c2 896 }
090089c4 897}
898
3fdadc70 899/* ====================================================================== */
900
901
b8d8561b 902static void
3fdadc70 903ftpWriteCommand(const char *buf, FtpStateData * ftpState)
234967c9 904{
a3d5953d 905 debug(9, 5) ("ftpWriteCommand: %s\n", buf);
3fdadc70 906 comm_write(ftpState->ctrl.fd,
907 xstrdup(buf),
908 strlen(buf),
909 ftpWriteCommandCallback,
910 ftpState,
911 xfree);
912 commSetSelect(ftpState->ctrl.fd,
913 COMM_SELECT_READ,
914 ftpReadControlReply,
915 ftpState,
916 0);
917}
918
919static void
920ftpWriteCommandCallback(int fd, char *buf, int size, int errflag, void *data)
921{
922 FtpStateData *ftpState = data;
923 StoreEntry *entry = ftpState->entry;
a3d5953d 924 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size);
3fdadc70 925 if (errflag) {
270b86af 926 debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd, xstrerror());
3fdadc70 927 BIT_RESET(entry->flag, ENTRY_CACHABLE);
928 storeReleaseRequest(entry);
474cac1b 929 storeAbort(entry, ERR_WRITE_ERROR, xstrerror(), 0);
3fdadc70 930 comm_close(fd);
931 }
932}
933
934static wordlist *
935ftpParseControlReply(char *buf, size_t len, int *codep)
936{
937 char *s;
938 int complete = 0;
939 wordlist *head;
940 wordlist *list;
941 wordlist **tail = &head;
942 off_t offset;
943 size_t linelen;
b5639035 944 int code = -1;
a3d5953d 945 debug(9, 5) ("ftpParseControlReply\n");
3fdadc70 946 if (*(buf + len - 1) != '\n')
947 return NULL;
948 for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) {
949 linelen = strcspn(s, crlf) + 1;
950 if (linelen > 3)
951 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
952 if (complete)
953 code = atoi(s);
954 offset = 0;
955 if (linelen > 3)
956 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
957 offset = 4;
958 list = xcalloc(1, sizeof(wordlist));
959 list->key = xmalloc(linelen - offset);
960 xstrncpy(list->key, s + offset, linelen - offset);
4939c5da 961 debug(9, 7) ("%d %s\n", code, list->key);
3fdadc70 962 *tail = list;
963 tail = &list->next;
964 }
965 if (!complete)
966 wordlistDestroy(&head);
967 if (codep)
968 *codep = code;
969 return head;
970}
971
972static void
973ftpReadControlReply(int fd, void *data)
974{
975 FtpStateData *ftpState = data;
976 StoreEntry *entry = ftpState->entry;
977 char *oldbuf;
978 wordlist **W;
979 int len;
a3d5953d 980 debug(9, 5) ("ftpReadControlReply\n");
3fdadc70 981 assert(ftpState->ctrl.offset < ftpState->ctrl.size);
982 len = read(fd,
983 ftpState->ctrl.buf + ftpState->ctrl.offset,
984 ftpState->ctrl.size - ftpState->ctrl.offset);
985 fd_bytes(fd, len, FD_READ);
a3d5953d 986 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd, len);
3fdadc70 987 if (len > 0)
988 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
3fdadc70 989 if (len < 0) {
a3d5953d 990 debug(50, 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
3fdadc70 991 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
992 commSetSelect(fd,
993 COMM_SELECT_READ,
994 ftpReadControlReply,
995 ftpState,
996 0);
997 } else {
998 BIT_RESET(entry->flag, ENTRY_CACHABLE);
999 storeReleaseRequest(entry);
474cac1b 1000 storeAbort(entry, ERR_READ_ERROR, xstrerror(), 0);
3fdadc70 1001 comm_close(fd);
1002 }
1003 return;
1004 }
1005 if (len == 0) {
a3d5953d 1006 debug(9, 1) ("Read 0 bytes from FTP control socket?\n");
9906e724 1007 assert(len);
3fdadc70 1008 BIT_RESET(entry->flag, ENTRY_CACHABLE);
1009 storeReleaseRequest(entry);
474cac1b 1010 storeAbort(entry, ERR_READ_ERROR, xstrerror(), 0);
3fdadc70 1011 comm_close(fd);
1012 return;
1013 }
b5639035 1014 len += ftpState->ctrl.offset;
1015 ftpState->ctrl.offset = len;
3fdadc70 1016 assert(len <= ftpState->ctrl.size);
1017 wordlistDestroy(&ftpState->ctrl.message);
1018 ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len,
1019 &ftpState->ctrl.replycode);
1020 if (ftpState->ctrl.message == NULL) {
a3d5953d 1021 debug(9, 5) ("ftpReadControlReply: partial server reply\n");
3fdadc70 1022 if (len == ftpState->ctrl.size) {
1023 oldbuf = ftpState->ctrl.buf;
1024 ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1);
1025 xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size);
1026 ftpState->ctrl.size <<= 1;
1027 ftpState->ctrl.freefunc(oldbuf);
1028 ftpState->ctrl.freefunc = xfree;
1029 }
1030 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
1031 return;
1032 }
1033 for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next);
b5639035 1034 safe_free(ftpState->ctrl.last_message);
3fdadc70 1035 ftpState->ctrl.last_message = (*W)->key;
1036 safe_free(*W);
1037 ftpState->ctrl.offset = 0;
1038 FTP_SM_FUNCS[ftpState->state] (ftpState);
234967c9 1039}
1040
3fdadc70 1041/* ====================================================================== */
1042
1043static void
1044ftpReadWelcome(FtpStateData * ftpState)
1045{
1046 int code = ftpState->ctrl.replycode;
a3d5953d 1047 debug(9, 3) ("ftpReadWelcome\n");
3fdadc70 1048 if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY))
1049 ftpState->login_att++;
1050 if (code == 220) {
1051 if (ftpState->ctrl.message)
1052 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1053 EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
1054 if (ftpState->proxy_host != NULL)
1055 sprintf(cbuf, "USER %s@%s\r\n",
1056 ftpState->user,
1057 ftpState->request->host);
1058 else
1059 sprintf(cbuf, "USER %s\r\n", ftpState->user);
1060 ftpWriteCommand(cbuf, ftpState);
1061 ftpState->state = SENT_USER;
1062 } else {
1063 ftpFail(ftpState);
1064 }
1065}
1066
1067static void
1068ftpReadUser(FtpStateData * ftpState)
234967c9 1069{
3fdadc70 1070 int code = ftpState->ctrl.replycode;
a3d5953d 1071 debug(9, 3) ("ftpReadUser\n");
3fdadc70 1072 if (code == 230) {
1073 ftpReadPass(ftpState);
1074 } else if (code == 331) {
1075 sprintf(cbuf, "PASS %s\r\n", ftpState->password);
1076 ftpWriteCommand(cbuf, ftpState);
1077 ftpState->state = SENT_PASS;
1078 } else {
1079 ftpFail(ftpState);
1080 }
1081}
1082
1083static void
1084ftpReadPass(FtpStateData * ftpState)
1085{
1086 int code = ftpState->ctrl.replycode;
1087 char *t;
1088 char *filename;
1089 char mode;
a3d5953d 1090 debug(9, 3) ("ftpReadPass\n");
3fdadc70 1091 if (code == 230) {
1092 t = strrchr(ftpState->request->urlpath, '/');
1093 filename = t ? t + 1 : ftpState->request->urlpath;
1094 mode = mimeGetTransferMode(filename);
1095 sprintf(cbuf, "TYPE %c\r\n", mode);
1096 ftpWriteCommand(cbuf, ftpState);
1097 ftpState->state = SENT_TYPE;
1098 } else {
1099 ftpFail(ftpState);
1100 }
1101}
1102
1103static void
1104ftpReadType(FtpStateData * ftpState)
1105{
1106 int code = ftpState->ctrl.replycode;
1107 wordlist *w;
1108 wordlist **T;
1109 char *path;
1110 char *d;
a3d5953d 1111 debug(9, 3) ("This is ftpReadType\n");
3fdadc70 1112 if (code == 200) {
1113 if (EBIT_TEST(ftpState->flags, FTP_ROOT_DIR)) {
1114 ftpSendPasv(ftpState);
1115 } else {
1116 path = xstrdup(ftpState->request->urlpath);
1117 T = &ftpState->pathcomps;
1118 for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) {
1119 w = xcalloc(1, sizeof(wordlist));
1120 w->key = xstrdup(d);
1121 *T = w;
1122 T = &w->next;
1123 }
b5639035 1124 xfree(path);
3fdadc70 1125 ftpSendCwd(ftpState);
1126 }
1127 } else {
1128 ftpFail(ftpState);
1129 }
1130}
1131
1132static void
1133ftpSendCwd(FtpStateData * ftpState)
1134{
1135 wordlist *w;
a3d5953d 1136 debug(9, 3) ("ftpSendCwd\n");
3fdadc70 1137 if ((w = ftpState->pathcomps) == NULL) {
a3d5953d 1138 debug(9, 3) ("the final component was a directory\n");
3fdadc70 1139 EBIT_SET(ftpState->flags, FTP_ISDIR);
4939c5da 1140 EBIT_SET(ftpState->flags, FTP_USE_BASE);
3fdadc70 1141 if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR))
1142 strcat(ftpState->title_url, "/");
1143 ftpSendPasv(ftpState);
234967c9 1144 return;
3fdadc70 1145 }
1146 sprintf(cbuf, "CWD %s\r\n", w->key);
1147 ftpWriteCommand(cbuf, ftpState);
1148 ftpState->state = SENT_CWD;
1149}
77a30ebb 1150
3fdadc70 1151static void
1152ftpReadCwd(FtpStateData * ftpState)
1153{
1154 int code = ftpState->ctrl.replycode;
1155 size_t len = 0;
1156 wordlist *w;
a3d5953d 1157 debug(9, 3) ("This is ftpReadCwd\n");
3fdadc70 1158 w = ftpState->pathcomps;
1159 assert(w != NULL);
1160 if (code >= 200 && code < 300) {
1161 if (ftpState->cwd_message)
1162 wordlistDestroy(&ftpState->cwd_message);
1163 ftpState->cwd_message = ftpState->ctrl.message;
1164 ftpState->ctrl.message = NULL;
1165 /* CWD OK */
1166 ftpState->pathcomps = w->next;
1167 xfree(w->key);
1168 xfree(w);
1169 ftpSendCwd(ftpState);
1170 } else {
1171 /* CWD FAILED */
1172 while (w) {
1173 len += (strlen(w->key) + 1);
1174 w = w->next;
1175 }
1176 ftpState->filepath = xcalloc(len, 1);
1177 for (w = ftpState->pathcomps; w; w = w->next) {
1178 strcat(ftpState->filepath, w->key);
1179 if (w->next)
1180 strcat(ftpState->filepath, "/");
1181 }
1182 wordlistDestroy(&ftpState->pathcomps);
1183 assert(*ftpState->filepath != '\0');
1184 sprintf(cbuf, "MDTM %s\r\n", ftpState->filepath);
1185 ftpWriteCommand(cbuf, ftpState);
1186 ftpState->state = SENT_MDTM;
c021888f 1187 }
3fdadc70 1188}
1189
1190static void
1191ftpReadMdtm(FtpStateData * ftpState)
1192{
1193 int code = ftpState->ctrl.replycode;
a3d5953d 1194 debug(9, 3) ("This is ftpReadMdtm\n");
3fdadc70 1195 if (code == 213) {
1196 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_message);
3fdadc70 1197 } else if (code < 0) {
1198 ftpFail(ftpState);
77a30ebb 1199 }
4939c5da 1200 assert(ftpState->filepath != NULL);
1201 assert(*ftpState->filepath != '\0');
1202 sprintf(cbuf, "SIZE %s\r\n", ftpState->filepath);
1203 ftpWriteCommand(cbuf, ftpState);
1204 ftpState->state = SENT_SIZE;
3fdadc70 1205}
1206
1207static void
1208ftpReadSize(FtpStateData * ftpState)
1209{
1210 int code = ftpState->ctrl.replycode;
a3d5953d 1211 debug(9, 3) ("This is ftpReadSize\n");
3fdadc70 1212 if (code == 213) {
1213 ftpState->size = atoi(ftpState->ctrl.last_message);
3fdadc70 1214 } else if (code < 0) {
1215 ftpFail(ftpState);
1216 }
4939c5da 1217 ftpSendPasv(ftpState);
3fdadc70 1218}
1219
1220static void
1221ftpSendPasv(FtpStateData * ftpState)
1222{
1223 int fd;
1224 assert(ftpState->data.fd < 0);
1225 if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
1226 ftpSendPort(ftpState);
1227 return;
30a4f2a8 1228 }
3fdadc70 1229 fd = comm_open(SOCK_STREAM,
16b204c4 1230 0,
3fdadc70 1231 Config.Addrs.tcp_outgoing,
30a4f2a8 1232 0,
3fdadc70 1233 COMM_NONBLOCKING,
1234 ftpState->entry->url);
1235 if (fd < 0) {
1236 ftpFail(ftpState);
1237 return;
1238 }
1239 ftpState->data.fd = fd;
1240 commSetTimeout(fd, Config.Timeout.read, ftpTimeout, ftpState);
1241 sprintf(cbuf, "PASV\r\n");
1242 ftpWriteCommand(cbuf, ftpState);
1243 ftpState->state = SENT_PASV;
1244}
1245
1246static void
1247ftpReadPasv(FtpStateData * ftpState)
1248{
1249 int code = ftpState->ctrl.replycode;
1250 int h1, h2, h3, h4;
1251 int p1, p2;
1252 int n;
1253 u_short port;
1254 int fd = ftpState->data.fd;
1255 char *buf = ftpState->ctrl.last_message;
1256 LOCAL_ARRAY(char, junk, 1024);
a3d5953d 1257 debug(9, 3) ("This is ftpReadPasv\n");
3fdadc70 1258 if (code != 227) {
a3d5953d 1259 debug(9, 3) ("PASV not supported by remote end\n");
3fdadc70 1260 ftpSendPort(ftpState);
1261 return;
1262 }
1263 if (strlen(buf) > 1024) {
a3d5953d 1264 debug(9, 1) ("Avoiding potential buffer overflow\n");
3fdadc70 1265 ftpSendPort(ftpState);
1266 return;
1267 }
1268 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1269 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a3d5953d 1270 debug(9, 5) ("scanning: %s\n", buf);
3fdadc70 1271 n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1272 junk, &h1, &h2, &h3, &h4, &p1, &p2);
1273 if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a3d5953d 1274 debug(9, 3) ("Bad 227 reply\n");
1275 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n, p1, p2);
3fdadc70 1276 ftpSendPort(ftpState);
1277 return;
1278 }
1279 sprintf(junk, "%d.%d.%d.%d", h1, h2, h3, h4);
1280 if (!safe_inet_addr(junk, NULL)) {
a3d5953d 1281 debug(9, 1) ("unsafe address (%s)\n", junk);
3fdadc70 1282 ftpSendPort(ftpState);
1283 return;
1284 }
1285 port = ((p1 << 8) + p2);
a3d5953d 1286 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk, port);
3fdadc70 1287 commConnectStart(fd, junk, port, ftpPasvCallback, ftpState);
1288}
1289
1290static void
1291ftpPasvCallback(int fd, int status, void *data)
1292{
1293 FtpStateData *ftpState = data;
a3d5953d 1294 debug(9, 3) ("ftpPasvCallback\n");
3fdadc70 1295 if (status == COMM_ERROR) {
474cac1b 1296 storeAbort(ftpState->entry, ERR_CONNECT_FAIL, xstrerror(), 0);
3fdadc70 1297 comm_close(fd);
1298 return;
1299 }
1300 ftpRestOrList(ftpState);
1301}
1302
1303static void
1304ftpSendPort(FtpStateData * ftpState)
1305{
a3d5953d 1306 debug(9, 3) ("This is ftpSendPort\n");
3fdadc70 1307 EBIT_RESET(ftpState->flags, FTP_PASV_SUPPORTED);
1308}
1309
1310static void
1311ftpReadPort(FtpStateData * ftpState)
1312{
a3d5953d 1313 debug(9, 3) ("This is ftpReadPort\n");
3fdadc70 1314}
1315
1316static void
1317ftpRestOrList(FtpStateData * ftpState)
1318{
a3d5953d 1319 debug(9, 3) ("This is ftpRestOrList\n");
3fdadc70 1320 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1321 sprintf(cbuf, "LIST\r\n");
1322 ftpWriteCommand(cbuf, ftpState);
1323 ftpState->state = SENT_LIST;
1324 } else if (ftpState->restart_offset > 0) {
1325 sprintf(cbuf, "REST\r\n");
1326 ftpWriteCommand(cbuf, ftpState);
1327 ftpState->state = SENT_REST;
1328 } else {
1329 assert(ftpState->filepath != NULL);
1330 sprintf(cbuf, "RETR %s\r\n", ftpState->filepath);
1331 ftpWriteCommand(cbuf, ftpState);
1332 ftpState->state = SENT_RETR;
1333 }
1334}
1335
1336static void
1337ftpReadRest(FtpStateData * ftpState)
1338{
1339 int code = ftpState->ctrl.replycode;
a3d5953d 1340 debug(9, 3) ("This is ftpReadRest\n");
3fdadc70 1341 assert(ftpState->restart_offset > 0);
1342 if (code == 350) {
1343 assert(ftpState->filepath != NULL);
1344 sprintf(cbuf, "RETR %s\r\n", ftpState->filepath);
1345 ftpWriteCommand(cbuf, ftpState);
1346 ftpState->state = SENT_RETR;
1347 } else if (code > 0) {
a3d5953d 1348 debug(9, 3) ("ftpReadRest: REST not supported\n");
3fdadc70 1349 EBIT_RESET(ftpState->flags, FTP_REST_SUPPORTED);
1350 } else {
1351 ftpFail(ftpState);
1352 }
1353}
1354
1355static void
1356ftpReadList(FtpStateData * ftpState)
1357{
1358 int code = ftpState->ctrl.replycode;
a3d5953d 1359 debug(9, 3) ("This is ftpReadList\n");
3fdadc70 1360 if (code == 150 || code == 125) {
1361 ftpAppendSuccessHeader(ftpState);
1362 commSetSelect(ftpState->data.fd,
30a4f2a8 1363 COMM_SELECT_READ,
3fdadc70 1364 ftpReadData,
1365 ftpState,
1366 0);
1367 ftpState->state = READING_DATA;
1368 return;
1369 } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
1370 EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
1371 sprintf(cbuf, "NLST\r\n");
1372 ftpWriteCommand(cbuf, ftpState);
1373 ftpState->state = SENT_NLST;
1374 } else {
1375 ftpFail(ftpState);
1376 return;
1377 }
1378}
1379
1380static void
1381ftpReadRetr(FtpStateData * ftpState)
1382{
1383 int code = ftpState->ctrl.replycode;
a3d5953d 1384 debug(9, 3) ("This is ftpReadRetr\n");
3fdadc70 1385 if (code >= 100 && code < 200) {
a3d5953d 1386 debug(9, 3) ("reading data channel\n");
3fdadc70 1387 ftpAppendSuccessHeader(ftpState);
1388 commSetSelect(ftpState->data.fd,
1389 COMM_SELECT_READ,
1390 ftpReadData,
1391 ftpState,
1392 0);
1393 ftpState->state = READING_DATA;
1394 } else {
1395 ftpFail(ftpState);
1396 }
1397}
1398
1399static void
1400ftpReadTransferDone(FtpStateData * ftpState)
1401{
1402 int code = ftpState->ctrl.replycode;
a3d5953d 1403 debug(9, 3) ("This is ftpReadTransferDone\n");
3fdadc70 1404 if (code != 226) {
a3d5953d 1405 debug(9, 1) ("Got code %d after reading data, releasing entry\n");
3fdadc70 1406 storeReleaseRequest(ftpState->entry);
1407 }
02be0294 1408 ftpDataTransferDone(ftpState);
3fdadc70 1409}
1410
1411static void
1412ftpDataTransferDone(FtpStateData * ftpState)
1413{
a3d5953d 1414 debug(9, 3) ("This is ftpDataTransferDone\n");
033fa114 1415 if (ftpState->data.fd > -1) {
a3d5953d 1416 comm_close(ftpState->data.fd);
1417 ftpState->data.fd = -1;
033fa114 1418 }
1419 assert(ftpState->ctrl.fd > -1);
3fdadc70 1420 sprintf(cbuf, "QUIT\r\n");
1421 ftpWriteCommand(cbuf, ftpState);
1422 ftpState->state = SENT_QUIT;
1423}
1424
1425static void
1426ftpReadQuit(FtpStateData * ftpState)
1427{
1428 comm_close(ftpState->ctrl.fd);
1429}
1430
1431static void
1432ftpFail(FtpStateData * ftpState)
1433{
a3d5953d 1434 debug(9, 3) ("ftpFail\n");
3fdadc70 1435 comm_close(ftpState->ctrl.fd);
1436}
1437
1438static void
1439ftpAppendSuccessHeader(FtpStateData * ftpState)
1440{
1441 char *mime_type = NULL;
1442 char *mime_enc = NULL;
1443 char *urlpath = ftpState->request->urlpath;
1444 char *filename = NULL;
1445 char *t = NULL;
1446 StoreEntry *e = ftpState->entry;
9e975e4e 1447 http_reply *reply = e->mem_obj->reply;
3fdadc70 1448 if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT))
1449 return;
9e975e4e 1450 EBIT_SET(ftpState->flags, FTP_HTTP_HEADER_SENT);
3fdadc70 1451 assert(e->mem_obj->e_current_len == 0);
1452 filename = (t = strrchr(urlpath, '/')) ? t + 1 : urlpath;
1453 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1454 mime_type = "text/html";
1455 } else {
1456 mime_type = mimeGetContentType(filename);
1457 mime_enc = mimeGetContentEncoding(filename);
1458 }
1459 storeAppendPrintf(e, "HTTP/1.0 200 Gatewaying\r\n");
e6b02cfc 1460 reply->code = 200;
1461 reply->version = 1.0;
3fdadc70 1462 storeAppendPrintf(e, "Date: %s\r\n", mkrfc1123(squid_curtime));
e6b02cfc 1463 reply->date = squid_curtime;
3fdadc70 1464 storeAppendPrintf(e, "MIME-Version: 1.0\r\n");
1465 storeAppendPrintf(e, "Server: Squid %s\r\n", version_string);
e6b02cfc 1466 if (ftpState->size > 0) {
3fdadc70 1467 storeAppendPrintf(e, "Content-Length: %d\r\n", ftpState->size);
a3d5953d 1468 reply->content_length = ftpState->size;
e6b02cfc 1469 }
1470 if (mime_type) {
3fdadc70 1471 storeAppendPrintf(e, "Content-Type: %s\r\n", mime_type);
a3d5953d 1472 xstrncpy(reply->content_type, mime_type, HTTP_REPLY_FIELD_SZ);
e6b02cfc 1473 }
3fdadc70 1474 if (mime_enc)
1475 storeAppendPrintf(e, "Content-Encoding: %s\r\n", mime_enc);
e6b02cfc 1476 if (ftpState->mdtm > 0) {
3fdadc70 1477 storeAppendPrintf(e, "Last-Modified: %s\r\n", mkrfc1123(ftpState->mdtm));
e6b02cfc 1478 reply->last_modified = ftpState->mdtm;
1479 }
3fdadc70 1480 storeAppendPrintf(e, "\r\n");
e6b02cfc 1481 storeTimestampsSet(e);
e6b02cfc 1482 storeSetPublicKey(e);
77a30ebb 1483}
bfcaf585 1484
1485static void
1486ftpAbort(void *data)
1487{
1488 FtpStateData *ftpState = data;
a3d5953d 1489 debug(9, 1) ("ftpAbort: %s\n", ftpState->entry->url);
15888cf7 1490 if (ftpState->data.fd >= 0) {
270b86af 1491 comm_close(ftpState->data.fd);
15888cf7 1492 ftpState->data.fd = -1;
1493 }
7b2cdd7b 1494 comm_close(ftpState->ctrl.fd);
bfcaf585 1495}