[BACK]Return to main.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tftp

File: [local] / src / usr.bin / tftp / main.c (download)

Revision 1.30, Tue Oct 27 23:59:44 2009 UTC (14 years, 7 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_5_1_BASE, OPENBSD_5_1, OPENBSD_5_0_BASE, OPENBSD_5_0, OPENBSD_4_9_BASE, OPENBSD_4_9, OPENBSD_4_8_BASE, OPENBSD_4_8, OPENBSD_4_7_BASE, OPENBSD_4_7
Changes since 1.29: +1 -15 lines

rcsid[] and sccsid[] and copyright[] are essentially unmaintained (and
unmaintainable).  these days, people use source.  these id's do not provide
any benefit, and do hurt the small install media
(the 33,000 line diff is essentially mechanical)
ok with the idea millert, ok dms

/*	$OpenBSD: main.c,v 1.30 2009/10/27 23:59:44 deraadt Exp $	*/
/*	$NetBSD: main.c,v 1.6 1995/05/21 16:54:10 mycroft Exp $	*/

/*
 * Copyright (c) 1983, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * TFTP User Program -- Command Interface
 *
 * This version includes many modifications by Jim Guyton <guyton@rand-unix>
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "extern.h"

#define	LBUFLEN		200		/* size of input buffer */
#define	MAXARGV		20
#define HELPINDENT	(sizeof("connect"))

void			 get(int, char **);
void			 help(int, char **);
void			 modecmd(int, char **);
void			 put(int, char **);
void			 quit(int, char **);
void			 setascii(int, char **);
void			 setbinary(int, char **);
void			 setpeer(int, char **);
void			 setrexmt(int, char **);
void			 settimeout(int, char **);
void			 settrace(int, char **);
void			 setverbose(int, char **);
void			 settsize(int, char **);
void			 settout(int, char **);
void			 setblksize(int, char **);
void			 status(int, char **);
int			 readcmd(char *, int, FILE *);
static void		 getusage(char *);
static int		 makeargv(void);
static void		 putusage(char *);
static void		 settftpmode(char *);
static __dead void	 command(void);
struct cmd	 	*getcmd(char *);
char			*tail(char *);

struct sockaddr_in	 peeraddr;
int			 f;
short			 port;
int			 trace;
int			 verbose;
int			 connected;
char			 mode[32];
char			 line[LBUFLEN];
int			 margc;
char			*margv[MAXARGV+1];
char			*prompt = "tftp";
void			 intr(int);
struct servent		*sp;
int	 		 rexmtval = TIMEOUT;
int	 		 maxtimeout = 5 * TIMEOUT;
char	 		 hostname[MAXHOSTNAMELEN];
FILE			*file = NULL;
volatile sig_atomic_t	 intrflag = 0;
char			*ackbuf;
int			 has_options = 0;
int			 opt_tsize = 0;
int			 opt_tout = 0;
int			 opt_blksize = 0;

char	vhelp[] = "toggle verbose mode";
char	thelp[] = "toggle packet tracing";
char	chelp[] = "connect to remote tftp";
char	qhelp[] = "exit tftp";
char	hhelp[] = "print help information";
char	shelp[] = "send file";
char	rhelp[] = "receive file";
char	mhelp[] = "set file transfer mode";
char	sthelp[] = "show current status";
char	xhelp[] = "set per-packet retransmission timeout";
char	ihelp[] = "set total retransmission timeout";
char	ashelp[] = "set mode to netascii";
char	bnhelp[] = "set mode to octet";
char	oshelp[] = "toggle tsize option";
char	othelp[] = "toggle timeout option";
char	obhelp[] = "set alternative blksize option";

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

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 },
	{ "tsize",	oshelp, settsize },
	{ "tout",	othelp, settout },
	{ "blksize",	obhelp,	setblksize },
	{ "help",	hhelp,	help },
	{ "?",		hhelp,	help },
	{ NULL,		NULL,	NULL }
};

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

