/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * TFTP User Program -- Command Interface.
 */
#include <sys/types.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>

#include "tftpcommon.h"
#include "tftpprivate.h"

#define	NELEM(a)	(sizeof (a) / sizeof ((a)[0]))

#define	TIMEOUT		5		/* secs between rexmt's */

struct sockaddr_in6	sin6;
int			f;
int			maxtimeout = 5 * TIMEOUT;
int			verbose;
int			trace;
int			srexmtval;
int			blksize;
int			rexmtval = TIMEOUT;
int			tsize_opt;
jmp_buf			toplevel;

static int			default_port, port;
static int			connected;
static char			mode[32];
static char			line[200];
static char			*prompt = "tftp";
static char			hostname[MAXHOSTNAMELEN];

static void		intr(int);
static void		quit(int, char **);
static void		help(int, char **);
static void		setverbose(int, char **);
static void		settrace(int, char **);
static void		status(int, char **);
static void		get(int, char **);
static void		put(int, char **);
static void		setpeer(int, char **);
static void		modecmd(int, char **);
static void		setrexmt(int, char **);
static void		settimeout(int, char **);
static void		setbinary(int, char **);
static void		setascii(int, char **);
static void		setblksize(int, char **);
static void		setsrexmt(int, char **);
static void		settsize(int, char **);
static void		setmode(char *);
static void		putusage(char *);
static void		getusage(char *);
static char		*finddelimiter(char *);
static char		*removebrackets(char *);
static int		prompt_for_arg(char *, int, char *);
static struct cmd	*getcmd(char *);
static char		*tail(char *);
static void		command(int);
static void		makeargv(int *, char ***);

#define	HELPINDENT (sizeof ("connect"))

struct cmd {
	char	*name;
	char	*help;
	void	(*handler)(int, char **);
};

static char	vhelp[] =	"toggle verbose mode";
static char	thelp[] =	"toggle packet tracing";
static char	chelp[] =	"connect to remote tftp";
static char	qhelp[] =	"exit tftp";
static char	hhelp[] =	"print help information";
static char	shelp[] =	"send file";
static char	rhelp[] =	"receive file";
static char	mhelp[] =	"set file transfer mode";
static char	sthelp[] =	"show current status";
static char	xhelp[] =	"set per-packet retransmission timeout";
static char	ihelp[] =	"set total retransmission timeout";
static char	ashelp[] =	"set mode to netascii";
static char	bnhelp[] =	"set mode to octet";
static char	bshelp[] =	"set transfer blocksize to negotiate with the "
				"server";
static char	srhelp[] =	"set preferred per-packet retransmission "
				"timeout for server";
static char	tshelp[] =	"toggle sending the transfer size option to "
				"the server";

static struct cmd	cmdtab[] = {
	{ "connect",	chelp,		setpeer },
	{ "mode",	mhelp,		modecmd },
	{ "put",	shelp,		put },
	{ "get",	rhelp,		get },
	{ "quit",	qhelp,		quit },
	{ "verbose",	vhelp,		setverbose },
	{ "trace",	thelp,		settrace },
	{ "status",	sthelp,		status },
	{ "binary",	bnhelp,		setbinary },
	{ "ascii",	ashelp,		setascii },
	{ "rexmt",	xhelp,		setrexmt },
	{ "timeout",	ihelp,		settimeout },
	{ "blksize",	bshelp,		setblksize },
	{ "srexmt",	srhelp,		setsrexmt },
	{ "tsize",	tshelp,		settsize },
	{ "?",		hhelp,		help },
	{ NULL }
};

#define	AMBIGCMD	(&cmdtab[NELEM(cmdtab)])

