xref: /minix3/usr.bin/ftp/fetch.c (revision 84d9c625bfea59e274550651111ae9edfdc40fbd)
1*84d9c625SLionel Sambuc /*	$NetBSD: fetch.c,v 1.205 2013/11/07 02:06:51 christos Exp $	*/
204203a83SThomas Cort 
304203a83SThomas Cort /*-
404203a83SThomas Cort  * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
504203a83SThomas Cort  * All rights reserved.
604203a83SThomas Cort  *
704203a83SThomas Cort  * This code is derived from software contributed to The NetBSD Foundation
804203a83SThomas Cort  * by Luke Mewburn.
904203a83SThomas Cort  *
1004203a83SThomas Cort  * This code is derived from software contributed to The NetBSD Foundation
1104203a83SThomas Cort  * by Scott Aaron Bamford.
1204203a83SThomas Cort  *
1304203a83SThomas Cort  * Redistribution and use in source and binary forms, with or without
1404203a83SThomas Cort  * modification, are permitted provided that the following conditions
1504203a83SThomas Cort  * are met:
1604203a83SThomas Cort  * 1. Redistributions of source code must retain the above copyright
1704203a83SThomas Cort  *    notice, this list of conditions and the following disclaimer.
1804203a83SThomas Cort  * 2. Redistributions in binary form must reproduce the above copyright
1904203a83SThomas Cort  *    notice, this list of conditions and the following disclaimer in the
2004203a83SThomas Cort  *    documentation and/or other materials provided with the distribution.
2104203a83SThomas Cort  *
2204203a83SThomas Cort  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2304203a83SThomas Cort  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2404203a83SThomas Cort  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2504203a83SThomas Cort  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2604203a83SThomas Cort  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2704203a83SThomas Cort  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2804203a83SThomas Cort  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2904203a83SThomas Cort  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3004203a83SThomas Cort  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3104203a83SThomas Cort  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3204203a83SThomas Cort  * POSSIBILITY OF SUCH DAMAGE.
3304203a83SThomas Cort  */
3404203a83SThomas Cort 
3504203a83SThomas Cort #include <sys/cdefs.h>
3604203a83SThomas Cort #ifndef lint
37*84d9c625SLionel Sambuc __RCSID("$NetBSD: fetch.c,v 1.205 2013/11/07 02:06:51 christos Exp $");
3804203a83SThomas Cort #endif /* not lint */
3904203a83SThomas Cort 
4004203a83SThomas Cort /*
4104203a83SThomas Cort  * FTP User Program -- Command line file retrieval
4204203a83SThomas Cort  */
4304203a83SThomas Cort 
4404203a83SThomas Cort #include <sys/types.h>
4504203a83SThomas Cort #include <sys/param.h>
4604203a83SThomas Cort #include <sys/socket.h>
4704203a83SThomas Cort #include <sys/stat.h>
4804203a83SThomas Cort #include <sys/time.h>
4904203a83SThomas Cort 
5004203a83SThomas Cort #include <netinet/in.h>
5104203a83SThomas Cort 
5204203a83SThomas Cort #include <arpa/ftp.h>
5304203a83SThomas Cort #include <arpa/inet.h>
5404203a83SThomas Cort 
5504203a83SThomas Cort #include <assert.h>
5604203a83SThomas Cort #include <ctype.h>
5704203a83SThomas Cort #include <err.h>
5804203a83SThomas Cort #include <errno.h>
5904203a83SThomas Cort #include <netdb.h>
6004203a83SThomas Cort #include <fcntl.h>
6104203a83SThomas Cort #include <stdio.h>
6204203a83SThomas Cort #include <stdlib.h>
6304203a83SThomas Cort #include <string.h>
6404203a83SThomas Cort #include <unistd.h>
6504203a83SThomas Cort #include <time.h>
6604203a83SThomas Cort 
6704203a83SThomas Cort #include "ssl.h"
6804203a83SThomas Cort #include "ftp_var.h"
6904203a83SThomas Cort #include "version.h"
7004203a83SThomas Cort 
7104203a83SThomas Cort typedef enum {
7204203a83SThomas Cort 	UNKNOWN_URL_T=-1,
7304203a83SThomas Cort 	HTTP_URL_T,
7404203a83SThomas Cort #ifdef WITH_SSL
7504203a83SThomas Cort 	HTTPS_URL_T,
7604203a83SThomas Cort #endif
7704203a83SThomas Cort 	FTP_URL_T,
7804203a83SThomas Cort 	FILE_URL_T,
7904203a83SThomas Cort 	CLASSIC_URL_T
8004203a83SThomas Cort } url_t;
8104203a83SThomas Cort 
8204203a83SThomas Cort __dead static void	aborthttp(int);
83*84d9c625SLionel Sambuc __dead static void	timeouthttp(int);
8404203a83SThomas Cort #ifndef NO_AUTH
8504203a83SThomas Cort static int	auth_url(const char *, char **, const char *, const char *);
8604203a83SThomas Cort static void	base64_encode(const unsigned char *, size_t, unsigned char *);
8704203a83SThomas Cort #endif
8804203a83SThomas Cort static int	go_fetch(const char *);
8904203a83SThomas Cort static int	fetch_ftp(const char *);
9004203a83SThomas Cort static int	fetch_url(const char *, const char *, char *, char *);
9104203a83SThomas Cort static const char *match_token(const char **, const char *);
9204203a83SThomas Cort static int	parse_url(const char *, const char *, url_t *, char **,
9304203a83SThomas Cort 			    char **, char **, char **, in_port_t *, char **);
9404203a83SThomas Cort static void	url_decode(char *);
9504203a83SThomas Cort 
9604203a83SThomas Cort static int	redirect_loop;
9704203a83SThomas Cort 
9804203a83SThomas Cort 
9904203a83SThomas Cort #define	STRNEQUAL(a,b)	(strncasecmp((a), (b), sizeof((b))-1) == 0)
10004203a83SThomas Cort #define	ISLWS(x)	((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
10104203a83SThomas Cort #define	SKIPLWS(x)	do { while (ISLWS((*x))) x++; } while (0)
10204203a83SThomas Cort 
10304203a83SThomas Cort 
10404203a83SThomas Cort #define	ABOUT_URL	"about:"	/* propaganda */
10504203a83SThomas Cort #define	FILE_URL	"file://"	/* file URL prefix */
10604203a83SThomas Cort #define	FTP_URL		"ftp://"	/* ftp URL prefix */
10704203a83SThomas Cort #define	HTTP_URL	"http://"	/* http URL prefix */
10804203a83SThomas Cort #ifdef WITH_SSL
10904203a83SThomas Cort #define	HTTPS_URL	"https://"	/* https URL prefix */
11004203a83SThomas Cort 
11104203a83SThomas Cort #define	IS_HTTP_TYPE(urltype) \
11204203a83SThomas Cort 	(((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T))
11304203a83SThomas Cort #else
11404203a83SThomas Cort #define	IS_HTTP_TYPE(urltype) \
11504203a83SThomas Cort 	((urltype) == HTTP_URL_T)
11604203a83SThomas Cort #endif
11704203a83SThomas Cort 
11804203a83SThomas Cort /*
11904203a83SThomas Cort  * Determine if token is the next word in buf (case insensitive).
12004203a83SThomas Cort  * If so, advance buf past the token and any trailing LWS, and
12104203a83SThomas Cort  * return a pointer to the token (in buf).  Otherwise, return NULL.
12204203a83SThomas Cort  * token may be preceded by LWS.
12304203a83SThomas Cort  * token must be followed by LWS or NUL.  (I.e, don't partial match).
12404203a83SThomas Cort  */
12504203a83SThomas Cort static const char *
12604203a83SThomas Cort match_token(const char **buf, const char *token)
12704203a83SThomas Cort {
12804203a83SThomas Cort 	const char	*p, *orig;
12904203a83SThomas Cort 	size_t		tlen;
13004203a83SThomas Cort 
13104203a83SThomas Cort 	tlen = strlen(token);
13204203a83SThomas Cort 	p = *buf;
13304203a83SThomas Cort 	SKIPLWS(p);
13404203a83SThomas Cort 	orig = p;
13504203a83SThomas Cort 	if (strncasecmp(p, token, tlen) != 0)
13604203a83SThomas Cort 		return NULL;
13704203a83SThomas Cort 	p += tlen;
13804203a83SThomas Cort 	if (*p != '\0' && !ISLWS(*p))
13904203a83SThomas Cort 		return NULL;
14004203a83SThomas Cort 	SKIPLWS(p);
14104203a83SThomas Cort 	orig = *buf;
14204203a83SThomas Cort 	*buf = p;
14304203a83SThomas Cort 	return orig;
14404203a83SThomas Cort }
14504203a83SThomas Cort 
14604203a83SThomas Cort #ifndef NO_AUTH
14704203a83SThomas Cort /*
14804203a83SThomas Cort  * Generate authorization response based on given authentication challenge.
14904203a83SThomas Cort  * Returns -1 if an error occurred, otherwise 0.
15004203a83SThomas Cort  * Sets response to a malloc(3)ed string; caller should free.
15104203a83SThomas Cort  */
15204203a83SThomas Cort static int
15304203a83SThomas Cort auth_url(const char *challenge, char **response, const char *guser,
15404203a83SThomas Cort 	const char *gpass)
15504203a83SThomas Cort {
15604203a83SThomas Cort 	const char	*cp, *scheme, *errormsg;
15704203a83SThomas Cort 	char		*ep, *clear, *realm;
15804203a83SThomas Cort 	char		 uuser[BUFSIZ], *gotpass;
15904203a83SThomas Cort 	const char	*upass;
16004203a83SThomas Cort 	int		 rval;
16104203a83SThomas Cort 	size_t		 len, clen, rlen;
16204203a83SThomas Cort 
16304203a83SThomas Cort 	*response = NULL;
16404203a83SThomas Cort 	clear = realm = NULL;
16504203a83SThomas Cort 	rval = -1;
16604203a83SThomas Cort 	cp = challenge;
16704203a83SThomas Cort 	scheme = "Basic";	/* only support Basic authentication */
16804203a83SThomas Cort 	gotpass = NULL;
16904203a83SThomas Cort 
17004203a83SThomas Cort 	DPRINTF("auth_url: challenge `%s'\n", challenge);
17104203a83SThomas Cort 
17204203a83SThomas Cort 	if (! match_token(&cp, scheme)) {
17304203a83SThomas Cort 		warnx("Unsupported authentication challenge `%s'",
17404203a83SThomas Cort 		    challenge);
17504203a83SThomas Cort 		goto cleanup_auth_url;
17604203a83SThomas Cort 	}
17704203a83SThomas Cort 
17804203a83SThomas Cort #define	REALM "realm=\""
17904203a83SThomas Cort 	if (STRNEQUAL(cp, REALM))
18004203a83SThomas Cort 		cp += sizeof(REALM) - 1;
18104203a83SThomas Cort 	else {
18204203a83SThomas Cort 		warnx("Unsupported authentication challenge `%s'",
18304203a83SThomas Cort 		    challenge);
18404203a83SThomas Cort 		goto cleanup_auth_url;
18504203a83SThomas Cort 	}
18604203a83SThomas Cort /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
18704203a83SThomas Cort 	if ((ep = strchr(cp, '\"')) != NULL) {
18804203a83SThomas Cort 		len = ep - cp;
18904203a83SThomas Cort 		realm = (char *)ftp_malloc(len + 1);
19004203a83SThomas Cort 		(void)strlcpy(realm, cp, len + 1);
19104203a83SThomas Cort 	} else {
19204203a83SThomas Cort 		warnx("Unsupported authentication challenge `%s'",
19304203a83SThomas Cort 		    challenge);
19404203a83SThomas Cort 		goto cleanup_auth_url;
19504203a83SThomas Cort 	}
19604203a83SThomas Cort 
19704203a83SThomas Cort 	fprintf(ttyout, "Username for `%s': ", realm);
19804203a83SThomas Cort 	if (guser != NULL) {
19904203a83SThomas Cort 		(void)strlcpy(uuser, guser, sizeof(uuser));
20004203a83SThomas Cort 		fprintf(ttyout, "%s\n", uuser);
20104203a83SThomas Cort 	} else {
20204203a83SThomas Cort 		(void)fflush(ttyout);
20304203a83SThomas Cort 		if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) {
20404203a83SThomas Cort 			warnx("%s; can't authenticate", errormsg);
20504203a83SThomas Cort 			goto cleanup_auth_url;
20604203a83SThomas Cort 		}
20704203a83SThomas Cort 	}
20804203a83SThomas Cort 	if (gpass != NULL)
20904203a83SThomas Cort 		upass = gpass;
21004203a83SThomas Cort 	else {
21104203a83SThomas Cort 		gotpass = getpass("Password: ");
21204203a83SThomas Cort 		if (gotpass == NULL) {
21304203a83SThomas Cort 			warnx("Can't read password");
21404203a83SThomas Cort 			goto cleanup_auth_url;
21504203a83SThomas Cort 		}
21604203a83SThomas Cort 		upass = gotpass;
21704203a83SThomas Cort 	}
21804203a83SThomas Cort 
21904203a83SThomas Cort 	clen = strlen(uuser) + strlen(upass) + 2;	/* user + ":" + pass + "\0" */
22004203a83SThomas Cort 	clear = (char *)ftp_malloc(clen);
22104203a83SThomas Cort 	(void)strlcpy(clear, uuser, clen);
22204203a83SThomas Cort 	(void)strlcat(clear, ":", clen);
22304203a83SThomas Cort 	(void)strlcat(clear, upass, clen);
22404203a83SThomas Cort 	if (gotpass)
22504203a83SThomas Cort 		memset(gotpass, 0, strlen(gotpass));
22604203a83SThomas Cort 
22704203a83SThomas Cort 						/* scheme + " " + enc + "\0" */
22804203a83SThomas Cort 	rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
22904203a83SThomas Cort 	*response = (char *)ftp_malloc(rlen);
23004203a83SThomas Cort 	(void)strlcpy(*response, scheme, rlen);
23104203a83SThomas Cort 	len = strlcat(*response, " ", rlen);
23204203a83SThomas Cort 			/* use  `clen - 1'  to not encode the trailing NUL */
23304203a83SThomas Cort 	base64_encode((unsigned char *)clear, clen - 1,
23404203a83SThomas Cort 	    (unsigned char *)*response + len);
23504203a83SThomas Cort 	memset(clear, 0, clen);
23604203a83SThomas Cort 	rval = 0;
23704203a83SThomas Cort 
23804203a83SThomas Cort  cleanup_auth_url:
23904203a83SThomas Cort 	FREEPTR(clear);
24004203a83SThomas Cort 	FREEPTR(realm);
24104203a83SThomas Cort 	return (rval);
24204203a83SThomas Cort }
24304203a83SThomas Cort 
24404203a83SThomas Cort /*
24504203a83SThomas Cort  * Encode len bytes starting at clear using base64 encoding into encoded,
24604203a83SThomas Cort  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
24704203a83SThomas Cort  */
24804203a83SThomas Cort static void
24904203a83SThomas Cort base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
25004203a83SThomas Cort {
25104203a83SThomas Cort 	static const unsigned char enc[] =
25204203a83SThomas Cort 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
25304203a83SThomas Cort 	unsigned char	*cp;
25404203a83SThomas Cort 	size_t	 i;
25504203a83SThomas Cort 
25604203a83SThomas Cort 	cp = encoded;
25704203a83SThomas Cort 	for (i = 0; i < len; i += 3) {
25804203a83SThomas Cort 		*(cp++) = enc[((clear[i + 0] >> 2))];
25904203a83SThomas Cort 		*(cp++) = enc[((clear[i + 0] << 4) & 0x30)
26004203a83SThomas Cort 			    | ((clear[i + 1] >> 4) & 0x0f)];
26104203a83SThomas Cort 		*(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
26204203a83SThomas Cort 			    | ((clear[i + 2] >> 6) & 0x03)];
26304203a83SThomas Cort 		*(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
26404203a83SThomas Cort 	}
26504203a83SThomas Cort 	*cp = '\0';
26604203a83SThomas Cort 	while (i-- > len)
26704203a83SThomas Cort 		*(--cp) = '=';
26804203a83SThomas Cort }
26904203a83SThomas Cort #endif
27004203a83SThomas Cort 
27104203a83SThomas Cort /*
27204203a83SThomas Cort  * Decode %xx escapes in given string, `in-place'.
27304203a83SThomas Cort  */
27404203a83SThomas Cort static void
27504203a83SThomas Cort url_decode(char *url)
27604203a83SThomas Cort {
27704203a83SThomas Cort 	unsigned char *p, *q;
27804203a83SThomas Cort 
27904203a83SThomas Cort 	if (EMPTYSTRING(url))
28004203a83SThomas Cort 		return;
28104203a83SThomas Cort 	p = q = (unsigned char *)url;
28204203a83SThomas Cort 
28304203a83SThomas Cort #define	HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
28404203a83SThomas Cort 	while (*p) {
28504203a83SThomas Cort 		if (p[0] == '%'
28604203a83SThomas Cort 		    && p[1] && isxdigit((unsigned char)p[1])
28704203a83SThomas Cort 		    && p[2] && isxdigit((unsigned char)p[2])) {
28804203a83SThomas Cort 			*q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
28904203a83SThomas Cort 			p+=3;
29004203a83SThomas Cort 		} else
29104203a83SThomas Cort 			*q++ = *p++;
29204203a83SThomas Cort 	}
29304203a83SThomas Cort 	*q = '\0';
29404203a83SThomas Cort }
29504203a83SThomas Cort 
29604203a83SThomas Cort 
29704203a83SThomas Cort /*
29804203a83SThomas Cort  * Parse URL of form (per RFC 3986):
29904203a83SThomas Cort  *	<type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
30004203a83SThomas Cort  * Returns -1 if a parse error occurred, otherwise 0.
30104203a83SThomas Cort  * It's the caller's responsibility to url_decode() the returned
30204203a83SThomas Cort  * user, pass and path.
30304203a83SThomas Cort  *
30404203a83SThomas Cort  * Sets type to url_t, each of the given char ** pointers to a
30504203a83SThomas Cort  * malloc(3)ed strings of the relevant section, and port to
30604203a83SThomas Cort  * the number given, or ftpport if ftp://, or httpport if http://.
30704203a83SThomas Cort  *
30804203a83SThomas Cort  * XXX: this is not totally RFC 3986 compliant; <path> will have the
30904203a83SThomas Cort  * leading `/' unless it's an ftp:// URL, as this makes things easier
31004203a83SThomas Cort  * for file:// and http:// URLs.  ftp:// URLs have the `/' between the
31104203a83SThomas Cort  * host and the URL-path removed, but any additional leading slashes
31204203a83SThomas Cort  * in the URL-path are retained (because they imply that we should
31304203a83SThomas Cort  * later do "CWD" with a null argument).
31404203a83SThomas Cort  *
31504203a83SThomas Cort  * Examples:
31604203a83SThomas Cort  *	 input URL			 output path
31704203a83SThomas Cort  *	 ---------			 -----------
31804203a83SThomas Cort  *	"http://host"			"/"
31904203a83SThomas Cort  *	"http://host/"			"/"
32004203a83SThomas Cort  *	"http://host/path"		"/path"
32104203a83SThomas Cort  *	"file://host/dir/file"		"dir/file"
32204203a83SThomas Cort  *	"ftp://host"			""
32304203a83SThomas Cort  *	"ftp://host/"			""
32404203a83SThomas Cort  *	"ftp://host//"			"/"
32504203a83SThomas Cort  *	"ftp://host/dir/file"		"dir/file"
32604203a83SThomas Cort  *	"ftp://host//dir/file"		"/dir/file"
32704203a83SThomas Cort  */
32804203a83SThomas Cort static int
32904203a83SThomas Cort parse_url(const char *url, const char *desc, url_t *utype,
33004203a83SThomas Cort 		char **uuser, char **pass, char **host, char **port,
33104203a83SThomas Cort 		in_port_t *portnum, char **path)
33204203a83SThomas Cort {
33304203a83SThomas Cort 	const char	*origurl, *tport;
33404203a83SThomas Cort 	char		*cp, *ep, *thost;
33504203a83SThomas Cort 	size_t		 len;
33604203a83SThomas Cort 
33704203a83SThomas Cort 	if (url == NULL || desc == NULL || utype == NULL || uuser == NULL
33804203a83SThomas Cort 	    || pass == NULL || host == NULL || port == NULL || portnum == NULL
33904203a83SThomas Cort 	    || path == NULL)
34004203a83SThomas Cort 		errx(1, "parse_url: invoked with NULL argument!");
34104203a83SThomas Cort 	DPRINTF("parse_url: %s `%s'\n", desc, url);
34204203a83SThomas Cort 
34304203a83SThomas Cort 	origurl = url;
34404203a83SThomas Cort 	*utype = UNKNOWN_URL_T;
34504203a83SThomas Cort 	*uuser = *pass = *host = *port = *path = NULL;
34604203a83SThomas Cort 	*portnum = 0;
34704203a83SThomas Cort 	tport = NULL;
34804203a83SThomas Cort 
34904203a83SThomas Cort 	if (STRNEQUAL(url, HTTP_URL)) {
35004203a83SThomas Cort 		url += sizeof(HTTP_URL) - 1;
35104203a83SThomas Cort 		*utype = HTTP_URL_T;
35204203a83SThomas Cort 		*portnum = HTTP_PORT;
35304203a83SThomas Cort 		tport = httpport;
35404203a83SThomas Cort 	} else if (STRNEQUAL(url, FTP_URL)) {
35504203a83SThomas Cort 		url += sizeof(FTP_URL) - 1;
35604203a83SThomas Cort 		*utype = FTP_URL_T;
35704203a83SThomas Cort 		*portnum = FTP_PORT;
35804203a83SThomas Cort 		tport = ftpport;
35904203a83SThomas Cort 	} else if (STRNEQUAL(url, FILE_URL)) {
36004203a83SThomas Cort 		url += sizeof(FILE_URL) - 1;
36104203a83SThomas Cort 		*utype = FILE_URL_T;
36204203a83SThomas Cort #ifdef WITH_SSL
36304203a83SThomas Cort 	} else if (STRNEQUAL(url, HTTPS_URL)) {
36404203a83SThomas Cort 		url += sizeof(HTTPS_URL) - 1;
36504203a83SThomas Cort 		*utype = HTTPS_URL_T;
36604203a83SThomas Cort 		*portnum = HTTPS_PORT;
36704203a83SThomas Cort 		tport = httpsport;
36804203a83SThomas Cort #endif
36904203a83SThomas Cort 	} else {
37004203a83SThomas Cort 		warnx("Invalid %s `%s'", desc, url);
37104203a83SThomas Cort  cleanup_parse_url:
37204203a83SThomas Cort 		FREEPTR(*uuser);
37304203a83SThomas Cort 		if (*pass != NULL)
37404203a83SThomas Cort 			memset(*pass, 0, strlen(*pass));
37504203a83SThomas Cort 		FREEPTR(*pass);
37604203a83SThomas Cort 		FREEPTR(*host);
37704203a83SThomas Cort 		FREEPTR(*port);
37804203a83SThomas Cort 		FREEPTR(*path);
37904203a83SThomas Cort 		return (-1);
38004203a83SThomas Cort 	}
38104203a83SThomas Cort 
38204203a83SThomas Cort 	if (*url == '\0')
38304203a83SThomas Cort 		return (0);
38404203a83SThomas Cort 
38504203a83SThomas Cort 			/* find [user[:pass]@]host[:port] */
38604203a83SThomas Cort 	ep = strchr(url, '/');
38704203a83SThomas Cort 	if (ep == NULL)
38804203a83SThomas Cort 		thost = ftp_strdup(url);
38904203a83SThomas Cort 	else {
39004203a83SThomas Cort 		len = ep - url;
39104203a83SThomas Cort 		thost = (char *)ftp_malloc(len + 1);
39204203a83SThomas Cort 		(void)strlcpy(thost, url, len + 1);
39304203a83SThomas Cort 		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
39404203a83SThomas Cort 			ep++;
39504203a83SThomas Cort 		*path = ftp_strdup(ep);
39604203a83SThomas Cort 	}
39704203a83SThomas Cort 
39804203a83SThomas Cort 	cp = strchr(thost, '@');	/* look for user[:pass]@ in URLs */
39904203a83SThomas Cort 	if (cp != NULL) {
40004203a83SThomas Cort 		if (*utype == FTP_URL_T)
40104203a83SThomas Cort 			anonftp = 0;	/* disable anonftp */
40204203a83SThomas Cort 		*uuser = thost;
40304203a83SThomas Cort 		*cp = '\0';
40404203a83SThomas Cort 		thost = ftp_strdup(cp + 1);
40504203a83SThomas Cort 		cp = strchr(*uuser, ':');
40604203a83SThomas Cort 		if (cp != NULL) {
40704203a83SThomas Cort 			*cp = '\0';
40804203a83SThomas Cort 			*pass = ftp_strdup(cp + 1);
40904203a83SThomas Cort 		}
41004203a83SThomas Cort 		url_decode(*uuser);
41104203a83SThomas Cort 		if (*pass)
41204203a83SThomas Cort 			url_decode(*pass);
41304203a83SThomas Cort 	}
41404203a83SThomas Cort 
41504203a83SThomas Cort #ifdef INET6
41604203a83SThomas Cort 			/*
41704203a83SThomas Cort 			 * Check if thost is an encoded IPv6 address, as per
41804203a83SThomas Cort 			 * RFC 3986:
41904203a83SThomas Cort 			 *	`[' ipv6-address ']'
42004203a83SThomas Cort 			 */
42104203a83SThomas Cort 	if (*thost == '[') {
42204203a83SThomas Cort 		cp = thost + 1;
42304203a83SThomas Cort 		if ((ep = strchr(cp, ']')) == NULL ||
42404203a83SThomas Cort 		    (ep[1] != '\0' && ep[1] != ':')) {
42504203a83SThomas Cort 			warnx("Invalid address `%s' in %s `%s'",
42604203a83SThomas Cort 			    thost, desc, origurl);
42704203a83SThomas Cort 			goto cleanup_parse_url;
42804203a83SThomas Cort 		}
42904203a83SThomas Cort 		len = ep - cp;		/* change `[xyz]' -> `xyz' */
43004203a83SThomas Cort 		memmove(thost, thost + 1, len);
43104203a83SThomas Cort 		thost[len] = '\0';
43204203a83SThomas Cort 		if (! isipv6addr(thost)) {
43304203a83SThomas Cort 			warnx("Invalid IPv6 address `%s' in %s `%s'",
43404203a83SThomas Cort 			    thost, desc, origurl);
43504203a83SThomas Cort 			goto cleanup_parse_url;
43604203a83SThomas Cort 		}
43704203a83SThomas Cort 		cp = ep + 1;
43804203a83SThomas Cort 		if (*cp == ':')
43904203a83SThomas Cort 			cp++;
44004203a83SThomas Cort 		else
44104203a83SThomas Cort 			cp = NULL;
44204203a83SThomas Cort 	} else
44304203a83SThomas Cort #endif /* INET6 */
44404203a83SThomas Cort 		if ((cp = strchr(thost, ':')) != NULL)
44504203a83SThomas Cort 			*cp++ = '\0';
44604203a83SThomas Cort 	*host = thost;
44704203a83SThomas Cort 
44804203a83SThomas Cort 			/* look for [:port] */
44904203a83SThomas Cort 	if (cp != NULL) {
45004203a83SThomas Cort 		unsigned long	nport;
45104203a83SThomas Cort 
45204203a83SThomas Cort 		nport = strtoul(cp, &ep, 10);
45304203a83SThomas Cort 		if (*cp == '\0' || *ep != '\0' ||
45404203a83SThomas Cort 		    nport < 1 || nport > MAX_IN_PORT_T) {
45504203a83SThomas Cort 			warnx("Unknown port `%s' in %s `%s'",
45604203a83SThomas Cort 			    cp, desc, origurl);
45704203a83SThomas Cort 			goto cleanup_parse_url;
45804203a83SThomas Cort 		}
45904203a83SThomas Cort 		*portnum = nport;
46004203a83SThomas Cort 		tport = cp;
46104203a83SThomas Cort 	}
46204203a83SThomas Cort 
46304203a83SThomas Cort 	if (tport != NULL)
46404203a83SThomas Cort 		*port = ftp_strdup(tport);
46504203a83SThomas Cort 	if (*path == NULL) {
46604203a83SThomas Cort 		const char *emptypath = "/";
46704203a83SThomas Cort 		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
46804203a83SThomas Cort 			emptypath++;
46904203a83SThomas Cort 		*path = ftp_strdup(emptypath);
47004203a83SThomas Cort 	}
47104203a83SThomas Cort 
47204203a83SThomas Cort 	DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
47304203a83SThomas Cort 	    "path `%s'\n",
47404203a83SThomas Cort 	    STRorNULL(*uuser), STRorNULL(*pass),
47504203a83SThomas Cort 	    STRorNULL(*host), STRorNULL(*port),
47604203a83SThomas Cort 	    *portnum ? *portnum : -1, STRorNULL(*path));
47704203a83SThomas Cort 
47804203a83SThomas Cort 	return (0);
47904203a83SThomas Cort }
48004203a83SThomas Cort 
48104203a83SThomas Cort sigjmp_buf	httpabort;
48204203a83SThomas Cort 
48304203a83SThomas Cort /*
48404203a83SThomas Cort  * Retrieve URL, via a proxy if necessary, using HTTP.
48504203a83SThomas Cort  * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
48604203a83SThomas Cort  * http_proxy/https_proxy as appropriate.
48704203a83SThomas Cort  * Supports HTTP redirects.
48804203a83SThomas Cort  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
48904203a83SThomas Cort  * is still open (e.g, ftp xfer with trailing /)
49004203a83SThomas Cort  */
49104203a83SThomas Cort static int
49204203a83SThomas Cort fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
49304203a83SThomas Cort {
49404203a83SThomas Cort 	struct addrinfo		hints, *res, *res0 = NULL;
49504203a83SThomas Cort 	int			error;
496*84d9c625SLionel Sambuc 	sigfunc volatile	oldint;
497*84d9c625SLionel Sambuc 	sigfunc volatile	oldpipe;
498*84d9c625SLionel Sambuc 	sigfunc volatile	oldalrm;
499*84d9c625SLionel Sambuc 	sigfunc volatile	oldquit;
50004203a83SThomas Cort 	int volatile		s;
50104203a83SThomas Cort 	struct stat		sb;
50204203a83SThomas Cort 	int volatile		ischunked;
50304203a83SThomas Cort 	int volatile		isproxy;
50404203a83SThomas Cort 	int volatile		rval;
50504203a83SThomas Cort 	int volatile		hcode;
50604203a83SThomas Cort 	int			len;
50704203a83SThomas Cort 	size_t			flen;
50804203a83SThomas Cort 	static size_t		bufsize;
50904203a83SThomas Cort 	static char		*xferbuf;
51004203a83SThomas Cort 	const char		*cp, *token;
51104203a83SThomas Cort 	char			*ep;
51204203a83SThomas Cort 	char			buf[FTPBUFLEN];
51304203a83SThomas Cort 	const char		*errormsg;
51404203a83SThomas Cort 	char			*volatile savefile;
51504203a83SThomas Cort 	char			*volatile auth;
51604203a83SThomas Cort 	char			*volatile location;
51704203a83SThomas Cort 	char			*volatile message;
51804203a83SThomas Cort 	char			*uuser, *pass, *host, *port, *path;
51904203a83SThomas Cort 	char			*volatile decodedpath;
52004203a83SThomas Cort 	char			*puser, *ppass, *useragent;
52104203a83SThomas Cort 	off_t			hashbytes, rangestart, rangeend, entitylen;
52204203a83SThomas Cort 	int			(*volatile closefunc)(FILE *);
52304203a83SThomas Cort 	FETCH			*volatile fin;
52404203a83SThomas Cort 	FILE			*volatile fout;
525*84d9c625SLionel Sambuc 	const char		*volatile penv = proxyenv;
52604203a83SThomas Cort 	time_t			mtime;
52704203a83SThomas Cort 	url_t			urltype;
52804203a83SThomas Cort 	in_port_t		portnum;
52904203a83SThomas Cort #ifdef WITH_SSL
53004203a83SThomas Cort 	void			*ssl;
53104203a83SThomas Cort #endif
53204203a83SThomas Cort 
533*84d9c625SLionel Sambuc 	DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv));
53404203a83SThomas Cort 
535*84d9c625SLionel Sambuc 	oldquit = oldalrm = oldint = oldpipe = NULL;
53604203a83SThomas Cort 	closefunc = NULL;
53704203a83SThomas Cort 	fin = NULL;
53804203a83SThomas Cort 	fout = NULL;
53904203a83SThomas Cort 	s = -1;
54004203a83SThomas Cort 	savefile = NULL;
54104203a83SThomas Cort 	auth = location = message = NULL;
54204203a83SThomas Cort 	ischunked = isproxy = hcode = 0;
54304203a83SThomas Cort 	rval = 1;
54404203a83SThomas Cort 	uuser = pass = host = path = decodedpath = puser = ppass = NULL;
54504203a83SThomas Cort 
546*84d9c625SLionel Sambuc 	if (sigsetjmp(httpabort, 1))
547*84d9c625SLionel Sambuc 		goto cleanup_fetch_url;
548*84d9c625SLionel Sambuc 
54904203a83SThomas Cort 	if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port,
55004203a83SThomas Cort 	    &portnum, &path) == -1)
55104203a83SThomas Cort 		goto cleanup_fetch_url;
55204203a83SThomas Cort 
55304203a83SThomas Cort 	if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
55404203a83SThomas Cort 	    && strcasecmp(host, "localhost") != 0) {
55504203a83SThomas Cort 		warnx("No support for non local file URL `%s'", url);
55604203a83SThomas Cort 		goto cleanup_fetch_url;
55704203a83SThomas Cort 	}
55804203a83SThomas Cort 
55904203a83SThomas Cort 	if (EMPTYSTRING(path)) {
56004203a83SThomas Cort 		if (urltype == FTP_URL_T) {
56104203a83SThomas Cort 			rval = fetch_ftp(url);
56204203a83SThomas Cort 			goto cleanup_fetch_url;
56304203a83SThomas Cort 		}
56404203a83SThomas Cort 		if (!IS_HTTP_TYPE(urltype) || outfile == NULL)  {
56504203a83SThomas Cort 			warnx("Invalid URL (no file after host) `%s'", url);
56604203a83SThomas Cort 			goto cleanup_fetch_url;
56704203a83SThomas Cort 		}
56804203a83SThomas Cort 	}
56904203a83SThomas Cort 
57004203a83SThomas Cort 	decodedpath = ftp_strdup(path);
57104203a83SThomas Cort 	url_decode(decodedpath);
57204203a83SThomas Cort 
57304203a83SThomas Cort 	if (outfile)
57404203a83SThomas Cort 		savefile = ftp_strdup(outfile);
57504203a83SThomas Cort 	else {
57604203a83SThomas Cort 		cp = strrchr(decodedpath, '/');		/* find savefile */
57704203a83SThomas Cort 		if (cp != NULL)
57804203a83SThomas Cort 			savefile = ftp_strdup(cp + 1);
57904203a83SThomas Cort 		else
58004203a83SThomas Cort 			savefile = ftp_strdup(decodedpath);
58104203a83SThomas Cort 	}
582*84d9c625SLionel Sambuc 	DPRINTF("%s: savefile `%s'\n", __func__, savefile);
58304203a83SThomas Cort 	if (EMPTYSTRING(savefile)) {
58404203a83SThomas Cort 		if (urltype == FTP_URL_T) {
58504203a83SThomas Cort 			rval = fetch_ftp(url);
58604203a83SThomas Cort 			goto cleanup_fetch_url;
58704203a83SThomas Cort 		}
58804203a83SThomas Cort 		warnx("No file after directory (you must specify an "
58904203a83SThomas Cort 		    "output file) `%s'", url);
59004203a83SThomas Cort 		goto cleanup_fetch_url;
59104203a83SThomas Cort 	}
59204203a83SThomas Cort 
59304203a83SThomas Cort 	restart_point = 0;
59404203a83SThomas Cort 	filesize = -1;
59504203a83SThomas Cort 	rangestart = rangeend = entitylen = -1;
59604203a83SThomas Cort 	mtime = -1;
59704203a83SThomas Cort 	if (restartautofetch) {
59804203a83SThomas Cort 		if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
59904203a83SThomas Cort 		    stat(savefile, &sb) == 0)
60004203a83SThomas Cort 			restart_point = sb.st_size;
60104203a83SThomas Cort 	}
60204203a83SThomas Cort 	if (urltype == FILE_URL_T) {		/* file:// URLs */
60304203a83SThomas Cort 		direction = "copied";
60404203a83SThomas Cort 		fin = fetch_open(decodedpath, "r");
60504203a83SThomas Cort 		if (fin == NULL) {
60604203a83SThomas Cort 			warn("Can't open `%s'", decodedpath);
60704203a83SThomas Cort 			goto cleanup_fetch_url;
60804203a83SThomas Cort 		}
60904203a83SThomas Cort 		if (fstat(fetch_fileno(fin), &sb) == 0) {
61004203a83SThomas Cort 			mtime = sb.st_mtime;
61104203a83SThomas Cort 			filesize = sb.st_size;
61204203a83SThomas Cort 		}
61304203a83SThomas Cort 		if (restart_point) {
61404203a83SThomas Cort 			if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) {
61504203a83SThomas Cort 				warn("Can't seek to restart `%s'",
61604203a83SThomas Cort 				    decodedpath);
61704203a83SThomas Cort 				goto cleanup_fetch_url;
61804203a83SThomas Cort 			}
61904203a83SThomas Cort 		}
62004203a83SThomas Cort 		if (verbose) {
62104203a83SThomas Cort 			fprintf(ttyout, "Copying %s", decodedpath);
62204203a83SThomas Cort 			if (restart_point)
62304203a83SThomas Cort 				fprintf(ttyout, " (restarting at " LLF ")",
62404203a83SThomas Cort 				    (LLT)restart_point);
62504203a83SThomas Cort 			fputs("\n", ttyout);
62604203a83SThomas Cort 		}
62704203a83SThomas Cort 		if (0 == rcvbuf_size) {
62804203a83SThomas Cort 			rcvbuf_size = 8 * 1024; /* XXX */
62904203a83SThomas Cort 		}
63004203a83SThomas Cort 	} else {				/* ftp:// or http:// URLs */
63104203a83SThomas Cort 		const char *leading;
63204203a83SThomas Cort 		int hasleading;
63304203a83SThomas Cort 
634*84d9c625SLionel Sambuc 		if (penv == NULL) {
63504203a83SThomas Cort #ifdef WITH_SSL
63604203a83SThomas Cort 			if (urltype == HTTPS_URL_T)
637*84d9c625SLionel Sambuc 				penv = getoptionvalue("https_proxy");
63804203a83SThomas Cort #endif
639*84d9c625SLionel Sambuc 			if (penv == NULL && IS_HTTP_TYPE(urltype))
640*84d9c625SLionel Sambuc 				penv = getoptionvalue("http_proxy");
64104203a83SThomas Cort 			else if (urltype == FTP_URL_T)
642*84d9c625SLionel Sambuc 				penv = getoptionvalue("ftp_proxy");
64304203a83SThomas Cort 		}
64404203a83SThomas Cort 		direction = "retrieved";
645*84d9c625SLionel Sambuc 		if (! EMPTYSTRING(penv)) {			/* use proxy */
64604203a83SThomas Cort 			url_t purltype;
64704203a83SThomas Cort 			char *phost, *ppath;
64804203a83SThomas Cort 			char *pport, *no_proxy;
64904203a83SThomas Cort 			in_port_t pportnum;
65004203a83SThomas Cort 
65104203a83SThomas Cort 			isproxy = 1;
65204203a83SThomas Cort 
65304203a83SThomas Cort 				/* check URL against list of no_proxied sites */
65404203a83SThomas Cort 			no_proxy = getoptionvalue("no_proxy");
65504203a83SThomas Cort 			if (! EMPTYSTRING(no_proxy)) {
65604203a83SThomas Cort 				char *np, *np_copy, *np_iter;
65704203a83SThomas Cort 				unsigned long np_port;
65804203a83SThomas Cort 				size_t hlen, plen;
65904203a83SThomas Cort 
66004203a83SThomas Cort 				np_iter = np_copy = ftp_strdup(no_proxy);
66104203a83SThomas Cort 				hlen = strlen(host);
66204203a83SThomas Cort 				while ((cp = strsep(&np_iter, " ,")) != NULL) {
66304203a83SThomas Cort 					if (*cp == '\0')
66404203a83SThomas Cort 						continue;
66504203a83SThomas Cort 					if ((np = strrchr(cp, ':')) != NULL) {
66604203a83SThomas Cort 						*np++ =  '\0';
66704203a83SThomas Cort 						np_port = strtoul(np, &ep, 10);
66804203a83SThomas Cort 						if (*np == '\0' || *ep != '\0')
66904203a83SThomas Cort 							continue;
67004203a83SThomas Cort 						if (np_port != portnum)
67104203a83SThomas Cort 							continue;
67204203a83SThomas Cort 					}
67304203a83SThomas Cort 					plen = strlen(cp);
67404203a83SThomas Cort 					if (hlen < plen)
67504203a83SThomas Cort 						continue;
67604203a83SThomas Cort 					if (strncasecmp(host + hlen - plen,
67704203a83SThomas Cort 					    cp, plen) == 0) {
67804203a83SThomas Cort 						isproxy = 0;
67904203a83SThomas Cort 						break;
68004203a83SThomas Cort 					}
68104203a83SThomas Cort 				}
68204203a83SThomas Cort 				FREEPTR(np_copy);
68304203a83SThomas Cort 				if (isproxy == 0 && urltype == FTP_URL_T) {
68404203a83SThomas Cort 					rval = fetch_ftp(url);
68504203a83SThomas Cort 					goto cleanup_fetch_url;
68604203a83SThomas Cort 				}
68704203a83SThomas Cort 			}
68804203a83SThomas Cort 
68904203a83SThomas Cort 			if (isproxy) {
69004203a83SThomas Cort 				if (restart_point) {
69104203a83SThomas Cort 					warnx("Can't restart via proxy URL `%s'",
692*84d9c625SLionel Sambuc 					    penv);
69304203a83SThomas Cort 					goto cleanup_fetch_url;
69404203a83SThomas Cort 				}
695*84d9c625SLionel Sambuc 				if (parse_url(penv, "proxy URL", &purltype,
69604203a83SThomas Cort 				    &puser, &ppass, &phost, &pport, &pportnum,
69704203a83SThomas Cort 				    &ppath) == -1)
69804203a83SThomas Cort 					goto cleanup_fetch_url;
69904203a83SThomas Cort 
70004203a83SThomas Cort 				if ((!IS_HTTP_TYPE(purltype)
70104203a83SThomas Cort 				     && purltype != FTP_URL_T) ||
70204203a83SThomas Cort 				    EMPTYSTRING(phost) ||
70304203a83SThomas Cort 				    (! EMPTYSTRING(ppath)
70404203a83SThomas Cort 				     && strcmp(ppath, "/") != 0)) {
705*84d9c625SLionel Sambuc 					warnx("Malformed proxy URL `%s'", penv);
70604203a83SThomas Cort 					FREEPTR(phost);
70704203a83SThomas Cort 					FREEPTR(pport);
70804203a83SThomas Cort 					FREEPTR(ppath);
70904203a83SThomas Cort 					goto cleanup_fetch_url;
71004203a83SThomas Cort 				}
71104203a83SThomas Cort 				if (isipv6addr(host) &&
71204203a83SThomas Cort 				    strchr(host, '%') != NULL) {
71304203a83SThomas Cort 					warnx(
71404203a83SThomas Cort "Scoped address notation `%s' disallowed via web proxy",
71504203a83SThomas Cort 					    host);
71604203a83SThomas Cort 					FREEPTR(phost);
71704203a83SThomas Cort 					FREEPTR(pport);
71804203a83SThomas Cort 					FREEPTR(ppath);
71904203a83SThomas Cort 					goto cleanup_fetch_url;
72004203a83SThomas Cort 				}
72104203a83SThomas Cort 
72204203a83SThomas Cort 				FREEPTR(host);
72304203a83SThomas Cort 				host = phost;
72404203a83SThomas Cort 				FREEPTR(port);
72504203a83SThomas Cort 				port = pport;
72604203a83SThomas Cort 				FREEPTR(path);
72704203a83SThomas Cort 				path = ftp_strdup(url);
72804203a83SThomas Cort 				FREEPTR(ppath);
72904203a83SThomas Cort 				urltype = purltype;
73004203a83SThomas Cort 			}
731*84d9c625SLionel Sambuc 		} /* ! EMPTYSTRING(penv) */
73204203a83SThomas Cort 
73304203a83SThomas Cort 		memset(&hints, 0, sizeof(hints));
73404203a83SThomas Cort 		hints.ai_flags = 0;
73504203a83SThomas Cort 		hints.ai_family = family;
73604203a83SThomas Cort 		hints.ai_socktype = SOCK_STREAM;
73704203a83SThomas Cort 		hints.ai_protocol = 0;
73804203a83SThomas Cort 		error = getaddrinfo(host, port, &hints, &res0);
73904203a83SThomas Cort 		if (error) {
74004203a83SThomas Cort 			warnx("Can't LOOKUP `%s:%s': %s", host, port,
74104203a83SThomas Cort 			    (error == EAI_SYSTEM) ? strerror(errno)
74204203a83SThomas Cort 						  : gai_strerror(error));
74304203a83SThomas Cort 			goto cleanup_fetch_url;
74404203a83SThomas Cort 		}
74504203a83SThomas Cort 		if (res0->ai_canonname)
74604203a83SThomas Cort 			host = res0->ai_canonname;
74704203a83SThomas Cort 
74804203a83SThomas Cort 		s = -1;
74904203a83SThomas Cort #ifdef WITH_SSL
75004203a83SThomas Cort 		ssl = NULL;
75104203a83SThomas Cort #endif
75204203a83SThomas Cort 		for (res = res0; res; res = res->ai_next) {
75304203a83SThomas Cort 			char	hname[NI_MAXHOST], sname[NI_MAXSERV];
75404203a83SThomas Cort 
75504203a83SThomas Cort 			ai_unmapped(res);
75604203a83SThomas Cort 			if (getnameinfo(res->ai_addr, res->ai_addrlen,
75704203a83SThomas Cort 			    hname, sizeof(hname), sname, sizeof(sname),
75804203a83SThomas Cort 			    NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
75904203a83SThomas Cort 				strlcpy(hname, "?", sizeof(hname));
76004203a83SThomas Cort 				strlcpy(sname, "?", sizeof(sname));
76104203a83SThomas Cort 			}
76204203a83SThomas Cort 
76304203a83SThomas Cort 			if (verbose && res0->ai_next) {
76404203a83SThomas Cort 				fprintf(ttyout, "Trying %s:%s ...\n",
76504203a83SThomas Cort 				    hname, sname);
76604203a83SThomas Cort 			}
76704203a83SThomas Cort 
76804203a83SThomas Cort 			s = socket(res->ai_family, SOCK_STREAM,
76904203a83SThomas Cort 			    res->ai_protocol);
77004203a83SThomas Cort 			if (s < 0) {
77104203a83SThomas Cort 				warn(
77204203a83SThomas Cort 				    "Can't create socket for connection to "
77304203a83SThomas Cort 				    "`%s:%s'", hname, sname);
77404203a83SThomas Cort 				continue;
77504203a83SThomas Cort 			}
77604203a83SThomas Cort 
77704203a83SThomas Cort 			if (ftp_connect(s, res->ai_addr, res->ai_addrlen,
77804203a83SThomas Cort 			    verbose || !res->ai_next) < 0) {
77904203a83SThomas Cort 				close(s);
78004203a83SThomas Cort 				s = -1;
78104203a83SThomas Cort 				continue;
78204203a83SThomas Cort 			}
78304203a83SThomas Cort 
78404203a83SThomas Cort #ifdef WITH_SSL
78504203a83SThomas Cort 			if (urltype == HTTPS_URL_T) {
78604203a83SThomas Cort 				if ((ssl = fetch_start_ssl(s)) == NULL) {
78704203a83SThomas Cort 					close(s);
78804203a83SThomas Cort 					s = -1;
78904203a83SThomas Cort 					continue;
79004203a83SThomas Cort 				}
79104203a83SThomas Cort 			}
79204203a83SThomas Cort #endif
79304203a83SThomas Cort 
79404203a83SThomas Cort 			/* success */
79504203a83SThomas Cort 			break;
79604203a83SThomas Cort 		}
79704203a83SThomas Cort 
79804203a83SThomas Cort 		if (s < 0) {
79904203a83SThomas Cort 			warnx("Can't connect to `%s:%s'", host, port);
80004203a83SThomas Cort 			goto cleanup_fetch_url;
80104203a83SThomas Cort 		}
80204203a83SThomas Cort 
803*84d9c625SLionel Sambuc 		oldalrm = xsignal(SIGALRM, timeouthttp);
804*84d9c625SLionel Sambuc 		alarmtimer(quit_time ? quit_time : 60);
80504203a83SThomas Cort 		fin = fetch_fdopen(s, "r+");
80604203a83SThomas Cort 		fetch_set_ssl(fin, ssl);
807*84d9c625SLionel Sambuc 		alarmtimer(0);
80804203a83SThomas Cort 
809*84d9c625SLionel Sambuc 		alarmtimer(quit_time ? quit_time : 60);
81004203a83SThomas Cort 		/*
81104203a83SThomas Cort 		 * Construct and send the request.
81204203a83SThomas Cort 		 */
81304203a83SThomas Cort 		if (verbose)
81404203a83SThomas Cort 			fprintf(ttyout, "Requesting %s\n", url);
81504203a83SThomas Cort 		leading = "  (";
81604203a83SThomas Cort 		hasleading = 0;
81704203a83SThomas Cort 		if (isproxy) {
81804203a83SThomas Cort 			if (verbose) {
81904203a83SThomas Cort 				fprintf(ttyout, "%svia %s:%s", leading,
82004203a83SThomas Cort 				    host, port);
82104203a83SThomas Cort 				leading = ", ";
82204203a83SThomas Cort 				hasleading++;
82304203a83SThomas Cort 			}
82404203a83SThomas Cort 			fetch_printf(fin, "GET %s HTTP/1.0\r\n", path);
82504203a83SThomas Cort 			if (flushcache)
82604203a83SThomas Cort 				fetch_printf(fin, "Pragma: no-cache\r\n");
82704203a83SThomas Cort 		} else {
82804203a83SThomas Cort 			fetch_printf(fin, "GET %s HTTP/1.1\r\n", path);
82904203a83SThomas Cort 			if (strchr(host, ':')) {
83004203a83SThomas Cort 				char *h, *p;
83104203a83SThomas Cort 
83204203a83SThomas Cort 				/*
83304203a83SThomas Cort 				 * strip off IPv6 scope identifier, since it is
83404203a83SThomas Cort 				 * local to the node
83504203a83SThomas Cort 				 */
83604203a83SThomas Cort 				h = ftp_strdup(host);
83704203a83SThomas Cort 				if (isipv6addr(h) &&
83804203a83SThomas Cort 				    (p = strchr(h, '%')) != NULL) {
83904203a83SThomas Cort 					*p = '\0';
84004203a83SThomas Cort 				}
84104203a83SThomas Cort 				fetch_printf(fin, "Host: [%s]", h);
84204203a83SThomas Cort 				free(h);
84304203a83SThomas Cort 			} else
84404203a83SThomas Cort 				fetch_printf(fin, "Host: %s", host);
84504203a83SThomas Cort #ifdef WITH_SSL
84604203a83SThomas Cort 			if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) ||
84704203a83SThomas Cort 			    (urltype == HTTPS_URL_T && portnum != HTTPS_PORT))
84804203a83SThomas Cort #else
84904203a83SThomas Cort 			if (portnum != HTTP_PORT)
85004203a83SThomas Cort #endif
85104203a83SThomas Cort 				fetch_printf(fin, ":%u", portnum);
85204203a83SThomas Cort 			fetch_printf(fin, "\r\n");
85304203a83SThomas Cort 			fetch_printf(fin, "Accept: */*\r\n");
85404203a83SThomas Cort 			fetch_printf(fin, "Connection: close\r\n");
85504203a83SThomas Cort 			if (restart_point) {
85604203a83SThomas Cort 				fputs(leading, ttyout);
85704203a83SThomas Cort 				fetch_printf(fin, "Range: bytes=" LLF "-\r\n",
85804203a83SThomas Cort 				    (LLT)restart_point);
85904203a83SThomas Cort 				fprintf(ttyout, "restarting at " LLF,
86004203a83SThomas Cort 				    (LLT)restart_point);
86104203a83SThomas Cort 				leading = ", ";
86204203a83SThomas Cort 				hasleading++;
86304203a83SThomas Cort 			}
86404203a83SThomas Cort 			if (flushcache)
86504203a83SThomas Cort 				fetch_printf(fin, "Cache-Control: no-cache\r\n");
86604203a83SThomas Cort 		}
86704203a83SThomas Cort 		if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
86804203a83SThomas Cort 			fetch_printf(fin, "User-Agent: %s\r\n", useragent);
86904203a83SThomas Cort 		} else {
87004203a83SThomas Cort 			fetch_printf(fin, "User-Agent: %s/%s\r\n",
87104203a83SThomas Cort 			    FTP_PRODUCT, FTP_VERSION);
87204203a83SThomas Cort 		}
87304203a83SThomas Cort 		if (wwwauth) {
87404203a83SThomas Cort 			if (verbose) {
87504203a83SThomas Cort 				fprintf(ttyout, "%swith authorization",
87604203a83SThomas Cort 				    leading);
87704203a83SThomas Cort 				leading = ", ";
87804203a83SThomas Cort 				hasleading++;
87904203a83SThomas Cort 			}
88004203a83SThomas Cort 			fetch_printf(fin, "Authorization: %s\r\n", wwwauth);
88104203a83SThomas Cort 		}
88204203a83SThomas Cort 		if (proxyauth) {
88304203a83SThomas Cort 			if (verbose) {
88404203a83SThomas Cort 				fprintf(ttyout,
88504203a83SThomas Cort 				    "%swith proxy authorization", leading);
88604203a83SThomas Cort 				leading = ", ";
88704203a83SThomas Cort 				hasleading++;
88804203a83SThomas Cort 			}
88904203a83SThomas Cort 			fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
89004203a83SThomas Cort 		}
89104203a83SThomas Cort 		if (verbose && hasleading)
89204203a83SThomas Cort 			fputs(")\n", ttyout);
89304203a83SThomas Cort 		fetch_printf(fin, "\r\n");
89404203a83SThomas Cort 		if (fetch_flush(fin) == EOF) {
89504203a83SThomas Cort 			warn("Writing HTTP request");
896*84d9c625SLionel Sambuc 			alarmtimer(0);
89704203a83SThomas Cort 			goto cleanup_fetch_url;
89804203a83SThomas Cort 		}
899*84d9c625SLionel Sambuc 		alarmtimer(0);
90004203a83SThomas Cort 
90104203a83SThomas Cort 				/* Read the response */
902*84d9c625SLionel Sambuc 		alarmtimer(quit_time ? quit_time : 60);
90304203a83SThomas Cort 		len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
904*84d9c625SLionel Sambuc 		alarmtimer(0);
90504203a83SThomas Cort 		if (len < 0) {
90604203a83SThomas Cort 			if (*errormsg == '\n')
90704203a83SThomas Cort 				errormsg++;
90804203a83SThomas Cort 			warnx("Receiving HTTP reply: %s", errormsg);
90904203a83SThomas Cort 			goto cleanup_fetch_url;
91004203a83SThomas Cort 		}
91104203a83SThomas Cort 		while (len > 0 && (ISLWS(buf[len-1])))
91204203a83SThomas Cort 			buf[--len] = '\0';
913*84d9c625SLionel Sambuc 		DPRINTF("%s: received `%s'\n", __func__, buf);
91404203a83SThomas Cort 
91504203a83SThomas Cort 				/* Determine HTTP response code */
91604203a83SThomas Cort 		cp = strchr(buf, ' ');
91704203a83SThomas Cort 		if (cp == NULL)
91804203a83SThomas Cort 			goto improper;
91904203a83SThomas Cort 		else
92004203a83SThomas Cort 			cp++;
92104203a83SThomas Cort 		hcode = strtol(cp, &ep, 10);
92204203a83SThomas Cort 		if (*ep != '\0' && !isspace((unsigned char)*ep))
92304203a83SThomas Cort 			goto improper;
92404203a83SThomas Cort 		message = ftp_strdup(cp);
92504203a83SThomas Cort 
92604203a83SThomas Cort 				/* Read the rest of the header. */
92704203a83SThomas Cort 		while (1) {
928*84d9c625SLionel Sambuc 			alarmtimer(quit_time ? quit_time : 60);
92904203a83SThomas Cort 			len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
930*84d9c625SLionel Sambuc 			alarmtimer(0);
93104203a83SThomas Cort 			if (len < 0) {
93204203a83SThomas Cort 				if (*errormsg == '\n')
93304203a83SThomas Cort 					errormsg++;
93404203a83SThomas Cort 				warnx("Receiving HTTP reply: %s", errormsg);
93504203a83SThomas Cort 				goto cleanup_fetch_url;
93604203a83SThomas Cort 			}
93704203a83SThomas Cort 			while (len > 0 && (ISLWS(buf[len-1])))
93804203a83SThomas Cort 				buf[--len] = '\0';
93904203a83SThomas Cort 			if (len == 0)
94004203a83SThomas Cort 				break;
941*84d9c625SLionel Sambuc 			DPRINTF("%s: received `%s'\n", __func__, buf);
94204203a83SThomas Cort 
94304203a83SThomas Cort 		/*
94404203a83SThomas Cort 		 * Look for some headers
94504203a83SThomas Cort 		 */
94604203a83SThomas Cort 
94704203a83SThomas Cort 			cp = buf;
94804203a83SThomas Cort 
94904203a83SThomas Cort 			if (match_token(&cp, "Content-Length:")) {
95004203a83SThomas Cort 				filesize = STRTOLL(cp, &ep, 10);
95104203a83SThomas Cort 				if (filesize < 0 || *ep != '\0')
95204203a83SThomas Cort 					goto improper;
953*84d9c625SLionel Sambuc 				DPRINTF("%s: parsed len as: " LLF "\n",
954*84d9c625SLionel Sambuc 				    __func__, (LLT)filesize);
95504203a83SThomas Cort 
95604203a83SThomas Cort 			} else if (match_token(&cp, "Content-Range:")) {
95704203a83SThomas Cort 				if (! match_token(&cp, "bytes"))
95804203a83SThomas Cort 					goto improper;
95904203a83SThomas Cort 
96004203a83SThomas Cort 				if (*cp == '*')
96104203a83SThomas Cort 					cp++;
96204203a83SThomas Cort 				else {
96304203a83SThomas Cort 					rangestart = STRTOLL(cp, &ep, 10);
96404203a83SThomas Cort 					if (rangestart < 0 || *ep != '-')
96504203a83SThomas Cort 						goto improper;
96604203a83SThomas Cort 					cp = ep + 1;
96704203a83SThomas Cort 					rangeend = STRTOLL(cp, &ep, 10);
96804203a83SThomas Cort 					if (rangeend < 0 || rangeend < rangestart)
96904203a83SThomas Cort 						goto improper;
97004203a83SThomas Cort 					cp = ep;
97104203a83SThomas Cort 				}
97204203a83SThomas Cort 				if (*cp != '/')
97304203a83SThomas Cort 					goto improper;
97404203a83SThomas Cort 				cp++;
97504203a83SThomas Cort 				if (*cp == '*')
97604203a83SThomas Cort 					cp++;
97704203a83SThomas Cort 				else {
97804203a83SThomas Cort 					entitylen = STRTOLL(cp, &ep, 10);
97904203a83SThomas Cort 					if (entitylen < 0)
98004203a83SThomas Cort 						goto improper;
98104203a83SThomas Cort 					cp = ep;
98204203a83SThomas Cort 				}
98304203a83SThomas Cort 				if (*cp != '\0')
98404203a83SThomas Cort 					goto improper;
98504203a83SThomas Cort 
98604203a83SThomas Cort #ifndef NO_DEBUG
98704203a83SThomas Cort 				if (ftp_debug) {
98804203a83SThomas Cort 					fprintf(ttyout, "parsed range as: ");
98904203a83SThomas Cort 					if (rangestart == -1)
99004203a83SThomas Cort 						fprintf(ttyout, "*");
99104203a83SThomas Cort 					else
99204203a83SThomas Cort 						fprintf(ttyout, LLF "-" LLF,
99304203a83SThomas Cort 						    (LLT)rangestart,
99404203a83SThomas Cort 						    (LLT)rangeend);
99504203a83SThomas Cort 					fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
99604203a83SThomas Cort 				}
99704203a83SThomas Cort #endif
99804203a83SThomas Cort 				if (! restart_point) {
99904203a83SThomas Cort 					warnx(
100004203a83SThomas Cort 				    "Received unexpected Content-Range header");
100104203a83SThomas Cort 					goto cleanup_fetch_url;
100204203a83SThomas Cort 				}
100304203a83SThomas Cort 
100404203a83SThomas Cort 			} else if (match_token(&cp, "Last-Modified:")) {
100504203a83SThomas Cort 				struct tm parsed;
100604203a83SThomas Cort 				const char *t;
100704203a83SThomas Cort 
100804203a83SThomas Cort 				memset(&parsed, 0, sizeof(parsed));
100904203a83SThomas Cort 				t = parse_rfc2616time(&parsed, cp);
101004203a83SThomas Cort 				if (t != NULL) {
101104203a83SThomas Cort 					parsed.tm_isdst = -1;
101204203a83SThomas Cort 					if (*t == '\0')
101304203a83SThomas Cort 						mtime = timegm(&parsed);
101404203a83SThomas Cort #ifndef NO_DEBUG
101504203a83SThomas Cort 					if (ftp_debug && mtime != -1) {
101604203a83SThomas Cort 						fprintf(ttyout,
101704203a83SThomas Cort 						    "parsed time as: %s",
101804203a83SThomas Cort 						rfc2822time(localtime(&mtime)));
101904203a83SThomas Cort 					}
102004203a83SThomas Cort #endif
102104203a83SThomas Cort 				}
102204203a83SThomas Cort 
102304203a83SThomas Cort 			} else if (match_token(&cp, "Location:")) {
102404203a83SThomas Cort 				location = ftp_strdup(cp);
1025*84d9c625SLionel Sambuc 				DPRINTF("%s: parsed location as `%s'\n",
1026*84d9c625SLionel Sambuc 				    __func__, cp);
102704203a83SThomas Cort 
102804203a83SThomas Cort 			} else if (match_token(&cp, "Transfer-Encoding:")) {
102904203a83SThomas Cort 				if (match_token(&cp, "binary")) {
103004203a83SThomas Cort 					warnx(
103104203a83SThomas Cort 			"Bogus transfer encoding `binary' (fetching anyway)");
103204203a83SThomas Cort 					continue;
103304203a83SThomas Cort 				}
103404203a83SThomas Cort 				if (! (token = match_token(&cp, "chunked"))) {
103504203a83SThomas Cort 					warnx(
103604203a83SThomas Cort 				    "Unsupported transfer encoding `%s'",
103704203a83SThomas Cort 					    token);
103804203a83SThomas Cort 					goto cleanup_fetch_url;
103904203a83SThomas Cort 				}
104004203a83SThomas Cort 				ischunked++;
1041*84d9c625SLionel Sambuc 				DPRINTF("%s: using chunked encoding\n",
1042*84d9c625SLionel Sambuc 				    __func__);
104304203a83SThomas Cort 
104404203a83SThomas Cort 			} else if (match_token(&cp, "Proxy-Authenticate:")
104504203a83SThomas Cort 				|| match_token(&cp, "WWW-Authenticate:")) {
104604203a83SThomas Cort 				if (! (token = match_token(&cp, "Basic"))) {
1047*84d9c625SLionel Sambuc 					DPRINTF("%s: skipping unknown auth "
1048*84d9c625SLionel Sambuc 					    "scheme `%s'\n", __func__, token);
104904203a83SThomas Cort 					continue;
105004203a83SThomas Cort 				}
105104203a83SThomas Cort 				FREEPTR(auth);
105204203a83SThomas Cort 				auth = ftp_strdup(token);
1053*84d9c625SLionel Sambuc 				DPRINTF("%s: parsed auth as `%s'\n",
1054*84d9c625SLionel Sambuc 				    __func__, cp);
105504203a83SThomas Cort 			}
105604203a83SThomas Cort 
105704203a83SThomas Cort 		}
105804203a83SThomas Cort 				/* finished parsing header */
105904203a83SThomas Cort 
106004203a83SThomas Cort 		switch (hcode) {
106104203a83SThomas Cort 		case 200:
106204203a83SThomas Cort 			break;
106304203a83SThomas Cort 		case 206:
106404203a83SThomas Cort 			if (! restart_point) {
106504203a83SThomas Cort 				warnx("Not expecting partial content header");
106604203a83SThomas Cort 				goto cleanup_fetch_url;
106704203a83SThomas Cort 			}
106804203a83SThomas Cort 			break;
106904203a83SThomas Cort 		case 300:
107004203a83SThomas Cort 		case 301:
107104203a83SThomas Cort 		case 302:
107204203a83SThomas Cort 		case 303:
107304203a83SThomas Cort 		case 305:
107404203a83SThomas Cort 		case 307:
107504203a83SThomas Cort 			if (EMPTYSTRING(location)) {
107604203a83SThomas Cort 				warnx(
107704203a83SThomas Cort 				"No redirection Location provided by server");
107804203a83SThomas Cort 				goto cleanup_fetch_url;
107904203a83SThomas Cort 			}
108004203a83SThomas Cort 			if (redirect_loop++ > 5) {
108104203a83SThomas Cort 				warnx("Too many redirections requested");
108204203a83SThomas Cort 				goto cleanup_fetch_url;
108304203a83SThomas Cort 			}
108404203a83SThomas Cort 			if (hcode == 305) {
108504203a83SThomas Cort 				if (verbose)
108604203a83SThomas Cort 					fprintf(ttyout, "Redirected via %s\n",
108704203a83SThomas Cort 					    location);
108804203a83SThomas Cort 				rval = fetch_url(url, location,
108904203a83SThomas Cort 				    proxyauth, wwwauth);
109004203a83SThomas Cort 			} else {
109104203a83SThomas Cort 				if (verbose)
109204203a83SThomas Cort 					fprintf(ttyout, "Redirected to %s\n",
109304203a83SThomas Cort 					    location);
109404203a83SThomas Cort 				rval = go_fetch(location);
109504203a83SThomas Cort 			}
109604203a83SThomas Cort 			goto cleanup_fetch_url;
109704203a83SThomas Cort #ifndef NO_AUTH
109804203a83SThomas Cort 		case 401:
109904203a83SThomas Cort 		case 407:
110004203a83SThomas Cort 		    {
110104203a83SThomas Cort 			char **authp;
110204203a83SThomas Cort 			char *auser, *apass;
110304203a83SThomas Cort 
110404203a83SThomas Cort 			if (hcode == 401) {
110504203a83SThomas Cort 				authp = &wwwauth;
110604203a83SThomas Cort 				auser = uuser;
110704203a83SThomas Cort 				apass = pass;
110804203a83SThomas Cort 			} else {
110904203a83SThomas Cort 				authp = &proxyauth;
111004203a83SThomas Cort 				auser = puser;
111104203a83SThomas Cort 				apass = ppass;
111204203a83SThomas Cort 			}
111304203a83SThomas Cort 			if (verbose || *authp == NULL ||
111404203a83SThomas Cort 			    auser == NULL || apass == NULL)
111504203a83SThomas Cort 				fprintf(ttyout, "%s\n", message);
111604203a83SThomas Cort 			if (EMPTYSTRING(auth)) {
111704203a83SThomas Cort 				warnx(
111804203a83SThomas Cort 			    "No authentication challenge provided by server");
111904203a83SThomas Cort 				goto cleanup_fetch_url;
112004203a83SThomas Cort 			}
112104203a83SThomas Cort 			if (*authp != NULL) {
112204203a83SThomas Cort 				char reply[10];
112304203a83SThomas Cort 
112404203a83SThomas Cort 				fprintf(ttyout,
112504203a83SThomas Cort 				    "Authorization failed. Retry (y/n)? ");
112604203a83SThomas Cort 				if (get_line(stdin, reply, sizeof(reply), NULL)
112704203a83SThomas Cort 				    < 0) {
112804203a83SThomas Cort 					goto cleanup_fetch_url;
112904203a83SThomas Cort 				}
113004203a83SThomas Cort 				if (tolower((unsigned char)reply[0]) != 'y')
113104203a83SThomas Cort 					goto cleanup_fetch_url;
113204203a83SThomas Cort 				auser = NULL;
113304203a83SThomas Cort 				apass = NULL;
113404203a83SThomas Cort 			}
113504203a83SThomas Cort 			if (auth_url(auth, authp, auser, apass) == 0) {
1136*84d9c625SLionel Sambuc 				rval = fetch_url(url, penv,
113704203a83SThomas Cort 				    proxyauth, wwwauth);
113804203a83SThomas Cort 				memset(*authp, 0, strlen(*authp));
113904203a83SThomas Cort 				FREEPTR(*authp);
114004203a83SThomas Cort 			}
114104203a83SThomas Cort 			goto cleanup_fetch_url;
114204203a83SThomas Cort 		    }
114304203a83SThomas Cort #endif
114404203a83SThomas Cort 		default:
114504203a83SThomas Cort 			if (message)
114604203a83SThomas Cort 				warnx("Error retrieving file `%s'", message);
114704203a83SThomas Cort 			else
114804203a83SThomas Cort 				warnx("Unknown error retrieving file");
114904203a83SThomas Cort 			goto cleanup_fetch_url;
115004203a83SThomas Cort 		}
115104203a83SThomas Cort 	}		/* end of ftp:// or http:// specific setup */
115204203a83SThomas Cort 
115304203a83SThomas Cort 			/* Open the output file. */
115404203a83SThomas Cort 	if (strcmp(savefile, "-") == 0) {
115504203a83SThomas Cort 		fout = stdout;
115604203a83SThomas Cort 	} else if (*savefile == '|') {
1157*84d9c625SLionel Sambuc 		oldpipe = xsignal(SIGPIPE, SIG_IGN);
115804203a83SThomas Cort 		fout = popen(savefile + 1, "w");
115904203a83SThomas Cort 		if (fout == NULL) {
116004203a83SThomas Cort 			warn("Can't execute `%s'", savefile + 1);
116104203a83SThomas Cort 			goto cleanup_fetch_url;
116204203a83SThomas Cort 		}
116304203a83SThomas Cort 		closefunc = pclose;
116404203a83SThomas Cort 	} else {
116504203a83SThomas Cort 		if ((rangeend != -1 && rangeend <= restart_point) ||
116604203a83SThomas Cort 		    (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
116704203a83SThomas Cort 			/* already done */
116804203a83SThomas Cort 			if (verbose)
116904203a83SThomas Cort 				fprintf(ttyout, "already done\n");
117004203a83SThomas Cort 			rval = 0;
117104203a83SThomas Cort 			goto cleanup_fetch_url;
117204203a83SThomas Cort 		}
117304203a83SThomas Cort 		if (restart_point && rangestart != -1) {
117404203a83SThomas Cort 			if (entitylen != -1)
117504203a83SThomas Cort 				filesize = entitylen;
117604203a83SThomas Cort 			if (rangestart != restart_point) {
117704203a83SThomas Cort 				warnx(
117804203a83SThomas Cort 				    "Size of `%s' differs from save file `%s'",
117904203a83SThomas Cort 				    url, savefile);
118004203a83SThomas Cort 				goto cleanup_fetch_url;
118104203a83SThomas Cort 			}
118204203a83SThomas Cort 			fout = fopen(savefile, "a");
118304203a83SThomas Cort 		} else
118404203a83SThomas Cort 			fout = fopen(savefile, "w");
118504203a83SThomas Cort 		if (fout == NULL) {
118604203a83SThomas Cort 			warn("Can't open `%s'", savefile);
118704203a83SThomas Cort 			goto cleanup_fetch_url;
118804203a83SThomas Cort 		}
118904203a83SThomas Cort 		closefunc = fclose;
119004203a83SThomas Cort 	}
119104203a83SThomas Cort 
119204203a83SThomas Cort 			/* Trap signals */
1193*84d9c625SLionel Sambuc 	oldquit = xsignal(SIGQUIT, psummary);
1194*84d9c625SLionel Sambuc 	oldint = xsignal(SIGINT, aborthttp);
119504203a83SThomas Cort 
119604203a83SThomas Cort 	assert(rcvbuf_size > 0);
119704203a83SThomas Cort 	if ((size_t)rcvbuf_size > bufsize) {
119804203a83SThomas Cort 		if (xferbuf)
119904203a83SThomas Cort 			(void)free(xferbuf);
120004203a83SThomas Cort 		bufsize = rcvbuf_size;
120104203a83SThomas Cort 		xferbuf = ftp_malloc(bufsize);
120204203a83SThomas Cort 	}
120304203a83SThomas Cort 
120404203a83SThomas Cort 	bytes = 0;
120504203a83SThomas Cort 	hashbytes = mark;
1206*84d9c625SLionel Sambuc 	if (oldalrm) {
1207*84d9c625SLionel Sambuc 		(void)xsignal(SIGALRM, oldalrm);
1208*84d9c625SLionel Sambuc 		oldalrm = NULL;
1209*84d9c625SLionel Sambuc 	}
121004203a83SThomas Cort 	progressmeter(-1);
121104203a83SThomas Cort 
121204203a83SThomas Cort 			/* Finally, suck down the file. */
121304203a83SThomas Cort 	do {
121404203a83SThomas Cort 		long chunksize;
121504203a83SThomas Cort 		short lastchunk;
121604203a83SThomas Cort 
121704203a83SThomas Cort 		chunksize = 0;
121804203a83SThomas Cort 		lastchunk = 0;
121904203a83SThomas Cort 					/* read chunk-size */
122004203a83SThomas Cort 		if (ischunked) {
122104203a83SThomas Cort 			if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
122204203a83SThomas Cort 				warnx("Unexpected EOF reading chunk-size");
122304203a83SThomas Cort 				goto cleanup_fetch_url;
122404203a83SThomas Cort 			}
122504203a83SThomas Cort 			errno = 0;
122604203a83SThomas Cort 			chunksize = strtol(xferbuf, &ep, 16);
122704203a83SThomas Cort 			if (ep == xferbuf) {
122804203a83SThomas Cort 				warnx("Invalid chunk-size");
122904203a83SThomas Cort 				goto cleanup_fetch_url;
123004203a83SThomas Cort 			}
123104203a83SThomas Cort 			if (errno == ERANGE || chunksize < 0) {
123204203a83SThomas Cort 				errno = ERANGE;
123304203a83SThomas Cort 				warn("Chunk-size `%.*s'",
123404203a83SThomas Cort 				    (int)(ep-xferbuf), xferbuf);
123504203a83SThomas Cort 				goto cleanup_fetch_url;
123604203a83SThomas Cort 			}
123704203a83SThomas Cort 
123804203a83SThomas Cort 				/*
123904203a83SThomas Cort 				 * XXX:	Work around bug in Apache 1.3.9 and
124004203a83SThomas Cort 				 *	1.3.11, which incorrectly put trailing
124104203a83SThomas Cort 				 *	space after the chunk-size.
124204203a83SThomas Cort 				 */
124304203a83SThomas Cort 			while (*ep == ' ')
124404203a83SThomas Cort 				ep++;
124504203a83SThomas Cort 
124604203a83SThomas Cort 					/* skip [ chunk-ext ] */
124704203a83SThomas Cort 			if (*ep == ';') {
124804203a83SThomas Cort 				while (*ep && *ep != '\r')
124904203a83SThomas Cort 					ep++;
125004203a83SThomas Cort 			}
125104203a83SThomas Cort 
125204203a83SThomas Cort 			if (strcmp(ep, "\r\n") != 0) {
125304203a83SThomas Cort 				warnx("Unexpected data following chunk-size");
125404203a83SThomas Cort 				goto cleanup_fetch_url;
125504203a83SThomas Cort 			}
1256*84d9c625SLionel Sambuc 			DPRINTF("%s: got chunk-size of " LLF "\n", __func__,
125704203a83SThomas Cort 			    (LLT)chunksize);
125804203a83SThomas Cort 			if (chunksize == 0) {
125904203a83SThomas Cort 				lastchunk = 1;
126004203a83SThomas Cort 				goto chunkdone;
126104203a83SThomas Cort 			}
126204203a83SThomas Cort 		}
126304203a83SThomas Cort 					/* transfer file or chunk */
126404203a83SThomas Cort 		while (1) {
126504203a83SThomas Cort 			struct timeval then, now, td;
1266*84d9c625SLionel Sambuc 			volatile off_t bufrem;
126704203a83SThomas Cort 
126804203a83SThomas Cort 			if (rate_get)
126904203a83SThomas Cort 				(void)gettimeofday(&then, NULL);
127004203a83SThomas Cort 			bufrem = rate_get ? rate_get : (off_t)bufsize;
127104203a83SThomas Cort 			if (ischunked)
127204203a83SThomas Cort 				bufrem = MIN(chunksize, bufrem);
127304203a83SThomas Cort 			while (bufrem > 0) {
127404203a83SThomas Cort 				flen = fetch_read(xferbuf, sizeof(char),
127504203a83SThomas Cort 				    MIN((off_t)bufsize, bufrem), fin);
127604203a83SThomas Cort 				if (flen <= 0)
127704203a83SThomas Cort 					goto chunkdone;
127804203a83SThomas Cort 				bytes += flen;
127904203a83SThomas Cort 				bufrem -= flen;
128004203a83SThomas Cort 				if (fwrite(xferbuf, sizeof(char), flen, fout)
128104203a83SThomas Cort 				    != flen) {
128204203a83SThomas Cort 					warn("Writing `%s'", savefile);
128304203a83SThomas Cort 					goto cleanup_fetch_url;
128404203a83SThomas Cort 				}
128504203a83SThomas Cort 				if (hash && !progress) {
128604203a83SThomas Cort 					while (bytes >= hashbytes) {
128704203a83SThomas Cort 						(void)putc('#', ttyout);
128804203a83SThomas Cort 						hashbytes += mark;
128904203a83SThomas Cort 					}
129004203a83SThomas Cort 					(void)fflush(ttyout);
129104203a83SThomas Cort 				}
129204203a83SThomas Cort 				if (ischunked) {
129304203a83SThomas Cort 					chunksize -= flen;
129404203a83SThomas Cort 					if (chunksize <= 0)
129504203a83SThomas Cort 						break;
129604203a83SThomas Cort 				}
129704203a83SThomas Cort 			}
129804203a83SThomas Cort 			if (rate_get) {
129904203a83SThomas Cort 				while (1) {
130004203a83SThomas Cort 					(void)gettimeofday(&now, NULL);
130104203a83SThomas Cort 					timersub(&now, &then, &td);
130204203a83SThomas Cort 					if (td.tv_sec > 0)
130304203a83SThomas Cort 						break;
130404203a83SThomas Cort 					usleep(1000000 - td.tv_usec);
130504203a83SThomas Cort 				}
130604203a83SThomas Cort 			}
130704203a83SThomas Cort 			if (ischunked && chunksize <= 0)
130804203a83SThomas Cort 				break;
130904203a83SThomas Cort 		}
131004203a83SThomas Cort 					/* read CRLF after chunk*/
131104203a83SThomas Cort  chunkdone:
131204203a83SThomas Cort 		if (ischunked) {
131304203a83SThomas Cort 			if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
1314*84d9c625SLionel Sambuc 				alarmtimer(0);
131504203a83SThomas Cort 				warnx("Unexpected EOF reading chunk CRLF");
131604203a83SThomas Cort 				goto cleanup_fetch_url;
131704203a83SThomas Cort 			}
131804203a83SThomas Cort 			if (strcmp(xferbuf, "\r\n") != 0) {
131904203a83SThomas Cort 				warnx("Unexpected data following chunk");
132004203a83SThomas Cort 				goto cleanup_fetch_url;
132104203a83SThomas Cort 			}
132204203a83SThomas Cort 			if (lastchunk)
132304203a83SThomas Cort 				break;
132404203a83SThomas Cort 		}
132504203a83SThomas Cort 	} while (ischunked);
132604203a83SThomas Cort 
132704203a83SThomas Cort /* XXX: deal with optional trailer & CRLF here? */
132804203a83SThomas Cort 
132904203a83SThomas Cort 	if (hash && !progress && bytes > 0) {
133004203a83SThomas Cort 		if (bytes < mark)
133104203a83SThomas Cort 			(void)putc('#', ttyout);
133204203a83SThomas Cort 		(void)putc('\n', ttyout);
133304203a83SThomas Cort 	}
133404203a83SThomas Cort 	if (fetch_error(fin)) {
133504203a83SThomas Cort 		warn("Reading file");
133604203a83SThomas Cort 		goto cleanup_fetch_url;
133704203a83SThomas Cort 	}
133804203a83SThomas Cort 	progressmeter(1);
133904203a83SThomas Cort 	(void)fflush(fout);
134004203a83SThomas Cort 	if (closefunc == fclose && mtime != -1) {
134104203a83SThomas Cort 		struct timeval tval[2];
134204203a83SThomas Cort 
134304203a83SThomas Cort 		(void)gettimeofday(&tval[0], NULL);
134404203a83SThomas Cort 		tval[1].tv_sec = mtime;
134504203a83SThomas Cort 		tval[1].tv_usec = 0;
134604203a83SThomas Cort 		(*closefunc)(fout);
134704203a83SThomas Cort 		fout = NULL;
134804203a83SThomas Cort 
134904203a83SThomas Cort 		if (utimes(savefile, tval) == -1) {
135004203a83SThomas Cort 			fprintf(ttyout,
135104203a83SThomas Cort 			    "Can't change modification time to %s",
135204203a83SThomas Cort 			    rfc2822time(localtime(&mtime)));
135304203a83SThomas Cort 		}
135404203a83SThomas Cort 	}
135504203a83SThomas Cort 	if (bytes > 0)
135604203a83SThomas Cort 		ptransfer(0);
135704203a83SThomas Cort 	bytes = 0;
135804203a83SThomas Cort 
135904203a83SThomas Cort 	rval = 0;
136004203a83SThomas Cort 	goto cleanup_fetch_url;
136104203a83SThomas Cort 
136204203a83SThomas Cort  improper:
136304203a83SThomas Cort 	warnx("Improper response from `%s:%s'", host, port);
136404203a83SThomas Cort 
136504203a83SThomas Cort  cleanup_fetch_url:
1366*84d9c625SLionel Sambuc 	if (oldint)
1367*84d9c625SLionel Sambuc 		(void)xsignal(SIGINT, oldint);
1368*84d9c625SLionel Sambuc 	if (oldpipe)
1369*84d9c625SLionel Sambuc 		(void)xsignal(SIGPIPE, oldpipe);
1370*84d9c625SLionel Sambuc 	if (oldalrm)
1371*84d9c625SLionel Sambuc 		(void)xsignal(SIGALRM, oldalrm);
1372*84d9c625SLionel Sambuc 	if (oldquit)
1373*84d9c625SLionel Sambuc 		(void)xsignal(SIGQUIT, oldpipe);
137404203a83SThomas Cort 	if (fin != NULL)
137504203a83SThomas Cort 		fetch_close(fin);
137604203a83SThomas Cort 	else if (s != -1)
137704203a83SThomas Cort 		close(s);
137804203a83SThomas Cort 	if (closefunc != NULL && fout != NULL)
137904203a83SThomas Cort 		(*closefunc)(fout);
138004203a83SThomas Cort 	if (res0)
138104203a83SThomas Cort 		freeaddrinfo(res0);
138204203a83SThomas Cort 	FREEPTR(savefile);
138304203a83SThomas Cort 	FREEPTR(uuser);
138404203a83SThomas Cort 	if (pass != NULL)
138504203a83SThomas Cort 		memset(pass, 0, strlen(pass));
138604203a83SThomas Cort 	FREEPTR(pass);
138704203a83SThomas Cort 	FREEPTR(host);
138804203a83SThomas Cort 	FREEPTR(port);
138904203a83SThomas Cort 	FREEPTR(path);
139004203a83SThomas Cort 	FREEPTR(decodedpath);
139104203a83SThomas Cort 	FREEPTR(puser);
139204203a83SThomas Cort 	if (ppass != NULL)
139304203a83SThomas Cort 		memset(ppass, 0, strlen(ppass));
139404203a83SThomas Cort 	FREEPTR(ppass);
139504203a83SThomas Cort 	FREEPTR(auth);
139604203a83SThomas Cort 	FREEPTR(location);
139704203a83SThomas Cort 	FREEPTR(message);
139804203a83SThomas Cort 	return (rval);
139904203a83SThomas Cort }
140004203a83SThomas Cort 
140104203a83SThomas Cort /*
140204203a83SThomas Cort  * Abort a HTTP retrieval
140304203a83SThomas Cort  */
140404203a83SThomas Cort static void
140504203a83SThomas Cort aborthttp(int notused)
140604203a83SThomas Cort {
140704203a83SThomas Cort 	char msgbuf[100];
1408*84d9c625SLionel Sambuc 	int len;
140904203a83SThomas Cort 
141004203a83SThomas Cort 	sigint_raised = 1;
141104203a83SThomas Cort 	alarmtimer(0);
1412*84d9c625SLionel Sambuc 	if (fromatty) {
1413*84d9c625SLionel Sambuc 		len = snprintf(msgbuf, sizeof(msgbuf),
1414*84d9c625SLionel Sambuc 		    "\n%s: HTTP fetch aborted.\n", getprogname());
1415*84d9c625SLionel Sambuc 		if (len > 0)
141604203a83SThomas Cort 			write(fileno(ttyout), msgbuf, len);
1417*84d9c625SLionel Sambuc 	}
1418*84d9c625SLionel Sambuc 	siglongjmp(httpabort, 1);
1419*84d9c625SLionel Sambuc }
1420*84d9c625SLionel Sambuc 
1421*84d9c625SLionel Sambuc static void
1422*84d9c625SLionel Sambuc timeouthttp(int notused)
1423*84d9c625SLionel Sambuc {
1424*84d9c625SLionel Sambuc 	char msgbuf[100];
1425*84d9c625SLionel Sambuc 	int len;
1426*84d9c625SLionel Sambuc 
1427*84d9c625SLionel Sambuc 	alarmtimer(0);
1428*84d9c625SLionel Sambuc 	if (fromatty) {
1429*84d9c625SLionel Sambuc 		len = snprintf(msgbuf, sizeof(msgbuf),
1430*84d9c625SLionel Sambuc 		    "\n%s: HTTP fetch timeout.\n", getprogname());
1431*84d9c625SLionel Sambuc 		if (len > 0)
1432*84d9c625SLionel Sambuc 			write(fileno(ttyout), msgbuf, len);
1433*84d9c625SLionel Sambuc 	}
143404203a83SThomas Cort 	siglongjmp(httpabort, 1);
143504203a83SThomas Cort }
143604203a83SThomas Cort 
143704203a83SThomas Cort /*
143804203a83SThomas Cort  * Retrieve ftp URL or classic ftp argument using FTP.
143904203a83SThomas Cort  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
144004203a83SThomas Cort  * is still open (e.g, ftp xfer with trailing /)
144104203a83SThomas Cort  */
144204203a83SThomas Cort static int
144304203a83SThomas Cort fetch_ftp(const char *url)
144404203a83SThomas Cort {
144504203a83SThomas Cort 	char		*cp, *xargv[5], rempath[MAXPATHLEN];
144604203a83SThomas Cort 	char		*host, *path, *dir, *file, *uuser, *pass;
144704203a83SThomas Cort 	char		*port;
144804203a83SThomas Cort 	char		 cmdbuf[MAXPATHLEN];
144904203a83SThomas Cort 	char		 dirbuf[4];
145004203a83SThomas Cort 	int		 dirhasglob, filehasglob, rval, transtype, xargc;
145104203a83SThomas Cort 	int		 oanonftp, oautologin;
145204203a83SThomas Cort 	in_port_t	 portnum;
145304203a83SThomas Cort 	url_t		 urltype;
145404203a83SThomas Cort 
145504203a83SThomas Cort 	DPRINTF("fetch_ftp: `%s'\n", url);
145604203a83SThomas Cort 	host = path = dir = file = uuser = pass = NULL;
145704203a83SThomas Cort 	port = NULL;
145804203a83SThomas Cort 	rval = 1;
145904203a83SThomas Cort 	transtype = TYPE_I;
146004203a83SThomas Cort 
146104203a83SThomas Cort 	if (STRNEQUAL(url, FTP_URL)) {
146204203a83SThomas Cort 		if ((parse_url(url, "URL", &urltype, &uuser, &pass,
146304203a83SThomas Cort 		    &host, &port, &portnum, &path) == -1) ||
146404203a83SThomas Cort 		    (uuser != NULL && *uuser == '\0') ||
146504203a83SThomas Cort 		    EMPTYSTRING(host)) {
146604203a83SThomas Cort 			warnx("Invalid URL `%s'", url);
146704203a83SThomas Cort 			goto cleanup_fetch_ftp;
146804203a83SThomas Cort 		}
146904203a83SThomas Cort 		/*
147004203a83SThomas Cort 		 * Note: Don't url_decode(path) here.  We need to keep the
147104203a83SThomas Cort 		 * distinction between "/" and "%2F" until later.
147204203a83SThomas Cort 		 */
147304203a83SThomas Cort 
147404203a83SThomas Cort 					/* check for trailing ';type=[aid]' */
147504203a83SThomas Cort 		if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
147604203a83SThomas Cort 			if (strcasecmp(cp, ";type=a") == 0)
147704203a83SThomas Cort 				transtype = TYPE_A;
147804203a83SThomas Cort 			else if (strcasecmp(cp, ";type=i") == 0)
147904203a83SThomas Cort 				transtype = TYPE_I;
148004203a83SThomas Cort 			else if (strcasecmp(cp, ";type=d") == 0) {
148104203a83SThomas Cort 				warnx(
148204203a83SThomas Cort 			    "Directory listing via a URL is not supported");
148304203a83SThomas Cort 				goto cleanup_fetch_ftp;
148404203a83SThomas Cort 			} else {
148504203a83SThomas Cort 				warnx("Invalid suffix `%s' in URL `%s'", cp,
148604203a83SThomas Cort 				    url);
148704203a83SThomas Cort 				goto cleanup_fetch_ftp;
148804203a83SThomas Cort 			}
148904203a83SThomas Cort 			*cp = 0;
149004203a83SThomas Cort 		}
149104203a83SThomas Cort 	} else {			/* classic style `[user@]host:[file]' */
149204203a83SThomas Cort 		urltype = CLASSIC_URL_T;
149304203a83SThomas Cort 		host = ftp_strdup(url);
149404203a83SThomas Cort 		cp = strchr(host, '@');
149504203a83SThomas Cort 		if (cp != NULL) {
149604203a83SThomas Cort 			*cp = '\0';
149704203a83SThomas Cort 			uuser = host;
149804203a83SThomas Cort 			anonftp = 0;	/* disable anonftp */
149904203a83SThomas Cort 			host = ftp_strdup(cp + 1);
150004203a83SThomas Cort 		}
150104203a83SThomas Cort 		cp = strchr(host, ':');
150204203a83SThomas Cort 		if (cp != NULL) {
150304203a83SThomas Cort 			*cp = '\0';
150404203a83SThomas Cort 			path = ftp_strdup(cp + 1);
150504203a83SThomas Cort 		}
150604203a83SThomas Cort 	}
150704203a83SThomas Cort 	if (EMPTYSTRING(host))
150804203a83SThomas Cort 		goto cleanup_fetch_ftp;
150904203a83SThomas Cort 
151004203a83SThomas Cort 			/* Extract the file and (if present) directory name. */
151104203a83SThomas Cort 	dir = path;
151204203a83SThomas Cort 	if (! EMPTYSTRING(dir)) {
151304203a83SThomas Cort 		/*
151404203a83SThomas Cort 		 * If we are dealing with classic `[user@]host:[path]' syntax,
151504203a83SThomas Cort 		 * then a path of the form `/file' (resulting from input of the
151604203a83SThomas Cort 		 * form `host:/file') means that we should do "CWD /" before
151704203a83SThomas Cort 		 * retrieving the file.  So we set dir="/" and file="file".
151804203a83SThomas Cort 		 *
151904203a83SThomas Cort 		 * But if we are dealing with URLs like `ftp://host/path' then
152004203a83SThomas Cort 		 * a path of the form `/file' (resulting from a URL of the form
152104203a83SThomas Cort 		 * `ftp://host//file') means that we should do `CWD ' (with an
152204203a83SThomas Cort 		 * empty argument) before retrieving the file.  So we set
152304203a83SThomas Cort 		 * dir="" and file="file".
152404203a83SThomas Cort 		 *
152504203a83SThomas Cort 		 * If the path does not contain / at all, we set dir=NULL.
152604203a83SThomas Cort 		 * (We get a path without any slashes if we are dealing with
152704203a83SThomas Cort 		 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
152804203a83SThomas Cort 		 *
152904203a83SThomas Cort 		 * In all other cases, we set dir to a string that does not
153004203a83SThomas Cort 		 * include the final '/' that separates the dir part from the
153104203a83SThomas Cort 		 * file part of the path.  (This will be the empty string if
153204203a83SThomas Cort 		 * and only if we are dealing with a path of the form `/file'
153304203a83SThomas Cort 		 * resulting from an URL of the form `ftp://host//file'.)
153404203a83SThomas Cort 		 */
153504203a83SThomas Cort 		cp = strrchr(dir, '/');
153604203a83SThomas Cort 		if (cp == dir && urltype == CLASSIC_URL_T) {
153704203a83SThomas Cort 			file = cp + 1;
153804203a83SThomas Cort 			(void)strlcpy(dirbuf, "/", sizeof(dirbuf));
153904203a83SThomas Cort 			dir = dirbuf;
154004203a83SThomas Cort 		} else if (cp != NULL) {
154104203a83SThomas Cort 			*cp++ = '\0';
154204203a83SThomas Cort 			file = cp;
154304203a83SThomas Cort 		} else {
154404203a83SThomas Cort 			file = dir;
154504203a83SThomas Cort 			dir = NULL;
154604203a83SThomas Cort 		}
154704203a83SThomas Cort 	} else
154804203a83SThomas Cort 		dir = NULL;
154904203a83SThomas Cort 	if (urltype == FTP_URL_T && file != NULL) {
155004203a83SThomas Cort 		url_decode(file);
155104203a83SThomas Cort 		/* but still don't url_decode(dir) */
155204203a83SThomas Cort 	}
155304203a83SThomas Cort 	DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
155404203a83SThomas Cort 	    "path `%s' dir `%s' file `%s'\n",
155504203a83SThomas Cort 	    STRorNULL(uuser), STRorNULL(pass),
155604203a83SThomas Cort 	    STRorNULL(host), STRorNULL(port),
155704203a83SThomas Cort 	    STRorNULL(path), STRorNULL(dir), STRorNULL(file));
155804203a83SThomas Cort 
155904203a83SThomas Cort 	dirhasglob = filehasglob = 0;
156004203a83SThomas Cort 	if (doglob && urltype == CLASSIC_URL_T) {
156104203a83SThomas Cort 		if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
156204203a83SThomas Cort 			dirhasglob = 1;
156304203a83SThomas Cort 		if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
156404203a83SThomas Cort 			filehasglob = 1;
156504203a83SThomas Cort 	}
156604203a83SThomas Cort 
156704203a83SThomas Cort 			/* Set up the connection */
156804203a83SThomas Cort 	oanonftp = anonftp;
156904203a83SThomas Cort 	if (connected)
157004203a83SThomas Cort 		disconnect(0, NULL);
157104203a83SThomas Cort 	anonftp = oanonftp;
157204203a83SThomas Cort 	(void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
157304203a83SThomas Cort 	xargv[0] = cmdbuf;
157404203a83SThomas Cort 	xargv[1] = host;
157504203a83SThomas Cort 	xargv[2] = NULL;
157604203a83SThomas Cort 	xargc = 2;
157704203a83SThomas Cort 	if (port) {
157804203a83SThomas Cort 		xargv[2] = port;
157904203a83SThomas Cort 		xargv[3] = NULL;
158004203a83SThomas Cort 		xargc = 3;
158104203a83SThomas Cort 	}
158204203a83SThomas Cort 	oautologin = autologin;
158304203a83SThomas Cort 		/* don't autologin in setpeer(), use ftp_login() below */
158404203a83SThomas Cort 	autologin = 0;
158504203a83SThomas Cort 	setpeer(xargc, xargv);
158604203a83SThomas Cort 	autologin = oautologin;
158704203a83SThomas Cort 	if ((connected == 0) ||
158804203a83SThomas Cort 	    (connected == 1 && !ftp_login(host, uuser, pass))) {
158904203a83SThomas Cort 		warnx("Can't connect or login to host `%s:%s'",
159004203a83SThomas Cort 			host, port ? port : "?");
159104203a83SThomas Cort 		goto cleanup_fetch_ftp;
159204203a83SThomas Cort 	}
159304203a83SThomas Cort 
159404203a83SThomas Cort 	switch (transtype) {
159504203a83SThomas Cort 	case TYPE_A:
159604203a83SThomas Cort 		setascii(1, xargv);
159704203a83SThomas Cort 		break;
159804203a83SThomas Cort 	case TYPE_I:
159904203a83SThomas Cort 		setbinary(1, xargv);
160004203a83SThomas Cort 		break;
160104203a83SThomas Cort 	default:
160204203a83SThomas Cort 		errx(1, "fetch_ftp: unknown transfer type %d", transtype);
160304203a83SThomas Cort 	}
160404203a83SThomas Cort 
160504203a83SThomas Cort 		/*
160604203a83SThomas Cort 		 * Change directories, if necessary.
160704203a83SThomas Cort 		 *
160804203a83SThomas Cort 		 * Note: don't use EMPTYSTRING(dir) below, because
160904203a83SThomas Cort 		 * dir=="" means something different from dir==NULL.
161004203a83SThomas Cort 		 */
161104203a83SThomas Cort 	if (dir != NULL && !dirhasglob) {
161204203a83SThomas Cort 		char *nextpart;
161304203a83SThomas Cort 
161404203a83SThomas Cort 		/*
161504203a83SThomas Cort 		 * If we are dealing with a classic `[user@]host:[path]'
161604203a83SThomas Cort 		 * (urltype is CLASSIC_URL_T) then we have a raw directory
161704203a83SThomas Cort 		 * name (not encoded in any way) and we can change
161804203a83SThomas Cort 		 * directories in one step.
161904203a83SThomas Cort 		 *
162004203a83SThomas Cort 		 * If we are dealing with an `ftp://host/path' URL
162104203a83SThomas Cort 		 * (urltype is FTP_URL_T), then RFC 3986 says we need to
162204203a83SThomas Cort 		 * send a separate CWD command for each unescaped "/"
162304203a83SThomas Cort 		 * in the path, and we have to interpret %hex escaping
162404203a83SThomas Cort 		 * *after* we find the slashes.  It's possible to get
162504203a83SThomas Cort 		 * empty components here, (from multiple adjacent
162604203a83SThomas Cort 		 * slashes in the path) and RFC 3986 says that we should
162704203a83SThomas Cort 		 * still do `CWD ' (with a null argument) in such cases.
162804203a83SThomas Cort 		 *
162904203a83SThomas Cort 		 * Many ftp servers don't support `CWD ', so if there's an
163004203a83SThomas Cort 		 * error performing that command, bail out with a descriptive
163104203a83SThomas Cort 		 * message.
163204203a83SThomas Cort 		 *
163304203a83SThomas Cort 		 * Examples:
163404203a83SThomas Cort 		 *
163504203a83SThomas Cort 		 * host:			dir="", urltype=CLASSIC_URL_T
163604203a83SThomas Cort 		 *		logged in (to default directory)
163704203a83SThomas Cort 		 * host:file			dir=NULL, urltype=CLASSIC_URL_T
163804203a83SThomas Cort 		 *		"RETR file"
163904203a83SThomas Cort 		 * host:dir/			dir="dir", urltype=CLASSIC_URL_T
164004203a83SThomas Cort 		 *		"CWD dir", logged in
164104203a83SThomas Cort 		 * ftp://host/			dir="", urltype=FTP_URL_T
164204203a83SThomas Cort 		 *		logged in (to default directory)
164304203a83SThomas Cort 		 * ftp://host/dir/		dir="dir", urltype=FTP_URL_T
164404203a83SThomas Cort 		 *		"CWD dir", logged in
164504203a83SThomas Cort 		 * ftp://host/file		dir=NULL, urltype=FTP_URL_T
164604203a83SThomas Cort 		 *		"RETR file"
164704203a83SThomas Cort 		 * ftp://host//file		dir="", urltype=FTP_URL_T
164804203a83SThomas Cort 		 *		"CWD ", "RETR file"
164904203a83SThomas Cort 		 * host:/file			dir="/", urltype=CLASSIC_URL_T
165004203a83SThomas Cort 		 *		"CWD /", "RETR file"
165104203a83SThomas Cort 		 * ftp://host///file		dir="/", urltype=FTP_URL_T
165204203a83SThomas Cort 		 *		"CWD ", "CWD ", "RETR file"
165304203a83SThomas Cort 		 * ftp://host/%2F/file		dir="%2F", urltype=FTP_URL_T
165404203a83SThomas Cort 		 *		"CWD /", "RETR file"
165504203a83SThomas Cort 		 * ftp://host/foo/file		dir="foo", urltype=FTP_URL_T
165604203a83SThomas Cort 		 *		"CWD foo", "RETR file"
165704203a83SThomas Cort 		 * ftp://host/foo/bar/file	dir="foo/bar"
165804203a83SThomas Cort 		 *		"CWD foo", "CWD bar", "RETR file"
165904203a83SThomas Cort 		 * ftp://host//foo/bar/file	dir="/foo/bar"
166004203a83SThomas Cort 		 *		"CWD ", "CWD foo", "CWD bar", "RETR file"
166104203a83SThomas Cort 		 * ftp://host/foo//bar/file	dir="foo//bar"
166204203a83SThomas Cort 		 *		"CWD foo", "CWD ", "CWD bar", "RETR file"
166304203a83SThomas Cort 		 * ftp://host/%2F/foo/bar/file	dir="%2F/foo/bar"
166404203a83SThomas Cort 		 *		"CWD /", "CWD foo", "CWD bar", "RETR file"
166504203a83SThomas Cort 		 * ftp://host/%2Ffoo/bar/file	dir="%2Ffoo/bar"
166604203a83SThomas Cort 		 *		"CWD /foo", "CWD bar", "RETR file"
166704203a83SThomas Cort 		 * ftp://host/%2Ffoo%2Fbar/file	dir="%2Ffoo%2Fbar"
166804203a83SThomas Cort 		 *		"CWD /foo/bar", "RETR file"
166904203a83SThomas Cort 		 * ftp://host/%2Ffoo%2Fbar%2Ffile	dir=NULL
167004203a83SThomas Cort 		 *		"RETR /foo/bar/file"
167104203a83SThomas Cort 		 *
167204203a83SThomas Cort 		 * Note that we don't need `dir' after this point.
167304203a83SThomas Cort 		 */
167404203a83SThomas Cort 		do {
167504203a83SThomas Cort 			if (urltype == FTP_URL_T) {
167604203a83SThomas Cort 				nextpart = strchr(dir, '/');
167704203a83SThomas Cort 				if (nextpart) {
167804203a83SThomas Cort 					*nextpart = '\0';
167904203a83SThomas Cort 					nextpart++;
168004203a83SThomas Cort 				}
168104203a83SThomas Cort 				url_decode(dir);
168204203a83SThomas Cort 			} else
168304203a83SThomas Cort 				nextpart = NULL;
168404203a83SThomas Cort 			DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
168504203a83SThomas Cort 			    STRorNULL(dir), STRorNULL(nextpart));
168604203a83SThomas Cort 			if (urltype == FTP_URL_T || *dir != '\0') {
168704203a83SThomas Cort 				(void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
168804203a83SThomas Cort 				xargv[0] = cmdbuf;
168904203a83SThomas Cort 				xargv[1] = dir;
169004203a83SThomas Cort 				xargv[2] = NULL;
169104203a83SThomas Cort 				dirchange = 0;
169204203a83SThomas Cort 				cd(2, xargv);
169304203a83SThomas Cort 				if (! dirchange) {
169404203a83SThomas Cort 					if (*dir == '\0' && code == 500)
169504203a83SThomas Cort 						fprintf(stderr,
169604203a83SThomas Cort "\n"
169704203a83SThomas Cort "ftp: The `CWD ' command (without a directory), which is required by\n"
169804203a83SThomas Cort "     RFC 3986 to support the empty directory in the URL pathname (`//'),\n"
169904203a83SThomas Cort "     conflicts with the server's conformance to RFC 959.\n"
170004203a83SThomas Cort "     Try the same URL without the `//' in the URL pathname.\n"
170104203a83SThomas Cort "\n");
170204203a83SThomas Cort 					goto cleanup_fetch_ftp;
170304203a83SThomas Cort 				}
170404203a83SThomas Cort 			}
170504203a83SThomas Cort 			dir = nextpart;
170604203a83SThomas Cort 		} while (dir != NULL);
170704203a83SThomas Cort 	}
170804203a83SThomas Cort 
170904203a83SThomas Cort 	if (EMPTYSTRING(file)) {
171004203a83SThomas Cort 		rval = -1;
171104203a83SThomas Cort 		goto cleanup_fetch_ftp;
171204203a83SThomas Cort 	}
171304203a83SThomas Cort 
171404203a83SThomas Cort 	if (dirhasglob) {
171504203a83SThomas Cort 		(void)strlcpy(rempath, dir,	sizeof(rempath));
171604203a83SThomas Cort 		(void)strlcat(rempath, "/",	sizeof(rempath));
171704203a83SThomas Cort 		(void)strlcat(rempath, file,	sizeof(rempath));
171804203a83SThomas Cort 		file = rempath;
171904203a83SThomas Cort 	}
172004203a83SThomas Cort 
172104203a83SThomas Cort 			/* Fetch the file(s). */
172204203a83SThomas Cort 	xargc = 2;
172304203a83SThomas Cort 	(void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
172404203a83SThomas Cort 	xargv[0] = cmdbuf;
172504203a83SThomas Cort 	xargv[1] = file;
172604203a83SThomas Cort 	xargv[2] = NULL;
172704203a83SThomas Cort 	if (dirhasglob || filehasglob) {
172804203a83SThomas Cort 		int ointeractive;
172904203a83SThomas Cort 
173004203a83SThomas Cort 		ointeractive = interactive;
173104203a83SThomas Cort 		interactive = 0;
173204203a83SThomas Cort 		if (restartautofetch)
173304203a83SThomas Cort 			(void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf));
173404203a83SThomas Cort 		else
173504203a83SThomas Cort 			(void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf));
173604203a83SThomas Cort 		xargv[0] = cmdbuf;
173704203a83SThomas Cort 		mget(xargc, xargv);
173804203a83SThomas Cort 		interactive = ointeractive;
173904203a83SThomas Cort 	} else {
174004203a83SThomas Cort 		if (outfile == NULL) {
174104203a83SThomas Cort 			cp = strrchr(file, '/');	/* find savefile */
174204203a83SThomas Cort 			if (cp != NULL)
174304203a83SThomas Cort 				outfile = cp + 1;
174404203a83SThomas Cort 			else
174504203a83SThomas Cort 				outfile = file;
174604203a83SThomas Cort 		}
174704203a83SThomas Cort 		xargv[2] = (char *)outfile;
174804203a83SThomas Cort 		xargv[3] = NULL;
174904203a83SThomas Cort 		xargc++;
175004203a83SThomas Cort 		if (restartautofetch)
175104203a83SThomas Cort 			reget(xargc, xargv);
175204203a83SThomas Cort 		else
175304203a83SThomas Cort 			get(xargc, xargv);
175404203a83SThomas Cort 	}
175504203a83SThomas Cort 
175604203a83SThomas Cort 	if ((code / 100) == COMPLETE)
175704203a83SThomas Cort 		rval = 0;
175804203a83SThomas Cort 
175904203a83SThomas Cort  cleanup_fetch_ftp:
176004203a83SThomas Cort 	FREEPTR(port);
176104203a83SThomas Cort 	FREEPTR(host);
176204203a83SThomas Cort 	FREEPTR(path);
176304203a83SThomas Cort 	FREEPTR(uuser);
176404203a83SThomas Cort 	if (pass)
176504203a83SThomas Cort 		memset(pass, 0, strlen(pass));
176604203a83SThomas Cort 	FREEPTR(pass);
176704203a83SThomas Cort 	return (rval);
176804203a83SThomas Cort }
176904203a83SThomas Cort 
177004203a83SThomas Cort /*
177104203a83SThomas Cort  * Retrieve the given file to outfile.
177204203a83SThomas Cort  * Supports arguments of the form:
177304203a83SThomas Cort  *	"host:path", "ftp://host/path"	if $ftpproxy, call fetch_url() else
177404203a83SThomas Cort  *					call fetch_ftp()
177504203a83SThomas Cort  *	"http://host/path"		call fetch_url() to use HTTP
177604203a83SThomas Cort  *	"file:///path"			call fetch_url() to copy
177704203a83SThomas Cort  *	"about:..."			print a message
177804203a83SThomas Cort  *
177904203a83SThomas Cort  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
178004203a83SThomas Cort  * is still open (e.g, ftp xfer with trailing /)
178104203a83SThomas Cort  */
178204203a83SThomas Cort static int
178304203a83SThomas Cort go_fetch(const char *url)
178404203a83SThomas Cort {
178504203a83SThomas Cort 	char *proxyenv;
178604203a83SThomas Cort 	char *p;
178704203a83SThomas Cort 
178804203a83SThomas Cort #ifndef NO_ABOUT
178904203a83SThomas Cort 	/*
179004203a83SThomas Cort 	 * Check for about:*
179104203a83SThomas Cort 	 */
179204203a83SThomas Cort 	if (STRNEQUAL(url, ABOUT_URL)) {
179304203a83SThomas Cort 		url += sizeof(ABOUT_URL) -1;
179404203a83SThomas Cort 		if (strcasecmp(url, "ftp") == 0 ||
179504203a83SThomas Cort 		    strcasecmp(url, "tnftp") == 0) {
179604203a83SThomas Cort 			fputs(
179704203a83SThomas Cort "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
179804203a83SThomas Cort "for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
179904203a83SThomas Cort 		} else if (strcasecmp(url, "lukem") == 0) {
180004203a83SThomas Cort 			fputs(
180104203a83SThomas Cort "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
180204203a83SThomas Cort "Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
180304203a83SThomas Cort 		} else if (strcasecmp(url, "netbsd") == 0) {
180404203a83SThomas Cort 			fputs(
180504203a83SThomas Cort "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
180604203a83SThomas Cort "For more information, see http://www.NetBSD.org/\n", ttyout);
180704203a83SThomas Cort 		} else if (strcasecmp(url, "version") == 0) {
180804203a83SThomas Cort 			fprintf(ttyout, "Version: %s %s%s\n",
180904203a83SThomas Cort 			    FTP_PRODUCT, FTP_VERSION,
181004203a83SThomas Cort #ifdef INET6
181104203a83SThomas Cort 			    ""
181204203a83SThomas Cort #else
181304203a83SThomas Cort 			    " (-IPv6)"
181404203a83SThomas Cort #endif
181504203a83SThomas Cort 			);
181604203a83SThomas Cort 		} else {
181704203a83SThomas Cort 			fprintf(ttyout, "`%s' is an interesting topic.\n", url);
181804203a83SThomas Cort 		}
181904203a83SThomas Cort 		fputs("\n", ttyout);
182004203a83SThomas Cort 		return (0);
182104203a83SThomas Cort 	}
182204203a83SThomas Cort #endif
182304203a83SThomas Cort 
182404203a83SThomas Cort 	/*
182504203a83SThomas Cort 	 * Check for file:// and http:// URLs.
182604203a83SThomas Cort 	 */
182704203a83SThomas Cort 	if (STRNEQUAL(url, HTTP_URL)
182804203a83SThomas Cort #ifdef WITH_SSL
182904203a83SThomas Cort 	    || STRNEQUAL(url, HTTPS_URL)
183004203a83SThomas Cort #endif
183104203a83SThomas Cort 	    || STRNEQUAL(url, FILE_URL))
183204203a83SThomas Cort 		return (fetch_url(url, NULL, NULL, NULL));
183304203a83SThomas Cort 
183404203a83SThomas Cort 	/*
183504203a83SThomas Cort 	 * If it contains "://" but does not begin with ftp://
183604203a83SThomas Cort 	 * or something that was already handled, then it's
183704203a83SThomas Cort 	 * unsupported.
183804203a83SThomas Cort 	 *
183904203a83SThomas Cort 	 * If it contains ":" but not "://" then we assume the
184004203a83SThomas Cort 	 * part before the colon is a host name, not an URL scheme,
184104203a83SThomas Cort 	 * so we don't try to match that here.
184204203a83SThomas Cort 	 */
184304203a83SThomas Cort 	if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL))
184404203a83SThomas Cort 		errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url);
184504203a83SThomas Cort 
184604203a83SThomas Cort 	/*
184704203a83SThomas Cort 	 * Try FTP URL-style and host:file arguments next.
184804203a83SThomas Cort 	 * If ftpproxy is set with an FTP URL, use fetch_url()
184904203a83SThomas Cort 	 * Othewise, use fetch_ftp().
185004203a83SThomas Cort 	 */
185104203a83SThomas Cort 	proxyenv = getoptionvalue("ftp_proxy");
185204203a83SThomas Cort 	if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
185304203a83SThomas Cort 		return (fetch_url(url, NULL, NULL, NULL));
185404203a83SThomas Cort 
185504203a83SThomas Cort 	return (fetch_ftp(url));
185604203a83SThomas Cort }
185704203a83SThomas Cort 
185804203a83SThomas Cort /*
185904203a83SThomas Cort  * Retrieve multiple files from the command line,
186004203a83SThomas Cort  * calling go_fetch() for each file.
186104203a83SThomas Cort  *
186204203a83SThomas Cort  * If an ftp path has a trailing "/", the path will be cd-ed into and
186304203a83SThomas Cort  * the connection remains open, and the function will return -1
186404203a83SThomas Cort  * (to indicate the connection is alive).
186504203a83SThomas Cort  * If an error occurs the return value will be the offset+1 in
186604203a83SThomas Cort  * argv[] of the file that caused a problem (i.e, argv[x]
186704203a83SThomas Cort  * returns x+1)
186804203a83SThomas Cort  * Otherwise, 0 is returned if all files retrieved successfully.
186904203a83SThomas Cort  */
187004203a83SThomas Cort int
187104203a83SThomas Cort auto_fetch(int argc, char *argv[])
187204203a83SThomas Cort {
187304203a83SThomas Cort 	volatile int	argpos, rval;
187404203a83SThomas Cort 
187504203a83SThomas Cort 	argpos = rval = 0;
187604203a83SThomas Cort 
187704203a83SThomas Cort 	if (sigsetjmp(toplevel, 1)) {
187804203a83SThomas Cort 		if (connected)
187904203a83SThomas Cort 			disconnect(0, NULL);
188004203a83SThomas Cort 		if (rval > 0)
188104203a83SThomas Cort 			rval = argpos + 1;
188204203a83SThomas Cort 		return (rval);
188304203a83SThomas Cort 	}
188404203a83SThomas Cort 	(void)xsignal(SIGINT, intr);
188504203a83SThomas Cort 	(void)xsignal(SIGPIPE, lostpeer);
188604203a83SThomas Cort 
188704203a83SThomas Cort 	/*
188804203a83SThomas Cort 	 * Loop through as long as there's files to fetch.
188904203a83SThomas Cort 	 */
189004203a83SThomas Cort 	for (; (rval == 0) && (argpos < argc); argpos++) {
189104203a83SThomas Cort 		if (strchr(argv[argpos], ':') == NULL)
189204203a83SThomas Cort 			break;
189304203a83SThomas Cort 		redirect_loop = 0;
189404203a83SThomas Cort 		if (!anonftp)
189504203a83SThomas Cort 			anonftp = 2;	/* Handle "automatic" transfers. */
189604203a83SThomas Cort 		rval = go_fetch(argv[argpos]);
189704203a83SThomas Cort 		if (outfile != NULL && strcmp(outfile, "-") != 0
189804203a83SThomas Cort 		    && outfile[0] != '|')
189904203a83SThomas Cort 			outfile = NULL;
190004203a83SThomas Cort 		if (rval > 0)
190104203a83SThomas Cort 			rval = argpos + 1;
190204203a83SThomas Cort 	}
190304203a83SThomas Cort 
190404203a83SThomas Cort 	if (connected && rval != -1)
190504203a83SThomas Cort 		disconnect(0, NULL);
190604203a83SThomas Cort 	return (rval);
190704203a83SThomas Cort }
190804203a83SThomas Cort 
190904203a83SThomas Cort 
191004203a83SThomas Cort /*
191104203a83SThomas Cort  * Upload multiple files from the command line.
191204203a83SThomas Cort  *
191304203a83SThomas Cort  * If an error occurs the return value will be the offset+1 in
191404203a83SThomas Cort  * argv[] of the file that caused a problem (i.e, argv[x]
191504203a83SThomas Cort  * returns x+1)
191604203a83SThomas Cort  * Otherwise, 0 is returned if all files uploaded successfully.
191704203a83SThomas Cort  */
191804203a83SThomas Cort int
191904203a83SThomas Cort auto_put(int argc, char **argv, const char *uploadserver)
192004203a83SThomas Cort {
192104203a83SThomas Cort 	char	*uargv[4], *path, *pathsep;
192204203a83SThomas Cort 	int	 uargc, rval, argpos;
192304203a83SThomas Cort 	size_t	 len;
192404203a83SThomas Cort 	char	 cmdbuf[MAX_C_NAME];
192504203a83SThomas Cort 
192604203a83SThomas Cort 	(void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf));
192704203a83SThomas Cort 	uargv[0] = cmdbuf;
192804203a83SThomas Cort 	uargv[1] = argv[0];
192904203a83SThomas Cort 	uargc = 2;
193004203a83SThomas Cort 	uargv[2] = uargv[3] = NULL;
193104203a83SThomas Cort 	pathsep = NULL;
193204203a83SThomas Cort 	rval = 1;
193304203a83SThomas Cort 
193404203a83SThomas Cort 	DPRINTF("auto_put: target `%s'\n", uploadserver);
193504203a83SThomas Cort 
193604203a83SThomas Cort 	path = ftp_strdup(uploadserver);
193704203a83SThomas Cort 	len = strlen(path);
193804203a83SThomas Cort 	if (path[len - 1] != '/' && path[len - 1] != ':') {
193904203a83SThomas Cort 			/*
194004203a83SThomas Cort 			 * make sure we always pass a directory to auto_fetch
194104203a83SThomas Cort 			 */
194204203a83SThomas Cort 		if (argc > 1) {		/* more than one file to upload */
194304203a83SThomas Cort 			len = strlen(uploadserver) + 2;	/* path + "/" + "\0" */
194404203a83SThomas Cort 			free(path);
194504203a83SThomas Cort 			path = (char *)ftp_malloc(len);
194604203a83SThomas Cort 			(void)strlcpy(path, uploadserver, len);
194704203a83SThomas Cort 			(void)strlcat(path, "/", len);
194804203a83SThomas Cort 		} else {		/* single file to upload */
194904203a83SThomas Cort 			(void)strlcpy(cmdbuf, "put", sizeof(cmdbuf));
195004203a83SThomas Cort 			uargv[0] = cmdbuf;
195104203a83SThomas Cort 			pathsep = strrchr(path, '/');
195204203a83SThomas Cort 			if (pathsep == NULL) {
195304203a83SThomas Cort 				pathsep = strrchr(path, ':');
195404203a83SThomas Cort 				if (pathsep == NULL) {
195504203a83SThomas Cort 					warnx("Invalid URL `%s'", path);
195604203a83SThomas Cort 					goto cleanup_auto_put;
195704203a83SThomas Cort 				}
195804203a83SThomas Cort 				pathsep++;
195904203a83SThomas Cort 				uargv[2] = ftp_strdup(pathsep);
196004203a83SThomas Cort 				pathsep[0] = '/';
196104203a83SThomas Cort 			} else
196204203a83SThomas Cort 				uargv[2] = ftp_strdup(pathsep + 1);
196304203a83SThomas Cort 			pathsep[1] = '\0';
196404203a83SThomas Cort 			uargc++;
196504203a83SThomas Cort 		}
196604203a83SThomas Cort 	}
196704203a83SThomas Cort 	DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
196804203a83SThomas Cort 	    path, STRorNULL(uargv[2]));
196904203a83SThomas Cort 
197004203a83SThomas Cort 			/* connect and cwd */
197104203a83SThomas Cort 	rval = auto_fetch(1, &path);
197204203a83SThomas Cort 	if(rval >= 0)
197304203a83SThomas Cort 		goto cleanup_auto_put;
197404203a83SThomas Cort 
197504203a83SThomas Cort 	rval = 0;
197604203a83SThomas Cort 
197704203a83SThomas Cort 			/* target filename provided; upload 1 file */
197804203a83SThomas Cort 			/* XXX : is this the best way? */
197904203a83SThomas Cort 	if (uargc == 3) {
198004203a83SThomas Cort 		uargv[1] = argv[0];
198104203a83SThomas Cort 		put(uargc, uargv);
198204203a83SThomas Cort 		if ((code / 100) != COMPLETE)
198304203a83SThomas Cort 			rval = 1;
198404203a83SThomas Cort 	} else {	/* otherwise a target dir: upload all files to it */
198504203a83SThomas Cort 		for(argpos = 0; argv[argpos] != NULL; argpos++) {
198604203a83SThomas Cort 			uargv[1] = argv[argpos];
198704203a83SThomas Cort 			mput(uargc, uargv);
198804203a83SThomas Cort 			if ((code / 100) != COMPLETE) {
198904203a83SThomas Cort 				rval = argpos + 1;
199004203a83SThomas Cort 				break;
199104203a83SThomas Cort 			}
199204203a83SThomas Cort 		}
199304203a83SThomas Cort 	}
199404203a83SThomas Cort 
199504203a83SThomas Cort  cleanup_auto_put:
199604203a83SThomas Cort 	free(path);
199704203a83SThomas Cort 	FREEPTR(uargv[2]);
199804203a83SThomas Cort 	return (rval);
199904203a83SThomas Cort }
2000