int
main(int argc, char *argv[])
{
	struct sockaddr_in	s_in;

	/* socket, bind */
	sp = getservbyname("tftp", "udp");
	if (sp == 0)
		errx(1, "udp/tftp: unknown service");
	f = socket(AF_INET, SOCK_DGRAM, 0);
	if (f < 0)
		err(3, "socket");
	bzero((char *)&s_in, sizeof(s_in));
	s_in.sin_family = AF_INET;
	if (bind(f, (struct sockaddr *)&s_in, sizeof(s_in)) < 0)
		err(1, "bind");

	/* set default transfer mode */
	strlcpy(mode, "netascii", sizeof(mode));

	/* set peer if given */
	if (argc > 1)
		setpeer(argc, argv);

	/* catch SIGINT */
	signal(SIGINT, intr);

	/* allocate memory for packets */
	if ((ackbuf = malloc(SEGSIZE_MAX + 4)) == NULL)
		err(1, "malloc");

	/* command prompt */
	command();

	return (0);
}

void
setpeer(int argc, char *argv[])
{
	struct hostent	*host;
	const char	*errstr;

	if (argc < 2) {
		strlcpy(line, "Connect ", sizeof(line));
		printf("(to) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if ((argc < 2) || (argc > 3)) {
		printf("usage: %s [host [port]]\n", argv[0]);
		return;
	}
	if (inet_aton(argv[1], &peeraddr.sin_addr) != 0) {
		peeraddr.sin_family = AF_INET;
		(void)strncpy(hostname, argv[1], sizeof(hostname));
		hostname[sizeof(hostname) - 1] = '\0';
	} else {
		host = gethostbyname(argv[1]);
		if (host == 0) {
			connected = 0;
			printf("%s: unknown host\n", argv[1]);
			return;
		}
		peeraddr.sin_family = host->h_addrtype;
		bcopy(host->h_addr, &peeraddr.sin_addr, host->h_length);
		(void)strlcpy(hostname, host->h_name, sizeof(hostname));
	}
	port = sp->s_port;
	if (argc == 3) {
		port = strtonum(argv[2], 1, 65535, &errstr);
		if (errstr) {
			printf("%s: port number is %s\n", argv[2], errstr);
			connected = 0;
			return;
		}
		port = htons(port);
	}
	connected = 1;
}

void
modecmd(int argc, char *argv[])
{
	struct modes	*p;
	char		*sep;

	if (argc < 2) {
		printf("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)
				break;
		if (p->m_name) {
			settftpmode(p->m_mode);
			return;
		}
		printf("%s: unknown mode\n", argv[1]);
		/* drop through and print usage message */
	}

	printf("usage: %s [", argv[0]);
	sep = " ";
	for (p = modes; p->m_name != NULL; p++) {
		printf("%s%s", sep, p->m_name);
		if (*sep == ' ')
			sep = " | ";
	}
	printf(" ]\n");

	return;
}

/* ARGSUSED */
void
setbinary(int argc, char *argv[])
{
	settftpmode("octet");
}

/* ARGSUSED */
void
setascii(int argc, char *argv[])
{
	settftpmode("netascii");
}

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

/*
 * Send file(s).
 */
void
put(int argc, char *argv[])
{
	int	 fd;
	int	 n;
	char	*cp, *targ;

	if (argc < 2) {
		strlcpy(line, "send ", sizeof(line));
		printf("(file) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if (argc < 2) {
		putusage(argv[0]);
		return;
	}
	targ = argv[argc - 1];
	if (strchr(argv[argc - 1], ':')) {
		struct hostent	*hp;

		for (n = 1; n < argc - 1; n++)
			if (strchr(argv[n], ':')) {
				putusage(argv[0]);
				return;
			}
		cp = argv[argc - 1];
		targ = strchr(cp, ':');
		*targ++ = 0;
		hp = gethostbyname(cp);
		if (hp == NULL) {
			warnx("%s: %s", cp, hstrerror(h_errno));
			return;
		}
		bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, hp->h_length);
		peeraddr.sin_family = hp->h_addrtype;
		connected = 1;
		port = sp->s_port;
		strlcpy(hostname, hp->h_name, sizeof(hostname));
	}
	if (!connected) {
		printf("No target machine specified.\n");
		return;
	}
	if (argc < 4) {
		cp = argc == 2 ? tail(targ) : argv[1];
		fd = open(cp, O_RDONLY);
		if (fd < 0) {
			warn("open: %s", cp);
			return;
		}
		if (verbose)
			printf("putting %s to %s:%s [%s]\n",
			    cp, hostname, targ, mode);
		peeraddr.sin_port = port;
		sendfile(fd, targ, mode);
		return;
	}

	/*
	 * this assumes the target is a directory on
	 * on a remote unix system.  hmmmm.
	 */
	for (n = 1; n < argc - 1; n++) {
		if (asprintf(&cp, "%s/%s", targ, tail(argv[n])) == -1)
			err(1, "asprintf");
		fd = open(argv[n], O_RDONLY);
		if (fd < 0) {
			warn("open: %s", argv[n]);
			free(cp);
			continue;
		}
		if (verbose)
			printf("putting %s to %s:%s [%s]\n",
			    argv[n], hostname, cp, mode);
		peeraddr.sin_port = port;
		sendfile(fd, cp, mode);
		free(cp);
	}
}

static void
putusage(char *s)
{
	printf("usage: %s file [[host:]remotename]\n", s);
	printf("       %s file1 file2 ... fileN [[host:]remote-directory]\n",
	    s);
}

/*
 * Receive file(s).
 */
void
get(int argc, char *argv[])
{
	int	 fd;
	int	 n;
	char	*cp;
	char	*src;

	if (argc < 2) {
		strlcpy(line, "get ", sizeof(line));
		printf("(files) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if (argc < 2) {
		getusage(argv[0]);
		return;
	}
	if (!connected) {
		for (n = 1; n < argc; n++)
			if (strchr(argv[n], ':') == 0) {
				getusage(argv[0]);
				return;
			}
	}
	for (n = 1; n < argc; n++) {
		src = strchr(argv[n], ':');
		if (src == NULL)
			src = argv[n];
		else {
			struct hostent	*hp;

			*src++ = 0;
			hp = gethostbyname(argv[n]);
			if (hp == NULL) {
				warnx("%s: %s", argv[n], hstrerror(h_errno));
				continue;
			}
			bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
			    hp->h_length);
			peeraddr.sin_family = hp->h_addrtype;
			connected = 1;
			strlcpy(hostname, hp->h_name, sizeof(hostname));
		}
		if (argc < 4) {
			cp = argc == 3 ? argv[2] : tail(src);
			fd = creat(cp, 0644);
			if (fd < 0) {
				warn("create: %s", cp);
				return;
			}
			if (verbose)
				printf("getting from %s:%s to %s [%s]\n",
				    hostname, src, cp, mode);
			peeraddr.sin_port = port;
			recvfile(fd, src, mode);
			break;
		}
		cp = tail(src);	/* new .. jdg */
		fd = creat(cp, 0644);
		if (fd < 0) {
			warn("create: %s", cp);
			continue;
		}
		if (verbose)
			printf("getting from %s:%s to %s [%s]\n",
			    hostname, src, cp, mode);
		peeraddr.sin_port = port;
		recvfile(fd, src, mode);
	}
}

static void
getusage(char *s)
{
	printf("usage: %s [host:]file [localname]\n", s);
	printf("       %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s);
}

void
setrexmt(int argc, char *argv[])
{
	int		 t;
	const char	*errstr;

	if (argc < 2) {
		strlcpy(line, "Rexmt-timeout ", sizeof(line));
		printf("(value) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if (argc != 2) {
		printf("usage: %s value\n", argv[0]);
		return;
	}
	t = strtonum(argv[1], TIMEOUT_MIN, TIMEOUT_MAX, &errstr);
	if (errstr)
		printf("%s: value is %s\n", argv[1], errstr);
	else
		rexmtval = t;
}

void
settimeout(int argc, char *argv[])
{
	int		 t;
	const char	*errstr;

	if (argc < 2) {
		strlcpy(line, "Maximum-timeout ", sizeof(line));
		printf("(value) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if (argc != 2) {
		printf("usage: %s value\n", argv[0]);
		return;
	}
	t = strtonum(argv[1], TIMEOUT_MIN, TIMEOUT_MAX, &errstr);
	if (errstr)
		printf("%s: value is %s\n", argv[1], errstr);
	else
		maxtimeout = t;
}

/* ARGSUSED */
void
status(int argc, char *argv[])
{
	if (connected)
		printf("Connected to %s.\n", hostname);
	else
		printf("Not connected.\n");
	printf("Mode: %s Verbose: %s Tracing: %s\n",
	    mode, verbose ? "on" : "off", trace ? "on" : "off");
	printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
	    rexmtval, maxtimeout);
}

/* ARGSUSED */
void
intr(int signo)
{
	intrflag = 1;
}

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

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

	return (filename);
}

/*
 * Command parser.
 */
static __dead void
command(void)
{
	struct cmd	*c;

	for (;;) {
		printf("%s> ", prompt);
		if (readcmd(line, LBUFLEN, stdin) < 1)
			continue;
		if ((line[0] == 0) || (line[0] == '\n'))
			continue;
		if (makeargv())
			continue;
		if (margc == 0)
			continue;
		c = getcmd(margv[0]);
		if (c == (struct cmd *) - 1) {
			printf("?Ambiguous command\n");
			continue;
		}
		if (c == 0) {
			printf("?Invalid command\n");
			continue;
		}
		(*c->handler)(margc, margv);
	}
}

struct cmd *
getcmd(char *name)
{
	char		*p, *q;
	struct cmd	*c, *found;
	int		 nmatches, longest;

	longest = 0;
	nmatches = 0;
	found = 0;
	intrflag = 0;
	for (c = cmdtab; (p = c->name) != NULL; c++) {
		for (q = name; *q == *p++; q++)
			if (*q == 0)		/* exact match? */
				return (c);
		if (!*q) {			/* the name was a prefix */
			if (q - name > longest) {
				longest = q - name;
				nmatches = 1;
				found = c;
			} else if (q - name == longest)
				nmatches++;
		}
	}
	if (nmatches > 1)
		return ((struct cmd *) - 1);

	return (found);
}

/*
 * Slice a string up into argc/argv.
 */
static int
makeargv(void)
{
	char	 *cp;
	char	**argp = margv;
	int	  ret = 0;

	margc = 0;
	for (cp = line; *cp;) {
		if (margc >= MAXARGV) {
			printf("too many arguments\n");
			ret = 1;
			break;
		}
		while (isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*argp++ = cp;
		margc += 1;
		while (*cp != '\0' && !isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*cp++ = '\0';
	}
	*argp++ = 0;

	return (ret);
}

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

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

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

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

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

/* ARGSUSED */
void
settsize(int argc, char *argv[])
{
	opt_tsize = !opt_tsize;
	printf("Tsize option %s.\n", opt_tsize ? "on" : "off");
	if (opt_tsize)
		has_options++;
	else
		has_options--;
}

/* ARGSUSED */
void
settout(int argc, char *argv[])
{
	opt_tout = !opt_tout;
	printf("Timeout option %s.\n", opt_tout ? "on" : "off");
	if (opt_tout)
		has_options++;
	else
		has_options--;
}

void
setblksize(int argc, char *argv[])
{
	int		 t;
	const char	*errstr;

	if (argc < 2) {
		strlcpy(line, "Blocksize ", sizeof(line));
		printf("(value) ");
		readcmd(&line[strlen(line)], LBUFLEN - strlen(line), stdin);
		if (makeargv())
			return;
		argc = margc;
		argv = margv;
	}
	if (argc != 2) {
		printf("usage: %s value\n", argv[0]);
		return;
	}
	t = strtonum(argv[1], SEGSIZE_MIN, SEGSIZE_MAX, &errstr);
	if (errstr)
		printf("%s: value is %s\n", argv[1], errstr);
	else {
		if (opt_blksize == 0)
			has_options++;
		opt_blksize = t;
	}
}

int
readcmd(char *input, int len, FILE *stream)
{
	int		nfds;
	struct pollfd	pfd[1];

	fflush(stdout);

	pfd[0].fd = 0;
	pfd[0].events = POLLIN;
	nfds = poll(pfd, 1, INFTIM);
	if (nfds == -1) {
		if (intrflag) {
			intrflag = 0;
			putchar('\n');
			return (0);
		}
		exit(1);
	}

	if (fgets(input, len, stream) == NULL) {
		if (feof(stdin))
			exit(0);
		else
			return (-1);
	}

	return (1);
}