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