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