PageRenderTime 25ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  1. /*********************************************************************************************************
  2. * Software License Agreement (BSD License) *
  3. * Author: Sebastien Decugis <sdecugis@freediameter.net> *
  4. * *
  5. * Copyright (c) 2011, WIDE Project and NICT *
  6. * All rights reserved. *
  7. * *
  8. * Redistribution and use of this software in source and binary forms, with or without modification, are *
  9. * permitted provided that the following conditions are met: *
  10. * *
  11. * * Redistributions of source code must retain the above *
  12. * copyright notice, this list of conditions and the *
  13. * following disclaimer. *
  14. * *
  15. * * Redistributions in binary form must reproduce the above *
  16. * copyright notice, this list of conditions and the *
  17. * following disclaimer in the documentation and/or other *
  18. * materials provided with the distribution. *
  19. * *
  20. * * Neither the name of the WIDE Project or NICT nor the *
  21. * names of its contributors may be used to endorse or *
  22. * promote products derived from this software without *
  23. * specific prior written permission of WIDE Project and *
  24. * NICT. *
  25. * *
  26. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
  27. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
  28. * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
  29. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
  30. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
  31. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
  32. * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
  33. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
  34. *********************************************************************************************************/
  35. #include "rt_default.h"
  36. /* The regular expressions header */
  37. #include <regex.h>
  38. /* We will search for each candidate peer all the rules that are defined, and check which one applies to the message
  39. * Therefore our repository is organized hierarchicaly.
  40. * At the top level, we have two lists of TARGETS (one for IDENTITY, one for REALM), ordered as follow:
  41. * - first, the TARGETS defined with a regular expression. We will try matching all regexp to all candidates in the list.
  42. * - 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.
  43. *
  44. * 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.
  45. *
  46. * Note: Except during configuration parsing and module termination, the lists are only ever accessed read-only, so we do not need a lock.
  47. */
  48. /* Structure to hold the data that is used for matching. */
  49. struct match_data {
  50. int is_regex; /* determines how the string is matched */
  51. 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. */
  52. 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. */
  53. };
  54. /* The sentinels for the TARGET lists */
  55. static struct fd_list TARGETS[RTD_TAR_MAX];
  56. /* Structure of a TARGET element */
  57. struct target {
  58. struct fd_list chain; /* link in the top-level list */
  59. struct match_data md; /* the data to determine if the current candidate matches this element */
  60. struct fd_list rules[RTD_CRI_MAX]; /* Sentinels for the lists of rules applying to this target. One list per rtd_crit_type */
  61. /* 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 */
  62. };
  63. /* Structure of a RULE element */
  64. struct rule {
  65. struct fd_list chain; /* link in the parent target list */
  66. struct match_data md; /* the data that the criteria must match, -- ignored for RTD_CRI_ALL */
  67. int score; /* The score added to the candidate, if the message matches this criteria */
  68. /* The type of rule depends on the sentinel */
  69. };
  70. /*********************************************************************/
  71. /* Compile a regular expression pattern */
  72. static int compile_regex( regex_t * preg, char * str )
  73. {
  74. int err;
  75. /* Compile the regular expression */
  76. err = regcomp(preg, str, REG_EXTENDED | REG_NOSUB);
  77. if (err != 0) {
  78. char * buf;
  79. size_t bl;
  80. /* Error while compiling the regex */
  81. TRACE_DEBUG(INFO, "Error while compiling the regular expression '%s':", str);
  82. /* Get the error message size */
  83. bl = regerror(err, preg, NULL, 0);
  84. /* Alloc the buffer for error message */
  85. CHECK_MALLOC( buf = malloc(bl) );
  86. /* Get the error message content */
  87. regerror(err, preg, buf, bl);
  88. TRACE_DEBUG(INFO, "\t%s", buf);
  89. /* Free the buffer, return the error */
  90. free(buf);
  91. return EINVAL;
  92. }
  93. return 0;
  94. }
  95. /* Create a target item and initialize its content */
  96. static struct target * new_target(char * str, int regex)
  97. {
  98. int i;
  99. struct target *new = NULL;
  100. CHECK_MALLOC_DO( new = malloc(sizeof(struct target)), return NULL );
  101. memset(new, 0, sizeof(struct target));
  102. fd_list_init(&new->chain, new);
  103. new->md.plain = str;
  104. new->md.is_regex = regex;
  105. if (regex) {
  106. CHECK_FCT_DO( compile_regex(&new->md.preg, str),
  107. {
  108. free(new);
  109. return NULL;
  110. } );
  111. }
  112. for (i = 0; i < RTD_CRI_MAX; i++) {
  113. fd_list_init(&new->rules[i], new);
  114. }
  115. return new;
  116. }
  117. /* Create a rule item and initialize its content; return NULL in case of error */
  118. static struct rule * new_rule(char * str, int regex, int score)
  119. {
  120. struct rule *new = NULL;
  121. CHECK_MALLOC_DO( new = malloc(sizeof(struct rule)), return NULL );
  122. memset(new, 0, sizeof(struct rule));
  123. fd_list_init(&new->chain, new);
  124. new->md.plain = str;
  125. new->md.is_regex = regex;
  126. if (regex) {
  127. CHECK_FCT_DO( compile_regex(&new->md.preg, str),
  128. {
  129. free(new);
  130. return NULL;
  131. } );
  132. }
  133. new->score = score;
  134. return new;
  135. }
  136. /* Debug functions */
  137. static void dump_rule(int indent, struct rule * rule)
  138. {
  139. fd_log_debug("%*s%s%s%s += %d\n",
  140. indent, "",
  141. rule->md.is_regex ? "[" : "'",
  142. rule->md.plain,
  143. rule->md.is_regex ? "]" : "'",
  144. rule->score);
  145. }
  146. static void dump_target(int indent, struct target * target)
  147. {
  148. int i;
  149. fd_log_debug("%*s%s%s%s :\n",
  150. indent, "",
  151. target->md.is_regex ? "[" : "'",
  152. target->md.plain ?: "(empty)",
  153. target->md.is_regex ? "]" : "'");
  154. for (i = 0; i < RTD_CRI_MAX; i++) {
  155. if (! FD_IS_LIST_EMPTY(&target->rules[i])) {
  156. struct fd_list * li;
  157. fd_log_debug("%*s rules[%d]:\n",
  158. indent, "", i);
  159. for (li = target->rules[i].next; li != &target->rules[i]; li = li->next) {
  160. dump_rule(indent + 3, (struct rule *)li);
  161. }
  162. }
  163. }
  164. }
  165. static void clear_md(struct match_data * md)
  166. {
  167. /* delete the string */
  168. if (md->plain) {
  169. free(md->plain);
  170. md->plain = NULL;
  171. }
  172. /* delete the preg if needed */
  173. if (md->is_regex) {
  174. regfree(&md->preg);
  175. md->is_regex = 0;
  176. }
  177. }
  178. /* Destroy a rule item */
  179. static void del_rule(struct rule * del)
  180. {
  181. /* Unlink this rule */
  182. fd_list_unlink(&del->chain);
  183. /* Delete the match data */
  184. clear_md(&del->md);
  185. free(del);
  186. }
  187. /* Destroy a target item, and all its rules */
  188. static void del_target(struct target * del)
  189. {
  190. int i;
  191. /* Unlink this target */
  192. fd_list_unlink(&del->chain);
  193. /* Delete the match data */
  194. clear_md(&del->md);
  195. /* Delete the children rules */
  196. for (i = 0; i < RTD_CRI_MAX; i++) {
  197. while (! FD_IS_LIST_EMPTY(&del->rules[i]) ) {
  198. del_rule((struct rule *)(del->rules[i].next));
  199. }
  200. }
  201. free(del);
  202. }
  203. /* Compare a string with a match_data value. *res contains the result of the comparison (always >0 for regex non-match situations) */
  204. static int compare_match(char * str, size_t len, struct match_data * md, int * res)
  205. {
  206. int err;
  207. CHECK_PARAMS( str && md && res );
  208. /* Plain strings: we compare with strncasecmp */
  209. if (md->is_regex == 0) {
  210. *res = strncasecmp(str, md->plain, len);
  211. return 0;
  212. }
  213. /* Regexp */
  214. *res = 1;
  215. #ifdef HAVE_REG_STARTEND
  216. {
  217. regmatch_t pmatch[1];
  218. memset(pmatch, 0, sizeof(pmatch));
  219. pmatch[0].rm_so = 0;
  220. pmatch[0].rm_eo = len;
  221. err = regexec(&md->preg, str, 0, pmatch, REG_STARTEND);
  222. }
  223. #else /* HAVE_REG_STARTEND */
  224. {
  225. /* We have to create a copy of the string in this case */
  226. char *mystrcpy;
  227. CHECK_MALLOC( mystrcpy = os0dup(str, len) );
  228. err = regexec(&md->preg, mystrcpy, 0, NULL, 0);
  229. free(mystrcpy);
  230. }
  231. #endif /* HAVE_REG_STARTEND */
  232. /* Now check the result */
  233. if (err == 0) {
  234. /* We have a match */
  235. *res = 0;
  236. return 0;
  237. }
  238. if (err == REG_NOMATCH) {
  239. *res = 1;
  240. return 0;
  241. }
  242. /* In other cases, we have an error */
  243. {
  244. char * buf;
  245. size_t bl;
  246. /* Error while compiling the regex */
  247. TRACE_DEBUG(INFO, "Error while executing the regular expression '%s':", md->plain);
  248. /* Get the error message size */
  249. bl = regerror(err, &md->preg, NULL, 0);
  250. /* Alloc the buffer for error message */
  251. CHECK_MALLOC( buf = malloc(bl) );
  252. /* Get the error message content */
  253. regerror(err, &md->preg, buf, bl);
  254. TRACE_DEBUG(INFO, "\t%s", buf);
  255. /* Free the buffer, return the error */
  256. free(buf);
  257. }
  258. return (err == REG_ESPACE) ? ENOMEM : EINVAL;
  259. }
  260. /* 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. */
  261. static int get_next_match(struct fd_list * list, char * str, size_t len, struct fd_list ** next_match)
  262. {
  263. struct fd_list * li;
  264. TRACE_ENTRY("%p %p %zd %p", list, str, len, next_match);
  265. CHECK_PARAMS(list && str && len && next_match);
  266. if (*next_match)
  267. li = (*next_match)->next;
  268. else
  269. li = list->next;
  270. /* Initialize the return value */
  271. *next_match = NULL;
  272. for ( ; li != list; li = li->next) {
  273. int cmp;
  274. struct {
  275. struct fd_list chain;
  276. struct match_data md;
  277. } * next_item = (void *)li;
  278. /* Check if the string matches this next item */
  279. CHECK_FCT( compare_match(str, len, &next_item->md, &cmp) );
  280. if (cmp == 0) {
  281. /* matched! */
  282. *next_match = li;
  283. return 0;
  284. }
  285. if (cmp < 0) /* we can stop searching */
  286. break;
  287. }
  288. /* We're done with the list */
  289. return 0;
  290. }
  291. static struct dict_object * AVP_MODELS[RTD_CRI_MAX];
  292. /*********************************************************************/
  293. /* Prepare the module */
  294. int rtd_init(void)
  295. {
  296. int i;
  297. TRACE_ENTRY();
  298. for (i = 0; i < RTD_TAR_MAX; i++) {
  299. fd_list_init(&TARGETS[i], NULL);
  300. }
  301. for (i = 1; i < RTD_CRI_MAX; i++) {
  302. switch (i) {
  303. case RTD_CRI_OH:
  304. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &AVP_MODELS[i], ENOENT ));
  305. break;
  306. case RTD_CRI_OR:
  307. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &AVP_MODELS[i], ENOENT ));
  308. break;
  309. case RTD_CRI_DH:
  310. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &AVP_MODELS[i], ENOENT ));
  311. break;
  312. case RTD_CRI_DR:
  313. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &AVP_MODELS[i], ENOENT ));
  314. break;
  315. case RTD_CRI_UN:
  316. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &AVP_MODELS[i], ENOENT ));
  317. break;
  318. case RTD_CRI_SI:
  319. CHECK_FCT( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &AVP_MODELS[i], ENOENT ));
  320. break;
  321. default:
  322. TRACE_DEBUG(INFO, "Missing definition in extension initializer");
  323. ASSERT( 0 );
  324. return EINVAL;
  325. }
  326. }
  327. return 0;
  328. }
  329. /* Destroy the module's data */
  330. void rtd_fini(void)
  331. {
  332. int i;
  333. TRACE_ENTRY();
  334. for (i = 0; i < RTD_TAR_MAX; i++) {
  335. while (!FD_IS_LIST_EMPTY(&TARGETS[i])) {
  336. del_target((struct target *) TARGETS[i].next);
  337. }
  338. }
  339. }
  340. /* Add a new rule in the repository. this is called when the configuration file is being parsed */
  341. int rtd_add(enum rtd_crit_type ct, char * criteria, enum rtd_targ_type tt, char * target, int score, int flags)
  342. {
  343. struct fd_list * target_suiv = NULL;
  344. struct fd_list * rule_suiv = NULL;
  345. struct target * trg = NULL;
  346. struct rule * rul = NULL;
  347. TRACE_ENTRY("%d %p %d %p %d %x", ct, criteria, tt, target, score, flags);
  348. CHECK_PARAMS((ct < RTD_CRI_MAX) && ((ct == 0) || criteria) && (tt < RTD_TAR_MAX) && target);
  349. /* First, search in the TARGET list if we already have this target */
  350. for (target_suiv = TARGETS[tt].next; target_suiv != &TARGETS[tt]; target_suiv = target_suiv->next) {
  351. int cmp;
  352. struct target * cur = (struct target *)target_suiv;
  353. if (flags & RTD_TARG_REG) {
  354. /* We are adding a regexp, it is saved in the list before the plain expressions */
  355. if (cur->md.is_regex == 0)
  356. break;
  357. } else {
  358. /* We are adding a plain expression, save it after all regexps */
  359. if (cur->md.is_regex != 0)
  360. continue;
  361. }
  362. /* At this point, the type is the same, so compare the plain string value */
  363. cmp = strcmp(cur->md.plain, target);
  364. if (cmp < 0)
  365. continue;
  366. if (cmp == 0) /* We already have a target with the same string */
  367. trg = cur;
  368. break;
  369. }
  370. if (trg) {
  371. /* Ok, we can free the target string, we will use the previously allocated one */
  372. free(target);
  373. } else {
  374. CHECK_MALLOC( trg = new_target(target, flags & RTD_TARG_REG) );
  375. fd_list_insert_before( target_suiv, &trg->chain );
  376. }
  377. /* Now, search for the rule position in this target's list */
  378. if (ct == 0) {
  379. /* Special case: we don't have a criteria -- always create a rule element */
  380. CHECK_MALLOC( rul = new_rule(NULL, 0, score) );
  381. fd_list_insert_before( &trg->rules[0], &rul->chain );
  382. } else {
  383. for (rule_suiv = trg->rules[ct].next; rule_suiv != &trg->rules[ct]; rule_suiv = rule_suiv->next) {
  384. int cmp;
  385. struct rule * cur = (struct rule *)rule_suiv;
  386. if (flags & RTD_CRIT_REG) {
  387. /* We are adding a regexp, it is saved in the list before the plain expressions */
  388. if (cur->md.is_regex == 0)
  389. break;
  390. } else {
  391. /* We are adding a plain expression, save it after all regexps */
  392. if (cur->md.is_regex != 0)
  393. continue;
  394. }
  395. /* At this point, the type is the same, so compare the plain string value */
  396. cmp = strcmp(cur->md.plain, criteria);
  397. if (cmp < 0)
  398. continue;
  399. if (cmp == 0) /* We already have a target with the same string */
  400. rul = cur;
  401. break;
  402. }
  403. if (rul) {
  404. /* Ok, we can free the target string, we will use the previously allocated one */
  405. free(criteria);
  406. TRACE_DEBUG(INFO, "Warning: duplicate rule (%s : %s) found, merging score...", rul->md.plain, trg->md.plain);
  407. rul->score += score;
  408. } else {
  409. CHECK_MALLOC( rul = new_rule(criteria, flags & RTD_CRIT_REG, score) );
  410. fd_list_insert_before( rule_suiv, &rul->chain );
  411. }
  412. }
  413. return 0;
  414. }
  415. /* Check if a message and list of eligible candidate match any of our rules, and update its score according to it. */
  416. int rtd_process( struct msg * msg, struct fd_list * candidates )
  417. {
  418. struct fd_list * li;
  419. struct {
  420. enum { NOT_RESOLVED_YET = 0, NOT_FOUND, FOUND } status;
  421. union avp_value * avp;
  422. } parsed_msg_avp[RTD_CRI_MAX];
  423. TRACE_ENTRY("%p %p", msg, candidates);
  424. CHECK_PARAMS(msg && candidates);
  425. /* 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. */
  426. memset(parsed_msg_avp, 0, sizeof(parsed_msg_avp));
  427. /* For each candidate in the list */
  428. for (li = candidates->next; li != candidates; li = li->next) {
  429. struct rtd_candidate * cand = (struct rtd_candidate *)li;
  430. int i;
  431. struct {
  432. char * str;
  433. size_t len;
  434. } cand_data[RTD_TAR_MAX] = {
  435. { cand->diamid, strlen(cand->diamid) },
  436. { cand->realm, strlen(cand->realm) }
  437. };
  438. for (i = 0; i < RTD_TAR_MAX; i++) {
  439. /* Search the next rule matching this candidate in the i-th target list */
  440. struct target * target = NULL;
  441. do {
  442. int j;
  443. struct fd_list * l;
  444. struct rule * r;
  445. CHECK_FCT ( get_next_match( &TARGETS[i], cand_data[i].str, cand_data[i].len, (void *)&target) );
  446. if (!target)
  447. break;
  448. /* First, apply all rules of criteria RTD_CRI_ALL */
  449. for ( l = target->rules[0].next; l != &target->rules[0]; l = l->next ) {
  450. r = (struct rule *)l;
  451. cand->score += r->score;
  452. TRACE_DEBUG(ANNOYING, "Applied rule {'%s' : '%s' += %d} to candidate '%s'", r->md.plain, target->md.plain, r->score, cand->diamid);
  453. }
  454. /* The target is matching this candidate, check if there are additional rules criteria matching this message. */
  455. for ( j = 1; j < RTD_CRI_MAX; j++ ) {
  456. if ( ! FD_IS_LIST_EMPTY(&target->rules[j]) ) {
  457. /* if needed, find the required data in the message */
  458. if (parsed_msg_avp[j].status == NOT_RESOLVED_YET) {
  459. struct avp * avp = NULL;
  460. /* Search for the AVP in the message */
  461. CHECK_FCT( fd_msg_search_avp ( msg, AVP_MODELS[j], &avp ) );
  462. if (avp == NULL) {
  463. parsed_msg_avp[j].status = NOT_FOUND;
  464. } else {
  465. struct avp_hdr * ahdr = NULL;
  466. CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
  467. if (ahdr->avp_value == NULL) {
  468. /* This should not happen, but anyway let's just ignore it */
  469. parsed_msg_avp[j].status = NOT_FOUND;
  470. } else {
  471. /* OK, we got the AVP */
  472. parsed_msg_avp[j].status = FOUND;
  473. parsed_msg_avp[j].avp = ahdr->avp_value;
  474. }
  475. }
  476. }
  477. /* If we did not find the data for these rules in the message, just skip the series */
  478. if (parsed_msg_avp[j].status == NOT_FOUND) {
  479. TRACE_DEBUG(ANNOYING, "Skipping series of rules %d of target '%s', criteria absent from the message", j, target->md.plain);
  480. continue;
  481. }
  482. /* OK, we can now check if one of our rule's criteria match the message content */
  483. r = NULL;
  484. do {
  485. 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) );
  486. if (!r)
  487. break;
  488. cand->score += r->score;
  489. TRACE_DEBUG(ANNOYING, "Applied rule {'%s' : '%s' += %d} to candidate '%s'", r->md.plain, target->md.plain, r->score, cand->diamid);
  490. } while (1);
  491. }
  492. }
  493. } while (1);
  494. }
  495. }
  496. return 0;
  497. }
  498. void rtd_dump(void)
  499. {
  500. int i;
  501. fd_log_debug("[rt_default] Dumping rules repository...\n");
  502. for (i = 0; i < RTD_TAR_MAX; i++) {
  503. if (!FD_IS_LIST_EMPTY( &TARGETS[i] )) {
  504. struct fd_list * li;
  505. fd_log_debug(" Targets list %d:\n", i);
  506. for (li = TARGETS[i].next; li != &TARGETS[i]; li = li->next) {
  507. dump_target(4, (struct target *)li);
  508. }
  509. }
  510. }
  511. fd_log_debug("[rt_default] End of dump\n");
  512. }