xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/tftp/main.c (revision ed093b41a93e8563e6e1e5dae0768dda2a7bcc27)
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