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