Return per-entry controls in ldb_module_send_entry()
[abartlet/samba.git/.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_out.c
1 /* 
2    ldb database library
3
4    Copyright (C) Simo Sorce 2005-2008
5    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2008
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22  *  Name: ldb
23  *
24  *  Component: ldb extended dn control module
25  *
26  *  Description: this module builds a special dn for returned search
27  *  results 
28  *  values.
29  *
30  *  Authors: Simo Sorce
31  *           Andrew Bartlett
32  */
33
34 #include "includes.h"
35 #include "ldb/include/ldb.h"
36 #include "ldb/include/ldb_errors.h"
37 #include "ldb/include/ldb_private.h"
38 #include "librpc/gen_ndr/ndr_misc.h"
39 #include "librpc/ndr/libndr.h"
40 #include "dsdb/samdb/samdb.h"
41
42 struct extended_dn_out_private {
43         bool dereference;
44         struct dsdb_openldap_dereference_control *dereference_control;
45 };
46
47 static bool is_attr_in_list(const char * const * attrs, const char *attr)
48 {
49         int i;
50
51         for (i = 0; attrs[i]; i++) {
52                 if (ldb_attr_cmp(attrs[i], attr) == 0)
53                         return true;
54         }
55
56         return false;
57 }
58
59 static char **copy_attrs(void *mem_ctx, const char * const * attrs)
60 {
61         char **new;
62         int i, num;
63
64         for (num = 0; attrs[num]; num++);
65
66         new = talloc_array(mem_ctx, char *, num + 1);
67         if (!new) return NULL;
68
69         for(i = 0; i < num; i++) {
70                 new[i] = talloc_strdup(new, attrs[i]);
71                 if (!new[i]) {
72                         talloc_free(new);
73                         return NULL;
74                 }
75         }
76         new[i] = NULL;
77
78         return new;
79 }
80
81 static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr)
82 {
83         char **new;
84         int num;
85
86         for (num = 0; (*attrs)[num]; num++);
87
88         new = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
89         if (!new) return false;
90
91         *attrs = new;
92
93         new[num] = talloc_strdup(new, attr);
94         if (!new[num]) return false;
95
96         new[num + 1] = NULL;
97
98         return true;
99 }
100
101 static int inject_extended_dn_out(struct ldb_reply *ares,
102                                   struct ldb_context *ldb,
103                                   int type,
104                                   bool remove_guid,
105                                   bool remove_sid)
106 {
107         int ret;
108         const struct ldb_val *val;
109         const DATA_BLOB *guid_blob;
110         const DATA_BLOB *sid_blob;
111
112         guid_blob = ldb_msg_find_ldb_val(ares->message, "objectGUID");
113         sid_blob = ldb_msg_find_ldb_val(ares->message, "objectSID");
114
115         if (!guid_blob) {
116                 ldb_set_errstring(ldb, "Did not find objectGUID to inject into extended DN");
117                 return LDB_ERR_OPERATIONS_ERROR;
118         }
119
120         ret = ldb_dn_set_extended_component(ares->message->dn, "GUID", guid_blob);
121         if (ret != LDB_SUCCESS) {
122                 return ret;
123         }
124         if (sid_blob) {
125                 ret = ldb_dn_set_extended_component(ares->message->dn, "SID", sid_blob);
126                 if (ret != LDB_SUCCESS) {
127                         return ret;
128                 }
129         }
130
131         if (remove_guid) {
132                 ldb_msg_remove_attr(ares->message, "objectGUID");
133         }
134
135         if (sid_blob && remove_sid) {
136                 ldb_msg_remove_attr(ares->message, "objectSID");
137         }
138
139         val = ldb_msg_find_ldb_val(ares->message, "distinguishedName");
140         if (val) {
141                 ldb_msg_remove_attr(ares->message, "distinguishedName");
142                 ret = ldb_msg_add_steal_string(ares->message, "distinguishedName", 
143                                                ldb_dn_get_extended_linearized(ares->message, ares->message->dn, type));
144                 if (ret != LDB_SUCCESS) {
145                         ldb_oom(ldb);
146                         return LDB_ERR_OPERATIONS_ERROR;
147                 }
148         }
149         return LDB_SUCCESS;
150 }
151
152 static int handle_dereference(struct ldb_dn *dn,
153                               struct dsdb_openldap_dereference_result **dereference_attrs, 
154                               const char *attr, const DATA_BLOB *val)
155 {
156         const struct ldb_val *entryUUIDblob, *sid_blob;
157         struct ldb_message fake_msg; /* easier to use routines that expect an ldb_message */
158         int j;
159         
160         fake_msg.num_elements = 0;
161                         
162         /* Look for this attribute in the returned control */
163         for (j = 0; dereference_attrs && dereference_attrs[j]; j++) {
164                 DATA_BLOB source_dn = data_blob_string_const(dereference_attrs[j]->dereferenced_dn);
165                 if (ldb_attr_cmp(dereference_attrs[j]->source_attribute, attr)
166                     && data_blob_cmp(&source_dn, val) == 0) {
167                         
168                         fake_msg.num_elements = dereference_attrs[j]->num_attributes;
169                         fake_msg.elements = dereference_attrs[j]->attributes;
170                         break;
171                 }
172         }
173         if (!fake_msg.num_elements) {
174                 return LDB_SUCCESS;
175         }
176         /* Look for an OpenLDAP entryUUID */
177         
178         entryUUIDblob = ldb_msg_find_ldb_val(&fake_msg, "entryUUID");
179         if (entryUUIDblob) {
180                 NTSTATUS status;
181                 enum ndr_err_code ndr_err;
182                 
183                 struct ldb_val guid_blob;
184                 struct GUID guid;
185                 
186                 status = GUID_from_data_blob(entryUUIDblob, &guid);
187                 
188                 if (!NT_STATUS_IS_OK(status)) {
189                         return LDB_ERR_INVALID_DN_SYNTAX;
190                 }
191                 ndr_err = ndr_push_struct_blob(&guid_blob, NULL, NULL, &guid,
192                                                (ndr_push_flags_fn_t)ndr_push_GUID);
193                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
194                         return LDB_ERR_INVALID_DN_SYNTAX;
195                 }
196                 
197                 ldb_dn_set_extended_component(dn, "GUID", &guid_blob);
198         }
199         
200         sid_blob = ldb_msg_find_ldb_val(&fake_msg, "objectSID");
201         
202         /* Look for the objectSID */
203         if (sid_blob) {
204                 ldb_dn_set_extended_component(dn, "SID", sid_blob);
205         }
206         return LDB_SUCCESS;
207 }
208
209 /* search */
210 struct extended_search_context {
211         struct ldb_module *module;
212         const struct dsdb_schema *schema;
213         struct ldb_request *req;
214         bool inject;
215         bool remove_guid;
216         bool remove_sid;
217         int extended_type;
218 };
219
220 static int extended_callback(struct ldb_request *req, struct ldb_reply *ares)
221 {
222         struct extended_search_context *ac;
223         struct ldb_control *control;
224         struct dsdb_openldap_dereference_result_control *dereference_control = NULL;
225         int ret, i, j;
226         struct ldb_message *msg = ares->message;
227         struct extended_dn_out_private *private;
228
229         ac = talloc_get_type(req->context, struct extended_search_context);
230         private = talloc_get_type(ac->module->private_data, struct extended_dn_out_private);
231
232         if (!ares) {
233                 return ldb_module_done(ac->req, NULL, NULL,
234                                         LDB_ERR_OPERATIONS_ERROR);
235         }
236         if (ares->error != LDB_SUCCESS) {
237                 return ldb_module_done(ac->req, ares->controls,
238                                         ares->response, ares->error);
239         }
240
241         switch (ares->type) {
242         case LDB_REPLY_REFERRAL:
243                 return ldb_module_send_referral(ac->req, ares->referral);
244
245         case LDB_REPLY_DONE:
246                 return ldb_module_done(ac->req, ares->controls,
247                                         ares->response, LDB_SUCCESS);
248         case LDB_REPLY_ENTRY:
249                 break;
250         }
251
252         if (ac->inject) {
253                 /* for each record returned post-process to add any derived
254                    attributes that have been asked for */
255                 ret = inject_extended_dn_out(ares, ac->module->ldb,
256                                              ac->extended_type, ac->remove_guid,
257                                              ac->remove_sid);
258                 if (ret != LDB_SUCCESS) {
259                         return ldb_module_done(ac->req, NULL, NULL, ret);
260                 }
261         }
262
263         if (private && private->dereference) {
264                 control = ldb_reply_get_control(ares, DSDB_OPENLDAP_DEREFERENCE_CONTROL);
265         
266                 if (control && control->data) {
267                         dereference_control = talloc_get_type(control->data, struct dsdb_openldap_dereference_result_control);
268                 }
269         }
270
271         /* Walk the retruned elements (but only if we have a schema to interpret the list with) */
272         for (i = 0; ac->schema && i < msg->num_elements; i++) {
273                 const struct dsdb_attribute *attribute;
274                 /* distinguishedName has been dealt with above */
275                 if (ldb_attr_cmp(msg->elements[i].name, "distinguishedName") == 0) {
276                         continue;
277                 }
278                 attribute = dsdb_attribute_by_lDAPDisplayName(ac->schema, msg->elements[i].name);
279                 if (!attribute) {
280                         continue;
281                 }
282                 /* Look to see if this attributeSyntax is a DN */
283                 if (strcmp(attribute->attributeSyntax_oid, "2.5.5.1") != 0) {
284                         continue;
285                 }
286
287                 for (j = 0; j < msg->elements[i].num_values; j++) {
288                         const char *dn_str;
289                         struct ldb_dn *dn = ldb_dn_from_ldb_val(ac, ac->module->ldb, &msg->elements[i].values[j]);
290                         if (!dn || !ldb_dn_validate(dn)) {
291                                 return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX);
292                         }
293
294                         /* If we are running in dereference mode (such
295                          * as against OpenLDAP) then the DN in the msg
296                          * above does not contain the extended values,
297                          * and we need to look in the dereference
298                          * result */
299
300                         /* Look for this value in the attribute */
301
302                         if (dereference_control) {
303                                 ret = handle_dereference(dn, 
304                                                          dereference_control->attributes,
305                                                          msg->elements[i].name,
306                                                          &msg->elements[i].values[j]);
307                                 if (ret != LDB_SUCCESS) {
308                                         
309                                         return ldb_module_done(ac->req, NULL, NULL, ret);
310                                 }
311                         }
312
313                         if (!ac->inject) {
314                                 dn_str = talloc_steal(msg->elements[i].values, 
315                                                       ldb_dn_get_linearized(dn));
316                         } else {
317                                 dn_str = talloc_steal(msg->elements[i].values, 
318                                                       ldb_dn_get_extended_linearized(msg->elements[i].values, 
319                                                                                      dn, ac->extended_type));
320                         }
321                         msg->elements[i].values[j] = data_blob_string_const(dn_str);
322                         talloc_free(dn);
323                 }
324         }
325         return ldb_module_send_entry(ac->req, msg, ares->controls);
326 }
327
328
329 static int extended_dn_out_search(struct ldb_module *module, struct ldb_request *req)
330 {
331         struct ldb_control *control;
332         struct ldb_control *storage_format_control;
333         struct ldb_extended_dn_control *extended_ctrl = NULL;
334         struct ldb_control **saved_controls;
335         struct extended_search_context *ac;
336         struct ldb_request *down_req;
337         char **new_attrs;
338         const char * const *const_attrs;
339         int ret;
340
341         struct extended_dn_out_private *private = talloc_get_type(module->private_data, struct extended_dn_out_private);
342
343         /* check if there's an extended dn control */
344         control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
345         if (control && control->data) {
346                 extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
347                 if (!extended_ctrl) {
348                         return LDB_ERR_PROTOCOL_ERROR;
349                 }
350         }
351
352         /* Look to see if, as we are in 'store DN+GUID+SID' mode, the
353          * client is after the storage format (to fill in linked
354          * attributes) */
355         storage_format_control = ldb_request_get_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID);
356         if (!control && storage_format_control && storage_format_control->data) {
357                 extended_ctrl = talloc_get_type(storage_format_control->data, struct ldb_extended_dn_control);
358                 if (!extended_ctrl) {
359                         ldb_set_errstring(module->ldb, "extended_dn_out: extended_ctrl was of the wrong data type");
360                         return LDB_ERR_PROTOCOL_ERROR;
361                 }
362         }
363
364         ac = talloc_zero(req, struct extended_search_context);
365         if (ac == NULL) {
366                 ldb_oom(module->ldb);
367                 return LDB_ERR_OPERATIONS_ERROR;
368         }
369
370         ac->module = module;
371         ac->schema = dsdb_get_schema(module->ldb);
372         ac->req = req;
373         ac->inject = false;
374         ac->remove_guid = false;
375         ac->remove_sid = false;
376         
377         const_attrs = req->op.search.attrs;
378
379         /* We only need to do special processing if we were asked for
380          * the extended DN, or we are 'store DN+GUID+SID'
381          * (!dereference) mode.  (This is the normal mode for LDB on
382          * tdb). */
383         if (control || (storage_format_control && private && !private->dereference)) {
384                 ac->inject = true;
385                 if (extended_ctrl) {
386                         ac->extended_type = extended_ctrl->type;
387                 } else {
388                         ac->extended_type = 0;
389                 }
390
391                 /* check if attrs only is specified, in that case check wether we need to modify them */
392                 if (req->op.search.attrs && !is_attr_in_list(req->op.search.attrs, "*")) {
393                         if (! is_attr_in_list(req->op.search.attrs, "objectGUID")) {
394                                 ac->remove_guid = true;
395                         }
396                         if (! is_attr_in_list(req->op.search.attrs, "objectSID")) {
397                                 ac->remove_sid = true;
398                         }
399                         if (ac->remove_guid || ac->remove_sid) {
400                                 new_attrs = copy_attrs(ac, req->op.search.attrs);
401                                 if (new_attrs == NULL) {
402                                         ldb_oom(module->ldb);
403                                         return LDB_ERR_OPERATIONS_ERROR;
404                                 }
405
406                                 if (ac->remove_guid) {
407                                         if (!add_attrs(ac, &new_attrs, "objectGUID"))
408                                                 return LDB_ERR_OPERATIONS_ERROR;
409                                 }
410                                 if (ac->remove_sid) {
411                                         if (!add_attrs(ac, &new_attrs, "objectSID"))
412                                                 return LDB_ERR_OPERATIONS_ERROR;
413                                 }
414                                 const_attrs = (const char * const *)new_attrs;
415                         }
416                 }
417         }
418
419         ret = ldb_build_search_req_ex(&down_req,
420                                       module->ldb, ac,
421                                       req->op.search.base,
422                                       req->op.search.scope,
423                                       req->op.search.tree,
424                                       const_attrs,
425                                       req->controls,
426                                       ac, extended_callback,
427                                       req);
428         if (ret != LDB_SUCCESS) {
429                 return ret;
430         }
431
432         /* Remove extended DN and storage format controls */
433
434         if (control) {
435                 /* save it locally and remove it from the list */
436                 /* we do not need to replace them later as we
437                  * are keeping the original req intact */
438                 if (!save_controls(control, down_req, &saved_controls)) {
439                         return LDB_ERR_OPERATIONS_ERROR;
440                 }
441         }
442
443         if (storage_format_control) {
444                 /* save it locally and remove it from the list */
445                 /* we do not need to replace them later as we
446                  * are keeping the original req intact */
447                 if (!save_controls(storage_format_control, down_req, &saved_controls)) {
448                         return LDB_ERR_OPERATIONS_ERROR;
449                 }
450         }
451
452         /* Add in dereference control, if we were asked to, we are
453          * using the 'dereference' mode (such as with an OpenLDAP
454          * backend) and have the control prepared */
455         if (control && private && private->dereference && private->dereference_control) {
456                 ret = ldb_request_add_control(down_req,
457                                               DSDB_OPENLDAP_DEREFERENCE_CONTROL,
458                                               false, private->dereference_control);
459                 if (ret != LDB_SUCCESS) {
460                         return ret;
461                 }
462         }
463
464         /* perform the search */
465         return ldb_next_request(module, down_req);
466 }
467
468 static int extended_dn_out_ldb_init(struct ldb_module *module)
469 {
470         int ret;
471
472         struct extended_dn_out_private *private = talloc(module, struct extended_dn_out_private);
473
474         module->private_data = private;
475
476         if (!private) {
477                 ldb_oom(module->ldb);
478                 return LDB_ERR_OPERATIONS_ERROR;
479         }
480
481         private->dereference = false;
482
483         ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID);
484         if (ret != LDB_SUCCESS) {
485                 ldb_debug(module->ldb, LDB_DEBUG_ERROR,
486                         "extended_dn_out: Unable to register control with rootdse!\n");
487                 return LDB_ERR_OPERATIONS_ERROR;
488         }
489
490         return ldb_next_init(module);
491 }
492
493 static int extended_dn_out_dereference_init(struct ldb_module *module)
494 {
495         int ret, i = 0;
496         struct extended_dn_out_private *private;
497         struct dsdb_openldap_dereference_control *dereference_control;
498         struct dsdb_attribute *cur;
499
500         struct dsdb_schema *schema;
501
502         module->private_data = private = talloc_zero(module, struct extended_dn_out_private);
503
504         if (!private) {
505                 ldb_oom(module->ldb);
506                 return LDB_ERR_OPERATIONS_ERROR;
507         }
508
509         private->dereference = true;
510
511         ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID);
512         if (ret != LDB_SUCCESS) {
513                 ldb_debug(module->ldb, LDB_DEBUG_ERROR,
514                         "extended_dn_out: Unable to register control with rootdse!\n");
515                 return LDB_ERR_OPERATIONS_ERROR;
516         }
517
518         ret = ldb_next_init(module);
519
520         if (ret != LDB_SUCCESS) {
521                 return ret;
522         }
523
524         schema = dsdb_get_schema(module->ldb);
525         if (!schema) {
526                 /* No schema on this DB (yet) */
527                 return LDB_SUCCESS;
528         }
529
530         private->dereference_control = dereference_control
531                 = talloc_zero(private, struct dsdb_openldap_dereference_control);
532
533         if (!private->dereference_control) {
534                 ldb_oom(module->ldb);
535                 return LDB_ERR_OPERATIONS_ERROR;
536         }
537         
538         for (cur = schema->attributes; cur; cur = cur->next) {
539                 static const char *attrs[] = {
540                         "entryUUID",
541                         "objectSID",
542                         NULL
543                 };
544
545                 if (strcmp(cur->syntax->attributeSyntax_oid, "2.5.5.1") != 0) {
546                         continue;
547                 }
548                 dereference_control->dereference
549                         = talloc_realloc(private, dereference_control->dereference, 
550                                          struct dsdb_openldap_dereference *, i + 2);
551                 if (!dereference_control) {
552                         ldb_oom(module->ldb);
553                         return LDB_ERR_OPERATIONS_ERROR;
554                 }
555                 dereference_control->dereference[i] = talloc(dereference_control->dereference,  
556                                          struct dsdb_openldap_dereference);
557                 if (!dereference_control->dereference[i]) {
558                         ldb_oom(module->ldb);
559                         return LDB_ERR_OPERATIONS_ERROR;
560                 }
561                 dereference_control->dereference[i]->source_attribute = cur->lDAPDisplayName;
562                 dereference_control->dereference[i]->dereference_attribute = attrs;
563                 i++;
564                 dereference_control->dereference[i] = NULL;
565         }
566         return LDB_SUCCESS;
567 }
568
569 _PUBLIC_ const struct ldb_module_ops ldb_extended_dn_out_ldb_module_ops = {
570         .name              = "extended_dn_out_ldb",
571         .search            = extended_dn_out_search,
572         .init_context      = extended_dn_out_ldb_init,
573 };
574
575
576 _PUBLIC_ const struct ldb_module_ops ldb_extended_dn_out_dereference_module_ops = {
577         .name              = "extended_dn_out_dereference",
578         .search            = extended_dn_out_search,
579         .init_context      = extended_dn_out_dereference_init,
580 };