Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : DNS tombstoning routines
5 :
6 : Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
7 :
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.
12 :
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.
17 :
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/>.
20 :
21 : */
22 :
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"
38 :
39 : #include "librpc/gen_ndr/ndr_misc.h"
40 : #include "librpc/gen_ndr/ndr_drsuapi.h"
41 : #include "librpc/gen_ndr/ndr_drsblobs.h"
42 :
43 : /*
44 : * Copy only non-expired dns records from one message element to another.
45 : */
46 12 : 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)
50 : {
51 0 : unsigned int i;
52 0 : struct dnsp_DnssrvRpcRecord rec;
53 0 : enum ndr_err_code ndr_err;
54 :
55 12 : el->values = talloc_zero_array(mem_ctx, struct ldb_val,
56 : old_el->num_values);
57 12 : if (el->values == NULL) {
58 0 : return NT_STATUS_NO_MEMORY;
59 : }
60 :
61 30 : for (i = 0; i < old_el->num_values; i++) {
62 18 : ndr_err = ndr_pull_struct_blob(
63 18 : &(old_el->values[i]),
64 : mem_ctx,
65 : &rec,
66 : (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
67 18 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
68 0 : DBG_ERR("Failed to pull dns rec blob.\n");
69 0 : return NT_STATUS_INTERNAL_ERROR;
70 : }
71 18 : if (rec.dwTimeStamp > dns_timestamp ||
72 12 : rec.dwTimeStamp == 0) {
73 6 : el->values[el->num_values] = old_el->values[i];
74 6 : el->num_values++;
75 : }
76 : }
77 :
78 12 : return NT_STATUS_OK;
79 : }
80 :
81 : /*
82 : * Check all records in a zone and tombstone them if they're expired.
83 : */
84 18 : 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)
90 : {
91 0 : WERROR werr;
92 0 : NTSTATUS status;
93 0 : unsigned int i;
94 18 : struct dnsserver_zoneinfo *zi = NULL;
95 18 : struct ldb_result *res = NULL;
96 18 : struct ldb_message_element *el = NULL;
97 18 : struct ldb_message_element *tombstone_el = NULL;
98 18 : struct ldb_message_element *old_el = NULL;
99 18 : struct ldb_message *new_msg = NULL;
100 0 : enum ndr_err_code ndr_err;
101 0 : int ret;
102 0 : struct GUID guid;
103 0 : struct GUID_txt_buf buf_guid;
104 18 : const char *attrs[] = {"dnsRecord",
105 : "dNSTombstoned",
106 : "objectGUID",
107 : NULL};
108 :
109 18 : struct ldb_val true_val = {
110 : .data = discard_const_p(uint8_t, "TRUE"),
111 : .length = 4
112 : };
113 :
114 0 : struct ldb_val tombstone_blob;
115 18 : struct dnsp_DnssrvRpcRecord tombstone_struct = {
116 : .wType = DNS_TYPE_TOMBSTONE,
117 : .data = {.EntombedTime = entombed_time}
118 : };
119 :
120 18 : 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 18 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
126 0 : *error_string = discard_const_p(char,
127 : "Failed to push TOMBSTONE"
128 : "dnsp_DnssrvRpcRecord\n");
129 0 : return NT_STATUS_INTERNAL_ERROR;
130 : }
131 :
132 18 : *error_string = NULL;
133 :
134 : /* Get NoRefreshInterval and RefreshInterval from zone properties.*/
135 18 : zi = talloc(mem_ctx, struct dnsserver_zoneinfo);
136 18 : if (zi == NULL) {
137 0 : return NT_STATUS_NO_MEMORY;
138 : }
139 18 : werr = dns_get_zone_properties(samdb, mem_ctx, zone->dn, zi);
140 18 : if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
141 6 : return NT_STATUS_PROPSET_NOT_FOUND;
142 12 : } else if (!W_ERROR_IS_OK(werr)) {
143 0 : return NT_STATUS_INTERNAL_ERROR;
144 : }
145 :
146 : /* Subtract them from current time to get the earliest possible.
147 : * timestamp allowed for a non-expired DNS record. */
148 12 : dns_timestamp -= zi->dwNoRefreshInterval + zi->dwRefreshInterval;
149 :
150 : /* Custom match gets dns records in the zone with dwTimeStamp < t. */
151 12 : 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 12 : if (ret != LDB_SUCCESS) {
163 0 : *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 0 : return NT_STATUS_INTERNAL_ERROR;
169 : }
170 :
171 : /*
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.
178 : */
179 24 : for (i = 0; i < res->count; i++) {
180 12 : new_msg = ldb_msg_copy(mem_ctx, res->msgs[i]);
181 12 : if (new_msg == NULL) {
182 0 : return NT_STATUS_INTERNAL_ERROR;
183 : }
184 :
185 12 : old_el = ldb_msg_find_element(new_msg, "dnsRecord");
186 12 : if (old_el == NULL) {
187 0 : TALLOC_FREE(new_msg);
188 0 : return NT_STATUS_INTERNAL_ERROR;
189 : }
190 12 : old_el->flags = LDB_FLAG_MOD_DELETE;
191 :
192 12 : ret = ldb_msg_add_empty(
193 : new_msg, "dnsRecord", LDB_FLAG_MOD_ADD, &el);
194 12 : if (ret != LDB_SUCCESS) {
195 0 : TALLOC_FREE(new_msg);
196 0 : return NT_STATUS_INTERNAL_ERROR;
197 : }
198 :
199 12 : status = copy_current_records(new_msg, old_el, el, dns_timestamp);
200 :
201 12 : if (!NT_STATUS_IS_OK(status)) {
202 0 : TALLOC_FREE(new_msg);
203 0 : return NT_STATUS_INTERNAL_ERROR;
204 : }
205 :
206 : /* If nothing was expired, do nothing. */
207 12 : if (el->num_values == old_el->num_values &&
208 0 : el->num_values != 0) {
209 0 : TALLOC_FREE(new_msg);
210 0 : continue;
211 : }
212 :
213 : /*
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:
217 : *
218 : * objectClass: dnsNode
219 : * dnsRecord: { .wType = DNSTYPE_TOMBSTONE,
220 : * .data.EntombedTime = <now> }
221 : * dnsTombstoned: TRUE
222 : *
223 : * and no other dnsRecords.
224 : */
225 12 : if (el->num_values == 0) {
226 8 : struct ldb_val *vals = talloc_realloc(new_msg->elements,
227 : el->values,
228 : struct ldb_val,
229 : 1);
230 8 : if (!vals) {
231 0 : TALLOC_FREE(new_msg);
232 0 : return NT_STATUS_INTERNAL_ERROR;
233 : }
234 8 : el->values = vals;
235 8 : el->values[0] = tombstone_blob;
236 8 : el->num_values = 1;
237 :
238 8 : tombstone_el = ldb_msg_find_element(new_msg,
239 : "dnsTombstoned");
240 :
241 8 : if (tombstone_el == NULL) {
242 4 : ret = ldb_msg_add_value(new_msg,
243 : "dnsTombstoned",
244 : &true_val,
245 : &tombstone_el);
246 4 : if (ret != LDB_SUCCESS) {
247 0 : TALLOC_FREE(new_msg);
248 0 : return NT_STATUS_INTERNAL_ERROR;
249 : }
250 4 : tombstone_el->flags = LDB_FLAG_MOD_ADD;
251 : } else {
252 4 : if (tombstone_el->num_values != 1) {
253 0 : vals = talloc_realloc(
254 : new_msg->elements,
255 : tombstone_el->values,
256 : struct ldb_val,
257 : 1);
258 0 : if (!vals) {
259 0 : TALLOC_FREE(new_msg);
260 0 : return NT_STATUS_INTERNAL_ERROR;
261 : }
262 0 : tombstone_el->values = vals;
263 0 : tombstone_el->num_values = 1;
264 : }
265 4 : tombstone_el->flags = LDB_FLAG_MOD_REPLACE;
266 4 : tombstone_el->values[0] = true_val;
267 : }
268 : } else {
269 : /*
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).
275 : */
276 4 : ldb_msg_remove_attr(new_msg,
277 : "dnsTombstoned");
278 : }
279 :
280 : /* Set DN to the GUID in case the object was moved. */
281 12 : el = ldb_msg_find_element(new_msg, "objectGUID");
282 12 : if (el == NULL) {
283 0 : TALLOC_FREE(new_msg);
284 0 : *error_string =
285 0 : talloc_asprintf(mem_ctx,
286 : "record has no objectGUID "
287 : "in zone %s",
288 : ldb_dn_get_linearized(zone->dn));
289 0 : return NT_STATUS_INTERNAL_ERROR;
290 : }
291 :
292 12 : status = GUID_from_ndr_blob(el->values, &guid);
293 12 : if (!NT_STATUS_IS_OK(status)) {
294 0 : TALLOC_FREE(new_msg);
295 0 : *error_string =
296 : discard_const_p(char, "Error: Invalid GUID.\n");
297 0 : return NT_STATUS_INTERNAL_ERROR;
298 : }
299 :
300 12 : GUID_buf_string(&guid, &buf_guid);
301 12 : new_msg->dn =
302 12 : ldb_dn_new_fmt(mem_ctx, samdb, "<GUID=%s>", buf_guid.buf);
303 :
304 : /* Remove the GUID so we're not trying to modify it. */
305 12 : ldb_msg_remove_attr(new_msg, "objectGUID");
306 :
307 12 : ret = ldb_modify(samdb, new_msg);
308 12 : if (ret != LDB_SUCCESS) {
309 0 : TALLOC_FREE(new_msg);
310 0 : *error_string =
311 0 : 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 0 : return NT_STATUS_INTERNAL_ERROR;
317 : }
318 12 : TALLOC_FREE(new_msg);
319 : }
320 :
321 12 : return NT_STATUS_OK;
322 : }
323 :
324 : /*
325 : * Tombstone all expired DNS records.
326 : */
327 6 : NTSTATUS dns_tombstone_records(TALLOC_CTX *mem_ctx,
328 : struct ldb_context *samdb,
329 : char **error_string)
330 : {
331 6 : struct dns_server_zone *zones = NULL;
332 6 : struct dns_server_zone *z = NULL;
333 0 : NTSTATUS ret;
334 0 : uint32_t dns_timestamp;
335 0 : NTTIME entombed_time;
336 6 : TALLOC_CTX *tmp_ctx = NULL;
337 6 : time_t unix_now = time(NULL);
338 :
339 6 : unix_to_nt_time(&entombed_time, unix_now);
340 6 : dns_timestamp = unix_to_dns_timestamp(unix_now);
341 :
342 6 : tmp_ctx = talloc_new(mem_ctx);
343 6 : if (tmp_ctx == NULL) {
344 0 : return NT_STATUS_NO_MEMORY;
345 : }
346 :
347 6 : ret = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
348 6 : if (!NT_STATUS_IS_OK(ret)) {
349 0 : TALLOC_FREE(tmp_ctx);
350 0 : return ret;
351 : }
352 :
353 24 : for (z = zones; z; z = z->next) {
354 18 : ret = dns_tombstone_records_zone(tmp_ctx,
355 : samdb,
356 : z,
357 : dns_timestamp,
358 : entombed_time,
359 : error_string);
360 18 : if (NT_STATUS_EQUAL(ret, NT_STATUS_PROPSET_NOT_FOUND)) {
361 6 : continue;
362 12 : } else if (!NT_STATUS_IS_OK(ret)) {
363 0 : TALLOC_FREE(tmp_ctx);
364 0 : return ret;
365 : }
366 : }
367 6 : TALLOC_FREE(tmp_ctx);
368 6 : return NT_STATUS_OK;
369 : }
370 :
371 : /*
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".
376 : */
377 67 : NTSTATUS dns_delete_tombstones(TALLOC_CTX *mem_ctx,
378 : struct ldb_context *samdb,
379 : char **error_string)
380 : {
381 67 : struct dns_server_zone *zones = NULL;
382 67 : struct dns_server_zone *z = NULL;
383 2 : int ret, i;
384 2 : NTSTATUS status;
385 2 : uint32_t current_time;
386 2 : uint32_t tombstone_interval;
387 2 : uint32_t tombstone_hours;
388 2 : NTTIME tombstone_nttime;
389 2 : enum ndr_err_code ndr_err;
390 67 : struct ldb_result *res = NULL;
391 67 : TALLOC_CTX *tmp_ctx = NULL;
392 67 : struct loadparm_context *lp_ctx = NULL;
393 67 : struct ldb_message_element *el = NULL;
394 67 : struct dnsp_DnssrvRpcRecord rec = {0};
395 67 : const char *attrs[] = {"dnsRecord", "dNSTombstoned", NULL};
396 :
397 67 : current_time = unix_to_dns_timestamp(time(NULL));
398 :
399 67 : lp_ctx = (struct loadparm_context *)ldb_get_opaque(samdb, "loadparm");
400 67 : tombstone_interval = lpcfg_parm_ulong(lp_ctx, NULL,
401 : "dnsserver",
402 : "dns_tombstone_interval",
403 : 24 * 14);
404 :
405 67 : tombstone_hours = current_time - tombstone_interval;
406 67 : status = dns_timestamp_to_nt_time(&tombstone_nttime,
407 : tombstone_hours);
408 :
409 67 : if (!NT_STATUS_IS_OK(status)) {
410 0 : DBG_ERR("DNS timestamp exceeds NTTIME epoch.\n");
411 0 : return NT_STATUS_INTERNAL_ERROR;
412 : }
413 :
414 67 : tmp_ctx = talloc_new(mem_ctx);
415 67 : if (tmp_ctx == NULL) {
416 0 : return NT_STATUS_NO_MEMORY;
417 : }
418 67 : status = dns_common_zones(samdb, tmp_ctx, NULL, &zones);
419 67 : if (!NT_STATUS_IS_OK(status)) {
420 0 : TALLOC_FREE(tmp_ctx);
421 0 : return status;
422 : }
423 :
424 205 : for (z = zones; z; z = z->next) {
425 : /*
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.
432 : */
433 :
434 138 : ret = ldb_search(samdb,
435 : tmp_ctx,
436 : &res,
437 : z->dn,
438 : LDB_SCOPE_SUBTREE,
439 : attrs,
440 : "(&(objectClass=dnsNode)(dNSTombstoned=TRUE))");
441 :
442 138 : if (ret != LDB_SUCCESS) {
443 0 : *error_string =
444 0 : 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 0 : TALLOC_FREE(tmp_ctx);
451 0 : return NT_STATUS_INTERNAL_ERROR;
452 : }
453 :
454 216 : for (i = 0; i < res->count; i++) {
455 78 : struct ldb_message *msg = res->msgs[i];
456 78 : el = ldb_msg_find_element(msg, "dnsRecord");
457 78 : if (el == NULL) {
458 0 : DBG_ERR("The tombstoned dns node %s has no dns "
459 : "records, which should not happen.\n",
460 : ldb_dn_get_linearized(msg->dn)
461 : );
462 0 : continue;
463 : }
464 : /*
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.
468 : */
469 78 : if (el->num_values != 1) {
470 0 : 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
474 : );
475 0 : continue;
476 : }
477 :
478 78 : ndr_err = ndr_pull_struct_blob(
479 78 : el->values,
480 : tmp_ctx,
481 : &rec,
482 : (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
483 78 : if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
484 0 : TALLOC_FREE(tmp_ctx);
485 0 : DBG_ERR("Failed to pull dns rec blob.\n");
486 0 : return NT_STATUS_INTERNAL_ERROR;
487 : }
488 :
489 78 : if (rec.wType != DNS_TYPE_TOMBSTONE) {
490 0 : DBG_ERR("A tombstoned dnsNode has non-tombstoned"
491 : " records, which should not happen.\n");
492 0 : continue;
493 : }
494 :
495 78 : if (rec.data.EntombedTime > tombstone_nttime) {
496 58 : continue;
497 : }
498 : /*
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.
502 : *
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.
508 : *
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.
513 : */
514 20 : if ((rec.data.EntombedTime < 10000000) &&
515 8 : (rec.data.EntombedTime > tombstone_hours)) {
516 4 : continue;
517 : }
518 :
519 16 : ret = dsdb_delete(samdb, msg->dn, 0);
520 16 : if (ret != LDB_ERR_NO_SUCH_OBJECT &&
521 0 : ret != LDB_SUCCESS) {
522 0 : TALLOC_FREE(tmp_ctx);
523 0 : DBG_ERR("Failed to delete dns node \n");
524 0 : return NT_STATUS_INTERNAL_ERROR;
525 : }
526 : }
527 :
528 : }
529 67 : TALLOC_FREE(tmp_ctx);
530 67 : return NT_STATUS_OK;
531 : }
|