]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- Document update process.
authorTed Lemon <source@isc.org>
Mon, 6 Mar 2000 23:13:35 +0000 (23:13 +0000)
committerTed Lemon <source@isc.org>
Mon, 6 Mar 2000 23:13:35 +0000 (23:13 +0000)
- Add database management for tsig keys and DNS zones.

common/dns.c

index 7423728a09b823be2a51d0e9feb8a18049c9b34e..f4132ef7d5f4f0e4bd71df7f12dcdd015bf2bb69 100644 (file)
@@ -3,7 +3,7 @@
    Domain Name Service subroutines. */
 
 /*
- * Copyright (c) 1996-1999 Internet Software Consortium.
+ * Copyright (c) 2000 Internet Software Consortium.
  * Use is subject to license terms which appear in the file named
  * ISC-LICENSE that should have accompanied this file when you
  * received it.   If a file named ISC-LICENSE did not accompany this
 
 #ifndef lint
 static char copyright[] =
-"$Id: dns.c,v 1.16 2000/02/03 03:43:51 mellon Exp $ Copyright (c) 1997 The Internet Software Consortium.  All rights reserved.\n";
+"$Id: dns.c,v 1.17 2000/03/06 23:13:35 mellon Exp $ Copyright (c) 2000 The Internet Software Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
 #include "arpa/nameser.h"
 
-int dns_protocol_initialized;
-int dns_protocol_fd;
-
-static unsigned addlabel PROTO ((u_int8_t *, const char *));
-static int skipname PROTO ((u_int8_t *));
-static int copy_out_name PROTO ((u_int8_t *, u_int8_t *, char *));
-static int nslookup PROTO ((u_int8_t, char *, int, u_int16_t, u_int16_t));
-static int zonelookup PROTO ((u_int8_t, char *, int, u_int16_t));
-u_int16_t dns_port;
-
-#define DNS_QUERY_HASH_SIZE    293
-struct dns_query *dns_query_hash [DNS_QUERY_HASH_SIZE];
-
-/* Initialize the DNS protocol. */
-
-void dns_startup ()
-{
-       struct servent *srv;
-       struct sockaddr_in from;
-
-       /* Only initialize icmp once. */
-       if (dns_protocol_initialized)
-               log_fatal ("attempted to reinitialize dns protocol");
-       dns_protocol_initialized = 1;
-
-       /* Get the protocol number (should be 1). */
-       srv = getservbyname ("domain", "tcp");
-       if (srv)
-               dns_port = srv -> s_port;
-       else
-               dns_port = htons (53);
-
-       /* Get a socket for the DNS protocol. */
-       dns_protocol_fd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-       if (dns_protocol_fd < 0)
-               log_fatal ("unable to create dns socket: %m");
-
-#if defined (HAVE_SETFD)
-       if (fcntl (dns_protocol_fd, F_SETFD, 1) < 0)
-               log_fatal ("unable to set close-on-exec on dns fd: %m");
-#endif
-
-       first_name_server ();
-
-       add_protocol ("dns", dns_protocol_fd, dns_packet, 0);
-}
-
-/* Label manipulation stuff; see RFC1035, page 28 section 4.1.2 and
-   page 30, section 4.1.4. */
-
-/* addlabel copies a label into the specified buffer, putting the length of
-   the label in the first character, the contents of the label in subsequent
-   characters, and returning the length of the conglomeration. */
-
-static unsigned addlabel (buf, label)
-       u_int8_t *buf;
-       const char *label;
-{
-       *buf = strlen (label);
-       memcpy (buf + 1, label, (unsigned)*buf);
-       return (unsigned)(*buf + 1);
-}
-
-/* skipname skips over all of the labels in a single domain name,
-   returning the length of the domain name. */
-
-static int skipname (label)
-     u_int8_t *label;
-{
-       if (*label & INDIR_MASK)
-               return 2;
-       if (*label == 0)
-               return 1;
-       return *label + 1 + skipname (label + *label + 1);
-}
+/* 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;
+ *       tsig-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:
+ *
+ *     tsig-key "FOO.COM Key" HMAC-MD5.SIG-ALG.REG.INT <CSHL>;
+ *
+ * CSHL is a colon-seperated list of hexadecimal bytes that make up
+ * 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.
+ */
 
