Add --tftp-single-port option.

This commit is contained in:
Simon Kelley
2020-01-05 16:21:24 +00:00
parent 18a6bdd541
commit 66f62650c3
6 changed files with 113 additions and 64 deletions

View File

@@ -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;
@@ -239,6 +240,25 @@ 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))
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
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;
}
}
if (name)
{
/* check for per-interface prefix */
@@ -264,16 +284,21 @@ void tftp_request(struct listener *listen, time_t now)
#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(listen->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;
@@ -286,7 +311,7 @@ void tftp_request(struct listener *listen, time_t now)
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)
@@ -315,7 +340,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)) ||
@@ -439,9 +464,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);
@@ -538,60 +562,25 @@ 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);
@@ -613,13 +602,14 @@ void check_tftp_listeners(time_t now)
}
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);
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;
@@ -638,15 +628,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);
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);
}