You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/ims_dialog/dlg_profile.c

874 lines
21 KiB

/*
* $Id$
*
* Copyright (C) 2008 Voice System SRL
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio 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
*
* Kamailio 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* History:
* --------
* 2008-04-20 initial version (bogdan)
*
*/
/*!
* \file
* \brief Profile related functions for the dialog module
* \ingroup dialog
* Module: \ref dialog
*/
#include "../../mem/shm_mem.h"
#include "../../dprint.h"
#include "../../ut.h"
#include "../../route.h"
#include "../../modules/tm/tm_load.h"
#include "../../trim.h"
#include "dlg_hash.h"
#include "dlg_handlers.h"
#include "dlg_profile.h"
#include "dlg_var.h"
/*! size of dialog profile hash */
#define PROFILE_HASH_SIZE 16
/*! tm bindings */
extern struct tm_binds d_tmb;
/*! global dialog message id */
static unsigned int current_dlg_msg_id = 0 ;
static unsigned int current_dlg_msg_pid = 0 ;
/*! pending dialog links */
static struct dlg_profile_link *current_pending_linkers = NULL;
/*! global dialog profile list */
static struct dlg_profile_table *profiles = NULL;
static struct dlg_profile_table* new_dlg_profile( str *name,
unsigned int size, unsigned int has_value);
static sruid_t _dlg_profile_sruid;
/*!
* \brief Add profile definitions to the global list
* \see new_dlg_profile
* \param profiles profile name
* \param has_value set to 0 for a profile without value, otherwise it has a value
* \return 0 on success, -1 on failure
*/
int add_profile_definitions( char* profiles, unsigned int has_value)
{
char *p;
char *d;
str name;
unsigned int i;
if (profiles==NULL || strlen(profiles)==0 )
return 0;
p = profiles;
do {
/* locate name of profile */
name.s = p;
d = strchr( p, ';');
if (d) {
name.len = d-p;
d++;
} else {
name.len = strlen(p);
}
/* we have the name -> trim it for spaces */
trim_spaces_lr( name );
/* check len name */
if (name.len==0)
/* ignore */
continue;
/* check the name format */
for(i=0;i<name.len;i++) {
if ( !isalnum(name.s[i]) ) {
LM_ERR("bad profile name <%.*s>, char %c - use only "
"alphanumerical characters\n", name.len,name.s,name.s[i]);
return -1;
}
}
/* name ok -> create the profile */
LM_DBG("creating profile <%.*s>\n",name.len,name.s);
if (new_dlg_profile( &name, PROFILE_HASH_SIZE, has_value)==NULL) {
LM_ERR("failed to create new profile <%.*s>\n",name.len,name.s);
return -1;
}
}while( (p=d)!=NULL );
return 0;
}
/*!
* \brief Search a dialog profile in the global list
* \note Linear search, this won't have the best performance for huge profile lists
* \param name searched dialog profile
* \return pointer to the profile on success, NULL otherwise
*/
struct dlg_profile_table* search_dlg_profile(str *name)
{
struct dlg_profile_table *profile;
for( profile=profiles ; profile ; profile=profile->next ) {
if (name->len==profile->name.len &&
memcmp(name->s,profile->name.s,name->len)==0 )
return profile;
}
return NULL;
}
/*!
* \brief Creates a new dialog profile
* \see add_profile_definitions
* \param name profile name
* \param size profile size
* \param has_value set to 0 for a profile without value, otherwise it has a value
* \return pointer to the created dialog on success, NULL otherwise
*/
static struct dlg_profile_table* new_dlg_profile( str *name, unsigned int size,
unsigned int has_value)
{
struct dlg_profile_table *profile;
struct dlg_profile_table *ptmp;
unsigned int len;
unsigned int i;
if ( name->s==NULL || name->len==0 || size==0 ) {
LM_ERR("invalid parameters\n");
return NULL;
}
for( len=0,i=0 ; i<8*sizeof(size) ; i++ ) {
if ( size & (1<<i) ) len++;
}
if (len!=1) {
LM_ERR(" size %u is not power of 2!\n", size);
return NULL;
}
profile = search_dlg_profile(name);
if (profile!=NULL) {
LM_ERR("duplicate dialog profile registered <%.*s>\n",
name->len, name->s);
return NULL;
}
len = sizeof(struct dlg_profile_table) +
size*sizeof(struct dlg_profile_entry) +
name->len + 1;
profile = (struct dlg_profile_table *)shm_malloc(len);
if (profile==NULL) {
LM_ERR("no more shm mem\n");
return NULL;
}
memset( profile , 0 , len);
profile->size = size;
profile->has_value = (has_value==0)?0:1;
/* init lock */
if (lock_init( &profile->lock )==NULL) {
LM_ERR("failed to init lock\n");
shm_free(profile);
return NULL;
}
/* set inner pointers */
profile->entries = (struct dlg_profile_entry*)(profile + 1);
profile->name.s = ((char*)profile->entries) +
size*sizeof(struct dlg_profile_entry);
/* copy the name of the profile */
memcpy( profile->name.s, name->s, name->len );
profile->name.len = name->len;
profile->name.s[profile->name.len] = 0;
/* link profile */
for( ptmp=profiles ; ptmp && ptmp->next; ptmp=ptmp->next );
if (ptmp==NULL)
profiles = profile;
else
ptmp->next = profile;
return profile;
}
/*!
* \brief Destroy a dialog profile list
* \param profile dialog profile
*/
static void destroy_dlg_profile(struct dlg_profile_table *profile)
{
if (profile==NULL)
return;
lock_destroy( &profile->lock );
shm_free( profile );
return;
}
/*!
* \brief Destroy the global dialog profile list
*/
void destroy_dlg_profiles(void)
{
struct dlg_profile_table *profile;
while(profiles) {
profile = profiles;
profiles = profiles->next;
destroy_dlg_profile( profile );
}
return;
}
/*!
* \brief Destroy dialog linkers
* \param linker dialog linker
*/
void destroy_linkers(struct dlg_profile_link *linker)
{
struct dlg_profile_entry *p_entry;
struct dlg_profile_link *l;
struct dlg_profile_hash *lh;
while(linker) {
l = linker;
linker = linker->next;
/* unlink from profile table */
if (l->hash_linker.next) {
p_entry = &l->profile->entries[l->hash_linker.hash];
lock_get( &l->profile->lock );
lh = &l->hash_linker;
/* last element on the list? */
if (lh==lh->next) {
p_entry->first = NULL;
} else {
if (p_entry->first==lh)
p_entry->first = lh->next;
lh->next->prev = lh->prev;
lh->prev->next = lh->next;
}
lh->next = lh->prev = NULL;
p_entry->content --;
lock_release( &l->profile->lock );
}
/* free memory */
shm_free(l);
}
}
/*!
* \brief Cleanup a profile
* \param msg SIP message
* \param flags unused
* \param unused
* \return 1
*/
int profile_cleanup( struct sip_msg *msg, unsigned int flags, void *param )
{
dlg_cell_t *dlg;
current_dlg_msg_id = 0;
current_dlg_msg_pid = 0;
dlg = dlg_get_ctx_dialog();
if (dlg != NULL) {
if (dlg->dflags & DLG_FLAG_TM) {
unref_dlg(dlg, 1);
} else {
/* dialog didn't make it to tm */
unref_dlg(dlg, 2);
}
}
if (current_pending_linkers) {
destroy_linkers(current_pending_linkers);
current_pending_linkers = NULL;
}
/* need to return non-zero - 0 will break the exec of the request */
return 1;
}
struct dlg_cell* get_dialog_from_tm(struct cell *t)
{
if (t==NULL || t==T_UNDEFINED)
return NULL;
struct tm_callback* x = (struct tm_callback*)(t->tmcb_hl.first);
while(x){
membar_depends();
if (x->types==TMCB_MAX && x->callback==dlg_tmcb_dummy){
return (struct dlg_cell*)(x->param);
}
x=x->next;
}
return NULL;
}
/*!
* \brief Calculate the hash profile from a dialog
* \see core_hash
* \param value hash source
* \param dlg dialog cell
* \param profile dialog profile table (for hash size)
* \return value hash if the value has a value, hash over dialog otherwise
*/
inline static unsigned int calc_hash_profile(str *value, struct dlg_cell *dlg,
struct dlg_profile_table *profile)
{
if (profile->has_value) {
/* do hash over the value */
return core_hash( value, NULL, profile->size);
} else {
/* do hash over dialog pointer */
return ((unsigned long)dlg) % profile->size ;
}
}
/*!
* \brief Link a dialog profile
* \param linker dialog linker
* \param dlg dialog cell
*/
static void link_dlg_profile(struct dlg_profile_link *linker, struct dlg_cell *dlg)
{
unsigned int hash;
struct dlg_profile_entry *p_entry;
struct dlg_entry *d_entry;
/* add the linker to the dialog */
/* FIXME zero h_id is not 100% for testing if the dialog is inserted
* into the hash table -> we need circular lists -bogdan */
if (dlg->h_id) {
d_entry = &d_table->entries[dlg->h_entry];
dlg_lock( d_table, d_entry);
linker->next = dlg->profile_links;
dlg->profile_links =linker;
linker->hash_linker.dlg = dlg;
dlg_unlock( d_table, d_entry);
} else {
linker->next = dlg->profile_links;
dlg->profile_links =linker;
linker->hash_linker.dlg = dlg;
}
/* calculate the hash position */
hash = calc_hash_profile(&linker->hash_linker.value, dlg, linker->profile);
linker->hash_linker.hash = hash;
/* insert into profile hash table */
p_entry = &linker->profile->entries[hash];
lock_get( &linker->profile->lock );
if (p_entry->first) {
linker->hash_linker.prev = p_entry->first->prev;
linker->hash_linker.next = p_entry->first;
p_entry->first->prev->next = &linker->hash_linker;
p_entry->first->prev = &linker->hash_linker;
} else {
p_entry->first = linker->hash_linker.next
= linker->hash_linker.prev = &linker->hash_linker;
}
p_entry->content ++;
lock_release( &linker->profile->lock );
}
/*!
* \brief Set the global variables to the current dialog
* \param msg SIP message
* \param dlg dialog cell
*/
void set_current_dialog(sip_msg_t *msg, dlg_cell_t *dlg)
{
struct dlg_profile_link *linker;
struct dlg_profile_link *tlinker;
LM_DBG("setting current dialog [%u:%u]\n", dlg->h_entry, dlg->h_id);
/* if linkers are not from current request, just discard them */
if (msg->id!=current_dlg_msg_id || msg->pid!=current_dlg_msg_pid) {
current_dlg_msg_id = msg->id;
current_dlg_msg_pid = msg->pid;
destroy_linkers(current_pending_linkers);
} else {
/* add the linker, one by one, to the dialog */
linker = current_pending_linkers;
while (linker) {
tlinker = linker;
linker = linker->next;
/* process tlinker */
tlinker->next = NULL;
link_dlg_profile( tlinker, dlg);
}
}
current_pending_linkers = NULL;
}
/*!
* \brief Set a dialog profile
* \param msg SIP message
* \param value value
* \param profile dialog profile table
* \return 0 on success, -1 on failure
*/
int set_dlg_profile(struct sip_msg *msg, str *value, struct dlg_profile_table *profile)
{
dlg_cell_t *dlg = NULL;
dlg_profile_link_t *linker;
/* get current dialog */
dlg = dlg_get_msg_dialog(msg);
if (dlg==NULL && !is_route_type(REQUEST_ROUTE)) {
LM_CRIT("BUG - dialog not found in a non REQUEST route (%d)\n",
REQUEST_ROUTE);
return -1;
}
/* build new linker */
linker = (struct dlg_profile_link*)shm_malloc(
sizeof(struct dlg_profile_link) + (profile->has_value?value->len:0) );
if (linker==NULL) {
LM_ERR("no more shm memory\n");
goto error;
}
memset(linker, 0, sizeof(struct dlg_profile_link));
/* set backpointers to profile and linker (itself) */
linker->profile = profile;
linker->hash_linker.linker = linker;
/* set the value */
if (profile->has_value) {
linker->hash_linker.value.s = (char*)(linker+1);
memcpy( linker->hash_linker.value.s, value->s, value->len);
linker->hash_linker.value.len = value->len;
}
sruid_next_safe(&_dlg_profile_sruid);
strcpy(linker->hash_linker.puid, _dlg_profile_sruid.uid.s);
linker->hash_linker.puid_len = _dlg_profile_sruid.uid.len;
if (dlg!=NULL) {
/* add linker directly to the dialog and profile */
link_dlg_profile( linker, dlg);
} else {
/* if existing linkers are not from current request, just discard them */
if (msg->id!=current_dlg_msg_id || msg->pid!=current_dlg_msg_pid) {
current_dlg_msg_id = msg->id;
current_dlg_msg_pid = msg->pid;
destroy_linkers(current_pending_linkers);
current_pending_linkers = NULL;
}
/* no dialog yet -> set linker as pending */
if (msg->id!=current_dlg_msg_id || msg->pid!=current_dlg_msg_pid) {
current_dlg_msg_id = msg->id;
current_dlg_msg_pid = msg->pid;
destroy_linkers(current_pending_linkers);
}
linker->next = current_pending_linkers;
current_pending_linkers = linker;
}
dlg_release(dlg);
return 0;
error:
dlg_release(dlg);
return -1;
}
/*!
* \brief Unset a dialog profile
* \param msg SIP message
* \param value value
* \param profile dialog profile table
* \return 1 on success, -1 on failure
*/
int unset_dlg_profile(struct sip_msg *msg, str *value,
struct dlg_profile_table *profile)
{
dlg_cell_t *dlg;
dlg_profile_link_t *linker;
dlg_profile_link_t *linker_prev;
dlg_entry_t *d_entry;
if (is_route_type(REQUEST_ROUTE)) {
LM_ERR("dialog delete profile cannot be used in request route\n");
return -1;
}
/* get current dialog */
dlg = dlg_get_msg_dialog(msg);
if (dlg==NULL) {
LM_WARN("dialog is NULL for delete profile\n");
return -1;
}
/* check the dialog linkers */
d_entry = &d_table->entries[dlg->h_entry];
dlg_lock( d_table, d_entry);
linker = dlg->profile_links;
linker_prev = NULL;
for( ; linker ; linker_prev=linker,linker=linker->next) {
if (linker->profile==profile) {
if (profile->has_value==0) {
goto found;
} else if (value && value->len==linker->hash_linker.value.len &&
memcmp(value->s,linker->hash_linker.value.s,value->len)==0){
goto found;
}
/* allow further search - maybe the dialog is inserted twice in
* the same profile, but with different values -bogdan
*/
}
}
dlg_unlock( d_table, d_entry);
dlg_release(dlg);
return -1;
found:
/* table still locked */
/* remove the linker element from dialog */
if (linker_prev==NULL) {
dlg->profile_links = linker->next;
} else {
linker_prev->next = linker->next;
}
linker->next = NULL;
dlg_unlock( d_table, d_entry);
/* remove linker from profile table and free it */
destroy_linkers(linker);
dlg_release(dlg);
return 1;
}
/*!
* \brief Check if a dialog belongs to a profile
* \param msg SIP message
* \param profile dialog profile table
* \param value value
* \return 1 on success, -1 on failure
*/
int is_dlg_in_profile(struct sip_msg *msg, struct dlg_profile_table *profile,
str *value) {
struct dlg_cell *dlg;
struct dlg_profile_link *linker;
struct dlg_entry *d_entry;
int ret;
LM_DBG("Getting current dialog");
/* get current dialog */
dlg = dlg_get_msg_dialog(msg);
if (dlg == NULL) {
LM_DBG("Error: Current dlg is null");
return -1;
}
LM_DBG("Current dlg found");
ret = -1;
/* check the dialog linkers */
d_entry = &d_table->entries[dlg->h_entry];
dlg_lock( d_table, d_entry);
for (linker = dlg->profile_links; linker; linker = linker->next) {
LM_DBG("Running through linkers");
if (linker->profile == profile) {
LM_DBG("Profile matches");
if (profile->has_value == 0) {
LM_DBG("Profile has value is zero returning true");
dlg_unlock( d_table, d_entry);
ret = 1;
goto done;
} else if (value && value->len == linker->hash_linker.value.len
&& memcmp(value->s, linker->hash_linker.value.s, value->len)
== 0) {
LM_DBG("Profile has value equal to passed value returning true");
dlg_unlock( d_table, d_entry);
ret = 1;
goto done;
}
/* allow further search - maybe the dialog is inserted twice in
* the same profile, but with different values -bogdan
*/
}
}
dlg_unlock( d_table, d_entry);
done:
dlg_release(dlg);
return ret;
}
/*!
* \brief Get the size of a profile
* \param profile evaluated profile
* \param value value
* \return the profile size
*/
unsigned int get_profile_size(struct dlg_profile_table *profile, str *value)
{
unsigned int n,i;
struct dlg_profile_hash *ph;
if (profile->has_value==0 || value==NULL) {
/* iterate through the hash and count all records */
lock_get( &profile->lock );
for( i=0,n=0 ; i<profile->size ; i++ )
n += profile->entries[i].content;
lock_release( &profile->lock );
return n;
} else {
/* iterate through the hash entry and count only matching */
/* calculate the hash position */
i = calc_hash_profile( value, NULL, profile);
n = 0;
lock_get( &profile->lock );
ph = profile->entries[i].first;
if(ph) {
do {
/* compare */
if ( value->len==ph->value.len &&
memcmp(value->s,ph->value.s,value->len)==0 ) {
/* found */
n++;
}
/* next */
ph=ph->next;
}while( ph!=profile->entries[i].first );
}
lock_release( &profile->lock );
return n;
}
}
/*
* Determine if message is in a dialog currently being tracked
*/
int is_known_dlg(struct sip_msg *msg) {
dlg_cell_t *dlg;
dlg = dlg_get_msg_dialog(msg);
if(dlg == NULL)
return -1;
dlg_release(dlg);
return 1;
}
/****************************** MI commands *********************************/
/*!
* \brief Output a profile via MI interface
* \param cmd_tree MI command tree
* \param param unused
* \return MI root output on success, NULL on failure
*/
struct mi_root * mi_get_profile(struct mi_root *cmd_tree, void *param)
{
struct mi_node* node;
struct mi_root* rpl_tree= NULL;
struct mi_node* rpl = NULL;
struct mi_attr* attr;
struct dlg_profile_table *profile;
str *value;
str *profile_name;
unsigned int size;
int len;
char *p;
node = cmd_tree->node.kids;
if (node==NULL || !node->value.s || !node->value.len)
return init_mi_tree( 400, MI_SSTR(MI_MISSING_PARM));
profile_name = &node->value;
if (node->next) {
node = node->next;
if (!node->value.s || !node->value.len)
return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
if (node->next)
return init_mi_tree( 400, MI_SSTR(MI_MISSING_PARM));
value = &node->value;
} else {
value = NULL;
}
/* search for the profile */
profile = search_dlg_profile( profile_name );
if (profile==NULL)
return init_mi_tree( 404, MI_SSTR("Profile not found"));
size = get_profile_size( profile , value );
rpl_tree = init_mi_tree( 200, MI_SSTR(MI_OK));
if (rpl_tree==0)
return 0;
rpl = &rpl_tree->node;
node = add_mi_node_child(rpl, MI_DUP_VALUE, "profile", 7, NULL, 0);
if (node==0) {
free_mi_tree(rpl_tree);
return NULL;
}
attr = add_mi_attr(node, MI_DUP_VALUE, "name", 4,
profile->name.s, profile->name.len);
if(attr == NULL) {
goto error;
}
if (value) {
attr = add_mi_attr(node, MI_DUP_VALUE, "value", 5, value->s, value->len);
} else {
attr = add_mi_attr(node, MI_DUP_VALUE, "value", 5, NULL, 0);
}
if(attr == NULL) {
goto error;
}
p= int2str((unsigned long)size, &len);
attr = add_mi_attr(node, MI_DUP_VALUE, "count", 5, p, len);
if(attr == NULL) {
goto error;
}
return rpl_tree;
error:
free_mi_tree(rpl_tree);
return NULL;
}
/*!
* \brief List the profiles via MI interface
* \param cmd_tree MI command tree
* \param param unused
* \return MI root output on success, NULL on failure
*/
struct mi_root * mi_profile_list(struct mi_root *cmd_tree, void *param )
{
struct mi_node* node;
struct mi_root* rpl_tree= NULL;
struct mi_node* rpl = NULL;
struct dlg_profile_table *profile;
struct dlg_profile_hash *ph;
str *profile_name;
str *value;
unsigned int i;
node = cmd_tree->node.kids;
if (node==NULL || !node->value.s || !node->value.len)
return init_mi_tree( 400, MI_SSTR(MI_MISSING_PARM));
profile_name = &node->value;
if (node->next) {
node = node->next;
if (!node->value.s || !node->value.len)
return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
if (node->next)
return init_mi_tree( 400, MI_SSTR(MI_MISSING_PARM));
value = &node->value;
} else {
value = NULL;
}
/* search for the profile */
profile = search_dlg_profile( profile_name );
if (profile==NULL)
return init_mi_tree( 404, MI_SSTR("Profile not found"));
rpl_tree = init_mi_tree( 200, MI_SSTR(MI_OK));
if (rpl_tree==0)
return 0;
rpl = &rpl_tree->node;
/* go through the hash and print the dialogs */
if (profile->has_value==0 || value==NULL) {
/* no value */
lock_get( &profile->lock );
for ( i=0 ; i< profile->size ; i++ ) {
ph = profile->entries[i].first;
if(ph) {
do {
/* print dialog */
if ( mi_print_dlg( rpl, ph->dlg, 0)!=0 )
goto error;
/* next */
ph=ph->next;
}while( ph!=profile->entries[i].first );
}
lock_release( &profile->lock );
}
} else {
/* check for value also */
lock_get( &profile->lock );
for ( i=0 ; i< profile->size ; i++ ) {
ph = profile->entries[i].first;
if(ph) {
do {
if ( value->len==ph->value.len &&
memcmp(value->s,ph->value.s,value->len)==0 ) {
/* print dialog */
if ( mi_print_dlg( rpl, ph->dlg, 0)!=0 )
goto error;
}
/* next */
ph=ph->next;
}while( ph!=profile->entries[i].first );
}
lock_release( &profile->lock );
}
}
return rpl_tree;
error:
free_mi_tree(rpl_tree);
return NULL;
}