Home | History | Annotate | Line # | Download | only in ftp
fetch.c revision 1.25
      1 /*	$NetBSD: fetch.c,v 1.25 1998/07/26 12:58:17 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.25 1998/07/26 12:58:17 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 
     52 #include <netinet/in.h>
     53 
     54 #include <arpa/ftp.h>
     55 #include <arpa/inet.h>
     56 
     57 #include <ctype.h>
     58 #include <err.h>
     59 #include <errno.h>
     60 #include <netdb.h>
     61 #include <fcntl.h>
     62 #include <signal.h>
     63 #include <stdio.h>
     64 #include <stdlib.h>
     65 #include <string.h>
     66 #include <unistd.h>
     67 #include <util.h>
     68 
     69 #include "ftp_var.h"
     70 
     71 static int	url_get __P((const char *, const char *, const char *));
     72 void    	aborthttp __P((int));
     73 
     74 
     75 #define	ABOUT_URL	"about:"	/* propaganda */
     76 #define	FILE_URL	"file://"	/* file URL prefix */
     77 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
     78 #define	HTTP_URL	"http://"	/* http URL prefix */
     79 #define FTP_PROXY	"ftp_proxy"	/* env var with ftp proxy location */
     80 #define HTTP_PROXY	"http_proxy"	/* env var with http proxy location */
     81 
     82 
     83 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
     84 
     85 jmp_buf	httpabort;
     86 
     87 /*
     88  * Retrieve URL, via the proxy in $proxyvar if necessary.
     89  * Modifies the string argument given.
     90  * Returns -1 on failure, 0 on success
     91  */
     92 static int
     93 url_get(origline, proxyenv, outfile)
     94 	const char *origline;
     95 	const char *proxyenv;
     96 	const char *outfile;
     97 {
     98 	struct sockaddr_in sin;
     99 	int isredirected;
    100 	in_port_t port;
    101 	volatile int s;
    102 	size_t len;
    103 	char *cp, *ep, *portnum, *path;
    104 	const char *savefile;
    105 	char *line, *proxy, *host, *buf;
    106 	volatile sig_t oldintr, oldintp;
    107 	off_t hashbytes;
    108 	struct hostent *hp = NULL;
    109 	int (*closefunc) __P((FILE *));
    110 	FILE *fin, *fout;
    111 	int retval;
    112 	enum { HTTP_URL_T=0, FTP_URL_T, FILE_URL_T } urltype;
    113 	time_t mtime;
    114 
    115 	closefunc = NULL;
    116 	fin = fout = NULL;
    117 	s = -1;
    118 	proxy = buf = NULL;
    119 	isredirected = 0;
    120 	retval = -1;
    121 
    122 #ifdef __GNUC__			/* to shut up gcc warnings */
    123 	(void)&closefunc;
    124 	(void)&fin;
    125 	(void)&fout;
    126 	(void)&buf;
    127 	(void)&proxy;
    128 	(void)&savefile;
    129 	(void)&retval;
    130 #endif
    131 
    132 	line = strdup(origline);
    133 	if (line == NULL)
    134 		errx(1, "Can't allocate memory to parse URL");
    135 	if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
    136 		host = line + sizeof(HTTP_URL) - 1;
    137 		urltype = HTTP_URL_T;
    138 	} else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
    139 		host = line + sizeof(FTP_URL) - 1;
    140 		urltype = FTP_URL_T;
    141 	} else if (strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
    142 		host = line + sizeof(FILE_URL) - 1;
    143 		urltype = FILE_URL_T;
    144 	} else
    145 		errx(1, "url_get: Invalid URL '%s'", line);
    146 
    147 	path = strchr(host, '/');		/* find path */
    148 	if (EMPTYSTRING(path)) {
    149 		switch (urltype) {
    150 		case FILE_URL_T:
    151 			break;
    152 		case FTP_URL_T:
    153 			goto noftpautologin;
    154 		case HTTP_URL_T:
    155 			warnx("Invalid URL (no `/' after host): %s", origline);
    156 			goto cleanup_url_get;
    157 		default:
    158 			errx(1, "Unknown URL type %d\n", urltype);
    159 		}
    160 	}
    161 	*path++ = '\0';
    162 	if (urltype == FILE_URL_T &&
    163 	    *host != '\0' && strcmp(host, "localhost") != 0) {
    164 		warnx("No support for non local file URL: %s", origline);
    165 		goto cleanup_url_get;
    166 	}
    167 	if (EMPTYSTRING(path)) {
    168 		if (urltype == FTP_URL_T)
    169 			goto noftpautologin;
    170 		warnx("Invalid URL (no file after host): %s", origline);
    171 		goto cleanup_url_get;
    172 	}
    173 
    174 	if (outfile)
    175 		savefile = outfile;
    176 	else {
    177 		savefile = strrchr(path, '/');		/* find savefile */
    178 		if (savefile != NULL)
    179 			savefile++;
    180 		else
    181 			savefile = path;
    182 	}
    183 	if (EMPTYSTRING(savefile)) {
    184 		if (urltype == FTP_URL_T)
    185 			goto noftpautologin;
    186 		warnx("Invalid URL (no file after directory): %s", origline);
    187 		goto cleanup_url_get;
    188 	}
    189 
    190 	filesize = -1;
    191 	mtime = -1;
    192 	if (urltype == FILE_URL_T) {		/* file:// URLs */
    193 		struct stat sb;
    194 
    195 		direction = "copied";
    196 		*--path = '/';			/* put / back in */
    197 		fin = fopen(path, "r");
    198 		if (fin == NULL) {
    199 			warn("Cannot open file %s", path);
    200 			goto cleanup_url_get;
    201 		}
    202 		if (fstat(fileno(fin), &sb) == 0) {
    203 			mtime = sb.st_mtime;
    204 			filesize = sb.st_size;
    205 		}
    206 		fprintf(ttyout, "Copying %s\n", path);
    207 	} else {				/* ftp:// or http:// URLs */
    208 		direction = "retrieved";
    209 		if (proxyenv != NULL) {				/* use proxy */
    210 			proxy = strdup(proxyenv);
    211 			if (proxy == NULL)
    212 				errx(1, "Can't allocate memory for proxy URL.");
    213 			if (strncasecmp(proxy, HTTP_URL,
    214 			    sizeof(HTTP_URL) - 1) == 0)
    215 				host = proxy + sizeof(HTTP_URL) - 1;
    216 			else if (strncasecmp(proxy, FTP_URL,
    217 			    sizeof(FTP_URL) - 1) == 0)
    218 				host = proxy + sizeof(FTP_URL) - 1;
    219 			else {
    220 				warnx("Malformed proxy URL: %s", proxyenv);
    221 				goto cleanup_url_get;
    222 			}
    223 			if (EMPTYSTRING(host)) {
    224 				warnx("Malformed proxy URL: %s", proxyenv);
    225 				goto cleanup_url_get;
    226 			}
    227 			*--path = '/';		/* add / back to real path */
    228 			path = strchr(host, '/');
    229 						/* remove trailing / on host */
    230 			if (! EMPTYSTRING(path))
    231 				*path++ = '\0';
    232 			path = line;
    233 		}
    234 
    235 		portnum = strchr(host, ':');		/* find portnum */
    236 		if (portnum != NULL)
    237 			*portnum++ = '\0';
    238 
    239 		if (debug)
    240 			fprintf(ttyout,
    241 			    "host %s, port %s, path %s, save as %s.\n",
    242 			    host, portnum, path, savefile);
    243 
    244 		memset(&sin, 0, sizeof(sin));
    245 		sin.sin_family = AF_INET;
    246 
    247 		if (isdigit((unsigned char)host[0])) {
    248 			if (inet_aton(host, &sin.sin_addr) == 0) {
    249 				warnx("Invalid IP address: %s", host);
    250 				goto cleanup_url_get;
    251 			}
    252 		} else {
    253 			hp = gethostbyname(host);
    254 			if (hp == NULL) {
    255 				warnx("%s: %s", host, hstrerror(h_errno));
    256 				goto cleanup_url_get;
    257 			}
    258 			if (hp->h_addrtype != AF_INET) {
    259 				warnx("%s: not an Internet address?", host);
    260 				goto cleanup_url_get;
    261 			}
    262 			memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    263 		}
    264 
    265 		if (! EMPTYSTRING(portnum)) {
    266 			char *ep;
    267 			long nport;
    268 
    269 			nport = strtol(portnum, &ep, 10);
    270 			if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0') {
    271 				warnx("Invalid port: %s", portnum);
    272 				goto cleanup_url_get;
    273 			}
    274 			port = htons((in_port_t)nport);
    275 		} else
    276 			port = httpport;
    277 		sin.sin_port = port;
    278 
    279 		s = socket(AF_INET, SOCK_STREAM, 0);
    280 		if (s == -1) {
    281 			warn("Can't create socket");
    282 			goto cleanup_url_get;
    283 		}
    284 
    285 		while (xconnect(s, (struct sockaddr *)&sin,
    286 		    sizeof(sin)) == -1) {
    287 			if (errno == EINTR)
    288 				continue;
    289 			if (hp && hp->h_addr_list[1]) {
    290 				int oerrno = errno;
    291 				char *ia;
    292 
    293 				ia = inet_ntoa(sin.sin_addr);
    294 				errno = oerrno;
    295 				warn("connect to address %s", ia);
    296 				hp->h_addr_list++;
    297 				memcpy(&sin.sin_addr, hp->h_addr_list[0],
    298 				    (size_t)hp->h_length);
    299 				fprintf(ttyout, "Trying %s...\n",
    300 				    inet_ntoa(sin.sin_addr));
    301 				(void)close(s);
    302 				s = socket(AF_INET, SOCK_STREAM, 0);
    303 				if (s < 0) {
    304 					warn("Can't create socket");
    305 					goto cleanup_url_get;
    306 				}
    307 				continue;
    308 			}
    309 			warn("Can't connect to %s", host);
    310 			goto cleanup_url_get;
    311 		}
    312 
    313 		fin = fdopen(s, "r+");
    314 		/*
    315 		 * Construct and send the request. Proxy requests don't want
    316 		 * leading /.
    317 		 */
    318 		if (!proxy) {
    319 			fprintf(ttyout, "Requesting %s\n", origline);
    320 			fprintf(fin, "GET /%s HTTP/1.1\r\n", path);
    321 			fprintf(fin, "Host: %s\r\n", host);
    322 			fprintf(fin, "Connection: close\r\n\r\n");
    323 		} else {
    324 			fprintf(ttyout, "Requesting %s\n  (via %s)\n",
    325 			    origline, proxyenv);
    326 			fprintf(fin, "GET %s HTTP/1.0\r\n\r\n", path);
    327 		}
    328 		if (fflush(fin) == EOF) {
    329 			warn("Writing HTTP request");
    330 			goto cleanup_url_get;
    331 		}
    332 
    333 				/* Read the response */
    334 		if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
    335 			warn("Receiving HTTP reply");
    336 			goto cleanup_url_get;
    337 		}
    338 		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
    339 			buf[--len] = '\0';
    340 		if (debug)
    341 			fprintf(ttyout, "received '%s'\n", buf);
    342 
    343 		cp = strchr(buf, ' ');
    344 		if (cp == NULL)
    345 			goto improper;
    346 		else
    347 			cp++;
    348 		if (strncmp(cp, "301", 3) == 0 || strncmp(cp, "302", 3) == 0) {
    349 			isredirected++;
    350 		} else if (strncmp(cp, "200", 3)) {
    351 			warnx("Error retrieving file: %s", cp);
    352 			goto cleanup_url_get;
    353 		}
    354 
    355 				/* Read the rest of the header. */
    356 		free(buf);
    357 		while (1) {
    358 			if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
    359 			    == NULL) {
    360 				warn("Receiving HTTP reply");
    361 				goto cleanup_url_get;
    362 			}
    363 			while (len > 0 &&
    364 			    (buf[len-1] == '\r' || buf[len-1] == '\n'))
    365 				buf[--len] = '\0';
    366 			if (len == 0)
    367 				break;
    368 			if (debug)
    369 				fprintf(ttyout, "received '%s'\n", buf);
    370 
    371 				/* Look for some headers */
    372 			cp = buf;
    373 #define CONTENTLEN "Content-Length: "
    374 			if (strncasecmp(cp, CONTENTLEN,
    375 			    sizeof(CONTENTLEN) - 1) == 0) {
    376 				cp += sizeof(CONTENTLEN) - 1;
    377 				filesize = strtol(cp, &ep, 10);
    378 				if (filesize < 1 || *ep != '\0')
    379 					goto improper;
    380 				if (debug)
    381 					fprintf(ttyout,
    382 #ifndef NO_QUAD
    383 					    "parsed length as: %qd\n",
    384 					    (long long)filesize);
    385 #else
    386 					    "parsed length as: %ld\n",
    387 					    (long)filesize);
    388 #endif
    389 #define LASTMOD "Last-Modified: "
    390 			} else if (strncasecmp(cp, LASTMOD,
    391 			    sizeof(LASTMOD) - 1) == 0) {
    392 				struct tm parsed;
    393 				char *t;
    394 
    395 				cp += sizeof(LASTMOD) - 1;
    396 							/* RFC 1123 */
    397 				if ((t = strptime(cp,
    398 						"%a, %d %b %Y %H:%M:%S GMT",
    399 						&parsed))
    400 							/* RFC 850 */
    401 				    || (t = strptime(cp,
    402 						"%a, %d-%b-%y %H:%M:%S GMT",
    403 						&parsed))
    404 							/* asctime */
    405 				    || (t = strptime(cp,
    406 						"%a, %b %d %H:%M:%S %Y",
    407 						&parsed))) {
    408 					if (*t == '\0')
    409 						mtime = mktime(&parsed);
    410 					if (debug && mtime != -1)
    411 						fprintf(ttyout,
    412 						    "parsed date as: %s",
    413 						    ctime(&mtime));
    414 				}
    415 #define LOCATION "Location: "
    416 			} else if (isredirected &&
    417 			    strncasecmp(cp, LOCATION,
    418 				sizeof(LOCATION) - 1) == 0) {
    419 				cp += sizeof(LOCATION) - 1;
    420 				if (debug)
    421 					fprintf(ttyout,
    422 					    "parsed location as: %s\n", cp);
    423 				if (verbose)
    424 					fprintf(ttyout,
    425 					    "Redirected to %s\n", cp);
    426 				resetsockbufsize();
    427 				if (fin != NULL)
    428 					fclose(fin);
    429 				else if (s != -1)
    430 					close(s);
    431 				if (closefunc != NULL && fout != NULL)
    432 					(*closefunc)(fout);
    433 				if (proxy)
    434 					free(proxy);
    435 				free(line);
    436 				retval = url_get(cp, proxyenv, outfile);
    437 				if (buf)
    438 					free(buf);
    439 				return retval;
    440 			}
    441 		}
    442 		free(buf);
    443 	}
    444 
    445 	oldintr = oldintp = NULL;
    446 
    447 			/* Open the output file. */
    448 	if (strcmp(savefile, "-") == 0) {
    449 		fout = stdout;
    450 	} else if (*savefile == '|') {
    451 		oldintp = signal(SIGPIPE, SIG_IGN);
    452 		fout = popen(savefile + 1, "w");
    453 		if (fout == NULL) {
    454 			warn("Can't run %s", savefile + 1);
    455 			goto cleanup_url_get;
    456 		}
    457 		closefunc = pclose;
    458 	} else {
    459 		fout = fopen(savefile, "w");
    460 		if (fout == NULL) {
    461 			warn("Can't open %s", savefile);
    462 			goto cleanup_url_get;
    463 		}
    464 		closefunc = fclose;
    465 	}
    466 
    467 			/* Trap signals */
    468 	if (setjmp(httpabort)) {
    469 		if (oldintr)
    470 			(void)signal(SIGINT, oldintr);
    471 		if (oldintp)
    472 			(void)signal(SIGPIPE, oldintp);
    473 		goto cleanup_url_get;
    474 	}
    475 	oldintr = signal(SIGINT, aborthttp);
    476 
    477 	bytes = 0;
    478 	hashbytes = mark;
    479 	progressmeter(-1);
    480 
    481 			/* Finally, suck down the file. */
    482 	if ((buf = malloc(BUFSIZ)) == NULL)
    483 		errx(1, "Can't allocate memory for transfer buffer\n");
    484 	while ((len = fread(buf, sizeof(char), BUFSIZ, fin)) > 0) {
    485 		bytes += len;
    486 		if (fwrite(buf, sizeof(char), len, fout) != len) {
    487 			warn("Writing %s", savefile);
    488 			goto cleanup_url_get;
    489 		}
    490 		if (hash && !progress) {
    491 			while (bytes >= hashbytes) {
    492 				(void)putc('#', ttyout);
    493 				hashbytes += mark;
    494 			}
    495 			(void)fflush(ttyout);
    496 		}
    497 	}
    498 	if (hash && !progress && bytes > 0) {
    499 		if (bytes < mark)
    500 			(void)putc('#', ttyout);
    501 		(void)putc('\n', ttyout);
    502 		(void)fflush(ttyout);
    503 	}
    504 	if (ferror(fin)) {
    505 		warn("Reading file");
    506 		goto cleanup_url_get;
    507 	}
    508 	progressmeter(1);
    509 	(void)fflush(fout);
    510 	(void)signal(SIGINT, oldintr);
    511 	if (oldintp)
    512 		(void)signal(SIGPIPE, oldintp);
    513 	if (closefunc == fclose && mtime != -1) {
    514 		struct timeval tval[2];
    515 
    516 		(void)gettimeofday(&tval[0], NULL);
    517 		tval[1].tv_sec = mtime;
    518 		tval[1].tv_usec = 0;
    519 		if (futimes(fileno(fout), tval) == -1) {
    520 			fprintf(ttyout,
    521 			    "Can't change modification time to %s",
    522 			    asctime(localtime(&mtime)));
    523 		}
    524 	}
    525 	if (bytes > 0)
    526 		ptransfer(0);
    527 
    528 	retval = 0;
    529 	goto cleanup_url_get;
    530 
    531 noftpautologin:
    532 	warnx(
    533 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
    534 	goto cleanup_url_get;
    535 
    536 improper:
    537 	warnx("Improper response from %s", host);
    538 
    539 cleanup_url_get:
    540 	resetsockbufsize();
    541 	if (fin != NULL)
    542 		fclose(fin);
    543 	else if (s != -1)
    544 		close(s);
    545 	if (closefunc != NULL && fout != NULL)
    546 		(*closefunc)(fout);
    547 	if (buf)
    548 		free(buf);
    549 	if (proxy)
    550 		free(proxy);
    551 	free(line);
    552 	return (retval);
    553 }
    554 
    555 /*
    556  * Abort a http retrieval
    557  */
    558 void
    559 aborthttp(notused)
    560 	int notused;
    561 {
    562 
    563 	alarmtimer(0);
    564 	fputs("\nHTTP fetch aborted.\n", ttyout);
    565 	(void)fflush(ttyout);
    566 	longjmp(httpabort, 1);
    567 }
    568 
    569 /*
    570  * Retrieve multiple files from the command line, transferring
    571  * URLs of the form "host:path", "ftp://host/path" using the
    572  * ftp protocol, URLs of the form "http://host/path" using the
    573  * http protocol, and URLs of the form "file:///" by simple
    574  * copying.
    575  * If path has a trailing "/", then return (-1);
    576  * the path will be cd-ed into and the connection remains open,
    577  * and the function will return -1 (to indicate the connection
    578  * is alive).
    579  * If an error occurs the return value will be the offset+1 in
    580  * argv[] of the file that caused a problem (i.e, argv[x]
    581  * returns x+1)
    582  * Otherwise, 0 is returned if all files retrieved successfully.
    583  */
    584 int
    585 auto_fetch(argc, argv, outfile)
    586 	int argc;
    587 	char *argv[];
    588 	char *outfile;
    589 {
    590 	static char lasthost[MAXHOSTNAMELEN];
    591 	char *xargv[5];
    592 	char *cp, *line, *host, *dir, *file, *portnum;
    593 	char *user, *pass;
    594 	char *ftpproxy, *httpproxy;
    595 	int rval, xargc;
    596 	volatile int argpos;
    597 	int dirhasglob, filehasglob;
    598 	char rempath[MAXPATHLEN];
    599 
    600 #ifdef __GNUC__			/* to shut up gcc warnings */
    601 	(void)&outfile;
    602 #endif
    603 
    604 	argpos = 0;
    605 
    606 	if (setjmp(toplevel)) {
    607 		if (connected)
    608 			disconnect(0, NULL);
    609 		return (argpos + 1);
    610 	}
    611 	(void)signal(SIGINT, (sig_t)intr);
    612 	(void)signal(SIGPIPE, (sig_t)lostpeer);
    613 
    614 	ftpproxy = getenv(FTP_PROXY);
    615 	httpproxy = getenv(HTTP_PROXY);
    616 
    617 	/*
    618 	 * Loop through as long as there's files to fetch.
    619 	 */
    620 	for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
    621 		if (strchr(argv[argpos], ':') == NULL)
    622 			break;
    623 		host = dir = file = portnum = user = pass = NULL;
    624 
    625 		/*
    626 		 * We muck with the string, so we make a copy.
    627 		 */
    628 		line = strdup(argv[argpos]);
    629 		if (line == NULL)
    630 			errx(1, "Can't allocate memory for auto-fetch.");
    631 
    632 #ifndef SMALL
    633 		/*
    634 		 * Check for about:*
    635 		 */
    636 		if (strncasecmp(line, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
    637 			cp = line + sizeof(ABOUT_URL) -1;
    638 			if (strcasecmp(cp, "ftp") == 0) {
    639 				fprintf(ttyout, "%s\n%s\n",
    640 "The version of ftp has been enhanced by Luke Mewburn <lukem (at) netbsd.org>.",
    641 "Execute 'man ftp' for more details");
    642 			} else if (strcasecmp(cp, "netbsd") == 0) {
    643 				fprintf(ttyout, "%s\n%s\n",
    644 "NetBSD is a freely available and redistributable UNIX-like operating system.",
    645 "For more information, see http://www.netbsd.org/index.html");
    646 			} else {
    647 				fprintf(ttyout,
    648 				    "'%s' is an interesting topic.\n", cp);
    649 			}
    650 			continue;
    651 		}
    652 #endif /* SMALL */
    653 
    654 		/*
    655 		 * Check for file:// and http:// URLs.
    656 		 */
    657 		if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
    658 		    strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
    659 			if (url_get(line, httpproxy, outfile) == -1)
    660 				rval = argpos + 1;
    661 			continue;
    662 		}
    663 
    664 		/*
    665 		 * Try FTP URL-style arguments next. If ftpproxy is
    666 		 * set, use url_get() instead of standard ftp.
    667 		 * Finally, try host:file.
    668 		 */
    669 		host = line;
    670 		if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
    671 			if (ftpproxy) {
    672 				if (url_get(line, ftpproxy, outfile) == -1)
    673 					rval = argpos + 1;
    674 				continue;
    675 			}
    676 			host += sizeof(FTP_URL) - 1;
    677 			dir = strchr(host, '/');
    678 
    679 				/* look for [user:pass@]host[:port] */
    680 			pass = strpbrk(host, ":@/");
    681 			if (pass == NULL || *pass == '/') {
    682 				pass = NULL;
    683 				goto parsed_url;
    684 			}
    685 			if (pass == host || *pass == '@') {
    686 bad_url:
    687 				warnx("Invalid URL: %s", argv[argpos]);
    688 				rval = argpos + 1;
    689 				continue;
    690 			}
    691 			*pass++ = '\0';
    692 			cp = strpbrk(pass, ":@/");
    693 			if (cp == NULL || *cp == '/') {
    694 				portnum = pass;
    695 				pass = NULL;
    696 				goto parsed_url;
    697 			}
    698 			if (EMPTYSTRING(cp) || *cp == ':')
    699 				goto bad_url;
    700 			*cp++ = '\0';
    701 			user = host;
    702 			if (EMPTYSTRING(user))
    703 				goto bad_url;
    704 			host = cp;
    705 			portnum = strchr(host, ':');
    706 			if (portnum != NULL)
    707 				*portnum++ = '\0';
    708 		} else {			/* classic style `host:file' */
    709 			dir = strchr(host, ':');
    710 		}
    711 parsed_url:
    712 		if (EMPTYSTRING(host)) {
    713 			rval = argpos + 1;
    714 			continue;
    715 		}
    716 
    717 		/*
    718 		 * If dir is NULL, the file wasn't specified
    719 		 * (URL looked something like ftp://host)
    720 		 */
    721 		if (dir != NULL)
    722 			*dir++ = '\0';
    723 
    724 		/*
    725 		 * Extract the file and (if present) directory name.
    726 		 */
    727 		if (! EMPTYSTRING(dir)) {
    728 			cp = strrchr(dir, '/');
    729 			if (cp != NULL) {
    730 				*cp++ = '\0';
    731 				file = cp;
    732 			} else {
    733 				file = dir;
    734 				dir = NULL;
    735 			}
    736 		}
    737 		if (debug)
    738 			fprintf(ttyout,
    739 			    "user %s:%s host %s port %s dir %s file %s\n",
    740 			    user, pass, host, portnum, dir, file);
    741 
    742 		/*
    743 		 * Set up the connection if we don't have one.
    744 		 */
    745 		if (strcmp(host, lasthost) != 0) {
    746 			int oautologin;
    747 
    748 			(void)strcpy(lasthost, host);
    749 			if (connected)
    750 				disconnect(0, NULL);
    751 			xargv[0] = __progname;
    752 			xargv[1] = host;
    753 			xargv[2] = NULL;
    754 			xargc = 2;
    755 			if (! EMPTYSTRING(portnum)) {
    756 				xargv[2] = portnum;
    757 				xargv[3] = NULL;
    758 				xargc = 3;
    759 			}
    760 			oautologin = autologin;
    761 			if (user != NULL)
    762 				autologin = 0;
    763 			setpeer(xargc, xargv);
    764 			autologin = oautologin;
    765 			if ((connected == 0)
    766 			 || ((connected == 1) &&
    767 			     !ftp_login(host, user, pass)) ) {
    768 				warnx("Can't connect or login to host `%s'",
    769 				    host);
    770 				rval = argpos + 1;
    771 				continue;
    772 			}
    773 
    774 			/* Always use binary transfers. */
    775 			setbinary(0, NULL);
    776 		}
    777 			/* cd back to '/' */
    778 		xargv[0] = "cd";
    779 		xargv[1] = "/";
    780 		xargv[2] = NULL;
    781 		cd(2, xargv);
    782 		if (! dirchange) {
    783 			rval = argpos + 1;
    784 			continue;
    785 		}
    786 
    787 		dirhasglob = filehasglob = 0;
    788 		if (doglob) {
    789 			if (! EMPTYSTRING(dir) &&
    790 			    strpbrk(dir, "*?[]{}") != NULL)
    791 				dirhasglob = 1;
    792 			if (! EMPTYSTRING(file) &&
    793 			    strpbrk(file, "*?[]{}") != NULL)
    794 				filehasglob = 1;
    795 		}
    796 
    797 		/* Change directories, if necessary. */
    798 		if (! EMPTYSTRING(dir) && !dirhasglob) {
    799 			xargv[0] = "cd";
    800 			xargv[1] = dir;
    801 			xargv[2] = NULL;
    802 			cd(2, xargv);
    803 			if (! dirchange) {
    804 				rval = argpos + 1;
    805 				continue;
    806 			}
    807 		}
    808 
    809 		if (EMPTYSTRING(file)) {
    810 			rval = -1;
    811 			continue;
    812 		}
    813 
    814 		if (!verbose)
    815 			fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "",
    816 			    file);
    817 
    818 		if (dirhasglob) {
    819 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
    820 			file = rempath;
    821 		}
    822 
    823 		/* Fetch the file(s). */
    824 		xargc = 2;
    825 		xargv[0] = "get";
    826 		xargv[1] = file;
    827 		xargv[2] = NULL;
    828 		if (dirhasglob || filehasglob) {
    829 			int ointeractive;
    830 
    831 			ointeractive = interactive;
    832 			interactive = 0;
    833 			xargv[0] = "mget";
    834 			mget(xargc, xargv);
    835 			interactive = ointeractive;
    836 		} else {
    837 			if (outfile != NULL) {
    838 				xargv[2] = outfile;
    839 				xargv[3] = NULL;
    840 				xargc++;
    841 			}
    842 			get(xargc, xargv);
    843 			if (outfile != NULL && strcmp(outfile, "-") != 0
    844 			    && outfile[0] != '|')
    845 				outfile = NULL;
    846 		}
    847 
    848 		if ((code / 100) != COMPLETE)
    849 			rval = argpos + 1;
    850 	}
    851 	if (connected && rval != -1)
    852 		disconnect(0, NULL);
    853 	return (rval);
    854 }
    855