1 /* $NetBSD: main.c,v 1.90 2004/07/21 00:09:14 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1996-2004 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Luke Mewburn. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* 40 * Copyright (c) 1985, 1989, 1993, 1994 41 * The Regents of the University of California. All rights reserved. 42 * 43 * Redistribution and use in source and binary forms, with or without 44 * modification, are permitted provided that the following conditions 45 * are met: 46 * 1. Redistributions of source code must retain the above copyright 47 * notice, this list of conditions and the following disclaimer. 48 * 2. Redistributions in binary form must reproduce the above copyright 49 * notice, this list of conditions and the following disclaimer in the 50 * documentation and/or other materials provided with the distribution. 51 * 3. Neither the name of the University nor the names of its contributors 52 * may be used to endorse or promote products derived from this software 53 * without specific prior written permission. 54 * 55 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 56 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 57 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 58 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 59 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 60 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 61 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 62 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 63 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 64 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 65 * SUCH DAMAGE. 66 */ 67 68 /* 69 * Copyright (C) 1997 and 1998 WIDE Project. 70 * All rights reserved. 71 * 72 * Redistribution and use in source and binary forms, with or without 73 * modification, are permitted provided that the following conditions 74 * are met: 75 * 1. Redistributions of source code must retain the above copyright 76 * notice, this list of conditions and the following disclaimer. 77 * 2. Redistributions in binary form must reproduce the above copyright 78 * notice, this list of conditions and the following disclaimer in the 79 * documentation and/or other materials provided with the distribution. 80 * 3. Neither the name of the project nor the names of its contributors 81 * may be used to endorse or promote products derived from this software 82 * without specific prior written permission. 83 * 84 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 85 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 86 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 87 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 88 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 89 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 90 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 91 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 92 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 93 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 94 * SUCH DAMAGE. 95 */ 96 97 #include <sys/cdefs.h> 98 #ifndef lint 99 __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\n\ 100 The Regents of the University of California. All rights reserved.\n"); 101 #endif /* not lint */ 102 103 #ifndef lint 104 #if 0 105 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 10/9/94"; 106 #else 107 __RCSID("$NetBSD: main.c,v 1.90 2004/07/21 00:09:14 lukem Exp $"); 108 #endif 109 #endif /* not lint */ 110 111 /* 112 * FTP User Program -- Command Interface. 113 */ 114 #include <sys/types.h> 115 #include <sys/socket.h> 116 117 #include <err.h> 118 #include <errno.h> 119 #include <netdb.h> 120 #include <paths.h> 121 #include <pwd.h> 122 #include <signal.h> 123 #include <stdio.h> 124 #include <stdlib.h> 125 #include <string.h> 126 #include <unistd.h> 127 #include <locale.h> 128 129 #define GLOBAL /* force GLOBAL decls in ftp_var.h to be declared */ 130 #include "ftp_var.h" 131 132 #define FTP_PROXY "ftp_proxy" /* env var with FTP proxy location */ 133 #define HTTP_PROXY "http_proxy" /* env var with HTTP proxy location */ 134 #define NO_PROXY "no_proxy" /* env var with list of non-proxied 135 * hosts, comma or space separated */ 136 137 static void setupoption(char *, char *, char *); 138 int main(int, char *[]); 139 140 int 141 main(int argc, char *argv[]) 142 { 143 int ch, rval; 144 struct passwd *pw; 145 char *cp, *ep, *anonuser, *anonpass, *upload_path; 146 int dumbterm, s, len, isupload; 147 148 setlocale(LC_ALL, ""); 149 setprogname(argv[0]); 150 151 sigint_raised = 0; 152 153 ftpport = "ftp"; 154 httpport = "http"; 155 gateport = NULL; 156 cp = getenv("FTPSERVERPORT"); 157 if (cp != NULL) 158 gateport = cp; 159 else 160 gateport = "ftpgate"; 161 doglob = 1; 162 interactive = 1; 163 autologin = 1; 164 passivemode = 1; 165 activefallback = 1; 166 preserve = 1; 167 verbose = 0; 168 progress = 0; 169 gatemode = 0; 170 data = -1; 171 outfile = NULL; 172 restartautofetch = 0; 173 #ifndef NO_EDITCOMPLETE 174 editing = 0; 175 el = NULL; 176 hist = NULL; 177 #endif 178 bytes = 0; 179 mark = HASHBYTES; 180 rate_get = 0; 181 rate_get_incr = DEFAULTINCR; 182 rate_put = 0; 183 rate_put_incr = DEFAULTINCR; 184 #ifdef INET6 185 epsv4 = 1; 186 #else 187 epsv4 = 0; 188 #endif 189 epsv4bad = 0; 190 upload_path = NULL; 191 isupload = 0; 192 reply_callback = NULL; 193 family = AF_UNSPEC; 194 195 netrc[0] = '\0'; 196 cp = getenv("NETRC"); 197 if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc)) 198 errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG)); 199 200 /* 201 * Get the default socket buffer sizes if we don't already have them. 202 * It doesn't matter which socket we do this to, because on the first 203 * call no socket buffer sizes will have been modified, so we are 204 * guaranteed to get the system defaults. 205 */ 206 s = socket(AF_INET, SOCK_STREAM, 0); 207 if (s == -1) 208 err(1, "can't create socket"); 209 len = sizeof(rcvbuf_size); 210 if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void *) &rcvbuf_size, &len) 211 < 0) 212 err(1, "unable to get default rcvbuf size"); 213 len = sizeof(sndbuf_size); 214 if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf_size, &len) 215 < 0) 216 err(1, "unable to get default sndbuf size"); 217 (void)close(s); 218 /* sanity check returned buffer sizes */ 219 if (rcvbuf_size <= 0) 220 rcvbuf_size = 8 * 1024; 221 if (sndbuf_size <= 0) 222 sndbuf_size = 8 * 1024; 223 224 if (sndbuf_size > 8 * 1024 * 1024) 225 sndbuf_size = 8 * 1024 * 1024; 226 if (rcvbuf_size > 8 * 1024 * 1024) 227 rcvbuf_size = 8 * 1024 * 1024; 228 229 marg_sl = xsl_init(); 230 if ((tmpdir = getenv("TMPDIR")) == NULL) 231 tmpdir = _PATH_TMP; 232 233 /* Set default operation mode based on FTPMODE environment variable */ 234 if ((cp = getenv("FTPMODE")) != NULL) { 235 if (strcasecmp(cp, "passive") == 0) { 236 passivemode = 1; 237 activefallback = 0; 238 } else if (strcasecmp(cp, "active") == 0) { 239 passivemode = 0; 240 activefallback = 0; 241 } else if (strcasecmp(cp, "gate") == 0) { 242 gatemode = 1; 243 } else if (strcasecmp(cp, "auto") == 0) { 244 passivemode = 1; 245 activefallback = 1; 246 } else 247 warnx("unknown $FTPMODE '%s'; using defaults", cp); 248 } 249 250 if (strcmp(getprogname(), "pftp") == 0) { 251 passivemode = 1; 252 activefallback = 0; 253 } else if (strcmp(getprogname(), "gate-ftp") == 0) 254 gatemode = 1; 255 256 gateserver = getenv("FTPSERVER"); 257 if (gateserver == NULL || *gateserver == '\0') 258 gateserver = GATE_SERVER; 259 if (gatemode) { 260 if (*gateserver == '\0') { 261 warnx( 262 "Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp"); 263 gatemode = 0; 264 } 265 } 266 267 cp = getenv("TERM"); 268 if (cp == NULL || strcmp(cp, "dumb") == 0) 269 dumbterm = 1; 270 else 271 dumbterm = 0; 272 fromatty = isatty(fileno(stdin)); 273 ttyout = stdout; 274 if (isatty(fileno(ttyout))) { 275 verbose = 1; /* verbose if to a tty */ 276 if (! dumbterm) { 277 #ifndef NO_EDITCOMPLETE 278 if (fromatty) /* editing mode on if tty is usable */ 279 editing = 1; 280 #endif 281 #ifndef NO_PROGRESS 282 if (foregroundproc()) 283 progress = 1; /* progress bar on if fg */ 284 #endif 285 } 286 } 287 288 while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:RtT:u:vV")) != -1) { 289 switch (ch) { 290 case '4': 291 family = AF_INET; 292 break; 293 294 case '6': 295 #ifdef INET6 296 family = AF_INET6; 297 #else 298 warnx("INET6 support is not available; ignoring -6"); 299 #endif 300 break; 301 302 case 'A': 303 activefallback = 0; 304 passivemode = 0; 305 break; 306 307 case 'a': 308 anonftp = 1; 309 break; 310 311 case 'd': 312 options |= SO_DEBUG; 313 debug++; 314 break; 315 316 case 'e': 317 #ifndef NO_EDITCOMPLETE 318 editing = 0; 319 #endif 320 break; 321 322 case 'f': 323 flushcache = 1; 324 break; 325 326 case 'g': 327 doglob = 0; 328 break; 329 330 case 'i': 331 interactive = 0; 332 break; 333 334 case 'n': 335 autologin = 0; 336 break; 337 338 case 'N': 339 if (strlcpy(netrc, optarg, sizeof(netrc)) 340 >= sizeof(netrc)) 341 errx(1, "%s: %s", optarg, 342 strerror(ENAMETOOLONG)); 343 break; 344 345 case 'o': 346 outfile = optarg; 347 if (strcmp(outfile, "-") == 0) 348 ttyout = stderr; 349 break; 350 351 case 'p': 352 passivemode = 1; 353 activefallback = 0; 354 break; 355 356 case 'P': 357 ftpport = optarg; 358 break; 359 360 case 'q': 361 quit_time = strtol(optarg, &ep, 10); 362 if (quit_time < 1 || *ep != '\0') 363 errx(1, "bad quit value: %s", optarg); 364 break; 365 366 case 'r': 367 retry_connect = strtol(optarg, &ep, 10); 368 if (retry_connect < 1 || *ep != '\0') 369 errx(1, "bad retry value: %s", optarg); 370 break; 371 372 case 'R': 373 restartautofetch = 1; 374 break; 375 376 case 't': 377 trace = 1; 378 break; 379 380 case 'T': 381 { 382 int targc; 383 char *targv[6], *oac; 384 385 /* look for `dir,max[,incr]' */ 386 targc = 0; 387 targv[targc++] = "-T"; 388 oac = xstrdup(optarg); 389 390 while ((cp = strsep(&oac, ",")) != NULL) { 391 if (*cp == '\0') { 392 warnx("bad throttle value: %s", optarg); 393 usage(); 394 /* NOTREACHED */ 395 } 396 targv[targc++] = cp; 397 if (targc >= 5) 398 break; 399 } 400 if (parserate(targc, targv, 1) == -1) 401 usage(); 402 free(oac); 403 break; 404 } 405 406 case 'u': 407 { 408 isupload = 1; 409 interactive = 0; 410 upload_path = xstrdup(optarg); 411 412 break; 413 } 414 415 case 'v': 416 progress = verbose = 1; 417 break; 418 419 case 'V': 420 progress = verbose = 0; 421 break; 422 423 default: 424 usage(); 425 } 426 } 427 /* set line buffering on ttyout */ 428 setvbuf(ttyout, NULL, _IOLBF, 0); 429 argc -= optind; 430 argv += optind; 431 432 cpend = 0; /* no pending replies */ 433 proxy = 0; /* proxy not active */ 434 crflag = 1; /* strip c.r. on ascii gets */ 435 sendport = -1; /* not using ports */ 436 437 /* 438 * Cache the user name and home directory. 439 */ 440 localhome = NULL; 441 localname = NULL; 442 anonuser = "anonymous"; 443 cp = getenv("HOME"); 444 if (! EMPTYSTRING(cp)) 445 localhome = xstrdup(cp); 446 pw = NULL; 447 cp = getlogin(); 448 if (cp != NULL) 449 pw = getpwnam(cp); 450 if (pw == NULL) 451 pw = getpwuid(getuid()); 452 if (pw != NULL) { 453 if (localhome == NULL && !EMPTYSTRING(pw->pw_dir)) 454 localhome = xstrdup(pw->pw_dir); 455 localname = xstrdup(pw->pw_name); 456 anonuser = localname; 457 } 458 if (netrc[0] == '\0' && localhome != NULL) { 459 if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) || 460 strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) { 461 warnx("%s/.netrc: %s", localhome, 462 strerror(ENAMETOOLONG)); 463 netrc[0] = '\0'; 464 } 465 } 466 if (localhome == NULL) 467 localhome = xstrdup("/"); 468 469 /* 470 * Every anonymous FTP server I've encountered will accept the 471 * string "username@", and will append the hostname itself. We 472 * do this by default since many servers are picky about not 473 * having a FQDN in the anonymous password. 474 * - thorpej@NetBSD.org 475 */ 476 len = strlen(anonuser) + 2; 477 anonpass = xmalloc(len); 478 (void)strlcpy(anonpass, anonuser, len); 479 (void)strlcat(anonpass, "@", len); 480 481 /* 482 * set all the defaults for options defined in 483 * struct option optiontab[] declared in cmdtab.c 484 */ 485 setupoption("anonpass", getenv("FTPANONPASS"), anonpass); 486 setupoption("ftp_proxy", getenv(FTP_PROXY), ""); 487 setupoption("http_proxy", getenv(HTTP_PROXY), ""); 488 setupoption("no_proxy", getenv(NO_PROXY), ""); 489 setupoption("pager", getenv("PAGER"), DEFAULTPAGER); 490 setupoption("prompt", getenv("FTPPROMPT"), DEFAULTPROMPT); 491 setupoption("rprompt", getenv("FTPRPROMPT"), DEFAULTRPROMPT); 492 493 free(anonpass); 494 495 setttywidth(0); 496 #ifdef SIGINFO 497 (void)xsignal(SIGINFO, psummary); 498 #endif 499 (void)xsignal(SIGQUIT, psummary); 500 (void)xsignal(SIGUSR1, crankrate); 501 (void)xsignal(SIGUSR2, crankrate); 502 (void)xsignal(SIGWINCH, setttywidth); 503 504 #ifdef __GNUC__ /* to shut up gcc warnings */ 505 (void)&argc; 506 (void)&argv; 507 #endif 508 509 if (argc > 0) { 510 if (isupload) { 511 rval = auto_put(argc, argv, upload_path); 512 sigint_or_rval_exit: 513 if (sigint_raised) { 514 (void)xsignal(SIGINT, SIG_DFL); 515 raise(SIGINT); 516 } 517 exit(rval); 518 } else if (strchr(argv[0], ':') != NULL 519 && ! isipv6addr(argv[0])) { 520 rval = auto_fetch(argc, argv); 521 if (rval >= 0) /* -1 == connected and cd-ed */ 522 goto sigint_or_rval_exit; 523 } else { 524 char *xargv[4], *user, *host; 525 526 if ((rval = sigsetjmp(toplevel, 1))) 527 goto sigint_or_rval_exit; 528 (void)xsignal(SIGINT, intr); 529 (void)xsignal(SIGPIPE, lostpeer); 530 user = NULL; 531 host = argv[0]; 532 cp = strchr(host, '@'); 533 if (cp) { 534 *cp = '\0'; 535 user = host; 536 host = cp + 1; 537 } 538 /* XXX discards const */ 539 xargv[0] = (char *)getprogname(); 540 xargv[1] = host; 541 xargv[2] = argv[1]; 542 xargv[3] = NULL; 543 do { 544 int oautologin; 545 546 oautologin = autologin; 547 if (user != NULL) { 548 anonftp = 0; 549 autologin = 0; 550 } 551 setpeer(argc+1, xargv); 552 autologin = oautologin; 553 if (connected == 1 && user != NULL) 554 (void)ftp_login(host, user, NULL); 555 if (!retry_connect) 556 break; 557 if (!connected) { 558 macnum = 0; 559 fprintf(ttyout, 560 "Retrying in %d seconds...\n", 561 retry_connect); 562 sleep(retry_connect); 563 } 564 } while (!connected); 565 retry_connect = 0; /* connected, stop hiding msgs */ 566 } 567 } 568 if (isupload) 569 usage(); 570 571 #ifndef NO_EDITCOMPLETE 572 controlediting(); 573 #endif /* !NO_EDITCOMPLETE */ 574 575 (void)sigsetjmp(toplevel, 1); 576 (void)xsignal(SIGINT, intr); 577 (void)xsignal(SIGPIPE, lostpeer); 578 for (;;) 579 cmdscanner(); 580 } 581 582 /* 583 * Generate a prompt 584 */ 585 char * 586 prompt(void) 587 { 588 static char **prompt; 589 static char buf[MAXPATHLEN]; 590 591 if (prompt == NULL) { 592 struct option *o; 593 594 o = getoption("prompt"); 595 if (o == NULL) 596 errx(1, "no such option `prompt'"); 597 prompt = &(o->value); 598 } 599 formatbuf(buf, sizeof(buf), *prompt ? *prompt : DEFAULTPROMPT); 600 return (buf); 601 } 602 603 /* 604 * Generate an rprompt 605 */ 606 char * 607 rprompt(void) 608 { 609 static char **rprompt; 610 static char buf[MAXPATHLEN]; 611 612 if (rprompt == NULL) { 613 struct option *o; 614 615 o = getoption("rprompt"); 616 if (o == NULL) 617 errx(1, "no such option `rprompt'"); 618 rprompt = &(o->value); 619 } 620 formatbuf(buf, sizeof(buf), *rprompt ? *rprompt : DEFAULTRPROMPT); 621 return (buf); 622 } 623 624 /* 625 * Command parser. 626 */ 627 void 628 cmdscanner(void) 629 { 630 struct cmd *c; 631 char *p; 632 int num; 633 634 for (;;) { 635 #ifndef NO_EDITCOMPLETE 636 if (!editing) { 637 #endif /* !NO_EDITCOMPLETE */ 638 if (fromatty) { 639 fputs(prompt(), ttyout); 640 p = rprompt(); 641 if (*p) 642 fprintf(ttyout, "%s ", p); 643 (void)fflush(ttyout); 644 } 645 if (fgets(line, sizeof(line), stdin) == NULL) { 646 if (fromatty) 647 putc('\n', ttyout); 648 quit(0, NULL); 649 } 650 num = strlen(line); 651 if (num == 0) 652 break; 653 if (line[--num] == '\n') { 654 if (num == 0) 655 break; 656 line[num] = '\0'; 657 } else if (num == sizeof(line) - 2) { 658 fputs("sorry, input line too long.\n", ttyout); 659 while ((num = getchar()) != '\n' && num != EOF) 660 /* void */; 661 break; 662 } /* else it was a line without a newline */ 663 #ifndef NO_EDITCOMPLETE 664 } else { 665 const char *buf; 666 HistEvent ev; 667 cursor_pos = NULL; 668 669 if ((buf = el_gets(el, &num)) == NULL || num == 0) { 670 if (fromatty) 671 putc('\n', ttyout); 672 quit(0, NULL); 673 } 674 if (buf[--num] == '\n') { 675 if (num == 0) 676 break; 677 } else if (num >= sizeof(line)) { 678 fputs("sorry, input line too long.\n", ttyout); 679 break; 680 } 681 memcpy(line, buf, num); 682 line[num] = '\0'; 683 history(hist, &ev, H_ENTER, buf); 684 } 685 #endif /* !NO_EDITCOMPLETE */ 686 687 makeargv(); 688 if (margc == 0) 689 continue; 690 c = getcmd(margv[0]); 691 if (c == (struct cmd *)-1) { 692 fputs("?Ambiguous command.\n", ttyout); 693 continue; 694 } 695 if (c == NULL) { 696 #if !defined(NO_EDITCOMPLETE) 697 /* 698 * attempt to el_parse() unknown commands. 699 * any command containing a ':' would be parsed 700 * as "[prog:]cmd ...", and will result in a 701 * false positive if prog != "ftp", so treat 702 * such commands as invalid. 703 */ 704 if (strchr(margv[0], ':') != NULL || 705 el_parse(el, margc, (const char **)margv) != 0) 706 #endif /* !NO_EDITCOMPLETE */ 707 fputs("?Invalid command.\n", ttyout); 708 continue; 709 } 710 if (c->c_conn && !connected) { 711 fputs("Not connected.\n", ttyout); 712 continue; 713 } 714 confirmrest = 0; 715 margv[0] = c->c_name; 716 (*c->c_handler)(margc, margv); 717 if (bell && c->c_bell) 718 (void)putc('\007', ttyout); 719 if (c->c_handler != help) 720 break; 721 } 722 (void)xsignal(SIGINT, intr); 723 (void)xsignal(SIGPIPE, lostpeer); 724 } 725 726 struct cmd * 727 getcmd(const char *name) 728 { 729 const char *p, *q; 730 struct cmd *c, *found; 731 int nmatches, longest; 732 733 if (name == NULL) 734 return (0); 735 736 longest = 0; 737 nmatches = 0; 738 found = 0; 739 for (c = cmdtab; (p = c->c_name) != NULL; c++) { 740 for (q = name; *q == *p++; q++) 741 if (*q == 0) /* exact match? */ 742 return (c); 743 if (!*q) { /* the name was a prefix */ 744 if (q - name > longest) { 745 longest = q - name; 746 nmatches = 1; 747 found = c; 748 } else if (q - name == longest) 749 nmatches++; 750 } 751 } 752 if (nmatches > 1) 753 return ((struct cmd *)-1); 754 return (found); 755 } 756 757 /* 758 * Slice a string up into argc/argv. 759 */ 760 761 int slrflag; 762 763 void 764 makeargv(void) 765 { 766 char *argp; 767 768 stringbase = line; /* scan from first of buffer */ 769 argbase = argbuf; /* store from first of buffer */ 770 slrflag = 0; 771 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 772 for (margc = 0; ; margc++) { 773 argp = slurpstring(); 774 xsl_add(marg_sl, argp); 775 if (argp == NULL) 776 break; 777 } 778 #ifndef NO_EDITCOMPLETE 779 if (cursor_pos == line) { 780 cursor_argc = 0; 781 cursor_argo = 0; 782 } else if (cursor_pos != NULL) { 783 cursor_argc = margc; 784 cursor_argo = strlen(margv[margc-1]); 785 } 786 #endif /* !NO_EDITCOMPLETE */ 787 } 788 789 #ifdef NO_EDITCOMPLETE 790 #define INC_CHKCURSOR(x) (x)++ 791 #else /* !NO_EDITCOMPLETE */ 792 #define INC_CHKCURSOR(x) { (x)++ ; \ 793 if (x == cursor_pos) { \ 794 cursor_argc = margc; \ 795 cursor_argo = ap-argbase; \ 796 cursor_pos = NULL; \ 797 } } 798 799 #endif /* !NO_EDITCOMPLETE */ 800 801 /* 802 * Parse string into argbuf; 803 * implemented with FSM to 804 * handle quoting and strings 805 */ 806 char * 807 slurpstring(void) 808 { 809 int got_one = 0; 810 char *sb = stringbase; 811 char *ap = argbase; 812 char *tmp = argbase; /* will return this if token found */ 813 814 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 815 switch (slrflag) { /* and $ as token for macro invoke */ 816 case 0: 817 slrflag++; 818 INC_CHKCURSOR(stringbase); 819 return ((*sb == '!') ? "!" : "$"); 820 /* NOTREACHED */ 821 case 1: 822 slrflag++; 823 altarg = stringbase; 824 break; 825 default: 826 break; 827 } 828 } 829 830 S0: 831 switch (*sb) { 832 833 case '\0': 834 goto OUT; 835 836 case ' ': 837 case '\t': 838 INC_CHKCURSOR(sb); 839 goto S0; 840 841 default: 842 switch (slrflag) { 843 case 0: 844 slrflag++; 845 break; 846 case 1: 847 slrflag++; 848 altarg = sb; 849 break; 850 default: 851 break; 852 } 853 goto S1; 854 } 855 856 S1: 857 switch (*sb) { 858 859 case ' ': 860 case '\t': 861 case '\0': 862 goto OUT; /* end of token */ 863 864 case '\\': 865 INC_CHKCURSOR(sb); 866 goto S2; /* slurp next character */ 867 868 case '"': 869 INC_CHKCURSOR(sb); 870 goto S3; /* slurp quoted string */ 871 872 default: 873 *ap = *sb; /* add character to token */ 874 ap++; 875 INC_CHKCURSOR(sb); 876 got_one = 1; 877 goto S1; 878 } 879 880 S2: 881 switch (*sb) { 882 883 case '\0': 884 goto OUT; 885 886 default: 887 *ap = *sb; 888 ap++; 889 INC_CHKCURSOR(sb); 890 got_one = 1; 891 goto S1; 892 } 893 894 S3: 895 switch (*sb) { 896 897 case '\0': 898 goto OUT; 899 900 case '"': 901 INC_CHKCURSOR(sb); 902 goto S1; 903 904 default: 905 *ap = *sb; 906 ap++; 907 INC_CHKCURSOR(sb); 908 got_one = 1; 909 goto S3; 910 } 911 912 OUT: 913 if (got_one) 914 *ap++ = '\0'; 915 argbase = ap; /* update storage pointer */ 916 stringbase = sb; /* update scan pointer */ 917 if (got_one) { 918 return (tmp); 919 } 920 switch (slrflag) { 921 case 0: 922 slrflag++; 923 break; 924 case 1: 925 slrflag++; 926 altarg = NULL; 927 break; 928 default: 929 break; 930 } 931 return (NULL); 932 } 933 934 /* 935 * Help/usage command. 936 * Call each command handler with argc == 0 and argv[0] == name. 937 */ 938 void 939 help(int argc, char *argv[]) 940 { 941 struct cmd *c; 942 char *nargv[1], *p, *cmd; 943 int isusage; 944 945 cmd = argv[0]; 946 isusage = (strcmp(cmd, "usage") == 0); 947 if (argc == 0 || (isusage && argc == 1)) { 948 fprintf(ttyout, "usage: %s [command [...]]\n", cmd); 949 return; 950 } 951 if (argc == 1) { 952 StringList *buf; 953 954 buf = xsl_init(); 955 fprintf(ttyout, 956 "%sommands may be abbreviated. Commands are:\n\n", 957 proxy ? "Proxy c" : "C"); 958 for (c = cmdtab; (p = c->c_name) != NULL; c++) 959 if (!proxy || c->c_proxy) 960 xsl_add(buf, p); 961 list_vertical(buf); 962 sl_free(buf, 0); 963 return; 964 } 965 966 #define HELPINDENT ((int) sizeof("disconnect")) 967 968 while (--argc > 0) { 969 char *arg; 970 971 arg = *++argv; 972 c = getcmd(arg); 973 if (c == (struct cmd *)-1) 974 fprintf(ttyout, "?Ambiguous %s command `%s'\n", 975 cmd, arg); 976 else if (c == NULL) 977 fprintf(ttyout, "?Invalid %s command `%s'\n", 978 cmd, arg); 979 else { 980 if (isusage) { 981 nargv[0] = c->c_name; 982 (*c->c_handler)(0, nargv); 983 } else 984 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, 985 c->c_name, c->c_help); 986 } 987 } 988 } 989 990 struct option * 991 getoption(const char *name) 992 { 993 const char *p; 994 struct option *c; 995 996 if (name == NULL) 997 return (NULL); 998 for (c = optiontab; (p = c->name) != NULL; c++) { 999 if (strcasecmp(p, name) == 0) 1000 return (c); 1001 } 1002 return (NULL); 1003 } 1004 1005 char * 1006 getoptionvalue(const char *name) 1007 { 1008 struct option *c; 1009 1010 if (name == NULL) 1011 errx(1, "getoptionvalue() invoked with NULL name"); 1012 c = getoption(name); 1013 if (c != NULL) 1014 return (c->value); 1015 errx(1, "getoptionvalue() invoked with unknown option `%s'", name); 1016 /* NOTREACHED */ 1017 } 1018 1019 static void 1020 setupoption(char *name, char *value, char *defaultvalue) 1021 { 1022 char *nargv[3]; 1023 int overbose; 1024 1025 nargv[0] = "setupoption()"; 1026 nargv[1] = name; 1027 nargv[2] = (value ? value : defaultvalue); 1028 overbose = verbose; 1029 verbose = 0; 1030 setoption(3, nargv); 1031 verbose = overbose; 1032 } 1033 1034 void 1035 usage(void) 1036 { 1037 const char *progname = getprogname(); 1038 1039 (void)fprintf(stderr, 1040 "usage: %s [-46AadefginpRtvV] [-N netrc] [-o outfile] [-P port] [-q quittime]\n" 1041 " [-r retry] [-T dir,max[,inc][[user@]host [port]]] [host:path[/]]\n" 1042 " [file:///file] [ftp://[user[:pass]@]host[:port]/path[/]]\n" 1043 " [http://[user[:pass]@]host[:port]/path] [...]\n" 1044 " %s -u URL file [...]\n", progname, progname); 1045 exit(1); 1046 } 1047