3 * $Id: ftp.cc,v 1.304 2001/01/07 10:57:15 hno Exp $
5 * DEBUG: section 9 File Transfer Protocol (FTP)
6 * AUTHOR: Harvest Derived
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by the
14 * National Science Foundation. Squid is Copyrighted (C) 1998 by
15 * the Regents of the University of California. Please see the
16 * COPYRIGHT file for full details. Squid incorporates software
17 * developed and/or copyrighted by other sources. Please see the
18 * CREDITS file for full details.
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.
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.
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
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 static const char *const crlf
= "\r\n";
39 static char cbuf
[1024];
64 unsigned int pasv_supported
:1;
65 unsigned int skip_whitespace
:1;
66 unsigned int rest_supported
:1;
67 unsigned int pasv_only
:1;
68 unsigned int authenticated
:1;
69 unsigned int http_header_sent
:1;
70 unsigned int tried_nlst
:1;
71 unsigned int use_base
:1;
72 unsigned int root_dir
:1;
73 unsigned int no_dotdot
:1;
74 unsigned int html_header_sent
:1;
75 unsigned int binary
:1;
76 unsigned int try_slash_hack
:1;
78 unsigned int put_mkdir
:1;
79 unsigned int listformat_unknown
:1;
80 unsigned int datachannel_hack
:1;
83 typedef struct _Ftpdata
{
87 char password
[MAX_URL
];
101 int restarted_offset
;
105 wordlist
*cwd_message
;
130 struct _ftp_flags flags
;
143 typedef void (FTPSM
) (FtpStateData
*);
145 #define FTP_LOGIN_ESCAPED 1
146 #define FTP_LOGIN_NOT_ESCAPED 0
148 /* Local functions */
149 static CNCB ftpPasvCallback
;
150 static PF ftpDataRead
;
151 static PF ftpStateFree
;
152 static PF ftpPumpClosedData
;
153 static PF ftpTimeout
;
154 static PF ftpReadControlReply
;
155 static CWCB ftpWriteCommandCallback
;
156 static void ftpLoginParser(const char *, FtpStateData
*, int escaped
);
157 static wordlist
*ftpParseControlReply(char *, size_t, int *, int *);
158 static int ftpRestartable(FtpStateData
* ftpState
);
159 static void ftpAppendSuccessHeader(FtpStateData
* ftpState
);
160 static void ftpAuthRequired(HttpReply
* reply
, request_t
* request
, const char *realm
);
161 static void ftpHackShortcut(FtpStateData
* ftpState
, FTPSM
* nextState
);
162 static void ftpPutStart(FtpStateData
*);
163 static CWCB ftpPutTransferDone
;
164 static void ftpUnhack(FtpStateData
* ftpState
);
165 static void ftpScheduleReadControlReply(FtpStateData
*, int);
166 static void ftpHandleControlReply(FtpStateData
*);
167 static char *ftpHtmlifyListEntry(char *line
, FtpStateData
* ftpState
);
168 static void ftpFailed(FtpStateData
*, err_type
);
169 static void ftpFailedErrorMessage(FtpStateData
*, err_type
);
172 * State machine functions
173 * send == state transition
174 * read == wait for response, and select next state transition
175 * other == Transition logic
177 static FTPSM ftpReadWelcome
;
178 static FTPSM ftpSendUser
;
179 static FTPSM ftpReadUser
;
180 static FTPSM ftpSendPass
;
181 static FTPSM ftpReadPass
;
182 static FTPSM ftpSendType
;
183 static FTPSM ftpReadType
;
184 static FTPSM ftpSendMdtm
;
185 static FTPSM ftpReadMdtm
;
186 static FTPSM ftpSendSize
;
187 static FTPSM ftpReadSize
;
188 static FTPSM ftpSendPort
;
189 static FTPSM ftpReadPort
;
190 static FTPSM ftpSendPasv
;
191 static FTPSM ftpReadPasv
;
192 static FTPSM ftpTraverseDirectory
;
193 static FTPSM ftpListDir
;
194 static FTPSM ftpGetFile
;
195 static FTPSM ftpSendCwd
;
196 static FTPSM ftpReadCwd
;
197 static FTPSM ftpSendList
;
198 static FTPSM ftpSendNlst
;
199 static FTPSM ftpReadList
;
200 static FTPSM ftpSendRest
;
201 static FTPSM ftpReadRest
;
202 static FTPSM ftpSendRetr
;
203 static FTPSM ftpReadRetr
;
204 static FTPSM ftpReadTransferDone
;
205 static FTPSM ftpSendQuit
;
206 static FTPSM ftpReadQuit
;
207 static FTPSM ftpFail
;
208 static FTPSM ftpDataTransferDone
;
209 static FTPSM ftpRestOrList
;
210 static FTPSM ftpSendStor
;
211 static FTPSM ftpReadStor
;
212 static FTPSM ftpSendReply
;
213 static FTPSM ftpTryMkdir
;
214 static FTPSM ftpReadMkdir
;
215 /************************************************
216 ** State Machine Description (excluding hacks) **
217 *************************************************
219 ---------------------------------------
223 Type TraverseDirectory / GetFile
224 TraverseDirectory Cwd / GetFile / ListDir
225 Cwd TraverseDirectory
231 RestOrList Rest / Retr / Nlst / List
233 Retr / Nlst / List (ftpDataRead on datachannel)
234 (ftpDataRead) ReadTransferDone
235 ReadTransferDone DataTransferDone
236 DataTransferDone Quit
238 ************************************************/
240 FTPSM
*FTP_SM_FUNCS
[] =
242 ftpReadWelcome
, /* BEGIN */
243 ftpReadUser
, /* SENT_USER */
244 ftpReadPass
, /* SENT_PASS */
245 ftpReadType
, /* SENT_TYPE */
246 ftpReadMdtm
, /* SENT_MDTM */
247 ftpReadSize
, /* SENT_SIZE */
248 ftpReadPort
, /* SENT_PORT */
249 ftpReadPasv
, /* SENT_PASV */
250 ftpReadCwd
, /* SENT_CWD */
251 ftpReadList
, /* SENT_LIST */
252 ftpReadList
, /* SENT_NLST */
253 ftpReadRest
, /* SENT_REST */
254 ftpReadRetr
, /* SENT_RETR */
255 ftpReadStor
, /* SENT_STOR */
256 ftpReadQuit
, /* SENT_QUIT */
257 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
258 ftpSendReply
, /* WRITING_DATA (STOR) */
259 ftpReadMkdir
/* SENT_MKDIR */
263 ftpStateFree(int fdnotused
, void *data
)
265 FtpStateData
*ftpState
= data
;
266 if (ftpState
== NULL
)
268 debug(9, 3) ("ftpStateFree: %s\n", storeUrl(ftpState
->entry
));
269 storeUnregisterAbort(ftpState
->entry
);
270 storeUnlockObject(ftpState
->entry
);
271 if (ftpState
->reply_hdr
) {
272 memFree(ftpState
->reply_hdr
, MEM_8K_BUF
);
273 /* this seems unnecessary, but people report SEGV's
274 * when freeing memory in this function */
275 ftpState
->reply_hdr
= NULL
;
277 requestUnlink(ftpState
->request
);
278 if (ftpState
->ctrl
.buf
) {
279 ftpState
->ctrl
.freefunc(ftpState
->ctrl
.buf
);
280 /* this seems unnecessary, but people report SEGV's
281 * when freeing memory in this function */
282 ftpState
->ctrl
.buf
= NULL
;
284 if (ftpState
->data
.buf
) {
285 ftpState
->data
.freefunc(ftpState
->data
.buf
);
286 /* this seems unnecessary, but people report SEGV's
287 * when freeing memory in this function */
288 ftpState
->data
.buf
= NULL
;
290 if (ftpState
->pathcomps
)
291 wordlistDestroy(&ftpState
->pathcomps
);
292 if (ftpState
->ctrl
.message
)
293 wordlistDestroy(&ftpState
->ctrl
.message
);
294 if (ftpState
->cwd_message
)
295 wordlistDestroy(&ftpState
->cwd_message
);
296 safe_free(ftpState
->ctrl
.last_reply
);
297 safe_free(ftpState
->ctrl
.last_command
);
298 safe_free(ftpState
->old_request
);
299 safe_free(ftpState
->old_reply
);
300 safe_free(ftpState
->old_filepath
);
301 safe_free(ftpState
->title_url
);
302 safe_free(ftpState
->base_href
);
303 safe_free(ftpState
->filepath
);
304 safe_free(ftpState
->data
.host
);
305 if (ftpState
->data
.fd
> -1) {
306 comm_close(ftpState
->data
.fd
);
307 ftpState
->data
.fd
= -1;
309 cbdataFree(ftpState
);
313 ftpLoginParser(const char *login
, FtpStateData
* ftpState
, int escaped
)
316 xstrncpy(ftpState
->user
, login
, MAX_URL
);
317 if ((s
= strchr(ftpState
->user
, ':'))) {
319 xstrncpy(ftpState
->password
, s
+ 1, MAX_URL
);
321 rfc1738_unescape(ftpState
->password
);
322 ftpState
->password_url
= 1;
324 xstrncpy(ftpState
->password
, null_string
, MAX_URL
);
327 rfc1738_unescape(ftpState
->user
);
328 if (ftpState
->user
[0] || ftpState
->password
[0])
330 xstrncpy(ftpState
->user
, "anonymous", MAX_URL
);
331 xstrncpy(ftpState
->password
, Config
.Ftp
.anon_user
, MAX_URL
);
335 ftpTimeout(int fd
, void *data
)
337 FtpStateData
*ftpState
= data
;
338 StoreEntry
*entry
= ftpState
->entry
;
339 debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd
, storeUrl(entry
));
340 if (SENT_PASV
== ftpState
->state
&& fd
== ftpState
->data
.fd
) {
341 /* stupid ftp.netscape.com */
342 ftpState
->fwd
->flags
.dont_retry
= 0;
343 ftpState
->fwd
->flags
.ftp_pasv_failed
= 1;
344 debug(9, 1) ("ftpTimeout: timeout in SENT_PASV state\n");
346 ftpFailed(ftpState
, ERR_READ_TIMEOUT
);
347 /* ftpFailed closes ctrl.fd and frees ftpState */
351 ftpListingStart(FtpStateData
* ftpState
)
353 StoreEntry
*e
= ftpState
->entry
;
359 storeAppendPrintf(e
, "<!-- HTML listing generated by Squid %s -->\n",
361 storeAppendPrintf(e
, "<!-- %s -->\n", mkrfc1123(squid_curtime
));
362 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>\n");
363 storeAppendPrintf(e
, "FTP Directory: %s\n",
364 html_quote(ftpState
->title_url
));
365 storeAppendPrintf(e
, "</TITLE>\n");
366 if (ftpState
->flags
.use_base
)
367 storeAppendPrintf(e
, "<BASE HREF=\"%s\">\n",
368 html_quote(ftpState
->base_href
));
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
)
373 storeAppendPrintf(e
, "%s\n", html_quote(w
->key
));
374 storeAppendPrintf(e
, "</PRE>\n");
375 storeAppendPrintf(e
, "<HR>\n");
376 wordlistDestroy(&ftpState
->cwd_message
);
378 storeAppendPrintf(e
, "<H2>\n");
379 storeAppendPrintf(e
, "FTP Directory: ");
380 /* "ftp://" == 6 characters */
381 assert(strlen(ftpState
->title_url
) >= 6);
382 title
= html_quote(ftpState
->title_url
);
383 for (i
= 6, j
= 0; title
[i
]; j
= i
) {
384 storeAppendPrintf(e
, "<A HREF=\"");
385 i
+= strcspn(&title
[i
], "/");
388 for (k
= 0; k
< i
; k
++)
389 storeAppendPrintf(e
, "%c", title
[k
]);
390 storeAppendPrintf(e
, "\">");
391 for (k
= j
; k
< i
- 1; k
++)
392 storeAppendPrintf(e
, "%c", title
[k
]);
393 if (ftpState
->title_url
[k
] != '/')
394 storeAppendPrintf(e
, "%c", title
[k
++]);
395 storeAppendPrintf(e
, "</A>");
397 storeAppendPrintf(e
, "%c", title
[k
++]);
399 /* Error guard, or "assert" */
400 storeAppendPrintf(e
, "ERROR: Failed to parse URL: %s\n",
401 html_quote(ftpState
->title_url
));
402 debug(9, 0) ("Failed to parse URL: %s\n", ftpState
->title_url
);
406 storeAppendPrintf(e
, "</H2>\n");
407 storeAppendPrintf(e
, "<PRE>\n");
408 dirup
= ftpHtmlifyListEntry("<internal-dirup>", ftpState
);
409 storeAppend(e
, dirup
, strlen(dirup
));
411 ftpState
->flags
.html_header_sent
= 1;
415 ftpListingFinish(FtpStateData
* ftpState
)
417 StoreEntry
*e
= ftpState
->entry
;
419 storeAppendPrintf(e
, "</PRE>\n");
420 if (ftpState
->flags
.listformat_unknown
&& !ftpState
->flags
.tried_nlst
) {
421 storeAppendPrintf(e
, "<A HREF=\"./;type=d\">[As plain directory]</A>\n");
422 } else if (ftpState
->typecode
== 'D') {
423 storeAppendPrintf(e
, "<A HREF=\"./\">[As extended directory]</A>\n");
425 storeAppendPrintf(e
, "<HR>\n");
426 storeAppendPrintf(e
, "<ADDRESS>\n");
427 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
428 mkrfc1123(squid_curtime
),
430 full_appname_string
);
431 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
435 static const char *Month
[] =
437 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
438 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
442 is_month(const char *buf
)
445 for (i
= 0; i
< 12; i
++)
446 if (!strcasecmp(buf
, Month
[i
]))
453 ftpListPartsFree(ftpListParts
** parts
)
455 safe_free((*parts
)->date
);
456 safe_free((*parts
)->name
);
457 safe_free((*parts
)->showname
);
458 safe_free((*parts
)->link
);
462 #define MAX_TOKENS 64
464 #define SCAN_FTP1 "%[0123456789]"
465 #define SCAN_FTP2 "%[0123456789:]"
466 #define SCAN_FTP3 "%[0123456789]-%[0123456789]-%[0123456789]"
467 #define SCAN_FTP4 "%[0123456789]:%[0123456789]%[AaPp]%[Mm]"
469 static ftpListParts
*
470 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
472 ftpListParts
*p
= NULL
;
474 const char *ct
= NULL
;
475 char *tokens
[MAX_TOKENS
];
478 static char sbuf
[128];
484 p
= xcalloc(1, sizeof(ftpListParts
));
486 for (i
= 0; i
< MAX_TOKENS
; i
++)
487 tokens
[i
] = (char *) NULL
;
489 if (flags
.tried_nlst
) {
490 /* Machine readable format, one name per line */
495 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
))
496 tokens
[n_tokens
++] = xstrdup(t
);
498 /* locate the Month field */
499 for (i
= 3; i
< n_tokens
- 2; i
++) {
500 if (!is_month(tokens
[i
])) /* Month */
502 if (!sscanf(tokens
[i
- 1], SCAN_FTP1
, sbuf
)) /* Size */
504 if (!sscanf(tokens
[i
+ 1], SCAN_FTP1
, sbuf
)) /* Day */
506 if (!sscanf(tokens
[i
+ 2], SCAN_FTP2
, sbuf
)) /* Yr | hh:mm */
508 p
->type
= *tokens
[0];
509 p
->size
= atoi(tokens
[i
- 1]);
510 snprintf(sbuf
, 128, "%s %2s %5s",
511 tokens
[i
], tokens
[i
+ 1], tokens
[i
+ 2]);
512 if (!strstr(buf
, sbuf
))
513 snprintf(sbuf
, 128, "%s %2s %-5s",
514 tokens
[i
], tokens
[i
+ 1], tokens
[i
+ 2]);
515 if ((t
= strstr(buf
, sbuf
))) {
516 p
->date
= xstrdup(sbuf
);
517 if (flags
.skip_whitespace
) {
519 while (strchr(w_space
, *t
))
522 /* XXX assumes a single space between date and filename
523 * suggested by: Nathan.Bailey@cc.monash.edu.au and
524 * Mike Battersby <mike@starbug.bofh.asn.au> */
525 t
+= strlen(sbuf
) + 1;
527 p
->name
= xstrdup(t
);
528 if ((t
= strstr(p
->name
, " -> "))) {
530 p
->link
= xstrdup(t
+ 4);
535 /* try it as a DOS listing */
536 if (n_tokens
> 3 && p
->name
== NULL
&&
537 sscanf(tokens
[0], SCAN_FTP3
, sbuf
, sbuf
, sbuf
) == 3 &&
539 sscanf(tokens
[1], SCAN_FTP4
, sbuf
, sbuf
, sbuf
, sbuf
) == 4) {
541 if (!strcasecmp(tokens
[2], "<dir>")) {
545 p
->size
= atoi(tokens
[2]);
547 snprintf(sbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
548 p
->date
= xstrdup(sbuf
);
549 if (p
->type
== 'd') {
550 /* Directory.. name begins with first printable after <dir> */
551 ct
= strstr(buf
, tokens
[2]);
552 ct
+= strlen(tokens
[2]);
553 while (xisspace(*ct
))
558 /* A file. Name begins after size, with a space in between */
559 snprintf(sbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
560 ct
= strstr(buf
, sbuf
);
562 ct
+= strlen(tokens
[2]) + 2;
565 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
567 /* Try EPLF format; carson@lehman.com */
568 if (p
->name
== NULL
&& buf
[0] == '+') {
576 sscanf(ct
+ 1, "%[^,]", sbuf
);
577 p
->name
= xstrdup(sbuf
);
580 sscanf(ct
+ 1, "%d", &(p
->size
));
583 if (1 != sscanf(ct
+ 1, "%ld", <
))
586 p
->date
= xstrdup(ctime(&t
));
587 *(strstr(p
->date
, "\n")) = '\0';
600 ct
= strstr(ct
, ",");
609 for (i
= 0; i
< n_tokens
; i
++)
612 ftpListPartsFree(&p
);
617 dots_fill(size_t len
)
619 static char buf
[256];
621 if (len
> Config
.Ftp
.list_width
) {
622 memset(buf
, ' ', 256);
624 buf
[Config
.Ftp
.list_width
+ 4] = '\0';
627 for (i
= (int) len
; i
< Config
.Ftp
.list_width
; i
++)
628 buf
[i
- len
] = (i
% 2) ? '.' : ' ';
634 ftpHtmlifyListEntry(char *line
, FtpStateData
* ftpState
)
636 LOCAL_ARRAY(char, icon
, 2048);
637 LOCAL_ARRAY(char, href
, 2048 + 40);
638 LOCAL_ARRAY(char, text
, 2048);
639 LOCAL_ARRAY(char, size
, 2048);
640 LOCAL_ARRAY(char, chdir
, 2048 + 40);
641 LOCAL_ARRAY(char, view
, 2048 + 40);
642 LOCAL_ARRAY(char, download
, 2048 + 40);
643 LOCAL_ARRAY(char, link
, 2048 + 40);
644 LOCAL_ARRAY(char, html
, 8192);
645 size_t width
= Config
.Ftp
.list_width
;
647 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= *html
= '\0';
648 if ((int) strlen(line
) > 1024) {
649 snprintf(html
, 8192, "%s\n", line
);
652 /* Handle builtin <dirup> */
653 if (strcmp(line
, "<internal-dirup>") == 0) {
654 /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> {link} */
655 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
656 mimeGetIconURL("internal-dirup"),
658 if (!ftpState
->flags
.no_dotdot
&& !ftpState
->flags
.root_dir
) {
659 /* Normal directory */
661 strcpy(text
, "Parent Directory");
662 } else if (!ftpState
->flags
.no_dotdot
&& ftpState
->flags
.root_dir
) {
663 /* "Top level" directory */
664 strcpy(href
, "%2e%2e/");
665 strcpy(text
, "Parent Directory");
666 snprintf(link
, 2048, "(<A HREF=\"%s\">%s</A>)",
669 } else if (ftpState
->flags
.no_dotdot
&& !ftpState
->flags
.root_dir
) {
670 /* Normal directory where last component is / or .. */
671 strcpy(href
, "%2e%2e/");
672 strcpy(text
, "Parent Directory");
673 snprintf(link
, 2048, "(<A HREF=\"%s\">%s</A>)",
676 } else { /* NO_DOTDOT && ROOT_DIR */
677 /* "UNIX Root" directory */
679 strcpy(text
, "Home Directory");
681 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A> %s\n",
682 href
, icon
, href
, text
, link
);
685 if ((parts
= ftpListParseParts(line
, ftpState
->flags
)) == NULL
) {
687 snprintf(html
, 8192, "%s\n", line
);
688 for (p
= line
; *p
&& xisspace(*p
); p
++);
689 if (*p
&& !xisspace(*p
))
690 ftpState
->flags
.listformat_unknown
= 1;
693 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
695 ftpListPartsFree(&parts
);
700 parts
->showname
= xstrdup(parts
->name
);
701 if (!Config
.Ftp
.list_wrap
) {
702 if (strlen(parts
->showname
) > width
- 1) {
703 *(parts
->showname
+ width
- 1) = '>';
704 *(parts
->showname
+ width
- 0) = '\0';
707 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
708 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
709 xstrncpy(text
, parts
->showname
, 2048);
710 switch (parts
->type
) {
712 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
713 mimeGetIconURL("internal-dir"),
715 strncat(href
, "/", 2048);
718 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
719 mimeGetIconURL("internal-link"),
721 /* sometimes there is an 'l' flag, but no "->" link */
723 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
724 snprintf(link
, 2048, " -> <A HREF=\"%s\">%s</A>",
726 html_quote(parts
->link
));
731 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
732 mimeGetIconURL(parts
->name
),
734 snprintf(chdir
, 2048, " <A HREF=\"%s/;type=d\"><IMG BORDER=0 SRC=\"%s\" "
735 "ALT=\"[DIR]\"></A>",
736 rfc1738_escape_part(parts
->name
),
737 mimeGetIconURL("internal-dir"));
741 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
742 mimeGetIconURL(parts
->name
),
744 snprintf(size
, 2048, " %6dk", parts
->size
);
747 if (parts
->type
!= 'd') {
748 if (mimeGetViewOption(parts
->name
)) {
749 snprintf(view
, 2048, " <A HREF=\"%s;type=a\"><IMG BORDER=0 SRC=\"%s\" "
750 "ALT=\"[VIEW]\"></A>",
751 href
, mimeGetIconURL("internal-view"));
753 if (mimeGetDownloadOption(parts
->name
)) {
754 snprintf(download
, 2048, " <A HREF=\"%s;type=i\"><IMG BORDER=0 SRC=\"%s\" "
755 "ALT=\"[DOWNLOAD]\"></A>",
756 href
, mimeGetIconURL("internal-download"));
759 /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> . . . {date}{size}{chdir}{view}{download}{link}\n */
760 if (parts
->type
!= '\0') {
761 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A>%s "
763 href
, icon
, href
, html_quote(text
), dots_fill(strlen(text
)),
764 parts
->date
, size
, chdir
, view
, download
, link
);
766 /* Plain listing. {icon} {text} ... {chdir}{view}{download} */
767 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A>%s "
769 href
, icon
, href
, html_quote(text
), dots_fill(strlen(text
)),
770 chdir
, view
, download
, link
);
772 ftpListPartsFree(&parts
);
777 ftpParseListing(FtpStateData
* ftpState
)
779 char *buf
= ftpState
->data
.buf
;
780 char *sbuf
; /* NULL-terminated copy of buf */
787 StoreEntry
*e
= ftpState
->entry
;
788 int len
= ftpState
->data
.offset
;
790 * We need a NULL-terminated buffer for scanning, ick
792 sbuf
= xmalloc(len
+ 1);
793 xstrncpy(sbuf
, buf
, len
+ 1);
794 end
= sbuf
+ len
- 1;
795 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
798 debug(9, 3) ("ftpParseListing: usable = %d\n", usable
);
800 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e
));
804 debug(9, 3) ("ftpParseListing: %d bytes to play with\n", len
);
805 line
= memAllocate(MEM_4K_BUF
);
809 s
+= strspn(s
, crlf
);
810 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
811 debug(9, 3) ("ftpParseListing: s = {%s}\n", s
);
812 linelen
= strcspn(s
, crlf
) + 1;
817 xstrncpy(line
, s
, linelen
);
818 debug(9, 7) ("ftpParseListing: {%s}\n", line
);
819 if (!strncmp(line
, "total", 5))
821 t
= ftpHtmlifyListEntry(line
, ftpState
);
823 storeAppend(e
, t
, strlen(t
));
826 assert(usable
<= len
);
828 /* must copy partial line to beginning of buf */
829 linelen
= len
- usable
;
832 xstrncpy(line
, end
, linelen
);
833 xstrncpy(ftpState
->data
.buf
, line
, ftpState
->data
.size
);
834 ftpState
->data
.offset
= strlen(ftpState
->data
.buf
);
836 memFree(line
, MEM_4K_BUF
);
841 ftpReadComplete(FtpStateData
* ftpState
)
843 debug(9, 3) ("ftpReadComplete\n");
844 /* Connection closed; retrieval done. */
845 if (ftpState
->data
.fd
> -1) {
847 * close data socket so it does not occupy resources while
850 comm_close(ftpState
->data
.fd
);
851 ftpState
->data
.fd
= -1;
853 /* expect the "transfer complete" message on the control socket */
854 ftpScheduleReadControlReply(ftpState
, 1);
858 ftpDataRead(int fd
, void *data
)
860 FtpStateData
*ftpState
= data
;
864 StoreEntry
*entry
= ftpState
->entry
;
865 MemObject
*mem
= entry
->mem_obj
;
868 delay_id delay_id
= delayMostBytesAllowed(mem
);
870 assert(fd
== ftpState
->data
.fd
);
871 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
872 comm_close(ftpState
->ctrl
.fd
);
876 read_sz
= ftpState
->data
.size
- ftpState
->data
.offset
;
878 read_sz
= delayBytesWanted(delay_id
, 1, read_sz
);
880 memset(ftpState
->data
.buf
+ ftpState
->data
.offset
, '\0', read_sz
);
881 statCounter
.syscalls
.sock
.reads
++;
882 len
= read(fd
, ftpState
->data
.buf
+ ftpState
->data
.offset
, read_sz
);
884 fd_bytes(fd
, len
, FD_READ
);
886 delayBytesIn(delay_id
, len
);
888 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
889 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, len
);
890 ftpState
->data
.offset
+= len
;
892 debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd
, len
);
895 for (j
= len
- 1, bin
= 0; j
; bin
++)
897 IOStats
.Ftp
.read_hist
[bin
]++;
899 if (ftpState
->flags
.isdir
&& !ftpState
->flags
.html_header_sent
&& len
>= 0) {
900 ftpListingStart(ftpState
);
903 debug(50, ignoreErrno(errno
) ? 3 : 1) ("ftpDataRead: read error: %s\n", xstrerror());
904 if (ignoreErrno(errno
)) {
909 Config
.Timeout
.read
);
911 ftpFailed(ftpState
, ERR_READ_ERROR
);
912 /* ftpFailed closes ctrl.fd and frees ftpState */
915 } else if (len
== 0) {
916 ftpReadComplete(ftpState
);
918 if (ftpState
->flags
.isdir
) {
919 ftpParseListing(ftpState
);
921 storeAppend(entry
, ftpState
->data
.buf
, len
);
922 ftpState
->data
.offset
= 0;
924 if (ftpState
->size
> 0 && mem
->inmem_hi
>= ftpState
->size
+ mem
->reply
->hdr_sz
)
925 ftpReadComplete(ftpState
);
931 Config
.Timeout
.read
);
938 * Return 1 if we have everything needed to complete this request.
939 * Return 0 if something is missing.
942 ftpCheckAuth(FtpStateData
* ftpState
, const HttpHeader
* req_hdr
)
946 ftpLoginParser(ftpState
->request
->login
, ftpState
, FTP_LOGIN_ESCAPED
);
947 if (!ftpState
->user
[0])
948 return 1; /* no name */
949 if (ftpState
->password_url
|| ftpState
->password
[0])
950 return 1; /* passwd provided in URL */
951 /* URL has name, but no passwd */
952 if (!(auth
= httpHeaderGetAuth(req_hdr
, HDR_AUTHORIZATION
, "Basic")))
953 return 0; /* need auth header */
954 ftpState
->flags
.authenticated
= 1;
955 orig_user
= xstrdup(ftpState
->user
);
956 ftpLoginParser(auth
, ftpState
, FTP_LOGIN_NOT_ESCAPED
);
957 if (!strcmp(orig_user
, ftpState
->user
)) {
959 return 1; /* same username */
961 strcpy(ftpState
->user
, orig_user
);
963 return 0; /* different username */
967 ftpCheckUrlpath(FtpStateData
* ftpState
)
969 request_t
*request
= ftpState
->request
;
972 if ((t
= strRChr(request
->urlpath
, ';')) != NULL
) {
973 if (strncasecmp(t
+ 1, "type=", 5) == 0) {
974 ftpState
->typecode
= (char) toupper((int) *(t
+ 6));
975 strCutPtr(request
->urlpath
, t
);
978 l
= strLen(request
->urlpath
);
979 ftpState
->flags
.use_base
= 1;
980 /* check for null path */
982 ftpState
->flags
.isdir
= 1;
983 ftpState
->flags
.root_dir
= 1;
984 } else if (!strCmp(request
->urlpath
, "/%2f/")) {
985 /* UNIX root directory */
986 ftpState
->flags
.use_base
= 0;
987 ftpState
->flags
.isdir
= 1;
988 ftpState
->flags
.root_dir
= 1;
989 } else if ((l
>= 1) && (*(strBuf(request
->urlpath
) + l
- 1) == '/')) {
990 /* Directory URL, ending in / */
991 ftpState
->flags
.isdir
= 1;
992 ftpState
->flags
.use_base
= 0;
994 ftpState
->flags
.root_dir
= 1;
999 ftpBuildTitleUrl(FtpStateData
* ftpState
)
1001 request_t
*request
= ftpState
->request
;
1005 + strlen(ftpState
->user
)
1006 + strlen(ftpState
->password
)
1007 + strlen(request
->host
)
1008 + strLen(request
->urlpath
);
1009 t
= ftpState
->title_url
= xcalloc(len
, 1);
1010 strcat(t
, "ftp://");
1011 if (strcmp(ftpState
->user
, "anonymous")) {
1012 strcat(t
, ftpState
->user
);
1015 strcat(t
, request
->host
);
1016 if (request
->port
!= urlDefaultPort(PROTO_FTP
))
1017 snprintf(&t
[strlen(t
)], len
- strlen(t
), ":%d", request
->port
);
1018 strcat(t
, strBuf(request
->urlpath
));
1019 t
= ftpState
->base_href
= xcalloc(len
, 1);
1020 strcat(t
, "ftp://");
1021 if (strcmp(ftpState
->user
, "anonymous")) {
1022 strcat(t
, rfc1738_escape_part(ftpState
->user
));
1023 if (ftpState
->password_url
) {
1025 strcat(t
, rfc1738_escape_part(ftpState
->password
));
1029 strcat(t
, request
->host
);
1030 if (request
->port
!= urlDefaultPort(PROTO_FTP
))
1031 snprintf(&t
[strlen(t
)], len
- strlen(t
), ":%d", request
->port
);
1032 strcat(t
, strBuf(request
->urlpath
));
1036 CBDATA_TYPE(FtpStateData
);
1038 ftpStart(FwdState
* fwd
)
1040 request_t
*request
= fwd
->request
;
1041 StoreEntry
*entry
= fwd
->entry
;
1042 int fd
= fwd
->server_fd
;
1043 LOCAL_ARRAY(char, realm
, 8192);
1044 const char *url
= storeUrl(entry
);
1045 FtpStateData
*ftpState
;
1048 CBDATA_INIT_TYPE(FtpStateData
);
1049 ftpState
= CBDATA_ALLOC(FtpStateData
, NULL
);
1050 debug(9, 3) ("ftpStart: '%s'\n", url
);
1051 statCounter
.server
.all
.requests
++;
1052 statCounter
.server
.ftp
.requests
++;
1053 storeLockObject(entry
);
1054 ftpState
->entry
= entry
;
1055 ftpState
->request
= requestLink(request
);
1056 ftpState
->ctrl
.fd
= fd
;
1057 ftpState
->data
.fd
= -1;
1058 ftpState
->size
= -1;
1059 ftpState
->mdtm
= -1;
1060 if (!Config
.Ftp
.passive
)
1061 ftpState
->flags
.rest_supported
= 0;
1062 else if (fwd
->flags
.ftp_pasv_failed
)
1063 ftpState
->flags
.pasv_supported
= 0;
1065 ftpState
->flags
.pasv_supported
= 1;
1066 ftpState
->flags
.rest_supported
= 1;
1067 ftpState
->fwd
= fwd
;
1068 comm_add_close_handler(fd
, ftpStateFree
, ftpState
);
1069 if (ftpState
->request
->method
== METHOD_PUT
)
1070 ftpState
->flags
.put
= 1;
1071 if (!ftpCheckAuth(ftpState
, &request
->header
)) {
1072 /* This request is not fully authenticated */
1073 if (request
->port
== 21) {
1074 snprintf(realm
, 8192, "ftp %s", ftpState
->user
);
1076 snprintf(realm
, 8192, "ftp %s port %d",
1077 ftpState
->user
, request
->port
);
1080 reply
= entry
->mem_obj
->reply
;
1081 assert(reply
!= NULL
);
1082 /* create appropriate reply */
1083 ftpAuthRequired(reply
, request
, realm
);
1084 httpReplySwapOut(reply
, entry
);
1085 fwdComplete(ftpState
->fwd
);
1089 ftpCheckUrlpath(ftpState
);
1090 ftpBuildTitleUrl(ftpState
);
1091 debug(9, 5) ("ftpStart: host=%s, path=%s, user=%s, passwd=%s\n",
1092 ftpState
->request
->host
, strBuf(ftpState
->request
->urlpath
),
1093 ftpState
->user
, ftpState
->password
);
1094 ftpState
->state
= BEGIN
;
1095 ftpState
->ctrl
.last_command
= xstrdup("Connect to server");
1096 ftpState
->ctrl
.buf
= memAllocate(MEM_4K_BUF
);
1097 ftpState
->ctrl
.freefunc
= memFree4K
;
1098 ftpState
->ctrl
.size
= 4096;
1099 ftpState
->ctrl
.offset
= 0;
1100 ftpState
->data
.buf
= xmalloc(SQUID_TCP_SO_RCVBUF
);
1101 ftpState
->data
.size
= SQUID_TCP_SO_RCVBUF
;
1102 ftpState
->data
.freefunc
= xfree
;
1103 ftpScheduleReadControlReply(ftpState
, 0);
1106 /* ====================================================================== */
1109 ftpWriteCommand(const char *buf
, FtpStateData
* ftpState
)
1111 debug(9, 5) ("ftpWriteCommand: %s\n", buf
);
1112 safe_free(ftpState
->ctrl
.last_command
);
1113 safe_free(ftpState
->ctrl
.last_reply
);
1114 ftpState
->ctrl
.last_command
= xstrdup(buf
);
1115 comm_write(ftpState
->ctrl
.fd
,
1118 ftpWriteCommandCallback
,
1121 ftpScheduleReadControlReply(ftpState
, 0);
1125 ftpWriteCommandCallback(int fd
, char *bufnotused
, size_t size
, int errflag
, void *data
)
1127 FtpStateData
*ftpState
= data
;
1128 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size
);
1130 fd_bytes(fd
, size
, FD_WRITE
);
1131 kb_incr(&statCounter
.server
.all
.kbytes_out
, size
);
1132 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, size
);
1134 if (errflag
== COMM_ERR_CLOSING
)
1137 debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd
, xstrerror());
1138 ftpFailed(ftpState
, ERR_WRITE_ERROR
);
1139 /* ftpFailed closes ctrl.fd and frees ftpState */
1145 ftpParseControlReply(char *buf
, size_t len
, int *codep
, int *used
)
1152 wordlist
*head
= NULL
;
1154 wordlist
**tail
= &head
;
1158 debug(9, 5) ("ftpParseControlReply\n");
1160 * We need a NULL-terminated buffer for scanning, ick
1162 sbuf
= xmalloc(len
+ 1);
1163 xstrncpy(sbuf
, buf
, len
+ 1);
1164 end
= sbuf
+ len
- 1;
1165 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1167 usable
= end
- sbuf
;
1168 debug(9, 3) ("ftpParseControlReply: usable = %d\n", usable
);
1170 debug(9, 3) ("ftpParseControlReply: didn't find end of line\n");
1174 debug(9, 3) ("ftpParseControlReply: %d bytes to play with\n", len
);
1177 s
+= strspn(s
, crlf
);
1178 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1181 debug(9, 3) ("ftpParseControlReply: s = {%s}\n", s
);
1182 linelen
= strcspn(s
, crlf
) + 1;
1186 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1191 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1193 list
= memAllocate(MEM_WORDLIST
);
1194 list
->key
= xmalloc(linelen
- offset
);
1195 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1196 debug(9, 7) ("%d %s\n", code
, list
->key
);
1200 *used
= (int) (s
- sbuf
);
1203 wordlistDestroy(&head
);
1210 ftpScheduleReadControlReply(FtpStateData
* ftpState
, int buffered_ok
)
1212 debug(9, 3) ("ftpScheduleReadControlReply: FD %d\n", ftpState
->ctrl
.fd
);
1213 if (buffered_ok
&& ftpState
->ctrl
.offset
> 0) {
1214 /* We've already read some reply data */
1215 ftpHandleControlReply(ftpState
);
1217 commSetSelect(ftpState
->ctrl
.fd
,
1219 ftpReadControlReply
,
1221 Config
.Timeout
.read
);
1223 * Cancel the timeout on the Data socket (if any) and
1224 * establish one on the control socket.
1226 if (ftpState
->data
.fd
> -1)
1227 commSetTimeout(ftpState
->data
.fd
, -1, NULL
, NULL
);
1228 commSetTimeout(ftpState
->ctrl
.fd
, Config
.Timeout
.read
, ftpTimeout
,
1234 ftpReadControlReply(int fd
, void *data
)
1236 FtpStateData
*ftpState
= data
;
1237 StoreEntry
*entry
= ftpState
->entry
;
1239 debug(9, 5) ("ftpReadControlReply\n");
1240 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1241 comm_close(ftpState
->ctrl
.fd
);
1244 assert(ftpState
->ctrl
.offset
< ftpState
->ctrl
.size
);
1245 statCounter
.syscalls
.sock
.reads
++;
1247 ftpState
->ctrl
.buf
+ ftpState
->ctrl
.offset
,
1248 ftpState
->ctrl
.size
- ftpState
->ctrl
.offset
);
1250 fd_bytes(fd
, len
, FD_READ
);
1251 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
1252 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, len
);
1254 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd
, len
);
1256 debug(50, ignoreErrno(errno
) ? 3 : 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
1257 if (ignoreErrno(errno
)) {
1258 ftpScheduleReadControlReply(ftpState
, 0);
1260 ftpFailed(ftpState
, ERR_READ_ERROR
);
1261 /* ftpFailed closes ctrl.fd and frees ftpState */
1267 if (entry
->store_status
== STORE_PENDING
) {
1268 ftpFailed(ftpState
, ERR_FTP_FAILURE
);
1269 /* ftpFailed closes ctrl.fd and frees ftpState */
1272 comm_close(ftpState
->ctrl
.fd
);
1275 len
+= ftpState
->ctrl
.offset
;
1276 ftpState
->ctrl
.offset
= len
;
1277 assert(len
<= ftpState
->ctrl
.size
);
1278 ftpHandleControlReply(ftpState
);
1282 ftpHandleControlReply(FtpStateData
* ftpState
)
1287 wordlistDestroy(&ftpState
->ctrl
.message
);
1288 ftpState
->ctrl
.message
= ftpParseControlReply(ftpState
->ctrl
.buf
,
1289 ftpState
->ctrl
.offset
, &ftpState
->ctrl
.replycode
, &bytes_used
);
1290 if (ftpState
->ctrl
.message
== NULL
) {
1291 /* didn't get complete reply yet */
1292 if (ftpState
->ctrl
.offset
== ftpState
->ctrl
.size
) {
1293 oldbuf
= ftpState
->ctrl
.buf
;
1294 ftpState
->ctrl
.buf
= xcalloc(ftpState
->ctrl
.size
<< 1, 1);
1295 xmemcpy(ftpState
->ctrl
.buf
, oldbuf
, ftpState
->ctrl
.size
);
1296 ftpState
->ctrl
.size
<<= 1;
1297 ftpState
->ctrl
.freefunc(oldbuf
);
1298 ftpState
->ctrl
.freefunc
= xfree
;
1300 ftpScheduleReadControlReply(ftpState
, 0);
1302 } else if (ftpState
->ctrl
.offset
== bytes_used
) {
1303 /* used it all up */
1304 ftpState
->ctrl
.offset
= 0;
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
);
1312 /* Move the last line of the reply message to ctrl.last_reply */
1313 for (W
= &ftpState
->ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1314 safe_free(ftpState
->ctrl
.last_reply
);
1315 ftpState
->ctrl
.last_reply
= xstrdup((*W
)->key
);
1317 /* Copy the rest of the message to cwd_message to be printed in
1320 wordlistAddWl(&ftpState
->cwd_message
, ftpState
->ctrl
.message
);
1321 debug(9, 8) ("ftpHandleControlReply: state=%d, code=%d\n", ftpState
->state
,
1322 ftpState
->ctrl
.replycode
);
1323 FTP_SM_FUNCS
[ftpState
->state
] (ftpState
);
1326 /* ====================================================================== */
1329 ftpReadWelcome(FtpStateData
* ftpState
)
1331 int code
= ftpState
->ctrl
.replycode
;
1332 debug(9, 3) ("ftpReadWelcome\n");
1333 if (ftpState
->flags
.pasv_only
)
1334 ftpState
->login_att
++;
1335 /* Dont retry if the FTP server accepted the connection */
1336 ftpState
->fwd
->flags
.dont_retry
= 1;
1338 if (ftpState
->ctrl
.message
) {
1339 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1340 ftpState
->flags
.skip_whitespace
= 1;
1342 ftpSendUser(ftpState
);
1343 } else if (code
== 120) {
1344 if (NULL
!= ftpState
->ctrl
.message
)
1345 debug(9, 3) ("FTP server is busy: %s\n",
1346 ftpState
->ctrl
.message
->key
);
1354 ftpSendUser(FtpStateData
* ftpState
)
1356 if (ftpState
->proxy_host
!= NULL
)
1357 snprintf(cbuf
, 1024, "USER %s@%s\r\n",
1359 ftpState
->request
->host
);
1361 snprintf(cbuf
, 1024, "USER %s\r\n", ftpState
->user
);
1362 ftpWriteCommand(cbuf
, ftpState
);
1363 ftpState
->state
= SENT_USER
;
1367 ftpReadUser(FtpStateData
* ftpState
)
1369 int code
= ftpState
->ctrl
.replycode
;
1370 debug(9, 3) ("ftpReadUser\n");
1372 ftpReadPass(ftpState
);
1373 } else if (code
== 331) {
1374 ftpSendPass(ftpState
);
1381 ftpSendPass(FtpStateData
* ftpState
)
1383 snprintf(cbuf
, 1024, "PASS %s\r\n", ftpState
->password
);
1384 ftpWriteCommand(cbuf
, ftpState
);
1385 ftpState
->state
= SENT_PASS
;
1389 ftpReadPass(FtpStateData
* ftpState
)
1391 int code
= ftpState
->ctrl
.replycode
;
1392 debug(9, 3) ("ftpReadPass\n");
1394 ftpSendType(ftpState
);
1401 ftpSendType(FtpStateData
* ftpState
)
1404 const char *filename
;
1407 * Ref section 3.2.2 of RFC 1738
1409 switch (mode
= ftpState
->typecode
) {
1417 if (ftpState
->flags
.isdir
) {
1420 t
= strRChr(ftpState
->request
->urlpath
, '/');
1421 filename
= t
? t
+ 1 : strBuf(ftpState
->request
->urlpath
);
1422 mode
= mimeGetTransferMode(filename
);
1427 ftpState
->flags
.binary
= 1;
1429 ftpState
->flags
.binary
= 0;
1430 snprintf(cbuf
, 1024, "TYPE %c\r\n", mode
);
1431 ftpWriteCommand(cbuf
, ftpState
);
1432 ftpState
->state
= SENT_TYPE
;
1436 ftpReadType(FtpStateData
* ftpState
)
1438 int code
= ftpState
->ctrl
.replycode
;
1441 debug(9, 3) ("This is ftpReadType\n");
1443 p
= path
= xstrdup(strBuf(ftpState
->request
->urlpath
));
1448 p
+= strcspn(p
, "/");
1451 rfc1738_unescape(d
);
1452 wordlistAdd(&ftpState
->pathcomps
, d
);
1455 if (ftpState
->pathcomps
)
1456 ftpTraverseDirectory(ftpState
);
1458 ftpListDir(ftpState
);
1465 ftpTraverseDirectory(FtpStateData
* ftpState
)
1468 debug(9, 4) ("ftpTraverseDirectory %s\n",
1469 ftpState
->filepath
? ftpState
->filepath
: "<NULL>");
1471 safe_free(ftpState
->filepath
);
1473 if (ftpState
->pathcomps
== NULL
) {
1474 debug(9, 3) ("the final component was a directory\n");
1475 ftpListDir(ftpState
);
1478 /* Go to next path component */
1479 w
= ftpState
->pathcomps
;
1480 ftpState
->filepath
= w
->key
;
1481 ftpState
->pathcomps
= w
->next
;
1482 memFree(w
, MEM_WORDLIST
);
1483 /* Check if we are to CWD or RETR */
1484 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
1485 ftpSendCwd(ftpState
);
1487 debug(9, 3) ("final component is probably a file\n");
1488 ftpGetFile(ftpState
);
1494 ftpSendCwd(FtpStateData
* ftpState
)
1496 char *path
= ftpState
->filepath
;
1497 debug(9, 3) ("ftpSendCwd\n");
1498 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
1499 ftpState
->flags
.no_dotdot
= 1;
1501 ftpState
->flags
.no_dotdot
= 0;
1504 snprintf(cbuf
, 1024, "CWD %s\r\n", path
);
1506 snprintf(cbuf
, 1024, "CWD\r\n");
1507 ftpWriteCommand(cbuf
, ftpState
);
1508 ftpState
->state
= SENT_CWD
;
1512 ftpReadCwd(FtpStateData
* ftpState
)
1514 int code
= ftpState
->ctrl
.replycode
;
1515 debug(9, 3) ("This is ftpReadCwd\n");
1516 if (code
>= 200 && code
< 300) {
1518 ftpUnhack(ftpState
);
1519 /* Reset cwd_message to only include the last message */
1520 if (ftpState
->cwd_message
)
1521 wordlistDestroy(&ftpState
->cwd_message
);
1522 ftpState
->cwd_message
= ftpState
->ctrl
.message
;
1523 ftpState
->ctrl
.message
= NULL
;
1524 /* Continue to traverse the path */
1525 ftpTraverseDirectory(ftpState
);
1528 if (!ftpState
->flags
.put
)
1531 ftpTryMkdir(ftpState
);
1536 ftpTryMkdir(FtpStateData
* ftpState
)
1538 char *path
= ftpState
->filepath
;
1539 debug(9, 3) ("ftpTryMkdir: with path=%s\n", path
);
1540 snprintf(cbuf
, 1024, "MKD %s\r\n", path
);
1541 ftpWriteCommand(cbuf
, ftpState
);
1542 ftpState
->state
= SENT_MKDIR
;
1546 ftpReadMkdir(FtpStateData
* ftpState
)
1548 char *path
= ftpState
->filepath
;
1549 int code
= ftpState
->ctrl
.replycode
;
1551 debug(9, 3) ("ftpReadMkdir: path %s, code %d\n", path
, code
);
1552 if (code
== 257) { /* success */
1553 ftpSendCwd(ftpState
);
1554 } else if (code
== 550) { /* dir exists */
1555 if (ftpState
->flags
.put_mkdir
) {
1556 ftpState
->flags
.put_mkdir
= 1;
1557 ftpSendCwd(ftpState
);
1559 ftpSendReply(ftpState
);
1561 ftpSendReply(ftpState
);
1565 ftpGetFile(FtpStateData
* ftpState
)
1567 assert(*ftpState
->filepath
!= '\0');
1568 ftpState
->flags
.isdir
= 0;
1569 ftpSendMdtm(ftpState
);
1573 ftpListDir(FtpStateData
* ftpState
)
1575 if (!ftpState
->flags
.isdir
) {
1576 debug(9, 3) ("Directory path did not end in /\n");
1577 strcat(ftpState
->title_url
, "/");
1578 ftpState
->flags
.isdir
= 1;
1579 ftpState
->flags
.use_base
= 1;
1581 ftpSendPasv(ftpState
);
1585 ftpSendMdtm(FtpStateData
* ftpState
)
1587 assert(*ftpState
->filepath
!= '\0');
1588 snprintf(cbuf
, 1024, "MDTM %s\r\n", ftpState
->filepath
);
1589 ftpWriteCommand(cbuf
, ftpState
);
1590 ftpState
->state
= SENT_MDTM
;
1594 ftpReadMdtm(FtpStateData
* ftpState
)
1596 int code
= ftpState
->ctrl
.replycode
;
1597 debug(9, 3) ("This is ftpReadMdtm\n");
1599 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
1600 ftpUnhack(ftpState
);
1601 } else if (code
< 0) {
1604 ftpSendSize(ftpState
);
1608 ftpSendSize(FtpStateData
* ftpState
)
1610 /* Only send SIZE for binary transfers. The returned size
1611 * is useless on ASCII transfers */
1612 if (ftpState
->flags
.binary
) {
1613 assert(ftpState
->filepath
!= NULL
);
1614 assert(*ftpState
->filepath
!= '\0');
1615 snprintf(cbuf
, 1024, "SIZE %s\r\n", ftpState
->filepath
);
1616 ftpWriteCommand(cbuf
, ftpState
);
1617 ftpState
->state
= SENT_SIZE
;
1619 /* Skip to next state no non-binary transfers */
1620 ftpSendPasv(ftpState
);
1624 ftpReadSize(FtpStateData
* ftpState
)
1626 int code
= ftpState
->ctrl
.replycode
;
1627 debug(9, 3) ("This is ftpReadSize\n");
1629 ftpUnhack(ftpState
);
1630 ftpState
->size
= atoi(ftpState
->ctrl
.last_reply
);
1631 if (ftpState
->size
== 0) {
1632 debug(9, 2) ("ftpReadSize: SIZE reported %s on %s\n",
1633 ftpState
->ctrl
.last_reply
,
1634 ftpState
->title_url
);
1635 ftpState
->size
= -1;
1637 } else if (code
< 0) {
1640 ftpSendPasv(ftpState
);
1644 ftpSendPasv(FtpStateData
* ftpState
)
1647 struct sockaddr_in addr
;
1649 if (ftpState
->request
->method
== METHOD_HEAD
) {
1650 /* Terminate here for HEAD requests */
1651 ftpAppendSuccessHeader(ftpState
);
1652 storeTimestampsSet(ftpState
->entry
);
1653 fwdComplete(ftpState
->fwd
);
1654 ftpSendQuit(ftpState
);
1657 if (ftpState
->data
.fd
>= 0) {
1658 if (!ftpState
->flags
.datachannel_hack
) {
1659 /* We are already connected, reuse this connection. */
1660 ftpRestOrList(ftpState
);
1663 /* Close old connection */
1664 comm_close(ftpState
->data
.fd
);
1665 ftpState
->data
.fd
= -1;
1668 if (!ftpState
->flags
.pasv_supported
) {
1669 ftpSendPort(ftpState
);
1672 addr_len
= sizeof(addr
);
1673 if (getsockname(ftpState
->ctrl
.fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1674 debug(9, 0) ("ftpSendPasv: getsockname(%d,..): %s\n",
1675 ftpState
->ctrl
.fd
, xstrerror());
1676 addr
.sin_addr
= Config
.Addrs
.tcp_outgoing
;
1678 /* Open data channel with the same local address as control channel */
1679 fd
= comm_open(SOCK_STREAM
,
1684 storeUrl(ftpState
->entry
));
1685 debug(9, 3) ("ftpSendPasv: Unconnected data socket created on FD %d\n", fd
);
1691 * No comm_add_close_handler() here. If we have both ctrl and
1692 * data FD's call ftpStateFree() upon close, then we have
1693 * to delete the close handler which did NOT get called
1694 * to prevent ftpStateFree() getting called twice.
1695 * Instead we'll always call comm_close() on the ctrl FD.
1697 ftpState
->data
.fd
= fd
;
1698 snprintf(cbuf
, 1024, "PASV\r\n");
1699 ftpWriteCommand(cbuf
, ftpState
);
1700 ftpState
->state
= SENT_PASV
;
1702 * ugly hack for ftp servers like ftp.netscape.com that sometimes
1703 * dont acknowledge PORT commands.
1705 commSetTimeout(ftpState
->data
.fd
, 15, ftpTimeout
, ftpState
);
1709 ftpReadPasv(FtpStateData
* ftpState
)
1711 int code
= ftpState
->ctrl
.replycode
;
1716 int fd
= ftpState
->data
.fd
;
1717 char *buf
= ftpState
->ctrl
.last_reply
;
1718 LOCAL_ARRAY(char, junk
, 1024);
1719 debug(9, 3) ("This is ftpReadPasv\n");
1721 debug(9, 3) ("PASV not supported by remote end\n");
1722 ftpSendPort(ftpState
);
1725 if ((int) strlen(buf
) > 1024) {
1726 debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n");
1727 ftpSendPort(ftpState
);
1730 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1731 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
1732 debug(9, 5) ("scanning: %s\n", buf
);
1733 n
= sscanf(buf
, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1734 junk
, &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
1735 if (n
!= 7 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
1736 debug(9, 3) ("Bad 227 reply\n");
1737 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n
, p1
, p2
);
1738 ftpSendPort(ftpState
);
1741 snprintf(junk
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
1742 if (!safe_inet_addr(junk
, NULL
)) {
1743 debug(9, 1) ("unsafe address (%s)\n", junk
);
1744 ftpSendPort(ftpState
);
1747 port
= ((p1
<< 8) + p2
);
1749 debug(9, 1) ("ftpReadPasv: Invalid PASV reply: %s\n", buf
);
1750 ftpSendPort(ftpState
);
1753 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk
, port
);
1754 ftpState
->data
.port
= port
;
1755 ftpState
->data
.host
= xstrdup(junk
);
1756 safe_free(ftpState
->ctrl
.last_command
);
1757 safe_free(ftpState
->ctrl
.last_reply
);
1758 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
1759 commConnectStart(fd
, junk
, port
, ftpPasvCallback
, ftpState
);
1763 ftpPasvCallback(int fd
, int status
, void *data
)
1765 FtpStateData
*ftpState
= data
;
1766 debug(9, 3) ("ftpPasvCallback\n");
1767 if (status
!= COMM_OK
) {
1768 debug(9, 2) ("ftpPasvCallback: failed to connect. Retrying without PASV.\n");
1769 ftpState
->fwd
->flags
.dont_retry
= 0; /* this is a retryable error */
1770 ftpState
->fwd
->flags
.ftp_pasv_failed
= 1;
1771 ftpFailed(ftpState
, ERR_NONE
);
1772 /* ftpFailed closes ctrl.fd and frees ftpState */
1775 ftpRestOrList(ftpState
);
1779 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
1782 struct sockaddr_in addr
;
1787 * Tear down any old data connection if any. We are about to
1788 * establish a new one.
1790 if (ftpState
->data
.fd
> 0) {
1791 comm_close(ftpState
->data
.fd
);
1792 ftpState
->data
.fd
= -1;
1795 * Set up a listen socket on the same local address as the
1796 * control connection.
1798 addr_len
= sizeof(addr
);
1799 if (getsockname(ftpState
->ctrl
.fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1800 debug(9, 0) ("ftpOpenListenSocket: getsockname(%d,..): %s\n",
1801 ftpState
->ctrl
.fd
, xstrerror());
1805 * REUSEADDR is needed in fallback mode, since the same port is
1806 * used for both control and data.
1809 setsockopt(ftpState
->ctrl
.fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
1810 port
= ntohs(addr
.sin_port
);
1812 fd
= comm_open(SOCK_STREAM
,
1816 COMM_NONBLOCKING
| (fallback
? COMM_REUSEADDR
: 0),
1817 storeUrl(ftpState
->entry
));
1818 debug(9, 3) ("ftpOpenListenSocket: Unconnected data socket created on FD %d\n", fd
);
1820 debug(9, 0) ("ftpOpenListenSocket: comm_open failed\n");
1823 if (comm_listen(fd
) < 0) {
1827 ftpState
->data
.fd
= fd
;
1828 ftpState
->data
.port
= comm_local_port(fd
);
1829 ftpState
->data
.host
= NULL
;
1834 ftpSendPort(FtpStateData
* ftpState
)
1837 struct sockaddr_in addr
;
1839 unsigned char *addrptr
;
1840 unsigned char *portptr
;
1841 debug(9, 3) ("This is ftpSendPort\n");
1842 ftpState
->flags
.pasv_supported
= 0;
1843 fd
= ftpOpenListenSocket(ftpState
, 0);
1844 addr_len
= sizeof(addr
);
1845 if (getsockname(fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1846 debug(9, 0) ("ftpSendPort: getsockname(%d,..): %s\n", fd
, xstrerror());
1847 /* XXX Need to set error message */
1851 addrptr
= (unsigned char *) &addr
.sin_addr
.s_addr
;
1852 portptr
= (unsigned char *) &addr
.sin_port
;
1853 snprintf(cbuf
, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
1854 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
1855 portptr
[0], portptr
[1]);
1856 ftpWriteCommand(cbuf
, ftpState
);
1857 ftpState
->state
= SENT_PORT
;
1861 ftpReadPort(FtpStateData
* ftpState
)
1863 int code
= ftpState
->ctrl
.replycode
;
1864 debug(9, 3) ("This is ftpReadPort\n");
1866 /* Fall back on using the same port as the control connection */
1867 debug(9, 3) ("PORT not supported by remote end\n");
1868 ftpOpenListenSocket(ftpState
, 1);
1870 ftpRestOrList(ftpState
);
1873 /* "read" handler to accept data connection */
1875 ftpAcceptDataConnection(int fd
, void *data
)
1877 FtpStateData
*ftpState
= data
;
1878 struct sockaddr_in my_peer
, me
;
1879 debug(9, 3) ("ftpAcceptDataConnection\n");
1881 if (EBIT_TEST(ftpState
->entry
->flags
, ENTRY_ABORTED
)) {
1882 comm_close(ftpState
->ctrl
.fd
);
1885 fd
= comm_accept(fd
, &my_peer
, &me
);
1887 debug(9, 1) ("ftpHandleDataAccept: comm_accept(%d): %s", fd
, xstrerror());
1888 /* XXX Need to set error message */
1892 /* Replace the Listen socket with the accepted data socket */
1893 comm_close(ftpState
->data
.fd
);
1894 debug(9, 3) ("ftpAcceptDataConnection: Connected data socket on FD %d\n", fd
);
1895 ftpState
->data
.fd
= fd
;
1896 ftpState
->data
.port
= ntohs(my_peer
.sin_port
);
1897 ftpState
->data
.host
= xstrdup(inet_ntoa(my_peer
.sin_addr
));
1898 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
1899 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
1901 /* XXX We should have a flag to track connect state...
1902 * host NULL -> not connected, port == local port
1903 * host set -> connected, port == remote port
1905 /* Restart state (SENT_NLST/LIST/RETR) */
1906 FTP_SM_FUNCS
[ftpState
->state
] (ftpState
);
1910 ftpRestOrList(FtpStateData
* ftpState
)
1912 debug(9, 3) ("This is ftpRestOrList\n");
1913 if (ftpState
->flags
.put
) {
1914 debug(9, 3) ("ftpRestOrList: Sending STOR request...\n");
1915 ftpSendStor(ftpState
);
1916 } else if (ftpState
->typecode
== 'D') {
1917 /* XXX This should NOT be here */
1918 ftpSendNlst(ftpState
); /* sec 3.2.2 of RFC 1738 */
1919 ftpState
->flags
.isdir
= 1;
1920 ftpState
->flags
.use_base
= 1;
1921 } else if (ftpState
->flags
.isdir
)
1922 ftpSendList(ftpState
);
1923 else if (ftpRestartable(ftpState
))
1924 ftpSendRest(ftpState
);
1926 ftpSendRetr(ftpState
);
1930 ftpSendStor(FtpStateData
* ftpState
)
1932 if (ftpState
->filepath
!= NULL
) {
1933 /* Plain file upload */
1934 snprintf(cbuf
, 1024, "STOR %s\r\n", ftpState
->filepath
);
1935 ftpWriteCommand(cbuf
, ftpState
);
1936 ftpState
->state
= SENT_STOR
;
1937 } else if (httpHeaderGetInt(&ftpState
->request
->header
, HDR_CONTENT_LENGTH
) > 0) {
1938 /* File upload without a filename. use STOU to generate one */
1939 snprintf(cbuf
, 1024, "STOU\r\n");
1940 ftpWriteCommand(cbuf
, ftpState
);
1941 ftpState
->state
= SENT_STOR
;
1943 /* No file to transfer. Only create directories if needed */
1944 ftpSendReply(ftpState
);
1949 ftpReadStor(FtpStateData
* ftpState
)
1951 int code
= ftpState
->ctrl
.replycode
;
1952 debug(9, 3) ("This is ftpReadStor\n");
1953 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
1954 /* Begin data transfer */
1955 debug(9, 3) ("ftpReadStor: starting data transfer\n");
1957 * Cancel the timeout on the Control socket, pumpStart will
1958 * establish one on the data socket.
1960 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
1961 ftpPutStart(ftpState
);
1962 debug(9, 3) ("ftpReadStor: writing data channel\n");
1963 ftpState
->state
= WRITING_DATA
;
1964 } else if (code
== 150) {
1965 /* Accept data channel */
1966 debug(9, 3) ("ftpReadStor: accepting data channel\n");
1967 commSetSelect(ftpState
->data
.fd
,
1969 ftpAcceptDataConnection
,
1973 debug(9, 3) ("ftpReadStor: Unexpected reply code %s\n", code
);
1979 ftpSendRest(FtpStateData
* ftpState
)
1981 snprintf(cbuf
, 1024, "REST %d\r\n", ftpState
->restart_offset
);
1982 ftpWriteCommand(cbuf
, ftpState
);
1983 ftpState
->state
= SENT_REST
;
1987 ftpRestartable(FtpStateData
* ftpState
)
1989 if (ftpState
->restart_offset
> 0)
1991 if (!ftpState
->request
->range
)
1993 if (!ftpState
->flags
.binary
)
1995 if (ftpState
->size
<= 0)
1998 ftpState
->restart_offset
= httpHdrRangeLowestOffset(ftpState
->request
->range
, (size_t) ftpState
->size
);
1999 if (ftpState
->restart_offset
<= 0)
2005 ftpReadRest(FtpStateData
* ftpState
)
2007 int code
= ftpState
->ctrl
.replycode
;
2008 debug(9, 3) ("This is ftpReadRest\n");
2009 assert(ftpState
->restart_offset
> 0);
2011 ftpState
->restarted_offset
= ftpState
->restart_offset
;
2012 ftpSendRetr(ftpState
);
2013 } else if (code
> 0) {
2014 debug(9, 3) ("ftpReadRest: REST not supported\n");
2015 ftpState
->flags
.rest_supported
= 0;
2016 ftpSendRetr(ftpState
);
2023 ftpSendList(FtpStateData
* ftpState
)
2025 if (ftpState
->filepath
) {
2026 ftpState
->flags
.use_base
= 1;
2027 snprintf(cbuf
, 1024, "LIST %s\r\n", ftpState
->filepath
);
2029 snprintf(cbuf
, 1024, "LIST\r\n");
2031 ftpWriteCommand(cbuf
, ftpState
);
2032 ftpState
->state
= SENT_LIST
;
2036 ftpSendNlst(FtpStateData
* ftpState
)
2038 ftpState
->flags
.tried_nlst
= 1;
2039 if (ftpState
->filepath
) {
2040 ftpState
->flags
.use_base
= 1;
2041 snprintf(cbuf
, 1024, "NLST %s\r\n", ftpState
->filepath
);
2043 snprintf(cbuf
, 1024, "NLST\r\n");
2045 ftpWriteCommand(cbuf
, ftpState
);
2046 ftpState
->state
= SENT_NLST
;
2050 ftpReadList(FtpStateData
* ftpState
)
2052 int code
= ftpState
->ctrl
.replycode
;
2053 debug(9, 3) ("This is ftpReadList\n");
2054 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
2055 /* Begin data transfer */
2056 ftpAppendSuccessHeader(ftpState
);
2057 commSetSelect(ftpState
->data
.fd
,
2061 Config
.Timeout
.read
);
2062 commSetDefer(ftpState
->data
.fd
, fwdCheckDeferRead
, ftpState
->entry
);
2063 ftpState
->state
= READING_DATA
;
2065 * Cancel the timeout on the Control socket and establish one
2066 * on the data socket
2068 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2069 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
, ftpState
);
2071 } else if (code
== 150) {
2072 /* Accept data channel */
2073 commSetSelect(ftpState
->data
.fd
,
2075 ftpAcceptDataConnection
,
2079 * Cancel the timeout on the Control socket and establish one
2080 * on the data socket
2082 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2083 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
, ftpState
);
2085 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
2086 ftpSendNlst(ftpState
);
2094 ftpSendRetr(FtpStateData
* ftpState
)
2096 assert(ftpState
->filepath
!= NULL
);
2097 snprintf(cbuf
, 1024, "RETR %s\r\n", ftpState
->filepath
);
2098 ftpWriteCommand(cbuf
, ftpState
);
2099 ftpState
->state
= SENT_RETR
;
2103 ftpReadRetr(FtpStateData
* ftpState
)
2105 int code
= ftpState
->ctrl
.replycode
;
2106 debug(9, 3) ("This is ftpReadRetr\n");
2107 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
2108 /* Begin data transfer */
2109 debug(9, 3) ("ftpReadRetr: reading data channel\n");
2110 ftpAppendSuccessHeader(ftpState
);
2111 commSetSelect(ftpState
->data
.fd
,
2115 Config
.Timeout
.read
);
2116 commSetDefer(ftpState
->data
.fd
, fwdCheckDeferRead
, ftpState
->entry
);
2117 ftpState
->state
= READING_DATA
;
2119 * Cancel the timeout on the Control socket and establish one
2120 * on the data socket
2122 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2123 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
2125 } else if (code
== 150) {
2126 /* Accept data channel */
2127 commSetSelect(ftpState
->data
.fd
,
2129 ftpAcceptDataConnection
,
2133 * Cancel the timeout on the Control socket and establish one
2134 * on the data socket
2136 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2137 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
2139 } else if (code
>= 300) {
2140 if (!ftpState
->flags
.try_slash_hack
) {
2141 /* Try this as a directory missing trailing slash... */
2142 ftpHackShortcut(ftpState
, ftpSendCwd
);
2152 ftpReadTransferDone(FtpStateData
* ftpState
)
2154 int code
= ftpState
->ctrl
.replycode
;
2155 debug(9, 3) ("This is ftpReadTransferDone\n");
2157 /* Connection closed; retrieval done. */
2158 if (ftpState
->flags
.html_header_sent
)
2159 ftpListingFinish(ftpState
);
2160 if (!ftpState
->flags
.put
) {
2161 storeTimestampsSet(ftpState
->entry
);
2162 fwdComplete(ftpState
->fwd
);
2164 ftpDataTransferDone(ftpState
);
2165 } else { /* != 226 */
2166 debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n",
2168 ftpFailed(ftpState
, ERR_FTP_FAILURE
);
2169 /* ftpFailed closes ctrl.fd and frees ftpState */
2175 ftpDataTransferDone(FtpStateData
* ftpState
)
2177 debug(9, 3) ("This is ftpDataTransferDone\n");
2178 if (ftpState
->data
.fd
> -1) {
2179 comm_close(ftpState
->data
.fd
);
2180 ftpState
->data
.fd
= -1;
2182 ftpSendQuit(ftpState
);
2186 ftpSendQuit(FtpStateData
* ftpState
)
2188 assert(ftpState
->ctrl
.fd
> -1);
2189 snprintf(cbuf
, 1024, "QUIT\r\n");
2190 ftpWriteCommand(cbuf
, ftpState
);
2191 ftpState
->state
= SENT_QUIT
;
2195 ftpReadQuit(FtpStateData
* ftpState
)
2197 comm_close(ftpState
->ctrl
.fd
);
2201 ftpTrySlashHack(FtpStateData
* ftpState
)
2204 ftpState
->flags
.try_slash_hack
= 1;
2205 /* Free old paths */
2206 if (ftpState
->pathcomps
)
2207 wordlistDestroy(&ftpState
->pathcomps
);
2208 safe_free(ftpState
->filepath
);
2209 /* Build the new path (urlpath begins with /) */
2210 path
= xstrdup(strBuf(ftpState
->request
->urlpath
));
2211 rfc1738_unescape(path
);
2212 ftpState
->filepath
= path
;
2214 ftpGetFile(ftpState
);
2218 ftpTryDatachannelHack(FtpStateData
* ftpState
)
2220 ftpState
->flags
.datachannel_hack
= 1;
2221 /* we have to undo some of the slash hack... */
2222 if (ftpState
->old_filepath
!= NULL
) {
2223 ftpState
->flags
.try_slash_hack
= 0;
2224 safe_free(ftpState
->filepath
);
2225 ftpState
->filepath
= ftpState
->old_filepath
;
2226 ftpState
->old_filepath
= NULL
;
2228 ftpState
->flags
.tried_nlst
= 0;
2230 if (ftpState
->flags
.isdir
) {
2231 ftpListDir(ftpState
);
2233 ftpGetFile(ftpState
);
2238 /* Forget hack status. Next error is shown to the user */
2240 ftpUnhack(FtpStateData
* ftpState
)
2242 if (ftpState
->old_request
!= NULL
) {
2243 safe_free(ftpState
->old_request
);
2244 safe_free(ftpState
->old_reply
);
2249 ftpHackShortcut(FtpStateData
* ftpState
, FTPSM
* nextState
)
2251 /* Clear some unwanted state */
2252 ftpState
->restarted_offset
= 0;
2253 ftpState
->restart_offset
= 0;
2254 /* Save old error message & some state info */
2255 if (ftpState
->old_request
== NULL
) {
2256 ftpState
->old_request
= ftpState
->ctrl
.last_command
;
2257 ftpState
->ctrl
.last_command
= NULL
;
2258 ftpState
->old_reply
= ftpState
->ctrl
.last_reply
;
2259 ftpState
->ctrl
.last_reply
= NULL
;
2260 if (ftpState
->pathcomps
== NULL
&& ftpState
->filepath
!= NULL
)
2261 ftpState
->old_filepath
= xstrdup(ftpState
->filepath
);
2263 /* Jump to the "hack" state */
2264 nextState(ftpState
);
2268 ftpFail(FtpStateData
* ftpState
)
2270 debug(9, 3) ("ftpFail\n");
2271 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2272 if (!ftpState
->flags
.isdir
&& /* Not a directory */
2273 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
2274 ftpState
->mdtm
<= 0 && ftpState
->size
< 0 && /* Not known as a file */
2275 strNCaseCmp(ftpState
->request
->urlpath
, "/%2f", 4) != 0) { /* No slash encoded */
2276 switch (ftpState
->state
) {
2279 /* Try the / hack */
2280 ftpHackShortcut(ftpState
, ftpTrySlashHack
);
2286 /* Try to reopen datachannel */
2287 if (!ftpState
->flags
.datachannel_hack
&&
2288 ftpState
->pathcomps
== NULL
) {
2289 switch (ftpState
->state
) {
2293 /* Try to reopen datachannel */
2294 ftpHackShortcut(ftpState
, ftpTryDatachannelHack
);
2300 ftpFailed(ftpState
, ERR_NONE
);
2301 /* ftpFailed closes ctrl.fd and frees ftpState */
2305 ftpFailed(FtpStateData
* ftpState
, err_type error
)
2307 StoreEntry
*entry
= ftpState
->entry
;
2308 if (entry
->mem_obj
->inmem_hi
== 0)
2309 ftpFailedErrorMessage(ftpState
, error
);
2310 if (ftpState
->data
.fd
> -1) {
2311 comm_close(ftpState
->data
.fd
);
2312 ftpState
->data
.fd
= -1;
2314 comm_close(ftpState
->ctrl
.fd
);
2318 ftpFailedErrorMessage(FtpStateData
* ftpState
, err_type error
)
2321 char *command
, *reply
;
2322 /* Translate FTP errors into HTTP errors */
2326 switch (ftpState
->state
) {
2329 if (ftpState
->ctrl
.replycode
> 500)
2330 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
);
2331 else if (ftpState
->ctrl
.replycode
== 421)
2332 err
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
);
2336 if (ftpState
->ctrl
.replycode
== 550)
2337 err
= errorCon(ERR_FTP_NOT_FOUND
, HTTP_NOT_FOUND
);
2343 case ERR_READ_TIMEOUT
:
2344 err
= errorCon(error
, HTTP_GATEWAY_TIMEOUT
);
2347 err
= errorCon(error
, HTTP_BAD_GATEWAY
);
2351 err
= errorCon(ERR_FTP_FAILURE
, HTTP_BAD_GATEWAY
);
2352 err
->xerrno
= errno
;
2353 err
->request
= requestLink(ftpState
->request
);
2354 err
->ftp
.server_msg
= ftpState
->ctrl
.message
;
2355 ftpState
->ctrl
.message
= NULL
;
2356 if (ftpState
->old_request
)
2357 command
= ftpState
->old_request
;
2359 command
= ftpState
->ctrl
.last_command
;
2360 if (command
&& strncmp(command
, "PASS", 4) == 0)
2361 command
= "PASS <yourpassword>";
2362 if (ftpState
->old_reply
)
2363 reply
= ftpState
->old_reply
;
2365 reply
= ftpState
->ctrl
.last_reply
;
2367 err
->ftp
.request
= xstrdup(command
);
2369 err
->ftp
.reply
= xstrdup(reply
);
2370 fwdFail(ftpState
->fwd
, err
);
2374 ftpPumpClosedData(int data_fd
, void *data
)
2376 FtpStateData
*ftpState
= data
;
2377 assert(data_fd
== ftpState
->data
.fd
);
2379 * Ugly pump module closed our server-side. Deal with it.
2380 * The data FD is already closed, so just set it to -1.
2382 ftpState
->data
.fd
= -1;
2384 * Currently, thats all we have to do. Because the upload failed,
2385 * storeAbort() will be called on the reply entry. That will
2386 * call fwdAbort, which closes ftpState->ctrl.fd and then
2387 * ftpStateFree gets called.
2392 ftpPutStart(FtpStateData
* ftpState
)
2394 debug(9, 3) ("ftpPutStart\n");
2396 * sigh, we need this gross hack to detect when ugly pump module
2397 * aborts and wants to close the server-side.
2399 comm_add_close_handler(ftpState
->data
.fd
, ftpPumpClosedData
, ftpState
);
2400 pumpStart(ftpState
->data
.fd
, ftpState
->fwd
, ftpPutTransferDone
, ftpState
);
2404 ftpPutTransferDone(int fd
, char *bufnotused
, size_t size
, int errflag
, void *data
)
2406 FtpStateData
*ftpState
= data
;
2407 if (ftpState
->data
.fd
>= 0) {
2408 comm_remove_close_handler(fd
, ftpPumpClosedData
, ftpState
);
2409 comm_close(ftpState
->data
.fd
);
2410 ftpState
->data
.fd
= -1;
2412 ftpReadComplete(ftpState
);
2416 ftpSendReply(FtpStateData
* ftpState
)
2419 int code
= ftpState
->ctrl
.replycode
;
2420 http_status http_code
;
2421 err_type err_code
= ERR_NONE
;
2422 debug(9, 5) ("ftpSendReply: %s, code %d\n",
2423 storeUrl(ftpState
->entry
), code
);
2424 if (cbdataValid(ftpState
))
2425 debug(9, 5) ("ftpSendReply: ftpState (%p) is valid!\n", ftpState
);
2427 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
2428 http_code
= (ftpState
->mdtm
> 0) ? HTTP_ACCEPTED
: HTTP_CREATED
;
2429 } else if (code
== 227) {
2430 err_code
= ERR_FTP_PUT_CREATED
;
2431 http_code
= HTTP_CREATED
;
2433 err_code
= ERR_FTP_PUT_ERROR
;
2434 http_code
= HTTP_INTERNAL_SERVER_ERROR
;
2436 err
= errorCon(err_code
, http_code
);
2437 err
->request
= requestLink(ftpState
->request
);
2438 if (ftpState
->old_request
)
2439 err
->ftp
.request
= xstrdup(ftpState
->old_request
);
2441 err
->ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
2442 if (ftpState
->old_reply
)
2443 err
->ftp
.reply
= xstrdup(ftpState
->old_reply
);
2445 err
->ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
2446 errorAppendEntry(ftpState
->entry
, err
);
2447 storeBufferFlush(ftpState
->entry
);
2448 ftpSendQuit(ftpState
);
2452 ftpAppendSuccessHeader(FtpStateData
* ftpState
)
2454 char *mime_type
= NULL
;
2455 char *mime_enc
= NULL
;
2456 String urlpath
= ftpState
->request
->urlpath
;
2457 const char *filename
= NULL
;
2458 const char *t
= NULL
;
2459 StoreEntry
*e
= ftpState
->entry
;
2460 StoreEntry
*pe
= NULL
;
2461 http_reply
*reply
= e
->mem_obj
->reply
;
2462 http_version_t version
;
2463 if (ftpState
->flags
.http_header_sent
)
2465 ftpState
->flags
.http_header_sent
= 1;
2466 assert(e
->mem_obj
->inmem_hi
== 0);
2467 EBIT_CLR(e
->flags
, ENTRY_FWD_HDR_WAIT
);
2468 filename
= (t
= strRChr(urlpath
, '/')) ? t
+ 1 : strBuf(urlpath
);
2469 if (ftpState
->flags
.isdir
) {
2470 mime_type
= "text/html";
2472 switch (ftpState
->typecode
) {
2474 mime_type
= "application/octet-stream";
2475 mime_enc
= mimeGetContentEncoding(filename
);
2478 mime_type
= "text/plain";
2481 mime_type
= mimeGetContentType(filename
);
2482 mime_enc
= mimeGetContentEncoding(filename
);
2487 httpReplyReset(reply
);
2488 /* set standard stuff */
2489 if (ftpState
->restarted_offset
) {
2491 HttpHdrRangeSpec range_spec
;
2492 range_spec
.offset
= ftpState
->restarted_offset
;
2493 range_spec
.length
= ftpState
->size
- ftpState
->restarted_offset
;
2494 httpBuildVersion(&version
, 1, 0);
2495 httpReplySetHeaders(reply
, version
, HTTP_PARTIAL_CONTENT
, "Gatewaying",
2496 mime_type
, ftpState
->size
- ftpState
->restarted_offset
, ftpState
->mdtm
, -2);
2497 httpHeaderAddContRange(&reply
->header
, range_spec
, ftpState
->size
);
2500 httpBuildVersion(&version
, 1, 0);
2501 httpReplySetHeaders(reply
, version
, HTTP_OK
, "Gatewaying",
2502 mime_type
, ftpState
->size
, ftpState
->mdtm
, -2);
2504 /* additional info */
2506 httpHeaderPutStr(&reply
->header
, HDR_CONTENT_ENCODING
, mime_enc
);
2507 httpReplySwapOut(reply
, e
);
2508 storeBufferFlush(e
);
2509 reply
->hdr_sz
= e
->mem_obj
->inmem_hi
;
2510 storeTimestampsSet(e
);
2511 if (ftpState
->flags
.authenticated
) {
2513 * Authenticated requests can't be cached. Eject any old cached
2516 pe
= storeGetPublic(e
->mem_obj
->url
, e
->mem_obj
->method
);
2520 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !ftpState
->restarted_offset
) {
2521 storeSetPublicKey(e
);
2528 ftpAuthRequired(HttpReply
* old_reply
, request_t
* request
, const char *realm
)
2530 ErrorState
*err
= errorCon(ERR_ACCESS_DENIED
, HTTP_UNAUTHORIZED
);
2532 err
->request
= requestLink(request
);
2533 rep
= errorBuildReply(err
);
2534 errorStateFree(err
);
2535 /* add Authenticate header */
2536 httpHeaderPutAuth(&rep
->header
, "Basic", realm
);
2537 /* move new reply to the old one */
2538 httpReplyAbsorb(old_reply
, rep
);
2542 ftpUrlWith2f(const request_t
* request
)
2544 LOCAL_ARRAY(char, buf
, MAX_URL
);
2545 LOCAL_ARRAY(char, loginbuf
, MAX_LOGIN_SZ
+ 1);
2546 LOCAL_ARRAY(char, portbuf
, 32);
2549 if (request
->protocol
!= PROTO_FTP
)
2551 if (request
->port
!= urlDefaultPort(request
->protocol
))
2552 snprintf(portbuf
, 32, ":%d", request
->port
);
2554 if ((int) strlen(request
->login
) > 0) {
2555 strcpy(loginbuf
, request
->login
);
2556 if ((t
= strchr(loginbuf
, ':')))
2558 strcat(loginbuf
, "@");
2560 snprintf(buf
, MAX_URL
, "%s://%s%s%s%s%s",
2561 ProtocolStr
[request
->protocol
],
2566 strBuf(request
->urlpath
));
2567 if ((t
= strchr(buf
, '?')))