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