Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 4 May 2010 06:19:20 +0000 (UTC)
From:      Warner Losh <imp@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r207608 - head/libexec/tftpd
Message-ID:  <201005040619.o446JKZU025085@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: imp
Date: Tue May  4 06:19:19 2010
New Revision: 207608
URL: http://svn.freebsd.org/changeset/base/207608

Log:
  Go ahead and merge the work edwin@ on tftpd into the tree.  It is a
  lot better than what's in the tree now.  Edwin tested it at a prior
  employer, but can't test it today.  I've found that it works a lot
  better with the various uboot versions that I've used in my embedded
  work.  Here's the pkg-descr from the port that describes the changes:
  
  It all started when we got some new routers, which told me the
  following when trying to upload configuration or download images
  from it: The TFTP server doesn't support the blocksize option.
  
  My curiousity was triggered, it took me some reading of RFCs and
  other documentation to find out what was possible and what could
  be done. Was plain TFTP very simple in its handshake, TFTP with
  options was kind of messy because of its backwards capability: The
  first packet returned could either be an acknowledgement of options,
  or the first data packet.
  
  Going through the source code of src/libexec/tftpd and going through
  the code of src/usr.bin/tftp showed that there was a lot of duplicate
  code, and the addition of options would only increase the amount
  of duplicate code. After all, both the client and the server can
  act as a sender and receiver.
  
  At the end, it ended up with a nearly complete rewrite of the tftp
  client and server. It has been tested against the following TFTP
  clients and servers:
  
  - Itself (yay!)
  - The standard FreeBSD tftp client and server
  - The Fedora Core 6 tftp client and server
  - Cisco router tftp client
  - Extreme Networks tftp client
  
  It supports the following RFCs:
  
  RFC1350 - THE TFTP PROTOCOL (REVISION 2)
  RFC2347 - TFTP Option Extension
  RFC2348 - TFTP Blocksize Option
  RFC2349 - TFTP Timeout Interval and Transfer Size Options
  RFC3617 - Uniform Resource Identifier (URI) Scheme and Applicability
            Statement for the Trivial File Transfer Protocol (TFTP)
  
  It supports the following unofficial TFTP Options as described at
  http://www.compuphase.com/tftp.htm:
  
  blksize2 - Block size restricted to powers of 2, excluding protocol headers
  rollover - Block counter roll-over (roll back to zero or to one)
  
  From the tftp program point of view the following things are changed:
  
  - New commands: "blocksize", "blocksize2", "rollover" and "options"
  - Development features: "debug" and "packetdrop"
  
  If you try this tftp/tftpd implementation, please let me know if
  it works (or doesn't work) and against which implementaion so I can
  get a list of confirmed working systems.
  
  Author: Edwin Groothuis <edwin@FreeBSD.org>

Modified:
  head/libexec/tftpd/Makefile
  head/libexec/tftpd/tftpd.8
  head/libexec/tftpd/tftpd.c

Modified: head/libexec/tftpd/Makefile
==============================================================================
--- head/libexec/tftpd/Makefile	Tue May  4 06:13:17 2010	(r207607)
+++ head/libexec/tftpd/Makefile	Tue May  4 06:19:19 2010	(r207608)
@@ -2,15 +2,14 @@
 # $FreeBSD$
 
 PROG=	tftpd
-SRCS=	tftpd.c tftpsubs.c
-DPADD=	${LIBUTIL}
-LDADD=	-lutil
-
-WARNS?=	1
+SRCS=	tftpd.c tftp-io.c tftp-utils.c tftp-file.c tftp-transfer.c tftp-options.c
+WARNS=	3
 WFORMAT=0
-
 MAN=	tftpd.8
-CFLAGS+=-I${.CURDIR}/../../usr.bin/tftp
+CFLAGS=-g -Wall
+CFLAGS+=-I${.CURDIR}/../../usr.bin/tftp -I${.CURDIR}/../../libexec/tftpd
 .PATH:	${.CURDIR}/../../usr.bin/tftp
+COPTFLAGS = -O
+LDFLAGS= -lwrap
 
 .include <bsd.prog.mk>

Modified: head/libexec/tftpd/tftpd.8
==============================================================================
--- head/libexec/tftpd/tftpd.8	Tue May  4 06:13:17 2010	(r207607)
+++ head/libexec/tftpd/tftpd.8	Tue May  4 06:19:19 2010	(r207608)
@@ -40,7 +40,7 @@
 .Nd Internet Trivial File Transfer Protocol server
 .Sh SYNOPSIS
 .Nm tftpd
-.Op Fl cClnwW
+.Op Fl cdClnow
 .Op Fl F Ar strftime-format
 .Op Fl s Ar directory
 .Op Fl u Ar user
@@ -150,6 +150,9 @@ compatible format string for the creatio
 .Fl W
 is specified.
 By default the string "%Y%m%d" is used.
+.It Fl d
+Enables debug output.
+If specified twice, it will log DATA and ACK packets too.
 .It Fl l
 Log all requests using
 .Xr syslog 3
@@ -164,6 +167,8 @@ must also be enabled in the syslog confi
 .It Fl n
 Suppress negative acknowledgement of requests for nonexistent
 relative filenames.
+.It Fl o
+Disable support for RFC2347 style TFTP Options.
 .It Fl s Ar directory
 Cause
 .Nm
@@ -240,10 +245,16 @@ and the
 and
 .Fl W
 options were introduced in
-.Fx 8.0 .
+.Fx 7 .
 .Pp
+Support for Timeout Interval and Transfer Size Options (RFC2349)
+was introduced in
+.Fx 5.0 ,
+support for the TFTP Blocksize Option (RFC2348) and the blksize2 option
+was introduced in
+.Fx 7 .
 .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 (RFC2348).
 .Pp
 Many tftp clients will not transfer files over 16744448 octets (32767 blocks).

Modified: head/libexec/tftpd/tftpd.c
==============================================================================
--- head/libexec/tftpd/tftpd.c	Tue May  4 06:13:17 2010	(r207607)
+++ head/libexec/tftpd/tftpd.c	Tue May  4 06:19:19 2010	(r207608)
@@ -41,9 +41,9 @@ static const char copyright[] =
 #if 0
 static char sccsid[] = "@(#)tftpd.c	8.1 (Berkeley) 6/4/93";
 #endif
-static const char rcsid[] =
-  "$FreeBSD$";
 #endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
 
 /*
  * Trivial file transfer protocol server.
@@ -56,43 +56,30 @@ static const char rcsid[] =
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/time.h>
 
 #include <netinet/in.h>
 #include <arpa/tftp.h>
-#include <arpa/inet.h>
 
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <libutil.h>
 #include <netdb.h>
 #include <pwd.h>
-#include <setjmp.h>
-#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <syslog.h>
+#include <tcpd.h>
 #include <unistd.h>
 
-#include "tftpsubs.h"
+#include "tftp-file.h"
+#include "tftp-io.h"
+#include "tftp-utils.h"
+#include "tftp-transfer.h"
+#include "tftp-options.h"
 
-#define	TIMEOUT		5
-#define	MAX_TIMEOUTS	5
-
-int	peer;
-int	rexmtval = TIMEOUT;
-int	max_rexmtval = 2*TIMEOUT;
-
-#define	PKTSIZE	SEGSIZE+4
-char	buf[PKTSIZE];
-char	ackbuf[PKTSIZE];
-struct	sockaddr_storage from;
-
-void	tftp(struct tftphdr *, int);
-static void unmappedaddr(struct sockaddr_in6 *);
+static void	tftp_wrq(int peer, char *, ssize_t);
+static void	tftp_rrq(int peer, char *, ssize_t);
 
 /*
  * Null-terminated directory prefix list for absolute pathname requests and
@@ -112,31 +99,44 @@ static int	ipchroot;
 static int	create_new = 0;
 static char	*newfile_format = "%Y%m%d";
 static int	increase_name = 0;
-static mode_t	mask = S_IWGRP|S_IWOTH;
+static mode_t	mask = S_IWGRP | S_IWOTH;
+
+struct formats;
+static void	tftp_recvfile(int peer, const char *mode);
+static void	tftp_xmitfile(int peer, const char *mode);
+static int	validate_access(int peer, char **, int);
+static char	peername[NI_MAXHOST];
 
-static const char *errtomsg(int);
-static void  nak(int);
-static void  oack(void);
+FILE *file;
 
-static void  timer(int);
-static void  justquit(int);
+struct formats {
+	const char	*f_mode;
+	int	f_convert;
+} formats[] = {
+	{ "netascii",	1 },
+	{ "octet",	0 },
+	{ NULL,		0 }
+};
 
 int
 main(int argc, char *argv[])
 {
 	struct tftphdr *tp;
-	socklen_t fromlen, len;
-	int n;
-	int ch, on;
-	struct sockaddr_storage me;
-	char *chroot_dir = NULL;
-	struct passwd *nobody;
-	const char *chuser = "nobody";
+	int		peer;
+	socklen_t	peerlen, len;
+	ssize_t		n;
+	int		ch;
+	char		*chroot_dir = NULL;
+	struct passwd	*nobody;
+	const char	*chuser = "nobody";
+	char		recvbuffer[MAXPKTSIZE];
+	int		allow_ro = 1, allow_wo = 1;
 
 	tzset();			/* syslog in localtime */
+	acting_as_client = 0;
 
-	openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
-	while ((ch = getopt(argc, argv, "cCF:lns:u:U:wW")) != -1) {
+	tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
+	while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) {
 		switch (ch) {
 		case 'c':
 			ipchroot = 1;
@@ -144,6 +144,12 @@ main(int argc, char *argv[])
 		case 'C':
 			ipchroot = 2;
 			break;
+		case 'd':
+			if (atoi(optarg) != 0)
+				debug += atoi(optarg);
+			else
+				debug |= debug_finds(optarg);
+			break;
 		case 'F':
 			newfile_format = optarg;
 			break;
@@ -153,6 +159,18 @@ main(int argc, char *argv[])
 		case 'n':
 			suppress_naks = 1;
 			break;
+		case 'o':
+			options_rfc_enabled = 0;
+			break;
+		case 'O':
+			options_extra_enabled = 0;
+			break;
+		case 'p':
+			packetdroppercentage = atoi(optarg);
+			tftp_log(LOG_INFO,
+			    "Randomly dropping %d out of 100 packets",
+			    packetdroppercentage);
+			break;
 		case 's':
 			chroot_dir = optarg;
 			break;
@@ -170,7 +188,8 @@ main(int argc, char *argv[])
 			increase_name = 1;
 			break;
 		default:
-			syslog(LOG_WARNING, "ignoring unknown option -%c", ch);
+			tftp_log(LOG_WARNING,
+				"ignoring unknown option -%c", ch);
 		}
 	}
 	if (optind < argc) {
@@ -191,24 +210,31 @@ main(int argc, char *argv[])
 		dirs->len = 1;
 	}
 	if (ipchroot > 0 && chroot_dir == NULL) {
-		syslog(LOG_ERR, "-c requires -s");
+		tftp_log(LOG_ERR, "-c requires -s");
 		exit(1);
 	}
 
 	umask(mask);
 
-	on = 1;
-	if (ioctl(0, FIONBIO, &on) < 0) {
-		syslog(LOG_ERR, "ioctl(FIONBIO): %m");
-		exit(1);
+	{
+		int on = 1;
+		if (ioctl(0, FIONBIO, &on) < 0) {
+			tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno));
+			exit(1);
+		}
 	}
