s4:dsdb: Remove redundant user flags macro
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blobe71728da8bcdabe6fe4ef5b0fedce32cf533661f
1 /*
2 ldb database library
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
34 the resulting values
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?
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "dsdb/samdb/samdb.h"
74 #include "dsdb/samdb/ldb_modules/managed_pwd.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
77 #include "auth/auth.h"
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
83 #undef strcasecmp
85 struct operational_data {
86 struct ldb_dn *aggregate_dn;
89 enum search_type {
90 TOKEN_GROUPS,
91 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
92 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
95 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
96 * all account groups in a given domain, excluding built-in groups.
97 * (Used internally for msDS-ResultantPSO support)
99 ACCOUNT_GROUPS
102 static int get_pso_for_user(struct ldb_module *module,
103 struct ldb_message *user_msg,
104 struct ldb_request *parent,
105 struct ldb_message **pso_msg);
108 construct a canonical name from a message
110 static int construct_canonical_name(struct ldb_module *module,
111 struct ldb_message *msg, enum ldb_scope scope,
112 struct ldb_request *parent, struct ldb_reply *ares)
114 char *canonicalName;
115 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
116 if (canonicalName == NULL) {
117 return ldb_operr(ldb_module_get_ctx(module));
119 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
123 construct a primary group token for groups from a message
125 static int construct_primary_group_token(struct ldb_module *module,
126 struct ldb_message *msg, enum ldb_scope scope,
127 struct ldb_request *parent, struct ldb_reply *ares)
129 struct ldb_context *ldb;
130 uint32_t primary_group_token;
132 ldb = ldb_module_get_ctx(module);
133 if (ldb_match_msg_objectclass(msg, "group") == 1) {
134 primary_group_token
135 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
136 if (primary_group_token == 0) {
137 return LDB_SUCCESS;
140 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
141 primary_group_token);
142 } else {
143 return LDB_SUCCESS;
148 * Returns the group SIDs for the user in the given LDB message
150 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
151 struct ldb_message *msg, const char *attribute_string,
152 enum search_type type, struct auth_SidAttr **groupSIDs,
153 uint32_t *num_groupSIDs)
155 const char *filter = NULL;
156 NTSTATUS status;
157 struct dom_sid *primary_group_sid;
158 const char *primary_group_string;
159 const char *primary_group_dn;
160 DATA_BLOB primary_group_blob;
161 struct dom_sid *account_sid;
162 const char *account_sid_string;
163 const char *account_sid_dn;
164 DATA_BLOB account_sid_blob;
165 struct dom_sid *domain_sid;
167 /* If it's not a user, it won't have a primaryGroupID */
168 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
169 return LDB_SUCCESS;
172 /* Ensure it has an objectSID too */
173 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
174 if (account_sid == NULL) {
175 return LDB_SUCCESS;
178 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
179 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
180 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
181 } else if (!NT_STATUS_IS_OK(status)) {
182 return LDB_ERR_OPERATIONS_ERROR;
185 primary_group_sid = dom_sid_add_rid(mem_ctx,
186 domain_sid,
187 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
188 if (!primary_group_sid) {
189 return ldb_oom(ldb);
192 /* only return security groups */
193 switch(type) {
194 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
195 filter = talloc_asprintf(mem_ctx,
196 "(&(objectClass=group)"
197 "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
198 "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
199 GROUP_TYPE_SECURITY_ENABLED,
200 GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
201 break;
202 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
203 case TOKEN_GROUPS:
204 filter = talloc_asprintf(mem_ctx,
205 "(&(objectClass=group)"
206 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
207 GROUP_TYPE_SECURITY_ENABLED);
208 break;
210 /* for RevMembGetAccountGroups, exclude built-in groups */
211 case ACCOUNT_GROUPS:
212 filter = talloc_asprintf(mem_ctx,
213 "(&(objectClass=group)"
214 "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
215 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
216 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
217 break;
220 if (!filter) {
221 return ldb_oom(ldb);
224 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
225 if (!primary_group_string) {
226 return ldb_oom(ldb);
229 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
230 if (!primary_group_dn) {
231 return ldb_oom(ldb);
234 primary_group_blob = data_blob_string_const(primary_group_dn);
236 account_sid_string = dom_sid_string(mem_ctx, account_sid);
237 if (!account_sid_string) {
238 return ldb_oom(ldb);
241 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
242 if (!account_sid_dn) {
243 return ldb_oom(ldb);
246 account_sid_blob = data_blob_string_const(account_sid_dn);
248 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
249 true, /* We don't want to add the object's SID itself,
250 it's not returned in this attribute */
251 filter,
252 mem_ctx, groupSIDs, num_groupSIDs);
254 if (!NT_STATUS_IS_OK(status)) {
255 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
256 attribute_string, account_sid_string,
257 nt_errstr(status));
258 return LDB_ERR_OPERATIONS_ERROR;
261 /* Expands the primary group - this function takes in
262 * memberOf-like values, so we fake one up with the
263 * <SID=S-...> format of DN and then let it expand
264 * them, as long as they meet the filter - so only
265 * domain groups, not builtin groups
267 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
268 mem_ctx, groupSIDs, num_groupSIDs);
269 if (!NT_STATUS_IS_OK(status)) {
270 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
271 attribute_string, account_sid_string,
272 nt_errstr(status));
273 return LDB_ERR_OPERATIONS_ERROR;
276 return LDB_SUCCESS;
280 construct the token groups for SAM objects from a message
282 static int construct_generic_token_groups(struct ldb_module *module,
283 struct ldb_message *msg, enum ldb_scope scope,
284 struct ldb_request *parent,
285 const char *attribute_string,
286 enum search_type type)
288 struct ldb_context *ldb = ldb_module_get_ctx(module);
289 TALLOC_CTX *tmp_ctx = talloc_new(msg);
290 uint32_t i;
291 int ret;
292 struct auth_SidAttr *groupSIDs = NULL;
293 uint32_t num_groupSIDs = 0;
295 if (scope != LDB_SCOPE_BASE) {
296 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
297 return LDB_ERR_OPERATIONS_ERROR;
300 /* calculate the group SIDs for this object */
301 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
302 &groupSIDs, &num_groupSIDs);
304 if (ret != LDB_SUCCESS) {
305 talloc_free(tmp_ctx);
306 return LDB_ERR_OPERATIONS_ERROR;
309 /* add these SIDs to the search result */
310 for (i=0; i < num_groupSIDs; i++) {
311 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
312 if (ret) {
313 talloc_free(tmp_ctx);
314 return ret;
318 return LDB_SUCCESS;
321 static int construct_token_groups(struct ldb_module *module,
322 struct ldb_message *msg, enum ldb_scope scope,
323 struct ldb_request *parent, struct ldb_reply *ares)
326 * TODO: Add in a limiting domain when we start to support
327 * trusted domains.
329 return construct_generic_token_groups(module, msg, scope, parent,
330 "tokenGroups",
331 TOKEN_GROUPS);
334 static int construct_token_groups_no_gc(struct ldb_module *module,
335 struct ldb_message *msg, enum ldb_scope scope,
336 struct ldb_request *parent, struct ldb_reply *ares)
339 * TODO: Add in a limiting domain when we start to support
340 * trusted domains.
342 return construct_generic_token_groups(module, msg, scope, parent,
343 "tokenGroupsNoGCAcceptable",
344 TOKEN_GROUPS);
347 static int construct_global_universal_token_groups(struct ldb_module *module,
348 struct ldb_message *msg, enum ldb_scope scope,
349 struct ldb_request *parent, struct ldb_reply *ares)
351 return construct_generic_token_groups(module, msg, scope, parent,
352 "tokenGroupsGlobalAndUniversal",
353 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
356 construct the parent GUID for an entry from a message
358 static int construct_parent_guid(struct ldb_module *module,
359 struct ldb_message *msg, enum ldb_scope scope,
360 struct ldb_request *parent, struct ldb_reply *ares)
362 struct ldb_result *res, *parent_res;
363 const struct ldb_val *parent_guid;
364 const char *attrs[] = { "instanceType", NULL };
365 const char *attrs2[] = { "objectGUID", NULL };
366 uint32_t instanceType;
367 int ret;
368 struct ldb_dn *parent_dn;
369 struct ldb_val v;
371 /* determine if the object is NC by instance type */
372 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
373 DSDB_FLAG_NEXT_MODULE |
374 DSDB_SEARCH_SHOW_RECYCLED, parent);
375 if (ret != LDB_SUCCESS) {
376 return ret;
379 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
380 "instanceType", 0);
381 talloc_free(res);
382 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
383 DEBUG(4,(__location__ ": Object %s is NC\n",
384 ldb_dn_get_linearized(msg->dn)));
385 return LDB_SUCCESS;
387 parent_dn = ldb_dn_get_parent(msg, msg->dn);
389 if (parent_dn == NULL) {
390 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
391 ldb_dn_get_linearized(msg->dn)));
392 return LDB_ERR_OTHER;
394 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
395 DSDB_FLAG_NEXT_MODULE |
396 DSDB_SEARCH_SHOW_RECYCLED, parent);
397 /* not NC, so the object should have a parent*/
398 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
399 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
400 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
401 ldb_dn_get_linearized(parent_dn),
402 ldb_dn_get_linearized(msg->dn)));
403 talloc_free(parent_dn);
404 return ret;
405 } else if (ret != LDB_SUCCESS) {
406 talloc_free(parent_dn);
407 return ret;
409 talloc_free(parent_dn);
411 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
412 if (!parent_guid) {
413 talloc_free(parent_res);
414 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
417 v = data_blob_dup_talloc(parent_res, *parent_guid);
418 if (!v.data) {
419 talloc_free(parent_res);
420 return ldb_oom(ldb_module_get_ctx(module));
422 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
423 talloc_free(parent_res);
424 return ret;
427 static int construct_modifyTimeStamp(struct ldb_module *module,
428 struct ldb_message *msg, enum ldb_scope scope,
429 struct ldb_request *parent, struct ldb_reply *ares)
431 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
432 struct ldb_context *ldb = ldb_module_get_ctx(module);
434 /* We may be being called before the init function has finished */
435 if (!data) {
436 return LDB_SUCCESS;
439 /* Try and set this value up, if possible. Don't worry if it
440 * fails, we may not have the DB set up yet.
442 if (!data->aggregate_dn) {
443 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
446 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
448 * If we have the DN for the object with common name = Aggregate and
449 * the request is for this DN then let's do the following:
450 * 1) search the object which changedUSN correspond to the one of the loaded
451 * schema.
452 * 2) Get the whenChanged attribute
453 * 3) Generate the modifyTimestamp out of the whenChanged attribute
455 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
456 char *value = ldb_timestring(msg, schema->ts_last_change);
458 if (value == NULL) {
459 return ldb_oom(ldb_module_get_ctx(module));
462 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
464 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
468 construct a subSchemaSubEntry
470 static int construct_subschema_subentry(struct ldb_module *module,
471 struct ldb_message *msg, enum ldb_scope scope,
472 struct ldb_request *parent, struct ldb_reply *ares)
474 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
475 char *subSchemaSubEntry;
477 /* We may be being called before the init function has finished */
478 if (!data) {
479 return LDB_SUCCESS;
482 /* Try and set this value up, if possible. Don't worry if it
483 * fails, we may not have the DB set up yet, and it's not
484 * really vital anyway */
485 if (!data->aggregate_dn) {
486 struct ldb_context *ldb = ldb_module_get_ctx(module);
487 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
490 if (data->aggregate_dn) {
491 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
492 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
494 return LDB_SUCCESS;
498 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
499 struct ldb_message *msg,
500 struct ldb_message_element *object_category)
502 struct ldb_context *ldb;
503 struct ldb_dn *dn;
504 const struct ldb_val *val;
506 ldb = ldb_module_get_ctx(module);
507 if (!ldb) {
508 DEBUG(4, (__location__ ": Failed to get ldb \n"));
509 return LDB_ERR_OPERATIONS_ERROR;
512 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
513 if (!dn) {
514 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
515 (const char *)object_category->values[0].data));
516 return ldb_operr(ldb);
519 val = ldb_dn_get_rdn_val(dn);
520 if (!val) {
521 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
522 ldb_dn_get_linearized(dn)));
523 return ldb_operr(ldb);
526 if (strequal((const char *)val->data, "NTDS-DSA")) {
527 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
528 } else {
529 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
531 return LDB_SUCCESS;
534 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
535 struct ldb_message *msg,
536 struct ldb_dn *dn,
537 struct ldb_request *parent)
539 struct ldb_dn *server_dn;
540 const char *attr_obj_cat[] = { "objectCategory", NULL };
541 struct ldb_result *res;
542 struct ldb_message_element *object_category;
543 int ret;
545 server_dn = ldb_dn_copy(msg, dn);
546 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
547 DEBUG(4, (__location__ ": Failed to add child to %s \n",
548 ldb_dn_get_linearized(server_dn)));
549 return ldb_operr(ldb_module_get_ctx(module));
552 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
553 DSDB_FLAG_NEXT_MODULE, parent);
554 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
555 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
556 ldb_dn_get_linearized(server_dn)));
557 return LDB_SUCCESS;
558 } else if (ret != LDB_SUCCESS) {
559 return ret;
562 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
563 if (!object_category) {
564 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
565 ldb_dn_get_linearized(res->msgs[0]->dn)));
566 return LDB_SUCCESS;
568 return construct_msds_isrodc_with_dn(module, msg, object_category);
571 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
572 struct ldb_message *msg,
573 struct ldb_request *parent)
575 int ret;
576 struct ldb_dn *server_dn;
578 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
579 &server_dn, parent);
580 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
581 /* it's OK if we can't find serverReferenceBL attribute */
582 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
583 ldb_dn_get_linearized(msg->dn)));
584 return LDB_SUCCESS;
585 } else if (ret != LDB_SUCCESS) {
586 return ret;
589 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
593 construct msDS-isRODC attr
595 static int construct_msds_isrodc(struct ldb_module *module,
596 struct ldb_message *msg, enum ldb_scope scope,
597 struct ldb_request *parent, struct ldb_reply *ares)
599 struct ldb_message_element * object_class;
600 struct ldb_message_element * object_category;
601 unsigned int i;
603 object_class = ldb_msg_find_element(msg, "objectClass");
604 if (!object_class) {
605 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
606 ldb_dn_get_linearized(msg->dn)));
607 return ldb_operr(ldb_module_get_ctx(module));
610 for (i=0; i<object_class->num_values; i++) {
611 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
612 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
613 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
615 object_category = ldb_msg_find_element(msg, "objectCategory");
616 if (!object_category) {
617 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
618 ldb_dn_get_linearized(msg->dn)));
619 return LDB_SUCCESS;
621 return construct_msds_isrodc_with_dn(module, msg, object_category);
623 if (strequal((const char*)object_class->values[i].data, "server")) {
624 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
625 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
626 * substituting TN for TO.
628 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
630 if (strequal((const char*)object_class->values[i].data, "computer")) {
631 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
632 * rule for the "TO is a server object" case, substituting TS for TO.
634 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
638 return LDB_SUCCESS;
643 construct msDS-keyVersionNumber attr
645 TODO: Make this based on the 'win2k' DS heuristics bit...
648 static int construct_msds_keyversionnumber(struct ldb_module *module,
649 struct ldb_message *msg,
650 enum ldb_scope scope,
651 struct ldb_request *parent,
652 struct ldb_reply *ares)
654 uint32_t i;
655 enum ndr_err_code ndr_err;
656 const struct ldb_val *omd_value;
657 struct replPropertyMetaDataBlob *omd;
658 int ret;
660 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
661 if (!omd_value) {
662 /* We can't make up a key version number without meta data */
663 return LDB_SUCCESS;
666 omd = talloc(msg, struct replPropertyMetaDataBlob);
667 if (!omd) {
668 ldb_module_oom(module);
669 return LDB_SUCCESS;
672 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
673 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
674 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
675 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
676 ldb_dn_get_linearized(msg->dn)));
677 return ldb_operr(ldb_module_get_ctx(module));
680 if (omd->version != 1) {
681 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
682 omd->version, ldb_dn_get_linearized(msg->dn)));
683 talloc_free(omd);
684 return LDB_SUCCESS;
686 for (i=0; i<omd->ctr.ctr1.count; i++) {
687 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
688 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
689 msg, msg,
690 "msDS-KeyVersionNumber",
691 omd->ctr.ctr1.array[i].version);
692 if (ret != LDB_SUCCESS) {
693 talloc_free(omd);
694 return ret;
696 break;
699 return LDB_SUCCESS;
703 #define _UF_NO_EXPIRY_ACCOUNTS ( \
704 UF_SMARTCARD_REQUIRED | \
705 UF_DONT_EXPIRE_PASSWD | \
706 UF_TRUST_ACCOUNT_MASK \
711 * Returns the Effective-MaximumPasswordAge for a user
713 static int64_t get_user_max_pwd_age(struct ldb_module *module,
714 struct ldb_message *user_msg,
715 struct ldb_request *parent,
716 struct ldb_dn *nc_root)
718 int ret;
719 struct ldb_message *pso = NULL;
720 struct ldb_context *ldb = ldb_module_get_ctx(module);
722 /* if a PSO applies to the user, use its maxPwdAge */
723 ret = get_pso_for_user(module, user_msg, parent, &pso);
724 if (ret != LDB_SUCCESS) {
726 /* log the error, but fallback to the domain default */
727 DBG_ERR("Error retrieving PSO for %s\n",
728 ldb_dn_get_linearized(user_msg->dn));
731 if (pso != NULL) {
732 return ldb_msg_find_attr_as_int64(pso,
733 "msDS-MaximumPasswordAge", 0);
736 /* otherwise return the default domain value */
737 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
741 calculate msDS-UserPasswordExpiryTimeComputed
743 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
744 struct ldb_message *msg,
745 struct ldb_request *parent,
746 struct ldb_dn *domain_dn)
748 int64_t pwdLastSet, maxPwdAge;
749 uint32_t userAccountControl;
750 NTTIME ret;
752 userAccountControl = ldb_msg_find_attr_as_uint(msg,
753 "userAccountControl",
755 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
756 return INT64_MAX;
759 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
760 if (pwdLastSet == 0) {
761 return 0;
764 if (pwdLastSet <= -1) {
766 * This can't really happen...
768 return INT64_MAX;
771 if (pwdLastSet >= INT64_MAX) {
773 * Somethings wrong with the clock...
775 return INT64_MAX;
779 * Note that maxPwdAge is a stored as negative value.
781 * Possible values are in the range of:
783 * maxPwdAge: -864000000001
784 * to
785 * maxPwdAge: -9223372036854775808 (INT64_MIN)
788 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
789 if (maxPwdAge >= -864000000000) {
791 * This is not really possible...
793 return INT64_MAX;
796 if (maxPwdAge == INT64_MIN) {
797 return INT64_MAX;
801 * Note we already caught maxPwdAge == INT64_MIN
802 * and pwdLastSet >= INT64_MAX above.
804 * Remember maxPwdAge is a negative number,
805 * so it results in the following.
807 * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
809 * 0xFFFFFFFFFFFFFFFDULL
811 * or to put it another way, adding two numbers less than 1<<63 can't
812 * ever be more than 1<<64, therefore this result can't wrap.
814 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
815 if (ret >= INT64_MAX) {
816 return INT64_MAX;
819 return ret;
823 * Returns the Effective-LockoutDuration for a user
825 static int64_t get_user_lockout_duration(struct ldb_module *module,
826 struct ldb_message *user_msg,
827 struct ldb_request *parent,
828 struct ldb_dn *nc_root)
830 int ret;
831 struct ldb_message *pso = NULL;
832 struct ldb_context *ldb = ldb_module_get_ctx(module);
834 /* if a PSO applies to the user, use its lockoutDuration */
835 ret = get_pso_for_user(module, user_msg, parent, &pso);
836 if (ret != LDB_SUCCESS) {
838 /* log the error, but fallback to the domain default */
839 DBG_ERR("Error retrieving PSO for %s\n",
840 ldb_dn_get_linearized(user_msg->dn));
843 if (pso != NULL) {
844 return ldb_msg_find_attr_as_int64(pso,
845 "msDS-LockoutDuration", 0);
848 /* otherwise return the default domain value */
849 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
850 NULL);
854 construct msDS-User-Account-Control-Computed attr
856 static int construct_msds_user_account_control_computed(struct ldb_module *module,
857 struct ldb_message *msg, enum ldb_scope scope,
858 struct ldb_request *parent, struct ldb_reply *ares)
860 uint32_t userAccountControl;
861 uint32_t msDS_User_Account_Control_Computed = 0;
862 struct ldb_context *ldb = ldb_module_get_ctx(module);
863 NTTIME now;
864 struct ldb_dn *nc_root;
865 int ret;
867 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
868 if (ret != 0) {
869 ldb_asprintf_errstring(ldb,
870 "Failed to find NC root of DN: %s: %s",
871 ldb_dn_get_linearized(msg->dn),
872 ldb_errstring(ldb_module_get_ctx(module)));
873 return ret;
875 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
876 /* Only calculate this on our default NC */
877 return 0;
879 /* Test account expire time */
880 unix_to_nt_time(&now, time(NULL));
882 userAccountControl = ldb_msg_find_attr_as_uint(msg,
883 "userAccountControl",
885 if (!(userAccountControl & UF_TRUST_ACCOUNT_MASK)) {
887 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
888 if (lockoutTime != 0) {
889 int64_t lockoutDuration;
891 lockoutDuration = get_user_lockout_duration(module, msg,
892 parent,
893 nc_root);
895 /* zero locks out until the administrator intervenes */
896 if (lockoutDuration >= 0) {
897 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
898 } else if (lockoutTime - lockoutDuration >= now) {
899 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
904 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
905 NTTIME must_change_time
906 = get_msds_user_password_expiry_time_computed(module,
907 msg,
908 parent,
909 nc_root);
910 /* check for expired password */
911 if (must_change_time < now) {
912 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
916 return samdb_msg_add_int64(ldb,
917 msg->elements, msg,
918 "msDS-User-Account-Control-Computed",
919 msDS_User_Account_Control_Computed);
923 construct msDS-UserPasswordExpiryTimeComputed
925 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
926 struct ldb_message *msg, enum ldb_scope scope,
927 struct ldb_request *parent, struct ldb_reply *ares)
929 struct ldb_context *ldb = ldb_module_get_ctx(module);
930 struct ldb_dn *nc_root;
931 int64_t password_expiry_time;
932 int ret;
934 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
935 if (ret != 0) {
936 ldb_asprintf_errstring(ldb,
937 "Failed to find NC root of DN: %s: %s",
938 ldb_dn_get_linearized(msg->dn),
939 ldb_errstring(ldb));
940 return ret;
943 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
944 /* Only calculate this on our default NC */
945 return 0;
948 password_expiry_time
949 = get_msds_user_password_expiry_time_computed(module, msg,
950 parent, nc_root);
952 return samdb_msg_add_int64(ldb,
953 msg->elements, msg,
954 "msDS-UserPasswordExpiryTimeComputed",
955 password_expiry_time);
959 * Checks whether the msDS-ResultantPSO attribute is supported for a given
960 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
962 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
964 int functional_level;
965 uint32_t uac;
966 uint32_t user_rid;
968 functional_level = dsdb_functional_level(ldb);
969 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
970 return false;
973 /* msDS-ResultantPSO is only supported for user objects */
974 if (!ldb_match_msg_objectclass(msg, "user")) {
975 return false;
978 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
979 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
980 if (!(uac & UF_NORMAL_ACCOUNT)) {
981 return false;
984 /* skip it if it's the special KRBTGT default account */
985 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
986 if (user_rid == DOMAIN_RID_KRBTGT) {
987 return false;
990 /* ...or if it's a special KRBTGT account for an RODC KDC */
991 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
992 return false;
995 return true;
999 * Returns the number of PSO objects that exist in the DB
1001 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1002 struct ldb_request *parent, int *pso_count)
1004 static const char * const attrs[] = { NULL };
1005 int ret;
1006 struct ldb_dn *psc_dn = NULL;
1007 struct ldb_result *res = NULL;
1008 struct ldb_context *ldb = ldb_module_get_ctx(module);
1009 bool psc_ok;
1011 *pso_count = 0;
1012 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1013 if (psc_dn == NULL) {
1014 return ldb_oom(ldb);
1016 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1017 if (psc_ok == false) {
1018 return ldb_oom(ldb);
1021 /* get the number of PSO children */
1022 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1023 LDB_SCOPE_ONELEVEL, attrs,
1024 DSDB_FLAG_NEXT_MODULE, parent,
1025 "(objectClass=msDS-PasswordSettings)");
1028 * Just ignore PSOs if the container doesn't exist. This is a weird
1029 * corner-case where the AD DB was created from a pre-2008 base schema,
1030 * and then the FL was manually upgraded.
1032 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1033 DBG_NOTICE("No Password Settings Container exists\n");
1034 return LDB_SUCCESS;
1037 if (ret != LDB_SUCCESS) {
1038 return ret;
1041 *pso_count = res->count;
1042 talloc_free(res);
1043 talloc_free(psc_dn);
1045 return LDB_SUCCESS;
1049 * Compares two PSO objects returned by a search, to work out the better PSO.
1050 * The PSO with the lowest precedence is better, otherwise (if the precedence
1051 * is equal) the PSO with the lower GUID wins.
1053 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1055 uint32_t prec1;
1056 uint32_t prec2;
1058 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1059 0xffffffff);
1060 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1061 0xffffffff);
1063 /* if precedence is equal, use the lowest GUID */
1064 if (prec1 == prec2) {
1065 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1066 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1068 return ndr_guid_compare(&guid1, &guid2);
1069 } else {
1070 return NUMERIC_CMP(prec1, prec2);
1075 * Search for PSO objects that apply to the object SIDs specified
1077 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1078 struct ldb_request *parent,
1079 struct auth_SidAttr *sid_array, unsigned int num_sids,
1080 struct ldb_result **result)
1082 int ret;
1083 int i;
1084 struct ldb_context *ldb = ldb_module_get_ctx(module);
1085 char *sid_filter = NULL;
1086 struct ldb_dn *psc_dn = NULL;
1087 bool psc_ok;
1088 const char *attrs[] = {
1089 "msDS-PasswordSettingsPrecedence",
1090 "objectGUID",
1091 "msDS-LockoutDuration",
1092 "msDS-MaximumPasswordAge",
1093 NULL
1096 /* build a query for PSO objects that apply to any of the SIDs given */
1097 sid_filter = talloc_strdup(mem_ctx, "");
1098 if (sid_filter == NULL) {
1099 return ldb_oom(ldb);
1102 for (i = 0; sid_filter && i < num_sids; i++) {
1103 struct dom_sid_buf sid_buf;
1105 sid_filter = talloc_asprintf_append(
1106 sid_filter,
1107 "(msDS-PSOAppliesTo=<SID=%s>)",
1108 dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
1109 if (sid_filter == NULL) {
1110 return ldb_oom(ldb);
1114 /* only PSOs located in the Password Settings Container are valid */
1115 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1116 if (psc_dn == NULL) {
1117 return ldb_oom(ldb);
1119 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1120 if (psc_ok == false) {
1121 return ldb_oom(ldb);
1124 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1125 LDB_SCOPE_ONELEVEL, attrs,
1126 DSDB_FLAG_NEXT_MODULE, parent,
1127 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1128 sid_filter);
1129 talloc_free(sid_filter);
1130 return ret;
1134 * Returns the best PSO object that applies to the object SID(s) specified
1136 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1137 struct ldb_request *parent, struct auth_SidAttr *sid_array,
1138 unsigned int num_sids, struct ldb_message **best_pso)
1140 struct ldb_result *res = NULL;
1141 int ret;
1143 *best_pso = NULL;
1145 /* find any PSOs that apply to the SIDs specified */
1146 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1147 &res);
1148 if (ret != LDB_SUCCESS) {
1149 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1150 return ret;
1153 /* sort the list so that the best PSO is first */
1154 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1156 if (res->count > 0) {
1157 *best_pso = res->msgs[0];
1160 return LDB_SUCCESS;
1164 * Determines the Password Settings Object (PSO) that applies to the given user
1166 static int get_pso_for_user(struct ldb_module *module,
1167 struct ldb_message *user_msg,
1168 struct ldb_request *parent,
1169 struct ldb_message **pso_msg)
1171 bool pso_supported;
1172 struct auth_SidAttr *groupSIDs = NULL;
1173 uint32_t num_groupSIDs = 0;
1174 struct ldb_context *ldb = ldb_module_get_ctx(module);
1175 struct ldb_message *best_pso = NULL;
1176 struct ldb_dn *pso_dn = NULL;
1177 int ret;
1178 struct ldb_message_element *el = NULL;
1179 TALLOC_CTX *tmp_ctx = NULL;
1180 int pso_count = 0;
1181 struct ldb_result *res = NULL;
1182 static const char *attrs[] = {
1183 "msDS-LockoutDuration",
1184 "msDS-MaximumPasswordAge",
1185 NULL
1188 *pso_msg = NULL;
1190 /* first, check msDS-ResultantPSO is supported for this object */
1191 pso_supported = pso_is_supported(ldb, user_msg);
1193 if (!pso_supported) {
1194 return LDB_SUCCESS;
1197 tmp_ctx = talloc_new(user_msg);
1200 * Several different constructed attributes try to use the PSO info. If
1201 * we've already constructed the msDS-ResultantPSO for this user, we can
1202 * just re-use the result, rather than calculating it from scratch again
1204 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1205 "msDS-ResultantPSO");
1207 if (pso_dn != NULL) {
1208 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1209 attrs, DSDB_FLAG_NEXT_MODULE,
1210 parent);
1211 if (ret != LDB_SUCCESS) {
1212 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1213 ldb_dn_get_linearized(pso_dn));
1214 talloc_free(tmp_ctx);
1215 return ret;
1218 if (res->count == 1) {
1219 *pso_msg = res->msgs[0];
1220 return LDB_SUCCESS;
1225 * if any PSOs apply directly to the user, they are considered first
1226 * before we check group membership PSOs
1228 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1230 if (el != NULL && el->num_values > 0) {
1231 struct auth_SidAttr *user_sid = NULL;
1233 /* lookup the best PSO object, based on the user's SID */
1234 user_sid = samdb_result_dom_sid_attrs(
1235 tmp_ctx, user_msg, "objectSid",
1236 SE_GROUP_DEFAULT_FLAGS);
1238 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1239 &best_pso);
1240 if (ret != LDB_SUCCESS) {
1241 talloc_free(tmp_ctx);
1242 return ret;
1245 if (best_pso != NULL) {
1246 *pso_msg = best_pso;
1247 return LDB_SUCCESS;
1252 * If no valid PSO applies directly to the user, then try its groups.
1253 * The group expansion is expensive, so check there are actually
1254 * PSOs in the DB first (which is a quick search). Note in the above
1255 * cases we could tell that a PSO applied to the user, based on info
1256 * already retrieved by the user search.
1258 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1259 if (ret != LDB_SUCCESS) {
1260 DBG_ERR("Error %d determining PSOs in system\n", ret);
1261 talloc_free(tmp_ctx);
1262 return ret;
1265 if (pso_count == 0) {
1266 talloc_free(tmp_ctx);
1267 return LDB_SUCCESS;
1270 /* Work out the SIDs of any account groups the user is a member of */
1271 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1272 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1273 &groupSIDs, &num_groupSIDs);
1274 if (ret != LDB_SUCCESS) {
1275 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1276 ldb_dn_get_linearized(user_msg->dn));
1277 talloc_free(tmp_ctx);
1278 return ret;
1281 /* lookup the best PSO that applies to any of these groups */
1282 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1283 num_groupSIDs, &best_pso);
1284 if (ret != LDB_SUCCESS) {
1285 talloc_free(tmp_ctx);
1286 return ret;
1289 *pso_msg = best_pso;
1290 return LDB_SUCCESS;
1294 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1295 * Settings Object (PSO) that applies to that user.
1297 static int construct_resultant_pso(struct ldb_module *module,
1298 struct ldb_message *msg,
1299 enum ldb_scope scope,
1300 struct ldb_request *parent,
1301 struct ldb_reply *ares)
1303 struct ldb_message *pso = NULL;
1304 int ret;
1306 /* work out the PSO (if any) that applies to this user */
1307 ret = get_pso_for_user(module, msg, parent, &pso);
1308 if (ret != LDB_SUCCESS) {
1309 DBG_ERR("Couldn't determine PSO for %s\n",
1310 ldb_dn_get_linearized(msg->dn));
1311 return ret;
1314 if (pso != NULL) {
1315 DBG_INFO("%s is resultant PSO for user %s\n",
1316 ldb_dn_get_linearized(pso->dn),
1317 ldb_dn_get_linearized(msg->dn));
1318 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1319 ldb_dn_get_linearized(pso->dn));
1322 /* no PSO applies to this user */
1323 return LDB_SUCCESS;
1326 struct op_controls_flags {
1327 bool sd;
1328 bool bypassoperational;
1331 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1332 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1333 return true;
1335 return false;
1339 a list of attribute names that should be substituted in the parse
1340 tree before the search is done
1342 static const struct {
1343 const char *attr;
1344 const char *replace;
1345 } parse_tree_sub[] = {
1346 { "createTimeStamp", "whenCreated" },
1347 { "modifyTimeStamp", "whenChanged" }
1351 struct op_attributes_replace {
1352 const char *attr;
1353 const char *replace;
1354 const char * const *extra_attrs;
1355 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *, struct ldb_reply *);
1358 /* the 'extra_attrs' required for msDS-ResultantPSO */
1359 #define RESULTANT_PSO_COMPUTED_ATTRS \
1360 "msDS-PSOApplied", \
1361 "userAccountControl", \
1362 "objectSid", \
1363 "msDS-SecondaryKrbTgtNumber", \
1364 "primaryGroupID"
1367 * any other constructed attributes that want to work out the PSO also need to
1368 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1370 #define PSO_ATTR_DEPENDENCIES \
1371 RESULTANT_PSO_COMPUTED_ATTRS, \
1372 "objectClass"
1374 static const char *objectSid_attr[] =
1376 "objectSid",
1377 NULL
1381 static const char *objectCategory_attr[] =
1383 "objectCategory",
1384 NULL
1388 static const char *user_account_control_computed_attrs[] =
1390 "lockoutTime",
1391 "pwdLastSet",
1392 PSO_ATTR_DEPENDENCIES,
1393 NULL
1397 static const char *user_password_expiry_time_computed_attrs[] =
1399 "pwdLastSet",
1400 PSO_ATTR_DEPENDENCIES,
1401 NULL
1404 static const char *resultant_pso_computed_attrs[] =
1406 RESULTANT_PSO_COMPUTED_ATTRS,
1407 NULL
1410 static const char *managed_password_computed_attrs[] = {
1411 "msDS-GroupMSAMembership",
1412 "msDS-ManagedPasswordId",
1413 "msDS-ManagedPasswordInterval",
1414 "msDS-ManagedPasswordPreviousId",
1415 "objectClass",
1416 "objectSid",
1417 "whenCreated",
1418 NULL,
1422 a list of attribute names that are hidden, but can be searched for
1423 using another (non-hidden) name to produce the correct result
1425 static const struct op_attributes_replace search_sub[] = {
1426 { "createTimeStamp", "whenCreated", NULL , NULL },
1427 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1428 { "structuralObjectClass", "objectClass", NULL , NULL },
1429 { "canonicalName", NULL, NULL , construct_canonical_name },
1430 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1431 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1432 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1433 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1434 { "parentGUID", "objectGUID", NULL, construct_parent_guid },
1435 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1436 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1437 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1438 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1439 construct_msds_user_account_control_computed },
1440 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1441 construct_msds_user_password_expiry_time_computed },
1442 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1443 construct_resultant_pso },
1444 {"msDS-ManagedPassword",
1445 NULL,
1446 managed_password_computed_attrs,
1447 constructed_msds_managed_password},
1451 enum op_remove {
1452 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1453 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1454 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1455 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an ad hoc control has been specified */
1459 a list of attributes that may need to be removed from the
1460 underlying db return
1462 Some of these are attributes that were once stored, but are now calculated
1464 struct op_attributes_operations {
1465 const char *attr;
1466 enum op_remove op;
1469 static const struct op_attributes_operations operational_remove[] = {
1470 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1471 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1472 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1473 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1474 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1475 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1480 post process a search result record. For any search_sub[] attributes that were
1481 asked for, we need to call the appropriate copy routine to copy the result
1482 into the message, then remove any attributes that we added to the search but
1483 were not asked for by the user
1485 static int operational_search_post_process(struct ldb_module *module,
1486 struct ldb_message *msg,
1487 enum ldb_scope scope,
1488 const char * const *attrs_from_user,
1489 const char * const *attrs_searched_for,
1490 struct op_controls_flags* controls_flags,
1491 struct op_attributes_operations *list,
1492 unsigned int list_size,
1493 struct op_attributes_replace *list_replace,
1494 unsigned int list_replace_size,
1495 struct ldb_request *parent,
1496 struct ldb_reply *ares)
1498 struct ldb_context *ldb;
1499 unsigned int i, a = 0;
1500 bool constructed_attributes = false;
1502 ldb = ldb_module_get_ctx(module);
1504 /* removed any attrs that should not be shown to the user */
1505 for (i=0; i < list_size; i++) {
1506 ldb_msg_remove_attr(msg, list[i].attr);
1509 for (a=0; a < list_replace_size; a++) {
1510 if (check_keep_control_for_attribute(controls_flags,
1511 list_replace[a].attr)) {
1512 continue;
1515 /* construct the new attribute, using either a supplied
1516 constructor or a simple copy */
1517 constructed_attributes = true;
1518 if (list_replace[a].constructor != NULL) {
1519 if (list_replace[a].constructor(module, msg, scope, parent, ares) != LDB_SUCCESS) {
1520 goto failed;
1522 } else if (ldb_msg_copy_attr(msg,
1523 list_replace[a].replace,
1524 list_replace[a].attr) != LDB_SUCCESS) {
1525 goto failed;
1529 /* Deletion of the search helper attributes are needed if:
1530 * - we generated constructed attributes and
1531 * - we aren't requesting all attributes
1533 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1534 for (i=0; i < list_replace_size; i++) {
1535 /* remove the added search helper attributes, unless
1536 * they were asked for by the user */
1537 if (list_replace[i].replace != NULL &&
1538 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1539 ldb_msg_remove_attr(msg, list_replace[i].replace);
1541 if (list_replace[i].extra_attrs != NULL) {
1542 unsigned int j;
1543 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1544 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1545 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1552 return 0;
1554 failed:
1555 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1556 "operational_search_post_process failed for attribute '%s' - %s",
1557 list_replace[a].attr, ldb_errstring(ldb));
1558 return -1;
1562 hook search operations
1565 struct operational_context {
1566 struct ldb_module *module;
1567 struct ldb_request *req;
1568 enum ldb_scope scope;
1569 const char * const *attrs;
1570 struct ldb_parse_tree *tree;
1571 struct op_controls_flags* controls_flags;
1572 struct op_attributes_operations *list_operations;
1573 unsigned int list_operations_size;
1574 struct op_attributes_replace *attrs_to_replace;
1575 unsigned int attrs_to_replace_size;
1578 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1580 struct operational_context *ac;
1581 int ret;
1583 ac = talloc_get_type(req->context, struct operational_context);
1585 if (!ares) {
1586 return ldb_module_done(ac->req, NULL, NULL,
1587 LDB_ERR_OPERATIONS_ERROR);
1589 if (ares->error != LDB_SUCCESS) {
1590 return ldb_module_done(ac->req, ares->controls,
1591 ares->response, ares->error);
1594 switch (ares->type) {
1595 case LDB_REPLY_ENTRY:
1596 /* for each record returned post-process to add any derived
1597 attributes that have been asked for */
1598 ret = operational_search_post_process(ac->module,
1599 ares->message,
1600 ac->scope,
1601 ac->attrs,
1602 req->op.search.attrs,
1603 ac->controls_flags,
1604 ac->list_operations,
1605 ac->list_operations_size,
1606 ac->attrs_to_replace,
1607 ac->attrs_to_replace_size,
1608 req,
1609 ares);
1610 if (ret != 0) {
1611 return ldb_module_done(ac->req, NULL, NULL,
1612 LDB_ERR_OPERATIONS_ERROR);
1614 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1616 case LDB_REPLY_REFERRAL:
1617 return ldb_module_send_referral(ac->req, ares->referral);
1619 case LDB_REPLY_DONE:
1621 return ldb_module_done(ac->req, ares->controls,
1622 ares->response, LDB_SUCCESS);
1625 talloc_free(ares);
1626 return LDB_SUCCESS;
1629 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1630 const char* const* attrs,
1631 const char* const* searched_attrs,
1632 struct op_controls_flags* controls_flags)
1634 int idx = 0;
1635 int i;
1636 struct op_attributes_operations *list = talloc_zero_array(ctx,
1637 struct op_attributes_operations,
1638 ARRAY_SIZE(operational_remove) + 1);
1640 if (list == NULL) {
1641 return NULL;
1644 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1645 switch (operational_remove[i].op) {
1646 case OPERATIONAL_REMOVE_UNASKED:
1647 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1648 continue;
1650 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1651 continue;
1653 list[idx].attr = operational_remove[i].attr;
1654 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1655 idx++;
1656 break;
1658 case OPERATIONAL_REMOVE_ALWAYS:
1659 list[idx].attr = operational_remove[i].attr;
1660 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1661 idx++;
1662 break;
1664 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1665 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1666 list[idx].attr = operational_remove[i].attr;
1667 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1668 idx++;
1670 break;
1672 case OPERATIONAL_SD_FLAGS:
1673 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1674 continue;
1676 if (controls_flags->sd) {
1677 if (attrs == NULL) {
1678 continue;
1680 if (attrs[0] == NULL) {
1681 continue;
1683 if (ldb_attr_in_list(attrs, "*")) {
1684 continue;
1687 list[idx].attr = operational_remove[i].attr;
1688 list[idx].op = OPERATIONAL_SD_FLAGS;
1689 idx++;
1690 break;
1694 return list;
1697 struct operational_present_ctx {
1698 const char *attr;
1699 bool found_operational;
1703 callback to determine if an operational attribute (needing
1704 replacement) is in use at all
1706 static int operational_present(struct ldb_parse_tree *tree, void *private_context)
1708 struct operational_present_ctx *ctx = private_context;
1709 switch (tree->operation) {
1710 case LDB_OP_EQUALITY:
1711 if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
1712 ctx->found_operational = true;
1714 break;
1715 case LDB_OP_GREATER:
1716 case LDB_OP_LESS:
1717 case LDB_OP_APPROX:
1718 if (ldb_attr_cmp(tree->u.comparison.attr, ctx->attr) == 0) {
1719 ctx->found_operational = true;
1721 break;
1722 case LDB_OP_SUBSTRING:
1723 if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
1724 ctx->found_operational = true;
1726 break;
1727 case LDB_OP_PRESENT:
1728 if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
1729 ctx->found_operational = true;
1731 break;
1732 case LDB_OP_EXTENDED:
1733 if (tree->u.extended.attr &&
1734 ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
1735 ctx->found_operational = true;
1737 break;
1738 default:
1739 break;
1741 return LDB_SUCCESS;
1745 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1747 struct ldb_context *ldb;
1748 struct operational_context *ac;
1749 struct ldb_request *down_req;
1750 const char **search_attrs = NULL;
1751 struct operational_present_ctx ctx;
1752 unsigned int i, a;
1753 int ret;
1755 /* There are no operational attributes on special DNs */
1756 if (ldb_dn_is_special(req->op.search.base)) {
1757 return ldb_next_request(module, req);
1760 ldb = ldb_module_get_ctx(module);
1762 ac = talloc(req, struct operational_context);
1763 if (ac == NULL) {
1764 return ldb_oom(ldb);
1767 ac->module = module;
1768 ac->req = req;
1769 ac->scope = req->op.search.scope;
1770 ac->attrs = req->op.search.attrs;
1772 ctx.found_operational = false;
1775 * find any attributes in the parse tree that are searchable,
1776 * but are stored using a different name in the backend, so we
1777 * only duplicate the memory when needed
1779 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1780 ctx.attr = parse_tree_sub[i].attr;
1782 ldb_parse_tree_walk(req->op.search.tree,
1783 operational_present,
1784 &ctx);
1785 if (ctx.found_operational) {
1786 break;
1790 if (ctx.found_operational) {
1792 ac->tree = ldb_parse_tree_copy_shallow(ac,
1793 req->op.search.tree);
1795 if (ac->tree == NULL) {
1796 return ldb_operr(ldb);
1799 /* replace any attributes in the parse tree that are
1800 searchable, but are stored using a different name in the
1801 backend */
1802 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1803 ldb_parse_tree_attr_replace(ac->tree,
1804 parse_tree_sub[i].attr,
1805 parse_tree_sub[i].replace);
1807 } else {
1808 /* Avoid allocating a copy if we do not need to */
1809 ac->tree = req->op.search.tree;
1812 ac->controls_flags = talloc(ac, struct op_controls_flags);
1813 /* remember if the SD_FLAGS_OID was set */
1814 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1815 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1816 ac->controls_flags->bypassoperational =
1817 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1819 ac->attrs_to_replace = NULL;
1820 ac->attrs_to_replace_size = 0;
1821 /* in the list of attributes we are looking for, rename any
1822 attributes to the alias for any hidden attributes that can
1823 be fetched directly using non-hidden names.
1824 Note that order here can affect performance, e.g. we should process
1825 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1826 latter is also dependent on the PSO information) */
1827 for (a=0;ac->attrs && ac->attrs[a];a++) {
1828 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1829 continue;
1831 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1833 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1834 continue;
1837 ac->attrs_to_replace = talloc_realloc(ac,
1838 ac->attrs_to_replace,
1839 struct op_attributes_replace,
1840 ac->attrs_to_replace_size + 1);
1842 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1843 ac->attrs_to_replace_size++;
1845 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1846 unsigned int j;
1847 const char **search_attrs2;
1848 /* Only adds to the end of the list */
1849 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1850 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1851 ? search_attrs
1852 : ac->attrs,
1853 search_sub[i].extra_attrs[j]);
1854 if (search_attrs2 == NULL) {
1855 return ldb_operr(ldb);
1857 /* may be NULL, talloc_free() doesn't mind */
1858 talloc_free(search_attrs);
1859 search_attrs = search_attrs2;
1863 if (!search_sub[i].replace) {
1864 continue;
1867 if (!search_attrs) {
1868 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1869 if (search_attrs == NULL) {
1870 return ldb_operr(ldb);
1873 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1874 search_attrs[a] = search_sub[i].replace;
1877 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1878 search_attrs == NULL?req->op.search.attrs:search_attrs,
1879 ac->controls_flags);
1880 ac->list_operations_size = 0;
1881 i = 0;
1883 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1884 i++;
1886 ac->list_operations_size = i;
1887 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1888 req->op.search.base,
1889 req->op.search.scope,
1890 ac->tree,
1891 /* use new set of attrs if any */
1892 search_attrs == NULL?req->op.search.attrs:search_attrs,
1893 req->controls,
1894 ac, operational_callback,
1895 req);
1896 LDB_REQ_SET_LOCATION(down_req);
1897 if (ret != LDB_SUCCESS) {
1898 return ldb_operr(ldb);
1901 /* perform the search */
1902 return ldb_next_request(module, down_req);
1905 static int operational_init(struct ldb_module *ctx)
1907 struct operational_data *data;
1908 int ret;
1910 ret = ldb_next_init(ctx);
1912 if (ret != LDB_SUCCESS) {
1913 return ret;
1916 data = talloc_zero(ctx, struct operational_data);
1917 if (!data) {
1918 return ldb_module_oom(ctx);
1921 ldb_module_set_private(ctx, data);
1923 return LDB_SUCCESS;
1926 static const struct ldb_module_ops ldb_operational_module_ops = {
1927 .name = "operational",
1928 .search = operational_search,
1929 .init_context = operational_init
1932 int ldb_operational_module_init(const char *version)
1934 LDB_MODULE_CHECK_VERSION(version);
1935 return ldb_register_module(&ldb_operational_module_ops);