162216Sdes/*-
2330449Seadler * SPDX-License-Identifier: BSD-3-Clause
3330449Seadler *
4261233Sdes * Copyright (c) 2000-2014 Dag-Erling Sm��rgrav
5253680Sdes * Copyright (c) 2013 Michael Gmelin <freebsd@grem.de>
662216Sdes * All rights reserved.
762216Sdes *
862216Sdes * Redistribution and use in source and binary forms, with or without
962216Sdes * modification, are permitted provided that the following conditions
1062216Sdes * are met:
1162216Sdes * 1. Redistributions of source code must retain the above copyright
1262216Sdes *    notice, this list of conditions and the following disclaimer
1362216Sdes *    in this position and unchanged.
1462216Sdes * 2. Redistributions in binary form must reproduce the above copyright
1562216Sdes *    notice, this list of conditions and the following disclaimer in the
1662216Sdes *    documentation and/or other materials provided with the distribution.
1762216Sdes * 3. The name of the author may not be used to endorse or promote products
1862216Sdes *    derived from this software without specific prior written permission
1962216Sdes *
2062216Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2162216Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2262216Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2362216Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2462216Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2562216Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2662216Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2762216Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2862216Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2962216Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3062216Sdes */
3162216Sdes
3293213Scharnier#include <sys/cdefs.h>
3393213Scharnier__FBSDID("$FreeBSD: stable/11/usr.bin/fetch/fetch.c 339250 2018-10-09 10:49:19Z des $");
3493213Scharnier
3562216Sdes#include <sys/param.h>
3691225Sbde#include <sys/socket.h>
3762216Sdes#include <sys/stat.h>
3893257Sbde#include <sys/time.h>
3962216Sdes
40200462Sdelphij#include <ctype.h>
4162216Sdes#include <err.h>
4262216Sdes#include <errno.h>
43253680Sdes#include <getopt.h>
4463235Sdes#include <signal.h>
45125976Sdes#include <stdint.h>
4662216Sdes#include <stdio.h>
4762216Sdes#include <stdlib.h>
4862216Sdes#include <string.h>
4977241Sdes#include <termios.h>
5062216Sdes#include <unistd.h>
5162216Sdes
5262216Sdes#include <fetch.h>
5362216Sdes
54261234Sdes#define MINBUFSIZE	16384
55187361Sdes#define TIMEOUT		120
5662216Sdes
5762216Sdes/* Option flags */
58241737Sedstatic int	 A_flag;	/*    -A: do not follow 302 redirects */
59241737Sedstatic int	 a_flag;	/*    -a: auto retry */
60241737Sedstatic off_t	 B_size;	/*    -B: buffer size */
61241737Sedstatic int	 b_flag;	/*!   -b: workaround TCP bug */
62241737Sedstatic char    *c_dirname;	/*    -c: remote directory */
63241737Sedstatic int	 d_flag;	/*    -d: direct connection */
64241737Sedstatic int	 F_flag;	/*    -F: restart without checking mtime  */
65241737Sedstatic char	*f_filename;	/*    -f: file to fetch */
66241737Sedstatic char	*h_hostname;	/*    -h: host to fetch from */
67241737Sedstatic int	 i_flag;	/*    -i: specify file for mtime comparison */
68241737Sedstatic char	*i_filename;	/*        name of input file */
69241737Sedstatic int	 l_flag;	/*    -l: link rather than copy file: URLs */
70241737Sedstatic int	 m_flag;	/* -[Mm]: mirror mode */
71241737Sedstatic char	*N_filename;	/*    -N: netrc file name */
72241737Sedstatic int	 n_flag;	/*    -n: do not preserve modification time */
73241737Sedstatic int	 o_flag;	/*    -o: specify output file */
74241737Sedstatic int	 o_directory;	/*        output file is a directory */
75241737Sedstatic char	*o_filename;	/*        name of output file */
76241737Sedstatic int	 o_stdout;	/*        output file is stdout */
77241737Sedstatic int	 once_flag;	/*    -1: stop at first successful file */
78241737Sedstatic int	 p_flag;	/* -[Pp]: use passive FTP */
79241737Sedstatic int	 R_flag;	/*    -R: don't delete partial files */
80241737Sedstatic int	 r_flag;	/*    -r: restart previous transfer */
81241737Sedstatic off_t	 S_size;        /*    -S: require size to match */
82241737Sedstatic int	 s_flag;        /*    -s: show size, don't fetch */
83241737Sedstatic long	 T_secs;	/*    -T: transfer timeout in seconds */
84241737Sedstatic int	 t_flag;	/*!   -t: workaround TCP bug */
85241737Sedstatic int	 U_flag;	/*    -U: do not use high ports */
86241737Sedstatic int	 v_level = 1;	/*    -v: verbosity level */
87241737Sedstatic int	 v_tty;		/*        stdout is a tty */
88339250Sdesstatic int	 v_progress;	/*        whether to display progress */
89241737Sedstatic pid_t	 pgrp;		/*        our process group */
90241737Sedstatic long	 w_secs;	/*    -w: retry delay */
91241737Sedstatic int	 family = PF_UNSPEC;	/* -[46]: address family to use */
9262216Sdes
93241737Sedstatic int	 sigalrm;	/* SIGALRM received */
94241737Sedstatic int	 siginfo;	/* SIGINFO received */
95241737Sedstatic int	 sigint;	/* SIGINT received */
9662216Sdes
97241737Sedstatic long	 ftp_timeout = TIMEOUT;	/* default timeout for FTP transfers */
98241737Sedstatic long	 http_timeout = TIMEOUT;/* default timeout for HTTP transfers */
99241737Sedstatic char	*buf;		/* transfer buffer */
10062216Sdes
101253680Sdesenum options
102253680Sdes{
103253680Sdes	OPTION_BIND_ADDRESS,
104253680Sdes	OPTION_NO_FTP_PASSIVE_MODE,
105253680Sdes	OPTION_HTTP_REFERER,
106253680Sdes	OPTION_HTTP_USER_AGENT,
107253680Sdes	OPTION_NO_PROXY,
108253680Sdes	OPTION_SSL_CA_CERT_FILE,
109253680Sdes	OPTION_SSL_CA_CERT_PATH,
110253680Sdes	OPTION_SSL_CLIENT_CERT_FILE,
111253680Sdes	OPTION_SSL_CLIENT_KEY_FILE,
112253680Sdes	OPTION_SSL_CRL_FILE,
113253680Sdes	OPTION_SSL_NO_SSL3,
114261233Sdes	OPTION_SSL_NO_TLS1,
115253680Sdes	OPTION_SSL_NO_VERIFY_HOSTNAME,
116253680Sdes	OPTION_SSL_NO_VERIFY_PEER
117253680Sdes};
11862216Sdes
119253680Sdes
120253680Sdesstatic struct option longopts[] =
121253680Sdes{
122253680Sdes	/* mapping to single character argument */
123253680Sdes	{ "one-file", no_argument, NULL, '1' },
124253680Sdes	{ "ipv4-only", no_argument, NULL, '4' },
125253680Sdes	{ "ipv6-only", no_argument, NULL, '6' },
126253680Sdes	{ "no-redirect", no_argument, NULL, 'A' },
127253680Sdes	{ "retry", no_argument, NULL, 'a' },
128253680Sdes	{ "buffer-size", required_argument, NULL, 'B' },
129253680Sdes	/* -c not mapped, since it's deprecated */
130253680Sdes	{ "direct", no_argument, NULL, 'd' },
131253680Sdes	{ "force-restart", no_argument, NULL, 'F' },
132253680Sdes	/* -f not mapped, since it's deprecated */
133253680Sdes	/* -h not mapped, since it's deprecated */
134253680Sdes	{ "if-modified-since", required_argument, NULL, 'i' },
135253680Sdes	{ "symlink", no_argument, NULL, 'l' },
136253680Sdes	/* -M not mapped since it's the same as -m */
137253680Sdes	{ "mirror", no_argument, NULL, 'm' },
138253680Sdes	{ "netrc", required_argument, NULL, 'N' },
139253680Sdes	{ "no-mtime", no_argument, NULL, 'n' },
140253680Sdes	{ "output", required_argument, NULL, 'o' },
141253680Sdes	/* -P not mapped since it's the same as -p */
142253680Sdes	{ "passive", no_argument, NULL, 'p' },
143253680Sdes	{ "quiet", no_argument, NULL, 'q' },
144253680Sdes	{ "keep-output", no_argument, NULL, 'R' },
145253680Sdes	{ "restart", no_argument, NULL, 'r' },
146253680Sdes	{ "require-size", required_argument, NULL, 'S' },
147253680Sdes	{ "print-size", no_argument, NULL, 's' },
148253680Sdes	{ "timeout", required_argument, NULL, 'T' },
149253680Sdes	{ "passive-portrange-default", no_argument, NULL, 'T' },
150253680Sdes	{ "verbose", no_argument, NULL, 'v' },
151253680Sdes	{ "retry-delay", required_argument, NULL, 'w' },
152261233Sdes
153253680Sdes	/* options without a single character equivalent */
154253680Sdes	{ "bind-address", required_argument, NULL, OPTION_BIND_ADDRESS },
155253680Sdes	{ "no-passive", no_argument, NULL, OPTION_NO_FTP_PASSIVE_MODE },
156253680Sdes	{ "referer", required_argument, NULL, OPTION_HTTP_REFERER },
157253680Sdes	{ "user-agent", required_argument, NULL, OPTION_HTTP_USER_AGENT },
158253680Sdes	{ "no-proxy", required_argument, NULL, OPTION_NO_PROXY },
159253680Sdes	{ "ca-cert", required_argument, NULL, OPTION_SSL_CA_CERT_FILE },
160253680Sdes	{ "ca-path", required_argument, NULL, OPTION_SSL_CA_CERT_PATH },
161253680Sdes	{ "cert", required_argument, NULL, OPTION_SSL_CLIENT_CERT_FILE },
162253680Sdes	{ "key", required_argument, NULL, OPTION_SSL_CLIENT_KEY_FILE },
163253680Sdes	{ "crl", required_argument, NULL, OPTION_SSL_CRL_FILE },
164253680Sdes	{ "no-sslv3", no_argument, NULL, OPTION_SSL_NO_SSL3 },
165253680Sdes	{ "no-tlsv1", no_argument, NULL, OPTION_SSL_NO_TLS1 },
166253680Sdes	{ "no-verify-hostname", no_argument, NULL, OPTION_SSL_NO_VERIFY_HOSTNAME },
167253680Sdes	{ "no-verify-peer", no_argument, NULL, OPTION_SSL_NO_VERIFY_PEER },
168253680Sdes
169253680Sdes	{ NULL, 0, NULL, 0 }
170253680Sdes};
171253680Sdes
17281863Sdes/*
17381863Sdes * Signal handler
17481863Sdes */
17579837Sdesstatic void
17662216Sdessig_handler(int sig)
17762216Sdes{
17879837Sdes	switch (sig) {
17979837Sdes	case SIGALRM:
18079837Sdes		sigalrm = 1;
18179837Sdes		break;
18279837Sdes	case SIGINFO:
18379837Sdes		siginfo = 1;
18479837Sdes		break;
18579837Sdes	case SIGINT:
18679837Sdes		sigint = 1;
18779837Sdes		break;
18879837Sdes	}
18962216Sdes}
19062216Sdes
19162216Sdesstruct xferstat {
192125965Sdes	char		 name[64];
193243147Sandre	struct timeval	 start;		/* start of transfer */
194243147Sandre	struct timeval	 last;		/* time of last update */
195243147Sandre	struct timeval	 last2;		/* time of previous last update */
196243147Sandre	off_t		 size;		/* size of file per HTTP hdr */
197243147Sandre	off_t		 offset;	/* starting offset in file */
198243147Sandre	off_t		 rcvd;		/* bytes already received */
199243147Sandre	off_t		 lastrcvd;	/* bytes received since last update */
20062216Sdes};
20162216Sdes
20281863Sdes/*
203339250Sdes * Format a number of seconds as either XXdYYh, XXhYYm, XXmYYs, or XXs
204339250Sdes * depending on its magnitude
205339250Sdes */
206339250Sdesstatic void
207339250Sdesstat_seconds(char *str, size_t strsz, long seconds)
208339250Sdes{
209339250Sdes
210339250Sdes	if (seconds > 86400)
211339250Sdes		snprintf(str, strsz, "%02ldd%02ldh",
212339250Sdes		    seconds / 86400, (seconds % 86400) / 3600);
213339250Sdes	else if (seconds > 3600)
214339250Sdes		snprintf(str, strsz, "%02ldh%02ldm",
215339250Sdes		    seconds / 3600, (seconds % 3600) / 60);
216339250Sdes	else if (seconds > 60)
217339250Sdes		snprintf(str, strsz, "%02ldm%02lds",
218339250Sdes		    seconds / 60, seconds % 60);
219339250Sdes	else
220339250Sdes		snprintf(str, strsz, "   %02lds",
221339250Sdes		    seconds);
222339250Sdes}
223339250Sdes
224339250Sdes/*
225109702Sdes * Compute and display ETA
226109702Sdes */
227339250Sdesstatic void
228339250Sdesstat_eta(char *str, size_t strsz, const struct xferstat *xs)
229109702Sdes{
230125976Sdes	long elapsed, eta;
231125976Sdes	off_t received, expected;
232109702Sdes
233109702Sdes	elapsed = xs->last.tv_sec - xs->start.tv_sec;
234112083Sdes	received = xs->rcvd - xs->offset;
235112083Sdes	expected = xs->size - xs->rcvd;
236112114Sdes	eta = (long)((double)elapsed * expected / received);
237339250Sdes	if (eta > 0)
238339250Sdes		stat_seconds(str, strsz, eta);
239125965Sdes	else
240339250Sdes		stat_seconds(str, strsz, elapsed);
241125965Sdes}
242125965Sdes
243125965Sdes/*
244125965Sdes * Format a number as "xxxx YB" where Y is ' ', 'k', 'M'...
245125965Sdes */
246125965Sdesstatic const char *prefixes = " kMGTP";
247339250Sdesstatic void
248339250Sdesstat_bytes(char *str, size_t strsz, off_t bytes)
249125965Sdes{
250125965Sdes	const char *prefix = prefixes;
251125965Sdes
252125965Sdes	while (bytes > 9999 && prefix[1] != '\0') {
253125965Sdes		bytes /= 1024;
254125965Sdes		prefix++;
255109702Sdes	}
256339250Sdes	snprintf(str, strsz, "%4ju %cB", (uintmax_t)bytes, *prefix);
257109702Sdes}
258109702Sdes
259109702Sdes/*
260109702Sdes * Compute and display transfer rate
261109702Sdes */
262339250Sdesstatic void
263339250Sdesstat_bps(char *str, size_t strsz, struct xferstat *xs)
264109702Sdes{
265339250Sdes	char bytes[16];
266109735Sdes	double delta, bps;
267109702Sdes
268339250Sdes	delta = ((double)xs->last.tv_sec + (xs->last.tv_usec / 1.e6))
269339250Sdes	    - ((double)xs->last2.tv_sec + (xs->last2.tv_usec / 1.e6));
270243147Sandre
271109735Sdes	if (delta == 0.0) {
272339250Sdes		snprintf(str, strsz, "?? Bps");
273125965Sdes	} else {
274244058Sandre		bps = (xs->rcvd - xs->lastrcvd) / delta;
275339250Sdes		stat_bytes(bytes, sizeof bytes, (off_t)bps);
276339250Sdes		snprintf(str, strsz, "%sps", bytes);
277109735Sdes	}
278109702Sdes}
279109702Sdes
280109702Sdes/*
28181863Sdes * Update the stats display
28281863Sdes */
28379837Sdesstatic void
28463046Sdesstat_display(struct xferstat *xs, int force)
28562216Sdes{
286339250Sdes	char bytes[16], bps[16], eta[16];
28779837Sdes	struct timeval now;
28883863Sdes	int ctty_pgrp;
28979837Sdes
29083863Sdes	/* check if we're the foreground process */
291339250Sdes	if (ioctl(STDERR_FILENO, TIOCGPGRP, &ctty_pgrp) != 0 ||
29283863Sdes	    (pid_t)ctty_pgrp != pgrp)
29383863Sdes		return;
294106043Sdes
29579837Sdes	gettimeofday(&now, NULL);
29679837Sdes	if (!force && now.tv_sec <= xs->last.tv_sec)
29779837Sdes		return;
298243147Sandre	xs->last2 = xs->last;
29979837Sdes	xs->last = now;
30079837Sdes
301131615Sdes	fprintf(stderr, "\r%-46.46s", xs->name);
302339250Sdes	if (xs->rcvd >= xs->size) {
303339250Sdes		stat_bytes(bytes, sizeof bytes, xs->rcvd);
304339250Sdes		setproctitle("%s [%s]", xs->name, bytes);
305339250Sdes		fprintf(stderr, "        %s", bytes);
306106041Sdes	} else {
307339250Sdes		stat_bytes(bytes, sizeof bytes, xs->size);
308153894Sdes		setproctitle("%s [%d%% of %s]", xs->name,
309153894Sdes		    (int)((100.0 * xs->rcvd) / xs->size),
310339250Sdes		    bytes);
311125965Sdes		fprintf(stderr, "%3d%% of %s",
312125965Sdes		    (int)((100.0 * xs->rcvd) / xs->size),
313339250Sdes		    bytes);
314106041Sdes	}
315243147Sandre	if (force == 2) {
316243147Sandre		xs->lastrcvd = xs->offset;
317243147Sandre		xs->last2 = xs->start;
318243147Sandre	}
319339250Sdes	stat_bps(bps, sizeof bps, xs);
320339250Sdes	fprintf(stderr, " %s", bps);
321243147Sandre	if ((xs->size > 0 && xs->rcvd > 0 &&
322243147Sandre	     xs->last.tv_sec >= xs->start.tv_sec + 3) ||
323339250Sdes	    force == 2) {
324339250Sdes		stat_eta(eta, sizeof eta, xs);
325339250Sdes		fprintf(stderr, " %s", eta);
326339250Sdes	}
327243147Sandre	xs->lastrcvd = xs->rcvd;
32862216Sdes}
32962216Sdes
33081863Sdes/*
33181863Sdes * Initialize the transfer statistics
33281863Sdes */
33379837Sdesstatic void
33479837Sdesstat_start(struct xferstat *xs, const char *name, off_t size, off_t offset)
33563046Sdes{
336339250Sdes
337339250Sdes	memset(xs, 0, sizeof *xs);
33879837Sdes	snprintf(xs->name, sizeof xs->name, "%s", name);
33979837Sdes	gettimeofday(&xs->start, NULL);
340339250Sdes	xs->last2 = xs->last = xs->start;
34179837Sdes	xs->size = size;
34279837Sdes	xs->offset = offset;
34379837Sdes	xs->rcvd = offset;
344243147Sandre	xs->lastrcvd = offset;
345339250Sdes	if (v_progress)
346125965Sdes		stat_display(xs, 1);
347125965Sdes	else if (v_level > 0)
348125965Sdes		fprintf(stderr, "%-46s", xs->name);
34963046Sdes}
35063046Sdes
35181863Sdes/*
35281863Sdes * Update the transfer statistics
35381863Sdes */
35479837Sdesstatic void
35579837Sdesstat_update(struct xferstat *xs, off_t rcvd)
35663046Sdes{
357339250Sdes
35879837Sdes	xs->rcvd = rcvd;
359339250Sdes	if (v_progress)
360125965Sdes		stat_display(xs, 0);
36163046Sdes}
36263046Sdes
36381863Sdes/*
36481863Sdes * Finalize the transfer statistics
36581863Sdes */
36679837Sdesstatic void
36762216Sdesstat_end(struct xferstat *xs)
36862216Sdes{
369339250Sdes	char bytes[16], bps[16], eta[16];
370339250Sdes
371109735Sdes	gettimeofday(&xs->last, NULL);
372339250Sdes	if (v_progress) {
373243147Sandre		stat_display(xs, 2);
374125965Sdes		putc('\n', stderr);
375125965Sdes	} else if (v_level > 0) {
376339250Sdes		stat_bytes(bytes, sizeof bytes, xs->rcvd);
377339250Sdes		stat_bps(bps, sizeof bps, xs);
378339250Sdes		stat_eta(eta, sizeof eta, xs);
379339250Sdes		fprintf(stderr, "        %s %s %s\n", bytes, bps, eta);
380125965Sdes	}
38162216Sdes}
38262216Sdes
38381863Sdes/*
38481863Sdes * Ask the user for authentication details
38581863Sdes */
38679837Sdesstatic int
38777241Sdesquery_auth(struct url *URL)
38877241Sdes{
38979837Sdes	struct termios tios;
39079837Sdes	tcflag_t saved_flags;
39179837Sdes	int i, nopwd;
39277241Sdes
39379837Sdes	fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n",
39486242Siedowse	    URL->scheme, URL->host, URL->port);
39579837Sdes
39679837Sdes	fprintf(stderr, "Login: ");
39779837Sdes	if (fgets(URL->user, sizeof URL->user, stdin) == NULL)
398132695Sdes		return (-1);
399132696Sdes	for (i = strlen(URL->user); i >= 0; --i)
400132696Sdes		if (URL->user[i] == '\r' || URL->user[i] == '\n')
40179837Sdes			URL->user[i] = '\0';
40279837Sdes
40379837Sdes	fprintf(stderr, "Password: ");
40479837Sdes	if (tcgetattr(STDIN_FILENO, &tios) == 0) {
40579837Sdes		saved_flags = tios.c_lflag;
40679837Sdes		tios.c_lflag &= ~ECHO;
40779837Sdes		tios.c_lflag |= ECHONL|ICANON;
40879837Sdes		tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios);
40979837Sdes		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
41079837Sdes		tios.c_lflag = saved_flags;
41179837Sdes		tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios);
41279837Sdes	} else {
41379837Sdes		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
41479837Sdes	}
41579837Sdes	if (nopwd)
416132695Sdes		return (-1);
417132696Sdes	for (i = strlen(URL->pwd); i >= 0; --i)
418132696Sdes		if (URL->pwd[i] == '\r' || URL->pwd[i] == '\n')
419132696Sdes			URL->pwd[i] = '\0';
42079837Sdes
421132695Sdes	return (0);
42277241Sdes}
42377241Sdes
42481863Sdes/*
42581863Sdes * Fetch a file
42681863Sdes */
42779837Sdesstatic int
42879837Sdesfetch(char *URL, const char *path)
42962216Sdes{
43079837Sdes	struct url *url;
43179837Sdes	struct url_stat us;
43283217Sdes	struct stat sb, nsb;
43379837Sdes	struct xferstat xs;
43479837Sdes	FILE *f, *of;
435230307Sdes	size_t size, readcnt, wr;
43679837Sdes	off_t count;
43779837Sdes	char flags[8];
43883217Sdes	const char *slash;
43983217Sdes	char *tmppath;
44079837Sdes	int r;
441125976Sdes	unsigned timeout;
442125976Sdes	char *ptr;
44362216Sdes
44479837Sdes	f = of = NULL;
44583217Sdes	tmppath = NULL;
44662216Sdes
447109702Sdes	timeout = 0;
448109702Sdes	*flags = 0;
449109702Sdes	count = 0;
450109702Sdes
451109702Sdes	/* set verbosity level */
452109702Sdes	if (v_level > 1)
453109702Sdes		strcat(flags, "v");
454109702Sdes	if (v_level > 2)
455109702Sdes		fetchDebug = 1;
456109702Sdes
45779837Sdes	/* parse URL */
458201290Sru	url = NULL;
459201290Sru	if (*URL == '\0') {
460201290Sru		warnx("empty URL");
461201290Sru		goto failure;
462201290Sru	}
46379837Sdes	if ((url = fetchParseURL(URL)) == NULL) {
46479837Sdes		warnx("%s: parse error", URL);
46579837Sdes		goto failure;
46679837Sdes	}
46762216Sdes
46879837Sdes	/* if no scheme was specified, take a guess */
46979837Sdes	if (!*url->scheme) {
47079837Sdes		if (!*url->host)
47179837Sdes			strcpy(url->scheme, SCHEME_FILE);
47279837Sdes		else if (strncasecmp(url->host, "ftp.", 4) == 0)
47379837Sdes			strcpy(url->scheme, SCHEME_FTP);
47479837Sdes		else if (strncasecmp(url->host, "www.", 4) == 0)
47579837Sdes			strcpy(url->scheme, SCHEME_HTTP);
47679837Sdes	}
47769976Sdes
47879837Sdes	/* common flags */
47979837Sdes	switch (family) {
48079837Sdes	case PF_INET:
48179837Sdes		strcat(flags, "4");
48279837Sdes		break;
48379837Sdes	case PF_INET6:
48479837Sdes		strcat(flags, "6");
48579837Sdes		break;
48679837Sdes	}
48762216Sdes
48879837Sdes	/* FTP specific flags */
489181962Sobrien	if (strcmp(url->scheme, SCHEME_FTP) == 0) {
49079837Sdes		if (p_flag)
49179837Sdes			strcat(flags, "p");
49279837Sdes		if (d_flag)
49379837Sdes			strcat(flags, "d");
49479837Sdes		if (U_flag)
49579837Sdes			strcat(flags, "l");
49679837Sdes		timeout = T_secs ? T_secs : ftp_timeout;
49779837Sdes	}
49862216Sdes
49979837Sdes	/* HTTP specific flags */
500185912Sdes	if (strcmp(url->scheme, SCHEME_HTTP) == 0 ||
501185912Sdes	    strcmp(url->scheme, SCHEME_HTTPS) == 0) {
50279837Sdes		if (d_flag)
50379837Sdes			strcat(flags, "d");
50479837Sdes		if (A_flag)
50579837Sdes			strcat(flags, "A");
50679837Sdes		timeout = T_secs ? T_secs : http_timeout;
507186124Smurray		if (i_flag) {
508186124Smurray			if (stat(i_filename, &sb)) {
509186124Smurray				warn("%s: stat()", i_filename);
510186124Smurray				goto failure;
511186124Smurray			}
512186124Smurray			url->ims_time = sb.st_mtime;
513186124Smurray			strcat(flags, "i");
514186124Smurray		}
51579837Sdes	}
51662216Sdes
51779837Sdes	/* set the protocol timeout. */
51879837Sdes	fetchTimeout = timeout;
51962216Sdes
52079837Sdes	/* just print size */
52179837Sdes	if (s_flag) {
522106041Sdes		if (timeout)
523106041Sdes			alarm(timeout);
524106041Sdes		r = fetchStat(url, &us, flags);
525106041Sdes		if (timeout)
526106043Sdes			alarm(0);
527106041Sdes		if (sigalrm || sigint)
528106041Sdes			goto signal;
529106041Sdes		if (r == -1) {
530106041Sdes			warnx("%s", fetchLastErrString);
53179837Sdes			goto failure;
532106041Sdes		}
53379837Sdes		if (us.size == -1)
53479837Sdes			printf("Unknown\n");
53579837Sdes		else
536125976Sdes			printf("%jd\n", (intmax_t)us.size);
53779837Sdes		goto success;
53863345Sdes	}
53979837Sdes
54079837Sdes	/*
54179837Sdes	 * If the -r flag was specified, we have to compare the local
54279837Sdes	 * and remote files, so we should really do a fetchStat()
54379837Sdes	 * first, but I know of at least one HTTP server that only
54479837Sdes	 * sends the content size in response to GET requests, and
54579837Sdes	 * leaves it out of replies to HEAD requests.  Also, in the
54679837Sdes	 * (frequent) case that the local and remote files match but
54779837Sdes	 * the local file is truncated, we have sufficient information
54879837Sdes	 * before the compare to issue a correct request.  Therefore,
54979837Sdes	 * we always issue a GET request as if we were sure the local
55079837Sdes	 * file was a truncated copy of the remote file; we can drop
55179837Sdes	 * the connection later if we change our minds.
55279837Sdes	 */
55383217Sdes	sb.st_size = -1;
554133779Sdes	if (!o_stdout) {
555133779Sdes		r = stat(path, &sb);
556134350Sdes		if (r == 0 && r_flag && S_ISREG(sb.st_mode)) {
557133779Sdes			url->offset = sb.st_size;
558153919Sdes		} else if (r == -1 || !S_ISREG(sb.st_mode)) {
559133779Sdes			/*
560133779Sdes			 * Whatever value sb.st_size has now is either
561133779Sdes			 * wrong (if stat(2) failed) or irrelevant (if the
562133779Sdes			 * path does not refer to a regular file)
563133779Sdes			 */
564133779Sdes			sb.st_size = -1;
565133779Sdes		}
566153919Sdes		if (r == -1 && errno != ENOENT) {
567153919Sdes			warnx("%s: stat()", path);
568153919Sdes			goto failure;
569153919Sdes		}
57062216Sdes	}
57162216Sdes
57279837Sdes	/* start the transfer */
573106041Sdes	if (timeout)
574106041Sdes		alarm(timeout);
575106041Sdes	f = fetchXGet(url, &us, flags);
576106042Sdes	if (timeout)
577106042Sdes		alarm(0);
578106041Sdes	if (sigalrm || sigint)
579106041Sdes		goto signal;
580106041Sdes	if (f == NULL) {
581107353Sdes		warnx("%s: %s", URL, fetchLastErrString);
582339250Sdes		if (i_flag && (strcmp(url->scheme, SCHEME_HTTP) == 0 ||
583339250Sdes		    strcmp(url->scheme, SCHEME_HTTPS) == 0) &&
584339250Sdes		    fetchLastErrCode == FETCH_OK &&
585339250Sdes		    strcmp(fetchLastErrString, "Not Modified") == 0) {
586186124Smurray			/* HTTP Not Modified Response, return OK. */
587186124Smurray			r = 0;
588186124Smurray			goto done;
589186124Smurray		} else
590186124Smurray			goto failure;
59179837Sdes	}
59279837Sdes	if (sigint)
59363345Sdes		goto signal;
59479837Sdes
59579837Sdes	/* check that size is as expected */
59679837Sdes	if (S_size) {
59779837Sdes		if (us.size == -1) {
598107353Sdes			warnx("%s: size unknown", URL);
59979837Sdes		} else if (us.size != S_size) {
600125976Sdes			warnx("%s: size mismatch: expected %jd, actual %jd",
601125976Sdes			    URL, (intmax_t)S_size, (intmax_t)us.size);
60279837Sdes			goto failure;
60379837Sdes		}
60479837Sdes	}
60579837Sdes
60679837Sdes	/* symlink instead of copy */
60779837Sdes	if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
60879837Sdes		if (symlink(url->doc, path) == -1) {
60979837Sdes			warn("%s: symlink()", path);
61079837Sdes			goto failure;
61179837Sdes		}
61263568Sdes		goto success;
61363345Sdes	}
61479837Sdes
615106051Sdes	if (us.size == -1 && !o_stdout && v_level > 0)
616107353Sdes		warnx("%s: size of remote file is not known", URL);
61779837Sdes	if (v_level > 1) {
61879837Sdes		if (sb.st_size != -1)
619125976Sdes			fprintf(stderr, "local size / mtime: %jd / %ld\n",
620125976Sdes			    (intmax_t)sb.st_size, (long)sb.st_mtime);
62179837Sdes		if (us.size != -1)
622125976Sdes			fprintf(stderr, "remote size / mtime: %jd / %ld\n",
623125976Sdes			    (intmax_t)us.size, (long)us.mtime);
62462216Sdes	}
62562216Sdes
62679837Sdes	/* open output file */
62779837Sdes	if (o_stdout) {
62879837Sdes		/* output to stdout */
62979837Sdes		of = stdout;
63083217Sdes	} else if (r_flag && sb.st_size != -1) {
63179837Sdes		/* resume mode, local file exists */
63279837Sdes		if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
63379837Sdes			/* no match! have to refetch */
63479837Sdes			fclose(f);
63579837Sdes			/* if precious, warn the user and give up */
63679837Sdes			if (R_flag) {
63779837Sdes				warnx("%s: local modification time "
63879837Sdes				    "does not match remote", path);
63979837Sdes				goto failure_keep;
64079837Sdes			}
641225599Sdes		} else if (url->offset > sb.st_size) {
642225599Sdes			/* gap between what we asked for and what we got */
643225599Sdes			warnx("%s: gap in resume mode", URL);
644225599Sdes			fclose(of);
645225599Sdes			of = NULL;
646225599Sdes			/* picked up again later */
647127941Sdes		} else if (us.size != -1) {
64879837Sdes			if (us.size == sb.st_size)
64979837Sdes				/* nothing to do */
65079837Sdes				goto success;
65179837Sdes			if (sb.st_size > us.size) {
65279837Sdes				/* local file too long! */
653125976Sdes				warnx("%s: local file (%jd bytes) is longer "
654125976Sdes				    "than remote file (%jd bytes)", path,
655125976Sdes				    (intmax_t)sb.st_size, (intmax_t)us.size);
65679837Sdes				goto failure;
65779837Sdes			}
65883217Sdes			/* we got it, open local file */
659225800Sdes			if ((of = fopen(path, "r+")) == NULL) {
66079837Sdes				warn("%s: fopen()", path);
66179837Sdes				goto failure;
66279837Sdes			}
66383217Sdes			/* check that it didn't move under our feet */
66483217Sdes			if (fstat(fileno(of), &nsb) == -1) {
66583217Sdes				/* can't happen! */
66683217Sdes				warn("%s: fstat()", path);
66779837Sdes				goto failure;
66879837Sdes			}
66983217Sdes			if (nsb.st_dev != sb.st_dev ||
670251262Seadler			    nsb.st_ino != sb.st_ino ||
67183217Sdes			    nsb.st_size != sb.st_size) {
672107353Sdes				warnx("%s: file has changed", URL);
67383217Sdes				fclose(of);
67483217Sdes				of = NULL;
67583217Sdes				sb = nsb;
676225599Sdes				/* picked up again later */
67783217Sdes			}
67879837Sdes		}
679225800Sdes		/* seek to where we left off */
680225805Sdes		if (of != NULL && fseeko(of, url->offset, SEEK_SET) != 0) {
681225805Sdes			warn("%s: fseeko()", path);
682225800Sdes			fclose(of);
683225800Sdes			of = NULL;
684225800Sdes			/* picked up again later */
685225800Sdes		}
68683217Sdes	} else if (m_flag && sb.st_size != -1) {
68779837Sdes		/* mirror mode, local file exists */
68879837Sdes		if (sb.st_size == us.size && sb.st_mtime == us.mtime)
68979837Sdes			goto success;
69079837Sdes	}
691106043Sdes
69283217Sdes	if (of == NULL) {
69379837Sdes		/*
69479837Sdes		 * We don't yet have an output file; either this is a
69579837Sdes		 * vanilla run with no special flags, or the local and
69679837Sdes		 * remote files didn't match.
69779837Sdes		 */
698106043Sdes
699109702Sdes		if (url->offset > 0) {
70083217Sdes			/*
70183217Sdes			 * We tried to restart a transfer, but for
70283217Sdes			 * some reason gave up - so we have to restart
70383217Sdes			 * from scratch if we want the whole file
70483217Sdes			 */
70583217Sdes			url->offset = 0;
70683217Sdes			if ((f = fetchXGet(url, &us, flags)) == NULL) {
707107353Sdes				warnx("%s: %s", URL, fetchLastErrString);
70883217Sdes				goto failure;
70983217Sdes			}
71083217Sdes			if (sigint)
71183217Sdes				goto signal;
71283217Sdes		}
71383217Sdes
71483217Sdes		/* construct a temp file name */
71583217Sdes		if (sb.st_size != -1 && S_ISREG(sb.st_mode)) {
71683217Sdes			if ((slash = strrchr(path, '/')) == NULL)
71783217Sdes				slash = path;
71883217Sdes			else
71983217Sdes				++slash;
72083217Sdes			asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s",
72183307Smike			    (int)(slash - path), path, slash);
722100834Sdes			if (tmppath != NULL) {
723244037Seadler				if (mkstemps(tmppath, strlen(slash) + 1) == -1) {
724244037Seadler					warn("%s: mkstemps()", path);
725244037Seadler					goto failure;
726244037Seadler				}
727100834Sdes				of = fopen(tmppath, "w");
728164152Sdes				chown(tmppath, sb.st_uid, sb.st_gid);
729164152Sdes				chmod(tmppath, sb.st_mode & ALLPERMS);
730100834Sdes			}
73183217Sdes		}
732100834Sdes		if (of == NULL)
73383217Sdes			of = fopen(path, "w");
73483217Sdes		if (of == NULL) {
73579837Sdes			warn("%s: open()", path);
73679837Sdes			goto failure;
73779837Sdes		}
73879837Sdes	}
73979837Sdes	count = url->offset;
74062216Sdes
74179837Sdes	/* start the counter */
74279837Sdes	stat_start(&xs, path, us.size, count);
74363046Sdes
74479837Sdes	sigalrm = siginfo = sigint = 0;
74579837Sdes
74679837Sdes	/* suck in the data */
747261234Sdes	setvbuf(f, NULL, _IOFBF, B_size);
74879837Sdes	signal(SIGINFO, sig_handler);
749106041Sdes	while (!sigint) {
750137854Scperciva		if (us.size != -1 && us.size - count < B_size &&
751137854Scperciva		    us.size - count >= 0)
75279837Sdes			size = us.size - count;
75379837Sdes		else
75479837Sdes			size = B_size;
75579837Sdes		if (siginfo) {
75679837Sdes			stat_end(&xs);
75779837Sdes			siginfo = 0;
75879837Sdes		}
759230307Sdes
760230307Sdes		if (size == 0)
761230307Sdes			break;
762230307Sdes
763230307Sdes		if ((readcnt = fread(buf, 1, size, f)) < size) {
764106041Sdes			if (ferror(f) && errno == EINTR && !sigint)
76579837Sdes				clearerr(f);
766230307Sdes			else if (readcnt == 0)
76779837Sdes				break;
76879837Sdes		}
769230307Sdes
770230307Sdes		stat_update(&xs, count += readcnt);
771230307Sdes		for (ptr = buf; readcnt > 0; ptr += wr, readcnt -= wr)
772230307Sdes			if ((wr = fwrite(ptr, 1, readcnt, of)) < readcnt) {
773106041Sdes				if (ferror(of) && errno == EINTR && !sigint)
77479837Sdes					clearerr(of);
77579837Sdes				else
77679837Sdes					break;
77779837Sdes			}
778230307Sdes		if (readcnt != 0)
77979837Sdes			break;
78077241Sdes	}
781106041Sdes	if (!sigalrm)
782106041Sdes		sigalrm = ferror(f) && errno == ETIMEDOUT;
78379837Sdes	signal(SIGINFO, SIG_DFL);
78479837Sdes
78579837Sdes	stat_end(&xs);
78662216Sdes
787106041Sdes	/*
788106041Sdes	 * If the transfer timed out or was interrupted, we still want to
789106041Sdes	 * set the mtime in case the file is not removed (-r or -R) and
790106041Sdes	 * the user later restarts the transfer.
791106041Sdes	 */
792106041Sdes signal:
79379837Sdes	/* set mtime of local file */
794106857Sdes	if (!n_flag && us.mtime && !o_stdout && of != NULL &&
795106857Sdes	    (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) {
79679837Sdes		struct timeval tv[2];
797106043Sdes
79879837Sdes		fflush(of);
79979837Sdes		tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
80079837Sdes		tv[1].tv_sec = (long)us.mtime;
80179837Sdes		tv[0].tv_usec = tv[1].tv_usec = 0;
80290729Sdes		if (utimes(tmppath ? tmppath : path, tv))
80390729Sdes			warn("%s: utimes()", tmppath ? tmppath : path);
80479837Sdes	}
80563015Sdes
80679837Sdes	/* timed out or interrupted? */
80779837Sdes	if (sigalrm)
80879837Sdes		warnx("transfer timed out");
80979837Sdes	if (sigint) {
81079837Sdes		warnx("transfer interrupted");
81179837Sdes		goto failure;
81279837Sdes	}
81363046Sdes
814106586Sfenner	/* timeout / interrupt before connection completley established? */
815106586Sfenner	if (f == NULL)
816106586Sfenner		goto failure;
817106586Sfenner
81879837Sdes	if (!sigalrm) {
81979837Sdes		/* check the status of our files */
82079837Sdes		if (ferror(f))
82179837Sdes			warn("%s", URL);
82279837Sdes		if (ferror(of))
82379837Sdes			warn("%s", path);
82479837Sdes		if (ferror(f) || ferror(of))
82579837Sdes			goto failure;
82679837Sdes	}
82779837Sdes
82879837Sdes	/* did the transfer complete normally? */
82979837Sdes	if (us.size != -1 && count < us.size) {
830125976Sdes		warnx("%s appears to be truncated: %jd/%jd bytes",
831125976Sdes		    path, (intmax_t)count, (intmax_t)us.size);
83279837Sdes		goto failure_keep;
83379837Sdes	}
83479837Sdes
83579837Sdes	/*
83679837Sdes	 * If the transfer timed out and we didn't know how much to
83779837Sdes	 * expect, assume the worst (i.e. we didn't get all of it)
83879837Sdes	 */
83979837Sdes	if (sigalrm && us.size == -1) {
84079837Sdes		warnx("%s may be truncated", path);
84179837Sdes		goto failure_keep;
84279837Sdes	}
84379837Sdes
84462216Sdes success:
84579837Sdes	r = 0;
84683217Sdes	if (tmppath != NULL && rename(tmppath, path) == -1) {
84783217Sdes		warn("%s: rename()", path);
84883217Sdes		goto failure_keep;
84983217Sdes	}
85079837Sdes	goto done;
85162216Sdes failure:
85279837Sdes	if (of && of != stdout && !R_flag && !r_flag)
85379837Sdes		if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG))
85483217Sdes			unlink(tmppath ? tmppath : path);
85583217Sdes	if (R_flag && tmppath != NULL && sb.st_size == -1)
85683217Sdes		rename(tmppath, path); /* ignore errors here */
85763046Sdes failure_keep:
85879837Sdes	r = -1;
85979837Sdes	goto done;
86062216Sdes done:
86179837Sdes	if (f)
86279837Sdes		fclose(f);
86379837Sdes	if (of && of != stdout)
86479837Sdes		fclose(of);
86579837Sdes	if (url)
86679837Sdes		fetchFreeURL(url);
86783217Sdes	if (tmppath != NULL)
86883217Sdes		free(tmppath);
869132695Sdes	return (r);
87062216Sdes}
87162216Sdes
87279837Sdesstatic void
87362216Sdesusage(void)
87462216Sdes{
875253680Sdes	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",
876280630Sjkim"usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
877280630Sjkim"       [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
878280630Sjkim"       [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]",
879280630Sjkim"       [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]",
880280630Sjkim"       [-o file] [--referer=URL] [-S bytes] [-T seconds]",
881253680Sdes"       [--user-agent=agent-string] [-w seconds] URL ...",
882280630Sjkim"       fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
883280630Sjkim"       [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
884280630Sjkim"       [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]",
885280630Sjkim"       [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]",
886280630Sjkim"       [-o file] [--referer=URL] [-S bytes] [-T seconds]",
887253680Sdes"       [--user-agent=agent-string] [-w seconds] -h host -f file [-c dir]");
88862216Sdes}
88962216Sdes
89062216Sdes
89181863Sdes/*
89281863Sdes * Entry point
89381863Sdes */
89462216Sdesint
89562216Sdesmain(int argc, char *argv[])
89662216Sdes{
89779837Sdes	struct stat sb;
89879837Sdes	struct sigaction sa;
89979837Sdes	const char *p, *s;
900100834Sdes	char *end, *q;
90179837Sdes	int c, e, r;
90262216Sdes
903253680Sdes
904253680Sdes	while ((c = getopt_long(argc, argv,
905253680Sdes	    "146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:",
906253680Sdes	    longopts, NULL)) != -1)
90779837Sdes		switch (c) {
90879837Sdes		case '1':
90979837Sdes			once_flag = 1;
91079837Sdes			break;
91179837Sdes		case '4':
91279837Sdes			family = PF_INET;
91379837Sdes			break;
91479837Sdes		case '6':
91579837Sdes			family = PF_INET6;
91679837Sdes			break;
91779837Sdes		case 'A':
91879837Sdes			A_flag = 1;
91979837Sdes			break;
92079837Sdes		case 'a':
92179837Sdes			a_flag = 1;
92279837Sdes			break;
92379837Sdes		case 'B':
924100834Sdes			B_size = (off_t)strtol(optarg, &end, 10);
925100834Sdes			if (*optarg == '\0' || *end != '\0')
92680521Sse				errx(1, "invalid buffer size (%s)", optarg);
92779837Sdes			break;
92879837Sdes		case 'b':
92979837Sdes			warnx("warning: the -b option is deprecated");
93079837Sdes			b_flag = 1;
93179837Sdes			break;
93279837Sdes		case 'c':
93379837Sdes			c_dirname = optarg;
93479837Sdes			break;
93579837Sdes		case 'd':
93679837Sdes			d_flag = 1;
93779837Sdes			break;
93879837Sdes		case 'F':
93979837Sdes			F_flag = 1;
94079837Sdes			break;
94179837Sdes		case 'f':
94279837Sdes			f_filename = optarg;
94379837Sdes			break;
94479837Sdes		case 'H':
94593213Scharnier			warnx("the -H option is now implicit, "
94679837Sdes			    "use -U to disable");
94779837Sdes			break;
94879837Sdes		case 'h':
94979837Sdes			h_hostname = optarg;
95079837Sdes			break;
951186124Smurray		case 'i':
952186124Smurray			i_flag = 1;
953186124Smurray			i_filename = optarg;
954186124Smurray			break;
95579837Sdes		case 'l':
95679837Sdes			l_flag = 1;
95779837Sdes			break;
95879837Sdes		case 'o':
95979837Sdes			o_flag = 1;
96079837Sdes			o_filename = optarg;
96179837Sdes			break;
96279837Sdes		case 'M':
96379837Sdes		case 'm':
96479837Sdes			if (r_flag)
96579837Sdes				errx(1, "the -m and -r flags "
96679837Sdes				    "are mutually exclusive");
96779837Sdes			m_flag = 1;
96879837Sdes			break;
969109702Sdes		case 'N':
970109702Sdes			N_filename = optarg;
971109702Sdes			break;
97279837Sdes		case 'n':
97379837Sdes			n_flag = 1;
97479837Sdes			break;
97579837Sdes		case 'P':
97679837Sdes		case 'p':
97779837Sdes			p_flag = 1;
97879837Sdes			break;
97979837Sdes		case 'q':
98079837Sdes			v_level = 0;
98179837Sdes			break;
98279837Sdes		case 'R':
98379837Sdes			R_flag = 1;
98479837Sdes			break;
98579837Sdes		case 'r':
98679837Sdes			if (m_flag)
98779837Sdes				errx(1, "the -m and -r flags "
98879837Sdes				    "are mutually exclusive");
98979837Sdes			r_flag = 1;
99079837Sdes			break;
99179837Sdes		case 'S':
992100834Sdes			S_size = (off_t)strtol(optarg, &end, 10);
993100834Sdes			if (*optarg == '\0' || *end != '\0')
99480521Sse				errx(1, "invalid size (%s)", optarg);
99579837Sdes			break;
99679837Sdes		case 's':
99779837Sdes			s_flag = 1;
99879837Sdes			break;
999106043Sdes		case 'T':
1000100834Sdes			T_secs = strtol(optarg, &end, 10);
1001100834Sdes			if (*optarg == '\0' || *end != '\0')
100280521Sse				errx(1, "invalid timeout (%s)", optarg);
100379837Sdes			break;
100479837Sdes		case 't':
100579837Sdes			t_flag = 1;
100679837Sdes			warnx("warning: the -t option is deprecated");
100779837Sdes			break;
100879837Sdes		case 'U':
100979837Sdes			U_flag = 1;
101079837Sdes			break;
101179837Sdes		case 'v':
101279837Sdes			v_level++;
101379837Sdes			break;
101479837Sdes		case 'w':
101579837Sdes			a_flag = 1;
1016100834Sdes			w_secs = strtol(optarg, &end, 10);
1017100834Sdes			if (*optarg == '\0' || *end != '\0')
101880521Sse				errx(1, "invalid delay (%s)", optarg);
101979837Sdes			break;
1020253680Sdes		case OPTION_BIND_ADDRESS:
1021253680Sdes			setenv("FETCH_BIND_ADDRESS", optarg, 1);
1022253680Sdes			break;
1023253680Sdes		case OPTION_NO_FTP_PASSIVE_MODE:
1024253680Sdes			setenv("FTP_PASSIVE_MODE", "no", 1);
1025253680Sdes			break;
1026253680Sdes		case OPTION_HTTP_REFERER:
1027253680Sdes			setenv("HTTP_REFERER", optarg, 1);
1028253680Sdes			break;
1029253680Sdes		case OPTION_HTTP_USER_AGENT:
1030253680Sdes			setenv("HTTP_USER_AGENT", optarg, 1);
1031253680Sdes			break;
1032253680Sdes		case OPTION_NO_PROXY:
1033253680Sdes			setenv("NO_PROXY", optarg, 1);
1034253680Sdes			break;
1035253680Sdes		case OPTION_SSL_CA_CERT_FILE:
1036253680Sdes			setenv("SSL_CA_CERT_FILE", optarg, 1);
1037253680Sdes			break;
1038253680Sdes		case OPTION_SSL_CA_CERT_PATH:
1039253680Sdes			setenv("SSL_CA_CERT_PATH", optarg, 1);
1040253680Sdes			break;
1041253680Sdes		case OPTION_SSL_CLIENT_CERT_FILE:
1042253680Sdes			setenv("SSL_CLIENT_CERT_FILE", optarg, 1);
1043253680Sdes			break;
1044253680Sdes		case OPTION_SSL_CLIENT_KEY_FILE:
1045253680Sdes			setenv("SSL_CLIENT_KEY_FILE", optarg, 1);
1046253680Sdes			break;
1047253680Sdes		case OPTION_SSL_CRL_FILE:
1048253680Sdes			setenv("SSL_CLIENT_CRL_FILE", optarg, 1);
1049253680Sdes			break;
1050253680Sdes		case OPTION_SSL_NO_SSL3:
1051253680Sdes			setenv("SSL_NO_SSL3", "", 1);
1052253680Sdes			break;
1053253680Sdes		case OPTION_SSL_NO_TLS1:
1054253680Sdes			setenv("SSL_NO_TLS1", "", 1);
1055253680Sdes			break;
1056253680Sdes		case OPTION_SSL_NO_VERIFY_HOSTNAME:
1057253680Sdes			setenv("SSL_NO_VERIFY_HOSTNAME", "", 1);
1058253680Sdes			break;
1059253680Sdes		case OPTION_SSL_NO_VERIFY_PEER:
1060253680Sdes			setenv("SSL_NO_VERIFY_PEER", "", 1);
1061253680Sdes			break;
106279837Sdes		default:
106379837Sdes			usage();
1064186241Smurray			exit(1);
106579837Sdes		}
106662216Sdes
106779837Sdes	argc -= optind;
106879837Sdes	argv += optind;
106962216Sdes
107079837Sdes	if (h_hostname || f_filename || c_dirname) {
107179837Sdes		if (!h_hostname || !f_filename || argc) {
107279837Sdes			usage();
1073186241Smurray			exit(1);
107479837Sdes		}
107579837Sdes		/* XXX this is a hack. */
107679837Sdes		if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
107779837Sdes			errx(1, "invalid hostname");
107879837Sdes		if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
107979837Sdes		    c_dirname ? c_dirname : "", f_filename) == -1)
108079837Sdes			errx(1, "%s", strerror(ENOMEM));
108179837Sdes		argc++;
108262216Sdes	}
108363345Sdes
108479837Sdes	if (!argc) {
108579837Sdes		usage();
1086186241Smurray		exit(1);
108779837Sdes	}
108862216Sdes
108979837Sdes	/* allocate buffer */
109079837Sdes	if (B_size < MINBUFSIZE)
109179837Sdes		B_size = MINBUFSIZE;
109279837Sdes	if ((buf = malloc(B_size)) == NULL)
109379837Sdes		errx(1, "%s", strerror(ENOMEM));
109462216Sdes
109579837Sdes	/* timeouts */
109679837Sdes	if ((s = getenv("FTP_TIMEOUT")) != NULL) {
1097100834Sdes		ftp_timeout = strtol(s, &end, 10);
1098102478Sdes		if (*s == '\0' || *end != '\0' || ftp_timeout < 0) {
1099102478Sdes			warnx("FTP_TIMEOUT (%s) is not a positive integer", s);
110079837Sdes			ftp_timeout = 0;
110179837Sdes		}
110262216Sdes	}
110379837Sdes	if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
1104100834Sdes		http_timeout = strtol(s, &end, 10);
1105102478Sdes		if (*s == '\0' || *end != '\0' || http_timeout < 0) {
1106102478Sdes			warnx("HTTP_TIMEOUT (%s) is not a positive integer", s);
110779837Sdes			http_timeout = 0;
110879837Sdes		}
110962216Sdes	}
111062216Sdes
111179837Sdes	/* signal handling */
111279837Sdes	sa.sa_flags = 0;
111379837Sdes	sa.sa_handler = sig_handler;
111479837Sdes	sigemptyset(&sa.sa_mask);
111579837Sdes	sigaction(SIGALRM, &sa, NULL);
111679837Sdes	sa.sa_flags = SA_RESETHAND;
111779837Sdes	sigaction(SIGINT, &sa, NULL);
111879837Sdes	fetchRestartCalls = 0;
111979837Sdes
112079837Sdes	/* output file */
112179837Sdes	if (o_flag) {
112279837Sdes		if (strcmp(o_filename, "-") == 0) {
112379837Sdes			o_stdout = 1;
112479837Sdes		} else if (stat(o_filename, &sb) == -1) {
112579837Sdes			if (errno == ENOENT) {
112679837Sdes				if (argc > 1)
1127186241Smurray					errx(1, "%s is not a directory",
112879837Sdes					    o_filename);
112979837Sdes			} else {
1130186241Smurray				err(1, "%s", o_filename);
113179837Sdes			}
113279837Sdes		} else {
113379837Sdes			if (sb.st_mode & S_IFDIR)
113479837Sdes				o_directory = 1;
113579837Sdes		}
113662216Sdes	}
113762216Sdes
113879837Sdes	/* check if output is to a tty (for progress report) */
113979837Sdes	v_tty = isatty(STDERR_FILENO);
1140339250Sdes	v_progress = v_tty && v_level > 0;
1141339250Sdes	if (v_progress)
114283863Sdes		pgrp = getpgrp();
1143106043Sdes
114479837Sdes	r = 0;
1145106043Sdes
114679837Sdes	/* authentication */
114779837Sdes	if (v_tty)
114879837Sdes		fetchAuthMethod = query_auth;
1149109702Sdes	if (N_filename != NULL)
1150244037Seadler		if (setenv("NETRC", N_filename, 1) == -1)
1151244037Seadler			err(1, "setenv: cannot set NETRC=%s", N_filename);
115262216Sdes
115379837Sdes	while (argc) {
115479837Sdes		if ((p = strrchr(*argv, '/')) == NULL)
115579837Sdes			p = *argv;
115679837Sdes		else
115779837Sdes			p++;
115862216Sdes
115979837Sdes		if (!*p)
116079837Sdes			p = "fetch.out";
1161106043Sdes
116279837Sdes		fetchLastErrCode = 0;
1163106043Sdes
116479837Sdes		if (o_flag) {
116579837Sdes			if (o_stdout) {
116679837Sdes				e = fetch(*argv, "-");
116779837Sdes			} else if (o_directory) {
116879837Sdes				asprintf(&q, "%s/%s", o_filename, p);
116979837Sdes				e = fetch(*argv, q);
117079837Sdes				free(q);
117179837Sdes			} else {
117279837Sdes				e = fetch(*argv, o_filename);
117379837Sdes			}
117479837Sdes		} else {
117579837Sdes			e = fetch(*argv, p);
117679837Sdes		}
117762216Sdes
117879837Sdes		if (sigint)
117979837Sdes			kill(getpid(), SIGINT);
1180106043Sdes
118179837Sdes		if (e == 0 && once_flag)
118279837Sdes			exit(0);
1183106043Sdes
118479837Sdes		if (e) {
118579837Sdes			r = 1;
118679837Sdes			if ((fetchLastErrCode
118779837Sdes			    && fetchLastErrCode != FETCH_UNAVAIL
118879837Sdes			    && fetchLastErrCode != FETCH_MOVED
118979837Sdes			    && fetchLastErrCode != FETCH_URL
119079837Sdes			    && fetchLastErrCode != FETCH_RESOLV
119179837Sdes			    && fetchLastErrCode != FETCH_UNKNOWN)) {
119279837Sdes				if (w_secs && v_level)
1193100834Sdes					fprintf(stderr, "Waiting %ld seconds "
119479837Sdes					    "before retrying\n", w_secs);
119579837Sdes				if (w_secs)
119679837Sdes					sleep(w_secs);
119779837Sdes				if (a_flag)
119879837Sdes					continue;
119979837Sdes			}
120062216Sdes		}
120179837Sdes
120279837Sdes		argc--, argv++;
120362216Sdes	}
120462216Sdes
120579837Sdes	exit(r);
120662216Sdes}
1207