]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
update
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
be335c22 1
30a4f2a8 2/*
067bea91 3 * $Id: ftp.cc,v 1.187 1998/01/12 04:30:38 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,
48 FTP_TRY_SLASH_HACK
3fdadc70 49};
50
51static const char *const crlf = "\r\n";
52static char cbuf[1024];
53
54typedef enum {
55 BEGIN,
56 SENT_USER,
57 SENT_PASS,
58 SENT_TYPE,
59 SENT_MDTM,
60 SENT_SIZE,
61 SENT_PORT,
62 SENT_PASV,
63 SENT_CWD,
64 SENT_LIST,
65 SENT_NLST,
66 SENT_REST,
67 SENT_RETR,
68 SENT_QUIT,
69 READING_DATA
70} ftp_state_t;
090089c4 71
72typedef struct _Ftpdata {
73 StoreEntry *entry;
983061ed 74 request_t *request;
77a30ebb 75 char user[MAX_URL];
76 char password[MAX_URL];
f0afe435 77 char *reply_hdr;
f0afe435 78 int reply_hdr_state;
3fdadc70 79 char *title_url;
80 int conn_att;
81 int login_att;
82 ftp_state_t state;
3fdadc70 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;
969c39b9 93 char *old_request;
94 char *old_reply;
9e242e02 95 char typecode;
3fdadc70 96 struct {
97 int fd;
98 char *buf;
99 size_t size;
100 off_t offset;
101 FREE *freefunc;
102 wordlist *message;
b9916917 103 char *last_command;
104 char *last_reply;
3fdadc70 105 int replycode;
106 } ctrl;
107 struct {
108 int fd;
109 char *buf;
110 size_t size;
111 off_t offset;
112 FREE *freefunc;
9b312a19 113 char *host;
114 u_short port;
3fdadc70 115 } data;
e5f6c5c2 116} FtpStateData;
090089c4 117
3fdadc70 118typedef struct {
119 char type;
120 int size;
121 char *date;
122 char *name;
123 char *showname;
124 char *link;
125} ftpListParts;
126
ea3a2a69 127typedef void (FTPSM) (FtpStateData *);
0a0bf5db 128
983061ed 129/* Local functions */
9e4ad609 130static CNCB ftpConnectDone;
3fdadc70 131static CNCB ftpPasvCallback;
3fdadc70 132static PF ftpReadData;
9e4ad609 133static PF ftpStateFree;
5c5783a2 134static PF ftpTimeout;
3fdadc70 135static PF ftpReadControlReply;
136static CWCB ftpWriteCommandCallback;
f5b8bbc4 137static char *ftpGetBasicAuth(const char *);
138static void ftpLoginParser(const char *, FtpStateData *);
f5b8bbc4 139static wordlist *ftpParseControlReply(char *buf, size_t len, int *code);
f5b8bbc4 140static void ftpRestOrList(FtpStateData * ftpState);
f5b8bbc4 141static void ftpDataTransferDone(FtpStateData * ftpState);
142static void ftpAppendSuccessHeader(FtpStateData * ftpState);
143static char *ftpAuthRequired(const request_t *, const char *);
bfcaf585 144static STABH ftpAbort;
969c39b9 145static void ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState);
983061ed 146
969c39b9 147/* State machine functions
148 * send == state transition
149 * read == wait for response, and select next state transition
150 */
3fdadc70 151static FTPSM ftpReadWelcome;
969c39b9 152static FTPSM ftpSendUser;
3fdadc70 153static FTPSM ftpReadUser;
969c39b9 154static FTPSM ftpSendPass;
3fdadc70 155static FTPSM ftpReadPass;
969c39b9 156static FTPSM ftpSendType;
3fdadc70 157static FTPSM ftpReadType;
969c39b9 158static FTPSM ftpSendMdtm;
3fdadc70 159static FTPSM ftpReadMdtm;
969c39b9 160static FTPSM ftpSendSize;
3fdadc70 161static FTPSM ftpReadSize;
969c39b9 162static FTPSM ftpSendPort;
3fdadc70 163static FTPSM ftpReadPort;
969c39b9 164static FTPSM ftpSendPasv;
3fdadc70 165static FTPSM ftpReadPasv;
969c39b9 166static FTPSM ftpTraverseDirectory; /* Selects CWD or RETR */
167static FTPSM ftpSendCwd;
3fdadc70 168static FTPSM ftpReadCwd;
969c39b9 169static FTPSM ftpSendList;
170static FTPSM ftpSendNlst;
3fdadc70 171static FTPSM ftpReadList;
969c39b9 172static FTPSM ftpSendRest;
3fdadc70 173static FTPSM ftpReadRest;
969c39b9 174static FTPSM ftpSendRetr;
3fdadc70 175static FTPSM ftpReadRetr;
176static FTPSM ftpReadTransferDone;
969c39b9 177static FTPSM ftpSendQuit;
178static FTPSM ftpReadQuit;
179static FTPSM ftpFail;
3fdadc70 180
181FTPSM *FTP_SM_FUNCS[] =
182{
183 ftpReadWelcome,
184 ftpReadUser,
185 ftpReadPass,
186 ftpReadType,
187 ftpReadMdtm,
188 ftpReadSize,
189 ftpReadPort,
190 ftpReadPasv,
191 ftpReadCwd,
192 ftpReadList, /* SENT_LIST */
193 ftpReadList, /* SENT_NLST */
194 ftpReadRest,
195 ftpReadRetr,
196 ftpReadQuit,
197 ftpReadTransferDone
198};
e381a13d 199
582b6456 200static void
79d39a72 201ftpStateFree(int fdnotused, void *data)
ba718c8f 202{
582b6456 203 FtpStateData *ftpState = data;
51fa90db 204 if (ftpState == NULL)
582b6456 205 return;
9fb13bb6 206 debug(9, 3) ("ftpStateFree: %s\n", storeUrl(ftpState->entry));
bfcaf585 207 storeUnregisterAbort(ftpState->entry);
f88211e8 208 storeUnlockObject(ftpState->entry);
51fa90db 209 if (ftpState->reply_hdr) {
3f6c0fb2 210 memFree(MEM_8K_BUF, ftpState->reply_hdr);
51fa90db 211 ftpState->reply_hdr = NULL;
f0afe435 212 }
30a4f2a8 213 requestUnlink(ftpState->request);
3fdadc70 214 if (ftpState->ctrl.buf)
215 ftpState->ctrl.freefunc(ftpState->ctrl.buf);
216 if (ftpState->data.buf)
217 ftpState->data.freefunc(ftpState->data.buf);
218 if (ftpState->pathcomps)
219 wordlistDestroy(&ftpState->pathcomps);
220 if (ftpState->ctrl.message)
221 wordlistDestroy(&ftpState->ctrl.message);
222 if (ftpState->cwd_message)
223 wordlistDestroy(&ftpState->cwd_message);
b9916917 224 safe_free(ftpState->ctrl.last_reply);
225 safe_free(ftpState->ctrl.last_command);
969c39b9 226 safe_free(ftpState->old_request);
227 safe_free(ftpState->old_reply);
b5639035 228 safe_free(ftpState->title_url);
229 safe_free(ftpState->filepath);
9b312a19 230 safe_free(ftpState->data.host);
a0eff6be 231 if (ftpState->data.fd > -1) {
15888cf7 232 comm_close(ftpState->data.fd);
a0eff6be 233 ftpState->data.fd = -1;
234 }
7dd44885 235 cbdataFree(ftpState);
ba718c8f 236}
237
b8d8561b 238static void
9e4ad609 239ftpLoginParser(const char *login, FtpStateData * ftpState)
090089c4 240{
983061ed 241 char *s = NULL;
582b6456 242 xstrncpy(ftpState->user, login, MAX_URL);
243 if ((s = strchr(ftpState->user, ':'))) {
983061ed 244 *s = 0;
582b6456 245 xstrncpy(ftpState->password, s + 1, MAX_URL);
983061ed 246 } else {
582b6456 247 xstrncpy(ftpState->password, null_string, MAX_URL);
983061ed 248 }
582b6456 249 if (ftpState->user[0] || ftpState->password[0])
429fdbec 250 return;
582b6456 251 xstrncpy(ftpState->user, "anonymous", MAX_URL);
e34e0322 252 xstrncpy(ftpState->password, Config.Ftp.anon_user, MAX_URL);
090089c4 253}
254
24382924 255static void
5c5783a2 256ftpTimeout(int fd, void *data)
090089c4 257{
582b6456 258 FtpStateData *ftpState = data;
259 StoreEntry *entry = ftpState->entry;
9b312a19 260 ErrorState *err;
9fb13bb6 261 debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd, storeUrl(entry));
185b9571 262 if (entry->store_status == STORE_PENDING) {
263 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 264 err = errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT);
185b9571 265 err->request = requestLink(ftpState->request);
266 errorAppendEntry(entry, err);
b50179a6 267 } else {
268 storeAbort(entry, 0);
185b9571 269 }
9b312a19 270 }
15888cf7 271 if (ftpState->data.fd >= 0) {
3fdadc70 272 comm_close(ftpState->data.fd);
15888cf7 273 ftpState->data.fd = -1;
274 }
3fdadc70 275 comm_close(ftpState->ctrl.fd);
3c30232f 276 /* don't modify ftpState here, it has been freed */
090089c4 277}
278
3fdadc70 279static void
280ftpListingStart(FtpStateData * ftpState)
281{
282 StoreEntry *e = ftpState->entry;
283 wordlist *w;
438fc1e3 284 storeBuffer(e);
3fdadc70 285 storeAppendPrintf(e, "<!-- HTML listing generated by Squid %s -->\n",
286 version_string);
287 storeAppendPrintf(e, "<!-- %s -->\n", mkrfc1123(squid_curtime));
288 storeAppendPrintf(e, "<HTML><HEAD><TITLE>\n");
289 storeAppendPrintf(e, "FTP Directory: %s\n",
290 ftpState->title_url);
291 storeAppendPrintf(e, "</TITLE>\n");
292 if (EBIT_TEST(ftpState->flags, FTP_USE_BASE))
293 storeAppendPrintf(e, "<BASE HREF=\"%s\">\n",
13e80e5b 294 ftpState->title_url);
3fdadc70 295 storeAppendPrintf(e, "</HEAD><BODY>\n");
296 if (ftpState->cwd_message) {
297 storeAppendPrintf(e, "<PRE>\n");
298 for (w = ftpState->cwd_message; w; w = w->next)
299 storeAppendPrintf(e, "%s\n", w->key);
300 storeAppendPrintf(e, "</PRE>\n");
301 storeAppendPrintf(e, "<HR>\n");
302 wordlistDestroy(&ftpState->cwd_message);
303 }
304 storeAppendPrintf(e, "<H2>\n");
305 storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url);
306 storeAppendPrintf(e, "</H2>\n");
307 storeAppendPrintf(e, "<PRE>\n");
438fc1e3 308 storeBufferFlush(e);
3fdadc70 309 EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT);
310}
311
312static void
313ftpListingFinish(FtpStateData * ftpState)
314{
315 StoreEntry *e = ftpState->entry;
438fc1e3 316 storeBuffer(e);
3fdadc70 317 storeAppendPrintf(e, "</PRE>\n");
318 storeAppendPrintf(e, "<HR>\n");
319 storeAppendPrintf(e, "<ADDRESS>\n");
320 storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n",
321 mkrfc1123(squid_curtime),
322 appname,
323 version_string,
324 getMyHostname());
325 storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
438fc1e3 326 storeBufferFlush(e);
3fdadc70 327}
328
329static const char *Month[] =
330{
331 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
332 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
333};
334
335static int
336is_month(const char *buf)
337{
338 int i;
339 for (i = 0; i < 12; i++)
340 if (!strcasecmp(buf, Month[i]))
341 return 1;
342 return 0;
343}
344
345
346static void
347ftpListPartsFree(ftpListParts ** parts)
348{
349 safe_free((*parts)->date);
350 safe_free((*parts)->name);
351 safe_free((*parts)->showname);
352 safe_free((*parts)->link);
353 safe_free(*parts);
354}
355
356#define MAX_TOKENS 64
357
75e5a32e 358#define SCAN_FTP1 "%[0123456789]"
359#define SCAN_FTP2 "%[0123456789:]"
360#define SCAN_FTP3 "%[0123456789]-%[0123456789]-%[0123456789]"
361#define SCAN_FTP4 "%[0123456789]:%[0123456789]%[AaPp]%[Mm]"
362
3fdadc70 363static ftpListParts *
364ftpListParseParts(const char *buf, int flags)
365{
366 ftpListParts *p = NULL;
367 char *t = NULL;
368 const char *ct = NULL;
369 char *tokens[MAX_TOKENS];
370 int i;
371 int n_tokens;
372 static char sbuf[128];
373 char *xbuf = NULL;
374 if (buf == NULL)
375 return NULL;
376 if (*buf == '\0')
377 return NULL;
378 p = xcalloc(1, sizeof(ftpListParts));
379 n_tokens = 0;
380 for (i = 0; i < MAX_TOKENS; i++)
381 tokens[i] = (char *) NULL;
382 xbuf = xstrdup(buf);
383 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
384 tokens[n_tokens++] = xstrdup(t);
385 xfree(xbuf);
386 /* locate the Month field */
387 for (i = 3; i < n_tokens - 3; i++) {
388 if (!is_month(tokens[i])) /* Month */
389 continue;
75e5a32e 390 if (!sscanf(tokens[i - 1], SCAN_FTP1, sbuf)) /* Size */
3fdadc70 391 continue;
75e5a32e 392 if (!sscanf(tokens[i + 1], SCAN_FTP1, sbuf)) /* Day */
3fdadc70 393 continue;
75e5a32e 394 if (!sscanf(tokens[i + 2], SCAN_FTP2, sbuf)) /* Yr | hh:mm */
3fdadc70 395 continue;
396 p->type = *tokens[0];
397 p->size = atoi(tokens[i - 1]);
042461c3 398 snprintf(sbuf, 128, "%s %2s %5s",
3fdadc70 399 tokens[i], tokens[i + 1], tokens[i + 2]);
400 if (!strstr(buf, sbuf))
042461c3 401 snprintf(sbuf, 128, "%s %2s %-5s",
3fdadc70 402 tokens[i], tokens[i + 1], tokens[i + 2]);
403 if ((t = strstr(buf, sbuf))) {
404 p->date = xstrdup(sbuf);
b9916917 405 if (EBIT_TEST(flags, FTP_SKIP_WHITESPACE)) {
3fdadc70 406 t += strlen(sbuf);
407 while (strchr(w_space, *t))
408 t++;
409 } else {
410 /* XXX assumes a single space between date and filename
411 * suggested by: Nathan.Bailey@cc.monash.edu.au and
412 * Mike Battersby <mike@starbug.bofh.asn.au> */
413 t += strlen(sbuf) + 1;
414 }
415 p->name = xstrdup(t);
416 if ((t = strstr(p->name, " -> "))) {
417 *t = '\0';
418 p->link = xstrdup(t + 4);
419 }
420 }
421 break;
422 }
423 /* try it as a DOS listing */
424 if (n_tokens > 3 && p->name == NULL &&
75e5a32e 425 sscanf(tokens[0], SCAN_FTP3, sbuf, sbuf, sbuf) == 3 &&
3fdadc70 426 /* 04-05-70 */
75e5a32e 427 sscanf(tokens[1], SCAN_FTP4, sbuf, sbuf, sbuf, sbuf) == 4) {
3fdadc70 428 /* 09:33PM */
429 if (!strcasecmp(tokens[2], "<dir>")) {
430 p->type = 'd';
431 } else {
432 p->type = '-';
433 p->size = atoi(tokens[2]);
434 }
042461c3 435 snprintf(sbuf, 128, "%s %s", tokens[0], tokens[1]);
3fdadc70 436 p->date = xstrdup(sbuf);
437 p->name = xstrdup(tokens[3]);
438 }
439 /* Try EPLF format; carson@lehman.com */
440 if (p->name == NULL && buf[0] == '+') {
441 ct = buf + 1;
442 p->type = 0;
443 while (ct && *ct) {
444 switch (*ct) {
445 case '\t':
446 sscanf(ct + 1, "%[^,]", sbuf);
447 p->name = xstrdup(sbuf);
448 break;
449 case 's':
450 sscanf(ct + 1, "%d", &(p->size));
451 break;
452 case 'm':
453 sscanf(ct + 1, "%d", &i);
454 p->date = xstrdup(ctime((time_t *) & i));
455 *(strstr(p->date, "\n")) = '\0';
456 break;
457 case '/':
458 p->type = 'd';
459 break;
460 case 'r':
461 p->type = '-';
462 break;
463 case 'i':
464 break;
465 default:
466 break;
467 }
468 ct = strstr(ct, ",");
469 if (ct) {
470 ct++;
471 }
472 }
473 if (p->type == 0) {
474 p->type = '-';
475 }
476 }
477 for (i = 0; i < n_tokens; i++)
478 xfree(tokens[i]);
b5639035 479 if (p->name == NULL)
480 ftpListPartsFree(&p);
3fdadc70 481 return p;
482}
483
484static const char *
485dots_fill(size_t len)
486{
487 static char buf[256];
488 int i = 0;
489 if (len > Config.Ftp.list_width) {
490 memset(buf, ' ', 256);
491 buf[0] = '\n';
492 buf[Config.Ftp.list_width + 4] = '\0';
493 return buf;
494 }
495 for (i = (int) len; i < Config.Ftp.list_width; i++)
496 buf[i - len] = (i % 2) ? '.' : ' ';
497 buf[i - len] = '\0';
498 return buf;
499}
500
501static char *
502ftpHtmlifyListEntry(char *line, int flags)
503{
13e80e5b 504 LOCAL_ARRAY(char, link, 2048 + 40);
9fc0b4b8 505 LOCAL_ARRAY(char, link2, 2048 + 40);
3fdadc70 506 LOCAL_ARRAY(char, icon, 2048);
507 LOCAL_ARRAY(char, html, 8192);
3fdadc70 508 size_t width = Config.Ftp.list_width;
509 ftpListParts *parts;
3fdadc70 510 if (strlen(line) > 1024) {
042461c3 511 snprintf(html, 8192, "%s\n", line);
3fdadc70 512 return html;
513 }
514 if ((parts = ftpListParseParts(line, flags)) == NULL) {
042461c3 515 snprintf(html, 8192, "%s\n", line);
3fdadc70 516 return html;
517 }
13e80e5b 518 /* check .. as special case */
519 if (!strcmp(parts->name, "..")) {
520 snprintf(icon, 2048, "<IMG BORDER=0 SRC=\"%s%s\" ALT=\"%-6s\">",
521 "http://internal.squid/icons/",
522 ICON_DIRUP,
523 "[DIR]");
524 if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) {
525 /* Normal directory */
526 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
527 "../",
528 "Parent Directory");
529 } else if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && EBIT_TEST(flags, FTP_ROOT_DIR)) {
530 /* "Top level" directory */
531 snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)",
532 "%2e%2e/",
533 "Parent Directory",
534 "%2f/",
535 "Root Directory");
536 } else if (EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) {
537 /* Normal directory where last component is / or .. */
538 snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)",
539 "%2e%2e/",
540 "Parent Directory",
541 "../",
542 "Up");
543 } else { /* NO_DOTDOT && ROOT_DIR */
544 /* "UNIX Root" directory */
545 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
546 "../",
547 "Home Directory");
548 }
549 snprintf(html, 8192, "%s %s\n", icon, link);
550 ftpListPartsFree(&parts);
551 return html;
552 }
3fdadc70 553 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
554 *html = '\0';
b5639035 555 ftpListPartsFree(&parts);
3fdadc70 556 return html;
557 }
558 parts->size += 1023;
559 parts->size >>= 10;
560 parts->showname = xstrdup(parts->name);
561 if (!Config.Ftp.list_wrap) {
562 if (strlen(parts->showname) > width - 1) {
563 *(parts->showname + width - 1) = '>';
564 *(parts->showname + width - 0) = '\0';
565 }
566 }
3fdadc70 567 switch (parts->type) {
568 case 'd':
042461c3 569 snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
365cb147 570 "http://internal.squid/icons/",
4f117cb4 571 ICON_MENU,
3fdadc70 572 "[DIR]");
56878878 573 snprintf(link, 2048, "<A HREF=\"%s/\">%s</A>%s",
9fc0b4b8 574 rfc1738_escape(parts->name),
3fdadc70 575 parts->showname,
576 dots_fill(strlen(parts->showname)));
042461c3 577 snprintf(html, 8192, "%s %s [%s]\n",
3fdadc70 578 icon,
579 link,
580 parts->date);
581 break;
582 case 'l':
56878878 583 snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
365cb147 584 "http://internal.squid/icons/",
4f117cb4 585 ICON_LINK,
3fdadc70 586 "[LINK]");
042461c3 587 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s",
9fc0b4b8 588 rfc1738_escape(parts->name),
3fdadc70 589 parts->showname,
590 dots_fill(strlen(parts->showname)));
9fc0b4b8 591 snprintf(link2, 2048, "<A HREF=\"%s\">%s</A>",
592 rfc1738_escape(parts->link),
593 parts->link);
594 snprintf(html, 8192, "%s %s [%s] -> %s\n",
3fdadc70 595 icon,
596 link,
9fc0b4b8 597 parts->date,
598 link2);
3fdadc70 599 break;
600 case '-':
601 default:
56878878 602 snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
365cb147 603 "http://internal.squid/icons/",
3fdadc70 604 mimeGetIcon(parts->name),
3fdadc70 605 "[FILE]");
042461c3 606 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s",
9fc0b4b8 607 rfc1738_escape(parts->name),
3fdadc70 608 parts->showname,
609 dots_fill(strlen(parts->showname)));
042461c3 610 snprintf(html, 8192, "%s %s [%s] %6dk\n",
3fdadc70 611 icon,
612 link,
613 parts->date,
614 parts->size);
615 break;
616 }
617 ftpListPartsFree(&parts);
3fdadc70 618 return html;
619}
620
621static void
622ftpParseListing(FtpStateData * ftpState, int len)
623{
624 char *buf = ftpState->data.buf;
b5639035 625 char *end;
626 char *line;
3fdadc70 627 char *s;
628 char *t;
629 size_t linelen;
b5639035 630 size_t usable;
3fdadc70 631 StoreEntry *e = ftpState->entry;
b5639035 632 len += ftpState->data.offset;
633 end = buf + len - 1;
3fdadc70 634 while (*end != '\r' && *end != '\n' && end > buf)
635 end--;
b5639035 636 usable = end - buf;
637 if (usable == 0) {
9fb13bb6 638 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e));
3fdadc70 639 return;
640 }
3f6c0fb2 641 line = memAllocate(MEM_4K_BUF, 1);
3fdadc70 642 end++;
1931735b 643 /* XXX there is an ABR bug here. We need to make sure buf is
644 * NULL terminated */
438fc1e3 645 storeBuffer(e);
3fdadc70 646 for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
647 linelen = strcspn(s, crlf) + 1;
648 if (linelen > 4096)
649 linelen = 4096;
650 xstrncpy(line, s, linelen);
a3d5953d 651 debug(9, 7) ("%s\n", line);
3fdadc70 652 if (!strncmp(line, "total", 5))
653 continue;
654 t = ftpHtmlifyListEntry(line, ftpState->flags);
655 assert(t != NULL);
656 storeAppend(e, t, strlen(t));
657 }
438fc1e3 658 storeBufferFlush(e);
b5639035 659 assert(usable <= len);
660 if (usable < len) {
661 /* must copy partial line to beginning of buf */
e29f7586 662 linelen = len - usable;
b5639035 663 if (linelen > 4096)
664 linelen = 4096;
665 xstrncpy(line, end, linelen);
666 xstrncpy(ftpState->data.buf, line, ftpState->data.size);
667 ftpState->data.offset = strlen(ftpState->data.buf);
668 }
3f6c0fb2 669 memFree(MEM_4K_BUF, line);
3fdadc70 670}
090089c4 671
79a15e0a 672static void
673ftpReadComplete(FtpStateData * ftpState)
674{
4cca8b93 675 debug(9, 3) ("ftpReadComplete\n");
79a15e0a 676 /* Connection closed; retrieval done. */
677 if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
678 ftpListingFinish(ftpState);
679 storeTimestampsSet(ftpState->entry);
680 storeComplete(ftpState->entry);
681 /* expect the "transfer complete" message on the control socket */
682 commSetSelect(ftpState->ctrl.fd,
683 COMM_SELECT_READ,
684 ftpReadControlReply,
685 ftpState,
686 Config.Timeout.read);
687}
688
582b6456 689static void
3fdadc70 690ftpReadData(int fd, void *data)
090089c4 691{
582b6456 692 FtpStateData *ftpState = data;
090089c4 693 int len;
a57512fa 694 int j;
30a4f2a8 695 int bin;
bfcaf585 696 StoreEntry *entry = ftpState->entry;
79a15e0a 697 MemObject *mem = entry->mem_obj;
3fdadc70 698 assert(fd == ftpState->data.fd);
3fdadc70 699 if (protoAbortFetch(entry)) {
9b312a19 700 storeAbort(entry, 0);
a3d5953d 701 ftpDataTransferDone(ftpState);
702 return;
30a4f2a8 703 }
1a6e21ac 704 errno = 0;
3fdadc70 705 memset(ftpState->data.buf + ftpState->data.offset, '\0',
706 ftpState->data.size - ftpState->data.offset);
707 len = read(fd,
708 ftpState->data.buf + ftpState->data.offset,
709 ftpState->data.size - ftpState->data.offset);
4f92c80c 710 fd_bytes(fd, len, FD_READ);
a3d5953d 711 debug(9, 5) ("ftpReadData: FD %d, Read %d bytes\n", fd, len);
30a4f2a8 712 if (len > 0) {
4a63c85f 713 IOStats.Ftp.reads++;
a57512fa 714 for (j = len - 1, bin = 0; j; bin++)
715 j >>= 1;
30a4f2a8 716 IOStats.Ftp.read_hist[bin]++;
717 }
ba718c8f 718 if (len < 0) {
a3d5953d 719 debug(50, 1) ("ftpReadData: read error: %s\n", xstrerror());
b224ea98 720 if (ignoreErrno(errno)) {
a57512fa 721 commSetSelect(fd,
722 COMM_SELECT_READ,
723 ftpReadData,
724 data,
725 Config.Timeout.read);
6fe6313d 726 } else {
a57512fa 727 assert(mem->inmem_hi > 0);
728 storeAbort(entry, 0);
3fdadc70 729 ftpDataTransferDone(ftpState);
6fe6313d 730 }
090089c4 731 } else if (len == 0) {
79a15e0a 732 ftpReadComplete(ftpState);
090089c4 733 } else {
b5639035 734 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
a57512fa 735 if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
736 ftpListingStart(ftpState);
3fdadc70 737 ftpParseListing(ftpState, len);
b5639035 738 } else {
739 assert(ftpState->data.offset == 0);
740 storeAppend(entry, ftpState->data.buf, len);
741 }
3ccfdd9e 742 if (ftpState->size && mem->inmem_hi >= ftpState->size + mem->reply->hdr_sz)
79a15e0a 743 ftpReadComplete(ftpState);
744 else
745 commSetSelect(fd,
746 COMM_SELECT_READ,
747 ftpReadData,
748 data,
749 Config.Timeout.read);
090089c4 750 }
090089c4 751}
752
0f9306d9 753static char *
0ee4272b 754ftpGetBasicAuth(const char *req_hdr)
0f9306d9 755{
756 char *auth_hdr;
757 char *t;
758 if (req_hdr == NULL)
759 return NULL;
760 if ((auth_hdr = mime_get_header(req_hdr, "Authorization")) == NULL)
761 return NULL;
762 if ((t = strtok(auth_hdr, " \t")) == NULL)
763 return NULL;
764 if (strcasecmp(t, "Basic") != 0)
765 return NULL;
766 if ((t = strtok(NULL, " \t")) == NULL)
767 return NULL;
768 return base64_decode(t);
769}
770
429fdbec 771/*
772 * ftpCheckAuth
773 *
774 * Return 1 if we have everything needed to complete this request.
775 * Return 0 if something is missing.
776 */
777static int
778ftpCheckAuth(FtpStateData * ftpState, char *req_hdr)
779{
780 char *orig_user;
781 char *auth;
9e4ad609 782 ftpLoginParser(ftpState->request->login, ftpState);
429fdbec 783 if (ftpState->user[0] && ftpState->password[0])
784 return 1; /* name and passwd both in URL */
785 if (!ftpState->user[0] && !ftpState->password[0])
786 return 1; /* no name or passwd */
787 if (ftpState->password[0])
788 return 1; /* passwd with no name? */
789 /* URL has name, but no passwd */
790 if ((auth = ftpGetBasicAuth(req_hdr)) == NULL)
791 return 0; /* need auth header */
792 orig_user = xstrdup(ftpState->user);
9e4ad609 793 ftpLoginParser(auth, ftpState);
429fdbec 794 if (!strcmp(orig_user, ftpState->user)) {
795 xfree(orig_user);
796 return 1; /* same username */
797 }
798 strcpy(ftpState->user, orig_user);
799 xfree(orig_user);
800 return 0; /* different username */
801}
802
13e80e5b 803static void
804ftpCheckUrlpath(FtpStateData * ftpState)
805{
806 request_t *request = ftpState->request;
807 int l;
9e242e02 808 char *t;
13e80e5b 809 l = strlen(request->urlpath);
810 EBIT_SET(ftpState->flags, FTP_USE_BASE);
811 /* check for null path */
812 if (*request->urlpath == '\0') {
813 xstrncpy(request->urlpath, "/", MAX_URL);
814 EBIT_SET(ftpState->flags, FTP_ISDIR);
815 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
816 } else if (!strcmp(request->urlpath, "/%2f/")) {
817 EBIT_SET(ftpState->flags, FTP_ISDIR);
818 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
819 } else if ((l >= 1) && (*(request->urlpath + l - 1) == '/')) {
820 EBIT_SET(ftpState->flags, FTP_ISDIR);
821 EBIT_CLR(ftpState->flags, FTP_USE_BASE);
822 if (l == 1)
823 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
824 }
9e242e02 825 if ((t = strrchr(request->urlpath, ';')) != NULL) {
826 if (strncasecmp(t + 1, "type=", 5) == 0) {
827 ftpState->typecode = (char) toupper((int) *(t + 6));
70dcc6ec 828 *t = '\0';
9e242e02 829 }
830 }
13e80e5b 831}
832
3fdadc70 833static void
834ftpBuildTitleUrl(FtpStateData * ftpState)
835{
836 request_t *request = ftpState->request;
837 size_t len;
838 char *t;
839 len = 64
840 + strlen(ftpState->user)
841 + strlen(ftpState->password)
842 + strlen(request->host)
843 + strlen(request->urlpath);
844 t = ftpState->title_url = xcalloc(len, 1);
845 strcat(t, "ftp://");
846 if (strcmp(ftpState->user, "anonymous")) {
847 strcat(t, ftpState->user);
848 strcat(t, "@");
849 }
850 strcat(t, request->host);
851 if (request->port != urlDefaultPort(PROTO_FTP))
56878878 852 snprintf(&t[strlen(t)], len - strlen(t), ":%d", request->port);
13e80e5b 853 strcat(t, request->urlpath);
3fdadc70 854}
855
770f051d 856void
75e88d56 857ftpStart(request_t * request, StoreEntry * entry)
0a0bf5db 858{
0a0bf5db 859 LOCAL_ARRAY(char, realm, 8192);
9fb13bb6 860 const char *url = storeUrl(entry);
5c5783a2 861 FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData));
0a0bf5db 862 char *response;
3fdadc70 863 int fd;
9b312a19 864 ErrorState *err;
3f6c0fb2 865 cbdataAdd(ftpState, MEM_NONE);
9fb13bb6 866 debug(9, 3) ("FtpStart: '%s'\n", url);
770f051d 867 storeLockObject(entry);
5c5783a2 868 ftpState->entry = entry;
5c5783a2 869 ftpState->request = requestLink(request);
3fdadc70 870 ftpState->ctrl.fd = -1;
871 ftpState->data.fd = -1;
872 EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED);
873 EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED);
62dec5a7 874 if (!ftpCheckAuth(ftpState, request->headers)) {
429fdbec 875 /* This request is not fully authenticated */
876 if (request->port == 21) {
56878878 877 snprintf(realm, 8192, "ftp %s", ftpState->user);
429fdbec 878 } else {
56878878 879 snprintf(realm, 8192, "ftp %s port %d",
5c5783a2 880 ftpState->user, request->port);
e381a13d 881 }
9b312a19 882 response = ftpAuthRequired(request, realm);
429fdbec 883 storeAppend(entry, response, strlen(response));
884 httpParseReplyHeaders(response, entry->mem_obj->reply);
885 storeComplete(entry);
5c5783a2 886 ftpStateFree(-1, ftpState);
429fdbec 887 return;
e381a13d 888 }
13e80e5b 889 ftpCheckUrlpath(ftpState);
3fdadc70 890 ftpBuildTitleUrl(ftpState);
a3d5953d 891 debug(9, 5) ("FtpStart: host=%s, path=%s, user=%s, passwd=%s\n",
5c5783a2 892 ftpState->request->host, ftpState->request->urlpath,
893 ftpState->user, ftpState->password);
3fdadc70 894 fd = comm_open(SOCK_STREAM,
16b204c4 895 0,
3fdadc70 896 Config.Addrs.tcp_outgoing,
30a4f2a8 897 0,
16b204c4 898 COMM_NONBLOCKING,
30a4f2a8 899 url);
3fdadc70 900 if (fd == COMM_ERROR) {
185b9571 901 debug(9, 4) ("ftpStart: Failed to open a socket.\n");
fe40a877 902 err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 903 err->xerrno = errno;
9b312a19 904 err->request = requestLink(ftpState->request);
905 errorAppendEntry(entry, err);
0a0bf5db 906 return;
090089c4 907 }
3fdadc70 908 ftpState->ctrl.fd = fd;
909 comm_add_close_handler(fd, ftpStateFree, ftpState);
5fbef0c9 910 storeRegisterAbort(entry, ftpAbort, ftpState);
3fdadc70 911 commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
3fdadc70 912 commConnectStart(ftpState->ctrl.fd,
913 request->host,
914 request->port,
e924600d 915 ftpConnectDone,
5c5783a2 916 ftpState);
e5f6c5c2 917}
090089c4 918
e5f6c5c2 919static void
920ftpConnectDone(int fd, int status, void *data)
921{
5c5783a2 922 FtpStateData *ftpState = data;
9e975e4e 923 request_t *request = ftpState->request;
9b312a19 924 ErrorState *err;
9e975e4e 925 debug(9, 3) ("ftpConnectDone, status = %d\n", status);
926 if (status == COMM_ERR_DNS) {
927 debug(9, 4) ("ftpConnectDone: Unknown host: %s\n", request->host);
fe40a877 928 err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE);
9b312a19 929 err->dnsserver_msg = xstrdup(dns_error_message);
930 err->request = requestLink(request);
931 errorAppendEntry(ftpState->entry, err);
ff008d84 932 comm_close(ftpState->ctrl.fd);
9e975e4e 933 } else if (status != COMM_OK) {
fe40a877 934 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 935 err->xerrno = errno;
9b312a19 936 err->host = xstrdup(request->host);
937 err->port = request->port;
938 err->request = requestLink(request);
939 errorAppendEntry(ftpState->entry, err);
ff008d84 940 comm_close(ftpState->ctrl.fd);
9e975e4e 941 } else {
942 ftpState->state = BEGIN;
3f6c0fb2 943 ftpState->ctrl.buf = memAllocate(MEM_4K_BUF, 1);
944 ftpState->ctrl.freefunc = memFree4K;
9e975e4e 945 ftpState->ctrl.size = 4096;
946 ftpState->ctrl.offset = 0;
947 ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF);
948 ftpState->data.size = SQUID_TCP_SO_RCVBUF;
949 ftpState->data.freefunc = xfree;
2acf4be6 950 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read);
e5f6c5c2 951 }
090089c4 952}
953
3fdadc70 954/* ====================================================================== */
955
b8d8561b 956static void
3fdadc70 957ftpWriteCommand(const char *buf, FtpStateData * ftpState)
234967c9 958{
a3d5953d 959 debug(9, 5) ("ftpWriteCommand: %s\n", buf);
b9916917 960 safe_free(ftpState->ctrl.last_command);
961 ftpState->ctrl.last_command = xstrdup(buf);
3fdadc70 962 comm_write(ftpState->ctrl.fd,
963 xstrdup(buf),
964 strlen(buf),
965 ftpWriteCommandCallback,
966 ftpState,
967 xfree);
968 commSetSelect(ftpState->ctrl.fd,
969 COMM_SELECT_READ,
970 ftpReadControlReply,
971 ftpState,
2acf4be6 972 Config.Timeout.read);
3fdadc70 973}
974
975static void
79a15e0a 976ftpWriteCommandCallback(int fd, char *bufnotused, size_t size, int errflag, void *data)
3fdadc70 977{
978 FtpStateData *ftpState = data;
979 StoreEntry *entry = ftpState->entry;
9b312a19 980 ErrorState *err;
a3d5953d 981 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size);
96f1be5d 982 if (errflag == COMM_ERR_CLOSING)
983 return;
3fdadc70 984 if (errflag) {
270b86af 985 debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd, xstrerror());
73a3014d 986 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 987 err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 988 err->xerrno = errno;
9b312a19 989 err->request = requestLink(ftpState->request);
990 errorAppendEntry(entry, err);
991 }
43d9de30 992 if (entry->store_status == STORE_PENDING)
993 storeAbort(entry, 0);
ff008d84 994 comm_close(ftpState->ctrl.fd);
3fdadc70 995 }
996}
997
998static wordlist *
999ftpParseControlReply(char *buf, size_t len, int *codep)
1000{
1001 char *s;
1002 int complete = 0;
1003 wordlist *head;
1004 wordlist *list;
1005 wordlist **tail = &head;
1006 off_t offset;
1007 size_t linelen;
b5639035 1008 int code = -1;
a3d5953d 1009 debug(9, 5) ("ftpParseControlReply\n");
3fdadc70 1010 if (*(buf + len - 1) != '\n')
1011 return NULL;
1012 for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) {
1013 linelen = strcspn(s, crlf) + 1;
1014 if (linelen > 3)
1015 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
1016 if (complete)
1017 code = atoi(s);
1018 offset = 0;
1019 if (linelen > 3)
1020 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
1021 offset = 4;
1022 list = xcalloc(1, sizeof(wordlist));
1023 list->key = xmalloc(linelen - offset);
1024 xstrncpy(list->key, s + offset, linelen - offset);
4939c5da 1025 debug(9, 7) ("%d %s\n", code, list->key);
3fdadc70 1026 *tail = list;
1027 tail = &list->next;
1028 }
1029 if (!complete)
1030 wordlistDestroy(&head);
1031 if (codep)
1032 *codep = code;
1033 return head;
1034}
1035
1036static void
1037ftpReadControlReply(int fd, void *data)
1038{
1039 FtpStateData *ftpState = data;
1040 StoreEntry *entry = ftpState->entry;
1041 char *oldbuf;
1042 wordlist **W;
1043 int len;
9b312a19 1044 ErrorState *err;
a3d5953d 1045 debug(9, 5) ("ftpReadControlReply\n");
3fdadc70 1046 assert(ftpState->ctrl.offset < ftpState->ctrl.size);
1047 len = read(fd,
1048 ftpState->ctrl.buf + ftpState->ctrl.offset,
1049 ftpState->ctrl.size - ftpState->ctrl.offset);
1050 fd_bytes(fd, len, FD_READ);
a3d5953d 1051 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd, len);
3fdadc70 1052 if (len < 0) {
a3d5953d 1053 debug(50, 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
b224ea98 1054 if (ignoreErrno(errno)) {
3fdadc70 1055 commSetSelect(fd,
1056 COMM_SELECT_READ,
1057 ftpReadControlReply,
1058 ftpState,
2acf4be6 1059 Config.Timeout.read);
3fdadc70 1060 } else {
73a3014d 1061 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1062 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 1063 err->xerrno = errno;
9b312a19 1064 err->request = requestLink(ftpState->request);
1065 errorAppendEntry(entry, err);
1066 }
43d9de30 1067 if (entry->store_status == STORE_PENDING)
1068 storeAbort(entry, 0);
ff008d84 1069 comm_close(ftpState->ctrl.fd);
3fdadc70 1070 }
1071 return;
1072 }
1073 if (len == 0) {
cc11e161 1074 debug(9, 1) ("ftpReadControlReply: FD %d Read 0 bytes\n", fd);
1075 if (entry->store_status == STORE_PENDING) {
1076 storeReleaseRequest(entry);
1077 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1078 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 1079 err->xerrno = errno;
cc11e161 1080 err->request = requestLink(ftpState->request);
1081 errorAppendEntry(entry, err);
1082 }
9b312a19 1083 }
ff008d84 1084 comm_close(ftpState->ctrl.fd);
3fdadc70 1085 return;
1086 }
b5639035 1087 len += ftpState->ctrl.offset;
1088 ftpState->ctrl.offset = len;
3fdadc70 1089 assert(len <= ftpState->ctrl.size);
1090 wordlistDestroy(&ftpState->ctrl.message);
1091 ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len,
1092 &ftpState->ctrl.replycode);
1093 if (ftpState->ctrl.message == NULL) {
a3d5953d 1094 debug(9, 5) ("ftpReadControlReply: partial server reply\n");
3fdadc70 1095 if (len == ftpState->ctrl.size) {
1096 oldbuf = ftpState->ctrl.buf;
1097 ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1);
1098 xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size);
1099 ftpState->ctrl.size <<= 1;
1100 ftpState->ctrl.freefunc(oldbuf);
1101 ftpState->ctrl.freefunc = xfree;
1102 }
2acf4be6 1103 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read);
3fdadc70 1104 return;
1105 }
1106 for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next);
b9916917 1107 safe_free(ftpState->ctrl.last_reply);
1108 ftpState->ctrl.last_reply = (*W)->key;
3fdadc70 1109 safe_free(*W);
1110 ftpState->ctrl.offset = 0;
1111 FTP_SM_FUNCS[ftpState->state] (ftpState);
234967c9 1112}
1113
3fdadc70 1114/* ====================================================================== */
1115
1116static void
1117ftpReadWelcome(FtpStateData * ftpState)
1118{
1119 int code = ftpState->ctrl.replycode;
a3d5953d 1120 debug(9, 3) ("ftpReadWelcome\n");
3fdadc70 1121 if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY))
1122 ftpState->login_att++;
1123 if (code == 220) {
1124 if (ftpState->ctrl.message)
1125 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1126 EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
969c39b9 1127 ftpSendUser(ftpState);
3fdadc70 1128 } else {
1129 ftpFail(ftpState);
1130 }
1131}
1132
969c39b9 1133static void
1134ftpSendUser(FtpStateData * ftpState)
1135{
1136 if (ftpState->proxy_host != NULL)
1137 snprintf(cbuf, 1024, "USER %s@%s\r\n",
1138 ftpState->user,
1139 ftpState->request->host);
1140 else
1141 snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user);
1142 ftpWriteCommand(cbuf, ftpState);
1143 ftpState->state = SENT_USER;
1144}
1145
3fdadc70 1146static void
1147ftpReadUser(FtpStateData * ftpState)
234967c9 1148{
3fdadc70 1149 int code = ftpState->ctrl.replycode;
a3d5953d 1150 debug(9, 3) ("ftpReadUser\n");
3fdadc70 1151 if (code == 230) {
1152 ftpReadPass(ftpState);
1153 } else if (code == 331) {
969c39b9 1154 ftpSendPass(ftpState);
3fdadc70 1155 } else {
1156 ftpFail(ftpState);
1157 }
1158}
1159
969c39b9 1160static void
1161ftpSendPass(FtpStateData * ftpState)
1162{
1163 snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password);
1164 ftpWriteCommand(cbuf, ftpState);
1165 ftpState->state = SENT_PASS;
1166}
1167
3fdadc70 1168static void
1169ftpReadPass(FtpStateData * ftpState)
1170{
1171 int code = ftpState->ctrl.replycode;
a3d5953d 1172 debug(9, 3) ("ftpReadPass\n");
3fdadc70 1173 if (code == 230) {
969c39b9 1174 ftpSendType(ftpState);
3fdadc70 1175 } else {
1176 ftpFail(ftpState);
1177 }
1178}
1179
969c39b9 1180static void
1181ftpSendType(FtpStateData * ftpState)
1182{
1183 char *t;
1184 char *filename;
1185 char mode;
9e242e02 1186 /*
1187 * Ref section 3.2.2 of RFC 1738
1188 */
1189 switch (mode = ftpState->typecode) {
1190 case 'D':
1191 mode = 'A';
1192 break;
1193 case 'A':
1194 case 'I':
1195 break;
1196 default:
1197 t = strrchr(ftpState->request->urlpath, '/');
1198 filename = t ? t + 1 : ftpState->request->urlpath;
1199 mode = mimeGetTransferMode(filename);
1200 break;
1201 }
969c39b9 1202 if (mode == 'I')
1203 EBIT_SET(ftpState->flags, FTP_BINARY);
1204 snprintf(cbuf, 1024, "TYPE %c\r\n", mode);
1205 ftpWriteCommand(cbuf, ftpState);
1206 ftpState->state = SENT_TYPE;
1207}
1208
3fdadc70 1209static void
1210ftpReadType(FtpStateData * ftpState)
1211{
1212 int code = ftpState->ctrl.replycode;
1213 wordlist *w;
1214 wordlist **T;
1215 char *path;
1216 char *d;
a3d5953d 1217 debug(9, 3) ("This is ftpReadType\n");
3fdadc70 1218 if (code == 200) {
13e80e5b 1219 path = xstrdup(ftpState->request->urlpath);
1220 T = &ftpState->pathcomps;
1221 for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) {
1222 rfc1738_unescape(d);
1223 w = xcalloc(1, sizeof(wordlist));
1224 w->key = xstrdup(d);
1225 *T = w;
1226 T = &w->next;
3fdadc70 1227 }
13e80e5b 1228 xfree(path);
969c39b9 1229 if (ftpState->pathcomps)
1230 ftpTraverseDirectory(ftpState);
1231 else
13e80e5b 1232 ftpSendPasv(ftpState);
3fdadc70 1233 } else {
1234 ftpFail(ftpState);
1235 }
1236}
1237
1238static void
969c39b9 1239ftpTraverseDirectory(FtpStateData * ftpState)
3fdadc70 1240{
1241 wordlist *w;
969c39b9 1242 debug(9, 4) ("ftpTraverseDirectory\n");
1243
1244 safe_free(ftpState->filepath);
1245 /* Done? */
1246 if (ftpState->pathcomps == NULL) {
a3d5953d 1247 debug(9, 3) ("the final component was a directory\n");
13e80e5b 1248 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1249 debug(9, 3) ("and path did not end in /\n");
3fdadc70 1250 strcat(ftpState->title_url, "/");
13e80e5b 1251 EBIT_SET(ftpState->flags, FTP_ISDIR);
1252 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1253 }
3fdadc70 1254 ftpSendPasv(ftpState);
234967c9 1255 return;
3fdadc70 1256 }
969c39b9 1257 /* Go to next path component */
1258 w = ftpState->pathcomps;
1259 ftpState->filepath = w->key;
1260 ftpState->pathcomps = w->next;
1261 xfree(w);
1262 /* Check if we are to CWD or RETR */
1263 if (ftpState->pathcomps != NULL || EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1264 ftpSendCwd(ftpState);
1265 } else {
1266 debug(9, 3) ("final component is probably a file\n");
1267 ftpSendMdtm(ftpState);
1268 return;
1269 }
1270}
1271
1272static void
1273ftpSendCwd(FtpStateData * ftpState)
1274{
1275 char *path = ftpState->filepath;
1276 debug(9, 3) ("ftpSendCwd\n");
1277 if (!strcmp(path, "..") || !strcmp(path, "/")) {
13e80e5b 1278 EBIT_SET(ftpState->flags, FTP_NO_DOTDOT);
1279 } else {
1280 EBIT_CLR(ftpState->flags, FTP_NO_DOTDOT);
1281 }
969c39b9 1282 snprintf(cbuf, 1024, "CWD %s\r\n", path);
1283 ftpWriteCommand(cbuf, ftpState);
1284 ftpState->state = SENT_CWD;
3fdadc70 1285}
77a30ebb 1286
3fdadc70 1287static void
1288ftpReadCwd(FtpStateData * ftpState)
1289{
1290 int code = ftpState->ctrl.replycode;
a3d5953d 1291 debug(9, 3) ("This is ftpReadCwd\n");
3fdadc70 1292 if (code >= 200 && code < 300) {
969c39b9 1293 /* CWD OK */
3fdadc70 1294 if (ftpState->cwd_message)
1295 wordlistDestroy(&ftpState->cwd_message);
1296 ftpState->cwd_message = ftpState->ctrl.message;
1297 ftpState->ctrl.message = NULL;
969c39b9 1298 /* Continue to traverse the path */
1299 ftpTraverseDirectory(ftpState);
3fdadc70 1300 } else {
1301 /* CWD FAILED */
969c39b9 1302 ftpFail(ftpState);
c021888f 1303 }
3fdadc70 1304}
1305
969c39b9 1306static void
1307ftpSendMdtm(FtpStateData * ftpState)
1308{
1309 assert(*ftpState->filepath != '\0');
1310 snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath);
1311 ftpWriteCommand(cbuf, ftpState);
1312 ftpState->state = SENT_MDTM;
1313}
1314
3fdadc70 1315static void
1316ftpReadMdtm(FtpStateData * ftpState)
1317{
1318 int code = ftpState->ctrl.replycode;
a3d5953d 1319 debug(9, 3) ("This is ftpReadMdtm\n");
3fdadc70 1320 if (code == 213) {
b9916917 1321 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
3fdadc70 1322 } else if (code < 0) {
1323 ftpFail(ftpState);
77a30ebb 1324 }
969c39b9 1325 ftpSendSize(ftpState);
1326}
1327
1328static void
1329ftpSendSize(FtpStateData * ftpState)
1330{
1331 /* Only send SIZE for binary transfers. The returned size
1332 * is useless on ASCII transfers */
1333 if (!EBIT_TEST(ftpState->flags, FTP_BINARY)) {
1334 assert(ftpState->filepath != NULL);
1335 assert(*ftpState->filepath != '\0');
1336 snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
1337 ftpWriteCommand(cbuf, ftpState);
1338 ftpState->state = SENT_SIZE;
1339 } else
1340 /* Skip to next state no non-binary transfers */
1341 ftpSendPasv(ftpState);
3fdadc70 1342}
1343
1344static void
1345ftpReadSize(FtpStateData * ftpState)
1346{
1347 int code = ftpState->ctrl.replycode;
a3d5953d 1348 debug(9, 3) ("This is ftpReadSize\n");
3fdadc70 1349 if (code == 213) {
b9916917 1350 ftpState->size = atoi(ftpState->ctrl.last_reply);
3fdadc70 1351 } else if (code < 0) {
1352 ftpFail(ftpState);
1353 }
4939c5da 1354 ftpSendPasv(ftpState);
3fdadc70 1355}
1356
1357static void
1358ftpSendPasv(FtpStateData * ftpState)
1359{
1360 int fd;
a57512fa 1361 if (ftpState->data.fd >= 0) {
1362 /* We are already connected, reuse this connection. */
1363 ftpRestOrList(ftpState);
1364 return;
1365 }
3fdadc70 1366 assert(ftpState->data.fd < 0);
1367 if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
1368 ftpSendPort(ftpState);
1369 return;
30a4f2a8 1370 }
3fdadc70 1371 fd = comm_open(SOCK_STREAM,
16b204c4 1372 0,
3fdadc70 1373 Config.Addrs.tcp_outgoing,
30a4f2a8 1374 0,
3fdadc70 1375 COMM_NONBLOCKING,
9fb13bb6 1376 storeUrl(ftpState->entry));
3fdadc70 1377 if (fd < 0) {
1378 ftpFail(ftpState);
1379 return;
1380 }
ff008d84 1381 /*
1382 * No comm_add_close_handler() here. If we have both ctrl and
1383 * data FD's call ftpStateFree() upon close, then we have
1384 * to delete the close handler which did NOT get called
1385 * to prevent ftpStateFree() getting called twice.
1386 * Instead we'll always call comm_close() on the ctrl FD.
1387 */
3fdadc70 1388 ftpState->data.fd = fd;
042461c3 1389 snprintf(cbuf, 1024, "PASV\r\n");
3fdadc70 1390 ftpWriteCommand(cbuf, ftpState);
1391 ftpState->state = SENT_PASV;
1392}
1393
1394static void
1395ftpReadPasv(FtpStateData * ftpState)
1396{
1397 int code = ftpState->ctrl.replycode;
1398 int h1, h2, h3, h4;
1399 int p1, p2;
1400 int n;
1401 u_short port;
1402 int fd = ftpState->data.fd;
b9916917 1403 char *buf = ftpState->ctrl.last_reply;
3fdadc70 1404 LOCAL_ARRAY(char, junk, 1024);
a3d5953d 1405 debug(9, 3) ("This is ftpReadPasv\n");
3fdadc70 1406 if (code != 227) {
a3d5953d 1407 debug(9, 3) ("PASV not supported by remote end\n");
969c39b9 1408 /* XXX Shouldn't we get rid of the PASV socket? */
3fdadc70 1409 ftpSendPort(ftpState);
1410 return;
1411 }
1412 if (strlen(buf) > 1024) {
2a1bc30a 1413 debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n");
3fdadc70 1414 ftpSendPort(ftpState);
1415 return;
1416 }
1417 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1418 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a3d5953d 1419 debug(9, 5) ("scanning: %s\n", buf);
3fdadc70 1420 n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1421 junk, &h1, &h2, &h3, &h4, &p1, &p2);
1422 if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a3d5953d 1423 debug(9, 3) ("Bad 227 reply\n");
1424 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n, p1, p2);
3fdadc70 1425 ftpSendPort(ftpState);
1426 return;
1427 }
56878878 1428 snprintf(junk, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
3fdadc70 1429 if (!safe_inet_addr(junk, NULL)) {
a3d5953d 1430 debug(9, 1) ("unsafe address (%s)\n", junk);
3fdadc70 1431 ftpSendPort(ftpState);
1432 return;
1433 }
1434 port = ((p1 << 8) + p2);
a3d5953d 1435 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk, port);
9b312a19 1436 ftpState->data.port = port;
1437 ftpState->data.host = xstrdup(junk);
3fdadc70 1438 commConnectStart(fd, junk, port, ftpPasvCallback, ftpState);
1439}
1440
1441static void
1442ftpPasvCallback(int fd, int status, void *data)
1443{
1444 FtpStateData *ftpState = data;
9b312a19 1445 request_t *request = ftpState->request;
1446 ErrorState *err;
a3d5953d 1447 debug(9, 3) ("ftpPasvCallback\n");
9b312a19 1448 if (status != COMM_OK) {
fe40a877 1449 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1450 err->xerrno = errno;
9b312a19 1451 err->host = xstrdup(ftpState->data.host);
1452 err->port = ftpState->data.port;
1453 err->request = requestLink(request);
1454 errorAppendEntry(ftpState->entry, err);
ff008d84 1455 comm_close(ftpState->ctrl.fd);
3fdadc70 1456 return;
1457 }
1458 ftpRestOrList(ftpState);
1459}
1460
1461static void
1462ftpSendPort(FtpStateData * ftpState)
1463{
a3d5953d 1464 debug(9, 3) ("This is ftpSendPort\n");
d3f89c29 1465 EBIT_CLR(ftpState->flags, FTP_PASV_SUPPORTED);
969c39b9 1466 /* XXX Not implemented? ftpFail??? */
3fdadc70 1467}
1468
1469static void
79d39a72 1470ftpReadPort(FtpStateData * ftpStateNotUsed)
3fdadc70 1471{
a3d5953d 1472 debug(9, 3) ("This is ftpReadPort\n");
969c39b9 1473 /* XXX Not implemented? */
3fdadc70 1474}
1475
1476static void
1477ftpRestOrList(FtpStateData * ftpState)
1478{
a3d5953d 1479 debug(9, 3) ("This is ftpRestOrList\n");
9e242e02 1480 if (ftpState->typecode == 'D') {
1481 ftpSendNlst(ftpState); /* sec 3.2.2 of RFC 1738 */
1482 EBIT_SET(ftpState->flags, FTP_ISDIR);
1483 } else if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
969c39b9 1484 ftpSendList(ftpState);
1485 else if (ftpState->restart_offset > 0)
1486 ftpSendRest(ftpState);
1487 else
1488 ftpSendRetr(ftpState);
1489}
1490
1491static void
1492ftpSendRest(FtpStateData * ftpState)
1493{
1494 snprintf(cbuf, 1024, "REST %d\r\n", ftpState->restart_offset);
1495 ftpWriteCommand(cbuf, ftpState);
1496 ftpState->state = SENT_REST;
3fdadc70 1497}
1498
1499static void
1500ftpReadRest(FtpStateData * ftpState)
1501{
1502 int code = ftpState->ctrl.replycode;
a3d5953d 1503 debug(9, 3) ("This is ftpReadRest\n");
3fdadc70 1504 assert(ftpState->restart_offset > 0);
1505 if (code == 350) {
969c39b9 1506 ftpSendRetr(ftpState);
3fdadc70 1507 } else if (code > 0) {
a3d5953d 1508 debug(9, 3) ("ftpReadRest: REST not supported\n");
d3f89c29 1509 EBIT_CLR(ftpState->flags, FTP_REST_SUPPORTED);
3fdadc70 1510 } else {
1511 ftpFail(ftpState);
1512 }
1513}
1514
969c39b9 1515static void
1516ftpSendList(FtpStateData * ftpState)
1517{
1518 snprintf(cbuf, 1024, "LIST\r\n");
1519 ftpWriteCommand(cbuf, ftpState);
1520 ftpState->state = SENT_LIST;
1521}
1522
3fdadc70 1523static void
1524ftpReadList(FtpStateData * ftpState)
1525{
1526 int code = ftpState->ctrl.replycode;
a3d5953d 1527 debug(9, 3) ("This is ftpReadList\n");
3fdadc70 1528 if (code == 150 || code == 125) {
1529 ftpAppendSuccessHeader(ftpState);
1530 commSetSelect(ftpState->data.fd,
30a4f2a8 1531 COMM_SELECT_READ,
3fdadc70 1532 ftpReadData,
1533 ftpState,
2acf4be6 1534 Config.Timeout.read);
70a9dab4 1535 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1536 ftpState->state = READING_DATA;
a0eff6be 1537 /* Cancel the timeout on the Control socket and establish one
1538 * on the data socket */
1539 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1540 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
3fdadc70 1541 return;
1542 } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
969c39b9 1543 ftpSendNlst(ftpState);
3fdadc70 1544 } else {
1545 ftpFail(ftpState);
1546 return;
1547 }
1548}
1549
969c39b9 1550static void
1551ftpSendNlst(FtpStateData * ftpState)
1552{
1553 EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
9e242e02 1554 if (ftpState->filepath)
1555 snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
1556 else
1557 snprintf(cbuf, 1024, "NLST\r\n");
969c39b9 1558 ftpWriteCommand(cbuf, ftpState);
1559 ftpState->state = SENT_NLST;
1560}
1561
1562static void
1563ftpSendRetr(FtpStateData * ftpState)
1564{
1565 assert(ftpState->filepath != NULL);
1566 snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath);
1567 ftpWriteCommand(cbuf, ftpState);
1568 ftpState->state = SENT_RETR;
1569}
1570
3fdadc70 1571static void
1572ftpReadRetr(FtpStateData * ftpState)
1573{
1574 int code = ftpState->ctrl.replycode;
a3d5953d 1575 debug(9, 3) ("This is ftpReadRetr\n");
3fdadc70 1576 if (code >= 100 && code < 200) {
a57512fa 1577 debug(9, 3) ("ftpReadRetr: reading data channel\n");
3fdadc70 1578 ftpAppendSuccessHeader(ftpState);
1579 commSetSelect(ftpState->data.fd,
1580 COMM_SELECT_READ,
1581 ftpReadData,
1582 ftpState,
2acf4be6 1583 Config.Timeout.read);
70a9dab4 1584 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1585 ftpState->state = READING_DATA;
2acf4be6 1586 /* Cancel the timeout on the Control socket and establish one
1587 * on the data socket */
1588 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1589 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
3fdadc70 1590 } else {
969c39b9 1591 if (!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
1592 /* Try this as a directory missing trailing slash... */
1593 ftpHackShortcut(ftpState, ftpSendCwd);
1594 } else {
1595 ftpFail(ftpState);
1596 }
3fdadc70 1597 }
1598}
1599
1600static void
1601ftpReadTransferDone(FtpStateData * ftpState)
1602{
1603 int code = ftpState->ctrl.replycode;
a3d5953d 1604 debug(9, 3) ("This is ftpReadTransferDone\n");
3fdadc70 1605 if (code != 226) {
e33ba616 1606 debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n");
9fb13bb6 1607 debug(9, 1) ("--> releasing '%s'\n", storeUrl(ftpState->entry));
3fdadc70 1608 storeReleaseRequest(ftpState->entry);
1609 }
02be0294 1610 ftpDataTransferDone(ftpState);
3fdadc70 1611}
1612
1613static void
1614ftpDataTransferDone(FtpStateData * ftpState)
1615{
a3d5953d 1616 debug(9, 3) ("This is ftpDataTransferDone\n");
033fa114 1617 if (ftpState->data.fd > -1) {
a3d5953d 1618 comm_close(ftpState->data.fd);
1619 ftpState->data.fd = -1;
033fa114 1620 }
969c39b9 1621 ftpSendQuit(ftpState);
1622}
1623
1624static void
1625ftpSendQuit(FtpStateData * ftpState)
1626{
033fa114 1627 assert(ftpState->ctrl.fd > -1);
56878878 1628 snprintf(cbuf, 1024, "QUIT\r\n");
3fdadc70 1629 ftpWriteCommand(cbuf, ftpState);
1630 ftpState->state = SENT_QUIT;
1631}
1632
1633static void
1634ftpReadQuit(FtpStateData * ftpState)
1635{
1636 comm_close(ftpState->ctrl.fd);
1637}
1638
969c39b9 1639static void
1640ftpTrySlashHack(FtpStateData * ftpState)
1641{
1642 char *path;
1643 EBIT_SET(ftpState->flags, FTP_TRY_SLASH_HACK);
1644 /* Free old paths */
1645 if (ftpState->pathcomps)
1646 wordlistDestroy(&ftpState->pathcomps);
1647 safe_free(ftpState->filepath);
1648 /* Build the new path (urlpath begins with /) */
1649 path = xstrdup(ftpState->request->urlpath);
1650 rfc1738_unescape(path);
1651 ftpState->filepath = path;
1652 /* And off we go */
1653 ftpSendMdtm(ftpState);
1654}
1655
1656static void
1657ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState)
1658{
a57512fa 1659 /* Leave the data connection open for future use */
969c39b9 1660 /* Save old error message */
1661 ftpState->old_request = ftpState->ctrl.last_command;
1662 ftpState->ctrl.last_command = NULL;
1663 ftpState->old_reply = ftpState->ctrl.last_reply;
1664 ftpState->ctrl.last_reply = NULL;
1665 /* Jump to the "hack" state */
1666 nextState(ftpState);
1667}
1668
3fdadc70 1669static void
1670ftpFail(FtpStateData * ftpState)
1671{
b9916917 1672 ErrorState *err;
a3d5953d 1673 debug(9, 3) ("ftpFail\n");
969c39b9 1674 /* Try the / hack to support "Netscape" FTP URL's
1675 * only if we failed on CWD or RETR, !IS_DIR */
1676 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR) &&
1677 !EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
1678 switch (ftpState->state) {
1679 case SENT_CWD:
1680 case SENT_RETR:
1681 /* Try the / hack */
1682 ftpHackShortcut(ftpState, ftpTrySlashHack);
1683 return;
1684 default:
1685 break;
1686 }
1687 }
1931735b 1688 err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
b9916917 1689 err->request = requestLink(ftpState->request);
969c39b9 1690 if (ftpState->old_request)
1691 err->ftp.request = ftpState->old_request;
1692 else
1693 err->ftp.request = ftpState->ctrl.last_command;
1694 if (ftpState->old_reply)
1695 err->ftp.reply = ftpState->old_reply;
1696 else
1697 err->ftp.reply = ftpState->ctrl.last_reply;
b9916917 1698 errorAppendEntry(ftpState->entry, err);
3fdadc70 1699 comm_close(ftpState->ctrl.fd);
1700}
1701
1702static void
1703ftpAppendSuccessHeader(FtpStateData * ftpState)
1704{
1705 char *mime_type = NULL;
1706 char *mime_enc = NULL;
1707 char *urlpath = ftpState->request->urlpath;
1708 char *filename = NULL;
1709 char *t = NULL;
1710 StoreEntry *e = ftpState->entry;
9e975e4e 1711 http_reply *reply = e->mem_obj->reply;
3fdadc70 1712 if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT))
1713 return;
9e975e4e 1714 EBIT_SET(ftpState->flags, FTP_HTTP_HEADER_SENT);
8350fe9b 1715 assert(e->mem_obj->inmem_hi == 0);
3fdadc70 1716 filename = (t = strrchr(urlpath, '/')) ? t + 1 : urlpath;
1717 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1718 mime_type = "text/html";
1719 } else {
1720 mime_type = mimeGetContentType(filename);
1721 mime_enc = mimeGetContentEncoding(filename);
1722 }
438fc1e3 1723 storeBuffer(e);
3fdadc70 1724 storeAppendPrintf(e, "HTTP/1.0 200 Gatewaying\r\n");
e6b02cfc 1725 reply->code = 200;
1726 reply->version = 1.0;
3fdadc70 1727 storeAppendPrintf(e, "Date: %s\r\n", mkrfc1123(squid_curtime));
e6b02cfc 1728 reply->date = squid_curtime;
3fdadc70 1729 storeAppendPrintf(e, "MIME-Version: 1.0\r\n");
1730 storeAppendPrintf(e, "Server: Squid %s\r\n", version_string);
e6b02cfc 1731 if (ftpState->size > 0) {
3fdadc70 1732 storeAppendPrintf(e, "Content-Length: %d\r\n", ftpState->size);
a3d5953d 1733 reply->content_length = ftpState->size;
e6b02cfc 1734 }
1735 if (mime_type) {
3fdadc70 1736 storeAppendPrintf(e, "Content-Type: %s\r\n", mime_type);
a3d5953d 1737 xstrncpy(reply->content_type, mime_type, HTTP_REPLY_FIELD_SZ);
e6b02cfc 1738 }
3fdadc70 1739 if (mime_enc)
1740 storeAppendPrintf(e, "Content-Encoding: %s\r\n", mime_enc);
e6b02cfc 1741 if (ftpState->mdtm > 0) {
3fdadc70 1742 storeAppendPrintf(e, "Last-Modified: %s\r\n", mkrfc1123(ftpState->mdtm));
e6b02cfc 1743 reply->last_modified = ftpState->mdtm;
1744 }
3fdadc70 1745 storeAppendPrintf(e, "\r\n");
438fc1e3 1746 storeBufferFlush(e);
2acf4be6 1747 reply->hdr_sz = e->mem_obj->inmem_hi;
e6b02cfc 1748 storeTimestampsSet(e);
e6b02cfc 1749 storeSetPublicKey(e);
77a30ebb 1750}
bfcaf585 1751
1752static void
1753ftpAbort(void *data)
1754{
1755 FtpStateData *ftpState = data;
9fb13bb6 1756 debug(9, 2) ("ftpAbort: %s\n", storeUrl(ftpState->entry));
15888cf7 1757 if (ftpState->data.fd >= 0) {
270b86af 1758 comm_close(ftpState->data.fd);
15888cf7 1759 ftpState->data.fd = -1;
1760 }
7b2cdd7b 1761 comm_close(ftpState->ctrl.fd);
bfcaf585 1762}
9b312a19 1763
9b312a19 1764static char *
1765ftpAuthRequired(const request_t * request, const char *realm)
1766{
1767 LOCAL_ARRAY(char, content, AUTH_MSG_SZ);
2761b2be 1768 LOCAL_ARRAY(char, buf, AUTH_MSG_SZ);
9b312a19 1769 char *hdr;
1770 int s = AUTH_MSG_SZ;
1771 int l = 0;
1772 /* Generate the reply body */
2761b2be 1773 l += snprintf(content + l, s - l,
1774 "<HTML><HEAD><TITLE>Authorization needed</TITLE>\n"
1775 "</HEAD><BODY><H1>Authorization needed</H1>\n"
1776 "<P>Sorry, you have to authorize yourself to request:\n"
1777 "<PRE> ftp://%s@%s%256.256s</PRE>\n"
1778 "<P>from this cache. Please check with the\n"
1779 "<A HREF=\"mailto:%s\">cache administrator</A>\n"
1780 "if you believe this is incorrect.\n"
1781 "<P>\n"
1782 "%s\n"
1783 "<HR>\n"
1784 "<ADDRESS>\n"
1785 "Generated by %s/%s@%s\n"
1786 "</ADDRESS></BODY></HTML>\n"
1787 "\n",
9b312a19 1788 request->login,
1789 request->host,
1790 request->urlpath,
2761b2be 1791 Config.adminEmail,
1792 Config.errHtmlText,
9b312a19 1793 appname,
1794 version_string,
1795 getMyHostname());
1796 /* Now generate reply headers with correct content length */
1797 hdr = httpReplyHeader(1.0, HTTP_UNAUTHORIZED,
1798 "text/html",
1799 strlen(content),
1800 squid_curtime,
1801 squid_curtime + Config.negativeTtl);
1802 /* Now stuff them together and add Authenticate header */
1803 l = 0;
1804 s = AUTH_MSG_SZ;
2761b2be 1805 l += snprintf(buf + l, s - l, "%s", hdr);
1806 l += snprintf(buf + l, s - l,
9b312a19 1807 "WWW-Authenticate: Basic realm=\"%s\"\r\n",
1808 realm);
2761b2be 1809 l += snprintf(buf + l, s - l, "\r\n%s", content);
1810 return buf;
9b312a19 1811}
8f872bb6 1812
1813char *
1814ftpUrlWith2f(const request_t * request)
1815{
1816 LOCAL_ARRAY(char, buf, MAX_URL);
1817 LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1);
1818 LOCAL_ARRAY(char, portbuf, 32);
1819 char *t;
1820 portbuf[0] = '\0';
23d92c64 1821 if (request->protocol != PROTO_FTP)
1822 return NULL;
8f872bb6 1823 if (request->port != urlDefaultPort(request->protocol))
1824 snprintf(portbuf, 32, ":%d", request->port);
1825 loginbuf[0] = '\0';
1826 if (strlen(request->login) > 0) {
1827 strcpy(loginbuf, request->login);
1828 if ((t = strchr(loginbuf, ':')))
1829 *t = '\0';
1830 strcat(loginbuf, "@");
1831 }
1832 snprintf(buf, MAX_URL, "%s://%s%s%s%s%s",
1833 ProtocolStr[request->protocol],
1834 loginbuf,
1835 request->host,
1836 portbuf,
1837 "/%2f",
1838 request->urlpath);
1839 if ((t = strchr(buf, '?')))
1840 *t = '\0';
1841 return buf;
1842}