Index: head/libexec/tftpd/tests/functional.c =================================================================== --- head/libexec/tftpd/tests/functional.c (revision 358555) +++ head/libexec/tftpd/tests/functional.c (revision 358556) @@ -1,1003 +1,1205 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #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); /* * 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; \ } \ } 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, ssize_t len) { ssize_t r; r = sendto(s, cmd, len, 0, (struct sockaddr*)(&addr), addr.ss_len); ATF_REQUIRE_EQ(r, len); } 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( \ sendto(s, (cmd), sizeof(cmd) - 1, 0, (struct sockaddr*)(&addr), \ addr.ss_len), \ sizeof(cmd) - 1) /* * 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) \ { \ __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 \ 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) /* Standard cleanup used by all testcases */ static void cleanup(void) { FILE *f; pid_t pid; f = fopen(pidfile, "r"); if (f == NULL) return; 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) { ssize_t i; ATF_REQUIRE_EQ_MSG(expected_len, len, "Expected %zd bytes but got %zd", 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", 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; 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(getcwd(pwd, sizeof(pwd)), pwd); /* Must bind(2) pre-fork so it happens before the client's send(2) */ ATF_REQUIRE((server_s = socket(protocol, SOCK_DGRAM, 0)) > 0); ATF_REQUIRE_EQ_MSG(bind(server_s, server_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_MSG(pfh != NULL, "pidfile_open: %s", strerror(errno)); ATF_REQUIRE_EQ(pidfile_write(pfh), 0); ATF_REQUIRE_EQ(pidfile_close(pfh), 0); 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); execv(execname, argv); atf_tc_fail("exec failed"); break; default: /* In parent */ 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; } 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; } } /* * 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); 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)); close(fd); require_bufeq(contents, contents_len, buffer, 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)); close(fd); require_bufeq((const char*)contents, 768, buffer, 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)); close(fd); require_bufeq(contents, contents_len, buffer, 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)); close(fd); require_bufeq((const char*)contents, 768, buffer, 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)); close(fd); require_bufeq((const char*)contents, 768, buffer, 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)); + close(fd); + require_bufeq((const char*)contents, 768, buffer, 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)); close(fd); require_bufeq(expected, sizeof(expected), buffer, 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)); close(fd); require_bufeq(contents, contents_len, buffer, 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); } +/* + * 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)); + close(fd); + require_bufeq(contents, sizeof(contents), buffer, r); +} + + /* * 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); return (atf_no_error()); } Index: head/libexec/tftpd/tftp-file.c =================================================================== --- head/libexec/tftpd/tftp-file.c (revision 358555) +++ head/libexec/tftpd/tftp-file.c (revision 358556) @@ -1,288 +1,302 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #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); } } } Index: head/libexec/tftpd/tftp-file.h =================================================================== --- head/libexec/tftpd/tftp-file.h (revision 358555) +++ head/libexec/tftpd/tftp-file.h (revision 358556) @@ -1,39 +1,42 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); 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); Index: head/libexec/tftpd/tftp-options.c =================================================================== --- head/libexec/tftpd/tftp-options.c (revision 358555) +++ head/libexec/tftpd/tftp-options.c (revision 358556) @@ -1,390 +1,426 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #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; /* * 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) asprintf(&options[OPT_TSIZE].o_reply, "%ju", stbuf->st_size); else /* XXX Allows writes of all sizes. */ options[OPT_TSIZE].o_reply = strdup(options[OPT_TSIZE].o_request); 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[OPT_TIMEOUT].o_reply = strdup(options[OPT_TIMEOUT].o_request); } 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[OPT_ROLLOVER].o_reply = strdup(options[OPT_ROLLOVER].o_request); 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); } 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 */ } } asprintf(&options[OPT_BLKSIZE].o_reply, "%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 */ } asprintf(&options[OPT_BLKSIZE2].o_reply, "%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. */ + asprintf(&options[OPT_WINDOWSIZE].o_reply, "%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[i].o_request = 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[OPT_ROLLOVER].o_request = strdup("0"); } Index: head/libexec/tftpd/tftp-options.h =================================================================== --- head/libexec/tftpd/tftp-options.h (revision 358555) +++ head/libexec/tftpd/tftp-options.h (revision 358556) @@ -1,64 +1,66 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); /* * 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, }; Index: head/libexec/tftpd/tftp-transfer.c =================================================================== --- head/libexec/tftpd/tftp-transfer.c (revision 358555) +++ head/libexec/tftpd/tftp-transfer.c (revision 358556) @@ -1,326 +1,429 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #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-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. */ void tftp_send(int peer, uint16_t *block, struct tftp_stats *ts) { struct tftphdr *rp; - int size, n_data, n_ack, try; - uint16_t oldblock; + 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", *block); + 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); goto abort; } + window[windowblock].size = size; + windowblock++; - for (try = 0; ; try++) { + for (sendtry = 0; ; sendtry++) { n_data = send_data(peer, *block, sendbuffer, size); - if (n_data > 0) { - if (try == maxtimeouts) { - tftp_log(LOG_ERR, - "Cannot send DATA packet #%d, " - "giving up", *block); - return; - } + if (n_data == 0) + break; + + if (sendtry == maxtimeouts) { tftp_log(LOG_ERR, - "Cannot send DATA packet #%d, trying again", - *block); - continue; + "Cannot send DATA packet #%d, " + "giving up", *block); + return; } + 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 (try == maxtimeouts) { + if (acktry == maxtimeouts) { tftp_log(LOG_ERR, "Timeout #%d send ACK %d " - "giving up", try, *block); + "giving up", acktry, *block); return; } tftp_log(LOG_WARNING, "Timeout #%d on ACK %d", - try, *block); - continue; + acktry, *block); + + acktry++; + ts->retries++; + seek_file(window[0].offset); + *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)); goto abort; } if (rp->th_opcode == ACK) { - ts->blocks++; - if (rp->th_block == *block) { - ts->amount += size; - break; + /* + * Look for the ACKed block in our open + * window. + */ + for (i = 0; i < windowblock; i++) { + if (rp->th_block == window[i].block) + break; } - /* Re-synchronize with the other side */ - (void) synchnet(peer); - if (rp->th_block == (*block - 1)) { + 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++; - continue; + seek_file(window[0].offset); + *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"); + seek_file(window[i + 1].offset); + *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); abort: return; } /* * 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. */ void tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts, struct tftphdr *firstblock, size_t fb_size) { struct tftphdr *rp; - uint16_t oldblock; - int n_data, n_ack, writesize, i, retry; + 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; - for (i = 0; ; i++) { - n_ack = send_ack(peer, *block); - if (n_ack > 0) { - if (i == maxtimeouts) { + 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; + } tftp_log(LOG_ERR, - "Cannot send ACK packet #%d, " - "giving up", *block); - return; + "Cannot send ACK packet #%d, trying again", + *block); + continue; } - tftp_log(LOG_ERR, - "Cannot send ACK packet #%d, trying again", - *block); - continue; - } - break; + break; + } } if (fb_size != segsize) { gettimeofday(&(ts->tstop), NULL); return; } } 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", *block); + "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; } 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)); goto abort; } 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); - if (rp->th_block == (*block-1)) { - tftp_log(LOG_INFO, "Trying to sync"); - *block = oldblock; - ts->retries++; - goto send_ack; /* rexmit */ - } + 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); goto abort; } 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; } 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; 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 */ } abort: return; } Index: head/libexec/tftpd/tftp-utils.c =================================================================== --- head/libexec/tftpd/tftp-utils.c (revision 358555) +++ head/libexec/tftpd/tftp-utils.c (revision 358556) @@ -1,324 +1,325 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #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) { 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; char *s; 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); } /* * 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'); } Index: head/libexec/tftpd/tftp-utils.h =================================================================== --- head/libexec/tftpd/tftp-utils.h (revision 358555) +++ head/libexec/tftpd/tftp-utils.h (revision 358556) @@ -1,126 +1,132 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); /* */ #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); /* * 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); Index: head/libexec/tftpd/tftpd.8 =================================================================== --- head/libexec/tftpd/tftpd.8 (revision 358555) +++ head/libexec/tftpd/tftpd.8 (revision 358556) @@ -1,306 +1,312 @@ .\" 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. .\" .\" @(#)tftpd.8 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd June 22, 2011 +.Dd March 2, 2020 .Dt TFTPD 8 .Os .Sh NAME .Nm tftpd .Nd Internet Trivial File Transfer Protocol server .Sh SYNOPSIS .Nm tftpd .Op Fl cdClnow .Op Fl F Ar strftime-format .Op Fl s Ar directory .Op Fl u Ar user .Op Fl U Ar umask .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 and are publicly writable. 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 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 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 , and the .Fl F and .Fl W options were introduced in .Fx 7.4 . .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 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. Index: head/usr.bin/tftp/main.c =================================================================== --- head/usr.bin/tftp/main.c (revision 358555) +++ head/usr.bin/tftp/main.c (revision 358556) @@ -1,1071 +1,1097 @@ /*- * 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 #if 0 #ifndef lint static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93"; #endif #endif #include __FBSDID("$FreeBSD$"); /* Many bug fixes are from Jim Guyton */ /* * TFTP User Program -- Command Interface. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftp-utils.h" #include "tftp-io.h" #include "tftp-options.h" #include "tftp.h" #define MAXLINE (2 * MAXPATHLEN) #define TIMEOUT 5 /* secs between rexmt's */ typedef struct sockaddr_storage peeraddr; static int connected; static char mode[32]; static jmp_buf toplevel; volatile int txrx_error; static int peer; #define MAX_MARGV 20 static int margc; static char *margv[MAX_MARGV]; int verbose; static char *port = NULL; static void get(int, char **); static void help(int, char **); static void intr(int); static void modecmd(int, char **); static void put(int, char **); static void quit(int, char **); static void setascii(int, char **); static void setbinary(int, char **); static void setpeer0(char *, const char *); static void setpeer(int, char **); static void settimeoutpacket(int, char **); static void settimeoutnetwork(int, char **); static void setdebug(int, char **); static void setverbose(int, char **); static void showstatus(int, char **); static void setblocksize(int, char **); static void setblocksize2(int, char **); static void setoptions(int, char **); static void setrollover(int, char **); static void setpacketdrop(int, char **); +static void setwindowsize(int, char **); static void command(bool, EditLine *, History *, HistEvent *) __dead2; static const char *command_prompt(void); static void urihandling(char *URI); static void getusage(char *); static void makeargv(char *line); static void putusage(char *); static void settftpmode(const char *); static char *tail(char *); static struct cmd *getcmd(char *); #define HELPINDENT (sizeof("connect")) struct cmd { const char *name; void (*handler)(int, char **); const char *help; }; static struct cmd cmdtab[] = { { "connect", setpeer, "connect to remote tftp" }, { "mode", modecmd, "set file transfer mode" }, { "put", put, "send file" }, { "get", get, "receive file" }, { "quit", quit, "exit tftp" }, { "verbose", setverbose, "toggle verbose mode" }, { "status", showstatus, "show current status" }, { "binary", setbinary, "set mode to octet" }, { "ascii", setascii, "set mode to netascii" }, { "rexmt", settimeoutpacket, "set per-packet retransmission timeout[-]" }, { "timeout", settimeoutnetwork, "set total retransmission timeout" }, { "trace", setdebug, "enable 'debug packet'[-]" }, { "debug", setdebug, "enable verbose output" }, { "blocksize", setblocksize, "set blocksize[*]" }, { "blocksize2", setblocksize2, "set blocksize as a power of 2[**]" }, { "rollover", setrollover, "rollover after 64K packets[**]" }, { "options", setoptions, "enable or disable RFC2347 style options" }, { "help", help, "print help information" }, { "packetdrop", setpacketdrop, "artificial packetloss feature" }, + { "windowsize", setwindowsize, "set windowsize[*]" }, { "?", help, "print help information" }, { NULL, NULL, NULL } }; static struct modes { const char *m_name; const char *m_mode; } modes[] = { { "ascii", "netascii" }, { "netascii", "netascii" }, { "binary", "octet" }, { "image", "octet" }, { "octet", "octet" }, { NULL, NULL } }; int main(int argc, char *argv[]) { HistEvent he; static EditLine *el; static History *hist; bool interactive; acting_as_client = 1; peer = -1; strcpy(mode, "octet"); signal(SIGINT, intr); interactive = isatty(STDIN_FILENO); if (interactive) { el = el_init("tftp", stdin, stdout, stderr); hist = history_init(); history(hist, &he, H_SETSIZE, 100); el_set(el, EL_HIST, history, hist); el_set(el, EL_EDITOR, "emacs"); el_set(el, EL_PROMPT, command_prompt); el_set(el, EL_SIGNAL, 1); el_source(el, NULL); } if (argc > 1) { if (setjmp(toplevel) != 0) exit(txrx_error); if (strncmp(argv[1], "tftp://", 7) == 0) { urihandling(argv[1]); exit(txrx_error); } setpeer(argc, argv); } if (setjmp(toplevel) != 0) { if (interactive) el_reset(el); (void)putchar('\n'); } init_options(); command(interactive, el, hist, &he); } /* * RFC3617 handling of TFTP URIs: * * tftpURI = "tftp://" host "/" file [ mode ] * mode = ";" "mode=" ( "netascii" / "octet" ) * file = *( unreserved / escaped ) * host = * unreserved = * escaped = * * We are cheating a little bit by allowing any mode as specified in the * modes table defined earlier on in this file and mapping it on the real * mode. */ static void urihandling(char *URI) { char uri[ARG_MAX]; char *host = NULL; char *path = NULL; char *opts = NULL; const char *tmode = "octet"; char *s; char line[MAXLINE]; int i; strlcpy(uri, URI, ARG_MAX); host = uri + 7; if ((s = strchr(host, '/')) == NULL) { fprintf(stderr, "Invalid URI: Couldn't find / after hostname\n"); exit(1); } *s = '\0'; path = s + 1; if ((s = strchr(path, ';')) != NULL) { *s = '\0'; opts = s + 1; if (strncmp(opts, "mode=", 5) == 0) { tmode = opts; tmode += 5; for (i = 0; modes[i].m_name != NULL; i++) { if (strcmp(modes[i].m_name, tmode) == 0) break; } if (modes[i].m_name == NULL) { fprintf(stderr, "Invalid mode: '%s'\n", mode); exit(1); } settftpmode(modes[i].m_mode); } } else { settftpmode("octet"); } setpeer0(host, NULL); sprintf(line, "get %s", path); makeargv(line); get(margc, margv); } static char hostname[MAXHOSTNAMELEN]; static void setpeer0(char *host, const char *lport) { struct addrinfo hints, *res0, *res; int error; const char *cause = "unknown"; if (connected) { close(peer); peer = -1; } connected = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; hints.ai_flags = AI_CANONNAME; if (!lport) lport = "tftp"; error = getaddrinfo(host, lport, &hints, &res0); if (error) { warnx("%s", gai_strerror(error)); return; } for (res = res0; res; res = res->ai_next) { if (res->ai_addrlen > sizeof(peeraddr)) continue; peer = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (peer < 0) { cause = "socket"; continue; } memset(&peer_sock, 0, sizeof(peer_sock)); peer_sock.ss_family = res->ai_family; peer_sock.ss_len = res->ai_addrlen; if (bind(peer, (struct sockaddr *)&peer_sock, peer_sock.ss_len) < 0) { cause = "bind"; close(peer); peer = -1; continue; } break; } if (peer < 0) warn("%s", cause); else { /* res->ai_addr <= sizeof(peeraddr) is guaranteed */ memcpy(&peer_sock, res->ai_addr, res->ai_addrlen); if (res->ai_canonname) { (void) strlcpy(hostname, res->ai_canonname, sizeof(hostname)); } else (void) strlcpy(hostname, host, sizeof(hostname)); connected = 1; } freeaddrinfo(res0); } static void setpeer(int argc, char *argv[]) { char line[MAXLINE]; if (argc < 2) { strcpy(line, "Connect "); printf("(to) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); makeargv(line); argc = margc; argv = margv; } if ((argc < 2) || (argc > 3)) { printf("usage: %s [host [port]]\n", argv[0]); return; } if (argc == 3) { port = argv[2]; setpeer0(argv[1], argv[2]); } else setpeer0(argv[1], NULL); } static void modecmd(int argc, char *argv[]) { struct modes *p; const char *sep; if (argc < 2) { printf("Using %s mode to transfer files.\n", mode); return; } if (argc == 2) { for (p = modes; p->m_name; p++) if (strcmp(argv[1], p->m_name) == 0) break; if (p->m_name) { settftpmode(p->m_mode); return; } printf("%s: unknown mode\n", argv[1]); /* drop through and print usage message */ } printf("usage: %s [", argv[0]); sep = " "; for (p = modes; p->m_name != NULL; p++) { printf("%s%s", sep, p->m_name); if (*sep == ' ') sep = " | "; } printf(" ]\n"); return; } static void setbinary(int argc __unused, char *argv[] __unused) { settftpmode("octet"); } static void setascii(int argc __unused, char *argv[] __unused) { settftpmode("netascii"); } static void settftpmode(const char *newmode) { strlcpy(mode, newmode, sizeof(mode)); if (verbose) printf("mode set to %s\n", mode); } /* * Send file(s). */ static void put(int argc, char *argv[]) { int fd; int n; char *cp, *targ; char line[MAXLINE]; struct stat sb; if (argc < 2) { strcpy(line, "send "); printf("(file) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); makeargv(line); argc = margc; argv = margv; } if (argc < 2) { putusage(argv[0]); return; } targ = argv[argc - 1]; if (strrchr(argv[argc - 1], ':')) { char *lcp; for (n = 1; n < argc - 1; n++) if (strchr(argv[n], ':')) { putusage(argv[0]); return; } lcp = argv[argc - 1]; targ = strrchr(lcp, ':'); *targ++ = 0; if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') { lcp[strlen(lcp) - 1] = '\0'; lcp++; } setpeer0(lcp, NULL); } if (!connected) { printf("No target machine specified.\n"); return; } if (argc < 4) { cp = argc == 2 ? tail(targ) : argv[1]; fd = open(cp, O_RDONLY); if (fd < 0) { warn("%s", cp); return; } if (fstat(fd, &sb) < 0) { warn("%s", cp); close(fd); return; } asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size); if (verbose) printf("putting %s to %s:%s [%s]\n", cp, hostname, targ, mode); xmitfile(peer, port, fd, targ, mode); close(fd); return; } /* this assumes the target is a directory */ /* on a remote unix system. hmmmm. */ cp = strchr(targ, '\0'); *cp++ = '/'; for (n = 1; n < argc - 1; n++) { strcpy(cp, tail(argv[n])); fd = open(argv[n], O_RDONLY); if (fd < 0) { warn("%s", argv[n]); continue; } if (fstat(fd, &sb) < 0) { warn("%s", argv[n]); continue; } asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size); if (verbose) printf("putting %s to %s:%s [%s]\n", argv[n], hostname, targ, mode); xmitfile(peer, port, fd, targ, mode); } } static void putusage(char *s) { printf("usage: %s file [remotename]\n", s); printf(" %s file host:remotename\n", s); printf(" %s file1 file2 ... fileN [[host:]remote-directory]\n", s); } /* * Receive file(s). */ static void get(int argc, char *argv[]) { int fd; int n; char *cp; char *src; char line[MAXLINE]; if (argc < 2) { strcpy(line, "get "); printf("(files) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); makeargv(line); argc = margc; argv = margv; } if (argc < 2) { getusage(argv[0]); return; } if (!connected) { for (n = 1; n < argc ; n++) if (strrchr(argv[n], ':') == 0) { printf("No remote host specified and " "no host given for file '%s'\n", argv[n]); getusage(argv[0]); return; } } for (n = 1; n < argc ; n++) { src = strrchr(argv[n], ':'); if (src == NULL) src = argv[n]; else { char *lcp; *src++ = 0; lcp = argv[n]; if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') { lcp[strlen(lcp) - 1] = '\0'; lcp++; } setpeer0(lcp, NULL); if (!connected) continue; } if (argc < 4) { cp = argc == 3 ? argv[2] : tail(src); fd = creat(cp, 0644); if (fd < 0) { warn("%s", cp); return; } if (verbose) printf("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); recvfile(peer, port, fd, src, mode); break; } cp = tail(src); /* new .. jdg */ fd = creat(cp, 0644); if (fd < 0) { warn("%s", cp); continue; } if (verbose) printf("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); recvfile(peer, port, fd, src, mode); } } static void getusage(char *s) { printf("usage: %s file [localname]\n", s); printf(" %s [host:]file [localname]\n", s); printf(" %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s); } static void settimeoutpacket(int argc, char *argv[]) { int t; char line[MAXLINE]; if (argc < 2) { strcpy(line, "Packet timeout "); printf("(value) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); makeargv(line); argc = margc; argv = margv; } if (argc != 2) { printf("usage: %s value\n", argv[0]); return; } t = atoi(argv[1]); if (t < 0) { printf("%s: bad value\n", argv[1]); return; } settimeouts(t, timeoutnetwork, maxtimeouts); } static void settimeoutnetwork(int argc, char *argv[]) { int t; char line[MAXLINE]; if (argc < 2) { strcpy(line, "Network timeout "); printf("(value) "); fgets(&line[strlen(line)], sizeof line - strlen(line), stdin); makeargv(line); argc = margc; argv = margv; } if (argc != 2) { printf("usage: %s value\n", argv[0]); return; } t = atoi(argv[1]); if (t < 0) { printf("%s: bad value\n", argv[1]); return; } settimeouts(timeoutpacket, t, maxtimeouts); } static void showstatus(int argc __unused, char *argv[] __unused) { printf("Remote host: %s\n", connected ? hostname : "none specified yet"); printf("RFC2347 Options support: %s\n", options_rfc_enabled ? "enabled" : "disabled"); printf("Non-RFC defined options support: %s\n", options_extra_enabled ? "enabled" : "disabled"); printf("Mode: %s\n", mode); printf("Verbose: %s\n", verbose ? "on" : "off"); printf("Debug: %s\n", debug_show(debug)); printf("Artificial packetloss: %d in 100 packets\n", packetdroppercentage); printf("Segment size: %d bytes\n", segsize); printf("Network timeout: %d seconds\n", timeoutpacket); printf("Maximum network timeout: %d seconds\n", timeoutnetwork); printf("Maximum timeouts: %d \n", maxtimeouts); } static void intr(int dummy __unused) { signal(SIGALRM, SIG_IGN); alarm(0); longjmp(toplevel, -1); } static char * tail(char *filename) { char *s; while (*filename) { s = strrchr(filename, '/'); if (s == NULL) break; if (s[1]) return (s + 1); *s = '\0'; } return (filename); } static const char * command_prompt(void) { return ("tftp> "); } /* * Command parser. */ static void command(bool interactive, EditLine *el, History *hist, HistEvent *hep) { struct cmd *c; const char *bp; char *cp; int len, num; char line[MAXLINE]; for (;;) { if (interactive) { if ((bp = el_gets(el, &num)) == NULL || num == 0) exit(0); len = MIN(MAXLINE, num); memcpy(line, bp, len); line[len - 1] = '\0'; history(hist, hep, H_ENTER, bp); } else { line[0] = 0; if (fgets(line, sizeof line , stdin) == NULL) { if (feof(stdin)) { exit(txrx_error); } else { continue; } } } if ((cp = strchr(line, '\n'))) *cp = '\0'; if (line[0] == 0) continue; makeargv(line); if (margc == 0) continue; c = getcmd(margv[0]); if (c == (struct cmd *)-1) { printf("?Ambiguous command\n"); continue; } if (c == NULL) { printf("?Invalid command\n"); continue; } (*c->handler)(margc, margv); } } static struct cmd * getcmd(char *name) { const char *p, *q; struct cmd *c, *found; int nmatches, longest; longest = 0; nmatches = 0; found = 0; for (c = cmdtab; (p = c->name) != NULL; c++) { for (q = name; *q == *p++; q++) if (*q == 0) /* exact match? */ return (c); if (!*q) { /* the name was a prefix */ if (q - name > longest) { longest = q - name; nmatches = 1; found = c; } else if (q - name == longest) nmatches++; } } if (nmatches > 1) return ((struct cmd *)-1); return (found); } /* * Slice a string up into argc/argv. */ static void makeargv(char *line) { char *cp; char **argp = margv; margc = 0; if ((cp = strchr(line, '\n')) != NULL) *cp = '\0'; for (cp = line; margc < MAX_MARGV - 1 && *cp != '\0';) { while (isspace(*cp)) cp++; if (*cp == '\0') break; *argp++ = cp; margc += 1; while (*cp != '\0' && !isspace(*cp)) cp++; if (*cp == '\0') break; *cp++ = '\0'; } *argp++ = 0; } static void quit(int argc __unused, char *argv[] __unused) { exit(txrx_error); } /* * Help command. */ static void help(int argc, char *argv[]) { struct cmd *c; if (argc == 1) { printf("Commands may be abbreviated. Commands are:\n\n"); for (c = cmdtab; c->name; c++) printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help); printf("\n[-] : You shouldn't use these ones anymore.\n"); printf("[*] : RFC2347 options support required.\n"); printf("[**] : Non-standard RFC2347 option.\n"); return; } while (--argc > 0) { char *arg; arg = *++argv; c = getcmd(arg); if (c == (struct cmd *)-1) printf("?Ambiguous help command: %s\n", arg); else if (c == (struct cmd *)0) printf("?Invalid help command: %s\n", arg); else printf("%s\n", c->help); } } static void setverbose(int argc __unused, char *argv[] __unused) { verbose = !verbose; printf("Verbose mode %s.\n", verbose ? "on" : "off"); } static void setoptions(int argc, char *argv[]) { if (argc == 2) { if (strcasecmp(argv[1], "enable") == 0 || strcasecmp(argv[1], "on") == 0) { options_extra_enabled = 1; options_rfc_enabled = 1; } if (strcasecmp(argv[1], "disable") == 0 || strcasecmp(argv[1], "off") == 0) { options_extra_enabled = 0; options_rfc_enabled = 0; } if (strcasecmp(argv[1], "extra") == 0) options_extra_enabled = !options_extra_enabled; } printf("Support for RFC2347 style options are now %s.\n", options_rfc_enabled ? "enabled" : "disabled"); printf("Support for non-RFC defined options are now %s.\n", options_extra_enabled ? "enabled" : "disabled"); printf("\nThe following options are available:\n" "\toptions on : enable support for RFC2347 style options\n" "\toptions off : disable support for RFC2347 style options\n" "\toptions extra : toggle support for non-RFC defined options\n" ); } static void setrollover(int argc, char *argv[]) { if (argc == 2) { if (strcasecmp(argv[1], "never") == 0 || strcasecmp(argv[1], "none") == 0) { free(options[OPT_ROLLOVER].o_request); options[OPT_ROLLOVER].o_request = NULL; } if (strcasecmp(argv[1], "1") == 0) { free(options[OPT_ROLLOVER].o_request); options[OPT_ROLLOVER].o_request = strdup("1"); } if (strcasecmp(argv[1], "0") == 0) { free(options[OPT_ROLLOVER].o_request); options[OPT_ROLLOVER].o_request = strdup("0"); } } printf("Support for the rollover options is %s.\n", options[OPT_ROLLOVER].o_request != NULL ? "enabled" : "disabled"); if (options[OPT_ROLLOVER].o_request != NULL) printf("Block rollover will be to block %s.\n", options[OPT_ROLLOVER].o_request); printf("\nThe following rollover options are available:\n" "\trollover 0 : rollover to block zero (default)\n" "\trollover 1 : rollover to block one\n" "\trollover never : do not support the rollover option\n" "\trollover none : do not support the rollover option\n" ); } static void setdebug(int argc, char *argv[]) { int i; if (argc != 1) { i = 1; while (i < argc) debug ^= debug_find(argv[i++]); } printf("The following debugging is enabled: %s\n", debug_show(debug)); printf("\nThe following debugs are available:\n"); i = 0; while (debugs[i].name != NULL) { printf("\t%s\t%s\n", debugs[i].name, debugs[i].desc); i++; } } static void setblocksize(int argc, char *argv[]) { if (!options_rfc_enabled) printf("RFC2347 style options are not enabled " "(but proceeding anyway)\n"); if (argc != 1) { int size = atoi(argv[1]); size_t max; u_long maxdgram; max = sizeof(maxdgram); if (sysctlbyname("net.inet.udp.maxdgram", &maxdgram, &max, NULL, 0) < 0) { perror("sysctl: net.inet.udp.maxdgram"); return; } if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { printf("Blocksize should be between %d and %d bytes.\n", BLKSIZE_MIN, BLKSIZE_MAX); return; } else if (size > (int)maxdgram - 4) { printf("Blocksize can't be bigger than %ld bytes due " "to the net.inet.udp.maxdgram sysctl limitation.\n", maxdgram - 4); asprintf(&options[OPT_BLKSIZE].o_request, "%ld", maxdgram - 4); } else { asprintf(&options[OPT_BLKSIZE].o_request, "%d", size); } } printf("Blocksize is now %s bytes.\n", options[OPT_BLKSIZE].o_request); } static void setblocksize2(int argc, char *argv[]) { if (!options_rfc_enabled || !options_extra_enabled) printf( "RFC2347 style or non-RFC defined options are not enabled " "(but proceeding anyway)\n"); if (argc != 1) { int size = atoi(argv[1]); int i; size_t max; u_long maxdgram; int sizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 0 }; max = sizeof(maxdgram); if (sysctlbyname("net.inet.udp.maxdgram", &maxdgram, &max, NULL, 0) < 0) { perror("sysctl: net.inet.udp.maxdgram"); return; } for (i = 0; sizes[i] != 0; i++) { if (sizes[i] == size) break; } if (sizes[i] == 0) { printf("Blocksize2 should be a power of two between " "8 and 32768.\n"); return; } if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { printf("Blocksize2 should be between " "%d and %d bytes.\n", BLKSIZE_MIN, BLKSIZE_MAX); return; } else if (size > (int)maxdgram - 4) { printf("Blocksize2 can't be bigger than %ld bytes due " "to the net.inet.udp.maxdgram sysctl limitation.\n", maxdgram - 4); for (i = 0; sizes[i+1] != 0; i++) { if ((int)maxdgram < sizes[i+1]) break; } asprintf(&options[OPT_BLKSIZE2].o_request, "%d", sizes[i]); } else { asprintf(&options[OPT_BLKSIZE2].o_request, "%d", size); } } printf("Blocksize2 is now %s bytes.\n", options[OPT_BLKSIZE2].o_request); } static void setpacketdrop(int argc, char *argv[]) { if (argc != 1) packetdroppercentage = atoi(argv[1]); printf("Randomly %d in 100 packets will be dropped\n", packetdroppercentage); +} + +static void +setwindowsize(int argc, char *argv[]) +{ + + if (!options_rfc_enabled) + printf("RFC2347 style options are not enabled " + "(but proceeding anyway)\n"); + + if (argc != 1) { + int size = atoi(argv[1]); + + if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) { + printf("Windowsize should be between %d and %d " + "blocks.\n", WINDOWSIZE_MIN, WINDOWSIZE_MAX); + return; + } else { + asprintf(&options[OPT_WINDOWSIZE].o_request, "%d", + size); + } + } + printf("Windowsize is now %s blocks.\n", + options[OPT_WINDOWSIZE].o_request); } Index: head/usr.bin/tftp/tftp.1 =================================================================== --- head/usr.bin/tftp/tftp.1 (revision 358555) +++ head/usr.bin/tftp/tftp.1 (revision 358556) @@ -1,272 +1,283 @@ .\" Copyright (c) 1990, 1993, 1994 .\" 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. .\" .\" @(#)tftp.1 8.2 (Berkeley) 4/18/94 .\" $FreeBSD$ .\" -.Dd Aug 22, 2018 +.Dd March 2, 2020 .Dt TFTP 1 .Os .Sh NAME .Nm tftp .Nd trivial file transfer program .Sh SYNOPSIS .Nm .Op Ar host Op Ar port .Sh DESCRIPTION The .Nm utility is the user interface to the Internet .Tn TFTP (Trivial File Transfer Protocol), which allows users to transfer files to and from a remote machine. The remote .Ar host may be specified on the command line, in which case .Nm uses .Ar host as the default host for future transfers (see the .Cm connect command below). .Sh COMMANDS Once .Nm is running, it issues the prompt .Dq Li tftp> and recognizes the following commands: .Pp .Bl -tag -width verbose -compact .It Cm \&? Ar command-name ... Print help information. .Pp .It Cm ascii Shorthand for "mode ascii" .Pp .It Cm binary Shorthand for "mode binary" .Pp .It Cm blocksize Ar [size] Sets the TFTP blksize option in TFTP Read Request or Write Request packets to .Ar [size] as specified in RFC 2348. Valid values are between 8 and 65464. If no blocksize is specified, then by default a blocksize of 512 bytes will be used. .Pp .It Cm blocksize2 Ar [size] Sets the TFTP blksize2 option in TFTP Read Request or Write Request packets to .Ar [size] . Values are restricted to powers of 2 between 8 and 32768. This is a non-standard TFTP option. .Pp .It Cm connect Ar host Op Ar port Set the .Ar host (and optionally .Ar port ) for transfers. Note that the .Tn TFTP protocol, unlike the .Tn FTP protocol, does not maintain connections between transfers; thus, the .Cm connect command does not actually create a connection, but merely remembers what host is to be used for transfers. You do not have to use the .Cm connect command; the remote host can be specified as part of the .Cm get or .Cm put commands. .Pp .It Cm debug Ar level Enable or disable debugging levels during verbose output. The value of .Ar level can be one of .Cm packet , simple , options , or .Cm access . .Pp .It Cm get Oo Ar host : Oc Ns Ar file Op Ar localname .It Cm get Xo .Oo Ar host1 : Oc Ns Ar file1 .Oo Ar host2 : Oc Ns Ar file2 ... .Oo Ar hostN : Oc Ns Ar fileN .Xc Get one or more files from the remote host. When using the .Ar host argument, the .Ar host will be used as default host for future transfers. If .Ar localname is specified, the file is stored locally as .Ar localname , otherwise the original filename is used. Note that it is not possible to download two files at a time, only one, three, or more than three files, at a time. .Pp To specify an IPv6 numeric address for a host, wrap it using square brackets like .Dq Li [3ffe:2900:e00c:ffee::1234] : Ns Ar file to disambiguate the colons used in the IPv6 address from the colon separating the host and the filename. .Pp .It Cm mode Ar transfer-mode Set the mode for transfers; .Ar transfer-mode may be one of .Em ascii or .Em binary . The default is .Em binary . .Pp .It Cm packetdrop [arg] Randomly drop .Ar arg out of 100 packets during a transfer. This is a debugging feature. .Pp .It Cm put Ar file Op Oo Ar host : Oc Ns Ar remotename .It Cm put Ar file1 file2 ... fileN Op Oo Ar host : Oc Ns Ar remote-directory Put a file or set of files to the remote host. When .Ar remotename is specified, the file is stored remotely as .Ar remotename , otherwise the original filename is used. If the .Ar remote-directory argument is used, the remote host is assumed to be a .Ux machine. To specify an IPv6 numeric address for a .Ar host , see the example under the .Cm get command. .Pp .It Cm options Ar [arg] Enable or disable support for TFTP options. The valid values of .Ar arg are .Cm on (enable RFC 2347 options), .Cm off (disable RFC 2347 options), and .Cm extra (toggle support for non-RFC defined options). .Pp .It Cm quit Exit .Nm . An end of file also exits. .Pp .It Cm rexmt Ar retransmission-timeout Set the per-packet retransmission timeout, in seconds. .Pp .It Cm rollover [arg] Specify the rollover option in TFTP Read Request or Write Request packets. After 65535 packets have been transmitted, set the block counter to .Ar arg . Valid values of .Ar arg are 0 and 1. This is a non-standard TFTP option. .Pp .It Cm status Show current status. .Pp .It Cm timeout Ar total-transmission-timeout Set the total transmission timeout, in seconds. .Pp .It Cm trace Toggle packet tracing. .Pp .It Cm verbose Toggle verbose mode. +.Pp +.It Cm windowsize Op Ar size +Sets the TFTP windowsize option in TFTP Read Request or Write Request packets to +.Op Ar size +blocks as specified in RFC 7440. +Valid values are between 1 and 65535. +If no windowsize is specified, +then the default windowsize of 1 block will be used. .El .Sh SEE ALSO .Xr tftpd 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 3617: Uniform Resource Identifier (URI) Scheme and Applicability Statement for the Trivial File Transfer Protocol (TFTP) +.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 command appeared in .Bx 4.3 . .Pp Edwin Groothuis performed a major rewrite of the .Xr tftpd 8 and .Nm code to support RFC2348. .Sh NOTES Because there is no user-login or validation within the .Tn TFTP protocol, the remote site will probably have some sort of file-access restrictions in place. The exact methods are specific to each site and therefore difficult to document here. .Pp Files larger than 33488896 octets (65535 blocks) cannot be transferred without client and server supporting the TFTP blocksize option (RFC2348), or the non-standard TFTP rollover option.