s4:operational LDB module - cosmetic - reorder an attribute list
[ira/wip.git] / source4 / dsdb / samdb / ldb_modules / operational.c
1 /*
2    ldb database library
3
4    Copyright (C) Andrew Tridgell 2005
5    Copyright (C) Simo Sorce 2006-2008
6    Copyright (C) Matthias Dieter Wallnöfer 2009
7
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.
12    
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.
17    
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/>.
20 */
21
22 /*
23   handle operational attributes
24  */
25
26 /*
27   createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
28   modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
29
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
34
35      we also need to replace these with the whenCreated/whenChanged
36      equivalent in the search expression trees
37
38   whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
39   whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40
41      on init we need to setup attribute handlers for these so
42      comparisons are done correctly. The resolution is 1 second.
43
44      on add we need to add both the above, for current time
45
46      on modify we need to change whenChanged
47
48   structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
49
50      for this one we do the search as normal, then if requested ask
51      for objectclass, change the attribute name, and add it
52
53   primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
54
55      contains the RID of a certain group object
56     
57
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?
64 */
65
66 #include "includes.h"
67 #include "ldb_includes.h"
68 #include "ldb_module.h"
69
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"
74
75 #ifndef ARRAY_SIZE
76 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
77 #endif
78
79 struct operational_data {
80         struct ldb_dn *aggregate_dn;
81 };
82
83 /*
84   construct a canonical name from a message
85 */
86 static int construct_canonical_name(struct ldb_module *module,
87         struct ldb_message *msg)
88 {
89         char *canonicalName;
90         canonicalName = ldb_dn_canonical_string(msg, msg->dn);
91         if (canonicalName == NULL) {
92                 return LDB_ERR_OPERATIONS_ERROR;
93         }
94         return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
95 }
96
97 /*
98   construct a primary group token for groups from a message
99 */
100 static int construct_primary_group_token(struct ldb_module *module,
101                                          struct ldb_message *msg)
102 {
103         struct ldb_context *ldb;
104         uint32_t primary_group_token;
105         
106         ldb = ldb_module_get_ctx(module);
107         if (ldb_match_msg_objectclass(msg, "group") == 1) {
108                 primary_group_token
109                         = samdb_result_rid_from_sid(ldb, msg, "objectSid", 0);
110                 if (primary_group_token == 0) {
111                         return LDB_SUCCESS;
112                 }
113
114                 return samdb_msg_add_int(ldb, ldb, msg, "primaryGroupToken",
115                         primary_group_token);
116         } else {
117                 return LDB_SUCCESS;
118         }
119 }
120
121 static int construct_parent_guid(struct ldb_module *module,
122                                  struct ldb_message *msg)
123 {
124         struct ldb_result *res;
125         const struct ldb_val *parent_guid;
126         const char *attrs[] = { "objectGUID", NULL };
127         int ret;
128         struct ldb_val v;
129
130         /* TODO:  In the future, this needs to honour the partition boundaries */
131         struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
132
133         if (parent_dn == NULL) {
134                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
135                                          ldb_dn_get_linearized(msg->dn)));
136                 return LDB_SUCCESS;
137         }
138
139         ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
140         talloc_free(parent_dn);
141         /* if there is no parentGUID for this object, then return */
142         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
143                 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
144                          ldb_dn_get_linearized(msg->dn)));
145                 return LDB_SUCCESS;
146         } else if (ret != LDB_SUCCESS) {
147                 return ret;
148         }
149
150         parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
151         if (!parent_guid) {
152                 talloc_free(res);
153                 return LDB_SUCCESS;
154         }
155
156         v = data_blob_dup_talloc(res, parent_guid);
157         if (!v.data) {
158                 talloc_free(res);
159                 return LDB_ERR_OPERATIONS_ERROR;
160         }
161         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
162         talloc_free(res);
163         return ret;
164 }
165
166 /*
167   construct a subSchemaSubEntry
168 */
169 static int construct_subschema_subentry(struct ldb_module *module,
170                                         struct ldb_message *msg)
171 {
172         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
173         char *subSchemaSubEntry;
174         if (data && data->aggregate_dn) {
175                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
176                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
177         }
178         return LDB_SUCCESS;
179 }
180
181
182 /*
183   a list of attribute names that should be substituted in the parse
184   tree before the search is done
185 */
186 static const struct {
187         const char *attr;
188         const char *replace;
189 } parse_tree_sub[] = {
190         { "createTimestamp", "whenCreated" },
191         { "modifyTimestamp", "whenChanged" }
192 };
193
194
195 /*
196   a list of attribute names that are hidden, but can be searched for
197   using another (non-hidden) name to produce the correct result
198 */
199 static const struct {
200         const char *attr;
201         const char *replace;
202         const char *extra_attr;
203         int (*constructor)(struct ldb_module *, struct ldb_message *);
204 } search_sub[] = {
205         { "createTimestamp", "whenCreated", NULL , NULL },
206         { "modifyTimestamp", "whenChanged", NULL , NULL },
207         { "structuralObjectClass", "objectClass", NULL , NULL },
208         { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
209         { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
210         { "parentGUID", NULL, NULL, construct_parent_guid },
211         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry }
212 };
213
214
215 enum op_remove {
216         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
217         OPERATIONAL_REMOVE_UNASKED /* remove if not requested */
218 };
219
220 /*
221   a list of attributes that may need to be removed from the
222   underlying db return
223 */
224 static const struct {
225         const char *attr;
226         enum op_remove op;
227 } operational_remove[] = {
228         { "nTSecurityDescriptor",    OPERATIONAL_REMOVE_UNASKED },
229         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
230         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
231         { "unicodePwd",              OPERATIONAL_REMOVE_UNASKED },
232         { "dBCSPwd",                 OPERATIONAL_REMOVE_UNASKED },
233         { "ntPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
234         { "lmPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
235         { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
236 };
237
238
239 /*
240   post process a search result record. For any search_sub[] attributes that were
241   asked for, we need to call the appropriate copy routine to copy the result
242   into the message, then remove any attributes that we added to the search but
243   were not asked for by the user
244 */
245 static int operational_search_post_process(struct ldb_module *module,
246                                            struct ldb_message *msg,
247                                            const char * const *attrs)
248 {
249         struct ldb_context *ldb;
250         int i, a=0;
251
252         ldb = ldb_module_get_ctx(module);
253
254         /* removed any attrs that should not be shown to the user */
255         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
256                 struct ldb_message_element *el;
257
258                 switch (operational_remove[i].op) {
259                 case OPERATIONAL_REMOVE_UNASKED:
260                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
261                                 continue;
262                         }
263                 case OPERATIONAL_REMOVE_ALWAYS:
264                         el = ldb_msg_find_element(msg, operational_remove[i].attr);
265                         if (el) {
266                                 ldb_msg_remove_element(msg, el);
267                         }
268                         break;
269                 }
270         }
271
272         for (a=0;attrs && attrs[a];a++) {
273                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
274                         if (ldb_attr_cmp(attrs[a], search_sub[i].attr) != 0) {
275                                 continue;
276                         }
277
278                         /* construct the new attribute, using either a supplied
279                            constructor or a simple copy */
280                         if (search_sub[i].constructor != NULL) {
281                                 if (search_sub[i].constructor(module, msg) != LDB_SUCCESS) {
282                                         goto failed;
283                                 }
284                         } else if (ldb_msg_copy_attr(msg,
285                                                      search_sub[i].replace,
286                                                      search_sub[i].attr) != LDB_SUCCESS) {
287                                 goto failed;
288                         }
289
290                         /* remove the added search attribute, unless it was
291                            asked for by the user */
292                         if (search_sub[i].replace != NULL && 
293                             !ldb_attr_in_list(attrs, search_sub[i].replace) &&
294                             !ldb_attr_in_list(attrs, "*")) {
295                                 ldb_msg_remove_attr(msg, search_sub[i].replace);
296                         }
297                         if (search_sub[i].extra_attr != NULL && 
298                             !ldb_attr_in_list(attrs, search_sub[i].extra_attr) &&
299                             !ldb_attr_in_list(attrs, "*")) {
300                                 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
301                         }
302                 }
303         }
304
305         return 0;
306
307 failed:
308         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
309                       "operational_search_post_process failed for attribute '%s'",
310                       attrs[a]);
311         return -1;
312 }
313
314
315 /*
316   hook search operations
317 */
318
319 struct operational_context {
320         struct ldb_module *module;
321         struct ldb_request *req;
322
323         const char * const *attrs;
324 };
325
326 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
327 {
328         struct operational_context *ac;
329         int ret;
330
331         ac = talloc_get_type(req->context, struct operational_context);
332
333         if (!ares) {
334                 return ldb_module_done(ac->req, NULL, NULL,
335                                         LDB_ERR_OPERATIONS_ERROR);
336         }
337         if (ares->error != LDB_SUCCESS) {
338                 return ldb_module_done(ac->req, ares->controls,
339                                         ares->response, ares->error);
340         }
341
342         switch (ares->type) {
343         case LDB_REPLY_ENTRY:
344                 /* for each record returned post-process to add any derived
345                    attributes that have been asked for */
346                 ret = operational_search_post_process(ac->module,
347                                                         ares->message,
348                                                         ac->attrs);
349                 if (ret != 0) {
350                         return ldb_module_done(ac->req, NULL, NULL,
351                                                 LDB_ERR_OPERATIONS_ERROR);
352                 }
353                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
354
355         case LDB_REPLY_REFERRAL:
356                 /* ignore referrals */
357                 break;
358
359         case LDB_REPLY_DONE:
360
361                 return ldb_module_done(ac->req, ares->controls,
362                                         ares->response, LDB_SUCCESS);
363         }
364
365         talloc_free(ares);
366         return LDB_SUCCESS;
367 }
368
369 static int operational_search(struct ldb_module *module, struct ldb_request *req)
370 {
371         struct ldb_context *ldb;
372         struct operational_context *ac;
373         struct ldb_request *down_req;
374         const char **search_attrs = NULL;
375         int i, a;
376         int ret;
377
378         ldb = ldb_module_get_ctx(module);
379
380         ac = talloc(req, struct operational_context);
381         if (ac == NULL) {
382                 return LDB_ERR_OPERATIONS_ERROR;
383         }
384
385         ac->module = module;
386         ac->req = req;
387         ac->attrs = req->op.search.attrs;
388
389         /*  FIXME: We must copy the tree and keep the original
390          *  unmodified. SSS */
391         /* replace any attributes in the parse tree that are
392            searchable, but are stored using a different name in the
393            backend */
394         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
395                 ldb_parse_tree_attr_replace(req->op.search.tree,
396                                             parse_tree_sub[i].attr,
397                                             parse_tree_sub[i].replace);
398         }
399
400         /* in the list of attributes we are looking for, rename any
401            attributes to the alias for any hidden attributes that can
402            be fetched directly using non-hidden names */
403         for (a=0;ac->attrs && ac->attrs[a];a++) {
404                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
405                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
406                             search_sub[i].replace) {
407
408                                 if (search_sub[i].extra_attr) {
409                                         const char **search_attrs2;
410                                         /* Only adds to the end of the list */
411                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
412                                                                                ? search_attrs
413                                                                                : ac->attrs, 
414                                                                                search_sub[i].extra_attr);
415                                         if (search_attrs2 == NULL) {
416                                                 return LDB_ERR_OPERATIONS_ERROR;
417                                         }
418                                         /* may be NULL, talloc_free() doesn't mind */
419                                         talloc_free(search_attrs);
420                                         search_attrs = search_attrs2;
421                                 }
422
423                                 if (!search_attrs) {
424                                         search_attrs = ldb_attr_list_copy(req, ac->attrs);
425                                         if (search_attrs == NULL) {
426                                                 return LDB_ERR_OPERATIONS_ERROR;
427                                         }
428                                 }
429                                 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
430                                 search_attrs[a] = search_sub[i].replace;
431                         }
432                 }
433         }
434
435         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
436                                         req->op.search.base,
437                                         req->op.search.scope,
438                                         req->op.search.tree,
439                                         /* use new set of attrs if any */
440                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
441                                         req->controls,
442                                         ac, operational_callback,
443                                         req);
444         if (ret != LDB_SUCCESS) {
445                 return LDB_ERR_OPERATIONS_ERROR;
446         }
447
448         /* perform the search */
449         return ldb_next_request(module, down_req);
450 }
451
452 static int operational_init(struct ldb_module *ctx)
453 {
454         struct operational_data *data;
455         struct ldb_context *ldb = ldb_module_get_ctx(ctx);
456         int ret = ldb_next_init(ctx);
457
458         if (ret != LDB_SUCCESS) {
459                 return ret;
460         }
461
462         data = talloc(ctx, struct operational_data);
463         if (!data) {
464                 ldb_module_oom(ctx);
465                 return LDB_ERR_OPERATIONS_ERROR;
466         }
467
468         data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
469         if (!data->aggregate_dn) {
470                 ldb_set_errstring(ldb, "Could not build aggregate schema DN");
471                 return LDB_ERR_OPERATIONS_ERROR;
472         }
473
474         ldb_module_set_private(ctx, data);
475
476         return LDB_SUCCESS;
477 }
478
479 const struct ldb_module_ops ldb_operational_module_ops = {
480         .name              = "operational",
481         .search            = operational_search,
482         .init_context      = operational_init
483 };