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