siproxd/src/plugin_fix_fbox_anoncall.c
2021-02-10 21:21:50 +01:00

332 lines
12 KiB
C

/*
Copyright (C) 2016 Thomas Ries <tries@gmx.net>
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 <plugin.h> */
#define PLUGIN_NAME plugin_fix_fbox_anoncall
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <osipparser2/osip_parser.h>
#include "siproxd.h"
#include "plugins.h"
#include "log.h"
/* 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=NULL;
osip_uri_t *to_url=NULL;
int idx=0;
int full_match=0;
int param_match=0;
int param_match_idx=0;
int to_user_match=0;
int to_user_match_idx=0;
char *tmp=NULL;
if (ticket == NULL) {
ERROR("being called with ticket == NULL");
return STS_FAILURE;
}
type = ticket->direction;
DEBUGC(DBCLASS_PLUGIN, "PLUGIN_PROCESS entered: 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 SIP message");
return STS_SUCCESS;
}
if(contact->url == NULL) {
DEBUGC(DBCLASS_PLUGIN, "no Contact->url header found in SIP message");
return STS_SUCCESS;
}
if (contact->url->host == NULL) {
DEBUGC(DBCLASS_PLUGIN, "no Contact->url->host header found in SIP message");
return STS_SUCCESS;
}
if (ticket->sipmsg && ticket->sipmsg->to && ticket->sipmsg->to->url) {
to_url=ticket->sipmsg->to->url;
}
/*
- loop through URLMAP table
- skip if IP does not match
- set full_match if IP and user do match and break out of loop
- param_match++ if uniq= matches
- user_match++ if to: user matches
- end loop
- if full_match, then we do not need to fiddle around with the
contact header, it has not been anonymized.
- if param_match==1, we have found a possibly matching entry
do fixup Contact header
- if to_user_match==1, we have found a possibly matching entry
do fixup Contact header
- if no param_match and no to_user_match, complain
- return from plugin
Unfortunately, FritzBox does use the same 'uniq=' value for all lines, so if
having a Fritzbox using multiple lines we are screwed here. I may need to
find some additional markers that allow nailing the actual phone number
*/
/* check for sender IP is in configured range */
DEBUGC(DBCLASS_PLUGIN, "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, "checking for bogus Contact header");
/* loop through urlmap table */
for (idx=0; idx<URLMAP_SIZE; idx++){
if (urlmap[idx].active == 0) continue;
if (urlmap[idx].expires < ticket->timestamp) continue;
if (urlmap[idx].true_url == NULL) continue;
/* outgoing response - only look at true_url */
/* 1) check host, skip of no match */
if (contact->url->host && urlmap[idx].true_url->host) {
if (osip_strcasecmp(contact->url->host, urlmap[idx].true_url->host) != 0) {
/* no IP match, continue */
continue;
}
}
DEBUGC(DBCLASS_PLUGIN, "idx=%i, IP/Host match [%s]",
idx, contact->url->host);
osip_uri_to_str(contact->url, &tmp); \
DEBUGC(DBCLASS_PLUGIN, " contact->url=[%s]", (tmp)? tmp: "NULL");
if (tmp) osip_free(tmp);
tmp = NULL;
osip_uri_to_str(urlmap[idx].true_url, &tmp); \
DEBUGC(DBCLASS_PLUGIN, " urlmap[%i]->true_url=[%s]", idx, (tmp)? tmp: "NULL");
if (tmp) osip_free(tmp);
tmp = NULL;
/* 2) check username match */
if (contact->url->username && urlmap[idx].true_url->username) {
DEBUGC(DBCLASS_PLUGIN, "check username: "
"contact->url->username [%s] <-> true_url->username [%s]",
contact->url->username, urlmap[idx].true_url->username);
if (osip_strcasecmp(contact->url->username, urlmap[idx].true_url->username) == 0) {
/* MATCH, all OK - return */
full_match=1;
DEBUGC(DBCLASS_PLUGIN, "username matches");
break;
}
} else {
DEBUGC(DBCLASS_PLUGIN, "NULL username: "
"contact->username 0x%p <-> true_url->username 0x%p",
contact->url->username, urlmap[idx].true_url->username);
}
/* 3) check param field ("uniq=" param)*/
{
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) ) {
DEBUGC(DBCLASS_PLUGIN, "check param: "
"contact-> [%s]=[%s] <-> true_url->[%s]=[%s]",
p1->gname, p1->gvalue, p2->gname, p2->gvalue);
if ((osip_strcasecmp(p1->gname, p2->gname) == 0) &&
(osip_strcasecmp(p1->gvalue, p2->gvalue) == 0) ) {
/* MATCH */
param_match += 1;
param_match_idx=idx;
DEBUGC(DBCLASS_PLUGIN, "uniq param matches");
}
} else {
if (p1 && p2) {
DEBUGC(DBCLASS_PLUGIN, "NULL 'uniq' param fields: "
"contact-> 0x%p=0x%p <-> true_url->0x%p=0x%p",
p1->gname, p1->gvalue, p2->gname, p2->gvalue);
} else {
DEBUGC(DBCLASS_PLUGIN, "NULL 'uniq' param: "
"contact->param 0x%p <-> true_url->param 0x%p",
p1, p2);
}
}
} /* */
/* 4) search for match on To: user field */
if (to_url && to_url->username && urlmap[idx].true_url->username) {
DEBUGC(DBCLASS_PLUGIN, "check username: "
"to_url->username [%s] <-> true_url->username [%s]",
to_url->username, urlmap[idx].true_url->username);
if (osip_strcasecmp(to_url->username, urlmap[idx].true_url->username) == 0) {
/* MATCH, all OK - return */
to_user_match += 1;
to_user_match_idx=idx;
DEBUGC(DBCLASS_PLUGIN, "To: username [%s] matches", to_url->username);
break;
}
} else {
DEBUGC(DBCLASS_PLUGIN, "NULL username: "
"to_url(0x%p)->username(0x%p) <-> true_url->username(0x%p)",
(to_url)?to_url:NULL,
(to_url && to_url->username)?to_url->username:NULL,
urlmap[idx].true_url->username);
}
} /* for idx */
/* full match (host & user) */
if (full_match == 1) {
DEBUGC(DBCLASS_PLUGIN, "PLUGIN_PROCESS exit: got a user@host match - OK");
return STS_SUCCESS;
}
/* partial match (uniq=) found */
if (param_match == 1) {
/* replace the username part from [param_match_idx] -> Contact */
osip_free(contact->url->username);
osip_uri_set_username(contact->url,
osip_strdup(urlmap[param_match_idx].true_url->username));
DEBUGC(DBCLASS_PLUGIN, "sanitized Contact from [%s] (uniq= match)",
utils_inet_ntoa(ticket->from.sin_addr));
/* partial match To: user match found */
} else if (to_user_match == 1) {
/* replace the username part from [to_user_match_idx] -> Contact */
osip_free(contact->url->username);
osip_uri_set_username(contact->url,
osip_strdup(urlmap[to_user_match_idx].true_url->username));
DEBUGC(DBCLASS_PLUGIN, "sanitized Contact from [%s]"
" (To: user match)", utils_inet_ntoa(ticket->from.sin_addr));
/* no matches at all -> log something */
} else {
DEBUGC(DBCLASS_PLUGIN, "unable to sanitize bogus outgoing"
" response Contact header from [%s]"
" param_match=%i, to_user_match=%i",
utils_inet_ntoa(ticket->from.sin_addr),
param_match, to_user_match);
}
} else {
DEBUGC(DBCLASS_PLUGIN, "no aclist IP match, returning.");
}
} // if (type == ..
DEBUGC(DBCLASS_PLUGIN, "PLUGIN_PROCESS exit");
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;
}