1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2002 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * University Copyright- Copyright (c) 1982, 1986, 1988 32 * The Regents of the University of California 33 * All Rights Reserved 34 * 35 * University Acknowledgment- Portions of this document are derived from 36 * software developed by the University of California, Berkeley, and its 37 * contributors. 38 */ 39 40 /* 41 * TFTP User Program -- Command Interface. 42 */ 43 #include <sys/types.h> 44 #include <sys/socket.h> 45 #include <sys/sysmacros.h> 46 47 #include <arpa/inet.h> 48 49 #include <signal.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <stdbool.h> 53 #include <errno.h> 54 #include <ctype.h> 55 #include <netdb.h> 56 #include <fcntl.h> 57 #include <string.h> 58 #include <limits.h> 59 #include <libtecla.h> 60 61 #include "tftpcommon.h" 62 #include "tftpprivate.h" 63 64 #define TIMEOUT 5 /* secs between rexmt's */ 65 66 struct sockaddr_in6 sin6; 67 int f; 68 int maxtimeout = 5 * TIMEOUT; 69 int verbose; 70 int trace; 71 int srexmtval; 72 int blksize; 73 int rexmtval = TIMEOUT; 74 int tsize_opt; 75 jmp_buf toplevel; 76 77 static int default_port, port; 78 static int connected; 79 static char mode[32]; 80 static char line[200]; 81 static char *prompt = "tftp> "; 82 static char hostname[MAXHOSTNAMELEN]; 83 static GetLine *gl; 84 85 static void intr(int); 86 static void quit(int, char **); 87 static void help(int, char **); 88 static void setverbose(int, char **); 89 static void settrace(int, char **); 90 static void status(int, char **); 91 static void get(int, char **); 92 static void put(int, char **); 93 static void setpeer(int, char **); 94 static void modecmd(int, char **); 95 static void setrexmt(int, char **); 96 static void settimeout(int, char **); 97 static void setbinary(int, char **); 98 static void setascii(int, char **); 99 static void setblksize(int, char **); 100 static void setsrexmt(int, char **); 101 static void settsize(int, char **); 102 static void setmode(char *); 103 static void putusage(char *); 104 static void getusage(char *); 105 static char *finddelimiter(char *); 106 static char *removebrackets(char *); 107 static int prompt_for_arg(char *, int, char *); 108 static struct cmd *getcmd(char *); 109 static char *tail(char *); 110 static void command(int); 111 static void makeargv(char *, int *, char ***); 112 113 #define HELPINDENT (sizeof ("connect")) 114 115 struct cmd { 116 char *name; 117 char *help; 118 void (*handler)(int, char **); 119 }; 120 121 static char vhelp[] = "toggle verbose mode"; 122 static char thelp[] = "toggle packet tracing"; 123 static char chelp[] = "connect to remote tftp"; 124 static char qhelp[] = "exit tftp"; 125 static char hhelp[] = "print help information"; 126 static char shelp[] = "send file"; 127 static char rhelp[] = "receive file"; 128 static char mhelp[] = "set file transfer mode"; 129 static char sthelp[] = "show current status"; 130 static char xhelp[] = "set per-packet retransmission timeout"; 131 static char ihelp[] = "set total retransmission timeout"; 132 static char ashelp[] = "set mode to netascii"; 133 static char bnhelp[] = "set mode to octet"; 134 static char bshelp[] = "set transfer blocksize to negotiate with the " 135 "server"; 136 static char srhelp[] = "set preferred per-packet retransmission " 137 "timeout for server"; 138 static char tshelp[] = "toggle sending the transfer size option to " 139 "the server"; 140 141 static struct cmd cmdtab[] = { 142 { "connect", chelp, setpeer }, 143 { "mode", mhelp, modecmd }, 144 { "put", shelp, put }, 145 { "get", rhelp, get }, 146 { "quit", qhelp, quit }, 147 { "verbose", vhelp, setverbose }, 148 { "trace", thelp, settrace }, 149 { "status", sthelp, status }, 150 { "binary", bnhelp, setbinary }, 151 { "ascii", ashelp, setascii }, 152 { "rexmt", xhelp, setrexmt }, 153 { "timeout", ihelp, settimeout }, 154 { "blksize", bshelp, setblksize }, 155 { "srexmt", srhelp, setsrexmt }, 156 { "tsize", tshelp, settsize }, 157 { "help", hhelp, help }, 158 { "?", hhelp, help }, 159 { NULL } 160 }; 161 162 #define AMBIGCMD (&cmdtab[ARRAY_SIZE(cmdtab)]) 163 164 static struct modes { 165 char *m_name; 166 char *m_mode; 167 } modes[] = { 168 { "ascii", "netascii" }, 169 { "netascii", "netascii" }, 170 { "binary", "octet" }, 171 { "image", "octet" }, 172 { "octet", "octet" }, 173 /* { "mail", "mail" }, */ 174 { NULL, NULL } 175 }; 176 177 static int 178 cmdmatch(WordCompletion *cpl, void *data, const char *line, int word_end) 179 { 180 struct cmd *cmds = data; 181 const char *word; 182 int i, rc = 0; 183 184 for (word = line + word_end; word > line && *(word - 1) != ' '; word--) 185 ; 186 187 /* This word is command */ 188 if (word == line) { 189 for (i = 0; cmds[i].name != NULL; i++) { 190 const char *cmd = strstr(cmds[i].name, word); 191 192 if (cmd == cmds[i].name) { 193 rc = cpl_add_completion(cpl, line, 0, 194 word_end, cmds[i].name + strlen(word), 195 NULL, NULL); 196 } 197 } 198 } else { 199 /* We only complete arguments for mode command */ 200 if (strncmp(line, "mode", 4) == 0) { 201 for (i = 0; modes[i].m_name != NULL; i++) { 202 const char *mode; 203 204 mode = strstr(modes[i].m_name, word); 205 if (mode == modes[i].m_name) { 206 rc = cpl_add_completion(cpl, line, 0, 207 word_end, 208 modes[i].m_name + strlen(word), 209 NULL, NULL); 210 } 211 } 212 } 213 } 214 215 return (rc); 216 } 217 218 #define LINELEN 1024 219 #define HISTORY 2048 220 221 int 222 main(int argc, char **argv) 223 { 224 struct servent *sp; 225 struct sockaddr_in6 sin6; 226 int top; 227 228 sp = getservbyname("tftp", "udp"); 229 default_port = (sp != NULL) ? sp->s_port : htons(IPPORT_TFTP); 230 port = default_port; 231 232 f = socket(AF_INET6, SOCK_DGRAM, 0); 233 if (f < 0) { 234 perror("tftp: socket"); 235 exit(3); 236 } 237 238 (void) memset(&sin6, 0, sizeof (sin6)); 239 sin6.sin6_family = AF_INET6; 240 if (bind(f, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) { 241 perror("tftp: bind"); 242 exit(1); 243 } 244 245 (void) strlcpy(mode, "netascii", sizeof (mode)); 246 247 gl = new_GetLine(LINELEN, HISTORY); 248 if (gl == NULL) { 249 perror("tftp: cli setup"); 250 exit(1); 251 } 252 253 /* SIGALRM is used by tftp */ 254 if (gl_ignore_signal(gl, SIGALRM) == 0) { 255 if (gl_customize_completion(gl, cmdtab, cmdmatch) != 0) 256 perror("gl_customize_completion"); 257 } else { 258 perror("gl_ignore_signal"); 259 } 260 261 (void) signal(SIGINT, intr); 262 if (argc > 1) { 263 if (setjmp(toplevel) != 0) 264 exit(0); 265 setpeer(argc, argv); 266 } 267 268 top = (setjmp(toplevel) == 0); 269 for (;;) 270 command(top); 271 272 /*NOTREACHED*/ 273 return (0); 274 } 275 276 /* Prompt for command argument, add to buffer with space separator */ 277 static int 278 prompt_for_arg(char *buffer, int buffer_size, char *prompt) 279 { 280 char *buf; 281 char *p; 282 283 if (strlcat(buffer, " ", buffer_size) >= buffer_size) { 284 (void) fputs("?Line too long\n", stderr); 285 return (-1); 286 } 287 288 if (asprintf(&p, "(%s) ", prompt) < 0) 289 perror("prompt_for_arg"); 290 buf = gl_get_line(gl, p, NULL, -1); 291 free(p); 292 if (buf == NULL) 293 return (-1); 294 295 if (strlcat(buffer, buf, buffer_size) >= buffer_size) { 296 (void) fputs("?Line too long\n", stderr); 297 return (-1); 298 } 299 return (0); 300 } 301 302 static void 303 unknown_host(int error, char *hostname) 304 { 305 if (error == TRY_AGAIN) 306 (void) fprintf(stderr, "%s: Unknown host (try again later).\n", 307 hostname); 308 else 309 (void) fprintf(stderr, "%s: Unknown host.\n", hostname); 310 } 311 312 static void 313 setpeer(int argc, char **argv) 314 { 315 struct hostent *host; 316 int error_num; 317 struct in6_addr ipv6addr; 318 struct in_addr ipv4addr; 319 char *hostnameinput; 320 const char *errstr; 321 322 if (argc < 2) { 323 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 324 (void) fprintf(stderr, "%s is too big\n", argv[0]); 325 return; 326 } 327 if (prompt_for_arg(line, sizeof (line), "to") == -1) 328 return; 329 makeargv(line, &argc, &argv); 330 } 331 if (argc > 3 || argc < 2) { 332 (void) fprintf(stderr, "usage: %s host-name [port]\n", 333 argv[0]); 334 return; 335 } 336 hostnameinput = removebrackets(argv[1]); 337 338 (void) memset(&sin6, 0, sizeof (sin6)); 339 sin6.sin6_family = AF_INET6; 340 host = getipnodebyname(hostnameinput, AF_INET6, 341 AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, &error_num); 342 if (host != NULL) { 343 (void) memcpy(&sin6.sin6_addr, host->h_addr_list[0], 344 host->h_length); 345 /* 346 * If host->h_name is a IPv4-mapped IPv6 literal, we'll convert 347 * it to IPv4 literal address. 348 */ 349 if ((inet_pton(AF_INET6, host->h_name, &ipv6addr) > 0) && 350 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) { 351 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr); 352 (void) inet_ntop(AF_INET, &ipv4addr, hostname, 353 sizeof (hostname)); 354 } else { 355 (void) strlcpy(hostname, host->h_name, 356 sizeof (hostname)); 357 } 358 freehostent(host); 359 } else { 360 /* Keeping with previous semantics */ 361 connected = 0; 362 unknown_host(error_num, hostnameinput); 363 return; 364 } 365 366 port = default_port; 367 if (argc == 3) { 368 port = strtonum(argv[2], 1, 65535, &errstr); 369 if (errstr != NULL) { 370 (void) fprintf(stderr, "%s: bad port number: %s\n", 371 argv[2], errstr); 372 connected = 0; 373 return; 374 } 375 port = htons(port); 376 } 377 connected = 1; 378 } 379 380 static void 381 modecmd(int argc, char **argv) 382 { 383 struct modes *p; 384 385 if (argc < 2) { 386 (void) fprintf(stderr, "Using %s mode to transfer files.\n", 387 mode); 388 return; 389 } 390 if (argc == 2) { 391 for (p = modes; p->m_name != NULL; p++) 392 if (strcmp(argv[1], p->m_name) == 0) { 393 setmode(p->m_mode); 394 return; 395 } 396 (void) fprintf(stderr, "%s: unknown mode\n", argv[1]); 397 /* drop through and print usage message */ 398 } 399 400 p = modes; 401 (void) fprintf(stderr, "usage: %s [ %s", argv[0], p->m_name); 402 for (p++; p->m_name != NULL; p++) 403 (void) fprintf(stderr, " | %s", p->m_name); 404 (void) puts(" ]"); 405 } 406 407 /*ARGSUSED*/ 408 static void 409 setbinary(int argc, char **argv) 410 { 411 setmode("octet"); 412 } 413 414 /*ARGSUSED*/ 415 static void 416 setascii(int argc, char **argv) 417 { 418 setmode("netascii"); 419 } 420 421 static void 422 setmode(char *newmode) 423 { 424 (void) strlcpy(mode, newmode, sizeof (mode)); 425 if (verbose) 426 (void) printf("mode set to %s\n", mode); 427 } 428 429 /* 430 * Send file(s). 431 */ 432 static void 433 put(int argc, char **argv) 434 { 435 int fd; 436 int n; 437 char *cp, *targ; 438 struct in6_addr ipv6addr; 439 struct in_addr ipv4addr; 440 char buf[PATH_MAX + 1], *argtail; 441 442 if (argc < 2) { 443 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 444 (void) fprintf(stderr, "%s is too big\n", argv[0]); 445 return; 446 } 447 if (prompt_for_arg(line, sizeof (line), "file") == -1) 448 return; 449 makeargv(line, &argc, &argv); 450 } 451 if (argc < 2) { 452 putusage(argv[0]); 453 return; 454 } 455 targ = argv[argc - 1]; 456 if (finddelimiter(argv[argc - 1])) { 457 char *cp; 458 struct hostent *hp; 459 int error_num; 460 461 for (n = 1; n < argc - 1; n++) 462 if (finddelimiter(argv[n])) { 463 putusage(argv[0]); 464 return; 465 } 466 cp = argv[argc - 1]; 467 targ = finddelimiter(cp); 468 *targ++ = 0; 469 cp = removebrackets(cp); 470 471 if ((hp = getipnodebyname(cp, 472 AF_INET6, AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, 473 &error_num)) == NULL) { 474 unknown_host(error_num, cp); 475 return; 476 } 477 (void) memcpy(&sin6.sin6_addr, hp->h_addr_list[0], 478 hp->h_length); 479 480 sin6.sin6_family = AF_INET6; 481 connected = 1; 482 /* 483 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll convert 484 * it to IPv4 literal address. 485 */ 486 if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) && 487 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) { 488 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr); 489 (void) inet_ntop(AF_INET, &ipv4addr, hostname, 490 sizeof (hostname)); 491 } else { 492 (void) strlcpy(hostname, hp->h_name, 493 sizeof (hostname)); 494 } 495 } 496 if (!connected) { 497 (void) fputs("No target machine specified.\n", stderr); 498 return; 499 } 500 if (argc < 4) { 501 cp = argc == 2 ? tail(targ) : argv[1]; 502 fd = open(cp, O_RDONLY); 503 if (fd < 0) { 504 (void) fprintf(stderr, "tftp: %s: %s\n", cp, 505 strerror(errno)); 506 return; 507 } 508 if (verbose) 509 (void) printf("putting %s to %s:%s [%s]\n", 510 cp, hostname, targ, mode); 511 sin6.sin6_port = port; 512 tftp_sendfile(fd, targ, mode); 513 return; 514 } 515 /* this assumes the target is a directory */ 516 /* on a remote unix system. hmmmm. */ 517 if (strlen(targ) + 1 >= sizeof (buf)) { 518 (void) fprintf(stderr, "tftp: filename too long: %s\n", targ); 519 return; 520 } 521 for (n = 1; n < argc - 1; n++) { 522 argtail = tail(argv[n]); 523 if (snprintf(buf, sizeof (buf), "%s/%s", targ, argtail) >= 524 sizeof (buf)) { 525 (void) fprintf(stderr, 526 "tftp: filename too long: %s/%s\n", targ, argtail); 527 continue; 528 } 529 fd = open(argv[n], O_RDONLY); 530 if (fd < 0) { 531 (void) fprintf(stderr, "tftp: %s: %s\n", argv[n], 532 strerror(errno)); 533 continue; 534 } 535 if (verbose) 536 (void) printf("putting %s to %s:%s [%s]\n", 537 argv[n], hostname, buf, mode); 538 sin6.sin6_port = port; 539 tftp_sendfile(fd, buf, mode); 540 } 541 } 542 543 static void 544 putusage(char *s) 545 { 546 (void) fprintf(stderr, "usage: %s file ... host:target, or\n" 547 " %s file ... target (when already connected)\n", s, s); 548 } 549 550 /* 551 * Receive file(s). 552 */ 553 static void 554 get(int argc, char **argv) 555 { 556 int fd; 557 int n; 558 char *cp; 559 char *src; 560 struct in6_addr ipv6addr; 561 struct in_addr ipv4addr; 562 int error_num; 563 564 if (argc < 2) { 565 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 566 (void) fprintf(stderr, "%s is too big\n", argv[0]); 567 return; 568 } 569 if (prompt_for_arg(line, sizeof (line), "files") == -1) 570 return; 571 makeargv(line, &argc, &argv); 572 } 573 if (argc < 2) { 574 getusage(argv[0]); 575 return; 576 } 577 if (!connected) { 578 for (n = 1; n < argc; n++) 579 if (finddelimiter(argv[n]) == 0) { 580 getusage(argv[0]); 581 return; 582 } 583 } 584 for (n = 1; n < argc; n++) { 585 src = finddelimiter(argv[n]); 586 if (src == NULL) 587 src = argv[n]; 588 else { 589 struct hostent *hp; 590 char *hostnameinput; 591 592 *src++ = 0; 593 hostnameinput = removebrackets(argv[n]); 594 595 if ((hp = getipnodebyname(hostnameinput, AF_INET6, 596 AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, 597 &error_num)) == NULL) { 598 unknown_host(error_num, hostnameinput); 599 continue; 600 } 601 (void) memcpy((caddr_t)&sin6.sin6_addr, 602 hp->h_addr_list[0], hp->h_length); 603 604 sin6.sin6_family = AF_INET6; 605 connected = 1; 606 /* 607 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll 608 * convert it to IPv4 literal address. 609 */ 610 if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) && 611 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) { 612 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr); 613 (void) inet_ntop(AF_INET, &ipv4addr, hostname, 614 sizeof (hostname)); 615 } else { 616 (void) strlcpy(hostname, hp->h_name, 617 sizeof (hostname)); 618 } 619 } 620 if (argc < 4) { 621 cp = argc == 3 ? argv[2] : tail(src); 622 fd = creat(cp, 0644); 623 if (fd < 0) { 624 (void) fprintf(stderr, "tftp: %s: %s\n", cp, 625 strerror(errno)); 626 return; 627 } 628 if (verbose) 629 (void) printf("getting from %s:%s to %s [%s]\n", 630 hostname, src, cp, mode); 631 sin6.sin6_port = port; 632 tftp_recvfile(fd, src, mode); 633 break; 634 } 635 cp = tail(src); /* new .. jdg */ 636 fd = creat(cp, 0644); 637 if (fd < 0) { 638 (void) fprintf(stderr, "tftp: %s: %s\n", cp, 639 strerror(errno)); 640 continue; 641 } 642 if (verbose) 643 (void) printf("getting from %s:%s to %s [%s]\n", 644 hostname, src, cp, mode); 645 sin6.sin6_port = port; 646 tftp_recvfile(fd, src, mode); 647 } 648 } 649 650 static void 651 getusage(char *s) 652 { 653 (void) fprintf(stderr, "usage: %s host:file host:file ... file, or\n" 654 " %s file file ... file if connected\n", s, s); 655 } 656 657 static void 658 setrexmt(int argc, char **argv) 659 { 660 int t; 661 const char *errstr; 662 663 if (argc < 2) { 664 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 665 (void) fprintf(stderr, "%s is too big\n", argv[0]); 666 return; 667 } 668 if (prompt_for_arg(line, sizeof (line), "value") == -1) 669 return; 670 makeargv(line, &argc, &argv); 671 } 672 if (argc != 2) { 673 (void) fprintf(stderr, "usage: %s value\n", argv[0]); 674 return; 675 } 676 677 t = strtonum(argv[1], 0, INT_MAX, &errstr); 678 if (errstr != NULL) 679 (void) fprintf(stderr, "%s: bad value: %s\n", argv[1], errstr); 680 else 681 rexmtval = t; 682 } 683 684 static void 685 settimeout(int argc, char **argv) 686 { 687 int t; 688 const char *errstr; 689 690 if (argc < 2) { 691 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 692 (void) fprintf(stderr, "%s is too big\n", argv[0]); 693 return; 694 } 695 if (prompt_for_arg(line, sizeof (line), "value") == -1) 696 return; 697 makeargv(line, &argc, &argv); 698 } 699 if (argc != 2) { 700 (void) fprintf(stderr, "usage: %s value\n", argv[0]); 701 return; 702 } 703 t = strtonum(argv[1], 0, INT_MAX, &errstr); 704 if (errstr != NULL) 705 (void) fprintf(stderr, "%s: bad value: %s\n", argv[1], errstr); 706 else 707 maxtimeout = t; 708 } 709 710 /*ARGSUSED*/ 711 static void 712 status(int argc, char **argv) 713 { 714 if (connected) 715 (void) printf("Connected to %s.\n", hostname); 716 else 717 (void) puts("Not connected."); 718 (void) printf("Mode: %s Verbose: %s Tracing: %s\n", mode, 719 verbose ? "on" : "off", trace ? "on" : "off"); 720 (void) printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", 721 rexmtval, maxtimeout); 722 (void) printf("Transfer blocksize option: "); 723 if (blksize == 0) 724 (void) puts("off"); 725 else 726 (void) printf("%d bytes\n", blksize); 727 (void) printf("Server rexmt-interval option: "); 728 if (srexmtval == 0) 729 (void) puts("off"); 730 else 731 (void) printf("%d seconds\n", srexmtval); 732 (void) printf("Transfer size option: %s\n", tsize_opt ? "on" : "off"); 733 } 734 735 /*ARGSUSED*/ 736 static void 737 intr(int signum) 738 { 739 (void) cancel_alarm(); 740 longjmp(toplevel, -1); 741 } 742 743 static char * 744 tail(char *filename) 745 { 746 char *s; 747 748 while (*filename != '\0') { 749 s = strrchr(filename, '/'); 750 if (s == NULL) 751 break; 752 if (s[1] != '\0') 753 return (&s[1]); 754 *s = '\0'; 755 } 756 return (filename); 757 } 758 759 /* 760 * Command parser. 761 */ 762 static void 763 command(int top) 764 { 765 struct cmd *c; 766 char *buf, **argv; 767 int argc; 768 769 if (!top) 770 (void) putchar('\n'); 771 for (;;) { 772 buf = gl_get_line(gl, prompt, NULL, -1); 773 if (buf == NULL) { 774 quit(0, NULL); 775 } 776 777 makeargv(buf, &argc, &argv); 778 c = getcmd(argv[0]); 779 if (c == AMBIGCMD) 780 (void) fputs("?Ambiguous command\n", stderr); 781 else if (c == NULL) 782 (void) fputs("?Invalid command\n", stderr); 783 else 784 (*c->handler)(argc, argv); 785 } 786 } 787 788 static struct cmd * 789 getcmd(char *name) 790 { 791 char *p, *q; 792 struct cmd *c, *found; 793 794 if (name == NULL) 795 return (NULL); 796 797 found = NULL; 798 for (c = cmdtab; (p = c->name) != NULL; c++) { 799 for (q = name; *q == *p++; q++) 800 if (*q == '\0') /* exact match? */ 801 return (c); 802 if (*q == '\0') /* the name was a prefix */ 803 found = (found == NULL) ? c : AMBIGCMD; 804 } 805 return (found); 806 } 807 808 /* 809 * Given a string, this function returns the pointer to the delimiting ':'. 810 * The string can contain an IPv6 literal address, which should be inside a 811 * pair of brackets, e.g. [1::2]. Any colons inside a pair of brackets are not 812 * accepted as delimiters. Returns NULL if delimiting ':' is not found. 813 */ 814 static char * 815 finddelimiter(char *str) 816 { 817 bool is_bracket_open = false; 818 char *cp; 819 820 for (cp = str; *cp != '\0'; cp++) { 821 if (*cp == '[') 822 is_bracket_open = true; 823 else if (*cp == ']') 824 is_bracket_open = false; 825 else if (*cp == ':' && !is_bracket_open) 826 return (cp); 827 } 828 return (NULL); 829 } 830 831 /* 832 * Given a string which is possibly surrounded by brackets, e.g. [1::2], this 833 * function returns a string after removing those brackets. If the brackets 834 * don't match, it does nothing. 835 */ 836 static char * 837 removebrackets(char *str) 838 { 839 char *newstr = str; 840 841 if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) { 842 newstr = str + 1; 843 str[strlen(str) - 1] = '\0'; 844 } 845 return (newstr); 846 } 847 848 #define MARGV_INC 20 849 850 /* 851 * Slice a string up into argc/argv. 852 */ 853 static void 854 makeargv(char *buf, int *argcp, char ***argvp) 855 { 856 char *cp; 857 char **argp; 858 int argc; 859 static char **argv; 860 static int argv_size; 861 862 if (argv == NULL) { 863 argv_size = MARGV_INC; 864 if ((argv = malloc(argv_size * sizeof (char *))) == NULL) { 865 perror("tftp: malloc"); 866 exit(1); 867 } 868 } 869 argc = 0; 870 argp = argv; 871 for (cp = buf; *cp != '\0'; ) { 872 while (isspace(*cp)) 873 cp++; 874 if (*cp == '\0') 875 break; 876 *argp++ = cp; 877 argc++; 878 if (argc == argv_size) { 879 argv_size += MARGV_INC; 880 if ((argv = realloc(argv, 881 argv_size * sizeof (char *))) == NULL) { 882 perror("tftp: realloc"); 883 exit(1); 884 } 885 argp = argv + argc; 886 } 887 while (*cp != '\0' && !isspace(*cp)) 888 cp++; 889 if (*cp == '\0') 890 break; 891 *cp++ = '\0'; 892 } 893 *argp = NULL; 894 895 *argcp = argc; 896 *argvp = argv; 897 } 898 899 /*ARGSUSED*/ 900 static void 901 quit(int argc, char **argv) 902 { 903 exit(0); 904 } 905 906 /* 907 * Help command. 908 */ 909 static void 910 help(int argc, char **argv) 911 { 912 struct cmd *c; 913 914 if (argc == 1) { 915 (void) puts("Commands may be abbreviated. Commands are:\n"); 916 for (c = cmdtab; c->name != NULL; c++) 917 (void) printf("%-*s\t%s\n", HELPINDENT, c->name, 918 c->help); 919 return; 920 } 921 while (--argc > 0) { 922 char *arg; 923 arg = *++argv; 924 c = getcmd(arg); 925 if (c == AMBIGCMD) 926 (void) fprintf(stderr, "?Ambiguous help command %s\n", 927 arg); 928 else if (c == NULL) 929 (void) fprintf(stderr, "?Invalid help command %s\n", 930 arg); 931 else 932 (void) fprintf(stderr, "%s\n", c->help); 933 } 934 } 935 936 /*ARGSUSED*/ 937 static void 938 settrace(int argc, char **argv) 939 { 940 trace = !trace; 941 (void) printf("Packet tracing %s.\n", trace ? "on" : "off"); 942 } 943 944 /*ARGSUSED*/ 945 static void 946 setverbose(int argc, char **argv) 947 { 948 verbose = !verbose; 949 (void) printf("Verbose mode %s.\n", verbose ? "on" : "off"); 950 } 951 952 static void 953 setblksize(int argc, char **argv) 954 { 955 int b; 956 const char *errstr; 957 958 if (argc < 2) { 959 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 960 (void) fprintf(stderr, "%s is too big\n", argv[0]); 961 return; 962 } 963 if (prompt_for_arg(line, sizeof (line), "value") == -1) 964 return; 965 makeargv(line, &argc, &argv); 966 } 967 if (argc != 2) { 968 (void) fprintf(stderr, "usage: %s value\n", argv[0]); 969 return; 970 } 971 972 /* RFC 2348 specifies valid blksize range, allow 0 to turn option off */ 973 errno = 0; 974 b = strtonum(argv[1], 0, MAX_BLKSIZE, &errstr); 975 if (errstr != NULL || (b > 0 && b < MIN_BLKSIZE)) 976 (void) fprintf(stderr, "%s: bad value: %s\n", argv[1], errstr); 977 else 978 blksize = b; 979 } 980 981 static void 982 setsrexmt(int argc, char **argv) 983 { 984 int t; 985 const char *errstr; 986 987 if (argc < 2) { 988 if (strlcat(line, argv[0], sizeof (line)) >= sizeof (line)) { 989 (void) fprintf(stderr, "%s is too big\n", argv[0]); 990 return; 991 } 992 if (prompt_for_arg(line, sizeof (line), "value") == -1) 993 return; 994 makeargv(line, &argc, &argv); 995 } 996 if (argc != 2) { 997 (void) fprintf(stderr, "usage: %s value\n", argv[0]); 998 return; 999 } 1000 1001 /* RFC 2349 specifies valid timeout range, allow 0 to turn option off */ 1002 t = strtonum(argv[1], 0, MAX_TIMEOUT, &errstr); 1003 if (errstr != NULL || (t > 0 && t < MIN_TIMEOUT)) 1004 (void) fprintf(stderr, "%s: bad value: %s\n", argv[1], errstr); 1005 else 1006 srexmtval = t; 1007 } 1008 1009 static void 1010 settsize(int argc, char **argv) 1011 { 1012 if (argc != 1) { 1013 (void) fprintf(stderr, "usage: %s\n", argv[0]); 1014 return; 1015 } 1016 tsize_opt = !tsize_opt; 1017 (void) printf("Transfer size option %s.\n", tsize_opt ? "on" : "off"); 1018 } 1019