4 Copyright (C) Andrew Tridgell 2005
5 Copyright (C) Simo Sorce 2006-2008
6 Copyright (C) Matthias Dieter Wallnöfer 2009
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 handle operational attributes
27 createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
28 modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
30 for the above two, we do the search as normal, and if
31 createTimestamp or modifyTimestamp is asked for, then do
32 additional searches for whenCreated and whenChanged and fill in
35 we also need to replace these with the whenCreated/whenChanged
36 equivalent in the search expression trees
38 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
39 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
41 on init we need to setup attribute handlers for these so
42 comparisons are done correctly. The resolution is 1 second.
44 on add we need to add both the above, for current time
46 on modify we need to change whenChanged
48 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
50 for this one we do the search as normal, then if requested ask
51 for objectclass, change the attribute name, and add it
53 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
55 contains the RID of a certain group object
58 attributeTypes: in schema only
59 objectClasses: in schema only
60 matchingRules: in schema only
61 matchingRuleUse: in schema only
62 creatorsName: not supported by w2k3?
63 modifiersName: not supported by w2k3?
67 #include "ldb_includes.h"
68 #include "ldb_module.h"
70 #include "librpc/gen_ndr/ndr_misc.h"
71 #include "librpc/gen_ndr/ndr_drsblobs.h"
72 #include "param/param.h"
73 #include "dsdb/samdb/samdb.h"
74 #include "dsdb/samdb/ldb_modules/util.h"
76 #include "auth/auth.h"
77 #include "libcli/security/dom_sid.h"
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
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)
94 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
95 if (canonicalName == NULL) {
96 return LDB_ERR_OPERATIONS_ERROR;
98 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
102 construct a primary group token for groups from a message
104 static int construct_primary_group_token(struct ldb_module *module,
105 struct ldb_message *msg)
107 struct ldb_context *ldb;
108 uint32_t primary_group_token;
110 ldb = ldb_module_get_ctx(module);
111 if (ldb_match_msg_objectclass(msg, "group") == 1) {
113 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
114 if (primary_group_token == 0) {
118 return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
119 primary_group_token);
126 construct the token groups for SAM objects from a message
128 static int construct_token_groups(struct ldb_module *module,
129 struct ldb_message *msg)
131 struct ldb_context *ldb;
132 const struct dom_sid *sid;
134 ldb = ldb_module_get_ctx(module);
136 sid = samdb_result_dom_sid(msg, msg, "objectSid");
139 uint32_t prim_group_rid;
140 struct dom_sid **sids = NULL;
141 unsigned int i, num_sids = 0;
144 prim_group_rid = samdb_result_uint(msg, "primaryGroupID", 0);
145 if (prim_group_rid != 0) {
146 struct dom_sid *prim_group_sid;
148 prim_group_sid = dom_sid_add_rid(msg,
149 samdb_domain_sid(ldb),
151 if (prim_group_sid == NULL) {
153 return LDB_ERR_OPERATIONS_ERROR;
156 /* onlyChilds = false, we want to consider also the
157 * "primaryGroupID" for membership */
158 status = authsam_expand_nested_groups(ldb,
162 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
164 return LDB_ERR_OPERATIONS_ERROR;
166 if (!NT_STATUS_IS_OK(status)) {
167 return LDB_ERR_OPERATIONS_ERROR;
170 for (i = 0; i < num_sids; i++) {
171 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
174 if (ret != LDB_SUCCESS) {
186 /* onlyChils = true, we don't want to have the SAM object itself
188 status = authsam_expand_nested_groups(ldb, sid, true, msg,
190 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
192 return LDB_ERR_OPERATIONS_ERROR;
194 if (!NT_STATUS_IS_OK(status)) {
195 return LDB_ERR_OPERATIONS_ERROR;
198 for (i = 0; i < num_sids; i++) {
199 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
200 "tokenGroups", sids[i]);
201 if (ret != LDB_SUCCESS) {
214 construct the parent GUID for an entry from a message
216 static int construct_parent_guid(struct ldb_module *module,
217 struct ldb_message *msg)
219 struct ldb_result *res;
220 const struct ldb_val *parent_guid;
221 const char *attrs[] = { "objectGUID", NULL };
225 /* TODO: In the future, this needs to honour the partition boundaries */
226 struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
228 if (parent_dn == NULL) {
229 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
230 ldb_dn_get_linearized(msg->dn)));
234 ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
235 talloc_free(parent_dn);
236 /* if there is no parentGUID for this object, then return */
237 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
238 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
239 ldb_dn_get_linearized(msg->dn)));
241 } else if (ret != LDB_SUCCESS) {
245 parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
251 v = data_blob_dup_talloc(res, parent_guid);
254 return LDB_ERR_OPERATIONS_ERROR;
256 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
262 construct a subSchemaSubEntry
264 static int construct_subschema_subentry(struct ldb_module *module,
265 struct ldb_message *msg)
267 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
268 char *subSchemaSubEntry;
270 /* We may be being called before the init function has finished */
275 /* Try and set this value up, if possible. Don't worry if it
276 * fails, we may not have the DB set up yet, and it's not
277 * really vital anyway */
278 if (!data->aggregate_dn) {
279 struct ldb_context *ldb = ldb_module_get_ctx(module);
280 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
283 if (data->aggregate_dn) {
284 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
285 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
291 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
292 struct ldb_message *msg,
293 struct ldb_message_element *object_category)
295 struct ldb_context *ldb;
297 const struct ldb_val *val;
299 ldb = ldb_module_get_ctx(module);
301 DEBUG(4, (__location__ ": Failed to get ldb \n"));
302 return LDB_ERR_OPERATIONS_ERROR;
305 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
307 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
308 (const char *)object_category->values[0].data));
309 return LDB_ERR_OPERATIONS_ERROR;
312 val = ldb_dn_get_rdn_val(dn);
314 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
315 ldb_dn_get_linearized(dn)));
316 return LDB_ERR_OPERATIONS_ERROR;
319 if (strequal((const char *)val->data, "NTDS-DSA")) {
320 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
322 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
327 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
328 struct ldb_message *msg,
331 struct ldb_dn *server_dn;
332 const char *attr_obj_cat[] = { "objectCategory", NULL };
333 struct ldb_result *res;
334 struct ldb_message_element *object_category;
337 server_dn = ldb_dn_copy(msg, dn);
338 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
339 DEBUG(4, (__location__ ": Failed to add child to %s \n",
340 ldb_dn_get_linearized(server_dn)));
341 return LDB_ERR_OPERATIONS_ERROR;
344 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat, 0);
345 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
346 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
347 ldb_dn_get_linearized(server_dn)));
349 } else if (ret != LDB_SUCCESS) {
353 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
354 if (!object_category) {
355 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
356 ldb_dn_get_linearized(res->msgs[0]->dn)));
359 return construct_msds_isrodc_with_dn(module, msg, object_category);
362 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
363 struct ldb_message *msg)
365 struct ldb_context *ldb;
366 const char *attr[] = { "serverReferenceBL", NULL };
367 struct ldb_result *res;
369 struct ldb_dn *server_dn;
371 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr, 0);
372 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
373 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
374 ldb_dn_get_linearized(msg->dn)));
376 } else if (ret != LDB_SUCCESS) {
380 ldb = ldb_module_get_ctx(module);
385 server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
387 DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
388 ldb_dn_get_linearized(res->msgs[0]->dn)));
391 return construct_msds_isrodc_with_server_dn(module, msg, server_dn);
395 construct msDS-isRODC attr
397 static int construct_msds_isrodc(struct ldb_module *module, struct ldb_message *msg)
399 struct ldb_message_element * object_class;
400 struct ldb_message_element * object_category;
403 object_class = ldb_msg_find_element(msg, "objectClass");
405 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
406 ldb_dn_get_linearized(msg->dn)));
407 return LDB_ERR_OPERATIONS_ERROR;
410 for (i=0; i<object_class->num_values; i++) {
411 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
412 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
413 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
415 object_category = ldb_msg_find_element(msg, "objectCategory");
416 if (!object_category) {
417 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
418 ldb_dn_get_linearized(msg->dn)));
421 return construct_msds_isrodc_with_dn(module, msg, object_category);
423 if (strequal((const char*)object_class->values[i].data, "server")) {
424 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
425 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
426 * substituting TN for TO.
428 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
430 if (strequal((const char*)object_class->values[i].data, "computer")) {
431 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
432 * rule for the "TO is a server object" case, substituting TS for TO.
434 return construct_msds_isrodc_with_computer_dn(module, msg);
443 construct msDS-keyVersionNumber attr
445 TODO: Make this based on the 'win2k' DS huristics bit...
448 static int construct_msds_keyversionnumber(struct ldb_module *module, struct ldb_message *msg)
451 enum ndr_err_code ndr_err;
452 const struct ldb_val *omd_value;
453 struct replPropertyMetaDataBlob *omd;
454 struct ldb_context *ldb = ldb_module_get_ctx(module);
456 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
458 /* We can't make up a key version number without meta data */
465 omd = talloc(msg, struct replPropertyMetaDataBlob);
467 ldb_module_oom(module);
471 ndr_err = ndr_pull_struct_blob(omd_value, omd,
472 lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
474 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
475 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
476 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
477 ldb_dn_get_linearized(msg->dn)));
478 return LDB_ERR_OPERATIONS_ERROR;
481 if (omd->version != 1) {
482 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
483 omd->version, ldb_dn_get_linearized(msg->dn)));
487 for (i=0; i<omd->ctr.ctr1.count; i++) {
488 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTRIBUTE_unicodePwd) {
489 ldb_msg_add_fmt(msg, "msDS-KeyVersionNumber", "%u", omd->ctr.ctr1.array[i].version);
498 a list of attribute names that should be substituted in the parse
499 tree before the search is done
501 static const struct {
504 } parse_tree_sub[] = {
505 { "createTimestamp", "whenCreated" },
506 { "modifyTimestamp", "whenChanged" }
511 a list of attribute names that are hidden, but can be searched for
512 using another (non-hidden) name to produce the correct result
514 static const struct {
517 const char *extra_attr;
518 int (*constructor)(struct ldb_module *, struct ldb_message *);
520 { "createTimestamp", "whenCreated", NULL , NULL },
521 { "modifyTimestamp", "whenChanged", NULL , NULL },
522 { "structuralObjectClass", "objectClass", NULL , NULL },
523 { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
524 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
525 { "tokenGroups", "objectSid", "primaryGroupID", construct_token_groups },
526 { "parentGUID", NULL, NULL, construct_parent_guid },
527 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
528 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
529 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
534 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
535 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
536 OPERATIONAL_SD_FLAGS /* show if SD_FLAGS_OID set, or asked for */
540 a list of attributes that may need to be removed from the
543 Some of these are attributes that were once stored, but are now calculated
545 static const struct {
548 } operational_remove[] = {
549 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
550 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_ALWAYS },
551 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
552 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
553 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
554 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED },
555 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
556 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
557 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
562 post process a search result record. For any search_sub[] attributes that were
563 asked for, we need to call the appropriate copy routine to copy the result
564 into the message, then remove any attributes that we added to the search but
565 were not asked for by the user
567 static int operational_search_post_process(struct ldb_module *module,
568 struct ldb_message *msg,
569 const char * const *attrs_from_user,
570 const char * const *attrs_searched_for,
573 struct ldb_context *ldb;
574 unsigned int i, a = 0;
575 bool constructed_attributes = false;
577 ldb = ldb_module_get_ctx(module);
579 /* removed any attrs that should not be shown to the user */
580 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
581 switch (operational_remove[i].op) {
582 case OPERATIONAL_REMOVE_UNASKED:
583 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
586 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
589 case OPERATIONAL_REMOVE_ALWAYS:
590 ldb_msg_remove_attr(msg, operational_remove[i].attr);
592 case OPERATIONAL_SD_FLAGS:
594 ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
597 ldb_msg_remove_attr(msg, operational_remove[i].attr);
602 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
603 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
604 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
608 /* construct the new attribute, using either a supplied
609 constructor or a simple copy */
610 constructed_attributes = true;
611 if (search_sub[i].constructor != NULL) {
612 if (search_sub[i].constructor(module, msg) != LDB_SUCCESS) {
615 } else if (ldb_msg_copy_attr(msg,
616 search_sub[i].replace,
617 search_sub[i].attr) != LDB_SUCCESS) {
623 /* Deletion of the search helper attributes are needed if:
624 * - we generated constructed attributes and
625 * - we aren't requesting all attributes
627 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
628 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
629 /* remove the added search helper attributes, unless
630 * they were asked for by the user */
631 if (search_sub[i].replace != NULL &&
632 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
633 ldb_msg_remove_attr(msg, search_sub[i].replace);
635 if (search_sub[i].extra_attr != NULL &&
636 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
637 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
645 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
646 "operational_search_post_process failed for attribute '%s'",
653 hook search operations
656 struct operational_context {
657 struct ldb_module *module;
658 struct ldb_request *req;
660 const char * const *attrs;
664 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
666 struct operational_context *ac;
669 ac = talloc_get_type(req->context, struct operational_context);
672 return ldb_module_done(ac->req, NULL, NULL,
673 LDB_ERR_OPERATIONS_ERROR);
675 if (ares->error != LDB_SUCCESS) {
676 return ldb_module_done(ac->req, ares->controls,
677 ares->response, ares->error);
680 switch (ares->type) {
681 case LDB_REPLY_ENTRY:
682 /* for each record returned post-process to add any derived
683 attributes that have been asked for */
684 ret = operational_search_post_process(ac->module,
687 req->op.search.attrs,
690 return ldb_module_done(ac->req, NULL, NULL,
691 LDB_ERR_OPERATIONS_ERROR);
693 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
695 case LDB_REPLY_REFERRAL:
696 return ldb_module_send_referral(ac->req, ares->referral);
700 return ldb_module_done(ac->req, ares->controls,
701 ares->response, LDB_SUCCESS);
708 static int operational_search(struct ldb_module *module, struct ldb_request *req)
710 struct ldb_context *ldb;
711 struct operational_context *ac;
712 struct ldb_request *down_req;
713 const char **search_attrs = NULL;
717 /* There are no operational attributes on special DNs */
718 if (ldb_dn_is_special(req->op.search.base)) {
719 return ldb_next_request(module, req);
722 ldb = ldb_module_get_ctx(module);
724 ac = talloc(req, struct operational_context);
726 return LDB_ERR_OPERATIONS_ERROR;
731 ac->attrs = req->op.search.attrs;
733 /* FIXME: We must copy the tree and keep the original
735 /* replace any attributes in the parse tree that are
736 searchable, but are stored using a different name in the
738 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
739 ldb_parse_tree_attr_replace(req->op.search.tree,
740 parse_tree_sub[i].attr,
741 parse_tree_sub[i].replace);
744 /* in the list of attributes we are looking for, rename any
745 attributes to the alias for any hidden attributes that can
746 be fetched directly using non-hidden names */
747 for (a=0;ac->attrs && ac->attrs[a];a++) {
748 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
749 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
750 search_sub[i].replace) {
752 if (search_sub[i].extra_attr) {
753 const char **search_attrs2;
754 /* Only adds to the end of the list */
755 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
758 search_sub[i].extra_attr);
759 if (search_attrs2 == NULL) {
760 return LDB_ERR_OPERATIONS_ERROR;
762 /* may be NULL, talloc_free() doesn't mind */
763 talloc_free(search_attrs);
764 search_attrs = search_attrs2;
768 search_attrs = ldb_attr_list_copy(req, ac->attrs);
769 if (search_attrs == NULL) {
770 return LDB_ERR_OPERATIONS_ERROR;
773 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
774 search_attrs[a] = search_sub[i].replace;
779 /* remember if the SD_FLAGS_OID was set */
780 ac->sd_flags_set = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
782 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
784 req->op.search.scope,
786 /* use new set of attrs if any */
787 search_attrs == NULL?req->op.search.attrs:search_attrs,
789 ac, operational_callback,
791 if (ret != LDB_SUCCESS) {
792 return LDB_ERR_OPERATIONS_ERROR;
795 /* perform the search */
796 return ldb_next_request(module, down_req);
799 static int operational_init(struct ldb_module *ctx)
801 struct operational_data *data;
802 int ret = ldb_next_init(ctx);
804 if (ret != LDB_SUCCESS) {
808 data = talloc_zero(ctx, struct operational_data);
811 return LDB_ERR_OPERATIONS_ERROR;
814 ldb_module_set_private(ctx, data);
819 const struct ldb_module_ops ldb_operational_module_ops = {
820 .name = "operational",
821 .search = operational_search,
822 .init_context = operational_init