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