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[] = {