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