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