1/*	$NetBSD: main.c,v 1.94 2005/05/13 05:03:49 lukem Exp $	*/
2
3/*-
4 * Copyright (c) 1996-2004 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *	This product includes software developed by the NetBSD
21 *	Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 *    contributors may be used to endorse or promote products derived
24 *    from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39/*
40 * Copyright (c) 1985, 1989, 1993, 1994
41 *	The Regents of the University of California.  All rights reserved.
42 *
43 * Redistribution and use in source and binary forms, with or without
44 * modification, are permitted provided that the following conditions
45 * are met:
46 * 1. Redistributions of source code must retain the above copyright
47 *    notice, this list of conditions and the following disclaimer.
48 * 2. Redistributions in binary form must reproduce the above copyright
49 *    notice, this list of conditions and the following disclaimer in the
50 *    documentation and/or other materials provided with the distribution.
51 * 3. Neither the name of the University nor the names of its contributors
52 *    may be used to endorse or promote products derived from this software
53 *    without specific prior written permission.
54 *
55 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
56 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
57 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
58 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
59 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
60 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
61 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
62 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
63 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
64 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
65 * SUCH DAMAGE.
66 */
67
68/*
69 * Copyright (C) 1997 and 1998 WIDE Project.
70 * All rights reserved.
71 *
72 * Redistribution and use in source and binary forms, with or without
73 * modification, are permitted provided that the following conditions
74 * are met:
75 * 1. Redistributions of source code must retain the above copyright
76 *    notice, this list of conditions and the following disclaimer.
77 * 2. Redistributions in binary form must reproduce the above copyright
78 *    notice, this list of conditions and the following disclaimer in the
79 *    documentation and/or other materials provided with the distribution.
80 * 3. Neither the name of the project nor the names of its contributors
81 *    may be used to endorse or promote products derived from this software
82 *    without specific prior written permission.
83 *
84 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
85 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
86 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
87 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
88 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
89 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
90 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
91 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
92 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
93 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
94 * SUCH DAMAGE.
95 */
96
97#include <sys/cdefs.h>
98
99/*
100 * FTP User Program -- Command Interface.
101 */
102#include <sys/types.h>
103#include <sys/socket.h>
104
105#include <err.h>
106#include <errno.h>
107#include <netdb.h>
108#include <paths.h>
109#include <pwd.h>
110#include <signal.h>
111#include <stdio.h>
112#include <stdlib.h>
113#include <string.h>
114#include <unistd.h>
115#include <locale.h>
116
117#define	GLOBAL		/* force GLOBAL decls in ftp_var.h to be declared */
118#include "ftp_var.h"
119
120#define	FTP_PROXY	"ftp_proxy"	/* env var with FTP proxy location */
121#define	HTTP_PROXY	"http_proxy"	/* env var with HTTP proxy location */
122#define	NO_PROXY	"no_proxy"	/* env var with list of non-proxied
123					 * hosts, comma or space separated */
124
125static void	setupoption(char *, char *, char *);
126int		main(int, char *[]);
127
128int
129main(int argc, char *argv[])
130{
131	int ch, rval;
132	struct passwd *pw;
133	char *cp, *ep, *anonuser, *anonpass, *upload_path;
134	int dumbterm, s, len, isupload;
135	socklen_t slen;
136
137	setlocale(LC_ALL, "");
138	setprogname(argv[0]);
139
140	sigint_raised = 0;
141
142	ftpport = "ftp";
143	httpport = "http";
144	gateport = NULL;
145	cp = getenv("FTPSERVERPORT");
146	if (cp != NULL)
147		gateport = cp;
148	else
149		gateport = "ftpgate";
150	doglob = 1;
151	interactive = 1;
152	autologin = 1;
153	passivemode = 1;
154	activefallback = 1;
155	preserve = 1;
156	verbose = 0;
157	progress = 0;
158	gatemode = 0;
159	data = -1;
160	outfile = NULL;
161	restartautofetch = 0;
162#ifndef NO_EDITCOMPLETE
163	editing = 0;
164	el = NULL;
165	hist = NULL;
166#endif
167	bytes = 0;
168	mark = HASHBYTES;
169	rate_get = 0;
170	rate_get_incr = DEFAULTINCR;
171	rate_put = 0;
172	rate_put_incr = DEFAULTINCR;
173#ifdef INET6
174	epsv4 = 1;
175#else
176	epsv4 = 0;
177#endif
178	epsv4bad = 0;
179	upload_path = NULL;
180	isupload = 0;
181	reply_callback = NULL;
182	family = AF_UNSPEC;
183
184	netrc[0] = '\0';
185	cp = getenv("NETRC");
186	if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc))
187		errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG));
188
189	/*
190	 * Get the default socket buffer sizes if we don't already have them.
191	 * It doesn't matter which socket we do this to, because on the first
192	 * call no socket buffer sizes will have been modified, so we are
193	 * guaranteed to get the system defaults.
194	 */
195	s = socket(AF_INET, SOCK_STREAM, 0);
196	if (s == -1)
197		err(1, "can't create socket");
198	slen = sizeof(rcvbuf_size);
199	if (getsockopt(s, SOL_SOCKET, SO_RCVBUF,
200	    (void *)&rcvbuf_size, &slen) == -1)
201		err(1, "unable to get default rcvbuf size");
202	slen = sizeof(sndbuf_size);
203	if (getsockopt(s, SOL_SOCKET, SO_SNDBUF,
204	    (void *)&sndbuf_size, &slen) == -1)
205		err(1, "unable to get default sndbuf size");
206	(void)close(s);
207					/* sanity check returned buffer sizes */
208	if (rcvbuf_size <= 0)
209		rcvbuf_size = 8 * 1024;
210	if (sndbuf_size <= 0)
211		sndbuf_size = 8 * 1024;
212
213	if (sndbuf_size > 8 * 1024 * 1024)
214		sndbuf_size = 8 * 1024 * 1024;
215	if (rcvbuf_size > 8 * 1024 * 1024)
216		rcvbuf_size = 8 * 1024 * 1024;
217
218	marg_sl = xsl_init();
219	if ((tmpdir = getenv("TMPDIR")) == NULL)
220		tmpdir = _PATH_TMP;
221
222	/* Set default operation mode based on FTPMODE environment variable */
223	if ((cp = getenv("FTPMODE")) != NULL) {
224		if (strcasecmp(cp, "passive") == 0) {
225			passivemode = 1;
226			activefallback = 0;
227		} else if (strcasecmp(cp, "active") == 0) {
228			passivemode = 0;
229			activefallback = 0;
230		} else if (strcasecmp(cp, "gate") == 0) {
231			gatemode = 1;
232		} else if (strcasecmp(cp, "auto") == 0) {
233			passivemode = 1;
234			activefallback = 1;
235		} else
236			warnx("unknown $FTPMODE '%s'; using defaults", cp);
237	}
238
239	if (strcmp(getprogname(), "pftp") == 0) {
240		passivemode = 1;
241		activefallback = 0;
242	} else if (strcmp(getprogname(), "gate-ftp") == 0)
243		gatemode = 1;
244
245	gateserver = getenv("FTPSERVER");
246	if (gateserver == NULL || *gateserver == '\0')
247		gateserver = GATE_SERVER;
248	if (gatemode) {
249		if (*gateserver == '\0') {
250			warnx(
251"Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp");
252			gatemode = 0;
253		}
254	}
255
256	cp = getenv("TERM");
257	if (cp == NULL || strcmp(cp, "dumb") == 0)
258		dumbterm = 1;
259	else
260		dumbterm = 0;
261	fromatty = isatty(fileno(stdin));
262	ttyout = stdout;
263	if (isatty(fileno(ttyout))) {
264		verbose = 1;		/* verbose if to a tty */
265		if (! dumbterm) {
266#ifndef NO_EDITCOMPLETE
267			if (fromatty)	/* editing mode on if tty is usable */
268				editing = 1;
269#endif
270#ifndef NO_PROGRESS
271			if (foregroundproc())
272				progress = 1;	/* progress bar on if fg */
273#endif
274		}
275	}
276
277	while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:RtT:u:vV")) != -1) {
278		switch (ch) {
279		case '4':
280			family = AF_INET;
281			break;
282
283		case '6':
284#ifdef INET6
285			family = AF_INET6;
286#else
287			warnx("INET6 support is not available; ignoring -6");
288#endif
289			break;
290
291		case 'A':
292			activefallback = 0;
293			passivemode = 0;
294			break;
295
296		case 'a':
297			anonftp = 1;
298			break;
299
300		case 'd':
301			options |= SO_DEBUG;
302			debug++;
303			break;
304
305		case 'e':
306#ifndef NO_EDITCOMPLETE
307			editing = 0;
308#endif
309			break;
310
311		case 'f':
312			flushcache = 1;
313			break;
314
315		case 'g':
316			doglob = 0;
317			break;
318
319		case 'i':
320			interactive = 0;
321			break;
322
323		case 'n':
324			autologin = 0;
325			break;
326
327		case 'N':
328			if (strlcpy(netrc, optarg, sizeof(netrc))
329			    >= sizeof(netrc))
330				errx(1, "%s: %s", optarg,
331				    strerror(ENAMETOOLONG));
332			break;
333
334		case 'o':
335			outfile = optarg;
336			if (strcmp(outfile, "-") == 0)
337				ttyout = stderr;
338			break;
339
340		case 'p':
341			passivemode = 1;
342			activefallback = 0;
343			break;
344
345		case 'P':
346			ftpport = optarg;
347			break;
348
349		case 'q':
350			quit_time = strtol(optarg, &ep, 10);
351			if (quit_time < 1 || *ep != '\0')
352				errx(1, "bad quit value: %s", optarg);
353			break;
354
355		case 'r':
356			retry_connect = strtol(optarg, &ep, 10);
357			if (retry_connect < 1 || *ep != '\0')
358				errx(1, "bad retry value: %s", optarg);
359			break;
360
361		case 'R':
362			restartautofetch = 1;
363			break;
364
365		case 't':
366			trace = 1;
367			break;
368
369		case 'T':
370		{
371			int targc;
372			char *targv[6], *oac;
373
374				/* look for `dir,max[,incr]' */
375			targc = 0;
376			targv[targc++] = "-T";
377			oac = xstrdup(optarg);
378
379			while ((cp = strsep(&oac, ",")) != NULL) {
380				if (*cp == '\0') {
381					warnx("bad throttle value: %s", optarg);
382					usage();
383					/* NOTREACHED */
384				}
385				targv[targc++] = cp;
386				if (targc >= 5)
387					break;
388			}
389			if (parserate(targc, targv, 1) == -1)
390				usage();
391			free(oac);
392			break;
393		}
394
395		case 'u':
396		{
397			isupload = 1;
398			interactive = 0;
399			upload_path = xstrdup(optarg);
400
401			break;
402		}
403
404		case 'v':
405			progress = verbose = 1;
406			break;
407
408		case 'V':
409			progress = verbose = 0;
410			break;
411
412		default:
413			usage();
414		}
415	}
416			/* set line buffering on ttyout */
417	setvbuf(ttyout, NULL, _IOLBF, 0);
418	argc -= optind;
419	argv += optind;
420
421	cpend = 0;	/* no pending replies */
422	proxy = 0;	/* proxy not active */
423	crflag = 1;	/* strip c.r. on ascii gets */
424	sendport = -1;	/* not using ports */
425
426	/*
427	 * Cache the user name and home directory.
428	 */
429	localhome = NULL;
430	localname = NULL;
431	anonuser = "anonymous";
432	cp = getenv("HOME");
433	if (! EMPTYSTRING(cp))
434		localhome = xstrdup(cp);
435	pw = NULL;
436	cp = getlogin();
437	if (cp != NULL)
438		pw = getpwnam(cp);
439	if (pw == NULL)
440		pw = getpwuid(getuid());
441	if (pw != NULL) {
442		if (localhome == NULL && !EMPTYSTRING(pw->pw_dir))
443			localhome = xstrdup(pw->pw_dir);
444		localname = xstrdup(pw->pw_name);
445		anonuser = localname;
446	}
447	if (netrc[0] == '\0' && localhome != NULL) {
448		if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) ||
449		    strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) {
450			warnx("%s/.netrc: %s", localhome,
451			    strerror(ENAMETOOLONG));
452			netrc[0] = '\0';
453		}
454	}
455	if (localhome == NULL)
456		localhome = xstrdup("/");
457
458	/*
459	 * Every anonymous FTP server I've encountered will accept the
460	 * string "username@", and will append the hostname itself. We
461	 * do this by default since many servers are picky about not
462	 * having a FQDN in the anonymous password.
463	 * - thorpej@NetBSD.org
464	 */
465	len = strlen(anonuser) + 2;
466	anonpass = xmalloc(len);
467	(void)strlcpy(anonpass, anonuser, len);
468	(void)strlcat(anonpass, "@",	  len);
469
470			/*
471			 * set all the defaults for options defined in
472			 * struct option optiontab[]  declared in cmdtab.c
473			 */
474	setupoption("anonpass",		getenv("FTPANONPASS"),	anonpass);
475	setupoption("ftp_proxy",	getenv(FTP_PROXY),	"");
476	setupoption("http_proxy",	getenv(HTTP_PROXY),	"");
477	setupoption("no_proxy",		getenv(NO_PROXY),	"");
478	setupoption("pager",		getenv("PAGER"),	DEFAULTPAGER);
479	setupoption("prompt",		getenv("FTPPROMPT"),	DEFAULTPROMPT);
480	setupoption("rprompt",		getenv("FTPRPROMPT"),	DEFAULTRPROMPT);
481
482	free(anonpass);
483
484	setttywidth(0);
485#ifdef SIGINFO
486	(void)xsignal(SIGINFO, psummary);
487#endif
488	(void)xsignal(SIGQUIT, psummary);
489	(void)xsignal(SIGUSR1, crankrate);
490	(void)xsignal(SIGUSR2, crankrate);
491	(void)xsignal(SIGWINCH, setttywidth);
492
493#ifdef __GNUC__			/* to shut up gcc warnings */
494	(void)&argc;
495	(void)&argv;
496#endif
497
498	if (argc > 0) {
499		if (isupload) {
500			rval = auto_put(argc, argv, upload_path);
501 sigint_or_rval_exit:
502			if (sigint_raised) {
503				(void)xsignal(SIGINT, SIG_DFL);
504				raise(SIGINT);
505			}
506			exit(rval);
507		} else if (strchr(argv[0], ':') != NULL
508			    && ! isipv6addr(argv[0])) {
509			rval = auto_fetch(argc, argv);
510			if (rval >= 0)		/* -1 == connected and cd-ed */
511				goto sigint_or_rval_exit;
512		} else {
513			char *xargv[4], *user, *host;
514
515			if ((rval = sigsetjmp(toplevel, 1)))
516				goto sigint_or_rval_exit;
517			(void)xsignal(SIGINT, intr);
518			(void)xsignal(SIGPIPE, lostpeer);
519			user = NULL;
520			host = argv[0];
521			cp = strchr(host, '@');
522			if (cp) {
523				*cp = '\0';
524				user = host;
525				host = cp + 1;
526			}
527			/* XXX discards const */
528			xargv[0] = (char *)getprogname();
529			xargv[1] = host;
530			xargv[2] = argv[1];
531			xargv[3] = NULL;
532			do {
533				int oautologin;
534
535				oautologin = autologin;
536				if (user != NULL) {
537					anonftp = 0;
538					autologin = 0;
539				}
540				setpeer(argc+1, xargv);
541				autologin = oautologin;
542				if (connected == 1 && user != NULL)
543					(void)ftp_login(host, user, NULL);
544				if (!retry_connect)
545					break;
546				if (!connected) {
547					macnum = 0;
548					fprintf(ttyout,
549					    "Retrying in %d seconds...\n",
550					    retry_connect);
551					sleep(retry_connect);
552				}
553			} while (!connected);
554			retry_connect = 0; /* connected, stop hiding msgs */
555		}
556	}
557	if (isupload)
558		usage();
559
560#ifndef NO_EDITCOMPLETE
561	controlediting();
562#endif /* !NO_EDITCOMPLETE */
563
564	(void)sigsetjmp(toplevel, 1);
565	(void)xsignal(SIGINT, intr);
566	(void)xsignal(SIGPIPE, lostpeer);
567	for (;;)
568		cmdscanner();
569}
570
571/*
572 * Generate a prompt
573 */
574char *
575prompt(void)
576{
577	static char	**prompt;
578	static char	  buf[MAXPATHLEN];
579
580	if (prompt == NULL) {
581		struct option *o;
582
583		o = getoption("prompt");
584		if (o == NULL)
585			errx(1, "no such option `prompt'");
586		prompt = &(o->value);
587	}
588	formatbuf(buf, sizeof(buf), *prompt ? *prompt : DEFAULTPROMPT);
589	return (buf);
590}
591
592/*
593 * Generate an rprompt
594 */
595char *
596rprompt(void)
597{
598	static char	**rprompt;
599	static char	  buf[MAXPATHLEN];
600
601	if (rprompt == NULL) {
602		struct option *o;
603
604		o = getoption("rprompt");
605		if (o == NULL)
606			errx(1, "no such option `rprompt'");
607		rprompt = &(o->value);
608	}
609	formatbuf(buf, sizeof(buf), *rprompt ? *rprompt : DEFAULTRPROMPT);
610	return (buf);
611}
612
613/*
614 * Command parser.
615 */
616void
617cmdscanner(void)
618{
619	struct cmd	*c;
620	char		*p;
621	int		 num;
622
623	for (;;) {
624#ifndef NO_EDITCOMPLETE
625		if (!editing) {
626#endif /* !NO_EDITCOMPLETE */
627			if (fromatty) {
628				fputs(prompt(), ttyout);
629				p = rprompt();
630				if (*p)
631					fprintf(ttyout, "%s ", p);
632				(void)fflush(ttyout);
633			}
634			if (fgets(line, sizeof(line), stdin) == NULL) {
635				if (fromatty)
636					putc('\n', ttyout);
637				quit(0, NULL);
638			}
639			num = strlen(line);
640			if (num == 0)
641				break;
642			if (line[--num] == '\n') {
643				if (num == 0)
644					break;
645				line[num] = '\0';
646			} else if (num == sizeof(line) - 2) {
647				fputs("Sorry, input line is too long.\n",
648				    ttyout);
649				while ((num = getchar()) != '\n' && num != EOF)
650					/* void */;
651				break;
652			} /* else it was a line without a newline */
653#ifndef NO_EDITCOMPLETE
654		} else {
655			const char *buf;
656			HistEvent ev;
657			cursor_pos = NULL;
658
659			buf = el_gets(el, &num);
660			if (buf == NULL || num == 0) {
661				if (fromatty)
662					putc('\n', ttyout);
663				quit(0, NULL);
664			}
665			if (num >= sizeof(line)) {
666				fputs("Sorry, input line is too long.\n",
667				    ttyout);
668				break;
669			}
670			memcpy(line, buf, num);
671			if (line[--num] == '\n') {
672				line[num] = '\0';
673				if (num == 0)
674					break;
675			}
676			history(hist, &ev, H_ENTER, buf);
677		}
678#endif /* !NO_EDITCOMPLETE */
679
680		makeargv();
681		if (margc == 0)
682			continue;
683		c = getcmd(margv[0]);
684		if (c == (struct cmd *)-1) {
685			fputs("?Ambiguous command.\n", ttyout);
686			continue;
687		}
688		if (c == NULL) {
689#if !defined(NO_EDITCOMPLETE)
690			/*
691			 * attempt to el_parse() unknown commands.
692			 * any command containing a ':' would be parsed
693			 * as "[prog:]cmd ...", and will result in a
694			 * false positive if prog != "ftp", so treat
695			 * such commands as invalid.
696			 */
697			if (strchr(margv[0], ':') != NULL ||
698			    el_parse(el, margc, (const char **)margv) != 0)
699#endif /* !NO_EDITCOMPLETE */
700				fputs("?Invalid command.\n", ttyout);
701			continue;
702		}
703		if (c->c_conn && !connected) {
704			fputs("Not connected.\n", ttyout);
705			continue;
706		}
707		confirmrest = 0;
708		margv[0] = c->c_name;
709		(*c->c_handler)(margc, margv);
710		if (bell && c->c_bell)
711			(void)putc('\007', ttyout);
712		if (c->c_handler != help)
713			break;
714	}
715	(void)xsignal(SIGINT, intr);
716	(void)xsignal(SIGPIPE, lostpeer);
717}
718
719struct cmd *
720getcmd(const char *name)
721{
722	const char *p, *q;
723	struct cmd *c, *found;
724	int nmatches, longest;
725
726	if (name == NULL)
727		return (0);
728
729	longest = 0;
730	nmatches = 0;
731	found = 0;
732	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
733		for (q = name; *q == *p++; q++)
734			if (*q == 0)		/* exact match? */
735				return (c);
736		if (!*q) {			/* the name was a prefix */
737			if (q - name > longest) {
738				longest = q - name;
739				nmatches = 1;
740				found = c;
741			} else if (q - name == longest)
742				nmatches++;
743		}
744	}
745	if (nmatches > 1)
746		return ((struct cmd *)-1);
747	return (found);
748}
749
750/*
751 * Slice a string up into argc/argv.
752 */
753
754int slrflag;
755
756void
757makeargv(void)
758{
759	char *argp;
760
761	stringbase = line;		/* scan from first of buffer */
762	argbase = argbuf;		/* store from first of buffer */
763	slrflag = 0;
764	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
765	for (margc = 0; ; margc++) {
766		argp = slurpstring();
767		xsl_add(marg_sl, argp);
768		if (argp == NULL)
769			break;
770	}
771#ifndef NO_EDITCOMPLETE
772	if (cursor_pos == line) {
773		cursor_argc = 0;
774		cursor_argo = 0;
775	} else if (cursor_pos != NULL) {
776		cursor_argc = margc;
777		cursor_argo = strlen(margv[margc-1]);
778	}
779#endif /* !NO_EDITCOMPLETE */
780}
781
782#ifdef NO_EDITCOMPLETE
783#define	INC_CHKCURSOR(x)	(x)++
784#else  /* !NO_EDITCOMPLETE */
785#define	INC_CHKCURSOR(x)	{ (x)++ ; \
786				if (x == cursor_pos) { \
787					cursor_argc = margc; \
788					cursor_argo = ap-argbase; \
789					cursor_pos = NULL; \
790				} }
791
792#endif /* !NO_EDITCOMPLETE */
793
794/*
795 * Parse string into argbuf;
796 * implemented with FSM to
797 * handle quoting and strings
798 */
799char *
800slurpstring(void)
801{
802	int got_one = 0;
803	char *sb = stringbase;
804	char *ap = argbase;
805	char *tmp = argbase;		/* will return this if token found */
806
807	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
808		switch (slrflag) {	/* and $ as token for macro invoke */
809			case 0:
810				slrflag++;
811				INC_CHKCURSOR(stringbase);
812				return ((*sb == '!') ? "!" : "$");
813				/* NOTREACHED */
814			case 1:
815				slrflag++;
816				altarg = stringbase;
817				break;
818			default:
819				break;
820		}
821	}
822
823S0:
824	switch (*sb) {
825
826	case '\0':
827		goto OUT;
828
829	case ' ':
830	case '\t':
831		INC_CHKCURSOR(sb);
832		goto S0;
833
834	default:
835		switch (slrflag) {
836			case 0:
837				slrflag++;
838				break;
839			case 1:
840				slrflag++;
841				altarg = sb;
842				break;
843			default:
844				break;
845		}
846		goto S1;
847	}
848
849S1:
850	switch (*sb) {
851
852	case ' ':
853	case '\t':
854	case '\0':
855		goto OUT;	/* end of token */
856
857	case '\\':
858		INC_CHKCURSOR(sb);
859		goto S2;	/* slurp next character */
860
861	case '"':
862		INC_CHKCURSOR(sb);
863		goto S3;	/* slurp quoted string */
864
865	default:
866		*ap = *sb;	/* add character to token */
867		ap++;
868		INC_CHKCURSOR(sb);
869		got_one = 1;
870		goto S1;
871	}
872
873S2:
874	switch (*sb) {
875
876	case '\0':
877		goto OUT;
878
879	default:
880		*ap = *sb;
881		ap++;
882		INC_CHKCURSOR(sb);
883		got_one = 1;
884		goto S1;
885	}
886
887S3:
888	switch (*sb) {
889
890	case '\0':
891		goto OUT;
892
893	case '"':
894		INC_CHKCURSOR(sb);
895		goto S1;
896
897	default:
898		*ap = *sb;
899		ap++;
900		INC_CHKCURSOR(sb);
901		got_one = 1;
902		goto S3;
903	}
904
905OUT:
906	if (got_one)
907		*ap++ = '\0';
908	argbase = ap;			/* update storage pointer */
909	stringbase = sb;		/* update scan pointer */
910	if (got_one) {
911		return (tmp);
912	}
913	switch (slrflag) {
914		case 0:
915			slrflag++;
916			break;
917		case 1:
918			slrflag++;
919			altarg = NULL;
920			break;
921		default:
922			break;
923	}
924	return (NULL);
925}
926
927/*
928 * Help/usage command.
929 * Call each command handler with argc == 0 and argv[0] == name.
930 */
931void
932help(int argc, char *argv[])
933{
934	struct cmd *c;
935	char *nargv[1], *p, *cmd;
936	int isusage;
937
938	cmd = argv[0];
939	isusage = (strcmp(cmd, "usage") == 0);
940	if (argc == 0 || (isusage && argc == 1)) {
941		fprintf(ttyout, "usage: %s [command [...]]\n", cmd);
942		return;
943	}
944	if (argc == 1) {
945		StringList *buf;
946
947		buf = xsl_init();
948		fprintf(ttyout,
949		    "%sommands may be abbreviated.  Commands are:\n\n",
950		    proxy ? "Proxy c" : "C");
951		for (c = cmdtab; (p = c->c_name) != NULL; c++)
952			if (!proxy || c->c_proxy)
953				xsl_add(buf, p);
954		list_vertical(buf);
955		sl_free(buf, 0);
956		return;
957	}
958
959#define	HELPINDENT ((int) sizeof("disconnect"))
960
961	while (--argc > 0) {
962		char *arg;
963
964		arg = *++argv;
965		c = getcmd(arg);
966		if (c == (struct cmd *)-1)
967			fprintf(ttyout, "?Ambiguous %s command `%s'\n",
968			    cmd, arg);
969		else if (c == NULL)
970			fprintf(ttyout, "?Invalid %s command `%s'\n",
971			    cmd, arg);
972		else {
973			if (isusage) {
974				nargv[0] = c->c_name;
975				(*c->c_handler)(0, nargv);
976			} else
977				fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
978				    c->c_name, c->c_help);
979		}
980	}
981}
982
983struct option *
984getoption(const char *name)
985{
986	const char *p;
987	struct option *c;
988
989	if (name == NULL)
990		return (NULL);
991	for (c = optiontab; (p = c->name) != NULL; c++) {
992		if (strcasecmp(p, name) == 0)
993			return (c);
994	}
995	return (NULL);
996}
997
998char *
999getoptionvalue(const char *name)
1000{
1001	struct option *c;
1002
1003	if (name == NULL)
1004		errx(1, "getoptionvalue() invoked with NULL name");
1005	c = getoption(name);
1006	if (c != NULL)
1007		return (c->value);
1008	errx(1, "getoptionvalue() invoked with unknown option `%s'", name);
1009	/* NOTREACHED */
1010}
1011
1012static void
1013setupoption(char *name, char *value, char *defaultvalue)
1014{
1015	char *nargv[3];
1016	int overbose;
1017
1018	nargv[0] = "setupoption()";
1019	nargv[1] = name;
1020	nargv[2] = (value ? value : defaultvalue);
1021	overbose = verbose;
1022	verbose = 0;
1023	setoption(3, nargv);
1024	verbose = overbose;
1025}
1026
1027void
1028usage(void)
1029{
1030	const char *progname = getprogname();
1031
1032	(void)fprintf(stderr,
1033"usage: %s [-46AadefginpRtvV] [-N netrc] [-o outfile] [-P port] [-q quittime]\n"
1034"           [-r retry] [-T dir,max[,inc][[user@]host [port]]] [host:path[/]]\n"
1035"           [file:///file] [ftp://[user[:pass]@]host[:port]/path[/]]\n"
1036"           [http://[user[:pass]@]host[:port]/path] [...]\n"
1037"       %s -u URL file [...]\n", progname, progname);
1038	exit(1);
1039}
1040