/*
* 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");
}