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/>.
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"
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
)
52 struct dnsp_DnssrvRpcRecord rec
;
53 enum ndr_err_code ndr_err
;
55 el
->values
= talloc_zero_array(mem_ctx
, struct ldb_val
,
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(
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
];
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
,
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
;
103 struct GUID_txt_buf buf_guid
;
104 const char *attrs
[] = {"dnsRecord",
109 struct ldb_val true_val
= {
110 .data
= discard_const_p(uint8_t, "TRUE"),
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(
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
);
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
,
157 "(&(objectClass=dnsNode)"
158 "(&(!(dnsTombstoned=TRUE))"
159 "(dnsRecord:" DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME
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
);
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
,
231 TALLOC_FREE(new_msg
);
232 return NT_STATUS_INTERNAL_ERROR
;
235 el
->values
[0] = tombstone_blob
;
238 tombstone_el
= ldb_msg_find_element(new_msg
,
241 if (tombstone_el
== NULL
) {
242 ret
= ldb_msg_add_value(new_msg
,
246 if (ret
!= LDB_SUCCESS
) {
247 TALLOC_FREE(new_msg
);
248 return NT_STATUS_INTERNAL_ERROR
;
250 tombstone_el
->flags
= LDB_FLAG_MOD_ADD
;
252 if (tombstone_el
->num_values
!= 1) {
253 vals
= talloc_realloc(
255 tombstone_el
->values
,
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
;
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
,
280 /* Set DN to the GUID in case the object was moved. */
281 el
= ldb_msg_find_element(new_msg
, "objectGUID");
283 TALLOC_FREE(new_msg
);
285 talloc_asprintf(mem_ctx
,
286 "record has no objectGUID "
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
);
296 discard_const_p(char, "Error: Invalid GUID.\n");
297 return NT_STATUS_INTERNAL_ERROR
;
300 GUID_buf_string(&guid
, &buf_guid
);
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
);
311 talloc_asprintf(mem_ctx
,
312 "Failed to modify dns record "
314 ldb_dn_get_linearized(zone
->dn
),
315 ldb_errstring(samdb
));
316 return NT_STATUS_INTERNAL_ERROR
;
318 TALLOC_FREE(new_msg
);
325 * Tombstone all expired DNS records.
327 NTSTATUS
dns_tombstone_records(TALLOC_CTX
*mem_ctx
,
328 struct ldb_context
*samdb
,
331 struct dns_server_zone
*zones
= NULL
;
332 struct dns_server_zone
*z
= NULL
;
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
);
353 for (z
= zones
; z
; z
= z
->next
) {
354 ret
= dns_tombstone_records_zone(tmp_ctx
,
360 if (NT_STATUS_EQUAL(ret
, NT_STATUS_PROPSET_NOT_FOUND
)) {
362 } else if (!NT_STATUS_IS_OK(ret
)) {
363 TALLOC_FREE(tmp_ctx
);
367 TALLOC_FREE(tmp_ctx
);
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
,
381 struct dns_server_zone
*zones
= NULL
;
382 struct dns_server_zone
*z
= NULL
;
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
,
402 "dns_tombstone_interval",
405 tombstone_hours
= current_time
- tombstone_interval
;
406 status
= dns_timestamp_to_nt_time(&tombstone_nttime
,
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
);
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
,
440 "(&(objectClass=dnsNode)(dNSTombstoned=TRUE))");
442 if (ret
!= LDB_SUCCESS
) {
444 talloc_asprintf(mem_ctx
,
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");
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
)
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
),
478 ndr_err
= ndr_pull_struct_blob(
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");
495 if (rec
.data
.EntombedTime
> tombstone_nttime
) {
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
)) {
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
);