]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
printf fixes
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
be335c22 1
30a4f2a8 2/*
c3b838d4 3 * $Id: ftp.cc,v 1.214 1998/03/28 20:29:49 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 9 File Transfer Protocol (FTP)
6 * AUTHOR: Harvest Derived
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
019dd986 31
44a47c6e 32#include "squid.h"
090089c4 33
3fdadc70 34enum {
35 FTP_ISDIR,
36 FTP_PASV_SUPPORTED,
37 FTP_SKIP_WHITESPACE,
38 FTP_REST_SUPPORTED,
39 FTP_PASV_ONLY,
40 FTP_AUTHENTICATED,
3fdadc70 41 FTP_HTTP_HEADER_SENT,
42 FTP_TRIED_NLST,
43 FTP_USE_BASE,
44 FTP_ROOT_DIR,
13e80e5b 45 FTP_NO_DOTDOT,
2761b2be 46 FTP_HTML_HEADER_SENT,
969c39b9 47 FTP_BINARY,
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;
c3b838d4 248 debug(9, 3) ("ftpStateFree: %s\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)));
7b63416b 646 /* sometimes there is an 'l' flag, but no "->" link */
647 if (parts->link)
648 snprintf(link2, 2048, "<A HREF=\"%s\">%s</A>",
649 rfc1738_escape(parts->link),
650 parts->link);
9fc0b4b8 651 snprintf(html, 8192, "%s %s [%s] -> %s\n",
3fdadc70 652 icon,
653 link,
9fc0b4b8 654 parts->date,
655 link2);
3fdadc70 656 break;
dbfed404 657 case '\0':
4162ee3b 658 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
659 mimeGetIconURL(parts->name),
dbfed404 660 "[UNKNOWN]");
661 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
662 rfc1738_escape(parts->name),
663 parts->name);
664 snprintf(link2, 2048, "(<A HREF=\"%s/;type=d\">chdir</A>)",
665 rfc1738_escape(parts->name));
666 snprintf(html, 8192, "%s %s %s\n",
667 icon,
668 link,
669 link2);
670 break;
3fdadc70 671 case '-':
672 default:
4162ee3b 673 snprintf(icon, 2048, "<IMG SRC=\"%s\" ALT=\"%-6s\">",
674 mimeGetIconURL(parts->name),
3fdadc70 675 "[FILE]");
042461c3 676 snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s",
9fc0b4b8 677 rfc1738_escape(parts->name),
3fdadc70 678 parts->showname,
679 dots_fill(strlen(parts->showname)));
042461c3 680 snprintf(html, 8192, "%s %s [%s] %6dk\n",
3fdadc70 681 icon,
682 link,
683 parts->date,
684 parts->size);
685 break;
686 }
687 ftpListPartsFree(&parts);
3fdadc70 688 return html;
689}
690
691static void
692ftpParseListing(FtpStateData * ftpState, int len)
693{
694 char *buf = ftpState->data.buf;
b5639035 695 char *end;
696 char *line;
3fdadc70 697 char *s;
698 char *t;
699 size_t linelen;
b5639035 700 size_t usable;
3fdadc70 701 StoreEntry *e = ftpState->entry;
b5639035 702 len += ftpState->data.offset;
703 end = buf + len - 1;
3fdadc70 704 while (*end != '\r' && *end != '\n' && end > buf)
705 end--;
b5639035 706 usable = end - buf;
707 if (usable == 0) {
9fb13bb6 708 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e));
3fdadc70 709 return;
710 }
7021844c 711 line = memAllocate(MEM_4K_BUF);
3fdadc70 712 end++;
1931735b 713 /* XXX there is an ABR bug here. We need to make sure buf is
714 * NULL terminated */
438fc1e3 715 storeBuffer(e);
3fdadc70 716 for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
717 linelen = strcspn(s, crlf) + 1;
718 if (linelen > 4096)
719 linelen = 4096;
720 xstrncpy(line, s, linelen);
a3d5953d 721 debug(9, 7) ("%s\n", line);
3fdadc70 722 if (!strncmp(line, "total", 5))
723 continue;
dbfed404 724 t = ftpHtmlifyListEntry(line, ftpState);
3fdadc70 725 assert(t != NULL);
726 storeAppend(e, t, strlen(t));
727 }
438fc1e3 728 storeBufferFlush(e);
b5639035 729 assert(usable <= len);
730 if (usable < len) {
731 /* must copy partial line to beginning of buf */
e29f7586 732 linelen = len - usable;
b5639035 733 if (linelen > 4096)
734 linelen = 4096;
735 xstrncpy(line, end, linelen);
736 xstrncpy(ftpState->data.buf, line, ftpState->data.size);
737 ftpState->data.offset = strlen(ftpState->data.buf);
738 }
3f6c0fb2 739 memFree(MEM_4K_BUF, line);
3fdadc70 740}
090089c4 741
79a15e0a 742static void
743ftpReadComplete(FtpStateData * ftpState)
744{
4cca8b93 745 debug(9, 3) ("ftpReadComplete\n");
79a15e0a 746 /* Connection closed; retrieval done. */
747 if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
748 ftpListingFinish(ftpState);
54220df8 749 if (!EBIT_TEST(ftpState->flags, FTP_PUT)) {
4162ee3b 750 storeTimestampsSet(ftpState->entry);
751 storeComplete(ftpState->entry);
54220df8 752 }
79a15e0a 753 /* expect the "transfer complete" message on the control socket */
754 commSetSelect(ftpState->ctrl.fd,
755 COMM_SELECT_READ,
756 ftpReadControlReply,
757 ftpState,
758 Config.Timeout.read);
759}
760
582b6456 761static void
dbfed404 762ftpDataRead(int fd, void *data)
090089c4 763{
582b6456 764 FtpStateData *ftpState = data;
090089c4 765 int len;
a57512fa 766 int j;
30a4f2a8 767 int bin;
bfcaf585 768 StoreEntry *entry = ftpState->entry;
79a15e0a 769 MemObject *mem = entry->mem_obj;
3fdadc70 770 assert(fd == ftpState->data.fd);
3fdadc70 771 if (protoAbortFetch(entry)) {
9b312a19 772 storeAbort(entry, 0);
a3d5953d 773 ftpDataTransferDone(ftpState);
774 return;
30a4f2a8 775 }
1a6e21ac 776 errno = 0;
3fdadc70 777 memset(ftpState->data.buf + ftpState->data.offset, '\0',
778 ftpState->data.size - ftpState->data.offset);
779 len = read(fd,
780 ftpState->data.buf + ftpState->data.offset,
781 ftpState->data.size - ftpState->data.offset);
ee1679df 782 if (len > 0) {
783 fd_bytes(fd, len, FD_READ);
a0f32775 784 kb_incr(&Counter.server.all.kbytes_in, len);
785 kb_incr(&Counter.server.ftp.kbytes_in, len);
ee1679df 786 }
dbfed404 787 debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd, len);
30a4f2a8 788 if (len > 0) {
4a63c85f 789 IOStats.Ftp.reads++;
a57512fa 790 for (j = len - 1, bin = 0; j; bin++)
791 j >>= 1;
30a4f2a8 792 IOStats.Ftp.read_hist[bin]++;
793 }
ba718c8f 794 if (len < 0) {
dbfed404 795 debug(50, 1) ("ftpDataRead: read error: %s\n", xstrerror());
b224ea98 796 if (ignoreErrno(errno)) {
a57512fa 797 commSetSelect(fd,
798 COMM_SELECT_READ,
dbfed404 799 ftpDataRead,
a57512fa 800 data,
801 Config.Timeout.read);
6fe6313d 802 } else {
a57512fa 803 assert(mem->inmem_hi > 0);
804 storeAbort(entry, 0);
3fdadc70 805 ftpDataTransferDone(ftpState);
6fe6313d 806 }
090089c4 807 } else if (len == 0) {
79a15e0a 808 ftpReadComplete(ftpState);
090089c4 809 } else {
b5639035 810 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
a57512fa 811 if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT))
812 ftpListingStart(ftpState);
3fdadc70 813 ftpParseListing(ftpState, len);
b5639035 814 } else {
815 assert(ftpState->data.offset == 0);
816 storeAppend(entry, ftpState->data.buf, len);
817 }
48451fb1 818 if (ftpState->size > 0 && mem->inmem_hi >= ftpState->size + mem->reply->hdr_sz)
79a15e0a 819 ftpReadComplete(ftpState);
820 else
821 commSetSelect(fd,
822 COMM_SELECT_READ,
dbfed404 823 ftpDataRead,
79a15e0a 824 data,
825 Config.Timeout.read);
090089c4 826 }
090089c4 827}
828
429fdbec 829/*
830 * ftpCheckAuth
831 *
832 * Return 1 if we have everything needed to complete this request.
833 * Return 0 if something is missing.
834 */
835static int
836ftpCheckAuth(FtpStateData * ftpState, char *req_hdr)
837{
838 char *orig_user;
63259c34 839 const char *auth;
9e4ad609 840 ftpLoginParser(ftpState->request->login, ftpState);
429fdbec 841 if (ftpState->user[0] && ftpState->password[0])
842 return 1; /* name and passwd both in URL */
843 if (!ftpState->user[0] && !ftpState->password[0])
844 return 1; /* no name or passwd */
845 if (ftpState->password[0])
846 return 1; /* passwd with no name? */
847 /* URL has name, but no passwd */
63259c34 848 if (!(auth = mime_get_auth(req_hdr, "Basic", NULL)))
429fdbec 849 return 0; /* need auth header */
850 orig_user = xstrdup(ftpState->user);
9e4ad609 851 ftpLoginParser(auth, ftpState);
429fdbec 852 if (!strcmp(orig_user, ftpState->user)) {
853 xfree(orig_user);
854 return 1; /* same username */
855 }
856 strcpy(ftpState->user, orig_user);
857 xfree(orig_user);
858 return 0; /* different username */
859}
860
13e80e5b 861static void
862ftpCheckUrlpath(FtpStateData * ftpState)
863{
864 request_t *request = ftpState->request;
865 int l;
02922e76 866 const char *t;
867 if ((t = strRChr(request->urlpath, ';')) != NULL) {
dbfed404 868 if (strncasecmp(t + 1, "type=", 5) == 0) {
869 ftpState->typecode = (char) toupper((int) *(t + 6));
02922e76 870 strSet(request->urlpath, t, '\0');
dbfed404 871 }
872 }
02922e76 873 l = strLen(request->urlpath);
13e80e5b 874 EBIT_SET(ftpState->flags, FTP_USE_BASE);
875 /* check for null path */
02922e76 876 if (!l) {
877 stringReset(&request->urlpath, "/");
13e80e5b 878 EBIT_SET(ftpState->flags, FTP_ISDIR);
879 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
02922e76 880 } else if (!strCmp(request->urlpath, "/%2f/")) {
13e80e5b 881 EBIT_SET(ftpState->flags, FTP_ISDIR);
882 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
02922e76 883 } else if ((l >= 1) && (*(strBuf(request->urlpath) + l - 1) == '/')) {
13e80e5b 884 EBIT_SET(ftpState->flags, FTP_ISDIR);
885 EBIT_CLR(ftpState->flags, FTP_USE_BASE);
886 if (l == 1)
887 EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
888 }
889}
890
3fdadc70 891static void
892ftpBuildTitleUrl(FtpStateData * ftpState)
893{
894 request_t *request = ftpState->request;
895 size_t len;
896 char *t;
897 len = 64
898 + strlen(ftpState->user)
899 + strlen(ftpState->password)
900 + strlen(request->host)
02922e76 901 + strLen(request->urlpath);
3fdadc70 902 t = ftpState->title_url = xcalloc(len, 1);
903 strcat(t, "ftp://");
904 if (strcmp(ftpState->user, "anonymous")) {
905 strcat(t, ftpState->user);
906 strcat(t, "@");
907 }
908 strcat(t, request->host);
909 if (request->port != urlDefaultPort(PROTO_FTP))
56878878 910 snprintf(&t[strlen(t)], len - strlen(t), ":%d", request->port);
02922e76 911 strcat(t, strBuf(request->urlpath));
3fdadc70 912}
913
770f051d 914void
75e88d56 915ftpStart(request_t * request, StoreEntry * entry)
0a0bf5db 916{
0a0bf5db 917 LOCAL_ARRAY(char, realm, 8192);
9fb13bb6 918 const char *url = storeUrl(entry);
5c5783a2 919 FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData));
3fdadc70 920 int fd;
9b312a19 921 ErrorState *err;
a0f32775 922 HttpReply *reply;
3f6c0fb2 923 cbdataAdd(ftpState, MEM_NONE);
9fb13bb6 924 debug(9, 3) ("FtpStart: '%s'\n", url);
a0f32775 925 Counter.server.all.requests++;
926 Counter.server.ftp.requests++;
770f051d 927 storeLockObject(entry);
5c5783a2 928 ftpState->entry = entry;
5c5783a2 929 ftpState->request = requestLink(request);
3fdadc70 930 ftpState->ctrl.fd = -1;
931 ftpState->data.fd = -1;
a533dc81 932 ftpState->size = -1;
3fdadc70 933 EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED);
934 EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED);
54220df8 935 if (ftpState->request->method == METHOD_PUT)
4162ee3b 936 EBIT_SET(ftpState->flags, FTP_PUT);
62dec5a7 937 if (!ftpCheckAuth(ftpState, request->headers)) {
429fdbec 938 /* This request is not fully authenticated */
939 if (request->port == 21) {
56878878 940 snprintf(realm, 8192, "ftp %s", ftpState->user);
429fdbec 941 } else {
56878878 942 snprintf(realm, 8192, "ftp %s port %d",
5c5783a2 943 ftpState->user, request->port);
e381a13d 944 }
63259c34 945 /* create reply */
a0f32775 946 reply = entry->mem_obj->reply;
947 assert(reply != NULL);
948 /* create appropriate reply */
949 ftpAuthRequired(reply, request, realm);
950 httpReplySwapOut(reply, entry);
429fdbec 951 storeComplete(entry);
5c5783a2 952 ftpStateFree(-1, ftpState);
429fdbec 953 return;
e381a13d 954 }
13e80e5b 955 ftpCheckUrlpath(ftpState);
3fdadc70 956 ftpBuildTitleUrl(ftpState);
a3d5953d 957 debug(9, 5) ("FtpStart: host=%s, path=%s, user=%s, passwd=%s\n",
02922e76 958 ftpState->request->host, strBuf(ftpState->request->urlpath),
5c5783a2 959 ftpState->user, ftpState->password);
3fdadc70 960 fd = comm_open(SOCK_STREAM,
16b204c4 961 0,
3fdadc70 962 Config.Addrs.tcp_outgoing,
30a4f2a8 963 0,
16b204c4 964 COMM_NONBLOCKING,
30a4f2a8 965 url);
3fdadc70 966 if (fd == COMM_ERROR) {
185b9571 967 debug(9, 4) ("ftpStart: Failed to open a socket.\n");
fe40a877 968 err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 969 err->xerrno = errno;
9b312a19 970 err->request = requestLink(ftpState->request);
971 errorAppendEntry(entry, err);
0a0bf5db 972 return;
090089c4 973 }
3fdadc70 974 ftpState->ctrl.fd = fd;
975 comm_add_close_handler(fd, ftpStateFree, ftpState);
5fbef0c9 976 storeRegisterAbort(entry, ftpAbort, ftpState);
3fdadc70 977 commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState);
3fdadc70 978 commConnectStart(ftpState->ctrl.fd,
979 request->host,
980 request->port,
e924600d 981 ftpConnectDone,
5c5783a2 982 ftpState);
e5f6c5c2 983}
090089c4 984
e5f6c5c2 985static void
986ftpConnectDone(int fd, int status, void *data)
987{
5c5783a2 988 FtpStateData *ftpState = data;
9e975e4e 989 request_t *request = ftpState->request;
9b312a19 990 ErrorState *err;
9e975e4e 991 debug(9, 3) ("ftpConnectDone, status = %d\n", status);
992 if (status == COMM_ERR_DNS) {
993 debug(9, 4) ("ftpConnectDone: Unknown host: %s\n", request->host);
fe40a877 994 err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE);
9b312a19 995 err->dnsserver_msg = xstrdup(dns_error_message);
996 err->request = requestLink(request);
997 errorAppendEntry(ftpState->entry, err);
ff008d84 998 comm_close(ftpState->ctrl.fd);
9e975e4e 999 } else if (status != COMM_OK) {
fe40a877 1000 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1001 err->xerrno = errno;
9b312a19 1002 err->host = xstrdup(request->host);
1003 err->port = request->port;
1004 err->request = requestLink(request);
1005 errorAppendEntry(ftpState->entry, err);
ff008d84 1006 comm_close(ftpState->ctrl.fd);
9e975e4e 1007 } else {
1008 ftpState->state = BEGIN;
7021844c 1009 ftpState->ctrl.buf = memAllocate(MEM_4K_BUF);
3f6c0fb2 1010 ftpState->ctrl.freefunc = memFree4K;
9e975e4e 1011 ftpState->ctrl.size = 4096;
1012 ftpState->ctrl.offset = 0;
1013 ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF);
1014 ftpState->data.size = SQUID_TCP_SO_RCVBUF;
1015 ftpState->data.freefunc = xfree;
86cf9987 1016 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0);
05e11a8c 1017 commSetTimeout(fd, Config.Timeout.read, ftpTimeout, ftpState);
e5f6c5c2 1018 }
090089c4 1019}
1020
3fdadc70 1021/* ====================================================================== */
1022
b8d8561b 1023static void
3fdadc70 1024ftpWriteCommand(const char *buf, FtpStateData * ftpState)
234967c9 1025{
a3d5953d 1026 debug(9, 5) ("ftpWriteCommand: %s\n", buf);
b9916917 1027 safe_free(ftpState->ctrl.last_command);
1028 ftpState->ctrl.last_command = xstrdup(buf);
3fdadc70 1029 comm_write(ftpState->ctrl.fd,
1030 xstrdup(buf),
1031 strlen(buf),
1032 ftpWriteCommandCallback,
1033 ftpState,
1034 xfree);
1035 commSetSelect(ftpState->ctrl.fd,
1036 COMM_SELECT_READ,
1037 ftpReadControlReply,
1038 ftpState,
2acf4be6 1039 Config.Timeout.read);
3fdadc70 1040}
1041
1042static void
79a15e0a 1043ftpWriteCommandCallback(int fd, char *bufnotused, size_t size, int errflag, void *data)
3fdadc70 1044{
1045 FtpStateData *ftpState = data;
1046 StoreEntry *entry = ftpState->entry;
9b312a19 1047 ErrorState *err;
a3d5953d 1048 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size);
ee1679df 1049 if (size > 0) {
1050 fd_bytes(fd, size, FD_WRITE);
a0f32775 1051 kb_incr(&Counter.server.all.kbytes_out, size);
399e85ea 1052 kb_incr(&Counter.server.ftp.kbytes_out, size);
ee1679df 1053 }
96f1be5d 1054 if (errflag == COMM_ERR_CLOSING)
1055 return;
3fdadc70 1056 if (errflag) {
270b86af 1057 debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd, xstrerror());
73a3014d 1058 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1059 err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1060 err->xerrno = errno;
9b312a19 1061 err->request = requestLink(ftpState->request);
1062 errorAppendEntry(entry, err);
1063 }
43d9de30 1064 if (entry->store_status == STORE_PENDING)
1065 storeAbort(entry, 0);
ff008d84 1066 comm_close(ftpState->ctrl.fd);
3fdadc70 1067 }
1068}
1069
1070static wordlist *
1071ftpParseControlReply(char *buf, size_t len, int *codep)
1072{
1073 char *s;
1074 int complete = 0;
1075 wordlist *head;
1076 wordlist *list;
1077 wordlist **tail = &head;
1078 off_t offset;
1079 size_t linelen;
b5639035 1080 int code = -1;
a3d5953d 1081 debug(9, 5) ("ftpParseControlReply\n");
3fdadc70 1082 if (*(buf + len - 1) != '\n')
1083 return NULL;
1084 for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) {
1085 linelen = strcspn(s, crlf) + 1;
1086 if (linelen > 3)
1087 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
1088 if (complete)
1089 code = atoi(s);
1090 offset = 0;
1091 if (linelen > 3)
1092 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
1093 offset = 4;
1094 list = xcalloc(1, sizeof(wordlist));
1095 list->key = xmalloc(linelen - offset);
1096 xstrncpy(list->key, s + offset, linelen - offset);
4939c5da 1097 debug(9, 7) ("%d %s\n", code, list->key);
3fdadc70 1098 *tail = list;
1099 tail = &list->next;
1100 }
1101 if (!complete)
1102 wordlistDestroy(&head);
1103 if (codep)
1104 *codep = code;
1105 return head;
1106}
1107
1108static void
1109ftpReadControlReply(int fd, void *data)
1110{
1111 FtpStateData *ftpState = data;
1112 StoreEntry *entry = ftpState->entry;
1113 char *oldbuf;
1114 wordlist **W;
1115 int len;
9b312a19 1116 ErrorState *err;
a3d5953d 1117 debug(9, 5) ("ftpReadControlReply\n");
3fdadc70 1118 assert(ftpState->ctrl.offset < ftpState->ctrl.size);
1119 len = read(fd,
1120 ftpState->ctrl.buf + ftpState->ctrl.offset,
1121 ftpState->ctrl.size - ftpState->ctrl.offset);
ee1679df 1122 if (len > 0) {
1123 fd_bytes(fd, len, FD_READ);
a0f32775 1124 kb_incr(&Counter.server.all.kbytes_in, len);
399e85ea 1125 kb_incr(&Counter.server.ftp.kbytes_in, len);
ee1679df 1126 }
a3d5953d 1127 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd, len);
3fdadc70 1128 if (len < 0) {
a3d5953d 1129 debug(50, 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
b224ea98 1130 if (ignoreErrno(errno)) {
3fdadc70 1131 commSetSelect(fd,
1132 COMM_SELECT_READ,
1133 ftpReadControlReply,
1134 ftpState,
2acf4be6 1135 Config.Timeout.read);
3fdadc70 1136 } else {
73a3014d 1137 if (entry->mem_obj->inmem_hi == 0) {
fe40a877 1138 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
c45ed9ad 1139 err->xerrno = errno;
9b312a19 1140 err->request = requestLink(ftpState->request);
1141 errorAppendEntry(entry, err);
1142 }
43d9de30 1143 if (entry->store_status == STORE_PENDING)
1144 storeAbort(entry, 0);
ff008d84 1145 comm_close(ftpState->ctrl.fd);
3fdadc70 1146 }
1147 return;
1148 }
1149 if (len == 0) {
cc11e161 1150 if (entry->store_status == STORE_PENDING) {
1151 storeReleaseRequest(entry);
1152 if (entry->mem_obj->inmem_hi == 0) {
1614c049 1153 err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
1154 err->xerrno = 0;
cc11e161 1155 err->request = requestLink(ftpState->request);
1156 errorAppendEntry(entry, err);
1157 }
9b312a19 1158 }
ff008d84 1159 comm_close(ftpState->ctrl.fd);
3fdadc70 1160 return;
1161 }
b5639035 1162 len += ftpState->ctrl.offset;
1163 ftpState->ctrl.offset = len;
3fdadc70 1164 assert(len <= ftpState->ctrl.size);
1165 wordlistDestroy(&ftpState->ctrl.message);
1166 ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len,
1167 &ftpState->ctrl.replycode);
1168 if (ftpState->ctrl.message == NULL) {
a3d5953d 1169 debug(9, 5) ("ftpReadControlReply: partial server reply\n");
3fdadc70 1170 if (len == ftpState->ctrl.size) {
1171 oldbuf = ftpState->ctrl.buf;
1172 ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1);
1173 xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size);
1174 ftpState->ctrl.size <<= 1;
1175 ftpState->ctrl.freefunc(oldbuf);
1176 ftpState->ctrl.freefunc = xfree;
1177 }
2acf4be6 1178 commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read);
3fdadc70 1179 return;
1180 }
1181 for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next);
b9916917 1182 safe_free(ftpState->ctrl.last_reply);
1183 ftpState->ctrl.last_reply = (*W)->key;
3fdadc70 1184 safe_free(*W);
1185 ftpState->ctrl.offset = 0;
4162ee3b 1186 debug(9, 8) ("ftpReadControlReply: state=%d\n", ftpState->state);
3fdadc70 1187 FTP_SM_FUNCS[ftpState->state] (ftpState);
234967c9 1188}
1189
3fdadc70 1190/* ====================================================================== */
1191
1192static void
1193ftpReadWelcome(FtpStateData * ftpState)
1194{
1195 int code = ftpState->ctrl.replycode;
a3d5953d 1196 debug(9, 3) ("ftpReadWelcome\n");
3fdadc70 1197 if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY))
1198 ftpState->login_att++;
1199 if (code == 220) {
1200 if (ftpState->ctrl.message)
1201 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1202 EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
969c39b9 1203 ftpSendUser(ftpState);
cdc33f35 1204 } else if (code == 120) {
c3b838d4 1205 if (NULL != ftpState->ctrl.message)
1206 debug(9, 3) ("FTP server is busy: %s\n",
1207 ftpState->ctrl.message->key);
cdc33f35 1208 return;
3fdadc70 1209 } else {
1210 ftpFail(ftpState);
1211 }
1212}
1213
969c39b9 1214static void
1215ftpSendUser(FtpStateData * ftpState)
1216{
1217 if (ftpState->proxy_host != NULL)
1218 snprintf(cbuf, 1024, "USER %s@%s\r\n",
1219 ftpState->user,
1220 ftpState->request->host);
1221 else
1222 snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user);
1223 ftpWriteCommand(cbuf, ftpState);
1224 ftpState->state = SENT_USER;
1225}
1226
3fdadc70 1227static void
1228ftpReadUser(FtpStateData * ftpState)
234967c9 1229{
3fdadc70 1230 int code = ftpState->ctrl.replycode;
a3d5953d 1231 debug(9, 3) ("ftpReadUser\n");
3fdadc70 1232 if (code == 230) {
1233 ftpReadPass(ftpState);
1234 } else if (code == 331) {
969c39b9 1235 ftpSendPass(ftpState);
3fdadc70 1236 } else {
1237 ftpFail(ftpState);
1238 }
1239}
1240
969c39b9 1241static void
1242ftpSendPass(FtpStateData * ftpState)
1243{
1244 snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password);
1245 ftpWriteCommand(cbuf, ftpState);
1246 ftpState->state = SENT_PASS;
1247}
1248
3fdadc70 1249static void
1250ftpReadPass(FtpStateData * ftpState)
1251{
1252 int code = ftpState->ctrl.replycode;
a3d5953d 1253 debug(9, 3) ("ftpReadPass\n");
3fdadc70 1254 if (code == 230) {
969c39b9 1255 ftpSendType(ftpState);
3fdadc70 1256 } else {
1257 ftpFail(ftpState);
1258 }
1259}
1260
969c39b9 1261static void
1262ftpSendType(FtpStateData * ftpState)
1263{
02922e76 1264 const char *t;
1265 const char *filename;
969c39b9 1266 char mode;
9e242e02 1267 /*
1268 * Ref section 3.2.2 of RFC 1738
1269 */
1270 switch (mode = ftpState->typecode) {
1271 case 'D':
1272 mode = 'A';
1273 break;
1274 case 'A':
1275 case 'I':
1276 break;
1277 default:
dbfed404 1278 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1279 mode = 'A';
1280 } else {
02922e76 1281 t = strRChr(ftpState->request->urlpath, '/');
1282 filename = t ? t + 1 : strBuf(ftpState->request->urlpath);
dbfed404 1283 mode = mimeGetTransferMode(filename);
1284 }
9e242e02 1285 break;
1286 }
969c39b9 1287 if (mode == 'I')
1288 EBIT_SET(ftpState->flags, FTP_BINARY);
1289 snprintf(cbuf, 1024, "TYPE %c\r\n", mode);
1290 ftpWriteCommand(cbuf, ftpState);
1291 ftpState->state = SENT_TYPE;
1292}
1293
3fdadc70 1294static void
1295ftpReadType(FtpStateData * ftpState)
1296{
1297 int code = ftpState->ctrl.replycode;
1298 wordlist *w;
1299 wordlist **T;
1300 char *path;
1301 char *d;
a3d5953d 1302 debug(9, 3) ("This is ftpReadType\n");
3fdadc70 1303 if (code == 200) {
02922e76 1304 path = xstrdup(strBuf(ftpState->request->urlpath));
13e80e5b 1305 T = &ftpState->pathcomps;
1306 for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) {
1307 rfc1738_unescape(d);
1308 w = xcalloc(1, sizeof(wordlist));
1309 w->key = xstrdup(d);
1310 *T = w;
1311 T = &w->next;
3fdadc70 1312 }
13e80e5b 1313 xfree(path);
969c39b9 1314 if (ftpState->pathcomps)
1315 ftpTraverseDirectory(ftpState);
1316 else
dbfed404 1317 ftpListDir(ftpState);
3fdadc70 1318 } else {
1319 ftpFail(ftpState);
1320 }
1321}
1322
1323static void
969c39b9 1324ftpTraverseDirectory(FtpStateData * ftpState)
3fdadc70 1325{
1326 wordlist *w;
54220df8 1327 debug(9, 4) ("ftpTraverseDirectory %s\n", ftpState->filepath);
969c39b9 1328
1329 safe_free(ftpState->filepath);
1330 /* Done? */
1331 if (ftpState->pathcomps == NULL) {
a3d5953d 1332 debug(9, 3) ("the final component was a directory\n");
dbfed404 1333 ftpListDir(ftpState);
234967c9 1334 return;
3fdadc70 1335 }
969c39b9 1336 /* Go to next path component */
1337 w = ftpState->pathcomps;
1338 ftpState->filepath = w->key;
1339 ftpState->pathcomps = w->next;
1340 xfree(w);
1341 /* Check if we are to CWD or RETR */
1342 if (ftpState->pathcomps != NULL || EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1343 ftpSendCwd(ftpState);
1344 } else {
1345 debug(9, 3) ("final component is probably a file\n");
dbfed404 1346 ftpGetFile(ftpState);
969c39b9 1347 return;
1348 }
1349}
1350
1351static void
1352ftpSendCwd(FtpStateData * ftpState)
1353{
1354 char *path = ftpState->filepath;
1355 debug(9, 3) ("ftpSendCwd\n");
1356 if (!strcmp(path, "..") || !strcmp(path, "/")) {
13e80e5b 1357 EBIT_SET(ftpState->flags, FTP_NO_DOTDOT);
1358 } else {
1359 EBIT_CLR(ftpState->flags, FTP_NO_DOTDOT);
1360 }
969c39b9 1361 snprintf(cbuf, 1024, "CWD %s\r\n", path);
1362 ftpWriteCommand(cbuf, ftpState);
1363 ftpState->state = SENT_CWD;
3fdadc70 1364}
77a30ebb 1365
3fdadc70 1366static void
1367ftpReadCwd(FtpStateData * ftpState)
1368{
1369 int code = ftpState->ctrl.replycode;
a3d5953d 1370 debug(9, 3) ("This is ftpReadCwd\n");
3fdadc70 1371 if (code >= 200 && code < 300) {
969c39b9 1372 /* CWD OK */
3fdadc70 1373 if (ftpState->cwd_message)
1374 wordlistDestroy(&ftpState->cwd_message);
1375 ftpState->cwd_message = ftpState->ctrl.message;
1376 ftpState->ctrl.message = NULL;
969c39b9 1377 /* Continue to traverse the path */
1378 ftpTraverseDirectory(ftpState);
3fdadc70 1379 } else {
1380 /* CWD FAILED */
4162ee3b 1381 if (!EBIT_TEST(ftpState->flags, FTP_PUT))
1382 ftpFail(ftpState);
54220df8 1383 else
4162ee3b 1384 ftpTryMkdir(ftpState);
c021888f 1385 }
3fdadc70 1386}
1387
54220df8 1388static void
4162ee3b 1389ftpTryMkdir(FtpStateData * ftpState)
54220df8 1390{
4162ee3b 1391 char *path = ftpState->filepath;
1392 debug(9, 3) ("ftpTryMkdir: with path=%s\n", path);
1393 snprintf(cbuf, 1024, "MKD %s\r\n", path);
1394 ftpWriteCommand(cbuf, ftpState);
1395 ftpState->state = SENT_MKDIR;
54220df8 1396}
1397
1398static void
4162ee3b 1399ftpReadMkdir(FtpStateData * ftpState)
1400{
1401 char *path = ftpState->filepath;
1402 int code = ftpState->ctrl.replycode;
1403
c3b838d4 1404 debug(9, 3) ("ftpReadMkdir: path %s, code %d\n", path, code);
4162ee3b 1405 if (code == 257) { /* success */
1406 ftpSendCwd(ftpState);
1407 } else if (code == 550) { /* dir exists */
1408 if (EBIT_TEST(ftpState->flags, FTP_PUT_MKDIR)) {
1409 EBIT_SET(ftpState->flags, FTP_PUT_MKDIR);
1410 ftpSendCwd(ftpState);
1411 } else
1412 ftpSendReply(ftpState);
1413 } else
1414 ftpSendReply(ftpState);
54220df8 1415}
1416
dbfed404 1417static void
1418ftpGetFile(FtpStateData * ftpState)
1419{
1420 assert(*ftpState->filepath != '\0');
1421 EBIT_CLR(ftpState->flags, FTP_ISDIR);
1422 ftpSendMdtm(ftpState);
1423}
1424
1425static void
1426ftpListDir(FtpStateData * ftpState)
1427{
1428 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
1429 debug(9, 3) ("Directory path did not end in /\n");
1430 strcat(ftpState->title_url, "/");
1431 EBIT_SET(ftpState->flags, FTP_ISDIR);
1432 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1433 }
1434 ftpSendPasv(ftpState);
1435}
1436
969c39b9 1437static void
1438ftpSendMdtm(FtpStateData * ftpState)
1439{
1440 assert(*ftpState->filepath != '\0');
1441 snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath);
1442 ftpWriteCommand(cbuf, ftpState);
1443 ftpState->state = SENT_MDTM;
1444}
1445
3fdadc70 1446static void
1447ftpReadMdtm(FtpStateData * ftpState)
1448{
1449 int code = ftpState->ctrl.replycode;
a3d5953d 1450 debug(9, 3) ("This is ftpReadMdtm\n");
3fdadc70 1451 if (code == 213) {
b9916917 1452 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
3fdadc70 1453 } else if (code < 0) {
1454 ftpFail(ftpState);
77a30ebb 1455 }
969c39b9 1456 ftpSendSize(ftpState);
1457}
1458
1459static void
1460ftpSendSize(FtpStateData * ftpState)
1461{
1462 /* Only send SIZE for binary transfers. The returned size
1463 * is useless on ASCII transfers */
dbfed404 1464 if (EBIT_TEST(ftpState->flags, FTP_BINARY)) {
969c39b9 1465 assert(ftpState->filepath != NULL);
1466 assert(*ftpState->filepath != '\0');
1467 snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
1468 ftpWriteCommand(cbuf, ftpState);
1469 ftpState->state = SENT_SIZE;
1470 } else
1471 /* Skip to next state no non-binary transfers */
1472 ftpSendPasv(ftpState);
3fdadc70 1473}
1474
1475static void
1476ftpReadSize(FtpStateData * ftpState)
1477{
1478 int code = ftpState->ctrl.replycode;
a3d5953d 1479 debug(9, 3) ("This is ftpReadSize\n");
3fdadc70 1480 if (code == 213) {
b9916917 1481 ftpState->size = atoi(ftpState->ctrl.last_reply);
3fdadc70 1482 } else if (code < 0) {
1483 ftpFail(ftpState);
1484 }
4939c5da 1485 ftpSendPasv(ftpState);
3fdadc70 1486}
1487
1488static void
1489ftpSendPasv(FtpStateData * ftpState)
1490{
1491 int fd;
cdc33f35 1492 struct sockaddr_in addr;
1493 int addr_len;
a57512fa 1494 if (ftpState->data.fd >= 0) {
1495 /* We are already connected, reuse this connection. */
1496 ftpRestOrList(ftpState);
1497 return;
1498 }
3fdadc70 1499 if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
1500 ftpSendPort(ftpState);
1501 return;
30a4f2a8 1502 }
cdc33f35 1503 addr_len = sizeof(addr);
1504 if (getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
1505 debug(9, 0) ("ftpSendPasv: getsockname(%d,..): %s\n",
1506 ftpState->ctrl.fd, xstrerror());
1507 addr.sin_addr = Config.Addrs.tcp_outgoing;
1508 }
1509 /* Open data channel with the same local address as control channel */
3fdadc70 1510 fd = comm_open(SOCK_STREAM,
16b204c4 1511 0,
cdc33f35 1512 addr.sin_addr,
30a4f2a8 1513 0,
3fdadc70 1514 COMM_NONBLOCKING,
9fb13bb6 1515 storeUrl(ftpState->entry));
05e11a8c 1516 debug(9, 3) ("ftpSendPasv: Unconnected data socket created on FD %d\n", fd);
3fdadc70 1517 if (fd < 0) {
1518 ftpFail(ftpState);
1519 return;
1520 }
ff008d84 1521 /*
1522 * No comm_add_close_handler() here. If we have both ctrl and
1523 * data FD's call ftpStateFree() upon close, then we have
1524 * to delete the close handler which did NOT get called
1525 * to prevent ftpStateFree() getting called twice.
1526 * Instead we'll always call comm_close() on the ctrl FD.
1527 */
3fdadc70 1528 ftpState->data.fd = fd;
042461c3 1529 snprintf(cbuf, 1024, "PASV\r\n");
3fdadc70 1530 ftpWriteCommand(cbuf, ftpState);
1531 ftpState->state = SENT_PASV;
1532}
1533
1534static void
1535ftpReadPasv(FtpStateData * ftpState)
1536{
1537 int code = ftpState->ctrl.replycode;
1538 int h1, h2, h3, h4;
1539 int p1, p2;
1540 int n;
1541 u_short port;
1542 int fd = ftpState->data.fd;
b9916917 1543 char *buf = ftpState->ctrl.last_reply;
3fdadc70 1544 LOCAL_ARRAY(char, junk, 1024);
a3d5953d 1545 debug(9, 3) ("This is ftpReadPasv\n");
3fdadc70 1546 if (code != 227) {
a3d5953d 1547 debug(9, 3) ("PASV not supported by remote end\n");
cdc33f35 1548 comm_close(ftpState->data.fd);
1549 ftpState->data.fd = -1;
3fdadc70 1550 ftpSendPort(ftpState);
1551 return;
1552 }
69e81830 1553 if ((int) strlen(buf) > 1024) {
2a1bc30a 1554 debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n");
3fdadc70 1555 ftpSendPort(ftpState);
1556 return;
1557 }
1558 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1559 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a3d5953d 1560 debug(9, 5) ("scanning: %s\n", buf);
3fdadc70 1561 n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1562 junk, &h1, &h2, &h3, &h4, &p1, &p2);
1563 if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a3d5953d 1564 debug(9, 3) ("Bad 227 reply\n");
1565 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n, p1, p2);
3fdadc70 1566 ftpSendPort(ftpState);
1567 return;
1568 }
56878878 1569 snprintf(junk, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
3fdadc70 1570 if (!safe_inet_addr(junk, NULL)) {
a3d5953d 1571 debug(9, 1) ("unsafe address (%s)\n", junk);
3fdadc70 1572 ftpSendPort(ftpState);
1573 return;
1574 }
1575 port = ((p1 << 8) + p2);
a3d5953d 1576 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk, port);
9b312a19 1577 ftpState->data.port = port;
1578 ftpState->data.host = xstrdup(junk);
3fdadc70 1579 commConnectStart(fd, junk, port, ftpPasvCallback, ftpState);
1580}
1581
1582static void
1583ftpPasvCallback(int fd, int status, void *data)
1584{
1585 FtpStateData *ftpState = data;
9b312a19 1586 request_t *request = ftpState->request;
1587 ErrorState *err;
a3d5953d 1588 debug(9, 3) ("ftpPasvCallback\n");
9b312a19 1589 if (status != COMM_OK) {
fe40a877 1590 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
c45ed9ad 1591 err->xerrno = errno;
9b312a19 1592 err->host = xstrdup(ftpState->data.host);
1593 err->port = ftpState->data.port;
1594 err->request = requestLink(request);
1595 errorAppendEntry(ftpState->entry, err);
ff008d84 1596 comm_close(ftpState->ctrl.fd);
3fdadc70 1597 return;
1598 }
1599 ftpRestOrList(ftpState);
1600}
1601
cdc33f35 1602static int
1603ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
1604{
1605 int fd;
1606 struct sockaddr_in addr;
1607 int addr_len;
1608 int on = 1;
1609 u_short port = 0;
1610 /* Set up a listen socket on the same local address as the control connection. */
1611 addr_len = sizeof(addr);
1612 if (getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
1613 debug(9, 0) ("ftpOpenListenSocket: getsockname(%d,..): %s\n",
1614 ftpState->ctrl.fd, xstrerror());
1615 return -1;
1616 }
1617 /* REUSEADDR is needed in fallback mode, since the same port is used for both
1618 * control and data
1619 */
1620 if (fallback) {
1621 setsockopt(ftpState->ctrl.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
1622 port = ntohs(addr.sin_port);
1623 }
1624 fd = comm_open(SOCK_STREAM,
1625 0,
1626 addr.sin_addr,
1627 port,
1628 COMM_NONBLOCKING | (fallback ? COMM_REUSEADDR : 0),
1629 storeUrl(ftpState->entry));
05e11a8c 1630 debug(9, 3) ("ftpOpenListenSocket: Unconnected data socket created on FD %d\n", fd);
cdc33f35 1631 if (fd < 0) {
1632 debug(9, 0) ("ftpOpenListenSocket: comm_open failed\n");
1633 return -1;
1634 }
1635 if (comm_listen(fd) < 0) {
1636 comm_close(fd);
1637 return -1;
1638 }
1639 ftpState->data.fd = fd;
1640 ftpState->data.port = comm_local_port(fd);;
1641 ftpState->data.host = NULL;
1642 return fd;
1643}
1644
3fdadc70 1645static void
1646ftpSendPort(FtpStateData * ftpState)
1647{
cdc33f35 1648 int fd;
1649 struct sockaddr_in addr;
1650 int addr_len;
1651 unsigned char *addrptr;
1652 unsigned char *portptr;
a3d5953d 1653 debug(9, 3) ("This is ftpSendPort\n");
d3f89c29 1654 EBIT_CLR(ftpState->flags, FTP_PASV_SUPPORTED);
cdc33f35 1655 fd = ftpOpenListenSocket(ftpState, 0);
1656 addr_len = sizeof(addr);
1657 if (getsockname(fd, (struct sockaddr *) &addr, &addr_len)) {
1658 debug(9, 0) ("ftpSendPort: getsockname(%d,..): %s\n", fd, xstrerror());
1659 /* XXX Need to set error message */
1660 ftpFail(ftpState);
1661 return;
1662 }
1663 addrptr = (unsigned char *) &addr.sin_addr.s_addr;
1664 portptr = (unsigned char *) &addr.sin_port;
1665 snprintf(cbuf, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
1666 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
1667 portptr[0], portptr[1]);
1668 ftpWriteCommand(cbuf, ftpState);
1669 ftpState->state = SENT_PORT;
3fdadc70 1670}
1671
1672static void
cdc33f35 1673ftpReadPort(FtpStateData * ftpState)
3fdadc70 1674{
cdc33f35 1675 int code = ftpState->ctrl.replycode;
a3d5953d 1676 debug(9, 3) ("This is ftpReadPort\n");
cdc33f35 1677 if (code != 200) {
1678 /* Fall back on using the same port as the control connection */
1679 debug(9, 3) ("PORT not supported by remote end\n");
1680 comm_close(ftpState->data.fd);
1681 ftpOpenListenSocket(ftpState, 1);
1682 }
1683 ftpRestOrList(ftpState);
1684}
1685
1686/* "read" handler to accept data connection */
1687static void
1688ftpAcceptDataConnection(int fd, void *data)
1689{
1690 FtpStateData *ftpState = data;
1691 struct sockaddr_in peer, me;
1692 debug(9, 3) ("ftpAcceptDataConnection\n");
1693
1694 fd = comm_accept(fd, &peer, &me);
1695 if (fd < 0) {
1696 debug(9, 1) ("ftpHandleDataAccept: comm_accept(%d): %s", fd, xstrerror());
1697 /* XXX Need to set error message */
1698 ftpFail(ftpState);
1699 return;
1700 }
c93a9f49 1701 /* Replace the Listen socket with the accepted data socket */
1702 comm_close(ftpState->data.fd);
05e11a8c 1703 debug(9, 3) ("ftpAcceptDataConnection: Connected data socket on FD %d\n", fd);
cdc33f35 1704 ftpState->data.fd = fd;
1705 ftpState->data.port = ntohs(peer.sin_port);
1706 ftpState->data.host = xstrdup(inet_ntoa(peer.sin_addr));
05e11a8c 1707 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1708 ftpState);
cdc33f35 1709 /* XXX We should have a flag to track connect state...
1710 * host NULL -> not connected, port == local port
1711 * host set -> connected, port == remote port
1712 */
1713 /* Restart state (SENT_NLST/LIST/RETR) */
1714 FTP_SM_FUNCS[ftpState->state] (ftpState);
3fdadc70 1715}
1716
1717static void
1718ftpRestOrList(FtpStateData * ftpState)
1719{
54220df8 1720
a3d5953d 1721 debug(9, 3) ("This is ftpRestOrList\n");
54220df8 1722 if (EBIT_TEST(ftpState->flags, FTP_PUT)) {
4162ee3b 1723 debug(9, 3) ("ftpRestOrList: Sending STOR request...\n");
54220df8 1724 ftpSendStor(ftpState);
1725 } else if (ftpState->typecode == 'D') {
dbfed404 1726 /* XXX This should NOT be here */
9e242e02 1727 ftpSendNlst(ftpState); /* sec 3.2.2 of RFC 1738 */
1728 EBIT_SET(ftpState->flags, FTP_ISDIR);
dbfed404 1729 EBIT_SET(ftpState->flags, FTP_USE_BASE);
9e242e02 1730 } else if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
969c39b9 1731 ftpSendList(ftpState);
1732 else if (ftpState->restart_offset > 0)
1733 ftpSendRest(ftpState);
1734 else
1735 ftpSendRetr(ftpState);
1736}
1737
54220df8 1738static void
1739ftpSendStor(FtpStateData * ftpState)
1740{
1741 assert(ftpState->filepath != NULL);
1742 snprintf(cbuf, 1024, "STOR %s\r\n", ftpState->filepath);
1743 ftpWriteCommand(cbuf, ftpState);
1744 ftpState->state = SENT_STOR;
1745}
1746
1747static void
1748ftpReadStor(FtpStateData * ftpState)
1749{
1750 int code = ftpState->ctrl.replycode;
1751 debug(9, 3) ("This is ftpReadStor\n");
1752 if (code >= 100 && code < 200) {
acce49bf 1753 /*
1754 * Cancel the timeout on the Control socket, pumpStart will
1755 * establish one on the data socket.
1756 */
1757 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1758 ftpPutStart(ftpState);
1759 debug(9, 3) ("ftpReadStor: writing data channel\n");
1760 ftpState->state = WRITING_DATA;
1761 } else if (code == 553) {
26970499 1762 /* directory does not exist, have to create, sigh */
1763#if WORK_IN_PROGRESS
54220df8 1764 ftpTraverseDirectory(ftpState);
1765#endif
1766 ftpSendReply(ftpState);
1767 } else {
acce49bf 1768 debug(9, 3) ("ftpReadStor: that's all folks\n");
54220df8 1769 ftpSendReply(ftpState);
1770 }
1771}
1772
969c39b9 1773static void
1774ftpSendRest(FtpStateData * ftpState)
1775{
1776 snprintf(cbuf, 1024, "REST %d\r\n", ftpState->restart_offset);
1777 ftpWriteCommand(cbuf, ftpState);
1778 ftpState->state = SENT_REST;
3fdadc70 1779}
1780
1781static void
1782ftpReadRest(FtpStateData * ftpState)
1783{
1784 int code = ftpState->ctrl.replycode;
a3d5953d 1785 debug(9, 3) ("This is ftpReadRest\n");
3fdadc70 1786 assert(ftpState->restart_offset > 0);
1787 if (code == 350) {
969c39b9 1788 ftpSendRetr(ftpState);
3fdadc70 1789 } else if (code > 0) {
a3d5953d 1790 debug(9, 3) ("ftpReadRest: REST not supported\n");
d3f89c29 1791 EBIT_CLR(ftpState->flags, FTP_REST_SUPPORTED);
3fdadc70 1792 } else {
1793 ftpFail(ftpState);
1794 }
1795}
1796
969c39b9 1797static void
1798ftpSendList(FtpStateData * ftpState)
1799{
dbfed404 1800 if (ftpState->filepath) {
1801 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1802 snprintf(cbuf, 1024, "LIST %s\r\n", ftpState->filepath);
1803 } else {
1804 snprintf(cbuf, 1024, "LIST\r\n");
1805 }
969c39b9 1806 ftpWriteCommand(cbuf, ftpState);
1807 ftpState->state = SENT_LIST;
1808}
1809
dbfed404 1810static void
1811ftpSendNlst(FtpStateData * ftpState)
1812{
1813 EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
1814 if (ftpState->filepath) {
1815 EBIT_SET(ftpState->flags, FTP_USE_BASE);
1816 snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
1817 } else {
1818 snprintf(cbuf, 1024, "NLST\r\n");
1819 }
1820 ftpWriteCommand(cbuf, ftpState);
1821 ftpState->state = SENT_NLST;
1822}
1823
3fdadc70 1824static void
1825ftpReadList(FtpStateData * ftpState)
1826{
1827 int code = ftpState->ctrl.replycode;
a3d5953d 1828 debug(9, 3) ("This is ftpReadList\n");
cdc33f35 1829 if (code == 125 || (code == 150 && ftpState->data.host)) {
1830 /* Begin data transfer */
3fdadc70 1831 ftpAppendSuccessHeader(ftpState);
1832 commSetSelect(ftpState->data.fd,
30a4f2a8 1833 COMM_SELECT_READ,
dbfed404 1834 ftpDataRead,
3fdadc70 1835 ftpState,
2acf4be6 1836 Config.Timeout.read);
70a9dab4 1837 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1838 ftpState->state = READING_DATA;
05e11a8c 1839 /*
1840 * Cancel the timeout on the Control socket and establish one
1841 * on the data socket
1842 */
a0eff6be 1843 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1844 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
3fdadc70 1845 return;
cdc33f35 1846 } else if (code == 150) {
1847 /* Accept data channel */
1848 commSetSelect(ftpState->data.fd,
1849 COMM_SELECT_READ,
1850 ftpAcceptDataConnection,
1851 ftpState,
05e11a8c 1852 0);
1853 /*
1854 * Cancel the timeout on the Control socket and establish one
1855 * on the data socket
1856 */
1857 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1858 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
cdc33f35 1859 return;
1860 } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST) && code > 300) {
969c39b9 1861 ftpSendNlst(ftpState);
3fdadc70 1862 } else {
1863 ftpFail(ftpState);
1864 return;
1865 }
1866}
1867
969c39b9 1868static void
1869ftpSendRetr(FtpStateData * ftpState)
1870{
1871 assert(ftpState->filepath != NULL);
1872 snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath);
1873 ftpWriteCommand(cbuf, ftpState);
1874 ftpState->state = SENT_RETR;
1875}
1876
3fdadc70 1877static void
1878ftpReadRetr(FtpStateData * ftpState)
1879{
1880 int code = ftpState->ctrl.replycode;
a3d5953d 1881 debug(9, 3) ("This is ftpReadRetr\n");
cdc33f35 1882 if (code == 125 || (code == 150 && ftpState->data.host)) {
1883 /* Begin data transfer */
a57512fa 1884 debug(9, 3) ("ftpReadRetr: reading data channel\n");
3fdadc70 1885 ftpAppendSuccessHeader(ftpState);
1886 commSetSelect(ftpState->data.fd,
1887 COMM_SELECT_READ,
dbfed404 1888 ftpDataRead,
3fdadc70 1889 ftpState,
2acf4be6 1890 Config.Timeout.read);
70a9dab4 1891 commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
3fdadc70 1892 ftpState->state = READING_DATA;
bd200c90 1893 /*
1894 * Cancel the timeout on the Control socket and establish one
1895 * on the data socket
1896 */
2acf4be6 1897 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
bd200c90 1898 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1899 ftpState);
cdc33f35 1900 } else if (code == 150) {
1901 /* Accept data channel */
1902 commSetSelect(ftpState->data.fd,
1903 COMM_SELECT_READ,
1904 ftpAcceptDataConnection,
1905 ftpState,
05e11a8c 1906 0);
bd200c90 1907 /*
1908 * Cancel the timeout on the Control socket and establish one
1909 * on the data socket
1910 */
1911 commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
1912 commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout,
1913 ftpState);
cdc33f35 1914 } else if (code >= 300) {
969c39b9 1915 if (!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
1916 /* Try this as a directory missing trailing slash... */
1917 ftpHackShortcut(ftpState, ftpSendCwd);
1918 } else {
1919 ftpFail(ftpState);
1920 }
cdc33f35 1921 } else {
1922 ftpFail(ftpState);
3fdadc70 1923 }
1924}
1925
1926static void
1927ftpReadTransferDone(FtpStateData * ftpState)
1928{
1929 int code = ftpState->ctrl.replycode;
a3d5953d 1930 debug(9, 3) ("This is ftpReadTransferDone\n");
3fdadc70 1931 if (code != 226) {
c3b838d4 1932 debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n",
1933 code);
9fb13bb6 1934 debug(9, 1) ("--> releasing '%s'\n", storeUrl(ftpState->entry));
3fdadc70 1935 storeReleaseRequest(ftpState->entry);
1936 }
02be0294 1937 ftpDataTransferDone(ftpState);
3fdadc70 1938}
1939
1940static void
1941ftpDataTransferDone(FtpStateData * ftpState)
1942{
a3d5953d 1943 debug(9, 3) ("This is ftpDataTransferDone\n");
033fa114 1944 if (ftpState->data.fd > -1) {
a3d5953d 1945 comm_close(ftpState->data.fd);
1946 ftpState->data.fd = -1;
033fa114 1947 }
969c39b9 1948 ftpSendQuit(ftpState);
1949}
1950
1951static void
1952ftpSendQuit(FtpStateData * ftpState)
1953{
033fa114 1954 assert(ftpState->ctrl.fd > -1);
56878878 1955 snprintf(cbuf, 1024, "QUIT\r\n");
3fdadc70 1956 ftpWriteCommand(cbuf, ftpState);
1957 ftpState->state = SENT_QUIT;
1958}
1959
1960static void
1961ftpReadQuit(FtpStateData * ftpState)
1962{
1963 comm_close(ftpState->ctrl.fd);
1964}
1965
969c39b9 1966static void
1967ftpTrySlashHack(FtpStateData * ftpState)
1968{
1969 char *path;
1970 EBIT_SET(ftpState->flags, FTP_TRY_SLASH_HACK);
1971 /* Free old paths */
1972 if (ftpState->pathcomps)
1973 wordlistDestroy(&ftpState->pathcomps);
1974 safe_free(ftpState->filepath);
1975 /* Build the new path (urlpath begins with /) */
02922e76 1976 path = xstrdup(strBuf(ftpState->request->urlpath));
969c39b9 1977 rfc1738_unescape(path);
1978 ftpState->filepath = path;
1979 /* And off we go */
dbfed404 1980 ftpGetFile(ftpState);
969c39b9 1981}
1982
1983static void
1984ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState)
1985{
a57512fa 1986 /* Leave the data connection open for future use */
969c39b9 1987 /* Save old error message */
1988 ftpState->old_request = ftpState->ctrl.last_command;
1989 ftpState->ctrl.last_command = NULL;
1990 ftpState->old_reply = ftpState->ctrl.last_reply;
1991 ftpState->ctrl.last_reply = NULL;
1992 /* Jump to the "hack" state */
1993 nextState(ftpState);
1994}
1995
3fdadc70 1996static void
1997ftpFail(FtpStateData * ftpState)
1998{
b9916917 1999 ErrorState *err;
a3d5953d 2000 debug(9, 3) ("ftpFail\n");
dbfed404 2001 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
969c39b9 2002 if (!EBIT_TEST(ftpState->flags, FTP_ISDIR) &&
2003 !EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
2004 switch (ftpState->state) {
2005 case SENT_CWD:
2006 case SENT_RETR:
2007 /* Try the / hack */
2008 ftpHackShortcut(ftpState, ftpTrySlashHack);
2009 return;
2010 default:
2011 break;
2012 }
2013 }
1931735b 2014 err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
b9916917 2015 err->request = requestLink(ftpState->request);
969c39b9 2016 if (ftpState->old_request)
2017 err->ftp.request = ftpState->old_request;
2018 else
2019 err->ftp.request = ftpState->ctrl.last_command;
2020 if (ftpState->old_reply)
2021 err->ftp.reply = ftpState->old_reply;
2022 else
2023 err->ftp.reply = ftpState->ctrl.last_reply;
b9916917 2024 errorAppendEntry(ftpState->entry, err);
3fdadc70 2025 comm_close(ftpState->ctrl.fd);
2026}
2027
54220df8 2028static void
2029ftpPutStart(FtpStateData * ftpState)
2030{
2031 debug(9, 3) ("ftpPutStart\n");
4162ee3b 2032 pumpStart(ftpState->data.fd, ftpState->entry,
2033 ftpState->request, ftpPutTransferDone, ftpState);
54220df8 2034}
2035
4162ee3b 2036static void
54220df8 2037ftpPutTransferDone(int fd, char *bufnotused, size_t size, int errflag, void *data)
2038{
4162ee3b 2039 FtpStateData *ftpState = (FtpStateData *) data;
54220df8 2040 if (ftpState->data.fd >= 0) {
4162ee3b 2041 comm_close(ftpState->data.fd);
2042 ftpState->data.fd = -1;
54220df8 2043 }
2044 ftpReadComplete(ftpState);
2045}
2046
2047static void
2048ftpSendReply(FtpStateData * ftpState)
2049{
2050 ErrorState *err;
acce49bf 2051 int code = ftpState->ctrl.replycode;
54220df8 2052 int http_code;
acce49bf 2053 int err_code = ERR_NONE;
c3b838d4 2054 debug(9, 5) ("ftpSendReply: %s, code %d\n",
2055 storeUrl(ftpState->entry), code);
54220df8 2056 if (cbdataValid(ftpState))
acce49bf 2057 debug(9, 5) ("ftpSendReply: ftpState (%p) is valid!\n", ftpState);
2058 if (code == 226) {
2059 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
2060 http_code = (ftpState->mdtm > 0) ? HTTP_ACCEPTED : HTTP_CREATED;
54220df8 2061 } else {
2062 err_code = ERR_FTP_PUT_ERROR;
acce49bf 2063 http_code = HTTP_INTERNAL_SERVER_ERROR;
54220df8 2064 }
54220df8 2065 err = errorCon(err_code, http_code);
2066 err->request = requestLink(ftpState->request);
2067 if (ftpState->old_request)
acce49bf 2068 err->ftp.request = ftpState->old_request;
54220df8 2069 else
acce49bf 2070 err->ftp.request = ftpState->ctrl.last_command;
54220df8 2071 if (ftpState->old_reply)
acce49bf 2072 err->ftp.reply = ftpState->old_reply;
54220df8 2073 else
acce49bf 2074 err->ftp.reply = ftpState->ctrl.last_reply;
54220df8 2075 errorAppendEntry(ftpState->entry, err);
54220df8 2076 storeBufferFlush(ftpState->entry);
2077 comm_close(ftpState->ctrl.fd);
2078}
2079
3fdadc70 2080static void
2081ftpAppendSuccessHeader(FtpStateData * ftpState)
2082{
2083 char *mime_type = NULL;
2084 char *mime_enc = NULL;
02922e76 2085 String urlpath = ftpState->request->urlpath;
2086 const char *filename = NULL;
2087 const char *t = NULL;
3fdadc70 2088 StoreEntry *e = ftpState->entry;
9e975e4e 2089 http_reply *reply = e->mem_obj->reply;
3fdadc70 2090 if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT))
2091 return;
9e975e4e 2092 EBIT_SET(ftpState->flags, FTP_HTTP_HEADER_SENT);
8350fe9b 2093 assert(e->mem_obj->inmem_hi == 0);
02922e76 2094 filename = (t = strRChr(urlpath, '/')) ? t + 1 : strBuf(urlpath);
3fdadc70 2095 if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
2096 mime_type = "text/html";
2097 } else {
31b4984a 2098 switch (ftpState->typecode) {
2099 case 'I':
2100 mime_type = "application/octet-stream";
2101 mime_enc = mimeGetContentEncoding(filename);
2102 break;
2103 case 'A':
2104 mime_type = "text/plain";
2105 break;
2106 default:
2107 mime_type = mimeGetContentType(filename);
2108 mime_enc = mimeGetContentEncoding(filename);
2109 break;
2110 }
3fdadc70 2111 }
438fc1e3 2112 storeBuffer(e);
cb69b4c7 2113 httpReplyReset(reply);
2114 /* set standard stuff */
2115 httpReplySetHeaders(reply, 1.0, HTTP_OK, "Gatewaying",
2116 mime_type, ftpState->size, ftpState->mdtm, -2);
2117 /* additional info */
2118 if (mime_enc)
d8b249ef 2119 httpHeaderPutStr(&reply->header, HDR_CONTENT_ENCODING, mime_enc);
cb69b4c7 2120 httpReplySwapOut(reply, e);
438fc1e3 2121 storeBufferFlush(e);
2acf4be6 2122 reply->hdr_sz = e->mem_obj->inmem_hi;
e6b02cfc 2123 storeTimestampsSet(e);
e6b02cfc 2124 storeSetPublicKey(e);
77a30ebb 2125}
bfcaf585 2126
2127static void
2128ftpAbort(void *data)
2129{
2130 FtpStateData *ftpState = data;
9fb13bb6 2131 debug(9, 2) ("ftpAbort: %s\n", storeUrl(ftpState->entry));
15888cf7 2132 if (ftpState->data.fd >= 0) {
270b86af 2133 comm_close(ftpState->data.fd);
15888cf7 2134 ftpState->data.fd = -1;
2135 }
7b2cdd7b 2136 comm_close(ftpState->ctrl.fd);
bfcaf585 2137}
9b312a19 2138
cb69b4c7 2139static void
ee1679df 2140ftpAuthRequired(HttpReply * old_reply, request_t * request, const char *realm)
cb69b4c7 2141{
2142 ErrorState *err = errorCon(ERR_ACCESS_DENIED, HTTP_UNAUTHORIZED);
2143 HttpReply *rep;
2144 err->request = requestLink(request);
2145 rep = errorBuildReply(err);
cb69b4c7 2146 errorStateFree(err);
63259c34 2147 /* add Authenticate header */
d8b249ef 2148 httpHeaderPutAuth(&rep->header, "Basic", realm);
63259c34 2149 /* move new reply to the old one */
2150 httpReplyAbsorb(old_reply, rep);
cb69b4c7 2151}
8f872bb6 2152
2153char *
2154ftpUrlWith2f(const request_t * request)
2155{
2156 LOCAL_ARRAY(char, buf, MAX_URL);
2157 LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1);
2158 LOCAL_ARRAY(char, portbuf, 32);
2159 char *t;
2160 portbuf[0] = '\0';
23d92c64 2161 if (request->protocol != PROTO_FTP)
2162 return NULL;
8f872bb6 2163 if (request->port != urlDefaultPort(request->protocol))
2164 snprintf(portbuf, 32, ":%d", request->port);
2165 loginbuf[0] = '\0';
69e81830 2166 if ((int) strlen(request->login) > 0) {
8f872bb6 2167 strcpy(loginbuf, request->login);
2168 if ((t = strchr(loginbuf, ':')))
2169 *t = '\0';
2170 strcat(loginbuf, "@");
2171 }
2172 snprintf(buf, MAX_URL, "%s://%s%s%s%s%s",
2173 ProtocolStr[request->protocol],
2174 loginbuf,
2175 request->host,
2176 portbuf,
2177 "/%2f",
02922e76 2178 strBuf(request->urlpath));
8f872bb6 2179 if ((t = strchr(buf, '?')))
2180 *t = '\0';
2181 return buf;
2182}