ldb-samba: Implement transitive extended matching
[amitay/samba.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                 visited = talloc_array(mem_ctx, struct dsdb_dn *, 1);
111                 if (visited == NULL) {
112                         talloc_free(tmp_ctx);
113                         return LDB_ERR_OPERATIONS_ERROR;
114                 }
115                 visited[0] = to_visit;
116                 (*visited_count) = 1;
117         } else {
118                 visited = talloc_realloc(mem_ctx, visited, struct dsdb_dn *,
119                                          (*visited_count) + 1);
120                 if (visited == NULL) {
121                         talloc_free(tmp_ctx);
122                         return LDB_ERR_OPERATIONS_ERROR;
123                 }
124                 visited[(*visited_count)] = to_visit;
125                 (*visited_count)++;
126         }
127
128         /*
129          * steal to_visit into visited array context, as it has to live until
130          * the array is freed.
131          */
132         talloc_steal(visited, to_visit);
133
134         /*
135          * Iterate over the values of the attribute of the entry being
136          * visited (to_visit) and follow them, calling this function
137          * recursively.
138          * If the value is in the visited array, skip it.
139          * Otherwise, follow the link and visit it.
140          */
141         for (i=0; i<el->num_values; i++) {
142                 struct dsdb_dn *next_to_visit;
143                 bool skip = false;
144
145                 next_to_visit = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], dn_oid);
146                 if (next_to_visit == NULL) {
147                         talloc_free(tmp_ctx);
148                         *matched = false;
149                         return LDB_ERR_INVALID_DN_SYNTAX;
150                 }
151
152                 /*
153                  * If the value is already in the visited array, skip it.
154                  * Note the last element of the array is ignored because it is
155                  * the current entry DN.
156                  */
157                 for (j=0; j < (*visited_count) - 1; j++) {
158                         struct dsdb_dn *visited_dn = visited[j];
159                         if (ldb_dn_compare(visited_dn->dn,
160                                            next_to_visit->dn) == 0) {
161                                 skip = true;
162                                 break;
163                         }
164                 }
165                 if (skip) {
166                         talloc_free(next_to_visit);
167                         continue;
168                 }
169
170                 /* If the value is not in the visited array, evaluate it */
171                 ret = ldb_eval_transitive_filter_helper(tmp_ctx, ldb, attr,
172                                                         dn_to_match, dn_oid,
173                                                         next_to_visit,
174                                                         visited, visited_count,
175                                                         matched);
176                 if (ret != LDB_SUCCESS) {
177                         talloc_free(tmp_ctx);
178                         return ret;
179                 }
180                 if (*matched) {
181                         talloc_free(tmp_ctx);
182                         return LDB_SUCCESS;
183                 }
184         }
185
186         talloc_free(tmp_ctx);
187         *matched = false;
188         return LDB_SUCCESS;
189 }
190
191 /*
192  * This function parses the linked attribute value to match, whose syntax
193  * will be one of the different DN syntaxes, into a ldb_dn struct.
194  */
195 static int ldb_eval_transitive_filter(TALLOC_CTX *mem_ctx,
196                                       struct ldb_context *ldb,
197                                       const char *attr,
198                                       const struct ldb_val *value_to_match,
199                                       struct dsdb_dn *current_object_dn,
200                                       bool *matched)
201 {
202         const struct dsdb_schema *schema;
203         const struct dsdb_attribute *schema_attr;
204         struct dsdb_dn *dn_to_match;
205         const char *dn_oid;
206         unsigned int count;
207
208         schema = dsdb_get_schema(ldb, mem_ctx);
209         if (schema == NULL) {
210                 return LDB_ERR_OPERATIONS_ERROR;
211         }
212
213         schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attr);
214         if (schema_attr == NULL) {
215                 return LDB_ERR_NO_SUCH_ATTRIBUTE;
216         }
217
218         /* This is the DN syntax of the attribute being matched */
219         dn_oid = schema_attr->syntax->ldap_oid;
220
221         /*
222          * Build a ldb_dn struct holding the value to match, which is the
223          * value entered in the search filter
224          */
225         dn_to_match = dsdb_dn_parse(mem_ctx, ldb, value_to_match, dn_oid);
226         if (dn_to_match == NULL) {
227                 *matched = false;
228                 return LDB_ERR_INVALID_DN_SYNTAX;
229         }
230
231         return ldb_eval_transitive_filter_helper(mem_ctx, ldb, attr,
232                                                  dn_to_match, dn_oid,
233                                                  current_object_dn,
234                                                  NULL, &count, matched);
235 }
236
237 /*
238  * This rule provides recursive search of a link attribute
239  *
240  * Documented in [MS-ADTS] section 3.1.1.3.4.4.3 LDAP_MATCHING_RULE_TRANSITIVE_EVAL
241  * This allows a search filter such as:
242  *
243  * member:1.2.840.113556.1.4.1941:=cn=user,cn=users,dc=samba,dc=example,dc=com
244  *
245  * This searches not only the member attribute, but also any member
246  * attributes that point at an object with this member in them.  All the
247  * various DN syntax types are supported, not just plain DNs.
248  *
249  */
250 static int ldb_comparator_trans(struct ldb_context *ldb,
251                                 const char *oid,
252                                 const struct ldb_message *msg,
253                                 const char *attribute_to_match,
254                                 const struct ldb_val *value_to_match,
255                                 bool *matched)
256 {
257         const struct dsdb_schema *schema;
258         const struct dsdb_attribute *schema_attr;
259         struct ldb_dn *msg_dn;
260         struct dsdb_dn *dsdb_msg_dn;
261         TALLOC_CTX *tmp_ctx;
262         int ret;
263
264         tmp_ctx = talloc_new(ldb);
265         if (tmp_ctx == NULL) {
266                 return LDB_ERR_OPERATIONS_ERROR;
267         }
268
269         /*
270          * If the target attribute to match is not a linked attribute, then
271          * the filter evaluates to undefined
272          */
273         schema = dsdb_get_schema(ldb, tmp_ctx);
274         if (schema == NULL) {
275                 talloc_free(tmp_ctx);
276                 return LDB_ERR_OPERATIONS_ERROR;
277         }
278
279         schema_attr = dsdb_attribute_by_lDAPDisplayName(schema, attribute_to_match);
280         if (schema_attr == NULL) {
281                 talloc_free(tmp_ctx);
282                 return LDB_ERR_NO_SUCH_ATTRIBUTE;
283         }
284
285         /*
286          * This extended match filter is only valid for linked attributes,
287          * following the MS definition (the schema attribute has a linkID
288          * defined). See dochelp request 114111212024789 on cifs-protocols
289          * mailing list.
290          */
291         if (schema_attr->linkID == 0) {
292                 talloc_free(tmp_ctx);
293                 return LDB_ERR_INAPPROPRIATE_MATCHING;
294         }
295
296         /* Duplicate original msg dn as the msg must not be modified */
297         msg_dn = ldb_dn_copy(tmp_ctx, msg->dn);
298         if (msg_dn == NULL) {
299                 talloc_free(tmp_ctx);
300                 return LDB_ERR_OPERATIONS_ERROR;
301         }
302
303         /*
304          * Build a dsdb dn from the message copied DN, which should be a plain
305          * DN syntax.
306          */
307         dsdb_msg_dn = dsdb_dn_construct(tmp_ctx, msg_dn, data_blob_null,
308                                         LDB_SYNTAX_DN);
309         if (dsdb_msg_dn == NULL) {
310                 *matched = false;
311                 return LDB_ERR_INVALID_DN_SYNTAX;
312         }
313
314         ret = ldb_eval_transitive_filter(tmp_ctx, ldb,
315                                          attribute_to_match,
316                                          value_to_match,
317                                          dsdb_msg_dn, matched);
318         talloc_free(tmp_ctx);
319         return ret;
320 }
321
322
323 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
324 {
325         struct ldb_extended_match_rule *transitive_eval;
326         int ret;
327
328         transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
329         transitive_eval->oid = SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL;
330         transitive_eval->callback = ldb_comparator_trans;
331         ret = ldb_register_extended_match_rule(ldb, transitive_eval);
332         if (ret != LDB_SUCCESS) {
333                 talloc_free(transitive_eval);
334                 return ret;
335         }
336
337         return LDB_SUCCESS;
338 }