changeset 1338:f1b65381c1e7

rt_ereg: Support config reload. Support grouped AVPs. Support multiple separate AVPs. Written for Effortel Technologies SA, published with their consent.
author Thomas Klausner <tk@giga.or.at>
date Tue, 09 Apr 2019 15:48:45 +0200
parents d66f60e29b22
children e70ce64ce689
files doc/rt_ereg.conf.sample extensions/rt_ereg/rtereg.c extensions/rt_ereg/rtereg.h extensions/rt_ereg/rtereg_conf.y
diffstat 4 files changed, 344 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/doc/rt_ereg.conf.sample	Tue Apr 09 15:47:39 2019 +0200
+++ b/doc/rt_ereg.conf.sample	Tue Apr 09 15:48:45 2019 +0200
@@ -3,9 +3,15 @@
 #
 # The rt_ereg extension allows creation of routing rules based on AVP value matching regular expressions.
 
+# This extension supports configuration reload at runtime. Send
+# signal SIGUSR1 to the process to cause the process to reload its
+# config.
+
 # First, one must indicate which AVP should be used for matching. 
 # At the moment, only AVP with OCTETSTRING types are valid.
 #  AVP = "User-Name";
+# It is possible to specify AVPs below GROUPED AVPs with the by separating AVPs with a colon (':'):
+#  AVP = "Grouped-AVP1" : "Grouped-AVP2" : "Octetstring-AVP";
 # This parameter is mandatory. There is no default value.
 
 # Then a list of rules follow. A rule has this format:
@@ -19,3 +25,7 @@
 #    means that if the AVP value is only numeric, the ServerA will have its score decreased by 3 points.
 #    (reminder: the server with the peer with the highest score gets the message)
 # Note that all rules are tested for each message that contain the AVP, not only the first match.
+
+# There can be multiple blocks of AVPs and rules; just start the next one with another AVP line:
+#  AVP = "Other-AVP";
+# and continue with rules as above.
--- a/extensions/rt_ereg/rtereg.c	Tue Apr 09 15:47:39 2019 +0200
+++ b/extensions/rt_ereg/rtereg.c	Tue Apr 09 15:48:45 2019 +0200
@@ -33,16 +33,26 @@
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
 *********************************************************************************************************/
 
-/* 
+/*
  * This extension allows to perform some pattern-matching on an AVP
  * and send the message to a server accordingly.
  * See rt_ereg.conf.sample file for the format of the configuration file.
  */
 
+#include <pthread.h>
+#include <signal.h>
+
 #include "rtereg.h"
 
+static pthread_rwlock_t rte_lock;
+
+#define MODULE_NAME "rt_ereg"
+
+static char *rt_ereg_config_file;
+
 /* The configuration structure */
-struct rtereg_conf rtereg_conf;
+struct rtereg_conf *rtereg_conf;
+int rtereg_conf_size;
 
 #ifndef HAVE_REG_STARTEND
 static char * buf = NULL;
@@ -50,18 +60,41 @@
 static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
 #endif /* HAVE_REG_STARTEND */
 
