core/ari/pjsip: Add refer mechanism

This change adds support for refers that are not session based. It
includes a refer implementation for the PJSIP technology which results
in out-of-dialog REFERs being sent to a PJSIP endpoint. These can be
triggered using the new ARI endpoint `/endpoints/refer`.

Resolves: #71

UserNote: There is a new ARI endpoint `/endpoints/refer` for referring
an endpoint to some URI or endpoint.
This commit is contained in:
Maximilian Fridrich 2023-05-10 15:53:33 +02:00 committed by asterisk-org-access-app[bot]
parent d16046e41f
commit 51a7b18038
13 changed files with 2815 additions and 736 deletions

View File

@ -57,6 +57,7 @@ int ast_msg_init(void); /*!< Provided by message.c */
void ast_msg_shutdown(void); /*!< Provided by message.c */
int aco_init(void); /*!< Provided by config_options.c */
int dns_core_init(void); /*!< Provided by dns_core.c */
int ast_refer_init(void); /*!< Provided by refer.c */
/*!
* \brief Initialize malloc debug phase 1.

325
include/asterisk/refer.h Normal file
View File

@ -0,0 +1,325 @@
/*
* 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>
*
* The purpose of this API is to provide support for refers that
* are not session based. The refers are passed into the Asterisk core
* to be routed through the dialplan or another interface and potentially
* sent back out through a refer technology that has been registered
* through this API.
*/
#ifndef __AST_REFER_H__
#define __AST_REFER_H__
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*!
* \brief A refer structure.
*
* This is an opaque type that represents a refer.
*/
struct ast_refer;
/*!
* \brief A refer technology
*
* A refer technology is capable of transmitting text refers.
*/
struct ast_refer_tech {
/*!
* \brief Name of this refer technology
*
* This is the name that comes at the beginning of a URI for refers
* that should be sent to this refer technology implementation.
* For example, refers sent to "pjsip:m.fridrich@commend.com" would be
* passed to the ast_refer_tech with a name of "pjsip".
*/
const char * const name;
/*!
* \brief Send a refer.
*
* \param refer The refer to send
*
* The fields of the ast_refer are guaranteed not to change during the
* duration of this function call.
*
* \retval 0 success
* \retval non-zero failure
*/
int (* const refer_send)(const struct ast_refer *refer);
};
/*!
* \brief Register a refer technology
*
* \retval 0 success
* \retval non-zero failure
*/
int ast_refer_tech_register(const struct ast_refer_tech *tech);
/*!
* \brief Unregister a refer technology.
*
* \retval 0 success
* \retval non-zero failure
*/
int ast_refer_tech_unregister(const struct ast_refer_tech *tech);
/*!
* \brief Allocate a refer.
*
* Allocate a refer for the purposes of passing it into the Asterisk core
* to be routed through the dialplan. This refer must be destroyed using
* ast_refer_destroy().
*
* \return A refer object. This function will return NULL if an allocation
* error occurs.
*/
struct ast_refer *ast_refer_alloc(void);
/*!
* \brief Destroy an ast_refer
*
* \retval NULL always.
*/
struct ast_refer *ast_refer_destroy(struct ast_refer *refer);
/*!
* \brief Bump a refer's ref count
*/
struct ast_refer *ast_refer_ref(struct ast_refer *refer);
/*!
* \brief Set the 'to' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'from' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'refer_to' URI of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the 'to_self' value of a refer
*
* \retval 0 success
* \retval -1 failure
*/
int ast_refer_set_to_self(struct ast_refer *refer, int val);
/*!
* \brief Set the technology associated with this refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set the technology's endpoint associated with this refer
*
* \retval 0 success
* \retval -1 failure
*/
int __attribute__((format(printf, 2, 3)))
ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...);
/*!
* \brief Set a variable on the refer being sent to a refer tech directly.
* \note Setting a variable that already exists overwrites the existing variable value
*
* \param refer
* \param name Name of variable to set
* \param value Value of variable to set
*
* \retval 0 success
* \retval -1 failure
*/
int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value);
/*!
* \brief Get the specified variable on the refer and unlink it from the container of variables
* \note The return value must be freed by the caller.
*
* \param refer
* \param name Name of variable to get
*
* \return The value associated with variable "name". NULL if variable not found.
*/
char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name);
/*!
* \brief Get the specified variable on the refer
* \note The return value is valid only as long as the ast_refer is valid. Hold a reference
* to the refer if you plan on storing the return value. It is possible to re-set the
* same refer var name (with ast_refer_set_var_outbound passing the variable name)
* while holding a pointer to the result of this function.
*
* \param refer
* \param name Name of variable to get
*
* \return The value associated with variable "name". NULL if variable not found.
*/
const char *ast_refer_get_var(struct ast_refer *refer, const char *name);
/*!
* \brief Get the "refer-to" value of a refer.
* \note The return value is valid only as long as the ast_refer is valid. Hold a reference
* to the refer if you plan on storing the return value.
*
* \param refer The refer to get the "refer-to" value from
*
* \return The "refer-to" value of the refer, encoded in UTF-8.
*/
const char *ast_refer_get_refer_to(const struct ast_refer *refer);
/*!
* \brief Retrieve the source of this refer
*
* \param refer The refer to get the soure from
*
* \return The source of the refer
* \retval NULL or empty string if the refer has no source
*/
const char *ast_refer_get_from(const struct ast_refer *refer);
/*!
* \brief Retrieve the destination of this refer
*
* \param refer The refer to get the destination from
*
* \return The destination of the refer
* \retval NULL or empty string if the refer has no destination
*/
const char *ast_refer_get_to(const struct ast_refer *refer);
/*!
* \brief Retrieve the "to_self" value of this refer
*
* \param refer The refer to get the destination from
*
* \return The to_self value of the refer
*/
int ast_refer_get_to_self(const struct ast_refer *refer);
/*!
* \brief Retrieve the technology associated with this refer
*
* \param refer The refer to get the technology from
*
* \return The technology of the refer
* \retval NULL or empty string if the refer has no associated technology
*/
const char *ast_refer_get_tech(const struct ast_refer *refer);
/*!
* \brief Retrieve the endpoint associated with this refer
*
* \param refer The refer to get the endpoint from
*
* \return The endpoint associated with the refer
* \retval NULL or empty string if the refer has no associated endpoint
*/
const char *ast_refer_get_endpoint(const struct ast_refer *refer);
/*!
* \brief Send a refer directly to an endpoint.
*
* Regardless of the return value of this function, this function will take
* care of ensuring that the refer object is properly destroyed when needed.
*
* \retval 0 refer successfully queued to be sent out
* \retval non-zero failure, refer not get sent out.
*/
int ast_refer_send(struct ast_refer *refer);
/*!
* \brief Opaque iterator for refer variables
*/
struct ast_refer_var_iterator;
/*!
* \brief Create a new refer variable iterator
* \param refer A refer whose variables are to be iterated over
*
* \return An opaque pointer to the new iterator
*/
struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer);
/*!
* \brief Get the next variable name and value
*
* \param iter An iterator created with ast_refer_var_iterator_init
* \param name A pointer to the name result pointer
* \param value A pointer to the value result pointer
*
* \note The refcount to iter->current_used must be decremented by the caller
* by calling ast_refer_var_unref_current.
*
* \retval 0 No more entries
* \retval 1 Valid entry
*/
int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value);
/*!
* \brief Destroy a refer variable iterator
* \param iter Iterator to be destroyed
*/
void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter);
/*!
* \brief Unref a refer var from inside an iterator loop
*/
void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter);
/*!
* @}
*/
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* __AST_REFER_H__ */

View File

@ -339,6 +339,20 @@ struct ast_sip_nat_hook {
void (*outgoing_external_message)(struct pjsip_tx_data *tdata, struct ast_sip_transport *transport);
};
/*! \brief Structure which contains information about a transport */
struct ast_sip_request_transport_details {
/*! \brief Type of transport */
enum ast_transport type;
/*! \brief Potential pointer to the transport itself, if UDP */
pjsip_transport *transport;
/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
pjsip_tpfactory *factory;
/*! \brief Local address for transport */
pj_str_t local_address;
/*! \brief Local port for transport */
int local_port;
};
/*!
* \brief The kind of security negotiation
*/
@ -3506,18 +3520,65 @@ struct ast_threadpool *ast_sip_threadpool(void);
* \brief Retrieve transport state
* \since 13.7.1
*
* @param transport_id
* @returns transport_state
* \param transport_id
* \retval transport_state
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id);
/*!
* \brief Return the SIP URI of the Contact header
*
* \param tdata
* \retval Pointer to SIP URI of Contact
* \retval NULL if Contact header not found or not a SIP(S) URI
*
* \note Do not free the returned object.
*/
pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata);
/*!
* \brief Returns the transport state currently in use based on request transport details
*
* \param details
* \retval transport_state
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details);
/*!
* \brief Sets request transport details based on tdata
*
* \param details pre-allocated request transport details to set
* \param tdata
* \param use_ipv6 if non-zero, ipv6 transports will be considered
* \retval 0 success
* \retval -1 failure
*/
int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata, int use_ipv6);
/*!
* \brief Replace domain and port of SIP URI to point to (external) signaling address of this Asterisk instance
*
* \param uri
* \param tdata
*
* \retval 0 success
* \retval -1 failure
*
* \note Uses domain and port in Contact header if it exists, otherwise the local URI of the dialog is used if the
* message is sent within the context of a dialog. Further, NAT settings are considered - i.e. if the target
* is not in the localnet, the external_signaling_address and port are used.
*/
int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata);
/*!
* \brief Retrieves all transport states
* \since 13.7.1
*
* @returns ao2_container
* \retval ao2_container
*
* \note ao2_cleanup(...) or ao2_ref(..., -1) must be called on the returned object
*/
@ -3647,6 +3708,105 @@ int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id
void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
const struct ast_party_id *id);
/*!
* \brief Retrieves an endpoint and URI from the "to" string.
*
* This URI is used as the Request URI.
*
* Expects the given 'to' to be in one of the following formats:
* Why we allow so many is a mystery.
*
* Basic:
*
* endpoint : We'll get URI from the default aor/contact
* endpoint/aor : We'll get the URI from the specific aor/contact
* endpoint@domain : We toss the domain part and just use the endpoint
*
* These all use the endpoint and specified URI:
* \verbatim
endpoint/<sip[s]:host>
endpoint/<sip[s]:user@host>
endpoint/"Bob" <sip[s]:host>
endpoint/"Bob" <sip[s]:user@host>
endpoint/sip[s]:host
endpoint/sip[s]:user@host
endpoint/host
endpoint/user@host
\endverbatim
*
* These all use the default endpoint and specified URI:
* \verbatim
<sip[s]:host>
<sip[s]:user@host>
"Bob" <sip[s]:host>
"Bob" <sip[s]:user@host>
sip[s]:host
sip[s]:user@host
\endverbatim
*
* These use the default endpoint and specified host:
* \verbatim
host
user@host
\endverbatim
*
* This form is similar to a dialstring:
* \verbatim
PJSIP/user@endpoint
\endverbatim
*
* In this case, the user will be added to the endpoint contact's URI.
* If the contact URI already has a user, it will be replaced.
*
* The ones that have the sip[s] scheme are the easiest to parse.
* The rest all have some issue.
*
* endpoint vs host : We have to test for endpoint first
* endpoint/aor vs endpoint/host : We have to test for aor first
* What if there's an aor with the same
* name as the host?
* endpoint@domain vs user@host : We have to test for endpoint first.
* What if there's an endpoint with the
* same name as the user?
*
* \param to 'To' field with possible endpoint
* \param get_default_outbound If nonzero, try to retrieve the default
* outbound endpoint if no endpoint was found.
* Otherwise, return NULL if no endpoint was found.
* \param uri Pointer to a char* which will be set to the URI.
* Always must be ast_free'd by the caller - even if the return value is NULL!
*
* \note The logic below could probably be condensed but then it wouldn't be
* as clear.
*/
struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri);
/*!
* \brief Replace the To URI in the tdata with the supplied one
*
* \param tdata the outbound message data structure
* \param to URI to replace the To URI with. Must be a valid SIP URI.
*
* \retval 0: success, -1: failure
*/
int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to);
/*!
* \brief Overwrite fields in the outbound 'From' header
*
* The outbound 'From' header is created/added in ast_sip_create_request with
* default data. If available that data may be info specified in the 'from_user'
* and 'from_domain' options found on the endpoint. That information will be
* overwritten with data in the given 'from' parameter.
*
* \param tdata the outbound message data structure
* \param from info to copy into the header.
* Can be either a SIP URI, or in the format user[@domain]
*
* \retval 0: success, -1: failure
*/
int ast_sip_update_from(pjsip_tx_data *tdata, char *from);
/*!
* \brief Retrieve the unidentified request security event thresholds
* \since 13.8.0

View File

@ -4256,6 +4256,7 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
check_init(load_pbx_app(), "PBX Application Support");
check_init(load_pbx_hangup_handler(), "PBX Hangup Handler Support");
check_init(ast_local_init(), "Local Proxy Channel Driver");
check_init(ast_refer_init(), "Refer API");
/* We should avoid most config loads before this point as they can't use realtime. */
check_init(load_modules(), "Module");

