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