aboutsummaryrefslogtreecommitdiff
path: root/libexec/tftpd
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/tftpd')
-rw-r--r--libexec/tftpd/Makefile18
-rw-r--r--libexec/tftpd/Makefile.depend16
-rw-r--r--libexec/tftpd/Makefile.depend.options5
-rw-r--r--libexec/tftpd/tests/Makefile8
-rw-r--r--libexec/tftpd/tests/functional.c1283
-rw-r--r--libexec/tftpd/tftp-file.c299
-rw-r--r--libexec/tftpd/tftp-file.h39
-rw-r--r--libexec/tftpd/tftp-io.c446
-rw-r--r--libexec/tftpd/tftp-io.h46
-rw-r--r--libexec/tftpd/tftp-options.c477
-rw-r--r--libexec/tftpd/tftp-options.h68
-rw-r--r--libexec/tftpd/tftp-transfer.c446
-rw-r--r--libexec/tftpd/tftp-transfer.h30
-rw-r--r--libexec/tftpd/tftp-utils.c328
-rw-r--r--libexec/tftpd/tftp-utils.h129
-rw-r--r--libexec/tftpd/tftpd.8333
-rw-r--r--libexec/tftpd/tftpd.c849
17 files changed, 4820 insertions, 0 deletions
diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile
new file mode 100644
index 000000000000..1cea8178ae07
--- /dev/null
+++ b/libexec/tftpd/Makefile
@@ -0,0 +1,18 @@
+.include <src.opts.mk>
+
+PROG= tftpd
+MAN= tftpd.8
+SRCS= tftp-file.c tftp-io.c tftp-options.c tftp-transfer.c tftp-utils.c
+SRCS+= tftpd.c
+
+.if ${MK_TCP_WRAPPERS} != "no"
+CFLAGS+= -DLIBWRAP
+LIBADD= wrap
+.endif
+
+CWARNFLAGS.gcc+= -Wno-format-nonliteral
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/libexec/tftpd/Makefile.depend b/libexec/tftpd/Makefile.depend
new file mode 100644
index 000000000000..344a5d0e9310
--- /dev/null
+++ b/libexec/tftpd/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/libexec/tftpd/Makefile.depend.options b/libexec/tftpd/Makefile.depend.options
new file mode 100644
index 000000000000..fbc21670804e
--- /dev/null
+++ b/libexec/tftpd/Makefile.depend.options
@@ -0,0 +1,5 @@
+# This file is not autogenerated - take care!
+
+DIRDEPS_OPTIONS= TCP_WRAPPERS
+
+.include <dirdeps-options.mk>
diff --git a/libexec/tftpd/tests/Makefile b/libexec/tftpd/tests/Makefile
new file mode 100644
index 000000000000..d1faca03331e
--- /dev/null
+++ b/libexec/tftpd/tests/Makefile
@@ -0,0 +1,8 @@
+.include <bsd.own.mk>
+
+ATF_TESTS_C= functional
+TEST_METADATA.functional+= timeout=15
+
+LIBADD= util
+
+.include <bsd.test.mk>
diff --git a/libexec/tftpd/tests/functional.c b/libexec/tftpd/tests/functional.c
new file mode 100644
index 000000000000..791aa9190a2f
--- /dev/null
+++ b/libexec/tftpd/tests/functional.c
@@ -0,0 +1,1283 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Alan Somers.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdalign.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+#include <libutil.h>
+
+static const uint16_t BASEPORT = 6969;
+static const char pidfile[] = "tftpd.pid";
+static int protocol = PF_UNSPEC;
+static int s = -1; /* tftp client socket */
+static struct sockaddr_storage addr; /* Destination address for the client */
+static bool s_flag = false; /* Pass -s to tftpd */
+static bool w_flag = false; /* Pass -w to tftpd */
+
+/* Helper functions*/
+static void require_bufeq(const char *expected, size_t expected_len,
+ const char *actual, size_t len);
+
+/*
+ * Receive a response from tftpd
+ * @param hdr The reply's expected header, as a char array
+ * @param contents The reply's expected contents, as a char array
+ * @param contents_len Length of contents
+ */
+#define RECV(hdr, contents, contents_len) do { \
+ char buffer[1024]; \
+ struct sockaddr_storage from; \
+ socklen_t fromlen = sizeof(from); \
+ ssize_t r = recvfrom(s, buffer, sizeof(buffer), 0, \
+ (struct sockaddr *)&from, &fromlen); \
+ ATF_REQUIRE(r > 0); \
+ require_bufeq((hdr), sizeof(hdr), buffer, \
+ MIN((size_t)r, sizeof(hdr))); \
+ require_bufeq((const char *) (contents), (contents_len), \
+ &buffer[sizeof(hdr)], r - sizeof(hdr)); \
+ if (protocol == PF_INET) { \
+ ((struct sockaddr_in *)&addr)->sin_port = \
+ ((struct sockaddr_in *)&from)->sin_port; \
+ } else { \
+ ((struct sockaddr_in6 *)&addr)->sin6_port = \
+ ((struct sockaddr_in6 *)&from)->sin6_port; \
+ } \
+} while(0)
+
+static void
+recv_ack(uint16_t blocknum)
+{
+ char hdr[] = {0, 4, blocknum >> 8, blocknum & 0xFF};
+ RECV(hdr, NULL, 0);
+}
+
+static void
+recv_oack(const char *options, size_t options_len)
+{
+ char hdr[] = {0, 6};
+ RECV(hdr, options, options_len);
+}
+
+/*
+ * Receive a data packet from tftpd
+ * @param blocknum Expected block number to be received
+ * @param contents Pointer to expected contents
+ * @param contents_len Length of contents expected to receive
+ */
+static void
+recv_data(uint16_t blocknum, const char *contents, size_t contents_len)
+{
+ char hdr[] = {0, 3, blocknum >> 8, blocknum & 0xFF};
+ RECV(hdr, contents, contents_len);
+}
+
+#define RECV_ERROR(code, msg) do { \
+ char hdr[] = {0, 5, code >> 8, code & 0xFF}; \
+ RECV(hdr, msg, sizeof(msg)); \
+} while (0)
+
+/*
+ * send a command to tftpd.
+ * @param cmd Command to send, as a char array
+ */
+static void
+send_bytes(const void *cmd, size_t len)
+{
+ ssize_t r;
+
+ r = sendto(s, cmd, len, 0, (struct sockaddr *)(&addr), addr.ss_len);
+ ATF_REQUIRE(r >= 0);
+ ATF_REQUIRE_EQ(len, (size_t)r);
+}
+
+static void
+send_data(uint16_t blocknum, const char *contents, size_t contents_len)
+{
+ char buffer[1024];
+
+ buffer[0] = 0; /* DATA opcode high byte */
+ buffer[1] = 3; /* DATA opcode low byte */
+ buffer[2] = blocknum >> 8;
+ buffer[3] = blocknum & 0xFF;
+ memmove(&buffer[4], contents, contents_len);
+ send_bytes(buffer, 4 + contents_len);
+}
+
+/*
+ * send a command to tftpd.
+ * @param cmd Command to send, as a const string
+ * (terminating NUL will be ignored)
+ */
+#define SEND_STR(cmd) \
+ ATF_REQUIRE_EQ(sizeof(cmd) - 1, \
+ sendto(s, (cmd), sizeof(cmd) - 1, 0, \
+ (struct sockaddr *)(&addr), addr.ss_len))
+
+/*
+ * Acknowledge block blocknum
+ */
+static void
+send_ack(uint16_t blocknum)
+{
+ char packet[] = {
+ 0, 4, /* ACK opcode in BE */
+ blocknum >> 8,
+ blocknum & 0xFF
+ };
+
+ send_bytes(packet, sizeof(packet));
+}
+
+/*
+ * build an option string
+ */
+#define OPTION_STR(name, value) name "\000" value "\000"
+
+/*
+ * send a read request to tftpd.
+ * @param filename filename as a string, absolute or relative
+ * @param mode either "octet" or "netascii"
+ */
+#define SEND_RRQ(filename, mode) \
+ SEND_STR("\0\001" filename "\0" mode "\0")
+
+/*
+ * send a read request with options
+ */
+#define SEND_RRQ_OPT(filename, mode, options) \
+ SEND_STR("\0\001" filename "\0" mode "\000" options)
+
+/*
+ * send a write request to tftpd.
+ * @param filename filename as a string, absolute or relative
+ * @param mode either "octet" or "netascii"
+ */
+#define SEND_WRQ(filename, mode) \
+ SEND_STR("\0\002" filename "\0" mode "\0")
+
+/*
+ * send a write request with options
+ */
+#define SEND_WRQ_OPT(filename, mode, options) \
+ SEND_STR("\0\002" filename "\0" mode "\000" options)
+
+/* Define a test case, for both IPv4 and IPv6 */
+#define TFTPD_TC_DEFINE(name, head, ...) \
+static void \
+name ## _body(void); \
+ATF_TC_WITH_CLEANUP(name ## _v4); \
+ATF_TC_HEAD(name ## _v4, tc) \
+{ \
+ head \
+} \
+ATF_TC_BODY(name ## _v4, tc) \
+{ \
+ int exitcode = 0; \
+ __VA_ARGS__; \
+ protocol = AF_INET; \
+ s = setup(&addr, __COUNTER__); \
+ name ## _body(); \
+ close(s); \
+ if (exitcode >= 0) \
+ check_server(exitcode); \
+} \
+ATF_TC_CLEANUP(name ## _v4, tc) \
+{ \
+ cleanup(); \
+} \
+ATF_TC_WITH_CLEANUP(name ## _v6); \
+ATF_TC_HEAD(name ## _v6, tc) \
+{ \
+ head \
+} \
+ATF_TC_BODY(name ## _v6, tc) \
+{ \
+ int exitcode = 0; \
+ __VA_ARGS__; \
+ protocol = AF_INET6; \
+ s = setup(&addr, __COUNTER__); \
+ name ## _body(); \
+ close(s); \
+ if (exitcode >= 0) \
+ check_server(exitcode); \
+} \
+ATF_TC_CLEANUP(name ## _v6, tc) \
+{ \
+ cleanup(); \
+} \
+static void \
+name ## _body(void)
+
+/* Add the IPv4 and IPv6 versions of a test case */
+#define TFTPD_TC_ADD(tp, name) do { \
+ ATF_TP_ADD_TC(tp, name ## _v4); \
+ ATF_TP_ADD_TC(tp, name ## _v6); \
+} while (0)
+
+static void
+sigalrm(int signo __unused)
+{
+}
+
+/* Check that server exits with specific exit code */
+static void
+check_server(int exitcode)
+{
+ struct sigaction sa = { .sa_handler = sigalrm };
+ struct itimerval it = { .it_value = { .tv_sec = 30 } };
+ FILE *f;
+ pid_t pid;
+ int wstatus;
+
+ f = fopen(pidfile, "r");
+ ATF_REQUIRE(f != NULL);
+ ATF_REQUIRE_INTEQ(1, fscanf(f, "%d", &pid));
+ ATF_CHECK_INTEQ(0, fclose(f));
+ ATF_REQUIRE_INTEQ(0, sigaction(SIGALRM, &sa, NULL));
+ ATF_REQUIRE_EQ(0, setitimer(ITIMER_REAL, &it, NULL));
+ ATF_REQUIRE_EQ(pid, waitpid(pid, &wstatus, 0));
+ ATF_CHECK(WIFEXITED(wstatus));
+ ATF_CHECK_INTEQ(exitcode, WEXITSTATUS(wstatus));
+ unlink(pidfile);
+}
+
+/* Standard cleanup used by all testcases */
+static void
+cleanup(void)
+{
+ FILE *f;
+ pid_t pid;
+
+ f = fopen(pidfile, "r");
+ if (f == NULL)
+ return;
+ unlink(pidfile);
+ if (fscanf(f, "%d", &pid) == 1) {
+ kill(pid, SIGTERM);
+ waitpid(pid, NULL, 0);
+ }
+ fclose(f);
+}
+
+/* Assert that two binary buffers are identical */
+static void
+require_bufeq(const char *expected, size_t expected_len,
+ const char *actual, size_t len)
+{
+ size_t i;
+
+ ATF_REQUIRE_EQ_MSG(expected_len, len,
+ "Expected %zu bytes but got %zu", expected_len, len);
+ for (i = 0; i < len; i++) {
+ ATF_REQUIRE_EQ_MSG(expected[i], actual[i],
+ "Expected %#hhx at position %zu; got %hhx instead",
+ expected[i], i, actual[i]);
+ }
+}
+
+/*
+ * Start tftpd and return its communicating socket
+ * @param to Will be filled in for use with sendto
+ * @param idx Unique identifier of the test case
+ * @return Socket ready to use
+ */
+static int
+setup(struct sockaddr_storage *to, uint16_t idx)
+{
+ int client_s, server_s, pid, argv_idx;
+ char execname[] = "/usr/libexec/tftpd";
+ char b_flag_str[] = "-b";
+ char s_flag_str[] = "-s";
+ char w_flag_str[] = "-w";
+ char pwd[MAXPATHLEN];
+ char *argv[10];
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ struct sockaddr *server_addr;
+ struct pidfh *pfh;
+ uint16_t port = BASEPORT + idx;
+ socklen_t len;
+ int pd[2];
+
+ ATF_REQUIRE_EQ(0, pipe2(pd, O_CLOEXEC));
+
+ if (protocol == PF_INET) {
+ len = sizeof(addr4);
+ bzero(&addr4, len);
+ addr4.sin_len = len;
+ addr4.sin_family = PF_INET;
+ addr4.sin_port = htons(port);
+ server_addr = (struct sockaddr *)&addr4;
+ } else {
+ len = sizeof(addr6);
+ bzero(&addr6, len);
+ addr6.sin6_len = len;
+ addr6.sin6_family = PF_INET6;
+ addr6.sin6_port = htons(port);
+ server_addr = (struct sockaddr *)&addr6;
+ }
+
+ ATF_REQUIRE_EQ(pwd, getcwd(pwd, sizeof(pwd)));
+
+ /* Must bind(2) pre-fork so it happens before the client's send(2) */
+ server_s = socket(protocol, SOCK_DGRAM, 0);
+ if (server_s < 0 && errno == EAFNOSUPPORT) {
+ atf_tc_skip("This test requires IPv%d support",
+ protocol == PF_INET ? 4 : 6);
+ }
+ ATF_REQUIRE_MSG(server_s >= 0,
+ "socket failed with error %s", strerror(errno));
+ ATF_REQUIRE_EQ_MSG(0, bind(server_s, server_addr, len),
+ "bind failed with error %s", strerror(errno));
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ atf_tc_fail("fork failed");
+ break;
+ case 0:
+ /* In child */
+ pfh = pidfile_open(pidfile, 0644, NULL);
+ ATF_REQUIRE_MSG(pfh != NULL,
+ "pidfile_open: %s", strerror(errno));
+ ATF_REQUIRE_EQ(0, pidfile_write(pfh));
+ ATF_REQUIRE_EQ(0, pidfile_close(pfh));
+
+ bzero(argv, sizeof(argv));
+ argv[0] = execname;
+ argv_idx = 1;
+ argv[argv_idx++] = b_flag_str;
+ if (w_flag)
+ argv[argv_idx++] = w_flag_str;
+ if (s_flag)
+ argv[argv_idx++] = s_flag_str;
+ argv[argv_idx++] = pwd;
+ ATF_REQUIRE_EQ(STDOUT_FILENO, dup2(server_s, STDOUT_FILENO));
+ ATF_REQUIRE_EQ(STDIN_FILENO, dup2(server_s, STDIN_FILENO));
+ ATF_REQUIRE_EQ(STDERR_FILENO, dup2(server_s, STDERR_FILENO));
+ execv(execname, argv);
+ atf_tc_fail("exec failed");
+ break;
+ default:
+ /* In parent */
+ ATF_REQUIRE_INTEQ(0, close(pd[1]));
+ /* block until other end is closed on exec() or exit() */
+ ATF_REQUIRE_INTEQ(0, read(pd[0], &pd[1], sizeof(pd[1])));
+ ATF_REQUIRE_INTEQ(0, close(pd[0]));
+ bzero(to, sizeof(*to));
+ if (protocol == PF_INET) {
+ struct sockaddr_in *to4 = (struct sockaddr_in *)to;
+ to4->sin_len = sizeof(*to4);
+ to4->sin_family = PF_INET;
+ to4->sin_port = htons(port);
+ to4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else {
+ struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
+ struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)to;
+ to6->sin6_len = sizeof(*to6);
+ to6->sin6_family = PF_INET6;
+ to6->sin6_port = htons(port);
+ to6->sin6_addr = loopback;
+ }
+
+ ATF_REQUIRE_INTEQ(0, close(server_s));
+ ATF_REQUIRE((client_s = socket(protocol, SOCK_DGRAM, 0)) > 0);
+ break;
+ }
+
+ /* Clear the client's umask. Test cases will specify exact modes */
+ umask(0000);
+
+ return (client_s);
+}
+
+/* Like write(2), but never returns less than the requested length */
+static void
+write_all(int fd, const void *buf, size_t nbytes)
+{
+ ssize_t r;
+
+ while (nbytes > 0) {
+ r = write(fd, buf, nbytes);
+ ATF_REQUIRE(r > 0);
+ nbytes -= (size_t)r;
+ buf = (const char *)buf + (size_t)r;
+ }
+}
+
+
+/*
+ * Test Cases
+ */
+
+/*
+ * Read a file, specified by absolute pathname.
+ */
+TFTPD_TC_DEFINE(abspath,)
+{
+ int fd;
+ char command[1024];
+ size_t pathlen;
+ char suffix[] = {'\0', 'o', 'c', 't', 'e', 't', '\0'};
+
+ command[0] = 0; /* RRQ high byte */
+ command[1] = 1; /* RRQ low byte */
+ ATF_REQUIRE(getcwd(&command[2], sizeof(command) - 2) != NULL);
+ pathlen = strlcat(&command[2], "/abspath.txt", sizeof(command) - 2);
+ ATF_REQUIRE(pathlen + sizeof(suffix) < sizeof(command) - 2);
+ memmove(&command[2 + pathlen], suffix, sizeof(suffix));
+
+ fd = open("abspath.txt", O_CREAT | O_RDONLY, 0644);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ send_bytes(command, 2 + pathlen + sizeof(suffix));
+ recv_data(1, NULL, 0);
+ send_ack(1);
+}
+
+/*
+ * Attempt to read a file outside of the allowed directory(ies)
+ */
+TFTPD_TC_DEFINE(dotdot,)
+{
+ ATF_REQUIRE_EQ(0, mkdir("subdir", 0777));
+ SEND_RRQ("../disallowed.txt", "octet");
+ RECV_ERROR(2, "Access violation");
+ s = setup(&addr, __COUNTER__);
+ SEND_RRQ("subdir/../../disallowed.txt", "octet");
+ RECV_ERROR(2, "Access violation");
+ s = setup(&addr, __COUNTER__);
+ SEND_RRQ("/etc/passwd", "octet");
+ RECV_ERROR(2, "Access violation");
+}
+
+/*
+ * With "-s", tftpd should chroot to the specified directory
+ */
+TFTPD_TC_DEFINE(s_flag,
+ atf_tc_set_md_var(tc, "require.user", "root");,
+ s_flag = true)
+{
+ int fd;
+ char contents[] = "small";
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, strlen(contents) + 1);
+ close(fd);
+
+ SEND_RRQ("/small.txt", "octet");
+ recv_data(1, contents, strlen(contents) + 1);
+ send_ack(1);
+}
+
+/*
+ * Read a file, and simulate a dropped ACK packet
+ */
+TFTPD_TC_DEFINE(rrq_dropped_ack,)
+{
+ int fd;
+ char contents[] = "small";
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, strlen(contents) + 1);
+ close(fd);
+
+ SEND_RRQ("small.txt", "octet");
+ recv_data(1, contents, strlen(contents) + 1);
+ /*
+ * client "sends" the ack, but network drops it
+ * Eventually, tftpd should resend the data packet
+ */
+ recv_data(1, contents, strlen(contents) + 1);
+ send_ack(1);
+}
+
+/*
+ * Read a file, and simulate a dropped DATA packet
+ */
+TFTPD_TC_DEFINE(rrq_dropped_data,)
+{
+ int fd;
+ size_t i;
+ uint32_t contents[192];
+ char buffer[1024];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ("medium.txt", "octet");
+ recv_data(1, (const char *)&contents[0], 512);
+ send_ack(1);
+ (void) recvfrom(s, buffer, sizeof(buffer), 0, NULL, NULL);
+ /*
+ * server "sends" the data, but network drops it
+ * Eventually, client should resend the last ACK
+ */
+ send_ack(1);
+ recv_data(2, (const char *)&contents[128], 256);
+ send_ack(2);
+}
+
+/*
+ * Read a medium file, and simulate a duplicated ACK packet
+ */
+TFTPD_TC_DEFINE(rrq_duped_ack,)
+{
+ int fd;
+ size_t i;
+ uint32_t contents[192];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ("medium.txt", "octet");
+ recv_data(1, (const char *)&contents[0], 512);
+ send_ack(1);
+ send_ack(1); /* Dupe an ACK packet */
+ recv_data(2, (const char *)&contents[128], 256);
+ recv_data(2, (const char *)&contents[128], 256);
+ send_ack(2);
+}
+
+
+/*
+ * Attempt to read a file without read permissions
+ */
+TFTPD_TC_DEFINE(rrq_eaccess,)
+{
+ int fd;
+
+ fd = open("empty.txt", O_CREAT | O_RDONLY, 0000);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_RRQ("empty.txt", "octet");
+ RECV_ERROR(2, "Access violation");
+}
+
+/*
+ * Read an empty file
+ */
+TFTPD_TC_DEFINE(rrq_empty,)
+{
+ int fd;
+
+ fd = open("empty.txt", O_CREAT | O_RDONLY, 0644);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_RRQ("empty.txt", "octet");
+ recv_data(1, NULL, 0);
+ send_ack(1);
+}
+
+/*
+ * Read a medium file of more than one block
+ */
+TFTPD_TC_DEFINE(rrq_medium,)
+{
+ int fd;
+ size_t i;
+ uint32_t contents[192];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ("medium.txt", "octet");
+ recv_data(1, (const char *)&contents[0], 512);
+ send_ack(1);
+ recv_data(2, (const char *)&contents[128], 256);
+ send_ack(2);
+}
+
+/*
+ * Read a medium file with a window size of 2.
+ */
+TFTPD_TC_DEFINE(rrq_medium_window,)
+{
+ int fd;
+ size_t i;
+ uint32_t contents[192];
+ char options[] = OPTION_STR("windowsize", "2");
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ_OPT("medium.txt", "octet", OPTION_STR("windowsize", "2"));
+ recv_oack(options, sizeof(options) - 1);
+ send_ack(0);
+ recv_data(1, (const char *)&contents[0], 512);
+ recv_data(2, (const char *)&contents[128], 256);
+ send_ack(2);
+}
+
+/*
+ * Read a file in netascii format
+ */
+TFTPD_TC_DEFINE(rrq_netascii,)
+{
+ int fd;
+ char contents[] = "foo\nbar\rbaz\n";
+ /*
+ * Weirdly, RFC-764 says that CR must be followed by NUL if a line feed
+ * is not intended
+ */
+ char expected[] = "foo\r\nbar\r\0baz\r\n";
+
+ fd = open("unix.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, strlen(contents) + 1);
+ close(fd);
+
+ SEND_RRQ("unix.txt", "netascii");
+ recv_data(1, expected, sizeof(expected));
+ send_ack(1);
+}
+
+/*
+ * Read a file that doesn't exist
+ */
+TFTPD_TC_DEFINE(rrq_nonexistent,)
+{
+ SEND_RRQ("nonexistent.txt", "octet");
+ RECV_ERROR(1, "File not found");
+}
+
+/*
+ * Attempt to read a file whose name exceeds PATH_MAX
+ */
+TFTPD_TC_DEFINE(rrq_path_max,)
+{
+#define AReallyBigFileName \
+ "AReallyBigFileNameXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+ ".txt"
+ ATF_REQUIRE_MSG(strlen(AReallyBigFileName) > PATH_MAX,
+ "Somebody increased PATH_MAX. Update the test");
+ SEND_RRQ(AReallyBigFileName, "octet");
+ RECV_ERROR(4, "Illegal TFTP operation");
+}
+
+/*
+ * Read a small file of less than one block
+ */
+TFTPD_TC_DEFINE(rrq_small,)
+{
+ int fd;
+ char contents[] = "small";
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, strlen(contents) + 1);
+ close(fd);
+
+ SEND_RRQ("small.txt", "octet");
+ recv_data(1, contents, strlen(contents) + 1);
+ send_ack(1);
+}
+
+/*
+ * Read a file following the example in RFC 7440.
+ */
+TFTPD_TC_DEFINE(rrq_window_rfc7440,)
+{
+ int fd;
+ size_t i;
+ char options[] = OPTION_STR("windowsize", "4");
+ alignas(uint32_t) char contents[13 * 512 - 4];
+ uint32_t *u32p;
+
+ u32p = (uint32_t *)contents;
+ for (i = 0; i < sizeof(contents) / sizeof(uint32_t); i++)
+ u32p[i] = i;
+
+ fd = open("rfc7440.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ_OPT("rfc7440.txt", "octet", OPTION_STR("windowsize", "4"));
+ recv_oack(options, sizeof(options) - 1);
+ send_ack(0);
+ recv_data(1, &contents[0 * 512], 512);
+ recv_data(2, &contents[1 * 512], 512);
+ recv_data(3, &contents[2 * 512], 512);
+ recv_data(4, &contents[3 * 512], 512);
+ send_ack(4);
+ recv_data(5, &contents[4 * 512], 512);
+ recv_data(6, &contents[5 * 512], 512);
+ recv_data(7, &contents[6 * 512], 512);
+ recv_data(8, &contents[7 * 512], 512);
+
+ /* ACK 5 as if 6-8 were dropped. */
+ send_ack(5);
+ recv_data(6, &contents[5 * 512], 512);
+ recv_data(7, &contents[6 * 512], 512);
+ recv_data(8, &contents[7 * 512], 512);
+ recv_data(9, &contents[8 * 512], 512);
+ send_ack(9);
+ recv_data(10, &contents[9 * 512], 512);
+ recv_data(11, &contents[10 * 512], 512);
+ recv_data(12, &contents[11 * 512], 512);
+ recv_data(13, &contents[12 * 512], 508);
+
+ /* Drop ACK and after timeout receive 10-13. */
+ recv_data(10, &contents[9 * 512], 512);
+ recv_data(11, &contents[10 * 512], 512);
+ recv_data(12, &contents[11 * 512], 512);
+ recv_data(13, &contents[12 * 512], 508);
+ send_ack(13);
+}
+
+/*
+ * Try to transfer a file with an unknown mode.
+ */
+TFTPD_TC_DEFINE(unknown_modes,)
+{
+ SEND_RRQ("foo.txt", "ascii"); /* Misspelling of "ascii" */
+ RECV_ERROR(4, "Illegal TFTP operation");
+ s = setup(&addr, __COUNTER__);
+ SEND_RRQ("foo.txt", "binary"); /* Obsolete. Use "octet" instead */
+ RECV_ERROR(4, "Illegal TFTP operation");
+ s = setup(&addr, __COUNTER__);
+ SEND_RRQ("foo.txt", "en_US.UTF-8");
+ RECV_ERROR(4, "Illegal TFTP operation");
+ s = setup(&addr, __COUNTER__);
+ SEND_RRQ("foo.txt", "mail"); /* Obsolete in RFC-1350 */
+ RECV_ERROR(4, "Illegal TFTP operation");
+}
+
+/*
+ * Send an unknown opcode. tftpd should respond with the appropriate error
+ */
+TFTPD_TC_DEFINE(unknown_opcode,)
+{
+ /* Looks like an RRQ or WRQ request, but with a bad opcode */
+ SEND_STR("\0\007foo.txt\0octet\0");
+ RECV_ERROR(4, "Illegal TFTP operation");
+}
+
+/*
+ * Invoke tftpd with "-w" and write to a nonexistent file.
+ */
+TFTPD_TC_DEFINE(w_flag,, w_flag = 1;)
+{
+ int fd;
+ ssize_t r;
+ char contents[] = "small";
+ char buffer[1024];
+ size_t contents_len;
+
+ contents_len = strlen(contents) + 1;
+ SEND_WRQ("small.txt", "octet");
+ recv_ack(0);
+ send_data(1, contents, contents_len);
+ recv_ack(1);
+
+ fd = open("small.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq(contents, contents_len, buffer, (size_t)r);
+}
+
+/*
+ * Write a medium file, and simulate a dropped ACK packet
+ */
+TFTPD_TC_DEFINE(wrq_dropped_ack,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ uint32_t contents[192];
+ char buffer[1024];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ("medium.txt", "octet");
+ recv_ack(0);
+ send_data(1, (const char *)&contents[0], 512);
+ /*
+ * Servers "sends" an ACK packet, but network drops it.
+ * Eventually, server should resend the last ACK
+ */
+ (void) recvfrom(s, buffer, sizeof(buffer), 0, NULL, NULL);
+ recv_ack(1);
+ send_data(2, (const char *)&contents[128], 256);
+ recv_ack(2);
+
+ fd = open("medium.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq((const char *)contents, 768, buffer, (size_t)r);
+}
+
+/*
+ * Write a small file, and simulate a dropped DATA packet
+ */
+TFTPD_TC_DEFINE(wrq_dropped_data,)
+{
+ int fd;
+ ssize_t r;
+ char contents[] = "small";
+ size_t contents_len;
+ char buffer[1024];
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+ contents_len = strlen(contents) + 1;
+
+ SEND_WRQ("small.txt", "octet");
+ recv_ack(0);
+ /*
+ * Client "sends" a DATA packet, but network drops it.
+ * Eventually, server should resend the last ACK
+ */
+ recv_ack(0);
+ send_data(1, contents, contents_len);
+ recv_ack(1);
+
+ fd = open("small.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq(contents, contents_len, buffer, (size_t)r);
+}
+
+/*
+ * Write a medium file, and simulate a duplicated DATA packet
+ */
+TFTPD_TC_DEFINE(wrq_duped_data,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ uint32_t contents[192];
+ char buffer[1024];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ("medium.txt", "octet");
+ recv_ack(0);
+ send_data(1, (const char *)&contents[0], 512);
+ send_data(1, (const char *)&contents[0], 512);
+ recv_ack(1);
+ recv_ack(1);
+ send_data(2, (const char *)&contents[128], 256);
+ recv_ack(2);
+
+ fd = open("medium.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq((const char *)contents, 768, buffer, (size_t)r);
+}
+
+/*
+ * Attempt to write a file without write permissions
+ */
+TFTPD_TC_DEFINE(wrq_eaccess,)
+{
+ int fd;
+
+ fd = open("empty.txt", O_CREAT | O_RDONLY, 0440);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ("empty.txt", "octet");
+ RECV_ERROR(2, "Access violation");
+}
+
+/*
+ * Attempt to write a file without world write permissions, but with world
+ * read permissions
+ */
+TFTPD_TC_DEFINE(wrq_eaccess_world_readable,)
+{
+ int fd;
+
+ fd = open("empty.txt", O_CREAT | O_RDONLY, 0444);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ("empty.txt", "octet");
+ RECV_ERROR(2, "Access violation");
+}
+
+
+/*
+ * Write a medium file of more than one block
+ */
+TFTPD_TC_DEFINE(wrq_medium,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ uint32_t contents[192];
+ char buffer[1024];
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ("medium.txt", "octet");
+ recv_ack(0);
+ send_data(1, (const char *)&contents[0], 512);
+ recv_ack(1);
+ send_data(2, (const char *)&contents[128], 256);
+ recv_ack(2);
+
+ fd = open("medium.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq((const char *)contents, 768, buffer, (size_t)r);
+}
+
+/*
+ * Write a medium file with a window size of 2.
+ */
+TFTPD_TC_DEFINE(wrq_medium_window,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ uint32_t contents[192];
+ char buffer[1024];
+ char options[] = OPTION_STR("windowsize", "2");
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ_OPT("medium.txt", "octet", OPTION_STR("windowsize", "2"));
+ recv_oack(options, sizeof(options) - 1);
+ send_data(1, (const char *)&contents[0], 512);
+ send_data(2, (const char *)&contents[128], 256);
+ recv_ack(2);
+
+ fd = open("medium.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq((const char *)contents, 768, buffer, (size_t)r);
+}
+
+/*
+ * Write a file in netascii format
+ */
+TFTPD_TC_DEFINE(wrq_netascii,)
+{
+ int fd;
+ ssize_t r;
+ /*
+ * Weirdly, RFC-764 says that CR must be followed by NUL if a line feed
+ * is not intended
+ */
+ char contents[] = "foo\r\nbar\r\0baz\r\n";
+ char expected[] = "foo\nbar\rbaz\n";
+ size_t contents_len;
+ char buffer[1024];
+
+ fd = open("unix.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+ contents_len = sizeof(contents);
+
+ SEND_WRQ("unix.txt", "netascii");
+ recv_ack(0);
+ send_data(1, contents, contents_len);
+ recv_ack(1);
+
+ fd = open("unix.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq(expected, sizeof(expected), buffer, (size_t)r);
+}
+
+/*
+ * Attempt to write to a nonexistent file. With the default options, this
+ * isn't allowed.
+ */
+TFTPD_TC_DEFINE(wrq_nonexistent,)
+{
+ SEND_WRQ("nonexistent.txt", "octet");
+ RECV_ERROR(1, "File not found");
+}
+
+/*
+ * Write a small file of less than one block
+ */
+TFTPD_TC_DEFINE(wrq_small,)
+{
+ int fd;
+ ssize_t r;
+ char contents[] = "small";
+ size_t contents_len;
+ char buffer[1024];
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+ contents_len = strlen(contents) + 1;
+
+ SEND_WRQ("small.txt", "octet");
+ recv_ack(0);
+ send_data(1, contents, contents_len);
+ recv_ack(1);
+
+ fd = open("small.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq(contents, contents_len, buffer, (size_t)r);
+}
+
+/*
+ * Write an empty file over a non-empty one
+ */
+TFTPD_TC_DEFINE(wrq_truncate,)
+{
+ int fd;
+ char contents[] = "small";
+ struct stat sb;
+
+ fd = open("small.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, strlen(contents) + 1);
+ close(fd);
+
+ SEND_WRQ("small.txt", "octet");
+ recv_ack(0);
+ send_data(1, NULL, 0);
+ recv_ack(1);
+
+ ATF_REQUIRE_EQ(0, stat("small.txt", &sb));
+ ATF_REQUIRE_EQ(0, sb.st_size);
+}
+
+/*
+ * Write a file following the example in RFC 7440.
+ */
+TFTPD_TC_DEFINE(wrq_window_rfc7440,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ char options[] = OPTION_STR("windowsize", "4");
+ alignas(uint32_t) char contents[13 * 512 - 4];
+ char buffer[sizeof(contents)];
+ uint32_t *u32p;
+
+ u32p = (uint32_t *)contents;
+ for (i = 0; i < sizeof(contents) / sizeof(uint32_t); i++)
+ u32p[i] = i;
+
+ fd = open("rfc7440.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ_OPT("rfc7440.txt", "octet", OPTION_STR("windowsize", "4"));
+ recv_oack(options, sizeof(options) - 1);
+ send_data(1, &contents[0 * 512], 512);
+ send_data(2, &contents[1 * 512], 512);
+ send_data(3, &contents[2 * 512], 512);
+ send_data(4, &contents[3 * 512], 512);
+ recv_ack(4);
+ send_data(5, &contents[4 * 512], 512);
+
+ /* Drop 6-8. */
+ recv_ack(5);
+ send_data(6, &contents[5 * 512], 512);
+ send_data(7, &contents[6 * 512], 512);
+ send_data(8, &contents[7 * 512], 512);
+ send_data(9, &contents[8 * 512], 512);
+ recv_ack(9);
+
+ /* Drop 11. */
+ send_data(10, &contents[9 * 512], 512);
+ send_data(12, &contents[11 * 512], 512);
+
+ /*
+ * We can't send 13 here as tftpd has probably already seen 12
+ * and sent the ACK of 10 if running locally. While it would
+ * recover by sending another ACK of 10, our state machine
+ * would be out of sync.
+ */
+
+ /* Ignore ACK for 10 and resend 10-13. */
+ recv_ack(10);
+ send_data(10, &contents[9 * 512], 512);
+ send_data(11, &contents[10 * 512], 512);
+ send_data(12, &contents[11 * 512], 512);
+ send_data(13, &contents[12 * 512], 508);
+ recv_ack(13);
+
+ fd = open("rfc7440.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ ATF_REQUIRE(r > 0);
+ close(fd);
+ require_bufeq(contents, sizeof(contents), buffer, (size_t)r);
+}
+
+/*
+ * Send less than four bytes
+ */
+TFTPD_TC_DEFINE(short_packet1, /* no head */, exitcode = 1)
+{
+ SEND_STR("\1");
+}
+TFTPD_TC_DEFINE(short_packet2, /* no head */, exitcode = 1)
+{
+ SEND_STR("\1\2");
+}
+TFTPD_TC_DEFINE(short_packet3, /* no head */, exitcode = 1)
+{
+ SEND_STR("\1\2\3");
+}
+
+
+/*
+ * Main
+ */
+
+ATF_TP_ADD_TCS(tp)
+{
+ TFTPD_TC_ADD(tp, abspath);
+ TFTPD_TC_ADD(tp, dotdot);
+ TFTPD_TC_ADD(tp, s_flag);
+ TFTPD_TC_ADD(tp, rrq_dropped_ack);
+ TFTPD_TC_ADD(tp, rrq_dropped_data);
+ TFTPD_TC_ADD(tp, rrq_duped_ack);
+ TFTPD_TC_ADD(tp, rrq_eaccess);
+ TFTPD_TC_ADD(tp, rrq_empty);
+ TFTPD_TC_ADD(tp, rrq_medium);
+ TFTPD_TC_ADD(tp, rrq_medium_window);
+ TFTPD_TC_ADD(tp, rrq_netascii);
+ TFTPD_TC_ADD(tp, rrq_nonexistent);
+ TFTPD_TC_ADD(tp, rrq_path_max);
+ TFTPD_TC_ADD(tp, rrq_small);
+ TFTPD_TC_ADD(tp, rrq_window_rfc7440);
+ TFTPD_TC_ADD(tp, unknown_modes);
+ TFTPD_TC_ADD(tp, unknown_opcode);
+ TFTPD_TC_ADD(tp, w_flag);
+ TFTPD_TC_ADD(tp, wrq_dropped_ack);
+ TFTPD_TC_ADD(tp, wrq_dropped_data);
+ TFTPD_TC_ADD(tp, wrq_duped_data);
+ TFTPD_TC_ADD(tp, wrq_eaccess);
+ TFTPD_TC_ADD(tp, wrq_eaccess_world_readable);
+ TFTPD_TC_ADD(tp, wrq_medium);
+ TFTPD_TC_ADD(tp, wrq_medium_window);
+ TFTPD_TC_ADD(tp, wrq_netascii);
+ TFTPD_TC_ADD(tp, wrq_nonexistent);
+ TFTPD_TC_ADD(tp, wrq_small);
+ TFTPD_TC_ADD(tp, wrq_truncate);
+ TFTPD_TC_ADD(tp, wrq_window_rfc7440);
+ TFTPD_TC_ADD(tp, short_packet1);
+ TFTPD_TC_ADD(tp, short_packet2);
+ TFTPD_TC_ADD(tp, short_packet3);
+
+ return (atf_no_error());
+}
diff --git a/libexec/tftpd/tftp-file.c b/libexec/tftpd/tftp-file.c
new file mode 100644
index 000000000000..405d1b9018c9
--- /dev/null
+++ b/libexec/tftpd/tftp-file.c
@@ -0,0 +1,299 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/tftp.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "tftp-file.h"
+#include "tftp-utils.h"
+
+static FILE *file;
+static int convert;
+
+static char convbuffer[66000];
+static int gotcr = 0;
+
+static size_t
+convert_from_net(char *buffer, size_t count)
+{
+ size_t i, n;
+
+ /*
+ * Convert all CR/LF to LF and all CR,NUL to CR
+ */
+
+ n = 0;
+ for (i = 0; i < count; i++) {
+
+ if (gotcr == 0) {
+ convbuffer[n++] = buffer[i];
+ gotcr = (buffer[i] == '\r');
+ continue;
+ }
+
+ /* CR, NULL -> CR */
+ if (buffer[i] == '\0') {
+ gotcr = 0;
+ continue;
+ }
+
+ /* CR, LF -> LF */
+ if (buffer[i] == '\n') {
+ if (n == 0) {
+ if (ftell(file) != 0) {
+ int r = fseek(file, -1, SEEK_END);
+ assert(r == 0);
+ convbuffer[n++] = '\n';
+ } else {
+ /* This shouldn't happen */
+ tftp_log(LOG_ERR,
+ "Received LF as first character");
+ abort();
+ }
+ } else
+ convbuffer[n-1] = '\n';
+ gotcr = 0;
+ continue;
+ }
+
+ /* Everything else just accept as is */
+ convbuffer[n++] = buffer[i];
+ gotcr = (buffer[i] == '\r');
+ continue;
+ }
+
+ return fwrite(convbuffer, 1, n, file);
+}
+
+static size_t
+convert_to_net(char *buffer, size_t count, int init)
+{
+ size_t i;
+ static size_t n = 0, in = 0;
+ static int newline = -1;
+
+ if (init) {
+ newline = -1;
+ n = 0;
+ in = 0;
+ return 0 ;
+ }
+
+ /*
+ * Convert all LF to CR,LF and all CR to CR,NUL
+ */
+ i = 0;
+
+ if (newline != -1) {
+ buffer[i++] = newline;
+ newline = -1;
+ }
+
+ while (i < count) {
+ if (n == in) {
+ /* When done we're done */
+ if (feof(file)) break;
+
+ /* Otherwise read another bunch */
+ in = fread(convbuffer, 1, count, file);
+ if (in == 0) break;
+ n = 0;
+ }
+
+ /* CR -> CR,NULL */
+ if (convbuffer[n] == '\r') {
+ buffer[i++] = '\r';
+ buffer[i++] = '\0';
+ n++;
+ continue;
+ }
+
+ /* LF -> CR,LF */
+ if (convbuffer[n] == '\n') {
+ buffer[i++] = '\r';
+ buffer[i++] = '\n';
+ n++;
+ continue;
+ }
+
+ buffer[i++] = convbuffer[n++];
+ }
+
+ if (i > count) {
+ /*
+ * Whoops... that isn't allowed (but it will happen
+ * when there is a CR or LF at the end of the buffer)
+ */
+ newline = buffer[i-1];
+ }
+
+ if (i < count) {
+ /* We are done! */
+ return i;
+ } else
+ return count;
+
+}
+
+int
+write_init(int fd, FILE *f, const char *mode)
+{
+
+ if (f == NULL) {
+ file = fdopen(fd, "w");
+ if (file == NULL) {
+ int en = errno;
+ tftp_log(LOG_ERR, "fdopen() failed: %s",
+ strerror(errno));
+ return en;
+ }
+ } else
+ file = f;
+ convert = !strcmp(mode, "netascii");
+ return 0;
+}
+
+size_t
+write_file(char *buffer, int count)
+{
+
+ if (convert == 0)
+ return fwrite(buffer, 1, count, file);
+
+ return convert_from_net(buffer, count);
+}
+
+int
+write_close(void)
+{
+
+ if (fclose(file) != 0) {
+ tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+off_t
+tell_file(void)
+{
+
+ return ftello(file);
+}
+
+int
+seek_file(off_t offset)
+{
+
+ return fseeko(file, offset, SEEK_SET);
+}
+
+int
+read_init(int fd, FILE *f, const char *mode)
+{
+
+ convert_to_net(NULL, 0, 1);
+ if (f == NULL) {
+ file = fdopen(fd, "r");
+ if (file == NULL) {
+ int en = errno;
+ tftp_log(LOG_ERR, "fdopen() failed: %s",
+ strerror(errno));
+ return en;
+ }
+ } else
+ file = f;
+ convert = !strcmp(mode, "netascii");
+ return 0;
+}
+
+size_t
+read_file(char *buffer, int count)
+{
+
+ if (convert == 0)
+ return fread(buffer, 1, count, file);
+
+ return convert_to_net(buffer, count, 0);
+}
+
+int
+read_close(void)
+{
+
+ if (fclose(file) != 0) {
+ tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+
+/* When an error has occurred, it is possible that the two sides
+ * are out of synch. Ie: that what I think is the other side's
+ * response to packet N is really their response to packet N-1.
+ *
+ * So, to try to prevent that, we flush all the input queued up
+ * for us on the network connection on our host.
+ *
+ * We return the number of packets we flushed (mostly for reporting
+ * when trace is active).
+ */
+
+int
+synchnet(int peer) /* socket to flush */
+{
+ int i, j = 0;
+ char rbuf[MAXPKTSIZE];
+ struct sockaddr_storage from;
+ socklen_t fromlen;
+
+ while (1) {
+ (void) ioctl(peer, FIONREAD, &i);
+ if (i) {
+ j++;
+ fromlen = sizeof from;
+ (void) recvfrom(peer, rbuf, sizeof (rbuf), 0,
+ (struct sockaddr *)&from, &fromlen);
+ } else {
+ return(j);
+ }
+ }
+}
diff --git a/libexec/tftpd/tftp-file.h b/libexec/tftpd/tftp-file.h
new file mode 100644
index 000000000000..c424e5cbc75b
--- /dev/null
+++ b/libexec/tftpd/tftp-file.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+int write_init(int fd, FILE *f, const char *mode);
+size_t write_file(char *buffer, int count);
+int write_close(void);
+
+int read_init(int fd, FILE *f, const char *mode);
+size_t read_file(char *buffer, int count);
+int read_close(void);
+
+int seek_file(off_t offset);
+off_t tell_file(void);
+
+int synchnet(int peer);
diff --git a/libexec/tftpd/tftp-io.c b/libexec/tftpd/tftp-io.c
new file mode 100644
index 000000000000..50102e652d2f
--- /dev/null
+++ b/libexec/tftpd/tftp-io.c
@@ -0,0 +1,446 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/tftp.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "tftp-file.h"
+#include "tftp-io.h"
+#include "tftp-utils.h"
+#include "tftp-options.h"
+
+struct sockaddr_storage peer_sock;
+struct sockaddr_storage me_sock;
+
+static int send_packet(int peer, uint16_t block, char *pkt, int size);
+
+static 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, NULL }
+};
+
+#define DROPPACKET(s) \
+ if (packetdroppercentage != 0 && \
+ arc4random()%100 < packetdroppercentage) { \
+ tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \
+ return; \
+ }
+#define DROPPACKETn(s,n) \
+ if (packetdroppercentage != 0 && \
+ arc4random()%100 < packetdroppercentage) { \
+ tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \
+ return (n); \
+ }
+
+const char *
+errtomsg(int error)
+{
+ static char ebuf[40];
+ 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(ebuf), "error %d", error);
+ return (ebuf);
+}
+
+static int
+send_packet(int peer, uint16_t block, char *pkt, int size)
+{
+ int i;
+ int t = 1;
+
+ for (i = 0; i < 12 ; i++) {
+ DROPPACKETn("send_packet", 0);
+
+ if (sendto(peer, pkt, size, 0, (struct sockaddr *)&peer_sock,
+ peer_sock.ss_len) == size) {
+ if (i)
+ tftp_log(LOG_ERR,
+ "%s block %d, attempt %d successful",
+ packettype(ntohs(((struct tftphdr *)
+ (pkt))->th_opcode)), block, i);
+ return (0);
+ }
+ tftp_log(LOG_ERR,
+ "%s block %d, attempt %d failed (Error %d: %s)",
+ packettype(ntohs(((struct tftphdr *)(pkt))->th_opcode)),
+ block, i, errno, strerror(errno));
+ sleep(t);
+ if (t < 32)
+ t <<= 1;
+ }
+ tftp_log(LOG_ERR, "send_packet: %s", strerror(errno));
+ return (1);
+}
+
+/*
+ * Send an ERROR packet (error message).
+ * Error code passed in is one of the
+ * standard TFTP codes, or a UNIX errno
+ * offset by 100.
+ */
+void
+send_error(int peer, int error)
+{
+ struct tftphdr *tp;
+ int length;
+ struct errmsg *pe;
+ char buf[MAXPKTSIZE];
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending ERROR %d", error);
+
+ DROPPACKET("send_error");
+
+ 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 */
+ }
+ snprintf(tp->th_msg, MAXPKTSIZE - 4, "%s%n", pe->e_msg, &length);
+ length += 5; /* header and terminator */
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending ERROR %d: %s", error, tp->th_msg);
+
+ if (sendto(peer, buf, length, 0,
+ (struct sockaddr *)&peer_sock, peer_sock.ss_len) != length)
+ tftp_log(LOG_ERR, "send_error: %s", strerror(errno));
+}
+
+/*
+ * Send an WRQ packet (write request).
+ */
+int
+send_wrq(int peer, char *filename, char *mode)
+{
+ int n;
+ struct tftphdr *tp;
+ char *bp;
+ char buf[MAXPKTSIZE];
+ int size;
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending WRQ: filename: '%s', mode '%s'",
+ filename, mode
+ );
+
+ DROPPACKETn("send_wrq", 0);
+
+ tp = (struct tftphdr *)buf;
+ tp->th_opcode = htons((u_short)WRQ);
+ size = offsetof(struct tftphdr, th_stuff);
+
+ bp = tp->th_stuff;
+ strlcpy(bp, filename, sizeof(buf) - size);
+ bp += strlen(filename);
+ *bp = 0;
+ bp++;
+ size += strlen(filename) + 1;
+
+ strlcpy(bp, mode, sizeof(buf) - size);
+ bp += strlen(mode);
+ *bp = 0;
+ bp++;
+ size += strlen(mode) + 1;
+
+ if (options_rfc_enabled)
+ size += make_options(peer, bp, sizeof(buf) - size);
+
+ n = sendto(peer, buf, size, 0,
+ (struct sockaddr *)&peer_sock, peer_sock.ss_len);
+ if (n != size) {
+ tftp_log(LOG_ERR, "send_wrq: %s", strerror(errno));
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Send an RRQ packet (write request).
+ */
+int
+send_rrq(int peer, char *filename, char *mode)
+{
+ int n;
+ struct tftphdr *tp;
+ char *bp;
+ char buf[MAXPKTSIZE];
+ int size;
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending RRQ: filename: '%s', mode '%s'",
+ filename, mode
+ );
+
+ DROPPACKETn("send_rrq", 0);
+
+ tp = (struct tftphdr *)buf;
+ tp->th_opcode = htons((u_short)RRQ);
+ size = offsetof(struct tftphdr, th_stuff);
+
+ bp = tp->th_stuff;
+ strlcpy(bp, filename, sizeof(buf) - size);
+ bp += strlen(filename);
+ *bp = 0;
+ bp++;
+ size += strlen(filename) + 1;
+
+ strlcpy(bp, mode, sizeof(buf) - size);
+ bp += strlen(mode);
+ *bp = 0;
+ bp++;
+ size += strlen(mode) + 1;
+
+ if (options_rfc_enabled) {
+ options_set_request(OPT_TSIZE, "0");
+ size += make_options(peer, bp, sizeof(buf) - size);
+ }
+
+ n = sendto(peer, buf, size, 0,
+ (struct sockaddr *)&peer_sock, peer_sock.ss_len);
+ if (n != size) {
+ tftp_log(LOG_ERR, "send_rrq: %d %s", n, strerror(errno));
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Send an OACK packet (option acknowledgement).
+ */
+int
+send_oack(int peer)
+{
+ struct tftphdr *tp;
+ int size, i, n;
+ char *bp;
+ char buf[MAXPKTSIZE];
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending OACK");
+
+ DROPPACKETn("send_oack", 0);
+
+ /*
+ * Send back an options acknowledgement (only the ones with
+ * a reply for)
+ */
+ 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_reply != NULL) {
+ n = snprintf(bp, size, "%s%c%s", options[i].o_type,
+ 0, options[i].o_reply);
+ bp += n+1;
+ size -= n+1;
+ if (size < 0) {
+ tftp_log(LOG_ERR, "oack: buffer overflow");
+ exit(1);
+ }
+ }
+ }
+ size = bp - buf;
+
+ if (sendto(peer, buf, size, 0,
+ (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) {
+ tftp_log(LOG_INFO, "send_oack: %s", strerror(errno));
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Send an ACK packet (acknowledgement).
+ */
+int
+send_ack(int fp, uint16_t block)
+{
+ struct tftphdr *tp;
+ int size;
+ char buf[MAXPKTSIZE];
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending ACK for block %d", block);
+
+ DROPPACKETn("send_ack", 0);
+
+ tp = (struct tftphdr *)buf;
+ tp->th_opcode = htons((u_short)ACK);
+ tp->th_block = htons((u_short)block);
+ size = 4;
+
+ if (sendto(fp, buf, size, 0,
+ (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) {
+ tftp_log(LOG_INFO, "send_ack: %s", strerror(errno));
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Send a DATA packet
+ */
+int
+send_data(int peer, uint16_t block, char *data, int size)
+{
+ char buf[MAXPKTSIZE];
+ struct tftphdr *pkt;
+ int n;
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Sending DATA packet %d of %d bytes",
+ block, size);
+
+ DROPPACKETn("send_data", 0);
+
+ pkt = (struct tftphdr *)buf;
+
+ pkt->th_opcode = htons((u_short)DATA);
+ pkt->th_block = htons((u_short)block);
+ memcpy(pkt->th_data, data, size);
+
+ n = send_packet(peer, block, (char *)pkt, size + 4);
+ return (n);
+}
+
+
+/*
+ * Receive a packet
+ *
+ * If timeout is negative, no error will be logged on timeout.
+ */
+int
+receive_packet(int peer, char *data, int size, struct sockaddr_storage *from,
+ int timeout)
+{
+ struct pollfd pfd;
+ struct tftphdr *pkt;
+ struct sockaddr_storage from_local;
+ struct sockaddr_storage *pfrom;
+ socklen_t fromlen;
+ int n;
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG,
+ "Waiting %d seconds for packet", timeoutpacket);
+
+ pkt = (struct tftphdr *)data;
+
+ pfd.fd = peer;
+ pfd.events = POLLIN;
+ if (poll(&pfd, 1, 1000 * (timeout < 0 ? -timeout : timeout)) < 1) {
+ if (timeout > 0)
+ tftp_log(LOG_ERR, "receive_packet: timeout");
+ return (RP_TIMEOUT);
+ }
+
+ pfrom = (from == NULL) ? &from_local : from;
+ fromlen = sizeof(*pfrom);
+ n = recvfrom(peer, data, size, 0, (struct sockaddr *)pfrom, &fromlen);
+
+ DROPPACKETn("receive_packet", RP_TIMEOUT);
+
+ if (n < 0) {
+ /* No idea what could have happened if it isn't a timeout */
+ tftp_log(LOG_ERR, "receive_packet: %s", strerror(errno));
+ return (RP_RECVFROM);
+ }
+ if (n < 4) {
+ tftp_log(LOG_ERR,
+ "receive_packet: packet too small (%d bytes)", n);
+ return (RP_TOOSMALL);
+ }
+
+ pkt->th_opcode = ntohs((u_short)pkt->th_opcode);
+ if (pkt->th_opcode == DATA ||
+ pkt->th_opcode == ACK)
+ pkt->th_block = ntohs((u_short)pkt->th_block);
+
+ if (pkt->th_opcode == DATA && n > pktsize) {
+ tftp_log(LOG_ERR, "receive_packet: packet too big");
+ return (RP_TOOBIG);
+ }
+
+ if (((struct sockaddr_in *)(pfrom))->sin_addr.s_addr !=
+ ((struct sockaddr_in *)(&peer_sock))->sin_addr.s_addr) {
+ tftp_log(LOG_ERR,
+ "receive_packet: received packet from wrong source");
+ return (RP_WRONGSOURCE);
+ }
+
+ if (pkt->th_opcode == ERROR) {
+ tftp_log(pkt->th_code == EUNDEF ? LOG_DEBUG : LOG_ERR,
+ "Got ERROR packet: %s", pkt->th_msg);
+ return (RP_ERROR);
+ }
+
+ if (debug & DEBUG_PACKETS)
+ tftp_log(LOG_DEBUG, "Received %d bytes in a %s packet",
+ n, packettype(pkt->th_opcode));
+
+ return n - 4;
+}
diff --git a/libexec/tftpd/tftp-io.h b/libexec/tftpd/tftp-io.h
new file mode 100644
index 000000000000..1d6bc2bd8b5e
--- /dev/null
+++ b/libexec/tftpd/tftp-io.h
@@ -0,0 +1,46 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#define RP_NONE 0
+#define RP_RECVFROM -1
+#define RP_TOOSMALL -2
+#define RP_ERROR -3
+#define RP_WRONGSOURCE -4
+#define RP_TIMEOUT -5
+#define RP_TOOBIG -6
+
+const char *errtomsg(int);
+void send_error(int peer, int);
+int send_wrq(int peer, char *, char *);
+int send_rrq(int peer, char *, char *);
+int send_oack(int peer);
+int send_ack(int peer, unsigned short);
+int send_data(int peer, uint16_t, char *, int);
+int receive_packet(int peer, char *, int, struct sockaddr_storage *, int);
+
+extern struct sockaddr_storage peer_sock;
+extern struct sockaddr_storage me_sock;
diff --git a/libexec/tftpd/tftp-options.c b/libexec/tftpd/tftp-options.c
new file mode 100644
index 000000000000..7a261ac3d7c3
--- /dev/null
+++ b/libexec/tftpd/tftp-options.c
@@ -0,0 +1,477 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <arpa/tftp.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "tftp-utils.h"
+#include "tftp-io.h"
+#include "tftp-options.h"
+
+/*
+ * Option handlers
+ */
+
+struct options options[] = {
+ { "tsize", NULL, NULL, NULL /* option_tsize */, 1 },
+ { "timeout", NULL, NULL, option_timeout, 1 },
+ { "blksize", NULL, NULL, option_blksize, 1 },
+ { "blksize2", NULL, NULL, option_blksize2, 0 },
+ { "rollover", NULL, NULL, option_rollover, 0 },
+ { "windowsize", NULL, NULL, option_windowsize, 1 },
+ { NULL, NULL, NULL, NULL, 0 }
+};
+
+/* By default allow them */
+int options_rfc_enabled = 1;
+int options_extra_enabled = 1;
+
+int
+options_set_request(enum opt_enum opt, const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+ int ret;
+
+ if (fmt == NULL) {
+ str = NULL;
+ } else {
+ va_start(ap, fmt);
+ ret = vasprintf(&str, fmt, ap);
+ va_end(ap);
+ if (ret < 0)
+ return (ret);
+ }
+ if (options[opt].o_request != NULL &&
+ options[opt].o_request != options[opt].o_reply)
+ free(options[opt].o_request);
+ options[opt].o_request = str;
+ return (0);
+}
+
+int
+options_set_reply(enum opt_enum opt, const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+ int ret;
+
+ if (fmt == NULL) {
+ str = NULL;
+ } else {
+ va_start(ap, fmt);
+ ret = vasprintf(&str, fmt, ap);
+ va_end(ap);
+ if (ret < 0)
+ return (ret);
+ }
+ if (options[opt].o_reply != NULL &&
+ options[opt].o_reply != options[opt].o_request)
+ free(options[opt].o_reply);
+ options[opt].o_reply = str;
+ return (0);
+}
+
+static void
+options_set_reply_equal_request(enum opt_enum opt)
+{
+
+ if (options[opt].o_reply != NULL &&
+ options[opt].o_reply != options[opt].o_request)
+ free(options[opt].o_reply);
+ options[opt].o_reply = options[opt].o_request;
+}
+
+/*
+ * Rules for the option handlers:
+ * - If there is no o_request, there will be no processing.
+ *
+ * For servers
+ * - Logging is done as warnings.
+ * - The handler exit()s if there is a serious problem with the
+ * values submitted in the option.
+ *
+ * For clients
+ * - Logging is done as errors. After all, the server shouldn't
+ * return rubbish.
+ * - The handler returns if there is a serious problem with the
+ * values submitted in the option.
+ * - Sending the EBADOP packets is done by the handler.
+ */
+
+int
+option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
+ struct stat *stbuf)
+{
+
+ if (options[OPT_TSIZE].o_request == NULL)
+ return (0);
+
+ if (mode == RRQ)
+ options_set_reply(OPT_TSIZE, "%ju", (uintmax_t)stbuf->st_size);
+ else
+ /* XXX Allows writes of all sizes. */
+ options_set_reply_equal_request(OPT_TSIZE);
+ return (0);
+}
+
+int
+option_timeout(int peer)
+{
+ int to;
+
+ if (options[OPT_TIMEOUT].o_request == NULL)
+ return (0);
+
+ to = atoi(options[OPT_TIMEOUT].o_request);
+ if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
+ tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
+ "Received bad value for timeout. "
+ "Should be between %d and %d, received %d",
+ TIMEOUT_MIN, TIMEOUT_MAX, to);
+ send_error(peer, EBADOP);
+ if (acting_as_client)
+ return (1);
+ exit(1);
+ } else {
+ timeoutpacket = to;
+ options_set_reply_equal_request(OPT_TIMEOUT);
+ }
+ settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
+
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
+ options[OPT_TIMEOUT].o_reply);
+
+ return (0);
+}
+
+int
+option_rollover(int peer)
+{
+
+ if (options[OPT_ROLLOVER].o_request == NULL)
+ return (0);
+
+ if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
+ && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
+ tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
+ "Bad value for rollover, "
+ "should be either 0 or 1, received '%s', "
+ "ignoring request",
+ options[OPT_ROLLOVER].o_request);
+ if (acting_as_client) {
+ send_error(peer, EBADOP);
+ return (1);
+ }
+ return (0);
+ }
+ options_set_reply_equal_request(OPT_ROLLOVER);
+
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
+ options[OPT_ROLLOVER].o_reply);
+
+ return (0);
+}
+
+int
+option_blksize(int peer)
+{
+ u_long maxdgram;
+ size_t len;
+
+ if (options[OPT_BLKSIZE].o_request == NULL)
+ return (0);
+
+ /* maximum size of an UDP packet according to the system */
+ len = sizeof(maxdgram);
+ if (sysctlbyname("net.inet.udp.maxdgram",
+ &maxdgram, &len, NULL, 0) < 0) {
+ tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
+ return (acting_as_client ? 1 : 0);
+ }
+ maxdgram -= 4; /* leave room for header */
+
+ int size = atoi(options[OPT_BLKSIZE].o_request);
+ if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
+ if (acting_as_client) {
+ tftp_log(LOG_ERR,
+ "Invalid blocksize (%d bytes), aborting",
+ size);
+ send_error(peer, EBADOP);
+ return (1);
+ } else {
+ tftp_log(LOG_WARNING,
+ "Invalid blocksize (%d bytes), ignoring request",
+ size);
+ return (0);
+ }
+ }
+
+ if (size > (int)maxdgram) {
+ if (acting_as_client) {
+ tftp_log(LOG_ERR,
+ "Invalid blocksize (%d bytes), "
+ "net.inet.udp.maxdgram sysctl limits it to "
+ "%ld bytes.\n", size, maxdgram);
+ send_error(peer, EBADOP);
+ return (1);
+ } else {
+ tftp_log(LOG_WARNING,
+ "Invalid blocksize (%d bytes), "
+ "net.inet.udp.maxdgram sysctl limits it to "
+ "%ld bytes.\n", size, maxdgram);
+ size = maxdgram;
+ /* No reason to return */
+ }
+ }
+
+ options_set_reply(OPT_BLKSIZE, "%d", size);
+ segsize = size;
+ pktsize = size + 4;
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
+ options[OPT_BLKSIZE].o_reply);
+
+ return (0);
+}
+
+int
+option_blksize2(int peer __unused)
+{
+ u_long maxdgram;
+ int size, i;
+ size_t len;
+
+ int sizes[] = {
+ 8, 16, 32, 64, 128, 256, 512, 1024,
+ 2048, 4096, 8192, 16384, 32768, 0
+ };
+
+ if (options[OPT_BLKSIZE2].o_request == NULL)
+ return (0);
+
+ /* maximum size of an UDP packet according to the system */
+ len = sizeof(maxdgram);
+ if (sysctlbyname("net.inet.udp.maxdgram",
+ &maxdgram, &len, NULL, 0) < 0) {
+ tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
+ return (acting_as_client ? 1 : 0);
+ }
+
+ size = atoi(options[OPT_BLKSIZE2].o_request);
+ for (i = 0; sizes[i] != 0; i++) {
+ if (size == sizes[i]) break;
+ }
+ if (sizes[i] == 0) {
+ tftp_log(LOG_INFO,
+ "Invalid blocksize2 (%d bytes), ignoring request", size);
+ return (acting_as_client ? 1 : 0);
+ }
+
+ if (size > (int)maxdgram) {
+ for (i = 0; sizes[i+1] != 0; i++) {
+ if ((int)maxdgram < sizes[i+1]) break;
+ }
+ tftp_log(LOG_INFO,
+ "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
+ "sysctl limits it to %ld bytes.\n", size, maxdgram);
+ size = sizes[i];
+ /* No need to return */
+ }
+
+ options_set_reply(OPT_BLKSIZE2, "%d", size);
+ segsize = size;
+ pktsize = size + 4;
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
+ options[OPT_BLKSIZE2].o_reply);
+
+ return (0);
+}
+
+int
+option_windowsize(int peer)
+{
+ int size;
+
+ if (options[OPT_WINDOWSIZE].o_request == NULL)
+ return (0);
+
+ size = atoi(options[OPT_WINDOWSIZE].o_request);
+ if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
+ if (acting_as_client) {
+ tftp_log(LOG_ERR,
+ "Invalid windowsize (%d blocks), aborting",
+ size);
+ send_error(peer, EBADOP);
+ return (1);
+ } else {
+ tftp_log(LOG_WARNING,
+ "Invalid windowsize (%d blocks), ignoring request",
+ size);
+ return (0);
+ }
+ }
+
+ /* XXX: Should force a windowsize of 1 for non-seekable files. */
+ options_set_reply(OPT_WINDOWSIZE, "%d", size);
+ windowsize = size;
+
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
+ options[OPT_WINDOWSIZE].o_reply);
+
+ return (0);
+}
+
+/*
+ * Append the available options to the header
+ */
+uint16_t
+make_options(int peer __unused, char *buffer, uint16_t size) {
+ int i;
+ char *value;
+ const char *option;
+ uint16_t length;
+ uint16_t returnsize = 0;
+
+ if (!options_rfc_enabled) return (0);
+
+ for (i = 0; options[i].o_type != NULL; i++) {
+ if (options[i].rfc == 0 && !options_extra_enabled)
+ continue;
+
+ option = options[i].o_type;
+ if (acting_as_client)
+ value = options[i].o_request;
+ else
+ value = options[i].o_reply;
+ if (value == NULL)
+ continue;
+
+ length = strlen(value) + strlen(option) + 2;
+ if (size <= length) {
+ tftp_log(LOG_ERR,
+ "Running out of option space for "
+ "option '%s' with value '%s': "
+ "needed %d bytes, got %d bytes",
+ option, value, size, length);
+ continue;
+ }
+
+ sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
+ size -= length;
+ buffer += length;
+ returnsize += length;
+ }
+
+ return (returnsize);
+}
+
+/*
+ * Parse the received options in the header
+ */
+int
+parse_options(int peer, char *buffer, uint16_t size)
+{
+ int i, options_failed;
+ char *c, *cp, *option, *value;
+
+ if (!options_rfc_enabled) return (0);
+
+ /* Parse the options */
+ cp = buffer;
+ options_failed = 0;
+ while (size > 0) {
+ option = cp;
+ i = get_field(peer, cp, size);
+ cp += i;
+
+ value = cp;
+ i = get_field(peer, cp, size);
+ cp += i;
+
+ /* We are at the end */
+ if (*option == '\0') break;
+
+ if (debug & DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG,
+ "option: '%s' value: '%s'", option, value);
+
+ for (c = option; *c; c++)
+ if (isupper(*c))
+ *c = tolower(*c);
+ for (i = 0; options[i].o_type != NULL; i++) {
+ if (strcmp(option, options[i].o_type) == 0) {
+ if (!acting_as_client)
+ options_set_request(i, "%s", value);
+ if (!options_extra_enabled && !options[i].rfc) {
+ tftp_log(LOG_INFO,
+ "Option '%s' with value '%s' found "
+ "but it is not an RFC option",
+ option, value);
+ continue;
+ }
+ if (options[i].o_handler)
+ options_failed +=
+ (options[i].o_handler)(peer);
+ break;
+ }
+ }
+ if (options[i].o_type == NULL)
+ tftp_log(LOG_WARNING,
+ "Unknown option: '%s'", option);
+
+ size -= strlen(option) + strlen(value) + 2;
+ }
+
+ return (options_failed);
+}
+
+/*
+ * Set some default values in the options
+ */
+void
+init_options(void)
+{
+
+ options_set_request(OPT_ROLLOVER, "0");
+}
diff --git a/libexec/tftpd/tftp-options.h b/libexec/tftpd/tftp-options.h
new file mode 100644
index 000000000000..f1b0a5cfaf32
--- /dev/null
+++ b/libexec/tftpd/tftp-options.h
@@ -0,0 +1,68 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+/*
+ * Options
+ */
+
+void init_options(void);
+uint16_t make_options(int peer, char *buffer, uint16_t size);
+int parse_options(int peer, char *buffer, uint16_t size);
+
+/* Call back functions */
+int option_tsize(int peer, struct tftphdr *, int, struct stat *);
+int option_timeout(int peer);
+int option_blksize(int peer);
+int option_blksize2(int peer);
+int option_rollover(int peer);
+int option_windowsize(int peer);
+
+extern int options_extra_enabled;
+extern int options_rfc_enabled;
+
+struct options {
+ const char *o_type;
+ char *o_request;
+ char *o_reply;
+ int (*o_handler)(int peer);
+ int rfc;
+};
+
+extern struct options options[];
+enum opt_enum {
+ OPT_TSIZE = 0,
+ OPT_TIMEOUT,
+ OPT_BLKSIZE,
+ OPT_BLKSIZE2,
+ OPT_ROLLOVER,
+ OPT_WINDOWSIZE,
+};
+
+int options_set_request(enum opt_enum, const char *, ...)
+ __printf0like(2, 3);
+int options_set_reply(enum opt_enum, const char *, ...)
+ __printf0like(2, 3);
diff --git a/libexec/tftpd/tftp-transfer.c b/libexec/tftpd/tftp-transfer.c
new file mode 100644
index 000000000000..ea386f8a3c1a
--- /dev/null
+++ b/libexec/tftpd/tftp-transfer.c
@@ -0,0 +1,446 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/tftp.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "tftp-file.h"
+#include "tftp-io.h"
+#include "tftp-utils.h"
+#include "tftp-options.h"
+#include "tftp-transfer.h"
+
+struct block_data {
+ off_t offset;
+ uint16_t block;
+ int size;
+};
+
+/*
+ * Send a file via the TFTP data session.
+ */
+int
+tftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
+{
+ struct tftphdr *rp;
+ int size, n_data, n_ack, sendtry, acktry;
+ u_int i, j;
+ uint16_t oldblock, windowblock;
+ char sendbuffer[MAXPKTSIZE];
+ char recvbuffer[MAXPKTSIZE];
+ struct block_data window[WINDOWSIZE_MAX];
+
+ rp = (struct tftphdr *)recvbuffer;
+ *block = 1;
+ ts->amount = 0;
+ windowblock = 0;
+ acktry = 0;
+ do {
+read_block:
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Sending block %d (window block %d)",
+ *block, windowblock);
+
+ window[windowblock].offset = tell_file();
+ window[windowblock].block = *block;
+ size = read_file(sendbuffer, segsize);
+ if (size < 0) {
+ tftp_log(LOG_ERR, "read_file returned %d", size);
+ send_error(peer, errno + 100);
+ return -1;
+ }
+ window[windowblock].size = size;
+ windowblock++;
+
+ for (sendtry = 0; ; sendtry++) {
+ n_data = send_data(peer, *block, sendbuffer, size);
+ if (n_data == 0)
+ break;
+
+ if (sendtry == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Cannot send DATA packet #%d, "
+ "giving up", *block);
+ return -1;
+ }
+ tftp_log(LOG_ERR,
+ "Cannot send DATA packet #%d, trying again",
+ *block);
+ }
+
+ /* Only check for ACK for last block in window. */
+ if (windowblock == windowsize || size != segsize) {
+ n_ack = receive_packet(peer, recvbuffer,
+ MAXPKTSIZE, NULL, timeoutpacket);
+ if (n_ack < 0) {
+ if (n_ack == RP_TIMEOUT) {
+ if (acktry == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Timeout #%d send ACK %d "
+ "giving up", acktry, *block);
+ return -1;
+ }
+ tftp_log(LOG_WARNING,
+ "Timeout #%d on ACK %d",
+ acktry, *block);
+
+ acktry++;
+ ts->retries++;
+ if (seek_file(window[0].offset) != 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ return -1;
+ }
+ *block = window[0].block;
+ windowblock = 0;
+ goto read_block;
+ }
+
+ /* Either read failure or ERROR packet */
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_ERR, "Aborting: %s",
+ rp_strerror(n_ack));
+ return -1;
+ }
+ if (rp->th_opcode == ACK) {
+ /*
+ * Look for the ACKed block in our open
+ * window.
+ */
+ for (i = 0; i < windowblock; i++) {
+ if (rp->th_block == window[i].block)
+ break;
+ }
+
+ if (i == windowblock) {
+ /* Did not recognize ACK. */
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACK %d out of window",
+ rp->th_block);
+
+ /* Re-synchronize with the other side */
+ (void) synchnet(peer);
+
+ /* Resend the current window. */
+ ts->retries++;
+ if (seek_file(window[0].offset) != 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ return -1;
+ }
+ *block = window[0].block;
+ windowblock = 0;
+ goto read_block;
+ }
+
+ /* ACKed at least some data. */
+ acktry = 0;
+ for (j = 0; j <= i; j++) {
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACKed block %d",
+ window[j].block);
+ ts->blocks++;
+ ts->amount += window[j].size;
+ }
+
+ /*
+ * Partial ACK. Rewind state to first
+ * un-ACKed block.
+ */
+ if (i + 1 != windowblock) {
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Partial ACK");
+ if (seek_file(window[i + 1].offset) !=
+ 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ return -1;
+ }
+ *block = window[i + 1].block;
+ windowblock = 0;
+ ts->retries++;
+ goto read_block;
+ }
+
+ windowblock = 0;
+ }
+
+ }
+ oldblock = *block;
+ (*block)++;
+ if (oldblock > *block) {
+ if (options[OPT_ROLLOVER].o_request == NULL) {
+ /*
+ * "rollover" option not specified in
+ * tftp client. Default to rolling block
+ * counter to 0.
+ */
+ *block = 0;
+ } else {
+ *block = atoi(options[OPT_ROLLOVER].o_request);
+ }
+
+ ts->rollovers++;
+ }
+ gettimeofday(&(ts->tstop), NULL);
+ } while (size == segsize);
+ return 0;
+}
+
+/*
+ * Receive a file via the TFTP data session.
+ *
+ * - It could be that the first block has already arrived while
+ * trying to figure out if we were receiving options or not. In
+ * that case it is passed to this function.
+ */
+int
+tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
+ struct tftphdr *firstblock, size_t fb_size)
+{
+ struct tftphdr *rp;
+ uint16_t oldblock, windowstart;
+ int n_data, n_ack, writesize, i, retry, windowblock;
+ char recvbuffer[MAXPKTSIZE];
+
+ ts->amount = 0;
+ windowblock = 0;
+
+ if (firstblock != NULL) {
+ writesize = write_file(firstblock->th_data, fb_size);
+ ts->amount += writesize;
+ ts->blocks++;
+ windowblock++;
+ if (windowsize == 1 || fb_size != segsize) {
+ for (i = 0; ; i++) {
+ n_ack = send_ack(peer, *block);
+ if (n_ack > 0) {
+ if (i == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, "
+ "giving up", *block);
+ return -1;
+ }
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, trying again",
+ *block);
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (fb_size != segsize) {
+ write_close();
+ gettimeofday(&(ts->tstop), NULL);
+ return 0;
+ }
+ }
+
+ rp = (struct tftphdr *)recvbuffer;
+ do {
+ oldblock = *block;
+ (*block)++;
+ if (oldblock > *block) {
+ if (options[OPT_ROLLOVER].o_request == NULL) {
+ /*
+ * "rollover" option not specified in
+ * tftp client. Default to rolling block
+ * counter to 0.
+ */
+ *block = 0;
+ } else {
+ *block = atoi(options[OPT_ROLLOVER].o_request);
+ }
+
+ ts->rollovers++;
+ }
+
+ for (retry = 0; ; retry++) {
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Receiving DATA block %d (window block %d)",
+ *block, windowblock);
+
+ n_data = receive_packet(peer, recvbuffer,
+ MAXPKTSIZE, NULL, timeoutpacket);
+ if (n_data < 0) {
+ if (retry == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Timeout #%d on DATA block %d, "
+ "giving up", retry, *block);
+ return -1;
+ }
+ if (n_data == RP_TIMEOUT) {
+ tftp_log(LOG_WARNING,
+ "Timeout #%d on DATA block %d",
+ retry, *block);
+ send_ack(peer, oldblock);
+ windowblock = 0;
+ continue;
+ }
+
+ /* Either read failure or ERROR packet */
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Aborting: %s",
+ rp_strerror(n_data));
+ return -1;
+ }
+ if (rp->th_opcode == DATA) {
+ ts->blocks++;
+
+ if (rp->th_block == *block)
+ break;
+
+ /*
+ * Ignore duplicate blocks within the
+ * window.
+ *
+ * This does not handle duplicate
+ * blocks during a rollover as
+ * gracefully, but that should still
+ * recover eventually.
+ */
+ if (*block > windowsize)
+ windowstart = *block - windowsize;
+ else
+ windowstart = 0;
+ if (rp->th_block > windowstart &&
+ rp->th_block < *block) {
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Ignoring duplicate DATA block %d",
+ rp->th_block);
+ windowblock++;
+ retry = 0;
+ continue;
+ }
+
+ tftp_log(LOG_WARNING,
+ "Expected DATA block %d, got block %d",
+ *block, rp->th_block);
+
+ /* Re-synchronize with the other side */
+ (void) synchnet(peer);
+
+ tftp_log(LOG_INFO, "Trying to sync");
+ *block = oldblock;
+ ts->retries++;
+ goto send_ack; /* rexmit */
+
+ } else {
+ tftp_log(LOG_WARNING,
+ "Expected DATA block, got %s block",
+ packettype(rp->th_opcode));
+ }
+ }
+
+ if (n_data > 0) {
+ writesize = write_file(rp->th_data, n_data);
+ ts->amount += writesize;
+ if (writesize <= 0) {
+ tftp_log(LOG_ERR,
+ "write_file returned %d", writesize);
+ if (writesize < 0)
+ send_error(peer, errno + 100);
+ else
+ send_error(peer, ENOSPACE);
+ return -1;
+ }
+ }
+ if (n_data != segsize)
+ write_close();
+ windowblock++;
+
+ /* Only send ACKs for the last block in the window. */
+ if (windowblock < windowsize && n_data == segsize)
+ continue;
+send_ack:
+ for (i = 0; ; i++) {
+ n_ack = send_ack(peer, *block);
+ if (n_ack > 0) {
+
+ if (i == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, "
+ "giving up", *block);
+ return -1;
+ }
+
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, trying again",
+ *block);
+ continue;
+ }
+
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
+ windowblock = 0;
+ break;
+ }
+ gettimeofday(&(ts->tstop), NULL);
+ } while (n_data == segsize);
+
+ /* Don't do late packet management for the client implementation */
+ if (acting_as_client)
+ return 0;
+
+ for (i = 0; ; i++) {
+ n_data = receive_packet(peer, (char *)rp, pktsize,
+ NULL, -timeoutpacket);
+ if (n_data <= 0)
+ break;
+ if (n_data > 0 &&
+ rp->th_opcode == DATA && /* and got a data block */
+ *block == rp->th_block) /* then my last ack was lost */
+ send_ack(peer, *block); /* resend final ack */
+ }
+
+ return 0;
+}
diff --git a/libexec/tftpd/tftp-transfer.h b/libexec/tftpd/tftp-transfer.h
new file mode 100644
index 000000000000..449f29c246e0
--- /dev/null
+++ b/libexec/tftpd/tftp-transfer.h
@@ -0,0 +1,30 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+int tftp_send(int peer, uint16_t *block, struct tftp_stats *tp);
+int tftp_receive(int peer, uint16_t *block, struct tftp_stats *tp,
+ struct tftphdr *firstblock, size_t fb_size);
diff --git a/libexec/tftpd/tftp-utils.c b/libexec/tftpd/tftp-utils.c
new file mode 100644
index 000000000000..8ce7c09c9992
--- /dev/null
+++ b/libexec/tftpd/tftp-utils.c
@@ -0,0 +1,328 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/tftp.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "tftp-utils.h"
+#include "tftp-io.h"
+
+/*
+ * Default values, can be changed later via the TFTP Options
+ */
+int timeoutpacket = TIMEOUT;
+int timeoutnetwork = MAX_TIMEOUTS * TIMEOUT;
+int maxtimeouts = MAX_TIMEOUTS;
+uint16_t segsize = SEGSIZE;
+uint16_t pktsize = SEGSIZE + 4;
+uint16_t windowsize = WINDOWSIZE;
+
+int acting_as_client;
+
+
+/*
+ * Set timeout values for packet reception. The idea is that you
+ * get 'maxtimeouts' of 5 seconds between 'timeoutpacket' (i.e. the
+ * first timeout) to 'timeoutnetwork' (i.e. the last timeout)
+ */
+int
+settimeouts(int _timeoutpacket, int _timeoutnetwork, int _maxtimeouts __unused)
+{
+ int i;
+
+ /* We cannot do impossible things */
+ if (_timeoutpacket >= _timeoutnetwork)
+ return (0);
+
+ maxtimeouts = 0;
+ i = _timeoutpacket;
+ while (i < _timeoutnetwork || maxtimeouts < MIN_TIMEOUTS) {
+ maxtimeouts++;
+ i += 5;
+ }
+
+ timeoutpacket = _timeoutpacket;
+ timeoutnetwork = i;
+ return (1);
+}
+
+/* translate IPv4 mapped IPv6 address to IPv4 address */
+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;
+ memcpy(&addr, &sin6->sin6_addr.s6_addr[12], sizeof(addr));
+ 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);
+}
+
+/* Get a field from a \0 separated string */
+size_t
+get_field(int peer, char *buffer, size_t size)
+{
+ char *cp = buffer;
+
+ while (cp < buffer + size) {
+ if (*cp == '\0') break;
+ cp++;
+ }
+ if (*cp != '\0') {
+ tftp_log(LOG_ERR, "Bad option - no trailing \\0 found");
+ send_error(peer, EBADOP);
+ exit(1);
+ }
+ return (cp - buffer + 1);
+}
+
+/*
+ * Logging functions
+ */
+static int _tftp_logtostdout = 1;
+
+void
+tftp_openlog(const char *ident, int logopt, int facility)
+{
+
+ _tftp_logtostdout = (ident == NULL);
+ if (_tftp_logtostdout == 0)
+ openlog(ident, logopt, facility);
+}
+
+void
+tftp_closelog(void)
+{
+
+ if (_tftp_logtostdout == 0)
+ closelog();
+}
+
+void
+tftp_log(int priority, const char *message, ...)
+{
+ va_list ap;
+ int serrno;
+ char *s;
+
+ serrno = errno;
+ va_start(ap, message);
+ if (_tftp_logtostdout == 0) {
+ vasprintf(&s, message, ap);
+ syslog(priority, "%s", s);
+ } else {
+ vprintf(message, ap);
+ printf("\n");
+ }
+ va_end(ap);
+ errno = serrno;
+}
+
+/*
+ * Packet types
+ */
+struct packettypes packettypes[] = {
+ { RRQ, "RRQ" },
+ { WRQ, "WRQ" },
+ { DATA, "DATA" },
+ { ACK, "ACK" },
+ { ERROR, "ERROR" },
+ { OACK, "OACK" },
+ { 0, NULL },
+};
+
+const char *
+packettype(int type)
+{
+ static char failed[100];
+ int i = 0;
+
+ while (packettypes[i].name != NULL) {
+ if (packettypes[i].value == type)
+ break;
+ i++;
+ }
+ if (packettypes[i].name != NULL)
+ return packettypes[i].name;
+ sprintf(failed, "unknown (type: %d)", type);
+ return (failed);
+}
+
+/*
+ * Debugs
+ */
+int debug = DEBUG_NONE;
+struct debugs debugs[] = {
+ { DEBUG_PACKETS, "packet", "Packet debugging" },
+ { DEBUG_SIMPLE, "simple", "Simple debugging" },
+ { DEBUG_OPTIONS, "options", "Options debugging" },
+ { DEBUG_ACCESS, "access", "TCPd access debugging" },
+ { DEBUG_NONE, NULL, "No debugging" },
+};
+unsigned int packetdroppercentage = 0;
+
+int
+debug_find(char *s)
+{
+ int i = 0;
+
+ while (debugs[i].name != NULL) {
+ if (strcasecmp(debugs[i].name, s) == 0)
+ break;
+ i++;
+ }
+ return (debugs[i].value);
+}
+
+int
+debug_finds(char *s)
+{
+ int i = 0;
+ char *ps = s;
+
+ while (s != NULL) {
+ ps = strchr(s, ' ');
+ if (ps != NULL)
+ *ps = '\0';
+ i += debug_find(s);
+ if (ps != NULL)
+ *ps = ' ';
+ s = ps;
+ }
+ return (i);
+}
+
+const char *
+debug_show(int d)
+{
+ static char s[100];
+ size_t space = sizeof(s);
+ int i = 0;
+
+ s[0] = '\0';
+ while (debugs[i].name != NULL) {
+ if (d&debugs[i].value) {
+ if (s[0] != '\0')
+ strlcat(s, " ", space);
+ strlcat(s, debugs[i].name, space);
+ }
+ i++;
+ }
+ if (s[0] != '\0')
+ return (s);
+ return ("none");
+}
+
+/*
+ * RP_
+ */
+struct rp_errors rp_errors[] = {
+ { RP_TIMEOUT, "Network timeout" },
+ { RP_TOOSMALL, "Not enough data bytes" },
+ { RP_WRONGSOURCE, "Invalid IP address of UDP port" },
+ { RP_ERROR, "Error packet" },
+ { RP_RECVFROM, "recvfrom() complained" },
+ { RP_TOOBIG, "Too many data bytes" },
+ { RP_NONE, NULL }
+};
+
+char *
+rp_strerror(int error)
+{
+ static char s[100];
+ size_t space = sizeof(s);
+ int i = 0;
+
+ while (rp_errors[i].desc != NULL) {
+ if (rp_errors[i].error == error) {
+ strlcpy(s, rp_errors[i].desc, space);
+ space -= strlen(rp_errors[i].desc);
+ }
+ i++;
+ }
+ if (s[0] == '\0')
+ sprintf(s, "unknown (error=%d)", error);
+ return (s);
+}
+
+/*
+ * Performance figures
+ */
+
+void
+stats_init(struct tftp_stats *ts)
+{
+
+ ts->amount = 0;
+ ts->rollovers = 0;
+ ts->retries = 0;
+ ts->blocks = 0;
+ ts->amount = 0;
+ gettimeofday(&(ts->tstart), NULL);
+}
+
+void
+printstats(const char *direction, int verbose, struct tftp_stats *ts)
+{
+ double delta; /* compute delta in 1/10's second units */
+
+ delta = ((ts->tstop.tv_sec*10.)+(ts->tstop.tv_usec/100000)) -
+ ((ts->tstart.tv_sec*10.)+(ts->tstart.tv_usec/100000));
+ delta = delta/10.; /* back to seconds */
+
+ printf("%s %zu bytes during %.1f seconds in %u blocks",
+ direction, ts->amount, delta, ts->blocks);
+
+ if (ts->rollovers != 0)
+ printf(" with %d rollover%s",
+ ts->rollovers, ts->rollovers != 1 ? "s" : "");
+
+ if (verbose)
+ printf(" [%.0f bits/sec]", (ts->amount*8.)/delta);
+ putchar('\n');
+}
diff --git a/libexec/tftpd/tftp-utils.h b/libexec/tftpd/tftp-utils.h
new file mode 100644
index 000000000000..276dedcf74cd
--- /dev/null
+++ b/libexec/tftpd/tftp-utils.h
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2008 Edwin Groothuis. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+/*
+ */
+#define TIMEOUT 5
+#define MAX_TIMEOUTS 5
+
+/* Generic values */
+#define MAXSEGSIZE 65464 /* Maximum size of the data segment */
+#define MAXPKTSIZE (MAXSEGSIZE + 4) /* Maximum size of the packet */
+
+/* For the blksize option */
+#define BLKSIZE_MIN 8 /* Minimum size of the data segment */
+#define BLKSIZE_MAX MAXSEGSIZE /* Maximum size of the data segment */
+
+/* For the timeout option */
+#define TIMEOUT_MIN 0 /* Minimum timeout value */
+#define TIMEOUT_MAX 255 /* Maximum timeout value */
+#define MIN_TIMEOUTS 3
+
+/* For the windowsize option */
+#define WINDOWSIZE 1
+#define WINDOWSIZE_MIN 1
+#define WINDOWSIZE_MAX 65535
+
+extern int timeoutpacket;
+extern int timeoutnetwork;
+extern int maxtimeouts;
+int settimeouts(int timeoutpacket, int timeoutnetwork, int maxtimeouts);
+
+extern uint16_t segsize;
+extern uint16_t pktsize;
+extern uint16_t windowsize;
+
+extern int acting_as_client;
+
+/*
+ */
+void unmappedaddr(struct sockaddr_in6 *sin6);
+size_t get_field(int peer, char *buffer, size_t size);
+
+/*
+ * Packet types
+ */
+struct packettypes {
+ int value;
+ const char *const name;
+};
+extern struct packettypes packettypes[];
+const char *packettype(int);
+
+/*
+ * RP_
+ */
+struct rp_errors {
+ int error;
+ const char *const desc;
+};
+extern struct rp_errors rp_errors[];
+char *rp_strerror(int error);
+
+/*
+ * Debug features
+ */
+#define DEBUG_NONE 0x0000
+#define DEBUG_PACKETS 0x0001
+#define DEBUG_SIMPLE 0x0002
+#define DEBUG_OPTIONS 0x0004
+#define DEBUG_ACCESS 0x0008
+struct debugs {
+ int value;
+ const char *const name;
+ const char *const desc;
+};
+extern int debug;
+extern struct debugs debugs[];
+extern unsigned int packetdroppercentage;
+int debug_find(char *s);
+int debug_finds(char *s);
+const char *debug_show(int d);
+
+/*
+ * Log routines
+ */
+#define DEBUG(s) tftp_log(LOG_DEBUG, "%s", s)
+extern int tftp_logtostdout;
+void tftp_openlog(const char *ident, int logopt, int facility);
+void tftp_closelog(void);
+void tftp_log(int priority, const char *message, ...) __printflike(2, 3);
+
+/*
+ * Performance figures
+ */
+struct tftp_stats {
+ size_t amount;
+ int rollovers;
+ uint32_t blocks;
+ int retries;
+ struct timeval tstart;
+ struct timeval tstop;
+};
+
+void stats_init(struct tftp_stats *ts);
+void printstats(const char *direction, int verbose, struct tftp_stats *ts);
diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8
new file mode 100644
index 000000000000..0118198da53d
--- /dev/null
+++ b/libexec/tftpd/tftpd.8
@@ -0,0 +1,333 @@
+.\" Copyright (c) 1983, 1991, 1993
+.\" The 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. 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.
+.\"
+.Dd November 3, 2024
+.Dt TFTPD 8
+.Os
+.Sh NAME
+.Nm tftpd
+.Nd Internet Trivial File Transfer Protocol server
+.Sh SYNOPSIS
+.Nm tftpd
+.Op Fl bCcdlnoSw
+.Op Fl F Ar strftime-format
+.Op Fl s Ar directory
+.Op Fl U Ar umask
+.Op Fl u Ar user
+.Op Ar directory ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a server which supports the
+Internet Trivial File Transfer
+Protocol
+.Pq Tn RFC 1350 .
+The
+.Tn TFTP
+server operates
+at the port indicated in the
+.Ql tftp
+service description;
+see
+.Xr services 5 .
+The server is normally started by
+.Xr inetd 8 .
+.Pp
+The use of
+.Xr tftp 1
+does not require an account or password on the remote system.
+Due to the lack of authentication information,
+.Nm
+will allow only publicly readable files to be
+accessed.
+Files containing the string
+.Dq Li "/../"
+or starting with
+.Dq Li "../"
+are not allowed.
+Files may be written only if they already exist (unless the
+.Fl w
+option is used) and are publicly writable (unless chrooted and the
+.Fl S
+option is used).
+Note that this extends the concept of
+.Dq public
+to include
+all users on all hosts that can be reached through the network;
+this may not be appropriate on all systems, and its implications
+should be considered before enabling tftp service.
+The server should have the user ID with the lowest possible privilege.
+.Pp
+Access to files may be restricted by invoking
+.Nm
+with a list of directories by including up to 20 pathnames
+as server program arguments in
+.Xr inetd.conf 5 .
+In this case access is restricted to files whose
+names are prefixed by the one of the given directories.
+The given directories are also treated as a search path for
+relative filename requests.
+.Pp
+The
+.Fl s
+option provides additional security by changing
+the root directory of
+.Nm ,
+thereby prohibiting accesses to outside of the specified
+.Ar directory .
+Because
+.Xr chroot 2
+requires super-user privileges,
+.Nm
+must be run as
+.Li root .
+However, after performing the
+.Xr chroot 2
+call,
+.Nm
+will set its user ID to that of the specified
+.Ar user ,
+or
+.Dq Li nobody
+if no
+.Fl u
+option is specified.
+.Pp
+The options are:
+.Bl -tag -width Ds
+.It Fl b
+By default,
+.Nm
+expects an initial message to be available on its input socket.
+If no data is available, the server exits immediately.
+If
+.Fl b
+is specified,
+.Nm
+will block waiting for the initial message.
+.It Fl c
+Changes the default root directory of a connecting host via
+.Xr chroot 2
+based on the connecting IP address.
+This prevents multiple clients from writing to the same file at the same time.
+If the directory does not exist, the client connection is refused.
+The
+.Fl s
+option is required for
+.Fl c
+and the specified
+.Ar directory
+is used as a base.
+.It Fl C
+Operates the same as
+.Fl c
+except it falls back to
+.Ar directory
+specified via
+.Fl s
+if a directory does not exist for the client's IP.
+.It Fl F
+Use this
+.Xr strftime 3
+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, d Ar [value]
+Enables debug output.
+If
+.Ar value
+is not specified, then the debug level is increased by one
+for each instance of
+.Fl d
+which is specified.
+.Pp
+If
+.Ar value
+is specified, then the debug level is set to
+.Ar value .
+The debug level is a bitmask implemented in
+.Pa src/libexec/tftpd/tftp-utils.h .
+Valid values are 0 (DEBUG_NONE), 1 (DEBUG_PACKETS), 2, (DEBUG_SIMPLE),
+4 (DEBUG_OPTIONS), and 8 (DEBUG_ACCESS). Multiple debug values can be combined
+in the bitmask by logically OR'ing the values. For example, specifying
+.Fl d
+.Ar 15
+will enable all the debug values.
+.It Fl l
+Log all requests using
+.Xr syslog 3
+with the facility of
+.Dv LOG_FTP .
+.Sy Note :
+Logging of
+.Dv LOG_FTP
+messages
+must also be enabled in the syslog configuration file,
+.Xr syslog.conf 5 .
+.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
+to change its root directory to
+.Ar directory .
+After doing that but before accepting commands,
+.Nm
+will switch credentials to an unprivileged user.
+.It Fl S
+If
+.Nm
+runs chrooted, the option allows write requests according to generic
+file permissions, skipping requirement for files to be publicly writable.
+The option is ignored for non-chrooted run.
+.It Fl u Ar user
+Switch credentials to
+.Ar user
+(default
+.Dq Li nobody )
+when the
+.Fl s
+option is used.
+The user must be specified by name, not a numeric UID.
+.It Fl U Ar umask
+Set the
+.Ar umask
+for newly created files.
+The default is 022
+.Pq Dv S_IWGRP | S_IWOTH .
+.It Fl w
+Allow write requests to create new files.
+By default
+.Nm
+requires that the file specified in a write request exist.
+Note that this only works in directories writable by the user
+specified with
+.Fl u
+option
+.It Fl W
+As
+.Fl w
+but append a YYYYMMDD.nn sequence number to the end of the filename.
+Note that the string YYYYMMDD can be changed with the
+.Fl F
+option.
+.El
+.Sh SEE ALSO
+.Xr tftp 1 ,
+.Xr chroot 2 ,
+.Xr syslog 3 ,
+.Xr inetd.conf 5 ,
+.Xr services 5 ,
+.Xr syslog.conf 5 ,
+.Xr inetd 8
+.Pp
+The following RFC's are supported:
+.Rs
+.%T RFC 1350: The TFTP Protocol (Revision 2)
+.Re
+.Rs
+.%T RFC 2347: TFTP Option Extension
+.Re
+.Rs
+.%T RFC 2348: TFTP Blocksize Option
+.Re
+.Rs
+.%T RFC 2349: TFTP Timeout Interval and Transfer Size Options
+.Re
+.Rs
+.%T RFC 7440: TFTP Windowsize Option
+.Re
+.Pp
+The non-standard
+.Cm rollover
+and
+.Cm blksize2
+TFTP options are mentioned here:
+.Rs
+.%T Extending TFTP
+.%U http://www.compuphase.com/tftp.htm
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 ;
+the
+.Fl s
+option was introduced in
+.Fx 2.2 ,
+the
+.Fl u
+option was introduced in
+.Fx 4.2 ,
+the
+.Fl c
+option was introduced in
+.Fx 4.3 ,
+the
+.Fl F
+and
+.Fl W
+options were introduced in
+.Fx 7.4 ,
+and the
+.Fl S
+option was introduced in
+.Fx 13.3 .
+.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.4 .
+.Pp
+Edwin Groothuis <edwin@FreeBSD.org> performed a major rewrite of the
+.Nm
+and
+.Xr tftp 1
+code to support RFC2348.
+.Pp
+Support for the windowsize option (RFC7440) was introduced in
+.Fx 13.0 .
+.Sh NOTES
+Files larger than 33,553,919 octets (65535 blocks, last one <512
+octets) cannot be correctly transferred without client and server
+supporting blocksize negotiation (RFCs 2347 and 2348),
+or the non-standard TFTP rollover option.
+As a kludge,
+.Nm
+accepts a sequence of block number which wrap to zero after 65535,
+even if the rollover option is not specified.
+.Pp
+Many tftp clients will not transfer files over 16,776,703 octets
+(32767 blocks), as they incorrectly count the block number using
+a signed rather than unsigned 16-bit integer.
diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c
new file mode 100644
index 000000000000..a3faee86e7d0
--- /dev/null
+++ b/libexec/tftpd/tftpd.c
@@ -0,0 +1,849 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ * The 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. 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.
+ */
+
+/*
+ * Trivial file transfer protocol server.
+ *
+ * This version includes many modifications by Jim Guyton
+ * <guyton@rand-unix>.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/tftp.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tftp-file.h"
+#include "tftp-io.h"
+#include "tftp-utils.h"
+#include "tftp-transfer.h"
+#include "tftp-options.h"
+
+#ifdef LIBWRAP
+#include <tcpd.h>
+#endif
+
+static void tftp_wrq(int peer, char *, size_t);
+static void tftp_rrq(int peer, char *, size_t);
+
+/*
+ * Null-terminated directory prefix list for absolute pathname requests and
+ * search list for relative pathname requests.
+ *
+ * MAXDIRS should be at least as large as the number of arguments that
+ * inetd allows (currently 20).
+ */
+#define MAXDIRS 20
+static struct dirlist {
+ const char *name;
+ size_t len;
+} dirs[MAXDIRS+1];
+static int suppress_naks;
+static int logging;
+static int ipchroot;
+static int check_woth = 1;
+static int create_new = 0;
+static const char *newfile_format = "%Y%m%d";
+static int increase_name = 0;
+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 FILE *file;
+
+static 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;
+ 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, block = 0, on = 1;
+ pid_t pid;
+
+ tzset(); /* syslog in localtime */
+ acting_as_client = 0;
+
+ tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
+ while ((ch = getopt(argc, argv, "bcCd::F:lnoOp:s:Su:U:wW")) != -1) {
+ switch (ch) {
+ case 'b':
+ block = 1;
+ break;
+ case 'c':
+ ipchroot = 1;
+ break;
+ case 'C':
+ ipchroot = 2;
+ break;
+ case 'd':
+ if (optarg == NULL)
+ debug++;
+ else if (atoi(optarg) != 0)
+ debug += atoi(optarg);
+ else
+ debug |= debug_finds(optarg);
+ break;
+ case 'F':
+ newfile_format = optarg;
+ break;
+ case 'l':
+ logging = 1;
+ break;
+ case 'n':
+ suppress_naks = 1;
+ break;
+ case 'o':
+ options_rfc_enabled = 0;
+ break;
+ case 'O':
+ options_extra_enabled = 0;
+ break;
+ case 'p':
+ packetdroppercentage = (unsigned int)atoi(optarg);
+ tftp_log(LOG_INFO,
+ "Randomly dropping %d out of 100 packets",
+ packetdroppercentage);
+ break;
+ case 's':
+ chroot_dir = optarg;
+ break;
+ case 'S':
+ check_woth = -1;
+ break;
+ case 'u':
+ chuser = optarg;
+ break;
+ case 'U':
+ mask = strtol(optarg, NULL, 0);
+ break;
+ case 'w':
+ create_new = 1;
+ break;
+ case 'W':
+ create_new = 1;
+ increase_name = 1;
+ break;
+ default:
+ tftp_log(LOG_WARNING,
+ "ignoring unknown option -%c", ch);
+ }
+ }
+ if (optind < argc) {
+ struct dirlist *dirp;
+
+ /* Get list of directory prefixes. Skip relative pathnames. */
+ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
+ optind++) {
+ if (argv[optind][0] == '/') {
+ dirp->name = argv[optind];
+ dirp->len = strlen(dirp->name);
+ dirp++;
+ }
+ }
+ }
+ else if (chroot_dir) {
+ dirs->name = "/";
+ dirs->len = 1;
+ }
+ if (ipchroot > 0 && chroot_dir == NULL) {
+ tftp_log(LOG_ERR, "-c requires -s");
+ exit(1);
+ }
+
+ umask(mask);
+
+ /* Find out who we are talking to and what we are going to do */
+ peerlen = sizeof(peer_sock);
+ n = recvfrom(0, recvbuffer, MAXPKTSIZE, block ? 0 : MSG_DONTWAIT,
+ (struct sockaddr *)&peer_sock, &peerlen);
+ if (n < 0) {
+ 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);
+ if ((size_t)n < 4 /* tftphdr */) {
+ tftp_log(LOG_ERR, "Rejecting %zd-byte request from %s",
+ n, peername);
+ exit(1);
+ }
+
+ if (ioctl(0, FIONBIO, &on) < 0) {
+ tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * Now that we have read the message out of the UDP
+ * socket, we fork and exit. Thus, inetd will go back
+ * to listening to the tftp port, and the next request
+ * to come in will start up a new instance of tftpd.
+ *
+ * We do this so that inetd can run tftpd in "wait" mode.
+ * The problem with tftpd running in "nowait" mode is that
+ * inetd may get one or more successful "selects" on the
+ * tftp port before we do our receive, so more than one
+ * instance of tftpd may be started up. Worse, if tftpd
+ * break before doing the above "recvfrom", inetd would
+ * spawn endless instances, clogging the system.
+ */
+ pid = fork();
+ if (pid < 0) {
+ tftp_log(LOG_ERR, "fork: %s", strerror(errno));
+ exit(1);
+ } else if (pid != 0) {
+ exit(0);
+ }
+ /* child */
+
+#ifdef LIBWRAP
+ /*
+ * 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");
+ }
+#endif
+
+ /*
+ * 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.
+ */
+ if (chroot_dir) {
+ if (ipchroot > 0) {
+ char *tempchroot;
+ struct stat sb;
+ int statret;
+ struct sockaddr_storage ss;
+ char hbuf[NI_MAXHOST];
+
+ 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,
+ NI_NUMERICHOST);
+ asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf);
+ if (ipchroot == 2)
+ statret = stat(tempchroot, &sb);
+ if (ipchroot == 1 ||
+ (statret == 0 && (sb.st_mode & S_IFDIR)))
+ chroot_dir = tempchroot;
+ }
+ /* Must get this before chroot because /etc might go away */
+ if ((nobody = getpwnam(chuser)) == NULL) {
+ tftp_log(LOG_ERR, "%s: no such user", chuser);
+ exit(1);
+ }
+ if (chroot(chroot_dir)) {
+ tftp_log(LOG_ERR, "chroot: %s: %s",
+ chroot_dir, strerror(errno));
+ exit(1);
+ }
+ if (chdir("/") != 0) {
+ tftp_log(LOG_ERR, "chdir: %s", strerror(errno));
+ exit(1);
+ }
+ if (setgroups(0, NULL) != 0) {
+ tftp_log(LOG_ERR, "setgroups failed");
+ exit(1);
+ }
+ if (setgid(nobody->pw_gid) != 0) {
+ tftp_log(LOG_ERR, "setgid failed");
+ exit(1);
+ }
+ if (setuid(nobody->pw_uid) != 0) {
+ tftp_log(LOG_ERR, "setuid failed");
+ exit(1);
+ }
+ if (check_woth == -1)
+ check_woth = 0;
+ }
+ if (check_woth == -1)
+ check_woth = 1;
+
+ 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_sock)->sin_port = 0;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0;
+ break;
+ default:
+ /* unsupported */
+ break;
+ }
+ } else {
+ memset(&me_sock, 0, sizeof(me_sock));
+ me_sock.ss_family = peer_sock.ss_family;
+ me_sock.ss_len = peer_sock.ss_len;
+ }
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0);
+ if (peer < 0) {
+ tftp_log(LOG_ERR, "socket: %s", strerror(errno));
+ exit(1);
+ }
+ 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 *)recvbuffer;
+ tp->th_opcode = ntohs(tp->th_opcode);
+ if (tp->th_opcode == RRQ) {
+ if (allow_ro)
+ tftp_rrq(peer, tp->th_stuff, (size_t)n - 1);
+ else {
+ tftp_log(LOG_WARNING,
+ "%s read access denied", peername);
+ exit(1);
+ }
+ } else if (tp->th_opcode == WRQ) {
+ if (allow_wo)
+ tftp_wrq(peer, tp->th_stuff, (size_t)n - 1);
+ else {
+ tftp_log(LOG_WARNING,
+ "%s write access denied", peername);
+ exit(1);
+ }
+ } else
+ send_error(peer, EBADOP);
+ exit(1);
+}
+
+static void
+reduce_path(char *fn)
+{
+ char *slash, *ptr;
+
+ /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */
+ while ((slash = strstr(fn, "/./")) != NULL) {
+ for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--)
+ ;
+ slash += 2;
+ while (*slash)
+ *++ptr = *++slash;
+ }
+
+ /* Now reduce all "/something/+../" to "/" */
+ while ((slash = strstr(fn, "/../")) != NULL) {
+ if (slash == fn)
+ break;
+ for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--)
+ ;
+ for (ptr--; ptr >= fn; ptr--)
+ if (*ptr == '/')
+ break;
+ if (ptr < fn)
+ break;
+ slash += 3;
+ while (*slash)
+ *++ptr = *++slash;
+ }
+}
+
+static char *
+parse_header(int peer, char *recvbuffer, size_t size,
+ char **filename, char **mode)
+{
+ struct formats *pf;
+ char *cp;
+ size_t i;
+
+ *mode = NULL;
+ cp = recvbuffer;
+
+ 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;
+
+ i = get_field(peer, cp, size);
+ *mode = cp;
+
+ /* Find the file transfer mode */
+ for (; *cp; cp++)
+ if (isupper((unsigned char)*cp))
+ *cp = tolower((unsigned char)*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);
+}
+
+/*
+ * WRQ - receive a file from the client
+ */
+void
+tftp_wrq(int peer, char *recvbuffer, size_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;
+
+ strlcpy(fnbuf, filename, sizeof(fnbuf));
+ 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");
+ }
+
+ ecode = validate_access(peer, &filename, WRQ);
+ if (ecode == 0) {
+ if (has_options)
+ send_oack(peer);
+ else
+ send_ack(peer, 0);
+ }
+ if (logging) {
+ tftp_log(LOG_INFO, "%s: write request for %s: %s", peername,
+ filename, errtomsg(ecode));
+ }
+
+ if (ecode) {
+ send_error(peer, ecode);
+ exit(1);
+ }
+ tftp_recvfile(peer, mode);
+ exit(0);
+}
+
+/*
+ * RRQ - send a file to the client
+ */
+void
+tftp_rrq(int peer, char *recvbuffer, size_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;
+
+ strlcpy(fnbuf, filename, sizeof(fnbuf));
+ 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");
+ }
+
+ 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;
+ }
+ }
+ }
+
+ 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
+ * bootfile pathname from a diskless Sun.
+ */
+ if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
+ exit(0);
+ send_error(peer, ecode);
+ exit(1);
+ }
+ tftp_xmitfile(peer, mode);
+}
+
+/*
+ * 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.
+ * Besides, that is four updates per hour on a file, which is kind of
+ * execessive anyway.
+ */
+static int
+find_next_name(char *filename, int *fd)
+{
+ /*
+ * GCC "knows" that we might write all of yyyymmdd plus the static
+ * elemenents in the format into into newname and thus complains
+ * unless we reduce the size. This array is still too big, but since
+ * the format is user supplied, it's not clear what a better limit
+ * value would be and this is sufficent to silence the warnings.
+ */
+ static const int suffix_len = strlen("..00");
+ char yyyymmdd[MAXPATHLEN - suffix_len];
+ char newname[MAXPATHLEN];
+ int i, ret;
+ time_t tval;
+ size_t len, namelen;
+ struct tm lt;
+
+ /* Create the YYYYMMDD part of the filename */
+ time(&tval);
+ lt = *localtime(&tval);
+ len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, &lt);
+ if (len == 0) {
+ syslog(LOG_WARNING,
+ "Filename suffix too long (%zu characters maximum)",
+ sizeof(yyyymmdd) - 1);
+ return (EACCESS);
+ }
+
+ /* Make sure the new filename is not too long */
+ namelen = strlen(filename);
+ if (namelen >= sizeof(newname) - len - suffix_len) {
+ syslog(LOG_WARNING,
+ "Filename too long (%zu characters, %zu maximum)",
+ namelen,
+ sizeof(newname) - len - suffix_len - 1);
+ return (EACCESS);
+ }
+
+ /* Find the first file which doesn't exist */
+ for (i = 0; i < 100; i++) {
+ ret = snprintf(newname, sizeof(newname), "%s.%s.%02d",
+ filename, yyyymmdd, i);
+ /*
+ * Size checked above so this can't happen, we'd use a
+ * (void) cast, but gcc intentionally ignores that if
+ * snprintf has __attribute__((warn_unused_result)).
+ */
+ if (ret < 0 || (size_t)ret >= sizeof(newname))
+ __unreachable();
+ *fd = open(newname, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (*fd > 0)
+ return 0;
+ }
+
+ return (EEXIST);
+}
+
+/*
+ * Validate file access. Since we
+ * have no uid or gid, for now require
+ * file to exist and be publicly
+ * readable/writable.
+ * If we were invoked with arguments
+ * from inetd then the file must also be
+ * in one of the given directory prefixes.
+ * Note also, full path name must be
+ * given as we have no login directory.
+ */
+int
+validate_access(int peer, char **filep, int mode)
+{
+ static char pathname[MAXPATHLEN];
+ struct stat sb;
+ struct dirlist *dirp;
+ char *filename = *filep;
+ int err, fd;
+
+ /*
+ * Prevent tricksters from getting around the directory restrictions
+ */
+ if (strncmp(filename, "../", 3) == 0 ||
+ strstr(filename, "/../") != NULL)
+ return (EACCESS);
+
+ if (*filename == '/') {
+ /*
+ * Absolute file name: allow the request if it's in one of the
+ * approved locations.
+ */
+ for (dirp = dirs; dirp->name != NULL; dirp++) {
+ if (dirp->len == 1)
+ /* Only "/" can have len 1 */
+ break;
+ if (strncmp(filename, dirp->name, dirp->len) == 0 &&
+ filename[dirp->len] == '/')
+ break;
+ }
+ /* If directory list is empty, allow access to any file */
+ if (dirp->name == NULL && dirp != dirs)
+ return (EACCESS);
+ if (stat(filename, &sb) != 0)
+ return (errno == ENOENT ? ENOTFOUND : EACCESS);
+ if (!S_ISREG(sb.st_mode))
+ return (ENOTFOUND);
+ if (mode == RRQ) {
+ if ((sb.st_mode & S_IROTH) == 0)
+ return (EACCESS);
+ } else {
+ if (check_woth && (sb.st_mode & S_IWOTH) == 0)
+ return (EACCESS);
+ }
+ } else {
+ /*
+ * Relative file name: search the approved locations for it.
+ * If the file exists in one of the directories and isn't
+ * readable, continue looking. However, change the error code
+ * to give an indication that the file exists.
+ */
+ err = ENOTFOUND;
+ for (dirp = dirs; dirp->name != NULL; dirp++) {
+ snprintf(pathname, sizeof(pathname), "%s/%s",
+ dirp->name, filename);
+ if (stat(pathname, &sb) != 0)
+ continue;
+ if (!S_ISREG(sb.st_mode))
+ continue;
+ err = EACCESS;
+ if (mode == RRQ) {
+ if ((sb.st_mode & S_IROTH) == 0)
+ continue;
+ } else {
+ if (check_woth && (sb.st_mode & S_IWOTH) == 0)
+ continue;
+ }
+ break;
+ }
+ if (dirp->name != NULL)
+ *filep = filename = pathname;
+ else if (mode == RRQ)
+ return (err);
+ else if (err != ENOTFOUND || !create_new)
+ return (err);
+ }
+
+ /*
+ * This option is handled here because it (might) require(s) the
+ * size of the file.
+ */
+ option_tsize(peer, NULL, mode, &sb);
+
+ if (mode == RRQ) {
+ fd = open(filename, O_RDONLY);
+ } else if (create_new) {
+ if (increase_name) {
+ err = find_next_name(filename, &fd);
+ if (err > 0)
+ return (err + 100);
+ } else {
+ fd = open(filename,
+ O_WRONLY | O_TRUNC | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP |
+ S_IWGRP | S_IROTH | S_IWOTH );
+ }
+ } else {
+ fd = open(filename, O_WRONLY | O_TRUNC);
+ }
+ if (fd < 0)
+ return (errno + 100);
+ file = fdopen(fd, mode == RRQ ? "r" : "w");
+ if (file == NULL) {
+ close(fd);
+ return (errno + 100);
+ }
+ return (0);
+}
+
+static void
+tftp_xmitfile(int peer, const char *mode)
+{
+ uint16_t block;
+ time_t now;
+ struct tftp_stats ts;
+
+ memset(&ts, 0, sizeof(ts));
+ now = time(NULL);
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Transmitting file");
+
+ read_init(0, file, mode);
+ block = 1;
+ tftp_send(peer, &block, &ts);
+ read_close();
+ if (debug & DEBUG_SIMPLE)
+ tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds",
+ (intmax_t)ts.amount, (intmax_t)time(NULL) - now);
+}
+
+static void
+tftp_recvfile(int peer, const char *mode)
+{
+ 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);
+
+ block = 0;
+ tftp_receive(peer, &block, &ts, NULL, 0);
+
+ gettimeofday(&now2, NULL);
+
+ if (debug & DEBUG_SIMPLE) {
+ double f;
+ if (now1.tv_usec > now2.tv_usec) {
+ now2.tv_usec += 1000000;
+ now2.tv_sec--;
+ }
+
+ f = now2.tv_sec - now1.tv_sec +
+ (now2.tv_usec - now1.tv_usec) / 100000.0;
+ tftp_log(LOG_INFO,
+ "Download of %jd bytes in %d blocks completed after %0.1f seconds\n",
+ (intmax_t)ts.amount, block, f);
+ }
+
+ return;
+}