Implement all TRP and TID fields for filtering
[trust_router.git] / common / tr_filter.c
1 /*
2  * Copyright (c) 2012, 2013, 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
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <talloc.h>
39 #include <assert.h>
40
41 #include <tr_filter.h>
42 #include <trp_internal.h>
43 #include <tid_internal.h>
44
45 /* Function types for handling filter fields generally. All target values
46  * are represented as strings in a TR_NAME.
47  */
48
49 /* CMP functions return values like strcmp: 0 on match, <0 on target<val, >0 on target>val */
50 typedef int (*TR_FILTER_FIELD_CMP)(TR_FILTER_TARGET *target, TR_NAME *val);
51 /* get functions return TR_NAME format of the field value. Caller must free it. */
52 typedef TR_NAME *(*TR_FILTER_FIELD_GET)(TR_FILTER_TARGET *target);
53
54 static TR_FILTER_TARGET *tr_filter_target_new(TALLOC_CTX *mem_ctx)
55 {
56   TR_FILTER_TARGET *target=talloc(mem_ctx, TR_FILTER_TARGET);
57   if (target) {
58     target->trp_inforec=NULL;
59     target->trp_upd=NULL;
60     target->tid_req=NULL;
61   }
62   return target;
63 }
64 void tr_filter_target_free(TR_FILTER_TARGET *target)
65 {
66   talloc_free(target);
67 }
68
69 /**
70  * Create a filter target for a TID request. Does not change the context of the request,
71  * so this is only valid until that is freed.
72  *
73  * @param mem_ctx talloc context for the object
74  * @param req TID request object
75  * @return pointer to a TR_FILTER_TARGET structure, or null on allocation failure
76  */
77 TR_FILTER_TARGET *tr_filter_target_tid_req(TALLOC_CTX *mem_ctx, TID_REQ *req)
78 {
79   TR_FILTER_TARGET *target=tr_filter_target_new(mem_ctx);
80   if (target)
81     target->tid_req=req; /* borrowed, not adding to our context */
82   return target;
83 }
84
85 /**
86  * Create a filter target for a TRP inforec. Does not change the context of the inforec or duplicate TR_NAMEs,
87  * so this is only valid until those are freed.
88  *
89  * @param mem_ctx talloc context for the object
90  * @param upd Update containing the TRP inforec
91  * @param inforec TRP inforec
92  * @return pointer to a TR_FILTER_TARGET structure, or null on allocation failure
93  */
94 TR_FILTER_TARGET *tr_filter_target_trp_inforec(TALLOC_CTX *mem_ctx, TRP_UPD *upd, TRP_INFOREC *inforec)
95 {
96   TR_FILTER_TARGET *target=tr_filter_target_new(mem_ctx);
97   if (target) {
98     target->trp_inforec = inforec; /* borrowed, not adding to our context */
99     target->trp_upd=upd;
100   }
101   return target;
102 }
103
104 /** Handler functions for TID RP_REALM field */
105 static int tr_ff_cmp_tid_rp_realm(TR_FILTER_TARGET *target, TR_NAME *val)
106 {
107   return tr_name_cmp(tid_req_get_rp_realm(target->tid_req), val);
108 }
109
110 static TR_NAME *tr_ff_get_tid_rp_realm(TR_FILTER_TARGET *target)
111 {
112   return tr_dup_name(tid_req_get_rp_realm(target->tid_req));
113 }
114
115 /** Handler functions for TRP info_type field */
116 static int tr_ff_cmp_trp_info_type(TR_FILTER_TARGET *target, TR_NAME *val)
117 {
118   TRP_INFOREC *inforec=target->trp_inforec;
119   char *valstr=NULL;
120   int val_type=0;
121
122   assert(val);
123   assert(inforec);
124
125   /* nothing matches unknown */
126   if (inforec->type==TRP_INFOREC_TYPE_UNKNOWN)
127     return 0;
128
129   valstr = tr_name_strdup(val); /* get this as an official null-terminated string */
130   val_type = trp_inforec_type_from_string(valstr);
131   free(valstr);
132
133   /* we do not define an ordering of info types */
134   return (val_type==inforec->type);
135 }
136
137 static TR_NAME *tr_ff_get_trp_info_type(TR_FILTER_TARGET *target)
138 {
139   TRP_INFOREC *inforec=target->trp_inforec;
140   return tr_new_name(trp_inforec_type_to_string(inforec->type));
141 }
142
143 /** Handlers for TRP realm field */
144 static int tr_ff_cmp_trp_realm(TR_FILTER_TARGET *target, TR_NAME *val)
145 {
146   return tr_name_cmp(trp_upd_get_realm(target->trp_upd), val);
147 }
148
149 static TR_NAME *tr_ff_get_trp_realm(TR_FILTER_TARGET *target)
150 {
151   return tr_dup_name(trp_upd_get_realm(target->trp_upd));
152 }
153
154 /** Handlers for TID realm field */
155 static int tr_ff_cmp_tid_realm(TR_FILTER_TARGET *target, TR_NAME *val)
156 {
157   return tr_name_cmp(tid_req_get_realm(target->tid_req), val);
158 }
159
160 static TR_NAME *tr_ff_get_tid_realm(TR_FILTER_TARGET *target)
161 {
162   return tr_dup_name(tid_req_get_realm(target->tid_req));
163 }
164
165 /** Handlers for TRP community field */
166 static int tr_ff_cmp_trp_comm(TR_FILTER_TARGET *target, TR_NAME *val)
167 {
168   return tr_name_cmp(trp_upd_get_comm(target->trp_upd), val);
169 }
170
171 static TR_NAME *tr_ff_get_trp_comm(TR_FILTER_TARGET *target)
172 {
173   return tr_dup_name(trp_upd_get_comm(target->trp_upd));
174 }
175
176 /** Handlers for TID community field */
177 static int tr_ff_cmp_tid_comm(TR_FILTER_TARGET *target, TR_NAME *val)
178 {
179   return tr_name_cmp(tid_req_get_comm(target->tid_req), val);
180 }
181
182 static TR_NAME *tr_ff_get_tid_comm(TR_FILTER_TARGET *target)
183 {
184   return tr_dup_name(tid_req_get_comm(target->tid_req));
185 }
186
187 /** Handlers for TRP community_type field */
188 static TR_NAME *tr_ff_get_trp_comm_type(TR_FILTER_TARGET *target)
189 {
190   TR_NAME *type=NULL;
191
192   switch(trp_inforec_get_comm_type(target->trp_inforec)) {
193     case TR_COMM_APC:
194       type=tr_new_name("apc");
195       break;
196     case TR_COMM_COI:
197       type=tr_new_name("coi");
198       break;
199     default:
200       type=NULL;
201       break; /* unknown types always fail */
202   }
203
204   return type;
205 }
206
207 static int tr_ff_cmp_trp_comm_type(TR_FILTER_TARGET *target, TR_NAME *val)
208 {
209   TR_NAME *type=tr_ff_get_trp_comm_type(target);
210   int retval=0;
211
212   if (type==NULL)
213     retval=1;
214   else {
215     retval = tr_name_cmp(val, type);
216     tr_free_name(type);
217   }
218   return retval;
219 }
220
221 /** Handlers for TRP realm_role field */
222 static TR_NAME *tr_ff_get_trp_realm_role(TR_FILTER_TARGET *target)
223 {
224   TR_NAME *type=NULL;
225
226   switch(trp_inforec_get_role(target->trp_inforec)) {
227     case TR_ROLE_IDP:
228       type=tr_new_name("idp");
229       break;
230     case TR_ROLE_RP:
231       type=tr_new_name("rp");
232       break;
233     default:
234       type=NULL;
235       break; /* unknown types always fail */
236   }
237
238   return type;
239 }
240
241 static int tr_ff_cmp_trp_realm_role(TR_FILTER_TARGET *target, TR_NAME *val)
242 {
243   TR_NAME *type=tr_ff_get_trp_realm_role(target);
244   int retval=0;
245
246   if (type==NULL)
247     retval=1;
248   else {
249     retval = tr_name_cmp(val, type);
250     tr_free_name(type);
251   }
252   return retval;
253 }
254
255 /** Handlers for TRP apc field */
256 static int tr_ff_cmp_trp_apc(TR_FILTER_TARGET *target, TR_NAME *val)
257 {
258   /* TODO: Handle multiple APCs, not just the first */
259   return tr_name_cmp(tr_apc_get_id(trp_inforec_get_apcs(target->trp_inforec)), val);
260 }
261
262 static TR_NAME *tr_ff_get_trp_apc(TR_FILTER_TARGET *target)
263 {
264   /* TODO: Handle multiple APCs, not just the first */
265   return tr_dup_name(tr_apc_get_id(trp_inforec_get_apcs(target->trp_inforec)));
266 }
267
268 /** Handlers for TRP owner_realm field */
269 static int tr_ff_cmp_trp_owner_realm(TR_FILTER_TARGET *target, TR_NAME *val)
270 {
271   return tr_name_cmp(trp_inforec_get_owner_realm(target->trp_inforec), val);
272 }
273
274 static TR_NAME *tr_ff_get_trp_owner_realm(TR_FILTER_TARGET *target)
275 {
276   return tr_dup_name(trp_inforec_get_owner_realm(target->trp_inforec));
277 }
278
279 /** Handlers for TRP trust_router field */
280 static int tr_ff_cmp_trp_trust_router(TR_FILTER_TARGET *target, TR_NAME *val)
281 {
282   return tr_name_cmp(trp_inforec_get_trust_router(target->trp_inforec), val);
283 }
284
285 static TR_NAME *tr_ff_get_trp_trust_router(TR_FILTER_TARGET *target)
286 {
287   return tr_dup_name(trp_inforec_get_trust_router(target->trp_inforec));
288 }
289
290 /** Handlers for TRP owner_contact field */
291 static int tr_ff_cmp_trp_owner_contact(TR_FILTER_TARGET *target, TR_NAME *val)
292 {
293   return tr_name_cmp(trp_inforec_get_owner_contact(target->trp_inforec), val);
294 }
295
296 static TR_NAME *tr_ff_get_trp_owner_contact(TR_FILTER_TARGET *target)
297 {
298   return tr_dup_name(trp_inforec_get_owner_contact(target->trp_inforec));
299 }
300
301 /** Handlers for TID req original_coi field */
302 static int tr_ff_cmp_tid_orig_coi(TR_FILTER_TARGET *target, TR_NAME *val)
303 {
304   return tr_name_cmp(tid_req_get_orig_coi(target->tid_req), val);
305 }
306
307 static TR_NAME *tr_ff_get_tid_orig_coi(TR_FILTER_TARGET *target)
308 {
309   return tr_dup_name(tid_req_get_orig_coi(target->tid_req));
310 }
311
312 /**
313  * Filter field handler table
314  */
315 struct tr_filter_field_entry {
316   TR_FILTER_TYPE filter_type;
317   const char *name;
318   TR_FILTER_FIELD_CMP cmp;
319   TR_FILTER_FIELD_GET get;
320 };
321 static struct tr_filter_field_entry tr_filter_field_table[] = {
322     /* realm */
323     {TR_FILTER_TYPE_TID_INBOUND, "realm", tr_ff_cmp_tid_realm, tr_ff_get_tid_realm},
324     {TR_FILTER_TYPE_TRP_INBOUND, "realm", tr_ff_cmp_trp_realm, tr_ff_get_trp_realm},
325     {TR_FILTER_TYPE_TRP_OUTBOUND, "realm", tr_ff_cmp_trp_realm, tr_ff_get_trp_realm},
326
327     /* community */
328     {TR_FILTER_TYPE_TID_INBOUND, "community", tr_ff_cmp_tid_comm, tr_ff_get_tid_comm},
329     {TR_FILTER_TYPE_TRP_INBOUND, "community", tr_ff_cmp_trp_comm, tr_ff_get_trp_comm},
330     {TR_FILTER_TYPE_TRP_OUTBOUND, "community", tr_ff_cmp_trp_comm, tr_ff_get_trp_comm},
331
332     /* community type */
333     {TR_FILTER_TYPE_TRP_INBOUND, "community_type", tr_ff_cmp_trp_comm_type, tr_ff_get_trp_comm_type},
334     {TR_FILTER_TYPE_TRP_OUTBOUND, "community_type", tr_ff_cmp_trp_comm_type, tr_ff_get_trp_comm_type},
335
336     /* realm role */
337     {TR_FILTER_TYPE_TRP_INBOUND, "realm_role", tr_ff_cmp_trp_realm_role, tr_ff_get_trp_realm_role},
338     {TR_FILTER_TYPE_TRP_OUTBOUND, "realm_role", tr_ff_cmp_trp_realm_role, tr_ff_get_trp_realm_role},
339
340     /* apc */
341     {TR_FILTER_TYPE_TRP_INBOUND, "apc", tr_ff_cmp_trp_apc, tr_ff_get_trp_apc},
342     {TR_FILTER_TYPE_TRP_OUTBOUND, "apc", tr_ff_cmp_trp_apc, tr_ff_get_trp_apc},
343
344     /* trust_router */
345     {TR_FILTER_TYPE_TRP_INBOUND, "trust_router", tr_ff_cmp_trp_trust_router, tr_ff_get_trp_trust_router},
346     {TR_FILTER_TYPE_TRP_OUTBOUND, "trust_router", tr_ff_cmp_trp_trust_router, tr_ff_get_trp_trust_router},
347
348     /* owner_realm */
349     {TR_FILTER_TYPE_TRP_INBOUND, "owner_realm", tr_ff_cmp_trp_owner_realm, tr_ff_get_trp_owner_realm},
350     {TR_FILTER_TYPE_TRP_OUTBOUND, "owner_realm", tr_ff_cmp_trp_owner_realm, tr_ff_get_trp_owner_realm},
351
352     /* owner_contact */
353     {TR_FILTER_TYPE_TRP_INBOUND, "owner_contact", tr_ff_cmp_trp_owner_contact, tr_ff_get_trp_owner_contact},
354     {TR_FILTER_TYPE_TRP_OUTBOUND, "owner_contact", tr_ff_cmp_trp_owner_contact, tr_ff_get_trp_owner_contact},
355
356     /* rp_realm */
357     {TR_FILTER_TYPE_TID_INBOUND, "rp_realm", tr_ff_cmp_tid_rp_realm, tr_ff_get_tid_rp_realm},
358
359     /* original coi */
360     {TR_FILTER_TYPE_TID_INBOUND, "original_coi", tr_ff_cmp_tid_orig_coi, tr_ff_get_tid_orig_coi},
361
362     /* info_type */
363     {TR_FILTER_TYPE_TRP_INBOUND, "info_type", tr_ff_cmp_trp_info_type, tr_ff_get_trp_info_type},
364     {TR_FILTER_TYPE_TRP_OUTBOUND, "info_type", tr_ff_cmp_trp_info_type, tr_ff_get_trp_info_type},
365
366     /* Unknown */
367     {TR_FILTER_TYPE_UNKNOWN, NULL } /* This must be the final entry */
368 };
369
370 /* TODO: support TRP metric field (requires > < comparison instead of wildcard match) */
371
372 static struct tr_filter_field_entry *tr_filter_field_entry(TR_FILTER_TYPE filter_type, TR_NAME *field_name)
373 {
374   unsigned int ii;
375
376   for (ii=0; tr_filter_field_table[ii].filter_type!=TR_FILTER_TYPE_UNKNOWN; ii++) {
377     if ((tr_filter_field_table[ii].filter_type==filter_type)
378         && (tr_name_cmp_str(field_name, tr_filter_field_table[ii].name)==0)) {
379       return tr_filter_field_table+ii;
380     }
381   }
382   return NULL;
383 }
384
385 /**
386  * Apply a filter to a target record or TID request.
387  *
388  * If one of the filter lines matches, out_action is set to the applicable action. If constraints
389  * is not NULL, the constraints from the matching filter line will be added to the constraint set
390  * *constraints, or to a new one if *constraints is NULL. In this case, TR_FILTER_MATCH will be
391  * returned.
392  *
393  * If there is no match, returns TR_FILTER_NO_MATCH, out_action is undefined, and constraints
394  * will not be changed.
395  *
396  * @param target Record or request to which the filter is applied
397  * @param filt Filter to apply
398  * @param constraints Pointer to existing set of constraints (NULL if not tracking constraints)
399  * @param out_action Action to be carried out (output)
400  * @return TR_FILTER_MATCH or TR_FILTER_NO_MATCH
401  */
402 int tr_filter_apply(TR_FILTER_TARGET *target,
403                     TR_FILTER *filt,
404                     TR_CONSTRAINT_SET **constraints,
405                     TR_FILTER_ACTION *out_action)
406 {
407   unsigned int ii=0, jj=0;
408   int retval=TR_FILTER_NO_MATCH;
409
410   /* Default action is reject */
411   *out_action = TR_FILTER_ACTION_REJECT;
412
413   /* Validate filter */
414   if ((filt==NULL) || (filt->type==TR_FILTER_TYPE_UNKNOWN))
415     return TR_FILTER_NO_MATCH;
416
417   /* Step through filter lines looking for a match. If a line matches, retval
418    * will be set to TR_FILTER_MATCH, so stop then. */
419   for (ii=0, retval=TR_FILTER_NO_MATCH;
420        ii<TR_MAX_FILTER_LINES;
421        ii++) {
422     /* skip empty lines (these shouldn't really happen) */
423     if (filt->lines[ii]==NULL)
424       continue;
425
426     /* Assume we are going to succeed. If any specs fail to match, we'll set
427      * this to TR_FILTER_NO_MATCH. */
428     retval=TR_FILTER_MATCH;
429     for (jj=0; jj<TR_MAX_FILTER_SPECS; jj++) {
430       /* skip empty specs (these shouldn't really happen either) */
431       if (filt->lines[ii]->specs[jj]==NULL)
432         continue;
433
434       if (!tr_fspec_matches(filt->lines[ii]->specs[jj], filt->type, target)) {
435         retval=TR_FILTER_NO_MATCH; /* set this in case this is the last filter line */
436         break; /* give up on this filter line */
437       }
438     }
439
440     if (retval==TR_FILTER_MATCH)
441       break;
442   }
443
444   if (retval==TR_FILTER_MATCH) {
445     /* Matched line ii. Grab its action and constraints. */
446     *out_action = filt->lines[ii]->action;
447     if (constraints!=NULL) {
448       /* if either constraint is missing, these are no-ops */
449       tr_constraint_add_to_set(constraints, filt->lines[ii]->realm_cons);
450       tr_constraint_add_to_set(constraints, filt->lines[ii]->domain_cons);
451     }
452   }
453
454   return retval;
455 }
456
457 void tr_fspec_free(TR_FSPEC *fspec)
458 {
459   talloc_free(fspec);
460 }
461
462 static int tr_fspec_destructor(void *obj)
463 {
464   TR_FSPEC *fspec = talloc_get_type_abort(obj, TR_FSPEC);
465   size_t ii;
466
467   if (fspec->field != NULL)
468     tr_free_name(fspec->field);
469   for (ii=0; ii<TR_MAX_FILTER_SPEC_MATCHES; ii++) {
470     if (fspec->match[ii] != NULL)
471       tr_free_name(fspec->match[ii]);
472   }
473   return 0;
474 }
475
476 TR_FSPEC *tr_fspec_new(TALLOC_CTX *mem_ctx)
477 {
478   TR_FSPEC *fspec = talloc(mem_ctx, TR_FSPEC);
479   size_t ii=0;
480
481   if (fspec != NULL) {
482     fspec->field = NULL;
483     for (ii=0; ii<TR_MAX_FILTER_SPEC_MATCHES; ii++)
484       fspec->match[ii] = NULL;
485
486     talloc_set_destructor((void *)fspec, tr_fspec_destructor);
487   }
488   return fspec;
489 }
490
491 void tr_fspec_add_match(TR_FSPEC *fspec, TR_NAME *match)
492 {
493   size_t ii;
494   for (ii=0; ii<TR_MAX_FILTER_SPEC_MATCHES; ii++) {
495     if (fspec->match[ii]==NULL) {
496       fspec->match[ii]=match;
497       break;
498     }
499   }
500   /* TODO: handle case that adding the match failed */
501 }
502
503 /* returns 1 if the spec matches */
504 int tr_fspec_matches(TR_FSPEC *fspec, TR_FILTER_TYPE ftype, TR_FILTER_TARGET *target)
505 {
506   struct tr_filter_field_entry *field=NULL;
507   TR_NAME *name=NULL;
508   int retval=0;
509
510   size_t ii=0;
511
512   if (fspec==NULL)
513     return 0;
514
515   /* Look up how to handle the requested field */
516   field = tr_filter_field_entry(ftype, fspec->field);
517   if (field==NULL)
518     return 0;
519
520   name=field->get(target);
521   for (ii=0; ii<TR_MAX_FILTER_SPEC_MATCHES; ii++) {
522     if (fspec->match[ii]!=NULL) {
523       if (tr_name_prefix_wildcard_match(name, fspec->match[ii]))
524         retval=1;
525     }
526   }
527   tr_free_name(name);
528   return retval;
529 }
530
531 void tr_fline_free(TR_FLINE *fline)
532 {
533   talloc_free(fline);
534 }
535
536 TR_FLINE *tr_fline_new(TALLOC_CTX *mem_ctx)
537 {
538   TR_FLINE *fl = talloc(mem_ctx, TR_FLINE);
539   int ii = 0;
540
541   if (fl != NULL) {
542     fl->action = TR_FILTER_ACTION_UNKNOWN;
543     fl->realm_cons = NULL;
544     fl->domain_cons = NULL;
545     for (ii = 0; ii < TR_MAX_FILTER_SPECS; ii++)
546       fl->specs[ii] = NULL;
547   }
548   return fl;
549 }
550
551 TR_FILTER *tr_filter_new(TALLOC_CTX *mem_ctx)
552 {
553   TR_FILTER *f = talloc(mem_ctx, TR_FILTER);
554   int ii = 0;
555
556   if (f != NULL) {
557     f->type = TR_FILTER_TYPE_UNKNOWN;
558     for (ii = 0; ii < TR_MAX_FILTER_LINES; ii++)
559       f->lines[ii] = NULL;
560   }
561   return f;
562 }
563
564 void tr_filter_free(TR_FILTER *filt)
565 {
566   talloc_free(filt);
567 }
568
569 void tr_filter_set_type(TR_FILTER *filt, TR_FILTER_TYPE type)
570 {
571   filt->type = type;
572 }
573
574 TR_FILTER_TYPE tr_filter_get_type(TR_FILTER *filt)
575 {
576   return filt->type;
577 }
578
579 /**
580  * Check that a filter is valid, i.e., can be processed.
581  *
582  * @param filt Filter to verify
583  * @return 1 if the filter is valid, 0 otherwise
584  */
585 int tr_filter_validate(TR_FILTER *filt)
586 {
587   size_t ii=0, jj=0, kk=0;
588
589   if (!filt)
590     return 0;
591
592   /* check that we recognize the type */
593   switch(filt->type) {
594     case TR_FILTER_TYPE_TID_INBOUND:
595     case TR_FILTER_TYPE_TRP_INBOUND:
596     case TR_FILTER_TYPE_TRP_OUTBOUND:
597       break;
598
599     default:
600       return 0; /* if we get here, either TR_FILTER_TYPE_UNKNOWN or an invalid value was found */
601   }
602   for (ii=0; ii<TR_MAX_FILTER_LINES; ii++) {
603     if (filt->lines[ii]==NULL)
604       continue; /* an empty filter line is valid */
605
606     /* check that we recognize the action */
607     switch(filt->lines[ii]->action) {
608       case TR_FILTER_ACTION_ACCEPT:
609       case TR_FILTER_ACTION_REJECT:
610         break;
611
612       default:
613         /* if we get here, either TR_FILTER_ACTION_UNKNOWN or an invalid value was found */
614         return 0;
615     }
616
617     for (jj=0; jj<TR_MAX_FILTER_SPECS; jj++) {
618       if (filt->lines[ii]->specs[jj]==NULL)
619         continue; /* an empty filter spec is valid */
620
621       if (!tr_filter_validate_spec_field(filt->type, filt->lines[ii]->specs[jj]))
622         return 0;
623
624       /* check that at least one match is non-null */
625       for (kk=0; kk<TR_MAX_FILTER_SPEC_MATCHES; kk++) {
626         if (filt->lines[ii]->specs[jj]->match[kk]!=NULL)
627           break;
628       }
629       if (kk==TR_MAX_FILTER_SPEC_MATCHES)
630         return 0;
631     }
632   }
633
634   /* We ran the gauntlet. Success! */
635   return 1;
636 }
637
638 int tr_filter_validate_spec_field(TR_FILTER_TYPE ftype, TR_FSPEC *fspec)
639 {
640   if ((fspec==NULL) || (tr_filter_field_entry(ftype, fspec->field)==NULL))
641     return 0; /* unknown field */
642
643   return 1;
644 }
645
646 /**
647  * Allocate a new filter set.
648  *
649  * @param mem_ctx Talloc context for the new set
650  * @return Pointer to new set, or null on error
651  */
652 TR_FILTER_SET *tr_filter_set_new(TALLOC_CTX *mem_ctx)
653 {
654   TR_FILTER_SET *set=talloc(mem_ctx, TR_FILTER_SET);
655   if (set!=NULL) {
656     set->next=NULL;
657     set->this=NULL;
658   }
659   return set;
660 }
661
662 /**
663  * Free a filter set
664  *
665  * @param fs Filter set to free
666  */
667 void tr_filter_set_free(TR_FILTER_SET *fs)
668 {
669   talloc_free(fs);
670 }
671
672 /**
673  * Find the tail of the filter set linked list.
674  *
675  * @param set Set to find tail of
676  * @return Last element in the list
677  */
678 static TR_FILTER_SET *tr_filter_set_tail(TR_FILTER_SET *set)
679 {
680   while (set->next)
681     set=set->next;
682   return set;
683 }
684
685 /**
686  * Add new filter to filter set.
687  *
688  * @param set Filter set
689  * @param new New filter to add
690  * @return 0 on success, nonzero on error
691  */
692 int tr_filter_set_add(TR_FILTER_SET *set, TR_FILTER *new)
693 {
694   TR_FILTER_SET *tail=NULL;
695
696   if (set->this==NULL)
697     tail=set;
698   else {
699     tail=tr_filter_set_tail(set);
700     tail->next=tr_filter_set_new(set);
701     if (tail->next==NULL)
702       return 1;
703     tail=tail->next;
704   }
705   tail->this=new;
706   talloc_steal(tail, new);
707   return 0;
708 }
709
710 /**
711  * Find a filter of a given type in the filter set. If there are multiple, returns the first one.
712  *
713  * @param set Filter set to search
714  * @param type Type of filter to find
715  * @return Borrowed pointer to the filter, or null if no filter of that type is found
716  */
717 TR_FILTER *tr_filter_set_get(TR_FILTER_SET *set, TR_FILTER_TYPE type)
718 {
719   TR_FILTER_SET *cur=set;
720   while(cur!=NULL) {
721     if ((cur->this != NULL) && (cur->this->type == type))
722       return cur->this;
723     cur=cur->next;
724   }
725   return NULL;
726 }
727
728 TR_FILTER_TYPE filter_type[]={TR_FILTER_TYPE_TID_INBOUND,
729                               TR_FILTER_TYPE_TRP_INBOUND,
730                               TR_FILTER_TYPE_TRP_OUTBOUND};
731 const char *filter_label[]={"tid_inbound",
732                             "trp_inbound",
733                             "trp_outbound"};
734 size_t num_filter_types=sizeof(filter_type)/sizeof(filter_type[0]);
735
736 const char *tr_filter_type_to_string(TR_FILTER_TYPE ftype)
737 {
738   size_t ii=0;
739
740   for (ii=0; ii<num_filter_types; ii++) {
741     if (ftype==filter_type[ii])
742       return filter_label[ii];
743   }
744   return "unknown";
745 }
746
747 TR_FILTER_TYPE tr_filter_type_from_string(const char *s)
748 {
749   size_t ii=0;
750
751   for(ii=0; ii<num_filter_types; ii++) {
752     if (0==strcmp(s, filter_label[ii]))
753       return filter_type[ii];
754   }
755   return TR_FILTER_TYPE_UNKNOWN;
756 }
757