gensec: Refactor gensec_security_mechs()
[samba4-gss.git] / source4 / dsdb / kcc / scavenge_dns_records.c
blobf41250cbd1b96ba41e0a173eb7bf2d7cca49cce0
1 /*
2 Unix SMB/CIFS implementation.
4 DNS tombstoning routines
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "includes.h"
24 #include <ldb_errors.h>
25 #include "../lib/util/dlinklist.h"
26 #include "librpc/gen_ndr/ndr_misc.h"
27 #include "librpc/gen_ndr/ndr_drsuapi.h"
28 #include "librpc/gen_ndr/ndr_drsblobs.h"
29 #include "param/param.h"
30 #include "lib/util/dlinklist.h"
31 #include "ldb.h"
32 #include "dsdb/kcc/scavenge_dns_records.h"
33 #include "lib/ldb-samba/ldb_matching_rules.h"
34 #include "lib/util/time.h"
35 #include "dns_server/dnsserver_common.h"
36 #include "librpc/gen_ndr/ndr_dnsp.h"
37 #include "param/param.h"
39 #include "librpc/gen_ndr/ndr_misc.h"
40 #include "librpc/gen_ndr/ndr_drsuapi.h"
41 #include "librpc/gen_ndr/ndr_drsblobs.h"
44 * Copy only non-expired dns records from one message element to another.
46 static NTSTATUS copy_current_records(TALLOC_CTX *mem_ctx,
47 struct ldb_message_element *old_el,
48 struct ldb_message_element *el,
49 uint32_t dns_timestamp)
51 unsigned int i;
52 struct dnsp_DnssrvRpcRecord rec;
53 enum ndr_err_code ndr_err;
55 el->values = talloc_zero_array(mem_ctx, struct ldb_val,
56 old_el->num_values);
57 if (el->values == NULL) {
58 return NT_STATUS_NO_MEMORY;
61 for (i = 0; i < old_el->num_values; i++) {
62 ndr_err = ndr_pull_struct_blob(
63 &(old_el->values[i]),
64 mem_ctx,
65 &rec,
66 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
67 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
68 DBG_ERR("Failed to pull dns rec blob.\n");
69 return NT_STATUS_INTERNAL_ERROR;
71 if (rec.dwTimeStamp > dns_timestamp ||
72 rec.dwTimeStamp == 0) {
73 el->values[el->num_values] = old_el->values[i];
74 el->num_values++;
78 return NT_STATUS_OK;
82 * Check all records in a zone and tombstone them if they're expired.
84 static NTSTATUS dns_tombstone_records_zone(TALLOC_CTX *mem_ctx,
85 struct ldb_context *samdb,
86 struct dns_server_zone *zone,
87 uint32_t dns_timestamp,
88 NTTIME entombed_time,
89 char **error_string)
91 WERROR werr;
92 NTSTATUS status;
93 unsigned int i;
94 struct dnsserver_zoneinfo *zi = NULL;
95 struct ldb_result *res = NULL;
96 struct ldb_message_element *el = NULL;
97 struct ldb_message_element *tombstone_el = NULL;
98 struct ldb_message_element *old_el = NULL;
99 struct ldb_message *new_msg = NULL;
100 enum ndr_err_code ndr_err;
101 int ret;
102 struct GUID guid;
103 struct GUID_txt_buf buf_guid;
104 const char *attrs[] = {"dnsRecord",
105 "dNSTombstoned",
106 "objectGUID",
107 NULL};
109 struct ldb_val true_val = {
110 .data = discard_const_p(uint8_t, "TRUE"),
111 .length = 4
114 struct ldb_val tombstone_blob;
115 struct dnsp_DnssrvRpcRecord tombstone_struct = {
116 .wType = DNS_TYPE_TOMBSTONE,
117 .data = {.EntombedTime = entombed_time}
120 ndr_err = ndr_push_struct_blob(
121 &tombstone_blob,
122 mem_ctx,
123 &tombstone_struct,
124 (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
125 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
126 *error_string = discard_const_p(char,
127 "Failed to push TOMBSTONE"
128 "dnsp_DnssrvRpcRecord\n");
129 return NT_STATUS_INTERNAL_ERROR;
132 *error_string = NULL;
134 /* Get NoRefreshInterval and RefreshInterval from zone properties.*/
135 zi = talloc(mem_ctx, struct dnsserver_zoneinfo);
136 if (zi == NULL) {
137 return NT_STATUS_NO_MEMORY;
139 werr = dns_get_zone_properties(samdb, mem_ctx, zone->dn, zi);
140 if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
141 return NT_STATUS_PROPSET_NOT_FOUND;
142 } else if (!W_ERROR_IS_OK(werr)) {
143 return NT_STATUS_INTERNAL_ERROR;
146 /* Subtract them from current time to get the earliest possible.
147 * timestamp allowed for a non-expired DNS record. */
148 dns_timestamp -= zi->dwNoRefreshInterval + zi->dwRefreshInterval;
150 /* Custom match gets dns records in the zone with dwTimeStamp < t. */
151 ret = ldb_search(samdb,
152 mem_ctx,
153 &res,
154 zone->dn,
155 LDB_SCOPE_SUBTREE,
156 attrs,
157 "(&(objectClass=dnsNode)"
158 "(&(!(dnsTombstoned=TRUE))"
159 "(dnsRecord:" DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME
160 ":=%"PRIu32")))",
161 dns_timestamp);
162 if (ret != LDB_SUCCESS) {
163 *error_string = talloc_asprintf(mem_ctx,
164 "Failed to search for dns "
165 "objects in zone %s: %s",
166 ldb_dn_get_linearized(zone->dn),
167 ldb_errstring(samdb));
168 return NT_STATUS_INTERNAL_ERROR;
172 * Do a constrained update on each expired DNS node. To do a constrained
173 * update we leave the dnsRecord element as is, and just change the flag
174 * to MOD_DELETE, then add a new element with the changes we want. LDB
175 * will run the deletion first, and bail out if a binary comparison
176 * between the attribute we pass and the one in the database shows a
177 * change. This prevents race conditions.
179 for (i = 0; i < res->count; i++) {
180 new_msg = ldb_msg_copy(mem_ctx, res->msgs[i]);
181 if (new_msg == NULL) {
182 return NT_STATUS_INTERNAL_ERROR;
185 old_el = ldb_msg_find_element(new_msg, "dnsRecord");
186 if (old_el == NULL) {
187 TALLOC_FREE(new_msg);
188 return NT_STATUS_INTERNAL_ERROR;
190 old_el->flags = LDB_FLAG_MOD_DELETE;
192 ret = ldb_msg_add_empty(
193 new_msg, "dnsRecord", LDB_FLAG_MOD_ADD, &el);
194 if (ret != LDB_SUCCESS) {
195 TALLOC_FREE(new_msg);
196 return NT_STATUS_INTERNAL_ERROR;
199 status = copy_current_records(new_msg, old_el, el, dns_timestamp);
201 if (!NT_STATUS_IS_OK(status)) {
202 TALLOC_FREE(new_msg);
203 return NT_STATUS_INTERNAL_ERROR;
206 /* If nothing was expired, do nothing. */
207 if (el->num_values == old_el->num_values &&
208 el->num_values != 0) {
209 TALLOC_FREE(new_msg);
210 continue;
214 * If everything was expired, we tombstone the node, which
215 * involves adding a tombstone dnsRecord and a 'dnsTombstoned:
216 * TRUE' attribute. That is, we want to end up with this:
218 * objectClass: dnsNode
219 * dnsRecord: { .wType = DNSTYPE_TOMBSTONE,
220 * .data.EntombedTime = <now> }
221 * dnsTombstoned: TRUE
223 * and no other dnsRecords.
225 if (el->num_values == 0) {
226 struct ldb_val *vals = talloc_realloc(new_msg->elements,
227 el->values,
228 struct ldb_val,
230 if (!vals) {
231 TALLOC_FREE(new_msg);
232 return NT_STATUS_INTERNAL_ERROR;
234 el->values = vals;
235 el->values[0] = tombstone_blob;
236 el->num_values = 1;
238 tombstone_el = ldb_msg_find_element(new_msg,
239 "dnsTombstoned");
241 if (tombstone_el == NULL) {
242 ret = ldb_msg_add_value(new_msg,
243 "dnsTombstoned",
244 &true_val,
245 &tombstone_el);
246 if (ret != LDB_SUCCESS) {
247 TALLOC_FREE(new_msg);
248 return NT_STATUS_INTERNAL_ERROR;
250 tombstone_el->flags = LDB_FLAG_MOD_ADD;
251 } else {
252 if (tombstone_el->num_values != 1) {
253 vals = talloc_realloc(
254 new_msg->elements,
255 tombstone_el->values,
256 struct ldb_val,
258 if (!vals) {
259 TALLOC_FREE(new_msg);
260 return NT_STATUS_INTERNAL_ERROR;
262 tombstone_el->values = vals;
263 tombstone_el->num_values = 1;
265 tombstone_el->flags = LDB_FLAG_MOD_REPLACE;
266 tombstone_el->values[0] = true_val;
268 } else {
270 * Do not change the status of dnsTombstoned if we
271 * found any live records. If it exists, its value
272 * will be the harmless "FALSE", which is what we end
273 * up with when a tombstoned record is untombstoned.
274 * (in dns_common_replace).
276 ldb_msg_remove_attr(new_msg,
277 "dnsTombstoned");
280 /* Set DN to the GUID in case the object was moved. */
281 el = ldb_msg_find_element(new_msg, "objectGUID");
282 if (el == NULL) {
283 TALLOC_FREE(new_msg);
284 *error_string =
285 talloc_asprintf(mem_ctx,
286 "record has no objectGUID "
287 "in zone %s",
288 ldb_dn_get_linearized(zone->dn));
289 return NT_STATUS_INTERNAL_ERROR;
292 status = GUID_from_ndr_blob(el->values, &guid);
293 if (!NT_STATUS_IS_OK(status)) {
294 TALLOC_FREE(new_msg);
295 *error_string =
296 discard_const_p(char, "Error: Invalid GUID.\n");
297 return NT_STATUS_INTERNAL_ERROR;
300 GUID_buf_string(&guid, &buf_guid);
301 new_msg->dn =
302 ldb_dn_new_fmt(mem_ctx, samdb, "<GUID=%s>", buf_guid.buf);
304 /* Remove the GUID so we're not trying to modify it. */
305 ldb_msg_remove_attr(new_msg, "objectGUID");
307 ret = ldb_modify(samdb, new_msg);
308 if (ret != LDB_SUCCESS) {
309 TALLOC_FREE(new_msg);
310 *error_string =
311 talloc_asprintf(mem_ctx,
312 "Failed to modify dns record "
313 "in zone %s: %s",
314 ldb_dn_get_linearized(zone->dn),
315 ldb_errstring(samdb));
316 return NT_STATUS_INTERNAL_ERROR;
318 TALLOC_FREE(new_msg);
321 return NT_STATUS_OK;
325 * Tombstone all expired DNS records.
327 NTSTATUS dns_tombstone_records(TALLOC_CTX *mem_ctx,
328 struct ldb_context *samdb,
329 char **error_string)
331 struct dns_server_zone *zones = NULL;
332 struct dns_server_zone *z = NULL;
333 NTSTATUS ret;
334 uint32_t dns_timestamp;
335 NTTIME entombed_time;
336 TALLOC_CTX *tmp_ctx = NULL;
337 time_t unix_now = time(NULL);
339 unix_to_nt_time(&entombed_time, unix_now);
340 dns_timestamp = unix_to_dns_timestamp(unix_now);
342 tmp_ctx = talloc_new(mem_ctx);
343 if (tmp_ctx == NULL) {
344 return NT_STATUS_NO_MEMORY;
347 ret = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
348 if (!NT_STATUS_IS_OK(ret)) {
349 TALLOC_FREE(tmp_ctx);
350 return ret;
353 for (z = zones; z; z = z->next) {
354 ret = dns_tombstone_records_zone(tmp_ctx,
355 samdb,
357 dns_timestamp,
358 entombed_time,
359 error_string);
360 if (NT_STATUS_EQUAL(ret, NT_STATUS_PROPSET_NOT_FOUND)) {
361 continue;
362 } else if (!NT_STATUS_IS_OK(ret)) {
363 TALLOC_FREE(tmp_ctx);
364 return ret;
367 TALLOC_FREE(tmp_ctx);
368 return NT_STATUS_OK;
372 * Delete all DNS tombstones that have been around for longer than the server
373 * property 'dns_tombstone_interval' which we store in smb.conf, which
374 * corresponds to DsTombstoneInterval in [MS-DNSP] 3.1.1.1.1 "DNS Server
375 * Integer Properties".
377 NTSTATUS dns_delete_tombstones(TALLOC_CTX *mem_ctx,
378 struct ldb_context *samdb,
379 char **error_string)
381 struct dns_server_zone *zones = NULL;
382 struct dns_server_zone *z = NULL;
383 int ret, i;
384 NTSTATUS status;
385 uint32_t current_time;
386 uint32_t tombstone_interval;
387 uint32_t tombstone_hours;
388 NTTIME tombstone_nttime;
389 enum ndr_err_code ndr_err;
390 struct ldb_result *res = NULL;
391 TALLOC_CTX *tmp_ctx = NULL;
392 struct loadparm_context *lp_ctx = NULL;
393 struct ldb_message_element *el = NULL;
394 struct dnsp_DnssrvRpcRecord rec = {0};
395 const char *attrs[] = {"dnsRecord", "dNSTombstoned", NULL};
397 current_time = unix_to_dns_timestamp(time(NULL));
399 lp_ctx = (struct loadparm_context *)ldb_get_opaque(samdb, "loadparm");
400 tombstone_interval = lpcfg_parm_ulong(lp_ctx, NULL,
401 "dnsserver",
402 "dns_tombstone_interval",
403 24 * 14);
405 tombstone_hours = current_time - tombstone_interval;
406 status = dns_timestamp_to_nt_time(&tombstone_nttime,
407 tombstone_hours);
409 if (!NT_STATUS_IS_OK(status)) {
410 DBG_ERR("DNS timestamp exceeds NTTIME epoch.\n");
411 return NT_STATUS_INTERNAL_ERROR;
414 tmp_ctx = talloc_new(mem_ctx);
415 if (tmp_ctx == NULL) {
416 return NT_STATUS_NO_MEMORY;
418 status = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
419 if (!NT_STATUS_IS_OK(status)) {
420 TALLOC_FREE(tmp_ctx);
421 return status;
424 for (z = zones; z; z = z->next) {
426 * This can load a very large set, but on the
427 * assumption that the number of tombstones is
428 * relatively small compared with the number of active
429 * records, and that this is an indexed lookup, this
430 * should be OK. We can make a match rule if
431 * returning the set of tombstones becomes an issue.
434 ret = ldb_search(samdb,
435 tmp_ctx,
436 &res,
437 z->dn,
438 LDB_SCOPE_SUBTREE,
439 attrs,
440 "(&(objectClass=dnsNode)(dNSTombstoned=TRUE))");
442 if (ret != LDB_SUCCESS) {
443 *error_string =
444 talloc_asprintf(mem_ctx,
445 "Failed to "
446 "search for tombstoned "
447 "dns objects in zone %s: %s",
448 ldb_dn_get_linearized(z->dn),
449 ldb_errstring(samdb));
450 TALLOC_FREE(tmp_ctx);
451 return NT_STATUS_INTERNAL_ERROR;
454 for (i = 0; i < res->count; i++) {
455 struct ldb_message *msg = res->msgs[i];
456 el = ldb_msg_find_element(msg, "dnsRecord");
457 if (el == NULL) {
458 DBG_ERR("The tombstoned dns node %s has no dns "
459 "records, which should not happen.\n",
460 ldb_dn_get_linearized(msg->dn)
462 continue;
465 * Below we assume the element has one value, which we
466 * expect because when we tombstone a node we remove
467 * all the records except for the tombstone.
469 if (el->num_values != 1) {
470 DBG_ERR("The tombstoned dns node %s has %u "
471 "dns records, expected one.\n",
472 ldb_dn_get_linearized(msg->dn),
473 el->num_values
475 continue;
478 ndr_err = ndr_pull_struct_blob(
479 el->values,
480 tmp_ctx,
481 &rec,
482 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
483 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
484 TALLOC_FREE(tmp_ctx);
485 DBG_ERR("Failed to pull dns rec blob.\n");
486 return NT_STATUS_INTERNAL_ERROR;
489 if (rec.wType != DNS_TYPE_TOMBSTONE) {
490 DBG_ERR("A tombstoned dnsNode has non-tombstoned"
491 " records, which should not happen.\n");
492 continue;
495 if (rec.data.EntombedTime > tombstone_nttime) {
496 continue;
499 * Between 4.9 and 4.14 in some places we saved the
500 * tombstone time as hours since the start of 1601,
501 * not in NTTIME ten-millionths of a second units.
503 * We can accommodate these bad values by noting that
504 * all the realistic timestamps in that measurement
505 * fall within the first *second* of NTTIME, that is,
506 * before 1601-01-01 00:00:01; and that these
507 * timestamps are not realistic for NTTIME timestamps.
509 * Calculation: there are roughly 365.25 * 24 = 8766
510 * hours per year, and < 500 years since 1601, so
511 * 4383000 would be a fine threshold. We round up to
512 * the crore-second (c. 2741CE) in honour of NTTIME.
514 if ((rec.data.EntombedTime < 10000000) &&
515 (rec.data.EntombedTime > tombstone_hours)) {
516 continue;
519 ret = dsdb_delete(samdb, msg->dn, 0);
520 if (ret != LDB_ERR_NO_SUCH_OBJECT &&
521 ret != LDB_SUCCESS) {
522 TALLOC_FREE(tmp_ctx);
523 DBG_ERR("Failed to delete dns node \n");
524 return NT_STATUS_INTERNAL_ERROR;
529 TALLOC_FREE(tmp_ctx);
530 return NT_STATUS_OK;