]> git.ipfire.org Git - thirdparty/ldns.git/commitdiff
duh add the new files too
authorMiek Gieben <miekg@NLnetLabs.nl>
Tue, 13 Sep 2005 12:00:13 +0000 (12:00 +0000)
committerMiek Gieben <miekg@NLnetLabs.nl>
Tue, 13 Sep 2005 12:00:13 +0000 (12:00 +0000)
ldns-update.c [new file with mode: 0644]
ldns/tsig.h [new file with mode: 0644]
ldns/update.h [new file with mode: 0644]
tsig.c [new file with mode: 0644]
update.c [new file with mode: 0644]

diff --git a/ldns-update.c b/ldns-update.c
new file mode 100644 (file)
index 0000000..e10e6e1
--- /dev/null
@@ -0,0 +1,69 @@
+/* $Id: ldns-update.c,v 1.1 2005/09/13 09:37:05 ho Exp $ */
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#include <ldns/dns.h>
+
+int
+main(int argc, char **argv)
+{
+       char            *fqdn, *ipaddr, *zone;
+       u_int16_t       defttl = 300;
+       ldns_status     ret;
+       ldns_tsig_credentials   tsig_cr, *tsig_cred;
+       int             c = 2;
+       
+       switch (argc) {
+       case 3:
+       case 4:
+       case 6:
+       case 7:
+               break;
+       default:
+               fprintf(stderr, "usage: %s FQDN [zone] IP "
+                   "[tsig_name tsig_alg tsig_hmac]\n", argv[0]);
+               fprintf(stderr, "Example: %s my.host.org 1.2.3.4\n", argv[0]);
+               fprintf(stderr, "Use 'none' instead of IP to remove any "
+                   "previous address.\n");
+               fprintf(stderr, "If 'zone' is not specified, "
+                   "try to figure it from SOA.\n");
+               exit(1);
+       }
+
+       fqdn = argv[1]; 
+       c = 2;
+       if (argc == 4 || argc == 7)
+               zone = argv[c++];
+       else
+               zone = NULL;
+       
+       if (strcmp(argv[c], "none") == 0)
+               ipaddr = NULL;
+       else
+               ipaddr = argv[c];
+       c++;
+       if (argc == 6 || argc == 7) {
+               tsig_cr.keyname = argv[c++];
+               if (strncasecmp(argv[c], "hmac-sha1", 9) == 0)
+                       tsig_cr.algorithm = "hmac-sha1.";
+               else if (strncasecmp(argv[c], "hmac-md5", 8) == 0)
+                       tsig_cr.algorithm = "hmac-md5.sig-alg.reg.int.";
+               else {
+                       fprintf(stderr, "Unknown algorithm, try \"hmac-md5\" "
+                           "or \"hmac-sha1\".\n");
+                       exit(1);
+               }
+               tsig_cr.keydata = argv[++c];
+               tsig_cred = &tsig_cr;
+       } else
+               tsig_cred = NULL;
+
+       printf(";; trying UPDATE with FQDN \"%s\" and IP \"%s\"\n",
+           fqdn, ipaddr ? ipaddr : "<none>");
+       printf(";; tsig: \"%s\" \"%s\" \"%s\"\n", tsig_cr.keyname,
+           tsig_cr.algorithm, tsig_cr.keydata);
+
+       ret = ldns_update_send_simple_A(fqdn, zone, ipaddr, defttl, tsig_cred);
+       exit(ret);
+}
diff --git a/ldns/tsig.h b/ldns/tsig.h
new file mode 100644 (file)
index 0000000..c1bb0cd
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * tsig.h -- defines for TSIG [RFC2845]
+ *
+ * Copyright (c) 2001-2005, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ */
+
+#ifndef _LDNS_TSIG_H_
+#define _LDNS_TSIG_H_
+
+#include <ldns/common.h>
+#include <ldns/dns.h>
+#include <ldns/packet.h>
+#include <ldns/zone.h>
+
+typedef struct _ldns_tsig_credentials 
+{
+    char *algorithm;
+    char *keyname;
+    char *keydata;
+    /* XXX More eventually. */
+} ldns_tsig_credentials;
+
+char *ldns_tsig_algorithm(ldns_tsig_credentials *);
+char *ldns_tsig_keyname(ldns_tsig_credentials *);
+char *ldns_tsig_keydata(ldns_tsig_credentials *);
+char *ldns_tsig_keyname_clone(ldns_tsig_credentials *);
+char *ldns_tsig_keydata_clone(ldns_tsig_credentials *);
+
+/**
+ * verifies the tsig rr for the given packet and key (string?).
+ * The wire must be given too because tsig does not sign normalized packets.
+ *
+ * \return true if tsig is correct, false if not, or if tsig is not set
+ */
+bool ldns_pkt_tsig_verify(ldns_pkt *pkt, uint8_t *wire, size_t wire_size,
+    const char *key_name, const char *key_data, ldns_rdf *mac);
+
+/**
+ * creates a tsig rr for the given packet and key (string?).
+ * \param[in] pkt the packet to sign
+ * \param[in] key_name the name of the shared key
+ * \param[in] key_data the key in base 64 format
+ * \param[in] fudge seconds of error permitted in time signed
+ * \param[in] algorithm_name the name of the algorithm used (TODO more than only hmac-md5.sig-alg.reg.int.?)
+ * \param[in] query_mac is added to the digest if not NULL (so NULL is for signing queries, not NULL is for signing answers)
+ * \return status (OK if success)
+ */
+ldns_status ldns_pkt_tsig_sign(ldns_pkt *pkt, const char *key_name,
+    const char *key_data, uint16_t fudge, const char *algorithm_name,
+    ldns_rdf *query_mac);
+
+#endif /* _LDNS_TSIG_H_ */
diff --git a/ldns/update.h b/ldns/update.h
new file mode 100644 (file)
index 0000000..8825a1d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * update.h
+ *
+ * Functions for RFC 2136 Dynamic Update
+ *
+ * Copyright (c) 2005, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ */
+
+#ifndef _LDNS_UPDATE_H
+#define _LDNS_UPDATE_H
+
+ldns_pkt       *ldns_update_pkt_new(ldns_rdf *, ldns_rr_class, ldns_rr_list *,
+    ldns_rr_list *, ldns_rr_list *);
+ldns_status    ldns_update_pkt_tsig_add(ldns_pkt *, ldns_resolver *);
+ldns_resolver  *ldns_update_resolver_new(const char *, const char *,
+    ldns_rr_class, ldns_tsig_credentials *, ldns_rdf **);
+
+uint16_t ldns_update_get_zo(const ldns_pkt *);
+uint16_t ldns_update_get_pr(const ldns_pkt *);
+uint16_t ldns_update_get_up(const ldns_pkt *);
+uint16_t ldns_update_get_ad(const ldns_pkt *);
+
+void ldns_update_set_zo(ldns_pkt *, u_int16_t);
+void ldns_update_set_pr(ldns_pkt *, u_int16_t);
+void ldns_update_set_up(ldns_pkt *, u_int16_t);
+void ldns_update_set_ad(ldns_pkt *, u_int16_t);
+
+#endif  /* !_LDNS_UPDATE_H */
diff --git a/tsig.c b/tsig.c
new file mode 100644 (file)
index 0000000..31a5fe0
--- /dev/null
+++ b/tsig.c
@@ -0,0 +1,404 @@
+/* 
+ * tsig.c
+ *
+ * contains the functions needed for TSIG [RFC2845]
+ *
+ * See the file LICENSE for the license
+ */
+
+#include <ldns/config.h>
+
+#include <ldns/dns.h>
+
+#include <strings.h>
+
+#include <openssl/hmac.h>
+#include <openssl/md5.h>
+
+char *
+ldns_tsig_algorithm(ldns_tsig_credentials *tc)
+{
+       return tc->algorithm;
+}
+
+char *
+ldns_tsig_keyname(ldns_tsig_credentials *tc)
+{
+       return tc->keyname;
+}
+
+char *
+ldns_tsig_keydata(ldns_tsig_credentials *tc)
+{
+       return tc->keydata;
+}
+
+char *
+ldns_tsig_keyname_clone(ldns_tsig_credentials *tc)
+{
+       return strdup(tc->keyname);
+}
+
+char *
+ldns_tsig_keydata_clone(ldns_tsig_credentials *tc)
+{
+       return strdup(tc->keydata);
+}
+
+/*
+ *  Makes an exact copy of the wire, but with the tsig rr removed
+ */
+uint8_t *
+ldns_tsig_prepare_pkt_wire(uint8_t *wire, size_t wire_len, size_t *result_len)
+{
+       uint8_t *wire2 = NULL;
+       uint16_t qd_count;
+       uint16_t an_count;
+       uint16_t ns_count;
+       uint16_t ar_count;
+       ldns_rr *rr;
+       
+       size_t pos;
+       uint16_t i;
+       
+       ldns_status status;
+
+       /* fake parse the wire */
+       qd_count = LDNS_QDCOUNT(wire);
+       an_count = LDNS_ANCOUNT(wire);
+       ns_count = LDNS_NSCOUNT(wire);
+       ar_count = LDNS_ARCOUNT(wire);
+       
+       if (ar_count > 0) {
+               ar_count--;
+       } else {
+               return NULL;
+       }
+
+       pos = LDNS_HEADER_SIZE;
+       
+       for (i = 0; i < qd_count; i++) {
+               status = ldns_wire2rr(&rr, wire, wire_len, &pos,
+                                     LDNS_SECTION_QUESTION);
+               if (status != LDNS_STATUS_OK) {
+                       return NULL;
+               }
+               ldns_rr_free(rr);
+       }
+       
+       for (i = 0; i < an_count; i++) {
+               status = ldns_wire2rr(&rr, wire, wire_len, &pos,
+                                     LDNS_SECTION_ANSWER);
+               if (status != LDNS_STATUS_OK) {
+                       return NULL;
+               }
+               ldns_rr_free(rr);
+       }
+       
+       for (i = 0; i < ns_count; i++) {
+               status = ldns_wire2rr(&rr, wire, wire_len, &pos,
+                                     LDNS_SECTION_AUTHORITY);
+               if (status != LDNS_STATUS_OK) {
+                       return NULL;
+               }
+               ldns_rr_free(rr);
+       }
+       
+       for (i = 0; i < ar_count; i++) {
+               status = ldns_wire2rr(&rr, wire, wire_len, &pos,
+                                     LDNS_SECTION_ADDITIONAL);
+               if (status != LDNS_STATUS_OK) {
+                       return NULL;
+               }
+               ldns_rr_free(rr);
+       }
+       
+       *result_len = pos;
+       wire2 = LDNS_XMALLOC(uint8_t, *result_len);
+       memcpy(wire2, wire, *result_len);
+       
+       ldns_write_uint16(wire2 + LDNS_ARCOUNT_OFF, ar_count);
+       
+       return wire2;
+}
+
+const EVP_MD *
+ldns_get_digest_function(char *name)
+{
+       /* TODO replace with openssl's EVP_get_digestbyname
+               (need init somewhere for that)
+       */
+       if (strlen(name) == 10 && strncasecmp(name, "hmac-sha1.", 9) == 0)
+               return EVP_sha1();
+       else if (strlen(name) == 25 && strncasecmp(name,
+                    "hmac-md5.sig-alg.reg.int.", 25) == 0)
+               return EVP_md5();
+       else
+               return NULL;
+}
+
+ldns_status
+ldns_create_tsig_mac(
+       ldns_rdf **tsig_mac,
+       uint8_t *pkt_wire,
+       size_t pkt_wire_size,
+       const char *key_data,
+       ldns_rdf *key_name_rdf,
+       ldns_rdf *fudge_rdf,
+       ldns_rdf *algorithm_rdf,
+       ldns_rdf *time_signed_rdf,
+       ldns_rdf *error_rdf,
+       ldns_rdf *other_data_rdf,
+       ldns_rdf *orig_mac_rdf
+)
+{
+       ldns_buffer *data_buffer = NULL;
+       char *wireformat;
+       int wiresize;
+       unsigned char *mac_bytes;
+       unsigned int md_len = EVP_MAX_MD_SIZE;
+       unsigned char *key_bytes;
+       int key_size;
+       const EVP_MD *digester;
+       char *algorithm_name;
+       ldns_rdf *result = NULL;
+       
+       /* 
+        * prepare the digestable information
+        */
+       data_buffer = ldns_buffer_new(LDNS_MAX_PACKETLEN);
+       /* if orig_mac is not NULL, add it too */
+       if (orig_mac_rdf) {
+               (void) ldns_rdf2buffer_wire(data_buffer, orig_mac_rdf);
+       }
+       ldns_buffer_write(data_buffer, pkt_wire, pkt_wire_size);
+       (void) ldns_rdf2buffer_wire(data_buffer, key_name_rdf);
+       ldns_buffer_write_u16(data_buffer, LDNS_RR_CLASS_ANY);
+       ldns_buffer_write_u32(data_buffer, 0);
+       (void) ldns_rdf2buffer_wire(data_buffer, algorithm_rdf);
+       (void) ldns_rdf2buffer_wire(data_buffer, time_signed_rdf);
+       (void) ldns_rdf2buffer_wire(data_buffer, fudge_rdf);
+       (void) ldns_rdf2buffer_wire(data_buffer, error_rdf);
+       (void) ldns_rdf2buffer_wire(data_buffer, other_data_rdf);
+       
+       wireformat = (char *) data_buffer->_data;
+       wiresize = (int) ldns_buffer_position(data_buffer);
+       
+       algorithm_name = ldns_rdf2str(algorithm_rdf);
+       
+       /* prepare the key */
+       key_bytes = LDNS_XMALLOC(unsigned char, b64_pton_calculate_size(strlen(key_data)));
+       key_size = b64_pton(key_data, key_bytes, strlen(key_data) * 2);
+       if (key_size < 0) {
+               /* LDNS_STATUS_INVALID_B64 */
+               dprintf("%s\n", "Bad base64 string");
+               return LDNS_STATUS_INVALID_B64;
+       }
+       /* hmac it */
+       /* 2 spare bytes for the length */
+       mac_bytes = LDNS_XMALLOC(unsigned char, md_len);
+       memset(mac_bytes, 0, md_len);
+       
+       digester = ldns_get_digest_function(algorithm_name);
+       
+       if (digester) {
+               (void) HMAC(digester, key_bytes, key_size, (void *)wireformat, wiresize, mac_bytes + 2, &md_len);
+       
+               ldns_write_uint16(mac_bytes, md_len);
+               result = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_INT16_DATA, md_len + 2, mac_bytes);
+       } else {
+               /*dprintf("No digest found for %s\n", algorithm_name);*/
+               return LDNS_STATUS_CRYPTO_UNKNOWN_ALGO;
+       }
+       
+       LDNS_FREE(algorithm_name);
+       LDNS_FREE(mac_bytes);
+       LDNS_FREE(key_bytes);
+       ldns_buffer_free(data_buffer);
+
+       *tsig_mac = result;
+       
+       return LDNS_STATUS_OK;
+}
+
+
+/* THIS FUNC WILL REMOVE TSIG ITSELF */
+bool
+ldns_pkt_tsig_verify(ldns_pkt *pkt, 
+                     uint8_t *wire,
+                     size_t wirelen,
+                     const char *key_name, 
+                     const char *key_data, 
+                     ldns_rdf *orig_mac_rdf)
+{
+       ldns_rdf *fudge_rdf;
+       ldns_rdf *algorithm_rdf;
+       ldns_rdf *time_signed_rdf;
+       ldns_rdf *orig_id_rdf;
+       ldns_rdf *error_rdf;
+       ldns_rdf *other_data_rdf;
+       ldns_rdf *pkt_mac_rdf;
+       ldns_rdf *my_mac_rdf;
+       ldns_rdf *key_name_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, key_name);
+       uint16_t pkt_id, orig_pkt_id;
+       ldns_status status;
+       
+       uint8_t *prepared_wire = NULL;
+       size_t prepared_wire_size = 0;
+       
+       ldns_rr *orig_tsig = ldns_pkt_tsig(pkt);
+       
+       if (!orig_tsig) {
+               ldns_rdf_deep_free(key_name_rdf);
+               return false;
+       }
+       algorithm_rdf = ldns_rr_rdf(orig_tsig, 0);
+       time_signed_rdf = ldns_rr_rdf(orig_tsig, 1);
+       fudge_rdf = ldns_rr_rdf(orig_tsig, 2);
+       pkt_mac_rdf = ldns_rr_rdf(orig_tsig, 3);
+       orig_id_rdf = ldns_rr_rdf(orig_tsig, 4);
+       error_rdf = ldns_rr_rdf(orig_tsig, 5);
+       other_data_rdf = ldns_rr_rdf(orig_tsig, 6);
+       
+       /* remove temporarily */
+       ldns_pkt_set_tsig(pkt, NULL);
+       /* temporarily change the id to the original id */
+       pkt_id = ldns_pkt_id(pkt);
+       orig_pkt_id = ldns_rdf2native_int16(orig_id_rdf);
+       ldns_pkt_set_id(pkt, orig_pkt_id);
+
+       prepared_wire = ldns_tsig_prepare_pkt_wire(wire, wirelen, &prepared_wire_size);
+       
+       status = ldns_create_tsig_mac(&my_mac_rdf,
+                                     prepared_wire,
+                                     prepared_wire_size,
+                                     key_data, 
+                                     key_name_rdf,
+                                     fudge_rdf,
+                                     algorithm_rdf,
+                                     time_signed_rdf,
+                                     error_rdf,
+                                     other_data_rdf,
+                                     orig_mac_rdf
+                                    );
+       
+       LDNS_FREE(prepared_wire);
+       
+       if (status != LDNS_STATUS_OK) {
+               ldns_rdf_deep_free(key_name_rdf);
+               return false;
+       }
+       /* Put back the values */
+       ldns_pkt_set_tsig(pkt, orig_tsig);
+       ldns_pkt_set_id(pkt, pkt_id);
+       
+       ldns_rdf_deep_free(key_name_rdf);
+       
+       if (ldns_rdf_compare(pkt_mac_rdf, my_mac_rdf) == 0) {
+               ldns_rdf_deep_free(my_mac_rdf);
+               return true;
+       } else {
+               ldns_rdf_deep_free(my_mac_rdf);
+               return false;
+       }
+}
+
+/* TODO: memory :p */
+ldns_status
+ldns_pkt_tsig_sign(ldns_pkt *pkt, const char *key_name, const char *key_data, uint16_t fudge, const char *algorithm_name, ldns_rdf *query_mac)
+{
+       ldns_rr *tsig_rr;
+       ldns_rdf *key_name_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, key_name);
+       ldns_rdf *fudge_rdf = NULL;
+       ldns_rdf *orig_id_rdf = NULL;
+       ldns_rdf *algorithm_rdf;
+       ldns_rdf *error_rdf = NULL;
+       ldns_rdf *mac_rdf = NULL;
+       ldns_rdf *other_data_rdf = NULL;
+       
+       ldns_status status = LDNS_STATUS_OK;
+       
+       uint8_t *pkt_wire = NULL;
+       size_t pkt_wire_len;
+       
+       struct timeval tv_time_signed;
+       uint8_t *time_signed = NULL;
+       ldns_rdf *time_signed_rdf = NULL;
+       
+       algorithm_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, algorithm_name);
+
+       /* eww don't have create tsigtime rdf yet :( */
+       /* bleh :p */
+       if (gettimeofday(&tv_time_signed, NULL) == 0) {
+               time_signed = LDNS_XMALLOC(uint8_t, 6);
+               ldns_write_uint64_as_uint48(time_signed, tv_time_signed.tv_sec);
+       } else {
+               status = LDNS_STATUS_INTERNAL_ERR;
+               goto clean;
+       }
+
+       time_signed_rdf = ldns_rdf_new(LDNS_RDF_TYPE_TSIGTIME, 6, time_signed);
+       
+       fudge_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, fudge);
+
+       orig_id_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, ldns_pkt_id(pkt));
+
+       error_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, 0);
+       
+       other_data_rdf = ldns_native2rdf_int16_data(0, NULL);
+
+       if (ldns_pkt2wire(&pkt_wire, pkt, &pkt_wire_len) != LDNS_STATUS_OK) {
+               status = LDNS_STATUS_ERR;
+               goto clean;
+       }
+
+       status = ldns_create_tsig_mac(&mac_rdf,
+                                     pkt_wire,
+                                     pkt_wire_len,
+                                     key_data,
+                                     key_name_rdf, 
+                                     fudge_rdf, 
+                                     algorithm_rdf,
+                                     time_signed_rdf,
+                                     error_rdf,
+                                     other_data_rdf,
+                                     query_mac
+                                     );
+       
+       if (!mac_rdf) {
+               goto clean;
+       }
+       
+       LDNS_FREE(pkt_wire);
+       
+       /* Create the TSIG RR */
+       tsig_rr = ldns_rr_new();
+       ldns_rr_set_owner(tsig_rr, key_name_rdf);
+       ldns_rr_set_class(tsig_rr, LDNS_RR_CLASS_ANY);
+       ldns_rr_set_type(tsig_rr, LDNS_RR_TYPE_TSIG);
+       ldns_rr_set_ttl(tsig_rr, 0);
+       
+       ldns_rr_push_rdf(tsig_rr, algorithm_rdf);
+       ldns_rr_push_rdf(tsig_rr, time_signed_rdf);
+       ldns_rr_push_rdf(tsig_rr, fudge_rdf);
+       ldns_rr_push_rdf(tsig_rr, mac_rdf);
+       ldns_rr_push_rdf(tsig_rr, orig_id_rdf);
+       ldns_rr_push_rdf(tsig_rr, error_rdf);
+       ldns_rr_push_rdf(tsig_rr, other_data_rdf);
+       
+       ldns_pkt_set_tsig(pkt, tsig_rr);
+
+       return status;
+
+  clean:
+       ldns_rdf_free(key_name_rdf);
+       ldns_rdf_free(algorithm_rdf);
+       ldns_rdf_free(time_signed_rdf);
+       ldns_rdf_free(fudge_rdf);
+       ldns_rdf_free(orig_id_rdf);
+       ldns_rdf_free(error_rdf);
+       ldns_rdf_free(other_data_rdf);
+       return status;
+}
+
+
diff --git a/update.c b/update.c
new file mode 100644 (file)
index 0000000..04d84ce
--- /dev/null
+++ b/update.c
@@ -0,0 +1,440 @@
+/* update.c
+ *
+ * Functions for RFC 2136 Dynamic Update
+ *
+ * Copyright (c) 2005, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ */
+
+#include <ldns/config.h>
+
+#include <ldns/dns.h>
+
+#include <strings.h>
+#include <stdlib.h>
+#include <limits.h>
+
+/*
+ * RFC 2136 sections mapped to RFC 1035:
+ *              zone/ZO -- QD/question
+ *     prerequisites/PR -- AN/answers
+ *           updates/UP -- NS/authority records
+ *   additional data/AD -- AR/additional records
+ */
+
+#define _zone    _question
+#define _prereq  _answer
+#define _updates _authority
+
+/**
+ * create an update packet from zone name, class and the rr lists
+ * \param[in] zone name of the zone
+ * \param[in] class zone class
+ * \param[in] pr_rrlist list of Prerequisite Section RRs
+ * \param[in] up_rrlist list of Updates Section RRs
+ * \param[in] ad_rrlist list of Additional Data Section RRs (currently unused)
+ */
+ldns_pkt *
+ldns_update_pkt_new(ldns_rdf *zone_rdf, ldns_rr_class class,
+    ldns_rr_list *pr_rrlist, ldns_rr_list *up_rrlist, ldns_rr_list *ad_rrlist)
+{
+       ldns_pkt *p;
+
+       if (!zone_rdf || !up_rrlist) {
+               dprintf("%s", "bad input to ldns_update_pkt_new()\n");
+               return NULL;
+       }
+
+       if (class == 0)
+               class = LDNS_RR_CLASS_IN;
+
+       /* Create packet, fill in Zone Section. */
+       p = ldns_pkt_query_new(zone_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD);
+       if (!p)
+               return NULL;
+       zone_rdf = NULL; /* No longer safe to use. */
+
+       ldns_pkt_set_opcode(p, LDNS_PACKET_UPDATE);
+
+       ldns_rr_list_deep_free(p->_updates);
+       p->_updates = ldns_rr_list_clone(up_rrlist);
+       ldns_update_set_up(p, ldns_rr_list_rr_count(up_rrlist));
+
+       if (pr_rrlist) {
+               ldns_rr_list_deep_free(p->_prereq);
+               p->_prereq = ldns_rr_list_clone(pr_rrlist);
+               ldns_update_set_pr(p, ldns_rr_list_rr_count(pr_rrlist));
+       }
+
+       if (ad_rrlist) {
+               ldns_rr_list_deep_free(p->_additional);
+               p->_additional = ldns_rr_list_clone(ad_rrlist);
+               ldns_update_set_ad(p, ldns_rr_list_rr_count(ad_rrlist));
+       }
+
+       return p;
+}
+
+ldns_status
+ldns_update_pkt_tsig_add(ldns_pkt *p, ldns_resolver *r)
+{
+       u_int16_t fudge = 300; /* Recommended fudge. [RFC2845 6.4]  */
+
+       if (ldns_resolver_tsig_keyname(r) && ldns_resolver_tsig_keydata(r))
+               return ldns_pkt_tsig_sign(p, ldns_resolver_tsig_keyname(r),
+                   ldns_resolver_tsig_keydata(r), fudge,
+                   ldns_resolver_tsig_algorithm(r), NULL);
+
+       /* No TSIG to do. */
+       return LDNS_STATUS_OK;
+}
+
+/* Move to higher.c or similar? */
+
+ldns_status
+ldns_update_get_soa_mname(ldns_rdf *zone, ldns_resolver *r,
+    ldns_rr_class class, ldns_rdf **mname)
+{
+       ldns_rr         *soa_rr;
+       ldns_pkt        *query, *resp;
+
+       /* Nondestructive, so clone 'zone' here */
+       query = ldns_pkt_query_new(ldns_rdf_clone(zone), LDNS_RR_TYPE_SOA,
+           class, LDNS_RD);
+       if (!query)
+               return LDNS_STATUS_ERR;
+
+       ldns_pkt_set_random_id(query);
+       if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) {
+               dprintf("%s", "SOA query failed (MNAME)\n");
+               ldns_pkt_free(query);
+               return LDNS_STATUS_ERR;
+       }
+       ldns_pkt_free(query);
+       if (!resp)
+               return LDNS_STATUS_ERR;
+
+       /* Expect a SOA answer. */
+       *mname = NULL;
+       while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp)))) {
+               if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA)
+                       continue;
+               /* [RFC1035 3.3.13] */
+               *mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0));
+               break;
+       }
+       ldns_pkt_free(resp);
+
+       return *mname ? LDNS_STATUS_OK : LDNS_STATUS_ERR;
+}
+
+/* Try to get zone and MNAME from SOA queries. */
+ldns_status
+ldns_update_get_soa_zone_mname(const char *fqdn, ldns_resolver *r,
+    ldns_rr_class class, ldns_rdf **zone_rdf, ldns_rdf **mname_rdf)
+{
+       ldns_rr         *soa_rr, *rr;
+       ldns_rdf        *soa_zone = NULL, *soa_mname = NULL;
+       ldns_rdf        *ipaddr, *fqdn_rdf, *tmp;
+       ldns_rdf        **nslist;
+       ldns_pkt        *query, *resp;
+       int             i;
+
+       /* 
+        * XXX Ok, this cannot be the best way to find this...?
+        * XXX (I run into weird cache-related stuff here)
+        */
+
+       /* Step 1 - first find a nameserver that should know *something* */
+       fqdn_rdf = ldns_dname_new_frm_str(fqdn);
+       query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD);
+       if (!query)
+               return LDNS_STATUS_ERR;
+       fqdn_rdf = NULL;
+
+       ldns_pkt_set_random_id(query);
+       if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) {
+               dprintf("%s", "SOA query failed\n");
+               ldns_pkt_free(query);
+               return LDNS_STATUS_ERR;
+       }
+       ldns_pkt_free(query);
+       if (!resp)
+               return LDNS_STATUS_ERR;
+
+       /* XXX Is it safe to only look in authority section here? */
+       while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) {
+               if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA)
+                       continue;
+               /* [RFC1035 3.3.13] */
+               soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0));
+               break;
+       }
+       ldns_pkt_free(resp);
+       if (!soa_rr)
+               return LDNS_STATUS_ERR;
+
+       /* Step 2 - find SOA MNAME IP address, add to resolver */
+       query = ldns_pkt_query_new(soa_mname, LDNS_RR_TYPE_A, class, LDNS_RD);
+       if (!query)
+               return LDNS_STATUS_ERR;
+       soa_mname = NULL;
+
+       ldns_pkt_set_random_id(query);
+       if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) {
+               dprintf("%s", "SOA query 2 failed\n");
+               ldns_pkt_free(query);
+               return LDNS_STATUS_ERR;
+       }
+       ldns_pkt_free(query);
+       if (!resp)
+               return LDNS_STATUS_ERR;
+
+       if (ldns_pkt_ancount(resp) == 0) {
+               ldns_pkt_free(resp);
+               return LDNS_STATUS_ERR;
+       }
+
+       /* XXX There may be more than one answer RR here. */
+       rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp));
+       ipaddr = ldns_rr_rdf(rr, 0);
+
+       /* Put the SOA mname IP first in the nameserver list. */
+       nslist = ldns_resolver_nameservers(r);
+       for (i = 0; i < ldns_resolver_nameserver_count(r); i++) {
+               if (ldns_rdf_compare(ipaddr, nslist[i]) == 0) {
+                       if (i) {
+                               tmp = nslist[0];
+                               nslist[0] = nslist[i];
+                               nslist[i] = tmp;
+                       }
+                       break;
+               }
+       }
+       if (i >= ldns_resolver_nameserver_count(r)) {
+               /* SOA mname was not part of the resolver so add it first. */
+               ldns_resolver_push_nameserver(r, ipaddr);
+               nslist = ldns_resolver_nameservers(r);
+               i = ldns_resolver_nameserver_count(r) - 1;
+               tmp = nslist[0];
+               nslist[0] = nslist[i];
+               nslist[i] = tmp;
+       }
+       ldns_pkt_free(resp);
+
+       /* Make sure to ask the first in the list, i.e SOA mname */
+       ldns_resolver_set_random(r, false);
+
+       /* Step 3 - Redo SOA query, sending to SOA MNAME directly. */
+       fqdn_rdf = ldns_dname_new_frm_str(fqdn);
+       query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD);
+       if (!query)
+               return LDNS_STATUS_ERR;
+       fqdn_rdf = NULL;
+
+       ldns_pkt_set_random_id(query);
+       if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) {
+               dprintf("%s", "SOA query failed\n");
+               ldns_pkt_free(query);
+               return LDNS_STATUS_ERR;
+       }
+       ldns_pkt_free(query);
+       if (!resp)
+               return LDNS_STATUS_ERR;
+
+       /* XXX Is it safe to only look in authority section here, too? */
+       while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) {
+               if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA)
+                       continue;
+               /* [RFC1035 3.3.13] */
+               soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0));
+               soa_zone = ldns_rdf_clone(ldns_rr_owner(soa_rr));
+               break;
+       }
+       ldns_pkt_free(resp);
+       if (!soa_rr)
+               return LDNS_STATUS_ERR;
+
+       /* That seems to have worked, pass results to caller. */
+       *zone_rdf = soa_zone;
+       *mname_rdf = soa_mname;
+       return LDNS_STATUS_OK;
+}      
+
+/**
+ * Create a resolver suitable for use with UPDATE. [RFC2136 4.3]
+ * SOA MNAME is used as the "primary master".
+ * \param[in] fqdn FQDN of a host in a zone
+ * \param[in] zone zone name, if explicitly given, otherwise use SOA
+ * \param[in] class zone class
+ * \param[in] tsig_cred TSIG credentials
+ * \param[out] zone returns zone/owner rdf from the 'fqdn' SOA MNAME query 
+ */
+ldns_resolver *
+ldns_update_resolver_new(const char *fqdn, const char *zone,
+    ldns_rr_class class, ldns_tsig_credentials *tsig_cred, ldns_rdf **zone_rdf)
+{
+       ldns_resolver   *r1, *r2;
+       ldns_pkt        *query = NULL, *resp;
+       ldns_rr_list    *nslist, *iplist;
+       ldns_rdf        *soa_zone, *soa_mname, *ns_name;
+       int             i;
+
+       if (class == 0)
+               class = LDNS_RR_CLASS_IN;
+
+       /* First, get data from /etc/resolv.conf */
+       r1 = ldns_resolver_new_frm_file(NULL);
+       if (!r1)
+               return NULL;
+
+       r2 = ldns_resolver_new();
+       if (!r2)
+               goto bad;
+
+       /* TSIG key data available? Copy into the resolver. */
+       if (tsig_cred) {
+               ldns_resolver_set_tsig_algorithm(r2,
+                   ldns_tsig_algorithm(tsig_cred));
+               ldns_resolver_set_tsig_keyname(r2,
+                   ldns_tsig_keyname_clone(tsig_cred));
+               /*
+                * XXX Weird that ldns_resolver_deep_free() will free()
+                * keyname but not hmac key data?
+                */
+               ldns_resolver_set_tsig_keydata(r2,
+                   ldns_tsig_keydata_clone(tsig_cred));
+       }
+       
+       /* Now get SOA zone, mname, NS, and construct r2. [RFC2136 4.3] */
+
+       /* Explicit 'zone' or no? */
+       if (zone) {
+               soa_zone = ldns_dname_new_frm_str(zone);
+               if (ldns_update_get_soa_mname(soa_zone, r1, class, &soa_mname)
+                   != LDNS_STATUS_OK)
+                       goto bad;
+       } else {
+               if (ldns_update_get_soa_zone_mname(fqdn, r1, class, &soa_zone,
+                       &soa_mname) != LDNS_STATUS_OK)
+                       goto bad;
+       }
+       
+       /* Pass zone_rdf on upwards. */
+       *zone_rdf = ldns_rdf_clone(soa_zone);
+       
+       /* NS */
+       query = ldns_pkt_query_new(soa_zone, LDNS_RR_TYPE_NS, class, LDNS_RD);
+       if (!query)
+               goto bad;
+       soa_zone = NULL;
+
+       ldns_pkt_set_random_id(query);
+       if (ldns_resolver_send_pkt(&resp, r1, query) != LDNS_STATUS_OK) {
+               dprintf("%s", "NS query failed!\n");
+               goto bad;
+       }
+       ldns_pkt_free(query);
+       if (!resp)
+               goto bad;
+
+       /* Match SOA MNAME to NS list, adding it first */
+       nslist = ldns_pkt_answer(resp);
+       for (i = 0; i < ldns_rr_list_rr_count(nslist); i++) {
+               ns_name = ldns_rr_rdf(ldns_rr_list_rr(nslist, i), 0);
+               if (!ns_name)
+                       continue;
+               if (ldns_rdf_compare(soa_mname, ns_name) == 0) {
+                       /* Match */
+                       iplist = ldns_get_rr_list_addr_by_name(r1, ns_name,
+                           class, 0);
+                       ldns_resolver_push_nameserver_rr_list(r2, iplist);
+                       break;
+               }
+       }
+
+       /* Then all the other NSs. XXX Randomize? */
+       for (i = 0; i < ldns_rr_list_rr_count(nslist); i++) {
+               ns_name = ldns_rr_rdf(ldns_rr_list_rr(nslist, i), 0);
+               if (!ns_name)
+                       continue;
+               if (ldns_rdf_compare(soa_mname, ns_name) != 0) {
+                       /* No match, add it now. */
+                       iplist = ldns_get_rr_list_addr_by_name(r1, ns_name,
+                           class, 0);
+                       ldns_resolver_push_nameserver_rr_list(r2, iplist);
+               }
+       }
+
+       /* Cleanup and return. */
+       ldns_resolver_set_random(r2, false);
+       ldns_pkt_free(resp);
+       ldns_resolver_deep_free(r1);
+       return r2;
+       
+  bad:
+       if (r1)
+               ldns_resolver_deep_free(r1);
+       if (r2)
+               ldns_resolver_deep_free(r2);
+       if (query)
+               ldns_pkt_free(query);
+       if (resp)
+               ldns_pkt_free(resp);
+       return NULL;
+}
+
+/*
+ * ldns_update_{get,set}_{zo,pr,up,ad}.
+ */
+
+uint16_t
+ldns_update_get_zo(const ldns_pkt *p)
+{
+       return ldns_pkt_qdcount(p);
+}
+
+uint16_t
+ldns_update_get_pr(const ldns_pkt *p)
+{
+       return ldns_pkt_ancount(p);
+}
+
+uint16_t
+ldns_update_get_up(const ldns_pkt *p)
+{
+       return ldns_pkt_nscount(p);
+}
+
+uint16_t
+ldns_update_get_ad(const ldns_pkt *p)
+{
+       return ldns_pkt_arcount(p);
+}
+
+void
+ldns_update_set_zo(ldns_pkt *p, u_int16_t v)
+{
+       ldns_pkt_set_qdcount(p, v);
+}
+
+void
+ldns_update_set_pr(ldns_pkt *p, u_int16_t v)
+{
+       ldns_pkt_set_ancount(p, v);
+}
+
+void
+ldns_update_set_up(ldns_pkt *p, u_int16_t v)
+{
+       ldns_pkt_set_nscount(p, v);
+}
+
+void
+ldns_update_set_ad(ldns_pkt *p, u_int16_t v)
+{
+       ldns_pkt_set_arcount(p, v);
+}
+
+