diff --git a/libexec/tftpd/tests/functional.c b/libexec/tftpd/tests/functional.c index 1d5c1e17c420..f31d2a893da1 100644 --- a/libexec/tftpd/tests/functional.c +++ b/libexec/tftpd/tests/functional.c @@ -1,1209 +1,1281 @@ /*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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, ssize_t expected_len, - const char *actual, ssize_t len); +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(r, (ssize_t)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; \ - } \ +#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) +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)); \ +#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, ssize_t len) +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_EQ(r, len); + 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) +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( \ - sendto(s, (cmd), sizeof(cmd) - 1, 0, (struct sockaddr*)(&addr), \ - addr.ss_len), \ - sizeof(cmd) - 1) +#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 + 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") +#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) +#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") +#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 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) \ -{ \ - __VA_ARGS__; \ - protocol = AF_INET; \ - s = setup(&addr, __COUNTER__); \ - name ## _body(); \ - close(s); \ -} \ -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) \ -{ \ - __VA_ARGS__; \ - protocol = AF_INET6; \ - s = setup(&addr, __COUNTER__); \ - name ## _body(); \ - close(s); \ -} \ -ATF_TC_CLEANUP(name ## _v6, tc) \ -{ \ - cleanup(); \ -} \ -static void \ +#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); \ +#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); - unlink(pidfile); } /* Assert that two binary buffers are identical */ static void -require_bufeq(const char *expected, ssize_t expected_len, const char *actual, - ssize_t len) +require_bufeq(const char *expected, size_t expected_len, + const char *actual, size_t len) { - ssize_t i; + size_t i; ATF_REQUIRE_EQ_MSG(expected_len, len, - "Expected %zd bytes but got %zd", expected_len, len); + "Expected %zu bytes but got %zu", expected_len, len); for (i = 0; i < len; i++) { - ATF_REQUIRE_EQ_MSG(actual[i], expected[i], - "Expected %#hhx at position %zd; got %hhx instead", + 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 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; + 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; + server_addr = (struct sockaddr *)&addr6; } - ATF_REQUIRE_EQ(getcwd(pwd, sizeof(pwd)), pwd); + 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(bind(server_s, server_addr, len), 0, + 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(pidfile_write(pfh), 0); - ATF_REQUIRE_EQ(pidfile_close(pfh), 0); + ATF_REQUIRE_EQ(0, pidfile_write(pfh)); + ATF_REQUIRE_EQ(0, pidfile_close(pfh)); bzero(argv, sizeof(argv)); argv[0] = execname; argv_idx = 1; 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(dup2(server_s, STDOUT_FILENO), STDOUT_FILENO); - ATF_REQUIRE_EQ(dup2(server_s, STDIN_FILENO), STDIN_FILENO); - ATF_REQUIRE_EQ(dup2(server_s, STDERR_FILENO), STDERR_FILENO); + 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; + 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; + 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; } - close(server_s); + 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 -= r; - buf = (const char*)buf + r; + 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(mkdir("subdir", 0777), 0); + ATF_REQUIRE_EQ(0, mkdir("subdir", 0777)); SEND_RRQ("../disallowed.txt", "octet"); RECV_ERROR(2, "Access violation"); - s = setup(&addr, __COUNTER__); \ + s = setup(&addr, __COUNTER__); SEND_RRQ("subdir/../../disallowed.txt", "octet"); RECV_ERROR(2, "Access violation"); - s = setup(&addr, __COUNTER__); \ + 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) +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); + 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); + 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); + 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); + 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); + recv_data(1, (const char *)&contents[0], 512); send_ack(1); - recv_data(2, (const char*)&contents[128], 256); + 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); + 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__); \ + s = setup(&addr, __COUNTER__); SEND_RRQ("foo.txt", "binary"); /* Obsolete. Use "octet" instead */ RECV_ERROR(4, "Illegal TFTP operation"); - s = setup(&addr, __COUNTER__); \ + s = setup(&addr, __COUNTER__); SEND_RRQ("foo.txt", "en_US.UTF-8"); RECV_ERROR(4, "Illegal TFTP operation"); - s = setup(&addr, __COUNTER__); \ + 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, r); + 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); + 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); + 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, r); + 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, r); + 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); + 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); + 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, r); + 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); + send_data(1, (const char *)&contents[0], 512); recv_ack(1); - send_data(2, (const char*)&contents[128], 256); + 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, r); + 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); + 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, r); + 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, r); + 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, r); + 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(stat("small.txt", &sb), 0); - ATF_REQUIRE_EQ(sb.st_size, 0); + 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, r); + 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-utils.c b/libexec/tftpd/tftp-utils.c index 9754c3238d50..b309a94f7653 100644 --- a/libexec/tftpd/tftp-utils.c +++ b/libexec/tftpd/tftp-utils.c @@ -1,328 +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 #include #include #include #include #include #include #include #include #include #include #include #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 */ -ssize_t -get_field(int peer, char *buffer, ssize_t size) +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" }, }; 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 index 3fecf1fc8696..763b3b493c7e 100644 --- a/libexec/tftpd/tftp-utils.h +++ b/libexec/tftpd/tftp-utils.h @@ -1,130 +1,130 @@ /*- * 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 /* */ #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); -ssize_t get_field(int peer, char *buffer, ssize_t size); +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 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.c b/libexec/tftpd/tftpd.c index ffe31927780e..78348a8c6aaf 100644 --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -1,843 +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. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; #endif #endif /* not lint */ #include /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton * . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftp-file.h" #include "tftp-io.h" #include "tftp-utils.h" #include "tftp-transfer.h" #include "tftp-options.h" #ifdef LIBWRAP #include #endif -static void tftp_wrq(int peer, char *, ssize_t); -static void tftp_rrq(int peer, char *, ssize_t); +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; - int len; + 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, 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, "cCd::F:lnoOp:s:Su:U:wW")) != -1) { switch (ch) { 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 = 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); if (ioctl(0, FIONBIO, &on) < 0) { tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); exit(1); } /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { 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); + } /* * 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(1, &nobody->pw_gid) != 0) { tftp_log(LOG_ERR, "setgroups 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, n - 1); + 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, n - 1); + 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, ssize_t size, +parse_header(int peer, char *recvbuffer, size_t size, char **filename, char **mode) { char *cp; int i; struct formats *pf; *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; cp += i; /* Find the file transfer mode */ for (cp = *mode; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) if (strcmp(pf->f_mode, *mode) == 0) break; if (pf->f_mode == NULL) { tftp_log(LOG_ERR, "Bad option - Unknown transfer mode (%s)", *mode); send_error(peer, EBADOP); exit(1); } tftp_log(LOG_INFO, "Mode: '%s'", *mode); return (cp + 1); } /* * WRQ - receive a file from the client */ void -tftp_wrq(int peer, char *recvbuffer, ssize_t size) +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, ssize_t size) +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) { int i; time_t tval; size_t len; struct tm lt; char yyyymmdd[MAXPATHLEN]; char newname[MAXPATHLEN]; /* Create the YYYYMMDD part of the filename */ time(&tval); lt = *localtime(&tval); len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); if (len == 0) { syslog(LOG_WARNING, "Filename suffix too long (%d characters maximum)", MAXPATHLEN); return (EACCESS); } /* Make sure the new filename is not too long */ if (strlen(filename) > MAXPATHLEN - len - 5) { syslog(LOG_WARNING, "Filename too long (%zd characters, %zd maximum)", strlen(filename), MAXPATHLEN - len - 5); return (EACCESS); } /* Find the first file which doesn't exist */ for (i = 0; i < 100; i++) { sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); *fd = open(newname, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 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) { struct stat stbuf; int fd; int error; struct dirlist *dirp; static char pathname[MAXPATHLEN]; char *filename = *filep; /* * Prevent tricksters from getting around the directory restrictions */ if (strstr(filename, "/../")) return (EACCESS); if (*filename == '/') { /* * Allow the request if it's in one of the approved locations. * Special case: check the null prefix ("/") by looking * for length = 1 and relying on the arg. processing that * it's a /. */ for (dirp = dirs; dirp->name != NULL; dirp++) { - if (dirp->len == 1 || - (!strncmp(filename, dirp->name, dirp->len) && - filename[dirp->len] == '/')) - break; + if (dirp->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, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); if ((stbuf.st_mode & S_IFMT) != S_IFREG) return (ENOTFOUND); if (mode == RRQ) { if ((stbuf.st_mode & S_IROTH) == 0) return (EACCESS); } else { if (check_woth && ((stbuf.st_mode & S_IWOTH) == 0)) return (EACCESS); } } else { int err; /* * Relative file name: search the approved locations for it. * Don't allow write requests that avoid directory * restrictions. */ if (!strncmp(filename, "../", 3)) return (EACCESS); /* * 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, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFREG) { if (mode == RRQ) { if ((stbuf.st_mode & S_IROTH) != 0) break; } else { if (!check_woth || ((stbuf.st_mode & S_IWOTH) != 0)) break; } err = EACCESS; } } 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, &stbuf); if (mode == RRQ) fd = open(filename, O_RDONLY); else { if (create_new) { if (increase_name) { error = find_next_name(filename, &fd); if (error > 0) return (error + 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; }