fetch.c revision 116424
1/*	$NetBSD: fetch.c,v 1.141 2003/05/14 14:31:00 wiz Exp $	*/
2
3/*-
4 * Copyright (c) 1997-2003 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * This code is derived from software contributed to The NetBSD Foundation
11 * by Scott Aaron Bamford.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer in the
20 *    documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 *    must display the following acknowledgement:
23 *	This product includes software developed by the NetBSD
24 *	Foundation, Inc. and its contributors.
25 * 4. Neither the name of The NetBSD Foundation nor the names of its
26 *    contributors may be used to endorse or promote products derived
27 *    from this software without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
30 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
31 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
33 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 * POSSIBILITY OF SUCH DAMAGE.
40 */
41
42#include <sys/cdefs.h>
43#ifndef lint
44__RCSID("$NetBSD: fetch.c,v 1.141 2003/05/14 14:31:00 wiz Exp $");
45#endif /* not lint */
46
47/*
48 * FTP User Program -- Command line file retrieval
49 */
50
51#include <sys/types.h>
52#include <sys/param.h>
53#include <sys/socket.h>
54#include <sys/stat.h>
55#include <sys/time.h>
56
57#include <netinet/in.h>
58
59#include <arpa/ftp.h>
60#include <arpa/inet.h>
61
62#include <ctype.h>
63#include <err.h>
64#include <errno.h>
65#include <netdb.h>
66#include <fcntl.h>
67#include <stdio.h>
68#include <stdlib.h>
69#include <string.h>
70#include <unistd.h>
71#include <time.h>
72#include <libutil.h>
73
74#include "ftp_var.h"
75#include "version.h"
76
77typedef enum {
78	UNKNOWN_URL_T=-1,
79	HTTP_URL_T,
80	FTP_URL_T,
81	FILE_URL_T,
82	CLASSIC_URL_T
83} url_t;
84
85void		aborthttp(int);
86static int	auth_url(const char *, char **, const char *, const char *);
87static void	base64_encode(const u_char *, size_t, u_char *);
88static int	go_fetch(const char *);
89static int	fetch_ftp(const char *);
90static int	fetch_url(const char *, const char *, char *, char *);
91static int	parse_url(const char *, const char *, url_t *, char **,
92			    char **, char **, char **, in_port_t *, char **);
93static void	url_decode(char *);
94
95static int	redirect_loop;
96
97
98#define	ABOUT_URL	"about:"	/* propaganda */
99#define	FILE_URL	"file://"	/* file URL prefix */
100#define	FTP_URL		"ftp://"	/* ftp URL prefix */
101#define	HTTP_URL	"http://"	/* http URL prefix */
102
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 */
109static int
110auth_url(const char *challenge, char **response, const char *guser,
111	const char *gpass)
112{
113	char		*cp, *ep, *clear, *line, *realm, *scheme;
114	char		 user[BUFSIZ], *pass;
115	int		 rval;
116	size_t		 len, clen, rlen;
117
118	*response = NULL;
119	clear = realm = scheme = NULL;
120	rval = -1;
121	line = xstrdup(challenge);
122	cp = line;
123
124	if (debug)
125		fprintf(ttyout, "auth_url: challenge `%s'\n", challenge);
126
127	scheme = strsep(&cp, " ");
128#define	SCHEME_BASIC "Basic"
129	if (strncasecmp(scheme, SCHEME_BASIC, sizeof(SCHEME_BASIC) - 1) != 0) {
130		warnx("Unsupported WWW Authentication challenge - `%s'",
131		    challenge);
132		goto cleanup_auth_url;
133	}
134	cp += strspn(cp, " ");
135
136#define	REALM "realm=\""
137	if (strncasecmp(cp, REALM, sizeof(REALM) - 1) == 0)
138		cp += sizeof(REALM) - 1;
139	else {
140		warnx("Unsupported WWW Authentication challenge - `%s'",
141		    challenge);
142		goto cleanup_auth_url;
143	}
144	if ((ep = strchr(cp, '\"')) != NULL) {
145		size_t len = ep - cp;
146
147		realm = (char *)xmalloc(len + 1);
148		(void)strlcpy(realm, cp, len + 1);
149	} else {
150		warnx("Unsupported WWW Authentication challenge - `%s'",
151		    challenge);
152		goto cleanup_auth_url;
153	}
154
155	if (guser != NULL)
156		(void)strlcpy(user, guser, sizeof(user));
157	else {
158		fprintf(ttyout, "Username for `%s': ", realm);
159		(void)fflush(ttyout);
160		if (fgets(user, sizeof(user) - 1, stdin) == NULL) {
161			clearerr(stdin);
162			goto cleanup_auth_url;
163		}
164		user[strlen(user) - 1] = '\0';
165	}
166	if (gpass != NULL)
167		pass = (char *)gpass;
168	else
169		pass = getpass("Password: ");
170
171	clen = strlen(user) + strlen(pass) + 2;	/* user + ":" + pass + "\0" */
172	clear = (char *)xmalloc(clen);
173	(void)strlcpy(clear, user, clen);
174	(void)strlcat(clear, ":", clen);
175	(void)strlcat(clear, pass, clen);
176	if (gpass == NULL)
177		memset(pass, 0, strlen(pass));
178
179						/* scheme + " " + enc + "\0" */
180	rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
181	*response = (char *)xmalloc(rlen);
182	(void)strlcpy(*response, scheme, rlen);
183	len = strlcat(*response, " ", rlen);
184	base64_encode(clear, clen, (u_char *)*response + len);
185	memset(clear, 0, clen);
186	rval = 0;
187
188 cleanup_auth_url:
189	FREEPTR(clear);
190	FREEPTR(line);
191	FREEPTR(realm);
192	return (rval);
193}
194
195/*
196 * Encode len bytes starting at clear using base64 encoding into encoded,
197 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
198 */
199static void
200base64_encode(const u_char *clear, size_t len, u_char *encoded)
201{
202	static const u_char enc[] =
203	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
204	u_char	*cp;
205	int	 i;
206
207	cp = encoded;
208	for (i = 0; i < len; i += 3) {
209		*(cp++) = enc[((clear[i + 0] >> 2))];
210		*(cp++) = enc[((clear[i + 0] << 4) & 0x30)
211			    | ((clear[i + 1] >> 4) & 0x0f)];
212		*(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
213			    | ((clear[i + 2] >> 6) & 0x03)];
214		*(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
215	}
216	*cp = '\0';
217	while (i-- > len)
218		*(--cp) = '=';
219}
220
221/*
222 * Decode %xx escapes in given string, `in-place'.
223 */
224static void
225url_decode(char *url)
226{
227	unsigned char *p, *q;
228
229	if (EMPTYSTRING(url))
230		return;
231	p = q = (unsigned char *)url;
232
233#define	HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
234	while (*p) {
235		if (p[0] == '%'
236		    && p[1] && isxdigit((unsigned char)p[1])
237		    && p[2] && isxdigit((unsigned char)p[2])) {
238			*q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
239			p+=3;
240		} else
241			*q++ = *p++;
242	}
243	*q = '\0';
244}
245
246
247/*
248 * Parse URL of form:
249 *	<type>://[<user>[:<password>@]]<host>[:<port>][/<path>]
250 * Returns -1 if a parse error occurred, otherwise 0.
251 * It's the caller's responsibility to url_decode() the returned
252 * user, pass and path.
253 *
254 * Sets type to url_t, each of the given char ** pointers to a
255 * malloc(3)ed strings of the relevant section, and port to
256 * the number given, or ftpport if ftp://, or httpport if http://.
257 *
258 * If <host> is surrounded by `[' and ']', it's parsed as an
259 * IPv6 address (as per RFC 2732).
260 *
261 * XXX: this is not totally RFC 1738 compliant; <path> will have the
262 * leading `/' unless it's an ftp:// URL, as this makes things easier
263 * for file:// and http:// URLs. ftp:// URLs have the `/' between the
264 * host and the URL-path removed, but any additional leading slashes
265 * in the URL-path are retained (because they imply that we should
266 * later do "CWD" with a null argument).
267 *
268 * Examples:
269 *	 input URL			 output path
270 *	 ---------			 -----------
271 *	"ftp://host"			NULL
272 *	"http://host/"			NULL
273 *	"file://host/dir/file"		"dir/file"
274 *	"ftp://host/"			""
275 *	"ftp://host//"			NULL
276 *	"ftp://host//dir/file"		"/dir/file"
277 */
278static int
279parse_url(const char *url, const char *desc, url_t *type,
280		char **user, char **pass, char **host, char **port,
281		in_port_t *portnum, char **path)
282{
283	const char	*origurl;
284	char		*cp, *ep, *thost, *tport;
285	size_t		 len;
286
287	if (url == NULL || desc == NULL || type == NULL || user == NULL
288	    || pass == NULL || host == NULL || port == NULL || portnum == NULL
289	    || path == NULL)
290		errx(1, "parse_url: invoked with NULL argument!");
291
292	origurl = url;
293	*type = UNKNOWN_URL_T;
294	*user = *pass = *host = *port = *path = NULL;
295	*portnum = 0;
296	tport = NULL;
297
298	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
299		url += sizeof(HTTP_URL) - 1;
300		*type = HTTP_URL_T;
301		*portnum = HTTP_PORT;
302		tport = httpport;
303	} else if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
304		url += sizeof(FTP_URL) - 1;
305		*type = FTP_URL_T;
306		*portnum = FTP_PORT;
307		tport = ftpport;
308	} else if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
309		url += sizeof(FILE_URL) - 1;
310		*type = FILE_URL_T;
311	} else {
312		warnx("Invalid %s `%s'", desc, url);
313 cleanup_parse_url:
314		FREEPTR(*user);
315		FREEPTR(*pass);
316		FREEPTR(*host);
317		FREEPTR(*port);
318		FREEPTR(*path);
319		return (-1);
320	}
321
322	if (*url == '\0')
323		return (0);
324
325			/* find [user[:pass]@]host[:port] */
326	ep = strchr(url, '/');
327	if (ep == NULL)
328		thost = xstrdup(url);
329	else {
330		len = ep - url;
331		thost = (char *)xmalloc(len + 1);
332		(void)strlcpy(thost, url, len + 1);
333		if (*type == FTP_URL_T)	/* skip first / for ftp URLs */
334			ep++;
335		*path = xstrdup(ep);
336	}
337
338	cp = strchr(thost, '@');	/* look for user[:pass]@ in URLs */
339	if (cp != NULL) {
340		if (*type == FTP_URL_T)
341			anonftp = 0;	/* disable anonftp */
342		*user = thost;
343		*cp = '\0';
344		thost = xstrdup(cp + 1);
345		cp = strchr(*user, ':');
346		if (cp != NULL) {
347			*cp = '\0';
348			*pass = xstrdup(cp + 1);
349		}
350	}
351
352#ifdef INET6
353			/*
354			 * Check if thost is an encoded IPv6 address, as per
355			 * RFC 2732:
356			 *	`[' ipv6-address ']'
357			 */
358	if (*thost == '[') {
359		cp = thost + 1;
360		if ((ep = strchr(cp, ']')) == NULL ||
361		    (ep[1] != '\0' && ep[1] != ':')) {
362			warnx("Invalid address `%s' in %s `%s'",
363			    thost, desc, origurl);
364			goto cleanup_parse_url;
365		}
366		len = ep - cp;		/* change `[xyz]' -> `xyz' */
367		memmove(thost, thost + 1, len);
368		thost[len] = '\0';
369		if (! isipv6addr(thost)) {
370			warnx("Invalid IPv6 address `%s' in %s `%s'",
371			    thost, desc, origurl);
372			goto cleanup_parse_url;
373		}
374		cp = ep + 1;
375		if (*cp == ':')
376			cp++;
377		else
378			cp = NULL;
379	} else
380#endif /* INET6 */
381	    if ((cp = strchr(thost, ':')) != NULL)
382		*cp++ =  '\0';
383	*host = thost;
384
385			/* look for [:port] */
386	if (cp != NULL) {
387		long	nport;
388
389		nport = parseport(cp, -1);
390		if (nport == -1) {
391			warnx("Unknown port `%s' in %s `%s'",
392			    cp, desc, origurl);
393			goto cleanup_parse_url;
394		}
395		*portnum = nport;
396		tport = cp;
397	}
398
399	if (tport != NULL)
400		*port = xstrdup(tport);
401	if (*path == NULL)
402		*path = xstrdup("/");
403
404	if (debug)
405		fprintf(ttyout,
406		    "parse_url: user `%s' pass `%s' host %s port %s(%d) "
407		    "path `%s'\n",
408		    *user ? *user : "<null>", *pass ? *pass : "<null>",
409		    *host ? *host : "<null>", *port ? *port : "<null>",
410		    *portnum ? *portnum : -1, *path ? *path : "<null>");
411
412	return (0);
413}
414
415sigjmp_buf	httpabort;
416
417/*
418 * Retrieve URL, via a proxy if necessary, using HTTP.
419 * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
420 * http_proxy as appropriate.
421 * Supports HTTP redirects.
422 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
423 * is still open (e.g, ftp xfer with trailing /)
424 */
425static int
426fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
427{
428	struct addrinfo		hints, *res, *res0 = NULL;
429	int			error;
430	char			hbuf[NI_MAXHOST];
431	volatile sigfunc	oldintr, oldintp;
432	volatile int		s;
433	struct stat		sb;
434	int			ischunked, isproxy, rval, hcode;
435	size_t			len;
436	static size_t		bufsize;
437	static char		*xferbuf;
438	char			*cp, *ep, *buf, *savefile;
439	char			*auth, *location, *message;
440	char			*user, *pass, *host, *port, *path, *decodedpath;
441	char			*puser, *ppass;
442	off_t			hashbytes, rangestart, rangeend, entitylen;
443	int			 (*closefunc)(FILE *);
444	FILE			*fin, *fout;
445	time_t			mtime;
446	url_t			urltype;
447	in_port_t		portnum;
448
449	oldintr = oldintp = NULL;
450	closefunc = NULL;
451	fin = fout = NULL;
452	s = -1;
453	buf = savefile = NULL;
454	auth = location = message = NULL;
455	ischunked = isproxy = hcode = 0;
456	rval = 1;
457	user = pass = host = path = decodedpath = puser = ppass = NULL;
458
459#ifdef __GNUC__			/* shut up gcc warnings */
460	(void)&closefunc;
461	(void)&fin;
462	(void)&fout;
463	(void)&buf;
464	(void)&savefile;
465	(void)&rval;
466	(void)&isproxy;
467	(void)&hcode;
468	(void)&ischunked;
469	(void)&message;
470	(void)&location;
471	(void)&auth;
472	(void)&decodedpath;
473#endif
474
475	if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port,
476	    &portnum, &path) == -1)
477		goto cleanup_fetch_url;
478
479	if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
480	    && strcasecmp(host, "localhost") != 0) {
481		warnx("No support for non local file URL `%s'", url);
482		goto cleanup_fetch_url;
483	}
484
485	if (EMPTYSTRING(path)) {
486		if (urltype == FTP_URL_T) {
487			rval = fetch_ftp(url);
488			goto cleanup_fetch_url;
489		}
490		if (urltype != HTTP_URL_T || outfile == NULL)  {
491			warnx("Invalid URL (no file after host) `%s'", url);
492			goto cleanup_fetch_url;
493		}
494	}
495
496	decodedpath = xstrdup(path);
497	url_decode(decodedpath);
498
499	if (outfile)
500		savefile = xstrdup(outfile);
501	else {
502		cp = strrchr(decodedpath, '/');		/* find savefile */
503		if (cp != NULL)
504			savefile = xstrdup(cp + 1);
505		else
506			savefile = xstrdup(decodedpath);
507	}
508	if (EMPTYSTRING(savefile)) {
509		if (urltype == FTP_URL_T) {
510			rval = fetch_ftp(url);
511			goto cleanup_fetch_url;
512		}
513		warnx("no file after directory (you must specify an "
514		    "output file) `%s'", url);
515		goto cleanup_fetch_url;
516	} else {
517		if (debug)
518			fprintf(ttyout, "got savefile as `%s'\n", savefile);
519	}
520
521	restart_point = 0;
522	filesize = -1;
523	rangestart = rangeend = entitylen = -1;
524	mtime = -1;
525	if (restartautofetch) {
526		if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
527		    stat(savefile, &sb) == 0)
528			restart_point = sb.st_size;
529	}
530	if (urltype == FILE_URL_T) {		/* file:// URLs */
531		direction = "copied";
532		fin = fopen(decodedpath, "r");
533		if (fin == NULL) {
534			warn("Cannot open file `%s'", decodedpath);
535			goto cleanup_fetch_url;
536		}
537		if (fstat(fileno(fin), &sb) == 0) {
538			mtime = sb.st_mtime;
539			filesize = sb.st_size;
540		}
541		if (restart_point) {
542			if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
543				warn("Can't lseek to restart `%s'",
544				    decodedpath);
545				goto cleanup_fetch_url;
546			}
547		}
548		if (verbose) {
549			fprintf(ttyout, "Copying %s", decodedpath);
550			if (restart_point)
551				fprintf(ttyout, " (restarting at " LLF ")",
552				    (LLT)restart_point);
553			fputs("\n", ttyout);
554		}
555	} else {				/* ftp:// or http:// URLs */
556		char *leading;
557		int hasleading;
558
559		if (proxyenv == NULL) {
560			if (urltype == HTTP_URL_T)
561				proxyenv = getoptionvalue("http_proxy");
562			else if (urltype == FTP_URL_T)
563				proxyenv = getoptionvalue("ftp_proxy");
564		}
565		direction = "retrieved";
566		if (! EMPTYSTRING(proxyenv)) {			/* use proxy */
567			url_t purltype;
568			char *phost, *ppath;
569			char *pport, *no_proxy;
570
571			isproxy = 1;
572
573				/* check URL against list of no_proxied sites */
574			no_proxy = getoptionvalue("no_proxy");
575			if (! EMPTYSTRING(no_proxy)) {
576				char *np, *np_copy;
577				long np_port;
578				size_t hlen, plen;
579
580				np_copy = xstrdup(no_proxy);
581				hlen = strlen(host);
582				while ((cp = strsep(&np_copy, " ,")) != NULL) {
583					if (*cp == '\0')
584						continue;
585					if ((np = strrchr(cp, ':')) != NULL) {
586						*np = '\0';
587						np_port =
588						    strtol(np + 1, &ep, 10);
589						if (*ep != '\0')
590							continue;
591						if (np_port != portnum)
592							continue;
593					}
594					plen = strlen(cp);
595					if (hlen < plen)
596						continue;
597					if (strncasecmp(host + hlen - plen,
598					    cp, plen) == 0) {
599						isproxy = 0;
600						break;
601					}
602				}
603				FREEPTR(np_copy);
604				if (isproxy == 0 && urltype == FTP_URL_T) {
605					rval = fetch_ftp(url);
606					goto cleanup_fetch_url;
607				}
608			}
609
610			if (isproxy) {
611				if (parse_url(proxyenv, "proxy URL", &purltype,
612				    &puser, &ppass, &phost, &pport, &portnum,
613				    &ppath) == -1)
614					goto cleanup_fetch_url;
615
616				if ((purltype != HTTP_URL_T
617				     && purltype != FTP_URL_T) ||
618				    EMPTYSTRING(phost) ||
619				    (! EMPTYSTRING(ppath)
620				     && strcmp(ppath, "/") != 0)) {
621					warnx("Malformed proxy URL `%s'",
622					    proxyenv);
623					FREEPTR(phost);
624					FREEPTR(pport);
625					FREEPTR(ppath);
626					goto cleanup_fetch_url;
627				}
628				if (isipv6addr(host) &&
629				    strchr(host, '%') != NULL) {
630					warnx(
631"Scoped address notation `%s' disallowed via web proxy",
632					    host);
633					FREEPTR(phost);
634					FREEPTR(pport);
635					FREEPTR(ppath);
636					goto cleanup_fetch_url;
637				}
638
639				FREEPTR(host);
640				host = phost;
641				FREEPTR(port);
642				port = pport;
643				FREEPTR(path);
644				path = xstrdup(url);
645				FREEPTR(ppath);
646			}
647		} /* ! EMPTYSTRING(proxyenv) */
648
649		memset(&hints, 0, sizeof(hints));
650		hints.ai_flags = 0;
651		hints.ai_family = family;
652		hints.ai_socktype = SOCK_STREAM;
653		hints.ai_protocol = 0;
654		error = getaddrinfo(host, NULL, &hints, &res0);
655		if (error) {
656			warnx("%s", gai_strerror(error));
657			goto cleanup_fetch_url;
658		}
659		if (res0->ai_canonname)
660			host = res0->ai_canonname;
661
662		s = -1;
663		for (res = res0; res; res = res->ai_next) {
664			/*
665			 * see comment in hookup()
666			 */
667			ai_unmapped(res);
668			if (getnameinfo(res->ai_addr, res->ai_addrlen,
669					hbuf, sizeof(hbuf), NULL, 0,
670					NI_NUMERICHOST) != 0)
671				strncpy(hbuf, "invalid", sizeof(hbuf));
672
673			if (verbose && res != res0)
674				fprintf(ttyout, "Trying %s...\n", hbuf);
675
676			((struct sockaddr_in *)res->ai_addr)->sin_port =
677			    htons(portnum);
678			s = socket(res->ai_family, SOCK_STREAM,
679			    res->ai_protocol);
680			if (s < 0) {
681				warn("Can't create socket");
682				continue;
683			}
684
685			if (xconnect(s, res->ai_addr, res->ai_addrlen) < 0) {
686				warn("Connect to address `%s'", hbuf);
687				close(s);
688				s = -1;
689				continue;
690			}
691
692			/* success */
693			break;
694		}
695		freeaddrinfo(res0);
696
697		if (s < 0) {
698			warn("Can't connect to %s", host);
699			goto cleanup_fetch_url;
700		}
701
702		fin = fdopen(s, "r+");
703		/*
704		 * Construct and send the request.
705		 */
706		if (verbose)
707			fprintf(ttyout, "Requesting %s\n", url);
708		leading = "  (";
709		hasleading = 0;
710		if (isproxy) {
711			if (verbose) {
712				fprintf(ttyout, "%svia %s:%s", leading,
713				    host, port);
714				leading = ", ";
715				hasleading++;
716			}
717			fprintf(fin, "GET %s HTTP/1.0\r\n", path);
718			if (flushcache)
719				fprintf(fin, "Pragma: no-cache\r\n");
720		} else {
721			fprintf(fin, "GET %s HTTP/1.1\r\n", path);
722			if (strchr(host, ':')) {
723				char *h, *p;
724
725				/*
726				 * strip off IPv6 scope identifier, since it is
727				 * local to the node
728				 */
729				h = xstrdup(host);
730				if (isipv6addr(h) &&
731				    (p = strchr(h, '%')) != NULL) {
732					*p = '\0';
733				}
734				fprintf(fin, "Host: [%s]", h);
735				free(h);
736			} else
737				fprintf(fin, "Host: %s", host);
738			if (portnum != HTTP_PORT)
739				fprintf(fin, ":%u", portnum);
740			fprintf(fin, "\r\n");
741			fprintf(fin, "Accept: */*\r\n");
742			fprintf(fin, "Connection: close\r\n");
743			if (restart_point) {
744				fputs(leading, ttyout);
745				fprintf(fin, "Range: bytes=" LLF "-\r\n",
746				    (LLT)restart_point);
747				fprintf(ttyout, "restarting at " LLF,
748				    (LLT)restart_point);
749				leading = ", ";
750				hasleading++;
751			}
752			if (flushcache)
753				fprintf(fin, "Cache-Control: no-cache\r\n");
754		}
755		fprintf(fin, "User-Agent: %s/%s\r\n", FTP_PRODUCT, FTP_VERSION);
756		if (wwwauth) {
757			if (verbose) {
758				fprintf(ttyout, "%swith authorization",
759				    leading);
760				leading = ", ";
761				hasleading++;
762			}
763			fprintf(fin, "Authorization: %s\r\n", wwwauth);
764		}
765		if (proxyauth) {
766			if (verbose) {
767				fprintf(ttyout,
768				    "%swith proxy authorization", leading);
769				leading = ", ";
770				hasleading++;
771			}
772			fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
773		}
774		if (verbose && hasleading)
775			fputs(")\n", ttyout);
776		fprintf(fin, "\r\n");
777		if (fflush(fin) == EOF) {
778			warn("Writing HTTP request");
779			goto cleanup_fetch_url;
780		}
781
782				/* Read the response */
783		if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
784			warn("Receiving HTTP reply");
785			goto cleanup_fetch_url;
786		}
787		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
788			buf[--len] = '\0';
789		if (debug)
790			fprintf(ttyout, "received `%s'\n", buf);
791
792				/* Determine HTTP response code */
793		cp = strchr(buf, ' ');
794		if (cp == NULL)
795			goto improper;
796		else
797			cp++;
798		hcode = strtol(cp, &ep, 10);
799		if (*ep != '\0' && !isspace((unsigned char)*ep))
800			goto improper;
801		message = xstrdup(cp);
802
803				/* Read the rest of the header. */
804		FREEPTR(buf);
805		while (1) {
806			if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
807			    == NULL) {
808				warn("Receiving HTTP reply");
809				goto cleanup_fetch_url;
810			}
811			while (len > 0 &&
812			    (buf[len-1] == '\r' || buf[len-1] == '\n'))
813				buf[--len] = '\0';
814			if (len == 0)
815				break;
816			if (debug)
817				fprintf(ttyout, "received `%s'\n", buf);
818
819				/* Look for some headers */
820			cp = buf;
821
822#define	CONTENTLEN "Content-Length: "
823			if (strncasecmp(cp, CONTENTLEN,
824					sizeof(CONTENTLEN) - 1) == 0) {
825				cp += sizeof(CONTENTLEN) - 1;
826				filesize = STRTOLL(cp, &ep, 10);
827				if (filesize < 0 || *ep != '\0')
828					goto improper;
829				if (debug)
830					fprintf(ttyout,
831					    "parsed len as: " LLF "\n",
832					    (LLT)filesize);
833
834#define CONTENTRANGE "Content-Range: bytes "
835			} else if (strncasecmp(cp, CONTENTRANGE,
836					sizeof(CONTENTRANGE) - 1) == 0) {
837				cp += sizeof(CONTENTRANGE) - 1;
838				if (*cp == '*') {
839					ep = cp + 1;
840				}
841				else {
842					rangestart = STRTOLL(cp, &ep, 10);
843					if (rangestart < 0 || *ep != '-')
844						goto improper;
845					cp = ep + 1;
846					rangeend = STRTOLL(cp, &ep, 10);
847					if (rangeend < 0 || rangeend < rangestart)
848						goto improper;
849				}
850				if (*ep != '/')
851					goto improper;
852				cp = ep + 1;
853				if (*cp == '*') {
854					ep = cp + 1;
855				}
856				else {
857					entitylen = STRTOLL(cp, &ep, 10);
858					if (entitylen < 0)
859						goto improper;
860				}
861				if (*ep != '\0')
862					goto improper;
863
864				if (debug) {
865					fprintf(ttyout, "parsed range as: ");
866					if (rangestart == -1)
867						fprintf(ttyout, "*");
868					else
869						fprintf(ttyout, LLF "-" LLF,
870						    (LLT)rangestart,
871						    (LLT)rangeend);
872					fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
873				}
874				if (! restart_point) {
875					warnx(
876				    "Received unexpected Content-Range header");
877					goto cleanup_fetch_url;
878				}
879
880#define	LASTMOD "Last-Modified: "
881			} else if (strncasecmp(cp, LASTMOD,
882						sizeof(LASTMOD) - 1) == 0) {
883				struct tm parsed;
884				char *t;
885
886				cp += sizeof(LASTMOD) - 1;
887							/* RFC 1123 */
888				if ((t = strptime(cp,
889						"%a, %d %b %Y %H:%M:%S GMT",
890						&parsed))
891							/* RFC 850 */
892				    || (t = strptime(cp,
893						"%a, %d-%b-%y %H:%M:%S GMT",
894						&parsed))
895							/* asctime */
896				    || (t = strptime(cp,
897						"%a, %b %d %H:%M:%S %Y",
898						&parsed))) {
899					parsed.tm_isdst = -1;
900					if (*t == '\0')
901						mtime = timegm(&parsed);
902					if (debug && mtime != -1) {
903						fprintf(ttyout,
904						    "parsed date as: %s",
905						    ctime(&mtime));
906					}
907				}
908
909#define	LOCATION "Location: "
910			} else if (strncasecmp(cp, LOCATION,
911						sizeof(LOCATION) - 1) == 0) {
912				cp += sizeof(LOCATION) - 1;
913				location = xstrdup(cp);
914				if (debug)
915					fprintf(ttyout,
916					    "parsed location as: %s\n", cp);
917
918#define	TRANSENC "Transfer-Encoding: "
919			} else if (strncasecmp(cp, TRANSENC,
920						sizeof(TRANSENC) - 1) == 0) {
921				cp += sizeof(TRANSENC) - 1;
922				if (strcasecmp(cp, "binary") == 0) {
923					warnx(
924			"Bogus transfer encoding - `%s' (fetching anyway)",
925					    cp);
926					continue;
927				}
928				if (strcasecmp(cp, "chunked") != 0) {
929					warnx(
930				    "Unsupported transfer encoding - `%s'",
931					    cp);
932					goto cleanup_fetch_url;
933				}
934				ischunked++;
935				if (debug)
936					fprintf(ttyout,
937					    "using chunked encoding\n");
938
939#define	PROXYAUTH "Proxy-Authenticate: "
940			} else if (strncasecmp(cp, PROXYAUTH,
941						sizeof(PROXYAUTH) - 1) == 0) {
942				cp += sizeof(PROXYAUTH) - 1;
943				FREEPTR(auth);
944				auth = xstrdup(cp);
945				if (debug)
946					fprintf(ttyout,
947					    "parsed proxy-auth as: %s\n", cp);
948
949#define	WWWAUTH	"WWW-Authenticate: "
950			} else if (strncasecmp(cp, WWWAUTH,
951			    sizeof(WWWAUTH) - 1) == 0) {
952				cp += sizeof(WWWAUTH) - 1;
953				FREEPTR(auth);
954				auth = xstrdup(cp);
955				if (debug)
956					fprintf(ttyout,
957					    "parsed www-auth as: %s\n", cp);
958
959			}
960
961		}
962				/* finished parsing header */
963		FREEPTR(buf);
964
965		switch (hcode) {
966		case 200:
967			break;
968		case 206:
969			if (! restart_point) {
970				warnx("Not expecting partial content header");
971				goto cleanup_fetch_url;
972			}
973			break;
974		case 300:
975		case 301:
976		case 302:
977		case 303:
978		case 305:
979			if (EMPTYSTRING(location)) {
980				warnx(
981				"No redirection Location provided by server");
982				goto cleanup_fetch_url;
983			}
984			if (redirect_loop++ > 5) {
985				warnx("Too many redirections requested");
986				goto cleanup_fetch_url;
987			}
988			if (hcode == 305) {
989				if (verbose)
990					fprintf(ttyout, "Redirected via %s\n",
991					    location);
992				rval = fetch_url(url, location,
993				    proxyauth, wwwauth);
994			} else {
995				if (verbose)
996					fprintf(ttyout, "Redirected to %s\n",
997					    location);
998				rval = go_fetch(location);
999			}
1000			goto cleanup_fetch_url;
1001		case 401:
1002		case 407:
1003		    {
1004			char **authp;
1005			char *auser, *apass;
1006
1007			fprintf(ttyout, "%s\n", message);
1008			if (EMPTYSTRING(auth)) {
1009				warnx(
1010			    "No authentication challenge provided by server");
1011				goto cleanup_fetch_url;
1012			}
1013			if (hcode == 401) {
1014				authp = &wwwauth;
1015				auser = user;
1016				apass = pass;
1017			} else {
1018				authp = &proxyauth;
1019				auser = puser;
1020				apass = ppass;
1021			}
1022			if (*authp != NULL) {
1023				char reply[10];
1024
1025				fprintf(ttyout,
1026				    "Authorization failed. Retry (y/n)? ");
1027				if (fgets(reply, sizeof(reply), stdin)
1028				    == NULL) {
1029					clearerr(stdin);
1030					goto cleanup_fetch_url;
1031				} else {
1032					if (tolower(reply[0]) != 'y')
1033						goto cleanup_fetch_url;
1034				}
1035				auser = NULL;
1036				apass = NULL;
1037			}
1038			if (auth_url(auth, authp, auser, apass) == 0) {
1039				rval = fetch_url(url, proxyenv,
1040				    proxyauth, wwwauth);
1041				memset(*authp, 0, strlen(*authp));
1042				FREEPTR(*authp);
1043			}
1044			goto cleanup_fetch_url;
1045		    }
1046		default:
1047			if (message)
1048				warnx("Error retrieving file - `%s'", message);
1049			else
1050				warnx("Unknown error retrieving file");
1051			goto cleanup_fetch_url;
1052		}
1053	}		/* end of ftp:// or http:// specific setup */
1054
1055			/* Open the output file. */
1056	if (strcmp(savefile, "-") == 0) {
1057		fout = stdout;
1058	} else if (*savefile == '|') {
1059		oldintp = xsignal(SIGPIPE, SIG_IGN);
1060		fout = popen(savefile + 1, "w");
1061		if (fout == NULL) {
1062			warn("Can't run `%s'", savefile + 1);
1063			goto cleanup_fetch_url;
1064		}
1065		closefunc = pclose;
1066	} else {
1067		if ((rangeend != -1 && rangeend <= restart_point) ||
1068		    (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
1069			/* already done */
1070			if (verbose)
1071				fprintf(ttyout, "already done\n");
1072			rval = 0;
1073			goto cleanup_fetch_url;
1074		}
1075		if (restart_point && rangestart != -1) {
1076			if (entitylen != -1)
1077				filesize = entitylen;
1078			if (rangestart != restart_point) {
1079				warnx(
1080				    "Size of `%s' differs from save file `%s'",
1081				    url, savefile);
1082				goto cleanup_fetch_url;
1083			}
1084			fout = fopen(savefile, "a");
1085		} else
1086			fout = fopen(savefile, "w");
1087		if (fout == NULL) {
1088			warn("Can't open `%s'", savefile);
1089			goto cleanup_fetch_url;
1090		}
1091		closefunc = fclose;
1092	}
1093
1094			/* Trap signals */
1095	if (sigsetjmp(httpabort, 1))
1096		goto cleanup_fetch_url;
1097	(void)xsignal(SIGQUIT, psummary);
1098	oldintr = xsignal(SIGINT, aborthttp);
1099
1100	if (rcvbuf_size > bufsize) {
1101		if (xferbuf)
1102			(void)free(xferbuf);
1103		bufsize = rcvbuf_size;
1104		xferbuf = xmalloc(bufsize);
1105	}
1106
1107	bytes = 0;
1108	hashbytes = mark;
1109	progressmeter(-1);
1110
1111			/* Finally, suck down the file. */
1112	do {
1113		long chunksize;
1114
1115		chunksize = 0;
1116					/* read chunksize */
1117		if (ischunked) {
1118			if (fgets(xferbuf, bufsize, fin) == NULL) {
1119				warnx("Unexpected EOF reading chunksize");
1120				goto cleanup_fetch_url;
1121			}
1122			chunksize = strtol(xferbuf, &ep, 16);
1123
1124				/*
1125				 * XXX:	Work around bug in Apache 1.3.9 and
1126				 *	1.3.11, which incorrectly put trailing
1127				 *	space after the chunksize.
1128				 */
1129			while (*ep == ' ')
1130				ep++;
1131
1132			if (strcmp(ep, "\r\n") != 0) {
1133				warnx("Unexpected data following chunksize");
1134				goto cleanup_fetch_url;
1135			}
1136			if (debug)
1137				fprintf(ttyout, "got chunksize of " LLF "\n",
1138				    (LLT)chunksize);
1139			if (chunksize == 0)
1140				break;
1141		}
1142					/* transfer file or chunk */
1143		while (1) {
1144			struct timeval then, now, td;
1145			off_t bufrem;
1146
1147			if (rate_get)
1148				(void)gettimeofday(&then, NULL);
1149			bufrem = rate_get ? rate_get : bufsize;
1150			if (ischunked)
1151				bufrem = MIN(chunksize, bufrem);
1152			while (bufrem > 0) {
1153				len = fread(xferbuf, sizeof(char),
1154				    MIN(bufsize, bufrem), fin);
1155				if (len <= 0)
1156					goto chunkdone;
1157				bytes += len;
1158				bufrem -= len;
1159				if (fwrite(xferbuf, sizeof(char), len, fout)
1160				    != len) {
1161					warn("Writing `%s'", savefile);
1162					goto cleanup_fetch_url;
1163				}
1164				if (hash && !progress) {
1165					while (bytes >= hashbytes) {
1166						(void)putc('#', ttyout);
1167						hashbytes += mark;
1168					}
1169					(void)fflush(ttyout);
1170				}
1171				if (ischunked) {
1172					chunksize -= len;
1173					if (chunksize <= 0)
1174						break;
1175				}
1176			}
1177			if (rate_get) {
1178				while (1) {
1179					(void)gettimeofday(&now, NULL);
1180					timersub(&now, &then, &td);
1181					if (td.tv_sec > 0)
1182						break;
1183					usleep(1000000 - td.tv_usec);
1184				}
1185			}
1186			if (ischunked && chunksize <= 0)
1187				break;
1188		}
1189					/* read CRLF after chunk*/
1190 chunkdone:
1191		if (ischunked) {
1192			if (fgets(xferbuf, bufsize, fin) == NULL)
1193				break;
1194			if (strcmp(xferbuf, "\r\n") != 0) {
1195				warnx("Unexpected data following chunk");
1196				goto cleanup_fetch_url;
1197			}
1198		}
1199	} while (ischunked);
1200	if (hash && !progress && bytes > 0) {
1201		if (bytes < mark)
1202			(void)putc('#', ttyout);
1203		(void)putc('\n', ttyout);
1204	}
1205	if (ferror(fin)) {
1206		warn("Reading file");
1207		goto cleanup_fetch_url;
1208	}
1209	progressmeter(1);
1210	(void)fflush(fout);
1211	if (closefunc == fclose && mtime != -1) {
1212		struct timeval tval[2];
1213
1214		(void)gettimeofday(&tval[0], NULL);
1215		tval[1].tv_sec = mtime;
1216		tval[1].tv_usec = 0;
1217		(*closefunc)(fout);
1218		fout = NULL;
1219
1220		if (utimes(savefile, tval) == -1) {
1221			fprintf(ttyout,
1222			    "Can't change modification time to %s",
1223			    asctime(localtime(&mtime)));
1224		}
1225	}
1226	if (bytes > 0)
1227		ptransfer(0);
1228	bytes = 0;
1229
1230	rval = 0;
1231	goto cleanup_fetch_url;
1232
1233 improper:
1234	warnx("Improper response from `%s'", host);
1235
1236 cleanup_fetch_url:
1237	if (oldintr)
1238		(void)xsignal(SIGINT, oldintr);
1239	if (oldintp)
1240		(void)xsignal(SIGPIPE, oldintp);
1241	if (fin != NULL)
1242		fclose(fin);
1243	else if (s != -1)
1244		close(s);
1245	if (closefunc != NULL && fout != NULL)
1246		(*closefunc)(fout);
1247	FREEPTR(savefile);
1248	FREEPTR(user);
1249	FREEPTR(pass);
1250	FREEPTR(host);
1251	FREEPTR(port);
1252	FREEPTR(path);
1253	FREEPTR(decodedpath);
1254	FREEPTR(puser);
1255	FREEPTR(ppass);
1256	FREEPTR(buf);
1257	FREEPTR(auth);
1258	FREEPTR(location);
1259	FREEPTR(message);
1260	return (rval);
1261}
1262
1263/*
1264 * Abort a HTTP retrieval
1265 */
1266void
1267aborthttp(int notused)
1268{
1269	char msgbuf[100];
1270	int len;
1271
1272	alarmtimer(0);
1273	len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
1274	write(fileno(ttyout), msgbuf, len);
1275	siglongjmp(httpabort, 1);
1276}
1277
1278/*
1279 * Retrieve ftp URL or classic ftp argument using FTP.
1280 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1281 * is still open (e.g, ftp xfer with trailing /)
1282 */
1283static int
1284fetch_ftp(const char *url)
1285{
1286	char		*cp, *xargv[5], rempath[MAXPATHLEN];
1287	char		*host, *path, *dir, *file, *user, *pass;
1288	char		*port;
1289	int		 dirhasglob, filehasglob, oautologin, rval, type, xargc;
1290	in_port_t	 portnum;
1291	url_t		 urltype;
1292
1293	host = path = dir = file = user = pass = NULL;
1294	port = NULL;
1295	rval = 1;
1296	type = TYPE_I;
1297
1298	if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1299		if ((parse_url(url, "URL", &urltype, &user, &pass,
1300		    &host, &port, &portnum, &path) == -1) ||
1301		    (user != NULL && *user == '\0') ||
1302		    (pass != NULL && *pass == '\0') ||
1303		    EMPTYSTRING(host)) {
1304			warnx("Invalid URL `%s'", url);
1305			goto cleanup_fetch_ftp;
1306		}
1307		url_decode(user);
1308		url_decode(pass);
1309		/*
1310		 * Note: Don't url_decode(path) here.  We need to keep the
1311		 * distinction between "/" and "%2F" until later.
1312		 */
1313
1314					/* check for trailing ';type=[aid]' */
1315		if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1316			if (strcasecmp(cp, ";type=a") == 0)
1317				type = TYPE_A;
1318			else if (strcasecmp(cp, ";type=i") == 0)
1319				type = TYPE_I;
1320			else if (strcasecmp(cp, ";type=d") == 0) {
1321				warnx(
1322			    "Directory listing via a URL is not supported");
1323				goto cleanup_fetch_ftp;
1324			} else {
1325				warnx("Invalid suffix `%s' in URL `%s'", cp,
1326				    url);
1327				goto cleanup_fetch_ftp;
1328			}
1329			*cp = 0;
1330		}
1331	} else {			/* classic style `[user@]host:[file]' */
1332		urltype = CLASSIC_URL_T;
1333		host = xstrdup(url);
1334		cp = strchr(host, '@');
1335		if (cp != NULL) {
1336			*cp = '\0';
1337			user = host;
1338			anonftp = 0;	/* disable anonftp */
1339			host = xstrdup(cp + 1);
1340		}
1341		cp = strchr(host, ':');
1342		if (cp != NULL) {
1343			*cp = '\0';
1344			path = xstrdup(cp + 1);
1345		}
1346	}
1347	if (EMPTYSTRING(host))
1348		goto cleanup_fetch_ftp;
1349
1350			/* Extract the file and (if present) directory name. */
1351	dir = path;
1352	if (! EMPTYSTRING(dir)) {
1353		/*
1354		 * If we are dealing with classic `[user@]host:[path]' syntax,
1355		 * then a path of the form `/file' (resulting from input of the
1356		 * form `host:/file') means that we should do "CWD /" before
1357		 * retrieving the file.  So we set dir="/" and file="file".
1358		 *
1359		 * But if we are dealing with URLs like `ftp://host/path' then
1360		 * a path of the form `/file' (resulting from a URL of the form
1361		 * `ftp://host//file') means that we should do `CWD ' (with an
1362		 * empty argument) before retrieving the file.  So we set
1363		 * dir="" and file="file".
1364		 *
1365		 * If the path does not contain / at all, we set dir=NULL.
1366		 * (We get a path without any slashes if we are dealing with
1367		 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1368		 *
1369		 * In all other cases, we set dir to a string that does not
1370		 * include the final '/' that separates the dir part from the
1371		 * file part of the path.  (This will be the empty string if
1372		 * and only if we are dealing with a path of the form `/file'
1373		 * resulting from an URL of the form `ftp://host//file'.)
1374		 */
1375		cp = strrchr(dir, '/');
1376		if (cp == dir && urltype == CLASSIC_URL_T) {
1377			file = cp + 1;
1378			dir = "/";
1379		} else if (cp != NULL) {
1380			*cp++ = '\0';
1381			file = cp;
1382		} else {
1383			file = dir;
1384			dir = NULL;
1385		}
1386	} else
1387		dir = NULL;
1388	if (urltype == FTP_URL_T && file != NULL) {
1389		url_decode(file);
1390		/* but still don't url_decode(dir) */
1391	}
1392	if (debug)
1393		fprintf(ttyout,
1394		    "fetch_ftp: user `%s' pass `%s' host %s port %s "
1395		    "path `%s' dir `%s' file `%s'\n",
1396		    user ? user : "<null>", pass ? pass : "<null>",
1397		    host ? host : "<null>", port ? port : "<null>",
1398		    path ? path : "<null>",
1399		    dir ? dir : "<null>", file ? file : "<null>");
1400
1401	dirhasglob = filehasglob = 0;
1402	if (doglob && urltype == CLASSIC_URL_T) {
1403		if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
1404			dirhasglob = 1;
1405		if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
1406			filehasglob = 1;
1407	}
1408
1409			/* Set up the connection */
1410	if (connected)
1411		disconnect(0, NULL);
1412	xargv[0] = (char *)getprogname();	/* XXX discards const */
1413	xargv[1] = host;
1414	xargv[2] = NULL;
1415	xargc = 2;
1416	if (port) {
1417		xargv[2] = port;
1418		xargv[3] = NULL;
1419		xargc = 3;
1420	}
1421	oautologin = autologin;
1422		/* don't autologin in setpeer(), use ftp_login() below */
1423	autologin = 0;
1424	setpeer(xargc, xargv);
1425	autologin = oautologin;
1426	if ((connected == 0) ||
1427	    (connected == 1 && !ftp_login(host, user, pass))) {
1428		warnx("Can't connect or login to host `%s'", host);
1429		goto cleanup_fetch_ftp;
1430	}
1431
1432	switch (type) {
1433	case TYPE_A:
1434		setascii(1, xargv);
1435		break;
1436	case TYPE_I:
1437		setbinary(1, xargv);
1438		break;
1439	default:
1440		errx(1, "fetch_ftp: unknown transfer type %d", type);
1441	}
1442
1443		/*
1444		 * Change directories, if necessary.
1445		 *
1446		 * Note: don't use EMPTYSTRING(dir) below, because
1447		 * dir=="" means something different from dir==NULL.
1448		 */
1449	if (dir != NULL && !dirhasglob) {
1450		char *nextpart;
1451
1452		/*
1453		 * If we are dealing with a classic `[user@]host:[path]'
1454		 * (urltype is CLASSIC_URL_T) then we have a raw directory
1455		 * name (not encoded in any way) and we can change
1456		 * directories in one step.
1457		 *
1458		 * If we are dealing with an `ftp://host/path' URL
1459		 * (urltype is FTP_URL_T), then RFC 1738 says we need to
1460		 * send a separate CWD command for each unescaped "/"
1461		 * in the path, and we have to interpret %hex escaping
1462		 * *after* we find the slashes.  It's possible to get
1463		 * empty components here, (from multiple adjacent
1464		 * slashes in the path) and RFC 1738 says that we should
1465		 * still do `CWD ' (with a null argument) in such cases.
1466		 *
1467		 * Many ftp servers don't support `CWD ', so if there's an
1468		 * error performing that command, bail out with a descriptive
1469		 * message.
1470		 *
1471		 * Examples:
1472		 *
1473		 * host:			dir="", urltype=CLASSIC_URL_T
1474		 *		logged in (to default directory)
1475		 * host:file			dir=NULL, urltype=CLASSIC_URL_T
1476		 *		"RETR file"
1477		 * host:dir/			dir="dir", urltype=CLASSIC_URL_T
1478		 *		"CWD dir", logged in
1479		 * ftp://host/			dir="", urltype=FTP_URL_T
1480		 *		logged in (to default directory)
1481		 * ftp://host/dir/		dir="dir", urltype=FTP_URL_T
1482		 *		"CWD dir", logged in
1483		 * ftp://host/file		dir=NULL, urltype=FTP_URL_T
1484		 *		"RETR file"
1485		 * ftp://host//file		dir="", urltype=FTP_URL_T
1486		 *		"CWD ", "RETR file"
1487		 * host:/file			dir="/", urltype=CLASSIC_URL_T
1488		 *		"CWD /", "RETR file"
1489		 * ftp://host///file		dir="/", urltype=FTP_URL_T
1490		 *		"CWD ", "CWD ", "RETR file"
1491		 * ftp://host/%2F/file		dir="%2F", urltype=FTP_URL_T
1492		 *		"CWD /", "RETR file"
1493		 * ftp://host/foo/file		dir="foo", urltype=FTP_URL_T
1494		 *		"CWD foo", "RETR file"
1495		 * ftp://host/foo/bar/file	dir="foo/bar"
1496		 *		"CWD foo", "CWD bar", "RETR file"
1497		 * ftp://host//foo/bar/file	dir="/foo/bar"
1498		 *		"CWD ", "CWD foo", "CWD bar", "RETR file"
1499		 * ftp://host/foo//bar/file	dir="foo//bar"
1500		 *		"CWD foo", "CWD ", "CWD bar", "RETR file"
1501		 * ftp://host/%2F/foo/bar/file	dir="%2F/foo/bar"
1502		 *		"CWD /", "CWD foo", "CWD bar", "RETR file"
1503		 * ftp://host/%2Ffoo/bar/file	dir="%2Ffoo/bar"
1504		 *		"CWD /foo", "CWD bar", "RETR file"
1505		 * ftp://host/%2Ffoo%2Fbar/file	dir="%2Ffoo%2Fbar"
1506		 *		"CWD /foo/bar", "RETR file"
1507		 * ftp://host/%2Ffoo%2Fbar%2Ffile	dir=NULL
1508		 *		"RETR /foo/bar/file"
1509		 *
1510		 * Note that we don't need `dir' after this point.
1511		 */
1512		do {
1513			if (urltype == FTP_URL_T) {
1514				nextpart = strchr(dir, '/');
1515				if (nextpart) {
1516					*nextpart = '\0';
1517					nextpart++;
1518				}
1519				url_decode(dir);
1520			} else
1521				nextpart = NULL;
1522			if (debug)
1523				fprintf(ttyout, "dir `%s', nextpart `%s'\n",
1524				    dir ? dir : "<null>",
1525				    nextpart ? nextpart : "<null>");
1526			if (urltype == FTP_URL_T || *dir != '\0') {
1527				xargv[0] = "cd";
1528				xargv[1] = dir;
1529				xargv[2] = NULL;
1530				dirchange = 0;
1531				cd(2, xargv);
1532				if (! dirchange) {
1533					if (*dir == '\0' && code == 500)
1534						fprintf(stderr,
1535"\n"
1536"ftp: The `CWD ' command (without a directory), which is required by\n"
1537"     RFC 1738 to support the empty directory in the URL pathname (`//'),\n"
1538"     conflicts with the server's conformance to RFC 959.\n"
1539"     Try the same URL without the `//' in the URL pathname.\n"
1540"\n");
1541					goto cleanup_fetch_ftp;
1542				}
1543			}
1544			dir = nextpart;
1545		} while (dir != NULL);
1546	}
1547
1548	if (EMPTYSTRING(file)) {
1549		rval = -1;
1550		goto cleanup_fetch_ftp;
1551	}
1552
1553	if (dirhasglob) {
1554		(void)strlcpy(rempath, dir,	sizeof(rempath));
1555		(void)strlcat(rempath, "/",	sizeof(rempath));
1556		(void)strlcat(rempath, file,	sizeof(rempath));
1557		file = rempath;
1558	}
1559
1560			/* Fetch the file(s). */
1561	xargc = 2;
1562	xargv[0] = "get";
1563	xargv[1] = file;
1564	xargv[2] = NULL;
1565	if (dirhasglob || filehasglob) {
1566		int ointeractive;
1567
1568		ointeractive = interactive;
1569		interactive = 0;
1570		xargv[0] = "mget";
1571		mget(xargc, xargv);
1572		interactive = ointeractive;
1573	} else {
1574		if (outfile == NULL) {
1575			cp = strrchr(file, '/');	/* find savefile */
1576			if (cp != NULL)
1577				outfile = cp + 1;
1578			else
1579				outfile = file;
1580		}
1581		xargv[2] = (char *)outfile;
1582		xargv[3] = NULL;
1583		xargc++;
1584		if (restartautofetch)
1585			reget(xargc, xargv);
1586		else
1587			get(xargc, xargv);
1588	}
1589
1590	if ((code / 100) == COMPLETE)
1591		rval = 0;
1592
1593 cleanup_fetch_ftp:
1594	FREEPTR(host);
1595	FREEPTR(path);
1596	FREEPTR(user);
1597	FREEPTR(pass);
1598	return (rval);
1599}
1600
1601/*
1602 * Retrieve the given file to outfile.
1603 * Supports arguments of the form:
1604 *	"host:path", "ftp://host/path"	if $ftpproxy, call fetch_url() else
1605 *					call fetch_ftp()
1606 *	"http://host/path"		call fetch_url() to use HTTP
1607 *	"file:///path"			call fetch_url() to copy
1608 *	"about:..."			print a message
1609 *
1610 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1611 * is still open (e.g, ftp xfer with trailing /)
1612 */
1613static int
1614go_fetch(const char *url)
1615{
1616	char *proxy;
1617
1618	/*
1619	 * Check for about:*
1620	 */
1621	if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
1622		url += sizeof(ABOUT_URL) -1;
1623		if (strcasecmp(url, "ftp") == 0) {
1624			fputs(
1625"This version of ftp has been enhanced by Luke Mewburn <lukem@netbsd.org>\n"
1626"for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
1627		} else if (strcasecmp(url, "lukem") == 0) {
1628			fputs(
1629"Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1630"Please email feedback to <lukem@netbsd.org>.\n", ttyout);
1631		} else if (strcasecmp(url, "netbsd") == 0) {
1632			fputs(
1633"NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1634"For more information, see http://www.netbsd.org/index.html\n", ttyout);
1635		} else if (strcasecmp(url, "version") == 0) {
1636			fprintf(ttyout, "Version: %s %s%s\n",
1637			    FTP_PRODUCT, FTP_VERSION,
1638#ifdef INET6
1639			    ""
1640#else
1641			    " (-IPv6)"
1642#endif
1643			);
1644		} else {
1645			fprintf(ttyout, "`%s' is an interesting topic.\n", url);
1646		}
1647		fputs("\n", ttyout);
1648		return (0);
1649	}
1650
1651	/*
1652	 * Check for file:// and http:// URLs.
1653	 */
1654	if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1655	    strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0)
1656		return (fetch_url(url, NULL, NULL, NULL));
1657
1658	/*
1659	 * Try FTP URL-style and host:file arguments next.
1660	 * If ftpproxy is set with an FTP URL, use fetch_url()
1661	 * Othewise, use fetch_ftp().
1662	 */
1663	proxy = getoptionvalue("ftp_proxy");
1664	if (!EMPTYSTRING(proxy) &&
1665	    strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0)
1666		return (fetch_url(url, NULL, NULL, NULL));
1667
1668	return (fetch_ftp(url));
1669}
1670
1671/*
1672 * Retrieve multiple files from the command line,
1673 * calling go_fetch() for each file.
1674 *
1675 * If an ftp path has a trailing "/", the path will be cd-ed into and
1676 * the connection remains open, and the function will return -1
1677 * (to indicate the connection is alive).
1678 * If an error occurs the return value will be the offset+1 in
1679 * argv[] of the file that caused a problem (i.e, argv[x]
1680 * returns x+1)
1681 * Otherwise, 0 is returned if all files retrieved successfully.
1682 */
1683int
1684auto_fetch(int argc, char *argv[])
1685{
1686	volatile int	argpos;
1687	int		rval;
1688
1689	argpos = 0;
1690
1691	if (sigsetjmp(toplevel, 1)) {
1692		if (connected)
1693			disconnect(0, NULL);
1694		return (argpos + 1);
1695	}
1696	(void)xsignal(SIGINT, intr);
1697	(void)xsignal(SIGPIPE, lostpeer);
1698
1699	/*
1700	 * Loop through as long as there's files to fetch.
1701	 */
1702	for (rval = 0; (rval == 0) && (argpos < argc); argpos++) {
1703		if (strchr(argv[argpos], ':') == NULL)
1704			break;
1705		redirect_loop = 0;
1706		if (!anonftp)
1707			anonftp = 2;	/* Handle "automatic" transfers. */
1708		rval = go_fetch(argv[argpos]);
1709		if (outfile != NULL && strcmp(outfile, "-") != 0
1710		    && outfile[0] != '|')
1711			outfile = NULL;
1712		if (rval > 0)
1713			rval = argpos + 1;
1714	}
1715
1716	if (connected && rval != -1)
1717		disconnect(0, NULL);
1718	return (rval);
1719}
1720
1721
1722int
1723auto_put(int argc, char **argv, const char *uploadserver)
1724{
1725	char	*uargv[4], *path, *pathsep;
1726	int	 uargc, rval, len;
1727
1728	uargc = 0;
1729	uargv[uargc++] = "mput";
1730	uargv[uargc++] = argv[0];
1731	uargv[2] = uargv[3] = NULL;
1732	pathsep = NULL;
1733	rval = 1;
1734
1735	if (debug)
1736		fprintf(ttyout, "auto_put: target `%s'\n", uploadserver);
1737
1738	path = xstrdup(uploadserver);
1739	len = strlen(path);
1740	if (path[len - 1] != '/' && path[len - 1] != ':') {
1741			/*
1742			 * make sure we always pass a directory to auto_fetch
1743			 */
1744		if (argc > 1) {		/* more than one file to upload */
1745			int len;
1746
1747			len = strlen(uploadserver) + 2;	/* path + "/" + "\0" */
1748			free(path);
1749			path = (char *)xmalloc(len);
1750			(void)strlcpy(path, uploadserver, len);
1751			(void)strlcat(path, "/", len);
1752		} else {		/* single file to upload */
1753			uargv[0] = "put";
1754			pathsep = strrchr(path, '/');
1755			if (pathsep == NULL) {
1756				pathsep = strrchr(path, ':');
1757				if (pathsep == NULL) {
1758					warnx("Invalid URL `%s'", path);
1759					goto cleanup_auto_put;
1760				}
1761				pathsep++;
1762				uargv[2] = xstrdup(pathsep);
1763				pathsep[0] = '/';
1764			} else
1765				uargv[2] = xstrdup(pathsep + 1);
1766			pathsep[1] = '\0';
1767			uargc++;
1768		}
1769	}
1770	if (debug)
1771		fprintf(ttyout, "auto_put: URL `%s' argv[2] `%s'\n",
1772		    path, uargv[2] ? uargv[2] : "<null>");
1773
1774			/* connect and cwd */
1775	rval = auto_fetch(1, &path);
1776	free(path);
1777	if(rval >= 0)
1778		goto cleanup_auto_put;
1779
1780			/* XXX : is this the best way? */
1781	if (uargc == 3) {
1782		uargv[1] = argv[0];
1783		put(uargc, uargv);
1784		goto cleanup_auto_put;
1785	}
1786
1787	for(; argv[0] != NULL; argv++) {
1788		uargv[1] = argv[0];
1789		mput(uargc, uargv);
1790	}
1791	rval = 0;
1792
1793 cleanup_auto_put:
1794	FREEPTR(uargv[2]);
1795	return (rval);
1796}
1797