Return per-entry controls in ldb_module_send_entry()
[abartlet/samba.git/.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_store.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 nad creates the special DN in the backend store for new
28  *  values.
29  *
30  *  This also has the curious result that we convert <SID=S-1-2-345>
31  *  in an attribute value into a normal DN for the rest of the stack
32  *  to process
33  *
34  *  Authors: Simo Sorce
35  *           Andrew Bartlett
36  */
37
38 #include "includes.h"
39 #include "ldb/include/ldb.h"
40 #include "ldb/include/ldb_errors.h"
41 #include "ldb/include/ldb_private.h"
42 #include "librpc/gen_ndr/ndr_misc.h"
43 #include "dsdb/samdb/samdb.h"
44 #include "libcli/security/security.h"
45
46 #include <time.h>
47
48 struct extended_dn_replace_list {
49         struct extended_dn_replace_list *next;
50         struct ldb_dn *dn;
51         TALLOC_CTX *mem_ctx;
52         struct ldb_val *replace_dn;
53         struct extended_dn_context *ac;
54         struct ldb_request *search_req;
55 };
56
57
58 struct extended_dn_context {
59         const struct dsdb_schema *schema;
60         struct ldb_module *module;
61         struct ldb_request *req;
62         struct ldb_request *new_req;
63
64         struct extended_dn_replace_list *ops;
65         struct extended_dn_replace_list *cur;
66 };
67
68
69 static struct extended_dn_context *extended_dn_context_init(struct ldb_module *module,
70                                                             struct ldb_request *req)
71 {
72         struct extended_dn_context *ac;
73
74         ac = talloc_zero(req, struct extended_dn_context);
75         if (ac == NULL) {
76                 ldb_oom(module->ldb);
77                 return NULL;
78         }
79
80         ac->schema = dsdb_get_schema(module->ldb);
81         ac->module = module;
82         ac->req = req;
83
84         return ac;
85 }
86
87 /* An extra layer of indirection because LDB does not allow the original request to be altered */
88
89 static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
90 {
91         int ret = LDB_ERR_OPERATIONS_ERROR;
92         struct extended_dn_context *ac;
93         ac = talloc_get_type(req->context, struct extended_dn_context);
94
95         if (ares->error != LDB_SUCCESS) {
96                 ret = ldb_module_done(ac->req, ares->controls,
97                                       ares->response, ares->error);
98         } else {
99                 switch (ares->type) {
100                 case LDB_REPLY_ENTRY:
101                         
102                         ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
103                         break;
104                 case LDB_REPLY_REFERRAL:
105                         
106                         ret = ldb_module_send_referral(ac->req, ares->referral);
107                         break;
108                 case LDB_REPLY_DONE:
109                         
110                         ret = ldb_module_done(ac->req, ares->controls,
111                                               ares->response, ares->error);
112                         break;
113                 }
114         }
115         return ret;
116 }
117
118 static int extended_replace_dn(struct ldb_request *req, struct ldb_reply *ares)
119 {
120         struct extended_dn_replace_list *os = talloc_get_type(req->context, 
121                                                            struct extended_dn_replace_list);
122
123         if (!ares) {
124                 return ldb_module_done(os->ac->req, NULL, NULL,
125                                         LDB_ERR_OPERATIONS_ERROR);
126         }
127         if (ares->error == LDB_ERR_NO_SUCH_OBJECT) {
128                 /* Don't worry too much about dangling references */
129
130                 ldb_reset_err_string(os->ac->module->ldb);
131                 if (os->next) {
132                         struct extended_dn_replace_list *next;
133
134                         next = os->next;
135
136                         talloc_free(os);
137
138                         os = next;
139                         return ldb_next_request(os->ac->module, next->search_req);
140                 } else {
141                         /* Otherwise, we are done - let's run the
142                          * request now we have swapped the DNs for the
143                          * full versions */
144                         return ldb_next_request(os->ac->module, os->ac->req);
145                 }
146         }
147         if (ares->error != LDB_SUCCESS) {
148                 return ldb_module_done(os->ac->req, ares->controls,
149                                         ares->response, ares->error);
150         }
151
152         /* Only entries are interesting, and we only want the olddn */
153         switch (ares->type) {
154         case LDB_REPLY_ENTRY:
155         {
156                 /* This *must* be the right DN, as this is a base
157                  * search.  We can't check, as it could be an extended
158                  * DN, so a module below will resolve it */
159                 struct ldb_dn *dn = ares->message->dn;
160
161                 *os->replace_dn = data_blob_string_const(
162                         ldb_dn_get_extended_linearized(os->mem_ctx, 
163                                                        dn, 1));
164                 if (os->replace_dn->data == NULL) {
165                         return ldb_module_done(os->ac->req, NULL, NULL,
166                                                 LDB_ERR_OPERATIONS_ERROR);
167                 }
168                 break;
169         }
170         case LDB_REPLY_REFERRAL:
171                 /* ignore */
172                 break;
173
174         case LDB_REPLY_DONE:
175
176                 talloc_free(ares);
177                 
178                 /* Run the next search */
179
180                 if (os->next) {
181                         struct extended_dn_replace_list *next;
182
183                         next = os->next;
184
185                         talloc_free(os);
186
187                         os = next;
188                         return ldb_next_request(os->ac->module, next->search_req);
189                 } else {
190                         /* Otherwise, we are done - let's run the
191                          * request now we have swapped the DNs for the
192                          * full versions */
193                         return ldb_next_request(os->ac->module, os->ac->new_req);
194                 }
195         }
196
197         talloc_free(ares);
198         return LDB_SUCCESS;
199 }
200
201 /* We have a 'normal' DN in the inbound request.  We need to find out
202  * what the GUID and SID are on the DN it points to, so we can
203  * construct an extended DN for storage.
204  *
205  * This creates a list of DNs to look up, and the plain DN to replace
206  */
207
208 static int extended_store_replace(struct extended_dn_context *ac,
209                                   TALLOC_CTX *callback_mem_ctx,
210                                   struct ldb_val *plain_dn)
211 {
212         int ret;
213         struct extended_dn_replace_list *os;
214         static const char *attrs[] = {
215                 "objectSid",
216                 "objectGUID",
217                 NULL
218         };
219
220         os = talloc_zero(ac, struct extended_dn_replace_list);
221         if (!os) {
222                 return LDB_ERR_OPERATIONS_ERROR;
223         }
224
225         os->ac = ac;
226         
227         os->mem_ctx = callback_mem_ctx;
228
229         os->dn = ldb_dn_from_ldb_val(os, ac->module->ldb, plain_dn);
230         if (!os->dn || !ldb_dn_validate(os->dn)) {
231                 talloc_free(os);
232                 ldb_asprintf_errstring(ac->module->ldb, 
233                                        "could not parse %.*s as a DN", (int)plain_dn->length, plain_dn->data);
234                 return LDB_ERR_INVALID_DN_SYNTAX;
235         }
236
237         os->replace_dn = plain_dn;
238
239         /* The search request here might happen to be for an
240          * 'extended' style DN, such as <GUID=abced...>.  The next
241          * module in the stack will convert this into a normal DN for
242          * processing */
243         ret = ldb_build_search_req(&os->search_req,
244                                    ac->module->ldb, os, os->dn, LDB_SCOPE_BASE, NULL, 
245                                    attrs, NULL, os, extended_replace_dn,
246                                    ac->req);
247
248         if (ret != LDB_SUCCESS) {
249                 talloc_free(os);
250                 return ret;
251         }
252
253         ret = ldb_request_add_control(os->search_req,
254                                       DSDB_CONTROL_DN_STORAGE_FORMAT_OID,
255                                       true, NULL);
256         if (ret != LDB_SUCCESS) {
257                 talloc_free(os);
258                 return ret;
259         }
260
261         if (ac->ops) {
262                 ac->cur->next = os;
263         } else {
264                 ac->ops = os;
265         }
266         ac->cur = os;
267
268         return LDB_SUCCESS;
269 }
270
271
272 /* add */
273 static int extended_dn_add(struct ldb_module *module, struct ldb_request *req)
274 {
275         struct extended_dn_context *ac;
276         int ret;
277         int i, j;
278
279         if (ldb_dn_is_special(req->op.add.message->dn)) {
280                 /* do not manipulate our control entries */
281                 return ldb_next_request(module, req);
282         }
283
284         ac = extended_dn_context_init(module, req);
285         if (!ac) {
286                 return LDB_ERR_OPERATIONS_ERROR;
287         }
288
289         if (!ac->schema) {
290                 /* without schema, this doesn't make any sense */
291                 talloc_free(ac);
292                 return ldb_next_request(module, req);
293         }
294
295         for (i=0; i < req->op.add.message->num_elements; i++) {
296                 const struct ldb_message_element *el = &req->op.add.message->elements[i];
297                 const struct dsdb_attribute *schema_attr
298                         = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
299                 if (!schema_attr) {
300                         continue;
301                 }
302
303                 /* We only setup an extended DN GUID on these particular DN objects */
304                 if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0) {
305                         continue;
306                 }
307
308                 /* Before we setup a procedure to modify the incoming message, we must copy it */
309                 if (!ac->new_req) {
310                         struct ldb_message *msg = ldb_msg_copy(ac, req->op.add.message);
311                         if (!msg) {
312                                 ldb_oom(module->ldb);
313                                 return LDB_ERR_OPERATIONS_ERROR;
314                         }
315                    
316                         ret = ldb_build_add_req(&ac->new_req, module->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
317                         if (ret != LDB_SUCCESS) {
318                                 return ret;
319                         }
320                 }
321                 /* Re-calculate el */
322                 el = &ac->new_req->op.add.message->elements[i];
323                 for (j = 0; j < el->num_values; j++) {
324                         ret = extended_store_replace(ac, ac->new_req->op.add.message->elements, &el->values[j]);
325                         if (ret != LDB_SUCCESS) {
326                                 return ret;
327                         }
328                 }
329         }
330
331         /* if DNs were set continue */
332         if (ac->ops == NULL) {
333                 talloc_free(ac);
334                 return ldb_next_request(module, req);
335         }
336
337         /* start with the searches */
338         return ldb_next_request(module, ac->ops->search_req);
339 }
340
341 /* modify */
342 static int extended_dn_modify(struct ldb_module *module, struct ldb_request *req)
343 {
344         /* Look over list of modifications */
345         /* Find if any are for linked attributes */
346         /* Determine the effect of the modification */
347         /* Apply the modify to the linked entry */
348
349         int i, j;
350         struct extended_dn_context *ac;
351         int ret;
352
353         if (ldb_dn_is_special(req->op.mod.message->dn)) {
354                 /* do not manipulate our control entries */
355                 return ldb_next_request(module, req);
356         }
357
358         ac = extended_dn_context_init(module, req);
359         if (!ac) {
360                 return LDB_ERR_OPERATIONS_ERROR;
361         }
362
363         if (!ac->schema) {
364                 /* without schema, this doesn't make any sense */
365                 return ldb_next_request(module, req);
366         }
367
368         for (i=0; i < req->op.mod.message->num_elements; i++) {
369                 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
370                 const struct dsdb_attribute *schema_attr
371                         = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
372                 if (!schema_attr) {
373                         continue;
374                 }
375
376                 /* We only setup an extended DN GUID on these particular DN objects */
377                 if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0) {
378                         continue;
379                 }
380                 
381                 /* Before we setup a procedure to modify the incoming message, we must copy it */
382                 if (!ac->new_req) {
383                         struct ldb_message *msg = ldb_msg_copy(ac, req->op.mod.message);
384                         if (!msg) {
385                                 ldb_oom(module->ldb);
386                                 return LDB_ERR_OPERATIONS_ERROR;
387                         }
388                    
389                         ret = ldb_build_mod_req(&ac->new_req, module->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
390                         if (ret != LDB_SUCCESS) {
391                                 return ret;
392                         }
393                 }
394                 /* Re-calculate el */
395                 el = &ac->new_req->op.mod.message->elements[i];
396                 /* For each value being added, we need to setup the lookups to fill in the extended DN */
397                 for (j = 0; j < el->num_values; j++) {
398                         struct ldb_dn *dn = ldb_dn_from_ldb_val(ac, module->ldb, &el->values[j]);
399                         if (!dn || !ldb_dn_validate(dn)) {
400                                 ldb_asprintf_errstring(module->ldb, 
401                                                        "could not parse attribute %s as a DN", el->name);
402                                 return LDB_ERR_INVALID_DN_SYNTAX;
403                         }
404                         if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE) && !ldb_dn_has_extended(dn)) {
405                                 /* NO need to figure this DN out, it's going to be deleted anyway */
406                                 continue;
407                         }
408                         ret = extended_store_replace(ac, req->op.mod.message->elements, &el->values[j]);
409                         if (ret != LDB_SUCCESS) {
410                                 return ret;
411                         }
412                 }
413         }
414
415         /* if DNs were set continue */
416         if (ac->ops == NULL) {
417                 talloc_free(ac);
418                 return ldb_next_request(module, req);
419         }
420
421         /* start with the searches */
422         return ldb_next_request(module, ac->ops->search_req);
423 }
424
425 _PUBLIC_ const struct ldb_module_ops ldb_extended_dn_store_module_ops = {
426         .name              = "extended_dn_store",
427         .add               = extended_dn_add,
428         .modify            = extended_dn_modify,
429 };