summaryrefslogtreecommitdiff
path: root/src/tftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tftp.c')
-rw-r--r--src/tftp.c234
1 files changed, 137 insertions, 97 deletions
diff --git a/src/tftp.c b/src/tftp.c
index bccca69..aa240e5 100644
--- a/src/tftp.c
+++ b/src/tftp.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
#ifdef HAVE_TFTP
+static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix);
static void free_transfer(struct tftp_transfer *transfer);
static ssize_t tftp_err(int err, char *packet, char *message, char *file);
@@ -50,7 +51,7 @@ void tftp_request(struct listener *listen, time_t now)
struct ifreq ifr;
int is_err = 1, if_index = 0, mtu = 0;
struct iname *tmp;
- struct tftp_transfer *transfer;
+ struct tftp_transfer *transfer = NULL, **up;
int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
int mtuflag = IP_PMTUDISC_DONT;
@@ -59,24 +60,21 @@ void tftp_request(struct listener *listen, time_t now)
char *name = NULL;
char *prefix = daemon->tftp_prefix;
struct tftp_prefix *pref;
- struct all_addr addra;
-#ifdef HAVE_IPV6
+ union all_addr addra;
+ int family = listen->addr.sa.sa_family;
/* Can always get recvd interface for IPv6 */
- int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6;
-#else
- int check_dest = !option_bool(OPT_NOWILD);
-#endif
+ int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6;
union {
struct cmsghdr align; /* this ensures alignment */
-#ifdef HAVE_IPV6
char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
-#endif
#if defined(HAVE_LINUX_NETWORK)
char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
#elif defined(HAVE_SOLARIS_NETWORK)
- char control[CMSG_SPACE(sizeof(unsigned int))];
+ char control[CMSG_SPACE(sizeof(struct in_addr)) +
+ CMSG_SPACE(sizeof(unsigned int))];
#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
- char control[CMSG_SPACE(sizeof(struct sockaddr_dl))];
+ char control[CMSG_SPACE(sizeof(struct in_addr)) +
+ CMSG_SPACE(sizeof(struct sockaddr_dl))];
#endif
} control_u;
@@ -124,10 +122,10 @@ void tftp_request(struct listener *listen, time_t now)
if (msg.msg_controllen < sizeof(struct cmsghdr))
return;
- addr.sa.sa_family = listen->family;
+ addr.sa.sa_family = family;
#if defined(HAVE_LINUX_NETWORK)
- if (listen->family == AF_INET)
+ if (family == AF_INET)
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
{
@@ -141,7 +139,7 @@ void tftp_request(struct listener *listen, time_t now)
}
#elif defined(HAVE_SOLARIS_NETWORK)
- if (listen->family == AF_INET)
+ if (family == AF_INET)
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
{
union {
@@ -157,7 +155,7 @@ void tftp_request(struct listener *listen, time_t now)
}
#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
- if (listen->family == AF_INET)
+ if (family == AF_INET)
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
{
union {
@@ -174,8 +172,7 @@ void tftp_request(struct listener *listen, time_t now)
#endif
-#ifdef HAVE_IPV6
- if (listen->family == AF_INET6)
+ if (family == AF_INET6)
{
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
@@ -190,19 +187,16 @@ void tftp_request(struct listener *listen, time_t now)
if_index = p.p->ipi6_ifindex;
}
}
-#endif
if (!indextoname(listen->tftpfd, if_index, namebuff))
return;
name = namebuff;
- addra.addr.addr4 = addr.in.sin_addr;
+ addra.addr4 = addr.in.sin_addr;
-#ifdef HAVE_IPV6
- if (listen->family == AF_INET6)
- addra.addr.addr6 = addr.in6.sin6_addr;
-#endif
+ if (family == AF_INET6)
+ addra.addr6 = addr.in6.sin6_addr;
if (daemon->tftp_interfaces)
{
@@ -217,12 +211,12 @@ void tftp_request(struct listener *listen, time_t now)
else
{
/* Do the same as DHCP */
- if (!iface_check(listen->family, &addra, name, NULL))
+ if (!iface_check(family, &addra, name, NULL))
{
if (!option_bool(OPT_CLEVERBIND))
enumerate_interfaces(0);
- if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) &&
- !label_exception(if_index, listen->family, &addra) )
+ if (!loopback_exception(listen->tftpfd, family, &addra, name) &&
+ !label_exception(if_index, family, &addra))
return;
}
@@ -234,7 +228,7 @@ void tftp_request(struct listener *listen, time_t now)
#endif
}
- strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1)
{
mtu = ifr.ifr_mtu;
@@ -247,6 +241,39 @@ void tftp_request(struct listener *listen, time_t now)
if (mtu == 0)
mtu = daemon->tftp_mtu;
+ /* data transfer via server listening socket */
+ if (option_bool(OPT_SINGLE_PORT))
+ {
+ int tftp_cnt;
+
+ for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
+ {
+ tftp_cnt++;
+
+ if (sockaddr_isequal(&peer, &transfer->peer))
+ {
+ if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
+ {
+ /* Handle repeated RRQ or abandoned transfer from same host and port
+ by unlinking and reusing the struct transfer. */
+ *up = transfer->next;
+ break;
+ }
+ else
+ {
+ handle_tftp(now, transfer, len);
+ return;
+ }
+ }
+ }
+
+ /* Enforce simultaneous transfer limit. In non-single-port mode
+ this is doene by not listening on the server socket when
+ too many transfers are in progress. */
+ if (!transfer && tftp_cnt >= daemon->tftp_max)
+ return;
+ }
+
if (name)
{
/* check for per-interface prefix */
@@ -255,14 +282,13 @@ void tftp_request(struct listener *listen, time_t now)
prefix = pref->prefix;
}
- if (listen->family == AF_INET)
+ if (family == AF_INET)
{
addr.in.sin_port = htons(port);
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(addr.in);
#endif
}
-#ifdef HAVE_IPV6
else
{
addr.in6.sin6_port = htons(port);
@@ -272,18 +298,22 @@ void tftp_request(struct listener *listen, time_t now)
addr.in6.sin6_len = sizeof(addr.in6);
#endif
}
-#endif
- if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
+ /* May reuse struct transfer from abandoned transfer in single port mode. */
+ if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
return;
- if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1)
+ if (option_bool(OPT_SINGLE_PORT))
+ transfer->sockfd = listen->tftpfd;
+ else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
{
free(transfer);
return;
}
transfer->peer = peer;
+ transfer->source = addra;
+ transfer->if_index = if_index;
transfer->timeout = now + 2;
transfer->backoff = 1;
transfer->block = 1;
@@ -293,10 +323,10 @@ void tftp_request(struct listener *listen, time_t now)
transfer->opt_blocksize = transfer->opt_transize = 0;
transfer->netascii = transfer->carrylf = 0;
- prettyprint_addr(&peer, daemon->addrbuff);
+ (void)prettyprint_addr(&peer, daemon->addrbuff);
/* if we have a nailed-down range, iterate until we find a free one. */
- while (1)
+ while (!option_bool(OPT_SINGLE_PORT))
{
if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 ||
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
@@ -308,12 +338,11 @@ void tftp_request(struct listener *listen, time_t now)
{
if (++port <= daemon->end_tftp_port)
{
- if (listen->family == AF_INET)
+ if (family == AF_INET)
addr.in.sin_port = htons(port);
-#ifdef HAVE_IPV6
else
- addr.in6.sin6_port = htons(port);
-#endif
+ addr.in6.sin6_port = htons(port);
+
continue;
}
my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP"));
@@ -326,7 +355,7 @@ void tftp_request(struct listener *listen, time_t now)
p = packet + 2;
end = packet + len;
-
+
if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
!(filename = next(&p, end)) ||
!(mode = next(&p, end)) ||
@@ -347,7 +376,7 @@ void tftp_request(struct listener *listen, time_t now)
if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK))
{
/* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */
- int overhead = (listen->family == AF_INET) ? 32 : 52;
+ int overhead = (family == AF_INET) ? 32 : 52;
transfer->blocksize = atoi(opt);
if (transfer->blocksize < 1)
transfer->blocksize = 1;
@@ -450,9 +479,8 @@ void tftp_request(struct listener *listen, time_t now)
is_err = 0;
}
}
-
- while (sendto(transfer->sockfd, packet, len, 0,
- (struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR);
+
+ send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
if (is_err)
free_transfer(transfer);
@@ -549,63 +577,28 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix)
void check_tftp_listeners(time_t now)
{
struct tftp_transfer *transfer, *tmp, **up;
- ssize_t len;
- struct ack {
- unsigned short op, block;
- } *mess = (struct ack *)daemon->packet;
-
- /* Check for activity on any existing transfers */
- for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
- {
- tmp = transfer->next;
-
- prettyprint_addr(&transfer->peer, daemon->addrbuff);
-
+ /* In single port mode, all packets come via port 69 and tftp_request() */
+ if (!option_bool(OPT_SINGLE_PORT))
+ for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
if (poll_check(transfer->sockfd, POLLIN))
{
/* we overwrote the buffer... */
daemon->srv_save = NULL;
-
- if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
- {
- if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
- {
- /* Got ack, ensure we take the (re)transmit path */
- transfer->timeout = now;
- transfer->backoff = 0;
- if (transfer->block++ != 0)
- transfer->offset += transfer->blocksize - transfer->expansion;
- }
- else if (ntohs(mess->op) == OP_ERR)
- {
- char *p = daemon->packet + sizeof(struct ack);
- char *end = daemon->packet + len;
- char *err = next(&p, end);
-
- /* Sanitise error message */
- if (!err)
- err = "";
- else
- sanitise(err);
-
- my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
- (int)ntohs(mess->block), err,
- daemon->addrbuff);
-
- /* Got err, ensure we take abort */
- transfer->timeout = now;
- transfer->backoff = 100;
- }
- }
+ handle_tftp(now, transfer, recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0));
}
+
+ for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
+ {
+ tmp = transfer->next;
if (difftime(now, transfer->timeout) >= 0.0)
{
int endcon = 0;
+ ssize_t len;
/* timeout, retransmit */
- transfer->timeout += 1 + (1<<transfer->backoff);
+ transfer->timeout += 1 + (1<<(transfer->backoff/2));
/* we overwrote the buffer... */
daemon->srv_save = NULL;
@@ -615,22 +608,24 @@ void check_tftp_listeners(time_t now)
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = 1;
}
- /* don't complain about timeout when we're awaiting the last
- ACK, some clients never send it */
- else if (++transfer->backoff > 7 && len != 0)
+ else if (++transfer->backoff > 7)
{
- endcon = 1;
+ /* don't complain about timeout when we're awaiting the last
+ ACK, some clients never send it */
+ if ((unsigned)len == transfer->blocksize + 4)
+ endcon = 1;
len = 0;
}
if (len != 0)
- while(sendto(transfer->sockfd, daemon->packet, len, 0,
- (struct sockaddr *)&transfer->peer, sa_len(&transfer->peer)) == -1 && errno == EINTR);
-
+ send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
+ &transfer->peer, &transfer->source, transfer->if_index);
+
if (endcon || len == 0)
{
strcpy(daemon->namebuff, transfer->file->filename);
sanitise(daemon->namebuff);
+ (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
/* unlink */
*up = tmp;
@@ -649,15 +644,60 @@ void check_tftp_listeners(time_t now)
up = &transfer->next;
}
}
+
+/* packet in daemon->packet as this is called. */
+static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
+{
+ struct ack {
+ unsigned short op, block;
+ } *mess = (struct ack *)daemon->packet;
+
+ if (len >= (ssize_t)sizeof(struct ack))
+ {
+ if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
+ {
+ /* Got ack, ensure we take the (re)transmit path */
+ transfer->timeout = now;
+ transfer->backoff = 0;
+ if (transfer->block++ != 0)
+ transfer->offset += transfer->blocksize - transfer->expansion;
+ }
+ else if (ntohs(mess->op) == OP_ERR)
+ {
+ char *p = daemon->packet + sizeof(struct ack);
+ char *end = daemon->packet + len;
+ char *err = next(&p, end);
+
+ (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
+
+ /* Sanitise error message */
+ if (!err)
+ err = "";
+ else
+ sanitise(err);
+
+ my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
+ (int)ntohs(mess->block), err,
+ daemon->addrbuff);
+
+ /* Got err, ensure we take abort */
+ transfer->timeout = now;
+ transfer->backoff = 100;
+ }
+ }
+}
static void free_transfer(struct tftp_transfer *transfer)
{
- close(transfer->sockfd);
+ if (!option_bool(OPT_SINGLE_PORT))
+ close(transfer->sockfd);
+
if (transfer->file && (--transfer->file->refcount) == 0)
{
close(transfer->file->fd);
free(transfer->file);
}
+
free(transfer);
}