@ -22,239 +22,584 @@
* \ author David M . Lee , II < dlee @ digium . com >
*/
# include <limits.h>
# include "asterisk.h"
# include "asterisk/sorcery.h"
# include "asterisk/config_options.h"
# include "asterisk/http_websocket.h"
# include "asterisk/websocket_client.h"
# include "asterisk/app.h"
# include "asterisk/channel.h"
# include "asterisk/vector.h"
# include "internal.h"
/*! \brief Locking container for safe configuration access. */
static AO2_GLOBAL_OBJ_STATIC ( confs ) ;
/*! \brief Mapping of the ARI conf struct's globals to the
* general context in the config file . */
static struct aco_type general_option = {
. type = ACO_GLOBAL ,
. name = " general " ,
. item_offset = offsetof ( struct ast_ari_conf , general ) ,
. category = " general " ,
. category_match = ACO_WHITELIST_EXACT ,
static struct ast_sorcery * sorcery ;
struct outbound_websocket_state {
enum ari_conf_owc_fields invalid_fields ;
char id [ 0 ] ;
} ;
static struct aco_type * general_options [ ] = ACO_TYPES ( & general_option ) ;
# define OWC_STATES_BUCKETS 13
struct ao2_container * owc_states = NULL ;
/*! \brief Encoding format handler converts from boolean to enum. */
static int encoding_format_handler ( const struct aco_option * opt ,
static void outbound_websocket_dtor ( void * obj )
{
struct ari_conf_outbound_websocket * owc = obj ;
ast_debug ( 3 , " %s: Disposing of outbound websocket config \n " ,
ast_sorcery_object_get_id ( owc ) ) ;
ast_string_field_free_memory ( owc ) ;
ao2_cleanup ( owc - > websocket_client ) ;
owc - > websocket_client = NULL ;
}
static void * outbound_websocket_alloc ( const char * id )
{
struct ari_conf_outbound_websocket * owc = NULL ;
owc = ast_sorcery_generic_alloc ( sizeof ( * owc ) , outbound_websocket_dtor ) ;
if ( ! owc ) {
return NULL ;
}
if ( ast_string_field_init ( owc , 1024 ) ! = 0 ) {
ao2_cleanup ( owc ) ;
return NULL ;
}
ast_debug ( 2 , " %s: Allocated outbound websocket config \n " , id ) ;
return owc ;
}
static int outbound_websocket_websocket_client_id_from_str ( const struct aco_option * opt ,
struct ast_variable * var , void * obj )
{
struct ast_ari_conf_general * general = obj ;
struct a ri_conf_outbound_websocket * owc = obj ;
if ( ! strcasecmp ( var - > name , " pretty " ) ) {
general - > format = ast_true ( var - > value ) ?
AST_JSON_PRETTY : AST_JSON_COMPACT ;
} else {
if ( ast_strlen_zero ( var - > value ) ) {
ast_log ( LOG_ERROR , " %s: Outbound websocket missing websocket client id \n " ,
ast_sorcery_object_get_id ( owc ) ) ;
return - 1 ;
}
owc - > websocket_client = ast_websocket_client_retrieve_by_id ( var - > value ) ;
if ( ! owc - > websocket_client ) {
ast_log ( LOG_ERROR , " %s: Outbound websocket invalid websocket client id '%s' \n " ,
ast_sorcery_object_get_id ( owc ) , var - > value ) ;
return - 1 ;
}
if ( ast_string_field_set ( owc , websocket_client_id , var - > value ) ! = 0 ) {
return - 1 ;
}
return 0 ;
}
static int outbound_websocket_websocket_client_id_to_str ( const void * obj , const intptr_t * args , char * * buf )
{
const struct ari_conf_outbound_websocket * owc = obj ;
if ( ! owc - > websocket_client ) {
return - 1 ;
}
* buf = ast_strdup ( owc - > websocket_client_id ) ;
return 0 ;
}
/*! \brief Parses the ast_ari_password_format enum from a config file */
static int password_format_handler ( const struct aco_option * opt ,
struct ast_variable * var , void * obj )
/*!
* \ brief Callback to initialize an outbound websocket object
* \ retval 0 on success
* \ retval CMP_MATCH on error which will cause the object to be removed
*/
static int outbound_websocket_apply ( const struct ast_sorcery * sorcery , void * obj )
{
struct ast_ari_conf_user * user = obj ;
struct ari_conf_outbound_websocket * owc = obj ;
const char * id = ast_sorcery_object_get_id ( owc ) ;
int res = 0 ;
if ( strcasecmp ( var - > value , " plain " ) = = 0 ) {
user - > password_format = ARI_PASSWORD_FORMAT_PLAIN ;
} else if ( strcasecmp ( var - > value , " crypt " ) = = 0 ) {
user - > password_format = ARI_PASSWORD_FORMAT_CRYPT ;
ast_debug ( 3 , " %s: Initializing outbound websocket \n " , id ) ;
if ( ast_strlen_zero ( owc - > apps ) ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket missing apps \n " , id ) ;
res = - 1 ;
} else {
return - 1 ;
char * apps = ast_strdupa ( owc - > apps ) ;
char * app ;
while ( ( app = ast_strsep ( & apps , ' , ' , AST_STRSEP_STRIP ) ) ) {
if ( ast_strlen_zero ( app ) ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket has empty app \n " , id ) ;
res = - 1 ;
}
if ( strlen ( app ) > ARI_MAX_APP_NAME_LEN ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket app '%s' > %d characters \n " ,
id , app , ( int ) ARI_MAX_APP_NAME_LEN ) ;
res = - 1 ;
}
return 0 ;
}
}
/*! \brief Destructor for \ref ast_ari_conf_user */
static void user_dtor ( void * obj )
if ( ast_strlen_zero ( owc - > local_ari_user ) ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket missing local_ari_user \n " , id ) ;
res = - 1 ;
}
if ( res ! = 0 ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket configuration failed \n " , id ) ;
} else {
ast_debug ( 3 , " %s: Outbound websocket configuration succeeded \n " , id ) ;
}
/* Reminder: If res is -1, the config will be discarded. */
return res ;
}
enum ari_conf_owc_fields ari_conf_owc_get_invalid_fields ( const char * id )
{
struct ast_ari_conf_user * user = obj ;
ast_debug ( 3 , " Disposing of user %s \n " , user - > username ) ;
ast_free ( user - > username ) ;
RAII_VAR ( struct outbound_websocket_state * , state , NULL , ao2_cleanup ) ;
state = ao2_find ( owc_states , id , OBJ_SEARCH_KEY ) ;
return state ? state - > invalid_fields : ARI_OWC_FIELD_NONE ;
}
/*! \brief Allocate an \ref ast_ari_conf_user for config parsing */
static void * user_alloc ( const char * cat )
static int outbound_websocket_validate_cb ( void * obj , void * args , int flags )
{
RAII_VAR ( struct ast_ari_conf_user * , user , NULL , ao2_cleanup ) ;
struct ari_conf_outbound_websocket * owc = obj ;
struct ari_conf_outbound_websocket * other_owc = NULL ;
RAII_VAR ( struct ao2_container * , owcs , NULL , ao2_cleanup ) ;
struct ao2_iterator it ;
const char * id = ast_sorcery_object_get_id ( owc ) ;
struct ast_vector_string apps = { 0 , } ;
struct ari_conf_user * user = NULL ;
struct outbound_websocket_state * state = NULL ;
int res = 0 ;
ast_debug ( 2 , " %s: Validating outbound websocket \n " , id ) ;
owcs = ari_conf_get_owcs ( ) ;
if ( ! owcs | | ao2_container_count ( owcs ) = = 0 ) {
return 0 ;
}
if ( ! cat ) {
return NULL ;
if ( AST_VECTOR_INIT ( & apps , 5 ) ! = 0 ) {
return 0 ;
}
ast_debug ( 3 , " Allocating user %s \n " , cat ) ;
res = ast_vector_string_split ( & apps , owc - > apps , " , " , 0 , NULL ) ;
if ( res ! = 0 ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket apps '%s' failed to split \n " ,
id , owc - > apps ) ;
AST_VECTOR_RESET ( & apps , ast_free_ptr ) ;
AST_VECTOR_FREE ( & apps ) ;
return 0 ;
}
user = ao2_alloc_options ( sizeof ( * user ) , user_dtor ,
AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
state = ao2_find ( owc_states , id , OBJ_SEARCH_KEY ) ;
if ( ! state ) {
state = ao2_alloc ( sizeof ( * state ) + strlen ( id ) + 1 , NULL ) ;
if ( ! state ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket state allocation failed \n " , id ) ;
AST_VECTOR_RESET ( & apps , ast_free_ptr ) ;
AST_VECTOR_FREE ( & apps ) ;
return 0 ;
}
strcpy ( state - > id , id ) ; /* Safe */
ast_debug ( 3 , " %s: Created new outbound websocket state \n " , id ) ;
} else {
ast_debug ( 3 , " %s: Outbound websocket state already exists \n " , id ) ;
}
state - > invalid_fields = ARI_OWC_FIELD_NONE ;
/*
* Check all other owcs to make sure we don ' t have
* duplicate apps .
*/
it = ao2_iterator_init ( owcs , 0 ) ;
while ( ( other_owc = ao2_iterator_next ( & it ) ) ) {
const char * other_id = ast_sorcery_object_get_id ( other_owc ) ;
if ( ! ast_strings_equal ( other_id , id ) ) {
int i = 0 ;
for ( i = 0 ; i < AST_VECTOR_SIZE ( & apps ) ; i + + ) {
const char * app = AST_VECTOR_GET ( & apps , i ) ;
if ( ast_in_delimited_string ( app , other_owc - > apps , ' , ' ) ) {
ast_log ( LOG_WARNING ,
" %s: Outbound websocket '%s' is also trying to register app '%s' \n " ,
id , other_id , app ) ;
state - > invalid_fields | = ARI_OWC_FIELD_APPS ;
}
}
}
ao2_cleanup ( other_owc ) ;
if ( owc - > invalid ) {
break ;
}
}
ao2_iterator_destroy ( & it ) ;
AST_VECTOR_RESET ( & apps , ast_free_ptr ) ;
AST_VECTOR_FREE ( & apps ) ;
/*
* Check that the local_ari_user is valid and has
* a plain text password .
*/
user = ast_sorcery_retrieve_by_id ( sorcery , " user " , owc - > local_ari_user ) ;
if ( ! user ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket ARI user '%s' not found \n " ,
id , owc - > local_ari_user ) ;
state - > invalid_fields | = ARI_OWC_FIELD_LOCAL_ARI_USER ;
} else {
if ( user - > password_format ! = ARI_PASSWORD_FORMAT_PLAIN ) {
ast_log ( LOG_WARNING , " %s: Outbound websocket ARI user '%s' password MUST be plain text \n " ,
id , owc - > local_ari_user ) ;
state - > invalid_fields | = ARI_OWC_FIELD_LOCAL_ARI_USER ;
}
if ( ast_string_field_set ( owc , local_ari_password , user - > password ) ! = 0 ) {
state - > invalid_fields | = ARI_OWC_FIELD_LOCAL_ARI_USER ;
}
}
ao2_cleanup ( user ) ;
/*
* The container has AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE set so
* this is an insert or replace operation .
*/
ao2_link ( owc_states , state ) ;
ao2_cleanup ( state ) ;
return 0 ;
}
static int outbound_websocket_state_cleanup ( void * obj , void * arg , int flags )
{
struct outbound_websocket_state * state = obj ;
struct ari_conf_outbound_websocket * owc = ari_conf_get_owc ( state - > id ) ;
int res = 0 ;
if ( ! owc ) {
ast_debug ( 3 , " %s: Cleaning up orphaned outbound websocket state \n " , state - > id ) ;
res = CMP_MATCH ;
}
ao2_cleanup ( owc ) ;
return res ;
}
static void outbound_websockets_validate ( const char * name )
{
RAII_VAR ( struct ao2_container * , owcs , ari_conf_get_owcs ( ) , ao2_cleanup ) ;
ao2_callback ( owcs , OBJ_NODATA , outbound_websocket_validate_cb , NULL ) ;
/* Clean up any states whose configs have disappeared. */
ao2_callback ( owc_states , OBJ_NODATA | OBJ_UNLINK ,
outbound_websocket_state_cleanup , NULL ) ;
}
struct ao2_container * ari_conf_get_owcs ( void )
{
if ( ! sorcery ) {
return NULL ;
}
user - > username = ast_strdup ( cat ) ;
if ( ! user - > username ) {
return ast_sorcery_retrieve_by_fields ( sorcery , " outbound_websocket " ,
AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
}
struct ari_conf_outbound_websocket * ari_conf_get_owc ( const char * id )
{
if ( ! sorcery ) {
return NULL ;
}
ao2_ref ( user , + 1 ) ;
return user ;
return ast_sorcery_retrieve_by_id ( sorcery , " outbound_websocket " , id ) ;
}
/*! \brief Sorting function for use with red/black tree */
static int user_sort_cmp ( const void * obj_left , const void * obj_right , int flags )
struct ari_conf_outbound_websocket * ari_conf_get_owc_for_app (
const char * app_name , unsigned int ws_type )
{
const struct ast_ari_conf_user * user_left = obj_left ;
const struct ast_ari_conf_user * user_right = obj_right ;
const char * key_right = obj_right ;
int cmp ;
struct ari_conf_outbound_websocket * owc = NULL ;
struct ao2_container * owcs = NULL ;
struct ao2_iterator i ;
switch ( flags & OBJ_SEARCH_MASK ) {
case OBJ_SEARCH_OBJECT :
key_right = user_right - > username ;
/* Fall through */
case OBJ_SEARCH_KEY :
cmp = strcasecmp ( user_left - > username , key_right ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
/*
* We could also use a partial key struct containing a length
* so strlen ( ) does not get called for every comparison instead .
*/
cmp = strncasecmp ( user_left - > username , key_right , strlen ( key_right ) ) ;
if ( ast_strlen_zero ( app_name ) ) {
return NULL ;
}
ast_debug ( 3 , " Checking outbound websockets for app '%s' \n " , app_name ) ;
owcs = ari_conf_get_owcs ( ) ;
if ( ! owcs | | ao2_container_count ( owcs ) = = 0 ) {
ast_debug ( 3 , " No outbound websockets found \n " ) ;
return NULL ;
}
i = ao2_iterator_init ( owcs , 0 ) ;
while ( ( owc = ao2_iterator_next ( & i ) ) ) {
const char * id = ast_sorcery_object_get_id ( owc ) ;
ast_debug ( 3 , " %s: Checking outbound websocket apps '%s' for app '%s' \n " ,
id , owc - > apps , app_name ) ;
if ( owc - > websocket_client - > connection_type & ws_type
& & ast_in_delimited_string ( app_name , owc - > apps , ' , ' ) ) {
ast_debug ( 3 , " %s: Found correct websocket type for apps '%s' for app '%s' \n " ,
id , owc - > apps , app_name ) ;
break ;
}
ao2_cleanup ( owc ) ;
}
ao2_iterator_destroy ( & i ) ;
ao2_cleanup ( owcs ) ;
if ( ! owc ) {
ast_debug ( 3 , " No outbound websocket found for app '%s' \n " , app_name ) ;
}
return owc ;
}
const char * ari_websocket_type_to_str ( enum ast_websocket_type type )
{
switch ( type ) {
case AST_WS_TYPE_CLIENT_PERSISTENT :
return " persistent " ;
case AST_WS_TYPE_CLIENT_PER_CALL :
return " per_call " ;
case AST_WS_TYPE_CLIENT_PER_CALL_CONFIG :
return " per_call_config " ;
case AST_WS_TYPE_INBOUND :
return " inbound " ;
case AST_WS_TYPE_ANY :
return " any " ;
default :
/* Sort can only work on something with a full or partial key. */
ast_assert ( 0 ) ;
cmp = 0 ;
break ;
return " unknown " ;
}
return cmp ;
}
/*! \brief \ref aco_type item_find function */
static void * user_find ( struct ao2_container * tmp_container , const char * cat )
enum ari_conf_owc_fields ari_conf_owc_detect_changes (
struct ari_conf_outbound_websocket * old_owc ,
struct ari_conf_outbound_websocket * new_owc )
{
if ( ! cat ) {
return NULL ;
enum ari_conf_owc_fields changed = ARI_OWC_FIELD_NONE ;
const char * new_id = ast_sorcery_object_get_id ( new_owc ) ;
RAII_VAR ( struct ast_variable * , changes , NULL , ast_variables_destroy ) ;
struct ast_variable * v = NULL ;
int res = 0 ;
int changes_found = 0 ;
ast_debug ( 2 , " %s: Detecting changes \n " , new_id ) ;
res = ast_sorcery_diff ( sorcery , old_owc , new_owc , & changes ) ;
if ( res ! = 0 ) {
ast_log ( LOG_WARNING , " %s: Failed to create changeset \n " , new_id ) ;
return ARI_OWC_FIELD_NONE ;
}
for ( v = changes ; v ; v = v - > next ) {
changes_found = 1 ;
ast_debug ( 2 , " %s: %s changed to %s \n " , new_id , v - > name , v - > value ) ;
if ( ast_strings_equal ( v - > name , " apps " ) ) {
changed | = ARI_OWC_FIELD_APPS ;
} else if ( ast_strings_equal ( v - > name , " subscribe_all " ) ) {
changed | = ARI_OWC_FIELD_SUBSCRIBE_ALL ;
} else if ( ast_strings_equal ( v - > name , " local_ari_user " ) ) {
changed | = ARI_OWC_FIELD_LOCAL_ARI_USER ;
} else if ( ast_strings_equal ( v - > name , " local_ari_password " ) ) {
changed | = ARI_OWC_FIELD_LOCAL_ARI_PASSWORD ;
} else {
ast_debug ( 2 , " %s: Unknown change %s \n " , new_id , v - > name ) ;
}
}
if ( ! changes_found ) {
ast_debug ( 2 , " %s: No changes found %p %p \n " , new_id ,
old_owc - > websocket_client , new_owc - > websocket_client ) ;
}
changed | = ast_websocket_client_get_field_diff (
old_owc - > websocket_client , new_owc - > websocket_client ) ;
return changed ;
return ao2_find ( tmp_container , cat , OBJ_SEARCH_KEY ) ;
}
static struct aco_type user_option = {
. type = ACO_ITEM ,
. name = " user " ,
. category_match = ACO_BLACKLIST_EXACT ,
. category = " general " ,
. matchfield = " type " ,
. matchvalue = " user " ,
. item_alloc = user_alloc ,
. item_find = user_find ,
. item_offset = offsetof ( struct ast_ari_conf , users ) ,
} ;
/*! \brief \ref ast_ari_conf destructor. */
static void general_dtor ( void * obj )
{
struct ari_conf_general * cfg = obj ;
static struct aco_type * global_user [ ] = ACO_TYPES ( & user_option ) ;
ast_string_field_free_memory ( cfg ) ;
}
static void conf_general_dtor ( void * obj )
static void * general_alloc ( const char * name )
{
struct ast_ari_conf_general * general = obj ;
struct ari_conf_general * general = ast_sorcery_generic_alloc (
sizeof ( * general ) , general_dtor ) ;
ast_string_field_free_memory ( general ) ;
if ( ! general ) {
return NULL ;
}
/*! \brief \ref ast_ari_conf destructor. */
static void conf_destructor ( void * obj )
if ( ast_string_field_init ( general , 64 ) ! = 0 ) {
return NULL ;
}
return general ;
}
# define MAX_VARS 128
static int general_apply ( const struct ast_sorcery * sorcery , void * obj )
{
struct ast_ari_conf * cfg = obj ;
struct ari_conf_general * general = obj ;
char * parse = NULL ;
AST_DECLARE_APP_ARGS ( args ,
AST_APP_ARG ( vars ) [ MAX_VARS ] ;
) ;
ast_debug ( 2 , " Initializing general config \n " ) ;
parse = ast_strdupa ( general - > channelvars ) ;
AST_STANDARD_APP_ARGS ( args , parse ) ;
ao2_cleanup ( cfg - > general ) ;
ao2_cleanup ( cfg - > users ) ;
a st_channel_set_ari_vars( args . argc , args . vars ) ;
return 0 ;
}
/*! \brief Allocate an \ref ast_ari_conf for config parsing */
static void * conf_alloc ( void )
/*! \brief Encoding format handler converts from boolean to enum. */
static int general_pretty_from_str ( const struct aco_option * opt ,
struct ast_variable * var , void * obj )
{
struct ast_ari_conf * cfg ;
struct ari_conf_general * general = obj ;
general - > format = ast_true ( var - > value ) ? AST_JSON_PRETTY : AST_JSON_COMPACT ;
cfg = ao2_alloc_options ( sizeof ( * cfg ) , conf_destructor ,
AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
if ( ! cfg ) {
return 0 ;
}
struct ari_conf_general * ari_conf_get_general ( void )
{
if ( ! sorcery ) {
return NULL ;
}
cfg - > general = ao2_alloc_options ( sizeof ( * cfg - > general ) , conf_general_dtor ,
AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
return ast_sorcery_retrieve_by_id ( sorcery , " general " , " general " ) ;
}
cfg - > users = ao2_container_alloc_rbtree ( AO2_ALLOC_OPT_LOCK_NOLOCK ,
AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE , user_sort_cmp , NULL ) ;
static int general_pretty_to_str ( const void * obj , const intptr_t * args , char * * buf )
{
const struct ari_conf_general * general = obj ;
if ( ! cfg - > users
| | ! cfg - > general
| | ast_string_field_init ( cfg - > general , 64 )
| | aco_set_defaults ( & general_option , " general " , cfg - > general ) ) {
ao2_ref ( cfg , - 1 ) ;
if ( general - > format = = AST_JSON_PRETTY ) {
* buf = ast_strdup ( " yes " ) ;
} else {
* buf = ast_strdup ( " no " ) ;
}
return 0 ;
}
/*! \brief Destructor for \ref ast_ari_conf_user */
static void user_dtor ( void * obj )
{
struct ari_conf_user * user = obj ;
ast_string_field_free_memory ( user ) ;
ast_debug ( 3 , " %s: Disposing of user \n " , ast_sorcery_object_get_id ( user ) ) ;
}
/*! \brief Allocate an \ref ast_ari_conf_user for config parsing */
static void * user_alloc ( const char * cat )
{
struct ari_conf_user * user = ast_sorcery_generic_alloc (
sizeof ( * user ) , user_dtor ) ;
if ( ! user ) {
return NULL ;
}
return cfg ;
if ( ast_string_field_init ( user , 64 ) ! = 0 ) {
ao2_cleanup ( user ) ;
user = NULL ;
}
# define CONF_FILENAME "ari.conf"
return user ;
}
/*! \brief The conf file that's processed for the module. */
static struct aco_file conf_file = {
/*! The config file name. */
. filename = CONF_FILENAME ,
/*! The mapping object types to be processed. */
. types = ACO_TYPES ( & general_option , & user_option ) ,
} ;
static int user_apply ( const struct ast_sorcery * sorcery , void * obj )
{
struct ari_conf_user * user = obj ;
const char * id = ast_sorcery_object_get_id ( user ) ;
ast_debug ( 2 , " %s: Initializing user \n " , id ) ;
if ( ast_strlen_zero ( user - > password ) ) {
ast_log ( LOG_WARNING , " %s: User missing password \n " , id ) ;
return - 1 ;
}
CONFIG_INFO_STANDARD ( cfg_info , confs , conf_alloc ,
. files = ACO_FILES ( & conf_file ) ) ;
return 0 ;
}
struct ast_ari_conf * ast_ari_config_get ( void )
/*! \brief Parses the ast_ari_password_format enum from a config file */
static int user_password_format_from_str ( const struct aco_option * opt ,
struct ast_variable * var , void * obj )
{
struct ast_ari_conf * res = ao2_global_obj_ref ( confs ) ;
if ( ! res ) {
ast_log ( LOG_ERROR ,
" Error obtaining config from " CONF_FILENAME " \n " ) ;
struct ari_conf_user * user = obj ;
if ( strcasecmp ( var - > value , " plain " ) = = 0 ) {
user - > password_format = ARI_PASSWORD_FORMAT_PLAIN ;
} else if ( strcasecmp ( var - > value , " crypt " ) = = 0 ) {
user - > password_format = ARI_PASSWORD_FORMAT_CRYPT ;
} else {
return - 1 ;
}
return res ;
return 0 ;
}
struct ast_ari_conf_user * ast_ari_config_validate_user ( const char * username ,
const char * password )
static int user_password_format_to_str ( const void * obj , const intptr_t * args , char * * buf )
{
RAII_VAR ( struct ast_ari_conf * , conf , NULL , ao2_cleanup ) ;
RAII_VAR ( struct ast_ari_conf_user * , user , NULL , ao2_cleanup ) ;
int is_valid = 0 ;
const struct ari_conf_user * user = obj ;
conf = ast_ari_config_get ( ) ;
if ( ! conf ) {
if ( user - > password_format = = ARI_PASSWORD_FORMAT_CRYPT ) {
* buf = ast_strdup ( " crypt " ) ;
} else {
* buf = ast_strdup ( " plain " ) ;
}
return 0 ;
}
struct ao2_container * ari_conf_get_users ( void )
{
if ( ! sorcery ) {
return NULL ;
}
user = ao2_find ( conf - > users , username , OBJ_SEARCH_KEY ) ;
if ( ! user ) {
return ast_sorcery_retrieve_by_fields ( sorcery , " user " ,
AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
}
struct ari_conf_user * ari_conf_get_user ( const char * username )
{
if ( ! sorcery ) {
return NULL ;
}
if ( ast_strlen_zero ( user - > password ) ) {
ast_log ( LOG_WARNING ,
" User '%s' missing password; authentication failed \n " ,
user - > username ) ;
return ast_sorcery_retrieve_by_id ( sorcery , " user " , username ) ;
}
/*
* This is called by res_ari . c to validate the user and password
* for the websocket connection .
*/
struct ari_conf_user * ari_conf_validate_user ( const char * username ,
const char * password )
{
struct ari_conf_user * user = NULL ;
int is_valid = 0 ;
if ( ast_strlen_zero ( username ) | | ast_strlen_zero ( password ) ) {
return NULL ;
}
user = ast_sorcery_retrieve_by_id ( sorcery , " user " , username ) ;
if ( ! user ) {
return NULL ;
}
@ -268,120 +613,184 @@ struct ast_ari_conf_user *ast_ari_config_validate_user(const char *username,
}
if ( ! is_valid ) {
return NULL ;
ao2_cleanup ( user ) ;
user = NULL ;
}
ao2_ref ( user , + 1 ) ;
return user ;
}
/*! \brief Callback to validate a user object */
static int validate_user_cb ( void * obj , void * arg , int flag s)
int ari_sorcery_observer_add ( const char * object_type ,
const struct ast_sorcery_observer * callback s)
{
struct ast_ari_conf_user * user = obj ;
if ( ast_strlen_zero ( user - > password ) ) {
ast_log ( LOG_WARNING , " User '%s' missing password \n " ,
user - > username ) ;
if ( ! sorcery ) {
return - 1 ;
}
return ast_sorcery_observer_add ( sorcery , object_type , callbacks ) ;
}
int ari_sorcery_observer_remove ( const char * object_type ,
const struct ast_sorcery_observer * callbacks )
{
if ( ! sorcery ) {
return - 1 ;
}
ast_sorcery_observer_remove ( sorcery , object_type , callbacks ) ;
return 0 ;
}
/*! \brief Load (or reload) configuration. */
static int process_config ( int reload )
static struct ast_sorcery_observer observer_callbacks = {
. loaded = outbound_websockets_validate ,
} ;
static void ws_client_load ( const char * name )
{
RAII_VAR ( struct ast_ari_conf * , conf , NULL , ao2_cleanup ) ;
ast_sorcery_force_reload_object ( sorcery , " outbound_websocket " ) ;
}
static struct ast_sorcery_observer ws_client_observer_callbacks = {
. loaded = ws_client_load ,
} ;
switch ( aco_process_config ( & cfg_info , reload ) ) {
case ACO_PROCESS_ERROR :
AO2_STRING_FIELD_HASH_FN ( outbound_websocket_state , id )
AO2_STRING_FIELD_CMP_FN ( outbound_websocket_state , id )
static int ari_conf_init ( void )
{
int res = 0 ;
ast_debug ( 2 , " Initializing ARI configuration \n " ) ;
owc_states = ao2_container_alloc_hash ( AO2_ALLOC_OPT_LOCK_MUTEX ,
AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE , OWC_STATES_BUCKETS ,
outbound_websocket_state_hash_fn , NULL ,
outbound_websocket_state_cmp_fn ) ;
if ( ! owc_states ) {
ast_log ( LOG_ERROR , " Failed to allocate outbound websocket states \n " ) ;
return - 1 ;
case ACO_PROCESS_OK :
case ACO_PROCESS_UNCHANGED :
break ;
}
conf = ast_ari_config_get ( ) ;
if ( ! conf ) {
ast_assert ( 0 ) ; /* We just configured; it should be there */
if ( ! ( sorcery = ast_sorcery_open ( ) ) ) {
ast_log ( LOG_ERROR , " Failed to open sorcery \n " ) ;
return - 1 ;
}
if ( conf - > general - > enabled ) {
if ( ao2_container_count ( conf - > users ) = = 0 ) {
ast_log ( LOG_ERROR , " No configured users for ARI \n " ) ;
} else {
ao2_callback ( conf - > users , OBJ_NODATA , validate_user_cb , NULL ) ;
}
ast_sorcery_apply_default ( sorcery , " general " , " config " ,
" ari.conf,criteria=type=general,single_object=yes,explicit_name=general " ) ;
ast_sorcery_apply_default ( sorcery , " user " , " config " ,
" ari.conf,criteria=type=user " ) ;
ast_sorcery_apply_default ( sorcery , " outbound_websocket " , " config " ,
" ari.conf,criteria=type=outbound_websocket " ) ;
if ( ast_sorcery_object_register ( sorcery , " general " , general_alloc , NULL , general_apply ) ) {
ast_log ( LOG_ERROR , " Failed to register ARI general object with sorcery \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
return 0 ;
if ( ast_sorcery_object_register ( sorcery , " user " , user_alloc , NULL , user_apply ) ) {
ast_log ( LOG_ERROR , " Failed to register ARI user object with sorcery \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
# define MAX_VARS 128
if ( ast_sorcery_object_register ( sorcery , " outbound_websocket " , outbound_websocket_alloc ,
NULL , outbound_websocket_apply ) ) {
ast_log ( LOG_ERROR , " Failed to register ARI outbound_websocket object with sorcery \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
static int channelvars_handler ( const struct aco_option * opt , struct ast_variable * var , void * obj )
{
char * parse = NULL ;
AST_DECLARE_APP_ARGS ( args ,
AST_APP_ARG ( vars ) [ MAX_VARS ] ;
);
if ( ast_sorcery_observer_add ( sorcery , " outbound_websocket " , & observer_callbacks ) ) {
ast_log ( LOG_ERROR , " Failed to register ARI outbound_websocket observer with sorcery \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
parse = ast_strdupa ( var - > value ) ;
AST_STANDARD_APP_ARGS ( args , parse ) ;
ast_sorcery_object_field_register_nodoc ( sorcery , " general " , " type " , " " , OPT_NOOP_T , 0 , 0 ) ;
ast_sorcery_register_sf ( general , ari_conf_general , auth_realm , auth_realm , " Asterisk REST Interface " ) ;
ast_sorcery_register_sf ( general , ari_conf_general , allowed_origins , allowed_origins , " " ) ;
ast_sorcery_register_sf ( general , ari_conf_general , channelvars , channelvars , " " ) ;
ast_sorcery_register_bool ( general , ari_conf_general , enabled , enabled , " yes " ) ;
ast_sorcery_register_cust ( general , pretty , " no " ) ;
ast_sorcery_register_int ( general , ari_conf_general , websocket_write_timeout , write_timeout ,
AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT ) ;
ast_sorcery_object_field_register ( sorcery , " user " , " type " , " " , OPT_NOOP_T , 0 , 0 ) ;
ast_sorcery_register_sf ( user , ari_conf_user , password , password , " " ) ;
ast_sorcery_register_bool ( user , ari_conf_user , read_only , read_only , " no " ) ;
ast_sorcery_register_cust ( user , password_format , " plain " ) ;
ast_sorcery_object_field_register ( sorcery , " outbound_websocket " , " type " , " " , OPT_NOOP_T , 0 , 0 ) ;
ast_sorcery_register_cust ( outbound_websocket , websocket_client_id , " " ) ;
ast_sorcery_register_sf ( outbound_websocket , ari_conf_outbound_websocket , apps , apps , " " ) ;
ast_sorcery_register_sf ( outbound_websocket , ari_conf_outbound_websocket , local_ari_user , local_ari_user , " " ) ;
ast_sorcery_register_bool ( outbound_websocket , ari_conf_outbound_websocket , subscribe_all , subscribe_all , " no " ) ;
res = ast_websocket_client_observer_add ( & ws_client_observer_callbacks ) ;
if ( res < 0 ) {
ast_log ( LOG_WARNING , " Failed to register websocket client observer \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
ast_channel_set_ari_vars ( args . argc , args . vars ) ;
return 0 ;
}
int ast_ari_config_init ( void )
int a ri_conf_load( enum ari_conf_load_flags flags )
{
if ( aco_info_init ( & cfg_info ) ) {
aco_info_destroy ( & cfg_info ) ;
void ( * loader ) ( const struct ast_sorcery * sorcery , const char * type ) ;
const char * msg_prefix ;
if ( flags & ARI_CONF_RELOAD ) {
loader = ast_sorcery_reload_object ;
msg_prefix = " Reloading " ;
ast_websocket_client_reload ( ) ;
} else {
loader = ast_sorcery_load_object ;
msg_prefix = " Loading " ;
}
if ( flags & ARI_CONF_INIT ) {
if ( ari_conf_init ( ) ! = 0 ) {
ast_log ( LOG_ERROR , " Failed to initialize ARI configuration \n " ) ;
return - 1 ;
}
}
if ( ! sorcery ) {
ast_log ( LOG_ERROR , " ARI configuration not initialized \n " ) ;
return - 1 ;
}
if ( flags & ARI_CONF_LOAD_GENERAL ) {
ast_debug ( 2 , " %s ARI '%s' configuration \n " , msg_prefix , " general " ) ;
loader ( sorcery , " general " ) ;
}
if ( flags & ARI_CONF_LOAD_USER ) {
ast_debug ( 2 , " %s ARI '%s' configuration \n " , msg_prefix , " user " ) ;
loader ( sorcery , " user " ) ;
}
if ( flags & ARI_CONF_LOAD_OWC ) {
ast_debug ( 2 , " %s ARI '%s' configuration \n " , msg_prefix , " outbound_websocket " ) ;
loader ( sorcery , " outbound_websocket " ) ;
}
return 0 ;
}
void ari_conf_destroy ( void )
{
ast_websocket_client_observer_remove ( & ws_client_observer_callbacks ) ;
/* ARI general category options */
aco_option_register ( & cfg_info , " enabled " , ACO_EXACT , general_options ,
" yes " , OPT_BOOL_T , 1 ,
FLDSET ( struct ast_ari_conf_general , enabled ) ) ;
aco_option_register_custom ( & cfg_info , " pretty " , ACO_EXACT ,
general_options , " no " , encoding_format_handler , 0 ) ;
aco_option_register ( & cfg_info , " auth_realm " , ACO_EXACT , general_options ,
" Asterisk REST Interface " , OPT_CHAR_ARRAY_T , 0 ,
FLDSET ( struct ast_ari_conf_general , auth_realm ) ,
ARI_AUTH_REALM_LEN ) ;
aco_option_register ( & cfg_info , " allowed_origins " , ACO_EXACT , general_options ,
" " , OPT_STRINGFIELD_T , 0 ,
STRFLDSET ( struct ast_ari_conf_general , allowed_origins ) ) ;
aco_option_register ( & cfg_info , " websocket_write_timeout " , ACO_EXACT , general_options ,
AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR , OPT_INT_T , PARSE_IN_RANGE ,
FLDSET ( struct ast_ari_conf_general , write_timeout ) , 1 , INT_MAX ) ;
aco_option_register_custom ( & cfg_info , " channelvars " , ACO_EXACT , general_options ,
" " , channelvars_handler , 0 ) ;
/* ARI type=user category options */
aco_option_register ( & cfg_info , " type " , ACO_EXACT , global_user , NULL ,
OPT_NOOP_T , 0 , 0 ) ;
aco_option_register ( & cfg_info , " read_only " , ACO_EXACT , global_user ,
" no " , OPT_BOOL_T , 1 ,
FLDSET ( struct ast_ari_conf_user , read_only ) ) ;
aco_option_register ( & cfg_info , " password " , ACO_EXACT , global_user ,
" " , OPT_CHAR_ARRAY_T , 0 ,
FLDSET ( struct ast_ari_conf_user , password ) , ARI_PASSWORD_LEN ) ;
aco_option_register_custom ( & cfg_info , " password_format " , ACO_EXACT ,
global_user , " plain " , password_format_handler , 0 ) ;
return process_config ( 0 ) ;
}
int ast_ari_config_reload ( void )
{
return process_config ( 1 ) ;
}
void ast_ari_config_destroy ( void )
{
aco_info_destroy ( & cfg_info ) ;
ao2_global_obj_release ( confs ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
ao2_cleanup ( owc_states ) ;
}