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
94 construct a canonical name from a message
96 static int construct_canonical_name(struct ldb_module *module,
97 struct ldb_message *msg, enum ldb_scope scope,
98 struct ldb_request *parent)
101 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
102 if (canonicalName == NULL) {
103 return ldb_operr(ldb_module_get_ctx(module));
105 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
109 construct a primary group token for groups from a message
111 static int construct_primary_group_token(struct ldb_module *module,
112 struct ldb_message *msg, enum ldb_scope scope,
113 struct ldb_request *parent)
115 struct ldb_context *ldb;
116 uint32_t primary_group_token;
118 ldb = ldb_module_get_ctx(module);
119 if (ldb_match_msg_objectclass(msg, "group") == 1) {
121 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
122 if (primary_group_token == 0) {
126 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
127 primary_group_token);
134 construct the token groups for SAM objects from a message
136 static int construct_generic_token_groups(struct ldb_module *module,
137 struct ldb_message *msg, enum ldb_scope scope,
138 struct ldb_request *parent,
139 const char *attribute_string,
140 enum search_type type)
142 struct ldb_context *ldb = ldb_module_get_ctx(module);
143 TALLOC_CTX *tmp_ctx = talloc_new(msg);
150 struct dom_sid *primary_group_sid;
151 const char *primary_group_string;
152 const char *primary_group_dn;
153 DATA_BLOB primary_group_blob;
155 struct dom_sid *account_sid;
156 const char *account_sid_string;
157 const char *account_sid_dn;
158 DATA_BLOB account_sid_blob;
159 struct dom_sid *groupSIDs = NULL;
160 unsigned int num_groupSIDs = 0;
162 struct dom_sid *domain_sid;
164 if (scope != LDB_SCOPE_BASE) {
165 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
166 return LDB_ERR_OPERATIONS_ERROR;
169 /* If it's not a user, it won't have a primaryGroupID */
170 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
171 talloc_free(tmp_ctx);
175 /* Ensure it has an objectSID too */
176 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
177 if (account_sid == NULL) {
178 talloc_free(tmp_ctx);
182 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
183 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
184 talloc_free(tmp_ctx);
185 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
186 } else if (!NT_STATUS_IS_OK(status)) {
187 talloc_free(tmp_ctx);
188 return LDB_ERR_OPERATIONS_ERROR;
191 primary_group_sid = dom_sid_add_rid(tmp_ctx,
193 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
194 if (!primary_group_sid) {
195 talloc_free(tmp_ctx);
199 /* only return security groups */
201 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
202 filter = talloc_asprintf(tmp_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)))",
203 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
205 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
207 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
208 GROUP_TYPE_SECURITY_ENABLED);
213 talloc_free(tmp_ctx);
217 primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
218 if (!primary_group_string) {
219 talloc_free(tmp_ctx);
223 primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
224 if (!primary_group_dn) {
225 talloc_free(tmp_ctx);
229 primary_group_blob = data_blob_string_const(primary_group_dn);
231 account_sid_string = dom_sid_string(tmp_ctx, account_sid);
232 if (!account_sid_string) {
233 talloc_free(tmp_ctx);
237 account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
238 if (!account_sid_dn) {
239 talloc_free(tmp_ctx);
243 account_sid_blob = data_blob_string_const(account_sid_dn);
245 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
246 true, /* We don't want to add the object's SID itself,
247 it's not returend in this attribute */
249 tmp_ctx, &groupSIDs, &num_groupSIDs);
251 if (!NT_STATUS_IS_OK(status)) {
252 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
253 account_sid_string, nt_errstr(status));
254 talloc_free(tmp_ctx);
255 return LDB_ERR_OPERATIONS_ERROR;
258 /* Expands the primary group - this function takes in
259 * memberOf-like values, so we fake one up with the
260 * <SID=S-...> format of DN and then let it expand
261 * them, as long as they meet the filter - so only
262 * domain groups, not builtin groups
264 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
265 tmp_ctx, &groupSIDs, &num_groupSIDs);
266 if (!NT_STATUS_IS_OK(status)) {
267 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
268 account_sid_string, nt_errstr(status));
269 talloc_free(tmp_ctx);
270 return LDB_ERR_OPERATIONS_ERROR;
273 for (i=0; i < num_groupSIDs; i++) {
274 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
276 talloc_free(tmp_ctx);
284 static int construct_token_groups(struct ldb_module *module,
285 struct ldb_message *msg, enum ldb_scope scope,
286 struct ldb_request *parent)
289 * TODO: Add in a limiting domain when we start to support
292 return construct_generic_token_groups(module, msg, scope, parent,
297 static int construct_token_groups_no_gc(struct ldb_module *module,
298 struct ldb_message *msg, enum ldb_scope scope,
299 struct ldb_request *parent)
302 * TODO: Add in a limiting domain when we start to support
305 return construct_generic_token_groups(module, msg, scope, parent,
306 "tokenGroupsNoGCAcceptable",
310 static int construct_global_universal_token_groups(struct ldb_module *module,
311 struct ldb_message *msg, enum ldb_scope scope,
312 struct ldb_request *parent)
314 return construct_generic_token_groups(module, msg, scope, parent,
315 "tokenGroupsGlobalAndUniversal",
316 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
319 construct the parent GUID for an entry from a message
321 static int construct_parent_guid(struct ldb_module *module,
322 struct ldb_message *msg, enum ldb_scope scope,
323 struct ldb_request *parent)
325 struct ldb_result *res, *parent_res;
326 const struct ldb_val *parent_guid;
327 const char *attrs[] = { "instanceType", NULL };
328 const char *attrs2[] = { "objectGUID", NULL };
329 uint32_t instanceType;
331 struct ldb_dn *parent_dn;
334 /* determine if the object is NC by instance type */
335 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
336 DSDB_FLAG_NEXT_MODULE |
337 DSDB_SEARCH_SHOW_RECYCLED, parent);
338 if (ret != LDB_SUCCESS) {
342 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
345 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
346 DEBUG(4,(__location__ ": Object %s is NC\n",
347 ldb_dn_get_linearized(msg->dn)));
350 parent_dn = ldb_dn_get_parent(msg, msg->dn);
352 if (parent_dn == NULL) {
353 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
354 ldb_dn_get_linearized(msg->dn)));
357 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
358 DSDB_FLAG_NEXT_MODULE |
359 DSDB_SEARCH_SHOW_RECYCLED, parent);
360 talloc_free(parent_dn);
362 /* not NC, so the object should have a parent*/
363 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
365 talloc_asprintf(msg, "Parent dn for %s does not exist",
366 ldb_dn_get_linearized(msg->dn)));
367 } else if (ret != LDB_SUCCESS) {
371 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
373 talloc_free(parent_res);
377 v = data_blob_dup_talloc(parent_res, *parent_guid);
379 talloc_free(parent_res);
380 return ldb_oom(ldb_module_get_ctx(module));
382 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
383 talloc_free(parent_res);
387 static int construct_modifyTimeStamp(struct ldb_module *module,
388 struct ldb_message *msg, enum ldb_scope scope,
389 struct ldb_request *parent)
391 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
392 struct ldb_context *ldb = ldb_module_get_ctx(module);
394 /* We may be being called before the init function has finished */
399 /* Try and set this value up, if possible. Don't worry if it
400 * fails, we may not have the DB set up yet.
402 if (!data->aggregate_dn) {
403 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
406 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
408 * If we have the DN for the object with common name = Aggregate and
409 * the request is for this DN then let's do the following:
410 * 1) search the object which changedUSN correspond to the one of the loaded
412 * 2) Get the whenChanged attribute
413 * 3) Generate the modifyTimestamp out of the whenChanged attribute
415 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
416 char *value = ldb_timestring(msg, schema->ts_last_change);
418 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
420 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
424 construct a subSchemaSubEntry
426 static int construct_subschema_subentry(struct ldb_module *module,
427 struct ldb_message *msg, enum ldb_scope scope,
428 struct ldb_request *parent)
430 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
431 char *subSchemaSubEntry;
433 /* We may be being called before the init function has finished */
438 /* Try and set this value up, if possible. Don't worry if it
439 * fails, we may not have the DB set up yet, and it's not
440 * really vital anyway */
441 if (!data->aggregate_dn) {
442 struct ldb_context *ldb = ldb_module_get_ctx(module);
443 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
446 if (data->aggregate_dn) {
447 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
448 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
454 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
455 struct ldb_message *msg,
456 struct ldb_message_element *object_category)
458 struct ldb_context *ldb;
460 const struct ldb_val *val;
462 ldb = ldb_module_get_ctx(module);
464 DEBUG(4, (__location__ ": Failed to get ldb \n"));
465 return ldb_operr(ldb);
468 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
470 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
471 (const char *)object_category->values[0].data));
472 return ldb_operr(ldb);
475 val = ldb_dn_get_rdn_val(dn);
477 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
478 ldb_dn_get_linearized(dn)));
479 return ldb_operr(ldb);
482 if (strequal((const char *)val->data, "NTDS-DSA")) {
483 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
485 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
490 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
491 struct ldb_message *msg,
493 struct ldb_request *parent)
495 struct ldb_dn *server_dn;
496 const char *attr_obj_cat[] = { "objectCategory", NULL };
497 struct ldb_result *res;
498 struct ldb_message_element *object_category;
501 server_dn = ldb_dn_copy(msg, dn);
502 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
503 DEBUG(4, (__location__ ": Failed to add child to %s \n",
504 ldb_dn_get_linearized(server_dn)));
505 return ldb_operr(ldb_module_get_ctx(module));
508 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
509 DSDB_FLAG_NEXT_MODULE, parent);
510 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
511 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
512 ldb_dn_get_linearized(server_dn)));
514 } else if (ret != LDB_SUCCESS) {
518 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
519 if (!object_category) {
520 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
521 ldb_dn_get_linearized(res->msgs[0]->dn)));
524 return construct_msds_isrodc_with_dn(module, msg, object_category);
527 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
528 struct ldb_message *msg,
529 struct ldb_request *parent)
532 struct ldb_dn *server_dn;
534 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
536 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
537 /* it's OK if we can't find serverReferenceBL attribute */
538 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
539 ldb_dn_get_linearized(msg->dn)));
541 } else if (ret != LDB_SUCCESS) {
545 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
549 construct msDS-isRODC attr
551 static int construct_msds_isrodc(struct ldb_module *module,
552 struct ldb_message *msg, enum ldb_scope scope,
553 struct ldb_request *parent)
555 struct ldb_message_element * object_class;
556 struct ldb_message_element * object_category;
559 object_class = ldb_msg_find_element(msg, "objectClass");
561 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
562 ldb_dn_get_linearized(msg->dn)));
563 return ldb_operr(ldb_module_get_ctx(module));
566 for (i=0; i<object_class->num_values; i++) {
567 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
568 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
569 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
571 object_category = ldb_msg_find_element(msg, "objectCategory");
572 if (!object_category) {
573 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
574 ldb_dn_get_linearized(msg->dn)));
577 return construct_msds_isrodc_with_dn(module, msg, object_category);
579 if (strequal((const char*)object_class->values[i].data, "server")) {
580 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
581 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
582 * substituting TN for TO.
584 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
586 if (strequal((const char*)object_class->values[i].data, "computer")) {
587 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
588 * rule for the "TO is a server object" case, substituting TS for TO.
590 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
599 construct msDS-keyVersionNumber attr
601 TODO: Make this based on the 'win2k' DS huristics bit...
604 static int construct_msds_keyversionnumber(struct ldb_module *module,
605 struct ldb_message *msg,
606 enum ldb_scope scope,
607 struct ldb_request *parent)
610 enum ndr_err_code ndr_err;
611 const struct ldb_val *omd_value;
612 struct replPropertyMetaDataBlob *omd;
615 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
617 /* We can't make up a key version number without meta data */
624 omd = talloc(msg, struct replPropertyMetaDataBlob);
626 ldb_module_oom(module);
630 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
631 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
632 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
633 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
634 ldb_dn_get_linearized(msg->dn)));
635 return ldb_operr(ldb_module_get_ctx(module));
638 if (omd->version != 1) {
639 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
640 omd->version, ldb_dn_get_linearized(msg->dn)));
644 for (i=0; i<omd->ctr.ctr1.count; i++) {
645 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
646 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
648 "msDS-KeyVersionNumber",
649 omd->ctr.ctr1.array[i].version);
650 if (ret != LDB_SUCCESS) {
661 #define _UF_TRUST_ACCOUNTS ( \
662 UF_WORKSTATION_TRUST_ACCOUNT | \
663 UF_SERVER_TRUST_ACCOUNT | \
664 UF_INTERDOMAIN_TRUST_ACCOUNT \
666 #define _UF_NO_EXPIRY_ACCOUNTS ( \
667 UF_SMARTCARD_REQUIRED | \
668 UF_DONT_EXPIRE_PASSWD | \
673 calculate msDS-UserPasswordExpiryTimeComputed
675 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
676 struct ldb_message *msg,
677 struct ldb_dn *domain_dn)
679 int64_t pwdLastSet, maxPwdAge;
680 uint32_t userAccountControl;
683 userAccountControl = ldb_msg_find_attr_as_uint(msg,
684 "userAccountControl",
686 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
687 return 0x7FFFFFFFFFFFFFFFULL;
690 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
691 if (pwdLastSet == 0) {
695 if (pwdLastSet <= -1) {
697 * This can't really happen...
699 return 0x7FFFFFFFFFFFFFFFULL;
702 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL) {
704 * Somethings wrong with the clock...
706 return 0x7FFFFFFFFFFFFFFFULL;
710 * Note that maxPwdAge is a stored as negative value.
712 * Possible values are in the range of:
714 * maxPwdAge: -864000000001
716 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
719 maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
720 domain_dn, "maxPwdAge", NULL);
721 if (maxPwdAge >= -864000000000) {
723 * This is not really possible...
725 return 0x7FFFFFFFFFFFFFFFULL;
728 if (maxPwdAge == -0x8000000000000000ULL) {
729 return 0x7FFFFFFFFFFFFFFFULL;
733 * Note we already catched maxPwdAge == -0x8000000000000000ULL
734 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
736 * Remember maxPwdAge is a negative number,
737 * so it results in the following.
739 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
741 * 0xFFFFFFFFFFFFFFFFULL
743 ret = pwdLastSet - maxPwdAge;
744 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
745 return 0x7FFFFFFFFFFFFFFFULL;
753 construct msDS-User-Account-Control-Computed attr
755 static int construct_msds_user_account_control_computed(struct ldb_module *module,
756 struct ldb_message *msg, enum ldb_scope scope,
757 struct ldb_request *parent)
759 uint32_t userAccountControl;
760 uint32_t msDS_User_Account_Control_Computed = 0;
761 struct ldb_context *ldb = ldb_module_get_ctx(module);
763 struct ldb_dn *nc_root;
766 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
768 ldb_asprintf_errstring(ldb,
769 "Failed to find NC root of DN: %s: %s",
770 ldb_dn_get_linearized(msg->dn),
771 ldb_errstring(ldb_module_get_ctx(module)));
774 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
775 /* Only calculate this on our default NC */
778 /* Test account expire time */
779 unix_to_nt_time(&now, time(NULL));
781 userAccountControl = ldb_msg_find_attr_as_uint(msg,
782 "userAccountControl",
784 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
786 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
787 if (lockoutTime != 0) {
788 int64_t lockoutDuration = samdb_search_int64(ldb,
790 "lockoutDuration", NULL);
791 if (lockoutDuration >= 0) {
792 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
793 } else if (lockoutTime - lockoutDuration >= now) {
794 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
799 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
800 NTTIME must_change_time
801 = get_msds_user_password_expiry_time_computed(module,
803 /* check for expired password */
804 if (must_change_time < now) {
805 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
809 return samdb_msg_add_int64(ldb,
811 "msDS-User-Account-Control-Computed",
812 msDS_User_Account_Control_Computed);
816 construct msDS-UserPasswordExpiryTimeComputed
818 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
819 struct ldb_message *msg, enum ldb_scope scope,
820 struct ldb_request *parent)
822 struct ldb_context *ldb = ldb_module_get_ctx(module);
823 struct ldb_dn *nc_root;
824 int64_t password_expiry_time;
827 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
829 ldb_asprintf_errstring(ldb,
830 "Failed to find NC root of DN: %s: %s",
831 ldb_dn_get_linearized(msg->dn),
836 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
837 /* Only calculate this on our default NC */
842 = get_msds_user_password_expiry_time_computed(module, msg,
845 return samdb_msg_add_int64(ldb,
847 "msDS-UserPasswordExpiryTimeComputed",
848 password_expiry_time);
852 struct op_controls_flags {
854 bool bypassoperational;
857 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
858 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
865 a list of attribute names that should be substituted in the parse
866 tree before the search is done
868 static const struct {
871 } parse_tree_sub[] = {
872 { "createTimeStamp", "whenCreated" },
873 { "modifyTimeStamp", "whenChanged" }
877 struct op_attributes_replace {
880 const char * const *extra_attrs;
881 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
885 static const char *objectSid_attr[] =
892 static const char *objectCategory_attr[] =
899 static const char *user_account_control_computed_attrs[] =
907 static const char *user_password_expiry_time_computed_attrs[] =
915 a list of attribute names that are hidden, but can be searched for
916 using another (non-hidden) name to produce the correct result
918 static const struct op_attributes_replace search_sub[] = {
919 { "createTimeStamp", "whenCreated", NULL , NULL },
920 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
921 { "structuralObjectClass", "objectClass", NULL , NULL },
922 { "canonicalName", NULL, NULL , construct_canonical_name },
923 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
924 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
925 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
926 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
927 { "parentGUID", NULL, NULL, construct_parent_guid },
928 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
929 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
930 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
931 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
932 construct_msds_user_account_control_computed },
933 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
934 construct_msds_user_password_expiry_time_computed }
939 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
940 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
941 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
942 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
946 a list of attributes that may need to be removed from the
949 Some of these are attributes that were once stored, but are now calculated
951 struct op_attributes_operations {
956 static const struct op_attributes_operations operational_remove[] = {
957 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
958 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
959 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
960 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
961 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
962 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
967 post process a search result record. For any search_sub[] attributes that were
968 asked for, we need to call the appropriate copy routine to copy the result
969 into the message, then remove any attributes that we added to the search but
970 were not asked for by the user
972 static int operational_search_post_process(struct ldb_module *module,
973 struct ldb_message *msg,
974 enum ldb_scope scope,
975 const char * const *attrs_from_user,
976 const char * const *attrs_searched_for,
977 struct op_controls_flags* controls_flags,
978 struct op_attributes_operations *list,
979 unsigned int list_size,
980 struct op_attributes_replace *list_replace,
981 unsigned int list_replace_size,
982 struct ldb_request *parent)
984 struct ldb_context *ldb;
985 unsigned int i, a = 0;
986 bool constructed_attributes = false;
988 ldb = ldb_module_get_ctx(module);
990 /* removed any attrs that should not be shown to the user */
991 for (i=0; i < list_size; i++) {
992 ldb_msg_remove_attr(msg, list[i].attr);
995 for (a=0; a < list_replace_size; a++) {
996 if (check_keep_control_for_attribute(controls_flags,
997 list_replace[a].attr)) {
1001 /* construct the new attribute, using either a supplied
1002 constructor or a simple copy */
1003 constructed_attributes = true;
1004 if (list_replace[a].constructor != NULL) {
1005 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1008 } else if (ldb_msg_copy_attr(msg,
1009 list_replace[a].replace,
1010 list_replace[a].attr) != LDB_SUCCESS) {
1015 /* Deletion of the search helper attributes are needed if:
1016 * - we generated constructed attributes and
1017 * - we aren't requesting all attributes
1019 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1020 for (i=0; i < list_replace_size; i++) {
1021 /* remove the added search helper attributes, unless
1022 * they were asked for by the user */
1023 if (list_replace[i].replace != NULL &&
1024 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1025 ldb_msg_remove_attr(msg, list_replace[i].replace);
1027 if (list_replace[i].extra_attrs != NULL) {
1029 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1030 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1031 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1041 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1042 "operational_search_post_process failed for attribute '%s' - %s",
1043 attrs_from_user[a], ldb_errstring(ldb));
1048 hook search operations
1051 struct operational_context {
1052 struct ldb_module *module;
1053 struct ldb_request *req;
1054 enum ldb_scope scope;
1055 const char * const *attrs;
1056 struct op_controls_flags* controls_flags;
1057 struct op_attributes_operations *list_operations;
1058 unsigned int list_operations_size;
1059 struct op_attributes_replace *attrs_to_replace;
1060 unsigned int attrs_to_replace_size;
1063 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1065 struct operational_context *ac;
1068 ac = talloc_get_type(req->context, struct operational_context);
1071 return ldb_module_done(ac->req, NULL, NULL,
1072 LDB_ERR_OPERATIONS_ERROR);
1074 if (ares->error != LDB_SUCCESS) {
1075 return ldb_module_done(ac->req, ares->controls,
1076 ares->response, ares->error);
1079 switch (ares->type) {
1080 case LDB_REPLY_ENTRY:
1081 /* for each record returned post-process to add any derived
1082 attributes that have been asked for */
1083 ret = operational_search_post_process(ac->module,
1087 req->op.search.attrs,
1089 ac->list_operations,
1090 ac->list_operations_size,
1091 ac->attrs_to_replace,
1092 ac->attrs_to_replace_size,
1095 return ldb_module_done(ac->req, NULL, NULL,
1096 LDB_ERR_OPERATIONS_ERROR);
1098 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1100 case LDB_REPLY_REFERRAL:
1101 return ldb_module_send_referral(ac->req, ares->referral);
1103 case LDB_REPLY_DONE:
1105 return ldb_module_done(ac->req, ares->controls,
1106 ares->response, LDB_SUCCESS);
1113 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1114 const char* const* attrs,
1115 const char* const* searched_attrs,
1116 struct op_controls_flags* controls_flags)
1120 struct op_attributes_operations *list = talloc_zero_array(ctx,
1121 struct op_attributes_operations,
1122 ARRAY_SIZE(operational_remove) + 1);
1128 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1129 switch (operational_remove[i].op) {
1130 case OPERATIONAL_REMOVE_UNASKED:
1131 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1134 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1137 list[idx].attr = operational_remove[i].attr;
1138 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1142 case OPERATIONAL_REMOVE_ALWAYS:
1143 list[idx].attr = operational_remove[i].attr;
1144 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1148 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1149 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1150 list[idx].attr = operational_remove[i].attr;
1151 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1156 case OPERATIONAL_SD_FLAGS:
1157 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1160 if (controls_flags->sd) {
1161 if (attrs == NULL) {
1164 if (attrs[0] == NULL) {
1167 if (ldb_attr_in_list(attrs, "*")) {
1171 list[idx].attr = operational_remove[i].attr;
1172 list[idx].op = OPERATIONAL_SD_FLAGS;
1181 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1183 struct ldb_context *ldb;
1184 struct operational_context *ac;
1185 struct ldb_request *down_req;
1186 const char **search_attrs = NULL;
1190 /* There are no operational attributes on special DNs */
1191 if (ldb_dn_is_special(req->op.search.base)) {
1192 return ldb_next_request(module, req);
1195 ldb = ldb_module_get_ctx(module);
1197 ac = talloc(req, struct operational_context);
1199 return ldb_oom(ldb);
1202 ac->module = module;
1204 ac->scope = req->op.search.scope;
1205 ac->attrs = req->op.search.attrs;
1207 /* FIXME: We must copy the tree and keep the original
1208 * unmodified. SSS */
1209 /* replace any attributes in the parse tree that are
1210 searchable, but are stored using a different name in the
1212 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1213 ldb_parse_tree_attr_replace(req->op.search.tree,
1214 parse_tree_sub[i].attr,
1215 parse_tree_sub[i].replace);
1218 ac->controls_flags = talloc(ac, struct op_controls_flags);
1219 /* remember if the SD_FLAGS_OID was set */
1220 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1221 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1222 ac->controls_flags->bypassoperational =
1223 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1225 ac->attrs_to_replace = NULL;
1226 ac->attrs_to_replace_size = 0;
1227 /* in the list of attributes we are looking for, rename any
1228 attributes to the alias for any hidden attributes that can
1229 be fetched directly using non-hidden names */
1230 for (a=0;ac->attrs && ac->attrs[a];a++) {
1231 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1234 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1236 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1240 ac->attrs_to_replace = talloc_realloc(ac,
1241 ac->attrs_to_replace,
1242 struct op_attributes_replace,
1243 ac->attrs_to_replace_size + 1);
1245 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1246 ac->attrs_to_replace_size++;
1247 if (!search_sub[i].replace) {
1251 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1253 const char **search_attrs2;
1254 /* Only adds to the end of the list */
1255 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1256 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1259 search_sub[i].extra_attrs[j]);
1260 if (search_attrs2 == NULL) {
1261 return ldb_operr(ldb);
1263 /* may be NULL, talloc_free() doesn't mind */
1264 talloc_free(search_attrs);
1265 search_attrs = search_attrs2;
1269 if (!search_attrs) {
1270 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1271 if (search_attrs == NULL) {
1272 return ldb_operr(ldb);
1275 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1276 search_attrs[a] = search_sub[i].replace;
1279 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1280 search_attrs == NULL?req->op.search.attrs:search_attrs,
1281 ac->controls_flags);
1282 ac->list_operations_size = 0;
1285 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1288 ac->list_operations_size = i;
1289 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1290 req->op.search.base,
1291 req->op.search.scope,
1292 req->op.search.tree,
1293 /* use new set of attrs if any */
1294 search_attrs == NULL?req->op.search.attrs:search_attrs,
1296 ac, operational_callback,
1298 LDB_REQ_SET_LOCATION(down_req);
1299 if (ret != LDB_SUCCESS) {
1300 return ldb_operr(ldb);
1303 /* perform the search */
1304 return ldb_next_request(module, down_req);
1307 static int operational_init(struct ldb_module *ctx)
1309 struct operational_data *data;
1312 ret = ldb_next_init(ctx);
1314 if (ret != LDB_SUCCESS) {
1318 data = talloc_zero(ctx, struct operational_data);
1320 return ldb_module_oom(ctx);
1323 ldb_module_set_private(ctx, data);
1328 static const struct ldb_module_ops ldb_operational_module_ops = {
1329 .name = "operational",
1330 .search = operational_search,
1331 .init_context = operational_init
1334 int ldb_operational_module_init(const char *version)
1336 LDB_MODULE_CHECK_VERSION(version);
1337 return ldb_register_module(&ldb_operational_module_ops);