s4-dsdb: catch duplicate matches in extended_dn_in
[amitay/samba.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_in.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 interprets DNs of the form <SID=S-1-2-4456> into normal DNs.
27  *
28  *  Authors: Simo Sorce
29  *           Andrew Bartlett
30  */
31
32 #include "includes.h"
33 #include <ldb.h>
34 #include <ldb_errors.h>
35 #include <ldb_module.h>
36
37 /*
38   TODO: if relax is not set then we need to reject the fancy RMD_* and
39   DELETED extended DN codes
40  */
41
42 /* search */
43 struct extended_search_context {
44         struct ldb_module *module;
45         struct ldb_request *req;
46         struct ldb_dn *basedn;
47         char *wellknown_object;
48         int extended_type;
49 };
50
51 /* An extra layer of indirection because LDB does not allow the original request to be altered */
52
53 static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
54 {
55         int ret = LDB_ERR_OPERATIONS_ERROR;
56         struct extended_search_context *ac;
57         ac = talloc_get_type(req->context, struct extended_search_context);
58
59         if (ares->error != LDB_SUCCESS) {
60                 ret = ldb_module_done(ac->req, ares->controls,
61                                       ares->response, ares->error);
62         } else {
63                 switch (ares->type) {
64                 case LDB_REPLY_ENTRY:
65                         
66                         ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
67                         break;
68                 case LDB_REPLY_REFERRAL:
69                         
70                         ret = ldb_module_send_referral(ac->req, ares->referral);
71                         break;
72                 case LDB_REPLY_DONE:
73                         
74                         ret = ldb_module_done(ac->req, ares->controls,
75                                               ares->response, ares->error);
76                         break;
77                 }
78         }
79         return ret;
80 }
81
82 static int extended_base_callback(struct ldb_request *req, struct ldb_reply *ares)
83 {
84         struct extended_search_context *ac;
85         struct ldb_request *down_req;
86         struct ldb_message_element *el;
87         int ret;
88         unsigned int i;
89         size_t wkn_len = 0;
90         char *valstr = NULL;
91         const char *found = NULL;
92
93         ac = talloc_get_type(req->context, struct extended_search_context);
94
95         if (!ares) {
96                 return ldb_module_done(ac->req, NULL, NULL,
97                                         LDB_ERR_OPERATIONS_ERROR);
98         }
99         if (ares->error != LDB_SUCCESS) {
100                 return ldb_module_done(ac->req, ares->controls,
101                                         ares->response, ares->error);
102         }
103
104         switch (ares->type) {
105         case LDB_REPLY_ENTRY:
106                 if (ac->basedn) {
107                         /* we have more than one match! This can
108                            happen as S-1-5-17 appears twice in a
109                            normal provision. We need to return
110                            NO_SUCH_OBJECT */
111                         const char *str = talloc_asprintf(req, "Duplicate base-DN matches found for '%s'",
112                                                           ldb_dn_get_extended_linearized(req, ac->req->op.search.base, 1));
113                         ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
114                         return ldb_module_done(ac->req, NULL, NULL,
115                                                LDB_ERR_NO_SUCH_OBJECT);
116                 }
117
118                 if (!ac->wellknown_object) {
119                         ac->basedn = talloc_steal(ac, ares->message->dn);
120                         break;
121                 }
122
123                 wkn_len = strlen(ac->wellknown_object);
124
125                 el = ldb_msg_find_element(ares->message, "wellKnownObjects");
126                 if (!el) {
127                         ac->basedn = NULL;
128                         break;
129                 }
130
131                 for (i=0; i < el->num_values; i++) {
132                         valstr = talloc_strndup(ac,
133                                                 (const char *)el->values[i].data,
134                                                 el->values[i].length);
135                         if (!valstr) {
136                                 ldb_oom(ldb_module_get_ctx(ac->module));
137                                 return ldb_module_done(ac->req, NULL, NULL,
138                                                        LDB_ERR_OPERATIONS_ERROR);
139                         }
140
141                         if (strncasecmp(valstr, ac->wellknown_object, wkn_len) != 0) {
142                                 talloc_free(valstr);
143                                 continue;
144                         }
145
146                         found = &valstr[wkn_len];
147                         break;
148                 }
149
150                 if (!found) {
151                         break;
152                 }
153
154                 ac->basedn = ldb_dn_new(ac, ldb_module_get_ctx(ac->module), found);
155                 talloc_free(valstr);
156                 if (!ac->basedn) {
157                         ldb_oom(ldb_module_get_ctx(ac->module));
158                         return ldb_module_done(ac->req, NULL, NULL,
159                                                LDB_ERR_OPERATIONS_ERROR);
160                 }
161
162                 break;
163
164         case LDB_REPLY_REFERRAL:
165                 break;
166
167         case LDB_REPLY_DONE:
168
169                 if (!ac->basedn) {
170                         const char *str = talloc_asprintf(req, "Base-DN '%s' not found",
171                                                           ldb_dn_get_extended_linearized(req, ac->req->op.search.base, 1));
172                         ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
173                         return ldb_module_done(ac->req, NULL, NULL,
174                                                LDB_ERR_NO_SUCH_OBJECT);
175                 }
176
177                 switch (ac->req->operation) {
178                 case LDB_SEARCH:
179                         ret = ldb_build_search_req_ex(&down_req,
180                                                       ldb_module_get_ctx(ac->module), ac->req,
181                                                       ac->basedn,
182                                                       ac->req->op.search.scope,
183                                                       ac->req->op.search.tree,
184                                                       ac->req->op.search.attrs,
185                                                       ac->req->controls,
186                                                       ac, extended_final_callback, 
187                                                       ac->req);
188                         LDB_REQ_SET_LOCATION(down_req);
189                         break;
190                 case LDB_ADD:
191                 {
192                         struct ldb_message *add_msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
193                         if (!add_msg) {
194                                 ldb_oom(ldb_module_get_ctx(ac->module));
195                                 return ldb_module_done(ac->req, NULL, NULL,
196                                                        LDB_ERR_OPERATIONS_ERROR);
197                         }
198                         
199                         add_msg->dn = ac->basedn;
200
201                         ret = ldb_build_add_req(&down_req,
202                                                 ldb_module_get_ctx(ac->module), ac->req,
203                                                 add_msg, 
204                                                 ac->req->controls,
205                                                 ac, extended_final_callback, 
206                                                 ac->req);
207                         LDB_REQ_SET_LOCATION(down_req);
208                         break;
209                 }
210                 case LDB_MODIFY:
211                 {
212                         struct ldb_message *mod_msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message);
213                         if (!mod_msg) {
214                                 ldb_oom(ldb_module_get_ctx(ac->module));
215                                 return ldb_module_done(ac->req, NULL, NULL,
216                                                        LDB_ERR_OPERATIONS_ERROR);
217                         }
218                         
219                         mod_msg->dn = ac->basedn;
220
221                         ret = ldb_build_mod_req(&down_req,
222                                                 ldb_module_get_ctx(ac->module), ac->req,
223                                                 mod_msg, 
224                                                 ac->req->controls,
225                                                 ac, extended_final_callback, 
226                                                 ac->req);
227                         LDB_REQ_SET_LOCATION(down_req);
228                         break;
229                 }
230                 case LDB_DELETE:
231                         ret = ldb_build_del_req(&down_req,
232                                                 ldb_module_get_ctx(ac->module), ac->req,
233                                                 ac->basedn, 
234                                                 ac->req->controls,
235                                                 ac, extended_final_callback, 
236                                                 ac->req);
237                         LDB_REQ_SET_LOCATION(down_req);
238                         break;
239                 case LDB_RENAME:
240                         ret = ldb_build_rename_req(&down_req,
241                                                    ldb_module_get_ctx(ac->module), ac->req,
242                                                    ac->basedn, 
243                                                    ac->req->op.rename.newdn,
244                                                    ac->req->controls,
245                                                    ac, extended_final_callback, 
246                                                    ac->req);
247                         LDB_REQ_SET_LOCATION(down_req);
248                         break;
249                 default:
250                         return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
251                 }
252                 
253                 if (ret != LDB_SUCCESS) {
254                         return ldb_module_done(ac->req, NULL, NULL, ret);
255                 }
256
257                 return ldb_next_request(ac->module, down_req);
258         }
259         talloc_free(ares);
260         return LDB_SUCCESS;
261 }
262
263 static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
264 {
265         struct extended_search_context *ac;
266         struct ldb_request *down_req;
267         int ret;
268         struct ldb_dn *base_dn = NULL;
269         enum ldb_scope base_dn_scope = LDB_SCOPE_BASE;
270         const char *base_dn_filter = NULL;
271         const char * const *base_dn_attrs = NULL;
272         char *wellknown_object = NULL;
273         static const char *no_attr[] = {
274                 NULL
275         };
276         static const char *wkattr[] = {
277                 "wellKnownObjects",
278                 NULL
279         };
280         bool all_partitions = false;
281
282         if (!ldb_dn_has_extended(dn)) {
283                 /* Move along there isn't anything to see here */
284                 return ldb_next_request(module, req);
285         } else {
286                 /* It looks like we need to map the DN */
287                 const struct ldb_val *sid_val, *guid_val, *wkguid_val;
288                 int num_components = ldb_dn_get_comp_num(dn);
289                 int num_ex_components = ldb_dn_get_extended_comp_num(dn);
290
291                 /*
292                   windows ldap searchs don't allow a baseDN with more
293                   than one extended component, or an extended
294                   component and a string DN
295
296                   We only enforce this over ldap, not for internal
297                   use, as there are just too many places where we
298                   internally want to use a DN that has come from a
299                   search with extended DN enabled, or comes from a DRS
300                   naming context.
301
302                   Enforcing this would also make debugging samba much
303                   harder, as we'd need to use ldb_dn_minimise() in a
304                   lot of places, and that would lose the DN string
305                   which is so useful for working out what a request is
306                   for
307                  */
308                 if ((num_components != 0 || num_ex_components != 1) &&
309                     ldb_req_is_untrusted(req)) {
310                         return ldb_error(ldb_module_get_ctx(module),
311                                          LDB_ERR_INVALID_DN_SYNTAX, "invalid number of DN components");
312                 }
313
314                 sid_val = ldb_dn_get_extended_component(dn, "SID");
315                 guid_val = ldb_dn_get_extended_component(dn, "GUID");
316                 wkguid_val = ldb_dn_get_extended_component(dn, "WKGUID");
317
318                 if (sid_val) {
319                         all_partitions = true;
320                         base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
321                         base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", 
322                                                          ldb_binary_encode(req, *sid_val));
323                         if (!base_dn_filter) {
324                                 return ldb_oom(ldb_module_get_ctx(module));
325                         }
326                         base_dn_scope = LDB_SCOPE_SUBTREE;
327                         base_dn_attrs = no_attr;
328
329                 } else if (guid_val) {
330
331                         all_partitions = true;
332                         base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
333                         base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", 
334                                                          ldb_binary_encode(req, *guid_val));
335                         if (!base_dn_filter) {
336                                 return ldb_oom(ldb_module_get_ctx(module));
337                         }
338                         base_dn_scope = LDB_SCOPE_SUBTREE;
339                         base_dn_attrs = no_attr;
340
341
342                 } else if (wkguid_val) {
343                         char *wkguid_dup;
344                         char *tail_str;
345                         char *p;
346
347                         wkguid_dup = talloc_strndup(req, (char *)wkguid_val->data, wkguid_val->length);
348
349                         p = strchr(wkguid_dup, ',');
350                         if (!p) {
351                                 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
352                                                  "Invalid WKGUID format");
353                         }
354
355                         p[0] = '\0';
356                         p++;
357
358                         wellknown_object = talloc_asprintf(req, "B:32:%s:", wkguid_dup);
359                         if (!wellknown_object) {
360                                 return ldb_oom(ldb_module_get_ctx(module));
361                         }
362
363                         tail_str = p;
364
365                         base_dn = ldb_dn_new(req, ldb_module_get_ctx(module), tail_str);
366                         talloc_free(wkguid_dup);
367                         if (!base_dn) {
368                                 return ldb_oom(ldb_module_get_ctx(module));
369                         }
370                         base_dn_filter = talloc_strdup(req, "(objectClass=*)");
371                         if (!base_dn_filter) {
372                                 return ldb_oom(ldb_module_get_ctx(module));
373                         }
374                         base_dn_scope = LDB_SCOPE_BASE;
375                         base_dn_attrs = wkattr;
376                 } else {
377                         return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
378                                          "Invalid extended DN component");
379                 }
380
381                 ac = talloc_zero(req, struct extended_search_context);
382                 if (ac == NULL) {
383                         return ldb_oom(ldb_module_get_ctx(module));
384                 }
385                 
386                 ac->module = module;
387                 ac->req = req;
388                 ac->basedn = NULL;  /* Filled in if the search finds the DN by SID/GUID etc */
389                 ac->wellknown_object = wellknown_object;
390                 
391                 /* If the base DN was an extended DN (perhaps a well known
392                  * GUID) then search for that, so we can proceed with the original operation */
393
394                 ret = ldb_build_search_req(&down_req,
395                                            ldb_module_get_ctx(module), ac,
396                                            base_dn,
397                                            base_dn_scope,
398                                            base_dn_filter,
399                                            base_dn_attrs,
400                                            req->controls,
401                                            ac, extended_base_callback,
402                                            req);
403                 LDB_REQ_SET_LOCATION(down_req);
404                 if (ret != LDB_SUCCESS) {
405                         return ldb_operr(ldb_module_get_ctx(module));
406                 }
407
408                 if (all_partitions) {
409                         struct ldb_search_options_control *control;
410                         control = talloc(down_req, struct ldb_search_options_control);
411                         control->search_options = 2;
412                         ret = ldb_request_replace_control(down_req,
413                                                       LDB_CONTROL_SEARCH_OPTIONS_OID,
414                                                       true, control);
415                         if (ret != LDB_SUCCESS) {
416                                 ldb_oom(ldb_module_get_ctx(module));
417                                 return ret;
418                         }
419                 }
420
421                 /* perform the search */
422                 return ldb_next_request(module, down_req);
423         }
424 }
425
426 static int extended_dn_in_search(struct ldb_module *module, struct ldb_request *req)
427 {
428         return extended_dn_in_fix(module, req, req->op.search.base);
429 }
430
431 static int extended_dn_in_modify(struct ldb_module *module, struct ldb_request *req)
432 {
433         return extended_dn_in_fix(module, req, req->op.mod.message->dn);
434 }
435
436 static int extended_dn_in_del(struct ldb_module *module, struct ldb_request *req)
437 {
438         return extended_dn_in_fix(module, req, req->op.del.dn);
439 }
440
441 static int extended_dn_in_rename(struct ldb_module *module, struct ldb_request *req)
442 {
443         return extended_dn_in_fix(module, req, req->op.rename.olddn);
444 }
445
446 static const struct ldb_module_ops ldb_extended_dn_in_module_ops = {
447         .name              = "extended_dn_in",
448         .search            = extended_dn_in_search,
449         .modify            = extended_dn_in_modify,
450         .del               = extended_dn_in_del,
451         .rename            = extended_dn_in_rename,
452 };
453
454 int ldb_extended_dn_in_module_init(const char *version)
455 {
456         LDB_MODULE_CHECK_VERSION(version);
457         return ldb_register_module(&ldb_extended_dn_in_module_ops);
458 }