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