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