int
main(int argc, char **argv)
{
	struct servent *sp;
	struct sockaddr_in6 sin6;
	int top;

	sp = getservbyname("tftp", "udp");
	default_port = (sp != NULL) ? sp->s_port : htons(IPPORT_TFTP);
	port = default_port;

	f = socket(AF_INET6, SOCK_DGRAM, 0);
	if (f < 0) {
		perror("tftp: socket");
		exit(3);
	}

	(void) memset(&sin6, 0, sizeof (sin6));
	sin6.sin6_family = AF_INET6;
	if (bind(f, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) {
		perror("tftp: bind");
		exit(1);
	}

	(void) strlcpy(mode, "netascii", sizeof (mode));
	(void) signal(SIGINT, intr);
	if (argc > 1) {
		if (setjmp(toplevel) != 0)
			exit(0);
		setpeer(argc, argv);
	}

	top = (setjmp(toplevel) == 0);
	for (;;)
		command(top);

	/*NOTREACHED*/
	return (0);
}

/* Prompt for command argument, add to buffer with space separator */
static int
prompt_for_arg(char *buffer, int buffer_size, char *prompt)
{
	int ch;

	if (strlcat(buffer, " ", buffer_size) >= buffer_size) {
		(void) fputs("?Line too long\n", stderr);
		return (-1);
	}
	(void) printf("(%s) ", prompt);
	if (fgets(buffer + strlen(buffer), buffer_size - strlen(buffer),
		stdin) == NULL) {
		return (-1);
	}
	/* Flush what didn't fit in the buffer */
	if (buffer[strlen(buffer)-1] != '\n') {
		while (((ch = getchar()) != EOF) && (ch != '\n'))
			;
		(void) fputs("?Line too long\n", stderr);
		return (-1);
	} else {
		buffer[strlen(buffer)-1] = '\0';
	}
	return (0);
}

static void
unknown_host(int error, char *hostname)
{
	if (error == TRY_AGAIN)
		(void) fprintf(stderr, "%s: Unknown host (try again later).\n",
		    hostname);
	else
		(void) fprintf(stderr, "%s: Unknown host.\n", hostname);
}

static void
setpeer(int argc, char **argv)
{
	struct hostent *host;
	int error_num;
	struct in6_addr ipv6addr;
	struct in_addr ipv4addr;
	char *hostnameinput;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "to") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc > 3 || argc < 2) {
		(void) fprintf(stderr, "usage: %s host-name [port]\n",
		    argv[0]);
		return;
	}
	hostnameinput = removebrackets(argv[1]);

	(void) memset(&sin6, 0, sizeof (sin6));
	sin6.sin6_family = AF_INET6;
	if (host = getipnodebyname(hostnameinput, AF_INET6,
	    AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, &error_num)) {
		(void) memcpy(&sin6.sin6_addr, host->h_addr_list[0],
		    host->h_length);
		/*
		 * If host->h_name is a IPv4-mapped IPv6 literal, we'll convert
		 * it to IPv4 literal address.
		 */
		if ((inet_pton(AF_INET6, host->h_name, &ipv6addr) > 0) &&
		    IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
			IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
			(void) inet_ntop(AF_INET, &ipv4addr, hostname,
			    sizeof (hostname));
		} else {
			(void) strlcpy(hostname, host->h_name,
			    sizeof (hostname));
		}
		freehostent(host);
	} else {
		/* Keeping with previous semantics */
		connected = 0;
		unknown_host(error_num, hostnameinput);
		return;
	}

	port = default_port;
	if (argc == 3) {
		port = atoi(argv[2]);
		if ((port < 1) || (port > 65535)) {
			(void) fprintf(stderr, "%s: bad port number\n",
			    argv[2]);
			connected = 0;
			return;
		}
		port = htons(port);
	}
	connected = 1;
}

static struct modes {
	char *m_name;
	char *m_mode;
} modes[] = {
	{ "ascii",	"netascii" },
	{ "netascii",   "netascii" },
	{ "binary",     "octet" },
	{ "image",      "octet" },
	{ "octet",     "octet" },
/*      { "mail",       "mail" },       */
	{ 0,		0 }
};

static void
modecmd(int argc, char **argv)
{
	struct modes *p;

	if (argc < 2) {
		(void) fprintf(stderr, "Using %s mode to transfer files.\n",
		    mode);
		return;
	}
	if (argc == 2) {
		for (p = modes; p->m_name != NULL; p++)
			if (strcmp(argv[1], p->m_name) == 0) {
				setmode(p->m_mode);
				return;
			}
		(void) fprintf(stderr, "%s: unknown mode\n", argv[1]);
		/* drop through and print usage message */
	}

	p = modes;
	(void) fprintf(stderr, "usage: %s [ %s", argv[0], p->m_name);
	for (p++; p->m_name != NULL; p++)
		(void) fprintf(stderr, " | %s", p->m_name);
	(void) puts(" ]");
}

/*ARGSUSED*/
static void
setbinary(int argc, char **argv)
{
	setmode("octet");
}

/*ARGSUSED*/
static void
setascii(int argc, char **argv)
{
	setmode("netascii");
}

static void
setmode(char *newmode)
{
	(void) strlcpy(mode, newmode, sizeof (mode));
	if (verbose)
		(void) printf("mode set to %s\n", mode);
}

/*
 * Send file(s).
 */
