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