asterisk/main/refer.c

538 lines
11 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2023, Commend International
*
* Maximilian Fridrich <m.fridrich@commend.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief Out-of-call refer support
*
* \author Maximilian Fridrich <m.fridrich@commend.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/_private.h"
#include "asterisk/module.h"
#include "asterisk/datastore.h"
#include "asterisk/pbx.h"
#include "asterisk/manager.h"
#include "asterisk/strings.h"
#include "asterisk/astobj2.h"
#include "asterisk/vector.h"
#include "asterisk/app.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/refer.h"
struct refer_data {
/* Stored in stuff[] at struct end */
char *name;
/* Stored separately */
char *value;
/* Holds name */
char stuff[0];
};
/*!
* \brief A refer.
*/
struct ast_refer {
AST_DECLARE_STRING_FIELDS(
/*! Where the refer is going */
AST_STRING_FIELD(to);
/*! Where we "say" the refer came from */
AST_STRING_FIELD(from);
/*! Where to refer to */
AST_STRING_FIELD(refer_to);
/*! An endpoint associated with this refer */
AST_STRING_FIELD(endpoint);
/*! The technology of the endpoint associated with this refer */
AST_STRING_FIELD(tech);
);
/* Whether to refer to Asterisk itself, if refer_to is an Asterisk endpoint. */
int to_self;
/*! Technology/dialplan specific variables associated with the refer */
struct ao2_container *vars;
};
/*! \brief Lock for \c refer_techs vector */
static ast_rwlock_t refer_techs_lock;
/*! \brief Vector of refer technologies */
AST_VECTOR(, const struct ast_refer_tech *) refer_techs;
static int refer_data_cmp_fn(void *obj, void *arg, int flags)
{
const struct refer_data *object_left = obj;
const struct refer_data *object_right = arg;
const char *right_key = arg;
int cmp;
switch (flags & OBJ_SEARCH_MASK) {
case OBJ_SEARCH_OBJECT:
right_key = object_right->name;
case OBJ_SEARCH_KEY:
cmp = strcasecmp(object_left->name, right_key);
break;
case OBJ_SEARCH_PARTIAL_KEY:
cmp = strncasecmp(object_left->name, right_key, strlen(right_key));
break;
default:
cmp = 0;
break;
}
if (cmp) {
return 0;
}
return CMP_MATCH;
}
static void refer_data_destructor(void *obj)
{
struct refer_data *data = obj;
ast_free(data->value);
}
static void refer_destructor(void *obj)
{
struct ast_refer *refer = obj;
ast_string_field_free_memory(refer);
ao2_cleanup(refer->vars);
}
struct ast_refer *ast_refer_alloc(void)
{
struct ast_refer *refer;
if (!(refer = ao2_alloc_options(sizeof(*refer), refer_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
return NULL;
}
if (ast_string_field_init(refer, 128)) {
ao2_ref(refer, -1);
return NULL;
}
refer->vars = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
NULL, refer_data_cmp_fn);
if (!refer->vars) {
ao2_ref(refer, -1);
return NULL;
}
refer->to_self = 0;
return refer;
}
struct ast_refer *ast_refer_ref(struct ast_refer *refer)
{
ao2_ref(refer, 1);
return refer;
}
struct ast_refer *ast_refer_destroy(struct ast_refer *refer)
{
ao2_ref(refer, -1);
return NULL;
}
int ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ast_string_field_build_va(refer, to, fmt, ap);
va_end(ap);
return 0;
}
int ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ast_string_field_build_va(refer, from, fmt, ap);
va_end(ap);
return 0;
}
int ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ast_string_field_build_va(refer, refer_to, fmt, ap);
va_end(ap);
return 0;
}
int ast_refer_set_to_self(struct ast_refer *refer, int val)
{
refer->to_self = val;
return 0;
}
int ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ast_string_field_build_va(refer, tech, fmt, ap);
va_end(ap);
return 0;
}
int ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ast_string_field_build_va(refer, endpoint, fmt, ap);
va_end(ap);
return 0;
}
const char *ast_refer_get_refer_to(const struct ast_refer *refer)
{
return refer->refer_to;
}
const char *ast_refer_get_from(const struct ast_refer *refer)
{
return refer->from;
}
const char *ast_refer_get_to(const struct ast_refer *refer)
{
return refer->to;
}
int ast_refer_get_to_self(const struct ast_refer *refer)
{
return refer->to_self;
}
const char *ast_refer_get_tech(const struct ast_refer *refer)
{
return refer->tech;
}
const char *ast_refer_get_endpoint(const struct ast_refer *refer)
{
return refer->endpoint;
}
static struct refer_data *refer_data_new(const char *name)
{
struct refer_data *data;
int name_len = strlen(name) + 1;
if ((data = ao2_alloc_options(name_len + sizeof(*data), refer_data_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
data->name = data->stuff;
strcpy(data->name, name);
}
return data;
}
static struct refer_data *refer_data_find(struct ao2_container *vars, const char *name)
{
return ao2_find(vars, name, OBJ_SEARCH_KEY);
}
char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name)
{
struct refer_data *data;
char *val = NULL;
if (!(data = ao2_find(refer->vars, name, OBJ_SEARCH_KEY | OBJ_UNLINK))) {
return NULL;
}
val = ast_strdup(data->value);
ao2_ref(data, -1);
return val;
}
static int refer_set_var_full(struct ast_refer *refer, const char *name, const char *value)
{
struct refer_data *data;
if (!(data = refer_data_find(refer->vars, name))) {
if (ast_strlen_zero(value)) {
return 0;
}
if (!(data = refer_data_new(name))) {
return -1;
};
data->value = ast_strdup(value);
ao2_link(refer->vars, data);
} else {
if (ast_strlen_zero(value)) {
ao2_unlink(refer->vars, data);
} else {
ast_free(data->value);
data->value = ast_strdup(value);
}
}
ao2_ref(data, -1);
return 0;
}
int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value)
{
return refer_set_var_full(refer, name, value);
}
const char *ast_refer_get_var(struct ast_refer *refer, const char *name)
{
struct refer_data *data;
const char *val = NULL;
if (!(data = refer_data_find(refer->vars, name))) {
return NULL;
}
val = data->value;
ao2_ref(data, -1);
return val;
}
struct ast_refer_var_iterator {
struct ao2_iterator iter;
struct refer_data *current_used;
};
struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer)
{
struct ast_refer_var_iterator *iter;
iter = ast_calloc(1, sizeof(*iter));
if (!iter) {
return NULL;
}
iter->iter = ao2_iterator_init(refer->vars, 0);
return iter;
}
int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value)
{
struct refer_data *data;
if (!iter) {
return 0;
}
data = ao2_iterator_next(&iter->iter);
if (!data) {
return 0;
}
*name = data->name;
*value = data->value;
iter->current_used = data;
return 1;
}
void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter)
{
ao2_cleanup(iter->current_used);
iter->current_used = NULL;
}
void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter)
{
if (iter) {
ao2_iterator_destroy(&iter->iter);
ast_refer_var_unref_current(iter);
ast_free(iter);
}
}
/*!
* \internal \brief Find a \c ast_refer_tech by its technology name
*
* \param tech_name The name of the refer technology
*
* \note \c refer_techs should be locked via \c refer_techs_lock prior to
* calling this function
*
* \retval NULL if no \ref ast_refer_tech has been registered
* \return \ref ast_refer_tech if registered
*/
static const struct ast_refer_tech *refer_find_by_tech_name(const char *tech_name)
{
const struct ast_refer_tech *current;
int i;
for (i = 0; i < AST_VECTOR_SIZE(&refer_techs); i++) {
current = AST_VECTOR_GET(&refer_techs, i);
if (!strcmp(current->name, tech_name)) {
return current;
}
}
return NULL;
}
int ast_refer_send(struct ast_refer *refer)
{
char *tech_name = NULL;
const struct ast_refer_tech *refer_tech;
int res = -1;
if (ast_strlen_zero(refer->to)) {
ao2_ref(refer, -1);
return -1;
}
tech_name = ast_strdupa(refer->to);
tech_name = strsep(&tech_name, ":");
ast_rwlock_rdlock(&refer_techs_lock);
refer_tech = refer_find_by_tech_name(tech_name);
if (!refer_tech) {
ast_log(LOG_ERROR, "Unknown refer tech: %s\n", tech_name);
ast_rwlock_unlock(&refer_techs_lock);
ao2_ref(refer, -1);
return -1;
}
ao2_lock(refer);
res = refer_tech->refer_send(refer);
ao2_unlock(refer);
ast_rwlock_unlock(&refer_techs_lock);
ao2_ref(refer, -1);
return res;
}
int ast_refer_tech_register(const struct ast_refer_tech *tech)
{
const struct ast_refer_tech *match;
ast_rwlock_wrlock(&refer_techs_lock);
match = refer_find_by_tech_name(tech->name);
if (match) {
ast_log(LOG_ERROR, "Refer technology already registered for '%s'\n",
tech->name);
ast_rwlock_unlock(&refer_techs_lock);
return -1;
}
if (AST_VECTOR_APPEND(&refer_techs, tech)) {
ast_log(LOG_ERROR, "Failed to register refer technology for '%s'\n",
tech->name);
ast_rwlock_unlock(&refer_techs_lock);
return -1;
}
ast_verb(5, "Refer technology '%s' registered.\n", tech->name);
ast_rwlock_unlock(&refer_techs_lock);
return 0;
}
/*!
* \brief Comparison callback for \c ast_refer_tech vector removal
*
* \param vec_elem The element in the vector being compared
* \param srch The element being looked up
*
* \retval non-zero The items are equal
* \retval 0 The items are not equal
*/
static int refer_tech_cmp(const struct ast_refer_tech *vec_elem, const struct ast_refer_tech *srch)
{
if (!vec_elem->name || !srch->name) {
return (vec_elem->name == srch->name) ? 1 : 0;
}
return !strcmp(vec_elem->name, srch->name);
}
int ast_refer_tech_unregister(const struct ast_refer_tech *tech)
{
int match;
ast_rwlock_wrlock(&refer_techs_lock);
match = AST_VECTOR_REMOVE_CMP_UNORDERED(&refer_techs, tech, refer_tech_cmp,
AST_VECTOR_ELEM_CLEANUP_NOOP);
ast_rwlock_unlock(&refer_techs_lock);
if (match) {
ast_log(LOG_ERROR, "No '%s' refer technology found.\n", tech->name);
return -1;
}
ast_verb(5, "Refer technology '%s' unregistered.\n", tech->name);
return 0;
}
/*!
* \internal
* \brief Clean up other resources on Asterisk shutdown
*/
static void refer_shutdown(void)
{
AST_VECTOR_FREE(&refer_techs);
ast_rwlock_destroy(&refer_techs_lock);
}
/*!
* \internal
* \brief Initialize stuff during Asterisk startup.
*
* Cleanup isn't a big deal in this function. If we return non-zero,
* Asterisk is going to exit.
*
* \retval 0 success
* \retval non-zero failure
*/
int ast_refer_init(void)
{
ast_rwlock_init(&refer_techs_lock);
if (AST_VECTOR_INIT(&refer_techs, 8)) {
return -1;
}
ast_register_cleanup(refer_shutdown);
return 0;
}