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/>.
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 static int ldb_eval_transitive_filter_helper(TALLOC_CTX
*mem_ctx
,
33 struct ldb_context
*ldb
,
35 const struct dsdb_dn
*dn_to_match
,
37 struct dsdb_dn
*to_visit
,
38 struct dsdb_dn
***visited
,
39 unsigned int *visited_count
,
44 struct ldb_result
*res
;
45 struct ldb_message
*msg
;
46 struct ldb_message_element
*el
;
47 const char *attrs
[] = { attr
, NULL
};
49 tmp_ctx
= talloc_new(mem_ctx
);
50 if (tmp_ctx
== NULL
) {
51 return LDB_ERR_OPERATIONS_ERROR
;
55 * Fetch the entry to_visit
57 * NOTE: This is a new LDB search from the TOP of the module
58 * stack. This means that this search runs the whole stack
61 * This may seem to be in-efficient, but it is also the only
62 * way to ensure that the ACLs for this search are applied
65 * Note also that we don't have the original request
66 * here, so we can not apply controls or timeouts here.
68 ret
= dsdb_search_dn(ldb
, tmp_ctx
, &res
, to_visit
->dn
, attrs
, 0);
69 if (ret
!= LDB_SUCCESS
) {
73 if (res
->count
!= 1) {
75 return LDB_ERR_OPERATIONS_ERROR
;
79 /* Fetch the attribute to match from the entry being visited */
80 el
= ldb_msg_find_element(msg
, attr
);
82 /* This entry does not have the attribute to match */
89 * If the value to match is present in the attribute values of the
90 * current entry being visited, set matched to true and return OK
92 for (i
=0; i
<el
->num_values
; i
++) {
94 dn
= dsdb_dn_parse(tmp_ctx
, ldb
, &el
->values
[i
], dn_oid
);
98 return LDB_ERR_INVALID_DN_SYNTAX
;
101 if (ldb_dn_compare(dn_to_match
->dn
, dn
->dn
) == 0) {
102 talloc_free(tmp_ctx
);
109 * If arrived here, the value to match is not in the values of the
110 * entry being visited. Add the entry being visited (to_visit)
111 * to the visited array. The array is (re)allocated in the parent
114 if (visited
== NULL
) {
115 return LDB_ERR_OPERATIONS_ERROR
;
116 } else if (*visited
== NULL
) {
117 *visited
= talloc_array(mem_ctx
, struct dsdb_dn
*, 1);
118 if (*visited
== NULL
) {
119 talloc_free(tmp_ctx
);
120 return LDB_ERR_OPERATIONS_ERROR
;
122 (*visited
)[0] = to_visit
;
123 (*visited_count
) = 1;
125 *visited
= talloc_realloc(mem_ctx
, *visited
, struct dsdb_dn
*,
126 (*visited_count
) + 1);
127 if (*visited
== NULL
) {
128 talloc_free(tmp_ctx
);
129 return LDB_ERR_OPERATIONS_ERROR
;
131 (*visited
)[(*visited_count
)] = to_visit
;
136 * steal to_visit into visited array context, as it has to live until
137 * the array is freed.
139 talloc_steal(*visited
, to_visit
);
142 * Iterate over the values of the attribute of the entry being
143 * visited (to_visit) and follow them, calling this function
145 * If the value is in the visited array, skip it.
146 * Otherwise, follow the link and visit it.
148 for (i
=0; i
<el
->num_values
; i
++) {
149 struct dsdb_dn
*next_to_visit
;
152 next_to_visit
= dsdb_dn_parse(tmp_ctx
, ldb
, &el
->values
[i
], dn_oid
);
153 if (next_to_visit
== NULL
) {
154 talloc_free(tmp_ctx
);
156 return LDB_ERR_INVALID_DN_SYNTAX
;
160 * If the value is already in the visited array, skip it.
161 * Note the last element of the array is ignored because it is
162 * the current entry DN.
164 for (j
=0; j
< (*visited_count
) - 1; j
++) {
165 struct dsdb_dn
*visited_dn
= (*visited
)[j
];
166 if (ldb_dn_compare(visited_dn
->dn
,
167 next_to_visit
->dn
) == 0) {
173 talloc_free(next_to_visit
);
177 /* If the value is not in the visited array, evaluate it */
178 ret
= ldb_eval_transitive_filter_helper(tmp_ctx
, ldb
, attr
,
181 visited
, visited_count
,
183 if (ret
!= LDB_SUCCESS
) {
184 talloc_free(tmp_ctx
);
188 talloc_free(tmp_ctx
);
193 talloc_free(tmp_ctx
);
199 * This function parses the linked attribute value to match, whose syntax
200 * will be one of the different DN syntaxes, into a ldb_dn struct.
202 static int ldb_eval_transitive_filter(TALLOC_CTX
*mem_ctx
,
203 struct ldb_context
*ldb
,
205 const struct ldb_val
*value_to_match
,
206 struct dsdb_dn
*current_object_dn
,
209 const struct dsdb_schema
*schema
;
210 const struct dsdb_attribute
*schema_attr
;
211 struct dsdb_dn
*dn_to_match
;
214 struct dsdb_dn
**visited
= NULL
;
216 schema
= dsdb_get_schema(ldb
, mem_ctx
);
217 if (schema
== NULL
) {
218 return LDB_ERR_OPERATIONS_ERROR
;
221 schema_attr
= dsdb_attribute_by_lDAPDisplayName(schema
, attr
);
222 if (schema_attr
== NULL
) {
223 return LDB_ERR_NO_SUCH_ATTRIBUTE
;
226 /* This is the DN syntax of the attribute being matched */
227 dn_oid
= schema_attr
->syntax
->ldap_oid
;
230 * Build a ldb_dn struct holding the value to match, which is the
231 * value entered in the search filter
233 dn_to_match
= dsdb_dn_parse(mem_ctx
, ldb
, value_to_match
, dn_oid
);
234 if (dn_to_match
== NULL
) {
239 return ldb_eval_transitive_filter_helper(mem_ctx
, ldb
, attr
,
242 &visited
, &count
, matched
);
246 * This rule provides recursive search of a link attribute
248 * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
249 * This allows a search filter such as:
251 * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
253 * This searches not only the member attribute, but also any member
254 * attributes that point at an object with this member in them. All the
255 * various DN syntax types are supported, not just plain DNs.
258 static int ldb_comparator_trans(struct ldb_context
*ldb
,
260 const struct ldb_message
*msg
,
261 const char *attribute_to_match
,
262 const struct ldb_val
*value_to_match
,
265 const struct dsdb_schema
*schema
;
266 const struct dsdb_attribute
*schema_attr
;
267 struct ldb_dn
*msg_dn
;
268 struct dsdb_dn
*dsdb_msg_dn
;
272 tmp_ctx
= talloc_new(ldb
);
273 if (tmp_ctx
== NULL
) {
274 return LDB_ERR_OPERATIONS_ERROR
;
278 * If the target attribute to match is not a linked attribute, then
279 * the filter evaluates to undefined
281 schema
= dsdb_get_schema(ldb
, tmp_ctx
);
282 if (schema
== NULL
) {
283 talloc_free(tmp_ctx
);
284 return LDB_ERR_OPERATIONS_ERROR
;
287 schema_attr
= dsdb_attribute_by_lDAPDisplayName(schema
, attribute_to_match
);
288 if (schema_attr
== NULL
) {
289 talloc_free(tmp_ctx
);
290 return LDB_ERR_NO_SUCH_ATTRIBUTE
;
294 * This extended match filter is only valid for linked attributes,
295 * following the MS definition (the schema attribute has a linkID
296 * defined). See dochelp request 114111212024789 on cifs-protocols
299 if (schema_attr
->linkID
== 0) {
301 talloc_free(tmp_ctx
);
305 /* Duplicate original msg dn as the msg must not be modified */
306 msg_dn
= ldb_dn_copy(tmp_ctx
, msg
->dn
);
307 if (msg_dn
== NULL
) {
308 talloc_free(tmp_ctx
);
309 return LDB_ERR_OPERATIONS_ERROR
;
313 * Build a dsdb dn from the message copied DN, which should be a plain
316 dsdb_msg_dn
= dsdb_dn_construct(tmp_ctx
, msg_dn
, data_blob_null
,
318 if (dsdb_msg_dn
== NULL
) {
320 return LDB_ERR_INVALID_DN_SYNTAX
;
323 ret
= ldb_eval_transitive_filter(tmp_ctx
, ldb
,
326 dsdb_msg_dn
, matched
);
327 talloc_free(tmp_ctx
);
333 * This rule provides match of a dns object with expired records.
335 * This allows a search filter such as:
337 * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=131139216000000000
339 * This allows the caller to find records that should become a DNS
340 * tomestone, despite that information being deep within an NDR packed
343 static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context
*ldb
,
345 const struct ldb_message
*msg
,
346 const char *attribute_to_match
,
347 const struct ldb_val
*value_to_match
,
352 struct ldb_message_element
*el
= NULL
;
353 struct auth_session_info
*session_info
= NULL
;
354 uint64_t tombstone_time
;
355 struct dnsp_DnssrvRpcRecord
*rec
= NULL
;
356 enum ndr_err_code err
;
359 /* Needs to be dnsRecord, no match otherwise */
360 if (ldb_attr_cmp(attribute_to_match
, "dnsRecord") != 0) {
364 el
= ldb_msg_find_element(msg
, attribute_to_match
);
369 session_info
= talloc_get_type(ldb_get_opaque(ldb
, "sessionInfo"),
370 struct auth_session_info
);
371 if (session_info
== NULL
) {
374 if (security_session_user_level(session_info
, NULL
)
375 != SECURITY_SYSTEM
) {
377 DBG_ERR("unauthorised access\n");
378 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS
;
381 /* Just check we don't allow the caller to fill our stack */
382 if (value_to_match
->length
>= 64) {
383 DBG_ERR("Invalid timestamp passed\n");
384 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
387 char s
[value_to_match
->length
+1];
389 memcpy(s
, value_to_match
->data
, value_to_match
->length
);
390 s
[value_to_match
->length
] = 0;
391 if (s
[0] == '\0' || s
[0] == '-') {
392 DBG_ERR("Empty timestamp passed\n");
393 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
395 tombstone_time
= smb_strtoull(s
,
399 SMB_STR_FULL_STR_CONV
);
401 DBG_ERR("Invalid timestamp string passed\n");
402 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
406 tmp_ctx
= talloc_new(ldb
);
407 if (tmp_ctx
== NULL
) {
411 for (i
= 0; i
< el
->num_values
; i
++) {
412 rec
= talloc_zero(tmp_ctx
, struct dnsp_DnssrvRpcRecord
);
414 TALLOC_FREE(tmp_ctx
);
417 err
= ndr_pull_struct_blob(
421 (ndr_pull_flags_fn_t
)ndr_pull_dnsp_DnssrvRpcRecord
);
422 if (!NDR_ERR_CODE_IS_SUCCESS(err
)){
423 DBG_ERR("Failed to pull dns rec blob.\n");
424 TALLOC_FREE(tmp_ctx
);
425 return LDB_ERR_OPERATIONS_ERROR
;
428 if (rec
->wType
== DNS_TYPE_SOA
|| rec
->wType
== DNS_TYPE_NS
) {
429 TALLOC_FREE(tmp_ctx
);
433 if (rec
->wType
== DNS_TYPE_TOMBSTONE
) {
434 TALLOC_FREE(tmp_ctx
);
437 if (rec
->dwTimeStamp
== 0) {
438 TALLOC_FREE(tmp_ctx
);
441 if (rec
->dwTimeStamp
> tombstone_time
) {
442 TALLOC_FREE(tmp_ctx
);
450 TALLOC_FREE(tmp_ctx
);
456 * This rule provides match of a link attribute against a 'should be expunged' criteria
458 * This allows a search filter such as:
460 * member:1.3.6.1.4.1.7165.4.5.2:=131139216000000000
462 * This searches the member attribute, but also any member attributes
463 * that are deleted and should be expunged after the specified NTTIME
467 static int dsdb_match_for_expunge(struct ldb_context
*ldb
,
469 const struct ldb_message
*msg
,
470 const char *attribute_to_match
,
471 const struct ldb_val
*value_to_match
,
474 const struct dsdb_schema
*schema
;
475 const struct dsdb_attribute
*schema_attr
;
478 struct ldb_message_element
*el
;
479 struct auth_session_info
*session_info
;
480 uint64_t tombstone_time
;
483 el
= ldb_msg_find_element(msg
, attribute_to_match
);
489 = talloc_get_type(ldb_get_opaque(ldb
, DSDB_SESSION_INFO
),
490 struct auth_session_info
);
491 if (security_session_user_level(session_info
, NULL
) != SECURITY_SYSTEM
) {
492 return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS
;
496 * If the target attribute to match is not a linked attribute, then
497 * the filter evaluates to undefined
499 schema
= dsdb_get_schema(ldb
, NULL
);
500 if (schema
== NULL
) {
501 return LDB_ERR_OPERATIONS_ERROR
;
504 /* TODO this is O(log n) per attribute */
505 schema_attr
= dsdb_attribute_by_lDAPDisplayName(schema
, attribute_to_match
);
506 if (schema_attr
== NULL
) {
507 return LDB_ERR_NO_SUCH_ATTRIBUTE
;
511 * This extended match filter is only valid for forward linked attributes.
513 if (schema_attr
->linkID
== 0 || (schema_attr
->linkID
& 1) == 1) {
514 return LDB_ERR_NO_SUCH_ATTRIBUTE
;
517 /* Just check we don't allow the caller to fill our stack */
518 if (value_to_match
->length
>=64) {
519 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
522 char s
[value_to_match
->length
+1];
524 memcpy(s
, value_to_match
->data
, value_to_match
->length
);
525 s
[value_to_match
->length
] = 0;
526 if (s
[0] == '\0' || s
[0] == '-') {
527 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
529 tombstone_time
= smb_strtoull(s
,
533 SMB_STR_FULL_STR_CONV
);
535 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
539 tmp_ctx
= talloc_new(ldb
);
540 if (tmp_ctx
== NULL
) {
541 return LDB_ERR_OPERATIONS_ERROR
;
544 for (i
= 0; i
< el
->num_values
; i
++) {
547 uint64_t rmd_changetime
;
548 if (dsdb_dn_is_deleted_val(&el
->values
[i
]) == false) {
552 dn
= dsdb_dn_parse(tmp_ctx
, ldb
, &el
->values
[i
],
553 schema_attr
->syntax
->ldap_oid
);
555 DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el
->name
));
559 status
= dsdb_get_extended_dn_uint64(dn
->dn
, &rmd_changetime
,
561 if (!NT_STATUS_IS_OK(status
)) {
562 DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n"));
566 if (rmd_changetime
> tombstone_time
) {
573 talloc_free(tmp_ctx
);
578 int ldb_register_samba_matching_rules(struct ldb_context
*ldb
)
580 struct ldb_extended_match_rule
*transitive_eval
= NULL
,
581 *match_for_expunge
= NULL
,
582 *match_for_dns_to_tombstone_time
= NULL
;
585 transitive_eval
= talloc_zero(ldb
, struct ldb_extended_match_rule
);
586 transitive_eval
->oid
= SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL
;
587 transitive_eval
->callback
= ldb_comparator_trans
;
588 ret
= ldb_register_extended_match_rule(ldb
, transitive_eval
);
589 if (ret
!= LDB_SUCCESS
) {
590 talloc_free(transitive_eval
);
594 match_for_expunge
= talloc_zero(ldb
, struct ldb_extended_match_rule
);
595 match_for_expunge
->oid
= DSDB_MATCH_FOR_EXPUNGE
;
596 match_for_expunge
->callback
= dsdb_match_for_expunge
;
597 ret
= ldb_register_extended_match_rule(ldb
, match_for_expunge
);
598 if (ret
!= LDB_SUCCESS
) {
599 talloc_free(match_for_expunge
);
603 match_for_dns_to_tombstone_time
= talloc_zero(
605 struct ldb_extended_match_rule
);
606 match_for_dns_to_tombstone_time
->oid
= DSDB_MATCH_FOR_DNS_TO_TOMBSTONE_TIME
;
607 match_for_dns_to_tombstone_time
->callback
608 = dsdb_match_for_dns_to_tombstone_time
;
609 ret
= ldb_register_extended_match_rule(ldb
,
610 match_for_dns_to_tombstone_time
);
611 if (ret
!= LDB_SUCCESS
) {
612 TALLOC_FREE(match_for_dns_to_tombstone_time
);