dsdb: Fix comment wording
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blobd66b918e95df580dc6626efb5ee31fe007c7b52e
1 /*
2 ldb database library
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Andrew Tridgell 2005
6 Copyright (C) Simo Sorce 2006-2008
7 Copyright (C) Matthias Dieter Wallnöfer 2009
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 handle operational attributes
28 createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
31 for the above two, we do the search as normal, and if
32 createTimeStamp or modifyTimeStamp is asked for, then do
33 additional searches for whenCreated and whenChanged and fill in
34 the resulting values
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
45 on add we need to add both the above, for current time
47 on modify we need to change whenChanged
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
56 contains the RID of a certain group object
59 attributeTypes: in schema only
60 objectClasses: in schema only
61 matchingRules: in schema only
62 matchingRuleUse: in schema only
63 creatorsName: not supported by w2k3?
64 modifiersName: not supported by w2k3?
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "param/param.h"
74 #include "dsdb/samdb/samdb.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
77 #include "libcli/security/security.h"
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
83 #undef strcasecmp
85 struct operational_data {
86 struct ldb_dn *aggregate_dn;
89 enum search_type {
90 TOKEN_GROUPS,
91 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
92 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
95 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
96 * all account groups in a given domain, excluding built-in groups.
97 * (Used internally for msDS-ResultantPSO support)
99 ACCOUNT_GROUPS
102 static int get_pso_for_user(struct ldb_module *module,
103 struct ldb_message *user_msg,
104 struct ldb_request *parent,
105 struct ldb_message **pso_msg);
108 construct a canonical name from a message
110 static int construct_canonical_name(struct ldb_module *module,
111 struct ldb_message *msg, enum ldb_scope scope,
112 struct ldb_request *parent)
114 char *canonicalName;
115 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
116 if (canonicalName == NULL) {
117 return ldb_operr(ldb_module_get_ctx(module));
119 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
123 construct a primary group token for groups from a message
125 static int construct_primary_group_token(struct ldb_module *module,
126 struct ldb_message *msg, enum ldb_scope scope,
127 struct ldb_request *parent)
129 struct ldb_context *ldb;
130 uint32_t primary_group_token;
132 ldb = ldb_module_get_ctx(module);
133 if (ldb_match_msg_objectclass(msg, "group") == 1) {
134 primary_group_token
135 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
136 if (primary_group_token == 0) {
137 return LDB_SUCCESS;
140 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
141 primary_group_token);
142 } else {
143 return LDB_SUCCESS;
148 * Returns the group SIDs for the user in the given LDB message
150 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
151 struct ldb_message *msg, const char *attribute_string,
152 enum search_type type, struct dom_sid **groupSIDs,
153 unsigned int *num_groupSIDs)
155 const char *filter = NULL;
156 NTSTATUS status;
157 struct dom_sid *primary_group_sid;
158 const char *primary_group_string;
159 const char *primary_group_dn;
160 DATA_BLOB primary_group_blob;
161 struct dom_sid *account_sid;
162 const char *account_sid_string;
163 const char *account_sid_dn;
164 DATA_BLOB account_sid_blob;
165 struct dom_sid *domain_sid;
167 /* If it's not a user, it won't have a primaryGroupID */
168 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
169 return LDB_SUCCESS;
172 /* Ensure it has an objectSID too */
173 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
174 if (account_sid == NULL) {
175 return LDB_SUCCESS;
178 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
179 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
180 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
181 } else if (!NT_STATUS_IS_OK(status)) {
182 return LDB_ERR_OPERATIONS_ERROR;
185 primary_group_sid = dom_sid_add_rid(mem_ctx,
186 domain_sid,
187 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
188 if (!primary_group_sid) {
189 return ldb_oom(ldb);
192 /* only return security groups */
193 switch(type) {
194 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
195 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
196 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
197 break;
198 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
199 case TOKEN_GROUPS:
200 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
201 GROUP_TYPE_SECURITY_ENABLED);
202 break;
204 /* for RevMembGetAccountGroups, exclude built-in groups */
205 case ACCOUNT_GROUPS:
206 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))",
207 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
208 break;
211 if (!filter) {
212 return ldb_oom(ldb);
215 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
216 if (!primary_group_string) {
217 return ldb_oom(ldb);
220 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
221 if (!primary_group_dn) {
222 return ldb_oom(ldb);
225 primary_group_blob = data_blob_string_const(primary_group_dn);
227 account_sid_string = dom_sid_string(mem_ctx, account_sid);
228 if (!account_sid_string) {
229 return ldb_oom(ldb);
232 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
233 if (!account_sid_dn) {
234 return ldb_oom(ldb);
237 account_sid_blob = data_blob_string_const(account_sid_dn);
239 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
240 true, /* We don't want to add the object's SID itself,
241 it's not returend in this attribute */
242 filter,
243 mem_ctx, groupSIDs, num_groupSIDs);
245 if (!NT_STATUS_IS_OK(status)) {
246 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
247 attribute_string, account_sid_string,
248 nt_errstr(status));
249 return LDB_ERR_OPERATIONS_ERROR;
252 /* Expands the primary group - this function takes in
253 * memberOf-like values, so we fake one up with the
254 * <SID=S-...> format of DN and then let it expand
255 * them, as long as they meet the filter - so only
256 * domain groups, not builtin groups
258 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
259 mem_ctx, groupSIDs, num_groupSIDs);
260 if (!NT_STATUS_IS_OK(status)) {
261 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
262 attribute_string, account_sid_string,
263 nt_errstr(status));
264 return LDB_ERR_OPERATIONS_ERROR;
267 return LDB_SUCCESS;
271 construct the token groups for SAM objects from a message
273 static int construct_generic_token_groups(struct ldb_module *module,
274 struct ldb_message *msg, enum ldb_scope scope,
275 struct ldb_request *parent,
276 const char *attribute_string,
277 enum search_type type)
279 struct ldb_context *ldb = ldb_module_get_ctx(module);
280 TALLOC_CTX *tmp_ctx = talloc_new(msg);
281 unsigned int i;
282 int ret;
283 struct dom_sid *groupSIDs = NULL;
284 unsigned int num_groupSIDs = 0;
286 if (scope != LDB_SCOPE_BASE) {
287 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
288 return LDB_ERR_OPERATIONS_ERROR;
291 /* calculate the group SIDs for this object */
292 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
293 &groupSIDs, &num_groupSIDs);
295 if (ret != LDB_SUCCESS) {
296 talloc_free(tmp_ctx);
297 return LDB_ERR_OPERATIONS_ERROR;
300 /* add these SIDs to the search result */
301 for (i=0; i < num_groupSIDs; i++) {
302 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
303 if (ret) {
304 talloc_free(tmp_ctx);
305 return ret;
309 return LDB_SUCCESS;
312 static int construct_token_groups(struct ldb_module *module,
313 struct ldb_message *msg, enum ldb_scope scope,
314 struct ldb_request *parent)
317 * TODO: Add in a limiting domain when we start to support
318 * trusted domains.
320 return construct_generic_token_groups(module, msg, scope, parent,
321 "tokenGroups",
322 TOKEN_GROUPS);
325 static int construct_token_groups_no_gc(struct ldb_module *module,
326 struct ldb_message *msg, enum ldb_scope scope,
327 struct ldb_request *parent)
330 * TODO: Add in a limiting domain when we start to support
331 * trusted domains.
333 return construct_generic_token_groups(module, msg, scope, parent,
334 "tokenGroupsNoGCAcceptable",
335 TOKEN_GROUPS);
338 static int construct_global_universal_token_groups(struct ldb_module *module,
339 struct ldb_message *msg, enum ldb_scope scope,
340 struct ldb_request *parent)
342 return construct_generic_token_groups(module, msg, scope, parent,
343 "tokenGroupsGlobalAndUniversal",
344 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
347 construct the parent GUID for an entry from a message
349 static int construct_parent_guid(struct ldb_module *module,
350 struct ldb_message *msg, enum ldb_scope scope,
351 struct ldb_request *parent)
353 struct ldb_result *res, *parent_res;
354 const struct ldb_val *parent_guid;
355 const char *attrs[] = { "instanceType", NULL };
356 const char *attrs2[] = { "objectGUID", NULL };
357 uint32_t instanceType;
358 int ret;
359 struct ldb_dn *parent_dn;
360 struct ldb_val v;
362 /* determine if the object is NC by instance type */
363 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
364 DSDB_FLAG_NEXT_MODULE |
365 DSDB_SEARCH_SHOW_RECYCLED, parent);
366 if (ret != LDB_SUCCESS) {
367 return ret;
370 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
371 "instanceType", 0);
372 talloc_free(res);
373 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
374 DEBUG(4,(__location__ ": Object %s is NC\n",
375 ldb_dn_get_linearized(msg->dn)));
376 return LDB_SUCCESS;
378 parent_dn = ldb_dn_get_parent(msg, msg->dn);
380 if (parent_dn == NULL) {
381 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
382 ldb_dn_get_linearized(msg->dn)));
383 return LDB_ERR_OTHER;
385 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
386 DSDB_FLAG_NEXT_MODULE |
387 DSDB_SEARCH_SHOW_RECYCLED, parent);
388 /* not NC, so the object should have a parent*/
389 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
390 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
391 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
392 ldb_dn_get_linearized(parent_dn),
393 ldb_dn_get_linearized(msg->dn)));
394 talloc_free(parent_dn);
395 return ret;
396 } else if (ret != LDB_SUCCESS) {
397 talloc_free(parent_dn);
398 return ret;
400 talloc_free(parent_dn);
402 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
403 if (!parent_guid) {
404 talloc_free(parent_res);
405 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
408 v = data_blob_dup_talloc(parent_res, *parent_guid);
409 if (!v.data) {
410 talloc_free(parent_res);
411 return ldb_oom(ldb_module_get_ctx(module));
413 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
414 talloc_free(parent_res);
415 return ret;
418 static int construct_modifyTimeStamp(struct ldb_module *module,
419 struct ldb_message *msg, enum ldb_scope scope,
420 struct ldb_request *parent)
422 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
423 struct ldb_context *ldb = ldb_module_get_ctx(module);
425 /* We may be being called before the init function has finished */
426 if (!data) {
427 return LDB_SUCCESS;
430 /* Try and set this value up, if possible. Don't worry if it
431 * fails, we may not have the DB set up yet.
433 if (!data->aggregate_dn) {
434 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
437 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
439 * If we have the DN for the object with common name = Aggregate and
440 * the request is for this DN then let's do the following:
441 * 1) search the object which changedUSN correspond to the one of the loaded
442 * schema.
443 * 2) Get the whenChanged attribute
444 * 3) Generate the modifyTimestamp out of the whenChanged attribute
446 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
447 char *value = ldb_timestring(msg, schema->ts_last_change);
449 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
451 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
455 construct a subSchemaSubEntry
457 static int construct_subschema_subentry(struct ldb_module *module,
458 struct ldb_message *msg, enum ldb_scope scope,
459 struct ldb_request *parent)
461 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
462 char *subSchemaSubEntry;
464 /* We may be being called before the init function has finished */
465 if (!data) {
466 return LDB_SUCCESS;
469 /* Try and set this value up, if possible. Don't worry if it
470 * fails, we may not have the DB set up yet, and it's not
471 * really vital anyway */
472 if (!data->aggregate_dn) {
473 struct ldb_context *ldb = ldb_module_get_ctx(module);
474 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
477 if (data->aggregate_dn) {
478 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
479 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
481 return LDB_SUCCESS;
485 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
486 struct ldb_message *msg,
487 struct ldb_message_element *object_category)
489 struct ldb_context *ldb;
490 struct ldb_dn *dn;
491 const struct ldb_val *val;
493 ldb = ldb_module_get_ctx(module);
494 if (!ldb) {
495 DEBUG(4, (__location__ ": Failed to get ldb \n"));
496 return LDB_ERR_OPERATIONS_ERROR;
499 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
500 if (!dn) {
501 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
502 (const char *)object_category->values[0].data));
503 return ldb_operr(ldb);
506 val = ldb_dn_get_rdn_val(dn);
507 if (!val) {
508 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
509 ldb_dn_get_linearized(dn)));
510 return ldb_operr(ldb);
513 if (strequal((const char *)val->data, "NTDS-DSA")) {
514 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
515 } else {
516 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
518 return LDB_SUCCESS;
521 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
522 struct ldb_message *msg,
523 struct ldb_dn *dn,
524 struct ldb_request *parent)
526 struct ldb_dn *server_dn;
527 const char *attr_obj_cat[] = { "objectCategory", NULL };
528 struct ldb_result *res;
529 struct ldb_message_element *object_category;
530 int ret;
532 server_dn = ldb_dn_copy(msg, dn);
533 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
534 DEBUG(4, (__location__ ": Failed to add child to %s \n",
535 ldb_dn_get_linearized(server_dn)));
536 return ldb_operr(ldb_module_get_ctx(module));
539 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
540 DSDB_FLAG_NEXT_MODULE, parent);
541 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
542 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
543 ldb_dn_get_linearized(server_dn)));
544 return LDB_SUCCESS;
545 } else if (ret != LDB_SUCCESS) {
546 return ret;
549 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
550 if (!object_category) {
551 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
552 ldb_dn_get_linearized(res->msgs[0]->dn)));
553 return LDB_SUCCESS;
555 return construct_msds_isrodc_with_dn(module, msg, object_category);
558 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
559 struct ldb_message *msg,
560 struct ldb_request *parent)
562 int ret;
563 struct ldb_dn *server_dn;
565 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
566 &server_dn, parent);
567 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
568 /* it's OK if we can't find serverReferenceBL attribute */
569 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
570 ldb_dn_get_linearized(msg->dn)));
571 return LDB_SUCCESS;
572 } else if (ret != LDB_SUCCESS) {
573 return ret;
576 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
580 construct msDS-isRODC attr
582 static int construct_msds_isrodc(struct ldb_module *module,
583 struct ldb_message *msg, enum ldb_scope scope,
584 struct ldb_request *parent)
586 struct ldb_message_element * object_class;
587 struct ldb_message_element * object_category;
588 unsigned int i;
590 object_class = ldb_msg_find_element(msg, "objectClass");
591 if (!object_class) {
592 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
593 ldb_dn_get_linearized(msg->dn)));
594 return ldb_operr(ldb_module_get_ctx(module));
597 for (i=0; i<object_class->num_values; i++) {
598 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
599 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
600 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
602 object_category = ldb_msg_find_element(msg, "objectCategory");
603 if (!object_category) {
604 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
605 ldb_dn_get_linearized(msg->dn)));
606 return LDB_SUCCESS;
608 return construct_msds_isrodc_with_dn(module, msg, object_category);
610 if (strequal((const char*)object_class->values[i].data, "server")) {
611 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
612 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
613 * substituting TN for TO.
615 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
617 if (strequal((const char*)object_class->values[i].data, "computer")) {
618 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
619 * rule for the "TO is a server object" case, substituting TS for TO.
621 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
625 return LDB_SUCCESS;
630 construct msDS-keyVersionNumber attr
632 TODO: Make this based on the 'win2k' DS huristics bit...
635 static int construct_msds_keyversionnumber(struct ldb_module *module,
636 struct ldb_message *msg,
637 enum ldb_scope scope,
638 struct ldb_request *parent)
640 uint32_t i;
641 enum ndr_err_code ndr_err;
642 const struct ldb_val *omd_value;
643 struct replPropertyMetaDataBlob *omd;
644 int ret;
646 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
647 if (!omd_value) {
648 /* We can't make up a key version number without meta data */
649 return LDB_SUCCESS;
652 omd = talloc(msg, struct replPropertyMetaDataBlob);
653 if (!omd) {
654 ldb_module_oom(module);
655 return LDB_SUCCESS;
658 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
659 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
660 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
661 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
662 ldb_dn_get_linearized(msg->dn)));
663 return ldb_operr(ldb_module_get_ctx(module));
666 if (omd->version != 1) {
667 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
668 omd->version, ldb_dn_get_linearized(msg->dn)));
669 talloc_free(omd);
670 return LDB_SUCCESS;
672 for (i=0; i<omd->ctr.ctr1.count; i++) {
673 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
674 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
675 msg, msg,
676 "msDS-KeyVersionNumber",
677 omd->ctr.ctr1.array[i].version);
678 if (ret != LDB_SUCCESS) {
679 talloc_free(omd);
680 return ret;
682 break;
685 return LDB_SUCCESS;
689 #define _UF_TRUST_ACCOUNTS ( \
690 UF_WORKSTATION_TRUST_ACCOUNT | \
691 UF_SERVER_TRUST_ACCOUNT | \
692 UF_INTERDOMAIN_TRUST_ACCOUNT \
694 #define _UF_NO_EXPIRY_ACCOUNTS ( \
695 UF_SMARTCARD_REQUIRED | \
696 UF_DONT_EXPIRE_PASSWD | \
697 _UF_TRUST_ACCOUNTS \
702 * Returns the Effective-MaximumPasswordAge for a user
704 static int64_t get_user_max_pwd_age(struct ldb_module *module,
705 struct ldb_message *user_msg,
706 struct ldb_request *parent,
707 struct ldb_dn *nc_root)
709 int ret;
710 struct ldb_message *pso = NULL;
711 struct ldb_context *ldb = ldb_module_get_ctx(module);
713 /* if a PSO applies to the user, use its maxPwdAge */
714 ret = get_pso_for_user(module, user_msg, parent, &pso);
715 if (ret != LDB_SUCCESS) {
717 /* log the error, but fallback to the domain default */
718 DBG_ERR("Error retrieving PSO for %s\n",
719 ldb_dn_get_linearized(user_msg->dn));
722 if (pso != NULL) {
723 return ldb_msg_find_attr_as_int64(pso,
724 "msDS-MaximumPasswordAge", 0);
727 /* otherwise return the default domain value */
728 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
732 calculate msDS-UserPasswordExpiryTimeComputed
734 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
735 struct ldb_message *msg,
736 struct ldb_request *parent,
737 struct ldb_dn *domain_dn)
739 int64_t pwdLastSet, maxPwdAge;
740 uint32_t userAccountControl;
741 NTTIME ret;
743 userAccountControl = ldb_msg_find_attr_as_uint(msg,
744 "userAccountControl",
746 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
747 return 0x7FFFFFFFFFFFFFFFULL;
750 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
751 if (pwdLastSet == 0) {
752 return 0;
755 if (pwdLastSet <= -1) {
757 * This can't really happen...
759 return 0x7FFFFFFFFFFFFFFFULL;
762 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
764 * Somethings wrong with the clock...
766 return 0x7FFFFFFFFFFFFFFFULL;
770 * Note that maxPwdAge is a stored as negative value.
772 * Possible values are in the range of:
774 * maxPwdAge: -864000000001
775 * to
776 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
779 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
780 if (maxPwdAge >= -864000000000) {
782 * This is not really possible...
784 return 0x7FFFFFFFFFFFFFFFULL;
787 if (maxPwdAge == -0x8000000000000000LL) {
788 return 0x7FFFFFFFFFFFFFFFULL;
792 * Note we already caught maxPwdAge == -0x8000000000000000ULL
793 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
795 * Remember maxPwdAge is a negative number,
796 * so it results in the following.
798 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
800 * 0xFFFFFFFFFFFFFFFDULL
802 * or to put it another way, adding two numbers less than 1<<63 can't
803 * ever be more than 1<<64, therefore this result can't wrap.
805 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
806 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
807 return 0x7FFFFFFFFFFFFFFFULL;
810 return ret;
814 * Returns the Effective-LockoutDuration for a user
816 static int64_t get_user_lockout_duration(struct ldb_module *module,
817 struct ldb_message *user_msg,
818 struct ldb_request *parent,
819 struct ldb_dn *nc_root)
821 int ret;
822 struct ldb_message *pso = NULL;
823 struct ldb_context *ldb = ldb_module_get_ctx(module);
825 /* if a PSO applies to the user, use its lockoutDuration */
826 ret = get_pso_for_user(module, user_msg, parent, &pso);
827 if (ret != LDB_SUCCESS) {
829 /* log the error, but fallback to the domain default */
830 DBG_ERR("Error retrieving PSO for %s\n",
831 ldb_dn_get_linearized(user_msg->dn));
834 if (pso != NULL) {
835 return ldb_msg_find_attr_as_int64(pso,
836 "msDS-LockoutDuration", 0);
839 /* otherwise return the default domain value */
840 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
841 NULL);
845 construct msDS-User-Account-Control-Computed attr
847 static int construct_msds_user_account_control_computed(struct ldb_module *module,
848 struct ldb_message *msg, enum ldb_scope scope,
849 struct ldb_request *parent)
851 uint32_t userAccountControl;
852 uint32_t msDS_User_Account_Control_Computed = 0;
853 struct ldb_context *ldb = ldb_module_get_ctx(module);
854 NTTIME now;
855 struct ldb_dn *nc_root;
856 int ret;
858 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
859 if (ret != 0) {
860 ldb_asprintf_errstring(ldb,
861 "Failed to find NC root of DN: %s: %s",
862 ldb_dn_get_linearized(msg->dn),
863 ldb_errstring(ldb_module_get_ctx(module)));
864 return ret;
866 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
867 /* Only calculate this on our default NC */
868 return 0;
870 /* Test account expire time */
871 unix_to_nt_time(&now, time(NULL));
873 userAccountControl = ldb_msg_find_attr_as_uint(msg,
874 "userAccountControl",
876 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
878 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
879 if (lockoutTime != 0) {
880 int64_t lockoutDuration;
882 lockoutDuration = get_user_lockout_duration(module, msg,
883 parent,
884 nc_root);
886 /* zero locks out until the administrator intervenes */
887 if (lockoutDuration >= 0) {
888 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
889 } else if (lockoutTime - lockoutDuration >= now) {
890 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
895 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
896 NTTIME must_change_time
897 = get_msds_user_password_expiry_time_computed(module,
898 msg,
899 parent,
900 nc_root);
901 /* check for expired password */
902 if (must_change_time < now) {
903 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
907 return samdb_msg_add_int64(ldb,
908 msg->elements, msg,
909 "msDS-User-Account-Control-Computed",
910 msDS_User_Account_Control_Computed);
914 construct msDS-UserPasswordExpiryTimeComputed
916 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
917 struct ldb_message *msg, enum ldb_scope scope,
918 struct ldb_request *parent)
920 struct ldb_context *ldb = ldb_module_get_ctx(module);
921 struct ldb_dn *nc_root;
922 int64_t password_expiry_time;
923 int ret;
925 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
926 if (ret != 0) {
927 ldb_asprintf_errstring(ldb,
928 "Failed to find NC root of DN: %s: %s",
929 ldb_dn_get_linearized(msg->dn),
930 ldb_errstring(ldb));
931 return ret;
934 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
935 /* Only calculate this on our default NC */
936 return 0;
939 password_expiry_time
940 = get_msds_user_password_expiry_time_computed(module, msg,
941 parent, nc_root);
943 return samdb_msg_add_int64(ldb,
944 msg->elements, msg,
945 "msDS-UserPasswordExpiryTimeComputed",
946 password_expiry_time);
950 * Checks whether the msDS-ResultantPSO attribute is supported for a given
951 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
953 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
955 int functional_level;
956 uint32_t uac;
957 uint32_t user_rid;
959 functional_level = dsdb_functional_level(ldb);
960 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
961 return false;
964 /* msDS-ResultantPSO is only supported for user objects */
965 if (!ldb_match_msg_objectclass(msg, "user")) {
966 return false;
969 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
970 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
971 if (!(uac & UF_NORMAL_ACCOUNT)) {
972 return false;
975 /* skip it if it's the special KRBTGT default account */
976 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
977 if (user_rid == DOMAIN_RID_KRBTGT) {
978 return false;
981 /* ...or if it's a special KRBTGT account for an RODC KDC */
982 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
983 return false;
986 return true;
990 * Returns the number of PSO objects that exist in the DB
992 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
993 struct ldb_request *parent, int *pso_count)
995 static const char * const attrs[] = { NULL };
996 int ret;
997 struct ldb_dn *domain_dn = NULL;
998 struct ldb_dn *psc_dn = NULL;
999 struct ldb_result *res = NULL;
1000 struct ldb_context *ldb = ldb_module_get_ctx(module);
1002 *pso_count = 0;
1003 domain_dn = ldb_get_default_basedn(ldb);
1004 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1005 "CN=Password Settings Container,CN=System,%s",
1006 ldb_dn_get_linearized(domain_dn));
1007 if (psc_dn == NULL) {
1008 return ldb_oom(ldb);
1011 /* get the number of PSO children */
1012 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1013 LDB_SCOPE_ONELEVEL, attrs,
1014 DSDB_FLAG_NEXT_MODULE, parent,
1015 "(objectClass=msDS-PasswordSettings)");
1018 * Just ignore PSOs if the container doesn't exist. This is a weird
1019 * corner-case where the AD DB was created from a pre-2008 base schema,
1020 * and then the FL was manually upgraded.
1022 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1023 DBG_NOTICE("No Password Settings Container exists\n");
1024 return LDB_SUCCESS;
1027 if (ret != LDB_SUCCESS) {
1028 return ret;
1031 *pso_count = res->count;
1032 talloc_free(res);
1033 talloc_free(psc_dn);
1035 return LDB_SUCCESS;
1039 * Compares two PSO objects returned by a search, to work out the better PSO.
1040 * The PSO with the lowest precedence is better, otherwise (if the precedence
1041 * is equal) the PSO with the lower GUID wins.
1043 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1045 uint32_t prec1;
1046 uint32_t prec2;
1048 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1049 0xffffffff);
1050 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1051 0xffffffff);
1053 /* if precedence is equal, use the lowest GUID */
1054 if (prec1 == prec2) {
1055 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1056 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1058 return ndr_guid_compare(&guid1, &guid2);
1059 } else {
1060 return prec1 - prec2;
1065 * Search for PSO objects that apply to the object SIDs specified
1067 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1068 struct ldb_request *parent,
1069 struct dom_sid *sid_array, unsigned int num_sids,
1070 struct ldb_result **result)
1072 int ret;
1073 int i;
1074 struct ldb_context *ldb = ldb_module_get_ctx(module);
1075 char *sid_filter = NULL;
1076 struct ldb_dn *domain_dn = NULL;
1077 struct ldb_dn *psc_dn = NULL;
1078 const char *attrs[] = {
1079 "msDS-PasswordSettingsPrecedence",
1080 "objectGUID",
1081 "msDS-LockoutDuration",
1082 "msDS-MaximumPasswordAge",
1083 NULL
1086 /* build a query for PSO objects that apply to any of the SIDs given */
1087 sid_filter = talloc_strdup(mem_ctx, "");
1089 for (i = 0; sid_filter && i < num_sids; i++) {
1090 struct dom_sid_buf sid_buf;
1092 sid_filter = talloc_asprintf_append(
1093 sid_filter,
1094 "(msDS-PSOAppliesTo=<SID=%s>)",
1095 dom_sid_str_buf(&sid_array[i], &sid_buf));
1098 if (sid_filter == NULL) {
1099 return ldb_oom(ldb);
1102 /* only PSOs located in the Password Settings Container are valid */
1103 domain_dn = ldb_get_default_basedn(ldb);
1104 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1105 "CN=Password Settings Container,CN=System,%s",
1106 ldb_dn_get_linearized(domain_dn));
1107 if (psc_dn == NULL) {
1108 return ldb_oom(ldb);
1111 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1112 LDB_SCOPE_ONELEVEL, attrs,
1113 DSDB_FLAG_NEXT_MODULE, parent,
1114 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1115 sid_filter);
1116 talloc_free(sid_filter);
1117 return ret;
1121 * Returns the best PSO object that applies to the object SID(s) specified
1123 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1124 struct ldb_request *parent, struct dom_sid *sid_array,
1125 unsigned int num_sids, struct ldb_message **best_pso)
1127 struct ldb_result *res = NULL;
1128 int ret;
1130 *best_pso = NULL;
1132 /* find any PSOs that apply to the SIDs specified */
1133 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1134 &res);
1135 if (ret != LDB_SUCCESS) {
1136 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1137 return ret;
1140 /* sort the list so that the best PSO is first */
1141 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1143 if (res->count > 0) {
1144 *best_pso = res->msgs[0];
1147 return LDB_SUCCESS;
1151 * Determines the Password Settings Object (PSO) that applies to the given user
1153 static int get_pso_for_user(struct ldb_module *module,
1154 struct ldb_message *user_msg,
1155 struct ldb_request *parent,
1156 struct ldb_message **pso_msg)
1158 bool pso_supported;
1159 struct dom_sid *groupSIDs = NULL;
1160 unsigned int num_groupSIDs = 0;
1161 struct ldb_context *ldb = ldb_module_get_ctx(module);
1162 struct ldb_message *best_pso = NULL;
1163 struct ldb_dn *pso_dn = NULL;
1164 int ret;
1165 struct ldb_message_element *el = NULL;
1166 TALLOC_CTX *tmp_ctx = NULL;
1167 int pso_count = 0;
1168 struct ldb_result *res = NULL;
1169 static const char *attrs[] = {
1170 "msDS-LockoutDuration",
1171 "msDS-MaximumPasswordAge",
1172 NULL
1175 *pso_msg = NULL;
1177 /* first, check msDS-ResultantPSO is supported for this object */
1178 pso_supported = pso_is_supported(ldb, user_msg);
1180 if (!pso_supported) {
1181 return LDB_SUCCESS;
1184 tmp_ctx = talloc_new(user_msg);
1187 * Several different constructed attributes try to use the PSO info. If
1188 * we've already constructed the msDS-ResultantPSO for this user, we can
1189 * just re-use the result, rather than calculating it from scratch again
1191 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1192 "msDS-ResultantPSO");
1194 if (pso_dn != NULL) {
1195 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1196 attrs, DSDB_FLAG_NEXT_MODULE,
1197 parent);
1198 if (ret != LDB_SUCCESS) {
1199 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1200 ldb_dn_get_linearized(pso_dn));
1201 talloc_free(tmp_ctx);
1202 return ret;
1205 if (res->count == 1) {
1206 *pso_msg = res->msgs[0];
1207 return LDB_SUCCESS;
1212 * if any PSOs apply directly to the user, they are considered first
1213 * before we check group membership PSOs
1215 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1217 if (el != NULL && el->num_values > 0) {
1218 struct dom_sid *user_sid = NULL;
1220 /* lookup the best PSO object, based on the user's SID */
1221 user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
1223 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1224 &best_pso);
1225 if (ret != LDB_SUCCESS) {
1226 talloc_free(tmp_ctx);
1227 return ret;
1230 if (best_pso != NULL) {
1231 *pso_msg = best_pso;
1232 return LDB_SUCCESS;
1237 * If no valid PSO applies directly to the user, then try its groups.
1238 * The group expansion is expensive, so check there are actually
1239 * PSOs in the DB first (which is a quick search). Note in the above
1240 * cases we could tell that a PSO applied to the user, based on info
1241 * already retrieved by the user search.
1243 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1244 if (ret != LDB_SUCCESS) {
1245 DBG_ERR("Error %d determining PSOs in system\n", ret);
1246 talloc_free(tmp_ctx);
1247 return ret;
1250 if (pso_count == 0) {
1251 talloc_free(tmp_ctx);
1252 return LDB_SUCCESS;
1255 /* Work out the SIDs of any account groups the user is a member of */
1256 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1257 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1258 &groupSIDs, &num_groupSIDs);
1259 if (ret != LDB_SUCCESS) {
1260 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1261 ldb_dn_get_linearized(user_msg->dn));
1262 talloc_free(tmp_ctx);
1263 return ret;
1266 /* lookup the best PSO that applies to any of these groups */
1267 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1268 num_groupSIDs, &best_pso);
1269 if (ret != LDB_SUCCESS) {
1270 talloc_free(tmp_ctx);
1271 return ret;
1274 *pso_msg = best_pso;
1275 return LDB_SUCCESS;
1279 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1280 * Settings Object (PSO) that applies to that user.
1282 static int construct_resultant_pso(struct ldb_module *module,
1283 struct ldb_message *msg,
1284 enum ldb_scope scope,
1285 struct ldb_request *parent)
1287 struct ldb_message *pso = NULL;
1288 int ret;
1290 /* work out the PSO (if any) that applies to this user */
1291 ret = get_pso_for_user(module, msg, parent, &pso);
1292 if (ret != LDB_SUCCESS) {
1293 DBG_ERR("Couldn't determine PSO for %s\n",
1294 ldb_dn_get_linearized(msg->dn));
1295 return ret;
1298 if (pso != NULL) {
1299 DBG_INFO("%s is resultant PSO for user %s\n",
1300 ldb_dn_get_linearized(pso->dn),
1301 ldb_dn_get_linearized(msg->dn));
1302 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1303 ldb_dn_get_linearized(pso->dn));
1306 /* no PSO applies to this user */
1307 return LDB_SUCCESS;
1310 struct op_controls_flags {
1311 bool sd;
1312 bool bypassoperational;
1315 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1316 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1317 return true;
1319 return false;
1323 a list of attribute names that should be substituted in the parse
1324 tree before the search is done
1326 static const struct {
1327 const char *attr;
1328 const char *replace;
1329 } parse_tree_sub[] = {
1330 { "createTimeStamp", "whenCreated" },
1331 { "modifyTimeStamp", "whenChanged" }
1335 struct op_attributes_replace {
1336 const char *attr;
1337 const char *replace;
1338 const char * const *extra_attrs;
1339 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1342 /* the 'extra_attrs' required for msDS-ResultantPSO */
1343 #define RESULTANT_PSO_COMPUTED_ATTRS \
1344 "msDS-PSOApplied", \
1345 "userAccountControl", \
1346 "objectSid", \
1347 "msDS-SecondaryKrbTgtNumber", \
1348 "primaryGroupID"
1351 * any other constructed attributes that want to work out the PSO also need to
1352 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1354 #define PSO_ATTR_DEPENDENCIES \
1355 RESULTANT_PSO_COMPUTED_ATTRS, \
1356 "objectClass"
1358 static const char *objectSid_attr[] =
1360 "objectSid",
1361 NULL
1365 static const char *objectCategory_attr[] =
1367 "objectCategory",
1368 NULL
1372 static const char *user_account_control_computed_attrs[] =
1374 "lockoutTime",
1375 "pwdLastSet",
1376 PSO_ATTR_DEPENDENCIES,
1377 NULL
1381 static const char *user_password_expiry_time_computed_attrs[] =
1383 "pwdLastSet",
1384 PSO_ATTR_DEPENDENCIES,
1385 NULL
1388 static const char *resultant_pso_computed_attrs[] =
1390 RESULTANT_PSO_COMPUTED_ATTRS,
1391 NULL
1395 a list of attribute names that are hidden, but can be searched for
1396 using another (non-hidden) name to produce the correct result
1398 static const struct op_attributes_replace search_sub[] = {
1399 { "createTimeStamp", "whenCreated", NULL , NULL },
1400 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1401 { "structuralObjectClass", "objectClass", NULL , NULL },
1402 { "canonicalName", NULL, NULL , construct_canonical_name },
1403 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1404 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1405 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1406 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1407 { "parentGUID", NULL, NULL, construct_parent_guid },
1408 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1409 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1410 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1411 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1412 construct_msds_user_account_control_computed },
1413 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1414 construct_msds_user_password_expiry_time_computed },
1415 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1416 construct_resultant_pso }
1420 enum op_remove {
1421 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1422 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1423 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1424 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
1428 a list of attributes that may need to be removed from the
1429 underlying db return
1431 Some of these are attributes that were once stored, but are now calculated
1433 struct op_attributes_operations {
1434 const char *attr;
1435 enum op_remove op;
1438 static const struct op_attributes_operations operational_remove[] = {
1439 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1440 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1441 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1442 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1443 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1444 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1449 post process a search result record. For any search_sub[] attributes that were
1450 asked for, we need to call the appropriate copy routine to copy the result
1451 into the message, then remove any attributes that we added to the search but
1452 were not asked for by the user
1454 static int operational_search_post_process(struct ldb_module *module,
1455 struct ldb_message *msg,
1456 enum ldb_scope scope,
1457 const char * const *attrs_from_user,
1458 const char * const *attrs_searched_for,
1459 struct op_controls_flags* controls_flags,
1460 struct op_attributes_operations *list,
1461 unsigned int list_size,
1462 struct op_attributes_replace *list_replace,
1463 unsigned int list_replace_size,
1464 struct ldb_request *parent)
1466 struct ldb_context *ldb;
1467 unsigned int i, a = 0;
1468 bool constructed_attributes = false;
1470 ldb = ldb_module_get_ctx(module);
1472 /* removed any attrs that should not be shown to the user */
1473 for (i=0; i < list_size; i++) {
1474 ldb_msg_remove_attr(msg, list[i].attr);
1477 for (a=0; a < list_replace_size; a++) {
1478 if (check_keep_control_for_attribute(controls_flags,
1479 list_replace[a].attr)) {
1480 continue;
1483 /* construct the new attribute, using either a supplied
1484 constructor or a simple copy */
1485 constructed_attributes = true;
1486 if (list_replace[a].constructor != NULL) {
1487 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1488 goto failed;
1490 } else if (ldb_msg_copy_attr(msg,
1491 list_replace[a].replace,
1492 list_replace[a].attr) != LDB_SUCCESS) {
1493 goto failed;
1497 /* Deletion of the search helper attributes are needed if:
1498 * - we generated constructed attributes and
1499 * - we aren't requesting all attributes
1501 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1502 for (i=0; i < list_replace_size; i++) {
1503 /* remove the added search helper attributes, unless
1504 * they were asked for by the user */
1505 if (list_replace[i].replace != NULL &&
1506 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1507 ldb_msg_remove_attr(msg, list_replace[i].replace);
1509 if (list_replace[i].extra_attrs != NULL) {
1510 unsigned int j;
1511 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1512 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1513 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1520 return 0;
1522 failed:
1523 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1524 "operational_search_post_process failed for attribute '%s' - %s",
1525 list_replace[a].attr, ldb_errstring(ldb));
1526 return -1;
1530 hook search operations
1533 struct operational_context {
1534 struct ldb_module *module;
1535 struct ldb_request *req;
1536 enum ldb_scope scope;
1537 const char * const *attrs;
1538 struct op_controls_flags* controls_flags;
1539 struct op_attributes_operations *list_operations;
1540 unsigned int list_operations_size;
1541 struct op_attributes_replace *attrs_to_replace;
1542 unsigned int attrs_to_replace_size;
1545 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1547 struct operational_context *ac;
1548 int ret;
1550 ac = talloc_get_type(req->context, struct operational_context);
1552 if (!ares) {
1553 return ldb_module_done(ac->req, NULL, NULL,
1554 LDB_ERR_OPERATIONS_ERROR);
1556 if (ares->error != LDB_SUCCESS) {
1557 return ldb_module_done(ac->req, ares->controls,
1558 ares->response, ares->error);
1561 switch (ares->type) {
1562 case LDB_REPLY_ENTRY:
1563 /* for each record returned post-process to add any derived
1564 attributes that have been asked for */
1565 ret = operational_search_post_process(ac->module,
1566 ares->message,
1567 ac->scope,
1568 ac->attrs,
1569 req->op.search.attrs,
1570 ac->controls_flags,
1571 ac->list_operations,
1572 ac->list_operations_size,
1573 ac->attrs_to_replace,
1574 ac->attrs_to_replace_size,
1575 req);
1576 if (ret != 0) {
1577 return ldb_module_done(ac->req, NULL, NULL,
1578 LDB_ERR_OPERATIONS_ERROR);
1580 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1582 case LDB_REPLY_REFERRAL:
1583 return ldb_module_send_referral(ac->req, ares->referral);
1585 case LDB_REPLY_DONE:
1587 return ldb_module_done(ac->req, ares->controls,
1588 ares->response, LDB_SUCCESS);
1591 talloc_free(ares);
1592 return LDB_SUCCESS;
1595 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1596 const char* const* attrs,
1597 const char* const* searched_attrs,
1598 struct op_controls_flags* controls_flags)
1600 int idx = 0;
1601 int i;
1602 struct op_attributes_operations *list = talloc_zero_array(ctx,
1603 struct op_attributes_operations,
1604 ARRAY_SIZE(operational_remove) + 1);
1606 if (list == NULL) {
1607 return NULL;
1610 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1611 switch (operational_remove[i].op) {
1612 case OPERATIONAL_REMOVE_UNASKED:
1613 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1614 continue;
1616 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1617 continue;
1619 list[idx].attr = operational_remove[i].attr;
1620 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1621 idx++;
1622 break;
1624 case OPERATIONAL_REMOVE_ALWAYS:
1625 list[idx].attr = operational_remove[i].attr;
1626 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1627 idx++;
1628 break;
1630 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1631 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1632 list[idx].attr = operational_remove[i].attr;
1633 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1634 idx++;
1636 break;
1638 case OPERATIONAL_SD_FLAGS:
1639 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1640 continue;
1642 if (controls_flags->sd) {
1643 if (attrs == NULL) {
1644 continue;
1646 if (attrs[0] == NULL) {
1647 continue;
1649 if (ldb_attr_in_list(attrs, "*")) {
1650 continue;
1653 list[idx].attr = operational_remove[i].attr;
1654 list[idx].op = OPERATIONAL_SD_FLAGS;
1655 idx++;
1656 break;
1660 return list;
1663 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1665 struct ldb_context *ldb;
1666 struct operational_context *ac;
1667 struct ldb_request *down_req;
1668 const char **search_attrs = NULL;
1669 unsigned int i, a;
1670 int ret;
1672 /* There are no operational attributes on special DNs */
1673 if (ldb_dn_is_special(req->op.search.base)) {
1674 return ldb_next_request(module, req);
1677 ldb = ldb_module_get_ctx(module);
1679 ac = talloc(req, struct operational_context);
1680 if (ac == NULL) {
1681 return ldb_oom(ldb);
1684 ac->module = module;
1685 ac->req = req;
1686 ac->scope = req->op.search.scope;
1687 ac->attrs = req->op.search.attrs;
1689 /* FIXME: We must copy the tree and keep the original
1690 * unmodified. SSS */
1691 /* replace any attributes in the parse tree that are
1692 searchable, but are stored using a different name in the
1693 backend */
1694 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1695 ldb_parse_tree_attr_replace(req->op.search.tree,
1696 parse_tree_sub[i].attr,
1697 parse_tree_sub[i].replace);
1700 ac->controls_flags = talloc(ac, struct op_controls_flags);
1701 /* remember if the SD_FLAGS_OID was set */
1702 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1703 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1704 ac->controls_flags->bypassoperational =
1705 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1707 ac->attrs_to_replace = NULL;
1708 ac->attrs_to_replace_size = 0;
1709 /* in the list of attributes we are looking for, rename any
1710 attributes to the alias for any hidden attributes that can
1711 be fetched directly using non-hidden names.
1712 Note that order here can affect performance, e.g. we should process
1713 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1714 latter is also dependent on the PSO information) */
1715 for (a=0;ac->attrs && ac->attrs[a];a++) {
1716 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1717 continue;
1719 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1721 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1722 continue;
1725 ac->attrs_to_replace = talloc_realloc(ac,
1726 ac->attrs_to_replace,
1727 struct op_attributes_replace,
1728 ac->attrs_to_replace_size + 1);
1730 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1731 ac->attrs_to_replace_size++;
1732 if (!search_sub[i].replace) {
1733 continue;
1736 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1737 unsigned int j;
1738 const char **search_attrs2;
1739 /* Only adds to the end of the list */
1740 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1741 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1742 ? search_attrs
1743 : ac->attrs,
1744 search_sub[i].extra_attrs[j]);
1745 if (search_attrs2 == NULL) {
1746 return ldb_operr(ldb);
1748 /* may be NULL, talloc_free() doesn't mind */
1749 talloc_free(search_attrs);
1750 search_attrs = search_attrs2;
1754 if (!search_attrs) {
1755 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1756 if (search_attrs == NULL) {
1757 return ldb_operr(ldb);
1760 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1761 search_attrs[a] = search_sub[i].replace;
1764 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1765 search_attrs == NULL?req->op.search.attrs:search_attrs,
1766 ac->controls_flags);
1767 ac->list_operations_size = 0;
1768 i = 0;
1770 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1771 i++;
1773 ac->list_operations_size = i;
1774 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1775 req->op.search.base,
1776 req->op.search.scope,
1777 req->op.search.tree,
1778 /* use new set of attrs if any */
1779 search_attrs == NULL?req->op.search.attrs:search_attrs,
1780 req->controls,
1781 ac, operational_callback,
1782 req);
1783 LDB_REQ_SET_LOCATION(down_req);
1784 if (ret != LDB_SUCCESS) {
1785 return ldb_operr(ldb);
1788 /* perform the search */
1789 return ldb_next_request(module, down_req);
1792 static int operational_init(struct ldb_module *ctx)
1794 struct operational_data *data;
1795 int ret;
1797 ret = ldb_next_init(ctx);
1799 if (ret != LDB_SUCCESS) {
1800 return ret;
1803 data = talloc_zero(ctx, struct operational_data);
1804 if (!data) {
1805 return ldb_module_oom(ctx);
1808 ldb_module_set_private(ctx, data);
1810 return LDB_SUCCESS;
1813 static const struct ldb_module_ops ldb_operational_module_ops = {
1814 .name = "operational",
1815 .search = operational_search,
1816 .init_context = operational_init
1819 int ldb_operational_module_init(const char *version)
1821 LDB_MODULE_CHECK_VERSION(version);
1822 return ldb_register_module(&ldb_operational_module_ops);