version 1.128, 2019/05/15 13:42:40 |
version 1.129, 2019/05/16 12:44:18 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
|
/* $NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $ */ |
|
|
/* |
/* |
* Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org> |
* Copyright (C) 1997 and 1998 WIDE Project. |
|
* All rights reserved. |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Redistribution and use in source and binary forms, with or without |
* purpose with or without fee is hereby granted, provided that the above |
* modification, are permitted provided that the following conditions |
* copyright notice and this permission notice appear in all copies. |
* 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. |
* |
* |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* 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. |
*/ |
*/ |
|
|
#include <sys/cdefs.h> |
/* |
|
* 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/types.h> |
#include <sys/queue.h> |
|
#include <sys/stat.h> |
|
#include <sys/socket.h> |
#include <sys/socket.h> |
#include <sys/wait.h> |
|
|
|
|
#include <ctype.h> |
#include <err.h> |
#include <err.h> |
#include <errno.h> |
|
#include <fcntl.h> |
#include <fcntl.h> |
#include <imsg.h> |
#include <netdb.h> |
#include <libgen.h> |
#include <pwd.h> |
#include <signal.h> |
|
#include <stdio.h> |
#include <stdio.h> |
|
#include <errno.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#include "ftp.h" |
#include <tls.h> |
#include "xmalloc.h" |
|
|
|
static int auto_fetch(int, char **); |
#include "cmds.h" |
static void child(int, int, char **); |
#include "ftp_var.h" |
static int parent(int, pid_t); |
|
static struct url *proxy_parse(const char *); |
|
static struct url *get_proxy(int); |
|
static void re_exec(int, int, char **); |
|
static void validate_output_fname(struct url *, const char *); |
|
static __dead void usage(void); |
|
|
|
struct imsgbuf child_ibuf; |
int connect_timeout; |
const char *useragent = "OpenBSD ftp"; |
|
int activemode, family = AF_UNSPEC, io_debug; |
|
int progressmeter, verbose = 1; |
|
volatile sig_atomic_t interrupted = 0; |
|
FILE *msgout = stdout; |
|
|
|
static const char *title; |
#ifndef NOSSL |
static char *tls_options, *oarg; |
char * const ssl_verify_opts[] = { |
static int connect_timeout, resume; |
#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) |
|
{ |
|
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)); |
|
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)); |
|
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 |
int |
main(int argc, char **argv) |
main(volatile int argc, char *argv[]) |
{ |
{ |
const char *e; |
int ch, rval; |
char **save_argv, *term; |
#ifndef SMALL |
int ch, csock, dumb_terminal, rexec, save_argc; |
int top; |
|
#endif |
|
struct passwd *pw = NULL; |
|
char *cp, homedir[PATH_MAX]; |
|
char *outfile = NULL; |
|
const char *errstr; |
|
int dumb_terminal = 0; |
|
|
if (isatty(fileno(stdin)) != 1) |
ftpport = "ftp"; |
verbose = 0; |
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; |
|
|
io_debug = getenv("IO_DEBUG") != NULL; |
/* Set default operation mode based on FTPMODE environment variable */ |
term = getenv("TERM"); |
if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') { |
dumb_terminal = (term == NULL || *term == '\0' || |
if (strcmp(cp, "passive") == 0) { |
!strcmp(term, "dumb") || !strcmp(term, "emacs") || |
passivemode = 1; |
!strcmp(term, "su")); |
activefallback = 0; |
if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && !dumb_terminal) |
} else if (strcmp(cp, "active") == 0) { |
progressmeter = 1; |
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); |
|
} |
|
|
csock = rexec = 0; |
if (strcmp(__progname, "gate-ftp") == 0) |
save_argc = argc; |
gatemode = 1; |
save_argv = argv; |
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, |
while ((ch = getopt(argc, argv, |
"46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:xz:")) != -1) { |
"46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:")) != -1) { |
switch (ch) { |
switch (ch) { |
case '4': |
case '4': |
family = AF_INET; |
family = PF_INET; |
break; |
break; |
case '6': |
case '6': |
family = AF_INET6; |
family = PF_INET6; |
break; |
break; |
case 'A': |
case 'A': |
activemode = 1; |
activefallback = 0; |
|
passivemode = 0; |
break; |
break; |
|
|
|
case 'a': |
|
anonftp = 1; |
|
break; |
|
|
case 'C': |
case 'C': |
|
#ifndef SMALL |
resume = 1; |
resume = 1; |
|
#endif /* !SMALL */ |
break; |
break; |
|
|
|
case 'c': |
|
#ifndef SMALL |
|
cookiefile = optarg; |
|
#endif /* !SMALL */ |
|
break; |
|
|
case 'D': |
case 'D': |
title = optarg; |
action = optarg; |
break; |
break; |
case 'o': |
case 'd': |
oarg = optarg; |
#ifndef SMALL |
if (!strlen(oarg)) |
options |= SO_DEBUG; |
oarg = NULL; |
debug++; |
|
#endif /* !SMALL */ |
break; |
break; |
case 'M': |
|
progressmeter = 0; |
case 'E': |
|
epsv4 = 0; |
break; |
break; |
case 'm': |
|
progressmeter = 1; |
case 'e': |
|
#ifndef SMALL |
|
editing = 0; |
|
#endif /* !SMALL */ |
break; |
break; |
case 'S': |
|
tls_options = optarg; |
case 'g': |
|
doglob = 0; |
break; |
break; |
case 'U': |
|
useragent = optarg; |
case 'i': |
|
interactive = 0; |
break; |
break; |
case 'V': |
|
verbose = 0; |
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; |
break; |
case 'v': |
case 'M': |
verbose = 1; |
progress = 0; |
break; |
break; |
case 'w': |
case 'm': |
connect_timeout = strtonum(optarg, 0, 200, &e); |
progress = -1; |
if (e) |
|
errx(1, "-w: %s", e); |
|
break; |
break; |
/* options for internal use only */ |
|
case 'x': |
case 'n': |
rexec = 1; |
autologin = 0; |
break; |
break; |
case 'z': |
|
csock = strtonum(optarg, 3, getdtablesize() - 1, &e); |
case 'o': |
if (e) |
outfile = optarg; |
errx(1, "-z: %s", e); |
if (*outfile == '\0') { |
|
pipeout = 0; |
|
outfile = NULL; |
|
ttyout = stdout; |
|
} else { |
|
pipeout = strcmp(outfile, "-") == 0; |
|
ttyout = pipeout ? stderr : stdout; |
|
} |
break; |
break; |
/* Ignoring all remaining options */ |
|
case 'a': |
|
case 'c': |
|
case 'd': |
|
case 'E': |
|
case 'e': |
|
case 'g': |
|
case 'i': |
|
case 'k': |
|
case 'n': |
|
case 'P': |
|
case 'p': |
case 'p': |
|
passivemode = 1; |
|
activefallback = 0; |
|
break; |
|
|
|
case 'P': |
|
ftpport = optarg; |
|
break; |
|
|
case 'r': |
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); |
|
#endif /* !NOSSL */ |
|
break; |
|
|
case 's': |
case 's': |
|
#ifndef SMALL |
|
srcaddr = optarg; |
|
#endif /* !SMALL */ |
|
break; |
|
|
case 't': |
case 't': |
|
trace = 1; |
break; |
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: |
default: |
usage(); |
usage(); |
} |
} |
|
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
if (rexec) |
#ifndef NOSSL |
child(csock, argc, argv); |
cookie_load(); |
|
#endif /* !NOSSL */ |
|
if (httpuseragent == NULL) |
|
httpuseragent = HTTP_USER_AGENT; |
|
|
#ifndef SMALL |
cpend = 0; /* no pending replies */ |
struct url *url; |
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; |
|
} |
|
|
switch (argc) { |
setttywidth(0); |
case 0: |
(void)signal(SIGWINCH, setttywidth); |
cmd(NULL, NULL, NULL); |
|
return 0; |
|
case 1: |
|
case 2: |
|
switch (scheme_lookup(argv[0])) { |
|
case -1: |
|
cmd(argv[0], argv[1], NULL); |
|
return 0; |
|
case S_FTP: |
|
if ((url = url_parse(argv[0])) == NULL) |
|
exit(1); |
|
|
|
if (url->path && |
if (argc > 0) { |
url->path[strlen(url->path) - 1] != '/') |
if (isurl(argv[0])) { |
break; /* auto fetch */ |
if (pipeout) { |
|
#ifndef SMALL |
cmd(url->host, url->port, url->path); |
if (pledge("stdio rpath dns tty inet proc exec fattr", |
return 0; |
NULL) == -1) |
} |
err(1, "pledge"); |
break; |
|
} |
|
#else |
#else |
if (argc == 0) |
if (pledge("stdio rpath dns tty inet fattr", |
usage(); |
NULL) == -1) |
|
err(1, "pledge"); |
#endif |
#endif |
|
} else { |
|
#ifndef SMALL |
|
if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", |
|
NULL) == -1) |
|
err(1, "pledge"); |
|
#else |
|
if (pledge("stdio rpath wpath cpath dns tty inet fattr", |
|
NULL) == -1) |
|
err(1, "pledge"); |
|
#endif |
|
} |
|
|
return auto_fetch(save_argc, save_argv); |
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 */ |
} |
} |
|
|
static int |
void |
auto_fetch(int sargc, char **sargv) |
intr(void) |
{ |
{ |
pid_t pid; |
int save_errno = errno; |
int sp[2]; |
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) != 0) |
write(fileno(ttyout), "\n\r", 2); |
err(1, "socketpair"); |
alarmtimer(0); |
|
|
switch (pid = fork()) { |
errno = save_errno; |
case -1: |
longjmp(toplevel, 1); |
err(1, "fork"); |
|
case 0: |
|
close(sp[0]); |
|
re_exec(sp[1], sargc, sargv); |
|
} |
|
|
|
close(sp[1]); |
|
return parent(sp[0], pid); |
|
} |
} |
|
|
static void |
void |
re_exec(int sock, int argc, char **argv) |
lostpeer(void) |
{ |
{ |
char **nargv, *sock_str; |
int save_errno = errno; |
int i, j, nargc; |
|
|
|
nargc = argc + 4; |
alarmtimer(0); |
nargv = xcalloc(nargc, sizeof(*nargv)); |
if (connected) { |
xasprintf(&sock_str, "%d", sock); |
if (cout != NULL) { |
i = 0; |
(void)shutdown(fileno(cout), SHUT_RDWR); |
nargv[i++] = argv[0]; |
(void)fclose(cout); |
nargv[i++] = "-z"; |
cout = NULL; |
nargv[i++] = sock_str; |
} |
nargv[i++] = "-x"; |
if (data >= 0) { |
for (j = 1; j < argc; j++) |
(void)shutdown(data, SHUT_RDWR); |
nargv[i++] = argv[j]; |
(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; |
|
} |
|
|
execvp(nargv[0], nargv); |
#ifndef SMALL |
err(1, "execvp"); |
/* |
|
* Generate a prompt |
|
*/ |
|
char * |
|
prompt(void) |
|
{ |
|
return ("ftp> "); |
} |
} |
|
|
static int |
/* |
parent(int sock, pid_t child_pid) |
* Command parser. |
|
*/ |
|
void |
|
cmdscanner(int top) |
{ |
{ |
struct imsgbuf ibuf; |
struct cmd *c; |
struct imsg imsg; |
int num; |
struct stat sb; |
HistEvent hev; |
off_t offset; |
|
int fd, save_errno, sig, status; |
|
|
|
setproctitle("%s", "parent"); |
if (!top && !editing) |
if (pledge("stdio cpath rpath wpath sendfd", NULL) == -1) |
(void)putc('\n', ttyout); |
err(1, "pledge"); |
|
|
|
imsg_init(&ibuf, sock); |
|
for (;;) { |
for (;;) { |
if (read_message(&ibuf, &imsg) == 0) |
if (!editing) { |
break; |
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 (imsg.hdr.type != IMSG_OPEN) |
if ((buf = el_gets(el, &num)) == NULL || num == 0) { |
errx(1, "%s: IMSG_OPEN expected", __func__); |
putc('\n', ttyout); |
|
fflush(ttyout); |
offset = 0; |
quit(0, 0); |
fd = open(imsg.data, imsg.hdr.peerid, 0666); |
} |
save_errno = errno; |
if (buf[--num] == '\n') { |
if (fd != -1 && fstat(fd, &sb) == 0) { |
if (num == 0) |
if (sb.st_mode & S_IFDIR) { |
break; |
close(fd); |
} |
fd = -1; |
if (num >= sizeof(line)) { |
save_errno = EISDIR; |
fputs("sorry, input line too long.\n", ttyout); |
} else |
break; |
offset = sb.st_size; |
} |
|
memcpy(line, buf, (size_t)num); |
|
line[num] = '\0'; |
|
history(hist, &hev, H_ENTER, buf); |
} |
} |
|
|
send_message(&ibuf, IMSG_OPEN, save_errno, |
makeargv(); |
&offset, sizeof offset, fd); |
if (margc == 0) |
imsg_free(&imsg); |
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); |
|
} |
|
|
close(sock); |
struct cmd * |
if (waitpid(child_pid, &status, 0) == -1 && errno != ECHILD) |
getcmd(const char *name) |
err(1, "wait"); |
{ |
|
const char *p, *q; |
|
struct cmd *c, *found; |
|
int nmatches, longest; |
|
|
sig = WTERMSIG(status); |
if (name == NULL) |
if (WIFSIGNALED(status) && sig != SIGPIPE) |
return (0); |
errx(1, "child terminated: signal %d", sig); |
|
|
|
return WEXITSTATUS(status); |
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); |
} |
} |
|
|
static void |
/* |
child(int sock, int argc, char **argv) |
* Slice a string up into argc/argv. |
{ |
*/ |
struct url *url; |
|
FILE *dst_fp; |
|
char *p; |
|
off_t offset, sz; |
|
int fd, i, tostdout; |
|
|
|
setproctitle("%s", "child"); |
int slrflag; |
#ifndef NOSSL |
|
https_init(tls_options); |
|
#endif |
|
if (pledge("stdio inet dns recvfd tty", NULL) == -1) |
|
err(1, "pledge"); |
|
if (!progressmeter && pledge("stdio inet dns recvfd", NULL) == -1) |
|
err(1, "pledge"); |
|
|
|
imsg_init(&child_ibuf, sock); |
void |
tostdout = oarg && (strcmp(oarg, "-") == 0); |
makeargv(void) |
if (tostdout) |
{ |
msgout = stderr; |
char *argp; |
if (resume && tostdout) |
|
errx(1, "can't append to stdout"); |
|
|
|
for (i = 0; i < argc; i++) { |
stringbase = line; /* scan from first of buffer */ |
fd = -1; |
argbase = argbuf; /* store from first of buffer */ |
offset = sz = 0; |
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]); |
|
} |
|
} |
|
|
if ((url = url_parse(argv[i])) == NULL) |
#define INC_CHKCURSOR(x) { (x)++ ; \ |
exit(1); |
if (x == cursor_pos) { \ |
|
cursor_argc = margc; \ |
|
cursor_argo = ap-argbase; \ |
|
cursor_pos = NULL; \ |
|
} } |
|
|
validate_output_fname(url, argv[i]); |
/* |
url_connect(url, get_proxy(url->scheme), connect_timeout); |
* Parse string into argbuf; |
if (resume) |
* implemented with FSM to |
fd = fd_request(url->fname, O_WRONLY|O_APPEND, &offset); |
* 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 */ |
|
|
url = url_request(url, get_proxy(url->scheme), &offset, &sz); |
if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ |
if (resume && offset == 0 && fd != -1) |
switch (slrflag) { /* and $ as token for macro invoke */ |
if (ftruncate(fd, 0) != 0) |
case 0: |
err(1, "ftruncate"); |
slrflag++; |
|
INC_CHKCURSOR(stringbase); |
|
return ((*sb == '!') ? "!" : "$"); |
|
/* NOTREACHED */ |
|
case 1: |
|
slrflag++; |
|
altarg = stringbase; |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
if (fd == -1 && !tostdout && |
S0: |
(fd = fd_request(url->fname, |
switch (*sb) { |
O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1) |
|
err(1, "Can't open file %s", url->fname); |
|
|
|
if (tostdout) { |
case '\0': |
dst_fp = stdout; |
goto OUT; |
} else if ((dst_fp = fdopen(fd, "w")) == NULL) |
|
err(1, "%s: fdopen", __func__); |
|
|
|
init_stats(sz, &offset); |
case ' ': |
if (progressmeter) { |
case '\t': |
p = basename(url->path); |
INC_CHKCURSOR(sb); |
start_progress_meter(p, title); |
goto S0; |
|
|
|
default: |
|
switch (slrflag) { |
|
case 0: |
|
slrflag++; |
|
break; |
|
case 1: |
|
slrflag++; |
|
altarg = sb; |
|
break; |
|
default: |
|
break; |
} |
} |
|
goto S1; |
|
} |
|
|
url_save(url, dst_fp, &offset); |
S1: |
if (progressmeter) |
switch (*sb) { |
stop_progress_meter(); |
|
finish_stats(); |
|
|
|
if (!tostdout) |
case ' ': |
fclose(dst_fp); |
case '\t': |
|
case '\0': |
|
goto OUT; /* end of token */ |
|
|
url_close(url); |
case '\\': |
url_free(url); |
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; |
} |
} |
|
|
exit(0); |
S2: |
} |
switch (*sb) { |
|
|
static struct url * |
case '\0': |
get_proxy(int scheme) |
goto OUT; |
{ |
|
static struct url *ftp_proxy, *http_proxy; |
|
|
|
switch (scheme) { |
|
case S_HTTP: |
|
case S_HTTPS: |
|
if (http_proxy) |
|
return http_proxy; |
|
else |
|
return (http_proxy = proxy_parse("http_proxy")); |
|
case S_FTP: |
|
if (ftp_proxy) |
|
return ftp_proxy; |
|
else |
|
return (ftp_proxy = proxy_parse("ftp_proxy")); |
|
default: |
default: |
return NULL; |
*ap = *sb; |
|
ap++; |
|
INC_CHKCURSOR(sb); |
|
got_one = 1; |
|
goto S1; |
} |
} |
} |
|
|
|
static void |
S3: |
validate_output_fname(struct url *url, const char *name) |
switch (*sb) { |
{ |
|
url->fname = xstrdup(oarg ? oarg : basename(url->path)); |
|
if (strcmp(url->fname, "/") == 0) |
|
errx(1, "No filename after host (use -o): %s", name); |
|
|
|
if (strcmp(url->fname, ".") == 0) |
case '\0': |
errx(1, "No '/' after host (use -o): %s", name); |
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); |
} |
} |
|
|
static struct url * |
/* |
proxy_parse(const char *name) |
* Help command. |
|
* Call each command handler with argc == 0 and argv[0] == name. |
|
*/ |
|
void |
|
help(int argc, char *argv[]) |
{ |
{ |
struct url *proxy; |
struct cmd *c; |
char *str; |
|
|
|
if ((str = getenv(name)) == NULL) |
if (argc == 1) { |
return NULL; |
StringList *buf; |
|
|
if (strlen(str) == 0) |
buf = sl_init(); |
return NULL; |
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; |
|
} |
|
|
if ((proxy = url_parse(str)) == NULL) |
#define HELPINDENT ((int) sizeof("disconnect")) |
exit(1); |
|
|
|
if (proxy->scheme != S_HTTP) |
while (--argc > 0) { |
errx(1, "Malformed proxy URL: %s", str); |
char *arg; |
|
|
return proxy; |
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 */ |
|
|
static __dead void |
__dead void |
usage(void) |
usage(void) |
{ |
{ |
fprintf(stderr, "usage:\t%s [-46AVv] [-D title] [host [port]]\n" |
fprintf(stderr, "usage: " |
"\t%s [-46ACMmVv] [-D title] [-o output] [-S tls_options]\n" |
#ifndef SMALL |
"\t\t[-U useragent] [-w seconds] url ...\n", getprogname(), |
"ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] " |
getprogname()); |
"[-r seconds]\n" |
|
" [-s srcaddr] [host [port]]\n" |
|
" ftp [-C] [-o output] [-s srcaddr]\n" |
|
" ftp://[user:password@]host[:port]/file[/] ...\n" |
|
" ftp [-C] [-c cookie] [-o output] [-S ssl_options] " |
|
"[-s srcaddr]\n" |
|
" [-U useragent] [-w seconds] " |
|
"http[s]://[user:password@]host[:port]/file ...\n" |
|
" ftp [-C] [-o output] [-s srcaddr] file:file ...\n" |
|
" ftp [-C] [-o output] [-s srcaddr] host:/file[/] ...\n" |
|
#else /* !SMALL */ |
|
"ftp [-o output] " |
|
"ftp://[user:password@]host[:port]/file[/] ...\n" |
|
#ifndef NOSSL |
|
" ftp [-o output] [-S ssl_options] [-w seconds] " |
|
"http[s]://[user:password@]host[:port]/file ...\n" |
|
#else |
|
" ftp [-o output] [-w seconds] http://host[:port]/file ...\n" |
|
#endif /* NOSSL */ |
|
" ftp [-o output] file:file ...\n" |
|
" ftp [-o output] host:/file[/] ...\n" |
|
#endif /* !SMALL */ |
|
); |
exit(1); |
exit(1); |
} |
} |