From: Aaron Haslett Date: Mon, 2 Jul 2018 01:48:06 +0000 (+1200) Subject: dns: custom match rule for DNS records to be tombstoned X-Git-Tag: tdb-1.3.16~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00002b8df9b865b896d264ee22bf6f22cf935f56;p=thirdparty%2Fsamba.git dns: custom match rule for DNS records to be tombstoned A custom match rule for records to be tombstoned by the scavenging process. Needed because DNS records are a multi-valued attribute on name records, so without a custom match rule we'd have entire zones into memory to search for expired records. BUG: https://bugzilla.samba.org/show_bug.cgi?id=10812 Signed-off-by: Aaron Haslett Reviewed-by: Gary Lockyer Signed-off-by: Andrew Bartlett --- diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c index 5999943dcd2..2aaaeb7450b 100644 --- a/lib/ldb-samba/ldb_matching_rules.c +++ b/lib/ldb-samba/ldb_matching_rules.c @@ -26,6 +26,7 @@ #include "ldb_matching_rules.h" #include "libcli/security/security.h" #include "dsdb/common/util.h" +#include "librpc/gen_ndr/ndr_dnsp.h" static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, @@ -327,6 +328,125 @@ static int ldb_comparator_trans(struct ldb_context *ldb, } +/* + * This rule provides match of a dns object with expired records. + * + * This allows a search filter such as: + * + * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=131139216000000000 + * + * This allows the caller to find records that should become a DNS + * tomestone, despite that information being deep within an NDR packed + * object + */ +static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb, + const char *oid, + const struct ldb_message *msg, + const char *attribute_to_match, + const struct ldb_val *value_to_match, + bool *matched) +{ + TALLOC_CTX *tmp_ctx; + unsigned int i; + struct ldb_message_element *el = NULL; + struct auth_session_info *session_info = NULL; + uint64_t tombstone_time; + struct dnsp_DnssrvRpcRecord *rec = NULL; + enum ndr_err_code err; + *matched = false; + + /* Needs to be dnsRecord, no match otherwise */ + if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) { + return LDB_SUCCESS; + } + + el = ldb_msg_find_element(msg, attribute_to_match); + if (el == NULL) { + return LDB_SUCCESS; + } + + session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"), + struct auth_session_info); + if (session_info == NULL) { + return ldb_oom(ldb); + } + if (security_session_user_level(session_info, NULL) + != SECURITY_SYSTEM) { + + DBG_ERR("unauthorised access\n"); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + /* Just check we don't allow the caller to fill our stack */ + if (value_to_match->length >= 64) { + DBG_ERR("Invalid timestamp passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } else { + char *p = NULL; + char s[value_to_match->length+1]; + memcpy(s, value_to_match->data, value_to_match->length); + s[value_to_match->length] = 0; + if (s[0] == '\0' || s[0] == '-') { + DBG_ERR("Empty timestamp passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + tombstone_time = strtoull(s, &p, 10); + if (p == NULL || p == s || *p != '\0' || + tombstone_time == ULLONG_MAX) { + DBG_ERR("Invalid timestamp string passed\n"); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + for (i = 0; i < el->num_values; i++) { + rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord); + if (rec == NULL) { + TALLOC_FREE(tmp_ctx); + return ldb_oom(ldb); + } + err = ndr_pull_struct_blob( + &(el->values[i]), + tmp_ctx, + rec, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(err)){ + DBG_ERR("Failed to pull dns rec blob.\n"); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) { + TALLOC_FREE(tmp_ctx); + continue; + } + + if (rec->wType == DNS_TYPE_TOMBSTONE) { + TALLOC_FREE(tmp_ctx); + continue; + } + if (rec->dwTimeStamp == 0) { + TALLOC_FREE(tmp_ctx); + continue; + } + if (rec->dwTimeStamp > tombstone_time) { + TALLOC_FREE(tmp_ctx); + continue; + } + + *matched = true; + break; + } + + TALLOC_FREE(tmp_ctx); + return LDB_SUCCESS; +} + + /* * This rule provides match of a link attribute against a 'should be expunged' criteria * @@ -448,7 +568,8 @@ static int dsdb_match_for_expunge(struct ldb_context *ldb, int ldb_register_samba_matching_rules(struct ldb_context *ldb) { struct ldb_extended_match_rule *transitive_eval = NULL, - *match_for_expunge = NULL; + *match_for_expunge = NULL, + *match_for_dns_to_tombstone_time = NULL; int ret; transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule); @@ -469,5 +590,18 @@ int ldb_register_samba_matching_rules(struct ldb_context *ldb) return ret; } + match_for_dns_to_tombstone_time = talloc_zero( + ldb, + struct ldb_extended_match_rule); + match_for_dns_to_tombstone_time->oid = DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME; + match_for_dns_to_tombstone_time->callback + = dsdb_match_for_dns_to_tombstone_time; + ret = ldb_register_extended_match_rule(ldb, + match_for_dns_to_tombstone_time); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(match_for_dns_to_tombstone_time); + return ret; + } + return LDB_SUCCESS; } diff --git a/lib/ldb-samba/ldb_matching_rules.h b/lib/ldb-samba/ldb_matching_rules.h index 421e1ceaec8..28c4e3de0c0 100644 --- a/lib/ldb-samba/ldb_matching_rules.h +++ b/lib/ldb-samba/ldb_matching_rules.h @@ -25,5 +25,6 @@ /* This rule provides recursive search of a link attribute */ #define SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL "1.2.840.113556.1.4.1941" #define DSDB_MATCH_FOR_EXPUNGE "1.3.6.1.4.1.7165.4.5.2" +#define DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME "1.3.6.1.4.1.7165.4.5.3" #endif /* _LDB_MATCHING_RULES_H_ */ diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py index 800ce576dec..52505560e1c 100644 --- a/python/samba/tests/dns.py +++ b/python/samba/tests/dns.py @@ -1091,6 +1091,52 @@ class TestZones(DNSTest): self.assertEqual(len(recs), 1) self.assertEqual(recs[0].dwTimeStamp, 0) + def test_dns_tombstone_custom_match_rule(self): + name,txt = 'agingtest', ['test txt'] + name2,txt2 = 'agingtest2', ['test txt2'] + name3,txt3 = 'agingtest3', ['test txt3'] + self.create_zone(self.zone, aging_enabled=True) + interval = 10 + self.set_params(NoRefreshInterval=interval, RefreshInterval=interval, + Aging=1, zone=self.zone, + AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE) + + self.dns_update_record(name, txt), + + self.dns_update_record(name2, txt), + self.dns_update_record(name2, txt2), + + self.dns_update_record(name3, txt), + self.dns_update_record(name3, txt2), + last_update = self.dns_update_record(name3, txt3) + + # Modify txt1 of the first 2 names + def mod_ts(rec): + if rec.data.str == txt: + rec.dwTimeStamp -= 2 + self.ldap_modify_dnsrecs(name, mod_ts) + self.ldap_modify_dnsrecs(name2, mod_ts) + + recs = self.ldap_get_dns_records(name3) + expr = "(dnsRecord:1.3.6.1.4.1.7165.4.5.3:={})" + expr = expr.format(int(last_update.dwTimeStamp)-1) + try: + res = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE, + expression=expr, attrs=["*"]) + except ldb.LdbError as e: + self.fail(str(e)) + updated_names = {str(r.get('name')) for r in res} + self.assertEqual(updated_names, set([name, name2])) + + def test_dns_tombstone_custom_match_rule_fail(self): + self.create_zone(self.zone, aging_enabled=True) + + # The check here is that this does not blow up on silly input + expr = "(dnsProperty:1.3.6.1.4.1.7165.4.5.3:=1)" + res = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE, + expression=expr, attrs=["*"]) + self.assertEquals(len(res), 0) + def test_basic_scavenging(self): self.create_zone(self.zone, aging_enabled=True) interval = 1 diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns index 4beeabf4386..5a3e456b9c8 100644 --- a/selftest/knownfail.d/dns +++ b/selftest/knownfail.d/dns @@ -44,12 +44,15 @@ samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(rodc:local\) samba.tests.dns.__main__.TestZones.test_aging_refresh\(rodc:local\) samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(rodc:local\) samba.tests.dns.__main__.TestZones.test_basic_scavenging\(rodc:local\) +samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(rodc:local\) +samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule_fail\(rodc:local\) samba.tests.dns.__main__.TestZones.test_set_aging\(vampire_dc:local\) samba.tests.dns.__main__.TestZones.test_aging_update\(vampire_dc:local\) samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(vampire_dc:local\) samba.tests.dns.__main__.TestZones.test_aging_refresh\(vampire_dc:local\) samba.tests.dns.__main__.TestZones.test_basic_scavenging\(vampire_dc:local\) +samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(vampire_dc:local\) samba.tests.dns.__main__.TestComplexQueries.test_cname_two_chain\(vampire_dc:local\) samba.tests.dns.__main__.TestComplexQueries.test_one_a_query\(vampire_dc:local\) diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging index fe71f17c03c..8de310186d1 100644 --- a/selftest/knownfail.d/dns-scavenging +++ b/selftest/knownfail.d/dns-scavenging @@ -4,3 +4,4 @@ # Will be removed once the tests are implemented. # samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\) +samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(fl2003dc:local\) diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif index 5b26dc0bee5..648f04944b8 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -246,6 +246,7 @@ # ldap extended matches #Allocated: SAMBA_LDAP_MATCH_ALWAYS_FALSE 1.3.6.1.4.1.7165.4.5.1 #Allocated: DSDB_MATCH_FOR_EXPUNGE 1.3.6.1.4.1.7165.4.5.2 +#Allocated: DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME 1.3.6.1.4.1.7165.4.5.3 #Allocated: (middleName) attributeID: 1.3.6.1.4.1.7165.4.255.1