samba-tool: add `samba-tool domain kds root_key list`
[samba.git] / lib / ldb-samba / ldb_matching_rules.c
blob59d1385f4e367893352003623a223d80d29487af
1 /*
2 Unix SMB/CIFS implementation.
4 ldb database library - Extended match rules
6 Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me>
7 Copyright (C) Andrew Bartlett <abartlet@samba.org>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "includes.h"
24 #include <ldb_module.h>
25 #include "dsdb/samdb/samdb.h"
26 #include "ldb_matching_rules.h"
27 #include "libcli/security/security.h"
28 #include "dsdb/common/util.h"
29 #include "librpc/gen_ndr/ndr_dnsp.h"
30 #include "lib/util/smb_strtox.h"
32 #undef strcasecmp
34 static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx,
35 struct ldb_context *ldb,
36 const char *attr,
37 const struct dsdb_dn *dn_to_match,
38 const char *dn_oid,
39 struct dsdb_dn *to_visit,
40 struct dsdb_dn ***visited,
41 unsigned int *visited_count,
42 bool *matched)
44 TALLOC_CTX *tmp_ctx;
45 int ret, i, j;
46 struct ldb_result *res;
47 struct ldb_message *msg;
48 struct ldb_message_element *el;
49 const char *attrs[] = { attr, NULL };
51 tmp_ctx = talloc_new(mem_ctx);
52 if (tmp_ctx == NULL) {
53 return LDB_ERR_OPERATIONS_ERROR;
57 * Fetch the entry to_visit
59 * NOTE: This is a new LDB search from the TOP of the module
60 * stack. This means that this search runs the whole stack
61 * from top to bottom.
63 * This may seem to be in-efficient, but it is also the only
64 * way to ensure that the ACLs for this search are applied
65 * correctly.
67 * Note also that we don't have the original request
68 * here, so we can not apply controls or timeouts here.
70 ret = dsdb_search_dn(ldb,
71 tmp_ctx,
72 &res,
73 to_visit->dn,
74 attrs,
75 DSDB_MARK_REQ_UNTRUSTED);
76 if (ret != LDB_SUCCESS) {
77 talloc_free(tmp_ctx);
78 return ret;
80 if (res->count != 1) {
81 talloc_free(tmp_ctx);
82 return LDB_ERR_OPERATIONS_ERROR;
84 msg = res->msgs[0];
86 /* Fetch the attribute to match from the entry being visited */
87 el = ldb_msg_find_element(msg, attr);
88 if (el == NULL) {
89 /* This entry does not have the attribute to match */
90 talloc_free(tmp_ctx);
91 *matched = false;
92 return LDB_SUCCESS;
96 * If the value to match is present in the attribute values of the
97 * current entry being visited, set matched to true and return OK
99 for (i=0; i<el->num_values; i++) {
100 struct dsdb_dn *dn;
101 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
102 if (dn == NULL) {
103 talloc_free(tmp_ctx);
104 *matched = false;
105 return LDB_ERR_INVALID_DN_SYNTAX;
108 if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) {
109 talloc_free(tmp_ctx);
110 *matched = true;
111 return LDB_SUCCESS;
116 * If arrived here, the value to match is not in the values of the
117 * entry being visited. Add the entry being visited (to_visit)
118 * to the visited array. The array is (re)allocated in the parent
119 * memory context.
121 if (visited == NULL) {
122 return LDB_ERR_OPERATIONS_ERROR;
123 } else if (*visited == NULL) {
124 *visited = talloc_array(mem_ctx, struct dsdb_dn *, 1);
125 if (*visited == NULL) {
126 talloc_free(tmp_ctx);
127 return LDB_ERR_OPERATIONS_ERROR;
129 (*visited)[0] = to_visit;
130 (*visited_count) = 1;
131 } else {
132 *visited = talloc_realloc(mem_ctx, *visited, struct dsdb_dn *,
133 (*visited_count) + 1);
134 if (*visited == NULL) {
135 talloc_free(tmp_ctx);
136 return LDB_ERR_OPERATIONS_ERROR;
138 (*visited)[(*visited_count)] = to_visit;
139 (*visited_count)++;
143 * steal to_visit into visited array context, as it has to live until
144 * the array is freed.
146 talloc_steal(*visited, to_visit);
149 * Iterate over the values of the attribute of the entry being
150 * visited (to_visit) and follow them, calling this function
151 * recursively.
152 * If the value is in the visited array, skip it.
153 * Otherwise, follow the link and visit it.
155 for (i=0; i<el->num_values; i++) {
156 struct dsdb_dn *next_to_visit;
157 bool skip = false;
159 next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
160 if (next_to_visit == NULL) {
161 talloc_free(tmp_ctx);
162 *matched = false;
163 return LDB_ERR_INVALID_DN_SYNTAX;
167 * If the value is already in the visited array, skip it.
168 * Note the last element of the array is ignored because it is
169 * the current entry DN.
171 for (j=0; j < (*visited_count) - 1; j++) {
172 struct dsdb_dn *visited_dn = (*visited)[j];
173 if (ldb_dn_compare(visited_dn->dn,
174 next_to_visit->dn) == 0) {
175 skip = true;
176 break;
179 if (skip) {
180 talloc_free(next_to_visit);
181 continue;
184 /* If the value is not in the visited array, evaluate it */
185 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
186 dn_to_match, dn_oid,
187 next_to_visit,
188 visited, visited_count,
189 matched);
190 if (ret != LDB_SUCCESS) {
191 talloc_free(tmp_ctx);
192 return ret;
194 if (*matched) {
195 talloc_free(tmp_ctx);
196 return LDB_SUCCESS;
200 talloc_free(tmp_ctx);
201 *matched = false;
202 return LDB_SUCCESS;
206 * This function parses the linked attribute value to match, whose syntax
207 * will be one of the different DN syntaxes, into a ldb_dn struct.
209 static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx,
210 struct ldb_context *ldb,
211 const char *attr,
212 const struct ldb_val *value_to_match,
213 struct dsdb_dn *current_object_dn,
214 bool *matched)
216 const struct dsdb_schema *schema;
217 const struct dsdb_attribute *schema_attr;
218 struct dsdb_dn *dn_to_match;
219 const char *dn_oid;
220 unsigned int count;
221 struct dsdb_dn **visited = NULL;
223 schema = dsdb_get_schema(ldb, mem_ctx);
224 if (schema == NULL) {
225 return LDB_ERR_OPERATIONS_ERROR;
228 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr);
229 if (schema_attr == NULL) {
230 return LDB_ERR_NO_SUCH_ATTRIBUTE;
233 /* This is the DN syntax of the attribute being matched */
234 dn_oid = schema_attr->syntax->ldap_oid;
237 * Build a ldb_dn struct holding the value to match, which is the
238 * value entered in the search filter
240 dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid);
241 if (dn_to_match == NULL) {
242 *matched = false;
243 return LDB_SUCCESS;
246 return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
247 dn_to_match, dn_oid,
248 current_object_dn,
249 &visited, &count, matched);
253 * This rule provides recursive search of a link attribute
255 * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
256 * This allows a search filter such as:
258 * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
260 * This searches not only the member attribute, but also any member
261 * attributes that point at an object with this member in them. All the
262 * various DN syntax types are supported, not just plain DNs.
265 static int ldb_comparator_trans(struct ldb_context *ldb,
266 const char *oid,
267 const struct ldb_message *msg,
268 const char *attribute_to_match,
269 const struct ldb_val *value_to_match,
270 bool *matched)
272 const struct dsdb_schema *schema;
273 const struct dsdb_attribute *schema_attr;
274 struct ldb_dn *msg_dn;
275 struct dsdb_dn *dsdb_msg_dn;
276 TALLOC_CTX *tmp_ctx;
277 int ret;
279 tmp_ctx = talloc_new(ldb);
280 if (tmp_ctx == NULL) {
281 return LDB_ERR_OPERATIONS_ERROR;
285 * If the target attribute to match is not a linked attribute, then
286 * the filter evaluates to undefined
288 schema = dsdb_get_schema(ldb, tmp_ctx);
289 if (schema == NULL) {
290 talloc_free(tmp_ctx);
291 return LDB_ERR_OPERATIONS_ERROR;
294 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
295 if (schema_attr == NULL) {
296 talloc_free(tmp_ctx);
297 return LDB_ERR_NO_SUCH_ATTRIBUTE;
301 * This extended match filter is only valid for linked attributes,
302 * following the MS definition (the schema attribute has a linkID
303 * defined). See dochelp request 114111212024789 on cifs-protocols
304 * mailing list.
306 if (schema_attr->linkID == 0) {
307 *matched = false;
308 talloc_free(tmp_ctx);
309 return LDB_SUCCESS;
312 /* Duplicate original msg dn as the msg must not be modified */
313 msg_dn = ldb_dn_copy(tmp_ctx, msg->dn);
314 if (msg_dn == NULL) {
315 talloc_free(tmp_ctx);
316 return LDB_ERR_OPERATIONS_ERROR;
320 * Build a dsdb dn from the message copied DN, which should be a plain
321 * DN syntax.
323 dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
324 LDB_SYNTAX_DN);
325 if (dsdb_msg_dn == NULL) {
326 *matched = false;
327 return LDB_ERR_INVALID_DN_SYNTAX;
330 ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
331 attribute_to_match,
332 value_to_match,
333 dsdb_msg_dn, matched);
334 talloc_free(tmp_ctx);
335 return ret;
340 * This rule provides match of a dns object with expired records.
342 * This allows a search filter such as:
344 * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=3694869
346 * where the value is a number of hours since the start of 1601.
348 * This allows the caller to find records that should become a DNS
349 * tomestone, despite that information being deep within an NDR packed
350 * object
352 static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb,
353 const char *oid,
354 const struct ldb_message *msg,
355 const char *attribute_to_match,
356 const struct ldb_val *value_to_match,
357 bool *matched)
359 TALLOC_CTX *tmp_ctx;
360 unsigned int i;
361 struct ldb_message_element *el = NULL;
362 struct auth_session_info *session_info = NULL;
363 uint64_t tombstone_time;
364 struct dnsp_DnssrvRpcRecord *rec = NULL;
365 enum ndr_err_code err;
366 *matched = false;
368 /* Needs to be dnsRecord, no match otherwise */
369 if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) {
370 return LDB_SUCCESS;
373 el = ldb_msg_find_element(msg, attribute_to_match);
374 if (el == NULL) {
375 return LDB_SUCCESS;
378 if (ldb_msg_element_is_inaccessible(el)) {
379 *matched = false;
380 return LDB_SUCCESS;
383 session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
384 struct auth_session_info);
385 if (session_info == NULL) {
386 return ldb_oom(ldb);
388 if (security_session_user_level(session_info, NULL)
389 != SECURITY_SYSTEM) {
391 DBG_ERR("unauthorised access\n");
392 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
395 /* We only expect uint32_t <= 10 digits */
396 if (value_to_match->length >= 12) {
397 DBG_ERR("Invalid timestamp passed\n");
398 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
399 } else {
400 int error = 0;
401 char s[12];
403 memcpy(s, value_to_match->data, value_to_match->length);
404 s[value_to_match->length] = 0;
405 if (s[0] == '\0') {
406 DBG_ERR("Empty timestamp passed\n");
407 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
409 tombstone_time = smb_strtoull(s,
410 NULL,
412 &error,
413 SMB_STR_FULL_STR_CONV);
414 if (error != 0) {
415 DBG_ERR("Invalid timestamp string passed\n");
416 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
420 tmp_ctx = talloc_new(ldb);
421 if (tmp_ctx == NULL) {
422 return ldb_oom(ldb);
425 for (i = 0; i < el->num_values; i++) {
426 rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord);
427 if (rec == NULL) {
428 TALLOC_FREE(tmp_ctx);
429 return ldb_oom(ldb);
431 err = ndr_pull_struct_blob(
432 &(el->values[i]),
433 tmp_ctx,
434 rec,
435 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
436 if (!NDR_ERR_CODE_IS_SUCCESS(err)){
437 DBG_ERR("Failed to pull dns rec blob.\n");
438 TALLOC_FREE(tmp_ctx);
439 return LDB_ERR_OPERATIONS_ERROR;
442 if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) {
443 TALLOC_FREE(rec);
444 continue;
447 if (rec->wType == DNS_TYPE_TOMBSTONE) {
448 TALLOC_FREE(rec);
449 continue;
451 if (rec->dwTimeStamp == 0) {
452 TALLOC_FREE(rec);
453 continue;
455 if (rec->dwTimeStamp > tombstone_time) {
456 TALLOC_FREE(rec);
457 continue;
460 *matched = true;
461 break;
464 TALLOC_FREE(tmp_ctx);
465 return LDB_SUCCESS;
470 * This rule provides match of a link attribute against a 'should be expunged' criteria
472 * This allows a search filter such as:
474 * member:1.3.6.1.4.1.7165.4.5.2:=131139216000000000
476 * This searches the member attribute, but also any member attributes
477 * that are deleted and should be expunged after the specified NTTIME
478 * time.
481 static int dsdb_match_for_expunge(struct ldb_context *ldb,
482 const char *oid,
483 const struct ldb_message *msg,
484 const char *attribute_to_match,
485 const struct ldb_val *value_to_match,
486 bool *matched)
488 const struct dsdb_schema *schema;
489 const struct dsdb_attribute *schema_attr;
490 TALLOC_CTX *tmp_ctx;
491 unsigned int i;
492 struct ldb_message_element *el;
493 struct auth_session_info *session_info;
494 uint64_t tombstone_time;
495 *matched = false;
497 el = ldb_msg_find_element(msg, attribute_to_match);
498 if (el == NULL) {
499 return LDB_SUCCESS;
502 if (ldb_msg_element_is_inaccessible(el)) {
503 *matched = false;
504 return LDB_SUCCESS;
507 session_info
508 = talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO),
509 struct auth_session_info);
510 if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
511 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
515 * If the target attribute to match is not a linked attribute, then
516 * the filter evaluates to undefined
518 schema = dsdb_get_schema(ldb, NULL);
519 if (schema == NULL) {
520 return LDB_ERR_OPERATIONS_ERROR;
523 /* TODO this is O(log n) per attribute */
524 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
525 if (schema_attr == NULL) {
526 return LDB_ERR_NO_SUCH_ATTRIBUTE;
530 * This extended match filter is only valid for forward linked attributes.
532 if (schema_attr->linkID == 0 || (schema_attr->linkID & 1) == 1) {
533 return LDB_ERR_NO_SUCH_ATTRIBUTE;
536 /* Just check we don't allow the caller to fill our stack */
537 if (value_to_match->length >=64) {
538 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
539 } else {
540 int error = 0;
541 char s[value_to_match->length+1];
543 memcpy(s, value_to_match->data, value_to_match->length);
544 s[value_to_match->length] = 0;
545 if (s[0] == '\0' || s[0] == '-') {
546 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
548 tombstone_time = smb_strtoull(s,
549 NULL,
551 &error,
552 SMB_STR_FULL_STR_CONV);
553 if (error != 0) {
554 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
558 tmp_ctx = talloc_new(ldb);
559 if (tmp_ctx == NULL) {
560 return LDB_ERR_OPERATIONS_ERROR;
563 for (i = 0; i < el->num_values; i++) {
564 NTSTATUS status;
565 struct dsdb_dn *dn;
566 uint64_t rmd_changetime;
567 if (dsdb_dn_is_deleted_val(&el->values[i]) == false) {
568 continue;
571 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i],
572 schema_attr->syntax->ldap_oid);
573 if (dn == NULL) {
574 DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el->name));
575 continue;
578 status = dsdb_get_extended_dn_uint64(dn->dn, &rmd_changetime,
579 "RMD_CHANGETIME");
580 if (!NT_STATUS_IS_OK(status)) {
581 DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n"));
582 continue;
585 if (rmd_changetime > tombstone_time) {
586 continue;
589 *matched = true;
590 break;
592 talloc_free(tmp_ctx);
593 return LDB_SUCCESS;
597 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
599 struct ldb_extended_match_rule *transitive_eval = NULL,
600 *match_for_expunge = NULL,
601 *match_for_dns_to_tombstone_time = NULL;
602 int ret;
604 transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
605 transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL;
606 transitive_eval->callback = ldb_comparator_trans;
607 ret = ldb_register_extended_match_rule(ldb, transitive_eval);
608 if (ret != LDB_SUCCESS) {
609 talloc_free(transitive_eval);
610 return ret;
613 match_for_expunge = talloc_zero(ldb, struct ldb_extended_match_rule);
614 match_for_expunge->oid = DSDB_MATCH_FOR_EXPUNGE;
615 match_for_expunge->callback = dsdb_match_for_expunge;
616 ret = ldb_register_extended_match_rule(ldb, match_for_expunge);
617 if (ret != LDB_SUCCESS) {
618 talloc_free(match_for_expunge);
619 return ret;
622 match_for_dns_to_tombstone_time = talloc_zero(
623 ldb,
624 struct ldb_extended_match_rule);
625 match_for_dns_to_tombstone_time->oid = DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME;
626 match_for_dns_to_tombstone_time->callback
627 = dsdb_match_for_dns_to_tombstone_time;
628 ret = ldb_register_extended_match_rule(ldb,
629 match_for_dns_to_tombstone_time);
630 if (ret != LDB_SUCCESS) {
631 TALLOC_FREE(match_for_dns_to_tombstone_time);
632 return ret;
635 return LDB_SUCCESS;