dsdb: Add tokenGroupsGlobalAndUniversal, tokenGroups, tokenGroupsNoGCAcceptable
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blobf77474f51559aadf55815e2d2a702a826013f66f
1 /*
2 ldb database library
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Andrew Tridgell 2005
6 Copyright (C) Simo Sorce 2006-2008
7 Copyright (C) Matthias Dieter Wallnöfer 2009
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 handle operational attributes
28 createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
31 for the above two, we do the search as normal, and if
32 createTimeStamp or modifyTimeStamp is asked for, then do
33 additional searches for whenCreated and whenChanged and fill in
34 the resulting values
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
45 on add we need to add both the above, for current time
47 on modify we need to change whenChanged
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
56 contains the RID of a certain group object
59 attributeTypes: in schema only
60 objectClasses: in schema only
61 matchingRules: in schema only
62 matchingRuleUse: in schema only
63 creatorsName: not supported by w2k3?
64 modifiersName: not supported by w2k3?
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "param/param.h"
74 #include "dsdb/samdb/samdb.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
77 #include "libcli/security/security.h"
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
83 struct operational_data {
84 struct ldb_dn *aggregate_dn;
87 enum search_type {
88 TOKEN_GROUPS,
89 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
90 TOKEN_GROUPS_NO_GC_ACCEPTABLE
94 construct a canonical name from a message
96 static int construct_canonical_name(struct ldb_module *module,
97 struct ldb_message *msg, enum ldb_scope scope,
98 struct ldb_request *parent)
100 char *canonicalName;
101 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
102 if (canonicalName == NULL) {
103 return ldb_operr(ldb_module_get_ctx(module));
105 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
109 construct a primary group token for groups from a message
111 static int construct_primary_group_token(struct ldb_module *module,
112 struct ldb_message *msg, enum ldb_scope scope,
113 struct ldb_request *parent)
115 struct ldb_context *ldb;
116 uint32_t primary_group_token;
118 ldb = ldb_module_get_ctx(module);
119 if (ldb_match_msg_objectclass(msg, "group") == 1) {
120 primary_group_token
121 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
122 if (primary_group_token == 0) {
123 return LDB_SUCCESS;
126 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
127 primary_group_token);
128 } else {
129 return LDB_SUCCESS;
134 construct the token groups for SAM objects from a message
136 static int construct_generic_token_groups(struct ldb_module *module,
137 struct ldb_message *msg, enum ldb_scope scope,
138 struct ldb_request *parent,
139 const char *attribute_string,
140 enum search_type type)
142 struct ldb_context *ldb = ldb_module_get_ctx(module);
143 TALLOC_CTX *tmp_ctx = talloc_new(msg);
144 unsigned int i;
145 int ret;
146 const char *filter;
148 NTSTATUS status;
150 struct dom_sid *primary_group_sid;
151 const char *primary_group_string;
152 const char *primary_group_dn;
153 DATA_BLOB primary_group_blob;
155 struct dom_sid *account_sid;
156 const char *account_sid_string;
157 const char *account_sid_dn;
158 DATA_BLOB account_sid_blob;
159 struct dom_sid *groupSIDs = NULL;
160 unsigned int num_groupSIDs = 0;
162 struct dom_sid *domain_sid;
164 if (scope != LDB_SCOPE_BASE) {
165 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
166 return LDB_ERR_OPERATIONS_ERROR;
169 /* If it's not a user, it won't have a primaryGroupID */
170 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
171 talloc_free(tmp_ctx);
172 return LDB_SUCCESS;
175 /* Ensure it has an objectSID too */
176 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
177 if (account_sid == NULL) {
178 talloc_free(tmp_ctx);
179 return LDB_SUCCESS;
182 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
183 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
184 talloc_free(tmp_ctx);
185 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
186 } else if (!NT_STATUS_IS_OK(status)) {
187 talloc_free(tmp_ctx);
188 return LDB_ERR_OPERATIONS_ERROR;
191 primary_group_sid = dom_sid_add_rid(tmp_ctx,
192 domain_sid,
193 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
194 if (!primary_group_sid) {
195 talloc_free(tmp_ctx);
196 return ldb_oom(ldb);
199 /* only return security groups */
200 switch(type) {
201 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
202 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
203 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
204 break;
205 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
206 case TOKEN_GROUPS:
207 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
208 GROUP_TYPE_SECURITY_ENABLED);
209 break;
212 if (!filter) {
213 talloc_free(tmp_ctx);
214 return ldb_oom(ldb);
217 primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
218 if (!primary_group_string) {
219 talloc_free(tmp_ctx);
220 return ldb_oom(ldb);
223 primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
224 if (!primary_group_dn) {
225 talloc_free(tmp_ctx);
226 return ldb_oom(ldb);
229 primary_group_blob = data_blob_string_const(primary_group_dn);
231 account_sid_string = dom_sid_string(tmp_ctx, account_sid);
232 if (!account_sid_string) {
233 talloc_free(tmp_ctx);
234 return ldb_oom(ldb);
237 account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
238 if (!account_sid_dn) {
239 talloc_free(tmp_ctx);
240 return ldb_oom(ldb);
243 account_sid_blob = data_blob_string_const(account_sid_dn);
245 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
246 true, /* We don't want to add the object's SID itself,
247 it's not returend in this attribute */
248 filter,
249 tmp_ctx, &groupSIDs, &num_groupSIDs);
251 if (!NT_STATUS_IS_OK(status)) {
252 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
253 account_sid_string, nt_errstr(status));
254 talloc_free(tmp_ctx);
255 return LDB_ERR_OPERATIONS_ERROR;
258 /* Expands the primary group - this function takes in
259 * memberOf-like values, so we fake one up with the
260 * <SID=S-...> format of DN and then let it expand
261 * them, as long as they meet the filter - so only
262 * domain groups, not builtin groups
264 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
265 tmp_ctx, &groupSIDs, &num_groupSIDs);
266 if (!NT_STATUS_IS_OK(status)) {
267 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
268 account_sid_string, nt_errstr(status));
269 talloc_free(tmp_ctx);
270 return LDB_ERR_OPERATIONS_ERROR;
273 for (i=0; i < num_groupSIDs; i++) {
274 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
275 if (ret) {
276 talloc_free(tmp_ctx);
277 return ret;
281 return LDB_SUCCESS;
284 static int construct_token_groups(struct ldb_module *module,
285 struct ldb_message *msg, enum ldb_scope scope,
286 struct ldb_request *parent)
289 * TODO: Add in a limiting domain when we start to support
290 * trusted domains.
292 return construct_generic_token_groups(module, msg, scope, parent,
293 "tokenGroups",
294 TOKEN_GROUPS);
297 static int construct_token_groups_no_gc(struct ldb_module *module,
298 struct ldb_message *msg, enum ldb_scope scope,
299 struct ldb_request *parent)
302 * TODO: Add in a limiting domain when we start to support
303 * trusted domains.
305 return construct_generic_token_groups(module, msg, scope, parent,
306 "tokenGroupsNoGCAcceptable",
307 TOKEN_GROUPS);
310 static int construct_global_universal_token_groups(struct ldb_module *module,
311 struct ldb_message *msg, enum ldb_scope scope,
312 struct ldb_request *parent)
314 return construct_generic_token_groups(module, msg, scope, parent,
315 "tokenGroupsGlobalAndUniversal",
316 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
319 construct the parent GUID for an entry from a message
321 static int construct_parent_guid(struct ldb_module *module,
322 struct ldb_message *msg, enum ldb_scope scope,
323 struct ldb_request *parent)
325 struct ldb_result *res, *parent_res;
326 const struct ldb_val *parent_guid;
327 const char *attrs[] = { "instanceType", NULL };
328 const char *attrs2[] = { "objectGUID", NULL };
329 uint32_t instanceType;
330 int ret;
331 struct ldb_dn *parent_dn;
332 struct ldb_val v;
334 /* determine if the object is NC by instance type */
335 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
336 DSDB_FLAG_NEXT_MODULE |
337 DSDB_SEARCH_SHOW_RECYCLED, parent);
338 if (ret != LDB_SUCCESS) {
339 return ret;
342 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
343 "instanceType", 0);
344 talloc_free(res);
345 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
346 DEBUG(4,(__location__ ": Object %s is NC\n",
347 ldb_dn_get_linearized(msg->dn)));
348 return LDB_SUCCESS;
350 parent_dn = ldb_dn_get_parent(msg, msg->dn);
352 if (parent_dn == NULL) {
353 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
354 ldb_dn_get_linearized(msg->dn)));
355 return LDB_SUCCESS;
357 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
358 DSDB_FLAG_NEXT_MODULE |
359 DSDB_SEARCH_SHOW_RECYCLED, parent);
360 talloc_free(parent_dn);
362 /* not NC, so the object should have a parent*/
363 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
365 talloc_asprintf(msg, "Parent dn for %s does not exist",
366 ldb_dn_get_linearized(msg->dn)));
367 } else if (ret != LDB_SUCCESS) {
368 return ret;
371 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
372 if (!parent_guid) {
373 talloc_free(parent_res);
374 return LDB_SUCCESS;
377 v = data_blob_dup_talloc(parent_res, *parent_guid);
378 if (!v.data) {
379 talloc_free(parent_res);
380 return ldb_oom(ldb_module_get_ctx(module));
382 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
383 talloc_free(parent_res);
384 return ret;
387 static int construct_modifyTimeStamp(struct ldb_module *module,
388 struct ldb_message *msg, enum ldb_scope scope,
389 struct ldb_request *parent)
391 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
392 struct ldb_context *ldb = ldb_module_get_ctx(module);
394 /* We may be being called before the init function has finished */
395 if (!data) {
396 return LDB_SUCCESS;
399 /* Try and set this value up, if possible. Don't worry if it
400 * fails, we may not have the DB set up yet.
402 if (!data->aggregate_dn) {
403 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
406 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
408 * If we have the DN for the object with common name = Aggregate and
409 * the request is for this DN then let's do the following:
410 * 1) search the object which changedUSN correspond to the one of the loaded
411 * schema.
412 * 2) Get the whenChanged attribute
413 * 3) Generate the modifyTimestamp out of the whenChanged attribute
415 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
416 char *value = ldb_timestring(msg, schema->ts_last_change);
418 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
420 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
424 construct a subSchemaSubEntry
426 static int construct_subschema_subentry(struct ldb_module *module,
427 struct ldb_message *msg, enum ldb_scope scope,
428 struct ldb_request *parent)
430 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
431 char *subSchemaSubEntry;
433 /* We may be being called before the init function has finished */
434 if (!data) {
435 return LDB_SUCCESS;
438 /* Try and set this value up, if possible. Don't worry if it
439 * fails, we may not have the DB set up yet, and it's not
440 * really vital anyway */
441 if (!data->aggregate_dn) {
442 struct ldb_context *ldb = ldb_module_get_ctx(module);
443 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
446 if (data->aggregate_dn) {
447 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
448 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
450 return LDB_SUCCESS;
454 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
455 struct ldb_message *msg,
456 struct ldb_message_element *object_category)
458 struct ldb_context *ldb;
459 struct ldb_dn *dn;
460 const struct ldb_val *val;
462 ldb = ldb_module_get_ctx(module);
463 if (!ldb) {
464 DEBUG(4, (__location__ ": Failed to get ldb \n"));
465 return ldb_operr(ldb);
468 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
469 if (!dn) {
470 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
471 (const char *)object_category->values[0].data));
472 return ldb_operr(ldb);
475 val = ldb_dn_get_rdn_val(dn);
476 if (!val) {
477 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
478 ldb_dn_get_linearized(dn)));
479 return ldb_operr(ldb);
482 if (strequal((const char *)val->data, "NTDS-DSA")) {
483 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
484 } else {
485 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
487 return LDB_SUCCESS;
490 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
491 struct ldb_message *msg,
492 struct ldb_dn *dn,
493 struct ldb_request *parent)
495 struct ldb_dn *server_dn;
496 const char *attr_obj_cat[] = { "objectCategory", NULL };
497 struct ldb_result *res;
498 struct ldb_message_element *object_category;
499 int ret;
501 server_dn = ldb_dn_copy(msg, dn);
502 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
503 DEBUG(4, (__location__ ": Failed to add child to %s \n",
504 ldb_dn_get_linearized(server_dn)));
505 return ldb_operr(ldb_module_get_ctx(module));
508 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
509 DSDB_FLAG_NEXT_MODULE, parent);
510 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
511 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
512 ldb_dn_get_linearized(server_dn)));
513 return LDB_SUCCESS;
514 } else if (ret != LDB_SUCCESS) {
515 return ret;
518 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
519 if (!object_category) {
520 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
521 ldb_dn_get_linearized(res->msgs[0]->dn)));
522 return LDB_SUCCESS;
524 return construct_msds_isrodc_with_dn(module, msg, object_category);
527 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
528 struct ldb_message *msg,
529 struct ldb_request *parent)
531 int ret;
532 struct ldb_dn *server_dn;
534 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
535 &server_dn, parent);
536 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
537 /* it's OK if we can't find serverReferenceBL attribute */
538 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
539 ldb_dn_get_linearized(msg->dn)));
540 return LDB_SUCCESS;
541 } else if (ret != LDB_SUCCESS) {
542 return ret;
545 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
549 construct msDS-isRODC attr
551 static int construct_msds_isrodc(struct ldb_module *module,
552 struct ldb_message *msg, enum ldb_scope scope,
553 struct ldb_request *parent)
555 struct ldb_message_element * object_class;
556 struct ldb_message_element * object_category;
557 unsigned int i;
559 object_class = ldb_msg_find_element(msg, "objectClass");
560 if (!object_class) {
561 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
562 ldb_dn_get_linearized(msg->dn)));
563 return ldb_operr(ldb_module_get_ctx(module));
566 for (i=0; i<object_class->num_values; i++) {
567 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
568 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
569 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
571 object_category = ldb_msg_find_element(msg, "objectCategory");
572 if (!object_category) {
573 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
574 ldb_dn_get_linearized(msg->dn)));
575 return LDB_SUCCESS;
577 return construct_msds_isrodc_with_dn(module, msg, object_category);
579 if (strequal((const char*)object_class->values[i].data, "server")) {
580 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
581 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
582 * substituting TN for TO.
584 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
586 if (strequal((const char*)object_class->values[i].data, "computer")) {
587 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
588 * rule for the "TO is a server object" case, substituting TS for TO.
590 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
594 return LDB_SUCCESS;
599 construct msDS-keyVersionNumber attr
601 TODO: Make this based on the 'win2k' DS huristics bit...
604 static int construct_msds_keyversionnumber(struct ldb_module *module,
605 struct ldb_message *msg,
606 enum ldb_scope scope,
607 struct ldb_request *parent)
609 uint32_t i;
610 enum ndr_err_code ndr_err;
611 const struct ldb_val *omd_value;
612 struct replPropertyMetaDataBlob *omd;
613 int ret;
615 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
616 if (!omd_value) {
617 /* We can't make up a key version number without meta data */
618 return LDB_SUCCESS;
620 if (!omd_value) {
621 return LDB_SUCCESS;
624 omd = talloc(msg, struct replPropertyMetaDataBlob);
625 if (!omd) {
626 ldb_module_oom(module);
627 return LDB_SUCCESS;
630 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
631 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
632 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
633 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
634 ldb_dn_get_linearized(msg->dn)));
635 return ldb_operr(ldb_module_get_ctx(module));
638 if (omd->version != 1) {
639 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
640 omd->version, ldb_dn_get_linearized(msg->dn)));
641 talloc_free(omd);
642 return LDB_SUCCESS;
644 for (i=0; i<omd->ctr.ctr1.count; i++) {
645 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
646 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
647 msg, msg,
648 "msDS-KeyVersionNumber",
649 omd->ctr.ctr1.array[i].version);
650 if (ret != LDB_SUCCESS) {
651 talloc_free(omd);
652 return ret;
654 break;
657 return LDB_SUCCESS;
661 #define _UF_TRUST_ACCOUNTS ( \
662 UF_WORKSTATION_TRUST_ACCOUNT | \
663 UF_SERVER_TRUST_ACCOUNT | \
664 UF_INTERDOMAIN_TRUST_ACCOUNT \
666 #define _UF_NO_EXPIRY_ACCOUNTS ( \
667 UF_SMARTCARD_REQUIRED | \
668 UF_DONT_EXPIRE_PASSWD | \
669 _UF_TRUST_ACCOUNTS \
673 calculate msDS-UserPasswordExpiryTimeComputed
675 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
676 struct ldb_message *msg,
677 struct ldb_dn *domain_dn)
679 int64_t pwdLastSet, maxPwdAge;
680 uint32_t userAccountControl;
681 NTTIME ret;
683 userAccountControl = ldb_msg_find_attr_as_uint(msg,
684 "userAccountControl",
686 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
687 return 0x7FFFFFFFFFFFFFFFULL;
690 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
691 if (pwdLastSet == 0) {
692 return 0;
695 if (pwdLastSet <= -1) {
697 * This can't really happen...
699 return 0x7FFFFFFFFFFFFFFFULL;
702 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL) {
704 * Somethings wrong with the clock...
706 return 0x7FFFFFFFFFFFFFFFULL;
710 * Note that maxPwdAge is a stored as negative value.
712 * Possible values are in the range of:
714 * maxPwdAge: -864000000001
715 * to
716 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
719 maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
720 domain_dn, "maxPwdAge", NULL);
721 if (maxPwdAge >= -864000000000) {
723 * This is not really possible...
725 return 0x7FFFFFFFFFFFFFFFULL;
728 if (maxPwdAge == -0x8000000000000000ULL) {
729 return 0x7FFFFFFFFFFFFFFFULL;
733 * Note we already catched maxPwdAge == -0x8000000000000000ULL
734 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
736 * Remember maxPwdAge is a negative number,
737 * so it results in the following.
739 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
741 * 0xFFFFFFFFFFFFFFFFULL
743 ret = pwdLastSet - maxPwdAge;
744 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
745 return 0x7FFFFFFFFFFFFFFFULL;
748 return ret;
753 construct msDS-User-Account-Control-Computed attr
755 static int construct_msds_user_account_control_computed(struct ldb_module *module,
756 struct ldb_message *msg, enum ldb_scope scope,
757 struct ldb_request *parent)
759 uint32_t userAccountControl;
760 uint32_t msDS_User_Account_Control_Computed = 0;
761 struct ldb_context *ldb = ldb_module_get_ctx(module);
762 NTTIME now;
763 struct ldb_dn *nc_root;
764 int ret;
766 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
767 if (ret != 0) {
768 ldb_asprintf_errstring(ldb,
769 "Failed to find NC root of DN: %s: %s",
770 ldb_dn_get_linearized(msg->dn),
771 ldb_errstring(ldb_module_get_ctx(module)));
772 return ret;
774 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
775 /* Only calculate this on our default NC */
776 return 0;
778 /* Test account expire time */
779 unix_to_nt_time(&now, time(NULL));
781 userAccountControl = ldb_msg_find_attr_as_uint(msg,
782 "userAccountControl",
784 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
786 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
787 if (lockoutTime != 0) {
788 int64_t lockoutDuration = samdb_search_int64(ldb,
789 msg, 0, nc_root,
790 "lockoutDuration", NULL);
791 if (lockoutDuration >= 0) {
792 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
793 } else if (lockoutTime - lockoutDuration >= now) {
794 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
799 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
800 NTTIME must_change_time
801 = get_msds_user_password_expiry_time_computed(module,
802 msg, nc_root);
803 /* check for expired password */
804 if (must_change_time < now) {
805 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
809 return samdb_msg_add_int64(ldb,
810 msg->elements, msg,
811 "msDS-User-Account-Control-Computed",
812 msDS_User_Account_Control_Computed);
816 construct msDS-UserPasswordExpiryTimeComputed
818 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
819 struct ldb_message *msg, enum ldb_scope scope,
820 struct ldb_request *parent)
822 struct ldb_context *ldb = ldb_module_get_ctx(module);
823 struct ldb_dn *nc_root;
824 int64_t password_expiry_time;
825 int ret;
827 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
828 if (ret != 0) {
829 ldb_asprintf_errstring(ldb,
830 "Failed to find NC root of DN: %s: %s",
831 ldb_dn_get_linearized(msg->dn),
832 ldb_errstring(ldb));
833 return ret;
836 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
837 /* Only calculate this on our default NC */
838 return 0;
841 password_expiry_time
842 = get_msds_user_password_expiry_time_computed(module, msg,
843 nc_root);
845 return samdb_msg_add_int64(ldb,
846 msg->elements, msg,
847 "msDS-UserPasswordExpiryTimeComputed",
848 password_expiry_time);
852 struct op_controls_flags {
853 bool sd;
854 bool bypassoperational;
857 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
858 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
859 return true;
861 return false;
865 a list of attribute names that should be substituted in the parse
866 tree before the search is done
868 static const struct {
869 const char *attr;
870 const char *replace;
871 } parse_tree_sub[] = {
872 { "createTimeStamp", "whenCreated" },
873 { "modifyTimeStamp", "whenChanged" }
877 struct op_attributes_replace {
878 const char *attr;
879 const char *replace;
880 const char * const *extra_attrs;
881 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
885 static const char *objectSid_attr[] =
887 "objectSid",
888 NULL
892 static const char *objectCategory_attr[] =
894 "objectCategory",
895 NULL
899 static const char *user_account_control_computed_attrs[] =
901 "lockoutTime",
902 "pwdLastSet",
903 NULL
907 static const char *user_password_expiry_time_computed_attrs[] =
909 "pwdLastSet",
910 NULL
915 a list of attribute names that are hidden, but can be searched for
916 using another (non-hidden) name to produce the correct result
918 static const struct op_attributes_replace search_sub[] = {
919 { "createTimeStamp", "whenCreated", NULL , NULL },
920 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
921 { "structuralObjectClass", "objectClass", NULL , NULL },
922 { "canonicalName", NULL, NULL , construct_canonical_name },
923 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
924 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
925 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
926 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
927 { "parentGUID", NULL, NULL, construct_parent_guid },
928 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
929 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
930 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
931 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
932 construct_msds_user_account_control_computed },
933 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
934 construct_msds_user_password_expiry_time_computed }
938 enum op_remove {
939 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
940 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
941 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
942 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
946 a list of attributes that may need to be removed from the
947 underlying db return
949 Some of these are attributes that were once stored, but are now calculated
951 struct op_attributes_operations {
952 const char *attr;
953 enum op_remove op;
956 static const struct op_attributes_operations operational_remove[] = {
957 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
958 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
959 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
960 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
961 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
962 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
967 post process a search result record. For any search_sub[] attributes that were
968 asked for, we need to call the appropriate copy routine to copy the result
969 into the message, then remove any attributes that we added to the search but
970 were not asked for by the user
972 static int operational_search_post_process(struct ldb_module *module,
973 struct ldb_message *msg,
974 enum ldb_scope scope,
975 const char * const *attrs_from_user,
976 const char * const *attrs_searched_for,
977 struct op_controls_flags* controls_flags,
978 struct op_attributes_operations *list,
979 unsigned int list_size,
980 struct op_attributes_replace *list_replace,
981 unsigned int list_replace_size,
982 struct ldb_request *parent)
984 struct ldb_context *ldb;
985 unsigned int i, a = 0;
986 bool constructed_attributes = false;
988 ldb = ldb_module_get_ctx(module);
990 /* removed any attrs that should not be shown to the user */
991 for (i=0; i < list_size; i++) {
992 ldb_msg_remove_attr(msg, list[i].attr);
995 for (a=0; a < list_replace_size; a++) {
996 if (check_keep_control_for_attribute(controls_flags,
997 list_replace[a].attr)) {
998 continue;
1001 /* construct the new attribute, using either a supplied
1002 constructor or a simple copy */
1003 constructed_attributes = true;
1004 if (list_replace[a].constructor != NULL) {
1005 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1006 goto failed;
1008 } else if (ldb_msg_copy_attr(msg,
1009 list_replace[a].replace,
1010 list_replace[a].attr) != LDB_SUCCESS) {
1011 goto failed;
1015 /* Deletion of the search helper attributes are needed if:
1016 * - we generated constructed attributes and
1017 * - we aren't requesting all attributes
1019 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1020 for (i=0; i < list_replace_size; i++) {
1021 /* remove the added search helper attributes, unless
1022 * they were asked for by the user */
1023 if (list_replace[i].replace != NULL &&
1024 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1025 ldb_msg_remove_attr(msg, list_replace[i].replace);
1027 if (list_replace[i].extra_attrs != NULL) {
1028 unsigned int j;
1029 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1030 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1031 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1038 return 0;
1040 failed:
1041 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1042 "operational_search_post_process failed for attribute '%s' - %s",
1043 attrs_from_user[a], ldb_errstring(ldb));
1044 return -1;
1048 hook search operations
1051 struct operational_context {
1052 struct ldb_module *module;
1053 struct ldb_request *req;
1054 enum ldb_scope scope;
1055 const char * const *attrs;
1056 struct op_controls_flags* controls_flags;
1057 struct op_attributes_operations *list_operations;
1058 unsigned int list_operations_size;
1059 struct op_attributes_replace *attrs_to_replace;
1060 unsigned int attrs_to_replace_size;
1063 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1065 struct operational_context *ac;
1066 int ret;
1068 ac = talloc_get_type(req->context, struct operational_context);
1070 if (!ares) {
1071 return ldb_module_done(ac->req, NULL, NULL,
1072 LDB_ERR_OPERATIONS_ERROR);
1074 if (ares->error != LDB_SUCCESS) {
1075 return ldb_module_done(ac->req, ares->controls,
1076 ares->response, ares->error);
1079 switch (ares->type) {
1080 case LDB_REPLY_ENTRY:
1081 /* for each record returned post-process to add any derived
1082 attributes that have been asked for */
1083 ret = operational_search_post_process(ac->module,
1084 ares->message,
1085 ac->scope,
1086 ac->attrs,
1087 req->op.search.attrs,
1088 ac->controls_flags,
1089 ac->list_operations,
1090 ac->list_operations_size,
1091 ac->attrs_to_replace,
1092 ac->attrs_to_replace_size,
1093 req);
1094 if (ret != 0) {
1095 return ldb_module_done(ac->req, NULL, NULL,
1096 LDB_ERR_OPERATIONS_ERROR);
1098 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1100 case LDB_REPLY_REFERRAL:
1101 return ldb_module_send_referral(ac->req, ares->referral);
1103 case LDB_REPLY_DONE:
1105 return ldb_module_done(ac->req, ares->controls,
1106 ares->response, LDB_SUCCESS);
1109 talloc_free(ares);
1110 return LDB_SUCCESS;
1113 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1114 const char* const* attrs,
1115 const char* const* searched_attrs,
1116 struct op_controls_flags* controls_flags)
1118 int idx = 0;
1119 int i;
1120 struct op_attributes_operations *list = talloc_zero_array(ctx,
1121 struct op_attributes_operations,
1122 ARRAY_SIZE(operational_remove) + 1);
1124 if (list == NULL) {
1125 return NULL;
1128 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1129 switch (operational_remove[i].op) {
1130 case OPERATIONAL_REMOVE_UNASKED:
1131 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1132 continue;
1134 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1135 continue;
1137 list[idx].attr = operational_remove[i].attr;
1138 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1139 idx++;
1140 break;
1142 case OPERATIONAL_REMOVE_ALWAYS:
1143 list[idx].attr = operational_remove[i].attr;
1144 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1145 idx++;
1146 break;
1148 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1149 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1150 list[idx].attr = operational_remove[i].attr;
1151 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1152 idx++;
1154 break;
1156 case OPERATIONAL_SD_FLAGS:
1157 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1158 continue;
1160 if (controls_flags->sd) {
1161 if (attrs == NULL) {
1162 continue;
1164 if (attrs[0] == NULL) {
1165 continue;
1167 if (ldb_attr_in_list(attrs, "*")) {
1168 continue;
1171 list[idx].attr = operational_remove[i].attr;
1172 list[idx].op = OPERATIONAL_SD_FLAGS;
1173 idx++;
1174 break;
1178 return list;
1181 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1183 struct ldb_context *ldb;
1184 struct operational_context *ac;
1185 struct ldb_request *down_req;
1186 const char **search_attrs = NULL;
1187 unsigned int i, a;
1188 int ret;
1190 /* There are no operational attributes on special DNs */
1191 if (ldb_dn_is_special(req->op.search.base)) {
1192 return ldb_next_request(module, req);
1195 ldb = ldb_module_get_ctx(module);
1197 ac = talloc(req, struct operational_context);
1198 if (ac == NULL) {
1199 return ldb_oom(ldb);
1202 ac->module = module;
1203 ac->req = req;
1204 ac->scope = req->op.search.scope;
1205 ac->attrs = req->op.search.attrs;
1207 /* FIXME: We must copy the tree and keep the original
1208 * unmodified. SSS */
1209 /* replace any attributes in the parse tree that are
1210 searchable, but are stored using a different name in the
1211 backend */
1212 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1213 ldb_parse_tree_attr_replace(req->op.search.tree,
1214 parse_tree_sub[i].attr,
1215 parse_tree_sub[i].replace);
1218 ac->controls_flags = talloc(ac, struct op_controls_flags);
1219 /* remember if the SD_FLAGS_OID was set */
1220 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1221 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1222 ac->controls_flags->bypassoperational =
1223 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1225 ac->attrs_to_replace = NULL;
1226 ac->attrs_to_replace_size = 0;
1227 /* in the list of attributes we are looking for, rename any
1228 attributes to the alias for any hidden attributes that can
1229 be fetched directly using non-hidden names */
1230 for (a=0;ac->attrs && ac->attrs[a];a++) {
1231 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1232 continue;
1234 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1236 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1237 continue;
1240 ac->attrs_to_replace = talloc_realloc(ac,
1241 ac->attrs_to_replace,
1242 struct op_attributes_replace,
1243 ac->attrs_to_replace_size + 1);
1245 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1246 ac->attrs_to_replace_size++;
1247 if (!search_sub[i].replace) {
1248 continue;
1251 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1252 unsigned int j;
1253 const char **search_attrs2;
1254 /* Only adds to the end of the list */
1255 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1256 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1257 ? search_attrs
1258 : ac->attrs,
1259 search_sub[i].extra_attrs[j]);
1260 if (search_attrs2 == NULL) {
1261 return ldb_operr(ldb);
1263 /* may be NULL, talloc_free() doesn't mind */
1264 talloc_free(search_attrs);
1265 search_attrs = search_attrs2;
1269 if (!search_attrs) {
1270 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1271 if (search_attrs == NULL) {
1272 return ldb_operr(ldb);
1275 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1276 search_attrs[a] = search_sub[i].replace;
1279 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1280 search_attrs == NULL?req->op.search.attrs:search_attrs,
1281 ac->controls_flags);
1282 ac->list_operations_size = 0;
1283 i = 0;
1285 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1286 i++;
1288 ac->list_operations_size = i;
1289 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1290 req->op.search.base,
1291 req->op.search.scope,
1292 req->op.search.tree,
1293 /* use new set of attrs if any */
1294 search_attrs == NULL?req->op.search.attrs:search_attrs,
1295 req->controls,
1296 ac, operational_callback,
1297 req);
1298 LDB_REQ_SET_LOCATION(down_req);
1299 if (ret != LDB_SUCCESS) {
1300 return ldb_operr(ldb);
1303 /* perform the search */
1304 return ldb_next_request(module, down_req);
1307 static int operational_init(struct ldb_module *ctx)
1309 struct operational_data *data;
1310 int ret;
1312 ret = ldb_next_init(ctx);
1314 if (ret != LDB_SUCCESS) {
1315 return ret;
1318 data = talloc_zero(ctx, struct operational_data);
1319 if (!data) {
1320 return ldb_module_oom(ctx);
1323 ldb_module_set_private(ctx, data);
1325 return LDB_SUCCESS;
1328 static const struct ldb_module_ops ldb_operational_module_ops = {
1329 .name = "operational",
1330 .search = operational_search,
1331 .init_context = operational_init
1334 int ldb_operational_module_init(const char *version)
1336 LDB_MODULE_CHECK_VERSION(version);
1337 return ldb_register_module(&ldb_operational_module_ops);