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