self.assertEqual(recs[0].dwTimeStamp, 0)
def test_dns_tombstone_custom_match_rule(self):
+ lp = self.get_loadparm()
+ self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+ session_info=system_session(),
+ credentials=self.creds)
+
name,txt = 'agingtest', ['test txt']
name2,txt2 = 'agingtest2', ['test txt2']
name3,txt3 = 'agingtest3', ['test txt3']
self.assertEquals(len(res), 0)
def test_basic_scavenging(self):
+ lp = self.get_loadparm()
+ self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+ session_info=system_session(),
+ credentials=self.creds)
+
self.create_zone(self.zone, aging_enabled=True)
interval = 1
self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
zone=self.zone, Aging=1,
AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
name, txt = 'agingtest', ['test txt']
- rec = self.dns_update_record(name,txt)
- rec = self.dns_update_record(name+'2',txt)
+ name2, txt2 = 'agingtest2', ['test txt2']
+ name3, txt3 = 'agingtest3', ['test txt3']
+ 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_add = self.dns_update_record(name3,txt3)
+
def mod_ts(rec):
self.assertTrue(rec.dwTimeStamp > 0)
- rec.dwTimeStamp -= interval*5
+ if rec.data.str == txt:
+ rec.dwTimeStamp -= interval*5
self.ldap_modify_dnsrecs(name, mod_ts)
+ self.ldap_modify_dnsrecs(name2, mod_ts)
+ self.ldap_modify_dnsrecs(name3, mod_ts)
self.assertTrue(callable(getattr(dsdb, '_scavenge_dns_records', None)))
dsdb._scavenge_dns_records(self.samdb)
recs = self.ldap_get_dns_records(name)
self.assertEqual(len(recs), 1)
self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TOMBSTONE)
- recs = self.ldap_get_dns_records(name+'2')
+
+ recs = self.ldap_get_dns_records(name2)
self.assertEqual(len(recs), 1)
self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
+ self.assertEqual(recs[0].data.str, txt2)
+
+ recs = self.ldap_get_dns_records(name3)
+ self.assertEqual(len(recs), 2)
+ txts = {str(r.data.str) for r in recs}
+ self.assertEqual(txts, {str(txt2),str(txt3)})
+ self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
+ self.assertEqual(recs[1].wType, dnsp.DNS_TYPE_TXT)
+
+ for make_it_work in [False, True]:
+ inc = -1 if make_it_work else 1
+ def mod_ts(rec):
+ rec.data = (last_add.dwTimeStamp - 24*14) + inc
+ self.ldap_modify_dnsrecs(name, mod_ts)
+ dsdb._dns_delete_tombstones(self.samdb)
+ recs = self.ldap_get_records(name)
+ if make_it_work:
+ self.assertEqual(len(recs), 0)
+ else:
+ self.assertEqual(len(recs), 1)
def delete_zone(self, zone):
self.rpc_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
--- /dev/null
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS tombstoning routines
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "lib/util/dlinklist.h"
+#include "ldb.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
+#include "lib/ldb-samba/ldb_matching_rules.h"
+#include "lib/util/time.h"
+#include "dns_server/dnsserver_common.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include "param/param.h"
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+/*
+ * Copy only non-expired dns records from one message element to another.
+ */
+NTSTATUS copy_current_records(TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *old_el,
+ struct ldb_message_element *el,
+ NTTIME t)
+{
+ unsigned int i, num_kept = 0;
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ recs = talloc_zero_array(
+ tmp_ctx, struct dnsp_DnssrvRpcRecord, el->num_values);
+ if (recs == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ ndr_err = ndr_pull_struct_blob(
+ &(old_el->values[i]),
+ tmp_ctx,
+ &(recs[num_kept]),
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to pull dns rec blob.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (recs[num_kept].dwTimeStamp > t ||
+ recs[num_kept].dwTimeStamp == 0) {
+ num_kept++;
+ }
+ }
+
+ if (num_kept == el->num_values) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ el->values = talloc_zero_array(mem_ctx, struct ldb_val, num_kept);
+ if (el->values == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ el->num_values = num_kept;
+ for (i = 0; i < el->num_values; i++) {
+ ndr_err = ndr_push_struct_blob(
+ &(el->values[i]),
+ mem_ctx,
+ &(recs[i]),
+ (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to push dnsp_DnssrvRpcRecord\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Check all records in a zone and tombstone them if they're expired.
+ */
+NTSTATUS dns_tombstone_records_zone(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dns_server_zone *zone,
+ struct ldb_val *true_struct,
+ struct ldb_val *tombstone_blob,
+ NTTIME t,
+ char **error_string)
+{
+ WERROR werr;
+ NTSTATUS status;
+ unsigned int i;
+ struct dnsserver_zoneinfo *zi = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_message_element *el = NULL;
+ struct ldb_message_element *tombstone_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ int ret;
+ struct GUID guid;
+ struct GUID_txt_buf buf_guid;
+ const char *attrs[] = {"dnsRecord",
+ "dNSTombstoned",
+ "objectGUID",
+ NULL};
+
+ *error_string = NULL;
+
+ /* Get NoRefreshInterval and RefreshInterval from zone properties.*/
+ zi = talloc(mem_ctx, struct dnsserver_zoneinfo);
+ if (zi == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ werr = dns_get_zone_properties(samdb, mem_ctx, zone->dn, zi);
+ if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ } else if (!W_ERROR_IS_OK(werr)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Subtract them from current time to get the earliest possible.
+ * timestamp allowed for a non-expired DNS record. */
+ t -= zi->dwNoRefreshInterval + zi->dwRefreshInterval;
+
+ /* Custom match gets dns records in the zone with dwTimeStamp < t. */
+ ret = ldb_search(samdb,
+ mem_ctx,
+ &res,
+ zone->dn,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ "(&(objectClass=dnsNode)"
+ "(&(!(dnsTombstoned=TRUE))"
+ "(dnsRecord:" DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME
+ ":=%lu)))",
+ t);
+ if (ret != LDB_SUCCESS) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "Failed to search for dns "
+ "objects in zone %s: %s",
+ ldb_dn_get_linearized(zone->dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Do a constrained update on each expired DNS node. To do a constrained
+ * update we leave the dnsRecord element as is, and just change the flag
+ * to MOD_DELETE, then add a new element with the changes we want. LDB
+ * will run the deletion first, and bail out if a binary comparison
+ * between the attribute we pass and the one in the database shows a
+ * change. This prevents race conditions.
+ */
+ for (i = 0; i < res->count; i++) {
+ old_el = ldb_msg_find_element(res->msgs[i], "dnsRecord");
+ old_el->flags = LDB_FLAG_MOD_DELETE;
+
+ ret = ldb_msg_add_empty(
+ res->msgs[i], "dnsRecord", LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ el->num_values = old_el->num_values;
+ status = copy_current_records(mem_ctx, old_el, el, t);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* If nothing was expired, do nothing. */
+ if (el->num_values == old_el->num_values &&
+ el->num_values != 0) {
+ continue;
+ }
+
+ el->flags = LDB_FLAG_MOD_ADD;
+
+ /* If everything was expired, we tombstone the node. */
+ if (el->num_values == 0) {
+ el->values = tombstone_blob;
+ el->num_values = 1;
+
+ tombstone_el = ldb_msg_find_element(res->msgs[i],
+ "dnsTombstoned");
+ if (tombstone_el == NULL) {
+ ret = ldb_msg_add_value(res->msgs[i],
+ "dnsTombstoned",
+ true_struct,
+ &tombstone_el);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ tombstone_el->flags = LDB_FLAG_MOD_ADD;
+ } else {
+ tombstone_el->flags = LDB_FLAG_MOD_REPLACE;
+ tombstone_el->values = true_struct;
+ }
+ tombstone_el->num_values = 1;
+ } else {
+ /*
+ * Do not change the status of dnsTombstoned
+ * if we found any live records
+ */
+ ldb_msg_remove_attr(res->msgs[i],
+ "dnsTombstoned");
+ }
+
+ /* Set DN to the GUID in case the object was moved. */
+ el = ldb_msg_find_element(res->msgs[i], "objectGUID");
+ if (el == NULL) {
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "record has no objectGUID "
+ "in zone %s",
+ ldb_dn_get_linearized(zone->dn));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = GUID_from_ndr_blob(el->values, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ *error_string =
+ discard_const_p(char, "Error: Invalid GUID.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ GUID_buf_string(&guid, &buf_guid);
+ res->msgs[i]->dn =
+ ldb_dn_new_fmt(mem_ctx, samdb, "<GUID=%s>", buf_guid.buf);
+
+ /* Remove the GUID so we're not trying to modify it. */
+ ldb_msg_remove_attr(res->msgs[i], "objectGUID");
+
+ ret = ldb_modify(samdb, res->msgs[i]);
+ if (ret != LDB_SUCCESS) {
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "Failed to modify dns record "
+ "in zone %s: %s",
+ ldb_dn_get_linearized(zone->dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Tombstone all expired DNS records.
+ */
+NTSTATUS dns_tombstone_records(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string)
+{
+ struct dns_server_zone *zones = NULL;
+ struct dns_server_zone *z = NULL;
+ NTSTATUS ret;
+ struct dnsp_DnssrvRpcRecord tombstone_struct;
+ struct ldb_val tombstone_blob;
+ struct ldb_val true_struct;
+ NTTIME t;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint8_t true_str[4] = "TRUE";
+
+ unix_to_nt_time(&t, time(NULL));
+ t /= 10 * 1000 * 1000;
+ t /= 3600;
+
+ tombstone_struct = (struct dnsp_DnssrvRpcRecord){
+ .wType = DNS_TYPE_TOMBSTONE, .data = {.timestamp = t}};
+
+ true_struct = (struct ldb_val){.data = true_str, .length = 4};
+
+ ndr_err = ndr_push_struct_blob(
+ &tombstone_blob,
+ mem_ctx,
+ &tombstone_struct,
+ (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ *error_string = discard_const_p(char,
+ "Failed to push "
+ "dnsp_DnssrvRpcRecord\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ dns_common_zones(samdb, mem_ctx, NULL, &zones);
+ for (z = zones; z; z = z->next) {
+ tmp_ctx = talloc_new(NULL);
+ ret = dns_tombstone_records_zone(tmp_ctx,
+ samdb,
+ z,
+ &true_struct,
+ &tombstone_blob,
+ t,
+ error_string);
+ TALLOC_FREE(tmp_ctx);
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_PROPSET_NOT_FOUND)) {
+ continue;
+ } else if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Delete all DNS tombstones that have been around for longer than the server
+ * property 'DsTombstoneInterval' which we store in smb.conf
+ */
+NTSTATUS dns_delete_tombstones(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ char **error_string)
+{
+ struct dns_server_zone *zones = NULL;
+ struct dns_server_zone *z = NULL;
+ int ret, i;
+ NTTIME current_time;
+ enum ndr_err_code ndr_err;
+ struct ldb_result *res = NULL;
+ int tombstone_time;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_message_element *el = NULL;
+ struct dnsp_DnssrvRpcRecord *rec = NULL;
+ const char *attrs[] = {"dnsRecord", "dNSTombstoned", NULL};
+ rec = talloc_zero(mem_ctx, struct dnsp_DnssrvRpcRecord);
+
+ unix_to_nt_time(¤t_time, time(NULL));
+ current_time /= 10 * 1000 * 1000;
+ current_time /= 3600;
+
+ lp_ctx = (struct loadparm_context *)ldb_get_opaque(samdb, "loadparm");
+ tombstone_time =
+ current_time -
+ lpcfg_parm_int(
+ lp_ctx, NULL, "dnsserver", "dns_tombstone_interval", 24 * 14);
+
+ dns_common_zones(samdb, mem_ctx, NULL, &zones);
+ for (z = zones; z; z = z->next) {
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This can load a very large set, but on the
+ * assumption that the number of tombstones is
+ * relatively small compared with the number of active
+ * records, and that this is an indexed lookup, this
+ * should be OK. We can make a match rule if
+ * returning the set of tombstones becomes an issue.
+ */
+
+ ret = ldb_search(samdb,
+ tmp_ctx,
+ &res,
+ z->dn,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ "(&(objectClass=dnsNode)(dNSTombstoned=TRUE))");
+
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(tmp_ctx);
+ *error_string =
+ talloc_asprintf(mem_ctx,
+ "Failed to "
+ "search for tombstoned "
+ "dns objects in zone %s: %s",
+ ldb_dn_get_linearized(z->dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ el = ldb_msg_find_element(res->msgs[i], "dnsRecord");
+ ndr_err = ndr_pull_struct_blob(
+ el->values,
+ tmp_ctx,
+ rec,
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to pull dns rec blob.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (rec->wType != DNS_TYPE_TOMBSTONE) {
+ continue;
+ }
+
+ if (rec->data.timestamp > tombstone_time) {
+ continue;
+ }
+
+ ret = dsdb_delete(samdb, res->msgs[i]->dn, 0);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT &&
+ ret != LDB_SUCCESS) {
+ TALLOC_FREE(tmp_ctx);
+ DBG_ERR("Failed to delete dns node \n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ }
+ return NT_STATUS_OK;
+}
#include "param/pyparam.h"
#include "lib/util/dlinklist.h"
#include "dsdb/kcc/garbage_collect_tombstones.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
/* FIXME: These should be in a header file somewhere */
return PyInt_FromLong(rid);
}
+static PyObject *py_dns_delete_tombstones(PyObject *self, PyObject *args)
+{
+ PyObject *py_ldb;
+ NTSTATUS status;
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ char *error_string = NULL;
+
+ if (!PyArg_ParseTuple(args, "O", &py_ldb)) {
+ return NULL;
+ }
+ PyErr_LDB_OR_RAISE(py_ldb, ldb);
+
+ mem_ctx = talloc_new(ldb);
+ if (mem_ctx == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ status = dns_delete_tombstones(mem_ctx, ldb, &error_string);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (error_string) {
+ PyErr_Format(PyExc_RuntimeError, "%s", error_string);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ TALLOC_FREE(mem_ctx);
+ return NULL;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_scavenge_dns_records(PyObject *self, PyObject *args)
+{
+ PyObject *py_ldb;
+ NTSTATUS status;
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ char *error_string = NULL;
+
+ if (!PyArg_ParseTuple(args, "O", &py_ldb)) {
+ return NULL;
+ }
+ PyErr_LDB_OR_RAISE(py_ldb, ldb);
+
+ mem_ctx = talloc_new(ldb);
+ if (mem_ctx == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ status = dns_tombstone_records(mem_ctx, ldb, &error_string);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (error_string) {
+ PyErr_Format(PyExc_RuntimeError, "%s", error_string);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ TALLOC_FREE(mem_ctx);
+ return NULL;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ Py_RETURN_NONE;
+}
+
static PyObject *py_dsdb_garbage_collect_tombstones(PyObject *self, PyObject *args)
{
PyObject *py_ldb, *py_list_dn;
{ "_dsdb_garbage_collect_tombstones", (PyCFunction)py_dsdb_garbage_collect_tombstones, METH_VARARGS,
"_dsdb_kcc_check_deleted(samdb, [dn], current_time, tombstone_lifetime)"
" -> (num_objects_expunged, num_links_expunged)" },
+ { "_scavenge_dns_records", (PyCFunction)py_scavenge_dns_records,
+ METH_VARARGS, NULL},
+ { "_dns_delete_tombstones", (PyCFunction)py_dns_delete_tombstones,
+ METH_VARARGS, NULL},
{ "_dsdb_create_own_rid_set", (PyCFunction)py_dsdb_create_own_rid_set, METH_VARARGS,
"_dsdb_create_own_rid_set(samdb)"
" -> None" },