537
main/refer.c Normal file
View File

@ -0,0 +1,537 @@
/*
* 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);
ast_free(data);
}
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 {
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(3, "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(2, "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;
}

View File

@ -33,6 +33,7 @@
#include "asterisk/stasis_endpoints.h"
#include "asterisk/channel.h"
#include "asterisk/message.h"
#include "asterisk/refer.h"
void ast_ari_endpoints_list(struct ast_variable *headers,
struct ast_ari_endpoints_list_args *args,
@ -307,3 +308,135 @@ void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers,
send_message(msg_to, args->from, args->body, variables, response);
ast_variables_destroy(variables);
}
static void send_refer(const char *to, const char *from, const char *refer_to, int to_self, struct ast_variable *variables, struct ast_ari_response *response)
{
struct ast_variable *current;
struct ast_refer *refer;
int res = 0;
if (ast_strlen_zero(to)) {
ast_ari_response_error(response, 400, "Bad Request",
"To must be specified");
return;
}
refer = ast_refer_alloc();
if (!refer) {
ast_ari_response_alloc_failed(response);
return;
}
ast_refer_set_to(refer, "%s", to);
ast_refer_set_to_self(refer, to_self);
if (!ast_strlen_zero(from)) {
ast_refer_set_from(refer, "%s", from);
}
if (!ast_strlen_zero(refer_to)) {
ast_refer_set_refer_to(refer, "%s", refer_to);
}
for (current = variables; current; current = current->next) {
res |= ast_refer_set_var_outbound(refer, current->name, current->value);
}
if (res) {
ast_ari_response_alloc_failed(response);
ast_refer_destroy(refer);
return;
}
if (ast_refer_send(refer)) {
ast_ari_response_error(response, 404, "Not Found",
"Endpoint not found");
return;
}
response->message = ast_json_null();
response->response_code = 202;
response->response_text = "Accepted";
}
static int parse_refer_json(struct ast_json *body,
struct ast_ari_response *response,
struct ast_variable **variables)
{
const char *known_variables[] = { "display_name" };
const char *value;
struct ast_variable *new_var;
struct ast_json *json_variable;
int err = 0;
int i;
if (!body) {
return 0;
}
json_variable = ast_json_object_get(body, "variables");
if (json_variable) {
err = json_to_ast_variables(response, json_variable, variables);
if (err) {
return err;
}
}
for (i = 0; i < sizeof(known_variables) / sizeof(*known_variables); ++i) {
json_variable = ast_json_object_get(body, known_variables[i]);
if (json_variable && ast_json_typeof(json_variable) == AST_JSON_STRING) {
value = ast_json_string_get(json_variable);
new_var = ast_variable_new(known_variables[i], value, "");
if (new_var) {
ast_variable_list_append(variables, new_var);
}
}
}
return err;
}
void ast_ari_endpoints_refer(struct ast_variable *headers,
struct ast_ari_endpoints_refer_args *args,
struct ast_ari_response *response)
{
struct ast_variable *variables = NULL;
ast_ari_endpoints_refer_parse_body(args->variables, args);
if (parse_refer_json(args->variables, response, &variables)) {
return;
}
send_refer(args->to, args->from, args->refer_to, args->to_self, variables, response);
ast_variables_destroy(variables);
}
void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers,
struct ast_ari_endpoints_refer_to_endpoint_args *args,
struct ast_ari_response *response)
{
struct ast_variable *variables = NULL;
struct ast_endpoint_snapshot *snapshot;
char to[128];
char *tech = ast_strdupa(args->tech);
/* Really, we just want to know if this thing exists */
snapshot = ast_endpoint_latest_snapshot(args->tech, args->resource);
if (!snapshot) {
ast_ari_response_error(response, 404, "Not Found",
"Endpoint not found");
return;
}
ao2_ref(snapshot, -1);
ast_ari_endpoints_refer_to_endpoint_parse_body(args->variables, args);
if (parse_refer_json(args->variables, response, &variables)) {
return;
}
snprintf(to, sizeof(to), "%s:%s", ast_str_to_lower(tech), args->resource);
send_refer(to, args->from, args->refer_to, args->to_self, variables, response);
ast_variables_destroy(variables);
}

View File