static void
put(int argc, char **argv)
{
	int fd;
	int n;
	char *cp, *targ;
	struct in6_addr	ipv6addr;
	struct in_addr ipv4addr;
	char buf[PATH_MAX + 1], *argtail;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "file") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc < 2) {
		putusage(argv[0]);
		return;
	}
	targ = argv[argc - 1];
	if (finddelimiter(argv[argc - 1])) {
		char *cp;
		struct hostent *hp;
		int error_num;

		for (n = 1; n < argc - 1; n++)
			if (finddelimiter(argv[n])) {
				putusage(argv[0]);
				return;
			}
		cp = argv[argc - 1];
		targ = finddelimiter(cp);
		*targ++ = 0;
		cp = removebrackets(cp);

		if ((hp = getipnodebyname(cp,
		    AF_INET6, AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED,
		    &error_num)) == NULL) {
			unknown_host(error_num, cp);
			return;
		}
		(void) memcpy(&sin6.sin6_addr, hp->h_addr_list[0],
		    hp->h_length);

		sin6.sin6_family = AF_INET6;
		connected = 1;
		/*
		 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll convert
		 * it to IPv4 literal address.
		 */
		if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) &&
		    IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
			IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
			(void) inet_ntop(AF_INET, &ipv4addr, hostname,
			    sizeof (hostname));
		} else {
			(void) strlcpy(hostname, hp->h_name,
			    sizeof (hostname));
		}
	}
	if (!connected) {
		(void) fputs("No target machine specified.\n", stderr);
		return;
	}
	if (argc < 4) {
		cp = argc == 2 ? tail(targ) : argv[1];
		fd = open(cp, O_RDONLY);
		if (fd < 0) {
			(void) fprintf(stderr, "tftp: %s: %s\n", cp,
			    strerror(errno));
			return;
		}
		if (verbose)
			(void) printf("putting %s to %s:%s [%s]\n",
				cp, hostname, targ, mode);
		sin6.sin6_port = port;
		tftp_sendfile(fd, targ, mode);
		return;
	}
	/* this assumes the target is a directory */
	/* on a remote unix system.  hmmmm.  */
	if (strlen(targ) + 1 >= sizeof (buf)) {
		(void) fprintf(stderr, "tftp: filename too long: %s\n", targ);
		return;
	}
	for (n = 1; n < argc - 1; n++) {
		argtail = tail(argv[n]);
		if (snprintf(buf, sizeof (buf), "%s/%s", targ, argtail) >=
		    sizeof (buf)) {
			(void) fprintf(stderr,
			    "tftp: filename too long: %s/%s\n", targ, argtail);
			continue;
		}
		fd = open(argv[n], O_RDONLY);
		if (fd < 0) {
			(void) fprintf(stderr, "tftp: %s: %s\n", argv[n],
			    strerror(errno));
			continue;
		}
		if (verbose)
			(void) printf("putting %s to %s:%s [%s]\n",
				argv[n], hostname, buf, mode);
		sin6.sin6_port = port;
		tftp_sendfile(fd, buf, mode);
	}
}

static void
putusage(char *s)
{
	(void) fprintf(stderr, "usage: %s file ... host:target, or\n"
	    "       %s file ... target (when already connected)\n", s, s);
}

/*
 * Receive file(s).
 */
static void
get(int argc, char **argv)
{
	int fd;
	int n;
	char *cp;
	char *src;
	struct in6_addr ipv6addr;
	struct in_addr ipv4addr;
	int error_num;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "files") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc < 2) {
		getusage(argv[0]);
		return;
	}
	if (!connected) {
		for (n = 1; n < argc; n++)
			if (finddelimiter(argv[n]) == 0) {
				getusage(argv[0]);
				return;
			}
	}
	for (n = 1; n < argc; n++) {
		src = finddelimiter(argv[n]);
		if (src == NULL)
			src = argv[n];
		else {
			struct hostent *hp;
			char *hostnameinput;

			*src++ = 0;
			hostnameinput = removebrackets(argv[n]);

			if ((hp = getipnodebyname(hostnameinput, AF_INET6,
			    AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED,
			    &error_num)) == NULL) {
				unknown_host(error_num, hostnameinput);
				continue;
			}
			(void) memcpy((caddr_t)&sin6.sin6_addr,
			    hp->h_addr_list[0], hp->h_length);

			sin6.sin6_family = AF_INET6;
			connected = 1;
			/*
			 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll
			 * convert it to IPv4 literal address.
			 */
			if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) &&
			    IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
				IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
				(void) inet_ntop(AF_INET, &ipv4addr, hostname,
				    sizeof (hostname));
			} else {
				(void) strlcpy(hostname, hp->h_name,
				    sizeof (hostname));
			}
		}
		if (argc < 4) {
			cp = argc == 3 ? argv[2] : tail(src);
			fd = creat(cp, 0644);
			if (fd < 0) {
				(void) fprintf(stderr, "tftp: %s: %s\n", cp,
				    strerror(errno));
				return;
			}
			if (verbose)
				(void) printf("getting from %s:%s to %s [%s]\n",
					hostname, src, cp, mode);
			sin6.sin6_port = port;
			tftp_recvfile(fd, src, mode);
			break;
		}
		cp = tail(src);	/* new .. jdg */
		fd = creat(cp, 0644);
		if (fd < 0) {
			(void) fprintf(stderr, "tftp: %s: %s\n", cp,
			    strerror(errno));
			continue;
		}
		if (verbose)
			(void) printf("getting from %s:%s to %s [%s]\n",
			    hostname, src, cp, mode);
		sin6.sin6_port = port;
		tftp_recvfile(fd, src, mode);
	}
}

