1*a0c22864Snaddy /* $OpenBSD: fetch.c,v 1.202 2021/02/25 20:51:55 naddy Exp $ */ 2bfd817adSflorian /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */ 3bfd817adSflorian 4bfd817adSflorian /*- 5bfd817adSflorian * Copyright (c) 1997 The NetBSD Foundation, Inc. 6bfd817adSflorian * All rights reserved. 7bfd817adSflorian * 8bfd817adSflorian * This code is derived from software contributed to The NetBSD Foundation 9bfd817adSflorian * by Jason Thorpe and Luke Mewburn. 10bfd817adSflorian * 11bfd817adSflorian * Redistribution and use in source and binary forms, with or without 12bfd817adSflorian * modification, are permitted provided that the following conditions 13bfd817adSflorian * are met: 14bfd817adSflorian * 1. Redistributions of source code must retain the above copyright 15bfd817adSflorian * notice, this list of conditions and the following disclaimer. 16bfd817adSflorian * 2. Redistributions in binary form must reproduce the above copyright 17bfd817adSflorian * notice, this list of conditions and the following disclaimer in the 18bfd817adSflorian * documentation and/or other materials provided with the distribution. 19bfd817adSflorian * 20bfd817adSflorian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21bfd817adSflorian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22bfd817adSflorian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23bfd817adSflorian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24bfd817adSflorian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25bfd817adSflorian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26bfd817adSflorian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27bfd817adSflorian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28bfd817adSflorian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29bfd817adSflorian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30bfd817adSflorian * POSSIBILITY OF SUCH DAMAGE. 31bfd817adSflorian */ 32bfd817adSflorian 33bfd817adSflorian /* 34bfd817adSflorian * FTP User Program -- Command line file retrieval 35bfd817adSflorian */ 36bfd817adSflorian 37bfd817adSflorian #include <sys/types.h> 38bfd817adSflorian #include <sys/socket.h> 39bfd817adSflorian #include <sys/stat.h> 40bfd817adSflorian 41bfd817adSflorian #include <netinet/in.h> 42bfd817adSflorian 43bfd817adSflorian #include <arpa/ftp.h> 44bfd817adSflorian #include <arpa/inet.h> 45bfd817adSflorian 46bfd817adSflorian #include <ctype.h> 47bfd817adSflorian #include <err.h> 48bfd817adSflorian #include <libgen.h> 49bfd817adSflorian #include <netdb.h> 50bfd817adSflorian #include <fcntl.h> 51bfd817adSflorian #include <signal.h> 52740dbc81Sderaadt #include <vis.h> 53bfd817adSflorian #include <stdio.h> 54bfd817adSflorian #include <stdarg.h> 55bfd817adSflorian #include <errno.h> 56bfd817adSflorian #include <stdlib.h> 57bfd817adSflorian #include <string.h> 58bfd817adSflorian #include <unistd.h> 59bfd817adSflorian #include <resolv.h> 6049dd5d56Srobert #include <utime.h> 61bfd817adSflorian 62bfd817adSflorian #ifndef NOSSL 63bfd817adSflorian #include <tls.h> 64bfd817adSflorian #else /* !NOSSL */ 65bfd817adSflorian struct tls; 66bfd817adSflorian #endif /* !NOSSL */ 67bfd817adSflorian 68bfd817adSflorian #include "ftp_var.h" 69bfd817adSflorian #include "cmds.h" 70bfd817adSflorian 71ba6894b2Sjca static int file_get(const char *, const char *); 72bfd817adSflorian static int url_get(const char *, const char *, const char *, int); 737102f6c2Sjca static int save_chunked(FILE *, struct tls *, int , char *, size_t); 74194d6d9dSjca static void aborthttp(int); 75194d6d9dSjca static char hextochar(const char *); 76194d6d9dSjca static char *urldecode(const char *); 77194d6d9dSjca static char *recode_credentials(const char *_userinfo); 78583394bbSjca static void ftp_close(FILE **, struct tls **, int *); 79194d6d9dSjca static const char *sockerror(struct tls *); 80b237d516Sjca #ifdef SMALL 81b237d516Sjca #define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__) 82b237d516Sjca #else 83b237d516Sjca static int ftp_printf(FILE *, const char *, ...); 84b237d516Sjca #endif /* SMALL */ 85bfd817adSflorian #ifndef NOSSL 86194d6d9dSjca static int proxy_connect(int, char *, char *); 87194d6d9dSjca static int stdio_tls_write_wrapper(void *, const char *, int); 88194d6d9dSjca static int stdio_tls_read_wrapper(void *, char *, int); 89bfd817adSflorian #endif /* !NOSSL */ 90bfd817adSflorian 91bfd817adSflorian #define FTP_URL "ftp://" /* ftp URL prefix */ 92bfd817adSflorian #define HTTP_URL "http://" /* http URL prefix */ 93bfd817adSflorian #define HTTPS_URL "https://" /* https URL prefix */ 94bfd817adSflorian #define FILE_URL "file:" /* file URL prefix */ 95bfd817adSflorian #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ 96bfd817adSflorian #define HTTP_PROXY "http_proxy" /* env var with http proxy location */ 97bfd817adSflorian 98bfd817adSflorian #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) 99bfd817adSflorian 100bfd817adSflorian static const char at_encoding_warning[] = 101bfd817adSflorian "Extra `@' characters in usernames and passwords should be encoded as %%40"; 102bfd817adSflorian 103194d6d9dSjca static jmp_buf httpabort; 104bfd817adSflorian 105bfd817adSflorian static int redirect_loop; 106f78cba61Sjca static int retried; 107bfd817adSflorian 108bfd817adSflorian /* 109bfd817adSflorian * Determine whether the character needs encoding, per RFC1738: 110bfd817adSflorian * - No corresponding graphic US-ASCII. 111bfd817adSflorian * - Unsafe characters. 112bfd817adSflorian */ 113bfd817adSflorian static int 114bfd817adSflorian unsafe_char(const char *c0) 115bfd817adSflorian { 116bfd817adSflorian const char *unsafe_chars = " <>\"#{}|\\^~[]`"; 117bfd817adSflorian const unsigned char *c = (const unsigned char *)c0; 118bfd817adSflorian 119bfd817adSflorian /* 120bfd817adSflorian * No corresponding graphic US-ASCII. 121bfd817adSflorian * Control characters and octets not used in US-ASCII. 122bfd817adSflorian */ 123bfd817adSflorian return (iscntrl(*c) || !isascii(*c) || 124bfd817adSflorian 125bfd817adSflorian /* 126bfd817adSflorian * Unsafe characters. 127bfd817adSflorian * '%' is also unsafe, if is not followed by two 128bfd817adSflorian * hexadecimal digits. 129bfd817adSflorian */ 130bfd817adSflorian strchr(unsafe_chars, *c) != NULL || 131bfd817adSflorian (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c)))); 132bfd817adSflorian } 133bfd817adSflorian 134bfd817adSflorian /* 135bfd817adSflorian * Encode given URL, per RFC1738. 136bfd817adSflorian * Allocate and return string to the caller. 137bfd817adSflorian */ 138bfd817adSflorian static char * 139bfd817adSflorian url_encode(const char *path) 140bfd817adSflorian { 141bfd817adSflorian size_t i, length, new_length; 142bfd817adSflorian char *epath, *epathp; 143bfd817adSflorian 144bfd817adSflorian length = new_length = strlen(path); 145bfd817adSflorian 146bfd817adSflorian /* 147bfd817adSflorian * First pass: 148bfd817adSflorian * Count unsafe characters, and determine length of the 149bfd817adSflorian * final URL. 150bfd817adSflorian */ 151bfd817adSflorian for (i = 0; i < length; i++) 152bfd817adSflorian if (unsafe_char(path + i)) 153bfd817adSflorian new_length += 2; 154bfd817adSflorian 155bfd817adSflorian epath = epathp = malloc(new_length + 1); /* One more for '\0'. */ 156bfd817adSflorian if (epath == NULL) 157bfd817adSflorian err(1, "Can't allocate memory for URL encoding"); 158bfd817adSflorian 159bfd817adSflorian /* 160bfd817adSflorian * Second pass: 161bfd817adSflorian * Encode, and copy final URL. 162bfd817adSflorian */ 163bfd817adSflorian for (i = 0; i < length; i++) 164bfd817adSflorian if (unsafe_char(path + i)) { 165bfd817adSflorian snprintf(epathp, 4, "%%" "%02x", 166bfd817adSflorian (unsigned char)path[i]); 167bfd817adSflorian epathp += 3; 168bfd817adSflorian } else 169bfd817adSflorian *(epathp++) = path[i]; 170bfd817adSflorian 171bfd817adSflorian *epathp = '\0'; 172bfd817adSflorian return (epath); 173bfd817adSflorian } 174bfd817adSflorian 175bfd817adSflorian /* ARGSUSED */ 176bfd817adSflorian static void 177bfd817adSflorian tooslow(int signo) 178bfd817adSflorian { 179bfd817adSflorian dprintf(STDERR_FILENO, "%s: connect taking too long\n", __progname); 180bfd817adSflorian _exit(2); 181bfd817adSflorian } 182bfd817adSflorian 183bfd817adSflorian /* 184ba6894b2Sjca * Copy a local file (used by the OpenBSD installer). 185ba6894b2Sjca * Returns -1 on failure, 0 on success 186ba6894b2Sjca */ 187ba6894b2Sjca static int 188ba6894b2Sjca file_get(const char *path, const char *outfile) 189ba6894b2Sjca { 190ba6894b2Sjca struct stat st; 1913f8a9645Sjca int fd, out = -1, rval = -1, save_errno; 192ba6894b2Sjca volatile sig_t oldintr, oldinti; 193ba6894b2Sjca const char *savefile; 1943411e43fSnaddy char *buf = NULL, *cp, *pathbuf = NULL; 195ba6894b2Sjca const size_t buflen = 128 * 1024; 196ba6894b2Sjca off_t hashbytes; 197ba6894b2Sjca ssize_t len, wlen; 198ba6894b2Sjca 199ba6894b2Sjca direction = "received"; 200ba6894b2Sjca 201ba6894b2Sjca fd = open(path, O_RDONLY); 202ba6894b2Sjca if (fd == -1) { 203ba6894b2Sjca warn("Can't open file %s", path); 204ba6894b2Sjca return -1; 205ba6894b2Sjca } 206ba6894b2Sjca 207ba6894b2Sjca if (fstat(fd, &st) == -1) 208ba6894b2Sjca filesize = -1; 209ba6894b2Sjca else 210ba6894b2Sjca filesize = st.st_size; 211ba6894b2Sjca 212ba6894b2Sjca if (outfile != NULL) 213ba6894b2Sjca savefile = outfile; 214ba6894b2Sjca else { 215ba6894b2Sjca if (path[strlen(path) - 1] == '/') /* Consider no file */ 216ba6894b2Sjca savefile = NULL; /* after dir invalid. */ 2173411e43fSnaddy else { 2183411e43fSnaddy pathbuf = strdup(path); 2193411e43fSnaddy if (pathbuf == NULL) 2203411e43fSnaddy errx(1, "Can't allocate memory for filename"); 2213411e43fSnaddy savefile = basename(pathbuf); 2223411e43fSnaddy } 223ba6894b2Sjca } 224ba6894b2Sjca 225ba6894b2Sjca if (EMPTYSTRING(savefile)) { 226ba6894b2Sjca warnx("No filename after directory (use -o): %s", path); 227ba6894b2Sjca goto cleanup_copy; 228ba6894b2Sjca } 229ba6894b2Sjca 230ba6894b2Sjca /* Open the output file. */ 231ba6894b2Sjca if (!pipeout) { 232ba6894b2Sjca out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); 233ba6894b2Sjca if (out == -1) { 234ba6894b2Sjca warn("Can't open %s", savefile); 235ba6894b2Sjca goto cleanup_copy; 236ba6894b2Sjca } 237ba6894b2Sjca } else 238ba6894b2Sjca out = fileno(stdout); 239ba6894b2Sjca 240ba6894b2Sjca if ((buf = malloc(buflen)) == NULL) 241ba6894b2Sjca errx(1, "Can't allocate memory for transfer buffer"); 242ba6894b2Sjca 243ba6894b2Sjca /* Trap signals */ 244ba6894b2Sjca oldintr = NULL; 245ba6894b2Sjca oldinti = NULL; 246ba6894b2Sjca if (setjmp(httpabort)) { 247ba6894b2Sjca if (oldintr) 248ba6894b2Sjca (void)signal(SIGINT, oldintr); 249ba6894b2Sjca if (oldinti) 250ba6894b2Sjca (void)signal(SIGINFO, oldinti); 251ba6894b2Sjca goto cleanup_copy; 252ba6894b2Sjca } 2535fbde59fSkn oldintr = signal(SIGINT, aborthttp); 254ba6894b2Sjca 255ba6894b2Sjca bytes = 0; 256ba6894b2Sjca hashbytes = mark; 257ba6894b2Sjca progressmeter(-1, path); 258ba6894b2Sjca 259ba6894b2Sjca /* Finally, suck down the file. */ 260ba6894b2Sjca oldinti = signal(SIGINFO, psummary); 261ba6894b2Sjca while ((len = read(fd, buf, buflen)) > 0) { 262ba6894b2Sjca bytes += len; 263ba6894b2Sjca for (cp = buf; len > 0; len -= wlen, cp += wlen) { 264ba6894b2Sjca if ((wlen = write(out, cp, len)) == -1) { 265ba6894b2Sjca warn("Writing %s", savefile); 2667413ba97Sjca signal(SIGINT, oldintr); 267ba6894b2Sjca signal(SIGINFO, oldinti); 268ba6894b2Sjca goto cleanup_copy; 269ba6894b2Sjca } 270ba6894b2Sjca } 271ba6894b2Sjca if (hash && !progress) { 272ba6894b2Sjca while (bytes >= hashbytes) { 273ba6894b2Sjca (void)putc('#', ttyout); 274ba6894b2Sjca hashbytes += mark; 275ba6894b2Sjca } 276ba6894b2Sjca (void)fflush(ttyout); 277ba6894b2Sjca } 278ba6894b2Sjca } 279ba6894b2Sjca save_errno = errno; 2807413ba97Sjca signal(SIGINT, oldintr); 281ba6894b2Sjca signal(SIGINFO, oldinti); 282ba6894b2Sjca if (hash && !progress && bytes > 0) { 283ba6894b2Sjca if (bytes < mark) 284ba6894b2Sjca (void)putc('#', ttyout); 285ba6894b2Sjca (void)putc('\n', ttyout); 286ba6894b2Sjca (void)fflush(ttyout); 287ba6894b2Sjca } 288ba6894b2Sjca if (len == -1) { 289ba6894b2Sjca warnc(save_errno, "Reading from file"); 290ba6894b2Sjca goto cleanup_copy; 291ba6894b2Sjca } 292ba6894b2Sjca progressmeter(1, NULL); 293ba6894b2Sjca if (verbose) 294ba6894b2Sjca ptransfer(0); 295ba6894b2Sjca 296ba6894b2Sjca rval = 0; 297ba6894b2Sjca 298ba6894b2Sjca cleanup_copy: 299ba6894b2Sjca free(buf); 3003411e43fSnaddy free(pathbuf); 301ba6894b2Sjca if (out >= 0 && out != fileno(stdout)) 302ba6894b2Sjca close(out); 303ba6894b2Sjca close(fd); 304ba6894b2Sjca 305ba6894b2Sjca return rval; 306ba6894b2Sjca } 307ba6894b2Sjca 308ba6894b2Sjca /* 309bfd817adSflorian * Retrieve URL, via the proxy in $proxyvar if necessary. 310bfd817adSflorian * Returns -1 on failure, 0 on success 311bfd817adSflorian */ 312bfd817adSflorian static int 313bfd817adSflorian url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile) 314bfd817adSflorian { 315bfd817adSflorian char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; 316bfd817adSflorian char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; 317740dbc81Sderaadt char *epath, *redirurl, *loctail, *h, *p, gerror[200]; 318583394bbSjca int error, isftpurl = 0, isredirect = 0, rval = -1; 3193733520cSjca int isunavail = 0, retryafter = -1; 320bfd817adSflorian struct addrinfo hints, *res0, *res; 321583394bbSjca const char *savefile; 3223411e43fSnaddy char *pathbuf = NULL; 323583394bbSjca char *proxyurl = NULL; 3247e8f63e0Syasuoka char *credentials = NULL, *proxy_credentials = NULL; 325583394bbSjca int fd = -1, out = -1; 326bfd817adSflorian volatile sig_t oldintr, oldinti; 327bfd817adSflorian FILE *fin = NULL; 328bfd817adSflorian off_t hashbytes; 329bfd817adSflorian const char *errstr; 330bfd817adSflorian ssize_t len, wlen; 331bb1c3b3fSnaddy size_t bufsize; 332bfd817adSflorian char *proxyhost = NULL; 333bfd817adSflorian #ifndef NOSSL 334bfd817adSflorian char *sslpath = NULL, *sslhost = NULL; 3350f23e651Sjca int ishttpsurl = 0; 336bfd817adSflorian #endif /* !NOSSL */ 337bfd817adSflorian #ifndef SMALL 338e4b576d7Sjca char *full_host = NULL; 339e4b576d7Sjca const char *scheme; 340bfd817adSflorian char *locbase; 341bfd817adSflorian struct addrinfo *ares = NULL; 34249dd5d56Srobert char tmbuf[32]; 34349dd5d56Srobert time_t mtime = 0; 34449dd5d56Srobert struct stat stbuf; 34549dd5d56Srobert struct tm lmt = { 0 }; 34649dd5d56Srobert struct timespec ts[2]; 347e4b576d7Sjca #endif /* !SMALL */ 348bfd817adSflorian struct tls *tls = NULL; 349bfd817adSflorian int status; 350bfd817adSflorian int save_errno; 351bfd817adSflorian const size_t buflen = 128 * 1024; 3527102f6c2Sjca int chunked = 0; 353bfd817adSflorian 354bfd817adSflorian direction = "received"; 355bfd817adSflorian 356bfd817adSflorian newline = strdup(origline); 357bfd817adSflorian if (newline == NULL) 358bfd817adSflorian errx(1, "Can't allocate memory to parse URL"); 359bfd817adSflorian if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) { 360bfd817adSflorian host = newline + sizeof(HTTP_URL) - 1; 361e4b576d7Sjca #ifndef SMALL 362bfd817adSflorian scheme = HTTP_URL; 363bfd817adSflorian #endif /* !SMALL */ 364bfd817adSflorian } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 365bfd817adSflorian host = newline + sizeof(FTP_URL) - 1; 366bfd817adSflorian isftpurl = 1; 367bfd817adSflorian #ifndef SMALL 368bfd817adSflorian scheme = FTP_URL; 369bfd817adSflorian #endif /* !SMALL */ 370bfd817adSflorian } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) { 371d5ffb3f3Sjca #ifndef NOSSL 372bfd817adSflorian host = newline + sizeof(HTTPS_URL) - 1; 373bfd817adSflorian ishttpsurl = 1; 374d5ffb3f3Sjca #else 375d5ffb3f3Sjca errx(1, "%s: No HTTPS support", newline); 376d5ffb3f3Sjca #endif /* !NOSSL */ 377e4b576d7Sjca #ifndef SMALL 378bfd817adSflorian scheme = HTTPS_URL; 379e4b576d7Sjca #endif /* !SMALL */ 380bfd817adSflorian } else 381ba6894b2Sjca errx(1, "%s: URL not permitted", newline); 382bfd817adSflorian 383bfd817adSflorian path = strchr(host, '/'); /* Find path */ 38434ed7cb2Sjca 38534ed7cb2Sjca /* 38634ed7cb2Sjca * Look for auth header in host. 38734ed7cb2Sjca * Basic auth from RFC 2617, valid characters for path are in 38834ed7cb2Sjca * RFC 3986 section 3.3. 38934ed7cb2Sjca */ 3900f23e651Sjca if (!isftpurl) { 39134ed7cb2Sjca p = strchr(host, '@'); 39234ed7cb2Sjca if (p != NULL && (path == NULL || p < path)) { 39334ed7cb2Sjca *p++ = '\0'; 39434ed7cb2Sjca credentials = recode_credentials(host); 39534ed7cb2Sjca 39634ed7cb2Sjca /* Overwrite userinfo */ 39734ed7cb2Sjca memmove(host, p, strlen(p) + 1); 39834ed7cb2Sjca path = strchr(host, '/'); 39934ed7cb2Sjca } 40034ed7cb2Sjca } 40134ed7cb2Sjca 402bfd817adSflorian if (EMPTYSTRING(path)) { 403bfd817adSflorian if (outfile) { /* No slash, but */ 404bfd817adSflorian path = strchr(host,'\0'); /* we have outfile. */ 405bfd817adSflorian goto noslash; 406bfd817adSflorian } 407bfd817adSflorian if (isftpurl) 408bfd817adSflorian goto noftpautologin; 409bfd817adSflorian warnx("No `/' after host (use -o): %s", origline); 410bfd817adSflorian goto cleanup_url_get; 411bfd817adSflorian } 412bfd817adSflorian *path++ = '\0'; 413bfd817adSflorian if (EMPTYSTRING(path) && !outfile) { 414bfd817adSflorian if (isftpurl) 415bfd817adSflorian goto noftpautologin; 416bfd817adSflorian warnx("No filename after host (use -o): %s", origline); 417bfd817adSflorian goto cleanup_url_get; 418bfd817adSflorian } 419bfd817adSflorian 420bfd817adSflorian noslash: 421bfd817adSflorian if (outfile) 422bfd817adSflorian savefile = outfile; 423bfd817adSflorian else { 424bfd817adSflorian if (path[strlen(path) - 1] == '/') /* Consider no file */ 425bfd817adSflorian savefile = NULL; /* after dir invalid. */ 4263411e43fSnaddy else { 4273411e43fSnaddy pathbuf = strdup(path); 4283411e43fSnaddy if (pathbuf == NULL) 4293411e43fSnaddy errx(1, "Can't allocate memory for filename"); 4303411e43fSnaddy savefile = basename(pathbuf); 4313411e43fSnaddy } 432bfd817adSflorian } 433bfd817adSflorian 434bfd817adSflorian if (EMPTYSTRING(savefile)) { 435bfd817adSflorian if (isftpurl) 436bfd817adSflorian goto noftpautologin; 437bfd817adSflorian warnx("No filename after directory (use -o): %s", origline); 438bfd817adSflorian goto cleanup_url_get; 439bfd817adSflorian } 440bfd817adSflorian 441bfd817adSflorian #ifndef SMALL 442bfd817adSflorian if (resume && pipeout) { 443bfd817adSflorian warnx("can't append to stdout"); 444bfd817adSflorian goto cleanup_url_get; 445bfd817adSflorian } 446bfd817adSflorian #endif /* !SMALL */ 447bfd817adSflorian 448ba6894b2Sjca if (proxyenv != NULL) { /* use proxy */ 449bfd817adSflorian #ifndef NOSSL 450bfd817adSflorian if (ishttpsurl) { 451bfd817adSflorian sslpath = strdup(path); 452bfd817adSflorian sslhost = strdup(host); 453bfd817adSflorian if (! sslpath || ! sslhost) 454bfd817adSflorian errx(1, "Can't allocate memory for https path/host."); 455bfd817adSflorian } 456bfd817adSflorian #endif /* !NOSSL */ 457bfd817adSflorian proxyhost = strdup(host); 458bfd817adSflorian if (proxyhost == NULL) 459bfd817adSflorian errx(1, "Can't allocate memory for proxy host."); 460bfd817adSflorian proxyurl = strdup(proxyenv); 461bfd817adSflorian if (proxyurl == NULL) 462bfd817adSflorian errx(1, "Can't allocate memory for proxy URL."); 463bfd817adSflorian if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) 464bfd817adSflorian host = proxyurl + sizeof(HTTP_URL) - 1; 465bfd817adSflorian else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0) 466bfd817adSflorian host = proxyurl + sizeof(FTP_URL) - 1; 467bfd817adSflorian else { 468bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv); 469bfd817adSflorian goto cleanup_url_get; 470bfd817adSflorian } 471bfd817adSflorian if (EMPTYSTRING(host)) { 472bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv); 473bfd817adSflorian goto cleanup_url_get; 474bfd817adSflorian } 475bfd817adSflorian if (*--path == '\0') 476bfd817adSflorian *path = '/'; /* add / back to real path */ 477bfd817adSflorian path = strchr(host, '/'); /* remove trailing / on host */ 478bfd817adSflorian if (!EMPTYSTRING(path)) 479bfd817adSflorian *path++ = '\0'; /* i guess this ++ is useless */ 480bfd817adSflorian 481bfd817adSflorian path = strchr(host, '@'); /* look for credentials in proxy */ 482bfd817adSflorian if (!EMPTYSTRING(path)) { 483bfd817adSflorian *path = '\0'; 484bfd817adSflorian if (strchr(host, ':') == NULL) { 485bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv); 486bfd817adSflorian goto cleanup_url_get; 487bfd817adSflorian } 4887e8f63e0Syasuoka proxy_credentials = recode_credentials(host); 489bfd817adSflorian *path = '@'; /* restore @ in proxyurl */ 490bfd817adSflorian 491bfd817adSflorian /* 492bfd817adSflorian * This removes the password from proxyurl, 493bfd817adSflorian * filling with stars 494bfd817adSflorian */ 495bfd817adSflorian for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@'; 496bfd817adSflorian host++) 497bfd817adSflorian *host = '*'; 498bfd817adSflorian 499bfd817adSflorian host = path + 1; 500bfd817adSflorian } 501bfd817adSflorian 502bfd817adSflorian path = newline; 503bfd817adSflorian } 504bfd817adSflorian 505bfd817adSflorian if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && 506bfd817adSflorian (hosttail[1] == '\0' || hosttail[1] == ':')) { 507bfd817adSflorian host++; 508bfd817adSflorian *hosttail++ = '\0'; 509bfd817adSflorian #ifndef SMALL 510bfd817adSflorian if (asprintf(&full_host, "[%s]", host) == -1) 511bfd817adSflorian errx(1, "Cannot allocate memory for hostname"); 512bfd817adSflorian #endif /* !SMALL */ 513bfd817adSflorian } else 514bfd817adSflorian hosttail = host; 515bfd817adSflorian 516bfd817adSflorian portnum = strrchr(hosttail, ':'); /* find portnum */ 517bfd817adSflorian if (portnum != NULL) 518bfd817adSflorian *portnum++ = '\0'; 519bfd817adSflorian #ifndef NOSSL 520bfd817adSflorian port = portnum ? portnum : (ishttpsurl ? httpsport : httpport); 521bfd817adSflorian #else /* !NOSSL */ 522bfd817adSflorian port = portnum ? portnum : httpport; 523bfd817adSflorian #endif /* !NOSSL */ 524bfd817adSflorian 525bfd817adSflorian #ifndef SMALL 526bfd817adSflorian if (full_host == NULL) 527bfd817adSflorian if ((full_host = strdup(host)) == NULL) 528bfd817adSflorian errx(1, "Cannot allocate memory for hostname"); 529bfd817adSflorian if (debug) 530bfd817adSflorian fprintf(ttyout, "host %s, port %s, path %s, " 531bfd817adSflorian "save as %s, auth %s.\n", host, port, path, 532bfd817adSflorian savefile, credentials ? credentials : "none"); 533bfd817adSflorian #endif /* !SMALL */ 534bfd817adSflorian 535bfd817adSflorian memset(&hints, 0, sizeof(hints)); 536bfd817adSflorian hints.ai_family = family; 537bfd817adSflorian hints.ai_socktype = SOCK_STREAM; 538bfd817adSflorian error = getaddrinfo(host, port, &hints, &res0); 539bfd817adSflorian /* 540bfd817adSflorian * If the services file is corrupt/missing, fall back 541bfd817adSflorian * on our hard-coded defines. 542bfd817adSflorian */ 543bfd817adSflorian if (error == EAI_SERVICE && port == httpport) { 544bfd817adSflorian snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); 545bfd817adSflorian error = getaddrinfo(host, pbuf, &hints, &res0); 546bfd817adSflorian #ifndef NOSSL 547bfd817adSflorian } else if (error == EAI_SERVICE && port == httpsport) { 548bfd817adSflorian snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); 549bfd817adSflorian error = getaddrinfo(host, pbuf, &hints, &res0); 550bfd817adSflorian #endif /* !NOSSL */ 551bfd817adSflorian } 552bfd817adSflorian if (error) { 553bfd817adSflorian warnx("%s: %s", host, gai_strerror(error)); 554bfd817adSflorian goto cleanup_url_get; 555bfd817adSflorian } 556bfd817adSflorian 557bfd817adSflorian #ifndef SMALL 558bfd817adSflorian if (srcaddr) { 559bfd817adSflorian hints.ai_flags |= AI_NUMERICHOST; 560bfd817adSflorian error = getaddrinfo(srcaddr, NULL, &hints, &ares); 561bfd817adSflorian if (error) { 562bfd817adSflorian warnx("%s: %s", srcaddr, gai_strerror(error)); 563bfd817adSflorian goto cleanup_url_get; 564bfd817adSflorian } 565bfd817adSflorian } 566bfd817adSflorian #endif /* !SMALL */ 567bfd817adSflorian 568bfd817adSflorian /* ensure consistent order of the output */ 569bfd817adSflorian if (verbose) 570bfd817adSflorian setvbuf(ttyout, NULL, _IOLBF, 0); 571bfd817adSflorian 572bfd817adSflorian fd = -1; 573bfd817adSflorian for (res = res0; res; res = res->ai_next) { 574bfd817adSflorian if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, 575bfd817adSflorian sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) 576bfd817adSflorian strlcpy(hbuf, "(unknown)", sizeof(hbuf)); 577bfd817adSflorian if (verbose) 578bfd817adSflorian fprintf(ttyout, "Trying %s...\n", hbuf); 579bfd817adSflorian 580bfd817adSflorian fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 581bfd817adSflorian if (fd == -1) { 582bfd817adSflorian cause = "socket"; 583bfd817adSflorian continue; 584bfd817adSflorian } 585bfd817adSflorian 586bfd817adSflorian #ifndef SMALL 587bfd817adSflorian if (srcaddr) { 588bfd817adSflorian if (ares->ai_family != res->ai_family) { 589bfd817adSflorian close(fd); 590bfd817adSflorian fd = -1; 591bfd817adSflorian errno = EINVAL; 592bfd817adSflorian cause = "bind"; 593bfd817adSflorian continue; 594bfd817adSflorian } 5953aaa63ebSderaadt if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) { 596bfd817adSflorian save_errno = errno; 597bfd817adSflorian close(fd); 598bfd817adSflorian errno = save_errno; 599bfd817adSflorian fd = -1; 600bfd817adSflorian cause = "bind"; 601bfd817adSflorian continue; 602bfd817adSflorian } 603bfd817adSflorian } 604bfd817adSflorian #endif /* !SMALL */ 605bfd817adSflorian 606bfd817adSflorian if (connect_timeout) { 607bfd817adSflorian (void)signal(SIGALRM, tooslow); 608bfd817adSflorian alarmtimer(connect_timeout); 609bfd817adSflorian } 610bfd817adSflorian 611bfd817adSflorian for (error = connect(fd, res->ai_addr, res->ai_addrlen); 612bfd817adSflorian error != 0 && errno == EINTR; error = connect_wait(fd)) 613bfd817adSflorian continue; 614bfd817adSflorian if (error != 0) { 615bfd817adSflorian save_errno = errno; 616bfd817adSflorian close(fd); 617bfd817adSflorian errno = save_errno; 618bfd817adSflorian fd = -1; 619bfd817adSflorian cause = "connect"; 620bfd817adSflorian continue; 621bfd817adSflorian } 622bfd817adSflorian 623bfd817adSflorian /* get port in numeric */ 624bfd817adSflorian if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, 625bfd817adSflorian pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0) 626bfd817adSflorian port = pbuf; 627bfd817adSflorian else 628bfd817adSflorian port = NULL; 629bfd817adSflorian 630bfd817adSflorian #ifndef NOSSL 631bfd817adSflorian if (proxyenv && sslhost) 6327e8f63e0Syasuoka proxy_connect(fd, sslhost, proxy_credentials); 633bfd817adSflorian #endif /* !NOSSL */ 634bfd817adSflorian break; 635bfd817adSflorian } 636bfd817adSflorian freeaddrinfo(res0); 637bfd817adSflorian #ifndef SMALL 638bfd817adSflorian if (srcaddr) 639bfd817adSflorian freeaddrinfo(ares); 640bfd817adSflorian #endif /* !SMALL */ 641bfd817adSflorian if (fd < 0) { 642bfd817adSflorian warn("%s", cause); 643bfd817adSflorian goto cleanup_url_get; 644bfd817adSflorian } 645bfd817adSflorian 646bfd817adSflorian #ifndef NOSSL 647bfd817adSflorian if (ishttpsurl) { 64880ddac7aSbeck ssize_t ret; 649bfd817adSflorian if (proxyenv && sslpath) { 650bfd817adSflorian ishttpsurl = 0; 651bfd817adSflorian proxyurl = NULL; 652bfd817adSflorian path = sslpath; 653bfd817adSflorian } 654bfd817adSflorian if (sslhost == NULL) { 655bfd817adSflorian sslhost = strdup(host); 656bfd817adSflorian if (sslhost == NULL) 657bfd817adSflorian errx(1, "Can't allocate memory for https host."); 658bfd817adSflorian } 659bfd817adSflorian if ((tls = tls_client()) == NULL) { 660bfd817adSflorian fprintf(ttyout, "failed to create SSL client\n"); 661bfd817adSflorian goto cleanup_url_get; 662bfd817adSflorian } 663bfd817adSflorian if (tls_configure(tls, tls_config) != 0) { 66480ddac7aSbeck fprintf(ttyout, "TLS configuration failure: %s\n", 665bfd817adSflorian tls_error(tls)); 666bfd817adSflorian goto cleanup_url_get; 667bfd817adSflorian } 668bfd817adSflorian if (tls_connect_socket(tls, fd, sslhost) != 0) { 66980ddac7aSbeck fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls)); 670bfd817adSflorian goto cleanup_url_get; 671bfd817adSflorian } 67280ddac7aSbeck do { 67380ddac7aSbeck ret = tls_handshake(tls); 67480ddac7aSbeck } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 67580ddac7aSbeck if (ret != 0) { 67680ddac7aSbeck fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls)); 67794d4d1aaSjca goto cleanup_url_get; 67894d4d1aaSjca } 67994d4d1aaSjca fin = funopen(tls, stdio_tls_read_wrapper, 68094d4d1aaSjca stdio_tls_write_wrapper, NULL, NULL); 681bfd817adSflorian } else { 682bfd817adSflorian fin = fdopen(fd, "r+"); 683bfd817adSflorian fd = -1; 684bfd817adSflorian } 685bfd817adSflorian #else /* !NOSSL */ 686bfd817adSflorian fin = fdopen(fd, "r+"); 687bfd817adSflorian fd = -1; 688bfd817adSflorian #endif /* !NOSSL */ 689bfd817adSflorian 690bfd817adSflorian #ifdef SMALL 691bfd817adSflorian if (lastfile) { 692bfd817adSflorian if (pipeout) { 693bfd817adSflorian if (pledge("stdio rpath inet dns tty", NULL) == -1) 694bfd817adSflorian err(1, "pledge"); 695bfd817adSflorian } else { 696bfd817adSflorian if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1) 697bfd817adSflorian err(1, "pledge"); 698bfd817adSflorian } 699bfd817adSflorian } 700bfd817adSflorian #endif 701bfd817adSflorian 702bfd817adSflorian if (connect_timeout) { 703bfd817adSflorian signal(SIGALRM, SIG_DFL); 704bfd817adSflorian alarmtimer(0); 705bfd817adSflorian } 706bfd817adSflorian 707bfd817adSflorian /* 708bfd817adSflorian * Construct and send the request. Proxy requests don't want leading /. 709bfd817adSflorian */ 710bfd817adSflorian #ifndef NOSSL 711bfd817adSflorian cookie_get(host, path, ishttpsurl, &buf); 712bfd817adSflorian #endif /* !NOSSL */ 713bfd817adSflorian 714bfd817adSflorian epath = url_encode(path); 715bfd817adSflorian if (proxyurl) { 716bfd817adSflorian if (verbose) { 717bfd817adSflorian fprintf(ttyout, "Requesting %s (via %s)\n", 718bfd817adSflorian origline, proxyurl); 719bfd817adSflorian } 720bfd817adSflorian /* 721bfd817adSflorian * Host: directive must use the destination host address for 722bfd817adSflorian * the original URI (path). 723bfd817adSflorian */ 724b237d516Sjca ftp_printf(fin, "GET %s HTTP/1.1\r\n" 7257102f6c2Sjca "Connection: close\r\n" 7267e8f63e0Syasuoka "Host: %s\r\n%s%s\r\n", 727bfd817adSflorian epath, proxyhost, buf ? buf : "", httpuseragent); 7287e8f63e0Syasuoka if (credentials) 7297e8f63e0Syasuoka ftp_printf(fin, "Authorization: Basic %s\r\n", 7307e8f63e0Syasuoka credentials); 7317e8f63e0Syasuoka if (proxy_credentials) 7327e8f63e0Syasuoka ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n", 7337e8f63e0Syasuoka proxy_credentials); 7347e8f63e0Syasuoka ftp_printf(fin, "\r\n"); 735bfd817adSflorian } else { 736bfd817adSflorian if (verbose) 737bfd817adSflorian fprintf(ttyout, "Requesting %s\n", origline); 738bfd817adSflorian #ifndef SMALL 73949dd5d56Srobert if (resume || timestamp) { 74049dd5d56Srobert if (stat(savefile, &stbuf) == 0) { 74149dd5d56Srobert if (resume) 742bfd817adSflorian restart_point = stbuf.st_size; 74349dd5d56Srobert if (timestamp) 74449dd5d56Srobert mtime = stbuf.st_mtime; 74549dd5d56Srobert } else { 746bfd817adSflorian restart_point = 0; 74749dd5d56Srobert mtime = 0; 74849dd5d56Srobert } 749bfd817adSflorian } 750bfd817adSflorian #endif /* SMALL */ 751b237d516Sjca ftp_printf(fin, 7527102f6c2Sjca "GET /%s HTTP/1.1\r\n" 7537102f6c2Sjca "Connection: close\r\n" 7547102f6c2Sjca "Host: ", epath); 755bfd817adSflorian if (proxyhost) { 756b237d516Sjca ftp_printf(fin, "%s", proxyhost); 757bfd817adSflorian port = NULL; 758bfd817adSflorian } else if (strchr(host, ':')) { 759bfd817adSflorian /* 760bfd817adSflorian * strip off scoped address portion, since it's 761bfd817adSflorian * local to node 762bfd817adSflorian */ 763bfd817adSflorian h = strdup(host); 764bfd817adSflorian if (h == NULL) 765bfd817adSflorian errx(1, "Can't allocate memory."); 766bfd817adSflorian if ((p = strchr(h, '%')) != NULL) 767bfd817adSflorian *p = '\0'; 768b237d516Sjca ftp_printf(fin, "[%s]", h); 769bfd817adSflorian free(h); 770bfd817adSflorian } else 771b237d516Sjca ftp_printf(fin, "%s", host); 772bfd817adSflorian 773bfd817adSflorian /* 774bfd817adSflorian * Send port number only if it's specified and does not equal 775bfd817adSflorian * 80. Some broken HTTP servers get confused if you explicitly 776bfd817adSflorian * send them the port number. 777bfd817adSflorian */ 778bfd817adSflorian #ifndef NOSSL 779bfd817adSflorian if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0) 780b237d516Sjca ftp_printf(fin, ":%s", port); 781bfd817adSflorian if (restart_point) 782b237d516Sjca ftp_printf(fin, "\r\nRange: bytes=%lld-", 783bfd817adSflorian (long long)restart_point); 784bfd817adSflorian #else /* !NOSSL */ 785bfd817adSflorian if (port && strcmp(port, "80") != 0) 786b237d516Sjca ftp_printf(fin, ":%s", port); 787bfd817adSflorian #endif /* !NOSSL */ 78849dd5d56Srobert 78949dd5d56Srobert #ifndef SMALL 79049dd5d56Srobert if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0)) 79149dd5d56Srobert ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf); 79249dd5d56Srobert #endif /* SMALL */ 79349dd5d56Srobert 79467a189a2Sjca ftp_printf(fin, "\r\n%s%s\r\n", 795bfd817adSflorian buf ? buf : "", httpuseragent); 79667a189a2Sjca if (credentials) 79767a189a2Sjca ftp_printf(fin, "Authorization: Basic %s\r\n", 79867a189a2Sjca credentials); 79967a189a2Sjca ftp_printf(fin, "\r\n"); 800bfd817adSflorian } 801bfd817adSflorian free(epath); 802bfd817adSflorian 803bfd817adSflorian #ifndef NOSSL 804bfd817adSflorian free(buf); 805bfd817adSflorian #endif /* !NOSSL */ 806bfd817adSflorian buf = NULL; 807bb1c3b3fSnaddy bufsize = 0; 808bfd817adSflorian 80994d4d1aaSjca if (fflush(fin) == EOF) { 81094d4d1aaSjca warnx("Writing HTTP request: %s", sockerror(tls)); 811bfd817adSflorian goto cleanup_url_get; 812bfd817adSflorian } 813bb1c3b3fSnaddy if ((len = getline(&buf, &bufsize, fin)) == -1) { 81494d4d1aaSjca warnx("Receiving HTTP reply: %s", sockerror(tls)); 815bfd817adSflorian goto cleanup_url_get; 816bfd817adSflorian } 817bfd817adSflorian 818bfd817adSflorian while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) 819bfd817adSflorian buf[--len] = '\0'; 820bfd817adSflorian #ifndef SMALL 821bfd817adSflorian if (debug) 822bfd817adSflorian fprintf(ttyout, "received '%s'\n", buf); 823bfd817adSflorian #endif /* !SMALL */ 824bfd817adSflorian 825bfd817adSflorian cp = strchr(buf, ' '); 826bfd817adSflorian if (cp == NULL) 827bfd817adSflorian goto improper; 828bfd817adSflorian else 829bfd817adSflorian cp++; 830bfd817adSflorian 831bfd817adSflorian strlcpy(ststr, cp, sizeof(ststr)); 832f78cba61Sjca status = strtonum(ststr, 200, 503, &errstr); 833bfd817adSflorian if (errstr) { 834740dbc81Sderaadt strnvis(gerror, cp, sizeof gerror, VIS_SAFE); 835740dbc81Sderaadt warnx("Error retrieving %s: %s", origline, gerror); 836bfd817adSflorian goto cleanup_url_get; 837bfd817adSflorian } 838bfd817adSflorian 839bfd817adSflorian switch (status) { 840bfd817adSflorian case 200: /* OK */ 841bfd817adSflorian #ifndef SMALL 842bfd817adSflorian /* 843bfd817adSflorian * When we request a partial file, and we receive an HTTP 200 844bfd817adSflorian * it is a good indication that the server doesn't support 845bfd817adSflorian * range requests, and is about to send us the entire file. 846bfd817adSflorian * If the restart_point == 0, then we are not actually 847bfd817adSflorian * requesting a partial file, and an HTTP 200 is appropriate. 848bfd817adSflorian */ 849bfd817adSflorian if (resume && restart_point != 0) { 850bfd817adSflorian warnx("Server does not support resume."); 851bfd817adSflorian restart_point = resume = 0; 852bfd817adSflorian } 853bfd817adSflorian /* FALLTHROUGH */ 854bfd817adSflorian case 206: /* Partial Content */ 855bfd817adSflorian #endif /* !SMALL */ 856bfd817adSflorian break; 857bfd817adSflorian case 301: /* Moved Permanently */ 858bfd817adSflorian case 302: /* Found */ 859bfd817adSflorian case 303: /* See Other */ 860bfd817adSflorian case 307: /* Temporary Redirect */ 861e0549513Schrisz case 308: /* Permanent Redirect (RFC 7538) */ 862bfd817adSflorian isredirect++; 863bfd817adSflorian if (redirect_loop++ > 10) { 864bfd817adSflorian warnx("Too many redirections requested"); 865bfd817adSflorian goto cleanup_url_get; 866bfd817adSflorian } 867bfd817adSflorian break; 868bfd817adSflorian #ifndef SMALL 86949dd5d56Srobert case 304: /* Not Modified */ 87049dd5d56Srobert warnx("File is not modified on the server"); 87149dd5d56Srobert goto cleanup_url_get; 872bfd817adSflorian case 416: /* Requested Range Not Satisfiable */ 873bfd817adSflorian warnx("File is already fully retrieved."); 874bfd817adSflorian goto cleanup_url_get; 875bfd817adSflorian #endif /* !SMALL */ 876f78cba61Sjca case 503: 8773733520cSjca isunavail = 1; 8783733520cSjca break; 879bfd817adSflorian default: 880740dbc81Sderaadt strnvis(gerror, cp, sizeof gerror, VIS_SAFE); 881740dbc81Sderaadt warnx("Error retrieving %s: %s", origline, gerror); 882bfd817adSflorian goto cleanup_url_get; 883bfd817adSflorian } 884bfd817adSflorian 885bfd817adSflorian /* 886bfd817adSflorian * Read the rest of the header. 887bfd817adSflorian */ 888bfd817adSflorian filesize = -1; 889bfd817adSflorian 890bfd817adSflorian for (;;) { 891bb1c3b3fSnaddy if ((len = getline(&buf, &bufsize, fin)) == -1) { 89294d4d1aaSjca warnx("Receiving HTTP reply: %s", sockerror(tls)); 893bfd817adSflorian goto cleanup_url_get; 894bfd817adSflorian } 895bfd817adSflorian 896bfd817adSflorian while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) 897bfd817adSflorian buf[--len] = '\0'; 898bfd817adSflorian if (len == 0) 899bfd817adSflorian break; 900bfd817adSflorian #ifndef SMALL 901bfd817adSflorian if (debug) 902bfd817adSflorian fprintf(ttyout, "received '%s'\n", buf); 903bfd817adSflorian #endif /* !SMALL */ 904bfd817adSflorian 905bfd817adSflorian /* Look for some headers */ 906bfd817adSflorian cp = buf; 907bfd817adSflorian #define CONTENTLEN "Content-Length: " 908bfd817adSflorian if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) { 909bfd817adSflorian size_t s; 910bfd817adSflorian cp += sizeof(CONTENTLEN) - 1; 911bfd817adSflorian if ((s = strcspn(cp, " \t"))) 912bfd817adSflorian *(cp+s) = 0; 913bfd817adSflorian filesize = strtonum(cp, 0, LLONG_MAX, &errstr); 914bfd817adSflorian if (errstr != NULL) 915bfd817adSflorian goto improper; 916bfd817adSflorian #ifndef SMALL 917bfd817adSflorian if (restart_point) 918bfd817adSflorian filesize += restart_point; 919bfd817adSflorian #endif /* !SMALL */ 920bfd817adSflorian #define LOCATION "Location: " 921bfd817adSflorian } else if (isredirect && 922bfd817adSflorian strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) { 923bfd817adSflorian cp += sizeof(LOCATION) - 1; 924bfd817adSflorian /* 925bfd817adSflorian * If there is a colon before the first slash, this URI 926bfd817adSflorian * is not relative. RFC 3986 4.2 927bfd817adSflorian */ 928bfd817adSflorian if (cp[strcspn(cp, ":/")] != ':') { 929bfd817adSflorian #ifdef SMALL 930bfd817adSflorian errx(1, "Relative redirect not supported"); 931bfd817adSflorian #else /* SMALL */ 932bfd817adSflorian /* XXX doesn't handle protocol-relative URIs */ 933bfd817adSflorian if (*cp == '/') { 934bfd817adSflorian locbase = NULL; 935bfd817adSflorian cp++; 936bfd817adSflorian } else { 937bfd817adSflorian locbase = strdup(path); 938bfd817adSflorian if (locbase == NULL) 939bfd817adSflorian errx(1, "Can't allocate memory" 940bfd817adSflorian " for location base"); 941bfd817adSflorian loctail = strchr(locbase, '#'); 942bfd817adSflorian if (loctail != NULL) 943bfd817adSflorian *loctail = '\0'; 944bfd817adSflorian loctail = strchr(locbase, '?'); 945bfd817adSflorian if (loctail != NULL) 946bfd817adSflorian *loctail = '\0'; 947bfd817adSflorian loctail = strrchr(locbase, '/'); 948bfd817adSflorian if (loctail == NULL) { 949bfd817adSflorian free(locbase); 950bfd817adSflorian locbase = NULL; 951bfd817adSflorian } else 952bfd817adSflorian loctail[1] = '\0'; 953bfd817adSflorian } 954bfd817adSflorian /* Contruct URL from relative redirect */ 955bfd817adSflorian if (asprintf(&redirurl, "%s%s%s%s/%s%s", 956bfd817adSflorian scheme, full_host, 957bfd817adSflorian portnum ? ":" : "", 958bfd817adSflorian portnum ? portnum : "", 959bfd817adSflorian locbase ? locbase : "", 960bfd817adSflorian cp) == -1) 961bfd817adSflorian errx(1, "Cannot build " 962bfd817adSflorian "redirect URL"); 963bfd817adSflorian free(locbase); 964bfd817adSflorian #endif /* SMALL */ 965bfd817adSflorian } else if ((redirurl = strdup(cp)) == NULL) 966bfd817adSflorian errx(1, "Cannot allocate memory for URL"); 967bfd817adSflorian loctail = strchr(redirurl, '#'); 968bfd817adSflorian if (loctail != NULL) 969bfd817adSflorian *loctail = '\0'; 970bfd817adSflorian if (verbose) 971bfd817adSflorian fprintf(ttyout, "Redirected to %s\n", redirurl); 972535fae4eSjca ftp_close(&fin, &tls, &fd); 973bfd817adSflorian rval = url_get(redirurl, proxyenv, savefile, lastfile); 974bfd817adSflorian free(redirurl); 975bfd817adSflorian goto cleanup_url_get; 9763733520cSjca #define RETRYAFTER "Retry-After: " 9773733520cSjca } else if (isunavail && 9783733520cSjca strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) { 9793733520cSjca size_t s; 9803733520cSjca cp += sizeof(RETRYAFTER) - 1; 9813733520cSjca if ((s = strcspn(cp, " \t"))) 9823733520cSjca cp[s] = '\0'; 9833733520cSjca retryafter = strtonum(cp, 0, 0, &errstr); 9843733520cSjca if (errstr != NULL) 9853733520cSjca retryafter = -1; 9867102f6c2Sjca #define TRANSFER_ENCODING "Transfer-Encoding: " 9877102f6c2Sjca } else if (strncasecmp(cp, TRANSFER_ENCODING, 9887102f6c2Sjca sizeof(TRANSFER_ENCODING) - 1) == 0) { 9897102f6c2Sjca cp += sizeof(TRANSFER_ENCODING) - 1; 9907102f6c2Sjca cp[strcspn(cp, " \t")] = '\0'; 9917102f6c2Sjca if (strcasecmp(cp, "chunked") == 0) 9927102f6c2Sjca chunked = 1; 99349dd5d56Srobert #ifndef SMALL 99449dd5d56Srobert #define LAST_MODIFIED "Last-Modified: " 99549dd5d56Srobert } else if (strncasecmp(cp, LAST_MODIFIED, 99649dd5d56Srobert sizeof(LAST_MODIFIED) - 1) == 0) { 99749dd5d56Srobert cp += sizeof(LAST_MODIFIED) - 1; 99849dd5d56Srobert cp[strcspn(cp, "\t")] = '\0'; 99949dd5d56Srobert if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL) 100049dd5d56Srobert server_timestamps = 0; 100149dd5d56Srobert #endif /* !SMALL */ 1002bfd817adSflorian } 1003bfd817adSflorian } 1004bb1c3b3fSnaddy free(buf); 1005*a0c22864Snaddy buf = NULL; 1006bfd817adSflorian 10077102f6c2Sjca /* Content-Length should be ignored for Transfer-Encoding: chunked */ 10087102f6c2Sjca if (chunked) 10097102f6c2Sjca filesize = -1; 10107102f6c2Sjca 10113733520cSjca if (isunavail) { 10123733520cSjca if (retried || retryafter != 0) 1013740dbc81Sderaadt warnx("Error retrieving %s: 503 Service Unavailable", 1014740dbc81Sderaadt origline); 10153733520cSjca else { 10163733520cSjca if (verbose) 10173733520cSjca fprintf(ttyout, "Retrying %s\n", origline); 10183733520cSjca retried = 1; 10191e6907dcSjca ftp_close(&fin, &tls, &fd); 10203733520cSjca rval = url_get(origline, proxyenv, savefile, lastfile); 10213733520cSjca } 10223733520cSjca goto cleanup_url_get; 10233733520cSjca } 10243733520cSjca 1025bfd817adSflorian /* Open the output file. */ 1026bfd817adSflorian if (!pipeout) { 1027bfd817adSflorian #ifndef SMALL 1028bfd817adSflorian if (resume) 1029bfd817adSflorian out = open(savefile, O_CREAT | O_WRONLY | O_APPEND, 1030bfd817adSflorian 0666); 1031bfd817adSflorian else 1032bfd817adSflorian #endif /* !SMALL */ 1033bfd817adSflorian out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 1034bfd817adSflorian 0666); 10353aaa63ebSderaadt if (out == -1) { 1036bfd817adSflorian warn("Can't open %s", savefile); 1037bfd817adSflorian goto cleanup_url_get; 1038bfd817adSflorian } 1039bfd817adSflorian } else { 1040bfd817adSflorian out = fileno(stdout); 1041bfd817adSflorian #ifdef SMALL 1042bfd817adSflorian if (lastfile) { 1043bfd817adSflorian if (pledge("stdio tty", NULL) == -1) 1044bfd817adSflorian err(1, "pledge"); 1045bfd817adSflorian } 1046bfd817adSflorian #endif 1047bfd817adSflorian } 1048bfd817adSflorian 1049583394bbSjca if ((buf = malloc(buflen)) == NULL) 1050583394bbSjca errx(1, "Can't allocate memory for transfer buffer"); 1051583394bbSjca 1052bfd817adSflorian /* Trap signals */ 1053bfd817adSflorian oldintr = NULL; 1054bfd817adSflorian oldinti = NULL; 1055bfd817adSflorian if (setjmp(httpabort)) { 1056bfd817adSflorian if (oldintr) 1057bfd817adSflorian (void)signal(SIGINT, oldintr); 1058bfd817adSflorian if (oldinti) 1059bfd817adSflorian (void)signal(SIGINFO, oldinti); 1060bfd817adSflorian goto cleanup_url_get; 1061bfd817adSflorian } 1062bfd817adSflorian oldintr = signal(SIGINT, aborthttp); 1063bfd817adSflorian 1064bfd817adSflorian bytes = 0; 1065bfd817adSflorian hashbytes = mark; 1066bfd817adSflorian progressmeter(-1, path); 1067bfd817adSflorian 1068bfd817adSflorian /* Finally, suck down the file. */ 10697102f6c2Sjca oldinti = signal(SIGINFO, psummary); 10707102f6c2Sjca if (chunked) { 10712b371687Sjca error = save_chunked(fin, tls, out, buf, buflen); 10727413ba97Sjca signal(SIGINT, oldintr); 10737102f6c2Sjca signal(SIGINFO, oldinti); 10742b371687Sjca if (error == -1) 10757102f6c2Sjca goto cleanup_url_get; 10767102f6c2Sjca } else { 1077d6fcb866Sjca while ((len = fread(buf, 1, buflen, fin)) > 0) { 1078bfd817adSflorian bytes += len; 1079d6fcb866Sjca for (cp = buf; len > 0; len -= wlen, cp += wlen) { 1080d6fcb866Sjca if ((wlen = write(out, cp, len)) == -1) { 1081bfd817adSflorian warn("Writing %s", savefile); 10827413ba97Sjca signal(SIGINT, oldintr); 1083bfd817adSflorian signal(SIGINFO, oldinti); 1084bfd817adSflorian goto cleanup_url_get; 1085d6fcb866Sjca } 1086bfd817adSflorian } 1087bfd817adSflorian if (hash && !progress) { 1088bfd817adSflorian while (bytes >= hashbytes) { 1089bfd817adSflorian (void)putc('#', ttyout); 1090bfd817adSflorian hashbytes += mark; 1091bfd817adSflorian } 1092bfd817adSflorian (void)fflush(ttyout); 1093bfd817adSflorian } 1094bfd817adSflorian } 1095d6fcb866Sjca save_errno = errno; 10967413ba97Sjca signal(SIGINT, oldintr); 1097bfd817adSflorian signal(SIGINFO, oldinti); 1098bfd817adSflorian if (hash && !progress && bytes > 0) { 1099bfd817adSflorian if (bytes < mark) 1100bfd817adSflorian (void)putc('#', ttyout); 1101bfd817adSflorian (void)putc('\n', ttyout); 1102bfd817adSflorian (void)fflush(ttyout); 1103bfd817adSflorian } 1104d6fcb866Sjca if (len == 0 && ferror(fin)) { 1105d6fcb866Sjca errno = save_errno; 110694d4d1aaSjca warnx("Reading from socket: %s", sockerror(tls)); 1107bfd817adSflorian goto cleanup_url_get; 1108bfd817adSflorian } 11097102f6c2Sjca } 1110bfd817adSflorian progressmeter(1, NULL); 1111bfd817adSflorian if ( 1112bfd817adSflorian #ifndef SMALL 1113bfd817adSflorian !resume && 1114bfd817adSflorian #endif /* !SMALL */ 1115bfd817adSflorian filesize != -1 && len == 0 && bytes != filesize) { 1116bfd817adSflorian if (verbose) 1117bfd817adSflorian fputs("Read short file.\n", ttyout); 1118bfd817adSflorian goto cleanup_url_get; 1119bfd817adSflorian } 1120bfd817adSflorian 1121bfd817adSflorian if (verbose) 1122bfd817adSflorian ptransfer(0); 1123bfd817adSflorian 1124bfd817adSflorian rval = 0; 1125bfd817adSflorian goto cleanup_url_get; 1126bfd817adSflorian 1127bfd817adSflorian noftpautologin: 1128bfd817adSflorian warnx( 1129bfd817adSflorian "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); 1130bfd817adSflorian goto cleanup_url_get; 1131bfd817adSflorian 1132bfd817adSflorian improper: 1133bfd817adSflorian warnx("Improper response from %s", host); 1134bfd817adSflorian 1135bfd817adSflorian cleanup_url_get: 1136e4b576d7Sjca #ifndef SMALL 1137bfd817adSflorian free(full_host); 1138e4b576d7Sjca #endif /* !SMALL */ 1139e4b576d7Sjca #ifndef NOSSL 1140bfd817adSflorian free(sslhost); 1141bfd817adSflorian #endif /* !NOSSL */ 1142535fae4eSjca ftp_close(&fin, &tls, &fd); 114349dd5d56Srobert if (out >= 0 && out != fileno(stdout)) { 114449dd5d56Srobert #ifndef SMALL 114549dd5d56Srobert if (server_timestamps && lmt.tm_zone != 0) { 114649dd5d56Srobert ts[0].tv_nsec = UTIME_NOW; 114749dd5d56Srobert ts[1].tv_nsec = 0; 114849dd5d56Srobert setenv("TZ", lmt.tm_zone, 1); 114949dd5d56Srobert if (((ts[1].tv_sec = mktime(&lmt)) != -1) && 115049dd5d56Srobert (futimens(out, ts) == -1)) 115149dd5d56Srobert warnx("Unable to set file modification time"); 115249dd5d56Srobert } 115349dd5d56Srobert #endif /* !SMALL */ 1154bfd817adSflorian close(out); 115549dd5d56Srobert } 1156bfd817adSflorian free(buf); 11573411e43fSnaddy free(pathbuf); 1158bfd817adSflorian free(proxyhost); 1159bfd817adSflorian free(proxyurl); 1160bfd817adSflorian free(newline); 1161bfd817adSflorian free(credentials); 11627e8f63e0Syasuoka free(proxy_credentials); 1163bfd817adSflorian return (rval); 1164bfd817adSflorian } 1165bfd817adSflorian 11667102f6c2Sjca static int 11677102f6c2Sjca save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen) 11687102f6c2Sjca { 11697102f6c2Sjca 1170bb1c3b3fSnaddy char *header = NULL, *end, *cp; 11717102f6c2Sjca unsigned long chunksize; 1172bb1c3b3fSnaddy size_t hsize = 0, rlen, wlen; 11737102f6c2Sjca ssize_t written; 11747102f6c2Sjca char cr, lf; 11757102f6c2Sjca 11767102f6c2Sjca for (;;) { 1177bb1c3b3fSnaddy if (getline(&header, &hsize, fin) == -1) 11787102f6c2Sjca break; 11797102f6c2Sjca /* strip CRLF and any optional chunk extension */ 11807102f6c2Sjca header[strcspn(header, ";\r\n")] = '\0'; 11817102f6c2Sjca errno = 0; 11827102f6c2Sjca chunksize = strtoul(header, &end, 16); 11837102f6c2Sjca if (errno || header[0] == '\0' || *end != '\0' || 11847102f6c2Sjca chunksize > INT_MAX) { 11857102f6c2Sjca warnx("Invalid chunk size '%s'", header); 11867102f6c2Sjca free(header); 11877102f6c2Sjca return -1; 11887102f6c2Sjca } 11897102f6c2Sjca 11907102f6c2Sjca if (chunksize == 0) { 11917102f6c2Sjca /* We're done. Ignore optional trailer. */ 1192bb1c3b3fSnaddy free(header); 11937102f6c2Sjca return 0; 11947102f6c2Sjca } 11957102f6c2Sjca 11967102f6c2Sjca for (written = 0; chunksize != 0; chunksize -= rlen) { 11977102f6c2Sjca rlen = (chunksize < buflen) ? chunksize : buflen; 11987102f6c2Sjca rlen = fread(buf, 1, rlen, fin); 11997102f6c2Sjca if (rlen == 0) 12007102f6c2Sjca break; 12017102f6c2Sjca bytes += rlen; 12027102f6c2Sjca for (cp = buf, wlen = rlen; wlen > 0; 12037102f6c2Sjca wlen -= written, cp += written) { 12047102f6c2Sjca if ((written = write(out, cp, wlen)) == -1) { 12057102f6c2Sjca warn("Writing output file"); 1206bb1c3b3fSnaddy free(header); 12077102f6c2Sjca return -1; 12087102f6c2Sjca } 12097102f6c2Sjca } 12107102f6c2Sjca } 12117102f6c2Sjca 12127102f6c2Sjca if (rlen == 0 || 12137102f6c2Sjca fread(&cr, 1, 1, fin) != 1 || 12147102f6c2Sjca fread(&lf, 1, 1, fin) != 1) 12157102f6c2Sjca break; 12167102f6c2Sjca 12177102f6c2Sjca if (cr != '\r' || lf != '\n') { 12187102f6c2Sjca warnx("Invalid chunked encoding"); 1219bb1c3b3fSnaddy free(header); 12207102f6c2Sjca return -1; 12217102f6c2Sjca } 12227102f6c2Sjca } 1223bb1c3b3fSnaddy free(header); 12247102f6c2Sjca 12257102f6c2Sjca if (ferror(fin)) 12267102f6c2Sjca warnx("Error while reading from socket: %s", sockerror(tls)); 12277102f6c2Sjca else 12287102f6c2Sjca warnx("Invalid chunked encoding: short read"); 12297102f6c2Sjca 12307102f6c2Sjca return -1; 12317102f6c2Sjca } 12327102f6c2Sjca 1233bfd817adSflorian /* 1234bfd817adSflorian * Abort a http retrieval 1235bfd817adSflorian */ 1236bfd817adSflorian /* ARGSUSED */ 1237194d6d9dSjca static void 1238bfd817adSflorian aborthttp(int signo) 1239bfd817adSflorian { 12405fbde59fSkn const char errmsg[] = "\nfetch aborted.\n"; 1241bfd817adSflorian 1242bfd817adSflorian alarmtimer(0); 12435fbde59fSkn write(fileno(ttyout), errmsg, sizeof(errmsg) - 1); 1244bfd817adSflorian longjmp(httpabort, 1); 1245bfd817adSflorian } 1246bfd817adSflorian 1247bfd817adSflorian /* 1248bfd817adSflorian * Retrieve multiple files from the command line, transferring 1249bfd817adSflorian * files of the form "host:path", "ftp://host/path" using the 1250bfd817adSflorian * ftp protocol, and files of the form "http://host/path" using 1251bfd817adSflorian * the http protocol. 1252bfd817adSflorian * If path has a trailing "/", then return (-1); 1253bfd817adSflorian * the path will be cd-ed into and the connection remains open, 1254bfd817adSflorian * and the function will return -1 (to indicate the connection 1255bfd817adSflorian * is alive). 1256bfd817adSflorian * If an error occurs the return value will be the offset+1 in 1257bfd817adSflorian * argv[] of the file that caused a problem (i.e, argv[x] 1258bfd817adSflorian * returns x+1) 1259bfd817adSflorian * Otherwise, 0 is returned if all files retrieved successfully. 1260bfd817adSflorian */ 1261bfd817adSflorian int 1262bfd817adSflorian auto_fetch(int argc, char *argv[], char *outfile) 1263bfd817adSflorian { 1264bfd817adSflorian char *xargv[5]; 1265bfd817adSflorian char *cp, *url, *host, *dir, *file, *portnum; 1266bfd817adSflorian char *username, *pass, *pathstart; 1267bfd817adSflorian char *ftpproxy, *httpproxy; 1268bfd817adSflorian int rval, xargc, lastfile; 1269bfd817adSflorian volatile int argpos; 1270bfd817adSflorian int dirhasglob, filehasglob, oautologin; 1271bfd817adSflorian char rempath[PATH_MAX]; 1272bfd817adSflorian 1273bfd817adSflorian argpos = 0; 1274bfd817adSflorian 1275bfd817adSflorian if (setjmp(toplevel)) { 1276bfd817adSflorian if (connected) 1277bfd817adSflorian disconnect(0, NULL); 1278bfd817adSflorian return (argpos + 1); 1279bfd817adSflorian } 1280bfd817adSflorian (void)signal(SIGINT, (sig_t)intr); 1281bfd817adSflorian (void)signal(SIGPIPE, (sig_t)lostpeer); 1282bfd817adSflorian 1283bfd817adSflorian if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0') 1284bfd817adSflorian ftpproxy = NULL; 1285bfd817adSflorian if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0') 1286bfd817adSflorian httpproxy = NULL; 1287bfd817adSflorian 1288bfd817adSflorian /* 1289bfd817adSflorian * Loop through as long as there's files to fetch. 1290bfd817adSflorian */ 1291bfd817adSflorian username = pass = NULL; 1292bfd817adSflorian for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) { 1293bfd817adSflorian if (strchr(argv[argpos], ':') == NULL) 1294bfd817adSflorian break; 1295bfd817adSflorian 1296bfd817adSflorian free(username); 1297bfd817adSflorian free(pass); 1298bfd817adSflorian host = dir = file = portnum = username = pass = NULL; 1299bfd817adSflorian 1300bfd817adSflorian lastfile = (argv[argpos+1] == NULL); 1301bfd817adSflorian 1302bfd817adSflorian /* 1303bfd817adSflorian * We muck with the string, so we make a copy. 1304bfd817adSflorian */ 1305bfd817adSflorian url = strdup(argv[argpos]); 1306bfd817adSflorian if (url == NULL) 1307bfd817adSflorian errx(1, "Can't allocate memory for auto-fetch."); 1308bfd817adSflorian 1309ba6894b2Sjca if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { 1310ba6894b2Sjca if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1) 1311ba6894b2Sjca rval = argpos + 1; 1312ba6894b2Sjca continue; 1313ba6894b2Sjca } 1314ba6894b2Sjca 1315bfd817adSflorian /* 1316ba6894b2Sjca * Try HTTP URL-style arguments next. 1317bfd817adSflorian */ 1318bfd817adSflorian if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || 1319ba6894b2Sjca strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) { 1320bfd817adSflorian redirect_loop = 0; 1321f78cba61Sjca retried = 0; 1322bfd817adSflorian if (url_get(url, httpproxy, outfile, lastfile) == -1) 1323bfd817adSflorian rval = argpos + 1; 1324bfd817adSflorian continue; 1325bfd817adSflorian } 1326bfd817adSflorian 1327bfd817adSflorian /* 1328bfd817adSflorian * Try FTP URL-style arguments next. If ftpproxy is 1329bfd817adSflorian * set, use url_get() instead of standard ftp. 1330bfd817adSflorian * Finally, try host:file. 1331bfd817adSflorian */ 1332bfd817adSflorian host = url; 1333bfd817adSflorian if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 1334bfd817adSflorian char *passend, *passagain, *userend; 1335bfd817adSflorian 1336bfd817adSflorian if (ftpproxy) { 1337bfd817adSflorian if (url_get(url, ftpproxy, outfile, lastfile) == -1) 1338bfd817adSflorian rval = argpos + 1; 1339bfd817adSflorian continue; 1340bfd817adSflorian } 1341bfd817adSflorian host += sizeof(FTP_URL) - 1; 1342bfd817adSflorian dir = strchr(host, '/'); 1343bfd817adSflorian 1344bfd817adSflorian /* Look for [user:pass@]host[:port] */ 1345bfd817adSflorian 1346bfd817adSflorian /* check if we have "user:pass@" */ 1347bfd817adSflorian userend = strchr(host, ':'); 1348bfd817adSflorian passend = strchr(host, '@'); 1349bfd817adSflorian if (passend && userend && userend < passend && 1350bfd817adSflorian (!dir || passend < dir)) { 1351bfd817adSflorian username = host; 1352bfd817adSflorian pass = userend + 1; 1353bfd817adSflorian host = passend + 1; 1354bfd817adSflorian *userend = *passend = '\0'; 1355bfd817adSflorian passagain = strchr(host, '@'); 1356bfd817adSflorian if (strchr(pass, '@') != NULL || 1357bfd817adSflorian (passagain != NULL && passagain < dir)) { 1358bfd817adSflorian warnx(at_encoding_warning); 1359bfd817adSflorian username = pass = NULL; 1360bfd817adSflorian goto bad_ftp_url; 1361bfd817adSflorian } 1362bfd817adSflorian 1363bfd817adSflorian if (EMPTYSTRING(username)) { 1364bfd817adSflorian bad_ftp_url: 1365bfd817adSflorian warnx("Invalid URL: %s", argv[argpos]); 1366bfd817adSflorian rval = argpos + 1; 1367bfd817adSflorian username = pass = NULL; 1368bfd817adSflorian continue; 1369bfd817adSflorian } 1370bfd817adSflorian username = urldecode(username); 1371bfd817adSflorian pass = urldecode(pass); 1372bfd817adSflorian } 1373bfd817adSflorian 1374bfd817adSflorian /* check [host]:port, or [host] */ 1375bfd817adSflorian if (host[0] == '[') { 1376bfd817adSflorian cp = strchr(host, ']'); 1377bfd817adSflorian if (cp && (!dir || cp < dir)) { 1378bfd817adSflorian if (cp + 1 == dir || cp[1] == ':') { 1379bfd817adSflorian host++; 1380bfd817adSflorian *cp++ = '\0'; 1381bfd817adSflorian } else 1382bfd817adSflorian cp = NULL; 1383bfd817adSflorian } else 1384bfd817adSflorian cp = host; 1385bfd817adSflorian } else 1386bfd817adSflorian cp = host; 1387bfd817adSflorian 1388bfd817adSflorian /* split off host[:port] if there is */ 1389bfd817adSflorian if (cp) { 1390bfd817adSflorian portnum = strchr(cp, ':'); 1391bfd817adSflorian pathstart = strchr(cp, '/'); 1392bfd817adSflorian /* : in path is not a port # indicator */ 1393bfd817adSflorian if (portnum && pathstart && 1394bfd817adSflorian pathstart < portnum) 1395bfd817adSflorian portnum = NULL; 1396bfd817adSflorian 1397bfd817adSflorian if (!portnum) 1398bfd817adSflorian ; 1399bfd817adSflorian else { 1400bfd817adSflorian if (!dir) 1401bfd817adSflorian ; 1402bfd817adSflorian else if (portnum + 1 < dir) { 1403bfd817adSflorian *portnum++ = '\0'; 1404bfd817adSflorian /* 1405bfd817adSflorian * XXX should check if portnum 1406bfd817adSflorian * is decimal number 1407bfd817adSflorian */ 1408bfd817adSflorian } else { 1409bfd817adSflorian /* empty portnum */ 1410bfd817adSflorian goto bad_ftp_url; 1411bfd817adSflorian } 1412bfd817adSflorian } 1413bfd817adSflorian } else 1414bfd817adSflorian portnum = NULL; 1415bfd817adSflorian } else { /* classic style `host:file' */ 1416bfd817adSflorian dir = strchr(host, ':'); 1417bfd817adSflorian } 1418bfd817adSflorian if (EMPTYSTRING(host)) { 1419bfd817adSflorian rval = argpos + 1; 1420bfd817adSflorian continue; 1421bfd817adSflorian } 1422bfd817adSflorian 1423bfd817adSflorian /* 1424bfd817adSflorian * If dir is NULL, the file wasn't specified 1425bfd817adSflorian * (URL looked something like ftp://host) 1426bfd817adSflorian */ 1427bfd817adSflorian if (dir != NULL) 1428bfd817adSflorian *dir++ = '\0'; 1429bfd817adSflorian 1430bfd817adSflorian /* 1431bfd817adSflorian * Extract the file and (if present) directory name. 1432bfd817adSflorian */ 1433bfd817adSflorian if (!EMPTYSTRING(dir)) { 1434bfd817adSflorian cp = strrchr(dir, '/'); 1435bfd817adSflorian if (cp != NULL) { 1436bfd817adSflorian *cp++ = '\0'; 1437bfd817adSflorian file = cp; 1438bfd817adSflorian } else { 1439bfd817adSflorian file = dir; 1440bfd817adSflorian dir = NULL; 1441bfd817adSflorian } 1442bfd817adSflorian } 1443bfd817adSflorian #ifndef SMALL 1444bfd817adSflorian if (debug) 1445bfd817adSflorian fprintf(ttyout, 1446bfd817adSflorian "user %s:%s host %s port %s dir %s file %s\n", 1447bfd817adSflorian username, pass ? "XXXX" : NULL, host, portnum, 1448bfd817adSflorian dir, file); 1449bfd817adSflorian #endif /* !SMALL */ 1450bfd817adSflorian 1451bfd817adSflorian /* 1452bfd817adSflorian * Set up the connection. 1453bfd817adSflorian */ 1454bfd817adSflorian if (connected) 1455bfd817adSflorian disconnect(0, NULL); 1456bfd817adSflorian xargv[0] = __progname; 1457bfd817adSflorian xargv[1] = host; 1458bfd817adSflorian xargv[2] = NULL; 1459bfd817adSflorian xargc = 2; 1460bfd817adSflorian if (!EMPTYSTRING(portnum)) { 1461bfd817adSflorian xargv[2] = portnum; 1462bfd817adSflorian xargv[3] = NULL; 1463bfd817adSflorian xargc = 3; 1464bfd817adSflorian } 1465bfd817adSflorian oautologin = autologin; 1466bfd817adSflorian if (username == NULL) 1467bfd817adSflorian anonftp = 1; 1468bfd817adSflorian else { 1469bfd817adSflorian anonftp = 0; 1470bfd817adSflorian autologin = 0; 1471bfd817adSflorian } 1472bfd817adSflorian setpeer(xargc, xargv); 1473bfd817adSflorian autologin = oautologin; 1474bfd817adSflorian if (connected == 0 || 1475bfd817adSflorian (connected == 1 && autologin && (username == NULL || 1476bfd817adSflorian !ftp_login(host, username, pass)))) { 1477bfd817adSflorian warnx("Can't connect or login to host `%s'", host); 1478bfd817adSflorian rval = argpos + 1; 1479bfd817adSflorian continue; 1480bfd817adSflorian } 1481bfd817adSflorian 1482bfd817adSflorian /* Always use binary transfers. */ 1483bfd817adSflorian setbinary(0, NULL); 1484bfd817adSflorian 1485bfd817adSflorian dirhasglob = filehasglob = 0; 1486bfd817adSflorian if (doglob) { 1487bfd817adSflorian if (!EMPTYSTRING(dir) && 1488bfd817adSflorian strpbrk(dir, "*?[]{}") != NULL) 1489bfd817adSflorian dirhasglob = 1; 1490bfd817adSflorian if (!EMPTYSTRING(file) && 1491bfd817adSflorian strpbrk(file, "*?[]{}") != NULL) 1492bfd817adSflorian filehasglob = 1; 1493bfd817adSflorian } 1494bfd817adSflorian 1495bfd817adSflorian /* Change directories, if necessary. */ 1496bfd817adSflorian if (!EMPTYSTRING(dir) && !dirhasglob) { 1497bfd817adSflorian xargv[0] = "cd"; 1498bfd817adSflorian xargv[1] = dir; 1499bfd817adSflorian xargv[2] = NULL; 1500bfd817adSflorian cd(2, xargv); 1501bfd817adSflorian if (!dirchange) { 1502bfd817adSflorian rval = argpos + 1; 1503bfd817adSflorian continue; 1504bfd817adSflorian } 1505bfd817adSflorian } 1506bfd817adSflorian 1507bfd817adSflorian if (EMPTYSTRING(file)) { 1508bfd817adSflorian #ifndef SMALL 1509bfd817adSflorian rval = -1; 1510bfd817adSflorian #else /* !SMALL */ 1511bfd817adSflorian recvrequest("NLST", "-", NULL, "w", 0, 0); 1512bfd817adSflorian rval = 0; 1513bfd817adSflorian #endif /* !SMALL */ 1514bfd817adSflorian continue; 1515bfd817adSflorian } 1516bfd817adSflorian 1517bfd817adSflorian if (verbose) 1518bfd817adSflorian fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file); 1519bfd817adSflorian 1520bfd817adSflorian if (dirhasglob) { 1521bfd817adSflorian snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); 1522bfd817adSflorian file = rempath; 1523bfd817adSflorian } 1524bfd817adSflorian 1525bfd817adSflorian /* Fetch the file(s). */ 1526bfd817adSflorian xargc = 2; 1527bfd817adSflorian xargv[0] = "get"; 1528bfd817adSflorian xargv[1] = file; 1529bfd817adSflorian xargv[2] = NULL; 1530bfd817adSflorian if (dirhasglob || filehasglob) { 1531bfd817adSflorian int ointeractive; 1532bfd817adSflorian 1533bfd817adSflorian ointeractive = interactive; 1534bfd817adSflorian interactive = 0; 1535bfd817adSflorian xargv[0] = "mget"; 1536bfd817adSflorian #ifndef SMALL 1537bfd817adSflorian if (resume) { 1538bfd817adSflorian xargc = 3; 1539bfd817adSflorian xargv[1] = "-c"; 1540bfd817adSflorian xargv[2] = file; 1541bfd817adSflorian xargv[3] = NULL; 1542bfd817adSflorian } 1543bfd817adSflorian #endif /* !SMALL */ 1544bfd817adSflorian mget(xargc, xargv); 1545bfd817adSflorian interactive = ointeractive; 1546bfd817adSflorian } else { 1547bfd817adSflorian if (outfile != NULL) { 1548bfd817adSflorian xargv[2] = outfile; 1549bfd817adSflorian xargv[3] = NULL; 1550bfd817adSflorian xargc++; 1551bfd817adSflorian } 1552bfd817adSflorian #ifndef SMALL 1553bfd817adSflorian if (resume) 1554bfd817adSflorian reget(xargc, xargv); 1555bfd817adSflorian else 1556bfd817adSflorian #endif /* !SMALL */ 1557bfd817adSflorian get(xargc, xargv); 1558bfd817adSflorian } 1559bfd817adSflorian 1560bfd817adSflorian if ((code / 100) != COMPLETE) 1561bfd817adSflorian rval = argpos + 1; 1562bfd817adSflorian } 1563bfd817adSflorian if (connected && rval != -1) 1564bfd817adSflorian disconnect(0, NULL); 1565bfd817adSflorian return (rval); 1566bfd817adSflorian } 1567bfd817adSflorian 1568bfd817adSflorian char * 1569bfd817adSflorian urldecode(const char *str) 1570bfd817adSflorian { 1571bfd817adSflorian char *ret, c; 1572bfd817adSflorian int i, reallen; 1573bfd817adSflorian 1574bfd817adSflorian if (str == NULL) 1575bfd817adSflorian return NULL; 1576bfd817adSflorian if ((ret = malloc(strlen(str)+1)) == NULL) 1577bfd817adSflorian err(1, "Can't allocate memory for URL decoding"); 1578bfd817adSflorian for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) { 1579bfd817adSflorian c = str[i]; 1580bfd817adSflorian if (c == '+') { 1581bfd817adSflorian *ret = ' '; 1582bfd817adSflorian continue; 1583bfd817adSflorian } 1584bfd817adSflorian 1585bfd817adSflorian /* Cannot use strtol here because next char 1586bfd817adSflorian * after %xx may be a digit. 1587bfd817adSflorian */ 1588bfd817adSflorian if (c == '%' && isxdigit((unsigned char)str[i+1]) && 1589bfd817adSflorian isxdigit((unsigned char)str[i+2])) { 1590bfd817adSflorian *ret = hextochar(&str[i+1]); 1591bfd817adSflorian i+=2; 1592bfd817adSflorian continue; 1593bfd817adSflorian } 1594bfd817adSflorian *ret = c; 1595bfd817adSflorian } 1596bfd817adSflorian *ret = '\0'; 1597bfd817adSflorian 1598bfd817adSflorian return ret-reallen; 1599bfd817adSflorian } 1600bfd817adSflorian 1601194d6d9dSjca static char * 1602bfd817adSflorian recode_credentials(const char *userinfo) 1603bfd817adSflorian { 1604bfd817adSflorian char *ui, *creds; 1605bfd817adSflorian size_t ulen, credsize; 1606bfd817adSflorian 1607bfd817adSflorian /* url-decode the user and pass */ 1608bfd817adSflorian ui = urldecode(userinfo); 1609bfd817adSflorian 1610bfd817adSflorian ulen = strlen(ui); 1611bfd817adSflorian credsize = (ulen + 2) / 3 * 4 + 1; 1612bfd817adSflorian creds = malloc(credsize); 1613bfd817adSflorian if (creds == NULL) 1614bfd817adSflorian errx(1, "out of memory"); 1615bfd817adSflorian if (b64_ntop(ui, ulen, creds, credsize) == -1) 1616bfd817adSflorian errx(1, "error in base64 encoding"); 1617bfd817adSflorian free(ui); 1618bfd817adSflorian return (creds); 1619bfd817adSflorian } 1620bfd817adSflorian 1621194d6d9dSjca static char 1622bfd817adSflorian hextochar(const char *str) 1623bfd817adSflorian { 1624bfd817adSflorian unsigned char c, ret; 1625bfd817adSflorian 1626bfd817adSflorian c = str[0]; 1627bfd817adSflorian ret = c; 1628bfd817adSflorian if (isalpha(c)) 1629bfd817adSflorian ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 1630bfd817adSflorian else 1631bfd817adSflorian ret -= '0'; 1632bfd817adSflorian ret *= 16; 1633bfd817adSflorian 1634bfd817adSflorian c = str[1]; 1635bfd817adSflorian ret += c; 1636bfd817adSflorian if (isalpha(c)) 1637bfd817adSflorian ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 1638bfd817adSflorian else 1639bfd817adSflorian ret -= '0'; 1640bfd817adSflorian return ret; 1641bfd817adSflorian } 1642bfd817adSflorian 1643bfd817adSflorian int 1644bfd817adSflorian isurl(const char *p) 1645bfd817adSflorian { 1646bfd817adSflorian 1647bfd817adSflorian if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 || 1648bfd817adSflorian strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || 1649bfd817adSflorian #ifndef NOSSL 1650bfd817adSflorian strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 || 1651bfd817adSflorian #endif /* !NOSSL */ 1652bfd817adSflorian strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 || 1653bfd817adSflorian strstr(p, ":/")) 1654bfd817adSflorian return (1); 1655bfd817adSflorian return (0); 1656bfd817adSflorian } 1657bfd817adSflorian 1658b237d516Sjca #ifndef SMALL 1659b237d516Sjca static int 1660b237d516Sjca ftp_printf(FILE *fp, const char *fmt, ...) 1661b237d516Sjca { 1662b237d516Sjca va_list ap; 1663b237d516Sjca int ret; 1664b237d516Sjca 1665b237d516Sjca va_start(ap, fmt); 1666b237d516Sjca ret = vfprintf(fp, fmt, ap); 1667b237d516Sjca va_end(ap); 1668b237d516Sjca 1669b237d516Sjca if (debug) { 1670b237d516Sjca va_start(ap, fmt); 1671b237d516Sjca vfprintf(ttyout, fmt, ap); 1672b237d516Sjca va_end(ap); 1673b237d516Sjca } 1674b237d516Sjca 1675b237d516Sjca return ret; 1676b237d516Sjca } 1677b237d516Sjca #endif /* !SMALL */ 1678b237d516Sjca 1679194d6d9dSjca static void 1680583394bbSjca ftp_close(FILE **fin, struct tls **tls, int *fd) 1681535fae4eSjca { 1682535fae4eSjca #ifndef NOSSL 1683535fae4eSjca int ret; 1684535fae4eSjca 1685535fae4eSjca if (*tls != NULL) { 1686535fae4eSjca if (tls_session_fd != -1) 1687535fae4eSjca dprintf(STDERR_FILENO, "tls session resumed: %s\n", 1688535fae4eSjca tls_conn_session_resumed(*tls) ? "yes" : "no"); 1689535fae4eSjca do { 1690535fae4eSjca ret = tls_close(*tls); 1691535fae4eSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 1692535fae4eSjca tls_free(*tls); 1693535fae4eSjca *tls = NULL; 1694535fae4eSjca } 169594d4d1aaSjca if (*fd != -1) { 169694d4d1aaSjca close(*fd); 169794d4d1aaSjca *fd = -1; 169894d4d1aaSjca } 1699535fae4eSjca #endif 1700535fae4eSjca if (*fin != NULL) { 1701535fae4eSjca fclose(*fin); 1702535fae4eSjca *fin = NULL; 1703535fae4eSjca } 1704535fae4eSjca } 170594d4d1aaSjca 1706194d6d9dSjca static const char * 170794d4d1aaSjca sockerror(struct tls *tls) 170894d4d1aaSjca { 170994d4d1aaSjca int save_errno = errno; 171094d4d1aaSjca #ifndef NOSSL 171194d4d1aaSjca if (tls != NULL) { 171294d4d1aaSjca const char *tlserr = tls_error(tls); 171394d4d1aaSjca if (tlserr != NULL) 171494d4d1aaSjca return tlserr; 171594d4d1aaSjca } 171694d4d1aaSjca #endif 171794d4d1aaSjca return strerror(save_errno); 1718535fae4eSjca } 1719535fae4eSjca 1720bfd817adSflorian #ifndef NOSSL 1721194d6d9dSjca static int 1722bfd817adSflorian proxy_connect(int socket, char *host, char *cookie) 1723bfd817adSflorian { 1724bfd817adSflorian int l; 1725bfd817adSflorian char buf[1024]; 17268a4b8ac2Syasuoka char *connstr, *hosttail, *port; 1727bfd817adSflorian 1728bfd817adSflorian if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && 1729bfd817adSflorian (hosttail[1] == '\0' || hosttail[1] == ':')) { 1730bfd817adSflorian host++; 1731bfd817adSflorian *hosttail++ = '\0'; 1732bfd817adSflorian } else 1733bfd817adSflorian hosttail = host; 1734bfd817adSflorian 1735bfd817adSflorian port = strrchr(hosttail, ':'); /* find portnum */ 1736bfd817adSflorian if (port != NULL) 1737bfd817adSflorian *port++ = '\0'; 1738bfd817adSflorian if (!port) 1739bfd817adSflorian port = "443"; 1740bfd817adSflorian 1741bfd817adSflorian if (cookie) { 1742bfd817adSflorian l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n" 1743bfd817adSflorian "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n", 1744bfd817adSflorian host, port, cookie, HTTP_USER_AGENT); 1745bfd817adSflorian } else { 1746bfd817adSflorian l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n", 1747bfd817adSflorian host, port, HTTP_USER_AGENT); 1748bfd817adSflorian } 1749bfd817adSflorian 1750bfd817adSflorian if (l == -1) 1751bfd817adSflorian errx(1, "Could not allocate memory to assemble connect string!"); 1752bfd817adSflorian #ifndef SMALL 1753bfd817adSflorian if (debug) 1754bfd817adSflorian printf("%s", connstr); 1755bfd817adSflorian #endif /* !SMALL */ 1756bfd817adSflorian if (write(socket, connstr, l) != l) 1757bfd817adSflorian err(1, "Could not send connect string"); 17588a4b8ac2Syasuoka read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */ 1759bfd817adSflorian free(connstr); 1760bfd817adSflorian return(200); 1761bfd817adSflorian } 176294d4d1aaSjca 1763194d6d9dSjca static int 176494d4d1aaSjca stdio_tls_write_wrapper(void *arg, const char *buf, int len) 176594d4d1aaSjca { 176694d4d1aaSjca struct tls *tls = arg; 176794d4d1aaSjca ssize_t ret; 176894d4d1aaSjca 176994d4d1aaSjca do { 177094d4d1aaSjca ret = tls_write(tls, buf, len); 177194d4d1aaSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 177294d4d1aaSjca 177394d4d1aaSjca return ret; 177494d4d1aaSjca } 177594d4d1aaSjca 1776194d6d9dSjca static int 177794d4d1aaSjca stdio_tls_read_wrapper(void *arg, char *buf, int len) 177894d4d1aaSjca { 177994d4d1aaSjca struct tls *tls = arg; 178094d4d1aaSjca ssize_t ret; 178194d4d1aaSjca 178294d4d1aaSjca do { 178394d4d1aaSjca ret = tls_read(tls, buf, len); 178494d4d1aaSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 178594d4d1aaSjca 178694d4d1aaSjca return ret; 178794d4d1aaSjca } 1788bfd817adSflorian #endif /* !NOSSL */ 1789