diff --git a/SOURCES/tftp-doc.patch b/SOURCES/tftp-doc.patch index 20352c9..f1cb511 100644 --- a/SOURCES/tftp-doc.patch +++ b/SOURCES/tftp-doc.patch @@ -25,3 +25,21 @@ Specify the use of filename remapping. The .I remap-file is a file containing the remapping rules. See the section on filename +@@ -243,7 +243,7 @@ option, but crash with an error if they + accepted by the server. + .SH "FILENAME REMAPPING" + The +-.B \-\-mapfile ++.B \-\-map\-file + option specifies a file which contains filename remapping rules. Each + non-comment line (comments begin with hash marks, + .BR # ) +@@ -395,7 +395,7 @@ flag is used to set up a chroot() enviro + once a connection has been set up. + .PP + Finally, the filename remapping +-.RB ( \-\-mapfile ++.RB ( \-\-map\-file + flag) support can be used to provide a limited amount of additional + access control. + .SH "CONFORMING TO" diff --git a/SOURCES/tftp-rewrite-macro.patch b/SOURCES/tftp-rewrite-macro.patch new file mode 100644 index 0000000..98b0874 --- /dev/null +++ b/SOURCES/tftp-rewrite-macro.patch @@ -0,0 +1,44 @@ +Resolves: #1485943 + +--- a/tftpd/tftpd.c 2017-08-28 09:12:11.681299000 +0200 ++++ b/tftpd/tftpd.c 2017-08-28 13:30:03.109312000 +0200 +@@ -1388,24 +1388,25 @@ + return strlen(p); + + case 'x': +- if (output) { +- if (from.sa.sa_family == AF_INET) { ++ if (from.sa.sa_family == AF_INET) { ++ if (output) { + sprintf(output, "%08lX", + (unsigned long)ntohl(from.si.sin_addr.s_addr)); +- l = 8; ++ } ++ l = 8; + #ifdef HAVE_IPV6 +- } else { +- unsigned char *c = (unsigned char *)SOCKADDR_P(&from); +- p = tb; +- for (l = 0; l < 16; l++) { +- sprintf(p, "%02X", *c); +- c++; +- p += 2; +- } ++ } else { ++ unsigned char *c = (unsigned char *)SOCKADDR_P(&from); ++ p = tb; ++ for (l = 0; l < 16; l++) { ++ sprintf(p, "%02X", *c); ++ c++; ++ p += 2; ++ } ++ if (output) + strcpy(output, tb); +- l = strlen(tb); ++ l = strlen(tb); + #endif +- } + } + return l; + + diff --git a/SOURCES/tftp-rfc7440-windowsize.patch b/SOURCES/tftp-rfc7440-windowsize.patch new file mode 100644 index 0000000..d8d3163 --- /dev/null +++ b/SOURCES/tftp-rfc7440-windowsize.patch @@ -0,0 +1,1992 @@ +commit e46782908e7026f27ef92de52e47ec3720116125 +Author: Jan Synacek +Date: Tue Nov 7 08:26:54 2017 +0100 + + implement rfc7440 + +diff --git a/common/Makefile b/common/Makefile +index a825213..d1e97b5 100644 +--- a/common/Makefile ++++ b/common/Makefile +@@ -4,7 +4,7 @@ VERSION = $(shell cat ../version) + -include ../MCONFIG + include ../MRULES + +-OBJS = tftpsubs.$(O) ++OBJS = tftpsubs.$(O) common.$(O) + LIB = libcommon.a + + all: $(LIB) +@@ -14,7 +14,7 @@ $(LIB): $(OBJS) + $(AR) $(LIB) $(OBJS) + $(RANLIB) $(LIB) + +-$(OBJS): tftpsubs.h ++$(OBJS): tftpsubs.h common.h + + install: + +diff --git a/common/common.c b/common/common.c +new file mode 100644 +index 0000000..a4e2fef +--- /dev/null ++++ b/common/common.c +@@ -0,0 +1,433 @@ ++/* ++ * Copyright (c) 2017 Jan Synáček ++ * 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. All advertising materials mentioning features or use of this software ++ * must display the following acknowledgement: ++ * This product includes software developed by the University of ++ * California, Berkeley and its contributors. ++ * 4. 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. ++ */ ++ ++#include ++#include ++#include ++#include "common.h" ++ ++static char *pktbuf; ++static int verbose; ++ ++const int SYNC_TIMEOUT = 50; /* ms */ ++ ++static void die(const char *fmt, ...) ++{ ++ va_list ap; ++ ++ va_start(ap, fmt); ++ fprintf(stderr, "fatal: "); ++ vfprintf(stderr, fmt, ap); ++ printf("\n"); ++ va_end(ap); ++ exit(1); ++} ++ ++int format_error(struct tftphdr *tp, char *error) ++{ ++ int r = 0; ++ ++ if (error) ++ r = snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg); ++ return r; ++} ++ ++void die_on_error(struct tftphdr *tp) ++{ ++ char error[ERROR_MAXLEN]; ++ ++ snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg); ++ fprintf(stderr, "%s\n", error); ++ exit(1); ++} ++ ++void send_error(int sockfd, union sock_addr *to, const char *msg) ++{ ++ char buf[516]; ++ struct tftphdr *out = (struct tftphdr *)buf; ++ int len; ++ ++ out->th_opcode = htons(ERROR); ++ out->th_code = htons(EUNDEF); ++ ++ len = strlen(msg) + 1; ++ memset(buf, 0, 516); ++ memcpy(out->th_msg, msg, len > 511 ? 511 : len); ++ len += 4; ++ ++ if (to) { ++ if (sendto(sockfd, out, len, 0, &to->sa, SOCKLEN(to)) != len) ++ die("send_error: sendto: %s", strerror(-errno)); ++ } else { ++ if (send(sockfd, out, len, 0) != len) ++ die("send_error: send: %s", strerror(-errno)); ++ } ++} ++ ++static void _send_ack(int sockfd, union sock_addr *to, unsigned short block, int check_errors) ++{ ++ struct tftphdr out; ++ out.th_opcode = htons(ACK); ++ out.th_block = htons(block); ++ ++ if (to) { ++ if (sendto(sockfd, &out, 4, 0, &to->sa, SOCKLEN(to)) != 4 && check_errors) ++ die("send_ack: sendto: %m"); ++ } else { ++ if (send(sockfd, &out, 4, 0) != 4 && check_errors) ++ die("send_ack: send: %m"); ++ } ++} ++ ++void send_ack(int sockfd, union sock_addr *to, unsigned short block) ++{ ++ _send_ack(sockfd, to, block, 1); ++} ++ ++ ++static size_t read_data(FILE *fp, ++ int blocksize, ++ unsigned short block, ++ int seek, ++ struct tftphdr *out) ++{ ++ out->th_opcode = htons(DATA); ++ out->th_block = htons(block); ++ if (seek) ++ (void) fseek(fp, seek, SEEK_CUR); ++ return fread(pktbuf + 4, 1, blocksize, fp); ++} ++ ++static size_t write_data(FILE *fp, char *buf, size_t count, int convert) ++{ ++ char wbuf[count]; ++ size_t i = 0, cnt = 0; ++ static int was_cr = 0; ++ ++ /* TODO: jsynacek: I don't think any conversion should take place... ++ * RFC 1350 says: "A host which receives netascii mode data must translate ++ * the data to its own format." ++ * That basically means nothing. What does "own format" even mean? ++ * The original implementation translated \r\n to \n and skipped \0 bytes, ++ * which aren't even legal in the netascii format. ++ * However, I believe that the file should remain as is before and after ++ * the transfer. ++ */ ++ convert = 0; ++ ++ if (convert == 0) ++ return fwrite(buf, 1, count, fp); ++ ++ /* Working conversion. Leave it as dead code for now. */ ++ if (was_cr && buf[0] == '\n') { ++ wbuf[cnt++] = '\n'; ++ i = 1; ++ (void) fseek(fp, -1, SEEK_CUR); ++ } ++ ++ while(i < count) { ++ if (buf[i] == '\r' && buf[i + 1] == '\n') { ++ wbuf[cnt++] = '\n'; ++ ++i; ++ } else if (buf[i] == '\0') { ++ /* Skip it */ ++ } else { ++ wbuf[cnt++] = buf[i]; ++ } ++ ++i; ++ } ++ /* Preserve state between data chunks */ ++ was_cr = buf[i - 1] == '\r'; ++ ++ if (fwrite(wbuf, 1, cnt, fp) == 0) ++ return 0; ++ ++ return count; ++} ++ ++void set_verbose(int v) ++{ ++ verbose = v; ++} ++ ++int recv_with_timeout(int s, void *in, int len, int timeout) ++{ ++ return recvfrom_flags_with_timeout(s, in, len, NULL, timeout, 0); ++} ++ ++int recvfrom_with_timeout(int s, ++ void *in, ++ size_t len, ++ union sock_addr *from, ++ int timeout) ++{ ++ return recvfrom_flags_with_timeout(s, in, len, from, timeout, 0); ++} ++ ++int recvfrom_flags_with_timeout(int s, ++ void *in, ++ size_t len, ++ union sock_addr *from, ++ int timeout, ++ int flags) ++{ ++ socklen_t fromlen = sizeof(from); ++ struct pollfd pfd; ++ int r; ++ ++ pfd.fd = s; ++ pfd.events = POLLIN; ++ pfd.revents = 0; ++ ++ r = poll(&pfd, 1, timeout); ++ if (r > 0) { ++ if (from) { ++ r = recvfrom(s, in, len, flags, &from->sa, &fromlen); ++ if (r < 0) ++ die("recvfrom_flags_with_timeout: recvfrom: %m"); ++ } else { ++ r = recv(s, in, len, flags); ++ if (r < 0) ++ die("recvfrom_flags_with_timeout: recv: %m"); ++ } ++ } ++ ++ return r; ++} ++ ++int receiver(int sockfd, ++ union sock_addr *server, ++ int blocksize, ++ int windowsize, ++ int timeout, ++ FILE *fp, ++ unsigned long *received, ++ char *error) ++{ ++ struct tftphdr *tp; ++ unsigned short tp_opcode, tp_block; ++ unsigned short block = 1; ++ unsigned long amount = 0; ++ int pktsize = blocksize + 4; ++ int window = 1; ++ size_t size; ++ int retries; ++ int n, r = 0; ++ ++ pktbuf = calloc(pktsize, 1); ++ if (!pktbuf) ++ die("Out of memory!"); ++ tp = (struct tftphdr *)pktbuf; ++ ++ retries = RETRIES; ++ do { ++ n = recvfrom_with_timeout(sockfd, tp, pktsize, server, timeout); ++ if (n == 0) { ++ if (--retries <= 0) { ++ r = E_TIMED_OUT; ++ goto abort; ++ } ++ continue; ++ } ++ retries = RETRIES; ++ ++ tp_opcode = ntohs(tp->th_opcode); ++ tp_block = ntohs(tp->th_block); ++ ++ if (tp_opcode == DATA) { ++ if (tp_block == block) { ++ size = write_data(fp, tp->th_data, n - 4, 0); ++ if (size == 0 && ferror(fp)) { ++ send_error(sockfd, server, "Failed to write data"); ++ r = E_FAILED_TO_WRITE; ++ goto abort; ++ } ++ amount += size; ++ ++ if (window++ >= windowsize || size != blocksize) { ++ send_ack(sockfd, server, block); ++ window = 1; ++ } ++ ++ ++block; ++ } else { ++ do { ++ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT); ++ } while (n > 0); ++ if (windowsize > 0) { ++ send_ack(sockfd, server, block - 1); ++ } ++ window = 1; ++ n = 0; ++ } ++ } else if (tp_opcode == ERROR) { ++ format_error(tp, error); ++ r = E_RECEIVED_ERROR; ++ goto abort; ++ } else { ++ r = E_UNEXPECTED_PACKET; ++ goto abort; ++ } ++ } while (size == blocksize || n == 0); ++ ++ /* Last ack can get lost, let's try and resend it twice ++ * to make it more likely that the ack gets to the sender. ++ */ ++ --block; ++ for (n = 0; n < 2; ++n) { ++ usleep(SYNC_TIMEOUT * 1000); ++ _send_ack(sockfd, server, block, 0); ++ } ++ ++ if (received) ++ *received = amount; ++abort: ++ free(pktbuf); ++ return r; ++} ++ ++int sender(int sockfd, ++ union sock_addr *server, ++ int blocksize, ++ int windowsize, ++ int timeout, ++ int rollover, ++ FILE *fp, ++ unsigned long *sent) ++{ ++ struct tftphdr *tp; ++ unsigned short tp_opcode, tp_block; ++ unsigned short block = 1; ++ unsigned long amount = 0; ++ int l_timeout = timeout; ++ int pktsize = blocksize + 4; ++ int window = 1; ++ int seek = 0; ++ int retries; ++ size_t size; ++ int done = 0; ++ int n, r = 0; ++ ++ pktbuf = calloc(pktsize, 1); ++ if (!pktbuf) ++ die("Out of memory!"); ++ tp = (struct tftphdr *)pktbuf; ++ ++ retries = RETRIES; ++ do { ++ size = read_data(fp, blocksize, block, seek, tp); ++ if (size == 0 && ferror(fp)) { ++ send_error(sockfd, server, "Error while reading the file"); ++ r = E_FAILED_TO_READ; ++ goto abort; ++ } ++ amount += size; ++ seek = 0; ++ ++ if (server) ++ n = sendto(sockfd, tp, size + 4, 0, &server->sa, SOCKLEN(server)); ++ else ++ n = send(sockfd, tp, size + 4, 0); ++ if (n != size + 4) { ++ syslog(LOG_WARNING, "tftpd: send: %m"); ++ r = E_SYSTEM_ERROR; ++ goto abort; ++ } ++ ++ if (window++ < windowsize) { ++ /* Even if the entire window is not sent, the server should check for incoming packets ++ * and react to out of order ACKs, or ERRORs. ++ */ ++ l_timeout = 0; ++ } else { ++ l_timeout = timeout; ++ window = 1; ++ } ++ done = size != blocksize; ++ if (done) { ++ l_timeout = timeout; ++ window = 1; ++ } ++ ++ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, l_timeout); ++ if (n < 0) { ++ syslog(LOG_WARNING, "tftpd: recv: %m"); ++ r = E_SYSTEM_ERROR; ++ goto abort; ++ } else if (l_timeout > 0 && n == 0) { ++ seek = -size; ++ if (--retries <= 0) { ++ r = E_TIMED_OUT; ++ goto abort; ++ } ++ done = 0; ++ continue; ++ } ++ ++ if (++block == 0) ++ block = rollover; ++ ++ tp_opcode = ntohs(tp->th_opcode); ++ tp_block = ntohs(tp->th_block); ++ ++ if (tp_opcode == ACK) { ++ if (tp_block != (unsigned short)(block - 1)) { ++ int offset = tp_block; ++ ++ done = 0; ++ do { ++ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT); ++ } while (n > 0); ++ /* This is a bit of a hack. Mismatched packets that are "over the edge" of the unsigned short ++ * have to be handled and a correct seek has to be issued. In theory, overflowing block number ++ * should not even be supported, as it is impossible to distinguish correctly if more than 2^16 ++ * packets are sent, but the first ones are received later than the last ones. ++ */ ++ if (tp_block > 65000 && tp_block > (unsigned short)(block - 1)) ++ offset -= 65535; ++ seek = (offset - block + 2) * blocksize - size; ++ } ++ block = tp_block + 1; ++ retries = RETRIES; ++ } else if (tp_opcode == ERROR) { ++ r = E_RECEIVED_ERROR; ++ goto abort; ++ } ++ } while (!done); ++ ++ if (sent) ++ *sent = amount; ++abort: ++ free(pktbuf); ++ return r; ++} +diff --git a/common/common.h b/common/common.h +new file mode 100644 +index 0000000..31c1b7c +--- /dev/null ++++ b/common/common.h +@@ -0,0 +1,97 @@ ++/* ++ * Copyright (c) 2017 Jan Synáček ++ * 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. All advertising materials mentioning features or use of this software ++ * must display the following acknowledgement: ++ * This product includes software developed by the University of ++ * California, Berkeley and its contributors. ++ * 4. 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 _COMMON_H ++#define _COMMON_H ++ ++#include ++#include "config.h" ++ ++#define TIMEOUT 1000 /* ms */ ++#define RETRIES 6 ++ ++#define E_TIMED_OUT -1 ++#define E_RECEIVED_ERROR -2 ++#define E_UNEXPECTED_PACKET -3 ++#define E_FAILED_TO_READ -4 ++#define E_FAILED_TO_WRITE -5 ++#define E_SYSTEM_ERROR -6 ++#define ERROR_MAXLEN 511 ++ ++union sock_addr { ++ struct sockaddr sa; ++ struct sockaddr_in si; ++#ifdef HAVE_IPV6 ++ struct sockaddr_in6 s6; ++#endif ++}; ++ ++#define SOCKLEN(sock) \ ++ (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ ++ (sizeof(struct sockaddr_in)) : \ ++ (sizeof(union sock_addr))) ++ ++const char *opcode_to_str(unsigned short opcode); ++int str_equal(const char *s1, const char *s2); ++void set_verbose(int v); ++ ++int format_error(struct tftphdr *tp, char *error); ++void die_on_error(struct tftphdr *tp); ++void send_error(int sockfd, union sock_addr *to, const char *msg); ++void send_ack(int sockfd, union sock_addr *to, unsigned short block); ++int recv_with_timeout(int s, void *in, int len, int timeout); ++int recvfrom_with_timeout(int s, void *in, size_t len, union sock_addr *from, int timeout); ++int recvfrom_flags_with_timeout(int s, ++ void *in, ++ size_t len, ++ union sock_addr *from, ++ int timeout, ++ int flags); ++int receiver(int sockfd, ++ union sock_addr *server, ++ int blocksize, ++ int windowsize, ++ int timeout, ++ FILE *fp, ++ unsigned long *received, ++ char *error); ++ ++int sender(int sockfd, ++ union sock_addr *server, ++ int blocksize, ++ int windowsize, ++ int timeout, ++ int rollover, ++ FILE *fp, ++ unsigned long *sent); ++#endif +diff --git a/common/tftpsubs.c b/common/tftpsubs.c +index 8c999f6..6033e9b 100644 +--- a/common/tftpsubs.c ++++ b/common/tftpsubs.c +@@ -404,3 +404,23 @@ char *strip_address(char *addr) + return addr; + } + #endif ++ ++ ++int str_equal(const char *s1, const char *s2) ++{ ++ return !strcmp(s1, s2); ++} ++ ++ ++const char *opcode_to_str(unsigned short opcode) ++{ ++ switch(opcode) { ++ case RRQ: return "RRQ"; ++ case WRQ: return "WRQ"; ++ case DATA: return "DATA"; ++ case ACK: return "ACK"; ++ case ERROR: return "ERROR"; ++ case OACK: return "OACK"; ++ } ++ return "UNKNOWN"; ++} +diff --git a/common/tftpsubs.h b/common/tftpsubs.h +index b3a3bf3..311b141 100644 +--- a/common/tftpsubs.h ++++ b/common/tftpsubs.h +@@ -38,21 +38,9 @@ + #ifndef TFTPSUBS_H + #define TFTPSUBS_H + ++#include "common.h" + #include "config.h" + +-union sock_addr { +- struct sockaddr sa; +- struct sockaddr_in si; +-#ifdef HAVE_IPV6 +- struct sockaddr_in6 s6; +-#endif +-}; +- +-#define SOCKLEN(sock) \ +- (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ +- (sizeof(struct sockaddr_in)) : \ +- (sizeof(union sock_addr))) +- + #ifdef HAVE_IPV6 + #define SOCKPORT(sock) \ + (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ +diff --git a/tftp/extern.h b/tftp/extern.h +index 78474fc..dd0523f 100644 +--- a/tftp/extern.h ++++ b/tftp/extern.h +@@ -34,7 +34,7 @@ + #ifndef RECVFILE_H + #define RECVFILE_H + +-void tftp_recvfile(int, const char *, const char *); +-void tftp_sendfile(int, const char *, const char *); ++void tftp_recvfile(int, const char *, const char *, int); ++void tftp_sendfile(int, const char *, const char *, int); + + #endif +diff --git a/tftp/main.c b/tftp/main.c +index fcf5a25..5a7d3a6 100644 +--- a/tftp/main.c ++++ b/tftp/main.c +@@ -49,7 +49,7 @@ + + #include "extern.h" + +-#define TIMEOUT 5 /* secs between rexmt's */ ++#define RTIMEOUT 5 /* secs between rexmt's */ + #define LBUFLEN 200 /* size of input buffer */ + + struct modes { +@@ -82,7 +82,7 @@ int ai_fam_sock = AF_INET; + union sock_addr peeraddr; + int f = -1; + u_short port; +-int trace; ++int trace_opt; + int verbose; + int literal; + int connected; +@@ -104,6 +104,7 @@ struct servent *sp; + int portrange = 0; + unsigned int portrange_from = 0; + unsigned int portrange_to = 0; ++int windowsize = -1; + + void get(int, char **); + void help(int, char **); +@@ -191,14 +192,14 @@ char *xstrdup(const char *); + + const char *program; + +-static inline void usage(int errcode) ++static void usage(int errcode) + { + fprintf(stderr, + #ifdef HAVE_IPV6 +- "Usage: %s [-4][-6][-v][-V][-l][-m mode] [-R port:port] " ++ "Usage: %s [-4][-6][-v][-V][-l][-m mode][-w size] [-R port:port] " + "[host [port]] [-c command]\n", + #else +- "Usage: %s [-v][-V][-l][-m mode] [-R port:port] " ++ "Usage: %s [-v][-V][-l][-m mode][-w size] [-R port:port] " + "[host [port]] [-c command]\n", + #endif + program); +@@ -279,6 +280,15 @@ int main(int argc, char *argv[]) + } + portrange = 1; + break; ++ case 'w': ++ if (++arg >= argc) ++ usage(EX_USAGE); ++ windowsize = atoi(argv[arg]); ++ if (windowsize <= 0 || windowsize > 64) { ++ fprintf(stderr, "Bad window size: %s (1-64)\n", argv[arg]); ++ exit(EX_USAGE); ++ } ++ break; + case 'h': + default: + usage(*optx == 'h' ? 0 : EX_USAGE); +@@ -593,7 +603,7 @@ void put(int argc, char *argv[]) + printf("putting %s to %s:%s [%s]\n", + cp, hostname, targ, mode->m_mode); + sa_set_port(&peeraddr, port); +- tftp_sendfile(fd, targ, mode->m_mode); ++ tftp_sendfile(fd, targ, mode->m_mode, windowsize); + return; + } + /* this assumes the target is a directory */ +@@ -625,7 +635,7 @@ void put(int argc, char *argv[]) + printf("putting %s to %s:%s [%s]\n", + argv[n], hostname, remote_pth, mode->m_mode); + sa_set_port(&peeraddr, port); +- tftp_sendfile(fd, remote_pth, mode->m_mode); ++ tftp_sendfile(fd, remote_pth, mode->m_mode, windowsize); + } + } + +@@ -693,7 +703,7 @@ void get(int argc, char *argv[]) + printf("getting from %s:%s to %s [%s]\n", + hostname, src, cp, mode->m_mode); + sa_set_port(&peeraddr, port); +- tftp_recvfile(fd, src, mode->m_mode); ++ tftp_recvfile(fd, src, mode->m_mode, windowsize); + break; + } + cp = tail(src); /* new .. jdg */ +@@ -708,7 +718,7 @@ void get(int argc, char *argv[]) + printf("getting from %s:%s to %s [%s]\n", + hostname, src, cp, mode->m_mode); + sa_set_port(&peeraddr, port); +- tftp_recvfile(fd, src, mode->m_mode); ++ tftp_recvfile(fd, src, mode->m_mode, windowsize); + } + } + +@@ -718,7 +728,7 @@ static void getusage(char *s) + printf(" %s file file ... file if connected\n", s); + } + +-int rexmtval = TIMEOUT; ++int rexmtval = RTIMEOUT; + + void setrexmt(int argc, char *argv[]) + { +@@ -741,7 +751,7 @@ void setrexmt(int argc, char *argv[]) + rexmtval = t; + } + +-int maxtimeout = 5 * TIMEOUT; ++int maxtimeout = 5 * RTIMEOUT; + + void settimeout(int argc, char *argv[]) + { +@@ -781,7 +791,7 @@ void status(int argc, char *argv[]) + else + printf("Not connected.\n"); + printf("Mode: %s Verbose: %s Tracing: %s Literal: %s\n", mode->m_mode, +- verbose ? "on" : "off", trace ? "on" : "off", ++ verbose ? "on" : "off", trace_opt ? "on" : "off", + literal ? "on" : "off"); + printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", + rexmtval, maxtimeout); +@@ -969,8 +979,8 @@ void settrace(int argc, char *argv[]) + (void)argc; + (void)argv; /* Quiet unused warning */ + +- trace = !trace; +- printf("Packet tracing %s.\n", trace ? "on" : "off"); ++ trace_opt = !trace_opt; ++ printf("Packet tracing %s.\n", trace_opt ? "on" : "off"); + } + + void setverbose(int argc, char *argv[]) +diff --git a/tftp/tftp.1.in b/tftp/tftp.1.in +index b41f7b5..1779a43 100644 +--- a/tftp/tftp.1.in ++++ b/tftp/tftp.1.in +@@ -82,6 +82,9 @@ Default to verbose mode. + .B \-V + Print the version number and configuration to standard output, then + exit gracefully. ++.TP ++.B \-w\fP \fIwindow-size\fP ++Set the "windowsize" TFTP option (RFC 7440) to the specified value. + .SH COMMANDS + Once + .B tftp +diff --git a/tftp/tftp.c b/tftp/tftp.c +index 9d15022..58ea597 100644 +--- a/tftp/tftp.c ++++ b/tftp/tftp.c +@@ -31,355 +31,307 @@ + * SUCH DAMAGE. + */ + ++#include ++#include + #include "common/tftpsubs.h" +- +-/* +- * TFTP User Program -- Protocol Machines +- */ + #include "extern.h" + ++/* TODO: This 'peeraddr' global should be removed. */ + extern union sock_addr peeraddr; /* filled in by main */ + extern int f; /* the opened socket */ +-extern int trace; ++extern int trace_opt; + extern int verbose; +-extern int rexmtval; +-extern int maxtimeout; +- ++/* TODO: Adjust when blocksize is implemented. */ + #define PKTSIZE SEGSIZE+4 +-char ackbuf[PKTSIZE]; +-int timeout; +-sigjmp_buf toplevel; +-sigjmp_buf timeoutbuf; ++static char pktbuf[PKTSIZE]; + +-static void nak(int, const char *); +-static int makerequest(int, const char *, struct tftphdr *, const char *); + static void printstats(const char *, unsigned long); + static void startclock(void); + static void stopclock(void); +-static void timer(int); +-static void tpacket(const char *, struct tftphdr *, int); ++static void timed_out(void) ++{ ++ printf("client: timed out"); ++ exit(1); ++} ++ ++static void die(const char *fmt, ...) ++{ ++ va_list ap; ++ ++ va_start(ap, fmt); ++ fprintf(stderr, "fatal: client: "); ++ vfprintf(stderr, fmt, ap); ++ printf("\n"); ++ va_end(ap); ++ exit(1); ++} ++ ++static size_t make_request(unsigned short opcode, ++ const char *name, ++ const char *mode, ++ int blocksize, ++ int windowsize, ++ struct tftphdr *out) ++{ ++ char *cp, buf[16]; ++ size_t len; ++ ++ cp = (char *)&(out->th_stuff); ++ ++ out->th_opcode = htons(opcode); ++ ++ len = strlen(name) + 1; ++ memcpy(cp, name, len); ++ cp += len; ++ ++ len = strlen(mode) + 1; ++ memcpy(cp, mode, len); ++ cp += len; ++ ++ /* Don't include options with default values. */ ++ ++ /* TODO: TBI in a separate patch. */ ++ (void) blocksize; ++ /*if (blocksize != SEGSIZE) { ++ len = strlen("blksize") + 1; ++ memcpy(cp, "blksize", len); ++ cp += len; ++ if (snprintf(buf, 16, "%u", blocksize) < 0) ++ die("out of memory"); ++ len = strlen(buf) + 1; ++ memcpy(cp, buf, len); ++ cp += len; ++ }*/ ++ ++ if (windowsize > 0) { ++ len = strlen("windowsize") + 1; ++ memcpy(cp, "windowsize", len); ++ cp += len; ++ if (snprintf(buf, 16, "%u", windowsize) < 0) ++ die("out of memory"); ++ len = strlen(buf) + 1; ++ memcpy(cp, buf, len); ++ cp += len; ++ } ++ ++ return (cp - (char *)out); ++} ++ ++static void send_request(int sock, ++ union sock_addr *to, ++ short request, ++ const char *name, ++ const char *mode, ++ unsigned blocksize, ++ unsigned windowsize) ++{ ++ struct tftphdr *out; ++ size_t size; ++ ++ out = (struct tftphdr *)pktbuf; ++ size = make_request(request, name, mode, blocksize, windowsize, out); ++ ++ if (sendto(sock, out, size, 0, &to->sa, SOCKLEN(to)) != (unsigned)size) ++ die("send_request: sendto: %m"); ++} ++ ++static int wait_for_oack(int sock, union sock_addr *from, char **options, int *optlen) ++{ ++ unsigned short in_opcode; ++ struct tftphdr *in; ++ int r; ++ ++ r = recvfrom_with_timeout(sock, pktbuf, sizeof(pktbuf), from, TIMEOUT); ++ if (r == 0) ++ return r; ++ ++ in = (struct tftphdr *)pktbuf; ++ in_opcode = ntohs(in->th_opcode); ++ ++ if (in_opcode == ERROR) ++ die_on_error(in); ++ if (in_opcode != OACK) ++ return -in_opcode; ++ ++ *options = pktbuf + 2; ++ *optlen = r - 2; ++ ++ return 1; ++} + + /* + * Send the requested file. + */ +-void tftp_sendfile(int fd, const char *name, const char *mode) ++void tftp_sendfile(int fd, const char *name, const char *mode, int windowsize) + { +- struct tftphdr *ap; /* data and ack packets */ +- struct tftphdr *dp; +- int n; +- volatile int is_request; +- volatile u_short block; +- volatile int size, convert; +- volatile off_t amount; +- union sock_addr from; +- socklen_t fromlen; +- FILE *file; +- u_short ap_opcode, ap_block; +- +- startclock(); /* start stat's clock */ +- dp = r_init(); /* reset fillbuf/read-ahead code */ +- ap = (struct tftphdr *)ackbuf; +- convert = !strcmp(mode, "netascii"); +- file = fdopen(fd, convert ? "rt" : "rb"); +- block = 0; +- is_request = 1; /* First packet is the actual WRQ */ +- amount = 0; +- +- bsd_signal(SIGALRM, timer); ++ union sock_addr server = peeraddr; ++ unsigned long amount = 0; ++ int blocksize = SEGSIZE; ++ char *options; ++ int optlen; ++ int retries; ++ FILE *fp; ++ int n, r; ++ ++ set_verbose(trace_opt + verbose); ++ ++ startclock(); ++ send_request(f, &server, WRQ, name, mode, blocksize, windowsize); ++ ++ /* If no windowsize was specified on the command line, ++ * don't bother with options. ++ * When blocksize is supported, this should actually only be called ++ * if no options were sent in the RRQ. ++ */ ++ if (windowsize < 0) ++ goto no_options; ++ retries = RETRIES; + do { +- if (is_request) { +- size = makerequest(WRQ, name, dp, mode) - 4; +- } else { +- /* size = read(fd, dp->th_data, SEGSIZE); */ +- size = readit(file, &dp, convert); +- if (size < 0) { +- nak(errno + 100, NULL); +- break; +- } +- dp->th_opcode = htons((u_short) DATA); +- dp->th_block = htons((u_short) block); ++ r = wait_for_oack(f, &server, &options, &optlen); ++ if (r < 0) { ++ return; + } +- timeout = 0; +- (void)sigsetjmp(timeoutbuf, 1); +- +- if (trace) +- tpacket("sent", dp, size + 4); +- n = sendto(f, dp, size + 4, 0, +- &peeraddr.sa, SOCKLEN(&peeraddr)); +- if (n != size + 4) { +- perror("tftp: sendto"); +- goto abort; +- } +- read_ahead(file, convert); +- for (;;) { +- alarm(rexmtval); +- do { +- fromlen = sizeof(from); +- n = recvfrom(f, ackbuf, sizeof(ackbuf), 0, +- &from.sa, &fromlen); +- } while (n <= 0); +- alarm(0); +- if (n < 0) { +- perror("tftp: recvfrom"); +- goto abort; +- } +- sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */ +- if (trace) +- tpacket("received", ap, n); +- /* should verify packet came from server */ +- ap_opcode = ntohs((u_short) ap->th_opcode); +- ap_block = ntohs((u_short) ap->th_block); +- if (ap_opcode == ERROR) { +- printf("Error code %d: %s\n", ap_block, ap->th_msg); +- goto abort; +- } +- if (ap_opcode == ACK) { +- int j; + +- if (ap_block == block) { +- break; ++ /* Parse returned options. */ ++ n = 0; ++ if (r != 0) { ++ char *opt, *val; ++ int got_ws = 0; ++ int v; ++ ++ while (n < optlen) { ++ opt = options + n; ++ n += strlen(opt) + 1; ++ val = options + n; ++ if (str_equal(opt, "windowsize") && windowsize != 1) { ++ v = atoi(val); ++ if (v != windowsize) ++ printf("client: server negotiated different windowsize: %d", v); ++ /* Assumes v > 0, it probably shouldn't. */ ++ windowsize = v; ++ got_ws = 1; + } +- /* On an error, try to synchronize +- * both sides. +- */ +- j = synchnet(f); +- if (j && trace) { +- printf("discarded %d packets\n", j); +- } +- /* +- * RFC1129/RFC1350: We MUST NOT re-send the DATA +- * packet in response to an invalid ACK. Doing so +- * would cause the Sorcerer's Apprentice bug. +- */ ++ n += strlen(val) + 1; ++ } ++ ++ if (got_ws == 0 && windowsize != 1) { ++ windowsize = 1; ++ printf("client: server didn't negotiate windowsize, continuing with windowsize=1"); + } + } +- if (!is_request) +- amount += size; +- is_request = 0; +- block++; +- } while (size == SEGSIZE || block == 1); +- abort: +- fclose(file); ++ } while (r == 0 && --retries > 0); ++ if (retries <= 0) ++ timed_out(); ++ ++no_options: ++ if (windowsize < 0) { ++ struct tftphdr *tp = (struct tftphdr *)pktbuf; ++ ++ retries = RETRIES; ++ do { ++ r = recvfrom_with_timeout(f, pktbuf, PKTSIZE, &server, TIMEOUT); ++ if (r == 0) { ++ /* Timed out. */ ++ continue; ++ } ++ if (ntohs(tp->th_opcode) == ACK && ntohs(tp->th_block) == 0) { ++ break; ++ } else if (ntohs(tp->th_opcode) == ERROR) { ++ die_on_error(tp); ++ } ++ } while (r == 0 && --retries > 0); ++ if (retries <= 0) ++ timed_out(); ++ } ++ fp = fdopen(fd, "r"); ++ r = sender(f, &server, blocksize, windowsize, TIMEOUT, 0, fp, &amount); ++ if (r < 0) ++ exit(1); ++ + stopclock(); + if (amount > 0) + printstats("Sent", amount); + } + ++ + /* + * Receive a file. + */ +-void tftp_recvfile(int fd, const char *name, const char *mode) ++void tftp_recvfile(int fd, const char *name, const char *mode, int windowsize) + { +- struct tftphdr *ap; +- struct tftphdr *dp; +- int n; +- volatile u_short block; +- volatile int size, firsttrip; +- volatile unsigned long amount; +- union sock_addr from; +- socklen_t fromlen; +- FILE *file; +- volatile int convert; /* true if converting crlf -> lf */ +- u_short dp_opcode, dp_block; ++ union sock_addr server = peeraddr; ++ unsigned long amount = 0; ++ int blocksize = SEGSIZE; ++ char *options, error[ERROR_MAXLEN]; ++ int optlen; ++ int retries; ++ FILE *fp; ++ int n, r; ++ ++ set_verbose(trace_opt + verbose); + + startclock(); +- dp = w_init(); +- ap = (struct tftphdr *)ackbuf; +- convert = !strcmp(mode, "netascii"); +- file = fdopen(fd, convert ? "wt" : "wb"); +- block = 1; +- firsttrip = 1; +- amount = 0; +- +- bsd_signal(SIGALRM, timer); ++ ++ send_request(f, &server, RRQ, name, mode, blocksize, windowsize); ++ /* If no windowsize was specified on the command line, ++ * don't bother with options. ++ * When blocksize is supported, this should actually only be called ++ * if no options were sent in the RRQ. ++ */ ++ if (windowsize < 0) ++ goto no_options; ++ retries = RETRIES; + do { +- if (firsttrip) { +- size = makerequest(RRQ, name, ap, mode); +- firsttrip = 0; +- } else { +- ap->th_opcode = htons((u_short) ACK); +- ap->th_block = htons((u_short) block); +- size = 4; +- block++; +- } +- timeout = 0; +- (void)sigsetjmp(timeoutbuf, 1); +- send_ack: +- if (trace) +- tpacket("sent", ap, size); +- if (sendto(f, ackbuf, size, 0, &peeraddr.sa, +- SOCKLEN(&peeraddr)) != size) { +- alarm(0); +- perror("tftp: sendto"); +- goto abort; ++ r = wait_for_oack(f, &server, &options, &optlen); ++ if (r < 0) { ++ return; + } +- write_behind(file, convert); +- for (;;) { +- alarm(rexmtval); +- do { +- fromlen = sizeof(from); +- n = recvfrom(f, dp, PKTSIZE, 0, +- &from.sa, &fromlen); +- } while (n <= 0); +- alarm(0); +- if (n < 0) { +- perror("tftp: recvfrom"); +- goto abort; +- } +- sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */ +- if (trace) +- tpacket("received", dp, n); +- /* should verify client address */ +- dp_opcode = ntohs((u_short) dp->th_opcode); +- dp_block = ntohs((u_short) dp->th_block); +- if (dp_opcode == ERROR) { +- printf("Error code %d: %s\n", dp_block, dp->th_msg); +- goto abort; +- } +- if (dp_opcode == DATA) { +- int j; + +- if (dp_block == block) { +- break; /* have next packet */ +- } +- /* On an error, try to synchronize +- * both sides. +- */ +- j = synchnet(f); +- if (j && trace) { +- printf("discarded %d packets\n", j); +- } +- if (dp_block == (block - 1)) { +- goto send_ack; /* resend ack */ ++ /* Parse returned options. */ ++ n = 0; ++ if (r != 0) { ++ char *opt, *val; ++ int got_ws = 0; ++ int v; ++ ++ while (n < optlen) { ++ opt = options + n; ++ n += strlen(opt) + 1; ++ val = options + n; ++ if (str_equal(opt, "windowsize") && windowsize != 1) { ++ v = atoi(val); ++ if (v != windowsize) ++ printf("client: server negotiated different windowsize: %d", v); ++ /* Assumes v > 0, it probably shouldn't. */ ++ windowsize = v; ++ got_ws = 1; + } ++ n += strlen(val) + 1; ++ } ++ ++ if (got_ws == 0 && windowsize != 1) { ++ windowsize = 1; ++ printf("client: server didn't negotiate windowsize, continuing with windowsize=1"); + } + } +- /* size = write(fd, dp->th_data, n - 4); */ +- size = writeit(file, &dp, n - 4, convert); +- if (size < 0) { +- nak(errno + 100, NULL); +- break; +- } +- amount += size; +- } while (size == SEGSIZE); +- abort: /* ok to ack, since user */ +- ap->th_opcode = htons((u_short) ACK); /* has seen err msg */ +- ap->th_block = htons((u_short) block); +- (void)sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr, +- SOCKLEN(&peeraddr)); +- write_behind(file, convert); /* flush last buffer */ +- fclose(file); ++ } while (r == 0 && --retries > 0); ++ if (retries <= 0) ++ timed_out(); ++ ++ send_ack(f, &server, 0); ++ ++no_options: ++ fp = fdopen(fd, "w"); ++ r = receiver(f, &server, blocksize, windowsize, TIMEOUT, fp, &amount, error); ++ if (r < 0) { ++ fprintf(stderr, "%s\n", error); ++ exit(1); ++ } ++ + stopclock(); + if (amount > 0) + printstats("Received", amount); +-} +- +-static int +-makerequest(int request, const char *name, +- struct tftphdr *tp, const char *mode) +-{ +- char *cp; +- size_t len; +- +- tp->th_opcode = htons((u_short) request); +- cp = (char *)&(tp->th_stuff); +- len = strlen(name) + 1; +- memcpy(cp, name, len); +- cp += len; +- len = strlen(mode) + 1; +- memcpy(cp, mode, len); +- cp += len; +- return (cp - (char *)tp); +-} +- +-static const char *const errmsgs[] = { +- "Undefined error code", /* 0 - EUNDEF */ +- "File not found", /* 1 - ENOTFOUND */ +- "Access denied", /* 2 - EACCESS */ +- "Disk full or allocation exceeded", /* 3 - ENOSPACE */ +- "Illegal TFTP operation", /* 4 - EBADOP */ +- "Unknown transfer ID", /* 5 - EBADID */ +- "File already exists", /* 6 - EEXISTS */ +- "No such user", /* 7 - ENOUSER */ +- "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */ +-}; +- +-#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *)) +- +-/* +- * Send a nak packet (error message). +- * Error code passed in is one of the +- * standard TFTP codes, or a UNIX errno +- * offset by 100. +- */ +-static void nak(int error, const char *msg) +-{ +- struct tftphdr *tp; +- int length; +- +- tp = (struct tftphdr *)ackbuf; +- tp->th_opcode = htons((u_short) ERROR); +- tp->th_code = htons((u_short) error); +- +- if (error >= 100) { +- /* This is a Unix errno+100 */ +- if (!msg) +- msg = strerror(error - 100); +- error = EUNDEF; +- } else { +- if ((unsigned)error >= ERR_CNT) +- error = EUNDEF; +- +- if (!msg) +- msg = errmsgs[error]; +- } +- +- tp->th_code = htons((u_short) error); +- +- length = strlen(msg) + 1; +- memcpy(tp->th_msg, msg, length); +- length += 4; /* Add space for header */ +- +- if (trace) +- tpacket("sent", tp, length); +- if (sendto(f, ackbuf, length, 0, &peeraddr.sa, +- SOCKLEN(&peeraddr)) != length) +- perror("nak"); +-} +- +-static void tpacket(const char *s, struct tftphdr *tp, int n) +-{ +- static const char *opcodes[] = +- { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" }; +- char *cp, *file; +- u_short op = ntohs((u_short) tp->th_opcode); +- +- if (op < RRQ || op > ERROR) +- printf("%s opcode=%x ", s, op); +- else +- printf("%s %s ", s, opcodes[op]); +- switch (op) { +- +- case RRQ: +- case WRQ: +- n -= 2; +- file = cp = (char *)&(tp->th_stuff); +- cp = strchr(cp, '\0'); +- printf("\n", file, cp + 1); +- break; +- +- case DATA: +- printf("\n", ntohs(tp->th_block), n - 4); +- break; +- +- case ACK: +- printf("\n", ntohs(tp->th_block)); +- break; +- +- case ERROR: +- printf("\n", ntohs(tp->th_code), tp->th_msg); +- break; +- } ++ fclose(fp); + } + + struct timeval tstart; +@@ -404,23 +356,9 @@ static void printstats(const char *direction, unsigned long amount) + (tstart.tv_sec + (tstart.tv_usec / 1000000.0)); + if (verbose) { + printf("%s %lu bytes in %.1f seconds", direction, amount, delta); ++ /* TODO: Change the statistics in a separate patch (bits???)! */ + printf(" [%.0f bit/s]", (amount * 8.) / delta); + putchar('\n'); + } + } + +-static void timer(int sig) +-{ +- int save_errno = errno; +- +- (void)sig; /* Shut up unused warning */ +- +- timeout += rexmtval; +- if (timeout >= maxtimeout) { +- printf("Transfer timed out.\n"); +- errno = save_errno; +- siglongjmp(toplevel, -1); +- } +- errno = save_errno; +- siglongjmp(timeoutbuf, 1); +-} +diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in +index 321b8c6..46384d6 100644 +--- a/tftpd/tftpd.8.in ++++ b/tftpd/tftpd.8.in +@@ -227,6 +227,11 @@ Set the time before the server retransmits a packet, in microseconds. + \fBrollover\fP (nonstandard) + Set the block number to resume at after a block number rollover. The + default and recommended value is zero. ++.TP ++\fBwindowsize\fP (RFC 7440) ++Set the windowsize to a number of blocks that should be sent before ++expecting an ack. The default is 1, which means the same functionality ++as if windowsize wasn't used. Maximum is 64. + .PP + The + .B \-\-refuse +@@ -408,6 +413,9 @@ RFC 2348, + .br + RFC 2349, + .IR "TFTP Timeout Interval and Transfer Size Options" . ++.br ++RFC 7440, ++.IR "TFTP Windowsize Option" . + .SH "AUTHOR" + This version of + .B tftpd +diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c +index b7a5a95..98adbc1 100644 +--- a/tftpd/tftpd.c ++++ b/tftpd/tftpd.c +@@ -48,6 +48,8 @@ + #include + #include + #include ++#include ++#include + + #include "common/tftpsubs.h" + #include "recvfrom.h" +@@ -72,23 +74,16 @@ static int ai_fam = AF_UNSPEC; + static int ai_fam = AF_INET; + #endif + +-#define TIMEOUT 1000000 /* Default timeout (us) */ +-#define TRIES 6 /* Number of attempts to send each packet */ +-#define TIMEOUT_LIMIT ((1 << TRIES)-1) +- + const char *__progname; + static int peer; +-static unsigned long timeout = TIMEOUT; /* Current timeout value */ +-static unsigned long rexmtval = TIMEOUT; /* Basic timeout value */ +-static unsigned long maxtimeout = TIMEOUT_LIMIT * TIMEOUT; +-static int timeout_quit = 0; +-static sigjmp_buf timeoutbuf; + static uint16_t rollover_val = 0; ++static int windowsize = 1; + + #define PKTSIZE MAX_SEGSIZE+4 + static char buf[PKTSIZE]; +-static char ackbuf[PKTSIZE]; ++static char pktbuf[PKTSIZE]; + static unsigned int max_blksize = MAX_SEGSIZE; ++#define MAX_WINDOWSIZE 64 + + static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p; + +@@ -113,8 +108,7 @@ static struct rule *rewrite_rules = NULL; + #endif + + int tftp(struct tftphdr *, int); +-static void nak(int, const char *); +-static void timer(int); ++static void nak(int error, const char *msg); + static void do_opt(const char *, const char *, char **); + + static int set_blksize(uintmax_t *); +@@ -123,6 +117,9 @@ static int set_tsize(uintmax_t *); + static int set_timeout(uintmax_t *); + static int set_utimeout(uintmax_t *); + static int set_rollover(uintmax_t *); ++static int set_windowsize(uintmax_t *); ++ ++int g_timeout = 1000; /* ms */ + + struct options { + const char *o_opt; +@@ -134,6 +131,7 @@ struct options { + {"timeout", set_timeout}, + {"utimeout", set_utimeout}, + {"rollover", set_rollover}, ++ {"windowsize", set_windowsize}, + {NULL, NULL} + }; + +@@ -152,16 +150,6 @@ static void handle_exit(int sig) + exit_signal = sig; + } + +-/* Handle timeout signal or timeout event */ +-void timer(int sig) +-{ +- (void)sig; /* Suppress unused warning */ +- timeout <<= 1; +- if (timeout >= maxtimeout || timeout_quit) +- exit(0); +- siglongjmp(timeoutbuf, 1); +-} +- + #ifdef WITH_REGEX + static struct rule *read_remap_rules(const char *file) + { +@@ -229,64 +217,6 @@ static void pmtu_discovery_off(int fd) + #endif + } + +-/* +- * Receive packet with synchronous timeout; timeout is adjusted +- * to account for time spent waiting. +- */ +-static int recv_time(int s, void *rbuf, int len, unsigned int flags, +- unsigned long *timeout_us_p) +-{ +- fd_set fdset; +- struct timeval tmv, t0, t1; +- int rv, err; +- unsigned long timeout_us = *timeout_us_p; +- unsigned long timeout_left, dt; +- +- gettimeofday(&t0, NULL); +- timeout_left = timeout_us; +- +- for (;;) { +- FD_ZERO(&fdset); +- FD_SET(s, &fdset); +- +- do { +- tmv.tv_sec = timeout_left / 1000000; +- tmv.tv_usec = timeout_left % 1000000; +- +- rv = select(s + 1, &fdset, NULL, NULL, &tmv); +- err = errno; +- +- gettimeofday(&t1, NULL); +- +- dt = (t1.tv_sec - t0.tv_sec) * 1000000 + +- (t1.tv_usec - t0.tv_usec); +- *timeout_us_p = timeout_left = +- (dt >= timeout_us) ? 1 : (timeout_us - dt); +- } while (rv == -1 && err == EINTR); +- +- if (rv == 0) { +- timer(0); /* Should not return */ +- return -1; +- } +- +- set_socket_nonblock(s, 1); +- rv = recv(s, rbuf, len, flags); +- err = errno; +- set_socket_nonblock(s, 0); +- +- if (rv < 0) { +- if (E_WOULD_BLOCK(err) || err == EINTR) { +- continue; /* Once again, with feeling... */ +- } else { +- errno = err; +- return rv; +- } +- } else { +- return rv; +- } +- } +-} +- + static int split_port(char **ap, char **pp) + { + char *a, *p; +@@ -325,7 +255,7 @@ static int split_port(char **ap, char **pp) + enum long_only_options { + OPT_VERBOSITY = 256, + }; +- ++ + static struct option long_options[] = { + { "ipv4", 0, NULL, '4' }, + { "ipv6", 0, NULL, '6' }, +@@ -389,7 +319,7 @@ int main(int argc, char **argv) + char envtz[10]; + my_time = time(NULL); + p_tm = localtime(&my_time); +- snprintf(envtz, sizeof(envtz) - 1, "UTC%+d", (p_tm->tm_gmtoff * -1)/3600); ++ snprintf(envtz, sizeof(envtz) - 1, "UTC%+ld", (p_tm->tm_gmtoff * -1)/3600); + setenv("TZ", envtz, 0); + + /* basename() is way too much of a pain from a portability standpoint */ +@@ -455,8 +385,7 @@ int main(int argc, char **argv) + syslog(LOG_ERR, "Bad timeout value: %s", optarg); + exit(EX_USAGE); + } +- rexmtval = timeout = tov; +- maxtimeout = rexmtval * TIMEOUT_LIMIT; ++ g_timeout = tov; + } + break; + case 'R': +@@ -1090,9 +1019,9 @@ int tftp(struct tftphdr *tp, int size) + u_short tp_opcode = ntohs(tp->th_opcode); + + char *val = NULL, *opt = NULL; +- char *ap = ackbuf + 2; ++ char *ap = pktbuf + 2; + +- ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK); ++ ((struct tftphdr *)pktbuf)->th_opcode = htons(OACK); + + origfilename = cp = (char *)&(tp->th_stuff); + argn = 0; +@@ -1176,11 +1105,11 @@ int tftp(struct tftphdr *tp, int size) + exit(0); + } + +- if (ap != (ackbuf + 2)) { ++ if (ap != (pktbuf + 2)) { + if (tp_opcode == WRQ) +- (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); ++ (*pf->f_recv) (pf, (struct tftphdr *)pktbuf, ap - pktbuf); + else +- (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf, origfilename); ++ (*pf->f_send) (pf, (struct tftphdr *)pktbuf, ap - pktbuf, origfilename); + } else { + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, NULL, 0); +@@ -1248,7 +1177,7 @@ static int set_blksize2(uintmax_t *vp) + static int set_rollover(uintmax_t *vp) + { + uintmax_t ro = *vp; +- ++ + if (ro > 65535) + return 0; + +@@ -1287,8 +1216,7 @@ static int set_timeout(uintmax_t *vp) + if (to < 1 || to > 255) + return 0; + +- rexmtval = timeout = to * 1000000UL; +- maxtimeout = rexmtval * TIMEOUT_LIMIT; ++ g_timeout = to * 1000; + + return 1; + } +@@ -1301,8 +1229,20 @@ static int set_utimeout(uintmax_t *vp) + if (to < 10000UL || to > 255000000UL) + return 0; + +- rexmtval = timeout = to; +- maxtimeout = rexmtval * TIMEOUT_LIMIT; ++ g_timeout = to / 1000; ++ ++ return 1; ++} ++ ++/* ++ * Set window size (c.f. RFC7440) ++ */ ++static int set_windowsize(uintmax_t *vp) ++{ ++ if (*vp < 1 || *vp > MAX_WINDOWSIZE) ++ return 0; ++ ++ windowsize = *vp; + + return 1; + } +@@ -1343,11 +1283,11 @@ static void do_opt(const char *opt, const char *val, char **ap) + optlen = strlen(opt); + retlen = sprintf(retbuf, "%"PRIuMAX, v); + +- if (p + optlen + retlen + 2 >= ackbuf + sizeof(ackbuf)) { ++ if (p + optlen + retlen + 2 >= pktbuf + sizeof(pktbuf)) { + nak(EOPTNEG, "Insufficient space for options"); + exit(0); + } +- ++ + memcpy(p, opt, optlen+1); + p += optlen+1; + memcpy(p, retbuf, retlen+1); +@@ -1568,104 +1508,63 @@ static int validate_access(char *filename, int mode, + */ + static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen, char *filename) + { +- struct tftphdr *dp; +- struct tftphdr *ap; /* ack packet */ +- static u_short block = 1; /* Static to avoid longjmp funnies */ +- u_short ap_opcode, ap_block; +- unsigned long r_timeout; +- int size, n; +- ++ struct tftphdr *tp = (struct tftphdr *)pktbuf; ++ unsigned short tp_opcode, tp_block; ++ int retries = RETRIES; ++ int timed_out = 0; ++ int n, r = 0; ++ (void) pf; ++ ++ set_verbose(verbosity); + if (oap) { +- timeout = rexmtval; +- (void)sigsetjmp(timeoutbuf, 1); +- oack: +- r_timeout = timeout; +- if (send(peer, oap, oacklen, 0) != oacklen) { +- syslog(LOG_WARNING, "tftpd: oack: %m\n"); +- goto abort; +- } +- for (;;) { +- n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); +- if (n < 0) { +- syslog(LOG_WARNING, "tftpd: read: %m\n"); +- goto abort; +- } +- ap = (struct tftphdr *)ackbuf; +- ap_opcode = ntohs((u_short) ap->th_opcode); +- ap_block = ntohs((u_short) ap->th_block); +- +- if (ap_opcode == ERROR) { +- syslog(LOG_WARNING, +- "tftp: client does not accept options\n"); ++ do { ++ if (send(peer, oap, oacklen, 0) != oacklen) { ++ syslog(LOG_WARNING, "tftpd: oack: %m\n"); + goto abort; + } +- if (ap_opcode == ACK) { +- if (ap_block == 0) +- break; +- /* Resynchronize with the other side */ +- (void)synchnet(peer); +- goto oack; +- } +- } +- } + +- dp = r_init(); +- do { +- size = readit(file, &dp, pf->f_convert); +- if (size < 0) { +- nak(errno + 100, NULL); +- goto abort; +- } +- dp->th_opcode = htons((u_short) DATA); +- dp->th_block = htons((u_short) block); +- timeout = rexmtval; +- (void)sigsetjmp(timeoutbuf, 1); +- +- r_timeout = timeout; +- if (send(peer, dp, size + 4, 0) != size + 4) { +- syslog(LOG_WARNING, "tftpd: write: %m"); +- goto abort; +- } +- read_ahead(file, pf->f_convert); +- for (;;) { +- n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); ++ n = recv_with_timeout(peer, pktbuf, sizeof(pktbuf), g_timeout); + if (n < 0) { +- syslog(LOG_WARNING, "tftpd: read(ack): %m"); ++ syslog(LOG_WARNING, "tftpd: recv: %m"); + goto abort; ++ } else if (n == 0) { ++ if (--retries <= 0) { ++ timed_out = 1; ++ goto abort; ++ } ++ continue; + } +- ap = (struct tftphdr *)ackbuf; +- ap_opcode = ntohs((u_short) ap->th_opcode); +- ap_block = ntohs((u_short) ap->th_block); + +- if (ap_opcode == ERROR) +- goto abort; ++ tp_opcode = ntohs(tp->th_opcode); ++ tp_block = ntohs(tp->th_block); + +- if (ap_opcode == ACK) { +- if (ap_block == block) { +- break; +- } +- /* Re-synchronize with the other side */ +- (void)synchnet(peer); +- /* +- * RFC1129/RFC1350: We MUST NOT re-send the DATA +- * packet in response to an invalid ACK. Doing so +- * would cause the Sorcerer's Apprentice bug. +- */ ++ if (tp_opcode == ERROR) { ++ char error[ERROR_MAXLEN]; ++ ++ format_error(tp, error); ++ syslog(LOG_WARNING, "%s", error); ++ goto abort; ++ } else if (!(tp_opcode == ACK && tp_block == 0)) { ++ syslog(LOG_WARNING, "unexpected packet %s block=%u", opcode_to_str(tp_opcode), tp_block); ++ send_error(peer, NULL, "Unexpected packet"); ++ exit(1); + } ++ } while (n == 0); ++ } ++ ++ r = sender(peer, NULL, segsize, windowsize, TIMEOUT, rollover_val, file, NULL); + +- } +- if (!++block) +- block = rollover_val; +- } while (size == segsize); + tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), +- tmpbuf, INET6_ADDRSTRLEN); ++ tmpbuf, INET6_ADDRSTRLEN); + if (!tmp_p) { + tmp_p = tmpbuf; + strcpy(tmpbuf, "???"); + } +- syslog(LOG_NOTICE, "Client %s finished %s",tmp_p,filename); +- abort: +- (void)fclose(file); ++ syslog(LOG_NOTICE, "Client %s finished %s", tmp_p, filename); ++abort: ++ if (timed_out || r == E_TIMED_OUT) ++ syslog(LOG_NOTICE, "Client %s timed out", tmp_p); ++ fclose(file); + } + + /* +@@ -1673,90 +1572,44 @@ static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oac + */ + static void tftp_recvfile(const struct formats *pf, struct tftphdr *oap, int oacklen) + { +- struct tftphdr *dp; +- int n, size; +- /* These are "static" to avoid longjmp funnies */ +- static struct tftphdr *ap; /* ack buffer */ +- static u_short block = 0; +- static int acksize; +- u_short dp_opcode, dp_block; +- unsigned long r_timeout; +- +- dp = w_init(); +- do { +- timeout = rexmtval; +- +- if (!block && oap) { +- ap = (struct tftphdr *)ackbuf; +- acksize = oacklen; +- } else { +- ap = (struct tftphdr *)ackbuf; +- ap->th_opcode = htons((u_short) ACK); +- ap->th_block = htons((u_short) block); +- acksize = 4; +- /* If we're sending a regular ACK, that means we have successfully +- * sent the OACK. Clear oap so that we won't try to send another +- * OACK when the block number wraps back to 0. */ +- oap = NULL; +- } +- if (!++block) +- block = rollover_val; +- (void)sigsetjmp(timeoutbuf, 1); +- send_ack: +- r_timeout = timeout; +- if (send(peer, ackbuf, acksize, 0) != acksize) { +- syslog(LOG_WARNING, "tftpd: write(ack): %m"); +- goto abort; +- } +- write_behind(file, pf->f_convert); +- for (;;) { +- n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout); +- if (n < 0) { /* really? */ +- syslog(LOG_WARNING, "tftpd: read: %m"); ++ int retries = RETRIES; ++ int timed_out = 0; ++ int r; ++ (void) pf; ++ ++ set_verbose(verbosity); ++ if (oap) { ++ do { ++ if (send(peer, oap, oacklen, 0) != oacklen) { ++ syslog(LOG_WARNING, "tftpd: oack: %m\n"); + goto abort; + } +- dp_opcode = ntohs((u_short) dp->th_opcode); +- dp_block = ntohs((u_short) dp->th_block); +- if (dp_opcode == ERROR) +- goto abort; +- if (dp_opcode == DATA) { +- if (dp_block == block) { +- break; /* normal */ ++ r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK); ++ if (r == 0) { ++ if (--retries <= 0) { ++ timed_out = 1; ++ goto abort; + } +- /* Re-synchronize with the other side */ +- (void)synchnet(peer); +- if (dp_block == (block - 1)) +- goto send_ack; /* rexmit */ + } +- } +- /* size = write(file, dp->th_data, n - 4); */ +- size = writeit(file, &dp, n - 4, pf->f_convert); +- if (size != (n - 4)) { /* ahem */ +- if (size < 0) +- nak(errno + 100, NULL); +- else +- nak(ENOSPACE, NULL); +- goto abort; +- } +- } while (size == segsize); +- write_behind(file, pf->f_convert); +- (void)fclose(file); /* close data file */ +- +- ap->th_opcode = htons((u_short) ACK); /* send the "final" ack */ +- ap->th_block = htons((u_short) (block)); +- (void)send(peer, ackbuf, 4, 0); +- +- timeout_quit = 1; /* just quit on timeout */ +- n = recv_time(peer, buf, sizeof(buf), 0, &timeout); /* normally times out and quits */ +- timeout_quit = 0; +- +- if (n >= 4 && /* if read some data */ +- dp_opcode == DATA && /* and got a data block */ +- block == dp_block) { /* then my last ack was lost */ +- (void)send(peer, ackbuf, 4, 0); /* resend final ack */ ++ } while (r == 0); ++ } else { ++ do { ++ send_ack(peer, NULL, 0); ++ r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK); ++ if (r == 0) { ++ if (--retries <= 0) { ++ timed_out = 1; ++ goto abort; ++ } ++ } ++ } while (r == 0); + } +- abort: +- return; ++ ++ r = receiver(peer, NULL, segsize, windowsize, TIMEOUT, file, NULL, NULL); ++abort: ++ if (timed_out || r == E_TIMED_OUT) ++ syslog(LOG_NOTICE, "Client %s timed out", tmp_p); ++ fclose(file); + } + + static const char *const errmsgs[] = { diff --git a/SPECS/tftp.spec b/SPECS/tftp.spec index 6d1141f..b0809e6 100644 --- a/SPECS/tftp.spec +++ b/SPECS/tftp.spec @@ -4,7 +4,7 @@ Summary: The client for the Trivial File Transfer Protocol (TFTP) Name: tftp Version: 5.2 -Release: 13%{?dist} +Release: 22%{?dist} License: BSD Group: Applications/Internet URL: http://www.kernel.org/pub/software/network/tftp/ @@ -22,6 +22,8 @@ Patch7: tftp-hpa-0.49-stats.patch Patch8: tftp-hpa-5.2-pktinfo.patch Patch9: tftp-doc.patch Patch10: tftp-enhanced-logging.patch +Patch11: tftp-rfc7440-windowsize.patch +Patch12: tftp-rewrite-macro.patch BuildRequires: tcp_wrappers-devel readline-devel autoconf systemd-units @@ -58,6 +60,8 @@ enabled unless it is expressly needed. %patch8 -p1 -b .pktinfo %patch9 -p1 -b .doc %patch10 -p1 -b .logging +%patch11 -p1 -b .windowsize +%patch12 -p1 -b .rewrite-macro %build autoreconf @@ -108,6 +112,33 @@ rm -rf ${RPM_BUILD_ROOT} %{_unitdir}/* %changelog +* Wed Feb 7 2018 Jan Synáček - 5.2-22 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Wed Feb 7 2018 Jan Synáček - 5.2-21 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Tue Jan 23 2018 Jan Synáček - 5.2-20 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Tue Jan 9 2018 Jan Synáček - 5.2-19 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Tue Nov 28 2017 Jan Synáček - 5.2-18 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Thu Nov 16 2017 Jan Synáček - 5.2.17 +- re: implement RFC7440 TFTP Windowsize Option (#1328827) + +* Mon Sep 11 2017 Jan Synáček - 5.2-16 +- inconsistent --mapfile / --map-file option spelling in manual (#1490139) + +* Tue Sep 5 2017 Jan Synáček - 5.2-15 +- memory corruption in tftpd when using filename remapping (#1485943) + +* Mon Aug 14 2017 Jan Synáček - 5.2-14 +- implement RFC7440 TFTP Windowsize Option (#1328827) + * Wed Mar 2 2016 Jan Synáček - 5.2-13 - enhance in.tftpd logging capabilities (#1311092)