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