Home | History | Annotate | Line # | Download | only in ftp
fetch.c revision 1.53
      1 /*	$NetBSD: fetch.c,v 1.53 1999/04/28 13:35:40 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997, 1998, 1999 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Jason Thorpe and Luke Mewburn.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 
     39 #include <sys/cdefs.h>
     40 #ifndef lint
     41 __RCSID("$NetBSD: fetch.c,v 1.53 1999/04/28 13:35:40 lukem Exp $");
     42 #endif /* not lint */
     43 
     44 /*
     45  * FTP User Program -- Command line file retrieval
     46  */
     47 
     48 #include <sys/types.h>
     49 #include <sys/param.h>
     50 #include <sys/socket.h>
     51 #include <sys/stat.h>
     52 #include <sys/time.h>
     53 #include <sys/utsname.h>
     54 
     55 #include <netinet/in.h>
     56 
     57 #include <arpa/ftp.h>
     58 #include <arpa/inet.h>
     59 
     60 #include <ctype.h>
     61 #include <err.h>
     62 #include <errno.h>
     63 #include <netdb.h>
     64 #include <fcntl.h>
     65 #include <signal.h>
     66 #include <stdio.h>
     67 #include <stdlib.h>
     68 #include <string.h>
     69 #include <unistd.h>
     70 #include <util.h>
     71 
     72 #include "ftp_var.h"
     73 
     74 typedef enum {
     75 	UNKNOWN_URL_T=-1,
     76 	HTTP_URL_T,
     77 	FTP_URL_T,
     78 	FILE_URL_T,
     79 	CLASSIC_URL_T
     80 } url_t;
     81 
     82 void    	aborthttp __P((int));
     83 static int	auth_url __P((const char *, char **));
     84 static void	base64_encode __P((const char *, size_t, char *));
     85 static int	go_fetch __P((const char *));
     86 static int	fetch_ftp __P((const char *));
     87 static int	fetch_url __P((const char *, const char *, char *, char *));
     88 static int	parse_url __P((const char *, const char *, url_t *, char **,
     89 				char **, char **, in_port_t *, char **));
     90 static void	url_decode __P((char *));
     91 
     92 static int	redirect_loop;
     93 
     94 
     95 #define	ABOUT_URL	"about:"	/* propaganda */
     96 #define	FILE_URL	"file://"	/* file URL prefix */
     97 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
     98 #define	HTTP_URL	"http://"	/* http URL prefix */
     99 
    100 
    101 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
    102 #define FREEPTR(x)	if ((x) != NULL) { free(x); (x) = NULL; }
    103 
    104 /*
    105  * Generate authorization response based on given authentication challenge.
    106  * Returns -1 if an error occurred, otherwise 0.
    107  * Sets response to a malloc(3)ed string; caller should free.
    108  */
    109 static int
    110 auth_url(challenge, response)
    111 	const char	 *challenge;
    112 	char		**response;
    113 {
    114 	char		*cp, *ep, *clear, *line, *realm, *scheme;
    115 	char		user[BUFSIZ], *pass;
    116 	int		rval;
    117 	size_t		len;
    118 
    119 	*response = NULL;
    120 	clear = realm = scheme = NULL;
    121 	rval = -1;
    122 	line = xstrdup(challenge);
    123 	cp = line;
    124 
    125 	if (debug)
    126 		fprintf(ttyout, "auth_url: challenge `%s'\n", challenge);
    127 
    128 	scheme = strsep(&cp, " ");
    129 #define SCHEME_BASIC "Basic"
    130 	if (strncasecmp(scheme, SCHEME_BASIC, sizeof(SCHEME_BASIC) - 1) != 0) {
    131 		warnx("Unsupported WWW Authentication challenge - `%s'",
    132 		    challenge);
    133 		goto cleanup_auth_url;
    134 	}
    135 	cp += strspn(cp, " ");
    136 
    137 #define REALM "realm=\""
    138 	if (strncasecmp(cp, REALM, sizeof(REALM) - 1) == 0)
    139 		cp += sizeof(REALM) - 1;
    140 	else {
    141 		warnx("Unsupported WWW Authentication challenge - `%s'",
    142 		    challenge);
    143 		goto cleanup_auth_url;
    144 	}
    145 	if ((ep = strchr(cp, '\"')) != NULL) {
    146 		size_t len = ep - cp;
    147 
    148 		realm = (char *)xmalloc(len + 1);
    149 		strncpy(realm, cp, len);
    150 		realm[len] = '\0';
    151 	} else {
    152 		warnx("Unsupported WWW Authentication challenge - `%s'",
    153 		    challenge);
    154 		goto cleanup_auth_url;
    155 	}
    156 
    157 	fprintf(ttyout, "Username for `%s': ", realm);
    158 	(void)fflush(ttyout);
    159 	if (fgets(user, sizeof(user) - 1, stdin) == NULL)
    160 		goto cleanup_auth_url;
    161 	user[strlen(user) - 1] = '\0';
    162 	pass = getpass("Password: ");
    163 
    164 	len = strlen(user) + strlen(pass) + 1;		/* user + ":" + pass */
    165 	clear = (char *)xmalloc(len + 1);
    166 	sprintf(clear, "%s:%s", user, pass);
    167 	memset(pass, '\0', strlen(pass));
    168 
    169 						/* scheme + " " + enc */
    170 	len = strlen(scheme) + 1 + (len + 2) * 4 / 3;
    171 	*response = (char *)xmalloc(len + 1);
    172 	len = sprintf(*response, "%s ", scheme);
    173 	base64_encode(clear, strlen(clear), *response + len);
    174 	rval = 0;
    175 
    176 cleanup_auth_url:
    177 	FREEPTR(clear);
    178 	FREEPTR(line);
    179 	FREEPTR(realm);
    180 	return (rval);
    181 }
    182 
    183 /*
    184  * Encode len bytes starting at clear using base64 encoding into encoded,
    185  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
    186  */
    187 void
    188 base64_encode(clear, len, encoded)
    189 	const char	*clear;
    190 	size_t		 len;
    191 	char		*encoded;
    192 {
    193 	static const char enc[] =
    194 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    195 	char *cp;
    196 	int i;
    197 
    198 	cp = encoded;
    199 	for (i = 0; i < len; i += 3) {
    200 		*(cp++) = enc[((clear[i + 0] >> 2))];
    201 		*(cp++) = enc[((clear[i + 0] << 4) & 0x30)
    202 			    | ((clear[i + 1] >> 4) & 0x0f)];
    203 		*(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
    204 			    | ((clear[i + 2] >> 6) & 0x03)];
    205 		*(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
    206 	}
    207 	*cp = '\0';
    208 	while (i-- > len)
    209 		*(--cp) = '=';
    210 }
    211 
    212 /*
    213  * Decode %xx escapes in given string, `in-place'.
    214  */
    215 static void
    216 url_decode(url)
    217 	char *url;
    218 {
    219 	unsigned char *p, *q;
    220 
    221 	if (EMPTYSTRING(url))
    222 		return;
    223 	p = q = url;
    224 
    225 #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
    226 	while (*p) {
    227 		if (p[0] == '%'
    228 		    && p[1] && isxdigit((unsigned char)p[1])
    229 		    && p[2] && isxdigit((unsigned char)p[2])) {
    230 			*q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
    231 			p+=3;
    232 		} else
    233 			*q++ = *p++;
    234 	}
    235 	*q = '\0';
    236 }
    237 
    238 
    239 /*
    240  * Parse URL of form:
    241  *	<type>://[<user>[:<password>@]]<host>[:<port>]/<url-path>
    242  * Returns -1 if a parse error occurred, otherwise 0.
    243  * Only permit [<user>[:<password>@]] for ftp:// URLs
    244  * It's the caller's responsibility to url_decode() the returned
    245  * user, pass and path.
    246  * Sets type to url_t, each of the given char ** pointers to a
    247  * malloc(3)ed strings of the relevant section, and port to
    248  * the number given, or ftpport if ftp://, or httpport if http://.
    249  *
    250  * XXX: this is not totally RFC 1738 compliant; path will have the
    251  * leading `/' unless it's an ftp:// URL, as this makes things easier
    252  * for file:// and http:// URLs. ftp:// URLs have the `/' between the
    253  * host and the url-path removed, but any additional leading slashes
    254  * in the url-path are retained (because they imply that we should
    255  * later do "CWD" with a null argument).
    256  *
    257  * Examples:
    258  *	 input url			 output path
    259  *	 ---------			 -----------
    260  *	"http://host"			NULL
    261  *	"http://host/"			"/"
    262  *	"http://host/dir/file"		"/dir/file"
    263  *	"ftp://host/dir/file"		"dir/file"
    264  *	"ftp://host//dir/file"		"/dir/file"
    265  */
    266 static int
    267 parse_url(url, desc, type, user, pass, host, port, path)
    268 	const char	 *url;
    269 	const char	 *desc;
    270 	url_t		 *type;
    271 	char		**user;
    272 	char		**pass;
    273 	char		**host;
    274 	in_port_t	 *port;
    275 	char		**path;
    276 {
    277 	char *cp, *ep, *thost;
    278 	size_t len;
    279 
    280 	if (url == NULL || desc == NULL || type == NULL || user == NULL
    281 	    || pass == NULL || host == NULL || port == NULL || path == NULL)
    282 		errx(1, "parse_url: invoked with NULL argument!");
    283 
    284 	*type = UNKNOWN_URL_T;
    285 	*user = *pass = *host = *path = NULL;
    286 	*port = 0;
    287 
    288 	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
    289 		url += sizeof(HTTP_URL) - 1;
    290 		*type = HTTP_URL_T;
    291 		*port = httpport;
    292 	} else if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
    293 		url += sizeof(FTP_URL) - 1;
    294 		*type = FTP_URL_T;
    295 		*port = ftpport;
    296 	} else if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
    297 		url += sizeof(FILE_URL) - 1;
    298 		*type = FILE_URL_T;
    299 	} else {
    300 		warnx("Invalid %s `%s'", desc, url);
    301 cleanup_parse_url:
    302 		FREEPTR(*user);
    303 		FREEPTR(*pass);
    304 		FREEPTR(*host);
    305 		FREEPTR(*path);
    306 		return (-1);
    307 	}
    308 
    309 	if (*url == '\0')
    310 		return (0);
    311 
    312 			/* find [user[:pass]@]host[:port] */
    313 	ep = strchr(url, '/');
    314 	if (ep == NULL)
    315 		thost = xstrdup(url);
    316 	else {
    317 		len = ep - url;
    318 		thost = (char *)xmalloc(len + 1);
    319 		strncpy(thost, url, len);
    320 		thost[len] = '\0';
    321 		if (*type == FTP_URL_T)	/* skip first / for ftp URLs */
    322 			ep++;
    323 		*path = xstrdup(ep);
    324 	}
    325 
    326 	cp = strchr(thost, '@');
    327 					/* look for user[:pass]@ in ftp URLs */
    328 	if (*type == FTP_URL_T && cp != NULL) {
    329 		anonftp = 0;		/* disable anonftp */
    330 		*user = thost;
    331 		*cp = '\0';
    332 		*host = xstrdup(cp + 1);
    333 		cp = strchr(*user, ':');
    334 		if (cp != NULL) {
    335 			*cp = '\0';
    336 			*pass = xstrdup(cp + 1);
    337 		}
    338 	} else
    339 		*host = thost;
    340 
    341 			/* look for [:port] */
    342 	cp = strrchr(*host, ':');
    343 	if (cp != NULL) {
    344 		long nport;
    345 
    346 		*cp = '\0';
    347 		nport = strtol(cp + 1, &ep, 10);
    348 		if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0') {
    349 			warnx("Invalid port `%s' in %s `%s'", cp, desc, url);
    350 			goto cleanup_parse_url;
    351 		}
    352 		*port = htons((in_port_t)nport);
    353 	}
    354 
    355 	if (debug)
    356 		fprintf(ttyout,
    357 		    "parse_url: user `%s' pass `%s' host %s:%d path `%s'\n",
    358 		    *user ? *user : "<null>", *pass ? *pass : "<null>",
    359 		    *host ? *host : "<null>", ntohs(*port),
    360 		    *path ? *path : "<null>");
    361 
    362 	return (0);
    363 }
    364 
    365 
    366 jmp_buf	httpabort;
    367 
    368 /*
    369  * Retrieve URL, via a proxy if necessary, using HTTP.
    370  * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
    371  * http_proxy as appropriate.
    372  * Supports HTTP redirects.
    373  * Returns -1 on failure, 0 on completed xfer, 1 if ftp connection
    374  * is still open (e.g, ftp xfer with trailing /)
    375  */
    376 static int
    377 fetch_url(url, proxyenv, proxyauth, wwwauth)
    378 	const char	*url;
    379 	const char	*proxyenv;
    380 	char		*proxyauth;
    381 	char		*wwwauth;
    382 {
    383 	struct sockaddr_in	sin;
    384 	struct hostent		*hp;
    385 	volatile sig_t		oldintr, oldintp;
    386 	volatile int		s;
    387 	int 			ischunked, isproxy, rval, hcode;
    388 	size_t			len;
    389 	char			*cp, *ep, *buf, *savefile;
    390 	char			*auth, *location, *message;
    391 	char			*user, *pass, *host, *path, *decodedpath;
    392 	off_t			hashbytes;
    393 	int			 (*closefunc) __P((FILE *));
    394 	FILE			*fin, *fout;
    395 	time_t			mtime;
    396 	url_t			urltype;
    397 	in_port_t		port;
    398 
    399 	closefunc = NULL;
    400 	fin = fout = NULL;
    401 	s = -1;
    402 	buf = savefile = NULL;
    403 	auth = location = message = NULL;
    404 	ischunked = isproxy = hcode = 0;
    405 	rval = 1;
    406 	hp = NULL;
    407 	user = pass = host = path = decodedpath = NULL;
    408 
    409 #ifdef __GNUC__			/* shut up gcc warnings */
    410 	(void)&closefunc;
    411 	(void)&fin;
    412 	(void)&fout;
    413 	(void)&buf;
    414 	(void)&savefile;
    415 	(void)&rval;
    416 	(void)&isproxy;
    417 	(void)&hcode;
    418 	(void)&ischunked;
    419 	(void)&message;
    420 	(void)&location;
    421 	(void)&auth;
    422 	(void)&decodedpath;
    423 #endif
    424 
    425 	if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port, &path)
    426 	    == -1)
    427 		goto cleanup_fetch_url;
    428 
    429 	if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
    430 	    && strcasecmp(host, "localhost") != 0) {
    431 		warnx("No support for non local file URL `%s'", url);
    432 		goto cleanup_fetch_url;
    433 	}
    434 
    435 	if (EMPTYSTRING(path)) {
    436 		if (urltype == FTP_URL_T) {
    437 			rval = fetch_ftp(url);
    438 			goto cleanup_fetch_url;
    439 		}
    440 		if (urltype != HTTP_URL_T || outfile == NULL)  {
    441 			warnx("Invalid URL (no file after host) `%s'", url);
    442 			goto cleanup_fetch_url;
    443 		}
    444 	}
    445 
    446 	decodedpath = xstrdup(path);
    447 	url_decode(decodedpath);
    448 
    449 	if (outfile)
    450 		savefile = xstrdup(outfile);
    451 	else {
    452 		cp = strrchr(decodedpath, '/');		/* find savefile */
    453 		if (cp != NULL)
    454 			savefile = xstrdup(cp + 1);
    455 		else
    456 			savefile = xstrdup(decodedpath);
    457 	}
    458 	if (EMPTYSTRING(savefile)) {
    459 		if (urltype == FTP_URL_T) {
    460 			rval = fetch_ftp(url);
    461 			goto cleanup_fetch_url;
    462 		}
    463 		warnx("Invalid URL (no file after directory) `%s'", url);
    464 		goto cleanup_fetch_url;
    465 	} else {
    466 		if (debug)
    467 			fprintf(ttyout, "got savefile as `%s'\n", savefile);
    468 	}
    469 
    470 	filesize = -1;
    471 	mtime = -1;
    472 	if (urltype == FILE_URL_T) {		/* file:// URLs */
    473 		struct stat sb;
    474 
    475 		direction = "copied";
    476 		fin = fopen(decodedpath, "r");
    477 		if (fin == NULL) {
    478 			warn("Cannot open file `%s'", decodedpath);
    479 			goto cleanup_fetch_url;
    480 		}
    481 		if (fstat(fileno(fin), &sb) == 0) {
    482 			mtime = sb.st_mtime;
    483 			filesize = sb.st_size;
    484 		}
    485 		if (verbose)
    486 			fprintf(ttyout, "Copying %s\n", decodedpath);
    487 	} else {				/* ftp:// or http:// URLs */
    488 		if (proxyenv == NULL) {
    489 			if (urltype == HTTP_URL_T)
    490 				proxyenv = httpproxy;
    491 			else if (urltype == FTP_URL_T)
    492 				proxyenv = ftpproxy;
    493 		}
    494 		direction = "retrieved";
    495 		if (proxyenv != NULL) {				/* use proxy */
    496 			url_t purltype;
    497 			char *puser, *ppass, *phost;
    498 			char *ppath;
    499 
    500 			isproxy = 1;
    501 
    502 				/* check URL against list of no_proxied sites */
    503 			if (no_proxy != NULL) {
    504 				char *np, *np_copy;
    505 				long np_port;
    506 				size_t hlen, plen;
    507 
    508 				np_copy = xstrdup(no_proxy);
    509 				hlen = strlen(host);
    510 				while ((cp = strsep(&np_copy, " ,")) != NULL) {
    511 					if (*cp == '\0')
    512 						continue;
    513 					if ((np = strchr(cp, ':')) != NULL) {
    514 						*np = '\0';
    515 						np_port =
    516 						    strtol(np + 1, &ep, 10);
    517 						if (*ep != '\0')
    518 							continue;
    519 						if (port !=
    520 						    htons((in_port_t)np_port))
    521 							continue;
    522 					}
    523 					plen = strlen(cp);
    524 					if (strncasecmp(host + hlen - plen,
    525 					    cp, plen) == 0) {
    526 						isproxy = 0;
    527 						break;
    528 					}
    529 				}
    530 				FREEPTR(np_copy);
    531 			}
    532 
    533 			if (isproxy) {
    534 				if (parse_url(proxyenv, "proxy URL", &purltype,
    535 				    &puser, &ppass, &phost, &port, &ppath)
    536 				    == -1)
    537 					goto cleanup_fetch_url;
    538 
    539 				if ((purltype != HTTP_URL_T
    540 				     && purltype != FTP_URL_T) ||
    541 				    EMPTYSTRING(phost) ||
    542 				    (! EMPTYSTRING(ppath)
    543 				     && strcmp(ppath, "/") != 0)) {
    544 					warnx("Malformed proxy URL `%s'",
    545 					    proxyenv);
    546 					FREEPTR(puser);
    547 					FREEPTR(ppass);
    548 					FREEPTR(phost);
    549 					FREEPTR(ppath);
    550 					goto cleanup_fetch_url;
    551 				}
    552 
    553 				FREEPTR(user);
    554 				user = puser;
    555 				FREEPTR(pass);
    556 				pass = ppass;
    557 				FREEPTR(host);
    558 				host = phost;
    559 				FREEPTR(path);
    560 				FREEPTR(ppath);
    561 				path = xstrdup(url);
    562 			}
    563 		} /* proxyenv != NULL */
    564 
    565 		memset(&sin, 0, sizeof(sin));
    566 		sin.sin_family = AF_INET;
    567 
    568 		if (isdigit((unsigned char)host[0])) {
    569 			if (inet_aton(host, &sin.sin_addr) == 0) {
    570 				warnx("Invalid IP address `%s'", host);
    571 				goto cleanup_fetch_url;
    572 			}
    573 		} else {
    574 			hp = gethostbyname(host);
    575 			if (hp == NULL) {
    576 				warnx("%s: %s", host, hstrerror(h_errno));
    577 				goto cleanup_fetch_url;
    578 			}
    579 			if (hp->h_addrtype != AF_INET) {
    580 				warnx("`%s': not an Internet address?", host);
    581 				goto cleanup_fetch_url;
    582 			}
    583 			memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    584 		}
    585 
    586 		if (port == 0) {
    587 			warnx("Unknown port for URL `%s'", url);
    588 			goto cleanup_fetch_url;
    589 		}
    590 		sin.sin_port = port;
    591 
    592 		s = socket(AF_INET, SOCK_STREAM, 0);
    593 		if (s == -1) {
    594 			warn("Can't create socket");
    595 			goto cleanup_fetch_url;
    596 		}
    597 
    598 		while (xconnect(s, (struct sockaddr *)&sin,
    599 		    sizeof(sin)) == -1) {
    600 			if (errno == EINTR)
    601 				continue;
    602 			if (hp && hp->h_addr_list[1]) {
    603 				int oerrno = errno;
    604 				char *ia;
    605 
    606 				ia = inet_ntoa(sin.sin_addr);
    607 				errno = oerrno;
    608 				warn("Connect to address `%s'", ia);
    609 				hp->h_addr_list++;
    610 				memcpy(&sin.sin_addr, hp->h_addr_list[0],
    611 				    (size_t)hp->h_length);
    612 				if (verbose)
    613 					fprintf(ttyout, "Trying %s...\n",
    614 					    inet_ntoa(sin.sin_addr));
    615 				(void)close(s);
    616 				s = socket(AF_INET, SOCK_STREAM, 0);
    617 				if (s < 0) {
    618 					warn("Can't create socket");
    619 					goto cleanup_fetch_url;
    620 				}
    621 				continue;
    622 			}
    623 			warn("Can't connect to `%s'", host);
    624 			goto cleanup_fetch_url;
    625 		}
    626 
    627 		fin = fdopen(s, "r+");
    628 		/*
    629 		 * Construct and send the request.
    630 		 * Proxy requests don't want leading /.
    631 		 */
    632 		if (isproxy) {
    633 			if (verbose)
    634 				fprintf(ttyout, "Requesting %s\n  (via %s)\n",
    635 				    url, proxyenv);
    636 			fprintf(fin, "GET %s HTTP/1.0\r\n", path);
    637 			if (flushcache)
    638 				fprintf(fin, "Pragma: no-cache\r\n");
    639 		} else {
    640 			struct utsname unam;
    641 
    642 			if (verbose)
    643 				fprintf(ttyout, "Requesting %s\n", url);
    644 			fprintf(fin, "GET %s HTTP/1.1\r\n", path);
    645 			fprintf(fin, "Host: %s:%d\r\n", host, ntohs(port));
    646 			fprintf(fin, "Accept: */*\r\n");
    647 			if (uname(&unam) != -1) {
    648 				fprintf(fin, "User-Agent: %s-%s/ftp\r\n",
    649 				    unam.sysname, unam.release);
    650 			}
    651 			fprintf(fin, "Connection: close\r\n");
    652 			if (flushcache)
    653 				fprintf(fin, "Cache-Control: no-cache\r\n");
    654 		}
    655 		if (wwwauth) {
    656 			if (verbose)
    657 				fprintf(ttyout, "  (with authorization)\n");
    658 			fprintf(fin, "Authorization: %s\r\n", wwwauth);
    659 		}
    660 		if (proxyauth) {
    661 			if (verbose)
    662 				fprintf(ttyout,
    663 				    "  (with proxy authorization)\n");
    664 			fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
    665 		}
    666 		fprintf(fin, "\r\n");
    667 		if (fflush(fin) == EOF) {
    668 			warn("Writing HTTP request");
    669 			goto cleanup_fetch_url;
    670 		}
    671 
    672 				/* Read the response */
    673 		if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
    674 			warn("Receiving HTTP reply");
    675 			goto cleanup_fetch_url;
    676 		}
    677 		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
    678 			buf[--len] = '\0';
    679 		if (debug)
    680 			fprintf(ttyout, "received `%s'\n", buf);
    681 
    682 				/* Determine HTTP response code */
    683 		cp = strchr(buf, ' ');
    684 		if (cp == NULL)
    685 			goto improper;
    686 		else
    687 			cp++;
    688 		hcode = strtol(cp, &ep, 10);
    689 		if (*ep != '\0' && !isspace((unsigned char)*ep))
    690 			goto improper;
    691 		message = xstrdup(cp);
    692 
    693 				/* Read the rest of the header. */
    694 		FREEPTR(buf);
    695 		while (1) {
    696 			if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
    697 			    == NULL) {
    698 				warn("Receiving HTTP reply");
    699 				goto cleanup_fetch_url;
    700 			}
    701 			while (len > 0 &&
    702 			    (buf[len-1] == '\r' || buf[len-1] == '\n'))
    703 				buf[--len] = '\0';
    704 			if (len == 0)
    705 				break;
    706 			if (debug)
    707 				fprintf(ttyout, "received `%s'\n", buf);
    708 
    709 				/* Look for some headers */
    710 			cp = buf;
    711 
    712 #define CONTENTLEN "Content-Length: "
    713 			if (strncasecmp(cp, CONTENTLEN,
    714 					sizeof(CONTENTLEN) - 1) == 0) {
    715 				cp += sizeof(CONTENTLEN) - 1;
    716 				filesize = strtol(cp, &ep, 10);
    717 				if (filesize < 1 || *ep != '\0')
    718 					goto improper;
    719 				if (debug)
    720 					fprintf(ttyout,
    721 #ifndef NO_QUAD
    722 					    "parsed length as: %qd\n",
    723 					    (long long)filesize);
    724 #else
    725 					    "parsed length as: %ld\n",
    726 					    (long)filesize);
    727 #endif
    728 
    729 #define LASTMOD "Last-Modified: "
    730 			} else if (strncasecmp(cp, LASTMOD,
    731 						sizeof(LASTMOD) - 1) == 0) {
    732 				struct tm parsed;
    733 				char *t;
    734 
    735 				cp += sizeof(LASTMOD) - 1;
    736 							/* RFC 1123 */
    737 				if ((t = strptime(cp,
    738 						"%a, %d %b %Y %H:%M:%S GMT",
    739 						&parsed))
    740 							/* RFC 850 */
    741 				    || (t = strptime(cp,
    742 						"%a, %d-%b-%y %H:%M:%S GMT",
    743 						&parsed))
    744 							/* asctime */
    745 				    || (t = strptime(cp,
    746 						"%a, %b %d %H:%M:%S %Y",
    747 						&parsed))) {
    748 					parsed.tm_isdst = -1;
    749 					if (*t == '\0')
    750 						mtime = mkgmtime(&parsed);
    751 					if (debug && mtime != -1) {
    752 						fprintf(ttyout,
    753 						    "parsed date as: %s",
    754 						    ctime(&mtime));
    755 					}
    756 				}
    757 
    758 #define LOCATION "Location: "
    759 			} else if (strncasecmp(cp, LOCATION,
    760 						sizeof(LOCATION) - 1) == 0) {
    761 				cp += sizeof(LOCATION) - 1;
    762 				location = xstrdup(cp);
    763 				if (debug)
    764 					fprintf(ttyout,
    765 					    "parsed location as: %s\n", cp);
    766 
    767 #define TRANSENC "Transfer-Encoding: "
    768 			} else if (strncasecmp(cp, TRANSENC,
    769 						sizeof(TRANSENC) - 1) == 0) {
    770 				cp += sizeof(TRANSENC) - 1;
    771 				if (strcasecmp(cp, "chunked") != 0) {
    772 					warnx(
    773 				    "Unsupported transfer encoding - `%s'",
    774 					    cp);
    775 					goto cleanup_fetch_url;
    776 				}
    777 				ischunked++;
    778 				if (debug)
    779 					fprintf(ttyout,
    780 					    "using chunked encoding\n");
    781 
    782 #define PROXYAUTH "Proxy-Authenticate: "
    783 			} else if (strncasecmp(cp, PROXYAUTH,
    784 						sizeof(PROXYAUTH) - 1) == 0) {
    785 				cp += sizeof(PROXYAUTH) - 1;
    786 				FREEPTR(auth);
    787 				auth = xstrdup(cp);
    788 				if (debug)
    789 					fprintf(ttyout,
    790 					    "parsed proxy-auth as: %s\n", cp);
    791 
    792 #define WWWAUTH	"WWW-Authenticate: "
    793 			} else if (strncasecmp(cp, WWWAUTH,
    794 			    sizeof(WWWAUTH) - 1) == 0) {
    795 				cp += sizeof(WWWAUTH) - 1;
    796 				FREEPTR(auth);
    797 				auth = xstrdup(cp);
    798 				if (debug)
    799 					fprintf(ttyout,
    800 					    "parsed www-auth as: %s\n", cp);
    801 
    802 			}
    803 
    804 		}
    805 		FREEPTR(buf);
    806 
    807 		switch (hcode) {
    808 		case 200:
    809 			break;
    810 		case 300:
    811 		case 301:
    812 		case 302:
    813 		case 303:
    814 		case 305:
    815 			if (EMPTYSTRING(location)) {
    816 				warnx(
    817 				"No redirection Location provided by server");
    818 				goto cleanup_fetch_url;
    819 			}
    820 			if (redirect_loop++ > 5) {
    821 				warnx("Too many redirections requested");
    822 				goto cleanup_fetch_url;
    823 			}
    824 			if (hcode == 305) {
    825 				if (verbose)
    826 					fprintf(ttyout, "Redirected via %s\n",
    827 					    location);
    828 				rval = fetch_url(url, location,
    829 				    proxyauth, wwwauth);
    830 			} else {
    831 				if (verbose)
    832 					fprintf(ttyout, "Redirected to %s\n",
    833 					    location);
    834 				rval = go_fetch(location);
    835 			}
    836 			goto cleanup_fetch_url;
    837 		case 401:
    838 		case 407:
    839 		    {
    840 			char **authp;
    841 
    842 			fprintf(ttyout, "%s\n", message);
    843 			if (EMPTYSTRING(auth)) {
    844 				warnx(
    845 			    "No authentication challenge provided by server");
    846 				goto cleanup_fetch_url;
    847 			}
    848 			authp = (hcode == 401) ? &wwwauth : &proxyauth;
    849 			if (*authp != NULL) {
    850 				char reply[10];
    851 
    852 				fprintf(ttyout,
    853 				    "Authorization failed. Retry (y/n)? ");
    854 				if (fgets(reply, sizeof(reply), stdin) != NULL
    855 				    && tolower(reply[0]) != 'y')
    856 					goto cleanup_fetch_url;
    857 			}
    858 			if (auth_url(auth, authp) == 0) {
    859 				rval = fetch_url(url, proxyenv,
    860 				    proxyauth, wwwauth);
    861 				memset(*authp, '\0', strlen(*authp));
    862 				FREEPTR(*authp);
    863 			}
    864 			goto cleanup_fetch_url;
    865 		    }
    866 		default:
    867 			if (message)
    868 				warnx("Error retrieving file - `%s'", message);
    869 			else
    870 				warnx("Unknown error retrieving file");
    871 			goto cleanup_fetch_url;
    872 		}
    873 	}		/* end of ftp:// or http:// specific setup */
    874 
    875 	oldintr = oldintp = NULL;
    876 
    877 			/* Open the output file. */
    878 	if (strcmp(savefile, "-") == 0) {
    879 		fout = stdout;
    880 	} else if (*savefile == '|') {
    881 		oldintp = signal(SIGPIPE, SIG_IGN);
    882 		fout = popen(savefile + 1, "w");
    883 		if (fout == NULL) {
    884 			warn("Can't run `%s'", savefile + 1);
    885 			goto cleanup_fetch_url;
    886 		}
    887 		closefunc = pclose;
    888 	} else {
    889 		fout = fopen(savefile, "w");
    890 		if (fout == NULL) {
    891 			warn("Can't open `%s'", savefile);
    892 			goto cleanup_fetch_url;
    893 		}
    894 		closefunc = fclose;
    895 	}
    896 
    897 			/* Trap signals */
    898 	if (setjmp(httpabort)) {
    899 		if (oldintr)
    900 			(void)signal(SIGINT, oldintr);
    901 		if (oldintp)
    902 			(void)signal(SIGPIPE, oldintp);
    903 		goto cleanup_fetch_url;
    904 	}
    905 	oldintr = signal(SIGINT, aborthttp);
    906 
    907 	bytes = 0;
    908 	hashbytes = mark;
    909 	progressmeter(-1);
    910 
    911 			/* Finally, suck down the file. */
    912 	buf = xmalloc(BUFSIZ + 1);
    913 	do {
    914 		ssize_t chunksize;
    915 
    916 		chunksize = 0;
    917 					/* read chunksize */
    918 		if (ischunked) {
    919 			if (fgets(buf, BUFSIZ, fin) == NULL) {
    920 				warnx("Unexpected EOF reading chunksize");
    921 				goto cleanup_fetch_url;
    922 			}
    923 			chunksize = strtol(buf, &ep, 16);
    924 			if (strcmp(ep, "\r\n") != 0) {
    925 				warnx("Unexpected data following chunksize");
    926 				goto cleanup_fetch_url;
    927 			}
    928 			if (debug)
    929 				fprintf(ttyout, "got chunksize of %qd\n",
    930 				    (long long)chunksize);
    931 			if (chunksize == 0)
    932 				break;
    933 		}
    934 		while ((len = fread(buf, sizeof(char),
    935 		    ischunked ? MIN(chunksize, BUFSIZ) : BUFSIZ, fin)) > 0) {
    936 			bytes += len;
    937 			if (fwrite(buf, sizeof(char), len, fout) != len) {
    938 				warn("Writing `%s'", savefile);
    939 				goto cleanup_fetch_url;
    940 			}
    941 			if (hash && !progress) {
    942 				while (bytes >= hashbytes) {
    943 					(void)putc('#', ttyout);
    944 					hashbytes += mark;
    945 				}
    946 				(void)fflush(ttyout);
    947 			}
    948 			if (ischunked)
    949 				chunksize -= len;
    950 		}
    951 					/* read CRLF after chunk*/
    952 		if (ischunked) {
    953 			if (fgets(buf, BUFSIZ, fin) == NULL)
    954 				break;
    955 			if (strcmp(buf, "\r\n") != 0) {
    956 				warnx("Unexpected data following chunk");
    957 				goto cleanup_fetch_url;
    958 			}
    959 		}
    960 	} while (ischunked);
    961 	if (hash && !progress && bytes > 0) {
    962 		if (bytes < mark)
    963 			(void)putc('#', ttyout);
    964 		(void)putc('\n', ttyout);
    965 	}
    966 	if (ferror(fin)) {
    967 		warn("Reading file");
    968 		goto cleanup_fetch_url;
    969 	}
    970 	progressmeter(1);
    971 	(void)fflush(fout);
    972 	(void)signal(SIGINT, oldintr);
    973 	if (oldintp)
    974 		(void)signal(SIGPIPE, oldintp);
    975 	if (closefunc == fclose && mtime != -1) {
    976 		struct timeval tval[2];
    977 
    978 		(void)gettimeofday(&tval[0], NULL);
    979 		tval[1].tv_sec = mtime;
    980 		tval[1].tv_usec = 0;
    981 		(*closefunc)(fout);
    982 		fout = NULL;
    983 
    984 		if (utimes(savefile, tval) == -1) {
    985 			fprintf(ttyout,
    986 			    "Can't change modification time to %s",
    987 			    asctime(localtime(&mtime)));
    988 		}
    989 	}
    990 	if (bytes > 0)
    991 		ptransfer(0);
    992 
    993 	rval = 0;
    994 	goto cleanup_fetch_url;
    995 
    996 improper:
    997 	warnx("Improper response from `%s'", host);
    998 
    999 cleanup_fetch_url:
   1000 	resetsockbufsize();
   1001 	if (fin != NULL)
   1002 		fclose(fin);
   1003 	else if (s != -1)
   1004 		close(s);
   1005 	if (closefunc != NULL && fout != NULL)
   1006 		(*closefunc)(fout);
   1007 	FREEPTR(savefile);
   1008 	FREEPTR(user);
   1009 	FREEPTR(pass);
   1010 	FREEPTR(host);
   1011 	FREEPTR(path);
   1012 	FREEPTR(decodedpath);
   1013 	FREEPTR(buf);
   1014 	FREEPTR(auth);
   1015 	FREEPTR(location);
   1016 	FREEPTR(message);
   1017 	return (rval);
   1018 }
   1019 
   1020 /*
   1021  * Abort a HTTP retrieval
   1022  */
   1023 void
   1024 aborthttp(notused)
   1025 	int notused;
   1026 {
   1027 
   1028 	alarmtimer(0);
   1029 	fputs("\nHTTP fetch aborted.\n", ttyout);
   1030 	longjmp(httpabort, 1);
   1031 }
   1032 
   1033 /*
   1034  * Retrieve ftp URL or classic ftp argument using FTP.
   1035  * Returns -1 on failure, 0 on completed xfer, 1 if ftp connection
   1036  * is still open (e.g, ftp xfer with trailing /)
   1037  */
   1038 static int
   1039 fetch_ftp(url)
   1040 	const char *url;
   1041 {
   1042 	char		*cp, *xargv[5], rempath[MAXPATHLEN];
   1043 	char		portnum[6];		/* large enough for "65535\0" */
   1044 	char		*host, *path, *dir, *file, *user, *pass;
   1045 	in_port_t	port;
   1046 	int		dirhasglob, filehasglob, oautologin, rval, type, xargc;
   1047 	url_t		urltype;
   1048 
   1049 	host = path = dir = file = user = pass = NULL;
   1050 	port = 0;
   1051 	rval = 1;
   1052 	type = TYPE_I;
   1053 
   1054 	if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
   1055 		if ((parse_url(url, "URL", &urltype, &user, &pass,
   1056 		    &host, &port, &path) == -1) ||
   1057 		    (user != NULL && *user == '\0') ||
   1058 		    (pass != NULL && *pass == '\0') ||
   1059 		    EMPTYSTRING(host)) {
   1060 			warnx("Invalid URL `%s'", url);
   1061 			goto cleanup_fetch_ftp;
   1062 		}
   1063 		url_decode(user);
   1064 		url_decode(pass);
   1065 		/*
   1066 		 * Note: Don't url_decode(path) here.  We need to keep the
   1067 		 * distinction between "/" and "%2F" until later.
   1068 		 */
   1069 
   1070 					/* check for trailing ';type=[aid]' */
   1071 		cp = strrchr(path, ';');
   1072 		if (cp != NULL) {
   1073 			if (strcasecmp(cp, ";type=a") == 0)
   1074 				type = TYPE_A;
   1075 			else if (strcasecmp(cp, ";type=i") == 0)
   1076 				type = TYPE_I;
   1077 			else if (strcasecmp(cp, ";type=d") == 0) {
   1078 				warnx(
   1079 			    "Directory listing via a URL is not supported");
   1080 				goto cleanup_fetch_ftp;
   1081 			} else {
   1082 				warnx("Invalid suffix `%s' in URL `%s'", cp,
   1083 				    url);
   1084 				goto cleanup_fetch_ftp;
   1085 			}
   1086 			*cp = 0;
   1087 		}
   1088 	} else {			/* classic style `host:file' */
   1089 		urltype = CLASSIC_URL_T;
   1090 		host = xstrdup(url);
   1091 		cp = strchr(host, ':');
   1092 		if (cp != NULL) {
   1093 			*cp = '\0';
   1094 			path = xstrdup(cp + 1);
   1095 		}
   1096 	}
   1097 	if (EMPTYSTRING(host))
   1098 		goto cleanup_fetch_ftp;
   1099 
   1100 			/* Extract the file and (if present) directory name. */
   1101 	dir = path;
   1102 	if (! EMPTYSTRING(dir)) {
   1103 		/*
   1104 		 * If we are dealing with classic `host:path' syntax,
   1105 		 * then a path of the form `/file' (resulting from
   1106 		 * input of the form `host:/file') means that we should
   1107 		 * do "CWD /" before retreiving the file.  So we set
   1108 		 * dir="/" and file="file".
   1109 		 *
   1110 		 * But if we are dealing with URLs like
   1111 		 * `ftp://host/path' then a path of the form `/file'
   1112 		 * (resulting from a URL of the form `ftp://host//file')
   1113 		 * means that we should do `CWD ' (with an empty
   1114 		 * argument) before retrieving the file.  So we set
   1115 		 * dir="" and file="file".
   1116 		 *
   1117 		 * If the path does not contain / at all, we set
   1118 		 * dir=NULL.  (We get a path without any slashes if
   1119 		 * we are dealing with classic `host:file' or URL
   1120 		 * `ftp://host/file'.)
   1121 		 *
   1122 		 * In all other cases, we set dir to a string that does
   1123 		 * not include the final '/' that separates the dir part
   1124 		 * from the file part of the path.  (This will be the
   1125 		 * empty string if and only if we are dealing with a
   1126 		 * path of the form `/file' resulting from an URL of the
   1127 		 * form `ftp://host//file'.)
   1128 		 */
   1129 		cp = strrchr(dir, '/');
   1130 		if (cp == dir && urltype == CLASSIC_URL_T) {
   1131 			file = cp + 1;
   1132 			dir = "/";
   1133 		} else if (cp != NULL) {
   1134 			*cp++ = '\0';
   1135 			file = cp;
   1136 		} else {
   1137 			file = dir;
   1138 			dir = NULL;
   1139 		}
   1140 	}
   1141 	if (urltype == FTP_URL_T && file != NULL) {
   1142 		url_decode(file);
   1143 		/* but still don't url_decode(dir) */
   1144 	}
   1145 	if (debug)
   1146 		fprintf(ttyout,
   1147     "fetch_ftp: user `%s' pass `%s' host %s:%d path `%s' dir `%s' file `%s'\n",
   1148 		    user ? user : "<null>", pass ? pass : "<null>",
   1149 		    host ? host : "<null>", ntohs(port), path ? path : "<null>",
   1150 		    dir ? dir : "<null>", file ? file : "<null>");
   1151 
   1152 	dirhasglob = filehasglob = 0;
   1153 	if (doglob && urltype == CLASSIC_URL_T) {
   1154 		if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
   1155 			dirhasglob = 1;
   1156 		if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
   1157 			filehasglob = 1;
   1158 	}
   1159 
   1160 			/* Set up the connection */
   1161 	if (connected)
   1162 		disconnect(0, NULL);
   1163 	xargv[0] = __progname;
   1164 	xargv[1] = host;
   1165 	xargv[2] = NULL;
   1166 	xargc = 2;
   1167 	if (port) {
   1168 		snprintf(portnum, sizeof(portnum), "%d", ntohs(port));
   1169 		xargv[2] = portnum;
   1170 		xargv[3] = NULL;
   1171 		xargc = 3;
   1172 	}
   1173 	oautologin = autologin;
   1174 	if (user != NULL)
   1175 		autologin = 0;
   1176 	setpeer(xargc, xargv);
   1177 	autologin = oautologin;
   1178 	if ((connected == 0) || ((connected == 1)
   1179 	    && !ftp_login(host, user, pass))) {
   1180 		warnx("Can't connect or login to host `%s'", host);
   1181 		goto cleanup_fetch_ftp;
   1182 	}
   1183 
   1184 	switch (type) {
   1185 	case TYPE_A:
   1186 		setascii(0, NULL);
   1187 		break;
   1188 	case TYPE_I:
   1189 		setbinary(0, NULL);
   1190 		break;
   1191 	default:
   1192 		errx(1, "fetch_ftp: unknown transfer type %d\n", type);
   1193 	}
   1194 
   1195 			/*
   1196 			 * Change directories, if necessary.
   1197 			 *
   1198 			 * Note: don't use EMPTYSTRING(dir) below, because
   1199 			 * dir="" means something different from dir=NULL.
   1200 			 */
   1201 	if (dir != NULL && !dirhasglob) {
   1202 		char *nextpart;
   1203 
   1204 		/*
   1205 		 * If we are dealing with a classic `host:path' (urltype
   1206 		 * is CLASSIC_URL_T) then we have a raw directory
   1207 		 * name (not encoded in any way) and we can change
   1208 		 * directories in one step.
   1209 		 *
   1210 		 * If we are dealing with an `ftp://host/path' URL
   1211 		 * (urltype is FTP_URL_T), then RFC 1738 says we need to
   1212 		 * send a separate CWD command for each unescaped "/"
   1213 		 * in the path, and we have to interpret %hex escaping
   1214 		 * *after* we find the slashes.  It's possible to get
   1215 		 * empty components here, (from multiple adjacent
   1216 		 * slashes in the path) and RFC 1738 says that we should
   1217 		 * still do `CWD ' (with a null argument) in such cases.
   1218 		 *
   1219 		 * XXX: many ftp servers don't support `CWD ', so we
   1220 		 * just skip the empty components.
   1221 		 *
   1222 		 * Examples:
   1223 		 *
   1224 		 * host:file			dir=NULL, urltype=CLASSIC_URL_T
   1225 		 *		"RETR file"
   1226 		 * ftp://host/file		dir=NULL, urltype=FTP_URL_T
   1227 		 *		"RETR file"
   1228 		 * ftp://host//file		dir="", urltype=FTP_URL_T
   1229 		 *		(no-op), "RETR file"
   1230 		 * host:/file			dir="/", urltype=CLASSIC_URL_T
   1231 		 *		"CWD /", "RETR file"
   1232 		 * ftp://host///file		dir="/", urltype=FTP_URL_T
   1233 		 *		(no-op), (no-op), "RETR file"
   1234 		 * ftp://host/%2F/file		dir="%2F", urltype=FTP_URL_T
   1235 		 *		"CWD /", "RETR file"
   1236 		 * ftp://host/foo/file		dir="foo", urltype=FTP_URL_T
   1237 		 *		"CWD foo", "RETR file"
   1238 		 * ftp://host/foo/bar/file	dir="foo/bar"
   1239 		 *		"CWD foo", "CWD bar", "RETR file"
   1240 		 * ftp://host//foo/bar/file	dir="/foo/bar"
   1241 		 *		(no-op), "CWD foo", "CWD bar", "RETR file"
   1242 		 * ftp://host/foo//bar/file	dir="foo//bar"
   1243 		 *		"CWD foo", (no-op), "CWD bar", "RETR file"
   1244 		 * ftp://host/%2F/foo/bar/file	dir="%2F/foo/bar"
   1245 		 *		"CWD /", "CWD foo", "CWD bar", "RETR file"
   1246 		 * ftp://host/%2Ffoo/bar/file	dir="%2Ffoo/bar"
   1247 		 *		"CWD /foo", "CWD bar", "RETR file"
   1248 		 * ftp://host/%2Ffoo%2Fbar/file	dir="%2Ffoo%2Fbar"
   1249 		 *		"CWD /foo/bar", "RETR file"
   1250 		 * ftp://host/%2Ffoo%2Fbar%2Ffile	dir=NULL
   1251 		 *		"RETR /foo/bar/file"
   1252 		 *
   1253 		 * Note that we don't need `dir' after this point.
   1254 		 */
   1255 		do {
   1256 			if (urltype == FTP_URL_T) {
   1257 				nextpart = strchr(dir, '/');
   1258 				if (nextpart) {
   1259 					*nextpart = '\0';
   1260 					nextpart++;
   1261 				}
   1262 				url_decode(dir);
   1263 			} else
   1264 				nextpart = NULL;
   1265 			if (debug)
   1266 				fprintf(ttyout, "dir `%s', nextpart `%s'\n",
   1267 				    dir ? dir : "<null>",
   1268 				    nextpart ? nextpart : "<null>");
   1269 			if (*dir != '\0') {
   1270 				xargv[0] = "cd";
   1271 				xargv[1] = dir;
   1272 				xargv[2] = NULL;
   1273 				dirchange = 0;
   1274 				cd(2, xargv);
   1275 				if (! dirchange)
   1276 					goto cleanup_fetch_ftp;
   1277 			}
   1278 			dir = nextpart;
   1279 		} while (dir != NULL);
   1280 	}
   1281 
   1282 	if (EMPTYSTRING(file)) {
   1283 		rval = -1;
   1284 		goto cleanup_fetch_ftp;
   1285 	}
   1286 
   1287 	if (dirhasglob) {
   1288 		snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
   1289 		file = rempath;
   1290 	}
   1291 
   1292 			/* Fetch the file(s). */
   1293 	xargc = 2;
   1294 	xargv[0] = "get";
   1295 	xargv[1] = file;
   1296 	xargv[2] = NULL;
   1297 	if (dirhasglob || filehasglob) {
   1298 		int ointeractive;
   1299 
   1300 		ointeractive = interactive;
   1301 		interactive = 0;
   1302 		xargv[0] = "mget";
   1303 		mget(xargc, xargv);
   1304 		interactive = ointeractive;
   1305 	} else {
   1306 		if (outfile == NULL) {
   1307 			cp = strrchr(file, '/');	/* find savefile */
   1308 			if (cp != NULL)
   1309 				outfile = cp + 1;
   1310 			else
   1311 				outfile = file;
   1312 		}
   1313 		xargv[2] = (char *)outfile;
   1314 		xargv[3] = NULL;
   1315 		xargc++;
   1316 		if (restartautofetch)
   1317 			reget(xargc, xargv);
   1318 		else
   1319 			get(xargc, xargv);
   1320 	}
   1321 
   1322 	if ((code / 100) == COMPLETE)
   1323 		rval = 0;
   1324 
   1325 cleanup_fetch_ftp:
   1326 	FREEPTR(host);
   1327 	FREEPTR(path);
   1328 	FREEPTR(user);
   1329 	FREEPTR(pass);
   1330 	return (rval);
   1331 }
   1332 
   1333 /*
   1334  * Retrieve the given file to outfile.
   1335  * Supports arguments of the form:
   1336  *	"host:path", "ftp://host/path"	if $ftpproxy, call fetch_url() else
   1337  *					call fetch_ftp()
   1338  *	"http://host/path"		call fetch_url() to use HTTP
   1339  *	"file:///path"			call fetch_url() to copy
   1340  *	"about:..."			print a message
   1341  *
   1342  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
   1343  * is still open (e.g, ftp xfer with trailing /)
   1344  */
   1345 static int
   1346 go_fetch(url)
   1347 	const char *url;
   1348 {
   1349 
   1350 #ifndef SMALL
   1351 	/*
   1352 	 * Check for about:*
   1353 	 */
   1354 	if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
   1355 		url += sizeof(ABOUT_URL) -1;
   1356 		if (strcasecmp(url, "ftp") == 0) {
   1357 			fprintf(ttyout, "%s\n%s\n",
   1358 "This version of ftp has been enhanced by Luke Mewburn <lukem (at) netbsd.org>.",
   1359 "Execute `man ftp' for more details");
   1360 		} else if (strcasecmp(url, "netbsd") == 0) {
   1361 			fprintf(ttyout, "%s\n%s\n",
   1362 "NetBSD is a freely available and redistributable UNIX-like operating system.",
   1363 "For more information, see http://www.netbsd.org/index.html");
   1364 		} else {
   1365 			fprintf(ttyout, "`%s' is an interesting topic.\n", url);
   1366 		}
   1367 		return (0);
   1368 	}
   1369 #endif /* SMALL */
   1370 
   1371 	/*
   1372 	 * Check for file:// and http:// URLs.
   1373 	 */
   1374 	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
   1375 	    strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0)
   1376 		return (fetch_url(url, NULL, NULL, NULL));
   1377 
   1378 	/*
   1379 	 * Try FTP URL-style and host:file arguments next.
   1380 	 * If ftpproxy is set with an FTP URL, use fetch_url()
   1381 	 * Othewise, use fetch_ftp().
   1382 	 */
   1383 	if (ftpproxy && strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0)
   1384 		return (fetch_url(url, NULL, NULL, NULL));
   1385 
   1386 	return (fetch_ftp(url));
   1387 }
   1388 
   1389 /*
   1390  * Retrieve multiple files from the command line,
   1391  * calling go_fetch() for each file.
   1392  *
   1393  * If an ftp path has a trailing "/", the path will be cd-ed into and
   1394  * the connection remains open, and the function will return -1
   1395  * (to indicate the connection is alive).
   1396  * If an error occurs the return value will be the offset+1 in
   1397  * argv[] of the file that caused a problem (i.e, argv[x]
   1398  * returns x+1)
   1399  * Otherwise, 0 is returned if all files retrieved successfully.
   1400  */
   1401 int
   1402 auto_fetch(argc, argv)
   1403 	int argc;
   1404 	char *argv[];
   1405 {
   1406 	volatile int	argpos;
   1407 	int		rval;
   1408 
   1409 	argpos = 0;
   1410 
   1411 	if (setjmp(toplevel)) {
   1412 		if (connected)
   1413 			disconnect(0, NULL);
   1414 		return (argpos + 1);
   1415 	}
   1416 	(void)signal(SIGINT, (sig_t)intr);
   1417 	(void)signal(SIGPIPE, (sig_t)lostpeer);
   1418 
   1419 	/*
   1420 	 * Loop through as long as there's files to fetch.
   1421 	 */
   1422 	for (rval = 0; (rval == 0) && (argpos < argc); argpos++) {
   1423 		if (strchr(argv[argpos], ':') == NULL)
   1424 			break;
   1425 		redirect_loop = 0;
   1426 		anonftp = 1;		/* Handle "automatic" transfers. */
   1427 		rval = go_fetch(argv[argpos]);
   1428 		if (outfile != NULL && strcmp(outfile, "-") != 0
   1429 		    && outfile[0] != '|')
   1430 			outfile = NULL;
   1431 		if (rval > 0)
   1432 			rval = argpos + 1;
   1433 	}
   1434 
   1435 	if (connected && rval != -1)
   1436 		disconnect(0, NULL);
   1437 	return (rval);
   1438 }
   1439