diff --git a/ChangeLog b/ChangeLog index 0b5039f..3817835 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 0.8.2 ===== + 30-Jan-2016: - added plugin_fix_fbox_anoncall: a plugin to work around + some quirks from Fritzboxes when receiving anonymous calls + (calls with supressed CLID). Might also work for other UAs. 11-Oct-2015: - performance improvement in DNS lookup/caching 20-Sep-2015: - added plugin_siptrunk: a plugin to support SIP trunks with multiple numbers within the same SIP account. diff --git a/doc/siproxd.conf.example b/doc/siproxd.conf.example index b5fd7e6..c8f40fa 100644 --- a/doc/siproxd.conf.example +++ b/doc/siproxd.conf.example @@ -338,7 +338,7 @@ load_plugin=plugin_logcall.la #load_plugin=plugin_stripheader.la #load_plugin=plugin_codecfilter.la #load_plugin=plugin_siptrunk.la - +#load_plugin=plugin_fix_fbox_anoncall.la ###################################################################### # Plugin_demo @@ -490,3 +490,15 @@ plugin_codecfilter_blacklist = GSM #plugin_siptrunk_name = Example Trunk, 555-123100 ... 555-123112 #plugin_siptrunk_account = sip:user@sip.example.org #plugin_siptrunk_numbers_regex = ^555123(10[0-9]|11[012])$ + +###################################################################### +# Plugin_fix_fbox_anoncall +# +# This plugin attempts to work-around some SIP issues with +# Fritzbox devices and anonymous calls. Fritzbox devices do change their +# Contact header when answering an anonymous call (supressed CLID) - this +# in turn confuses siproxd. This plugin attempts to work around this by +# sanitizing the Contact Header before processing. +# DTAG_networks: Local Networks where such Fritzboxes are located. Only SIP +# messages originating in those ranges will be sanitized. +plugin_fix_fbox_anoncall_networks = 217.0.23.100/32,91.121.209.194/32,81.221.125.10/32 diff --git a/src/Makefile.am b/src/Makefile.am index 05e7472..fe505de 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,7 +40,9 @@ pkglib_LTLIBRARIES = plugin_demo.la \ plugin_regex.la \ plugin_codecfilter.la \ plugin_stripheader.la \ - plugin_siptrunk.la + plugin_siptrunk.la \ + plugin_fix_fbox_anoncall.la + DLOPENPLUGINS = -dlopen plugin_demo.la \ -dlopen plugin_shortdial.la \ -dlopen plugin_logcall.la \ @@ -52,7 +54,8 @@ DLOPENPLUGINS = -dlopen plugin_demo.la \ -dlopen plugin_regex.la \ -dlopen plugin_codecfilter.la \ -dlopen plugin_stripheader.la \ - -dlopen plugin_siptrunk.la + -dlopen plugin_siptrunk.la \ + -dlopen plugin_fix_fbox_anoncall.la # plugin_demo_la_SOURCES = plugin_demo.c plugin_demo_la_LDFLAGS = -module -avoid-version -shrext '.so' @@ -89,6 +92,9 @@ plugin_stripheader_la_LDFLAGS = -module -avoid-version -shrext '.so' # plugin_siptrunk_la_SOURCES = plugin_siptrunk.c plugin_siptrunk_la_LDFLAGS = -module -avoid-version -shrext '.so' +# +plugin_fix_fbox_anoncall_la_SOURCES = plugin_fix_fbox_anoncall.c +plugin_fix_fbox_anoncall_la_LDFLAGS = -module -avoid-version -shrext '.so' # diff --git a/src/plugin_fix_fbox_anoncall.c b/src/plugin_fix_fbox_anoncall.c new file mode 100644 index 0000000..34c8bcc --- /dev/null +++ b/src/plugin_fix_fbox_anoncall.c @@ -0,0 +1,241 @@ +/* + Copyright (C) 2016 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 warrantry 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 +*/ + +/* must be defined before including */ +#define PLUGIN_NAME plugin_fix_fbox_anoncall + +#include "config.h" + +#include + +#include +#include +#include + +#include + +#include "siproxd.h" +#include "plugins.h" +#include "log.h" + +static char const ident[]="$Id$"; + +/* Plug-in identification */ +static char name[]="plugin_fix_fbox_anoncall"; +static char desc[]="Fixes issues with incoming anonymous calls on Fritzbox UAs"; + +/* global configuration storage - required for config file location */ +extern struct siproxd_config configuration; + +/* global URL mapping table */ +extern struct urlmap_s urlmap[]; + +/* plugin configuration storage */ +static struct plugin_config { + char *networks; // networks where we shall fix Fritzbox behaviour +} plugin_cfg; + +/* Instructions for config parser */ +static cfgopts_t plugin_cfg_opts[] = { + { "plugin_fix_fbox_anoncall_networks", TYP_STRING, &plugin_cfg.networks, {0, NULL} }, + {0, 0, 0} +}; + +/* Prototypes */ +//static int sip_fix_topvia(sip_ticket_t *ticket); + + +/* + * Initialization. + * Called once suring siproxd startup. + */ +int PLUGIN_INIT(plugin_def_t *plugin_def) { + /* API version number of siproxd that this plugin is built against. + * This constant will change whenever changes to the API are made + * that require adaptions in the plugin. */ + plugin_def->api_version=SIPROXD_API_VERSION; + + /* Name and descriptive text of the plugin */ + plugin_def->name=name; + plugin_def->desc=desc; + + /* Execution mask - during what stages of SIP processing shall + * the plugin be called. */ + plugin_def->exe_mask=PLUGIN_PRE_PROXY; + + /* 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; + } + + INFO("plugin_fix_fbox_anoncall is initialized"); + return STS_SUCCESS; +} + +/* + * Processing. + * + */ +int PLUGIN_PROCESS(int stage, sip_ticket_t *ticket){ + /* stage contains the PLUGIN_* value - the stage of SIP processing. */ + int type; + osip_contact_t *contact; + int idx=0; + int param_match_idx=0; + int user_match=0; + int param_match=0; + + type = ticket->direction; + +DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: type=%i", type); + + /* Outgoing SIP response? - may also need to process outgoing SIP requests - */ + if ((type == RESTYP_OUTGOING) || (type == REQTYP_OUTGOING)) { + /* a Contact header needs to be present in response */ + osip_message_get_contact(ticket->sipmsg, 0, &contact); + if(contact == NULL) { + DEBUGC(DBCLASS_PLUGIN, "no Contact header found in outgoing SIP message"); + return STS_SUCCESS; + } + if(contact->url == NULL) { + DEBUGC(DBCLASS_PLUGIN, "no Contact->Url header found in outgoing SIP message"); + return STS_SUCCESS; + } + + + +/* + loop through URLMAP table + compare IP, param, if match + set partial match + compare username, if match + all OK with this Contact header, return from plugin + if no math + probably broken username part in header (as rest matches) + remember urlmap index + end loop + if partial match == 0 + unable to figure out how to fix contact header. + dump some info + if partial match == 1 + replace username part from urlmap[saved_index].true_url + return from plugin + +*/ + + /* check for sender IP is in configured range */ + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: processing from host [%s]", + utils_inet_ntoa(ticket->from.sin_addr)); + if ((plugin_cfg.networks != NULL) && + (strcmp(plugin_cfg.networks, "") !=0) && + (process_aclist(plugin_cfg.networks, ticket->from) == STS_SUCCESS)) { + /* Sender IP is in list, fix check and fix Contact header */ + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: checking for bogus Contact header"); + + /* loop through urlmap table */ + for (idx=0; idxurl->host && urlmap[idx].true_url->host) { + if (osip_strcasecmp(contact->url->host, urlmap[idx].true_url->host) != 0) { + /* no IP match, continue */ + continue; + } + } + + /* 2) check username match */ + if (contact->url->username && urlmap[idx].true_url->username) { + if (osip_strcasecmp(contact->url->username, urlmap[idx].true_url->username) == 0) { + /* MATCH, all OK - return */ + user_match=1; + break; + } + } + + /* 3) check param field ("uniq=" param)*/ + if (contact->url && urlmap[idx].true_url) { + int sts1, sts2; + osip_uri_param_t *p1=NULL, *p2=NULL; + + sts1=osip_uri_param_get_byname(&(contact->url->url_params), "uniq", &p1); + sts2=osip_uri_param_get_byname(&(urlmap[idx].true_url->url_params), "uniq", &p2); + if ( ((sts1 == OSIP_SUCCESS) && (sts2 == OSIP_SUCCESS)) && + (p1 && p2) && + (p1->gname && p2->gname && p1->gvalue && p2->gvalue) && + (osip_strcasecmp(p1->gname, p2->gname) == 0) && + (osip_strcasecmp(p1->gvalue, p2->gvalue) == 0) ) { + /* MATCH */ + param_match=1; + param_match_idx=idx; + } + } + + } // for + + /* full match (host & user) */ + if (user_match == 1) { + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: got a user@host match - OK"); + return STS_SUCCESS; + } + + /* no partial match (no host, or no user / no param match) */ + if (param_match == 0) { + WARN("Bogus outgoing response Contact header from [%s], unable to sanitize!", + utils_inet_ntoa(ticket->from.sin_addr)); + return STS_FAILURE; + } + + /* param_match - replace the username part from [param_match_idx] -> Contact */ + /* replace contact URI (username part) */ + osip_free(contact->url->username); + osip_uri_set_username(contact->url, + osip_strdup(urlmap[param_match_idx].true_url->username)); + + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: sanitized Contact from [%s]", + utils_inet_ntoa(ticket->from.sin_addr)); + + + } else { + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: no IP match, returning."); + } + DEBUGC(DBCLASS_PLUGIN, "plugin_fix_fbox_anoncall: done"); + } // if (type == RESTYP_OUTGOING) + return STS_SUCCESS; +} + +/* + * De-Initialization. + * Called during shutdown of siproxd. Gives the plugin the chance + * to clean up its mess (e.g. dynamic memory allocation, database + * connections, whatever the plugin messes around with) + */ +int PLUGIN_END(plugin_def_t *plugin_def){ + INFO("plugin_fix_fbox_anoncall ends here"); + return STS_SUCCESS; +} + diff --git a/src/sip_utils.c b/src/sip_utils.c index 0ee3aed..5993e4e 100644 --- a/src/sip_utils.c +++ b/src/sip_utils.c @@ -670,6 +670,16 @@ int sip_rewrite_contact (sip_ticket_t *ticket, int direction) { (compare_url(contact->url, urlmap[i].masq_url)==STS_SUCCESS)) break; } + /* NOTE: + It has been observed with some UAs (e.g. Fritzbox) that when receiving + an anonymous call (call with supressed CLID) these UAs do send a *modified* + Contact header (compared to the Contact used in REGISTER). + This confuses siproxd as now masquerading cannot match the Contact Header + with the URLMAP table. + -> see plugin_fix_fbox_anoncall that tries to work around things by + detecting this and restoring the "sane" Contact header. + /* + /* found a mapping entry */ if (i