Index: etc/mtree/BSD.tests.dist =================================================================== --- etc/mtree/BSD.tests.dist +++ etc/mtree/BSD.tests.dist @@ -382,6 +382,8 @@ .. rtld-elf .. + tftpd + .. .. sbin dhclient Index: libexec/tftpd/Makefile =================================================================== --- libexec/tftpd/Makefile +++ libexec/tftpd/Makefile @@ -14,4 +14,7 @@ LIBADD= wrap .endif +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + .include Index: libexec/tftpd/tests/Makefile =================================================================== --- /dev/null +++ libexec/tftpd/tests/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +ATF_TESTS_C= functional +TEST_METADATA.functional+= timeout=15 + +LIBADD= util +WARNS?= 6 + +.include Index: libexec/tftpd/tests/functional.c =================================================================== --- /dev/null +++ libexec/tftpd/tests/functional.c @@ -0,0 +1,352 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2018 Alan Somers. 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +static const uint16_t BASEPORT = 6969; +static const char pidfile[] = "tftpd.pid"; + +/* Helper functions*/ + +static void +cleanup(void) +{ + int fd = -1; + char buffer[80]; + pid_t pid; + + fd = open(pidfile, O_RDONLY); + if (fd < 0) + return; + if (read(fd, buffer, sizeof(buffer)) > 0) { + sscanf(buffer, "%d", &pid); + kill(pid, SIGTERM); + waitpid(pid, NULL, 0); + } + close(fd); +} + +/* Assert that two binary buffers are identical */ +static void +require_bufeq(char *expected, size_t expected_len, char *actual, size_t len) +{ + size_t i; + + ATF_REQUIRE_EQ_MSG(expected_len, len, + "Expected %lu bytes but got %lu", expected_len, len); + for (i=0; i 0); + ATF_REQUIRE_EQ_MSG(bind(server_s, (struct sockaddr*) &addr, len), 0, + "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(pfh != NULL); + ATF_REQUIRE_EQ(pidfile_write(pfh), 0); + ATF_REQUIRE_EQ(pidfile_close(pfh), 0); + + addr.sin_addr.s_addr = htonl(INADDR_ANY); + argv[0] = execname; + argv[1] = pwd; + argv[2] = NULL; + 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); + execv(execname, argv); + atf_tc_fail("exec failed"); + break; + default: + /* In parent */ + bzero(to, sizeof(*to)); + to->sin_len = sizeof(*to); + to->sin_family = PF_INET; + to->sin_port = htons(port); + to->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + close(server_s); + ATF_REQUIRE((client_s = socket(PF_INET, SOCK_DGRAM, 0)) > 0); + break; + } + return (client_s); +} + +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 void*)((const char*)buf + r); + } +} + + +/* + * Test Cases + */ + +/* + * Read an empty file + */ +ATF_TC_WITH_CLEANUP(rrq_empty); +ATF_TC_HEAD(rrq_empty, tc) +{ +} +ATF_TC_BODY(rrq_empty, tc) +{ + int fd, r, s; + char buffer[1024]; + struct sockaddr_in addr, from; + socklen_t fromlen = sizeof(from); + + s = setup(&addr, 0); + + char cmd[] = { + 0, 1, /* RRQ opcode in BE */ + 'e', 'm', 'p', 't', 'y', '.', 't', 'x', 't', 0, + 'o', 'c', 't', 'e', 't', 0 + }; + char expected[] = { + 0, 3, /* DATA opcode in BE */ + 0, 1, /* Block 1 in BE */ + /* No data, indicating a 0-length file */ + }; + char ack0[] = { + 0, 4, /* ACK opcode in BE */ + 0, 1 /* Block 1 in BE */ + }; + + fd = open("empty.txt", O_CREAT, 0644); + ATF_REQUIRE(fd >= 0); + close(fd); + + ATF_REQUIRE_EQ(sendto(s, cmd, sizeof(cmd), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(cmd)); + r = (size_t) recvfrom(s, buffer, sizeof(buffer), 0, + (struct sockaddr*)&from, &fromlen); + require_bufeq(expected, sizeof(expected), buffer, r); + + addr.sin_port = from.sin_port; + ATF_REQUIRE_EQ(sendto(s, ack0, sizeof(ack0), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(ack0)); +} +ATF_TC_CLEANUP(rrq_empty, tc) +{ + cleanup(); +} + +/* + * Read a small file of less than one block + */ +ATF_TC_WITH_CLEANUP(rrq_small); +ATF_TC_HEAD(rrq_small, tc) +{ +} +ATF_TC_BODY(rrq_small, tc) +{ + int fd, r, s; + char buffer[1024]; + char contents[] = "small"; + struct sockaddr_in addr, from; + socklen_t fromlen = sizeof(from); + + s = setup(&addr, 1); + + char cmd[] = { + 0, 1, /* RRQ opcode in BE */ + 's', 'm', 'a', 'l', 'l', '.', 't', 'x', 't', 0, + 'o', 'c', 't', 'e', 't', 0 + }; + char expected[] = { + 0, 3, /* DATA opcode in BE */ + 0, 1, /* Block 1 in BE */ + 's', 'm', 'a', 'l', 'l', 0, /* data */ + }; + char ack0[] = { + 0, 4, /* ACK opcode in BE */ + 0, 1 /* Block 1 in BE */ + }; + + fd = open("small.txt", O_RDWR | O_CREAT, 0644); + ATF_REQUIRE(fd >= 0); + write_all(fd, contents, strlen(contents) + 1); + close(fd); + + ATF_REQUIRE_EQ(sendto(s, cmd, sizeof(cmd), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(cmd)); + + r = (size_t) recvfrom(s, buffer, sizeof(buffer), 0, + (struct sockaddr*)&from, &fromlen); + require_bufeq(expected, sizeof(expected), buffer, r); + + addr.sin_port = from.sin_port; + ATF_REQUIRE_EQ(sendto(s, ack0, sizeof(ack0), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(ack0)); +} +ATF_TC_CLEANUP(rrq_small, tc) +{ + cleanup(); +} + +/* + * Read a medium file of more than one block + */ +ATF_TC_WITH_CLEANUP(rrq_medium); +ATF_TC_HEAD(rrq_medium, tc) +{ +} +ATF_TC_BODY(rrq_medium, tc) +{ + int fd, s; + size_t i, r; + char buffer[1024]; + uint32_t contents[192]; + struct sockaddr_in addr, from; + socklen_t fromlen = sizeof(from); + + s = setup(&addr, 2); + + char cmd[] = { + 0, 1, /* RRQ opcode in BE */ + 'm', 'e', 'd', 'i', 'u', 'm', '.', 't', 'x', 't', 0, + 'o', 'c', 't', 'e', 't', 0 + }; + char expected_hdr0[] = { + 0, 3, /* DATA opcode in BE */ + 0, 1, /* Block 1 in BE */ + }; + char ack0[] = { + 0, 4, /* ACK opcode in BE */ + 0, 1 /* Block 1 in BE */ + }; + char expected_hdr1[] = { + 0, 3, /* DATA opcode in BE */ + 0, 2, /* Block 2 in BE */ + }; + + for (i=0; i= 0); + write_all(fd, contents, sizeof(contents)); + close(fd); + + ATF_REQUIRE_EQ(sendto(s, cmd, sizeof(cmd), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(cmd)); + + r = (size_t) recvfrom(s, buffer, sizeof(buffer), 0, + (struct sockaddr*)&from, &fromlen); + require_bufeq(expected_hdr0, sizeof(expected_hdr0), buffer, + MIN(r, sizeof(expected_hdr0))); + require_bufeq((char*) &contents[0], 512, + &buffer[sizeof(expected_hdr0)], r - sizeof(expected_hdr0)); + + addr.sin_port = from.sin_port; + ATF_REQUIRE_EQ(sendto(s, ack0, sizeof(ack0), 0, + (struct sockaddr*)&addr, sizeof(addr)), sizeof(ack0)); + + r = recvfrom(s, buffer, sizeof(buffer), 0, NULL, NULL); + require_bufeq(expected_hdr1, sizeof(expected_hdr1), buffer, + MIN(r, sizeof(expected_hdr1))); + require_bufeq((char*) &contents[128], 256, + &buffer[sizeof(expected_hdr1)], r - sizeof(expected_hdr1)); +} +ATF_TC_CLEANUP(rrq_medium, tc) +{ + cleanup(); +} + +/* + * Main + */ + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, rrq_empty); + ATF_TP_ADD_TC(tp, rrq_small); + ATF_TP_ADD_TC(tp, rrq_medium); + + return (atf_no_error()); +}