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
cmdmatch(WordCompletion * cpl,void * data,const char * line,int word_end)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
main(int argc,char ** argv)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
prompt_for_arg(char * buffer,int buffer_size,char * prompt)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
unknown_host(int error,char * hostname)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
setpeer(int argc,char ** argv)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
modecmd(int argc,char ** argv)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
setbinary(int argc,char ** argv)409 setbinary(int argc, char **argv)
410 {
411 setmode("octet");
412 }
413
414 /*ARGSUSED*/
415 static void
setascii(int argc,char ** argv)416 setascii(int argc, char **argv)
417 {
418 setmode("netascii");
419 }
420
421 static void
setmode(char * newmode)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
put(int argc,char ** argv)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
putusage(char * s)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
get(int argc,char ** argv)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
getusage(char * s)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
setrexmt(int argc,char ** argv)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
settimeout(int argc,char ** argv)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
status(int argc,char ** argv)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
intr(int signum)737 intr(int signum)
738 {
739 (void) cancel_alarm();
740 longjmp(toplevel, -1);
741 }
742
743 static char *
tail(char * filename)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
command(int top)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 *
getcmd(char * name)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 *
finddelimiter(char * str)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 *
removebrackets(char * str)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
makeargv(char * buf,int * argcp,char *** argvp)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
quit(int argc,char ** argv)901 quit(int argc, char **argv)
902 {
903 exit(0);
904 }
905
906 /*
907 * Help command.
908 */
909 static void
help(int argc,char ** argv)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
settrace(int argc,char ** argv)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
setverbose(int argc,char ** argv)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
setblksize(int argc,char ** argv)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
setsrexmt(int argc,char ** argv)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
settsize(int argc,char ** argv)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