@ -58,6 +58,7 @@ struct ast_ari_endpoints_send_message_args {
const char *from;
/*! The body of the message */
const char *body;
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
struct ast_json *variables;
};
/*!
@ -79,6 +80,38 @@ int ast_ari_endpoints_send_message_parse_body(
* \param[out] response HTTP response
*/
void ast_ari_endpoints_send_message(struct ast_variable *headers, struct ast_ari_endpoints_send_message_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_endpoints_refer() */
struct ast_ari_endpoints_refer_args {
/*! The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip. */
const char *to;
/*! The endpoint resource or technology specific identity to refer from. */
const char *from;
/*! The endpoint resource or technology specific URI to refer to. */
const char *refer_to;
/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
int to_self;
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The "display_name" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI. */
struct ast_json *variables;
};
/*!
* \brief Body parsing function for /endpoints/refer.
* \param body The JSON body from which to parse parameters.
* \param[out] args The args structure to parse into.
* \retval zero on success
* \retval non-zero on failure
*/
int ast_ari_endpoints_refer_parse_body(
struct ast_json *body,
struct ast_ari_endpoints_refer_args *args);
/*!
* \brief Refer an endpoint or technology URI to some technology URI or endpoint.
*
* \param headers HTTP headers
* \param args Swagger parameters
* \param[out] response HTTP response
*/
void ast_ari_endpoints_refer(struct ast_variable *headers, struct ast_ari_endpoints_refer_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_endpoints_list_by_tech() */
struct ast_ari_endpoints_list_by_tech_args {
/*! Technology of the endpoints (pjsip,iax2,...) */
@ -117,6 +150,7 @@ struct ast_ari_endpoints_send_message_to_endpoint_args {
const char *from;
/*! The body of the message */
const char *body;
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
struct ast_json *variables;
};
/*!
@ -138,5 +172,39 @@ int ast_ari_endpoints_send_message_to_endpoint_parse_body(
* \param[out] response HTTP response
*/
void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_send_message_to_endpoint_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_endpoints_refer_to_endpoint() */
struct ast_ari_endpoints_refer_to_endpoint_args {
/*! Technology of the endpoint */
const char *tech;
/*! ID of the endpoint */
const char *resource;
/*! The endpoint resource or technology specific identity to refer from. */
const char *from;
/*! The endpoint resource or technology specific URI to refer to. */
const char *refer_to;
/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
int to_self;
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers, */
struct ast_json *variables;
};
/*!
* \brief Body parsing function for /endpoints/{tech}/{resource}/refer.
* \param body The JSON body from which to parse parameters.
* \param[out] args The args structure to parse into.
* \retval zero on success
* \retval non-zero on failure
*/
int ast_ari_endpoints_refer_to_endpoint_parse_body(
struct ast_json *body,
struct ast_ari_endpoints_refer_to_endpoint_args *args);
/*!
* \brief Refer an endpoint or technology URI to some technology URI or endpoint.
*
* \param headers HTTP headers
* \param args Swagger parameters
* \param[out] response HTTP response
*/
void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_refer_to_endpoint_args *args, struct ast_ari_response *response);
#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */

View File

@ -188,6 +188,102 @@ static void ast_ari_endpoints_send_message_cb(
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
int ast_ari_endpoints_refer_parse_body(
struct ast_json *body,
struct ast_ari_endpoints_refer_args *args)
{
struct ast_json *field;
/* Parse query parameters out of it */
field = ast_json_object_get(body, "to");
if (field) {
args->to = ast_json_string_get(field);
}
field = ast_json_object_get(body, "from");
if (field) {
args->from = ast_json_string_get(field);
}
field = ast_json_object_get(body, "refer_to");
if (field) {
args->refer_to = ast_json_string_get(field);
}
field = ast_json_object_get(body, "to_self");
if (field) {
args->to_self = ast_json_is_true(field);
}
return 0;
}
/*!
* \brief Parameter parsing callback for /endpoints/refer.
* \param ser TCP/TLS session object
* \param get_params GET parameters in the HTTP request.
* \param path_vars Path variables extracted from the request.
* \param headers HTTP headers.
* \param body
* \param[out] response Response to the HTTP request.
*/
static void ast_ari_endpoints_refer_cb(
struct ast_tcptls_session_instance *ser,
struct ast_variable *get_params, struct ast_variable *path_vars,
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
{
struct ast_ari_endpoints_refer_args args = {};
struct ast_variable *i;
#if defined(AST_DEVMODE)
int is_valid;
int code;
#endif /* AST_DEVMODE */
for (i = get_params; i; i = i->next) {
if (strcmp(i->name, "to") == 0) {
args.to = (i->value);
} else
if (strcmp(i->name, "from") == 0) {
args.from = (i->value);
} else
if (strcmp(i->name, "refer_to") == 0) {
args.refer_to = (i->value);
} else
if (strcmp(i->name, "to_self") == 0) {
args.to_self = ast_true(i->value);
} else
{}
}
args.variables = body;
ast_ari_endpoints_refer(headers, &args, response);
#if defined(AST_DEVMODE)
code = response->response_code;
switch (code) {
case 0: /* Implementation is still a stub, or the code wasn't set */
is_valid = response->message == NULL;
break;
case 500: /* Internal Server Error */
case 501: /* Not Implemented */
case 400: /* Invalid parameters for referring. */
case 404: /* Endpoint not found */
is_valid = 1;
break;
default:
if (200 <= code && code <= 299) {
is_valid = ast_ari_validate_void(
response->message);
} else {
ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/refer\n", code);
is_valid = 0;
}
}
if (!is_valid) {
ast_log(LOG_ERROR, "Response validation failed for /endpoints/refer\n");
ast_ari_response_error(response, 500,
"Internal Server Error", "Response validation failed");
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
@ -403,6 +499,104 @@ static void ast_ari_endpoints_send_message_to_endpoint_cb(
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
int ast_ari_endpoints_refer_to_endpoint_parse_body(
struct ast_json *body,
struct ast_ari_endpoints_refer_to_endpoint_args *args)
{
struct ast_json *field;
/* Parse query parameters out of it */
field = ast_json_object_get(body, "from");
if (field) {
args->from = ast_json_string_get(field);
}
field = ast_json_object_get(body, "refer_to");
if (field) {
args->refer_to = ast_json_string_get(field);
}
field = ast_json_object_get(body, "to_self");
if (field) {
args->to_self = ast_json_is_true(field);
}
return 0;
}
/*!
* \brief Parameter parsing callback for /endpoints/{tech}/{resource}/refer.
* \param ser TCP/TLS session object
* \param get_params GET parameters in the HTTP request.
* \param path_vars Path variables extracted from the request.
* \param headers HTTP headers.
* \param body
* \param[out] response Response to the HTTP request.
*/
static void ast_ari_endpoints_refer_to_endpoint_cb(
struct ast_tcptls_session_instance *ser,
struct ast_variable *get_params, struct ast_variable *path_vars,
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
{
struct ast_ari_endpoints_refer_to_endpoint_args args = {};
struct ast_variable *i;
#if defined(AST_DEVMODE)
int is_valid;
int code;
#endif /* AST_DEVMODE */
for (i = get_params; i; i = i->next) {
if (strcmp(i->name, "from") == 0) {
args.from = (i->value);
} else
if (strcmp(i->name, "refer_to") == 0) {
args.refer_to = (i->value);
} else
if (strcmp(i->name, "to_self") == 0) {
args.to_self = ast_true(i->value);
} else
{}
}
for (i = path_vars; i; i = i->next) {
if (strcmp(i->name, "tech") == 0) {
args.tech = (i->value);
} else
if (strcmp(i->name, "resource") == 0) {
args.resource = (i->value);
} else
{}
}
args.variables = body;
ast_ari_endpoints_refer_to_endpoint(headers, &args, response);
#if defined(AST_DEVMODE)
code = response->response_code;
switch (code) {
case 0: /* Implementation is still a stub, or the code wasn't set */
is_valid = response->message == NULL;
break;
case 500: /* Internal Server Error */
case 501: /* Not Implemented */
case 400: /* Invalid parameters for referring. */
case 404: /* Endpoint not found */
is_valid = 1;
break;
default:
if (200 <= code && code <= 299) {
is_valid = ast_ari_validate_void(
response->message);
} else {
ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}/{resource}/refer\n", code);
is_valid = 0;
}
}
if (!is_valid) {
ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}/{resource}/refer\n");
ast_ari_response_error(response, 500,
"Internal Server Error", "Response validation failed");
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
@ -417,6 +611,15 @@ static struct stasis_rest_handlers endpoints_sendMessage = {
.children = { }
};
/*! \brief REST handler for /api-docs/endpoints.json */
static struct stasis_rest_handlers endpoints_refer = {
.path_segment = "refer",
.callbacks = {
[AST_HTTP_POST] = ast_ari_endpoints_refer_cb,
},
.num_children = 0,
.children = { }
};
/*! \brief REST handler for /api-docs/endpoints.json */
static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
.path_segment = "sendMessage",
.callbacks = {
@ -426,14 +629,23 @@ static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
.children = { }
};
/*! \brief REST handler for /api-docs/endpoints.json */
static struct stasis_rest_handlers endpoints_tech_resource_refer = {
.path_segment = "refer",
.callbacks = {
[AST_HTTP_POST] = ast_ari_endpoints_refer_to_endpoint_cb,
},
.num_children = 0,
.children = { }
};
/*! \brief REST handler for /api-docs/endpoints.json */
static struct stasis_rest_handlers endpoints_tech_resource = {
.path_segment = "resource",
.is_wildcard = 1,
.callbacks = {
[AST_HTTP_GET] = ast_ari_endpoints_get_cb,
},
.num_children = 1,
.children = { &endpoints_tech_resource_sendMessage, }
.num_children = 2,
.children = { &endpoints_tech_resource_sendMessage,&endpoints_tech_resource_refer, }
};
/*! \brief REST handler for /api-docs/endpoints.json */
static struct stasis_rest_handlers endpoints_tech = {
@ -451,8 +663,8 @@ static struct stasis_rest_handlers endpoints = {
.callbacks = {
[AST_HTTP_GET] = ast_ari_endpoints_list_cb,
},
.num_children = 2,
.children = { &endpoints_sendMessage,&endpoints_tech, }
.num_children = 3,
.children = { &endpoints_sendMessage,&endpoints_refer,&endpoints_tech, }
};
static int unload_module(void)

View File

@ -27,6 +27,8 @@
#include <pjmedia/errno.h>
#include "asterisk/res_pjsip.h"
#include "asterisk/strings.h"
#include "pjsip/sip_parser.h"
#include "res_pjsip/include/res_pjsip_private.h"
#include "asterisk/linkedlists.h"
#include "asterisk/logger.h"
@ -48,6 +50,7 @@
#include "asterisk/res_pjsip_presence_xml.h"
#include "asterisk/res_pjproject.h"
#include "asterisk/utf8.h"
#include "asterisk/acl.h"
/*** MODULEINFO
<depend>pjproject</depend>
@ -558,6 +561,140 @@ int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint
return result;
}
pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata)
{
pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
return NULL;
}
return pjsip_uri_get_uri(contact->uri);
}
/*! \brief Callback function for finding the transport the request is going out on */
static int find_transport_state_in_use(void *obj, void *arg, int flags)
{
struct ast_sip_transport_state *transport_state = obj;
struct ast_sip_request_transport_details *details = arg;
/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
* to compare based on that we make sure that the type is the same and the source IP address/port are the same
*/
if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
(details->factory && details->factory == transport_state->factory) ||
((details->type == transport_state->type) && (transport_state->factory) &&
!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
transport_state->factory->addr_name.port == details->local_port))) {
return CMP_MATCH | CMP_STOP;
}
return 0;
}
struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details) {
RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
if (!(transport_states = ast_sip_get_transport_states())) {
return NULL;
}
return ao2_callback(transport_states, 0, find_transport_state_in_use, details);
}
int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata) {
RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
struct ast_sip_request_transport_details details;
pjsip_sip_uri *tmp_uri;
pjsip_dialog *dlg;
struct ast_sockaddr addr = { { 0, } };
if ((tmp_uri = ast_sip_get_contact_sip_uri(tdata))) {
pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
uri->port = tmp_uri->port;
} else if ((dlg = pjsip_tdata_get_dlg(tdata))
&& (tmp_uri = pjsip_uri_get_uri(dlg->local.info->uri))
&& (PJSIP_URI_SCHEME_IS_SIP(tmp_uri) || PJSIP_URI_SCHEME_IS_SIPS(tmp_uri))) {
pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
uri->port = tmp_uri->port;
}
if (ast_sip_set_request_transport_details(&details, tdata, 1)
|| !(transport_state = ast_sip_find_transport_state_in_use(&details))
|| !(transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))) {
return 0;
}
if (transport_state->localnet) {
ast_sockaddr_parse(&addr, tdata->tp_info.dst_name, PARSE_PORT_FORBID);
ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
if (ast_sip_transport_is_local(transport_state, &addr)) {
return 0;
}
}
if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
}
if (transport->external_signaling_port) {
uri->port = transport->external_signaling_port;
}
return 0;
}
int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata,
int use_ipv6) {
pjsip_sip_uri *uri;
pjsip_via_hdr *via;
long transport_type;
if (!details || !tdata) {
return -1;
}
/* If IPv6 should be considered, un-set Bit 7 to make TCP6 equal to TCP and TLS6 equal to TLS */
transport_type = use_ipv6 ? tdata->tp_info.transport->key.type & ~(PJSIP_TRANSPORT_IPV6)
: tdata->tp_info.transport->key.type;
if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
details->transport = tdata->tp_sel.u.transport;
} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
details->factory = tdata->tp_sel.u.listener;
} else if (transport_type == PJSIP_TRANSPORT_UDP || transport_type == PJSIP_TRANSPORT_UDP6) {
/* Connectionless uses the same transport for all requests */
details->type = AST_TRANSPORT_UDP;
details->transport = tdata->tp_info.transport;
} else {
if (transport_type == PJSIP_TRANSPORT_TCP) {
details->type = AST_TRANSPORT_TCP;
} else if (transport_type == PJSIP_TRANSPORT_TLS) {
details->type = AST_TRANSPORT_TLS;
} else {
/* Unknown transport type, we can't map. */
return -1;
}
if ((uri = ast_sip_get_contact_sip_uri(tdata))) {
details->local_address = uri->host;
details->local_port = uri->port;
} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
details->local_address = via->sent_by.host;
details->local_port = via->sent_by.port;
} else {
return -1;
}
if (!details->local_port) {
details->local_port = (details->type == AST_TRANSPORT_TLS) ? 5061 : 5060;
}
}
return 0;
}
int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
pjsip_sip_uri *sip_uri, char *buf, size_t buf_len)
{
@ -835,7 +972,11 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
static const pj_str_t HCONTACT = { "Contact", 7 };
snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
if (!ast_begins_with(uri, "<")) {
snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
} else {
snprintf(enclosed_uri, sizeof(enclosed_uri), "%s", uri);
}
pj_cstr(&remote_uri, enclosed_uri);
pj_cstr(&target_uri, uri);
@ -1130,6 +1271,7 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
/* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
static const pjsip_method info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
static const pjsip_method message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
static const pjsip_method refer_method = {PJSIP_OTHER_METHOD, {"REFER", 5} };
static struct {
const char *method;
@ -1146,6 +1288,7 @@ static struct {
{ "PUBLISH", &pjsip_publish_method },
{ "INFO", &info_method },
{ "MESSAGE", &message_method },
{ "REFER", &refer_method },
};
static const pjsip_method *get_pjsip_method(const char *method)
@ -2727,6 +2870,575 @@ void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const s
}
}
/*!
* \brief Find a contact and insert a "user@" into its URI.
*
* \param to Original destination (for error messages only)
* \param endpoint_name Endpoint name (for error messages only)
* \param aors Command separated list of AORs
* \param user The user to insert in the contact URI
* \param uri Pointer to buffer in which to return the URI. Must be freed by caller.
*
* \return 0 Success
* \return -1 Fail
*
* \note If the contact URI found for the endpoint already has a user in
* its URI, it will be replaced by the user passed as an argument to this function.
*/
static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
const char *user, char **uri)
{
RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
pj_pool_t *pool;
pjsip_name_addr *name_addr;
pjsip_sip_uri *sip_uri;
int err = 0;
contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
if (!contact) {
ast_log(LOG_WARNING, "Dest: '%s'. Couldn't find contact for endpoint '%s'\n",
to, endpoint_name);
return -1;
}
pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri-user-insert", 128, 128);
if (!pool) {
ast_log(LOG_WARNING, "Failed to allocate ParseUri endpoint pool.\n");
return -1;
}
name_addr = (pjsip_name_addr *) pjsip_parse_uri(pool, (char*)contact->uri, strlen(contact->uri), PJSIP_PARSE_URI_AS_NAMEADDR);
if (!name_addr || (!PJSIP_URI_SCHEME_IS_SIP(name_addr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(name_addr->uri))) {
ast_log(LOG_WARNING, "Failed to parse URI '%s'\n", contact->uri);
err = -1;
goto out;
}
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact->uri);
sip_uri = pjsip_uri_get_uri(name_addr->uri);
pj_strset2(&sip_uri->user, (char*)user);
*uri = ast_malloc(PJSIP_MAX_URL_SIZE);
if (!(*uri)) {
err = -1;
goto out;
}
pjsip_uri_print(PJSIP_URI_IN_REQ_URI, name_addr, *uri, PJSIP_MAX_URL_SIZE);
out:
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
return err;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination is only a single token
*
* "destination" could be one of the following:
* \verbatim
endpoint_name
hostname
* \endverbatim
*
* \param to
* \param destination
* \param get_default_outbound If nonzero, try to retrieve the default
* outbound endpoint if no endpoint was found.
* Otherwise, return NULL if no endpoint was found.
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
* \return endpoint
*/
static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, int get_default_outbound, char **uri) {
RAII_VAR(struct ast_sip_contact*, contact, NULL, ao2_cleanup);
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
/*
* If "destination" is just one token, it could be an endpoint name
* or a hostname without a scheme.
*/
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
if (!endpoint) {
/*
* We can only assume it's a hostname.
*/
char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
if (!temp_uri) {
goto failure;
}
sprintf(temp_uri, "sip:%s", destination);
*uri = temp_uri;
if (get_default_outbound) {
endpoint = ast_sip_default_outbound_endpoint();
}
ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s'%s\n",
to, *uri, get_default_outbound ? " with default endpoint" : "");
return endpoint;
}
/*
* It's an endpoint
*/
endpoint_name = destination;
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
if (!contact) {
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find an aor/contact for it\n",
to, endpoint_name);
ao2_cleanup(endpoint);
goto failure;
}
*uri = ast_strdup(contact->uri);
if (!(*uri)) {
ao2_cleanup(endpoint);
goto failure;
}
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
to, endpoint_name, *uri);
return endpoint;
failure:
*uri = NULL;
return NULL;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination contained a '/'
*
* "to" could be one of the following:
* \verbatim
endpoint/aor
endpoint/<sip[s]:host>
endpoint/<sip[s]:user@host>
endpoint/"Bob" <sip[s]:host>
endpoint/"Bob" <sip[s]:user@host>
endpoint/sip[s]:host
endpoint/sip[s]:user@host
endpoint/host
endpoint/user@host
* \endverbatim
*
* \param to Destination
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
* \param destination, slash, atsign, scheme
* \return endpoint
*/
static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
char *slash, char *atsign, char *scheme)
{
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
struct ast_sip_contact *contact = NULL;
char *user = NULL;
char *afterslash = slash + 1;
struct ast_sip_aor *aor;
if (ast_begins_with(destination, "PJSIP/")) {
ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
/*
* This has to be the form PJSIP/user@endpoint
*/
if (!atsign || strchr(afterslash, '/')) {
/*
* If there's no "user@" or there's a slash somewhere after
* "PJSIP/" then we go no further.
*/
ast_log(LOG_WARNING,
"Dest: '%s'. Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
to);
goto failure;
}
*atsign = '\0';
user = afterslash;
endpoint_name = atsign + 1;
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
} else {
/*
* Either...
* endpoint/aor
* endpoint/uri
*/
*slash = '\0';
endpoint_name = destination;
ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
}
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
if (!endpoint) {
ast_log(LOG_WARNING, "Dest: '%s'. Didn't find endpoint with name '%s'\n",
to, endpoint_name);
goto failure;
}
if (scheme) {
/*
* If we found a scheme, then everything after the slash MUST be a URI.
* We don't need to do any further modification.
*/
*uri = ast_strdup(afterslash);
if (!(*uri)) {
goto failure;
}
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
to, endpoint_name, *uri);
return endpoint;
}
if (user) {
/*
* This has to be the form PJSIP/user@endpoint
*/
int rc;
/*
* Set the return URI to be the endpoint's contact URI with the user
* portion set to the user that was specified before the endpoint name.
*/
rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
if (rc != 0) {
/*
* insert_user_in_contact_uri prints the warning message.
*/
goto failure;
}
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
endpoint_name, *uri);
return endpoint;
}
/*
* We're now left with two possibilities...
* endpoint/aor
* endpoint/uri-without-scheme
*/
aor = ast_sip_location_retrieve_aor(afterslash);
if (!aor) {
/*
* It's probably a URI without a scheme but we don't have a way to tell
* for sure. We're going to assume it is and prepend it with a scheme.
*/
*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
if (!(*uri)) {
goto failure;
}
sprintf(*uri, "sip:%s", afterslash);
ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
to, endpoint_name, *uri);
return endpoint;
}
/*
* Only one possibility left... There was an aor name after the slash.
*/
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
to, endpoint_name, ast_sorcery_object_get_id(aor));
contact = ast_sip_location_retrieve_first_aor_contact(aor);
if (!contact) {
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact for aor '%s'\n",
to, endpoint_name, ast_sorcery_object_get_id(aor));
ao2_cleanup(aor);
goto failure;
}
*uri = ast_strdup(contact->uri);
ao2_cleanup(contact);
ao2_cleanup(aor);
if (!(*uri)) {
goto failure;
}
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
return endpoint;
failure:
ao2_cleanup(endpoint);
*uri = NULL;
return NULL;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination contained a '@' but no '/' or scheme
*
* "to" could be one of the following:
* \verbatim
<sip[s]:user@host>
"Bob" <sip[s]:user@host>
sip[s]:user@host
user@host
* \endverbatim
*
* \param to Destination
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
* \param destination, slash, atsign, scheme
* \param get_default_outbound If nonzero, try to retrieve the default
* outbound endpoint if no endpoint was found.
* Otherwise, return NULL if no endpoint was found.
* \return endpoint
*/
static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
char *slash, char *atsign, char *scheme, int get_default_outbound)
{
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
struct ast_sip_contact *contact = NULL;
char *afterat = atsign + 1;
*atsign = '\0';
endpoint_name = destination;
/* Apparently there may be ';<user_options>' after the endpoint name ??? */
AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
if (!endpoint) {
/*
* It's probably a uri with a user but without a scheme but we don't have a way to tell.
* We're going to assume it is and prepend it with a scheme.
*/
*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
if (!(*uri)) {
goto failure;
}
sprintf(*uri, "sip:%s", to);
if (get_default_outbound) {
endpoint = ast_sip_default_outbound_endpoint();
}
ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s'%s\n",
to, *uri, get_default_outbound ? " with default endpoint" : "");
return endpoint;
}
/*
* OK, it's an endpoint and a domain (which we ignore)
*/
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
if (!contact) {
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact\n",
to, endpoint_name);
goto failure;
}
*uri = ast_strdup(contact->uri);
ao2_cleanup(contact);
if (!(*uri)) {
goto failure;
}
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
to, endpoint_name, *uri, afterat);
return endpoint;
failure:
ao2_cleanup(endpoint);
*uri = NULL;
return NULL;
}
struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri)
{
char *destination;
char *slash = NULL;
char *atsign = NULL;
char *scheme = NULL;
struct ast_sip_endpoint *endpoint = NULL;
destination = ast_strdupa(to);
slash = strchr(destination, '/');
atsign = strchr(destination, '@');
scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
if (!slash && !atsign && !scheme) {
/*
* If there's only a single token, it can be either...
* endpoint
* host
*/
return handle_single_token(to, destination, get_default_outbound, uri);
}
if (slash) {
/*
* If there's a '/', then the form must be one of the following...
* PJSIP/user@endpoint
* endpoint/aor
* endpoint/uri
*/
return handle_slash(to, destination, uri, slash, atsign, scheme);
}
if (atsign && !scheme) {
/*
* If there's an '@' but no scheme then it's either following an endpoint name
* and being followed by a domain name (which we discard).
* OR is's a user@host uri without a scheme. It's probably the latter but because
* endpoint@domain looks just like user@host, we'll test for endpoint first.
*/
return handle_atsign(to, destination, uri, slash, atsign, scheme, get_default_outbound);
}
/*
* If all else fails, we assume it's a URI or just a hostname.
*/
if (scheme) {
*uri = ast_strdup(destination);
if (!(*uri)) {
goto failure;
}
ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s'%s\n",
to, *uri, get_default_outbound ? " with default endpoint" : "");
} else {
*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
if (!(*uri)) {
goto failure;
}
sprintf(*uri, "sip:%s", destination);
ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s'%s\n",
to, *uri, get_default_outbound ? " with default endpoint" : "");
}
if (get_default_outbound) {
endpoint = ast_sip_default_outbound_endpoint();
}
return endpoint;
failure:
ao2_cleanup(endpoint);
*uri = NULL;
return NULL;
}
int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to)
{
pjsip_name_addr *parsed_name_addr;
pjsip_sip_uri *sip_uri;
pjsip_name_addr *tdata_name_addr;
pjsip_sip_uri *tdata_sip_uri;
pjsip_to_hdr *to_hdr;
char *buf = NULL;
#define DEBUG_BUF_SIZE 256
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, (char*)to, strlen(to),
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
return -1;
}
sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
if (DEBUG_ATLEAST(3)) {
buf = ast_alloca(DEBUG_BUF_SIZE);
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
parsed_name_addr->display.ptr, buf);
}
to_hdr = PJSIP_MSG_TO_HDR(tdata->msg);
tdata_name_addr = to_hdr ? (pjsip_name_addr *) to_hdr->uri : NULL;
if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
/* Highly unlikely but we have to check */
ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
return -1;
}
tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
if (DEBUG_ATLEAST(3)) {
buf[0] = '\0';
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
tdata_name_addr->display.ptr, buf);
}
/* Replace the uri */
pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
/* The display name isn't part of the URI so we need to replace it separately */
pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
if (DEBUG_ATLEAST(3)) {
buf[0] = '\0';
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
tdata_name_addr->display.ptr, buf);
}
return 0;
#undef DEBUG_BUF_SIZE
}
int ast_sip_update_from(pjsip_tx_data *tdata, char *from)
{
pjsip_name_addr *name_addr;
pjsip_sip_uri *uri;
pjsip_name_addr *parsed_name_addr;
pjsip_from_hdr *from_hdr;
if (ast_strlen_zero(from)) {
return 0;
}
from_hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
if (!from_hdr) {
return -1;
}
name_addr = (pjsip_name_addr *) from_hdr->uri;
uri = pjsip_uri_get_uri(name_addr);
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
if (parsed_name_addr) {
pjsip_sip_uri *parsed_uri;
if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
return -1;
}
parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
if (pj_strlen(&parsed_name_addr->display)) {
pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
}
/* Unlike the To header, we only want to replace the user, host and port */
pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
uri->port = parsed_uri->port;
return 0;
} else {
/* assume it is 'user[@domain]' format */
char *domain = strchr(from, '@');
if (domain) {
pj_str_t pj_from;
pj_strset3(&pj_from, from, domain);
pj_strdup(tdata->pool, &uri->user, &pj_from);
pj_strdup2(tdata->pool, &uri->host, domain + 1);
} else {
pj_strdup2(tdata->pool, &uri->user, from);
}
return 0;
}
return -1;
}
static void remove_request_headers(pjsip_endpoint *endpt)
{

View File

@ -195,571 +195,6 @@ static enum pjsip_status_code check_content_type_in_dialog(const pjsip_rx_data *
return res;
}
/*!
* \brief Find a contact and insert a "user@" into its URI.
*
* \param to Original destination (for error messages only)
* \param endpoint_name Endpoint name (for error messages only)
* \param aors Command separated list of AORs
* \param user The user to insert in the contact URI
* \param uri Pointer to buffer in which to return the URI
*
* \return 0 Success
* \return -1 Fail
*
* \note If the contact URI found for the endpoint already has a user in
* its URI, it will be replaced.
*/
static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
const char *user, char **uri)
{
char *scheme = NULL;
char *contact_uri = NULL;
char *after_scheme = NULL;
char *host;
struct ast_sip_contact *contact = NULL;
contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
if (!contact) {
/*
* We're getting the contact using the same method as
* ast_sip_create_request() so if there's no contact
* we can never send this message.
*/
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Couldn't find contact for endpoint '%s'\n",
to, endpoint_name);
return -1;
}
contact_uri = ast_strdupa(contact->uri);
ao2_cleanup(contact);
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact_uri);
/*
* Contact URIs must have a scheme so we must insert the user between it and the host.
*/
scheme = contact_uri;
after_scheme = strchr(contact_uri, ':');
if (!after_scheme) {
/* A contact URI without a scheme? Something's wrong. Bail */
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: There was no scheme in the contact URI '%s'\n",
to, contact_uri);
return -1;
}
/*
* Terminate the scheme.
*/
*after_scheme = '\0';
after_scheme++;
/*
* If the contact_uri already has a user, the host starts after the '@', otherwise
* the host is at after_scheme.
*
* We're going to ignore the existing user.
*/
host = strchr(after_scheme, '@');
if (host) {
host++;
} else {
host = after_scheme;
}
*uri = ast_malloc(strlen(scheme) + strlen(user) + strlen(host) + 3 /* One for the ':', '@' and terminating NULL */);
sprintf(*uri, "%s:%s@%s", scheme, user, host); /* Safe */
return 0;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination is only a single token
*
* "to" could be one of the following:
* \verbatim
endpoint_name
hostname
* \endverbatim
*
* \param to Destination specified in MessageSend
* \param destination
* \param uri Pointer to URI variable. Must be freed by caller
* \return endpoint
*/
static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, char **uri) {
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
struct ast_sip_contact *contact = NULL;
/*
* If "to" is just one token, it could be an endpoint name
* or a hostname without a scheme.
*/
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
if (!endpoint) {
/*
* We can only assume it's a hostname.
*/
char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
sprintf(temp_uri, "sip:%s", destination);
*uri = temp_uri;
endpoint = ast_sip_default_outbound_endpoint();
ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s' with default endpoint\n",
to, *uri);
return endpoint;
}
/*
* It's an endpoint
*/
endpoint_name = destination;
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
if (!contact) {
/*
* We're getting the contact using the same method as
* ast_sip_create_request() so if there's no contact
* we can never send this message.
*/
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find an aor/contact for it\n",
to, endpoint_name);
ao2_cleanup(endpoint);
*uri = NULL;
return NULL;
}
*uri = ast_strdup(contact->uri);
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
to, endpoint_name, *uri);
ao2_cleanup(contact);
return endpoint;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination contained a '/'
*
* "to" could be one of the following:
* \verbatim
endpoint/aor
endpoint/<sip[s]:host>
endpoint/<sip[s]:user@host>
endpoint/"Bob" <sip[s]:host>
endpoint/"Bob" <sip[s]:user@host>
endpoint/sip[s]:host
endpoint/sip[s]:user@host
endpoint/host
endpoint/user@host
* \endverbatim
*
* \param to Destination specified in MessageSend
* \param uri Pointer to URI variable. Must be freed by caller
* \param destination, slash, atsign, scheme
* \return endpoint
*/
static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
char *slash, char *atsign, char *scheme)
{
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
struct ast_sip_contact *contact = NULL;
char *user = NULL;
char *afterslash = slash + 1;
struct ast_sip_aor *aor;
if (ast_begins_with(destination, "PJSIP/")) {
ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
/*
* This has to be the form PJSIP/user@endpoint
*/
if (!atsign || strchr(afterslash, '/')) {
/*
* If there's no "user@" or there's a slash somewhere after
* "PJSIP/" then we go no further.
*/
*uri = NULL;
ast_log(LOG_WARNING,
"Dest: '%s' MSG SEND FAIL: Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
to);
return NULL;
}
*atsign = '\0';
user = afterslash;
endpoint_name = atsign + 1;
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
} else {
/*
* Either...
* endpoint/aor
* endpoint/uri
*/
*slash = '\0';
endpoint_name = destination;
ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
}
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
if (!endpoint) {
*uri = NULL;
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Didn't find endpoint with name '%s'\n",
to, endpoint_name);
return NULL;
}
if (scheme) {
/*
* If we found a scheme, then everything after the slash MUST be a URI.
* We don't need to do any further modification.
*/
*uri = ast_strdup(afterslash);
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
to, endpoint_name, *uri);
return endpoint;
}
if (user) {
/*
* This has to be the form PJSIP/user@endpoint
*/
int rc;
/*
* Set the return URI to be the endpoint's contact URI with the user
* portion set to the user that was specified before the endpoint name.
*/
rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
if (rc != 0) {
/*
* insert_user_in_contact_uri prints the warning message.
*/
ao2_cleanup(endpoint);
endpoint = NULL;
*uri = NULL;
}
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
endpoint_name, *uri);
return endpoint;
}
/*
* We're now left with two possibilities...
* endpoint/aor
* endpoint/uri-without-scheme
*/
aor = ast_sip_location_retrieve_aor(afterslash);
if (!aor) {
/*
* It's probably a URI without a scheme but we don't have a way to tell
* for sure. We're going to assume it is and prepend it with a scheme.
*/
*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
sprintf(*uri, "sip:%s", afterslash);
ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
to, endpoint_name, *uri);
return endpoint;
}
/*
* Only one possibility left... There was an aor name after the slash.
*/
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
to, endpoint_name, ast_sorcery_object_get_id(aor));
contact = ast_sip_location_retrieve_first_aor_contact(aor);
if (!contact) {
/*
* An aor without a contact is useless and since
* ast_sip_create_message() won't be able to find one
* either, we just need to bail.
*/
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact for aor '%s'\n",
to, endpoint_name, ast_sorcery_object_get_id(aor));
ao2_cleanup(aor);
ao2_cleanup(endpoint);
*uri = NULL;
return NULL;
}
*uri = ast_strdup(contact->uri);
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
ao2_cleanup(contact);
ao2_cleanup(aor);
return endpoint;
}
/*!
* \internal
* \brief Get endpoint and URI when the destination contained a '\@' but no '/' or scheme
*
* "to" could be one of the following:
* \verbatim
<sip[s]:user@host>
"Bob" <sip[s]:user@host>
sip[s]:user@host
user@host
* \endverbatim
*
* \param to Destination specified in MessageSend
* \param uri Pointer to URI variable. Must be freed by caller
* \param destination, slash, atsign, scheme
* \return endpoint
*/
static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
char *slash, char *atsign, char *scheme)
{
char *endpoint_name = NULL;
struct ast_sip_endpoint *endpoint = NULL;
struct ast_sip_contact *contact = NULL;
char *afterat = atsign + 1;
*atsign = '\0';
endpoint_name = destination;
/* Apparently there may be ';<user_options>' after the endpoint name ??? */
AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
if (!endpoint) {
/*
* It's probably a uri with a user but without a scheme but we don't have a way to tell.
* We're going to assume it is and prepend it with a scheme.
*/
*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
sprintf(*uri, "sip:%s", to);
endpoint = ast_sip_default_outbound_endpoint();
ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s' with default endpoint\n",
to, *uri);
return endpoint;
}
/*
* OK, it's an endpoint and a domain (which we ignore)
*/
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
if (!contact) {
/*
* We're getting the contact using the same method as
* ast_sip_create_request() so if there's no contact
* we can never send this message.
*/
ao2_cleanup(endpoint);
endpoint = NULL;
*uri = NULL;
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact\n",
to, endpoint_name);
return NULL;
}
*uri = ast_strdup(contact->uri);
ao2_cleanup(contact);
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
to, endpoint_name, *uri, afterat);
return endpoint;
}
/*!
* \internal
* \brief Retrieves an endpoint and URI from the "to" string.
*
* This URI is used as the Request URI.
*
* Expects the given 'to' to be in one of the following formats:
* Why we allow so many is a mystery.
*
* Basic:
*
* endpoint : We'll get URI from the default aor/contact
* endpoint/aor : We'll get the URI from the specific aor/contact
* endpoint@domain : We toss the domain part and just use the endpoint
*
* These all use the endpoint and specified URI:
* \verbatim
endpoint/<sip[s]:host>
endpoint/<sip[s]:user@host>
endpoint/"Bob" <sip[s]:host>
endpoint/"Bob" <sip[s]:user@host>
endpoint/sip[s]:host
endpoint/sip[s]:user@host
endpoint/host
endpoint/user@host
\endverbatim
*
* These all use the default endpoint and specified URI:
* \verbatim
<sip[s]:host>
<sip[s]:user@host>
"Bob" <sip[s]:host>
"Bob" <sip[s]:user@host>
sip[s]:host
sip[s]:user@host
\endverbatim
*
* These use the default endpoint and specified host:
* \verbatim
host
user@host
\endverbatim
*
* This form is similar to a dialstring:
* \verbatim
PJSIP/user@endpoint
\endverbatim
*
* In this case, the user will be added to the endpoint contact's URI.
* If the contact URI already has a user, it will be replaced.
*
* The ones that have the sip[s] scheme are the easiest to parse.
* The rest all have some issue.
*
* endpoint vs host : We have to test for endpoint first
* endpoint/aor vs endpoint/host : We have to test for aor first
* What if there's an aor with the same
* name as the host?
* endpoint@domain vs user@host : We have to test for endpoint first.
* What if there's an endpoint with the
* same name as the user?
*
* \param to 'To' field with possible endpoint
* \param uri Pointer to a char* which will be set to the URI.
* Must be ast_free'd by the caller.
*
* \note The logic below could probably be condensed but then it wouldn't be
* as clear.
*/
static struct ast_sip_endpoint *get_outbound_endpoint(const char *to, char **uri)
{
char *destination;
char *slash = NULL;
char *atsign = NULL;
char *scheme = NULL;
struct ast_sip_endpoint *endpoint = NULL;
destination = ast_strdupa(to);
slash = strchr(destination, '/');
atsign = strchr(destination, '@');
scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
if (!slash && !atsign && !scheme) {
/*
* If there's only a single token, it can be either...
* endpoint
* host
*/
return handle_single_token(to, destination, uri);
}
if (slash) {
/*
* If there's a '/', then the form must be one of the following...
* PJSIP/user@endpoint
* endpoint/aor
* endpoint/uri
*/
return handle_slash(to, destination, uri, slash, atsign, scheme);
}
if (!endpoint && atsign && !scheme) {
/*
* If there's an '@' but no scheme then it's either following an endpoint name
* and being followed by a domain name (which we discard).
* OR is's a user@host uri without a scheme. It's probably the latter but because
* endpoint@domain looks just like user@host, we'll test for endpoint first.
*/
return handle_atsign(to, destination, uri, slash, atsign, scheme);
}
/*
* If all else fails, we assume it's a URI or just a hostname.
*/
if (scheme) {
*uri = ast_strdup(destination);
ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s' with default endpoint\n",
to, *uri);
} else {
*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
sprintf(*uri, "sip:%s", destination);
ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s' with default endpoint\n",
to, *uri);
}
endpoint = ast_sip_default_outbound_endpoint();
return endpoint;
}
/*!
* \internal
* \brief Replace the To URI in the tdata with the supplied one
*
* \param tdata the outbound message data structure
* \param to URI to replace the To URI with
*
* \return 0: success, -1: failure
*/
static int update_to_uri(pjsip_tx_data *tdata, char *to)
{
pjsip_name_addr *parsed_name_addr;
pjsip_sip_uri *sip_uri;
pjsip_name_addr *tdata_name_addr;
pjsip_sip_uri *tdata_sip_uri;
char *buf = NULL;
#define DEBUG_BUF_SIZE 256
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, to, strlen(to),
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
return -1;
}
sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
if (DEBUG_ATLEAST(3)) {
buf = ast_alloca(DEBUG_BUF_SIZE);
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
parsed_name_addr->display.ptr, buf);
}
tdata_name_addr = (pjsip_name_addr *) PJSIP_MSG_TO_HDR(tdata->msg)->uri;
if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
/* Highly unlikely but we have to check */
ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
return -1;
}
tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
if (DEBUG_ATLEAST(3)) {
buf[0] = '\0';
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
tdata_name_addr->display.ptr, buf);
}
/* Replace the uri */
pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
/* The display name isn't part of the URI so we need to replace it separately */
pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
if (DEBUG_ATLEAST(3)) {
buf[0] = '\0';
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
tdata_name_addr->display.ptr, buf);
}
return 0;
#undef DEBUG_BUF_SIZE
}
/*!
* \internal
* \brief Update the display name in the To uri in the tdata with the one from the supplied uri
@ -790,77 +225,6 @@ static int update_to_display_name(pjsip_tx_data *tdata, char *to)
return -1;
}
/*!
* \internal
* \brief Overwrite fields in the outbound 'From' header
*
* The outbound 'From' header is created/added in ast_sip_create_request with
* default data. If available that data may be info specified in the 'from_user'
* and 'from_domain' options found on the endpoint. That information will be
* overwritten with data in the given 'from' parameter.
*
* \param tdata the outbound message data structure
* \param from info to copy into the header
*
* \return 0: success, -1: failure
*/
static int update_from(pjsip_tx_data *tdata, char *from)
{
pjsip_name_addr *name_addr;
pjsip_sip_uri *uri;
pjsip_name_addr *parsed_name_addr;
if (ast_strlen_zero(from)) {
return 0;
}
name_addr = (pjsip_name_addr *) PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
uri = pjsip_uri_get_uri(name_addr);
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
if (parsed_name_addr) {
pjsip_sip_uri *parsed_uri;
if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
return -1;
}
parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
if (pj_strlen(&parsed_name_addr->display)) {
pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
}
/* Unlike the To header, we only want to replace the user, host and port */
pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
uri->port = parsed_uri->port;
return 0;
} else {
/* assume it is 'user[@domain]' format */
char *domain = strchr(from, '@');
if (domain) {
pj_str_t pj_from;
pj_strset3(&pj_from, from, domain);
pj_strdup(tdata->pool, &uri->user, &pj_from);
pj_strdup2(tdata->pool, &uri->host, domain + 1);
} else {
pj_strdup2(tdata->pool, &uri->user, from);
}
return 0;
}
return -1;
}
/*!
* \internal
* \brief Checks if the given msg var name should be blocked.
@ -1252,7 +616,7 @@ static int msg_send(void *data)
ast_debug(3, "mdata From: %s msg From: %s mdata Destination: %s msg To: %s\n",
mdata->from, ast_msg_get_from(mdata->msg), mdata->destination, ast_msg_get_to(mdata->msg));
endpoint = get_outbound_endpoint(mdata->destination, &uri);
endpoint = ast_sip_get_endpoint(mdata->destination, 1, &uri);
if (!endpoint) {
ast_log(LOG_ERROR,
"PJSIP MESSAGE - Could not find endpoint '%s' and no default outbound endpoint configured\n",
@ -1290,7 +654,7 @@ static int msg_send(void *data)
if (ast_begins_with(msg_to, "pjsip:")) {
msg_to += 6;
}
update_to_uri(tdata, msg_to);
ast_sip_update_to_uri(tdata, msg_to);
} else {
/*
* If there was no To in the message, it's still possible
@ -1301,9 +665,9 @@ static int msg_send(void *data)
}
if (!ast_strlen_zero(mdata->from)) {
update_from(tdata, mdata->from);
ast_sip_update_from(tdata, mdata->from);
} else if (!ast_strlen_zero(ast_msg_get_from(mdata->msg))) {
update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
ast_sip_update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
}
#ifdef TEST_FRAMEWORK

View File

@ -235,52 +235,6 @@ static pj_bool_t nat_on_rx_message(pjsip_rx_data *rdata)
return res;
}
/*! \brief Structure which contains information about a transport */
struct request_transport_details {
/*! \brief Type of transport */
enum ast_transport type;
/*! \brief Potential pointer to the transport itself, if UDP */
pjsip_transport *transport;
/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
pjsip_tpfactory *factory;
/*! \brief Local address for transport */
pj_str_t local_address;
/*! \brief Local port for transport */
int local_port;
};
/*! \brief Callback function for finding the transport the request is going out on */
static int find_transport_state_in_use(void *obj, void *arg, int flags)
{
struct ast_sip_transport_state *transport_state = obj;
struct request_transport_details *details = arg;
/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
* to compare based on that we make sure that the type is the same and the source IP address/port are the same
*/
if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
(details->factory && details->factory == transport_state->factory) ||
((details->type == transport_state->type) && (transport_state->factory) &&
!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
transport_state->factory->addr_name.port == details->local_port))) {
return CMP_MATCH;
}
return 0;
}
/*! \brief Helper function which returns the SIP URI of a Contact header */
static pjsip_sip_uri *nat_get_contact_sip_uri(pjsip_tx_data *tdata)
{
pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
return NULL;
}
return pjsip_uri_get_uri(contact->uri);
}
/*! \brief Structure which contains hook details */
struct nat_hook_details {
/*! \brief Outgoing message itself */
@ -363,55 +317,22 @@ static void restore_orig_contact_host(pjsip_tx_data *tdata)
static pj_status_t process_nat(pjsip_tx_data *tdata)
{
RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
struct request_transport_details details = { 0, };
pjsip_via_hdr *via = NULL;
struct ast_sip_request_transport_details details;
struct ast_sockaddr addr = { { 0, } };
pjsip_sip_uri *uri = NULL;
RAII_VAR(struct ao2_container *, hooks, NULL, ao2_cleanup);
/* If a transport selector is in use we know the transport or factory, so explicitly find it */
if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
details.transport = tdata->tp_sel.u.transport;
} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
details.factory = tdata->tp_sel.u.listener;
} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP || tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP6) {
/* Connectionless uses the same transport for all requests */
details.type = AST_TRANSPORT_UDP;
details.transport = tdata->tp_info.transport;
} else {
if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TCP) {
details.type = AST_TRANSPORT_TCP;
} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TLS) {
details.type = AST_TRANSPORT_TLS;
} else {
/* Unknown transport type, we can't map and thus can't apply NAT changes */
return PJ_SUCCESS;
}
if ((uri = nat_get_contact_sip_uri(tdata))) {
details.local_address = uri->host;
details.local_port = uri->port;
} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
details.local_address = via->sent_by.host;
details.local_port = via->sent_by.port;
} else {
return PJ_SUCCESS;
}
if (!details.local_port) {
details.local_port = (details.type == AST_TRANSPORT_TLS) ? 5061 : 5060;
}
}
if (!(transport_states = ast_sip_get_transport_states())) {
if (ast_sip_set_request_transport_details(&details, tdata, 0)) {
return PJ_SUCCESS;
}
if (!(transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, &details))) {
uri = ast_sip_get_contact_sip_uri(tdata);
via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
if (!(transport_state = ast_sip_find_transport_state_in_use(&details))) {
return PJ_SUCCESS;
}
@ -443,7 +364,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
if (!cseq || tdata->msg->type == PJSIP_REQUEST_MSG ||
pjsip_method_cmp(&cseq->method, &pjsip_register_method)) {
/* We can only rewrite the URI when one is present */
if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
if (uri || (uri = ast_sip_get_contact_sip_uri(tdata))) {
pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
if (transport->external_signaling_port) {
uri->port = transport->external_signaling_port;

View File

@ -39,6 +39,11 @@
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/causes.h"
#include "asterisk/refer.h"
static struct ast_taskprocessor *refer_serializer;
static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata);
/*! \brief REFER Progress structure */
struct refer_progress {
@ -786,6 +791,499 @@ static void refer_blind_callback(struct ast_channel *chan, struct transfer_chann
ast_channel_unlock((session)->channel); \
} while (0) \
struct refer_data {
struct ast_refer *refer;
char *destination;
char *from;
char *refer_to;
int to_self;
};
static void refer_data_destroy(void *obj)
{
struct refer_data *rdata = obj;
ast_free(rdata->destination);
ast_free(rdata->from);
ast_free(rdata->refer_to);
ast_refer_destroy(rdata->refer);
}
static struct refer_data *refer_data_create(const struct ast_refer *refer)
{
char *uri_params;
const char *destination;
struct refer_data *rdata = ao2_alloc_options(sizeof(*rdata), refer_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!rdata) {
return NULL;
}
/* typecast to suppress const warning */
rdata->refer = ast_refer_ref((struct ast_refer *) refer);
destination = ast_refer_get_to(refer);
/* To starts with 'pjsip:' which needs to be removed. */
if (!(destination = strchr(destination, ':'))) {
goto failure;
}
++destination;/* Now skip the ':' */
rdata->destination = ast_strdup(destination);
if (!rdata->destination) {
goto failure;
}
rdata->from = ast_strdup(ast_refer_get_from(refer));
if (!rdata->from) {
goto failure;
}
rdata->refer_to = ast_strdup(ast_refer_get_refer_to(refer));
if (!rdata->refer_to) {
goto failure;
}
rdata->to_self = ast_refer_get_to_self(refer);
/*
* Sometimes from URI can contain URI parameters, so remove them.
*
* sip:user;user-options@domain;uri-parameters
*/
uri_params = strchr(rdata->from, '@');
if (uri_params && (uri_params = strchr(uri_params, ';'))) {
*uri_params = '\0';
}
return rdata;
failure:
ao2_cleanup(rdata);
return NULL;
}
/*!
* \internal
* \brief Checks if the given refer var name should be blocked.
*
* \details Some headers are not allowed to be overridden by the user.
* Determine if the given var header name from the user is blocked for
* an outgoing REFER.
*
* \param name name of header to see if it is blocked.
*
* \retval TRUE if the given header is blocked.
*/
static int is_refer_var_blocked(const char *name)
{
int i;
/* Don't block the Max-Forwards header because the user can override it */
static const char *hdr[] = {
"To",
"From",
"Via",
"Route",
"Contact",
"Call-ID",
"CSeq",
"Allow",
"Content-Length",
"Content-Type",
"Request-URI",
};
for (i = 0; i < ARRAY_LEN(hdr); ++i) {
if (!strcasecmp(name, hdr[i])) {
/* Block addition of this header. */
return 1;
}
}
return 0;
}
/*!
* \internal
* \brief Copies any other refer vars over to the request headers.
*
* \param refer The refer structure to copy headers from
* \param tdata The SIP transmission data
*/
static enum pjsip_status_code vars_to_headers(const struct ast_refer *refer, pjsip_tx_data *tdata)
{
const char *name;
const char *value;
struct ast_refer_var_iterator *iter;
for (iter = ast_refer_var_iterator_init(refer);
ast_refer_var_iterator_next(iter, &name, &value);
ast_refer_var_unref_current(iter)) {
if (!is_refer_var_blocked(name)) {
ast_sip_add_header(tdata, name, value);
}
}
ast_refer_var_iterator_destroy(iter);
return PJSIP_SC_OK;
}
struct refer_out_of_dialog {
pjsip_dialog *dlg;
int authentication_challenge_count;
};
/*! \brief REFER Out-of-dialog module, used to attach session data structure to subscription */
static pjsip_module refer_out_of_dialog_module = {
.name = { "REFER Out-of-dialog Module", 26 },
.id = -1,
.on_tx_request = refer_on_tx_request,
/* Ensure that we are called after res_pjsp_nat module and before transport priority */
.priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 4,
};
/*! \brief Helper function which returns the name-addr of the Refer-To header or NULL */
static pjsip_uri *get_refer_to_uri(pjsip_tx_data *tdata)
{
const pj_str_t REFER_TO = { "Refer-To", 8 };
pjsip_generic_string_hdr *refer_to;
pjsip_uri *parsed_uri;
if (!(refer_to = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL))
|| !(parsed_uri = pjsip_parse_uri(tdata->pool, refer_to->hvalue.ptr, refer_to->hvalue.slen, 0))
|| (!PJSIP_URI_SCHEME_IS_SIP(parsed_uri) && !PJSIP_URI_SCHEME_IS_SIPS(parsed_uri))) {
return NULL;
}
return parsed_uri;
}
static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata) {
RAII_VAR(struct ast_str *, refer_to_str, ast_str_create(PJSIP_MAX_URL_SIZE), ast_free_ptr);
const pj_str_t REFER_TO = { "Refer-To", 8 };
pjsip_generic_string_hdr *refer_to_hdr;
pjsip_dialog *dlg;
struct refer_data *refer_data;
pjsip_uri *parsed_uri;
pjsip_sip_uri *refer_to_uri;
/*
* If this is a request in response to a 401/407 Unauthorized challenge, the
* Refer-To URI has been rewritten already, so don't attempt to re-write it again.
* Checking for presence of the Authorization header is not an ideal solution. We do this because
* there exists some race condition where this dialog is not the same as the one used
* to send the original request in which case we don't have the correct refer_data.
*/
if (!refer_to_str
|| pjsip_msg_find_hdr(tdata->msg, PJSIP_H_AUTHORIZATION, NULL)
|| !(dlg = pjsip_tdata_get_dlg(tdata))
|| !(refer_data = pjsip_dlg_get_mod_data(dlg, refer_out_of_dialog_module.id))
|| !refer_data->to_self
|| !(parsed_uri = get_refer_to_uri(tdata))) {
goto out;
}
refer_to_uri = pjsip_uri_get_uri(parsed_uri);
ast_sip_rewrite_uri_to_local(refer_to_uri, tdata);
pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, parsed_uri, ast_str_buffer(refer_to_str), ast_str_size(refer_to_str));
refer_to_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL);
pj_strdup2(tdata->pool, &refer_to_hdr->hvalue, ast_str_buffer(refer_to_str));
out:
return PJ_SUCCESS;
}
static int refer_unreference_dialog(void *obj)
{
struct refer_out_of_dialog *data = obj;
/* This is why we keep the dialog on the subscription. When the subscription
* is destroyed, there is no guarantee that the underlying dialog is ready
* to be destroyed. Furthermore, there's no guarantee in the opposite direction
* either. The dialog could be destroyed before our subscription is. We fix
* this problem by keeping a reference to the dialog until it is time to
* destroy the subscription.
*/
pjsip_dlg_dec_session(data->dlg, &refer_out_of_dialog_module);
data->dlg = NULL;
return 0;
}
/*! \brief Destructor for REFER out of dialog structure */
static void refer_out_of_dialog_destroy(void *obj) {
struct refer_out_of_dialog *data = obj;
if (data->dlg) {
/* ast_sip_push_task_wait_servant should not be called in a destructor,
* however in this case it seems to be fine.
*/
ast_sip_push_task_wait_servant(refer_serializer, refer_unreference_dialog, data);
}
}
/*!
* \internal
* \brief Callback function to report status of implicit REFER-NOTIFY subscription.
*
* This function will be called on any state change in the REFER-NOTIFY subscription.
* Its primary purpose is to report SUCCESS/FAILURE of a refer initiated via
* \ref refer_send as well as to terminate the subscription, if necessary.
*/
static void refer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
{
pjsip_tx_data *tdata;
RAII_VAR(struct ast_sip_endpoint *, endpt, NULL, ao2_cleanup);
struct refer_out_of_dialog *refer_data;
int refer_success;
int res = 0;
if (!event) {
return;
}
refer_data = pjsip_evsub_get_mod_data(sub, refer_out_of_dialog_module.id);
if (!refer_data || !refer_data->dlg) {
return;
}
endpt = ast_sip_dialog_get_endpoint(refer_data->dlg);
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
/* Check if subscription is suppressed and terminate and send completion code, if so. */
pjsip_rx_data *rdata;
pjsip_generic_string_hdr *refer_sub;
const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
ast_debug(3, "Refer accepted by %s\n", endpt ? ast_sorcery_object_get_id(endpt) : "(unknown endpoint)");
/* Check if response message */
if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
rdata = event->body.tsx_state.src.rdata;
/* Find Refer-Sub header */
refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &REFER_SUB, NULL);
/* Check if subscription is suppressed. If it is, the far end will not terminate it,
* and the subscription will remain active until it times out. Terminating it here
* eliminates the unnecessary timeout.
*/
if (refer_sub && !pj_stricmp2(&refer_sub->hvalue, "false")) {
/* Since no subscription is desired, assume that call has been referred successfully
* and terminate subscription.
*/
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
pjsip_evsub_terminate(sub, PJ_TRUE);
res = -1;
}
}
} else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
/* Check for NOTIFY complete or error. */
pjsip_msg *msg;
pjsip_msg_body *body;
pjsip_status_line status_line = { .code = 0 };
pj_bool_t is_last;
pj_status_t status;
if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
pjsip_rx_data *rdata;
pj_str_t refer_str;
pj_cstr(&refer_str, "REFER");
rdata = event->body.tsx_state.src.rdata;
msg = rdata->msg_info.msg;
if (msg->type == PJSIP_RESPONSE_MSG
&& (event->body.tsx_state.tsx->status_code == 401
|| event->body.tsx_state.tsx->status_code == 407)
&& pj_stristr(&refer_str, &event->body.tsx_state.tsx->method.name)
&& ++refer_data->authentication_challenge_count < MAX_RX_CHALLENGES
&& endpt) {
if (!ast_sip_create_request_with_auth(&endpt->outbound_auths,
event->body.tsx_state.src.rdata, event->body.tsx_state.tsx->last_tx, &tdata)) {
/* Send authed REFER */
ast_sip_send_request(tdata, refer_data->dlg, NULL, NULL, NULL);
goto out;
}
}
if (msg->type == PJSIP_REQUEST_MSG) {
if (!pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())) {
body = msg->body;
if (body && !pj_stricmp2(&body->content_type.type, "message")
&& !pj_stricmp2(&body->content_type.subtype, "sipfrag")) {
pjsip_parse_status_line((char *)body->data, body->len, &status_line);
}
}
} else {
status_line.code = msg->line.status.code;
status_line.reason = msg->line.status.reason;
}
} else {
status_line.code = 500;
status_line.reason = *pjsip_get_status_text(500);
}
is_last = (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED);
/* If the status code is >= 200, the subscription is finished. */
if (status_line.code >= 200 || is_last) {
res = -1;
refer_success = status_line.code >= 200 && status_line.code < 300;
/* If subscription not terminated and subscription is finished (status code >= 200)
* terminate it */
if (!is_last) {
pjsip_tx_data *tdata;
status = pjsip_evsub_initiate(sub, pjsip_get_subscribe_method(), 0, &tdata);
if (status == PJ_SUCCESS) {
pjsip_evsub_send_request(sub, tdata);
}
}
ast_debug(3, "Refer completed: %d %.*s (%s)\n",
status_line.code,
(int)status_line.reason.slen, status_line.reason.ptr,
refer_success ? "Success" : "Failure");
}
}
out:
if (res) {
ao2_cleanup(refer_data);
}
}
/*!
* \internal
* \brief Send a REFER
*
* \param data The outbound refer data structure
*
* \return 0: success, -1: failure
*/
static int refer_send(void *data)
{
struct refer_data *rdata = data; /* The caller holds a reference */
pjsip_tx_data *tdata;
pjsip_evsub *sub;
pj_str_t tmp;
char refer_to_str[PJSIP_MAX_URL_SIZE];
char disp_name_escaped[128];
struct refer_out_of_dialog *refer;
struct pjsip_evsub_user xfer_cb;
RAII_VAR(char *, uri, NULL, ast_free);
RAII_VAR(char *, tmp_str, NULL, ast_free);
RAII_VAR(char *, display_name, NULL, ast_free);
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_endpoint *, refer_to_endpoint, NULL, ao2_cleanup);
endpoint = ast_sip_get_endpoint(rdata->destination, 1, &uri);
if (!endpoint) {
ast_log(LOG_ERROR,
"PJSIP REFER - Could not find endpoint '%s' and no default outbound endpoint configured\n",
rdata->destination);
return -1;
}
ast_debug(3, "Request URI: %s\n", uri);
refer_to_endpoint = ast_sip_get_endpoint(rdata->refer_to, 0, &tmp_str);
if (!tmp_str) {
ast_log(LOG_WARNING, "PJSIP REFER - Refer to not a valid resource identifier or SIP URI\n");
return -1;
}
if (!(refer = ao2_alloc(sizeof(struct refer_out_of_dialog), refer_out_of_dialog_destroy))) {
ast_log(LOG_ERROR, "PJSIP REFER - Could not allocate resources.\n");
return -1;
}
/* The dialog will be terminated in the subscription event callback
* when the subscription has terminated. */
refer->authentication_challenge_count = 0;
refer->dlg = ast_sip_create_dialog_uac(endpoint, uri, NULL);
if (!refer->dlg) {
ast_log(LOG_WARNING, "PJSIP REFER - Could not create dialog\n");
ao2_cleanup(refer);
return -1;
}
ast_sip_dialog_set_endpoint(refer->dlg, endpoint);
pj_bzero(&xfer_cb, sizeof(xfer_cb));
xfer_cb.on_evsub_state = &refer_client_on_evsub_state;
if (pjsip_xfer_create_uac(refer->dlg, &xfer_cb, &sub) != PJ_SUCCESS) {
ast_log(LOG_WARNING, "PJSIP REFER - Could not create uac\n");
ao2_cleanup(refer);
return -1;
}
display_name = ast_refer_get_var_and_unlink(rdata->refer, "display_name");
if (display_name) {
ast_escape_quoted(display_name, disp_name_escaped, sizeof(disp_name_escaped));
snprintf(refer_to_str, sizeof(refer_to_str), "\"%s\" <%s>", disp_name_escaped, tmp_str);
} else {
snprintf(refer_to_str, sizeof(refer_to_str), "%s", tmp_str);
}
/* refer_out_of_dialog_module requires a reference to dlg
* which will be released in refer_client_on_evsub_state()
* when the implicit REFER subscription terminates */
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, refer);
if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, refer_to_str), &tdata) != PJ_SUCCESS) {
ast_log(LOG_WARNING, "PJSIP REFER - Could not create request\n");
goto failure;
}
if (refer_to_endpoint && rdata->to_self) {
pjsip_dlg_add_usage(refer->dlg, &refer_out_of_dialog_module, rdata);
}
ast_sip_update_to_uri(tdata, uri);
ast_sip_update_from(tdata, rdata->from);
/*
* This copies any headers found in the refer's variables to
* tdata.
*/
vars_to_headers(rdata->refer, tdata);
ast_debug(1, "Sending REFER to '%s' (via endpoint %s) from '%s'\n",
rdata->destination, ast_sorcery_object_get_id(endpoint), rdata->from);
if (pjsip_xfer_send_request(sub, tdata) == PJ_SUCCESS) {
return 0;
}
failure:
ao2_cleanup(refer);
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
pjsip_evsub_terminate(sub, PJ_FALSE);
return -1;
}
static int sip_refer_send(const struct ast_refer *refer)
{
struct refer_data *rdata;
int res;
if (ast_strlen_zero(ast_refer_get_to(refer))) {
ast_log(LOG_ERROR, "SIP REFER - a 'To' URI must be specified\n");
return -1;
}
rdata = refer_data_create(refer);
if (!rdata) {
return -1;
}
res = ast_sip_push_task_wait_serializer(refer_serializer, refer_send, rdata);
ao2_ref(rdata, -1);
return res;
}
static const struct ast_refer_tech refer_tech = {
.name = "pjsip",
.refer_send = sip_refer_send,
};
static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
pjsip_param *replaces_param, struct refer_progress *progress)
{
@ -1274,6 +1772,17 @@ static int load_module(void)
pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
}
if (ast_refer_tech_register(&refer_tech)) {
return AST_MODULE_LOAD_DECLINE;
}
refer_serializer = ast_sip_create_serializer("pjsip/refer");
if (!refer_serializer) {
ast_refer_tech_unregister(&refer_tech);
return AST_MODULE_LOAD_DECLINE;
}
ast_sip_register_service(&refer_out_of_dialog_module);
ast_sip_register_service(&refer_progress_module);
ast_sip_session_register_supplement(&refer_supplement);
@ -1285,7 +1794,9 @@ static int load_module(void)
static int unload_module(void)
{
ast_sip_session_unregister_supplement(&refer_supplement);
ast_sip_unregister_service(&refer_out_of_dialog_module);
ast_sip_unregister_service(&refer_progress_module);
ast_taskprocessor_unreference(refer_serializer);
return 0;
}

