s4:operational LDB - implement the "tokenGroups" constructed attribute
[Samba/nascimento.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blob7bb74689c6ee7345d38e7312b87000aa56f411e2
1 /*
2 ldb database library
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
33 the resulting values
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?
66 #include "includes.h"
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"
73 #include "dsdb/samdb/ldb_modules/util.h"
75 #include "auth/auth.h"
76 #include "libcli/security/dom_sid.h"
78 #ifndef ARRAY_SIZE
79 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
80 #endif
82 struct operational_data {
83 struct ldb_dn *aggregate_dn;
87 construct a canonical name from a message
89 static int construct_canonical_name(struct ldb_module *module,
90 struct ldb_message *msg)
92 char *canonicalName;
93 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
94 if (canonicalName == NULL) {
95 return LDB_ERR_OPERATIONS_ERROR;
97 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
101 construct a primary group token for groups from a message
103 static int construct_primary_group_token(struct ldb_module *module,
104 struct ldb_message *msg)
106 struct ldb_context *ldb;
107 uint32_t primary_group_token;
109 ldb = ldb_module_get_ctx(module);
110 if (ldb_match_msg_objectclass(msg, "group") == 1) {
111 primary_group_token
112 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
113 if (primary_group_token == 0) {
114 return LDB_SUCCESS;
117 return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
118 primary_group_token);
119 } else {
120 return LDB_SUCCESS;
125 construct the token groups for SAM objects from a message
127 static int construct_token_groups(struct ldb_module *module,
128 struct ldb_message *msg)
130 struct ldb_context *ldb;
131 const struct dom_sid *sid;
133 ldb = ldb_module_get_ctx(module);
135 sid = samdb_result_dom_sid(msg, msg, "objectSid");
136 if (sid != NULL) {
137 NTSTATUS status;
138 uint32_t prim_group_rid;
139 struct dom_sid **sids = NULL;
140 unsigned int i, num_sids = 0;
141 int ret;
143 prim_group_rid = samdb_result_uint(msg, "primaryGroupID", 0);
144 if (prim_group_rid != 0) {
145 struct dom_sid *prim_group_sid;
147 prim_group_sid = dom_sid_add_rid(msg,
148 samdb_domain_sid(ldb),
149 prim_group_rid);
150 if (prim_group_sid == NULL) {
151 ldb_oom(ldb);
152 return LDB_ERR_OPERATIONS_ERROR;
155 /* onlyChilds = false, we want to consider also the
156 * "primaryGroupID" for membership */
157 status = authsam_expand_nested_groups(ldb,
158 prim_group_sid,
159 false, msg,
160 &sids, &num_sids);
161 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
162 ldb_oom(ldb);
163 return LDB_ERR_OPERATIONS_ERROR;
165 if (!NT_STATUS_IS_OK(status)) {
166 return LDB_ERR_OPERATIONS_ERROR;
169 for (i = 0; i < num_sids; i++) {
170 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
171 "tokenGroups",
172 sids[i]);
173 if (ret != LDB_SUCCESS) {
174 talloc_free(sids);
175 return ret;
179 talloc_free(sids);
182 sids = NULL;
183 num_sids = 0;
185 /* onlyChils = true, we don't want to have the SAM object itself
186 * in the result */
187 status = authsam_expand_nested_groups(ldb, sid, true, msg,
188 &sids, &num_sids);
189 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
190 ldb_oom(ldb);
191 return LDB_ERR_OPERATIONS_ERROR;
193 if (!NT_STATUS_IS_OK(status)) {
194 return LDB_ERR_OPERATIONS_ERROR;
197 for (i = 0; i < num_sids; i++) {
198 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
199 "tokenGroups", sids[i]);
200 if (ret != LDB_SUCCESS) {
201 talloc_free(sids);
202 return ret;
206 talloc_free(sids);
209 return LDB_SUCCESS;
213 construct the parent GUID for an entry from a message
215 static int construct_parent_guid(struct ldb_module *module,
216 struct ldb_message *msg)
218 struct ldb_result *res;
219 const struct ldb_val *parent_guid;
220 const char *attrs[] = { "objectGUID", NULL };
221 int ret;
222 struct ldb_val v;
224 /* TODO: In the future, this needs to honour the partition boundaries */
225 struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
227 if (parent_dn == NULL) {
228 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
229 ldb_dn_get_linearized(msg->dn)));
230 return LDB_SUCCESS;
233 ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
234 talloc_free(parent_dn);
235 /* if there is no parentGUID for this object, then return */
236 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
237 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
238 ldb_dn_get_linearized(msg->dn)));
239 return LDB_SUCCESS;
240 } else if (ret != LDB_SUCCESS) {
241 return ret;
244 parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
245 if (!parent_guid) {
246 talloc_free(res);
247 return LDB_SUCCESS;
250 v = data_blob_dup_talloc(res, parent_guid);
251 if (!v.data) {
252 talloc_free(res);
253 return LDB_ERR_OPERATIONS_ERROR;
255 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
256 talloc_free(res);
257 return ret;
261 construct a subSchemaSubEntry
263 static int construct_subschema_subentry(struct ldb_module *module,
264 struct ldb_message *msg)
266 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
267 char *subSchemaSubEntry;
268 if (data && data->aggregate_dn) {
269 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
270 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
272 return LDB_SUCCESS;
277 a list of attribute names that should be substituted in the parse
278 tree before the search is done
280 static const struct {
281 const char *attr;
282 const char *replace;
283 } parse_tree_sub[] = {
284 { "createTimestamp", "whenCreated" },
285 { "modifyTimestamp", "whenChanged" }
290 a list of attribute names that are hidden, but can be searched for
291 using another (non-hidden) name to produce the correct result
293 static const struct {
294 const char *attr;
295 const char *replace;
296 const char *extra_attr;
297 int (*constructor)(struct ldb_module *, struct ldb_message *);
298 } search_sub[] = {
299 { "createTimestamp", "whenCreated", NULL , NULL },
300 { "modifyTimestamp", "whenChanged", NULL , NULL },
301 { "structuralObjectClass", "objectClass", NULL , NULL },
302 { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
303 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
304 { "tokenGroups", "objectSid", "primaryGroupID", construct_token_groups },
305 { "parentGUID", NULL, NULL, construct_parent_guid },
306 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry }
310 enum op_remove {
311 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
312 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
313 OPERATIONAL_SD_FLAGS /* show if SD_FLAGS_OID set, or asked for */
317 a list of attributes that may need to be removed from the
318 underlying db return
320 static const struct {
321 const char *attr;
322 enum op_remove op;
323 } operational_remove[] = {
324 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
325 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
326 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
327 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
328 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED },
329 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
330 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
331 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
336 post process a search result record. For any search_sub[] attributes that were
337 asked for, we need to call the appropriate copy routine to copy the result
338 into the message, then remove any attributes that we added to the search but
339 were not asked for by the user
341 static int operational_search_post_process(struct ldb_module *module,
342 struct ldb_message *msg,
343 const char * const *attrs,
344 bool sd_flags_set)
346 struct ldb_context *ldb;
347 int i, a=0;
349 ldb = ldb_module_get_ctx(module);
351 /* removed any attrs that should not be shown to the user */
352 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
353 switch (operational_remove[i].op) {
354 case OPERATIONAL_REMOVE_UNASKED:
355 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
356 continue;
358 case OPERATIONAL_REMOVE_ALWAYS:
359 ldb_msg_remove_attr(msg, operational_remove[i].attr);
360 break;
361 case OPERATIONAL_SD_FLAGS:
362 if (sd_flags_set ||
363 ldb_attr_in_list(attrs, operational_remove[i].attr)) {
364 continue;
366 ldb_msg_remove_attr(msg, operational_remove[i].attr);
367 break;
371 for (a=0;attrs && attrs[a];a++) {
372 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
373 if (ldb_attr_cmp(attrs[a], search_sub[i].attr) != 0) {
374 continue;
377 /* construct the new attribute, using either a supplied
378 constructor or a simple copy */
379 if (search_sub[i].constructor != NULL) {
380 if (search_sub[i].constructor(module, msg) != LDB_SUCCESS) {
381 goto failed;
383 } else if (ldb_msg_copy_attr(msg,
384 search_sub[i].replace,
385 search_sub[i].attr) != LDB_SUCCESS) {
386 goto failed;
389 /* remove the added search attribute, unless it was
390 asked for by the user */
391 if (search_sub[i].replace != NULL &&
392 !ldb_attr_in_list(attrs, search_sub[i].replace) &&
393 !ldb_attr_in_list(attrs, "*")) {
394 ldb_msg_remove_attr(msg, search_sub[i].replace);
396 if (search_sub[i].extra_attr != NULL &&
397 !ldb_attr_in_list(attrs, search_sub[i].extra_attr) &&
398 !ldb_attr_in_list(attrs, "*")) {
399 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
404 return 0;
406 failed:
407 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
408 "operational_search_post_process failed for attribute '%s'",
409 attrs[a]);
410 return -1;
415 hook search operations
418 struct operational_context {
419 struct ldb_module *module;
420 struct ldb_request *req;
422 const char * const *attrs;
423 bool sd_flags_set;
426 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
428 struct operational_context *ac;
429 int ret;
431 ac = talloc_get_type(req->context, struct operational_context);
433 if (!ares) {
434 return ldb_module_done(ac->req, NULL, NULL,
435 LDB_ERR_OPERATIONS_ERROR);
437 if (ares->error != LDB_SUCCESS) {
438 return ldb_module_done(ac->req, ares->controls,
439 ares->response, ares->error);
442 switch (ares->type) {
443 case LDB_REPLY_ENTRY:
444 /* for each record returned post-process to add any derived
445 attributes that have been asked for */
446 ret = operational_search_post_process(ac->module,
447 ares->message,
448 ac->attrs,
449 ac->sd_flags_set);
450 if (ret != 0) {
451 return ldb_module_done(ac->req, NULL, NULL,
452 LDB_ERR_OPERATIONS_ERROR);
454 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
456 case LDB_REPLY_REFERRAL:
457 return ldb_module_send_referral(ac->req, ares->referral);
459 case LDB_REPLY_DONE:
461 return ldb_module_done(ac->req, ares->controls,
462 ares->response, LDB_SUCCESS);
465 talloc_free(ares);
466 return LDB_SUCCESS;
469 static int operational_search(struct ldb_module *module, struct ldb_request *req)
471 struct ldb_context *ldb;
472 struct operational_context *ac;
473 struct ldb_request *down_req;
474 const char **search_attrs = NULL;
475 int i, a;
476 int ret;
478 ldb = ldb_module_get_ctx(module);
480 ac = talloc(req, struct operational_context);
481 if (ac == NULL) {
482 return LDB_ERR_OPERATIONS_ERROR;
485 ac->module = module;
486 ac->req = req;
487 ac->attrs = req->op.search.attrs;
489 /* FIXME: We must copy the tree and keep the original
490 * unmodified. SSS */
491 /* replace any attributes in the parse tree that are
492 searchable, but are stored using a different name in the
493 backend */
494 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
495 ldb_parse_tree_attr_replace(req->op.search.tree,
496 parse_tree_sub[i].attr,
497 parse_tree_sub[i].replace);
500 /* in the list of attributes we are looking for, rename any
501 attributes to the alias for any hidden attributes that can
502 be fetched directly using non-hidden names */
503 for (a=0;ac->attrs && ac->attrs[a];a++) {
504 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
505 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
506 search_sub[i].replace) {
508 if (search_sub[i].extra_attr) {
509 const char **search_attrs2;
510 /* Only adds to the end of the list */
511 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
512 ? search_attrs
513 : ac->attrs,
514 search_sub[i].extra_attr);
515 if (search_attrs2 == NULL) {
516 return LDB_ERR_OPERATIONS_ERROR;
518 /* may be NULL, talloc_free() doesn't mind */
519 talloc_free(search_attrs);
520 search_attrs = search_attrs2;
523 if (!search_attrs) {
524 search_attrs = ldb_attr_list_copy(req, ac->attrs);
525 if (search_attrs == NULL) {
526 return LDB_ERR_OPERATIONS_ERROR;
529 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
530 search_attrs[a] = search_sub[i].replace;
535 /* remember if the SD_FLAGS_OID was set */
536 ac->sd_flags_set = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
538 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
539 req->op.search.base,
540 req->op.search.scope,
541 req->op.search.tree,
542 /* use new set of attrs if any */
543 search_attrs == NULL?req->op.search.attrs:search_attrs,
544 req->controls,
545 ac, operational_callback,
546 req);
547 if (ret != LDB_SUCCESS) {
548 return LDB_ERR_OPERATIONS_ERROR;
551 /* perform the search */
552 return ldb_next_request(module, down_req);
555 static int operational_init(struct ldb_module *ctx)
557 struct operational_data *data;
558 struct ldb_context *ldb = ldb_module_get_ctx(ctx);
559 int ret = ldb_next_init(ctx);
561 if (ret != LDB_SUCCESS) {
562 return ret;
565 data = talloc(ctx, struct operational_data);
566 if (!data) {
567 ldb_module_oom(ctx);
568 return LDB_ERR_OPERATIONS_ERROR;
571 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
572 if (!data->aggregate_dn) {
573 ldb_set_errstring(ldb, "Could not build aggregate schema DN");
574 return LDB_ERR_OPERATIONS_ERROR;
577 ldb_module_set_private(ctx, data);
579 return LDB_SUCCESS;
582 const struct ldb_module_ops ldb_operational_module_ops = {
583 .name = "operational",
584 .search = operational_search,
585 .init_context = operational_init