diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index 65e403fb49..684452fee0 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -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. diff --git a/include/asterisk/refer.h b/include/asterisk/refer.h new file mode 100644 index 0000000000..4d0744c876 --- /dev/null +++ b/include/asterisk/refer.h @@ -0,0 +1,325 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2023, Commend International + * + * Maximilian Fridrich + * + * 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 + * + * 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__ */ diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 673ec95505..cf95aafda4 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -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/ + endpoint/ + endpoint/"Bob" + endpoint/"Bob" + 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 + + + "Bob" + "Bob" + 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 diff --git a/main/asterisk.c b/main/asterisk.c index f96f6b2cad..c9b491b040 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -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"); diff --git a/main/refer.c b/main/refer.c new file mode 100644 index 0000000000..11db0652fc --- /dev/null +++ b/main/refer.c @@ -0,0 +1,537 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2023, Commend International + * + * Maximilian Fridrich + * + * 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 + */ + +/*** MODULEINFO + core + ***/ + +#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; +} diff --git a/res/ari/resource_endpoints.c b/res/ari/resource_endpoints.c index 461328277a..3663232cc3 100644 --- a/res/ari/resource_endpoints.c +++ b/res/ari/resource_endpoints.c @@ -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); +} diff --git a/res/ari/resource_endpoints.h b/res/ari/resource_endpoints.h index b5ad634077..3c212b93fa 100644 --- a/res/ari/resource_endpoints.h +++ b/res/ari/resource_endpoints.h @@ -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 */ diff --git a/res/res_ari_endpoints.c b/res/res_ari_endpoints.c index d41096c0e2..b40fd8cebc 100644 --- a/res/res_ari_endpoints.c +++ b/res/res_ari_endpoints.c @@ -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) diff --git a/res/res_pjsip.c b/res/res_pjsip.c index d112aa3043..98a406d74f 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -27,6 +27,8 @@ #include #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 pjproject @@ -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/ + endpoint/ + endpoint/"Bob" + endpoint/"Bob" + 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 + + "Bob" + 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 ';' 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) { diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c index 336d392cd4..51d8160b61 100644 --- a/res/res_pjsip_messaging.c +++ b/res/res_pjsip_messaging.c @@ -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/ - endpoint/ - endpoint/"Bob" - endpoint/"Bob" - 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 - - "Bob" - 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 ';' 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/ - endpoint/ - endpoint/"Bob" - endpoint/"Bob" - 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 - - - "Bob" - "Bob" - 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 diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c index 59223f7bbf..62f8aa0b23 100644 --- a/res/res_pjsip_nat.c +++ b/res/res_pjsip_nat.c @@ -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; diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c index a20bf6ba4d..a10a9fde50 100644 --- a/res/res_pjsip_refer.c +++ b/res/res_pjsip_refer.c @@ -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; } diff --git a/rest-api/api-docs/endpoints.json b/rest-api/api-docs/endpoints.json index 80baf97bcc..3f3f98cec1 100644 --- a/rest-api/api-docs/endpoints.json +++ b/rest-api/api-docs/endpoints.json @@ -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": {