]> git.ipfire.org Git - thirdparty/dhcp.git/blobdiff - common/dns.c
Merge changes between 3.0rc7 and 3.0rc8pl2.
[thirdparty/dhcp.git] / common / dns.c
index abd329dea3abb3e089757b75aa1cc42a21256948..49f52b338a9d1e08eb9840d586d42f6b62ea4252 100644 (file)
@@ -3,7 +3,7 @@
    Domain Name Service subroutines. */
 
 /*
- * Copyright (c) 2000 Internet Software Consortium.
+ * Copyright (c) 2001 Internet Software Consortium.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -42,7 +42,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: dns.c,v 1.35 2001/02/22 07:37:13 mellon Exp $ Copyright (c) 2000 The Internet Software Consortium.  All rights reserved.\n";
+"$Id: dns.c,v 1.36 2001/06/27 00:29:46 mellon Exp $ Copyright (c) 2001 The Internet Software Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -151,7 +151,9 @@ isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname,
             strlen (zone -> key -> name) > NS_MAXDNAME) ||
            (!zone -> key -> algorithm ||
             strlen (zone -> key -> algorithm) > NS_MAXDNAME) ||
-           (!zone -> key)) {
+           (!zone -> key) ||
+           (!zone -> key -> key) ||
+           (zone -> key -> key -> len == 0)) {
                return ISC_R_INVALIDKEY;
        }
        tkey = dmalloc (sizeof *tkey, MDL);
@@ -202,7 +204,8 @@ isc_result_t enter_dns_zone (struct dns_zone *zone)
        } else {
                dns_zone_hash =
                        new_hash ((hash_reference)dns_zone_reference,
-                                 (hash_dereference)dns_zone_dereference, 1);
+                                 (hash_dereference)dns_zone_dereference, 1,
+                                 MDL);
                if (!dns_zone_hash)
                        return ISC_R_NOMEMORY;
        }
@@ -213,7 +216,7 @@ isc_result_t enter_dns_zone (struct dns_zone *zone)
 isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name)
 {
        struct dns_zone *tz = (struct dns_zone *)0;
-       unsigned len;
+       int len;
        char *tname = (char *)0;
        isc_result_t status;
 
@@ -222,7 +225,7 @@ isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name)
 
        len = strlen (name);
        if (name [len - 1] != '.') {
-               tname = dmalloc (len + 2, MDL);
+               tname = dmalloc ((unsigned)len + 2, MDL);
                if (!tname)
                        return ISC_R_NOMEMORY;;
                strcpy (tname, name);
@@ -260,14 +263,14 @@ int dns_zone_dereference (ptr, file, line)
        dns_zone = *ptr;
        *ptr = (struct dns_zone *)0;
        --dns_zone -> refcnt;
-       rc_register (file, line, ptr, dns_zone, dns_zone -> refcnt);
+       rc_register (file, line, ptr, dns_zone, dns_zone -> refcnt, 1);
        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 ();
+               dump_rc_history (dns_zone);
 #endif
 #if defined (POINTER_DEBUG)
                abort ();
@@ -312,7 +315,7 @@ isc_result_t find_cached_zone (const char *dname, ns_class class,
                return ISC_R_INVALIDARG;
 
        /* For each subzone, try to find a cached zone. */
-       for (np = dname - 1; np; np = strchr (np, '.')) {
+       for (np = dname; np; np = strchr (np, '.')) {
                np++;
                status = dns_zone_lookup (&zone, np);
                if (status == ISC_R_SUCCESS)
@@ -466,12 +469,21 @@ void cache_found_zone (ns_class class,
        enter_dns_zone (zone);
 }
 
-int get_dhcid (struct data_string *id, struct lease *lease)
+/* 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;
@@ -494,27 +506,13 @@ int get_dhcid (struct data_string *id, struct lease *lease)
         *      M. Stapp, Y. Rekhter
         */
 
-       MD5_Init (&md5);
-
-       if (lease -> uid) {
-               id -> buffer -> data [0] =
-                       "0123456789abcdef" [DHO_DHCP_CLIENT_IDENTIFIER >> 4];
-               id -> buffer -> data [1] =
-                       "0123456789abcdef" [DHO_DHCP_CLIENT_IDENTIFIER % 15];
-               /* Use the DHCP Client Identifier option. */
-               MD5_Update (&md5, lease -> uid, lease -> uid_len);
-       } else if (lease -> hardware_addr.hlen) {
-               id -> buffer -> data [0] = '0';
-               id -> buffer -> data [1] = '0';
-               /* Use the link-layer address. */
-               MD5_Update (&md5,
-                           lease -> hardware_addr.hbuf,
-                           lease -> hardware_addr.hlen);
-       } else {
-               /* Uh-oh.  Something isn't right here. */
-               return 1;
-       }
+       /* 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. */
@@ -528,8 +526,417 @@ int get_dhcid (struct data_string *id, struct lease *lease)
        id -> buffer -> data [id -> len] = 0;
        id -> terminated = 1;
 
-       return 0;
+       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;
+#ifndef NO_SNPRINTF
+       snprintf (ddns_address, 16, "%d.%d.%d.%d",
+                 ddns_addr.iabuf[0], ddns_addr.iabuf[1],
+                 ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
+#else
+       sprintf (ddns_address, "%d.%d.%d.%d",
+                ddns_addr.iabuf[0], ddns_addr.iabuf[1],
+                ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
+#endif
+
+       /*
+        * 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));
+
+       print_dns_status ((int)result, &updqueue);
+
+       while (!ISC_LIST_EMPTY (updqueue)) {
+               updrec = ISC_LIST_HEAD (updqueue);
+               ISC_LIST_UNLINK (updqueue, updrec, r_link);
+               minires_freeupdrec (updrec);
+       }
+
+
+       /*
+        * 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)
+               return result;
+
+
+       /*
+        * 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))
+               return result;
+
+
+       /*
+        * 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));
+
+       print_dns_status ((int)result, &updqueue);
+
+       /*
+        * 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;
+
+#ifndef NO_SNPRINTF
+       snprintf (ddns_address, 16, "%d.%d.%d.%d",
+                 ddns_addr.iabuf[0], ddns_addr.iabuf[1],
+                 ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
+#else
+       sprintf (ddns_address, "%d.%d.%d.%d",
+                ddns_addr.iabuf[0], ddns_addr.iabuf[1],
+                ddns_addr.iabuf[2], ddns_addr.iabuf[3]);
+#endif
+
+
+       /*
+        * 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)
+               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)