s3:printing: Allow to run samba-bgqd as a standalone systemd service
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blobd911118417891a93b371076728e75fd69bb03c85
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 enum expire_uf_smartcard {
103 EXPIRE_UF_SMARTCARD_UNINIT = 0,
104 NO_EXPIRE_UF_SMARTCARD = 1,
105 EXPIRE_UF_SMARTCARD = 2
108 struct operational_context {
109 struct ldb_module *module;
110 struct ldb_request *req;
111 enum ldb_scope scope;
112 const char * const *attrs;
113 struct ldb_parse_tree *tree;
114 struct op_controls_flags* controls_flags;
115 struct op_attributes_operations *list_operations;
116 unsigned int list_operations_size;
117 struct op_attributes_replace *attrs_to_replace;
118 unsigned int attrs_to_replace_size;
119 enum expire_uf_smartcard expire_passwords_onsmartcardonlyaccounts;
120 NTTIME now;
123 static int get_pso_for_user(struct ldb_module *module,
124 struct ldb_message *user_msg,
125 struct ldb_request *parent,
126 struct ldb_message **pso_msg);
129 construct a canonical name from a message
131 static int construct_canonical_name(struct ldb_module *module,
132 struct ldb_message *msg, enum ldb_scope scope,
133 struct ldb_request *parent, struct ldb_reply *ares)
135 char *canonicalName;
136 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
137 if (canonicalName == NULL) {
138 return ldb_operr(ldb_module_get_ctx(module));
140 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
144 construct a primary group token for groups from a message
146 static int construct_primary_group_token(struct ldb_module *module,
147 struct ldb_message *msg, enum ldb_scope scope,
148 struct ldb_request *parent, struct ldb_reply *ares)
150 struct ldb_context *ldb;
151 uint32_t primary_group_token;
153 ldb = ldb_module_get_ctx(module);
154 if (ldb_match_msg_objectclass(msg, "group") == 1) {
155 primary_group_token
156 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
157 if (primary_group_token == 0) {
158 return LDB_SUCCESS;
161 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
162 primary_group_token);
163 } else {
164 return LDB_SUCCESS;
169 * Returns the group SIDs for the user in the given LDB message
171 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
172 struct ldb_message *msg, const char *attribute_string,
173 enum search_type type, struct auth_SidAttr **groupSIDs,
174 uint32_t *num_groupSIDs)
176 const char *filter = NULL;
177 NTSTATUS status;
178 struct dom_sid *primary_group_sid;
179 const char *primary_group_string;
180 const char *primary_group_dn;
181 DATA_BLOB primary_group_blob;
182 struct dom_sid *account_sid;
183 const char *account_sid_string;
184 const char *account_sid_dn;
185 DATA_BLOB account_sid_blob;
186 struct dom_sid *domain_sid;
188 /* If it's not a user, it won't have a primaryGroupID */
189 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
190 return LDB_SUCCESS;
193 /* Ensure it has an objectSID too */
194 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
195 if (account_sid == NULL) {
196 return LDB_SUCCESS;
199 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
200 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
201 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
202 } else if (!NT_STATUS_IS_OK(status)) {
203 return LDB_ERR_OPERATIONS_ERROR;
206 primary_group_sid = dom_sid_add_rid(mem_ctx,
207 domain_sid,
208 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
209 if (!primary_group_sid) {
210 return ldb_oom(ldb);
213 /* only return security groups */
214 switch(type) {
215 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
216 filter = talloc_asprintf(mem_ctx,
217 "(&(objectClass=group)"
218 "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
219 "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
220 GROUP_TYPE_SECURITY_ENABLED,
221 GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
222 break;
223 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
224 case TOKEN_GROUPS:
225 filter = talloc_asprintf(mem_ctx,
226 "(&(objectClass=group)"
227 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
228 GROUP_TYPE_SECURITY_ENABLED);
229 break;
231 /* for RevMembGetAccountGroups, exclude built-in groups */
232 case ACCOUNT_GROUPS:
233 filter = talloc_asprintf(mem_ctx,
234 "(&(objectClass=group)"
235 "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
236 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
237 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
238 break;
241 if (!filter) {
242 return ldb_oom(ldb);
245 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
246 if (!primary_group_string) {
247 return ldb_oom(ldb);
250 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
251 if (!primary_group_dn) {
252 return ldb_oom(ldb);
255 primary_group_blob = data_blob_string_const(primary_group_dn);
257 account_sid_string = dom_sid_string(mem_ctx, account_sid);
258 if (!account_sid_string) {
259 return ldb_oom(ldb);
262 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
263 if (!account_sid_dn) {
264 return ldb_oom(ldb);
267 account_sid_blob = data_blob_string_const(account_sid_dn);
269 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
270 true, /* We don't want to add the object's SID itself,
271 it's not returned in this attribute */
272 filter,
273 mem_ctx, groupSIDs, num_groupSIDs);
275 if (!NT_STATUS_IS_OK(status)) {
276 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
277 attribute_string, account_sid_string,
278 nt_errstr(status));
279 return LDB_ERR_OPERATIONS_ERROR;
282 /* Expands the primary group - this function takes in
283 * memberOf-like values, so we fake one up with the
284 * <SID=S-...> format of DN and then let it expand
285 * them, as long as they meet the filter - so only
286 * domain groups, not builtin groups
288 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
289 mem_ctx, groupSIDs, num_groupSIDs);
290 if (!NT_STATUS_IS_OK(status)) {
291 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
292 attribute_string, account_sid_string,
293 nt_errstr(status));
294 return LDB_ERR_OPERATIONS_ERROR;
297 return LDB_SUCCESS;
301 construct the token groups for SAM objects from a message
303 static int construct_generic_token_groups(struct ldb_module *module,
304 struct ldb_message *msg, enum ldb_scope scope,
305 struct ldb_request *parent,
306 const char *attribute_string,
307 enum search_type type)
309 struct ldb_context *ldb = ldb_module_get_ctx(module);
310 TALLOC_CTX *tmp_ctx = talloc_new(msg);
311 uint32_t i;
312 int ret;
313 struct auth_SidAttr *groupSIDs = NULL;
314 uint32_t num_groupSIDs = 0;
316 if (scope != LDB_SCOPE_BASE) {
317 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
318 return LDB_ERR_OPERATIONS_ERROR;
321 /* calculate the group SIDs for this object */
322 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
323 &groupSIDs, &num_groupSIDs);
325 if (ret != LDB_SUCCESS) {
326 talloc_free(tmp_ctx);
327 return LDB_ERR_OPERATIONS_ERROR;
330 /* add these SIDs to the search result */
331 for (i=0; i < num_groupSIDs; i++) {
332 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
333 if (ret) {
334 talloc_free(tmp_ctx);
335 return ret;
339 return LDB_SUCCESS;
342 static int construct_token_groups(struct ldb_module *module,
343 struct ldb_message *msg, enum ldb_scope scope,
344 struct ldb_request *parent, struct ldb_reply *ares)
347 * TODO: Add in a limiting domain when we start to support
348 * trusted domains.
350 return construct_generic_token_groups(module, msg, scope, parent,
351 "tokenGroups",
352 TOKEN_GROUPS);
355 static int construct_token_groups_no_gc(struct ldb_module *module,
356 struct ldb_message *msg, enum ldb_scope scope,
357 struct ldb_request *parent, struct ldb_reply *ares)
360 * TODO: Add in a limiting domain when we start to support
361 * trusted domains.
363 return construct_generic_token_groups(module, msg, scope, parent,
364 "tokenGroupsNoGCAcceptable",
365 TOKEN_GROUPS);
368 static int construct_global_universal_token_groups(struct ldb_module *module,
369 struct ldb_message *msg, enum ldb_scope scope,
370 struct ldb_request *parent, struct ldb_reply *ares)
372 return construct_generic_token_groups(module, msg, scope, parent,
373 "tokenGroupsGlobalAndUniversal",
374 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
377 construct the parent GUID for an entry from a message
379 static int construct_parent_guid(struct ldb_module *module,
380 struct ldb_message *msg, enum ldb_scope scope,
381 struct ldb_request *parent, struct ldb_reply *ares)
383 struct ldb_result *res, *parent_res;
384 const struct ldb_val *parent_guid;
385 const char *attrs[] = { "instanceType", NULL };
386 const char *attrs2[] = { "objectGUID", NULL };
387 uint32_t instanceType;
388 int ret;
389 struct ldb_dn *parent_dn;
390 struct ldb_val v;
392 /* determine if the object is NC by instance type */
393 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
394 DSDB_FLAG_NEXT_MODULE |
395 DSDB_SEARCH_SHOW_RECYCLED, parent);
396 if (ret != LDB_SUCCESS) {
397 return ret;
400 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
401 "instanceType", 0);
402 talloc_free(res);
403 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
404 DEBUG(4,(__location__ ": Object %s is NC\n",
405 ldb_dn_get_linearized(msg->dn)));
406 return LDB_SUCCESS;
408 parent_dn = ldb_dn_get_parent(msg, msg->dn);
410 if (parent_dn == NULL) {
411 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
412 ldb_dn_get_linearized(msg->dn)));
413 return LDB_ERR_OTHER;
415 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
416 DSDB_FLAG_NEXT_MODULE |
417 DSDB_SEARCH_SHOW_RECYCLED, parent);
418 /* not NC, so the object should have a parent*/
419 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
420 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
421 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
422 ldb_dn_get_linearized(parent_dn),
423 ldb_dn_get_linearized(msg->dn)));
424 talloc_free(parent_dn);
425 return ret;
426 } else if (ret != LDB_SUCCESS) {
427 talloc_free(parent_dn);
428 return ret;
430 talloc_free(parent_dn);
432 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
433 if (!parent_guid) {
434 talloc_free(parent_res);
435 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
438 v = data_blob_dup_talloc(parent_res, *parent_guid);
439 if (!v.data) {
440 talloc_free(parent_res);
441 return ldb_oom(ldb_module_get_ctx(module));
443 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
444 talloc_free(parent_res);
445 return ret;
448 static int construct_modifyTimeStamp(struct ldb_module *module,
449 struct ldb_message *msg, enum ldb_scope scope,
450 struct ldb_request *parent, struct ldb_reply *ares)
452 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
453 struct ldb_context *ldb = ldb_module_get_ctx(module);
455 /* We may be being called before the init function has finished */
456 if (!data) {
457 return LDB_SUCCESS;
460 /* Try and set this value up, if possible. Don't worry if it
461 * fails, we may not have the DB set up yet.
463 if (!data->aggregate_dn) {
464 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
467 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
469 * If we have the DN for the object with common name = Aggregate and
470 * the request is for this DN then let's do the following:
471 * 1) search the object which changedUSN correspond to the one of the loaded
472 * schema.
473 * 2) Get the whenChanged attribute
474 * 3) Generate the modifyTimestamp out of the whenChanged attribute
476 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
477 char *value = ldb_timestring(msg, schema->ts_last_change);
479 if (value == NULL) {
480 return ldb_oom(ldb_module_get_ctx(module));
483 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
485 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
489 construct a subSchemaSubEntry
491 static int construct_subschema_subentry(struct ldb_module *module,
492 struct ldb_message *msg, enum ldb_scope scope,
493 struct ldb_request *parent, struct ldb_reply *ares)
495 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
496 char *subSchemaSubEntry;
498 /* We may be being called before the init function has finished */
499 if (!data) {
500 return LDB_SUCCESS;
503 /* Try and set this value up, if possible. Don't worry if it
504 * fails, we may not have the DB set up yet, and it's not
505 * really vital anyway */
506 if (!data->aggregate_dn) {
507 struct ldb_context *ldb = ldb_module_get_ctx(module);
508 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
511 if (data->aggregate_dn) {
512 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
513 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
515 return LDB_SUCCESS;
519 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
520 struct ldb_message *msg,
521 struct ldb_message_element *object_category)
523 struct ldb_context *ldb;
524 struct ldb_dn *dn;
525 const struct ldb_val *val;
527 ldb = ldb_module_get_ctx(module);
528 if (!ldb) {
529 DEBUG(4, (__location__ ": Failed to get ldb \n"));
530 return LDB_ERR_OPERATIONS_ERROR;
533 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
534 if (!dn) {
535 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
536 (const char *)object_category->values[0].data));
537 return ldb_operr(ldb);
540 val = ldb_dn_get_rdn_val(dn);
541 if (!val) {
542 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
543 ldb_dn_get_linearized(dn)));
544 return ldb_operr(ldb);
547 if (strequal((const char *)val->data, "NTDS-DSA")) {
548 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
549 } else {
550 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
552 return LDB_SUCCESS;
555 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
556 struct ldb_message *msg,
557 struct ldb_dn *dn,
558 struct ldb_request *parent)
560 struct ldb_dn *server_dn;
561 const char *attr_obj_cat[] = { "objectCategory", NULL };
562 struct ldb_result *res;
563 struct ldb_message_element *object_category;
564 int ret;
566 server_dn = ldb_dn_copy(msg, dn);
567 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
568 DEBUG(4, (__location__ ": Failed to add child to %s \n",
569 ldb_dn_get_linearized(server_dn)));
570 return ldb_operr(ldb_module_get_ctx(module));
573 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
574 DSDB_FLAG_NEXT_MODULE, parent);
575 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
576 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
577 ldb_dn_get_linearized(server_dn)));
578 return LDB_SUCCESS;
579 } else if (ret != LDB_SUCCESS) {
580 return ret;
583 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
584 if (!object_category) {
585 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
586 ldb_dn_get_linearized(res->msgs[0]->dn)));
587 return LDB_SUCCESS;
589 return construct_msds_isrodc_with_dn(module, msg, object_category);
592 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
593 struct ldb_message *msg,
594 struct ldb_request *parent)
596 int ret;
597 struct ldb_dn *server_dn;
599 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
600 &server_dn, parent);
601 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
602 /* it's OK if we can't find serverReferenceBL attribute */
603 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
604 ldb_dn_get_linearized(msg->dn)));
605 return LDB_SUCCESS;
606 } else if (ret != LDB_SUCCESS) {
607 return ret;
610 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
614 construct msDS-isRODC attr
616 static int construct_msds_isrodc(struct ldb_module *module,
617 struct ldb_message *msg, enum ldb_scope scope,
618 struct ldb_request *parent, struct ldb_reply *ares)
620 struct ldb_message_element * object_class;
621 struct ldb_message_element * object_category;
622 unsigned int i;
624 object_class = ldb_msg_find_element(msg, "objectClass");
625 if (!object_class) {
626 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
627 ldb_dn_get_linearized(msg->dn)));
628 return ldb_operr(ldb_module_get_ctx(module));
631 for (i=0; i<object_class->num_values; i++) {
632 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
633 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
634 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
636 object_category = ldb_msg_find_element(msg, "objectCategory");
637 if (!object_category) {
638 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
639 ldb_dn_get_linearized(msg->dn)));
640 return LDB_SUCCESS;
642 return construct_msds_isrodc_with_dn(module, msg, object_category);
644 if (strequal((const char*)object_class->values[i].data, "server")) {
645 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
646 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
647 * substituting TN for TO.
649 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
651 if (strequal((const char*)object_class->values[i].data, "computer")) {
652 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
653 * rule for the "TO is a server object" case, substituting TS for TO.
655 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
659 return LDB_SUCCESS;
664 construct msDS-keyVersionNumber attr
666 TODO: Make this based on the 'win2k' DS heuristics bit...
669 static int construct_msds_keyversionnumber(struct ldb_module *module,
670 struct ldb_message *msg,
671 enum ldb_scope scope,
672 struct ldb_request *parent,
673 struct ldb_reply *ares)
675 uint32_t i;
676 enum ndr_err_code ndr_err;
677 const struct ldb_val *omd_value;
678 struct replPropertyMetaDataBlob *omd;
679 int ret;
681 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
682 if (!omd_value) {
683 /* We can't make up a key version number without meta data */
684 return LDB_SUCCESS;
687 omd = talloc(msg, struct replPropertyMetaDataBlob);
688 if (!omd) {
689 ldb_module_oom(module);
690 return LDB_SUCCESS;
693 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
694 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
695 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
696 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
697 ldb_dn_get_linearized(msg->dn)));
698 return ldb_operr(ldb_module_get_ctx(module));
701 if (omd->version != 1) {
702 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
703 omd->version, ldb_dn_get_linearized(msg->dn)));
704 talloc_free(omd);
705 return LDB_SUCCESS;
707 for (i=0; i<omd->ctr.ctr1.count; i++) {
708 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
709 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
710 msg, msg,
711 "msDS-KeyVersionNumber",
712 omd->ctr.ctr1.array[i].version);
713 if (ret != LDB_SUCCESS) {
714 talloc_free(omd);
715 return ret;
717 break;
720 return LDB_SUCCESS;
724 #define _UF_NO_EXPIRY_ACCOUNTS ( \
725 UF_DONT_EXPIRE_PASSWD | \
726 UF_TRUST_ACCOUNT_MASK \
731 * Returns the Effective-MaximumPasswordAge for a user
733 static int64_t get_user_max_pwd_age(struct ldb_module *module,
734 struct ldb_message *user_msg,
735 struct ldb_request *parent,
736 struct ldb_dn *nc_root)
738 int ret;
739 struct ldb_message *pso = NULL;
740 struct ldb_context *ldb = ldb_module_get_ctx(module);
742 /* if a PSO applies to the user, use its maxPwdAge */
743 ret = get_pso_for_user(module, user_msg, parent, &pso);
744 if (ret != LDB_SUCCESS) {
746 /* log the error, but fallback to the domain default */
747 DBG_ERR("Error retrieving PSO for %s\n",
748 ldb_dn_get_linearized(user_msg->dn));
751 if (pso != NULL) {
752 return ldb_msg_find_attr_as_int64(pso,
753 "msDS-MaximumPasswordAge", 0);
756 /* otherwise return the default domain value */
757 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
760 static enum expire_uf_smartcard get_expire_passwords_onsmartcardonlyaccounts(struct ldb_module *module,
761 struct operational_context *ac)
763 struct ldb_context *ldb = ldb_module_get_ctx(module);
764 if (ac->expire_passwords_onsmartcardonlyaccounts != EXPIRE_UF_SMARTCARD_UNINIT) {
765 return ac->expire_passwords_onsmartcardonlyaccounts;
768 if (dsdb_functional_level(ldb) < DS_DOMAIN_FUNCTION_2016) {
769 ac->expire_passwords_onsmartcardonlyaccounts
770 = NO_EXPIRE_UF_SMARTCARD;
771 } else {
772 const char *base_attrs[] = { "msDS-ExpirePasswordsOnSmartCardOnlyAccounts",
773 NULL };
774 struct ldb_message *base_msg;
775 bool attr_in_ldb;
777 int ldb_ret = dsdb_search_one(ldb, ac,
778 &base_msg,
779 ldb_get_default_basedn(ldb),
780 LDB_SCOPE_BASE,
781 base_attrs, 0, NULL);
782 if (ldb_ret != LDB_SUCCESS) {
783 DBG_WARNING("could not find own base DN in DB: %s\n", ldb_errstring(ldb));
784 return EXPIRE_UF_SMARTCARD_UNINIT;
787 * This attribute is to allow these passwords to
788 * expire, and if they expire to rotate them. TRUE
789 * means rotate, FALSE or absent meant never allow to
790 * expire.
792 attr_in_ldb = ldb_msg_find_attr_as_bool(base_msg,
793 "msDS-ExpirePasswordsOnSmartCardOnlyAccounts",
794 false);
795 talloc_free(base_msg);
796 if (attr_in_ldb) {
797 ac->expire_passwords_onsmartcardonlyaccounts
798 = EXPIRE_UF_SMARTCARD;
799 } else {
800 ac->expire_passwords_onsmartcardonlyaccounts
801 = NO_EXPIRE_UF_SMARTCARD;
804 return ac->expire_passwords_onsmartcardonlyaccounts;
809 calculate msDS-UserPasswordExpiryTimeComputed
811 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
812 struct operational_context *ac,
813 struct ldb_message *msg,
814 struct ldb_request *parent,
815 struct ldb_dn *domain_dn)
817 int64_t pwdLastSet, maxPwdAge;
818 uint32_t userAccountControl;
819 NTTIME ret;
821 userAccountControl = ldb_msg_find_attr_as_uint(msg,
822 "userAccountControl",
824 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
825 return INT64_MAX;
828 if (userAccountControl & UF_SMARTCARD_REQUIRED) {
829 enum expire_uf_smartcard expire_uf_smartcard =
830 get_expire_passwords_onsmartcardonlyaccounts(module, ac);
832 if (expire_uf_smartcard != EXPIRE_UF_SMARTCARD) {
833 return INT64_MAX;
837 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
838 if (pwdLastSet == 0) {
839 return 0;
842 if (pwdLastSet <= -1) {
844 * This can't really happen...
846 return INT64_MAX;
849 if (pwdLastSet >= INT64_MAX) {
851 * Somethings wrong with the clock...
853 return INT64_MAX;
857 * Note that maxPwdAge is a stored as negative value.
859 * Possible values are in the range of:
861 * maxPwdAge: -2
862 * to
863 * maxPwdAge: -9223372036854775808 (INT64_MIN)
866 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
867 if (maxPwdAge >= -1) {
869 * This is not really possible...
871 return INT64_MAX;
874 if (maxPwdAge == INT64_MIN) {
875 return INT64_MAX;
879 * Note we already caught maxPwdAge == INT64_MIN
880 * and pwdLastSet >= INT64_MAX above.
882 * Remember maxPwdAge is a negative number,
883 * so it results in the following.
885 * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
887 * 0xFFFFFFFFFFFFFFFDULL
889 * or to put it another way, adding two numbers less than 1<<63 can't
890 * ever be more than 1<<64, therefore this result can't wrap.
892 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
893 if (ret >= INT64_MAX) {
894 return INT64_MAX;
897 return ret;
901 * Returns the Effective-LockoutDuration for a user
903 static int64_t get_user_lockout_duration(struct ldb_module *module,
904 struct ldb_message *user_msg,
905 struct ldb_request *parent,
906 struct ldb_dn *nc_root)
908 int ret;
909 struct ldb_message *pso = NULL;
910 struct ldb_context *ldb = ldb_module_get_ctx(module);
912 /* if a PSO applies to the user, use its lockoutDuration */
913 ret = get_pso_for_user(module, user_msg, parent, &pso);
914 if (ret != LDB_SUCCESS) {
916 /* log the error, but fallback to the domain default */
917 DBG_ERR("Error retrieving PSO for %s\n",
918 ldb_dn_get_linearized(user_msg->dn));
921 if (pso != NULL) {
922 return ldb_msg_find_attr_as_int64(pso,
923 "msDS-LockoutDuration", 0);
926 /* otherwise return the default domain value */
927 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
928 NULL);
932 construct msDS-User-Account-Control-Computed attr
934 static int construct_msds_user_account_control_computed(struct ldb_module *module,
935 struct ldb_message *msg, enum ldb_scope scope,
936 struct ldb_request *parent, struct ldb_reply *ares)
938 uint32_t msDS_User_Account_Control_Computed = 0;
939 struct ldb_context *ldb = ldb_module_get_ctx(module);
940 struct ldb_dn *nc_root;
941 NTTIME must_change_time;
942 int ret;
943 struct operational_context *ac
944 = talloc_get_type_abort(parent->context,
945 struct operational_context);
947 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
948 if (ret != 0) {
949 ldb_asprintf_errstring(ldb,
950 "Failed to find NC root of DN: %s: %s",
951 ldb_dn_get_linearized(msg->dn),
952 ldb_errstring(ldb_module_get_ctx(module)));
953 return ret;
955 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
956 /* Only calculate this on our default NC */
957 return 0;
960 if (ac->now == 0) {
961 /* Get the current or simulated time */
962 bool time_ok = dsdb_gmsa_current_time(ldb, &ac->now);
963 if (!time_ok) {
964 return ldb_module_operr(module);
968 if (!dsdb_account_is_trust(msg)) {
970 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
971 if (lockoutTime != 0) {
972 int64_t lockoutDuration;
974 lockoutDuration = get_user_lockout_duration(module, msg,
975 parent,
976 nc_root);
978 /* zero locks out until the administrator intervenes */
979 if (lockoutDuration >= 0) {
980 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
981 } else if (lockoutTime - lockoutDuration >= ac->now) {
982 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
987 must_change_time
988 = get_msds_user_password_expiry_time_computed(module,
990 msg,
991 parent,
992 nc_root);
993 /* check for expired password */
994 if (must_change_time < ac->now) {
995 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
998 return samdb_msg_add_int64(ldb,
999 msg->elements, msg,
1000 "msDS-User-Account-Control-Computed",
1001 msDS_User_Account_Control_Computed);
1005 construct msDS-UserPasswordExpiryTimeComputed
1007 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
1008 struct ldb_message *msg, enum ldb_scope scope,
1009 struct ldb_request *parent, struct ldb_reply *ares)
1011 struct ldb_context *ldb = ldb_module_get_ctx(module);
1012 struct operational_context *ac
1013 = talloc_get_type_abort(parent->context,
1014 struct operational_context);
1015 struct ldb_dn *nc_root;
1016 int64_t password_expiry_time;
1017 int ret;
1019 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
1020 if (ret != 0) {
1021 ldb_asprintf_errstring(ldb,
1022 "Failed to find NC root of DN: %s: %s",
1023 ldb_dn_get_linearized(msg->dn),
1024 ldb_errstring(ldb));
1025 return ret;
1028 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
1029 /* Only calculate this on our default NC */
1030 return 0;
1033 password_expiry_time
1034 = get_msds_user_password_expiry_time_computed(module,
1036 msg,
1037 parent, nc_root);
1039 return samdb_msg_add_int64(ldb,
1040 msg->elements, msg,
1041 "msDS-UserPasswordExpiryTimeComputed",
1042 password_expiry_time);
1046 * Checks whether the msDS-ResultantPSO attribute is supported for a given
1047 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
1049 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
1051 int functional_level;
1052 uint32_t uac;
1053 uint32_t user_rid;
1055 functional_level = dsdb_functional_level(ldb);
1056 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
1057 return false;
1060 /* msDS-ResultantPSO is only supported for user objects */
1061 if (!ldb_match_msg_objectclass(msg, "user")) {
1062 return false;
1065 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
1066 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
1067 if (!(uac & UF_NORMAL_ACCOUNT)) {
1068 return false;
1071 /* skip it if it's the special KRBTGT default account */
1072 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
1073 if (user_rid == DOMAIN_RID_KRBTGT) {
1074 return false;
1077 /* ...or if it's a special KRBTGT account for an RODC KDC */
1078 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
1079 return false;
1082 return true;
1086 * Returns the number of PSO objects that exist in the DB
1088 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1089 struct ldb_request *parent, int *pso_count)
1091 static const char * const attrs[] = { NULL };
1092 int ret;
1093 struct ldb_dn *psc_dn = NULL;
1094 struct ldb_result *res = NULL;
1095 struct ldb_context *ldb = ldb_module_get_ctx(module);
1096 bool psc_ok;
1098 *pso_count = 0;
1099 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1100 if (psc_dn == NULL) {
1101 return ldb_oom(ldb);
1103 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1104 if (psc_ok == false) {
1105 return ldb_oom(ldb);
1108 /* get the number of PSO children */
1109 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1110 LDB_SCOPE_ONELEVEL, attrs,
1111 DSDB_FLAG_NEXT_MODULE, parent,
1112 "(objectClass=msDS-PasswordSettings)");
1115 * Just ignore PSOs if the container doesn't exist. This is a weird
1116 * corner-case where the AD DB was created from a pre-2008 base schema,
1117 * and then the FL was manually upgraded.
1119 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1120 DBG_NOTICE("No Password Settings Container exists\n");
1121 return LDB_SUCCESS;
1124 if (ret != LDB_SUCCESS) {
1125 return ret;
1128 *pso_count = res->count;
1129 talloc_free(res);
1130 talloc_free(psc_dn);
1132 return LDB_SUCCESS;
1136 * Compares two PSO objects returned by a search, to work out the better PSO.
1137 * The PSO with the lowest precedence is better, otherwise (if the precedence
1138 * is equal) the PSO with the lower GUID wins.
1140 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1142 uint32_t prec1;
1143 uint32_t prec2;
1145 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1146 0xffffffff);
1147 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1148 0xffffffff);
1150 /* if precedence is equal, use the lowest GUID */
1151 if (prec1 == prec2) {
1152 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1153 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1155 return ndr_guid_compare(&guid1, &guid2);
1156 } else {
1157 return NUMERIC_CMP(prec1, prec2);
1162 * Search for PSO objects that apply to the object SIDs specified
1164 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1165 struct ldb_request *parent,
1166 struct auth_SidAttr *sid_array, unsigned int num_sids,
1167 struct ldb_result **result)
1169 int ret;
1170 int i;
1171 struct ldb_context *ldb = ldb_module_get_ctx(module);
1172 char *sid_filter = NULL;
1173 struct ldb_dn *psc_dn = NULL;
1174 bool psc_ok;
1175 const char *attrs[] = {
1176 "msDS-PasswordSettingsPrecedence",
1177 "objectGUID",
1178 "msDS-LockoutDuration",
1179 "msDS-MaximumPasswordAge",
1180 NULL
1183 /* build a query for PSO objects that apply to any of the SIDs given */
1184 sid_filter = talloc_strdup(mem_ctx, "");
1185 if (sid_filter == NULL) {
1186 return ldb_oom(ldb);
1189 for (i = 0; sid_filter && i < num_sids; i++) {
1190 struct dom_sid_buf sid_buf;
1192 sid_filter = talloc_asprintf_append(
1193 sid_filter,
1194 "(msDS-PSOAppliesTo=<SID=%s>)",
1195 dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
1196 if (sid_filter == NULL) {
1197 return ldb_oom(ldb);
1201 /* only PSOs located in the Password Settings Container are valid */
1202 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1203 if (psc_dn == NULL) {
1204 return ldb_oom(ldb);
1206 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1207 if (psc_ok == false) {
1208 return ldb_oom(ldb);
1211 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1212 LDB_SCOPE_ONELEVEL, attrs,
1213 DSDB_FLAG_NEXT_MODULE, parent,
1214 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1215 sid_filter);
1216 talloc_free(sid_filter);
1217 return ret;
1221 * Returns the best PSO object that applies to the object SID(s) specified
1223 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1224 struct ldb_request *parent, struct auth_SidAttr *sid_array,
1225 unsigned int num_sids, struct ldb_message **best_pso)
1227 struct ldb_result *res = NULL;
1228 int ret;
1230 *best_pso = NULL;
1232 /* find any PSOs that apply to the SIDs specified */
1233 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1234 &res);
1235 if (ret != LDB_SUCCESS) {
1236 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1237 return ret;
1240 /* sort the list so that the best PSO is first */
1241 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1243 if (res->count > 0) {
1244 *best_pso = res->msgs[0];
1247 return LDB_SUCCESS;
1251 * Determines the Password Settings Object (PSO) that applies to the given user
1253 static int get_pso_for_user(struct ldb_module *module,
1254 struct ldb_message *user_msg,
1255 struct ldb_request *parent,
1256 struct ldb_message **pso_msg)
1258 bool pso_supported;
1259 struct auth_SidAttr *groupSIDs = NULL;
1260 uint32_t num_groupSIDs = 0;
1261 struct ldb_context *ldb = ldb_module_get_ctx(module);
1262 struct ldb_message *best_pso = NULL;
1263 struct ldb_dn *pso_dn = NULL;
1264 int ret;
1265 struct ldb_message_element *el = NULL;
1266 TALLOC_CTX *tmp_ctx = NULL;
1267 int pso_count = 0;
1268 struct ldb_result *res = NULL;
1269 static const char *attrs[] = {
1270 "msDS-LockoutDuration",
1271 "msDS-MaximumPasswordAge",
1272 NULL
1275 *pso_msg = NULL;
1277 /* first, check msDS-ResultantPSO is supported for this object */
1278 pso_supported = pso_is_supported(ldb, user_msg);
1280 if (!pso_supported) {
1281 return LDB_SUCCESS;
1284 tmp_ctx = talloc_new(user_msg);
1287 * Several different constructed attributes try to use the PSO info. If
1288 * we've already constructed the msDS-ResultantPSO for this user, we can
1289 * just re-use the result, rather than calculating it from scratch again
1291 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1292 "msDS-ResultantPSO");
1294 if (pso_dn != NULL) {
1295 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1296 attrs, DSDB_FLAG_NEXT_MODULE,
1297 parent);
1298 if (ret != LDB_SUCCESS) {
1299 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1300 ldb_dn_get_linearized(pso_dn));
1301 talloc_free(tmp_ctx);
1302 return ret;
1305 if (res->count == 1) {
1306 *pso_msg = res->msgs[0];
1307 return LDB_SUCCESS;
1312 * if any PSOs apply directly to the user, they are considered first
1313 * before we check group membership PSOs
1315 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1317 if (el != NULL && el->num_values > 0) {
1318 struct auth_SidAttr *user_sid = NULL;
1320 /* lookup the best PSO object, based on the user's SID */
1321 user_sid = samdb_result_dom_sid_attrs(
1322 tmp_ctx, user_msg, "objectSid",
1323 SE_GROUP_DEFAULT_FLAGS);
1325 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1326 &best_pso);
1327 if (ret != LDB_SUCCESS) {
1328 talloc_free(tmp_ctx);
1329 return ret;
1332 if (best_pso != NULL) {
1333 *pso_msg = best_pso;
1334 return LDB_SUCCESS;
1339 * If no valid PSO applies directly to the user, then try its groups.
1340 * The group expansion is expensive, so check there are actually
1341 * PSOs in the DB first (which is a quick search). Note in the above
1342 * cases we could tell that a PSO applied to the user, based on info
1343 * already retrieved by the user search.
1345 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1346 if (ret != LDB_SUCCESS) {
1347 DBG_ERR("Error %d determining PSOs in system\n", ret);
1348 talloc_free(tmp_ctx);
1349 return ret;
1352 if (pso_count == 0) {
1353 talloc_free(tmp_ctx);
1354 return LDB_SUCCESS;
1357 /* Work out the SIDs of any account groups the user is a member of */
1358 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1359 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1360 &groupSIDs, &num_groupSIDs);
1361 if (ret != LDB_SUCCESS) {
1362 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1363 ldb_dn_get_linearized(user_msg->dn));
1364 talloc_free(tmp_ctx);
1365 return ret;
1368 /* lookup the best PSO that applies to any of these groups */
1369 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1370 num_groupSIDs, &best_pso);
1371 if (ret != LDB_SUCCESS) {
1372 talloc_free(tmp_ctx);
1373 return ret;
1376 *pso_msg = best_pso;
1377 return LDB_SUCCESS;
1381 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1382 * Settings Object (PSO) that applies to that user.
1384 static int construct_resultant_pso(struct ldb_module *module,
1385 struct ldb_message *msg,
1386 enum ldb_scope scope,
1387 struct ldb_request *parent,
1388 struct ldb_reply *ares)
1390 struct ldb_message *pso = NULL;
1391 int ret;
1393 /* work out the PSO (if any) that applies to this user */
1394 ret = get_pso_for_user(module, msg, parent, &pso);
1395 if (ret != LDB_SUCCESS) {
1396 DBG_ERR("Couldn't determine PSO for %s\n",
1397 ldb_dn_get_linearized(msg->dn));
1398 return ret;
1401 if (pso != NULL) {
1402 DBG_INFO("%s is resultant PSO for user %s\n",
1403 ldb_dn_get_linearized(pso->dn),
1404 ldb_dn_get_linearized(msg->dn));
1405 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1406 ldb_dn_get_linearized(pso->dn));
1409 /* no PSO applies to this user */
1410 return LDB_SUCCESS;
1413 struct op_controls_flags {
1414 bool sd;
1415 bool bypassoperational;
1418 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1419 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1420 return true;
1422 return false;
1426 a list of attribute names that should be substituted in the parse
1427 tree before the search is done
1429 static const struct {
1430 const char *attr;
1431 const char *replace;
1432 } parse_tree_sub[] = {
1433 { "createTimeStamp", "whenCreated" },
1434 { "modifyTimeStamp", "whenChanged" }
1438 struct op_attributes_replace {
1439 const char *attr;
1440 const char *replace;
1441 const char * const *extra_attrs;
1442 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *, struct ldb_reply *);
1445 /* the 'extra_attrs' required for msDS-ResultantPSO */
1446 #define RESULTANT_PSO_COMPUTED_ATTRS \
1447 "msDS-PSOApplied", \
1448 "userAccountControl", \
1449 "objectSid", \
1450 "msDS-SecondaryKrbTgtNumber", \
1451 "primaryGroupID"
1454 * any other constructed attributes that want to work out the PSO also need to
1455 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1457 #define PSO_ATTR_DEPENDENCIES \
1458 RESULTANT_PSO_COMPUTED_ATTRS, \
1459 "objectClass"
1461 static const char *objectSid_attr[] =
1463 "objectSid",
1464 NULL
1468 static const char *objectCategory_attr[] =
1470 "objectCategory",
1471 NULL
1475 static const char *user_account_control_computed_attrs[] =
1477 "lockoutTime",
1478 "pwdLastSet",
1479 PSO_ATTR_DEPENDENCIES,
1480 NULL
1484 static const char *user_password_expiry_time_computed_attrs[] =
1486 "pwdLastSet",
1487 PSO_ATTR_DEPENDENCIES,
1488 NULL
1491 static const char *resultant_pso_computed_attrs[] =
1493 RESULTANT_PSO_COMPUTED_ATTRS,
1494 NULL
1497 static const char *managed_password_computed_attrs[] = {
1498 "msDS-GroupMSAMembership",
1499 "msDS-ManagedPasswordId",
1500 "msDS-ManagedPasswordInterval",
1501 "msDS-ManagedPasswordPreviousId",
1502 "objectClass",
1503 "objectSid",
1504 "whenCreated",
1505 NULL,
1509 a list of attribute names that are hidden, but can be searched for
1510 using another (non-hidden) name to produce the correct result
1512 static const struct op_attributes_replace search_sub[] = {
1513 { "createTimeStamp", "whenCreated", NULL , NULL },
1514 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1515 { "structuralObjectClass", "objectClass", NULL , NULL },
1516 { "canonicalName", NULL, NULL , construct_canonical_name },
1517 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1518 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1519 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1520 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1521 { "parentGUID", "objectGUID", NULL, construct_parent_guid },
1522 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1523 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1524 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1525 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1526 construct_msds_user_account_control_computed },
1527 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1528 construct_msds_user_password_expiry_time_computed },
1529 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1530 construct_resultant_pso },
1531 {"msDS-ManagedPassword",
1532 NULL,
1533 managed_password_computed_attrs,
1534 constructed_msds_managed_password},
1538 enum op_remove {
1539 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1540 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1541 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1542 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an ad hoc control has been specified */
1546 a list of attributes that may need to be removed from the
1547 underlying db return
1549 Some of these are attributes that were once stored, but are now calculated
1551 struct op_attributes_operations {
1552 const char *attr;
1553 enum op_remove op;
1556 static const struct op_attributes_operations operational_remove[] = {
1557 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1558 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1559 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1560 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1561 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1562 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1567 post process a search result record. For any search_sub[] attributes that were
1568 asked for, we need to call the appropriate copy routine to copy the result
1569 into the message, then remove any attributes that we added to the search but
1570 were not asked for by the user
1572 static int operational_search_post_process(struct ldb_module *module,
1573 struct ldb_message *msg,
1574 enum ldb_scope scope,
1575 const char * const *attrs_from_user,
1576 const char * const *attrs_searched_for,
1577 struct op_controls_flags* controls_flags,
1578 struct op_attributes_operations *list,
1579 unsigned int list_size,
1580 struct op_attributes_replace *list_replace,
1581 unsigned int list_replace_size,
1582 struct ldb_request *parent,
1583 struct ldb_reply *ares)
1585 struct ldb_context *ldb;
1586 unsigned int i, a = 0;
1587 bool constructed_attributes = false;
1589 ldb = ldb_module_get_ctx(module);
1591 /* removed any attrs that should not be shown to the user */
1592 for (i=0; i < list_size; i++) {
1593 ldb_msg_remove_attr(msg, list[i].attr);
1596 for (a=0; a < list_replace_size; a++) {
1597 if (check_keep_control_for_attribute(controls_flags,
1598 list_replace[a].attr)) {
1599 continue;
1602 /* construct the new attribute, using either a supplied
1603 constructor or a simple copy */
1604 constructed_attributes = true;
1605 if (list_replace[a].constructor != NULL) {
1606 if (list_replace[a].constructor(module, msg, scope, parent, ares) != LDB_SUCCESS) {
1607 goto failed;
1609 } else if (ldb_msg_copy_attr(msg,
1610 list_replace[a].replace,
1611 list_replace[a].attr) != LDB_SUCCESS) {
1612 goto failed;
1616 /* Deletion of the search helper attributes are needed if:
1617 * - we generated constructed attributes and
1618 * - we aren't requesting all attributes
1620 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1621 for (i=0; i < list_replace_size; i++) {
1622 /* remove the added search helper attributes, unless
1623 * they were asked for by the user */
1624 if (list_replace[i].replace != NULL &&
1625 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1626 ldb_msg_remove_attr(msg, list_replace[i].replace);
1628 if (list_replace[i].extra_attrs != NULL) {
1629 unsigned int j;
1630 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1631 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1632 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1639 return 0;
1641 failed:
1642 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1643 "operational_search_post_process failed for attribute '%s' - %s",
1644 list_replace[a].attr, ldb_errstring(ldb));
1645 return -1;
1649 hook search operations
1652 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1654 struct operational_context *ac;
1655 int ret;
1657 ac = talloc_get_type_abort(req->context, struct operational_context);
1659 if (!ares) {
1660 return ldb_module_done(ac->req, NULL, NULL,
1661 LDB_ERR_OPERATIONS_ERROR);
1663 if (ares->error != LDB_SUCCESS) {
1664 return ldb_module_done(ac->req, ares->controls,
1665 ares->response, ares->error);
1668 switch (ares->type) {
1669 case LDB_REPLY_ENTRY:
1670 /* for each record returned post-process to add any derived
1671 attributes that have been asked for */
1672 ret = operational_search_post_process(ac->module,
1673 ares->message,
1674 ac->scope,
1675 ac->attrs,
1676 req->op.search.attrs,
1677 ac->controls_flags,
1678 ac->list_operations,
1679 ac->list_operations_size,
1680 ac->attrs_to_replace,
1681 ac->attrs_to_replace_size,
1682 req,
1683 ares);
1684 if (ret != 0) {
1685 return ldb_module_done(ac->req, NULL, NULL,
1686 LDB_ERR_OPERATIONS_ERROR);
1688 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1690 case LDB_REPLY_REFERRAL:
1691 return ldb_module_send_referral(ac->req, ares->referral);
1693 case LDB_REPLY_DONE:
1695 return ldb_module_done(ac->req, ares->controls,
1696 ares->response, LDB_SUCCESS);
1699 talloc_free(ares);
1700 return LDB_SUCCESS;
1703 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1704 const char* const* attrs,
1705 const char* const* searched_attrs,
1706 struct op_controls_flags* controls_flags)
1708 int idx = 0;
1709 int i;
1710 struct op_attributes_operations *list = talloc_zero_array(ctx,
1711 struct op_attributes_operations,
1712 ARRAY_SIZE(operational_remove) + 1);
1714 if (list == NULL) {
1715 return NULL;
1718 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1719 switch (operational_remove[i].op) {
1720 case OPERATIONAL_REMOVE_UNASKED:
1721 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1722 continue;
1724 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1725 continue;
1727 list[idx].attr = operational_remove[i].attr;
1728 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1729 idx++;
1730 break;
1732 case OPERATIONAL_REMOVE_ALWAYS:
1733 list[idx].attr = operational_remove[i].attr;
1734 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1735 idx++;
1736 break;
1738 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1739 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1740 list[idx].attr = operational_remove[i].attr;
1741 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1742 idx++;
1744 break;
1746 case OPERATIONAL_SD_FLAGS:
1747 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1748 continue;
1750 if (controls_flags->sd) {
1751 if (attrs == NULL) {
1752 continue;
1754 if (attrs[0] == NULL) {
1755 continue;
1757 if (ldb_attr_in_list(attrs, "*")) {
1758 continue;
1761 list[idx].attr = operational_remove[i].attr;
1762 list[idx].op = OPERATIONAL_SD_FLAGS;
1763 idx++;
1764 break;
1768 return list;
1771 struct operational_present_ctx {
1772 const char *attr;
1773 bool found_operational;
1777 callback to determine if an operational attribute (needing
1778 replacement) is in use at all
1780 static int operational_present(struct ldb_parse_tree *tree, void *private_context)
1782 struct operational_present_ctx *ctx = private_context;
1783 switch (tree->operation) {
1784 case LDB_OP_EQUALITY:
1785 if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
1786 ctx->found_operational = true;
1788 break;
1789 case LDB_OP_GREATER:
1790 case LDB_OP_LESS:
1791 case LDB_OP_APPROX:
1792 if (ldb_attr_cmp(tree->u.comparison.attr, ctx->attr) == 0) {
1793 ctx->found_operational = true;
1795 break;
1796 case LDB_OP_SUBSTRING:
1797 if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
1798 ctx->found_operational = true;
1800 break;
1801 case LDB_OP_PRESENT:
1802 if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
1803 ctx->found_operational = true;
1805 break;
1806 case LDB_OP_EXTENDED:
1807 if (tree->u.extended.attr &&
1808 ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
1809 ctx->found_operational = true;
1811 break;
1812 default:
1813 break;
1815 return LDB_SUCCESS;
1819 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1821 struct ldb_context *ldb;
1822 struct operational_context *ac;
1823 struct ldb_request *down_req;
1824 const char **search_attrs = NULL;
1825 struct operational_present_ctx ctx;
1826 unsigned int i, a;
1827 int ret;
1829 /* There are no operational attributes on special DNs */
1830 if (ldb_dn_is_special(req->op.search.base)) {
1831 return ldb_next_request(module, req);
1834 ldb = ldb_module_get_ctx(module);
1836 ac = talloc(req, struct operational_context);
1837 if (ac == NULL) {
1838 return ldb_oom(ldb);
1841 ac->expire_passwords_onsmartcardonlyaccounts
1842 = EXPIRE_UF_SMARTCARD_UNINIT;
1844 ac->now = 0;
1846 ac->module = module;
1847 ac->req = req;
1848 ac->scope = req->op.search.scope;
1849 ac->attrs = req->op.search.attrs;
1850 ctx.found_operational = false;
1853 * find any attributes in the parse tree that are searchable,
1854 * but are stored using a different name in the backend, so we
1855 * only duplicate the memory when needed
1857 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1858 ctx.attr = parse_tree_sub[i].attr;
1860 ldb_parse_tree_walk(req->op.search.tree,
1861 operational_present,
1862 &ctx);
1863 if (ctx.found_operational) {
1864 break;
1868 if (ctx.found_operational) {
1870 ac->tree = ldb_parse_tree_copy_shallow(ac,
1871 req->op.search.tree);
1873 if (ac->tree == NULL) {
1874 return ldb_operr(ldb);
1877 /* replace any attributes in the parse tree that are
1878 searchable, but are stored using a different name in the
1879 backend */
1880 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1881 ldb_parse_tree_attr_replace(ac->tree,
1882 parse_tree_sub[i].attr,
1883 parse_tree_sub[i].replace);
1885 } else {
1886 /* Avoid allocating a copy if we do not need to */
1887 ac->tree = req->op.search.tree;
1890 ac->controls_flags = talloc(ac, struct op_controls_flags);
1891 /* remember if the SD_FLAGS_OID was set */
1892 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1893 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1894 ac->controls_flags->bypassoperational =
1895 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1897 ac->attrs_to_replace = NULL;
1898 ac->attrs_to_replace_size = 0;
1899 /* in the list of attributes we are looking for, rename any
1900 attributes to the alias for any hidden attributes that can
1901 be fetched directly using non-hidden names.
1902 Note that order here can affect performance, e.g. we should process
1903 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1904 latter is also dependent on the PSO information) */
1905 for (a=0;ac->attrs && ac->attrs[a];a++) {
1906 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1907 continue;
1909 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1911 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1912 continue;
1915 ac->attrs_to_replace = talloc_realloc(ac,
1916 ac->attrs_to_replace,
1917 struct op_attributes_replace,
1918 ac->attrs_to_replace_size + 1);
1920 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1921 ac->attrs_to_replace_size++;
1923 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1924 unsigned int j;
1925 const char **search_attrs2;
1926 /* Only adds to the end of the list */
1927 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1928 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1929 ? search_attrs
1930 : ac->attrs,
1931 search_sub[i].extra_attrs[j]);
1932 if (search_attrs2 == NULL) {
1933 return ldb_operr(ldb);
1935 /* may be NULL, talloc_free() doesn't mind */
1936 talloc_free(search_attrs);
1937 search_attrs = search_attrs2;
1941 if (!search_sub[i].replace) {
1942 continue;
1945 if (!search_attrs) {
1946 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1947 if (search_attrs == NULL) {
1948 return ldb_operr(ldb);
1951 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1952 search_attrs[a] = search_sub[i].replace;
1955 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1956 search_attrs == NULL?req->op.search.attrs:search_attrs,
1957 ac->controls_flags);
1958 ac->list_operations_size = 0;
1959 i = 0;
1961 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1962 i++;
1964 ac->list_operations_size = i;
1965 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1966 req->op.search.base,
1967 req->op.search.scope,
1968 ac->tree,
1969 /* use new set of attrs if any */
1970 search_attrs == NULL?req->op.search.attrs:search_attrs,
1971 req->controls,
1972 ac, operational_callback,
1973 req);
1974 LDB_REQ_SET_LOCATION(down_req);
1975 if (ret != LDB_SUCCESS) {
1976 return ldb_operr(ldb);
1979 /* perform the search */
1980 return ldb_next_request(module, down_req);
1983 static int operational_init(struct ldb_module *ctx)
1985 struct operational_data *data;
1986 int ret;
1988 ret = ldb_next_init(ctx);
1990 if (ret != LDB_SUCCESS) {
1991 return ret;
1994 data = talloc_zero(ctx, struct operational_data);
1995 if (!data) {
1996 return ldb_module_oom(ctx);
1999 ldb_module_set_private(ctx, data);
2001 return LDB_SUCCESS;
2004 static const struct ldb_module_ops ldb_operational_module_ops = {
2005 .name = "operational",
2006 .search = operational_search,
2007 .init_context = operational_init
2010 int ldb_operational_module_init(const char *version)
2012 LDB_MODULE_CHECK_VERSION(version);
2013 return ldb_register_module(&ldb_operational_module_ops);