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