-	fromlen = sizeof (from);
-	n = recvfrom(0, buf, sizeof (buf), 0,
-	    (struct sockaddr *)&from, &fromlen);
+
+	/* Find out who we are talking to and what we are going to do */
+	peerlen = sizeof(peer_sock);
+	n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0,
+	    (struct sockaddr *)&peer_sock, &peerlen);
 	if (n < 0) {
-		syslog(LOG_ERR, "recvfrom: %m");
+		tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno));
 		exit(1);
 	}
+	getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len,
+	    peername, sizeof(peername), NULL, 0, NI_NUMERICHOST);
+
 	/*
 	 * Now that we have read the message out of the UDP
 	 * socket, we fork and exit.  Thus, inetd will go back
@@ -240,9 +266,9 @@ main(int argc, char *argv[])
 				 * than one tftpd being started up to service
 				 * a single request from a single client.
 				 */
-				fromlen = sizeof from;
-				i = recvfrom(0, buf, sizeof (buf), 0,
-				    (struct sockaddr *)&from, &fromlen);
+				peerlen = sizeof peer_sock;
+				i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0,
+				    (struct sockaddr *)&peer_sock, &peerlen);
 				if (i > 0) {
 					n = i;
 				}
@@ -251,7 +277,7 @@ main(int argc, char *argv[])
 		    }
 		}
 		if (pid < 0) {
-			syslog(LOG_ERR, "fork: %m");
+			tftp_log(LOG_ERR, "fork: %s", strerror(errno));
 			exit(1);
 		} else if (pid != 0) {
 			exit(0);
@@ -259,6 +285,55 @@ main(int argc, char *argv[])
 	}
 
 	/*
+	 * See if the client is allowed to talk to me.
+	 * (This needs to be done before the chroot())
+	 */
+	{
+		struct request_info req;
+
+		request_init(&req, RQ_CLIENT_ADDR, peername, 0);
+		request_set(&req, RQ_DAEMON, "tftpd", 0);
+
+		if (hosts_access(&req) == 0) {
+			if (debug&DEBUG_ACCESS)
+				tftp_log(LOG_WARNING,
+				    "Access denied by 'tftpd' entry "
+				    "in /etc/hosts.allow");
+
+			/*
+			 * Full access might be disabled, but maybe the
+			 * client is allowed to do read-only access.
+			 */
+			request_set(&req, RQ_DAEMON, "tftpd-ro", 0);
+			allow_ro = hosts_access(&req);
+
+			request_set(&req, RQ_DAEMON, "tftpd-wo", 0);
+			allow_wo = hosts_access(&req);
+
+			if (allow_ro == 0 && allow_wo == 0) {
+				tftp_log(LOG_WARNING,
+				    "Unauthorized access from %s", peername);
+				exit(1);
+			}
+
+			if (debug&DEBUG_ACCESS) {
+				if (allow_ro)
+					tftp_log(LOG_WARNING,
+					    "But allowed readonly access "
+					    "via 'tftpd-ro' entry");
+				if (allow_wo)
+					tftp_log(LOG_WARNING,
+					    "But allowed writeonly access "
+					    "via 'tftpd-wo' entry");
+			}
+		} else
+			if (debug&DEBUG_ACCESS)
+				tftp_log(LOG_WARNING,
+				    "Full access allowed"
+				    "in /etc/hosts.allow");
+	}
+
+	/*
 	 * Since we exit here, we should do that only after the above
 	 * recvfrom to keep inetd from constantly forking should there
 	 * be a problem.  See the above comment about system clogging.
@@ -271,7 +346,8 @@ main(int argc, char *argv[])
 			struct sockaddr_storage ss;
 			char hbuf[NI_MAXHOST];
 
-			memcpy(&ss, &from, from.ss_len);
+			statret = -1;
+			memcpy(&ss, &peer_sock, peer_sock.ss_len);
 			unmappedaddr((struct sockaddr_in6 *)&ss);
 			getnameinfo((struct sockaddr *)&ss, ss.ss_len,
 				    hbuf, sizeof(hbuf), NULL, 0,
@@ -285,11 +361,12 @@ main(int argc, char *argv[])
 		}
 		/* Must get this before chroot because /etc might go away */
 		if ((nobody = getpwnam(chuser)) == NULL) {
-			syslog(LOG_ERR, "%s: no such user", chuser);
+			tftp_log(LOG_ERR, "%s: no such user", chuser);
 			exit(1);
 		}
 		if (chroot(chroot_dir)) {
-			syslog(LOG_ERR, "chroot: %s: %m", chroot_dir);
+			tftp_log(LOG_ERR, "chroot: %s: %s",
+			    chroot_dir, strerror(errno));
 			exit(1);
 		}
 		chdir("/");
