Bug 23725 - current tftp clients / ?servers? do not handle files greater then 32 MBytes properly
Summary: current tftp clients / ?servers? do not handle files greater then 32 MBytes p...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Red Hat Linux
Classification: Retired
Component: tftp
Version: 7.0
Hardware: i386
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Helge Deller
QA Contact: David Lawrence
URL: N/A
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2001-01-10 20:07 UTC by Larry J. Kopenkoskey
Modified: 2007-04-18 16:30 UTC (History)
1 user (show)

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2001-02-08 15:19:26 UTC
Embargoed:


Attachments (Terms of Use)
tftp.c diff file (1.88 KB, patch)
2001-01-11 23:17 UTC, Larry J. Kopenkoskey
no flags Details | Diff

Description Larry J. Kopenkoskey 2001-01-10 20:07:19 UTC
The TFTP client, and possibly server, doesn't handle file sizes larger then
32 MBytes properly. This is attributed to the variable callouts (int
instead of short int, etc) and truncation of like, but different sized,
variables.

The current implementation will transfer 32 MBytes (or one block size less
then 32 MBytes) and then time out. The following tftp.c file (a messy hack,
with lies) produces a fix for this problem, that with limited testing seems
to work well.

-----------------modified tftp.c-----------------
/*
 * Copyright (c) 1983 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.
 */

/*
 * From: @(#)tftp.c	5.10 (Berkeley) 3/1/91
 */
char tftp_rcsid[] = 
  "$Id: tftp.c,v 1.8 1999/12/12 18:05:06 dholland Exp $";

/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */

/*
 * TFTP User Program -- Protocol Machines
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
/* #include <netinet/ip.h> <--- unused? */
#include <arpa/tftp.h>

#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <setjmp.h>
#include <unistd.h>
#include <string.h>

#include "tftpsubs.h"

#include "../version.h"

extern  struct sockaddr_in s_inn;  /* filled in by main */
extern  int     f;                      /* the opened socket */
extern  int     trace;
extern  int     verbose;
extern  int     rexmtval;
extern  int     maxtimeout;
extern sigjmp_buf toplevel;
void sendfile(int fd, char *name, char *modestr);
void recvfile(int fd, char *name, char *modestr);


#define PKTSIZE    SEGSIZE+4
static char ackbuf[PKTSIZE];
static int timeout;
static sigjmp_buf timeoutbuf;


static int makerequest(int request, char *name, 
		       struct tftphdr *tp, char *mode);
static void nak(int errnor);
static void tpacket(const char *s, struct tftphdr *tp, int n);
static void startclock(void);
static void stopclock(void);
static void printstats(const char *direction, unsigned long amount);

static
void
timer(int signum)
{
	(void)signum;

	timeout += rexmtval;
	if (timeout >= maxtimeout) {
		printf("Transfer timed out.\n");
		siglongjmp(toplevel, -1);
	}
	siglongjmp(timeoutbuf, 1);
}

/*
 * Send the requested file.
 */
void
sendfile(int fd, char *name, char *mode)
{
	register struct tftphdr *ap;       /* data and ack packets */
	struct tftphdr *dp;
	int sendreq = 1;
	volatile unsigned short int block = 0;
	volatile int size = 0;
	int n;
	volatile unsigned long amount = 0;
	struct sockaddr_in from;
	socklen_t fromlen;
	volatile int convert;            /* true if doing nl->crlf conversion */
	FILE *file;

	startclock();           /* start stat's clock */
	dp = r_init();          /* reset fillbuf/read-ahead code */
	ap = (struct tftphdr *)ackbuf;
	file = fdopen(fd, "r");
	convert = !strcmp(mode, "netascii");

	signal(SIGALRM, timer);
	do {
		if (sendreq)
			size = makerequest(WRQ, name, dp, mode) - 4;
		else {
		/*      size = read(fd, dp->th_data, SEGSIZE);   */
			size = readit(file, &dp, convert);
			if (size < 0) {
				nak(errno + 100);
				break;
			}
			dp->th_opcode = htons((u_short)DATA);
			dp->th_block = htons((u_short)block);
		}
		timeout = 0;
		(void) sigsetjmp(timeoutbuf, 1);
send_data:
		if (trace)
			tpacket("sent", dp, size + 4);
		n = sendto(f, dp, size + 4, 0,
		    (struct sockaddr *)&s_inn, sizeof(s_inn));
		if (n != size + 4) {
			perror("tftp: sendto");
			goto abort;
		}
		read_ahead(file, convert);
		for ( ; ; ) {
			alarm(rexmtval);
			do {
				fromlen = sizeof (from);
				n = recvfrom(f, ackbuf, sizeof (ackbuf), 0,
				    (struct sockaddr *)&from, &fromlen);
			} while (n <= 0);
			alarm(0);
			if (n < 0) {
				perror("tftp: recvfrom");
				goto abort;
			}
			s_inn.sin_port = from.sin_port;   /* added */
			if (trace)
				tpacket("received", ap, n);
			/* should verify packet came from server */
			ap->th_opcode = ntohs(ap->th_opcode);
			ap->th_block = ntohs(ap->th_block);
			if (ap->th_opcode == ERROR) {
				printf("Error code %d: %s\n", ap->th_code,
					ap->th_msg);
				goto abort;
			}
			if (ap->th_opcode == ACK) {
				volatile int j = 0;

				if (ap->th_block == block) {
					break;
				}
				/* On an error, try to synchronize
				 * both sides.
				 */
				j = synchnet(f);
				if (j && trace) {
					printf("discarded %d packets\n",
							j);
				}
				if (ap->th_block == (block-1)) {
					goto send_data;
				}
			}
		}
		if (sendreq) {
			sendreq = 0;
			size = SEGSIZE; /* lie to stay in the while loop */
		} else {
			amount += size;
		}
		block++;
	} while (size == SEGSIZE);
abort:
	fclose(file);
	stopclock();
	if (amount > 0)
		printstats("Sent", amount);
}

