1 /* $NetBSD: main.c,v 1.111 2008/07/21 23:11:16 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1996-2008 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.111 2008/07/21 23:11:16 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 static void setupoption(char *, char *, char *); 133 int main(int, 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, *anonuser, *anonpass, *upload_path, *src_addr; 141 int dumbterm, s, isupload; 142 size_t len; 143 socklen_t slen; 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 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 to determine default socket sizes"); 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 = ftp_sl_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:Rs:tT: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 ftp_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 's': 379 src_addr = optarg; 380 break; 381 382 case 't': 383 trace = 1; 384 break; 385 386 case 'T': 387 { 388 int targc; 389 char *targv[6], *oac; 390 391 /* look for `dir,max[,incr]' */ 392 targc = 0; 393 targv[targc++] = "-T"; 394 oac = ftp_strdup(optarg); 395 396 while ((cp = strsep(&oac, ",")) != NULL) { 397 if (*cp == '\0') { 398 warnx("Bad throttle value `%s'", 399 optarg); 400 usage(); 401 /* NOTREACHED */ 402 } 403 targv[targc++] = cp; 404 if (targc >= 5) 405 break; 406 } 407 if (parserate(targc, targv, 1) == -1) 408 usage(); 409 free(oac); 410 break; 411 } 412 413 case 'u': 414 { 415 isupload = 1; 416 interactive = 0; 417 upload_path = ftp_strdup(optarg); 418 419 break; 420 } 421 422 case 'v': 423 progress = verbose = 1; 424 break; 425 426 case 'V': 427 progress = verbose = 0; 428 break; 429 430 default: 431 usage(); 432 } 433 } 434 /* set line buffering on ttyout */ 435 setvbuf(ttyout, NULL, _IOLBF, 0); 436 argc -= optind; 437 argv += optind; 438 439 cpend = 0; /* no pending replies */ 440 proxy = 0; /* proxy not active */ 441 crflag = 1; /* strip c.r. on ascii gets */ 442 sendport = -1; /* not using ports */ 443 444 if (src_addr != NULL) { 445 struct addrinfo hints; 446 int error; 447 448 memset(&hints, 0, sizeof(hints)); 449 hints.ai_family = family; 450 hints.ai_socktype = SOCK_STREAM; 451 hints.ai_flags = AI_PASSIVE; 452 error = getaddrinfo(src_addr, NULL, &hints, &bindai); 453 if (error) { 454 errx(1, "Can't lookup `%s': %s", src_addr, 455 (error == EAI_SYSTEM) ? strerror(errno) 456 : gai_strerror(error)); 457 } 458 } 459 460 /* 461 * Cache the user name and home directory. 462 */ 463 localhome = NULL; 464 localname = NULL; 465 anonuser = "anonymous"; 466 cp = getenv("HOME"); 467 if (! EMPTYSTRING(cp)) 468 localhome = ftp_strdup(cp); 469 pw = NULL; 470 cp = getlogin(); 471 if (cp != NULL) 472 pw = getpwnam(cp); 473 if (pw == NULL) 474 pw = getpwuid(getuid()); 475 if (pw != NULL) { 476 if (localhome == NULL && !EMPTYSTRING(pw->pw_dir)) 477 localhome = ftp_strdup(pw->pw_dir); 478 localname = ftp_strdup(pw->pw_name); 479 anonuser = localname; 480 } 481 if (netrc[0] == '\0' && localhome != NULL) { 482 if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) || 483 strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) { 484 warnx("%s/.netrc: %s", localhome, 485 strerror(ENAMETOOLONG)); 486 netrc[0] = '\0'; 487 } 488 } 489 if (localhome == NULL) 490 localhome = ftp_strdup("/"); 491 492 /* 493 * Every anonymous FTP server I've encountered will accept the 494 * string "username@", and will append the hostname itself. We 495 * do this by default since many servers are picky about not 496 * having a FQDN in the anonymous password. 497 * - thorpej@NetBSD.org 498 */ 499 len = strlen(anonuser) + 2; 500 anonpass = ftp_malloc(len); 501 (void)strlcpy(anonpass, anonuser, len); 502 (void)strlcat(anonpass, "@", len); 503 504 /* 505 * set all the defaults for options defined in 506 * struct option optiontab[] declared in cmdtab.c 507 */ 508 setupoption("anonpass", getenv("FTPANONPASS"), anonpass); 509 setupoption("ftp_proxy", getenv(FTP_PROXY), ""); 510 setupoption("http_proxy", getenv(HTTP_PROXY), ""); 511 setupoption("no_proxy", getenv(NO_PROXY), ""); 512 setupoption("pager", getenv("PAGER"), DEFAULTPAGER); 513 setupoption("prompt", getenv("FTPPROMPT"), DEFAULTPROMPT); 514 setupoption("rprompt", getenv("FTPRPROMPT"), DEFAULTRPROMPT); 515 516 free(anonpass); 517 518 setttywidth(0); 519 #ifdef SIGINFO 520 (void)xsignal(SIGINFO, psummary); 521 #endif 522 (void)xsignal(SIGQUIT, psummary); 523 (void)xsignal(SIGUSR1, crankrate); 524 (void)xsignal(SIGUSR2, crankrate); 525 (void)xsignal(SIGWINCH, setttywidth); 526 527 if (argc > 0) { 528 if (isupload) { 529 rval = auto_put(argc, argv, upload_path); 530 sigint_or_rval_exit: 531 if (sigint_raised) { 532 (void)xsignal(SIGINT, SIG_DFL); 533 raise(SIGINT); 534 } 535 exit(rval); 536 } else if (strchr(argv[0], ':') != NULL 537 && ! isipv6addr(argv[0])) { 538 rval = auto_fetch(argc, argv); 539 if (rval >= 0) /* -1 == connected and cd-ed */ 540 goto sigint_or_rval_exit; 541 } else { 542 char *xargv[4], *user, *host; 543 544 if ((rval = sigsetjmp(toplevel, 1))) 545 goto sigint_or_rval_exit; 546 (void)xsignal(SIGINT, intr); 547 (void)xsignal(SIGPIPE, lostpeer); 548 user = NULL; 549 host = argv[0]; 550 cp = strchr(host, '@'); 551 if (cp) { 552 *cp = '\0'; 553 user = host; 554 host = cp + 1; 555 } 556 /* XXX discards const */ 557 xargv[0] = (char *)getprogname(); 558 xargv[1] = host; 559 xargv[2] = argv[1]; 560 xargv[3] = NULL; 561 do { 562 int oautologin; 563 564 oautologin = autologin; 565 if (user != NULL) { 566 anonftp = 0; 567 autologin = 0; 568 } 569 setpeer(argc+1, xargv); 570 autologin = oautologin; 571 if (connected == 1 && user != NULL) 572 (void)ftp_login(host, user, NULL); 573 if (!retry_connect) 574 break; 575 if (!connected) { 576 macnum = 0; 577 fprintf(ttyout, 578 "Retrying in %d seconds...\n", 579 retry_connect); 580 sleep(retry_connect); 581 } 582 } while (!connected); 583 retry_connect = 0; /* connected, stop hiding msgs */ 584 } 585 } 586 if (isupload) 587 usage(); 588 589 #ifndef NO_EDITCOMPLETE 590 controlediting(); 591 #endif /* !NO_EDITCOMPLETE */ 592 593 (void)sigsetjmp(toplevel, 1); 594 (void)xsignal(SIGINT, intr); 595 (void)xsignal(SIGPIPE, lostpeer); 596 for (;;) 597 cmdscanner(); 598 } 599 600 /* 601 * Generate a prompt 602 */ 603 char * 604 prompt(void) 605 { 606 static char **prompt; 607 static char buf[MAXPATHLEN]; 608 609 if (prompt == NULL) { 610 struct option *o; 611 612 o = getoption("prompt"); 613 if (o == NULL) 614 errx(1, "prompt: no such option `prompt'"); 615 prompt = &(o->value); 616 } 617 formatbuf(buf, sizeof(buf), *prompt ? *prompt : DEFAULTPROMPT); 618 return (buf); 619 } 620 621 /* 622 * Generate an rprompt 623 */ 624 char * 625 rprompt(void) 626 { 627 static char **rprompt; 628 static char buf[MAXPATHLEN]; 629 630 if (rprompt == NULL) { 631 struct option *o; 632 633 o = getoption("rprompt"); 634 if (o == NULL) 635 errx(1, "rprompt: no such option `rprompt'"); 636 rprompt = &(o->value); 637 } 638 formatbuf(buf, sizeof(buf), *rprompt ? *rprompt : DEFAULTRPROMPT); 639 return (buf); 640 } 641 642 /* 643 * Command parser. 644 */ 645 void 646 cmdscanner(void) 647 { 648 struct cmd *c; 649 char *p; 650 #ifndef NO_EDITCOMPLETE 651 int ch; 652 #endif 653 size_t num; 654 655 for (;;) { 656 #ifndef NO_EDITCOMPLETE 657 if (!editing) { 658 #endif /* !NO_EDITCOMPLETE */ 659 if (fromatty) { 660 fputs(prompt(), ttyout); 661 p = rprompt(); 662 if (*p) 663 fprintf(ttyout, "%s ", p); 664 } 665 (void)fflush(ttyout); 666 num = getline(stdin, line, sizeof(line), NULL); 667 switch (num) { 668 case -1: /* EOF */ 669 case -2: /* error */ 670 if (fromatty) 671 putc('\n', ttyout); 672 quit(0, NULL); 673 /* NOTREACHED */ 674 case -3: /* too long; try again */ 675 fputs("Sorry, input line is too long.\n", 676 ttyout); 677 continue; 678 case 0: /* empty; try again */ 679 continue; 680 default: /* all ok */ 681 break; 682 } 683 #ifndef NO_EDITCOMPLETE 684 } else { 685 const char *buf; 686 HistEvent ev; 687 cursor_pos = NULL; 688 689 buf = el_gets(el, &ch); 690 num = ch; 691 if (buf == NULL || num == 0) { 692 if (fromatty) 693 putc('\n', ttyout); 694 quit(0, NULL); 695 } 696 if (num >= sizeof(line)) { 697 fputs("Sorry, input line is too long.\n", 698 ttyout); 699 break; 700 } 701 memcpy(line, buf, num); 702 if (line[--num] == '\n') { 703 line[num] = '\0'; 704 if (num == 0) 705 break; 706 } 707 history(hist, &ev, H_ENTER, buf); 708 } 709 #endif /* !NO_EDITCOMPLETE */ 710 711 makeargv(); 712 if (margc == 0) 713 continue; 714 c = getcmd(margv[0]); 715 if (c == (struct cmd *)-1) { 716 fputs("?Ambiguous command.\n", ttyout); 717 continue; 718 } 719 if (c == NULL) { 720 #if !defined(NO_EDITCOMPLETE) 721 /* 722 * attempt to el_parse() unknown commands. 723 * any command containing a ':' would be parsed 724 * as "[prog:]cmd ...", and will result in a 725 * false positive if prog != "ftp", so treat 726 * such commands as invalid. 727 */ 728 if (strchr(margv[0], ':') != NULL || 729 !editing || 730 el_parse(el, margc, (const char **)margv) != 0) 731 #endif /* !NO_EDITCOMPLETE */ 732 fputs("?Invalid command.\n", ttyout); 733 continue; 734 } 735 if (c->c_conn && !connected) { 736 fputs("Not connected.\n", ttyout); 737 continue; 738 } 739 confirmrest = 0; 740 margv[0] = c->c_name; 741 (*c->c_handler)(margc, margv); 742 if (bell && c->c_bell) 743 (void)putc('\007', ttyout); 744 if (c->c_handler != help) 745 break; 746 } 747 (void)xsignal(SIGINT, intr); 748 (void)xsignal(SIGPIPE, lostpeer); 749 } 750 751 struct cmd * 752 getcmd(const char *name) 753 { 754 const char *p, *q; 755 struct cmd *c, *found; 756 int nmatches, longest; 757 758 if (name == NULL) 759 return (0); 760 761 longest = 0; 762 nmatches = 0; 763 found = 0; 764 for (c = cmdtab; (p = c->c_name) != NULL; c++) { 765 for (q = name; *q == *p++; q++) 766 if (*q == 0) /* exact match? */ 767 return (c); 768 if (!*q) { /* the name was a prefix */ 769 if (q - name > longest) { 770 longest = q - name; 771 nmatches = 1; 772 found = c; 773 } else if (q - name == longest) 774 nmatches++; 775 } 776 } 777 if (nmatches > 1) 778 return ((struct cmd *)-1); 779 return (found); 780 } 781 782 /* 783 * Slice a string up into argc/argv. 784 */ 785 786 int slrflag; 787 788 void 789 makeargv(void) 790 { 791 char *argp; 792 793 stringbase = line; /* scan from first of buffer */ 794 argbase = argbuf; /* store from first of buffer */ 795 slrflag = 0; 796 marg_sl->sl_cur = 0; /* reset to start of marg_sl */ 797 for (margc = 0; ; margc++) { 798 argp = slurpstring(); 799 ftp_sl_add(marg_sl, argp); 800 if (argp == NULL) 801 break; 802 } 803 #ifndef NO_EDITCOMPLETE 804 if (cursor_pos == line) { 805 cursor_argc = 0; 806 cursor_argo = 0; 807 } else if (cursor_pos != NULL) { 808 cursor_argc = margc; 809 cursor_argo = strlen(margv[margc-1]); 810 } 811 #endif /* !NO_EDITCOMPLETE */ 812 } 813 814 #ifdef NO_EDITCOMPLETE 815 #define INC_CHKCURSOR(x) (x)++ 816 #else /* !NO_EDITCOMPLETE */ 817 #define INC_CHKCURSOR(x) { (x)++ ; \ 818 if (x == cursor_pos) { \ 819 cursor_argc = margc; \ 820 cursor_argo = ap-argbase; \ 821 cursor_pos = NULL; \ 822 } } 823 824 #endif /* !NO_EDITCOMPLETE */ 825 826 /* 827 * Parse string into argbuf; 828 * implemented with FSM to 829 * handle quoting and strings 830 */ 831 char * 832 slurpstring(void) 833 { 834 int got_one = 0; 835 char *sb = stringbase; 836 char *ap = argbase; 837 char *tmp = argbase; /* will return this if token found */ 838 839 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */ 840 switch (slrflag) { /* and $ as token for macro invoke */ 841 case 0: 842 slrflag++; 843 INC_CHKCURSOR(stringbase); 844 return ((*sb == '!') ? "!" : "$"); 845 /* NOTREACHED */ 846 case 1: 847 slrflag++; 848 altarg = stringbase; 849 break; 850 default: 851 break; 852 } 853 } 854 855 S0: 856 switch (*sb) { 857 858 case '\0': 859 goto OUT; 860 861 case ' ': 862 case '\t': 863 INC_CHKCURSOR(sb); 864 goto S0; 865 866 default: 867 switch (slrflag) { 868 case 0: 869 slrflag++; 870 break; 871 case 1: 872 slrflag++; 873 altarg = sb; 874 break; 875 default: 876 break; 877 } 878 goto S1; 879 } 880 881 S1: 882 switch (*sb) { 883 884 case ' ': 885 case '\t': 886 case '\0': 887 goto OUT; /* end of token */ 888 889 case '\\': 890 INC_CHKCURSOR(sb); 891 goto S2; /* slurp next character */ 892 893 case '"': 894 INC_CHKCURSOR(sb); 895 goto S3; /* slurp quoted string */ 896 897 default: 898 *ap = *sb; /* add character to token */ 899 ap++; 900 INC_CHKCURSOR(sb); 901 got_one = 1; 902 goto S1; 903 } 904 905 S2: 906 switch (*sb) { 907 908 case '\0': 909 goto OUT; 910 911 default: 912 *ap = *sb; 913 ap++; 914 INC_CHKCURSOR(sb); 915 got_one = 1; 916 goto S1; 917 } 918 919 S3: 920 switch (*sb) { 921 922 case '\0': 923 goto OUT; 924 925 case '"': 926 INC_CHKCURSOR(sb); 927 goto S1; 928 929 default: 930 *ap = *sb; 931 ap++; 932 INC_CHKCURSOR(sb); 933 got_one = 1; 934 goto S3; 935 } 936 937 OUT: 938 if (got_one) 939 *ap++ = '\0'; 940 argbase = ap; /* update storage pointer */ 941 stringbase = sb; /* update scan pointer */ 942 if (got_one) { 943 return (tmp); 944 } 945 switch (slrflag) { 946 case 0: 947 slrflag++; 948 break; 949 case 1: 950 slrflag++; 951 altarg = NULL; 952 break; 953 default: 954 break; 955 } 956 return (NULL); 957 } 958 959 /* 960 * Help/usage command. 961 * Call each command handler with argc == 0 and argv[0] == name. 962 */ 963 void 964 help(int argc, char *argv[]) 965 { 966 struct cmd *c; 967 char *nargv[1], *p, *cmd; 968 int isusage; 969 970 cmd = argv[0]; 971 isusage = (strcmp(cmd, "usage") == 0); 972 if (argc == 0 || (isusage && argc == 1)) { 973 UPRINTF("usage: %s [command [...]]\n", cmd); 974 return; 975 } 976 if (argc == 1) { 977 StringList *buf; 978 979 buf = ftp_sl_init(); 980 fprintf(ttyout, 981 "%sommands may be abbreviated. Commands are:\n\n", 982 proxy ? "Proxy c" : "C"); 983 for (c = cmdtab; (p = c->c_name) != NULL; c++) 984 if (!proxy || c->c_proxy) 985 ftp_sl_add(buf, p); 986 list_vertical(buf); 987 sl_free(buf, 0); 988 return; 989 } 990 991 #define HELPINDENT ((int) sizeof("disconnect")) 992 993 while (--argc > 0) { 994 char *arg; 995 996 arg = *++argv; 997 c = getcmd(arg); 998 if (c == (struct cmd *)-1) 999 fprintf(ttyout, "?Ambiguous %s command `%s'\n", 1000 cmd, arg); 1001 else if (c == NULL) 1002 fprintf(ttyout, "?Invalid %s command `%s'\n", 1003 cmd, arg); 1004 else { 1005 if (isusage) { 1006 nargv[0] = c->c_name; 1007 (*c->c_handler)(0, nargv); 1008 } else 1009 fprintf(ttyout, "%-*s\t%s\n", HELPINDENT, 1010 c->c_name, c->c_help); 1011 } 1012 } 1013 } 1014 1015 struct option * 1016 getoption(const char *name) 1017 { 1018 const char *p; 1019 struct option *c; 1020 1021 if (name == NULL) 1022 return (NULL); 1023 for (c = optiontab; (p = c->name) != NULL; c++) { 1024 if (strcasecmp(p, name) == 0) 1025 return (c); 1026 } 1027 return (NULL); 1028 } 1029 1030 char * 1031 getoptionvalue(const char *name) 1032 { 1033 struct option *c; 1034 1035 if (name == NULL) 1036 errx(1, "getoptionvalue: invoked with NULL name"); 1037 c = getoption(name); 1038 if (c != NULL) 1039 return (c->value); 1040 errx(1, "getoptionvalue: invoked with unknown option `%s'", name); 1041 /* NOTREACHED */ 1042 } 1043 1044 static void 1045 setupoption(char *name, char *value, char *defaultvalue) 1046 { 1047 char *nargv[3]; 1048 int overbose; 1049 1050 nargv[0] = "setupoption()"; 1051 nargv[1] = name; 1052 nargv[2] = (value ? value : defaultvalue); 1053 overbose = verbose; 1054 verbose = 0; 1055 setoption(3, nargv); 1056 verbose = overbose; 1057 } 1058 1059 void 1060 usage(void) 1061 { 1062 const char *progname = getprogname(); 1063 1064 (void)fprintf(stderr, 1065 "usage: %s [-46AadefginpRtVv] [-N netrc] [-o outfile] [-P port] [-q quittime]\n" 1066 " [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n" 1067 " [[user@]host [port]] [host:path[/]] [file:///file]\n" 1068 " [ftp://[user[:pass]@]host[:port]/path[/]]\n" 1069 " [http://[user[:pass]@]host[:port]/path] [...]\n" 1070 " %s -u URL file [...]\n", progname, progname); 1071 exit(1); 1072 } 1073