/* Unix SMB/CIFS implementation. DNS tombstoning routines Copyright (C) Andrew Bartlett 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 . */ #include "includes.h" #include #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 ":=%"PRIu64")))", 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, "", 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; }