/freeDiameter-1.1.2/extensions/rt_default/rtd_rules.c
C | 608 lines | 397 code | 96 blank | 115 comment | 96 complexity | bc98232d08f35e566d9cf7a7fc69b580 MD5 | raw file
Possible License(s): BSD-3-Clause
- /*********************************************************************************************************
- * Software License Agreement (BSD License) *
- * Author: Sebastien Decugis <sdecugis@freediameter.net> *
- * *
- * Copyright (c) 2011, WIDE Project and NICT *
- * All rights reserved. *
- * *
- * Redistribution and use of this software in source and binary forms, with or without modification, are *
- * permitted provided that the following conditions are met: *
- * *
- * * Redistributions of source code must retain the above *
- * copyright notice, this list of conditions and the *
- * following disclaimer. *
- * *
- * * Redistributions in binary form must reproduce the above *
- * copyright notice, this list of conditions and the *
- * following disclaimer in the documentation and/or other *
- * materials provided with the distribution. *
- * *
- * * Neither the name of the WIDE Project or NICT nor the *
- * names of its contributors may be used to endorse or *
- * promote products derived from this software without *
- * specific prior written permission of WIDE Project and *
- * NICT. *
- * *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
- *********************************************************************************************************/
- #include "rt_default.h"
- /* The regular expressions header */
- #include <regex.h>
- /* We will search for each candidate peer all the rules that are defined, and check which one applies to the message
- * Therefore our repository is organized hierarchicaly.
- * At the top level, we have two lists of TARGETS (one for IDENTITY, one for REALM), ordered as follow:
- * - first, the TARGETS defined with a regular expression. We will try matching all regexp to all candidates in the list.
- * - then, the TARGETS defined by a plain text. We don't have to compare the whole list to each candidate since the list is ordered.
- *
- * Under each TARGET element, we have the list of RULES that are defined for this target, ordered by CRITERIA type, then is_regex, then string value.
- *
- * Note: Except during configuration parsing and module termination, the lists are only ever accessed read-only, so we do not need a lock.
- */
- /* Structure to hold the data that is used for matching. */
- struct match_data {
- int is_regex; /* determines how the string is matched */
- char *plain; /* match this value with strcasecmp if is_regex is false. The string is allocated by the lex tokenizer, must be freed at the end. */
- regex_t preg; /* match with regexec if is_regex is true. regfree must be called at the end. A copy of the original string is anyway saved in plain. */
- };
- /* The sentinels for the TARGET lists */
- static struct fd_list TARGETS[RTD_TAR_MAX];
- /* Structure of a TARGET element */
- struct target {
- struct fd_list chain; /* link in the top-level list */
- struct match_data md; /* the data to determine if the current candidate matches this element */
- struct fd_list rules[RTD_CRI_MAX]; /* Sentinels for the lists of rules applying to this target. One list per rtd_crit_type */
- /* note : we do not need the rtd_targ_type here, it is implied by the root of the list this target element is attached to */
- };
- /* Structure of a RULE element */
- struct rule {
- struct fd_list chain; /* link in the parent target list */
- struct match_data md; /* the data that the criteria must match, -- ignored for RTD_CRI_ALL */
- int score; /* The score added to the candidate, if the message matches this criteria */
- /* The type of rule depends on the sentinel */
- };
- /*********************************************************************/
- /* Compile a regular expression pattern */
- static int compile_regex( regex_t * preg, char * str )
- {
- int err;
-
- /* Compile the regular expression */
- err = regcomp(preg, str, REG_EXTENDED | REG_NOSUB);
- if (err != 0) {
- char * buf;
- size_t bl;
-
- /* Error while compiling the regex */
- TRACE_DEBUG(INFO, "Error while compiling the regular expression '%s':", str);
-
- /* Get the error message size */
- bl = regerror(err, preg, NULL, 0);
-
- /* Alloc the buffer for error message */
- CHECK_MALLOC( buf = malloc(bl) );
-
- /* Get the error message content */
- regerror(err, preg, buf, bl);
- TRACE_DEBUG(INFO, "\t%s", buf);
-
- /* Free the buffer, return the error */
- free(buf);
- return EINVAL;
- }
- return 0;
- }
- /* Create a target item and initialize its content */
- static struct target * new_target(char * str, int regex)
- {
- int i;
- struct target *new = NULL;
- CHECK_MALLOC_DO( new = malloc(sizeof(struct target)), return NULL );
- memset(new, 0, sizeof(struct target));
-
- fd_list_init(&new->chain, new);
- new->md.plain = str;
- new->md.is_regex = regex;
- if (regex) {
- CHECK_FCT_DO( compile_regex(&new->md.preg, str),
- {
- free(new);
- return NULL;
- } );
- }
- for (i = 0; i < RTD_CRI_MAX; i++) {
- fd_list_init(&new->rules[i], new);
- }
- return new;
- }
- /* Create a rule item and initialize its content; return NULL in case of error */
- static struct rule * new_rule(char * str, int regex, int score)
- {
- struct rule *new = NULL;
- CHECK_MALLOC_DO( new = malloc(sizeof(struct rule)), return NULL );
- memset(new, 0, sizeof(struct rule));
-
- fd_list_init(&new->chain, new);
- new->md.plain = str;
- new->md.is_regex = regex;
- if (regex) {
- CHECK_FCT_DO( compile_regex(&new->md.preg, str),
- {
- free(new);
- return NULL;
- } );
- }
- new->score = score;
- return new;
- }
- /* Debug functions */
- static void dump_rule(int indent, struct rule * rule)
- {
- fd_log_debug("%*s%s%s%s += %d\n",
- indent, "",
- rule->md.is_regex ? "[" : "'",
- rule->md.plain,
- rule->md.is_regex ? "]" : "'",
- rule->score);
- }
- static void dump_target(int indent, struct target * target)
- {
- int i;
- fd_log_debug("%*s%s%s%s :\n",
- indent, "",
- target->md.is_regex ? "[" : "'",
- target->md.plain ?: "(empty)",
- target->md.is_regex ? "]" : "'");
- for (i = 0; i < RTD_CRI_MAX; i++) {
- if (! FD_IS_LIST_EMPTY(&target->rules[i])) {
- struct fd_list * li;
- fd_log_debug("%*s rules[%d]:\n",
- indent, "", i);
- for (li = target->rules[i].next; li != &target->rules[i]; li = li->next) {
- dump_rule(indent + 3, (struct rule *)li);
- }
- }
- }
- }
- static void clear_md(struct match_data * md)
- {
- /* delete the string */
- if (md->plain) {
- free(md->plain);
- md->plain = NULL;
- }
-
- /* delete the preg if needed */
- if (md->is_regex) {
- regfree(&md->preg);
- md->is_regex = 0;
- }
- }
- /* Destroy a rule item */
- static void del_rule(struct rule * del)
- {
- /* Unlink this rule */
- fd_list_unlink(&del->chain);
-
- /* Delete the match data */
- clear_md(&del->md);
-
- free(del);
- }
- /* Destroy a target item, and all its rules */
- static void del_target(struct target * del)
- {
- int i;
-
- /* Unlink this target */
- fd_list_unlink(&del->chain);
-
- /* Delete the match data */
- clear_md(&del->md);
-
- /* Delete the children rules */
- for (i = 0; i < RTD_CRI_MAX; i++) {
- while (! FD_IS_LIST_EMPTY(&del->rules[i]) ) {
- del_rule((struct rule *)(del->rules[i].next));
- }
- }
-
- free(del);
- }
- /* Compare a string with a match_data value. *res contains the result of the comparison (always >0 for regex non-match situations) */
- static int compare_match(char * str, size_t len, struct match_data * md, int * res)
- {
- int err;
-
- CHECK_PARAMS( str && md && res );
-
- /* Plain strings: we compare with strncasecmp */
- if (md->is_regex == 0) {
- *res = strncasecmp(str, md->plain, len);
- return 0;
- }
- /* Regexp */
- *res = 1;
-
- #ifdef HAVE_REG_STARTEND
- {
- regmatch_t pmatch[1];
- memset(pmatch, 0, sizeof(pmatch));
- pmatch[0].rm_so = 0;
- pmatch[0].rm_eo = len;
- err = regexec(&md->preg, str, 0, pmatch, REG_STARTEND);
- }
- #else /* HAVE_REG_STARTEND */
- {
- /* We have to create a copy of the string in this case */
- char *mystrcpy;
- CHECK_MALLOC( mystrcpy = os0dup(str, len) );
- err = regexec(&md->preg, mystrcpy, 0, NULL, 0);
- free(mystrcpy);
- }
- #endif /* HAVE_REG_STARTEND */
-
- /* Now check the result */
- if (err == 0) {
- /* We have a match */
- *res = 0;
- return 0;
- }
-
- if (err == REG_NOMATCH) {
- *res = 1;
- return 0;
- }
-
- /* In other cases, we have an error */
- {
- char * buf;
- size_t bl;
-
- /* Error while compiling the regex */
- TRACE_DEBUG(INFO, "Error while executing the regular expression '%s':", md->plain);
-
- /* Get the error message size */
- bl = regerror(err, &md->preg, NULL, 0);
-
- /* Alloc the buffer for error message */
- CHECK_MALLOC( buf = malloc(bl) );
-
- /* Get the error message content */
- regerror(err, &md->preg, buf, bl);
- TRACE_DEBUG(INFO, "\t%s", buf);
-
- /* Free the buffer, return the error */
- free(buf);
- }
- return (err == REG_ESPACE) ? ENOMEM : EINVAL;
- }
- /* Search in list (targets or rules) the next matching item for octet string str(len). Returned in next_match, or *next_match == NULL if no more match. Re-enter with same next_match for the next one. */
- static int get_next_match(struct fd_list * list, char * str, size_t len, struct fd_list ** next_match)
- {
- struct fd_list * li;
-
- TRACE_ENTRY("%p %p %zd %p", list, str, len, next_match);
- CHECK_PARAMS(list && str && len && next_match);
-
- if (*next_match)
- li = (*next_match)->next;
- else
- li = list->next;
-
- /* Initialize the return value */
- *next_match = NULL;
-
- for ( ; li != list; li = li->next) {
- int cmp;
- struct {
- struct fd_list chain;
- struct match_data md;
- } * next_item = (void *)li;
-
- /* Check if the string matches this next item */
- CHECK_FCT( compare_match(str, len, &next_item->md, &cmp) );
-
- if (cmp == 0) {
- /* matched! */
- *next_match = li;
- return 0;
- }
-
- if (cmp < 0) /* we can stop searching */
- break;
- }
-
- /* We're done with the list */
- return 0;
- }
- static struct dict_object * AVP_MODELS[RTD_CRI_MAX];
- /*********************************************************************/
- /* Prepare the module */
- int rtd_init(void)
- {
- int i;
-
- TRACE_ENTRY();
-
- for (i = 0; i < RTD_TAR_MAX; i++) {
- fd_list_init(&TARGETS[i], NULL);
- }
-
- for (i = 1; i < RTD_CRI_MAX; i++) {
- switch (i) {
- case RTD_CRI_OH:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &AVP_MODELS[i], ENOENT ));
- break;
- case RTD_CRI_OR:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &AVP_MODELS[i], ENOENT ));
- break;
- case RTD_CRI_DH:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &AVP_MODELS[i], ENOENT ));
- break;
- case RTD_CRI_DR:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &AVP_MODELS[i], ENOENT ));
- break;
- case RTD_CRI_UN:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &AVP_MODELS[i], ENOENT ));
- break;
- case RTD_CRI_SI:
- CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &AVP_MODELS[i], ENOENT ));
- break;
- default:
- TRACE_DEBUG(INFO, "Missing definition in extension initializer");
- ASSERT( 0 );
- return EINVAL;
- }
- }
-
- return 0;
- }
- /* Destroy the module's data */
- void rtd_fini(void)
- {
- int i;
-
- TRACE_ENTRY();
- for (i = 0; i < RTD_TAR_MAX; i++) {
- while (!FD_IS_LIST_EMPTY(&TARGETS[i])) {
- del_target((struct target *) TARGETS[i].next);
- }
- }
-
- }
- /* Add a new rule in the repository. this is called when the configuration file is being parsed */
- int rtd_add(enum rtd_crit_type ct, char * criteria, enum rtd_targ_type tt, char * target, int score, int flags)
- {
- struct fd_list * target_suiv = NULL;
- struct fd_list * rule_suiv = NULL;
- struct target * trg = NULL;
- struct rule * rul = NULL;
-
- TRACE_ENTRY("%d %p %d %p %d %x", ct, criteria, tt, target, score, flags);
- CHECK_PARAMS((ct < RTD_CRI_MAX) && ((ct == 0) || criteria) && (tt < RTD_TAR_MAX) && target);
-
- /* First, search in the TARGET list if we already have this target */
- for (target_suiv = TARGETS[tt].next; target_suiv != &TARGETS[tt]; target_suiv = target_suiv->next) {
- int cmp;
- struct target * cur = (struct target *)target_suiv;
-
- if (flags & RTD_TARG_REG) {
- /* We are adding a regexp, it is saved in the list before the plain expressions */
- if (cur->md.is_regex == 0)
- break;
- } else {
- /* We are adding a plain expression, save it after all regexps */
- if (cur->md.is_regex != 0)
- continue;
- }
-
- /* At this point, the type is the same, so compare the plain string value */
- cmp = strcmp(cur->md.plain, target);
- if (cmp < 0)
- continue;
-
- if (cmp == 0) /* We already have a target with the same string */
- trg = cur;
-
- break;
- }
-
- if (trg) {
- /* Ok, we can free the target string, we will use the previously allocated one */
- free(target);
- } else {
- CHECK_MALLOC( trg = new_target(target, flags & RTD_TARG_REG) );
- fd_list_insert_before( target_suiv, &trg->chain );
- }
-
- /* Now, search for the rule position in this target's list */
- if (ct == 0) {
- /* Special case: we don't have a criteria -- always create a rule element */
- CHECK_MALLOC( rul = new_rule(NULL, 0, score) );
- fd_list_insert_before( &trg->rules[0], &rul->chain );
- } else {
- for (rule_suiv = trg->rules[ct].next; rule_suiv != &trg->rules[ct]; rule_suiv = rule_suiv->next) {
- int cmp;
- struct rule * cur = (struct rule *)rule_suiv;
- if (flags & RTD_CRIT_REG) {
- /* We are adding a regexp, it is saved in the list before the plain expressions */
- if (cur->md.is_regex == 0)
- break;
- } else {
- /* We are adding a plain expression, save it after all regexps */
- if (cur->md.is_regex != 0)
- continue;
- }
- /* At this point, the type is the same, so compare the plain string value */
- cmp = strcmp(cur->md.plain, criteria);
- if (cmp < 0)
- continue;
- if (cmp == 0) /* We already have a target with the same string */
- rul = cur;
- break;
- }
-
- if (rul) {
- /* Ok, we can free the target string, we will use the previously allocated one */
- free(criteria);
- TRACE_DEBUG(INFO, "Warning: duplicate rule (%s : %s) found, merging score...", rul->md.plain, trg->md.plain);
- rul->score += score;
- } else {
- CHECK_MALLOC( rul = new_rule(criteria, flags & RTD_CRIT_REG, score) );
- fd_list_insert_before( rule_suiv, &rul->chain );
- }
- }
-
- return 0;
- }
- /* Check if a message and list of eligible candidate match any of our rules, and update its score according to it. */
- int rtd_process( struct msg * msg, struct fd_list * candidates )
- {
- struct fd_list * li;
- struct {
- enum { NOT_RESOLVED_YET = 0, NOT_FOUND, FOUND } status;
- union avp_value * avp;
- } parsed_msg_avp[RTD_CRI_MAX];
-
- TRACE_ENTRY("%p %p", msg, candidates);
- CHECK_PARAMS(msg && candidates);
-
- /* We delay looking for the AVPs in the message until we really need them. Another approach would be to parse the message once and save all needed AVPs. */
- memset(parsed_msg_avp, 0, sizeof(parsed_msg_avp));
-
- /* For each candidate in the list */
- for (li = candidates->next; li != candidates; li = li->next) {
- struct rtd_candidate * cand = (struct rtd_candidate *)li;
- int i;
- struct {
- char * str;
- size_t len;
- } cand_data[RTD_TAR_MAX] = {
- { cand->diamid, strlen(cand->diamid) },
- { cand->realm, strlen(cand->realm) }
- };
-
- for (i = 0; i < RTD_TAR_MAX; i++) {
- /* Search the next rule matching this candidate in the i-th target list */
- struct target * target = NULL;
-
- do {
- int j;
- struct fd_list * l;
- struct rule * r;
-
- CHECK_FCT ( get_next_match( &TARGETS[i], cand_data[i].str, cand_data[i].len, (void *)&target) );
- if (!target)
- break;
-
- /* First, apply all rules of criteria RTD_CRI_ALL */
- for ( l = target->rules[0].next; l != &target->rules[0]; l = l->next ) {
- r = (struct rule *)l;
- cand->score += r->score;
- TRACE_DEBUG(ANNOYING, "Applied rule {'%s' : '%s' += %d} to candidate '%s'", r->md.plain, target->md.plain, r->score, cand->diamid);
- }
-
- /* The target is matching this candidate, check if there are additional rules criteria matching this message. */
- for ( j = 1; j < RTD_CRI_MAX; j++ ) {
- if ( ! FD_IS_LIST_EMPTY(&target->rules[j]) ) {
- /* if needed, find the required data in the message */
- if (parsed_msg_avp[j].status == NOT_RESOLVED_YET) {
- struct avp * avp = NULL;
- /* Search for the AVP in the message */
- CHECK_FCT( fd_msg_search_avp ( msg, AVP_MODELS[j], &avp ) );
- if (avp == NULL) {
- parsed_msg_avp[j].status = NOT_FOUND;
- } else {
- struct avp_hdr * ahdr = NULL;
- CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
- if (ahdr->avp_value == NULL) {
- /* This should not happen, but anyway let's just ignore it */
- parsed_msg_avp[j].status = NOT_FOUND;
- } else {
- /* OK, we got the AVP */
- parsed_msg_avp[j].status = FOUND;
- parsed_msg_avp[j].avp = ahdr->avp_value;
- }
- }
- }
-
- /* If we did not find the data for these rules in the message, just skip the series */
- if (parsed_msg_avp[j].status == NOT_FOUND) {
- TRACE_DEBUG(ANNOYING, "Skipping series of rules %d of target '%s', criteria absent from the message", j, target->md.plain);
- continue;
- }
-
- /* OK, we can now check if one of our rule's criteria match the message content */
- r = NULL;
- do {
- CHECK_FCT ( get_next_match( &target->rules[j], (char *) /* is this cast safe? */ parsed_msg_avp[j].avp->os.data, parsed_msg_avp[j].avp->os.len, (void *)&r) );
- if (!r)
- break;
-
- cand->score += r->score;
- TRACE_DEBUG(ANNOYING, "Applied rule {'%s' : '%s' += %d} to candidate '%s'", r->md.plain, target->md.plain, r->score, cand->diamid);
- } while (1);
- }
- }
- } while (1);
- }
- }
-
- return 0;
- }
- void rtd_dump(void)
- {
- int i;
- fd_log_debug("[rt_default] Dumping rules repository...\n");
- for (i = 0; i < RTD_TAR_MAX; i++) {
- if (!FD_IS_LIST_EMPTY( &TARGETS[i] )) {
- struct fd_list * li;
- fd_log_debug(" Targets list %d:\n", i);
- for (li = TARGETS[i].next; li != &TARGETS[i]; li = li->next) {
- dump_target(4, (struct target *)li);
- }
- }
- }
-
- fd_log_debug("[rt_default] End of dump\n");
- }