From: wessels <> Date: Mon, 26 May 1997 10:04:52 +0000 (+0000) Subject: mostly done inlining all FTP requsts X-Git-Tag: SQUID_3_0_PRE1~4970 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3fdadc70bf49427e4830bec1560ec76f09256685;p=thirdparty%2Fsquid.git mostly done inlining all FTP requsts --- diff --git a/include/util.h b/include/util.h index 5d15187ed6..96a4f91803 100644 --- a/include/util.h +++ b/include/util.h @@ -1,5 +1,5 @@ /* - * $Id: util.h,v 1.29 1997/04/28 04:30:02 wessels Exp $ + * $Id: util.h,v 1.30 1997/05/26 04:04:52 wessels Exp $ * * AUTHOR: Harvest Derived * @@ -153,5 +153,7 @@ void malloc_statistics _PARAMS((void (*)_PARAMS((int, int, void *)), void *)); typedef struct in_addr SIA; extern int safe_inet_addr _PARAMS((const char *, SIA *)); +extern time_t parse_iso3307_time _PARAMS((const char *buf)); +extern char *base64_decode _PARAMS((const char *coded)); #endif /* ndef _UTIL_H_ */ diff --git a/lib/Makefile.in b/lib/Makefile.in index 7e72840985..49b5c74c32 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -3,7 +3,7 @@ # # Darren Hardy, hardy@cs.colorado.edu, April 1994 # -# $Id: Makefile.in,v 1.23 1997/05/14 21:08:14 wessels Exp $ +# $Id: Makefile.in,v 1.24 1997/05/26 04:04:53 wessels Exp $ # prefix = @prefix@ srcdir = @srcdir@ @@ -33,6 +33,7 @@ UTILOBJS = rfc1123.o \ tree.o \ splay.o \ safe_inet_addr.o \ + iso3307.o \ $(LIBOBJS) REGEXOBJS = GNUregex.o LIBS = libmiscutil.a @LIBREGEX@ diff --git a/lib/rfc1738.c b/lib/rfc1738.c index 381a9632a0..d1bd68beea 100644 --- a/lib/rfc1738.c +++ b/lib/rfc1738.c @@ -1,5 +1,5 @@ /* - * $Id: rfc1738.c,v 1.8 1996/11/06 23:14:15 wessels Exp $ + * $Id: rfc1738.c,v 1.9 1997/05/26 04:04:53 wessels Exp $ * * DEBUG: * AUTHOR: Harvest Derived @@ -114,7 +114,6 @@ #include "ansiproto.h" #include "util.h" -#define BIG_BUFSIZ (BUFSIZ * 4) /* * RFC 1738 defines that these characters should be escaped, as well @@ -147,12 +146,19 @@ static char rfc1738_unsafe_chars[] = char * rfc1738_escape(const char *url) { - static char buf[BIG_BUFSIZ]; + static char *buf; + static size_t bufsize = 0; const char *p; char *q; int i, do_escape; - for (p = url, q = &buf[0]; *p != '\0'; p++, q++) { + if (buf == NULL || strlen(url) * 3 > bufsize) { + safe_free(buf); + bufsize = strlen(url) * 3 + 1; + buf = xcalloc (bufsize, 1); + } + + for (p = url, q = buf; *p != '\0'; p++, q++) { do_escape = 0; /* RFC 1738 defines these chars as unsafe */ diff --git a/src/Makefile.in b/src/Makefile.in index e4486101da..0ab033e387 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,7 +1,7 @@ # # Makefile for the Squid Object Cache server # -# $Id: Makefile.in,v 1.71 1997/05/15 01:06:49 wessels Exp $ +# $Id: Makefile.in,v 1.72 1997/05/26 04:04:54 wessels Exp $ # # Uncomment and customize the following to suit your needs: # @@ -33,7 +33,6 @@ VPATH = @srcdir@ DEFAULT_CONFIG_FILE = $(sysconfdir)/squid.conf DEFAULT_MIME_TABLE = $(sysconfdir)/mime.conf -DEFAULT_FTPGET = $(libexecdir)/ftpget DEFAULT_DNSSERVER = $(libexecdir)/dnsserver DEFAULT_CACHE_LOG = $(localstatedir)/logs/cache.log DEFAULT_ACCESS_LOG = $(localstatedir)/logs/access.log @@ -72,7 +71,7 @@ FTPGET_LIBS = -L../lib -lmiscutil $(XTRA_LIBS) PINGER_LIBS = -L../lib -lmiscutil $(XTRA_LIBS) PROGS = squid client -UTILS = dnsserver ftpget unlinkd +UTILS = dnsserver unlinkd SUID_UTILS = pinger CGIPROGS = cachemgr.cgi OBJS = \ @@ -156,9 +155,6 @@ dnsserver: dnsserver.o cachemgr.cgi: cachemgr.o $(CC) -o $@ $(LDFLAGS) cachemgr.o $(CLIENT_LIBS) -ftpget: ftpget.o - $(CC) -o $@ $(LDFLAGS) ftpget.o debug.o mime.o $(FTPGET_LIBS) $(REGEXLIB) - pinger: pinger.o $(CC) -o $@ $(LDFLAGS) pinger.o debug.o $(PINGER_LIBS) diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 13fdecb28b..c9902ca8dd 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -1,5 +1,5 @@ /* - * $Id: cache_cf.cc,v 1.191 1997/05/23 19:45:25 wessels Exp $ + * $Id: cache_cf.cc,v 1.192 1997/05/26 04:04:55 wessels Exp $ * * DEBUG: section 3 Configuration File Parsing * AUTHOR: Harvest Derived @@ -209,6 +209,11 @@ struct SquidConfig Config; #define DefaultOptionsClientDb 1 /* default on */ #define DefaultOptionsQueryIcmp 0 /* default off */ +#define DefaultFtpIconPrefix "internal-" +#define DefaultFtpIconSuffix null_string +#define DefaultFtpListWidth 32 +#define DefaultFtpListWrap 0 + static const char *const T_SECOND_STR = "second"; static const char *const T_MINUTE_STR = "minute"; static const char *const T_HOUR_STR = "hour"; @@ -1430,6 +1435,10 @@ configSetFactoryDefaults(void) Config.Options.enable_purge = DefaultOptionsEnablePurge; Config.Options.client_db = DefaultOptionsClientDb; Config.Options.query_icmp = DefaultOptionsQueryIcmp; + Config.Ftp.icon_prefix = safe_xstrdup(DefaultFtpIconPrefix); + Config.Ftp.icon_suffix = safe_xstrdup(DefaultFtpIconSuffix); + Config.Ftp.list_width = DefaultFtpListWidth; + Config.Ftp.list_wrap = DefaultFtpListWrap; } static void diff --git a/src/comm.cc b/src/comm.cc index ffc3e250bc..bca1aecbac 100644 --- a/src/comm.cc +++ b/src/comm.cc @@ -1,6 +1,6 @@ /* - * $Id: comm.cc,v 1.154 1997/05/22 15:51:51 wessels Exp $ + * $Id: comm.cc,v 1.155 1997/05/26 04:04:56 wessels Exp $ * * DEBUG: section 5 Socket Functions * AUTHOR: Harvest Derived @@ -773,7 +773,6 @@ comm_poll(time_t sec) fatal_dump(NULL); if (shutdown_pending || reconfigure_pending) { serverConnectionsClose(); - ftpServerClose(); dnsShutdownServers(); redirectShutdownServers(); /* shutdown_pending will be set to @@ -926,7 +925,6 @@ comm_select(time_t sec) if (shutdown_pending || reconfigure_pending) { serverConnectionsClose(); - ftpServerClose(); dnsShutdownServers(); redirectShutdownServers(); /* shutdown_pending will be set to diff --git a/src/errorpage.cc b/src/errorpage.cc index 5ceb5346eb..22f4edb6d2 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -1,6 +1,6 @@ /* - * $Id: errorpage.cc,v 1.54 1997/02/28 21:33:36 wessels Exp $ + * $Id: errorpage.cc,v 1.55 1997/05/26 04:04:58 wessels Exp $ * * DEBUG: section 4 Error Generation * AUTHOR: Duane Wessels @@ -86,6 +86,9 @@ static error_data ErrorData[] = {"ERR_READ_ERROR", "Read Error", "The remote site or network may be down. Please try again."}, + {"ERR_WRITE_ERROR", + "Write Error", + "The remote site or network may be down. Please try again."}, {"ERR_CLIENT_ABORT", "Client Aborted", "Client(s) dropped connection before transmission completed.\nObject fetching is aborted.",}, diff --git a/src/ftp.cc b/src/ftp.cc index 6460a76a87..02467952f2 100644 --- a/src/ftp.cc +++ b/src/ftp.cc @@ -1,6 +1,6 @@ /* - * $Id: ftp.cc,v 1.109 1997/05/23 05:33:18 wessels Exp $ + * $Id: ftp.cc,v 1.110 1997/05/26 04:04:58 wessels Exp $ * * DEBUG: section 9 File Transfer Protocol (FTP) * AUTHOR: Harvest Derived @@ -29,90 +29,45 @@ * */ -/* - * Copyright (c) 1994, 1995. All rights reserved. - * - * The Harvest software was developed by the Internet Research Task - * Force Research Group on Resource Discovery (IRTF-RD): - * - * Mic Bowman of Transarc Corporation. - * Peter Danzig of the University of Southern California. - * Darren R. Hardy of the University of Colorado at Boulder. - * Udi Manber of the University of Arizona. - * Michael F. Schwartz of the University of Colorado at Boulder. - * Duane Wessels of the University of Colorado at Boulder. - * - * This copyright notice applies to software in the Harvest - * ``src/'' directory only. Users should consult the individual - * copyright notices in the ``components/'' subdirectories for - * copyright information about other software bundled with the - * Harvest source code distribution. - * - * TERMS OF USE - * - * The Harvest software may be used and re-distributed without - * charge, provided that the software origin and research team are - * cited in any use of the system. Most commonly this is - * accomplished by including a link to the Harvest Home Page - * (http://harvest.cs.colorado.edu/) from the query page of any - * Broker you deploy, as well as in the query result pages. These - * links are generated automatically by the standard Broker - * software distribution. - * - * The Harvest software is provided ``as is'', without express or - * implied warranty, and with no support nor obligation to assist - * in its use, correction, modification or enhancement. We assume - * no liability with respect to the infringement of copyrights, - * trade secrets, or any patents, and are not responsible for - * consequential damages. Proper use of the Harvest software is - * entirely the responsibility of the user. - * - * DERIVATIVE WORKS - * - * Users may make derivative works from the Harvest software, subject - * to the following constraints: - * - * - You must include the above copyright notice and these - * accompanying paragraphs in all forms of derivative works, - * and any documentation and other materials related to such - * distribution and use acknowledge that the software was - * developed at the above institutions. - * - * - You must notify IRTF-RD regarding your distribution of - * the derivative work. - * - * - You must clearly notify users that your are distributing - * a modified version and not the original Harvest software. - * - * - Any derivative product is also subject to these copyright - * and use restrictions. - * - * Note that the Harvest software is NOT in the public domain. We - * retain copyright, as specified above. - * - * HISTORY OF FREE SOFTWARE STATUS - * - * Originally we required sites to license the software in cases - * where they were going to build commercial products/services - * around Harvest. In June 1995 we changed this policy. We now - * allow people to use the core Harvest software (the code found in - * the Harvest ``src/'' directory) for free. We made this change - * in the interest of encouraging the widest possible deployment of - * the technology. The Harvest software is really a reference - * implementation of a set of protocols and formats, some of which - * we intend to standardize. We encourage commercial - * re-implementations of code complying to this set of standards. - */ - #include "squid.h" #define FTP_DELETE_GAP (1<<18) -#define MAGIC_MARKER "\004\004\004" /* No doubt this should be more configurable */ -#define MAGIC_MARKER_SZ 3 -static int ftpget_server_read = -1; -static int ftpget_server_write = -1; -static u_short ftpget_port = 0; +enum { + FTP_ISDIR, + FTP_PASV_SUPPORTED, + FTP_SKIP_WHITESPACE, + FTP_REST_SUPPORTED, + FTP_PASV_ONLY, + FTP_AUTHENTICATED, + FTP_IP_LOOKUP_PENDING, + FTP_HTTP_HEADER_SENT, + FTP_TRIED_NLST, + FTP_USE_BASE, + FTP_ROOT_DIR, + FTP_HTML_HEADER_SENT +}; + +static const char *const crlf = "\r\n"; +static char cbuf[1024]; + +typedef enum { + BEGIN, + SENT_USER, + SENT_PASS, + SENT_TYPE, + SENT_MDTM, + SENT_SIZE, + SENT_PORT, + SENT_PASV, + SENT_CWD, + SENT_LIST, + SENT_NLST, + SENT_REST, + SENT_RETR, + SENT_QUIT, + READING_DATA +} ftp_state_t; typedef struct _Ftpdata { StoreEntry *entry; @@ -120,31 +75,106 @@ typedef struct _Ftpdata { char user[MAX_URL]; char password[MAX_URL]; char *reply_hdr; - int ftp_fd; - int got_marker; /* denotes end of successful request */ int reply_hdr_state; - int authenticated; /* This ftp request is authenticated */ + char *title_url; + int conn_att; + int login_att; + ftp_state_t state; + char *errmsg; + time_t mdtm; + int size; + int flags; + wordlist *pathcomps; + char *filepath; + int restart_offset; + int rest_att; + char *proxy_host; + size_t list_width; + wordlist *cwd_message; + struct { + int fd; + char *buf; + size_t size; + off_t offset; + FREE *freefunc; + wordlist *message; + char *last_message; + int replycode; + } ctrl; + struct { + int fd; + char *buf; + size_t size; + off_t offset; + FREE *freefunc; + } data; } FtpStateData; -typedef struct ftp_ctrl_t { - request_t *request; - StoreEntry *entry; -} ftp_ctrl_t; +typedef struct { + char type; + int size; + char *date; + char *name; + char *showname; + char *link; +} ftpListParts; + +typedef void (FTPSM) _PARAMS((FtpStateData *)); /* Local functions */ static CNCB ftpConnectDone; -static CWCB ftpSendComplete; -static PF ftpReadReply; -static PF ftpSendRequest; -static PF ftpServerClosed; +static CNCB ftpPasvCallback; +static IPH ftpConnect; +static PF ftpReadData; static PF ftpStateFree; static PF ftpTimeout; +static PF ftpReadControlReply; +static CWCB ftpWriteCommandCallback; static char *ftpGetBasicAuth _PARAMS((const char *)); static void ftpProcessReplyHeader _PARAMS((FtpStateData *, const char *, int)); static void ftpLoginParser _PARAMS((const char *, FtpStateData *)); +static void ftpFail _PARAMS((FtpStateData * ftpState)); +static wordlist *ftpParseControlReply _PARAMS((char *buf, size_t len, int *code)); +static void ftpSendPasv _PARAMS((FtpStateData * ftpState)); +static void ftpSendCwd _PARAMS((FtpStateData * ftpState)); +static void ftpSendPort _PARAMS((FtpStateData * ftpState)); +static void ftpRestOrList _PARAMS((FtpStateData * ftpState)); +static void ftpReadQuit _PARAMS((FtpStateData * ftpState)); +static void ftpDataTransferDone _PARAMS((FtpStateData * ftpState)); +static void ftpAppendSuccessHeader _PARAMS((FtpStateData * ftpState)); -/* External functions */ -extern char *base64_decode _PARAMS((const char *coded)); +static FTPSM ftpReadWelcome; +static FTPSM ftpReadUser; +static FTPSM ftpReadPass; +static FTPSM ftpReadType; +static FTPSM ftpReadMdtm; +static FTPSM ftpReadSize; +static FTPSM ftpReadPort; +static FTPSM ftpReadPasv; +static FTPSM ftpReadCwd; +static FTPSM ftpReadList; +static FTPSM ftpReadRest; +static FTPSM ftpReadRetr; +static FTPSM ftpReadTransferDone; + +FTPSM *FTP_SM_FUNCS[] = +{ + ftpReadWelcome, + ftpReadUser, + ftpReadPass, + ftpReadType, + ftpReadMdtm, + ftpReadSize, + ftpReadPort, + ftpReadPasv, + ftpReadCwd, + ftpReadList, /* SENT_LIST */ + ftpReadList, /* SENT_NLST */ + ftpReadRest, + ftpReadRetr, + ftpReadQuit, + ftpReadTransferDone +}; static void ftpStateFree(int fd, void *data) @@ -158,6 +188,16 @@ ftpStateFree(int fd, void *data) ftpState->reply_hdr = NULL; } requestUnlink(ftpState->request); + if (ftpState->ctrl.buf) + ftpState->ctrl.freefunc(ftpState->ctrl.buf); + if (ftpState->data.buf) + ftpState->data.freefunc(ftpState->data.buf); + if (ftpState->pathcomps) + wordlistDestroy(&ftpState->pathcomps); + if (ftpState->ctrl.message) + wordlistDestroy(&ftpState->ctrl.message); + if (ftpState->cwd_message) + wordlistDestroy(&ftpState->cwd_message); xfree(ftpState); } @@ -185,7 +225,9 @@ ftpTimeout(int fd, void *data) StoreEntry *entry = ftpState->entry; debug(9, 4, "ftpLifeTimeExpire: FD %d: '%s'\n", fd, entry->url); squid_error_entry(entry, ERR_READ_TIMEOUT, NULL); - comm_close(fd); + if (ftpState->data.fd >= 0) + comm_close(ftpState->data.fd); + comm_close(ftpState->ctrl.fd); } /* This is too much duplicated code from httpProcessReplyHeader. Only @@ -199,7 +241,7 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size) int hdr_len; struct _http_reply *reply = entry->mem_obj->reply; - debug(11, 3, "ftpProcessReplyHeader: key '%s'\n", entry->key); + debug(9, 3, "ftpProcessReplyHeader: key '%s'\n", entry->key); if (ftpState->reply_hdr == NULL) ftpState->reply_hdr = get_free_8k_page(); @@ -209,7 +251,7 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size) strncat(ftpState->reply_hdr, buf, room < size ? room : size); hdr_len += room < size ? room : size; if (hdr_len > 4 && strncmp(ftpState->reply_hdr, "HTTP/", 5)) { - debug(11, 3, "ftpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key); + debug(9, 3, "ftpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key); ftpState->reply_hdr_state += 2; return; } @@ -222,14 +264,14 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size) } if (ftpState->reply_hdr_state == 1) { ftpState->reply_hdr_state++; - debug(11, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n", + debug(9, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n", ftpState->reply_hdr); /* Parse headers into reply structure */ httpParseReplyHeaders(ftpState->reply_hdr, reply); storeTimestampsSet(entry); /* Check if object is cacheable or not based on reply code */ if (reply->code) - debug(11, 3, "ftpProcessReplyHeader: HTTP CODE: %d\n", reply->code); + debug(9, 3, "ftpProcessReplyHeader: HTTP CODE: %d\n", reply->code); switch (reply->code) { case 200: /* OK */ case 203: /* Non-Authoritative Information */ @@ -259,11 +301,367 @@ ftpProcessReplyHeader(FtpStateData * ftpState, const char *buf, int size) } } +static void +ftpListingStart(FtpStateData * ftpState) +{ + StoreEntry *e = ftpState->entry; + wordlist *w; + storeAppendPrintf(e, "\n", + version_string); + storeAppendPrintf(e, "\n", mkrfc1123(squid_curtime)); + storeAppendPrintf(e, "\n"); + storeAppendPrintf(e, "FTP Directory: %s\n", + ftpState->title_url); + storeAppendPrintf(e, "\n"); + if (EBIT_TEST(ftpState->flags, FTP_USE_BASE)) + storeAppendPrintf(e, "\n", + rfc1738_escape(ftpState->title_url)); + storeAppendPrintf(e, "\n"); + if (ftpState->cwd_message) { + storeAppendPrintf(e, "
\n");
+	for (w = ftpState->cwd_message; w; w = w->next)
+	    storeAppendPrintf(e, "%s\n", w->key);
+	storeAppendPrintf(e, "
\n"); + storeAppendPrintf(e, "
\n"); + wordlistDestroy(&ftpState->cwd_message); + } + storeAppendPrintf(e, "

\n"); + storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url); + storeAppendPrintf(e, "

\n"); + storeAppendPrintf(e, "
\n");
+    EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT);
+}
+
+static void
+ftpListingFinish(FtpStateData * ftpState)
+{
+    StoreEntry *e = ftpState->entry;
+    storeAppendPrintf(e, "
\n"); + storeAppendPrintf(e, "
\n"); + storeAppendPrintf(e, "
\n"); + storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n", + mkrfc1123(squid_curtime), + appname, + version_string, + getMyHostname()); + storeAppendPrintf(e, "
\n"); +} + +static const char *Month[] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static int +is_month(const char *buf) +{ + int i; + for (i = 0; i < 12; i++) + if (!strcasecmp(buf, Month[i])) + return 1; + return 0; +} + + +static void +ftpListPartsFree(ftpListParts ** parts) +{ + safe_free((*parts)->date); + safe_free((*parts)->name); + safe_free((*parts)->showname); + safe_free((*parts)->link); + safe_free(*parts); +} + +#define MAX_TOKENS 64 + +static ftpListParts * +ftpListParseParts(const char *buf, int flags) +{ + ftpListParts *p = NULL; + char *t = NULL; + const char *ct = NULL; + char *tokens[MAX_TOKENS]; + int i; + int n_tokens; + static char sbuf[128]; + char *xbuf = NULL; + if (buf == NULL) + return NULL; + if (*buf == '\0') + return NULL; + p = xcalloc(1, sizeof(ftpListParts)); + n_tokens = 0; + for (i = 0; i < MAX_TOKENS; i++) + tokens[i] = (char *) NULL; + xbuf = xstrdup(buf); + for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) + tokens[n_tokens++] = xstrdup(t); + xfree(xbuf); + /* locate the Month field */ + for (i = 3; i < n_tokens - 3; i++) { + if (!is_month(tokens[i])) /* Month */ + continue; + if (!sscanf(tokens[i - 1], "%[0-9]", sbuf)) /* Size */ + continue; + if (!sscanf(tokens[i + 1], "%[0-9]", sbuf)) /* Day */ + continue; + if (!sscanf(tokens[i + 2], "%[0-9:]", sbuf)) /* Yr | hh:mm */ + continue; + p->type = *tokens[0]; + p->size = atoi(tokens[i - 1]); + sprintf(sbuf, "%s %2s %5s", + tokens[i], tokens[i + 1], tokens[i + 2]); + if (!strstr(buf, sbuf)) + sprintf(sbuf, "%s %2s %-5s", + tokens[i], tokens[i + 1], tokens[i + 2]); + if ((t = strstr(buf, sbuf))) { + p->date = xstrdup(sbuf); + if (BIT_TEST(flags, FTP_SKIP_WHITESPACE)) { + t += strlen(sbuf); + while (strchr(w_space, *t)) + t++; + } else { + /* XXX assumes a single space between date and filename + * suggested by: Nathan.Bailey@cc.monash.edu.au and + * Mike Battersby */ + t += strlen(sbuf) + 1; + } + p->name = xstrdup(t); + if ((t = strstr(p->name, " -> "))) { + *t = '\0'; + p->link = xstrdup(t + 4); + } + } + break; + } + /* try it as a DOS listing */ + if (n_tokens > 3 && p->name == NULL && + sscanf(tokens[0], "%[0-9]-%[0-9]-%[0-9]", sbuf, sbuf, sbuf) == 3 && + /* 04-05-70 */ + sscanf(tokens[1], "%[0-9]:%[0-9]%[AaPp]%[Mm]", sbuf, sbuf, sbuf, sbuf) == 4) { + /* 09:33PM */ + if (!strcasecmp(tokens[2], "")) { + p->type = 'd'; + } else { + p->type = '-'; + p->size = atoi(tokens[2]); + } + sprintf(sbuf, "%s %s", tokens[0], tokens[1]); + p->date = xstrdup(sbuf); + p->name = xstrdup(tokens[3]); + } + /* Try EPLF format; carson@lehman.com */ + if (p->name == NULL && buf[0] == '+') { + ct = buf + 1; + p->type = 0; + while (ct && *ct) { + switch (*ct) { + case '\t': + sscanf(ct + 1, "%[^,]", sbuf); + p->name = xstrdup(sbuf); + break; + case 's': + sscanf(ct + 1, "%d", &(p->size)); + break; + case 'm': + sscanf(ct + 1, "%d", &i); + p->date = xstrdup(ctime((time_t *) & i)); + *(strstr(p->date, "\n")) = '\0'; + break; + case '/': + p->type = 'd'; + break; + case 'r': + p->type = '-'; + break; + case 'i': + break; + default: + break; + } + ct = strstr(ct, ","); + if (ct) { + ct++; + } + } + if (p->type == 0) { + p->type = '-'; + } + } + for (i = 0; i < n_tokens; i++) + xfree(tokens[i]); + if (p->name == NULL) { + xfree(p->date); + xfree(p); + p = NULL; + } + return p; +} + +static const char * +dots_fill(size_t len) +{ + static char buf[256]; + int i = 0; + if (len > Config.Ftp.list_width) { + memset(buf, ' ', 256); + buf[0] = '\n'; + buf[Config.Ftp.list_width + 4] = '\0'; + return buf; + } + for (i = (int) len; i < Config.Ftp.list_width; i++) + buf[i - len] = (i % 2) ? '.' : ' '; + buf[i - len] = '\0'; + return buf; +} + +static char * +ftpHtmlifyListEntry(char *line, int flags) +{ + LOCAL_ARRAY(char, link, 2048); + LOCAL_ARRAY(char, icon, 2048); + LOCAL_ARRAY(char, html, 8192); + char *ename = NULL; + size_t width = Config.Ftp.list_width; + ftpListParts *parts; + /* check .. as special case */ + if (!strcmp(line, "..")) { + sprintf(icon, "\"%-6s\"", + Config.Ftp.icon_prefix, + "gopher-menu", + Config.Ftp.icon_suffix, + "[DIR]"); + sprintf(link, "%s", "../", "Parent Directory"); + sprintf(html, "%s %s\n", icon, link); + return html; + } + if (strlen(line) > 1024) { + sprintf(html, "%s\n", line); + return html; + } + if ((parts = ftpListParseParts(line, flags)) == NULL) { + sprintf(html, "%s\n", line); + return html; + } + if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) { + *html = '\0'; + return html; + } + parts->size += 1023; + parts->size >>= 10; + parts->showname = xstrdup(parts->name); + if (!Config.Ftp.list_wrap) { + if (strlen(parts->showname) > width - 1) { + *(parts->showname + width - 1) = '>'; + *(parts->showname + width - 0) = '\0'; + } + } + ename = xstrdup(rfc1738_escape(parts->name)); + switch (parts->type) { + case 'd': + sprintf(icon, "\"%-6s\"", + Config.Ftp.icon_prefix, + "menu", + Config.Ftp.icon_suffix, + "[DIR]"); + sprintf(link, "%s%s", + ename, + parts->showname, + dots_fill(strlen(parts->showname))); + sprintf(html, "%s %s [%s]\n", + icon, + link, + parts->date); + break; + case 'l': + sprintf(icon, "\"%-6s\"", + Config.Ftp.icon_prefix, + mimeGetIcon(parts->link), + Config.Ftp.icon_suffix, + "[LINK]"); + sprintf(link, "%s%s", + ename, + parts->showname, + dots_fill(strlen(parts->showname))); + sprintf(html, "%s %s [%s]\n", + icon, + link, + parts->date); + break; + case '-': + default: + sprintf(icon, "\"%-6s\"", + Config.Ftp.icon_prefix, + mimeGetIcon(parts->name), + Config.Ftp.icon_suffix, + "[FILE]"); + sprintf(link, "%s%s", + ename, + parts->showname, + dots_fill(strlen(parts->showname))); + sprintf(html, "%s %s [%s] %6dk\n", + icon, + link, + parts->date, + parts->size); + break; + } + ftpListPartsFree(&parts); + xfree(ename); + return html; +} + +static void +ftpParseListing(FtpStateData * ftpState, int len) +{ + char *buf = ftpState->data.buf; + char *end = buf + ftpState->data.offset + len - 1; + char *line = get_free_4k_page(); + char *s; + char *t; + size_t linelen; + StoreEntry *e = ftpState->entry; + debug(0, 0, "buf=\n%s|\n", buf); + while (*end != '\r' && *end != '\n' && end > buf) + end--; + if (end == buf) { + debug(0, 0, "ftpParseListing: didn't find end\n"); + return; + } + end++; + for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) { + linelen = strcspn(s, crlf) + 1; + if (linelen > 4096) + linelen = 4096; + xstrncpy(line, s, linelen); + debug(0, 0, "%s\n", line); + if (!strncmp(line, "total", 5)) + continue; + t = ftpHtmlifyListEntry(line, ftpState->flags); + assert(t != NULL); + storeAppend(e, t, strlen(t)); + } + if (end - buf == len) + return; + /* must copy partial line to beginning of buf */ + linelen = ftpState->data.offset + len - (end - buf) + 1; + debug(0, 0, "len=%d\n", len); + debug(0, 0, "buf=%d\n", buf); + debug(0, 0, "end=%d\n", end); + debug(0, 0, "linelen=%d\n", linelen); + assert(0 < linelen); + if (linelen > 4096) + linelen = 4096; + xstrncpy(line, end, linelen); + xstrncpy(ftpState->data.buf, line, ftpState->data.size); + ftpState->data.offset = strlen(ftpState->data.buf); + debug(0, 0, "offset=%d\n", ftpState->data.offset); +} -/* This will be called when data is ready to be read from fd. Read until - * error or connection closed. */ static void -ftpReadReply(int fd, void *data) +ftpReadData(int fd, void *data) { FtpStateData *ftpState = data; LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF); @@ -272,11 +670,11 @@ ftpReadReply(int fd, void *data) int off; int bin; StoreEntry *entry = NULL; - + assert(fd == ftpState->data.fd); entry = ftpState->entry; - if (protoAbortFetch(entry)) { + if (protoAbortFetch(entry)) { squid_error_entry(entry, ERR_CLIENT_ABORT, NULL); - comm_close(fd); + ftpDataTransferDone(ftpState); return; } /* check if we want to defer reading */ @@ -285,18 +683,12 @@ ftpReadReply(int fd, void *data) if ((clen - off) > FTP_DELETE_GAP) { if (entry->flag & CLIENT_ABORT_REQUEST) { squid_error_entry(entry, ERR_CLIENT_ABORT, NULL); - comm_close(fd); + ftpDataTransferDone(ftpState); } IOStats.Ftp.reads_deferred++; - debug(11, 3, "ftpReadReply: Read deferred for Object: %s\n", + debug(9, 3, "ftpReadData: Read deferred for Object: %s\n", entry->url); - debug(11, 3, " Current Gap: %d bytes\n", clen - off); - /* reschedule, so it will be automatically reactivated - * when Gap is big enough. */ - commSetSelect(fd, - COMM_SELECT_READ, - ftpReadReply, - data, 0); + commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0); if (!BIT_TEST(entry->flag, READ_DEFERRED)) { commSetTimeout(fd, Config.Timeout.defer, NULL, NULL); BIT_SET(entry->flag, READ_DEFERRED); @@ -307,10 +699,17 @@ ftpReadReply(int fd, void *data) } else { BIT_RESET(entry->flag, READ_DEFERRED); } + if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) + if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT)) + ftpListingStart(ftpState); errno = 0; - len = read(fd, buf, SQUID_TCP_SO_RCVBUF); + memset(ftpState->data.buf + ftpState->data.offset, '\0', + ftpState->data.size - ftpState->data.offset); + len = read(fd, + ftpState->data.buf + ftpState->data.offset, + ftpState->data.size - ftpState->data.offset); fd_bytes(fd, len, FD_READ); - debug(9, 5, "ftpReadReply: FD %d, Read %d bytes\n", fd, len); + debug(9, 5, "ftpReadData: FD %d, Read %d bytes\n", fd, len); if (len > 0) { commSetTimeout(fd, Config.Timeout.read, NULL, NULL); IOStats.Ftp.reads++; @@ -319,171 +718,40 @@ ftpReadReply(int fd, void *data) IOStats.Ftp.read_hist[bin]++; } if (len < 0) { - debug(50, 1, "ftpReadReply: read error: %s\n", xstrerror()); + debug(50, 1, "ftpReadData: read error: %s\n", xstrerror()); if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { - /* reinstall handlers */ - /* XXX This may loop forever */ commSetSelect(fd, COMM_SELECT_READ, - ftpReadReply, data, 0); - /* note there is no ftpReadReplyTimeout. Timeouts are handled - * by `ftpget'. */ + ftpReadData, data, 0); } else { BIT_RESET(entry->flag, ENTRY_CACHABLE); storeReleaseRequest(entry); squid_error_entry(entry, ERR_READ_ERROR, xstrerror()); - comm_close(fd); + ftpDataTransferDone(ftpState); } } else if (len == 0 && entry->mem_obj->e_current_len == 0) { squid_error_entry(entry, ERR_ZERO_SIZE_OBJECT, errno ? xstrerror() : NULL); - comm_close(fd); + ftpDataTransferDone(ftpState); } else if (len == 0) { /* Connection closed; retrieval done. */ - if (!ftpState->got_marker) { - /* If we didn't see the magic marker, assume the transfer - * failed and arrange so the object gets ejected and - * never gets to disk. */ - debug(9, 1, "ftpReadReply: Purging '%s'\n", entry->url); - storeNegativeCache(entry); - BIT_RESET(entry->flag, ENTRY_CACHABLE); - storeReleaseRequest(entry); - } else { - storeTimestampsSet(entry); - } + if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT)) + ftpListingFinish(ftpState); + storeTimestampsSet(entry); storeComplete(entry); - comm_close(fd); + ftpDataTransferDone(ftpState); } else if (entry->flag & CLIENT_ABORT_REQUEST) { squid_error_entry(entry, ERR_CLIENT_ABORT, NULL); - comm_close(fd); + ftpDataTransferDone(ftpState); } else { - if (ftpState->got_marker) { - /* oh, this is so gross -- we found the marker at the - * end of the previous read, but theres more data! - * So put the marker back in. */ - storeAppend(entry, MAGIC_MARKER, MAGIC_MARKER_SZ); - } - /* check for a magic marker at the end of the read */ - ftpState->got_marker = 0; - if (len >= MAGIC_MARKER_SZ) { - if (!memcmp(MAGIC_MARKER, buf + len - MAGIC_MARKER_SZ, MAGIC_MARKER_SZ)) { - ftpState->got_marker = 1; - len -= MAGIC_MARKER_SZ; - } - } - storeAppend(entry, buf, len); - if (ftpState->reply_hdr_state < 2 && len > 0) - ftpProcessReplyHeader(data, buf, len); - commSetSelect(fd, - COMM_SELECT_READ, - ftpReadReply, - data, 0); + if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) + ftpParseListing(ftpState, len); + else + storeAppend(entry, buf, len); + commSetSelect(fd, COMM_SELECT_READ, ftpReadData, data, 0); } } -static void -ftpSendComplete(int fd, char *buf, int size, int errflag, void *data) -{ - FtpStateData *ftpState = (FtpStateData *) data; - StoreEntry *entry = NULL; - - entry = ftpState->entry; - debug(9, 5, "ftpSendComplete: FD %d: size %d: errflag %d.\n", - fd, size, errflag); - - if (buf) { - put_free_8k_page(buf); /* Allocated by ftpSendRequest. */ - buf = NULL; - } - if (errflag) { - squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); - comm_close(fd); - return; - } - commSetSelect(ftpState->ftp_fd, - COMM_SELECT_READ, - ftpReadReply, - ftpState, 0); -} - -static void -ftpSendRequest(int fd, void *data) -{ - FtpStateData *ftpState = data; - char *path = NULL; - const char *mode = NULL; - char *buf = NULL; - LOCAL_ARRAY(char, tbuf, BUFSIZ); - LOCAL_ARRAY(char, opts, BUFSIZ); - const char *const space = " "; - char *s = NULL; - int got_timeout = 0; - int got_negttl = 0; - - debug(9, 5, "ftpSendRequest: FD %d\n", fd); - - buf = get_free_8k_page(); - - path = ftpState->request->urlpath; - mode = "-"; /* let ftpget figure it out */ - - /* Start building the buffer ... */ - strcat(buf, Config.Program.ftpget); - strcat(buf, space); - - xstrncpy(opts, Config.Program.ftpget_opts, BUFSIZ); - for (s = strtok(opts, w_space); s; s = strtok(NULL, w_space)) { - strcat(buf, s); - strcat(buf, space); - if (!strncmp(s, "-t", 2)) - got_timeout = 1; - if (!strncmp(s, "-n", 2)) - got_negttl = 1; - } - if (!got_timeout) { - sprintf(tbuf, "-t %d ", Config.Timeout.read); - strcat(buf, tbuf); - } - if (!got_negttl) { - sprintf(tbuf, "-n %d ", Config.negativeTtl); - strcat(buf, tbuf); - } - if (ftpState->request->port) { - sprintf(tbuf, "-P %d ", ftpState->request->port); - strcat(buf, tbuf); - } - if ((s = Config.visibleHostname)) { - sprintf(tbuf, "-H %s ", s); - strcat(buf, tbuf); - } - if (ftpState->authenticated) { - strcat(buf, "-a "); - } - if (Config.Addrs.tcp_outgoing.s_addr != no_addr.s_addr) { - sprintf(tbuf, "-o %s ", inet_ntoa(Config.Addrs.tcp_outgoing)); - strcat(buf, tbuf); - } - strcat(buf, "-h "); /* httpify */ - strcat(buf, "- "); /* stdout */ - strcat(buf, ftpState->request->host); - strcat(buf, space); - strcat(buf, *path ? path : "\"\""); - strcat(buf, space); - strcat(buf, mode); /* A or I */ - strcat(buf, space); - strcat(buf, *ftpState->user ? ftpState->user : "\"\""); - strcat(buf, space); - strcat(buf, *ftpState->password ? ftpState->password : "\"\""); - strcat(buf, "\n"); - debug(9, 5, "ftpSendRequest: FD %d: buf '%s'\n", fd, buf); - comm_write(fd, - buf, - strlen(buf), - ftpSendComplete, - ftpState, - put_free_8k_page); -} - static char * ftpGetBasicAuth(const char *req_hdr) { @@ -534,6 +802,89 @@ ftpCheckAuth(FtpStateData * ftpState, char *req_hdr) return 0; /* different username */ } +static void +ftpCleanupUrlpath(FtpStateData * ftpState) +{ + request_t *request = ftpState->request; + int again; + int l; + char *t = NULL; + char *s = NULL; + do { + again = 0; + l = strlen(request->urlpath); + /* check for null path */ + if (*request->urlpath == '\0') { + xstrncpy(request->urlpath, ".", MAX_URL); + EBIT_SET(ftpState->flags, FTP_ROOT_DIR); + again = 1; + } else if ((l >= 1) && (*(request->urlpath + l - 1) == '/')) { + /* remove any trailing slashes from path */ + *(request->urlpath + l - 1) = '\0'; + EBIT_SET(ftpState->flags, FTP_ISDIR); + EBIT_RESET(ftpState->flags, FTP_USE_BASE); + again = 1; + } else if ((l >= 2) && (!strcmp(request->urlpath + l - 2, "/."))) { + /* remove trailing /. */ + *(request->urlpath + l - 2) = '\0'; + EBIT_SET(ftpState->flags, FTP_ISDIR); + EBIT_RESET(ftpState->flags, FTP_USE_BASE); + again = 1; + } else if (*request->urlpath == '/') { + /* remove any leading slashes from path */ + t = xstrdup(request->urlpath + 1); + xstrncpy(request->urlpath, t, MAX_URL); + xfree(t); + again = 1; + } else if (!strncmp(request->urlpath, "./", 2)) { + /* remove leading ./ */ + t = xstrdup(request->urlpath + 2); + xstrncpy(request->urlpath, t, MAX_URL); + xfree(t); + again = 1; + } else if ((t = strstr(request->urlpath, "/./"))) { + /* remove /./ */ + s = xstrdup(t + 2); + xstrncpy(t, s, strlen(s)); + xfree(s); + again = 1; + } else if ((t = strstr(request->urlpath, "//"))) { + /* remove // */ + s = xstrdup(t + 1); + xstrncpy(t, s, strlen(s)); + xfree(s); + again = 1; + } + } while (again); +} + + +static void +ftpBuildTitleUrl(FtpStateData * ftpState) +{ + request_t *request = ftpState->request; + size_t len; + char *t; + len = 64 + + strlen(ftpState->user) + + strlen(ftpState->password) + + strlen(request->host) + + strlen(request->urlpath); + t = ftpState->title_url = xcalloc(len, 1); + strcat(t, "ftp://"); + if (strcmp(ftpState->user, "anonymous")) { + strcat(t, ftpState->user); + strcat(t, "@"); + } + strcat(t, request->host); + if (request->port != urlDefaultPort(PROTO_FTP)) + sprintf(&t[strlen(t)], ":%d", request->port); + strcat(t, "/"); + if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR)) + strcat(t, request->urlpath); +} + + void ftpStart(request_t * request, StoreEntry * entry) { @@ -542,15 +893,16 @@ ftpStart(request_t * request, StoreEntry * entry) FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData)); char *req_hdr; char *response; + int fd; debug(9, 3, "FtpStart: '%s'\n", entry->url); - if (ftpget_server_write < 0) { - squid_error_entry(entry, ERR_FTP_DISABLED, NULL); - return; - } storeLockObject(entry); ftpState->entry = entry; req_hdr = entry->mem_obj->request_hdr; ftpState->request = requestLink(request); + ftpState->ctrl.fd = -1; + ftpState->data.fd = -1; + EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED); + EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED); if (!ftpCheckAuth(ftpState, req_hdr)) { /* This request is not fully authenticated */ if (request->port == 21) { @@ -566,32 +918,51 @@ ftpStart(request_t * request, StoreEntry * entry) ftpStateFree(-1, ftpState); return; } + ftpCleanupUrlpath(ftpState); + ftpBuildTitleUrl(ftpState); debug(9, 5, "FtpStart: host=%s, path=%s, user=%s, passwd=%s\n", ftpState->request->host, ftpState->request->urlpath, ftpState->user, ftpState->password); - ftpState->ftp_fd = comm_open(SOCK_STREAM, + fd = comm_open(SOCK_STREAM, 0, - local_addr, + Config.Addrs.tcp_outgoing, 0, COMM_NONBLOCKING, url); - if (ftpState->ftp_fd == COMM_ERROR) { - squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); - ftpStateFree(-1, ftpState); + if (fd == COMM_ERROR) { + debug(9, 4, "ftpStart: Failed because we're out of sockets.\n"); + squid_error_entry(entry, ERR_NO_FDS, xstrerror()); return; } - /* Pipe/socket created ok */ - /* register close handler */ - comm_add_close_handler(ftpState->ftp_fd, - ftpStateFree, - ftpState); - commSetTimeout(ftpState->ftp_fd, - Config.Timeout.connect, - ftpTimeout, - ftpState); - commConnectStart(ftpState->ftp_fd, - localhost, - ftpget_port, + ftpState->ctrl.fd = fd; + comm_add_close_handler(fd, ftpStateFree, ftpState); + commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState); + ipcache_nbgethostbyname(request->host, fd, ftpConnect, ftpState); +} + +static void +ftpConnect(int fd, const ipcache_addrs * ia, void *data) +{ + FtpStateData *ftpState = data; + request_t *request = ftpState->request; + StoreEntry *entry = ftpState->entry; + EBIT_RESET(ftpState->flags, FTP_IP_LOOKUP_PENDING); + debug(0, 0, "ftpConnect\n"); + assert(fd == ftpState->ctrl.fd); + if (ia == NULL) { + debug(9, 4, "ftpConnect: Unknown host: %s\n", request->host); + squid_error_entry(entry, ERR_DNS_FAIL, dns_error_message); + comm_close(fd); + return; + } + debug(0, 0, "ftpConnect: %s is %s\n", request->host, + inet_ntoa(ia->in_addrs[0])); + debug(0, 0, "ftpConnect: port %d\n", (int) request->port); + /* Open connection. */ + commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState); + commConnectStart(ftpState->ctrl.fd, + request->host, + request->port, ftpConnectDone, ftpState); } @@ -600,149 +971,587 @@ static void ftpConnectDone(int fd, int status, void *data) { FtpStateData *ftpState = data; + debug(0, 0, "ftpConnectDone\n"); if (status == COMM_ERROR) { squid_error_entry(ftpState->entry, ERR_CONNECT_FAIL, xstrerror()); comm_close(fd); return; } - commSetNonBlocking(fd); - (void) fd_note(fd, ftpState->entry->url); - /* Install connection complete handler. */ - fd_note(fd, ftpState->entry->url); - commSetSelect(fd, - COMM_SELECT_WRITE, - ftpSendRequest, - data, 0); - if (opt_no_ipcache) - ipcacheInvalidate(ftpState->request->host); + ftpState->state = BEGIN; + ftpState->ctrl.buf = get_free_4k_page(); + ftpState->ctrl.freefunc = put_free_4k_page; + ftpState->ctrl.size = 4096; + ftpState->ctrl.offset = 0; + ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF); + ftpState->data.size = SQUID_TCP_SO_RCVBUF; + ftpState->data.freefunc = xfree; + commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0); } +/* ====================================================================== */ + + static void -ftpServerClosed(int fd, void *nodata) +ftpWriteCommand(const char *buf, FtpStateData * ftpState) { - static time_t last_restart = 0; - comm_close(fd); - if (squid_curtime - last_restart < 2) { - debug(9, 0, "ftpget server failing too rapidly\n"); - debug(9, 0, "WARNING: FTP access is disabled!\n"); - ftpget_server_write = -1; - ftpget_server_read = -1; + debug(0, 0, "ftpWriteCommand: %s\n", buf); + comm_write(ftpState->ctrl.fd, + xstrdup(buf), + strlen(buf), + ftpWriteCommandCallback, + ftpState, + xfree); + commSetSelect(ftpState->ctrl.fd, + COMM_SELECT_READ, + ftpReadControlReply, + ftpState, + 0); +} + +static void +ftpWriteCommandCallback(int fd, char *buf, int size, int errflag, void *data) +{ + FtpStateData *ftpState = data; + StoreEntry *entry = ftpState->entry; + debug(0, 0, "ftpWriteCommandCallback: wrote %d bytes\n", size); + if (errflag) { + BIT_RESET(entry->flag, ENTRY_CACHABLE); + storeReleaseRequest(entry); + squid_error_entry(entry, ERR_WRITE_ERROR, xstrerror()); + comm_close(fd); + } +} + +static wordlist * +ftpParseControlReply(char *buf, size_t len, int *codep) +{ + char *s; + int complete = 0; + wordlist *head; + wordlist *list; + wordlist **tail = &head; + off_t offset; + size_t linelen; + int code; + debug(0, 0, "ftpParseControlReply\n"); + if (*(buf + len - 1) != '\n') + return NULL; + for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) { + linelen = strcspn(s, crlf) + 1; + if (linelen > 3) + complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' '); + if (complete) + code = atoi(s); + offset = 0; + if (linelen > 3) + if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' ')) + offset = 4; + list = xcalloc(1, sizeof(wordlist)); + list->key = xmalloc(linelen - offset); + xstrncpy(list->key, s + offset, linelen - offset); + debug(38, 0, "%s\n", list->key); + *tail = list; + tail = &list->next; + } + if (!complete) + wordlistDestroy(&head); + if (codep) + *codep = code; + return head; +} + +static void +ftpReadControlReply(int fd, void *data) +{ + FtpStateData *ftpState = data; + StoreEntry *entry = ftpState->entry; + char *oldbuf; + wordlist **W; + int len; + debug(0, 0, "ftpReadControlReply\n"); + assert(ftpState->ctrl.offset < ftpState->ctrl.size); + len = read(fd, + ftpState->ctrl.buf + ftpState->ctrl.offset, + ftpState->ctrl.size - ftpState->ctrl.offset); + fd_bytes(fd, len, FD_READ); + debug(9, 5, "ftpReadControlReply: FD %d, Read %d bytes\n", fd, len); + if (len > 0) + commSetTimeout(fd, Config.Timeout.read, NULL, NULL); + if (entry->flag & CLIENT_ABORT_REQUEST) { + squid_error_entry(entry, ERR_CLIENT_ABORT, NULL); + comm_close(fd); return; } - last_restart = squid_curtime; - debug(9, 1, "Restarting ftpget server...\n"); - (void) ftpInitialize(); + if (len < 0) { + debug(50, 1, "ftpReadControlReply: read error: %s\n", xstrerror()); + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + commSetSelect(fd, + COMM_SELECT_READ, + ftpReadControlReply, + ftpState, + 0); + } else { + BIT_RESET(entry->flag, ENTRY_CACHABLE); + storeReleaseRequest(entry); + squid_error_entry(entry, ERR_READ_ERROR, xstrerror()); + comm_close(fd); + } + return; + } + if (len == 0) { + debug(38, 0, "Read 0 bytes from FTP control socket?\n"); + BIT_RESET(entry->flag, ENTRY_CACHABLE); + storeReleaseRequest(entry); + squid_error_entry(entry, ERR_READ_ERROR, xstrerror()); + comm_close(fd); + return; + } + assert(len <= ftpState->ctrl.size); + wordlistDestroy(&ftpState->ctrl.message); + ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len, + &ftpState->ctrl.replycode); + if (ftpState->ctrl.message == NULL) { + if (len == ftpState->ctrl.size) { + oldbuf = ftpState->ctrl.buf; + ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1); + xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size); + ftpState->ctrl.size <<= 1; + ftpState->ctrl.freefunc(oldbuf); + ftpState->ctrl.freefunc = xfree; + } + commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, 0); + return; + } + for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next); + ftpState->ctrl.last_message = (*W)->key; + safe_free(*W); + ftpState->ctrl.offset = 0; + FTP_SM_FUNCS[ftpState->state] (ftpState); } -void -ftpServerClose(void) +/* ====================================================================== */ + +static void +ftpReadWelcome(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + char *p = NULL; + debug(0, 0, "ftpReadWelcome\n"); + if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY)) + ftpState->login_att++; + if (code == 220) { + if (ftpState->ctrl.message) + if (strstr(ftpState->ctrl.message->key, "NetWare")) + EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE); + if (ftpState->proxy_host != NULL) + sprintf(cbuf, "USER %s@%s\r\n", + ftpState->user, + ftpState->request->host); + else + sprintf(cbuf, "USER %s\r\n", ftpState->user); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_USER; + } else { + ftpFail(ftpState); + } +} + +static void +ftpReadUser(FtpStateData * ftpState) { - /* NOTE: this function will be called repeatedly while shutdown is - * pending */ - if (ftpget_server_read < 0) + int code = ftpState->ctrl.replycode; + debug(0, 0, "ftpReadUser\n"); + if (code == 230) { + ftpReadPass(ftpState); + } else if (code == 331) { + sprintf(cbuf, "PASS %s\r\n", ftpState->password); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_PASS; + } else { + ftpFail(ftpState); + } +} + +static void +ftpReadPass(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + char *t; + char *filename; + char mode; + debug(0, 0, "ftpReadPass\n"); + if (code == 230) { + t = strrchr(ftpState->request->urlpath, '/'); + filename = t ? t + 1 : ftpState->request->urlpath; + mode = mimeGetTransferMode(filename); + sprintf(cbuf, "TYPE %c\r\n", mode); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_TYPE; + } else { + ftpFail(ftpState); + } +} + +static void +ftpReadType(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + wordlist *w; + wordlist **T; + char *path; + char *d; + debug(38, 1, "This is ftpReadType\n"); + if (code == 200) { + if (EBIT_TEST(ftpState->flags, FTP_ROOT_DIR)) { + ftpSendPasv(ftpState); + } else { + path = xstrdup(ftpState->request->urlpath); + T = &ftpState->pathcomps; + for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) { + w = xcalloc(1, sizeof(wordlist)); + w->key = xstrdup(d); + *T = w; + T = &w->next; + } + ftpSendCwd(ftpState); + } + } else { + ftpFail(ftpState); + } +} + +static void +ftpSendCwd(FtpStateData * ftpState) +{ + wordlist *w; + debug(0, 0, "ftpSendCwd\n"); + if ((w = ftpState->pathcomps) == NULL) { + debug(0, 0, "the final component was a directory\n"); + EBIT_SET(ftpState->flags, FTP_ISDIR); + if (!EBIT_TEST(ftpState->flags, FTP_ROOT_DIR)) + strcat(ftpState->title_url, "/"); + ftpSendPasv(ftpState); return; - commSetSelect(ftpget_server_read, - COMM_SELECT_READ, - NULL, - NULL, 0); - fd_close(ftpget_server_read); - close(ftpget_server_read); - ftpget_server_read = -1; - fd_close(ftpget_server_write); - close(ftpget_server_write); - ftpget_server_write = -1; -} - - -int -ftpInitialize(void) -{ - pid_t pid; - int cfd; - int squid_to_ftpget[2]; - int ftpget_to_squid[2]; - LOCAL_ARRAY(char, pbuf, 128); - char *ftpget = Config.Program.ftpget; - struct sockaddr_in S; - int len; - struct timeval slp; + } + sprintf(cbuf, "CWD %s\r\n", w->key); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_CWD; +} - if (!strcmp(ftpget, "none")) { - debug(9, 1, "ftpInitialize: ftpget is disabled.\n"); - return -1; +static void +ftpReadCwd(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + size_t len = 0; + wordlist *w; + debug(38, 1, "This is ftpReadCwd\n"); + w = ftpState->pathcomps; + assert(w != NULL); + if (code >= 200 && code < 300) { + if (ftpState->cwd_message) + wordlistDestroy(&ftpState->cwd_message); + ftpState->cwd_message = ftpState->ctrl.message; + ftpState->ctrl.message = NULL; + /* CWD OK */ + ftpState->pathcomps = w->next; + xfree(w->key); + xfree(w); + ftpSendCwd(ftpState); + } else { + /* CWD FAILED */ + while (w) { + len += (strlen(w->key) + 1); + w = w->next; + } + ftpState->filepath = xcalloc(len, 1); + for (w = ftpState->pathcomps; w; w = w->next) { + strcat(ftpState->filepath, w->key); + if (w->next) + strcat(ftpState->filepath, "/"); + } + wordlistDestroy(&ftpState->pathcomps); + assert(*ftpState->filepath != '\0'); + sprintf(cbuf, "MDTM %s\r\n", ftpState->filepath); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_MDTM; } - debug(9, 5, "ftpInitialize: Initializing...\n"); - if (pipe(squid_to_ftpget) < 0) { - debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror()); - return -1; +} + +static void +ftpReadMdtm(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadMdtm\n"); + if (code == 213) { + ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_message); + assert(ftpState->filepath != NULL); + assert(*ftpState->filepath != '\0'); + sprintf(cbuf, "SIZE %s\r\n", ftpState->filepath); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_SIZE; + } else if (code < 0) { + ftpFail(ftpState); } - if (pipe(ftpget_to_squid) < 0) { - debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror()); - return -1; +} + +static void +ftpReadSize(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadSize\n"); + if (code == 213) { + ftpState->size = atoi(ftpState->ctrl.last_message); + ftpSendPasv(ftpState); + } else if (code < 0) { + ftpFail(ftpState); + } +} + +static void +ftpSendPasv(FtpStateData * ftpState) +{ + int fd; + assert(ftpState->data.fd < 0); + if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) { + ftpSendPort(ftpState); + return; } - cfd = comm_open(SOCK_STREAM, + fd = comm_open(SOCK_STREAM, 0, - local_addr, + Config.Addrs.tcp_outgoing, 0, - COMM_NOCLOEXEC, - "ftpget -S socket"); - debug(9, 5, "ftpget -S socket on FD %d\n", cfd); - if (cfd == COMM_ERROR) { - debug(9, 0, "ftpInitialize: Failed to create socket\n"); - return -1; - } - len = sizeof(S); - memset(&S, '\0', len); - if (getsockname(cfd, (struct sockaddr *) &S, &len) < 0) { - debug(50, 0, "ftpInitialize: getsockname: %s\n", xstrerror()); - comm_close(cfd); - return -1; - } - ftpget_port = ntohs(S.sin_port); - listen(cfd, Squid_MaxFD >> 2); - if ((pid = fork()) < 0) { - debug(50, 0, "ftpInitialize: fork: %s\n", xstrerror()); - comm_close(cfd); - fatal("Failed to fork() for ftpget."); - } - if (pid != 0) { /* parent */ - comm_close(cfd); - close(squid_to_ftpget[0]); - close(ftpget_to_squid[1]); - fd_open(squid_to_ftpget[1], FD_PIPE, "squid -> ftpget"); - fd_open(ftpget_to_squid[0], FD_PIPE, "squid <- ftpget"); - commSetCloseOnExec(squid_to_ftpget[1]); - commSetCloseOnExec(ftpget_to_squid[0]); - /* if ftpget -S goes away, this handler should get called */ - commSetSelect(ftpget_to_squid[0], + COMM_NONBLOCKING, + ftpState->entry->url); + if (fd < 0) { + ftpFail(ftpState); + return; + } + ftpState->data.fd = fd; + commSetTimeout(fd, Config.Timeout.read, ftpTimeout, ftpState); + sprintf(cbuf, "PASV\r\n"); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_PASV; +} + +static void +ftpReadPasv(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + int h1, h2, h3, h4; + int p1, p2; + int n; + u_short port; + int fd = ftpState->data.fd; + char *buf = ftpState->ctrl.last_message; + LOCAL_ARRAY(char, junk, 1024); + debug(38, 1, "This is ftpReadPasv\n"); + if (code != 227) { + debug(38, 0, "PASV not supported by remote end\n"); + ftpSendPort(ftpState); + return; + } + if (strlen(buf) > 1024) { + debug(38, 0, "Avoiding potential buffer overflow\n"); + ftpSendPort(ftpState); + return; + } + /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ + /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */ + debug(0, 0, "scanning: %s\n", buf); + n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d", + junk, &h1, &h2, &h3, &h4, &p1, &p2); + if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) { + debug(38, 0, "Bad 227 reply\n"); + debug(38, 0, "n=%d, p1=%d, p2=%d\n", n, p1, p2); + ftpSendPort(ftpState); + return; + } + sprintf(junk, "%d.%d.%d.%d", h1, h2, h3, h4); + if (!safe_inet_addr(junk, NULL)) { + debug(38, 0, "unsafe address (%s)\n", junk); + ftpSendPort(ftpState); + return; + } + port = ((p1 << 8) + p2); + debug(0, 0, "ftpReadPasv: connecting to %s, port %d\n", junk, port); + commConnectStart(fd, junk, port, ftpPasvCallback, ftpState); +} + +static void +ftpPasvCallback(int fd, int status, void *data) +{ + FtpStateData *ftpState = data; + debug(0, 0, "ftpPasvCallback\n"); + if (status == COMM_ERROR) { + squid_error_entry(ftpState->entry, ERR_CONNECT_FAIL, xstrerror()); + comm_close(fd); + return; + } + ftpRestOrList(ftpState); +} + +static void +ftpSendPort(FtpStateData * ftpState) +{ + debug(38, 1, "This is ftpSendPort\n"); + EBIT_RESET(ftpState->flags, FTP_PASV_SUPPORTED); +} + +static void +ftpReadPort(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadPort\n"); +} + +static void +ftpRestOrList(FtpStateData * ftpState) +{ + debug(38, 1, "This is ftpRestOrList\n"); + if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) { + sprintf(cbuf, "LIST\r\n"); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_LIST; + } else if (ftpState->restart_offset > 0) { + sprintf(cbuf, "REST\r\n"); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_REST; + } else { + assert(ftpState->filepath != NULL); + sprintf(cbuf, "RETR %s\r\n", ftpState->filepath); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_RETR; + } +} + +static void +ftpReadRest(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadRest\n"); + assert(ftpState->restart_offset > 0); + if (code == 350) { + assert(ftpState->filepath != NULL); + sprintf(cbuf, "RETR %s\r\n", ftpState->filepath); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_RETR; + } else if (code > 0) { + debug(0, 0, "ftpReadRest: REST not supported\n"); + EBIT_RESET(ftpState->flags, FTP_REST_SUPPORTED); + } else { + ftpFail(ftpState); + } +} + +static void +ftpReadList(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadList\n"); + if (code == 150 || code == 125) { + ftpAppendSuccessHeader(ftpState); + commSetSelect(ftpState->data.fd, COMM_SELECT_READ, - ftpServerClosed, - NULL, 0); - ftpget_server_write = squid_to_ftpget[1]; - ftpget_server_read = ftpget_to_squid[0]; - slp.tv_sec = 0; - slp.tv_usec = 250000; - select(0, NULL, NULL, NULL, &slp); - return 0; - } - /* child */ - /* give up all extra priviligies */ - no_suid(); - /* set up stdin,stdout */ - dup2(squid_to_ftpget[0], 0); - dup2(ftpget_to_squid[1], 1); - dup2(fileno(debug_log), 2); - close(squid_to_ftpget[0]); - close(squid_to_ftpget[1]); - close(ftpget_to_squid[0]); - close(ftpget_to_squid[1]); - dup2(cfd, 3); /* pass listening socket to ftpget */ - /* inherit stdin,stdout,stderr */ - for (cfd = 4; cfd <= Biggest_FD; cfd++) - (void) close(cfd); - sprintf(pbuf, "%d", ftpget_port); - execlp(ftpget, ftpget, "-S", pbuf, NULL); - debug(50, 0, "ftpInitialize: %s: %s\n", ftpget, xstrerror()); - _exit(1); - return (1); /* eliminate compiler warning */ + ftpReadData, + ftpState, + 0); + ftpState->state = READING_DATA; + return; + } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) { + EBIT_SET(ftpState->flags, FTP_TRIED_NLST); + sprintf(cbuf, "NLST\r\n"); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_NLST; + } else { + ftpFail(ftpState); + return; + } +} + +static void +ftpReadRetr(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadRetr\n"); + if (code >= 100 && code < 200) { + debug(0, 0, "reading data channel\n"); + ftpAppendSuccessHeader(ftpState); + commSetSelect(ftpState->data.fd, + COMM_SELECT_READ, + ftpReadData, + ftpState, + 0); + ftpState->state = READING_DATA; + } else { + ftpFail(ftpState); + } +} + +static void +ftpReadTransferDone(FtpStateData * ftpState) +{ + int code = ftpState->ctrl.replycode; + debug(38, 1, "This is ftpReadTransferDone\n"); + if (code != 226) { + debug(38, 1, "Got code %d after reading data, releasing entry\n"); + storeReleaseRequest(ftpState->entry); + } +} + +static void +ftpDataTransferDone(FtpStateData * ftpState) +{ + assert(ftpState->data.fd >= 0); + comm_close(ftpState->data.fd); + ftpState->data.fd = -1; + sprintf(cbuf, "QUIT\r\n"); + ftpWriteCommand(cbuf, ftpState); + ftpState->state = SENT_QUIT; +} + +static void +ftpReadQuit(FtpStateData * ftpState) +{ + comm_close(ftpState->ctrl.fd); +} + +static void +ftpFail(FtpStateData * ftpState) +{ + debug(0, 0, "ftpFail\n"); + comm_close(ftpState->ctrl.fd); +} + +static void +ftpAppendSuccessHeader(FtpStateData * ftpState) +{ + char *mime_type = NULL; + char *mime_enc = NULL; + char *urlpath = ftpState->request->urlpath; + char *filename = NULL; + char *t = NULL; + StoreEntry *e = ftpState->entry; + if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT)) + return; + assert(e->mem_obj->e_current_len == 0); + filename = (t = strrchr(urlpath, '/')) ? t + 1 : urlpath; + if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) { + mime_type = "text/html"; + } else { + mime_type = mimeGetContentType(filename); + mime_enc = mimeGetContentEncoding(filename); + } + storeAppendPrintf(e, "HTTP/1.0 200 Gatewaying\r\n"); + storeAppendPrintf(e, "Date: %s\r\n", mkrfc1123(squid_curtime)); + storeAppendPrintf(e, "MIME-Version: 1.0\r\n"); + storeAppendPrintf(e, "Server: Squid %s\r\n", version_string); + if (ftpState->size > 0) + storeAppendPrintf(e, "Content-Length: %d\r\n", ftpState->size); + if (mime_type) + storeAppendPrintf(e, "Content-Type: %s\r\n", mime_type); + if (mime_enc) + storeAppendPrintf(e, "Content-Encoding: %s\r\n", mime_enc); + if (ftpState->mdtm > 0) + storeAppendPrintf(e, "Last-Modified: %s\r\n", mkrfc1123(ftpState->mdtm)); + storeAppendPrintf(e, "\r\n"); } diff --git a/src/main.cc b/src/main.cc index 92aaca35c5..a9af7001df 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ /* - * $Id: main.cc,v 1.148 1997/05/16 07:42:38 wessels Exp $ + * $Id: main.cc,v 1.149 1997/05/26 04:05:00 wessels Exp $ * * DEBUG: section 1 Startup and Main Loop * AUTHOR: Harvest Derived @@ -514,7 +514,6 @@ mainReconfigure(void) dnsOpenServers(); redirectOpenServers(); serverConnectionsOpen(); - (void) ftpInitialize(); if (theOutIcpConnection >= 0 && (!httpd_accel_mode || Config.Accel.withProxy)) neighbors_open(theOutIcpConnection); debug(1, 0, "Ready to serve requests.\n"); @@ -564,7 +563,6 @@ mainInitialize(void) dnsOpenServers(); redirectOpenServers(); useragentOpenLog(); - (void) ftpInitialize(); #if MALLOC_DBG malloc_debug(0, malloc_debug_level); @@ -692,14 +690,12 @@ main(int argc, char **argv) /* main loop */ for (;;) { if (rotate_pending) { - ftpServerClose(); icmpClose(); _db_rotate_log(); /* cache.log */ storeWriteCleanLogs(); storeRotateLog(); /* store.log */ stat_rotate_log(); /* access.log */ useragentRotateLog(); /* useragent.log */ - (void) ftpInitialize(); icmpOpen(); rotate_pending = 0; } diff --git a/src/url.cc b/src/url.cc index ca2103d2af..b9420931e6 100644 --- a/src/url.cc +++ b/src/url.cc @@ -1,6 +1,6 @@ /* - * $Id: url.cc,v 1.55 1997/04/28 04:23:34 wessels Exp $ + * $Id: url.cc,v 1.56 1997/05/26 04:05:01 wessels Exp $ * * DEBUG: section 23 URL Parsing * AUTHOR: Duane Wessels @@ -56,7 +56,6 @@ static char *ProtocolStr[] = static int url_acceptable[256]; static const char *const hex = "0123456789abcdef"; -static int urlDefaultPort _PARAMS((protocol_t p)); /* convert %xx in url string to a character * Allocate a new string and return a pointer to converted string */ @@ -172,7 +171,7 @@ urlParseProtocol(const char *s) } -static int +int urlDefaultPort(protocol_t p) { switch (p) {