Support negotiation and use of TFTP Option Extension (rfc 2347) for the

'blksize' option (rfc 2348) and the 'timeout' and 'tsize' options (rfc 2349).

Contributed by Wasabi Systems, Inc.
This commit is contained in:
briggs 2003-06-11 01:43:52 +00:00
parent 39d51ab631
commit 4441128638
7 changed files with 708 additions and 87 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: tftpd.8,v 1.17 2003/05/14 12:15:17 wiz Exp $
.\" $NetBSD: tftpd.8,v 1.18 2003/06/11 01:43:52 briggs Exp $
.\"
.\" Copyright (c) 1983, 1991, 1993
.\" The Regents of the University of California. All rights reserved.
@ -155,6 +155,24 @@ as well.
.%D July 1992
.%T "The TFTP Protocol (Revision 2)"
.Re
.Rs
.%R RFC
.%N 2347
.%D May 1998
.%T "TFTP Option Extension"
.Re
.Rs
.%R RFC
.%N 2348
.%D May 1998
.%T "TFTP Blocksize Option"
.Re
.Rs
.%R RFC
.%N 2349
.%D May 1998
.%T "TFTP Timeout Interval and Transfer Size Options"
.Re
.Sh HISTORY
The
.Nm
@ -174,9 +192,13 @@ flags appeared in
.Nx 1.4 .
.Pp
IPv6 support was implemented by WIDE/KAME project in 1999.
.Pp
TFTP options were implemented by Wasabi Systems, Inc., in 2003,
and first appeared in
.Nx 2.0 .
.Sh BUGS
Files larger than 33488896 octets (65535 blocks) cannot be transferred
without client and server supporting blocksize negotiation (RFC1783).
without client and server supporting blocksize negotiation (RFCs 2347 & 2348).
.Pp
Many tftp clients will not transfer files over 16744448 octets (32767 blocks).
.Sh SECURITY CONSIDERATIONS

View File

