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