ldb: Remove unnecessary declaration
[samba.git] / lib / ldb-samba / ldb_matching_rules.c
blobdd1f80628c980cb35fd6955d63942eedf94eadba
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 DBG_NOTICE("search failure (%d: %s) looking for '%s' on '%s'\n",
78 ret,
79 ldb_strerror(ret),
80 attr,
81 ldb_dn_get_linearized(to_visit->dn));
82 talloc_free(tmp_ctx);
83 return ret;
85 if (res->count != 1) {
86 talloc_free(tmp_ctx);
87 return LDB_ERR_OPERATIONS_ERROR;
89 msg = res->msgs[0];
91 /* Fetch the attribute to match from the entry being visited */
92 el = ldb_msg_find_element(msg, attr);
93 if (el == NULL) {
94 /* This entry does not have the attribute to match */
95 talloc_free(tmp_ctx);
96 *matched = false;
97 return LDB_SUCCESS;
101 * If the value to match is present in the attribute values of the
102 * current entry being visited, set matched to true and return OK
104 for (i=0; i<el->num_values; i++) {
105 struct dsdb_dn *dn;
106 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
107 if (dn == NULL) {
108 talloc_free(tmp_ctx);
109 *matched = false;
110 return LDB_ERR_INVALID_DN_SYNTAX;
113 if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) {
114 talloc_free(tmp_ctx);
115 *matched = true;
116 return LDB_SUCCESS;
121 * If arrived here, the value to match is not in the values of the
122 * entry being visited. Add the entry being visited (to_visit)
123 * to the visited array. The array is (re)allocated in the parent
124 * memory context.
126 if (visited == NULL) {
127 return LDB_ERR_OPERATIONS_ERROR;
128 } else if (*visited == NULL) {
129 *visited = talloc_array(mem_ctx, struct dsdb_dn *, 1);
130 if (*visited == NULL) {
131 talloc_free(tmp_ctx);
132 return LDB_ERR_OPERATIONS_ERROR;
134 (*visited)[0] = to_visit;
135 (*visited_count) = 1;
136 } else {
137 *visited = talloc_realloc(mem_ctx, *visited, struct dsdb_dn *,
138 (*visited_count) + 1);
139 if (*visited == NULL) {
140 talloc_free(tmp_ctx);
141 return LDB_ERR_OPERATIONS_ERROR;
143 (*visited)[(*visited_count)] = to_visit;
144 (*visited_count)++;
148 * steal to_visit into visited array context, as it has to live until
149 * the array is freed.
151 talloc_steal(*visited, to_visit);
154 * Iterate over the values of the attribute of the entry being
155 * visited (to_visit) and follow them, calling this function
156 * recursively.
157 * If the value is in the visited array, skip it.
158 * Otherwise, follow the link and visit it.
160 for (i=0; i<el->num_values; i++) {
161 struct dsdb_dn *next_to_visit;
162 bool skip = false;
164 next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
165 if (next_to_visit == NULL) {
166 talloc_free(tmp_ctx);
167 *matched = false;
168 return LDB_ERR_INVALID_DN_SYNTAX;
172 * If the value is already in the visited array, skip it.
173 * Note the last element of the array is ignored because it is
174 * the current entry DN.
176 for (j=0; j < (*visited_count) - 1; j++) {
177 struct dsdb_dn *visited_dn = (*visited)[j];
178 if (ldb_dn_compare(visited_dn->dn,
179 next_to_visit->dn) == 0) {
180 skip = true;
181 break;
184 if (skip) {
185 talloc_free(next_to_visit);
186 continue;
189 /* If the value is not in the visited array, evaluate it */
190 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
191 dn_to_match, dn_oid,
192 next_to_visit,
193 visited, visited_count,
194 matched);
195 if (ret != LDB_SUCCESS) {
196 talloc_free(tmp_ctx);
197 return ret;
199 if (*matched) {
200 talloc_free(tmp_ctx);
201 return LDB_SUCCESS;
205 talloc_free(tmp_ctx);
206 *matched = false;
207 return LDB_SUCCESS;
211 * This function parses the linked attribute value to match, whose syntax
212 * will be one of the different DN syntaxes, into a ldb_dn struct.
214 static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx,
215 struct ldb_context *ldb,
216 const char *attr,
217 const struct ldb_val *value_to_match,
218 struct dsdb_dn *current_object_dn,
219 bool *matched)
221 const struct dsdb_schema *schema;
222 const struct dsdb_attribute *schema_attr;
223 struct dsdb_dn *dn_to_match;
224 const char *dn_oid;
225 unsigned int count;
226 struct dsdb_dn **visited = NULL;
228 schema = dsdb_get_schema(ldb, mem_ctx);
229 if (schema == NULL) {
230 return LDB_ERR_OPERATIONS_ERROR;
233 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr);
234 if (schema_attr == NULL) {
235 return LDB_ERR_NO_SUCH_ATTRIBUTE;
238 /* This is the DN syntax of the attribute being matched */
239 dn_oid = schema_attr->syntax->ldap_oid;
242 * Build a ldb_dn struct holding the value to match, which is the
243 * value entered in the search filter
245 dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid);
246 if (dn_to_match == NULL) {
247 *matched = false;
248 return LDB_SUCCESS;
251 return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
252 dn_to_match, dn_oid,
253 current_object_dn,
254 &visited, &count, matched);
258 * This rule provides recursive search of a link attribute
260 * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
261 * This allows a search filter such as:
263 * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
265 * This searches not only the member attribute, but also any member
266 * attributes that point at an object with this member in them. All the
267 * various DN syntax types are supported, not just plain DNs.
270 static int ldb_comparator_trans(struct ldb_context *ldb,
271 const char *oid,
272 const struct ldb_message *msg,
273 const char *attribute_to_match,
274 const struct ldb_val *value_to_match,
275 bool *matched)
277 const struct dsdb_schema *schema;
278 const struct dsdb_attribute *schema_attr;
279 struct ldb_dn *msg_dn;
280 struct dsdb_dn *dsdb_msg_dn;
281 TALLOC_CTX *tmp_ctx;
282 int ret;
284 tmp_ctx = talloc_new(ldb);
285 if (tmp_ctx == NULL) {
286 return LDB_ERR_OPERATIONS_ERROR;
290 * If the target attribute to match is not a linked attribute, then
291 * the filter evaluates to undefined
293 schema = dsdb_get_schema(ldb, tmp_ctx);
294 if (schema == NULL) {
295 talloc_free(tmp_ctx);
296 return LDB_ERR_OPERATIONS_ERROR;
299 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
300 if (schema_attr == NULL) {
301 talloc_free(tmp_ctx);
302 return LDB_ERR_NO_SUCH_ATTRIBUTE;
306 * This extended match filter is only valid for linked attributes,
307 * following the MS definition (the schema attribute has a linkID
308 * defined). See dochelp request 114111212024789 on cifs-protocols
309 * mailing list.
311 if (schema_attr->linkID == 0) {
312 *matched = false;
313 talloc_free(tmp_ctx);
314 return LDB_SUCCESS;
317 /* Duplicate original msg dn as the msg must not be modified */
318 msg_dn = ldb_dn_copy(tmp_ctx, msg->dn);
319 if (msg_dn == NULL) {
320 talloc_free(tmp_ctx);
321 return LDB_ERR_OPERATIONS_ERROR;
325 * Build a dsdb dn from the message copied DN, which should be a plain
326 * DN syntax.
328 dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
329 LDB_SYNTAX_DN);
330 if (dsdb_msg_dn == NULL) {
331 *matched = false;
332 return LDB_ERR_INVALID_DN_SYNTAX;
335 ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
336 attribute_to_match,
337 value_to_match,
338 dsdb_msg_dn, matched);
339 talloc_free(tmp_ctx);
340 return ret;
345 * This rule provides match of a dns object with expired records.
347 * This allows a search filter such as:
349 * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=3694869
351 * where the value is a number of hours since the start of 1601.
353 * This allows the caller to find records that should become a DNS
354 * tomestone, despite that information being deep within an NDR packed
355 * object
357 static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb,
358 const char *oid,
359 const struct ldb_message *msg,
360 const char *attribute_to_match,
361 const struct ldb_val *value_to_match,
362 bool *matched)
364 TALLOC_CTX *tmp_ctx;
365 unsigned int i;
366 struct ldb_message_element *el = NULL;
367 struct auth_session_info *session_info = NULL;
368 uint64_t tombstone_time;
369 struct dnsp_DnssrvRpcRecord *rec = NULL;
370 enum ndr_err_code err;
371 *matched = false;
373 /* Needs to be dnsRecord, no match otherwise */
374 if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) {
375 return LDB_SUCCESS;
378 el = ldb_msg_find_element(msg, attribute_to_match);
379 if (el == NULL) {
380 return LDB_SUCCESS;
383 if (ldb_msg_element_is_inaccessible(el)) {
384 *matched = false;
385 return LDB_SUCCESS;
388 session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
389 struct auth_session_info);
390 if (session_info == NULL) {
391 return ldb_oom(ldb);
393 if (security_session_user_level(session_info, NULL)
394 != SECURITY_SYSTEM) {
396 DBG_ERR("unauthorised access\n");
397 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
400 /* We only expect uint32_t <= 10 digits */
401 if (value_to_match->length >= 12) {
402 DBG_ERR("Invalid timestamp passed\n");
403 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
404 } else {
405 int error = 0;
406 char s[12];
408 memcpy(s, value_to_match->data, value_to_match->length);
409 s[value_to_match->length] = 0;
410 if (s[0] == '\0') {
411 DBG_ERR("Empty timestamp passed\n");
412 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
414 tombstone_time = smb_strtoull(s,
415 NULL,
417 &error,
418 SMB_STR_FULL_STR_CONV);
419 if (error != 0) {
420 DBG_ERR("Invalid timestamp string passed\n");
421 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
425 tmp_ctx = talloc_new(ldb);
426 if (tmp_ctx == NULL) {
427 return ldb_oom(ldb);
430 for (i = 0; i < el->num_values; i++) {
431 rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord);
432 if (rec == NULL) {
433 TALLOC_FREE(tmp_ctx);
434 return ldb_oom(ldb);
436 err = ndr_pull_struct_blob(
437 &(el->values[i]),
438 tmp_ctx,
439 rec,
440 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
441 if (!NDR_ERR_CODE_IS_SUCCESS(err)){
442 DBG_ERR("Failed to pull dns rec blob.\n");
443 TALLOC_FREE(tmp_ctx);
444 return LDB_ERR_OPERATIONS_ERROR;
447 if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) {
448 TALLOC_FREE(rec);
449 continue;
452 if (rec->wType == DNS_TYPE_TOMBSTONE) {
453 TALLOC_FREE(rec);
454 continue;
456 if (rec->dwTimeStamp == 0) {
457 TALLOC_FREE(rec);
458 continue;
460 if (rec->dwTimeStamp > tombstone_time) {
461 TALLOC_FREE(rec);
462 continue;
465 *matched = true;
466 break;
469 TALLOC_FREE(tmp_ctx);
470 return LDB_SUCCESS;
475 * This rule provides match of a link attribute against a 'should be expunged' criteria
477 * This allows a search filter such as:
479 * member:1.3.6.1.4.1.7165.4.5.2:=131139216000000000
481 * This searches the member attribute, but also any member attributes
482 * that are deleted and should be expunged after the specified NTTIME
483 * time.
486 static int dsdb_match_for_expunge(struct ldb_context *ldb,
487 const char *oid,
488 const struct ldb_message *msg,
489 const char *attribute_to_match,
490 const struct ldb_val *value_to_match,
491 bool *matched)
493 const struct dsdb_schema *schema;
494 const struct dsdb_attribute *schema_attr;
495 TALLOC_CTX *tmp_ctx;
496 unsigned int i;
497 struct ldb_message_element *el;
498 struct auth_session_info *session_info;
499 uint64_t tombstone_time;
500 *matched = false;
502 el = ldb_msg_find_element(msg, attribute_to_match);
503 if (el == NULL) {
504 return LDB_SUCCESS;
507 if (ldb_msg_element_is_inaccessible(el)) {
508 *matched = false;
509 return LDB_SUCCESS;
512 session_info
513 = talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO),
514 struct auth_session_info);
515 if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
516 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
520 * If the target attribute to match is not a linked attribute, then
521 * the filter evaluates to undefined
523 schema = dsdb_get_schema(ldb, NULL);
524 if (schema == NULL) {
525 return LDB_ERR_OPERATIONS_ERROR;
528 /* TODO this is O(log n) per attribute */
529 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
530 if (schema_attr == NULL) {
531 return LDB_ERR_NO_SUCH_ATTRIBUTE;
535 * This extended match filter is only valid for forward linked attributes.
537 if (schema_attr->linkID == 0 || (schema_attr->linkID & 1) == 1) {
538 return LDB_ERR_NO_SUCH_ATTRIBUTE;
541 /* Just check we don't allow the caller to fill our stack */
542 if (value_to_match->length >=64) {
543 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
544 } else {
545 int error = 0;
546 char s[value_to_match->length+1];
548 memcpy(s, value_to_match->data, value_to_match->length);
549 s[value_to_match->length] = 0;
550 if (s[0] == '\0' || s[0] == '-') {
551 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
553 tombstone_time = smb_strtoull(s,
554 NULL,
556 &error,
557 SMB_STR_FULL_STR_CONV);
558 if (error != 0) {
559 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
563 tmp_ctx = talloc_new(ldb);
564 if (tmp_ctx == NULL) {
565 return LDB_ERR_OPERATIONS_ERROR;
568 for (i = 0; i < el->num_values; i++) {
569 NTSTATUS status;
570 struct dsdb_dn *dn;
571 uint64_t rmd_changetime;
572 if (dsdb_dn_is_deleted_val(&el->values[i]) == false) {
573 continue;
576 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i],
577 schema_attr->syntax->ldap_oid);
578 if (dn == NULL) {
579 DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el->name));
580 continue;
583 status = dsdb_get_extended_dn_uint64(dn->dn, &rmd_changetime,
584 "RMD_CHANGETIME");
585 if (!NT_STATUS_IS_OK(status)) {
586 DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n"));
587 continue;
590 if (rmd_changetime > tombstone_time) {
591 continue;
594 *matched = true;
595 break;
597 talloc_free(tmp_ctx);
598 return LDB_SUCCESS;
602 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
604 struct ldb_extended_match_rule *transitive_eval = NULL,
605 *match_for_expunge = NULL,
606 *match_for_dns_to_tombstone_time = NULL;
607 int ret;
609 transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
610 transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL;
611 transitive_eval->callback = ldb_comparator_trans;
612 ret = ldb_register_extended_match_rule(ldb, transitive_eval);
613 if (ret != LDB_SUCCESS) {
614 talloc_free(transitive_eval);
615 return ret;
618 match_for_expunge = talloc_zero(ldb, struct ldb_extended_match_rule);
619 match_for_expunge->oid = DSDB_MATCH_FOR_EXPUNGE;
620 match_for_expunge->callback = dsdb_match_for_expunge;
621 ret = ldb_register_extended_match_rule(ldb, match_for_expunge);
622 if (ret != LDB_SUCCESS) {
623 talloc_free(match_for_expunge);
624 return ret;
627 match_for_dns_to_tombstone_time = talloc_zero(
628 ldb,
629 struct ldb_extended_match_rule);
630 match_for_dns_to_tombstone_time->oid = DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME;
631 match_for_dns_to_tombstone_time->callback
632 = dsdb_match_for_dns_to_tombstone_time;
633 ret = ldb_register_extended_match_rule(ldb,
634 match_for_dns_to_tombstone_time);
635 if (ret != LDB_SUCCESS) {
636 TALLOC_FREE(match_for_dns_to_tombstone_time);
637 return ret;
640 return LDB_SUCCESS;