xref: /openbsd-src/usr.bin/ftp/main.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: main.c,v 1.135 2020/09/06 09:49:11 tb Exp $	*/
2 /*	$NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $	*/
3 
4 /*
5  * Copyright (C) 1997 and 1998 WIDE Project.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the project nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * Copyright (c) 1985, 1989, 1993, 1994
35  *	The Regents of the University of California.  All rights reserved.
36  *
37  * Redistribution and use in source and binary forms, with or without
38  * modification, are permitted provided that the following conditions
39  * are met:
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  * 2. Redistributions in binary form must reproduce the above copyright
43  *    notice, this list of conditions and the following disclaimer in the
44  *    documentation and/or other materials provided with the distribution.
45  * 3. Neither the name of the University nor the names of its contributors
46  *    may be used to endorse or promote products derived from this software
47  *    without specific prior written permission.
48  *
49  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59  * SUCH DAMAGE.
60  */
61 
62 /*
63  * FTP User Program -- Command Interface.
64  */
65 #include <sys/types.h>
66 #include <sys/socket.h>
67 
68 #include <ctype.h>
69 #include <err.h>
70 #include <fcntl.h>
71 #include <netdb.h>
72 #include <pwd.h>
73 #include <stdio.h>
74 #include <errno.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <unistd.h>
78 
79 #include <tls.h>
80 
81 #include "cmds.h"
82 #include "ftp_var.h"
83 
84 int	trace;
85 int	hash;
86 int	mark;
87 int	sendport;
88 int	verbose;
89 int	connected;
90 int	fromatty;
91 int	interactive;
92 #ifndef SMALL
93 int	confirmrest;
94 int	debug;
95 int	bell;
96 char   *altarg;
97 #endif /* !SMALL */
98 int	doglob;
99 int	autologin;
100 int	proxy;
101 int	proxflag;
102 int	gatemode;
103 char   *gateserver;
104 int	sunique;
105 int	runique;
106 int	mcase;
107 int	ntflag;
108 int	mapflag;
109 int	preserve;
110 int	progress;
111 int	code;
112 int	crflag;
113 char	pasv[BUFSIZ];
114 int	passivemode;
115 int	activefallback;
116 char	ntin[17];
117 char	ntout[17];
118 char	mapin[PATH_MAX];
119 char	mapout[PATH_MAX];
120 char	typename[32];
121 int	type;
122 int	curtype;
123 char	structname[32];
124 int	stru;
125 char	formname[32];
126 int	form;
127 char	modename[32];
128 int	mode;
129 char	bytename[32];
130 int	bytesize;
131 int	anonftp;
132 int	dirchange;
133 unsigned int retry_connect;
134 int	ttywidth;
135 int	epsv4;
136 int	epsv4bad;
137 
138 #ifndef SMALL
139 int	  editing;
140 EditLine *el;
141 History  *hist;
142 char	 *cursor_pos;
143 size_t	  cursor_argc;
144 size_t	  cursor_argo;
145 int	  resume;
146 char	 *srcaddr;
147 #endif /* !SMALL */
148 
149 char	 *cookiefile;
150 
151 off_t	bytes;
152 off_t	filesize;
153 char   *direction;
154 
155 char   *hostname;
156 int	unix_server;
157 int	unix_proxy;
158 
159 char *ftpport;
160 char *httpport;
161 #ifndef NOSSL
162 char *httpsport;
163 #endif /* !SMALL */
164 char *httpuseragent;
165 char *gateport;
166 
167 jmp_buf	toplevel;
168 
169 #ifndef SMALL
170 char	line[FTPBUFLEN];
171 char	*argbase;
172 char	*stringbase;
173 char	argbuf[FTPBUFLEN];
174 StringList *marg_sl;
175 int	margc;
176 int	options;
177 #endif /* !SMALL */
178 
179 int	cpend;
180 int	mflag;
181 
182 #ifndef SMALL
183 int macnum;
184 struct macel macros[16];
185 char macbuf[4096];
186 #endif /* !SMALL */
187 
188 FILE	*ttyout;
189 
190 int connect_timeout;
191 
192 #ifndef NOSSL
193 char * const ssl_verify_opts[] = {
194 #define SSL_CAFILE		0
195 	"cafile",
196 #define SSL_CAPATH		1
197 	"capath",
198 #define SSL_CIPHERS		2
199 	"ciphers",
200 #define SSL_DONTVERIFY		3
201 	"dont",
202 #define SSL_DOVERIFY		4
203 	"do",
204 #define SSL_VERIFYDEPTH		5
205 	"depth",
206 #define SSL_MUSTSTAPLE		6
207 	"muststaple",
208 #define SSL_NOVERIFYTIME	7
209 	"noverifytime",
210 #define SSL_SESSION		8
211 	"session",
212 #define SSL_PROTOCOLS		9
213 	"protocols",
214 	NULL
215 };
216 
217 struct tls_config *tls_config;
218 int tls_session_fd = -1;
219 
220 static void
221 process_ssl_options(char *cp)
222 {
223 	const char *errstr;
224 	char *str;
225 	int depth;
226 	uint32_t protocols;
227 
228 	while (*cp) {
229 		switch (getsubopt(&cp, ssl_verify_opts, &str)) {
230 		case SSL_CAFILE:
231 			if (str == NULL)
232 				errx(1, "missing CA file");
233 			if (tls_config_set_ca_file(tls_config, str) != 0)
234 				errx(1, "tls ca file failed: %s",
235 				    tls_config_error(tls_config));
236 			break;
237 		case SSL_CAPATH:
238 			if (str == NULL)
239 				errx(1, "missing CA directory path");
240 			if (tls_config_set_ca_path(tls_config, str) != 0)
241 				errx(1, "tls ca path failed: %s",
242 				    tls_config_error(tls_config));
243 			break;
244 		case SSL_CIPHERS:
245 			if (str == NULL)
246 				errx(1, "missing cipher list");
247 			if (tls_config_set_ciphers(tls_config, str) != 0)
248 				errx(1, "tls ciphers failed: %s",
249 				    tls_config_error(tls_config));
250 			break;
251 		case SSL_DONTVERIFY:
252 			tls_config_insecure_noverifycert(tls_config);
253 			tls_config_insecure_noverifyname(tls_config);
254 			break;
255 		case SSL_DOVERIFY:
256 			tls_config_verify(tls_config);
257 			break;
258 		case SSL_VERIFYDEPTH:
259 			if (str == NULL)
260 				errx(1, "missing depth");
261 			depth = strtonum(str, 0, INT_MAX, &errstr);
262 			if (errstr)
263 				errx(1, "certificate validation depth is %s",
264 				    errstr);
265 			tls_config_set_verify_depth(tls_config, depth);
266 			break;
267 		case SSL_MUSTSTAPLE:
268 			tls_config_ocsp_require_stapling(tls_config);
269 			break;
270 		case SSL_NOVERIFYTIME:
271 			tls_config_insecure_noverifytime(tls_config);
272 			break;
273 		case SSL_SESSION:
274 			if (str == NULL)
275 				errx(1, "missing session file");
276 			if ((tls_session_fd = open(str, O_RDWR|O_CREAT,
277 			    0600)) == -1)
278 				err(1, "failed to open or create session file "
279 				    "'%s'", str);
280 			if (tls_config_set_session_fd(tls_config,
281 			    tls_session_fd) == -1)
282 				errx(1, "failed to set session: %s",
283 				    tls_config_error(tls_config));
284 			break;
285 		case SSL_PROTOCOLS:
286 			if (str == NULL)
287 				errx(1, "missing protocol name");
288 			if (tls_config_parse_protocols(&protocols, str) != 0)
289 				errx(1, "failed to parse TLS protocols");
290 			if (tls_config_set_protocols(tls_config, protocols) != 0)
291 				errx(1, "failed to set TLS protocols: %s",
292 				    tls_config_error(tls_config));
293 			break;
294 		default:
295 			errx(1, "unknown -S suboption `%s'",
296 			    suboptarg ? suboptarg : "");
297 			/* NOTREACHED */
298 		}
299 	}
300 }
301 #endif /* !NOSSL */
302 
303 int family = PF_UNSPEC;
304 int pipeout;
305 
306 int
307 main(volatile int argc, char *argv[])
308 {
309 	int ch, rval;
310 #ifndef SMALL
311 	int top;
312 #endif
313 	struct passwd *pw = NULL;
314 	char *cp, homedir[PATH_MAX];
315 	char *outfile = NULL;
316 	const char *errstr;
317 	int dumb_terminal = 0;
318 
319 	ftpport = "ftp";
320 	httpport = "http";
321 #ifndef NOSSL
322 	httpsport = "https";
323 #endif /* !NOSSL */
324 	gateport = getenv("FTPSERVERPORT");
325 	if (gateport == NULL || *gateport == '\0')
326 		gateport = "ftpgate";
327 	doglob = 1;
328 	interactive = 1;
329 	autologin = 1;
330 	passivemode = 1;
331 	activefallback = 1;
332 	preserve = 1;
333 	verbose = 0;
334 	progress = 0;
335 	gatemode = 0;
336 #ifndef NOSSL
337 	cookiefile = NULL;
338 #endif /* NOSSL */
339 #ifndef SMALL
340 	editing = 0;
341 	el = NULL;
342 	hist = NULL;
343 	resume = 0;
344 	srcaddr = NULL;
345 	marg_sl = sl_init();
346 #endif /* !SMALL */
347 	mark = HASHBYTES;
348 	epsv4 = 1;
349 	epsv4bad = 0;
350 
351 	/* Set default operation mode based on FTPMODE environment variable */
352 	if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') {
353 		if (strcmp(cp, "passive") == 0) {
354 			passivemode = 1;
355 			activefallback = 0;
356 		} else if (strcmp(cp, "active") == 0) {
357 			passivemode = 0;
358 			activefallback = 0;
359 		} else if (strcmp(cp, "gate") == 0) {
360 			gatemode = 1;
361 		} else if (strcmp(cp, "auto") == 0) {
362 			passivemode = 1;
363 			activefallback = 1;
364 		} else
365 			warnx("unknown FTPMODE: %s.  Using defaults", cp);
366 	}
367 
368 	if (strcmp(__progname, "gate-ftp") == 0)
369 		gatemode = 1;
370 	gateserver = getenv("FTPSERVER");
371 	if (gateserver == NULL)
372 		gateserver = "";
373 	if (gatemode) {
374 		if (*gateserver == '\0') {
375 			warnx(
376 "Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp");
377 			gatemode = 0;
378 		}
379 	}
380 
381 	cp = getenv("TERM");
382 	dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") ||
383 	    !strcmp(cp, "emacs") || !strcmp(cp, "su"));
384 	fromatty = isatty(fileno(stdin));
385 	if (fromatty) {
386 		verbose = 1;		/* verbose if from a tty */
387 #ifndef SMALL
388 		if (!dumb_terminal)
389 			editing = 1;	/* editing mode on if tty is usable */
390 #endif /* !SMALL */
391 	}
392 
393 	ttyout = stdout;
394 	if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc())
395 		progress = 1;		/* progress bar on if tty is usable */
396 
397 #ifndef NOSSL
398 	cookiefile = getenv("http_cookies");
399 	if (tls_init() != 0)
400 		errx(1, "tls init failed");
401 	if (tls_config == NULL) {
402 		tls_config = tls_config_new();
403 		if (tls_config == NULL)
404 			errx(1, "tls config failed");
405 		if (tls_config_set_protocols(tls_config,
406 		    TLS_PROTOCOLS_ALL) != 0)
407 			errx(1, "tls set protocols failed: %s",
408 			    tls_config_error(tls_config));
409 		if (tls_config_set_ciphers(tls_config, "legacy") != 0)
410 			errx(1, "tls set ciphers failed: %s",
411 			    tls_config_error(tls_config));
412 	}
413 #endif /* !NOSSL */
414 
415 	httpuseragent = NULL;
416 
417 	while ((ch = getopt(argc, argv,
418 		    "46AaCc:dD:EeN:gik:Mmno:pP:r:S:s:tU:vVw:")) != -1) {
419 		switch (ch) {
420 		case '4':
421 			family = PF_INET;
422 			break;
423 		case '6':
424 			family = PF_INET6;
425 			break;
426 		case 'A':
427 			activefallback = 0;
428 			passivemode = 0;
429 			break;
430 
431 		case 'N':
432 			setprogname(optarg);
433 			break;
434 		case 'a':
435 			anonftp = 1;
436 			break;
437 
438 		case 'C':
439 #ifndef SMALL
440 			resume = 1;
441 #endif /* !SMALL */
442 			break;
443 
444 		case 'c':
445 #ifndef SMALL
446 			cookiefile = optarg;
447 #endif /* !SMALL */
448 			break;
449 
450 		case 'D':
451 			action = optarg;
452 			break;
453 		case 'd':
454 #ifndef SMALL
455 			options |= SO_DEBUG;
456 			debug++;
457 #endif /* !SMALL */
458 			break;
459 
460 		case 'E':
461 			epsv4 = 0;
462 			break;
463 
464 		case 'e':
465 #ifndef SMALL
466 			editing = 0;
467 #endif /* !SMALL */
468 			break;
469 
470 		case 'g':
471 			doglob = 0;
472 			break;
473 
474 		case 'i':
475 			interactive = 0;
476 			break;
477 
478 		case 'k':
479 			keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
480 			    &errstr);
481 			if (errstr != NULL) {
482 				warnx("keep alive amount is %s: %s", errstr,
483 					optarg);
484 				usage();
485 			}
486 			break;
487 		case 'M':
488 			progress = 0;
489 			break;
490 		case 'm':
491 			progress = -1;
492 			break;
493 
494 		case 'n':
495 			autologin = 0;
496 			break;
497 
498 		case 'o':
499 			outfile = optarg;
500 			if (*outfile == '\0') {
501 				pipeout = 0;
502 				outfile = NULL;
503 				ttyout = stdout;
504 			} else {
505 				pipeout = strcmp(outfile, "-") == 0;
506 				ttyout = pipeout ? stderr : stdout;
507 			}
508 			break;
509 
510 		case 'p':
511 			passivemode = 1;
512 			activefallback = 0;
513 			break;
514 
515 		case 'P':
516 			ftpport = optarg;
517 			break;
518 
519 		case 'r':
520 			retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
521 			if (errstr != NULL) {
522 				warnx("retry amount is %s: %s", errstr,
523 					optarg);
524 				usage();
525 			}
526 			break;
527 
528 		case 'S':
529 #ifndef NOSSL
530 			process_ssl_options(optarg);
531 #endif /* !NOSSL */
532 			break;
533 
534 		case 's':
535 #ifndef SMALL
536 			srcaddr = optarg;
537 #endif /* !SMALL */
538 			break;
539 
540 		case 't':
541 			trace = 1;
542 			break;
543 
544 #ifndef SMALL
545 		case 'U':
546 			free (httpuseragent);
547 			if (strcspn(optarg, "\r\n") != strlen(optarg))
548 				errx(1, "Invalid User-Agent: %s.", optarg);
549 			if (asprintf(&httpuseragent, "User-Agent: %s",
550 			    optarg) == -1)
551 				errx(1, "Can't allocate memory for HTTP(S) "
552 				    "User-Agent");
553 			break;
554 #endif /* !SMALL */
555 
556 		case 'v':
557 			verbose = 1;
558 			break;
559 
560 		case 'V':
561 			verbose = 0;
562 			break;
563 
564 		case 'w':
565 			connect_timeout = strtonum(optarg, 0, 200, &errstr);
566 			if (errstr)
567 				errx(1, "-w: %s", errstr);
568 			break;
569 		default:
570 			usage();
571 		}
572 	}
573 	argc -= optind;
574 	argv += optind;
575 
576 #ifndef NOSSL
577 	cookie_load();
578 #endif /* !NOSSL */
579 	if (httpuseragent == NULL)
580 		httpuseragent = HTTP_USER_AGENT;
581 
582 	cpend = 0;	/* no pending replies */
583 	proxy = 0;	/* proxy not active */
584 	crflag = 1;	/* strip c.r. on ascii gets */
585 	sendport = -1;	/* not using ports */
586 	/*
587 	 * Set up the home directory in case we're globbing.
588 	 */
589 	cp = getlogin();
590 	if (cp != NULL) {
591 		pw = getpwnam(cp);
592 	}
593 	if (pw == NULL)
594 		pw = getpwuid(getuid());
595 	if (pw != NULL) {
596 		(void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
597 		home = homedir;
598 	}
599 
600 	setttywidth(0);
601 	(void)signal(SIGWINCH, setttywidth);
602 
603 	if (argc > 0) {
604 		if (isurl(argv[0])) {
605 			if (pipeout) {
606 #ifndef SMALL
607 				if (pledge("stdio rpath dns tty inet proc exec fattr",
608 				    NULL) == -1)
609 					err(1, "pledge");
610 #else
611 				if (pledge("stdio rpath dns tty inet fattr",
612 				    NULL) == -1)
613 					err(1, "pledge");
614 #endif
615 			} else {
616 #ifndef SMALL
617 				if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
618 				    NULL) == -1)
619 					err(1, "pledge");
620 #else
621 				if (pledge("stdio rpath wpath cpath dns tty inet fattr",
622 				    NULL) == -1)
623 					err(1, "pledge");
624 #endif
625 			}
626 
627 			rval = auto_fetch(argc, argv, outfile);
628 			if (rval >= 0)		/* -1 == connected and cd-ed */
629 				exit(rval);
630 		} else {
631 #ifndef SMALL
632 			char *xargv[5];
633 
634 			if (setjmp(toplevel))
635 				exit(0);
636 			(void)signal(SIGINT, (sig_t)intr);
637 			(void)signal(SIGPIPE, (sig_t)lostpeer);
638 			xargv[0] = __progname;
639 			xargv[1] = argv[0];
640 			xargv[2] = argv[1];
641 			xargv[3] = argv[2];
642 			xargv[4] = NULL;
643 			do {
644 				setpeer(argc+1, xargv);
645 				if (!retry_connect)
646 					break;
647 				if (!connected) {
648 					macnum = 0;
649 					fputs("Retrying...\n", ttyout);
650 					sleep(retry_connect);
651 				}
652 			} while (!connected);
653 			retry_connect = 0; /* connected, stop hiding msgs */
654 #endif /* !SMALL */
655 		}
656 	}
657 #ifndef SMALL
658 	controlediting();
659 	top = setjmp(toplevel) == 0;
660 	if (top) {
661 		(void)signal(SIGINT, (sig_t)intr);
662 		(void)signal(SIGPIPE, (sig_t)lostpeer);
663 	}
664 	for (;;) {
665 		cmdscanner(top);
666 		top = 1;
667 	}
668 #else /* !SMALL */
669 	usage();
670 #endif /* !SMALL */
671 }
672 
673 void
674 intr(void)
675 {
676 	int save_errno = errno;
677 
678 	write(fileno(ttyout), "\n\r", 2);
679 	alarmtimer(0);
680 
681 	errno = save_errno;
682 	longjmp(toplevel, 1);
683 }
684 
685 void
686 lostpeer(void)
687 {
688 	int save_errno = errno;
689 
690 	alarmtimer(0);
691 	if (connected) {
692 		if (cout != NULL) {
693 			(void)shutdown(fileno(cout), SHUT_RDWR);
694 			(void)fclose(cout);
695 			cout = NULL;
696 		}
697 		if (data >= 0) {
698 			(void)shutdown(data, SHUT_RDWR);
699 			(void)close(data);
700 			data = -1;
701 		}
702 		connected = 0;
703 	}
704 	pswitch(1);
705 	if (connected) {
706 		if (cout != NULL) {
707 			(void)shutdown(fileno(cout), SHUT_RDWR);
708 			(void)fclose(cout);
709 			cout = NULL;
710 		}
711 		connected = 0;
712 	}
713 	proxflag = 0;
714 	pswitch(0);
715 	errno = save_errno;
716 }
717 
718 #ifndef SMALL
719 /*
720  * Generate a prompt
721  */
722 char *
723 prompt(void)
724 {
725 	return ("ftp> ");
726 }
727 
728 /*
729  * Command parser.
730  */
731 void
732 cmdscanner(int top)
733 {
734 	struct cmd *c;
735 	int num;
736 	HistEvent hev;
737 
738 	if (!top && !editing)
739 		(void)putc('\n', ttyout);
740 	for (;;) {
741 		if (!editing) {
742 			if (fromatty) {
743 				fputs(prompt(), ttyout);
744 				(void)fflush(ttyout);
745 			}
746 			if (fgets(line, sizeof(line), stdin) == NULL)
747 				quit(0, 0);
748 			num = strlen(line);
749 			if (num == 0)
750 				break;
751 			if (line[--num] == '\n') {
752 				if (num == 0)
753 					break;
754 				line[num] = '\0';
755 			} else if (num == sizeof(line) - 2) {
756 				fputs("sorry, input line too long.\n", ttyout);
757 				while ((num = getchar()) != '\n' && num != EOF)
758 					/* void */;
759 				break;
760 			} /* else it was a line without a newline */
761 		} else {
762 			const char *buf;
763 			cursor_pos = NULL;
764 
765 			if ((buf = el_gets(el, &num)) == NULL || num == 0) {
766 				putc('\n', ttyout);
767 				fflush(ttyout);
768 				quit(0, 0);
769 			}
770 			if (buf[--num] == '\n') {
771 				if (num == 0)
772 					break;
773 			}
774 			if (num >= sizeof(line)) {
775 				fputs("sorry, input line too long.\n", ttyout);
776 				break;
777 			}
778 			memcpy(line, buf, (size_t)num);
779 			line[num] = '\0';
780 			history(hist, &hev, H_ENTER, buf);
781 		}
782 
783 		makeargv();
784 		if (margc == 0)
785 			continue;
786 		c = getcmd(margv[0]);
787 		if (c == (struct cmd *)-1) {
788 			fputs("?Ambiguous command.\n", ttyout);
789 			continue;
790 		}
791 		if (c == 0) {
792 			/*
793 			 * Give editline(3) a shot at unknown commands.
794 			 * XXX - bogus commands with a colon in
795 			 *       them will not elicit an error.
796 			 */
797 			if (editing &&
798 			    el_parse(el, margc, (const char **)margv) != 0)
799 				fputs("?Invalid command.\n", ttyout);
800 			continue;
801 		}
802 		if (c->c_conn && !connected) {
803 			fputs("Not connected.\n", ttyout);
804 			continue;
805 		}
806 		confirmrest = 0;
807 		(*c->c_handler)(margc, margv);
808 		if (bell && c->c_bell)
809 			(void)putc('\007', ttyout);
810 		if (c->c_handler != help)
811 			break;
812 	}
813 	(void)signal(SIGINT, (sig_t)intr);
814 	(void)signal(SIGPIPE, (sig_t)lostpeer);
815 }
816 
817 struct cmd *
818 getcmd(const char *name)
819 {
820 	const char *p, *q;
821 	struct cmd *c, *found;
822 	int nmatches, longest;
823 
824 	if (name == NULL)
825 		return (0);
826 
827 	longest = 0;
828 	nmatches = 0;
829 	found = 0;
830 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
831 		for (q = name; *q == *p++; q++)
832 			if (*q == 0)		/* exact match? */
833 				return (c);
834 		if (!*q) {			/* the name was a prefix */
835 			if (q - name > longest) {
836 				longest = q - name;
837 				nmatches = 1;
838 				found = c;
839 			} else if (q - name == longest)
840 				nmatches++;
841 		}
842 	}
843 	if (nmatches > 1)
844 		return ((struct cmd *)-1);
845 	return (found);
846 }
847 
848 /*
849  * Slice a string up into argc/argv.
850  */
851 
852 int slrflag;
853 
854 void
855 makeargv(void)
856 {
857 	char *argp;
858 
859 	stringbase = line;		/* scan from first of buffer */
860 	argbase = argbuf;		/* store from first of buffer */
861 	slrflag = 0;
862 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
863 	for (margc = 0; ; margc++) {
864 		argp = slurpstring();
865 		sl_add(marg_sl, argp);
866 		if (argp == NULL)
867 			break;
868 	}
869 	if (cursor_pos == line) {
870 		cursor_argc = 0;
871 		cursor_argo = 0;
872 	} else if (cursor_pos != NULL) {
873 		cursor_argc = margc;
874 		cursor_argo = strlen(margv[margc-1]);
875 	}
876 }
877 
878 #define INC_CHKCURSOR(x)	{ (x)++ ; \
879 				if (x == cursor_pos) { \
880 					cursor_argc = margc; \
881 					cursor_argo = ap-argbase; \
882 					cursor_pos = NULL; \
883 				} }
884 
885 /*
886  * Parse string into argbuf;
887  * implemented with FSM to
888  * handle quoting and strings
889  */
890 char *
891 slurpstring(void)
892 {
893 	int got_one = 0;
894 	char *sb = stringbase;
895 	char *ap = argbase;
896 	char *tmp = argbase;		/* will return this if token found */
897 
898 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
899 		switch (slrflag) {	/* and $ as token for macro invoke */
900 			case 0:
901 				slrflag++;
902 				INC_CHKCURSOR(stringbase);
903 				return ((*sb == '!') ? "!" : "$");
904 				/* NOTREACHED */
905 			case 1:
906 				slrflag++;
907 				altarg = stringbase;
908 				break;
909 			default:
910 				break;
911 		}
912 	}
913 
914 S0:
915 	switch (*sb) {
916 
917 	case '\0':
918 		goto OUT;
919 
920 	case ' ':
921 	case '\t':
922 		INC_CHKCURSOR(sb);
923 		goto S0;
924 
925 	default:
926 		switch (slrflag) {
927 			case 0:
928 				slrflag++;
929 				break;
930 			case 1:
931 				slrflag++;
932 				altarg = sb;
933 				break;
934 			default:
935 				break;
936 		}
937 		goto S1;
938 	}
939 
940 S1:
941 	switch (*sb) {
942 
943 	case ' ':
944 	case '\t':
945 	case '\0':
946 		goto OUT;	/* end of token */
947 
948 	case '\\':
949 		INC_CHKCURSOR(sb);
950 		goto S2;	/* slurp next character */
951 
952 	case '"':
953 		INC_CHKCURSOR(sb);
954 		goto S3;	/* slurp quoted string */
955 
956 	default:
957 		*ap = *sb;	/* add character to token */
958 		ap++;
959 		INC_CHKCURSOR(sb);
960 		got_one = 1;
961 		goto S1;
962 	}
963 
964 S2:
965 	switch (*sb) {
966 
967 	case '\0':
968 		goto OUT;
969 
970 	default:
971 		*ap = *sb;
972 		ap++;
973 		INC_CHKCURSOR(sb);
974 		got_one = 1;
975 		goto S1;
976 	}
977 
978 S3:
979 	switch (*sb) {
980 
981 	case '\0':
982 		goto OUT;
983 
984 	case '"':
985 		INC_CHKCURSOR(sb);
986 		goto S1;
987 
988 	default:
989 		*ap = *sb;
990 		ap++;
991 		INC_CHKCURSOR(sb);
992 		got_one = 1;
993 		goto S3;
994 	}
995 
996 OUT:
997 	if (got_one)
998 		*ap++ = '\0';
999 	argbase = ap;			/* update storage pointer */
1000 	stringbase = sb;		/* update scan pointer */
1001 	if (got_one) {
1002 		return (tmp);
1003 	}
1004 	switch (slrflag) {
1005 		case 0:
1006 			slrflag++;
1007 			break;
1008 		case 1:
1009 			slrflag++;
1010 			altarg = (char *) 0;
1011 			break;
1012 		default:
1013 			break;
1014 	}
1015 	return (NULL);
1016 }
1017 
1018 /*
1019  * Help command.
1020  * Call each command handler with argc == 0 and argv[0] == name.
1021  */
1022 void
1023 help(int argc, char *argv[])
1024 {
1025 	struct cmd *c;
1026 
1027 	if (argc == 1) {
1028 		StringList *buf;
1029 
1030 		buf = sl_init();
1031 		fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
1032 		    proxy ? "Proxy c" : "C");
1033 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
1034 			if (c->c_name && (!proxy || c->c_proxy))
1035 				sl_add(buf, c->c_name);
1036 		list_vertical(buf);
1037 		sl_free(buf, 0);
1038 		return;
1039 	}
1040 
1041 #define HELPINDENT ((int) sizeof("disconnect"))
1042 
1043 	while (--argc > 0) {
1044 		char *arg;
1045 
1046 		arg = *++argv;
1047 		c = getcmd(arg);
1048 		if (c == (struct cmd *)-1)
1049 			fprintf(ttyout, "?Ambiguous help command %s\n", arg);
1050 		else if (c == NULL)
1051 			fprintf(ttyout, "?Invalid help command %s\n", arg);
1052 		else
1053 			fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
1054 				c->c_name, c->c_help);
1055 	}
1056 }
1057 #endif /* !SMALL */
1058 
1059 __dead void
1060 usage(void)
1061 {
1062 	fprintf(stderr, "usage: "
1063 #ifndef SMALL
1064 	    "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
1065 	    "[-r seconds]\n"
1066 	    "           [-s sourceaddr] [host [port]]\n"
1067 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr]\n"
1068 	    "           ftp://[user:password@]host[:port]/file[/] ...\n"
1069 	    "       ftp [-C] [-c cookie] [-N name] [-o output] [-S ssl_options] "
1070 	    "[-s sourceaddr]\n"
1071 	    "           [-U useragent] [-w seconds] "
1072 	    "http[s]://[user:password@]host[:port]/file ...\n"
1073 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr] file:file ...\n"
1074 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr] host:/file[/] ...\n"
1075 #else /* !SMALL */
1076 	    "ftp [-N name] [-o output] "
1077 	    "ftp://[user:password@]host[:port]/file[/] ...\n"
1078 #ifndef NOSSL
1079 	    "       ftp [-N name] [-o output] [-S ssl_options] [-w seconds] "
1080 	    "http[s]://[user:password@]host[:port]/file ...\n"
1081 #else
1082 	    "       ftp [-N name] [-o output] [-w seconds] http://host[:port]/file ...\n"
1083 #endif /* NOSSL */
1084 	    "       ftp [-N name] [-o output] file:file ...\n"
1085 	    "       ftp [-N name] [-o output] host:/file[/] ...\n"
1086 #endif /* !SMALL */
1087 	    );
1088 	exit(1);
1089 }
1090