From esk@ira.uka.de  Fri Sep 21 10:13:13 2001
Return-Path: <esk@ira.uka.de>
Received: from iraun2.uka.de (iraun2.uka.de [129.13.10.91])
	by hub.freebsd.org (Postfix) with ESMTP id 1507E37B420
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 21 Sep 2001 10:13:13 -0700 (PDT)
Received: from i30nb2.ira.uka.de ([129.13.30.52])
	by iraun2.uka.de with esmtp (Exim 3.30 #7 (Debian))
	id 15kTrU-0006AD-00
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 21 Sep 2001 19:13:12 +0200
Received: (from esk@localhost)
	by i30nb2.ira.uka.de (8.11.5/8.11.3) id f8LHClv65384;
	Fri, 21 Sep 2001 19:12:47 +0200 (CEST)
	(envelope-from esk)
Message-Id: <200109211712.f8LHClv65384@i30nb2.ira.uka.de>
Date: Fri, 21 Sep 2001 19:12:47 +0200 (CEST)
From: Espen Skoglund <esk@ira.uka.de>
Reply-To: esk@ira.uka.de
To: FreeBSD-gnats-submit@freebsd.org
Subject: Patch: tftpd extension --- RFC2349
X-Send-Pr-Version: 3.2

>Number:         30710
>Category:       bin
>Synopsis:       Patch: tftpd extension --- RFC2349
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Sep 21 10:20:00 PDT 2001
>Closed-Date:    Thu Sep 27 13:50:23 PDT 2001
>Last-Modified:  Thu Sep 27 13:50:33 PDT 2001
>Originator:     Espen Skoglund
>Release:        FreeBSD 4.4-PRERELEASE i386
>Organization:
Karlsruhe University
>Environment:


>Description:

RFC2349 <URL:http://www.hypermail.org/rfcs/rfc2349.html> adds support
for negotiation of timeout and file size to the tftp protocol.  This
is *required* by some firmware like EFI boot managers (at least on
HP i2000 Itanium servers) in order to boot an image using tftp.  The
attached patch implements the RFC, and in doing so also implements
RFC2347; a generic tftp option extension.

The patch is only tested with -STABLE, but given that tftpd.c is not
changed in -CURRENT, I see no reason why it shouldn't work there.

>How-To-Repeat:


>Fix:

--- libexec/tftpd/tftpd.c.orig	Fri Sep 21 18:32:14 2001
+++ libexec/tftpd/tftpd.c	Fri Sep 21 19:07:03 2001
@@ -79,10 +79,11 @@
 #include "tftpsubs.h"
 
 #define	TIMEOUT		5
+#define	MAX_TIMEOUTS	5
 
 int	peer;
 int	rexmtval = TIMEOUT;
-int	maxtimeout = 5*TIMEOUT;
+int	max_rexmtval = 2*TIMEOUT;
 
 #define	PKTSIZE	SEGSIZE+4
 char	buf[PKTSIZE];
@@ -110,6 +111,7 @@
 
 static char *errtomsg __P((int));
 static void  nak __P((int));
+static void  oack __P(());
 
 int
 main(argc, argv)
@@ -311,6 +313,21 @@
 	{ 0 }
 };
 
+struct options {
+	char	*o_type;
+	char	*o_request;
+	int	o_reply;	/* turn into union if need be */
+} options[] = {
+	{ "tsize" },		/* OPT_TSIZE */
+	{ "timeout" },		/* OPT_TIMEOUT */
+	{ NULL }
+};
+
+enum opt_enum {
+	OPT_TSIZE = 0,
+	OPT_TIMEOUT,
+};
+
 /*
  * Handle initial connection protocol.
  */
@@ -320,9 +337,9 @@
 	int size;
 {
 	register char *cp;
-	int first = 1, ecode;
+	int i, first = 1, has_options = 0, ecode;
 	register struct formats *pf;
-	char *filename, *mode;
+	char *filename, *mode, *option, *ccp;
 
 	filename = cp = tp->th_stuff;
 again:
@@ -350,7 +367,40 @@
 		nak(EBADOP);
 		exit(1);
 	}
+	while (++cp < buf + size) {
+		for (i = 2, ccp = cp; i > 0; ccp++) {
+			if (ccp >= buf + size) {
+				nak(EBADOP);
+				exit(1);
+			} else if (*ccp == '\0')
+				i--;
+		}
+		for (option = cp; *cp; cp++)
+			if (isupper(*cp))
+				*cp = tolower(*cp);
+		for (i = 0; options[i].o_type != NULL; i++)
+			if (strcmp(option, options[i].o_type) == 0) {
+				options[i].o_request = ++cp;
+				has_options = 1;
+			}
+		cp = ccp-1;
+	}
+
+	if (options[OPT_TIMEOUT].o_request) {
+		int to = atoi(options[OPT_TIMEOUT].o_request);
+		if (to < 1 || to > 255) {
+			nak(EBADOP);
+			exit(1);
+		}
+		else if (to <= max_rexmtval)
+			options[OPT_TIMEOUT].o_reply = rexmtval = to;
+		else
+			options[OPT_TIMEOUT].o_request = NULL;
+	}
+
 	ecode = (*pf->f_validate)(&filename, tp->th_opcode);
