xref: /openbsd-src/usr.bin/ftp/fetch.c (revision 8cc1b8fa16e27d6a1d25ec2062848ea0c4fb2619)
1*8cc1b8faSsthen /*	$OpenBSD: fetch.c,v 1.218 2024/04/23 08:50:38 sthen Exp $	*/
2bfd817adSflorian /*	$NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $	*/
3bfd817adSflorian 
4bfd817adSflorian /*-
5bfd817adSflorian  * Copyright (c) 1997 The NetBSD Foundation, Inc.
6bfd817adSflorian  * All rights reserved.
7bfd817adSflorian  *
8bfd817adSflorian  * This code is derived from software contributed to The NetBSD Foundation
9bfd817adSflorian  * by Jason Thorpe and Luke Mewburn.
10bfd817adSflorian  *
11bfd817adSflorian  * Redistribution and use in source and binary forms, with or without
12bfd817adSflorian  * modification, are permitted provided that the following conditions
13bfd817adSflorian  * are met:
14bfd817adSflorian  * 1. Redistributions of source code must retain the above copyright
15bfd817adSflorian  *    notice, this list of conditions and the following disclaimer.
16bfd817adSflorian  * 2. Redistributions in binary form must reproduce the above copyright
17bfd817adSflorian  *    notice, this list of conditions and the following disclaimer in the
18bfd817adSflorian  *    documentation and/or other materials provided with the distribution.
19bfd817adSflorian  *
20bfd817adSflorian  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21bfd817adSflorian  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22bfd817adSflorian  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23bfd817adSflorian  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24bfd817adSflorian  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25bfd817adSflorian  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26bfd817adSflorian  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27bfd817adSflorian  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28bfd817adSflorian  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29bfd817adSflorian  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30bfd817adSflorian  * POSSIBILITY OF SUCH DAMAGE.
31bfd817adSflorian  */
32bfd817adSflorian 
33bfd817adSflorian /*
34bfd817adSflorian  * FTP User Program -- Command line file retrieval
35bfd817adSflorian  */
36bfd817adSflorian 
37bfd817adSflorian #include <sys/types.h>
38bfd817adSflorian #include <sys/socket.h>
39bfd817adSflorian #include <sys/stat.h>
40bfd817adSflorian 
41bfd817adSflorian #include <netinet/in.h>
42bfd817adSflorian 
43bfd817adSflorian #include <arpa/ftp.h>
44bfd817adSflorian #include <arpa/inet.h>
45bfd817adSflorian 
46bfd817adSflorian #include <ctype.h>
47bfd817adSflorian #include <err.h>
48bfd817adSflorian #include <libgen.h>
49bfd817adSflorian #include <netdb.h>
50bfd817adSflorian #include <fcntl.h>
51bfd817adSflorian #include <signal.h>
52740dbc81Sderaadt #include <vis.h>
53bfd817adSflorian #include <stdio.h>
54bfd817adSflorian #include <stdarg.h>
55bfd817adSflorian #include <errno.h>
56bfd817adSflorian #include <stdlib.h>
57bfd817adSflorian #include <string.h>
58bfd817adSflorian #include <unistd.h>
59bfd817adSflorian #include <resolv.h>
6049dd5d56Srobert #include <utime.h>
61bfd817adSflorian 
62bfd817adSflorian #ifndef NOSSL
63bfd817adSflorian #include <tls.h>
64bfd817adSflorian #else /* !NOSSL */
65bfd817adSflorian struct tls;
66bfd817adSflorian #endif /* !NOSSL */
67bfd817adSflorian 
68bfd817adSflorian #include "ftp_var.h"
69bfd817adSflorian #include "cmds.h"
70bfd817adSflorian 
71ba6894b2Sjca static int	file_get(const char *, const char *);
72bfd817adSflorian static int	url_get(const char *, const char *, const char *, int);
737102f6c2Sjca static int	save_chunked(FILE *, struct tls *, int , char *, size_t);
74194d6d9dSjca static void	aborthttp(int);
75194d6d9dSjca static char	hextochar(const char *);
76194d6d9dSjca static char	*urldecode(const char *);
77194d6d9dSjca static char	*recode_credentials(const char *_userinfo);
78583394bbSjca static void	ftp_close(FILE **, struct tls **, int *);
79194d6d9dSjca static const char *sockerror(struct tls *);
80b237d516Sjca #ifdef SMALL
81b237d516Sjca #define 	ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__)
82b237d516Sjca #else
83b237d516Sjca static int	ftp_printf(FILE *, const char *, ...);
84b237d516Sjca #endif /* SMALL */
85bfd817adSflorian #ifndef NOSSL
86194d6d9dSjca static int	proxy_connect(int, char *, char *);
87194d6d9dSjca static int	stdio_tls_write_wrapper(void *, const char *, int);
88194d6d9dSjca static int	stdio_tls_read_wrapper(void *, char *, int);
89bfd817adSflorian #endif /* !NOSSL */
90bfd817adSflorian 
91bfd817adSflorian #define	FTP_URL		"ftp://"	/* ftp URL prefix */
92bfd817adSflorian #define	HTTP_URL	"http://"	/* http URL prefix */
93bfd817adSflorian #define	HTTPS_URL	"https://"	/* https URL prefix */
94bfd817adSflorian #define	FILE_URL	"file:"		/* file URL prefix */
95bfd817adSflorian #define FTP_PROXY	"ftp_proxy"	/* env var with ftp proxy location */
96bfd817adSflorian #define HTTP_PROXY	"http_proxy"	/* env var with http proxy location */
97bfd817adSflorian 
98bfd817adSflorian #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
99bfd817adSflorian 
100bfd817adSflorian static const char at_encoding_warning[] =
101bfd817adSflorian     "Extra `@' characters in usernames and passwords should be encoded as %%40";
102bfd817adSflorian 
103194d6d9dSjca static jmp_buf	httpabort;
104bfd817adSflorian 
105bfd817adSflorian static int	redirect_loop;
106f78cba61Sjca static int	retried;
107bfd817adSflorian 
108bfd817adSflorian /*
1097138ccd3Skn  * Determine whether the character needs encoding, per RFC2396.
110bfd817adSflorian  */
111bfd817adSflorian static int
to_encode(const char * c0)1127138ccd3Skn to_encode(const char *c0)
113bfd817adSflorian {
1147138ccd3Skn 	/* 2.4.3. Excluded US-ASCII Characters */
1157138ccd3Skn 	const char *excluded_chars =
1167138ccd3Skn 	    " "		/* space */
1177138ccd3Skn 	    "<>#\""	/* delims (modulo "%", see below) */
1187138ccd3Skn 	    "{}|\\^[]`"	/* unwise */
1197138ccd3Skn 	    ;
120bfd817adSflorian 	const unsigned char *c = (const unsigned char *)c0;
121bfd817adSflorian 
122bfd817adSflorian 	/*
123bfd817adSflorian 	 * No corresponding graphic US-ASCII.
124bfd817adSflorian 	 * Control characters and octets not used in US-ASCII.
125bfd817adSflorian 	 */
126bfd817adSflorian 	return (iscntrl(*c) || !isascii(*c) ||
127bfd817adSflorian 
128bfd817adSflorian 	    /*
1297138ccd3Skn 	     * '%' is also reserved, if is not followed by two
130bfd817adSflorian 	     * hexadecimal digits.
131bfd817adSflorian 	     */
1327138ccd3Skn 	    strchr(excluded_chars, *c) != NULL ||
1337810f2e0Sderaadt 	    (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2]))));
134bfd817adSflorian }
135bfd817adSflorian 
136bfd817adSflorian /*
1377138ccd3Skn  * Encode given URL, per RFC2396.
138bfd817adSflorian  * Allocate and return string to the caller.
139bfd817adSflorian  */
140bfd817adSflorian static char *
url_encode(const char * path)141bfd817adSflorian url_encode(const char *path)
142bfd817adSflorian {
143bfd817adSflorian 	size_t i, length, new_length;
144bfd817adSflorian 	char *epath, *epathp;
145bfd817adSflorian 
146bfd817adSflorian 	length = new_length = strlen(path);
147bfd817adSflorian 
148bfd817adSflorian 	/*
149bfd817adSflorian 	 * First pass:
1507138ccd3Skn 	 * Count characters to encode and determine length of the final URL.
151bfd817adSflorian 	 */
152bfd817adSflorian 	for (i = 0; i < length; i++)
1537138ccd3Skn 		if (to_encode(path + i))
154bfd817adSflorian 			new_length += 2;
155bfd817adSflorian 
156bfd817adSflorian 	epath = epathp = malloc(new_length + 1);	/* One more for '\0'. */
157bfd817adSflorian 	if (epath == NULL)
158bfd817adSflorian 		err(1, "Can't allocate memory for URL encoding");
159bfd817adSflorian 
160bfd817adSflorian 	/*
161bfd817adSflorian 	 * Second pass:
162bfd817adSflorian 	 * Encode, and copy final URL.
163bfd817adSflorian 	 */
164bfd817adSflorian 	for (i = 0; i < length; i++)
1657138ccd3Skn 		if (to_encode(path + i)) {
166bfd817adSflorian 			snprintf(epathp, 4, "%%" "%02x",
167bfd817adSflorian 			    (unsigned char)path[i]);
168bfd817adSflorian 			epathp += 3;
169bfd817adSflorian 		} else
170bfd817adSflorian 			*(epathp++) = path[i];
171bfd817adSflorian 
172bfd817adSflorian 	*epathp = '\0';
173bfd817adSflorian 	return (epath);
174bfd817adSflorian }
175bfd817adSflorian 
176bfd817adSflorian /*
177ba6894b2Sjca  * Copy a local file (used by the OpenBSD installer).
178ba6894b2Sjca  * Returns -1 on failure, 0 on success
179ba6894b2Sjca  */
180ba6894b2Sjca static int
file_get(const char * path,const char * outfile)181ba6894b2Sjca file_get(const char *path, const char *outfile)
182ba6894b2Sjca {
183ba6894b2Sjca 	struct stat	 st;
1843f8a9645Sjca 	int		 fd, out = -1, rval = -1, save_errno;
185ba6894b2Sjca 	volatile sig_t	 oldintr, oldinti;
186ba6894b2Sjca 	const char	*savefile;
1873411e43fSnaddy 	char		*buf = NULL, *cp, *pathbuf = NULL;
188ba6894b2Sjca 	const size_t	 buflen = 128 * 1024;
189ba6894b2Sjca 	off_t		 hashbytes;
190ba6894b2Sjca 	ssize_t		 len, wlen;
191ba6894b2Sjca 
192ba6894b2Sjca 	direction = "received";
193ba6894b2Sjca 
194ba6894b2Sjca 	fd = open(path, O_RDONLY);
195ba6894b2Sjca 	if (fd == -1) {
196ba6894b2Sjca 		warn("Can't open file %s", path);
197ba6894b2Sjca 		return -1;
198ba6894b2Sjca 	}
199ba6894b2Sjca 
200ba6894b2Sjca 	if (fstat(fd, &st) == -1)
201ba6894b2Sjca 		filesize = -1;
202ba6894b2Sjca 	else
203ba6894b2Sjca 		filesize = st.st_size;
204ba6894b2Sjca 
205ba6894b2Sjca 	if (outfile != NULL)
206ba6894b2Sjca 		savefile = outfile;
207ba6894b2Sjca 	else {
208ba6894b2Sjca 		if (path[strlen(path) - 1] == '/')	/* Consider no file */
209ba6894b2Sjca 			savefile = NULL;		/* after dir invalid. */
2103411e43fSnaddy 		else {
2113411e43fSnaddy 			pathbuf = strdup(path);
2123411e43fSnaddy 			if (pathbuf == NULL)
2133411e43fSnaddy 				errx(1, "Can't allocate memory for filename");
2143411e43fSnaddy 			savefile = basename(pathbuf);
2153411e43fSnaddy 		}
216ba6894b2Sjca 	}
217ba6894b2Sjca 
218ba6894b2Sjca 	if (EMPTYSTRING(savefile)) {
219ba6894b2Sjca 		warnx("No filename after directory (use -o): %s", path);
220ba6894b2Sjca 		goto cleanup_copy;
221ba6894b2Sjca 	}
222ba6894b2Sjca 
223ba6894b2Sjca 	/* Open the output file.  */
224ba6894b2Sjca 	if (!pipeout) {
225ba6894b2Sjca 		out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
226ba6894b2Sjca 		if (out == -1) {
227ba6894b2Sjca 			warn("Can't open %s", savefile);
228ba6894b2Sjca 			goto cleanup_copy;
229ba6894b2Sjca 		}
230ba6894b2Sjca 	} else
231ba6894b2Sjca 		out = fileno(stdout);
232ba6894b2Sjca 
233ba6894b2Sjca 	if ((buf = malloc(buflen)) == NULL)
234ba6894b2Sjca 		errx(1, "Can't allocate memory for transfer buffer");
235ba6894b2Sjca 
236ba6894b2Sjca 	/* Trap signals */
237ba6894b2Sjca 	oldintr = NULL;
238ba6894b2Sjca 	oldinti = NULL;
239ba6894b2Sjca 	if (setjmp(httpabort)) {
240ba6894b2Sjca 		if (oldintr)
241ba6894b2Sjca 			(void)signal(SIGINT, oldintr);
242ba6894b2Sjca 		if (oldinti)
243ba6894b2Sjca 			(void)signal(SIGINFO, oldinti);
244ba6894b2Sjca 		goto cleanup_copy;
245ba6894b2Sjca 	}
2465fbde59fSkn 	oldintr = signal(SIGINT, aborthttp);
247ba6894b2Sjca 
248ba6894b2Sjca 	bytes = 0;
249ba6894b2Sjca 	hashbytes = mark;
250ba6894b2Sjca 	progressmeter(-1, path);
251ba6894b2Sjca 
252ba6894b2Sjca 	/* Finally, suck down the file. */
253ba6894b2Sjca 	oldinti = signal(SIGINFO, psummary);
254ba6894b2Sjca 	while ((len = read(fd, buf, buflen)) > 0) {
255ba6894b2Sjca 		bytes += len;
256ba6894b2Sjca 		for (cp = buf; len > 0; len -= wlen, cp += wlen) {
257ba6894b2Sjca 			if ((wlen = write(out, cp, len)) == -1) {
258ba6894b2Sjca 				warn("Writing %s", savefile);
2597413ba97Sjca 				signal(SIGINT, oldintr);
260ba6894b2Sjca 				signal(SIGINFO, oldinti);
261ba6894b2Sjca 				goto cleanup_copy;
262ba6894b2Sjca 			}
263ba6894b2Sjca 		}
264ba6894b2Sjca 		if (hash && !progress) {
265ba6894b2Sjca 			while (bytes >= hashbytes) {
266ba6894b2Sjca 				(void)putc('#', ttyout);
267ba6894b2Sjca 				hashbytes += mark;
268ba6894b2Sjca 			}
269ba6894b2Sjca 			(void)fflush(ttyout);
270ba6894b2Sjca 		}
271ba6894b2Sjca 	}
272ba6894b2Sjca 	save_errno = errno;
2737413ba97Sjca 	signal(SIGINT, oldintr);
274ba6894b2Sjca 	signal(SIGINFO, oldinti);
275ba6894b2Sjca 	if (hash && !progress && bytes > 0) {
276ba6894b2Sjca 		if (bytes < mark)
277ba6894b2Sjca 			(void)putc('#', ttyout);
278ba6894b2Sjca 		(void)putc('\n', ttyout);
279ba6894b2Sjca 		(void)fflush(ttyout);
280ba6894b2Sjca 	}
281ba6894b2Sjca 	if (len == -1) {
282ba6894b2Sjca 		warnc(save_errno, "Reading from file");
283ba6894b2Sjca 		goto cleanup_copy;
284ba6894b2Sjca 	}
285ba6894b2Sjca 	progressmeter(1, NULL);
286ba6894b2Sjca 	if (verbose)
287ba6894b2Sjca 		ptransfer(0);
288ba6894b2Sjca 
289ba6894b2Sjca 	rval = 0;
290ba6894b2Sjca 
291ba6894b2Sjca cleanup_copy:
292ba6894b2Sjca 	free(buf);
2933411e43fSnaddy 	free(pathbuf);
294ba6894b2Sjca 	if (out >= 0 && out != fileno(stdout))
295ba6894b2Sjca 		close(out);
296ba6894b2Sjca 	close(fd);
297ba6894b2Sjca 
298ba6894b2Sjca 	return rval;
299ba6894b2Sjca }
300ba6894b2Sjca 
301ba6894b2Sjca /*
302bfd817adSflorian  * Retrieve URL, via the proxy in $proxyvar if necessary.
303bfd817adSflorian  * Returns -1 on failure, 0 on success
304bfd817adSflorian  */
305bfd817adSflorian static int
url_get(const char * origline,const char * proxyenv,const char * outfile,int lastfile)306bfd817adSflorian url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile)
307bfd817adSflorian {
308bfd817adSflorian 	char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4];
309bfd817adSflorian 	char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL;
310740dbc81Sderaadt 	char *epath, *redirurl, *loctail, *h, *p, gerror[200];
311583394bbSjca 	int error, isftpurl = 0, isredirect = 0, rval = -1;
3123733520cSjca 	int isunavail = 0, retryafter = -1;
313bfd817adSflorian 	struct addrinfo hints, *res0, *res;
314583394bbSjca 	const char *savefile;
3153411e43fSnaddy 	char *pathbuf = NULL;
316583394bbSjca 	char *proxyurl = NULL;
3177e8f63e0Syasuoka 	char *credentials = NULL, *proxy_credentials = NULL;
318583394bbSjca 	int fd = -1, out = -1;
319bfd817adSflorian 	volatile sig_t oldintr, oldinti;
320bfd817adSflorian 	FILE *fin = NULL;
321bfd817adSflorian 	off_t hashbytes;
322bfd817adSflorian 	const char *errstr;
323bfd817adSflorian 	ssize_t len, wlen;
324bb1c3b3fSnaddy 	size_t bufsize;
325bfd817adSflorian 	char *proxyhost = NULL;
326bfd817adSflorian #ifndef NOSSL
327bfd817adSflorian 	char *sslpath = NULL, *sslhost = NULL;
3280f23e651Sjca 	int ishttpsurl = 0;
329bfd817adSflorian #endif /* !NOSSL */
330bfd817adSflorian #ifndef SMALL
331e4b576d7Sjca 	char *full_host = NULL;
332e4b576d7Sjca 	const char *scheme;
333bfd817adSflorian 	char *locbase;
334bfd817adSflorian 	struct addrinfo *ares = NULL;
33549dd5d56Srobert 	char tmbuf[32];
33649dd5d56Srobert 	time_t mtime = 0;
33749dd5d56Srobert 	struct stat stbuf;
33849dd5d56Srobert 	struct tm lmt = { 0 };
33949dd5d56Srobert 	struct timespec ts[2];
340e4b576d7Sjca #endif /* !SMALL */
341bfd817adSflorian 	struct tls *tls = NULL;
342bfd817adSflorian 	int status;
343bfd817adSflorian 	int save_errno;
344bfd817adSflorian 	const size_t buflen = 128 * 1024;
3457102f6c2Sjca 	int chunked = 0;
346bfd817adSflorian 
347bfd817adSflorian 	direction = "received";
348bfd817adSflorian 
349bfd817adSflorian 	newline = strdup(origline);
350bfd817adSflorian 	if (newline == NULL)
351bfd817adSflorian 		errx(1, "Can't allocate memory to parse URL");
352bfd817adSflorian 	if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
353bfd817adSflorian 		host = newline + sizeof(HTTP_URL) - 1;
354e4b576d7Sjca #ifndef SMALL
355bfd817adSflorian 		scheme = HTTP_URL;
356bfd817adSflorian #endif /* !SMALL */
357bfd817adSflorian 	} else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
358bfd817adSflorian 		host = newline + sizeof(FTP_URL) - 1;
359bfd817adSflorian 		isftpurl = 1;
360bfd817adSflorian #ifndef SMALL
361bfd817adSflorian 		scheme = FTP_URL;
362bfd817adSflorian #endif /* !SMALL */
363bfd817adSflorian 	} else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) {
364d5ffb3f3Sjca #ifndef NOSSL
365bfd817adSflorian 		host = newline + sizeof(HTTPS_URL) - 1;
366bfd817adSflorian 		ishttpsurl = 1;
367d5ffb3f3Sjca #else
368d5ffb3f3Sjca 		errx(1, "%s: No HTTPS support", newline);
369d5ffb3f3Sjca #endif /* !NOSSL */
370e4b576d7Sjca #ifndef SMALL
371bfd817adSflorian 		scheme = HTTPS_URL;
372e4b576d7Sjca #endif /* !SMALL */
373bfd817adSflorian 	} else
374ba6894b2Sjca 		errx(1, "%s: URL not permitted", newline);
375bfd817adSflorian 
376bfd817adSflorian 	path = strchr(host, '/');		/* Find path */
37734ed7cb2Sjca 
37834ed7cb2Sjca 	/*
37934ed7cb2Sjca 	 * Look for auth header in host.
38034ed7cb2Sjca 	 * Basic auth from RFC 2617, valid characters for path are in
38134ed7cb2Sjca 	 * RFC 3986 section 3.3.
38234ed7cb2Sjca 	 */
3830f23e651Sjca 	if (!isftpurl) {
38434ed7cb2Sjca 		p = strchr(host, '@');
38534ed7cb2Sjca 		if (p != NULL && (path == NULL || p < path)) {
38634ed7cb2Sjca 			*p++ = '\0';
38734ed7cb2Sjca 			credentials = recode_credentials(host);
38834ed7cb2Sjca 
38934ed7cb2Sjca 			/* Overwrite userinfo */
39034ed7cb2Sjca 			memmove(host, p, strlen(p) + 1);
39134ed7cb2Sjca 			path = strchr(host, '/');
39234ed7cb2Sjca 		}
39334ed7cb2Sjca 	}
39434ed7cb2Sjca 
395bfd817adSflorian 	if (EMPTYSTRING(path)) {
396bfd817adSflorian 		if (outfile) {				/* No slash, but */
397bfd817adSflorian 			path = strchr(host,'\0');	/* we have outfile. */
398bfd817adSflorian 			goto noslash;
399bfd817adSflorian 		}
400bfd817adSflorian 		if (isftpurl)
401bfd817adSflorian 			goto noftpautologin;
402bfd817adSflorian 		warnx("No `/' after host (use -o): %s", origline);
403bfd817adSflorian 		goto cleanup_url_get;
404bfd817adSflorian 	}
405bfd817adSflorian 	*path++ = '\0';
406bfd817adSflorian 	if (EMPTYSTRING(path) && !outfile) {
407bfd817adSflorian 		if (isftpurl)
408bfd817adSflorian 			goto noftpautologin;
409bfd817adSflorian 		warnx("No filename after host (use -o): %s", origline);
410bfd817adSflorian 		goto cleanup_url_get;
411bfd817adSflorian 	}
412bfd817adSflorian 
413bfd817adSflorian noslash:
414bfd817adSflorian 	if (outfile)
415bfd817adSflorian 		savefile = outfile;
416bfd817adSflorian 	else {
417bfd817adSflorian 		if (path[strlen(path) - 1] == '/')	/* Consider no file */
418bfd817adSflorian 			savefile = NULL;		/* after dir invalid. */
4193411e43fSnaddy 		else {
4203411e43fSnaddy 			pathbuf = strdup(path);
4213411e43fSnaddy 			if (pathbuf == NULL)
4223411e43fSnaddy 				errx(1, "Can't allocate memory for filename");
4233411e43fSnaddy 			savefile = basename(pathbuf);
4243411e43fSnaddy 		}
425bfd817adSflorian 	}
426bfd817adSflorian 
427bfd817adSflorian 	if (EMPTYSTRING(savefile)) {
428bfd817adSflorian 		if (isftpurl)
429bfd817adSflorian 			goto noftpautologin;
430bfd817adSflorian 		warnx("No filename after directory (use -o): %s", origline);
431bfd817adSflorian 		goto cleanup_url_get;
432bfd817adSflorian 	}
433bfd817adSflorian 
434bfd817adSflorian #ifndef SMALL
435bfd817adSflorian 	if (resume && pipeout) {
436bfd817adSflorian 		warnx("can't append to stdout");
437bfd817adSflorian 		goto cleanup_url_get;
438bfd817adSflorian 	}
439bfd817adSflorian #endif /* !SMALL */
440bfd817adSflorian 
441ba6894b2Sjca 	if (proxyenv != NULL) {		/* use proxy */
442bfd817adSflorian #ifndef NOSSL
443bfd817adSflorian 		if (ishttpsurl) {
444bfd817adSflorian 			sslpath = strdup(path);
445bfd817adSflorian 			sslhost = strdup(host);
446bfd817adSflorian 			if (! sslpath || ! sslhost)
447bfd817adSflorian 				errx(1, "Can't allocate memory for https path/host.");
448bfd817adSflorian 		}
449bfd817adSflorian #endif /* !NOSSL */
450bfd817adSflorian 		proxyhost = strdup(host);
451bfd817adSflorian 		if (proxyhost == NULL)
452bfd817adSflorian 			errx(1, "Can't allocate memory for proxy host.");
453bfd817adSflorian 		proxyurl = strdup(proxyenv);
454bfd817adSflorian 		if (proxyurl == NULL)
455bfd817adSflorian 			errx(1, "Can't allocate memory for proxy URL.");
456bfd817adSflorian 		if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
457bfd817adSflorian 			host = proxyurl + sizeof(HTTP_URL) - 1;
458bfd817adSflorian 		else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0)
459bfd817adSflorian 			host = proxyurl + sizeof(FTP_URL) - 1;
460bfd817adSflorian 		else {
461bfd817adSflorian 			warnx("Malformed proxy URL: %s", proxyenv);
462bfd817adSflorian 			goto cleanup_url_get;
463bfd817adSflorian 		}
464bfd817adSflorian 		if (EMPTYSTRING(host)) {
465bfd817adSflorian 			warnx("Malformed proxy URL: %s", proxyenv);
466bfd817adSflorian 			goto cleanup_url_get;
467bfd817adSflorian 		}
468bfd817adSflorian 		if (*--path == '\0')
469bfd817adSflorian 			*path = '/';		/* add / back to real path */
470bfd817adSflorian 		path = strchr(host, '/');	/* remove trailing / on host */
471bfd817adSflorian 		if (!EMPTYSTRING(path))
472bfd817adSflorian 			*path++ = '\0';		/* i guess this ++ is useless */
473bfd817adSflorian 
474bfd817adSflorian 		path = strchr(host, '@');	/* look for credentials in proxy */
475bfd817adSflorian 		if (!EMPTYSTRING(path)) {
476bfd817adSflorian 			*path = '\0';
477bfd817adSflorian 			if (strchr(host, ':') == NULL) {
478bfd817adSflorian 				warnx("Malformed proxy URL: %s", proxyenv);
479bfd817adSflorian 				goto cleanup_url_get;
480bfd817adSflorian 			}
4817e8f63e0Syasuoka 			proxy_credentials = recode_credentials(host);
482bfd817adSflorian 			*path = '@'; /* restore @ in proxyurl */
483bfd817adSflorian 
484bfd817adSflorian 			/*
485bfd817adSflorian 			 * This removes the password from proxyurl,
486bfd817adSflorian 			 * filling with stars
487bfd817adSflorian 			 */
488bfd817adSflorian 			for (host = 1 + strchr(proxyurl + 5, ':');  *host != '@';
489bfd817adSflorian 			    host++)
490bfd817adSflorian 				*host = '*';
491bfd817adSflorian 
492bfd817adSflorian 			host = path + 1;
493bfd817adSflorian 		}
494bfd817adSflorian 
495bfd817adSflorian 		path = newline;
496bfd817adSflorian 	}
497bfd817adSflorian 
498bfd817adSflorian 	if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
499bfd817adSflorian 	    (hosttail[1] == '\0' || hosttail[1] == ':')) {
500bfd817adSflorian 		host++;
501bfd817adSflorian 		*hosttail++ = '\0';
502bfd817adSflorian #ifndef SMALL
503bfd817adSflorian 		if (asprintf(&full_host, "[%s]", host) == -1)
504bfd817adSflorian 			errx(1, "Cannot allocate memory for hostname");
505bfd817adSflorian #endif /* !SMALL */
506bfd817adSflorian 	} else
507bfd817adSflorian 		hosttail = host;
508bfd817adSflorian 
509bfd817adSflorian 	portnum = strrchr(hosttail, ':');		/* find portnum */
510bfd817adSflorian 	if (portnum != NULL)
511bfd817adSflorian 		*portnum++ = '\0';
512bfd817adSflorian #ifndef NOSSL
513bfd817adSflorian 	port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
514bfd817adSflorian #else /* !NOSSL */
515bfd817adSflorian 	port = portnum ? portnum : httpport;
516bfd817adSflorian #endif /* !NOSSL */
517bfd817adSflorian 
518bfd817adSflorian #ifndef SMALL
519bfd817adSflorian 	if (full_host == NULL)
520bfd817adSflorian 		if ((full_host = strdup(host)) == NULL)
521bfd817adSflorian 			errx(1, "Cannot allocate memory for hostname");
522bfd817adSflorian 	if (debug)
523bfd817adSflorian 		fprintf(ttyout, "host %s, port %s, path %s, "
524bfd817adSflorian 		    "save as %s, auth %s.\n", host, port, path,
525bfd817adSflorian 		    savefile, credentials ? credentials : "none");
526bfd817adSflorian #endif /* !SMALL */
527bfd817adSflorian 
528bfd817adSflorian 	memset(&hints, 0, sizeof(hints));
529bfd817adSflorian 	hints.ai_family = family;
530bfd817adSflorian 	hints.ai_socktype = SOCK_STREAM;
531bfd817adSflorian 	error = getaddrinfo(host, port, &hints, &res0);
532bfd817adSflorian 	/*
533bfd817adSflorian 	 * If the services file is corrupt/missing, fall back
534bfd817adSflorian 	 * on our hard-coded defines.
535bfd817adSflorian 	 */
536bfd817adSflorian 	if (error == EAI_SERVICE && port == httpport) {
537bfd817adSflorian 		snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
538bfd817adSflorian 		error = getaddrinfo(host, pbuf, &hints, &res0);
539bfd817adSflorian #ifndef NOSSL
540bfd817adSflorian 	} else if (error == EAI_SERVICE && port == httpsport) {
541bfd817adSflorian 		snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
542bfd817adSflorian 		error = getaddrinfo(host, pbuf, &hints, &res0);
543bfd817adSflorian #endif /* !NOSSL */
544bfd817adSflorian 	}
545bfd817adSflorian 	if (error) {
546bfd817adSflorian 		warnx("%s: %s", host, gai_strerror(error));
547bfd817adSflorian 		goto cleanup_url_get;
548bfd817adSflorian 	}
549bfd817adSflorian 
550bfd817adSflorian #ifndef SMALL
551bfd817adSflorian 	if (srcaddr) {
552bfd817adSflorian 		hints.ai_flags |= AI_NUMERICHOST;
553bfd817adSflorian 		error = getaddrinfo(srcaddr, NULL, &hints, &ares);
554bfd817adSflorian 		if (error) {
555bfd817adSflorian 			warnx("%s: %s", srcaddr, gai_strerror(error));
556bfd817adSflorian 			goto cleanup_url_get;
557bfd817adSflorian 		}
558bfd817adSflorian 	}
559bfd817adSflorian #endif /* !SMALL */
560bfd817adSflorian 
561bfd817adSflorian 	/* ensure consistent order of the output */
562bfd817adSflorian 	if (verbose)
563bfd817adSflorian 		setvbuf(ttyout, NULL, _IOLBF, 0);
564bfd817adSflorian 
565bfd817adSflorian 	fd = -1;
566bfd817adSflorian 	for (res = res0; res; res = res->ai_next) {
567bfd817adSflorian 		if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
568bfd817adSflorian 		    sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
569bfd817adSflorian 			strlcpy(hbuf, "(unknown)", sizeof(hbuf));
570bfd817adSflorian 		if (verbose)
571bfd817adSflorian 			fprintf(ttyout, "Trying %s...\n", hbuf);
572bfd817adSflorian 
573bfd817adSflorian 		fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
574bfd817adSflorian 		if (fd == -1) {
575bfd817adSflorian 			cause = "socket";
576bfd817adSflorian 			continue;
577bfd817adSflorian 		}
578bfd817adSflorian 
579bfd817adSflorian #ifndef SMALL
580bfd817adSflorian 		if (srcaddr) {
581bfd817adSflorian 			if (ares->ai_family != res->ai_family) {
582bfd817adSflorian 				close(fd);
583bfd817adSflorian 				fd = -1;
584bfd817adSflorian 				errno = EINVAL;
585bfd817adSflorian 				cause = "bind";
586bfd817adSflorian 				continue;
587bfd817adSflorian 			}
5883aaa63ebSderaadt 			if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) {
589bfd817adSflorian 				save_errno = errno;
590bfd817adSflorian 				close(fd);
591bfd817adSflorian 				errno = save_errno;
592bfd817adSflorian 				fd = -1;
593bfd817adSflorian 				cause = "bind";
594bfd817adSflorian 				continue;
595bfd817adSflorian 			}
596bfd817adSflorian 		}
597bfd817adSflorian #endif /* !SMALL */
598bfd817adSflorian 
599f37faed6Smillert 		error = timed_connect(fd, res->ai_addr, res->ai_addrlen,
600f37faed6Smillert 		    connect_timeout);
601bfd817adSflorian 		if (error != 0) {
602bfd817adSflorian 			save_errno = errno;
603bfd817adSflorian 			close(fd);
604bfd817adSflorian 			errno = save_errno;
605bfd817adSflorian 			fd = -1;
606bfd817adSflorian 			cause = "connect";
607bfd817adSflorian 			continue;
608bfd817adSflorian 		}
609bfd817adSflorian 
610bfd817adSflorian 		/* get port in numeric */
611bfd817adSflorian 		if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
612bfd817adSflorian 		    pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
613bfd817adSflorian 			port = pbuf;
614bfd817adSflorian 		else
615bfd817adSflorian 			port = NULL;
616bfd817adSflorian 
617bfd817adSflorian #ifndef NOSSL
618bfd817adSflorian 		if (proxyenv && sslhost)
6197e8f63e0Syasuoka 			proxy_connect(fd, sslhost, proxy_credentials);
620bfd817adSflorian #endif /* !NOSSL */
621bfd817adSflorian 		break;
622bfd817adSflorian 	}
623bfd817adSflorian 	freeaddrinfo(res0);
624bfd817adSflorian #ifndef SMALL
625bfd817adSflorian 	if (srcaddr)
626bfd817adSflorian 		freeaddrinfo(ares);
627bfd817adSflorian #endif /* !SMALL */
628bfd817adSflorian 	if (fd < 0) {
629bfd817adSflorian 		warn("%s", cause);
630bfd817adSflorian 		goto cleanup_url_get;
631bfd817adSflorian 	}
632bfd817adSflorian 
633bfd817adSflorian #ifndef NOSSL
634bfd817adSflorian 	if (ishttpsurl) {
63580ddac7aSbeck 		ssize_t ret;
636bfd817adSflorian 		if (proxyenv && sslpath) {
637bfd817adSflorian 			ishttpsurl = 0;
638bfd817adSflorian 			proxyurl = NULL;
639bfd817adSflorian 			path = sslpath;
640bfd817adSflorian 		}
641bfd817adSflorian 		if (sslhost == NULL) {
642bfd817adSflorian 			sslhost = strdup(host);
643bfd817adSflorian 			if (sslhost == NULL)
644bfd817adSflorian 				errx(1, "Can't allocate memory for https host.");
645bfd817adSflorian 		}
646bfd817adSflorian 		if ((tls = tls_client()) == NULL) {
647bfd817adSflorian 			fprintf(ttyout, "failed to create SSL client\n");
648bfd817adSflorian 			goto cleanup_url_get;
649bfd817adSflorian 		}
650bfd817adSflorian 		if (tls_configure(tls, tls_config) != 0) {
65180ddac7aSbeck 			fprintf(ttyout, "TLS configuration failure: %s\n",
652bfd817adSflorian 			    tls_error(tls));
653bfd817adSflorian 			goto cleanup_url_get;
654bfd817adSflorian 		}
655bfd817adSflorian 		if (tls_connect_socket(tls, fd, sslhost) != 0) {
65680ddac7aSbeck 			fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls));
657bfd817adSflorian 			goto cleanup_url_get;
658bfd817adSflorian 		}
65980ddac7aSbeck 		do {
66080ddac7aSbeck 			ret = tls_handshake(tls);
66180ddac7aSbeck 		} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
66280ddac7aSbeck 		if (ret != 0) {
66380ddac7aSbeck 			fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls));
66494d4d1aaSjca 			goto cleanup_url_get;
66594d4d1aaSjca 		}
66694d4d1aaSjca 		fin = funopen(tls, stdio_tls_read_wrapper,
66794d4d1aaSjca 		    stdio_tls_write_wrapper, NULL, NULL);
668bfd817adSflorian 	} else {
669bfd817adSflorian 		fin = fdopen(fd, "r+");
670bfd817adSflorian 		fd = -1;
671bfd817adSflorian 	}
672bfd817adSflorian #else /* !NOSSL */
673bfd817adSflorian 	fin = fdopen(fd, "r+");
674bfd817adSflorian 	fd = -1;
675bfd817adSflorian #endif /* !NOSSL */
676bfd817adSflorian 
677bfd817adSflorian #ifdef SMALL
678bfd817adSflorian 	if (lastfile) {
679bfd817adSflorian 		if (pipeout) {
680bfd817adSflorian 			if (pledge("stdio rpath inet dns tty",  NULL) == -1)
681bfd817adSflorian 				err(1, "pledge");
682bfd817adSflorian 		} else {
683bfd817adSflorian 			if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1)
684bfd817adSflorian 				err(1, "pledge");
685bfd817adSflorian 		}
686bfd817adSflorian 	}
687bfd817adSflorian #endif
688bfd817adSflorian 
689bfd817adSflorian 	/*
690bfd817adSflorian 	 * Construct and send the request. Proxy requests don't want leading /.
691bfd817adSflorian 	 */
692bfd817adSflorian #ifndef NOSSL
693bfd817adSflorian 	cookie_get(host, path, ishttpsurl, &buf);
694bfd817adSflorian #endif /* !NOSSL */
695bfd817adSflorian 
696bfd817adSflorian 	epath = url_encode(path);
697bfd817adSflorian 	if (proxyurl) {
698bfd817adSflorian 		if (verbose) {
699bfd817adSflorian 			fprintf(ttyout, "Requesting %s (via %s)\n",
70033488478Skn 			    origline, proxyurl);
701bfd817adSflorian 		}
702bfd817adSflorian 		/*
703bfd817adSflorian 		 * Host: directive must use the destination host address for
704bfd817adSflorian 		 * the original URI (path).
705bfd817adSflorian 		 */
706b237d516Sjca 		ftp_printf(fin, "GET %s HTTP/1.1\r\n"
7077102f6c2Sjca 		    "Connection: close\r\n"
708456e8b6aStb 		    "Host: %s\r\n%s%s\r\n"
709456e8b6aStb 		    "Accept: */*\r\n",
710bfd817adSflorian 		    epath, proxyhost, buf ? buf : "", httpuseragent);
7117e8f63e0Syasuoka 		if (credentials)
7127e8f63e0Syasuoka 			ftp_printf(fin, "Authorization: Basic %s\r\n",
7137e8f63e0Syasuoka 			    credentials);
7147e8f63e0Syasuoka 		if (proxy_credentials)
7157e8f63e0Syasuoka 			ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n",
7167e8f63e0Syasuoka 			    proxy_credentials);
7177e8f63e0Syasuoka 		ftp_printf(fin, "\r\n");
718bfd817adSflorian 	} else {
7195818f8d1Skn 		if (verbose)
72033488478Skn 			fprintf(ttyout, "Requesting %s\n", origline);
72133488478Skn #ifndef SMALL
72249dd5d56Srobert 		if (resume || timestamp) {
72349dd5d56Srobert 			if (stat(savefile, &stbuf) == 0) {
72449dd5d56Srobert 				if (resume)
725bfd817adSflorian 					restart_point = stbuf.st_size;
72649dd5d56Srobert 				if (timestamp)
72749dd5d56Srobert 					mtime = stbuf.st_mtime;
72849dd5d56Srobert 			} else {
729bfd817adSflorian 				restart_point = 0;
73049dd5d56Srobert 				mtime = 0;
73149dd5d56Srobert 			}
732bfd817adSflorian 		}
733bfd817adSflorian #endif	/* SMALL */
734b237d516Sjca 		ftp_printf(fin,
7357102f6c2Sjca 		    "GET /%s HTTP/1.1\r\n"
7367102f6c2Sjca 		    "Connection: close\r\n"
7377102f6c2Sjca 		    "Host: ", epath);
738bfd817adSflorian 		if (proxyhost) {
739b237d516Sjca 			ftp_printf(fin, "%s", proxyhost);
740bfd817adSflorian 			port = NULL;
741bfd817adSflorian 		} else if (strchr(host, ':')) {
742bfd817adSflorian 			/*
743bfd817adSflorian 			 * strip off scoped address portion, since it's
744bfd817adSflorian 			 * local to node
745bfd817adSflorian 			 */
746bfd817adSflorian 			h = strdup(host);
747bfd817adSflorian 			if (h == NULL)
748bfd817adSflorian 				errx(1, "Can't allocate memory.");
749bfd817adSflorian 			if ((p = strchr(h, '%')) != NULL)
750bfd817adSflorian 				*p = '\0';
751b237d516Sjca 			ftp_printf(fin, "[%s]", h);
752bfd817adSflorian 			free(h);
753bfd817adSflorian 		} else
754b237d516Sjca 			ftp_printf(fin, "%s", host);
755bfd817adSflorian 
756bfd817adSflorian 		/*
757bfd817adSflorian 		 * Send port number only if it's specified and does not equal
758bfd817adSflorian 		 * 80. Some broken HTTP servers get confused if you explicitly
759bfd817adSflorian 		 * send them the port number.
760bfd817adSflorian 		 */
761bfd817adSflorian #ifndef NOSSL
762bfd817adSflorian 		if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
763b237d516Sjca 			ftp_printf(fin, ":%s", port);
764bfd817adSflorian 		if (restart_point)
765b237d516Sjca 			ftp_printf(fin, "\r\nRange: bytes=%lld-",
766bfd817adSflorian 				(long long)restart_point);
767bfd817adSflorian #else /* !NOSSL */
768bfd817adSflorian 		if (port && strcmp(port, "80") != 0)
769b237d516Sjca 			ftp_printf(fin, ":%s", port);
770bfd817adSflorian #endif /* !NOSSL */
77149dd5d56Srobert 
77249dd5d56Srobert #ifndef SMALL
77349dd5d56Srobert 		if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0))
77449dd5d56Srobert 			ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf);
77549dd5d56Srobert #endif /* SMALL */
77649dd5d56Srobert 
777456e8b6aStb 		ftp_printf(fin, "\r\n%s%s\r\n", buf ? buf : "", httpuseragent);
778456e8b6aStb 		ftp_printf(fin, "Accept: */*\r\n");
77967a189a2Sjca 		if (credentials)
78067a189a2Sjca 			ftp_printf(fin, "Authorization: Basic %s\r\n",
78167a189a2Sjca 			    credentials);
78267a189a2Sjca 		ftp_printf(fin, "\r\n");
783bfd817adSflorian 	}
784bfd817adSflorian 	free(epath);
785bfd817adSflorian 
786bfd817adSflorian #ifndef NOSSL
787bfd817adSflorian 	free(buf);
788bfd817adSflorian #endif /* !NOSSL */
789bfd817adSflorian 	buf = NULL;
790bb1c3b3fSnaddy 	bufsize = 0;
791bfd817adSflorian 
79294d4d1aaSjca 	if (fflush(fin) == EOF) {
79394d4d1aaSjca 		warnx("Writing HTTP request: %s", sockerror(tls));
794bfd817adSflorian 		goto cleanup_url_get;
795bfd817adSflorian 	}
796bb1c3b3fSnaddy 	if ((len = getline(&buf, &bufsize, fin)) == -1) {
79794d4d1aaSjca 		warnx("Receiving HTTP reply: %s", sockerror(tls));
798bfd817adSflorian 		goto cleanup_url_get;
799bfd817adSflorian 	}
800bfd817adSflorian 
801bfd817adSflorian 	while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
802bfd817adSflorian 		buf[--len] = '\0';
803bfd817adSflorian #ifndef SMALL
804bfd817adSflorian 	if (debug)
805bfd817adSflorian 		fprintf(ttyout, "received '%s'\n", buf);
806bfd817adSflorian #endif /* !SMALL */
807bfd817adSflorian 
808bfd817adSflorian 	cp = strchr(buf, ' ');
809bfd817adSflorian 	if (cp == NULL)
810bfd817adSflorian 		goto improper;
811bfd817adSflorian 	else
812bfd817adSflorian 		cp++;
813bfd817adSflorian 
814bfd817adSflorian 	strlcpy(ststr, cp, sizeof(ststr));
815f78cba61Sjca 	status = strtonum(ststr, 200, 503, &errstr);
816bfd817adSflorian 	if (errstr) {
817740dbc81Sderaadt 		strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
818740dbc81Sderaadt 		warnx("Error retrieving %s: %s", origline, gerror);
819bfd817adSflorian 		goto cleanup_url_get;
820bfd817adSflorian 	}
821bfd817adSflorian 
822bfd817adSflorian 	switch (status) {
823bfd817adSflorian 	case 200:	/* OK */
824bfd817adSflorian #ifndef SMALL
825bfd817adSflorian 		/*
826bfd817adSflorian 		 * When we request a partial file, and we receive an HTTP 200
827bfd817adSflorian 		 * it is a good indication that the server doesn't support
828bfd817adSflorian 		 * range requests, and is about to send us the entire file.
829bfd817adSflorian 		 * If the restart_point == 0, then we are not actually
830bfd817adSflorian 		 * requesting a partial file, and an HTTP 200 is appropriate.
831bfd817adSflorian 		 */
832bfd817adSflorian 		if (resume && restart_point != 0) {
833bfd817adSflorian 			warnx("Server does not support resume.");
834bfd817adSflorian 			restart_point = resume = 0;
835bfd817adSflorian 		}
836bfd817adSflorian 		/* FALLTHROUGH */
837bfd817adSflorian 	case 206:	/* Partial Content */
838bfd817adSflorian #endif /* !SMALL */
839bfd817adSflorian 		break;
840bfd817adSflorian 	case 301:	/* Moved Permanently */
841bfd817adSflorian 	case 302:	/* Found */
842bfd817adSflorian 	case 303:	/* See Other */
843bfd817adSflorian 	case 307:	/* Temporary Redirect */
844e0549513Schrisz 	case 308:	/* Permanent Redirect (RFC 7538) */
845bfd817adSflorian 		isredirect++;
846bfd817adSflorian 		if (redirect_loop++ > 10) {
847bfd817adSflorian 			warnx("Too many redirections requested");
848bfd817adSflorian 			goto cleanup_url_get;
849bfd817adSflorian 		}
850bfd817adSflorian 		break;
851bfd817adSflorian #ifndef SMALL
85249dd5d56Srobert 	case 304:	/* Not Modified */
85349dd5d56Srobert 		warnx("File is not modified on the server");
85449dd5d56Srobert 		goto cleanup_url_get;
855bfd817adSflorian 	case 416:	/* Requested Range Not Satisfiable */
856bfd817adSflorian 		warnx("File is already fully retrieved.");
857bfd817adSflorian 		goto cleanup_url_get;
858bfd817adSflorian #endif /* !SMALL */
859f78cba61Sjca 	case 503:
8603733520cSjca 		isunavail = 1;
8613733520cSjca 		break;
862bfd817adSflorian 	default:
863740dbc81Sderaadt 		strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
864740dbc81Sderaadt 		warnx("Error retrieving %s: %s", origline, gerror);
865bfd817adSflorian 		goto cleanup_url_get;
866bfd817adSflorian 	}
867bfd817adSflorian 
868bfd817adSflorian 	/*
869bfd817adSflorian 	 * Read the rest of the header.
870bfd817adSflorian 	 */
871bfd817adSflorian 	filesize = -1;
872bfd817adSflorian 
873bfd817adSflorian 	for (;;) {
874bb1c3b3fSnaddy 		if ((len = getline(&buf, &bufsize, fin)) == -1) {
87594d4d1aaSjca 			warnx("Receiving HTTP reply: %s", sockerror(tls));
876bfd817adSflorian 			goto cleanup_url_get;
877bfd817adSflorian 		}
878bfd817adSflorian 
87939263320Sclaudio 		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n' ||
88039263320Sclaudio 		    buf[len-1] == ' ' || buf[len-1] == '\t'))
881bfd817adSflorian 			buf[--len] = '\0';
882bfd817adSflorian 		if (len == 0)
883bfd817adSflorian 			break;
884bfd817adSflorian #ifndef SMALL
885bfd817adSflorian 		if (debug)
886bfd817adSflorian 			fprintf(ttyout, "received '%s'\n", buf);
887bfd817adSflorian #endif /* !SMALL */
888bfd817adSflorian 
889bfd817adSflorian 		/* Look for some headers */
890bfd817adSflorian 		cp = buf;
891bfd817adSflorian #define CONTENTLEN "Content-Length:"
892bfd817adSflorian 		if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
893bfd817adSflorian 			cp += sizeof(CONTENTLEN) - 1;
8944520669eSclaudio 			cp += strspn(cp, " \t");
895bfd817adSflorian 			filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
896bfd817adSflorian 			if (errstr != NULL)
897bfd817adSflorian 				goto improper;
898bfd817adSflorian #ifndef SMALL
899bfd817adSflorian 			if (restart_point)
900bfd817adSflorian 				filesize += restart_point;
901bfd817adSflorian #endif /* !SMALL */
902bfd817adSflorian #define LOCATION "Location:"
903bfd817adSflorian 		} else if (isredirect &&
904bfd817adSflorian 		    strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
905bfd817adSflorian 			cp += sizeof(LOCATION) - 1;
9064520669eSclaudio 			cp += strspn(cp, " \t");
907bfd817adSflorian 			/*
908bfd817adSflorian 			 * If there is a colon before the first slash, this URI
909bfd817adSflorian 			 * is not relative. RFC 3986 4.2
910bfd817adSflorian 			 */
911bfd817adSflorian 			if (cp[strcspn(cp, ":/")] != ':') {
912bfd817adSflorian #ifdef SMALL
913bfd817adSflorian 				errx(1, "Relative redirect not supported");
914bfd817adSflorian #else /* SMALL */
915bfd817adSflorian 				/* XXX doesn't handle protocol-relative URIs */
916bfd817adSflorian 				if (*cp == '/') {
917bfd817adSflorian 					locbase = NULL;
918bfd817adSflorian 					cp++;
919bfd817adSflorian 				} else {
920bfd817adSflorian 					locbase = strdup(path);
921bfd817adSflorian 					if (locbase == NULL)
922bfd817adSflorian 						errx(1, "Can't allocate memory"
923bfd817adSflorian 						    " for location base");
924bfd817adSflorian 					loctail = strchr(locbase, '#');
925bfd817adSflorian 					if (loctail != NULL)
926bfd817adSflorian 						*loctail = '\0';
927bfd817adSflorian 					loctail = strchr(locbase, '?');
928bfd817adSflorian 					if (loctail != NULL)
929bfd817adSflorian 						*loctail = '\0';
930bfd817adSflorian 					loctail = strrchr(locbase, '/');
931bfd817adSflorian 					if (loctail == NULL) {
932bfd817adSflorian 						free(locbase);
933bfd817adSflorian 						locbase = NULL;
934bfd817adSflorian 					} else
935bfd817adSflorian 						loctail[1] = '\0';
936bfd817adSflorian 				}
937d9a51c35Sjmc 				/* Construct URL from relative redirect */
938bfd817adSflorian 				if (asprintf(&redirurl, "%s%s%s%s/%s%s",
939bfd817adSflorian 				    scheme, full_host,
940bfd817adSflorian 				    portnum ? ":" : "",
941bfd817adSflorian 				    portnum ? portnum : "",
942bfd817adSflorian 				    locbase ? locbase : "",
943bfd817adSflorian 				    cp) == -1)
944bfd817adSflorian 					errx(1, "Cannot build "
945bfd817adSflorian 					    "redirect URL");
946bfd817adSflorian 				free(locbase);
947bfd817adSflorian #endif /* SMALL */
948bfd817adSflorian 			} else if ((redirurl = strdup(cp)) == NULL)
949bfd817adSflorian 				errx(1, "Cannot allocate memory for URL");
950bfd817adSflorian 			loctail = strchr(redirurl, '#');
951bfd817adSflorian 			if (loctail != NULL)
952bfd817adSflorian 				*loctail = '\0';
953e8e14267Sclaudio 			if (verbose) {
954e8e14267Sclaudio 				char *visbuf;
955e8e14267Sclaudio 				if (stravis(&visbuf, redirurl, VIS_SAFE) == -1)
956e8e14267Sclaudio 					err(1, "Cannot vis redirect URL");
957e8e14267Sclaudio 				fprintf(ttyout, "Redirected to %s\n", visbuf);
958e8e14267Sclaudio 				free(visbuf);
959e8e14267Sclaudio 			}
960535fae4eSjca 			ftp_close(&fin, &tls, &fd);
961bfd817adSflorian 			rval = url_get(redirurl, proxyenv, savefile, lastfile);
962bfd817adSflorian 			free(redirurl);
963bfd817adSflorian 			goto cleanup_url_get;
9643733520cSjca #define RETRYAFTER "Retry-After:"
9653733520cSjca 		} else if (isunavail &&
9663733520cSjca 		    strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) {
9673733520cSjca 			cp += sizeof(RETRYAFTER) - 1;
9684520669eSclaudio 			cp += strspn(cp, " \t");
9693733520cSjca 			retryafter = strtonum(cp, 0, 0, &errstr);
9703733520cSjca 			if (errstr != NULL)
9713733520cSjca 				retryafter = -1;
9727102f6c2Sjca #define TRANSFER_ENCODING "Transfer-Encoding:"
9737102f6c2Sjca 		} else if (strncasecmp(cp, TRANSFER_ENCODING,
9747102f6c2Sjca 			    sizeof(TRANSFER_ENCODING) - 1) == 0) {
9757102f6c2Sjca 			cp += sizeof(TRANSFER_ENCODING) - 1;
9764520669eSclaudio 			cp += strspn(cp, " \t");
9777102f6c2Sjca 			if (strcasecmp(cp, "chunked") == 0)
9787102f6c2Sjca 				chunked = 1;
97949dd5d56Srobert #ifndef SMALL
98049dd5d56Srobert #define LAST_MODIFIED "Last-Modified:"
98149dd5d56Srobert 		} else if (strncasecmp(cp, LAST_MODIFIED,
98249dd5d56Srobert 			    sizeof(LAST_MODIFIED) - 1) == 0) {
98349dd5d56Srobert 			cp += sizeof(LAST_MODIFIED) - 1;
984e846c64dSop 			cp += strspn(cp, " \t");
98549dd5d56Srobert 			if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL)
98649dd5d56Srobert 				server_timestamps = 0;
98749dd5d56Srobert #endif /* !SMALL */
988bfd817adSflorian 		}
989bfd817adSflorian 	}
990bb1c3b3fSnaddy 	free(buf);
991a0c22864Snaddy 	buf = NULL;
992bfd817adSflorian 
9937102f6c2Sjca 	/* Content-Length should be ignored for Transfer-Encoding: chunked */
9947102f6c2Sjca 	if (chunked)
9957102f6c2Sjca 		filesize = -1;
9967102f6c2Sjca 
9973733520cSjca 	if (isunavail) {
9983733520cSjca 		if (retried || retryafter != 0)
999740dbc81Sderaadt 			warnx("Error retrieving %s: 503 Service Unavailable",
1000740dbc81Sderaadt 			    origline);
10013733520cSjca 		else {
10023733520cSjca 			if (verbose)
10033733520cSjca 				fprintf(ttyout, "Retrying %s\n", origline);
10043733520cSjca 			retried = 1;
10051e6907dcSjca 			ftp_close(&fin, &tls, &fd);
10063733520cSjca 			rval = url_get(origline, proxyenv, savefile, lastfile);
10073733520cSjca 		}
10083733520cSjca 		goto cleanup_url_get;
10093733520cSjca 	}
10103733520cSjca 
1011bfd817adSflorian 	/* Open the output file.  */
1012bfd817adSflorian 	if (!pipeout) {
1013bfd817adSflorian #ifndef SMALL
1014bfd817adSflorian 		if (resume)
1015bfd817adSflorian 			out = open(savefile, O_CREAT | O_WRONLY | O_APPEND,
1016bfd817adSflorian 				0666);
1017bfd817adSflorian 		else
1018bfd817adSflorian #endif /* !SMALL */
1019bfd817adSflorian 			out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
1020bfd817adSflorian 				0666);
10213aaa63ebSderaadt 		if (out == -1) {
1022bfd817adSflorian 			warn("Can't open %s", savefile);
1023bfd817adSflorian 			goto cleanup_url_get;
1024bfd817adSflorian 		}
1025bfd817adSflorian 	} else {
1026bfd817adSflorian 		out = fileno(stdout);
1027bfd817adSflorian #ifdef SMALL
1028bfd817adSflorian 		if (lastfile) {
1029bfd817adSflorian 			if (pledge("stdio tty", NULL) == -1)
1030bfd817adSflorian 				err(1, "pledge");
1031bfd817adSflorian 		}
1032bfd817adSflorian #endif
1033bfd817adSflorian 	}
1034bfd817adSflorian 
1035583394bbSjca 	if ((buf = malloc(buflen)) == NULL)
1036583394bbSjca 		errx(1, "Can't allocate memory for transfer buffer");
1037583394bbSjca 
1038bfd817adSflorian 	/* Trap signals */
1039bfd817adSflorian 	oldintr = NULL;
1040bfd817adSflorian 	oldinti = NULL;
1041bfd817adSflorian 	if (setjmp(httpabort)) {
1042bfd817adSflorian 		if (oldintr)
1043bfd817adSflorian 			(void)signal(SIGINT, oldintr);
1044bfd817adSflorian 		if (oldinti)
1045bfd817adSflorian 			(void)signal(SIGINFO, oldinti);
1046bfd817adSflorian 		goto cleanup_url_get;
1047bfd817adSflorian 	}
1048bfd817adSflorian 	oldintr = signal(SIGINT, aborthttp);
1049bfd817adSflorian 
1050bfd817adSflorian 	bytes = 0;
1051bfd817adSflorian 	hashbytes = mark;
1052bfd817adSflorian 	progressmeter(-1, path);
1053bfd817adSflorian 
1054bfd817adSflorian 	/* Finally, suck down the file. */
10557102f6c2Sjca 	oldinti = signal(SIGINFO, psummary);
10567102f6c2Sjca 	if (chunked) {
10572b371687Sjca 		error = save_chunked(fin, tls, out, buf, buflen);
10587413ba97Sjca 		signal(SIGINT, oldintr);
10597102f6c2Sjca 		signal(SIGINFO, oldinti);
10602b371687Sjca 		if (error == -1)
10617102f6c2Sjca 			goto cleanup_url_get;
10627102f6c2Sjca 	} else {
1063d6fcb866Sjca 		while ((len = fread(buf, 1, buflen, fin)) > 0) {
1064bfd817adSflorian 			bytes += len;
1065d6fcb866Sjca 			for (cp = buf; len > 0; len -= wlen, cp += wlen) {
1066d6fcb866Sjca 				if ((wlen = write(out, cp, len)) == -1) {
1067bfd817adSflorian 					warn("Writing %s", savefile);
10687413ba97Sjca 					signal(SIGINT, oldintr);
1069bfd817adSflorian 					signal(SIGINFO, oldinti);
1070bfd817adSflorian 					goto cleanup_url_get;
1071d6fcb866Sjca 				}
1072bfd817adSflorian 			}
1073bfd817adSflorian 			if (hash && !progress) {
1074bfd817adSflorian 				while (bytes >= hashbytes) {
1075bfd817adSflorian 					(void)putc('#', ttyout);
1076bfd817adSflorian 					hashbytes += mark;
1077bfd817adSflorian 				}
1078bfd817adSflorian 				(void)fflush(ttyout);
1079bfd817adSflorian 			}
1080bfd817adSflorian 		}
1081d6fcb866Sjca 		save_errno = errno;
10827413ba97Sjca 		signal(SIGINT, oldintr);
1083bfd817adSflorian 		signal(SIGINFO, oldinti);
1084bfd817adSflorian 		if (hash && !progress && bytes > 0) {
1085bfd817adSflorian 			if (bytes < mark)
1086bfd817adSflorian 				(void)putc('#', ttyout);
1087bfd817adSflorian 			(void)putc('\n', ttyout);
1088bfd817adSflorian 			(void)fflush(ttyout);
1089bfd817adSflorian 		}
1090d6fcb866Sjca 		if (len == 0 && ferror(fin)) {
1091d6fcb866Sjca 			errno = save_errno;
109294d4d1aaSjca 			warnx("Reading from socket: %s", sockerror(tls));
1093bfd817adSflorian 			goto cleanup_url_get;
1094bfd817adSflorian 		}
10957102f6c2Sjca 	}
1096bfd817adSflorian 	progressmeter(1, NULL);
1097bfd817adSflorian 	if (
1098bfd817adSflorian #ifndef SMALL
1099bfd817adSflorian 		!resume &&
1100bfd817adSflorian #endif /* !SMALL */
1101bfd817adSflorian 		filesize != -1 && len == 0 && bytes != filesize) {
1102bfd817adSflorian 		if (verbose)
1103bfd817adSflorian 			fputs("Read short file.\n", ttyout);
1104bfd817adSflorian 		goto cleanup_url_get;
1105bfd817adSflorian 	}
1106bfd817adSflorian 
1107bfd817adSflorian 	if (verbose)
1108bfd817adSflorian 		ptransfer(0);
1109bfd817adSflorian 
1110bfd817adSflorian 	rval = 0;
1111bfd817adSflorian 	goto cleanup_url_get;
1112bfd817adSflorian 
1113bfd817adSflorian noftpautologin:
1114bfd817adSflorian 	warnx(
1115bfd817adSflorian 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
1116bfd817adSflorian 	goto cleanup_url_get;
1117bfd817adSflorian 
1118bfd817adSflorian improper:
1119bfd817adSflorian 	warnx("Improper response from %s", host);
1120bfd817adSflorian 
1121bfd817adSflorian cleanup_url_get:
1122e4b576d7Sjca #ifndef SMALL
1123bfd817adSflorian 	free(full_host);
1124e4b576d7Sjca #endif /* !SMALL */
1125e4b576d7Sjca #ifndef NOSSL
1126bfd817adSflorian 	free(sslhost);
1127bfd817adSflorian #endif /* !NOSSL */
1128535fae4eSjca 	ftp_close(&fin, &tls, &fd);
112949dd5d56Srobert 	if (out >= 0 && out != fileno(stdout)) {
113049dd5d56Srobert #ifndef SMALL
11311e951bc7Ssthen 		if (server_timestamps && lmt.tm_zone != 0 &&
11321e951bc7Ssthen 		    fstat(out, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0) {
113349dd5d56Srobert 			ts[0].tv_nsec = UTIME_NOW;
113449dd5d56Srobert 			ts[1].tv_nsec = 0;
113549dd5d56Srobert 			setenv("TZ", lmt.tm_zone, 1);
113649dd5d56Srobert 			if (((ts[1].tv_sec = mktime(&lmt)) != -1) &&
113749dd5d56Srobert 			    (futimens(out, ts) == -1))
113849dd5d56Srobert 				warnx("Unable to set file modification time");
113949dd5d56Srobert 		}
114049dd5d56Srobert #endif /* !SMALL */
1141bfd817adSflorian 		close(out);
114249dd5d56Srobert 	}
1143bfd817adSflorian 	free(buf);
11443411e43fSnaddy 	free(pathbuf);
1145bfd817adSflorian 	free(proxyhost);
1146bfd817adSflorian 	free(proxyurl);
1147bfd817adSflorian 	free(newline);
1148bfd817adSflorian 	free(credentials);
11497e8f63e0Syasuoka 	free(proxy_credentials);
1150bfd817adSflorian 	return (rval);
1151bfd817adSflorian }
1152bfd817adSflorian 
11537102f6c2Sjca static int
save_chunked(FILE * fin,struct tls * tls,int out,char * buf,size_t buflen)11547102f6c2Sjca save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen)
11557102f6c2Sjca {
11567102f6c2Sjca 
1157bb1c3b3fSnaddy 	char			*header = NULL, *end, *cp;
11587102f6c2Sjca 	unsigned long		chunksize;
1159bb1c3b3fSnaddy 	size_t			hsize = 0, rlen, wlen;
11607102f6c2Sjca 	ssize_t			written;
11617102f6c2Sjca 	char			cr, lf;
11627102f6c2Sjca 
11637102f6c2Sjca 	for (;;) {
1164bb1c3b3fSnaddy 		if (getline(&header, &hsize, fin) == -1)
11657102f6c2Sjca 			break;
11667102f6c2Sjca 		/* strip CRLF and any optional chunk extension */
116739263320Sclaudio 		header[strcspn(header, "; \t\r\n")] = '\0';
11687102f6c2Sjca 		errno = 0;
11697102f6c2Sjca 		chunksize = strtoul(header, &end, 16);
11707102f6c2Sjca 		if (errno || header[0] == '\0' || *end != '\0' ||
11717102f6c2Sjca 		    chunksize > INT_MAX) {
11727102f6c2Sjca 			warnx("Invalid chunk size '%s'", header);
11737102f6c2Sjca 			free(header);
11747102f6c2Sjca 			return -1;
11757102f6c2Sjca 		}
11767102f6c2Sjca 
11777102f6c2Sjca 		if (chunksize == 0) {
11787102f6c2Sjca 			/* We're done.  Ignore optional trailer. */
1179bb1c3b3fSnaddy 			free(header);
11807102f6c2Sjca 			return 0;
11817102f6c2Sjca 		}
11827102f6c2Sjca 
11837102f6c2Sjca 		for (written = 0; chunksize != 0; chunksize -= rlen) {
11847102f6c2Sjca 			rlen = (chunksize < buflen) ? chunksize : buflen;
11857102f6c2Sjca 			rlen = fread(buf, 1, rlen, fin);
11867102f6c2Sjca 			if (rlen == 0)
11877102f6c2Sjca 				break;
11887102f6c2Sjca 			bytes += rlen;
11897102f6c2Sjca 			for (cp = buf, wlen = rlen; wlen > 0;
11907102f6c2Sjca 			    wlen -= written, cp += written) {
11917102f6c2Sjca 				if ((written = write(out, cp, wlen)) == -1) {
11927102f6c2Sjca 					warn("Writing output file");
1193bb1c3b3fSnaddy 					free(header);
11947102f6c2Sjca 					return -1;
11957102f6c2Sjca 				}
11967102f6c2Sjca 			}
11977102f6c2Sjca 		}
11987102f6c2Sjca 
11997102f6c2Sjca 		if (rlen == 0 ||
12007102f6c2Sjca 		    fread(&cr, 1, 1, fin) != 1 ||
12017102f6c2Sjca 		    fread(&lf, 1, 1, fin) != 1)
12027102f6c2Sjca 			break;
12037102f6c2Sjca 
12047102f6c2Sjca 		if (cr != '\r' || lf != '\n') {
12057102f6c2Sjca 			warnx("Invalid chunked encoding");
1206bb1c3b3fSnaddy 			free(header);
12077102f6c2Sjca 			return -1;
12087102f6c2Sjca 		}
12097102f6c2Sjca 	}
1210bb1c3b3fSnaddy 	free(header);
12117102f6c2Sjca 
12127102f6c2Sjca 	if (ferror(fin))
12137102f6c2Sjca 		warnx("Error while reading from socket: %s", sockerror(tls));
12147102f6c2Sjca 	else
12157102f6c2Sjca 		warnx("Invalid chunked encoding: short read");
12167102f6c2Sjca 
12177102f6c2Sjca 	return -1;
12187102f6c2Sjca }
12197102f6c2Sjca 
1220bfd817adSflorian /*
1221bfd817adSflorian  * Abort a http retrieval
1222bfd817adSflorian  */
1223194d6d9dSjca static void
aborthttp(int signo)1224bfd817adSflorian aborthttp(int signo)
1225bfd817adSflorian {
12265fbde59fSkn 	const char errmsg[] = "\nfetch aborted.\n";
1227bfd817adSflorian 
12285fbde59fSkn 	write(fileno(ttyout), errmsg, sizeof(errmsg) - 1);
1229bfd817adSflorian 	longjmp(httpabort, 1);
1230bfd817adSflorian }
1231bfd817adSflorian 
1232bfd817adSflorian /*
1233bfd817adSflorian  * Retrieve multiple files from the command line, transferring
1234bfd817adSflorian  * files of the form "host:path", "ftp://host/path" using the
1235bfd817adSflorian  * ftp protocol, and files of the form "http://host/path" using
1236bfd817adSflorian  * the http protocol.
1237bfd817adSflorian  * If path has a trailing "/", then return (-1);
1238bfd817adSflorian  * the path will be cd-ed into and the connection remains open,
1239bfd817adSflorian  * and the function will return -1 (to indicate the connection
1240bfd817adSflorian  * is alive).
1241bfd817adSflorian  * If an error occurs the return value will be the offset+1 in
1242bfd817adSflorian  * argv[] of the file that caused a problem (i.e, argv[x]
1243bfd817adSflorian  * returns x+1)
1244bfd817adSflorian  * Otherwise, 0 is returned if all files retrieved successfully.
1245bfd817adSflorian  */
1246bfd817adSflorian int
auto_fetch(int argc,char * argv[],char * outfile)1247bfd817adSflorian auto_fetch(int argc, char *argv[], char *outfile)
1248bfd817adSflorian {
1249bfd817adSflorian 	char *xargv[5];
1250bfd817adSflorian 	char *cp, *url, *host, *dir, *file, *portnum;
1251bfd817adSflorian 	char *username, *pass, *pathstart;
1252bfd817adSflorian 	char *ftpproxy, *httpproxy;
1253bfd817adSflorian 	int rval, xargc, lastfile;
1254bfd817adSflorian 	volatile int argpos;
1255bfd817adSflorian 	int dirhasglob, filehasglob, oautologin;
1256bfd817adSflorian 	char rempath[PATH_MAX];
1257bfd817adSflorian 
1258bfd817adSflorian 	argpos = 0;
1259bfd817adSflorian 
1260bfd817adSflorian 	if (setjmp(toplevel)) {
1261bfd817adSflorian 		if (connected)
1262bfd817adSflorian 			disconnect(0, NULL);
1263bfd817adSflorian 		return (argpos + 1);
1264bfd817adSflorian 	}
1265bfd817adSflorian 	(void)signal(SIGINT, (sig_t)intr);
1266bfd817adSflorian 	(void)signal(SIGPIPE, (sig_t)lostpeer);
1267bfd817adSflorian 
1268bfd817adSflorian 	if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
1269bfd817adSflorian 		ftpproxy = NULL;
1270bfd817adSflorian 	if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
1271bfd817adSflorian 		httpproxy = NULL;
1272bfd817adSflorian 
1273bfd817adSflorian 	/*
1274bfd817adSflorian 	 * Loop through as long as there's files to fetch.
1275bfd817adSflorian 	 */
1276bfd817adSflorian 	username = pass = NULL;
1277bfd817adSflorian 	for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
1278bfd817adSflorian 		if (strchr(argv[argpos], ':') == NULL)
1279bfd817adSflorian 			break;
1280bfd817adSflorian 
1281bfd817adSflorian 		free(username);
1282bfd817adSflorian 		free(pass);
1283bfd817adSflorian 		host = dir = file = portnum = username = pass = NULL;
1284bfd817adSflorian 
1285bfd817adSflorian 		lastfile = (argv[argpos+1] == NULL);
1286bfd817adSflorian 
1287bfd817adSflorian 		/*
1288bfd817adSflorian 		 * We muck with the string, so we make a copy.
1289bfd817adSflorian 		 */
1290bfd817adSflorian 		url = strdup(argv[argpos]);
1291bfd817adSflorian 		if (url == NULL)
1292bfd817adSflorian 			errx(1, "Can't allocate memory for auto-fetch.");
1293bfd817adSflorian 
1294ba6894b2Sjca 		if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
1295ba6894b2Sjca 			if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1)
1296ba6894b2Sjca 				rval = argpos + 1;
1297ba6894b2Sjca 			continue;
1298ba6894b2Sjca 		}
1299ba6894b2Sjca 
1300bfd817adSflorian 		/*
1301ba6894b2Sjca 		 * Try HTTP URL-style arguments next.
1302bfd817adSflorian 		 */
1303bfd817adSflorian 		if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1304ba6894b2Sjca 		    strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) {
1305bfd817adSflorian 			redirect_loop = 0;
1306f78cba61Sjca 			retried = 0;
1307bfd817adSflorian 			if (url_get(url, httpproxy, outfile, lastfile) == -1)
1308bfd817adSflorian 				rval = argpos + 1;
1309bfd817adSflorian 			continue;
1310bfd817adSflorian 		}
1311bfd817adSflorian 
1312bfd817adSflorian 		/*
1313bfd817adSflorian 		 * Try FTP URL-style arguments next. If ftpproxy is
1314bfd817adSflorian 		 * set, use url_get() instead of standard ftp.
1315bfd817adSflorian 		 * Finally, try host:file.
1316bfd817adSflorian 		 */
1317bfd817adSflorian 		host = url;
1318bfd817adSflorian 		if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1319bfd817adSflorian 			char *passend, *passagain, *userend;
1320bfd817adSflorian 
1321bfd817adSflorian 			if (ftpproxy) {
1322bfd817adSflorian 				if (url_get(url, ftpproxy, outfile, lastfile) == -1)
1323bfd817adSflorian 					rval = argpos + 1;
1324bfd817adSflorian 				continue;
1325bfd817adSflorian 			}
1326bfd817adSflorian 			host += sizeof(FTP_URL) - 1;
1327bfd817adSflorian 			dir = strchr(host, '/');
1328bfd817adSflorian 
1329bfd817adSflorian 			/* Look for [user:pass@]host[:port] */
1330bfd817adSflorian 
1331bfd817adSflorian 			/* check if we have "user:pass@" */
1332bfd817adSflorian 			userend = strchr(host, ':');
1333bfd817adSflorian 			passend = strchr(host, '@');
1334bfd817adSflorian 			if (passend && userend && userend < passend &&
1335bfd817adSflorian 			    (!dir || passend < dir)) {
1336bfd817adSflorian 				username = host;
1337bfd817adSflorian 				pass = userend + 1;
1338bfd817adSflorian 				host = passend + 1;
1339bfd817adSflorian 				*userend = *passend = '\0';
1340bfd817adSflorian 				passagain = strchr(host, '@');
1341bfd817adSflorian 				if (strchr(pass, '@') != NULL ||
1342bfd817adSflorian 				    (passagain != NULL && passagain < dir)) {
1343bfd817adSflorian 					warnx(at_encoding_warning);
1344bfd817adSflorian 					username = pass = NULL;
1345bfd817adSflorian 					goto bad_ftp_url;
1346bfd817adSflorian 				}
1347bfd817adSflorian 
1348bfd817adSflorian 				if (EMPTYSTRING(username)) {
1349bfd817adSflorian bad_ftp_url:
1350bfd817adSflorian 					warnx("Invalid URL: %s", argv[argpos]);
1351bfd817adSflorian 					rval = argpos + 1;
1352bfd817adSflorian 					username = pass = NULL;
1353bfd817adSflorian 					continue;
1354bfd817adSflorian 				}
1355bfd817adSflorian 				username = urldecode(username);
1356bfd817adSflorian 				pass = urldecode(pass);
1357bfd817adSflorian 			}
1358bfd817adSflorian 
1359bfd817adSflorian 			/* check [host]:port, or [host] */
1360bfd817adSflorian 			if (host[0] == '[') {
1361bfd817adSflorian 				cp = strchr(host, ']');
1362bfd817adSflorian 				if (cp && (!dir || cp < dir)) {
1363bfd817adSflorian 					if (cp + 1 == dir || cp[1] == ':') {
1364bfd817adSflorian 						host++;
1365bfd817adSflorian 						*cp++ = '\0';
1366bfd817adSflorian 					} else
1367bfd817adSflorian 						cp = NULL;
1368bfd817adSflorian 				} else
1369bfd817adSflorian 					cp = host;
1370bfd817adSflorian 			} else
1371bfd817adSflorian 				cp = host;
1372bfd817adSflorian 
1373bfd817adSflorian 			/* split off host[:port] if there is */
1374bfd817adSflorian 			if (cp) {
1375bfd817adSflorian 				portnum = strchr(cp, ':');
1376bfd817adSflorian 				pathstart = strchr(cp, '/');
1377bfd817adSflorian 				/* : in path is not a port # indicator */
1378bfd817adSflorian 				if (portnum && pathstart &&
1379bfd817adSflorian 				    pathstart < portnum)
1380bfd817adSflorian 					portnum = NULL;
1381bfd817adSflorian 
1382bfd817adSflorian 				if (!portnum)
1383bfd817adSflorian 					;
1384bfd817adSflorian 				else {
1385bfd817adSflorian 					if (!dir)
1386bfd817adSflorian 						;
1387bfd817adSflorian 					else if (portnum + 1 < dir) {
1388bfd817adSflorian 						*portnum++ = '\0';
1389bfd817adSflorian 						/*
1390bfd817adSflorian 						 * XXX should check if portnum
1391bfd817adSflorian 						 * is decimal number
1392bfd817adSflorian 						 */
1393bfd817adSflorian 					} else {
1394bfd817adSflorian 						/* empty portnum */
1395bfd817adSflorian 						goto bad_ftp_url;
1396bfd817adSflorian 					}
1397bfd817adSflorian 				}
1398bfd817adSflorian 			} else
1399bfd817adSflorian 				portnum = NULL;
1400bfd817adSflorian 		} else {			/* classic style `host:file' */
1401bfd817adSflorian 			dir = strchr(host, ':');
1402bfd817adSflorian 		}
1403bfd817adSflorian 		if (EMPTYSTRING(host)) {
1404bfd817adSflorian 			rval = argpos + 1;
1405bfd817adSflorian 			continue;
1406bfd817adSflorian 		}
1407bfd817adSflorian 
1408bfd817adSflorian 		/*
1409bfd817adSflorian 		 * If dir is NULL, the file wasn't specified
1410bfd817adSflorian 		 * (URL looked something like ftp://host)
1411bfd817adSflorian 		 */
1412bfd817adSflorian 		if (dir != NULL)
1413bfd817adSflorian 			*dir++ = '\0';
1414bfd817adSflorian 
1415bfd817adSflorian 		/*
1416bfd817adSflorian 		 * Extract the file and (if present) directory name.
1417bfd817adSflorian 		 */
1418bfd817adSflorian 		if (!EMPTYSTRING(dir)) {
1419bfd817adSflorian 			cp = strrchr(dir, '/');
1420bfd817adSflorian 			if (cp != NULL) {
1421bfd817adSflorian 				*cp++ = '\0';
1422bfd817adSflorian 				file = cp;
1423bfd817adSflorian 			} else {
1424bfd817adSflorian 				file = dir;
1425bfd817adSflorian 				dir = NULL;
1426bfd817adSflorian 			}
1427bfd817adSflorian 		}
1428bfd817adSflorian #ifndef SMALL
1429bfd817adSflorian 		if (debug)
1430bfd817adSflorian 			fprintf(ttyout,
1431bfd817adSflorian 			    "user %s:%s host %s port %s dir %s file %s\n",
1432bfd817adSflorian 			    username, pass ? "XXXX" : NULL, host, portnum,
1433bfd817adSflorian 			    dir, file);
1434bfd817adSflorian #endif /* !SMALL */
1435bfd817adSflorian 
1436bfd817adSflorian 		/*
1437bfd817adSflorian 		 * Set up the connection.
1438bfd817adSflorian 		 */
1439bfd817adSflorian 		if (connected)
1440bfd817adSflorian 			disconnect(0, NULL);
1441bfd817adSflorian 		xargv[0] = __progname;
1442bfd817adSflorian 		xargv[1] = host;
1443bfd817adSflorian 		xargv[2] = NULL;
1444bfd817adSflorian 		xargc = 2;
1445bfd817adSflorian 		if (!EMPTYSTRING(portnum)) {
1446bfd817adSflorian 			xargv[2] = portnum;
1447bfd817adSflorian 			xargv[3] = NULL;
1448bfd817adSflorian 			xargc = 3;
1449bfd817adSflorian 		}
1450bfd817adSflorian 		oautologin = autologin;
1451bfd817adSflorian 		if (username == NULL)
1452bfd817adSflorian 			anonftp = 1;
1453bfd817adSflorian 		else {
1454bfd817adSflorian 			anonftp = 0;
1455bfd817adSflorian 			autologin = 0;
1456bfd817adSflorian 		}
1457bfd817adSflorian 		setpeer(xargc, xargv);
1458bfd817adSflorian 		autologin = oautologin;
1459bfd817adSflorian 		if (connected == 0 ||
1460bfd817adSflorian 		    (connected == 1 && autologin && (username == NULL ||
1461bfd817adSflorian 		    !ftp_login(host, username, pass)))) {
1462bfd817adSflorian 			warnx("Can't connect or login to host `%s'", host);
1463bfd817adSflorian 			rval = argpos + 1;
1464bfd817adSflorian 			continue;
1465bfd817adSflorian 		}
1466bfd817adSflorian 
1467bfd817adSflorian 		/* Always use binary transfers. */
1468bfd817adSflorian 		setbinary(0, NULL);
1469bfd817adSflorian 
1470bfd817adSflorian 		dirhasglob = filehasglob = 0;
1471bfd817adSflorian 		if (doglob) {
1472bfd817adSflorian 			if (!EMPTYSTRING(dir) &&
1473bfd817adSflorian 			    strpbrk(dir, "*?[]{}") != NULL)
1474bfd817adSflorian 				dirhasglob = 1;
1475bfd817adSflorian 			if (!EMPTYSTRING(file) &&
1476bfd817adSflorian 			    strpbrk(file, "*?[]{}") != NULL)
1477bfd817adSflorian 				filehasglob = 1;
1478bfd817adSflorian 		}
1479bfd817adSflorian 
1480bfd817adSflorian 		/* Change directories, if necessary. */
1481bfd817adSflorian 		if (!EMPTYSTRING(dir) && !dirhasglob) {
1482bfd817adSflorian 			xargv[0] = "cd";
1483bfd817adSflorian 			xargv[1] = dir;
1484bfd817adSflorian 			xargv[2] = NULL;
1485bfd817adSflorian 			cd(2, xargv);
1486bfd817adSflorian 			if (!dirchange) {
1487bfd817adSflorian 				rval = argpos + 1;
1488bfd817adSflorian 				continue;
1489bfd817adSflorian 			}
1490bfd817adSflorian 		}
1491bfd817adSflorian 
1492bfd817adSflorian 		if (EMPTYSTRING(file)) {
1493bfd817adSflorian #ifndef SMALL
1494bfd817adSflorian 			rval = -1;
1495bfd817adSflorian #else /* !SMALL */
1496bfd817adSflorian 			recvrequest("NLST", "-", NULL, "w", 0, 0);
1497bfd817adSflorian 			rval = 0;
1498bfd817adSflorian #endif /* !SMALL */
1499bfd817adSflorian 			continue;
1500bfd817adSflorian 		}
1501bfd817adSflorian 
1502bfd817adSflorian 		if (verbose)
1503bfd817adSflorian 			fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1504bfd817adSflorian 
1505bfd817adSflorian 		if (dirhasglob) {
1506bfd817adSflorian 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
1507bfd817adSflorian 			file = rempath;
1508bfd817adSflorian 		}
1509bfd817adSflorian 
1510bfd817adSflorian 		/* Fetch the file(s). */
1511bfd817adSflorian 		xargc = 2;
1512bfd817adSflorian 		xargv[0] = "get";
1513bfd817adSflorian 		xargv[1] = file;
1514bfd817adSflorian 		xargv[2] = NULL;
1515bfd817adSflorian 		if (dirhasglob || filehasglob) {
1516bfd817adSflorian 			int ointeractive;
1517bfd817adSflorian 
1518bfd817adSflorian 			ointeractive = interactive;
1519bfd817adSflorian 			interactive = 0;
1520bfd817adSflorian 			xargv[0] = "mget";
1521bfd817adSflorian #ifndef SMALL
1522bfd817adSflorian 			if (resume) {
1523bfd817adSflorian 				xargc = 3;
1524bfd817adSflorian 				xargv[1] = "-c";
1525bfd817adSflorian 				xargv[2] = file;
1526bfd817adSflorian 				xargv[3] = NULL;
1527bfd817adSflorian 			}
1528bfd817adSflorian #endif /* !SMALL */
1529bfd817adSflorian 			mget(xargc, xargv);
1530bfd817adSflorian 			interactive = ointeractive;
1531bfd817adSflorian 		} else {
1532bfd817adSflorian 			if (outfile != NULL) {
1533bfd817adSflorian 				xargv[2] = outfile;
1534bfd817adSflorian 				xargv[3] = NULL;
1535bfd817adSflorian 				xargc++;
1536bfd817adSflorian 			}
1537bfd817adSflorian #ifndef SMALL
1538bfd817adSflorian 			if (resume)
1539bfd817adSflorian 				reget(xargc, xargv);
1540bfd817adSflorian 			else
1541bfd817adSflorian #endif /* !SMALL */
1542bfd817adSflorian 				get(xargc, xargv);
1543bfd817adSflorian 		}
1544bfd817adSflorian 
1545bfd817adSflorian 		if ((code / 100) != COMPLETE)
1546bfd817adSflorian 			rval = argpos + 1;
1547bfd817adSflorian 	}
1548bfd817adSflorian 	if (connected && rval != -1)
1549bfd817adSflorian 		disconnect(0, NULL);
1550bfd817adSflorian 	return (rval);
1551bfd817adSflorian }
1552bfd817adSflorian 
1553bfd817adSflorian char *
urldecode(const char * str)1554bfd817adSflorian urldecode(const char *str)
1555bfd817adSflorian {
1556bfd817adSflorian 	char *ret, c;
1557bfd817adSflorian 	int i, reallen;
1558bfd817adSflorian 
1559bfd817adSflorian 	if (str == NULL)
1560bfd817adSflorian 		return NULL;
1561bfd817adSflorian 	if ((ret = malloc(strlen(str) + 1)) == NULL)
1562bfd817adSflorian 		err(1, "Can't allocate memory for URL decoding");
1563bfd817adSflorian 	for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
1564bfd817adSflorian 		c = str[i];
1565bfd817adSflorian 		if (c == '+') {
1566bfd817adSflorian 			*ret = ' ';
1567bfd817adSflorian 			continue;
1568bfd817adSflorian 		}
1569bfd817adSflorian 
1570bfd817adSflorian 		/* Cannot use strtol here because next char
1571bfd817adSflorian 		 * after %xx may be a digit.
1572bfd817adSflorian 		 */
1573bfd817adSflorian 		if (c == '%' && isxdigit((unsigned char)str[i + 1]) &&
1574bfd817adSflorian 		    isxdigit((unsigned char)str[i + 2])) {
1575bfd817adSflorian 			*ret = hextochar(&str[i + 1]);
1576bfd817adSflorian 			i += 2;
1577bfd817adSflorian 			continue;
1578bfd817adSflorian 		}
1579bfd817adSflorian 		*ret = c;
1580bfd817adSflorian 	}
1581bfd817adSflorian 	*ret = '\0';
1582bfd817adSflorian 
1583bfd817adSflorian 	return ret - reallen;
1584bfd817adSflorian }
1585bfd817adSflorian 
1586194d6d9dSjca static char *
recode_credentials(const char * userinfo)1587bfd817adSflorian recode_credentials(const char *userinfo)
1588bfd817adSflorian {
1589bfd817adSflorian 	char *ui, *creds;
1590bfd817adSflorian 	size_t ulen, credsize;
1591bfd817adSflorian 
1592bfd817adSflorian 	/* url-decode the user and pass */
1593bfd817adSflorian 	ui = urldecode(userinfo);
1594bfd817adSflorian 
1595bfd817adSflorian 	ulen = strlen(ui);
1596bfd817adSflorian 	credsize = (ulen + 2) / 3 * 4 + 1;
1597bfd817adSflorian 	creds = malloc(credsize);
1598bfd817adSflorian 	if (creds == NULL)
1599bfd817adSflorian 		errx(1, "out of memory");
1600bfd817adSflorian 	if (b64_ntop(ui, ulen, creds, credsize) == -1)
1601bfd817adSflorian 		errx(1, "error in base64 encoding");
1602bfd817adSflorian 	free(ui);
1603bfd817adSflorian 	return (creds);
1604bfd817adSflorian }
1605bfd817adSflorian 
1606194d6d9dSjca static char
hextochar(const char * str)1607bfd817adSflorian hextochar(const char *str)
1608bfd817adSflorian {
1609bfd817adSflorian 	unsigned char c, ret;
1610bfd817adSflorian 
1611bfd817adSflorian 	c = str[0];
1612bfd817adSflorian 	ret = c;
1613bfd817adSflorian 	if (isalpha(c))
1614bfd817adSflorian 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1615bfd817adSflorian 	else
1616bfd817adSflorian 		ret -= '0';
1617bfd817adSflorian 	ret *= 16;
1618bfd817adSflorian 
1619bfd817adSflorian 	c = str[1];
1620bfd817adSflorian 	ret += c;
1621bfd817adSflorian 	if (isalpha(c))
1622bfd817adSflorian 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1623bfd817adSflorian 	else
1624bfd817adSflorian 		ret -= '0';
1625bfd817adSflorian 	return ret;
1626bfd817adSflorian }
1627bfd817adSflorian 
1628bfd817adSflorian int
isurl(const char * p)1629bfd817adSflorian isurl(const char *p)
1630bfd817adSflorian {
1631bfd817adSflorian 
1632bfd817adSflorian 	if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
1633bfd817adSflorian 	    strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1634bfd817adSflorian #ifndef NOSSL
1635bfd817adSflorian 	    strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
1636bfd817adSflorian #endif /* !NOSSL */
1637bfd817adSflorian 	    strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
1638bfd817adSflorian 	    strstr(p, ":/"))
1639bfd817adSflorian 		return (1);
1640bfd817adSflorian 	return (0);
1641bfd817adSflorian }
1642bfd817adSflorian 
1643b237d516Sjca #ifndef SMALL
1644b237d516Sjca static int
ftp_printf(FILE * fp,const char * fmt,...)1645b237d516Sjca ftp_printf(FILE *fp, const char *fmt, ...)
1646b237d516Sjca {
1647b237d516Sjca 	va_list	ap;
1648b237d516Sjca 	int	ret;
1649b237d516Sjca 
1650b237d516Sjca 	va_start(ap, fmt);
1651b237d516Sjca 	ret = vfprintf(fp, fmt, ap);
1652b237d516Sjca 	va_end(ap);
1653b237d516Sjca 
1654b237d516Sjca 	if (debug) {
1655b237d516Sjca 		va_start(ap, fmt);
1656b237d516Sjca 		vfprintf(ttyout, fmt, ap);
1657b237d516Sjca 		va_end(ap);
1658b237d516Sjca 	}
1659b237d516Sjca 
1660b237d516Sjca 	return ret;
1661b237d516Sjca }
1662b237d516Sjca #endif /* !SMALL */
1663b237d516Sjca 
1664194d6d9dSjca static void
ftp_close(FILE ** fin,struct tls ** tls,int * fd)1665583394bbSjca ftp_close(FILE **fin, struct tls **tls, int *fd)
1666535fae4eSjca {
1667535fae4eSjca #ifndef NOSSL
1668535fae4eSjca 	int	ret;
1669535fae4eSjca 
1670535fae4eSjca 	if (*tls != NULL) {
1671535fae4eSjca 		if (tls_session_fd != -1)
1672535fae4eSjca 			dprintf(STDERR_FILENO, "tls session resumed: %s\n",
1673535fae4eSjca 			    tls_conn_session_resumed(*tls) ? "yes" : "no");
1674535fae4eSjca 		do {
1675535fae4eSjca 			ret = tls_close(*tls);
1676535fae4eSjca 		} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
1677535fae4eSjca 		tls_free(*tls);
1678535fae4eSjca 		*tls = NULL;
1679535fae4eSjca 	}
168094d4d1aaSjca 	if (*fd != -1) {
168194d4d1aaSjca 		close(*fd);
168294d4d1aaSjca 		*fd = -1;
168394d4d1aaSjca 	}
1684535fae4eSjca #endif
1685535fae4eSjca 	if (*fin != NULL) {
1686535fae4eSjca 		fclose(*fin);
1687535fae4eSjca 		*fin = NULL;
1688535fae4eSjca 	}
1689535fae4eSjca }
169094d4d1aaSjca 
1691194d6d9dSjca static const char *
sockerror(struct tls * tls)169294d4d1aaSjca sockerror(struct tls *tls)
169394d4d1aaSjca {
169494d4d1aaSjca 	int	save_errno = errno;
169594d4d1aaSjca #ifndef NOSSL
169694d4d1aaSjca 	if (tls != NULL) {
169794d4d1aaSjca 		const char *tlserr = tls_error(tls);
169894d4d1aaSjca 		if (tlserr != NULL)
169994d4d1aaSjca 			return tlserr;
170094d4d1aaSjca 	}
170194d4d1aaSjca #endif
170294d4d1aaSjca 	return strerror(save_errno);
1703535fae4eSjca }
1704535fae4eSjca 
1705bfd817adSflorian #ifndef NOSSL
1706194d6d9dSjca static int
proxy_connect(int socket,char * host,char * cookie)1707bfd817adSflorian proxy_connect(int socket, char *host, char *cookie)
1708bfd817adSflorian {
1709bfd817adSflorian 	int l;
1710bfd817adSflorian 	char buf[1024];
17118a4b8ac2Syasuoka 	char *connstr, *hosttail, *port;
1712bfd817adSflorian 
1713bfd817adSflorian 	if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
1714bfd817adSflorian 		(hosttail[1] == '\0' || hosttail[1] == ':')) {
1715bfd817adSflorian 		host++;
1716bfd817adSflorian 		*hosttail++ = '\0';
1717bfd817adSflorian 	} else
1718bfd817adSflorian 		hosttail = host;
1719bfd817adSflorian 
1720bfd817adSflorian 	port = strrchr(hosttail, ':');		/* find portnum */
1721bfd817adSflorian 	if (port != NULL)
1722bfd817adSflorian 		*port++ = '\0';
1723bfd817adSflorian 	if (!port)
1724bfd817adSflorian 		port = "443";
1725bfd817adSflorian 
1726bfd817adSflorian 	if (cookie) {
1727bfd817adSflorian 		l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1728*8cc1b8faSsthen 			"Host: %s:%s\r\n"
1729bfd817adSflorian 			"Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
1730*8cc1b8faSsthen 			host, port, host, port, cookie, HTTP_USER_AGENT);
1731bfd817adSflorian 	} else {
1732*8cc1b8faSsthen 		l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1733*8cc1b8faSsthen 			"Host: %s:%s\r\n%s\r\n\r\n",
1734*8cc1b8faSsthen 			host, port, host, port, HTTP_USER_AGENT);
1735bfd817adSflorian 	}
1736bfd817adSflorian 
1737bfd817adSflorian 	if (l == -1)
1738bfd817adSflorian 		errx(1, "Could not allocate memory to assemble connect string!");
1739bfd817adSflorian #ifndef SMALL
1740bfd817adSflorian 	if (debug)
1741bfd817adSflorian 		printf("%s", connstr);
1742bfd817adSflorian #endif /* !SMALL */
1743bfd817adSflorian 	if (write(socket, connstr, l) != l)
1744bfd817adSflorian 		err(1, "Could not send connect string");
17458a4b8ac2Syasuoka 	read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
1746bfd817adSflorian 	free(connstr);
1747bfd817adSflorian 	return(200);
1748bfd817adSflorian }
174994d4d1aaSjca 
1750194d6d9dSjca static int
stdio_tls_write_wrapper(void * arg,const char * buf,int len)175194d4d1aaSjca stdio_tls_write_wrapper(void *arg, const char *buf, int len)
175294d4d1aaSjca {
175394d4d1aaSjca 	struct tls *tls = arg;
175494d4d1aaSjca 	ssize_t ret;
175594d4d1aaSjca 
175694d4d1aaSjca 	do {
175794d4d1aaSjca 		ret = tls_write(tls, buf, len);
175894d4d1aaSjca 	} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
175994d4d1aaSjca 
176094d4d1aaSjca 	return ret;
176194d4d1aaSjca }
176294d4d1aaSjca 
1763194d6d9dSjca static int
stdio_tls_read_wrapper(void * arg,char * buf,int len)176494d4d1aaSjca stdio_tls_read_wrapper(void *arg, char *buf, int len)
176594d4d1aaSjca {
176694d4d1aaSjca 	struct tls *tls = arg;
176794d4d1aaSjca 	ssize_t ret;
176894d4d1aaSjca 
176994d4d1aaSjca 	do {
177094d4d1aaSjca 		ret = tls_read(tls, buf, len);
177194d4d1aaSjca 	} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
177294d4d1aaSjca 
177394d4d1aaSjca 	return ret;
177494d4d1aaSjca }
1775bfd817adSflorian #endif /* !NOSSL */
1776