summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/kcc/scavenge_dns_records.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/kcc/scavenge_dns_records.c')
-rw-r--r--source4/dsdb/kcc/scavenge_dns_records.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/source4/dsdb/kcc/scavenge_dns_records.c b/source4/dsdb/kcc/scavenge_dns_records.c
new file mode 100644
index 0000000..f41250c
--- /dev/null
+++ b/source4/dsdb/kcc/scavenge_dns_records.c
@@ -0,0 +1,531 @@
+/*
+ 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.
+ */
+static NTSTATUS copy_current_records(TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *old_el,
+ struct ldb_message_element *el,
+ uint32_t dns_timestamp)
+{
+ unsigned int i;
+ struct dnsp_DnssrvRpcRecord rec;
+ enum ndr_err_code ndr_err;
+
+ el->values = talloc_zero_array(mem_ctx, struct ldb_val,
+ old_el->num_values);
+ if (el->values == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < old_el->num_values; i++) {
+ ndr_err = ndr_pull_struct_blob(
+ &(old_el->values[i]),
+ mem_ctx,
+ &rec,
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("Failed to pull dns rec blob.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (rec.dwTimeStamp > dns_timestamp ||
+ rec.dwTimeStamp == 0) {
+ el->values[el->num_values] = old_el->values[i];
+ el->num_values++;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Check all records in a zone and tombstone them if they're expired.
+ */
+static NTSTATUS dns_tombstone_records_zone(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct dns_server_zone *zone,
+ uint32_t dns_timestamp,
+ NTTIME entombed_time,
+ 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;
+ struct ldb_message *new_msg = NULL;
+ enum ndr_err_code ndr_err;
+ int ret;
+ struct GUID guid;
+ struct GUID_txt_buf buf_guid;
+ const char *attrs[] = {"dnsRecord",
+ "dNSTombstoned",
+ "objectGUID",
+ NULL};
+
+ struct ldb_val true_val = {
+ .data = discard_const_p(uint8_t, "TRUE"),
+ .length = 4
+ };
+
+ struct ldb_val tombstone_blob;
+ struct dnsp_DnssrvRpcRecord tombstone_struct = {
+ .wType = DNS_TYPE_TOMBSTONE,
+ .data = {.EntombedTime = entombed_time}
+ };
+
+ 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 TOMBSTONE"
+ "dnsp_DnssrvRpcRecord\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *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. */
+ dns_timestamp -= 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
+ ":=%"PRIu32")))",
+ dns_timestamp);
+ 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++) {
+ new_msg = ldb_msg_copy(mem_ctx, res->msgs[i]);
+ if (new_msg == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ old_el = ldb_msg_find_element(new_msg, "dnsRecord");
+ if (old_el == NULL) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ old_el->flags = LDB_FLAG_MOD_DELETE;
+
+ ret = ldb_msg_add_empty(
+ new_msg, "dnsRecord", LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = copy_current_records(new_msg, old_el, el, dns_timestamp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* If nothing was expired, do nothing. */
+ if (el->num_values == old_el->num_values &&
+ el->num_values != 0) {
+ TALLOC_FREE(new_msg);
+ continue;
+ }
+
+ /*
+ * If everything was expired, we tombstone the node, which
+ * involves adding a tombstone dnsRecord and a 'dnsTombstoned:
+ * TRUE' attribute. That is, we want to end up with this:
+ *
+ * objectClass: dnsNode
+ * dnsRecord: { .wType = DNSTYPE_TOMBSTONE,
+ * .data.EntombedTime = <now> }
+ * dnsTombstoned: TRUE
+ *
+ * and no other dnsRecords.
+ */
+ if (el->num_values == 0) {
+ struct ldb_val *vals = talloc_realloc(new_msg->elements,
+ el->values,
+ struct ldb_val,
+ 1);
+ if (!vals) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ el->values = vals;
+ el->values[0] = tombstone_blob;
+ el->num_values = 1;
+
+ tombstone_el = ldb_msg_find_element(new_msg,
+ "dnsTombstoned");
+
+ if (tombstone_el == NULL) {
+ ret = ldb_msg_add_value(new_msg,
+ "dnsTombstoned",
+ &true_val,
+ &tombstone_el);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ tombstone_el->flags = LDB_FLAG_MOD_ADD;
+ } else {
+ if (tombstone_el->num_values != 1) {
+ vals = talloc_realloc(
+ new_msg->elements,
+ tombstone_el->values,
+ struct ldb_val,
+ 1);
+ if (!vals) {
+ TALLOC_FREE(new_msg);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ tombstone_el->values = vals;
+ tombstone_el->num_values = 1;
+ }
+ tombstone_el->flags = LDB_FLAG_MOD_REPLACE;
+ tombstone_el->values[0] = true_val;
+ }
+ } else {
+ /*
+ * Do not change the status of dnsTombstoned if we
+ * found any live records. If it exists, its value
+ * will be the harmless "FALSE", which is what we end
+ * up with when a tombstoned record is untombstoned.
+ * (in dns_common_replace).
+ */
+ ldb_msg_remove_attr(new_msg,
+ "dnsTombstoned");
+ }
+
+ /* Set DN to the GUID in case the object was moved. */
+ el = ldb_msg_find_element(new_msg, "objectGUID");
+ if (el == NULL) {
+ TALLOC_FREE(new_msg);
+ *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)) {
+ TALLOC_FREE(new_msg);
+ *error_string =
+ discard_const_p(char, "Error: Invalid GUID.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ GUID_buf_string(&guid, &buf_guid);
+ new_msg->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(new_msg, "objectGUID");
+
+ ret = ldb_modify(samdb, new_msg);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(new_msg);
+ *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;
+ }
+ TALLOC_FREE(new_msg);
+ }
+
+ 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;
+ uint32_t dns_timestamp;
+ NTTIME entombed_time;
+ TALLOC_CTX *tmp_ctx = NULL;
+ time_t unix_now = time(NULL);
+
+ unix_to_nt_time(&entombed_time, unix_now);
+ dns_timestamp = unix_to_dns_timestamp(unix_now);
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
+ if (!NT_STATUS_IS_OK(ret)) {
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+ }
+
+ for (z = zones; z; z = z->next) {
+ ret = dns_tombstone_records_zone(tmp_ctx,
+ samdb,
+ z,
+ dns_timestamp,
+ entombed_time,
+ error_string);
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_PROPSET_NOT_FOUND)) {
+ continue;
+ } else if (!NT_STATUS_IS_OK(ret)) {
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+ }
+ }
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Delete all DNS tombstones that have been around for longer than the server
+ * property 'dns_tombstone_interval' which we store in smb.conf, which
+ * corresponds to DsTombstoneInterval in [MS-DNSP] 3.1.1.1.1 "DNS Server
+ * Integer Properties".
+ */
+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;
+ NTSTATUS status;
+ uint32_t current_time;
+ uint32_t tombstone_interval;
+ uint32_t tombstone_hours;
+ NTTIME tombstone_nttime;
+ enum ndr_err_code ndr_err;
+ struct ldb_result *res = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_message_element *el = NULL;
+ struct dnsp_DnssrvRpcRecord rec = {0};
+ const char *attrs[] = {"dnsRecord", "dNSTombstoned", NULL};
+
+ current_time = unix_to_dns_timestamp(time(NULL));
+
+ lp_ctx = (struct loadparm_context *)ldb_get_opaque(samdb, "loadparm");
+ tombstone_interval = lpcfg_parm_ulong(lp_ctx, NULL,
+ "dnsserver",
+ "dns_tombstone_interval",
+ 24 * 14);
+
+ tombstone_hours = current_time - tombstone_interval;
+ status = dns_timestamp_to_nt_time(&tombstone_nttime,
+ tombstone_hours);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("DNS timestamp exceeds NTTIME epoch.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ for (z = zones; z; z = z->next) {
+ /*
+ * 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) {
+ *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));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ el = ldb_msg_find_element(msg, "dnsRecord");
+ if (el == NULL) {
+ DBG_ERR("The tombstoned dns node %s has no dns "
+ "records, which should not happen.\n",
+ ldb_dn_get_linearized(msg->dn)
+ );
+ continue;
+ }
+ /*
+ * Below we assume the element has one value, which we
+ * expect because when we tombstone a node we remove
+ * all the records except for the tombstone.
+ */
+ if (el->num_values != 1) {
+ DBG_ERR("The tombstoned dns node %s has %u "
+ "dns records, expected one.\n",
+ ldb_dn_get_linearized(msg->dn),
+ el->num_values
+ );
+ continue;
+ }
+
+ 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) {
+ DBG_ERR("A tombstoned dnsNode has non-tombstoned"
+ " records, which should not happen.\n");
+ continue;
+ }
+
+ if (rec.data.EntombedTime > tombstone_nttime) {
+ continue;
+ }
+ /*
+ * Between 4.9 and 4.14 in some places we saved the
+ * tombstone time as hours since the start of 1601,
+ * not in NTTIME ten-millionths of a second units.
+ *
+ * We can accommodate these bad values by noting that
+ * all the realistic timestamps in that measurement
+ * fall within the first *second* of NTTIME, that is,
+ * before 1601-01-01 00:00:01; and that these
+ * timestamps are not realistic for NTTIME timestamps.
+ *
+ * Calculation: there are roughly 365.25 * 24 = 8766
+ * hours per year, and < 500 years since 1601, so
+ * 4383000 would be a fine threshold. We round up to
+ * the crore-second (c. 2741CE) in honour of NTTIME.
+ */
+ if ((rec.data.EntombedTime < 10000000) &&
+ (rec.data.EntombedTime > tombstone_hours)) {
+ continue;
+ }
+
+ ret = dsdb_delete(samdb, msg->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;
+}