4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Andrew Tridgell 2005
6 Copyright (C) Simo Sorce 2006-2008
7 Copyright (C) Matthias Dieter Wallnöfer 2009
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 handle operational attributes
28 createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
31 for the above two, we do the search as normal, and if
32 createTimeStamp or modifyTimeStamp is asked for, then do
33 additional searches for whenCreated and whenChanged and fill in
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
45 on add we need to add both the above, for current time
47 on modify we need to change whenChanged
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
56 contains the RID of a certain group object
59 attributeTypes: in schema only
60 objectClasses: in schema only
61 matchingRules: in schema only
62 matchingRuleUse: in schema only
63 creatorsName: not supported by w2k3?
64 modifiersName: not supported by w2k3?
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "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"
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
85 struct operational_data
{
86 struct ldb_dn
*aggregate_dn
;
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)
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
;
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
)
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) {
156 = samdb_result_rid_from_sid(msg
, msg
, "objectSid", 0);
157 if (primary_group_token
== 0) {
161 return samdb_msg_add_uint(ldb
, msg
, msg
, "primaryGroupToken",
162 primary_group_token
);
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
;
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
) {
193 /* Ensure it has an objectSID too */
194 account_sid
= samdb_result_dom_sid(mem_ctx
, msg
, "objectSid");
195 if (account_sid
== NULL
) {
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
,
208 ldb_msg_find_attr_as_uint(msg
, "primaryGroupID", ~0));
209 if (!primary_group_sid
) {
213 /* only return security groups */
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
);
223 case TOKEN_GROUPS_NO_GC_ACCEPTABLE
:
225 filter
= talloc_asprintf(mem_ctx
,
226 "(&(objectClass=group)"
227 "(groupType:"LDB_OID_COMPARATOR_AND
":=%u))",
228 GROUP_TYPE_SECURITY_ENABLED
);
231 /* for RevMembGetAccountGroups, exclude built-in 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
);
245 primary_group_string
= dom_sid_string(mem_ctx
, primary_group_sid
);
246 if (!primary_group_string
) {
250 primary_group_dn
= talloc_asprintf(mem_ctx
, "<SID=%s>", primary_group_string
);
251 if (!primary_group_dn
) {
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
) {
262 account_sid_dn
= talloc_asprintf(mem_ctx
, "<SID=%s>", account_sid_string
);
263 if (!account_sid_dn
) {
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 */
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
,
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
,
294 return LDB_ERR_OPERATIONS_ERROR
;
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
);
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
);
334 talloc_free(tmp_ctx
);
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
350 return construct_generic_token_groups(module
, msg
, scope
, parent
,
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
363 return construct_generic_token_groups(module
, msg
, scope
, parent
,
364 "tokenGroupsNoGCAcceptable",
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
;
389 struct ldb_dn
*parent_dn
;
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
) {
400 instanceType
= ldb_msg_find_attr_as_uint(res
->msgs
[0],
403 if (instanceType
& INSTANCE_TYPE_IS_NC_HEAD
) {
404 DEBUG(4,(__location__
": Object %s is NC\n",
405 ldb_dn_get_linearized(msg
->dn
)));
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
);
426 } else if (ret
!= LDB_SUCCESS
) {
427 talloc_free(parent_dn
);
430 talloc_free(parent_dn
);
432 parent_guid
= ldb_msg_find_ldb_val(parent_res
->msgs
[0], "objectGUID");
434 talloc_free(parent_res
);
435 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
438 v
= data_blob_dup_talloc(parent_res
, *parent_guid
);
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
);
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 */
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
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
);
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 */
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
);
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
;
525 const struct ldb_val
*val
;
527 ldb
= ldb_module_get_ctx(module
);
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
);
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
);
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");
550 ldb_msg_add_string(msg
, "msDS-isRODC", "TRUE");
555 static int construct_msds_isrodc_with_server_dn(struct ldb_module
*module
,
556 struct ldb_message
*msg
,
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
;
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
)));
579 } else if (ret
!= LDB_SUCCESS
) {
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
)));
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
)
597 struct ldb_dn
*server_dn
;
599 ret
= dsdb_module_reference_dn(module
, msg
, msg
->dn
, "serverReferenceBL",
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
)));
606 } else if (ret
!= LDB_SUCCESS
) {
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
;
624 object_class
= ldb_msg_find_element(msg
, "objectClass");
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
)));
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
);
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
)
676 enum ndr_err_code ndr_err
;
677 const struct ldb_val
*omd_value
;
678 struct replPropertyMetaDataBlob
*omd
;
681 omd_value
= ldb_msg_find_ldb_val(msg
, "replPropertyMetaData");
683 /* We can't make up a key version number without meta data */
687 omd
= talloc(msg
, struct replPropertyMetaDataBlob
);
689 ldb_module_oom(module
);
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
)));
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
),
711 "msDS-KeyVersionNumber",
712 omd
->ctr
.ctr1
.array
[i
].version
);
713 if (ret
!= 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
)
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
));
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
;
772 const char *base_attrs
[] = { "msDS-ExpirePasswordsOnSmartCardOnlyAccounts",
774 struct ldb_message
*base_msg
;
777 int ldb_ret
= dsdb_search_one(ldb
, ac
,
779 ldb_get_default_basedn(ldb
),
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
792 attr_in_ldb
= ldb_msg_find_attr_as_bool(base_msg
,
793 "msDS-ExpirePasswordsOnSmartCardOnlyAccounts",
795 talloc_free(base_msg
);
797 ac
->expire_passwords_onsmartcardonlyaccounts
798 = EXPIRE_UF_SMARTCARD
;
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
;
821 userAccountControl
= ldb_msg_find_attr_as_uint(msg
,
822 "userAccountControl",
824 if (userAccountControl
& _UF_NO_EXPIRY_ACCOUNTS
) {
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
) {
837 pwdLastSet
= ldb_msg_find_attr_as_int64(msg
, "pwdLastSet", 0);
838 if (pwdLastSet
== 0) {
842 if (pwdLastSet
<= -1) {
844 * This can't really happen...
849 if (pwdLastSet
>= INT64_MAX
) {
851 * Somethings wrong with the clock...
857 * Note that maxPwdAge is a stored as negative value.
859 * Possible values are in the range of:
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...
874 if (maxPwdAge
== INT64_MIN
) {
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
) {
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
)
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
));
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",
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
;
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
);
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
)));
955 if (ldb_dn_compare(nc_root
, ldb_get_default_basedn(ldb
)) != 0) {
956 /* Only calculate this on our default NC */
961 /* Get the current or simulated time */
962 bool time_ok
= dsdb_gmsa_current_time(ldb
, &ac
->now
);
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
,
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
;
988 = get_msds_user_password_expiry_time_computed(module
,
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
,
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
;
1019 ret
= dsdb_find_nc_root(ldb
, msg
, msg
->dn
, &nc_root
);
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
));
1028 if (ldb_dn_compare(nc_root
, ldb_get_default_basedn(ldb
)) != 0) {
1029 /* Only calculate this on our default NC */
1033 password_expiry_time
1034 = get_msds_user_password_expiry_time_computed(module
,
1039 return samdb_msg_add_int64(ldb
,
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
;
1055 functional_level
= dsdb_functional_level(ldb
);
1056 if (functional_level
< DS_DOMAIN_FUNCTION_2008
) {
1060 /* msDS-ResultantPSO is only supported for user objects */
1061 if (!ldb_match_msg_objectclass(msg
, "user")) {
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
)) {
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
) {
1077 /* ...or if it's a special KRBTGT account for an RODC KDC */
1078 if (ldb_msg_find_ldb_val(msg
, "msDS-SecondaryKrbTgtNumber") != NULL
) {
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
};
1093 struct ldb_dn
*psc_dn
= NULL
;
1094 struct ldb_result
*res
= NULL
;
1095 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
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");
1124 if (ret
!= LDB_SUCCESS
) {
1128 *pso_count
= res
->count
;
1130 talloc_free(psc_dn
);
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
)
1145 prec1
= ldb_msg_find_attr_as_uint(*m1
, "msDS-PasswordSettingsPrecedence",
1147 prec2
= ldb_msg_find_attr_as_uint(*m2
, "msDS-PasswordSettingsPrecedence",
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
);
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
)
1171 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
1172 char *sid_filter
= NULL
;
1173 struct ldb_dn
*psc_dn
= NULL
;
1175 const char *attrs
[] = {
1176 "msDS-PasswordSettingsPrecedence",
1178 "msDS-LockoutDuration",
1179 "msDS-MaximumPasswordAge",
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(
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))",
1216 talloc_free(sid_filter
);
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
;
1232 /* find any PSOs that apply to the SIDs specified */
1233 ret
= pso_search_by_sids(module
, mem_ctx
, parent
, sid_array
, num_sids
,
1235 if (ret
!= LDB_SUCCESS
) {
1236 DBG_ERR("Error %d retrieving PSO for SID(s)\n", 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];
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
)
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
;
1265 struct ldb_message_element
*el
= NULL
;
1266 TALLOC_CTX
*tmp_ctx
= NULL
;
1268 struct ldb_result
*res
= NULL
;
1269 static const char *attrs
[] = {
1270 "msDS-LockoutDuration",
1271 "msDS-MaximumPasswordAge",
1277 /* first, check msDS-ResultantPSO is supported for this object */
1278 pso_supported
= pso_is_supported(ldb
, user_msg
);
1280 if (!pso_supported
) {
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
,
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
);
1305 if (res
->count
== 1) {
1306 *pso_msg
= res
->msgs
[0];
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,
1327 if (ret
!= LDB_SUCCESS
) {
1328 talloc_free(tmp_ctx
);
1332 if (best_pso
!= NULL
) {
1333 *pso_msg
= best_pso
;
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
);
1352 if (pso_count
== 0) {
1353 talloc_free(tmp_ctx
);
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
);
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
);
1376 *pso_msg
= best_pso
;
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
;
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
));
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 */
1413 struct op_controls_flags
{
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 ) {
1426 a list of attribute names that should be substituted in the parse
1427 tree before the search is done
1429 static const struct {
1431 const char *replace
;
1432 } parse_tree_sub
[] = {
1433 { "createTimeStamp", "whenCreated" },
1434 { "modifyTimeStamp", "whenChanged" }
1438 struct op_attributes_replace
{
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", \
1450 "msDS-SecondaryKrbTgtNumber", \
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, \
1461 static const char *objectSid_attr
[] =
1468 static const char *objectCategory_attr
[] =
1475 static const char *user_account_control_computed_attrs
[] =
1479 PSO_ATTR_DEPENDENCIES
,
1484 static const char *user_password_expiry_time_computed_attrs
[] =
1487 PSO_ATTR_DEPENDENCIES
,
1491 static const char *resultant_pso_computed_attrs
[] =
1493 RESULTANT_PSO_COMPUTED_ATTRS
,
1497 static const char *managed_password_computed_attrs
[] = {
1498 "msDS-GroupMSAMembership",
1499 "msDS-ManagedPasswordId",
1500 "msDS-ManagedPasswordInterval",
1501 "msDS-ManagedPasswordPreviousId",
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",
1533 managed_password_computed_attrs
,
1534 constructed_msds_managed_password
},
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
{
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
)) {
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
) {
1609 } else if (ldb_msg_copy_attr(msg
,
1610 list_replace
[a
].replace
,
1611 list_replace
[a
].attr
) != LDB_SUCCESS
) {
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
) {
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
]);
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
));
1649 hook search operations
1652 static int operational_callback(struct ldb_request
*req
, struct ldb_reply
*ares
)
1654 struct operational_context
*ac
;
1657 ac
= talloc_get_type_abort(req
->context
, struct operational_context
);
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
,
1676 req
->op
.search
.attrs
,
1678 ac
->list_operations
,
1679 ac
->list_operations_size
,
1680 ac
->attrs_to_replace
,
1681 ac
->attrs_to_replace_size
,
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
);
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
)
1710 struct op_attributes_operations
*list
= talloc_zero_array(ctx
,
1711 struct op_attributes_operations
,
1712 ARRAY_SIZE(operational_remove
) + 1);
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
)) {
1724 if (ldb_attr_in_list(searched_attrs
, operational_remove
[i
].attr
)) {
1727 list
[idx
].attr
= operational_remove
[i
].attr
;
1728 list
[idx
].op
= OPERATIONAL_REMOVE_UNASKED
;
1732 case OPERATIONAL_REMOVE_ALWAYS
:
1733 list
[idx
].attr
= operational_remove
[i
].attr
;
1734 list
[idx
].op
= OPERATIONAL_REMOVE_ALWAYS
;
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
;
1746 case OPERATIONAL_SD_FLAGS
:
1747 if (ldb_attr_in_list(attrs
, operational_remove
[i
].attr
)) {
1750 if (controls_flags
->sd
) {
1751 if (attrs
== NULL
) {
1754 if (attrs
[0] == NULL
) {
1757 if (ldb_attr_in_list(attrs
, "*")) {
1761 list
[idx
].attr
= operational_remove
[i
].attr
;
1762 list
[idx
].op
= OPERATIONAL_SD_FLAGS
;
1771 struct operational_present_ctx
{
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;
1789 case LDB_OP_GREATER
:
1792 if (ldb_attr_cmp(tree
->u
.comparison
.attr
, ctx
->attr
) == 0) {
1793 ctx
->found_operational
= true;
1796 case LDB_OP_SUBSTRING
:
1797 if (ldb_attr_cmp(tree
->u
.substring
.attr
, ctx
->attr
) == 0) {
1798 ctx
->found_operational
= true;
1801 case LDB_OP_PRESENT
:
1802 if (ldb_attr_cmp(tree
->u
.present
.attr
, ctx
->attr
) == 0) {
1803 ctx
->found_operational
= true;
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;
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
;
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
);
1838 return ldb_oom(ldb
);
1841 ac
->expire_passwords_onsmartcardonlyaccounts
1842 = EXPIRE_UF_SMARTCARD_UNINIT
;
1846 ac
->module
= module
;
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
,
1863 if (ctx
.found_operational
) {
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
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
);
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
])) {
1909 for (i
=0;i
<ARRAY_SIZE(search_sub
);i
++) {
1911 if (ldb_attr_cmp(ac
->attrs
[a
], search_sub
[i
].attr
) != 0 ) {
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]) {
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
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
) {
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;
1961 while (ac
->list_operations
&& ac
->list_operations
[i
].attr
!= NULL
) {
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
,
1969 /* use new set of attrs if any */
1970 search_attrs
== NULL
?req
->op
.search
.attrs
:search_attrs
,
1972 ac
, operational_callback
,
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
;
1988 ret
= ldb_next_init(ctx
);
1990 if (ret
!= LDB_SUCCESS
) {
1994 data
= talloc_zero(ctx
, struct operational_data
);
1996 return ldb_module_oom(ctx
);
1999 ldb_module_set_private(ctx
, data
);
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
);