diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8 index d21a4f57b747..85335aa014c7 100644 --- a/libexec/tftpd/tftpd.8 +++ b/libexec/tftpd/tftpd.8 @@ -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 diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c index 9d1b5ace994b..9ebbce10a3ad 100644 --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -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 } }; diff --git a/usr.bin/tftp/main.c b/usr.bin/tftp/main.c index 76b77886618f..072086373922 100644 --- a/usr.bin/tftp/main.c +++ b/usr.bin/tftp/main.c @@ -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 #include +#include #include #include @@ -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"); +} diff --git a/usr.bin/tftp/tftp.1 b/usr.bin/tftp/tftp.1 index 1e3307d72711..e66a5d2ae5d4 100644 --- a/usr.bin/tftp/tftp.1 +++ b/usr.bin/tftp/tftp.1 @@ -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 diff --git a/usr.bin/tftp/tftp.c b/usr.bin/tftp/tftp.c index 7a2405ac0f20..2feedeb1ad91 100644 --- a/usr.bin/tftp/tftp.c +++ b/usr.bin/tftp/tftp.c @@ -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 +#include #include +#include #include #include @@ -60,6 +62,7 @@ __RCSID("$NetBSD: tftp.c,v 1.16 2003/02/01 16:42:31 wiz Exp $"); #include #include #include +#include #include #include #include @@ -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("\n", file, cp + 1); + cp = strchr(cp, '\0') + 1; + printf("\n"); break; case DATA: @@ -447,6 +598,30 @@ tpacket(s, tp, n) case ERROR: printf("\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; } } diff --git a/usr.bin/tftp/tftpsubs.c b/usr.bin/tftp/tftpsubs.c index 7f0ccef4dec4..374eecb72fee 100644 --- a/usr.bin/tftp/tftpsubs.c +++ b/usr.bin/tftp/tftpsubs.c @@ -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]; diff --git a/usr.bin/tftp/tftpsubs.h b/usr.bin/tftp/tftpsubs.h index 5fc6316a060c..d7902fe7320b 100644 --- a/usr.bin/tftp/tftpsubs.h +++ b/usr.bin/tftp/tftpsubs.h @@ -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));