Initial code to do authoritative DNS.

This commit is contained in:
Simon Kelley
2012-11-28 21:27:02 +00:00
parent 8e4b87918f
commit 4f7b304f53
15 changed files with 705 additions and 191 deletions

307
src/auth.c Normal file
View File

@@ -0,0 +1,307 @@
/* dnsmasq is Copyright (c) 2000-2012 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
the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dnsmasq.h"
static int filter_zone(struct auth_zone *zone, int flag, struct all_addr *addr_u)
{
struct subnet *subnet;
for (subnet = zone->subnet; subnet; subnet = subnet->next)
{
if (subnet->is6 && (flag & F_IPV4))
continue;
if (!subnet->is6)
{
struct in_addr addr = addr_u->addr.addr4;
struct in_addr mask;
mask.s_addr = (1 << (32 - subnet->prefixlen)) - 1;
if (is_same_net(addr, subnet->addr4, mask))
return 1;
}
#ifdef HAVE_IPV6
else if (is_same_net6(&(addr_u->addr.addr6), &subnet->addr6, subnet->prefixlen))
return 1;
#endif
}
return 0;
}
size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now)
{
char *name = daemon->namebuff;
unsigned char *p, *ansp;
int qtype, qclass;
unsigned int nameoffset;
int q, anscount = 0, authcount = 0;
struct crec *crecp;
int auth = 1, trunc = 0, nxdomain = 1, soa = 0;
struct auth_zone *zone = NULL;
if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
return 0;
/* determine end of question section (we put answers there) */
if (!(ansp = skip_questions(header, qlen)))
return 0; /* bad packet */
/* now process each question, answers go in RRs after the question */
p = (unsigned char *)(header+1);
for (q = ntohs(header->qdcount); q != 0; q--)
{
size_t domainlen, namelen;
unsigned short flag;
int found = 0;
/* save pointer to name for copying into answers */
nameoffset = p - (unsigned char *)header;
/* now extract name as .-concatenated string into name */
if (!extract_name(header, qlen, &p, name, 1, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qclass != C_IN)
continue;
if (qtype == T_PTR)
{
struct all_addr addr;
if (!(flag = in_arpa_name_2_addr(name, &addr)))
continue;
for (zone = daemon->auth_zones; zone; zone = zone->next)
if (filter_zone(zone, flag, &addr))
break;
if (!zone)
{
auth = 0;
continue;
}
if ((crecp = cache_find_by_addr(NULL, &addr, now, flag)))
do {
strcpy(name, cache_get_name(crecp));
if (crecp->flags & F_DHCP && !option_bool(OPT_DHCP_FQDN))
{
char *p = strchr(name, '.');
if (p)
*p = 0; /* must be bare name */
/* add external domain */
strcat(name, ".");
strcat(name, zone->domain);
log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid));
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL,
T_PTR, C_IN, "d", name))
{
anscount++;
found = 1;
}
}
else if (crecp->flags & (F_DHCP | F_HOSTS))
{
domainlen = strlen(zone->domain);
namelen = strlen(name);
if (namelen <= domainlen + 1 ||
name[namelen - domainlen - 1] != '.' ||
!hostname_isequal(zone->domain, &name[namelen - domainlen]))
continue; /* wrong domain */
log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid));
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL,
T_PTR, C_IN, "d", name))
{
anscount++;
found = 1;
}
}
else
continue;
} while ((crecp = cache_find_by_addr(crecp, &addr, now, flag)));
if (!found)
log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | F_AUTH, NULL, &addr, NULL);
continue;
}
namelen = strlen(name);
for (zone = daemon->auth_zones; zone; zone = zone->next)
{
domainlen = strlen(zone->domain);
if (namelen >= domainlen &&
hostname_isequal(zone->domain, &name[namelen - domainlen]))
break;
}
if (!zone)
{
auth = 0;
continue;
}
if (namelen > domainlen && name[namelen - domainlen - 1] != '.')
continue;
if (qtype == T_A)
flag = F_IPV4;
#ifdef HAVE_IPV6
else if (qtype == T_AAAA)
flag = F_IPV6;
#endif
else
{
if (qtype == T_SOA)
{
soa = 1; /* inhibits auth section */
found = 1;
log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>");
if (add_resource_record(header, limit, &trunc, 0, &ansp,
daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
zone->domain, daemon->authserver, daemon->hostmaster,
daemon->soa_sn, daemon->soa_refresh,
daemon->soa_retry, daemon->soa_expiry,
daemon->auth_ttl))
anscount++;
}
else if (qtype == T_NS)
{
soa = 1; /* inhibits auth section */
found = 1;
log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>");
if (add_resource_record(header, limit, &trunc, 0, &ansp,
daemon->auth_ttl, NULL, T_NS, C_IN, "d", zone->domain, daemon->authserver))
anscount++;
}
continue;
}
if (!option_bool(OPT_DHCP_FQDN) && namelen > domainlen + 1)
{
name[namelen - domainlen - 1] = 0; /* remove domain part */
if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
{
if (crecp->flags & F_DHCP)
do
{
nxdomain = 0;
if ((crecp->flags & flag) && filter_zone(zone, flag, &(crecp->addr.addr)))
{
name[namelen - domainlen - 1] = '.'; /* restore domain part */
log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid));
name[namelen - domainlen - 1] = 0; /* remove domain part */
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr))
{
anscount++;
found = 1;
}
}
} while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6)));
}
name[namelen - domainlen - 1] = '.'; /* restore domain part */
}
if (!found && (crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
{
if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
do
{
nxdomain = 0;
if ((crecp->flags & flag) && filter_zone(zone, flag, &(crecp->addr.addr)))
{
log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid));
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr))
{
anscount++;
found = 1;
}
}
} while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6)));
}
if (!found)
log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL);
}
/* Add auth section */
if (auth && !soa)
{
if (anscount != 0 && add_resource_record(header, limit, &trunc, 0, &ansp,
daemon->auth_ttl, NULL, T_NS, C_IN, "d", zone->domain, daemon->authserver))
authcount++;
if (anscount == 0 && add_resource_record(header, limit, &trunc, 0, &ansp,
daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
zone->domain, daemon->authserver, daemon->hostmaster,
daemon->soa_sn, daemon->soa_refresh,
daemon->soa_retry, daemon->soa_expiry,
daemon->auth_ttl))
authcount++;
}
/* done all questions, set up header and return length of result */
/* clear authoritative and truncated flags, set QR flag */
header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR;
/* clear RA flag */
header->hb4 &= ~HB4_RA;
/* authoritive */
if (auth)
header->hb3 |= HB3_AA;
/* truncation */
if (trunc)
header->hb3 |= HB3_TC;
if (anscount == 0 && auth && nxdomain)
SET_RCODE(header, NXDOMAIN);
else
SET_RCODE(header, NOERROR); /* no error */
header->ancount = htons(anscount);
header->nscount = htons(authcount);
return ansp - (unsigned char *)header;
}