torture: smbtorture test case to verify Compound related handling
[Samba.git] / lib / ldb-samba / ldb_matching_rules.c
blob73d957df3d9da3091fbc25f6b60be199e345bfe2
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, tmp_ctx, &res, to_visit->dn, attrs, 0);
71 if (ret != LDB_SUCCESS) {
72 talloc_free(tmp_ctx);
73 return ret;
75 if (res->count != 1) {
76 talloc_free(tmp_ctx);
77 return LDB_ERR_OPERATIONS_ERROR;
79 msg = res->msgs[0];
81 /* Fetch the attribute to match from the entry being visited */
82 el = ldb_msg_find_element(msg, attr);
83 if (el == NULL) {
84 /* This entry does not have the attribute to match */
85 talloc_free(tmp_ctx);
86 *matched = false;
87 return LDB_SUCCESS;
91 * If the value to match is present in the attribute values of the
92 * current entry being visited, set matched to true and return OK
94 for (i=0; i<el->num_values; i++) {
95 struct dsdb_dn *dn;
96 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
97 if (dn == NULL) {
98 talloc_free(tmp_ctx);
99 *matched = false;
100 return LDB_ERR_INVALID_DN_SYNTAX;
103 if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) {
104 talloc_free(tmp_ctx);
105 *matched = true;
106 return LDB_SUCCESS;
111 * If arrived here, the value to match is not in the values of the
112 * entry being visited. Add the entry being visited (to_visit)
113 * to the visited array. The array is (re)allocated in the parent
114 * memory context.
116 if (visited == NULL) {
117 return LDB_ERR_OPERATIONS_ERROR;
118 } else if (*visited == NULL) {
119 *visited = talloc_array(mem_ctx, struct dsdb_dn *, 1);
120 if (*visited == NULL) {
121 talloc_free(tmp_ctx);
122 return LDB_ERR_OPERATIONS_ERROR;
124 (*visited)[0] = to_visit;
125 (*visited_count) = 1;
126 } else {
127 *visited = talloc_realloc(mem_ctx, *visited, struct dsdb_dn *,
128 (*visited_count) + 1);
129 if (*visited == NULL) {
130 talloc_free(tmp_ctx);
131 return LDB_ERR_OPERATIONS_ERROR;
133 (*visited)[(*visited_count)] = to_visit;
134 (*visited_count)++;
138 * steal to_visit into visited array context, as it has to live until
139 * the array is freed.
141 talloc_steal(*visited, to_visit);
144 * Iterate over the values of the attribute of the entry being
145 * visited (to_visit) and follow them, calling this function
146 * recursively.
147 * If the value is in the visited array, skip it.
148 * Otherwise, follow the link and visit it.
150 for (i=0; i<el->num_values; i++) {
151 struct dsdb_dn *next_to_visit;
152 bool skip = false;
154 next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
155 if (next_to_visit == NULL) {
156 talloc_free(tmp_ctx);
157 *matched = false;
158 return LDB_ERR_INVALID_DN_SYNTAX;
162 * If the value is already in the visited array, skip it.
163 * Note the last element of the array is ignored because it is
164 * the current entry DN.
166 for (j=0; j < (*visited_count) - 1; j++) {
167 struct dsdb_dn *visited_dn = (*visited)[j];
168 if (ldb_dn_compare(visited_dn->dn,
169 next_to_visit->dn) == 0) {
170 skip = true;
171 break;
174 if (skip) {
175 talloc_free(next_to_visit);
176 continue;
179 /* If the value is not in the visited array, evaluate it */
180 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
181 dn_to_match, dn_oid,
182 next_to_visit,
183 visited, visited_count,
184 matched);
185 if (ret != LDB_SUCCESS) {
186 talloc_free(tmp_ctx);
187 return ret;
189 if (*matched) {
190 talloc_free(tmp_ctx);
191 return LDB_SUCCESS;
195 talloc_free(tmp_ctx);
196 *matched = false;
197 return LDB_SUCCESS;
201 * This function parses the linked attribute value to match, whose syntax
202 * will be one of the different DN syntaxes, into a ldb_dn struct.
204 static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx,
205 struct ldb_context *ldb,
206 const char *attr,
207 const struct ldb_val *value_to_match,
208 struct dsdb_dn *current_object_dn,
209 bool *matched)
211 const struct dsdb_schema *schema;
212 const struct dsdb_attribute *schema_attr;
213 struct dsdb_dn *dn_to_match;
214 const char *dn_oid;
215 unsigned int count;
216 struct dsdb_dn **visited = NULL;
218 schema = dsdb_get_schema(ldb, mem_ctx);
219 if (schema == NULL) {
220 return LDB_ERR_OPERATIONS_ERROR;
223 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr);
224 if (schema_attr == NULL) {
225 return LDB_ERR_NO_SUCH_ATTRIBUTE;
228 /* This is the DN syntax of the attribute being matched */
229 dn_oid = schema_attr->syntax->ldap_oid;
232 * Build a ldb_dn struct holding the value to match, which is the
233 * value entered in the search filter
235 dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid);
236 if (dn_to_match == NULL) {
237 *matched = false;
238 return LDB_SUCCESS;
241 return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
242 dn_to_match, dn_oid,
243 current_object_dn,
244 &visited, &count, matched);
248 * This rule provides recursive search of a link attribute
250 * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
251 * This allows a search filter such as:
253 * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
255 * This searches not only the member attribute, but also any member
256 * attributes that point at an object with this member in them. All the
257 * various DN syntax types are supported, not just plain DNs.
260 static int ldb_comparator_trans(struct ldb_context *ldb,
261 const char *oid,
262 const struct ldb_message *msg,
263 const char *attribute_to_match,
264 const struct ldb_val *value_to_match,
265 bool *matched)
267 const struct dsdb_schema *schema;
268 const struct dsdb_attribute *schema_attr;
269 struct ldb_dn *msg_dn;
270 struct dsdb_dn *dsdb_msg_dn;
271 TALLOC_CTX *tmp_ctx;
272 int ret;
274 tmp_ctx = talloc_new(ldb);
275 if (tmp_ctx == NULL) {
276 return LDB_ERR_OPERATIONS_ERROR;
280 * If the target attribute to match is not a linked attribute, then
281 * the filter evaluates to undefined
283 schema = dsdb_get_schema(ldb, tmp_ctx);
284 if (schema == NULL) {
285 talloc_free(tmp_ctx);
286 return LDB_ERR_OPERATIONS_ERROR;
289 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
290 if (schema_attr == NULL) {
291 talloc_free(tmp_ctx);
292 return LDB_ERR_NO_SUCH_ATTRIBUTE;
296 * This extended match filter is only valid for linked attributes,
297 * following the MS definition (the schema attribute has a linkID
298 * defined). See dochelp request 114111212024789 on cifs-protocols
299 * mailing list.
301 if (schema_attr->linkID == 0) {
302 *matched = false;
303 talloc_free(tmp_ctx);
304 return LDB_SUCCESS;
307 /* Duplicate original msg dn as the msg must not be modified */
308 msg_dn = ldb_dn_copy(tmp_ctx, msg->dn);
309 if (msg_dn == NULL) {
310 talloc_free(tmp_ctx);
311 return LDB_ERR_OPERATIONS_ERROR;
315 * Build a dsdb dn from the message copied DN, which should be a plain
316 * DN syntax.
318 dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
319 LDB_SYNTAX_DN);
320 if (dsdb_msg_dn == NULL) {
321 *matched = false;
322 return LDB_ERR_INVALID_DN_SYNTAX;
325 ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
326 attribute_to_match,
327 value_to_match,
328 dsdb_msg_dn, matched);
329 talloc_free(tmp_ctx);
330 return ret;
335 * This rule provides match of a dns object with expired records.
337 * This allows a search filter such as:
339 * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=131139216000000000
341 * This allows the caller to find records that should become a DNS
342 * tomestone, despite that information being deep within an NDR packed
343 * object
345 static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb,
346 const char *oid,
347 const struct ldb_message *msg,
348 const char *attribute_to_match,
349 const struct ldb_val *value_to_match,
350 bool *matched)
352 TALLOC_CTX *tmp_ctx;
353 unsigned int i;
354 struct ldb_message_element *el = NULL;
355 struct auth_session_info *session_info = NULL;
356 uint64_t tombstone_time;
357 struct dnsp_DnssrvRpcRecord *rec = NULL;
358 enum ndr_err_code err;
359 *matched = false;
361 /* Needs to be dnsRecord, no match otherwise */
362 if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) {
363 return LDB_SUCCESS;
366 el = ldb_msg_find_element(msg, attribute_to_match);
367 if (el == NULL) {
368 return LDB_SUCCESS;
371 session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
372 struct auth_session_info);
373 if (session_info == NULL) {
374 return ldb_oom(ldb);
376 if (security_session_user_level(session_info, NULL)
377 != SECURITY_SYSTEM) {
379 DBG_ERR("unauthorised access\n");
380 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
383 /* Just check we don't allow the caller to fill our stack */
384 if (value_to_match->length >= 64) {
385 DBG_ERR("Invalid timestamp passed\n");
386 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
387 } else {
388 int error = 0;
389 char s[65];
391 memcpy(s, value_to_match->data, value_to_match->length);
392 s[value_to_match->length] = 0;
393 if (s[0] == '\0') {
394 DBG_ERR("Empty timestamp passed\n");
395 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
397 tombstone_time = smb_strtoull(s,
398 NULL,
400 &error,
401 SMB_STR_FULL_STR_CONV);
402 if (error != 0) {
403 DBG_ERR("Invalid timestamp string passed\n");
404 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
408 tmp_ctx = talloc_new(ldb);
409 if (tmp_ctx == NULL) {
410 return ldb_oom(ldb);
413 for (i = 0; i < el->num_values; i++) {
414 rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord);
415 if (rec == NULL) {
416 TALLOC_FREE(tmp_ctx);
417 return ldb_oom(ldb);
419 err = ndr_pull_struct_blob(
420 &(el->values[i]),
421 tmp_ctx,
422 rec,
423 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
424 if (!NDR_ERR_CODE_IS_SUCCESS(err)){
425 DBG_ERR("Failed to pull dns rec blob.\n");
426 TALLOC_FREE(tmp_ctx);
427 return LDB_ERR_OPERATIONS_ERROR;
430 if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) {
431 TALLOC_FREE(rec);
432 continue;
435 if (rec->wType == DNS_TYPE_TOMBSTONE) {
436 TALLOC_FREE(rec);
437 continue;
439 if (rec->dwTimeStamp == 0) {
440 TALLOC_FREE(rec);
441 continue;
443 if (rec->dwTimeStamp > tombstone_time) {
444 TALLOC_FREE(rec);
445 continue;
448 *matched = true;
449 break;
452 TALLOC_FREE(tmp_ctx);
453 return LDB_SUCCESS;
458 * This rule provides match of a link attribute against a 'should be expunged' criteria
460 * This allows a search filter such as:
462 * member:1.3.6.1.4.1.7165.4.5.2:=131139216000000000
464 * This searches the member attribute, but also any member attributes
465 * that are deleted and should be expunged after the specified NTTIME
466 * time.
469 static int dsdb_match_for_expunge(struct ldb_context *ldb,
470 const char *oid,
471 const struct ldb_message *msg,
472 const char *attribute_to_match,
473 const struct ldb_val *value_to_match,
474 bool *matched)
476 const struct dsdb_schema *schema;
477 const struct dsdb_attribute *schema_attr;
478 TALLOC_CTX *tmp_ctx;
479 unsigned int i;
480 struct ldb_message_element *el;
481 struct auth_session_info *session_info;
482 uint64_t tombstone_time;
483 *matched = false;
485 el = ldb_msg_find_element(msg, attribute_to_match);
486 if (el == NULL) {
487 return LDB_SUCCESS;
490 session_info
491 = talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO),
492 struct auth_session_info);
493 if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
494 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
498 * If the target attribute to match is not a linked attribute, then
499 * the filter evaluates to undefined
501 schema = dsdb_get_schema(ldb, NULL);
502 if (schema == NULL) {
503 return LDB_ERR_OPERATIONS_ERROR;
506 /* TODO this is O(log n) per attribute */
507 schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
508 if (schema_attr == NULL) {
509 return LDB_ERR_NO_SUCH_ATTRIBUTE;
513 * This extended match filter is only valid for forward linked attributes.
515 if (schema_attr->linkID == 0 || (schema_attr->linkID & 1) == 1) {
516 return LDB_ERR_NO_SUCH_ATTRIBUTE;
519 /* Just check we don't allow the caller to fill our stack */
520 if (value_to_match->length >=64) {
521 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
522 } else {
523 int error = 0;
524 char s[value_to_match->length+1];
526 memcpy(s, value_to_match->data, value_to_match->length);
527 s[value_to_match->length] = 0;
528 if (s[0] == '\0' || s[0] == '-') {
529 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
531 tombstone_time = smb_strtoull(s,
532 NULL,
534 &error,
535 SMB_STR_FULL_STR_CONV);
536 if (error != 0) {
537 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
541 tmp_ctx = talloc_new(ldb);
542 if (tmp_ctx == NULL) {
543 return LDB_ERR_OPERATIONS_ERROR;
546 for (i = 0; i < el->num_values; i++) {
547 NTSTATUS status;
548 struct dsdb_dn *dn;
549 uint64_t rmd_changetime;
550 if (dsdb_dn_is_deleted_val(&el->values[i]) == false) {
551 continue;
554 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i],
555 schema_attr->syntax->ldap_oid);
556 if (dn == NULL) {
557 DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el->name));
558 continue;
561 status = dsdb_get_extended_dn_uint64(dn->dn, &rmd_changetime,
562 "RMD_CHANGETIME");
563 if (!NT_STATUS_IS_OK(status)) {
564 DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n"));
565 continue;
568 if (rmd_changetime > tombstone_time) {
569 continue;
572 *matched = true;
573 break;
575 talloc_free(tmp_ctx);
576 return LDB_SUCCESS;
580 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
582 struct ldb_extended_match_rule *transitive_eval = NULL,
583 *match_for_expunge = NULL,
584 *match_for_dns_to_tombstone_time = NULL;
585 int ret;
587 transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
588 transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL;
589 transitive_eval->callback = ldb_comparator_trans;
590 ret = ldb_register_extended_match_rule(ldb, transitive_eval);
591 if (ret != LDB_SUCCESS) {
592 talloc_free(transitive_eval);
593 return ret;
596 match_for_expunge = talloc_zero(ldb, struct ldb_extended_match_rule);
597 match_for_expunge->oid = DSDB_MATCH_FOR_EXPUNGE;
598 match_for_expunge->callback = dsdb_match_for_expunge;
599 ret = ldb_register_extended_match_rule(ldb, match_for_expunge);
600 if (ret != LDB_SUCCESS) {
601 talloc_free(match_for_expunge);
602 return ret;
605 match_for_dns_to_tombstone_time = talloc_zero(
606 ldb,
607 struct ldb_extended_match_rule);
608 match_for_dns_to_tombstone_time->oid = DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME;
609 match_for_dns_to_tombstone_time->callback
610 = dsdb_match_for_dns_to_tombstone_time;
611 ret = ldb_register_extended_match_rule(ldb,
612 match_for_dns_to_tombstone_time);
613 if (ret != LDB_SUCCESS) {
614 TALLOC_FREE(match_for_dns_to_tombstone_time);
615 return ret;
618 return LDB_SUCCESS;