diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0df399e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/tftp-hpa-5.2.tar.bz2 diff --git a/.tftp.metadata b/.tftp.metadata new file mode 100644 index 0000000..c2ac720 --- /dev/null +++ b/.tftp.metadata @@ -0,0 +1 @@ +e7879f0bdab1fc6f226db6f6f848d58f50548de4 SOURCES/tftp-hpa-5.2.tar.bz2 diff --git a/README.md b/README.md deleted file mode 100644 index 0e7897f..0000000 --- a/README.md +++ /dev/null @@ -1,5 +0,0 @@ -The master branch has no content - -Look at the c7 branch if you are working with CentOS-7, or the c4/c5/c6 branch for CentOS-4, 5 or 6 - -If you find this file in a distro specific branch, it means that no content has been checked in yet diff --git a/SOURCES/tftp-0.40-remap.patch b/SOURCES/tftp-0.40-remap.patch new file mode 100644 index 0000000..755a307 --- /dev/null +++ b/SOURCES/tftp-0.40-remap.patch @@ -0,0 +1,19 @@ +diff -up tftp-hpa-0.49/tftpd/remap.c.zero tftp-hpa-0.49/tftpd/remap.c +--- tftp-hpa-0.49/tftpd/remap.c.zero 2008-10-20 18:08:31.000000000 -0400 ++++ tftp-hpa-0.49/tftpd/remap.c 2008-11-25 11:41:09.000000000 -0500 +@@ -286,6 +286,7 @@ struct rule *parserulefile(FILE * f) + int lineno = 0; + int err = 0; + ++ memset(this_rule, '\0', sizeof(struct rule)); + while (lineno++, fgets(line, MAXLINE, f)) { + rv = parseline(line, this_rule, lineno); + if (rv < 0) +@@ -294,6 +295,7 @@ struct rule *parserulefile(FILE * f) + *last_rule = this_rule; + last_rule = &this_rule->next; + this_rule = tfmalloc(sizeof(struct rule)); ++ memset(this_rule, '\0', sizeof(struct rule)); + } + } + diff --git a/SOURCES/tftp-0.42-tftpboot.patch b/SOURCES/tftp-0.42-tftpboot.patch new file mode 100644 index 0000000..a0de58d --- /dev/null +++ b/SOURCES/tftp-0.42-tftpboot.patch @@ -0,0 +1,54 @@ +diff -up tftp-hpa-0.48/tftp-xinetd.tftpboot tftp-hpa-0.48/tftp-xinetd +--- tftp-hpa-0.48/tftp-xinetd.tftpboot 2007-01-31 00:51:05.000000000 +0100 ++++ tftp-hpa-0.48/tftp-xinetd 2008-05-20 12:05:53.000000000 +0200 +@@ -10,7 +10,7 @@ service tftp + wait = yes + user = root + server = /usr/sbin/in.tftpd +- server_args = -s /tftpboot ++ server_args = -s /var/lib/tftpboot + disable = yes + per_source = 11 + cps = 100 2 +diff -up tftp-hpa-0.48/README.security.tftpboot tftp-hpa-0.48/README.security +--- tftp-hpa-0.48/README.security.tftpboot 2008-05-29 17:36:32.000000000 +0200 ++++ tftp-hpa-0.48/README.security 2008-05-29 17:37:21.000000000 +0200 +@@ -17,10 +17,10 @@ probably the following: + + 1. Create a separate "tftpd" user and group only used for tftpd; + 2. Have all your boot files in a single directory tree (usually called +- /tftpboot). +-3. Specify "-p -u tftpd -s /tftpboot" on the tftpd command line; if ++ /var/lib/tftpboot). ++3. Specify "-p -u tftpd -s /var/lib/tftpboot" on the tftpd command line; if + you want clients to be able to create files use +- "-p -c -U 002 -u tftpd -s /tftpboot" (replace 002 with whatever ++ "-p -c -U 002 -u tftpd -s /var/lib/tftpboot" (replace 002 with whatever + umask is appropriate for your setup.) + + ======================================= +@@ -40,12 +40,12 @@ directly. Thus, if your /etc/inetd.conf + line): + + tftp dgram udp wait root /usr/sbin/tcpd +-/usr/sbin/in.tftpd -s /tftpboot -r blksize ++/usr/sbin/in.tftpd -s /var/lib/tftpboot -r blksize + + ... it's better to change to ... + + tftp dgram udp wait root /usr/sbin/in.tftpd +-in.tftpd -s /tftpboot -r blksize ++in.tftpd -s /var/lib/tftpboot -r blksize + + You should make sure that you are using "wait" option in tftpd; you + also need to have tftpd spawned as root in order for chroot (-s) to +diff -up tftp-hpa-0.48/tftpd/sample.rules.tftpboot tftp-hpa-0.48/tftpd/sample.rules +--- tftp-hpa-0.48/tftpd/sample.rules.tftpboot 2008-05-29 17:38:46.000000000 +0200 ++++ tftp-hpa-0.48/tftpd/sample.rules 2008-05-29 17:38:05.000000000 +0200 +@@ -30,5 +30,5 @@ rg \\ / # Convert backslashes to slash + rg \# @ # Convert hash marks to @ signs + rg /../ /..no../ # Convert /../ to /..no../ + e ^ok/ # These are always ok +-r ^[^/] /tftpboot/\0 # Convert non-absolute files ++r ^[^/] /var/lib/tftpboot/\0 # Convert non-absolute files + a \.pvt$ # Reject requests for private files diff --git a/SOURCES/tftp-0.49-chk_retcodes.patch b/SOURCES/tftp-0.49-chk_retcodes.patch new file mode 100644 index 0000000..6d63571 --- /dev/null +++ b/SOURCES/tftp-0.49-chk_retcodes.patch @@ -0,0 +1,15 @@ +diff -up tftp-hpa-0.49/tftpd/tftpd.c.chk_retcodes tftp-hpa-0.49/tftpd/tftpd.c +--- tftp-hpa-0.49/tftpd/tftpd.c.chk_retcodes 2009-01-15 15:28:50.000000000 +0100 ++++ tftp-hpa-0.49/tftpd/tftpd.c 2009-01-15 15:31:36.000000000 +0100 +@@ -932,7 +932,10 @@ int main(int argc, char **argv) + exit(EX_OSERR); + } + #ifdef __CYGWIN__ +- chdir("/"); /* Cygwin chroot() bug workaround */ ++ if (chdir("/") < 0) { /* Cygwin chroot() bug workaround */ ++ syslog(LOG_ERR, "chroot: %m"); ++ exit(EX_OSERR); ++ } + #endif + } + #ifdef HAVE_SETREGID diff --git a/SOURCES/tftp-0.49-cmd_arg.patch b/SOURCES/tftp-0.49-cmd_arg.patch new file mode 100644 index 0000000..2b9023a --- /dev/null +++ b/SOURCES/tftp-0.49-cmd_arg.patch @@ -0,0 +1,159 @@ +diff -up tftp-hpa-0.49/config.h.cmd_arg tftp-hpa-0.49/config.h +--- tftp-hpa-0.49/config.h.cmd_arg 2010-04-19 15:29:10.567331454 +0200 ++++ tftp-hpa-0.49/config.h 2010-04-20 07:33:03.133232772 +0200 +@@ -291,6 +291,7 @@ typedef int socklen_t; + /* Prototypes for libxtra functions */ + + void *xmalloc(size_t); ++void *xrealloc(void *, size_t); + char *xstrdup(const char *); + + #ifndef HAVE_BSD_SIGNAL +diff -up tftp-hpa-0.49/configure.in.cmd_arg tftp-hpa-0.49/configure.in +--- tftp-hpa-0.49/configure.in.cmd_arg 2008-10-21 00:08:31.000000000 +0200 ++++ tftp-hpa-0.49/configure.in 2010-04-19 11:05:12.387340698 +0200 +@@ -152,6 +152,7 @@ OBJROOT=`pwd` + + XTRA=false + PA_SEARCH_LIBS_AND_ADD(xmalloc, iberty) ++PA_SEARCH_LIBS_AND_ADD(xrealloc, iberty) + PA_SEARCH_LIBS_AND_ADD(xstrdup, iberty) + PA_SEARCH_LIBS_AND_ADD(bsd_signal, bsd, bsdsignal) + PA_SEARCH_LIBS_AND_ADD(getopt_long, getopt, getopt_long) +diff -up tftp-hpa-0.49/lib/xrealloc.c.cmd_arg tftp-hpa-0.49/lib/xrealloc.c +--- tftp-hpa-0.49/lib/xrealloc.c.cmd_arg 2010-04-19 11:05:12.387340698 +0200 ++++ tftp-hpa-0.49/lib/xrealloc.c 2010-04-19 11:05:12.387340698 +0200 +@@ -0,0 +1,20 @@ ++/* ++ * xrealloc.c ++ * ++ * Simple error-checking version of realloc() ++ * ++ */ ++ ++#include "config.h" ++ ++void *xrealloc(void *ptr, size_t size) ++{ ++ void *p = realloc(ptr, size); ++ ++ if (!p) { ++ fprintf(stderr, "Out of memory!\n"); ++ exit(128); ++ } ++ ++ return p; ++} +diff -up tftp-hpa-0.49/tftp/main.c.cmd_arg tftp-hpa-0.49/tftp/main.c +--- tftp-hpa-0.49/tftp/main.c.cmd_arg 2008-10-21 00:08:31.000000000 +0200 ++++ tftp-hpa-0.49/tftp/main.c 2010-04-19 11:05:12.389329337 +0200 +@@ -89,11 +89,14 @@ int connected; + const struct modes *mode; + #ifdef WITH_READLINE + char *line = NULL; ++char *remote_pth = NULL; + #else + char line[LBUFLEN]; ++char remote_pth[LBUFLEN]; + #endif + int margc; +-char *margv[20]; ++char **margv; ++int sizeof_margv=0; + const char *prompt = "tftp> "; + sigjmp_buf toplevel; + void intr(int); +@@ -379,6 +382,10 @@ static void getmoreargs(const char *part + free(line); + line = NULL; + } ++ if (remote_pth) { ++ free(remote_pth); ++ remote_pth = NULL; ++ } + line = xmalloc(len + elen + 1); + strcpy(line, partial); + strcpy(line + len, eline); +@@ -535,6 +542,7 @@ void put(int argc, char *argv[]) + int fd; + int n, err; + char *cp, *targ; ++ long dirlen, namelen, lastlen=0; + + if (argc < 2) { + getmoreargs("send ", "(file) "); +@@ -588,9 +596,22 @@ void put(int argc, char *argv[]) + } + /* this assumes the target is a directory */ + /* on a remote unix system. hmmmm. */ +- cp = strchr(targ, '\0'); +- *cp++ = '/'; ++ dirlen = strlen(targ)+1; ++#ifdef WITH_READLINE ++ remote_pth = xmalloc(dirlen+1); ++#endif ++ strcpy(remote_pth, targ); ++ remote_pth[dirlen-1] = '/'; ++ cp = remote_pth + dirlen; + for (n = 1; n < argc - 1; n++) { ++#ifdef WITH_READLINE ++ namelen = strlen(tail(argv[n])) + 1; ++ if (namelen > lastlen) { ++ remote_pth = xrealloc(remote_pth, dirlen + namelen + 1); ++ cp = remote_pth + dirlen; ++ lastlen = namelen; ++ } ++#endif + strcpy(cp, tail(argv[n])); + fd = open(argv[n], O_RDONLY | mode->m_openflags); + if (fd < 0) { +@@ -600,9 +621,9 @@ void put(int argc, char *argv[]) + } + if (verbose) + printf("putting %s to %s:%s [%s]\n", +- argv[n], hostname, targ, mode->m_mode); ++ argv[n], hostname, remote_pth, mode->m_mode); + sa_set_port(&peeraddr, port); +- tftp_sendfile(fd, targ, mode->m_mode); ++ tftp_sendfile(fd, remote_pth, mode->m_mode); + } + } + +@@ -801,6 +822,10 @@ static void command(void) + free(line); + line = NULL; + } ++ if (remote_pth) { ++ free(remote_pth); ++ remote_pth = NULL; ++ } + line = readline(prompt); + if (!line) + exit(0); /* EOF */ +@@ -872,7 +897,13 @@ struct cmd *getcmd(char *name) + static void makeargv(void) + { + char *cp; +- char **argp = margv; ++ char **argp; ++ ++ if (!sizeof_margv) { ++ sizeof_margv = 20; ++ margv = xmalloc(sizeof_margv * sizeof(char *)); ++ } ++ argp = margv; + + margc = 0; + for (cp = line; *cp;) { +@@ -882,6 +913,11 @@ static void makeargv(void) + break; + *argp++ = cp; + margc += 1; ++ if (margc == sizeof_margv) { ++ sizeof_margv += 20; ++ margv = xrealloc(margv, sizeof_margv * sizeof(char *)); ++ argp = margv + margc; ++ } + while (*cp != '\0' && !isspace(*cp)) + cp++; + if (*cp == '\0') diff --git a/SOURCES/tftp-doc.patch b/SOURCES/tftp-doc.patch new file mode 100644 index 0000000..f1cb511 --- /dev/null +++ b/SOURCES/tftp-doc.patch @@ -0,0 +1,45 @@ +--- tftp-hpa-5.2/tftp/main.c 2013-04-19 09:34:09.737410319 +0200 ++++ tftp-hpa-5.2/tftp/main.c 2013-04-19 09:42:53.559946374 +0200 +@@ -195,9 +195,11 @@ + { + fprintf(stderr, + #ifdef HAVE_IPV6 +- "Usage: %s [-4][-6][-v][-l][-m mode] [host [port]] [-c command]\n", ++ "Usage: %s [-4][-6][-v][-V][-l][-m mode] [-R port:port] " ++ "[host [port]] [-c command]\n", + #else +- "Usage: %s [-v][-l][-m mode] [host [port]] [-c command]\n", ++ "Usage: %s [-v][-V][-l][-m mode] [-R port:port] " ++ "[host [port]] [-c command]\n", + #endif + program); + exit(errcode); +--- tftp-hpa-5.2/tftpd/tftpd.8.in 2012-11-20 09:43:46.000000000 +0100 ++++ tftp-hpa-5.2/tftpd/tftpd.8.in 2013-04-19 09:44:37.399057279 +0200 +@@ -155,7 +155,7 @@ + .B utimeout + option is negotiated. The default is 1000000 (1 second.) + .TP +-\fB\-\-mapfile\fP \fIremap-file\fP, \fB\-m\fP \fIremap-file\fP ++\fB\-\-map\-file\fP \fIremap-file\fP, \fB\-m\fP \fIremap-file\fP + 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-enhanced-logging.patch b/SOURCES/tftp-enhanced-logging.patch new file mode 100644 index 0000000..dce9ef9 --- /dev/null +++ b/SOURCES/tftp-enhanced-logging.patch @@ -0,0 +1,84 @@ +--- a/tftpd/tftpd.c 2016-03-02 11:32:30.710775130 +0100 ++++ b/tftpd/tftpd.c 2016-03-02 11:36:24.086541019 +0100 +@@ -1056,14 +1056,14 @@ int main(int argc, char **argv) + + static char *rewrite_access(char *, int, const char **); + static int validate_access(char *, int, const struct formats *, const char **); +-static void tftp_sendfile(const struct formats *, struct tftphdr *, int); ++static void tftp_sendfile(const struct formats *, struct tftphdr *, int, char *); + static void tftp_recvfile(const struct formats *, struct tftphdr *, int); + + struct formats { + const char *f_mode; + char *(*f_rewrite) (char *, int, const char **); + int (*f_validate) (char *, int, const struct formats *, const char **); +- void (*f_send) (const struct formats *, struct tftphdr *, int); ++ void (*f_send) (const struct formats *, struct tftphdr *, int, char *); + void (*f_recv) (const struct formats *, struct tftphdr *, int); + int f_convert; + }; +@@ -1129,6 +1129,9 @@ int tftp(struct tftphdr *tp, int size) + nak(EACCESS, errmsgptr); /* File denied by mapping rule */ + exit(0); + } ++ ecode = ++ (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); ++ + if (verbosity >= 1) { + tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), + tmpbuf, INET6_ADDRSTRLEN); +@@ -1147,9 +1150,14 @@ int tftp(struct tftphdr *tp, int size) + tp_opcode == WRQ ? "WRQ" : "RRQ", + tmp_p, origfilename, + filename); ++ ++ if (ecode == 1) { ++ syslog(LOG_NOTICE, "Client %s File not found %s\n", ++ tmp_p,filename); ++ } ++ + } +- ecode = +- (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); ++ + if (ecode) { + nak(ecode, errmsgptr); + exit(0); +@@ -1172,12 +1180,12 @@ int tftp(struct tftphdr *tp, int size) + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); + else +- (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); ++ (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf, origfilename); + } else { + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, NULL, 0); + else +- (*pf->f_send) (pf, NULL, 0); ++ (*pf->f_send) (pf, NULL, 0, origfilename); + } + exit(0); /* Request completed */ + } +@@ -1557,7 +1565,7 @@ static int validate_access(char *filenam + /* + * Send the requested file. + */ +-static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen) ++static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen, char *filename) + { + struct tftphdr *dp; + struct tftphdr *ap; /* ack packet */ +@@ -1648,6 +1656,13 @@ static void tftp_sendfile(const struct f + if (!++block) + block = rollover_val; + } while (size == segsize); ++ tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), ++ 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); + } diff --git a/SOURCES/tftp-hpa-0.39-tzfix.patch b/SOURCES/tftp-hpa-0.39-tzfix.patch new file mode 100644 index 0000000..ded02ef --- /dev/null +++ b/SOURCES/tftp-hpa-0.39-tzfix.patch @@ -0,0 +1,18 @@ +diff -up tftp-hpa-0.49/tftpd/tftpd.c.tzfix tftp-hpa-0.49/tftpd/tftpd.c +--- tftp-hpa-0.49/tftpd/tftpd.c.tzfix 2008-10-20 18:08:31.000000000 -0400 ++++ tftp-hpa-0.49/tftpd/tftpd.c 2008-11-25 11:45:27.000000000 -0500 +@@ -350,6 +350,14 @@ int main(int argc, char **argv) + const char *pidfile = NULL; + u_short tp_opcode; + ++ time_t my_time = 0; ++ struct tm* p_tm; ++ 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); ++ setenv("TZ", envtz, 0); ++ + /* basename() is way too much of a pain from a portability standpoint */ + + p = strrchr(argv[0], '/'); diff --git a/SOURCES/tftp-hpa-0.49-fortify-strcpy-crash.patch b/SOURCES/tftp-hpa-0.49-fortify-strcpy-crash.patch new file mode 100644 index 0000000..e9b70d4 --- /dev/null +++ b/SOURCES/tftp-hpa-0.49-fortify-strcpy-crash.patch @@ -0,0 +1,26 @@ +diff -urN tftp-hpa-0.49.orig/tftp/tftp.c tftp-hpa-0.49/tftp/tftp.c +--- tftp-hpa-0.49.orig/tftp/tftp.c 2008-10-20 18:08:31.000000000 -0400 ++++ tftp-hpa-0.49/tftp/tftp.c 2009-08-05 09:47:18.072585848 -0400 +@@ -279,15 +279,16 @@ + struct tftphdr *tp, const char *mode) + { + char *cp; ++ size_t len; + + tp->th_opcode = htons((u_short) request); + cp = (char *)&(tp->th_stuff); +- strcpy(cp, name); +- cp += strlen(name); +- *cp++ = '\0'; +- strcpy(cp, mode); +- cp += strlen(mode); +- *cp++ = '\0'; ++ len = strlen(name) + 1; ++ memcpy(cp, name, len); ++ cp += len; ++ len = strlen(mode) + 1; ++ memcpy(cp, mode, len); ++ cp += len; + return (cp - (char *)tp); + } + diff --git a/SOURCES/tftp-hpa-0.49-stats.patch b/SOURCES/tftp-hpa-0.49-stats.patch new file mode 100644 index 0000000..b6c9d05 --- /dev/null +++ b/SOURCES/tftp-hpa-0.49-stats.patch @@ -0,0 +1,14 @@ +diff -up tftp-hpa-0.49/tftp/tftp.c.stats tftp-hpa-0.49/tftp/tftp.c +--- tftp-hpa-0.49/tftp/tftp.c.stats 2011-01-03 15:38:34.217918067 +0100 ++++ tftp-hpa-0.49/tftp/tftp.c 2011-01-03 15:38:37.498917014 +0100 +@@ -400,8 +400,8 @@ static void printstats(const char *direc + { + double delta; + +- delta = (tstop.tv_sec + (tstop.tv_usec / 100000.0)) - +- (tstart.tv_sec + (tstart.tv_usec / 100000.0)); ++ delta = (tstop.tv_sec + (tstop.tv_usec / 1000000.0)) - ++ (tstart.tv_sec + (tstart.tv_usec / 1000000.0)); + if (verbose) { + printf("%s %lu bytes in %.1f seconds", direction, amount, delta); + printf(" [%.0f bit/s]", (amount * 8.) / delta); diff --git a/SOURCES/tftp-hpa-5.2-pktinfo.patch b/SOURCES/tftp-hpa-5.2-pktinfo.patch new file mode 100644 index 0000000..d1fa75c --- /dev/null +++ b/SOURCES/tftp-hpa-5.2-pktinfo.patch @@ -0,0 +1,23 @@ +diff -up tftp-hpa-5.2/tftpd/recvfrom.c.test tftp-hpa-5.2/tftpd/recvfrom.c +--- tftp-hpa-5.2/tftpd/recvfrom.c.test 2011-12-11 23:13:52.000000000 +0100 ++++ tftp-hpa-5.2/tftpd/recvfrom.c 2012-01-04 10:05:17.852042256 +0100 +@@ -149,16 +149,16 @@ myrecvfrom(int s, void *buf, int len, un + + /* Try to enable getting the return address */ + #ifdef IP_RECVDSTADDR +- if (from->sa_family == AF_INET) ++ if (from->sa_family == AF_INET || !from->sa_family) + setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); + #endif + #ifdef IP_PKTINFO +- if (from->sa_family == AF_INET) ++ if (from->sa_family == AF_INET || !from->sa_family) + setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); + #endif + #ifdef HAVE_IPV6 + #ifdef IPV6_RECVPKTINFO +- if (from->sa_family == AF_INET6) ++ if (from->sa_family == AF_INET6 || !from->sa_family) + setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); + #endif + #endif 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/SOURCES/tftp.service b/SOURCES/tftp.service new file mode 100644 index 0000000..c26ad3b --- /dev/null +++ b/SOURCES/tftp.service @@ -0,0 +1,11 @@ +[Unit] +Description=Tftp Server +Requires=tftp.socket +Documentation=man:in.tftpd + +[Service] +ExecStart=/usr/sbin/in.tftpd -s /var/lib/tftpboot +StandardInput=socket + +[Install] +Also=tftp.socket diff --git a/SOURCES/tftp.socket b/SOURCES/tftp.socket new file mode 100644 index 0000000..8764c1d --- /dev/null +++ b/SOURCES/tftp.socket @@ -0,0 +1,8 @@ +[Unit] +Description=Tftp Server Activation Socket + +[Socket] +ListenDatagram=69 + +[Install] +WantedBy=sockets.target diff --git a/SPECS/tftp.spec b/SPECS/tftp.spec new file mode 100644 index 0000000..b0809e6 --- /dev/null +++ b/SPECS/tftp.spec @@ -0,0 +1,459 @@ +%global systemctl_bin /usr/bin/systemctl +%global _hardened_build 1 + +Summary: The client for the Trivial File Transfer Protocol (TFTP) +Name: tftp +Version: 5.2 +Release: 22%{?dist} +License: BSD +Group: Applications/Internet +URL: http://www.kernel.org/pub/software/network/tftp/ +Source0: http://www.kernel.org/pub/software/network/tftp/tftp-hpa/tftp-hpa-%{version}.tar.bz2 +Source1: tftp.socket +Source2: tftp.service + +Patch0: tftp-0.40-remap.patch +Patch2: tftp-hpa-0.39-tzfix.patch +Patch3: tftp-0.42-tftpboot.patch +Patch4: tftp-0.49-chk_retcodes.patch +Patch5: tftp-hpa-0.49-fortify-strcpy-crash.patch +Patch6: tftp-0.49-cmd_arg.patch +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 + +%description +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp package provides the user +interface for TFTP, which allows users to transfer files to and from a +remote machine. This program and TFTP provide very little security, +and should not be enabled unless it is expressly needed. + +%package server +Group: System Environment/Daemons +Summary: The server for the Trivial File Transfer Protocol (TFTP) +Requires: systemd-units +Requires(post): systemd-units +Requires(postun): systemd-units + +%description server +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp-server package provides the +server for TFTP, which allows users to transfer files to and from a +remote machine. TFTP provides very little security, and should not be +enabled unless it is expressly needed. + +%prep +%setup -q -n tftp-hpa-%{version} +%patch0 -p1 -b .zero +%patch2 -p1 -b .tzfix +%patch3 -p1 -b .tftpboot +%patch4 -p1 -b .chk_retcodes +%patch5 -p1 -b .fortify-strcpy-crash +%patch6 -p1 -b .cmd_arg +%patch7 -p1 -b .stats +%patch8 -p1 -b .pktinfo +%patch9 -p1 -b .doc +%patch10 -p1 -b .logging +%patch11 -p1 -b .windowsize +%patch12 -p1 -b .rewrite-macro + +%build +autoreconf +%configure +make %{?_smp_mflags} + +%install +rm -rf ${RPM_BUILD_ROOT} +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man{1,8} +mkdir -p ${RPM_BUILD_ROOT}%{_sbindir} +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/lib/tftpboot +mkdir -p ${RPM_BUILD_ROOT}%{_unitdir} + +make INSTALLROOT=${RPM_BUILD_ROOT} SBINDIR=%{_sbindir} MANDIR=%{_mandir} INSTALL='install -p' install +install -m755 -d -p ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/ ${RPM_BUILD_ROOT}%{_localstatedir}/lib/tftpboot +sed -e 's:/var:%{_localstatedir}:' -e 's:/usr/sbin:%{_sbindir}:' \ + tftp-xinetd > ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/tftp +touch -r tftp-xinetd ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/tftp + +install -p -m 644 %SOURCE1 ${RPM_BUILD_ROOT}%{_unitdir} +install -p -m 644 %SOURCE2 ${RPM_BUILD_ROOT}%{_unitdir} + +%post server +%systemd_post tftp.socket + +%preun server +%systemd_preun tftp.socket + +%postun server +%systemd_postun_with_restart tftp.socket + + +%clean +rm -rf ${RPM_BUILD_ROOT} + +%files +%doc README README.security CHANGES +%{_bindir}/tftp +%{_mandir}/man1/* + +%files server +%doc README README.security CHANGES +%config(noreplace) %{_sysconfdir}/xinetd.d/tftp +%dir %{_localstatedir}/lib/tftpboot +%{_sbindir}/in.tftpd +%{_mandir}/man8/* +%{_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) + +* Tue Apr 28 2015 Jan Synáček - 5.2-12 +- remove unnecessary installation dependency on xinetd(#1136866) +- improve systemd unit files(#1167777) + +* Fri Jan 24 2014 Daniel Mach - 5.2-11 +- Mass rebuild 2014-01-24 + +* Fri Dec 27 2013 Daniel Mach - 5.2-10 +- Mass rebuild 2013-12-27 + +* Tue Apr 23 2013 Jan Synáček - 5.2-9 +- harden the package (#955197) + +* Fri Apr 19 2013 Jan Synáček - 5.2-8 +- documentation fixes + +* Fri Feb 15 2013 Fedora Release Engineering - 5.2-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Wed Aug 22 2012 Jan Synáček - 5.2-6 +- add systemd-rpm macros +- Resolves: #850338 + +* Sat Jul 21 2012 Fedora Release Engineering - 5.2-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Thu Jul 19 2012 Jan Synáček - 5.2.4 +- make fedora-review-friendly + +* Wed Jul 18 2012 Jan Synáček - 5.2-4 +- update spec: fix Source0 + +* Wed May 30 2012 Jan Synáček - 5.2-4 +- use systemd instead of xinetd as a default + +* Tue May 22 2012 Jan Synáček - 5.2-3 +- provide native systemd service files +- Resolves: #737212 + +* Wed Jan 04 2012 Jiri Skala - 5.2-2 +- fixes #739534 - TFTP to an IP alias of FC15 tftp server failed + +* Wed Dec 14 2011 Jiri Skala - 5.2-1 +- updated to latest upstream - 5.2 + +* Thu Oct 06 2011 Jiri Skala - 5.1-1 +- updated to latest upstream - 5.1 + +* Mon Jun 20 2011 Jiri Skala - 0.49-9 +- fixes #714261 - CVE-2011-2199: buffer overflow when setting utimeout option + +* Wed Feb 09 2011 Fedora Release Engineering - 0.49-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Mon Jan 03 2011 Jiri Skala - 0.49-7 +- fixes #666746 - Packaging mistake: confusing %doc files patched+unpatched +- fixes printing statistics using -v option + +* Fri May 28 2010 Jiri Skala - 0.49-6 +- patched handling arguments of commands (put) + +* Wed Aug 05 2009 Warren Togami - 0.49-5 +- Bug #515361 tftp FORTIFY_SOURCE strcpy crash + +* Sun Jul 26 2009 Fedora Release Engineering - 0.49-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Wed Feb 25 2009 Fedora Release Engineering - 0.49-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + +* Thu Jan 15 2009 Jiri Skala - 0.49-2 +- #473487 - unchecked return values + +* Tue Nov 25 2008 Tom "spot" Callaway - 0.49-1 +- update to 0.49 + +* Wed May 21 2008 Warren Togami - 0.48-5 +- fix troubles caused by added symlink + +* Tue May 20 2008 Martin Nagy - 0.48-4 +- add symlink to /var/lib/tftpboot + +* Mon Mar 03 2008 Martin Nagy - 0.48-3 +- changed description (#234099) + +* Mon Feb 11 2008 Martin Nagy - 0.48-2 +- rebuild for gcc-4.3 + +* Tue Jan 22 2008 Martin Nagy - 0.48-1 +- upgrade to 0.48 +- remove the old sigjmp patch (fixed in upstream) +- make some changes in spec file (#226489) + +* Tue Jan 22 2008 Martin Nagy - 0.42-6 +- changed the location of tftpboot directory to /var/lib/ + +* Fri Aug 31 2007 Maros Barabas - 0.42-5 +- rebuild + +* Mon Feb 19 2007 Maros Barabas - 0.42-4 +- make some changes in spec file (review) +- Resolves #226489 + +* Mon Dec 04 2006 Maros Barabas - 0.42-3.2 +- change BuildRequires from tcp_wrappers to tcp_wrappers-devel + +* Wed Jul 12 2006 Jesse Keating - 0.42-3.1 +- rebuild + +* Mon Apr 10 2006 Radek Vokál 0.42-3 +- show localtime instead of GMT (#172274) + +* Wed Mar 22 2006 Radek Vokál 0.42-2 +- fix double free error when hitting ^C (#186201) + +* Wed Feb 22 2006 Radek Vokál 0.42-1 +- upgrade to 0.42 + +* Fri Feb 10 2006 Jesse Keating - 0.41-1.2.1 +- bump again for double-long bug on ppc(64) + +* Tue Feb 07 2006 Jesse Keating - 0.41-1.2 +- rebuilt for new gcc4.1 snapshot and glibc changes + +* Fri Dec 09 2005 Jesse Keating +- rebuilt + +* Thu Nov 03 2005 Radek Vokal 0.41-1 +- upstream update (patterns fixes) + +* Tue Apr 19 2005 Radek Vokal 0.40-6 +- fix remap rules convert error + +* Wed Mar 23 2005 Radek Vokal 0.40-5 +- use tftp-xinetd from tarball (#143589) + +* Fri Mar 04 2005 Radek Vokal 0.40-4 +- gcc4 rebuilt + +* Sun Feb 27 2005 Florian La Roche +- Copyright: -> License + +* Wed Jan 12 2005 Tim Waugh 0.40-2 +- Rebuilt for new readline. + +* Mon Nov 15 2004 Radek Vokal 0.40-1 +- Update to new upstream version, fixes #139328 + +* Mon Sep 13 2004 Elliot Lee 0.39-1 +- Update to new version makes tftp work, says upstream. +- Remove malta patch + +* Mon Sep 13 2004 Elliot Lee 0.38-1 +- Update to new version fixes #131736 + +* Tue Jun 15 2004 Elliot Lee +- rebuilt + +* Thu Jun 03 2004 Elliot Lee 0.36-1 +- Update version + +* Fri Feb 13 2004 Elliot Lee +- rebuilt + +* Wed Jun 04 2003 Elliot Lee +- rebuilt + +* Fri Apr 11 2003 Elliot Lee +- 0.33 +- Add /tftpboot directory (#88204) + +* Mon Feb 24 2003 Elliot Lee +- rebuilt + +* Sun Feb 23 2003 Tim Powers +- add BuildPreReq on tcp_wrappers + +* Wed Jan 22 2003 Tim Powers +- rebuilt + +* Mon Nov 11 2002 Elliot Lee 0.32-1 +- Update to 0.32 + +* Wed Oct 23 2002 Elliot Lee 0.30-1 +- Fix #55789 +- Update to 0.30 + +* Thu Jun 27 2002 Elliot Lee +- Try applying HJ's patch from #65476 + +* Fri Jun 21 2002 Tim Powers +- automated rebuild + +* Mon Jun 17 2002 Elliot Lee +- Update to 0.29 + +* Thu May 23 2002 Tim Powers +- automated rebuild + +* Wed Jan 09 2002 Tim Powers +- automated rebuild + +* Tue Dec 18 2001 Elliot Lee 0.17-15 +- Add patch4: netkit-tftp-0.17-defaultport.patch for bug #57562 +- Update to tftp-hpa-0.28 (bug #56131) +- Remove include/arpa/tftp.h to fix #57259 +- Add resource limits in tftp-xinetd (#56722) + +* Sun Jun 24 2001 Elliot Lee +- Bump release + rebuild. + +* Tue Jun 12 2001 Helge Deller (0.17-13) +- updated tftp-hpa source to tftp-hpa-0.17 +- tweaked specfile with different defines for tftp-netkit and tftp-hpa version +- use hpa's tftpd.8 man page instead of the netkits one + +* Mon May 07 2001 Helge Deller +- rebuilt in 7.1.x + +* Wed Apr 18 2001 Helge Deller +- fix tftp client's put problems (#29529) +- update to tftp-hpa-0.16 + +* Wed Apr 4 2001 Jakub Jelinek +- don't let configure to guess compiler, it can pick up egcs + +* Thu Feb 08 2001 Helge Deller +- changed "wait" in xinetd file to "yes" (hpa-tftpd forks and exits) (#26467) +- fixed hpa-tftpd to handle files greater than 32MB (#23725) +- added "-l" flag to hpa-tftpd for file-logging (#26467) +- added description for "-l" to the man-page + +* Thu Feb 08 2001 Helge Deller +- updated tftp client to 0.17 stable (#19640), +- drop dependency on xinetd for tftp client (#25051), + +* Wed Jan 17 2001 Jeff Johnson +- xinetd shouldn't wait on tftp (which forks) (#23923). + +* Sat Jan 6 2001 Jeff Johnson +- fix to permit tftp put's (#18128). +- startup as root with chroot to /tftpboot with early reversion to nobody + is preferable to starting as nobody w/o ability to chroot. +- %%post is needed by server, not client. Add %%postun for erasure as well. + +* Wed Aug 23 2000 Nalin Dahyabhai +- default to being disabled + +* Thu Aug 17 2000 Jeff Johnson +- correct group. + +* Tue Jul 25 2000 Nalin Dahyabhai +- change user from root to nobody + +* Sat Jul 22 2000 Jeff Johnson +- update to tftp-hpa-0.14 (#14003). +- add server_args (#14003). +- remove -D_BSD_SOURCE (#14003). + +* Fri Jul 21 2000 Nalin Dahyabhai +- cook up an xinetd config file for tftpd + +* Wed Jul 12 2000 Prospector +- automatic rebuild + +* Sun Jun 18 2000 Jeff Johnson +- FHS packaging. +- update to 0.17. + +* Fri May 5 2000 Matt Wilson +- use _BSD_SOURCE for hpa's tftpd so we get BSD signal semantics. + +* Fri Feb 11 2000 Bill Nottingham +- fix description + +* Wed Feb 9 2000 Jeff Johnson +- compress man pages (again). + +* Wed Feb 02 2000 Cristian Gafton +- man pages are compressed +- fix description and summary + +* Tue Jan 4 2000 Bill Nottingham +- split client and server + +* Tue Dec 21 1999 Jeff Johnson +- update to 0.16. + +* Sat Aug 28 1999 Jeff Johnson +- update to 0.15. + +* Wed Apr 7 1999 Jeff Johnson +- tftpd should truncate file when overwriting (#412) + +* Sun Mar 21 1999 Cristian Gafton +- auto rebuild in the new build environment (release 22) + +* Mon Mar 15 1999 Jeff Johnson +- compile for 6.0. + +* Fri Aug 7 1998 Jeff Johnson +- build root + +* Mon Apr 27 1998 Prospector System +- translations modified for de, fr, tr + +* Mon Sep 22 1997 Erik Troan +- added check for getpwnam() failure + +* Tue Jul 15 1997 Erik Troan +- initial build