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