468 lines
14 KiB
C
468 lines
14 KiB
C
/* dns.c
|
|
|
|
Domain Name Service subroutines. */
|
|
|
|
/*
|
|
* Copyright (c) 2000 Internet Software Consortium.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of The Internet Software Consortium nor the names
|
|
* of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
|
|
* CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* This software has been written for the Internet Software Consortium
|
|
* by Ted Lemon in cooperation with Nominum, Inc.
|
|
* To learn more about the Internet Software Consortium, see
|
|
* ``http://www.isc.org/''. To learn more about Nominum, Inc., see
|
|
* ``http://www.nominum.com''.
|
|
*/
|
|
|
|
#ifndef lint
|
|
static char copyright[] =
|
|
"$Id: dns.c,v 1.3 2000/11/07 13:21:36 tron Exp $ Copyright (c) 2000 The Internet Software Consortium. All rights reserved.\n";
|
|
#endif /* not lint */
|
|
|
|
#include "dhcpd.h"
|
|
#include "arpa/nameser.h"
|
|
|
|
/* This file is kind of a crutch for the BIND 8 nsupdate code, which has
|
|
* itself been cruelly hacked from its original state. What this code
|
|
* does is twofold: first, it maintains a database of zone cuts that can
|
|
* be used to figure out which server should be contacted to update any
|
|
* given domain name. Secondly, it maintains a set of named TSIG keys,
|
|
* and associates those keys with zones. When an update is requested for
|
|
* a particular zone, the key associated with that zone is used for the
|
|
* update.
|
|
*
|
|
* The way this works is that you define the domain name to which an
|
|
* SOA corresponds, and the addresses of some primaries for that domain name:
|
|
*
|
|
* zone FOO.COM {
|
|
* primary 10.0.17.1;
|
|
* secondary 10.0.22.1, 10.0.23.1;
|
|
* key "FOO.COM Key";
|
|
* }
|
|
*
|
|
* If an update is requested for GAZANGA.TOPANGA.FOO.COM, then the name
|
|
* server looks in its database for a zone record for "GAZANGA.TOPANGA.FOO.COM",
|
|
* doesn't find it, looks for one for "TOPANGA.FOO.COM", doesn't find *that*,
|
|
* looks for "FOO.COM", finds it. So it
|
|
* attempts the update to the primary for FOO.COM. If that times out, it
|
|
* tries the secondaries. You can list multiple primaries if you have some
|
|
* kind of magic name server that supports that. You shouldn't list
|
|
* secondaries that don't know how to forward updates (e.g., BIND 8 doesn't
|
|
* support update forwarding, AFAIK). If no TSIG key is listed, the update
|
|
* is attempted without TSIG.
|
|
*
|
|
* The DHCP server tries to find an existing zone for any given name by
|
|
* trying to look up a local zone structure for each domain containing
|
|
* that name, all the way up to '.'. If it finds one cached, it tries
|
|
* to use that one to do the update. That's why it tries to update
|
|
* "FOO.COM" above, even though theoretically it should try GAZANGA...
|
|
* and TOPANGA... first.
|
|
*
|
|
* If the update fails with a predefined or cached zone (we'll get to
|
|
* those in a second), then it tries to find a more specific zone. This
|
|
* is done by looking first for an SOA for GAZANGA.TOPANGA.FOO.COM. Then
|
|
* an SOA for TOPANGA.FOO.COM is sought. If during this search a predefined
|
|
* or cached zone is found, the update fails - there's something wrong
|
|
* somewhere.
|
|
*
|
|
* If a more specific zone _is_ found, that zone is cached for the length of
|
|
* its TTL in the same database as that described above. TSIG updates are
|
|
* never done for cached zones - if you want TSIG updates you _must_
|
|
* write a zone definition linking the key to the zone. In cases where you
|
|
* know for sure what the key is but do not want to hardcode the IP addresses
|
|
* of the primary or secondaries, a zone declaration can be made that doesn't
|
|
* include any primary or secondary declarations. When the DHCP server
|
|
* encounters this while hunting up a matching zone for a name, it looks up
|
|
* the SOA, fills in the IP addresses, and uses that record for the update.
|
|
* If the SOA lookup returns NXRRSET, a warning is printed and the zone is
|
|
* discarded, TSIG key and all. The search for the zone then continues as if
|
|
* the zone record hadn't been found. Zones without IP addresses don't
|
|
* match when initially hunting for a predefined or cached zone to update.
|
|
*
|
|
* When an update is attempted and no predefined or cached zone is found
|
|
* that matches any enclosing domain of the domain being updated, the DHCP
|
|
* server goes through the same process that is done when the update to a
|
|
* predefined or cached zone fails - starting with the most specific domain
|
|
* name (GAZANGA.TOPANGA.FOO.COM) and moving to the least specific (the root),
|
|
* it tries to look up an SOA record. When it finds one, it creates a cached
|
|
* zone and attempts an update, and gives up if the update fails.
|
|
*
|
|
* TSIG keys are defined like this:
|
|
*
|
|
* key "FOO.COM Key" {
|
|
* algorithm HMAC-MD5.SIG-ALG.REG.INT;
|
|
* secret <Base64>;
|
|
* }
|
|
*
|
|
* <Base64> is a number expressed in base64 that represents the key.
|
|
* It's also permissible to use a quoted string here - this will be
|
|
* translated as the ASCII bytes making up the string, and will not
|
|
* include any NUL termination. The key name can be any text string,
|
|
* and the key type must be one of the key types defined in the draft
|
|
* or by the IANA. Currently only the HMAC-MD5... key type is
|
|
* supported.
|
|
*/
|
|
|
|
struct hash_table *tsig_key_hash;
|
|
struct hash_table *dns_zone_hash;
|
|
|
|
#if defined (NSUPDATE)
|
|
isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname,
|
|
struct dns_zone *zone)
|
|
{
|
|
isc_result_t status;
|
|
ns_tsig_key *tkey;
|
|
|
|
if (!zone)
|
|
return ISC_R_NOTFOUND;
|
|
|
|
if (!zone -> key) {
|
|
return ISC_R_KEY_UNKNOWN;
|
|
}
|
|
|
|
if ((!zone -> key -> name ||
|
|
strlen (zone -> key -> name) > NS_MAXDNAME) ||
|
|
(!zone -> key -> algorithm ||
|
|
strlen (zone -> key -> algorithm) > NS_MAXDNAME) ||
|
|
(!zone -> key)) {
|
|
return ISC_R_INVALIDKEY;
|
|
}
|
|
tkey = dmalloc (sizeof *tkey, MDL);
|
|
if (!tkey) {
|
|
nomem:
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
memset (tkey, 0, sizeof *tkey);
|
|
tkey -> data = dmalloc (zone -> key -> key -> len, MDL);
|
|
if (!tkey -> data) {
|
|
dfree (tkey, MDL);
|
|
goto nomem;
|
|
}
|
|
strcpy (tkey -> name, zone -> key -> name);
|
|
strcpy (tkey -> alg, zone -> key -> algorithm);
|
|
memcpy (tkey -> data,
|
|
zone -> key -> key -> value, zone -> key -> key -> len);
|
|
tkey -> len = zone -> key -> key -> len;
|
|
*key = tkey;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
void tkey_free (ns_tsig_key **key)
|
|
{
|
|
if ((*key) -> data)
|
|
dfree ((*key) -> data, MDL);
|
|
dfree ((*key), MDL);
|
|
*key = (ns_tsig_key *)0;
|
|
}
|
|
#endif
|
|
|
|
isc_result_t enter_dns_zone (struct dns_zone *zone)
|
|
{
|
|
struct dns_zone *tz = (struct dns_zone *)0;
|
|
|
|
if (dns_zone_hash) {
|
|
dns_zone_hash_lookup (&tz,
|
|
dns_zone_hash, zone -> name, 0, MDL);
|
|
if (tz == zone) {
|
|
dns_zone_dereference (&tz, MDL);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
if (tz) {
|
|
dns_zone_hash_delete (dns_zone_hash,
|
|
zone -> name, 0, MDL);
|
|
dns_zone_dereference (&tz, MDL);
|
|
}
|
|
} else {
|
|
dns_zone_hash =
|
|
new_hash ((hash_reference)dns_zone_reference,
|
|
(hash_dereference)dns_zone_dereference, 1);
|
|
if (!dns_zone_hash)
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
dns_zone_hash_add (dns_zone_hash, zone -> name, 0, zone, MDL);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name)
|
|
{
|
|
struct dns_zone *tz = (struct dns_zone *)0;
|
|
unsigned len;
|
|
char *tname = (char *)0;
|
|
isc_result_t status;
|
|
|
|
if (!dns_zone_hash)
|
|
return ISC_R_NOTFOUND;
|
|
|
|
len = strlen (name);
|
|
if (len == 0)
|
|
return ISC_R_NOTFOUND;
|
|
|
|
if (name [len - 1] != '.') {
|
|
tname = dmalloc (len + 2, MDL);
|
|
if (!tname)
|
|
return ISC_R_NOMEMORY;;
|
|
strcpy (tname, name);
|
|
tname [len] = '.';
|
|
tname [len + 1] = 0;
|
|
name = tname;
|
|
}
|
|
if (!dns_zone_hash_lookup (zone, dns_zone_hash, name, 0, MDL))
|
|
status = ISC_R_NOTFOUND;
|
|
else
|
|
status = ISC_R_SUCCESS;
|
|
|
|
if (tname)
|
|
dfree (tname, MDL);
|
|
return status;
|
|
}
|
|
|
|
int dns_zone_dereference (ptr, file, line)
|
|
struct dns_zone **ptr;
|
|
const char *file;
|
|
int line;
|
|
{
|
|
int i;
|
|
struct dns_zone *dns_zone;
|
|
|
|
if (!ptr || !*ptr) {
|
|
log_error ("%s(%d): null pointer", file, line);
|
|
#if defined (POINTER_DEBUG)
|
|
abort ();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
dns_zone = *ptr;
|
|
*ptr = (struct dns_zone *)0;
|
|
--dns_zone -> refcnt;
|
|
rc_register (file, line, ptr, dns_zone, dns_zone -> refcnt);
|
|
if (dns_zone -> refcnt > 0)
|
|
return 1;
|
|
|
|
if (dns_zone -> refcnt < 0) {
|
|
log_error ("%s(%d): negative refcnt!", file, line);
|
|
#if defined (DEBUG_RC_HISTORY)
|
|
dump_rc_history ();
|
|
#endif
|
|
#if defined (POINTER_DEBUG)
|
|
abort ();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
if (dns_zone -> name)
|
|
dfree (dns_zone -> name, file, line);
|
|
if (dns_zone -> key)
|
|
omapi_auth_key_dereference (&dns_zone -> key, file, line);
|
|
if (dns_zone -> primary)
|
|
option_cache_dereference (&dns_zone -> primary, file, line);
|
|
if (dns_zone -> secondary)
|
|
option_cache_dereference (&dns_zone -> secondary, file, line);
|
|
dfree (dns_zone, file, line);
|
|
return 1;
|
|
}
|
|
|
|
#if defined (NSUPDATE)
|
|
ns_rcode find_cached_zone (const char *dname, ns_class class,
|
|
char *zname, size_t zsize,
|
|
struct in_addr *addrs, int naddrs, int *naddrout,
|
|
struct dns_zone **zcookie)
|
|
{
|
|
isc_result_t status = ISC_R_NOTFOUND;
|
|
const char *np;
|
|
struct dns_zone *zone = (struct dns_zone *)0;
|
|
struct data_string nsaddrs;
|
|
int ix;
|
|
|
|
/* The absence of the zcookie pointer indicates that we
|
|
succeeded previously, but the update itself failed, meaning
|
|
that we shouldn't use the cached zone. */
|
|
if (!zcookie)
|
|
return ns_r_servfail;
|
|
|
|
/* For each subzone, try to find a cached zone. */
|
|
for (np = dname - 1; np; np = strchr (np, '.')) {
|
|
np++;
|
|
status = dns_zone_lookup (&zone, np);
|
|
if (status == ISC_R_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
if (status != ISC_R_SUCCESS)
|
|
return ns_r_servfail;
|
|
|
|
/* Make sure the zone is valid. */
|
|
if (zone -> timeout && zone -> timeout < cur_time) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return ns_r_servfail;
|
|
}
|
|
|
|
/* Make sure the zone name will fit. */
|
|
if (strlen (zone -> name) > zsize) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return ns_r_servfail;
|
|
}
|
|
strcpy (zname, zone -> name);
|
|
|
|
memset (&nsaddrs, 0, sizeof nsaddrs);
|
|
ix = 0;
|
|
|
|
if (zone -> primary) {
|
|
if (evaluate_option_cache (&nsaddrs, (struct packet *)0,
|
|
(struct lease *)0,
|
|
(struct option_state *)0,
|
|
(struct option_state *)0,
|
|
&global_scope,
|
|
zone -> primary, MDL)) {
|
|
int ip = 0;
|
|
while (ix < naddrs) {
|
|
if (ip + 4 > nsaddrs.len)
|
|
break;
|
|
memcpy (&addrs [ix], &nsaddrs.data [ip], 4);
|
|
ip += 4;
|
|
ix++;
|
|
}
|
|
data_string_forget (&nsaddrs, MDL);
|
|
}
|
|
}
|
|
if (zone -> secondary) {
|
|
if (evaluate_option_cache (&nsaddrs, (struct packet *)0,
|
|
(struct lease *)0,
|
|
(struct option_state *)0,
|
|
(struct option_state *)0,
|
|
&global_scope,
|
|
zone -> secondary, MDL)) {
|
|
int ip = 0;
|
|
while (ix < naddrs) {
|
|
if (ip + 4 > nsaddrs.len)
|
|
break;
|
|
memcpy (&addrs [ix], &nsaddrs.data [ip], 4);
|
|
ip += 4;
|
|
ix++;
|
|
}
|
|
data_string_forget (&nsaddrs, MDL);
|
|
}
|
|
}
|
|
|
|
/* It's not an error for zcookie to have a value here - actually,
|
|
it's quite likely, because res_nupdate cycles through all the
|
|
names in the update looking for their zones. */
|
|
if (!*zcookie)
|
|
dns_zone_reference (zcookie, zone, MDL);
|
|
dns_zone_dereference (&zone, MDL);
|
|
if (naddrout)
|
|
*naddrout = ix;
|
|
return ns_r_noerror;
|
|
}
|
|
|
|
void forget_zone (struct dns_zone **zone)
|
|
{
|
|
dns_zone_dereference (zone, MDL);
|
|
}
|
|
|
|
void repudiate_zone (struct dns_zone **zone)
|
|
{
|
|
/* XXX Currently we're not differentiating between a cached
|
|
XXX zone and a zone that's been repudiated, which means
|
|
XXX that if we reap cached zones, we blow away repudiated
|
|
XXX zones. This isn't a big problem since we're not yet
|
|
XXX caching zones... :'} */
|
|
|
|
(*zone) -> timeout = cur_time - 1;
|
|
dns_zone_dereference (zone, MDL);
|
|
}
|
|
|
|
void cache_found_zone (ns_class class,
|
|
char *zname, struct in_addr *addrs, int naddrs)
|
|
{
|
|
isc_result_t status = ISC_R_NOTFOUND;
|
|
struct dns_zone *zone = (struct dns_zone *)0;
|
|
struct data_string nsaddrs;
|
|
int ix = strlen (zname);
|
|
|
|
if (zname [ix - 1] == '.')
|
|
ix = 0;
|
|
|
|
/* See if there's already such a zone. */
|
|
if (dns_zone_lookup (&zone, zname) == ISC_R_SUCCESS) {
|
|
/* If it's not a dynamic zone, leave it alone. */
|
|
if (!zone -> timeout)
|
|
return;
|
|
/* Address may have changed, so just blow it away. */
|
|
if (zone -> primary)
|
|
option_cache_dereference (&zone -> primary, MDL);
|
|
if (zone -> secondary)
|
|
option_cache_dereference (&zone -> secondary, MDL);
|
|
}
|
|
|
|
if (!dns_zone_allocate (&zone, MDL))
|
|
return;
|
|
|
|
if (!zone -> name) {
|
|
zone -> name =
|
|
dmalloc (strlen (zname) + 1 + (ix != 0), MDL);
|
|
if (!zone -> name) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return;
|
|
}
|
|
strcpy (zone -> name, zname);
|
|
/* Add a trailing '.' if it was missing. */
|
|
if (ix) {
|
|
zone -> name [ix] = '.';
|
|
zone -> name [ix + 1] = 0;
|
|
}
|
|
}
|
|
|
|
/* XXX Need to get the lower-level code to push the actual zone
|
|
XXX TTL up to us. */
|
|
zone -> timeout = cur_time + 1800;
|
|
|
|
if (!option_cache_allocate (&zone -> primary, MDL)) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return;
|
|
}
|
|
if (!buffer_allocate (&zone -> primary -> data.buffer,
|
|
naddrs * sizeof (struct in_addr), MDL)) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return;
|
|
}
|
|
memcpy (zone -> primary -> data.buffer -> data,
|
|
addrs, naddrs * sizeof *addrs);
|
|
zone -> primary -> data.data =
|
|
&zone -> primary -> data.buffer -> data [0];
|
|
zone -> primary -> data.len = naddrs * sizeof *addrs;
|
|
|
|
enter_dns_zone (zone);
|
|
}
|
|
#endif /* NSUPDATE */
|
|
|
|
HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone)
|