+	if (has_options)
+		oack();
 	if (logging) {
 		char host[MAXHOSTNAMELEN];
 
@@ -468,6 +518,14 @@
 			return (err);
 		*filep = filename = pathname;
 	}
+	if (options[OPT_TSIZE].o_request) {
+		if (mode == RRQ) 
+			options[OPT_TSIZE].o_reply = stbuf.st_size;
+		else
+			/* XXX Allows writes of all sizes. */
+			options[OPT_TSIZE].o_reply =
+				atoi(options[OPT_TSIZE].o_request);
+	}
 	fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC);
 	if (fd < 0)
 		return (errno + 100);
@@ -478,15 +536,13 @@
 	return (0);
 }
 
-int	timeout;
+int	timeouts;
 jmp_buf	timeoutbuf;
 
 void
 timer()
 {
-
-	timeout += rexmtval;
-	if (timeout >= maxtimeout)
+	if (++timeouts > MAX_TIMEOUTS)
 		exit(1);
 	longjmp(timeoutbuf, 1);
 }
@@ -515,7 +571,7 @@
 		}
 		dp->th_opcode = htons((u_short)DATA);
 		dp->th_block = htons((u_short)block);
-		timeout = 0;
+		timeouts = 0;
 		(void)setjmp(timeoutbuf);
 
 send_data:
@@ -578,7 +634,7 @@
 	ap = (struct tftphdr *)ackbuf;
 	block = 0;
 	do {
-		timeout = 0;
+		timeouts = 0;
 		ap->th_opcode = htons((u_short)ACK);
 		ap->th_block = htons((u_short)block);
 		block++;
@@ -651,6 +707,7 @@
 	{ EBADID,	"Unknown transfer ID" },
 	{ EEXISTS,	"File already exists" },
 	{ ENOUSER,	"No such user" },
+	{ EOPTNEG,	"Option negotiation" },
 	{ -1,		0 }
 };
 
@@ -699,4 +756,58 @@
 	length += 5;
 	if (send(peer, buf, length, 0) != length)
 		syslog(LOG_ERR, "nak: %m");
+}
+
+/*
+ * Send an oack packet (option acknowledgement).
+ */
+static void
+oack()
+{
+	struct tftphdr *tp, *ap;
+	int size, i, n;
+	char *bp;
+
+	tp = (struct tftphdr *)buf;
+	bp = buf + 2;
+	size = sizeof(buf) - 2;
+	tp->th_opcode = htons((u_short)OACK);
+	for (i = 0; options[i].o_type != NULL; i++) {
+		if (options[i].o_request) {
+			n = snprintf(bp, size, "%s%c%d", options[i].o_type,
+				     0, options[i].o_reply);
+			bp += n+1;
+			size -= n+1;
+			if (size < 0) {
+				syslog(LOG_ERR, "oack: buffer overflow");
+				exit(1);
+			}
+		}
+	}
+	size = bp - buf;
+	ap = (struct tftphdr *)ackbuf;
+	signal(SIGALRM, timer);
+	timeouts = 0;
+
+	(void)setjmp(timeoutbuf);
+	if (send(peer, buf, size, 0) != size) {
+		syslog(LOG_INFO, "oack: %m");
+		exit(1);
+	}
+
+	for (;;) {
+		alarm(rexmtval);
+		n = recv(peer, ackbuf, sizeof (ackbuf), 0);
+		alarm(0);
+		if (n < 0) {
+			syslog(LOG_ERR, "recv: %m");
+			exit(1);
+		}
+		ap->th_opcode = ntohs((u_short)ap->th_opcode);
+		ap->th_block = ntohs((u_short)ap->th_block);
+		if (ap->th_opcode == ERROR)
+			exit(1);
+		if (ap->th_opcode == ACK && ap->th_block == 0)
+			break;
+	}
 }
--- include/arpa/tftp.h.orig	Fri Sep 21 18:42:39 2001
+++ include/arpa/tftp.h	Fri Sep 21 18:43:47 2001
@@ -49,6 +49,7 @@
 #define	DATA	03			/* data packet */
 #define	ACK	04			/* acknowledgement */
 #define	ERROR	05			/* error code */
+#define	OACK	06			/* option acknowledgement */
 
 struct	tftphdr {
 	unsigned short	th_opcode;		/* packet type */
@@ -76,5 +77,6 @@
 #define	EBADID		5		/* unknown transfer ID */
 #define	EEXISTS		6		/* file already exists */
 #define	ENOUSER		7		/* no such user */
+#define	EOPTNEG		8		/* option negotiation failed */
 
 #endif /* !_TFTP_H_ */
>Release-Note:
>Audit-Trail:
State-Changed-From-To: open->closed 
State-Changed-By: obrien 
State-Changed-When: Thu Sep 27 13:50:23 PDT 2001 
State-Changed-Why:  
committed.  Thanks!! 

http://www.FreeBSD.org/cgi/query-pr.cgi?pr=30710 
>Unformatted:
