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