s4:dsdb Use replPropertyMetaData as the basis for msDS-KeyVersionNumber
[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 "librpc/gen_ndr/ndr_drsblobs.h"
72 #include "param/param.h"
73 #include "dsdb/samdb/samdb.h"
74 #include "dsdb/samdb/ldb_modules/util.h"
75
76 #include "auth/auth.h"
77 #include "libcli/security/dom_sid.h"
78
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
82
83 struct operational_data {
84         struct ldb_dn *aggregate_dn;
85 };
86
87 /*
88   construct a canonical name from a message
89 */
90 static int construct_canonical_name(struct ldb_module *module,
91         struct ldb_message *msg)
92 {
93         char *canonicalName;
94         canonicalName = ldb_dn_canonical_string(msg, msg->dn);
95         if (canonicalName == NULL) {
96                 return LDB_ERR_OPERATIONS_ERROR;
97         }
98         return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
99 }
100
101 /*
102   construct a primary group token for groups from a message
103 */
104 static int construct_primary_group_token(struct ldb_module *module,
105                                          struct ldb_message *msg)
106 {
107         struct ldb_context *ldb;
108         uint32_t primary_group_token;
109         
110         ldb = ldb_module_get_ctx(module);
111         if (ldb_match_msg_objectclass(msg, "group") == 1) {
112                 primary_group_token
113                         = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
114                 if (primary_group_token == 0) {
115                         return LDB_SUCCESS;
116                 }
117
118                 return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
119                         primary_group_token);
120         } else {
121                 return LDB_SUCCESS;
122         }
123 }
124
125 /*
126   construct the token groups for SAM objects from a message
127 */
128 static int construct_token_groups(struct ldb_module *module,
129                                   struct ldb_message *msg)
130 {
131         struct ldb_context *ldb;
132         const struct dom_sid *sid;
133
134         ldb = ldb_module_get_ctx(module);
135
136         sid = samdb_result_dom_sid(msg, msg, "objectSid");
137         if (sid != NULL) {
138                 NTSTATUS status;
139                 uint32_t prim_group_rid;
140                 struct dom_sid **sids = NULL;
141                 unsigned int i, num_sids = 0;
142                 int ret;
143
144                 prim_group_rid = samdb_result_uint(msg, "primaryGroupID", 0);
145                 if (prim_group_rid != 0) {
146                         struct dom_sid *prim_group_sid;
147
148                         prim_group_sid = dom_sid_add_rid(msg,
149                                                          samdb_domain_sid(ldb),
150                                                          prim_group_rid);
151                         if (prim_group_sid == NULL) {
152                                 ldb_oom(ldb);
153                                 return LDB_ERR_OPERATIONS_ERROR;
154                         }
155
156                         /* onlyChilds = false, we want to consider also the
157                          * "primaryGroupID" for membership */
158                         status = authsam_expand_nested_groups(ldb,
159                                                               prim_group_sid,
160                                                               false, msg,
161                                                               &sids, &num_sids);
162                         if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
163                                 ldb_oom(ldb);
164                                 return LDB_ERR_OPERATIONS_ERROR;
165                         }
166                         if (!NT_STATUS_IS_OK(status)) {
167                                 return LDB_ERR_OPERATIONS_ERROR;
168                         }
169
170                         for (i = 0; i < num_sids; i++) {
171                                 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
172                                                             "tokenGroups",
173                                                             sids[i]);
174                                 if (ret != LDB_SUCCESS) {
175                                         talloc_free(sids);
176                                         return ret;
177                                 }
178                         }
179
180                         talloc_free(sids);
181                 }
182
183                 sids = NULL;
184                 num_sids = 0;
185
186                 /* onlyChils = true, we don't want to have the SAM object itself
187                  * in the result */
188                 status = authsam_expand_nested_groups(ldb, sid, true, msg,
189                                                       &sids, &num_sids);
190                 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
191                         ldb_oom(ldb);
192                         return LDB_ERR_OPERATIONS_ERROR;
193                 }
194                 if (!NT_STATUS_IS_OK(status)) {
195                         return LDB_ERR_OPERATIONS_ERROR;
196                 }
197
198                 for (i = 0; i < num_sids; i++) {
199                         ret = samdb_msg_add_dom_sid(ldb, msg, msg,
200                                                     "tokenGroups", sids[i]);
201                         if (ret != LDB_SUCCESS) {
202                                 talloc_free(sids);
203                                 return ret;
204                         }
205                 }
206
207                 talloc_free(sids);
208         }
209
210         return LDB_SUCCESS;
211 }
212
213 /*
214   construct the parent GUID for an entry from a message
215 */
216 static int construct_parent_guid(struct ldb_module *module,
217                                  struct ldb_message *msg)
218 {
219         struct ldb_result *res;
220         const struct ldb_val *parent_guid;
221         const char *attrs[] = { "objectGUID", NULL };
222         int ret;
223         struct ldb_val v;
224
225         /* TODO:  In the future, this needs to honour the partition boundaries */
226         struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
227
228         if (parent_dn == NULL) {
229                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
230                                          ldb_dn_get_linearized(msg->dn)));
231                 return LDB_SUCCESS;
232         }
233
234         ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
235         talloc_free(parent_dn);
236         /* if there is no parentGUID for this object, then return */
237         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
238                 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
239                          ldb_dn_get_linearized(msg->dn)));
240                 return LDB_SUCCESS;
241         } else if (ret != LDB_SUCCESS) {
242                 return ret;
243         }
244
245         parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
246         if (!parent_guid) {
247                 talloc_free(res);
248                 return LDB_SUCCESS;
249         }
250
251         v = data_blob_dup_talloc(res, parent_guid);
252         if (!v.data) {
253                 talloc_free(res);
254                 return LDB_ERR_OPERATIONS_ERROR;
255         }
256         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
257         talloc_free(res);
258         return ret;
259 }
260
261 /*
262   construct a subSchemaSubEntry
263 */
264 static int construct_subschema_subentry(struct ldb_module *module,
265                                         struct ldb_message *msg)
266 {
267         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
268         char *subSchemaSubEntry;
269
270         /* We may be being called before the init function has finished */
271         if (!data) {
272                 return LDB_SUCCESS;
273         }
274
275         /* Try and set this value up, if possible.  Don't worry if it
276          * fails, we may not have the DB set up yet, and it's not
277          * really vital anyway */
278         if (!data->aggregate_dn) {
279                 struct ldb_context *ldb = ldb_module_get_ctx(module);
280                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
281         }
282
283         if (data->aggregate_dn) {
284                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
285                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
286         }
287         return LDB_SUCCESS;
288 }
289
290
291 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
292                                          struct ldb_message *msg,
293                                          struct ldb_message_element *object_category)
294 {
295         struct ldb_context *ldb;
296         struct ldb_dn *dn;
297         const struct ldb_val *val;
298
299         ldb = ldb_module_get_ctx(module);
300         if (!ldb) {
301                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
302                 return LDB_ERR_OPERATIONS_ERROR;
303         }
304
305         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
306         if (!dn) {
307                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
308                           (const char *)object_category->values[0].data));
309                 return LDB_ERR_OPERATIONS_ERROR;
310         }
311
312         val = ldb_dn_get_rdn_val(dn);
313         if (!val) {
314                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
315                           ldb_dn_get_linearized(dn)));
316                 return LDB_ERR_OPERATIONS_ERROR;
317         }
318
319         if (strequal((const char *)val->data, "NTDS-DSA")) {
320                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
321         } else {
322                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
323         }
324         return LDB_SUCCESS;
325 }
326
327 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
328                                                 struct ldb_message *msg,
329                                                 struct ldb_dn *dn)
330 {
331         struct ldb_dn *server_dn;
332         const char *attr_obj_cat[] = { "objectCategory", NULL };
333         struct ldb_result *res;
334         struct ldb_message_element *object_category;
335         int ret;
336
337         server_dn = ldb_dn_copy(msg, dn);
338         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
339                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
340                           ldb_dn_get_linearized(server_dn)));
341                 return LDB_ERR_OPERATIONS_ERROR;
342         }
343
344         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat, 0);
345         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
346                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
347                                          ldb_dn_get_linearized(server_dn)));
348                 return LDB_SUCCESS;
349         } else if (ret != LDB_SUCCESS) {
350                 return ret;
351         }
352
353         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
354         if (!object_category) {
355                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
356                          ldb_dn_get_linearized(res->msgs[0]->dn)));
357                 return LDB_SUCCESS;
358         }
359         return construct_msds_isrodc_with_dn(module, msg, object_category);
360 }
361
362 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
363                                                   struct ldb_message *msg)
364 {
365         struct ldb_context *ldb;
366         const char *attr[] = { "serverReferenceBL", NULL };
367         struct ldb_result *res;
368         int ret;
369         struct ldb_dn *server_dn;
370
371         ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr, 0);
372         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
373                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
374                          ldb_dn_get_linearized(msg->dn)));
375                 return LDB_SUCCESS;
376         } else if (ret != LDB_SUCCESS) {
377                 return ret;
378         }
379
380         ldb = ldb_module_get_ctx(module);
381         if (!ldb) {
382                 return LDB_SUCCESS;
383         }
384
385         server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
386         if (!server_dn) {
387                 DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
388                          ldb_dn_get_linearized(res->msgs[0]->dn)));
389                 return LDB_SUCCESS;
390         }
391         return construct_msds_isrodc_with_server_dn(module, msg, server_dn);
392 }
393
394 /*
395   construct msDS-isRODC attr
396 */
397 static int construct_msds_isrodc(struct ldb_module *module, struct ldb_message *msg)
398 {
399         struct ldb_message_element * object_class;
400         struct ldb_message_element * object_category;
401         unsigned int i;
402
403         object_class = ldb_msg_find_element(msg, "objectClass");
404         if (!object_class) {
405                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
406                          ldb_dn_get_linearized(msg->dn)));
407                 return LDB_ERR_OPERATIONS_ERROR;
408         }
409
410         for (i=0; i<object_class->num_values; i++) {
411                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
412                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
413                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
414                          */
415                         object_category = ldb_msg_find_element(msg, "objectCategory");
416                         if (!object_category) {
417                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
418                                          ldb_dn_get_linearized(msg->dn)));
419                                 return LDB_SUCCESS;
420                         }
421                         return construct_msds_isrodc_with_dn(module, msg, object_category);
422                 }
423                 if (strequal((const char*)object_class->values[i].data, "server")) {
424                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
425                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
426                          * substituting TN for TO.
427                          */
428                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
429                 }
430                 if (strequal((const char*)object_class->values[i].data, "computer")) {
431                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
432                          * rule for the "TO is a server  object" case, substituting TS for TO.
433                          */
434                         return construct_msds_isrodc_with_computer_dn(module, msg);
435                 }
436         }
437
438         return LDB_SUCCESS;
439 }
440
441
442 /*
443   construct msDS-keyVersionNumber attr
444
445   TODO:  Make this based on the 'win2k' DS huristics bit...
446
447 */
448 static int construct_msds_keyversionnumber(struct ldb_module *module, struct ldb_message *msg)
449 {
450         uint32_t i;
451         enum ndr_err_code ndr_err;
452         const struct ldb_val *omd_value;
453         struct replPropertyMetaDataBlob *omd;
454         struct ldb_context *ldb = ldb_module_get_ctx(module);
455
456         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
457         if (!omd_value) {
458                 /* We can't make up a key version number without meta data */
459                 return LDB_SUCCESS;
460         }
461         if (!omd_value) {
462                 return LDB_SUCCESS;
463         }
464
465         omd = talloc(msg, struct replPropertyMetaDataBlob);
466         if (!omd) {
467                 ldb_module_oom(module);
468                 return LDB_SUCCESS;
469         }
470
471         ndr_err = ndr_pull_struct_blob(omd_value, omd,
472                                        lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
473                                        omd,
474                                        (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
475         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
476                 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
477                          ldb_dn_get_linearized(msg->dn)));
478                 return LDB_ERR_OPERATIONS_ERROR;
479         }
480
481         if (omd->version != 1) {
482                 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
483                          omd->version, ldb_dn_get_linearized(msg->dn)));
484                 talloc_free(omd);
485                 return LDB_SUCCESS;
486         }
487         for (i=0; i<omd->ctr.ctr1.count; i++) {
488                 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTRIBUTE_unicodePwd) {
489                         ldb_msg_add_fmt(msg, "msDS-KeyVersionNumber", "%u", omd->ctr.ctr1.array[i].version);
490                         break;
491                 }
492         }
493         return LDB_SUCCESS;
494
495 }
496
497 /*
498   a list of attribute names that should be substituted in the parse
499   tree before the search is done
500 */
501 static const struct {
502         const char *attr;
503         const char *replace;
504 } parse_tree_sub[] = {
505         { "createTimestamp", "whenCreated" },
506         { "modifyTimestamp", "whenChanged" }
507 };
508
509
510 /*
511   a list of attribute names that are hidden, but can be searched for
512   using another (non-hidden) name to produce the correct result
513 */
514 static const struct {
515         const char *attr;
516         const char *replace;
517         const char *extra_attr;
518         int (*constructor)(struct ldb_module *, struct ldb_message *);
519 } search_sub[] = {
520         { "createTimestamp", "whenCreated", NULL , NULL },
521         { "modifyTimestamp", "whenChanged", NULL , NULL },
522         { "structuralObjectClass", "objectClass", NULL , NULL },
523         { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
524         { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
525         { "tokenGroups", "objectSid", "primaryGroupID", construct_token_groups },
526         { "parentGUID", NULL, NULL, construct_parent_guid },
527         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
528         { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
529         { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
530 };
531
532
533 enum op_remove {
534         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
535         OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
536         OPERATIONAL_SD_FLAGS       /* show if SD_FLAGS_OID set, or asked for */
537 };
538
539 /*
540   a list of attributes that may need to be removed from the
541   underlying db return
542
543   Some of these are attributes that were once stored, but are now calculated
544 */
545 static const struct {
546         const char *attr;
547         enum op_remove op;
548 } operational_remove[] = {
549         { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
550         { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_ALWAYS  },
551         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
552         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
553         { "unicodePwd",              OPERATIONAL_REMOVE_UNASKED },
554         { "dBCSPwd",                 OPERATIONAL_REMOVE_UNASKED },
555         { "ntPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
556         { "lmPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
557         { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
558 };
559
560
561 /*
562   post process a search result record. For any search_sub[] attributes that were
563   asked for, we need to call the appropriate copy routine to copy the result
564   into the message, then remove any attributes that we added to the search but
565   were not asked for by the user
566 */
567 static int operational_search_post_process(struct ldb_module *module,
568                                            struct ldb_message *msg,
569                                            const char * const *attrs_from_user,
570                                            const char * const *attrs_searched_for,
571                                            bool sd_flags_set)
572 {
573         struct ldb_context *ldb;
574         unsigned int i, a = 0;
575         bool constructed_attributes = false;
576
577         ldb = ldb_module_get_ctx(module);
578
579         /* removed any attrs that should not be shown to the user */
580         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
581                 switch (operational_remove[i].op) {
582                 case OPERATIONAL_REMOVE_UNASKED:
583                         if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
584                                 continue;
585                         }
586                         if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
587                                 continue;
588                         }
589                 case OPERATIONAL_REMOVE_ALWAYS:
590                         ldb_msg_remove_attr(msg, operational_remove[i].attr);
591                         break;
592                 case OPERATIONAL_SD_FLAGS:
593                         if (sd_flags_set ||
594                             ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
595                                 continue;
596                         }
597                         ldb_msg_remove_attr(msg, operational_remove[i].attr);
598                         break;
599                 }
600         }
601
602         for (a=0;attrs_from_user && attrs_from_user[a];a++) {
603                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
604                         if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
605                                 continue;
606                         }
607
608                         /* construct the new attribute, using either a supplied
609                            constructor or a simple copy */
610                         constructed_attributes = true;
611                         if (search_sub[i].constructor != NULL) {
612                                 if (search_sub[i].constructor(module, msg) != LDB_SUCCESS) {
613                                         goto failed;
614                                 }
615                         } else if (ldb_msg_copy_attr(msg,
616                                                      search_sub[i].replace,
617                                                      search_sub[i].attr) != LDB_SUCCESS) {
618                                 goto failed;
619                         }
620                 }
621         }
622
623         /* Deletion of the search helper attributes are needed if:
624          * - we generated constructed attributes and
625          * - we aren't requesting all attributes
626          */
627         if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
628                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
629                         /* remove the added search helper attributes, unless
630                          * they were asked for by the user */
631                         if (search_sub[i].replace != NULL && 
632                             !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
633                                 ldb_msg_remove_attr(msg, search_sub[i].replace);
634                         }
635                         if (search_sub[i].extra_attr != NULL && 
636                             !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
637                                 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
638                         }
639                 }
640         }
641
642         return 0;
643
644 failed:
645         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
646                       "operational_search_post_process failed for attribute '%s'",
647                       attrs_from_user[a]);
648         return -1;
649 }
650
651
652 /*
653   hook search operations
654 */
655
656 struct operational_context {
657         struct ldb_module *module;
658         struct ldb_request *req;
659
660         const char * const *attrs;
661         bool sd_flags_set;
662 };
663
664 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
665 {
666         struct operational_context *ac;
667         int ret;
668
669         ac = talloc_get_type(req->context, struct operational_context);
670
671         if (!ares) {
672                 return ldb_module_done(ac->req, NULL, NULL,
673                                         LDB_ERR_OPERATIONS_ERROR);
674         }
675         if (ares->error != LDB_SUCCESS) {
676                 return ldb_module_done(ac->req, ares->controls,
677                                         ares->response, ares->error);
678         }
679
680         switch (ares->type) {
681         case LDB_REPLY_ENTRY:
682                 /* for each record returned post-process to add any derived
683                    attributes that have been asked for */
684                 ret = operational_search_post_process(ac->module,
685                                                       ares->message,
686                                                       ac->attrs,
687                                                       req->op.search.attrs,
688                                                       ac->sd_flags_set);
689                 if (ret != 0) {
690                         return ldb_module_done(ac->req, NULL, NULL,
691                                                 LDB_ERR_OPERATIONS_ERROR);
692                 }
693                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
694
695         case LDB_REPLY_REFERRAL:
696                 return ldb_module_send_referral(ac->req, ares->referral);
697
698         case LDB_REPLY_DONE:
699
700                 return ldb_module_done(ac->req, ares->controls,
701                                         ares->response, LDB_SUCCESS);
702         }
703
704         talloc_free(ares);
705         return LDB_SUCCESS;
706 }
707
708 static int operational_search(struct ldb_module *module, struct ldb_request *req)
709 {
710         struct ldb_context *ldb;
711         struct operational_context *ac;
712         struct ldb_request *down_req;
713         const char **search_attrs = NULL;
714         unsigned int i, a;
715         int ret;
716
717         /* There are no operational attributes on special DNs */
718         if (ldb_dn_is_special(req->op.search.base)) {
719                 return ldb_next_request(module, req);
720         }
721
722         ldb = ldb_module_get_ctx(module);
723
724         ac = talloc(req, struct operational_context);
725         if (ac == NULL) {
726                 return LDB_ERR_OPERATIONS_ERROR;
727         }
728
729         ac->module = module;
730         ac->req = req;
731         ac->attrs = req->op.search.attrs;
732
733         /*  FIXME: We must copy the tree and keep the original
734          *  unmodified. SSS */
735         /* replace any attributes in the parse tree that are
736            searchable, but are stored using a different name in the
737            backend */
738         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
739                 ldb_parse_tree_attr_replace(req->op.search.tree,
740                                             parse_tree_sub[i].attr,
741                                             parse_tree_sub[i].replace);
742         }
743
744         /* in the list of attributes we are looking for, rename any
745            attributes to the alias for any hidden attributes that can
746            be fetched directly using non-hidden names */
747         for (a=0;ac->attrs && ac->attrs[a];a++) {
748                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
749                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
750                             search_sub[i].replace) {
751
752                                 if (search_sub[i].extra_attr) {
753                                         const char **search_attrs2;
754                                         /* Only adds to the end of the list */
755                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
756                                                                                ? search_attrs
757                                                                                : ac->attrs, 
758                                                                                search_sub[i].extra_attr);
759                                         if (search_attrs2 == NULL) {
760                                                 return LDB_ERR_OPERATIONS_ERROR;
761                                         }
762                                         /* may be NULL, talloc_free() doesn't mind */
763                                         talloc_free(search_attrs);
764                                         search_attrs = search_attrs2;
765                                 }
766
767                                 if (!search_attrs) {
768                                         search_attrs = ldb_attr_list_copy(req, ac->attrs);
769                                         if (search_attrs == NULL) {
770                                                 return LDB_ERR_OPERATIONS_ERROR;
771                                         }
772                                 }
773                                 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
774                                 search_attrs[a] = search_sub[i].replace;
775                         }
776                 }
777         }
778
779         /* remember if the SD_FLAGS_OID was set */
780         ac->sd_flags_set = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
781
782         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
783                                         req->op.search.base,
784                                         req->op.search.scope,
785                                         req->op.search.tree,
786                                         /* use new set of attrs if any */
787                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
788                                         req->controls,
789                                         ac, operational_callback,
790                                         req);
791         if (ret != LDB_SUCCESS) {
792                 return LDB_ERR_OPERATIONS_ERROR;
793         }
794
795         /* perform the search */
796         return ldb_next_request(module, down_req);
797 }
798
799 static int operational_init(struct ldb_module *ctx)
800 {
801         struct operational_data *data;
802         int ret = ldb_next_init(ctx);
803
804         if (ret != LDB_SUCCESS) {
805                 return ret;
806         }
807
808         data = talloc_zero(ctx, struct operational_data);
809         if (!data) {
810                 ldb_module_oom(ctx);
811                 return LDB_ERR_OPERATIONS_ERROR;
812         }
813
814         ldb_module_set_private(ctx, data);
815
816         return LDB_SUCCESS;
817 }
818
819 const struct ldb_module_ops ldb_operational_module_ops = {
820         .name              = "operational",
821         .search            = operational_search,
822         .init_context      = operational_init
823 };