954 lines
28 KiB
C
954 lines
28 KiB
C
/* dns.c
|
|
|
|
Domain Name Service subroutines. */
|
|
|
|
/*
|
|
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
|
|
* Copyright (c) 2001-2003 by Internet Software Consortium
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* Internet Systems Consortium, Inc.
|
|
* 950 Charter Street
|
|
* Redwood City, CA 94063
|
|
* <info@isc.org>
|
|
* http://www.isc.org/
|
|
*
|
|
* This software has been written for Internet Systems Consortium
|
|
* by Ted Lemon in cooperation with Nominum, Inc.
|
|
* To learn more about Internet Systems 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.6 2005/08/11 17:13:21 drochner Exp $ Copyright (c) 2004 Internet Systems Consortium. All rights reserved.\n";
|
|
#endif /* not lint */
|
|
|
|
#include "dhcpd.h"
|
|
#include "arpa/nameser.h"
|
|
#include "dst/md5.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.
|
|
*/
|
|
|
|
dns_zone_hash_t *dns_zone_hash;
|
|
|
|
#if defined (NSUPDATE)
|
|
isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname,
|
|
struct dns_zone *zone)
|
|
{
|
|
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) ||
|
|
(!zone -> key -> key) ||
|
|
(zone -> key -> key -> len == 0)) {
|
|
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 {
|
|
if (!dns_zone_new_hash (&dns_zone_hash, 1, MDL))
|
|
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)
|
|
{
|
|
int 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 ((unsigned)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;
|
|
{
|
|
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, 1, RC_MISC);
|
|
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 (dns_zone);
|
|
#endif
|
|
#if defined (POINTER_DEBUG)
|
|
abort ();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
if (dns_zone -> name)
|
|
dfree (dns_zone -> name, file, line);
|
|
#if !defined (SMALL)
|
|
if (dns_zone -> key)
|
|
omapi_auth_key_dereference (&dns_zone -> key, file, line);
|
|
#endif
|
|
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)
|
|
isc_result_t 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 ISC_R_NOTFOUND;
|
|
|
|
/* We can't look up a null zone. */
|
|
if (!dname || !*dname)
|
|
return ISC_R_INVALIDARG;
|
|
|
|
/* For each subzone, try to find a cached zone. */
|
|
for (np = dname; np; np = strchr (np, '.')) {
|
|
np++;
|
|
status = dns_zone_lookup (&zone, np);
|
|
if (status == ISC_R_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
if (status != ISC_R_SUCCESS)
|
|
return status;
|
|
|
|
/* Make sure the zone is valid. */
|
|
if (zone -> timeout && zone -> timeout < cur_time) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return ISC_R_CANCELED;
|
|
}
|
|
|
|
/* Make sure the zone name will fit. */
|
|
if (strlen (zone -> name) > zsize) {
|
|
dns_zone_dereference (&zone, MDL);
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
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 client_state *)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 client_state *)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 ISC_R_SUCCESS;
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct dns_zone *zone = (struct dns_zone *)0;
|
|
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);
|
|
} else 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);
|
|
}
|
|
|
|
/* Have to use TXT records for now. */
|
|
#define T_DHCID T_TXT
|
|
|
|
int get_dhcid (struct data_string *id,
|
|
int type, const u_int8_t *data, unsigned len)
|
|
{
|
|
unsigned char buf[MD5_DIGEST_LENGTH];
|
|
MD5_CTX md5;
|
|
int i;
|
|
|
|
/* Types can only be 0..(2^16)-1. */
|
|
if (type < 0 || type > 65535)
|
|
return 0;
|
|
|
|
/* Hexadecimal MD5 digest plus two byte type and NUL. */
|
|
if (!buffer_allocate (&id -> buffer,
|
|
(MD5_DIGEST_LENGTH * 2) + 3, MDL))
|
|
return 0;
|
|
id -> data = id -> buffer -> data;
|
|
|
|
/*
|
|
* DHCP clients and servers should use the following forms of client
|
|
* identification, starting with the most preferable, and finishing
|
|
* with the least preferable. If the client does not send any of these
|
|
* forms of identification, the DHCP/DDNS interaction is not defined by
|
|
* this specification. The most preferable form of identification is
|
|
* the Globally Unique Identifier Option [TBD]. Next is the DHCP
|
|
* Client Identifier option. Last is the client's link-layer address,
|
|
* as conveyed in its DHCPREQUEST message. Implementors should note
|
|
* that the link-layer address cannot be used if there are no
|
|
* significant bytes in the chaddr field of the DHCP client's request,
|
|
* because this does not constitute a unique identifier.
|
|
* -- "Interaction between DHCP and DNS"
|
|
* <draft-ietf-dhc-dhcp-dns-12.txt>
|
|
* M. Stapp, Y. Rekhter
|
|
*/
|
|
|
|
/* Put the type in the first two bytes. */
|
|
id -> buffer -> data [0] = "0123456789abcdef" [type >> 4];
|
|
id -> buffer -> data [1] = "0123456789abcdef" [type % 15];
|
|
|
|
/* Mash together an MD5 hash of the identifier. */
|
|
MD5_Init (&md5);
|
|
MD5_Update (&md5, data, len);
|
|
MD5_Final (buf, &md5);
|
|
|
|
/* Convert into ASCII. */
|
|
for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
|
|
id -> buffer -> data [i * 2 + 2] =
|
|
"0123456789abcdef" [(buf [i] >> 4) & 0xf];
|
|
id -> buffer -> data [i * 2 + 3] =
|
|
"0123456789abcdef" [buf [i] & 0xf];
|
|
}
|
|
id -> len = MD5_DIGEST_LENGTH * 2 + 2;
|
|
id -> buffer -> data [id -> len] = 0;
|
|
id -> terminated = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Now for the DDNS update code that is shared between client and
|
|
server... */
|
|
|
|
isc_result_t ddns_update_a (struct data_string *ddns_fwd_name,
|
|
struct iaddr ddns_addr,
|
|
struct data_string *ddns_dhcid,
|
|
unsigned long ttl, int rrsetp)
|
|
{
|
|
ns_updque updqueue;
|
|
ns_updrec *updrec;
|
|
isc_result_t result;
|
|
char ddns_address [16];
|
|
|
|
if (ddns_addr.len != 4)
|
|
return ISC_R_INVALIDARG;
|
|
|
|
/* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe% */
|
|
sprintf (ddns_address, "%u.%u.%u.%u",
|
|
ddns_addr.iabuf[0], ddns_addr.iabuf[1],
|
|
ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
|
|
|
|
/*
|
|
* When a DHCP client or server intends to update an A RR, it first
|
|
* prepares a DNS UPDATE query which includes as a prerequisite the
|
|
* assertion that the name does not exist. The update section of the
|
|
* query attempts to add the new name and its IP address mapping (an A
|
|
* RR), and the DHCID RR with its unique client-identity.
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
ISC_LIST_INIT (updqueue);
|
|
|
|
/*
|
|
* A RR does not exist.
|
|
*/
|
|
updrec = minires_mkupdrec (S_PREREQ,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)0;
|
|
updrec -> r_size = 0;
|
|
updrec -> r_opcode = rrsetp ? NXRRSET : NXDOMAIN;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Add A RR.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, ttl);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)ddns_address;
|
|
updrec -> r_size = strlen (ddns_address);
|
|
updrec -> r_opcode = ADD;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Add DHCID RR.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_DHCID, ttl);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = ddns_dhcid -> data;
|
|
updrec -> r_size = ddns_dhcid -> len;
|
|
updrec -> r_opcode = ADD;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Attempt to perform the update.
|
|
*/
|
|
result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
|
|
|
|
#ifdef DEBUG_DNS_UPDATES
|
|
print_dns_status ((int)result, &updqueue);
|
|
#endif
|
|
|
|
/*
|
|
* If this update operation succeeds, the updater can conclude that it
|
|
* has added a new name whose only RRs are the A and DHCID RR records.
|
|
* The A RR update is now complete (and a client updater is finished,
|
|
* while a server might proceed to perform a PTR RR update).
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
log_info ("Added new forward map from %.*s to %s",
|
|
(int)ddns_fwd_name -> len,
|
|
(const char *)ddns_fwd_name -> data, ddns_address);
|
|
goto error;
|
|
}
|
|
|
|
|
|
/*
|
|
* If the first update operation fails with YXDOMAIN, the updater can
|
|
* conclude that the intended name is in use. The updater then
|
|
* attempts to confirm that the DNS name is not being used by some
|
|
* other host. The updater prepares a second UPDATE query in which the
|
|
* prerequisite is that the desired name has attached to it a DHCID RR
|
|
* whose contents match the client identity. The update section of
|
|
* this query deletes the existing A records on the name, and adds the
|
|
* A record that matches the DHCP binding and the DHCID RR with the
|
|
* client identity.
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
if (result != (rrsetp ? ISC_R_YXRRSET : ISC_R_YXDOMAIN)) {
|
|
log_error ("Unable to add forward map from %.*s to %s: %s",
|
|
(int)ddns_fwd_name -> len,
|
|
(const char *)ddns_fwd_name -> data, ddns_address,
|
|
isc_result_totext (result));
|
|
goto error;
|
|
}
|
|
|
|
while (!ISC_LIST_EMPTY (updqueue)) {
|
|
updrec = ISC_LIST_HEAD (updqueue);
|
|
ISC_LIST_UNLINK (updqueue, updrec, r_link);
|
|
minires_freeupdrec (updrec);
|
|
}
|
|
|
|
/*
|
|
* DHCID RR exists, and matches client identity.
|
|
*/
|
|
updrec = minires_mkupdrec (S_PREREQ,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_DHCID, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = ddns_dhcid -> data;
|
|
updrec -> r_size = ddns_dhcid -> len;
|
|
updrec -> r_opcode = YXRRSET;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Delete A RRset.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)0;
|
|
updrec -> r_size = 0;
|
|
updrec -> r_opcode = DELETE;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Add A RR.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, ttl);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)ddns_address;
|
|
updrec -> r_size = strlen (ddns_address);
|
|
updrec -> r_opcode = ADD;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Attempt to perform the update.
|
|
*/
|
|
result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (result == YXRRSET || result == YXDOMAIN ||
|
|
result == NXRRSET || result == NXDOMAIN)
|
|
log_error ("Forward map from %.*s to %s already in use",
|
|
(int)ddns_fwd_name -> len,
|
|
(const char *)ddns_fwd_name -> data,
|
|
ddns_address);
|
|
else
|
|
log_error ("Can't update forward map %.*s to %s: %s",
|
|
(int)ddns_fwd_name -> len,
|
|
(const char *)ddns_fwd_name -> data,
|
|
ddns_address, isc_result_totext (result));
|
|
|
|
} else {
|
|
log_info ("Added new forward map from %.*s to %s",
|
|
(int)ddns_fwd_name -> len,
|
|
(const char *)ddns_fwd_name -> data, ddns_address);
|
|
}
|
|
#if defined (DEBUG_DNS_UPDATES)
|
|
print_dns_status ((int)result, &updqueue);
|
|
#endif
|
|
|
|
/*
|
|
* If this query succeeds, the updater can conclude that the current
|
|
* client was the last client associated with the domain name, and that
|
|
* the name now contains the updated A RR. The A RR update is now
|
|
* complete (and a client updater is finished, while a server would
|
|
* then proceed to perform a PTR RR update).
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
/*
|
|
* If the second query fails with NXRRSET, the updater must conclude
|
|
* that the client's desired name is in use by another host. At this
|
|
* juncture, the updater can decide (based on some administrative
|
|
* configuration outside of the scope of this document) whether to let
|
|
* the existing owner of the name keep that name, and to (possibly)
|
|
* perform some name disambiguation operation on behalf of the current
|
|
* client, or to replace the RRs on the name with RRs that represent
|
|
* the current client. If the configured policy allows replacement of
|
|
* existing records, the updater submits a query that deletes the
|
|
* existing A RR and the existing DHCID RR, adding A and DHCID RRs that
|
|
* represent the IP address and client-identity of the new client.
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
error:
|
|
while (!ISC_LIST_EMPTY (updqueue)) {
|
|
updrec = ISC_LIST_HEAD (updqueue);
|
|
ISC_LIST_UNLINK (updqueue, updrec, r_link);
|
|
minires_freeupdrec (updrec);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
isc_result_t ddns_remove_a (struct data_string *ddns_fwd_name,
|
|
struct iaddr ddns_addr,
|
|
struct data_string *ddns_dhcid)
|
|
{
|
|
ns_updque updqueue;
|
|
ns_updrec *updrec;
|
|
isc_result_t result = SERVFAIL;
|
|
char ddns_address [16];
|
|
|
|
if (ddns_addr.len != 4)
|
|
return ISC_R_INVALIDARG;
|
|
|
|
/* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe% */
|
|
sprintf (ddns_address, "%u.%u.%u.%u",
|
|
ddns_addr.iabuf[0], ddns_addr.iabuf[1],
|
|
ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
|
|
|
|
/*
|
|
* The entity chosen to handle the A record for this client (either the
|
|
* client or the server) SHOULD delete the A record that was added when
|
|
* the lease was made to the client.
|
|
*
|
|
* In order to perform this delete, the updater prepares an UPDATE
|
|
* query which contains two prerequisites. The first prerequisite
|
|
* asserts that the DHCID RR exists whose data is the client identity
|
|
* described in Section 4.3. The second prerequisite asserts that the
|
|
* data in the A RR contains the IP address of the lease that has
|
|
* expired or been released.
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
ISC_LIST_INIT (updqueue);
|
|
|
|
/*
|
|
* DHCID RR exists, and matches client identity.
|
|
*/
|
|
updrec = minires_mkupdrec (S_PREREQ,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_DHCID,0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = ddns_dhcid -> data;
|
|
updrec -> r_size = ddns_dhcid -> len;
|
|
updrec -> r_opcode = YXRRSET;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* A RR matches the expiring lease.
|
|
*/
|
|
updrec = minires_mkupdrec (S_PREREQ,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)ddns_address;
|
|
updrec -> r_size = strlen (ddns_address);
|
|
updrec -> r_opcode = YXRRSET;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
|
|
/*
|
|
* Delete appropriate A RR.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)ddns_address;
|
|
updrec -> r_size = strlen (ddns_address);
|
|
updrec -> r_opcode = DELETE;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
/*
|
|
* Attempt to perform the update.
|
|
*/
|
|
result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
|
|
print_dns_status ((int)result, &updqueue);
|
|
|
|
/*
|
|
* If the query fails, the updater MUST NOT delete the DNS name. It
|
|
* may be that the host whose lease on the server has expired has moved
|
|
* to another network and obtained a lease from a different server,
|
|
* which has caused the client's A RR to be replaced. It may also be
|
|
* that some other client has been configured with a name that matches
|
|
* the name of the DHCP client, and the policy was that the last client
|
|
* to specify the name would get the name. In this case, the DHCID RR
|
|
* will no longer match the updater's notion of the client-identity of
|
|
* the host pointed to by the DNS name.
|
|
* -- "Interaction between DHCP and DNS"
|
|
*/
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* If the rrset isn't there, we didn't need to do the
|
|
delete, which is success. */
|
|
if (result == ISC_R_NXRRSET || result == ISC_R_NXDOMAIN)
|
|
result = ISC_R_SUCCESS;
|
|
goto error;
|
|
}
|
|
|
|
while (!ISC_LIST_EMPTY (updqueue)) {
|
|
updrec = ISC_LIST_HEAD (updqueue);
|
|
ISC_LIST_UNLINK (updqueue, updrec, r_link);
|
|
minires_freeupdrec (updrec);
|
|
}
|
|
|
|
/* If the deletion of the A succeeded, and there are no A records
|
|
left for this domain, then we can blow away the DHCID record
|
|
as well. We can't blow away the DHCID record above because
|
|
it's possible that more than one A has been added to this
|
|
domain name. */
|
|
ISC_LIST_INIT (updqueue);
|
|
|
|
/*
|
|
* A RR does not exist.
|
|
*/
|
|
updrec = minires_mkupdrec (S_PREREQ,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_A, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = (unsigned char *)0;
|
|
updrec -> r_size = 0;
|
|
updrec -> r_opcode = NXRRSET;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
/*
|
|
* Delete appropriate DHCID RR.
|
|
*/
|
|
updrec = minires_mkupdrec (S_UPDATE,
|
|
(const char *)ddns_fwd_name -> data,
|
|
C_IN, T_DHCID, 0);
|
|
if (!updrec) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
updrec -> r_data = ddns_dhcid -> data;
|
|
updrec -> r_size = ddns_dhcid -> len;
|
|
updrec -> r_opcode = DELETE;
|
|
|
|
ISC_LIST_APPEND (updqueue, updrec, r_link);
|
|
|
|
/*
|
|
* Attempt to perform the update.
|
|
*/
|
|
result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
|
|
print_dns_status ((int)result, &updqueue);
|
|
|
|
/* Fall through. */
|
|
error:
|
|
|
|
while (!ISC_LIST_EMPTY (updqueue)) {
|
|
updrec = ISC_LIST_HEAD (updqueue);
|
|
ISC_LIST_UNLINK (updqueue, updrec, r_link);
|
|
minires_freeupdrec (updrec);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#endif /* NSUPDATE */
|
|
|
|
HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone, dns_zone_hash_t,
|
|
dns_zone_reference, dns_zone_dereference)
|