tftp-hpa-google/tftp/main.c
Craig Johnston 899b7d500b Add option to TFTP client to ignore ':' in file name
I have encountered a situation where I would like to use tftp-hpa to
retrieve a file that resides within an absolute path containing a ':'
character.  Ala, "tftp foobar -c get C:2/tftpdir/myfile".  Since the
tftp client automatically converts the host:file syntax, I get an
error "tftp: C: Unknown host".

I made a chage to the tftp client code to add a literal mode (-l),
that prevents the special treatment of the ':' character.  I've
attached a patch set for main.c and tftp.1.in for your consideration.
I'm not sure how many folks run into this, but it may be somewhat
common for VxWorks and perhaps MSDOS users.

Thanks,
Craig Johnston
2006-12-12 14:31:51 -08:00

916 lines
19 KiB
C

/* $OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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.
*/
#include "common/tftpsubs.h"
#ifndef lint
static const char *copyright UNUSED =
"@(#) Copyright (c) 1983, 1993\n\
The Regents of the University of California. All rights reserved.\n";
/* static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93"; */
/* static char rcsid[] = "$OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert Exp $"; */
static const char *rcsid UNUSED =
"tftp-hpa $Id$";
#endif /* not lint */
/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
/*
* TFTP User Program -- Command Interface.
*/
#include <sys/file.h>
#include <ctype.h>
#include <netdb.h>
#ifdef WITH_READLINE
#include <readline/readline.h>
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
#endif
#endif
#include "extern.h"
#define TIMEOUT 5 /* secs between rexmt's */
#define LBUFLEN 200 /* size of input buffer */
struct modes {
const char *m_name;
const char *m_mode;
int m_openflags;
};
static const struct modes modes[] = {
{ "netascii", "netascii", O_TEXT },
{ "ascii", "netascii", O_TEXT },
{ "octet", "octet", O_BINARY },
{ "binary", "octet", O_BINARY },
{ "image", "octet", O_BINARY },
{ 0, 0, 0 }
};
#define MODE_OCTET (&modes[2])
#define MODE_NETASCII (&modes[0])
#define MODE_DEFAULT MODE_NETASCII
struct sockaddr_in peeraddr;
int f;
u_short port;
int trace;
int verbose;
int literal;
int connected;
const struct modes *mode;
#ifdef WITH_READLINE
char *line = NULL;
#else
char line[LBUFLEN];
#endif
int margc;
char *margv[20];
const char *prompt = "tftp> ";
sigjmp_buf toplevel;
void intr(int);
struct servent *sp;
int portrange = 0;
unsigned int portrange_from = 0;
unsigned int portrange_to = 0;
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 status (int, char **);
void setliteral (int, char **);
static void command (void);
static void getusage (char *);
static void makeargv (void);
static void putusage (char *);
static void settftpmode (const struct modes *);
#define HELPINDENT (sizeof("connect"))
struct cmd {
const char *name;
const char *help;
void (*handler) (int, char **);
};
struct cmd cmdtab[] = {
{ "connect",
"connect to remote tftp",
setpeer },
{ "mode",
"set file transfer mode",
modecmd },
{ "put",
"send file",
put },
{ "get",
"receive file",
get },
{ "quit",
"exit tftp",
quit },
{ "verbose",
"toggle verbose mode",
setverbose },
{ "trace",
"toggle packet tracing",
settrace },
{ "literal",
"toggle literal mode, ignore ':' in file name",
setliteral },
{ "status",
"show current status",
status },
{ "binary",
"set mode to octet",
setbinary },
{ "ascii",
"set mode to netascii",
setascii },
{ "rexmt",
"set per-packet transmission timeout",
setrexmt },
{ "timeout",
"set total retransmission timeout",
settimeout },
{ "?",
"print help information",
help },
{ "help",
"print help information",
help },
{ 0, 0, 0 }
};
struct cmd *getcmd(char *);
char *tail(char *);
char *xstrdup(const char *);
const char *program;
static inline void usage(int errcode)
{
fprintf(stderr, "Usage: %s [-v][-l][-m mode] [host [port]] [-c command]\n", program);
exit(errcode);
}
int
main(int argc, char *argv[])
{
struct sockaddr_in s_in;
int arg;
static int pargc, peerargc;
static int iscmd = 0;
char **pargv;
const char *optx;
char *peerargv[3];
program = argv[0];
mode = MODE_DEFAULT;
peerargv[0] = argv[0];
peerargc = 1;
for ( arg = 1 ; !iscmd && arg < argc ; arg++ ) {
if ( argv[arg][0] == '-' ) {
for ( optx = &argv[arg][1] ; *optx ; optx++ ) {
switch ( *optx ) {
case 'v':
verbose = 1;
break;
case 'V':
/* Print version and configuration to stdout and exit */
printf("%s\n", TFTP_CONFIG_STR);
exit(0);
case 'l':
literal = 1;
break;
case 'm':
if ( ++arg >= argc )
usage(EX_USAGE);
{
const struct modes *p;
for ( p = modes ; p->m_name ; p++ ) {
if (!strcmp(argv[arg], p->m_name))
break;
}
if (p->m_name) {
settftpmode(p);
} else {
fprintf(stderr, "%s: invalid mode: %s\n", argv[0], argv[arg]);
exit(EX_USAGE);
}
}
break;
case 'c':
iscmd = 1;
break;
case 'R':
if ( ++arg >= argc )
usage(EX_USAGE);
if ( sscanf(argv[arg], "%u:%u", &portrange_from, &portrange_to) != 2 ||
portrange_from > portrange_to || portrange_to > 65535 ) {
fprintf(stderr, "Bad port range: %s\n", argv[arg]);
exit(EX_USAGE);
}
portrange = 1;
break;
case 'h':
default:
usage(*optx == 'h' ? 0 : EX_USAGE);
}
}
} else {
if ( peerargc >= 3 )
usage(EX_USAGE);
peerargv[peerargc++] = argv[arg];
}
}
pargv = argv + arg;
pargc = argc - arg;
sp = getservbyname("tftp", "udp");
if (sp == 0) {
/* Use canned values */
if (verbose)
fprintf(stderr, "tftp: tftp/udp: unknown service, faking it...\n");
sp = xmalloc(sizeof(struct servent));
sp->s_name = (char *)"tftp";
sp->s_aliases = NULL;
sp->s_port = htons(IPPORT_TFTP);
sp->s_proto = (char *)"udp";
}
port = sp->s_port; /* Default port */
f = socket(AF_INET, SOCK_DGRAM, 0);
if (f < 0) {
perror("tftp: socket");
exit(EX_OSERR);
}
bzero((char *)&s_in, sizeof (s_in));
s_in.sin_family = AF_INET;
if (pick_port_bind(f, &s_in, portrange_from, portrange_to)) {
perror("tftp: bind");
exit(EX_OSERR);
}
bsd_signal(SIGINT, intr);
if ( peerargc ) {
/* Set peer */
if (sigsetjmp(toplevel,1) != 0)
exit(EX_NOHOST);
setpeer(peerargc, peerargv);
}
if ( iscmd && pargc ) {
/* -c specified; execute command and exit */
struct cmd *c;
if (sigsetjmp(toplevel,1) != 0)
exit(EX_UNAVAILABLE);
c = getcmd(pargv[0]);
if ( c == (struct cmd *)-1 || c == (struct cmd *)0 ) {
fprintf(stderr, "%s: invalid command: %s\n", argv[0], pargv[1]);
exit(EX_USAGE);
}
(*c->handler)(pargc, pargv);
exit(0);
}
#ifdef WITH_READLINE
#ifdef HAVE_READLINE_HISTORY_H
using_history();
#endif
#endif
if (sigsetjmp(toplevel,1) != 0)
(void)putchar('\n');
command();
return 0; /* Never reached */
}
char *hostname;
/* Called when a command is incomplete; modifies
the global variable "line" */
static void
getmoreargs(const char *partial, const char *mprompt)
{
#ifdef WITH_READLINE
char *eline;
int len, elen;
len = strlen(partial);
eline = readline(mprompt);
if (!eline)
exit(0); /* EOF */
elen = strlen(eline);
if (line) {
free(line);
line = NULL;
}
line = xmalloc(len+elen+1);
strcpy(line, partial);
strcpy(line+len, eline);
free(eline);
#ifdef HAVE_READLINE_HISTORY_H
add_history(line);
#endif
#else
int len = strlen(partial);
strcpy(line, partial);
fputs(mprompt, stdout);
if ( fgets(line+len, LBUFLEN-len, stdin) == 0 )
if ( feof(stdin) )
exit(0); /* EOF */
#endif
}
void
setpeer(int argc, char *argv[])
{
struct hostent *host;
if (argc < 2) {
getmoreargs("connect ", "(to) ");
makeargv();
argc = margc;
argv = margv;
}
if ((argc < 2) || (argc > 3)) {
printf("usage: %s host-name [port]\n", argv[0]);
return;
}
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);
hostname = xstrdup(host->h_name);
port = sp->s_port;
if (argc == 3) {
struct servent *usp;
usp = getservbyname(argv[2], "udp");
if ( usp ) {
port = usp->s_port;
} else {
unsigned long myport;
char *ep;
myport = strtoul(argv[2], &ep, 10);
if (*ep || myport > 65535UL) {
printf("%s: bad port number\n", argv[2]);
connected = 0;
return;
}
port = htons((u_short)myport);
}
}
if (verbose) {
printf("Connected to %s (%s), port %u\n",
hostname, inet_ntoa(peeraddr.sin_addr),
(unsigned int)ntohs(port));
}
connected = 1;
}
void
modecmd(int argc, char *argv[])
{
const struct modes *p;
const char *sep;
if (argc < 2) {
printf("Using %s mode to transfer files.\n", mode->m_mode);
return;
}
if (argc == 2) {
for (p = modes; p->m_name; p++)
if (strcmp(argv[1], p->m_name) == 0)
break;
if (p->m_name) {
settftpmode(p);
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; p++) {
printf("%s%s", sep, p->m_name);
if (*sep == ' ')
sep = " | ";
}
printf(" ]\n");
return;
}
void
setbinary(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
settftpmode(MODE_OCTET);
}
void
setascii(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
settftpmode(MODE_NETASCII);
}
static void
settftpmode(const struct modes *newmode)
{
mode = newmode;
if (verbose)
printf("mode set to %s\n", mode->m_mode);
}
/*
* Send file(s).
*/
void
put(int argc, char *argv[])
{
int fd;
int n;
char *cp, *targ;
if (argc < 2) {
getmoreargs("send ", "(file) ");
makeargv();
argc = margc;
argv = margv;
}
if (argc < 2) {
putusage(argv[0]);
return;
}
targ = argv[argc - 1];
if (!literal && 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) {
fprintf(stderr, "tftp: %s: ", cp);
herror((char *)NULL);
return;
}
bcopy(hp->h_addr, &peeraddr.sin_addr, hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
hostname = xstrdup(hp->h_name);
}
if (!connected) {
printf("No target machine specified.\n");
return;
}
if (argc < 4) {
cp = argc == 2 ? tail(targ) : argv[1];
fd = open(cp, O_RDONLY|mode->m_openflags);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
return;
}
if (verbose)
printf("putting %s to %s:%s [%s]\n",
cp, hostname, targ, mode->m_mode);
peeraddr.sin_port = port;
tftp_sendfile(fd, targ, mode->m_mode);
return;
}
/* this assumes the target is a directory */
/* on a remote unix system. hmmmm. */
cp = strchr(targ, '\0');
*cp++ = '/';
for (n = 1; n < argc - 1; n++) {
strcpy(cp, tail(argv[n]));
fd = open(argv[n], O_RDONLY|mode->m_openflags);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(argv[n]);
continue;
}
if (verbose)
printf("putting %s to %s:%s [%s]\n",
argv[n], hostname, targ, mode->m_mode);
peeraddr.sin_port = port;
tftp_sendfile(fd, targ, mode->m_mode);
}
}
static void
putusage(char *s)
{
printf("usage: %s file ... host:target, or\n", s);
printf(" %s file ... target (when already connected)\n", s);
}
/*
* Receive file(s).
*/
void
get(int argc, char *argv[])
{
int fd;
int n;
char *cp;
char *src;
if (argc < 2) {
getmoreargs("get ", "(files) ");
makeargv();
argc = margc;
argv = margv;
}
if (argc < 2) {
getusage(argv[0]);
return;
}
if (!connected) {
for (n = 1; n < argc ; n++)
if (literal || strchr(argv[n], ':') == 0) {
getusage(argv[0]);
return;
}
}
for (n = 1; n < argc ; n++) {
src = strchr(argv[n], ':');
if (literal || src == NULL)
src = argv[n];
else {
struct hostent *hp;
*src++ = 0;
hp = gethostbyname(argv[n]);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", argv[n]);
herror((char *)NULL);
continue;
}
bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
hostname = xstrdup(hp->h_name);
}
if (argc < 4) {
cp = argc == 3 ? argv[2] : tail(src);
fd = open(cp, O_WRONLY|O_CREAT|O_TRUNC|mode->m_openflags, 0666);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
return;
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode->m_mode);
peeraddr.sin_port = port;
tftp_recvfile(fd, src, mode->m_mode);
break;
}
cp = tail(src); /* new .. jdg */
fd = open(cp, O_WRONLY|O_CREAT|O_TRUNC|mode->m_openflags, 0666);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
continue;
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode->m_mode);
peeraddr.sin_port = port;
tftp_recvfile(fd, src, mode->m_mode);
}
}
static void
getusage(char *s)
{
printf("usage: %s host:file host:file ... file, or\n", s);
printf(" %s file file ... file if connected\n", s);
}
int rexmtval = TIMEOUT;
void
setrexmt(int argc, char *argv[])
{
int t;
if (argc < 2) {
getmoreargs("rexmt-timeout ", "(value) ");
makeargv();
argc = margc;
argv = margv;
}
if (argc != 2) {
printf("usage: %s value\n", argv[0]);
return;
}
t = atoi(argv[1]);
if (t < 0)
printf("%s: bad value\n", argv[1]);
else
rexmtval = t;
}
int maxtimeout = 5 * TIMEOUT;
void
settimeout(int argc, char *argv[])
{
int t;
if (argc < 2) {
getmoreargs("maximum-timeout ", "(value) ");
makeargv();
argc = margc;
argv = margv;
}
if (argc != 2) {
printf("usage: %s value\n", argv[0]);
return;
}
t = atoi(argv[1]);
if (t < 0)
printf("%s: bad value\n", argv[1]);
else
maxtimeout = t;
}
void
setliteral(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
literal = !literal;
printf("Literal mode %s.\n", literal ? "on" : "off");
}
void
status(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
if (connected)
printf("Connected to %s.\n", hostname);
else
printf("Not connected.\n");
printf("Mode: %s Verbose: %s Tracing: %s Literal: %s\n", mode->m_mode,
verbose ? "on" : "off", trace ? "on" : "off", literal ? "on" : "off");
printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
rexmtval, maxtimeout);
}
void
intr(int sig)
{
(void)sig; /* Quiet unused warning */
bsd_signal(SIGALRM, SIG_IGN);
alarm(0);
siglongjmp(toplevel, -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 void
command(void)
{
struct cmd *c;
for (;;) {
#ifdef WITH_READLINE
if ( line ) {
free(line);
line = NULL;
}
line = readline(prompt);
if ( !line )
exit(0); /* EOF */
#else
fputs(prompt, stdout);
if (fgets(line, LBUFLEN, stdin) == 0) {
if (feof(stdin)) {
exit(0);
} else {
continue;
}
}
#endif
if ((line[0] == 0) || (line[0] == '\n'))
continue;
#ifdef WITH_READLINE
#ifdef HAVE_READLINE_HISTORY_H
add_history(line);
#endif
#endif
makeargv();
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)
{
const char *p;
char *q;
struct cmd *c, *found;
int nmatches, longest;
longest = 0;
nmatches = 0;
found = 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 void
makeargv(void)
{
char *cp;
char **argp = margv;
margc = 0;
for (cp = line; *cp;) {
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;
}
void
quit(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
exit(0);
}
/*
* Help command.
*/
void
help(int argc, char *argv[])
{
struct cmd *c;
printf("%s\n", VERSION);
if (argc == 1) {
printf("Commands may be abbreviated. Commands are:\n\n");
for (c = cmdtab; c->name; 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);
}
}
void
settrace(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
trace = !trace;
printf("Packet tracing %s.\n", trace ? "on" : "off");
}
void
setverbose(int argc, char *argv[])
{
(void)argc; (void)argv; /* Quiet unused warning */
verbose = !verbose;
printf("Verbose mode %s.\n", verbose ? "on" : "off");
}