@ -1,4 +1,4 @@
/* $NetBSD: tftpd.c,v 1.24 2001/10/09 18:46:18 christos Exp $ */
/* $NetBSD: tftpd.c,v 1.25 2003/06/11 01:43:52 briggs Exp $ */
/*
* Copyright (c) 1983, 1993
@ -40,7 +40,7 @@ __COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\
#if 0
static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: tftpd.c,v 1.24 2001/10/09 18:46:18 christos Exp $");
__RCSID("$NetBSD: tftpd.c,v 1.25 2003/06/11 01:43:52 briggs Exp $");
#endif
#endif /* not lint */
@ -86,13 +86,17 @@ int peer;
int rexmtval = TIMEOUT;
int maxtimeout = 5*TIMEOUT;
#define PKTSIZE SEGSIZE+4
char buf[PKTSIZE];
char buf[MAXPKTSIZE];
char ackbuf[PKTSIZE];
char oackbuf[PKTSIZE];
struct sockaddr_storage from;
int fromlen;
int debug;
int tftp_opt_tsize = 0;
int tftp_blksize = SEGSIZE;
int tftp_tsize = 0;
/*
* Null-terminated directory prefix list for absolute pathname requests and
* search list for relative pathname requests.
@ -119,8 +123,8 @@ static void usage(void);
static char *verifyhost(struct sockaddr *);
void justquit(int);
int main(int, char **);
void recvfile(struct formats *);
void sendfile(struct formats *);
void recvfile(struct formats *, int, int);
void sendfile(struct formats *, int, int);
void timer(int);
static const char *opcode(int);
int validate_access(char **, int);
@ -128,8 +132,8 @@ int validate_access(char **, int);
struct formats {
const char *f_mode;
int (*f_validate)(char **, int);
void (*f_send)(struct formats *);
void (*f_recv)(struct formats *);
void (*f_send)(struct formats *, int, int);
void (*f_recv)(struct formats *, int, int);
int f_convert;
} formats[] = {
{ "netascii", validate_access, sendfile, recvfile, 1 },
@ -156,7 +160,7 @@ main(int argc, char *argv[])
struct tftphdr *tp;
char *tgtuser, *tgtgroup, *ep;
int n, ch, on, fd;
int len;
int len, soopt;
uid_t curuid, tgtuid;
gid_t curgid, tgtgid;
long nid;
@ -387,6 +391,16 @@ main(int argc, char *argv[])
syslog(LOG_ERR, "connect: %m");
exit(1);
}
soopt = 65536; /* larger than we'll ever need */
if (setsockopt(peer, SOL_SOCKET, SO_SNDBUF, (void *) &soopt, sizeof(soopt)) < 0) {
syslog(LOG_ERR, "set SNDBUF: %m");
exit(1);
}
if (setsockopt(peer, SOL_SOCKET, SO_RCVBUF, (void *) &soopt, sizeof(soopt)) < 0) {
syslog(LOG_ERR, "set RCVBUF: %m");
exit(1);
}
tp = (struct tftphdr *)buf;
tp->th_opcode = ntohs(tp->th_opcode);
if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
@ -394,6 +408,200 @@ main(int argc, char *argv[])
exit(1);
}
static int
blk_handler(struct tftphdr *tp, char *opt, char *val, char *ack,
int *ackl, int *ec)
{
unsigned long bsize;
char *endp;
int l;
/*
* On these failures, we could just ignore the blocksize option.
* Perhaps that should be a command-line option.
*/
errno = 0;
bsize = strtoul(val, &endp, 10);
if ((bsize == ULONG_MAX && errno == ERANGE) || *endp) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"illegal value %s for blksize option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
if (bsize < 8 || bsize > 65464) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"out of range value %s for blksize option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
tftp_blksize = bsize;
strcpy(ack + *ackl, "blksize");
*ackl += 8;
l = sprintf(ack + *ackl, "%lu", bsize);
*ackl += l + 1;
return 0;
}
static int
timeout_handler(struct tftphdr *tp, char *opt, char *val, char *ack,
int *ackl, int *ec)
{
unsigned long tout;
char *endp;
int l;
errno = 0;
tout = strtoul(val, &endp, 10);
if ((tout == ULONG_MAX && errno == ERANGE) || *endp) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"illegal value %s for timeout option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
if (tout < 1 || tout > 255) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"out of range value %s for timeout option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
rexmtval = tout;
strcpy(ack + *ackl, "timeout");
*ackl += 8;
l = sprintf(ack + *ackl, "%lu", tout);
*ackl += l + 1;
/*
* Arbitrarily pick a maximum timeout on a request to 3
* retransmissions if the interval timeout is more than
* one minute. Longest possible timeout is therefore
* 3 * 255 - 1, or 764 seconds.
*/
if (rexmtval > 60) {
maxtimeout = rexmtval * 3;
} else {
maxtimeout = rexmtval * 5;
}
return 0;
}
static int
tsize_handler(struct tftphdr *tp, char *opt, char *val, char *ack,
int *ackl, int *ec)
{
unsigned long fsize;
char *endp;
/*
* Maximum file even with extended tftp is 65535 blocks of
* length 65464, or 4290183240 octets (4784056 less than 2^32).
* unsigned long is at least 32 bits on all NetBSD archs.
*/
errno = 0;
fsize = strtoul(val, &endp, 10);
if ((fsize == ULONG_MAX && errno == ERANGE) || *endp) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"illegal value %s for tsize option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
if (fsize > (unsigned long) 65535 * 65464) {
syslog(LOG_NOTICE, "%s: %s request for %s: "
"out of range value %s for tsize option",
verifyhost((struct sockaddr *)&from),
tp->th_opcode == WRQ ? "write" : "read",
tp->th_stuff, val);
return 0;
}
tftp_opt_tsize = 1;
tftp_tsize = fsize;
/*
* We will report this later -- either replying with the fsize (WRQ)
* or replying with the actual filesize (RRQ).
*/
return 0;
}
struct tftp_options {
char *o_name;
int (*o_handler)(struct tftphdr *, char *, char *, char *,
int *, int *);
} options[] = {
{ "blksize", blk_handler },
{ "timeout", timeout_handler },
{ "tsize", tsize_handler },
{ NULL, NULL }
};
/*
* Get options for an extended tftp session. Stuff the ones we
* recognize in oackbuf.
*/
static int
get_options(struct tftphdr *tp, char *cp, int size, char *ackb,
int *alen, int *err)
{
struct tftp_options *op;
char *option, *value, *endp;
int r, rv=0, ec=0;
endp = cp + size;
while (cp < endp) {
option = cp;
while (*cp && cp < endp) {
*cp = tolower(*cp);
cp++;
}
if (*cp) {
/* if we have garbage at the end, just ignore it */
break;
}
cp++; /* skip over NUL */
value = cp;
while (*cp && cp < endp) {
cp++;
}
if (*cp) {
/* if we have garbage at the end, just ignore it */
break;
}
cp++;
for (op = options; op->o_name; op++) {
if (strcmp(op->o_name, option) == 0)
break;
}
if (op->o_name) {
r = op->o_handler(tp, option, value, ackb, alen, &ec);
if (r < 0) {
rv = -1;
break;
}
rv++;
} /* else ignore unknown options */
}
if (rv < 0)
*err = ec;
return rv;
}
/*
* Handle initial connection protocol.
*/
@ -403,7 +611,7 @@ tftp(struct tftphdr *tp, int size)
struct formats *pf;
char *cp;
char *filename, *mode;
int first, ecode;
int first, ecode, alen, etftp=0, r;
first = 1;
mode = NULL;
@ -434,6 +642,28 @@ again:
nak(EBADOP);
exit(1);
}
/*
* cp currently points to the NUL byte following the mode.
*
* If we have some valid options, then let's assume that we're
* now dealing with an extended tftp session. Note that if we
* don't get any options, then we *must* assume that we do not
* have an extended tftp session. If we get options, we fill
* in the ack buf to acknowledge them. If we skip that, then
* the client *must* assume that we are not using an extended
* session.
*/
size -= (++cp - (char *) tp);
if (size > 0 && *cp) {
alen = 2; /* Skip over opcode */
r = get_options(tp, cp, size, oackbuf, &alen, &ecode);
if (r > 0) {
etftp = 1;
} else if (r < 0) {
nak(ecode);
exit(1);
}
}
ecode = (*pf->f_validate)(&filename, tp->th_opcode);
if (logging) {
syslog(LOG_INFO, "%s: %s request for %s: %s",
@ -451,10 +681,26 @@ again:
nak(ecode);
exit(1);
}
if (etftp) {
struct tftphdr *oack_h;
if (tftp_opt_tsize) {
int l;
strcpy(oackbuf + alen, "tsize");
alen += 6;
l = sprintf(oackbuf + alen, "%u", tftp_tsize);
alen += l + 1;
}
oack_h = (struct tftphdr *) oackbuf;
oack_h->th_opcode = htons(OACK);
}
if (tp->th_opcode == WRQ)
(*pf->f_recv)(pf);
(*pf->f_recv)(pf, etftp, alen);
else
(*pf->f_send)(pf);
(*pf->f_send)(pf, etftp, alen);
exit(0);
}
@ -562,6 +808,10 @@ validate_access(char **filep, int mode)
*filep = filename;
}
}
if (tftp_opt_tsize && mode == RRQ)
tftp_tsize = (unsigned long) stbuf.st_size;
fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY | O_TRUNC);
if (fd < 0)
return (errno + 100);
@ -602,6 +852,8 @@ opcode(int code)
return "ACK";
case ERROR:
return "ERROR";
case OACK:
return "OACK";
default:
(void)snprintf(buf, sizeof(buf), "*code %d*", code);
return buf;
@ -612,7 +864,7 @@ opcode(int code)
* Send the requested file.
*/
void
sendfile(struct formats *pf)
sendfile(struct formats *pf, int etftp, int acklength)
{
volatile unsigned int block;
struct tftphdr *dp;
@ -620,31 +872,42 @@ sendfile(struct formats *pf)
int size, n;
signal(SIGALRM, timer);
dp = r_init();
ap = (struct tftphdr *)ackbuf;
block = 1;
if (etftp) {
dp = (struct tftphdr *)oackbuf;
size = acklength - 4;
block = 0;
} else {
dp = r_init();
size = 0;
block = 1;
}
do {
size = readit(file, &dp, pf->f_convert);
if (size < 0) {
nak(errno + 100);
goto abort;
if (block > 0) {
size = readit(file, &dp, tftp_blksize, pf->f_convert);
if (size < 0) {
nak(errno + 100);
goto abort;
}
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
}
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
timeout = 0;
(void)setjmp(timeoutbuf);
send_data:
if (debug)
if (!etftp && debug)
syslog(LOG_DEBUG, "Send DATA %u", block);
if (send(peer, dp, size + 4, 0) != size + 4) {
if ((n = send(peer, dp, size + 4, 0)) != size + 4) {
syslog(LOG_ERR, "tftpd: write: %m");
goto abort;
}
read_ahead(file, pf->f_convert);
if (block)
read_ahead(file, tftp_blksize, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval); /* read the ack */
n = recv(peer, ackbuf, sizeof (ackbuf), 0);
n = recv(peer, ackbuf, tftp_blksize, 0);
alarm(0);
if (n < 0) {
syslog(LOG_ERR, "tftpd: read: %m");
@ -657,13 +920,19 @@ send_data:
goto abort;
case ACK:
if (ap->th_block == 0) {
etftp = 0;
acklength = 0;
dp = r_init();
goto done;
}
if (ap->th_block == block)
goto done;
if (debug)
syslog(LOG_DEBUG, "Resync ACK %u != %u",
(unsigned int)ap->th_block, block);
/* Re-synchronize with the other side */
(void) synchnet(peer);
(void) synchnet(peer, tftp_blksize);
if (ap->th_block == (block -1))
goto send_data;
default:
@ -676,7 +945,7 @@ done:
if (debug)
syslog(LOG_DEBUG, "Received ACK for block %u", block);
block++;
} while (size == SEGSIZE);
} while (size == tftp_blksize || block == 1);
abort:
(void) fclose(file);
}
@ -692,7 +961,7 @@ justquit(int dummy)
* Receive a file.
*/
void
recvfile(struct formats *pf)
recvfile(struct formats *pf, int etftp, int acklength)
{
volatile unsigned int block;
struct tftphdr *dp;
@ -701,30 +970,35 @@ recvfile(struct formats *pf)
signal(SIGALRM, timer);
dp = w_init();
ap = (struct tftphdr *)ackbuf;
ap = (struct tftphdr *)oackbuf;
block = 0;
do {
timeout = 0;
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)block);
if (etftp == 0) {
ap = (struct tftphdr *)ackbuf;
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)block);
acklength = 4;
}
if (debug)
syslog(LOG_DEBUG, "Sending ACK for block %u\n", block);
block++;
(void) setjmp(timeoutbuf);
send_ack:
if (send(peer, ackbuf, 4, 0) != 4) {
if (send(peer, ap, acklength, 0) != acklength) {
syslog(LOG_ERR, "tftpd: write: %m");
goto abort;
}
write_behind(file, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval);
n = recv(peer, dp, PKTSIZE, 0);
n = recv(peer, dp, tftp_blksize + 4, 0);
alarm(0);
if (n < 0) { /* really? */
syslog(LOG_ERR, "tftpd: read: %m");
goto abort;
}
etftp = 0;
dp->th_opcode = ntohs((u_short)dp->th_opcode);
dp->th_block = ntohs((u_short)dp->th_block);
if (debug)
@ -742,7 +1016,7 @@ send_ack:
syslog(LOG_DEBUG, "Resync %u != %u",
(unsigned int)dp->th_block, block);
/* Re-synchronize with the other side */
(void) synchnet(peer);
(void) synchnet(peer, tftp_blksize);
if (dp->th_block == (block-1))
goto send_ack; /* rexmit */
break;
@ -762,7 +1036,7 @@ done:
else nak(ENOSPACE);
goto abort;
}
} while (size == SEGSIZE);
} while (size == tftp_blksize);
write_behind(file, pf->f_convert);
(void) fclose(file); /* close data file */
@ -797,6 +1071,7 @@ const struct errmsg {
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ EOPTNEG, "Option negotiation failed" },
{ -1, 0 }
};

