xref: /openbsd-src/usr.bin/ftp/fetch.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: fetch.c,v 1.34 2001/06/23 22:48:44 millert Exp $	*/
2 /*	$NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $	*/
3 
4 /*-
5  * Copyright (c) 1997 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Jason Thorpe and Luke Mewburn.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *        This product includes software developed by the NetBSD
22  *        Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #ifndef lint
41 static char rcsid[] = "$OpenBSD: fetch.c,v 1.34 2001/06/23 22:48:44 millert 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 
53 #include <netinet/in.h>
54 
55 #include <arpa/ftp.h>
56 #include <arpa/inet.h>
57 
58 #include <ctype.h>
59 #include <err.h>
60 #include <libgen.h>
61 #include <netdb.h>
62 #include <fcntl.h>
63 #include <signal.h>
64 #include <stdio.h>
65 #include <errno.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <unistd.h>
69 
70 #include "ftp_var.h"
71 
72 static int	url_get __P((const char *, const char *, const char *));
73 void		aborthttp __P((int));
74 void		abortfile __P((int));
75 
76 
77 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
78 #define	HTTP_URL	"http://"	/* http URL prefix */
79 #define	FILE_URL	"file:"		/* file URL prefix */
80 #define FTP_PROXY	"ftp_proxy"	/* env var with ftp proxy location */
81 #define HTTP_PROXY	"http_proxy"	/* env var with http proxy location */
82 
83 
84 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
85 
86 jmp_buf	httpabort;
87 
88 /*
89  * Retrieve URL, via the proxy in $proxyvar if necessary.
90  * Modifies the string argument given.
91  * Returns -1 on failure, 0 on success
92  */
93 static int
94 url_get(origline, proxyenv, outfile)
95 	const char *origline;
96 	const char *proxyenv;
97 	const char *outfile;
98 {
99 	struct addrinfo hints, *res0, *res;
100 	int error;
101 	int i, isftpurl, isfileurl;
102 	volatile int s, out;
103 	size_t len;
104 	char c, *cp, *ep, *portnum, *path, buf[4096];
105 	char pbuf[NI_MAXSERV];
106 	const char * volatile savefile;
107 	char *line, *host, *port;
108 	char * volatile proxy;
109 	char *hosttail;
110 	volatile sig_t oldintr;
111 	off_t hashbytes;
112 	char *cause = "unknown";
113 
114 	s = -1;
115 	proxy = NULL;
116 	isftpurl = 0;
117 	isfileurl = 0;
118 
119 	line = strdup(origline);
120 	if (line == NULL)
121 		errx(1, "Can't allocate memory to parse URL");
122 	if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
123 		host = line + sizeof(HTTP_URL) - 1;
124 	else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
125 		host = line + sizeof(FTP_URL) - 1;
126 		isftpurl = 1;
127 	} else if (strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
128 		host = line + sizeof(FILE_URL) - 1;
129 		isfileurl = 1;
130 	} else
131 		errx(1, "url_get: Invalid URL '%s'", line);
132 
133 	if (isfileurl) {
134 		path = host;
135 	} else {
136 		path = strchr(host, '/');		/* find path */
137 		if (EMPTYSTRING(path)) {
138 			if (isftpurl)
139 				goto noftpautologin;
140 			warnx("Invalid URL (no `/' after host): %s", origline);
141 			goto cleanup_url_get;
142 		}
143 		*path++ = '\0';
144 		if (EMPTYSTRING(path)) {
145 			if (isftpurl)
146 				goto noftpautologin;
147 			warnx("Invalid URL (no file after host): %s", origline);
148 			goto cleanup_url_get;
149 		}
150 	}
151 
152 	if (outfile)
153 		savefile = outfile;
154 	else
155 		savefile = basename(path);
156 
157 	if (EMPTYSTRING(savefile)) {
158 		if (isftpurl)
159 			goto noftpautologin;
160 		warnx("Invalid URL (no file after directory): %s", origline);
161 		goto cleanup_url_get;
162 	}
163 
164 	if (proxyenv != NULL) {				/* use proxy */
165 		proxy = strdup(proxyenv);
166 		if (proxy == NULL)
167 			errx(1, "Can't allocate memory for proxy URL.");
168 		if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
169 			host = proxy + sizeof(HTTP_URL) - 1;
170 		else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
171 			host = proxy + sizeof(FTP_URL) - 1;
172 		else {
173 			warnx("Malformed proxy URL: %s", proxyenv);
174 			goto cleanup_url_get;
175 		}
176 		if (EMPTYSTRING(host)) {
177 			warnx("Malformed proxy URL: %s", proxyenv);
178 			goto cleanup_url_get;
179 		}
180 		*--path = '/';			/* add / back to real path */
181 		path = strchr(host, '/');	/* remove trailing / on host */
182 		if (! EMPTYSTRING(path))
183 			*path++ = '\0';
184 		path = line;
185 	}
186 
187 	if (isfileurl) {
188 		struct stat st;
189 
190 		s = open(path, O_RDONLY);
191 		if (s == -1) {
192 			warn("Can't open file %s", path);
193 			goto cleanup_url_get;
194 		}
195 
196 		if (fstat(s, &st) == -1)
197 			filesize = -1;
198 		else
199 			filesize = st.st_size;
200 
201 		/* Open the output file.  */
202 		if (strcmp(savefile, "-") != 0) {
203 			out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
204 			if (out < 0) {
205 				warn("Can't open %s", savefile);
206 				goto cleanup_url_get;
207 			}
208 		} else
209 			out = fileno(stdout);
210 
211 		/* Trap signals */
212 		oldintr = NULL;
213 		if (setjmp(httpabort)) {
214 			if (oldintr)
215 				(void)signal(SIGINT, oldintr);
216 			goto cleanup_url_get;
217 		}
218 		oldintr = signal(SIGINT, abortfile);
219 
220 		bytes = 0;
221 		hashbytes = mark;
222 		progressmeter(-1);
223 
224 		/* Finally, suck down the file. */
225 		i = 0;
226 		while ((len = read(s, buf, sizeof(buf))) > 0) {
227 			bytes += len;
228 			for (cp = buf; len > 0; len -= i, cp += i) {
229 				if ((i = write(out, cp, len)) == -1) {
230 					warn("Writing %s", savefile);
231 					goto cleanup_url_get;
232 				}
233 				else if (i == 0)
234 					break;
235 			}
236 			if (hash && !progress) {
237 				while (bytes >= hashbytes) {
238 					(void)putc('#', ttyout);
239 					hashbytes += mark;
240 				}
241 				(void)fflush(ttyout);
242 			}
243 		}
244 		if (hash && !progress && bytes > 0) {
245 			if (bytes < mark)
246 				(void)putc('#', ttyout);
247 			(void)putc('\n', ttyout);
248 			(void)fflush(ttyout);
249 		}
250 		if (len != 0) {
251 			warn("Reading from file");
252 			goto cleanup_url_get;
253 		}
254 		progressmeter(1);
255 		if (verbose)
256 			fputs("Successfully retrieved file.\n", ttyout);
257 		(void)signal(SIGINT, oldintr);
258 
259 		close(s);
260 		if (out != fileno(stdout))
261 			close(out);
262 		if (proxy)
263 			free(proxy);
264 		free(line);
265 		return (0);
266 	}
267 
268 	if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
269 	    (hosttail[1] == '\0' || hosttail[1] == ':')) {
270 		host++;
271 		*hosttail++ = '\0';
272 	} else
273 		hosttail = host;
274 
275 	portnum = strrchr(hosttail, ':');		/* find portnum */
276 	if (portnum != NULL)
277 		*portnum++ = '\0';
278 
279 	if (debug)
280 		fprintf(ttyout, "host %s, port %s, path %s, save as %s.\n",
281 		    host, portnum, path, savefile);
282 
283 	memset(&hints, 0, sizeof(hints));
284 	hints.ai_family = PF_UNSPEC;
285 	hints.ai_socktype = SOCK_STREAM;
286 	port = portnum ? portnum : httpport;
287 	error = getaddrinfo(host, port, &hints, &res0);
288 	if (error == EAI_SERVICE && port == httpport) {
289 		/*
290 		 * If the services file is corrupt/missing, fall back
291 		 * on our hard-coded defines.
292 		 */
293 		char pbuf[NI_MAXSERV];
294 
295 		snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
296 		error = getaddrinfo(host, pbuf, &hints, &res0);
297 	}
298 	if (error) {
299 		warnx("%s: %s", gai_strerror(error), host);
300 		goto cleanup_url_get;
301 	}
302 
303 	s = -1;
304 	for (res = res0; res; res = res->ai_next) {
305 		getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof(buf),
306 			NULL, 0, NI_NUMERICHOST);
307 		fprintf(ttyout, "Trying %s...\n", buf);
308 
309 		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
310 		if (s == -1) {
311 			cause = "socket";
312 			continue;
313 		}
314 
315 again:
316 		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
317 			if (errno == EINTR)
318 				goto again;
319 			close(s);
320 			s = -1;
321 			cause = "connect";
322 			continue;
323 		}
324 
325 		/* get port in numeric */
326 		if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
327 		    pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
328 			port = pbuf;
329 		else
330 			port = NULL;
331 
332 		break;
333 	}
334 	freeaddrinfo(res0);
335 	if (s < 0) {
336 		warn("%s", cause);
337 		goto cleanup_url_get;
338 	}
339 
340 	/*
341 	 * Construct and send the request.  We're expecting a return
342 	 * status of "200". Proxy requests don't want leading /.
343 	 */
344 	if (proxy) {
345 		/*
346 		 * Host: directive must use the destination host address for
347 		 * the original URI (path).  We do not attach it at this moment.
348 		 */
349 		if (verbose)
350 			fprintf(ttyout, "Requesting %s (via %s)\n",
351 			    origline, proxyenv);
352 		snprintf(buf, sizeof(buf), "GET %s HTTP/1.0\r\n\r\n", path);
353 	} else {
354 		if (verbose)
355 			fprintf(ttyout, "Requesting %s\n", origline);
356 		if (strchr(host, ':')) {
357 			char *h, *p;
358 
359 			/* strip off scoped address portion, since it's local to node */
360 			h = strdup(host);
361 			if (h == NULL)
362 				errx(1, "Can't allocate memory.");
363 			if ((p = strchr(h, '%')) != NULL)
364 				*p = '\0';
365 			snprintf(buf, sizeof(buf),
366 			    "GET /%s HTTP/1.0\r\nHost: [%s]%s%s\r\n\r\n",
367 			    path, h, port ? ":" : "", port ? port : "");
368 			free(h);
369 		} else {
370 			snprintf(buf, sizeof(buf),
371 			    "GET /%s HTTP/1.0\r\nHost: %s%s%s\r\n\r\n",
372 			    path, host, port ? ":" : "", port ? port : "");
373 		}
374 	}
375 	len = strlen(buf);
376 	if (write(s, buf, len) < len) {
377 		warn("Writing HTTP request");
378 		goto cleanup_url_get;
379 	}
380 	memset(buf, 0, sizeof(buf));
381 	for (cp = buf; cp < buf + sizeof(buf); ) {
382 		if (read(s, cp, 1) != 1)
383 			goto improper;
384 		if (*cp == '\r')
385 			continue;
386 		if (*cp == '\n')
387 			break;
388 		cp++;
389 	}
390 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
391 	cp = strchr(buf, ' ');
392 	if (cp == NULL)
393 		goto improper;
394 	else
395 		cp++;
396 	if (strncmp(cp, "200", 3)) {
397 		warnx("Error retrieving file: %s", cp);
398 		goto cleanup_url_get;
399 	}
400 
401 	/*
402 	 * Read the rest of the header.
403 	 */
404 	memset(buf, 0, sizeof(buf));
405 	c = '\0';
406 	for (cp = buf; cp < buf + sizeof(buf); ) {
407 		if (read(s, cp, 1) != 1)
408 			goto improper;
409 		if (*cp == '\r')
410 			continue;
411 		if (*cp == '\n' && c == '\n')
412 			break;
413 		c = *cp;
414 		cp++;
415 	}
416 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
417 
418 	/* Look for the "Content-length: " header.  */
419 #define CONTENTLEN "Content-Length: "
420 	for (cp = buf; *cp != '\0'; cp++) {
421 		if (tolower(*cp) == 'c' &&
422 		    strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
423 			break;
424 	}
425 	if (*cp != '\0') {
426 		cp += sizeof(CONTENTLEN) - 1;
427 		ep = strchr(cp, '\n');
428 		if (ep == NULL)
429 			goto improper;
430 		else
431 			*ep = '\0';
432 		filesize = strtol(cp, &ep, 10);
433 		if (filesize < 1 || *ep != '\0')
434 			goto improper;
435 	} else
436 		filesize = -1;
437 
438 	/* Open the output file.  */
439 	if (strcmp(savefile, "-") != 0) {
440 		out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
441 		if (out < 0) {
442 			warn("Can't open %s", savefile);
443 			goto cleanup_url_get;
444 		}
445 	} else
446 		out = fileno(stdout);
447 
448 	/* Trap signals */
449 	oldintr = NULL;
450 	if (setjmp(httpabort)) {
451 		if (oldintr)
452 			(void)signal(SIGINT, oldintr);
453 		goto cleanup_url_get;
454 	}
455 	oldintr = signal(SIGINT, aborthttp);
456 
457 	bytes = 0;
458 	hashbytes = mark;
459 	progressmeter(-1);
460 
461 	/* Finally, suck down the file. */
462 	i = 0;
463 	while ((len = read(s, buf, sizeof(buf))) > 0) {
464 		bytes += len;
465 		for (cp = buf; len > 0; len -= i, cp += i) {
466 			if ((i = write(out, cp, len)) == -1) {
467 				warn("Writing %s", savefile);
468 				goto cleanup_url_get;
469 			}
470 			else if (i == 0)
471 				break;
472 		}
473 		if (hash && !progress) {
474 			while (bytes >= hashbytes) {
475 				(void)putc('#', ttyout);
476 				hashbytes += mark;
477 			}
478 			(void)fflush(ttyout);
479 		}
480 	}
481 	if (hash && !progress && bytes > 0) {
482 		if (bytes < mark)
483 			(void)putc('#', ttyout);
484 		(void)putc('\n', ttyout);
485 		(void)fflush(ttyout);
486 	}
487 	if (len != 0) {
488 		warn("Reading from socket");
489 		goto cleanup_url_get;
490 	}
491 	progressmeter(1);
492 	if (filesize != -1 && len == 0 && bytes != filesize) {
493 		if (verbose)
494 			fputs("Read short file.\n", ttyout);
495 		goto cleanup_url_get;
496 	}
497 
498 	if (verbose)
499 		fputs("Successfully retrieved file.\n", ttyout);
500 	(void)signal(SIGINT, oldintr);
501 
502 	close(s);
503 	if (out != fileno(stdout))
504 		close(out);
505 	if (proxy)
506 		free(proxy);
507 	free(line);
508 	return (0);
509 
510 noftpautologin:
511 	warnx(
512 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
513 	goto cleanup_url_get;
514 
515 improper:
516 	warnx("Improper response from %s", host);
517 
518 cleanup_url_get:
519 	if (s != -1)
520 		close(s);
521 	if (proxy)
522 		free(proxy);
523 	free(line);
524 	return (-1);
525 }
526 
527 /*
528  * Abort a http retrieval
529  */
530 void
531 aborthttp(notused)
532 	int notused;
533 {
534 
535 	alarmtimer(0);
536 	fputs("\nhttp fetch aborted.\n", ttyout);
537 	(void)fflush(ttyout);
538 	longjmp(httpabort, 1);
539 }
540 
541 /*
542  * Abort a http retrieval
543  */
544 void
545 abortfile(notused)
546 	int notused;
547 {
548 
549 	alarmtimer(0);
550 	fputs("\nfile fetch aborted.\n", ttyout);
551 	(void)fflush(ttyout);
552 	longjmp(httpabort, 1);
553 }
554 
555 /*
556  * Retrieve multiple files from the command line, transferring
557  * files of the form "host:path", "ftp://host/path" using the
558  * ftp protocol, and files of the form "http://host/path" using
559  * the http protocol.
560  * If path has a trailing "/", then return (-1);
561  * the path will be cd-ed into and the connection remains open,
562  * and the function will return -1 (to indicate the connection
563  * is alive).
564  * If an error occurs the return value will be the offset+1 in
565  * argv[] of the file that caused a problem (i.e, argv[x]
566  * returns x+1)
567  * Otherwise, 0 is returned if all files retrieved successfully.
568  */
569 int
570 auto_fetch(argc, argv, outfile)
571 	int argc;
572 	char *argv[];
573 	char *outfile;
574 {
575 	static char lasthost[MAXHOSTNAMELEN];
576 	char *xargv[5];
577 	char *cp, *line, *host, *dir, *file, *portnum;
578 	char *user, *pass;
579 	char *ftpproxy, *httpproxy;
580 	int rval, xargc;
581 	volatile int argpos;
582 	int dirhasglob, filehasglob;
583 	char rempath[MAXPATHLEN];
584 
585 	argpos = 0;
586 
587 	if (setjmp(toplevel)) {
588 		if (connected)
589 			disconnect(0, NULL);
590 		return (argpos + 1);
591 	}
592 	(void)signal(SIGINT, (sig_t)intr);
593 	(void)signal(SIGPIPE, (sig_t)lostpeer);
594 
595 	ftpproxy = getenv(FTP_PROXY);
596 	httpproxy = getenv(HTTP_PROXY);
597 
598 	/*
599 	 * Loop through as long as there's files to fetch.
600 	 */
601 	for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
602 		if (strchr(argv[argpos], ':') == NULL)
603 			break;
604 		host = dir = file = portnum = user = pass = NULL;
605 
606 		/*
607 		 * We muck with the string, so we make a copy.
608 		 */
609 		line = strdup(argv[argpos]);
610 		if (line == NULL)
611 			errx(1, "Can't allocate memory for auto-fetch.");
612 
613 		/*
614 		 * Try HTTP URL-style arguments first.
615 		 */
616 		if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
617 		    strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
618 			if (url_get(line, httpproxy, outfile) == -1)
619 				rval = argpos + 1;
620 			continue;
621 		}
622 
623 		/*
624 		 * Try FTP URL-style arguments next. If ftpproxy is
625 		 * set, use url_get() instead of standard ftp.
626 		 * Finally, try host:file.
627 		 */
628 		host = line;
629 		if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
630 			char *passend, *userend;
631 
632 			if (ftpproxy) {
633 				if (url_get(line, ftpproxy, outfile) == -1)
634 					rval = argpos + 1;
635 				continue;
636 			}
637 			host += sizeof(FTP_URL) - 1;
638 			dir = strchr(host, '/');
639 
640 			/* Look for [user:pass@]host[:port] */
641 
642 			/* check if we have "user:pass@" */
643 			passend = strchr(host, '@');
644 			userend = strchr(host, ':');
645 			if (passend && userend && userend < passend &&
646 			    (!dir || passend < dir)) {
647 				user = host;
648 				pass = userend + 1;
649 				host = passend + 1;
650 				*userend = *passend = '\0';
651 
652 				if (EMPTYSTRING(user) || EMPTYSTRING(pass)) {
653 bad_ftp_url:
654 					warnx("Invalid URL: %s", argv[argpos]);
655 					rval = argpos + 1;
656 					continue;
657 				}
658 			}
659 
660 #ifdef INET6
661 			/* check [host]:port, or [host] */
662 			if (host[0] == '[') {
663 				cp = strchr(host, ']');
664 				if (cp && (!dir || cp < dir)) {
665 					if (cp + 1 == dir || cp[1] == ':') {
666 						host++;
667 						*cp++ = '\0';
668 					} else
669 						cp = NULL;
670 				} else
671 					cp = host;
672 			} else
673 				cp = host;
674 #else
675 			cp = host;
676 #endif
677 
678 			/* split off host[:port] if there is */
679 			if (cp) {
680 				portnum = strchr(cp, ':');
681 				if (!portnum)
682 					;
683 				else {
684 					if (!dir)
685 						;
686 					else if (portnum + 1 < dir) {
687 						*portnum++ = '\0';
688 						/*
689 						 * XXX should check if portnum
690 						 * is decimal number
691 						 */
692 					} else {
693 						/* empty portnum */
694 						goto bad_ftp_url;
695 					}
696 				}
697 			} else
698 				portnum = NULL;
699 		} else {			/* classic style `host:file' */
700 			dir = strchr(host, ':');
701 		}
702 		if (EMPTYSTRING(host)) {
703 			rval = argpos + 1;
704 			continue;
705 		}
706 
707 		/*
708 		 * If dir is NULL, the file wasn't specified
709 		 * (URL looked something like ftp://host)
710 		 */
711 		if (dir != NULL)
712 			*dir++ = '\0';
713 
714 		/*
715 		 * Extract the file and (if present) directory name.
716 		 */
717 		if (! EMPTYSTRING(dir)) {
718 			cp = strrchr(dir, '/');
719 			if (cp != NULL) {
720 				*cp++ = '\0';
721 				file = cp;
722 			} else {
723 				file = dir;
724 				dir = NULL;
725 			}
726 		}
727 		if (debug)
728 			fprintf(ttyout, "user %s:%s host %s port %s dir %s file %s\n",
729 			    user, pass, host, portnum, dir, file);
730 
731 		/*
732 		 * Set up the connection if we don't have one.
733 		 */
734 		if (strcmp(host, lasthost) != 0) {
735 			int oautologin;
736 
737 			(void)strcpy(lasthost, host);
738 			if (connected)
739 				disconnect(0, NULL);
740 			xargv[0] = __progname;
741 			xargv[1] = host;
742 			xargv[2] = NULL;
743 			xargc = 2;
744 			if (! EMPTYSTRING(portnum)) {
745 				xargv[2] = portnum;
746 				xargv[3] = NULL;
747 				xargc = 3;
748 			}
749 			oautologin = autologin;
750 			if (user != NULL)
751 				autologin = 0;
752 			setpeer(xargc, xargv);
753 			autologin = oautologin;
754 			if ((connected == 0) ||
755 			    ((connected == 1) && !login(host, user, pass))) {
756 				warnx("Can't connect or login to host `%s'",
757 				    host);
758 				rval = argpos + 1;
759 				continue;
760 			}
761 
762 			/* Always use binary transfers. */
763 			setbinary(0, NULL);
764 		}
765 		/* cd back to '/' */
766 		xargv[0] = "cd";
767 		xargv[1] = "/";
768 		xargv[2] = NULL;
769 		cd(2, xargv);
770 		if (! dirchange) {
771 			rval = argpos + 1;
772 			continue;
773 		}
774 
775 		dirhasglob = filehasglob = 0;
776 		if (doglob) {
777 			if (! EMPTYSTRING(dir) &&
778 			    strpbrk(dir, "*?[]{}") != NULL)
779 				dirhasglob = 1;
780 			if (! EMPTYSTRING(file) &&
781 			    strpbrk(file, "*?[]{}") != NULL)
782 				filehasglob = 1;
783 		}
784 
785 		/* Change directories, if necessary. */
786 		if (! EMPTYSTRING(dir) && !dirhasglob) {
787 			xargv[0] = "cd";
788 			xargv[1] = dir;
789 			xargv[2] = NULL;
790 			cd(2, xargv);
791 			if (! dirchange) {
792 				rval = argpos + 1;
793 				continue;
794 			}
795 		}
796 
797 		if (EMPTYSTRING(file)) {
798 			rval = -1;
799 			continue;
800 		}
801 
802 		if (verbose)
803 			fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
804 
805 		if (dirhasglob) {
806 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
807 			file = rempath;
808 		}
809 
810 		/* Fetch the file(s). */
811 		xargc = 2;
812 		xargv[0] = "get";
813 		xargv[1] = file;
814 		xargv[2] = NULL;
815 		if (dirhasglob || filehasglob) {
816 			int ointeractive;
817 
818 			ointeractive = interactive;
819 			interactive = 0;
820 			xargv[0] = "mget";
821 			mget(xargc, xargv);
822 			interactive = ointeractive;
823 		} else {
824 			if (outfile != NULL) {
825 				xargv[2] = outfile;
826 				xargv[3] = NULL;
827 				xargc++;
828 			}
829 			get(xargc, xargv);
830 		}
831 
832 		if ((code / 100) != COMPLETE)
833 			rval = argpos + 1;
834 	}
835 	if (connected && rval != -1)
836 		disconnect(0, NULL);
837 	return (rval);
838 }
839 
840 int
841 isurl(p)
842 	const char *p;
843 {
844 
845 	if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
846 	    strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
847 	    strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
848 	    strstr(p, ":/"))
849 		return (1);
850 	return (0);
851 }
852