-static int proceed(char * value, size_t len, struct fd_list * candidates)
+static int rtereg_init(void);
+static int rtereg_init_config(void);
+static void rtereg_fini(void);
+
+void rtereg_conf_free(struct rtereg_conf *config_struct, int config_size)
+{
+	int i, j;
+
+    	/* Destroy the data */
+	for (j=0; j<config_size; j++) {
+		if (config_struct[j].rules) {
+			for (i = 0; i < config_struct[j].rules_nb; i++) {
+				free(config_struct[j].rules[i].pattern);
+				free(config_struct[j].rules[i].server);
+				regfree(&config_struct[j].rules[i].preg);
+			}
+		}
+		free(config_struct[j].avps);
+		free(config_struct[j].rules);
+	}
+	free(config_struct);
+}
+
+static int proceed(char * value, size_t len, struct fd_list * candidates, int conf)
 {
 	int i;
-	
-	for (i = 0; i < rtereg_conf.rules_nb; i++) {
+
+	for (i = 0; i < rtereg_conf[conf].rules_nb; i++) {
 		/* Does this pattern match the value? */
-		struct rtereg_rule * r = &rtereg_conf.rules[i];
+		struct rtereg_rule * r = &rtereg_conf[conf].rules[i];
 		int err = 0;
 		struct fd_list * c;
-		
+
 		TRACE_DEBUG(ANNOYING, "Attempt pattern matching of '%.*s' with rule '%s'", (int)len, value, r->pattern);
-		
+
 		#ifdef HAVE_REG_STARTEND
 		{
 			regmatch_t pmatch[1];
@@ -76,10 +109,10 @@
 			err = regexec(&r->preg, value, 0, NULL, 0);
 		}
 		#endif /* HAVE_REG_STARTEND */
-		
+
 		if (err == REG_NOMATCH)
 			continue;
-			
+
 		if (err != 0) {
 			char * errstr;
 			size_t bl;
@@ -99,10 +132,10 @@
 
 			/* Free the buffer, return the error */
 			free(errstr);
-			
+
 			return (err == REG_ESPACE) ? ENOMEM : EINVAL;
 		}
-		
+
 		/* From this point, the expression matched the AVP value */
 		TRACE_DEBUG(FULL, "[rt_ereg] Match: '%s' to value '%.*s' => '%s' += %d",
 					r->pattern,
@@ -110,7 +143,7 @@
 					value,
 					r->server,
 					r->score);
-		
+
 		for (c = candidates->next; c != candidates; c = c->next) {
 			struct rtd_candidate * cand = (struct rtd_candidate *)c;
 
@@ -120,106 +153,240 @@
 			}
 		}
 	};
-	
+
+	return 0;
+}
+
+static int find_avp(msg_or_avp *where, int conf_index, int level, struct fd_list * candidates)
+{
+	struct dict_object *what;
+	struct dict_avp_data dictdata;
+	struct avp *nextavp = NULL;
+	struct avp_hdr *avp_hdr = NULL;
+
+	/* iterate over all AVPs and try to find a match */
+//	for (i = 0; i<rtereg_conf[j].level; i++) {
+	if (level > rtereg_conf[conf_index].level) {
+		TRACE_DEBUG(INFO, "internal error, dug too deep");
+		return 1;
+	}
+	what = rtereg_conf[conf_index].avps[level];
+
+	CHECK_FCT(fd_dict_getval(what, &dictdata));
+	CHECK_FCT(fd_msg_browse(where, MSG_BRW_FIRST_CHILD, (void *)&nextavp, NULL));
+	while (nextavp) {
+		CHECK_FCT(fd_msg_avp_hdr(nextavp, &avp_hdr));
+		if ((avp_hdr->avp_code == dictdata.avp_code) && (avp_hdr->avp_vendor == dictdata.avp_vendor)) {
+			if (level != rtereg_conf[conf_index].level - 1) {
+				TRACE_DEBUG(INFO, "[rt_ereg] found grouped AVP %d (vendor %d), digging deeper", avp_hdr->avp_code, avp_hdr->avp_vendor);
+				CHECK_FCT(find_avp(nextavp, conf_index, level+1, candidates));
+			} else {
+				TRACE_DEBUG(INFO, "[rt_ereg] found AVP %d (vendor %d)", avp_hdr->avp_code, avp_hdr->avp_vendor);
+				if (avp_hdr->avp_value != NULL) {
+#ifndef HAVE_REG_STARTEND
+					int ret;
+
+					/* Lock the buffer */
+					CHECK_POSIX( pthread_mutex_lock(&mtx) );
+
+					/* Augment the buffer if needed */
+					if (avp_hdr->avp_value->os.len >= bufsz) {
+						CHECK_MALLOC_DO( buf = realloc(buf, avp_hdr->avp_value->os.len + 1),
+							{ pthread_mutex_unlock(&mtx); return ENOMEM; } );
+					}
+
+					/* Copy the AVP value */
+					memcpy(buf, avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len);
+					buf[avp_hdr->avp_value->os.len] = '\0';
+
+					/* Now apply the rules */
+					ret = proceed(buf, avp_hdr->avp_value->os.len, candidates, conf_index);
+
+					CHECK_POSIX(pthread_mutex_unlock(&mtx));
+
+					CHECK_FCT(ret);
+#else /* HAVE_REG_STARTEND */
+					CHECK_FCT( proceed((char *) avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len, candidates, conf_index) );
+#endif /* HAVE_REG_STARTEND */
+				}
+			}
+		}
+		CHECK_FCT(fd_msg_browse(nextavp, MSG_BRW_NEXT, (void *)&nextavp, NULL));
+	}
+
 	return 0;
 }
 
 /* The callback called on new messages */
 static int rtereg_out(void * cbdata, struct msg ** pmsg, struct fd_list * candidates)
 {
-	struct msg * msg = *pmsg;
-	struct avp * avp = NULL;
-	
-	TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
-	
-	CHECK_PARAMS(msg && candidates);
-	
-	/* Check if it is worth processing the message */
-	if (FD_IS_LIST_EMPTY(candidates)) {
+	msg_or_avp *where;
+	int j, ret;
+
+	TRACE_ENTRY("%p %p %p", cbdata, *pmsg, candidates);
+
+	CHECK_PARAMS(pmsg && *pmsg && candidates);
+
+	if (pthread_rwlock_rdlock(&rte_lock) != 0) {
+		fd_log_notice("%s: read-lock failed, skipping handler", MODULE_NAME);
 		return 0;
 	}
-	
-	/* Now search the AVP in the message */
-	CHECK_FCT( fd_msg_search_avp ( msg, rtereg_conf.avp, &avp ) );
-	if (avp != NULL) {
-		struct avp_hdr * ahdr = NULL;
-		CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
-		if (ahdr->avp_value != NULL) {
-#ifndef HAVE_REG_STARTEND
-			int ret;
-		
-			/* Lock the buffer */
-			CHECK_POSIX( pthread_mutex_lock(&mtx) );
-			
-			/* Augment the buffer if needed */
-			if (ahdr->avp_value->os.len >= bufsz) {
-				CHECK_MALLOC_DO( buf = realloc(buf, ahdr->avp_value->os.len + 1), 
-					{ pthread_mutex_unlock(&mtx); return ENOMEM; } );
+	ret = 0;
+	/* Check if it is worth processing the message */
+	if (!FD_IS_LIST_EMPTY(candidates)) {
+		/* Now search the AVPs in the message */
+
+		for (j=0; j<rtereg_conf_size; j++) {
+			where = *pmsg;
+			TRACE_DEBUG(INFO, "[rt_ereg] iterating over AVP group %d", j);
+			if ((ret=find_avp(where, j, 0, candidates)) != 0) {
+				break;
 			}
-			
-			/* Copy the AVP value */
-			memcpy(buf, ahdr->avp_value->os.data, ahdr->avp_value->os.len);
-			buf[ahdr->avp_value->os.len] = '\0';
-			
-			/* Now apply the rules */
-			ret = proceed(buf, ahdr->avp_value->os.len, candidates);
-			
-			CHECK_POSIX(pthread_mutex_unlock(&mtx));
-			
-			CHECK_FCT(ret);
-#else /* HAVE_REG_STARTEND */
-			CHECK_FCT( proceed((char *) ahdr->avp_value->os.data, ahdr->avp_value->os.len, candidates) );
-#endif /* HAVE_REG_STARTEND */
 		}
 	}
-	
-	return 0;
+	if (pthread_rwlock_unlock(&rte_lock) != 0) {
+		fd_log_notice("%s: read-unlock failed after rtereg_out, exiting", MODULE_NAME);
+		exit(1);
+	}
+
+	return ret;
 }
 
 /* handler */
 static struct fd_rt_out_hdl * rtereg_hdl = NULL;
 
+static volatile int in_signal_handler = 0;
+
+/* signal handler */
+static void sig_hdlr(void)
+{
+	struct rtereg_conf *old_config;
+	int old_config_size;
+
+	if (in_signal_handler) {
+		fd_log_error("%s: already handling a signal, ignoring new one", MODULE_NAME);
+		return;
+	}
+	in_signal_handler = 1;
+
+	if (pthread_rwlock_wrlock(&rte_lock) != 0) {
+		fd_log_error("%s: locking failed, aborting config reload", MODULE_NAME);
+		return;
+	}
+
+	/* save old config in case reload goes wrong */
+	old_config = rtereg_conf;
+	old_config_size = rtereg_conf_size;
+	rtereg_conf = NULL;
+	rtereg_conf_size = 0;
+
+	if (rtereg_init_config() != 0) {
+		fd_log_notice("%s: error reloading configuration, restoring previous configuration", MODULE_NAME);
+		rtereg_conf = old_config;
+		rtereg_conf_size = old_config_size;
+	} else {
+		rtereg_conf_free(old_config, old_config_size);
+	}
+
+	if (pthread_rwlock_unlock(&rte_lock) != 0) {
+		fd_log_error("%s: unlocking failed after config reload, exiting", MODULE_NAME);
+		exit(1);
+	}
+
+	fd_log_notice("%s: reloaded configuration, %d AVP group%s defined", MODULE_NAME, rtereg_conf_size, rtereg_conf_size != 1 ? "s" : "");
+
+	in_signal_handler = 0;
+}
+
 /* entry point */
 static int rtereg_entry(char * conffile)
 {
 	TRACE_ENTRY("%p", conffile);
-	
+
+	rt_ereg_config_file = conffile;
+
+	if (rtereg_init() != 0) {
+	    return 1;
+	}
+
+	/* Register reload callback */
+	CHECK_FCT(fd_event_trig_regcb(SIGUSR1, MODULE_NAME, sig_hdlr));
+
+	fd_log_notice("%s: configured, %d AVP group%s defined", MODULE_NAME, rtereg_conf_size, rtereg_conf_size != 1 ? "s" : "");
+
+	return 0;
+}
+
+static int rtereg_init_config(void)
+{
 	/* Initialize the configuration */
-	memset(&rtereg_conf, 0, sizeof(rtereg_conf));
-	
+	if ((rtereg_conf=malloc(sizeof(*rtereg_conf))) == NULL) {
+	    TRACE_DEBUG(INFO, "malloc failured");
+	    return 1;
+	}
+	rtereg_conf_size = 1;
+	memset(rtereg_conf, 0, sizeof(*rtereg_conf));
+
 	/* Parse the configuration file */
-	CHECK_FCT( rtereg_conf_handle(conffile) );
-	
+	CHECK_FCT( rtereg_conf_handle(rt_ereg_config_file) );
+
+	return 0;
+}
+
+
+/* Load */
+static int rtereg_init(void)
+{
+	int ret;
+
+	pthread_rwlock_init(&rte_lock, NULL);
+
+	if (pthread_rwlock_wrlock(&rte_lock) != 0) {
+		fd_log_notice("%s: write-lock failed, aborting", MODULE_NAME);
+		return EDEADLK;
+	}
+
+	if ((ret=rtereg_init_config()) != 0) {
+		pthread_rwlock_unlock(&rte_lock);
+		return ret;
+	}
+
+	if (pthread_rwlock_unlock(&rte_lock) != 0) {
+		fd_log_notice("%s: write-unlock failed, aborting", MODULE_NAME);
+		return EDEADLK;
+	}
+
 	/* Register the callback */
 	CHECK_FCT( fd_rt_out_register( rtereg_out, NULL, 1, &rtereg_hdl ) );
-	
+
 	/* We're done */
 	return 0;
 }
 
 /* Unload */
-void fd_ext_fini(void)
+static void rtereg_fini(void)
 {
-	int i;
 	TRACE_ENTRY();
-	
+
 	/* Unregister the cb */
 	CHECK_FCT_DO( fd_rt_out_unregister ( rtereg_hdl, NULL ), /* continue */ );
-	
+
 	/* Destroy the data */
-	if (rtereg_conf.rules) 
-		for (i = 0; i < rtereg_conf.rules_nb; i++) {
-			free(rtereg_conf.rules[i].pattern);
-			free(rtereg_conf.rules[i].server);
-			regfree(&rtereg_conf.rules[i].preg);
-		}
-	free(rtereg_conf.rules);
+	rtereg_conf_free(rtereg_conf, rtereg_conf_size);
+	rtereg_conf = NULL;
+	rtereg_conf_size = 0;
 #ifndef HAVE_REG_STARTEND
 	free(buf);
+	buf = NULL;
 #endif /* HAVE_REG_STARTEND */
-	
+
 	/* Done */
 	return ;
 }
 
-EXTENSION_ENTRY("rt_ereg", rtereg_entry);
+void fd_ext_fini(void)
+{
+	rtereg_fini();
+}
+
+EXTENSION_ENTRY(MODULE_NAME, rtereg_entry);
--- a/extensions/rt_ereg/rtereg.h	Tue Apr 09 15:47:39 2019 +0200
+++ b/extensions/rt_ereg/rtereg.h	Tue Apr 09 15:48:45 2019 +0200
@@ -57,8 +57,11 @@
 extern struct rtereg_conf {
 	int			rules_nb; /* Number of rules in the configuration */
 	struct rtereg_rule 	*rules;   /* The array of rules */
-	
-	struct dict_object * avp; /* cache the dictionary object that we are searching */
+
+	int                     level;    /* how many levels of AVPs we have to dig down into */
+	int			finished; /* AVP fully configured, for configuration file reading */
+	struct dict_object      **avps;   /* cache the dictionary objects that we are searching */
 	
-} rtereg_conf;
+} *rtereg_conf;
 
+extern int rtereg_conf_size;
--- a/extensions/rt_ereg/rtereg_conf.y	Tue Apr 09 15:47:39 2019 +0200
+++ b/extensions/rt_ereg/rtereg_conf.y	Tue Apr 09 15:48:45 2019 +0200
@@ -37,14 +37,14 @@
  */
 
 /* For development only : */
-%debug 
+%debug
 %error-verbose
 
 /* The parser receives the configuration file filename as parameter */
 %parse-param {char * conffile}
 
 /* Keep track of location */
-%locations 
+%locations
 %pure-parser
 
 %{
@@ -53,36 +53,91 @@
 
 /* Forward declaration */
 int yyparse(char * conffile);
+void rtereg_confrestart(FILE *input_file);
 
 /* Parse the configuration file */
 int rtereg_conf_handle(char * conffile)
 {
 	extern FILE * rtereg_confin;
 	int ret;
-	
+
 	TRACE_ENTRY("%p", conffile);
-	
+
 	TRACE_DEBUG (FULL, "Parsing configuration file: %s...", conffile);
-	
+
 	rtereg_confin = fopen(conffile, "r");
 	if (rtereg_confin == NULL) {
 		ret = errno;
 		fd_log_debug("Unable to open extension configuration file %s for reading: %s", conffile, strerror(ret));
-		TRACE_DEBUG (INFO, "Error occurred, message logged -- configuration file.");
+		TRACE_DEBUG (INFO, "rt_ereg: error occurred, message logged -- configuration file.");
 		return ret;
 	}
 
+	rtereg_confrestart(rtereg_confin);
 	ret = yyparse(conffile);
 
 	fclose(rtereg_confin);
 
+	if (rtereg_conf[rtereg_conf_size-1].finished == 0) {
+		TRACE_DEBUG(INFO, "rt_ereg: configuration invalid, AVP ended without OCTETSTRING AVP");
+		return EINVAL;
+	}
+
 	if (ret != 0) {
-		TRACE_DEBUG (INFO, "Unable to parse the configuration file.");
+		TRACE_DEBUG(INFO, "rt_ereg: unable to parse the configuration file.");
 		return EINVAL;
 	} else {
-		TRACE_DEBUG(FULL, "[rt-ereg] Added %d rules successfully.", rtereg_conf.rules_nb);
+		int i, sum = 0;
+		for (i=0; i<rtereg_conf_size; i++) {
+			sum += rtereg_conf[i].rules_nb;
+		}
+		TRACE_DEBUG(FULL, "[rt-ereg] Added %d rules successfully.", sum);
+	}
+
+	return 0;
+}
+
+int avp_add(const char *name)
+{
+	void *ret;
+	int level;
+
+	if (rtereg_conf[rtereg_conf_size-1].finished) {
+		if ((ret = realloc(rtereg_conf, sizeof(*rtereg_conf)*(rtereg_conf_size+1))) == NULL) {
+			TRACE_DEBUG(INFO, "rt_ereg: realloc failed");
+			return -1;
+		}
+		rtereg_conf_size++;
+		rtereg_conf = ret;
+		memset(&rtereg_conf[rtereg_conf_size-1], 0, sizeof(*rtereg_conf));
+		TRACE_DEBUG(INFO, "rt_ereg: New AVP group found starting with %s", name);
 	}
-	
+	level = rtereg_conf[rtereg_conf_size-1].level + 1;
+
+	if ((ret = realloc(rtereg_conf[rtereg_conf_size-1].avps, sizeof(*rtereg_conf[rtereg_conf_size-1].avps)*level)) == NULL) {
+		TRACE_DEBUG(INFO, "rt_ereg: realloc failed");
+		return -1;
+	}
+	rtereg_conf[rtereg_conf_size-1].avps = ret;
+
+	CHECK_FCT_DO( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, name, &rtereg_conf[rtereg_conf_size-1].avps[level-1], ENOENT ),
+		      {
+			      TRACE_DEBUG(INFO, "rt_ereg: Unable to find '%s' AVP in the loaded dictionaries.", name);
+			      return -1;
+		      } );
+
+	/* Now check the type */
+	{
+		struct dict_avp_data data;
+		CHECK_FCT( fd_dict_getval( rtereg_conf[rtereg_conf_size-1].avps[level-1], &data) );
+		if (data.avp_basetype == AVP_TYPE_OCTETSTRING) {
+			rtereg_conf[rtereg_conf_size-1].finished = 1;
+		} else if (data.avp_basetype != AVP_TYPE_GROUPED) {
+			TRACE_DEBUG(INFO, "rt_ereg: '%s' AVP is not an OCTETSTRING nor GROUPED AVP (%d).", name, data.avp_basetype);
+			return -1;
+		}
+	}
+	rtereg_conf[rtereg_conf_size-1].level = level;
 	return 0;
 }
 
@@ -92,8 +147,8 @@
 /* Function to report the errors */
 void yyerror (YYLTYPE *ploc, char * conffile, char const *s)
 {
-	TRACE_DEBUG(INFO, "Error in configuration parsing");
-	
+	TRACE_DEBUG(INFO, "rt_ereg: error in configuration parsing");
+
 	if (ploc->first_line != ploc->last_line)
 		fd_log_debug("%s:%d.%d-%d.%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_line, ploc->last_column, s);
 	else if (ploc->first_column != ploc->last_column)
@@ -125,61 +180,41 @@
 %%
 
 	/* The grammar definition */
-conffile:		rules avp rules
+conffile:		avp rules
+			| conffile avp rules
 			;
-			
+
 	/* a server entry */
-avp:			AVP '=' QSTRING ';'
-			{
-				if (rtereg_conf.avp != NULL) {
-					yyerror(&yylloc, conffile, "Only one AVP can be specified");
-					YYERROR;
-				}
-				
-				CHECK_FCT_DO( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, $3, &rtereg_conf.avp, ENOENT ),
-					{
-						TRACE_DEBUG(INFO, "Unable to find '%s' AVP in the loaded dictionaries.", $3);
-						yyerror (&yylloc, conffile, "Invalid AVP value.");
-						YYERROR;
-					} );
-				
-				/* Now check the type */
-				{
-					struct dict_avp_data data;
-					CHECK_FCT( fd_dict_getval( rtereg_conf.avp, &data) );
-					CHECK_PARAMS_DO (data.avp_basetype == AVP_TYPE_OCTETSTRING, 
-						{
-							TRACE_DEBUG(INFO, "'%s' AVP in not an OCTETSTRING AVP (%d).", $3, data.avp_basetype);
-							yyerror (&yylloc, conffile, "AVP in not an OCTETSTRING type.");
-							YYERROR;
-						} );
-				}
-			}
+avp:			AVP '=' avp_part ';'
 			;
-			
+
+avp_part: 		avp_part ':' QSTRING { if (avp_add($3) < 0) { YYERROR; } }
+			| QSTRING { if (avp_add($1) < 0) { YYERROR; } }
+			;
+
 rules:			/* empty OK */
 			| rules rule
 			;
-			
+
 rule:			QSTRING ':' QSTRING '+' '=' INTEGER ';'
 			{
 				struct rtereg_rule * new;
 				int err;
-				
+
 				/* Add new rule in the array */
-				rtereg_conf.rules_nb += 1;
-				CHECK_MALLOC_DO(rtereg_conf.rules = realloc(rtereg_conf.rules, rtereg_conf.rules_nb * sizeof(struct rtereg_rule)),
+				rtereg_conf[rtereg_conf_size-1].rules_nb += 1;
+				CHECK_MALLOC_DO(rtereg_conf[rtereg_conf_size-1].rules = realloc(rtereg_conf[rtereg_conf_size-1].rules, rtereg_conf[rtereg_conf_size-1].rules_nb * sizeof(struct rtereg_rule)),
 					{
 						yyerror (&yylloc, conffile, "Not enough memory to store the configuration...");
 						YYERROR;
 					} );
-				
-				new = &rtereg_conf.rules[rtereg_conf.rules_nb - 1];
-				
+
+				new = &rtereg_conf[rtereg_conf_size-1].rules[rtereg_conf[rtereg_conf_size-1].rules_nb - 1];
+
 				new->pattern = $1;
 				new->server  = $3;
 				new->score   = $6;
-				
+
 				/* Attempt to compile the regex */
 				CHECK_FCT_DO( err=regcomp(&new->preg, new->pattern, REG_EXTENDED | REG_NOSUB),
 					{
@@ -187,7 +222,7 @@
 						size_t bl;
 
 						/* Error while compiling the regex */
-						TRACE_DEBUG(INFO, "Error while compiling the regular expression '%s':", new->pattern);
+						TRACE_DEBUG(INFO, "rt_ereg: error while compiling the regular expression '%s':", new->pattern);
 
 						/* Get the error message size */
 						bl = regerror(err, &new->preg, NULL, 0);
@@ -198,10 +233,10 @@
 						/* Get the error message content */
 						regerror(err, &new->preg, buf, bl);
 						TRACE_DEBUG(INFO, "\t%s", buf);
-						
+
 						/* Free the buffer, return the error */
 						free(buf);
-						
+
 						yyerror (&yylloc, conffile, "Invalid regular expression.");
 						YYERROR;
 					} );
"Welcome to our mercurial repository"