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