Home | History | Annotate | Download | only in tftp
      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