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