@@ -297,44 +374,56 @@ main(int argc, char *argv[])
 		setuid(nobody->pw_uid);
 	}
 
-	len = sizeof(me);
-	if (getsockname(0, (struct sockaddr *)&me, &len) == 0) {
-		switch (me.ss_family) {
+	len = sizeof(me_sock);
+	if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) {
+		switch (me_sock.ss_family) {
 		case AF_INET:
-			((struct sockaddr_in *)&me)->sin_port = 0;
+			((struct sockaddr_in *)&me_sock)->sin_port = 0;
 			break;
 		case AF_INET6:
-			((struct sockaddr_in6 *)&me)->sin6_port = 0;
+			((struct sockaddr_in6 *)&me_sock)->sin6_port = 0;
 			break;
 		default:
 			/* unsupported */
 			break;
 		}
 	} else {
-		memset(&me, 0, sizeof(me));
-		me.ss_family = from.ss_family;
-		me.ss_len = from.ss_len;
+		memset(&me_sock, 0, sizeof(me_sock));
+		me_sock.ss_family = peer_sock.ss_family;
+		me_sock.ss_len = peer_sock.ss_len;
 	}
-	alarm(0);
 	close(0);
 	close(1);
-	peer = socket(from.ss_family, SOCK_DGRAM, 0);
+	peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0);
 	if (peer < 0) {
-		syslog(LOG_ERR, "socket: %m");
+		tftp_log(LOG_ERR, "socket: %s", strerror(errno));
 		exit(1);
 	}