-/* copy_out_name copies out the name appearing at the specified location
-   into a string, stored as fields seperated by dots rather than lengths
-   and labels.   The length of the label-formatted name is returned. */
+struct hash_table *tsig_key_hash;
+struct hash_table *dns_zone_hash;
 
-static int copy_out_name (base, name, buf)
-     u_int8_t *base;
-     u_int8_t *name;
-     char *buf;
+isc_result_t enter_dns_zone (struct dns_zone *zone)
 {
-       if (*name & INDIR_MASK) {
-               int offset = (*name & ~INDIR_MASK) + (*name + 1);
-               return copy_out_name (base, base + offset, buf);
-       }
-       if (!*name) {
-               *buf = 0;
-               return 1;
+       struct dns_zone *tz;
+
+       if (dns_zone_hash) {
+               tz = hash_lookup (dns_zone_hash, zone -> name, 0);
+               if (tz == zone)
+                       return ISC_R_SUCCESS;
+               if (tz)
+                       delete_hash_entry (dns_zone_hash, zone -> name, 0);
+       } else {
+               dns_zone_hash =
+                       new_hash ((hash_reference)dns_zone_reference,
+                                 (hash_dereference)dns_zone_dereference);
+               if (!dns_zone_hash)
+                       return ISC_R_NOMEMORY;
        }
-       memcpy (buf, name + 1, *name);
-       *(buf + *name) = '.';
-       return (*name + 1
-               + copy_out_name (base, name + *name + 1, buf + *name + 1));
+       add_hash (dns_zone_hash, zone -> name, 0, zone);
+       return ISC_R_SUCCESS;
 }
 
-/* Compute a hash on the question. */
-
-static INLINE u_int32_t dns_hash_question (struct dns_question *question)
-{
-       u_int32_t sum;
-       u_int32_t remainder;
-       u_int32_t *p = (u_int32_t *)question;
-       u_int8_t *s;
-
-       /* First word. */
-       sum = *p++;
-       s = (u_int8_t *)p;
-
-       remainder = 0;
-       while (s [0]) {
-               remainder = s [0];
-               if (s [1]) {
-                       remainder = (remainder << 8) + s [1];
-                       if (s [2]) {
-                               remainder = (remainder << 8) + s [2];
-                               if (s [3])
-                                       remainder = (remainder << 8) + s [3];
-                               else
-                                       goto done;
-                       } else
-                               goto done;
-               } else {
-                     done:
-                       sum += remainder;
-                       break;
-               }
-               if ((sum & 0x80000000) && (remainder & 0x80000000))
-                       ++sum;
-               sum += remainder;
-               s += 4;
-       }
-
-       while (sum > DNS_QUERY_HASH_SIZE) {
-               remainder = sum / DNS_QUERY_HASH_SIZE;
-               sum = sum % DNS_QUERY_HASH_SIZE;
-               while (remainder) {
-                       sum += remainder % DNS_QUERY_HASH_SIZE;
-                       remainder /= DNS_QUERY_HASH_SIZE;
-               }
-       } 
-
-       return sum;
+isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name) {
+       struct dns_zone *tz;
+
+       if (!dns_zone_hash)
+               return ISC_R_NOTFOUND;
+       tz = hash_lookup (dns_zone_hash, name, 0);
+       if (!tz)
+               return ISC_R_NOTFOUND;
+       if (!dns_zone_reference (zone, tz, MDL))
+               return ISC_R_UNEXPECTED;
+       return ISC_R_SUCCESS;
 }
 
-/* Find a query that matches the specified name.  If one can't be
-   found, and new is nonzero, allocate one, hash it in, and save the
-   question.  Otherwise, if new is nonzero, free() the question.
-   Return the query if one was found or allocated. */
-
-struct dns_query *find_dns_query (question, new)
-       struct dns_question *question;
-       int new;
+isc_result_t enter_tsig_key (struct tsig_key *tkey)
 {
-       int hash = dns_hash_question (question);
-       struct dns_query *q;
-
-       for (q = dns_query_hash [hash]; q; q = q -> next) {
-               if (q -> question -> type == question -> type &&
-                   q -> question -> class == question -> class &&
-                   !strcmp ((char *)q -> question -> data,
-                            (char *)question -> data))
-                       break;
+       struct tsig_key *tk;
+
+       if (tsig_key_hash) {
+               tk = hash_lookup (tsig_key_hash, tkey -> name, 0);
+               if (tk == tkey)
+                       return ISC_R_SUCCESS;
+               if (tk)
+                       delete_hash_entry (tsig_key_hash, tkey -> name, 0);
+       } else {
+               tsig_key_hash =
+                       new_hash ((hash_reference)tsig_key_reference,
+                                 (hash_dereference)tsig_key_dereference);
+               if (!tsig_key_hash)
+                       return ISC_R_NOMEMORY;
        }
-       if (q || !new) {
-               if (new)
-                       dfree (question, MDL);
-               return q;
-       }
-
-       /* Allocate and zap a new query. */
-       q = (struct dns_query *)dmalloc (sizeof (struct dns_query), MDL);
-       memset (q, 0, sizeof *q);
-
-       /* All we need to set up is the question and the hash. */
-       q -> question = question;
-       q -> next = dns_query_hash [hash];
-       dns_query_hash [hash] = q;
-       q -> hash = hash;
-       return q;
+       add_hash (tsig_key_hash, tkey -> name, 0, tkey);
+       return ISC_R_SUCCESS;
+       
 }
 
-/* Free up all memory associated with a DNS query and remove it from the
-   query hash. */
-
-void destroy_dns_query (query)
-       struct dns_query *query;
-{
-       struct dns_query *q;
-
-       /* Free up attached free data. */
-       if (query -> question)
-               dfree (query -> question, MDL);
-       if (query -> answer)
-               dfree (query -> answer, MDL);
-       if (query -> query)
-               dfree (query -> query, MDL);
-
-       /* Remove query from hash table. */
-       if (dns_query_hash [query -> hash] == query)
-               dns_query_hash [query -> hash] = query -> next;
-       else {
-               for (q = dns_query_hash [query -> hash];
-                    q -> next && q -> next != query; q = q -> next)
-                       ;
-               if (q -> next)
-                       q -> next = query -> next;
-       }
-
-       /* Free the query structure. */
-       dfree (query, MDL);
+isc_result_t tsig_key_lookup (struct tsig_key **tkey, const char *name) {
+       struct tsig_key *tk;
+
+       if (!tsig_key_hash)
+               return ISC_R_NOTFOUND;
+       tk = hash_lookup (tsig_key_hash, name, 0);
+       if (!tk)
+               return ISC_R_NOTFOUND;
+       if (!tsig_key_reference (tkey, tk, MDL))
+               return ISC_R_UNEXPECTED;
+       return ISC_R_SUCCESS;
 }
 
-/* ns_inaddr_lookup constructs a PTR lookup query for an internet address -
-   e.g., 1.200.9.192.in-addr.arpa.   It then passes it on to ns_query for
-   completion. */
-
-struct dns_query *ns_inaddr_lookup (inaddr, wakeup)
-       struct iaddr inaddr;
-       struct dns_wakeup *wakeup;
+int dns_zone_dereference (ptr, file, line)
+       struct dns_zone **ptr;
+       const char *file;
+       int line;
 {
-       unsigned char query [512];
-       unsigned char *s;
-       unsigned char *label;
        int i;
-       unsigned char c;
-       struct dns_question *question;
-
-       /* First format the query in the internal format. */
-       sprintf ((char *)query, "%d.%d.%d.%d.in-addr.arpa.",
-                inaddr.iabuf [0], inaddr.iabuf [1],
-                inaddr.iabuf [2], inaddr.iabuf [3]);
-
-       question = (struct dns_question *)dmalloc (strlen ((char *)query) +
-                                                  sizeof *question, MDL);
-       if (!question)
-               return (struct dns_query *)-1;
-       question -> type = T_PTR;
-       question -> class = C_IN;
-       strcpy ((char *)question -> data, (char *)query);
-
-       /* Now format the query for the name server. */
-       s = query;
-
-       /* Copy out the digits. */
-       for (i = 3; i >= 0; --i) {
-               label = s++;
-               sprintf ((char *)s, "%d", inaddr.iabuf [i]);
-               *label = strlen ((char *)s);
-               s += *label;
-       }
-       s += addlabel (s, "in-addr");
-       s += addlabel (s, "arpa");
-       *s++ = 0;
-
-       /* Set the query type. */
-       putUShort (s, T_PTR);
-       s += sizeof (u_int16_t);
-
-       /* Set the query class. */
-       putUShort (s, C_IN);
-       s += sizeof (u_int16_t);
-
-       return ns_query (question, query, (unsigned)(s - query), wakeup);
-}
-
-/* Try to satisfy a query out of the local cache.  If no answer has
-   been cached, and if there isn't already a query pending on this
-   question, send it.  If the query can be immediately satisfied,
-   a pointer to the dns_query structure is returned.  If the query
-   can't even be made for some reason, (struct dns_query *)-1 is
-   returned.  Otherwise, the null pointer is returned, indicating that
-   a wakeup will be performed later when the answer comes back. */
-
-struct dns_query *ns_query (question, formatted_query, len, wakeup)
-       struct dns_question *question;
-       unsigned char *formatted_query;
-       unsigned len;
-       struct dns_wakeup *wakeup;
-{
-       HEADER *hdr;
-       struct dns_query *query;
-       unsigned char *s;
-       unsigned char buf [512];
-
-       /* If the query won't fit, don't bother setting it up. */
-       if (len > 255) {
-               dfree (question, MDL);
-               return (struct dns_query *)-1;
-       }
-
-       /* See if there's already a query for this name, and allocate a
-          query if none exists. */
-       query = find_dns_query (question, 1);
-
-       /* If we can't allocate a query, report that the query failed. */
-       if (!query)
-               return (struct dns_query *)-1;
-
-       /* If the query has already been answered, return it. */
-       if (query -> expiry > cur_time)
-               return query;
-
-       /* The query hasn't yet been answered, so we have to wait, one
-          way or another.   Put the wakeup on the list. */
-       if (wakeup) {
-               wakeup -> next = query -> wakeups;
-               query -> wakeups = wakeup;
+       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
        }
 
-       /* If the query has already been sent, but we don't yet have
-          an answer, we're done. */
-       if (query -> sent)
-               return (struct dns_query *)0;
-
-       /* Construct a header... */
-       hdr = (HEADER *)buf;
-       memset (hdr, 0, sizeof *hdr);
-       hdr -> id = query -> id;
-       hdr -> rd = 1;
-       hdr -> opcode = QUERY;
-       hdr -> qdcount = htons (1);
-
-       /* Copy the formatted name into the buffer. */
-       s = (unsigned char *)hdr + 1;
-       memcpy (s, formatted_query, len);
-
-       /* Figure out how long the whole message is */
-       s += len;
-       query -> len = s - buf;
+       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;
 
-       /* Save the raw query data. */
-       query -> query = dmalloc (len, MDL);
-       if (!query -> query) {
-               destroy_dns_query (query);
-               return (struct dns_query *)-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
        }
-       memcpy (query -> query, buf, query -> len);
-
-       /* Flag the query as having been sent. */
-       query -> sent = 1;
 
-       /* Send the query. */
-       dns_timeout (query);
-
-       /* No answer yet, obviously. */
-       return (struct dns_query *)0;
+       if (dns_zone -> name)
+               dfree (dns_zone -> name, file, line);
+       if (dns_zone -> key)
+               tsig_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;
 }
 
-/* Retransmit a DNS query. */
-
-void dns_timeout (qv)
-       void *qv;
-{
-       struct dns_query *query = qv;
-       int status;
-
-       /* Choose the server to send to. */
-       if (!query -> next_server)
-               query -> next_server = first_name_server ();
-
-       /* Send the query. */
-       if (query -> next_server)
-               status = sendto (dns_protocol_fd,
-                                (char *)query -> query, query -> len, 0,
-                                ((struct sockaddr *)&query ->
-                                 next_server -> addr),
-                                sizeof query -> next_server -> addr);
-       else
-               status = -1;
-
-       /* Look for the next server... */
-       query -> next_server = query -> next_server -> next;
-
-       /* If this is our first time, backoff one second. */
-       if (!query -> backoff)
-               query -> backoff = 1;
-
-       /* If the send failed, don't advance the backoff. */
-       else if (status < 0)
-               ;
-
-       /* If we haven't run out of servers to try, don't backoff. */
-       else if (query -> next_server)
-               ;
-
-       /* If we haven't backed off enough yet, back off some more. */
-       else if (query -> backoff < 30)
-               query -> backoff += random() % query -> backoff;
-
-       /* Set up the timeout. */
-       add_timeout (cur_time + query -> backoff, dns_timeout, query);
-}
-
-/* Process a reply from a name server. */
-
-void dns_packet (protocol)
-       struct protocol *protocol;
-{
-       HEADER *ns_header;
-       struct sockaddr_in from;
-       struct dns_wakeup *wakeup;
-       unsigned char buf [512];
-       union {
-               unsigned char u [512];
-               struct dns_question q;
-       } qbuf;
-       unsigned char *base;
-       unsigned char *dptr, *name;
-       u_int16_t type;
-       u_int16_t class;
-       TIME ttl;
-       u_int16_t rdlength;
-       SOCKLEN_T len;
-       int status;
-       int i;
-       struct dns_query *query;
-
-       len = sizeof from;
-       status = recvfrom (protocol -> fd, (char *)buf, sizeof buf, 0,
-                         (struct sockaddr *)&from, &len);
-       if (status < 0) {
-               log_error ("dns_packet: %m");
-               return;
-       }
-
-       /* Response is too long? */
-       if (len > 512) {
-               log_error ("dns_packet: dns message too long (%d)", len);
-               return;
-       }
-
-       ns_header = (HEADER *)buf;
-       base = (unsigned char *)(ns_header + 1);
-
-       /* Parse the response... */
-       dptr = base;
-
-       /* If this is a response to a query from us, there should have
-           been only one query. */
-       if (ntohs (ns_header -> qdcount) != 1) {
-               log_error ("Bogus DNS answer packet from %s claims %d queries.\n",
-                     inet_ntoa (from.sin_addr),
-                     ntohs (ns_header -> qdcount));
-               return;
-       }
-
-       /* Find the start of the name in the query. */
-       name = dptr;
-
-       /* Skip over the name. */
-       dptr += copy_out_name (name, name, (char *)qbuf.q.data);
-
-       /* Skip over the query type and query class. */
-       qbuf.q.type = getUShort (dptr);
-       dptr += sizeof (u_int16_t);
-       qbuf.q.class = getUShort (dptr);
-       dptr += sizeof (u_int16_t);
-
-       /* See if we asked this question. */
-       query = find_dns_query (&qbuf.q, 0);
-       if (!query) {
-log_error ("got answer for question %s from DNS, which we didn't ask.",
-qbuf.q.data);
-               return;
-       }
-
-log_info ("got answer for question %s from DNS", qbuf.q.data);
-
-       /* Wake up everybody who's waiting. */
-       for (wakeup = query -> wakeups; wakeup; wakeup = wakeup -> next) {
-               (*wakeup -> func) (query);
-       }
-}