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