-	if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
-		syslog(LOG_ERR, "bind: %m");
+	if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) {
+		tftp_log(LOG_ERR, "bind: %s", strerror(errno));
 		exit(1);
 	}
-	if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
-		syslog(LOG_ERR, "connect: %m");
-		exit(1);
-	}
-	tp = (struct tftphdr *)buf;
+
+	tp = (struct tftphdr *)recvbuffer;
 	tp->th_opcode = ntohs(tp->th_opcode);
-	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
-		tftp(tp, n);
+	if (tp->th_opcode == RRQ) {
+		if (allow_ro)
+			tftp_rrq(peer, tp->th_stuff, n - 1);
+		else {
+			tftp_log(LOG_WARNING,
+			    "%s read access denied", peername);
+			exit(1);
+		}
+	}
+	if (tp->th_opcode == WRQ) {
+		if (allow_wo)
+			tftp_wrq(peer, tp->th_stuff, n - 1);
+		else {
+			tftp_log(LOG_WARNING,
+			    "%s write access denied", peername);
+			exit(1);
+		}
+	}
 	exit(1);
 }
 
@@ -369,138 +458,145 @@ reduce_path(char *fn)
 	}
 }
 
-struct formats;
-int	validate_access(char **, int);
-void	xmitfile(struct formats *);
-void	recvfile(struct formats *);
+static char *
+parse_header(int peer, char *recvbuffer, ssize_t size,
+	char **filename, char **mode)
+{
+	char	*cp;
+	int	i;
+	struct formats *pf;
 
-struct formats {
-	const char	*f_mode;
-	int	(*f_validate)(char **, int);
-	void	(*f_send)(struct formats *);
-	void	(*f_recv)(struct formats *);
-	int	f_convert;
-} formats[] = {
-	{ "netascii",	validate_access,	xmitfile,	recvfile, 1 },
-	{ "octet",	validate_access,	xmitfile,	recvfile, 0 },
-#ifdef notdef
-	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
-#endif
-	{ 0,		NULL,			NULL,		NULL,	  0 }
-};
+	*mode = NULL;
+	cp = recvbuffer;
 
-struct options {
-	const char	*o_type;
-	char	*o_request;
-	int	o_reply;	/* turn into union if need be */
-} options[] = {
-	{ "tsize",	NULL, 0 },		/* OPT_TSIZE */
-	{ "timeout",	NULL, 0 },		/* OPT_TIMEOUT */
-	{ NULL,		NULL, 0 }
-};
+	i = get_field(peer, recvbuffer, size);
+	if (i >= PATH_MAX) {
+		tftp_log(LOG_ERR, "Bad option - filename too long");
+		send_error(peer, EBADOP);
+		exit(1);
+	}
+	*filename = recvbuffer;
+	tftp_log(LOG_INFO, "Filename: '%s'", *filename);
+	cp += i;
 
-enum opt_enum {
-	OPT_TSIZE = 0,
-	OPT_TIMEOUT,
-};
+	i = get_field(peer, cp, size);
+	*mode = cp;
+	cp += i;
+
+	/* Find the file transfer mode */
+	for (cp = *mode; *cp; cp++)
+		if (isupper(*cp))
+			*cp = tolower(*cp);
+	for (pf = formats; pf->f_mode; pf++)
+		if (strcmp(pf->f_mode, *mode) == 0)
+			break;
+	if (pf->f_mode == NULL) {
+		tftp_log(LOG_ERR,
+		    "Bad option - Unknown transfer mode (%s)", *mode);
+		send_error(peer, EBADOP);
+		exit(1);
+	}
+	tftp_log(LOG_INFO, "Mode: '%s'", *mode);
+
+	return (cp + 1);
+}
 
 /*
- * Handle initial connection protocol.
+ * WRQ - receive a file from the client
  */
 void
