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