there was tiny error I created. -Luke
On Wed, Jun 3, 2020 at 2:24 PM Luke Small <lukensm...@gmail.com> wrote: > There! It doesn't use an unveil list. It has 2 dry runs as proposed. > It could just have a dry run to see if it goes into interactive mode > and then unveil as we go! but I like to see all the unveil calls before > the ftp output statements myself! > -Luke > > > On Wed, Jun 3, 2020 at 11:30 AM Luke Small <lukensm...@gmail.com> wrote: > >> Or you could have 2 dry runs. One to merely see that it won't head into >> interactive mode >> and a second one to start the unveiling directly in fetch.c. Unless >> unveil itself will >> have too many entries! >> -Luke >> >> >> On Wed, Jun 3, 2020 at 11:12 AM Luke Small <lukensm...@gmail.com> wrote: >> >>> I figure if it took up that much stack space from before, it'd start >>> needing to >>> dang near run the stack into on-disk virtual memory anyway. At that >>> point, >>> it'd perhaps be a better design choice to break up your ftp calls into >>> slightly >>> smaller chunks to avoid massively poor performance, yeah? LOL >>> >>> If you're really worried about it, perhaps you could put in a goto which >>> jumps >>> over the offending part, when argc is so massive that >>> it would cause such a thing...Hmmm. >>> -Luke >>> >>> >>> On Wed, Jun 3, 2020 at 10:39 AM Theo de Raadt <dera...@openbsd.org> >>> wrote: >>> >>>> You really don't get it. >>>> >>>> + unveil_list = calloc(2 * argc, sizeof(char*)); >>>> >>>> Imagine argc is 10000. >>>> >>>> + for (i = 2 * argc - 2; i >= 0; i -= 2) { >>>> + if (unveil_list[i]) { >>>> + if (unveil(unveil_list[i], "r") >>>> == -1) >>>> ... >>>> + if (unveil_list[i | 1]) { >>>> + if (unveil(unveil_list[i | 1], >>>> "cw") == -1) >>>> + err(1, "unveil"); >>>> ... >>>> >>>> >>>> E2BIG The addition of path would exceed the >>>> per-process >>>> limit for unveiled paths. >>>> >>>> >>>> Great, under fairly normal usage ftp aborts with an error. >>>> >>>> Since you start with up to 8 others, it looks like this limit is easily >>>> hit at around 120 filenames. >>>> >>>> So ftp simply fails to perform the task it is designed for. >>>> >>>> Your proposal is to break the command. >>>> >>>>
Common subdirectories: /usr/src/usr.bin/ftp/CVS and /home/luke/ftp/CVS diff -uNp /usr/src/usr.bin/ftp/extern.h /home/luke/ftp/extern.h --- /usr/src/usr.bin/ftp/extern.h Thu May 16 07:44:17 2019 +++ /home/luke/ftp/extern.h Wed Jun 3 13:41:49 2020 @@ -69,6 +69,7 @@ void abortrecv(int); void alarmtimer(int); int another(int *, char ***, const char *); int auto_fetch(int, char **, char *); +int auto_fetch_u(int, char **, char *, int, int); void blkfree(char **); void cdup(int, char **); void cmdabort(int); diff -uNp /usr/src/usr.bin/ftp/fetch.c /home/luke/ftp/fetch.c --- /usr/src/usr.bin/ftp/fetch.c Fri Feb 21 19:00:07 2020 +++ /home/luke/ftp/fetch.c Wed Jun 3 15:51:45 2020 @@ -68,8 +68,10 @@ struct tls; #include "ftp_var.h" #include "cmds.h" -static int file_get(const char *, const char *); +//~ static int file_get(const char *, const char *); +static int file_get_u(const char *, const char *, int, int); static int url_get(const char *, const char *, const char *, int); +static int url_get_u(const char *, const char *, const char *, int, int, int); static int save_chunked(FILE *, struct tls *, int , char *, size_t); static void aborthttp(int); static void abortfile(int); @@ -186,8 +188,14 @@ tooslow(int signo) * Copy a local file (used by the OpenBSD installer). * Returns -1 on failure, 0 on success */ +//~ static int +//~ file_get(const char *path, const char *outfile) +//~ { + //~ return file_get_u(path, outfile, 1, 0); +//~ } + static int -file_get(const char *path, const char *outfile) +file_get_u(const char *path, const char *outfile, int ready, int tout) { struct stat st; int fd, out, rval = -1, save_errno; @@ -200,17 +208,28 @@ file_get(const char *path, const char *outfile) direction = "received"; - fd = open(path, O_RDONLY); - if (fd == -1) { - warn("Can't open file %s", path); - return -1; - } + if (ready) { + + fd = open(path, O_RDONLY); + if (fd == -1) { + warn("Can't open file %s", path); + return -1; + } - if (fstat(fd, &st) == -1) - filesize = -1; - else - filesize = st.st_size; + if (fstat(fd, &st) == -1) + filesize = -1; + else + filesize = st.st_size; + } else { + if (unveil(path, "r") == -1) + err(1, "unveil"); + dprintf(tout, "unveil(%s, \"r\")\n", path); + if (pipeout) + return 0; + } + + if (outfile != NULL) savefile = outfile; else { @@ -220,6 +239,14 @@ file_get(const char *path, const char *outfile) savefile = basename(path); } + if (!ready) { + if (unveil(savefile, "cw") == -1) + err(1, "unveil"); + + dprintf(tout, "unveil(%s, \"cw\")\n", savefile); + return 0; + } + if (EMPTYSTRING(savefile)) { warnx("No filename after directory (use -o): %s", path); goto cleanup_copy; @@ -308,6 +335,12 @@ cleanup_copy: static int url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile) { + return url_get_u(origline, proxyenv, outfile, lastfile, 1, 0); +} + +static int +url_get_u(const char *origline, const char *proxyenv, const char *outfile, int lastfile, int ready, int tout) +{ char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; char *epath, *redirurl, *loctail, *h, *p, gerror[200]; @@ -415,7 +448,15 @@ noslash: else savefile = basename(path); } + + if (!ready) { + if (unveil(savefile, "cw") == -1) + err(1, "unveil"); + dprintf(tout, "unveil(%s, \"cw\")\n", savefile); + return (0); + } + if (EMPTYSTRING(savefile)) { if (isftpurl) goto noftpautologin; @@ -1222,6 +1263,12 @@ abortfile(int signo) int auto_fetch(int argc, char *argv[], char *outfile) { + return auto_fetch_u(argc, argv, outfile, 1, 0); +} + +int +auto_fetch_u(int argc, char *argv[], char *outfile, int ready, int tout) +{ char *xargv[5]; char *cp, *url, *host, *dir, *file, *portnum; char *username, *pass, *pathstart; @@ -1268,21 +1315,32 @@ auto_fetch(int argc, char *argv[], char *outfile) errx(1, "Can't allocate memory for auto-fetch."); if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { - if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1) + if (ready < 0) + continue; + if (file_get_u(url + sizeof(FILE_URL) - 1, outfile, ready, tout) == -1) rval = argpos + 1; continue; + if (!ready) + continue; } + + if (!ready && pipeout) + continue; /* * Try HTTP URL-style arguments next. */ if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) { + if (ready < 0) + continue; redirect_loop = 0; retried = 0; - if (url_get(url, httpproxy, outfile, lastfile) == -1) + if (url_get_u(url, httpproxy, outfile, lastfile, ready, tout) == -1) rval = argpos + 1; continue; + if (!ready) + continue; } /* @@ -1295,9 +1353,14 @@ auto_fetch(int argc, char *argv[], char *outfile) char *passend, *passagain, *userend; if (ftpproxy) { - if (url_get(url, ftpproxy, outfile, lastfile) == -1) + if (ready < 0) + continue; + if (url_get_u(url, ftpproxy, outfile, lastfile, + ready, tout) == -1) rval = argpos + 1; continue; + if (!ready) + continue; } host += sizeof(FTP_URL) - 1; dir = strchr(host, '/'); @@ -1376,6 +1439,12 @@ bad_ftp_url: } else { /* classic style `host:file' */ dir = strchr(host, ':'); } + + if (ready < 1) { + rval = -2; + continue; + } + if (EMPTYSTRING(host)) { rval = argpos + 1; continue; diff -uNp /usr/src/usr.bin/ftp/main.c /home/luke/ftp/main.c --- /usr/src/usr.bin/ftp/main.c Tue Feb 11 12:41:39 2020 +++ /home/luke/ftp/main.c Wed Jun 3 14:14:13 2020 @@ -81,6 +81,10 @@ #include "cmds.h" #include "ftp_var.h" +#ifndef SMALL +#include "pathnames.h" +#endif + int connect_timeout; #ifndef NOSSL @@ -110,7 +114,7 @@ struct tls_config *tls_config; int tls_session_fd = -1; static void -process_ssl_options(char *cp) +process_ssl_options(char *cp, char *ca_file, char *ca_path) { const char *errstr; long long depth; @@ -124,6 +128,7 @@ process_ssl_options(char *cp) if (tls_config_set_ca_file(tls_config, str) != 0) errx(1, "tls ca file failed: %s", tls_config_error(tls_config)); + strlcpy(ca_file, str, PATH_MAX + 1); break; case SSL_CAPATH: if (str == NULL) @@ -131,6 +136,7 @@ process_ssl_options(char *cp) if (tls_config_set_ca_path(tls_config, str) != 0) errx(1, "tls ca path failed: %s", tls_config_error(tls_config)); + strlcpy(ca_path, str, PATH_MAX + 1); break; case SSL_CIPHERS: if (str == NULL) @@ -188,7 +194,7 @@ int pipeout; int main(volatile int argc, char *argv[]) { - int ch, rval; + int ch, rval, i; #ifndef SMALL int top; #endif @@ -197,7 +203,24 @@ main(volatile int argc, char *argv[]) char *outfile = NULL; const char *errstr; int dumb_terminal = 0; - + int temp_stdout; + int temp_stderr; +#ifndef SMALL + char *shellp; + char *ca_file; + char *ca_path; + char *ca_temp; + char *ca_temp2; + ca_file = malloc(PATH_MAX + 1); + if (ca_file == NULL) + err(1, "malloc"); + *ca_file = '\0'; + + ca_path = malloc(PATH_MAX + 1); + if (ca_path == NULL) + err(1, "malloc"); + *ca_path = '\0'; +#endif ftpport = "ftp"; httpport = "http"; #ifndef NOSSL @@ -409,7 +432,7 @@ main(volatile int argc, char *argv[]) case 'S': #ifndef NOSSL - process_ssl_options(optarg); + process_ssl_options(optarg, ca_file, ca_path); #endif /* !NOSSL */ break; @@ -482,30 +505,146 @@ main(volatile int argc, char *argv[]) setttywidth(0); (void)signal(SIGWINCH, setttywidth); + if ( !(argc > 0 && isurl(argv[0])) ) { + free(ca_file); + free(ca_path); + ca_file = NULL; + ca_path = NULL; + } + if (argc > 0) { if (isurl(argv[0])) { if (pipeout) { #ifndef SMALL - if (pledge("stdio rpath dns tty inet proc exec fattr", + if (pledge("stdio rpath dns tty inet proc exec fattr unveil", NULL) == -1) err(1, "pledge"); #else - if (pledge("stdio rpath dns tty inet fattr", + if (pledge("stdio rpath dns tty inet fattr unveil", NULL) == -1) err(1, "pledge"); #endif } else { #ifndef SMALL - if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", + if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr unveil", NULL) == -1) err(1, "pledge"); #else - if (pledge("stdio rpath wpath cpath dns tty inet fattr", + if (pledge("stdio rpath wpath cpath dns tty inet fattr unveil", NULL) == -1) err(1, "pledge"); #endif } + + + /* suppress autofetch() error statements */ + + i = open("/dev/null", O_WRONLY); + if (i == -1) + err(1, "can't open /dev/null"); + + temp_stdout = dup(STDOUT_FILENO); + temp_stderr = dup(STDERR_FILENO); + + if(dup2(i, STDOUT_FILENO) < 0) + err(1, "dup2(i, STDOUT_FILENO)"); + + if(dup2(i, STDERR_FILENO) < 0) + err(1, "dup2(i, STDERR_FILENO)"); + + + + /* autofetch() dry run to populate unveil_list */ + rval = auto_fetch_u(argc, argv, outfile, -1, 0); + + if (rval != -2) + rval = auto_fetch_u(argc, argv, outfile, 0, temp_stderr); + + + + /* resume autofetch() error statements */ + if(dup2(temp_stderr, STDERR_FILENO) < 0) { + dprintf(temp_stderr, + "dup2(temp_stderr, STDERR_FILENO)\n"); + exit(1); + } + + if(dup2(temp_stdout, STDOUT_FILENO) < 0) + err(1, "dup2(temp_stdout, STDOUT_FILENO)"); + + close(i); + + + /* skip unveil */ + if (rval == -2) + goto unveil_cleanup; + +#ifndef SMALL + /* exerpt from cmds.c; needs pathnames.h */ + shellp = getenv("SHELL"); + if (shellp == NULL || *shellp == '\0') + shellp = _PATH_BSHELL; + + if (unveil(shellp, "x") == -1) + err(1, "unveil"); + + fprintf(ttyout, "unveil(%s, \"x\")\n", shellp); + + if (*ca_file || *ca_path) { + + ca_temp2 = malloc(PATH_MAX + 1); + if (ca_temp2 == NULL) + err(1, "malloc"); + i = 1; + do { + if (i) + ca_temp = ca_file; + else + ca_temp = ca_path; + if (*ca_temp == '\0') + continue; + if (unveil(ca_temp, "r") == -1) + err(1, "unveil"); + + fprintf(ttyout, + "unveil(%s, \"r\")\n", ca_temp); + + if (realpath(ca_temp, ca_temp2) == NULL) + err(1, "realpath"); + if (strcmp(ca_temp, ca_temp2)) { + + if (unveil(ca_temp2, "r") == -1) + err(1, "unveil"); + + fprintf(ttyout, + "unveil(%s, \"r\")\n", ca_temp2); + } + } while (i--); + + free(ca_temp2); + + } else { + if (unveil("/etc/ssl/cert.pem", "r") == -1) + err(1, "unveil"); + + fprintf(ttyout, + "unveil(/etc/ssl/cert.pem, \"r\")\n"); + } +#endif + + + if (unveil(NULL, NULL) == -1) + err(1, "unveil"); + + unveil_cleanup: + +#ifndef SMALL + free(ca_file); + free(ca_path); + ca_file = NULL; + ca_path = NULL; +#endif rval = auto_fetch(argc, argv, outfile); if (rval >= 0) /* -1 == connected and cd-ed */ exit(rval);
/* $OpenBSD: main.c,v 1.131 2020/02/11 18:41:39 deraadt Exp $ */ /* $NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $ */ /* * Copyright (C) 1997 and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1985, 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * FTP User Program -- Command Interface. */ #include <sys/types.h> #include <sys/socket.h> #include <ctype.h> #include <err.h> #include <fcntl.h> #include <netdb.h> #include <pwd.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <tls.h> #include "cmds.h" #include "ftp_var.h" #ifndef SMALL #include "pathnames.h" #endif int connect_timeout; #ifndef NOSSL char * const ssl_verify_opts[] = { #define SSL_CAFILE 0 "cafile", #define SSL_CAPATH 1 "capath", #define SSL_CIPHERS 2 "ciphers", #define SSL_DONTVERIFY 3 "dont", #define SSL_DOVERIFY 4 "do", #define SSL_VERIFYDEPTH 5 "depth", #define SSL_MUSTSTAPLE 6 "muststaple", #define SSL_NOVERIFYTIME 7 "noverifytime", #define SSL_SESSION 8 "session", NULL }; struct tls_config *tls_config; int tls_session_fd = -1; static void process_ssl_options(char *cp, char *ca_file, char *ca_path) { const char *errstr; long long depth; char *str; while (*cp) { switch (getsubopt(&cp, ssl_verify_opts, &str)) { case SSL_CAFILE: if (str == NULL) errx(1, "missing CA file"); if (tls_config_set_ca_file(tls_config, str) != 0) errx(1, "tls ca file failed: %s", tls_config_error(tls_config)); strlcpy(ca_file, str, PATH_MAX + 1); break; case SSL_CAPATH: if (str == NULL) errx(1, "missing CA directory path"); if (tls_config_set_ca_path(tls_config, str) != 0) errx(1, "tls ca path failed: %s", tls_config_error(tls_config)); strlcpy(ca_path, str, PATH_MAX + 1); break; case SSL_CIPHERS: if (str == NULL) errx(1, "missing cipher list"); if (tls_config_set_ciphers(tls_config, str) != 0) errx(1, "tls ciphers failed: %s", tls_config_error(tls_config)); break; case SSL_DONTVERIFY: tls_config_insecure_noverifycert(tls_config); tls_config_insecure_noverifyname(tls_config); break; case SSL_DOVERIFY: tls_config_verify(tls_config); break; case SSL_VERIFYDEPTH: if (str == NULL) errx(1, "missing depth"); depth = strtonum(str, 0, INT_MAX, &errstr); if (errstr) errx(1, "certificate validation depth is %s", errstr); tls_config_set_verify_depth(tls_config, (int)depth); break; case SSL_MUSTSTAPLE: tls_config_ocsp_require_stapling(tls_config); break; case SSL_NOVERIFYTIME: tls_config_insecure_noverifytime(tls_config); break; case SSL_SESSION: if (str == NULL) errx(1, "missing session file"); if ((tls_session_fd = open(str, O_RDWR|O_CREAT, 0600)) == -1) err(1, "failed to open or create session file " "'%s'", str); if (tls_config_set_session_fd(tls_config, tls_session_fd) == -1) errx(1, "failed to set session: %s", tls_config_error(tls_config)); break; default: errx(1, "unknown -S suboption `%s'", suboptarg ? suboptarg : ""); /* NOTREACHED */ } } } #endif /* !NOSSL */ int family = PF_UNSPEC; int pipeout; int main(volatile int argc, char *argv[]) { int ch, rval, i; #ifndef SMALL int top; #endif struct passwd *pw = NULL; char *cp, homedir[PATH_MAX]; char *outfile = NULL; const char *errstr; int dumb_terminal = 0; int temp_stdout; int temp_stderr; #ifndef SMALL char *shellp; char *ca_file; char *ca_path; char *ca_temp; char *ca_temp2; ca_file = malloc(PATH_MAX + 1); if (ca_file == NULL) err(1, "malloc"); *ca_file = '\0'; ca_path = malloc(PATH_MAX + 1); if (ca_path == NULL) err(1, "malloc"); *ca_path = '\0'; #endif ftpport = "ftp"; httpport = "http"; #ifndef NOSSL httpsport = "https"; #endif /* !NOSSL */ gateport = getenv("FTPSERVERPORT"); if (gateport == NULL || *gateport == '\0') gateport = "ftpgate"; doglob = 1; interactive = 1; autologin = 1; passivemode = 1; activefallback = 1; preserve = 1; verbose = 0; progress = 0; gatemode = 0; #ifndef NOSSL cookiefile = NULL; #endif /* NOSSL */ #ifndef SMALL editing = 0; el = NULL; hist = NULL; resume = 0; srcaddr = NULL; marg_sl = sl_init(); #endif /* !SMALL */ mark = HASHBYTES; epsv4 = 1; epsv4bad = 0; /* Set default operation mode based on FTPMODE environment variable */ if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') { if (strcmp(cp, "passive") == 0) { passivemode = 1; activefallback = 0; } else if (strcmp(cp, "active") == 0) { passivemode = 0; activefallback = 0; } else if (strcmp(cp, "gate") == 0) { gatemode = 1; } else if (strcmp(cp, "auto") == 0) { passivemode = 1; activefallback = 1; } else warnx("unknown FTPMODE: %s. Using defaults", cp); } if (strcmp(__progname, "gate-ftp") == 0) gatemode = 1; gateserver = getenv("FTPSERVER"); if (gateserver == NULL) gateserver = ""; if (gatemode) { if (*gateserver == '\0') { warnx( "Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp"); gatemode = 0; } } cp = getenv("TERM"); dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") || !strcmp(cp, "emacs") || !strcmp(cp, "su")); fromatty = isatty(fileno(stdin)); if (fromatty) { verbose = 1; /* verbose if from a tty */ #ifndef SMALL if (!dumb_terminal) editing = 1; /* editing mode on if tty is usable */ #endif /* !SMALL */ } ttyout = stdout; if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc()) progress = 1; /* progress bar on if tty is usable */ #ifndef NOSSL cookiefile = getenv("http_cookies"); if (tls_init() != 0) errx(1, "tls init failed"); if (tls_config == NULL) { tls_config = tls_config_new(); if (tls_config == NULL) errx(1, "tls config failed"); if (tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL) != 0) errx(1, "tls set protocols failed: %s", tls_config_error(tls_config)); if (tls_config_set_ciphers(tls_config, "legacy") != 0) errx(1, "tls set ciphers failed: %s", tls_config_error(tls_config)); } #endif /* !NOSSL */ httpuseragent = NULL; while ((ch = getopt(argc, argv, "46AaCc:dD:EeN:gik:Mmno:pP:r:S:s:tU:vVw:")) != -1) { switch (ch) { case '4': family = PF_INET; break; case '6': family = PF_INET6; break; case 'A': activefallback = 0; passivemode = 0; break; case 'N': setprogname(optarg); break; case 'a': anonftp = 1; break; case 'C': #ifndef SMALL resume = 1; #endif /* !SMALL */ break; case 'c': #ifndef SMALL cookiefile = optarg; #endif /* !SMALL */ break; case 'D': action = optarg; break; case 'd': #ifndef SMALL options |= SO_DEBUG; debug++; #endif /* !SMALL */ break; case 'E': epsv4 = 0; break; case 'e': #ifndef SMALL editing = 0; #endif /* !SMALL */ break; case 'g': doglob = 0; break; case 'i': interactive = 0; break; case 'k': keep_alive_timeout = strtonum(optarg, 0, INT_MAX, &errstr); if (errstr != NULL) { warnx("keep alive amount is %s: %s", errstr, optarg); usage(); } break; case 'M': progress = 0; break; case 'm': progress = -1; break; case 'n': autologin = 0; break; case 'o': outfile = optarg; if (*outfile == '\0') { pipeout = 0; outfile = NULL; ttyout = stdout; } else { pipeout = strcmp(outfile, "-") == 0; ttyout = pipeout ? stderr : stdout; } break; case 'p': passivemode = 1; activefallback = 0; break; case 'P': ftpport = optarg; break; case 'r': retry_connect = strtonum(optarg, 0, INT_MAX, &errstr); if (errstr != NULL) { warnx("retry amount is %s: %s", errstr, optarg); usage(); } break; case 'S': #ifndef NOSSL process_ssl_options(optarg, ca_file, ca_path); #endif /* !NOSSL */ break; case 's': #ifndef SMALL srcaddr = optarg; #endif /* !SMALL */ break; case 't': trace = 1; break; #ifndef SMALL case 'U': free (httpuseragent); if (strcspn(optarg, "\r\n") != strlen(optarg)) errx(1, "Invalid User-Agent: %s.", optarg); if (asprintf(&httpuseragent, "User-Agent: %s", optarg) == -1) errx(1, "Can't allocate memory for HTTP(S) " "User-Agent"); break; #endif /* !SMALL */ case 'v': verbose = 1; break; case 'V': verbose = 0; break; case 'w': connect_timeout = strtonum(optarg, 0, 200, &errstr); if (errstr) errx(1, "-w: %s", errstr); break; default: usage(); } } argc -= optind; argv += optind; #ifndef NOSSL cookie_load(); #endif /* !NOSSL */ if (httpuseragent == NULL) httpuseragent = HTTP_USER_AGENT; cpend = 0; /* no pending replies */ proxy = 0; /* proxy not active */ crflag = 1; /* strip c.r. on ascii gets */ sendport = -1; /* not using ports */ /* * Set up the home directory in case we're globbing. */ cp = getlogin(); if (cp != NULL) { pw = getpwnam(cp); } if (pw == NULL) pw = getpwuid(getuid()); if (pw != NULL) { (void)strlcpy(homedir, pw->pw_dir, sizeof homedir); home = homedir; } setttywidth(0); (void)signal(SIGWINCH, setttywidth); if ( !(argc > 0 && isurl(argv[0])) ) { free(ca_file); free(ca_path); ca_file = NULL; ca_path = NULL; } if (argc > 0) { if (isurl(argv[0])) { if (pipeout) { #ifndef SMALL if (pledge("stdio rpath dns tty inet proc exec fattr unveil", NULL) == -1) err(1, "pledge"); #else if (pledge("stdio rpath dns tty inet fattr unveil", NULL) == -1) err(1, "pledge"); #endif } else { #ifndef SMALL if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr unveil", NULL) == -1) err(1, "pledge"); #else if (pledge("stdio rpath wpath cpath dns tty inet fattr unveil", NULL) == -1) err(1, "pledge"); #endif } /* suppress autofetch() error statements */ i = open("/dev/null", O_WRONLY); if (i == -1) err(1, "can't open /dev/null"); temp_stdout = dup(STDOUT_FILENO); temp_stderr = dup(STDERR_FILENO); if(dup2(i, STDOUT_FILENO) < 0) err(1, "dup2(i, STDOUT_FILENO)"); if(dup2(i, STDERR_FILENO) < 0) err(1, "dup2(i, STDERR_FILENO)"); /* autofetch() dry run to populate unveil_list */ rval = auto_fetch_u(argc, argv, outfile, -1, 0); if (rval != -2) rval = auto_fetch_u(argc, argv, outfile, 0, temp_stderr); /* resume autofetch() error statements */ if(dup2(temp_stderr, STDERR_FILENO) < 0) { dprintf(temp_stderr, "dup2(temp_stderr, STDERR_FILENO)\n"); exit(1); } if(dup2(temp_stdout, STDOUT_FILENO) < 0) err(1, "dup2(temp_stdout, STDOUT_FILENO)"); close(i); /* skip unveil */ if (rval == -2) goto unveil_cleanup; #ifndef SMALL /* exerpt from cmds.c; needs pathnames.h */ shellp = getenv("SHELL"); if (shellp == NULL || *shellp == '\0') shellp = _PATH_BSHELL; if (unveil(shellp, "x") == -1) err(1, "unveil"); fprintf(ttyout, "unveil(%s, \"x\")\n", shellp); if (*ca_file || *ca_path) { ca_temp2 = malloc(PATH_MAX + 1); if (ca_temp2 == NULL) err(1, "malloc"); i = 1; do { if (i) ca_temp = ca_file; else ca_temp = ca_path; if (*ca_temp == '\0') continue; if (unveil(ca_temp, "r") == -1) err(1, "unveil"); fprintf(ttyout, "unveil(%s, \"r\")\n", ca_temp); if (realpath(ca_temp, ca_temp2) == NULL) err(1, "realpath"); if (strcmp(ca_temp, ca_temp2)) { if (unveil(ca_temp2, "r") == -1) err(1, "unveil"); fprintf(ttyout, "unveil(%s, \"r\")\n", ca_temp2); } } while (i--); free(ca_temp2); } else { if (unveil("/etc/ssl/cert.pem", "r") == -1) err(1, "unveil"); fprintf(ttyout, "unveil(/etc/ssl/cert.pem, \"r\")\n"); } #endif if (unveil(NULL, NULL) == -1) err(1, "unveil"); unveil_cleanup: #ifndef SMALL free(ca_file); free(ca_path); ca_file = NULL; ca_path = NULL; #endif rval = auto_fetch(argc, argv, outfile); if (rval >= 0) /* -1 == connected and cd-ed */ exit(rval); } else { #ifndef SMALL char *xargv[5]; if (setjmp(toplevel)) exit(0); (void)signal(SIGINT, (sig_t)intr); (void)signal(SIGPIPE, (sig_t)lostpeer); xargv[0] = __progname; xargv[1] = argv[0]; xargv[2] = argv[1]; xargv[3] = argv[2]; xargv[4] = NULL; do { setpeer(argc+1, xargv); if (!retry_connect) break; if (!connected) { macnum = 0; fputs("Retrying...\n", ttyout); sleep(retry_connect); } } while (!connected); retry_connect = 0; /* connected, stop hiding msgs */ #endif /* !SMALL */ } } #ifndef SMALL controlediting(); top = setjmp(toplevel) == 0; if (top) { (void)signal(SIGINT, (sig_t)intr); (void)signal(SIGPIPE, (sig_t)lostpeer); } for (;;) { cmdscanner(top); top = 1; } #else /* !SMALL */ usage(); #endif /* !SMALL */ } void intr(void) { int save_errno = errno; write(fileno(ttyout), "\n\r", 2); alarmtimer(0); errno = save_errno; longjmp(toplevel, 1); } void lostpeer(void) { int save_errno = errno; alarmtimer(0); if (connected) { if (cout != NULL) { (void)shutdown(fileno(cout), SHUT_RDWR); (void)fclose(cout); cout = NULL; } if (data >= 0) { (void)shutdown(data, SHUT_RDWR); (void)close(data); data = -1; } connected = 0; } pswitch(1); if (connected) { if (cout != NULL) { (void)shutdown(fileno(cout), SHUT_RDWR); (void)fclose(cout); cout = NULL; } connected = 0; } proxflag = 0; pswitch(0); errno = save_errno; } #ifndef SMALL /* * Generate a prompt */ char * prompt(void) { return ("ftp> "); } /* * Command parser. */ void cmdscanner(int top) { struct cmd *c; int num; HistEvent hev; if (!top && !editing) (void)putc('\n', ttyout); for (;;) { if (!editing) { if (fromatty) { fputs(prompt(), ttyout); (void)fflush(ttyout); } if (fgets(line, sizeof(line), stdin) == NULL) quit(0, 0); num = strlen(line); if (num == 0) break; if (line[--num] == '\n') { if (num == 0) break; line[num] = '\0'; } else if (num == sizeof(line) - 2) { fputs("sorry, input line too long.\n", ttyout); while ((num = getchar()) != '\n' && num != EOF) /* void */; break; } /* else it was a line without a newline */ } else { const char *buf; cursor_pos = NULL; if ((buf = el_gets(el, &num)) == NULL || num == 0) { putc('\n', ttyout); fflush(ttyout); quit(0, 0); } if (buf[--num] == '\n') { if (num == 0) break; } if (num >= sizeof(line)) { fputs("sorry, input line too long.\n", ttyout); break; } memcpy(line, buf, (size_t)num); line[num] = '\0'; history(hist, &hev, H_ENTER, buf); } makeargv(); if (margc == 0) continue; c = getcmd(margv[0]); if (c == (struct cmd *)-1) { fputs("?Ambiguous command.\n", ttyout); continue; } if (c == 0) { /* * Give editline(3) a shot at unknown commands. * XXX - bogus commands with a colon in * them will not elicit an error. */ if (editing && el_parse(el, margc, (const char **)margv) != 0) fputs("?Invalid command.\n", ttyout); continue; } if (c->c_conn && !connected) { fputs("Not connected.\n", ttyout); continue; } confirmrest = 0; (*c->c_handler)(margc, margv); if (bell && c->c_bell) (void)putc('\007', ttyout); if (c->c_handler != help) break; } (void)signal(SIGINT, (sig_t)intr); (void)signal(SIGPIPE, (sig_t)lostpeer); } struct cmd * getcmd(const char *name) { const char *p, *q; struct cmd *c, *found; int nmatches, longest; if (name == NULL) return (0); longest = 0; nmatches = 0; found = 0; for (c = cmdtab; (p = c->c_name) != NULL; c++) { for (q = name; *q == *p++; q++) if (*q == 0) /* exact match? */ return (c); if (!*q) { /* the name was a prefix */ if (q - name > longest) { longest = q - name; nmatches = 1; found = c; } else if (q - name == longest) nmatches++; } } if (nmatches > 1) return ((struct cmd *)-1); return (found); } /* * Slice a string up into argc/argv. */ int slrflag; void makeargv(void) { char *argp; stringbase = line; /* scan from first of buffer */ argbase = argbuf; /* store from first of buffer */ slrflag = 0; marg_sl->sl_cur = 0; /* reset to start of marg_sl */ for (margc = 0; ; margc++) { argp = slurpstring(); sl_add(marg_sl, argp); if (argp == NULL) break; } if (cursor_pos == line) { cursor_argc = 0; cursor_argo = 0; } else if (cursor_pos != NULL) { cursor_argc = margc; cursor_argo = strlen(margv[margc-1]); } } #define INC_CHKCURSOR(x) { (x)++ ; \ if (x == cursor_pos) { \ cursor_argc = margc; \ cursor_argo = ap-argbase; \ cursor_pos = NULL; \ } } /* * Parse string into argbuf; * implemented with FSM to * handle quoting and strings */ char * slurpstring(void) { int got_one = 0; char *sb = stringbase; char *ap = argbase; char *tmp = argbase; /* will return this if token found */ if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ switch (slrflag) { /* and $ as token for macro invoke */ case 0: slrflag++; INC_CHKCURSOR(stringbase); return ((*sb == '!') ? "!" : "$"); /* NOTREACHED */ case 1: slrflag++; altarg = stringbase; break; default: break; } } S0: switch (*sb) { case '\0': goto OUT; case ' ': case '\t': INC_CHKCURSOR(sb); goto S0; default: switch (slrflag) { case 0: slrflag++; break; case 1: slrflag++; altarg = sb; break; default: break; } goto S1; } S1: switch (*sb) { case ' ': case '\t': case '\0': goto OUT; /* end of token */ case '\\': INC_CHKCURSOR(sb); goto S2; /* slurp next character */ case '"': INC_CHKCURSOR(sb); goto S3; /* slurp quoted string */ default: *ap = *sb; /* add character to token */ ap++; INC_CHKCURSOR(sb); got_one = 1; goto S1; } S2: switch (*sb) { case '\0': goto OUT; default: *ap = *sb; ap++; INC_CHKCURSOR(sb); got_one = 1; goto S1; } S3: switch (*sb) { case '\0': goto OUT; case '"': INC_CHKCURSOR(sb); goto S1; default: *ap = *sb; ap++; INC_CHKCURSOR(sb); got_one = 1; goto S3; } OUT: if (got_one) *ap++ = '\0'; argbase = ap; /* update storage pointer */ stringbase = sb; /* update scan pointer */ if (got_one) { return (tmp); } switch (slrflag) { case 0: slrflag++; break; case 1: slrflag++; altarg = (char *) 0; break; default: break; } return (NULL); } /* * Help command. * Call each command handler with argc == 0 and argv[0] == name. */ void help(int argc, char *argv[]) { struct cmd *c; if (argc == 1) { StringList *buf; buf = sl_init(); fprintf(ttyout, "%sommands may be abbreviated. Commands are:\n\n", proxy ? "Proxy c" : "C"); for (c = cmdtab; c < &cmdtab[NCMDS]; c++) if (c->c_name && (!proxy || c->c_proxy)) sl_add(buf, c->c_name); list_vertical(buf); sl_free(buf, 0); return; } #define HELPINDENT ((int) sizeof("disconnect")) while (--argc > 0) { char *arg; arg = *++argv; c = getcmd(arg); if (c == (struct cmd *)-1) fprintf(ttyout, "?Ambiguous help command %s\n", arg); else if (c == NULL) fprintf(ttyout, "?Invalid help command %s\n", arg); else fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, c->c_name, c->c_help); } } #endif /* !SMALL */ __dead void usage(void) { fprintf(stderr, "usage: " #ifndef SMALL "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] " "[-r seconds]\n" " [-s sourceaddr] [host [port]]\n" " ftp [-C] [-N name] [-o output] [-s sourceaddr]\n" " ftp://[user:password@]host[:port]/file[/] ...\n" " ftp [-C] [-c cookie] [-N name] [-o output] [-S ssl_options] " "[-s sourceaddr]\n" " [-U useragent] [-w seconds] " "http[s]://[user:password@]host[:port]/file ...\n" " ftp [-C] [-N name] [-o output] [-s sourceaddr] file:file ...\n" " ftp [-C] [-N name] [-o output] [-s sourceaddr] host:/file[/] ...\n" #else /* !SMALL */ "ftp [-N name] [-o output] " "ftp://[user:password@]host[:port]/file[/] ...\n" #ifndef NOSSL " ftp [-N name] [-o output] [-S ssl_options] [-w seconds] " "http[s]://[user:password@]host[:port]/file ...\n" #else " ftp [-N name] [-o output] [-w seconds] http://host[:port]/file ...\n" #endif /* NOSSL */ " ftp [-N name] [-o output] file:file ...\n" " ftp [-N name] [-o output] host:/file[/] ...\n" #endif /* !SMALL */ ); exit(1); }
/* $OpenBSD: extern.h,v 1.51 2019/05/16 12:44:17 florian Exp $ */ /* $NetBSD: extern.h,v 1.17 1997/08/18 10:20:19 lukem Exp $ */ /* * Copyright (C) 1997 and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Copyright (c) 1994 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)extern.h 8.3 (Berkeley) 10/9/94 */ #include <sys/types.h> void abort_remote(FILE *); void abortpt(int); void abortrecv(int); void alarmtimer(int); int another(int *, char ***, const char *); int auto_fetch(int, char **, char *); int auto_fetch_u(int, char **, char *, int, int); void blkfree(char **); void cdup(int, char **); void cmdabort(int); void cmdscanner(int); int command(const char *, ...); int confirm(const char *, const char *); int connect_wait(int); FILE *dataconn(const char *); int foregroundproc(void); int fileindir(const char *, const char *); struct cmd *getcmd(const char *); int getreply(int); int globulize(char **); char *gunique(const char *); void help(int, char **); char *hookup(char *, char *); int initconn(void); void intr(void); int isurl(const char *); int ftp_login(const char *, char *, char *); void lostpeer(void); void makeargv(void); void progressmeter(int, const char *); char *prompt(void); void proxtrans(const char *, const char *, const char *); void psabort(int); void psummary(int); void pswitch(int); void ptransfer(int); void recvrequest(const char *, const char *, const char *, const char *, int, int); char *remglob(char **, int, char **); off_t remotesize(const char *, int); time_t remotemodtime(const char *, int); void reset(int, char **); void rmthelp(int, char **); void sethash(int, char **); void setpeer(int, char **); void setttywidth(int); char *slurpstring(void); __dead void usage(void); void cookie_get(const char *, const char *, int, char **); void cookie_load(void); #ifndef SMALL void abortsend(int); unsigned char complete(EditLine *, int); void controlediting(void); void domacro(int, char **); void list_vertical(StringList *); void parse_list(char **, char *); char *remglob2(char **, int, char **, FILE **ftemp, char *type); int ruserpass(const char *, char **, char **, char **); void sendrequest(const char *, const char *, const char *, int); #endif /* !SMALL */ extern jmp_buf abortprox; extern int abrtflag; extern FILE *cout; extern int data; extern char *home; extern jmp_buf jabort; extern int family; extern int proxy; extern char reply_string[]; extern off_t restart_point; extern int keep_alive_timeout; extern int connect_timeout; extern int pipeout; extern char *action; #ifndef SMALL extern int NCMDS; #endif /* !SMALL */ extern char *__progname; /* from crt0.o */
/* $OpenBSD: fetch.c,v 1.194 2020/02/22 01:00:07 jca Exp $ */ /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */ /*- * Copyright (c) 1997 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason Thorpe and Luke Mewburn. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * FTP User Program -- Command line file retrieval */ #include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #include <netinet/in.h> #include <arpa/ftp.h> #include <arpa/inet.h> #include <ctype.h> #include <err.h> #include <libgen.h> #include <netdb.h> #include <fcntl.h> #include <signal.h> #include <vis.h> #include <stdio.h> #include <stdarg.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <util.h> #include <resolv.h> #ifndef NOSSL #include <tls.h> #else /* !NOSSL */ struct tls; #endif /* !NOSSL */ #include "ftp_var.h" #include "cmds.h" //~ static int file_get(const char *, const char *); static int file_get_u(const char *, const char *, int, int); static int url_get(const char *, const char *, const char *, int); static int url_get_u(const char *, const char *, const char *, int, int, int); static int save_chunked(FILE *, struct tls *, int , char *, size_t); static void aborthttp(int); static void abortfile(int); static char hextochar(const char *); static char *urldecode(const char *); static char *recode_credentials(const char *_userinfo); static char *ftp_readline(FILE *, size_t *); static void ftp_close(FILE **, struct tls **, int *); static const char *sockerror(struct tls *); #ifdef SMALL #define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__) #else static int ftp_printf(FILE *, const char *, ...); #endif /* SMALL */ #ifndef NOSSL static int proxy_connect(int, char *, char *); static int stdio_tls_write_wrapper(void *, const char *, int); static int stdio_tls_read_wrapper(void *, char *, int); #endif /* !NOSSL */ #define FTP_URL "ftp://" /* ftp URL prefix */ #define HTTP_URL "http://" /* http URL prefix */ #define HTTPS_URL "https://" /* https URL prefix */ #define FILE_URL "file:" /* file URL prefix */ #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ #define HTTP_PROXY "http_proxy" /* env var with http proxy location */ #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) static const char at_encoding_warning[] = "Extra `@' characters in usernames and passwords should be encoded as %%40"; static jmp_buf httpabort; static int redirect_loop; static int retried; /* * Determine whether the character needs encoding, per RFC1738: * - No corresponding graphic US-ASCII. * - Unsafe characters. */ static int unsafe_char(const char *c0) { const char *unsafe_chars = " <>\"#{}|\\^~[]`"; const unsigned char *c = (const unsigned char *)c0; /* * No corresponding graphic US-ASCII. * Control characters and octets not used in US-ASCII. */ return (iscntrl(*c) || !isascii(*c) || /* * Unsafe characters. * '%' is also unsafe, if is not followed by two * hexadecimal digits. */ strchr(unsafe_chars, *c) != NULL || (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c)))); } /* * Encode given URL, per RFC1738. * Allocate and return string to the caller. */ static char * url_encode(const char *path) { size_t i, length, new_length; char *epath, *epathp; length = new_length = strlen(path); /* * First pass: * Count unsafe characters, and determine length of the * final URL. */ for (i = 0; i < length; i++) if (unsafe_char(path + i)) new_length += 2; epath = epathp = malloc(new_length + 1); /* One more for '\0'. */ if (epath == NULL) err(1, "Can't allocate memory for URL encoding"); /* * Second pass: * Encode, and copy final URL. */ for (i = 0; i < length; i++) if (unsafe_char(path + i)) { snprintf(epathp, 4, "%%" "%02x", (unsigned char)path[i]); epathp += 3; } else *(epathp++) = path[i]; *epathp = '\0'; return (epath); } /* ARGSUSED */ static void tooslow(int signo) { dprintf(STDERR_FILENO, "%s: connect taking too long\n", __progname); _exit(2); } /* * Copy a local file (used by the OpenBSD installer). * Returns -1 on failure, 0 on success */ //~ static int //~ file_get(const char *path, const char *outfile) //~ { //~ return file_get_u(path, outfile, 1, 0); //~ } static int file_get_u(const char *path, const char *outfile, int ready, int tout) { struct stat st; int fd, out, rval = -1, save_errno; volatile sig_t oldintr, oldinti; const char *savefile; char *buf = NULL, *cp; const size_t buflen = 128 * 1024; off_t hashbytes; ssize_t len, wlen; direction = "received"; if (ready) { fd = open(path, O_RDONLY); if (fd == -1) { warn("Can't open file %s", path); return -1; } if (fstat(fd, &st) == -1) filesize = -1; else filesize = st.st_size; } else { if (unveil(path, "r") == -1) err(1, "unveil"); dprintf(tout, "unveil(%s, \"r\")\n", path); if (pipeout) return 0; } if (outfile != NULL) savefile = outfile; else { if (path[strlen(path) - 1] == '/') /* Consider no file */ savefile = NULL; /* after dir invalid. */ else savefile = basename(path); } if (!ready) { if (unveil(savefile, "cw") == -1) err(1, "unveil"); dprintf(tout, "unveil(%s, \"cw\")\n", savefile); return 0; } if (EMPTYSTRING(savefile)) { warnx("No filename after directory (use -o): %s", path); goto cleanup_copy; } /* Open the output file. */ if (!pipeout) { out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (out == -1) { warn("Can't open %s", savefile); goto cleanup_copy; } } else out = fileno(stdout); if ((buf = malloc(buflen)) == NULL) errx(1, "Can't allocate memory for transfer buffer"); /* Trap signals */ oldintr = NULL; oldinti = NULL; if (setjmp(httpabort)) { if (oldintr) (void)signal(SIGINT, oldintr); if (oldinti) (void)signal(SIGINFO, oldinti); goto cleanup_copy; } oldintr = signal(SIGINT, abortfile); bytes = 0; hashbytes = mark; progressmeter(-1, path); /* Finally, suck down the file. */ oldinti = signal(SIGINFO, psummary); while ((len = read(fd, buf, buflen)) > 0) { bytes += len; for (cp = buf; len > 0; len -= wlen, cp += wlen) { if ((wlen = write(out, cp, len)) == -1) { warn("Writing %s", savefile); signal(SIGINFO, oldinti); goto cleanup_copy; } } if (hash && !progress) { while (bytes >= hashbytes) { (void)putc('#', ttyout); hashbytes += mark; } (void)fflush(ttyout); } } save_errno = errno; signal(SIGINFO, oldinti); if (hash && !progress && bytes > 0) { if (bytes < mark) (void)putc('#', ttyout); (void)putc('\n', ttyout); (void)fflush(ttyout); } if (len == -1) { warnc(save_errno, "Reading from file"); goto cleanup_copy; } progressmeter(1, NULL); if (verbose) ptransfer(0); (void)signal(SIGINT, oldintr); rval = 0; cleanup_copy: free(buf); if (out >= 0 && out != fileno(stdout)) close(out); close(fd); return rval; } /* * Retrieve URL, via the proxy in $proxyvar if necessary. * Returns -1 on failure, 0 on success */ static int url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile) { return url_get_u(origline, proxyenv, outfile, lastfile, 1, 0); } static int url_get_u(const char *origline, const char *proxyenv, const char *outfile, int lastfile, int ready, int tout) { char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; char *epath, *redirurl, *loctail, *h, *p, gerror[200]; int error, isftpurl = 0, isredirect = 0, rval = -1; int isunavail = 0, retryafter = -1; struct addrinfo hints, *res0, *res; const char *savefile; char *proxyurl = NULL; char *credentials = NULL, *proxy_credentials = NULL; int fd = -1, out = -1; volatile sig_t oldintr, oldinti; FILE *fin = NULL; off_t hashbytes; const char *errstr; ssize_t len, wlen; char *proxyhost = NULL; #ifndef NOSSL char *sslpath = NULL, *sslhost = NULL; int ishttpsurl = 0; #endif /* !NOSSL */ #ifndef SMALL char *full_host = NULL; const char *scheme; char *locbase; struct addrinfo *ares = NULL; #endif /* !SMALL */ struct tls *tls = NULL; int status; int save_errno; const size_t buflen = 128 * 1024; int chunked = 0; direction = "received"; newline = strdup(origline); if (newline == NULL) errx(1, "Can't allocate memory to parse URL"); if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) { host = newline + sizeof(HTTP_URL) - 1; #ifndef SMALL scheme = HTTP_URL; #endif /* !SMALL */ } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) { host = newline + sizeof(FTP_URL) - 1; isftpurl = 1; #ifndef SMALL scheme = FTP_URL; #endif /* !SMALL */ } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) { #ifndef NOSSL host = newline + sizeof(HTTPS_URL) - 1; ishttpsurl = 1; #else errx(1, "%s: No HTTPS support", newline); #endif /* !NOSSL */ #ifndef SMALL scheme = HTTPS_URL; #endif /* !SMALL */ } else errx(1, "%s: URL not permitted", newline); path = strchr(host, '/'); /* Find path */ /* * Look for auth header in host. * Basic auth from RFC 2617, valid characters for path are in * RFC 3986 section 3.3. */ if (!isftpurl) { p = strchr(host, '@'); if (p != NULL && (path == NULL || p < path)) { *p++ = '\0'; credentials = recode_credentials(host); /* Overwrite userinfo */ memmove(host, p, strlen(p) + 1); path = strchr(host, '/'); } } if (EMPTYSTRING(path)) { if (outfile) { /* No slash, but */ path = strchr(host,'\0'); /* we have outfile. */ goto noslash; } if (isftpurl) goto noftpautologin; warnx("No `/' after host (use -o): %s", origline); goto cleanup_url_get; } *path++ = '\0'; if (EMPTYSTRING(path) && !outfile) { if (isftpurl) goto noftpautologin; warnx("No filename after host (use -o): %s", origline); goto cleanup_url_get; } noslash: if (outfile) savefile = outfile; else { if (path[strlen(path) - 1] == '/') /* Consider no file */ savefile = NULL; /* after dir invalid. */ else savefile = basename(path); } if (!ready) { if (unveil(savefile, "cw") == -1) err(1, "unveil"); dprintf(tout, "unveil(%s, \"cw\")\n", savefile); return (0); } if (EMPTYSTRING(savefile)) { if (isftpurl) goto noftpautologin; warnx("No filename after directory (use -o): %s", origline); goto cleanup_url_get; } #ifndef SMALL if (resume && pipeout) { warnx("can't append to stdout"); goto cleanup_url_get; } #endif /* !SMALL */ if (proxyenv != NULL) { /* use proxy */ #ifndef NOSSL if (ishttpsurl) { sslpath = strdup(path); sslhost = strdup(host); if (! sslpath || ! sslhost) errx(1, "Can't allocate memory for https path/host."); } #endif /* !NOSSL */ proxyhost = strdup(host); if (proxyhost == NULL) errx(1, "Can't allocate memory for proxy host."); proxyurl = strdup(proxyenv); if (proxyurl == NULL) errx(1, "Can't allocate memory for proxy URL."); if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) host = proxyurl + sizeof(HTTP_URL) - 1; else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0) host = proxyurl + sizeof(FTP_URL) - 1; else { warnx("Malformed proxy URL: %s", proxyenv); goto cleanup_url_get; } if (EMPTYSTRING(host)) { warnx("Malformed proxy URL: %s", proxyenv); goto cleanup_url_get; } if (*--path == '\0') *path = '/'; /* add / back to real path */ path = strchr(host, '/'); /* remove trailing / on host */ if (!EMPTYSTRING(path)) *path++ = '\0'; /* i guess this ++ is useless */ path = strchr(host, '@'); /* look for credentials in proxy */ if (!EMPTYSTRING(path)) { *path = '\0'; if (strchr(host, ':') == NULL) { warnx("Malformed proxy URL: %s", proxyenv); goto cleanup_url_get; } proxy_credentials = recode_credentials(host); *path = '@'; /* restore @ in proxyurl */ /* * This removes the password from proxyurl, * filling with stars */ for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@'; host++) *host = '*'; host = path + 1; } path = newline; } if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && (hosttail[1] == '\0' || hosttail[1] == ':')) { host++; *hosttail++ = '\0'; #ifndef SMALL if (asprintf(&full_host, "[%s]", host) == -1) errx(1, "Cannot allocate memory for hostname"); #endif /* !SMALL */ } else hosttail = host; portnum = strrchr(hosttail, ':'); /* find portnum */ if (portnum != NULL) *portnum++ = '\0'; #ifndef NOSSL port = portnum ? portnum : (ishttpsurl ? httpsport : httpport); #else /* !NOSSL */ port = portnum ? portnum : httpport; #endif /* !NOSSL */ #ifndef SMALL if (full_host == NULL) if ((full_host = strdup(host)) == NULL) errx(1, "Cannot allocate memory for hostname"); if (debug) fprintf(ttyout, "host %s, port %s, path %s, " "save as %s, auth %s.\n", host, port, path, savefile, credentials ? credentials : "none"); #endif /* !SMALL */ memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(host, port, &hints, &res0); /* * If the services file is corrupt/missing, fall back * on our hard-coded defines. */ if (error == EAI_SERVICE && port == httpport) { snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); error = getaddrinfo(host, pbuf, &hints, &res0); #ifndef NOSSL } else if (error == EAI_SERVICE && port == httpsport) { snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); error = getaddrinfo(host, pbuf, &hints, &res0); #endif /* !NOSSL */ } if (error) { warnx("%s: %s", host, gai_strerror(error)); goto cleanup_url_get; } #ifndef SMALL if (srcaddr) { hints.ai_flags |= AI_NUMERICHOST; error = getaddrinfo(srcaddr, NULL, &hints, &ares); if (error) { warnx("%s: %s", srcaddr, gai_strerror(error)); goto cleanup_url_get; } } #endif /* !SMALL */ /* ensure consistent order of the output */ if (verbose) setvbuf(ttyout, NULL, _IOLBF, 0); fd = -1; for (res = res0; res; res = res->ai_next) { if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) strlcpy(hbuf, "(unknown)", sizeof(hbuf)); if (verbose) fprintf(ttyout, "Trying %s...\n", hbuf); fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd == -1) { cause = "socket"; continue; } #ifndef SMALL if (srcaddr) { if (ares->ai_family != res->ai_family) { close(fd); fd = -1; errno = EINVAL; cause = "bind"; continue; } if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) { save_errno = errno; close(fd); errno = save_errno; fd = -1; cause = "bind"; continue; } } #endif /* !SMALL */ if (connect_timeout) { (void)signal(SIGALRM, tooslow); alarmtimer(connect_timeout); } for (error = connect(fd, res->ai_addr, res->ai_addrlen); error != 0 && errno == EINTR; error = connect_wait(fd)) continue; if (error != 0) { save_errno = errno; close(fd); errno = save_errno; fd = -1; cause = "connect"; continue; } /* get port in numeric */ if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0) port = pbuf; else port = NULL; #ifndef NOSSL if (proxyenv && sslhost) proxy_connect(fd, sslhost, proxy_credentials); #endif /* !NOSSL */ break; } freeaddrinfo(res0); #ifndef SMALL if (srcaddr) freeaddrinfo(ares); #endif /* !SMALL */ if (fd < 0) { warn("%s", cause); goto cleanup_url_get; } #ifndef NOSSL if (ishttpsurl) { ssize_t ret; if (proxyenv && sslpath) { ishttpsurl = 0; proxyurl = NULL; path = sslpath; } if (sslhost == NULL) { sslhost = strdup(host); if (sslhost == NULL) errx(1, "Can't allocate memory for https host."); } if ((tls = tls_client()) == NULL) { fprintf(ttyout, "failed to create SSL client\n"); goto cleanup_url_get; } if (tls_configure(tls, tls_config) != 0) { fprintf(ttyout, "TLS configuration failure: %s\n", tls_error(tls)); goto cleanup_url_get; } if (tls_connect_socket(tls, fd, sslhost) != 0) { fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls)); goto cleanup_url_get; } do { ret = tls_handshake(tls); } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); if (ret != 0) { fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls)); goto cleanup_url_get; } fin = funopen(tls, stdio_tls_read_wrapper, stdio_tls_write_wrapper, NULL, NULL); } else { fin = fdopen(fd, "r+"); fd = -1; } #else /* !NOSSL */ fin = fdopen(fd, "r+"); fd = -1; #endif /* !NOSSL */ #ifdef SMALL if (lastfile) { if (pipeout) { if (pledge("stdio rpath inet dns tty", NULL) == -1) err(1, "pledge"); } else { if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1) err(1, "pledge"); } } #endif if (connect_timeout) { signal(SIGALRM, SIG_DFL); alarmtimer(0); } /* * Construct and send the request. Proxy requests don't want leading /. */ #ifndef NOSSL cookie_get(host, path, ishttpsurl, &buf); #endif /* !NOSSL */ epath = url_encode(path); if (proxyurl) { if (verbose) { fprintf(ttyout, "Requesting %s (via %s)\n", origline, proxyurl); } /* * Host: directive must use the destination host address for * the original URI (path). */ ftp_printf(fin, "GET %s HTTP/1.1\r\n" "Connection: close\r\n" "Host: %s\r\n%s%s\r\n", epath, proxyhost, buf ? buf : "", httpuseragent); if (credentials) ftp_printf(fin, "Authorization: Basic %s\r\n", credentials); if (proxy_credentials) ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n", proxy_credentials); ftp_printf(fin, "\r\n"); } else { if (verbose) fprintf(ttyout, "Requesting %s\n", origline); #ifndef SMALL if (resume) { struct stat stbuf; if (stat(savefile, &stbuf) == 0) restart_point = stbuf.st_size; else restart_point = 0; } #endif /* SMALL */ ftp_printf(fin, "GET /%s HTTP/1.1\r\n" "Connection: close\r\n" "Host: ", epath); if (proxyhost) { ftp_printf(fin, "%s", proxyhost); port = NULL; } else if (strchr(host, ':')) { /* * strip off scoped address portion, since it's * local to node */ h = strdup(host); if (h == NULL) errx(1, "Can't allocate memory."); if ((p = strchr(h, '%')) != NULL) *p = '\0'; ftp_printf(fin, "[%s]", h); free(h); } else ftp_printf(fin, "%s", host); /* * Send port number only if it's specified and does not equal * 80. Some broken HTTP servers get confused if you explicitly * send them the port number. */ #ifndef NOSSL if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0) ftp_printf(fin, ":%s", port); if (restart_point) ftp_printf(fin, "\r\nRange: bytes=%lld-", (long long)restart_point); #else /* !NOSSL */ if (port && strcmp(port, "80") != 0) ftp_printf(fin, ":%s", port); #endif /* !NOSSL */ ftp_printf(fin, "\r\n%s%s\r\n", buf ? buf : "", httpuseragent); if (credentials) ftp_printf(fin, "Authorization: Basic %s\r\n", credentials); ftp_printf(fin, "\r\n"); } free(epath); #ifndef NOSSL free(buf); #endif /* !NOSSL */ buf = NULL; if (fflush(fin) == EOF) { warnx("Writing HTTP request: %s", sockerror(tls)); goto cleanup_url_get; } if ((buf = ftp_readline(fin, &len)) == NULL) { warnx("Receiving HTTP reply: %s", sockerror(tls)); goto cleanup_url_get; } while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) buf[--len] = '\0'; #ifndef SMALL if (debug) fprintf(ttyout, "received '%s'\n", buf); #endif /* !SMALL */ cp = strchr(buf, ' '); if (cp == NULL) goto improper; else cp++; strlcpy(ststr, cp, sizeof(ststr)); status = strtonum(ststr, 200, 503, &errstr); if (errstr) { strnvis(gerror, cp, sizeof gerror, VIS_SAFE); warnx("Error retrieving %s: %s", origline, gerror); goto cleanup_url_get; } switch (status) { case 200: /* OK */ #ifndef SMALL /* * When we request a partial file, and we receive an HTTP 200 * it is a good indication that the server doesn't support * range requests, and is about to send us the entire file. * If the restart_point == 0, then we are not actually * requesting a partial file, and an HTTP 200 is appropriate. */ if (resume && restart_point != 0) { warnx("Server does not support resume."); restart_point = resume = 0; } /* FALLTHROUGH */ case 206: /* Partial Content */ #endif /* !SMALL */ break; case 301: /* Moved Permanently */ case 302: /* Found */ case 303: /* See Other */ case 307: /* Temporary Redirect */ isredirect++; if (redirect_loop++ > 10) { warnx("Too many redirections requested"); goto cleanup_url_get; } break; #ifndef SMALL case 416: /* Requested Range Not Satisfiable */ warnx("File is already fully retrieved."); goto cleanup_url_get; #endif /* !SMALL */ case 503: isunavail = 1; break; default: strnvis(gerror, cp, sizeof gerror, VIS_SAFE); warnx("Error retrieving %s: %s", origline, gerror); goto cleanup_url_get; } /* * Read the rest of the header. */ free(buf); filesize = -1; for (;;) { if ((buf = ftp_readline(fin, &len)) == NULL) { warnx("Receiving HTTP reply: %s", sockerror(tls)); goto cleanup_url_get; } while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) buf[--len] = '\0'; if (len == 0) break; #ifndef SMALL if (debug) fprintf(ttyout, "received '%s'\n", buf); #endif /* !SMALL */ /* Look for some headers */ cp = buf; #define CONTENTLEN "Content-Length: " if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) { size_t s; cp += sizeof(CONTENTLEN) - 1; if ((s = strcspn(cp, " \t"))) *(cp+s) = 0; filesize = strtonum(cp, 0, LLONG_MAX, &errstr); if (errstr != NULL) goto improper; #ifndef SMALL if (restart_point) filesize += restart_point; #endif /* !SMALL */ #define LOCATION "Location: " } else if (isredirect && strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) { cp += sizeof(LOCATION) - 1; /* * If there is a colon before the first slash, this URI * is not relative. RFC 3986 4.2 */ if (cp[strcspn(cp, ":/")] != ':') { #ifdef SMALL errx(1, "Relative redirect not supported"); #else /* SMALL */ /* XXX doesn't handle protocol-relative URIs */ if (*cp == '/') { locbase = NULL; cp++; } else { locbase = strdup(path); if (locbase == NULL) errx(1, "Can't allocate memory" " for location base"); loctail = strchr(locbase, '#'); if (loctail != NULL) *loctail = '\0'; loctail = strchr(locbase, '?'); if (loctail != NULL) *loctail = '\0'; loctail = strrchr(locbase, '/'); if (loctail == NULL) { free(locbase); locbase = NULL; } else loctail[1] = '\0'; } /* Contruct URL from relative redirect */ if (asprintf(&redirurl, "%s%s%s%s/%s%s", scheme, full_host, portnum ? ":" : "", portnum ? portnum : "", locbase ? locbase : "", cp) == -1) errx(1, "Cannot build " "redirect URL"); free(locbase); #endif /* SMALL */ } else if ((redirurl = strdup(cp)) == NULL) errx(1, "Cannot allocate memory for URL"); loctail = strchr(redirurl, '#'); if (loctail != NULL) *loctail = '\0'; if (verbose) fprintf(ttyout, "Redirected to %s\n", redirurl); ftp_close(&fin, &tls, &fd); rval = url_get(redirurl, proxyenv, savefile, lastfile); free(redirurl); goto cleanup_url_get; #define RETRYAFTER "Retry-After: " } else if (isunavail && strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) { size_t s; cp += sizeof(RETRYAFTER) - 1; if ((s = strcspn(cp, " \t"))) cp[s] = '\0'; retryafter = strtonum(cp, 0, 0, &errstr); if (errstr != NULL) retryafter = -1; #define TRANSFER_ENCODING "Transfer-Encoding: " } else if (strncasecmp(cp, TRANSFER_ENCODING, sizeof(TRANSFER_ENCODING) - 1) == 0) { cp += sizeof(TRANSFER_ENCODING) - 1; cp[strcspn(cp, " \t")] = '\0'; if (strcasecmp(cp, "chunked") == 0) chunked = 1; } free(buf); } /* Content-Length should be ignored for Transfer-Encoding: chunked */ if (chunked) filesize = -1; if (isunavail) { if (retried || retryafter != 0) warnx("Error retrieving %s: 503 Service Unavailable", origline); else { if (verbose) fprintf(ttyout, "Retrying %s\n", origline); retried = 1; ftp_close(&fin, &tls, &fd); rval = url_get(origline, proxyenv, savefile, lastfile); } goto cleanup_url_get; } /* Open the output file. */ if (!pipeout) { #ifndef SMALL if (resume) out = open(savefile, O_CREAT | O_WRONLY | O_APPEND, 0666); else #endif /* !SMALL */ out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (out == -1) { warn("Can't open %s", savefile); goto cleanup_url_get; } } else { out = fileno(stdout); #ifdef SMALL if (lastfile) { if (pledge("stdio tty", NULL) == -1) err(1, "pledge"); } #endif } free(buf); if ((buf = malloc(buflen)) == NULL) errx(1, "Can't allocate memory for transfer buffer"); /* Trap signals */ oldintr = NULL; oldinti = NULL; if (setjmp(httpabort)) { if (oldintr) (void)signal(SIGINT, oldintr); if (oldinti) (void)signal(SIGINFO, oldinti); goto cleanup_url_get; } oldintr = signal(SIGINT, aborthttp); bytes = 0; hashbytes = mark; progressmeter(-1, path); /* Finally, suck down the file. */ oldinti = signal(SIGINFO, psummary); if (chunked) { error = save_chunked(fin, tls, out, buf, buflen); signal(SIGINFO, oldinti); if (error == -1) goto cleanup_url_get; } else { while ((len = fread(buf, 1, buflen, fin)) > 0) { bytes += len; for (cp = buf; len > 0; len -= wlen, cp += wlen) { if ((wlen = write(out, cp, len)) == -1) { warn("Writing %s", savefile); signal(SIGINFO, oldinti); goto cleanup_url_get; } } if (hash && !progress) { while (bytes >= hashbytes) { (void)putc('#', ttyout); hashbytes += mark; } (void)fflush(ttyout); } } save_errno = errno; signal(SIGINFO, oldinti); if (hash && !progress && bytes > 0) { if (bytes < mark) (void)putc('#', ttyout); (void)putc('\n', ttyout); (void)fflush(ttyout); } if (len == 0 && ferror(fin)) { errno = save_errno; warnx("Reading from socket: %s", sockerror(tls)); goto cleanup_url_get; } } progressmeter(1, NULL); if ( #ifndef SMALL !resume && #endif /* !SMALL */ filesize != -1 && len == 0 && bytes != filesize) { if (verbose) fputs("Read short file.\n", ttyout); goto cleanup_url_get; } if (verbose) ptransfer(0); (void)signal(SIGINT, oldintr); rval = 0; goto cleanup_url_get; noftpautologin: warnx( "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); goto cleanup_url_get; improper: warnx("Improper response from %s", host); cleanup_url_get: #ifndef SMALL free(full_host); #endif /* !SMALL */ #ifndef NOSSL free(sslhost); #endif /* !NOSSL */ ftp_close(&fin, &tls, &fd); if (out >= 0 && out != fileno(stdout)) close(out); free(buf); free(proxyhost); free(proxyurl); free(newline); free(credentials); free(proxy_credentials); return (rval); } static int save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen) { char *header, *end, *cp; unsigned long chunksize; size_t hlen, rlen, wlen; ssize_t written; char cr, lf; for (;;) { header = ftp_readline(fin, &hlen); if (header == NULL) break; /* strip CRLF and any optional chunk extension */ header[strcspn(header, ";\r\n")] = '\0'; errno = 0; chunksize = strtoul(header, &end, 16); if (errno || header[0] == '\0' || *end != '\0' || chunksize > INT_MAX) { warnx("Invalid chunk size '%s'", header); free(header); return -1; } free(header); if (chunksize == 0) { /* We're done. Ignore optional trailer. */ return 0; } for (written = 0; chunksize != 0; chunksize -= rlen) { rlen = (chunksize < buflen) ? chunksize : buflen; rlen = fread(buf, 1, rlen, fin); if (rlen == 0) break; bytes += rlen; for (cp = buf, wlen = rlen; wlen > 0; wlen -= written, cp += written) { if ((written = write(out, cp, wlen)) == -1) { warn("Writing output file"); return -1; } } } if (rlen == 0 || fread(&cr, 1, 1, fin) != 1 || fread(&lf, 1, 1, fin) != 1) break; if (cr != '\r' || lf != '\n') { warnx("Invalid chunked encoding"); return -1; } } if (ferror(fin)) warnx("Error while reading from socket: %s", sockerror(tls)); else warnx("Invalid chunked encoding: short read"); return -1; } /* * Abort a http retrieval */ /* ARGSUSED */ static void aborthttp(int signo) { alarmtimer(0); fputs("\nhttp fetch aborted.\n", ttyout); (void)fflush(ttyout); longjmp(httpabort, 1); } /* * Abort a http retrieval */ /* ARGSUSED */ static void abortfile(int signo) { alarmtimer(0); fputs("\nfile fetch aborted.\n", ttyout); (void)fflush(ttyout); longjmp(httpabort, 1); } /* * Retrieve multiple files from the command line, transferring * files of the form "host:path", "ftp://host/path" using the * ftp protocol, and files of the form "http://host/path" using * the http protocol. * If path has a trailing "/", then return (-1); * the path will be cd-ed into and the connection remains open, * and the function will return -1 (to indicate the connection * is alive). * If an error occurs the return value will be the offset+1 in * argv[] of the file that caused a problem (i.e, argv[x] * returns x+1) * Otherwise, 0 is returned if all files retrieved successfully. */ int auto_fetch(int argc, char *argv[], char *outfile) { return auto_fetch_u(argc, argv, outfile, 1, 0); } int auto_fetch_u(int argc, char *argv[], char *outfile, int ready, int tout) { char *xargv[5]; char *cp, *url, *host, *dir, *file, *portnum; char *username, *pass, *pathstart; char *ftpproxy, *httpproxy; int rval, xargc, lastfile; volatile int argpos; int dirhasglob, filehasglob, oautologin; char rempath[PATH_MAX]; argpos = 0; if (setjmp(toplevel)) { if (connected) disconnect(0, NULL); return (argpos + 1); } (void)signal(SIGINT, (sig_t)intr); (void)signal(SIGPIPE, (sig_t)lostpeer); if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0') ftpproxy = NULL; if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0') httpproxy = NULL; /* * Loop through as long as there's files to fetch. */ username = pass = NULL; for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) { if (strchr(argv[argpos], ':') == NULL) break; free(username); free(pass); host = dir = file = portnum = username = pass = NULL; lastfile = (argv[argpos+1] == NULL); /* * We muck with the string, so we make a copy. */ url = strdup(argv[argpos]); if (url == NULL) errx(1, "Can't allocate memory for auto-fetch."); if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { if (ready < 0) continue; if (file_get_u(url + sizeof(FILE_URL) - 1, outfile, ready, tout) == -1) rval = argpos + 1; continue; if (!ready) continue; } if (!ready && pipeout) continue; /* * Try HTTP URL-style arguments next. */ if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) { if (ready < 0) continue; redirect_loop = 0; retried = 0; if (url_get_u(url, httpproxy, outfile, lastfile, ready, tout) == -1) rval = argpos + 1; continue; if (!ready) continue; } /* * Try FTP URL-style arguments next. If ftpproxy is * set, use url_get() instead of standard ftp. * Finally, try host:file. */ host = url; if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { char *passend, *passagain, *userend; if (ftpproxy) { if (ready < 0) continue; if (url_get_u(url, ftpproxy, outfile, lastfile, ready, tout) == -1) rval = argpos + 1; continue; if (!ready) continue; } host += sizeof(FTP_URL) - 1; dir = strchr(host, '/'); /* Look for [user:pass@]host[:port] */ /* check if we have "user:pass@" */ userend = strchr(host, ':'); passend = strchr(host, '@'); if (passend && userend && userend < passend && (!dir || passend < dir)) { username = host; pass = userend + 1; host = passend + 1; *userend = *passend = '\0'; passagain = strchr(host, '@'); if (strchr(pass, '@') != NULL || (passagain != NULL && passagain < dir)) { warnx(at_encoding_warning); username = pass = NULL; goto bad_ftp_url; } if (EMPTYSTRING(username)) { bad_ftp_url: warnx("Invalid URL: %s", argv[argpos]); rval = argpos + 1; username = pass = NULL; continue; } username = urldecode(username); pass = urldecode(pass); } /* check [host]:port, or [host] */ if (host[0] == '[') { cp = strchr(host, ']'); if (cp && (!dir || cp < dir)) { if (cp + 1 == dir || cp[1] == ':') { host++; *cp++ = '\0'; } else cp = NULL; } else cp = host; } else cp = host; /* split off host[:port] if there is */ if (cp) { portnum = strchr(cp, ':'); pathstart = strchr(cp, '/'); /* : in path is not a port # indicator */ if (portnum && pathstart && pathstart < portnum) portnum = NULL; if (!portnum) ; else { if (!dir) ; else if (portnum + 1 < dir) { *portnum++ = '\0'; /* * XXX should check if portnum * is decimal number */ } else { /* empty portnum */ goto bad_ftp_url; } } } else portnum = NULL; } else { /* classic style `host:file' */ dir = strchr(host, ':'); } if (ready < 1) { rval = -2; continue; } if (EMPTYSTRING(host)) { rval = argpos + 1; continue; } /* * If dir is NULL, the file wasn't specified * (URL looked something like ftp://host) */ if (dir != NULL) *dir++ = '\0'; /* * Extract the file and (if present) directory name. */ if (!EMPTYSTRING(dir)) { cp = strrchr(dir, '/'); if (cp != NULL) { *cp++ = '\0'; file = cp; } else { file = dir; dir = NULL; } } #ifndef SMALL if (debug) fprintf(ttyout, "user %s:%s host %s port %s dir %s file %s\n", username, pass ? "XXXX" : NULL, host, portnum, dir, file); #endif /* !SMALL */ /* * Set up the connection. */ if (connected) disconnect(0, NULL); xargv[0] = __progname; xargv[1] = host; xargv[2] = NULL; xargc = 2; if (!EMPTYSTRING(portnum)) { xargv[2] = portnum; xargv[3] = NULL; xargc = 3; } oautologin = autologin; if (username == NULL) anonftp = 1; else { anonftp = 0; autologin = 0; } setpeer(xargc, xargv); autologin = oautologin; if (connected == 0 || (connected == 1 && autologin && (username == NULL || !ftp_login(host, username, pass)))) { warnx("Can't connect or login to host `%s'", host); rval = argpos + 1; continue; } /* Always use binary transfers. */ setbinary(0, NULL); dirhasglob = filehasglob = 0; if (doglob) { if (!EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) dirhasglob = 1; if (!EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) filehasglob = 1; } /* Change directories, if necessary. */ if (!EMPTYSTRING(dir) && !dirhasglob) { xargv[0] = "cd"; xargv[1] = dir; xargv[2] = NULL; cd(2, xargv); if (!dirchange) { rval = argpos + 1; continue; } } if (EMPTYSTRING(file)) { #ifndef SMALL rval = -1; #else /* !SMALL */ recvrequest("NLST", "-", NULL, "w", 0, 0); rval = 0; #endif /* !SMALL */ continue; } if (verbose) fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file); if (dirhasglob) { snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); file = rempath; } /* Fetch the file(s). */ xargc = 2; xargv[0] = "get"; xargv[1] = file; xargv[2] = NULL; if (dirhasglob || filehasglob) { int ointeractive; ointeractive = interactive; interactive = 0; xargv[0] = "mget"; #ifndef SMALL if (resume) { xargc = 3; xargv[1] = "-c"; xargv[2] = file; xargv[3] = NULL; } #endif /* !SMALL */ mget(xargc, xargv); interactive = ointeractive; } else { if (outfile != NULL) { xargv[2] = outfile; xargv[3] = NULL; xargc++; } #ifndef SMALL if (resume) reget(xargc, xargv); else #endif /* !SMALL */ get(xargc, xargv); } if ((code / 100) != COMPLETE) rval = argpos + 1; } if (connected && rval != -1) disconnect(0, NULL); return (rval); } char * urldecode(const char *str) { char *ret, c; int i, reallen; if (str == NULL) return NULL; if ((ret = malloc(strlen(str)+1)) == NULL) err(1, "Can't allocate memory for URL decoding"); for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) { c = str[i]; if (c == '+') { *ret = ' '; continue; } /* Cannot use strtol here because next char * after %xx may be a digit. */ if (c == '%' && isxdigit((unsigned char)str[i+1]) && isxdigit((unsigned char)str[i+2])) { *ret = hextochar(&str[i+1]); i+=2; continue; } *ret = c; } *ret = '\0'; return ret-reallen; } static char * recode_credentials(const char *userinfo) { char *ui, *creds; size_t ulen, credsize; /* url-decode the user and pass */ ui = urldecode(userinfo); ulen = strlen(ui); credsize = (ulen + 2) / 3 * 4 + 1; creds = malloc(credsize); if (creds == NULL) errx(1, "out of memory"); if (b64_ntop(ui, ulen, creds, credsize) == -1) errx(1, "error in base64 encoding"); free(ui); return (creds); } static char hextochar(const char *str) { unsigned char c, ret; c = str[0]; ret = c; if (isalpha(c)) ret -= isupper(c) ? 'A' - 10 : 'a' - 10; else ret -= '0'; ret *= 16; c = str[1]; ret += c; if (isalpha(c)) ret -= isupper(c) ? 'A' - 10 : 'a' - 10; else ret -= '0'; return ret; } int isurl(const char *p) { if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 || strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || #ifndef NOSSL strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 || #endif /* !NOSSL */ strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 || strstr(p, ":/")) return (1); return (0); } static char * ftp_readline(FILE *fp, size_t *lenp) { return fparseln(fp, lenp, NULL, "\0\0\0", 0); } #ifndef SMALL static int ftp_printf(FILE *fp, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = vfprintf(fp, fmt, ap); va_end(ap); if (debug) { va_start(ap, fmt); vfprintf(ttyout, fmt, ap); va_end(ap); } return ret; } #endif /* !SMALL */ static void ftp_close(FILE **fin, struct tls **tls, int *fd) { #ifndef NOSSL int ret; if (*tls != NULL) { if (tls_session_fd != -1) dprintf(STDERR_FILENO, "tls session resumed: %s\n", tls_conn_session_resumed(*tls) ? "yes" : "no"); do { ret = tls_close(*tls); } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); tls_free(*tls); *tls = NULL; } if (*fd != -1) { close(*fd); *fd = -1; } #endif if (*fin != NULL) { fclose(*fin); *fin = NULL; } } static const char * sockerror(struct tls *tls) { int save_errno = errno; #ifndef NOSSL if (tls != NULL) { const char *tlserr = tls_error(tls); if (tlserr != NULL) return tlserr; } #endif return strerror(save_errno); } #ifndef NOSSL static int proxy_connect(int socket, char *host, char *cookie) { int l; char buf[1024]; char *connstr, *hosttail, *port; if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && (hosttail[1] == '\0' || hosttail[1] == ':')) { host++; *hosttail++ = '\0'; } else hosttail = host; port = strrchr(hosttail, ':'); /* find portnum */ if (port != NULL) *port++ = '\0'; if (!port) port = "443"; if (cookie) { l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n" "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n", host, port, cookie, HTTP_USER_AGENT); } else { l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n", host, port, HTTP_USER_AGENT); } if (l == -1) errx(1, "Could not allocate memory to assemble connect string!"); #ifndef SMALL if (debug) printf("%s", connstr); #endif /* !SMALL */ if (write(socket, connstr, l) != l) err(1, "Could not send connect string"); read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */ free(connstr); return(200); } static int stdio_tls_write_wrapper(void *arg, const char *buf, int len) { struct tls *tls = arg; ssize_t ret; do { ret = tls_write(tls, buf, len); } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); return ret; } static int stdio_tls_read_wrapper(void *arg, char *buf, int len) { struct tls *tls = arg; ssize_t ret; do { ret = tls_read(tls, buf, len); } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); return ret; } #endif /* !NOSSL */