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