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.
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * The purpose of this API is to provide support for refers that
+ * are not session based. The refers are passed into the Asterisk core
+ * to be routed through the dialplan or another interface and potentially
+ * sent back out through a refer technology that has been registered
+ * through this API.
+ */
+
+#ifndef __AST_REFER_H__
+#define __AST_REFER_H__
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*!
+ * \brief A refer structure.
+ *
+ * This is an opaque type that represents a refer.
+ */
+struct ast_refer;
+
+/*!
+ * \brief A refer technology
+ *
+ * A refer technology is capable of transmitting text refers.
+ */
+struct ast_refer_tech {
+ /*!
+ * \brief Name of this refer technology
+ *
+ * This is the name that comes at the beginning of a URI for refers
+ * that should be sent to this refer technology implementation.
+ * For example, refers sent to "pjsip:m.fridrich@commend.com" would be
+ * passed to the ast_refer_tech with a name of "pjsip".
+ */
+ const char * const name;
+ /*!
+ * \brief Send a refer.
+ *
+ * \param refer The refer to send
+ *
+ * The fields of the ast_refer are guaranteed not to change during the
+ * duration of this function call.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+ int (* const refer_send)(const struct ast_refer *refer);
+};
+
+/*!
+ * \brief Register a refer technology
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_register(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Unregister a refer technology.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Allocate a refer.
+ *
+ * Allocate a refer for the purposes of passing it into the Asterisk core
+ * to be routed through the dialplan. This refer must be destroyed using
+ * ast_refer_destroy().
+ *
+ * \return A refer object. This function will return NULL if an allocation
+ * error occurs.
+ */
+struct ast_refer *ast_refer_alloc(void);
+
+/*!
+ * \brief Destroy an ast_refer
+ *
+ * \retval NULL always.
+ */
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer);
+
+/*!
+ * \brief Bump a refer's ref count
+ */
+struct ast_refer *ast_refer_ref(struct ast_refer *refer);
+
+/*!
+ * \brief Set the 'to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+ ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'from' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+ ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'refer_to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+ ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'to_self' value of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_to_self(struct ast_refer *refer, int val);
+
+/*!
+ * \brief Set the technology associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+ ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the technology's endpoint associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+ ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set a variable on the refer being sent to a refer tech directly.
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param refer
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value);
+
+/*!
+ * \brief Get the specified variable on the refer and unlink it from the container of variables
+ * \note The return value must be freed by the caller.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the specified variable on the refer
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ * to the refer if you plan on storing the return value. It is possible to re-set the
+ * same refer var name (with ast_refer_set_var_outbound passing the variable name)
+ * while holding a pointer to the result of this function.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the "refer-to" value of a refer.
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ * to the refer if you plan on storing the return value.
+ *
+ * \param refer The refer to get the "refer-to" value from
+ *
+ * \return The "refer-to" value of the refer, encoded in UTF-8.
+ */
+const char *ast_refer_get_refer_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the source of this refer
+ *
+ * \param refer The refer to get the soure from
+ *
+ * \return The source of the refer
+ * \retval NULL or empty string if the refer has no source
+ */
+const char *ast_refer_get_from(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the destination of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The destination of the refer
+ * \retval NULL or empty string if the refer has no destination
+ */
+const char *ast_refer_get_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the "to_self" value of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The to_self value of the refer
+ */
+int ast_refer_get_to_self(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the technology associated with this refer
+ *
+ * \param refer The refer to get the technology from
+ *
+ * \return The technology of the refer
+ * \retval NULL or empty string if the refer has no associated technology
+ */
+const char *ast_refer_get_tech(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the endpoint associated with this refer
+ *
+ * \param refer The refer to get the endpoint from
+ *
+ * \return The endpoint associated with the refer
+ * \retval NULL or empty string if the refer has no associated endpoint
+ */
+const char *ast_refer_get_endpoint(const struct ast_refer *refer);
+
+/*!
+ * \brief Send a refer directly to an endpoint.
+ *
+ * Regardless of the return value of this function, this function will take
+ * care of ensuring that the refer object is properly destroyed when needed.
+ *
+ * \retval 0 refer successfully queued to be sent out
+ * \retval non-zero failure, refer not get sent out.
+ */
+int ast_refer_send(struct ast_refer *refer);
+
+/*!
+ * \brief Opaque iterator for refer variables
+ */
+struct ast_refer_var_iterator;
+
+/*!
+ * \brief Create a new refer variable iterator
+ * \param refer A refer whose variables are to be iterated over
+ *
+ * \return An opaque pointer to the new iterator
+ */
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer);
+
+/*!
+ * \brief Get the next variable name and value
+ *
+ * \param iter An iterator created with ast_refer_var_iterator_init
+ * \param name A pointer to the name result pointer
+ * \param value A pointer to the value result pointer
+ *
+ * \note The refcount to iter->current_used must be decremented by the caller
+ * by calling ast_refer_var_unref_current.
+ *
+ * \retval 0 No more entries
+ * \retval 1 Valid entry
+ */
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value);
+
+/*!
+ * \brief Destroy a refer variable iterator
+ * \param iter Iterator to be destroyed
+ */
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter);
+
+/*!
+ * \brief Unref a refer var from inside an iterator loop
+ */
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter);
+
+/*!
+ * @}
+ */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_REFER_H__ */
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
*/
* \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
*/
void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
const struct ast_party_id *id);
+/*!
+ * \brief Retrieves an endpoint and URI from the "to" string.
+ *
+ * This URI is used as the Request URI.
+ *
+ * Expects the given 'to' to be in one of the following formats:
+ * Why we allow so many is a mystery.
+ *
+ * Basic:
+ *
+ * endpoint : We'll get URI from the default aor/contact
+ * endpoint/aor : We'll get the URI from the specific aor/contact
+ * endpoint@domain : We toss the domain part and just use the endpoint
+ *
+ * These all use the endpoint and specified URI:
+ * \verbatim
+ endpoint/<sip[s]:host>
+ endpoint/<sip[s]:user@host>
+ endpoint/"Bob" <sip[s]:host>
+ endpoint/"Bob" <sip[s]:user@host>
+ endpoint/sip[s]:host
+ endpoint/sip[s]:user@host
+ endpoint/host
+ endpoint/user@host
+ \endverbatim
+ *
+ * These all use the default endpoint and specified URI:
+ * \verbatim
+ <sip[s]:host>
+ <sip[s]:user@host>
+ "Bob" <sip[s]:host>
+ "Bob" <sip[s]:user@host>
+ sip[s]:host
+ sip[s]:user@host
+ \endverbatim
+ *
+ * These use the default endpoint and specified host:
+ * \verbatim
+ host
+ user@host
+ \endverbatim
+ *
+ * This form is similar to a dialstring:
+ * \verbatim
+ PJSIP/user@endpoint
+ \endverbatim
+ *
+ * In this case, the user will be added to the endpoint contact's URI.
+ * If the contact URI already has a user, it will be replaced.
+ *
+ * The ones that have the sip[s] scheme are the easiest to parse.
+ * The rest all have some issue.
+ *
+ * endpoint vs host : We have to test for endpoint first
+ * endpoint/aor vs endpoint/host : We have to test for aor first
+ * What if there's an aor with the same
+ * name as the host?
+ * endpoint@domain vs user@host : We have to test for endpoint first.
+ * What if there's an endpoint with the
+ * same name as the user?
+ *
+ * \param to 'To' field with possible endpoint
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * outbound endpoint if no endpoint was found.
+ * Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to a char* which will be set to the URI.
+ * Always must be ast_free'd by the caller - even if the return value is NULL!
+ *
+ * \note The logic below could probably be condensed but then it wouldn't be
+ * as clear.
+ */
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri);
+
+/*!
+ * \brief Replace the To URI in the tdata with the supplied one
+ *
+ * \param tdata the outbound message data structure
+ * \param to URI to replace the To URI with. Must be a valid SIP URI.
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to);
+
+/*!
+ * \brief Overwrite fields in the outbound 'From' header
+ *
+ * The outbound 'From' header is created/added in ast_sip_create_request with
+ * default data. If available that data may be info specified in the 'from_user'
+ * and 'from_domain' options found on the endpoint. That information will be
+ * overwritten with data in the given 'from' parameter.
+ *
+ * \param tdata the outbound message data structure
+ * \param from info to copy into the header.
+ * Can be either a SIP URI, or in the format user[@domain]
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from);
+
/*!
* \brief Retrieve the unidentified request security event thresholds
* \since 13.8.0
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");
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/_private.h"
+
+#include "asterisk/module.h"
+#include "asterisk/datastore.h"
+#include "asterisk/pbx.h"
+#include "asterisk/manager.h"
+#include "asterisk/strings.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/vector.h"
+#include "asterisk/app.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/refer.h"
+
+struct refer_data {
+ /* Stored in stuff[] at struct end */
+ char *name;
+ /* Stored separately */
+ char *value;
+ /* Holds name */
+ char stuff[0];
+};
+
+/*!
+ * \brief A refer.
+ */
+struct ast_refer {
+ AST_DECLARE_STRING_FIELDS(
+ /*! Where the refer is going */
+ AST_STRING_FIELD(to);
+ /*! Where we "say" the refer came from */
+ AST_STRING_FIELD(from);
+ /*! Where to refer to */
+ AST_STRING_FIELD(refer_to);
+ /*! An endpoint associated with this refer */
+ AST_STRING_FIELD(endpoint);
+ /*! The technology of the endpoint associated with this refer */
+ AST_STRING_FIELD(tech);
+ );
+ /* Whether to refer to Asterisk itself, if refer_to is an Asterisk endpoint. */
+ int to_self;
+ /*! Technology/dialplan specific variables associated with the refer */
+ struct ao2_container *vars;
+};
+
+/*! \brief Lock for \c refer_techs vector */
+static ast_rwlock_t refer_techs_lock;
+
+/*! \brief Vector of refer technologies */
+AST_VECTOR(, const struct ast_refer_tech *) refer_techs;
+
+static int refer_data_cmp_fn(void *obj, void *arg, int flags)
+{
+ const struct refer_data *object_left = obj;
+ const struct refer_data *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->name;
+ case OBJ_SEARCH_KEY:
+ cmp = strcasecmp(object_left->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncasecmp(object_left->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ if (cmp) {
+ return 0;
+ }
+ return CMP_MATCH;
+}
+
+static void refer_data_destructor(void *obj)
+{
+ struct refer_data *data = obj;
+ ast_free(data->value);
+ ast_free(data);
+}
+
+static void refer_destructor(void *obj)
+{
+ struct ast_refer *refer = obj;
+
+ ast_string_field_free_memory(refer);
+ ao2_cleanup(refer->vars);
+}
+
+struct ast_refer *ast_refer_alloc(void)
+{
+ struct ast_refer *refer;
+
+ if (!(refer = ao2_alloc_options(sizeof(*refer), refer_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(refer, 128)) {
+ ao2_ref(refer, -1);
+ return NULL;
+ }
+
+ refer->vars = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+ NULL, refer_data_cmp_fn);
+ if (!refer->vars) {
+ ao2_ref(refer, -1);
+ return NULL;
+ }
+ refer->to_self = 0;
+
+ return refer;
+}
+
+struct ast_refer *ast_refer_ref(struct ast_refer *refer)
+{
+ ao2_ref(refer, 1);
+ return refer;
+}
+
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer)
+{
+ ao2_ref(refer, -1);
+ return NULL;
+}
+
+int ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ast_string_field_build_va(refer, to, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+int ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ast_string_field_build_va(refer, from, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+int ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ast_string_field_build_va(refer, refer_to, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+int ast_refer_set_to_self(struct ast_refer *refer, int val)
+{
+ refer->to_self = val;
+ return 0;
+}
+
+int ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ast_string_field_build_va(refer, tech, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+int ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ast_string_field_build_va(refer, endpoint, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+const char *ast_refer_get_refer_to(const struct ast_refer *refer)
+{
+ return refer->refer_to;
+}
+
+const char *ast_refer_get_from(const struct ast_refer *refer)
+{
+ return refer->from;
+}
+
+const char *ast_refer_get_to(const struct ast_refer *refer)
+{
+ return refer->to;
+}
+
+int ast_refer_get_to_self(const struct ast_refer *refer)
+{
+ return refer->to_self;
+}
+
+const char *ast_refer_get_tech(const struct ast_refer *refer)
+{
+ return refer->tech;
+}
+
+const char *ast_refer_get_endpoint(const struct ast_refer *refer)
+{
+ return refer->endpoint;
+}
+
+static struct refer_data *refer_data_new(const char *name)
+{
+ struct refer_data *data;
+ int name_len = strlen(name) + 1;
+
+ if ((data = ao2_alloc_options(name_len + sizeof(*data), refer_data_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+ data->name = data->stuff;
+ strcpy(data->name, name);
+ }
+
+ return data;
+}
+
+static struct refer_data *refer_data_find(struct ao2_container *vars, const char *name)
+{
+ return ao2_find(vars, name, OBJ_SEARCH_KEY);
+}
+
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name)
+{
+ struct refer_data *data;
+ char *val = NULL;
+
+ if (!(data = ao2_find(refer->vars, name, OBJ_SEARCH_KEY | OBJ_UNLINK))) {
+ return NULL;
+ }
+
+ val = ast_strdup(data->value);
+ ao2_ref(data, -1);
+
+ return val;
+}
+
+static int refer_set_var_full(struct ast_refer *refer, const char *name, const char *value)
+{
+ struct refer_data *data;
+
+ if (!(data = refer_data_find(refer->vars, name))) {
+ if (ast_strlen_zero(value)) {
+ return 0;
+ }
+ if (!(data = refer_data_new(name))) {
+ return -1;
+ };
+ data->value = ast_strdup(value);
+
+ ao2_link(refer->vars, data);
+ } else {
+ if (ast_strlen_zero(value)) {
+ ao2_unlink(refer->vars, data);
+ } else {
+ data->value = ast_strdup(value);
+ }
+ }
+
+ ao2_ref(data, -1);
+
+ return 0;
+}
+
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value)
+{
+ return refer_set_var_full(refer, name, value);
+}
+
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name)
+{
+ struct refer_data *data;
+ const char *val = NULL;
+
+ if (!(data = refer_data_find(refer->vars, name))) {
+ return NULL;
+ }
+
+ val = data->value;
+ ao2_ref(data, -1);
+
+ return val;
+}
+
+struct ast_refer_var_iterator {
+ struct ao2_iterator iter;
+ struct refer_data *current_used;
+};
+
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer)
+{
+ struct ast_refer_var_iterator *iter;
+
+ iter = ast_calloc(1, sizeof(*iter));
+ if (!iter) {
+ return NULL;
+ }
+
+ iter->iter = ao2_iterator_init(refer->vars, 0);
+
+ return iter;
+}
+
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value)
+{
+ struct refer_data *data;
+
+ if (!iter) {
+ return 0;
+ }
+
+ data = ao2_iterator_next(&iter->iter);
+ if (!data) {
+ return 0;
+ }
+
+ *name = data->name;
+ *value = data->value;
+
+ iter->current_used = data;
+
+ return 1;
+}
+
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter)
+{
+ ao2_cleanup(iter->current_used);
+ iter->current_used = NULL;
+}
+
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter)
+{
+ if (iter) {
+ ao2_iterator_destroy(&iter->iter);
+ ast_refer_var_unref_current(iter);
+ ast_free(iter);
+ }
+}
+
+/*!
+ * \internal \brief Find a \c ast_refer_tech by its technology name
+ *
+ * \param tech_name The name of the refer technology
+ *
+ * \note \c refer_techs should be locked via \c refer_techs_lock prior to
+ * calling this function
+ *
+ * \retval NULL if no \ref ast_refer_tech has been registered
+ * \return \ref ast_refer_tech if registered
+ */
+static const struct ast_refer_tech *refer_find_by_tech_name(const char *tech_name)
+{
+ const struct ast_refer_tech *current;
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&refer_techs); i++) {
+ current = AST_VECTOR_GET(&refer_techs, i);
+ if (!strcmp(current->name, tech_name)) {
+ return current;
+ }
+ }
+
+ return NULL;
+}
+
+int ast_refer_send(struct ast_refer *refer)
+{
+ char *tech_name = NULL;
+ const struct ast_refer_tech *refer_tech;
+ int res = -1;
+
+ if (ast_strlen_zero(refer->to)) {
+ ao2_ref(refer, -1);
+ return -1;
+ }
+
+ tech_name = ast_strdupa(refer->to);
+ tech_name = strsep(&tech_name, ":");
+
+ ast_rwlock_rdlock(&refer_techs_lock);
+ refer_tech = refer_find_by_tech_name(tech_name);
+
+ if (!refer_tech) {
+ ast_log(LOG_ERROR, "Unknown refer tech: %s\n", tech_name);
+ ast_rwlock_unlock(&refer_techs_lock);
+ ao2_ref(refer, -1);
+ return -1;
+ }
+
+ ao2_lock(refer);
+ res = refer_tech->refer_send(refer);
+ ao2_unlock(refer);
+
+ ast_rwlock_unlock(&refer_techs_lock);
+
+ ao2_ref(refer, -1);
+
+ return res;
+}
+
+int ast_refer_tech_register(const struct ast_refer_tech *tech)
+{
+ const struct ast_refer_tech *match;
+
+ ast_rwlock_wrlock(&refer_techs_lock);
+
+ match = refer_find_by_tech_name(tech->name);
+ if (match) {
+ ast_log(LOG_ERROR, "Refer technology already registered for '%s'\n",
+ tech->name);
+ ast_rwlock_unlock(&refer_techs_lock);
+ return -1;
+ }
+
+ if (AST_VECTOR_APPEND(&refer_techs, tech)) {
+ ast_log(LOG_ERROR, "Failed to register refer technology for '%s'\n",
+ tech->name);
+ ast_rwlock_unlock(&refer_techs_lock);
+ return -1;
+ }
+ ast_verb(3, "Refer technology '%s' registered.\n", tech->name);
+
+ ast_rwlock_unlock(&refer_techs_lock);
+
+ return 0;
+}
+
+/*!
+ * \brief Comparison callback for \c ast_refer_tech vector removal
+ *
+ * \param vec_elem The element in the vector being compared
+ * \param srch The element being looked up
+ *
+ * \retval non-zero The items are equal
+ * \retval 0 The items are not equal
+ */
+static int refer_tech_cmp(const struct ast_refer_tech *vec_elem, const struct ast_refer_tech *srch)
+{
+ if (!vec_elem->name || !srch->name) {
+ return (vec_elem->name == srch->name) ? 1 : 0;
+ }
+ return !strcmp(vec_elem->name, srch->name);
+}
+
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech)
+{
+ int match;
+
+ ast_rwlock_wrlock(&refer_techs_lock);
+ match = AST_VECTOR_REMOVE_CMP_UNORDERED(&refer_techs, tech, refer_tech_cmp,
+ AST_VECTOR_ELEM_CLEANUP_NOOP);
+ ast_rwlock_unlock(&refer_techs_lock);
+
+ if (match) {
+ ast_log(LOG_ERROR, "No '%s' refer technology found.\n", tech->name);
+ return -1;
+ }
+
+ ast_verb(2, "Refer technology '%s' unregistered.\n", tech->name);
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Clean up other resources on Asterisk shutdown
+ */
+static void refer_shutdown(void)
+{
+ AST_VECTOR_FREE(&refer_techs);
+ ast_rwlock_destroy(&refer_techs_lock);
+}
+
+/*!
+ * \internal
+ * \brief Initialize stuff during Asterisk startup.
+ *
+ * Cleanup isn't a big deal in this function. If we return non-zero,
+ * Asterisk is going to exit.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_init(void)
+{
+ ast_rwlock_init(&refer_techs_lock);
+ if (AST_VECTOR_INIT(&refer_techs, 8)) {
+ return -1;
+ }
+ ast_register_cleanup(refer_shutdown);
+ return 0;
+}
#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,
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);
+}
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;
};
/*!
* \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,...) */
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;
};
/*!
* \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 */
}
#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;
}
}
#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;
}
.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 = {
.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 = {
.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)
#include <pjmedia/errno.h>
#include "asterisk/res_pjsip.h"
+#include "asterisk/strings.h"
+#include "pjsip/sip_parser.h"
#include "res_pjsip/include/res_pjsip_private.h"
#include "asterisk/linkedlists.h"
#include "asterisk/logger.h"
#include "asterisk/res_pjsip_presence_xml.h"
#include "asterisk/res_pjproject.h"
#include "asterisk/utf8.h"
+#include "asterisk/acl.h"
/*** MODULEINFO
<depend>pjproject</depend>
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)
{
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);
/* 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;
{ "PUBLISH", &pjsip_publish_method },
{ "INFO", &info_method },
{ "MESSAGE", &message_method },
+ { "REFER", &refer_method },
};
static const pjsip_method *get_pjsip_method(const char *method)
}
}
+/*!
+ * \brief Find a contact and insert a "user@" into its URI.
+ *
+ * \param to Original destination (for error messages only)
+ * \param endpoint_name Endpoint name (for error messages only)
+ * \param aors Command separated list of AORs
+ * \param user The user to insert in the contact URI
+ * \param uri Pointer to buffer in which to return the URI. Must be freed by caller.
+ *
+ * \return 0 Success
+ * \return -1 Fail
+ *
+ * \note If the contact URI found for the endpoint already has a user in
+ * its URI, it will be replaced by the user passed as an argument to this function.
+ */
+static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
+ const char *user, char **uri)
+{
+ RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+ pj_pool_t *pool;
+ pjsip_name_addr *name_addr;
+ pjsip_sip_uri *sip_uri;
+ int err = 0;
+
+ contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
+ if (!contact) {
+ ast_log(LOG_WARNING, "Dest: '%s'. Couldn't find contact for endpoint '%s'\n",
+ to, endpoint_name);
+ return -1;
+ }
+
+ pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri-user-insert", 128, 128);
+ if (!pool) {
+ ast_log(LOG_WARNING, "Failed to allocate ParseUri endpoint pool.\n");
+ return -1;
+ }
+
+ name_addr = (pjsip_name_addr *) pjsip_parse_uri(pool, (char*)contact->uri, strlen(contact->uri), PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!name_addr || (!PJSIP_URI_SCHEME_IS_SIP(name_addr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(name_addr->uri))) {
+ ast_log(LOG_WARNING, "Failed to parse URI '%s'\n", contact->uri);
+ err = -1;
+ goto out;
+ }
+
+ ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact->uri);
+
+ sip_uri = pjsip_uri_get_uri(name_addr->uri);
+ pj_strset2(&sip_uri->user, (char*)user);
+
+ *uri = ast_malloc(PJSIP_MAX_URL_SIZE);
+ if (!(*uri)) {
+ err = -1;
+ goto out;
+ }
+ pjsip_uri_print(PJSIP_URI_IN_REQ_URI, name_addr, *uri, PJSIP_MAX_URL_SIZE);
+
+out:
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+ return err;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination is only a single token
+ *
+ * "destination" could be one of the following:
+ * \verbatim
+ endpoint_name
+ hostname
+ * \endverbatim
+ *
+ * \param to
+ * \param destination
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * outbound endpoint if no endpoint was found.
+ * Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, int get_default_outbound, char **uri) {
+ RAII_VAR(struct ast_sip_contact*, contact, NULL, ao2_cleanup);
+ char *endpoint_name = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
+
+ /*
+ * If "destination" is just one token, it could be an endpoint name
+ * or a hostname without a scheme.
+ */
+
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
+ if (!endpoint) {
+ /*
+ * We can only assume it's a hostname.
+ */
+ char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+ if (!temp_uri) {
+ goto failure;
+ }
+ sprintf(temp_uri, "sip:%s", destination);
+ *uri = temp_uri;
+ if (get_default_outbound) {
+ endpoint = ast_sip_default_outbound_endpoint();
+ }
+ ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s'%s\n",
+ to, *uri, get_default_outbound ? " with default endpoint" : "");
+ return endpoint;
+ }
+
+ /*
+ * It's an endpoint
+ */
+
+ endpoint_name = destination;
+ contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ if (!contact) {
+ ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find an aor/contact for it\n",
+ to, endpoint_name);
+ ao2_cleanup(endpoint);
+ goto failure;
+ }
+
+ *uri = ast_strdup(contact->uri);
+ if (!(*uri)) {
+ ao2_cleanup(endpoint);
+ goto failure;
+ }
+
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
+ to, endpoint_name, *uri);
+ return endpoint;
+
+failure:
+ *uri = NULL;
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '/'
+ *
+ * "to" could be one of the following:
+ * \verbatim
+ endpoint/aor
+ endpoint/<sip[s]:host>
+ endpoint/<sip[s]:user@host>
+ endpoint/"Bob" <sip[s]:host>
+ endpoint/"Bob" <sip[s]:user@host>
+ endpoint/sip[s]:host
+ endpoint/sip[s]:user@host
+ endpoint/host
+ endpoint/user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
+ char *slash, char *atsign, char *scheme)
+{
+ char *endpoint_name = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
+ struct ast_sip_contact *contact = NULL;
+ char *user = NULL;
+ char *afterslash = slash + 1;
+ struct ast_sip_aor *aor;
+
+ if (ast_begins_with(destination, "PJSIP/")) {
+ ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
+ /*
+ * This has to be the form PJSIP/user@endpoint
+ */
+ if (!atsign || strchr(afterslash, '/')) {
+ /*
+ * If there's no "user@" or there's a slash somewhere after
+ * "PJSIP/" then we go no further.
+ */
+ ast_log(LOG_WARNING,
+ "Dest: '%s'. Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
+ to);
+ goto failure;
+ }
+ *atsign = '\0';
+ user = afterslash;
+ endpoint_name = atsign + 1;
+ ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
+ } else {
+ /*
+ * Either...
+ * endpoint/aor
+ * endpoint/uri
+ */
+ *slash = '\0';
+ endpoint_name = destination;
+ ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
+ }
+
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+ if (!endpoint) {
+ ast_log(LOG_WARNING, "Dest: '%s'. Didn't find endpoint with name '%s'\n",
+ to, endpoint_name);
+ goto failure;
+ }
+
+ if (scheme) {
+ /*
+ * If we found a scheme, then everything after the slash MUST be a URI.
+ * We don't need to do any further modification.
+ */
+ *uri = ast_strdup(afterslash);
+ if (!(*uri)) {
+ goto failure;
+ }
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
+ to, endpoint_name, *uri);
+ return endpoint;
+ }
+
+ if (user) {
+ /*
+ * This has to be the form PJSIP/user@endpoint
+ */
+ int rc;
+
+ /*
+ * Set the return URI to be the endpoint's contact URI with the user
+ * portion set to the user that was specified before the endpoint name.
+ */
+ rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
+ if (rc != 0) {
+ /*
+ * insert_user_in_contact_uri prints the warning message.
+ */
+ goto failure;
+ }
+ ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
+ endpoint_name, *uri);
+
+ return endpoint;
+ }
+
+ /*
+ * We're now left with two possibilities...
+ * endpoint/aor
+ * endpoint/uri-without-scheme
+ */
+ aor = ast_sip_location_retrieve_aor(afterslash);
+ if (!aor) {
+ /*
+ * It's probably a URI without a scheme but we don't have a way to tell
+ * for sure. We're going to assume it is and prepend it with a scheme.
+ */
+ *uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
+ if (!(*uri)) {
+ goto failure;
+ }
+ sprintf(*uri, "sip:%s", afterslash);
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
+ to, endpoint_name, *uri);
+ return endpoint;
+ }
+
+ /*
+ * Only one possibility left... There was an aor name after the slash.
+ */
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
+ to, endpoint_name, ast_sorcery_object_get_id(aor));
+
+ contact = ast_sip_location_retrieve_first_aor_contact(aor);
+ if (!contact) {
+ ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact for aor '%s'\n",
+ to, endpoint_name, ast_sorcery_object_get_id(aor));
+ ao2_cleanup(aor);
+ goto failure;
+ }
+
+ *uri = ast_strdup(contact->uri);
+ ao2_cleanup(contact);
+ ao2_cleanup(aor);
+ if (!(*uri)) {
+ goto failure;
+ }
+
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
+ to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
+
+ return endpoint;
+
+failure:
+ ao2_cleanup(endpoint);
+ *uri = NULL;
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '@' but no '/' or scheme
+ *
+ * "to" could be one of the following:
+ * \verbatim
+ <sip[s]:user@host>
+ "Bob" <sip[s]:user@host>
+ sip[s]:user@host
+ user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * outbound endpoint if no endpoint was found.
+ * Otherwise, return NULL if no endpoint was found.
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
+ char *slash, char *atsign, char *scheme, int get_default_outbound)
+{
+ char *endpoint_name = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
+ struct ast_sip_contact *contact = NULL;
+ char *afterat = atsign + 1;
+
+ *atsign = '\0';
+ endpoint_name = destination;
+
+ /* Apparently there may be ';<user_options>' after the endpoint name ??? */
+ AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+ if (!endpoint) {
+ /*
+ * It's probably a uri with a user but without a scheme but we don't have a way to tell.
+ * We're going to assume it is and prepend it with a scheme.
+ */
+ *uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
+ if (!(*uri)) {
+ goto failure;
+ }
+ sprintf(*uri, "sip:%s", to);
+ if (get_default_outbound) {
+ endpoint = ast_sip_default_outbound_endpoint();
+ }
+ ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s'%s\n",
+ to, *uri, get_default_outbound ? " with default endpoint" : "");
+ return endpoint;
+ }
+
+ /*
+ * OK, it's an endpoint and a domain (which we ignore)
+ */
+ contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ if (!contact) {
+ ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact\n",
+ to, endpoint_name);
+ goto failure;
+ }
+
+ *uri = ast_strdup(contact->uri);
+ ao2_cleanup(contact);
+ if (!(*uri)) {
+ goto failure;
+ }
+ ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
+ to, endpoint_name, *uri, afterat);
+
+ return endpoint;
+
+failure:
+ ao2_cleanup(endpoint);
+ *uri = NULL;
+ return NULL;
+}
+
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri)
+{
+ char *destination;
+ char *slash = NULL;
+ char *atsign = NULL;
+ char *scheme = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
+
+ destination = ast_strdupa(to);
+
+ slash = strchr(destination, '/');
+ atsign = strchr(destination, '@');
+ scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
+
+ if (!slash && !atsign && !scheme) {
+ /*
+ * If there's only a single token, it can be either...
+ * endpoint
+ * host
+ */
+ return handle_single_token(to, destination, get_default_outbound, uri);
+ }
+
+ if (slash) {
+ /*
+ * If there's a '/', then the form must be one of the following...
+ * PJSIP/user@endpoint
+ * endpoint/aor
+ * endpoint/uri
+ */
+ return handle_slash(to, destination, uri, slash, atsign, scheme);
+ }
+
+ if (atsign && !scheme) {
+ /*
+ * If there's an '@' but no scheme then it's either following an endpoint name
+ * and being followed by a domain name (which we discard).
+ * OR is's a user@host uri without a scheme. It's probably the latter but because
+ * endpoint@domain looks just like user@host, we'll test for endpoint first.
+ */
+ return handle_atsign(to, destination, uri, slash, atsign, scheme, get_default_outbound);
+ }
+
+ /*
+ * If all else fails, we assume it's a URI or just a hostname.
+ */
+ if (scheme) {
+ *uri = ast_strdup(destination);
+ if (!(*uri)) {
+ goto failure;
+ }
+ ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s'%s\n",
+ to, *uri, get_default_outbound ? " with default endpoint" : "");
+ } else {
+ *uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+ if (!(*uri)) {
+ goto failure;
+ }
+ sprintf(*uri, "sip:%s", destination);
+ ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s'%s\n",
+ to, *uri, get_default_outbound ? " with default endpoint" : "");
+ }
+ if (get_default_outbound) {
+ endpoint = ast_sip_default_outbound_endpoint();
+ }
+
+ return endpoint;
+
+failure:
+ ao2_cleanup(endpoint);
+ *uri = NULL;
+ return NULL;
+}
+
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to)
+{
+ pjsip_name_addr *parsed_name_addr;
+ pjsip_sip_uri *sip_uri;
+ pjsip_name_addr *tdata_name_addr;
+ pjsip_sip_uri *tdata_sip_uri;
+ pjsip_to_hdr *to_hdr;
+ char *buf = NULL;
+#define DEBUG_BUF_SIZE 256
+
+ parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, (char*)to, strlen(to),
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+
+ if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+ && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
+ ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
+ return -1;
+ }
+
+ sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+ if (DEBUG_ATLEAST(3)) {
+ buf = ast_alloca(DEBUG_BUF_SIZE);
+ pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
+ ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
+ parsed_name_addr->display.ptr, buf);
+ }
+
+ to_hdr = PJSIP_MSG_TO_HDR(tdata->msg);
+ tdata_name_addr = to_hdr ? (pjsip_name_addr *) to_hdr->uri : NULL;
+ if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
+ && !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
+ /* Highly unlikely but we have to check */
+ ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
+ return -1;
+ }
+
+ tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
+ if (DEBUG_ATLEAST(3)) {
+ buf[0] = '\0';
+ pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
+ ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
+ tdata_name_addr->display.ptr, buf);
+ }
+
+ /* Replace the uri */
+ pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
+ /* The display name isn't part of the URI so we need to replace it separately */
+ pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
+
+ if (DEBUG_ATLEAST(3)) {
+ buf[0] = '\0';
+ pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
+ ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
+ tdata_name_addr->display.ptr, buf);
+ }
+
+ return 0;
+#undef DEBUG_BUF_SIZE
+}
+
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from)
+{
+ pjsip_name_addr *name_addr;
+ pjsip_sip_uri *uri;
+ pjsip_name_addr *parsed_name_addr;
+ pjsip_from_hdr *from_hdr;
+
+ if (ast_strlen_zero(from)) {
+ return 0;
+ }
+
+ from_hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
+ if (!from_hdr) {
+ return -1;
+ }
+ name_addr = (pjsip_name_addr *) from_hdr->uri;
+ uri = pjsip_uri_get_uri(name_addr);
+
+ parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
+ strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (parsed_name_addr) {
+ pjsip_sip_uri *parsed_uri;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+ && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
+ ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
+ return -1;
+ }
+
+ parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+
+ if (pj_strlen(&parsed_name_addr->display)) {
+ pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
+ }
+
+ /* Unlike the To header, we only want to replace the user, host and port */
+ pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
+ pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
+ uri->port = parsed_uri->port;
+
+ return 0;
+ } else {
+ /* assume it is 'user[@domain]' format */
+ char *domain = strchr(from, '@');
+
+ if (domain) {
+ pj_str_t pj_from;
+
+ pj_strset3(&pj_from, from, domain);
+ pj_strdup(tdata->pool, &uri->user, &pj_from);
+
+ pj_strdup2(tdata->pool, &uri->host, domain + 1);
+ } else {
+ pj_strdup2(tdata->pool, &uri->user, from);
+ }
+
+ return 0;
+ }
+
+ return -1;
+}
static void remove_request_headers(pjsip_endpoint *endpt)
{
return res;
}
-/*!
- * \brief Find a contact and insert a "user@" into its URI.
- *
- * \param to Original destination (for error messages only)
- * \param endpoint_name Endpoint name (for error messages only)
- * \param aors Command separated list of AORs
- * \param user The user to insert in the contact URI
- * \param uri Pointer to buffer in which to return the URI
- *
- * \return 0 Success
- * \return -1 Fail
- *
- * \note If the contact URI found for the endpoint already has a user in
- * its URI, it will be replaced.
- */
-static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
- const char *user, char **uri)
-{
- char *scheme = NULL;
- char *contact_uri = NULL;
- char *after_scheme = NULL;
- char *host;
- struct ast_sip_contact *contact = NULL;
-
-
- contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
- if (!contact) {
- /*
- * We're getting the contact using the same method as
- * ast_sip_create_request() so if there's no contact
- * we can never send this message.
- */
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Couldn't find contact for endpoint '%s'\n",
- to, endpoint_name);
- return -1;
- }
-
- contact_uri = ast_strdupa(contact->uri);
- ao2_cleanup(contact);
-
- ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact_uri);
-
- /*
- * Contact URIs must have a scheme so we must insert the user between it and the host.
- */
- scheme = contact_uri;
- after_scheme = strchr(contact_uri, ':');
- if (!after_scheme) {
- /* A contact URI without a scheme? Something's wrong. Bail */
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: There was no scheme in the contact URI '%s'\n",
- to, contact_uri);
- return -1;
- }
- /*
- * Terminate the scheme.
- */
- *after_scheme = '\0';
- after_scheme++;
-
- /*
- * If the contact_uri already has a user, the host starts after the '@', otherwise
- * the host is at after_scheme.
- *
- * We're going to ignore the existing user.
- */
- host = strchr(after_scheme, '@');
- if (host) {
- host++;
- } else {
- host = after_scheme;
- }
-
- *uri = ast_malloc(strlen(scheme) + strlen(user) + strlen(host) + 3 /* One for the ':', '@' and terminating NULL */);
- sprintf(*uri, "%s:%s@%s", scheme, user, host); /* Safe */
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination is only a single token
- *
- * "to" could be one of the following:
- * \verbatim
- endpoint_name
- hostname
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param destination
- * \param uri Pointer to URI variable. Must be freed by caller
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, char **uri) {
- char *endpoint_name = NULL;
- struct ast_sip_endpoint *endpoint = NULL;
- struct ast_sip_contact *contact = NULL;
-
- /*
- * If "to" is just one token, it could be an endpoint name
- * or a hostname without a scheme.
- */
-
- endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
- if (!endpoint) {
- /*
- * We can only assume it's a hostname.
- */
- char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
- sprintf(temp_uri, "sip:%s", destination);
- *uri = temp_uri;
- endpoint = ast_sip_default_outbound_endpoint();
- ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s' with default endpoint\n",
- to, *uri);
- return endpoint;
- }
-
- /*
- * It's an endpoint
- */
-
- endpoint_name = destination;
- contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
- if (!contact) {
- /*
- * We're getting the contact using the same method as
- * ast_sip_create_request() so if there's no contact
- * we can never send this message.
- */
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find an aor/contact for it\n",
- to, endpoint_name);
- ao2_cleanup(endpoint);
- *uri = NULL;
- return NULL;
- }
-
- *uri = ast_strdup(contact->uri);
- ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
- to, endpoint_name, *uri);
- ao2_cleanup(contact);
- return endpoint;
-
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '/'
- *
- * "to" could be one of the following:
- * \verbatim
- endpoint/aor
- endpoint/<sip[s]:host>
- endpoint/<sip[s]:user@host>
- endpoint/"Bob" <sip[s]:host>
- endpoint/"Bob" <sip[s]:user@host>
- endpoint/sip[s]:host
- endpoint/sip[s]:user@host
- endpoint/host
- endpoint/user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable. Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
- char *slash, char *atsign, char *scheme)
-{
- char *endpoint_name = NULL;
- struct ast_sip_endpoint *endpoint = NULL;
- struct ast_sip_contact *contact = NULL;
- char *user = NULL;
- char *afterslash = slash + 1;
- struct ast_sip_aor *aor;
-
- if (ast_begins_with(destination, "PJSIP/")) {
- ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
- /*
- * This has to be the form PJSIP/user@endpoint
- */
- if (!atsign || strchr(afterslash, '/')) {
- /*
- * If there's no "user@" or there's a slash somewhere after
- * "PJSIP/" then we go no further.
- */
- *uri = NULL;
- ast_log(LOG_WARNING,
- "Dest: '%s' MSG SEND FAIL: Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
- to);
- return NULL;
- }
- *atsign = '\0';
- user = afterslash;
- endpoint_name = atsign + 1;
- ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
- } else {
- /*
- * Either...
- * endpoint/aor
- * endpoint/uri
- */
- *slash = '\0';
- endpoint_name = destination;
- ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
- }
-
- endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
- if (!endpoint) {
- *uri = NULL;
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Didn't find endpoint with name '%s'\n",
- to, endpoint_name);
- return NULL;
- }
-
- if (scheme) {
- /*
- * If we found a scheme, then everything after the slash MUST be a URI.
- * We don't need to do any further modification.
- */
- *uri = ast_strdup(afterslash);
- ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
- to, endpoint_name, *uri);
- return endpoint;
- }
-
- if (user) {
- /*
- * This has to be the form PJSIP/user@endpoint
- */
- int rc;
-
- /*
- * Set the return URI to be the endpoint's contact URI with the user
- * portion set to the user that was specified before the endpoint name.
- */
- rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
- if (rc != 0) {
- /*
- * insert_user_in_contact_uri prints the warning message.
- */
- ao2_cleanup(endpoint);
- endpoint = NULL;
- *uri = NULL;
- }
- ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
- endpoint_name, *uri);
-
- return endpoint;
- }
-
- /*
- * We're now left with two possibilities...
- * endpoint/aor
- * endpoint/uri-without-scheme
- */
- aor = ast_sip_location_retrieve_aor(afterslash);
- if (!aor) {
- /*
- * It's probably a URI without a scheme but we don't have a way to tell
- * for sure. We're going to assume it is and prepend it with a scheme.
- */
- *uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
- sprintf(*uri, "sip:%s", afterslash);
- ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
- to, endpoint_name, *uri);
- return endpoint;
- }
-
- /*
- * Only one possibility left... There was an aor name after the slash.
- */
- ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
- to, endpoint_name, ast_sorcery_object_get_id(aor));
-
- contact = ast_sip_location_retrieve_first_aor_contact(aor);
- if (!contact) {
- /*
- * An aor without a contact is useless and since
- * ast_sip_create_message() won't be able to find one
- * either, we just need to bail.
- */
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact for aor '%s'\n",
- to, endpoint_name, ast_sorcery_object_get_id(aor));
- ao2_cleanup(aor);
- ao2_cleanup(endpoint);
- *uri = NULL;
- return NULL;
- }
-
- *uri = ast_strdup(contact->uri);
- ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
- to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
- ao2_cleanup(contact);
- ao2_cleanup(aor);
-
- return endpoint;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '\@' but no '/' or scheme
- *
- * "to" could be one of the following:
- * \verbatim
- <sip[s]:user@host>
- "Bob" <sip[s]:user@host>
- sip[s]:user@host
- user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable. Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
- char *slash, char *atsign, char *scheme)
-{
- char *endpoint_name = NULL;
- struct ast_sip_endpoint *endpoint = NULL;
- struct ast_sip_contact *contact = NULL;
- char *afterat = atsign + 1;
-
- *atsign = '\0';
- endpoint_name = destination;
-
- /* Apparently there may be ';<user_options>' after the endpoint name ??? */
- AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
- endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
- if (!endpoint) {
- /*
- * It's probably a uri with a user but without a scheme but we don't have a way to tell.
- * We're going to assume it is and prepend it with a scheme.
- */
- *uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
- sprintf(*uri, "sip:%s", to);
- endpoint = ast_sip_default_outbound_endpoint();
- ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s' with default endpoint\n",
- to, *uri);
- return endpoint;
- }
-
- /*
- * OK, it's an endpoint and a domain (which we ignore)
- */
- contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
- if (!contact) {
- /*
- * We're getting the contact using the same method as
- * ast_sip_create_request() so if there's no contact
- * we can never send this message.
- */
- ao2_cleanup(endpoint);
- endpoint = NULL;
- *uri = NULL;
- ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact\n",
- to, endpoint_name);
- return NULL;
- }
-
- *uri = ast_strdup(contact->uri);
- ao2_cleanup(contact);
- ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
- to, endpoint_name, *uri, afterat);
-
- return endpoint;
-}
-
-/*!
- * \internal
- * \brief Retrieves an endpoint and URI from the "to" string.
- *
- * This URI is used as the Request URI.
- *
- * Expects the given 'to' to be in one of the following formats:
- * Why we allow so many is a mystery.
- *
- * Basic:
- *
- * endpoint : We'll get URI from the default aor/contact
- * endpoint/aor : We'll get the URI from the specific aor/contact
- * endpoint@domain : We toss the domain part and just use the endpoint
- *
- * These all use the endpoint and specified URI:
- * \verbatim
- endpoint/<sip[s]:host>
- endpoint/<sip[s]:user@host>
- endpoint/"Bob" <sip[s]:host>
- endpoint/"Bob" <sip[s]:user@host>
- endpoint/sip[s]:host
- endpoint/sip[s]:user@host
- endpoint/host
- endpoint/user@host
- \endverbatim
- *
- * These all use the default endpoint and specified URI:
- * \verbatim
- <sip[s]:host>
- <sip[s]:user@host>
- "Bob" <sip[s]:host>
- "Bob" <sip[s]:user@host>
- sip[s]:host
- sip[s]:user@host
- \endverbatim
- *
- * These use the default endpoint and specified host:
- * \verbatim
- host
- user@host
- \endverbatim
- *
- * This form is similar to a dialstring:
- * \verbatim
- PJSIP/user@endpoint
- \endverbatim
- *
- * In this case, the user will be added to the endpoint contact's URI.
- * If the contact URI already has a user, it will be replaced.
- *
- * The ones that have the sip[s] scheme are the easiest to parse.
- * The rest all have some issue.
- *
- * endpoint vs host : We have to test for endpoint first
- * endpoint/aor vs endpoint/host : We have to test for aor first
- * What if there's an aor with the same
- * name as the host?
- * endpoint@domain vs user@host : We have to test for endpoint first.
- * What if there's an endpoint with the
- * same name as the user?
- *
- * \param to 'To' field with possible endpoint
- * \param uri Pointer to a char* which will be set to the URI.
- * Must be ast_free'd by the caller.
- *
- * \note The logic below could probably be condensed but then it wouldn't be
- * as clear.
- */
-static struct ast_sip_endpoint *get_outbound_endpoint(const char *to, char **uri)
-{
- char *destination;
- char *slash = NULL;
- char *atsign = NULL;
- char *scheme = NULL;
- struct ast_sip_endpoint *endpoint = NULL;
-
- destination = ast_strdupa(to);
- slash = strchr(destination, '/');
- atsign = strchr(destination, '@');
- scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
-
- if (!slash && !atsign && !scheme) {
- /*
- * If there's only a single token, it can be either...
- * endpoint
- * host
- */
- return handle_single_token(to, destination, uri);
- }
-
- if (slash) {
- /*
- * If there's a '/', then the form must be one of the following...
- * PJSIP/user@endpoint
- * endpoint/aor
- * endpoint/uri
- */
- return handle_slash(to, destination, uri, slash, atsign, scheme);
- }
-
- if (!endpoint && atsign && !scheme) {
- /*
- * If there's an '@' but no scheme then it's either following an endpoint name
- * and being followed by a domain name (which we discard).
- * OR is's a user@host uri without a scheme. It's probably the latter but because
- * endpoint@domain looks just like user@host, we'll test for endpoint first.
- */
- return handle_atsign(to, destination, uri, slash, atsign, scheme);
- }
-
- /*
- * If all else fails, we assume it's a URI or just a hostname.
- */
- if (scheme) {
- *uri = ast_strdup(destination);
- ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s' with default endpoint\n",
- to, *uri);
- } else {
- *uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
- sprintf(*uri, "sip:%s", destination);
- ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s' with default endpoint\n",
- to, *uri);
- }
- endpoint = ast_sip_default_outbound_endpoint();
-
- return endpoint;
-}
-
-/*!
- * \internal
- * \brief Replace the To URI in the tdata with the supplied one
- *
- * \param tdata the outbound message data structure
- * \param to URI to replace the To URI with
- *
- * \return 0: success, -1: failure
- */
-static int update_to_uri(pjsip_tx_data *tdata, char *to)
-{
- pjsip_name_addr *parsed_name_addr;
- pjsip_sip_uri *sip_uri;
- pjsip_name_addr *tdata_name_addr;
- pjsip_sip_uri *tdata_sip_uri;
- char *buf = NULL;
-#define DEBUG_BUF_SIZE 256
-
- parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, to, strlen(to),
- PJSIP_PARSE_URI_AS_NAMEADDR);
-
- if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
- && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
- ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
- return -1;
- }
-
- sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
- if (DEBUG_ATLEAST(3)) {
- buf = ast_alloca(DEBUG_BUF_SIZE);
- pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
- ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
- parsed_name_addr->display.ptr, buf);
- }
-
- tdata_name_addr = (pjsip_name_addr *) PJSIP_MSG_TO_HDR(tdata->msg)->uri;
- if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
- && !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
- /* Highly unlikely but we have to check */
- ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
- return -1;
- }
-
- tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
- if (DEBUG_ATLEAST(3)) {
- buf[0] = '\0';
- pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
- ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
- tdata_name_addr->display.ptr, buf);
- }
-
- /* Replace the uri */
- pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
- /* The display name isn't part of the URI so we need to replace it separately */
- pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
-
- if (DEBUG_ATLEAST(3)) {
- buf[0] = '\0';
- pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
- ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
- tdata_name_addr->display.ptr, buf);
- }
-
- return 0;
-#undef DEBUG_BUF_SIZE
-}
-
/*!
* \internal
* \brief Update the display name in the To uri in the tdata with the one from the supplied uri
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.
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",
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
}
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
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 */
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;
}
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;
#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 {
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)
{
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);
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;
}
},
{
"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",
}
]
},
+ {
+ "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",
},
{
"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",
]
}
]
+ },
+ {
+ "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": {