summary refs log tree commit diff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c1093
1 files changed, 1093 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..0124714
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1093 @@
+/*	$OpenBSD: main.c,v 1.146 2023/12/23 23:03:00 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 <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef NOSSL
+#include "tls_compat.h"
+#endif
+
+#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
+};
+
+tls_config_t 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, "DEFAULT") != 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 (outfile == NULL) {
+				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);
+			/* -1 == connected and cd-ed */
+			if (rval >= 0 || outfile != NULL)
+				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);
+}