static void
getusage(char *s)
{
	(void) fprintf(stderr, "usage: %s host:file host:file ... file, or\n"
	    "       %s file file ... file if connected\n", s, s);
}

static void
setrexmt(int argc, char **argv)
{
	int t;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "value") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc != 2) {
		(void) fprintf(stderr, "usage: %s value\n", argv[0]);
		return;
	}
	t = atoi(argv[1]);
	if (t < 0)
		(void) fprintf(stderr, "%s: bad value\n", argv[1]);
	else
		rexmtval = t;
}

static void
settimeout(int argc, char **argv)
{
	int t;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "value") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc != 2) {
		(void) fprintf(stderr, "usage: %s value\n", argv[0]);
		return;
	}
	t = atoi(argv[1]);
	if (t < 0)
		(void) fprintf(stderr, "%s: bad value\n", argv[1]);
	else
		maxtimeout = t;
}

/*ARGSUSED*/
static void
status(int argc, char **argv)
{
	if (connected)
		(void) printf("Connected to %s.\n", hostname);
	else
		(void) puts("Not connected.");
	(void) printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
		verbose ? "on" : "off", trace ? "on" : "off");
	(void) printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
		rexmtval, maxtimeout);
	(void) printf("Transfer blocksize option: ");
	if (blksize == 0)
		(void) puts("off");
	else
		(void) printf("%d bytes\n", blksize);
	(void) printf("Server rexmt-interval option: ");
	if (srexmtval == 0)
		(void) puts("off");
	else
		(void) printf("%d seconds\n", srexmtval);
	(void) printf("Transfer size option: %s\n", tsize_opt ? "on" : "off");
}

/*ARGSUSED*/
static void
intr(int signum)
{
	(void) cancel_alarm();
	longjmp(toplevel, -1);
}

static char *
tail(char *filename)
{
	char *s;

	while (*filename != '\0') {
		s = strrchr(filename, '/');
		if (s == NULL)
			break;
		if (s[1] != '\0')
			return (&s[1]);
		*s = '\0';
	}
	return (filename);
}

/*
 * Command parser.
 */
static void
command(int top)
{
	struct cmd *c;
	int ch;

	if (!top)
		(void) putchar('\n');
	for (;;) {
		(void) printf("%s> ", prompt);
		if (fgets(line, sizeof (line), stdin) == NULL) {
			if (feof(stdin))
				quit(0, NULL);
			else
				continue;
		}

		/* Flush what didn't fit in the buffer */
		if (line[strlen(line)-1] != '\n') {
			while (((ch = getchar()) != EOF) && (ch != '\n'))
				;
			(void) fputs("?Line too long\n", stderr);
		} else {
			line[strlen(line)-1] = '\0';
			if (line[0] != '\0') {
				int	argc;
				char	**argv;

				makeargv(&argc, &argv);
				c = getcmd(argv[0]);
				if (c == AMBIGCMD)
					(void) fputs("?Ambiguous command\n",
					    stderr);
				else if (c == NULL)
					(void) fputs("?Invalid command\n",
					    stderr);
				else
					(*c->handler)(argc, argv);
			}
		}
	}
}

static struct cmd *
getcmd(char *name)
{
	char *p, *q;
	struct cmd *c, *found;

	if (name == NULL)
		return (NULL);

	found = NULL;
	for (c = cmdtab; (p = c->name) != NULL; c++) {
		for (q = name; *q == *p++; q++)
			if (*q == '\0')		/* exact match? */
			    return (c);
		if (*q == '\0')		/* the name was a prefix */
			found = (found == NULL) ? c : AMBIGCMD;
	}
	return (found);
}

/*
 * Given a string, this function returns the pointer to the delimiting ':'.
 * The string can contain an IPv6 literal address, which should be inside a
 * pair of brackets, e.g. [1::2]. Any colons inside a pair of brackets are not
 * accepted as delimiters. Returns NULL if delimiting ':' is not found.
 */
