Add/move some comments.
[trust_router.git] / common / tr_constraint.c
1 /*
2  * Copyright (c) 2012-2014, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 #include <jansson.h>
35 #include "jansson_iterators.h"
36 #include <assert.h>
37 #include <talloc.h>
38
39 #include <tr_filter.h>
40 #include <tr_debug.h>
41
42 #include <trust_router/tr_constraint.h>
43 #include <tid_internal.h>
44
45
46 static int tr_constraint_destructor(void *obj)
47 {
48   TR_CONSTRAINT *cons = talloc_get_type_abort(obj, TR_CONSTRAINT);
49   int ii = 0;
50
51   if (cons->type != NULL)
52     tr_free_name(cons->type);
53   for (ii = 0; ii < TR_MAX_CONST_MATCHES; ii++) {
54     if (cons->matches[ii] != NULL)
55       tr_free_name(cons->matches[ii]);
56   }
57   return 0;
58 }
59
60 TR_CONSTRAINT *tr_constraint_new(TALLOC_CTX *mem_ctx)
61 {
62   TR_CONSTRAINT *cons = talloc(mem_ctx, TR_CONSTRAINT);
63   int ii = 0;
64
65   if (cons != NULL) {
66     cons->type = NULL;
67     for (ii = 0; ii < TR_MAX_CONST_MATCHES; ii++)
68       cons->matches[ii] = NULL;
69     talloc_set_destructor((void *) cons, tr_constraint_destructor);
70   }
71   return cons;
72 }
73
74 void tr_constraint_free(TR_CONSTRAINT *cons)
75 {
76   talloc_free(cons);
77 }
78
79 TR_CONSTRAINT *tr_constraint_dup(TALLOC_CTX *mem_ctx, TR_CONSTRAINT *cons)
80 {
81   TALLOC_CTX *tmp_ctx = NULL;
82   TR_CONSTRAINT *new = NULL;
83   int ii = 0;
84
85   if (cons == NULL)
86     return NULL;
87
88   tmp_ctx = talloc_new(NULL);
89   new = tr_constraint_new(tmp_ctx);
90
91   if (new != NULL) {
92     new->type = tr_dup_name(cons->type);
93     for (ii = 0; ii < TR_MAX_CONST_MATCHES; ii++)
94       new->matches[ii] = tr_dup_name(cons->matches[ii]);
95     talloc_steal(mem_ctx, new);
96   }
97
98   talloc_free(tmp_ctx);
99   return new;
100 }
101
102 /* Returns TRUE (1) if the the string (str) matches the wildcard string (wc_str), FALSE (0) if not.
103  * Allows for a single '*' as the wildcard character if it is the first character. Leading white
104  * space is significant.
105  */
106 int tr_prefix_wildcard_match(const char *str, const char *wc_str)
107 {
108   const char *wc_post = wc_str;
109   size_t len = 0;
110   size_t wc_len = 0;
111
112   if ((!str) || (!wc_str))
113     return 0;
114
115   len = strlen(str);
116   if (0 == (wc_len = strlen(wc_str)))
117     return 0;
118
119   /* TBD -- skip leading white space? */
120   if ('*' == wc_str[0]) {
121     wc_post = &(wc_str[1]);
122     wc_len--;
123   } else if (len != wc_len)
124     return 0;
125
126
127   if (wc_len > len)
128     return 0;
129
130   if (0 == strcmp(&(str[len - wc_len]), wc_post)) {
131     return 1;
132   } else
133     return 0;
134 }
135
136 /* This combines the two constraints in a filter line (TR_FLINE) into a single
137  * set with two constraints. */
138 TR_CONSTRAINT_SET *tr_constraint_set_from_fline(TR_FLINE *fline)
139 {
140   json_t *cset = NULL;
141
142   if (!fline)
143     return NULL;
144
145   if (fline->realm_cons)
146     tr_constraint_add_to_set((TR_CONSTRAINT_SET **) &cset, fline->realm_cons);
147   if (fline->domain_cons)
148     tr_constraint_add_to_set((TR_CONSTRAINT_SET **) &cset, fline->domain_cons);
149
150   return (TR_CONSTRAINT_SET *) cset;
151 }
152
153 /* A constraint set is represented in json as an array of constraint
154  * objects.  So, a constraint set (cset) that consists of one realm
155  * constraint and one domain constraint might look like:
156  *
157  *      {cset: [{domain: [a.com, b.co.uk]},
158  *              {realm: [c.net, d.org]}]}
159  *
160  * This routine takes a TR_CONSTRAINT, converts it to its JSON representation,
161  * and adds that to the TR_CONSTRAINT_SET.
162  */
163 void tr_constraint_add_to_set(TR_CONSTRAINT_SET **cset, TR_CONSTRAINT *cons)
164 {
165   json_t *jcons = NULL;
166   json_t *jmatches = NULL;
167   int i = 0;
168
169   if ((!cset) || (!cons))
170     return;
171
172   /* If we don't already have a json object, create one */
173   if (!(*cset))
174     *cset = (TR_CONSTRAINT_SET *) json_array();
175
176   /* Create a json object representing cons */
177   jmatches = json_array();
178   jcons = json_object();
179
180   for (i = 0; ((i < TR_MAX_CONST_MATCHES) && (NULL != cons->matches[i])); i++) {
181     json_array_append_new(jmatches, json_string(cons->matches[i]->buf));
182   }
183
184   json_object_set_new(jcons, cons->type->buf, jmatches);
185
186   /* Add the created object to the cset object */
187   json_array_append_new((json_t *) *cset, jcons);
188 }
189
190 /* Test whether a JSON object has a valid structure
191  * to represent a constraint set.
192  */
193 int tr_constraint_set_validate(TR_CONSTRAINT_SET *cset) {
194   json_t *json = (json_t *) cset;
195   size_t i;
196   json_t *set_member;
197   if (!json_is_array(json)) {
198     tr_debug("Constraint_set is not an array");
199     return 0;
200   }
201   json_array_foreach(json, i, set_member) {
202     json_t *value;
203     const char *key;
204     if (!json_is_object(set_member)) {
205       tr_debug("Constraint member at %zu is not an object\n", i);
206       return 0;
207     }
208     json_object_foreach(set_member, key, value) {
209       size_t inner_index;
210       json_t *inner_value;
211       if (!json_is_array(value)) {
212         tr_debug("Constraint type %s at index %zu in constraint set is not an array\n", key,
213                  i);
214         return 0;
215       }
216       json_array_foreach(value, inner_index, inner_value) {
217         if (!json_is_string(inner_value)) {
218           tr_debug("Constraint type %s at index %zu in constraint set has non-string element %zu\n",
219                    key, i, inner_index);
220           return 0;
221         }
222       }
223     }
224   }
225   return 1;
226 }
227
228
229 /**
230  * Create a new constraint set containing all constraints from #orig
231  * with constraint_type #constraint_type and no others.  This constraint set is
232  * live until #request is freed.
233  * TODO: use or remove the request parameter.
234  */
235 TR_CONSTRAINT_SET *tr_constraint_set_filter(TID_REQ *request,
236                                             TR_CONSTRAINT_SET *orig,
237                                             const char *constraint_type)
238 {
239   json_t *orig_cset = (json_t *) orig;
240   json_t *new_cs = NULL;
241   size_t index;
242   json_t *set_member;
243   if (!tr_constraint_set_validate((TR_CONSTRAINT_SET *) orig_cset)) {
244     tr_debug ("tr_constraint_set_filter: not a valid constraint set\n");
245     return NULL;
246   }
247   assert (new_cs = json_array());
248   json_array_foreach(orig_cset, index, set_member) {
249     if (json_object_get(set_member, constraint_type))
250       json_array_append(new_cs, set_member);
251   }
252   return (TR_CONSTRAINT_SET *) new_cs;
253 }
254
255 /**
256  * Within a given constraint object merge any overlapping domain or
257  * realm constraints.  For example ['*','*.net'] can be simplified to
258  * ['*']
259  */
260 static void merge_constraints(json_t *constraint, const char *key)
261 {
262   json_t *value_1, *value_2, *constraint_array;
263   size_t index_1, index_2;
264   /*
265    * Go through the loop pairwise linear, removing elements where one
266    * element is a subset of the other.  Always shrik the array from
267    * the end so that index_1 never becomes invalid (swapping if
268    * needed).
269    */
270   constraint_array = json_object_get(constraint, key);
271   if (NULL == constraint_array)
272     return;
273   json_array_foreach(constraint_array, index_1, value_1)json_array_foreach(constraint_array, index_2, value_2) {
274       if (index_2 <= index_1)
275         continue;
276       if (tr_prefix_wildcard_match(json_string_value(value_2),
277                                    json_string_value(value_1))) {
278         json_array_remove(constraint_array, index_2);
279         index_2--;
280       } else if (tr_prefix_wildcard_match(json_string_value(value_1),
281                                           json_string_value(value_2))) {
282         json_array_set(constraint_array, index_1, value_2);
283         json_array_remove(constraint_array, index_2);
284         index_2--;
285       }
286     }
287 }
288
289 /**
290  * Returns an array of constraint strings that is the intersection of
291  * all constraints in the constraint_set of type #type
292  */
293 static json_t *constraint_intersect_internal(TR_CONSTRAINT_SET *constraints,
294                                              const char *constraint_type)
295 {
296   json_t *constraint, *result = NULL;
297   size_t i;
298   json_array_foreach((json_t *) constraints, i, constraint) {
299     merge_constraints(constraint, constraint_type);
300     if (NULL == result) {
301       result = json_object_get(constraint, constraint_type);
302       if (NULL != result)
303         result = json_copy(result);
304     } else {
305       json_t *intersect, *value_1, *value_2;
306       size_t index_1, index_2;
307       intersect = json_object_get(constraint, constraint_type);
308       /*If an element of the constraint set doesn't have a particular
309        * constraint type, we ignore that element of the constraint set.
310        * However, if no element of the constraint set has a particular
311        *     constraint type we return empty (no access) rather than universal
312        * access.*/
313       if (!intersect)
314         continue;
315       result_loop:
316       json_array_foreach(result, index_1, value_1) {
317         json_array_foreach(intersect, index_2, value_2) {
318           if (tr_prefix_wildcard_match(json_string_value(value_1),
319                                        json_string_value(value_2)))
320             goto result_acceptable;
321           else if (tr_prefix_wildcard_match(json_string_value(value_2),
322                                             json_string_value(value_1))) {
323             json_array_set(result, index_1, value_2);
324             goto result_acceptable;
325           }
326         }
327         json_array_remove(result, index_1);
328         if (index_1 == 0)
329           goto result_loop;
330         index_1--;
331         result_acceptable:
332         continue;
333       }
334     }
335   }
336   return result;
337 }
338
339 /**
340  * Return the intersection of domain and realm constraints.
341  * Return is live until #request is freed.
342  */
343 TR_CONSTRAINT_SET *tr_constraint_set_intersect(TID_REQ *request,
344                                                TR_CONSTRAINT_SET *input)
345 {
346   json_t *domain = NULL, *realm = NULL;
347   json_t *result = NULL, *result_array = NULL;
348   if (tr_constraint_set_validate(input)) {
349     domain = constraint_intersect_internal(input, "domain");
350     realm = constraint_intersect_internal(input, "realm");
351   }
352   assert(result = json_object());
353   if (domain)
354     json_object_set_new(result, "domain", domain);
355   if (realm)
356     json_object_set_new(result, "realm", realm);
357   assert(result_array = json_array());
358   json_array_append_new(result_array, result);
359   tid_req_cleanup_json(request, result_array);
360   return (TR_CONSTRAINT_SET *) result_array;
361 }
362
363 /** Get the set of wildcard strings that matches a fully intersected
364  * constraint set.  Requires that the constraint set only have one
365  * constraint in it, but the constraint may have multiple matches for
366  * a given type.  Returns true on success false on failure.  The
367  * output is live as long as the request is live.
368  */
369 int tr_constraint_set_get_match_strings(TID_REQ *request,
370                                         TR_CONSTRAINT_SET *constraints,
371                                         const char *constraint_type,
372                                         tr_const_string **output,
373                                         size_t *output_len)
374 {
375   json_t *cset = (json_t *) constraints;
376   json_t *member, *matches, *value;;
377   size_t index, array_size;
378   assert (output && output_len);
379   *output = NULL;
380   *output_len = 0;
381   if (json_array_size(cset) != 1) {
382     tr_debug("Constraint set for get_match_strings has more than one member\n");
383     return -1;
384   }
385   member = json_array_get(cset, 0);
386   matches = json_object_get(member, constraint_type);
387   if (!matches)
388     return -1;
389   array_size = json_array_size(matches);
390   if (array_size == 0)
391     return -1;
392   *output = talloc_array_ptrtype(request, *output, array_size);
393   json_array_foreach(matches, index, value)(*output)[index] = json_string_value(value);
394   *output_len = array_size;
395   return 0;
396 }