Compare commits

...
Author SHA1 Message Date
Simon Kelley eb60168382 Fix SEGV in Dbus code.
A 'AddDhcpLease' DBus message which fails to allocate
a DHCP lease will cause dnsmasq to dereference a NULL pointer
and crash.

Allocation failure can happen if the particular
flavour (IPv4 pr IPv6) of DHCP is not configured in
this dnsmasq instance, or if the configured limit on
DHCP leases is reached or (unlikley) if dnsmasq cannot
allocate memory.

This patch provides error returns for all these situations.

This patch is inspired by a patch from
Stefan Hanreich <s.hanreich@proxmox.com> which was prompted by
a bug report from Lou Lécrivain.
2025-11-28 16:10:37 +00:00
Simon Kelley 57a2f5778c Fix confusion in address checking code.
This was supposed to disallow addresses which are in use by
the DHCP server, and the code works fine when only dhcp-range
is defined per broadcast domain. It's confused when there are
multiple dhcp-ranges which are valid for a particular request.
2025-11-28 15:14:09 +00:00
Simon Kelley 7d5fbe7da3 Fix failure to add client MAC address to queries in TCP mode.
The options which cause dnsmasq to decorate a DNS query with the MAC
address on the originating client can fail when the query is sent
using TCP.

In TCP mode, dnsmasq spawns a new process to handle each TCP connection.
These child processes do not have an open netlink socket, which is
needed to read the kernel ARP table, so the process of adding the
client's MAC address as an EDNS0 option silently fails.

This is fixed by this patch by updating dnsmasq's ARP cache in the
main process just before forking the TCP-handler child process.
This ensures that the copy of the ARP cache inherited by the
TCP-handler contains the information required without the need to
read the kernel ARP table.

Thanks to Bruno Ravara for spotting and characterising this bug.
2025-11-26 21:54:59 +00:00
Petr Menšík ded935be37 Add captive-portal name support for DHCP options
Adds support into DHCP and DHCPv6 record. Useful for announcing URL for
captive portal API URI.

https://www.rfc-editor.org/rfc/rfc8910.html
2025-11-18 22:41:15 +00:00
Simon Kelley aa9b71c681 Merge translated strings. 2025-11-18 22:20:42 +00:00
Daniel Nylander e497f3f2c8 Add Swedish translation.
Thanks to Daniel Nylander.
2025-11-18 22:18:02 +00:00
Simon Kelley ee09f0655c Optimise tftp.
This change attempts to optimse TFTP perfomance.

It reads the next block immediately after sending a block,
in order to reduce latency between receiving an ACK and sending
the next block. Is also suppresses lseek() calls when reading
successive blocks.

This improves performance when doing transfers without windowing
and when only a single transfer is in progress at one time.
2025-09-01 22:35:02 +01:00
22 changed files with 14967 additions and 6916 deletions

No files matched your search