View File

@ -55,7 +55,7 @@
},
{
"name": "variables",
"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip resource types will add the key/value pairs as SIP headers,",
"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers,",
"paramType": "body",
"required": false,
"dataType": "containers",
@ -75,6 +75,71 @@
}
]
},
{
"path": "/endpoints/refer",
"description": "Refer an endpoint or technology URI to some technology URI or endpoint.",
"operations": [
{
"httpMethod": "POST",
"summary": "Refer an endpoint or technology URI to some technology URI or endpoint.",
"nickname": "refer",
"responseClass": "void",
"parameters": [
{
"name": "to",
"description": "The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip.",
"paramType": "query",
"required": true,
"allowMultiple": false,
"dataType": "string"
},
{
"name": "from",
"description": "The endpoint resource or technology specific identity to refer from.",
"paramType": "query",
"required": true,
"allowMultiple": false,
"dataType": "string"
},
{
"name": "refer_to",
"description": "The endpoint resource or technology specific URI to refer to.",
"paramType": "query",
"required": true,
"allowMultiple": false,
"dataType": "string"
},
{
"name": "to_self",
"description": "If true and \"refer_to\" refers to an Asterisk endpoint, the \"refer_to\" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint.",
"paramType": "query",
"required": false,
"allowMultiple": false,
"dataType": "boolean",
"defaultValue": false
},
{
"name": "variables",
"description": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The \"display_name\" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI.",
"paramType": "body",
"required": false,
"dataType": "containers",
"allowMultiple": false
}
],
"errorResponses": [
{
"code": 400,
"reason": "Invalid parameters for referring."
},
{
"code": 404,
"reason": "Endpoint not found"
}
]
}
]
},
{
"path": "/endpoints/{tech}",
"description": "Asterisk endpoints",
@ -177,7 +242,7 @@
},
{
"name": "variables",
"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip resource types will add the key/value pairs as SIP headers,",
"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers,",
"paramType": "body",
"required": false,
"dataType": "containers",
@ -196,6 +261,75 @@
]
}
]
},
{
"path": "/endpoints/{tech}/{resource}/refer",
"description": "Refer an endpoint in a technology to some technology URI or endpoint..",
"operations": [
{
"httpMethod": "POST",
"summary": "Refer an endpoint or technology URI to some technology URI or endpoint.",
"nickname": "referToEndpoint",
"responseClass": "void",
"parameters": [
{
"name": "tech",
"description": "Technology of the endpoint",
"paramType": "path",
"dataType": "string"
},
{
"name": "resource",
"description": "ID of the endpoint",
"paramType": "path",
"dataType": "string"
},
{
"name": "from",
"description": "The endpoint resource or technology specific identity to refer from.",
"paramType": "query",
"required": true,
"allowMultiple": false,
"dataType": "string"
},
{
"name": "refer_to",
"description": "The endpoint resource or technology specific URI to refer to.",
"paramType": "query",
"required": true,
"allowMultiple": false,
"dataType": "string"
},
{
"name": "to_self",
"description": "If true and \"refer_to\" refers to an Asterisk endpoint, the \"refer_to\" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint.",
"paramType": "query",
"required": false,
"allowMultiple": false,
"dataType": "boolean",
"defaultValue": false
},
{
"name": "variables",
"description": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers,",
"paramType": "body",
"required": false,
"dataType": "containers",
"allowMultiple": false
}
],
"errorResponses": [
{
"code": 400,
"reason": "Invalid parameters for referring."
},
{
"code": 404,
"reason": "Endpoint not found"
}
]
}
]
}
],
"models": {