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