Home | History | Annotate | Line # | Download | only in ftp
fetch.c revision 1.43
      1 /*	$NetBSD: fetch.c,v 1.43 1998/12/31 02:10:34 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997, 1998 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.43 1998/12/31 02:10:34 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 } url_t;
     80 
     81 static int	go_fetch __P((const char *, const char *));
     82 static int	fetch_ftp __P((const char *, const char *));
     83 static int	fetch_url __P((const char *, const char *, const char *));
     84 static int	parse_url __P((const char *, const char *, url_t *, char **,
     85 				char **, char **, in_port_t *, char **));
     86 void    	aborthttp __P((int));
     87 
     88 static int	redirect_loop;
     89 
     90 
     91 #define	ABOUT_URL	"about:"	/* propaganda */
     92 #define	FILE_URL	"file://"	/* file URL prefix */
     93 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
     94 #define	HTTP_URL	"http://"	/* http URL prefix */
     95 
     96 
     97 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
     98 #define FREEPTR(x)	if ((x) != NULL) { free(x); (x) = NULL; }
     99 
    100 /*
    101  * Parse URL of form:
    102  *	<type>://[<user>[:<password>@]]<host>[:<port>]/<url-path>
    103  * Returns -1 if a parse error occurred, otherwise 0.
    104  * Sets type to url_t, each of the given char ** pointers to a
    105  * malloc(3)ed strings of the relevant section, and port to
    106  * the number given, or ftpport if ftp://, or httpport if http://.
    107  */
    108 static int
    109 parse_url(url, desc, type, user, pass, host, port, path)
    110 	const char	 *url;
    111 	const char	 *desc;
    112 	url_t		 *type;
    113 	char		**user;
    114 	char		**pass;
    115 	char		**host;
    116 	in_port_t	 *port;
    117 	char		**path;
    118 {
    119 	char *cp, *ep, *thost;
    120 
    121 	if (url == NULL || desc == NULL || type == NULL || user == NULL
    122 	    || pass == NULL || host == NULL || port == NULL || path == NULL)
    123 		errx(1, "parse_url: invoked with NULL argument!");
    124 
    125 	*type = UNKNOWN_URL_T;
    126 	*user = *pass = *host = *path = NULL;
    127 	*port = 0;
    128 
    129 	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
    130 		url += sizeof(HTTP_URL) - 1;
    131 		*type = HTTP_URL_T;
    132 		*port = httpport;
    133 	} else if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
    134 		url += sizeof(FTP_URL) - 1;
    135 		*type = FTP_URL_T;
    136 		*port = ftpport;
    137 	} else if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
    138 		url += sizeof(FILE_URL) - 1;
    139 		*type = FILE_URL_T;
    140 	} else {
    141 		warnx("Invalid %s `%s'", desc, url);
    142 cleanup_parse_url:
    143 		FREEPTR(*user);
    144 		FREEPTR(*pass);
    145 		FREEPTR(*host);
    146 		FREEPTR(*path);
    147 		return (-1);
    148 	}
    149 
    150 	if (*url == '\0')
    151 		return (0);
    152 
    153 			/* find [user[:pass]@]host[:port] */
    154 	ep = strchr(url, '/');
    155 	if (ep == NULL)
    156 		thost = xstrdup(url);
    157 	else {
    158 		size_t len = ep - url;
    159 		thost = (char *)xmalloc(len + 1);
    160 		strncpy(thost, url, len);
    161 		thost[len] = '\0';
    162 		*path = xstrdup(ep);
    163 	}
    164 
    165 	cp = strchr(thost, '@');
    166 	if (cp != NULL) {
    167 		*user = thost;
    168 		*cp = '\0';
    169 		*host = xstrdup(cp + 1);
    170 		cp = strchr(*user, ':');
    171 		if (cp != NULL) {
    172 			*cp = '\0';
    173 			*pass = xstrdup(cp + 1);
    174 		}
    175 	} else
    176 		*host = thost;
    177 
    178 			/* look for [:port] */
    179 	cp = strrchr(*host, ':');
    180 	if (cp != NULL) {
    181 		long nport;
    182 
    183 		*cp = '\0';
    184 		nport = strtol(cp + 1, &ep, 10);
    185 		if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0') {
    186 			warnx("Invalid port `%s' in %s `%s'", cp, desc, url);
    187 			goto cleanup_parse_url;
    188 		}
    189 		*port = htons((in_port_t)nport);
    190 	}
    191 
    192 	if (debug)
    193 		fprintf(ttyout,
    194 		    "parse_url: user `%s', pass `%s', host %s:%d, path `%s'\n",
    195 		    *user ? *user : "", *pass ? *pass : "", *host ? *host : "",
    196 		    ntohs(*port), *path ? *path : "");
    197 
    198 	return (0);
    199 }
    200 
    201 
    202 jmp_buf	httpabort;
    203 
    204 /*
    205  * Retrieve URL, via a proxy if necessary. If proxyenv is set, use that for
    206  * the proxy, otherwise try ftp_proxy or http_proxy as appropriate.
    207  * Supports http redirects.
    208  * Returns -1 on failure, 0 on completed xfer, 1 if ftp connection
    209  * is still open (e.g, ftp xfer with trailing /)
    210  */
    211 static int
    212 fetch_url(url, outfile, proxyenv)
    213 	const char *url;
    214 	const char *outfile;
    215 	const char *proxyenv;
    216 {
    217 	struct sockaddr_in	sin;
    218 	struct hostent		*hp;
    219 	volatile sig_t		oldintr, oldintp;
    220 	volatile int		s;
    221 	int 			isproxy, rval, hcode;
    222 	size_t			len;
    223 	char			*cp, *ep, *buf, *location, *message, *savefile;
    224 	char			*user, *pass, *host, *path;
    225 	off_t			hashbytes;
    226 	int			 (*closefunc) __P((FILE *));
    227 	FILE			*fin, *fout;
    228 	time_t			mtime;
    229 	url_t			urltype;
    230 	in_port_t		port;
    231 
    232 	closefunc = NULL;
    233 	fin = fout = NULL;
    234 	s = -1;
    235 	buf = location = message = savefile = NULL;
    236 	isproxy = 0;
    237 	rval = 1;
    238 	hp = NULL;
    239 
    240 #ifdef __GNUC__			/* shut up gcc warnings */
    241 	(void)&closefunc;
    242 	(void)&fin;
    243 	(void)&fout;
    244 	(void)&buf;
    245 	(void)&savefile;
    246 	(void)&rval;
    247 	(void)&isproxy;
    248 #endif
    249 
    250 	if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port, &path)
    251 	    == -1)
    252 		goto cleanup_fetch_url;
    253 
    254 	if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
    255 	    && strcasecmp(host, "localhost") != 0) {
    256 		warnx("No support for non local file URL `%s'", url);
    257 		goto cleanup_fetch_url;
    258 	}
    259 
    260 	if (EMPTYSTRING(path)) {
    261 		if (urltype == FTP_URL_T) {
    262 			rval = fetch_ftp(url, outfile);
    263 			goto cleanup_fetch_url;
    264 		}
    265 		if (urltype != HTTP_URL_T || outfile == NULL)  {
    266 			warnx("Invalid URL (no file after host) `%s'", url);
    267 			goto cleanup_fetch_url;
    268 		}
    269 	}
    270 
    271 	if (outfile)
    272 		savefile = xstrdup(outfile);
    273 	else {
    274 		cp = strrchr(path, '/');		/* find savefile */
    275 		if (cp != NULL)
    276 			savefile = xstrdup(cp + 1);
    277 		else
    278 			savefile = xstrdup(path);
    279 	}
    280 	if (EMPTYSTRING(savefile)) {
    281 		if (urltype == FTP_URL_T) {
    282 			rval = fetch_ftp(url, outfile);
    283 			goto cleanup_fetch_url;
    284 		}
    285 		warnx("Invalid URL (no file after directory) `%s'", url);
    286 		goto cleanup_fetch_url;
    287 	}
    288 
    289 	filesize = -1;
    290 	mtime = -1;
    291 	if (urltype == FILE_URL_T) {		/* file:// URLs */
    292 		struct stat sb;
    293 
    294 		direction = "copied";
    295 		fin = fopen(path, "r");
    296 		if (fin == NULL) {
    297 			warn("Cannot open file `%s'", path);
    298 			goto cleanup_fetch_url;
    299 		}
    300 		if (fstat(fileno(fin), &sb) == 0) {
    301 			mtime = sb.st_mtime;
    302 			filesize = sb.st_size;
    303 		}
    304 		fprintf(ttyout, "Copying %s\n", path);
    305 	} else {				/* ftp:// or http:// URLs */
    306 		if (proxyenv == NULL) {
    307 			if (urltype == HTTP_URL_T)
    308 				proxyenv = httpproxy;
    309 			else if (urltype == FTP_URL_T)
    310 				proxyenv = ftpproxy;
    311 		}
    312 		direction = "retrieved";
    313 		if (proxyenv != NULL) {				/* use proxy */
    314 			url_t purltype;
    315 			char *puser, *ppass, *phost;
    316 			char *ppath;
    317 
    318 			isproxy = 1;
    319 
    320 				/* check URL against list of no_proxied sites */
    321 			if (no_proxy != NULL) {
    322 				char *np, *np_copy;
    323 				long np_port;
    324 				size_t hlen, plen;
    325 
    326 				np_copy = xstrdup(no_proxy);
    327 				hlen = strlen(host);
    328 				while ((cp = strsep(&np_copy, " ,")) != NULL) {
    329 					if (*cp == '\0')
    330 						continue;
    331 					if ((np = strchr(cp, ':')) != NULL) {
    332 						*np = '\0';
    333 						np_port =
    334 						    strtol(np + 1, &ep, 10);
    335 						if (*ep != '\0')
    336 							continue;
    337 						if (port !=
    338 						    htons((in_port_t)np_port))
    339 							continue;
    340 					}
    341 					plen = strlen(cp);
    342 					if (strncasecmp(host + hlen - plen,
    343 					    cp, plen) == 0) {
    344 						isproxy = 0;
    345 						break;
    346 					}
    347 				}
    348 				FREEPTR(np_copy);
    349 			}
    350 
    351 			if (isproxy) {
    352 				if (parse_url(proxyenv, "proxy URL", &purltype,
    353 				    &puser, &ppass, &phost, &port, &ppath)
    354 				    == -1)
    355 					goto cleanup_fetch_url;
    356 
    357 				if ((purltype != HTTP_URL_T
    358 				     && purltype != FTP_URL_T) ||
    359 				    EMPTYSTRING(phost) ||
    360 				    (! EMPTYSTRING(ppath)
    361 				     && strcmp(ppath, "/") != 0)) {
    362 					warnx("Malformed proxy URL `%s'",
    363 					    proxyenv);
    364 					FREEPTR(puser);
    365 					FREEPTR(ppass);
    366 					FREEPTR(phost);
    367 					FREEPTR(ppath);
    368 					goto cleanup_fetch_url;
    369 				}
    370 
    371 				FREEPTR(user);
    372 				user = puser;
    373 				FREEPTR(pass);
    374 				pass = ppass;
    375 				FREEPTR(host);
    376 				host = phost;
    377 				FREEPTR(path);
    378 				FREEPTR(ppath);
    379 				path = xstrdup(url);
    380 			}
    381 		} /* proxyenv != NULL */
    382 
    383 		memset(&sin, 0, sizeof(sin));
    384 		sin.sin_family = AF_INET;
    385 
    386 		if (isdigit((unsigned char)host[0])) {
    387 			if (inet_aton(host, &sin.sin_addr) == 0) {
    388 				warnx("Invalid IP address `%s'", host);
    389 				goto cleanup_fetch_url;
    390 			}
    391 		} else {
    392 			hp = gethostbyname(host);
    393 			if (hp == NULL) {
    394 				warnx("%s: %s", host, hstrerror(h_errno));
    395 				goto cleanup_fetch_url;
    396 			}
    397 			if (hp->h_addrtype != AF_INET) {
    398 				warnx("`%s': not an Internet address?", host);
    399 				goto cleanup_fetch_url;
    400 			}
    401 			memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    402 		}
    403 
    404 		if (port == 0) {
    405 			warnx("Unknown port for URL `%s'", url);
    406 			goto cleanup_fetch_url;
    407 		}
    408 		sin.sin_port = port;
    409 
    410 		s = socket(AF_INET, SOCK_STREAM, 0);
    411 		if (s == -1) {
    412 			warn("Can't create socket");
    413 			goto cleanup_fetch_url;
    414 		}
    415 
    416 		while (xconnect(s, (struct sockaddr *)&sin,
    417 		    sizeof(sin)) == -1) {
    418 			if (errno == EINTR)
    419 				continue;
    420 			if (hp && hp->h_addr_list[1]) {
    421 				int oerrno = errno;
    422 				char *ia;
    423 
    424 				ia = inet_ntoa(sin.sin_addr);
    425 				errno = oerrno;
    426 				warn("Connect to address `%s'", ia);
    427 				hp->h_addr_list++;
    428 				memcpy(&sin.sin_addr, hp->h_addr_list[0],
    429 				    (size_t)hp->h_length);
    430 				fprintf(ttyout, "Trying %s...\n",
    431 				    inet_ntoa(sin.sin_addr));
    432 				(void)close(s);
    433 				s = socket(AF_INET, SOCK_STREAM, 0);
    434 				if (s < 0) {
    435 					warn("Can't create socket");
    436 					goto cleanup_fetch_url;
    437 				}
    438 				continue;
    439 			}
    440 			warn("Can't connect to `%s'", host);
    441 			goto cleanup_fetch_url;
    442 		}
    443 
    444 		fin = fdopen(s, "r+");
    445 		/*
    446 		 * Construct and send the request.
    447 		 * Proxy requests don't want leading /.
    448 		 */
    449 		if (isproxy) {
    450 			fprintf(ttyout, "Requesting %s\n  (via %s)\n",
    451 			    url, proxyenv);
    452 			fprintf(fin, "GET %s HTTP/1.0\r\n\r\n", path);
    453 		} else {
    454 			struct utsname unam;
    455 
    456 			fprintf(ttyout, "Requesting %s\n", url);
    457 			fprintf(fin, "GET %s HTTP/1.1\r\n", path);
    458 			fprintf(fin, "Host: %s\r\n", host);
    459 			fprintf(fin, "Accept: */*\r\n");
    460 			if (uname(&unam) != -1) {
    461 				fprintf(fin, "User-Agent: %s-%s/ftp\r\n",
    462 				    unam.sysname, unam.release);
    463 			}
    464 			fprintf(fin, "Connection: close\r\n");
    465 			fprintf(fin, "\r\n");
    466 		}
    467 		if (fflush(fin) == EOF) {
    468 			warn("Writing HTTP request");
    469 			goto cleanup_fetch_url;
    470 		}
    471 
    472 				/* Read the response */
    473 		if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
    474 			warn("Receiving HTTP reply");
    475 			goto cleanup_fetch_url;
    476 		}
    477 		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
    478 			buf[--len] = '\0';
    479 		if (debug)
    480 			fprintf(ttyout, "received `%s'\n", buf);
    481 
    482 				/* Determine HTTP response code */
    483 		cp = strchr(buf, ' ');
    484 		if (cp == NULL)
    485 			goto improper;
    486 		else
    487 			cp++;
    488 		hcode = strtol(cp, &ep, 10);
    489 		if (*ep != '\0' && !isspace((unsigned char)*ep))
    490 			goto improper;
    491 		message = xstrdup(cp);
    492 
    493 				/* Read the rest of the header. */
    494 		FREEPTR(buf);
    495 		while (1) {
    496 			if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
    497 			    == NULL) {
    498 				warn("Receiving HTTP reply");
    499 				goto cleanup_fetch_url;
    500 			}
    501 			while (len > 0 &&
    502 			    (buf[len-1] == '\r' || buf[len-1] == '\n'))
    503 				buf[--len] = '\0';
    504 			if (len == 0)
    505 				break;
    506 			if (debug)
    507 				fprintf(ttyout, "received `%s'\n", buf);
    508 
    509 				/* Look for some headers */
    510 			cp = buf;
    511 
    512 #define CONTENTLEN "Content-Length: "
    513 			if (strncasecmp(cp, CONTENTLEN,
    514 			    sizeof(CONTENTLEN) - 1) == 0) {
    515 				cp += sizeof(CONTENTLEN) - 1;
    516 				filesize = strtol(cp, &ep, 10);
    517 				if (filesize < 1 || *ep != '\0')
    518 					goto improper;
    519 				if (debug)
    520 					fprintf(ttyout,
    521 #ifndef NO_QUAD
    522 					    "parsed length as: %qd\n",
    523 					    (long long)filesize);
    524 #else
    525 					    "parsed length as: %ld\n",
    526 					    (long)filesize);
    527 #endif
    528 
    529 #define LASTMOD "Last-Modified: "
    530 			} else if (strncasecmp(cp, LASTMOD,
    531 			    sizeof(LASTMOD) - 1) == 0) {
    532 				struct tm parsed;
    533 				char *t;
    534 
    535 				cp += sizeof(LASTMOD) - 1;
    536 							/* RFC 1123 */
    537 				if ((t = strptime(cp,
    538 						"%a, %d %b %Y %H:%M:%S GMT",
    539 						&parsed))
    540 							/* RFC 850 */
    541 				    || (t = strptime(cp,
    542 						"%a, %d-%b-%y %H:%M:%S GMT",
    543 						&parsed))
    544 							/* asctime */
    545 				    || (t = strptime(cp,
    546 						"%a, %b %d %H:%M:%S %Y",
    547 						&parsed))) {
    548 					parsed.tm_isdst = -1;
    549 					if (*t == '\0')
    550 						mtime = mkgmtime(&parsed);
    551 					if (debug && mtime != -1) {
    552 						fprintf(ttyout,
    553 						    "parsed date as: %s",
    554 						    ctime(&mtime));
    555 					}
    556 				}
    557 
    558 #define LOCATION "Location: "
    559 			} else if (strncasecmp(cp, LOCATION,
    560 			    sizeof(LOCATION) - 1) == 0) {
    561 				cp += sizeof(LOCATION) - 1;
    562 				location = xstrdup(cp);
    563 				if (debug)
    564 					fprintf(ttyout,
    565 					    "parsed location as: %s\n", cp);
    566 			}
    567 		}
    568 		FREEPTR(buf);
    569 	}
    570 
    571 	switch (hcode) {
    572 	case 200:
    573 		break;
    574 	case 300:
    575 	case 301:
    576 	case 302:
    577 	case 303:
    578 	case 305:
    579 		if (EMPTYSTRING(location)) {
    580 			warnx("No redirection Location provided by server");
    581 			goto cleanup_fetch_url;
    582 		}
    583 		if (redirect_loop++ > 5) {
    584 			warnx("Too many redirections requested");
    585 			goto cleanup_fetch_url;
    586 		}
    587 		if (hcode == 305) {
    588 			if (verbose)
    589 				fprintf(ttyout, "Redirected via %s\n",
    590 				    location);
    591 			rval = fetch_url(url, outfile, location);
    592 		} else {
    593 			if (verbose)
    594 				fprintf(ttyout, "Redirected to %s\n", location);
    595 			rval = go_fetch(location, outfile);
    596 		}
    597 		goto cleanup_fetch_url;
    598 	default:
    599 		warnx("Error retrieving file - `%s'", message);
    600 		goto cleanup_fetch_url;
    601 	}
    602 
    603 	oldintr = oldintp = NULL;
    604 
    605 			/* Open the output file. */
    606 	if (strcmp(savefile, "-") == 0) {
    607 		fout = stdout;
    608 	} else if (*savefile == '|') {
    609 		oldintp = signal(SIGPIPE, SIG_IGN);
    610 		fout = popen(savefile + 1, "w");
    611 		if (fout == NULL) {
    612 			warn("Can't run `%s'", savefile + 1);
    613 			goto cleanup_fetch_url;
    614 		}
    615 		closefunc = pclose;
    616 	} else {
    617 		fout = fopen(savefile, "w");
    618 		if (fout == NULL) {
    619 			warn("Can't open `%s'", savefile);
    620 			goto cleanup_fetch_url;
    621 		}
    622 		closefunc = fclose;
    623 	}
    624 
    625 			/* Trap signals */
    626 	if (setjmp(httpabort)) {
    627 		if (oldintr)
    628 			(void)signal(SIGINT, oldintr);
    629 		if (oldintp)
    630 			(void)signal(SIGPIPE, oldintp);
    631 		goto cleanup_fetch_url;
    632 	}
    633 	oldintr = signal(SIGINT, aborthttp);
    634 
    635 	bytes = 0;
    636 	hashbytes = mark;
    637 	progressmeter(-1);
    638 
    639 			/* Finally, suck down the file. */
    640 	buf = xmalloc(BUFSIZ);
    641 	while ((len = fread(buf, sizeof(char), BUFSIZ, fin)) > 0) {
    642 		bytes += len;
    643 		if (fwrite(buf, sizeof(char), len, fout) != len) {
    644 			warn("Writing `%s'", savefile);
    645 			goto cleanup_fetch_url;
    646 		}
    647 		if (hash && !progress) {
    648 			while (bytes >= hashbytes) {
    649 				(void)putc('#', ttyout);
    650 				hashbytes += mark;
    651 			}
    652 			(void)fflush(ttyout);
    653 		}
    654 	}
    655 	if (hash && !progress && bytes > 0) {
    656 		if (bytes < mark)
    657 			(void)putc('#', ttyout);
    658 		(void)putc('\n', ttyout);
    659 		(void)fflush(ttyout);
    660 	}
    661 	if (ferror(fin)) {
    662 		warn("Reading file");
    663 		goto cleanup_fetch_url;
    664 	}
    665 	progressmeter(1);
    666 	(void)fflush(fout);
    667 	(void)signal(SIGINT, oldintr);
    668 	if (oldintp)
    669 		(void)signal(SIGPIPE, oldintp);
    670 	if (closefunc == fclose && mtime != -1) {
    671 		struct timeval tval[2];
    672 
    673 		(void)gettimeofday(&tval[0], NULL);
    674 		tval[1].tv_sec = mtime;
    675 		tval[1].tv_usec = 0;
    676 		(*closefunc)(fout);
    677 		fout = NULL;
    678 
    679 		if (utimes(savefile, tval) == -1) {
    680 			fprintf(ttyout,
    681 			    "Can't change modification time to %s",
    682 			    asctime(localtime(&mtime)));
    683 		}
    684 	}
    685 	if (bytes > 0)
    686 		ptransfer(0);
    687 
    688 	rval = 0;
    689 	goto cleanup_fetch_url;
    690 
    691 improper:
    692 	warnx("Improper response from `%s'", host);
    693 
    694 cleanup_fetch_url:
    695 	resetsockbufsize();
    696 	if (fin != NULL)
    697 		fclose(fin);
    698 	else if (s != -1)
    699 		close(s);
    700 	if (closefunc != NULL && fout != NULL)
    701 		(*closefunc)(fout);
    702 	FREEPTR(savefile);
    703 	FREEPTR(user);
    704 	FREEPTR(pass);
    705 	FREEPTR(host);
    706 	FREEPTR(path);
    707 	FREEPTR(buf);
    708 	FREEPTR(location);
    709 	FREEPTR(message);
    710 	return (rval);
    711 }
    712 
    713 /*
    714  * Abort a http retrieval
    715  */
    716 void
    717 aborthttp(notused)
    718 	int notused;
    719 {
    720 
    721 	alarmtimer(0);
    722 	fputs("\nHTTP fetch aborted.\n", ttyout);
    723 	(void)fflush(ttyout);
    724 	longjmp(httpabort, 1);
    725 }
    726 
    727 /*
    728  * Retrieve ftp URL or classic ftp argument.
    729  * Returns -1 on failure, 0 on completed xfer, 1 if ftp connection
    730  * is still open (e.g, ftp xfer with trailing /)
    731  */
    732 static int
    733 fetch_ftp(url, outfile)
    734 	const char *url;
    735 	const char *outfile;
    736 {
    737 	static char	lasthost[MAXHOSTNAMELEN];
    738 	char		*cp, *xargv[5], rempath[MAXPATHLEN];
    739 	char		portnum[6];		/* large enough for "65535\0" */
    740 	char		*host, *path, *dir, *file, *user, *pass;
    741 	in_port_t	port;
    742 	int		dirhasglob, filehasglob, rval, xargc;
    743 
    744 	host = path = dir = file = user = pass = NULL;
    745 	port = 0;
    746 	rval = 1;
    747 
    748 	if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
    749 		url_t urltype;
    750 
    751 		if ((parse_url(url, "URL", &urltype, &user, &pass,
    752 		    &host, &port, &path) == -1) ||
    753 		    (user != NULL && *user == '\0') ||
    754 		    (pass != NULL && *pass == '\0') ||
    755 		    EMPTYSTRING(host)) {
    756 			warnx("Invalid URL `%s'", url);
    757 			goto cleanup_fetch_ftp;
    758 		}
    759 	} else {			/* classic style `host:file' */
    760 		host = xstrdup(url);
    761 		cp = strchr(host, ':');
    762 		if (cp != NULL) {
    763 			*cp = '\0';
    764 			path = xstrdup(cp + 1);
    765 		}
    766 	}
    767 	if (EMPTYSTRING(host))
    768 		goto cleanup_fetch_ftp;
    769 
    770 	/*
    771 	 * Extract the file and (if present) directory name.
    772 	 */
    773 	dir = path;
    774 	if (! EMPTYSTRING(dir)) {
    775 		if (*dir == '/')
    776 			dir++;		/* skip leading / */
    777 		cp = strrchr(dir, '/');
    778 		if (cp != NULL) {
    779 			*cp++ = '\0';
    780 			file = cp;
    781 		} else {
    782 			file = dir;
    783 			dir = NULL;
    784 		}
    785 	}
    786 	if (debug)
    787 		fprintf(ttyout,
    788 "fetch_ftp: user `%s', pass `%s', host %s:%d, path, `%s', dir `%s', file `%s'\n",
    789 		    user ? user : "", pass ? pass : "",
    790 		    host ? host : "", ntohs(port), path ? path : "",
    791 		    dir ? dir : "", file ? file : "");
    792 
    793 	dirhasglob = filehasglob = 0;
    794 	if (doglob) {
    795 		if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
    796 			dirhasglob = 1;
    797 		if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
    798 			filehasglob = 1;
    799 	}
    800 
    801 	/*
    802 	 * Set up the connection if we don't have one.
    803 	 */
    804 	if (strcasecmp(host, lasthost) != 0) {
    805 		int oautologin;
    806 
    807 		(void)strcpy(lasthost, host);
    808 		if (connected)
    809 			disconnect(0, NULL);
    810 		xargv[0] = __progname;
    811 		xargv[1] = host;
    812 		xargv[2] = NULL;
    813 		xargc = 2;
    814 		if (port) {
    815 			snprintf(portnum, sizeof(portnum), "%d", ntohs(port));
    816 			xargv[2] = portnum;
    817 			xargv[3] = NULL;
    818 			xargc = 3;
    819 		}
    820 		oautologin = autologin;
    821 		if (user != NULL)
    822 			autologin = 0;
    823 		setpeer(xargc, xargv);
    824 		autologin = oautologin;
    825 		if ((connected == 0)
    826 		 || ((connected == 1) && !ftp_login(host, user, pass)) ) {
    827 			warnx("Can't connect or login to host `%s'",
    828 			    host);
    829 			goto cleanup_fetch_ftp;
    830 		}
    831 
    832 			/* Always use binary transfers. */
    833 		setbinary(0, NULL);
    834 	} else {
    835 			/* connection exists, cd back to `/' */
    836 		xargv[0] = "cd";
    837 		xargv[1] = "/";
    838 		xargv[2] = NULL;
    839 		dirchange = 0;
    840 		cd(2, xargv);
    841 		if (! dirchange)
    842 			goto cleanup_fetch_ftp;
    843 	}
    844 
    845 			/* Change directories, if necessary. */
    846 	if (! EMPTYSTRING(dir) && !dirhasglob) {
    847 		xargv[0] = "cd";
    848 		xargv[1] = dir;
    849 		xargv[2] = NULL;
    850 		dirchange = 0;
    851 		cd(2, xargv);
    852 		if (! dirchange)
    853 			goto cleanup_fetch_ftp;
    854 	}
    855 
    856 	if (EMPTYSTRING(file)) {
    857 		rval = -1;
    858 		goto cleanup_fetch_ftp;
    859 	}
    860 
    861 	if (!verbose)
    862 		fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
    863 
    864 	if (dirhasglob) {
    865 		snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
    866 		file = rempath;
    867 	}
    868 
    869 			/* Fetch the file(s). */
    870 	xargc = 2;
    871 	xargv[0] = "get";
    872 	xargv[1] = file;
    873 	xargv[2] = NULL;
    874 	if (dirhasglob || filehasglob) {
    875 		int ointeractive;
    876 
    877 		ointeractive = interactive;
    878 		interactive = 0;
    879 		xargv[0] = "mget";
    880 		mget(xargc, xargv);
    881 		interactive = ointeractive;
    882 	} else {
    883 		if (outfile != NULL) {
    884 			xargv[2] = (char *)outfile;
    885 			xargv[3] = NULL;
    886 			xargc++;
    887 		}
    888 		get(xargc, xargv);
    889 		if (outfile != NULL && strcmp(outfile, "-") != 0
    890 		    && outfile[0] != '|')
    891 			outfile = NULL;
    892 	}
    893 
    894 	if ((code / 100) == COMPLETE)
    895 		rval = 0;
    896 
    897 cleanup_fetch_ftp:
    898 	FREEPTR(host);
    899 	FREEPTR(path);
    900 	FREEPTR(user);
    901 	FREEPTR(pass);
    902 	return (rval);
    903 }
    904 
    905 /*
    906  * Retrieve the given file to outfile.
    907  * Supports arguments of the form:
    908  *	"host:path", "ftp://host/path"	if $ftpproxy, call fetch_url() else
    909  *					call fetch_ftp()
    910  *	"http://host/path"		call fetch_url() to use http
    911  *	"file:///path"			call fetch_url() to copy
    912  *	"about:..."			print a message
    913  *
    914  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
    915  * is still open (e.g, ftp xfer with trailing /)
    916  */
    917 static int
    918 go_fetch(url, outfile)
    919 	const char *url;
    920 	const char *outfile;
    921 {
    922 
    923 #ifndef SMALL
    924 	/*
    925 	 * Check for about:*
    926 	 */
    927 	if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
    928 		url += sizeof(ABOUT_URL) -1;
    929 		if (strcasecmp(url, "ftp") == 0) {
    930 			fprintf(ttyout, "%s\n%s\n",
    931 "This version of ftp has been enhanced by Luke Mewburn <lukem (at) netbsd.org>.",
    932 "Execute 'man ftp' for more details");
    933 		} else if (strcasecmp(url, "netbsd") == 0) {
    934 			fprintf(ttyout, "%s\n%s\n",
    935 "NetBSD is a freely available and redistributable UNIX-like operating system.",
    936 "For more information, see http://www.netbsd.org/index.html");
    937 		} else {
    938 			fprintf(ttyout, "`%s' is an interesting topic.\n", url);
    939 		}
    940 		return (0);
    941 	}
    942 #endif /* SMALL */
    943 
    944 	/*
    945 	 * Check for file:// and http:// URLs.
    946 	 */
    947 	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
    948 	    strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0)
    949 		return (fetch_url(url, outfile, NULL));
    950 
    951 	/*
    952 	 * Try FTP URL-style and host:file arguments next.
    953 	 * If ftpproxy is set with an FTP URL, use fetch_url()
    954 	 * Othewise, use fetch_ftp().
    955 	 */
    956 	if (ftpproxy && strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0)
    957 		return (fetch_url(url, outfile, NULL));
    958 
    959 	return (fetch_ftp(url, outfile));
    960 }
    961 
    962 /*
    963  * Retrieve multiple files from the command line,
    964  * calling go_fetch() for each file.
    965  *
    966  * If an ftp path has a trailing "/", the path will be cd-ed into and
    967  * the connection remains open, and the function will return -1
    968  * (to indicate the connection is alive).
    969  * If an error occurs the return value will be the offset+1 in
    970  * argv[] of the file that caused a problem (i.e, argv[x]
    971  * returns x+1)
    972  * Otherwise, 0 is returned if all files retrieved successfully.
    973  */
    974 int
    975 auto_fetch(argc, argv, outfile)
    976 	int argc;
    977 	char *argv[];
    978 	char *outfile;
    979 {
    980 	volatile int	argpos;
    981 	int		rval;
    982 
    983 	argpos = 0;
    984 
    985 	if (setjmp(toplevel)) {
    986 		if (connected)
    987 			disconnect(0, NULL);
    988 		return (argpos + 1);
    989 	}
    990 	(void)signal(SIGINT, (sig_t)intr);
    991 	(void)signal(SIGPIPE, (sig_t)lostpeer);
    992 
    993 	/*
    994 	 * Loop through as long as there's files to fetch.
    995 	 */
    996 	for (rval = 0; (rval == 0) && (argpos < argc); argpos++) {
    997 		if (strchr(argv[argpos], ':') == NULL)
    998 			break;
    999 		redirect_loop = 0;
   1000 		rval = go_fetch(argv[argpos], outfile);
   1001 		if (rval > 0)
   1002 			rval = argpos + 1;
   1003 	}
   1004 
   1005 	if (connected && rval != -1)
   1006 		disconnect(0, NULL);
   1007 	return (rval);
   1008 }
   1009