static char *
finddelimiter(char *str)
{
	boolean_t is_bracket_open = B_FALSE;
	char *cp;

	for (cp = str; *cp != '\0'; cp++) {
		if (*cp == '[')
			is_bracket_open = B_TRUE;
		else if (*cp == ']')
			is_bracket_open = B_FALSE;
		else if (*cp == ':' && !is_bracket_open)
			return (cp);
	}
	return (NULL);
}

/*
 * Given a string which is possibly surrounded by brackets, e.g. [1::2], this
 * function returns a string after removing those brackets. If the brackets
 * don't match, it does nothing.
 */
static char *
removebrackets(char *str)
{
	char *newstr = str;

	if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) {
		newstr = str + 1;
		str[strlen(str) - 1] = '\0';
	}
	return (newstr);
}

#define	MARGV_INC	20

/*
 * Slice a string up into argc/argv.
 */
static void
makeargv(int *argcp, char ***argvp)
{
	char *cp;
	char **argp;
	int argc;
	static char **argv;
	static int argv_size;

	if (argv == NULL) {
		argv_size = MARGV_INC;
		if ((argv = malloc(argv_size * sizeof (char *))) == NULL) {
			perror("tftp: malloc");
			exit(1);
		}
	}
	argc = 0;
	argp = argv;
	for (cp = line; *cp != '\0'; ) {
		while (isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*argp++ = cp;
		argc++;
		if (argc == argv_size) {
			argv_size += MARGV_INC;
			if ((argv = realloc(argv,
			    argv_size * sizeof (char *))) == NULL) {
				perror("tftp: realloc");
				exit(1);
			}
			argp = argv + argc;
		}
		while (*cp != '\0' && !isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*cp++ = '\0';
	}
	*argp = NULL;

	*argcp = argc;
	*argvp = argv;
}

/*ARGSUSED*/
static void
quit(int argc, char **argv)
{
	exit(0);
}

/*
 * Help command.
 */
static void
help(int argc, char **argv)
{
	struct cmd *c;

	if (argc == 1) {
		(void) puts("Commands may be abbreviated.  Commands are:\n");
		for (c = cmdtab; c->name != NULL; c++)
			(void) printf("%-*s\t%s\n", HELPINDENT, c->name,
			    c->help);
		return;
	}
	while (--argc > 0) {
		char *arg;
		arg = *++argv;
		c = getcmd(arg);
		if (c == AMBIGCMD)
			(void) fprintf(stderr, "?Ambiguous help command %s\n",
			    arg);
		else if (c == NULL)
			(void) fprintf(stderr, "?Invalid help command %s\n",
			    arg);
		else
			(void) fprintf(stderr, "%s\n", c->help);
	}
}

/*ARGSUSED*/
static void
settrace(int argc, char **argv)
{
	trace = !trace;
	(void) printf("Packet tracing %s.\n", trace ? "on" : "off");
}

/*ARGSUSED*/
static void
setverbose(int argc, char **argv)
{
	verbose = !verbose;
	(void) printf("Verbose mode %s.\n", verbose ? "on" : "off");
}

static void
setblksize(int argc, char **argv)
{
	int b;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "value") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc != 2) {
		(void) fprintf(stderr, "usage: %s value\n", argv[0]);
		return;
	}
	b = atoi(argv[1]);

	/* RFC 2348 specifies valid blksize range, allow 0 to turn option off */
	if ((b < MIN_BLKSIZE || b > MAX_BLKSIZE) && b != 0)
		(void) fprintf(stderr, "%s: bad value\n", argv[1]);
	else
		blksize = b;
}

static void
setsrexmt(int argc, char **argv)
{
	int t;

	if (argc < 2) {
		if (prompt_for_arg(line, sizeof (line), "value") == -1)
			return;
		makeargv(&argc, &argv);
	}
	if (argc != 2) {
		(void) fprintf(stderr, "usage: %s value\n", argv[0]);
		return;
	}
	t = atoi(argv[1]);

	/* RFC 2349 specifies valid timeout range, allow 0 to turn option off */
	if ((t < MIN_TIMEOUT || t > MAX_TIMEOUT) && t != 0)
		(void) fprintf(stderr, "%s: bad value\n", argv[1]);
	else
		srexmtval = t;
}

static void
settsize(int argc, char **argv)
{
	if (argc != 1) {
		(void) fprintf(stderr, "usage: %s\n", argv[0]);
		return;
	}
	tsize_opt = !tsize_opt;
	(void) printf("Transfer size option %s.\n", tsize_opt ? "on" : "off");
}