s4 dns: Update prerequisite checking conforming to RFC
authorKai Blin <kai@samba.org>
Fri, 16 Dec 2011 12:45:22 +0000 (13:45 +0100)
committerKai Blin <kai@samba.org>
Sat, 17 Dec 2011 01:46:08 +0000 (02:46 +0100)
source4/dns_server/dns_server.c
source4/dns_server/dns_server.h
source4/dns_server/dns_update.c
source4/scripting/python/samba/tests/dns.py

index 613bf9725ced4f16bc64841d21be7fee7f80539a..25873c2bf4c8a6e7c45a6e11667ee687817d3be6 100644 (file)
@@ -147,7 +147,7 @@ static NTSTATUS dns_process(struct dns_server *dns,
                break;
        case DNS_OPCODE_UPDATE:
                ret = dns_server_process_update(dns, out_packet, in_packet,
-                                               answers, num_answers,
+                                               &answers, &num_answers,
                                                &nsrecs,  &num_nsrecs,
                                                &additional, &num_additional);
                break;
index f1e9da5ffd630158cb6bc80ea0ef2ca210d8f567..8570281baab9174a03a49912d65968ce26f68488 100644 (file)
@@ -50,7 +50,7 @@ WERROR dns_server_process_query(struct dns_server *dns,
 WERROR dns_server_process_update(struct dns_server *dns,
                                 TALLOC_CTX *mem_ctx,
                                 struct dns_name_packet *in,
-                                struct dns_res_rec *prereqs,     uint16_t prereq_count,
+                                struct dns_res_rec **prereqs,    uint16_t *prereq_count,
                                 struct dns_res_rec **updates,    uint16_t *update_count,
                                 struct dns_res_rec **additional, uint16_t *arcount);
 
index 55589d227a891b9525f0c5ae8dc6bf7c73d7d2de..5b87e9f66999c456f7f4c821e0f8b6beb0cb8c6b 100644 (file)
 #include "dsdb/common/util.h"
 #include "dns_server/dns_server.h"
 
-static WERROR check_prerequsites(struct dns_server *dns,
-                                TALLOC_CTX *mem_ctx,
-                                const struct dns_name_packet *in,
-                                const struct dns_res_rec *prereqs, uint16_t count)
+static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
+                            const struct dns_res_rec *rrec,
+                            struct dnsp_DnssrvRpcRecord *r);
+
+static WERROR check_one_prerequisite(struct dns_server *dns,
+                                    TALLOC_CTX *mem_ctx,
+                                    const struct dns_name_question *zone,
+                                    const struct dns_res_rec *pr,
+                                    bool *final_result)
 {
-       const struct dns_name_question *zone;
-       size_t host_part_len = 0;
+       bool match;
+       WERROR werror;
+       struct ldb_dn *dn;
        uint16_t i;
+       bool found = false;
+       struct dnsp_DnssrvRpcRecord *rec = NULL;
+       struct dnsp_DnssrvRpcRecord *ans;
+       uint16_t acount;
 
-       zone = in->questions;
+       size_t host_part_len = 0;
 
-       for (i = 0; i < count; i++) {
-               const struct dns_res_rec *r = &prereqs[i];
-               bool match;
+       *final_result = true;
+
+       if (pr->ttl != 0) {
+               return DNS_ERR(FORMAT_ERROR);
+       }
+
+       match = dns_name_match(zone->name, pr->name, &host_part_len);
+       if (!match) {
+               return DNS_ERR(NOTZONE);
+       }
 
-               if (r->ttl != 0) {
+       werror = dns_name2dn(dns, mem_ctx, pr->name, &dn);
+       W_ERROR_NOT_OK_RETURN(werror);
+
+       if (pr->rr_class == DNS_QCLASS_ANY) {
+
+               if (pr->length != 0) {
                        return DNS_ERR(FORMAT_ERROR);
                }
-               match = dns_name_match(zone->name, r->name, &host_part_len);
-               if (!match) {
-                       /* TODO: check if we need to echo all prereqs if the
-                        * first fails */
-                       return DNS_ERR(NOTZONE);
-               }
-               if (r->rr_class == DNS_QCLASS_ANY) {
-                       if (r->length != 0) {
-                               return DNS_ERR(FORMAT_ERROR);
-                       }
-                       if (r->rr_type == DNS_QTYPE_ALL) {
-                               /* TODO: Check if zone has at least one RR */
+
+
+               if (pr->rr_type == DNS_QTYPE_ALL) {
+                       /*
+                        */
+                       werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &acount);
+                       W_ERROR_NOT_OK_RETURN(werror);
+
+                       if (acount == 0) {
                                return DNS_ERR(NAME_ERROR);
-                       } else {
-                               /* TODO: Check if RR exists of the specified type */
+                       }
+               } else {
+                       /*
+                        */
+                       werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &acount);
+                       if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
                                return DNS_ERR(NXRRSET);
                        }
-               }
-               if (r->rr_class == DNS_QCLASS_NONE) {
-                       if (r->length != 0) {
-                               return DNS_ERR(FORMAT_ERROR);
+                       W_ERROR_NOT_OK_RETURN(werror);
+
+                       for (i = 0; i < acount; i++) {
+                               if (ans[i].wType == pr->rr_type) {
+                                       found = true;
+                                       break;
+                               }
                        }
-                       if (r->rr_type == DNS_QTYPE_ALL) {
-                               /* TODO: Return this error if the given name exits in this zone */
+                       if (!found) {
+                               return DNS_ERR(NXRRSET);
+                       }
+               }
+
+               /*
+                * RFC2136 3.2.5 doesn't actually mention the need to return
+                * OK here, but otherwise we'd always return a FORMAT_ERROR
+                * later on. This also matches Microsoft DNS behavior.
+                */
+               return WERR_OK;
+       }
+
+       if (pr->rr_class == DNS_QCLASS_NONE) {
+               if (pr->length != 0) {
+                       return DNS_ERR(FORMAT_ERROR);
+               }
+
+               if (pr->rr_type == DNS_QTYPE_ALL) {
+                       /*
+                        */
+                       werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &acount);
+                       if (W_ERROR_EQUAL(werror, WERR_OK)) {
                                return DNS_ERR(YXDOMAIN);
-                       } else {
-                               /* TODO: Return error if there's an RRset of this type in the zone */
+                       }
+               } else {
+                       /*
+                        */
+                       werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &acount);
+                       if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
+                               werror = WERR_OK;
+                               ans = NULL;
+                               acount = 0;
+                       }
+
+                       for (i = 0; i < acount; i++) {
+                               if (ans[i].wType == pr->rr_type) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       if (found) {
                                return DNS_ERR(YXRRSET);
                        }
                }
-               if (r->rr_class == zone->question_class) {
-                       /* Check if there's a RR with this */
-                       return DNS_ERR(NOT_IMPLEMENTED);
-               } else {
-                       return DNS_ERR(FORMAT_ERROR);
+
+               /*
+                * RFC2136 3.2.5 doesn't actually mention the need to return
+                * OK here, but otherwise we'd always return a FORMAT_ERROR
+                * later on. This also matches Microsoft DNS behavior.
+                */
+               return WERR_OK;
+       }
+
+       if (pr->rr_class != zone->question_class) {
+               return DNS_ERR(FORMAT_ERROR);
+       }
+
+       *final_result = false;
+
+       werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &acount);
+       if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
+               return DNS_ERR(NXRRSET);
+       }
+       W_ERROR_NOT_OK_RETURN(werror);
+
+       rec = talloc_zero(mem_ctx, struct dnsp_DnssrvRpcRecord);
+       W_ERROR_HAVE_NO_MEMORY(rec);
+
+       werror = dns_rr_to_dnsp(rec, pr, rec);
+       W_ERROR_NOT_OK_RETURN(werror);
+
+       for (i = 0; i < acount; i++) {
+               if (dns_records_match(rec, &ans[i])) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found) {
+               return DNS_ERR(NXRRSET);
+       }
+
+       return WERR_OK;
+}
+
+static WERROR check_prerequisites(struct dns_server *dns,
+                                 TALLOC_CTX *mem_ctx,
+                                 const struct dns_name_question *zone,
+                                 const struct dns_res_rec *prereqs, uint16_t count)
+{
+       uint16_t i;
+       WERROR final_error = WERR_OK;
+
+       for (i = 0; i < count; i++) {
+               bool final;
+               WERROR werror;
+
+               werror = check_one_prerequisite(dns, mem_ctx, zone,
+                                               &prereqs[i], &final);
+               if (!W_ERROR_IS_OK(werror)) {
+                       if (final) {
+                               return werror;
+                       }
+                       if (W_ERROR_IS_OK(final_error)) {
+                               final_error = werror;
+                       }
                }
        }
 
+       if (!W_ERROR_IS_OK(final_error)) {
+               return final_error;
+       }
 
        return WERR_OK;
 }
@@ -123,10 +246,76 @@ static WERROR update_prescan(const struct dns_name_question *zone,
        return WERR_OK;
 }
 
+static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
+                            const struct dns_res_rec *rrec,
+                            struct dnsp_DnssrvRpcRecord *r)
+{
+       if (rrec->rr_type == DNS_QTYPE_ALL) {
+               return DNS_ERR(FORMAT_ERROR);
+       }
+
+       ZERO_STRUCTP(r);
+
+       r->wType = rrec->rr_type;
+       r->dwTtlSeconds = rrec->ttl;
+       r->rank = DNS_RANK_ZONE;
+       /* TODO: Autogenerate this somehow */
+       r->dwSerial = 110;
+
+       /* If we get QCLASS_ANY, we're done here */
+       if (rrec->rr_class == DNS_QCLASS_ANY) {
+               goto done;
+       }
+
+       switch(rrec->rr_type) {
+       case DNS_QTYPE_A:
+               r->data.ipv4 = talloc_strdup(mem_ctx, rrec->rdata.ipv4_record);
+               W_ERROR_HAVE_NO_MEMORY(r->data.ipv4);
+               break;
+       case DNS_QTYPE_AAAA:
+               r->data.ipv6 = talloc_strdup(mem_ctx, rrec->rdata.ipv6_record);
+               W_ERROR_HAVE_NO_MEMORY(r->data.ipv6);
+               break;
+       case DNS_QTYPE_NS:
+               r->data.ns = talloc_strdup(mem_ctx, rrec->rdata.ns_record);
+               W_ERROR_HAVE_NO_MEMORY(r->data.ns);
+               break;
+       case DNS_QTYPE_CNAME:
+               r->data.cname = talloc_strdup(mem_ctx, rrec->rdata.cname_record);
+               W_ERROR_HAVE_NO_MEMORY(r->data.cname);
+               break;
+       case DNS_QTYPE_SRV:
+               r->data.srv.wPriority = rrec->rdata.srv_record.priority;
+               r->data.srv.wWeight = rrec->rdata.srv_record.weight;
+               r->data.srv.wPort = rrec->rdata.srv_record.port;
+               r->data.srv.nameTarget = talloc_strdup(mem_ctx,
+                               rrec->rdata.srv_record.target);
+               W_ERROR_HAVE_NO_MEMORY(r->data.srv.nameTarget);
+               break;
+       case DNS_QTYPE_MX:
+               r->data.mx.wPriority = rrec->rdata.mx_record.preference;
+               r->data.mx.nameTarget = talloc_strdup(mem_ctx,
+                               rrec->rdata.mx_record.exchange);
+               W_ERROR_HAVE_NO_MEMORY(r->data.mx.nameTarget);
+               break;
+       case DNS_QTYPE_TXT:
+               r->data.txt = talloc_strdup(mem_ctx, rrec->rdata.txt_record.txt);
+               W_ERROR_HAVE_NO_MEMORY(r->data.txt);
+               break;
+       default:
+               DEBUG(0, ("Got a qytpe of %d\n", rrec->rr_type));
+               return DNS_ERR(NOT_IMPLEMENTED);
+       }
+
+done:
+
+       return WERR_OK;
+}
+
 WERROR dns_server_process_update(struct dns_server *dns,
                                 TALLOC_CTX *mem_ctx,
                                 struct dns_name_packet *in,
-                                struct dns_res_rec *prereqs,     uint16_t prereq_count,
+                                struct dns_res_rec **prereqs,    uint16_t *prereq_count,
                                 struct dns_res_rec **updates,    uint16_t *update_count,
                                 struct dns_res_rec **additional, uint16_t *arcount)
 {
@@ -171,7 +360,10 @@ WERROR dns_server_process_update(struct dns_server *dns,
                return DNS_ERR(NOT_IMPLEMENTED);
        }
 
-       werror = check_prerequsites(dns, mem_ctx, in, prereqs, prereq_count);
+       *prereq_count = in->ancount;
+       *prereqs = in->answers;
+       werror = check_prerequisites(dns, mem_ctx, in->questions, *prereqs,
+                                    *prereq_count);
        W_ERROR_NOT_OK_RETURN(werror);
 
        /* TODO: Check if update is allowed, we probably want "always",
index ed78d56dd3034e3b1e73628e408de1d6429cbfe5..ca9edbf50009c7e5dca4b9eb60774985d15617ca 100644 (file)
@@ -236,6 +236,85 @@ class DNSTest(TestCase):
         response = self.dns_transaction_udp(p)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NOTIMP)
 
+    def test_update_prereq_with_non_null_ttl(self):
+        "test update with a non-null TTL"
+        p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+        updates = []
+
+        name = self.get_dns_domain()
+
+        u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+        updates.append(u)
+        self.finish_name_packet(p, updates)
+
+        prereqs = []
+        r = dns.res_rec()
+        r.name = "%s.%s" % (os.getenv('DC_SERVER'), self.get_dns_domain())
+        r.rr_type = dns.DNS_QTYPE_TXT
+        r.rr_class = dns.DNS_QCLASS_NONE
+        r.ttl = 1
+        r.length = 0
+        prereqs.append(r)
+
+        p.ancount = len(prereqs)
+        p.answers = prereqs
+
+        response = self.dns_transaction_udp(p)
+        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+# I'd love to test this one, but it segfaults. :)
+#    def test_update_prereq_with_non_null_length(self):
+#        "test update with a non-null length"
+#        p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+#        updates = []
+#
+#        name = self.get_dns_domain()
+#
+#        u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+#        updates.append(u)
+#        self.finish_name_packet(p, updates)
+#
+#        prereqs = []
+#        r = dns.res_rec()
+#        r.name = "%s.%s" % (os.getenv('DC_SERVER'), self.get_dns_domain())
+#        r.rr_type = dns.DNS_QTYPE_TXT
+#        r.rr_class = dns.DNS_QCLASS_ANY
+#        r.ttl = 0
+#        r.length = 1
+#        prereqs.append(r)
+#
+#        p.ancount = len(prereqs)
+#        p.answers = prereqs
+#
+#        response = self.dns_transaction_udp(p)
+#        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+    def test_update_prereq_nonexisting_name(self):
+        "test update with a non-null TTL"
+        p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+        updates = []
+
+        name = self.get_dns_domain()
+
+        u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+        updates.append(u)
+        self.finish_name_packet(p, updates)
+
+        prereqs = []
+        r = dns.res_rec()
+        r.name = "idontexist.%s" % self.get_dns_domain()
+        r.rr_type = dns.DNS_QTYPE_TXT
+        r.rr_class = dns.DNS_QCLASS_ANY
+        r.ttl = 0
+        r.length = 0
+        prereqs.append(r)
+
+        p.ancount = len(prereqs)
+        p.answers = prereqs
+
+        response = self.dns_transaction_udp(p)
+        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET)
+
 if __name__ == "__main__":
     import unittest
     unittest.main()