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/sca/sca.c

376 lines
10 KiB

/*
* $Id$
*
* Copyright (C) 2012 Andrew Mortensen
*
* This file is part of the sca module for sip-router, a free SIP server.
*
* The sca module 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
*
* The sca module 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*/
#include "sca_common.h"
#include <sys/types.h>
#include <stdlib.h>
#include "../../timer.h"
#include "../../timer_proc.h"
#include "sca.h"
#include "sca_appearance.h"
#include "sca_db.h"
#include "sca_call_info.h"
#include "sca_rpc.h"
#include "sca_subscribe.h"
MODULE_VERSION
/* MODULE OBJECT */
sca_mod *sca;
/* EXTERNAL API */
db_func_t dbf; /* db api */
struct tm_binds tmb; /* tm functions for sending messages */
sl_api_t slb; /* sl callback, function for getting to-tag */
/* PROTOTYPES */
static int sca_mod_init( void );
static int sca_child_init( int );
static void sca_mod_destroy( void );
static int sca_set_config( sca_mod * );
/* EXPORTED COMMANDS */
static cmd_export_t cmds[] = {
{ "sca_handle_subscribe", sca_handle_subscribe, 0, NULL, REQUEST_ROUTE },
{ "sca_call_info_update", sca_call_info_update, 0, NULL,
REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE },
{ NULL, NULL, -1, 0, 0 },
};
/* EXPORTED RPC INTERFACE */
static rpc_export_t sca_rpc[] = {
{ "sca.all_subscriptions", sca_rpc_show_all_subscriptions,
sca_rpc_show_all_subscriptions_doc, 0 },
{ "sca.subscription_count", sca_rpc_subscription_count,
sca_rpc_subscription_count_doc, 0 },
{ "sca.show_subscription", sca_rpc_show_subscription,
sca_rpc_show_subscription_doc, 0 },
{ "sca.subscribers", sca_rpc_show_subscribers,
sca_rpc_show_subscribers_doc, 0 },
{ "sca.deactivate_all_subscriptions", sca_rpc_deactivate_all_subscriptions,
sca_rpc_deactivate_all_subscriptions_doc, 0 },
{ "sca.deactivate_subscription", sca_rpc_deactivate_subscription,
sca_rpc_deactivate_subscription_doc, 0 },
{ "sca.all_appearances", sca_rpc_show_all_appearances,
sca_rpc_show_all_appearances_doc, 0 },
{ "sca.show_appearance", sca_rpc_show_appearance,
sca_rpc_show_appearance_doc, 0 },
{ "sca.seize_appearance", sca_rpc_seize_appearance,
sca_rpc_seize_appearance_doc, 0 },
{ "sca.update_appearance", sca_rpc_update_appearance,
sca_rpc_update_appearance_doc, 0 },
{ "sca.release_appearance", sca_rpc_release_appearance,
sca_rpc_release_appearance_doc, 0 },
{ NULL, NULL, NULL, 0 },
};
/* EXPORTED PARAMETERS */
str outbound_proxy = STR_NULL;
str db_url = STR_STATIC_INIT( DEFAULT_DB_URL );
str db_subs_table = STR_STATIC_INIT( "sca_subscriptions" );
str db_state_table = STR_STATIC_INIT( "sca_state" );
int db_update_interval = 300;
int hash_table_size = -1;
int call_info_max_expires = 3600;
int line_seize_max_expires = 15;
int purge_expired_interval = 120;
static param_export_t params[] = {
{ "outbound_proxy", STR_PARAM, &outbound_proxy.s },
{ "db_url", STR_PARAM, &db_url.s },
{ "subs_table", STR_PARAM, &db_subs_table.s },
{ "state_table", STR_PARAM, &db_state_table.s },
{ "db_update_interval", INT_PARAM, &db_update_interval },
{ "hash_table_size", INT_PARAM, &hash_table_size },
{ "call_info_max_expires", INT_PARAM, &call_info_max_expires },
{ "line_seize_max_expires", INT_PARAM, &line_seize_max_expires },
{ "purge_expired_interval", INT_PARAM, &purge_expired_interval },
{ NULL, 0, NULL },
};
/* MODULE EXPORTS */
struct module_exports exports = {
"sca", /* module name */
cmds, /* exported functions */
NULL, /* RPC methods */
params, /* exported parameters */
sca_mod_init, /* module initialization function */
NULL, /* response handling function */
sca_mod_destroy, /* destructor function */
NULL, /* oncancel function */
sca_child_init, /* per-child initialization function */
};
static int
sca_bind_sl( sca_mod *scam, sl_api_t *sl_api )
{
sl_cbelem_t sl_cbe;
assert( scam != NULL );
assert( sl_api != NULL );
if ( sl_load_api( sl_api ) != 0 ) {
LM_ERR( "Failed to initialize required sl API" );
return( -1 );
}
scam->sl_api = sl_api;
sl_cbe.type = SLCB_REPLY_READY;
sl_cbe.cbf = (sl_cbf_f)sca_call_info_sl_reply_cb;
if ( scam->sl_api->register_cb( &sl_cbe ) < 0 ) {
LM_ERR( "Failed to register sl reply callback" );
return( -1 );
}
return( 0 );
}
static int
sca_bind_srdb1( sca_mod *scam, db_func_t *db_api )
{
db1_con_t *db_con = NULL;
int rc = -1;
if ( db_bind_mod( scam->cfg->db_url, db_api ) != 0 ) {
LM_ERR( "Failed to initialize required DB API" );
goto done;
}
scam->db_api = db_api;
if ( !DB_CAPABILITY( (*db_api), DB_CAP_ALL )) {
LM_ERR( "Selected database %.*s lacks required capabilities",
STR_FMT( scam->cfg->db_url ));
goto done;
}
/* ensure database exists and table schemas are correct */
db_con = db_api->init( scam->cfg->db_url );
if ( db_con == NULL ) {
LM_ERR( "sca_bind_srdb1: failed to connect to DB %.*s",
STR_FMT( scam->cfg->db_url ));
goto done;
}
if ( db_check_table_version( db_api, db_con,
scam->cfg->subs_table, SCA_DB_SUBSCRIPTIONS_TABLE_VERSION ) < 0 ) {
LM_ERR( "Version check of %.*s table in DB %.*s failed",
STR_FMT( scam->cfg->subs_table ), STR_FMT( scam->cfg->db_url ));
LM_ERR( "%.*s table version %d required",
STR_FMT( scam->cfg->subs_table ),
SCA_DB_SUBSCRIPTIONS_TABLE_VERSION );
goto done;
}
/* DB and tables are OK, close DB handle. reopen in each child. */
rc = 0;
done:
if ( db_con != NULL ) {
db_api->close( db_con );
db_con = NULL;
}
return( rc );
}
static int
sca_set_config( sca_mod *scam )
{
scam->cfg = (sca_config *)shm_malloc( sizeof( sca_config ));
if ( scam->cfg == NULL ) {
LM_ERR( "Failed to shm_malloc module configuration" );
return( -1 );
}
if ( outbound_proxy.s ) {
outbound_proxy.len = strlen( outbound_proxy.s );
scam->cfg->outbound_proxy = &outbound_proxy;
}
if ( db_url.s == NULL ) {
LM_ERR( "sca_set_config: db_url must be set!" );
return( -1 );
}
db_url.len = strlen( db_url.s );
scam->cfg->db_url = &db_url;
if ( db_subs_table.s == NULL ) {
LM_ERR( "sca_set_config: subs_table must be set!" );
return( -1 );
}
db_subs_table.len = strlen( db_subs_table.s );
scam->cfg->subs_table = &db_subs_table;
if ( db_state_table.s == NULL ) {
LM_ERR( "sca_set_config: state_table must be set!" );
return( -1 );
}
db_state_table.len = strlen( db_state_table.s );
scam->cfg->state_table = &db_state_table;
if ( hash_table_size > 0 ) {
scam->cfg->hash_table_size = 1 << hash_table_size;
} else {
scam->cfg->hash_table_size = 512;
}
scam->cfg->db_update_interval = db_update_interval;
scam->cfg->call_info_max_expires = call_info_max_expires;
scam->cfg->line_seize_max_expires = line_seize_max_expires;
scam->cfg->purge_expired_interval = purge_expired_interval;
return( 0 );
}
static int
sca_child_init( int rank )
{
if ( rank == PROC_INIT || rank == PROC_TCP_MAIN ) {
return( 0 );
}
if ( rank == PROC_MAIN ) {
if ( fork_dummy_timer( PROC_TIMER, "SCA DB SYNC PROCESS",
0, /* we don't need sockets, just writing to DB */
sca_subscription_db_update_timer, /* timer cb */
NULL, /* parameter passed to callback */
sca->cfg->db_update_interval ) < 0 ) {
LM_ERR( "sca_child_init: failed to register subscription DB "
"sync timer process" );
return( -1 );
}
return( 0 );
}
if ( sca->db_api == NULL || sca->db_api->init == NULL ) {
LM_CRIT( "sca_child_init: DB API not loaded!" );
return( -1 );
}
return( 0 );
}
static int
sca_mod_init( void )
{
sca = (sca_mod *)shm_malloc( sizeof( sca_mod ));
if ( sca == NULL ) {
LM_ERR( "Failed to shm_malloc module object" );
return( -1 );
}
memset( sca, 0, sizeof( sca_mod ));
if ( sca_set_config( sca ) != 0 ) {
LM_ERR( "Failed to set configuration" );
goto error;
}
if ( rpc_register_array( sca_rpc ) != 0 ) {
LM_ERR( "Failed to register RPC commands" );
return( -1 );
}
if ( sca_bind_srdb1( sca, &dbf ) != 0 ) {
LM_ERR( "Failed to initialize required DB API" );
return( -1 );
}
if ( load_tm_api( &tmb ) != 0 ) {
LM_ERR( "Failed to initialize required tm API" );
return( -1 );
}
sca->tm_api = &tmb;
if ( sca_bind_sl( sca, &slb ) != 0 ) {
LM_ERR( "Failed to initialize required sl API" );
return( -1 );
}
if ( sca_hash_table_create( &sca->subscriptions,
sca->cfg->hash_table_size ) != 0 ) {
LM_ERR( "Failed to create subscriptions hash table" );
goto error;
}
if ( sca_hash_table_create( &sca->appearances,
sca->cfg->hash_table_size ) != 0 ) {
LM_ERR( "Failed to create appearances hash table" );
goto error;
}
sca_subscriptions_restore_from_db( sca );
register_timer( sca_subscription_purge_expired, sca,
sca->cfg->purge_expired_interval );
register_timer( sca_appearance_purge_stale, sca,
sca->cfg->purge_expired_interval );
/*
* register separate timer process to write subscriptions to DB.
* move to 3.3+ timer API (register_basic_timer) at some point.
*
* timer process forks in sca_child_init, above.
*/
register_dummy_timers( 1 );
LM_INFO( "initialized" );
return( 0 );
error:
if ( sca != NULL ) {
if ( sca->cfg != NULL ) {
shm_free( sca->cfg );
}
if ( sca->subscriptions != NULL ) {
sca_hash_table_free( sca->subscriptions );
}
if ( sca->appearances != NULL ) {
sca_hash_table_free( sca->appearances );
}
shm_free( sca );
sca = NULL;
}
return( -1 );
}
void
sca_mod_destroy( void )
{
/* write back to the DB to retain most current subscription info */
if ( sca_subscription_db_update() != 0 ) {
LM_ERR( "sca_mod_destroy: failed to save current subscriptions "
"in DB %.*s", STR_FMT( sca->cfg->db_url ));
}
sca_db_disconnect();
}