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