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