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);
146 const char *filter = NULL;
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 %s: expanding groups of SID %s failed: %s",
253 attribute_string, account_sid_string,
255 talloc_free(tmp_ctx);
256 return LDB_ERR_OPERATIONS_ERROR;
259 /* Expands the primary group - this function takes in
260 * memberOf-like values, so we fake one up with the
261 * <SID=S-...> format of DN and then let it expand
262 * them, as long as they meet the filter - so only
263 * domain groups, not builtin groups
265 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
266 tmp_ctx, &groupSIDs, &num_groupSIDs);
267 if (!NT_STATUS_IS_OK(status)) {
268 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
269 attribute_string, account_sid_string,
271 talloc_free(tmp_ctx);
272 return LDB_ERR_OPERATIONS_ERROR;
275 for (i=0; i < num_groupSIDs; i++) {
276 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
278 talloc_free(tmp_ctx);
286 static int construct_token_groups(struct ldb_module *module,
287 struct ldb_message *msg, enum ldb_scope scope,
288 struct ldb_request *parent)
291 * TODO: Add in a limiting domain when we start to support
294 return construct_generic_token_groups(module, msg, scope, parent,
299 static int construct_token_groups_no_gc(struct ldb_module *module,
300 struct ldb_message *msg, enum ldb_scope scope,
301 struct ldb_request *parent)
304 * TODO: Add in a limiting domain when we start to support
307 return construct_generic_token_groups(module, msg, scope, parent,
308 "tokenGroupsNoGCAcceptable",
312 static int construct_global_universal_token_groups(struct ldb_module *module,
313 struct ldb_message *msg, enum ldb_scope scope,
314 struct ldb_request *parent)
316 return construct_generic_token_groups(module, msg, scope, parent,
317 "tokenGroupsGlobalAndUniversal",
318 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
321 construct the parent GUID for an entry from a message
323 static int construct_parent_guid(struct ldb_module *module,
324 struct ldb_message *msg, enum ldb_scope scope,
325 struct ldb_request *parent)
327 struct ldb_result *res, *parent_res;
328 const struct ldb_val *parent_guid;
329 const char *attrs[] = { "instanceType", NULL };
330 const char *attrs2[] = { "objectGUID", NULL };
331 uint32_t instanceType;
333 struct ldb_dn *parent_dn;
336 /* determine if the object is NC by instance type */
337 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
338 DSDB_FLAG_NEXT_MODULE |
339 DSDB_SEARCH_SHOW_RECYCLED, parent);
340 if (ret != LDB_SUCCESS) {
344 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
347 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
348 DEBUG(4,(__location__ ": Object %s is NC\n",
349 ldb_dn_get_linearized(msg->dn)));
352 parent_dn = ldb_dn_get_parent(msg, msg->dn);
354 if (parent_dn == NULL) {
355 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
356 ldb_dn_get_linearized(msg->dn)));
357 return LDB_ERR_OTHER;
359 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
360 DSDB_FLAG_NEXT_MODULE |
361 DSDB_SEARCH_SHOW_RECYCLED, parent);
362 /* not NC, so the object should have a parent*/
363 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
365 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
366 ldb_dn_get_linearized(parent_dn),
367 ldb_dn_get_linearized(msg->dn)));
368 talloc_free(parent_dn);
370 } else if (ret != LDB_SUCCESS) {
371 talloc_free(parent_dn);
374 talloc_free(parent_dn);
376 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
378 talloc_free(parent_res);
379 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
382 v = data_blob_dup_talloc(parent_res, *parent_guid);
384 talloc_free(parent_res);
385 return ldb_oom(ldb_module_get_ctx(module));
387 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
388 talloc_free(parent_res);
392 static int construct_modifyTimeStamp(struct ldb_module *module,
393 struct ldb_message *msg, enum ldb_scope scope,
394 struct ldb_request *parent)
396 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
397 struct ldb_context *ldb = ldb_module_get_ctx(module);
399 /* We may be being called before the init function has finished */
404 /* Try and set this value up, if possible. Don't worry if it
405 * fails, we may not have the DB set up yet.
407 if (!data->aggregate_dn) {
408 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
411 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
413 * If we have the DN for the object with common name = Aggregate and
414 * the request is for this DN then let's do the following:
415 * 1) search the object which changedUSN correspond to the one of the loaded
417 * 2) Get the whenChanged attribute
418 * 3) Generate the modifyTimestamp out of the whenChanged attribute
420 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
421 char *value = ldb_timestring(msg, schema->ts_last_change);
423 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
425 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
429 construct a subSchemaSubEntry
431 static int construct_subschema_subentry(struct ldb_module *module,
432 struct ldb_message *msg, enum ldb_scope scope,
433 struct ldb_request *parent)
435 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
436 char *subSchemaSubEntry;
438 /* We may be being called before the init function has finished */
443 /* Try and set this value up, if possible. Don't worry if it
444 * fails, we may not have the DB set up yet, and it's not
445 * really vital anyway */
446 if (!data->aggregate_dn) {
447 struct ldb_context *ldb = ldb_module_get_ctx(module);
448 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
451 if (data->aggregate_dn) {
452 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
453 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
459 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
460 struct ldb_message *msg,
461 struct ldb_message_element *object_category)
463 struct ldb_context *ldb;
465 const struct ldb_val *val;
467 ldb = ldb_module_get_ctx(module);
469 DEBUG(4, (__location__ ": Failed to get ldb \n"));
470 return LDB_ERR_OPERATIONS_ERROR;
473 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
475 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
476 (const char *)object_category->values[0].data));
477 return ldb_operr(ldb);
480 val = ldb_dn_get_rdn_val(dn);
482 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
483 ldb_dn_get_linearized(dn)));
484 return ldb_operr(ldb);
487 if (strequal((const char *)val->data, "NTDS-DSA")) {
488 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
490 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
495 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
496 struct ldb_message *msg,
498 struct ldb_request *parent)
500 struct ldb_dn *server_dn;
501 const char *attr_obj_cat[] = { "objectCategory", NULL };
502 struct ldb_result *res;
503 struct ldb_message_element *object_category;
506 server_dn = ldb_dn_copy(msg, dn);
507 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
508 DEBUG(4, (__location__ ": Failed to add child to %s \n",
509 ldb_dn_get_linearized(server_dn)));
510 return ldb_operr(ldb_module_get_ctx(module));
513 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
514 DSDB_FLAG_NEXT_MODULE, parent);
515 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
516 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
517 ldb_dn_get_linearized(server_dn)));
519 } else if (ret != LDB_SUCCESS) {
523 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
524 if (!object_category) {
525 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
526 ldb_dn_get_linearized(res->msgs[0]->dn)));
529 return construct_msds_isrodc_with_dn(module, msg, object_category);
532 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
533 struct ldb_message *msg,
534 struct ldb_request *parent)
537 struct ldb_dn *server_dn;
539 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
541 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
542 /* it's OK if we can't find serverReferenceBL attribute */
543 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
544 ldb_dn_get_linearized(msg->dn)));
546 } else if (ret != LDB_SUCCESS) {
550 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
554 construct msDS-isRODC attr
556 static int construct_msds_isrodc(struct ldb_module *module,
557 struct ldb_message *msg, enum ldb_scope scope,
558 struct ldb_request *parent)
560 struct ldb_message_element * object_class;
561 struct ldb_message_element * object_category;
564 object_class = ldb_msg_find_element(msg, "objectClass");
566 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
567 ldb_dn_get_linearized(msg->dn)));
568 return ldb_operr(ldb_module_get_ctx(module));
571 for (i=0; i<object_class->num_values; i++) {
572 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
573 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
574 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
576 object_category = ldb_msg_find_element(msg, "objectCategory");
577 if (!object_category) {
578 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
579 ldb_dn_get_linearized(msg->dn)));
582 return construct_msds_isrodc_with_dn(module, msg, object_category);
584 if (strequal((const char*)object_class->values[i].data, "server")) {
585 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
586 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
587 * substituting TN for TO.
589 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
591 if (strequal((const char*)object_class->values[i].data, "computer")) {
592 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
593 * rule for the "TO is a server object" case, substituting TS for TO.
595 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
604 construct msDS-keyVersionNumber attr
606 TODO: Make this based on the 'win2k' DS huristics bit...
609 static int construct_msds_keyversionnumber(struct ldb_module *module,
610 struct ldb_message *msg,
611 enum ldb_scope scope,
612 struct ldb_request *parent)
615 enum ndr_err_code ndr_err;
616 const struct ldb_val *omd_value;
617 struct replPropertyMetaDataBlob *omd;
620 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
622 /* We can't make up a key version number without meta data */
626 omd = talloc(msg, struct replPropertyMetaDataBlob);
628 ldb_module_oom(module);
632 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
633 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
634 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
635 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
636 ldb_dn_get_linearized(msg->dn)));
637 return ldb_operr(ldb_module_get_ctx(module));
640 if (omd->version != 1) {
641 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
642 omd->version, ldb_dn_get_linearized(msg->dn)));
646 for (i=0; i<omd->ctr.ctr1.count; i++) {
647 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
648 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
650 "msDS-KeyVersionNumber",
651 omd->ctr.ctr1.array[i].version);
652 if (ret != LDB_SUCCESS) {
663 #define _UF_TRUST_ACCOUNTS ( \
664 UF_WORKSTATION_TRUST_ACCOUNT | \
665 UF_SERVER_TRUST_ACCOUNT | \
666 UF_INTERDOMAIN_TRUST_ACCOUNT \
668 #define _UF_NO_EXPIRY_ACCOUNTS ( \
669 UF_SMARTCARD_REQUIRED | \
670 UF_DONT_EXPIRE_PASSWD | \
675 calculate msDS-UserPasswordExpiryTimeComputed
677 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
678 struct ldb_message *msg,
679 struct ldb_dn *domain_dn)
681 int64_t pwdLastSet, maxPwdAge;
682 uint32_t userAccountControl;
685 userAccountControl = ldb_msg_find_attr_as_uint(msg,
686 "userAccountControl",
688 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
689 return 0x7FFFFFFFFFFFFFFFULL;
692 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
693 if (pwdLastSet == 0) {
697 if (pwdLastSet <= -1) {
699 * This can't really happen...
701 return 0x7FFFFFFFFFFFFFFFULL;
704 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
706 * Somethings wrong with the clock...
708 return 0x7FFFFFFFFFFFFFFFULL;
712 * Note that maxPwdAge is a stored as negative value.
714 * Possible values are in the range of:
716 * maxPwdAge: -864000000001
718 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
721 maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
722 domain_dn, "maxPwdAge", NULL);
723 if (maxPwdAge >= -864000000000) {
725 * This is not really possible...
727 return 0x7FFFFFFFFFFFFFFFULL;
730 if (maxPwdAge == -0x8000000000000000LL) {
731 return 0x7FFFFFFFFFFFFFFFULL;
735 * Note we already catched maxPwdAge == -0x8000000000000000ULL
736 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
738 * Remember maxPwdAge is a negative number,
739 * so it results in the following.
741 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
743 * 0xFFFFFFFFFFFFFFFFULL
745 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
746 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
747 return 0x7FFFFFFFFFFFFFFFULL;
755 construct msDS-User-Account-Control-Computed attr
757 static int construct_msds_user_account_control_computed(struct ldb_module *module,
758 struct ldb_message *msg, enum ldb_scope scope,
759 struct ldb_request *parent)
761 uint32_t userAccountControl;
762 uint32_t msDS_User_Account_Control_Computed = 0;
763 struct ldb_context *ldb = ldb_module_get_ctx(module);
765 struct ldb_dn *nc_root;
768 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
770 ldb_asprintf_errstring(ldb,
771 "Failed to find NC root of DN: %s: %s",
772 ldb_dn_get_linearized(msg->dn),
773 ldb_errstring(ldb_module_get_ctx(module)));
776 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
777 /* Only calculate this on our default NC */
780 /* Test account expire time */
781 unix_to_nt_time(&now, time(NULL));
783 userAccountControl = ldb_msg_find_attr_as_uint(msg,
784 "userAccountControl",
786 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
788 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
789 if (lockoutTime != 0) {
790 int64_t lockoutDuration = samdb_search_int64(ldb,
792 "lockoutDuration", NULL);
793 if (lockoutDuration >= 0) {
794 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
795 } else if (lockoutTime - lockoutDuration >= now) {
796 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
801 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
802 NTTIME must_change_time
803 = get_msds_user_password_expiry_time_computed(module,
805 /* check for expired password */
806 if (must_change_time < now) {
807 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
811 return samdb_msg_add_int64(ldb,
813 "msDS-User-Account-Control-Computed",
814 msDS_User_Account_Control_Computed);
818 construct msDS-UserPasswordExpiryTimeComputed
820 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
821 struct ldb_message *msg, enum ldb_scope scope,
822 struct ldb_request *parent)
824 struct ldb_context *ldb = ldb_module_get_ctx(module);
825 struct ldb_dn *nc_root;
826 int64_t password_expiry_time;
829 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
831 ldb_asprintf_errstring(ldb,
832 "Failed to find NC root of DN: %s: %s",
833 ldb_dn_get_linearized(msg->dn),
838 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
839 /* Only calculate this on our default NC */
844 = get_msds_user_password_expiry_time_computed(module, msg,
847 return samdb_msg_add_int64(ldb,
849 "msDS-UserPasswordExpiryTimeComputed",
850 password_expiry_time);
854 struct op_controls_flags {
856 bool bypassoperational;
859 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
860 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
867 a list of attribute names that should be substituted in the parse
868 tree before the search is done
870 static const struct {
873 } parse_tree_sub[] = {
874 { "createTimeStamp", "whenCreated" },
875 { "modifyTimeStamp", "whenChanged" }
879 struct op_attributes_replace {
882 const char * const *extra_attrs;
883 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
887 static const char *objectSid_attr[] =
894 static const char *objectCategory_attr[] =
901 static const char *user_account_control_computed_attrs[] =
909 static const char *user_password_expiry_time_computed_attrs[] =
917 a list of attribute names that are hidden, but can be searched for
918 using another (non-hidden) name to produce the correct result
920 static const struct op_attributes_replace search_sub[] = {
921 { "createTimeStamp", "whenCreated", NULL , NULL },
922 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
923 { "structuralObjectClass", "objectClass", NULL , NULL },
924 { "canonicalName", NULL, NULL , construct_canonical_name },
925 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
926 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
927 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
928 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
929 { "parentGUID", NULL, NULL, construct_parent_guid },
930 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
931 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
932 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
933 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
934 construct_msds_user_account_control_computed },
935 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
936 construct_msds_user_password_expiry_time_computed }
941 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
942 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
943 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
944 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
948 a list of attributes that may need to be removed from the
951 Some of these are attributes that were once stored, but are now calculated
953 struct op_attributes_operations {
958 static const struct op_attributes_operations operational_remove[] = {
959 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
960 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
961 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
962 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
963 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
964 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
969 post process a search result record. For any search_sub[] attributes that were
970 asked for, we need to call the appropriate copy routine to copy the result
971 into the message, then remove any attributes that we added to the search but
972 were not asked for by the user
974 static int operational_search_post_process(struct ldb_module *module,
975 struct ldb_message *msg,
976 enum ldb_scope scope,
977 const char * const *attrs_from_user,
978 const char * const *attrs_searched_for,
979 struct op_controls_flags* controls_flags,
980 struct op_attributes_operations *list,
981 unsigned int list_size,
982 struct op_attributes_replace *list_replace,
983 unsigned int list_replace_size,
984 struct ldb_request *parent)
986 struct ldb_context *ldb;
987 unsigned int i, a = 0;
988 bool constructed_attributes = false;
990 ldb = ldb_module_get_ctx(module);
992 /* removed any attrs that should not be shown to the user */
993 for (i=0; i < list_size; i++) {
994 ldb_msg_remove_attr(msg, list[i].attr);
997 for (a=0; a < list_replace_size; a++) {
998 if (check_keep_control_for_attribute(controls_flags,
999 list_replace[a].attr)) {
1003 /* construct the new attribute, using either a supplied
1004 constructor or a simple copy */
1005 constructed_attributes = true;
1006 if (list_replace[a].constructor != NULL) {
1007 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1010 } else if (ldb_msg_copy_attr(msg,
1011 list_replace[a].replace,
1012 list_replace[a].attr) != LDB_SUCCESS) {
1017 /* Deletion of the search helper attributes are needed if:
1018 * - we generated constructed attributes and
1019 * - we aren't requesting all attributes
1021 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1022 for (i=0; i < list_replace_size; i++) {
1023 /* remove the added search helper attributes, unless
1024 * they were asked for by the user */
1025 if (list_replace[i].replace != NULL &&
1026 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1027 ldb_msg_remove_attr(msg, list_replace[i].replace);
1029 if (list_replace[i].extra_attrs != NULL) {
1031 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1032 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1033 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1043 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1044 "operational_search_post_process failed for attribute '%s' - %s",
1045 list_replace[a].attr, ldb_errstring(ldb));
1050 hook search operations
1053 struct operational_context {
1054 struct ldb_module *module;
1055 struct ldb_request *req;
1056 enum ldb_scope scope;
1057 const char * const *attrs;
1058 struct op_controls_flags* controls_flags;
1059 struct op_attributes_operations *list_operations;
1060 unsigned int list_operations_size;
1061 struct op_attributes_replace *attrs_to_replace;
1062 unsigned int attrs_to_replace_size;
1065 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1067 struct operational_context *ac;
1070 ac = talloc_get_type(req->context, struct operational_context);
1073 return ldb_module_done(ac->req, NULL, NULL,
1074 LDB_ERR_OPERATIONS_ERROR);
1076 if (ares->error != LDB_SUCCESS) {
1077 return ldb_module_done(ac->req, ares->controls,
1078 ares->response, ares->error);
1081 switch (ares->type) {
1082 case LDB_REPLY_ENTRY:
1083 /* for each record returned post-process to add any derived
1084 attributes that have been asked for */
1085 ret = operational_search_post_process(ac->module,
1089 req->op.search.attrs,
1091 ac->list_operations,
1092 ac->list_operations_size,
1093 ac->attrs_to_replace,
1094 ac->attrs_to_replace_size,
1097 return ldb_module_done(ac->req, NULL, NULL,
1098 LDB_ERR_OPERATIONS_ERROR);
1100 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1102 case LDB_REPLY_REFERRAL:
1103 return ldb_module_send_referral(ac->req, ares->referral);
1105 case LDB_REPLY_DONE:
1107 return ldb_module_done(ac->req, ares->controls,
1108 ares->response, LDB_SUCCESS);
1115 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1116 const char* const* attrs,
1117 const char* const* searched_attrs,
1118 struct op_controls_flags* controls_flags)
1122 struct op_attributes_operations *list = talloc_zero_array(ctx,
1123 struct op_attributes_operations,
1124 ARRAY_SIZE(operational_remove) + 1);
1130 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1131 switch (operational_remove[i].op) {
1132 case OPERATIONAL_REMOVE_UNASKED:
1133 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1136 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1139 list[idx].attr = operational_remove[i].attr;
1140 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1144 case OPERATIONAL_REMOVE_ALWAYS:
1145 list[idx].attr = operational_remove[i].attr;
1146 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1150 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1151 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1152 list[idx].attr = operational_remove[i].attr;
1153 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1158 case OPERATIONAL_SD_FLAGS:
1159 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1162 if (controls_flags->sd) {
1163 if (attrs == NULL) {
1166 if (attrs[0] == NULL) {
1169 if (ldb_attr_in_list(attrs, "*")) {
1173 list[idx].attr = operational_remove[i].attr;
1174 list[idx].op = OPERATIONAL_SD_FLAGS;
1183 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1185 struct ldb_context *ldb;
1186 struct operational_context *ac;
1187 struct ldb_request *down_req;
1188 const char **search_attrs = NULL;
1192 /* There are no operational attributes on special DNs */
1193 if (ldb_dn_is_special(req->op.search.base)) {
1194 return ldb_next_request(module, req);
1197 ldb = ldb_module_get_ctx(module);
1199 ac = talloc(req, struct operational_context);
1201 return ldb_oom(ldb);
1204 ac->module = module;
1206 ac->scope = req->op.search.scope;
1207 ac->attrs = req->op.search.attrs;
1209 /* FIXME: We must copy the tree and keep the original
1210 * unmodified. SSS */
1211 /* replace any attributes in the parse tree that are
1212 searchable, but are stored using a different name in the
1214 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1215 ldb_parse_tree_attr_replace(req->op.search.tree,
1216 parse_tree_sub[i].attr,
1217 parse_tree_sub[i].replace);
1220 ac->controls_flags = talloc(ac, struct op_controls_flags);
1221 /* remember if the SD_FLAGS_OID was set */
1222 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1223 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1224 ac->controls_flags->bypassoperational =
1225 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1227 ac->attrs_to_replace = NULL;
1228 ac->attrs_to_replace_size = 0;
1229 /* in the list of attributes we are looking for, rename any
1230 attributes to the alias for any hidden attributes that can
1231 be fetched directly using non-hidden names */
1232 for (a=0;ac->attrs && ac->attrs[a];a++) {
1233 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1236 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1238 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1242 ac->attrs_to_replace = talloc_realloc(ac,
1243 ac->attrs_to_replace,
1244 struct op_attributes_replace,
1245 ac->attrs_to_replace_size + 1);
1247 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1248 ac->attrs_to_replace_size++;
1249 if (!search_sub[i].replace) {
1253 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1255 const char **search_attrs2;
1256 /* Only adds to the end of the list */
1257 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1258 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1261 search_sub[i].extra_attrs[j]);
1262 if (search_attrs2 == NULL) {
1263 return ldb_operr(ldb);
1265 /* may be NULL, talloc_free() doesn't mind */
1266 talloc_free(search_attrs);
1267 search_attrs = search_attrs2;
1271 if (!search_attrs) {
1272 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1273 if (search_attrs == NULL) {
1274 return ldb_operr(ldb);
1277 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1278 search_attrs[a] = search_sub[i].replace;
1281 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1282 search_attrs == NULL?req->op.search.attrs:search_attrs,
1283 ac->controls_flags);
1284 ac->list_operations_size = 0;
1287 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1290 ac->list_operations_size = i;
1291 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1292 req->op.search.base,
1293 req->op.search.scope,
1294 req->op.search.tree,
1295 /* use new set of attrs if any */
1296 search_attrs == NULL?req->op.search.attrs:search_attrs,
1298 ac, operational_callback,
1300 LDB_REQ_SET_LOCATION(down_req);
1301 if (ret != LDB_SUCCESS) {
1302 return ldb_operr(ldb);
1305 /* perform the search */
1306 return ldb_next_request(module, down_req);
1309 static int operational_init(struct ldb_module *ctx)
1311 struct operational_data *data;
1314 ret = ldb_next_init(ctx);
1316 if (ret != LDB_SUCCESS) {
1320 data = talloc_zero(ctx, struct operational_data);
1322 return ldb_module_oom(ctx);
1325 ldb_module_set_private(ctx, data);
1330 static const struct ldb_module_ops ldb_operational_module_ops = {
1331 .name = "operational",
1332 .search = operational_search,
1333 .init_context = operational_init
1336 int ldb_operational_module_init(const char *version)
1338 LDB_MODULE_CHECK_VERSION(version);
1339 return ldb_register_module(&ldb_operational_module_ops);