dsdb-operational: Implement msDS-User-Account-Control-Computed
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blob5f15932f3f44e80eabd87b0a066daa2b5fe10704
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;
88 construct a canonical name from a message
90 static int construct_canonical_name(struct ldb_module *module,
91 struct ldb_message *msg, enum ldb_scope scope,
92 struct ldb_request *parent)
94 char *canonicalName;
95 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
96 if (canonicalName == NULL) {
97 return ldb_operr(ldb_module_get_ctx(module));
99 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
103 construct a primary group token for groups from a message
105 static int construct_primary_group_token(struct ldb_module *module,
106 struct ldb_message *msg, enum ldb_scope scope,
107 struct ldb_request *parent)
109 struct ldb_context *ldb;
110 uint32_t primary_group_token;
112 ldb = ldb_module_get_ctx(module);
113 if (ldb_match_msg_objectclass(msg, "group") == 1) {
114 primary_group_token
115 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
116 if (primary_group_token == 0) {
117 return LDB_SUCCESS;
120 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
121 primary_group_token);
122 } else {
123 return LDB_SUCCESS;
128 construct the token groups for SAM objects from a message
130 static int construct_token_groups(struct ldb_module *module,
131 struct ldb_message *msg, enum ldb_scope scope,
132 struct ldb_request *parent)
134 struct ldb_context *ldb = ldb_module_get_ctx(module);
135 TALLOC_CTX *tmp_ctx = talloc_new(msg);
136 unsigned int i;
137 int ret;
138 const char *filter;
140 NTSTATUS status;
142 struct dom_sid *primary_group_sid;
143 const char *primary_group_string;
144 const char *primary_group_dn;
145 DATA_BLOB primary_group_blob;
147 struct dom_sid *account_sid;
148 const char *account_sid_string;
149 const char *account_sid_dn;
150 DATA_BLOB account_sid_blob;
151 struct dom_sid *groupSIDs = NULL;
152 unsigned int num_groupSIDs = 0;
154 struct dom_sid *domain_sid;
156 if (scope != LDB_SCOPE_BASE) {
157 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
158 return LDB_ERR_OPERATIONS_ERROR;
161 /* If it's not a user, it won't have a primaryGroupID */
162 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
163 talloc_free(tmp_ctx);
164 return LDB_SUCCESS;
167 /* Ensure it has an objectSID too */
168 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
169 if (account_sid == NULL) {
170 talloc_free(tmp_ctx);
171 return LDB_SUCCESS;
174 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
175 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
176 talloc_free(tmp_ctx);
177 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
178 } else if (!NT_STATUS_IS_OK(status)) {
179 talloc_free(tmp_ctx);
180 return LDB_ERR_OPERATIONS_ERROR;
183 primary_group_sid = dom_sid_add_rid(tmp_ctx,
184 domain_sid,
185 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
186 if (!primary_group_sid) {
187 talloc_free(tmp_ctx);
188 return ldb_oom(ldb);
191 /* only return security groups */
192 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
193 GROUP_TYPE_SECURITY_ENABLED);
194 if (!filter) {
195 talloc_free(tmp_ctx);
196 return ldb_oom(ldb);
199 primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
200 if (!primary_group_string) {
201 talloc_free(tmp_ctx);
202 return ldb_oom(ldb);
205 primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
206 if (!primary_group_dn) {
207 talloc_free(tmp_ctx);
208 return ldb_oom(ldb);
211 primary_group_blob = data_blob_string_const(primary_group_dn);
213 account_sid_string = dom_sid_string(tmp_ctx, account_sid);
214 if (!account_sid_string) {
215 talloc_free(tmp_ctx);
216 return ldb_oom(ldb);
219 account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
220 if (!account_sid_dn) {
221 talloc_free(tmp_ctx);
222 return ldb_oom(ldb);
225 account_sid_blob = data_blob_string_const(account_sid_dn);
227 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
228 true, /* We don't want to add the object's SID itself,
229 it's not returend in this attribute */
230 filter,
231 tmp_ctx, &groupSIDs, &num_groupSIDs);
233 if (!NT_STATUS_IS_OK(status)) {
234 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
235 account_sid_string, nt_errstr(status));
236 talloc_free(tmp_ctx);
237 return LDB_ERR_OPERATIONS_ERROR;
240 /* Expands the primary group - this function takes in
241 * memberOf-like values, so we fake one up with the
242 * <SID=S-...> format of DN and then let it expand
243 * them, as long as they meet the filter - so only
244 * domain groups, not builtin groups
246 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
247 tmp_ctx, &groupSIDs, &num_groupSIDs);
248 if (!NT_STATUS_IS_OK(status)) {
249 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
250 account_sid_string, nt_errstr(status));
251 talloc_free(tmp_ctx);
252 return LDB_ERR_OPERATIONS_ERROR;
255 for (i=0; i < num_groupSIDs; i++) {
256 ret = samdb_msg_add_dom_sid(ldb, msg, msg, "tokenGroups", &groupSIDs[i]);
257 if (ret) {
258 talloc_free(tmp_ctx);
259 return ret;
263 return LDB_SUCCESS;
267 construct the parent GUID for an entry from a message
269 static int construct_parent_guid(struct ldb_module *module,
270 struct ldb_message *msg, enum ldb_scope scope,
271 struct ldb_request *parent)
273 struct ldb_result *res, *parent_res;
274 const struct ldb_val *parent_guid;
275 const char *attrs[] = { "instanceType", NULL };
276 const char *attrs2[] = { "objectGUID", NULL };
277 uint32_t instanceType;
278 int ret;
279 struct ldb_dn *parent_dn;
280 struct ldb_val v;
282 /* determine if the object is NC by instance type */
283 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
284 DSDB_FLAG_NEXT_MODULE |
285 DSDB_SEARCH_SHOW_RECYCLED, parent);
286 if (ret != LDB_SUCCESS) {
287 return ret;
290 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
291 "instanceType", 0);
292 talloc_free(res);
293 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
294 DEBUG(4,(__location__ ": Object %s is NC\n",
295 ldb_dn_get_linearized(msg->dn)));
296 return LDB_SUCCESS;
298 parent_dn = ldb_dn_get_parent(msg, msg->dn);
300 if (parent_dn == NULL) {
301 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
302 ldb_dn_get_linearized(msg->dn)));
303 return LDB_SUCCESS;
305 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
306 DSDB_FLAG_NEXT_MODULE |
307 DSDB_SEARCH_SHOW_RECYCLED, parent);
308 talloc_free(parent_dn);
310 /* not NC, so the object should have a parent*/
311 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
312 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
313 talloc_asprintf(msg, "Parent dn for %s does not exist",
314 ldb_dn_get_linearized(msg->dn)));
315 } else if (ret != LDB_SUCCESS) {
316 return ret;
319 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
320 if (!parent_guid) {
321 talloc_free(parent_res);
322 return LDB_SUCCESS;
325 v = data_blob_dup_talloc(parent_res, *parent_guid);
326 if (!v.data) {
327 talloc_free(parent_res);
328 return ldb_oom(ldb_module_get_ctx(module));
330 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
331 talloc_free(parent_res);
332 return ret;
335 static int construct_modifyTimeStamp(struct ldb_module *module,
336 struct ldb_message *msg, enum ldb_scope scope,
337 struct ldb_request *parent)
339 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
340 struct ldb_context *ldb = ldb_module_get_ctx(module);
342 /* We may be being called before the init function has finished */
343 if (!data) {
344 return LDB_SUCCESS;
347 /* Try and set this value up, if possible. Don't worry if it
348 * fails, we may not have the DB set up yet.
350 if (!data->aggregate_dn) {
351 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
354 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
356 * If we have the DN for the object with common name = Aggregate and
357 * the request is for this DN then let's do the following:
358 * 1) search the object which changedUSN correspond to the one of the loaded
359 * schema.
360 * 2) Get the whenChanged attribute
361 * 3) Generate the modifyTimestamp out of the whenChanged attribute
363 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
364 char *value = ldb_timestring(msg, schema->ts_last_change);
366 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
368 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
372 construct a subSchemaSubEntry
374 static int construct_subschema_subentry(struct ldb_module *module,
375 struct ldb_message *msg, enum ldb_scope scope,
376 struct ldb_request *parent)
378 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
379 char *subSchemaSubEntry;
381 /* We may be being called before the init function has finished */
382 if (!data) {
383 return LDB_SUCCESS;
386 /* Try and set this value up, if possible. Don't worry if it
387 * fails, we may not have the DB set up yet, and it's not
388 * really vital anyway */
389 if (!data->aggregate_dn) {
390 struct ldb_context *ldb = ldb_module_get_ctx(module);
391 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
394 if (data->aggregate_dn) {
395 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
396 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
398 return LDB_SUCCESS;
402 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
403 struct ldb_message *msg,
404 struct ldb_message_element *object_category)
406 struct ldb_context *ldb;
407 struct ldb_dn *dn;
408 const struct ldb_val *val;
410 ldb = ldb_module_get_ctx(module);
411 if (!ldb) {
412 DEBUG(4, (__location__ ": Failed to get ldb \n"));
413 return ldb_operr(ldb);
416 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
417 if (!dn) {
418 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
419 (const char *)object_category->values[0].data));
420 return ldb_operr(ldb);
423 val = ldb_dn_get_rdn_val(dn);
424 if (!val) {
425 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
426 ldb_dn_get_linearized(dn)));
427 return ldb_operr(ldb);
430 if (strequal((const char *)val->data, "NTDS-DSA")) {
431 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
432 } else {
433 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
435 return LDB_SUCCESS;
438 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
439 struct ldb_message *msg,
440 struct ldb_dn *dn,
441 struct ldb_request *parent)
443 struct ldb_dn *server_dn;
444 const char *attr_obj_cat[] = { "objectCategory", NULL };
445 struct ldb_result *res;
446 struct ldb_message_element *object_category;
447 int ret;
449 server_dn = ldb_dn_copy(msg, dn);
450 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
451 DEBUG(4, (__location__ ": Failed to add child to %s \n",
452 ldb_dn_get_linearized(server_dn)));
453 return ldb_operr(ldb_module_get_ctx(module));
456 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
457 DSDB_FLAG_NEXT_MODULE, parent);
458 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
459 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
460 ldb_dn_get_linearized(server_dn)));
461 return LDB_SUCCESS;
462 } else if (ret != LDB_SUCCESS) {
463 return ret;
466 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
467 if (!object_category) {
468 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
469 ldb_dn_get_linearized(res->msgs[0]->dn)));
470 return LDB_SUCCESS;
472 return construct_msds_isrodc_with_dn(module, msg, object_category);
475 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
476 struct ldb_message *msg,
477 struct ldb_request *parent)
479 int ret;
480 struct ldb_dn *server_dn;
482 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
483 &server_dn, parent);
484 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
485 /* it's OK if we can't find serverReferenceBL attribute */
486 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
487 ldb_dn_get_linearized(msg->dn)));
488 return LDB_SUCCESS;
489 } else if (ret != LDB_SUCCESS) {
490 return ret;
493 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
497 construct msDS-isRODC attr
499 static int construct_msds_isrodc(struct ldb_module *module,
500 struct ldb_message *msg, enum ldb_scope scope,
501 struct ldb_request *parent)
503 struct ldb_message_element * object_class;
504 struct ldb_message_element * object_category;
505 unsigned int i;
507 object_class = ldb_msg_find_element(msg, "objectClass");
508 if (!object_class) {
509 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
510 ldb_dn_get_linearized(msg->dn)));
511 return ldb_operr(ldb_module_get_ctx(module));
514 for (i=0; i<object_class->num_values; i++) {
515 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
516 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
517 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
519 object_category = ldb_msg_find_element(msg, "objectCategory");
520 if (!object_category) {
521 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
522 ldb_dn_get_linearized(msg->dn)));
523 return LDB_SUCCESS;
525 return construct_msds_isrodc_with_dn(module, msg, object_category);
527 if (strequal((const char*)object_class->values[i].data, "server")) {
528 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
529 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
530 * substituting TN for TO.
532 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
534 if (strequal((const char*)object_class->values[i].data, "computer")) {
535 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
536 * rule for the "TO is a server object" case, substituting TS for TO.
538 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
542 return LDB_SUCCESS;
547 construct msDS-keyVersionNumber attr
549 TODO: Make this based on the 'win2k' DS huristics bit...
552 static int construct_msds_keyversionnumber(struct ldb_module *module,
553 struct ldb_message *msg,
554 enum ldb_scope scope,
555 struct ldb_request *parent)
557 uint32_t i;
558 enum ndr_err_code ndr_err;
559 const struct ldb_val *omd_value;
560 struct replPropertyMetaDataBlob *omd;
561 int ret;
563 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
564 if (!omd_value) {
565 /* We can't make up a key version number without meta data */
566 return LDB_SUCCESS;
568 if (!omd_value) {
569 return LDB_SUCCESS;
572 omd = talloc(msg, struct replPropertyMetaDataBlob);
573 if (!omd) {
574 ldb_module_oom(module);
575 return LDB_SUCCESS;
578 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
579 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
580 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
581 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
582 ldb_dn_get_linearized(msg->dn)));
583 return ldb_operr(ldb_module_get_ctx(module));
586 if (omd->version != 1) {
587 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
588 omd->version, ldb_dn_get_linearized(msg->dn)));
589 talloc_free(omd);
590 return LDB_SUCCESS;
592 for (i=0; i<omd->ctr.ctr1.count; i++) {
593 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
594 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
595 msg, msg,
596 "msDS-KeyVersionNumber",
597 omd->ctr.ctr1.array[i].version);
598 if (ret != LDB_SUCCESS) {
599 talloc_free(omd);
600 return ret;
602 break;
605 return LDB_SUCCESS;
609 #define _UF_TRUST_ACCOUNTS ( \
610 UF_WORKSTATION_TRUST_ACCOUNT | \
611 UF_SERVER_TRUST_ACCOUNT | \
612 UF_INTERDOMAIN_TRUST_ACCOUNT \
614 #define _UF_NO_EXPIRY_ACCOUNTS ( \
615 UF_SMARTCARD_REQUIRED | \
616 UF_DONT_EXPIRE_PASSWD | \
617 _UF_TRUST_ACCOUNTS \
621 calculate msDS-UserPasswordExpiryTimeComputed
623 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
624 struct ldb_message *msg,
625 struct ldb_dn *domain_dn)
627 int64_t pwdLastSet, maxPwdAge;
628 uint32_t userAccountControl;
629 NTTIME ret;
631 userAccountControl = ldb_msg_find_attr_as_uint(msg,
632 "userAccountControl",
634 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
635 return 0x7FFFFFFFFFFFFFFFULL;
638 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
639 if (pwdLastSet == 0) {
640 return 0;
643 if (pwdLastSet <= -1) {
645 * This can't really happen...
647 return 0x7FFFFFFFFFFFFFFFULL;
650 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL) {
652 * Somethings wrong with the clock...
654 return 0x7FFFFFFFFFFFFFFFULL;
658 * Note that maxPwdAge is a stored as negative value.
660 * Possible values are in the range of:
662 * maxPwdAge: -864000000001
663 * to
664 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
667 maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
668 domain_dn, "maxPwdAge", NULL);
669 if (maxPwdAge >= -864000000000) {
671 * This is not really possible...
673 return 0x7FFFFFFFFFFFFFFFULL;
676 if (maxPwdAge == -0x8000000000000000ULL) {
677 return 0x7FFFFFFFFFFFFFFFULL;
681 * Note we already catched maxPwdAge == -0x8000000000000000ULL
682 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
684 * Remember maxPwdAge is a negative number,
685 * so it results in the following.
687 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
689 * 0xFFFFFFFFFFFFFFFFULL
691 ret = pwdLastSet - maxPwdAge;
692 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
693 return 0x7FFFFFFFFFFFFFFFULL;
696 return ret;
701 construct msDS-User-Account-Control-Computed attr
703 static int construct_msds_user_account_control_computed(struct ldb_module *module,
704 struct ldb_message *msg, enum ldb_scope scope,
705 struct ldb_request *parent)
707 uint32_t userAccountControl;
708 uint32_t msDS_User_Account_Control_Computed = 0;
709 struct ldb_context *ldb = ldb_module_get_ctx(module);
710 NTTIME now;
711 struct ldb_dn *nc_root;
712 int ret;
714 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
715 if (ret != 0) {
716 ldb_asprintf_errstring(ldb,
717 "Failed to find NC root of DN: %s: %s",
718 ldb_dn_get_linearized(msg->dn),
719 ldb_errstring(ldb_module_get_ctx(module)));
720 return ret;
722 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
723 /* Only calculate this on our default NC */
724 return 0;
726 /* Test account expire time */
727 unix_to_nt_time(&now, time(NULL));
729 userAccountControl = ldb_msg_find_attr_as_uint(msg,
730 "userAccountControl",
732 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
734 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
735 if (lockoutTime != 0) {
736 int64_t lockoutDuration = samdb_search_int64(ldb,
737 msg, 0, nc_root,
738 "lockoutDuration", NULL);
739 if (lockoutDuration >= 0) {
740 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
741 } else if (lockoutTime - lockoutDuration >= now) {
742 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
747 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
748 NTTIME must_change_time
749 = get_msds_user_password_expiry_time_computed(module,
750 msg, nc_root);
751 /* check for expired password */
752 if (must_change_time < now) {
753 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
757 return samdb_msg_add_int64(ldb,
758 msg->elements, msg,
759 "msDS-User-Account-Control-Computed",
760 msDS_User_Account_Control_Computed);
763 struct op_controls_flags {
764 bool sd;
765 bool bypassoperational;
768 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
769 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
770 return true;
772 return false;
776 a list of attribute names that should be substituted in the parse
777 tree before the search is done
779 static const struct {
780 const char *attr;
781 const char *replace;
782 } parse_tree_sub[] = {
783 { "createTimeStamp", "whenCreated" },
784 { "modifyTimeStamp", "whenChanged" }
788 struct op_attributes_replace {
789 const char *attr;
790 const char *replace;
791 const char * const *extra_attrs;
792 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
796 static const char *objectSid_attr[] =
798 "objectSid",
799 NULL
803 static const char *objectCategory_attr[] =
805 "objectCategory",
806 NULL
810 static const char *user_account_control_computed_attrs[] =
812 "lockoutTime",
813 "pwdLastSet",
814 NULL
819 a list of attribute names that are hidden, but can be searched for
820 using another (non-hidden) name to produce the correct result
822 static const struct op_attributes_replace search_sub[] = {
823 { "createTimeStamp", "whenCreated", NULL , NULL },
824 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
825 { "structuralObjectClass", "objectClass", NULL , NULL },
826 { "canonicalName", NULL, NULL , construct_canonical_name },
827 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
828 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
829 { "parentGUID", NULL, NULL, construct_parent_guid },
830 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
831 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
832 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
833 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
834 construct_msds_user_account_control_computed }
838 enum op_remove {
839 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
840 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
841 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
842 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
846 a list of attributes that may need to be removed from the
847 underlying db return
849 Some of these are attributes that were once stored, but are now calculated
851 struct op_attributes_operations {
852 const char *attr;
853 enum op_remove op;
856 static const struct op_attributes_operations operational_remove[] = {
857 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
858 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
859 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
860 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
861 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
862 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
867 post process a search result record. For any search_sub[] attributes that were
868 asked for, we need to call the appropriate copy routine to copy the result
869 into the message, then remove any attributes that we added to the search but
870 were not asked for by the user
872 static int operational_search_post_process(struct ldb_module *module,
873 struct ldb_message *msg,
874 enum ldb_scope scope,
875 const char * const *attrs_from_user,
876 const char * const *attrs_searched_for,
877 struct op_controls_flags* controls_flags,
878 struct op_attributes_operations *list,
879 unsigned int list_size,
880 struct op_attributes_replace *list_replace,
881 unsigned int list_replace_size,
882 struct ldb_request *parent)
884 struct ldb_context *ldb;
885 unsigned int i, a = 0;
886 bool constructed_attributes = false;
888 ldb = ldb_module_get_ctx(module);
890 /* removed any attrs that should not be shown to the user */
891 for (i=0; i < list_size; i++) {
892 ldb_msg_remove_attr(msg, list[i].attr);
895 for (a=0; a < list_replace_size; a++) {
896 if (check_keep_control_for_attribute(controls_flags,
897 list_replace[a].attr)) {
898 continue;
901 /* construct the new attribute, using either a supplied
902 constructor or a simple copy */
903 constructed_attributes = true;
904 if (list_replace[a].constructor != NULL) {
905 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
906 goto failed;
908 } else if (ldb_msg_copy_attr(msg,
909 list_replace[a].replace,
910 list_replace[a].attr) != LDB_SUCCESS) {
911 goto failed;
915 /* Deletion of the search helper attributes are needed if:
916 * - we generated constructed attributes and
917 * - we aren't requesting all attributes
919 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
920 for (i=0; i < list_replace_size; i++) {
921 /* remove the added search helper attributes, unless
922 * they were asked for by the user */
923 if (list_replace[i].replace != NULL &&
924 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
925 ldb_msg_remove_attr(msg, list_replace[i].replace);
927 if (list_replace[i].extra_attrs != NULL) {
928 unsigned int j;
929 for (j=0; list_replace[i].extra_attrs[j]; j++) {
930 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
931 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
938 return 0;
940 failed:
941 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
942 "operational_search_post_process failed for attribute '%s' - %s",
943 attrs_from_user[a], ldb_errstring(ldb));
944 return -1;
948 hook search operations
951 struct operational_context {
952 struct ldb_module *module;
953 struct ldb_request *req;
954 enum ldb_scope scope;
955 const char * const *attrs;
956 struct op_controls_flags* controls_flags;
957 struct op_attributes_operations *list_operations;
958 unsigned int list_operations_size;
959 struct op_attributes_replace *attrs_to_replace;
960 unsigned int attrs_to_replace_size;
963 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
965 struct operational_context *ac;
966 int ret;
968 ac = talloc_get_type(req->context, struct operational_context);
970 if (!ares) {
971 return ldb_module_done(ac->req, NULL, NULL,
972 LDB_ERR_OPERATIONS_ERROR);
974 if (ares->error != LDB_SUCCESS) {
975 return ldb_module_done(ac->req, ares->controls,
976 ares->response, ares->error);
979 switch (ares->type) {
980 case LDB_REPLY_ENTRY:
981 /* for each record returned post-process to add any derived
982 attributes that have been asked for */
983 ret = operational_search_post_process(ac->module,
984 ares->message,
985 ac->scope,
986 ac->attrs,
987 req->op.search.attrs,
988 ac->controls_flags,
989 ac->list_operations,
990 ac->list_operations_size,
991 ac->attrs_to_replace,
992 ac->attrs_to_replace_size,
993 req);
994 if (ret != 0) {
995 return ldb_module_done(ac->req, NULL, NULL,
996 LDB_ERR_OPERATIONS_ERROR);
998 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1000 case LDB_REPLY_REFERRAL:
1001 return ldb_module_send_referral(ac->req, ares->referral);
1003 case LDB_REPLY_DONE:
1005 return ldb_module_done(ac->req, ares->controls,
1006 ares->response, LDB_SUCCESS);
1009 talloc_free(ares);
1010 return LDB_SUCCESS;
1013 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1014 const char* const* attrs,
1015 const char* const* searched_attrs,
1016 struct op_controls_flags* controls_flags)
1018 int idx = 0;
1019 int i;
1020 struct op_attributes_operations *list = talloc_zero_array(ctx,
1021 struct op_attributes_operations,
1022 ARRAY_SIZE(operational_remove) + 1);
1024 if (list == NULL) {
1025 return NULL;
1028 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1029 switch (operational_remove[i].op) {
1030 case OPERATIONAL_REMOVE_UNASKED:
1031 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1032 continue;
1034 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1035 continue;
1037 list[idx].attr = operational_remove[i].attr;
1038 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1039 idx++;
1040 break;
1042 case OPERATIONAL_REMOVE_ALWAYS:
1043 list[idx].attr = operational_remove[i].attr;
1044 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1045 idx++;
1046 break;
1048 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1049 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1050 list[idx].attr = operational_remove[i].attr;
1051 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1052 idx++;
1054 break;
1056 case OPERATIONAL_SD_FLAGS:
1057 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1058 continue;
1060 if (controls_flags->sd) {
1061 if (attrs == NULL) {
1062 continue;
1064 if (attrs[0] == NULL) {
1065 continue;
1067 if (ldb_attr_in_list(attrs, "*")) {
1068 continue;
1071 list[idx].attr = operational_remove[i].attr;
1072 list[idx].op = OPERATIONAL_SD_FLAGS;
1073 idx++;
1074 break;
1078 return list;
1081 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1083 struct ldb_context *ldb;
1084 struct operational_context *ac;
1085 struct ldb_request *down_req;
1086 const char **search_attrs = NULL;
1087 unsigned int i, a;
1088 int ret;
1090 /* There are no operational attributes on special DNs */
1091 if (ldb_dn_is_special(req->op.search.base)) {
1092 return ldb_next_request(module, req);
1095 ldb = ldb_module_get_ctx(module);
1097 ac = talloc(req, struct operational_context);
1098 if (ac == NULL) {
1099 return ldb_oom(ldb);
1102 ac->module = module;
1103 ac->req = req;
1104 ac->scope = req->op.search.scope;
1105 ac->attrs = req->op.search.attrs;
1107 /* FIXME: We must copy the tree and keep the original
1108 * unmodified. SSS */
1109 /* replace any attributes in the parse tree that are
1110 searchable, but are stored using a different name in the
1111 backend */
1112 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1113 ldb_parse_tree_attr_replace(req->op.search.tree,
1114 parse_tree_sub[i].attr,
1115 parse_tree_sub[i].replace);
1118 ac->controls_flags = talloc(ac, struct op_controls_flags);
1119 /* remember if the SD_FLAGS_OID was set */
1120 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1121 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1122 ac->controls_flags->bypassoperational =
1123 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1125 ac->attrs_to_replace = NULL;
1126 ac->attrs_to_replace_size = 0;
1127 /* in the list of attributes we are looking for, rename any
1128 attributes to the alias for any hidden attributes that can
1129 be fetched directly using non-hidden names */
1130 for (a=0;ac->attrs && ac->attrs[a];a++) {
1131 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1132 continue;
1134 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1136 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1137 continue;
1140 ac->attrs_to_replace = talloc_realloc(ac,
1141 ac->attrs_to_replace,
1142 struct op_attributes_replace,
1143 ac->attrs_to_replace_size + 1);
1145 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1146 ac->attrs_to_replace_size++;
1147 if (!search_sub[i].replace) {
1148 continue;
1151 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1152 unsigned int j;
1153 const char **search_attrs2;
1154 /* Only adds to the end of the list */
1155 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1156 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1157 ? search_attrs
1158 : ac->attrs,
1159 search_sub[i].extra_attrs[j]);
1160 if (search_attrs2 == NULL) {
1161 return ldb_operr(ldb);
1163 /* may be NULL, talloc_free() doesn't mind */
1164 talloc_free(search_attrs);
1165 search_attrs = search_attrs2;
1169 if (!search_attrs) {
1170 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1171 if (search_attrs == NULL) {
1172 return ldb_operr(ldb);
1175 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1176 search_attrs[a] = search_sub[i].replace;
1179 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1180 search_attrs == NULL?req->op.search.attrs:search_attrs,
1181 ac->controls_flags);
1182 ac->list_operations_size = 0;
1183 i = 0;
1185 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1186 i++;
1188 ac->list_operations_size = i;
1189 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1190 req->op.search.base,
1191 req->op.search.scope,
1192 req->op.search.tree,
1193 /* use new set of attrs if any */
1194 search_attrs == NULL?req->op.search.attrs:search_attrs,
1195 req->controls,
1196 ac, operational_callback,
1197 req);
1198 LDB_REQ_SET_LOCATION(down_req);
1199 if (ret != LDB_SUCCESS) {
1200 return ldb_operr(ldb);
1203 /* perform the search */
1204 return ldb_next_request(module, down_req);
1207 static int operational_init(struct ldb_module *ctx)
1209 struct operational_data *data;
1210 int ret;
1212 ret = ldb_next_init(ctx);
1214 if (ret != LDB_SUCCESS) {
1215 return ret;
1218 data = talloc_zero(ctx, struct operational_data);
1219 if (!data) {
1220 return ldb_module_oom(ctx);
1223 ldb_module_set_private(ctx, data);
1225 return LDB_SUCCESS;
1228 static const struct ldb_module_ops ldb_operational_module_ops = {
1229 .name = "operational",
1230 .search = operational_search,
1231 .init_context = operational_init
1234 int ldb_operational_module_init(const char *version)
1236 LDB_MODULE_CHECK_VERSION(version);
1237 return ldb_register_module(&ldb_operational_module_ops);