1091 lines
22 KiB
C
1091 lines
22 KiB
C
/* $OpenBSD: main.c,v 1.144 2023/12/12 22:00:43 kn 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"
|
|
|
|
int trace;
|
|
int hash;
|
|
int mark;
|
|
int sendport;
|
|
int verbose;
|
|
int connected;
|
|
int fromatty;
|
|
int interactive;
|
|
#ifndef SMALL
|
|
int confirmrest;
|
|
int debug;
|
|
int bell;
|
|
char *altarg;
|
|
#endif /* !SMALL */
|
|
int doglob;
|
|
int autologin;
|
|
int proxy;
|
|
int proxflag;
|
|
int gatemode;
|
|
char *gateserver;
|
|
int sunique;
|
|
int runique;
|
|
int mcase;
|
|
int ntflag;
|
|
int mapflag;
|
|
int preserve;
|
|
int progress;
|
|
int code;
|
|
int crflag;
|
|
char pasv[BUFSIZ];
|
|
int passivemode;
|
|
int activefallback;
|
|
char ntin[17];
|
|
char ntout[17];
|
|
char mapin[PATH_MAX];
|
|
char mapout[PATH_MAX];
|
|
char typename[32];
|
|
int type;
|
|
int curtype;
|
|
char structname[32];
|
|
int stru;
|
|
char formname[32];
|
|
int form;
|
|
char modename[32];
|
|
int mode;
|
|
char bytename[32];
|
|
int bytesize;
|
|
int anonftp;
|
|
int dirchange;
|
|
unsigned int retry_connect;
|
|
int ttywidth;
|
|
int epsv4;
|
|
int epsv4bad;
|
|
|
|
#ifndef SMALL
|
|
int editing;
|
|
EditLine *el;
|
|
History *hist;
|
|
char *cursor_pos;
|
|
size_t cursor_argc;
|
|
size_t cursor_argo;
|
|
int resume;
|
|
char *srcaddr;
|
|
int timestamp;
|
|
#endif /* !SMALL */
|
|
|
|
char *cookiefile;
|
|
|
|
off_t bytes;
|
|
off_t filesize;
|
|
char *direction;
|
|
|
|
char *hostname;
|
|
int unix_server;
|
|
int unix_proxy;
|
|
|
|
char *ftpport;
|
|
char *httpport;
|
|
#ifndef NOSSL
|
|
char *httpsport;
|
|
#endif /* !SMALL */
|
|
char *httpuseragent;
|
|
char *gateport;
|
|
|
|
jmp_buf toplevel;
|
|
|
|
#ifndef SMALL
|
|
char line[FTPBUFLEN];
|
|
char *argbase;
|
|
char *stringbase;
|
|
char argbuf[FTPBUFLEN];
|
|
StringList *marg_sl;
|
|
int margc;
|
|
int options;
|
|
#endif /* !SMALL */
|
|
|
|
int cpend;
|
|
int mflag;
|
|
|
|
#ifndef SMALL
|
|
int macnum;
|
|
struct macel macros[16];
|
|
char macbuf[4096];
|
|
#endif /* !SMALL */
|
|
|
|
FILE *ttyout;
|
|
|
|
int connect_timeout;
|
|
|
|
#ifndef SMALL
|
|
/* enable using server timestamps by default */
|
|
int server_timestamps = 1;
|
|
#endif
|
|
|
|
#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",
|
|
#define SSL_PROTOCOLS 9
|
|
"protocols",
|
|
NULL
|
|
};
|
|
|
|
struct tls_config *tls_config;
|
|
int tls_session_fd = -1;
|
|
|
|
static void
|
|
process_ssl_options(char *cp)
|
|
{
|
|
const char *errstr;
|
|
char *str;
|
|
int depth;
|
|
uint32_t protocols;
|
|
|
|
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, 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;
|
|
case SSL_PROTOCOLS:
|
|
if (str == NULL)
|
|
errx(1, "missing protocol name");
|
|
if (tls_config_parse_protocols(&protocols, str) != 0)
|
|
errx(1, "failed to parse TLS protocols");
|
|
if (tls_config_set_protocols(tls_config, protocols) != 0)
|
|
errx(1, "failed to set TLS protocols: %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;
|
|
#ifndef SMALL
|
|
int top;
|
|
#endif
|
|
struct passwd *pw = NULL;
|
|
char *cp, homedir[PATH_MAX];
|
|
char *outfile = NULL;
|
|
const char *errstr;
|
|
int dumb_terminal = 0;
|
|
|
|
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;
|
|
timestamp = 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_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:TtU:uvVw:")) != -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;
|
|
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);
|
|
#endif /* !NOSSL */
|
|
break;
|
|
|
|
case 's':
|
|
#ifndef SMALL
|
|
srcaddr = optarg;
|
|
#endif /* !SMALL */
|
|
break;
|
|
|
|
#ifndef SMALL
|
|
case 'T':
|
|
timestamp = 1;
|
|
break;
|
|
#endif /* !SMALL */
|
|
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;
|
|
case 'u':
|
|
server_timestamps = 0;
|
|
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) {
|
|
if (isurl(argv[0])) {
|
|
if (pipeout) {
|
|
if (pledge("stdio rpath dns tty inet",
|
|
NULL) == -1)
|
|
err(1, "pledge");
|
|
} else {
|
|
#ifndef SMALL
|
|
if (!resume) {
|
|
if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
|
|
NULL) == -1)
|
|
err(1, "pledge");
|
|
} else
|
|
#endif /* !SMALL */
|
|
if (pledge("stdio rpath wpath cpath dns tty inet fattr",
|
|
NULL) == -1)
|
|
err(1, "pledge");
|
|
}
|
|
|
|
rval = auto_fetch(argc, argv, outfile);
|
|
if (rval >= 0 || pipeout) /* -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 [-CTu] [-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);
|
|
}
|