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 #pragma ident "%Z%%M% %I% %E% SMI"
41
42 /*
43 * TFTP User Program -- Command Interface.
44 */
45 #include <sys/types.h>
46 #include <sys/socket.h>
47
48 #include <arpa/inet.h>
49
50 #include <signal.h>
51 #include <stdio.h>
52 #include <stdlib.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
60 #include "tftpcommon.h"
61 #include "tftpprivate.h"
62
63 #define NELEM(a) (sizeof (a) / sizeof ((a)[0]))
64
65 #define TIMEOUT 5 /* secs between rexmt's */
66
67 struct sockaddr_in6 sin6;
68 int f;
69 int maxtimeout = 5 * TIMEOUT;
70 int verbose;
71 int trace;
72 int srexmtval;
73 int blksize;
74 int rexmtval = TIMEOUT;
75 int tsize_opt;
76 jmp_buf toplevel;
77
78 static int default_port, port;
79 static int connected;
80 static char mode[32];
81 static char line[200];
82 static char *prompt = "tftp";
83 static char hostname[MAXHOSTNAMELEN];
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(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 { "?", hhelp, help },
158 { NULL }
159 };
160
161 #define AMBIGCMD (&cmdtab[NELEM(cmdtab)])
162
163 int
main(int argc,char ** argv)164 main(int argc, char **argv)
165 {
166 struct servent *sp;
167 struct sockaddr_in6 sin6;
168 int top;
169
170 sp = getservbyname("tftp", "udp");
171 default_port = (sp != NULL) ? sp->s_port : htons(IPPORT_TFTP);
172 port = default_port;
173
174 f = socket(AF_INET6, SOCK_DGRAM, 0);
175 if (f < 0) {
176 perror("tftp: socket");
177 exit(3);
178 }
179
180 (void) memset(&sin6, 0, sizeof (sin6));
181 sin6.sin6_family = AF_INET6;
182 if (bind(f, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) {
183 perror("tftp: bind");
184 exit(1);
185 }
186
187 (void) strlcpy(mode, "netascii", sizeof (mode));
188 (void) signal(SIGINT, intr);
189 if (argc > 1) {
190 if (setjmp(toplevel) != 0)
191 exit(0);
192 setpeer(argc, argv);
193 }
194
195 top = (setjmp(toplevel) == 0);
196 for (;;)
197 command(top);
198
199 /*NOTREACHED*/
200 return (0);
201 }
202
203 /* Prompt for command argument, add to buffer with space separator */
204 static int
prompt_for_arg(char * buffer,int buffer_size,char * prompt)205 prompt_for_arg(char *buffer, int buffer_size, char *prompt)
206 {
207 int ch;
208
209 if (strlcat(buffer, " ", buffer_size) >= buffer_size) {
210 (void) fputs("?Line too long\n", stderr);
211 return (-1);
212 }
213 (void) printf("(%s) ", prompt);
214 if (fgets(buffer + strlen(buffer), buffer_size - strlen(buffer),
215 stdin) == NULL) {
216 return (-1);
217 }
218 /* Flush what didn't fit in the buffer */
219 if (buffer[strlen(buffer)-1] != '\n') {
220 while (((ch = getchar()) != EOF) && (ch != '\n'))
221 ;
222 (void) fputs("?Line too long\n", stderr);
223 return (-1);
224 } else {
225 buffer[strlen(buffer)-1] = '\0';
226 }
227 return (0);
228 }
229
230 static void
unknown_host(int error,char * hostname)231 unknown_host(int error, char *hostname)
232 {
233 if (error == TRY_AGAIN)
234 (void) fprintf(stderr, "%s: Unknown host (try again later).\n",
235 hostname);
236 else
237 (void) fprintf(stderr, "%s: Unknown host.\n", hostname);
238 }
239
240 static void
setpeer(int argc,char ** argv)241 setpeer(int argc, char **argv)
242 {
243 struct hostent *host;
244 int error_num;
245 struct in6_addr ipv6addr;
246 struct in_addr ipv4addr;
247 char *hostnameinput;
248
249 if (argc < 2) {
250 if (prompt_for_arg(line, sizeof (line), "to") == -1)
251 return;
252 makeargv(&argc, &argv);
253 }
254 if (argc > 3 || argc < 2) {
255 (void) fprintf(stderr, "usage: %s host-name [port]\n",
256 argv[0]);
257 return;
258 }
259 hostnameinput = removebrackets(argv[1]);
260
261 (void) memset(&sin6, 0, sizeof (sin6));
262 sin6.sin6_family = AF_INET6;
263 if (host = getipnodebyname(hostnameinput, AF_INET6,
264 AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, &error_num)) {
265 (void) memcpy(&sin6.sin6_addr, host->h_addr_list[0],
266 host->h_length);
267 /*
268 * If host->h_name is a IPv4-mapped IPv6 literal, we'll convert
269 * it to IPv4 literal address.
270 */
271 if ((inet_pton(AF_INET6, host->h_name, &ipv6addr) > 0) &&
272 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
273 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
274 (void) inet_ntop(AF_INET, &ipv4addr, hostname,
275 sizeof (hostname));
276 } else {
277 (void) strlcpy(hostname, host->h_name,
278 sizeof (hostname));
279 }
280 freehostent(host);
281 } else {
282 /* Keeping with previous semantics */
283 connected = 0;
284 unknown_host(error_num, hostnameinput);
285 return;
286 }
287
288 port = default_port;
289 if (argc == 3) {
290 port = atoi(argv[2]);
291 if ((port < 1) || (port > 65535)) {
292 (void) fprintf(stderr, "%s: bad port number\n",
293 argv[2]);
294 connected = 0;
295 return;
296 }
297 port = htons(port);
298 }
299 connected = 1;
300 }
301
302 static struct modes {
303 char *m_name;
304 char *m_mode;
305 } modes[] = {
306 { "ascii", "netascii" },
307 { "netascii", "netascii" },
308 { "binary", "octet" },
309 { "image", "octet" },
310 { "octet", "octet" },
311 /* { "mail", "mail" }, */
312 { 0, 0 }
313 };
314
315 static void
modecmd(int argc,char ** argv)316 modecmd(int argc, char **argv)
317 {
318 struct modes *p;
319
320 if (argc < 2) {
321 (void) fprintf(stderr, "Using %s mode to transfer files.\n",
322 mode);
323 return;
324 }
325 if (argc == 2) {
326 for (p = modes; p->m_name != NULL; p++)
327 if (strcmp(argv[1], p->m_name) == 0) {
328 setmode(p->m_mode);
329 return;
330 }
331 (void) fprintf(stderr, "%s: unknown mode\n", argv[1]);
332 /* drop through and print usage message */
333 }
334
335 p = modes;
336 (void) fprintf(stderr, "usage: %s [ %s", argv[0], p->m_name);
337 for (p++; p->m_name != NULL; p++)
338 (void) fprintf(stderr, " | %s", p->m_name);
339 (void) puts(" ]");
340 }
341
342 /*ARGSUSED*/
343 static void
setbinary(int argc,char ** argv)344 setbinary(int argc, char **argv)
345 {
346 setmode("octet");
347 }
348
349 /*ARGSUSED*/
350 static void
setascii(int argc,char ** argv)351 setascii(int argc, char **argv)
352 {
353 setmode("netascii");
354 }
355
356 static void
setmode(char * newmode)357 setmode(char *newmode)
358 {
359 (void) strlcpy(mode, newmode, sizeof (mode));
360 if (verbose)
361 (void) printf("mode set to %s\n", mode);
362 }
363
364 /*
365 * Send file(s).
366 */
367 static void
put(int argc,char ** argv)368 put(int argc, char **argv)
369 {
370 int fd;
371 int n;
372 char *cp, *targ;
373 struct in6_addr ipv6addr;
374 struct in_addr ipv4addr;
375 char buf[PATH_MAX + 1], *argtail;
376
377 if (argc < 2) {
378 if (prompt_for_arg(line, sizeof (line), "file") == -1)
379 return;
380 makeargv(&argc, &argv);
381 }
382 if (argc < 2) {
383 putusage(argv[0]);
384 return;
385 }
386 targ = argv[argc - 1];
387 if (finddelimiter(argv[argc - 1])) {
388 char *cp;
389 struct hostent *hp;
390 int error_num;
391
392 for (n = 1; n < argc - 1; n++)
393 if (finddelimiter(argv[n])) {
394 putusage(argv[0]);
395 return;
396 }
397 cp = argv[argc - 1];
398 targ = finddelimiter(cp);
399 *targ++ = 0;
400 cp = removebrackets(cp);
401
402 if ((hp = getipnodebyname(cp,
403 AF_INET6, AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED,
404 &error_num)) == NULL) {
405 unknown_host(error_num, cp);
406 return;
407 }
408 (void) memcpy(&sin6.sin6_addr, hp->h_addr_list[0],
409 hp->h_length);
410
411 sin6.sin6_family = AF_INET6;
412 connected = 1;
413 /*
414 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll convert
415 * it to IPv4 literal address.
416 */
417 if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) &&
418 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
419 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
420 (void) inet_ntop(AF_INET, &ipv4addr, hostname,
421 sizeof (hostname));
422 } else {
423 (void) strlcpy(hostname, hp->h_name,
424 sizeof (hostname));
425 }
426 }
427 if (!connected) {
428 (void) fputs("No target machine specified.\n", stderr);
429 return;
430 }
431 if (argc < 4) {
432 cp = argc == 2 ? tail(targ) : argv[1];
433 fd = open(cp, O_RDONLY);
434 if (fd < 0) {
435 (void) fprintf(stderr, "tftp: %s: %s\n", cp,
436 strerror(errno));
437 return;
438 }
439 if (verbose)
440 (void) printf("putting %s to %s:%s [%s]\n",
441 cp, hostname, targ, mode);
442 sin6.sin6_port = port;
443 tftp_sendfile(fd, targ, mode);
444 return;
445 }
446 /* this assumes the target is a directory */
447 /* on a remote unix system. hmmmm. */
448 if (strlen(targ) + 1 >= sizeof (buf)) {
449 (void) fprintf(stderr, "tftp: filename too long: %s\n", targ);
450 return;
451 }
452 for (n = 1; n < argc - 1; n++) {
453 argtail = tail(argv[n]);
454 if (snprintf(buf, sizeof (buf), "%s/%s", targ, argtail) >=
455 sizeof (buf)) {
456 (void) fprintf(stderr,
457 "tftp: filename too long: %s/%s\n", targ, argtail);
458 continue;
459 }
460 fd = open(argv[n], O_RDONLY);
461 if (fd < 0) {
462 (void) fprintf(stderr, "tftp: %s: %s\n", argv[n],
463 strerror(errno));
464 continue;
465 }
466 if (verbose)
467 (void) printf("putting %s to %s:%s [%s]\n",
468 argv[n], hostname, buf, mode);
469 sin6.sin6_port = port;
470 tftp_sendfile(fd, buf, mode);
471 }
472 }
473
474 static void
putusage(char * s)475 putusage(char *s)
476 {
477 (void) fprintf(stderr, "usage: %s file ... host:target, or\n"
478 " %s file ... target (when already connected)\n", s, s);
479 }
480
481 /*
482 * Receive file(s).
483 */
484 static void
get(int argc,char ** argv)485 get(int argc, char **argv)
486 {
487 int fd;
488 int n;
489 char *cp;
490 char *src;
491 struct in6_addr ipv6addr;
492 struct in_addr ipv4addr;
493 int error_num;
494
495 if (argc < 2) {
496 if (prompt_for_arg(line, sizeof (line), "files") == -1)
497 return;
498 makeargv(&argc, &argv);
499 }
500 if (argc < 2) {
501 getusage(argv[0]);
502 return;
503 }
504 if (!connected) {
505 for (n = 1; n < argc; n++)
506 if (finddelimiter(argv[n]) == 0) {
507 getusage(argv[0]);
508 return;
509 }
510 }
511 for (n = 1; n < argc; n++) {
512 src = finddelimiter(argv[n]);
513 if (src == NULL)
514 src = argv[n];
515 else {
516 struct hostent *hp;
517 char *hostnameinput;
518
519 *src++ = 0;
520 hostnameinput = removebrackets(argv[n]);
521
522 if ((hp = getipnodebyname(hostnameinput, AF_INET6,
523 AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED,
524 &error_num)) == NULL) {
525 unknown_host(error_num, hostnameinput);
526 continue;
527 }
528 (void) memcpy((caddr_t)&sin6.sin6_addr,
529 hp->h_addr_list[0], hp->h_length);
530
531 sin6.sin6_family = AF_INET6;
532 connected = 1;
533 /*
534 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll
535 * convert it to IPv4 literal address.
536 */
537 if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) &&
538 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
539 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
540 (void) inet_ntop(AF_INET, &ipv4addr, hostname,
541 sizeof (hostname));
542 } else {
543 (void) strlcpy(hostname, hp->h_name,
544 sizeof (hostname));
545 }
546 }
547 if (argc < 4) {
548 cp = argc == 3 ? argv[2] : tail(src);
549 fd = creat(cp, 0644);
550 if (fd < 0) {
551 (void) fprintf(stderr, "tftp: %s: %s\n", cp,
552 strerror(errno));
553 return;
554 }
555 if (verbose)
556 (void) printf("getting from %s:%s to %s [%s]\n",
557 hostname, src, cp, mode);
558 sin6.sin6_port = port;
559 tftp_recvfile(fd, src, mode);
560 break;
561 }
562 cp = tail(src); /* new .. jdg */
563 fd = creat(cp, 0644);
564 if (fd < 0) {
565 (void) fprintf(stderr, "tftp: %s: %s\n", cp,
566 strerror(errno));
567 continue;
568 }
569 if (verbose)
570 (void) printf("getting from %s:%s to %s [%s]\n",
571 hostname, src, cp, mode);
572 sin6.sin6_port = port;
573 tftp_recvfile(fd, src, mode);
574 }
575 }
576
577 static void
getusage(char * s)578 getusage(char *s)
579 {
580 (void) fprintf(stderr, "usage: %s host:file host:file ... file, or\n"
581 " %s file file ... file if connected\n", s, s);
582 }
583
584 static void
setrexmt(int argc,char ** argv)585 setrexmt(int argc, char **argv)
586 {
587 int t;
588
589 if (argc < 2) {
590 if (prompt_for_arg(line, sizeof (line), "value") == -1)
591 return;
592 makeargv(&argc, &argv);
593 }
594 if (argc != 2) {
595 (void) fprintf(stderr, "usage: %s value\n", argv[0]);
596 return;
597 }
598 t = atoi(argv[1]);
599 if (t < 0)
600 (void) fprintf(stderr, "%s: bad value\n", argv[1]);
601 else
602 rexmtval = t;
603 }
604
605 static void
settimeout(int argc,char ** argv)606 settimeout(int argc, char **argv)
607 {
608 int t;
609
610 if (argc < 2) {
611 if (prompt_for_arg(line, sizeof (line), "value") == -1)
612 return;
613 makeargv(&argc, &argv);
614 }
615 if (argc != 2) {
616 (void) fprintf(stderr, "usage: %s value\n", argv[0]);
617 return;
618 }
619 t = atoi(argv[1]);
620 if (t < 0)
621 (void) fprintf(stderr, "%s: bad value\n", argv[1]);
622 else
623 maxtimeout = t;
624 }
625
626 /*ARGSUSED*/
627 static void
status(int argc,char ** argv)628 status(int argc, char **argv)
629 {
630 if (connected)
631 (void) printf("Connected to %s.\n", hostname);
632 else
633 (void) puts("Not connected.");
634 (void) printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
635 verbose ? "on" : "off", trace ? "on" : "off");
636 (void) printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
637 rexmtval, maxtimeout);
638 (void) printf("Transfer blocksize option: ");
639 if (blksize == 0)
640 (void) puts("off");
641 else
642 (void) printf("%d bytes\n", blksize);
643 (void) printf("Server rexmt-interval option: ");
644 if (srexmtval == 0)
645 (void) puts("off");
646 else
647 (void) printf("%d seconds\n", srexmtval);
648 (void) printf("Transfer size option: %s\n", tsize_opt ? "on" : "off");
649 }
650
651 /*ARGSUSED*/
652 static void
intr(int signum)653 intr(int signum)
654 {
655 (void) cancel_alarm();
656 longjmp(toplevel, -1);
657 }
658
659 static char *
tail(char * filename)660 tail(char *filename)
661 {
662 char *s;
663
664 while (*filename != '\0') {
665 s = strrchr(filename, '/');
666 if (s == NULL)
667 break;
668 if (s[1] != '\0')
669 return (&s[1]);
670 *s = '\0';
671 }
672 return (filename);
673 }
674
675 /*
676 * Command parser.
677 */
678 static void
command(int top)679 command(int top)
680 {
681 struct cmd *c;
682 int ch;
683
684 if (!top)
685 (void) putchar('\n');
686 for (;;) {
687 (void) printf("%s> ", prompt);
688 if (fgets(line, sizeof (line), stdin) == NULL) {
689 if (feof(stdin))
690 quit(0, NULL);
691 else
692 continue;
693 }
694
695 /* Flush what didn't fit in the buffer */
696 if (line[strlen(line)-1] != '\n') {
697 while (((ch = getchar()) != EOF) && (ch != '\n'))
698 ;
699 (void) fputs("?Line too long\n", stderr);
700 } else {
701 line[strlen(line)-1] = '\0';
702 if (line[0] != '\0') {
703 int argc;
704 char **argv;
705
706 makeargv(&argc, &argv);
707 c = getcmd(argv[0]);
708 if (c == AMBIGCMD)
709 (void) fputs("?Ambiguous command\n",
710 stderr);
711 else if (c == NULL)
712 (void) fputs("?Invalid command\n",
713 stderr);
714 else
715 (*c->handler)(argc, argv);
716 }
717 }
718 }
719 }
720
721 static struct cmd *
getcmd(char * name)722 getcmd(char *name)
723 {
724 char *p, *q;
725 struct cmd *c, *found;
726
727 if (name == NULL)
728 return (NULL);
729
730 found = NULL;
731 for (c = cmdtab; (p = c->name) != NULL; c++) {
732 for (q = name; *q == *p++; q++)
733 if (*q == '\0') /* exact match? */
734 return (c);
735 if (*q == '\0') /* the name was a prefix */
736 found = (found == NULL) ? c : AMBIGCMD;
737 }
738 return (found);
739 }
740
741 /*
742 * Given a string, this function returns the pointer to the delimiting ':'.
743 * The string can contain an IPv6 literal address, which should be inside a
744 * pair of brackets, e.g. [1::2]. Any colons inside a pair of brackets are not
745 * accepted as delimiters. Returns NULL if delimiting ':' is not found.
746 */
747 static char *
finddelimiter(char * str)748 finddelimiter(char *str)
749 {
750 boolean_t is_bracket_open = B_FALSE;
751 char *cp;
752
753 for (cp = str; *cp != '\0'; cp++) {
754 if (*cp == '[')
755 is_bracket_open = B_TRUE;
756 else if (*cp == ']')
757 is_bracket_open = B_FALSE;
758 else if (*cp == ':' && !is_bracket_open)
759 return (cp);
760 }
761 return (NULL);
762 }
763
764 /*
765 * Given a string which is possibly surrounded by brackets, e.g. [1::2], this
766 * function returns a string after removing those brackets. If the brackets
767 * don't match, it does nothing.
768 */
769 static char *
removebrackets(char * str)770 removebrackets(char *str)
771 {
772 char *newstr = str;
773
774 if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) {
775 newstr = str + 1;
776 str[strlen(str) - 1] = '\0';
777 }
778 return (newstr);
779 }
780
781 #define MARGV_INC 20
782
783 /*
784 * Slice a string up into argc/argv.
785 */
786 static void
makeargv(int * argcp,char *** argvp)787 makeargv(int *argcp, char ***argvp)
788 {
789 char *cp;
790 char **argp;
791 int argc;
792 static char **argv;
793 static int argv_size;
794
795 if (argv == NULL) {
796 argv_size = MARGV_INC;
797 if ((argv = malloc(argv_size * sizeof (char *))) == NULL) {
798 perror("tftp: malloc");
799 exit(1);
800 }
801 }
802 argc = 0;
803 argp = argv;
804 for (cp = line; *cp != '\0'; ) {
805 while (isspace(*cp))
806 cp++;
807 if (*cp == '\0')
808 break;
809 *argp++ = cp;
810 argc++;
811 if (argc == argv_size) {
812 argv_size += MARGV_INC;
813 if ((argv = realloc(argv,
814 argv_size * sizeof (char *))) == NULL) {
815 perror("tftp: realloc");
816 exit(1);
817 }
818 argp = argv + argc;
819 }
820 while (*cp != '\0' && !isspace(*cp))
821 cp++;
822 if (*cp == '\0')
823 break;
824 *cp++ = '\0';
825 }
826 *argp = NULL;
827
828 *argcp = argc;
829 *argvp = argv;
830 }
831
832 /*ARGSUSED*/
833 static void
quit(int argc,char ** argv)834 quit(int argc, char **argv)
835 {
836 exit(0);
837 }
838
839 /*
840 * Help command.
841 */
842 static void
help(int argc,char ** argv)843 help(int argc, char **argv)
844 {
845 struct cmd *c;
846
847 if (argc == 1) {
848 (void) puts("Commands may be abbreviated. Commands are:\n");
849 for (c = cmdtab; c->name != NULL; c++)
850 (void) printf("%-*s\t%s\n", HELPINDENT, c->name,
851 c->help);
852 return;
853 }
854 while (--argc > 0) {
855 char *arg;
856 arg = *++argv;
857 c = getcmd(arg);
858 if (c == AMBIGCMD)
859 (void) fprintf(stderr, "?Ambiguous help command %s\n",
860 arg);
861 else if (c == NULL)
862 (void) fprintf(stderr, "?Invalid help command %s\n",
863 arg);
864 else
865 (void) fprintf(stderr, "%s\n", c->help);
866 }
867 }
868
869 /*ARGSUSED*/
870 static void
settrace(int argc,char ** argv)871 settrace(int argc, char **argv)
872 {
873 trace = !trace;
874 (void) printf("Packet tracing %s.\n", trace ? "on" : "off");
875 }
876
877 /*ARGSUSED*/
878 static void
setverbose(int argc,char ** argv)879 setverbose(int argc, char **argv)
880 {
881 verbose = !verbose;
882 (void) printf("Verbose mode %s.\n", verbose ? "on" : "off");
883 }
884
885 static void
setblksize(int argc,char ** argv)886 setblksize(int argc, char **argv)
887 {
888 int b;
889
890 if (argc < 2) {
891 if (prompt_for_arg(line, sizeof (line), "value") == -1)
892 return;
893 makeargv(&argc, &argv);
894 }
895 if (argc != 2) {
896 (void) fprintf(stderr, "usage: %s value\n", argv[0]);
897 return;
898 }
899 b = atoi(argv[1]);
900
901 /* RFC 2348 specifies valid blksize range, allow 0 to turn option off */
902 if ((b < MIN_BLKSIZE || b > MAX_BLKSIZE) && b != 0)
903 (void) fprintf(stderr, "%s: bad value\n", argv[1]);
904 else
905 blksize = b;
906 }
907
908 static void
setsrexmt(int argc,char ** argv)909 setsrexmt(int argc, char **argv)
910 {
911 int t;
912
913 if (argc < 2) {
914 if (prompt_for_arg(line, sizeof (line), "value") == -1)
915 return;
916 makeargv(&argc, &argv);
917 }
918 if (argc != 2) {
919 (void) fprintf(stderr, "usage: %s value\n", argv[0]);
920 return;
921 }
922 t = atoi(argv[1]);
923
924 /* RFC 2349 specifies valid timeout range, allow 0 to turn option off */
925 if ((t < MIN_TIMEOUT || t > MAX_TIMEOUT) && t != 0)
926 (void) fprintf(stderr, "%s: bad value\n", argv[1]);
927 else
928 srexmtval = t;
929 }
930
931 static void
settsize(int argc,char ** argv)932 settsize(int argc, char **argv)
933 {
934 if (argc != 1) {
935 (void) fprintf(stderr, "usage: %s\n", argv[0]);
936 return;
937 }
938 tsize_opt = !tsize_opt;
939 (void) printf("Transfer size option %s.\n", tsize_opt ? "on" : "off");
940 }
941