changeset 1341:b0401251d8c0

rt_rewrite: new extension This extension allows rewriting messages: putting data from one AVP into another, or removing AVPs altogether. Written for Effortel Technologies SA and published with their consent.
author Thomas Klausner <tk@giga.or.at>
date Tue, 09 Apr 2019 16:01:29 +0200
parents daf61e573fee
children fedc9eea70bc
files doc/rt_rewrite.conf.sample extensions/CMakeLists.txt extensions/rt_rewrite/CMakeLists.txt extensions/rt_rewrite/rt_rewrite.c extensions/rt_rewrite/rt_rewrite.h extensions/rt_rewrite/rt_rewrite_conf.l extensions/rt_rewrite/rt_rewrite_conf.y
diffstat 7 files changed, 1090 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/rt_rewrite.conf.sample	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,23 @@
+# This file contains information for configuring the rt_rewrite extension
+# To find how to have freeDiameter load this extension, please refer to the freeDiameter documentation.
+#
+# The rt_rewrite extension allows moving data from one AVP into another one, or dropping AVPs altogether.
+
+# This extension supports configuration reload at runtime. Send
+# signal SIGUSR1 to the process to cause the process to reload its
+# config.
+
+# The config consists of MAP for AVP pairs of source/target, or DROP for a source.
+#
+#  MAP = "Source-AVP" > "Destination-AVP";
+#
+# It is possible to specify AVPs below GROUPED AVPs with the by separating AVPs with a colon (':'):
+#  MAP = "Grouped-AVP1" : "Octetstring-AVP1" > "Grouped-AVP2" : "Grouped-AVP3" : "Octetstring-AVP2";
+# Intermediate destination grouped AVPs will be created automatically.
+#
+# NOTE: you can not move grouped AVPs as a unit, you have to move each separate AVP.
+# i.e., this will not work:
+#  MAP = "Grouped-AVP1" > "Grouped-AVP2";
+#
+# For removing AVPs, use DROP:
+#  DROP = "Grouped-AVP1" : "Octetstring-AVP1";
--- a/extensions/CMakeLists.txt	Tue Apr 09 15:57:12 2019 +0200
+++ b/extensions/CMakeLists.txt	Tue Apr 09 16:01:29 2019 +0200
@@ -76,6 +76,7 @@
 FD_EXTENSION_SUBDIR(rt_load_balance "Balance load over multiple equal hosts, based on outstanding requests"	ON)
 FD_EXTENSION_SUBDIR(rt_randomize    "Randomly choose one of the highest scored hosts and increase its score by one"	ON)
 FD_EXTENSION_SUBDIR(rt_redirect     "Handling of Diameter Redirect messages" 			ON)