+6
View File
@@ -70,6 +70,12 @@ version 2.92
is functional when client and server networks aren't mutually
route-able.
Fix failure to add client MAC address to queries in TCP mode.
The options which cause dnsmasq to decorate a DNS query with the MAC
address on the originating client can fail when the query is sent
using TCP. Thanks to Bruno Ravara for spotting and
characterising this bug.
version 2.91
Fix spurious "resource limit exceeded messages". Thanks to
+2870
View File
File diff suppressed because it is too large. Load diff
+793 -587
View File
File diff suppressed because it is too large. Load diff
+780 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+784 -588
View File
File diff suppressed because it is too large. Load diff
+793 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+793 -587
View File
File diff suppressed because it is too large. Load diff
+779 -588
View File
File diff suppressed because it is too large. Load diff
+785 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+779 -588
View File
File diff suppressed because it is too large. Load diff
+2979
View File
File diff suppressed because it is too large. Load diff
+13 -5
View File
@@ -111,22 +111,26 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
again:
/* If the database is less then INTERVAL old, look in there */
if (difftime(now, last) < INTERVAL)
/* If the database is less then INTERVAL old, look in there.
If we're a child process, we always rely on the existing cache we
inherited from the parent, since we don't have a netlink socket.
*/
if (difftime(now, last) < INTERVAL || daemon->pipe_to_parent != -1)
{
/* addr == NULL -> just make cache up-to-date */
if (!addr)
return 0;
for (arp = arps; arp; arp = arp->next)
{
if (addr->sa.sa_family != arp->family)
continue;
if (arp->family == AF_INET &&
arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
continue;
if (arp->family == AF_INET6 &&
!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
continue;
@@ -141,6 +145,10 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
}
}
/* Not in cache in child, no go. */
if (daemon->pipe_to_parent != -1)
return 0;
/* Not found, try the kernel */
if (!updated)
{
+16 -3
View File
@@ -530,8 +530,8 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
union all_addr addr;
time_t now = dnsmasq_time();
unsigned char dhcp_chaddr[DHCP_CHADDR_MAX];
DBusMessageIter iter, array_iter;
if (!dbus_message_iter_init(message, &iter))
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Failed to initialize dbus message iter");
@@ -599,6 +599,10 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
if (inet_pton(AF_INET, ipaddr, &addr.addr4))
{
if (!daemon->dhcp)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"DHCPv4 not configured");
if (ia_id != 0 || is_temporary)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"ia_id and is_temporary must be zero for IPv4 lease");
@@ -609,16 +613,25 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
{
if (!daemon->doing_dhcp6)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"DHCPv6 not configured");
if (!(lease = lease6_find_by_addr(&addr.addr6, 128, 0)))
lease = lease6_allocate(&addr.addr6,
is_temporary ? LEASE_TA : LEASE_NA);
lease_set_iaid(lease, ia_id);
if (lease)
lease_set_iaid(lease, ia_id);
}
#endif
else
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
"Invalid IP address '%s'", ipaddr);
if (!lease)
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
"unable to allocate lease for IP address '%s'", ipaddr);
hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, &hw_type);
if (hw_len < 0)
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+3 -1
View File
@@ -709,7 +709,8 @@ static const struct opttab_t {
{ "client-machine-id", 97, 0 },
{ "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "captive-portal", 114, OT_NAME }, /* RFC 8910 */
{ "subnet-select", 118, OT_INTERNAL },
{ "domain-search", 119, OT_RFC1035_NAME },
{ "sip-server", 120, 0 },
@@ -751,6 +752,7 @@ static const struct opttab_t opttab6[] = {
{ "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
{ "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING },
{ "captive-portal", 103, OT_NAME }, /* RFC 8910 */
{ NULL, 0, 0 }
};
#endif
+1 -1
View File
@@ -696,7 +696,7 @@ struct dhcp_context *address_available(struct dhcp_context *context,
struct dhcp_context *tmp;
for (tmp = context; tmp; tmp = tmp->current)
if (taddr.s_addr == context->router.s_addr)
if (taddr.s_addr == tmp->router.s_addr)
return NULL;
for (tmp = context; tmp; tmp = tmp->current)
+16 -1
View File
@@ -2033,9 +2033,24 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
if (!option_bool(OPT_DEBUG))
{
/* The code in edns0.c qthat decorates queries with the source MAC address depends
on the code in arp.c, which populates a cache with the contents of the ARP table
using netlink. Since the child process can't use netlink, we pre-populate
the cache with the ARP table entry for our source here, including a negative entry
if there is nothing for our address in the ARP table.
When the edns0 code calls find_mac() in the child process, it will
get the correct answer from the cache inherited from the parent
without having to use netlink to consult the kernel ARP table.
edns0_needs_mac() simply calls find_mac if any EDNS0 options
which need a MAC address are enabled. */
edns0_needs_mac(&tcp_addr, now);
if (pipe(pipefd) == -1)
goto closeconandreturn; /* pipe failed */
if ((p = fork()) == -1)
{
/* fork failed */
+4 -3
View File
@@ -1109,7 +1109,7 @@ struct ping_result {
struct tftp_file {
int refcount, fd;
off_t size;
off_t size, posn;
dev_t dev;
ino_t inode;
char filename[];
@@ -1124,7 +1124,7 @@ struct tftp_transfer {
union mysockaddr peer;
union all_addr source;
int if_index;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, backoff;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, lastcarrylf, backoff;
struct tftp_file *file;
struct tftp_transfer *next;
};
@@ -1278,7 +1278,7 @@ extern struct daemon {
struct serverfd *sfds;
struct irec *interfaces;
struct listener *listeners;
struct server *srv_save; /* Used for resend on DoD */
void *srv_save; /* Used for resend on DoD and tftp prefetch */
size_t packet_len; /* " " */
int fd_save; /* " " */
pid_t *tcp_pids;
@@ -1918,6 +1918,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
void edns0_needs_mac(union mysockaddr *addr, time_t now);
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
union mysockaddr *source, time_t now, int *cacheable);
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+10
View File
@@ -265,6 +265,15 @@ static void encoder(unsigned char *in, char *out)
out[3] = char64(in[2]);
}
/* This function needs to call find_mac if any option which requires a MAC address is enabled
and used below. If you add a new MAC consumer, modify this, otherwise your
new EDNS0 option won't work in TCP mode. */
void edns0_needs_mac(union mysockaddr *addr, time_t now)
{
if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX) || option_bool(OPT_ADD_MAC))
find_mac(addr, NULL, 0, now);
}
/* OPT_ADD_MAC = MAC is added (if available)
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
OPT_STRIP_MAC = MAC is removed */
@@ -562,3 +571,4 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l
return plen;
}
+108 -88
View File
@@ -18,12 +18,12 @@
#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, char *client);
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client);
static void free_transfer(struct tftp_transfer *transfer);
static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2);
static ssize_t tftp_err_oops(char *packet, const char *file);
static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
static ssize_t get_block(struct tftp_transfer *transfer);
static char *next(char **p, char *end);
static void sanitise(char *buf);
@@ -41,10 +41,9 @@ static void sanitise(char *buf);
#define ERR_ILL 4
#define ERR_TID 5
static void tftp_request(struct listener *listen, time_t now)
static void tftp_request(char *packet, ssize_t plen, struct listener *listen, time_t now)
{
ssize_t len;
char *packet = daemon->packet;
char *filename, *mode, *p, *end;
union mysockaddr addr, peer;
struct msghdr msg;
@@ -87,12 +86,10 @@ static void tftp_request(struct listener *listen, time_t now)
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* packet buff is DNS name workspace. */
iov.iov_base = packet;
iov.iov_len = daemon->packet_buff_sz;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
iov.iov_len = plen;
if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
return;
@@ -267,7 +264,7 @@ static void tftp_request(struct listener *listen, time_t now)
}
else
{
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
return;
}
}
@@ -501,17 +498,21 @@ static void tftp_request(struct listener *listen, time_t now)
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
/* check permissions and open file */
if ((transfer->file = check_tftp_fileperm(&len, prefix, daemon->addrbuff)))
if ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff)))
{
transfer->lastack = transfer->block;
transfer->retransmit = now + transfer->timeout;
/* This packet is may be the first data packet, but only if windowsize == 1
To get windowsize greater then one requires an option negotiation,
in which case this packet is the OACK. */
if ((len = get_block(packet, transfer)) == -1)
if ((len = get_block(transfer)) == -1)
len = tftp_err_oops(packet, daemon->namebuff);
else
is_err = 0;
{
is_err = 0;
/* get_block put the packet to send in a different buffer. */
packet = daemon->packet;
}
}
}
}
@@ -534,9 +535,9 @@ static void tftp_request(struct listener *listen, time_t now)
}
}
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client)
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client)
{
char *packet = daemon->packet, *namebuff = daemon->namebuff;
char *namebuff = daemon->namebuff;
struct tftp_file *file;
struct tftp_transfer *t;
uid_t uid = geteuid();
@@ -599,6 +600,7 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c
file->size = statbuf.st_size;
file->dev = statbuf.st_dev;
file->inode = statbuf.st_ino;
file->posn = 0;
file->refcount = 1;
strcpy(file->filename, namebuff);
return file;
@@ -618,12 +620,15 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c
void check_tftp_listeners(time_t now)
{
/* Use workspace to receive (small) request/ACK, to avoid overwriting precomputed reply */
char *packet = daemon->workspacename;
ssize_t plen = MAXDNAME * 2;
struct listener *listener;
struct tftp_transfer *transfer, *tmp, **up;
for (listener = daemon->listeners; listener; listener = listener->next)
if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN))
tftp_request(listener, now);
tftp_request(packet, plen, listener, now);
/* In single port mode, all packets come via port 69 and tftp_request() */
if (!option_bool(OPT_SINGLE_PORT))
@@ -634,26 +639,23 @@ void check_tftp_listeners(time_t now)
socklen_t addr_len = sizeof(union mysockaddr);
ssize_t len;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0)
if ((len = recvfrom(transfer->sockfd, packet, plen, 0, &peer.sa, &addr_len)) > 0)
{
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
#endif
if (sockaddr_isequal(&peer, &transfer->peer))
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
else
{
/* Wrong source address. See rfc1350 para 4. */
prettyprint_addr(&peer, daemon->addrbuff);
len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer))));
len = tftp_err(ERR_TID, packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, packet, len, 0, &peer.sa, sa_len(&peer))));
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
#endif
}
}
@@ -673,7 +675,7 @@ void check_tftp_listeners(time_t now)
endcon = 1;
/* don't complain about timeout when we're awaiting the last
ACK, some clients never send it */
if (get_block(daemon->packet, transfer) > 0)
if (get_block(transfer) > 0)
error = timeout = 1;
}
else if (difftime(now, transfer->retransmit) >= 0.0)
@@ -682,41 +684,43 @@ void check_tftp_listeners(time_t now)
bumps transfer->lastack and trips the retransmit timer so that we send the next block(s)
here. */
ssize_t len;
unsigned int i, winsize;
transfer->retransmit += transfer->timeout + (1<<(transfer->backoff/2));
transfer->backoff++;
transfer->block = transfer->lastack;
if ((len = get_block(daemon->packet, transfer)) == 0)
endcon = 1; /* got last ACK */
else
/* send a window'a worth of blocks unless we're retransmitting OACK */
winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize; i++, transfer->block++)
{
/* send a window'a worth of blocks unless we're retransmitting OACK */
unsigned int i, winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize && !endcon; i++, transfer->block++)
if ((len = get_block(transfer)) == 0)
{
if (i != 0)
len = get_block(daemon->packet, transfer);
if (i == 0)
endcon = 1; /* got last ACK */
if (len == 0)
break;
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
break;
}
}
}
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
}
/* prefetch the block we'll probably need when we get an ACK. */
if (!endcon)
get_block(transfer);
}
if (endcon)
{
strcpy(daemon->namebuff, transfer->file->filename);
@@ -745,12 +749,11 @@ void check_tftp_listeners(time_t now)
}
}
/* packet in daemon->packet as this is called. */
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len)
{
struct ack {
unsigned short op, block;
} *mess = (struct ack *)daemon->packet;
} *mess = (struct ack *)packet;
if (len >= (ssize_t)sizeof(struct ack))
{
@@ -774,7 +777,7 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
transfer->ackprev = new;
block = (((u32)transfer->block_hi) << 16) + (u32)new;
/* Ignore duplicate ACKs and ACKs for blocks we've not yet sent. */
if (block >= transfer->lastack &&
block <= transfer->block)
@@ -783,20 +786,23 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
transfer->retransmit = transfer->start = now;
transfer->backoff = 0;
transfer->lastack = block + 1;
/* We have no easy function from block no. to file offset when
expanding line breaks in netascii mode, so we update the offset here
as each block is acknowledged. This explains why the window size must be
one for a netascii transfer; to avoid the block no. doing anything
other than incrementing by one. */
if (transfer->netascii && block != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
{
transfer->offset += (off_t)transfer->blocksize - (off_t)transfer->expansion;
transfer->lastcarrylf = transfer->carrylf;
}
}
}
else if (ntohs(mess->op) == OP_ERR)
{
char *p = daemon->packet + sizeof(struct ack);
char *end = daemon->packet + len;
char *p = packet + sizeof(struct ack);
char *end = packet + len;
char *err = next(&p, end);
(void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
@@ -873,10 +879,6 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *
} *mess = (struct errmess *)packet;
ssize_t len, ret = 4;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
memset(packet, 0, daemon->packet_buff_sz);
if (file)
sanitise(file);
@@ -900,12 +902,10 @@ static ssize_t tftp_err_oops(char *packet, const char *file)
}
/* return -1 for error, zero for done. */
static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
static ssize_t get_block(struct tftp_transfer *transfer)
{
memset(packet, 0, daemon->packet_buff_sz);
/* we overwrote the buffer... */
daemon->srv_save = NULL;
static off_t saved_offset = 0;
static ssize_t saved_len = 0;
if (transfer->block == 0)
{
@@ -914,8 +914,12 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
struct oackmess {
unsigned short op;
char data[];
} *mess = (struct oackmess *)packet;
} *mess = (struct oackmess *)daemon->packet;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
memset(daemon->packet, 0, daemon->packet_buff_sz);
p = mess->data;
mess->op = htons(OP_OACK);
if (transfer->opt_blocksize)
@@ -939,7 +943,7 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
p += (sprintf(p, "%u", (unsigned int)transfer->windowsize) + 1);
}
return p - packet;
return p - daemon->packet;
}
else
{
@@ -947,7 +951,7 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
struct datamess {
unsigned short op, block;
unsigned char data[];
} *mess = (struct datamess *)packet;
} *mess = (struct datamess *)daemon->packet;
size_t size;
@@ -956,35 +960,49 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
if (transfer->offset > transfer->file->size)
return 0; /* finished */
/* We may have a prefetched block already in the buffer. */
if (daemon->srv_save == transfer && saved_offset == transfer->offset)
return saved_len;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((size = transfer->file->size - transfer->offset) > (size_t)transfer->blocksize)
size = (size_t)transfer->blocksize;
mess->op = htons(OP_DATA);
mess->block = htons((unsigned short)(transfer->block));
if (size != 0 &&
(lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
!read_write(transfer->file->fd, mess->data, size, RW_READ)))
return -1;
if (size != 0)
{
if (transfer->file->posn != transfer->offset &&
lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1)
return -1;
if (!read_write(transfer->file->fd, mess->data, size, RW_READ))
return -1;
transfer->file->posn = transfer->offset + size;
}
/* Map '\n' to CR-LF in netascii mode */
if (transfer->netascii)
{
size_t i;
int newcarrylf;
/* Map '\n' to CR-LF in netascii mode */
transfer->expansion = transfer->carrylf = 0;
transfer->expansion = 0;
for (i = 0, newcarrylf = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->carrylf))
for (i = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->lastcarrylf))
{
transfer->expansion++;
if (size != transfer->blocksize)
size++; /* room in this block */
else if (i == size - 1)
newcarrylf = 1; /* don't expand LF again if it moves to the next block */
transfer->carrylf = 1; /* don't expand LF again if it moves to the next block */
/* make space and insert CR */
memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
@@ -992,11 +1010,13 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
i++;
}
transfer->carrylf = newcarrylf;
}
return size + 4;
daemon->srv_save = transfer;
saved_offset = transfer->offset;
saved_len = size + 4;
return saved_len;
}
}