ldb-samba: Correct error reporting to match Windows
[obnox/samba/samba-obnox.git] / lib / ldb-samba / ldb_matching_rules.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    ldb database library - Extended match rules
5
6    Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me>
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 #include "includes.h"
23 #include <ldb_module.h>
24 #include "dsdb/samdb/samdb.h"
25 #include "ldb_matching_rules.h"
26
27 static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx,
28                                              struct ldb_context *ldb,
29                                              const char *attr,
30                                              const struct dsdb_dn *dn_to_match,
31                                              const char *dn_oid,
32                                              struct dsdb_dn *to_visit,
33                                              struct dsdb_dn ***visited,
34                                              unsigned int *visited_count,
35                                              bool *matched)
36 {
37         TALLOC_CTX *tmp_ctx;
38         int ret, i, j;
39         struct ldb_result *res;
40         struct ldb_message *msg;
41         struct ldb_message_element *el;
42         const char *attrs[] = { attr, NULL };
43
44         tmp_ctx = talloc_new(mem_ctx);
45         if (tmp_ctx == NULL) {
46                 return LDB_ERR_OPERATIONS_ERROR;
47         }
48
49         /*
50          * Fetch the entry to_visit
51          *
52          * NOTE: This is a new LDB search from the TOP of the module
53          * stack.  This means that this search runs the whole stack
54          * from top to bottom.
55          *
56          * This may seem to be in-efficient, but it is also the only
57          * way to ensure that the ACLs for this search are applied
58          * correctly.
59          *
60          * Note also that we don't have the original request
61          * here, so we can not apply controls or timeouts here.
62          */
63         ret = dsdb_search_dn(ldb, tmp_ctx, &res, to_visit->dn, attrs, 0);
64         if (ret != LDB_SUCCESS) {
65                 talloc_free(tmp_ctx);
66                 return ret;
67         }
68         if (res->count != 1) {
69                 talloc_free(tmp_ctx);
70                 return LDB_ERR_OPERATIONS_ERROR;
71         }
72         msg = res->msgs[0];
73
74         /* Fetch the attribute to match from the entry being visited */
75         el = ldb_msg_find_element(msg, attr);
76         if (el == NULL) {
77                 /* This entry does not have the attribute to match */
78                 talloc_free(tmp_ctx);
79                 *matched = false;
80                 return LDB_SUCCESS;
81         }
82
83         /*
84          * If the value to match is present in the attribute values of the
85          * current entry being visited, set matched to true and return OK
86          */
87         for (i=0; i<el->num_values; i++) {
88                 struct dsdb_dn *dn;
89                 dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
90                 if (dn == NULL) {
91                         talloc_free(tmp_ctx);
92                         *matched = false;
93                         return LDB_ERR_INVALID_DN_SYNTAX;
94                 }
95
96                 if (ldb_dn_compare(dn_to_match->dn, dn->dn) == 0) {
97                         talloc_free(tmp_ctx);
98                         *matched = true;
99                         return LDB_SUCCESS;
100                 }
101         }
102
103         /*
104          * If arrived here, the value to match is not in the values of the
105          * entry being visited. Add the entry being visited (to_visit)
106          * to the visited array. The array is (re)allocated in the parent
107          * memory context.
108          */
109         if (visited == NULL) {
110                 return LDB_ERR_OPERATIONS_ERROR;
111         } else if (*visited == NULL) {
112                 *visited = talloc_array(mem_ctx, struct dsdb_dn *, 1);
113                 if (*visited == NULL) {
114                         talloc_free(tmp_ctx);
115                         return LDB_ERR_OPERATIONS_ERROR;
116                 }
117                 (*visited)[0] = to_visit;
118                 (*visited_count) = 1;
119         } else {
120                 *visited = talloc_realloc(mem_ctx, *visited, struct dsdb_dn *,
121                                          (*visited_count) + 1);
122                 if (*visited == NULL) {
123                         talloc_free(tmp_ctx);
124                         return LDB_ERR_OPERATIONS_ERROR;
125                 }
126                 (*visited)[(*visited_count)] = to_visit;
127                 (*visited_count)++;
128         }
129
130         /*
131          * steal to_visit into visited array context, as it has to live until
132          * the array is freed.
133          */
134         talloc_steal(*visited, to_visit);
135
136         /*
137          * Iterate over the values of the attribute of the entry being
138          * visited (to_visit) and follow them, calling this function
139          * recursively.
140          * If the value is in the visited array, skip it.
141          * Otherwise, follow the link and visit it.
142          */
143         for (i=0; i<el->num_values; i++) {
144                 struct dsdb_dn *next_to_visit;
145                 bool skip = false;
146
147                 next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
148                 if (next_to_visit == NULL) {
149                         talloc_free(tmp_ctx);
150                         *matched = false;
151                         return LDB_ERR_INVALID_DN_SYNTAX;
152                 }
153
154                 /*
155                  * If the value is already in the visited array, skip it.
156                  * Note the last element of the array is ignored because it is
157                  * the current entry DN.
158                  */
159                 for (j=0; j < (*visited_count) - 1; j++) {
160                         struct dsdb_dn *visited_dn = (*visited)[j];
161                         if (ldb_dn_compare(visited_dn->dn,
162                                            next_to_visit->dn) == 0) {
163                                 skip = true;
164                                 break;
165                         }
166                 }
167                 if (skip) {
168                         talloc_free(next_to_visit);
169                         continue;
170                 }
171
172                 /* If the value is not in the visited array, evaluate it */
173                 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
174                                                         dn_to_match, dn_oid,
175                                                         next_to_visit,
176                                                         visited, visited_count,
177                                                         matched);
178                 if (ret != LDB_SUCCESS) {
179                         talloc_free(tmp_ctx);
180                         return ret;
181                 }
182                 if (*matched) {
183                         talloc_free(tmp_ctx);
184                         return LDB_SUCCESS;
185                 }
186         }
187
188         talloc_free(tmp_ctx);
189         *matched = false;
190         return LDB_SUCCESS;
191 }
192
193 /*
194  * This function parses the linked attribute value to match, whose syntax
195  * will be one of the different DN syntaxes, into a ldb_dn struct.
196  */
197 static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx,
198                                       struct ldb_context *ldb,
199                                       const char *attr,
200                                       const struct ldb_val *value_to_match,
201                                       struct dsdb_dn *current_object_dn,
202                                       bool *matched)
203 {
204         const struct dsdb_schema *schema;
205         const struct dsdb_attribute *schema_attr;
206         struct dsdb_dn *dn_to_match;
207         const char *dn_oid;
208         unsigned int count;
209         struct dsdb_dn **visited;
210
211         schema = dsdb_get_schema(ldb, mem_ctx);
212         if (schema == NULL) {
213                 return LDB_ERR_OPERATIONS_ERROR;
214         }
215
216         schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr);
217         if (schema_attr == NULL) {
218                 return LDB_ERR_NO_SUCH_ATTRIBUTE;
219         }
220
221         /* This is the DN syntax of the attribute being matched */
222         dn_oid = schema_attr->syntax->ldap_oid;
223
224         /*
225          * Build a ldb_dn struct holding the value to match, which is the
226          * value entered in the search filter
227          */
228         dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid);
229         if (dn_to_match == NULL) {
230                 *matched = false;
231                 return LDB_SUCCESS;
232         }
233
234         return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
235                                                  dn_to_match, dn_oid,
236                                                  current_object_dn,
237                                                  &visited, &count, matched);
238 }
239
240 /*
241  * This rule provides recursive search of a link attribute
242  *
243  * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
244  * This allows a search filter such as:
245  *
246  * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
247  *
248  * This searches not only the member attribute, but also any member
249  * attributes that point at an object with this member in them.  All the
250  * various DN syntax types are supported, not just plain DNs.
251  *
252  */
253 static int ldb_comparator_trans(struct ldb_context *ldb,
254                                 const char *oid,
255                                 const struct ldb_message *msg,
256                                 const char *attribute_to_match,
257                                 const struct ldb_val *value_to_match,
258                                 bool *matched)
259 {
260         const struct dsdb_schema *schema;
261         const struct dsdb_attribute *schema_attr;
262         struct ldb_dn *msg_dn;
263         struct dsdb_dn *dsdb_msg_dn;
264         TALLOC_CTX *tmp_ctx;
265         int ret;
266
267         tmp_ctx = talloc_new(ldb);
268         if (tmp_ctx == NULL) {
269                 return LDB_ERR_OPERATIONS_ERROR;
270         }
271
272         /*
273          * If the target attribute to match is not a linked attribute, then
274          * the filter evaluates to undefined
275          */
276         schema = dsdb_get_schema(ldb, tmp_ctx);
277         if (schema == NULL) {
278                 talloc_free(tmp_ctx);
279                 return LDB_ERR_OPERATIONS_ERROR;
280         }
281
282         schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
283         if (schema_attr == NULL) {
284                 talloc_free(tmp_ctx);
285                 return LDB_ERR_NO_SUCH_ATTRIBUTE;
286         }
287
288         /*
289          * This extended match filter is only valid for linked attributes,
290          * following the MS definition (the schema attribute has a linkID
291          * defined). See dochelp request 114111212024789 on cifs-protocols
292          * mailing list.
293          */
294         if (schema_attr->linkID == 0) {
295                 *matched = false;
296                 talloc_free(tmp_ctx);
297                 return LDB_SUCCESS;
298         }
299
300         /* Duplicate original msg dn as the msg must not be modified */
301         msg_dn = ldb_dn_copy(tmp_ctx, msg->dn);
302         if (msg_dn == NULL) {
303                 talloc_free(tmp_ctx);
304                 return LDB_ERR_OPERATIONS_ERROR;
305         }
306
307         /*
308          * Build a dsdb dn from the message copied DN, which should be a plain
309          * DN syntax.
310          */
311         dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
312                                         LDB_SYNTAX_DN);
313         if (dsdb_msg_dn == NULL) {
314                 *matched = false;
315                 return LDB_ERR_INVALID_DN_SYNTAX;
316         }
317
318         ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
319                                          attribute_to_match,
320                                          value_to_match,
321                                          dsdb_msg_dn, matched);
322         talloc_free(tmp_ctx);
323         return ret;
324 }
325
326
327 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
328 {
329         struct ldb_extended_match_rule *transitive_eval;
330         int ret;
331
332         transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
333         transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL;
334         transitive_eval->callback = ldb_comparator_trans;
335         ret = ldb_register_extended_match_rule(ldb, transitive_eval);
336         if (ret != LDB_SUCCESS) {
337                 talloc_free(transitive_eval);
338                 return ret;
339         }
340
341         return LDB_SUCCESS;
342 }