View File

@ -1,4 +1,4 @@
/* $NetBSD: main.c,v 1.14 2000/12/30 18:00:18 itojun Exp $ */
/* $NetBSD: main.c,v 1.15 2003/06/11 01:44:32 briggs Exp $ */
/*
* Copyright (c) 1983, 1993
@ -40,7 +40,7 @@ __COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\
#if 0
static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93";
#else
__RCSID("$NetBSD: main.c,v 1.14 2000/12/30 18:00:18 itojun Exp $");
__RCSID("$NetBSD: main.c,v 1.15 2003/06/11 01:44:32 briggs Exp $");
#endif
#endif /* not lint */
@ -55,6 +55,7 @@ __RCSID("$NetBSD: main.c,v 1.14 2000/12/30 18:00:18 itojun Exp $");
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <ctype.h>
#include <fcntl.h>
@ -77,6 +78,10 @@ struct sockaddr_storage peeraddr;
int f;
int trace;
int verbose;
int tsize=0;
int tout=0;
int def_blksize=SEGSIZE;
int blksize=SEGSIZE;
int connected;
char mode[32];
char line[LBUFLEN];
@ -98,6 +103,9 @@ void setrexmt __P((int, char **));
void settimeout __P((int, char **));
void settrace __P((int, char **));
void setverbose __P((int, char **));
void setblksize __P((int, char **));
void settsize __P((int, char **));
void settimeoutopt __P((int, char **));
void status __P((int, char **));
char *tail __P((char *));
int main __P((int, char *[]));
@ -121,6 +129,9 @@ struct cmd {
char vhelp[] = "toggle verbose mode";
char thelp[] = "toggle packet tracing";
char tshelp[] = "toggle extended tsize option";
char tohelp[] = "toggle extended timeout option";
char blhelp[] = "set an alternative blocksize (def. 512)";
char chelp[] = "connect to remote tftp";
char qhelp[] = "exit tftp";
char hhelp[] = "print help information";
@ -140,12 +151,15 @@ struct cmd cmdtab[] = {
{ "get", rhelp, get },
{ "quit", qhelp, quit },
{ "verbose", vhelp, setverbose },
{ "blksize", blhelp, setblksize },
{ "tsize", tshelp, settsize },
{ "trace", thelp, settrace },
{ "status", sthelp, status },
{ "binary", bnhelp, setbinary },
{ "ascii", ashelp, setascii },
{ "rexmt", xhelp, setrexmt },
{ "timeout", ihelp, settimeout },
{ "tout", tohelp, settimeoutopt },
{ "?", hhelp, help },
{ 0 }
};
@ -155,10 +169,31 @@ main(argc, argv)
int argc;
char *argv[];
{
int c;
f = -1;
strcpy(mode, "netascii");
signal(SIGINT, intr);
if (argc > 1) {
setprogname(argv[0]);
while ((c = getopt(argc, argv, "e")) != -1) {
switch (c) {
case 'e':
blksize = MAXSEGSIZE;
strcpy(mode, "octet");
tsize = 1;
tout = 1;
break;
default:
printf("usage: %s [-e] host-name [port]\n",
getprogname());
exit(1);
}
}
argc -= optind;
argv += optind;
if (argc >= 1) {
if (setjmp(toplevel) != 0)
exit(0);
setpeer(argc, argv);
@ -177,7 +212,7 @@ setpeer0(host, port)
char *port;
{
struct addrinfo hints, *res0, *res;
int error;
int error, soopt;
struct sockaddr_storage ss;
char *cause = "unknown";
@ -222,6 +257,22 @@ setpeer0(host, port)
break;
}
if (f >= 0) {
soopt = 65536;
if (setsockopt(f, SOL_SOCKET, SO_SNDBUF, &soopt, sizeof(soopt))
< 0) {
close(f);
f = -1;
cause = "setsockopt SNDBUF";
}
if (setsockopt(f, SOL_SOCKET, SO_RCVBUF, &soopt, sizeof(soopt))
< 0) {
close(f);
f = -1;
cause = "setsockopt RCVBUF";
}
}
if (f < 0)
warn("%s", cause);
else {
@ -245,7 +296,7 @@ setpeer(argc, argv)
char *argv[];
{
if (argc < 2) {
if (argc < 1) {
strcpy(line, "Connect ");
printf("(to) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
@ -253,14 +304,14 @@ setpeer(argc, argv)
argc = margc;
argv = margv;
}
if ((argc < 2) || (argc > 3)) {
printf("usage: %s host-name [port]\n", argv[0]);
if ((argc < 1) || (argc > 2)) {
printf("usage: %s [-e] host-name [port]\n", getprogname());
return;
}
if (argc == 2)
setpeer0(argv[1], NULL);
if (argc == 1)
setpeer0(argv[0], NULL);
else
setpeer0(argv[1], argv[2]);
setpeer0(argv[0], argv[1]);
}
struct modes {
@ -506,6 +557,33 @@ getusage(s)
printf(" %s file file ... file if connected\n", s);
}
void
setblksize(argc, argv)
int argc;
char *argv[];
{
int t;
if (argc < 2) {
strcpy(line, "blksize ");
printf("(blksize) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if (argc != 2) {
printf("usage: %s value\n", argv[0]);
return;
}
t = atoi(argv[1]);
if (t < 8 || t > 65464)
printf("%s: bad value\n", argv[1]);
else
blksize = t;
}
int def_rexmtval = TIMEOUT;
int rexmtval = TIMEOUT;
void
@ -749,3 +827,21 @@ setverbose(argc, argv)
verbose = !verbose;
printf("Verbose mode %s.\n", verbose ? "on" : "off");
}
void
settsize(argc, argv)
int argc;
char **argv;
{
tsize = !tsize;
printf("Tsize mode %s.\n", tsize ? "on" : "off");
}
void
settimeoutopt(argc, argv)
int argc;
char **argv;
{
tout = !tout;
printf("Timeout option %s.\n", tout ? "on" : "off");
}

View File

@ -1,4 +1,4 @@
.\" $NetBSD: tftp.1,v 1.15 2003/02/25 10:35:57 wiz Exp $
.\" $NetBSD: tftp.1,v 1.16 2003/06/11 01:44:32 briggs Exp $
.\"
.\" Copyright (c) 1990, 1993, 1994
.\" The Regents of the University of California. All rights reserved.
@ -41,7 +41,9 @@
.Nd trivial file transfer program
.Sh SYNOPSIS
.Nm
.Op Fl e
.Op Ar host
.Op Ar port
.Sh DESCRIPTION
.Nm
is the user interface to the Internet
@ -50,13 +52,27 @@ is the user interface to the Internet
which allows users to transfer files to and from a remote machine.
The remote
.Ar host
(and optional
.Ar port )
may be specified on the command line, in which case
.Nm
uses
.Ar host
as the default host for future transfers (see the
(and
.Ar port )
as the default for future transfers (see the
.Cm connect
command below).
.Pp
The optional
.Fl e
argument sets a binary transfer mode as well as setting the extended options
as if
.Cm tout ,
.Cm tsize ,
and
.Cm blksize 65464 ,
had been given.
.Sh COMMANDS
Once
.Nm
@ -74,6 +90,19 @@ Shorthand for "mode ascii"
.It Cm binary
Shorthand for "mode binary"
.Pp
.It Cm blksize Ar blk-size
Set the tftp blksize option to
.Ar blk-size
octets (8-bit bytes). Since the number of blocks in a tftp
.Cm get
or
.Cm put
is 65535, the default block size of 512 bytes only allows a maximum of
just under 32 megabytes to be transferred. The value given for
.Ar blk-size
must be between 8 and 65464, inclusive.
Note that many servers will not respect this option.
.Pp
.It Cm connect Ar host-name Op Ar port
Set the
.Ar host
@ -158,9 +187,20 @@ Show current status.
.It Cm timeout Ar total-transmission-timeout
Set the total transmission timeout, in seconds.
.Pp
.It Cm tout
Toggle the tftp "timeout" option. If enabled, the client will pass its
.Ar retransmission-timeout
to the server.
Note that many servers will not respect this option.
.Pp
.It Cm trace
Toggle packet tracing.
.Pp
.It Cm tsize
Toggle the tftp "tsize" option. If enabled, the client will pass and
request the filesize of a file at the beginning of a file transfer.
Note that many servers will not respect this option.
.Pp
.It Cm verbose
Toggle verbose mode.
.El
@ -170,6 +210,9 @@ The
command appeared in
.Bx 4.3 .
IPv6 support was implemented by WIDE/KAME project in 1999.
TFTP options were implemented by Wasabi Systems, Inc., in 2003,
and first appeared in
.Nx 2.0 .
.Sh SECURITY CONSIDERATIONS
Because there is no user-login or validation within
the

View File

@ -1,4 +1,4 @@
/* $NetBSD: tftp.c,v 1.16 2003/02/01 16:42:31 wiz Exp $ */
/* $NetBSD: tftp.c,v 1.17 2003/06/11 01:44:32 briggs Exp $ */
/*
* Copyright (c) 1983, 1993
@ -38,7 +38,7 @@
#if 0
static char sccsid[] = "@(#)tftp.c 8.1 (Berkeley) 6/6/93";
#else
__RCSID("$NetBSD: tftp.c,v 1.16 2003/02/01 16:42:31 wiz Exp $");
__RCSID("$NetBSD: tftp.c,v 1.17 2003/06/11 01:44:32 briggs Exp $");
#endif
#endif /* not lint */
@ -48,7 +48,9 @@ __RCSID("$NetBSD: tftp.c,v 1.16 2003/02/01 16:42:31 wiz Exp $");
* TFTP User Program -- Protocol Machines
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
@ -60,6 +62,7 @@ __RCSID("$NetBSD: tftp.c,v 1.16 2003/02/01 16:42:31 wiz Exp $");
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
@ -71,17 +74,21 @@ extern struct sockaddr_storage peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern int trace;
extern int verbose;
extern int def_rexmtval;
extern int rexmtval;
extern int maxtimeout;
extern int tsize;
extern int tout;
extern int def_blksize;
extern int blksize;
#define PKTSIZE SEGSIZE+4
char ackbuf[PKTSIZE];
int timeout;
jmp_buf toplevel;
jmp_buf timeoutbuf;
static void nak __P((int, struct sockaddr *));
static int makerequest __P((int, const char *, struct tftphdr *, const char *));
static int makerequest __P((int, const char *, struct tftphdr *, const char *, off_t));
static void printstats __P((const char *, unsigned long));
static void startclock __P((void));
static void stopclock __P((void));
@ -89,6 +96,57 @@ static void timer __P((int));
static void tpacket __P((const char *, struct tftphdr *, int));
static int cmpport __P((struct sockaddr *, struct sockaddr *));
static void get_options(struct tftphdr *, int);
static void
get_options(struct tftphdr *ap, int size)
{
unsigned long val;
char *opt, *endp, *nextopt, *valp;
int l;
size -= 2; /* skip over opcode */
opt = ap->th_stuff;
endp = opt + size - 1;
*endp = '\0';
while (opt < endp) {
l = strlen(opt) + 1;
valp = opt + l;
if (valp < endp) {
val = strtoul(valp, NULL, 10);
l = strlen(valp) + 1;
nextopt = valp + l;
if (val == ULONG_MAX && errno == ERANGE) {
/* Report illegal value */
opt = nextopt;
continue;
}
} else {
/* Badly formed OACK */
break;
}
if (strcmp(opt, "tsize") == 0) {
/* cool, but we'll ignore it */
} else if (strcmp(opt, "timeout") == 0) {
if (val >= 1 && val <= 255) {
rexmtval = val;
} else {
/* Report error? */
}
} else if (strcmp(opt, "blksize") == 0) {
if (val >= 8 && val <= MAXSEGSIZE) {
blksize = val;
} else {
/* Report error? */
}
} else {
/* unknown option */
}
opt = nextopt;
}
}
/*
* Send the requested file.
*/
@ -105,6 +163,8 @@ sendfile(fd, name, mode)
volatile int size, convert;
volatile unsigned long amount;
struct sockaddr_storage from;
struct stat sbuf;
off_t filesize=0;
int fromlen;
FILE *file;
struct sockaddr_storage peer;
@ -113,6 +173,13 @@ sendfile(fd, name, mode)
startclock(); /* start stat's clock */
dp = r_init(); /* reset fillbuf/read-ahead code */
ap = (struct tftphdr *)ackbuf;
if (tsize) {
if (fstat(fd, &sbuf) == 0) {
filesize = sbuf.st_size;
} else {
filesize = -1ULL;
}
}
file = fdopen(fd, "r");
convert = !strcmp(mode, "netascii");
block = 0;
@ -123,10 +190,10 @@ sendfile(fd, name, mode)
signal(SIGALRM, timer);
do {
if (block == 0)
size = makerequest(WRQ, name, dp, mode) - 4;
size = makerequest(WRQ, name, dp, mode, filesize) - 4;
else {
/* size = read(fd, dp->th_data, SEGSIZE); */
size = readit(file, &dp, convert);
size = readit(file, &dp, blksize, convert);
if (size < 0) {
nak(errno + 100, (struct sockaddr *)&peer);
break;
@ -145,7 +212,8 @@ send_data:
warn("sendto");
goto abort;
}
read_ahead(file, convert);
if (block)
read_ahead(file, blksize, convert);
for ( ; ; ) {
alarm(rexmtval);
do {
@ -179,13 +247,24 @@ send_data:
if (ap->th_opcode == ACK) {
int j;
if (ap->th_block == 0) {
/*
* If the extended options are enabled,
* the server just refused 'em all.
* The only one that _really_
* matters is blksize, but we'll
* clear timeout, too.
*/
blksize = def_blksize;
rexmtval = def_rexmtval;
}
if (ap->th_block == block) {
break;
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
j = synchnet(f, blksize+4);
if (j && trace) {
printf("discarded %d packets\n",
j);
@ -194,11 +273,19 @@ send_data:
goto send_data;
}
}
if (ap->th_opcode == OACK) {
if (block == 0) {
blksize = def_blksize;
rexmtval = def_rexmtval;
get_options(ap, n);
break;
}
}
}
if (block > 0)
amount += size;
block++;
} while (size == SEGSIZE || block == 1);
} while (size == blksize || block == 1);
abort:
fclose(file);
stopclock();
@ -217,12 +304,12 @@ recvfile(fd, name, mode)
{
struct tftphdr *ap;
struct tftphdr *dp;
int n;
int n, oack=0;
volatile unsigned int block;
volatile int size, firsttrip;
volatile unsigned long amount;
struct sockaddr_storage from;
int fromlen;
int fromlen, readlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
struct sockaddr_storage peer;
@ -242,11 +329,13 @@ recvfile(fd, name, mode)
signal(SIGALRM, timer);
do {
if (firsttrip) {
size = makerequest(RRQ, name, ap, mode);
size = makerequest(RRQ, name, ap, mode, 0);
readlen = PKTSIZE;
firsttrip = 0;
} else {
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)(block));
readlen = blksize+4;
size = 4;
block++;
}
@ -266,7 +355,7 @@ send_ack:
alarm(rexmtval);
do {
fromlen = sizeof(from);
n = recvfrom(f, dp, PKTSIZE, 0,
n = recvfrom(f, dp, readlen, 0,
(struct sockaddr *)&from, &fromlen);
} while (n <= 0);
alarm(0);
@ -295,13 +384,18 @@ send_ack:
if (dp->th_opcode == DATA) {
int j;
if (dp->th_block == 1 && !oack) {
/* no OACK, revert to defaults */
blksize = def_blksize;
rexmtval = def_rexmtval;
}
if (dp->th_block == block) {
break; /* have next packet */
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
j = synchnet(f, blksize);
if (j && trace) {
printf("discarded %d packets\n", j);
}
@ -309,6 +403,19 @@ send_ack:
goto send_ack; /* resend ack */
}
}
if (dp->th_opcode == OACK) {
if (block == 1) {
oack = 1;
blksize = def_blksize;
rexmtval = def_rexmtval;
get_options(dp, n);
ap->th_opcode = htons(ACK);
ap->th_block = 0;
readlen = blksize+4;
size = 4;
goto send_ack;
}
}
}
/* size = write(fd, dp->th_data, n - 4); */
size = writeit(file, &dp, n - 4, convert);
@ -317,7 +424,7 @@ send_ack:
break;
}
amount += size;
} while (size == SEGSIZE);
} while (size == blksize || block == 1);
abort: /* ok to ack, since user */
ap->th_opcode = htons((u_short)ACK); /* has seen err msg */
ap->th_block = htons((u_short)block);
@ -331,11 +438,12 @@ abort: /* ok to ack, since user */
}
static int
makerequest(request, name, tp, mode)
makerequest(request, name, tp, mode, filesize)
int request;
const char *name;
struct tftphdr *tp;
const char *mode;
off_t filesize;
{
char *cp;
@ -351,6 +459,30 @@ makerequest(request, name, tp, mode)
strcpy(cp, mode);
cp += strlen(mode);
*cp++ = '\0';
if (tsize) {
strcpy(cp, "tsize");
cp += strlen(cp);
*cp++ = '\0';
sprintf(cp, "%lu", (unsigned long) filesize);
cp += strlen(cp);
*cp++ = '\0';
}
if (tout) {
strcpy(cp, "timeout");
cp += strlen(cp);
*cp++ = '\0';
sprintf(cp, "%d", rexmtval);
cp += strlen(cp);
*cp++ = '\0';
}
if (blksize != SEGSIZE) {
strcpy(cp, "blksize");
cp += strlen(cp);
*cp++ = '\0';
sprintf(cp, "%d", blksize);
cp += strlen(cp);
*cp++ = '\0';
}
return (cp - (char *)tp);
}
@ -366,6 +498,7 @@ const struct errmsg {
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ EOPTNEG, "Option negotiation failed" },
{ -1, 0 }
};
@ -413,11 +546,12 @@ tpacket(s, tp, n)
int n;
{
static char *opcodes[] =
{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
char *cp, *file;
{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
char *cp, *file, *endp, *opt, *spc;
u_short op = ntohs(tp->th_opcode);
int i, o;
if (op < RRQ || op > ERROR)
if (op < RRQ || op > OACK)
printf("%s opcode=%x ", s, op);
else
printf("%s %s ", s, opcodes[op]);
@ -431,9 +565,26 @@ tpacket(s, tp, n)
#else
cp = (void *) &tp->th_stuff;
#endif
endp = cp + n - 1;
if (*endp != '\0') { /* Shouldn't happen, but... */
*endp = '\0';
}
file = cp;
cp = strchr(cp, '\0');
printf("<file=%s, mode=%s>\n", file, cp + 1);
cp = strchr(cp, '\0') + 1;
printf("<file=%s, mode=%s", file, cp);
cp = strchr(cp, '\0') + 1;
o = 0;
while (cp < endp) {
i = strlen(cp) + 1;
if (o) {
printf(", %s=%s", opt, cp);
} else {
opt = cp;
}
o = (o+1) % 2;
cp += i;
}
printf(">\n");
break;
case DATA:
@ -447,6 +598,30 @@ tpacket(s, tp, n)
case ERROR:
printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
break;
case OACK:
o = 0;
n -= 2;
cp = tp->th_stuff;
endp = cp + n - 1;
if (*endp != '\0') { /* Shouldn't happen, but... */
*endp = '\0';
}
printf("<");
spc = "";
while (cp < endp) {
i = strlen(cp) + 1;
if (o) {
printf("%s%s=%s", spc, opt, cp);
spc = ", ";
} else {
opt = cp;
}
o = (o+1) % 2;
cp += i;
}
printf(">\n");
break;
}
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: tftpsubs.c,v 1.6 1999/07/12 20:19:21 itojun Exp $ */
/* $NetBSD: tftpsubs.c,v 1.7 2003/06/11 01:44:32 briggs Exp $ */
/*
* Copyright (c) 1983, 1993
@ -38,7 +38,7 @@
#if 0
static char sccsid[] = "@(#)tftpsubs.c 8.1 (Berkeley) 6/6/93";
#else
__RCSID("$NetBSD: tftpsubs.c,v 1.6 1999/07/12 20:19:21 itojun Exp $");
__RCSID("$NetBSD: tftpsubs.c,v 1.7 2003/06/11 01:44:32 briggs Exp $");
#endif
#endif /* not lint */
@ -64,11 +64,9 @@ __RCSID("$NetBSD: tftpsubs.c,v 1.6 1999/07/12 20:19:21 itojun Exp $");
#include "tftpsubs.h"
#define PKTSIZE SEGSIZE+4 /* should be moved to tftp.h */
struct bf {
int counter; /* size of data in buffer, or flag */
char buf[PKTSIZE]; /* room for data packet */
char buf[MAXPKTSIZE]; /* room for data packet */
} bfs[2];
/* Values for bf.counter */
@ -85,8 +83,17 @@ int prevchar = -1; /* putbuf: previous char (cr check) */
static struct tftphdr *rw_init __P((int));
struct tftphdr *w_init() { return rw_init(0); } /* write-behind */
struct tftphdr *r_init() { return rw_init(1); } /* read-ahead */
struct tftphdr *
w_init() /* write-behind */
{
return rw_init(0);
}
struct tftphdr *
r_init() /* read-ahead */
{
return rw_init(1);
}
static struct tftphdr *
rw_init(x) /* init for either read-ahead or write-behind */
@ -105,9 +112,10 @@ rw_init(x) /* init for either read-ahead or write-behind */
Free it and return next buffer filled with data.
*/
int
readit(file, dpp, convert)
readit(file, dpp, amt, convert)
FILE *file; /* file opened for read */
struct tftphdr **dpp;
int amt;
int convert; /* if true, convert to ascii */
{
struct bf *b;
@ -117,7 +125,7 @@ readit(file, dpp, convert)
b = &bfs[current]; /* look at new buffer */
if (b->counter == BF_FREE) /* if it's empty */
read_ahead(file, convert); /* fill it */
read_ahead(file, amt, convert); /* fill it */
/* assert(b->counter != BF_FREE);*//* check */
*dpp = (struct tftphdr *)b->buf; /* set caller's ptr */
return b->counter;
@ -128,8 +136,9 @@ readit(file, dpp, convert)
* conversions are lf -> cr,lf and cr -> cr, nul
*/
void
read_ahead(file, convert)
read_ahead(file, amt, convert)
FILE *file; /* file opened for read */
int amt; /* number of bytes to read */
int convert; /* if true, convert to ascii */
{
int i;
@ -146,12 +155,12 @@ read_ahead(file, convert)
dp = (struct tftphdr *)b->buf;
if (convert == 0) {
b->counter = read(fileno(file), dp->th_data, SEGSIZE);
b->counter = read(fileno(file), dp->th_data, amt);
return;
}
p = dp->th_data;
for (i = 0 ; i < SEGSIZE; i++) {
for (i = 0 ; i < amt; i++) {
if (newline) {
if (prevchar == '\n')
c = '\n'; /* lf to cr,lf */
@ -257,8 +266,9 @@ skipit:
*/
int
synchnet(f)
synchnet(f, bsize)
int f; /* socket to flush */
int bsize; /* size of buffer to sync */
{
int i, j = 0;
char rbuf[PKTSIZE];

View File

@ -1,4 +1,4 @@
/* $NetBSD: tftpsubs.h,v 1.2 1994/12/08 09:51:32 jtc Exp $ */
/* $NetBSD: tftpsubs.h,v 1.3 2003/06/11 01:44:32 briggs Exp $ */
/*
* Copyright (c) 1993
@ -40,10 +40,10 @@
* server.
*/
struct tftphdr *r_init __P((void));
void read_ahead __P((FILE *, int));
int readit __P((FILE *, struct tftphdr **, int));
void read_ahead __P((FILE *, int, int));
int readit __P((FILE *, struct tftphdr **, int, int));
int synchnet __P((int));
int synchnet __P((int, int));
struct tftphdr *w_init __P((void));
int write_behind __P((FILE *, int));