From 35b9ea867b8ab59e54826449eb8bbf82839dc2f6 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 25 Jul 2017 14:14:53 +1200 Subject: [PATCH 1/5] dnsserver: Tests for dns wildcard entries Add tests for dns wildcards. Tests validated against Windows Server 2012 R2 Signed-off-by: Gary Lockyer --- python/samba/tests/dns_wildcard.py | 288 +++++++++++++++++++++++++++++++++++++ selftest/knownfail.d/dns_wildcard | 2 + source4/selftest/tests.py | 2 +- 3 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 python/samba/tests/dns_wildcard.py create mode 100644 selftest/knownfail.d/dns_wildcard diff --git a/python/samba/tests/dns_wildcard.py b/python/samba/tests/dns_wildcard.py new file mode 100644 index 00000000000..ca8426a6f14 --- /dev/null +++ b/python/samba/tests/dns_wildcard.py @@ -0,0 +1,288 @@ +# Unix SMB/CIFS implementation. +# Copyright (C) Andrew Bartlett 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import sys +from samba import credentials +from samba.dcerpc import dns, dnsserver +from samba.netcmd.dns import data_to_dns_record +from samba.tests.subunitrun import SubunitOptions, TestProgram +from samba import werror, WERRORError +from samba.tests.dns_base import DNSTest +import samba.getopt as options +import optparse + +parser = optparse.OptionParser( + "dns_wildcard.py [options]") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) + +# This timeout only has relevance when testing against Windows +# Format errors tend to return patchy responses, so a timeout is needed. +parser.add_option("--timeout", type="int", dest="timeout", + help="Specify timeout for DNS requests") + +# To run against Windows +# python python/samba/tests/dns_wildcard.py computer_name ip +# -U"Administrator%admin_password" +# --realm=Domain_name +# --timeout 10 +# + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +opts, args = parser.parse_args() + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) + +timeout = opts.timeout + +if len(args) < 2: + parser.print_usage() + sys.exit(1) + +server_name = args[0] +server_ip = args[1] +creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE) + +WILDCARD_IP = "1.1.1.1" +WILDCARD = "*.wildcardtest" +EXACT_IP = "1.1.1.2" +EXACT = "exact.wildcardtest" +LEVEL2_WILDCARD_IP = "1.1.1.3" +LEVEL2_WILDCARD = "*.level2.wildcardtest" +LEVEL2_EXACT_IP = "1.1.1.4" +LEVEL2_EXACT = "exact.level2.wildcardtest" + + +class TestWildCardQueries(DNSTest): + + def setUp(self): + super(TestWildCardQueries, self).setUp() + global server, server_ip, lp, creds, timeout + self.server = server_name + self.server_ip = server_ip + self.lp = lp + self.creds = creds + self.timeout = timeout + + # Create the dns records + self.dns_records = [(dns.DNS_QTYPE_A, + "%s.%s" % (WILDCARD, self.get_dns_domain()), + WILDCARD_IP), + (dns.DNS_QTYPE_A, + "%s.%s" % (EXACT, self.get_dns_domain()), + EXACT_IP), + (dns.DNS_QTYPE_A, + ("%s.%s" % ( + LEVEL2_WILDCARD, + self.get_dns_domain())), + LEVEL2_WILDCARD_IP), + (dns.DNS_QTYPE_A, + ("%s.%s" % ( + LEVEL2_EXACT, + self.get_dns_domain())), + LEVEL2_EXACT_IP)] + + c = self.dns_connect() + for (typ, name, data) in self.dns_records: + self.add_record(c, typ, name, data) + + def tearDown(self): + c = self.dns_connect() + for (typ, name, data) in self.dns_records: + self.delete_record(c, typ, name, data) + + def dns_connect(self): + binding_str = "ncacn_ip_tcp:%s[sign]" % self.server_ip + return dnsserver.dnsserver(binding_str, self.lp, self.creds) + + def delete_record(self, dns_conn, typ, name, data): + + rec = data_to_dns_record(typ, data) + del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + del_rec_buf.rec = rec + + try: + dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, + self.server, + self.get_dns_domain(), + name, + None, + del_rec_buf) + except WERRORError as e: + # Ignore record does not exist errors + if e.args[0] != werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST: + raise e + + def add_record(self, dns_conn, typ, name, data): + + rec = data_to_dns_record(typ, data) + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, + self.server, + self.get_dns_domain(), + name, + add_rec_buf, + None) + except WERRORError as e: + raise e + + def test_one_a_query_match_wildcard(self): + "Query an A record, should match the wildcard entry" + + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "miss.wildcardtest.%s" % self.get_dns_domain() + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, WILDCARD_IP) + + def test_one_a_query_wildcard_entry(self): + "Query the wildcard entry" + + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "%s.%s" % (WILDCARD, self.get_dns_domain()) + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, WILDCARD_IP) + + def test_one_a_query_exact_match(self): + """Query an entry that matches the wild card but has an exact match as + well. + """ + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "%s.%s" % (EXACT, self.get_dns_domain()) + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, EXACT_IP) + + def test_one_a_query_match_wildcard_l2(self): + "Query an A record, should match the level 2 wildcard entry" + + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "miss.level2.wildcardtest.%s" % self.get_dns_domain() + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, LEVEL2_WILDCARD_IP) + + def test_one_a_query_exact_match_l2(self): + """Query an entry that matches the wild card but has an exact match as + well. + """ + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "%s.%s" % (LEVEL2_EXACT, self.get_dns_domain()) + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, LEVEL2_EXACT_IP) + + def test_one_a_query_wildcard_entry_l2(self): + "Query the level 2 wildcard entry" + + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + # Check the record + name = "%s.%s" % (LEVEL2_WILDCARD, self.get_dns_domain()) + q = self.make_name_question(name, + dns.DNS_QTYPE_A, + dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + (response, response_packet) =\ + self.dns_transaction_udp(p, host=self.server_ip) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_A) + self.assertEquals(response.answers[0].rdata, LEVEL2_WILDCARD_IP) + + +TestProgram(module=__name__, opts=subunitopts) diff --git a/selftest/knownfail.d/dns_wildcard b/selftest/knownfail.d/dns_wildcard new file mode 100644 index 00000000000..7e7892f3ee4 --- /dev/null +++ b/selftest/knownfail.d/dns_wildcard @@ -0,0 +1,2 @@ +^samba.tests.dns_wildcard.__main__.TestWildCardQueries.test_one_a_query_match_wildcard\(ad_dc\) +^samba.tests.dns_wildcard.__main__.TestWildCardQueries.test_one_a_query_match_wildcard_l2\(ad_dc\) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 3f3d21685ff..da48730bb96 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -367,7 +367,7 @@ plantestsuite_loadlist("samba.tests.dns", "vampire_dc:local", [python, os.path.j plantestsuite_loadlist("samba.tests.dns_forwarder", "fl2003dc:local", [python, os.path.join(srcdir(), "python/samba/tests/dns_forwarder.py"), '$SERVER', '$SERVER_IP', '$DNS_FORWARDER1', '$DNS_FORWARDER2', '--machine-pass', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba.tests.dns_tkey", "fl2008r2dc", [python, os.path.join(srcdir(), "python/samba/tests/dns_tkey.py"), '$SERVER', '$SERVER_IP', '--machine-pass', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) - +plantestsuite_loadlist("samba.tests.dns_wildcard", "ad_dc", [python, os.path.join(srcdir(), "python/samba/tests/dns_wildcard.py"), '$SERVER', '$SERVER_IP', '--machine-pass', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) for t in smbtorture4_testsuites("dns_internal."): plansmbtorture4testsuite(t, "ad_dc_ntvfs:local", '//$SERVER/whavever') -- 2.11.0 From 6f6e31e2cddd6ae4cfb19d22a74abed3be3cee61 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 3 Aug 2017 15:12:51 +1200 Subject: [PATCH 2/5] dnsserver: Tighten DNS name checking Add checks for the maximum permitted length, maximum number of labels and the maximum label length. These extra checks will be used by the DNS wild card handling. Signed-off-by: Gary Lockyer --- librpc/idl/dns.idl | 3 +++ source4/dns_server/dnsserver_common.c | 35 +++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/librpc/idl/dns.idl b/librpc/idl/dns.idl index aebb106b053..8e8eed5ab23 100644 --- a/librpc/idl/dns.idl +++ b/librpc/idl/dns.idl @@ -18,6 +18,9 @@ import "misc.idl", "dnsp.idl"; interface dns { const int DNS_SERVICE_PORT = 53; + const int DNS_MAX_LABELS = 127; + const int DNS_MAX_DOMAIN_LENGTH = 253; + const int DNS_MAX_LABEL_LENGTH = 63; typedef [public,bitmap16bit] bitmap { DNS_RCODE = 0x000F, diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c index a56ff08031b..2a81b836722 100644 --- a/source4/dns_server/dnsserver_common.c +++ b/source4/dns_server/dnsserver_common.c @@ -246,25 +246,48 @@ static int rec_cmp(const struct dnsp_DnssrvRpcRecord *r1, } /* - * Check for valid DNS names. These are names which are non-empty, do not - * start with a dot and do not have any empty segments. + * Check for valid DNS names. These are names which: + * - are non-empty + * - do not start with a dot + * - do not have any empty labels + * - have no more than 127 labels + * - are no longer than 253 characters + * - none of the labels exceed 63 characters */ WERROR dns_name_check(TALLOC_CTX *mem_ctx, size_t len, const char *name) { size_t i; + unsigned int labels = 0; + unsigned int label_len = 0; if (len == 0) { return WERR_DS_INVALID_DN_SYNTAX; } + if (len > 1 && name[0] == '.') { + return WERR_DS_INVALID_DN_SYNTAX; + } + + if ((len - 1) > DNS_MAX_DOMAIN_LENGTH) { + return WERR_DS_INVALID_DN_SYNTAX; + } + for (i = 0; i < len - 1; i++) { if (name[i] == '.' && name[i+1] == '.') { return WERR_DS_INVALID_DN_SYNTAX; } - } - - if (len > 1 && name[0] == '.') { - return WERR_DS_INVALID_DN_SYNTAX; + if (name[i] == '.') { + labels++; + if (labels > DNS_MAX_LABELS) { + return WERR_DS_INVALID_DN_SYNTAX; + } + label_len = 0; + } else { + label_len++; + if (label_len > DNS_MAX_LABEL_LENGTH) { + return WERR_DS_INVALID_DN_SYNTAX; + } + } } return WERR_OK; -- 2.11.0 From f37efdf6fd0fc53519abfdad3ad4927638ca3769 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 3 Aug 2017 15:12:02 +1200 Subject: [PATCH 3/5] dnsserver: Add support for dns wildcards Add support for dns wildcard records. i.e. if the following records exist exact.samba.example.com 3600 A 1.1.1.1 *.samba.example.com 3600 A 1.1.1.2 look up on exact.samba.example.com will return 1.1.1.1 look up on *.samba.example.com will return 1.1.1.2 look up on other.samba.example.com will return 1.1.1.2 Signed-off-by: Gary Lockyer --- selftest/knownfail.d/dns_wildcard | 2 - source4/dns_server/dlz_bind9.c | 4 +- source4/dns_server/dns_query.c | 5 +- source4/dns_server/dns_server.h | 5 + source4/dns_server/dns_utils.c | 18 ++ source4/dns_server/dnsserver_common.c | 343 ++++++++++++++++++++++++++++++++++ source4/dns_server/dnsserver_common.h | 5 + 7 files changed, 375 insertions(+), 7 deletions(-) delete mode 100644 selftest/knownfail.d/dns_wildcard diff --git a/selftest/knownfail.d/dns_wildcard b/selftest/knownfail.d/dns_wildcard deleted file mode 100644 index 7e7892f3ee4..00000000000 --- a/selftest/knownfail.d/dns_wildcard +++ /dev/null @@ -1,2 +0,0 @@ -^samba.tests.dns_wildcard.__main__.TestWildCardQueries.test_one_a_query_match_wildcard\(ad_dc\) -^samba.tests.dns_wildcard.__main__.TestWildCardQueries.test_one_a_query_match_wildcard_l2\(ad_dc\) diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c index 7096f4749b2..6ef378c75a6 100644 --- a/source4/dns_server/dlz_bind9.c +++ b/source4/dns_server/dlz_bind9.c @@ -865,8 +865,8 @@ static isc_result_t dlz_lookup_types(struct dlz_bind9_data *state, return ISC_R_NOMEMORY; } - werr = dns_common_lookup(state->samdb, tmp_ctx, dn, - &records, &num_records, NULL); + werr = dns_common_wildcard_lookup(state->samdb, tmp_ctx, dn, + &records, &num_records); if (W_ERROR_IS_OK(werr)) { break; } diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c index 4b5bb0772a4..fa9272156ab 100644 --- a/source4/dns_server/dns_query.c +++ b/source4/dns_server/dns_query.c @@ -625,9 +625,8 @@ static struct tevent_req *handle_authoritative_send( if (tevent_req_werror(req, werr)) { return tevent_req_post(req, ev); } - - werr = dns_lookup_records(dns, state, dn, &state->recs, - &state->rec_count); + werr = dns_lookup_records_wildcard(dns, state, dn, &state->recs, + &state->rec_count); TALLOC_FREE(dn); if (tevent_req_werror(req, werr)) { return tevent_req_post(req, ev); diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h index 5395ff95161..382b6bdf95b 100644 --- a/source4/dns_server/dns_server.h +++ b/source4/dns_server/dns_server.h @@ -95,6 +95,11 @@ WERROR dns_lookup_records(struct dns_server *dns, struct ldb_dn *dn, struct dnsp_DnssrvRpcRecord **records, uint16_t *rec_count); +WERROR dns_lookup_records_wildcard(struct dns_server *dns, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct dnsp_DnssrvRpcRecord **records, + uint16_t *rec_count); WERROR dns_replace_records(struct dns_server *dns, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, diff --git a/source4/dns_server/dns_utils.c b/source4/dns_server/dns_utils.c index c728eaa8d39..ee35bd223f7 100644 --- a/source4/dns_server/dns_utils.c +++ b/source4/dns_server/dns_utils.c @@ -107,6 +107,10 @@ bool dns_records_match(struct dnsp_DnssrvRpcRecord *rec1, return false; } +/* + * Lookup a DNS record, performing an exact match. + * i.e. DNS wild card records are not considered. + */ WERROR dns_lookup_records(struct dns_server *dns, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, @@ -117,6 +121,20 @@ WERROR dns_lookup_records(struct dns_server *dns, records, rec_count, NULL); } +/* + * Lookup a DNS record, will match DNS wild card records if an exact match + * is not found. + */ +WERROR dns_lookup_records_wildcard(struct dns_server *dns, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct dnsp_DnssrvRpcRecord **records, + uint16_t *rec_count) +{ + return dns_common_wildcard_lookup(dns->samdb, mem_ctx, dn, + records, rec_count); +} + WERROR dns_replace_records(struct dns_server *dns, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c index 2a81b836722..919b227bae4 100644 --- a/source4/dns_server/dnsserver_common.c +++ b/source4/dns_server/dnsserver_common.c @@ -133,6 +133,10 @@ WERROR dns_common_extract(struct ldb_context *samdb, return WERR_OK; } +/* + * Lookup a DNS record, performing an exact match. + * i.e. DNS wild card records are not considered. + */ WERROR dns_common_lookup(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, @@ -229,6 +233,345 @@ WERROR dns_common_lookup(struct ldb_context *samdb, return WERR_OK; } +/* + * Build an ldb_parse_tree node for an equality check + * + * Note: name is assumed to have been validated by dns_name_check + * so will be zero terminated and of a reasonable size. + */ +static struct ldb_parse_tree *build_equality_operation( + TALLOC_CTX *mem_ctx, + bool add_asterix, /* prepend an '*' to the name */ + const uint8_t *name, /* the value being matched */ + const char *attr, /* the attribute to check name against */ + size_t size) /* length of name */ +{ + + struct ldb_parse_tree *el = NULL; /* Equality node being built */ + struct ldb_val *value = NULL; /* Value the attr will be compared + with */ + size_t length = 0; /* calculated length of the value + including option '*' prefix and + '\0' string terminator */ + + el = talloc(mem_ctx, struct ldb_parse_tree); + if (el == NULL) { + DBG_ERR("Unable to allocate ldb_parse_tree\n"); + return NULL; + } + + el->operation = LDB_OP_EQUALITY; + el->u.equality.attr = talloc_strdup(mem_ctx, attr); + value = &el->u.equality.value; + length = (add_asterix) ? size + 2 : size + 1; + value->data = talloc_zero_array(el, uint8_t, length); + if (el == NULL) { + DBG_ERR("Unable to allocate value->data\n"); + TALLOC_FREE(el); + return NULL; + } + + value->length = length; + if (add_asterix) { + value->data[0] = '*'; + memcpy(&value->data[1], name, size); + } else { + memcpy(value->data, name, size); + } + return el; +} + +/* + * Determine the number of levels in name + * essentially the number of '.'s in the name + 1 + * + * name is assumed to have been validated by dns_name_check + */ +static unsigned int number_of_labels(const struct ldb_val *name) { + int x = 0; + unsigned int labels = 1; + for (x = 0; x < name->length; x++) { + if (name->data[x] == '.') { + labels++; + } + } + return labels; +} +/* + * Build a query that matches the target name, and any possible + * DNS wild card entries + * + * Builds a parse tree equivalent to the example query. + * + * x.y.z -> (|(name=x.y.z)(name=\2a.y.z)(name=\2a.z)(name=\2a)) + * + * Returns NULL if unable to build the query. + * + * The first component of the DN is assumed to be the name being looked up + * and also that it has been validated by dns_name_check + * + */ +#define BASE "(&(objectClass=dnsNode)(!(dNSTombstoned=TRUE))(|(a=b)(c=d)))" +static struct ldb_parse_tree *build_wildcard_query( + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn) +{ + const struct ldb_val *name = NULL; /* The DNS name being + queried */ + const char *attr = NULL; /* The attribute name */ + struct ldb_parse_tree *query = NULL; /* The constructed query + parse tree*/ + struct ldb_parse_tree *wildcard_query = NULL; /* The parse tree for the + name and wild card + entries */ + int labels = 0; /* The number of labels in the name */ + + attr = ldb_dn_get_rdn_name(dn); + if (attr == NULL) { + DBG_ERR("Unable to get rdn_name\n"); + return NULL; + } + + name = ldb_dn_get_rdn_val(dn); + if (name == NULL) { + DBG_ERR("Unable to get domain name value\n"); + return NULL; + } + labels = number_of_labels(name); + + query = ldb_parse_tree(mem_ctx, BASE); + if (query == NULL) { + DBG_ERR("Unable to parse query %s\n", BASE); + return NULL; + } + + /* + * The 3rd element of BASE is a place holder which is replaced with + * the actual wild card query + */ + wildcard_query = query->u.list.elements[2]; + TALLOC_FREE(wildcard_query->u.list.elements); + + wildcard_query->u.list.num_elements = labels + 1; + wildcard_query->u.list.elements = talloc_array( + wildcard_query, + struct ldb_parse_tree *, + labels + 1); + /* + * Build the wild card query + */ + { + int x = 0; /* current character in the name */ + int l = 0; /* current equality operator index in elements */ + struct ldb_parse_tree *el = NULL; /* Equality operator being + built */ + bool add_asterix = true; /* prepend an '*' to the value */ + for (l = 0, x = 0; l < labels && x < name->length; l++) { + unsigned int size = name->length - x; + add_asterix = (name->data[x] == '.'); + el = build_equality_operation( + mem_ctx, + add_asterix, + &name->data[x], + attr, + size); + if (el == NULL) { + return NULL; /* Reason will have been logged */ + } + wildcard_query->u.list.elements[l] = el; + + /* skip to the start of the next label */ + for (;x < name->length && name->data[x] != '.'; x++); + } + + /* Add the base level "*" only query */ + el = build_equality_operation(mem_ctx, true, NULL, attr, 0); + if (el == NULL) { + TALLOC_FREE(query); + return NULL; /* Reason will have been logged */ + } + wildcard_query->u.list.elements[l] = el; + } + return query; +} + +/* + * Scan the list of records matching a dns wildcard query and return the + * best match. + * + * The best match is either an exact name match, or the longest wild card + * entry returned + * + * i.e. name = a.b.c candidates *.b.c, *.c, - *.b.c would be selected + * name = a.b.c candidates a.b.c, *.b.c, *.c - a.b.c would be selected + */ +static struct ldb_message *get_best_match(struct ldb_dn *dn, + struct ldb_result *result) +{ + int matched = 0; /* Index of the current best match in result */ + size_t length = 0; /* The length of the current candidate */ + const struct ldb_val *target = NULL; /* value we're looking for */ + const struct ldb_val *candidate = NULL; /* current candidate value */ + int x = 0; + + target = ldb_dn_get_rdn_val(dn); + for(x = 0; x < result->count; x++) { + candidate = ldb_dn_get_rdn_val(result->msgs[x]->dn); + if (strncasecmp((char *) target->data, + (char *) candidate->data, + target->length) == 0) { + /* Exact match stop searching and return */ + return result->msgs[x]; + } + if (candidate->length > length) { + matched = x; + length = candidate->length; + } + } + candidate = ldb_dn_get_rdn_val(result->msgs[matched]->dn); + return result->msgs[matched]; +} + +/* + * Look up a DNS entry, if an exact match does not exist, return the + * closest matching DNS wildcard entry if available + * + * Returns: LDB_ERR_NO_SUCH_OBJECT If no matching record exists + * LDB_ERR_OPERATIONS_ERROR If the query fails + * LDB_SUCCESS If a matching record was retrieved + * + */ +static int dns_wildcard_lookup(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct ldb_message **msg) +{ + static const char * const attrs[] = { + "dnsRecord", + "dNSTombstoned", + NULL + }; + struct ldb_dn *parent = NULL; /* The parent dn */ + struct ldb_result *result = NULL; /* Results of the search */ + int ret; /* Return code */ + struct ldb_parse_tree *query = NULL; /* The query to run */ + struct ldb_request *request = NULL; /* LDB request for the query op */ + TALLOC_CTX *frame = talloc_stackframe(); + + parent = ldb_dn_get_parent(frame, dn); + if (parent == NULL) { + DBG_ERR("Unable to extract parent from dn\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + query = build_wildcard_query(frame, dn); + if (query == NULL) { + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + result = talloc_zero(mem_ctx, struct ldb_result); + if (result == NULL) { + TALLOC_FREE(frame); + DBG_ERR("Unable to allocate ldb_result\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req_ex(&request, + samdb, + frame, + parent, + LDB_SCOPE_ONELEVEL, + query, + attrs, + NULL, + result, + ldb_search_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + DBG_ERR("ldb_build_search_req_ex returned %d\n", ret); + return ret; + } + + ret = ldb_request(samdb, request); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(request->handle, LDB_WAIT_ALL); + } + TALLOC_FREE(request); + + if (ret == LDB_SUCCESS) { + if (result->count == 0) { + ret = LDB_ERR_NO_SUCH_OBJECT; + } else { + struct ldb_message *match = + get_best_match(dn, result); + if (match == NULL) { + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + *msg = talloc_move(mem_ctx, &match); + } + } + TALLOC_FREE(frame); + return ret; +} + +/* + * Lookup a DNS record, will match DNS wild card records if an exact match + * is not found. + */ +WERROR dns_common_wildcard_lookup(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct dnsp_DnssrvRpcRecord **records, + uint16_t *num_records) +{ + int ret; + WERROR werr; + struct ldb_message *msg = NULL; + struct ldb_message_element *el = NULL; + const struct ldb_val *name = NULL; + + *records = NULL; + *num_records = 0; + + name = ldb_dn_get_rdn_val(dn); + if (name == NULL) { + return DNS_ERR(NAME_ERROR); + } + + werr = dns_name_check( + mem_ctx, + strlen((const char*)name->data), + (const char*) name->data); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ret = dns_wildcard_lookup(samdb, mem_ctx, dn, &msg); + if (ret == LDB_ERR_OPERATIONS_ERROR) { + return DNS_ERR(SERVER_FAILURE); + } + if (ret != LDB_SUCCESS) { + return DNS_ERR(NAME_ERROR); + } + + el = ldb_msg_find_element(msg, "dnsRecord"); + if (el == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + werr = dns_common_extract(samdb, el, mem_ctx, records, num_records); + TALLOC_FREE(msg); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + return WERR_OK; +} + static int rec_cmp(const struct dnsp_DnssrvRpcRecord *r1, const struct dnsp_DnssrvRpcRecord *r2) { diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h index b615e2dcfae..f2be44ff0d6 100644 --- a/source4/dns_server/dnsserver_common.h +++ b/source4/dns_server/dnsserver_common.h @@ -47,6 +47,11 @@ WERROR dns_common_lookup(struct ldb_context *samdb, struct dnsp_DnssrvRpcRecord **records, uint16_t *num_records, bool *tombstoned); +WERROR dns_common_wildcard_lookup(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct dnsp_DnssrvRpcRecord **records, + uint16_t *num_records); WERROR dns_name_check(TALLOC_CTX *mem_ctx, size_t len, const char *name); -- 2.11.0 From f65a9972cdc5b092c9ea94d56d92ffa9822acbf7 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Mon, 7 Aug 2017 13:42:02 +1200 Subject: [PATCH 4/5] samba-tool dns: Test support of DNS wild card in names As DNS wild cards are now supported we need to allow '*' characters in the domain names. Signed-off-by: Gary Lockyer --- python/samba/tests/samba_tool/dnscmd.py | 67 +++++++++++++++++++++++++++++++++ selftest/knownfail.d/sambatooldns | 3 ++ 2 files changed, 70 insertions(+) create mode 100644 selftest/knownfail.d/sambatooldns diff --git a/python/samba/tests/samba_tool/dnscmd.py b/python/samba/tests/samba_tool/dnscmd.py index 3a369d95b8a..1712c0efde1 100644 --- a/python/samba/tests/samba_tool/dnscmd.py +++ b/python/samba/tests/samba_tool/dnscmd.py @@ -659,3 +659,70 @@ class DnsCmdTestCase(SambaToolCmdTest): self.zone, "testrecord2", "A", self.testip, self.creds_string) self.assertCmdFail(result) + + def test_dns_wildcards(self): + """ + Ensure that DNS wild card entries can be added deleted and queried + """ + num_failures = 0 + failure_msgs = [] + records = [("*.", "MISS", "A", "1.1.1.1"), + ("*.SAMDOM", "MISS.SAMDOM", "A", "1.1.1.2")] + for (name, miss, dnstype, record) in records: + try: + result, out, err = self.runsubcmd("dns", "add", + os.environ["SERVER"], + self.zone, name, + dnstype, record, + self.creds_string) + self.assertCmdSuccess( + result, + out, + err, + ("Failed to add record %s (%s) with type %s." + % (name, record, dnstype))) + + result, out, err = self.runsubcmd("dns", "query", + os.environ["SERVER"], + self.zone, name, + dnstype, + self.creds_string) + self.assertCmdSuccess( + result, + out, + err, + ("Failed to query record %s with qualifier %s." + % (record, dnstype))) + + # dns tool does not perform dns wildcard search if the name + # does not match + result, out, err = self.runsubcmd("dns", "query", + os.environ["SERVER"], + self.zone, miss, + dnstype, + self.creds_string) + self.assertCmdFail( + result, + ("Failed to query record %s with qualifier %s." + % (record, dnstype))) + + result, out, err = self.runsubcmd("dns", "delete", + os.environ["SERVER"], + self.zone, name, + dnstype, record, + self.creds_string) + self.assertCmdSuccess( + result, + out, + err, + ("Failed to remove record %s with type %s." + % (record, dnstype))) + except AssertionError as e: + num_failures = num_failures + 1 + failure_msgs.append(e) + + if num_failures > 0: + for msg in failure_msgs: + print(msg) + self.fail("Failed to accept valid commands. %d total failures." + "Errors above." % num_failures) diff --git a/selftest/knownfail.d/sambatooldns b/selftest/knownfail.d/sambatooldns new file mode 100644 index 00000000000..b60e9b21e73 --- /dev/null +++ b/selftest/knownfail.d/sambatooldns @@ -0,0 +1,3 @@ +# Support for DNS wildcared entries by samba_tool dns sub command +# Will fail until implemented. +^samba.tests.samba_tool.dnscmd.samba.tests.samba_tool.dnscmd.DnsCmdTestCase.test_dns_wildcards\(ad_dc:local\) -- 2.11.0 From 5380e9064b19b2bf02bb5ab7f2c6ece8c9032d65 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 20 Jul 2017 09:13:43 +1200 Subject: [PATCH 5/5] samba-tool dns query: Allow '*' in names As DNS wild cards are now supported we need to allow '*' characters in the domain names. Signed-off-by: Gary Lockyer --- python/samba/netcmd/dns.py | 3 ++- selftest/knownfail.d/sambatooldns | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 selftest/knownfail.d/sambatooldns diff --git a/python/samba/netcmd/dns.py b/python/samba/netcmd/dns.py index 6f88817d701..fd8db937a52 100644 --- a/python/samba/netcmd/dns.py +++ b/python/samba/netcmd/dns.py @@ -819,7 +819,8 @@ class cmd_query(Command): record_type = dns_type_flag(rtype) if name.find('*') != -1: - raise CommandError('Wildcard searches not supported. To dump entire zone use "@"') + self.outf.write('use "@" to dump entire domain, looking up %s\n' % + name) select_flags = 0 if authority: diff --git a/selftest/knownfail.d/sambatooldns b/selftest/knownfail.d/sambatooldns deleted file mode 100644 index b60e9b21e73..00000000000 --- a/selftest/knownfail.d/sambatooldns +++ /dev/null @@ -1,3 +0,0 @@ -# Support for DNS wildcared entries by samba_tool dns sub command -# Will fail until implemented. -^samba.tests.samba_tool.dnscmd.samba.tests.samba_tool.dnscmd.DnsCmdTestCase.test_dns_wildcards\(ad_dc:local\) -- 2.11.0