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