version 1.1.1.2, 2014/06/15 16:31:38
|
version 1.1.1.5, 2023/09/27 11:02:07
|
Line 1
|
Line 1
|
/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley | /* dnsmasq is Copyright (c) 2000-2022 Simon Kelley |
|
|
This program is free software; you can redistribute it and/or modify |
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 |
it under the terms of the GNU General Public License as published by |
Line 18
|
Line 18
|
|
|
#ifdef HAVE_TFTP |
#ifdef HAVE_TFTP |
|
|
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix); | 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 free_transfer(struct tftp_transfer *transfer); |
static void free_transfer(struct tftp_transfer *transfer); |
static ssize_t tftp_err(int err, char *packet, char *mess, char *file); | static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2); |
static ssize_t tftp_err_oops(char *packet, char *file); | 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(char *packet, struct tftp_transfer *transfer); |
static char *next(char **p, char *end); |
static char *next(char **p, char *end); |
static void sanitise(char *buf); |
static void sanitise(char *buf); |
Line 38 static void sanitise(char *buf);
|
Line 39 static void sanitise(char *buf);
|
#define ERR_PERM 2 |
#define ERR_PERM 2 |
#define ERR_FULL 3 |
#define ERR_FULL 3 |
#define ERR_ILL 4 |
#define ERR_ILL 4 |
|
#define ERR_TID 5 |
|
|
void tftp_request(struct listener *listen, time_t now) |
void tftp_request(struct listener *listen, time_t now) |
{ |
{ |
Line 50 void tftp_request(struct listener *listen, time_t now)
|
Line 52 void tftp_request(struct listener *listen, time_t now)
|
struct ifreq ifr; |
struct ifreq ifr; |
int is_err = 1, if_index = 0, mtu = 0; |
int is_err = 1, if_index = 0, mtu = 0; |
struct iname *tmp; |
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 */ |
int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */ |
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
int mtuflag = IP_PMTUDISC_DONT; |
int mtuflag = IP_PMTUDISC_DONT; |
Line 59 void tftp_request(struct listener *listen, time_t now)
|
Line 61 void tftp_request(struct listener *listen, time_t now)
|
char *name = NULL; |
char *name = NULL; |
char *prefix = daemon->tftp_prefix; |
char *prefix = daemon->tftp_prefix; |
struct tftp_prefix *pref; |
struct tftp_prefix *pref; |
struct all_addr addra; | union all_addr addra; |
#ifdef HAVE_IPV6 | int family = listen->addr.sa.sa_family; |
/* Can always get recvd interface for IPv6 */ |
/* Can always get recvd interface for IPv6 */ |
int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; | int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6; |
#else | |
int check_dest = !option_bool(OPT_NOWILD); | |
#endif | |
union { |
union { |
struct cmsghdr align; /* this ensures alignment */ |
struct cmsghdr align; /* this ensures alignment */ |
#ifdef HAVE_IPV6 |
|
char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; |
char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; |
#endif |
|
#if defined(HAVE_LINUX_NETWORK) |
#if defined(HAVE_LINUX_NETWORK) |
char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; |
char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; |
#elif defined(HAVE_SOLARIS_NETWORK) |
#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) |
#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 |
#endif |
} control_u; |
} control_u; |
|
|
Line 97 void tftp_request(struct listener *listen, time_t now)
|
Line 96 void tftp_request(struct listener *listen, time_t now)
|
if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) |
if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) |
return; |
return; |
|
|
|
#ifdef HAVE_DUMPFILE |
|
dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, listen->tftpfd); |
|
#endif |
|
|
/* Can always get recvd interface for IPv6 */ |
/* Can always get recvd interface for IPv6 */ |
if (!check_dest) |
if (!check_dest) |
{ |
{ |
if (listen->iface) |
if (listen->iface) |
{ |
{ |
addr = listen->iface->addr; |
addr = listen->iface->addr; |
mtu = listen->iface->mtu; |
|
name = listen->iface->name; |
name = listen->iface->name; |
|
mtu = listen->iface->mtu; |
|
if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) |
|
mtu = daemon->tftp_mtu; |
} |
} |
else |
else |
{ |
{ |
Line 122 void tftp_request(struct listener *listen, time_t now)
|
Line 127 void tftp_request(struct listener *listen, time_t now)
|
if (msg.msg_controllen < sizeof(struct cmsghdr)) |
if (msg.msg_controllen < sizeof(struct cmsghdr)) |
return; |
return; |
|
|
addr.sa.sa_family = listen->family; | addr.sa.sa_family = family; |
|
|
#if defined(HAVE_LINUX_NETWORK) |
#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)) |
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) |
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) |
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) |
{ |
{ |
Line 139 void tftp_request(struct listener *listen, time_t now)
|
Line 144 void tftp_request(struct listener *listen, time_t now)
|
} |
} |
|
|
#elif defined(HAVE_SOLARIS_NETWORK) |
#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)) |
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) |
{ |
{ |
union { |
union { |
Line 155 void tftp_request(struct listener *listen, time_t now)
|
Line 160 void tftp_request(struct listener *listen, time_t now)
|
} |
} |
|
|
#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) |
#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)) |
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) |
{ |
{ |
union { |
union { |
Line 172 void tftp_request(struct listener *listen, time_t now)
|
Line 177 void tftp_request(struct listener *listen, time_t now)
|
|
|
#endif |
#endif |
|
|
#ifdef HAVE_IPV6 | if (family == AF_INET6) |
if (listen->family == AF_INET6) | |
{ |
{ |
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) |
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) |
if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) |
if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) |
Line 188 void tftp_request(struct listener *listen, time_t now)
|
Line 192 void tftp_request(struct listener *listen, time_t now)
|
if_index = p.p->ipi6_ifindex; |
if_index = p.p->ipi6_ifindex; |
} |
} |
} |
} |
#endif |
|
|
|
if (!indextoname(listen->tftpfd, if_index, namebuff)) |
if (!indextoname(listen->tftpfd, if_index, namebuff)) |
return; |
return; |
|
|
name = namebuff; |
name = namebuff; |
|
|
addra.addr.addr4 = addr.in.sin_addr; | addra.addr4 = addr.in.sin_addr; |
|
|
#ifdef HAVE_IPV6 | if (family == AF_INET6) |
if (listen->family == AF_INET6) | addra.addr6 = addr.in6.sin6_addr; |
addra.addr.addr6 = addr.in6.sin6_addr; | |
#endif | |
|
|
if (daemon->tftp_interfaces) |
if (daemon->tftp_interfaces) |
{ |
{ |
Line 215 void tftp_request(struct listener *listen, time_t now)
|
Line 216 void tftp_request(struct listener *listen, time_t now)
|
else |
else |
{ |
{ |
/* Do the same as DHCP */ |
/* 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)) |
if (!option_bool(OPT_CLEVERBIND)) |
enumerate_interfaces(0); |
enumerate_interfaces(0); |
if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) && | if (!loopback_exception(listen->tftpfd, family, &addra, name) && |
!label_exception(if_index, listen->family, &addra) ) | !label_exception(if_index, family, &addra)) |
return; |
return; |
} |
} |
|
|
Line 232 void tftp_request(struct listener *listen, time_t now)
|
Line 233 void tftp_request(struct listener *listen, time_t now)
|
#endif |
#endif |
} |
} |
|
|
strncpy(ifr.ifr_name, name, IF_NAMESIZE); | safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); |
if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) |
if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) |
mtu = ifr.ifr_mtu; | { |
| mtu = ifr.ifr_mtu; |
| if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) |
| mtu = daemon->tftp_mtu; |
| } |
} |
} |
|
|
|
/* Failed to get interface mtu - can use configured value. */ |
|
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) |
if (name) |
{ |
{ |
Line 245 void tftp_request(struct listener *listen, time_t now)
|
Line 287 void tftp_request(struct listener *listen, time_t now)
|
prefix = pref->prefix; |
prefix = pref->prefix; |
} |
} |
|
|
if (listen->family == AF_INET) | if (family == AF_INET) |
{ |
{ |
addr.in.sin_port = htons(port); |
addr.in.sin_port = htons(port); |
#ifdef HAVE_SOCKADDR_SA_LEN |
#ifdef HAVE_SOCKADDR_SA_LEN |
addr.in.sin_len = sizeof(addr.in); |
addr.in.sin_len = sizeof(addr.in); |
#endif |
#endif |
} |
} |
#ifdef HAVE_IPV6 |
|
else |
else |
{ |
{ |
addr.in6.sin6_port = htons(port); |
addr.in6.sin6_port = htons(port); |
Line 262 void tftp_request(struct listener *listen, time_t now)
|
Line 303 void tftp_request(struct listener *listen, time_t now)
|
addr.in6.sin6_len = sizeof(addr.in6); |
addr.in6.sin6_len = sizeof(addr.in6); |
#endif |
#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; |
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); |
free(transfer); |
return; |
return; |
} |
} |
|
|
transfer->peer = peer; |
transfer->peer = peer; |
|
transfer->source = addra; |
|
transfer->if_index = if_index; |
transfer->timeout = now + 2; |
transfer->timeout = now + 2; |
transfer->backoff = 1; |
transfer->backoff = 1; |
transfer->block = 1; |
transfer->block = 1; |
Line 283 void tftp_request(struct listener *listen, time_t now)
|
Line 328 void tftp_request(struct listener *listen, time_t now)
|
transfer->opt_blocksize = transfer->opt_transize = 0; |
transfer->opt_blocksize = transfer->opt_transize = 0; |
transfer->netascii = transfer->carrylf = 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. */ |
/* 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 (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 || |
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
Line 298 void tftp_request(struct listener *listen, time_t now)
|
Line 343 void tftp_request(struct listener *listen, time_t now)
|
{ |
{ |
if (++port <= daemon->end_tftp_port) |
if (++port <= daemon->end_tftp_port) |
{ |
{ |
if (listen->family == AF_INET) | if (family == AF_INET) |
addr.in.sin_port = htons(port); |
addr.in.sin_port = htons(port); |
#ifdef HAVE_IPV6 |
|
else |
else |
addr.in6.sin6_port = htons(port); | addr.in6.sin6_port = htons(port); |
#endif | |
continue; |
continue; |
} |
} |
my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP")); |
my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP")); |
Line 316 void tftp_request(struct listener *listen, time_t now)
|
Line 360 void tftp_request(struct listener *listen, time_t now)
|
|
|
p = packet + 2; |
p = packet + 2; |
end = packet + len; |
end = packet + len; |
| |
if (ntohs(*((unsigned short *)packet)) != OP_RRQ || |
if (ntohs(*((unsigned short *)packet)) != OP_RRQ || |
!(filename = next(&p, end)) || |
!(filename = next(&p, end)) || |
!(mode = next(&p, end)) || |
!(mode = next(&p, end)) || |
(strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0)) |
(strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0)) |
{ |
{ |
len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff); | len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff, NULL); |
is_err = 1; |
is_err = 1; |
} |
} |
else |
else |
Line 336 void tftp_request(struct listener *listen, time_t now)
|
Line 380 void tftp_request(struct listener *listen, time_t now)
|
{ |
{ |
if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) |
if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) |
{ |
{ |
|
/* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */ |
|
int overhead = (family == AF_INET) ? 32 : 52; |
transfer->blocksize = atoi(opt); |
transfer->blocksize = atoi(opt); |
if (transfer->blocksize < 1) |
if (transfer->blocksize < 1) |
transfer->blocksize = 1; |
transfer->blocksize = 1; |
if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4) |
if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4) |
transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4; |
transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4; |
/* 32 bytes for IP, UDP and TFTP headers */ | if (mtu != 0 && transfer->blocksize > (unsigned)mtu - overhead) |
if (mtu != 0 && transfer->blocksize > (unsigned)mtu - 32) | transfer->blocksize = (unsigned)mtu - overhead; |
transfer->blocksize = (unsigned)mtu - 32; | |
transfer->opt_blocksize = 1; |
transfer->opt_blocksize = 1; |
transfer->block = 0; |
transfer->block = 0; |
} |
} |
Line 360 void tftp_request(struct listener *listen, time_t now)
|
Line 405 void tftp_request(struct listener *listen, time_t now)
|
if (*p == '\\') |
if (*p == '\\') |
*p = '/'; |
*p = '/'; |
else if (option_bool(OPT_TFTP_LC)) |
else if (option_bool(OPT_TFTP_LC)) |
*p = tolower(*p); | *p = tolower((unsigned char)*p); |
|
|
strcpy(daemon->namebuff, "/"); |
strcpy(daemon->namebuff, "/"); |
if (prefix) |
if (prefix) |
Line 371 void tftp_request(struct listener *listen, time_t now)
|
Line 416 void tftp_request(struct listener *listen, time_t now)
|
if (prefix[strlen(prefix)-1] != '/') |
if (prefix[strlen(prefix)-1] != '/') |
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); |
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); |
|
|
if (option_bool(OPT_TFTP_APREF)) | if (option_bool(OPT_TFTP_APREF_IP)) |
{ |
{ |
size_t oldlen = strlen(daemon->namebuff); |
size_t oldlen = strlen(daemon->namebuff); |
struct stat statbuf; |
struct stat statbuf; |
Line 383 void tftp_request(struct listener *listen, time_t now)
|
Line 428 void tftp_request(struct listener *listen, time_t now)
|
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) |
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) |
daemon->namebuff[oldlen] = 0; |
daemon->namebuff[oldlen] = 0; |
} |
} |
| |
| if (option_bool(OPT_TFTP_APREF_MAC)) |
| { |
| unsigned char *macaddr = NULL; |
| unsigned char macbuf[DHCP_CHADDR_MAX]; |
| |
| #ifdef HAVE_DHCP |
| if (daemon->dhcp && peer.sa.sa_family == AF_INET) |
| { |
| /* Check if the client IP is in our lease database */ |
| struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr); |
| if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN) |
| macaddr = lease->hwaddr; |
| } |
| #endif |
| |
| /* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */ |
| if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0) |
| macaddr = macbuf; |
| |
| if (macaddr) |
| { |
| size_t oldlen = strlen(daemon->namebuff); |
| struct stat statbuf; |
| |
| snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/", |
| macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); |
| |
| /* remove unique-directory if it doesn't exist */ |
| if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) |
| daemon->namebuff[oldlen] = 0; |
| } |
| } |
| |
/* Absolute pathnames OK if they match prefix */ |
/* Absolute pathnames OK if they match prefix */ |
if (filename[0] == '/') |
if (filename[0] == '/') |
{ |
{ |
Line 396 void tftp_request(struct listener *listen, time_t now)
|
Line 474 void tftp_request(struct listener *listen, time_t now)
|
else if (filename[0] == '/') |
else if (filename[0] == '/') |
daemon->namebuff[0] = 0; |
daemon->namebuff[0] = 0; |
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff)); |
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff)); |
| |
/* check permissions and open file */ |
/* check permissions and open file */ |
if ((transfer->file = check_tftp_fileperm(&len, prefix))) | if ((transfer->file = check_tftp_fileperm(&len, prefix, daemon->addrbuff))) |
{ |
{ |
if ((len = get_block(packet, transfer)) == -1) |
if ((len = get_block(packet, transfer)) == -1) |
len = tftp_err_oops(packet, daemon->namebuff); |
len = tftp_err_oops(packet, daemon->namebuff); |
Line 406 void tftp_request(struct listener *listen, time_t now)
|
Line 484 void tftp_request(struct listener *listen, time_t now)
|
is_err = 0; |
is_err = 0; |
} |
} |
} |
} |
|
|
|
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index); |
|
|
|
#ifdef HAVE_DUMPFILE |
|
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd); |
|
#endif |
|
|
while (sendto(transfer->sockfd, packet, len, 0, |
|
(struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR); |
|
|
|
if (is_err) |
if (is_err) |
free_transfer(transfer); |
free_transfer(transfer); |
else |
else |
Line 419 void tftp_request(struct listener *listen, time_t now)
|
Line 500 void tftp_request(struct listener *listen, time_t now)
|
} |
} |
} |
} |
|
|
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix) | static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client) |
{ |
{ |
char *packet = daemon->packet, *namebuff = daemon->namebuff; |
char *packet = daemon->packet, *namebuff = daemon->namebuff; |
struct tftp_file *file; |
struct tftp_file *file; |
Line 436 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
Line 517 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
{ |
{ |
if (errno == ENOENT) |
if (errno == ENOENT) |
{ |
{ |
*len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff); | *len = tftp_err(ERR_FNF, packet, _("file %s not found for %s"), namebuff, client); |
return NULL; |
return NULL; |
} |
} |
else if (errno == EACCES) |
else if (errno == EACCES) |
Line 459 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
Line 540 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) |
else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) |
goto perm; |
goto perm; |
|
|
/* If we're doing many tranfers from the same file, only | /* If we're doing many transfers from the same file, only |
open it once this saves lots of file descriptors |
open it once this saves lots of file descriptors |
when mass-booting a big cluster, for instance. |
when mass-booting a big cluster, for instance. |
Be conservative and only share when inode and name match |
Be conservative and only share when inode and name match |
Line 489 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
Line 570 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
return file; |
return file; |
|
|
perm: |
perm: |
errno = EACCES; | *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff, strerror(EACCES)); |
*len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff); | |
if (fd != -1) |
if (fd != -1) |
close(fd); |
close(fd); |
return NULL; |
return NULL; |
Line 502 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
Line 582 static struct tftp_file *check_tftp_fileperm(ssize_t *
|
return NULL; |
return NULL; |
} |
} |
|
|
void check_tftp_listeners(fd_set *rset, time_t now) | void check_tftp_listeners(time_t now) |
{ |
{ |
struct tftp_transfer *transfer, *tmp, **up; |
struct tftp_transfer *transfer, *tmp, **up; |
ssize_t len; |
|
|
|
struct ack { | /* In single port mode, all packets come via port 69 and tftp_request() */ |
unsigned short op, block; | if (!option_bool(OPT_SINGLE_PORT)) |
} *mess = (struct ack *)daemon->packet; | for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) |
| if (poll_check(transfer->sockfd, POLLIN)) |
/* 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); | |
| |
if (FD_ISSET(transfer->sockfd, rset)) | |
{ |
{ |
|
union mysockaddr peer; |
|
socklen_t addr_len = sizeof(union mysockaddr); |
|
ssize_t len; |
|
|
/* we overwrote the buffer... */ |
/* we overwrote the buffer... */ |
daemon->srv_save = NULL; |
daemon->srv_save = NULL; |
| |
if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) | if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0) |
{ |
{ |
if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) | if (sockaddr_isequal(&peer, &transfer->peer)) |
| handle_tftp(now, transfer, len); |
| else |
{ |
{ |
/* Got ack, ensure we take the (re)transmit path */ | /* Wrong source address. See rfc1350 para 4. */ |
transfer->timeout = now; | prettyprint_addr(&peer, daemon->addrbuff); |
transfer->backoff = 0; | len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL); |
if (transfer->block++ != 0) | while(retry_send(sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer)))); |
transfer->offset += transfer->blocksize - transfer->expansion; | |
| #ifdef HAVE_DUMPFILE |
| dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd); |
| #endif |
} |
} |
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; |
|
} |
|
} |
} |
} |
} |
|
|
|
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) |
|
{ |
|
tmp = transfer->next; |
|
|
if (difftime(now, transfer->timeout) >= 0.0) |
if (difftime(now, transfer->timeout) >= 0.0) |
{ |
{ |
int endcon = 0; |
int endcon = 0; |
|
ssize_t len; |
|
|
/* timeout, retransmit */ |
/* timeout, retransmit */ |
transfer->timeout += 1 + (1<<transfer->backoff); | transfer->timeout += 1 + (1<<(transfer->backoff/2)); |
|
|
/* we overwrote the buffer... */ |
/* we overwrote the buffer... */ |
daemon->srv_save = NULL; |
daemon->srv_save = NULL; |
| |
if ((len = get_block(daemon->packet, transfer)) == -1) |
if ((len = get_block(daemon->packet, transfer)) == -1) |
{ |
{ |
len = tftp_err_oops(daemon->packet, transfer->file->filename); |
len = tftp_err_oops(daemon->packet, transfer->file->filename); |
endcon = 1; |
endcon = 1; |
} |
} |
/* don't complain about timeout when we're awaiting the last | else if (++transfer->backoff > 7) |
ACK, some clients never send it */ | |
else if (++transfer->backoff > 7 && len != 0) | |
{ |
{ |
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; |
len = 0; |
} |
} |
|
|
if (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); |
| #ifdef HAVE_DUMPFILE |
| dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd); |
| #endif |
| } |
|
|
if (endcon || len == 0) |
if (endcon || len == 0) |
{ |
{ |
strcpy(daemon->namebuff, transfer->file->filename); |
strcpy(daemon->namebuff, transfer->file->filename); |
sanitise(daemon->namebuff); |
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); |
my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff); |
/* unlink */ |
/* unlink */ |
*up = tmp; |
*up = tmp; |
Line 605 void check_tftp_listeners(fd_set *rset, time_t now)
|
Line 677 void check_tftp_listeners(fd_set *rset, time_t now)
|
up = &transfer->next; |
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) |
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) |
if (transfer->file && (--transfer->file->refcount) == 0) |
{ |
{ |
close(transfer->file->fd); |
close(transfer->file->fd); |
free(transfer->file); |
free(transfer->file); |
} |
} |
|
|
free(transfer); |
free(transfer); |
} |
} |
|
|
Line 641 static void sanitise(char *buf)
|
Line 758 static void sanitise(char *buf)
|
|
|
} |
} |
|
|
static ssize_t tftp_err(int err, char *packet, char *message, char *file) | #define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */ |
| static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2) |
{ |
{ |
struct errmess { |
struct errmess { |
unsigned short op, err; |
unsigned short op, err; |
char message[]; |
char message[]; |
} *mess = (struct errmess *)packet; |
} *mess = (struct errmess *)packet; |
ssize_t ret = 4; | ssize_t len, ret = 4; |
char *errstr = strerror(errno); | |
| memset(packet, 0, daemon->packet_buff_sz); |
| if (file) |
| sanitise(file); |
|
|
sanitise(file); |
|
|
|
mess->op = htons(OP_ERR); |
mess->op = htons(OP_ERR); |
mess->err = htons(err); |
mess->err = htons(err); |
ret += (snprintf(mess->message, 500, message, file, errstr) + 1); | len = snprintf(mess->message, MAXMESSAGE, message, file, arg2); |
my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); | ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */ |
|
|
|
if (err != ERR_FNF || !option_bool(OPT_QUIET_TFTP)) |
|
my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); |
|
|
return ret; |
return ret; |
} |
} |
|
|
static ssize_t tftp_err_oops(char *packet, char *file) | static ssize_t tftp_err_oops(char *packet, const char *file) |
{ |
{ |
/* May have >1 refs to file, so potentially mangle a copy of the name */ |
/* May have >1 refs to file, so potentially mangle a copy of the name */ |
strcpy(daemon->namebuff, file); | if (file != daemon->namebuff) |
return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff); | strcpy(daemon->namebuff, file); |
| return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff, strerror(errno)); |
} |
} |
|
|
/* return -1 for error, zero for done. */ |
/* return -1 for error, zero for done. */ |
static ssize_t get_block(char *packet, struct tftp_transfer *transfer) |
static ssize_t get_block(char *packet, struct tftp_transfer *transfer) |
{ |
{ |
|
memset(packet, 0, daemon->packet_buff_sz); |
|
|
if (transfer->block == 0) |
if (transfer->block == 0) |
{ |
{ |
/* send OACK */ |
/* send OACK */ |
Line 684 static ssize_t get_block(char *packet, struct tftp_tra
|
Line 809 static ssize_t get_block(char *packet, struct tftp_tra
|
if (transfer->opt_blocksize) |
if (transfer->opt_blocksize) |
{ |
{ |
p += (sprintf(p, "blksize") + 1); |
p += (sprintf(p, "blksize") + 1); |
p += (sprintf(p, "%d", transfer->blocksize) + 1); | p += (sprintf(p, "%u", transfer->blocksize) + 1); |
} |
} |
if (transfer->opt_transize) |
if (transfer->opt_transize) |
{ |
{ |