1 /* $OpenBSD: main.c,v 1.138 2021/07/14 13:33:57 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 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 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 if (*outfile == '\0') { 506 pipeout = 0; 507 outfile = NULL; 508 ttyout = stdout; 509 } else { 510 pipeout = strcmp(outfile, "-") == 0; 511 ttyout = pipeout ? stderr : stdout; 512 } 513 break; 514 515 case 'p': 516 passivemode = 1; 517 activefallback = 0; 518 break; 519 520 case 'P': 521 ftpport = optarg; 522 break; 523 524 case 'r': 525 retry_connect = strtonum(optarg, 0, INT_MAX, &errstr); 526 if (errstr != NULL) { 527 warnx("retry amount is %s: %s", errstr, 528 optarg); 529 usage(); 530 } 531 break; 532 533 case 'S': 534 #ifndef NOSSL 535 process_ssl_options(optarg); 536 #endif /* !NOSSL */ 537 break; 538 539 case 's': 540 #ifndef SMALL 541 srcaddr = optarg; 542 #endif /* !SMALL */ 543 break; 544 545 #ifndef SMALL 546 case 'T': 547 timestamp = 1; 548 break; 549 #endif /* !SMALL */ 550 case 't': 551 trace = 1; 552 break; 553 554 #ifndef SMALL 555 case 'U': 556 free (httpuseragent); 557 if (strcspn(optarg, "\r\n") != strlen(optarg)) 558 errx(1, "Invalid User-Agent: %s.", optarg); 559 if (asprintf(&httpuseragent, "User-Agent: %s", 560 optarg) == -1) 561 errx(1, "Can't allocate memory for HTTP(S) " 562 "User-Agent"); 563 break; 564 case 'u': 565 server_timestamps = 0; 566 break; 567 #endif /* !SMALL */ 568 569 case 'v': 570 verbose = 1; 571 break; 572 573 case 'V': 574 verbose = 0; 575 break; 576 577 case 'w': 578 connect_timeout = strtonum(optarg, 0, 200, &errstr); 579 if (errstr) 580 errx(1, "-w: %s", errstr); 581 break; 582 default: 583 usage(); 584 } 585 } 586 argc -= optind; 587 argv += optind; 588 589 #ifndef NOSSL 590 cookie_load(); 591 #endif /* !NOSSL */ 592 if (httpuseragent == NULL) 593 httpuseragent = HTTP_USER_AGENT; 594 595 cpend = 0; /* no pending replies */ 596 proxy = 0; /* proxy not active */ 597 crflag = 1; /* strip c.r. on ascii gets */ 598 sendport = -1; /* not using ports */ 599 /* 600 * Set up the home directory in case we're globbing. 601 */ 602 cp = getlogin(); 603 if (cp != NULL) { 604 pw = getpwnam(cp); 605 } 606 if (pw == NULL) 607 pw = getpwuid(getuid()); 608 if (pw != NULL) { 609 (void)strlcpy(homedir, pw->pw_dir, sizeof homedir); 610 home = homedir; 611 } 612 613 setttywidth(0); 614 (void)signal(SIGWINCH, setttywidth); 615 616 if (argc > 0) { 617 if (isurl(argv[0])) { 618 if (pipeout) { 619 #ifndef SMALL 620 if (pledge("stdio rpath dns tty inet proc exec fattr", 621 NULL) == -1) 622 err(1, "pledge"); 623 #else 624 if (pledge("stdio rpath dns tty inet fattr", 625 NULL) == -1) 626 err(1, "pledge"); 627 #endif 628 } else { 629 #ifndef SMALL 630 if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", 631 NULL) == -1) 632 err(1, "pledge"); 633 #else 634 if (pledge("stdio rpath wpath cpath dns tty inet fattr", 635 NULL) == -1) 636 err(1, "pledge"); 637 #endif 638 } 639 640 rval = auto_fetch(argc, argv, outfile); 641 if (rval >= 0) /* -1 == connected and cd-ed */ 642 exit(rval); 643 } else { 644 #ifndef SMALL 645 char *xargv[5]; 646 647 if (setjmp(toplevel)) 648 exit(0); 649 (void)signal(SIGINT, (sig_t)intr); 650 (void)signal(SIGPIPE, (sig_t)lostpeer); 651 xargv[0] = __progname; 652 xargv[1] = argv[0]; 653 xargv[2] = argv[1]; 654 xargv[3] = argv[2]; 655 xargv[4] = NULL; 656 do { 657 setpeer(argc+1, xargv); 658 if (!retry_connect) 659 break; 660 if (!connected) { 661 macnum = 0; 662 fputs("Retrying...\n", ttyout); 663 sleep(retry_connect); 664 } 665 } while (!connected); 666 retry_connect = 0; /* connected, stop hiding msgs */ 667 #endif /* !SMALL */ 668 } 669 } 670 #ifndef SMALL 671 controlediting(); 672 top = setjmp(toplevel) == 0; 673 if (top) { 674 (void)signal(SIGINT, (sig_t)intr); 675 (void)signal(SIGPIPE, (sig_t)lostpeer); 676 } 677 for (;;) { 678 cmdscanner(top); 679 top = 1; 680 } 681 #else /* !SMALL */ 682 usage(); 683 #endif /* !SMALL */ 684 } 685 686 void 687 intr(void) 688 { 689 int save_errno = errno; 690 691 write(fileno(ttyout), "\n\r", 2); 692 alarmtimer(0); 693 694 errno = save_errno; 695 longjmp(toplevel, 1); 696 } 697 698 void 699 lostpeer(void) 700 { 701 int save_errno = errno; 702 703 alarmtimer(0); 704 if (connected) { 705 if (cout != NULL) { 706 (void)shutdown(fileno(cout), SHUT_RDWR); 707 (void)fclose(cout); 708 cout = NULL; 709 } 710 if (data >= 0) { 711 (void)shutdown(data, SHUT_RDWR); 712 (void)close(data); 713 data = -1; 714 } 715 connected = 0; 716 } 717 pswitch(1); 718 if (connected) { 719 if (cout != NULL) { 720 (void)shutdown(fileno(cout), SHUT_RDWR); 721 (void)fclose(cout); 722 cout = NULL; 723 } 724 connected = 0; 725 } 726 proxflag = 0; 727 pswitch(0); 728 errno = save_errno; 729 } 730 731 #ifndef SMALL 732 /* 733 * Generate a prompt 734 */ 735 char * 736 prompt(void) 737 { 738 return ("ftp> "); 739 } 740 741 /* 742 * Command parser. 743 */ 744 void 745 cmdscanner(int top) 746 { 747 struct cmd *c; 748 int num; 749 HistEvent hev; 750 751 if (!top && !editing) 752 (void)putc('\n', ttyout); 753 for (;;) { 754 if (!editing) { 755 if (fromatty) { 756 fputs(prompt(), ttyout); 757 (void)fflush(ttyout); 758 } 759 if (fgets(line, sizeof(line), stdin) == NULL) 760 quit(0, 0); 761 num = strlen(line); 762 if (num == 0) 763 break; 764 if (line[--num] == '\n') { 765 if (num == 0) 766 break; 767 line[num] = '\0'; 768 } else if (num == sizeof(line) - 2) { 769 fputs("sorry, input line too long.\n", ttyout); 770 while ((num = getchar()) != '\n' && num != EOF) 771 /* void */; 772 break; 773 } /* else it was a line without a newline */ 774 } else { 775 const char *buf; 776 cursor_pos = NULL; 777 778 if ((buf = el_gets(el, &num)) == NULL || num == 0) { 779 putc('\n', ttyout); 780 fflush(ttyout); 781 quit(0, 0); 782 } 783 if (buf[--num] == '\n') { 784 if (num == 0) 785 break; 786 } 787 if (num >= sizeof(line)) { 788 fputs("sorry, input line too long.\n", ttyout); 789 break; 790 } 791 memcpy(line, buf, (size_t)num); 792 line[num] = '\0'; 793 history(hist, &hev, H_ENTER, buf); 794 } 795 796 makeargv(); 797 if (margc == 0) 798 continue; 799 c = getcmd(margv[0]); 800 if (c == (struct cmd *)-1) { 801 fputs("?Ambiguous command.\n", ttyout); 802 continue; 803 } 804 if (c == 0) { 805 /* 806 * Give editline(3) a shot at unknown commands. 807 * XXX - bogus commands with a colon in 808 * them will not elicit an error. 809 */ 810 if (editing && 811 el_parse(el, margc, (const char **)margv) != 0) 812 fputs("?Invalid command.\n", ttyout); 813 continue; 814 } 815 if (c->c_conn && !connected) { 816 fputs("Not connected.\n", ttyout); 817 continue; 818 } 819 confirmrest = 0; 820 (*c->c_handler)(margc, margv); 821 if (bell && c->c_bell) 822 (void)putc('\007', ttyout); 823 if (c->c_handler != help) 824 break; 825 } 826 (void)signal(SIGINT, (sig_t)intr); 827 (void)signal(SIGPIPE, (sig_t)lostpeer); 828 } 829 830 struct cmd * 831 getcmd(const char *name) 832 { 833 const char *p, *q; 834 struct cmd *c, *found; 835 int nmatches, longest; 836 837 if (name == NULL) 838 return (0); 839 840 longest = 0; 841 nmatches = 0; 842 found = 0; 843 for (c = cmdtab; (p = c->c_name) != NULL; c++) { 844 for (q = name; *q == *p++; q++) 845 if (*q == 0) /* exact match? */ 846 return (c); 847 if (!*q) { /* the name was a prefix */ 848 if (q - name > longest) { 849 longest = q - name; 850 nmatches = 1; 851 found = c; 852 } else if (q - name == longest) 853 nmatches++; 854 } 855 } 856 if (nmatches > 1) 857 return ((struct cmd *)-1); 858 return (found); 859 } 860 861 /* 862 * Slice a string up into argc/argv. 863 */ 864 865 int slrflag; 866 867 void 868 makeargv(void) 869 { 870 char *argp; 871 872 stringbase = line; /* scan from first of buffer */ 873 argbase = argbuf; /* store from first of buffer */ 874 slrflag = 0; 875 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 876 for (margc = 0; ; margc++) { 877 argp = slurpstring(); 878 sl_add(marg_sl, argp); 879 if (argp == NULL) 880 break; 881 } 882 if (cursor_pos == line) { 883 cursor_argc = 0; 884 cursor_argo = 0; 885 } else if (cursor_pos != NULL) { 886 cursor_argc = margc; 887 cursor_argo = strlen(margv[margc-1]); 888 } 889 } 890 891 #define INC_CHKCURSOR(x) { (x)++ ; \ 892 if (x == cursor_pos) { \ 893 cursor_argc = margc; \ 894 cursor_argo = ap-argbase; \ 895 cursor_pos = NULL; \ 896 } } 897 898 /* 899 * Parse string into argbuf; 900 * implemented with FSM to 901 * handle quoting and strings 902 */ 903 char * 904 slurpstring(void) 905 { 906 int got_one = 0; 907 char *sb = stringbase; 908 char *ap = argbase; 909 char *tmp = argbase; /* will return this if token found */ 910 911 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 912 switch (slrflag) { /* and $ as token for macro invoke */ 913 case 0: 914 slrflag++; 915 INC_CHKCURSOR(stringbase); 916 return ((*sb == '!') ? "!" : "$"); 917 /* NOTREACHED */ 918 case 1: 919 slrflag++; 920 altarg = stringbase; 921 break; 922 default: 923 break; 924 } 925 } 926 927 S0: 928 switch (*sb) { 929 930 case '\0': 931 goto OUT; 932 933 case ' ': 934 case '\t': 935 INC_CHKCURSOR(sb); 936 goto S0; 937 938 default: 939 switch (slrflag) { 940 case 0: 941 slrflag++; 942 break; 943 case 1: 944 slrflag++; 945 altarg = sb; 946 break; 947 default: 948 break; 949 } 950 goto S1; 951 } 952 953 S1: 954 switch (*sb) { 955 956 case ' ': 957 case '\t': 958 case '\0': 959 goto OUT; /* end of token */ 960 961 case '\\': 962 INC_CHKCURSOR(sb); 963 goto S2; /* slurp next character */ 964 965 case '"': 966 INC_CHKCURSOR(sb); 967 goto S3; /* slurp quoted string */ 968 969 default: 970 *ap = *sb; /* add character to token */ 971 ap++; 972 INC_CHKCURSOR(sb); 973 got_one = 1; 974 goto S1; 975 } 976 977 S2: 978 switch (*sb) { 979 980 case '\0': 981 goto OUT; 982 983 default: 984 *ap = *sb; 985 ap++; 986 INC_CHKCURSOR(sb); 987 got_one = 1; 988 goto S1; 989 } 990 991 S3: 992 switch (*sb) { 993 994 case '\0': 995 goto OUT; 996 997 case '"': 998 INC_CHKCURSOR(sb); 999 goto S1; 1000 1001 default: 1002 *ap = *sb; 1003 ap++; 1004 INC_CHKCURSOR(sb); 1005 got_one = 1; 1006 goto S3; 1007 } 1008 1009 OUT: 1010 if (got_one) 1011 *ap++ = '\0'; 1012 argbase = ap; /* update storage pointer */ 1013 stringbase = sb; /* update scan pointer */ 1014 if (got_one) { 1015 return (tmp); 1016 } 1017 switch (slrflag) { 1018 case 0: 1019 slrflag++; 1020 break; 1021 case 1: 1022 slrflag++; 1023 altarg = (char *) 0; 1024 break; 1025 default: 1026 break; 1027 } 1028 return (NULL); 1029 } 1030 1031 /* 1032 * Help command. 1033 * Call each command handler with argc == 0 and argv[0] == name. 1034 */ 1035 void 1036 help(int argc, char *argv[]) 1037 { 1038 struct cmd *c; 1039 1040 if (argc == 1) { 1041 StringList *buf; 1042 1043 buf = sl_init(); 1044 fprintf(ttyout, "%sommands may be abbreviated. Commands are:\n\n", 1045 proxy ? "Proxy c" : "C"); 1046 for (c = cmdtab; c < &cmdtab[NCMDS]; c++) 1047 if (c->c_name && (!proxy || c->c_proxy)) 1048 sl_add(buf, c->c_name); 1049 list_vertical(buf); 1050 sl_free(buf, 0); 1051 return; 1052 } 1053 1054 #define HELPINDENT ((int) sizeof("disconnect")) 1055 1056 while (--argc > 0) { 1057 char *arg; 1058 1059 arg = *++argv; 1060 c = getcmd(arg); 1061 if (c == (struct cmd *)-1) 1062 fprintf(ttyout, "?Ambiguous help command %s\n", arg); 1063 else if (c == NULL) 1064 fprintf(ttyout, "?Invalid help command %s\n", arg); 1065 else 1066 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, 1067 c->c_name, c->c_help); 1068 } 1069 } 1070 #endif /* !SMALL */ 1071 1072 __dead void 1073 usage(void) 1074 { 1075 fprintf(stderr, "usage: " 1076 #ifndef SMALL 1077 "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] " 1078 "[-r seconds]\n" 1079 " [-s sourceaddr] [host [port]]\n" 1080 " ftp [-C] [-N name] [-o output] [-s sourceaddr]\n" 1081 " ftp://[user:password@]host[:port]/file[/] ...\n" 1082 " ftp [-CTu] [-c cookie] [-N name] [-o output] [-S ssl_options] " 1083 "[-s sourceaddr]\n" 1084 " [-U useragent] [-w seconds] " 1085 "http[s]://[user:password@]host[:port]/file ...\n" 1086 " ftp [-C] [-N name] [-o output] [-s sourceaddr] file:file ...\n" 1087 " ftp [-C] [-N name] [-o output] [-s sourceaddr] host:/file[/] ...\n" 1088 #else /* !SMALL */ 1089 "ftp [-N name] [-o output] " 1090 "ftp://[user:password@]host[:port]/file[/] ...\n" 1091 #ifndef NOSSL 1092 " ftp [-N name] [-o output] [-S ssl_options] [-w seconds] " 1093 "http[s]://[user:password@]host[:port]/file ...\n" 1094 #else 1095 " ftp [-N name] [-o output] [-w seconds] http://host[:port]/file ...\n" 1096 #endif /* NOSSL */ 1097 " ftp [-N name] [-o output] file:file ...\n" 1098 " ftp [-N name] [-o output] host:/file[/] ...\n" 1099 #endif /* !SMALL */ 1100 ); 1101 exit(1); 1102 } 1103