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"
73 #include "dsdb/samdb/ldb_modules/util.h"
75 #include "auth/auth.h"
76 #include "libcli/security/dom_sid.h"
79 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
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
)
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) {
112 = samdb_result_rid_from_sid(msg
, msg
, "objectSid", 0);
113 if (primary_group_token
== 0) {
117 return samdb_msg_add_int(ldb
, msg
, msg
, "primaryGroupToken",
118 primary_group_token
);
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");
138 uint32_t prim_group_rid
;
139 struct dom_sid
**sids
= NULL
;
140 unsigned int i
, num_sids
= 0;
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
),
150 if (prim_group_sid
== NULL
) {
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
,
161 if (NT_STATUS_EQUAL(status
, NT_STATUS_NO_MEMORY
)) {
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
,
173 if (ret
!= LDB_SUCCESS
) {
185 /* onlyChils = true, we don't want to have the SAM object itself
187 status
= authsam_expand_nested_groups(ldb
, sid
, true, msg
,
189 if (NT_STATUS_EQUAL(status
, NT_STATUS_NO_MEMORY
)) {
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
) {
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
};
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
)));
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
)));
240 } else if (ret
!= LDB_SUCCESS
) {
244 parent_guid
= ldb_msg_find_ldb_val(res
->msgs
[0], "objectGUID");
250 v
= data_blob_dup_talloc(res
, parent_guid
);
253 return LDB_ERR_OPERATIONS_ERROR
;
255 ret
= ldb_msg_add_steal_value(msg
, "parentGUID", &v
);
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
;
269 /* We may be being called before the init function has finished */
274 /* Try and set this value up, if possible. Don't worry if it
275 * fails, we may not have the DB set up yet, and it's not
276 * really vital anyway */
277 if (!data
->aggregate_dn
) {
278 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
279 data
->aggregate_dn
= samdb_aggregate_schema_dn(ldb
, data
);
282 if (data
->aggregate_dn
) {
283 subSchemaSubEntry
= ldb_dn_alloc_linearized(msg
, data
->aggregate_dn
);
284 return ldb_msg_add_steal_string(msg
, "subSchemaSubEntry", subSchemaSubEntry
);
291 a list of attribute names that should be substituted in the parse
292 tree before the search is done
294 static const struct {
297 } parse_tree_sub
[] = {
298 { "createTimestamp", "whenCreated" },
299 { "modifyTimestamp", "whenChanged" }
304 a list of attribute names that are hidden, but can be searched for
305 using another (non-hidden) name to produce the correct result
307 static const struct {
310 const char *extra_attr
;
311 int (*constructor
)(struct ldb_module
*, struct ldb_message
*);
313 { "createTimestamp", "whenCreated", NULL
, NULL
},
314 { "modifyTimestamp", "whenChanged", NULL
, NULL
},
315 { "structuralObjectClass", "objectClass", NULL
, NULL
},
316 { "canonicalName", "distinguishedName", NULL
, construct_canonical_name
},
317 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token
},
318 { "tokenGroups", "objectSid", "primaryGroupID", construct_token_groups
},
319 { "parentGUID", NULL
, NULL
, construct_parent_guid
},
320 { "subSchemaSubEntry", NULL
, NULL
, construct_subschema_subentry
}
325 OPERATIONAL_REMOVE_ALWAYS
, /* remove always */
326 OPERATIONAL_REMOVE_UNASKED
,/* remove if not requested */
327 OPERATIONAL_SD_FLAGS
/* show if SD_FLAGS_OID set, or asked for */
331 a list of attributes that may need to be removed from the
334 static const struct {
337 } operational_remove
[] = {
338 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS
},
339 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS
},
340 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED
},
341 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED
},
342 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED
},
343 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED
},
344 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED
},
345 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED
}
350 post process a search result record. For any search_sub[] attributes that were
351 asked for, we need to call the appropriate copy routine to copy the result
352 into the message, then remove any attributes that we added to the search but
353 were not asked for by the user
355 static int operational_search_post_process(struct ldb_module
*module
,
356 struct ldb_message
*msg
,
357 const char * const *attrs
,
360 struct ldb_context
*ldb
;
361 unsigned int i
, a
= 0;
362 bool constructed_attributes
= false;
364 ldb
= ldb_module_get_ctx(module
);
366 /* removed any attrs that should not be shown to the user */
367 for (i
=0; i
<ARRAY_SIZE(operational_remove
); i
++) {
368 switch (operational_remove
[i
].op
) {
369 case OPERATIONAL_REMOVE_UNASKED
:
370 if (ldb_attr_in_list(attrs
, operational_remove
[i
].attr
)) {
373 case OPERATIONAL_REMOVE_ALWAYS
:
374 ldb_msg_remove_attr(msg
, operational_remove
[i
].attr
);
376 case OPERATIONAL_SD_FLAGS
:
378 ldb_attr_in_list(attrs
, operational_remove
[i
].attr
)) {
381 ldb_msg_remove_attr(msg
, operational_remove
[i
].attr
);
386 for (a
=0;attrs
&& attrs
[a
];a
++) {
387 for (i
=0;i
<ARRAY_SIZE(search_sub
);i
++) {
388 if (ldb_attr_cmp(attrs
[a
], search_sub
[i
].attr
) != 0) {
392 /* construct the new attribute, using either a supplied
393 constructor or a simple copy */
394 constructed_attributes
= true;
395 if (search_sub
[i
].constructor
!= NULL
) {
396 if (search_sub
[i
].constructor(module
, msg
) != LDB_SUCCESS
) {
399 } else if (ldb_msg_copy_attr(msg
,
400 search_sub
[i
].replace
,
401 search_sub
[i
].attr
) != LDB_SUCCESS
) {
407 /* Deletion of the search helper attributes are needed if:
408 * - we generated constructed attributes and
409 * - we aren't requesting all attributes
411 if ((constructed_attributes
) && (!ldb_attr_in_list(attrs
, "*"))) {
412 for (i
=0;i
<ARRAY_SIZE(search_sub
);i
++) {
413 /* remove the added search helper attributes, unless
414 * they were asked for by the user */
415 if (search_sub
[i
].replace
!= NULL
&&
416 !ldb_attr_in_list(attrs
, search_sub
[i
].replace
)) {
417 ldb_msg_remove_attr(msg
, search_sub
[i
].replace
);
419 if (search_sub
[i
].extra_attr
!= NULL
&&
420 !ldb_attr_in_list(attrs
, search_sub
[i
].extra_attr
)) {
421 ldb_msg_remove_attr(msg
, search_sub
[i
].extra_attr
);
429 ldb_debug_set(ldb
, LDB_DEBUG_WARNING
,
430 "operational_search_post_process failed for attribute '%s'",
437 hook search operations
440 struct operational_context
{
441 struct ldb_module
*module
;
442 struct ldb_request
*req
;
444 const char * const *attrs
;
448 static int operational_callback(struct ldb_request
*req
, struct ldb_reply
*ares
)
450 struct operational_context
*ac
;
453 ac
= talloc_get_type(req
->context
, struct operational_context
);
456 return ldb_module_done(ac
->req
, NULL
, NULL
,
457 LDB_ERR_OPERATIONS_ERROR
);
459 if (ares
->error
!= LDB_SUCCESS
) {
460 return ldb_module_done(ac
->req
, ares
->controls
,
461 ares
->response
, ares
->error
);
464 switch (ares
->type
) {
465 case LDB_REPLY_ENTRY
:
466 /* for each record returned post-process to add any derived
467 attributes that have been asked for */
468 ret
= operational_search_post_process(ac
->module
,
473 return ldb_module_done(ac
->req
, NULL
, NULL
,
474 LDB_ERR_OPERATIONS_ERROR
);
476 return ldb_module_send_entry(ac
->req
, ares
->message
, ares
->controls
);
478 case LDB_REPLY_REFERRAL
:
479 return ldb_module_send_referral(ac
->req
, ares
->referral
);
483 return ldb_module_done(ac
->req
, ares
->controls
,
484 ares
->response
, LDB_SUCCESS
);
491 static int operational_search(struct ldb_module
*module
, struct ldb_request
*req
)
493 struct ldb_context
*ldb
;
494 struct operational_context
*ac
;
495 struct ldb_request
*down_req
;
496 const char **search_attrs
= NULL
;
500 ldb
= ldb_module_get_ctx(module
);
502 ac
= talloc(req
, struct operational_context
);
504 return LDB_ERR_OPERATIONS_ERROR
;
509 ac
->attrs
= req
->op
.search
.attrs
;
511 /* FIXME: We must copy the tree and keep the original
513 /* replace any attributes in the parse tree that are
514 searchable, but are stored using a different name in the
516 for (i
=0;i
<ARRAY_SIZE(parse_tree_sub
);i
++) {
517 ldb_parse_tree_attr_replace(req
->op
.search
.tree
,
518 parse_tree_sub
[i
].attr
,
519 parse_tree_sub
[i
].replace
);
522 /* in the list of attributes we are looking for, rename any
523 attributes to the alias for any hidden attributes that can
524 be fetched directly using non-hidden names */
525 for (a
=0;ac
->attrs
&& ac
->attrs
[a
];a
++) {
526 for (i
=0;i
<ARRAY_SIZE(search_sub
);i
++) {
527 if (ldb_attr_cmp(ac
->attrs
[a
], search_sub
[i
].attr
) == 0 &&
528 search_sub
[i
].replace
) {
530 if (search_sub
[i
].extra_attr
) {
531 const char **search_attrs2
;
532 /* Only adds to the end of the list */
533 search_attrs2
= ldb_attr_list_copy_add(req
, search_attrs
536 search_sub
[i
].extra_attr
);
537 if (search_attrs2
== NULL
) {
538 return LDB_ERR_OPERATIONS_ERROR
;
540 /* may be NULL, talloc_free() doesn't mind */
541 talloc_free(search_attrs
);
542 search_attrs
= search_attrs2
;
546 search_attrs
= ldb_attr_list_copy(req
, ac
->attrs
);
547 if (search_attrs
== NULL
) {
548 return LDB_ERR_OPERATIONS_ERROR
;
551 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
552 search_attrs
[a
] = search_sub
[i
].replace
;
557 /* remember if the SD_FLAGS_OID was set */
558 ac
->sd_flags_set
= (ldb_request_get_control(req
, LDB_CONTROL_SD_FLAGS_OID
) != NULL
);
560 ret
= ldb_build_search_req_ex(&down_req
, ldb
, ac
,
562 req
->op
.search
.scope
,
564 /* use new set of attrs if any */
565 search_attrs
== NULL
?req
->op
.search
.attrs
:search_attrs
,
567 ac
, operational_callback
,
569 if (ret
!= LDB_SUCCESS
) {
570 return LDB_ERR_OPERATIONS_ERROR
;
573 /* perform the search */
574 return ldb_next_request(module
, down_req
);
577 static int operational_init(struct ldb_module
*ctx
)
579 struct operational_data
*data
;
580 int ret
= ldb_next_init(ctx
);
582 if (ret
!= LDB_SUCCESS
) {
586 data
= talloc_zero(ctx
, struct operational_data
);
589 return LDB_ERR_OPERATIONS_ERROR
;
592 ldb_module_set_private(ctx
, data
);
597 const struct ldb_module_ops ldb_operational_module_ops
= {
598 .name
= "operational",
599 .search
= operational_search
,
600 .init_context
= operational_init