xref: /freebsd-14.2/usr.bin/fetch/fetch.c (revision fffcbbcd)
1 /*-
2  * Copyright (c) 2000 Dag-Erling Co�dan Sm�rgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  *	$FreeBSD$
29  */
30 
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <sys/socket.h>
34 
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sysexits.h>
43 #include <termios.h>
44 #include <unistd.h>
45 
46 #include <fetch.h>
47 
48 #define MINBUFSIZE	4096
49 
50 /* Option flags */
51 int	 A_flag;	/*    -A: do not follow 302 redirects */
52 int	 a_flag;	/*    -a: auto retry */
53 off_t	 B_size;	/*    -B: buffer size */
54 int	 b_flag;	/*!   -b: workaround TCP bug */
55 char    *c_dirname;	/*    -c: remote directory */
56 int	 d_flag;	/*    -d: direct connection */
57 int	 F_flag;	/*    -F: restart without checking mtime  */
58 char	*f_filename;	/*    -f: file to fetch */
59 char	*h_hostname;	/*    -h: host to fetch from */
60 int	 l_flag;	/*    -l: link rather than copy file: URLs */
61 int	 m_flag;	/* -[Mm]: mirror mode */
62 int	 n_flag;	/*    -n: do not preserve modification time */
63 int	 o_flag;	/*    -o: specify output file */
64 int	 o_directory;	/*        output file is a directory */
65 char	*o_filename;	/*        name of output file */
66 int	 o_stdout;	/*        output file is stdout */
67 int	 once_flag;	/*    -1: stop at first successful file */
68 int	 p_flag;	/* -[Pp]: use passive FTP */
69 int	 R_flag;	/*    -R: don't delete partially transferred files */
70 int	 r_flag;	/*    -r: restart previously interrupted transfer */
71 off_t	 S_size;        /*    -S: require size to match */
72 int	 s_flag;        /*    -s: show size, don't fetch */
73 u_int	 T_secs = 0;	/*    -T: transfer timeout in seconds */
74 int	 t_flag;	/*!   -t: workaround TCP bug */
75 int	 U_flag;	/*    -U: do not use high ports */
76 int	 v_level = 1;	/*    -v: verbosity level */
77 int	 v_tty;		/*        stdout is a tty */
78 u_int	 w_secs;	/*    -w: retry delay */
79 int	 family = PF_UNSPEC;	/* -[46]: address family to use */
80 
81 int	 sigalrm;	/* SIGALRM received */
82 int	 siginfo;	/* SIGINFO received */
83 int	 sigint;	/* SIGINT received */
84 
85 u_int	 ftp_timeout;	/* default timeout for FTP transfers */
86 u_int	 http_timeout;	/* default timeout for HTTP transfers */
87 u_char	*buf;		/* transfer buffer */
88 
89 
90 static void
91 sig_handler(int sig)
92 {
93 	switch (sig) {
94 	case SIGALRM:
95 		sigalrm = 1;
96 		break;
97 	case SIGINFO:
98 		siginfo = 1;
99 		break;
100 	case SIGINT:
101 		sigint = 1;
102 		break;
103 	}
104 }
105 
106 struct xferstat {
107 	char		 name[40];
108 	struct timeval	 start;
109 	struct timeval	 end;
110 	struct timeval	 last;
111 	off_t		 size;
112 	off_t		 offset;
113 	off_t		 rcvd;
114 };
115 
116 static void
117 stat_display(struct xferstat *xs, int force)
118 {
119 	struct timeval now;
120 
121 	if (!v_tty || !v_level)
122 		return;
123 
124 	gettimeofday(&now, NULL);
125 	if (!force && now.tv_sec <= xs->last.tv_sec)
126 		return;
127 	xs->last = now;
128 
129 	fprintf(stderr, "\rReceiving %s", xs->name);
130 	if (xs->size <= 0)
131 		fprintf(stderr, ": %lld bytes", (long long)xs->rcvd);
132 	else
133 		fprintf(stderr, " (%lld bytes): %d%%", (long long)xs->size,
134 		    (int)((100.0 * xs->rcvd) / xs->size));
135 }
136 
137 static void
138 stat_start(struct xferstat *xs, const char *name, off_t size, off_t offset)
139 {
140 	snprintf(xs->name, sizeof xs->name, "%s", name);
141 	gettimeofday(&xs->start, NULL);
142 	xs->last.tv_sec = xs->last.tv_usec = 0;
143 	xs->end = xs->last;
144 	xs->size = size;
145 	xs->offset = offset;
146 	xs->rcvd = offset;
147 	stat_display(xs, 1);
148 }
149 
150 static void
151 stat_update(struct xferstat *xs, off_t rcvd)
152 {
153 	xs->rcvd = rcvd;
154 	stat_display(xs, 0);
155 }
156 
157 static void
158 stat_end(struct xferstat *xs)
159 {
160 	double delta;
161 	double bps;
162 
163 	if (!v_level)
164 		return;
165 
166 	gettimeofday(&xs->end, NULL);
167 
168 	stat_display(xs, 1);
169 	fputc('\n', stderr);
170 	delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6))
171 	    - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6));
172 	fprintf(stderr, "%lld bytes transferred in %.1f seconds ",
173 	    (long long)(xs->rcvd - xs->offset), delta);
174 	bps = (xs->rcvd - xs->offset) / delta;
175 	if (bps > 1024*1024)
176 		fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024));
177 	else if (bps > 1024)
178 		fprintf(stderr, "(%.2f kBps)\n", bps / 1024);
179 	else
180 		fprintf(stderr, "(%.2f Bps)\n", bps);
181 }
182 
183 static int
184 query_auth(struct url *URL)
185 {
186 	struct termios tios;
187 	tcflag_t saved_flags;
188 	int i, nopwd;
189 
190 
191 	fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n",
192 	    URL->scheme, URL->host, URL->port, URL->doc);
193 
194 	fprintf(stderr, "Login: ");
195 	if (fgets(URL->user, sizeof URL->user, stdin) == NULL)
196 		return -1;
197 	for (i = 0; URL->user[i]; ++i)
198 		if (isspace(URL->user[i]))
199 			URL->user[i] = '\0';
200 
201 	fprintf(stderr, "Password: ");
202 	if (tcgetattr(STDIN_FILENO, &tios) == 0) {
203 		saved_flags = tios.c_lflag;
204 		tios.c_lflag &= ~ECHO;
205 		tios.c_lflag |= ECHONL|ICANON;
206 		tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios);
207 		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
208 		tios.c_lflag = saved_flags;
209 		tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios);
210 	} else {
211 		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
212 	}
213 	if (nopwd)
214 		return -1;
215 
216 	for (i = 0; URL->pwd[i]; ++i)
217 		if (isspace(URL->pwd[i]))
218 			URL->pwd[i] = '\0';
219 	return 0;
220 }
221 
222 static int
223 fetch(char *URL, const char *path)
224 {
225 	struct url *url;
226 	struct url_stat us;
227 	struct stat sb;
228 	struct xferstat xs;
229 	FILE *f, *of;
230 	size_t size, wr;
231 	off_t count;
232 	char flags[8];
233 	int r;
234 	u_int timeout;
235 	u_char *ptr;
236 
237 	f = of = NULL;
238 
239 	/* parse URL */
240 	if ((url = fetchParseURL(URL)) == NULL) {
241 		warnx("%s: parse error", URL);
242 		goto failure;
243 	}
244 
245 	/* if no scheme was specified, take a guess */
246 	if (!*url->scheme) {
247 		if (!*url->host)
248 			strcpy(url->scheme, SCHEME_FILE);
249 		else if (strncasecmp(url->host, "ftp.", 4) == 0)
250 			strcpy(url->scheme, SCHEME_FTP);
251 		else if (strncasecmp(url->host, "www.", 4) == 0)
252 			strcpy(url->scheme, SCHEME_HTTP);
253 	}
254 
255 	timeout = 0;
256 	*flags = 0;
257 	count = 0;
258 
259 	/* common flags */
260 	if (v_level > 1)
261 		strcat(flags, "v");
262 	switch (family) {
263 	case PF_INET:
264 		strcat(flags, "4");
265 		break;
266 	case PF_INET6:
267 		strcat(flags, "6");
268 		break;
269 	}
270 
271 	/* FTP specific flags */
272 	if (strcmp(url->scheme, "ftp") == 0) {
273 		if (p_flag)
274 			strcat(flags, "p");
275 		if (d_flag)
276 			strcat(flags, "d");
277 		if (U_flag)
278 			strcat(flags, "l");
279 		timeout = T_secs ? T_secs : ftp_timeout;
280 	}
281 
282 	/* HTTP specific flags */
283 	if (strcmp(url->scheme, "http") == 0) {
284 		if (d_flag)
285 			strcat(flags, "d");
286 		if (A_flag)
287 			strcat(flags, "A");
288 		timeout = T_secs ? T_secs : http_timeout;
289 	}
290 
291 	/* set the protocol timeout. */
292 	fetchTimeout = timeout;
293 
294 	/* just print size */
295 	if (s_flag) {
296 		if (fetchStat(url, &us, flags) == -1)
297 			goto failure;
298 		if (us.size == -1)
299 			printf("Unknown\n");
300 		else
301 			printf("%lld\n", (long long)us.size);
302 		goto success;
303 	}
304 
305 	/*
306 	 * If the -r flag was specified, we have to compare the local
307 	 * and remote files, so we should really do a fetchStat()
308 	 * first, but I know of at least one HTTP server that only
309 	 * sends the content size in response to GET requests, and
310 	 * leaves it out of replies to HEAD requests.  Also, in the
311 	 * (frequent) case that the local and remote files match but
312 	 * the local file is truncated, we have sufficient information
313 	 * before the compare to issue a correct request.  Therefore,
314 	 * we always issue a GET request as if we were sure the local
315 	 * file was a truncated copy of the remote file; we can drop
316 	 * the connection later if we change our minds.
317 	 */
318 	if ((r_flag  || m_flag) && !o_stdout && stat(path, &sb) != -1) {
319 		if (r_flag)
320 			url->offset = sb.st_size;
321 	} else {
322 		sb.st_size = -1;
323 	}
324 
325 	/* start the transfer */
326 	if ((f = fetchXGet(url, &us, flags)) == NULL) {
327 		warnx("%s: %s", path, fetchLastErrString);
328 		goto failure;
329 	}
330 	if (sigint)
331 		goto signal;
332 
333 	/* check that size is as expected */
334 	if (S_size) {
335 		if (us.size == -1) {
336 			warnx("%s: size unknown", path);
337 			goto failure;
338 		} else if (us.size != S_size) {
339 			warnx("%s: size mismatch: expected %lld, actual %lld",
340 			    path, (long long)S_size, (long long)us.size);
341 			goto failure;
342 		}
343 	}
344 
345 	/* symlink instead of copy */
346 	if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
347 		if (symlink(url->doc, path) == -1) {
348 			warn("%s: symlink()", path);
349 			goto failure;
350 		}
351 		goto success;
352 	}
353 
354 	if (us.size == -1 && !o_stdout)
355 		warnx("%s: size of remote file is not known", path);
356 	if (v_level > 1) {
357 		if (sb.st_size != -1)
358 			fprintf(stderr, "local size / mtime: %lld / %ld\n",
359 			    (long long)sb.st_size, (long)sb.st_mtime);
360 		if (us.size != -1)
361 			fprintf(stderr, "remote size / mtime: %lld / %ld\n",
362 			    (long long)us.size, (long)us.mtime);
363 	}
364 
365 	/* open output file */
366 	if (o_stdout) {
367 		/* output to stdout */
368 		of = stdout;
369 	} else if (sb.st_size != -1) {
370 		/* resume mode, local file exists */
371 		if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
372 			/* no match! have to refetch */
373 			fclose(f);
374 			/* if precious, warn the user and give up */
375 			if (R_flag) {
376 				warnx("%s: local modification time "
377 				    "does not match remote", path);
378 				goto failure_keep;
379 			}
380 			url->offset = 0;
381 			if ((f = fetchXGet(url, &us, flags)) == NULL) {
382 				warnx("%s: %s", path, fetchLastErrString);
383 				goto failure;
384 			}
385 			if (sigint)
386 				goto signal;
387 		} else {
388 			if (us.size == sb.st_size)
389 				/* nothing to do */
390 				goto success;
391 			if (sb.st_size > us.size) {
392 				/* local file too long! */
393 				warnx("%s: local file (%lld bytes) is longer "
394 				    "than remote file (%lld bytes)", path,
395 				    (long long)sb.st_size, (long long)us.size);
396 				goto failure;
397 			}
398 			/* we got it, open local file and seek to offset */
399 			/*
400 			 * XXX there's a race condition here - the
401 			 * file we open is not necessarily the same as
402 			 * the one we stat()'ed earlier...
403 			 */
404 			if ((of = fopen(path, "a")) == NULL) {
405 				warn("%s: fopen()", path);
406 				goto failure;
407 			}
408 			if (fseek(of, url->offset, SEEK_SET) == -1) {
409 				warn("%s: fseek()", path);
410 				goto failure;
411 			}
412 		}
413 	}
414 	if (m_flag && sb.st_size != -1) {
415 		/* mirror mode, local file exists */
416 		if (sb.st_size == us.size && sb.st_mtime == us.mtime)
417 			goto success;
418 	}
419 	if (!of) {
420 		/*
421 		 * We don't yet have an output file; either this is a
422 		 * vanilla run with no special flags, or the local and
423 		 * remote files didn't match.
424 		 */
425 		if ((of = fopen(path, "w")) == NULL) {
426 			warn("%s: open()", path);
427 			goto failure;
428 		}
429 	}
430 	count = url->offset;
431 
432 	/* start the counter */
433 	stat_start(&xs, path, us.size, count);
434 
435 	sigalrm = siginfo = sigint = 0;
436 
437 	/* suck in the data */
438 	signal(SIGINFO, sig_handler);
439 	while (!sigint && !sigalrm) {
440 		if (us.size != -1 && us.size - count < B_size)
441 			size = us.size - count;
442 		else
443 			size = B_size;
444 		if (timeout)
445 			alarm(timeout);
446 		if (siginfo) {
447 			stat_end(&xs);
448 			siginfo = 0;
449 		}
450 		if ((size = fread(buf, 1, size, f)) == 0) {
451 			if (ferror(f) && errno == EINTR && !sigalrm && !sigint)
452 				clearerr(f);
453 			else
454 				break;
455 		}
456 		if (timeout)
457 			alarm(0);
458 		stat_update(&xs, count += size);
459 		for (ptr = buf; size > 0; ptr += wr, size -= wr)
460 			if ((wr = fwrite(ptr, 1, size, of)) < size) {
461 				if (ferror(of) && errno == EINTR &&
462 				    !sigalrm && !sigint)
463 					clearerr(of);
464 				else
465 					break;
466 			}
467 		if (size != 0)
468 			break;
469 	}
470 	signal(SIGINFO, SIG_DFL);
471 
472 	if (timeout)
473 		alarm(0);
474 
475 	stat_end(&xs);
476 
477 	/* set mtime of local file */
478 	if (!n_flag && us.mtime && !o_stdout
479 	    && (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) {
480 		struct timeval tv[2];
481 
482 		fflush(of);
483 		tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
484 		tv[1].tv_sec = (long)us.mtime;
485 		tv[0].tv_usec = tv[1].tv_usec = 0;
486 		if (utimes(path, tv))
487 			warn("%s: utimes()", path);
488 	}
489 
490 	/* timed out or interrupted? */
491  signal:
492 	if (sigalrm)
493 		warnx("transfer timed out");
494 	if (sigint) {
495 		warnx("transfer interrupted");
496 		goto failure;
497 	}
498 
499 	if (!sigalrm) {
500 		/* check the status of our files */
501 		if (ferror(f))
502 			warn("%s", URL);
503 		if (ferror(of))
504 			warn("%s", path);
505 		if (ferror(f) || ferror(of))
506 			goto failure;
507 	}
508 
509 	/* did the transfer complete normally? */
510 	if (us.size != -1 && count < us.size) {
511 		warnx("%s appears to be truncated: %lld/%lld bytes",
512 		    path, (long long)count, (long long)us.size);
513 		goto failure_keep;
514 	}
515 
516 	/*
517 	 * If the transfer timed out and we didn't know how much to
518 	 * expect, assume the worst (i.e. we didn't get all of it)
519 	 */
520 	if (sigalrm && us.size == -1) {
521 		warnx("%s may be truncated", path);
522 		goto failure_keep;
523 	}
524 
525  success:
526 	r = 0;
527 	goto done;
528  failure:
529 	if (of && of != stdout && !R_flag && !r_flag)
530 		if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG))
531 			unlink(path);
532  failure_keep:
533 	r = -1;
534 	goto done;
535  done:
536 	if (f)
537 		fclose(f);
538 	if (of && of != stdout)
539 		fclose(of);
540 	if (url)
541 		fetchFreeURL(url);
542 	return r;
543 }
544 
545 static void
546 usage(void)
547 {
548 	fprintf(stderr, "%s\n%s\n%s\n",
549 	    "Usage: fetch [-146AFMPRUadlmnpqrsv] [-o outputfile] [-S bytes]",
550 	    "             [-B bytes] [-T seconds] [-w seconds]",
551 	    "             [-h host -f file [-c dir] | URL ...]");
552 }
553 
554 
555 #define PARSENUM(NAME, TYPE)				\
556 static int						\
557 NAME(const char *s, TYPE *v)				\
558 {							\
559         *v = 0;						\
560 	for (*v = 0; *s; s++)				\
561 		if (isdigit(*s))			\
562 			*v = *v * 10 + *s - '0';	\
563 		else					\
564 			return -1;			\
565 	return 0;					\
566 }
567 
568 PARSENUM(parseint, u_int)
569 PARSENUM(parseoff, off_t)
570 
571 int
572 main(int argc, char *argv[])
573 {
574 	struct stat sb;
575 	struct sigaction sa;
576 	const char *p, *s;
577 	char *q;
578 	int c, e, r;
579 
580 	while ((c = getopt(argc, argv,
581 	    "146AaB:bc:dFf:Hh:lMmnPpo:qRrS:sT:tUvw:")) != EOF)
582 		switch (c) {
583 		case '1':
584 			once_flag = 1;
585 			break;
586 		case '4':
587 			family = PF_INET;
588 			break;
589 		case '6':
590 			family = PF_INET6;
591 			break;
592 		case 'A':
593 			A_flag = 1;
594 			break;
595 		case 'a':
596 			a_flag = 1;
597 			break;
598 		case 'B':
599 			if (parseoff(optarg, &B_size) == -1)
600 				errx(1, "invalid buffer size");
601 			break;
602 		case 'b':
603 			warnx("warning: the -b option is deprecated");
604 			b_flag = 1;
605 			break;
606 		case 'c':
607 			c_dirname = optarg;
608 			break;
609 		case 'd':
610 			d_flag = 1;
611 			break;
612 		case 'F':
613 			F_flag = 1;
614 			break;
615 		case 'f':
616 			f_filename = optarg;
617 			break;
618 		case 'H':
619 			warnx("The -H option is now implicit, "
620 			    "use -U to disable");
621 			break;
622 		case 'h':
623 			h_hostname = optarg;
624 			break;
625 		case 'l':
626 			l_flag = 1;
627 			break;
628 		case 'o':
629 			o_flag = 1;
630 			o_filename = optarg;
631 			break;
632 		case 'M':
633 		case 'm':
634 			if (r_flag)
635 				errx(1, "the -m and -r flags "
636 				    "are mutually exclusive");
637 			m_flag = 1;
638 			break;
639 		case 'n':
640 			n_flag = 1;
641 			break;
642 		case 'P':
643 		case 'p':
644 			p_flag = 1;
645 			break;
646 		case 'q':
647 			v_level = 0;
648 			break;
649 		case 'R':
650 			R_flag = 1;
651 			break;
652 		case 'r':
653 			if (m_flag)
654 				errx(1, "the -m and -r flags "
655 				    "are mutually exclusive");
656 			r_flag = 1;
657 			break;
658 		case 'S':
659 			if (parseoff(optarg, &S_size) == -1)
660 				errx(1, "invalid size");
661 			break;
662 		case 's':
663 			s_flag = 1;
664 			break;
665 		case 'T':
666 			if (parseint(optarg, &T_secs) == -1)
667 				errx(1, "invalid timeout");
668 			break;
669 		case 't':
670 			t_flag = 1;
671 			warnx("warning: the -t option is deprecated");
672 			break;
673 		case 'U':
674 			U_flag = 1;
675 			break;
676 		case 'v':
677 			v_level++;
678 			break;
679 		case 'w':
680 			a_flag = 1;
681 			if (parseint(optarg, &w_secs) == -1)
682 				errx(1, "invalid delay");
683 			break;
684 		default:
685 			usage();
686 			exit(EX_USAGE);
687 		}
688 
689 	argc -= optind;
690 	argv += optind;
691 
692 	if (h_hostname || f_filename || c_dirname) {
693 		if (!h_hostname || !f_filename || argc) {
694 			usage();
695 			exit(EX_USAGE);
696 		}
697 		/* XXX this is a hack. */
698 		if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
699 			errx(1, "invalid hostname");
700 		if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
701 		    c_dirname ? c_dirname : "", f_filename) == -1)
702 			errx(1, "%s", strerror(ENOMEM));
703 		argc++;
704 	}
705 
706 	if (!argc) {
707 		usage();
708 		exit(EX_USAGE);
709 	}
710 
711 	/* allocate buffer */
712 	if (B_size < MINBUFSIZE)
713 		B_size = MINBUFSIZE;
714 	if ((buf = malloc(B_size)) == NULL)
715 		errx(1, "%s", strerror(ENOMEM));
716 
717 	/* timeouts */
718 	if ((s = getenv("FTP_TIMEOUT")) != NULL) {
719 		if (parseint(s, &ftp_timeout) == -1) {
720 			warnx("FTP_TIMEOUT is not a positive integer");
721 			ftp_timeout = 0;
722 		}
723 	}
724 	if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
725 		if (parseint(s, &http_timeout) == -1) {
726 			warnx("HTTP_TIMEOUT is not a positive integer");
727 			http_timeout = 0;
728 		}
729 	}
730 
731 	/* signal handling */
732 	sa.sa_flags = 0;
733 	sa.sa_handler = sig_handler;
734 	sigemptyset(&sa.sa_mask);
735 	sigaction(SIGALRM, &sa, NULL);
736 	sa.sa_flags = SA_RESETHAND;
737 	sigaction(SIGINT, &sa, NULL);
738 	fetchRestartCalls = 0;
739 
740 	/* output file */
741 	if (o_flag) {
742 		if (strcmp(o_filename, "-") == 0) {
743 			o_stdout = 1;
744 		} else if (stat(o_filename, &sb) == -1) {
745 			if (errno == ENOENT) {
746 				if (argc > 1)
747 					errx(EX_USAGE, "%s is not a directory",
748 					    o_filename);
749 			} else {
750 				err(EX_IOERR, "%s", o_filename);
751 			}
752 		} else {
753 			if (sb.st_mode & S_IFDIR)
754 				o_directory = 1;
755 		}
756 	}
757 
758 	/* check if output is to a tty (for progress report) */
759 	v_tty = isatty(STDERR_FILENO);
760 	r = 0;
761 
762 	/* authentication */
763 	if (v_tty)
764 		fetchAuthMethod = query_auth;
765 
766 	while (argc) {
767 		if ((p = strrchr(*argv, '/')) == NULL)
768 			p = *argv;
769 		else
770 			p++;
771 
772 		if (!*p)
773 			p = "fetch.out";
774 
775 		fetchLastErrCode = 0;
776 
777 		if (o_flag) {
778 			if (o_stdout) {
779 				e = fetch(*argv, "-");
780 			} else if (o_directory) {
781 				asprintf(&q, "%s/%s", o_filename, p);
782 				e = fetch(*argv, q);
783 				free(q);
784 			} else {
785 				e = fetch(*argv, o_filename);
786 			}
787 		} else {
788 			e = fetch(*argv, p);
789 		}
790 
791 		if (sigint)
792 			kill(getpid(), SIGINT);
793 
794 		if (e == 0 && once_flag)
795 			exit(0);
796 
797 		if (e) {
798 			r = 1;
799 			if ((fetchLastErrCode
800 			    && fetchLastErrCode != FETCH_UNAVAIL
801 			    && fetchLastErrCode != FETCH_MOVED
802 			    && fetchLastErrCode != FETCH_URL
803 			    && fetchLastErrCode != FETCH_RESOLV
804 			    && fetchLastErrCode != FETCH_UNKNOWN)) {
805 				if (w_secs && v_level)
806 					fprintf(stderr, "Waiting %d seconds "
807 					    "before retrying\n", w_secs);
808 				if (w_secs)
809 					sleep(w_secs);
810 				if (a_flag)
811 					continue;
812 			}
813 		}
814 
815 		argc--, argv++;
816 	}
817 
818 	exit(r);
819 }
820