1 /* $NetBSD: main.c,v 1.120 2011/12/10 05:53:58 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1996-2009 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 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Copyright (c) 1985, 1989, 1993, 1994 34 * The Regents of the University of California. All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions 38 * are met: 39 * 1. Redistributions of source code must retain the above copyright 40 * notice, this list of conditions and the following disclaimer. 41 * 2. Redistributions in binary form must reproduce the above copyright 42 * notice, this list of conditions and the following disclaimer in the 43 * documentation and/or other materials provided with the distribution. 44 * 3. Neither the name of the University nor the names of its contributors 45 * may be used to endorse or promote products derived from this software 46 * without specific prior written permission. 47 * 48 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 49 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 51 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 52 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 53 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 54 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 57 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 58 * SUCH DAMAGE. 59 */ 60 61 /* 62 * Copyright (C) 1997 and 1998 WIDE Project. 63 * All rights reserved. 64 * 65 * Redistribution and use in source and binary forms, with or without 66 * modification, are permitted provided that the following conditions 67 * are met: 68 * 1. Redistributions of source code must retain the above copyright 69 * notice, this list of conditions and the following disclaimer. 70 * 2. Redistributions in binary form must reproduce the above copyright 71 * notice, this list of conditions and the following disclaimer in the 72 * documentation and/or other materials provided with the distribution. 73 * 3. Neither the name of the project nor the names of its contributors 74 * may be used to endorse or promote products derived from this software 75 * without specific prior written permission. 76 * 77 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 78 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 79 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 80 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 81 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 82 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 83 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 84 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 85 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 86 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 87 * SUCH DAMAGE. 88 */ 89 90 #include <sys/cdefs.h> 91 #ifndef lint 92 __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\ 93 The Regents of the University of California. All rights reserved.\ 94 Copyright 1996-2008 The NetBSD Foundation, Inc. All rights reserved"); 95 #endif /* not lint */ 96 97 #ifndef lint 98 #if 0 99 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 10/9/94"; 100 #else 101 __RCSID("$NetBSD: main.c,v 1.120 2011/12/10 05:53:58 lukem Exp $"); 102 #endif 103 #endif /* not lint */ 104 105 /* 106 * FTP User Program -- Command Interface. 107 */ 108 #include <sys/types.h> 109 #include <sys/socket.h> 110 111 #include <err.h> 112 #include <errno.h> 113 #include <netdb.h> 114 #include <paths.h> 115 #include <pwd.h> 116 #include <signal.h> 117 #include <stdio.h> 118 #include <stdlib.h> 119 #include <string.h> 120 #include <time.h> 121 #include <unistd.h> 122 #include <locale.h> 123 124 #define GLOBAL /* force GLOBAL decls in ftp_var.h to be declared */ 125 #include "ftp_var.h" 126 127 #define FTP_PROXY "ftp_proxy" /* env var with FTP proxy location */ 128 #define HTTP_PROXY "http_proxy" /* env var with HTTP proxy location */ 129 #define NO_PROXY "no_proxy" /* env var with list of non-proxied 130 * hosts, comma or space separated */ 131 132 __dead static void usage(void); 133 static void setupoption(const char *, const char *, const char *); 134 135 int 136 main(int volatile argc, char **volatile argv) 137 { 138 int ch, rval; 139 struct passwd *pw; 140 char *cp, *ep, *anonpass, *upload_path, *src_addr; 141 const char *anonuser; 142 int dumbterm, isupload; 143 size_t len; 144 145 tzset(); 146 setlocale(LC_ALL, ""); 147 setprogname(argv[0]); 148 149 sigint_raised = 0; 150 151 ftpport = "ftp"; 152 httpport = "http"; 153 gateport = NULL; 154 cp = getenv("FTPSERVERPORT"); 155 if (cp != NULL) 156 gateport = cp; 157 else 158 gateport = "ftpgate"; 159 doglob = 1; 160 interactive = 1; 161 autologin = 1; 162 passivemode = 1; 163 activefallback = 1; 164 preserve = 1; 165 verbose = 0; 166 progress = 0; 167 gatemode = 0; 168 data = -1; 169 outfile = NULL; 170 restartautofetch = 0; 171 #ifndef NO_EDITCOMPLETE 172 editing = 0; 173 el = NULL; 174 hist = NULL; 175 #endif 176 bytes = 0; 177 mark = HASHBYTES; 178 rate_get = 0; 179 rate_get_incr = DEFAULTINCR; 180 rate_put = 0; 181 rate_put_incr = DEFAULTINCR; 182 #ifdef INET6 183 epsv4 = 1; 184 epsv6 = 1; 185 #else 186 epsv4 = 0; 187 epsv6 = 0; 188 #endif 189 epsv4bad = 0; 190 epsv6bad = 0; 191 src_addr = NULL; 192 upload_path = NULL; 193 isupload = 0; 194 reply_callback = NULL; 195 #ifdef INET6 196 family = AF_UNSPEC; 197 #else 198 family = AF_INET; /* force AF_INET if no INET6 support */ 199 #endif 200 201 netrc[0] = '\0'; 202 cp = getenv("NETRC"); 203 if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc)) 204 errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG)); 205 206 marg_sl = ftp_sl_init(); 207 if ((tmpdir = getenv("TMPDIR")) == NULL) 208 tmpdir = _PATH_TMP; 209 210 /* Set default operation mode based on FTPMODE environment variable */ 211 if ((cp = getenv("FTPMODE")) != NULL) { 212 if (strcasecmp(cp, "passive") == 0) { 213 passivemode = 1; 214 activefallback = 0; 215 } else if (strcasecmp(cp, "active") == 0) { 216 passivemode = 0; 217 activefallback = 0; 218 } else if (strcasecmp(cp, "gate") == 0) { 219 gatemode = 1; 220 } else if (strcasecmp(cp, "auto") == 0) { 221 passivemode = 1; 222 activefallback = 1; 223 } else 224 warnx("Unknown $FTPMODE `%s'; using defaults", cp); 225 } 226 227 if (strcmp(getprogname(), "pftp") == 0) { 228 passivemode = 1; 229 activefallback = 0; 230 } else if (strcmp(getprogname(), "gate-ftp") == 0) 231 gatemode = 1; 232 233 gateserver = getenv("FTPSERVER"); 234 if (gateserver == NULL || *gateserver == '\0') 235 gateserver = GATE_SERVER; 236 if (gatemode) { 237 if (*gateserver == '\0') { 238 warnx( 239 "Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp"); 240 gatemode = 0; 241 } 242 } 243 244 cp = getenv("TERM"); 245 if (cp == NULL || strcmp(cp, "dumb") == 0) 246 dumbterm = 1; 247 else 248 dumbterm = 0; 249 fromatty = isatty(fileno(stdin)); 250 ttyout = stdout; 251 if (isatty(fileno(ttyout))) { 252 verbose = 1; /* verbose if to a tty */ 253 if (! dumbterm) { 254 #ifndef NO_EDITCOMPLETE 255 if (fromatty) /* editing mode on if tty is usable */ 256 editing = 1; 257 #endif 258 #ifndef NO_PROGRESS 259 if (foregroundproc()) 260 progress = 1; /* progress bar on if fg */ 261 #endif 262 } 263 } 264 265 while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:Rs:tT:u:vV")) != -1) { 266 switch (ch) { 267 case '4': 268 family = AF_INET; 269 break; 270 271 case '6': 272 #ifdef INET6 273 family = AF_INET6; 274 #else 275 warnx("INET6 support is not available; ignoring -6"); 276 #endif 277 break; 278 279 case 'A': 280 activefallback = 0; 281 passivemode = 0; 282 break; 283 284 case 'a': 285 anonftp = 1; 286 break; 287 288 case 'd': 289 options |= SO_DEBUG; 290 ftp_debug++; 291 break; 292 293 case 'e': 294 #ifndef NO_EDITCOMPLETE 295 editing = 0; 296 #endif 297 break; 298 299 case 'f': 300 flushcache = 1; 301 break; 302 303 case 'g': 304 doglob = 0; 305 break; 306 307 case 'i': 308 interactive = 0; 309 break; 310 311 case 'n': 312 autologin = 0; 313 break; 314 315 case 'N': 316 if (strlcpy(netrc, optarg, sizeof(netrc)) 317 >= sizeof(netrc)) 318 errx(1, "%s: %s", optarg, 319 strerror(ENAMETOOLONG)); 320 break; 321 322 case 'o': 323 outfile = optarg; 324 if (strcmp(outfile, "-") == 0) 325 ttyout = stderr; 326 break; 327 328 case 'p': 329 passivemode = 1; 330 activefallback = 0; 331 break; 332 333 case 'P': 334 ftpport = optarg; 335 break; 336 337 case 'q': 338 quit_time = strtol(optarg, &ep, 10); 339 if (quit_time < 1 || *ep != '\0') 340 errx(1, "Bad quit value: %s", optarg); 341 break; 342 343 case 'r': 344 retry_connect = strtol(optarg, &ep, 10); 345 if (retry_connect < 1 || *ep != '\0') 346 errx(1, "Bad retry value: %s", optarg); 347 break; 348 349 case 'R': 350 restartautofetch = 1; 351 break; 352 353 case 's': 354 src_addr = optarg; 355 break; 356 357 case 't': 358 trace = 1; 359 break; 360 361 case 'T': 362 { 363 int targc; 364 char *targv[6], *oac; 365 char cmdbuf[MAX_C_NAME]; 366 367 /* look for `dir,max[,incr]' */ 368 targc = 0; 369 (void)strlcpy(cmdbuf, "-T", sizeof(cmdbuf)); 370 targv[targc++] = cmdbuf; 371 oac = ftp_strdup(optarg); 372 373 while ((cp = strsep(&oac, ",")) != NULL) { 374 if (*cp == '\0') { 375 warnx("Bad throttle value `%s'", 376 optarg); 377 usage(); 378 /* NOTREACHED */ 379 } 380 targv[targc++] = cp; 381 if (targc >= 5) 382 break; 383 } 384 if (parserate(targc, targv, 1) == -1) 385 usage(); 386 free(oac); 387 break; 388 } 389 390 case 'u': 391 { 392 isupload = 1; 393 interactive = 0; 394 upload_path = ftp_strdup(optarg); 395 396 break; 397 } 398 399 case 'v': 400 progress = verbose = 1; 401 break; 402 403 case 'V': 404 progress = verbose = 0; 405 break; 406 407 default: 408 usage(); 409 } 410 } 411 /* set line buffering on ttyout */ 412 setvbuf(ttyout, NULL, _IOLBF, 0); 413 argc -= optind; 414 argv += optind; 415 416 cpend = 0; /* no pending replies */ 417 proxy = 0; /* proxy not active */ 418 crflag = 1; /* strip c.r. on ascii gets */ 419 sendport = -1; /* not using ports */ 420 421 if (src_addr != NULL) { 422 struct addrinfo hints; 423 int error; 424 425 memset(&hints, 0, sizeof(hints)); 426 hints.ai_family = family; 427 hints.ai_socktype = SOCK_STREAM; 428 hints.ai_flags = AI_PASSIVE; 429 error = getaddrinfo(src_addr, NULL, &hints, &bindai); 430 if (error) { 431 errx(1, "Can't lookup `%s': %s", src_addr, 432 (error == EAI_SYSTEM) ? strerror(errno) 433 : gai_strerror(error)); 434 } 435 } 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 = ftp_strdup(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 = ftp_strdup(pw->pw_dir); 455 localname = ftp_strdup(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 = ftp_strdup("/"); 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 = ftp_malloc(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 if (argc > 0) { 505 if (isupload) { 506 rval = auto_put(argc, argv, upload_path); 507 sigint_or_rval_exit: 508 if (sigint_raised) { 509 (void)xsignal(SIGINT, SIG_DFL); 510 raise(SIGINT); 511 } 512 exit(rval); 513 } else if (strchr(argv[0], ':') != NULL 514 && ! isipv6addr(argv[0])) { 515 rval = auto_fetch(argc, argv); 516 if (rval >= 0) /* -1 == connected and cd-ed */ 517 goto sigint_or_rval_exit; 518 } else { 519 char *xargv[4], *uuser, *host; 520 char cmdbuf[MAXPATHLEN]; 521 522 if ((rval = sigsetjmp(toplevel, 1))) 523 goto sigint_or_rval_exit; 524 (void)xsignal(SIGINT, intr); 525 (void)xsignal(SIGPIPE, lostpeer); 526 uuser = NULL; 527 host = argv[0]; 528 cp = strchr(host, '@'); 529 if (cp) { 530 *cp = '\0'; 531 uuser = host; 532 host = cp + 1; 533 } 534 (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); 535 xargv[0] = cmdbuf; 536 xargv[1] = host; 537 xargv[2] = argv[1]; 538 xargv[3] = NULL; 539 do { 540 int oautologin; 541 542 oautologin = autologin; 543 if (uuser != NULL) { 544 anonftp = 0; 545 autologin = 0; 546 } 547 setpeer(argc+1, xargv); 548 autologin = oautologin; 549 if (connected == 1 && uuser != NULL) 550 (void)ftp_login(host, uuser, NULL); 551 if (!retry_connect) 552 break; 553 if (!connected) { 554 macnum = 0; 555 fprintf(ttyout, 556 "Retrying in %d seconds...\n", 557 retry_connect); 558 sleep(retry_connect); 559 } 560 } while (!connected); 561 retry_connect = 0; /* connected, stop hiding msgs */ 562 } 563 } 564 if (isupload) 565 usage(); 566 567 #ifndef NO_EDITCOMPLETE 568 controlediting(); 569 #endif /* !NO_EDITCOMPLETE */ 570 571 (void)sigsetjmp(toplevel, 1); 572 (void)xsignal(SIGINT, intr); 573 (void)xsignal(SIGPIPE, lostpeer); 574 for (;;) 575 cmdscanner(); 576 } 577 578 /* 579 * Generate a prompt 580 */ 581 char * 582 prompt(void) 583 { 584 static char **promptopt; 585 static char buf[MAXPATHLEN]; 586 587 if (promptopt == NULL) { 588 struct option *o; 589 590 o = getoption("prompt"); 591 if (o == NULL) 592 errx(1, "prompt: no such option `prompt'"); 593 promptopt = &(o->value); 594 } 595 formatbuf(buf, sizeof(buf), *promptopt ? *promptopt : DEFAULTPROMPT); 596 return (buf); 597 } 598 599 /* 600 * Generate an rprompt 601 */ 602 char * 603 rprompt(void) 604 { 605 static char **rpromptopt; 606 static char buf[MAXPATHLEN]; 607 608 if (rpromptopt == NULL) { 609 struct option *o; 610 611 o = getoption("rprompt"); 612 if (o == NULL) 613 errx(1, "rprompt: no such option `rprompt'"); 614 rpromptopt = &(o->value); 615 } 616 formatbuf(buf, sizeof(buf), *rpromptopt ? *rpromptopt : DEFAULTRPROMPT); 617 return (buf); 618 } 619 620 /* 621 * Command parser. 622 */ 623 void 624 cmdscanner(void) 625 { 626 struct cmd *c; 627 char *p; 628 #ifndef NO_EDITCOMPLETE 629 int ch; 630 size_t num; 631 #endif 632 int len; 633 char cmdbuf[MAX_C_NAME]; 634 635 for (;;) { 636 #ifndef NO_EDITCOMPLETE 637 if (!editing) { 638 #endif /* !NO_EDITCOMPLETE */ 639 if (fromatty) { 640 fputs(prompt(), ttyout); 641 p = rprompt(); 642 if (*p) 643 fprintf(ttyout, "%s ", p); 644 } 645 (void)fflush(ttyout); 646 len = get_line(stdin, line, sizeof(line), NULL); 647 switch (len) { 648 case -1: /* EOF */ 649 case -2: /* error */ 650 if (fromatty) 651 putc('\n', ttyout); 652 quit(0, NULL); 653 /* NOTREACHED */ 654 case -3: /* too long; try again */ 655 fputs("Sorry, input line is too long.\n", 656 ttyout); 657 continue; 658 case 0: /* empty; try again */ 659 continue; 660 default: /* all ok */ 661 break; 662 } 663 #ifndef NO_EDITCOMPLETE 664 } else { 665 const char *buf; 666 HistEvent ev; 667 cursor_pos = NULL; 668 669 buf = el_gets(el, &ch); 670 num = ch; 671 if (buf == NULL || num == 0) { 672 if (fromatty) 673 putc('\n', ttyout); 674 quit(0, NULL); 675 } 676 if (num >= sizeof(line)) { 677 fputs("Sorry, input line is too long.\n", 678 ttyout); 679 break; 680 } 681 memcpy(line, buf, num); 682 if (line[--num] == '\n') { 683 line[num] = '\0'; 684 if (num == 0) 685 break; 686 } 687 history(hist, &ev, H_ENTER, buf); 688 } 689 #endif /* !NO_EDITCOMPLETE */ 690 691 makeargv(); 692 if (margc == 0) 693 continue; 694 c = getcmd(margv[0]); 695 if (c == (struct cmd *)-1) { 696 fputs("?Ambiguous command.\n", ttyout); 697 continue; 698 } 699 if (c == NULL) { 700 #if !defined(NO_EDITCOMPLETE) 701 /* 702 * attempt to el_parse() unknown commands. 703 * any command containing a ':' would be parsed 704 * as "[prog:]cmd ...", and will result in a 705 * false positive if prog != "ftp", so treat 706 * such commands as invalid. 707 */ 708 if (strchr(margv[0], ':') != NULL || 709 !editing || 710 el_parse(el, margc, (void *)margv) != 0) 711 #endif /* !NO_EDITCOMPLETE */ 712 fputs("?Invalid command.\n", ttyout); 713 continue; 714 } 715 if (c->c_conn && !connected) { 716 fputs("Not connected.\n", ttyout); 717 continue; 718 } 719 confirmrest = 0; 720 (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf)); 721 margv[0] = cmdbuf; 722 (*c->c_handler)(margc, margv); 723 if (bell && c->c_bell) 724 (void)putc('\007', ttyout); 725 if (c->c_handler != help) 726 break; 727 } 728 (void)xsignal(SIGINT, intr); 729 (void)xsignal(SIGPIPE, lostpeer); 730 } 731 732 struct cmd * 733 getcmd(const char *name) 734 { 735 const char *p, *q; 736 struct cmd *c, *found; 737 int nmatches, longest; 738 739 if (name == NULL) 740 return (0); 741 742 longest = 0; 743 nmatches = 0; 744 found = 0; 745 for (c = cmdtab; (p = c->c_name) != NULL; c++) { 746 for (q = name; *q == *p++; q++) 747 if (*q == 0) /* exact match? */ 748 return (c); 749 if (!*q) { /* the name was a prefix */ 750 if (q - name > longest) { 751 longest = q - name; 752 nmatches = 1; 753 found = c; 754 } else if (q - name == longest) 755 nmatches++; 756 } 757 } 758 if (nmatches > 1) 759 return ((struct cmd *)-1); 760 return (found); 761 } 762 763 /* 764 * Slice a string up into argc/argv. 765 */ 766 767 int slrflag; 768 769 void 770 makeargv(void) 771 { 772 char *argp; 773 774 stringbase = line; /* scan from first of buffer */ 775 argbase = argbuf; /* store from first of buffer */ 776 slrflag = 0; 777 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 778 for (margc = 0; ; margc++) { 779 argp = slurpstring(); 780 ftp_sl_add(marg_sl, argp); 781 if (argp == NULL) 782 break; 783 } 784 #ifndef NO_EDITCOMPLETE 785 if (cursor_pos == line) { 786 cursor_argc = 0; 787 cursor_argo = 0; 788 } else if (cursor_pos != NULL) { 789 cursor_argc = margc; 790 cursor_argo = strlen(margv[margc-1]); 791 } 792 #endif /* !NO_EDITCOMPLETE */ 793 } 794 795 #ifdef NO_EDITCOMPLETE 796 #define INC_CHKCURSOR(x) (x)++ 797 #else /* !NO_EDITCOMPLETE */ 798 #define INC_CHKCURSOR(x) { (x)++ ; \ 799 if (x == cursor_pos) { \ 800 cursor_argc = margc; \ 801 cursor_argo = ap-argbase; \ 802 cursor_pos = NULL; \ 803 } } 804 805 #endif /* !NO_EDITCOMPLETE */ 806 807 /* 808 * Parse string into argbuf; 809 * implemented with FSM to 810 * handle quoting and strings 811 */ 812 char * 813 slurpstring(void) 814 { 815 static char bangstr[2] = { '!', '\0' }; 816 static char dollarstr[2] = { '$', '\0' }; 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 == '!') ? bangstr : dollarstr); 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], *cmd; 951 const char *p; 952 int isusage; 953 954 cmd = argv[0]; 955 isusage = (strcmp(cmd, "usage") == 0); 956 if (argc == 0 || (isusage && argc == 1)) { 957 UPRINTF("usage: %s [command [...]]\n", cmd); 958 return; 959 } 960 if (argc == 1) { 961 StringList *buf; 962 963 buf = ftp_sl_init(); 964 fprintf(ttyout, 965 "%sommands may be abbreviated. Commands are:\n\n", 966 proxy ? "Proxy c" : "C"); 967 for (c = cmdtab; (p = c->c_name) != NULL; c++) 968 if (!proxy || c->c_proxy) 969 ftp_sl_add(buf, ftp_strdup(p)); 970 list_vertical(buf); 971 sl_free(buf, 1); 972 return; 973 } 974 975 #define HELPINDENT ((int) sizeof("disconnect")) 976 977 while (--argc > 0) { 978 char *arg; 979 char cmdbuf[MAX_C_NAME]; 980 981 arg = *++argv; 982 c = getcmd(arg); 983 if (c == (struct cmd *)-1) 984 fprintf(ttyout, "?Ambiguous %s command `%s'\n", 985 cmd, arg); 986 else if (c == NULL) 987 fprintf(ttyout, "?Invalid %s command `%s'\n", 988 cmd, arg); 989 else { 990 if (isusage) { 991 (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf)); 992 nargv[0] = cmdbuf; 993 (*c->c_handler)(0, nargv); 994 } else 995 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, 996 c->c_name, c->c_help); 997 } 998 } 999 } 1000 1001 struct option * 1002 getoption(const char *name) 1003 { 1004 const char *p; 1005 struct option *c; 1006 1007 if (name == NULL) 1008 return (NULL); 1009 for (c = optiontab; (p = c->name) != NULL; c++) { 1010 if (strcasecmp(p, name) == 0) 1011 return (c); 1012 } 1013 return (NULL); 1014 } 1015 1016 char * 1017 getoptionvalue(const char *name) 1018 { 1019 struct option *c; 1020 1021 if (name == NULL) 1022 errx(1, "getoptionvalue: invoked with NULL name"); 1023 c = getoption(name); 1024 if (c != NULL) 1025 return (c->value); 1026 errx(1, "getoptionvalue: invoked with unknown option `%s'", name); 1027 /* NOTREACHED */ 1028 } 1029 1030 static void 1031 setupoption(const char *name, const char *value, const char *defaultvalue) 1032 { 1033 set_option(name, value ? value : defaultvalue, 0); 1034 } 1035 1036 void 1037 usage(void) 1038 { 1039 const char *progname = getprogname(); 1040 1041 (void)fprintf(stderr, 1042 "usage: %s [-46AadefginpRtVv] [-N netrc] [-o outfile] [-P port] [-q quittime]\n" 1043 " [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n" 1044 " [[user@]host [port]] [host:path[/]] [file:///file]\n" 1045 " [ftp://[user[:pass]@]host[:port]/path[/]]\n" 1046 " [http://[user[:pass]@]host[:port]/path] [...]\n" 1047 " %s -u URL file [...]\n", progname, progname); 1048 exit(1); 1049 } 1050