3 * $Id: ftp.cc,v 1.305 2001/01/07 23:36:38 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 ftpDataWrite
;
152 static CWCB ftpDataWriteCallback
;
153 static PF ftpStateFree
;
154 static PF ftpTimeout
;
155 static PF ftpReadControlReply
;
156 static CWCB ftpWriteCommandCallback
;
157 static void ftpLoginParser(const char *, FtpStateData
*, int escaped
);
158 static wordlist
*ftpParseControlReply(char *, size_t, int *, int *);
159 static int ftpRestartable(FtpStateData
* ftpState
);
160 static void ftpAppendSuccessHeader(FtpStateData
* ftpState
);
161 static void ftpAuthRequired(HttpReply
* reply
, request_t
* request
, const char *realm
);
162 static void ftpHackShortcut(FtpStateData
* ftpState
, FTPSM
* nextState
);
163 static void ftpUnhack(FtpStateData
* ftpState
);
164 static void ftpScheduleReadControlReply(FtpStateData
*, int);
165 static void ftpHandleControlReply(FtpStateData
*);
166 static char *ftpHtmlifyListEntry(char *line
, FtpStateData
* ftpState
);
167 static void ftpFailed(FtpStateData
*, err_type
);
168 static void ftpFailedErrorMessage(FtpStateData
*, err_type
);
171 * State machine functions
172 * send == state transition
173 * read == wait for response, and select next state transition
174 * other == Transition logic
176 static FTPSM ftpReadWelcome
;
177 static FTPSM ftpSendUser
;
178 static FTPSM ftpReadUser
;
179 static FTPSM ftpSendPass
;
180 static FTPSM ftpReadPass
;
181 static FTPSM ftpSendType
;
182 static FTPSM ftpReadType
;
183 static FTPSM ftpSendMdtm
;
184 static FTPSM ftpReadMdtm
;
185 static FTPSM ftpSendSize
;
186 static FTPSM ftpReadSize
;
187 static FTPSM ftpSendPort
;
188 static FTPSM ftpReadPort
;
189 static FTPSM ftpSendPasv
;
190 static FTPSM ftpReadPasv
;
191 static FTPSM ftpTraverseDirectory
;
192 static FTPSM ftpListDir
;
193 static FTPSM ftpGetFile
;
194 static FTPSM ftpSendCwd
;
195 static FTPSM ftpReadCwd
;
196 static FTPSM ftpRestOrList
;
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 ftpSendStor
;
206 static FTPSM ftpReadStor
;
207 static FTPSM ftpWriteTransferDone
;
208 static FTPSM ftpSendReply
;
209 static FTPSM ftpSendMkdir
;
210 static FTPSM ftpReadMkdir
;
211 static FTPSM ftpFail
;
212 static FTPSM ftpSendQuit
;
213 static FTPSM ftpReadQuit
;
214 /************************************************
215 ** State Machine Description (excluding hacks) **
216 *************************************************
218 ---------------------------------------
222 Type TraverseDirectory / GetFile
223 TraverseDirectory Cwd / GetFile / ListDir
224 Cwd TraverseDirectory / Mkdir
230 FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
232 Retr / Nlst / List DataRead* (on datachannel)
233 DataRead* ReadTransferDone
234 ReadTransferDone DataTransferDone
235 Stor DataWrite* (on datachannel)
236 DataWrite* RequestPutBody** (from client)
237 RequestPutBody** DataWrite* / WriteTransferDone
238 WriteTransferDone DataTransferDone
239 DataTransferDone Quit
241 ************************************************/
243 FTPSM
*FTP_SM_FUNCS
[] =
245 ftpReadWelcome
, /* BEGIN */
246 ftpReadUser
, /* SENT_USER */
247 ftpReadPass
, /* SENT_PASS */
248 ftpReadType
, /* SENT_TYPE */
249 ftpReadMdtm
, /* SENT_MDTM */
250 ftpReadSize
, /* SENT_SIZE */
251 ftpReadPort
, /* SENT_PORT */
252 ftpReadPasv
, /* SENT_PASV */
253 ftpReadCwd
, /* SENT_CWD */
254 ftpReadList
, /* SENT_LIST */
255 ftpReadList
, /* SENT_NLST */
256 ftpReadRest
, /* SENT_REST */
257 ftpReadRetr
, /* SENT_RETR */
258 ftpReadStor
, /* SENT_STOR */
259 ftpReadQuit
, /* SENT_QUIT */
260 ftpReadTransferDone
, /* READING_DATA (RETR,LIST,NLST) */
261 ftpWriteTransferDone
, /* WRITING_DATA (STOR) */
262 ftpSendReply
, /* WRITTEN_DATA? (STOR) */
263 ftpReadMkdir
/* SENT_MKDIR */
267 ftpStateFree(int fdnotused
, void *data
)
269 FtpStateData
*ftpState
= data
;
270 if (ftpState
== NULL
)
272 debug(9, 3) ("ftpStateFree: %s\n", storeUrl(ftpState
->entry
));
273 storeUnregisterAbort(ftpState
->entry
);
274 storeUnlockObject(ftpState
->entry
);
275 if (ftpState
->reply_hdr
) {
276 memFree(ftpState
->reply_hdr
, MEM_8K_BUF
);
277 /* this seems unnecessary, but people report SEGV's
278 * when freeing memory in this function */
279 ftpState
->reply_hdr
= NULL
;
281 requestUnlink(ftpState
->request
);
282 if (ftpState
->ctrl
.buf
) {
283 ftpState
->ctrl
.freefunc(ftpState
->ctrl
.buf
);
284 /* this seems unnecessary, but people report SEGV's
285 * when freeing memory in this function */
286 ftpState
->ctrl
.buf
= NULL
;
288 if (ftpState
->data
.buf
) {
289 ftpState
->data
.freefunc(ftpState
->data
.buf
);
290 /* this seems unnecessary, but people report SEGV's
291 * when freeing memory in this function */
292 ftpState
->data
.buf
= NULL
;
294 if (ftpState
->pathcomps
)
295 wordlistDestroy(&ftpState
->pathcomps
);
296 if (ftpState
->ctrl
.message
)
297 wordlistDestroy(&ftpState
->ctrl
.message
);
298 if (ftpState
->cwd_message
)
299 wordlistDestroy(&ftpState
->cwd_message
);
300 safe_free(ftpState
->ctrl
.last_reply
);
301 safe_free(ftpState
->ctrl
.last_command
);
302 safe_free(ftpState
->old_request
);
303 safe_free(ftpState
->old_reply
);
304 safe_free(ftpState
->old_filepath
);
305 safe_free(ftpState
->title_url
);
306 safe_free(ftpState
->base_href
);
307 safe_free(ftpState
->filepath
);
308 safe_free(ftpState
->data
.host
);
309 if (ftpState
->data
.fd
> -1) {
310 comm_close(ftpState
->data
.fd
);
311 ftpState
->data
.fd
= -1;
313 cbdataFree(ftpState
);
317 ftpLoginParser(const char *login
, FtpStateData
* ftpState
, int escaped
)
320 xstrncpy(ftpState
->user
, login
, MAX_URL
);
321 if ((s
= strchr(ftpState
->user
, ':'))) {
323 xstrncpy(ftpState
->password
, s
+ 1, MAX_URL
);
325 rfc1738_unescape(ftpState
->password
);
326 ftpState
->password_url
= 1;
328 xstrncpy(ftpState
->password
, null_string
, MAX_URL
);
331 rfc1738_unescape(ftpState
->user
);
332 if (ftpState
->user
[0] || ftpState
->password
[0])
334 xstrncpy(ftpState
->user
, "anonymous", MAX_URL
);
335 xstrncpy(ftpState
->password
, Config
.Ftp
.anon_user
, MAX_URL
);
339 ftpTimeout(int fd
, void *data
)
341 FtpStateData
*ftpState
= data
;
342 StoreEntry
*entry
= ftpState
->entry
;
343 debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd
, storeUrl(entry
));
344 if (SENT_PASV
== ftpState
->state
&& fd
== ftpState
->data
.fd
) {
345 /* stupid ftp.netscape.com */
346 ftpState
->fwd
->flags
.dont_retry
= 0;
347 ftpState
->fwd
->flags
.ftp_pasv_failed
= 1;
348 debug(9, 1) ("ftpTimeout: timeout in SENT_PASV state\n");
350 ftpFailed(ftpState
, ERR_READ_TIMEOUT
);
351 /* ftpFailed closes ctrl.fd and frees ftpState */
355 ftpListingStart(FtpStateData
* ftpState
)
357 StoreEntry
*e
= ftpState
->entry
;
363 storeAppendPrintf(e
, "<!-- HTML listing generated by Squid %s -->\n",
365 storeAppendPrintf(e
, "<!-- %s -->\n", mkrfc1123(squid_curtime
));
366 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>\n");
367 storeAppendPrintf(e
, "FTP Directory: %s\n",
368 html_quote(ftpState
->title_url
));
369 storeAppendPrintf(e
, "</TITLE>\n");
370 if (ftpState
->flags
.use_base
)
371 storeAppendPrintf(e
, "<BASE HREF=\"%s\">\n",
372 html_quote(ftpState
->base_href
));
373 storeAppendPrintf(e
, "</HEAD><BODY>\n");
374 if (ftpState
->cwd_message
) {
375 storeAppendPrintf(e
, "<PRE>\n");
376 for (w
= ftpState
->cwd_message
; w
; w
= w
->next
)
377 storeAppendPrintf(e
, "%s\n", html_quote(w
->key
));
378 storeAppendPrintf(e
, "</PRE>\n");
379 storeAppendPrintf(e
, "<HR>\n");
380 wordlistDestroy(&ftpState
->cwd_message
);
382 storeAppendPrintf(e
, "<H2>\n");
383 storeAppendPrintf(e
, "FTP Directory: ");
384 /* "ftp://" == 6 characters */
385 assert(strlen(ftpState
->title_url
) >= 6);
386 title
= html_quote(ftpState
->title_url
);
387 for (i
= 6, j
= 0; title
[i
]; j
= i
) {
388 storeAppendPrintf(e
, "<A HREF=\"");
389 i
+= strcspn(&title
[i
], "/");
392 for (k
= 0; k
< i
; k
++)
393 storeAppendPrintf(e
, "%c", title
[k
]);
394 storeAppendPrintf(e
, "\">");
395 for (k
= j
; k
< i
- 1; k
++)
396 storeAppendPrintf(e
, "%c", title
[k
]);
397 if (ftpState
->title_url
[k
] != '/')
398 storeAppendPrintf(e
, "%c", title
[k
++]);
399 storeAppendPrintf(e
, "</A>");
401 storeAppendPrintf(e
, "%c", title
[k
++]);
403 /* Error guard, or "assert" */
404 storeAppendPrintf(e
, "ERROR: Failed to parse URL: %s\n",
405 html_quote(ftpState
->title_url
));
406 debug(9, 0) ("Failed to parse URL: %s\n", ftpState
->title_url
);
410 storeAppendPrintf(e
, "</H2>\n");
411 storeAppendPrintf(e
, "<PRE>\n");
412 dirup
= ftpHtmlifyListEntry("<internal-dirup>", ftpState
);
413 storeAppend(e
, dirup
, strlen(dirup
));
415 ftpState
->flags
.html_header_sent
= 1;
419 ftpListingFinish(FtpStateData
* ftpState
)
421 StoreEntry
*e
= ftpState
->entry
;
423 storeAppendPrintf(e
, "</PRE>\n");
424 if (ftpState
->flags
.listformat_unknown
&& !ftpState
->flags
.tried_nlst
) {
425 storeAppendPrintf(e
, "<A HREF=\"./;type=d\">[As plain directory]</A>\n");
426 } else if (ftpState
->typecode
== 'D') {
427 storeAppendPrintf(e
, "<A HREF=\"./\">[As extended directory]</A>\n");
429 storeAppendPrintf(e
, "<HR>\n");
430 storeAppendPrintf(e
, "<ADDRESS>\n");
431 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
432 mkrfc1123(squid_curtime
),
434 full_appname_string
);
435 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
439 static const char *Month
[] =
441 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
442 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
446 is_month(const char *buf
)
449 for (i
= 0; i
< 12; i
++)
450 if (!strcasecmp(buf
, Month
[i
]))
457 ftpListPartsFree(ftpListParts
** parts
)
459 safe_free((*parts
)->date
);
460 safe_free((*parts
)->name
);
461 safe_free((*parts
)->showname
);
462 safe_free((*parts
)->link
);
466 #define MAX_TOKENS 64
468 #define SCAN_FTP1 "%[0123456789]"
469 #define SCAN_FTP2 "%[0123456789:]"
470 #define SCAN_FTP3 "%[0123456789]-%[0123456789]-%[0123456789]"
471 #define SCAN_FTP4 "%[0123456789]:%[0123456789]%[AaPp]%[Mm]"
473 static ftpListParts
*
474 ftpListParseParts(const char *buf
, struct _ftp_flags flags
)
476 ftpListParts
*p
= NULL
;
478 const char *ct
= NULL
;
479 char *tokens
[MAX_TOKENS
];
482 static char sbuf
[128];
488 p
= xcalloc(1, sizeof(ftpListParts
));
490 for (i
= 0; i
< MAX_TOKENS
; i
++)
491 tokens
[i
] = (char *) NULL
;
493 if (flags
.tried_nlst
) {
494 /* Machine readable format, one name per line */
499 for (t
= strtok(xbuf
, w_space
); t
&& n_tokens
< MAX_TOKENS
; t
= strtok(NULL
, w_space
))
500 tokens
[n_tokens
++] = xstrdup(t
);
502 /* locate the Month field */
503 for (i
= 3; i
< n_tokens
- 2; i
++) {
504 if (!is_month(tokens
[i
])) /* Month */
506 if (!sscanf(tokens
[i
- 1], SCAN_FTP1
, sbuf
)) /* Size */
508 if (!sscanf(tokens
[i
+ 1], SCAN_FTP1
, sbuf
)) /* Day */
510 if (!sscanf(tokens
[i
+ 2], SCAN_FTP2
, sbuf
)) /* Yr | hh:mm */
512 p
->type
= *tokens
[0];
513 p
->size
= atoi(tokens
[i
- 1]);
514 snprintf(sbuf
, 128, "%s %2s %5s",
515 tokens
[i
], tokens
[i
+ 1], tokens
[i
+ 2]);
516 if (!strstr(buf
, sbuf
))
517 snprintf(sbuf
, 128, "%s %2s %-5s",
518 tokens
[i
], tokens
[i
+ 1], tokens
[i
+ 2]);
519 if ((t
= strstr(buf
, sbuf
))) {
520 p
->date
= xstrdup(sbuf
);
521 if (flags
.skip_whitespace
) {
523 while (strchr(w_space
, *t
))
526 /* XXX assumes a single space between date and filename
527 * suggested by: Nathan.Bailey@cc.monash.edu.au and
528 * Mike Battersby <mike@starbug.bofh.asn.au> */
529 t
+= strlen(sbuf
) + 1;
531 p
->name
= xstrdup(t
);
532 if ((t
= strstr(p
->name
, " -> "))) {
534 p
->link
= xstrdup(t
+ 4);
539 /* try it as a DOS listing */
540 if (n_tokens
> 3 && p
->name
== NULL
&&
541 sscanf(tokens
[0], SCAN_FTP3
, sbuf
, sbuf
, sbuf
) == 3 &&
543 sscanf(tokens
[1], SCAN_FTP4
, sbuf
, sbuf
, sbuf
, sbuf
) == 4) {
545 if (!strcasecmp(tokens
[2], "<dir>")) {
549 p
->size
= atoi(tokens
[2]);
551 snprintf(sbuf
, 128, "%s %s", tokens
[0], tokens
[1]);
552 p
->date
= xstrdup(sbuf
);
553 if (p
->type
== 'd') {
554 /* Directory.. name begins with first printable after <dir> */
555 ct
= strstr(buf
, tokens
[2]);
556 ct
+= strlen(tokens
[2]);
557 while (xisspace(*ct
))
562 /* A file. Name begins after size, with a space in between */
563 snprintf(sbuf
, 128, " %s %s", tokens
[2], tokens
[3]);
564 ct
= strstr(buf
, sbuf
);
566 ct
+= strlen(tokens
[2]) + 2;
569 p
->name
= xstrdup(ct
? ct
: tokens
[3]);
571 /* Try EPLF format; carson@lehman.com */
572 if (p
->name
== NULL
&& buf
[0] == '+') {
580 sscanf(ct
+ 1, "%[^,]", sbuf
);
581 p
->name
= xstrdup(sbuf
);
584 sscanf(ct
+ 1, "%d", &(p
->size
));
587 if (1 != sscanf(ct
+ 1, "%ld", <
))
590 p
->date
= xstrdup(ctime(&t
));
591 *(strstr(p
->date
, "\n")) = '\0';
604 ct
= strstr(ct
, ",");
613 for (i
= 0; i
< n_tokens
; i
++)
616 ftpListPartsFree(&p
);
621 dots_fill(size_t len
)
623 static char buf
[256];
625 if (len
> Config
.Ftp
.list_width
) {
626 memset(buf
, ' ', 256);
628 buf
[Config
.Ftp
.list_width
+ 4] = '\0';
631 for (i
= (int) len
; i
< Config
.Ftp
.list_width
; i
++)
632 buf
[i
- len
] = (i
% 2) ? '.' : ' ';
638 ftpHtmlifyListEntry(char *line
, FtpStateData
* ftpState
)
640 LOCAL_ARRAY(char, icon
, 2048);
641 LOCAL_ARRAY(char, href
, 2048 + 40);
642 LOCAL_ARRAY(char, text
, 2048);
643 LOCAL_ARRAY(char, size
, 2048);
644 LOCAL_ARRAY(char, chdir
, 2048 + 40);
645 LOCAL_ARRAY(char, view
, 2048 + 40);
646 LOCAL_ARRAY(char, download
, 2048 + 40);
647 LOCAL_ARRAY(char, link
, 2048 + 40);
648 LOCAL_ARRAY(char, html
, 8192);
649 size_t width
= Config
.Ftp
.list_width
;
651 *icon
= *href
= *text
= *size
= *chdir
= *view
= *download
= *link
= *html
= '\0';
652 if ((int) strlen(line
) > 1024) {
653 snprintf(html
, 8192, "%s\n", line
);
656 /* Handle builtin <dirup> */
657 if (strcmp(line
, "<internal-dirup>") == 0) {
658 /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> {link} */
659 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
660 mimeGetIconURL("internal-dirup"),
662 if (!ftpState
->flags
.no_dotdot
&& !ftpState
->flags
.root_dir
) {
663 /* Normal directory */
665 strcpy(text
, "Parent Directory");
666 } else if (!ftpState
->flags
.no_dotdot
&& ftpState
->flags
.root_dir
) {
667 /* "Top level" directory */
668 strcpy(href
, "%2e%2e/");
669 strcpy(text
, "Parent Directory");
670 snprintf(link
, 2048, "(<A HREF=\"%s\">%s</A>)",
673 } else if (ftpState
->flags
.no_dotdot
&& !ftpState
->flags
.root_dir
) {
674 /* Normal directory where last component is / or .. */
675 strcpy(href
, "%2e%2e/");
676 strcpy(text
, "Parent Directory");
677 snprintf(link
, 2048, "(<A HREF=\"%s\">%s</A>)",
680 } else { /* NO_DOTDOT && ROOT_DIR */
681 /* "UNIX Root" directory */
683 strcpy(text
, "Home Directory");
685 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A> %s\n",
686 href
, icon
, href
, text
, link
);
689 if ((parts
= ftpListParseParts(line
, ftpState
->flags
)) == NULL
) {
691 snprintf(html
, 8192, "%s\n", line
);
692 for (p
= line
; *p
&& xisspace(*p
); p
++);
693 if (*p
&& !xisspace(*p
))
694 ftpState
->flags
.listformat_unknown
= 1;
697 if (!strcmp(parts
->name
, ".") || !strcmp(parts
->name
, "..")) {
699 ftpListPartsFree(&parts
);
704 parts
->showname
= xstrdup(parts
->name
);
705 if (!Config
.Ftp
.list_wrap
) {
706 if (strlen(parts
->showname
) > width
- 1) {
707 *(parts
->showname
+ width
- 1) = '>';
708 *(parts
->showname
+ width
- 0) = '\0';
711 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
712 xstrncpy(href
, rfc1738_escape_part(parts
->name
), 2048);
713 xstrncpy(text
, parts
->showname
, 2048);
714 switch (parts
->type
) {
716 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
717 mimeGetIconURL("internal-dir"),
719 strncat(href
, "/", 2048);
722 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
723 mimeGetIconURL("internal-link"),
725 /* sometimes there is an 'l' flag, but no "->" link */
727 char *link2
= xstrdup(html_quote(rfc1738_escape(parts
->link
)));
728 snprintf(link
, 2048, " -> <A HREF=\"%s\">%s</A>",
730 html_quote(parts
->link
));
735 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
736 mimeGetIconURL(parts
->name
),
738 snprintf(chdir
, 2048, " <A HREF=\"%s/;type=d\"><IMG BORDER=0 SRC=\"%s\" "
739 "ALT=\"[DIR]\"></A>",
740 rfc1738_escape_part(parts
->name
),
741 mimeGetIconURL("internal-dir"));
745 snprintf(icon
, 2048, "<IMG BORDER=0 SRC=\"%s\" ALT=\"%-6s\">",
746 mimeGetIconURL(parts
->name
),
748 snprintf(size
, 2048, " %6dk", parts
->size
);
751 if (parts
->type
!= 'd') {
752 if (mimeGetViewOption(parts
->name
)) {
753 snprintf(view
, 2048, " <A HREF=\"%s;type=a\"><IMG BORDER=0 SRC=\"%s\" "
754 "ALT=\"[VIEW]\"></A>",
755 href
, mimeGetIconURL("internal-view"));
757 if (mimeGetDownloadOption(parts
->name
)) {
758 snprintf(download
, 2048, " <A HREF=\"%s;type=i\"><IMG BORDER=0 SRC=\"%s\" "
759 "ALT=\"[DOWNLOAD]\"></A>",
760 href
, mimeGetIconURL("internal-download"));
763 /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> . . . {date}{size}{chdir}{view}{download}{link}\n */
764 if (parts
->type
!= '\0') {
765 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A>%s "
767 href
, icon
, href
, html_quote(text
), dots_fill(strlen(text
)),
768 parts
->date
, size
, chdir
, view
, download
, link
);
770 /* Plain listing. {icon} {text} ... {chdir}{view}{download} */
771 snprintf(html
, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A>%s "
773 href
, icon
, href
, html_quote(text
), dots_fill(strlen(text
)),
774 chdir
, view
, download
, link
);
776 ftpListPartsFree(&parts
);
781 ftpParseListing(FtpStateData
* ftpState
)
783 char *buf
= ftpState
->data
.buf
;
784 char *sbuf
; /* NULL-terminated copy of buf */
791 StoreEntry
*e
= ftpState
->entry
;
792 int len
= ftpState
->data
.offset
;
794 * We need a NULL-terminated buffer for scanning, ick
796 sbuf
= xmalloc(len
+ 1);
797 xstrncpy(sbuf
, buf
, len
+ 1);
798 end
= sbuf
+ len
- 1;
799 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
802 debug(9, 3) ("ftpParseListing: usable = %d\n", usable
);
804 debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e
));
808 debug(9, 3) ("ftpParseListing: %d bytes to play with\n", len
);
809 line
= memAllocate(MEM_4K_BUF
);
813 s
+= strspn(s
, crlf
);
814 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
815 debug(9, 3) ("ftpParseListing: s = {%s}\n", s
);
816 linelen
= strcspn(s
, crlf
) + 1;
821 xstrncpy(line
, s
, linelen
);
822 debug(9, 7) ("ftpParseListing: {%s}\n", line
);
823 if (!strncmp(line
, "total", 5))
825 t
= ftpHtmlifyListEntry(line
, ftpState
);
827 storeAppend(e
, t
, strlen(t
));
830 assert(usable
<= len
);
832 /* must copy partial line to beginning of buf */
833 linelen
= len
- usable
;
836 xstrncpy(line
, end
, linelen
);
837 xstrncpy(ftpState
->data
.buf
, line
, ftpState
->data
.size
);
838 ftpState
->data
.offset
= strlen(ftpState
->data
.buf
);
840 memFree(line
, MEM_4K_BUF
);
845 ftpDataComplete(FtpStateData
* ftpState
)
847 debug(9, 3) ("ftpDataComplete\n");
848 /* Connection closed; transfer done. */
849 if (ftpState
->data
.fd
> -1) {
851 * close data socket so it does not occupy resources while
854 comm_close(ftpState
->data
.fd
);
855 ftpState
->data
.fd
= -1;
857 /* expect the "transfer complete" message on the control socket */
858 ftpScheduleReadControlReply(ftpState
, 1);
862 ftpDataRead(int fd
, void *data
)
864 FtpStateData
*ftpState
= data
;
868 StoreEntry
*entry
= ftpState
->entry
;
871 MemObject
*mem
= entry
->mem_obj
;
872 delay_id delay_id
= delayMostBytesAllowed(mem
);
874 assert(fd
== ftpState
->data
.fd
);
875 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
876 comm_close(ftpState
->ctrl
.fd
);
880 read_sz
= ftpState
->data
.size
- ftpState
->data
.offset
;
882 read_sz
= delayBytesWanted(delay_id
, 1, read_sz
);
884 memset(ftpState
->data
.buf
+ ftpState
->data
.offset
, '\0', read_sz
);
885 statCounter
.syscalls
.sock
.reads
++;
886 len
= read(fd
, ftpState
->data
.buf
+ ftpState
->data
.offset
, read_sz
);
888 fd_bytes(fd
, len
, FD_READ
);
890 delayBytesIn(delay_id
, len
);
892 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
893 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, len
);
894 ftpState
->data
.offset
+= len
;
896 debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd
, len
);
899 for (j
= len
- 1, bin
= 0; j
; bin
++)
901 IOStats
.Ftp
.read_hist
[bin
]++;
903 if (ftpState
->flags
.isdir
&& !ftpState
->flags
.html_header_sent
&& len
>= 0) {
904 ftpListingStart(ftpState
);
907 debug(50, ignoreErrno(errno
) ? 3 : 1) ("ftpDataRead: read error: %s\n", xstrerror());
908 if (ignoreErrno(errno
)) {
913 Config
.Timeout
.read
);
915 ftpFailed(ftpState
, ERR_READ_ERROR
);
916 /* ftpFailed closes ctrl.fd and frees ftpState */
919 } else if (len
== 0) {
920 ftpDataComplete(ftpState
);
922 if (ftpState
->flags
.isdir
) {
923 ftpParseListing(ftpState
);
925 storeAppend(entry
, ftpState
->data
.buf
, len
);
926 ftpState
->data
.offset
= 0;
932 Config
.Timeout
.read
);
939 * Return 1 if we have everything needed to complete this request.
940 * Return 0 if something is missing.
943 ftpCheckAuth(FtpStateData
* ftpState
, const HttpHeader
* req_hdr
)
947 ftpLoginParser(ftpState
->request
->login
, ftpState
, FTP_LOGIN_ESCAPED
);
948 if (!ftpState
->user
[0])
949 return 1; /* no name */
950 if (ftpState
->password_url
|| ftpState
->password
[0])
951 return 1; /* passwd provided in URL */
952 /* URL has name, but no passwd */
953 if (!(auth
= httpHeaderGetAuth(req_hdr
, HDR_AUTHORIZATION
, "Basic")))
954 return 0; /* need auth header */
955 ftpState
->flags
.authenticated
= 1;
956 orig_user
= xstrdup(ftpState
->user
);
957 ftpLoginParser(auth
, ftpState
, FTP_LOGIN_NOT_ESCAPED
);
958 if (!strcmp(orig_user
, ftpState
->user
)) {
960 return 1; /* same username */
962 strcpy(ftpState
->user
, orig_user
);
964 return 0; /* different username */
968 ftpCheckUrlpath(FtpStateData
* ftpState
)
970 request_t
*request
= ftpState
->request
;
973 if ((t
= strRChr(request
->urlpath
, ';')) != NULL
) {
974 if (strncasecmp(t
+ 1, "type=", 5) == 0) {
975 ftpState
->typecode
= (char) toupper((int) *(t
+ 6));
976 strCutPtr(request
->urlpath
, t
);
979 l
= strLen(request
->urlpath
);
980 ftpState
->flags
.use_base
= 1;
981 /* check for null path */
983 ftpState
->flags
.isdir
= 1;
984 ftpState
->flags
.root_dir
= 1;
985 } else if (!strCmp(request
->urlpath
, "/%2f/")) {
986 /* UNIX root directory */
987 ftpState
->flags
.use_base
= 0;
988 ftpState
->flags
.isdir
= 1;
989 ftpState
->flags
.root_dir
= 1;
990 } else if ((l
>= 1) && (*(strBuf(request
->urlpath
) + l
- 1) == '/')) {
991 /* Directory URL, ending in / */
992 ftpState
->flags
.isdir
= 1;
993 ftpState
->flags
.use_base
= 0;
995 ftpState
->flags
.root_dir
= 1;
1000 ftpBuildTitleUrl(FtpStateData
* ftpState
)
1002 request_t
*request
= ftpState
->request
;
1006 + strlen(ftpState
->user
)
1007 + strlen(ftpState
->password
)
1008 + strlen(request
->host
)
1009 + strLen(request
->urlpath
);
1010 t
= ftpState
->title_url
= xcalloc(len
, 1);
1011 strcat(t
, "ftp://");
1012 if (strcmp(ftpState
->user
, "anonymous")) {
1013 strcat(t
, ftpState
->user
);
1016 strcat(t
, request
->host
);
1017 if (request
->port
!= urlDefaultPort(PROTO_FTP
))
1018 snprintf(&t
[strlen(t
)], len
- strlen(t
), ":%d", request
->port
);
1019 strcat(t
, strBuf(request
->urlpath
));
1020 t
= ftpState
->base_href
= xcalloc(len
, 1);
1021 strcat(t
, "ftp://");
1022 if (strcmp(ftpState
->user
, "anonymous")) {
1023 strcat(t
, rfc1738_escape_part(ftpState
->user
));
1024 if (ftpState
->password_url
) {
1026 strcat(t
, rfc1738_escape_part(ftpState
->password
));
1030 strcat(t
, request
->host
);
1031 if (request
->port
!= urlDefaultPort(PROTO_FTP
))
1032 snprintf(&t
[strlen(t
)], len
- strlen(t
), ":%d", request
->port
);
1033 strcat(t
, strBuf(request
->urlpath
));
1037 CBDATA_TYPE(FtpStateData
);
1039 ftpStart(FwdState
* fwd
)
1041 request_t
*request
= fwd
->request
;
1042 StoreEntry
*entry
= fwd
->entry
;
1043 int fd
= fwd
->server_fd
;
1044 LOCAL_ARRAY(char, realm
, 8192);
1045 const char *url
= storeUrl(entry
);
1046 FtpStateData
*ftpState
;
1049 CBDATA_INIT_TYPE(FtpStateData
);
1050 ftpState
= CBDATA_ALLOC(FtpStateData
, NULL
);
1051 debug(9, 3) ("ftpStart: '%s'\n", url
);
1052 statCounter
.server
.all
.requests
++;
1053 statCounter
.server
.ftp
.requests
++;
1054 storeLockObject(entry
);
1055 ftpState
->entry
= entry
;
1056 ftpState
->request
= requestLink(request
);
1057 ftpState
->ctrl
.fd
= fd
;
1058 ftpState
->data
.fd
= -1;
1059 ftpState
->size
= -1;
1060 ftpState
->mdtm
= -1;
1061 if (!Config
.Ftp
.passive
)
1062 ftpState
->flags
.rest_supported
= 0;
1063 else if (fwd
->flags
.ftp_pasv_failed
)
1064 ftpState
->flags
.pasv_supported
= 0;
1066 ftpState
->flags
.pasv_supported
= 1;
1067 ftpState
->flags
.rest_supported
= 1;
1068 ftpState
->fwd
= fwd
;
1069 comm_add_close_handler(fd
, ftpStateFree
, ftpState
);
1070 if (ftpState
->request
->method
== METHOD_PUT
)
1071 ftpState
->flags
.put
= 1;
1072 if (!ftpCheckAuth(ftpState
, &request
->header
)) {
1073 /* This request is not fully authenticated */
1074 if (request
->port
== 21) {
1075 snprintf(realm
, 8192, "ftp %s", ftpState
->user
);
1077 snprintf(realm
, 8192, "ftp %s port %d",
1078 ftpState
->user
, request
->port
);
1081 reply
= entry
->mem_obj
->reply
;
1082 assert(reply
!= NULL
);
1083 /* create appropriate reply */
1084 ftpAuthRequired(reply
, request
, realm
);
1085 httpReplySwapOut(reply
, entry
);
1086 fwdComplete(ftpState
->fwd
);
1090 ftpCheckUrlpath(ftpState
);
1091 ftpBuildTitleUrl(ftpState
);
1092 debug(9, 5) ("ftpStart: host=%s, path=%s, user=%s, passwd=%s\n",
1093 ftpState
->request
->host
, strBuf(ftpState
->request
->urlpath
),
1094 ftpState
->user
, ftpState
->password
);
1095 ftpState
->state
= BEGIN
;
1096 ftpState
->ctrl
.last_command
= xstrdup("Connect to server");
1097 ftpState
->ctrl
.buf
= memAllocate(MEM_4K_BUF
);
1098 ftpState
->ctrl
.freefunc
= memFree4K
;
1099 ftpState
->ctrl
.size
= 4096;
1100 ftpState
->ctrl
.offset
= 0;
1101 ftpState
->data
.buf
= xmalloc(SQUID_TCP_SO_RCVBUF
);
1102 ftpState
->data
.size
= SQUID_TCP_SO_RCVBUF
;
1103 ftpState
->data
.freefunc
= xfree
;
1104 ftpScheduleReadControlReply(ftpState
, 0);
1107 /* ====================================================================== */
1110 ftpWriteCommand(const char *buf
, FtpStateData
* ftpState
)
1112 debug(9, 5) ("ftpWriteCommand: %s\n", buf
);
1113 safe_free(ftpState
->ctrl
.last_command
);
1114 safe_free(ftpState
->ctrl
.last_reply
);
1115 ftpState
->ctrl
.last_command
= xstrdup(buf
);
1116 comm_write(ftpState
->ctrl
.fd
,
1119 ftpWriteCommandCallback
,
1122 ftpScheduleReadControlReply(ftpState
, 0);
1126 ftpWriteCommandCallback(int fd
, char *bufnotused
, size_t size
, int errflag
, void *data
)
1128 FtpStateData
*ftpState
= data
;
1129 debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size
);
1131 fd_bytes(fd
, size
, FD_WRITE
);
1132 kb_incr(&statCounter
.server
.all
.kbytes_out
, size
);
1133 kb_incr(&statCounter
.server
.ftp
.kbytes_out
, size
);
1135 if (errflag
== COMM_ERR_CLOSING
)
1138 debug(9, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd
, xstrerror());
1139 ftpFailed(ftpState
, ERR_WRITE_ERROR
);
1140 /* ftpFailed closes ctrl.fd and frees ftpState */
1146 ftpParseControlReply(char *buf
, size_t len
, int *codep
, int *used
)
1153 wordlist
*head
= NULL
;
1155 wordlist
**tail
= &head
;
1159 debug(9, 5) ("ftpParseControlReply\n");
1161 * We need a NULL-terminated buffer for scanning, ick
1163 sbuf
= xmalloc(len
+ 1);
1164 xstrncpy(sbuf
, buf
, len
+ 1);
1165 end
= sbuf
+ len
- 1;
1166 while (*end
!= '\r' && *end
!= '\n' && end
> sbuf
)
1168 usable
= end
- sbuf
;
1169 debug(9, 3) ("ftpParseControlReply: usable = %d\n", usable
);
1171 debug(9, 3) ("ftpParseControlReply: didn't find end of line\n");
1175 debug(9, 3) ("ftpParseControlReply: %d bytes to play with\n", len
);
1178 s
+= strspn(s
, crlf
);
1179 for (; s
< end
; s
+= strcspn(s
, crlf
), s
+= strspn(s
, crlf
)) {
1182 debug(9, 3) ("ftpParseControlReply: s = {%s}\n", s
);
1183 linelen
= strcspn(s
, crlf
) + 1;
1187 complete
= (*s
>= '0' && *s
<= '9' && *(s
+ 3) == ' ');
1192 if (*s
>= '0' && *s
<= '9' && (*(s
+ 3) == '-' || *(s
+ 3) == ' '))
1194 list
= memAllocate(MEM_WORDLIST
);
1195 list
->key
= xmalloc(linelen
- offset
);
1196 xstrncpy(list
->key
, s
+ offset
, linelen
- offset
);
1197 debug(9, 7) ("%d %s\n", code
, list
->key
);
1201 *used
= (int) (s
- sbuf
);
1204 wordlistDestroy(&head
);
1211 ftpScheduleReadControlReply(FtpStateData
* ftpState
, int buffered_ok
)
1213 debug(9, 3) ("ftpScheduleReadControlReply: FD %d\n", ftpState
->ctrl
.fd
);
1214 if (buffered_ok
&& ftpState
->ctrl
.offset
> 0) {
1215 /* We've already read some reply data */
1216 ftpHandleControlReply(ftpState
);
1218 commSetSelect(ftpState
->ctrl
.fd
,
1220 ftpReadControlReply
,
1222 Config
.Timeout
.read
);
1224 * Cancel the timeout on the Data socket (if any) and
1225 * establish one on the control socket.
1227 if (ftpState
->data
.fd
> -1)
1228 commSetTimeout(ftpState
->data
.fd
, -1, NULL
, NULL
);
1229 commSetTimeout(ftpState
->ctrl
.fd
, Config
.Timeout
.read
, ftpTimeout
,
1235 ftpReadControlReply(int fd
, void *data
)
1237 FtpStateData
*ftpState
= data
;
1238 StoreEntry
*entry
= ftpState
->entry
;
1240 debug(9, 5) ("ftpReadControlReply\n");
1241 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
1242 comm_close(ftpState
->ctrl
.fd
);
1245 assert(ftpState
->ctrl
.offset
< ftpState
->ctrl
.size
);
1246 statCounter
.syscalls
.sock
.reads
++;
1248 ftpState
->ctrl
.buf
+ ftpState
->ctrl
.offset
,
1249 ftpState
->ctrl
.size
- ftpState
->ctrl
.offset
);
1251 fd_bytes(fd
, len
, FD_READ
);
1252 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
1253 kb_incr(&statCounter
.server
.ftp
.kbytes_in
, len
);
1255 debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd
, len
);
1257 debug(50, ignoreErrno(errno
) ? 3 : 1) ("ftpReadControlReply: read error: %s\n", xstrerror());
1258 if (ignoreErrno(errno
)) {
1259 ftpScheduleReadControlReply(ftpState
, 0);
1261 ftpFailed(ftpState
, ERR_READ_ERROR
);
1262 /* ftpFailed closes ctrl.fd and frees ftpState */
1268 if (entry
->store_status
== STORE_PENDING
) {
1269 ftpFailed(ftpState
, ERR_FTP_FAILURE
);
1270 /* ftpFailed closes ctrl.fd and frees ftpState */
1273 comm_close(ftpState
->ctrl
.fd
);
1276 len
+= ftpState
->ctrl
.offset
;
1277 ftpState
->ctrl
.offset
= len
;
1278 assert(len
<= ftpState
->ctrl
.size
);
1279 ftpHandleControlReply(ftpState
);
1283 ftpHandleControlReply(FtpStateData
* ftpState
)
1288 wordlistDestroy(&ftpState
->ctrl
.message
);
1289 ftpState
->ctrl
.message
= ftpParseControlReply(ftpState
->ctrl
.buf
,
1290 ftpState
->ctrl
.offset
, &ftpState
->ctrl
.replycode
, &bytes_used
);
1291 if (ftpState
->ctrl
.message
== NULL
) {
1292 /* didn't get complete reply yet */
1293 if (ftpState
->ctrl
.offset
== ftpState
->ctrl
.size
) {
1294 oldbuf
= ftpState
->ctrl
.buf
;
1295 ftpState
->ctrl
.buf
= xcalloc(ftpState
->ctrl
.size
<< 1, 1);
1296 xmemcpy(ftpState
->ctrl
.buf
, oldbuf
, ftpState
->ctrl
.size
);
1297 ftpState
->ctrl
.size
<<= 1;
1298 ftpState
->ctrl
.freefunc(oldbuf
);
1299 ftpState
->ctrl
.freefunc
= xfree
;
1301 ftpScheduleReadControlReply(ftpState
, 0);
1303 } else if (ftpState
->ctrl
.offset
== bytes_used
) {
1304 /* used it all up */
1305 ftpState
->ctrl
.offset
= 0;
1307 /* Got some data past the complete reply */
1308 assert(bytes_used
< ftpState
->ctrl
.offset
);
1309 ftpState
->ctrl
.offset
-= bytes_used
;
1310 xmemmove(ftpState
->ctrl
.buf
, ftpState
->ctrl
.buf
+ bytes_used
,
1311 ftpState
->ctrl
.offset
);
1313 /* Move the last line of the reply message to ctrl.last_reply */
1314 for (W
= &ftpState
->ctrl
.message
; (*W
)->next
; W
= &(*W
)->next
);
1315 safe_free(ftpState
->ctrl
.last_reply
);
1316 ftpState
->ctrl
.last_reply
= xstrdup((*W
)->key
);
1318 /* Copy the rest of the message to cwd_message to be printed in
1321 wordlistAddWl(&ftpState
->cwd_message
, ftpState
->ctrl
.message
);
1322 debug(9, 8) ("ftpHandleControlReply: state=%d, code=%d\n", ftpState
->state
,
1323 ftpState
->ctrl
.replycode
);
1324 FTP_SM_FUNCS
[ftpState
->state
] (ftpState
);
1327 /* ====================================================================== */
1330 ftpReadWelcome(FtpStateData
* ftpState
)
1332 int code
= ftpState
->ctrl
.replycode
;
1333 debug(9, 3) ("ftpReadWelcome\n");
1334 if (ftpState
->flags
.pasv_only
)
1335 ftpState
->login_att
++;
1336 /* Dont retry if the FTP server accepted the connection */
1337 ftpState
->fwd
->flags
.dont_retry
= 1;
1339 if (ftpState
->ctrl
.message
) {
1340 if (strstr(ftpState
->ctrl
.message
->key
, "NetWare"))
1341 ftpState
->flags
.skip_whitespace
= 1;
1343 ftpSendUser(ftpState
);
1344 } else if (code
== 120) {
1345 if (NULL
!= ftpState
->ctrl
.message
)
1346 debug(9, 3) ("FTP server is busy: %s\n",
1347 ftpState
->ctrl
.message
->key
);
1355 ftpSendUser(FtpStateData
* ftpState
)
1357 if (ftpState
->proxy_host
!= NULL
)
1358 snprintf(cbuf
, 1024, "USER %s@%s\r\n",
1360 ftpState
->request
->host
);
1362 snprintf(cbuf
, 1024, "USER %s\r\n", ftpState
->user
);
1363 ftpWriteCommand(cbuf
, ftpState
);
1364 ftpState
->state
= SENT_USER
;
1368 ftpReadUser(FtpStateData
* ftpState
)
1370 int code
= ftpState
->ctrl
.replycode
;
1371 debug(9, 3) ("ftpReadUser\n");
1373 ftpReadPass(ftpState
);
1374 } else if (code
== 331) {
1375 ftpSendPass(ftpState
);
1382 ftpSendPass(FtpStateData
* ftpState
)
1384 snprintf(cbuf
, 1024, "PASS %s\r\n", ftpState
->password
);
1385 ftpWriteCommand(cbuf
, ftpState
);
1386 ftpState
->state
= SENT_PASS
;
1390 ftpReadPass(FtpStateData
* ftpState
)
1392 int code
= ftpState
->ctrl
.replycode
;
1393 debug(9, 3) ("ftpReadPass\n");
1395 ftpSendType(ftpState
);
1402 ftpSendType(FtpStateData
* ftpState
)
1405 const char *filename
;
1408 * Ref section 3.2.2 of RFC 1738
1410 switch (mode
= ftpState
->typecode
) {
1418 if (ftpState
->flags
.isdir
) {
1421 t
= strRChr(ftpState
->request
->urlpath
, '/');
1422 filename
= t
? t
+ 1 : strBuf(ftpState
->request
->urlpath
);
1423 mode
= mimeGetTransferMode(filename
);
1428 ftpState
->flags
.binary
= 1;
1430 ftpState
->flags
.binary
= 0;
1431 snprintf(cbuf
, 1024, "TYPE %c\r\n", mode
);
1432 ftpWriteCommand(cbuf
, ftpState
);
1433 ftpState
->state
= SENT_TYPE
;
1437 ftpReadType(FtpStateData
* ftpState
)
1439 int code
= ftpState
->ctrl
.replycode
;
1442 debug(9, 3) ("This is ftpReadType\n");
1444 p
= path
= xstrdup(strBuf(ftpState
->request
->urlpath
));
1449 p
+= strcspn(p
, "/");
1452 rfc1738_unescape(d
);
1453 wordlistAdd(&ftpState
->pathcomps
, d
);
1456 if (ftpState
->pathcomps
)
1457 ftpTraverseDirectory(ftpState
);
1459 ftpListDir(ftpState
);
1466 ftpTraverseDirectory(FtpStateData
* ftpState
)
1469 debug(9, 4) ("ftpTraverseDirectory %s\n",
1470 ftpState
->filepath
? ftpState
->filepath
: "<NULL>");
1472 safe_free(ftpState
->filepath
);
1474 if (ftpState
->pathcomps
== NULL
) {
1475 debug(9, 3) ("the final component was a directory\n");
1476 ftpListDir(ftpState
);
1479 /* Go to next path component */
1480 w
= ftpState
->pathcomps
;
1481 ftpState
->filepath
= w
->key
;
1482 ftpState
->pathcomps
= w
->next
;
1483 memFree(w
, MEM_WORDLIST
);
1484 /* Check if we are to CWD or RETR */
1485 if (ftpState
->pathcomps
!= NULL
|| ftpState
->flags
.isdir
) {
1486 ftpSendCwd(ftpState
);
1488 debug(9, 3) ("final component is probably a file\n");
1489 ftpGetFile(ftpState
);
1495 ftpSendCwd(FtpStateData
* ftpState
)
1497 char *path
= ftpState
->filepath
;
1498 debug(9, 3) ("ftpSendCwd\n");
1499 if (!strcmp(path
, "..") || !strcmp(path
, "/")) {
1500 ftpState
->flags
.no_dotdot
= 1;
1502 ftpState
->flags
.no_dotdot
= 0;
1505 snprintf(cbuf
, 1024, "CWD %s\r\n", path
);
1507 snprintf(cbuf
, 1024, "CWD\r\n");
1508 ftpWriteCommand(cbuf
, ftpState
);
1509 ftpState
->state
= SENT_CWD
;
1513 ftpReadCwd(FtpStateData
* ftpState
)
1515 int code
= ftpState
->ctrl
.replycode
;
1516 debug(9, 3) ("This is ftpReadCwd\n");
1517 if (code
>= 200 && code
< 300) {
1519 ftpUnhack(ftpState
);
1520 /* Reset cwd_message to only include the last message */
1521 if (ftpState
->cwd_message
)
1522 wordlistDestroy(&ftpState
->cwd_message
);
1523 ftpState
->cwd_message
= ftpState
->ctrl
.message
;
1524 ftpState
->ctrl
.message
= NULL
;
1525 /* Continue to traverse the path */
1526 ftpTraverseDirectory(ftpState
);
1529 if (!ftpState
->flags
.put
)
1532 ftpSendMkdir(ftpState
);
1537 ftpSendMkdir(FtpStateData
* ftpState
)
1539 char *path
= ftpState
->filepath
;
1540 debug(9, 3) ("ftpSendMkdir: with path=%s\n", path
);
1541 snprintf(cbuf
, 1024, "MKD %s\r\n", path
);
1542 ftpWriteCommand(cbuf
, ftpState
);
1543 ftpState
->state
= SENT_MKDIR
;
1547 ftpReadMkdir(FtpStateData
* ftpState
)
1549 char *path
= ftpState
->filepath
;
1550 int code
= ftpState
->ctrl
.replycode
;
1552 debug(9, 3) ("ftpReadMkdir: path %s, code %d\n", path
, code
);
1553 if (code
== 257) { /* success */
1554 ftpSendCwd(ftpState
);
1555 } else if (code
== 550) { /* dir exists */
1556 if (ftpState
->flags
.put_mkdir
) {
1557 ftpState
->flags
.put_mkdir
= 1;
1558 ftpSendCwd(ftpState
);
1560 ftpSendReply(ftpState
);
1562 ftpSendReply(ftpState
);
1566 ftpGetFile(FtpStateData
* ftpState
)
1568 assert(*ftpState
->filepath
!= '\0');
1569 ftpState
->flags
.isdir
= 0;
1570 ftpSendMdtm(ftpState
);
1574 ftpListDir(FtpStateData
* ftpState
)
1576 if (!ftpState
->flags
.isdir
) {
1577 debug(9, 3) ("Directory path did not end in /\n");
1578 strcat(ftpState
->title_url
, "/");
1579 ftpState
->flags
.isdir
= 1;
1580 ftpState
->flags
.use_base
= 1;
1582 ftpSendPasv(ftpState
);
1586 ftpSendMdtm(FtpStateData
* ftpState
)
1588 assert(*ftpState
->filepath
!= '\0');
1589 snprintf(cbuf
, 1024, "MDTM %s\r\n", ftpState
->filepath
);
1590 ftpWriteCommand(cbuf
, ftpState
);
1591 ftpState
->state
= SENT_MDTM
;
1595 ftpReadMdtm(FtpStateData
* ftpState
)
1597 int code
= ftpState
->ctrl
.replycode
;
1598 debug(9, 3) ("This is ftpReadMdtm\n");
1600 ftpState
->mdtm
= parse_iso3307_time(ftpState
->ctrl
.last_reply
);
1601 ftpUnhack(ftpState
);
1602 } else if (code
< 0) {
1605 ftpSendSize(ftpState
);
1609 ftpSendSize(FtpStateData
* ftpState
)
1611 /* Only send SIZE for binary transfers. The returned size
1612 * is useless on ASCII transfers */
1613 if (ftpState
->flags
.binary
) {
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
;
1620 /* Skip to next state no non-binary transfers */
1621 ftpSendPasv(ftpState
);
1625 ftpReadSize(FtpStateData
* ftpState
)
1627 int code
= ftpState
->ctrl
.replycode
;
1628 debug(9, 3) ("This is ftpReadSize\n");
1630 ftpUnhack(ftpState
);
1631 ftpState
->size
= atoi(ftpState
->ctrl
.last_reply
);
1632 if (ftpState
->size
== 0) {
1633 debug(9, 2) ("ftpReadSize: SIZE reported %s on %s\n",
1634 ftpState
->ctrl
.last_reply
,
1635 ftpState
->title_url
);
1636 ftpState
->size
= -1;
1638 } else if (code
< 0) {
1641 ftpSendPasv(ftpState
);
1645 ftpSendPasv(FtpStateData
* ftpState
)
1648 struct sockaddr_in addr
;
1650 if (ftpState
->request
->method
== METHOD_HEAD
) {
1651 /* Terminate here for HEAD requests */
1652 ftpAppendSuccessHeader(ftpState
);
1653 storeTimestampsSet(ftpState
->entry
);
1654 fwdComplete(ftpState
->fwd
);
1655 ftpSendQuit(ftpState
);
1658 if (ftpState
->data
.fd
>= 0) {
1659 if (!ftpState
->flags
.datachannel_hack
) {
1660 /* We are already connected, reuse this connection. */
1661 ftpRestOrList(ftpState
);
1664 /* Close old connection */
1665 comm_close(ftpState
->data
.fd
);
1666 ftpState
->data
.fd
= -1;
1669 if (!ftpState
->flags
.pasv_supported
) {
1670 ftpSendPort(ftpState
);
1673 addr_len
= sizeof(addr
);
1674 if (getsockname(ftpState
->ctrl
.fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1675 debug(9, 0) ("ftpSendPasv: getsockname(%d,..): %s\n",
1676 ftpState
->ctrl
.fd
, xstrerror());
1677 addr
.sin_addr
= Config
.Addrs
.tcp_outgoing
;
1679 /* Open data channel with the same local address as control channel */
1680 fd
= comm_open(SOCK_STREAM
,
1685 storeUrl(ftpState
->entry
));
1686 debug(9, 3) ("ftpSendPasv: Unconnected data socket created on FD %d\n", fd
);
1692 * No comm_add_close_handler() here. If we have both ctrl and
1693 * data FD's call ftpStateFree() upon close, then we have
1694 * to delete the close handler which did NOT get called
1695 * to prevent ftpStateFree() getting called twice.
1696 * Instead we'll always call comm_close() on the ctrl FD.
1698 ftpState
->data
.fd
= fd
;
1699 snprintf(cbuf
, 1024, "PASV\r\n");
1700 ftpWriteCommand(cbuf
, ftpState
);
1701 ftpState
->state
= SENT_PASV
;
1703 * ugly hack for ftp servers like ftp.netscape.com that sometimes
1704 * dont acknowledge PORT commands.
1706 commSetTimeout(ftpState
->data
.fd
, 15, ftpTimeout
, ftpState
);
1710 ftpReadPasv(FtpStateData
* ftpState
)
1712 int code
= ftpState
->ctrl
.replycode
;
1717 int fd
= ftpState
->data
.fd
;
1718 char *buf
= ftpState
->ctrl
.last_reply
;
1719 LOCAL_ARRAY(char, junk
, 1024);
1720 debug(9, 3) ("This is ftpReadPasv\n");
1722 debug(9, 3) ("PASV not supported by remote end\n");
1723 ftpSendPort(ftpState
);
1726 if ((int) strlen(buf
) > 1024) {
1727 debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n");
1728 ftpSendPort(ftpState
);
1731 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1732 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
1733 debug(9, 5) ("scanning: %s\n", buf
);
1734 n
= sscanf(buf
, "%[^0123456789]%d,%d,%d,%d,%d,%d",
1735 junk
, &h1
, &h2
, &h3
, &h4
, &p1
, &p2
);
1736 if (n
!= 7 || p1
< 0 || p2
< 0 || p1
> 255 || p2
> 255) {
1737 debug(9, 3) ("Bad 227 reply\n");
1738 debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n
, p1
, p2
);
1739 ftpSendPort(ftpState
);
1742 snprintf(junk
, 1024, "%d.%d.%d.%d", h1
, h2
, h3
, h4
);
1743 if (!safe_inet_addr(junk
, NULL
)) {
1744 debug(9, 1) ("unsafe address (%s)\n", junk
);
1745 ftpSendPort(ftpState
);
1748 port
= ((p1
<< 8) + p2
);
1750 debug(9, 1) ("ftpReadPasv: Invalid PASV reply: %s\n", buf
);
1751 ftpSendPort(ftpState
);
1754 debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk
, port
);
1755 ftpState
->data
.port
= port
;
1756 ftpState
->data
.host
= xstrdup(junk
);
1757 safe_free(ftpState
->ctrl
.last_command
);
1758 safe_free(ftpState
->ctrl
.last_reply
);
1759 ftpState
->ctrl
.last_command
= xstrdup("Connect to server data port");
1760 commConnectStart(fd
, junk
, port
, ftpPasvCallback
, ftpState
);
1764 ftpPasvCallback(int fd
, int status
, void *data
)
1766 FtpStateData
*ftpState
= data
;
1767 debug(9, 3) ("ftpPasvCallback\n");
1768 if (status
!= COMM_OK
) {
1769 debug(9, 2) ("ftpPasvCallback: failed to connect. Retrying without PASV.\n");
1770 ftpState
->fwd
->flags
.dont_retry
= 0; /* this is a retryable error */
1771 ftpState
->fwd
->flags
.ftp_pasv_failed
= 1;
1772 ftpFailed(ftpState
, ERR_NONE
);
1773 /* ftpFailed closes ctrl.fd and frees ftpState */
1776 ftpRestOrList(ftpState
);
1780 ftpOpenListenSocket(FtpStateData
* ftpState
, int fallback
)
1783 struct sockaddr_in addr
;
1788 * Tear down any old data connection if any. We are about to
1789 * establish a new one.
1791 if (ftpState
->data
.fd
> 0) {
1792 comm_close(ftpState
->data
.fd
);
1793 ftpState
->data
.fd
= -1;
1796 * Set up a listen socket on the same local address as the
1797 * control connection.
1799 addr_len
= sizeof(addr
);
1800 if (getsockname(ftpState
->ctrl
.fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1801 debug(9, 0) ("ftpOpenListenSocket: getsockname(%d,..): %s\n",
1802 ftpState
->ctrl
.fd
, xstrerror());
1806 * REUSEADDR is needed in fallback mode, since the same port is
1807 * used for both control and data.
1810 setsockopt(ftpState
->ctrl
.fd
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof(on
));
1811 port
= ntohs(addr
.sin_port
);
1813 fd
= comm_open(SOCK_STREAM
,
1817 COMM_NONBLOCKING
| (fallback
? COMM_REUSEADDR
: 0),
1818 storeUrl(ftpState
->entry
));
1819 debug(9, 3) ("ftpOpenListenSocket: Unconnected data socket created on FD %d\n", fd
);
1821 debug(9, 0) ("ftpOpenListenSocket: comm_open failed\n");
1824 if (comm_listen(fd
) < 0) {
1828 ftpState
->data
.fd
= fd
;
1829 ftpState
->data
.port
= comm_local_port(fd
);
1830 ftpState
->data
.host
= NULL
;
1835 ftpSendPort(FtpStateData
* ftpState
)
1838 struct sockaddr_in addr
;
1840 unsigned char *addrptr
;
1841 unsigned char *portptr
;
1842 debug(9, 3) ("This is ftpSendPort\n");
1843 ftpState
->flags
.pasv_supported
= 0;
1844 fd
= ftpOpenListenSocket(ftpState
, 0);
1845 addr_len
= sizeof(addr
);
1846 if (getsockname(fd
, (struct sockaddr
*) &addr
, &addr_len
)) {
1847 debug(9, 0) ("ftpSendPort: getsockname(%d,..): %s\n", fd
, xstrerror());
1848 /* XXX Need to set error message */
1852 addrptr
= (unsigned char *) &addr
.sin_addr
.s_addr
;
1853 portptr
= (unsigned char *) &addr
.sin_port
;
1854 snprintf(cbuf
, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
1855 addrptr
[0], addrptr
[1], addrptr
[2], addrptr
[3],
1856 portptr
[0], portptr
[1]);
1857 ftpWriteCommand(cbuf
, ftpState
);
1858 ftpState
->state
= SENT_PORT
;
1862 ftpReadPort(FtpStateData
* ftpState
)
1864 int code
= ftpState
->ctrl
.replycode
;
1865 debug(9, 3) ("This is ftpReadPort\n");
1867 /* Fall back on using the same port as the control connection */
1868 debug(9, 3) ("PORT not supported by remote end\n");
1869 ftpOpenListenSocket(ftpState
, 1);
1871 ftpRestOrList(ftpState
);
1874 /* "read" handler to accept data connection */
1876 ftpAcceptDataConnection(int fd
, void *data
)
1878 FtpStateData
*ftpState
= data
;
1879 struct sockaddr_in my_peer
, me
;
1880 debug(9, 3) ("ftpAcceptDataConnection\n");
1882 if (EBIT_TEST(ftpState
->entry
->flags
, ENTRY_ABORTED
)) {
1883 comm_close(ftpState
->ctrl
.fd
);
1886 fd
= comm_accept(fd
, &my_peer
, &me
);
1888 debug(9, 1) ("ftpHandleDataAccept: comm_accept(%d): %s", fd
, xstrerror());
1889 /* XXX Need to set error message */
1893 /* Replace the Listen socket with the accepted data socket */
1894 comm_close(ftpState
->data
.fd
);
1895 debug(9, 3) ("ftpAcceptDataConnection: Connected data socket on FD %d\n", fd
);
1896 ftpState
->data
.fd
= fd
;
1897 ftpState
->data
.port
= ntohs(my_peer
.sin_port
);
1898 ftpState
->data
.host
= xstrdup(inet_ntoa(my_peer
.sin_addr
));
1899 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
1900 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
1902 /* XXX We should have a flag to track connect state...
1903 * host NULL -> not connected, port == local port
1904 * host set -> connected, port == remote port
1906 /* Restart state (SENT_NLST/LIST/RETR) */
1907 FTP_SM_FUNCS
[ftpState
->state
] (ftpState
);
1911 ftpRestOrList(FtpStateData
* ftpState
)
1913 debug(9, 3) ("This is ftpRestOrList\n");
1914 if (ftpState
->typecode
== 'D') {
1915 ftpState
->flags
.isdir
= 1;
1916 ftpState
->flags
.use_base
= 1;
1917 if (ftpState
->flags
.put
) {
1918 ftpSendMkdir(ftpState
); /* PUT name;type=d */
1920 ftpSendNlst(ftpState
); /* GET name;type=d sec 3.2.2 of RFC 1738 */
1922 } else if (ftpState
->flags
.put
) {
1923 debug(9, 3) ("ftpRestOrList: Sending STOR request...\n");
1924 ftpSendStor(ftpState
);
1925 } else if (ftpState
->flags
.isdir
)
1926 ftpSendList(ftpState
);
1927 else if (ftpRestartable(ftpState
))
1928 ftpSendRest(ftpState
);
1930 ftpSendRetr(ftpState
);
1934 ftpSendStor(FtpStateData
* ftpState
)
1936 if (ftpState
->filepath
!= NULL
) {
1937 /* Plain file upload */
1938 snprintf(cbuf
, 1024, "STOR %s\r\n", ftpState
->filepath
);
1939 ftpWriteCommand(cbuf
, ftpState
);
1940 ftpState
->state
= SENT_STOR
;
1941 } else if (httpHeaderGetInt(&ftpState
->request
->header
, HDR_CONTENT_LENGTH
) > 0) {
1942 /* File upload without a filename. use STOU to generate one */
1943 snprintf(cbuf
, 1024, "STOU\r\n");
1944 ftpWriteCommand(cbuf
, ftpState
);
1945 ftpState
->state
= SENT_STOR
;
1947 /* No file to transfer. Only create directories if needed */
1948 ftpSendReply(ftpState
);
1953 ftpReadStor(FtpStateData
* ftpState
)
1955 int code
= ftpState
->ctrl
.replycode
;
1956 debug(9, 3) ("This is ftpReadStor\n");
1957 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
1958 /* Begin data transfer */
1959 debug(9, 3) ("ftpReadStor: starting data transfer\n");
1960 commSetSelect(ftpState
->data
.fd
,
1964 Config
.Timeout
.read
);
1966 * Cancel the timeout on the Control socket and
1967 * establish one on the data socket.
1969 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
1970 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
1972 ftpState
->state
= WRITING_DATA
;
1973 debug(9, 3) ("ftpReadStor: writing data channel\n");
1974 } else if (code
== 150) {
1975 /* Accept data channel */
1976 debug(9, 3) ("ftpReadStor: accepting data channel\n");
1977 commSetSelect(ftpState
->data
.fd
,
1979 ftpAcceptDataConnection
,
1983 debug(9, 3) ("ftpReadStor: Unexpected reply code %s\n", code
);
1989 ftpSendRest(FtpStateData
* ftpState
)
1991 snprintf(cbuf
, 1024, "REST %d\r\n", ftpState
->restart_offset
);
1992 ftpWriteCommand(cbuf
, ftpState
);
1993 ftpState
->state
= SENT_REST
;
1997 ftpRestartable(FtpStateData
* ftpState
)
1999 if (ftpState
->restart_offset
> 0)
2001 if (!ftpState
->request
->range
)
2003 if (!ftpState
->flags
.binary
)
2005 if (ftpState
->size
<= 0)
2008 ftpState
->restart_offset
= httpHdrRangeLowestOffset(ftpState
->request
->range
, (size_t) ftpState
->size
);
2009 if (ftpState
->restart_offset
<= 0)
2015 ftpReadRest(FtpStateData
* ftpState
)
2017 int code
= ftpState
->ctrl
.replycode
;
2018 debug(9, 3) ("This is ftpReadRest\n");
2019 assert(ftpState
->restart_offset
> 0);
2021 ftpState
->restarted_offset
= ftpState
->restart_offset
;
2022 ftpSendRetr(ftpState
);
2023 } else if (code
> 0) {
2024 debug(9, 3) ("ftpReadRest: REST not supported\n");
2025 ftpState
->flags
.rest_supported
= 0;
2026 ftpSendRetr(ftpState
);
2033 ftpSendList(FtpStateData
* ftpState
)
2035 if (ftpState
->filepath
) {
2036 ftpState
->flags
.use_base
= 1;
2037 snprintf(cbuf
, 1024, "LIST %s\r\n", ftpState
->filepath
);
2039 snprintf(cbuf
, 1024, "LIST\r\n");
2041 ftpWriteCommand(cbuf
, ftpState
);
2042 ftpState
->state
= SENT_LIST
;
2046 ftpSendNlst(FtpStateData
* ftpState
)
2048 ftpState
->flags
.tried_nlst
= 1;
2049 if (ftpState
->filepath
) {
2050 ftpState
->flags
.use_base
= 1;
2051 snprintf(cbuf
, 1024, "NLST %s\r\n", ftpState
->filepath
);
2053 snprintf(cbuf
, 1024, "NLST\r\n");
2055 ftpWriteCommand(cbuf
, ftpState
);
2056 ftpState
->state
= SENT_NLST
;
2060 ftpReadList(FtpStateData
* ftpState
)
2062 int code
= ftpState
->ctrl
.replycode
;
2063 debug(9, 3) ("This is ftpReadList\n");
2064 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
2065 /* Begin data transfer */
2066 ftpAppendSuccessHeader(ftpState
);
2067 commSetSelect(ftpState
->data
.fd
,
2071 Config
.Timeout
.read
);
2072 commSetDefer(ftpState
->data
.fd
, fwdCheckDeferRead
, ftpState
->entry
);
2073 ftpState
->state
= READING_DATA
;
2075 * Cancel the timeout on the Control socket and establish one
2076 * on the data socket
2078 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2079 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
, ftpState
);
2081 } else if (code
== 150) {
2082 /* Accept data channel */
2083 commSetSelect(ftpState
->data
.fd
,
2085 ftpAcceptDataConnection
,
2089 * Cancel the timeout on the Control socket and establish one
2090 * on the data socket
2092 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2093 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
, ftpState
);
2095 } else if (!ftpState
->flags
.tried_nlst
&& code
> 300) {
2096 ftpSendNlst(ftpState
);
2104 ftpSendRetr(FtpStateData
* ftpState
)
2106 assert(ftpState
->filepath
!= NULL
);
2107 snprintf(cbuf
, 1024, "RETR %s\r\n", ftpState
->filepath
);
2108 ftpWriteCommand(cbuf
, ftpState
);
2109 ftpState
->state
= SENT_RETR
;
2113 ftpReadRetr(FtpStateData
* ftpState
)
2115 int code
= ftpState
->ctrl
.replycode
;
2116 debug(9, 3) ("This is ftpReadRetr\n");
2117 if (code
== 125 || (code
== 150 && ftpState
->data
.host
)) {
2118 /* Begin data transfer */
2119 debug(9, 3) ("ftpReadRetr: reading data channel\n");
2120 ftpAppendSuccessHeader(ftpState
);
2121 commSetSelect(ftpState
->data
.fd
,
2125 Config
.Timeout
.read
);
2126 commSetDefer(ftpState
->data
.fd
, fwdCheckDeferRead
, ftpState
->entry
);
2127 ftpState
->state
= READING_DATA
;
2129 * Cancel the timeout on the Control socket and establish one
2130 * on the data socket
2132 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2133 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
2135 } else if (code
== 150) {
2136 /* Accept data channel */
2137 commSetSelect(ftpState
->data
.fd
,
2139 ftpAcceptDataConnection
,
2143 * Cancel the timeout on the Control socket and establish one
2144 * on the data socket
2146 commSetTimeout(ftpState
->ctrl
.fd
, -1, NULL
, NULL
);
2147 commSetTimeout(ftpState
->data
.fd
, Config
.Timeout
.read
, ftpTimeout
,
2149 } else if (code
>= 300) {
2150 if (!ftpState
->flags
.try_slash_hack
) {
2151 /* Try this as a directory missing trailing slash... */
2152 ftpHackShortcut(ftpState
, ftpSendCwd
);
2162 ftpReadTransferDone(FtpStateData
* ftpState
)
2164 int code
= ftpState
->ctrl
.replycode
;
2165 debug(9, 3) ("This is ftpReadTransferDone\n");
2167 /* Connection closed; retrieval done. */
2168 if (ftpState
->flags
.html_header_sent
)
2169 ftpListingFinish(ftpState
);
2170 ftpSendQuit(ftpState
);
2171 } else { /* != 226 */
2172 debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n",
2174 ftpFailed(ftpState
, ERR_FTP_FAILURE
);
2175 /* ftpFailed closes ctrl.fd and frees ftpState */
2180 /* This will be called when there is data available to put */
2182 ftpRequestBody(char *buf
, size_t size
, void *data
)
2184 FtpStateData
*ftpState
= (FtpStateData
*) data
;
2185 debug(9, 3) ("ftpRequestBody: buf=%p size=%d ftpState=%p\n", buf
, size
, data
);
2186 ftpState
->data
.offset
= size
;
2189 comm_write(ftpState
->data
.fd
, buf
, size
, ftpDataWriteCallback
, data
, NULL
);
2190 } else if (size
< 0) {
2192 debug(9, 1) ("ftpRequestBody: request aborted");
2193 ftpFailed(ftpState
, ERR_READ_ERROR
);
2194 } else if (size
== 0) {
2195 /* End of transfer */
2196 ftpDataComplete(ftpState
);
2200 /* This will be called when the put write is completed */
2202 ftpDataWriteCallback(int fd
, char *buf
, size_t size
, int err
, void *data
)
2204 FtpStateData
*ftpState
= (FtpStateData
*) data
;
2206 /* Shedule the rest of the request */
2207 clientReadBody(ftpState
->request
, ftpState
->data
.buf
, ftpState
->data
.size
, ftpRequestBody
, ftpState
);
2209 debug(9, 1) ("ftpDataWriteCallback: write error: %s\n", xstrerror());
2210 ftpFailed(ftpState
, ERR_WRITE_ERROR
);
2215 ftpDataWrite(int ftp
, void *data
)
2217 FtpStateData
*ftpState
= (FtpStateData
*) data
;
2218 debug(9, 3) ("ftpDataWrite\n");
2219 /* This starts the body transfer */
2220 clientReadBody(ftpState
->request
, ftpState
->data
.buf
, ftpState
->data
.size
, ftpRequestBody
, ftpState
);
2224 ftpWriteTransferDone(FtpStateData
* ftpState
)
2226 int code
= ftpState
->ctrl
.replycode
;
2227 debug(9, 3) ("This is ftpWriteTransferDone\n");
2229 debug(9, 1) ("ftpReadTransferDone: Got code %d after sending data\n",
2231 ftpFailed(ftpState
, ERR_FTP_PUT_ERROR
);
2234 storeTimestampsSet(ftpState
->entry
); /* XXX Is this needed? */
2235 ftpSendReply(ftpState
);
2239 ftpSendQuit(FtpStateData
* ftpState
)
2241 assert(ftpState
->ctrl
.fd
> -1);
2242 snprintf(cbuf
, 1024, "QUIT\r\n");
2243 ftpWriteCommand(cbuf
, ftpState
);
2244 ftpState
->state
= SENT_QUIT
;
2248 ftpReadQuit(FtpStateData
* ftpState
)
2250 comm_close(ftpState
->ctrl
.fd
);
2254 ftpTrySlashHack(FtpStateData
* ftpState
)
2257 ftpState
->flags
.try_slash_hack
= 1;
2258 /* Free old paths */
2259 if (ftpState
->pathcomps
)
2260 wordlistDestroy(&ftpState
->pathcomps
);
2261 safe_free(ftpState
->filepath
);
2262 /* Build the new path (urlpath begins with /) */
2263 path
= xstrdup(strBuf(ftpState
->request
->urlpath
));
2264 rfc1738_unescape(path
);
2265 ftpState
->filepath
= path
;
2267 ftpGetFile(ftpState
);
2271 ftpTryDatachannelHack(FtpStateData
* ftpState
)
2273 ftpState
->flags
.datachannel_hack
= 1;
2274 /* we have to undo some of the slash hack... */
2275 if (ftpState
->old_filepath
!= NULL
) {
2276 ftpState
->flags
.try_slash_hack
= 0;
2277 safe_free(ftpState
->filepath
);
2278 ftpState
->filepath
= ftpState
->old_filepath
;
2279 ftpState
->old_filepath
= NULL
;
2281 ftpState
->flags
.tried_nlst
= 0;
2283 if (ftpState
->flags
.isdir
) {
2284 ftpListDir(ftpState
);
2286 ftpGetFile(ftpState
);
2291 /* Forget hack status. Next error is shown to the user */
2293 ftpUnhack(FtpStateData
* ftpState
)
2295 if (ftpState
->old_request
!= NULL
) {
2296 safe_free(ftpState
->old_request
);
2297 safe_free(ftpState
->old_reply
);
2302 ftpHackShortcut(FtpStateData
* ftpState
, FTPSM
* nextState
)
2304 /* Clear some unwanted state */
2305 ftpState
->restarted_offset
= 0;
2306 ftpState
->restart_offset
= 0;
2307 /* Save old error message & some state info */
2308 if (ftpState
->old_request
== NULL
) {
2309 ftpState
->old_request
= ftpState
->ctrl
.last_command
;
2310 ftpState
->ctrl
.last_command
= NULL
;
2311 ftpState
->old_reply
= ftpState
->ctrl
.last_reply
;
2312 ftpState
->ctrl
.last_reply
= NULL
;
2313 if (ftpState
->pathcomps
== NULL
&& ftpState
->filepath
!= NULL
)
2314 ftpState
->old_filepath
= xstrdup(ftpState
->filepath
);
2316 /* Jump to the "hack" state */
2317 nextState(ftpState
);
2321 ftpFail(FtpStateData
* ftpState
)
2323 debug(9, 3) ("ftpFail\n");
2324 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
2325 if (!ftpState
->flags
.isdir
&& /* Not a directory */
2326 !ftpState
->flags
.try_slash_hack
&& /* Not in slash hack */
2327 ftpState
->mdtm
<= 0 && ftpState
->size
< 0 && /* Not known as a file */
2328 strNCaseCmp(ftpState
->request
->urlpath
, "/%2f", 4) != 0) { /* No slash encoded */
2329 switch (ftpState
->state
) {
2332 /* Try the / hack */
2333 ftpHackShortcut(ftpState
, ftpTrySlashHack
);
2339 /* Try to reopen datachannel */
2340 if (!ftpState
->flags
.datachannel_hack
&&
2341 ftpState
->pathcomps
== NULL
) {
2342 switch (ftpState
->state
) {
2346 /* Try to reopen datachannel */
2347 ftpHackShortcut(ftpState
, ftpTryDatachannelHack
);
2353 ftpFailed(ftpState
, ERR_NONE
);
2354 /* ftpFailed closes ctrl.fd and frees ftpState */
2358 ftpFailed(FtpStateData
* ftpState
, err_type error
)
2360 StoreEntry
*entry
= ftpState
->entry
;
2361 if (entry
->mem_obj
->inmem_hi
== 0)
2362 ftpFailedErrorMessage(ftpState
, error
);
2363 if (ftpState
->data
.fd
> -1) {
2364 comm_close(ftpState
->data
.fd
);
2365 ftpState
->data
.fd
= -1;
2367 comm_close(ftpState
->ctrl
.fd
);
2371 ftpFailedErrorMessage(FtpStateData
* ftpState
, err_type error
)
2374 char *command
, *reply
;
2375 /* Translate FTP errors into HTTP errors */
2379 switch (ftpState
->state
) {
2382 if (ftpState
->ctrl
.replycode
> 500)
2383 err
= errorCon(ERR_FTP_FORBIDDEN
, HTTP_FORBIDDEN
);
2384 else if (ftpState
->ctrl
.replycode
== 421)
2385 err
= errorCon(ERR_FTP_UNAVAILABLE
, HTTP_SERVICE_UNAVAILABLE
);
2389 if (ftpState
->ctrl
.replycode
== 550)
2390 err
= errorCon(ERR_FTP_NOT_FOUND
, HTTP_NOT_FOUND
);
2396 case ERR_READ_TIMEOUT
:
2397 err
= errorCon(error
, HTTP_GATEWAY_TIMEOUT
);
2400 err
= errorCon(error
, HTTP_BAD_GATEWAY
);
2404 err
= errorCon(ERR_FTP_FAILURE
, HTTP_BAD_GATEWAY
);
2405 err
->xerrno
= errno
;
2406 err
->request
= requestLink(ftpState
->request
);
2407 err
->ftp
.server_msg
= ftpState
->ctrl
.message
;
2408 ftpState
->ctrl
.message
= NULL
;
2409 if (ftpState
->old_request
)
2410 command
= ftpState
->old_request
;
2412 command
= ftpState
->ctrl
.last_command
;
2413 if (command
&& strncmp(command
, "PASS", 4) == 0)
2414 command
= "PASS <yourpassword>";
2415 if (ftpState
->old_reply
)
2416 reply
= ftpState
->old_reply
;
2418 reply
= ftpState
->ctrl
.last_reply
;
2420 err
->ftp
.request
= xstrdup(command
);
2422 err
->ftp
.reply
= xstrdup(reply
);
2423 fwdFail(ftpState
->fwd
, err
);
2427 ftpSendReply(FtpStateData
* ftpState
)
2430 int code
= ftpState
->ctrl
.replycode
;
2431 http_status http_code
;
2432 err_type err_code
= ERR_NONE
;
2433 debug(9, 5) ("ftpSendReply: %s, code %d\n",
2434 storeUrl(ftpState
->entry
), code
);
2435 if (cbdataValid(ftpState
))
2436 debug(9, 5) ("ftpSendReply: ftpState (%p) is valid!\n", ftpState
);
2438 err_code
= (ftpState
->mdtm
> 0) ? ERR_FTP_PUT_MODIFIED
: ERR_FTP_PUT_CREATED
;
2439 http_code
= (ftpState
->mdtm
> 0) ? HTTP_ACCEPTED
: HTTP_CREATED
;
2440 } else if (code
== 227) {
2441 err_code
= ERR_FTP_PUT_CREATED
;
2442 http_code
= HTTP_CREATED
;
2444 err_code
= ERR_FTP_PUT_ERROR
;
2445 http_code
= HTTP_INTERNAL_SERVER_ERROR
;
2447 err
= errorCon(err_code
, http_code
);
2448 err
->request
= requestLink(ftpState
->request
);
2449 if (ftpState
->old_request
)
2450 err
->ftp
.request
= xstrdup(ftpState
->old_request
);
2452 err
->ftp
.request
= xstrdup(ftpState
->ctrl
.last_command
);
2453 if (ftpState
->old_reply
)
2454 err
->ftp
.reply
= xstrdup(ftpState
->old_reply
);
2456 err
->ftp
.reply
= xstrdup(ftpState
->ctrl
.last_reply
);
2457 errorAppendEntry(ftpState
->entry
, err
);
2458 storeBufferFlush(ftpState
->entry
);
2459 ftpSendQuit(ftpState
);
2463 ftpAppendSuccessHeader(FtpStateData
* ftpState
)
2465 char *mime_type
= NULL
;
2466 char *mime_enc
= NULL
;
2467 String urlpath
= ftpState
->request
->urlpath
;
2468 const char *filename
= NULL
;
2469 const char *t
= NULL
;
2470 StoreEntry
*e
= ftpState
->entry
;
2471 StoreEntry
*pe
= NULL
;
2472 http_reply
*reply
= e
->mem_obj
->reply
;
2473 http_version_t version
;
2474 if (ftpState
->flags
.http_header_sent
)
2476 ftpState
->flags
.http_header_sent
= 1;
2477 assert(e
->mem_obj
->inmem_hi
== 0);
2478 EBIT_CLR(e
->flags
, ENTRY_FWD_HDR_WAIT
);
2479 filename
= (t
= strRChr(urlpath
, '/')) ? t
+ 1 : strBuf(urlpath
);
2480 if (ftpState
->flags
.isdir
) {
2481 mime_type
= "text/html";
2483 switch (ftpState
->typecode
) {
2485 mime_type
= "application/octet-stream";
2486 mime_enc
= mimeGetContentEncoding(filename
);
2489 mime_type
= "text/plain";
2492 mime_type
= mimeGetContentType(filename
);
2493 mime_enc
= mimeGetContentEncoding(filename
);
2498 httpReplyReset(reply
);
2499 /* set standard stuff */
2500 if (ftpState
->restarted_offset
) {
2502 HttpHdrRangeSpec range_spec
;
2503 range_spec
.offset
= ftpState
->restarted_offset
;
2504 range_spec
.length
= ftpState
->size
- ftpState
->restarted_offset
;
2505 httpBuildVersion(&version
, 1, 0);
2506 httpReplySetHeaders(reply
, version
, HTTP_PARTIAL_CONTENT
, "Gatewaying",
2507 mime_type
, ftpState
->size
- ftpState
->restarted_offset
, ftpState
->mdtm
, -2);
2508 httpHeaderAddContRange(&reply
->header
, range_spec
, ftpState
->size
);
2511 httpBuildVersion(&version
, 1, 0);
2512 httpReplySetHeaders(reply
, version
, HTTP_OK
, "Gatewaying",
2513 mime_type
, ftpState
->size
, ftpState
->mdtm
, -2);
2515 /* additional info */
2517 httpHeaderPutStr(&reply
->header
, HDR_CONTENT_ENCODING
, mime_enc
);
2518 httpReplySwapOut(reply
, e
);
2519 storeBufferFlush(e
);
2520 reply
->hdr_sz
= e
->mem_obj
->inmem_hi
;
2521 storeTimestampsSet(e
);
2522 if (ftpState
->flags
.authenticated
) {
2524 * Authenticated requests can't be cached. Eject any old cached
2527 pe
= storeGetPublic(e
->mem_obj
->url
, e
->mem_obj
->method
);
2531 } else if (EBIT_TEST(e
->flags
, ENTRY_CACHABLE
) && !ftpState
->restarted_offset
) {
2532 storeSetPublicKey(e
);
2539 ftpAuthRequired(HttpReply
* old_reply
, request_t
* request
, const char *realm
)
2541 ErrorState
*err
= errorCon(ERR_ACCESS_DENIED
, HTTP_UNAUTHORIZED
);
2543 err
->request
= requestLink(request
);
2544 rep
= errorBuildReply(err
);
2545 errorStateFree(err
);
2546 /* add Authenticate header */
2547 httpHeaderPutAuth(&rep
->header
, "Basic", realm
);
2548 /* move new reply to the old one */
2549 httpReplyAbsorb(old_reply
, rep
);
2553 ftpUrlWith2f(const request_t
* request
)
2555 LOCAL_ARRAY(char, buf
, MAX_URL
);
2556 LOCAL_ARRAY(char, loginbuf
, MAX_LOGIN_SZ
+ 1);
2557 LOCAL_ARRAY(char, portbuf
, 32);
2560 if (request
->protocol
!= PROTO_FTP
)
2562 if (request
->port
!= urlDefaultPort(request
->protocol
))
2563 snprintf(portbuf
, 32, ":%d", request
->port
);
2565 if ((int) strlen(request
->login
) > 0) {
2566 strcpy(loginbuf
, request
->login
);
2567 if ((t
= strchr(loginbuf
, ':')))
2569 strcat(loginbuf
, "@");
2571 snprintf(buf
, MAX_URL
, "%s://%s%s%s%s%s",
2572 ProtocolStr
[request
->protocol
],
2577 strBuf(request
->urlpath
));
2578 if ((t
= strchr(buf
, '?')))