dns: custom match rule for DNS records to be tombstoned
authorAaron Haslett <aaronhaslett@catalyst.net.nz>
Mon, 2 Jul 2018 01:48:06 +0000 (13:48 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 12 Jul 2018 02:31:54 +0000 (04:31 +0200)
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 <aaronhaslett@catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
lib/ldb-samba/ldb_matching_rules.c
lib/ldb-samba/ldb_matching_rules.h
python/samba/tests/dns.py
selftest/knownfail.d/dns
selftest/knownfail.d/dns-scavenging
source4/setup/schema_samba4.ldif

index 5999943dcd2d9588d7e9f5a0df7a5abed8e2ccad..2aaaeb7450b15f67f3c11b92199529c909bf1bd0 100644 (file)
@@ -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;
 }
index 421e1ceaec88c0c5599ed8431b16faffcf2c99ef..28c4e3de0c0e409f066e1b16087a5345e545bbf7 100644 (file)
@@ -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_ */
index 800ce576dec9f2fb6195bfbcbca535e3a7b112ab..52505560e1c9cc68278b3bf0059c9bd9b73c8aaf 100644 (file)
@@ -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
index 4beeabf4386c6476f4e35c8fa6b19828bbb785cb..5a3e456b9c88fb82d3a92d53e2a3f6d164ea2a63 100644 (file)
@@ -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\)
index fe71f17c03cf27559dc6b432e859ba9adff1c130..8de310186d1fe2fe24717e09b3ab263afab2fe4d 100644 (file)
@@ -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\)
index 5b26dc0bee5ecd8428970830f31f33f66481eaa5..648f04944b882f2784ef38bbd5f0fb93c4325e38 100644 (file)
 # 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