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