/*
 * Receive a file.
 */
void
recvfile(int fd, char *name, char *mode)
{
	register struct tftphdr *ap;
	struct tftphdr *dp;
	volatile unsigned short int block = 1;
	volatile int size = 0;
	int n; 
	volatile unsigned long amount = 0;
	struct sockaddr_in from;
	socklen_t fromlen;
	volatile int firsttrip = 1;
	FILE *file;
	volatile int convert;            /* true if converting crlf -> lf */

	startclock();
	dp = w_init();
	ap = (struct tftphdr *)ackbuf;
	file = fdopen(fd, "w");
	convert = !strcmp(mode, "netascii");

	signal(SIGALRM, timer);
	do {
		if (firsttrip) {
			size = makerequest(RRQ, name, ap, mode);
			firsttrip = 0;
		} else {
			ap->th_opcode = htons((u_short)ACK);
			ap->th_block = htons((u_short)(block));
			size = 4;
			block++;
		}
		timeout = 0;
		(void) sigsetjmp(timeoutbuf, 1);
send_ack:
		if (trace)
			tpacket("sent", ap, size);
		if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&s_inn,
		    sizeof (s_inn)) != size) {
			alarm(0);
			perror("tftp: sendto");
			goto abort;
		}
		write_behind(file, convert);
		for ( ; ; ) {
			alarm(rexmtval);
			do  {
				fromlen = sizeof (from);
				n = recvfrom(f, dp, PKTSIZE, 0,
				    (struct sockaddr *)&from, &fromlen);
			} while (n <= 0);
			alarm(0);
			if (n < 0) {
				perror("tftp: recvfrom");
				goto abort;
			}
			s_inn.sin_port = from.sin_port;   /* added */
			if (trace)
				tpacket("received", dp, n);
			/* should verify client address */
			dp->th_opcode = ntohs(dp->th_opcode);
			dp->th_block = ntohs(dp->th_block);
			if (dp->th_opcode == ERROR) {
				printf("Error code %d: %s\n", dp->th_code,
					dp->th_msg);
				goto abort;
			}
			if (dp->th_opcode == DATA) {
				volatile int j = 0;

				if (dp->th_block == block) {
					break;          /* have next packet */
				}
				/* On an error, try to synchronize
				 * both sides.
				 */
				j = synchnet(f);
				if (j && trace) {
					printf("discarded %d packets\n", j);
				}
				if (dp->th_block == (block-1)) {
					goto send_ack;  /* resend ack */
				}
			}
		}
	/*      size = write(fd, dp->th_data, n - 4); */
		size = writeit(file, &dp, n - 4, convert);
		if (size < 0) {
			nak(errno + 100);
			break;
		}
		amount += size;
	} while (size == SEGSIZE);
abort:                                          /* ok to ack, since user */
	ap->th_opcode = htons((u_short)ACK);    /* has seen err msg */
	ap->th_block = htons((u_short)block);
	(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&s_inn, sizeof(s_inn));
	write_behind(file, convert);     /* flush last buffer */
	fclose(file);
	stopclock();
	if (amount > 0)
		printstats("Received", amount);
}

