doc: add samba-tool contact rename command to samba-tool man page
[Samba.git] / lib / ldb-samba / ldb_matching_rules.c
blob13edb51daaaa5ecfb9c600acc480297f75bcdeb2
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 static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx,
33 struct ldb_context *ldb,
34 const char *attr,
35 const struct dsdb_dn *dn_to_match,
36 const char *dn_oid,
37 struct dsdb_dn *to_visit,
38 struct dsdb_dn ***visited,
39 unsigned int *visited_count,
40 bool *matched)
42 TALLOC_CTX *tmp_ctx;
43 int ret, i, j;
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
59 * from top to bottom.
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
63 * correctly.
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) {
70 talloc_free(tmp_ctx);
71 return ret;
73 if (res->count != 1) {
74 talloc_free(tmp_ctx);
75 return LDB_ERR_OPERATIONS_ERROR;
77 msg = res->msgs[0];
79 /* Fetch the attribute to match from the entry being visited */
80 el = ldb_msg_find_element(msg, attr);
81 if (el == NULL) {
82 /* This entry does not have the attribute to match */
83 talloc_free(tmp_ctx);
84 *matched = false;
85 return LDB_SUCCESS;
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++) {
93 struct dsdb_dn *dn;
94 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
95 if (dn == NULL) {
96 talloc_free(tmp_ctx);
97 *matched = false;
98 return LDB_ERR_INVALID_DN_SYNTAX;
101 if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) {
102 talloc_free(tmp_ctx);
103 *matched = true;
104 return LDB_SUCCESS;
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
112 * memory context.
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;
124 } else {
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;
132 (*visited_count)++;
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
144 * recursively.
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;
150 bool skip = false;
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);
155 *matched = false;
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) {
168 skip = true;
169 break;
172 if (skip) {
173 talloc_free(next_to_visit);
174 continue;
177 /* If the value is not in the visited array, evaluate it */
178 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
179 dn_to_match, dn_oid,
180 next_to_visit,
181 visited, visited_count,
182 matched);
183 if (ret != LDB_SUCCESS) {
184 talloc_free(tmp_ctx);
185 return ret;
187 if (*matched) {
188 talloc_free(tmp_ctx);
189 return LDB_SUCCESS;
193 talloc_free(tmp_ctx);
194 *matched = false;
195 return LDB_SUCCESS;
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,
204 const char *attr,
205 const struct ldb_val *value_to_match,
206 struct dsdb_dn *current_object_dn,
207 bool *matched)
209 const struct dsdb_schema *schema;
210 const struct dsdb_attribute *schema_attr;
211 struct dsdb_dn *dn_to_match;
212 const char *dn_oid;
213 unsigned int count;
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) {
235 *matched = false;
236 return LDB_SUCCESS;
239 return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
240 dn_to_match, dn_oid,
241 current_object_dn,
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,
259 const char *oid,
260 const struct ldb_message *msg,
261 const char *attribute_to_match,
262 const struct ldb_val *value_to_match,
263 bool *matched)
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;
269 TALLOC_CTX *tmp_ctx;
270 int ret;
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
297 * mailing list.
299 if (schema_attr->linkID == 0) {
300 *matched = false;
301 talloc_free(tmp_ctx);
302 return LDB_SUCCESS;
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
314 * DN syntax.
316 dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
317 LDB_SYNTAX_DN);
318 if (dsdb_msg_dn == NULL) {
319 *matched = false;
320 return LDB_ERR_INVALID_DN_SYNTAX;
323 ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
324 attribute_to_match,
325 value_to_match,
326 dsdb_msg_dn, matched);
327 talloc_free(tmp_ctx);
328 return ret;
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
341 * object
343 static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb,
344 const char *oid,
345 const struct ldb_message *msg,
346 const char *attribute_to_match,
347 const struct ldb_val *value_to_match,
348 bool *matched)
350 TALLOC_CTX *tmp_ctx;
351 unsigned int i;
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;
357 *matched = false;
359 /* Needs to be dnsRecord, no match otherwise */
360 if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) {
361 return LDB_SUCCESS;
364 el = ldb_msg_find_element(msg, attribute_to_match);
365 if (el == NULL) {
366 return LDB_SUCCESS;
369 session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
370 struct auth_session_info);
371 if (session_info == NULL) {
372 return ldb_oom(ldb);
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;
385 } else {
386 int error = 0;
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,
396 NULL,
398 &error,
399 SMB_STR_FULL_STR_CONV);
400 if (error != 0) {
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) {
408 return ldb_oom(ldb);
411 for (i = 0; i < el->num_values; i++) {
412 rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord);
413 if (rec == NULL) {
414 TALLOC_FREE(tmp_ctx);
415 return ldb_oom(ldb);
417 err = ndr_pull_struct_blob(
418 &(el->values[i]),
419 tmp_ctx,
420 rec,
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);
430 continue;
433 if (rec->wType == DNS_TYPE_TOMBSTONE) {
434 TALLOC_FREE(tmp_ctx);
435 continue;
437 if (rec->dwTimeStamp == 0) {
438 TALLOC_FREE(tmp_ctx);
439 continue;
441 if (rec->dwTimeStamp > tombstone_time) {
442 TALLOC_FREE(tmp_ctx);
443 continue;
446 *matched = true;
447 break;
450 TALLOC_FREE(tmp_ctx);
451 return LDB_SUCCESS;
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
464 * time.
467 static int dsdb_match_for_expunge(struct ldb_context *ldb,
468 const char *oid,
469 const struct ldb_message *msg,
470 const char *attribute_to_match,
471 const struct ldb_val *value_to_match,
472 bool *matched)
474 const struct dsdb_schema *schema;
475 const struct dsdb_attribute *schema_attr;
476 TALLOC_CTX *tmp_ctx;
477 unsigned int i;
478 struct ldb_message_element *el;
479 struct auth_session_info *session_info;
480 uint64_t tombstone_time;
481 *matched = false;
483 el = ldb_msg_find_element(msg, attribute_to_match);
484 if (el == NULL) {
485 return LDB_SUCCESS;
488 session_info
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;
520 } else {
521 int error = 0;
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,
530 NULL,
532 &error,
533 SMB_STR_FULL_STR_CONV);
534 if (error != 0) {
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++) {
545 NTSTATUS status;
546 struct dsdb_dn *dn;
547 uint64_t rmd_changetime;
548 if (dsdb_dn_is_deleted_val(&el->values[i]) == false) {
549 continue;
552 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i],
553 schema_attr->syntax->ldap_oid);
554 if (dn == NULL) {
555 DEBUG(1, ("Error: Failed to parse linked attribute blob of %s.\n", el->name));
556 continue;
559 status = dsdb_get_extended_dn_uint64(dn->dn, &rmd_changetime,
560 "RMD_CHANGETIME");
561 if (!NT_STATUS_IS_OK(status)) {
562 DEBUG(1, ("Error: RMD_CHANGETIME is missing on a forward link.\n"));
563 continue;
566 if (rmd_changetime > tombstone_time) {
567 continue;
570 *matched = true;
571 break;
573 talloc_free(tmp_ctx);
574 return LDB_SUCCESS;
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;
583 int ret;
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);
591 return ret;
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);
600 return ret;
603 match_for_dns_to_tombstone_time = talloc_zero(
604 ldb,
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);
613 return ret;
616 return LDB_SUCCESS;