summaryrefslogtreecommitdiffstats
path: root/libexec/tftpd
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2010-05-04 06:19:19 +0000
committerimp <imp@FreeBSD.org>2010-05-04 06:19:19 +0000
commitff6273a4391b5559f8a5e847e32a80b51605974f (patch)
treeb1cde1d4bfa96baafbd358c285ddfcbf1f64d29a /libexec/tftpd
parent338b266e1a4c51244efd34c38dab2946bab967da (diff)
downloadFreeBSD-src-ff6273a4391b5559f8a5e847e32a80b51605974f.zip
FreeBSD-src-ff6273a4391b5559f8a5e847e32a80b51605974f.tar.gz
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>
Diffstat (limited to 'libexec/tftpd')
-rw-r--r--libexec/tftpd/Makefile13
-rw-r--r--libexec/tftpd/tftpd.817
-rw-r--r--libexec/tftpd/tftpd.c826
3 files changed, 354 insertions, 502 deletions
diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile
index 6c29243..b9db2a9 100644
--- a/libexec/tftpd/Makefile
+++ b/libexec/tftpd/Makefile
@@ -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>
diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8
index f9d7aec..08f40f3 100644
--- a/libexec/tftpd/tftpd.8
+++ b/libexec/tftpd/tftpd.8
@@ -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 creation of the suffix if
.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 configuration file,
.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).
diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c
index 027e4bc..93943f5 100644
--- a/libexec/tftpd/tftpd.c
+++ b/libexec/tftpd/tftpd.c
@@ -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;
-static const char *errtomsg(int);
-static void nak(int);
-static void oack(void);
+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];
+
+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");
- exit(1);
- }
- if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
- syslog(LOG_ERR, "bind: %m");
+ tftp_log(LOG_ERR, "socket: %s", strerror(errno));
exit(1);
}
- if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
- syslog(LOG_ERR, "connect: %m");
+ if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) {
+ tftp_log(LOG_ERR, "bind: %s", strerror(errno));
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++;
- }
- if (*cp != '\0') {
- nak(EBADOP);
- exit(1);
- }
- i = cp - tp->th_stuff;
- if (i >= sizeof(fnbuf)) {
- nak(EBADOP);
- exit(1);
- }
- memcpy(fnbuf, tp->th_stuff, i);
- fnbuf[i] = '\0';
+ 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;
+
+ if (size > 0) {
+ if (options_rfc_enabled)
+ has_options = !parse_options(peer, cp, size);
+ else
+ tftp_log(LOG_INFO, "Options found but not enabled");
}
- 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);
+
+ ecode = validate_access(peer, &filename, WRQ);
+ if (ecode == 0) {
+ if (has_options)
+ send_oack(peer);
+ else
+ send_ack(peer, 0);
}
- 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 (logging) {
+ tftp_log(LOG_INFO, "%s: write request for %s: %s", peername,
+ filename, errtomsg(ecode));
}
-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);
- }
- else if (to <= max_rexmtval)
- options[OPT_TIMEOUT].o_reply = rexmtval = to;
+ 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 (size > 0) {
+ if (options_rfc_enabled)
+ has_options = !parse_options(peer, cp, size);
else
- options[OPT_TIMEOUT].o_request = NULL;
+ tftp_log(LOG_INFO, "Options found but not enabled");
}
- ecode = (*pf->f_validate)(&filename, tp->th_opcode);
- if (has_options && ecode == 0)
- oack();
- if (logging) {
- char hbuf[NI_MAXHOST];
+ ecode = validate_access(peer, &filename, RRQ);
+ if (ecode == 0) {
+ if (has_options) {
+ int n;
+ char lrecvbuffer[MAXPKTSIZE];
+ struct tftphdr *rp = (struct tftphdr *)lrecvbuffer;
- 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));
+ 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;
+ }
+ }
}
+
+ if (logging)
+ tftp_log(LOG_INFO, "%s: read request for %s: %s", peername,
+ 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;
-/*
- * 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;
- 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);
+ now = time(NULL);
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Transmitting file");
- 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);
+ read_init(0, file, mode);
+ block = 1;
+ 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;
-}
+ tftp_receive(peer, &block, &ts, NULL, 0);
-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" },
- { EOPTNEG, "Option negotiation" },
- { -1, 0 }
-};
+ write_close();
-static const char *
-errtomsg(int error)
-{
- static char ebuf[20];
- struct errmsg *pe;
- if (error == 0)
- return "success";
- for (pe = errmsgs; pe->e_code >= 0; pe++)
- if (pe->e_code == error)
- return pe->e_msg;
- snprintf(ebuf, sizeof(buf), "error %d", error);
- return ebuf;
-}
+ if (debug&DEBUG_SIMPLE) {
+ double f;
+ if (now1.tv_usec > now2.tv_usec) {
+ now2.tv_usec += 1000000;
+ now2.tv_sec--;
+ }
-/*
- * Send a nak packet (error message).
- * Error code passed in is one of the
- * standard TFTP codes, or a UNIX errno
- * offset by 100.
- */
-static void
-nak(int error)
-{
- struct tftphdr *tp;
- int length;
- struct errmsg *pe;
-
- tp = (struct tftphdr *)buf;
- 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; /* set 'undef' errorcode */
+ f = now2.tv_sec - now1.tv_sec +
+ (now2.tv_usec - now1.tv_usec) / 100000.0;
+ tftp_log(LOG_INFO,
+ "Download of %d bytes in %d blocks completed after %0.1f seconds\n",
+ filesize, block, f);
}
- strcpy(tp->th_msg, pe->e_msg);
- length = strlen(pe->e_msg);
- tp->th_msg[length] = '\0';
- length += 5;
- if (send(peer, buf, length, 0) != length)
- syslog(LOG_ERR, "nak: %m");
-}
-/* translate IPv4 mapped IPv6 address to IPv4 address */
-static void
-unmappedaddr(struct sockaddr_in6 *sin6)
-{
- struct sockaddr_in *sin4;
- u_int32_t addr;
- int port;
-
- if (sin6->sin6_family != AF_INET6 ||
- !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
- return;
- sin4 = (struct sockaddr_in *)sin6;
- addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
- port = sin6->sin6_port;
- memset(sin4, 0, sizeof(struct sockaddr_in));
- sin4->sin_addr.s_addr = addr;
- sin4->sin_port = port;
- sin4->sin_family = AF_INET;
- sin4->sin_len = sizeof(struct sockaddr_in);
+ return;
}
-/*
- * Send an oack packet (option acknowledgement).
- */
-static void
-oack(void)
-{
- 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;
- }
-}
OpenPOWER on IntegriCloud