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 static int get_pso_for_user(struct ldb_module
*module
,
103 struct ldb_message
*user_msg
,
104 struct ldb_request
*parent
,
105 struct ldb_message
**pso_msg
);
108 construct a canonical name from a message
110 static int construct_canonical_name(struct ldb_module
*module
,
111 struct ldb_message
*msg
, enum ldb_scope scope
,
112 struct ldb_request
*parent
, struct ldb_reply
*ares
)
115 canonicalName
= ldb_dn_canonical_string(msg
, msg
->dn
);
116 if (canonicalName
== NULL
) {
117 return ldb_operr(ldb_module_get_ctx(module
));
119 return ldb_msg_add_steal_string(msg
, "canonicalName", canonicalName
);
123 construct a primary group token for groups from a message
125 static int construct_primary_group_token(struct ldb_module
*module
,
126 struct ldb_message
*msg
, enum ldb_scope scope
,
127 struct ldb_request
*parent
, struct ldb_reply
*ares
)
129 struct ldb_context
*ldb
;
130 uint32_t primary_group_token
;
132 ldb
= ldb_module_get_ctx(module
);
133 if (ldb_match_msg_objectclass(msg
, "group") == 1) {
135 = samdb_result_rid_from_sid(msg
, msg
, "objectSid", 0);
136 if (primary_group_token
== 0) {
140 return samdb_msg_add_uint(ldb
, msg
, msg
, "primaryGroupToken",
141 primary_group_token
);
148 * Returns the group SIDs for the user in the given LDB message
150 static int get_group_sids(struct ldb_context
*ldb
, TALLOC_CTX
*mem_ctx
,
151 struct ldb_message
*msg
, const char *attribute_string
,
152 enum search_type type
, struct auth_SidAttr
**groupSIDs
,
153 uint32_t *num_groupSIDs
)
155 const char *filter
= NULL
;
157 struct dom_sid
*primary_group_sid
;
158 const char *primary_group_string
;
159 const char *primary_group_dn
;
160 DATA_BLOB primary_group_blob
;
161 struct dom_sid
*account_sid
;
162 const char *account_sid_string
;
163 const char *account_sid_dn
;
164 DATA_BLOB account_sid_blob
;
165 struct dom_sid
*domain_sid
;
167 /* If it's not a user, it won't have a primaryGroupID */
168 if (ldb_msg_find_element(msg
, "primaryGroupID") == NULL
) {
172 /* Ensure it has an objectSID too */
173 account_sid
= samdb_result_dom_sid(mem_ctx
, msg
, "objectSid");
174 if (account_sid
== NULL
) {
178 status
= dom_sid_split_rid(mem_ctx
, account_sid
, &domain_sid
, NULL
);
179 if (NT_STATUS_EQUAL(status
, NT_STATUS_INVALID_PARAMETER
)) {
180 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
181 } else if (!NT_STATUS_IS_OK(status
)) {
182 return LDB_ERR_OPERATIONS_ERROR
;
185 primary_group_sid
= dom_sid_add_rid(mem_ctx
,
187 ldb_msg_find_attr_as_uint(msg
, "primaryGroupID", ~0));
188 if (!primary_group_sid
) {
192 /* only return security groups */
194 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL
:
195 filter
= talloc_asprintf(mem_ctx
,
196 "(&(objectClass=group)"
197 "(groupType:"LDB_OID_COMPARATOR_AND
":=%u)"
198 "(groupType:"LDB_OID_COMPARATOR_OR
":=%u))",
199 GROUP_TYPE_SECURITY_ENABLED
,
200 GROUP_TYPE_ACCOUNT_GROUP
| GROUP_TYPE_UNIVERSAL_GROUP
);
202 case TOKEN_GROUPS_NO_GC_ACCEPTABLE
:
204 filter
= talloc_asprintf(mem_ctx
,
205 "(&(objectClass=group)"
206 "(groupType:"LDB_OID_COMPARATOR_AND
":=%u))",
207 GROUP_TYPE_SECURITY_ENABLED
);
210 /* for RevMembGetAccountGroups, exclude built-in groups */
212 filter
= talloc_asprintf(mem_ctx
,
213 "(&(objectClass=group)"
214 "(!(groupType:"LDB_OID_COMPARATOR_AND
":=%u))"
215 "(groupType:"LDB_OID_COMPARATOR_AND
":=%u))",
216 GROUP_TYPE_BUILTIN_LOCAL_GROUP
, GROUP_TYPE_SECURITY_ENABLED
);
224 primary_group_string
= dom_sid_string(mem_ctx
, primary_group_sid
);
225 if (!primary_group_string
) {
229 primary_group_dn
= talloc_asprintf(mem_ctx
, "<SID=%s>", primary_group_string
);
230 if (!primary_group_dn
) {
234 primary_group_blob
= data_blob_string_const(primary_group_dn
);
236 account_sid_string
= dom_sid_string(mem_ctx
, account_sid
);
237 if (!account_sid_string
) {
241 account_sid_dn
= talloc_asprintf(mem_ctx
, "<SID=%s>", account_sid_string
);
242 if (!account_sid_dn
) {
246 account_sid_blob
= data_blob_string_const(account_sid_dn
);
248 status
= dsdb_expand_nested_groups(ldb
, &account_sid_blob
,
249 true, /* We don't want to add the object's SID itself,
250 it's not returned in this attribute */
252 mem_ctx
, groupSIDs
, num_groupSIDs
);
254 if (!NT_STATUS_IS_OK(status
)) {
255 ldb_asprintf_errstring(ldb
, "Failed to construct %s: expanding groups of SID %s failed: %s",
256 attribute_string
, account_sid_string
,
258 return LDB_ERR_OPERATIONS_ERROR
;
261 /* Expands the primary group - this function takes in
262 * memberOf-like values, so we fake one up with the
263 * <SID=S-...> format of DN and then let it expand
264 * them, as long as they meet the filter - so only
265 * domain groups, not builtin groups
267 status
= dsdb_expand_nested_groups(ldb
, &primary_group_blob
, false, filter
,
268 mem_ctx
, groupSIDs
, num_groupSIDs
);
269 if (!NT_STATUS_IS_OK(status
)) {
270 ldb_asprintf_errstring(ldb
, "Failed to construct %s: expanding groups of SID %s failed: %s",
271 attribute_string
, account_sid_string
,
273 return LDB_ERR_OPERATIONS_ERROR
;
280 construct the token groups for SAM objects from a message
282 static int construct_generic_token_groups(struct ldb_module
*module
,
283 struct ldb_message
*msg
, enum ldb_scope scope
,
284 struct ldb_request
*parent
,
285 const char *attribute_string
,
286 enum search_type type
)
288 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
289 TALLOC_CTX
*tmp_ctx
= talloc_new(msg
);
292 struct auth_SidAttr
*groupSIDs
= NULL
;
293 uint32_t num_groupSIDs
= 0;
295 if (scope
!= LDB_SCOPE_BASE
) {
296 ldb_set_errstring(ldb
, "Cannot provide tokenGroups attribute, this is not a BASE search");
297 return LDB_ERR_OPERATIONS_ERROR
;
300 /* calculate the group SIDs for this object */
301 ret
= get_group_sids(ldb
, tmp_ctx
, msg
, attribute_string
, type
,
302 &groupSIDs
, &num_groupSIDs
);
304 if (ret
!= LDB_SUCCESS
) {
305 talloc_free(tmp_ctx
);
306 return LDB_ERR_OPERATIONS_ERROR
;
309 /* add these SIDs to the search result */
310 for (i
=0; i
< num_groupSIDs
; i
++) {
311 ret
= samdb_msg_add_dom_sid(ldb
, msg
, msg
, attribute_string
, &groupSIDs
[i
].sid
);
313 talloc_free(tmp_ctx
);
321 static int construct_token_groups(struct ldb_module
*module
,
322 struct ldb_message
*msg
, enum ldb_scope scope
,
323 struct ldb_request
*parent
, struct ldb_reply
*ares
)
326 * TODO: Add in a limiting domain when we start to support
329 return construct_generic_token_groups(module
, msg
, scope
, parent
,
334 static int construct_token_groups_no_gc(struct ldb_module
*module
,
335 struct ldb_message
*msg
, enum ldb_scope scope
,
336 struct ldb_request
*parent
, struct ldb_reply
*ares
)
339 * TODO: Add in a limiting domain when we start to support
342 return construct_generic_token_groups(module
, msg
, scope
, parent
,
343 "tokenGroupsNoGCAcceptable",
347 static int construct_global_universal_token_groups(struct ldb_module
*module
,
348 struct ldb_message
*msg
, enum ldb_scope scope
,
349 struct ldb_request
*parent
, struct ldb_reply
*ares
)
351 return construct_generic_token_groups(module
, msg
, scope
, parent
,
352 "tokenGroupsGlobalAndUniversal",
353 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL
);
356 construct the parent GUID for an entry from a message
358 static int construct_parent_guid(struct ldb_module
*module
,
359 struct ldb_message
*msg
, enum ldb_scope scope
,
360 struct ldb_request
*parent
, struct ldb_reply
*ares
)
362 struct ldb_result
*res
, *parent_res
;
363 const struct ldb_val
*parent_guid
;
364 const char *attrs
[] = { "instanceType", NULL
};
365 const char *attrs2
[] = { "objectGUID", NULL
};
366 uint32_t instanceType
;
368 struct ldb_dn
*parent_dn
;
371 /* determine if the object is NC by instance type */
372 ret
= dsdb_module_search_dn(module
, msg
, &res
, msg
->dn
, attrs
,
373 DSDB_FLAG_NEXT_MODULE
|
374 DSDB_SEARCH_SHOW_RECYCLED
, parent
);
375 if (ret
!= LDB_SUCCESS
) {
379 instanceType
= ldb_msg_find_attr_as_uint(res
->msgs
[0],
382 if (instanceType
& INSTANCE_TYPE_IS_NC_HEAD
) {
383 DEBUG(4,(__location__
": Object %s is NC\n",
384 ldb_dn_get_linearized(msg
->dn
)));
387 parent_dn
= ldb_dn_get_parent(msg
, msg
->dn
);
389 if (parent_dn
== NULL
) {
390 DEBUG(4,(__location__
": Failed to find parent for dn %s\n",
391 ldb_dn_get_linearized(msg
->dn
)));
392 return LDB_ERR_OTHER
;
394 ret
= dsdb_module_search_dn(module
, msg
, &parent_res
, parent_dn
, attrs2
,
395 DSDB_FLAG_NEXT_MODULE
|
396 DSDB_SEARCH_SHOW_RECYCLED
, parent
);
397 /* not NC, so the object should have a parent*/
398 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
399 ret
= ldb_error(ldb_module_get_ctx(module
), LDB_ERR_OPERATIONS_ERROR
,
400 talloc_asprintf(msg
, "Parent dn %s for %s does not exist",
401 ldb_dn_get_linearized(parent_dn
),
402 ldb_dn_get_linearized(msg
->dn
)));
403 talloc_free(parent_dn
);
405 } else if (ret
!= LDB_SUCCESS
) {
406 talloc_free(parent_dn
);
409 talloc_free(parent_dn
);
411 parent_guid
= ldb_msg_find_ldb_val(parent_res
->msgs
[0], "objectGUID");
413 talloc_free(parent_res
);
414 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX
;
417 v
= data_blob_dup_talloc(parent_res
, *parent_guid
);
419 talloc_free(parent_res
);
420 return ldb_oom(ldb_module_get_ctx(module
));
422 ret
= ldb_msg_add_steal_value(msg
, "parentGUID", &v
);
423 talloc_free(parent_res
);
427 static int construct_modifyTimeStamp(struct ldb_module
*module
,
428 struct ldb_message
*msg
, enum ldb_scope scope
,
429 struct ldb_request
*parent
, struct ldb_reply
*ares
)
431 struct operational_data
*data
= talloc_get_type(ldb_module_get_private(module
), struct operational_data
);
432 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
434 /* We may be being called before the init function has finished */
439 /* Try and set this value up, if possible. Don't worry if it
440 * fails, we may not have the DB set up yet.
442 if (!data
->aggregate_dn
) {
443 data
->aggregate_dn
= samdb_aggregate_schema_dn(ldb
, data
);
446 if (data
->aggregate_dn
&& ldb_dn_compare(data
->aggregate_dn
, msg
->dn
) == 0) {
448 * If we have the DN for the object with common name = Aggregate and
449 * the request is for this DN then let's do the following:
450 * 1) search the object which changedUSN correspond to the one of the loaded
452 * 2) Get the whenChanged attribute
453 * 3) Generate the modifyTimestamp out of the whenChanged attribute
455 const struct dsdb_schema
*schema
= dsdb_get_schema(ldb
, NULL
);
456 char *value
= ldb_timestring(msg
, schema
->ts_last_change
);
459 return ldb_oom(ldb_module_get_ctx(module
));
462 return ldb_msg_add_string(msg
, "modifyTimeStamp", value
);
464 return ldb_msg_copy_attr(msg
, "whenChanged", "modifyTimeStamp");
468 construct a subSchemaSubEntry
470 static int construct_subschema_subentry(struct ldb_module
*module
,
471 struct ldb_message
*msg
, enum ldb_scope scope
,
472 struct ldb_request
*parent
, struct ldb_reply
*ares
)
474 struct operational_data
*data
= talloc_get_type(ldb_module_get_private(module
), struct operational_data
);
475 char *subSchemaSubEntry
;
477 /* We may be being called before the init function has finished */
482 /* Try and set this value up, if possible. Don't worry if it
483 * fails, we may not have the DB set up yet, and it's not
484 * really vital anyway */
485 if (!data
->aggregate_dn
) {
486 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
487 data
->aggregate_dn
= samdb_aggregate_schema_dn(ldb
, data
);
490 if (data
->aggregate_dn
) {
491 subSchemaSubEntry
= ldb_dn_alloc_linearized(msg
, data
->aggregate_dn
);
492 return ldb_msg_add_steal_string(msg
, "subSchemaSubEntry", subSchemaSubEntry
);
498 static int construct_msds_isrodc_with_dn(struct ldb_module
*module
,
499 struct ldb_message
*msg
,
500 struct ldb_message_element
*object_category
)
502 struct ldb_context
*ldb
;
504 const struct ldb_val
*val
;
506 ldb
= ldb_module_get_ctx(module
);
508 DEBUG(4, (__location__
": Failed to get ldb \n"));
509 return LDB_ERR_OPERATIONS_ERROR
;
512 dn
= ldb_dn_new(msg
, ldb
, (const char *)object_category
->values
[0].data
);
514 DEBUG(4, (__location__
": Failed to create dn from %s \n",
515 (const char *)object_category
->values
[0].data
));
516 return ldb_operr(ldb
);
519 val
= ldb_dn_get_rdn_val(dn
);
521 DEBUG(4, (__location__
": Failed to get rdn val from %s \n",
522 ldb_dn_get_linearized(dn
)));
523 return ldb_operr(ldb
);
526 if (strequal((const char *)val
->data
, "NTDS-DSA")) {
527 ldb_msg_add_string(msg
, "msDS-isRODC", "FALSE");
529 ldb_msg_add_string(msg
, "msDS-isRODC", "TRUE");
534 static int construct_msds_isrodc_with_server_dn(struct ldb_module
*module
,
535 struct ldb_message
*msg
,
537 struct ldb_request
*parent
)
539 struct ldb_dn
*server_dn
;
540 const char *attr_obj_cat
[] = { "objectCategory", NULL
};
541 struct ldb_result
*res
;
542 struct ldb_message_element
*object_category
;
545 server_dn
= ldb_dn_copy(msg
, dn
);
546 if (!ldb_dn_add_child_fmt(server_dn
, "CN=NTDS Settings")) {
547 DEBUG(4, (__location__
": Failed to add child to %s \n",
548 ldb_dn_get_linearized(server_dn
)));
549 return ldb_operr(ldb_module_get_ctx(module
));
552 ret
= dsdb_module_search_dn(module
, msg
, &res
, server_dn
, attr_obj_cat
,
553 DSDB_FLAG_NEXT_MODULE
, parent
);
554 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
555 DEBUG(4,(__location__
": Can't get objectCategory for %s \n",
556 ldb_dn_get_linearized(server_dn
)));
558 } else if (ret
!= LDB_SUCCESS
) {
562 object_category
= ldb_msg_find_element(res
->msgs
[0], "objectCategory");
563 if (!object_category
) {
564 DEBUG(4,(__location__
": Can't find objectCategory for %s \n",
565 ldb_dn_get_linearized(res
->msgs
[0]->dn
)));
568 return construct_msds_isrodc_with_dn(module
, msg
, object_category
);
571 static int construct_msds_isrodc_with_computer_dn(struct ldb_module
*module
,
572 struct ldb_message
*msg
,
573 struct ldb_request
*parent
)
576 struct ldb_dn
*server_dn
;
578 ret
= dsdb_module_reference_dn(module
, msg
, msg
->dn
, "serverReferenceBL",
580 if (ret
== LDB_ERR_NO_SUCH_OBJECT
|| ret
== LDB_ERR_NO_SUCH_ATTRIBUTE
) {
581 /* it's OK if we can't find serverReferenceBL attribute */
582 DEBUG(4,(__location__
": Can't get serverReferenceBL for %s \n",
583 ldb_dn_get_linearized(msg
->dn
)));
585 } else if (ret
!= LDB_SUCCESS
) {
589 return construct_msds_isrodc_with_server_dn(module
, msg
, server_dn
, parent
);
593 construct msDS-isRODC attr
595 static int construct_msds_isrodc(struct ldb_module
*module
,
596 struct ldb_message
*msg
, enum ldb_scope scope
,
597 struct ldb_request
*parent
, struct ldb_reply
*ares
)
599 struct ldb_message_element
* object_class
;
600 struct ldb_message_element
* object_category
;
603 object_class
= ldb_msg_find_element(msg
, "objectClass");
605 DEBUG(4,(__location__
": Can't get objectClass for %s \n",
606 ldb_dn_get_linearized(msg
->dn
)));
607 return ldb_operr(ldb_module_get_ctx(module
));
610 for (i
=0; i
<object_class
->num_values
; i
++) {
611 if (strequal((const char*)object_class
->values
[i
].data
, "nTDSDSA")) {
612 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
613 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
615 object_category
= ldb_msg_find_element(msg
, "objectCategory");
616 if (!object_category
) {
617 DEBUG(4,(__location__
": Can't get objectCategory for %s \n",
618 ldb_dn_get_linearized(msg
->dn
)));
621 return construct_msds_isrodc_with_dn(module
, msg
, object_category
);
623 if (strequal((const char*)object_class
->values
[i
].data
, "server")) {
624 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
625 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
626 * substituting TN for TO.
628 return construct_msds_isrodc_with_server_dn(module
, msg
, msg
->dn
, parent
);
630 if (strequal((const char*)object_class
->values
[i
].data
, "computer")) {
631 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
632 * rule for the "TO is a server object" case, substituting TS for TO.
634 return construct_msds_isrodc_with_computer_dn(module
, msg
, parent
);
643 construct msDS-keyVersionNumber attr
645 TODO: Make this based on the 'win2k' DS heuristics bit...
648 static int construct_msds_keyversionnumber(struct ldb_module
*module
,
649 struct ldb_message
*msg
,
650 enum ldb_scope scope
,
651 struct ldb_request
*parent
,
652 struct ldb_reply
*ares
)
655 enum ndr_err_code ndr_err
;
656 const struct ldb_val
*omd_value
;
657 struct replPropertyMetaDataBlob
*omd
;
660 omd_value
= ldb_msg_find_ldb_val(msg
, "replPropertyMetaData");
662 /* We can't make up a key version number without meta data */
666 omd
= talloc(msg
, struct replPropertyMetaDataBlob
);
668 ldb_module_oom(module
);
672 ndr_err
= ndr_pull_struct_blob(omd_value
, omd
, omd
,
673 (ndr_pull_flags_fn_t
)ndr_pull_replPropertyMetaDataBlob
);
674 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
675 DEBUG(0,(__location__
": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
676 ldb_dn_get_linearized(msg
->dn
)));
677 return ldb_operr(ldb_module_get_ctx(module
));
680 if (omd
->version
!= 1) {
681 DEBUG(0,(__location__
": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
682 omd
->version
, ldb_dn_get_linearized(msg
->dn
)));
686 for (i
=0; i
<omd
->ctr
.ctr1
.count
; i
++) {
687 if (omd
->ctr
.ctr1
.array
[i
].attid
== DRSUAPI_ATTID_unicodePwd
) {
688 ret
= samdb_msg_add_uint(ldb_module_get_ctx(module
),
690 "msDS-KeyVersionNumber",
691 omd
->ctr
.ctr1
.array
[i
].version
);
692 if (ret
!= LDB_SUCCESS
) {
703 #define _UF_TRUST_ACCOUNTS ( \
704 UF_WORKSTATION_TRUST_ACCOUNT | \
705 UF_SERVER_TRUST_ACCOUNT | \
706 UF_INTERDOMAIN_TRUST_ACCOUNT \
708 #define _UF_NO_EXPIRY_ACCOUNTS ( \
709 UF_SMARTCARD_REQUIRED | \
710 UF_DONT_EXPIRE_PASSWD | \
716 * Returns the Effective-MaximumPasswordAge for a user
718 static int64_t get_user_max_pwd_age(struct ldb_module
*module
,
719 struct ldb_message
*user_msg
,
720 struct ldb_request
*parent
,
721 struct ldb_dn
*nc_root
)
724 struct ldb_message
*pso
= NULL
;
725 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
727 /* if a PSO applies to the user, use its maxPwdAge */
728 ret
= get_pso_for_user(module
, user_msg
, parent
, &pso
);
729 if (ret
!= LDB_SUCCESS
) {
731 /* log the error, but fallback to the domain default */
732 DBG_ERR("Error retrieving PSO for %s\n",
733 ldb_dn_get_linearized(user_msg
->dn
));
737 return ldb_msg_find_attr_as_int64(pso
,
738 "msDS-MaximumPasswordAge", 0);
741 /* otherwise return the default domain value */
742 return samdb_search_int64(ldb
, user_msg
, 0, nc_root
, "maxPwdAge", NULL
);
746 calculate msDS-UserPasswordExpiryTimeComputed
748 static NTTIME
get_msds_user_password_expiry_time_computed(struct ldb_module
*module
,
749 struct ldb_message
*msg
,
750 struct ldb_request
*parent
,
751 struct ldb_dn
*domain_dn
)
753 int64_t pwdLastSet
, maxPwdAge
;
754 uint32_t userAccountControl
;
757 userAccountControl
= ldb_msg_find_attr_as_uint(msg
,
758 "userAccountControl",
760 if (userAccountControl
& _UF_NO_EXPIRY_ACCOUNTS
) {
764 pwdLastSet
= ldb_msg_find_attr_as_int64(msg
, "pwdLastSet", 0);
765 if (pwdLastSet
== 0) {
769 if (pwdLastSet
<= -1) {
771 * This can't really happen...
776 if (pwdLastSet
>= INT64_MAX
) {
778 * Somethings wrong with the clock...
784 * Note that maxPwdAge is a stored as negative value.
786 * Possible values are in the range of:
788 * maxPwdAge: -864000000001
790 * maxPwdAge: -9223372036854775808 (INT64_MIN)
793 maxPwdAge
= get_user_max_pwd_age(module
, msg
, parent
, domain_dn
);
794 if (maxPwdAge
>= -864000000000) {
796 * This is not really possible...
801 if (maxPwdAge
== INT64_MIN
) {
806 * Note we already caught maxPwdAge == INT64_MIN
807 * and pwdLastSet >= INT64_MAX above.
809 * Remember maxPwdAge is a negative number,
810 * so it results in the following.
812 * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
814 * 0xFFFFFFFFFFFFFFFDULL
816 * or to put it another way, adding two numbers less than 1<<63 can't
817 * ever be more than 1<<64, therefore this result can't wrap.
819 ret
= (NTTIME
)pwdLastSet
- (NTTIME
)maxPwdAge
;
820 if (ret
>= INT64_MAX
) {
828 * Returns the Effective-LockoutDuration for a user
830 static int64_t get_user_lockout_duration(struct ldb_module
*module
,
831 struct ldb_message
*user_msg
,
832 struct ldb_request
*parent
,
833 struct ldb_dn
*nc_root
)
836 struct ldb_message
*pso
= NULL
;
837 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
839 /* if a PSO applies to the user, use its lockoutDuration */
840 ret
= get_pso_for_user(module
, user_msg
, parent
, &pso
);
841 if (ret
!= LDB_SUCCESS
) {
843 /* log the error, but fallback to the domain default */
844 DBG_ERR("Error retrieving PSO for %s\n",
845 ldb_dn_get_linearized(user_msg
->dn
));
849 return ldb_msg_find_attr_as_int64(pso
,
850 "msDS-LockoutDuration", 0);
853 /* otherwise return the default domain value */
854 return samdb_search_int64(ldb
, user_msg
, 0, nc_root
, "lockoutDuration",
859 construct msDS-User-Account-Control-Computed attr
861 static int construct_msds_user_account_control_computed(struct ldb_module
*module
,
862 struct ldb_message
*msg
, enum ldb_scope scope
,
863 struct ldb_request
*parent
, struct ldb_reply
*ares
)
865 uint32_t userAccountControl
;
866 uint32_t msDS_User_Account_Control_Computed
= 0;
867 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
869 struct ldb_dn
*nc_root
;
872 ret
= dsdb_find_nc_root(ldb
, msg
, msg
->dn
, &nc_root
);
874 ldb_asprintf_errstring(ldb
,
875 "Failed to find NC root of DN: %s: %s",
876 ldb_dn_get_linearized(msg
->dn
),
877 ldb_errstring(ldb_module_get_ctx(module
)));
880 if (ldb_dn_compare(nc_root
, ldb_get_default_basedn(ldb
)) != 0) {
881 /* Only calculate this on our default NC */
884 /* Test account expire time */
885 unix_to_nt_time(&now
, time(NULL
));
887 userAccountControl
= ldb_msg_find_attr_as_uint(msg
,
888 "userAccountControl",
890 if (!(userAccountControl
& _UF_TRUST_ACCOUNTS
)) {
892 int64_t lockoutTime
= ldb_msg_find_attr_as_int64(msg
, "lockoutTime", 0);
893 if (lockoutTime
!= 0) {
894 int64_t lockoutDuration
;
896 lockoutDuration
= get_user_lockout_duration(module
, msg
,
900 /* zero locks out until the administrator intervenes */
901 if (lockoutDuration
>= 0) {
902 msDS_User_Account_Control_Computed
|= UF_LOCKOUT
;
903 } else if (lockoutTime
- lockoutDuration
>= now
) {
904 msDS_User_Account_Control_Computed
|= UF_LOCKOUT
;
909 if (!(userAccountControl
& _UF_NO_EXPIRY_ACCOUNTS
)) {
910 NTTIME must_change_time
911 = get_msds_user_password_expiry_time_computed(module
,
915 /* check for expired password */
916 if (must_change_time
< now
) {
917 msDS_User_Account_Control_Computed
|= UF_PASSWORD_EXPIRED
;
921 return samdb_msg_add_int64(ldb
,
923 "msDS-User-Account-Control-Computed",
924 msDS_User_Account_Control_Computed
);
928 construct msDS-UserPasswordExpiryTimeComputed
930 static int construct_msds_user_password_expiry_time_computed(struct ldb_module
*module
,
931 struct ldb_message
*msg
, enum ldb_scope scope
,
932 struct ldb_request
*parent
, struct ldb_reply
*ares
)
934 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
935 struct ldb_dn
*nc_root
;
936 int64_t password_expiry_time
;
939 ret
= dsdb_find_nc_root(ldb
, msg
, msg
->dn
, &nc_root
);
941 ldb_asprintf_errstring(ldb
,
942 "Failed to find NC root of DN: %s: %s",
943 ldb_dn_get_linearized(msg
->dn
),
948 if (ldb_dn_compare(nc_root
, ldb_get_default_basedn(ldb
)) != 0) {
949 /* Only calculate this on our default NC */
954 = get_msds_user_password_expiry_time_computed(module
, msg
,
957 return samdb_msg_add_int64(ldb
,
959 "msDS-UserPasswordExpiryTimeComputed",
960 password_expiry_time
);
964 * Checks whether the msDS-ResultantPSO attribute is supported for a given
965 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
967 static bool pso_is_supported(struct ldb_context
*ldb
, struct ldb_message
*msg
)
969 int functional_level
;
973 functional_level
= dsdb_functional_level(ldb
);
974 if (functional_level
< DS_DOMAIN_FUNCTION_2008
) {
978 /* msDS-ResultantPSO is only supported for user objects */
979 if (!ldb_match_msg_objectclass(msg
, "user")) {
983 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
984 uac
= ldb_msg_find_attr_as_uint(msg
, "userAccountControl", 0);
985 if (!(uac
& UF_NORMAL_ACCOUNT
)) {
989 /* skip it if it's the special KRBTGT default account */
990 user_rid
= samdb_result_rid_from_sid(msg
, msg
, "objectSid", 0);
991 if (user_rid
== DOMAIN_RID_KRBTGT
) {
995 /* ...or if it's a special KRBTGT account for an RODC KDC */
996 if (ldb_msg_find_ldb_val(msg
, "msDS-SecondaryKrbTgtNumber") != NULL
) {
1004 * Returns the number of PSO objects that exist in the DB
1006 static int get_pso_count(struct ldb_module
*module
, TALLOC_CTX
*mem_ctx
,
1007 struct ldb_request
*parent
, int *pso_count
)
1009 static const char * const attrs
[] = { NULL
};
1011 struct ldb_dn
*psc_dn
= NULL
;
1012 struct ldb_result
*res
= NULL
;
1013 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
1017 psc_dn
= samdb_system_container_dn(ldb
, mem_ctx
);
1018 if (psc_dn
== NULL
) {
1019 return ldb_oom(ldb
);
1021 psc_ok
= ldb_dn_add_child_fmt(psc_dn
, "CN=Password Settings Container");
1022 if (psc_ok
== false) {
1023 return ldb_oom(ldb
);
1026 /* get the number of PSO children */
1027 ret
= dsdb_module_search(module
, mem_ctx
, &res
, psc_dn
,
1028 LDB_SCOPE_ONELEVEL
, attrs
,
1029 DSDB_FLAG_NEXT_MODULE
, parent
,
1030 "(objectClass=msDS-PasswordSettings)");
1033 * Just ignore PSOs if the container doesn't exist. This is a weird
1034 * corner-case where the AD DB was created from a pre-2008 base schema,
1035 * and then the FL was manually upgraded.
1037 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
1038 DBG_NOTICE("No Password Settings Container exists\n");
1042 if (ret
!= LDB_SUCCESS
) {
1046 *pso_count
= res
->count
;
1048 talloc_free(psc_dn
);
1054 * Compares two PSO objects returned by a search, to work out the better PSO.
1055 * The PSO with the lowest precedence is better, otherwise (if the precedence
1056 * is equal) the PSO with the lower GUID wins.
1058 static int pso_compare(struct ldb_message
**m1
, struct ldb_message
**m2
)
1063 prec1
= ldb_msg_find_attr_as_uint(*m1
, "msDS-PasswordSettingsPrecedence",
1065 prec2
= ldb_msg_find_attr_as_uint(*m2
, "msDS-PasswordSettingsPrecedence",
1068 /* if precedence is equal, use the lowest GUID */
1069 if (prec1
== prec2
) {
1070 struct GUID guid1
= samdb_result_guid(*m1
, "objectGUID");
1071 struct GUID guid2
= samdb_result_guid(*m2
, "objectGUID");
1073 return ndr_guid_compare(&guid1
, &guid2
);
1075 return NUMERIC_CMP(prec1
, prec2
);
1080 * Search for PSO objects that apply to the object SIDs specified
1082 static int pso_search_by_sids(struct ldb_module
*module
, TALLOC_CTX
*mem_ctx
,
1083 struct ldb_request
*parent
,
1084 struct auth_SidAttr
*sid_array
, unsigned int num_sids
,
1085 struct ldb_result
**result
)
1089 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
1090 char *sid_filter
= NULL
;
1091 struct ldb_dn
*psc_dn
= NULL
;
1093 const char *attrs
[] = {
1094 "msDS-PasswordSettingsPrecedence",
1096 "msDS-LockoutDuration",
1097 "msDS-MaximumPasswordAge",
1101 /* build a query for PSO objects that apply to any of the SIDs given */
1102 sid_filter
= talloc_strdup(mem_ctx
, "");
1103 if (sid_filter
== NULL
) {
1104 return ldb_oom(ldb
);
1107 for (i
= 0; sid_filter
&& i
< num_sids
; i
++) {
1108 struct dom_sid_buf sid_buf
;
1110 sid_filter
= talloc_asprintf_append(
1112 "(msDS-PSOAppliesTo=<SID=%s>)",
1113 dom_sid_str_buf(&sid_array
[i
].sid
, &sid_buf
));
1114 if (sid_filter
== NULL
) {
1115 return ldb_oom(ldb
);
1119 /* only PSOs located in the Password Settings Container are valid */
1120 psc_dn
= samdb_system_container_dn(ldb
, mem_ctx
);
1121 if (psc_dn
== NULL
) {
1122 return ldb_oom(ldb
);
1124 psc_ok
= ldb_dn_add_child_fmt(psc_dn
, "CN=Password Settings Container");
1125 if (psc_ok
== false) {
1126 return ldb_oom(ldb
);
1129 ret
= dsdb_module_search(module
, mem_ctx
, result
, psc_dn
,
1130 LDB_SCOPE_ONELEVEL
, attrs
,
1131 DSDB_FLAG_NEXT_MODULE
, parent
,
1132 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1134 talloc_free(sid_filter
);
1139 * Returns the best PSO object that applies to the object SID(s) specified
1141 static int pso_find_best(struct ldb_module
*module
, TALLOC_CTX
*mem_ctx
,
1142 struct ldb_request
*parent
, struct auth_SidAttr
*sid_array
,
1143 unsigned int num_sids
, struct ldb_message
**best_pso
)
1145 struct ldb_result
*res
= NULL
;
1150 /* find any PSOs that apply to the SIDs specified */
1151 ret
= pso_search_by_sids(module
, mem_ctx
, parent
, sid_array
, num_sids
,
1153 if (ret
!= LDB_SUCCESS
) {
1154 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret
);
1158 /* sort the list so that the best PSO is first */
1159 TYPESAFE_QSORT(res
->msgs
, res
->count
, pso_compare
);
1161 if (res
->count
> 0) {
1162 *best_pso
= res
->msgs
[0];
1169 * Determines the Password Settings Object (PSO) that applies to the given user
1171 static int get_pso_for_user(struct ldb_module
*module
,
1172 struct ldb_message
*user_msg
,
1173 struct ldb_request
*parent
,
1174 struct ldb_message
**pso_msg
)
1177 struct auth_SidAttr
*groupSIDs
= NULL
;
1178 uint32_t num_groupSIDs
= 0;
1179 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
1180 struct ldb_message
*best_pso
= NULL
;
1181 struct ldb_dn
*pso_dn
= NULL
;
1183 struct ldb_message_element
*el
= NULL
;
1184 TALLOC_CTX
*tmp_ctx
= NULL
;
1186 struct ldb_result
*res
= NULL
;
1187 static const char *attrs
[] = {
1188 "msDS-LockoutDuration",
1189 "msDS-MaximumPasswordAge",
1195 /* first, check msDS-ResultantPSO is supported for this object */
1196 pso_supported
= pso_is_supported(ldb
, user_msg
);
1198 if (!pso_supported
) {
1202 tmp_ctx
= talloc_new(user_msg
);
1205 * Several different constructed attributes try to use the PSO info. If
1206 * we've already constructed the msDS-ResultantPSO for this user, we can
1207 * just re-use the result, rather than calculating it from scratch again
1209 pso_dn
= ldb_msg_find_attr_as_dn(ldb
, tmp_ctx
, user_msg
,
1210 "msDS-ResultantPSO");
1212 if (pso_dn
!= NULL
) {
1213 ret
= dsdb_module_search_dn(module
, tmp_ctx
, &res
, pso_dn
,
1214 attrs
, DSDB_FLAG_NEXT_MODULE
,
1216 if (ret
!= LDB_SUCCESS
) {
1217 DBG_ERR("Error %d retrieving PSO %s\n", ret
,
1218 ldb_dn_get_linearized(pso_dn
));
1219 talloc_free(tmp_ctx
);
1223 if (res
->count
== 1) {
1224 *pso_msg
= res
->msgs
[0];
1230 * if any PSOs apply directly to the user, they are considered first
1231 * before we check group membership PSOs
1233 el
= ldb_msg_find_element(user_msg
, "msDS-PSOApplied");
1235 if (el
!= NULL
&& el
->num_values
> 0) {
1236 struct auth_SidAttr
*user_sid
= NULL
;
1238 /* lookup the best PSO object, based on the user's SID */
1239 user_sid
= samdb_result_dom_sid_attrs(
1240 tmp_ctx
, user_msg
, "objectSid",
1241 SE_GROUP_DEFAULT_FLAGS
);
1243 ret
= pso_find_best(module
, tmp_ctx
, parent
, user_sid
, 1,
1245 if (ret
!= LDB_SUCCESS
) {
1246 talloc_free(tmp_ctx
);
1250 if (best_pso
!= NULL
) {
1251 *pso_msg
= best_pso
;
1257 * If no valid PSO applies directly to the user, then try its groups.
1258 * The group expansion is expensive, so check there are actually
1259 * PSOs in the DB first (which is a quick search). Note in the above
1260 * cases we could tell that a PSO applied to the user, based on info
1261 * already retrieved by the user search.
1263 ret
= get_pso_count(module
, tmp_ctx
, parent
, &pso_count
);
1264 if (ret
!= LDB_SUCCESS
) {
1265 DBG_ERR("Error %d determining PSOs in system\n", ret
);
1266 talloc_free(tmp_ctx
);
1270 if (pso_count
== 0) {
1271 talloc_free(tmp_ctx
);
1275 /* Work out the SIDs of any account groups the user is a member of */
1276 ret
= get_group_sids(ldb
, tmp_ctx
, user_msg
,
1277 "msDS-ResultantPSO", ACCOUNT_GROUPS
,
1278 &groupSIDs
, &num_groupSIDs
);
1279 if (ret
!= LDB_SUCCESS
) {
1280 DBG_ERR("Error %d determining group SIDs for %s\n", ret
,
1281 ldb_dn_get_linearized(user_msg
->dn
));
1282 talloc_free(tmp_ctx
);
1286 /* lookup the best PSO that applies to any of these groups */
1287 ret
= pso_find_best(module
, tmp_ctx
, parent
, groupSIDs
,
1288 num_groupSIDs
, &best_pso
);
1289 if (ret
!= LDB_SUCCESS
) {
1290 talloc_free(tmp_ctx
);
1294 *pso_msg
= best_pso
;
1299 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1300 * Settings Object (PSO) that applies to that user.
1302 static int construct_resultant_pso(struct ldb_module
*module
,
1303 struct ldb_message
*msg
,
1304 enum ldb_scope scope
,
1305 struct ldb_request
*parent
,
1306 struct ldb_reply
*ares
)
1308 struct ldb_message
*pso
= NULL
;
1311 /* work out the PSO (if any) that applies to this user */
1312 ret
= get_pso_for_user(module
, msg
, parent
, &pso
);
1313 if (ret
!= LDB_SUCCESS
) {
1314 DBG_ERR("Couldn't determine PSO for %s\n",
1315 ldb_dn_get_linearized(msg
->dn
));
1320 DBG_INFO("%s is resultant PSO for user %s\n",
1321 ldb_dn_get_linearized(pso
->dn
),
1322 ldb_dn_get_linearized(msg
->dn
));
1323 return ldb_msg_add_string(msg
, "msDS-ResultantPSO",
1324 ldb_dn_get_linearized(pso
->dn
));
1327 /* no PSO applies to this user */
1331 struct op_controls_flags
{
1333 bool bypassoperational
;
1336 static bool check_keep_control_for_attribute(struct op_controls_flags
* controls_flags
, const char* attr
) {
1337 if (controls_flags
->bypassoperational
&& ldb_attr_cmp(attr
, "msDS-KeyVersionNumber") == 0 ) {
1344 a list of attribute names that should be substituted in the parse
1345 tree before the search is done
1347 static const struct {
1349 const char *replace
;
1350 } parse_tree_sub
[] = {
1351 { "createTimeStamp", "whenCreated" },
1352 { "modifyTimeStamp", "whenChanged" }
1356 struct op_attributes_replace
{
1358 const char *replace
;
1359 const char * const *extra_attrs
;
1360 int (*constructor
)(struct ldb_module
*, struct ldb_message
*, enum ldb_scope
, struct ldb_request
*, struct ldb_reply
*);
1363 /* the 'extra_attrs' required for msDS-ResultantPSO */
1364 #define RESULTANT_PSO_COMPUTED_ATTRS \
1365 "msDS-PSOApplied", \
1366 "userAccountControl", \
1368 "msDS-SecondaryKrbTgtNumber", \
1372 * any other constructed attributes that want to work out the PSO also need to
1373 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1375 #define PSO_ATTR_DEPENDENCIES \
1376 RESULTANT_PSO_COMPUTED_ATTRS, \
1379 static const char *objectSid_attr
[] =
1386 static const char *objectCategory_attr
[] =
1393 static const char *user_account_control_computed_attrs
[] =
1397 PSO_ATTR_DEPENDENCIES
,
1402 static const char *user_password_expiry_time_computed_attrs
[] =
1405 PSO_ATTR_DEPENDENCIES
,
1409 static const char *resultant_pso_computed_attrs
[] =
1411 RESULTANT_PSO_COMPUTED_ATTRS
,
1415 static const char *managed_password_computed_attrs
[] = {
1416 "msDS-GroupMSAMembership",
1417 "msDS-ManagedPasswordId",
1418 "msDS-ManagedPasswordInterval",
1419 "msDS-ManagedPasswordPreviousId",
1427 a list of attribute names that are hidden, but can be searched for
1428 using another (non-hidden) name to produce the correct result
1430 static const struct op_attributes_replace search_sub
[] = {
1431 { "createTimeStamp", "whenCreated", NULL
, NULL
},
1432 { "modifyTimeStamp", "whenChanged", NULL
, construct_modifyTimeStamp
},
1433 { "structuralObjectClass", "objectClass", NULL
, NULL
},
1434 { "canonicalName", NULL
, NULL
, construct_canonical_name
},
1435 { "primaryGroupToken", "objectClass", objectSid_attr
, construct_primary_group_token
},
1436 { "tokenGroups", "primaryGroupID", objectSid_attr
, construct_token_groups
},
1437 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr
, construct_token_groups_no_gc
},
1438 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr
, construct_global_universal_token_groups
},
1439 { "parentGUID", "objectGUID", NULL
, construct_parent_guid
},
1440 { "subSchemaSubEntry", NULL
, NULL
, construct_subschema_subentry
},
1441 { "msDS-isRODC", "objectClass", objectCategory_attr
, construct_msds_isrodc
},
1442 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL
, construct_msds_keyversionnumber
},
1443 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs
,
1444 construct_msds_user_account_control_computed
},
1445 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs
,
1446 construct_msds_user_password_expiry_time_computed
},
1447 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs
,
1448 construct_resultant_pso
},
1449 {"msDS-ManagedPassword",
1451 managed_password_computed_attrs
,
1452 constructed_msds_managed_password
},
1457 OPERATIONAL_REMOVE_ALWAYS
, /* remove always */
1458 OPERATIONAL_REMOVE_UNASKED
,/* remove if not requested */
1459 OPERATIONAL_SD_FLAGS
, /* show if SD_FLAGS_OID set, or asked for */
1460 OPERATIONAL_REMOVE_UNLESS_CONTROL
/* remove always unless an ad hoc control has been specified */
1464 a list of attributes that may need to be removed from the
1465 underlying db return
1467 Some of these are attributes that were once stored, but are now calculated
1469 struct op_attributes_operations
{
1474 static const struct op_attributes_operations operational_remove
[] = {
1475 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS
},
1476 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL
},
1477 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS
},
1478 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED
},
1479 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1480 { DSDB_SECRET_ATTRIBUTES_EX(_SEP
), OPERATIONAL_REMOVE_UNASKED
}
1485 post process a search result record. For any search_sub[] attributes that were
1486 asked for, we need to call the appropriate copy routine to copy the result
1487 into the message, then remove any attributes that we added to the search but
1488 were not asked for by the user
1490 static int operational_search_post_process(struct ldb_module
*module
,
1491 struct ldb_message
*msg
,
1492 enum ldb_scope scope
,
1493 const char * const *attrs_from_user
,
1494 const char * const *attrs_searched_for
,
1495 struct op_controls_flags
* controls_flags
,
1496 struct op_attributes_operations
*list
,
1497 unsigned int list_size
,
1498 struct op_attributes_replace
*list_replace
,
1499 unsigned int list_replace_size
,
1500 struct ldb_request
*parent
,
1501 struct ldb_reply
*ares
)
1503 struct ldb_context
*ldb
;
1504 unsigned int i
, a
= 0;
1505 bool constructed_attributes
= false;
1507 ldb
= ldb_module_get_ctx(module
);
1509 /* removed any attrs that should not be shown to the user */
1510 for (i
=0; i
< list_size
; i
++) {
1511 ldb_msg_remove_attr(msg
, list
[i
].attr
);
1514 for (a
=0; a
< list_replace_size
; a
++) {
1515 if (check_keep_control_for_attribute(controls_flags
,
1516 list_replace
[a
].attr
)) {
1520 /* construct the new attribute, using either a supplied
1521 constructor or a simple copy */
1522 constructed_attributes
= true;
1523 if (list_replace
[a
].constructor
!= NULL
) {
1524 if (list_replace
[a
].constructor(module
, msg
, scope
, parent
, ares
) != LDB_SUCCESS
) {
1527 } else if (ldb_msg_copy_attr(msg
,
1528 list_replace
[a
].replace
,
1529 list_replace
[a
].attr
) != LDB_SUCCESS
) {
1534 /* Deletion of the search helper attributes are needed if:
1535 * - we generated constructed attributes and
1536 * - we aren't requesting all attributes
1538 if ((constructed_attributes
) && (!ldb_attr_in_list(attrs_from_user
, "*"))) {
1539 for (i
=0; i
< list_replace_size
; i
++) {
1540 /* remove the added search helper attributes, unless
1541 * they were asked for by the user */
1542 if (list_replace
[i
].replace
!= NULL
&&
1543 !ldb_attr_in_list(attrs_from_user
, list_replace
[i
].replace
)) {
1544 ldb_msg_remove_attr(msg
, list_replace
[i
].replace
);
1546 if (list_replace
[i
].extra_attrs
!= NULL
) {
1548 for (j
=0; list_replace
[i
].extra_attrs
[j
]; j
++) {
1549 if (!ldb_attr_in_list(attrs_from_user
, list_replace
[i
].extra_attrs
[j
])) {
1550 ldb_msg_remove_attr(msg
, list_replace
[i
].extra_attrs
[j
]);
1560 ldb_debug_set(ldb
, LDB_DEBUG_WARNING
,
1561 "operational_search_post_process failed for attribute '%s' - %s",
1562 list_replace
[a
].attr
, ldb_errstring(ldb
));
1567 hook search operations
1570 struct operational_context
{
1571 struct ldb_module
*module
;
1572 struct ldb_request
*req
;
1573 enum ldb_scope scope
;
1574 const char * const *attrs
;
1575 struct ldb_parse_tree
*tree
;
1576 struct op_controls_flags
* controls_flags
;
1577 struct op_attributes_operations
*list_operations
;
1578 unsigned int list_operations_size
;
1579 struct op_attributes_replace
*attrs_to_replace
;
1580 unsigned int attrs_to_replace_size
;
1583 static int operational_callback(struct ldb_request
*req
, struct ldb_reply
*ares
)
1585 struct operational_context
*ac
;
1588 ac
= talloc_get_type(req
->context
, struct operational_context
);
1591 return ldb_module_done(ac
->req
, NULL
, NULL
,
1592 LDB_ERR_OPERATIONS_ERROR
);
1594 if (ares
->error
!= LDB_SUCCESS
) {
1595 return ldb_module_done(ac
->req
, ares
->controls
,
1596 ares
->response
, ares
->error
);
1599 switch (ares
->type
) {
1600 case LDB_REPLY_ENTRY
:
1601 /* for each record returned post-process to add any derived
1602 attributes that have been asked for */
1603 ret
= operational_search_post_process(ac
->module
,
1607 req
->op
.search
.attrs
,
1609 ac
->list_operations
,
1610 ac
->list_operations_size
,
1611 ac
->attrs_to_replace
,
1612 ac
->attrs_to_replace_size
,
1616 return ldb_module_done(ac
->req
, NULL
, NULL
,
1617 LDB_ERR_OPERATIONS_ERROR
);
1619 return ldb_module_send_entry(ac
->req
, ares
->message
, ares
->controls
);
1621 case LDB_REPLY_REFERRAL
:
1622 return ldb_module_send_referral(ac
->req
, ares
->referral
);
1624 case LDB_REPLY_DONE
:
1626 return ldb_module_done(ac
->req
, ares
->controls
,
1627 ares
->response
, LDB_SUCCESS
);
1634 static struct op_attributes_operations
* operation_get_op_list(TALLOC_CTX
*ctx
,
1635 const char* const* attrs
,
1636 const char* const* searched_attrs
,
1637 struct op_controls_flags
* controls_flags
)
1641 struct op_attributes_operations
*list
= talloc_zero_array(ctx
,
1642 struct op_attributes_operations
,
1643 ARRAY_SIZE(operational_remove
) + 1);
1649 for (i
=0; i
<ARRAY_SIZE(operational_remove
); i
++) {
1650 switch (operational_remove
[i
].op
) {
1651 case OPERATIONAL_REMOVE_UNASKED
:
1652 if (ldb_attr_in_list(attrs
, operational_remove
[i
].attr
)) {
1655 if (ldb_attr_in_list(searched_attrs
, operational_remove
[i
].attr
)) {
1658 list
[idx
].attr
= operational_remove
[i
].attr
;
1659 list
[idx
].op
= OPERATIONAL_REMOVE_UNASKED
;
1663 case OPERATIONAL_REMOVE_ALWAYS
:
1664 list
[idx
].attr
= operational_remove
[i
].attr
;
1665 list
[idx
].op
= OPERATIONAL_REMOVE_ALWAYS
;
1669 case OPERATIONAL_REMOVE_UNLESS_CONTROL
:
1670 if (!check_keep_control_for_attribute(controls_flags
, operational_remove
[i
].attr
)) {
1671 list
[idx
].attr
= operational_remove
[i
].attr
;
1672 list
[idx
].op
= OPERATIONAL_REMOVE_UNLESS_CONTROL
;
1677 case OPERATIONAL_SD_FLAGS
:
1678 if (ldb_attr_in_list(attrs
, operational_remove
[i
].attr
)) {
1681 if (controls_flags
->sd
) {
1682 if (attrs
== NULL
) {
1685 if (attrs
[0] == NULL
) {
1688 if (ldb_attr_in_list(attrs
, "*")) {
1692 list
[idx
].attr
= operational_remove
[i
].attr
;
1693 list
[idx
].op
= OPERATIONAL_SD_FLAGS
;
1702 struct operational_present_ctx
{
1704 bool found_operational
;
1708 callback to determine if an operational attribute (needing
1709 replacement) is in use at all
1711 static int operational_present(struct ldb_parse_tree
*tree
, void *private_context
)
1713 struct operational_present_ctx
*ctx
= private_context
;
1714 switch (tree
->operation
) {
1715 case LDB_OP_EQUALITY
:
1716 if (ldb_attr_cmp(tree
->u
.equality
.attr
, ctx
->attr
) == 0) {
1717 ctx
->found_operational
= true;
1720 case LDB_OP_GREATER
:
1723 if (ldb_attr_cmp(tree
->u
.comparison
.attr
, ctx
->attr
) == 0) {
1724 ctx
->found_operational
= true;
1727 case LDB_OP_SUBSTRING
:
1728 if (ldb_attr_cmp(tree
->u
.substring
.attr
, ctx
->attr
) == 0) {
1729 ctx
->found_operational
= true;
1732 case LDB_OP_PRESENT
:
1733 if (ldb_attr_cmp(tree
->u
.present
.attr
, ctx
->attr
) == 0) {
1734 ctx
->found_operational
= true;
1737 case LDB_OP_EXTENDED
:
1738 if (tree
->u
.extended
.attr
&&
1739 ldb_attr_cmp(tree
->u
.extended
.attr
, ctx
->attr
) == 0) {
1740 ctx
->found_operational
= true;
1750 static int operational_search(struct ldb_module
*module
, struct ldb_request
*req
)
1752 struct ldb_context
*ldb
;
1753 struct operational_context
*ac
;
1754 struct ldb_request
*down_req
;
1755 const char **search_attrs
= NULL
;
1756 struct operational_present_ctx ctx
;
1760 /* There are no operational attributes on special DNs */
1761 if (ldb_dn_is_special(req
->op
.search
.base
)) {
1762 return ldb_next_request(module
, req
);
1765 ldb
= ldb_module_get_ctx(module
);
1767 ac
= talloc(req
, struct operational_context
);
1769 return ldb_oom(ldb
);
1772 ac
->module
= module
;
1774 ac
->scope
= req
->op
.search
.scope
;
1775 ac
->attrs
= req
->op
.search
.attrs
;
1777 ctx
.found_operational
= false;
1780 * find any attributes in the parse tree that are searchable,
1781 * but are stored using a different name in the backend, so we
1782 * only duplicate the memory when needed
1784 for (i
=0;i
<ARRAY_SIZE(parse_tree_sub
);i
++) {
1785 ctx
.attr
= parse_tree_sub
[i
].attr
;
1787 ldb_parse_tree_walk(req
->op
.search
.tree
,
1788 operational_present
,
1790 if (ctx
.found_operational
) {
1795 if (ctx
.found_operational
) {
1797 ac
->tree
= ldb_parse_tree_copy_shallow(ac
,
1798 req
->op
.search
.tree
);
1800 if (ac
->tree
== NULL
) {
1801 return ldb_operr(ldb
);
1804 /* replace any attributes in the parse tree that are
1805 searchable, but are stored using a different name in the
1807 for (i
=0;i
<ARRAY_SIZE(parse_tree_sub
);i
++) {
1808 ldb_parse_tree_attr_replace(ac
->tree
,
1809 parse_tree_sub
[i
].attr
,
1810 parse_tree_sub
[i
].replace
);
1813 /* Avoid allocating a copy if we do not need to */
1814 ac
->tree
= req
->op
.search
.tree
;
1817 ac
->controls_flags
= talloc(ac
, struct op_controls_flags
);
1818 /* remember if the SD_FLAGS_OID was set */
1819 ac
->controls_flags
->sd
= (ldb_request_get_control(req
, LDB_CONTROL_SD_FLAGS_OID
) != NULL
);
1820 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1821 ac
->controls_flags
->bypassoperational
=
1822 (ldb_request_get_control(req
, LDB_CONTROL_BYPASS_OPERATIONAL_OID
) != NULL
);
1824 ac
->attrs_to_replace
= NULL
;
1825 ac
->attrs_to_replace_size
= 0;
1826 /* in the list of attributes we are looking for, rename any
1827 attributes to the alias for any hidden attributes that can
1828 be fetched directly using non-hidden names.
1829 Note that order here can affect performance, e.g. we should process
1830 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1831 latter is also dependent on the PSO information) */
1832 for (a
=0;ac
->attrs
&& ac
->attrs
[a
];a
++) {
1833 if (check_keep_control_for_attribute(ac
->controls_flags
, ac
->attrs
[a
])) {
1836 for (i
=0;i
<ARRAY_SIZE(search_sub
);i
++) {
1838 if (ldb_attr_cmp(ac
->attrs
[a
], search_sub
[i
].attr
) != 0 ) {
1842 ac
->attrs_to_replace
= talloc_realloc(ac
,
1843 ac
->attrs_to_replace
,
1844 struct op_attributes_replace
,
1845 ac
->attrs_to_replace_size
+ 1);
1847 ac
->attrs_to_replace
[ac
->attrs_to_replace_size
] = search_sub
[i
];
1848 ac
->attrs_to_replace_size
++;
1850 if (search_sub
[i
].extra_attrs
&& search_sub
[i
].extra_attrs
[0]) {
1852 const char **search_attrs2
;
1853 /* Only adds to the end of the list */
1854 for (j
= 0; search_sub
[i
].extra_attrs
[j
]; j
++) {
1855 search_attrs2
= ldb_attr_list_copy_add(req
, search_attrs
1858 search_sub
[i
].extra_attrs
[j
]);
1859 if (search_attrs2
== NULL
) {
1860 return ldb_operr(ldb
);
1862 /* may be NULL, talloc_free() doesn't mind */
1863 talloc_free(search_attrs
);
1864 search_attrs
= search_attrs2
;
1868 if (!search_sub
[i
].replace
) {
1872 if (!search_attrs
) {
1873 search_attrs
= ldb_attr_list_copy(req
, ac
->attrs
);
1874 if (search_attrs
== NULL
) {
1875 return ldb_operr(ldb
);
1878 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1879 search_attrs
[a
] = search_sub
[i
].replace
;
1882 ac
->list_operations
= operation_get_op_list(ac
, ac
->attrs
,
1883 search_attrs
== NULL
?req
->op
.search
.attrs
:search_attrs
,
1884 ac
->controls_flags
);
1885 ac
->list_operations_size
= 0;
1888 while (ac
->list_operations
&& ac
->list_operations
[i
].attr
!= NULL
) {
1891 ac
->list_operations_size
= i
;
1892 ret
= ldb_build_search_req_ex(&down_req
, ldb
, ac
,
1893 req
->op
.search
.base
,
1894 req
->op
.search
.scope
,
1896 /* use new set of attrs if any */
1897 search_attrs
== NULL
?req
->op
.search
.attrs
:search_attrs
,
1899 ac
, operational_callback
,
1901 LDB_REQ_SET_LOCATION(down_req
);
1902 if (ret
!= LDB_SUCCESS
) {
1903 return ldb_operr(ldb
);
1906 /* perform the search */
1907 return ldb_next_request(module
, down_req
);
1910 static int operational_init(struct ldb_module
*ctx
)
1912 struct operational_data
*data
;
1915 ret
= ldb_next_init(ctx
);
1917 if (ret
!= LDB_SUCCESS
) {
1921 data
= talloc_zero(ctx
, struct operational_data
);
1923 return ldb_module_oom(ctx
);
1926 ldb_module_set_private(ctx
, data
);
1931 static const struct ldb_module_ops ldb_operational_module_ops
= {
1932 .name
= "operational",
1933 .search
= operational_search
,
1934 .init_context
= operational_init
1937 int ldb_operational_module_init(const char *version
)
1939 LDB_MODULE_CHECK_VERSION(version
);
1940 return ldb_register_module(&ldb_operational_module_ops
);