1 /* $OpenBSD: main.c,v 1.83 2012/05/19 02:04:22 lteo 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 <netdb.h> 71 #include <pwd.h> 72 #include <stdio.h> 73 #include <errno.h> 74 #include <stdlib.h> 75 #include <string.h> 76 #include <unistd.h> 77 78 #include "ftp_var.h" 79 #include "cmds.h" 80 81 int family = PF_UNSPEC; 82 int pipeout; 83 84 int 85 main(volatile int argc, char *argv[]) 86 { 87 int ch, top, rval; 88 struct passwd *pw = NULL; 89 char *cp, homedir[MAXPATHLEN]; 90 char *outfile = NULL; 91 const char *errstr; 92 int dumb_terminal = 0; 93 94 ftpport = "ftp"; 95 httpport = "http"; 96 #ifndef SMALL 97 httpsport = "https"; 98 #endif /* !SMALL */ 99 gateport = getenv("FTPSERVERPORT"); 100 if (gateport == NULL || *gateport == '\0') 101 gateport = "ftpgate"; 102 doglob = 1; 103 interactive = 1; 104 autologin = 1; 105 passivemode = 1; 106 activefallback = 1; 107 preserve = 1; 108 verbose = 0; 109 progress = 0; 110 gatemode = 0; 111 #ifndef SMALL 112 editing = 0; 113 el = NULL; 114 hist = NULL; 115 cookiefile = NULL; 116 resume = 0; 117 srcaddr = NULL; 118 marg_sl = sl_init(); 119 #endif /* !SMALL */ 120 mark = HASHBYTES; 121 #ifdef INET6 122 epsv4 = 1; 123 #else 124 epsv4 = 0; 125 #endif 126 epsv4bad = 0; 127 128 /* Set default operation mode based on FTPMODE environment variable */ 129 if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') { 130 if (strcmp(cp, "passive") == 0) { 131 passivemode = 1; 132 activefallback = 0; 133 } else if (strcmp(cp, "active") == 0) { 134 passivemode = 0; 135 activefallback = 0; 136 } else if (strcmp(cp, "gate") == 0) { 137 gatemode = 1; 138 } else if (strcmp(cp, "auto") == 0) { 139 passivemode = 1; 140 activefallback = 1; 141 } else 142 warnx("unknown FTPMODE: %s. Using defaults", cp); 143 } 144 145 if (strcmp(__progname, "gate-ftp") == 0) 146 gatemode = 1; 147 gateserver = getenv("FTPSERVER"); 148 if (gateserver == NULL || *gateserver == '\0') 149 gateserver = GATE_SERVER; 150 if (gatemode) { 151 if (*gateserver == '\0') { 152 warnx( 153 "Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp"); 154 gatemode = 0; 155 } 156 } 157 158 cp = getenv("TERM"); 159 dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") || 160 !strcmp(cp, "emacs") || !strcmp(cp, "su")); 161 fromatty = isatty(fileno(stdin)); 162 if (fromatty) { 163 verbose = 1; /* verbose if from a tty */ 164 #ifndef SMALL 165 if (!dumb_terminal) 166 editing = 1; /* editing mode on if tty is usable */ 167 #endif /* !SMALL */ 168 } 169 170 ttyout = stdout; 171 if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc()) 172 progress = 1; /* progress bar on if tty is usable */ 173 174 #ifndef SMALL 175 cookiefile = getenv("http_cookies"); 176 #endif /* !SMALL */ 177 178 while ((ch = getopt(argc, argv, "46AaCc:dEegik:mno:pP:r:s:tvV")) != -1) { 179 switch (ch) { 180 case '4': 181 family = PF_INET; 182 break; 183 case '6': 184 family = PF_INET6; 185 break; 186 case 'A': 187 activefallback = 0; 188 passivemode = 0; 189 break; 190 191 case 'a': 192 anonftp = 1; 193 break; 194 195 case 'C': 196 #ifndef SMALL 197 resume = 1; 198 #endif /* !SMALL */ 199 break; 200 201 case 'c': 202 #ifndef SMALL 203 cookiefile = optarg; 204 #endif /* !SMALL */ 205 break; 206 207 case 'd': 208 #ifndef SMALL 209 options |= SO_DEBUG; 210 debug++; 211 #endif /* !SMALL */ 212 break; 213 214 case 'E': 215 epsv4 = 0; 216 break; 217 218 case 'e': 219 #ifndef SMALL 220 editing = 0; 221 #endif /* !SMALL */ 222 break; 223 224 case 'g': 225 doglob = 0; 226 break; 227 228 case 'i': 229 interactive = 0; 230 break; 231 232 case 'k': 233 keep_alive_timeout = strtonum(optarg, 0, INT_MAX, 234 &errstr); 235 if (errstr != NULL) { 236 warnx("keep alive amount is %s: %s", errstr, 237 optarg); 238 usage(); 239 } 240 break; 241 case 'm': 242 progress = -1; 243 break; 244 245 case 'n': 246 autologin = 0; 247 break; 248 249 case 'o': 250 outfile = optarg; 251 if (*outfile == '\0') { 252 pipeout = 0; 253 outfile = NULL; 254 ttyout = stdout; 255 } else { 256 pipeout = strcmp(outfile, "-") == 0; 257 ttyout = pipeout ? stderr : stdout; 258 } 259 break; 260 261 case 'p': 262 passivemode = 1; 263 activefallback = 0; 264 break; 265 266 case 'P': 267 ftpport = optarg; 268 break; 269 270 case 'r': 271 retry_connect = strtonum(optarg, 0, INT_MAX, &errstr); 272 if (errstr != NULL) { 273 warnx("retry amount is %s: %s", errstr, 274 optarg); 275 usage(); 276 } 277 break; 278 279 case 's': 280 #ifndef SMALL 281 srcaddr = optarg; 282 #endif /* !SMALL */ 283 break; 284 285 case 't': 286 trace = 1; 287 break; 288 289 case 'v': 290 verbose = 1; 291 break; 292 293 case 'V': 294 verbose = 0; 295 break; 296 297 default: 298 usage(); 299 } 300 } 301 argc -= optind; 302 argv += optind; 303 304 #ifndef SMALL 305 cookie_load(); 306 #endif /* !SMALL */ 307 308 cpend = 0; /* no pending replies */ 309 proxy = 0; /* proxy not active */ 310 crflag = 1; /* strip c.r. on ascii gets */ 311 sendport = -1; /* not using ports */ 312 /* 313 * Set up the home directory in case we're globbing. 314 */ 315 cp = getlogin(); 316 if (cp != NULL) { 317 pw = getpwnam(cp); 318 } 319 if (pw == NULL) 320 pw = getpwuid(getuid()); 321 if (pw != NULL) { 322 (void)strlcpy(homedir, pw->pw_dir, sizeof homedir); 323 home = homedir; 324 } 325 326 setttywidth(0); 327 (void)signal(SIGWINCH, setttywidth); 328 329 if (argc > 0) { 330 if (isurl(argv[0])) { 331 rval = auto_fetch(argc, argv, outfile); 332 if (rval >= 0) /* -1 == connected and cd-ed */ 333 exit(rval); 334 } else { 335 #ifndef SMALL 336 char *xargv[5]; 337 338 if (setjmp(toplevel)) 339 exit(0); 340 (void)signal(SIGINT, (sig_t)intr); 341 (void)signal(SIGPIPE, (sig_t)lostpeer); 342 xargv[0] = __progname; 343 xargv[1] = argv[0]; 344 xargv[2] = argv[1]; 345 xargv[3] = argv[2]; 346 xargv[4] = NULL; 347 do { 348 setpeer(argc+1, xargv); 349 if (!retry_connect) 350 break; 351 if (!connected) { 352 macnum = 0; 353 fputs("Retrying...\n", ttyout); 354 sleep(retry_connect); 355 } 356 } while (!connected); 357 retry_connect = 0; /* connected, stop hiding msgs */ 358 #endif /* !SMALL */ 359 } 360 } 361 #ifndef SMALL 362 controlediting(); 363 top = setjmp(toplevel) == 0; 364 if (top) { 365 (void)signal(SIGINT, (sig_t)intr); 366 (void)signal(SIGPIPE, (sig_t)lostpeer); 367 } 368 for (;;) { 369 cmdscanner(top); 370 top = 1; 371 } 372 #else /* !SMALL */ 373 usage(); 374 #endif /* !SMALL */ 375 } 376 377 void 378 intr(void) 379 { 380 381 alarmtimer(0); 382 longjmp(toplevel, 1); 383 } 384 385 void 386 lostpeer(void) 387 { 388 int save_errno = errno; 389 390 alarmtimer(0); 391 if (connected) { 392 if (cout != NULL) { 393 (void)shutdown(fileno(cout), SHUT_RDWR); 394 (void)fclose(cout); 395 cout = NULL; 396 } 397 if (data >= 0) { 398 (void)shutdown(data, SHUT_RDWR); 399 (void)close(data); 400 data = -1; 401 } 402 connected = 0; 403 } 404 pswitch(1); 405 if (connected) { 406 if (cout != NULL) { 407 (void)shutdown(fileno(cout), SHUT_RDWR); 408 (void)fclose(cout); 409 cout = NULL; 410 } 411 connected = 0; 412 } 413 proxflag = 0; 414 pswitch(0); 415 errno = save_errno; 416 } 417 418 #ifndef SMALL 419 /* 420 * Generate a prompt 421 */ 422 char * 423 prompt(void) 424 { 425 return ("ftp> "); 426 } 427 428 /* 429 * Command parser. 430 */ 431 void 432 cmdscanner(int top) 433 { 434 struct cmd *c; 435 int num; 436 HistEvent hev; 437 438 if (!top && !editing) 439 (void)putc('\n', ttyout); 440 for (;;) { 441 if (!editing) { 442 if (fromatty) { 443 fputs(prompt(), ttyout); 444 (void)fflush(ttyout); 445 } 446 if (fgets(line, sizeof(line), stdin) == NULL) 447 quit(0, 0); 448 num = strlen(line); 449 if (num == 0) 450 break; 451 if (line[--num] == '\n') { 452 if (num == 0) 453 break; 454 line[num] = '\0'; 455 } else if (num == sizeof(line) - 2) { 456 fputs("sorry, input line too long.\n", ttyout); 457 while ((num = getchar()) != '\n' && num != EOF) 458 /* void */; 459 break; 460 } /* else it was a line without a newline */ 461 } else { 462 const char *buf; 463 cursor_pos = NULL; 464 465 if ((buf = el_gets(el, &num)) == NULL || num == 0) 466 quit(0, 0); 467 if (buf[--num] == '\n') { 468 if (num == 0) 469 break; 470 } 471 if (num >= sizeof(line)) { 472 fputs("sorry, input line too long.\n", ttyout); 473 break; 474 } 475 memcpy(line, buf, (size_t)num); 476 line[num] = '\0'; 477 history(hist, &hev, H_ENTER, buf); 478 } 479 480 makeargv(); 481 if (margc == 0) 482 continue; 483 c = getcmd(margv[0]); 484 if (c == (struct cmd *)-1) { 485 fputs("?Ambiguous command.\n", ttyout); 486 continue; 487 } 488 if (c == 0) { 489 /* 490 * Give editline(3) a shot at unknown commands. 491 * XXX - bogus commands with a colon in 492 * them will not elicit an error. 493 */ 494 if (editing && 495 el_parse(el, margc, (const char **)margv) != 0) 496 fputs("?Invalid command.\n", ttyout); 497 continue; 498 } 499 if (c->c_conn && !connected) { 500 fputs("Not connected.\n", ttyout); 501 continue; 502 } 503 confirmrest = 0; 504 (*c->c_handler)(margc, margv); 505 if (bell && c->c_bell) 506 (void)putc('\007', ttyout); 507 if (c->c_handler != help) 508 break; 509 } 510 (void)signal(SIGINT, (sig_t)intr); 511 (void)signal(SIGPIPE, (sig_t)lostpeer); 512 } 513 514 struct cmd * 515 getcmd(const char *name) 516 { 517 const char *p, *q; 518 struct cmd *c, *found; 519 int nmatches, longest; 520 521 if (name == NULL) 522 return (0); 523 524 longest = 0; 525 nmatches = 0; 526 found = 0; 527 for (c = cmdtab; (p = c->c_name) != NULL; c++) { 528 for (q = name; *q == *p++; q++) 529 if (*q == 0) /* exact match? */ 530 return (c); 531 if (!*q) { /* the name was a prefix */ 532 if (q - name > longest) { 533 longest = q - name; 534 nmatches = 1; 535 found = c; 536 } else if (q - name == longest) 537 nmatches++; 538 } 539 } 540 if (nmatches > 1) 541 return ((struct cmd *)-1); 542 return (found); 543 } 544 545 /* 546 * Slice a string up into argc/argv. 547 */ 548 549 int slrflag; 550 551 void 552 makeargv(void) 553 { 554 char *argp; 555 556 stringbase = line; /* scan from first of buffer */ 557 argbase = argbuf; /* store from first of buffer */ 558 slrflag = 0; 559 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 560 for (margc = 0; ; margc++) { 561 argp = slurpstring(); 562 sl_add(marg_sl, argp); 563 if (argp == NULL) 564 break; 565 } 566 if (cursor_pos == line) { 567 cursor_argc = 0; 568 cursor_argo = 0; 569 } else if (cursor_pos != NULL) { 570 cursor_argc = margc; 571 cursor_argo = strlen(margv[margc-1]); 572 } 573 } 574 575 #define INC_CHKCURSOR(x) { (x)++ ; \ 576 if (x == cursor_pos) { \ 577 cursor_argc = margc; \ 578 cursor_argo = ap-argbase; \ 579 cursor_pos = NULL; \ 580 } } 581 582 /* 583 * Parse string into argbuf; 584 * implemented with FSM to 585 * handle quoting and strings 586 */ 587 char * 588 slurpstring(void) 589 { 590 int got_one = 0; 591 char *sb = stringbase; 592 char *ap = argbase; 593 char *tmp = argbase; /* will return this if token found */ 594 595 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 596 switch (slrflag) { /* and $ as token for macro invoke */ 597 case 0: 598 slrflag++; 599 INC_CHKCURSOR(stringbase); 600 return ((*sb == '!') ? "!" : "$"); 601 /* NOTREACHED */ 602 case 1: 603 slrflag++; 604 altarg = stringbase; 605 break; 606 default: 607 break; 608 } 609 } 610 611 S0: 612 switch (*sb) { 613 614 case '\0': 615 goto OUT; 616 617 case ' ': 618 case '\t': 619 INC_CHKCURSOR(sb); 620 goto S0; 621 622 default: 623 switch (slrflag) { 624 case 0: 625 slrflag++; 626 break; 627 case 1: 628 slrflag++; 629 altarg = sb; 630 break; 631 default: 632 break; 633 } 634 goto S1; 635 } 636 637 S1: 638 switch (*sb) { 639 640 case ' ': 641 case '\t': 642 case '\0': 643 goto OUT; /* end of token */ 644 645 case '\\': 646 INC_CHKCURSOR(sb); 647 goto S2; /* slurp next character */ 648 649 case '"': 650 INC_CHKCURSOR(sb); 651 goto S3; /* slurp quoted string */ 652 653 default: 654 *ap = *sb; /* add character to token */ 655 ap++; 656 INC_CHKCURSOR(sb); 657 got_one = 1; 658 goto S1; 659 } 660 661 S2: 662 switch (*sb) { 663 664 case '\0': 665 goto OUT; 666 667 default: 668 *ap = *sb; 669 ap++; 670 INC_CHKCURSOR(sb); 671 got_one = 1; 672 goto S1; 673 } 674 675 S3: 676 switch (*sb) { 677 678 case '\0': 679 goto OUT; 680 681 case '"': 682 INC_CHKCURSOR(sb); 683 goto S1; 684 685 default: 686 *ap = *sb; 687 ap++; 688 INC_CHKCURSOR(sb); 689 got_one = 1; 690 goto S3; 691 } 692 693 OUT: 694 if (got_one) 695 *ap++ = '\0'; 696 argbase = ap; /* update storage pointer */ 697 stringbase = sb; /* update scan pointer */ 698 if (got_one) { 699 return (tmp); 700 } 701 switch (slrflag) { 702 case 0: 703 slrflag++; 704 break; 705 case 1: 706 slrflag++; 707 altarg = (char *) 0; 708 break; 709 default: 710 break; 711 } 712 return ((char *)0); 713 } 714 715 /* 716 * Help command. 717 * Call each command handler with argc == 0 and argv[0] == name. 718 */ 719 void 720 help(int argc, char *argv[]) 721 { 722 struct cmd *c; 723 724 if (argc == 1) { 725 StringList *buf; 726 727 buf = sl_init(); 728 fprintf(ttyout, "%sommands may be abbreviated. Commands are:\n\n", 729 proxy ? "Proxy c" : "C"); 730 for (c = cmdtab; c < &cmdtab[NCMDS]; c++) 731 if (c->c_name && (!proxy || c->c_proxy)) 732 sl_add(buf, c->c_name); 733 list_vertical(buf); 734 sl_free(buf, 0); 735 return; 736 } 737 738 #define HELPINDENT ((int) sizeof("disconnect")) 739 740 while (--argc > 0) { 741 char *arg; 742 743 arg = *++argv; 744 c = getcmd(arg); 745 if (c == (struct cmd *)-1) 746 fprintf(ttyout, "?Ambiguous help command %s\n", arg); 747 else if (c == (struct cmd *)0) 748 fprintf(ttyout, "?Invalid help command %s\n", arg); 749 else 750 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, 751 c->c_name, c->c_help); 752 } 753 } 754 #endif /* !SMALL */ 755 756 void 757 usage(void) 758 { 759 (void)fprintf(stderr, "usage: %s " 760 #ifndef SMALL 761 "[-46AadEegimnptVv] [-k seconds] [-P port] " 762 "[-r seconds] [-s srcaddr]\n" 763 " [host [port]]\n" 764 " %s [-C] " 765 #endif /* !SMALL */ 766 "[-o output] " 767 #ifndef SMALL 768 "[-s srcaddr]\n" 769 " " 770 #endif /* !SMALL */ 771 "ftp://[user:password@]host[:port]/file[/] ...\n" 772 " %s " 773 #ifndef SMALL 774 "[-C] [-c cookie] " 775 #endif /* !SMALL */ 776 "[-o output] " 777 #ifndef SMALL 778 "[-s srcaddr] " 779 #endif /* !SMALL */ 780 "http://host[:port]/file ...\n" 781 #ifndef SMALL 782 " %s [-C] [-c cookie] [-o output] [-s srcaddr] " 783 "https://host[:port]/file\n" 784 " ...\n" 785 #endif /* !SMALL */ 786 " %s " 787 #ifndef SMALL 788 "[-C] " 789 #endif /* !SMALL */ 790 "[-o output] " 791 #ifndef SMALL 792 "[-s srcaddr] " 793 #endif /* !SMALL */ 794 "file:file ...\n" 795 " %s " 796 #ifndef SMALL 797 "[-C] " 798 #endif /* !SMALL */ 799 "[-o output] " 800 #ifndef SMALL 801 "[-s srcaddr] " 802 #endif /* !SMALL */ 803 "host:/file[/] ...\n", 804 #ifndef SMALL 805 __progname, __progname, __progname, __progname, __progname, 806 __progname); 807 #else /* !SMALL */ 808 __progname, __progname, __progname, __progname); 809 #endif /* !SMALL */ 810 exit(1); 811 } 812 813