4 Copyright (C) Andrew Tridgell 2005
5 Copyright (C) Simo Sorce 2006
7 ** NOTE! The following LGPL license applies to the ldb
8 ** library. This does NOT imply that all of Samba is released
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public
22 License along with this library; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 handle operational attributes
30 createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
31 modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
33 for the above two, we do the search as normal, and if
34 createTimestamp or modifyTimestamp is asked for, then do
35 additional searches for whenCreated and whenChanged and fill in
38 we also need to replace these with the whenCreated/whenChanged
39 equivalent in the search expression trees
41 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
44 on init we need to setup attribute handlers for these so
45 comparisons are done correctly. The resolution is 1 second.
47 on add we need to add both the above, for current time
49 on modify we need to change whenChanged
52 subschemaSubentry: HIDDEN, not-searchable,
53 points at DN CN=Aggregate,CN=Schema,CN=Configuration,$BASEDN
55 for this one we do the search as normal, then add the static
56 value if requested. How do we work out the $BASEDN from inside a
60 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
62 for this one we do the search as normal, then if requested ask
63 for objectclass, change the attribute name, and add it
65 allowedAttributesEffective: HIDDEN, CONSTRUCTED, not-searchable,
66 list of attributes that can be modified - requires schema lookup
69 attributeTypes: in schema only
70 objectClasses: in schema only
71 matchingRules: in schema only
72 matchingRuleUse: in schema only
73 creatorsName: not supported by w2k3?
74 modifiersName: not supported by w2k3?
78 #include "ldb/include/includes.h"
81 construct a canonical name from a message
83 static int construct_canonical_name(struct ldb_module *module, struct ldb_message *msg)
86 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
87 if (canonicalName == NULL) {
90 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
94 a list of attribute names that should be substituted in the parse
95 tree before the search is done
100 } parse_tree_sub[] = {
101 { "createTimestamp", "whenCreated" },
102 { "modifyTimestamp", "whenChanged" }
107 a list of attribute names that are hidden, but can be searched for
108 using another (non-hidden) name to produce the correct result
110 static const struct {
113 int (*constructor)(struct ldb_module *, struct ldb_message *);
115 { "createTimestamp", "whenCreated", NULL },
116 { "modifyTimestamp", "whenChanged", NULL },
117 { "structuralObjectClass", "objectClass", NULL },
118 { "canonicalName", "distinguishedName", construct_canonical_name }
122 post process a search result record. For any search_sub[] attributes that were
123 asked for, we need to call the appropriate copy routine to copy the result
124 into the message, then remove any attributes that we added to the search but were
125 not asked for by the user
127 static int operational_search_post_process(struct ldb_module *module,
128 struct ldb_message *msg,
129 const char * const *attrs)
133 for (a=0;attrs && attrs[a];a++) {
134 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
135 if (ldb_attr_cmp(attrs[a], search_sub[i].attr) != 0) {
139 /* construct the new attribute, using either a supplied
140 constructor or a simple copy */
141 if (search_sub[i].constructor) {
142 if (search_sub[i].constructor(module, msg) != 0) {
145 } else if (ldb_msg_copy_attr(msg,
146 search_sub[i].replace,
147 search_sub[i].attr) != 0) {
151 /* remove the added search attribute, unless it was asked for
153 if (search_sub[i].replace == NULL ||
154 ldb_attr_in_list(attrs, search_sub[i].replace) ||
155 ldb_attr_in_list(attrs, "*")) {
159 ldb_msg_remove_attr(msg, search_sub[i].replace);
166 ldb_debug_set(module->ldb, LDB_DEBUG_WARNING,
167 "operational_search_post_process failed for attribute '%s'\n",
173 add a time element to a record
175 static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
177 struct ldb_message_element *el;
180 if (ldb_msg_find_element(msg, attr) != NULL) {
184 s = ldb_timestring(msg, t);
189 if (ldb_msg_add_string(msg, attr, s) != 0) {
193 el = ldb_msg_find_element(msg, attr);
194 /* always set as replace. This works because on add ops, the flag
196 el->flags = LDB_FLAG_MOD_REPLACE;
202 add a uint64_t element to a record
204 static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
206 struct ldb_message_element *el;
208 if (ldb_msg_find_element(msg, attr) != NULL) {
212 if (ldb_msg_add_fmt(msg, attr, "%llu", v) != 0) {
216 el = ldb_msg_find_element(msg, attr);
217 /* always set as replace. This works because on add ops, the flag
219 el->flags = LDB_FLAG_MOD_REPLACE;
226 hook search operations
229 struct operational_async_context {
231 struct ldb_module *module;
233 int (*up_callback)(struct ldb_context *, void *, struct ldb_async_result *);
236 const char * const *attrs;
239 static int operational_async_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
241 struct operational_async_context *ac;
243 if (!context || !ares) {
244 ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
248 ac = talloc_get_type(context, struct operational_async_context);
250 if (ares->type == LDB_REPLY_ENTRY) {
251 /* for each record returned post-process to add any derived
252 attributes that have been asked for */
253 if (operational_search_post_process(ac->module, ares->message, ac->attrs) != 0) {
258 return ac->up_callback(ldb, ac->up_context, ares);
262 return LDB_ERR_OPERATIONS_ERROR;
265 static int operational_search(struct ldb_module *module, struct ldb_request *req)
267 struct operational_async_context *ac;
268 struct ldb_request *down_req;
269 const char **search_attrs = NULL;
272 req->async.handle = NULL;
274 ac = talloc(req, struct operational_async_context);
276 return LDB_ERR_OPERATIONS_ERROR;
280 ac->up_context = req->async.context;
281 ac->up_callback = req->async.callback;
282 ac->timeout = req->async.timeout;
283 ac->attrs = req->op.search.attrs;
285 down_req = talloc_zero(req, struct ldb_request);
286 if (down_req == NULL) {
287 return LDB_ERR_OPERATIONS_ERROR;
290 down_req->operation = req->operation;
291 down_req->op.search.base = req->op.search.base;
292 down_req->op.search.scope = req->op.search.scope;
293 down_req->op.search.tree = req->op.search.tree;
295 /* FIXME: I hink we should copy the tree and keep the original
297 /* replace any attributes in the parse tree that are
298 searchable, but are stored using a different name in the
300 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
301 ldb_parse_tree_attr_replace(req->op.search.tree,
302 parse_tree_sub[i].attr,
303 parse_tree_sub[i].replace);
306 /* in the list of attributes we are looking for, rename any
307 attributes to the alias for any hidden attributes that can
308 be fetched directly using non-hidden names */
309 for (a=0;ac->attrs && ac->attrs[a];a++) {
310 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
311 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
312 search_sub[i].replace) {
314 search_attrs = ldb_attr_list_copy(req, ac->attrs);
315 if (search_attrs == NULL) {
316 return LDB_ERR_OPERATIONS_ERROR;
319 search_attrs[a] = search_sub[i].replace;
324 /* use new set of attrs if any */
325 if (search_attrs) down_req->op.search.attrs = search_attrs;
326 else down_req->op.search.attrs = req->op.search.attrs;
328 down_req->controls = req->controls;
330 down_req->async.context = ac;
331 down_req->async.callback = operational_async_callback;
332 down_req->async.timeout = req->async.timeout;
334 /* perform the search */
335 ret = ldb_next_request(module, down_req);
337 /* do not free down_req as the call results may be linked to it,
338 * it will be freed when the upper level request get freed */
339 if (ret == LDB_SUCCESS) {
340 req->async.handle = down_req->async.handle;
349 static int operational_add(struct ldb_module *module, struct ldb_request *req)
351 struct ldb_request *down_req;
352 struct ldb_message *msg;
353 time_t t = time(NULL);
356 if (ldb_dn_is_special(req->op.add.message->dn)) {
357 return ldb_next_request(module, req);
360 down_req = talloc(req, struct ldb_request);
361 if (down_req == NULL) {
362 return LDB_ERR_OPERATIONS_ERROR;
367 /* we have to copy the message as the caller might have it as a const */
368 down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
370 return LDB_ERR_OPERATIONS_ERROR;
372 if (add_time_element(msg, "whenCreated", t) != 0 ||
373 add_time_element(msg, "whenChanged", t) != 0) {
374 talloc_free(down_req);
375 return LDB_ERR_OPERATIONS_ERROR;
378 /* see if the backend can give us the USN */
379 if (module->ldb->sequence_number != NULL) {
380 uint64_t seq_num = module->ldb->sequence_number(module->ldb);
381 if (add_uint64_element(msg, "uSNCreated", seq_num) != 0 ||
382 add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
383 talloc_free(down_req);
384 return LDB_ERR_OPERATIONS_ERROR;
388 /* go on with the call chain */
389 ret = ldb_next_request(module, down_req);
391 /* do not free down_req as the call results may be linked to it,
392 * it will be freed when the upper level request get freed */
393 if (ret == LDB_SUCCESS) {
394 req->async.handle = down_req->async.handle;
401 hook modify record ops
403 static int operational_modify(struct ldb_module *module, struct ldb_request *req)
405 struct ldb_request *down_req;
406 struct ldb_message *msg;
407 time_t t = time(NULL);
410 if (ldb_dn_is_special(req->op.mod.message->dn)) {
411 return ldb_next_request(module, req);
414 down_req = talloc(req, struct ldb_request);
415 if (down_req == NULL) {
416 return LDB_ERR_OPERATIONS_ERROR;
421 /* we have to copy the message as the caller might have it as a const */
422 down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
424 return LDB_ERR_OPERATIONS_ERROR;
426 if (add_time_element(msg, "whenChanged", t) != 0) {
427 talloc_free(down_req);
428 return LDB_ERR_OPERATIONS_ERROR;
431 /* update the records USN if possible */
432 if (module->ldb->sequence_number != NULL &&
433 add_uint64_element(msg, "uSNChanged",
434 module->ldb->sequence_number(module->ldb)) != 0) {
435 talloc_free(down_req);
439 /* go on with the call chain */
440 ret = ldb_next_request(module, down_req);
442 /* do not free down_req as the call results may be linked to it,
443 * it will be freed when the upper level request get freed */
444 if (ret == LDB_SUCCESS) {
445 req->async.handle = down_req->async.handle;
451 static int operational_init(struct ldb_module *ctx)
453 /* setup some standard attribute handlers */
454 ldb_set_attrib_handler_syntax(ctx->ldb, "whenCreated", LDB_SYNTAX_UTC_TIME);
455 ldb_set_attrib_handler_syntax(ctx->ldb, "whenChanged", LDB_SYNTAX_UTC_TIME);
456 ldb_set_attrib_handler_syntax(ctx->ldb, "subschemaSubentry", LDB_SYNTAX_DN);
457 ldb_set_attrib_handler_syntax(ctx->ldb, "structuralObjectClass", LDB_SYNTAX_OBJECTCLASS);
459 return ldb_next_init(ctx);
462 static const struct ldb_module_ops operational_ops = {
463 .name = "operational",
464 .search = operational_search,
465 .add = operational_add,
466 .modify = operational_modify,
467 .init_context = operational_init
470 int ldb_operational_init(void)
472 return ldb_register_module(&operational_ops);