int
makerequest(int request, char *name, struct tftphdr *tp, char *mode)
{
	register char *cp;

	tp->th_opcode = htons((u_short)request);
	cp = tp->th_stuff;
	strcpy(cp, name);
	cp += strlen(name);
	*cp++ = '\0';
	strcpy(cp, mode);
	cp += strlen(mode);
	*cp++ = '\0';
	return (cp - (char *)tp);
}

struct errmsg {
	int e_code;
	const char *e_msg;
} errmsgs[] = {
	{ EUNDEF,	"Undefined error code" },
	{ ENOTFOUND,	"File not found" },
	{ EACCESS,	"Access violation" },
	{ ENOSPACE,	"Disk full or allocation exceeded" },
	{ EBADOP,	"Illegal TFTP operation" },
	{ EBADID,	"Unknown transfer ID" },
	{ EEXISTS,	"File already exists" },
	{ ENOUSER,	"No such user" },
	{ -1,		0 }
};

/*
 * Send a nak packet (error message).
 * Error code passed in is one of the
 * standard TFTP codes, or a UNIX errno
 * offset by 100.
 */
void
nak(int error)
{
	register struct errmsg *pe;
	register struct tftphdr *tp;
	int length;

	tp = (struct tftphdr *)ackbuf;
	tp->th_opcode = htons((u_short)ERROR);
	tp->th_code = htons((u_short)error);
	for (pe = errmsgs; pe->e_code >= 0; pe++)
		if (pe->e_code == error)
			break;
	if (pe->e_code < 0) {
		pe->e_msg = strerror(error - 100);
		tp->th_code = EUNDEF;
	}
	strcpy(tp->th_msg, pe->e_msg);
	length = strlen(pe->e_msg) + 4;
	if (trace)
		tpacket("sent", tp, length);
	if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&s_inn,
	    sizeof (s_inn)) != length)
		perror("nak");
}

static
void
tpacket(const char *s, struct tftphdr *tp, int n)
{
	static const char *opcodes[] =
	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
	register char *cp, *file;
	u_short op = ntohs(tp->th_opcode);

	if (op < RRQ || op > ERROR)
		printf("%s opcode=%x ", s, op);
	else
		printf("%s %s ", s, opcodes[op]);
	switch (op) {

	case RRQ:
	case WRQ:
		n -= 2;
		file = cp = tp->th_stuff;
		cp = cp + strlen(cp);
		printf("<file=%s, mode=%s>\n", file, cp + 1);
		break;

	case DATA:
		printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
		break;

	case ACK:
		printf("<block=%d>\n", ntohs(tp->th_block));
		break;

	case ERROR:
		printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
		break;
	}
}

struct timeval tstart;
struct timeval tstop;
struct timezone zone;

void
startclock(void) {
	gettimeofday(&tstart, &zone);
}

void
stopclock(void) {
	gettimeofday(&tstop, &zone);
}

void
printstats(const char *direction, unsigned long amount)
{
	double delta;
			/* compute delta in 1/10's second units */
	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
		((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
	delta = delta/10.;      /* back to seconds */
	printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
	if (verbose)
		printf(" [%.0f bits/sec]", (amount*8.)/delta);
	putchar('\n');
}

Comment 1 Larry J. Kopenkoskey 2001-01-11 23:17:51 UTC
Created attachment 7467 [details]
tftp.c diff file

Comment 2 Helge Deller 2001-02-08 12:44:04 UTC
AFAIK the TFTP protocol normally uses a blocksize of 512 bytes and indexes the
blocknumber with an unsigned 16bit integer. By multiplying those two sizes I
get:
65536 blocks * 512 bytes/block = 33554432 bytes = 32MB
which seems to be the maximum supported filesize for TFTP.

I will close this bug as "NOTABUG", but if you think that I'm completely wrong,
please don't hesitate to reopen this bug and show me where.


Comment 3 Larry J. Kopenkoskey 2001-02-08 14:37:24 UTC
RFC 1350 does _not_ specify that the block number stops at 0xFFFF, it 
only states that the block number is incremented.  "Each data packet 
has associated with it a block number; block numbers are consecutive 
and begin with one."  There is no inherent maximum file size.

The patch simply fixes the TFTP server math so that it is modulo 
65536.  The server, as it currently exists, uses 32 bit integers for 
the acknowledgement, causing a problem when the number should wrap back 
to zero (i.e. mod 65536).  The patch changes the acknowledgement 
variables to be unsigned 16 integers, fixing the math.


Note You need to log in before you can comment on or make changes to this bug.