main.c revision 1.80 1 /* $NetBSD: main.c,v 1.80 2002/03/18 20:14:03 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 1996-2001 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 /*
40 * Copyright (c) 1985, 1989, 1993, 1994
41 * The Regents of the University of California. All rights reserved.
42 *
43 * Redistribution and use in source and binary forms, with or without
44 * modification, are permitted provided that the following conditions
45 * are met:
46 * 1. Redistributions of source code must retain the above copyright
47 * notice, this list of conditions and the following disclaimer.
48 * 2. Redistributions in binary form must reproduce the above copyright
49 * notice, this list of conditions and the following disclaimer in the
50 * documentation and/or other materials provided with the distribution.
51 * 3. All advertising materials mentioning features or use of this software
52 * must display the following acknowledgement:
53 * This product includes software developed by the University of
54 * California, Berkeley and its contributors.
55 * 4. Neither the name of the University nor the names of its contributors
56 * may be used to endorse or promote products derived from this software
57 * without specific prior written permission.
58 *
59 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
60 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
63 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
65 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
66 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
67 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
68 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
69 * SUCH DAMAGE.
70 */
71
72 /*
73 * Copyright (C) 1997 and 1998 WIDE Project.
74 * All rights reserved.
75 *
76 * Redistribution and use in source and binary forms, with or without
77 * modification, are permitted provided that the following conditions
78 * are met:
79 * 1. Redistributions of source code must retain the above copyright
80 * notice, this list of conditions and the following disclaimer.
81 * 2. Redistributions in binary form must reproduce the above copyright
82 * notice, this list of conditions and the following disclaimer in the
83 * documentation and/or other materials provided with the distribution.
84 * 3. Neither the name of the project nor the names of its contributors
85 * may be used to endorse or promote products derived from this software
86 * without specific prior written permission.
87 *
88 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
89 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
90 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
91 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
92 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
93 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
94 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
95 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
96 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
97 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
98 * SUCH DAMAGE.
99 */
100
101 #include <sys/cdefs.h>
102 #ifndef lint
103 __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\n\
104 The Regents of the University of California. All rights reserved.\n");
105 #endif /* not lint */
106
107 #ifndef lint
108 #if 0
109 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 10/9/94";
110 #else
111 __RCSID("$NetBSD: main.c,v 1.80 2002/03/18 20:14:03 thorpej Exp $");
112 #endif
113 #endif /* not lint */
114
115 /*
116 * FTP User Program -- Command Interface.
117 */
118 #include <sys/types.h>
119 #include <sys/socket.h>
120
121 #include <err.h>
122 #include <errno.h>
123 #include <netdb.h>
124 #include <paths.h>
125 #include <pwd.h>
126 #include <stdio.h>
127 #include <stdlib.h>
128 #include <string.h>
129 #include <unistd.h>
130 #include <locale.h>
131
132 #define GLOBAL /* force GLOBAL decls in ftp_var.h to be declared */
133 #include "ftp_var.h"
134
135 #define FTP_PROXY "ftp_proxy" /* env var with FTP proxy location */
136 #define HTTP_PROXY "http_proxy" /* env var with HTTP proxy location */
137 #define NO_PROXY "no_proxy" /* env var with list of non-proxied
138 * hosts, comma or space separated */
139
140 static void setupoption(char *, char *, char *);
141 int main(int, char *[]);
142
143 int
144 main(int argc, char *argv[])
145 {
146 int ch, rval;
147 struct passwd *pw;
148 char *cp, *ep, *anonuser, *anonpass, *upload_path;
149 int dumbterm, s, len, isupload;
150
151 setlocale(LC_ALL, "");
152
153 ftpport = "ftp";
154 httpport = "http";
155 gateport = NULL;
156 cp = getenv("FTPSERVERPORT");
157 if (cp != NULL)
158 gateport = cp;
159 else
160 gateport = "ftpgate";
161 doglob = 1;
162 interactive = 1;
163 autologin = 1;
164 passivemode = 1;
165 activefallback = 1;
166 preserve = 1;
167 verbose = 0;
168 progress = 0;
169 gatemode = 0;
170 data = -1;
171 outfile = NULL;
172 restartautofetch = 0;
173 #ifndef NO_EDITCOMPLETE
174 editing = 0;
175 el = NULL;
176 hist = NULL;
177 #endif
178 bytes = 0;
179 mark = HASHBYTES;
180 rate_get = 0;
181 rate_get_incr = DEFAULTINCR;
182 rate_put = 0;
183 rate_put_incr = DEFAULTINCR;
184 #ifdef INET6
185 epsv4 = 1;
186 #else
187 epsv4 = 0;
188 #endif
189 epsv4bad = 0;
190 upload_path = NULL;
191 isupload = 0;
192 reply_callback = NULL;
193 family = AF_UNSPEC;
194
195 netrc[0] = '\0';
196 cp = getenv("NETRC");
197 if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc))
198 errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG));
199
200 /*
201 * Get the default socket buffer sizes if we don't already have them.
202 * It doesn't matter which socket we do this to, because on the first
203 * call no socket buffer sizes will have been modified, so we are
204 * guaranteed to get the system defaults.
205 */
206 s = socket(AF_INET, SOCK_STREAM, 0);
207 if (s == -1)
208 err(1, "can't create socket");
209 len = sizeof(rcvbuf_size);
210 if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void *) &rcvbuf_size, &len)
211 < 0)
212 err(1, "unable to get default rcvbuf size");
213 len = sizeof(sndbuf_size);
214 if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf_size, &len)
215 < 0)
216 err(1, "unable to get default sndbuf size");
217 (void)close(s);
218 /* sanity check returned buffer sizes */
219 if (rcvbuf_size <= 0)
220 rcvbuf_size = 8192;
221 if (sndbuf_size <= 0)
222 sndbuf_size = 8192;
223
224 marg_sl = xsl_init();
225 if ((tmpdir = getenv("TMPDIR")) == NULL)
226 tmpdir = _PATH_TMP;
227
228 /* Set default operation mode based on FTPMODE environment variable */
229 if ((cp = getenv("FTPMODE")) != NULL) {
230 if (strcasecmp(cp, "passive") == 0) {
231 passivemode = 1;
232 activefallback = 0;
233 } else if (strcasecmp(cp, "active") == 0) {
234 passivemode = 0;
235 activefallback = 0;
236 } else if (strcasecmp(cp, "gate") == 0) {
237 gatemode = 1;
238 } else if (strcasecmp(cp, "auto") == 0) {
239 passivemode = 1;
240 activefallback = 1;
241 } else
242 warnx("unknown $FTPMODE '%s'; using defaults", cp);
243 }
244
245 if (strcmp(getprogname(), "pftp") == 0) {
246 passivemode = 1;
247 activefallback = 0;
248 } else if (strcmp(getprogname(), "gate-ftp") == 0)
249 gatemode = 1;
250
251 gateserver = getenv("FTPSERVER");
252 if (gateserver == NULL || *gateserver == '\0')
253 gateserver = GATE_SERVER;
254 if (gatemode) {
255 if (*gateserver == '\0') {
256 warnx(
257 "Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp");
258 gatemode = 0;
259 }
260 }
261
262 cp = getenv("TERM");
263 if (cp == NULL || strcmp(cp, "dumb") == 0)
264 dumbterm = 1;
265 else
266 dumbterm = 0;
267 fromatty = isatty(fileno(stdin));
268 ttyout = stdout;
269 if (isatty(fileno(ttyout))) {
270 verbose = 1; /* verbose if to a tty */
271 if (! dumbterm) {
272 #ifndef NO_EDITCOMPLETE
273 if (fromatty) /* editing mode on if tty is usable */
274 editing = 1;
275 #endif
276 #ifndef NO_PROGRESS
277 if (foregroundproc())
278 progress = 1; /* progress bar on if fg */
279 #endif
280 }
281 }
282
283 while ((ch = getopt(argc, argv, "46AadefginN:o:pP:r:RtT:u:vV")) != -1) {
284 switch (ch) {
285 case '4':
286 family = AF_INET;
287 break;
288
289 case '6':
290 family = AF_INET6;
291 break;
292
293 case 'A':
294 activefallback = 0;
295 passivemode = 0;
296 break;
297
298 case 'a':
299 anonftp = 1;
300 break;
301
302 case 'd':
303 options |= SO_DEBUG;
304 debug++;
305 break;
306
307 case 'e':
308 #ifndef NO_EDITCOMPLETE
309 editing = 0;
310 #endif
311 break;
312
313 case 'f':
314 flushcache = 1;
315 break;
316
317 case 'g':
318 doglob = 0;
319 break;
320
321 case 'i':
322 interactive = 0;
323 break;
324
325 case 'n':
326 autologin = 0;
327 break;
328
329 case 'N':
330 if (strlcpy(netrc, optarg, sizeof(netrc))
331 >= sizeof(netrc))
332 errx(1, "%s: %s", optarg,
333 strerror(ENAMETOOLONG));
334 break;
335
336 case 'o':
337 outfile = optarg;
338 if (strcmp(outfile, "-") == 0)
339 ttyout = stderr;
340 break;
341
342 case 'p':
343 passivemode = 1;
344 activefallback = 0;
345 break;
346
347 case 'P':
348 ftpport = optarg;
349 break;
350
351 case 'r':
352 retry_connect = strtol(optarg, &ep, 10);
353 if (retry_connect < 1 || *ep != '\0')
354 errx(1, "bad retry value: %s", optarg);
355 break;
356
357 case 'R':
358 restartautofetch = 1;
359 break;
360
361 case 't':
362 trace = 1;
363 break;
364
365 case 'T':
366 {
367 int targc;
368 char *targv[6], *oac;
369
370 /* look for `dir,max[,incr]' */
371 targc = 0;
372 targv[targc++] = "-T";
373 oac = xstrdup(optarg);
374
375 while ((cp = strsep(&oac, ",")) != NULL) {
376 if (*cp == '\0') {
377 warnx("bad throttle value: %s", optarg);
378 usage();
379 /* NOTREACHED */
380 }
381 targv[targc++] = cp;
382 if (targc >= 5)
383 break;
384 }
385 if (parserate(targc, targv, 1) == -1)
386 usage();
387 free(oac);
388 break;
389 }
390
391 case 'u':
392 {
393 isupload = 1;
394 interactive = 0;
395 upload_path = xstrdup(optarg);
396
397 break;
398 }
399
400 case 'v':
401 progress = verbose = 1;
402 break;
403
404 case 'V':
405 progress = verbose = 0;
406 break;
407
408 default:
409 usage();
410 }
411 }
412 /* set line buffering on ttyout */
413 setvbuf(ttyout, NULL, _IOLBF, 0);
414 argc -= optind;
415 argv += optind;
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 /*
423 * Cache the user name and home directory.
424 */
425 localhome = NULL;
426 localname = NULL;
427 anonuser = "anonymous";
428 cp = getenv("HOME");
429 if (! EMPTYSTRING(cp))
430 localhome = xstrdup(cp);
431 pw = NULL;
432 cp = getlogin();
433 if (cp != NULL)
434 pw = getpwnam(cp);
435 if (pw == NULL)
436 pw = getpwuid(getuid());
437 if (pw != NULL) {
438 if (localhome == NULL && !EMPTYSTRING(pw->pw_dir))
439 localhome = xstrdup(pw->pw_dir);
440 localname = xstrdup(pw->pw_name);
441 anonuser = localname;
442 }
443 if (netrc[0] == '\0' && localhome != NULL) {
444 if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) ||
445 strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) {
446 warnx("%s/.netrc: %s", localhome,
447 strerror(ENAMETOOLONG));
448 netrc[0] = '\0';
449 }
450 }
451 if (localhome == NULL)
452 localhome = xstrdup("/");
453
454 /*
455 * Every anonymous FTP server I've encountered will accept the
456 * string "username@", and will append the hostname itself. We
457 * do this by default since many servers are picky about not
458 * having a FQDN in the anonymous password.
459 * - thorpej (at) netbsd.org
460 */
461 len = strlen(anonuser) + 2;
462 anonpass = xmalloc(len);
463 (void)strlcpy(anonpass, anonuser, len);
464 (void)strlcat(anonpass, "@", len);
465
466 /*
467 * set all the defaults for options defined in
468 * struct option optiontab[] declared in cmdtab.c
469 */
470 setupoption("anonpass", getenv("FTPANONPASS"), anonpass);
471 setupoption("ftp_proxy", getenv(FTP_PROXY), "");
472 setupoption("http_proxy", getenv(HTTP_PROXY), "");
473 setupoption("no_proxy", getenv(NO_PROXY), "");
474 setupoption("pager", getenv("PAGER"), DEFAULTPAGER);
475 setupoption("prompt", getenv("FTPPROMPT"), DEFAULTPROMPT);
476 setupoption("rprompt", getenv("FTPRPROMPT"), DEFAULTRPROMPT);
477
478 free(anonpass);
479
480 setttywidth(0);
481 #ifdef SIGINFO
482 (void)xsignal(SIGINFO, psummary);
483 #endif
484 (void)xsignal(SIGQUIT, psummary);
485 (void)xsignal(SIGUSR1, crankrate);
486 (void)xsignal(SIGUSR2, crankrate);
487 (void)xsignal(SIGWINCH, setttywidth);
488
489 #ifdef __GNUC__ /* to shut up gcc warnings */
490 (void)&argc;
491 (void)&argv;
492 #endif
493
494 if (argc > 0) {
495 if (isupload) {
496 rval = auto_put(argc, argv, upload_path);
497 exit(rval);
498 } else if (strchr(argv[0], ':') != NULL
499 && ! isipv6addr(argv[0])) {
500 rval = auto_fetch(argc, argv);
501 if (rval >= 0) /* -1 == connected and cd-ed */
502 exit(rval);
503 } else {
504 char *xargv[4], *user, *host;
505
506 if (sigsetjmp(toplevel, 1))
507 exit(0);
508 (void)xsignal(SIGINT, intr);
509 (void)xsignal(SIGPIPE, lostpeer);
510 user = NULL;
511 host = argv[0];
512 cp = strchr(host, '@');
513 if (cp) {
514 *cp = '\0';
515 user = host;
516 host = cp + 1;
517 }
518 /* XXX discards const */
519 xargv[0] = (char *)getprogname();
520 xargv[1] = host;
521 xargv[2] = argv[1];
522 xargv[3] = NULL;
523 do {
524 int oautologin;
525
526 oautologin = autologin;
527 if (user != NULL) {
528 anonftp = 0;
529 autologin = 0;
530 }
531 setpeer(argc+1, xargv);
532 autologin = oautologin;
533 if (connected == 1 && user != NULL)
534 (void)ftp_login(host, user, NULL);
535 if (!retry_connect)
536 break;
537 if (!connected) {
538 macnum = 0;
539 fprintf(ttyout,
540 "Retrying in %d seconds...\n",
541 retry_connect);
542 sleep(retry_connect);
543 }
544 } while (!connected);
545 retry_connect = 0; /* connected, stop hiding msgs */
546 }
547 }
548 if (isupload)
549 usage();
550
551 #ifndef NO_EDITCOMPLETE
552 controlediting();
553 #endif /* !NO_EDITCOMPLETE */
554
555 (void)sigsetjmp(toplevel, 1);
556 (void)xsignal(SIGINT, intr);
557 (void)xsignal(SIGPIPE, lostpeer);
558 for (;;)
559 cmdscanner();
560 }
561
562 /*
563 * Generate a prompt
564 */
565 char *
566 prompt(void)
567 {
568 static char **prompt;
569 static char buf[MAXPATHLEN];
570
571 if (prompt == NULL) {
572 struct option *o;
573
574 o = getoption("prompt");
575 if (o == NULL)
576 errx(1, "no such option `prompt'");
577 prompt = &(o->value);
578 }
579 formatbuf(buf, sizeof(buf), *prompt ? *prompt : DEFAULTPROMPT);
580 return (buf);
581 }
582
583 /*
584 * Generate an rprompt
585 */
586 char *
587 rprompt(void)
588 {
589 static char **rprompt;
590 static char buf[MAXPATHLEN];
591
592 if (rprompt == NULL) {
593 struct option *o;
594
595 o = getoption("rprompt");
596 if (o == NULL)
597 errx(1, "no such option `rprompt'");
598 rprompt = &(o->value);
599 }
600 formatbuf(buf, sizeof(buf), *rprompt ? *rprompt : DEFAULTRPROMPT);
601 return (buf);
602 }
603
604 /*
605 * Command parser.
606 */
607 void
608 cmdscanner(void)
609 {
610 struct cmd *c;
611 char *p;
612 int num;
613
614 for (;;) {
615 #ifndef NO_EDITCOMPLETE
616 if (!editing) {
617 #endif /* !NO_EDITCOMPLETE */
618 if (fromatty) {
619 fputs(prompt(), ttyout);
620 p = rprompt();
621 if (*p)
622 fprintf(ttyout, "%s ", p);
623 (void)fflush(ttyout);
624 }
625 if (fgets(line, sizeof(line), stdin) == NULL) {
626 if (fromatty)
627 putc('\n', ttyout);
628 quit(0, NULL);
629 }
630 num = strlen(line);
631 if (num == 0)
632 break;
633 if (line[--num] == '\n') {
634 if (num == 0)
635 break;
636 line[num] = '\0';
637 } else if (num == sizeof(line) - 2) {
638 fputs("sorry, input line too long.\n", ttyout);
639 while ((num = getchar()) != '\n' && num != EOF)
640 /* void */;
641 break;
642 } /* else it was a line without a newline */
643 #ifndef NO_EDITCOMPLETE
644 } else {
645 const char *buf;
646 HistEvent ev;
647 cursor_pos = NULL;
648
649 if ((buf = el_gets(el, &num)) == NULL || num == 0) {
650 if (fromatty)
651 putc('\n', ttyout);
652 quit(0, NULL);
653 }
654 if (buf[--num] == '\n') {
655 if (num == 0)
656 break;
657 } else if (num >= sizeof(line)) {
658 fputs("sorry, input line too long.\n", ttyout);
659 break;
660 }
661 memcpy(line, buf, num);
662 line[num] = '\0';
663 history(hist, &ev, H_ENTER, buf);
664 }
665 #endif /* !NO_EDITCOMPLETE */
666
667 makeargv();
668 if (margc == 0)
669 continue;
670 c = getcmd(margv[0]);
671 if (c == (struct cmd *)-1) {
672 fputs("?Ambiguous command.\n", ttyout);
673 continue;
674 }
675 if (c == NULL) {
676 #if !defined(NO_EDITCOMPLETE)
677 /*
678 * attempt to el_parse() unknown commands.
679 * any command containing a ':' would be parsed
680 * as "[prog:]cmd ...", and will result in a
681 * false positive if prog != "ftp", so treat
682 * such commands as invalid.
683 */
684 if (strchr(margv[0], ':') != NULL ||
685 el_parse(el, margc, (const char **)margv) != 0)
686 #endif /* !NO_EDITCOMPLETE */
687 fputs("?Invalid command.\n", ttyout);
688 continue;
689 }
690 if (c->c_conn && !connected) {
691 fputs("Not connected.\n", ttyout);
692 continue;
693 }
694 confirmrest = 0;
695 margv[0] = c->c_name;
696 (*c->c_handler)(margc, margv);
697 if (bell && c->c_bell)
698 (void)putc('\007', ttyout);
699 if (c->c_handler != help)
700 break;
701 }
702 (void)xsignal(SIGINT, intr);
703 (void)xsignal(SIGPIPE, lostpeer);
704 }
705
706 struct cmd *
707 getcmd(const char *name)
708 {
709 const char *p, *q;
710 struct cmd *c, *found;
711 int nmatches, longest;
712
713 if (name == NULL)
714 return (0);
715
716 longest = 0;
717 nmatches = 0;
718 found = 0;
719 for (c = cmdtab; (p = c->c_name) != NULL; c++) {
720 for (q = name; *q == *p++; q++)
721 if (*q == 0) /* exact match? */
722 return (c);
723 if (!*q) { /* the name was a prefix */
724 if (q - name > longest) {
725 longest = q - name;
726 nmatches = 1;
727 found = c;
728 } else if (q - name == longest)
729 nmatches++;
730 }
731 }
732 if (nmatches > 1)
733 return ((struct cmd *)-1);
734 return (found);
735 }
736
737 /*
738 * Slice a string up into argc/argv.
739 */
740
741 int slrflag;
742
743 void
744 makeargv(void)
745 {
746 char *argp;
747
748 stringbase = line; /* scan from first of buffer */
749 argbase = argbuf; /* store from first of buffer */
750 slrflag = 0;
751 marg_sl->sl_cur = 0; /* reset to start of marg_sl */
752 for (margc = 0; ; margc++) {
753 argp = slurpstring();
754 xsl_add(marg_sl, argp);
755 if (argp == NULL)
756 break;
757 }
758 #ifndef NO_EDITCOMPLETE
759 if (cursor_pos == line) {
760 cursor_argc = 0;
761 cursor_argo = 0;
762 } else if (cursor_pos != NULL) {
763 cursor_argc = margc;
764 cursor_argo = strlen(margv[margc-1]);
765 }
766 #endif /* !NO_EDITCOMPLETE */
767 }
768
769 #ifdef NO_EDITCOMPLETE
770 #define INC_CHKCURSOR(x) (x)++
771 #else /* !NO_EDITCOMPLETE */
772 #define INC_CHKCURSOR(x) { (x)++ ; \
773 if (x == cursor_pos) { \
774 cursor_argc = margc; \
775 cursor_argo = ap-argbase; \
776 cursor_pos = NULL; \
777 } }
778
779 #endif /* !NO_EDITCOMPLETE */
780
781 /*
782 * Parse string into argbuf;
783 * implemented with FSM to
784 * handle quoting and strings
785 */
786 char *
787 slurpstring(void)
788 {
789 int got_one = 0;
790 char *sb = stringbase;
791 char *ap = argbase;
792 char *tmp = argbase; /* will return this if token found */
793
794 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
795 switch (slrflag) { /* and $ as token for macro invoke */
796 case 0:
797 slrflag++;
798 INC_CHKCURSOR(stringbase);
799 return ((*sb == '!') ? "!" : "$");
800 /* NOTREACHED */
801 case 1:
802 slrflag++;
803 altarg = stringbase;
804 break;
805 default:
806 break;
807 }
808 }
809
810 S0:
811 switch (*sb) {
812
813 case '\0':
814 goto OUT;
815
816 case ' ':
817 case '\t':
818 INC_CHKCURSOR(sb);
819 goto S0;
820
821 default:
822 switch (slrflag) {
823 case 0:
824 slrflag++;
825 break;
826 case 1:
827 slrflag++;
828 altarg = sb;
829 break;
830 default:
831 break;
832 }
833 goto S1;
834 }
835
836 S1:
837 switch (*sb) {
838
839 case ' ':
840 case '\t':
841 case '\0':
842 goto OUT; /* end of token */
843
844 case '\\':
845 INC_CHKCURSOR(sb);
846 goto S2; /* slurp next character */
847
848 case '"':
849 INC_CHKCURSOR(sb);
850 goto S3; /* slurp quoted string */
851
852 default:
853 *ap = *sb; /* add character to token */
854 ap++;
855 INC_CHKCURSOR(sb);
856 got_one = 1;
857 goto S1;
858 }
859
860 S2:
861 switch (*sb) {
862
863 case '\0':
864 goto OUT;
865
866 default:
867 *ap = *sb;
868 ap++;
869 INC_CHKCURSOR(sb);
870 got_one = 1;
871 goto S1;
872 }
873
874 S3:
875 switch (*sb) {
876
877 case '\0':
878 goto OUT;
879
880 case '"':
881 INC_CHKCURSOR(sb);
882 goto S1;
883
884 default:
885 *ap = *sb;
886 ap++;
887 INC_CHKCURSOR(sb);
888 got_one = 1;
889 goto S3;
890 }
891
892 OUT:
893 if (got_one)
894 *ap++ = '\0';
895 argbase = ap; /* update storage pointer */
896 stringbase = sb; /* update scan pointer */
897 if (got_one) {
898 return (tmp);
899 }
900 switch (slrflag) {
901 case 0:
902 slrflag++;
903 break;
904 case 1:
905 slrflag++;
906 altarg = NULL;
907 break;
908 default:
909 break;
910 }
911 return (NULL);
912 }
913
914 /*
915 * Help/usage command.
916 * Call each command handler with argc == 0 and argv[0] == name.
917 */
918 void
919 help(int argc, char *argv[])
920 {
921 struct cmd *c;
922 char *nargv[1], *p, *cmd;
923 int isusage;
924
925 cmd = argv[0];
926 isusage = (strcmp(cmd, "usage") == 0);
927 if (argc == 0 || (isusage && argc == 1)) {
928 fprintf(ttyout, "usage: %s [command [...]]\n", cmd);
929 return;
930 }
931 if (argc == 1) {
932 StringList *buf;
933
934 buf = xsl_init();
935 fprintf(ttyout,
936 "%sommands may be abbreviated. Commands are:\n\n",
937 proxy ? "Proxy c" : "C");
938 for (c = cmdtab; (p = c->c_name) != NULL; c++)
939 if (!proxy || c->c_proxy)
940 xsl_add(buf, p);
941 list_vertical(buf);
942 sl_free(buf, 0);
943 return;
944 }
945
946 #define HELPINDENT ((int) sizeof("disconnect"))
947
948 while (--argc > 0) {
949 char *arg;
950
951 arg = *++argv;
952 c = getcmd(arg);
953 if (c == (struct cmd *)-1)
954 fprintf(ttyout, "?Ambiguous %s command `%s'\n",
955 cmd, arg);
956 else if (c == NULL)
957 fprintf(ttyout, "?Invalid %s command `%s'\n",
958 cmd, arg);
959 else {
960 if (isusage) {
961 nargv[0] = c->c_name;
962 (*c->c_handler)(0, nargv);
963 } else
964 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
965 c->c_name, c->c_help);
966 }
967 }
968 }
969
970 struct option *
971 getoption(const char *name)
972 {
973 const char *p;
974 struct option *c;
975
976 if (name == NULL)
977 return (NULL);
978 for (c = optiontab; (p = c->name) != NULL; c++) {
979 if (strcasecmp(p, name) == 0)
980 return (c);
981 }
982 return (NULL);
983 }
984
985 char *
986 getoptionvalue(const char *name)
987 {
988 struct option *c;
989
990 if (name == NULL)
991 errx(1, "getoptionvalue() invoked with NULL name");
992 c = getoption(name);
993 if (c != NULL)
994 return (c->value);
995 errx(1, "getoptionvalue() invoked with unknown option `%s'", name);
996 /* NOTREACHED */
997 }
998
999 static void
1000 setupoption(char *name, char *value, char *defaultvalue)
1001 {
1002 char *nargv[3];
1003 int overbose;
1004
1005 nargv[0] = "setupoption()";
1006 nargv[1] = name;
1007 nargv[2] = (value ? value : defaultvalue);
1008 overbose = verbose;
1009 verbose = 0;
1010 setoption(3, nargv);
1011 verbose = overbose;
1012 }
1013
1014 void
1015 usage(void)
1016 {
1017 const char *progname = getprogname();
1018
1019 (void)fprintf(stderr,
1020 "usage: %s [-46AadefginpRtvV] [-N netrc] [-o outfile] [-P port] [-r retry]\n"
1021 " [-T dir,max[,inc][[user@]host [port]]] [host:path[/]]\n"
1022 " [file:///file] [ftp://[user[:pass]@]host[:port]/path[/]]\n"
1023 " [http://[user[:pass]@]host[:port]/path] [...]\n"
1024 " %s -u url file [...]\n", progname, progname);
1025 exit(1);
1026 }
1027