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