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