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