xref: /openbsd-src/usr.bin/ftp/main.c (revision 4e1ee0786f11cc571bd0be17d38e46f635c719fc)
1 /*	$OpenBSD: main.c,v 1.138 2021/07/14 13:33:57 kn 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_config == NULL) {
407 		tls_config = tls_config_new();
408 		if (tls_config == NULL)
409 			errx(1, "tls config failed");
410 		if (tls_config_set_protocols(tls_config,
411 		    TLS_PROTOCOLS_ALL) != 0)
412 			errx(1, "tls set protocols failed: %s",
413 			    tls_config_error(tls_config));
414 		if (tls_config_set_ciphers(tls_config, "legacy") != 0)
415 			errx(1, "tls set ciphers failed: %s",
416 			    tls_config_error(tls_config));
417 	}
418 #endif /* !NOSSL */
419 
420 	httpuseragent = NULL;
421 
422 	while ((ch = getopt(argc, argv,
423 		    "46AaCc:dD:EeN:gik:Mmno:pP:r:S:s:TtU:uvVw:")) != -1) {
424 		switch (ch) {
425 		case '4':
426 			family = PF_INET;
427 			break;
428 		case '6':
429 			family = PF_INET6;
430 			break;
431 		case 'A':
432 			activefallback = 0;
433 			passivemode = 0;
434 			break;
435 
436 		case 'N':
437 			setprogname(optarg);
438 			break;
439 		case 'a':
440 			anonftp = 1;
441 			break;
442 
443 		case 'C':
444 #ifndef SMALL
445 			resume = 1;
446 #endif /* !SMALL */
447 			break;
448 
449 		case 'c':
450 #ifndef SMALL
451 			cookiefile = optarg;
452 #endif /* !SMALL */
453 			break;
454 
455 		case 'D':
456 			action = optarg;
457 			break;
458 		case 'd':
459 #ifndef SMALL
460 			options |= SO_DEBUG;
461 			debug++;
462 #endif /* !SMALL */
463 			break;
464 
465 		case 'E':
466 			epsv4 = 0;
467 			break;
468 
469 		case 'e':
470 #ifndef SMALL
471 			editing = 0;
472 #endif /* !SMALL */
473 			break;
474 
475 		case 'g':
476 			doglob = 0;
477 			break;
478 
479 		case 'i':
480 			interactive = 0;
481 			break;
482 
483 		case 'k':
484 			keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
485 			    &errstr);
486 			if (errstr != NULL) {
487 				warnx("keep alive amount is %s: %s", errstr,
488 					optarg);
489 				usage();
490 			}
491 			break;
492 		case 'M':
493 			progress = 0;
494 			break;
495 		case 'm':
496 			progress = -1;
497 			break;
498 
499 		case 'n':
500 			autologin = 0;
501 			break;
502 
503 		case 'o':
504 			outfile = optarg;
505 			if (*outfile == '\0') {
506 				pipeout = 0;
507 				outfile = NULL;
508 				ttyout = stdout;
509 			} else {
510 				pipeout = strcmp(outfile, "-") == 0;
511 				ttyout = pipeout ? stderr : stdout;
512 			}
513 			break;
514 
515 		case 'p':
516 			passivemode = 1;
517 			activefallback = 0;
518 			break;
519 
520 		case 'P':
521 			ftpport = optarg;
522 			break;
523 
524 		case 'r':
525 			retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
526 			if (errstr != NULL) {
527 				warnx("retry amount is %s: %s", errstr,
528 					optarg);
529 				usage();
530 			}
531 			break;
532 
533 		case 'S':
534 #ifndef NOSSL
535 			process_ssl_options(optarg);
536 #endif /* !NOSSL */
537 			break;
538 
539 		case 's':
540 #ifndef SMALL
541 			srcaddr = optarg;
542 #endif /* !SMALL */
543 			break;
544 
545 #ifndef SMALL
546 		case 'T':
547 			timestamp = 1;
548 			break;
549 #endif /* !SMALL */
550 		case 't':
551 			trace = 1;
552 			break;
553 
554 #ifndef SMALL
555 		case 'U':
556 			free (httpuseragent);
557 			if (strcspn(optarg, "\r\n") != strlen(optarg))
558 				errx(1, "Invalid User-Agent: %s.", optarg);
559 			if (asprintf(&httpuseragent, "User-Agent: %s",
560 			    optarg) == -1)
561 				errx(1, "Can't allocate memory for HTTP(S) "
562 				    "User-Agent");
563 			break;
564 		case 'u':
565 			server_timestamps = 0;
566 			break;
567 #endif /* !SMALL */
568 
569 		case 'v':
570 			verbose = 1;
571 			break;
572 
573 		case 'V':
574 			verbose = 0;
575 			break;
576 
577 		case 'w':
578 			connect_timeout = strtonum(optarg, 0, 200, &errstr);
579 			if (errstr)
580 				errx(1, "-w: %s", errstr);
581 			break;
582 		default:
583 			usage();
584 		}
585 	}
586 	argc -= optind;
587 	argv += optind;
588 
589 #ifndef NOSSL
590 	cookie_load();
591 #endif /* !NOSSL */
592 	if (httpuseragent == NULL)
593 		httpuseragent = HTTP_USER_AGENT;
594 
595 	cpend = 0;	/* no pending replies */
596 	proxy = 0;	/* proxy not active */
597 	crflag = 1;	/* strip c.r. on ascii gets */
598 	sendport = -1;	/* not using ports */
599 	/*
600 	 * Set up the home directory in case we're globbing.
601 	 */
602 	cp = getlogin();
603 	if (cp != NULL) {
604 		pw = getpwnam(cp);
605 	}
606 	if (pw == NULL)
607 		pw = getpwuid(getuid());
608 	if (pw != NULL) {
609 		(void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
610 		home = homedir;
611 	}
612 
613 	setttywidth(0);
614 	(void)signal(SIGWINCH, setttywidth);
615 
616 	if (argc > 0) {
617 		if (isurl(argv[0])) {
618 			if (pipeout) {
619 #ifndef SMALL
620 				if (pledge("stdio rpath dns tty inet proc exec fattr",
621 				    NULL) == -1)
622 					err(1, "pledge");
623 #else
624 				if (pledge("stdio rpath dns tty inet fattr",
625 				    NULL) == -1)
626 					err(1, "pledge");
627 #endif
628 			} else {
629 #ifndef SMALL
630 				if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
631 				    NULL) == -1)
632 					err(1, "pledge");
633 #else
634 				if (pledge("stdio rpath wpath cpath dns tty inet fattr",
635 				    NULL) == -1)
636 					err(1, "pledge");
637 #endif
638 			}
639 
640 			rval = auto_fetch(argc, argv, outfile);
641 			if (rval >= 0)		/* -1 == connected and cd-ed */
642 				exit(rval);
643 		} else {
644 #ifndef SMALL
645 			char *xargv[5];
646 
647 			if (setjmp(toplevel))
648 				exit(0);
649 			(void)signal(SIGINT, (sig_t)intr);
650 			(void)signal(SIGPIPE, (sig_t)lostpeer);
651 			xargv[0] = __progname;
652 			xargv[1] = argv[0];
653 			xargv[2] = argv[1];
654 			xargv[3] = argv[2];
655 			xargv[4] = NULL;
656 			do {
657 				setpeer(argc+1, xargv);
658 				if (!retry_connect)
659 					break;
660 				if (!connected) {
661 					macnum = 0;
662 					fputs("Retrying...\n", ttyout);
663 					sleep(retry_connect);
664 				}
665 			} while (!connected);
666 			retry_connect = 0; /* connected, stop hiding msgs */
667 #endif /* !SMALL */
668 		}
669 	}
670 #ifndef SMALL
671 	controlediting();
672 	top = setjmp(toplevel) == 0;
673 	if (top) {
674 		(void)signal(SIGINT, (sig_t)intr);
675 		(void)signal(SIGPIPE, (sig_t)lostpeer);
676 	}
677 	for (;;) {
678 		cmdscanner(top);
679 		top = 1;
680 	}
681 #else /* !SMALL */
682 	usage();
683 #endif /* !SMALL */
684 }
685 
686 void
687 intr(void)
688 {
689 	int save_errno = errno;
690 
691 	write(fileno(ttyout), "\n\r", 2);
692 	alarmtimer(0);
693 
694 	errno = save_errno;
695 	longjmp(toplevel, 1);
696 }
697 
698 void
699 lostpeer(void)
700 {
701 	int save_errno = errno;
702 
703 	alarmtimer(0);
704 	if (connected) {
705 		if (cout != NULL) {
706 			(void)shutdown(fileno(cout), SHUT_RDWR);
707 			(void)fclose(cout);
708 			cout = NULL;
709 		}
710 		if (data >= 0) {
711 			(void)shutdown(data, SHUT_RDWR);
712 			(void)close(data);
713 			data = -1;
714 		}
715 		connected = 0;
716 	}
717 	pswitch(1);
718 	if (connected) {
719 		if (cout != NULL) {
720 			(void)shutdown(fileno(cout), SHUT_RDWR);
721 			(void)fclose(cout);
722 			cout = NULL;
723 		}
724 		connected = 0;
725 	}
726 	proxflag = 0;
727 	pswitch(0);
728 	errno = save_errno;
729 }
730 
731 #ifndef SMALL
732 /*
733  * Generate a prompt
734  */
735 char *
736 prompt(void)
737 {
738 	return ("ftp> ");
739 }
740 
741 /*
742  * Command parser.
743  */
744 void
745 cmdscanner(int top)
746 {
747 	struct cmd *c;
748 	int num;
749 	HistEvent hev;
750 
751 	if (!top && !editing)
752 		(void)putc('\n', ttyout);
753 	for (;;) {
754 		if (!editing) {
755 			if (fromatty) {
756 				fputs(prompt(), ttyout);
757 				(void)fflush(ttyout);
758 			}
759 			if (fgets(line, sizeof(line), stdin) == NULL)
760 				quit(0, 0);
761 			num = strlen(line);
762 			if (num == 0)
763 				break;
764 			if (line[--num] == '\n') {
765 				if (num == 0)
766 					break;
767 				line[num] = '\0';
768 			} else if (num == sizeof(line) - 2) {
769 				fputs("sorry, input line too long.\n", ttyout);
770 				while ((num = getchar()) != '\n' && num != EOF)
771 					/* void */;
772 				break;
773 			} /* else it was a line without a newline */
774 		} else {
775 			const char *buf;
776 			cursor_pos = NULL;
777 
778 			if ((buf = el_gets(el, &num)) == NULL || num == 0) {
779 				putc('\n', ttyout);
780 				fflush(ttyout);
781 				quit(0, 0);
782 			}
783 			if (buf[--num] == '\n') {
784 				if (num == 0)
785 					break;
786 			}
787 			if (num >= sizeof(line)) {
788 				fputs("sorry, input line too long.\n", ttyout);
789 				break;
790 			}
791 			memcpy(line, buf, (size_t)num);
792 			line[num] = '\0';
793 			history(hist, &hev, H_ENTER, buf);
794 		}
795 
796 		makeargv();
797 		if (margc == 0)
798 			continue;
799 		c = getcmd(margv[0]);
800 		if (c == (struct cmd *)-1) {
801 			fputs("?Ambiguous command.\n", ttyout);
802 			continue;
803 		}
804 		if (c == 0) {
805 			/*
806 			 * Give editline(3) a shot at unknown commands.
807 			 * XXX - bogus commands with a colon in
808 			 *       them will not elicit an error.
809 			 */
810 			if (editing &&
811 			    el_parse(el, margc, (const char **)margv) != 0)
812 				fputs("?Invalid command.\n", ttyout);
813 			continue;
814 		}
815 		if (c->c_conn && !connected) {
816 			fputs("Not connected.\n", ttyout);
817 			continue;
818 		}
819 		confirmrest = 0;
820 		(*c->c_handler)(margc, margv);
821 		if (bell && c->c_bell)
822 			(void)putc('\007', ttyout);
823 		if (c->c_handler != help)
824 			break;
825 	}
826 	(void)signal(SIGINT, (sig_t)intr);
827 	(void)signal(SIGPIPE, (sig_t)lostpeer);
828 }
829 
830 struct cmd *
831 getcmd(const char *name)
832 {
833 	const char *p, *q;
834 	struct cmd *c, *found;
835 	int nmatches, longest;
836 
837 	if (name == NULL)
838 		return (0);
839 
840 	longest = 0;
841 	nmatches = 0;
842 	found = 0;
843 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
844 		for (q = name; *q == *p++; q++)
845 			if (*q == 0)		/* exact match? */
846 				return (c);
847 		if (!*q) {			/* the name was a prefix */
848 			if (q - name > longest) {
849 				longest = q - name;
850 				nmatches = 1;
851 				found = c;
852 			} else if (q - name == longest)
853 				nmatches++;
854 		}
855 	}
856 	if (nmatches > 1)
857 		return ((struct cmd *)-1);
858 	return (found);
859 }
860 
861 /*
862  * Slice a string up into argc/argv.
863  */
864 
865 int slrflag;
866 
867 void
868 makeargv(void)
869 {
870 	char *argp;
871 
872 	stringbase = line;		/* scan from first of buffer */
873 	argbase = argbuf;		/* store from first of buffer */
874 	slrflag = 0;
875 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
876 	for (margc = 0; ; margc++) {
877 		argp = slurpstring();
878 		sl_add(marg_sl, argp);
879 		if (argp == NULL)
880 			break;
881 	}
882 	if (cursor_pos == line) {
883 		cursor_argc = 0;
884 		cursor_argo = 0;
885 	} else if (cursor_pos != NULL) {
886 		cursor_argc = margc;
887 		cursor_argo = strlen(margv[margc-1]);
888 	}
889 }
890 
891 #define INC_CHKCURSOR(x)	{ (x)++ ; \
892 				if (x == cursor_pos) { \
893 					cursor_argc = margc; \
894 					cursor_argo = ap-argbase; \
895 					cursor_pos = NULL; \
896 				} }
897 
898 /*
899  * Parse string into argbuf;
900  * implemented with FSM to
901  * handle quoting and strings
902  */
903 char *
904 slurpstring(void)
905 {
906 	int got_one = 0;
907 	char *sb = stringbase;
908 	char *ap = argbase;
909 	char *tmp = argbase;		/* will return this if token found */
910 
911 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
912 		switch (slrflag) {	/* and $ as token for macro invoke */
913 			case 0:
914 				slrflag++;
915 				INC_CHKCURSOR(stringbase);
916 				return ((*sb == '!') ? "!" : "$");
917 				/* NOTREACHED */
918 			case 1:
919 				slrflag++;
920 				altarg = stringbase;
921 				break;
922 			default:
923 				break;
924 		}
925 	}
926 
927 S0:
928 	switch (*sb) {
929 
930 	case '\0':
931 		goto OUT;
932 
933 	case ' ':
934 	case '\t':
935 		INC_CHKCURSOR(sb);
936 		goto S0;
937 
938 	default:
939 		switch (slrflag) {
940 			case 0:
941 				slrflag++;
942 				break;
943 			case 1:
944 				slrflag++;
945 				altarg = sb;
946 				break;
947 			default:
948 				break;
949 		}
950 		goto S1;
951 	}
952 
953 S1:
954 	switch (*sb) {
955 
956 	case ' ':
957 	case '\t':
958 	case '\0':
959 		goto OUT;	/* end of token */
960 
961 	case '\\':
962 		INC_CHKCURSOR(sb);
963 		goto S2;	/* slurp next character */
964 
965 	case '"':
966 		INC_CHKCURSOR(sb);
967 		goto S3;	/* slurp quoted string */
968 
969 	default:
970 		*ap = *sb;	/* add character to token */
971 		ap++;
972 		INC_CHKCURSOR(sb);
973 		got_one = 1;
974 		goto S1;
975 	}
976 
977 S2:
978 	switch (*sb) {
979 
980 	case '\0':
981 		goto OUT;
982 
983 	default:
984 		*ap = *sb;
985 		ap++;
986 		INC_CHKCURSOR(sb);
987 		got_one = 1;
988 		goto S1;
989 	}
990 
991 S3:
992 	switch (*sb) {
993 
994 	case '\0':
995 		goto OUT;
996 
997 	case '"':
998 		INC_CHKCURSOR(sb);
999 		goto S1;
1000 
1001 	default:
1002 		*ap = *sb;
1003 		ap++;
1004 		INC_CHKCURSOR(sb);
1005 		got_one = 1;
1006 		goto S3;
1007 	}
1008 
1009 OUT:
1010 	if (got_one)
1011 		*ap++ = '\0';
1012 	argbase = ap;			/* update storage pointer */
1013 	stringbase = sb;		/* update scan pointer */
1014 	if (got_one) {
1015 		return (tmp);
1016 	}
1017 	switch (slrflag) {
1018 		case 0:
1019 			slrflag++;
1020 			break;
1021 		case 1:
1022 			slrflag++;
1023 			altarg = (char *) 0;
1024 			break;
1025 		default:
1026 			break;
1027 	}
1028 	return (NULL);
1029 }
1030 
1031 /*
1032  * Help command.
1033  * Call each command handler with argc == 0 and argv[0] == name.
1034  */
1035 void
1036 help(int argc, char *argv[])
1037 {
1038 	struct cmd *c;
1039 
1040 	if (argc == 1) {
1041 		StringList *buf;
1042 
1043 		buf = sl_init();
1044 		fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
1045 		    proxy ? "Proxy c" : "C");
1046 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
1047 			if (c->c_name && (!proxy || c->c_proxy))
1048 				sl_add(buf, c->c_name);
1049 		list_vertical(buf);
1050 		sl_free(buf, 0);
1051 		return;
1052 	}
1053 
1054 #define HELPINDENT ((int) sizeof("disconnect"))
1055 
1056 	while (--argc > 0) {
1057 		char *arg;
1058 
1059 		arg = *++argv;
1060 		c = getcmd(arg);
1061 		if (c == (struct cmd *)-1)
1062 			fprintf(ttyout, "?Ambiguous help command %s\n", arg);
1063 		else if (c == NULL)
1064 			fprintf(ttyout, "?Invalid help command %s\n", arg);
1065 		else
1066 			fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
1067 				c->c_name, c->c_help);
1068 	}
1069 }
1070 #endif /* !SMALL */
1071 
1072 __dead void
1073 usage(void)
1074 {
1075 	fprintf(stderr, "usage: "
1076 #ifndef SMALL
1077 	    "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
1078 	    "[-r seconds]\n"
1079 	    "           [-s sourceaddr] [host [port]]\n"
1080 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr]\n"
1081 	    "           ftp://[user:password@]host[:port]/file[/] ...\n"
1082 	    "       ftp [-CTu] [-c cookie] [-N name] [-o output] [-S ssl_options] "
1083 	    "[-s sourceaddr]\n"
1084 	    "           [-U useragent] [-w seconds] "
1085 	    "http[s]://[user:password@]host[:port]/file ...\n"
1086 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr] file:file ...\n"
1087 	    "       ftp [-C] [-N name] [-o output] [-s sourceaddr] host:/file[/] ...\n"
1088 #else /* !SMALL */
1089 	    "ftp [-N name] [-o output] "
1090 	    "ftp://[user:password@]host[:port]/file[/] ...\n"
1091 #ifndef NOSSL
1092 	    "       ftp [-N name] [-o output] [-S ssl_options] [-w seconds] "
1093 	    "http[s]://[user:password@]host[:port]/file ...\n"
1094 #else
1095 	    "       ftp [-N name] [-o output] [-w seconds] http://host[:port]/file ...\n"
1096 #endif /* NOSSL */
1097 	    "       ftp [-N name] [-o output] file:file ...\n"
1098 	    "       ftp [-N name] [-o output] host:/file[/] ...\n"
1099 #endif /* !SMALL */
1100 	    );
1101 	exit(1);
1102 }
1103