xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/tftp/main.c (revision bc1f688b4872ace323eaddbb1a6365d054e7bf56)
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
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
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
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
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
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
344 setbinary(int argc, char **argv)
345 {
346 	setmode("octet");
347 }
348 
349 /*ARGSUSED*/
350 static void
351 setascii(int argc, char **argv)
352 {
353 	setmode("netascii");
354 }
355 
356 static void
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
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
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
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
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
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
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
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
653 intr(int signum)
654 {
655 	(void) cancel_alarm();
656 	longjmp(toplevel, -1);
657 }
658 
659 static char *
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
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 *
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 *
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 *
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
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
834 quit(int argc, char **argv)
835 {
836 	exit(0);
837 }
838 
839 /*
840  * Help command.
841  */
842 static void
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
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
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
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
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
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