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"
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
83 struct operational_data {
84 struct ldb_dn *aggregate_dn;
89 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
90 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
93 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
94 * all account groups in a given domain, excluding built-in groups.
95 * (Used internally for msDS-ResultantPSO support)
100 static int get_pso_for_user(struct ldb_module *module,
101 struct ldb_message *user_msg,
102 struct ldb_request *parent,
103 struct ldb_message **pso_msg);
106 construct a canonical name from a message
108 static int construct_canonical_name(struct ldb_module *module,
109 struct ldb_message *msg, enum ldb_scope scope,
110 struct ldb_request *parent)
113 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
114 if (canonicalName == NULL) {
115 return ldb_operr(ldb_module_get_ctx(module));
117 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
121 construct a primary group token for groups from a message
123 static int construct_primary_group_token(struct ldb_module *module,
124 struct ldb_message *msg, enum ldb_scope scope,
125 struct ldb_request *parent)
127 struct ldb_context *ldb;
128 uint32_t primary_group_token;
130 ldb = ldb_module_get_ctx(module);
131 if (ldb_match_msg_objectclass(msg, "group") == 1) {
133 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
134 if (primary_group_token == 0) {
138 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
139 primary_group_token);
146 * Returns the group SIDs for the user in the given LDB message
148 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
149 struct ldb_message *msg, const char *attribute_string,
150 enum search_type type, struct dom_sid **groupSIDs,
151 unsigned int *num_groupSIDs)
153 const char *filter = NULL;
155 struct dom_sid *primary_group_sid;
156 const char *primary_group_string;
157 const char *primary_group_dn;
158 DATA_BLOB primary_group_blob;
159 struct dom_sid *account_sid;
160 const char *account_sid_string;
161 const char *account_sid_dn;
162 DATA_BLOB account_sid_blob;
163 struct dom_sid *domain_sid;
165 /* If it's not a user, it won't have a primaryGroupID */
166 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
170 /* Ensure it has an objectSID too */
171 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
172 if (account_sid == NULL) {
176 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
177 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
178 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
179 } else if (!NT_STATUS_IS_OK(status)) {
180 return LDB_ERR_OPERATIONS_ERROR;
183 primary_group_sid = dom_sid_add_rid(mem_ctx,
185 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
186 if (!primary_group_sid) {
190 /* only return security groups */
192 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
193 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)))",
194 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
196 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
198 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
199 GROUP_TYPE_SECURITY_ENABLED);
202 /* for RevMembGetAccountGroups, exclude built-in groups */
204 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))",
205 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
213 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
214 if (!primary_group_string) {
218 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
219 if (!primary_group_dn) {
223 primary_group_blob = data_blob_string_const(primary_group_dn);
225 account_sid_string = dom_sid_string(mem_ctx, account_sid);
226 if (!account_sid_string) {
230 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
231 if (!account_sid_dn) {
235 account_sid_blob = data_blob_string_const(account_sid_dn);
237 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
238 true, /* We don't want to add the object's SID itself,
239 it's not returend in this attribute */
241 mem_ctx, groupSIDs, num_groupSIDs);
243 if (!NT_STATUS_IS_OK(status)) {
244 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
245 attribute_string, account_sid_string,
247 return LDB_ERR_OPERATIONS_ERROR;
250 /* Expands the primary group - this function takes in
251 * memberOf-like values, so we fake one up with the
252 * <SID=S-...> format of DN and then let it expand
253 * them, as long as they meet the filter - so only
254 * domain groups, not builtin groups
256 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
257 mem_ctx, groupSIDs, num_groupSIDs);
258 if (!NT_STATUS_IS_OK(status)) {
259 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
260 attribute_string, account_sid_string,
262 return LDB_ERR_OPERATIONS_ERROR;
269 construct the token groups for SAM objects from a message
271 static int construct_generic_token_groups(struct ldb_module *module,
272 struct ldb_message *msg, enum ldb_scope scope,
273 struct ldb_request *parent,
274 const char *attribute_string,
275 enum search_type type)
277 struct ldb_context *ldb = ldb_module_get_ctx(module);
278 TALLOC_CTX *tmp_ctx = talloc_new(msg);
281 struct dom_sid *groupSIDs = NULL;
282 unsigned int num_groupSIDs = 0;
284 if (scope != LDB_SCOPE_BASE) {
285 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
286 return LDB_ERR_OPERATIONS_ERROR;
289 /* calculate the group SIDs for this object */
290 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
291 &groupSIDs, &num_groupSIDs);
293 if (ret != LDB_SUCCESS) {
294 talloc_free(tmp_ctx);
295 return LDB_ERR_OPERATIONS_ERROR;
298 /* add these SIDs to the search result */
299 for (i=0; i < num_groupSIDs; i++) {
300 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
302 talloc_free(tmp_ctx);
310 static int construct_token_groups(struct ldb_module *module,
311 struct ldb_message *msg, enum ldb_scope scope,
312 struct ldb_request *parent)
315 * TODO: Add in a limiting domain when we start to support
318 return construct_generic_token_groups(module, msg, scope, parent,
323 static int construct_token_groups_no_gc(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,
332 "tokenGroupsNoGCAcceptable",
336 static int construct_global_universal_token_groups(struct ldb_module *module,
337 struct ldb_message *msg, enum ldb_scope scope,
338 struct ldb_request *parent)
340 return construct_generic_token_groups(module, msg, scope, parent,
341 "tokenGroupsGlobalAndUniversal",
342 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
345 construct the parent GUID for an entry from a message
347 static int construct_parent_guid(struct ldb_module *module,
348 struct ldb_message *msg, enum ldb_scope scope,
349 struct ldb_request *parent)
351 struct ldb_result *res, *parent_res;
352 const struct ldb_val *parent_guid;
353 const char *attrs[] = { "instanceType", NULL };
354 const char *attrs2[] = { "objectGUID", NULL };
355 uint32_t instanceType;
357 struct ldb_dn *parent_dn;
360 /* determine if the object is NC by instance type */
361 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
362 DSDB_FLAG_NEXT_MODULE |
363 DSDB_SEARCH_SHOW_RECYCLED, parent);
364 if (ret != LDB_SUCCESS) {
368 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
371 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
372 DEBUG(4,(__location__ ": Object %s is NC\n",
373 ldb_dn_get_linearized(msg->dn)));
376 parent_dn = ldb_dn_get_parent(msg, msg->dn);
378 if (parent_dn == NULL) {
379 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
380 ldb_dn_get_linearized(msg->dn)));
381 return LDB_ERR_OTHER;
383 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
384 DSDB_FLAG_NEXT_MODULE |
385 DSDB_SEARCH_SHOW_RECYCLED, parent);
386 /* not NC, so the object should have a parent*/
387 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
388 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
389 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
390 ldb_dn_get_linearized(parent_dn),
391 ldb_dn_get_linearized(msg->dn)));
392 talloc_free(parent_dn);
394 } else if (ret != LDB_SUCCESS) {
395 talloc_free(parent_dn);
398 talloc_free(parent_dn);
400 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
402 talloc_free(parent_res);
403 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
406 v = data_blob_dup_talloc(parent_res, *parent_guid);
408 talloc_free(parent_res);
409 return ldb_oom(ldb_module_get_ctx(module));
411 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
412 talloc_free(parent_res);
416 static int construct_modifyTimeStamp(struct ldb_module *module,
417 struct ldb_message *msg, enum ldb_scope scope,
418 struct ldb_request *parent)
420 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
421 struct ldb_context *ldb = ldb_module_get_ctx(module);
423 /* We may be being called before the init function has finished */
428 /* Try and set this value up, if possible. Don't worry if it
429 * fails, we may not have the DB set up yet.
431 if (!data->aggregate_dn) {
432 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
435 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
437 * If we have the DN for the object with common name = Aggregate and
438 * the request is for this DN then let's do the following:
439 * 1) search the object which changedUSN correspond to the one of the loaded
441 * 2) Get the whenChanged attribute
442 * 3) Generate the modifyTimestamp out of the whenChanged attribute
444 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
445 char *value = ldb_timestring(msg, schema->ts_last_change);
447 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
449 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
453 construct a subSchemaSubEntry
455 static int construct_subschema_subentry(struct ldb_module *module,
456 struct ldb_message *msg, enum ldb_scope scope,
457 struct ldb_request *parent)
459 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
460 char *subSchemaSubEntry;
462 /* We may be being called before the init function has finished */
467 /* Try and set this value up, if possible. Don't worry if it
468 * fails, we may not have the DB set up yet, and it's not
469 * really vital anyway */
470 if (!data->aggregate_dn) {
471 struct ldb_context *ldb = ldb_module_get_ctx(module);
472 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
475 if (data->aggregate_dn) {
476 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
477 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
483 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
484 struct ldb_message *msg,
485 struct ldb_message_element *object_category)
487 struct ldb_context *ldb;
489 const struct ldb_val *val;
491 ldb = ldb_module_get_ctx(module);
493 DEBUG(4, (__location__ ": Failed to get ldb \n"));
494 return LDB_ERR_OPERATIONS_ERROR;
497 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
499 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
500 (const char *)object_category->values[0].data));
501 return ldb_operr(ldb);
504 val = ldb_dn_get_rdn_val(dn);
506 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
507 ldb_dn_get_linearized(dn)));
508 return ldb_operr(ldb);
511 if (strequal((const char *)val->data, "NTDS-DSA")) {
512 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
514 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
519 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
520 struct ldb_message *msg,
522 struct ldb_request *parent)
524 struct ldb_dn *server_dn;
525 const char *attr_obj_cat[] = { "objectCategory", NULL };
526 struct ldb_result *res;
527 struct ldb_message_element *object_category;
530 server_dn = ldb_dn_copy(msg, dn);
531 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
532 DEBUG(4, (__location__ ": Failed to add child to %s \n",
533 ldb_dn_get_linearized(server_dn)));
534 return ldb_operr(ldb_module_get_ctx(module));
537 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
538 DSDB_FLAG_NEXT_MODULE, parent);
539 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
540 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
541 ldb_dn_get_linearized(server_dn)));
543 } else if (ret != LDB_SUCCESS) {
547 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
548 if (!object_category) {
549 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
550 ldb_dn_get_linearized(res->msgs[0]->dn)));
553 return construct_msds_isrodc_with_dn(module, msg, object_category);
556 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
557 struct ldb_message *msg,
558 struct ldb_request *parent)
561 struct ldb_dn *server_dn;
563 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
565 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
566 /* it's OK if we can't find serverReferenceBL attribute */
567 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
568 ldb_dn_get_linearized(msg->dn)));
570 } else if (ret != LDB_SUCCESS) {
574 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
578 construct msDS-isRODC attr
580 static int construct_msds_isrodc(struct ldb_module *module,
581 struct ldb_message *msg, enum ldb_scope scope,
582 struct ldb_request *parent)
584 struct ldb_message_element * object_class;
585 struct ldb_message_element * object_category;
588 object_class = ldb_msg_find_element(msg, "objectClass");
590 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
591 ldb_dn_get_linearized(msg->dn)));
592 return ldb_operr(ldb_module_get_ctx(module));
595 for (i=0; i<object_class->num_values; i++) {
596 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
597 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
598 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
600 object_category = ldb_msg_find_element(msg, "objectCategory");
601 if (!object_category) {
602 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
603 ldb_dn_get_linearized(msg->dn)));
606 return construct_msds_isrodc_with_dn(module, msg, object_category);
608 if (strequal((const char*)object_class->values[i].data, "server")) {
609 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
610 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
611 * substituting TN for TO.
613 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
615 if (strequal((const char*)object_class->values[i].data, "computer")) {
616 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
617 * rule for the "TO is a server object" case, substituting TS for TO.
619 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
628 construct msDS-keyVersionNumber attr
630 TODO: Make this based on the 'win2k' DS huristics bit...
633 static int construct_msds_keyversionnumber(struct ldb_module *module,
634 struct ldb_message *msg,
635 enum ldb_scope scope,
636 struct ldb_request *parent)
639 enum ndr_err_code ndr_err;
640 const struct ldb_val *omd_value;
641 struct replPropertyMetaDataBlob *omd;
644 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
646 /* We can't make up a key version number without meta data */
650 omd = talloc(msg, struct replPropertyMetaDataBlob);
652 ldb_module_oom(module);
656 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
657 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
658 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
659 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
660 ldb_dn_get_linearized(msg->dn)));
661 return ldb_operr(ldb_module_get_ctx(module));
664 if (omd->version != 1) {
665 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
666 omd->version, ldb_dn_get_linearized(msg->dn)));
670 for (i=0; i<omd->ctr.ctr1.count; i++) {
671 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
672 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
674 "msDS-KeyVersionNumber",
675 omd->ctr.ctr1.array[i].version);
676 if (ret != LDB_SUCCESS) {
687 #define _UF_TRUST_ACCOUNTS ( \
688 UF_WORKSTATION_TRUST_ACCOUNT | \
689 UF_SERVER_TRUST_ACCOUNT | \
690 UF_INTERDOMAIN_TRUST_ACCOUNT \
692 #define _UF_NO_EXPIRY_ACCOUNTS ( \
693 UF_SMARTCARD_REQUIRED | \
694 UF_DONT_EXPIRE_PASSWD | \
700 * Returns the Effective-MaximumPasswordAge for a user
702 static int64_t get_user_max_pwd_age(struct ldb_module *module,
703 struct ldb_message *user_msg,
704 struct ldb_request *parent,
705 struct ldb_dn *nc_root)
708 struct ldb_message *pso = NULL;
709 struct ldb_context *ldb = ldb_module_get_ctx(module);
711 /* if a PSO applies to the user, use its maxPwdAge */
712 ret = get_pso_for_user(module, user_msg, parent, &pso);
713 if (ret != LDB_SUCCESS) {
715 /* log the error, but fallback to the domain default */
716 DBG_ERR("Error retrieving PSO for %s\n",
717 ldb_dn_get_linearized(user_msg->dn));
721 return ldb_msg_find_attr_as_int64(pso,
722 "msDS-MaximumPasswordAge", 0);
725 /* otherwise return the default domain value */
726 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
730 calculate msDS-UserPasswordExpiryTimeComputed
732 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
733 struct ldb_message *msg,
734 struct ldb_request *parent,
735 struct ldb_dn *domain_dn)
737 int64_t pwdLastSet, maxPwdAge;
738 uint32_t userAccountControl;
741 userAccountControl = ldb_msg_find_attr_as_uint(msg,
742 "userAccountControl",
744 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
745 return 0x7FFFFFFFFFFFFFFFULL;
748 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
749 if (pwdLastSet == 0) {
753 if (pwdLastSet <= -1) {
755 * This can't really happen...
757 return 0x7FFFFFFFFFFFFFFFULL;
760 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
762 * Somethings wrong with the clock...
764 return 0x7FFFFFFFFFFFFFFFULL;
768 * Note that maxPwdAge is a stored as negative value.
770 * Possible values are in the range of:
772 * maxPwdAge: -864000000001
774 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
777 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
778 if (maxPwdAge >= -864000000000) {
780 * This is not really possible...
782 return 0x7FFFFFFFFFFFFFFFULL;
785 if (maxPwdAge == -0x8000000000000000LL) {
786 return 0x7FFFFFFFFFFFFFFFULL;
790 * Note we already catched maxPwdAge == -0x8000000000000000ULL
791 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
793 * Remember maxPwdAge is a negative number,
794 * so it results in the following.
796 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
798 * 0xFFFFFFFFFFFFFFFFULL
800 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
801 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
802 return 0x7FFFFFFFFFFFFFFFULL;
809 * Returns the Effective-LockoutDuration for a user
811 static int64_t get_user_lockout_duration(struct ldb_module *module,
812 struct ldb_message *user_msg,
813 struct ldb_request *parent,
814 struct ldb_dn *nc_root)
817 struct ldb_message *pso = NULL;
818 struct ldb_context *ldb = ldb_module_get_ctx(module);
820 /* if a PSO applies to the user, use its lockoutDuration */
821 ret = get_pso_for_user(module, user_msg, parent, &pso);
822 if (ret != LDB_SUCCESS) {
824 /* log the error, but fallback to the domain default */
825 DBG_ERR("Error retrieving PSO for %s\n",
826 ldb_dn_get_linearized(user_msg->dn));
830 return ldb_msg_find_attr_as_int64(pso,
831 "msDS-LockoutDuration", 0);
834 /* otherwise return the default domain value */
835 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
840 construct msDS-User-Account-Control-Computed attr
842 static int construct_msds_user_account_control_computed(struct ldb_module *module,
843 struct ldb_message *msg, enum ldb_scope scope,
844 struct ldb_request *parent)
846 uint32_t userAccountControl;
847 uint32_t msDS_User_Account_Control_Computed = 0;
848 struct ldb_context *ldb = ldb_module_get_ctx(module);
850 struct ldb_dn *nc_root;
853 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
855 ldb_asprintf_errstring(ldb,
856 "Failed to find NC root of DN: %s: %s",
857 ldb_dn_get_linearized(msg->dn),
858 ldb_errstring(ldb_module_get_ctx(module)));
861 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
862 /* Only calculate this on our default NC */
865 /* Test account expire time */
866 unix_to_nt_time(&now, time(NULL));
868 userAccountControl = ldb_msg_find_attr_as_uint(msg,
869 "userAccountControl",
871 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
873 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
874 if (lockoutTime != 0) {
875 int64_t lockoutDuration;
877 lockoutDuration = get_user_lockout_duration(module, msg,
881 /* zero locks out until the administrator intervenes */
882 if (lockoutDuration >= 0) {
883 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
884 } else if (lockoutTime - lockoutDuration >= now) {
885 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
890 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
891 NTTIME must_change_time
892 = get_msds_user_password_expiry_time_computed(module,
896 /* check for expired password */
897 if (must_change_time < now) {
898 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
902 return samdb_msg_add_int64(ldb,
904 "msDS-User-Account-Control-Computed",
905 msDS_User_Account_Control_Computed);
909 construct msDS-UserPasswordExpiryTimeComputed
911 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
912 struct ldb_message *msg, enum ldb_scope scope,
913 struct ldb_request *parent)
915 struct ldb_context *ldb = ldb_module_get_ctx(module);
916 struct ldb_dn *nc_root;
917 int64_t password_expiry_time;
920 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
922 ldb_asprintf_errstring(ldb,
923 "Failed to find NC root of DN: %s: %s",
924 ldb_dn_get_linearized(msg->dn),
929 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
930 /* Only calculate this on our default NC */
935 = get_msds_user_password_expiry_time_computed(module, msg,
938 return samdb_msg_add_int64(ldb,
940 "msDS-UserPasswordExpiryTimeComputed",
941 password_expiry_time);
945 * Checks whether the msDS-ResultantPSO attribute is supported for a given
946 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
948 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
950 int functional_level;
954 functional_level = dsdb_functional_level(ldb);
955 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
959 /* msDS-ResultantPSO is only supported for user objects */
960 if (!ldb_match_msg_objectclass(msg, "user")) {
964 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
965 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
966 if (!(uac & UF_NORMAL_ACCOUNT)) {
970 /* skip it if it's the special KRBTGT default account */
971 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
972 if (user_rid == DOMAIN_RID_KRBTGT) {
976 /* ...or if it's a special KRBTGT account for an RODC KDC */
977 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
985 * Returns the number of PSO objects that exist in the DB
987 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
988 struct ldb_request *parent, int *pso_count)
990 static const char * const attrs[] = { NULL };
992 struct ldb_dn *domain_dn = NULL;
993 struct ldb_dn *psc_dn = NULL;
994 struct ldb_result *res = NULL;
995 struct ldb_context *ldb = ldb_module_get_ctx(module);
998 domain_dn = ldb_get_default_basedn(ldb);
999 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1000 "CN=Password Settings Container,CN=System,%s",
1001 ldb_dn_get_linearized(domain_dn));
1002 if (psc_dn == NULL) {
1003 return ldb_oom(ldb);
1006 /* get the number of PSO children */
1007 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1008 LDB_SCOPE_ONELEVEL, attrs,
1009 DSDB_FLAG_NEXT_MODULE, parent,
1010 "(objectClass=msDS-PasswordSettings)");
1013 * Just ignore PSOs if the container doesn't exist. This is a weird
1014 * corner-case where the AD DB was created from a pre-2008 base schema,
1015 * and then the FL was manually upgraded.
1017 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1018 DBG_NOTICE("No Password Settings Container exists\n");
1022 if (ret != LDB_SUCCESS) {
1026 *pso_count = res->count;
1028 talloc_free(psc_dn);
1034 * Compares two PSO objects returned by a search, to work out the better PSO.
1035 * The PSO with the lowest precedence is better, otherwise (if the precedence
1036 * is equal) the PSO with the lower GUID wins.
1038 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1043 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1045 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1048 /* if precedence is equal, use the lowest GUID */
1049 if (prec1 == prec2) {
1050 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1051 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1053 return ndr_guid_compare(&guid1, &guid2);
1055 return prec1 - prec2;
1060 * Search for PSO objects that apply to the object SIDs specified
1062 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1063 struct ldb_request *parent,
1064 struct dom_sid *sid_array, unsigned int num_sids,
1065 struct ldb_result **result)
1069 struct ldb_context *ldb = ldb_module_get_ctx(module);
1070 char *sid_filter = NULL;
1071 struct ldb_dn *domain_dn = NULL;
1072 struct ldb_dn *psc_dn = NULL;
1073 const char *attrs[] = {
1074 "msDS-PasswordSettingsPrecedence",
1076 "msDS-LockoutDuration",
1077 "msDS-MaximumPasswordAge",
1081 /* build a query for PSO objects that apply to any of the SIDs given */
1082 sid_filter = talloc_strdup(mem_ctx, "");
1084 for (i = 0; sid_filter && i < num_sids; i++) {
1085 struct dom_sid_buf sid_buf;
1087 sid_filter = talloc_asprintf_append(
1089 "(msDS-PSOAppliesTo=<SID=%s>)",
1090 dom_sid_str_buf(&sid_array[i], &sid_buf));
1093 if (sid_filter == NULL) {
1094 return ldb_oom(ldb);
1097 /* only PSOs located in the Password Settings Container are valid */
1098 domain_dn = ldb_get_default_basedn(ldb);
1099 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1100 "CN=Password Settings Container,CN=System,%s",
1101 ldb_dn_get_linearized(domain_dn));
1102 if (psc_dn == NULL) {
1103 return ldb_oom(ldb);
1106 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1107 LDB_SCOPE_ONELEVEL, attrs,
1108 DSDB_FLAG_NEXT_MODULE, parent,
1109 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1111 talloc_free(sid_filter);
1116 * Returns the best PSO object that applies to the object SID(s) specified
1118 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1119 struct ldb_request *parent, struct dom_sid *sid_array,
1120 unsigned int num_sids, struct ldb_message **best_pso)
1122 struct ldb_result *res = NULL;
1127 /* find any PSOs that apply to the SIDs specified */
1128 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1130 if (ret != LDB_SUCCESS) {
1131 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1135 /* sort the list so that the best PSO is first */
1136 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1138 if (res->count > 0) {
1139 *best_pso = res->msgs[0];
1146 * Determines the Password Settings Object (PSO) that applies to the given user
1148 static int get_pso_for_user(struct ldb_module *module,
1149 struct ldb_message *user_msg,
1150 struct ldb_request *parent,
1151 struct ldb_message **pso_msg)
1154 struct dom_sid *groupSIDs = NULL;
1155 unsigned int num_groupSIDs = 0;
1156 struct ldb_context *ldb = ldb_module_get_ctx(module);
1157 struct ldb_message *best_pso = NULL;
1158 struct ldb_dn *pso_dn = NULL;
1160 struct ldb_message_element *el = NULL;
1161 TALLOC_CTX *tmp_ctx = NULL;
1163 struct ldb_result *res = NULL;
1164 static const char *attrs[] = {
1165 "msDS-LockoutDuration",
1166 "msDS-MaximumPasswordAge",
1172 /* first, check msDS-ResultantPSO is supported for this object */
1173 pso_supported = pso_is_supported(ldb, user_msg);
1175 if (!pso_supported) {
1179 tmp_ctx = talloc_new(user_msg);
1182 * Several different constructed attributes try to use the PSO info. If
1183 * we've already constructed the msDS-ResultantPSO for this user, we can
1184 * just re-use the result, rather than calculating it from scratch again
1186 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1187 "msDS-ResultantPSO");
1189 if (pso_dn != NULL) {
1190 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1191 attrs, DSDB_FLAG_NEXT_MODULE,
1193 if (ret != LDB_SUCCESS) {
1194 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1195 ldb_dn_get_linearized(pso_dn));
1196 talloc_free(tmp_ctx);
1200 if (res->count == 1) {
1201 *pso_msg = res->msgs[0];
1207 * if any PSOs apply directly to the user, they are considered first
1208 * before we check group membership PSOs
1210 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1212 if (el != NULL && el->num_values > 0) {
1213 struct dom_sid *user_sid = NULL;
1215 /* lookup the best PSO object, based on the user's SID */
1216 user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
1218 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1220 if (ret != LDB_SUCCESS) {
1221 talloc_free(tmp_ctx);
1225 if (best_pso != NULL) {
1226 *pso_msg = best_pso;
1232 * If no valid PSO applies directly to the user, then try its groups.
1233 * The group expansion is expensive, so check there are actually
1234 * PSOs in the DB first (which is a quick search). Note in the above
1235 * cases we could tell that a PSO applied to the user, based on info
1236 * already retrieved by the user search.
1238 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1239 if (ret != LDB_SUCCESS) {
1240 DBG_ERR("Error %d determining PSOs in system\n", ret);
1241 talloc_free(tmp_ctx);
1245 if (pso_count == 0) {
1246 talloc_free(tmp_ctx);
1250 /* Work out the SIDs of any account groups the user is a member of */
1251 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1252 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1253 &groupSIDs, &num_groupSIDs);
1254 if (ret != LDB_SUCCESS) {
1255 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1256 ldb_dn_get_linearized(user_msg->dn));
1257 talloc_free(tmp_ctx);
1261 /* lookup the best PSO that applies to any of these groups */
1262 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1263 num_groupSIDs, &best_pso);
1264 if (ret != LDB_SUCCESS) {
1265 talloc_free(tmp_ctx);
1269 *pso_msg = best_pso;
1274 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1275 * Settings Object (PSO) that applies to that user.
1277 static int construct_resultant_pso(struct ldb_module *module,
1278 struct ldb_message *msg,
1279 enum ldb_scope scope,
1280 struct ldb_request *parent)
1282 struct ldb_message *pso = NULL;
1285 /* work out the PSO (if any) that applies to this user */
1286 ret = get_pso_for_user(module, msg, parent, &pso);
1287 if (ret != LDB_SUCCESS) {
1288 DBG_ERR("Couldn't determine PSO for %s\n",
1289 ldb_dn_get_linearized(msg->dn));
1294 DBG_INFO("%s is resultant PSO for user %s\n",
1295 ldb_dn_get_linearized(pso->dn),
1296 ldb_dn_get_linearized(msg->dn));
1297 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1298 ldb_dn_get_linearized(pso->dn));
1301 /* no PSO applies to this user */
1305 struct op_controls_flags {
1307 bool bypassoperational;
1310 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1311 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1318 a list of attribute names that should be substituted in the parse
1319 tree before the search is done
1321 static const struct {
1323 const char *replace;
1324 } parse_tree_sub[] = {
1325 { "createTimeStamp", "whenCreated" },
1326 { "modifyTimeStamp", "whenChanged" }
1330 struct op_attributes_replace {
1332 const char *replace;
1333 const char * const *extra_attrs;
1334 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1337 /* the 'extra_attrs' required for msDS-ResultantPSO */
1338 #define RESULTANT_PSO_COMPUTED_ATTRS \
1339 "msDS-PSOApplied", \
1340 "userAccountControl", \
1342 "msDS-SecondaryKrbTgtNumber", \
1346 * any other constructed attributes that want to work out the PSO also need to
1347 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1349 #define PSO_ATTR_DEPENDENCIES \
1350 RESULTANT_PSO_COMPUTED_ATTRS, \
1353 static const char *objectSid_attr[] =
1360 static const char *objectCategory_attr[] =
1367 static const char *user_account_control_computed_attrs[] =
1371 PSO_ATTR_DEPENDENCIES,
1376 static const char *user_password_expiry_time_computed_attrs[] =
1379 PSO_ATTR_DEPENDENCIES,
1383 static const char *resultant_pso_computed_attrs[] =
1385 RESULTANT_PSO_COMPUTED_ATTRS,
1390 a list of attribute names that are hidden, but can be searched for
1391 using another (non-hidden) name to produce the correct result
1393 static const struct op_attributes_replace search_sub[] = {
1394 { "createTimeStamp", "whenCreated", NULL , NULL },
1395 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1396 { "structuralObjectClass", "objectClass", NULL , NULL },
1397 { "canonicalName", NULL, NULL , construct_canonical_name },
1398 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1399 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1400 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1401 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1402 { "parentGUID", NULL, NULL, construct_parent_guid },
1403 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1404 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1405 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1406 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1407 construct_msds_user_account_control_computed },
1408 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1409 construct_msds_user_password_expiry_time_computed },
1410 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1411 construct_resultant_pso }
1416 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1417 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1418 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1419 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
1423 a list of attributes that may need to be removed from the
1424 underlying db return
1426 Some of these are attributes that were once stored, but are now calculated
1428 struct op_attributes_operations {
1433 static const struct op_attributes_operations operational_remove[] = {
1434 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1435 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1436 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1437 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1438 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1439 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1444 post process a search result record. For any search_sub[] attributes that were
1445 asked for, we need to call the appropriate copy routine to copy the result
1446 into the message, then remove any attributes that we added to the search but
1447 were not asked for by the user
1449 static int operational_search_post_process(struct ldb_module *module,
1450 struct ldb_message *msg,
1451 enum ldb_scope scope,
1452 const char * const *attrs_from_user,
1453 const char * const *attrs_searched_for,
1454 struct op_controls_flags* controls_flags,
1455 struct op_attributes_operations *list,
1456 unsigned int list_size,
1457 struct op_attributes_replace *list_replace,
1458 unsigned int list_replace_size,
1459 struct ldb_request *parent)
1461 struct ldb_context *ldb;
1462 unsigned int i, a = 0;
1463 bool constructed_attributes = false;
1465 ldb = ldb_module_get_ctx(module);
1467 /* removed any attrs that should not be shown to the user */
1468 for (i=0; i < list_size; i++) {
1469 ldb_msg_remove_attr(msg, list[i].attr);
1472 for (a=0; a < list_replace_size; a++) {
1473 if (check_keep_control_for_attribute(controls_flags,
1474 list_replace[a].attr)) {
1478 /* construct the new attribute, using either a supplied
1479 constructor or a simple copy */
1480 constructed_attributes = true;
1481 if (list_replace[a].constructor != NULL) {
1482 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1485 } else if (ldb_msg_copy_attr(msg,
1486 list_replace[a].replace,
1487 list_replace[a].attr) != LDB_SUCCESS) {
1492 /* Deletion of the search helper attributes are needed if:
1493 * - we generated constructed attributes and
1494 * - we aren't requesting all attributes
1496 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1497 for (i=0; i < list_replace_size; i++) {
1498 /* remove the added search helper attributes, unless
1499 * they were asked for by the user */
1500 if (list_replace[i].replace != NULL &&
1501 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1502 ldb_msg_remove_attr(msg, list_replace[i].replace);
1504 if (list_replace[i].extra_attrs != NULL) {
1506 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1507 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1508 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1518 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1519 "operational_search_post_process failed for attribute '%s' - %s",
1520 list_replace[a].attr, ldb_errstring(ldb));
1525 hook search operations
1528 struct operational_context {
1529 struct ldb_module *module;
1530 struct ldb_request *req;
1531 enum ldb_scope scope;
1532 const char * const *attrs;
1533 struct op_controls_flags* controls_flags;
1534 struct op_attributes_operations *list_operations;
1535 unsigned int list_operations_size;
1536 struct op_attributes_replace *attrs_to_replace;
1537 unsigned int attrs_to_replace_size;
1540 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1542 struct operational_context *ac;
1545 ac = talloc_get_type(req->context, struct operational_context);
1548 return ldb_module_done(ac->req, NULL, NULL,
1549 LDB_ERR_OPERATIONS_ERROR);
1551 if (ares->error != LDB_SUCCESS) {
1552 return ldb_module_done(ac->req, ares->controls,
1553 ares->response, ares->error);
1556 switch (ares->type) {
1557 case LDB_REPLY_ENTRY:
1558 /* for each record returned post-process to add any derived
1559 attributes that have been asked for */
1560 ret = operational_search_post_process(ac->module,
1564 req->op.search.attrs,
1566 ac->list_operations,
1567 ac->list_operations_size,
1568 ac->attrs_to_replace,
1569 ac->attrs_to_replace_size,
1572 return ldb_module_done(ac->req, NULL, NULL,
1573 LDB_ERR_OPERATIONS_ERROR);
1575 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1577 case LDB_REPLY_REFERRAL:
1578 return ldb_module_send_referral(ac->req, ares->referral);
1580 case LDB_REPLY_DONE:
1582 return ldb_module_done(ac->req, ares->controls,
1583 ares->response, LDB_SUCCESS);
1590 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1591 const char* const* attrs,
1592 const char* const* searched_attrs,
1593 struct op_controls_flags* controls_flags)
1597 struct op_attributes_operations *list = talloc_zero_array(ctx,
1598 struct op_attributes_operations,
1599 ARRAY_SIZE(operational_remove) + 1);
1605 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1606 switch (operational_remove[i].op) {
1607 case OPERATIONAL_REMOVE_UNASKED:
1608 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1611 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1614 list[idx].attr = operational_remove[i].attr;
1615 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1619 case OPERATIONAL_REMOVE_ALWAYS:
1620 list[idx].attr = operational_remove[i].attr;
1621 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1625 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1626 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1627 list[idx].attr = operational_remove[i].attr;
1628 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1633 case OPERATIONAL_SD_FLAGS:
1634 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1637 if (controls_flags->sd) {
1638 if (attrs == NULL) {
1641 if (attrs[0] == NULL) {
1644 if (ldb_attr_in_list(attrs, "*")) {
1648 list[idx].attr = operational_remove[i].attr;
1649 list[idx].op = OPERATIONAL_SD_FLAGS;
1658 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1660 struct ldb_context *ldb;
1661 struct operational_context *ac;
1662 struct ldb_request *down_req;
1663 const char **search_attrs = NULL;
1667 /* There are no operational attributes on special DNs */
1668 if (ldb_dn_is_special(req->op.search.base)) {
1669 return ldb_next_request(module, req);
1672 ldb = ldb_module_get_ctx(module);
1674 ac = talloc(req, struct operational_context);
1676 return ldb_oom(ldb);
1679 ac->module = module;
1681 ac->scope = req->op.search.scope;
1682 ac->attrs = req->op.search.attrs;
1684 /* FIXME: We must copy the tree and keep the original
1685 * unmodified. SSS */
1686 /* replace any attributes in the parse tree that are
1687 searchable, but are stored using a different name in the
1689 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1690 ldb_parse_tree_attr_replace(req->op.search.tree,
1691 parse_tree_sub[i].attr,
1692 parse_tree_sub[i].replace);
1695 ac->controls_flags = talloc(ac, struct op_controls_flags);
1696 /* remember if the SD_FLAGS_OID was set */
1697 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1698 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1699 ac->controls_flags->bypassoperational =
1700 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1702 ac->attrs_to_replace = NULL;
1703 ac->attrs_to_replace_size = 0;
1704 /* in the list of attributes we are looking for, rename any
1705 attributes to the alias for any hidden attributes that can
1706 be fetched directly using non-hidden names.
1707 Note that order here can affect performance, e.g. we should process
1708 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1709 latter is also dependent on the PSO information) */
1710 for (a=0;ac->attrs && ac->attrs[a];a++) {
1711 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1714 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1716 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1720 ac->attrs_to_replace = talloc_realloc(ac,
1721 ac->attrs_to_replace,
1722 struct op_attributes_replace,
1723 ac->attrs_to_replace_size + 1);
1725 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1726 ac->attrs_to_replace_size++;
1727 if (!search_sub[i].replace) {
1731 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1733 const char **search_attrs2;
1734 /* Only adds to the end of the list */
1735 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1736 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1739 search_sub[i].extra_attrs[j]);
1740 if (search_attrs2 == NULL) {
1741 return ldb_operr(ldb);
1743 /* may be NULL, talloc_free() doesn't mind */
1744 talloc_free(search_attrs);
1745 search_attrs = search_attrs2;
1749 if (!search_attrs) {
1750 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1751 if (search_attrs == NULL) {
1752 return ldb_operr(ldb);
1755 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1756 search_attrs[a] = search_sub[i].replace;
1759 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1760 search_attrs == NULL?req->op.search.attrs:search_attrs,
1761 ac->controls_flags);
1762 ac->list_operations_size = 0;
1765 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1768 ac->list_operations_size = i;
1769 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1770 req->op.search.base,
1771 req->op.search.scope,
1772 req->op.search.tree,
1773 /* use new set of attrs if any */
1774 search_attrs == NULL?req->op.search.attrs:search_attrs,
1776 ac, operational_callback,
1778 LDB_REQ_SET_LOCATION(down_req);
1779 if (ret != LDB_SUCCESS) {
1780 return ldb_operr(ldb);
1783 /* perform the search */
1784 return ldb_next_request(module, down_req);
1787 static int operational_init(struct ldb_module *ctx)
1789 struct operational_data *data;
1792 ret = ldb_next_init(ctx);
1794 if (ret != LDB_SUCCESS) {
1798 data = talloc_zero(ctx, struct operational_data);
1800 return ldb_module_oom(ctx);
1803 ldb_module_set_private(ctx, data);
1808 static const struct ldb_module_ops ldb_operational_module_ops = {
1809 .name = "operational",
1810 .search = operational_search,
1811 .init_context = operational_init
1814 int ldb_operational_module_init(const char *version)
1816 LDB_MODULE_CHECK_VERSION(version);
1817 return ldb_register_module(&ldb_operational_module_ops);