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
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?
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 #include "auth/auth.h"
82 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
87 struct operational_data {
88 struct ldb_dn *aggregate_dn;
93 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
94 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
97 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
98 * all account groups in a given domain, excluding built-in groups.
99 * (Used internally for msDS-ResultantPSO support)
104 static int get_pso_for_user(struct ldb_module *module,
105 struct ldb_message *user_msg,
106 struct ldb_request *parent,
107 struct ldb_message **pso_msg);
110 construct a canonical name from a message
112 static int construct_canonical_name(struct ldb_module *module,
113 struct ldb_message *msg, enum ldb_scope scope,
114 struct ldb_request *parent)
117 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
118 if (canonicalName == NULL) {
119 return ldb_operr(ldb_module_get_ctx(module));
121 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
125 construct a primary group token for groups from a message
127 static int construct_primary_group_token(struct ldb_module *module,
128 struct ldb_message *msg, enum ldb_scope scope,
129 struct ldb_request *parent)
131 struct ldb_context *ldb;
132 uint32_t primary_group_token;
134 ldb = ldb_module_get_ctx(module);
135 if (ldb_match_msg_objectclass(msg, "group") == 1) {
137 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
138 if (primary_group_token == 0) {
142 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
143 primary_group_token);
150 * Returns the group SIDs for the user in the given LDB message
152 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
153 struct ldb_message *msg, const char *attribute_string,
154 enum search_type type, struct auth_SidAttr **groupSIDs,
155 uint32_t *num_groupSIDs)
157 const char *filter = NULL;
159 struct dom_sid *primary_group_sid;
160 const char *primary_group_string;
161 const char *primary_group_dn;
162 DATA_BLOB primary_group_blob;
163 struct dom_sid *account_sid;
164 const char *account_sid_string;
165 const char *account_sid_dn;
166 DATA_BLOB account_sid_blob;
167 struct dom_sid *domain_sid;
169 /* If it's not a user, it won't have a primaryGroupID */
170 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
174 /* Ensure it has an objectSID too */
175 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
176 if (account_sid == NULL) {
180 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
181 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
182 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
183 } else if (!NT_STATUS_IS_OK(status)) {
184 return LDB_ERR_OPERATIONS_ERROR;
187 primary_group_sid = dom_sid_add_rid(mem_ctx,
189 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
190 if (!primary_group_sid) {
194 /* only return security groups */
196 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
197 filter = talloc_asprintf(mem_ctx,
198 "(&(objectClass=group)"
199 "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
200 "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
201 GROUP_TYPE_SECURITY_ENABLED,
202 GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
204 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
206 filter = talloc_asprintf(mem_ctx,
207 "(&(objectClass=group)"
208 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
209 GROUP_TYPE_SECURITY_ENABLED);
212 /* for RevMembGetAccountGroups, exclude built-in groups */
214 filter = talloc_asprintf(mem_ctx,
215 "(&(objectClass=group)"
216 "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
217 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
218 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
226 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
227 if (!primary_group_string) {
231 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
232 if (!primary_group_dn) {
236 primary_group_blob = data_blob_string_const(primary_group_dn);
238 account_sid_string = dom_sid_string(mem_ctx, account_sid);
239 if (!account_sid_string) {
243 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
244 if (!account_sid_dn) {
248 account_sid_blob = data_blob_string_const(account_sid_dn);
250 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
251 true, /* We don't want to add the object's SID itself,
252 it's not returend in this attribute */
254 mem_ctx, groupSIDs, num_groupSIDs);
256 if (!NT_STATUS_IS_OK(status)) {
257 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
258 attribute_string, account_sid_string,
260 return LDB_ERR_OPERATIONS_ERROR;
263 /* Expands the primary group - this function takes in
264 * memberOf-like values, so we fake one up with the
265 * <SID=S-...> format of DN and then let it expand
266 * them, as long as they meet the filter - so only
267 * domain groups, not builtin groups
269 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
270 mem_ctx, groupSIDs, num_groupSIDs);
271 if (!NT_STATUS_IS_OK(status)) {
272 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
273 attribute_string, account_sid_string,
275 return LDB_ERR_OPERATIONS_ERROR;
282 construct the token groups for SAM objects from a message
284 static int construct_generic_token_groups(struct ldb_module *module,
285 struct ldb_message *msg, enum ldb_scope scope,
286 struct ldb_request *parent,
287 const char *attribute_string,
288 enum search_type type)
290 struct ldb_context *ldb = ldb_module_get_ctx(module);
291 TALLOC_CTX *tmp_ctx = talloc_new(msg);
294 struct auth_SidAttr *groupSIDs = NULL;
295 uint32_t num_groupSIDs = 0;
297 if (scope != LDB_SCOPE_BASE) {
298 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
299 return LDB_ERR_OPERATIONS_ERROR;
302 /* calculate the group SIDs for this object */
303 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
304 &groupSIDs, &num_groupSIDs);
306 if (ret != LDB_SUCCESS) {
307 talloc_free(tmp_ctx);
308 return LDB_ERR_OPERATIONS_ERROR;
311 /* add these SIDs to the search result */
312 for (i=0; i < num_groupSIDs; i++) {
313 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
315 talloc_free(tmp_ctx);
323 static int construct_token_groups(struct ldb_module *module,
324 struct ldb_message *msg, enum ldb_scope scope,
325 struct ldb_request *parent)
328 * TODO: Add in a limiting domain when we start to support
331 return construct_generic_token_groups(module, msg, scope, parent,
336 static int construct_token_groups_no_gc(struct ldb_module *module,
337 struct ldb_message *msg, enum ldb_scope scope,
338 struct ldb_request *parent)
341 * TODO: Add in a limiting domain when we start to support
344 return construct_generic_token_groups(module, msg, scope, parent,
345 "tokenGroupsNoGCAcceptable",
349 static int construct_global_universal_token_groups(struct ldb_module *module,
350 struct ldb_message *msg, enum ldb_scope scope,
351 struct ldb_request *parent)
353 return construct_generic_token_groups(module, msg, scope, parent,
354 "tokenGroupsGlobalAndUniversal",
355 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
358 construct the parent GUID for an entry from a message
360 static int construct_parent_guid(struct ldb_module *module,
361 struct ldb_message *msg, enum ldb_scope scope,
362 struct ldb_request *parent)
364 struct ldb_result *res, *parent_res;
365 const struct ldb_val *parent_guid;
366 const char *attrs[] = { "instanceType", NULL };
367 const char *attrs2[] = { "objectGUID", NULL };
368 uint32_t instanceType;
370 struct ldb_dn *parent_dn;
373 /* determine if the object is NC by instance type */
374 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
375 DSDB_FLAG_NEXT_MODULE |
376 DSDB_SEARCH_SHOW_RECYCLED, parent);
377 if (ret != LDB_SUCCESS) {
381 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
384 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
385 DEBUG(4,(__location__ ": Object %s is NC\n",
386 ldb_dn_get_linearized(msg->dn)));
389 parent_dn = ldb_dn_get_parent(msg, msg->dn);
391 if (parent_dn == NULL) {
392 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
393 ldb_dn_get_linearized(msg->dn)));
394 return LDB_ERR_OTHER;
396 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
397 DSDB_FLAG_NEXT_MODULE |
398 DSDB_SEARCH_SHOW_RECYCLED, parent);
399 /* not NC, so the object should have a parent*/
400 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
401 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
402 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
403 ldb_dn_get_linearized(parent_dn),
404 ldb_dn_get_linearized(msg->dn)));
405 talloc_free(parent_dn);
407 } else if (ret != LDB_SUCCESS) {
408 talloc_free(parent_dn);
411 talloc_free(parent_dn);
413 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
415 talloc_free(parent_res);
416 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
419 v = data_blob_dup_talloc(parent_res, *parent_guid);
421 talloc_free(parent_res);
422 return ldb_oom(ldb_module_get_ctx(module));
424 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
425 talloc_free(parent_res);
429 static int construct_modifyTimeStamp(struct ldb_module *module,
430 struct ldb_message *msg, enum ldb_scope scope,
431 struct ldb_request *parent)
433 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
434 struct ldb_context *ldb = ldb_module_get_ctx(module);
436 /* We may be being called before the init function has finished */
441 /* Try and set this value up, if possible. Don't worry if it
442 * fails, we may not have the DB set up yet.
444 if (!data->aggregate_dn) {
445 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
448 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
450 * If we have the DN for the object with common name = Aggregate and
451 * the request is for this DN then let's do the following:
452 * 1) search the object which changedUSN correspond to the one of the loaded
454 * 2) Get the whenChanged attribute
455 * 3) Generate the modifyTimestamp out of the whenChanged attribute
457 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
458 char *value = ldb_timestring(msg, schema->ts_last_change);
461 return ldb_oom(ldb_module_get_ctx(module));
464 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
466 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
470 construct a subSchemaSubEntry
472 static int construct_subschema_subentry(struct ldb_module *module,
473 struct ldb_message *msg, enum ldb_scope scope,
474 struct ldb_request *parent)
476 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
477 char *subSchemaSubEntry;
479 /* We may be being called before the init function has finished */
484 /* Try and set this value up, if possible. Don't worry if it
485 * fails, we may not have the DB set up yet, and it's not
486 * really vital anyway */
487 if (!data->aggregate_dn) {
488 struct ldb_context *ldb = ldb_module_get_ctx(module);
489 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
492 if (data->aggregate_dn) {
493 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
494 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
500 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
501 struct ldb_message *msg,
502 struct ldb_message_element *object_category)
504 struct ldb_context *ldb;
506 const struct ldb_val *val;
508 ldb = ldb_module_get_ctx(module);
510 DEBUG(4, (__location__ ": Failed to get ldb \n"));
511 return LDB_ERR_OPERATIONS_ERROR;
514 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
516 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
517 (const char *)object_category->values[0].data));
518 return ldb_operr(ldb);
521 val = ldb_dn_get_rdn_val(dn);
523 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
524 ldb_dn_get_linearized(dn)));
525 return ldb_operr(ldb);
528 if (strequal((const char *)val->data, "NTDS-DSA")) {
529 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
531 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
536 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
537 struct ldb_message *msg,
539 struct ldb_request *parent)
541 struct ldb_dn *server_dn;
542 const char *attr_obj_cat[] = { "objectCategory", NULL };
543 struct ldb_result *res;
544 struct ldb_message_element *object_category;
547 server_dn = ldb_dn_copy(msg, dn);
548 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
549 DEBUG(4, (__location__ ": Failed to add child to %s \n",
550 ldb_dn_get_linearized(server_dn)));
551 return ldb_operr(ldb_module_get_ctx(module));
554 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
555 DSDB_FLAG_NEXT_MODULE, parent);
556 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
557 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
558 ldb_dn_get_linearized(server_dn)));
560 } else if (ret != LDB_SUCCESS) {
564 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
565 if (!object_category) {
566 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
567 ldb_dn_get_linearized(res->msgs[0]->dn)));
570 return construct_msds_isrodc_with_dn(module, msg, object_category);
573 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
574 struct ldb_message *msg,
575 struct ldb_request *parent)
578 struct ldb_dn *server_dn;
580 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
582 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
583 /* it's OK if we can't find serverReferenceBL attribute */
584 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
585 ldb_dn_get_linearized(msg->dn)));
587 } else if (ret != LDB_SUCCESS) {
591 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
595 construct msDS-isRODC attr
597 static int construct_msds_isrodc(struct ldb_module *module,
598 struct ldb_message *msg, enum ldb_scope scope,
599 struct ldb_request *parent)
601 struct ldb_message_element * object_class;
602 struct ldb_message_element * object_category;
605 object_class = ldb_msg_find_element(msg, "objectClass");
607 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
608 ldb_dn_get_linearized(msg->dn)));
609 return ldb_operr(ldb_module_get_ctx(module));
612 for (i=0; i<object_class->num_values; i++) {
613 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
614 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
615 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
617 object_category = ldb_msg_find_element(msg, "objectCategory");
618 if (!object_category) {
619 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
620 ldb_dn_get_linearized(msg->dn)));
623 return construct_msds_isrodc_with_dn(module, msg, object_category);
625 if (strequal((const char*)object_class->values[i].data, "server")) {
626 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
627 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
628 * substituting TN for TO.
630 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
632 if (strequal((const char*)object_class->values[i].data, "computer")) {
633 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
634 * rule for the "TO is a server object" case, substituting TS for TO.
636 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
645 construct msDS-keyVersionNumber attr
647 TODO: Make this based on the 'win2k' DS huristics bit...
650 static int construct_msds_keyversionnumber(struct ldb_module *module,
651 struct ldb_message *msg,
652 enum ldb_scope scope,
653 struct ldb_request *parent)
656 enum ndr_err_code ndr_err;
657 const struct ldb_val *omd_value;
658 struct replPropertyMetaDataBlob *omd;
661 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
663 /* We can't make up a key version number without meta data */
667 omd = talloc(msg, struct replPropertyMetaDataBlob);
669 ldb_module_oom(module);
673 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
674 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
675 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
676 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
677 ldb_dn_get_linearized(msg->dn)));
678 return ldb_operr(ldb_module_get_ctx(module));
681 if (omd->version != 1) {
682 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
683 omd->version, ldb_dn_get_linearized(msg->dn)));
687 for (i=0; i<omd->ctr.ctr1.count; i++) {
688 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
689 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
691 "msDS-KeyVersionNumber",
692 omd->ctr.ctr1.array[i].version);
693 if (ret != LDB_SUCCESS) {
704 #define _UF_TRUST_ACCOUNTS ( \
705 UF_WORKSTATION_TRUST_ACCOUNT | \
706 UF_SERVER_TRUST_ACCOUNT | \
707 UF_INTERDOMAIN_TRUST_ACCOUNT \
709 #define _UF_NO_EXPIRY_ACCOUNTS ( \
710 UF_SMARTCARD_REQUIRED | \
711 UF_DONT_EXPIRE_PASSWD | \
717 * Returns the Effective-MaximumPasswordAge for a user
719 static int64_t get_user_max_pwd_age(struct ldb_module *module,
720 struct ldb_message *user_msg,
721 struct ldb_request *parent,
722 struct ldb_dn *nc_root)
725 struct ldb_message *pso = NULL;
726 struct ldb_context *ldb = ldb_module_get_ctx(module);
728 /* if a PSO applies to the user, use its maxPwdAge */
729 ret = get_pso_for_user(module, user_msg, parent, &pso);
730 if (ret != LDB_SUCCESS) {
732 /* log the error, but fallback to the domain default */
733 DBG_ERR("Error retrieving PSO for %s\n",
734 ldb_dn_get_linearized(user_msg->dn));
738 return ldb_msg_find_attr_as_int64(pso,
739 "msDS-MaximumPasswordAge", 0);
742 /* otherwise return the default domain value */
743 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
747 calculate msDS-UserPasswordExpiryTimeComputed
749 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
750 struct ldb_message *msg,
751 struct ldb_request *parent,
752 struct ldb_dn *domain_dn)
754 int64_t pwdLastSet, maxPwdAge;
755 uint32_t userAccountControl;
758 userAccountControl = ldb_msg_find_attr_as_uint(msg,
759 "userAccountControl",
761 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
762 return 0x7FFFFFFFFFFFFFFFULL;
765 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
766 if (pwdLastSet == 0) {
770 if (pwdLastSet <= -1) {
772 * This can't really happen...
774 return 0x7FFFFFFFFFFFFFFFULL;
777 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
779 * Somethings wrong with the clock...
781 return 0x7FFFFFFFFFFFFFFFULL;
785 * Note that maxPwdAge is a stored as negative value.
787 * Possible values are in the range of:
789 * maxPwdAge: -864000000001
791 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
794 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
795 if (maxPwdAge >= -864000000000) {
797 * This is not really possible...
799 return 0x7FFFFFFFFFFFFFFFULL;
802 if (maxPwdAge == -0x8000000000000000LL) {
803 return 0x7FFFFFFFFFFFFFFFULL;
807 * Note we already caught maxPwdAge == -0x8000000000000000ULL
808 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
810 * Remember maxPwdAge is a negative number,
811 * so it results in the following.
813 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
815 * 0xFFFFFFFFFFFFFFFDULL
817 * or to put it another way, adding two numbers less than 1<<63 can't
818 * ever be more than 1<<64, therefore this result can't wrap.
820 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
821 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
822 return 0x7FFFFFFFFFFFFFFFULL;
829 * Returns the Effective-LockoutDuration for a user
831 static int64_t get_user_lockout_duration(struct ldb_module *module,
832 struct ldb_message *user_msg,
833 struct ldb_request *parent,
834 struct ldb_dn *nc_root)
837 struct ldb_message *pso = NULL;
838 struct ldb_context *ldb = ldb_module_get_ctx(module);
840 /* if a PSO applies to the user, use its lockoutDuration */
841 ret = get_pso_for_user(module, user_msg, parent, &pso);
842 if (ret != LDB_SUCCESS) {
844 /* log the error, but fallback to the domain default */
845 DBG_ERR("Error retrieving PSO for %s\n",
846 ldb_dn_get_linearized(user_msg->dn));
850 return ldb_msg_find_attr_as_int64(pso,
851 "msDS-LockoutDuration", 0);
854 /* otherwise return the default domain value */
855 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
860 construct msDS-User-Account-Control-Computed attr
862 static int construct_msds_user_account_control_computed(struct ldb_module *module,
863 struct ldb_message *msg, enum ldb_scope scope,
864 struct ldb_request *parent)
866 uint32_t userAccountControl;
867 uint32_t msDS_User_Account_Control_Computed = 0;
868 struct ldb_context *ldb = ldb_module_get_ctx(module);
870 struct ldb_dn *nc_root;
873 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
875 ldb_asprintf_errstring(ldb,
876 "Failed to find NC root of DN: %s: %s",
877 ldb_dn_get_linearized(msg->dn),
878 ldb_errstring(ldb_module_get_ctx(module)));
881 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
882 /* Only calculate this on our default NC */
885 /* Test account expire time */
886 unix_to_nt_time(&now, time(NULL));
888 userAccountControl = ldb_msg_find_attr_as_uint(msg,
889 "userAccountControl",
891 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
893 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
894 if (lockoutTime != 0) {
895 int64_t lockoutDuration;
897 lockoutDuration = get_user_lockout_duration(module, msg,
901 /* zero locks out until the administrator intervenes */
902 if (lockoutDuration >= 0) {
903 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
904 } else if (lockoutTime - lockoutDuration >= now) {
905 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
910 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
911 NTTIME must_change_time
912 = get_msds_user_password_expiry_time_computed(module,
916 /* check for expired password */
917 if (must_change_time < now) {
918 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
922 return samdb_msg_add_int64(ldb,
924 "msDS-User-Account-Control-Computed",
925 msDS_User_Account_Control_Computed);
929 construct msDS-UserPasswordExpiryTimeComputed
931 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
932 struct ldb_message *msg, enum ldb_scope scope,
933 struct ldb_request *parent)
935 struct ldb_context *ldb = ldb_module_get_ctx(module);
936 struct ldb_dn *nc_root;
937 int64_t password_expiry_time;
940 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
942 ldb_asprintf_errstring(ldb,
943 "Failed to find NC root of DN: %s: %s",
944 ldb_dn_get_linearized(msg->dn),
949 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
950 /* Only calculate this on our default NC */
955 = get_msds_user_password_expiry_time_computed(module, msg,
958 return samdb_msg_add_int64(ldb,
960 "msDS-UserPasswordExpiryTimeComputed",
961 password_expiry_time);
965 * Checks whether the msDS-ResultantPSO attribute is supported for a given
966 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
968 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
970 int functional_level;
974 functional_level = dsdb_functional_level(ldb);
975 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
979 /* msDS-ResultantPSO is only supported for user objects */
980 if (!ldb_match_msg_objectclass(msg, "user")) {
984 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
985 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
986 if (!(uac & UF_NORMAL_ACCOUNT)) {
990 /* skip it if it's the special KRBTGT default account */
991 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
992 if (user_rid == DOMAIN_RID_KRBTGT) {
996 /* ...or if it's a special KRBTGT account for an RODC KDC */
997 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
1005 * Returns the number of PSO objects that exist in the DB
1007 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1008 struct ldb_request *parent, int *pso_count)
1010 static const char * const attrs[] = { NULL };
1012 struct ldb_dn *domain_dn = NULL;
1013 struct ldb_dn *psc_dn = NULL;
1014 struct ldb_result *res = NULL;
1015 struct ldb_context *ldb = ldb_module_get_ctx(module);
1018 domain_dn = ldb_get_default_basedn(ldb);
1019 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1020 "CN=Password Settings Container,CN=System,%s",
1021 ldb_dn_get_linearized(domain_dn));
1022 if (psc_dn == NULL) {
1023 return ldb_oom(ldb);
1026 /* get the number of PSO children */
1027 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1028 LDB_SCOPE_ONELEVEL, attrs,
1029 DSDB_FLAG_NEXT_MODULE, parent,
1030 "(objectClass=msDS-PasswordSettings)");
1033 * Just ignore PSOs if the container doesn't exist. This is a weird
1034 * corner-case where the AD DB was created from a pre-2008 base schema,
1035 * and then the FL was manually upgraded.
1037 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1038 DBG_NOTICE("No Password Settings Container exists\n");
1042 if (ret != LDB_SUCCESS) {
1046 *pso_count = res->count;
1048 talloc_free(psc_dn);
1054 * Compares two PSO objects returned by a search, to work out the better PSO.
1055 * The PSO with the lowest precedence is better, otherwise (if the precedence
1056 * is equal) the PSO with the lower GUID wins.
1058 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1063 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1065 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1068 /* if precedence is equal, use the lowest GUID */
1069 if (prec1 == prec2) {
1070 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1071 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1073 return ndr_guid_compare(&guid1, &guid2);
1075 return prec1 - prec2;
1080 * Search for PSO objects that apply to the object SIDs specified
1082 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1083 struct ldb_request *parent,
1084 struct auth_SidAttr *sid_array, unsigned int num_sids,
1085 struct ldb_result **result)
1089 struct ldb_context *ldb = ldb_module_get_ctx(module);
1090 char *sid_filter = NULL;
1091 struct ldb_dn *domain_dn = NULL;
1092 struct ldb_dn *psc_dn = NULL;
1093 const char *attrs[] = {
1094 "msDS-PasswordSettingsPrecedence",
1096 "msDS-LockoutDuration",
1097 "msDS-MaximumPasswordAge",
1101 /* build a query for PSO objects that apply to any of the SIDs given */
1102 sid_filter = talloc_strdup(mem_ctx, "");
1104 for (i = 0; sid_filter && i < num_sids; i++) {
1105 struct dom_sid_buf sid_buf;
1107 sid_filter = talloc_asprintf_append(
1109 "(msDS-PSOAppliesTo=<SID=%s>)",
1110 dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
1113 if (sid_filter == NULL) {
1114 return ldb_oom(ldb);
1117 /* only PSOs located in the Password Settings Container are valid */
1118 domain_dn = ldb_get_default_basedn(ldb);
1119 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1120 "CN=Password Settings Container,CN=System,%s",
1121 ldb_dn_get_linearized(domain_dn));
1122 if (psc_dn == NULL) {
1123 return ldb_oom(ldb);
1126 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1127 LDB_SCOPE_ONELEVEL, attrs,
1128 DSDB_FLAG_NEXT_MODULE, parent,
1129 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1131 talloc_free(sid_filter);
1136 * Returns the best PSO object that applies to the object SID(s) specified
1138 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1139 struct ldb_request *parent, struct auth_SidAttr *sid_array,
1140 unsigned int num_sids, struct ldb_message **best_pso)
1142 struct ldb_result *res = NULL;
1147 /* find any PSOs that apply to the SIDs specified */
1148 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1150 if (ret != LDB_SUCCESS) {
1151 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1155 /* sort the list so that the best PSO is first */
1156 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1158 if (res->count > 0) {
1159 *best_pso = res->msgs[0];
1166 * Determines the Password Settings Object (PSO) that applies to the given user
1168 static int get_pso_for_user(struct ldb_module *module,
1169 struct ldb_message *user_msg,
1170 struct ldb_request *parent,
1171 struct ldb_message **pso_msg)
1174 struct auth_SidAttr *groupSIDs = NULL;
1175 uint32_t num_groupSIDs = 0;
1176 struct ldb_context *ldb = ldb_module_get_ctx(module);
1177 struct ldb_message *best_pso = NULL;
1178 struct ldb_dn *pso_dn = NULL;
1180 struct ldb_message_element *el = NULL;
1181 TALLOC_CTX *tmp_ctx = NULL;
1183 struct ldb_result *res = NULL;
1184 static const char *attrs[] = {
1185 "msDS-LockoutDuration",
1186 "msDS-MaximumPasswordAge",
1192 /* first, check msDS-ResultantPSO is supported for this object */
1193 pso_supported = pso_is_supported(ldb, user_msg);
1195 if (!pso_supported) {
1199 tmp_ctx = talloc_new(user_msg);
1202 * Several different constructed attributes try to use the PSO info. If
1203 * we've already constructed the msDS-ResultantPSO for this user, we can
1204 * just re-use the result, rather than calculating it from scratch again
1206 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1207 "msDS-ResultantPSO");
1209 if (pso_dn != NULL) {
1210 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1211 attrs, DSDB_FLAG_NEXT_MODULE,
1213 if (ret != LDB_SUCCESS) {
1214 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1215 ldb_dn_get_linearized(pso_dn));
1216 talloc_free(tmp_ctx);
1220 if (res->count == 1) {
1221 *pso_msg = res->msgs[0];
1227 * if any PSOs apply directly to the user, they are considered first
1228 * before we check group membership PSOs
1230 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1232 if (el != NULL && el->num_values > 0) {
1233 struct auth_SidAttr *user_sid = NULL;
1235 /* lookup the best PSO object, based on the user's SID */
1236 user_sid = samdb_result_dom_sid_attrs(
1237 tmp_ctx, user_msg, "objectSid",
1238 SE_GROUP_DEFAULT_FLAGS);
1240 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1242 if (ret != LDB_SUCCESS) {
1243 talloc_free(tmp_ctx);
1247 if (best_pso != NULL) {
1248 *pso_msg = best_pso;
1254 * If no valid PSO applies directly to the user, then try its groups.
1255 * The group expansion is expensive, so check there are actually
1256 * PSOs in the DB first (which is a quick search). Note in the above
1257 * cases we could tell that a PSO applied to the user, based on info
1258 * already retrieved by the user search.
1260 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1261 if (ret != LDB_SUCCESS) {
1262 DBG_ERR("Error %d determining PSOs in system\n", ret);
1263 talloc_free(tmp_ctx);
1267 if (pso_count == 0) {
1268 talloc_free(tmp_ctx);
1272 /* Work out the SIDs of any account groups the user is a member of */
1273 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1274 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1275 &groupSIDs, &num_groupSIDs);
1276 if (ret != LDB_SUCCESS) {
1277 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1278 ldb_dn_get_linearized(user_msg->dn));
1279 talloc_free(tmp_ctx);
1283 /* lookup the best PSO that applies to any of these groups */
1284 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1285 num_groupSIDs, &best_pso);
1286 if (ret != LDB_SUCCESS) {
1287 talloc_free(tmp_ctx);
1291 *pso_msg = best_pso;
1296 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1297 * Settings Object (PSO) that applies to that user.
1299 static int construct_resultant_pso(struct ldb_module *module,
1300 struct ldb_message *msg,
1301 enum ldb_scope scope,
1302 struct ldb_request *parent)
1304 struct ldb_message *pso = NULL;
1307 /* work out the PSO (if any) that applies to this user */
1308 ret = get_pso_for_user(module, msg, parent, &pso);
1309 if (ret != LDB_SUCCESS) {
1310 DBG_ERR("Couldn't determine PSO for %s\n",
1311 ldb_dn_get_linearized(msg->dn));
1316 DBG_INFO("%s is resultant PSO for user %s\n",
1317 ldb_dn_get_linearized(pso->dn),
1318 ldb_dn_get_linearized(msg->dn));
1319 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1320 ldb_dn_get_linearized(pso->dn));
1323 /* no PSO applies to this user */
1327 struct op_controls_flags {
1329 bool bypassoperational;
1332 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1333 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1340 a list of attribute names that should be substituted in the parse
1341 tree before the search is done
1343 static const struct {
1345 const char *replace;
1346 } parse_tree_sub[] = {
1347 { "createTimeStamp", "whenCreated" },
1348 { "modifyTimeStamp", "whenChanged" }
1352 struct op_attributes_replace {
1354 const char *replace;
1355 const char * const *extra_attrs;
1356 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1359 /* the 'extra_attrs' required for msDS-ResultantPSO */
1360 #define RESULTANT_PSO_COMPUTED_ATTRS \
1361 "msDS-PSOApplied", \
1362 "userAccountControl", \
1364 "msDS-SecondaryKrbTgtNumber", \
1368 * any other constructed attributes that want to work out the PSO also need to
1369 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1371 #define PSO_ATTR_DEPENDENCIES \
1372 RESULTANT_PSO_COMPUTED_ATTRS, \
1375 static const char *objectSid_attr[] =
1382 static const char *objectCategory_attr[] =
1389 static const char *user_account_control_computed_attrs[] =
1393 PSO_ATTR_DEPENDENCIES,
1398 static const char *user_password_expiry_time_computed_attrs[] =
1401 PSO_ATTR_DEPENDENCIES,
1405 static const char *resultant_pso_computed_attrs[] =
1407 RESULTANT_PSO_COMPUTED_ATTRS,
1412 a list of attribute names that are hidden, but can be searched for
1413 using another (non-hidden) name to produce the correct result
1415 static const struct op_attributes_replace search_sub[] = {
1416 { "createTimeStamp", "whenCreated", NULL , NULL },
1417 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1418 { "structuralObjectClass", "objectClass", NULL , NULL },
1419 { "canonicalName", NULL, NULL , construct_canonical_name },
1420 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1421 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1422 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1423 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1424 { "parentGUID", "objectGUID", NULL, construct_parent_guid },
1425 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1426 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1427 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1428 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1429 construct_msds_user_account_control_computed },
1430 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1431 construct_msds_user_password_expiry_time_computed },
1432 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1433 construct_resultant_pso }
1438 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1439 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1440 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1441 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
1445 a list of attributes that may need to be removed from the
1446 underlying db return
1448 Some of these are attributes that were once stored, but are now calculated
1450 struct op_attributes_operations {
1455 static const struct op_attributes_operations operational_remove[] = {
1456 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1457 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1458 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1459 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1460 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1461 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1466 post process a search result record. For any search_sub[] attributes that were
1467 asked for, we need to call the appropriate copy routine to copy the result
1468 into the message, then remove any attributes that we added to the search but
1469 were not asked for by the user
1471 static int operational_search_post_process(struct ldb_module *module,
1472 struct ldb_message *msg,
1473 enum ldb_scope scope,
1474 const char * const *attrs_from_user,
1475 const char * const *attrs_searched_for,
1476 struct op_controls_flags* controls_flags,
1477 struct op_attributes_operations *list,
1478 unsigned int list_size,
1479 struct op_attributes_replace *list_replace,
1480 unsigned int list_replace_size,
1481 struct ldb_request *parent)
1483 struct ldb_context *ldb;
1484 unsigned int i, a = 0;
1485 bool constructed_attributes = false;
1487 ldb = ldb_module_get_ctx(module);
1489 /* removed any attrs that should not be shown to the user */
1490 for (i=0; i < list_size; i++) {
1491 ldb_msg_remove_attr(msg, list[i].attr);
1494 for (a=0; a < list_replace_size; a++) {
1495 if (check_keep_control_for_attribute(controls_flags,
1496 list_replace[a].attr)) {
1500 /* construct the new attribute, using either a supplied
1501 constructor or a simple copy */
1502 constructed_attributes = true;
1503 if (list_replace[a].constructor != NULL) {
1504 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1507 } else if (ldb_msg_copy_attr(msg,
1508 list_replace[a].replace,
1509 list_replace[a].attr) != LDB_SUCCESS) {
1514 /* Deletion of the search helper attributes are needed if:
1515 * - we generated constructed attributes and
1516 * - we aren't requesting all attributes
1518 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1519 for (i=0; i < list_replace_size; i++) {
1520 /* remove the added search helper attributes, unless
1521 * they were asked for by the user */
1522 if (list_replace[i].replace != NULL &&
1523 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1524 ldb_msg_remove_attr(msg, list_replace[i].replace);
1526 if (list_replace[i].extra_attrs != NULL) {
1528 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1529 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1530 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1540 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1541 "operational_search_post_process failed for attribute '%s' - %s",
1542 list_replace[a].attr, ldb_errstring(ldb));
1547 hook search operations
1550 struct operational_context {
1551 struct ldb_module *module;
1552 struct ldb_request *req;
1553 enum ldb_scope scope;
1554 const char * const *attrs;
1555 struct op_controls_flags* controls_flags;
1556 struct op_attributes_operations *list_operations;
1557 unsigned int list_operations_size;
1558 struct op_attributes_replace *attrs_to_replace;
1559 unsigned int attrs_to_replace_size;
1562 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1564 struct operational_context *ac;
1567 ac = talloc_get_type(req->context, struct operational_context);
1570 return ldb_module_done(ac->req, NULL, NULL,
1571 LDB_ERR_OPERATIONS_ERROR);
1573 if (ares->error != LDB_SUCCESS) {
1574 return ldb_module_done(ac->req, ares->controls,
1575 ares->response, ares->error);
1578 switch (ares->type) {
1579 case LDB_REPLY_ENTRY:
1580 /* for each record returned post-process to add any derived
1581 attributes that have been asked for */
1582 ret = operational_search_post_process(ac->module,
1586 req->op.search.attrs,
1588 ac->list_operations,
1589 ac->list_operations_size,
1590 ac->attrs_to_replace,
1591 ac->attrs_to_replace_size,
1594 return ldb_module_done(ac->req, NULL, NULL,
1595 LDB_ERR_OPERATIONS_ERROR);
1597 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1599 case LDB_REPLY_REFERRAL:
1600 return ldb_module_send_referral(ac->req, ares->referral);
1602 case LDB_REPLY_DONE:
1604 return ldb_module_done(ac->req, ares->controls,
1605 ares->response, LDB_SUCCESS);
1612 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1613 const char* const* attrs,
1614 const char* const* searched_attrs,
1615 struct op_controls_flags* controls_flags)
1619 struct op_attributes_operations *list = talloc_zero_array(ctx,
1620 struct op_attributes_operations,
1621 ARRAY_SIZE(operational_remove) + 1);
1627 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1628 switch (operational_remove[i].op) {
1629 case OPERATIONAL_REMOVE_UNASKED:
1630 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1633 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1636 list[idx].attr = operational_remove[i].attr;
1637 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1641 case OPERATIONAL_REMOVE_ALWAYS:
1642 list[idx].attr = operational_remove[i].attr;
1643 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1647 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1648 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1649 list[idx].attr = operational_remove[i].attr;
1650 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1655 case OPERATIONAL_SD_FLAGS:
1656 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1659 if (controls_flags->sd) {
1660 if (attrs == NULL) {
1663 if (attrs[0] == NULL) {
1666 if (ldb_attr_in_list(attrs, "*")) {
1670 list[idx].attr = operational_remove[i].attr;
1671 list[idx].op = OPERATIONAL_SD_FLAGS;
1680 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1682 struct ldb_context *ldb;
1683 struct operational_context *ac;
1684 struct ldb_request *down_req;
1685 const char **search_attrs = NULL;
1689 /* There are no operational attributes on special DNs */
1690 if (ldb_dn_is_special(req->op.search.base)) {
1691 return ldb_next_request(module, req);
1694 ldb = ldb_module_get_ctx(module);
1696 ac = talloc(req, struct operational_context);
1698 return ldb_oom(ldb);
1701 ac->module = module;
1703 ac->scope = req->op.search.scope;
1704 ac->attrs = req->op.search.attrs;
1706 /* FIXME: We must copy the tree and keep the original
1707 * unmodified. SSS */
1708 /* replace any attributes in the parse tree that are
1709 searchable, but are stored using a different name in the
1711 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1712 ldb_parse_tree_attr_replace(req->op.search.tree,
1713 parse_tree_sub[i].attr,
1714 parse_tree_sub[i].replace);
1717 ac->controls_flags = talloc(ac, struct op_controls_flags);
1718 /* remember if the SD_FLAGS_OID was set */
1719 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1720 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1721 ac->controls_flags->bypassoperational =
1722 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1724 ac->attrs_to_replace = NULL;
1725 ac->attrs_to_replace_size = 0;
1726 /* in the list of attributes we are looking for, rename any
1727 attributes to the alias for any hidden attributes that can
1728 be fetched directly using non-hidden names.
1729 Note that order here can affect performance, e.g. we should process
1730 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1731 latter is also dependent on the PSO information) */
1732 for (a=0;ac->attrs && ac->attrs[a];a++) {
1733 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1736 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1738 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1742 ac->attrs_to_replace = talloc_realloc(ac,
1743 ac->attrs_to_replace,
1744 struct op_attributes_replace,
1745 ac->attrs_to_replace_size + 1);
1747 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1748 ac->attrs_to_replace_size++;
1749 if (!search_sub[i].replace) {
1753 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1755 const char **search_attrs2;
1756 /* Only adds to the end of the list */
1757 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1758 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1761 search_sub[i].extra_attrs[j]);
1762 if (search_attrs2 == NULL) {
1763 return ldb_operr(ldb);
1765 /* may be NULL, talloc_free() doesn't mind */
1766 talloc_free(search_attrs);
1767 search_attrs = search_attrs2;
1771 if (!search_attrs) {
1772 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1773 if (search_attrs == NULL) {
1774 return ldb_operr(ldb);
1777 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1778 search_attrs[a] = search_sub[i].replace;
1781 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1782 search_attrs == NULL?req->op.search.attrs:search_attrs,
1783 ac->controls_flags);
1784 ac->list_operations_size = 0;
1787 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1790 ac->list_operations_size = i;
1791 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1792 req->op.search.base,
1793 req->op.search.scope,
1794 req->op.search.tree,
1795 /* use new set of attrs if any */
1796 search_attrs == NULL?req->op.search.attrs:search_attrs,
1798 ac, operational_callback,
1800 LDB_REQ_SET_LOCATION(down_req);
1801 if (ret != LDB_SUCCESS) {
1802 return ldb_operr(ldb);
1805 /* perform the search */
1806 return ldb_next_request(module, down_req);
1809 static int operational_init(struct ldb_module *ctx)
1811 struct operational_data *data;
1814 ret = ldb_next_init(ctx);
1816 if (ret != LDB_SUCCESS) {
1820 data = talloc_zero(ctx, struct operational_data);
1822 return ldb_module_oom(ctx);
1825 ldb_module_set_private(ctx, data);
1830 static const struct ldb_module_ops ldb_operational_module_ops = {
1831 .name = "operational",
1832 .search = operational_search,
1833 .init_context = operational_init
1836 int ldb_operational_module_init(const char *version)
1838 LDB_MODULE_CHECK_VERSION(version);
1839 return ldb_register_module(&ldb_operational_module_ops);