-tftp(struct tftphdr *tp, int size)
+tftp_wrq(int peer, char *recvbuffer, ssize_t size)
 {
 	char *cp;
-	int i, first = 1, has_options = 0, ecode;
-	struct formats *pf;
-	char *filename, *mode, *option, *ccp;
+	int has_options = 0, ecode;
+	char *filename, *mode;
 	char fnbuf[PATH_MAX];
 
-	cp = tp->th_stuff;
-again:
-	while (cp < buf + size) {
-		if (*cp == '\0')
-			break;
-		cp++;
+	cp = parse_header(peer, recvbuffer, size, &filename, &mode);
+	size -= (cp - recvbuffer) + 1;
+
+	strcpy(fnbuf, filename);
+	reduce_path(fnbuf);
+	filename = fnbuf;
+
+	if (size > 0) {
+		if (options_rfc_enabled)
+			has_options = !parse_options(peer, cp, size);
+		else
+			tftp_log(LOG_INFO, "Options found but not enabled");
 	}
-	if (*cp != '\0') {
-		nak(EBADOP);
-		exit(1);
+
+	ecode = validate_access(peer, &filename, WRQ);
+	if (ecode == 0) {
+		if (has_options)
+			send_oack(peer);
+		else
+			send_ack(peer, 0);
 	}
-	i = cp - tp->th_stuff;
-	if (i >= sizeof(fnbuf)) {
-		nak(EBADOP);
-		exit(1);
+	if (logging) {
+		tftp_log(LOG_INFO, "%s: write request for %s: %s", peername,
+			    filename, errtomsg(ecode));
 	}
-	memcpy(fnbuf, tp->th_stuff, i);
-	fnbuf[i] = '\0';
+
+	tftp_recvfile(peer, mode);
+	exit(0);
+}
+
+/*
+ * RRQ - send a file to the client
+ */
+void
+tftp_rrq(int peer, char *recvbuffer, ssize_t size)
+{
+	char *cp;
+	int has_options = 0, ecode;
+	char *filename, *mode;
+	char	fnbuf[PATH_MAX];
+
+	cp = parse_header(peer, recvbuffer, size, &filename, &mode);
+	size -= (cp - recvbuffer) + 1;
+
+	strcpy(fnbuf, filename);
 	reduce_path(fnbuf);
 	filename = fnbuf;
-	if (first) {
-		mode = ++cp;
-		first = 0;
-		goto again;
-	}
-	for (cp = mode; *cp; cp++)
-		if (isupper(*cp))
-			*cp = tolower(*cp);
-	for (pf = formats; pf->f_mode; pf++)
-		if (strcmp(pf->f_mode, mode) == 0)
-			break;
-	if (pf->f_mode == 0) {
-		nak(EBADOP);
-		exit(1);
-	}
-	while (++cp < buf + size) {
-		for (i = 2, ccp = cp; i > 0; ccp++) {
-			if (ccp >= buf + size) {
-				/*
-				 * Don't reject the request, just stop trying
-				 * to parse the option and get on with it.
-				 * Some Apple Open Firmware versions have
-				 * trailing garbage on the end of otherwise
-				 * valid requests.
-				 */
-				goto option_fail;
-			} 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 (size > 0) {
+		if (options_rfc_enabled)
+			has_options = !parse_options(peer, cp, size);
+		else
+			tftp_log(LOG_INFO, "Options found but not enabled");
 	}
 
-option_fail:
-	if (options[OPT_TIMEOUT].o_request) {
-		int to = atoi(options[OPT_TIMEOUT].o_request);
-		if (to < 1 || to > 255) {
-			nak(EBADOP);
-			exit(1);
+	ecode = validate_access(peer, &filename, RRQ);
+	if (ecode == 0) {
+		if (has_options) {
+			int n;
+			char lrecvbuffer[MAXPKTSIZE];
+			struct tftphdr *rp = (struct tftphdr *)lrecvbuffer;
+
+			send_oack(peer);
+			n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE,
+				NULL, timeoutpacket);
+			if (n < 0) {
+				if (debug&DEBUG_SIMPLE)
+					tftp_log(LOG_DEBUG, "Aborting: %s",
+					    rp_strerror(n));
+				return;
+			}
+			if (rp->th_opcode != ACK) {
+				if (debug&DEBUG_SIMPLE)
+					tftp_log(LOG_DEBUG,
+					    "Expected ACK, got %s on OACK",
+					    packettype(rp->th_opcode));
+				return;
+			}
 		}
-		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 && ecode == 0)
-		oack();
-	if (logging) {
-		char hbuf[NI_MAXHOST];
+	if (logging)
+		tftp_log(LOG_INFO, "%s: read request for %s: %s", peername,
+			    filename, errtomsg(ecode));
 
-		getnameinfo((struct sockaddr *)&from, from.ss_len,
-			    hbuf, sizeof(hbuf), NULL, 0, 0);
-		syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf,
-			tp->th_opcode == WRQ ? "write" : "read",
-			filename, errtomsg(ecode));
-	}
 	if (ecode) {
 		/*
 		 * Avoid storms of naks to a RRQ broadcast for a relative
@@ -508,19 +604,13 @@ option_fail:
 		 */
 		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
 			exit(0);
-		nak(ecode);
+		tftp_log(LOG_ERR, "Prevent NAK storm");
+		send_error(peer, ecode);
 		exit(1);
 	}
-	if (tp->th_opcode == WRQ)
-		(*pf->f_recv)(pf);
-	else
-		(*pf->f_send)(pf);
-	exit(0);
+	tftp_xmitfile(peer, mode);
 }
 
-
-FILE *file;
-
 /*
  * Find the next value for YYYYMMDD.nn when the file to be written should
  * be unique. Due to the limitations of nn, we will fail if nn reaches 100.
@@ -536,8 +626,6 @@ find_next_name(char *filename, int *fd)
 	struct tm lt;
 	char yyyymmdd[MAXPATHLEN];
 	char newname[MAXPATHLEN];
-	struct stat sb;
-	int ret;
 
 	/* Create the YYYYMMDD part of the filename */
 	time(&tval);
@@ -553,7 +641,7 @@ find_next_name(char *filename, int *fd)
 	/* Make sure the new filename is not too long */
 	if (strlen(filename) > MAXPATHLEN - len - 5) {
 		syslog(LOG_WARNING,
-			"Filename too long (%d characters, %d maximum)",
+			"Filename too long (%zd characters, %zd maximum)",
 			strlen(filename), MAXPATHLEN - len - 5);
 		return (EACCESS);
 	}
@@ -584,7 +672,7 @@ find_next_name(char *filename, int *fd)
  * given as we have no login directory.
  */
 int
-validate_access(char **filep, int mode)
+validate_access(int peer, char **filep, int mode)
 {
 	struct stat stbuf;
 	int	fd;
@@ -660,14 +748,13 @@ validate_access(char **filep, int mode)
 		else if (mode == RRQ)
 			return (err);
 	}
-	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);
-	}
+
+	/*
+	 * This option is handled here because it (might) require(s) the
+	 * size of the file.
+	 */
+	option_tsize(peer, NULL, mode, &stbuf);
+
 	if (mode == RRQ)
 		fd = open(filename, O_RDONLY);
 	else {
@@ -694,305 +781,60 @@ validate_access(char **filep, int mode)
 	return (0);
 }
 
-int	timeouts;
-jmp_buf	timeoutbuf;
-
-void
-timer(int sig __unused)
+static void
+tftp_xmitfile(int peer, const char *mode)
 {
-	if (++timeouts > MAX_TIMEOUTS)
-		exit(1);
-	longjmp(timeoutbuf, 1);
-}
+	uint16_t block;
+	uint32_t amount;
+	time_t now;
+	struct tftp_stats ts;
+
+	now = time(NULL);
+	if (debug&DEBUG_SIMPLE)
+		tftp_log(LOG_DEBUG, "Transmitting file");
 
-/*
- * Send the requested file.
- */
-void
-xmitfile(struct formats *pf)
-{
-	struct tftphdr *dp;
-	struct tftphdr *ap;    /* ack packet */
-	int size, n;
-	volatile unsigned short block;
-
-	signal(SIGALRM, timer);
-	dp = r_init();
-	ap = (struct tftphdr *)ackbuf;
+	read_init(0, file, mode);
 	block = 1;
-	do {
-		size = readit(file, &dp, 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);
-		timeouts = 0;
-		(void)setjmp(timeoutbuf);
-
-send_data:
-		{
-			int i, t = 1;
-			for (i = 0; ; i++){
-				if (send(peer, dp, size + 4, 0) != size + 4) {
-					sleep(t);
-					t = (t < 32) ? t<< 1 : t;
-					if (i >= 12) {
-						syslog(LOG_ERR, "write: %m");
-						goto abort;
-					}
-				}
-				break;
-			}
-		}
-		read_ahead(file, pf->f_convert);
-		for ( ; ; ) {
-			alarm(rexmtval);        /* read the ack */
-			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
-			alarm(0);
-			if (n < 0) {
-				syslog(LOG_ERR, "read: %m");
-				goto abort;
-			}
-			ap->th_opcode = ntohs((u_short)ap->th_opcode);
-			ap->th_block = ntohs((u_short)ap->th_block);
-
-			if (ap->th_opcode == ERROR)
-				goto abort;
-
-			if (ap->th_opcode == ACK) {
-				if (ap->th_block == block)
-					break;
-				/* Re-synchronize with the other side */
-				(void) synchnet(peer);
-				if (ap->th_block == (block -1))
-					goto send_data;
-			}
-
-		}
-		block++;
-	} while (size == SEGSIZE);
-abort:
-	(void) fclose(file);
+	tftp_send(peer, &block, &ts);
+	read_close();
+	if (debug&DEBUG_SIMPLE)
+		tftp_log(LOG_INFO, "Sent %d bytes in %d seconds",
+		    amount, time(NULL) - now);
 }
 
-void
-justquit(int sig __unused)
+static void
+tftp_recvfile(int peer, const char *mode)
 {
-	exit(0);
-}
+	uint32_t filesize; 
+	uint16_t block;
+	struct timeval now1, now2;
+	struct tftp_stats ts;
+
+	gettimeofday(&now1, NULL);
+	if (debug&DEBUG_SIMPLE)
+		tftp_log(LOG_DEBUG, "Receiving file");
 
+	write_init(0, file, mode);
 
-/*
- * Receive a file.
- */
-void
-recvfile(struct formats *pf)
-{
-	struct tftphdr *dp;
-	struct tftphdr *ap;    /* ack buffer */
-	int n, size;
-	volatile unsigned short block;
-
-	signal(SIGALRM, timer);
-	dp = w_init();
-	ap = (struct tftphdr *)ackbuf;
 	block = 0;
-	do {
-		timeouts = 0;
-		ap->th_opcode = htons((u_short)ACK);
-		ap->th_block = htons((u_short)block);
-		block++;
-		(void) setjmp(timeoutbuf);
-send_ack:
-		if (send(peer, ackbuf, 4, 0) != 4) {
-			syslog(LOG_ERR, "write: %m");
-			goto abort;
-		}
-		write_behind(file, pf->f_convert);
-		for ( ; ; ) {
-			alarm(rexmtval);
-			n = recv(peer, dp, PKTSIZE, 0);
-			alarm(0);
-			if (n < 0) {            /* really? */
-				syslog(LOG_ERR, "read: %m");
-				goto abort;
-			}
-			dp->th_opcode = ntohs((u_short)dp->th_opcode);
-			dp->th_block = ntohs((u_short)dp->th_block);
-			if (dp->th_opcode == ERROR)
-				goto abort;
-			if (dp->th_opcode == DATA) {
-				if (dp->th_block == block) {
-					break;   /* normal */
-				}
-				/* Re-synchronize with the other side */
-				(void) synchnet(peer);
-				if (dp->th_block == (block-1))
-					goto send_ack;          /* rexmit */
-			}
-		}
-		/*  size = write(file, dp->th_data, n - 4); */
-		size = writeit(file, &dp, n - 4, pf->f_convert);
-		if (size != (n-4)) {                    /* ahem */
-			if (size < 0) nak(errno + 100);
-			else nak(ENOSPACE);
-			goto abort;
-		}
-	} while (size == SEGSIZE);
-	write_behind(file, pf->f_convert);
-	(void) fclose(file);            /* close data file */
-
-	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
-	ap->th_block = htons((u_short)(block));
-	(void) send(peer, ackbuf, 4, 0);
-
-	signal(SIGALRM, justquit);      /* just quit on timeout */
-	alarm(rexmtval);
-	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
-	alarm(0);
-	if (n >= 4 &&                   /* if read some data */
-	    dp->th_opcode == DATA &&    /* and got a data block */
-	    block == dp->th_block) {	/* then my last ack was lost */
-		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
-	}
-abort:
-	return;
-}
-
-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" },

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201005040619.o446JKZU025085>