+FD_EXTENSION_SUBDIR(rt_rewrite      "Convert/remove AVP data in messages" 			ON)
 
 
 ####
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_rewrite/CMakeLists.txt	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,27 @@
+# The rt_rewrite extension
+PROJECT("Routing extension that rewrites messages, replacing and removing AVPs" C)
+
+# List of source files
+SET(RT_REWRITE_SRC
+	rt_rewrite.c
+	lex.rt_rewrite_conf.c
+	rt_rewrite_conf.tab.c
+	rt_rewrite_conf.tab.h
+)
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
+
+BISON_FILE(rt_rewrite_conf.y)
+FLEX_FILE(rt_rewrite_conf.l)
+SET_SOURCE_FILES_PROPERTIES(lex.rt_rewrite_conf.c rt_rewrite_conf.tab.c PROPERTIES COMPILE_FLAGS "-I ${CMAKE_CURRENT_SOURCE_DIR}")
+
+# Compile these files as a freeDiameter extension
+FD_ADD_EXTENSION(rt_rewrite ${RT_REWRITE_SRC})
+
+####
+## INSTALL section ##
+
+# We install with the daemon component because it is a base feature.
+INSTALL(TARGETS rt_rewrite
+	LIBRARY DESTINATION ${INSTALL_EXTENSIONS_SUFFIX}
+	COMPONENT freeDiameter-daemon)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_rewrite/rt_rewrite.c	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,461 @@
+/*********************************************************************************************************
+ * Software License Agreement (BSD License)                                                               *
+ * Author: Thomas Klausner <tk@giga.or.at>                                                                *
+ *                                                                                                        *
+ * Copyright (c) 2018, Thomas Klausner                                                                    *
+ * All rights reserved.                                                                                   *
+ *                                                                                                        *
+ * Written under contract by Effortel Technologies SA, http://effortel.com/                               *
+ *                                                                                                        *
+ * 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.                                                            *
+ *                                                                                                        *
+ * 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 <freeDiameter/extension.h>
+#include "rt_rewrite.h"
+
+#include <pthread.h>
+#include <signal.h>
+
+/*
+ * Replace AVPs: put their values into other AVPs
+ * Remove AVPs: drop them from a message
+ */
+
+/* handler */
+static struct fd_rt_fwd_hdl * rt_rewrite_handle = NULL;
+
+static char *config_file;
+
+struct avp_match *avp_match_start = NULL;
+
+static pthread_rwlock_t rt_rewrite_lock;
+
+#define MODULE_NAME "rt_rewrite"
+
+struct store {
+	struct avp *avp;
+	struct avp_target *target;
+	struct store *next;
+};
+
+static struct store *store_new(void) {
+	struct store *ret;
+
+	if ((ret=malloc(sizeof(*ret))) == NULL) {
+		fd_log_error("%s: malloc failure", MODULE_NAME);
+		return NULL;
+	}
+	ret->avp = NULL;
+	ret->target = NULL;
+	ret->next = NULL;
+
+	return ret;
+}
+
+static void store_free(struct store *store)
+{
+	while (store) {
+		struct store *next;
+		if (store->avp) {
+			fd_msg_free((msg_or_avp *)store->avp);
+		}
+		store->target = NULL;
+		next = store->next;
+		free(store);
+		store = next;
+	}
+	return;
+}
+
+static int fd_avp_search_avp(msg_or_avp *where, struct dict_object *what, struct avp **avp)
+{
+	struct avp *nextavp;
+	struct dict_avp_data dictdata;
+	enum dict_object_type dicttype;
+
+	CHECK_PARAMS((fd_dict_gettype(what, &dicttype) == 0) && (dicttype == DICT_AVP));
+	CHECK_FCT(fd_dict_getval(what, &dictdata));
+
+	/* Loop on all top AVPs */
+	CHECK_FCT(fd_msg_browse(where, MSG_BRW_FIRST_CHILD, (void *)&nextavp, NULL));
+	while (nextavp) {
+		struct avp_hdr *header = NULL;
+		struct dict_object *model = NULL;
+		if (fd_msg_avp_hdr(nextavp, &header) != 0) {
+			fd_log_notice("%s: unable to get header for AVP", MODULE_NAME);
+			return -1;
+		}
+		if (fd_msg_model(nextavp, &model) != 0) {
+			fd_log_notice("%s: unable to get model for AVP (%d, vendor %d)", MODULE_NAME, header->avp_code, header->avp_vendor);
+			return 0;
+		}
+		if (model == NULL) {
+			fd_log_notice("%s: unknown AVP (%d, vendor %d) (not in dictionary), skipping", MODULE_NAME, header->avp_code, header->avp_vendor);
+		} else {
+			if ((header->avp_code == dictdata.avp_code) && (header->avp_vendor == dictdata.avp_vendor)) {
+				break;
+			}
+		}
+
+		/* Otherwise move to next AVP in the message */
+		CHECK_FCT(fd_msg_browse(nextavp, MSG_BRW_NEXT, (void *)&nextavp, NULL) );
+	}
+
+	if (avp)
+		*avp = nextavp;
+
+	if (*avp || nextavp) {
+		return 0;
+	} else {
+		return ENOENT;
+	}
+}
+
+
+
+static msg_or_avp *find_container(msg_or_avp *msg, struct avp_target *target)
+{
+	msg_or_avp *location = msg;
+	while (target->child) {
+		struct dict_object *avp_do;
+		msg_or_avp *new_location = NULL;
+		int ret;
+
+		fd_log_debug("%s: looking for '%s'", MODULE_NAME, target->name);
+		if ((ret=fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, target->name, &avp_do, ENOENT)) != 0) {
+			fd_log_error("%s: target AVP '%s' not in dictionary: %s", MODULE_NAME, target->name, strerror(ret));
+			return NULL;
+		}
+		if ((ret=fd_avp_search_avp(location, avp_do, (struct avp **)&new_location)) != 0) {
+			fd_log_debug("%s: did not find '%s', adding it", MODULE_NAME, target->name);
+			struct avp *avp = NULL;
+			if ((ret = fd_msg_avp_new(avp_do, 0, &avp)) != 0) {
+				fd_log_notice("%s: unable to create new '%s' AVP", MODULE_NAME, target->name);
+				return NULL;
+			}
+			if ((ret=fd_msg_avp_add(location, MSG_BRW_LAST_CHILD, avp)) != 0) {
+				fd_log_error("%s: cannot add AVP '%s' to message: %s", MODULE_NAME, target->name, strerror(ret));
+				return NULL;
+			}
+			fd_log_debug("%s: '%s' added", MODULE_NAME, target->name);
+			/* now find it in the new place */
+			continue;
+		} else {
+			location = new_location;
+			fd_log_debug("%s: found '%s'", MODULE_NAME, target->name);
+		}
+		target = target->child;
+	}
+	fd_log_debug("%s: returning AVP for '%s'", MODULE_NAME, target->name);
+	return location;
+}
+
+static int store_apply(msg_or_avp *msg, struct store **store)
+{
+	while (*store) {
+		msg_or_avp *container;
+		struct store *next;
+		if ((*store)->avp) {
+			int ret;
+			if ((container=find_container(msg, (*store)->target)) == NULL) {
+				fd_log_error("%s: internal error, container not found", MODULE_NAME);
+				return -1;
+			}
+
+			if ((ret=fd_msg_avp_add(container, MSG_BRW_LAST_CHILD, (*store)->avp)) != 0) {
+				fd_log_error("%s: cannot add AVP '%s' to message: %s", MODULE_NAME, (*store)->target->name, strerror(ret));
+				return -1;
+			}
+		}
+		next = (*store)->next;
+		free(*store);
+		*store = next;
+	}
+	return 0;
+}
+
+static int schedule_for_adding(struct avp *avp, struct avp_target *target, struct store *store)
+{
+	if (store->avp) {
+		struct store *new;
+		if ((new=store_new()) == NULL) {
+			return -1;
+		}
+		new->avp = avp;
+		new->target = target;
+		new->next = store->next;
+		store->next = new;
+	} else {
+		store->avp = avp;
+		store->target = target;
+	}
+	fd_log_debug("%s: noted %s for later adding", MODULE_NAME, target->name);
+	return 0;
+}
+
+static void move_avp_to_target(union avp_value *avp_value, struct avp_target *target, struct store *store)
+{
+	struct dict_object *avp_do;
+	struct avp *avp;
+	int ret;
+	struct avp_target *final = target;
+
+	while (final->child) {
+		final = final->child;
+	}
+	if ((ret=fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, final->name, &avp_do, ENOENT)) != 0) {
+		fd_log_error("internal error, target AVP '%s' not in dictionary: %s", final->name, strerror(ret));
+		return;
+	}
+	if ((ret=fd_msg_avp_new(avp_do, 0, &avp)) != 0) {
+		fd_log_error("internal error, error creating structure for AVP '%s': %s", final->name, strerror(ret));
+		return;
+	}
+	if ((ret=fd_msg_avp_setvalue(avp, avp_value)) != 0) {
+		fd_log_error("internal error, cannot set value for AVP '%s': %s", final->name, strerror(ret));
+		return;
+	}
+	if (schedule_for_adding(avp, target, store) != 0) {
+		fd_log_error("internal error, cannot add AVP '%s' to message", final->name);
+		return;
+	}
+
+	return;
+}
+
+
+static struct avp_match *avp_to_be_replaced(const char *avp_name, struct avp_match *subtree)
+{
+	struct avp_match *ret;
+	for (ret=subtree; ret != NULL; ret=ret->next) {
+		if (strcmp(ret->name, avp_name) == 0) {
+			return ret;
+		}
+	}
+	return NULL;
+}
+
+/*
+ * msg: whole message
+ * avp: current AVP in message
+ * subtree: comparison subtree
+ */
+static int replace_avps(struct msg *msg, msg_or_avp *avp, struct avp_match *subtree, struct store *store)
+{
+	int nothing_left = 1;
+	/* for each AVP in message */
+	while (avp) {
+		struct avp_hdr *header = NULL;
+		struct dict_object *model = NULL;
+		const char *avp_name = NULL;
+		msg_or_avp *next;
+		int delete = 0;
+
+		if (fd_msg_avp_hdr(avp, &header) != 0) {
+			fd_log_notice("internal error: unable to get header for AVP");
+			return 0;
+		}
+		if (fd_msg_model(avp, &model) != 0) {
+			fd_log_notice("internal error: unable to get model for AVP (%d, vendor %d)", header->avp_code, header->avp_vendor);
+			return 0;
+		}
+		if (model == NULL) {
+			fd_log_notice("unknown AVP (%d, vendor %d) (not in dictionary), skipping", header->avp_code, header->avp_vendor);
+
+		} else {
+			struct dict_avp_data dictdata;
+			struct avp_match *subtree_match;
+			enum dict_avp_basetype basetype = AVP_TYPE_OCTETSTRING;
+
+			fd_dict_getval(model, &dictdata);
+			avp_name = dictdata.avp_name;
+			basetype = dictdata.avp_basetype;
+			/* check if it exists in the subtree */
+			if ((subtree_match = avp_to_be_replaced(avp_name, subtree))) {
+				/* if this should be deleted, mark as such */
+				if (subtree_match->drop) {
+					fd_log_notice("%s: dropping AVP '%s'", MODULE_NAME, avp_name);
+					delete = 1;
+				}
+				/* if grouped, dive down */
+				if (basetype == AVP_TYPE_GROUPED) {
+					msg_or_avp *child = NULL;
+
+					fd_log_debug("%s: grouped AVP '%s'", MODULE_NAME, avp_name);
+					if (fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &child, NULL) != 0) {
+						fd_log_notice("internal error: unable to browse message at AVP (%d, vendor %d)", header->avp_code, header->avp_vendor);
+						return 0;
+					}
+
+					/* replace_avps returns if the AVP was emptied out */
+					if (replace_avps(msg, child, subtree_match->children, store)) {
+						fd_log_notice("%s: removing empty grouped AVP '%s'", MODULE_NAME, avp_name);
+						delete = 1;
+					}
+				}
+				else {
+					/* if single, remove it and add replacement AVP in target structure */
+					if (subtree_match->target) {
+						struct avp_target *final = subtree_match->target;
+						while (final->child) {
+							final = final->child;
+						}
+						fd_log_notice("%s: moving AVP '%s' to '%s'", MODULE_NAME, avp_name, final->name);
+						move_avp_to_target(header->avp_value, subtree_match->target, store);
+						delete = 1;
+					}
+				}
+			}
+		}
+		fd_msg_browse(avp, MSG_BRW_NEXT, &next, NULL);
+		if (delete) {
+			/* remove AVP from message */
+			fd_msg_free((msg_or_avp *)avp);
+		} else {
+		    nothing_left = 0;
+		}
+
+		avp = next;
+	}
+
+	return nothing_left;
+}
+
+static volatile int in_signal_handler = 0;
+
+/* signal handler */
+static void sig_hdlr(void)
+{
+	struct avp_match *old_config;
+
+	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(&rt_rewrite_lock) != 0) {
+		fd_log_error("%s: locking failed, aborting config reload", MODULE_NAME);
+		return;
+	}
+
+	/* save old config in case reload goes wrong */
+	old_config = avp_match_start;
+	avp_match_start = NULL;
+
+	if (rt_rewrite_conf_handle(config_file) != 0) {
+		fd_log_notice("%s: error reloading configuration, restoring previous configuration", MODULE_NAME);
+		avp_match_free(avp_match_start);
+		avp_match_start = old_config;
+	} else {
+		avp_match_free(old_config);
+	}
+
+	if (pthread_rwlock_unlock(&rt_rewrite_lock) != 0) {
+		fd_log_error("%s: unlocking failed after config reload, exiting", MODULE_NAME);
+		exit(1);
+	}
+
+	fd_log_notice("%s: reloaded configuration", MODULE_NAME);
+
+	in_signal_handler = 0;
+}
+
+static int rt_rewrite(void * cbdata, struct msg **msg)
+{
+	int ret;
+	msg_or_avp *avp = NULL;
+	struct store *store = NULL;
+
+	/* nothing configured */
+	if (avp_match_start == NULL) {
+		return 0;
+	}
+
+	if ((store=store_new()) == NULL) {
+		fd_log_error("%s: malloc failure");
+		return ENOMEM;
+	}
+	if ((ret = fd_msg_parse_dict(*msg, fd_g_config->cnf_dict, NULL)) != 0) {
+		fd_log_notice("%s: error parsing message", MODULE_NAME);
+		free(store);
+		return ret;
+	}
+	if ((ret=fd_msg_browse(*msg, MSG_BRW_FIRST_CHILD, &avp, NULL)) != 0) {
+		fd_log_notice("internal error: message has no child");
+		free(store);
+		return ret;
+	}
+	if (replace_avps(*msg, avp, avp_match_start->children, store) != 0) {
+		fd_log_error("%s: replace AVP function failed", MODULE_NAME);
+		store_free(store);
+		return -1;
+	}
+	return store_apply(*msg, &store);
+}
+
+/* entry point */
+static int rt_rewrite_entry(char * conffile)
+{
+	int ret;
+	config_file = conffile;
+
+	pthread_rwlock_init(&rt_rewrite_lock, NULL);
+
+	if (pthread_rwlock_wrlock(&rt_rewrite_lock) != 0) {
+		fd_log_notice("%s: write-lock failed, aborting", MODULE_NAME);
+		return EDEADLK;
+	}
+
+	/* Parse the configuration file */
+	if ((ret=rt_rewrite_conf_handle(config_file)) != 0) {
+		pthread_rwlock_unlock(&rt_rewrite_lock);
+		return ret;
+	}
+
+	if (pthread_rwlock_unlock(&rt_rewrite_lock) != 0) {
+		fd_log_notice("%s: write-unlock failed, aborting", MODULE_NAME);
+		return EDEADLK;
+	}
+
+	/* Register reload callback */
+	CHECK_FCT(fd_event_trig_regcb(SIGUSR1, MODULE_NAME, sig_hdlr));
+
+	/* Register the callback */
+	if ((ret=fd_rt_fwd_register(rt_rewrite, NULL, RT_FWD_ALL, &rt_rewrite_handle)) != 0) {
+		fd_log_error("Cannot register callback handler");
+		return ret;
+	}
+
+	fd_log_notice("Extension 'Rewrite' initialized");
+	return 0;
+}
+
+/* Unload */
+void fd_ext_fini(void)
+{
+	/* Unregister the callbacks */
+	fd_rt_fwd_unregister(rt_rewrite_handle, NULL);
+	return ;
+}
+
+EXTENSION_ENTRY("rt_rewrite", rt_rewrite_entry);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_rewrite/rt_rewrite.h	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,57 @@
+/*********************************************************************************************************
+ * Software License Agreement (BSD License)                                                               *
+ * Author: Thomas Klausner <tk@giga.or.at>                                                                *
+ *                                                                                                        *
+ * Copyright (c) 2018, Thomas Klausner                                                                    *
+ * All rights reserved.                                                                                   *
+ *                                                                                                        *
+ * Written under contract by Effortel Technologies SA, http://effortel.com/                               *
+ *                                                                                                        *
+ * 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.                                                            *
+ *                                                                                                        *
+ * 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.                                                             *
+ *********************************************************************************************************/
+
+/* FreeDiameter's common include file */
+#include <freeDiameter/extension.h>
+
+/* Parse the configuration file */
+int rt_rewrite_conf_handle(char * conffile);
+
+extern struct avp_match *avp_match_start;
+struct avp_match {
+	char *name;
+
+	struct avp_match *next;
+
+	/* either avp_children_list OR avp_target can be used */
+	/* NULL avp_children_list AND NULL avp_target -> drop AVP */
+	struct avp_match *children;
+	struct avp_target *target;
+	int drop;
+};
+
+struct avp_target {
+	char *name;
+
+	struct avp_target *child;
+};
+
+void avp_match_free(struct avp_match *match);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_rewrite/rt_rewrite_conf.l	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,100 @@
+/*********************************************************************************************************
+ * Software License Agreement (BSD License)                                                               *
+ * Author: Thomas Klausner <tk@giga.or.at>                                                                *
+ *                                                                                                        *
+ * Copyright (c) 2018, Thomas Klausner                                                                    *
+ * All rights reserved.                                                                                   *
+ *                                                                                                        *
+ * Written under contract by Effortel Technologies SA, http://effortel.com/                               *
+ *                                                                                                        *
+ * 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.                                                            *
+ *                                                                                                        *
+ * 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.                                                             *
+ *********************************************************************************************************/
+
+/* Tokenizer
+ *
+ */
+
+%{
+#include "rt_rewrite.h"
+/* Include yacc tokens definitions */
+#include "rt_rewrite_conf.tab.h"
+
+/* Update the column information */
+#define YY_USER_ACTION { 						\
+	yylloc->first_column = yylloc->last_column + 1; 		\
+	yylloc->last_column = yylloc->first_column + yyleng - 1;	\
+}
+
+/* Avoid warning with newer flex */
+#define YY_NO_INPUT
+
+%}
+
+qstring		\"[^\"\n]*\"
+
+
+%option bison-bridge bison-locations
+%option noyywrap
+%option nounput
+
+%%
+
+	/* Update the line count */
+\n			{
+				yylloc->first_line++;
+				yylloc->last_line++;
+				yylloc->last_column=0;
+			}
+
+	/* Eat all spaces but not new lines */
+([[:space:]]{-}[\n])+	;
+	/* Eat all comments */
+#.*$			;
+
+
+	/* Recognize quoted strings */
+{qstring}		{
+				/* Match a quoted string. */
+				CHECK_MALLOC_DO( yylval->string = strdup(yytext+1),
+				{
+					TRACE_DEBUG(INFO, "Unable to copy the string '%s': %s", yytext, strerror(errno));
+					return LEX_ERROR; /* trig an error in yacc parser */
+				} );
+				yylval->string[strlen(yytext) - 2] = '\0';
+				return QSTRING;
+			}
+
+
+	/* The key words */
+(?i:MAP)	 	{	return MAP;	}
+(?i:DROP)	 	{	return DROP;	}
+
+	/* Valid single characters for yyparse */
+[=:;>]			{ return yytext[0]; }
+
+	/* Unrecognized sequence, if it did not match any previous pattern */
+[^[:space:]\":=>;\n]+	{
+				fd_log_debug("Unrecognized text on line %d col %d: '%s'.", yylloc->first_line, yylloc->first_column, yytext);
+			 	return LEX_ERROR;
+			}
+
+%%
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_rewrite/rt_rewrite_conf.y	Tue Apr 09 16:01:29 2019 +0200
@@ -0,0 +1,421 @@
+/*********************************************************************************************************
+ * Software License Agreement (BSD License)                                                               *
+ * Author: Thomas Klausner <tk@giga.or.at>                                                                *
+ *                                                                                                        *
+ * Copyright (c) 2018, Thomas Klausner                                                                    *
+ * All rights reserved.                                                                                   *
+ *                                                                                                        *
+ * Written under contract by Effortel Technologies SA, http://effortel.com/                               *
+ *                                                                                                        *
+ * 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.                                                            *
+ *                                                                                                        *
+ * 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.                                                             *
+ *********************************************************************************************************/
+
+/* Yacc extension's configuration parser.
+ */
+
+/* For development only : */
+%debug
+%error-verbose
+
+/* The parser receives the configuration file filename as parameter */
+%parse-param {char * conffile}
+
+/* Keep track of location */
+%locations
+%pure-parser
+
+%{
+#include "rt_rewrite.h"
+#include "rt_rewrite_conf.tab.h"	/* bison is not smart enough to define the YYLTYPE before including this code, so... */
+
+/* Forward declaration */
+int yyparse(char * conffile);
+void rt_rewrite_confrestart(FILE *input_file);
+
+/* copied from libfdproto/dictionary.c because the symbol is not public */
+static const char * type_base_name[] = { /* must keep in sync with dict_avp_basetype */
+	"Grouped", 	/* AVP_TYPE_GROUPED */
+	"Octetstring", 	/* AVP_TYPE_OCTETSTRING */
+	"Integer32", 	/* AVP_TYPE_INTEGER32 */
+	"Integer64", 	/* AVP_TYPE_INTEGER64 */
+	"Unsigned32", 	/* AVP_TYPE_UNSIGNED32 */
+	"Unsigned64", 	/* AVP_TYPE_UNSIGNED64 */
+	"Float32", 	/* AVP_TYPE_FLOAT32 */
+	"Float64"	/* AVP_TYPE_FLOAT64 */
+	};
+
+static struct avp_match *avp_match_new(char *name);
+
+static struct avp_match *source_target = NULL, *drop_target = NULL;
+static struct avp_target *dest_target = NULL;
+
+static void print_target(struct avp_target *target, char *prefix)
+{
+	char *output = NULL;
+	if (asprintf(&output, "%s -> /TOP/%s", prefix, target->name) == -1) {
+		fd_log_error("rt_rewrite: print_target: setup:  asprintf failed: %s", strerror(errno));
+		return;
+	}
+	for (target=target->child; target != NULL; target=target->child) {
+		char *new_output = NULL;
+		if (asprintf(&new_output, "%s/%s", output, target->name) == -1) {
+			fd_log_error("rt_rewrite: print_target: asprintf failed: %s", strerror(errno));
+			free(output);
+			return;
+		}
+		free(output);
+		output = new_output;
+		new_output = NULL;
+	}
+	fd_log_debug(output);
+	free(output);
+	return;
+}
+
+static void compare_avp_type(const char *source, const char *dest)
+{
+	struct dict_object *model_source, *model_dest;
+	struct dict_avp_data dictdata_source, dictdata_dest;
+
+	if (fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, source, &model_source, ENOENT) != 0) {
+		fd_log_error("Unable to find '%s' AVP in the loaded dictionaries", source);
+		return;
+	}
+	if (fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, dest, &model_dest, ENOENT) != 0) {
+		fd_log_error("Unable to find '%s' AVP in the loaded dictionaries", dest);
+		return;
+	}
+	fd_dict_getval(model_source, &dictdata_source);
+	fd_dict_getval(model_dest, &dictdata_dest);
+	if (dictdata_source.avp_basetype != dictdata_dest.avp_basetype) {
+		fd_log_notice("rt_rewrite: type mismatch: %s (type %s) mapped to %s (type %s) (continuing anyway)", source, type_base_name[dictdata_source.avp_basetype], dest, type_base_name[dictdata_dest.avp_basetype]);
+	}
+	return;
+}
+
+static void compare_avp_types(struct avp_match *start)
+{
+	struct avp_match *iter;
+	for (iter=start; iter != NULL; iter=iter->next) {
+		compare_avp_types(iter->children);
+		if (iter->target) {
+			struct avp_target *final;
+			final = iter->target;
+			while (final->child) {
+				final = final->child;
+			}
+			compare_avp_type(iter->name, final->name);
+		}
+	}
+	return;
+}
+
+static void dump_config(struct avp_match *start, char *prefix)
+{
+	char *new_prefix = NULL;
+	struct avp_match *iter;
+	for (iter=start; iter != NULL; iter=iter->next) {
+		if (asprintf(&new_prefix, "%s/%s", prefix, iter->name) == -1) {
+			fd_log_error("rt_rewrite: dump_config: asprintf failed: %s", strerror(errno));
+			return;
+		}
+		dump_config(iter->children, new_prefix);
+		if (iter->target) {
+			print_target(iter->target, new_prefix);
+		}
+		if (iter->drop) {
+			fd_log_debug("%s -> DROP", new_prefix);
+		}
+		free(new_prefix);
+		new_prefix = NULL;
+	}
+	return;
+}
+
+/* Parse the configuration file */
+int rt_rewrite_conf_handle(char * conffile)
+{
+	extern FILE * rt_rewrite_confin;
+	int ret;
+	char *top;
+
+	TRACE_ENTRY("%p", conffile);
+
+	TRACE_DEBUG (FULL, "Parsing configuration file: '%s'", conffile);
+
+	/* to match other entries */
+	if ((top=strdup("TOP")) == NULL) {
+		fd_log_error("strdup error: %s", strerror(errno));
+		return EINVAL;
+	}
+	if ((avp_match_start=avp_match_new(top)) == NULL) {
+		fd_log_error("malloc error: %s", strerror(errno));
+		free(top);
+		return EINVAL;
+	}
+	rt_rewrite_confin = fopen(conffile, "r");
+	if (rt_rewrite_confin == NULL) {
+		ret = errno;
+		fd_log_debug("Unable to open extension configuration file '%s' for reading: %s", conffile, strerror(ret));
+		TRACE_DEBUG (INFO, "rt_rewrite: error occurred, message logged -- configuration file.");
+		avp_match_free(avp_match_start);
+		avp_match_start = NULL;
+		return ret;
+	}
+
+	rt_rewrite_confrestart(rt_rewrite_confin);
+	ret = yyparse(conffile);
+
+	fclose(rt_rewrite_confin);
+
+	if (ret != 0) {
+		TRACE_DEBUG(INFO, "rt_rewrite: unable to parse the configuration file.");
+		avp_match_free(avp_match_start);
+		avp_match_start = NULL;
+		return EINVAL;
+	}
+
+	compare_avp_types(avp_match_start);
+	dump_config(avp_match_start, "");
+
+	return 0;
+}
+
+static int verify_avp(const char *name)
+{
+	struct dict_object *model;
+	struct dict_avp_data dictdata;
+
+	if (fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, name, &model, ENOENT) != 0) {
+		fd_log_error("Unable to find '%s' AVP in the loaded dictionaries", name);
+		return -1;
+	}
+	fd_dict_getval(model, &dictdata);
+	if (dictdata.avp_basetype == AVP_TYPE_GROUPED) {
+		return 1;
+	}
+	return 0;
+}
+
+static struct avp_match *avp_match_new(char *name) {
+	struct avp_match *ret;
+
+	if ((ret=malloc(sizeof(*ret))) == NULL) {
+		fd_log_error("malloc error");
+		return NULL;
+	}
+	ret->name = name;
+	ret->next = NULL;
+	ret->children = NULL;
+	ret->target = NULL;
+	ret->drop = 0;
+	return ret;
+}
+
+static void avp_target_free(struct avp_target *target) {
+	struct avp_target *iter;
+
+	for (iter=target; iter != NULL; ) {
+		struct avp_target *next;
+		free(iter->name);
+		next = iter->child;
+		free(iter);
+		iter = next;
+	}
+}
+
+void avp_match_free(struct avp_match *match) {
+	struct avp_match *iter;
+
+	for (iter=match; iter != NULL; ) {
+		struct avp_match *next;
+		free(iter->name);
+		next = iter->next;
+		avp_match_free(iter->children);
+		avp_target_free(iter->target);
+		free(iter);
+		iter = next;
+	}
+}
+
+static struct avp_target *target_new(char *name) {
+	struct avp_target *ret;
+
+	if ((ret=malloc(sizeof(*ret))) == NULL) {
+		fd_log_error("malloc error");
+		return NULL;
+	}
+	ret->name = name;
+	ret->child = NULL;
+	return ret;
+}
+
+static struct avp_match *add_avp_next_to(char *name, struct avp_match *target)
+{
+	struct avp_match *iter, *prev;
+
+	if (target == NULL) {
+		return avp_match_new(name);
+	}
+
+	for (prev=iter=target; iter != NULL; iter=iter->next) {
+		if (strcmp(iter->name, name) == 0) {
+			return iter;
+		}
+		prev = iter;
+	}
+	prev->next = avp_match_new(name);
+	return prev->next;
+}
+
+static int add(struct avp_match **target, char *name)
+{
+	struct avp_match *temp;
+	if (verify_avp(name) < 0) {
+		return -1;
+	}
+	temp = add_avp_next_to(name, (*target)->children);
+	if ((*target)->children == NULL) {
+		(*target)->children = temp;
+	}
+	*target = temp;
+	return 0;
+}
+
+/* build tree for source */
+static int source_add(char *name)
+{
+	if (source_target == NULL) {
+		source_target = avp_match_start;
+	}
+	return add(&source_target, name);
+}
+
+/* build tree for destination */
+static int dest_add(char *name)
+{
+	struct avp_target *temp;
+
+	if (verify_avp(name) < 0) {
+		return -1;
+	}
+	if ((temp=target_new(name)) == NULL) {
+		dest_target = NULL;
+		source_target = NULL;
+		return -1;
+	}
+	if (dest_target == NULL) {
+		dest_target = temp;
+		source_target->target = dest_target;
+		source_target = NULL;
+		return 0;
+	}
+	dest_target->child = temp;
+	dest_target = temp;
+	return 0;
+}
+
+static void dest_finish(void)
+{
+	dest_target = NULL;
+}
+
+/* same as source_add, but for drop */
+static int drop_add(char *name)
+{
+	if (drop_target == NULL) {
+		drop_target = avp_match_start;
+	}
+	return add(&drop_target, name);
+}
+
+/* mark as to-drop */
+static void drop_finish(void)
+{
+	drop_target->drop = 1;
+	drop_target = NULL;
+}
+
+/* The Lex parser prototype */
+int rt_rewrite_conflex(YYSTYPE *lvalp, YYLTYPE *llocp);
+
+/* Function to report the errors */
+void yyerror (YYLTYPE *ploc, char * conffile, char const *s)
+{
+	TRACE_DEBUG(INFO, "rt_rewrite: 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)
+		fd_log_debug("%s:%d.%d-%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_column, s);
+	else
+		fd_log_debug("%s:%d.%d : %s", conffile, ploc->first_line, ploc->first_column, s);
+}
+
+%}
+
+/* Values returned by lex for token */
+%union {
+	char 		*string;	/* The string is allocated by strdup in lex.*/
+}
+
+/* In case of error in the lexical analysis */
+%token 		LEX_ERROR
+
+/* A (de)quoted string (malloc'd in lex parser; it must be freed after use) */
+%token <string>	 QSTRING
+
+/* Tokens */
+%token 		MAP
+%token 		DROP
+
+
+/* -------------------------------------- */
+%%
+
+ /* The grammar definition */
+rules:			/* empty ok */
+			| rules map
+			| rules drop
+			;
+
+/* source -> destination mapping */
+map:			MAP '=' source_part '>' dest_part { dest_finish(); }
+			';'
+			;
+
+source_part: 		source_part ':' QSTRING { if (source_add($3) < 0) { YYERROR; } }
+			| QSTRING { if (source_add($1) < 0) { YYERROR; } }
+			;
+
+dest_part: 		dest_part ':' QSTRING { if (dest_add($3) < 0) { YYERROR; } }
+			| QSTRING { if (dest_add($1) < 0) { YYERROR; } }
+			;
+
+/* for dropping an AVP */
+drop:			DROP '=' drop_part { drop_finish(); }
+			';'
+			;
+
+drop_part: 		drop_part ':' QSTRING { if (drop_add($3) < 0) { YYERROR; } }
+			| QSTRING { if (drop_add($1) < 0) { YYERROR; } }
+			;
"Welcome to our mercurial repository"