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