|
|
10665a |
commit e46782908e7026f27ef92de52e47ec3720116125
|
|
|
10665a |
Author: Jan Synacek <jsynacek@redhat.com>
|
|
|
10665a |
Date: Tue Nov 7 08:26:54 2017 +0100
|
|
|
10665a |
|
|
|
10665a |
implement rfc7440
|
|
|
10665a |
|
|
|
10665a |
diff --git a/common/Makefile b/common/Makefile
|
|
|
10665a |
index a825213..d1e97b5 100644
|
|
|
10665a |
--- a/common/Makefile
|
|
|
10665a |
+++ b/common/Makefile
|
|
|
10665a |
@@ -4,7 +4,7 @@ VERSION = $(shell cat ../version)
|
|
|
10665a |
-include ../MCONFIG
|
|
|
10665a |
include ../MRULES
|
|
|
10665a |
|
|
|
10665a |
-OBJS = tftpsubs.$(O)
|
|
|
10665a |
+OBJS = tftpsubs.$(O) common.$(O)
|
|
|
10665a |
LIB = libcommon.a
|
|
|
10665a |
|
|
|
10665a |
all: $(LIB)
|
|
|
10665a |
@@ -14,7 +14,7 @@ $(LIB): $(OBJS)
|
|
|
10665a |
$(AR) $(LIB) $(OBJS)
|
|
|
10665a |
$(RANLIB) $(LIB)
|
|
|
10665a |
|
|
|
10665a |
-$(OBJS): tftpsubs.h
|
|
|
10665a |
+$(OBJS): tftpsubs.h common.h
|
|
|
10665a |
|
|
|
10665a |
install:
|
|
|
10665a |
|
|
|
10665a |
diff --git a/common/common.c b/common/common.c
|
|
|
10665a |
new file mode 100644
|
|
|
10665a |
index 0000000..a4e2fef
|
|
|
10665a |
--- /dev/null
|
|
|
10665a |
+++ b/common/common.c
|
|
|
10665a |
@@ -0,0 +1,433 @@
|
|
|
10665a |
+/*
|
|
|
10665a |
+ * Copyright (c) 2017 Jan Synáček
|
|
|
10665a |
+ * All rights reserved.
|
|
|
10665a |
+ *
|
|
|
10665a |
+ * Redistribution and use in source and binary forms, with or without
|
|
|
10665a |
+ * modification, are permitted provided that the following conditions
|
|
|
10665a |
+ * are met:
|
|
|
10665a |
+ * 1. Redistributions of source code must retain the above copyright
|
|
|
10665a |
+ * notice, this list of conditions and the following disclaimer.
|
|
|
10665a |
+ * 2. Redistributions in binary form must reproduce the above copyright
|
|
|
10665a |
+ * notice, this list of conditions and the following disclaimer in the
|
|
|
10665a |
+ * documentation and/or other materials provided with the distribution.
|
|
|
10665a |
+ * 3. All advertising materials mentioning features or use of this software
|
|
|
10665a |
+ * must display the following acknowledgement:
|
|
|
10665a |
+ * This product includes software developed by the University of
|
|
|
10665a |
+ * California, Berkeley and its contributors.
|
|
|
10665a |
+ * 4. Neither the name of the University nor the names of its contributors
|
|
|
10665a |
+ * may be used to endorse or promote products derived from this software
|
|
|
10665a |
+ * without specific prior written permission.
|
|
|
10665a |
+ *
|
|
|
10665a |
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
|
10665a |
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
10665a |
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
10665a |
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
|
10665a |
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
10665a |
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
10665a |
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
10665a |
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
10665a |
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
10665a |
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
10665a |
+ * SUCH DAMAGE.
|
|
|
10665a |
+ */
|
|
|
10665a |
+
|
|
|
10665a |
+#include <poll.h>
|
|
|
10665a |
+#include <stdarg.h>
|
|
|
10665a |
+#include <syslog.h>
|
|
|
10665a |
+#include "common.h"
|
|
|
10665a |
+
|
|
|
10665a |
+static char *pktbuf;
|
|
|
10665a |
+static int verbose;
|
|
|
10665a |
+
|
|
|
10665a |
+const int SYNC_TIMEOUT = 50; /* ms */
|
|
|
10665a |
+
|
|
|
10665a |
+static void die(const char *fmt, ...)
|
|
|
10665a |
+{
|
|
|
10665a |
+ va_list ap;
|
|
|
10665a |
+
|
|
|
10665a |
+ va_start(ap, fmt);
|
|
|
10665a |
+ fprintf(stderr, "fatal: ");
|
|
|
10665a |
+ vfprintf(stderr, fmt, ap);
|
|
|
10665a |
+ printf("\n");
|
|
|
10665a |
+ va_end(ap);
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int format_error(struct tftphdr *tp, char *error)
|
|
|
10665a |
+{
|
|
|
10665a |
+ int r = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ if (error)
|
|
|
10665a |
+ r = snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg);
|
|
|
10665a |
+ return r;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+void die_on_error(struct tftphdr *tp)
|
|
|
10665a |
+{
|
|
|
10665a |
+ char error[ERROR_MAXLEN];
|
|
|
10665a |
+
|
|
|
10665a |
+ snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg);
|
|
|
10665a |
+ fprintf(stderr, "%s\n", error);
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+void send_error(int sockfd, union sock_addr *to, const char *msg)
|
|
|
10665a |
+{
|
|
|
10665a |
+ char buf[516];
|
|
|
10665a |
+ struct tftphdr *out = (struct tftphdr *)buf;
|
|
|
10665a |
+ int len;
|
|
|
10665a |
+
|
|
|
10665a |
+ out->th_opcode = htons(ERROR);
|
|
|
10665a |
+ out->th_code = htons(EUNDEF);
|
|
|
10665a |
+
|
|
|
10665a |
+ len = strlen(msg) + 1;
|
|
|
10665a |
+ memset(buf, 0, 516);
|
|
|
10665a |
+ memcpy(out->th_msg, msg, len > 511 ? 511 : len);
|
|
|
10665a |
+ len += 4;
|
|
|
10665a |
+
|
|
|
10665a |
+ if (to) {
|
|
|
10665a |
+ if (sendto(sockfd, out, len, 0, &to->sa, SOCKLEN(to)) != len)
|
|
|
10665a |
+ die("send_error: sendto: %s", strerror(-errno));
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ if (send(sockfd, out, len, 0) != len)
|
|
|
10665a |
+ die("send_error: send: %s", strerror(-errno));
|
|
|
10665a |
+ }
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static void _send_ack(int sockfd, union sock_addr *to, unsigned short block, int check_errors)
|
|
|
10665a |
+{
|
|
|
10665a |
+ struct tftphdr out;
|
|
|
10665a |
+ out.th_opcode = htons(ACK);
|
|
|
10665a |
+ out.th_block = htons(block);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (to) {
|
|
|
10665a |
+ if (sendto(sockfd, &out, 4, 0, &to->sa, SOCKLEN(to)) != 4 && check_errors)
|
|
|
10665a |
+ die("send_ack: sendto: %m");
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ if (send(sockfd, &out, 4, 0) != 4 && check_errors)
|
|
|
10665a |
+ die("send_ack: send: %m");
|
|
|
10665a |
+ }
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+void send_ack(int sockfd, union sock_addr *to, unsigned short block)
|
|
|
10665a |
+{
|
|
|
10665a |
+ _send_ack(sockfd, to, block, 1);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+
|
|
|
10665a |
+static size_t read_data(FILE *fp,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ unsigned short block,
|
|
|
10665a |
+ int seek,
|
|
|
10665a |
+ struct tftphdr *out)
|
|
|
10665a |
+{
|
|
|
10665a |
+ out->th_opcode = htons(DATA);
|
|
|
10665a |
+ out->th_block = htons(block);
|
|
|
10665a |
+ if (seek)
|
|
|
10665a |
+ (void) fseek(fp, seek, SEEK_CUR);
|
|
|
10665a |
+ return fread(pktbuf + 4, 1, blocksize, fp);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static size_t write_data(FILE *fp, char *buf, size_t count, int convert)
|
|
|
10665a |
+{
|
|
|
10665a |
+ char wbuf[count];
|
|
|
10665a |
+ size_t i = 0, cnt = 0;
|
|
|
10665a |
+ static int was_cr = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ /* TODO: jsynacek: I don't think any conversion should take place...
|
|
|
10665a |
+ * RFC 1350 says: "A host which receives netascii mode data must translate
|
|
|
10665a |
+ * the data to its own format."
|
|
|
10665a |
+ * That basically means nothing. What does "own format" even mean?
|
|
|
10665a |
+ * The original implementation translated \r\n to \n and skipped \0 bytes,
|
|
|
10665a |
+ * which aren't even legal in the netascii format.
|
|
|
10665a |
+ * However, I believe that the file should remain as is before and after
|
|
|
10665a |
+ * the transfer.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ convert = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ if (convert == 0)
|
|
|
10665a |
+ return fwrite(buf, 1, count, fp);
|
|
|
10665a |
+
|
|
|
10665a |
+ /* Working conversion. Leave it as dead code for now. */
|
|
|
10665a |
+ if (was_cr && buf[0] == '\n') {
|
|
|
10665a |
+ wbuf[cnt++] = '\n';
|
|
|
10665a |
+ i = 1;
|
|
|
10665a |
+ (void) fseek(fp, -1, SEEK_CUR);
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ while(i < count) {
|
|
|
10665a |
+ if (buf[i] == '\r' && buf[i + 1] == '\n') {
|
|
|
10665a |
+ wbuf[cnt++] = '\n';
|
|
|
10665a |
+ ++i;
|
|
|
10665a |
+ } else if (buf[i] == '\0') {
|
|
|
10665a |
+ /* Skip it */
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ wbuf[cnt++] = buf[i];
|
|
|
10665a |
+ }
|
|
|
10665a |
+ ++i;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ /* Preserve state between data chunks */
|
|
|
10665a |
+ was_cr = buf[i - 1] == '\r';
|
|
|
10665a |
+
|
|
|
10665a |
+ if (fwrite(wbuf, 1, cnt, fp) == 0)
|
|
|
10665a |
+ return 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ return count;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+void set_verbose(int v)
|
|
|
10665a |
+{
|
|
|
10665a |
+ verbose = v;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int recv_with_timeout(int s, void *in, int len, int timeout)
|
|
|
10665a |
+{
|
|
|
10665a |
+ return recvfrom_flags_with_timeout(s, in, len, NULL, timeout, 0);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int recvfrom_with_timeout(int s,
|
|
|
10665a |
+ void *in,
|
|
|
10665a |
+ size_t len,
|
|
|
10665a |
+ union sock_addr *from,
|
|
|
10665a |
+ int timeout)
|
|
|
10665a |
+{
|
|
|
10665a |
+ return recvfrom_flags_with_timeout(s, in, len, from, timeout, 0);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int recvfrom_flags_with_timeout(int s,
|
|
|
10665a |
+ void *in,
|
|
|
10665a |
+ size_t len,
|
|
|
10665a |
+ union sock_addr *from,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ int flags)
|
|
|
10665a |
+{
|
|
|
10665a |
+ socklen_t fromlen = sizeof(from);
|
|
|
10665a |
+ struct pollfd pfd;
|
|
|
10665a |
+ int r;
|
|
|
10665a |
+
|
|
|
10665a |
+ pfd.fd = s;
|
|
|
10665a |
+ pfd.events = POLLIN;
|
|
|
10665a |
+ pfd.revents = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ r = poll(&pfd, 1, timeout);
|
|
|
10665a |
+ if (r > 0) {
|
|
|
10665a |
+ if (from) {
|
|
|
10665a |
+ r = recvfrom(s, in, len, flags, &from->sa, &fromlen);
|
|
|
10665a |
+ if (r < 0)
|
|
|
10665a |
+ die("recvfrom_flags_with_timeout: recvfrom: %m");
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ r = recv(s, in, len, flags);
|
|
|
10665a |
+ if (r < 0)
|
|
|
10665a |
+ die("recvfrom_flags_with_timeout: recv: %m");
|
|
|
10665a |
+ }
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ return r;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int receiver(int sockfd,
|
|
|
10665a |
+ union sock_addr *server,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ int windowsize,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ FILE *fp,
|
|
|
10665a |
+ unsigned long *received,
|
|
|
10665a |
+ char *error)
|
|
|
10665a |
+{
|
|
|
10665a |
+ struct tftphdr *tp;
|
|
|
10665a |
+ unsigned short tp_opcode, tp_block;
|
|
|
10665a |
+ unsigned short block = 1;
|
|
|
10665a |
+ unsigned long amount = 0;
|
|
|
10665a |
+ int pktsize = blocksize + 4;
|
|
|
10665a |
+ int window = 1;
|
|
|
10665a |
+ size_t size;
|
|
|
10665a |
+ int retries;
|
|
|
10665a |
+ int n, r = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ pktbuf = calloc(pktsize, 1);
|
|
|
10665a |
+ if (!pktbuf)
|
|
|
10665a |
+ die("Out of memory!");
|
|
|
10665a |
+ tp = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ n = recvfrom_with_timeout(sockfd, tp, pktsize, server, timeout);
|
|
|
10665a |
+ if (n == 0) {
|
|
|
10665a |
+ if (--retries <= 0) {
|
|
|
10665a |
+ r = E_TIMED_OUT;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ continue;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
+
|
|
|
10665a |
+ tp_opcode = ntohs(tp->th_opcode);
|
|
|
10665a |
+ tp_block = ntohs(tp->th_block);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (tp_opcode == DATA) {
|
|
|
10665a |
+ if (tp_block == block) {
|
|
|
10665a |
+ size = write_data(fp, tp->th_data, n - 4, 0);
|
|
|
10665a |
+ if (size == 0 && ferror(fp)) {
|
|
|
10665a |
+ send_error(sockfd, server, "Failed to write data");
|
|
|
10665a |
+ r = E_FAILED_TO_WRITE;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ amount += size;
|
|
|
10665a |
+
|
|
|
10665a |
+ if (window++ >= windowsize || size != blocksize) {
|
|
|
10665a |
+ send_ack(sockfd, server, block);
|
|
|
10665a |
+ window = 1;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ ++block;
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT);
|
|
|
10665a |
+ } while (n > 0);
|
|
|
10665a |
+ if (windowsize > 0) {
|
|
|
10665a |
+ send_ack(sockfd, server, block - 1);
|
|
|
10665a |
+ }
|
|
|
10665a |
+ window = 1;
|
|
|
10665a |
+ n = 0;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ } else if (tp_opcode == ERROR) {
|
|
|
10665a |
+ format_error(tp, error);
|
|
|
10665a |
+ r = E_RECEIVED_ERROR;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ r = E_UNEXPECTED_PACKET;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ } while (size == blocksize || n == 0);
|
|
|
10665a |
+
|
|
|
10665a |
+ /* Last ack can get lost, let's try and resend it twice
|
|
|
10665a |
+ * to make it more likely that the ack gets to the sender.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ --block;
|
|
|
10665a |
+ for (n = 0; n < 2; ++n) {
|
|
|
10665a |
+ usleep(SYNC_TIMEOUT * 1000);
|
|
|
10665a |
+ _send_ack(sockfd, server, block, 0);
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ if (received)
|
|
|
10665a |
+ *received = amount;
|
|
|
10665a |
+abort:
|
|
|
10665a |
+ free(pktbuf);
|
|
|
10665a |
+ return r;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+int sender(int sockfd,
|
|
|
10665a |
+ union sock_addr *server,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ int windowsize,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ int rollover,
|
|
|
10665a |
+ FILE *fp,
|
|
|
10665a |
+ unsigned long *sent)
|
|
|
10665a |
+{
|
|
|
10665a |
+ struct tftphdr *tp;
|
|
|
10665a |
+ unsigned short tp_opcode, tp_block;
|
|
|
10665a |
+ unsigned short block = 1;
|
|
|
10665a |
+ unsigned long amount = 0;
|
|
|
10665a |
+ int l_timeout = timeout;
|
|
|
10665a |
+ int pktsize = blocksize + 4;
|
|
|
10665a |
+ int window = 1;
|
|
|
10665a |
+ int seek = 0;
|
|
|
10665a |
+ int retries;
|
|
|
10665a |
+ size_t size;
|
|
|
10665a |
+ int done = 0;
|
|
|
10665a |
+ int n, r = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ pktbuf = calloc(pktsize, 1);
|
|
|
10665a |
+ if (!pktbuf)
|
|
|
10665a |
+ die("Out of memory!");
|
|
|
10665a |
+ tp = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ size = read_data(fp, blocksize, block, seek, tp);
|
|
|
10665a |
+ if (size == 0 && ferror(fp)) {
|
|
|
10665a |
+ send_error(sockfd, server, "Error while reading the file");
|
|
|
10665a |
+ r = E_FAILED_TO_READ;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ amount += size;
|
|
|
10665a |
+ seek = 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ if (server)
|
|
|
10665a |
+ n = sendto(sockfd, tp, size + 4, 0, &server->sa, SOCKLEN(server));
|
|
|
10665a |
+ else
|
|
|
10665a |
+ n = send(sockfd, tp, size + 4, 0);
|
|
|
10665a |
+ if (n != size + 4) {
|
|
|
10665a |
+ syslog(LOG_WARNING, "tftpd: send: %m");
|
|
|
10665a |
+ r = E_SYSTEM_ERROR;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ if (window++ < windowsize) {
|
|
|
10665a |
+ /* Even if the entire window is not sent, the server should check for incoming packets
|
|
|
10665a |
+ * and react to out of order ACKs, or ERRORs.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ l_timeout = 0;
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ l_timeout = timeout;
|
|
|
10665a |
+ window = 1;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ done = size != blocksize;
|
|
|
10665a |
+ if (done) {
|
|
|
10665a |
+ l_timeout = timeout;
|
|
|
10665a |
+ window = 1;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, l_timeout);
|
|
|
10665a |
+ if (n < 0) {
|
|
|
10665a |
+ syslog(LOG_WARNING, "tftpd: recv: %m");
|
|
|
10665a |
+ r = E_SYSTEM_ERROR;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ } else if (l_timeout > 0 && n == 0) {
|
|
|
10665a |
+ seek = -size;
|
|
|
10665a |
+ if (--retries <= 0) {
|
|
|
10665a |
+ r = E_TIMED_OUT;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ done = 0;
|
|
|
10665a |
+ continue;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ if (++block == 0)
|
|
|
10665a |
+ block = rollover;
|
|
|
10665a |
+
|
|
|
10665a |
+ tp_opcode = ntohs(tp->th_opcode);
|
|
|
10665a |
+ tp_block = ntohs(tp->th_block);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (tp_opcode == ACK) {
|
|
|
10665a |
+ if (tp_block != (unsigned short)(block - 1)) {
|
|
|
10665a |
+ int offset = tp_block;
|
|
|
10665a |
+
|
|
|
10665a |
+ done = 0;
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT);
|
|
|
10665a |
+ } while (n > 0);
|
|
|
10665a |
+ /* This is a bit of a hack. Mismatched packets that are "over the edge" of the unsigned short
|
|
|
10665a |
+ * have to be handled and a correct seek has to be issued. In theory, overflowing block number
|
|
|
10665a |
+ * should not even be supported, as it is impossible to distinguish correctly if more than 2^16
|
|
|
10665a |
+ * packets are sent, but the first ones are received later than the last ones.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ if (tp_block > 65000 && tp_block > (unsigned short)(block - 1))
|
|
|
10665a |
+ offset -= 65535;
|
|
|
10665a |
+ seek = (offset - block + 2) * blocksize - size;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ block = tp_block + 1;
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
+ } else if (tp_opcode == ERROR) {
|
|
|
10665a |
+ r = E_RECEIVED_ERROR;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ } while (!done);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (sent)
|
|
|
10665a |
+ *sent = amount;
|
|
|
10665a |
+abort:
|
|
|
10665a |
+ free(pktbuf);
|
|
|
10665a |
+ return r;
|
|
|
10665a |
+}
|
|
|
10665a |
diff --git a/common/common.h b/common/common.h
|
|
|
10665a |
new file mode 100644
|
|
|
10665a |
index 0000000..31c1b7c
|
|
|
10665a |
--- /dev/null
|
|
|
10665a |
+++ b/common/common.h
|
|
|
10665a |
@@ -0,0 +1,97 @@
|
|
|
10665a |
+/*
|
|
|
10665a |
+ * Copyright (c) 2017 Jan Synáček
|
|
|
10665a |
+ * All rights reserved.
|
|
|
10665a |
+ *
|
|
|
10665a |
+ * Redistribution and use in source and binary forms, with or without
|
|
|
10665a |
+ * modification, are permitted provided that the following conditions
|
|
|
10665a |
+ * are met:
|
|
|
10665a |
+ * 1. Redistributions of source code must retain the above copyright
|
|
|
10665a |
+ * notice, this list of conditions and the following disclaimer.
|
|
|
10665a |
+ * 2. Redistributions in binary form must reproduce the above copyright
|
|
|
10665a |
+ * notice, this list of conditions and the following disclaimer in the
|
|
|
10665a |
+ * documentation and/or other materials provided with the distribution.
|
|
|
10665a |
+ * 3. All advertising materials mentioning features or use of this software
|
|
|
10665a |
+ * must display the following acknowledgement:
|
|
|
10665a |
+ * This product includes software developed by the University of
|
|
|
10665a |
+ * California, Berkeley and its contributors.
|
|
|
10665a |
+ * 4. Neither the name of the University nor the names of its contributors
|
|
|
10665a |
+ * may be used to endorse or promote products derived from this software
|
|
|
10665a |
+ * without specific prior written permission.
|
|
|
10665a |
+ *
|
|
|
10665a |
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
|
10665a |
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
10665a |
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
10665a |
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
|
10665a |
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
10665a |
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
10665a |
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
10665a |
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
10665a |
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
10665a |
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
10665a |
+ * SUCH DAMAGE.
|
|
|
10665a |
+ */
|
|
|
10665a |
+
|
|
|
10665a |
+#ifndef _COMMON_H
|
|
|
10665a |
+#define _COMMON_H
|
|
|
10665a |
+
|
|
|
10665a |
+#include <netinet/in.h>
|
|
|
10665a |
+#include "config.h"
|
|
|
10665a |
+
|
|
|
10665a |
+#define TIMEOUT 1000 /* ms */
|
|
|
10665a |
+#define RETRIES 6
|
|
|
10665a |
+
|
|
|
10665a |
+#define E_TIMED_OUT -1
|
|
|
10665a |
+#define E_RECEIVED_ERROR -2
|
|
|
10665a |
+#define E_UNEXPECTED_PACKET -3
|
|
|
10665a |
+#define E_FAILED_TO_READ -4
|
|
|
10665a |
+#define E_FAILED_TO_WRITE -5
|
|
|
10665a |
+#define E_SYSTEM_ERROR -6
|
|
|
10665a |
+#define ERROR_MAXLEN 511
|
|
|
10665a |
+
|
|
|
10665a |
+union sock_addr {
|
|
|
10665a |
+ struct sockaddr sa;
|
|
|
10665a |
+ struct sockaddr_in si;
|
|
|
10665a |
+#ifdef HAVE_IPV6
|
|
|
10665a |
+ struct sockaddr_in6 s6;
|
|
|
10665a |
+#endif
|
|
|
10665a |
+};
|
|
|
10665a |
+
|
|
|
10665a |
+#define SOCKLEN(sock) \
|
|
|
10665a |
+ (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
|
|
|
10665a |
+ (sizeof(struct sockaddr_in)) : \
|
|
|
10665a |
+ (sizeof(union sock_addr)))
|
|
|
10665a |
+
|
|
|
10665a |
+const char *opcode_to_str(unsigned short opcode);
|
|
|
10665a |
+int str_equal(const char *s1, const char *s2);
|
|
|
10665a |
+void set_verbose(int v);
|
|
|
10665a |
+
|
|
|
10665a |
+int format_error(struct tftphdr *tp, char *error);
|
|
|
10665a |
+void die_on_error(struct tftphdr *tp);
|
|
|
10665a |
+void send_error(int sockfd, union sock_addr *to, const char *msg);
|
|
|
10665a |
+void send_ack(int sockfd, union sock_addr *to, unsigned short block);
|
|
|
10665a |
+int recv_with_timeout(int s, void *in, int len, int timeout);
|
|
|
10665a |
+int recvfrom_with_timeout(int s, void *in, size_t len, union sock_addr *from, int timeout);
|
|
|
10665a |
+int recvfrom_flags_with_timeout(int s,
|
|
|
10665a |
+ void *in,
|
|
|
10665a |
+ size_t len,
|
|
|
10665a |
+ union sock_addr *from,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ int flags);
|
|
|
10665a |
+int receiver(int sockfd,
|
|
|
10665a |
+ union sock_addr *server,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ int windowsize,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ FILE *fp,
|
|
|
10665a |
+ unsigned long *received,
|
|
|
10665a |
+ char *error);
|
|
|
10665a |
+
|
|
|
10665a |
+int sender(int sockfd,
|
|
|
10665a |
+ union sock_addr *server,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ int windowsize,
|
|
|
10665a |
+ int timeout,
|
|
|
10665a |
+ int rollover,
|
|
|
10665a |
+ FILE *fp,
|
|
|
10665a |
+ unsigned long *sent);
|
|
|
10665a |
+#endif
|
|
|
10665a |
diff --git a/common/tftpsubs.c b/common/tftpsubs.c
|
|
|
10665a |
index 8c999f6..6033e9b 100644
|
|
|
10665a |
--- a/common/tftpsubs.c
|
|
|
10665a |
+++ b/common/tftpsubs.c
|
|
|
10665a |
@@ -404,3 +404,23 @@ char *strip_address(char *addr)
|
|
|
10665a |
return addr;
|
|
|
10665a |
}
|
|
|
10665a |
#endif
|
|
|
10665a |
+
|
|
|
10665a |
+
|
|
|
10665a |
+int str_equal(const char *s1, const char *s2)
|
|
|
10665a |
+{
|
|
|
10665a |
+ return !strcmp(s1, s2);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+
|
|
|
10665a |
+const char *opcode_to_str(unsigned short opcode)
|
|
|
10665a |
+{
|
|
|
10665a |
+ switch(opcode) {
|
|
|
10665a |
+ case RRQ: return "RRQ";
|
|
|
10665a |
+ case WRQ: return "WRQ";
|
|
|
10665a |
+ case DATA: return "DATA";
|
|
|
10665a |
+ case ACK: return "ACK";
|
|
|
10665a |
+ case ERROR: return "ERROR";
|
|
|
10665a |
+ case OACK: return "OACK";
|
|
|
10665a |
+ }
|
|
|
10665a |
+ return "UNKNOWN";
|
|
|
10665a |
+}
|
|
|
10665a |
diff --git a/common/tftpsubs.h b/common/tftpsubs.h
|
|
|
10665a |
index b3a3bf3..311b141 100644
|
|
|
10665a |
--- a/common/tftpsubs.h
|
|
|
10665a |
+++ b/common/tftpsubs.h
|
|
|
10665a |
@@ -38,21 +38,9 @@
|
|
|
10665a |
#ifndef TFTPSUBS_H
|
|
|
10665a |
#define TFTPSUBS_H
|
|
|
10665a |
|
|
|
10665a |
+#include "common.h"
|
|
|
10665a |
#include "config.h"
|
|
|
10665a |
|
|
|
10665a |
-union sock_addr {
|
|
|
10665a |
- struct sockaddr sa;
|
|
|
10665a |
- struct sockaddr_in si;
|
|
|
10665a |
-#ifdef HAVE_IPV6
|
|
|
10665a |
- struct sockaddr_in6 s6;
|
|
|
10665a |
-#endif
|
|
|
10665a |
-};
|
|
|
10665a |
-
|
|
|
10665a |
-#define SOCKLEN(sock) \
|
|
|
10665a |
- (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
|
|
|
10665a |
- (sizeof(struct sockaddr_in)) : \
|
|
|
10665a |
- (sizeof(union sock_addr)))
|
|
|
10665a |
-
|
|
|
10665a |
#ifdef HAVE_IPV6
|
|
|
10665a |
#define SOCKPORT(sock) \
|
|
|
10665a |
(((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
|
|
|
10665a |
diff --git a/tftp/extern.h b/tftp/extern.h
|
|
|
10665a |
index 78474fc..dd0523f 100644
|
|
|
10665a |
--- a/tftp/extern.h
|
|
|
10665a |
+++ b/tftp/extern.h
|
|
|
10665a |
@@ -34,7 +34,7 @@
|
|
|
10665a |
#ifndef RECVFILE_H
|
|
|
10665a |
#define RECVFILE_H
|
|
|
10665a |
|
|
|
10665a |
-void tftp_recvfile(int, const char *, const char *);
|
|
|
10665a |
-void tftp_sendfile(int, const char *, const char *);
|
|
|
10665a |
+void tftp_recvfile(int, const char *, const char *, int);
|
|
|
10665a |
+void tftp_sendfile(int, const char *, const char *, int);
|
|
|
10665a |
|
|
|
10665a |
#endif
|
|
|
10665a |
diff --git a/tftp/main.c b/tftp/main.c
|
|
|
10665a |
index fcf5a25..5a7d3a6 100644
|
|
|
10665a |
--- a/tftp/main.c
|
|
|
10665a |
+++ b/tftp/main.c
|
|
|
10665a |
@@ -49,7 +49,7 @@
|
|
|
10665a |
|
|
|
10665a |
#include "extern.h"
|
|
|
10665a |
|
|
|
10665a |
-#define TIMEOUT 5 /* secs between rexmt's */
|
|
|
10665a |
+#define RTIMEOUT 5 /* secs between rexmt's */
|
|
|
10665a |
#define LBUFLEN 200 /* size of input buffer */
|
|
|
10665a |
|
|
|
10665a |
struct modes {
|
|
|
10665a |
@@ -82,7 +82,7 @@ int ai_fam_sock = AF_INET;
|
|
|
10665a |
union sock_addr peeraddr;
|
|
|
10665a |
int f = -1;
|
|
|
10665a |
u_short port;
|
|
|
10665a |
-int trace;
|
|
|
10665a |
+int trace_opt;
|
|
|
10665a |
int verbose;
|
|
|
10665a |
int literal;
|
|
|
10665a |
int connected;
|
|
|
10665a |
@@ -104,6 +104,7 @@ struct servent *sp;
|
|
|
10665a |
int portrange = 0;
|
|
|
10665a |
unsigned int portrange_from = 0;
|
|
|
10665a |
unsigned int portrange_to = 0;
|
|
|
10665a |
+int windowsize = -1;
|
|
|
10665a |
|
|
|
10665a |
void get(int, char **);
|
|
|
10665a |
void help(int, char **);
|
|
|
10665a |
@@ -191,14 +192,14 @@ char *xstrdup(const char *);
|
|
|
10665a |
|
|
|
10665a |
const char *program;
|
|
|
10665a |
|
|
|
10665a |
-static inline void usage(int errcode)
|
|
|
10665a |
+static void usage(int errcode)
|
|
|
10665a |
{
|
|
|
10665a |
fprintf(stderr,
|
|
|
10665a |
#ifdef HAVE_IPV6
|
|
|
10665a |
- "Usage: %s [-4][-6][-v][-V][-l][-m mode] [-R port:port] "
|
|
|
10665a |
+ "Usage: %s [-4][-6][-v][-V][-l][-m mode][-w size] [-R port:port] "
|
|
|
10665a |
"[host [port]] [-c command]\n",
|
|
|
10665a |
#else
|
|
|
10665a |
- "Usage: %s [-v][-V][-l][-m mode] [-R port:port] "
|
|
|
10665a |
+ "Usage: %s [-v][-V][-l][-m mode][-w size] [-R port:port] "
|
|
|
10665a |
"[host [port]] [-c command]\n",
|
|
|
10665a |
#endif
|
|
|
10665a |
program);
|
|
|
10665a |
@@ -279,6 +280,15 @@ int main(int argc, char *argv[])
|
|
|
10665a |
}
|
|
|
10665a |
portrange = 1;
|
|
|
10665a |
break;
|
|
|
10665a |
+ case 'w':
|
|
|
10665a |
+ if (++arg >= argc)
|
|
|
10665a |
+ usage(EX_USAGE);
|
|
|
10665a |
+ windowsize = atoi(argv[arg]);
|
|
|
10665a |
+ if (windowsize <= 0 || windowsize > 64) {
|
|
|
10665a |
+ fprintf(stderr, "Bad window size: %s (1-64)\n", argv[arg]);
|
|
|
10665a |
+ exit(EX_USAGE);
|
|
|
10665a |
+ }
|
|
|
10665a |
+ break;
|
|
|
10665a |
case 'h':
|
|
|
10665a |
default:
|
|
|
10665a |
usage(*optx == 'h' ? 0 : EX_USAGE);
|
|
|
10665a |
@@ -593,7 +603,7 @@ void put(int argc, char *argv[])
|
|
|
10665a |
printf("putting %s to %s:%s [%s]\n",
|
|
|
10665a |
cp, hostname, targ, mode->m_mode);
|
|
|
10665a |
sa_set_port(&peeraddr, port);
|
|
|
10665a |
- tftp_sendfile(fd, targ, mode->m_mode);
|
|
|
10665a |
+ tftp_sendfile(fd, targ, mode->m_mode, windowsize);
|
|
|
10665a |
return;
|
|
|
10665a |
}
|
|
|
10665a |
/* this assumes the target is a directory */
|
|
|
10665a |
@@ -625,7 +635,7 @@ void put(int argc, char *argv[])
|
|
|
10665a |
printf("putting %s to %s:%s [%s]\n",
|
|
|
10665a |
argv[n], hostname, remote_pth, mode->m_mode);
|
|
|
10665a |
sa_set_port(&peeraddr, port);
|
|
|
10665a |
- tftp_sendfile(fd, remote_pth, mode->m_mode);
|
|
|
10665a |
+ tftp_sendfile(fd, remote_pth, mode->m_mode, windowsize);
|
|
|
10665a |
}
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
@@ -693,7 +703,7 @@ void get(int argc, char *argv[])
|
|
|
10665a |
printf("getting from %s:%s to %s [%s]\n",
|
|
|
10665a |
hostname, src, cp, mode->m_mode);
|
|
|
10665a |
sa_set_port(&peeraddr, port);
|
|
|
10665a |
- tftp_recvfile(fd, src, mode->m_mode);
|
|
|
10665a |
+ tftp_recvfile(fd, src, mode->m_mode, windowsize);
|
|
|
10665a |
break;
|
|
|
10665a |
}
|
|
|
10665a |
cp = tail(src); /* new .. jdg */
|
|
|
10665a |
@@ -708,7 +718,7 @@ void get(int argc, char *argv[])
|
|
|
10665a |
printf("getting from %s:%s to %s [%s]\n",
|
|
|
10665a |
hostname, src, cp, mode->m_mode);
|
|
|
10665a |
sa_set_port(&peeraddr, port);
|
|
|
10665a |
- tftp_recvfile(fd, src, mode->m_mode);
|
|
|
10665a |
+ tftp_recvfile(fd, src, mode->m_mode, windowsize);
|
|
|
10665a |
}
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
@@ -718,7 +728,7 @@ static void getusage(char *s)
|
|
|
10665a |
printf(" %s file file ... file if connected\n", s);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
-int rexmtval = TIMEOUT;
|
|
|
10665a |
+int rexmtval = RTIMEOUT;
|
|
|
10665a |
|
|
|
10665a |
void setrexmt(int argc, char *argv[])
|
|
|
10665a |
{
|
|
|
10665a |
@@ -741,7 +751,7 @@ void setrexmt(int argc, char *argv[])
|
|
|
10665a |
rexmtval = t;
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
-int maxtimeout = 5 * TIMEOUT;
|
|
|
10665a |
+int maxtimeout = 5 * RTIMEOUT;
|
|
|
10665a |
|
|
|
10665a |
void settimeout(int argc, char *argv[])
|
|
|
10665a |
{
|
|
|
10665a |
@@ -781,7 +791,7 @@ void status(int argc, char *argv[])
|
|
|
10665a |
else
|
|
|
10665a |
printf("Not connected.\n");
|
|
|
10665a |
printf("Mode: %s Verbose: %s Tracing: %s Literal: %s\n", mode->m_mode,
|
|
|
10665a |
- verbose ? "on" : "off", trace ? "on" : "off",
|
|
|
10665a |
+ verbose ? "on" : "off", trace_opt ? "on" : "off",
|
|
|
10665a |
literal ? "on" : "off");
|
|
|
10665a |
printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
|
|
|
10665a |
rexmtval, maxtimeout);
|
|
|
10665a |
@@ -969,8 +979,8 @@ void settrace(int argc, char *argv[])
|
|
|
10665a |
(void)argc;
|
|
|
10665a |
(void)argv; /* Quiet unused warning */
|
|
|
10665a |
|
|
|
10665a |
- trace = !trace;
|
|
|
10665a |
- printf("Packet tracing %s.\n", trace ? "on" : "off");
|
|
|
10665a |
+ trace_opt = !trace_opt;
|
|
|
10665a |
+ printf("Packet tracing %s.\n", trace_opt ? "on" : "off");
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
void setverbose(int argc, char *argv[])
|
|
|
10665a |
diff --git a/tftp/tftp.1.in b/tftp/tftp.1.in
|
|
|
10665a |
index b41f7b5..1779a43 100644
|
|
|
10665a |
--- a/tftp/tftp.1.in
|
|
|
10665a |
+++ b/tftp/tftp.1.in
|
|
|
10665a |
@@ -82,6 +82,9 @@ Default to verbose mode.
|
|
|
10665a |
.B \-V
|
|
|
10665a |
Print the version number and configuration to standard output, then
|
|
|
10665a |
exit gracefully.
|
|
|
10665a |
+.TP
|
|
|
10665a |
+.B \-w\fP \fIwindow-size\fP
|
|
|
10665a |
+Set the "windowsize" TFTP option (RFC 7440) to the specified value.
|
|
|
10665a |
.SH COMMANDS
|
|
|
10665a |
Once
|
|
|
10665a |
.B tftp
|
|
|
10665a |
diff --git a/tftp/tftp.c b/tftp/tftp.c
|
|
|
10665a |
index 9d15022..58ea597 100644
|
|
|
10665a |
--- a/tftp/tftp.c
|
|
|
10665a |
+++ b/tftp/tftp.c
|
|
|
10665a |
@@ -31,355 +31,307 @@
|
|
|
10665a |
* SUCH DAMAGE.
|
|
|
10665a |
*/
|
|
|
10665a |
|
|
|
10665a |
+#include <poll.h>
|
|
|
10665a |
+#include <stdarg.h>
|
|
|
10665a |
#include "common/tftpsubs.h"
|
|
|
10665a |
-
|
|
|
10665a |
-/*
|
|
|
10665a |
- * TFTP User Program -- Protocol Machines
|
|
|
10665a |
- */
|
|
|
10665a |
#include "extern.h"
|
|
|
10665a |
|
|
|
10665a |
+/* TODO: This 'peeraddr' global should be removed. */
|
|
|
10665a |
extern union sock_addr peeraddr; /* filled in by main */
|
|
|
10665a |
extern int f; /* the opened socket */
|
|
|
10665a |
-extern int trace;
|
|
|
10665a |
+extern int trace_opt;
|
|
|
10665a |
extern int verbose;
|
|
|
10665a |
-extern int rexmtval;
|
|
|
10665a |
-extern int maxtimeout;
|
|
|
10665a |
-
|
|
|
10665a |
+/* TODO: Adjust when blocksize is implemented. */
|
|
|
10665a |
#define PKTSIZE SEGSIZE+4
|
|
|
10665a |
-char ackbuf[PKTSIZE];
|
|
|
10665a |
-int timeout;
|
|
|
10665a |
-sigjmp_buf toplevel;
|
|
|
10665a |
-sigjmp_buf timeoutbuf;
|
|
|
10665a |
+static char pktbuf[PKTSIZE];
|
|
|
10665a |
|
|
|
10665a |
-static void nak(int, const char *);
|
|
|
10665a |
-static int makerequest(int, const char *, struct tftphdr *, const char *);
|
|
|
10665a |
static void printstats(const char *, unsigned long);
|
|
|
10665a |
static void startclock(void);
|
|
|
10665a |
static void stopclock(void);
|
|
|
10665a |
-static void timer(int);
|
|
|
10665a |
-static void tpacket(const char *, struct tftphdr *, int);
|
|
|
10665a |
+static void timed_out(void)
|
|
|
10665a |
+{
|
|
|
10665a |
+ printf("client: timed out");
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static void die(const char *fmt, ...)
|
|
|
10665a |
+{
|
|
|
10665a |
+ va_list ap;
|
|
|
10665a |
+
|
|
|
10665a |
+ va_start(ap, fmt);
|
|
|
10665a |
+ fprintf(stderr, "fatal: client: ");
|
|
|
10665a |
+ vfprintf(stderr, fmt, ap);
|
|
|
10665a |
+ printf("\n");
|
|
|
10665a |
+ va_end(ap);
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static size_t make_request(unsigned short opcode,
|
|
|
10665a |
+ const char *name,
|
|
|
10665a |
+ const char *mode,
|
|
|
10665a |
+ int blocksize,
|
|
|
10665a |
+ int windowsize,
|
|
|
10665a |
+ struct tftphdr *out)
|
|
|
10665a |
+{
|
|
|
10665a |
+ char *cp, buf[16];
|
|
|
10665a |
+ size_t len;
|
|
|
10665a |
+
|
|
|
10665a |
+ cp = (char *)&(out->th_stuff);
|
|
|
10665a |
+
|
|
|
10665a |
+ out->th_opcode = htons(opcode);
|
|
|
10665a |
+
|
|
|
10665a |
+ len = strlen(name) + 1;
|
|
|
10665a |
+ memcpy(cp, name, len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+
|
|
|
10665a |
+ len = strlen(mode) + 1;
|
|
|
10665a |
+ memcpy(cp, mode, len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+
|
|
|
10665a |
+ /* Don't include options with default values. */
|
|
|
10665a |
+
|
|
|
10665a |
+ /* TODO: TBI in a separate patch. */
|
|
|
10665a |
+ (void) blocksize;
|
|
|
10665a |
+ /*if (blocksize != SEGSIZE) {
|
|
|
10665a |
+ len = strlen("blksize") + 1;
|
|
|
10665a |
+ memcpy(cp, "blksize", len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+ if (snprintf(buf, 16, "%u", blocksize) < 0)
|
|
|
10665a |
+ die("out of memory");
|
|
|
10665a |
+ len = strlen(buf) + 1;
|
|
|
10665a |
+ memcpy(cp, buf, len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+ }*/
|
|
|
10665a |
+
|
|
|
10665a |
+ if (windowsize > 0) {
|
|
|
10665a |
+ len = strlen("windowsize") + 1;
|
|
|
10665a |
+ memcpy(cp, "windowsize", len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+ if (snprintf(buf, 16, "%u", windowsize) < 0)
|
|
|
10665a |
+ die("out of memory");
|
|
|
10665a |
+ len = strlen(buf) + 1;
|
|
|
10665a |
+ memcpy(cp, buf, len);
|
|
|
10665a |
+ cp += len;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ return (cp - (char *)out);
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static void send_request(int sock,
|
|
|
10665a |
+ union sock_addr *to,
|
|
|
10665a |
+ short request,
|
|
|
10665a |
+ const char *name,
|
|
|
10665a |
+ const char *mode,
|
|
|
10665a |
+ unsigned blocksize,
|
|
|
10665a |
+ unsigned windowsize)
|
|
|
10665a |
+{
|
|
|
10665a |
+ struct tftphdr *out;
|
|
|
10665a |
+ size_t size;
|
|
|
10665a |
+
|
|
|
10665a |
+ out = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+ size = make_request(request, name, mode, blocksize, windowsize, out);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (sendto(sock, out, size, 0, &to->sa, SOCKLEN(to)) != (unsigned)size)
|
|
|
10665a |
+ die("send_request: sendto: %m");
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+static int wait_for_oack(int sock, union sock_addr *from, char **options, int *optlen)
|
|
|
10665a |
+{
|
|
|
10665a |
+ unsigned short in_opcode;
|
|
|
10665a |
+ struct tftphdr *in;
|
|
|
10665a |
+ int r;
|
|
|
10665a |
+
|
|
|
10665a |
+ r = recvfrom_with_timeout(sock, pktbuf, sizeof(pktbuf), from, TIMEOUT);
|
|
|
10665a |
+ if (r == 0)
|
|
|
10665a |
+ return r;
|
|
|
10665a |
+
|
|
|
10665a |
+ in = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+ in_opcode = ntohs(in->th_opcode);
|
|
|
10665a |
+
|
|
|
10665a |
+ if (in_opcode == ERROR)
|
|
|
10665a |
+ die_on_error(in);
|
|
|
10665a |
+ if (in_opcode != OACK)
|
|
|
10665a |
+ return -in_opcode;
|
|
|
10665a |
+
|
|
|
10665a |
+ *options = pktbuf + 2;
|
|
|
10665a |
+ *optlen = r - 2;
|
|
|
10665a |
+
|
|
|
10665a |
+ return 1;
|
|
|
10665a |
+}
|
|
|
10665a |
|
|
|
10665a |
/*
|
|
|
10665a |
* Send the requested file.
|
|
|
10665a |
*/
|
|
|
10665a |
-void tftp_sendfile(int fd, const char *name, const char *mode)
|
|
|
10665a |
+void tftp_sendfile(int fd, const char *name, const char *mode, int windowsize)
|
|
|
10665a |
{
|
|
|
10665a |
- struct tftphdr *ap; /* data and ack packets */
|
|
|
10665a |
- struct tftphdr *dp;
|
|
|
10665a |
- int n;
|
|
|
10665a |
- volatile int is_request;
|
|
|
10665a |
- volatile u_short block;
|
|
|
10665a |
- volatile int size, convert;
|
|
|
10665a |
- volatile off_t amount;
|
|
|
10665a |
- union sock_addr from;
|
|
|
10665a |
- socklen_t fromlen;
|
|
|
10665a |
- FILE *file;
|
|
|
10665a |
- u_short ap_opcode, ap_block;
|
|
|
10665a |
-
|
|
|
10665a |
- startclock(); /* start stat's clock */
|
|
|
10665a |
- dp = r_init(); /* reset fillbuf/read-ahead code */
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- convert = !strcmp(mode, "netascii");
|
|
|
10665a |
- file = fdopen(fd, convert ? "rt" : "rb");
|
|
|
10665a |
- block = 0;
|
|
|
10665a |
- is_request = 1; /* First packet is the actual WRQ */
|
|
|
10665a |
- amount = 0;
|
|
|
10665a |
-
|
|
|
10665a |
- bsd_signal(SIGALRM, timer);
|
|
|
10665a |
+ union sock_addr server = peeraddr;
|
|
|
10665a |
+ unsigned long amount = 0;
|
|
|
10665a |
+ int blocksize = SEGSIZE;
|
|
|
10665a |
+ char *options;
|
|
|
10665a |
+ int optlen;
|
|
|
10665a |
+ int retries;
|
|
|
10665a |
+ FILE *fp;
|
|
|
10665a |
+ int n, r;
|
|
|
10665a |
+
|
|
|
10665a |
+ set_verbose(trace_opt + verbose);
|
|
|
10665a |
+
|
|
|
10665a |
+ startclock();
|
|
|
10665a |
+ send_request(f, &server, WRQ, name, mode, blocksize, windowsize);
|
|
|
10665a |
+
|
|
|
10665a |
+ /* If no windowsize was specified on the command line,
|
|
|
10665a |
+ * don't bother with options.
|
|
|
10665a |
+ * When blocksize is supported, this should actually only be called
|
|
|
10665a |
+ * if no options were sent in the RRQ.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ if (windowsize < 0)
|
|
|
10665a |
+ goto no_options;
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
do {
|
|
|
10665a |
- if (is_request) {
|
|
|
10665a |
- size = makerequest(WRQ, name, dp, mode) - 4;
|
|
|
10665a |
- } else {
|
|
|
10665a |
- /* size = read(fd, dp->th_data, SEGSIZE); */
|
|
|
10665a |
- size = readit(file, &dp, convert);
|
|
|
10665a |
- if (size < 0) {
|
|
|
10665a |
- nak(errno + 100, NULL);
|
|
|
10665a |
- break;
|
|
|
10665a |
- }
|
|
|
10665a |
- dp->th_opcode = htons((u_short) DATA);
|
|
|
10665a |
- dp->th_block = htons((u_short) block);
|
|
|
10665a |
+ r = wait_for_oack(f, &server, &options, &optlen);
|
|
|
10665a |
+ if (r < 0) {
|
|
|
10665a |
+ return;
|
|
|
10665a |
}
|
|
|
10665a |
- timeout = 0;
|
|
|
10665a |
- (void)sigsetjmp(timeoutbuf, 1);
|
|
|
10665a |
-
|
|
|
10665a |
- if (trace)
|
|
|
10665a |
- tpacket("sent", dp, size + 4);
|
|
|
10665a |
- n = sendto(f, dp, size + 4, 0,
|
|
|
10665a |
- &peeraddr.sa, SOCKLEN(&peeraddr));
|
|
|
10665a |
- if (n != size + 4) {
|
|
|
10665a |
- perror("tftp: sendto");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- read_ahead(file, convert);
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- alarm(rexmtval);
|
|
|
10665a |
- do {
|
|
|
10665a |
- fromlen = sizeof(from);
|
|
|
10665a |
- n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
|
|
|
10665a |
- &from.sa, &fromlen);
|
|
|
10665a |
- } while (n <= 0);
|
|
|
10665a |
- alarm(0);
|
|
|
10665a |
- if (n < 0) {
|
|
|
10665a |
- perror("tftp: recvfrom");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */
|
|
|
10665a |
- if (trace)
|
|
|
10665a |
- tpacket("received", ap, n);
|
|
|
10665a |
- /* should verify packet came from server */
|
|
|
10665a |
- ap_opcode = ntohs((u_short) ap->th_opcode);
|
|
|
10665a |
- ap_block = ntohs((u_short) ap->th_block);
|
|
|
10665a |
- if (ap_opcode == ERROR) {
|
|
|
10665a |
- printf("Error code %d: %s\n", ap_block, ap->th_msg);
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- if (ap_opcode == ACK) {
|
|
|
10665a |
- int j;
|
|
|
10665a |
|
|
|
10665a |
- if (ap_block == block) {
|
|
|
10665a |
- break;
|
|
|
10665a |
+ /* Parse returned options. */
|
|
|
10665a |
+ n = 0;
|
|
|
10665a |
+ if (r != 0) {
|
|
|
10665a |
+ char *opt, *val;
|
|
|
10665a |
+ int got_ws = 0;
|
|
|
10665a |
+ int v;
|
|
|
10665a |
+
|
|
|
10665a |
+ while (n < optlen) {
|
|
|
10665a |
+ opt = options + n;
|
|
|
10665a |
+ n += strlen(opt) + 1;
|
|
|
10665a |
+ val = options + n;
|
|
|
10665a |
+ if (str_equal(opt, "windowsize") && windowsize != 1) {
|
|
|
10665a |
+ v = atoi(val);
|
|
|
10665a |
+ if (v != windowsize)
|
|
|
10665a |
+ printf("client: server negotiated different windowsize: %d", v);
|
|
|
10665a |
+ /* Assumes v > 0, it probably shouldn't. */
|
|
|
10665a |
+ windowsize = v;
|
|
|
10665a |
+ got_ws = 1;
|
|
|
10665a |
}
|
|
|
10665a |
- /* On an error, try to synchronize
|
|
|
10665a |
- * both sides.
|
|
|
10665a |
- */
|
|
|
10665a |
- j = synchnet(f);
|
|
|
10665a |
- if (j && trace) {
|
|
|
10665a |
- printf("discarded %d packets\n", j);
|
|
|
10665a |
- }
|
|
|
10665a |
- /*
|
|
|
10665a |
- * RFC1129/RFC1350: We MUST NOT re-send the DATA
|
|
|
10665a |
- * packet in response to an invalid ACK. Doing so
|
|
|
10665a |
- * would cause the Sorcerer's Apprentice bug.
|
|
|
10665a |
- */
|
|
|
10665a |
+ n += strlen(val) + 1;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ if (got_ws == 0 && windowsize != 1) {
|
|
|
10665a |
+ windowsize = 1;
|
|
|
10665a |
+ printf("client: server didn't negotiate windowsize, continuing with windowsize=1");
|
|
|
10665a |
}
|
|
|
10665a |
}
|
|
|
10665a |
- if (!is_request)
|
|
|
10665a |
- amount += size;
|
|
|
10665a |
- is_request = 0;
|
|
|
10665a |
- block++;
|
|
|
10665a |
- } while (size == SEGSIZE || block == 1);
|
|
|
10665a |
- abort:
|
|
|
10665a |
- fclose(file);
|
|
|
10665a |
+ } while (r == 0 && --retries > 0);
|
|
|
10665a |
+ if (retries <= 0)
|
|
|
10665a |
+ timed_out();
|
|
|
10665a |
+
|
|
|
10665a |
+no_options:
|
|
|
10665a |
+ if (windowsize < 0) {
|
|
|
10665a |
+ struct tftphdr *tp = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ r = recvfrom_with_timeout(f, pktbuf, PKTSIZE, &server, TIMEOUT);
|
|
|
10665a |
+ if (r == 0) {
|
|
|
10665a |
+ /* Timed out. */
|
|
|
10665a |
+ continue;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ if (ntohs(tp->th_opcode) == ACK && ntohs(tp->th_block) == 0) {
|
|
|
10665a |
+ break;
|
|
|
10665a |
+ } else if (ntohs(tp->th_opcode) == ERROR) {
|
|
|
10665a |
+ die_on_error(tp);
|
|
|
10665a |
+ }
|
|
|
10665a |
+ } while (r == 0 && --retries > 0);
|
|
|
10665a |
+ if (retries <= 0)
|
|
|
10665a |
+ timed_out();
|
|
|
10665a |
+ }
|
|
|
10665a |
+ fp = fdopen(fd, "r");
|
|
|
10665a |
+ r = sender(f, &server, blocksize, windowsize, TIMEOUT, 0, fp, &amount);
|
|
|
10665a |
+ if (r < 0)
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+
|
|
|
10665a |
stopclock();
|
|
|
10665a |
if (amount > 0)
|
|
|
10665a |
printstats("Sent", amount);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
+
|
|
|
10665a |
/*
|
|
|
10665a |
* Receive a file.
|
|
|
10665a |
*/
|
|
|
10665a |
-void tftp_recvfile(int fd, const char *name, const char *mode)
|
|
|
10665a |
+void tftp_recvfile(int fd, const char *name, const char *mode, int windowsize)
|
|
|
10665a |
{
|
|
|
10665a |
- struct tftphdr *ap;
|
|
|
10665a |
- struct tftphdr *dp;
|
|
|
10665a |
- int n;
|
|
|
10665a |
- volatile u_short block;
|
|
|
10665a |
- volatile int size, firsttrip;
|
|
|
10665a |
- volatile unsigned long amount;
|
|
|
10665a |
- union sock_addr from;
|
|
|
10665a |
- socklen_t fromlen;
|
|
|
10665a |
- FILE *file;
|
|
|
10665a |
- volatile int convert; /* true if converting crlf -> lf */
|
|
|
10665a |
- u_short dp_opcode, dp_block;
|
|
|
10665a |
+ union sock_addr server = peeraddr;
|
|
|
10665a |
+ unsigned long amount = 0;
|
|
|
10665a |
+ int blocksize = SEGSIZE;
|
|
|
10665a |
+ char *options, error[ERROR_MAXLEN];
|
|
|
10665a |
+ int optlen;
|
|
|
10665a |
+ int retries;
|
|
|
10665a |
+ FILE *fp;
|
|
|
10665a |
+ int n, r;
|
|
|
10665a |
+
|
|
|
10665a |
+ set_verbose(trace_opt + verbose);
|
|
|
10665a |
|
|
|
10665a |
startclock();
|
|
|
10665a |
- dp = w_init();
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- convert = !strcmp(mode, "netascii");
|
|
|
10665a |
- file = fdopen(fd, convert ? "wt" : "wb");
|
|
|
10665a |
- block = 1;
|
|
|
10665a |
- firsttrip = 1;
|
|
|
10665a |
- amount = 0;
|
|
|
10665a |
-
|
|
|
10665a |
- bsd_signal(SIGALRM, timer);
|
|
|
10665a |
+
|
|
|
10665a |
+ send_request(f, &server, RRQ, name, mode, blocksize, windowsize);
|
|
|
10665a |
+ /* If no windowsize was specified on the command line,
|
|
|
10665a |
+ * don't bother with options.
|
|
|
10665a |
+ * When blocksize is supported, this should actually only be called
|
|
|
10665a |
+ * if no options were sent in the RRQ.
|
|
|
10665a |
+ */
|
|
|
10665a |
+ if (windowsize < 0)
|
|
|
10665a |
+ goto no_options;
|
|
|
10665a |
+ retries = RETRIES;
|
|
|
10665a |
do {
|
|
|
10665a |
- if (firsttrip) {
|
|
|
10665a |
- size = makerequest(RRQ, name, ap, mode);
|
|
|
10665a |
- firsttrip = 0;
|
|
|
10665a |
- } else {
|
|
|
10665a |
- ap->th_opcode = htons((u_short) ACK);
|
|
|
10665a |
- ap->th_block = htons((u_short) block);
|
|
|
10665a |
- size = 4;
|
|
|
10665a |
- block++;
|
|
|
10665a |
- }
|
|
|
10665a |
- timeout = 0;
|
|
|
10665a |
- (void)sigsetjmp(timeoutbuf, 1);
|
|
|
10665a |
- send_ack:
|
|
|
10665a |
- if (trace)
|
|
|
10665a |
- tpacket("sent", ap, size);
|
|
|
10665a |
- if (sendto(f, ackbuf, size, 0, &peeraddr.sa,
|
|
|
10665a |
- SOCKLEN(&peeraddr)) != size) {
|
|
|
10665a |
- alarm(0);
|
|
|
10665a |
- perror("tftp: sendto");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
+ r = wait_for_oack(f, &server, &options, &optlen);
|
|
|
10665a |
+ if (r < 0) {
|
|
|
10665a |
+ return;
|
|
|
10665a |
}
|
|
|
10665a |
- write_behind(file, convert);
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- alarm(rexmtval);
|
|
|
10665a |
- do {
|
|
|
10665a |
- fromlen = sizeof(from);
|
|
|
10665a |
- n = recvfrom(f, dp, PKTSIZE, 0,
|
|
|
10665a |
- &from.sa, &fromlen);
|
|
|
10665a |
- } while (n <= 0);
|
|
|
10665a |
- alarm(0);
|
|
|
10665a |
- if (n < 0) {
|
|
|
10665a |
- perror("tftp: recvfrom");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */
|
|
|
10665a |
- if (trace)
|
|
|
10665a |
- tpacket("received", dp, n);
|
|
|
10665a |
- /* should verify client address */
|
|
|
10665a |
- dp_opcode = ntohs((u_short) dp->th_opcode);
|
|
|
10665a |
- dp_block = ntohs((u_short) dp->th_block);
|
|
|
10665a |
- if (dp_opcode == ERROR) {
|
|
|
10665a |
- printf("Error code %d: %s\n", dp_block, dp->th_msg);
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- if (dp_opcode == DATA) {
|
|
|
10665a |
- int j;
|
|
|
10665a |
|
|
|
10665a |
- if (dp_block == block) {
|
|
|
10665a |
- break; /* have next packet */
|
|
|
10665a |
- }
|
|
|
10665a |
- /* On an error, try to synchronize
|
|
|
10665a |
- * both sides.
|
|
|
10665a |
- */
|
|
|
10665a |
- j = synchnet(f);
|
|
|
10665a |
- if (j && trace) {
|
|
|
10665a |
- printf("discarded %d packets\n", j);
|
|
|
10665a |
- }
|
|
|
10665a |
- if (dp_block == (block - 1)) {
|
|
|
10665a |
- goto send_ack; /* resend ack */
|
|
|
10665a |
+ /* Parse returned options. */
|
|
|
10665a |
+ n = 0;
|
|
|
10665a |
+ if (r != 0) {
|
|
|
10665a |
+ char *opt, *val;
|
|
|
10665a |
+ int got_ws = 0;
|
|
|
10665a |
+ int v;
|
|
|
10665a |
+
|
|
|
10665a |
+ while (n < optlen) {
|
|
|
10665a |
+ opt = options + n;
|
|
|
10665a |
+ n += strlen(opt) + 1;
|
|
|
10665a |
+ val = options + n;
|
|
|
10665a |
+ if (str_equal(opt, "windowsize") && windowsize != 1) {
|
|
|
10665a |
+ v = atoi(val);
|
|
|
10665a |
+ if (v != windowsize)
|
|
|
10665a |
+ printf("client: server negotiated different windowsize: %d", v);
|
|
|
10665a |
+ /* Assumes v > 0, it probably shouldn't. */
|
|
|
10665a |
+ windowsize = v;
|
|
|
10665a |
+ got_ws = 1;
|
|
|
10665a |
}
|
|
|
10665a |
+ n += strlen(val) + 1;
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ if (got_ws == 0 && windowsize != 1) {
|
|
|
10665a |
+ windowsize = 1;
|
|
|
10665a |
+ printf("client: server didn't negotiate windowsize, continuing with windowsize=1");
|
|
|
10665a |
}
|
|
|
10665a |
}
|
|
|
10665a |
- /* size = write(fd, dp->th_data, n - 4); */
|
|
|
10665a |
- size = writeit(file, &dp, n - 4, convert);
|
|
|
10665a |
- if (size < 0) {
|
|
|
10665a |
- nak(errno + 100, NULL);
|
|
|
10665a |
- break;
|
|
|
10665a |
- }
|
|
|
10665a |
- amount += size;
|
|
|
10665a |
- } while (size == SEGSIZE);
|
|
|
10665a |
- abort: /* ok to ack, since user */
|
|
|
10665a |
- ap->th_opcode = htons((u_short) ACK); /* has seen err msg */
|
|
|
10665a |
- ap->th_block = htons((u_short) block);
|
|
|
10665a |
- (void)sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
|
|
|
10665a |
- SOCKLEN(&peeraddr));
|
|
|
10665a |
- write_behind(file, convert); /* flush last buffer */
|
|
|
10665a |
- fclose(file);
|
|
|
10665a |
+ } while (r == 0 && --retries > 0);
|
|
|
10665a |
+ if (retries <= 0)
|
|
|
10665a |
+ timed_out();
|
|
|
10665a |
+
|
|
|
10665a |
+ send_ack(f, &server, 0);
|
|
|
10665a |
+
|
|
|
10665a |
+no_options:
|
|
|
10665a |
+ fp = fdopen(fd, "w");
|
|
|
10665a |
+ r = receiver(f, &server, blocksize, windowsize, TIMEOUT, fp, &amount, error);
|
|
|
10665a |
+ if (r < 0) {
|
|
|
10665a |
+ fprintf(stderr, "%s\n", error);
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
stopclock();
|
|
|
10665a |
if (amount > 0)
|
|
|
10665a |
printstats("Received", amount);
|
|
|
10665a |
-}
|
|
|
10665a |
-
|
|
|
10665a |
-static int
|
|
|
10665a |
-makerequest(int request, const char *name,
|
|
|
10665a |
- struct tftphdr *tp, const char *mode)
|
|
|
10665a |
-{
|
|
|
10665a |
- char *cp;
|
|
|
10665a |
- size_t len;
|
|
|
10665a |
-
|
|
|
10665a |
- tp->th_opcode = htons((u_short) request);
|
|
|
10665a |
- cp = (char *)&(tp->th_stuff);
|
|
|
10665a |
- len = strlen(name) + 1;
|
|
|
10665a |
- memcpy(cp, name, len);
|
|
|
10665a |
- cp += len;
|
|
|
10665a |
- len = strlen(mode) + 1;
|
|
|
10665a |
- memcpy(cp, mode, len);
|
|
|
10665a |
- cp += len;
|
|
|
10665a |
- return (cp - (char *)tp);
|
|
|
10665a |
-}
|
|
|
10665a |
-
|
|
|
10665a |
-static const char *const errmsgs[] = {
|
|
|
10665a |
- "Undefined error code", /* 0 - EUNDEF */
|
|
|
10665a |
- "File not found", /* 1 - ENOTFOUND */
|
|
|
10665a |
- "Access denied", /* 2 - EACCESS */
|
|
|
10665a |
- "Disk full or allocation exceeded", /* 3 - ENOSPACE */
|
|
|
10665a |
- "Illegal TFTP operation", /* 4 - EBADOP */
|
|
|
10665a |
- "Unknown transfer ID", /* 5 - EBADID */
|
|
|
10665a |
- "File already exists", /* 6 - EEXISTS */
|
|
|
10665a |
- "No such user", /* 7 - ENOUSER */
|
|
|
10665a |
- "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */
|
|
|
10665a |
-};
|
|
|
10665a |
-
|
|
|
10665a |
-#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *))
|
|
|
10665a |
-
|
|
|
10665a |
-/*
|
|
|
10665a |
- * Send a nak packet (error message).
|
|
|
10665a |
- * Error code passed in is one of the
|
|
|
10665a |
- * standard TFTP codes, or a UNIX errno
|
|
|
10665a |
- * offset by 100.
|
|
|
10665a |
- */
|
|
|
10665a |
-static void nak(int error, const char *msg)
|
|
|
10665a |
-{
|
|
|
10665a |
- struct tftphdr *tp;
|
|
|
10665a |
- int length;
|
|
|
10665a |
-
|
|
|
10665a |
- tp = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- tp->th_opcode = htons((u_short) ERROR);
|
|
|
10665a |
- tp->th_code = htons((u_short) error);
|
|
|
10665a |
-
|
|
|
10665a |
- if (error >= 100) {
|
|
|
10665a |
- /* This is a Unix errno+100 */
|
|
|
10665a |
- if (!msg)
|
|
|
10665a |
- msg = strerror(error - 100);
|
|
|
10665a |
- error = EUNDEF;
|
|
|
10665a |
- } else {
|
|
|
10665a |
- if ((unsigned)error >= ERR_CNT)
|
|
|
10665a |
- error = EUNDEF;
|
|
|
10665a |
-
|
|
|
10665a |
- if (!msg)
|
|
|
10665a |
- msg = errmsgs[error];
|
|
|
10665a |
- }
|
|
|
10665a |
-
|
|
|
10665a |
- tp->th_code = htons((u_short) error);
|
|
|
10665a |
-
|
|
|
10665a |
- length = strlen(msg) + 1;
|
|
|
10665a |
- memcpy(tp->th_msg, msg, length);
|
|
|
10665a |
- length += 4; /* Add space for header */
|
|
|
10665a |
-
|
|
|
10665a |
- if (trace)
|
|
|
10665a |
- tpacket("sent", tp, length);
|
|
|
10665a |
- if (sendto(f, ackbuf, length, 0, &peeraddr.sa,
|
|
|
10665a |
- SOCKLEN(&peeraddr)) != length)
|
|
|
10665a |
- perror("nak");
|
|
|
10665a |
-}
|
|
|
10665a |
-
|
|
|
10665a |
-static void tpacket(const char *s, struct tftphdr *tp, int n)
|
|
|
10665a |
-{
|
|
|
10665a |
- static const char *opcodes[] =
|
|
|
10665a |
- { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
|
|
|
10665a |
- char *cp, *file;
|
|
|
10665a |
- u_short op = ntohs((u_short) tp->th_opcode);
|
|
|
10665a |
-
|
|
|
10665a |
- if (op < RRQ || op > ERROR)
|
|
|
10665a |
- printf("%s opcode=%x ", s, op);
|
|
|
10665a |
- else
|
|
|
10665a |
- printf("%s %s ", s, opcodes[op]);
|
|
|
10665a |
- switch (op) {
|
|
|
10665a |
-
|
|
|
10665a |
- case RRQ:
|
|
|
10665a |
- case WRQ:
|
|
|
10665a |
- n -= 2;
|
|
|
10665a |
- file = cp = (char *)&(tp->th_stuff);
|
|
|
10665a |
- cp = strchr(cp, '\0');
|
|
|
10665a |
- printf("<file=%s, mode=%s>\n", file, cp + 1);
|
|
|
10665a |
- break;
|
|
|
10665a |
-
|
|
|
10665a |
- case DATA:
|
|
|
10665a |
- printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
|
|
|
10665a |
- break;
|
|
|
10665a |
-
|
|
|
10665a |
- case ACK:
|
|
|
10665a |
- printf("<block=%d>\n", ntohs(tp->th_block));
|
|
|
10665a |
- break;
|
|
|
10665a |
-
|
|
|
10665a |
- case ERROR:
|
|
|
10665a |
- printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
|
|
|
10665a |
- break;
|
|
|
10665a |
- }
|
|
|
10665a |
+ fclose(fp);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
struct timeval tstart;
|
|
|
10665a |
@@ -404,23 +356,9 @@ static void printstats(const char *direction, unsigned long amount)
|
|
|
10665a |
(tstart.tv_sec + (tstart.tv_usec / 1000000.0));
|
|
|
10665a |
if (verbose) {
|
|
|
10665a |
printf("%s %lu bytes in %.1f seconds", direction, amount, delta);
|
|
|
10665a |
+ /* TODO: Change the statistics in a separate patch (bits???)! */
|
|
|
10665a |
printf(" [%.0f bit/s]", (amount * 8.) / delta);
|
|
|
10665a |
putchar('\n');
|
|
|
10665a |
}
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
-static void timer(int sig)
|
|
|
10665a |
-{
|
|
|
10665a |
- int save_errno = errno;
|
|
|
10665a |
-
|
|
|
10665a |
- (void)sig; /* Shut up unused warning */
|
|
|
10665a |
-
|
|
|
10665a |
- timeout += rexmtval;
|
|
|
10665a |
- if (timeout >= maxtimeout) {
|
|
|
10665a |
- printf("Transfer timed out.\n");
|
|
|
10665a |
- errno = save_errno;
|
|
|
10665a |
- siglongjmp(toplevel, -1);
|
|
|
10665a |
- }
|
|
|
10665a |
- errno = save_errno;
|
|
|
10665a |
- siglongjmp(timeoutbuf, 1);
|
|
|
10665a |
-}
|
|
|
10665a |
diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in
|
|
|
10665a |
index 321b8c6..46384d6 100644
|
|
|
10665a |
--- a/tftpd/tftpd.8.in
|
|
|
10665a |
+++ b/tftpd/tftpd.8.in
|
|
|
10665a |
@@ -227,6 +227,11 @@ Set the time before the server retransmits a packet, in microseconds.
|
|
|
10665a |
\fBrollover\fP (nonstandard)
|
|
|
10665a |
Set the block number to resume at after a block number rollover. The
|
|
|
10665a |
default and recommended value is zero.
|
|
|
10665a |
+.TP
|
|
|
10665a |
+\fBwindowsize\fP (RFC 7440)
|
|
|
10665a |
+Set the windowsize to a number of blocks that should be sent before
|
|
|
10665a |
+expecting an ack. The default is 1, which means the same functionality
|
|
|
10665a |
+as if windowsize wasn't used. Maximum is 64.
|
|
|
10665a |
.PP
|
|
|
10665a |
The
|
|
|
10665a |
.B \-\-refuse
|
|
|
10665a |
@@ -408,6 +413,9 @@ RFC 2348,
|
|
|
10665a |
.br
|
|
|
10665a |
RFC 2349,
|
|
|
10665a |
.IR "TFTP Timeout Interval and Transfer Size Options" .
|
|
|
10665a |
+.br
|
|
|
10665a |
+RFC 7440,
|
|
|
10665a |
+.IR "TFTP Windowsize Option" .
|
|
|
10665a |
.SH "AUTHOR"
|
|
|
10665a |
This version of
|
|
|
10665a |
.B tftpd
|
|
|
10665a |
diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c
|
|
|
10665a |
index b7a5a95..98adbc1 100644
|
|
|
10665a |
--- a/tftpd/tftpd.c
|
|
|
10665a |
+++ b/tftpd/tftpd.c
|
|
|
10665a |
@@ -48,6 +48,8 @@
|
|
|
10665a |
#include <pwd.h>
|
|
|
10665a |
#include <limits.h>
|
|
|
10665a |
#include <syslog.h>
|
|
|
10665a |
+#include <poll.h>
|
|
|
10665a |
+#include <stdarg.h>
|
|
|
10665a |
|
|
|
10665a |
#include "common/tftpsubs.h"
|
|
|
10665a |
#include "recvfrom.h"
|
|
|
10665a |
@@ -72,23 +74,16 @@ static int ai_fam = AF_UNSPEC;
|
|
|
10665a |
static int ai_fam = AF_INET;
|
|
|
10665a |
#endif
|
|
|
10665a |
|
|
|
10665a |
-#define TIMEOUT 1000000 /* Default timeout (us) */
|
|
|
10665a |
-#define TRIES 6 /* Number of attempts to send each packet */
|
|
|
10665a |
-#define TIMEOUT_LIMIT ((1 << TRIES)-1)
|
|
|
10665a |
-
|
|
|
10665a |
const char *__progname;
|
|
|
10665a |
static int peer;
|
|
|
10665a |
-static unsigned long timeout = TIMEOUT; /* Current timeout value */
|
|
|
10665a |
-static unsigned long rexmtval = TIMEOUT; /* Basic timeout value */
|
|
|
10665a |
-static unsigned long maxtimeout = TIMEOUT_LIMIT * TIMEOUT;
|
|
|
10665a |
-static int timeout_quit = 0;
|
|
|
10665a |
-static sigjmp_buf timeoutbuf;
|
|
|
10665a |
static uint16_t rollover_val = 0;
|
|
|
10665a |
+static int windowsize = 1;
|
|
|
10665a |
|
|
|
10665a |
#define PKTSIZE MAX_SEGSIZE+4
|
|
|
10665a |
static char buf[PKTSIZE];
|
|
|
10665a |
-static char ackbuf[PKTSIZE];
|
|
|
10665a |
+static char pktbuf[PKTSIZE];
|
|
|
10665a |
static unsigned int max_blksize = MAX_SEGSIZE;
|
|
|
10665a |
+#define MAX_WINDOWSIZE 64
|
|
|
10665a |
|
|
|
10665a |
static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p;
|
|
|
10665a |
|
|
|
10665a |
@@ -113,8 +108,7 @@ static struct rule *rewrite_rules = NULL;
|
|
|
10665a |
#endif
|
|
|
10665a |
|
|
|
10665a |
int tftp(struct tftphdr *, int);
|
|
|
10665a |
-static void nak(int, const char *);
|
|
|
10665a |
-static void timer(int);
|
|
|
10665a |
+static void nak(int error, const char *msg);
|
|
|
10665a |
static void do_opt(const char *, const char *, char **);
|
|
|
10665a |
|
|
|
10665a |
static int set_blksize(uintmax_t *);
|
|
|
10665a |
@@ -123,6 +117,9 @@ static int set_tsize(uintmax_t *);
|
|
|
10665a |
static int set_timeout(uintmax_t *);
|
|
|
10665a |
static int set_utimeout(uintmax_t *);
|
|
|
10665a |
static int set_rollover(uintmax_t *);
|
|
|
10665a |
+static int set_windowsize(uintmax_t *);
|
|
|
10665a |
+
|
|
|
10665a |
+int g_timeout = 1000; /* ms */
|
|
|
10665a |
|
|
|
10665a |
struct options {
|
|
|
10665a |
const char *o_opt;
|
|
|
10665a |
@@ -134,6 +131,7 @@ struct options {
|
|
|
10665a |
{"timeout", set_timeout},
|
|
|
10665a |
{"utimeout", set_utimeout},
|
|
|
10665a |
{"rollover", set_rollover},
|
|
|
10665a |
+ {"windowsize", set_windowsize},
|
|
|
10665a |
{NULL, NULL}
|
|
|
10665a |
};
|
|
|
10665a |
|
|
|
10665a |
@@ -152,16 +150,6 @@ static void handle_exit(int sig)
|
|
|
10665a |
exit_signal = sig;
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
-/* Handle timeout signal or timeout event */
|
|
|
10665a |
-void timer(int sig)
|
|
|
10665a |
-{
|
|
|
10665a |
- (void)sig; /* Suppress unused warning */
|
|
|
10665a |
- timeout <<= 1;
|
|
|
10665a |
- if (timeout >= maxtimeout || timeout_quit)
|
|
|
10665a |
- exit(0);
|
|
|
10665a |
- siglongjmp(timeoutbuf, 1);
|
|
|
10665a |
-}
|
|
|
10665a |
-
|
|
|
10665a |
#ifdef WITH_REGEX
|
|
|
10665a |
static struct rule *read_remap_rules(const char *file)
|
|
|
10665a |
{
|
|
|
10665a |
@@ -229,64 +217,6 @@ static void pmtu_discovery_off(int fd)
|
|
|
10665a |
#endif
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
-/*
|
|
|
10665a |
- * Receive packet with synchronous timeout; timeout is adjusted
|
|
|
10665a |
- * to account for time spent waiting.
|
|
|
10665a |
- */
|
|
|
10665a |
-static int recv_time(int s, void *rbuf, int len, unsigned int flags,
|
|
|
10665a |
- unsigned long *timeout_us_p)
|
|
|
10665a |
-{
|
|
|
10665a |
- fd_set fdset;
|
|
|
10665a |
- struct timeval tmv, t0, t1;
|
|
|
10665a |
- int rv, err;
|
|
|
10665a |
- unsigned long timeout_us = *timeout_us_p;
|
|
|
10665a |
- unsigned long timeout_left, dt;
|
|
|
10665a |
-
|
|
|
10665a |
- gettimeofday(&t0, NULL);
|
|
|
10665a |
- timeout_left = timeout_us;
|
|
|
10665a |
-
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- FD_ZERO(&fdset);
|
|
|
10665a |
- FD_SET(s, &fdset);
|
|
|
10665a |
-
|
|
|
10665a |
- do {
|
|
|
10665a |
- tmv.tv_sec = timeout_left / 1000000;
|
|
|
10665a |
- tmv.tv_usec = timeout_left % 1000000;
|
|
|
10665a |
-
|
|
|
10665a |
- rv = select(s + 1, &fdset, NULL, NULL, &tmv);
|
|
|
10665a |
- err = errno;
|
|
|
10665a |
-
|
|
|
10665a |
- gettimeofday(&t1, NULL);
|
|
|
10665a |
-
|
|
|
10665a |
- dt = (t1.tv_sec - t0.tv_sec) * 1000000 +
|
|
|
10665a |
- (t1.tv_usec - t0.tv_usec);
|
|
|
10665a |
- *timeout_us_p = timeout_left =
|
|
|
10665a |
- (dt >= timeout_us) ? 1 : (timeout_us - dt);
|
|
|
10665a |
- } while (rv == -1 && err == EINTR);
|
|
|
10665a |
-
|
|
|
10665a |
- if (rv == 0) {
|
|
|
10665a |
- timer(0); /* Should not return */
|
|
|
10665a |
- return -1;
|
|
|
10665a |
- }
|
|
|
10665a |
-
|
|
|
10665a |
- set_socket_nonblock(s, 1);
|
|
|
10665a |
- rv = recv(s, rbuf, len, flags);
|
|
|
10665a |
- err = errno;
|
|
|
10665a |
- set_socket_nonblock(s, 0);
|
|
|
10665a |
-
|
|
|
10665a |
- if (rv < 0) {
|
|
|
10665a |
- if (E_WOULD_BLOCK(err) || err == EINTR) {
|
|
|
10665a |
- continue; /* Once again, with feeling... */
|
|
|
10665a |
- } else {
|
|
|
10665a |
- errno = err;
|
|
|
10665a |
- return rv;
|
|
|
10665a |
- }
|
|
|
10665a |
- } else {
|
|
|
10665a |
- return rv;
|
|
|
10665a |
- }
|
|
|
10665a |
- }
|
|
|
10665a |
-}
|
|
|
10665a |
-
|
|
|
10665a |
static int split_port(char **ap, char **pp)
|
|
|
10665a |
{
|
|
|
10665a |
char *a, *p;
|
|
|
10665a |
@@ -325,7 +255,7 @@ static int split_port(char **ap, char **pp)
|
|
|
10665a |
enum long_only_options {
|
|
|
10665a |
OPT_VERBOSITY = 256,
|
|
|
10665a |
};
|
|
|
10665a |
-
|
|
|
10665a |
+
|
|
|
10665a |
static struct option long_options[] = {
|
|
|
10665a |
{ "ipv4", 0, NULL, '4' },
|
|
|
10665a |
{ "ipv6", 0, NULL, '6' },
|
|
|
10665a |
@@ -389,7 +319,7 @@ int main(int argc, char **argv)
|
|
|
10665a |
char envtz[10];
|
|
|
10665a |
my_time = time(NULL);
|
|
|
10665a |
p_tm = localtime(&my_time);
|
|
|
10665a |
- snprintf(envtz, sizeof(envtz) - 1, "UTC%+d", (p_tm->tm_gmtoff * -1)/3600);
|
|
|
10665a |
+ snprintf(envtz, sizeof(envtz) - 1, "UTC%+ld", (p_tm->tm_gmtoff * -1)/3600);
|
|
|
10665a |
setenv("TZ", envtz, 0);
|
|
|
10665a |
|
|
|
10665a |
/* basename() is way too much of a pain from a portability standpoint */
|
|
|
10665a |
@@ -455,8 +385,7 @@ int main(int argc, char **argv)
|
|
|
10665a |
syslog(LOG_ERR, "Bad timeout value: %s", optarg);
|
|
|
10665a |
exit(EX_USAGE);
|
|
|
10665a |
}
|
|
|
10665a |
- rexmtval = timeout = tov;
|
|
|
10665a |
- maxtimeout = rexmtval * TIMEOUT_LIMIT;
|
|
|
10665a |
+ g_timeout = tov;
|
|
|
10665a |
}
|
|
|
10665a |
break;
|
|
|
10665a |
case 'R':
|
|
|
10665a |
@@ -1090,9 +1019,9 @@ int tftp(struct tftphdr *tp, int size)
|
|
|
10665a |
u_short tp_opcode = ntohs(tp->th_opcode);
|
|
|
10665a |
|
|
|
10665a |
char *val = NULL, *opt = NULL;
|
|
|
10665a |
- char *ap = ackbuf + 2;
|
|
|
10665a |
+ char *ap = pktbuf + 2;
|
|
|
10665a |
|
|
|
10665a |
- ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK);
|
|
|
10665a |
+ ((struct tftphdr *)pktbuf)->th_opcode = htons(OACK);
|
|
|
10665a |
|
|
|
10665a |
origfilename = cp = (char *)&(tp->th_stuff);
|
|
|
10665a |
argn = 0;
|
|
|
10665a |
@@ -1176,11 +1105,11 @@ int tftp(struct tftphdr *tp, int size)
|
|
|
10665a |
exit(0);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
- if (ap != (ackbuf + 2)) {
|
|
|
10665a |
+ if (ap != (pktbuf + 2)) {
|
|
|
10665a |
if (tp_opcode == WRQ)
|
|
|
10665a |
- (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf);
|
|
|
10665a |
+ (*pf->f_recv) (pf, (struct tftphdr *)pktbuf, ap - pktbuf);
|
|
|
10665a |
else
|
|
|
10665a |
- (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf, origfilename);
|
|
|
10665a |
+ (*pf->f_send) (pf, (struct tftphdr *)pktbuf, ap - pktbuf, origfilename);
|
|
|
10665a |
} else {
|
|
|
10665a |
if (tp_opcode == WRQ)
|
|
|
10665a |
(*pf->f_recv) (pf, NULL, 0);
|
|
|
10665a |
@@ -1248,7 +1177,7 @@ static int set_blksize2(uintmax_t *vp)
|
|
|
10665a |
static int set_rollover(uintmax_t *vp)
|
|
|
10665a |
{
|
|
|
10665a |
uintmax_t ro = *vp;
|
|
|
10665a |
-
|
|
|
10665a |
+
|
|
|
10665a |
if (ro > 65535)
|
|
|
10665a |
return 0;
|
|
|
10665a |
|
|
|
10665a |
@@ -1287,8 +1216,7 @@ static int set_timeout(uintmax_t *vp)
|
|
|
10665a |
if (to < 1 || to > 255)
|
|
|
10665a |
return 0;
|
|
|
10665a |
|
|
|
10665a |
- rexmtval = timeout = to * 1000000UL;
|
|
|
10665a |
- maxtimeout = rexmtval * TIMEOUT_LIMIT;
|
|
|
10665a |
+ g_timeout = to * 1000;
|
|
|
10665a |
|
|
|
10665a |
return 1;
|
|
|
10665a |
}
|
|
|
10665a |
@@ -1301,8 +1229,20 @@ static int set_utimeout(uintmax_t *vp)
|
|
|
10665a |
if (to < 10000UL || to > 255000000UL)
|
|
|
10665a |
return 0;
|
|
|
10665a |
|
|
|
10665a |
- rexmtval = timeout = to;
|
|
|
10665a |
- maxtimeout = rexmtval * TIMEOUT_LIMIT;
|
|
|
10665a |
+ g_timeout = to / 1000;
|
|
|
10665a |
+
|
|
|
10665a |
+ return 1;
|
|
|
10665a |
+}
|
|
|
10665a |
+
|
|
|
10665a |
+/*
|
|
|
10665a |
+ * Set window size (c.f. RFC7440)
|
|
|
10665a |
+ */
|
|
|
10665a |
+static int set_windowsize(uintmax_t *vp)
|
|
|
10665a |
+{
|
|
|
10665a |
+ if (*vp < 1 || *vp > MAX_WINDOWSIZE)
|
|
|
10665a |
+ return 0;
|
|
|
10665a |
+
|
|
|
10665a |
+ windowsize = *vp;
|
|
|
10665a |
|
|
|
10665a |
return 1;
|
|
|
10665a |
}
|
|
|
10665a |
@@ -1343,11 +1283,11 @@ static void do_opt(const char *opt, const char *val, char **ap)
|
|
|
10665a |
optlen = strlen(opt);
|
|
|
10665a |
retlen = sprintf(retbuf, "%"PRIuMAX, v);
|
|
|
10665a |
|
|
|
10665a |
- if (p + optlen + retlen + 2 >= ackbuf + sizeof(ackbuf)) {
|
|
|
10665a |
+ if (p + optlen + retlen + 2 >= pktbuf + sizeof(pktbuf)) {
|
|
|
10665a |
nak(EOPTNEG, "Insufficient space for options");
|
|
|
10665a |
exit(0);
|
|
|
10665a |
}
|
|
|
10665a |
-
|
|
|
10665a |
+
|
|
|
10665a |
memcpy(p, opt, optlen+1);
|
|
|
10665a |
p += optlen+1;
|
|
|
10665a |
memcpy(p, retbuf, retlen+1);
|
|
|
10665a |
@@ -1568,104 +1508,63 @@ static int validate_access(char *filename, int mode,
|
|
|
10665a |
*/
|
|
|
10665a |
static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen, char *filename)
|
|
|
10665a |
{
|
|
|
10665a |
- struct tftphdr *dp;
|
|
|
10665a |
- struct tftphdr *ap; /* ack packet */
|
|
|
10665a |
- static u_short block = 1; /* Static to avoid longjmp funnies */
|
|
|
10665a |
- u_short ap_opcode, ap_block;
|
|
|
10665a |
- unsigned long r_timeout;
|
|
|
10665a |
- int size, n;
|
|
|
10665a |
-
|
|
|
10665a |
+ struct tftphdr *tp = (struct tftphdr *)pktbuf;
|
|
|
10665a |
+ unsigned short tp_opcode, tp_block;
|
|
|
10665a |
+ int retries = RETRIES;
|
|
|
10665a |
+ int timed_out = 0;
|
|
|
10665a |
+ int n, r = 0;
|
|
|
10665a |
+ (void) pf;
|
|
|
10665a |
+
|
|
|
10665a |
+ set_verbose(verbosity);
|
|
|
10665a |
if (oap) {
|
|
|
10665a |
- timeout = rexmtval;
|
|
|
10665a |
- (void)sigsetjmp(timeoutbuf, 1);
|
|
|
10665a |
- oack:
|
|
|
10665a |
- r_timeout = timeout;
|
|
|
10665a |
- if (send(peer, oap, oacklen, 0) != oacklen) {
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: oack: %m\n");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout);
|
|
|
10665a |
- if (n < 0) {
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: read: %m\n");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- ap_opcode = ntohs((u_short) ap->th_opcode);
|
|
|
10665a |
- ap_block = ntohs((u_short) ap->th_block);
|
|
|
10665a |
-
|
|
|
10665a |
- if (ap_opcode == ERROR) {
|
|
|
10665a |
- syslog(LOG_WARNING,
|
|
|
10665a |
- "tftp: client does not accept options\n");
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ if (send(peer, oap, oacklen, 0) != oacklen) {
|
|
|
10665a |
+ syslog(LOG_WARNING, "tftpd: oack: %m\n");
|
|
|
10665a |
goto abort;
|
|
|
10665a |
}
|
|
|
10665a |
- if (ap_opcode == ACK) {
|
|
|
10665a |
- if (ap_block == 0)
|
|
|
10665a |
- break;
|
|
|
10665a |
- /* Resynchronize with the other side */
|
|
|
10665a |
- (void)synchnet(peer);
|
|
|
10665a |
- goto oack;
|
|
|
10665a |
- }
|
|
|
10665a |
- }
|
|
|
10665a |
- }
|
|
|
10665a |
|
|
|
10665a |
- dp = r_init();
|
|
|
10665a |
- do {
|
|
|
10665a |
- size = readit(file, &dp, pf->f_convert);
|
|
|
10665a |
- if (size < 0) {
|
|
|
10665a |
- nak(errno + 100, NULL);
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- dp->th_opcode = htons((u_short) DATA);
|
|
|
10665a |
- dp->th_block = htons((u_short) block);
|
|
|
10665a |
- timeout = rexmtval;
|
|
|
10665a |
- (void)sigsetjmp(timeoutbuf, 1);
|
|
|
10665a |
-
|
|
|
10665a |
- r_timeout = timeout;
|
|
|
10665a |
- if (send(peer, dp, size + 4, 0) != size + 4) {
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: write: %m");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- read_ahead(file, pf->f_convert);
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout);
|
|
|
10665a |
+ n = recv_with_timeout(peer, pktbuf, sizeof(pktbuf), g_timeout);
|
|
|
10665a |
if (n < 0) {
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: read(ack): %m");
|
|
|
10665a |
+ syslog(LOG_WARNING, "tftpd: recv: %m");
|
|
|
10665a |
goto abort;
|
|
|
10665a |
+ } else if (n == 0) {
|
|
|
10665a |
+ if (--retries <= 0) {
|
|
|
10665a |
+ timed_out = 1;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ continue;
|
|
|
10665a |
}
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- ap_opcode = ntohs((u_short) ap->th_opcode);
|
|
|
10665a |
- ap_block = ntohs((u_short) ap->th_block);
|
|
|
10665a |
|
|
|
10665a |
- if (ap_opcode == ERROR)
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
+ tp_opcode = ntohs(tp->th_opcode);
|
|
|
10665a |
+ tp_block = ntohs(tp->th_block);
|
|
|
10665a |
|
|
|
10665a |
- if (ap_opcode == ACK) {
|
|
|
10665a |
- if (ap_block == block) {
|
|
|
10665a |
- break;
|
|
|
10665a |
- }
|
|
|
10665a |
- /* Re-synchronize with the other side */
|
|
|
10665a |
- (void)synchnet(peer);
|
|
|
10665a |
- /*
|
|
|
10665a |
- * RFC1129/RFC1350: We MUST NOT re-send the DATA
|
|
|
10665a |
- * packet in response to an invalid ACK. Doing so
|
|
|
10665a |
- * would cause the Sorcerer's Apprentice bug.
|
|
|
10665a |
- */
|
|
|
10665a |
+ if (tp_opcode == ERROR) {
|
|
|
10665a |
+ char error[ERROR_MAXLEN];
|
|
|
10665a |
+
|
|
|
10665a |
+ format_error(tp, error);
|
|
|
10665a |
+ syslog(LOG_WARNING, "%s", error);
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ } else if (!(tp_opcode == ACK && tp_block == 0)) {
|
|
|
10665a |
+ syslog(LOG_WARNING, "unexpected packet %s block=%u", opcode_to_str(tp_opcode), tp_block);
|
|
|
10665a |
+ send_error(peer, NULL, "Unexpected packet");
|
|
|
10665a |
+ exit(1);
|
|
|
10665a |
}
|
|
|
10665a |
+ } while (n == 0);
|
|
|
10665a |
+ }
|
|
|
10665a |
+
|
|
|
10665a |
+ r = sender(peer, NULL, segsize, windowsize, TIMEOUT, rollover_val, file, NULL);
|
|
|
10665a |
|
|
|
10665a |
- }
|
|
|
10665a |
- if (!++block)
|
|
|
10665a |
- block = rollover_val;
|
|
|
10665a |
- } while (size == segsize);
|
|
|
10665a |
tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
|
|
|
10665a |
- tmpbuf, INET6_ADDRSTRLEN);
|
|
|
10665a |
+ tmpbuf, INET6_ADDRSTRLEN);
|
|
|
10665a |
if (!tmp_p) {
|
|
|
10665a |
tmp_p = tmpbuf;
|
|
|
10665a |
strcpy(tmpbuf, "???");
|
|
|
10665a |
}
|
|
|
10665a |
- syslog(LOG_NOTICE, "Client %s finished %s",tmp_p,filename);
|
|
|
10665a |
- abort:
|
|
|
10665a |
- (void)fclose(file);
|
|
|
10665a |
+ syslog(LOG_NOTICE, "Client %s finished %s", tmp_p, filename);
|
|
|
10665a |
+abort:
|
|
|
10665a |
+ if (timed_out || r == E_TIMED_OUT)
|
|
|
10665a |
+ syslog(LOG_NOTICE, "Client %s timed out", tmp_p);
|
|
|
10665a |
+ fclose(file);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
/*
|
|
|
10665a |
@@ -1673,90 +1572,44 @@ static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oac
|
|
|
10665a |
*/
|
|
|
10665a |
static void tftp_recvfile(const struct formats *pf, struct tftphdr *oap, int oacklen)
|
|
|
10665a |
{
|
|
|
10665a |
- struct tftphdr *dp;
|
|
|
10665a |
- int n, size;
|
|
|
10665a |
- /* These are "static" to avoid longjmp funnies */
|
|
|
10665a |
- static struct tftphdr *ap; /* ack buffer */
|
|
|
10665a |
- static u_short block = 0;
|
|
|
10665a |
- static int acksize;
|
|
|
10665a |
- u_short dp_opcode, dp_block;
|
|
|
10665a |
- unsigned long r_timeout;
|
|
|
10665a |
-
|
|
|
10665a |
- dp = w_init();
|
|
|
10665a |
- do {
|
|
|
10665a |
- timeout = rexmtval;
|
|
|
10665a |
-
|
|
|
10665a |
- if (!block && oap) {
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- acksize = oacklen;
|
|
|
10665a |
- } else {
|
|
|
10665a |
- ap = (struct tftphdr *)ackbuf;
|
|
|
10665a |
- ap->th_opcode = htons((u_short) ACK);
|
|
|
10665a |
- ap->th_block = htons((u_short) block);
|
|
|
10665a |
- acksize = 4;
|
|
|
10665a |
- /* If we're sending a regular ACK, that means we have successfully
|
|
|
10665a |
- * sent the OACK. Clear oap so that we won't try to send another
|
|
|
10665a |
- * OACK when the block number wraps back to 0. */
|
|
|
10665a |
- oap = NULL;
|
|
|
10665a |
- }
|
|
|
10665a |
- if (!++block)
|
|
|
10665a |
- block = rollover_val;
|
|
|
10665a |
- (void)sigsetjmp(timeoutbuf, 1);
|
|
|
10665a |
- send_ack:
|
|
|
10665a |
- r_timeout = timeout;
|
|
|
10665a |
- if (send(peer, ackbuf, acksize, 0) != acksize) {
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: write(ack): %m");
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- write_behind(file, pf->f_convert);
|
|
|
10665a |
- for (;;) {
|
|
|
10665a |
- n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout);
|
|
|
10665a |
- if (n < 0) { /* really? */
|
|
|
10665a |
- syslog(LOG_WARNING, "tftpd: read: %m");
|
|
|
10665a |
+ int retries = RETRIES;
|
|
|
10665a |
+ int timed_out = 0;
|
|
|
10665a |
+ int r;
|
|
|
10665a |
+ (void) pf;
|
|
|
10665a |
+
|
|
|
10665a |
+ set_verbose(verbosity);
|
|
|
10665a |
+ if (oap) {
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ if (send(peer, oap, oacklen, 0) != oacklen) {
|
|
|
10665a |
+ syslog(LOG_WARNING, "tftpd: oack: %m\n");
|
|
|
10665a |
goto abort;
|
|
|
10665a |
}
|
|
|
10665a |
- dp_opcode = ntohs((u_short) dp->th_opcode);
|
|
|
10665a |
- dp_block = ntohs((u_short) dp->th_block);
|
|
|
10665a |
- if (dp_opcode == ERROR)
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- if (dp_opcode == DATA) {
|
|
|
10665a |
- if (dp_block == block) {
|
|
|
10665a |
- break; /* normal */
|
|
|
10665a |
+ r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK);
|
|
|
10665a |
+ if (r == 0) {
|
|
|
10665a |
+ if (--retries <= 0) {
|
|
|
10665a |
+ timed_out = 1;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
}
|
|
|
10665a |
- /* Re-synchronize with the other side */
|
|
|
10665a |
- (void)synchnet(peer);
|
|
|
10665a |
- if (dp_block == (block - 1))
|
|
|
10665a |
- goto send_ack; /* rexmit */
|
|
|
10665a |
}
|
|
|
10665a |
- }
|
|
|
10665a |
- /* size = write(file, dp->th_data, n - 4); */
|
|
|
10665a |
- size = writeit(file, &dp, n - 4, pf->f_convert);
|
|
|
10665a |
- if (size != (n - 4)) { /* ahem */
|
|
|
10665a |
- if (size < 0)
|
|
|
10665a |
- nak(errno + 100, NULL);
|
|
|
10665a |
- else
|
|
|
10665a |
- nak(ENOSPACE, NULL);
|
|
|
10665a |
- goto abort;
|
|
|
10665a |
- }
|
|
|
10665a |
- } while (size == segsize);
|
|
|
10665a |
- write_behind(file, pf->f_convert);
|
|
|
10665a |
- (void)fclose(file); /* close data file */
|
|
|
10665a |
-
|
|
|
10665a |
- ap->th_opcode = htons((u_short) ACK); /* send the "final" ack */
|
|
|
10665a |
- ap->th_block = htons((u_short) (block));
|
|
|
10665a |
- (void)send(peer, ackbuf, 4, 0);
|
|
|
10665a |
-
|
|
|
10665a |
- timeout_quit = 1; /* just quit on timeout */
|
|
|
10665a |
- n = recv_time(peer, buf, sizeof(buf), 0, &timeout); /* normally times out and quits */
|
|
|
10665a |
- timeout_quit = 0;
|
|
|
10665a |
-
|
|
|
10665a |
- if (n >= 4 && /* if read some data */
|
|
|
10665a |
- dp_opcode == DATA && /* and got a data block */
|
|
|
10665a |
- block == dp_block) { /* then my last ack was lost */
|
|
|
10665a |
- (void)send(peer, ackbuf, 4, 0); /* resend final ack */
|
|
|
10665a |
+ } while (r == 0);
|
|
|
10665a |
+ } else {
|
|
|
10665a |
+ do {
|
|
|
10665a |
+ send_ack(peer, NULL, 0);
|
|
|
10665a |
+ r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK);
|
|
|
10665a |
+ if (r == 0) {
|
|
|
10665a |
+ if (--retries <= 0) {
|
|
|
10665a |
+ timed_out = 1;
|
|
|
10665a |
+ goto abort;
|
|
|
10665a |
+ }
|
|
|
10665a |
+ }
|
|
|
10665a |
+ } while (r == 0);
|
|
|
10665a |
}
|
|
|
10665a |
- abort:
|
|
|
10665a |
- return;
|
|
|
10665a |
+
|
|
|
10665a |
+ r = receiver(peer, NULL, segsize, windowsize, TIMEOUT, file, NULL, NULL);
|
|
|
10665a |
+abort:
|
|
|
10665a |
+ if (timed_out || r == E_TIMED_OUT)
|
|
|
10665a |
+ syslog(LOG_NOTICE, "Client %s timed out", tmp_p);
|
|
|
10665a |
+ fclose(file);
|
|
|
10665a |
}
|
|
|
10665a |
|
|
|
10665a |
static const char *const errmsgs[] = {
|