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);
997 domain_dn = ldb_get_default_basedn(ldb);
998 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
999 "CN=Password Settings Container,CN=System,%s",
1000 ldb_dn_get_linearized(domain_dn));
1001 if (psc_dn == NULL) {
1002 return ldb_oom(ldb);
1005 /* get the number of PSO children */
1006 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1007 LDB_SCOPE_ONELEVEL, attrs,
1008 DSDB_FLAG_NEXT_MODULE, parent,
1009 "(objectClass=msDS-PasswordSettings)");
1010 if (ret != LDB_SUCCESS) {
1014 *pso_count = res->count;
1016 talloc_free(psc_dn);
1022 * Compares two PSO objects returned by a search, to work out the better PSO.
1023 * The PSO with the lowest precedence is better, otherwise (if the precedence
1024 * is equal) the PSO with the lower GUID wins.
1026 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2,
1027 TALLOC_CTX *mem_ctx)
1032 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1034 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1037 /* if precedence is equal, use the lowest GUID */
1038 if (prec1 == prec2) {
1039 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1040 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1042 return ndr_guid_compare(&guid1, &guid2);
1044 return prec1 - prec2;
1049 * Search for PSO objects that apply to the object SIDs specified
1051 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1052 struct ldb_request *parent,
1053 struct dom_sid *sid_array, unsigned int num_sids,
1054 struct ldb_result **result)
1058 struct ldb_context *ldb = ldb_module_get_ctx(module);
1059 char *sid_filter = NULL;
1060 struct ldb_dn *domain_dn = NULL;
1061 struct ldb_dn *psc_dn = NULL;
1062 const char *attrs[] = {
1063 "msDS-PasswordSettingsPrecedence",
1065 "msDS-LockoutDuration",
1066 "msDS-MaximumPasswordAge",
1070 /* build a query for PSO objects that apply to any of the SIDs given */
1071 sid_filter = talloc_strdup(mem_ctx, "");
1073 for (i = 0; sid_filter && i < num_sids; i++) {
1074 char sid_buf[DOM_SID_STR_BUFLEN] = {0,};
1076 dom_sid_string_buf(&sid_array[i], sid_buf, sizeof(sid_buf));
1078 sid_filter = talloc_asprintf_append(sid_filter,
1079 "(msDS-PSOAppliesTo=<SID=%s>)",
1083 if (sid_filter == NULL) {
1084 return ldb_oom(ldb);
1087 /* only PSOs located in the Password Settings Container are valid */
1088 domain_dn = ldb_get_default_basedn(ldb);
1089 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1090 "CN=Password Settings Container,CN=System,%s",
1091 ldb_dn_get_linearized(domain_dn));
1092 if (psc_dn == NULL) {
1093 return ldb_oom(ldb);
1096 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1097 LDB_SCOPE_ONELEVEL, attrs,
1098 DSDB_FLAG_NEXT_MODULE, parent,
1099 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1101 talloc_free(sid_filter);
1106 * Returns the best PSO object that applies to the object SID(s) specified
1108 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1109 struct ldb_request *parent, struct dom_sid *sid_array,
1110 unsigned int num_sids, struct ldb_message **best_pso)
1112 struct ldb_result *res = NULL;
1117 /* find any PSOs that apply to the SIDs specified */
1118 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1120 if (ret != LDB_SUCCESS) {
1121 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1125 /* sort the list so that the best PSO is first */
1126 LDB_TYPESAFE_QSORT(res->msgs, res->count, mem_ctx, pso_compare);
1128 if (res->count > 0) {
1129 *best_pso = res->msgs[0];
1136 * Determines the Password Settings Object (PSO) that applies to the given user
1138 static int get_pso_for_user(struct ldb_module *module,
1139 struct ldb_message *user_msg,
1140 struct ldb_request *parent,
1141 struct ldb_message **pso_msg)
1144 struct dom_sid *groupSIDs = NULL;
1145 unsigned int num_groupSIDs = 0;
1146 struct ldb_context *ldb = ldb_module_get_ctx(module);
1147 struct ldb_message *best_pso = NULL;
1148 struct ldb_dn *pso_dn = NULL;
1150 struct ldb_message_element *el = NULL;
1151 TALLOC_CTX *tmp_ctx = NULL;
1153 struct ldb_result *res = NULL;
1154 static const char *attrs[] = {
1155 "msDS-LockoutDuration",
1156 "msDS-MaximumPasswordAge",
1162 /* first, check msDS-ResultantPSO is supported for this object */
1163 pso_supported = pso_is_supported(ldb, user_msg);
1165 if (!pso_supported) {
1169 tmp_ctx = talloc_new(user_msg);
1172 * Several different constructed attributes try to use the PSO info. If
1173 * we've already constructed the msDS-ResultantPSO for this user, we can
1174 * just re-use the result, rather than calculating it from scratch again
1176 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1177 "msDS-ResultantPSO");
1179 if (pso_dn != NULL) {
1180 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1181 attrs, DSDB_FLAG_NEXT_MODULE,
1183 if (ret != LDB_SUCCESS) {
1184 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1185 ldb_dn_get_linearized(pso_dn));
1186 talloc_free(tmp_ctx);
1190 if (res->count == 1) {
1191 *pso_msg = res->msgs[0];
1197 * if any PSOs apply directly to the user, they are considered first
1198 * before we check group membership PSOs
1200 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1202 if (el != NULL && el->num_values > 0) {
1203 struct dom_sid *user_sid = NULL;
1205 /* lookup the best PSO object, based on the user's SID */
1206 user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
1208 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1210 if (ret != LDB_SUCCESS) {
1211 talloc_free(tmp_ctx);
1215 if (best_pso != NULL) {
1216 *pso_msg = best_pso;
1222 * If no valid PSO applies directly to the user, then try its groups.
1223 * The group expansion is expensive, so check there are actually
1224 * PSOs in the DB first (which is a quick search). Note in the above
1225 * cases we could tell that a PSO applied to the user, based on info
1226 * already retrieved by the user search.
1228 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1229 if (ret != LDB_SUCCESS) {
1230 DBG_ERR("Error %d determining PSOs in system\n", ret);
1231 talloc_free(tmp_ctx);
1235 if (pso_count == 0) {
1236 talloc_free(tmp_ctx);
1240 /* Work out the SIDs of any account groups the user is a member of */
1241 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1242 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1243 &groupSIDs, &num_groupSIDs);
1244 if (ret != LDB_SUCCESS) {
1245 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1246 ldb_dn_get_linearized(user_msg->dn));
1247 talloc_free(tmp_ctx);
1251 /* lookup the best PSO that applies to any of these groups */
1252 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1253 num_groupSIDs, &best_pso);
1254 if (ret != LDB_SUCCESS) {
1255 talloc_free(tmp_ctx);
1259 *pso_msg = best_pso;
1264 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1265 * Settings Object (PSO) that applies to that user.
1267 static int construct_resultant_pso(struct ldb_module *module,
1268 struct ldb_message *msg,
1269 enum ldb_scope scope,
1270 struct ldb_request *parent)
1272 struct ldb_message *pso = NULL;
1275 /* work out the PSO (if any) that applies to this user */
1276 ret = get_pso_for_user(module, msg, parent, &pso);
1277 if (ret != LDB_SUCCESS) {
1278 DBG_ERR("Couldn't determine PSO for %s\n",
1279 ldb_dn_get_linearized(msg->dn));
1284 DBG_INFO("%s is resultant PSO for user %s\n",
1285 ldb_dn_get_linearized(pso->dn),
1286 ldb_dn_get_linearized(msg->dn));
1287 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1288 ldb_dn_get_linearized(pso->dn));
1291 /* no PSO applies to this user */
1295 struct op_controls_flags {
1297 bool bypassoperational;
1300 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1301 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1308 a list of attribute names that should be substituted in the parse
1309 tree before the search is done
1311 static const struct {
1313 const char *replace;
1314 } parse_tree_sub[] = {
1315 { "createTimeStamp", "whenCreated" },
1316 { "modifyTimeStamp", "whenChanged" }
1320 struct op_attributes_replace {
1322 const char *replace;
1323 const char * const *extra_attrs;
1324 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1327 /* the 'extra_attrs' required for msDS-ResultantPSO */
1328 #define RESULTANT_PSO_COMPUTED_ATTRS \
1329 "msDS-PSOApplied", \
1330 "userAccountControl", \
1332 "msDS-SecondaryKrbTgtNumber", \
1336 * any other constructed attributes that want to work out the PSO also need to
1337 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1339 #define PSO_ATTR_DEPENDENCIES \
1340 RESULTANT_PSO_COMPUTED_ATTRS, \
1343 static const char *objectSid_attr[] =
1350 static const char *objectCategory_attr[] =
1357 static const char *user_account_control_computed_attrs[] =
1361 PSO_ATTR_DEPENDENCIES,
1366 static const char *user_password_expiry_time_computed_attrs[] =
1369 PSO_ATTR_DEPENDENCIES,
1373 static const char *resultant_pso_computed_attrs[] =
1375 RESULTANT_PSO_COMPUTED_ATTRS,
1380 a list of attribute names that are hidden, but can be searched for
1381 using another (non-hidden) name to produce the correct result
1383 static const struct op_attributes_replace search_sub[] = {
1384 { "createTimeStamp", "whenCreated", NULL , NULL },
1385 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1386 { "structuralObjectClass", "objectClass", NULL , NULL },
1387 { "canonicalName", NULL, NULL , construct_canonical_name },
1388 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1389 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1390 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1391 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1392 { "parentGUID", NULL, NULL, construct_parent_guid },
1393 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1394 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1395 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1396 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1397 construct_msds_user_account_control_computed },
1398 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1399 construct_msds_user_password_expiry_time_computed },
1400 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1401 construct_resultant_pso }
1406 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1407 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1408 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1409 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
1413 a list of attributes that may need to be removed from the
1414 underlying db return
1416 Some of these are attributes that were once stored, but are now calculated
1418 struct op_attributes_operations {
1423 static const struct op_attributes_operations operational_remove[] = {
1424 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1425 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1426 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1427 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1428 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1429 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1434 post process a search result record. For any search_sub[] attributes that were
1435 asked for, we need to call the appropriate copy routine to copy the result
1436 into the message, then remove any attributes that we added to the search but
1437 were not asked for by the user
1439 static int operational_search_post_process(struct ldb_module *module,
1440 struct ldb_message *msg,
1441 enum ldb_scope scope,
1442 const char * const *attrs_from_user,
1443 const char * const *attrs_searched_for,
1444 struct op_controls_flags* controls_flags,
1445 struct op_attributes_operations *list,
1446 unsigned int list_size,
1447 struct op_attributes_replace *list_replace,
1448 unsigned int list_replace_size,
1449 struct ldb_request *parent)
1451 struct ldb_context *ldb;
1452 unsigned int i, a = 0;
1453 bool constructed_attributes = false;
1455 ldb = ldb_module_get_ctx(module);
1457 /* removed any attrs that should not be shown to the user */
1458 for (i=0; i < list_size; i++) {
1459 ldb_msg_remove_attr(msg, list[i].attr);
1462 for (a=0; a < list_replace_size; a++) {
1463 if (check_keep_control_for_attribute(controls_flags,
1464 list_replace[a].attr)) {
1468 /* construct the new attribute, using either a supplied
1469 constructor or a simple copy */
1470 constructed_attributes = true;
1471 if (list_replace[a].constructor != NULL) {
1472 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1475 } else if (ldb_msg_copy_attr(msg,
1476 list_replace[a].replace,
1477 list_replace[a].attr) != LDB_SUCCESS) {
1482 /* Deletion of the search helper attributes are needed if:
1483 * - we generated constructed attributes and
1484 * - we aren't requesting all attributes
1486 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1487 for (i=0; i < list_replace_size; i++) {
1488 /* remove the added search helper attributes, unless
1489 * they were asked for by the user */
1490 if (list_replace[i].replace != NULL &&
1491 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1492 ldb_msg_remove_attr(msg, list_replace[i].replace);
1494 if (list_replace[i].extra_attrs != NULL) {
1496 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1497 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1498 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1508 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1509 "operational_search_post_process failed for attribute '%s' - %s",
1510 list_replace[a].attr, ldb_errstring(ldb));
1515 hook search operations
1518 struct operational_context {
1519 struct ldb_module *module;
1520 struct ldb_request *req;
1521 enum ldb_scope scope;
1522 const char * const *attrs;
1523 struct op_controls_flags* controls_flags;
1524 struct op_attributes_operations *list_operations;
1525 unsigned int list_operations_size;
1526 struct op_attributes_replace *attrs_to_replace;
1527 unsigned int attrs_to_replace_size;
1530 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1532 struct operational_context *ac;
1535 ac = talloc_get_type(req->context, struct operational_context);
1538 return ldb_module_done(ac->req, NULL, NULL,
1539 LDB_ERR_OPERATIONS_ERROR);
1541 if (ares->error != LDB_SUCCESS) {
1542 return ldb_module_done(ac->req, ares->controls,
1543 ares->response, ares->error);
1546 switch (ares->type) {
1547 case LDB_REPLY_ENTRY:
1548 /* for each record returned post-process to add any derived
1549 attributes that have been asked for */
1550 ret = operational_search_post_process(ac->module,
1554 req->op.search.attrs,
1556 ac->list_operations,
1557 ac->list_operations_size,
1558 ac->attrs_to_replace,
1559 ac->attrs_to_replace_size,
1562 return ldb_module_done(ac->req, NULL, NULL,
1563 LDB_ERR_OPERATIONS_ERROR);
1565 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1567 case LDB_REPLY_REFERRAL:
1568 return ldb_module_send_referral(ac->req, ares->referral);
1570 case LDB_REPLY_DONE:
1572 return ldb_module_done(ac->req, ares->controls,
1573 ares->response, LDB_SUCCESS);
1580 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1581 const char* const* attrs,
1582 const char* const* searched_attrs,
1583 struct op_controls_flags* controls_flags)
1587 struct op_attributes_operations *list = talloc_zero_array(ctx,
1588 struct op_attributes_operations,
1589 ARRAY_SIZE(operational_remove) + 1);
1595 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1596 switch (operational_remove[i].op) {
1597 case OPERATIONAL_REMOVE_UNASKED:
1598 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1601 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1604 list[idx].attr = operational_remove[i].attr;
1605 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1609 case OPERATIONAL_REMOVE_ALWAYS:
1610 list[idx].attr = operational_remove[i].attr;
1611 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1615 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1616 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1617 list[idx].attr = operational_remove[i].attr;
1618 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1623 case OPERATIONAL_SD_FLAGS:
1624 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1627 if (controls_flags->sd) {
1628 if (attrs == NULL) {
1631 if (attrs[0] == NULL) {
1634 if (ldb_attr_in_list(attrs, "*")) {
1638 list[idx].attr = operational_remove[i].attr;
1639 list[idx].op = OPERATIONAL_SD_FLAGS;
1648 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1650 struct ldb_context *ldb;
1651 struct operational_context *ac;
1652 struct ldb_request *down_req;
1653 const char **search_attrs = NULL;
1657 /* There are no operational attributes on special DNs */
1658 if (ldb_dn_is_special(req->op.search.base)) {
1659 return ldb_next_request(module, req);
1662 ldb = ldb_module_get_ctx(module);
1664 ac = talloc(req, struct operational_context);
1666 return ldb_oom(ldb);
1669 ac->module = module;
1671 ac->scope = req->op.search.scope;
1672 ac->attrs = req->op.search.attrs;
1674 /* FIXME: We must copy the tree and keep the original
1675 * unmodified. SSS */
1676 /* replace any attributes in the parse tree that are
1677 searchable, but are stored using a different name in the
1679 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1680 ldb_parse_tree_attr_replace(req->op.search.tree,
1681 parse_tree_sub[i].attr,
1682 parse_tree_sub[i].replace);
1685 ac->controls_flags = talloc(ac, struct op_controls_flags);
1686 /* remember if the SD_FLAGS_OID was set */
1687 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1688 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1689 ac->controls_flags->bypassoperational =
1690 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1692 ac->attrs_to_replace = NULL;
1693 ac->attrs_to_replace_size = 0;
1694 /* in the list of attributes we are looking for, rename any
1695 attributes to the alias for any hidden attributes that can
1696 be fetched directly using non-hidden names.
1697 Note that order here can affect performance, e.g. we should process
1698 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1699 latter is also dependent on the PSO information) */
1700 for (a=0;ac->attrs && ac->attrs[a];a++) {
1701 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1704 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1706 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1710 ac->attrs_to_replace = talloc_realloc(ac,
1711 ac->attrs_to_replace,
1712 struct op_attributes_replace,
1713 ac->attrs_to_replace_size + 1);
1715 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1716 ac->attrs_to_replace_size++;
1717 if (!search_sub[i].replace) {
1721 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1723 const char **search_attrs2;
1724 /* Only adds to the end of the list */
1725 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1726 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1729 search_sub[i].extra_attrs[j]);
1730 if (search_attrs2 == NULL) {
1731 return ldb_operr(ldb);
1733 /* may be NULL, talloc_free() doesn't mind */
1734 talloc_free(search_attrs);
1735 search_attrs = search_attrs2;
1739 if (!search_attrs) {
1740 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1741 if (search_attrs == NULL) {
1742 return ldb_operr(ldb);
1745 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1746 search_attrs[a] = search_sub[i].replace;
1749 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1750 search_attrs == NULL?req->op.search.attrs:search_attrs,
1751 ac->controls_flags);
1752 ac->list_operations_size = 0;
1755 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1758 ac->list_operations_size = i;
1759 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1760 req->op.search.base,
1761 req->op.search.scope,
1762 req->op.search.tree,
1763 /* use new set of attrs if any */
1764 search_attrs == NULL?req->op.search.attrs:search_attrs,
1766 ac, operational_callback,
1768 LDB_REQ_SET_LOCATION(down_req);
1769 if (ret != LDB_SUCCESS) {
1770 return ldb_operr(ldb);
1773 /* perform the search */
1774 return ldb_next_request(module, down_req);
1777 static int operational_init(struct ldb_module *ctx)
1779 struct operational_data *data;
1782 ret = ldb_next_init(ctx);
1784 if (ret != LDB_SUCCESS) {
1788 data = talloc_zero(ctx, struct operational_data);
1790 return ldb_module_oom(ctx);
1793 ldb_module_set_private(ctx, data);
1798 static const struct ldb_module_ops ldb_operational_module_ops = {
1799 .name = "operational",
1800 .search = operational_search,
1801 .init_context = operational_init
1804 int ldb_operational_module_init(const char *version)
1806 LDB_MODULE_CHECK_VERSION(version);
1807 return ldb_register_module(&ldb_operational_module_ops);