diff --git a/ChangeLog b/ChangeLog index 922ec57..e46dc17 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 0.8.1 ===== + 17-Jun-2011: - plugin_prefix: correct handling of outgoing ACKs + (could lead to calls being aborted by one side) + - generic set of "redirect cache" that can be used by + plugins to keep track of Call-IDs when doing "302 Moved" 12-Jun-2011: - one-off in get_ip_by_ifname(), last interface in chain was not considered in matching. - Pthread stack size made adjustable (thread_stack_size) diff --git a/src/Makefile.am b/src/Makefile.am index 82be246..0f2c068 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,14 +35,16 @@ pkglib_LTLIBRARIES = plugin_demo.la \ plugin_defaulttarget.la \ plugin_fix_bogus_via.la \ plugin_stun.la \ - plugin_prefix.la + plugin_prefix.la \ + plugin_regex.la DLOPENPLUGINS = -dlopen plugin_demo.la \ -dlopen plugin_shortdial.la \ -dlopen plugin_logcall.la \ -dlopen plugin_defaulttarget.la \ -dlopen plugin_fix_bogus_via.la \ -dlopen plugin_stun.la \ - -dlopen plugin_prefix.la + -dlopen plugin_prefix.la \ + -dlopen plugin_regex.la # plugin_demo_la_SOURCES = plugin_demo.c plugin_demo_la_LDFLAGS = -module -avoid-version -shrext '.so' @@ -64,6 +66,9 @@ plugin_stun_la_LDFLAGS = -module -avoid-version -shrext '.so' # plugin_prefix_la_SOURCES = plugin_prefix.c plugin_prefix_la_LDFLAGS = -module -avoid-version -shrext '.so' +# +plugin_regex_la_SOURCES = plugin_regex.c +plugin_regex_la_LDFLAGS = -module -avoid-version -shrext '.so' # @@ -78,7 +83,7 @@ siproxd_SOURCES = siproxd.c proxy.c register.c sock.c utils.c \ sip_utils.c sip_layer.c log.c readconf.c rtpproxy.c \ rtpproxy_relay.c accessctl.c route_processing.c \ security.c auth.c fwapi.c resolve.c \ - dejitter.c plugins.c + dejitter.c plugins.c redirect_cache.c # addrcache.c diff --git a/src/plugin_regex.c b/src/plugin_regex.c new file mode 100644 index 0000000..20f17b7 --- /dev/null +++ b/src/plugin_regex.c @@ -0,0 +1,394 @@ +/* + Copyright (C) 2002-2011 Thomas Ries + + This file is part of Siproxd. + + Siproxd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Siproxd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Siproxd; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + * This plugin adds a regular expression rewrite support + * for SIP targets. + */ + + +/* must be defined before including */ +#define PLUGIN_NAME plugin_regex + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "siproxd.h" +#include "plugins.h" +#include "redirect_cache.h" +#include "log.h" + +static char const ident[]="$Id: plugin_regex.c 471 2011-05-28 10:03:49Z hb9xar $"; + +/* Plug-in identification */ +static char name[]="plugin_regex"; +static char desc[]="Use regular expressions to rewrite SIP targets"; + +/* global configuration storage - required for config file location */ +extern struct siproxd_config configuration; + +/* constants */ +#define REDIRECTED_TAG "redirected" +#define REDIRECTED_VAL "regex" + +/* plugin configuration storage */ +static struct plugin_config { + stringa_t regex_pattern; + stringa_t regex_replace; +} plugin_cfg; + +/* Instructions for config parser */ +static cfgopts_t plugin_cfg_opts[] = { + { "plugin_regex_pattern", TYP_STRINGA,&plugin_cfg.regex_pattern, {0, NULL} }, + { "plugin_regex_replace", TYP_STRINGA,&plugin_cfg.regex_replace, {0, NULL} }, + {0, 0, 0} +}; + +/* local storage needed for regular expression handling */ +static regex_t *re; +/* Redirect Cache: Queue Head is static */ +static redirected_cache_element_t redirected_cache; + + +/* local prototypes */ +static int plugin_regex_init(void); +static int plugin_regex_process(sip_ticket_t *ticket); +static int plugin_regex_redirect(sip_ticket_t *ticket); +regmatch_t * rmatch (char *buf, int size, regex_t *re); +int rreplace (char *buf, int size, regex_t *re, regmatch_t pmatch[], char *rp); + + +/* + * Plugin API functions code + */ +/* Initialization */ +int PLUGIN_INIT(plugin_def_t *plugin_def) { + plugin_def->api_version=SIPROXD_API_VERSION; + plugin_def->name=name; + plugin_def->desc=desc; + plugin_def->exe_mask=PLUGIN_DETERMINE_TARGET; + + /* read the config file */ + if (read_config(configuration.configfile, + configuration.config_search, + plugin_cfg_opts, name) == STS_FAILURE) { + ERROR("Plugin '%s': could not load config file", name); + return STS_FAILURE; + } + + return plugin_regex_init();; +} + +/* Processing */ +int PLUGIN_PROCESS(int stage, sip_ticket_t *ticket){ + int sts; + sts=plugin_regex_process(ticket); + return sts; +} + +/* De-Initialization */ +int PLUGIN_END(plugin_def_t *plugin_def){ + return STS_SUCCESS; +} + + +/* + * Workload code + */ +int plugin_regex_init(void) { + int i; + int sts, retsts; + int num_entries; + char errbuf[256]; + + retsts = STS_SUCCESS; + + /* check for equal entries of patterns and replacements */ + if (plugin_cfg.regex_pattern.used != plugin_cfg.regex_replace.used) { + ERROR("Plugin '%s': number of search patterns (%i) and number of " + "replacement patterns (%i) differ!", name, + plugin_cfg.regex_pattern.used, plugin_cfg.regex_replace.used); + return STS_FAILURE; + } + + /* allocate space for regexes and compile them */ + num_entries = plugin_cfg.regex_pattern.used; + re = malloc(num_entries*sizeof(re[0])); + for (i=0; i < num_entries; i++) { + sts = regcomp (&re[i], plugin_cfg.regex_pattern.string[i], REG_ICASE); + if (sts != 0) { + regerror(sts, &re[i], errbuf, sizeof(errbuf)); + ERROR("Regular expression [%s] failed to compile: %s", + plugin_cfg.regex_pattern.string[i], errbuf); + retsts = STS_FAILURE; + } + } + + return retsts; +} +/* returns STS_SIP_SENT if processing is to be terminated, + * otherwise STS_SUCCESS (go on with processing) */ +/* code (entry point) */ +static int plugin_regex_process(sip_ticket_t *ticket) { + int sts=STS_SUCCESS; + osip_uri_t *req_url; + osip_uri_t *to_url; + osip_generic_param_t *r=NULL; + + /* plugin loaded and not configured, return with success */ + if (plugin_cfg.regex_pattern.used==0) return STS_SUCCESS; + if (plugin_cfg.regex_replace.used==0) return STS_SUCCESS; + + DEBUGC(DBCLASS_PLUGIN,"plugin entered"); + req_url=osip_message_get_uri(ticket->sipmsg); + to_url=osip_to_get_url(ticket->sipmsg); + + /* only outgoing direction is handled */ + sip_find_direction(ticket, NULL); + if (ticket->direction != DIR_OUTGOING) + return STS_SUCCESS; + + /* only INVITE and ACK are handled */ + if (!MSG_IS_INVITE(ticket->sipmsg) && !MSG_IS_ACK(ticket->sipmsg)) + return STS_SUCCESS; + + /* expire old cache entries */ + expire_redirected_cache(&redirected_cache); + + /* REQ URI with username must exist, prefix string must exist */ + if (!req_url || !req_url->username) + return STS_SUCCESS; /* ignore */ + + /* Loop avoidance: + * If this INVITE has already been redirected by a prior 302 + * moved response a "REDIRECTED_TAG" parameter should be present in the + * URI. + * Hopefully all UAs (Clients) do honor RFC3261 and copy the + * *full* URI form the contact header into the new request header + * upon a 3xx response. + */ + if (req_url) { + osip_uri_param_get_byname(&(req_url->url_params), REDIRECTED_TAG, &r); + if (r && r->gvalue && strcmp(r->gvalue,REDIRECTED_VAL)== 0) { + DEBUGC(DBCLASS_PLUGIN,"Packet has already been processed (ReqURI)"); + return STS_SUCCESS; + } + } + if (to_url) { + osip_uri_param_get_byname(&(to_url->url_params), REDIRECTED_TAG, &r); + if (r && r->gvalue && strcmp(r->gvalue,REDIRECTED_VAL)== 0) { + DEBUGC(DBCLASS_PLUGIN,"Packet has already been processed (ToURI)"); + return STS_SUCCESS; + } + } + + /* + * The SIP message is to be processed + */ + + /* outgoing INVITE request */ + if (MSG_IS_INVITE(ticket->sipmsg)) { + DEBUGC(DBCLASS_PLUGIN,"processing INVITE"); + sts=plugin_regex_redirect(ticket); + } + /* outgoing ACK request: is result of a local 3xx answer (moved...) + * + * Only consume that particular ACK that belongs to a sent 302 answer, + * nothing else. Otherwise the ACK from the redirected call will get + * consumed as well and causes the call to be aborted (timeout). + * We keep a cache with Call-Ids of such "302 moved" dialogs. + * Only consume such ACKs that are part of such a dialog. + */ + else if (MSG_IS_ACK(ticket->sipmsg)) { + if (is_in_redirected_cache(&redirected_cache, ticket) == STS_TRUE) { + DEBUGC(DBCLASS_PLUGIN,"processing ACK (consume it)"); + sts=STS_SIP_SENT; /* eat up the ACK that was directed to myself */ + } + } + + return sts; +} + + +/* private plugin code */ +static int plugin_regex_redirect(sip_ticket_t *ticket) { + osip_uri_t *to_url=ticket->sipmsg->to->url; + char *to_user=to_url->username; + char *new_to_user=NULL; + int i, sts; + size_t username_len; + osip_contact_t *contact = NULL; + /* character workspaces for regex */ + #define WORKSPACE_SIZE 128 + static char in[WORKSPACE_SIZE+1], rp[WORKSPACE_SIZE+1]; + +/* perform search and replace of the regexes, first match hits */ +for (i = 0; i < plugin_cfg.regex_pattern.used; i++) { + regmatch_t *pmatch = NULL; + pmatch = rmatch(to_user, WORKSPACE_SIZE, &re[i]); + if (pmatch == NULL) continue; /* no match, next */ + /* have a match, do the replacement */ + strncpy (in, to_user, WORKSPACE_SIZE); + in[WORKSPACE_SIZE]='\0'; + strncpy (rp, plugin_cfg.regex_replace.string[i], WORKSPACE_SIZE); + rp[WORKSPACE_SIZE]='\0'; + + sts = rreplace(in, WORKSPACE_SIZE, &re[i], pmatch, rp); + if (sts != STS_SUCCESS) { + ERROR("regex replace failed: pattern:[%s] replace:[%s]", + plugin_cfg.regex_pattern.string[i], + plugin_cfg.regex_replace.string[i]); + return STS_FAILURE; + } + break; +} +if (i >= plugin_cfg.regex_pattern.used) { + // no match + return STS_SUCCESS; +} + +// in: contains the new string + + /* including \0 + leading character(s) */ + username_len=strlen(in) + 1; + + new_to_user = osip_malloc(username_len); /* *_len excluding \0 */ + if (!new_to_user) return STS_SUCCESS; + + /* only copy the part that really belongs to the username */ + snprintf(new_to_user, username_len, "%s", in ); + + /* strncpy may not terminate - do it manually to be sure */ + new_to_user[username_len-1]='\0'; + + + + + /* use a "302 Moved temporarily" response back to the client */ + /* new target is within the Contact Header */ + + /* remove all Contact headers in message */ + for (i=0; (contact != NULL) || (i == 0); i++) { + osip_message_get_contact(ticket->sipmsg, 0, &contact); + if (contact) { + osip_list_remove(&(ticket->sipmsg->contacts),0); + osip_contact_free(contact); + } + } /* for i */ + + /* insert one new Contact header containing the new target address */ + osip_contact_init(&contact); + osip_uri_clone(to_url, &contact->url); + osip_list_add(&(ticket->sipmsg->contacts),contact,0); + + /* USER part is always present, put new_to_user in contact URL */ + osip_free(contact->url->username); + contact->url->username=new_to_user; + + /* + * Add the 'REDIRECTED_TAG=REDIRECTED_VAL' parameter to URI. Required to figure out + * if this INVITE has already been processed (redirected) and + * does not need further attention by this plugin. + * THIS IS REQUIRED TO AVOID A LOOP + */ + osip_uri_param_add(&(contact->url->url_params), osip_strdup(REDIRECTED_TAG), + osip_strdup(REDIRECTED_VAL)); + + INFO("redirecting %s -> %s", to_user, new_to_user); + + /* sent redirect message back to local client */ + add_to_redirected_cache(&redirected_cache, ticket); + sip_gen_response(ticket, 302 /*Moved temporarily*/); + + return STS_SIP_SENT; +} + +/* + * This regex replacement code has been proudly borrowed from + * http://www.daniweb.com/software-development/c/code/216955# + * + * buf: input string + output result + * rp: replacement string, will be destroyed during processing! + * size: size of buf and rp + * re: regex to process + * + * rmatch() performs the initial regexec match, and if a match is found + * it returns a pointer to the regmatch array which contains the result + * of the match. + * Afterwards rreplace() is to be called, providing this regmatch array. + * + * This eliminates the need to copy the 'rp' string before knowing + * if a match is actually there. + */ +#define NMATCHES 10 +regmatch_t * rmatch (char *buf, int size, regex_t *re) { + static regmatch_t pm[NMATCHES]; /* regoff_t is int so size is int */ + + /* perform the match */ + if (regexec (re, buf, NMATCHES, pm, 0)) { + DEBUGC(DBCLASS_PLUGIN,"no match found."); + return NULL; + } + DEBUGC(DBCLASS_PLUGIN,"match found."); + return &pm[0]; +} + +int rreplace (char *buf, int size, regex_t *re, regmatch_t pmatch[], char *rp) { + char *pos; + int sub, so, n; + + /* match(es) found: */ + for (pos = rp; *pos; pos++) { + /* back references \1 ... \9: expand them in 'rp' */ + if (*pos == '\\' && *(pos + 1) > '0' && *(pos + 1) <= '9') { + so = pmatch[*(pos + 1) - 48].rm_so; /* pmatch[1..9] */ + n = pmatch[*(pos + 1) - 48].rm_eo - so; + if (so < 0 || strlen (rp) + n - 1 > size) return STS_FAILURE; + memmove (pos + n, pos + 2, strlen (pos) - 1); + memmove (pos, buf + so, n); + pos = pos + n - 2; + } + } + + sub = pmatch[1].rm_so; /* no repeated replace when sub >= 0 */ + /* and replace rp in the input buffer */ + for (pos = buf; !regexec (re, pos, 1, pmatch, 0); ) { + n = pmatch[0].rm_eo - pmatch[0].rm_so; + pos += pmatch[0].rm_so; + if (strlen (buf) - n + strlen (rp) > size) { + return STS_FAILURE; + } + memmove (pos + strlen (rp), pos + n, strlen (pos) - n + 1); + memmove (pos, rp, strlen (rp)); + pos += strlen (rp); + if (sub >= 0) break; + } + return STS_SUCCESS; +}