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 "param/param.h"
72 #include "dsdb/samdb/samdb.h"
75 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
79 construct a canonical name from a message
81 static int construct_canonical_name(struct ldb_module *module,
82 struct ldb_message *msg)
85 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
86 if (canonicalName == NULL) {
89 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
93 construct a primary group token for groups from a message
95 static int construct_primary_group_token(struct ldb_module *module,
96 struct ldb_message *msg)
98 struct ldb_context *ldb;
99 uint32_t primary_group_token;
101 ldb = ldb_module_get_ctx(module);
103 if (samdb_search_count(ldb, ldb, msg->dn, "(objectclass=group)") == 1) {
105 = samdb_result_rid_from_sid(ldb, msg, "objectSid", 0);
106 return samdb_msg_add_int(ldb, ldb, msg, "primaryGroupToken",
107 primary_group_token);
113 static int construct_parent_guid(struct ldb_module *module,
114 struct ldb_message *msg)
116 struct ldb_context *ldb;
117 struct GUID parent_guid;
120 ldb = ldb_module_get_ctx(module);
122 ret = dsdb_find_parentguid_by_dn(ldb, msg->dn, &parent_guid);
125 if (ret != LDB_SUCCESS){
127 /* if there is no parentGUID for this object, then return */
128 if (ret == LDB_ERR_NO_SUCH_OBJECT){
136 ret = dsdb_msg_add_guid(msg, &parent_guid, "parentGUID");
144 a list of attribute names that should be substituted in the parse
145 tree before the search is done
147 static const struct {
150 } parse_tree_sub[] = {
151 { "createTimestamp", "whenCreated" },
152 { "modifyTimestamp", "whenChanged" }
157 a list of attribute names that are hidden, but can be searched for
158 using another (non-hidden) name to produce the correct result
160 static const struct {
163 int (*constructor)(struct ldb_module *, struct ldb_message *);
165 { "createTimestamp", "whenCreated", NULL },
166 { "modifyTimestamp", "whenChanged", NULL },
167 { "structuralObjectClass", "objectClass", NULL },
168 { "canonicalName", "distinguishedName", construct_canonical_name },
169 { "primaryGroupToken", "objectSid", construct_primary_group_token },
170 { "parentGUID", NULL, construct_parent_guid }
175 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
176 OPERATIONAL_REMOVE_UNASKED /* remove if not requested */
180 a list of attributes that may need to be removed from the
183 static const struct {
186 } operational_remove[] = {
187 { "ntSecurityDescriptor", OPERATIONAL_REMOVE_UNASKED },
188 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
189 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
190 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
191 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
192 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
193 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
198 post process a search result record. For any search_sub[] attributes that were
199 asked for, we need to call the appropriate copy routine to copy the result
200 into the message, then remove any attributes that we added to the search but
201 were not asked for by the user
203 static int operational_search_post_process(struct ldb_module *module,
204 struct ldb_message *msg,
205 const char * const *attrs)
207 struct ldb_context *ldb;
210 ldb = ldb_module_get_ctx(module);
212 /* removed any attrs that should not be shown to the user */
213 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
214 struct ldb_message_element *el;
216 switch (operational_remove[i].op) {
217 case OPERATIONAL_REMOVE_UNASKED:
218 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
221 case OPERATIONAL_REMOVE_ALWAYS:
222 el = ldb_msg_find_element(msg, operational_remove[i].attr);
224 ldb_msg_remove_element(msg, el);
230 for (a=0;attrs && attrs[a];a++) {
231 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
232 if (ldb_attr_cmp(attrs[a], search_sub[i].attr) != 0) {
236 /* construct the new attribute, using either a supplied
237 constructor or a simple copy */
238 if (search_sub[i].constructor) {
239 if (search_sub[i].constructor(module, msg) != 0) {
242 } else if (ldb_msg_copy_attr(msg,
243 search_sub[i].replace,
244 search_sub[i].attr) != 0) {
248 /* remove the added search attribute, unless it was
249 asked for by the user */
250 if (search_sub[i].replace == NULL ||
251 ldb_attr_in_list(attrs, search_sub[i].replace) ||
252 ldb_attr_in_list(attrs, "*")) {
256 ldb_msg_remove_attr(msg, search_sub[i].replace);
263 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
264 "operational_search_post_process failed for attribute '%s'",
271 hook search operations
274 struct operational_context {
275 struct ldb_module *module;
276 struct ldb_request *req;
278 const char * const *attrs;
281 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
283 struct operational_context *ac;
286 ac = talloc_get_type(req->context, struct operational_context);
289 return ldb_module_done(ac->req, NULL, NULL,
290 LDB_ERR_OPERATIONS_ERROR);
292 if (ares->error != LDB_SUCCESS) {
293 return ldb_module_done(ac->req, ares->controls,
294 ares->response, ares->error);
297 switch (ares->type) {
298 case LDB_REPLY_ENTRY:
299 /* for each record returned post-process to add any derived
300 attributes that have been asked for */
301 ret = operational_search_post_process(ac->module,
305 return ldb_module_done(ac->req, NULL, NULL,
306 LDB_ERR_OPERATIONS_ERROR);
308 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
310 case LDB_REPLY_REFERRAL:
311 /* ignore referrals */
316 return ldb_module_done(ac->req, ares->controls,
317 ares->response, LDB_SUCCESS);
324 static int operational_search(struct ldb_module *module, struct ldb_request *req)
326 struct ldb_context *ldb;
327 struct operational_context *ac;
328 struct ldb_request *down_req;
329 const char **search_attrs = NULL;
333 ldb = ldb_module_get_ctx(module);
335 ac = talloc(req, struct operational_context);
337 return LDB_ERR_OPERATIONS_ERROR;
342 ac->attrs = req->op.search.attrs;
344 /* FIXME: We must copy the tree and keep the original
346 /* replace any attributes in the parse tree that are
347 searchable, but are stored using a different name in the
349 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
350 ldb_parse_tree_attr_replace(req->op.search.tree,
351 parse_tree_sub[i].attr,
352 parse_tree_sub[i].replace);
355 /* in the list of attributes we are looking for, rename any
356 attributes to the alias for any hidden attributes that can
357 be fetched directly using non-hidden names */
358 for (a=0;ac->attrs && ac->attrs[a];a++) {
359 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
360 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
361 search_sub[i].replace) {
363 search_attrs = ldb_attr_list_copy(req, ac->attrs);
364 if (search_attrs == NULL) {
365 return LDB_ERR_OPERATIONS_ERROR;
368 search_attrs[a] = search_sub[i].replace;
373 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
375 req->op.search.scope,
377 /* use new set of attrs if any */
378 search_attrs == NULL?req->op.search.attrs:search_attrs,
380 ac, operational_callback,
382 if (ret != LDB_SUCCESS) {
383 return LDB_ERR_OPERATIONS_ERROR;
386 /* perform the search */
387 return ldb_next_request(module, down_req);
390 static int operational_init(struct ldb_module *ctx)
398 return ldb_next_init(ctx);
401 const struct ldb_module_ops ldb_operational_module_ops = {
402 .name = "operational",
403 .search = operational_search,
404 .init_context = operational_init