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