# HG changeset patch # User Thomas Klausner # Date 1554818489 -7200 # Node ID b0401251d8c06557e114881c614c05ddf12545f6 # Parent daf61e573feeb3485ef4e95555e4c9df768c20be 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. diff -r daf61e573fee -r b0401251d8c0 doc/rt_rewrite.conf.sample --- /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"; diff -r daf61e573fee -r b0401251d8c0 extensions/CMakeLists.txt --- 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) #### diff -r daf61e573fee -r b0401251d8c0 extensions/rt_rewrite/CMakeLists.txt --- /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) diff -r daf61e573fee -r b0401251d8c0 extensions/rt_rewrite/rt_rewrite.c --- /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 * + * * + * 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 +#include "rt_rewrite.h" + +#include +#include + +/* + * 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); diff -r daf61e573fee -r b0401251d8c0 extensions/rt_rewrite/rt_rewrite.h --- /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 * + * * + * 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 + +/* 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); diff -r daf61e573fee -r b0401251d8c0 extensions/rt_rewrite/rt_rewrite_conf.l --- /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 * + * * + * 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; + } + +%% diff -r daf61e573fee -r b0401251d8c0 extensions/rt_rewrite/rt_rewrite_conf.y --- /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 * + * * + * 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 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; } } + ;