s4-ldb: accept the binary DN OIDs in extended DN modules
[ira/wip.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_module.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(ldb_module_get_ctx(module));
77                 return NULL;
78         }
79
80         ac->schema = dsdb_get_schema(ldb_module_get_ctx(module));
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(ldb_module_get_ctx(os->ac->module));
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                 /* Replace the DN with the extended version of the DN
162                  * (ie, add SID and GUID) */
163                 *os->replace_dn = data_blob_string_const(
164                         ldb_dn_get_extended_linearized(os->mem_ctx, 
165                                                        dn, 1));
166                 if (os->replace_dn->data == NULL) {
167                         return ldb_module_done(os->ac->req, NULL, NULL,
168                                                 LDB_ERR_OPERATIONS_ERROR);
169                 }
170                 break;
171         }
172         case LDB_REPLY_REFERRAL:
173                 /* ignore */
174                 break;
175
176         case LDB_REPLY_DONE:
177
178                 talloc_free(ares);
179                 
180                 /* Run the next search */
181
182                 if (os->next) {
183                         struct extended_dn_replace_list *next;
184
185                         next = os->next;
186
187                         talloc_free(os);
188
189                         os = next;
190                         return ldb_next_request(os->ac->module, next->search_req);
191                 } else {
192                         /* Otherwise, we are done - let's run the
193                          * request now we have swapped the DNs for the
194                          * full versions */
195                         return ldb_next_request(os->ac->module, os->ac->new_req);
196                 }
197         }
198
199         talloc_free(ares);
200         return LDB_SUCCESS;
201 }
202
203 /* We have a 'normal' DN in the inbound request.  We need to find out
204  * what the GUID and SID are on the DN it points to, so we can
205  * construct an extended DN for storage.
206  *
207  * This creates a list of DNs to look up, and the plain DN to replace
208  */
209
210 static int extended_store_replace(struct extended_dn_context *ac,
211                                   TALLOC_CTX *callback_mem_ctx,
212                                   struct ldb_val *plain_dn)
213 {
214         int ret;
215         struct extended_dn_replace_list *os;
216         static const char *attrs[] = {
217                 "objectSid",
218                 "objectGUID",
219                 NULL
220         };
221
222         os = talloc_zero(ac, struct extended_dn_replace_list);
223         if (!os) {
224                 return LDB_ERR_OPERATIONS_ERROR;
225         }
226
227         os->ac = ac;
228         
229         os->mem_ctx = callback_mem_ctx;
230
231         os->dn = ldb_dn_from_ldb_val(os, ldb_module_get_ctx(ac->module), plain_dn);
232         if (!os->dn || !ldb_dn_validate(os->dn)) {
233                 talloc_free(os);
234                 ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), 
235                                        "could not parse %.*s as a DN", (int)plain_dn->length, plain_dn->data);
236                 return LDB_ERR_INVALID_DN_SYNTAX;
237         }
238
239         os->replace_dn = plain_dn;
240
241         /* The search request here might happen to be for an
242          * 'extended' style DN, such as <GUID=abced...>.  The next
243          * module in the stack will convert this into a normal DN for
244          * processing */
245         ret = ldb_build_search_req(&os->search_req,
246                                    ldb_module_get_ctx(ac->module), os, os->dn, LDB_SCOPE_BASE, NULL, 
247                                    attrs, NULL, os, extended_replace_dn,
248                                    ac->req);
249
250         if (ret != LDB_SUCCESS) {
251                 talloc_free(os);
252                 return ret;
253         }
254
255         ret = ldb_request_add_control(os->search_req,
256                                       DSDB_CONTROL_DN_STORAGE_FORMAT_OID,
257                                       true, NULL);
258         if (ret != LDB_SUCCESS) {
259                 talloc_free(os);
260                 return ret;
261         }
262
263         if (ac->ops) {
264                 ac->cur->next = os;
265         } else {
266                 ac->ops = os;
267         }
268         ac->cur = os;
269
270         return LDB_SUCCESS;
271 }
272
273
274 /* add */
275 static int extended_dn_add(struct ldb_module *module, struct ldb_request *req)
276 {
277         struct extended_dn_context *ac;
278         int ret;
279         int i, j;
280
281         if (ldb_dn_is_special(req->op.add.message->dn)) {
282                 /* do not manipulate our control entries */
283                 return ldb_next_request(module, req);
284         }
285
286         ac = extended_dn_context_init(module, req);
287         if (!ac) {
288                 return LDB_ERR_OPERATIONS_ERROR;
289         }
290
291         if (!ac->schema) {
292                 /* without schema, this doesn't make any sense */
293                 talloc_free(ac);
294                 return ldb_next_request(module, req);
295         }
296
297         for (i=0; i < req->op.add.message->num_elements; i++) {
298                 const struct ldb_message_element *el = &req->op.add.message->elements[i];
299                 const struct dsdb_attribute *schema_attr
300                         = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
301                 if (!schema_attr) {
302                         continue;
303                 }
304
305                 /* We only setup an extended DN GUID on these particular DN objects */
306                 if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0 &&
307                     strcmp(schema_attr->attributeSyntax_oid, "2.5.5.7") != 0) {
308                         continue;
309                 }
310
311                 /* Before we setup a procedure to modify the incoming message, we must copy it */
312                 if (!ac->new_req) {
313                         struct ldb_message *msg = ldb_msg_copy(ac, req->op.add.message);
314                         if (!msg) {
315                                 ldb_oom(ldb_module_get_ctx(module));
316                                 return LDB_ERR_OPERATIONS_ERROR;
317                         }
318                    
319                         ret = ldb_build_add_req(&ac->new_req, ldb_module_get_ctx(module), ac, msg, req->controls, ac, extended_final_callback, req);
320                         if (ret != LDB_SUCCESS) {
321                                 return ret;
322                         }
323                 }
324                 /* Re-calculate el */
325                 el = &ac->new_req->op.add.message->elements[i];
326                 for (j = 0; j < el->num_values; j++) {
327                         ret = extended_store_replace(ac, ac->new_req->op.add.message->elements, &el->values[j]);
328                         if (ret != LDB_SUCCESS) {
329                                 return ret;
330                         }
331                 }
332         }
333
334         /* if DNs were set continue */
335         if (ac->ops == NULL) {
336                 talloc_free(ac);
337                 return ldb_next_request(module, req);
338         }
339
340         /* start with the searches */
341         return ldb_next_request(module, ac->ops->search_req);
342 }
343
344 /* modify */
345 static int extended_dn_modify(struct ldb_module *module, struct ldb_request *req)
346 {
347         /* Look over list of modifications */
348         /* Find if any are for linked attributes */
349         /* Determine the effect of the modification */
350         /* Apply the modify to the linked entry */
351
352         int i, j;
353         struct extended_dn_context *ac;
354         int ret;
355
356         if (ldb_dn_is_special(req->op.mod.message->dn)) {
357                 /* do not manipulate our control entries */
358                 return ldb_next_request(module, req);
359         }
360
361         ac = extended_dn_context_init(module, req);
362         if (!ac) {
363                 return LDB_ERR_OPERATIONS_ERROR;
364         }
365
366         if (!ac->schema) {
367                 /* without schema, this doesn't make any sense */
368                 return ldb_next_request(module, req);
369         }
370
371         for (i=0; i < req->op.mod.message->num_elements; i++) {
372                 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
373                 const struct dsdb_attribute *schema_attr
374                         = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
375                 if (!schema_attr) {
376                         continue;
377                 }
378
379                 /* We only setup an extended DN GUID on these particular DN objects */
380                 if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0 &&
381                     strcmp(schema_attr->attributeSyntax_oid, "2.5.5.7") != 0) {
382                         continue;
383                 }
384                 
385                 /* Before we setup a procedure to modify the incoming message, we must copy it */
386                 if (!ac->new_req) {
387                         struct ldb_message *msg = ldb_msg_copy(ac, req->op.mod.message);
388                         if (!msg) {
389                                 ldb_oom(ldb_module_get_ctx(module));
390                                 return LDB_ERR_OPERATIONS_ERROR;
391                         }
392                    
393                         ret = ldb_build_mod_req(&ac->new_req, ldb_module_get_ctx(module), ac, msg, req->controls, ac, extended_final_callback, req);
394                         if (ret != LDB_SUCCESS) {
395                                 return ret;
396                         }
397                 }
398                 /* Re-calculate el */
399                 el = &ac->new_req->op.mod.message->elements[i];
400                 /* For each value being added, we need to setup the lookups to fill in the extended DN */
401                 for (j = 0; j < el->num_values; j++) {
402                         struct ldb_dn *dn = ldb_dn_from_ldb_val(ac, ldb_module_get_ctx(module), &el->values[j]);
403                         if (!dn || !ldb_dn_validate(dn)) {
404                                 ldb_asprintf_errstring(ldb_module_get_ctx(module), 
405                                                        "could not parse attribute %s as a DN", el->name);
406                                 return LDB_ERR_INVALID_DN_SYNTAX;
407                         }
408                         if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE) && !ldb_dn_has_extended(dn)) {
409                                 /* NO need to figure this DN out, it's going to be deleted anyway */
410                                 continue;
411                         }
412                         ret = extended_store_replace(ac, req->op.mod.message->elements, &el->values[j]);
413                         if (ret != LDB_SUCCESS) {
414                                 return ret;
415                         }
416                 }
417         }
418
419         /* if DNs were set continue */
420         if (ac->ops == NULL) {
421                 talloc_free(ac);
422                 return ldb_next_request(module, req);
423         }
424
425         /* start with the searches */
426         return ldb_next_request(module, ac->ops->search_req);
427 }
428
429 _PUBLIC_ const struct ldb_module_ops ldb_extended_dn_store_module_ops = {
430         .name              = "extended_dn_store",
431         .add               = extended_dn_add,
432         .modify            = extended_dn_modify,
433 };