fetch.c revision 261233
162216Sdes/*-
2261233Sdes * Copyright (c) 2000-2014 Dag-Erling Sm��rgrav
3253680Sdes * Copyright (c) 2013 Michael Gmelin <freebsd@grem.de>
462216Sdes * All rights reserved.
562216Sdes *
662216Sdes * Redistribution and use in source and binary forms, with or without
762216Sdes * modification, are permitted provided that the following conditions
862216Sdes * are met:
962216Sdes * 1. Redistributions of source code must retain the above copyright
1062216Sdes *    notice, this list of conditions and the following disclaimer
1162216Sdes *    in this position and unchanged.
1262216Sdes * 2. Redistributions in binary form must reproduce the above copyright
1362216Sdes *    notice, this list of conditions and the following disclaimer in the
1462216Sdes *    documentation and/or other materials provided with the distribution.
1562216Sdes * 3. The name of the author may not be used to endorse or promote products
1662216Sdes *    derived from this software without specific prior written permission
1762216Sdes *
1862216Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1962216Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2062216Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2162216Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2262216Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2362216Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2462216Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2562216Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2662216Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2762216Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2862216Sdes */
2962216Sdes
3093213Scharnier#include <sys/cdefs.h>
3193213Scharnier__FBSDID("$FreeBSD: head/usr.bin/fetch/fetch.c 261233 2014-01-28 14:29:24Z des $");
3293213Scharnier
3362216Sdes#include <sys/param.h>
3491225Sbde#include <sys/socket.h>
3562216Sdes#include <sys/stat.h>
3693257Sbde#include <sys/time.h>
3762216Sdes
38200462Sdelphij#include <ctype.h>
3962216Sdes#include <err.h>
4062216Sdes#include <errno.h>
41253680Sdes#include <getopt.h>
4263235Sdes#include <signal.h>
43125976Sdes#include <stdint.h>
4462216Sdes#include <stdio.h>
4562216Sdes#include <stdlib.h>
4662216Sdes#include <string.h>
4777241Sdes#include <termios.h>
4862216Sdes#include <unistd.h>
4962216Sdes
5062216Sdes#include <fetch.h>
5162216Sdes
5262216Sdes#define MINBUFSIZE	4096
53187361Sdes#define TIMEOUT		120
5462216Sdes
5562216Sdes/* Option flags */
56241737Sedstatic int	 A_flag;	/*    -A: do not follow 302 redirects */
57241737Sedstatic int	 a_flag;	/*    -a: auto retry */
58241737Sedstatic off_t	 B_size;	/*    -B: buffer size */
59241737Sedstatic int	 b_flag;	/*!   -b: workaround TCP bug */
60241737Sedstatic char    *c_dirname;	/*    -c: remote directory */
61241737Sedstatic int	 d_flag;	/*    -d: direct connection */
62241737Sedstatic int	 F_flag;	/*    -F: restart without checking mtime  */
63241737Sedstatic char	*f_filename;	/*    -f: file to fetch */
64241737Sedstatic char	*h_hostname;	/*    -h: host to fetch from */
65241737Sedstatic int	 i_flag;	/*    -i: specify file for mtime comparison */
66241737Sedstatic char	*i_filename;	/*        name of input file */
67241737Sedstatic int	 l_flag;	/*    -l: link rather than copy file: URLs */
68241737Sedstatic int	 m_flag;	/* -[Mm]: mirror mode */
69241737Sedstatic char	*N_filename;	/*    -N: netrc file name */
70241737Sedstatic int	 n_flag;	/*    -n: do not preserve modification time */
71241737Sedstatic int	 o_flag;	/*    -o: specify output file */
72241737Sedstatic int	 o_directory;	/*        output file is a directory */
73241737Sedstatic char	*o_filename;	/*        name of output file */
74241737Sedstatic int	 o_stdout;	/*        output file is stdout */
75241737Sedstatic int	 once_flag;	/*    -1: stop at first successful file */
76241737Sedstatic int	 p_flag;	/* -[Pp]: use passive FTP */
77241737Sedstatic int	 R_flag;	/*    -R: don't delete partial files */
78241737Sedstatic int	 r_flag;	/*    -r: restart previous transfer */
79241737Sedstatic off_t	 S_size;        /*    -S: require size to match */
80241737Sedstatic int	 s_flag;        /*    -s: show size, don't fetch */
81241737Sedstatic long	 T_secs;	/*    -T: transfer timeout in seconds */
82241737Sedstatic int	 t_flag;	/*!   -t: workaround TCP bug */
83241737Sedstatic int	 U_flag;	/*    -U: do not use high ports */
84241737Sedstatic int	 v_level = 1;	/*    -v: verbosity level */
85241737Sedstatic int	 v_tty;		/*        stdout is a tty */
86241737Sedstatic pid_t	 pgrp;		/*        our process group */
87241737Sedstatic long	 w_secs;	/*    -w: retry delay */
88241737Sedstatic int	 family = PF_UNSPEC;	/* -[46]: address family to use */
8962216Sdes
90241737Sedstatic int	 sigalrm;	/* SIGALRM received */
91241737Sedstatic int	 siginfo;	/* SIGINFO received */
92241737Sedstatic int	 sigint;	/* SIGINT received */
9362216Sdes
94241737Sedstatic long	 ftp_timeout = TIMEOUT;	/* default timeout for FTP transfers */
95241737Sedstatic long	 http_timeout = TIMEOUT;/* default timeout for HTTP transfers */
96241737Sedstatic char	*buf;		/* transfer buffer */
9762216Sdes
98253680Sdesenum options
99253680Sdes{
100253680Sdes	OPTION_BIND_ADDRESS,
101253680Sdes	OPTION_NO_FTP_PASSIVE_MODE,
102253680Sdes	OPTION_HTTP_REFERER,
103253680Sdes	OPTION_HTTP_USER_AGENT,
104253680Sdes	OPTION_NO_PROXY,
105253680Sdes	OPTION_SSL_ALLOW_SSL2,
106253680Sdes	OPTION_SSL_CA_CERT_FILE,
107253680Sdes	OPTION_SSL_CA_CERT_PATH,
108253680Sdes	OPTION_SSL_CLIENT_CERT_FILE,
109253680Sdes	OPTION_SSL_CLIENT_KEY_FILE,
110253680Sdes	OPTION_SSL_CRL_FILE,
111253680Sdes	OPTION_SSL_NO_SSL3,
112261233Sdes	OPTION_SSL_NO_TLS1,
113253680Sdes	OPTION_SSL_NO_VERIFY_HOSTNAME,
114253680Sdes	OPTION_SSL_NO_VERIFY_PEER
115253680Sdes};
11662216Sdes
117253680Sdes
118253680Sdesstatic struct option longopts[] =
119253680Sdes{
120253680Sdes	/* mapping to single character argument */
121253680Sdes	{ "one-file", no_argument, NULL, '1' },
122253680Sdes	{ "ipv4-only", no_argument, NULL, '4' },
123253680Sdes	{ "ipv6-only", no_argument, NULL, '6' },
124253680Sdes	{ "no-redirect", no_argument, NULL, 'A' },
125253680Sdes	{ "retry", no_argument, NULL, 'a' },
126253680Sdes	{ "buffer-size", required_argument, NULL, 'B' },
127253680Sdes	/* -c not mapped, since it's deprecated */
128253680Sdes	{ "direct", no_argument, NULL, 'd' },
129253680Sdes	{ "force-restart", no_argument, NULL, 'F' },
130253680Sdes	/* -f not mapped, since it's deprecated */
131253680Sdes	/* -h not mapped, since it's deprecated */
132253680Sdes	{ "if-modified-since", required_argument, NULL, 'i' },
133253680Sdes	{ "symlink", no_argument, NULL, 'l' },
134253680Sdes	/* -M not mapped since it's the same as -m */
135253680Sdes	{ "mirror", no_argument, NULL, 'm' },
136253680Sdes	{ "netrc", required_argument, NULL, 'N' },
137253680Sdes	{ "no-mtime", no_argument, NULL, 'n' },
138253680Sdes	{ "output", required_argument, NULL, 'o' },
139253680Sdes	/* -P not mapped since it's the same as -p */
140253680Sdes	{ "passive", no_argument, NULL, 'p' },
141253680Sdes	{ "quiet", no_argument, NULL, 'q' },
142253680Sdes	{ "keep-output", no_argument, NULL, 'R' },
143253680Sdes	{ "restart", no_argument, NULL, 'r' },
144253680Sdes	{ "require-size", required_argument, NULL, 'S' },
145253680Sdes	{ "print-size", no_argument, NULL, 's' },
146253680Sdes	{ "timeout", required_argument, NULL, 'T' },
147253680Sdes	{ "passive-portrange-default", no_argument, NULL, 'T' },
148253680Sdes	{ "verbose", no_argument, NULL, 'v' },
149253680Sdes	{ "retry-delay", required_argument, NULL, 'w' },
150261233Sdes
151253680Sdes	/* options without a single character equivalent */
152253680Sdes	{ "bind-address", required_argument, NULL, OPTION_BIND_ADDRESS },
153253680Sdes	{ "no-passive", no_argument, NULL, OPTION_NO_FTP_PASSIVE_MODE },
154253680Sdes	{ "referer", required_argument, NULL, OPTION_HTTP_REFERER },
155253680Sdes	{ "user-agent", required_argument, NULL, OPTION_HTTP_USER_AGENT },
156253680Sdes	{ "no-proxy", required_argument, NULL, OPTION_NO_PROXY },
157253680Sdes	{ "allow-sslv2", no_argument, NULL, OPTION_SSL_ALLOW_SSL2 },
158253680Sdes	{ "ca-cert", required_argument, NULL, OPTION_SSL_CA_CERT_FILE },
159253680Sdes	{ "ca-path", required_argument, NULL, OPTION_SSL_CA_CERT_PATH },
160253680Sdes	{ "cert", required_argument, NULL, OPTION_SSL_CLIENT_CERT_FILE },
161253680Sdes	{ "key", required_argument, NULL, OPTION_SSL_CLIENT_KEY_FILE },
162253680Sdes	{ "crl", required_argument, NULL, OPTION_SSL_CRL_FILE },
163253680Sdes	{ "no-sslv3", no_argument, NULL, OPTION_SSL_NO_SSL3 },
164253680Sdes	{ "no-tlsv1", no_argument, NULL, OPTION_SSL_NO_TLS1 },
165253680Sdes	{ "no-verify-hostname", no_argument, NULL, OPTION_SSL_NO_VERIFY_HOSTNAME },
166253680Sdes	{ "no-verify-peer", no_argument, NULL, OPTION_SSL_NO_VERIFY_PEER },
167253680Sdes
168253680Sdes	{ NULL, 0, NULL, 0 }
169253680Sdes};
170253680Sdes
17181863Sdes/*
17281863Sdes * Signal handler
17381863Sdes */
17479837Sdesstatic void
17562216Sdessig_handler(int sig)
17662216Sdes{
17779837Sdes	switch (sig) {
17879837Sdes	case SIGALRM:
17979837Sdes		sigalrm = 1;
18079837Sdes		break;
18179837Sdes	case SIGINFO:
18279837Sdes		siginfo = 1;
18379837Sdes		break;
18479837Sdes	case SIGINT:
18579837Sdes		sigint = 1;
18679837Sdes		break;
18779837Sdes	}
18862216Sdes}
18962216Sdes
19062216Sdesstruct xferstat {
191125965Sdes	char		 name[64];
192243147Sandre	struct timeval	 start;		/* start of transfer */
193243147Sandre	struct timeval	 last;		/* time of last update */
194243147Sandre	struct timeval	 last2;		/* time of previous last update */
195243147Sandre	off_t		 size;		/* size of file per HTTP hdr */
196243147Sandre	off_t		 offset;	/* starting offset in file */
197243147Sandre	off_t		 rcvd;		/* bytes already received */
198243147Sandre	off_t		 lastrcvd;	/* bytes received since last update */
19962216Sdes};
20062216Sdes
20181863Sdes/*
202109702Sdes * Compute and display ETA
203109702Sdes */
204125965Sdesstatic const char *
205109702Sdesstat_eta(struct xferstat *xs)
206109702Sdes{
207125965Sdes	static char str[16];
208125976Sdes	long elapsed, eta;
209125976Sdes	off_t received, expected;
210109702Sdes
211109702Sdes	elapsed = xs->last.tv_sec - xs->start.tv_sec;
212112083Sdes	received = xs->rcvd - xs->offset;
213112083Sdes	expected = xs->size - xs->rcvd;
214112114Sdes	eta = (long)((double)elapsed * expected / received);
215125965Sdes	if (eta > 3600)
216125965Sdes		snprintf(str, sizeof str, "%02ldh%02ldm",
217125965Sdes		    eta / 3600, (eta % 3600) / 60);
218243147Sandre	else if (eta > 0)
219243147Sandre		snprintf(str, sizeof str, "%02ldm%02lds",
220243147Sandre		    eta / 60, eta % 60);
221125965Sdes	else
222125965Sdes		snprintf(str, sizeof str, "%02ldm%02lds",
223243147Sandre		    elapsed / 60, elapsed % 60);
224125965Sdes	return (str);
225125965Sdes}
226125965Sdes
227125965Sdes/*
228125965Sdes * Format a number as "xxxx YB" where Y is ' ', 'k', 'M'...
229125965Sdes */
230125965Sdesstatic const char *prefixes = " kMGTP";
231125965Sdesstatic const char *
232129440Slestat_bytes(off_t bytes)
233125965Sdes{
234125965Sdes	static char str[16];
235125965Sdes	const char *prefix = prefixes;
236125965Sdes
237125965Sdes	while (bytes > 9999 && prefix[1] != '\0') {
238125965Sdes		bytes /= 1024;
239125965Sdes		prefix++;
240109702Sdes	}
241129440Sle	snprintf(str, sizeof str, "%4jd %cB", (intmax_t)bytes, *prefix);
242125965Sdes	return (str);
243109702Sdes}
244109702Sdes
245109702Sdes/*
246109702Sdes * Compute and display transfer rate
247109702Sdes */
248125965Sdesstatic const char *
249109702Sdesstat_bps(struct xferstat *xs)
250109702Sdes{
251125965Sdes	static char str[16];
252109735Sdes	double delta, bps;
253109702Sdes
254109735Sdes	delta = (xs->last.tv_sec + (xs->last.tv_usec / 1.e6))
255243147Sandre	    - (xs->last2.tv_sec + (xs->last2.tv_usec / 1.e6));
256243147Sandre
257109735Sdes	if (delta == 0.0) {
258125965Sdes		snprintf(str, sizeof str, "?? Bps");
259125965Sdes	} else {
260244058Sandre		bps = (xs->rcvd - xs->lastrcvd) / delta;
261129440Sle		snprintf(str, sizeof str, "%sps", stat_bytes((off_t)bps));
262109735Sdes	}
263125965Sdes	return (str);
264109702Sdes}
265109702Sdes
266109702Sdes/*
26781863Sdes * Update the stats display
26881863Sdes */
26979837Sdesstatic void
27063046Sdesstat_display(struct xferstat *xs, int force)
27162216Sdes{
27279837Sdes	struct timeval now;
27383863Sdes	int ctty_pgrp;
27479837Sdes
27583863Sdes	/* check if we're the foreground process */
27683863Sdes	if (ioctl(STDERR_FILENO, TIOCGPGRP, &ctty_pgrp) == -1 ||
27783863Sdes	    (pid_t)ctty_pgrp != pgrp)
27883863Sdes		return;
279106043Sdes
28079837Sdes	gettimeofday(&now, NULL);
28179837Sdes	if (!force && now.tv_sec <= xs->last.tv_sec)
28279837Sdes		return;
283243147Sandre	xs->last2 = xs->last;
28479837Sdes	xs->last = now;
28579837Sdes
286131615Sdes	fprintf(stderr, "\r%-46.46s", xs->name);
287106041Sdes	if (xs->size <= 0) {
288153894Sdes		setproctitle("%s [%s]", xs->name, stat_bytes(xs->rcvd));
289125965Sdes		fprintf(stderr, "        %s", stat_bytes(xs->rcvd));
290106041Sdes	} else {
291153894Sdes		setproctitle("%s [%d%% of %s]", xs->name,
292153894Sdes		    (int)((100.0 * xs->rcvd) / xs->size),
293153894Sdes		    stat_bytes(xs->size));
294125965Sdes		fprintf(stderr, "%3d%% of %s",
295125965Sdes		    (int)((100.0 * xs->rcvd) / xs->size),
296125965Sdes		    stat_bytes(xs->size));
297106041Sdes	}
298243147Sandre	if (force == 2) {
299243147Sandre		xs->lastrcvd = xs->offset;
300243147Sandre		xs->last2 = xs->start;
301243147Sandre	}
302125965Sdes	fprintf(stderr, " %s", stat_bps(xs));
303243147Sandre	if ((xs->size > 0 && xs->rcvd > 0 &&
304243147Sandre	     xs->last.tv_sec >= xs->start.tv_sec + 3) ||
305243147Sandre	    force == 2)
306125965Sdes		fprintf(stderr, " %s", stat_eta(xs));
307243147Sandre	xs->lastrcvd = xs->rcvd;
30862216Sdes}
30962216Sdes
31081863Sdes/*
31181863Sdes * Initialize the transfer statistics
31281863Sdes */
31379837Sdesstatic void
31479837Sdesstat_start(struct xferstat *xs, const char *name, off_t size, off_t offset)
31563046Sdes{
31679837Sdes	snprintf(xs->name, sizeof xs->name, "%s", name);
31779837Sdes	gettimeofday(&xs->start, NULL);
31879837Sdes	xs->last.tv_sec = xs->last.tv_usec = 0;
31979837Sdes	xs->size = size;
32079837Sdes	xs->offset = offset;
32179837Sdes	xs->rcvd = offset;
322243147Sandre	xs->lastrcvd = offset;
323125965Sdes	if (v_tty && v_level > 0)
324125965Sdes		stat_display(xs, 1);
325125965Sdes	else if (v_level > 0)
326125965Sdes		fprintf(stderr, "%-46s", xs->name);
32763046Sdes}
32863046Sdes
32981863Sdes/*
33081863Sdes * Update the transfer statistics
33181863Sdes */
33279837Sdesstatic void
33379837Sdesstat_update(struct xferstat *xs, off_t rcvd)
33463046Sdes{
33579837Sdes	xs->rcvd = rcvd;
336125965Sdes	if (v_tty && v_level > 0)
337125965Sdes		stat_display(xs, 0);
33863046Sdes}
33963046Sdes
34081863Sdes/*
34181863Sdes * Finalize the transfer statistics
34281863Sdes */
34379837Sdesstatic void
34462216Sdesstat_end(struct xferstat *xs)
34562216Sdes{
346109735Sdes	gettimeofday(&xs->last, NULL);
347125965Sdes	if (v_tty && v_level > 0) {
348243147Sandre		stat_display(xs, 2);
349125965Sdes		putc('\n', stderr);
350125965Sdes	} else if (v_level > 0) {
351125965Sdes		fprintf(stderr, "        %s %s\n",
352125965Sdes		    stat_bytes(xs->size), stat_bps(xs));
353125965Sdes	}
35462216Sdes}
35562216Sdes
35681863Sdes/*
35781863Sdes * Ask the user for authentication details
35881863Sdes */
35979837Sdesstatic int
36077241Sdesquery_auth(struct url *URL)
36177241Sdes{
36279837Sdes	struct termios tios;
36379837Sdes	tcflag_t saved_flags;
36479837Sdes	int i, nopwd;
36577241Sdes
36679837Sdes	fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n",
36786242Siedowse	    URL->scheme, URL->host, URL->port);
36879837Sdes
36979837Sdes	fprintf(stderr, "Login: ");
37079837Sdes	if (fgets(URL->user, sizeof URL->user, stdin) == NULL)
371132695Sdes		return (-1);
372132696Sdes	for (i = strlen(URL->user); i >= 0; --i)
373132696Sdes		if (URL->user[i] == '\r' || URL->user[i] == '\n')
37479837Sdes			URL->user[i] = '\0';
37579837Sdes
37679837Sdes	fprintf(stderr, "Password: ");
37779837Sdes	if (tcgetattr(STDIN_FILENO, &tios) == 0) {
37879837Sdes		saved_flags = tios.c_lflag;
37979837Sdes		tios.c_lflag &= ~ECHO;
38079837Sdes		tios.c_lflag |= ECHONL|ICANON;
38179837Sdes		tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios);
38279837Sdes		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
38379837Sdes		tios.c_lflag = saved_flags;
38479837Sdes		tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios);
38579837Sdes	} else {
38679837Sdes		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
38779837Sdes	}
38879837Sdes	if (nopwd)
389132695Sdes		return (-1);
390132696Sdes	for (i = strlen(URL->pwd); i >= 0; --i)
391132696Sdes		if (URL->pwd[i] == '\r' || URL->pwd[i] == '\n')
392132696Sdes			URL->pwd[i] = '\0';
39379837Sdes
394132695Sdes	return (0);
39577241Sdes}
39677241Sdes
39781863Sdes/*
39881863Sdes * Fetch a file
39981863Sdes */
40079837Sdesstatic int
40179837Sdesfetch(char *URL, const char *path)
40262216Sdes{
40379837Sdes	struct url *url;
40479837Sdes	struct url_stat us;
40583217Sdes	struct stat sb, nsb;
40679837Sdes	struct xferstat xs;
40779837Sdes	FILE *f, *of;
408230307Sdes	size_t size, readcnt, wr;
40979837Sdes	off_t count;
41079837Sdes	char flags[8];
41183217Sdes	const char *slash;
41283217Sdes	char *tmppath;
41379837Sdes	int r;
414125976Sdes	unsigned timeout;
415125976Sdes	char *ptr;
41662216Sdes
41779837Sdes	f = of = NULL;
41883217Sdes	tmppath = NULL;
41962216Sdes
420109702Sdes	timeout = 0;
421109702Sdes	*flags = 0;
422109702Sdes	count = 0;
423109702Sdes
424109702Sdes	/* set verbosity level */
425109702Sdes	if (v_level > 1)
426109702Sdes		strcat(flags, "v");
427109702Sdes	if (v_level > 2)
428109702Sdes		fetchDebug = 1;
429109702Sdes
43079837Sdes	/* parse URL */
431201290Sru	url = NULL;
432201290Sru	if (*URL == '\0') {
433201290Sru		warnx("empty URL");
434201290Sru		goto failure;
435201290Sru	}
43679837Sdes	if ((url = fetchParseURL(URL)) == NULL) {
43779837Sdes		warnx("%s: parse error", URL);
43879837Sdes		goto failure;
43979837Sdes	}
44062216Sdes
44179837Sdes	/* if no scheme was specified, take a guess */
44279837Sdes	if (!*url->scheme) {
44379837Sdes		if (!*url->host)
44479837Sdes			strcpy(url->scheme, SCHEME_FILE);
44579837Sdes		else if (strncasecmp(url->host, "ftp.", 4) == 0)
44679837Sdes			strcpy(url->scheme, SCHEME_FTP);
44779837Sdes		else if (strncasecmp(url->host, "www.", 4) == 0)
44879837Sdes			strcpy(url->scheme, SCHEME_HTTP);
44979837Sdes	}
45069976Sdes
45179837Sdes	/* common flags */
45279837Sdes	switch (family) {
45379837Sdes	case PF_INET:
45479837Sdes		strcat(flags, "4");
45579837Sdes		break;
45679837Sdes	case PF_INET6:
45779837Sdes		strcat(flags, "6");
45879837Sdes		break;
45979837Sdes	}
46062216Sdes
46179837Sdes	/* FTP specific flags */
462181962Sobrien	if (strcmp(url->scheme, SCHEME_FTP) == 0) {
46379837Sdes		if (p_flag)
46479837Sdes			strcat(flags, "p");
46579837Sdes		if (d_flag)
46679837Sdes			strcat(flags, "d");
46779837Sdes		if (U_flag)
46879837Sdes			strcat(flags, "l");
46979837Sdes		timeout = T_secs ? T_secs : ftp_timeout;
47079837Sdes	}
47162216Sdes
47279837Sdes	/* HTTP specific flags */
473185912Sdes	if (strcmp(url->scheme, SCHEME_HTTP) == 0 ||
474185912Sdes	    strcmp(url->scheme, SCHEME_HTTPS) == 0) {
47579837Sdes		if (d_flag)
47679837Sdes			strcat(flags, "d");
47779837Sdes		if (A_flag)
47879837Sdes			strcat(flags, "A");
47979837Sdes		timeout = T_secs ? T_secs : http_timeout;
480186124Smurray		if (i_flag) {
481186124Smurray			if (stat(i_filename, &sb)) {
482186124Smurray				warn("%s: stat()", i_filename);
483186124Smurray				goto failure;
484186124Smurray			}
485186124Smurray			url->ims_time = sb.st_mtime;
486186124Smurray			strcat(flags, "i");
487186124Smurray		}
48879837Sdes	}
48962216Sdes
49079837Sdes	/* set the protocol timeout. */
49179837Sdes	fetchTimeout = timeout;
49262216Sdes
49379837Sdes	/* just print size */
49479837Sdes	if (s_flag) {
495106041Sdes		if (timeout)
496106041Sdes			alarm(timeout);
497106041Sdes		r = fetchStat(url, &us, flags);
498106041Sdes		if (timeout)
499106043Sdes			alarm(0);
500106041Sdes		if (sigalrm || sigint)
501106041Sdes			goto signal;
502106041Sdes		if (r == -1) {
503106041Sdes			warnx("%s", fetchLastErrString);
50479837Sdes			goto failure;
505106041Sdes		}
50679837Sdes		if (us.size == -1)
50779837Sdes			printf("Unknown\n");
50879837Sdes		else
509125976Sdes			printf("%jd\n", (intmax_t)us.size);
51079837Sdes		goto success;
51163345Sdes	}
51279837Sdes
51379837Sdes	/*
51479837Sdes	 * If the -r flag was specified, we have to compare the local
51579837Sdes	 * and remote files, so we should really do a fetchStat()
51679837Sdes	 * first, but I know of at least one HTTP server that only
51779837Sdes	 * sends the content size in response to GET requests, and
51879837Sdes	 * leaves it out of replies to HEAD requests.  Also, in the
51979837Sdes	 * (frequent) case that the local and remote files match but
52079837Sdes	 * the local file is truncated, we have sufficient information
52179837Sdes	 * before the compare to issue a correct request.  Therefore,
52279837Sdes	 * we always issue a GET request as if we were sure the local
52379837Sdes	 * file was a truncated copy of the remote file; we can drop
52479837Sdes	 * the connection later if we change our minds.
52579837Sdes	 */
52683217Sdes	sb.st_size = -1;
527133779Sdes	if (!o_stdout) {
528133779Sdes		r = stat(path, &sb);
529134350Sdes		if (r == 0 && r_flag && S_ISREG(sb.st_mode)) {
530133779Sdes			url->offset = sb.st_size;
531153919Sdes		} else if (r == -1 || !S_ISREG(sb.st_mode)) {
532133779Sdes			/*
533133779Sdes			 * Whatever value sb.st_size has now is either
534133779Sdes			 * wrong (if stat(2) failed) or irrelevant (if the
535133779Sdes			 * path does not refer to a regular file)
536133779Sdes			 */
537133779Sdes			sb.st_size = -1;
538133779Sdes		}
539153919Sdes		if (r == -1 && errno != ENOENT) {
540153919Sdes			warnx("%s: stat()", path);
541153919Sdes			goto failure;
542153919Sdes		}
54362216Sdes	}
54462216Sdes
54579837Sdes	/* start the transfer */
546106041Sdes	if (timeout)
547106041Sdes		alarm(timeout);
548106041Sdes	f = fetchXGet(url, &us, flags);
549106042Sdes	if (timeout)
550106042Sdes		alarm(0);
551106041Sdes	if (sigalrm || sigint)
552106041Sdes		goto signal;
553106041Sdes	if (f == NULL) {
554107353Sdes		warnx("%s: %s", URL, fetchLastErrString);
555186124Smurray		if (i_flag && strcmp(url->scheme, SCHEME_HTTP) == 0
556186124Smurray		    && fetchLastErrCode == FETCH_OK
557186124Smurray		    && strcmp(fetchLastErrString, "Not Modified") == 0) {
558186124Smurray			/* HTTP Not Modified Response, return OK. */
559186124Smurray			r = 0;
560186124Smurray			goto done;
561186124Smurray		} else
562186124Smurray			goto failure;
56379837Sdes	}
56479837Sdes	if (sigint)
56563345Sdes		goto signal;
56679837Sdes
56779837Sdes	/* check that size is as expected */
56879837Sdes	if (S_size) {
56979837Sdes		if (us.size == -1) {
570107353Sdes			warnx("%s: size unknown", URL);
57179837Sdes		} else if (us.size != S_size) {
572125976Sdes			warnx("%s: size mismatch: expected %jd, actual %jd",
573125976Sdes			    URL, (intmax_t)S_size, (intmax_t)us.size);
57479837Sdes			goto failure;
57579837Sdes		}
57679837Sdes	}
57779837Sdes
57879837Sdes	/* symlink instead of copy */
57979837Sdes	if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
58079837Sdes		if (symlink(url->doc, path) == -1) {
58179837Sdes			warn("%s: symlink()", path);
58279837Sdes			goto failure;
58379837Sdes		}
58463568Sdes		goto success;
58563345Sdes	}
58679837Sdes
587106051Sdes	if (us.size == -1 && !o_stdout && v_level > 0)
588107353Sdes		warnx("%s: size of remote file is not known", URL);
58979837Sdes	if (v_level > 1) {
59079837Sdes		if (sb.st_size != -1)
591125976Sdes			fprintf(stderr, "local size / mtime: %jd / %ld\n",
592125976Sdes			    (intmax_t)sb.st_size, (long)sb.st_mtime);
59379837Sdes		if (us.size != -1)
594125976Sdes			fprintf(stderr, "remote size / mtime: %jd / %ld\n",
595125976Sdes			    (intmax_t)us.size, (long)us.mtime);
59662216Sdes	}
59762216Sdes
59879837Sdes	/* open output file */
59979837Sdes	if (o_stdout) {
60079837Sdes		/* output to stdout */
60179837Sdes		of = stdout;
60283217Sdes	} else if (r_flag && sb.st_size != -1) {
60379837Sdes		/* resume mode, local file exists */
60479837Sdes		if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
60579837Sdes			/* no match! have to refetch */
60679837Sdes			fclose(f);
60779837Sdes			/* if precious, warn the user and give up */
60879837Sdes			if (R_flag) {
60979837Sdes				warnx("%s: local modification time "
61079837Sdes				    "does not match remote", path);
61179837Sdes				goto failure_keep;
61279837Sdes			}
613225599Sdes		} else if (url->offset > sb.st_size) {
614225599Sdes			/* gap between what we asked for and what we got */
615225599Sdes			warnx("%s: gap in resume mode", URL);
616225599Sdes			fclose(of);
617225599Sdes			of = NULL;
618225599Sdes			/* picked up again later */
619127941Sdes		} else if (us.size != -1) {
62079837Sdes			if (us.size == sb.st_size)
62179837Sdes				/* nothing to do */
62279837Sdes				goto success;
62379837Sdes			if (sb.st_size > us.size) {
62479837Sdes				/* local file too long! */
625125976Sdes				warnx("%s: local file (%jd bytes) is longer "
626125976Sdes				    "than remote file (%jd bytes)", path,
627125976Sdes				    (intmax_t)sb.st_size, (intmax_t)us.size);
62879837Sdes				goto failure;
62979837Sdes			}
63083217Sdes			/* we got it, open local file */
631225800Sdes			if ((of = fopen(path, "r+")) == NULL) {
63279837Sdes				warn("%s: fopen()", path);
63379837Sdes				goto failure;
63479837Sdes			}
63583217Sdes			/* check that it didn't move under our feet */
63683217Sdes			if (fstat(fileno(of), &nsb) == -1) {
63783217Sdes				/* can't happen! */
63883217Sdes				warn("%s: fstat()", path);
63979837Sdes				goto failure;
64079837Sdes			}
64183217Sdes			if (nsb.st_dev != sb.st_dev ||
642251262Seadler			    nsb.st_ino != sb.st_ino ||
64383217Sdes			    nsb.st_size != sb.st_size) {
644107353Sdes				warnx("%s: file has changed", URL);
64583217Sdes				fclose(of);
64683217Sdes				of = NULL;
64783217Sdes				sb = nsb;
648225599Sdes				/* picked up again later */
64983217Sdes			}
65079837Sdes		}
651225800Sdes		/* seek to where we left off */
652225805Sdes		if (of != NULL && fseeko(of, url->offset, SEEK_SET) != 0) {
653225805Sdes			warn("%s: fseeko()", path);
654225800Sdes			fclose(of);
655225800Sdes			of = NULL;
656225800Sdes			/* picked up again later */
657225800Sdes		}
65883217Sdes	} else if (m_flag && sb.st_size != -1) {
65979837Sdes		/* mirror mode, local file exists */
66079837Sdes		if (sb.st_size == us.size && sb.st_mtime == us.mtime)
66179837Sdes			goto success;
66279837Sdes	}
663106043Sdes
66483217Sdes	if (of == NULL) {
66579837Sdes		/*
66679837Sdes		 * We don't yet have an output file; either this is a
66779837Sdes		 * vanilla run with no special flags, or the local and
66879837Sdes		 * remote files didn't match.
66979837Sdes		 */
670106043Sdes
671109702Sdes		if (url->offset > 0) {
67283217Sdes			/*
67383217Sdes			 * We tried to restart a transfer, but for
67483217Sdes			 * some reason gave up - so we have to restart
67583217Sdes			 * from scratch if we want the whole file
67683217Sdes			 */
67783217Sdes			url->offset = 0;
67883217Sdes			if ((f = fetchXGet(url, &us, flags)) == NULL) {
679107353Sdes				warnx("%s: %s", URL, fetchLastErrString);
68083217Sdes				goto failure;
68183217Sdes			}
68283217Sdes			if (sigint)
68383217Sdes				goto signal;
68483217Sdes		}
68583217Sdes
68683217Sdes		/* construct a temp file name */
68783217Sdes		if (sb.st_size != -1 && S_ISREG(sb.st_mode)) {
68883217Sdes			if ((slash = strrchr(path, '/')) == NULL)
68983217Sdes				slash = path;
69083217Sdes			else
69183217Sdes				++slash;
69283217Sdes			asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s",
69383307Smike			    (int)(slash - path), path, slash);
694100834Sdes			if (tmppath != NULL) {
695244037Seadler				if (mkstemps(tmppath, strlen(slash) + 1) == -1) {
696244037Seadler					warn("%s: mkstemps()", path);
697244037Seadler					goto failure;
698244037Seadler				}
699100834Sdes				of = fopen(tmppath, "w");
700164152Sdes				chown(tmppath, sb.st_uid, sb.st_gid);
701164152Sdes				chmod(tmppath, sb.st_mode & ALLPERMS);
702100834Sdes			}
70383217Sdes		}
704100834Sdes		if (of == NULL)
70583217Sdes			of = fopen(path, "w");
70683217Sdes		if (of == NULL) {
70779837Sdes			warn("%s: open()", path);
70879837Sdes			goto failure;
70979837Sdes		}
71079837Sdes	}
71179837Sdes	count = url->offset;
71262216Sdes
71379837Sdes	/* start the counter */
71479837Sdes	stat_start(&xs, path, us.size, count);
71563046Sdes
71679837Sdes	sigalrm = siginfo = sigint = 0;
71779837Sdes
71879837Sdes	/* suck in the data */
71979837Sdes	signal(SIGINFO, sig_handler);
720106041Sdes	while (!sigint) {
721137854Scperciva		if (us.size != -1 && us.size - count < B_size &&
722137854Scperciva		    us.size - count >= 0)
72379837Sdes			size = us.size - count;
72479837Sdes		else
72579837Sdes			size = B_size;
72679837Sdes		if (siginfo) {
72779837Sdes			stat_end(&xs);
72879837Sdes			siginfo = 0;
72979837Sdes		}
730230307Sdes
731230307Sdes		if (size == 0)
732230307Sdes			break;
733230307Sdes
734230307Sdes		if ((readcnt = fread(buf, 1, size, f)) < size) {
735106041Sdes			if (ferror(f) && errno == EINTR && !sigint)
73679837Sdes				clearerr(f);
737230307Sdes			else if (readcnt == 0)
73879837Sdes				break;
73979837Sdes		}
740230307Sdes
741230307Sdes		stat_update(&xs, count += readcnt);
742230307Sdes		for (ptr = buf; readcnt > 0; ptr += wr, readcnt -= wr)
743230307Sdes			if ((wr = fwrite(ptr, 1, readcnt, of)) < readcnt) {
744106041Sdes				if (ferror(of) && errno == EINTR && !sigint)
74579837Sdes					clearerr(of);
74679837Sdes				else
74779837Sdes					break;
74879837Sdes			}
749230307Sdes		if (readcnt != 0)
75079837Sdes			break;
75177241Sdes	}
752106041Sdes	if (!sigalrm)
753106041Sdes		sigalrm = ferror(f) && errno == ETIMEDOUT;
75479837Sdes	signal(SIGINFO, SIG_DFL);
75579837Sdes
75679837Sdes	stat_end(&xs);
75762216Sdes
758106041Sdes	/*
759106041Sdes	 * If the transfer timed out or was interrupted, we still want to
760106041Sdes	 * set the mtime in case the file is not removed (-r or -R) and
761106041Sdes	 * the user later restarts the transfer.
762106041Sdes	 */
763106041Sdes signal:
76479837Sdes	/* set mtime of local file */
765106857Sdes	if (!n_flag && us.mtime && !o_stdout && of != NULL &&
766106857Sdes	    (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) {
76779837Sdes		struct timeval tv[2];
768106043Sdes
76979837Sdes		fflush(of);
77079837Sdes		tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
77179837Sdes		tv[1].tv_sec = (long)us.mtime;
77279837Sdes		tv[0].tv_usec = tv[1].tv_usec = 0;
77390729Sdes		if (utimes(tmppath ? tmppath : path, tv))
77490729Sdes			warn("%s: utimes()", tmppath ? tmppath : path);
77579837Sdes	}
77663015Sdes
77779837Sdes	/* timed out or interrupted? */
77879837Sdes	if (sigalrm)
77979837Sdes		warnx("transfer timed out");
78079837Sdes	if (sigint) {
78179837Sdes		warnx("transfer interrupted");
78279837Sdes		goto failure;
78379837Sdes	}
78463046Sdes
785106586Sfenner	/* timeout / interrupt before connection completley established? */
786106586Sfenner	if (f == NULL)
787106586Sfenner		goto failure;
788106586Sfenner
78979837Sdes	if (!sigalrm) {
79079837Sdes		/* check the status of our files */
79179837Sdes		if (ferror(f))
79279837Sdes			warn("%s", URL);
79379837Sdes		if (ferror(of))
79479837Sdes			warn("%s", path);
79579837Sdes		if (ferror(f) || ferror(of))
79679837Sdes			goto failure;
79779837Sdes	}
79879837Sdes
79979837Sdes	/* did the transfer complete normally? */
80079837Sdes	if (us.size != -1 && count < us.size) {
801125976Sdes		warnx("%s appears to be truncated: %jd/%jd bytes",
802125976Sdes		    path, (intmax_t)count, (intmax_t)us.size);
80379837Sdes		goto failure_keep;
80479837Sdes	}
80579837Sdes
80679837Sdes	/*
80779837Sdes	 * If the transfer timed out and we didn't know how much to
80879837Sdes	 * expect, assume the worst (i.e. we didn't get all of it)
80979837Sdes	 */
81079837Sdes	if (sigalrm && us.size == -1) {
81179837Sdes		warnx("%s may be truncated", path);
81279837Sdes		goto failure_keep;
81379837Sdes	}
81479837Sdes
81562216Sdes success:
81679837Sdes	r = 0;
81783217Sdes	if (tmppath != NULL && rename(tmppath, path) == -1) {
81883217Sdes		warn("%s: rename()", path);
81983217Sdes		goto failure_keep;
82083217Sdes	}
82179837Sdes	goto done;
82262216Sdes failure:
82379837Sdes	if (of && of != stdout && !R_flag && !r_flag)
82479837Sdes		if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG))
82583217Sdes			unlink(tmppath ? tmppath : path);
82683217Sdes	if (R_flag && tmppath != NULL && sb.st_size == -1)
82783217Sdes		rename(tmppath, path); /* ignore errors here */
82863046Sdes failure_keep:
82979837Sdes	r = -1;
83079837Sdes	goto done;
83162216Sdes done:
83279837Sdes	if (f)
83379837Sdes		fclose(f);
83479837Sdes	if (of && of != stdout)
83579837Sdes		fclose(of);
83679837Sdes	if (url)
83779837Sdes		fetchFreeURL(url);
83883217Sdes	if (tmppath != NULL)
83983217Sdes		free(tmppath);
840132695Sdes	return (r);
84162216Sdes}
84262216Sdes
84379837Sdesstatic void
84462216Sdesusage(void)
84562216Sdes{
846253680Sdes	fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
847253680Sdes"usage: fetch [-146AadFlMmnPpqRrsUv] [--allow-sslv2] [-B bytes]",
848253680Sdes"       [--bind-address=host] [--ca-cert=file] [--ca-path=dir] [--cert=file]",
849253680Sdes"       [--crl=file] [-i file] [--key=file] [-N file] [--no-passive]",
850253680Sdes"       [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
851253680Sdes"       [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
852253680Sdes"       [--user-agent=agent-string] [-w seconds] URL ...",
853253680Sdes"       fetch [-146AadFlMmnPpqRrsUv] [--allow-sslv2] [-B bytes]",
854253680Sdes"       [--bind-address=host] [--ca-cert=file] [--ca-path=dir] [--cert=file]",
855253680Sdes"       [--crl=file] [-i file] [--key=file] [-N file] [--no-passive]",
856253680Sdes"       [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
857253680Sdes"       [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
858253680Sdes"       [--user-agent=agent-string] [-w seconds] -h host -f file [-c dir]");
85962216Sdes}
86062216Sdes
86162216Sdes
86281863Sdes/*
86381863Sdes * Entry point
86481863Sdes */
86562216Sdesint
86662216Sdesmain(int argc, char *argv[])
86762216Sdes{
86879837Sdes	struct stat sb;
86979837Sdes	struct sigaction sa;
87079837Sdes	const char *p, *s;
871100834Sdes	char *end, *q;
87279837Sdes	int c, e, r;
87362216Sdes
874253680Sdes
875253680Sdes	while ((c = getopt_long(argc, argv,
876253680Sdes	    "146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:",
877253680Sdes	    longopts, NULL)) != -1)
87879837Sdes		switch (c) {
87979837Sdes		case '1':
88079837Sdes			once_flag = 1;
88179837Sdes			break;
88279837Sdes		case '4':
88379837Sdes			family = PF_INET;
88479837Sdes			break;
88579837Sdes		case '6':
88679837Sdes			family = PF_INET6;
88779837Sdes			break;
88879837Sdes		case 'A':
88979837Sdes			A_flag = 1;
89079837Sdes			break;
89179837Sdes		case 'a':
89279837Sdes			a_flag = 1;
89379837Sdes			break;
89479837Sdes		case 'B':
895100834Sdes			B_size = (off_t)strtol(optarg, &end, 10);
896100834Sdes			if (*optarg == '\0' || *end != '\0')
89780521Sse				errx(1, "invalid buffer size (%s)", optarg);
89879837Sdes			break;
89979837Sdes		case 'b':
90079837Sdes			warnx("warning: the -b option is deprecated");
90179837Sdes			b_flag = 1;
90279837Sdes			break;
90379837Sdes		case 'c':
90479837Sdes			c_dirname = optarg;
90579837Sdes			break;
90679837Sdes		case 'd':
90779837Sdes			d_flag = 1;
90879837Sdes			break;
90979837Sdes		case 'F':
91079837Sdes			F_flag = 1;
91179837Sdes			break;
91279837Sdes		case 'f':
91379837Sdes			f_filename = optarg;
91479837Sdes			break;
91579837Sdes		case 'H':
91693213Scharnier			warnx("the -H option is now implicit, "
91779837Sdes			    "use -U to disable");
91879837Sdes			break;
91979837Sdes		case 'h':
92079837Sdes			h_hostname = optarg;
92179837Sdes			break;
922186124Smurray		case 'i':
923186124Smurray			i_flag = 1;
924186124Smurray			i_filename = optarg;
925186124Smurray			break;
92679837Sdes		case 'l':
92779837Sdes			l_flag = 1;
92879837Sdes			break;
92979837Sdes		case 'o':
93079837Sdes			o_flag = 1;
93179837Sdes			o_filename = optarg;
93279837Sdes			break;
93379837Sdes		case 'M':
93479837Sdes		case 'm':
93579837Sdes			if (r_flag)
93679837Sdes				errx(1, "the -m and -r flags "
93779837Sdes				    "are mutually exclusive");
93879837Sdes			m_flag = 1;
93979837Sdes			break;
940109702Sdes		case 'N':
941109702Sdes			N_filename = optarg;
942109702Sdes			break;
94379837Sdes		case 'n':
94479837Sdes			n_flag = 1;
94579837Sdes			break;
94679837Sdes		case 'P':
94779837Sdes		case 'p':
94879837Sdes			p_flag = 1;
94979837Sdes			break;
95079837Sdes		case 'q':
95179837Sdes			v_level = 0;
95279837Sdes			break;
95379837Sdes		case 'R':
95479837Sdes			R_flag = 1;
95579837Sdes			break;
95679837Sdes		case 'r':
95779837Sdes			if (m_flag)
95879837Sdes				errx(1, "the -m and -r flags "
95979837Sdes				    "are mutually exclusive");
96079837Sdes			r_flag = 1;
96179837Sdes			break;
96279837Sdes		case 'S':
963100834Sdes			S_size = (off_t)strtol(optarg, &end, 10);
964100834Sdes			if (*optarg == '\0' || *end != '\0')
96580521Sse				errx(1, "invalid size (%s)", optarg);
96679837Sdes			break;
96779837Sdes		case 's':
96879837Sdes			s_flag = 1;
96979837Sdes			break;
970106043Sdes		case 'T':
971100834Sdes			T_secs = strtol(optarg, &end, 10);
972100834Sdes			if (*optarg == '\0' || *end != '\0')
97380521Sse				errx(1, "invalid timeout (%s)", optarg);
97479837Sdes			break;
97579837Sdes		case 't':
97679837Sdes			t_flag = 1;
97779837Sdes			warnx("warning: the -t option is deprecated");
97879837Sdes			break;
97979837Sdes		case 'U':
98079837Sdes			U_flag = 1;
98179837Sdes			break;
98279837Sdes		case 'v':
98379837Sdes			v_level++;
98479837Sdes			break;
98579837Sdes		case 'w':
98679837Sdes			a_flag = 1;
987100834Sdes			w_secs = strtol(optarg, &end, 10);
988100834Sdes			if (*optarg == '\0' || *end != '\0')
98980521Sse				errx(1, "invalid delay (%s)", optarg);
99079837Sdes			break;
991253680Sdes		case OPTION_BIND_ADDRESS:
992253680Sdes			setenv("FETCH_BIND_ADDRESS", optarg, 1);
993253680Sdes			break;
994253680Sdes		case OPTION_NO_FTP_PASSIVE_MODE:
995253680Sdes			setenv("FTP_PASSIVE_MODE", "no", 1);
996253680Sdes			break;
997253680Sdes		case OPTION_HTTP_REFERER:
998253680Sdes			setenv("HTTP_REFERER", optarg, 1);
999253680Sdes			break;
1000253680Sdes		case OPTION_HTTP_USER_AGENT:
1001253680Sdes			setenv("HTTP_USER_AGENT", optarg, 1);
1002253680Sdes			break;
1003253680Sdes		case OPTION_NO_PROXY:
1004253680Sdes			setenv("NO_PROXY", optarg, 1);
1005253680Sdes			break;
1006253680Sdes		case OPTION_SSL_ALLOW_SSL2:
1007253680Sdes			setenv("SSL_ALLOW_SSL2", "", 1);
1008253680Sdes			break;
1009253680Sdes		case OPTION_SSL_CA_CERT_FILE:
1010253680Sdes			setenv("SSL_CA_CERT_FILE", optarg, 1);
1011253680Sdes			break;
1012253680Sdes		case OPTION_SSL_CA_CERT_PATH:
1013253680Sdes			setenv("SSL_CA_CERT_PATH", optarg, 1);
1014253680Sdes			break;
1015253680Sdes		case OPTION_SSL_CLIENT_CERT_FILE:
1016253680Sdes			setenv("SSL_CLIENT_CERT_FILE", optarg, 1);
1017253680Sdes			break;
1018253680Sdes		case OPTION_SSL_CLIENT_KEY_FILE:
1019253680Sdes			setenv("SSL_CLIENT_KEY_FILE", optarg, 1);
1020253680Sdes			break;
1021253680Sdes		case OPTION_SSL_CRL_FILE:
1022253680Sdes			setenv("SSL_CLIENT_CRL_FILE", optarg, 1);
1023253680Sdes			break;
1024253680Sdes		case OPTION_SSL_NO_SSL3:
1025253680Sdes			setenv("SSL_NO_SSL3", "", 1);
1026253680Sdes			break;
1027253680Sdes		case OPTION_SSL_NO_TLS1:
1028253680Sdes			setenv("SSL_NO_TLS1", "", 1);
1029253680Sdes			break;
1030253680Sdes		case OPTION_SSL_NO_VERIFY_HOSTNAME:
1031253680Sdes			setenv("SSL_NO_VERIFY_HOSTNAME", "", 1);
1032253680Sdes			break;
1033253680Sdes		case OPTION_SSL_NO_VERIFY_PEER:
1034253680Sdes			setenv("SSL_NO_VERIFY_PEER", "", 1);
1035253680Sdes			break;
103679837Sdes		default:
103779837Sdes			usage();
1038186241Smurray			exit(1);
103979837Sdes		}
104062216Sdes
104179837Sdes	argc -= optind;
104279837Sdes	argv += optind;
104362216Sdes
104479837Sdes	if (h_hostname || f_filename || c_dirname) {
104579837Sdes		if (!h_hostname || !f_filename || argc) {
104679837Sdes			usage();
1047186241Smurray			exit(1);
104879837Sdes		}
104979837Sdes		/* XXX this is a hack. */
105079837Sdes		if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
105179837Sdes			errx(1, "invalid hostname");
105279837Sdes		if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
105379837Sdes		    c_dirname ? c_dirname : "", f_filename) == -1)
105479837Sdes			errx(1, "%s", strerror(ENOMEM));
105579837Sdes		argc++;
105662216Sdes	}
105763345Sdes
105879837Sdes	if (!argc) {
105979837Sdes		usage();
1060186241Smurray		exit(1);
106179837Sdes	}
106262216Sdes
106379837Sdes	/* allocate buffer */
106479837Sdes	if (B_size < MINBUFSIZE)
106579837Sdes		B_size = MINBUFSIZE;
106679837Sdes	if ((buf = malloc(B_size)) == NULL)
106779837Sdes		errx(1, "%s", strerror(ENOMEM));
106862216Sdes
106979837Sdes	/* timeouts */
107079837Sdes	if ((s = getenv("FTP_TIMEOUT")) != NULL) {
1071100834Sdes		ftp_timeout = strtol(s, &end, 10);
1072102478Sdes		if (*s == '\0' || *end != '\0' || ftp_timeout < 0) {
1073102478Sdes			warnx("FTP_TIMEOUT (%s) is not a positive integer", s);
107479837Sdes			ftp_timeout = 0;
107579837Sdes		}
107662216Sdes	}
107779837Sdes	if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
1078100834Sdes		http_timeout = strtol(s, &end, 10);
1079102478Sdes		if (*s == '\0' || *end != '\0' || http_timeout < 0) {
1080102478Sdes			warnx("HTTP_TIMEOUT (%s) is not a positive integer", s);
108179837Sdes			http_timeout = 0;
108279837Sdes		}
108362216Sdes	}
108462216Sdes
108579837Sdes	/* signal handling */
108679837Sdes	sa.sa_flags = 0;
108779837Sdes	sa.sa_handler = sig_handler;
108879837Sdes	sigemptyset(&sa.sa_mask);
108979837Sdes	sigaction(SIGALRM, &sa, NULL);
109079837Sdes	sa.sa_flags = SA_RESETHAND;
109179837Sdes	sigaction(SIGINT, &sa, NULL);
109279837Sdes	fetchRestartCalls = 0;
109379837Sdes
109479837Sdes	/* output file */
109579837Sdes	if (o_flag) {
109679837Sdes		if (strcmp(o_filename, "-") == 0) {
109779837Sdes			o_stdout = 1;
109879837Sdes		} else if (stat(o_filename, &sb) == -1) {
109979837Sdes			if (errno == ENOENT) {
110079837Sdes				if (argc > 1)
1101186241Smurray					errx(1, "%s is not a directory",
110279837Sdes					    o_filename);
110379837Sdes			} else {
1104186241Smurray				err(1, "%s", o_filename);
110579837Sdes			}
110679837Sdes		} else {
110779837Sdes			if (sb.st_mode & S_IFDIR)
110879837Sdes				o_directory = 1;
110979837Sdes		}
111062216Sdes	}
111162216Sdes
111279837Sdes	/* check if output is to a tty (for progress report) */
111379837Sdes	v_tty = isatty(STDERR_FILENO);
111483863Sdes	if (v_tty)
111583863Sdes		pgrp = getpgrp();
1116106043Sdes
111779837Sdes	r = 0;
1118106043Sdes
111979837Sdes	/* authentication */
112079837Sdes	if (v_tty)
112179837Sdes		fetchAuthMethod = query_auth;
1122109702Sdes	if (N_filename != NULL)
1123244037Seadler		if (setenv("NETRC", N_filename, 1) == -1)
1124244037Seadler			err(1, "setenv: cannot set NETRC=%s", N_filename);
112562216Sdes
112679837Sdes	while (argc) {
112779837Sdes		if ((p = strrchr(*argv, '/')) == NULL)
112879837Sdes			p = *argv;
112979837Sdes		else
113079837Sdes			p++;
113162216Sdes
113279837Sdes		if (!*p)
113379837Sdes			p = "fetch.out";
1134106043Sdes
113579837Sdes		fetchLastErrCode = 0;
1136106043Sdes
113779837Sdes		if (o_flag) {
113879837Sdes			if (o_stdout) {
113979837Sdes				e = fetch(*argv, "-");
114079837Sdes			} else if (o_directory) {
114179837Sdes				asprintf(&q, "%s/%s", o_filename, p);
114279837Sdes				e = fetch(*argv, q);
114379837Sdes				free(q);
114479837Sdes			} else {
114579837Sdes				e = fetch(*argv, o_filename);
114679837Sdes			}
114779837Sdes		} else {
114879837Sdes			e = fetch(*argv, p);
114979837Sdes		}
115062216Sdes
115179837Sdes		if (sigint)
115279837Sdes			kill(getpid(), SIGINT);
1153106043Sdes
115479837Sdes		if (e == 0 && once_flag)
115579837Sdes			exit(0);
1156106043Sdes
115779837Sdes		if (e) {
115879837Sdes			r = 1;
115979837Sdes			if ((fetchLastErrCode
116079837Sdes			    && fetchLastErrCode != FETCH_UNAVAIL
116179837Sdes			    && fetchLastErrCode != FETCH_MOVED
116279837Sdes			    && fetchLastErrCode != FETCH_URL
116379837Sdes			    && fetchLastErrCode != FETCH_RESOLV
116479837Sdes			    && fetchLastErrCode != FETCH_UNKNOWN)) {
116579837Sdes				if (w_secs && v_level)
1166100834Sdes					fprintf(stderr, "Waiting %ld seconds "
116779837Sdes					    "before retrying\n", w_secs);
116879837Sdes				if (w_secs)
116979837Sdes					sleep(w_secs);
117079837Sdes				if (a_flag)
117179837Sdes					continue;
117279837Sdes			}
117362216Sdes		}
117479837Sdes
117579837Sdes		argc--, argv++;
117662216Sdes	}
117762216Sdes
117879837Sdes	exit(r);
117962216Sdes}
1180