From 02c9e18094f68d9014fe56c4d1b1c51fd22bd1b5 Mon Sep 17 00:00:00 2001 From: Kai Blin Date: Fri, 16 Dec 2011 13:45:22 +0100 Subject: [PATCH] s4 dns: Update prerequisite checking conforming to RFC --- source4/dns_server/dns_server.c | 2 +- source4/dns_server/dns_server.h | 2 +- source4/dns_server/dns_update.c | 272 ++++++++++++++++++++++++---- source4/scripting/python/samba/tests/dns.py | 79 ++++++++ 4 files changed, 313 insertions(+), 42 deletions(-) diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c index 613bf9725ce..25873c2bf4c 100644 --- a/source4/dns_server/dns_server.c +++ b/source4/dns_server/dns_server.c @@ -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; diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h index f1e9da5ffd6..8570281baab 100644 --- a/source4/dns_server/dns_server.h +++ b/source4/dns_server/dns_server.h @@ -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); diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c index 55589d227a8..5b87e9f6699 100644 --- a/source4/dns_server/dns_update.c +++ b/source4/dns_server/dns_update.c @@ -29,62 +29,185 @@ #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", diff --git a/source4/scripting/python/samba/tests/dns.py b/source4/scripting/python/samba/tests/dns.py index ed78d56dd30..ca9edbf5000 100644 --- a/source4/scripting/python/samba/tests/dns.py +++ b/source4/scripting/python/samba/tests/dns.py @@ -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() -- 2.11.4.GIT