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 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 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 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 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 * 726 prompt(void) 727 { 728 return ("ftp> "); 729 } 730 731 /* 732 * Command parser. 733 */ 734 void 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 * 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 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 * 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 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 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