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