]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
core/ari/pjsip: Add refer mechanism
authorMaximilian Fridrich <m.fridrich@commend.com>
Wed, 10 May 2023 13:53:33 +0000 (15:53 +0200)
committerasterisk-org-access-app[bot] <120671045+asterisk-org-access-app[bot]@users.noreply.github.com>
Wed, 9 Aug 2023 15:10:46 +0000 (15:10 +0000)
This change adds support for refers that are not session based. It
includes a refer implementation for the PJSIP technology which results
in out-of-dialog REFERs being sent to a PJSIP endpoint. These can be
triggered using the new ARI endpoint `/endpoints/refer`.

Resolves: #71

UserNote: There is a new ARI endpoint `/endpoints/refer` for referring
an endpoint to some URI or endpoint.

13 files changed:
include/asterisk/_private.h
include/asterisk/refer.h [new file with mode: 0644]
include/asterisk/res_pjsip.h
main/asterisk.c
main/refer.c [new file with mode: 0644]
res/ari/resource_endpoints.c
res/ari/resource_endpoints.h
res/res_ari_endpoints.c
res/res_pjsip.c
res/res_pjsip_messaging.c
res/res_pjsip_nat.c
res/res_pjsip_refer.c
rest-api/api-docs/endpoints.json

index 65e403fb49936994faaadf88fdfdf8095fdf8667..684452fee0c7be4ba0beb40ee1b6c0efcc9ed69f 100644 (file)
@@ -57,6 +57,7 @@ int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
 int aco_init(void);             /*!< Provided by config_options.c */
 int dns_core_init(void);        /*!< Provided by dns_core.c */
+int ast_refer_init(void);             /*!< Provided by refer.c */
 
 /*!
  * \brief Initialize malloc debug phase 1.
diff --git a/include/asterisk/refer.h b/include/asterisk/refer.h
new file mode 100644 (file)
index 0000000..4d0744c
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * The purpose of this API is to provide support for refers that
+ * are not session based. The refers are passed into the Asterisk core
+ * to be routed through the dialplan or another interface and potentially
+ * sent back out through a refer technology that has been registered
+ * through this API.
+ */
+
+#ifndef __AST_REFER_H__
+#define __AST_REFER_H__
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*!
+ * \brief A refer structure.
+ *
+ * This is an opaque type that represents a refer.
+ */
+struct ast_refer;
+
+/*!
+ * \brief A refer technology
+ *
+ * A refer technology is capable of transmitting text refers.
+ */
+struct ast_refer_tech {
+       /*!
+        * \brief Name of this refer technology
+        *
+        * This is the name that comes at the beginning of a URI for refers
+        * that should be sent to this refer technology implementation.
+        * For example, refers sent to "pjsip:m.fridrich@commend.com" would be
+        * passed to the ast_refer_tech with a name of "pjsip".
+        */
+       const char * const name;
+       /*!
+        * \brief Send a refer.
+        *
+        * \param refer The refer to send
+        *
+        * The fields of the ast_refer are guaranteed not to change during the
+        * duration of this function call.
+        *
+        * \retval 0 success
+        * \retval non-zero failure
+        */
+       int (* const refer_send)(const struct ast_refer *refer);
+};
+
+/*!
+ * \brief Register a refer technology
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_register(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Unregister a refer technology.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Allocate a refer.
+ *
+ * Allocate a refer for the purposes of passing it into the Asterisk core
+ * to be routed through the dialplan. This refer must be destroyed using
+ * ast_refer_destroy().
+ *
+ * \return A refer object. This function will return NULL if an allocation
+ *         error occurs.
+ */
+struct ast_refer *ast_refer_alloc(void);
+
+/*!
+ * \brief Destroy an ast_refer
+ *
+ * \retval NULL always.
+ */
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer);
+
+/*!
+ * \brief Bump a refer's ref count
+ */
+struct ast_refer *ast_refer_ref(struct ast_refer *refer);
+
+/*!
+ * \brief Set the 'to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'from' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'refer_to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'to_self' value of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_to_self(struct ast_refer *refer, int val);
+
+/*!
+ * \brief Set the technology associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the technology's endpoint associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set a variable on the refer being sent to a refer tech directly.
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param refer
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value);
+
+/*!
+ * \brief Get the specified variable on the refer and unlink it from the container of variables
+ * \note The return value must be freed by the caller.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the specified variable on the refer
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ *       to the refer if you plan on storing the return value. It is possible to re-set the
+ *       same refer var name (with ast_refer_set_var_outbound passing the variable name)
+ *       while holding a pointer to the result of this function.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the "refer-to" value of a refer.
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ *       to the refer if you plan on storing the return value.
+ *
+ * \param refer The refer to get the "refer-to" value from
+ *
+ * \return The "refer-to" value of the refer, encoded in UTF-8.
+ */
+const char *ast_refer_get_refer_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the source of this refer
+ *
+ * \param refer The refer to get the soure from
+ *
+ * \return The source of the refer
+ * \retval NULL or empty string if the refer has no source
+ */
+const char *ast_refer_get_from(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the destination of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The destination of the refer
+ * \retval NULL or empty string if the refer has no destination
+ */
+const char *ast_refer_get_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the "to_self" value of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The to_self value of the refer
+ */
+int ast_refer_get_to_self(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the technology associated with this refer
+ *
+ * \param refer The refer to get the technology from
+ *
+ * \return The technology of the refer
+ * \retval NULL or empty string if the refer has no associated technology
+ */
+const char *ast_refer_get_tech(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the endpoint associated with this refer
+ *
+ * \param refer The refer to get the endpoint from
+ *
+ * \return The endpoint associated with the refer
+ * \retval NULL or empty string if the refer has no associated endpoint
+ */
+const char *ast_refer_get_endpoint(const struct ast_refer *refer);
+
+/*!
+ * \brief Send a refer directly to an endpoint.
+ *
+ * Regardless of the return value of this function, this function will take
+ * care of ensuring that the refer object is properly destroyed when needed.
+ *
+ * \retval 0 refer successfully queued to be sent out
+ * \retval non-zero failure, refer not get sent out.
+ */
+int ast_refer_send(struct ast_refer *refer);
+
+/*!
+ * \brief Opaque iterator for refer variables
+ */
+struct ast_refer_var_iterator;
+
+/*!
+ * \brief Create a new refer variable iterator
+ * \param refer A refer whose variables are to be iterated over
+ *
+ * \return An opaque pointer to the new iterator
+ */
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer);
+
+/*!
+ * \brief Get the next variable name and value
+ *
+ * \param iter An iterator created with ast_refer_var_iterator_init
+ * \param name A pointer to the name result pointer
+ * \param value A pointer to the value result pointer
+ *
+ * \note The refcount to iter->current_used must be decremented by the caller
+ *       by calling ast_refer_var_unref_current.
+ *
+ * \retval 0 No more entries
+ * \retval 1 Valid entry
+ */
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value);
+
+/*!
+ * \brief Destroy a refer variable iterator
+ * \param iter Iterator to be destroyed
+ */
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter);
+
+/*!
+ * \brief Unref a refer var from inside an iterator loop
+ */
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter);
+
+/*!
+ *  @}
+ */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_REFER_H__ */
index 673ec95505e76554d04573d9a64c4e68c57cbabb..cf95aafda4f5eb00f8b51e7a6812a54241399dd8 100644 (file)
@@ -339,6 +339,20 @@ struct ast_sip_nat_hook {
        void (*outgoing_external_message)(struct pjsip_tx_data *tdata, struct ast_sip_transport *transport);
 };
 
+/*! \brief Structure which contains information about a transport */
+struct ast_sip_request_transport_details {
+       /*! \brief Type of transport */
+       enum ast_transport type;
+       /*! \brief Potential pointer to the transport itself, if UDP */
+       pjsip_transport *transport;
+       /*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
+       pjsip_tpfactory *factory;
+       /*! \brief Local address for transport */
+       pj_str_t local_address;
+       /*! \brief Local port for transport */
+       int local_port;
+};
+
 /*!
  * \brief The kind of security negotiation
  */
@@ -3506,18 +3520,65 @@ struct ast_threadpool *ast_sip_threadpool(void);
  * \brief Retrieve transport state
  * \since 13.7.1
  *
- * @param transport_id
- * @returns transport_state
+ * \param transport_id
+ * \retval transport_state
  *
  * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
  */
 struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id);
 
+/*!
+ * \brief Return the SIP URI of the Contact header
+ * 
+ * \param tdata
+ * \retval Pointer to SIP URI of Contact
+ * \retval NULL if Contact header not found or not a SIP(S) URI
+ *
+ * \note Do not free the returned object.
+ */
+pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata);
+
+/*!
+ * \brief Returns the transport state currently in use based on request transport details
+ *
+ * \param details
+ * \retval transport_state
+ *
+ * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
+ */
+struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details);
+
+/*!
+ * \brief Sets request transport details based on tdata
+ *
+ * \param details pre-allocated request transport details to set
+ * \param tdata
+ * \param use_ipv6 if non-zero, ipv6 transports will be considered
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata, int use_ipv6);
+
+/*!
+ * \brief Replace domain and port of SIP URI to point to (external) signaling address of this Asterisk instance
+ *
+ * \param uri
+ * \param tdata
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Uses domain and port in Contact header if it exists, otherwise the local URI of the dialog is used if the
+ *       message is sent within the context of a dialog. Further, NAT settings are considered - i.e. if the target
+ *       is not in the localnet, the external_signaling_address and port are used.
+ */
+int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata);
+
 /*!
  * \brief Retrieves all transport states
  * \since 13.7.1
  *
- * @returns ao2_container
+ * \retval ao2_container
  *
  * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
  */
@@ -3647,6 +3708,105 @@ int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id
 void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
        const struct ast_party_id *id);
 
+/*!
+ * \brief Retrieves an endpoint and URI from the "to" string.
+ *
+ * This URI is used as the Request URI.
+ *
+ * Expects the given 'to' to be in one of the following formats:
+ * Why we allow so many is a mystery.
+ *
+ * Basic:
+ *
+ *      endpoint        : We'll get URI from the default aor/contact
+ *      endpoint/aor    : We'll get the URI from the specific aor/contact
+ *      endpoint@domain : We toss the domain part and just use the endpoint
+ *
+ *   These all use the endpoint and specified URI:
+ * \verbatim
+        endpoint/<sip[s]:host>
+        endpoint/<sip[s]:user@host>
+        endpoint/"Bob" <sip[s]:host>
+        endpoint/"Bob" <sip[s]:user@host>
+        endpoint/sip[s]:host
+        endpoint/sip[s]:user@host
+        endpoint/host
+        endpoint/user@host
+   \endverbatim
+ *
+ *   These all use the default endpoint and specified URI:
+ * \verbatim
+        <sip[s]:host>
+        <sip[s]:user@host>
+        "Bob" <sip[s]:host>
+        "Bob" <sip[s]:user@host>
+        sip[s]:host
+        sip[s]:user@host
+   \endverbatim
+ *
+ *   These use the default endpoint and specified host:
+ * \verbatim
+        host
+        user@host
+   \endverbatim
+ *
+ *   This form is similar to a dialstring:
+ * \verbatim
+        PJSIP/user@endpoint
+   \endverbatim
+ *
+ *   In this case, the user will be added to the endpoint contact's URI.
+ *   If the contact URI already has a user, it will be replaced.
+ *
+ * The ones that have the sip[s] scheme are the easiest to parse.
+ * The rest all have some issue.
+ *
+ *      endpoint vs host              : We have to test for endpoint first
+ *      endpoint/aor vs endpoint/host : We have to test for aor first
+ *                                      What if there's an aor with the same
+ *                                      name as the host?
+ *      endpoint@domain vs user@host  : We have to test for endpoint first.
+ *                                      What if there's an endpoint with the
+ *                                      same name as the user?
+ *
+ * \param to 'To' field with possible endpoint
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ *                            outbound endpoint if no endpoint was found.
+ *                            Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to a char* which will be set to the URI.
+ *            Always must be ast_free'd by the caller - even if the return value is NULL!
+ *
+ * \note The logic below could probably be condensed but then it wouldn't be
+ * as clear.
+ */
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri);
+
+/*!
+ * \brief Replace the To URI in the tdata with the supplied one
+ *
+ * \param tdata the outbound message data structure
+ * \param to URI to replace the To URI with. Must be a valid SIP URI.
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to);
+
+/*!
+ * \brief Overwrite fields in the outbound 'From' header
+ *
+ * The outbound 'From' header is created/added in ast_sip_create_request with
+ * default data.  If available that data may be info specified in the 'from_user'
+ * and 'from_domain' options found on the endpoint.  That information will be
+ * overwritten with data in the given 'from' parameter.
+ *
+ * \param tdata the outbound message data structure
+ * \param from info to copy into the header.
+ *               Can be either a SIP URI, or in the format user[@domain]
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from);
+
 /*!
  * \brief Retrieve the unidentified request security event thresholds
  * \since 13.8.0
index f96f6b2cadddd6759a2f4e3e9c4540f6ef8ee144..c9b491b040841ef1c749e168b0a9ee8b8c401be7 100644 (file)
@@ -4256,6 +4256,7 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
        check_init(load_pbx_app(), "PBX Application Support");
        check_init(load_pbx_hangup_handler(), "PBX Hangup Handler Support");
        check_init(ast_local_init(), "Local Proxy Channel Driver");
+       check_init(ast_refer_init(), "Refer API");
 
        /* We should avoid most config loads before this point as they can't use realtime. */
        check_init(load_modules(), "Module");
diff --git a/main/refer.c b/main/refer.c
new file mode 100644 (file)
index 0000000..11db065
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/_private.h"
+
+#include "asterisk/module.h"
+#include "asterisk/datastore.h"
+#include "asterisk/pbx.h"
+#include "asterisk/manager.h"
+#include "asterisk/strings.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/vector.h"
+#include "asterisk/app.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/refer.h"
+
+struct refer_data {
+       /* Stored in stuff[] at struct end */
+       char *name;
+       /* Stored separately */
+       char *value;
+       /* Holds name */
+       char stuff[0];
+};
+
+/*!
+ * \brief A refer.
+ */
+struct ast_refer {
+       AST_DECLARE_STRING_FIELDS(
+               /*! Where the refer is going */
+               AST_STRING_FIELD(to);
+               /*! Where we "say" the refer came from */
+               AST_STRING_FIELD(from);
+               /*! Where to refer to */
+               AST_STRING_FIELD(refer_to);
+               /*! An endpoint associated with this refer */
+               AST_STRING_FIELD(endpoint);
+               /*! The technology of the endpoint associated with this refer */
+               AST_STRING_FIELD(tech);
+       );
+       /* Whether to refer to Asterisk itself, if refer_to is an Asterisk endpoint. */
+       int to_self;
+       /*! Technology/dialplan specific variables associated with the refer */
+       struct ao2_container *vars;
+};
+
+/*! \brief Lock for \c refer_techs vector */
+static ast_rwlock_t refer_techs_lock;
+
+/*! \brief Vector of refer technologies */
+AST_VECTOR(, const struct ast_refer_tech *) refer_techs;
+
+static int refer_data_cmp_fn(void *obj, void *arg, int flags)
+{
+       const struct refer_data *object_left = obj;
+       const struct refer_data *object_right = arg;
+       const char *right_key = arg;
+       int cmp;
+
+       switch (flags & OBJ_SEARCH_MASK) {
+       case OBJ_SEARCH_OBJECT:
+               right_key = object_right->name;
+       case OBJ_SEARCH_KEY:
+               cmp = strcasecmp(object_left->name, right_key);
+               break;
+       case OBJ_SEARCH_PARTIAL_KEY:
+               cmp = strncasecmp(object_left->name, right_key, strlen(right_key));
+               break;
+       default:
+               cmp = 0;
+               break;
+       }
+       if (cmp) {
+               return 0;
+       }
+       return CMP_MATCH;
+}
+
+static void refer_data_destructor(void *obj)
+{
+       struct refer_data *data = obj;
+       ast_free(data->value);
+       ast_free(data);
+}
+
+static void refer_destructor(void *obj)
+{
+       struct ast_refer *refer = obj;
+
+       ast_string_field_free_memory(refer);
+       ao2_cleanup(refer->vars);
+}
+
+struct ast_refer *ast_refer_alloc(void)
+{
+       struct ast_refer *refer;
+
+       if (!(refer = ao2_alloc_options(sizeof(*refer), refer_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(refer, 128)) {
+               ao2_ref(refer, -1);
+               return NULL;
+       }
+
+       refer->vars = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+               NULL, refer_data_cmp_fn);
+       if (!refer->vars) {
+               ao2_ref(refer, -1);
+               return NULL;
+       }
+       refer->to_self = 0;
+
+       return refer;
+}
+
+struct ast_refer *ast_refer_ref(struct ast_refer *refer)
+{
+       ao2_ref(refer, 1);
+       return refer;
+}
+
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer)
+{
+       ao2_ref(refer, -1);
+       return NULL;
+}
+
+int ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ast_string_field_build_va(refer, to, fmt, ap);
+       va_end(ap);
+
+       return 0;
+}
+
+int ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ast_string_field_build_va(refer, from, fmt, ap);
+       va_end(ap);
+
+       return 0;
+}
+
+int ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ast_string_field_build_va(refer, refer_to, fmt, ap);
+       va_end(ap);
+
+       return 0;
+}
+
+int ast_refer_set_to_self(struct ast_refer *refer, int val)
+{
+       refer->to_self = val;
+       return 0;
+}
+
+int ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ast_string_field_build_va(refer, tech, fmt, ap);
+       va_end(ap);
+
+       return 0;
+}
+
+int ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ast_string_field_build_va(refer, endpoint, fmt, ap);
+       va_end(ap);
+
+       return 0;
+}
+
+const char *ast_refer_get_refer_to(const struct ast_refer *refer)
+{
+       return refer->refer_to;
+}
+
+const char *ast_refer_get_from(const struct ast_refer *refer)
+{
+       return refer->from;
+}
+
+const char *ast_refer_get_to(const struct ast_refer *refer)
+{
+       return refer->to;
+}
+
+int ast_refer_get_to_self(const struct ast_refer *refer)
+{
+       return refer->to_self;
+}
+
+const char *ast_refer_get_tech(const struct ast_refer *refer)
+{
+       return refer->tech;
+}
+
+const char *ast_refer_get_endpoint(const struct ast_refer *refer)
+{
+       return refer->endpoint;
+}
+
+static struct refer_data *refer_data_new(const char *name)
+{
+       struct refer_data *data;
+       int name_len = strlen(name) + 1;
+
+       if ((data = ao2_alloc_options(name_len + sizeof(*data), refer_data_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+               data->name = data->stuff;
+               strcpy(data->name, name);
+       }
+
+       return data;
+}
+
+static struct refer_data *refer_data_find(struct ao2_container *vars, const char *name)
+{
+       return ao2_find(vars, name, OBJ_SEARCH_KEY);
+}
+
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name)
+{
+       struct refer_data *data;
+       char *val = NULL;
+
+       if (!(data = ao2_find(refer->vars, name, OBJ_SEARCH_KEY | OBJ_UNLINK))) {
+               return NULL;
+       }
+
+       val = ast_strdup(data->value);
+       ao2_ref(data, -1);
+
+       return val;
+}
+
+static int refer_set_var_full(struct ast_refer *refer, const char *name, const char *value)
+{
+       struct refer_data *data;
+
+       if (!(data = refer_data_find(refer->vars, name))) {
+               if (ast_strlen_zero(value)) {
+                       return 0;
+               }
+               if (!(data = refer_data_new(name))) {
+                       return -1;
+               };
+               data->value = ast_strdup(value);
+
+               ao2_link(refer->vars, data);
+       } else {
+               if (ast_strlen_zero(value)) {
+                       ao2_unlink(refer->vars, data);
+               } else {
+                       data->value = ast_strdup(value);
+               }
+       }
+
+       ao2_ref(data, -1);
+
+       return 0;
+}
+
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value)
+{
+       return refer_set_var_full(refer, name, value);
+}
+
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name)
+{
+       struct refer_data *data;
+       const char *val = NULL;
+
+       if (!(data = refer_data_find(refer->vars, name))) {
+               return NULL;
+       }
+
+       val = data->value;
+       ao2_ref(data, -1);
+
+       return val;
+}
+
+struct ast_refer_var_iterator {
+       struct ao2_iterator iter;
+       struct refer_data *current_used;
+};
+
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer)
+{
+       struct ast_refer_var_iterator *iter;
+
+       iter = ast_calloc(1, sizeof(*iter));
+       if (!iter) {
+               return NULL;
+       }
+
+       iter->iter = ao2_iterator_init(refer->vars, 0);
+
+       return iter;
+}
+
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value)
+{
+       struct refer_data *data;
+
+       if (!iter) {
+               return 0;
+       }
+
+       data = ao2_iterator_next(&iter->iter);
+       if (!data) {
+               return 0;
+       }
+
+       *name = data->name;
+       *value = data->value;
+
+       iter->current_used = data;
+
+       return 1;
+}
+
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter)
+{
+       ao2_cleanup(iter->current_used);
+       iter->current_used = NULL;
+}
+
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter)
+{
+       if (iter) {
+               ao2_iterator_destroy(&iter->iter);
+               ast_refer_var_unref_current(iter);
+               ast_free(iter);
+       }
+}
+
+/*!
+ * \internal \brief Find a \c ast_refer_tech by its technology name
+ *
+ * \param tech_name The name of the refer technology
+ *
+ * \note \c refer_techs should be locked via \c refer_techs_lock prior to
+ *       calling this function
+ *
+ * \retval NULL if no \ref ast_refer_tech has been registered
+ * \return \ref ast_refer_tech if registered
+ */
+static const struct ast_refer_tech *refer_find_by_tech_name(const char *tech_name)
+{
+       const struct ast_refer_tech *current;
+       int i;
+
+       for (i = 0; i < AST_VECTOR_SIZE(&refer_techs); i++) {
+               current = AST_VECTOR_GET(&refer_techs, i);
+               if (!strcmp(current->name, tech_name)) {
+                       return current;
+               }
+       }
+
+       return NULL;
+}
+
+int ast_refer_send(struct ast_refer *refer)
+{
+       char *tech_name = NULL;
+       const struct ast_refer_tech *refer_tech;
+       int res = -1;
+
+       if (ast_strlen_zero(refer->to)) {
+               ao2_ref(refer, -1);
+               return -1;
+       }
+
+       tech_name = ast_strdupa(refer->to);
+       tech_name = strsep(&tech_name, ":");
+
+       ast_rwlock_rdlock(&refer_techs_lock);
+       refer_tech = refer_find_by_tech_name(tech_name);
+
+       if (!refer_tech) {
+               ast_log(LOG_ERROR, "Unknown refer tech: %s\n", tech_name);
+               ast_rwlock_unlock(&refer_techs_lock);
+               ao2_ref(refer, -1);
+               return -1;
+       }
+
+       ao2_lock(refer);
+       res = refer_tech->refer_send(refer);
+       ao2_unlock(refer);
+
+       ast_rwlock_unlock(&refer_techs_lock);
+
+       ao2_ref(refer, -1);
+
+       return res;
+}
+
+int ast_refer_tech_register(const struct ast_refer_tech *tech)
+{
+       const struct ast_refer_tech *match;
+
+       ast_rwlock_wrlock(&refer_techs_lock);
+
+       match = refer_find_by_tech_name(tech->name);
+       if (match) {
+               ast_log(LOG_ERROR, "Refer technology already registered for '%s'\n",
+                       tech->name);
+               ast_rwlock_unlock(&refer_techs_lock);
+               return -1;
+       }
+
+       if (AST_VECTOR_APPEND(&refer_techs, tech)) {
+               ast_log(LOG_ERROR, "Failed to register refer technology for '%s'\n",
+                       tech->name);
+               ast_rwlock_unlock(&refer_techs_lock);
+               return -1;
+       }
+       ast_verb(3, "Refer technology '%s' registered.\n", tech->name);
+
+       ast_rwlock_unlock(&refer_techs_lock);
+
+       return 0;
+}
+
+/*!
+ * \brief Comparison callback for \c ast_refer_tech vector removal
+ *
+ * \param vec_elem The element in the vector being compared
+ * \param srch The element being looked up
+ *
+ * \retval non-zero The items are equal
+ * \retval 0 The items are not equal
+ */
+static int refer_tech_cmp(const struct ast_refer_tech *vec_elem, const struct ast_refer_tech *srch)
+{
+       if (!vec_elem->name || !srch->name) {
+               return (vec_elem->name == srch->name) ? 1 : 0;
+       }
+       return !strcmp(vec_elem->name, srch->name);
+}
+
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech)
+{
+       int match;
+
+       ast_rwlock_wrlock(&refer_techs_lock);
+       match = AST_VECTOR_REMOVE_CMP_UNORDERED(&refer_techs, tech, refer_tech_cmp,
+                                               AST_VECTOR_ELEM_CLEANUP_NOOP);
+       ast_rwlock_unlock(&refer_techs_lock);
+
+       if (match) {
+               ast_log(LOG_ERROR, "No '%s' refer technology found.\n", tech->name);
+               return -1;
+       }
+
+       ast_verb(2, "Refer technology '%s' unregistered.\n", tech->name);
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Clean up other resources on Asterisk shutdown
+ */
+static void refer_shutdown(void)
+{
+       AST_VECTOR_FREE(&refer_techs);
+       ast_rwlock_destroy(&refer_techs_lock);
+}
+
+/*!
+ * \internal
+ * \brief Initialize stuff during Asterisk startup.
+ *
+ * Cleanup isn't a big deal in this function.  If we return non-zero,
+ * Asterisk is going to exit.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_init(void)
+{
+       ast_rwlock_init(&refer_techs_lock);
+       if (AST_VECTOR_INIT(&refer_techs, 8)) {
+               return -1;
+       }
+       ast_register_cleanup(refer_shutdown);
+       return 0;
+}
index 461328277a86c664c04c6237acbd040df861b70d..3663232cc349f5659a1eba70f729cc68e8981ef7 100644 (file)
@@ -33,6 +33,7 @@
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/channel.h"
 #include "asterisk/message.h"
+#include "asterisk/refer.h"
 
 void ast_ari_endpoints_list(struct ast_variable *headers,
        struct ast_ari_endpoints_list_args *args,
@@ -307,3 +308,135 @@ void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers,
        send_message(msg_to, args->from, args->body, variables, response);
        ast_variables_destroy(variables);
 }
+
+static void send_refer(const char *to, const char *from, const char *refer_to, int to_self, struct ast_variable *variables, struct ast_ari_response *response)
+{
+       struct ast_variable *current;
+       struct ast_refer *refer;
+       int res = 0;
+
+       if (ast_strlen_zero(to)) {
+               ast_ari_response_error(response, 400, "Bad Request",
+                       "To must be specified");
+               return;
+       }
+
+       refer = ast_refer_alloc();
+       if (!refer) {
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       ast_refer_set_to(refer, "%s", to);
+       ast_refer_set_to_self(refer, to_self);
+
+       if (!ast_strlen_zero(from)) {
+               ast_refer_set_from(refer, "%s", from);
+       }
+       if (!ast_strlen_zero(refer_to)) {
+               ast_refer_set_refer_to(refer, "%s", refer_to);
+       }
+
+       for (current = variables; current; current = current->next) {
+               res |= ast_refer_set_var_outbound(refer, current->name, current->value);
+       }
+
+       if (res) {
+               ast_ari_response_alloc_failed(response);
+               ast_refer_destroy(refer);
+               return;
+       }
+
+       if (ast_refer_send(refer)) {
+               ast_ari_response_error(response, 404, "Not Found",
+                       "Endpoint not found");
+               return;
+       }
+
+       response->message = ast_json_null();
+       response->response_code = 202;
+       response->response_text = "Accepted";
+}
+
+static int parse_refer_json(struct ast_json *body,
+       struct ast_ari_response *response,
+       struct ast_variable **variables)
+{
+       const char *known_variables[] = { "display_name" };
+       const char *value;
+       struct ast_variable *new_var;
+       struct ast_json *json_variable;
+       int err = 0;
+       int i;
+
+       if (!body) {
+               return 0;
+       }
+
+       json_variable = ast_json_object_get(body, "variables");
+       if (json_variable) {
+               err = json_to_ast_variables(response, json_variable, variables);
+               if (err) {
+                       return err;
+               }
+       }
+
+       for (i = 0; i < sizeof(known_variables) / sizeof(*known_variables); ++i) {
+               json_variable = ast_json_object_get(body, known_variables[i]);
+               if (json_variable && ast_json_typeof(json_variable) == AST_JSON_STRING) {
+                       value = ast_json_string_get(json_variable);
+                       new_var = ast_variable_new(known_variables[i], value, "");
+                       if (new_var) {
+                               ast_variable_list_append(variables, new_var);
+                       }
+               }
+       }
+
+       return err;
+}
+
+void ast_ari_endpoints_refer(struct ast_variable *headers,
+       struct ast_ari_endpoints_refer_args *args,
+       struct ast_ari_response *response)
+{
+       struct ast_variable *variables = NULL;
+
+       ast_ari_endpoints_refer_parse_body(args->variables, args);
+
+       if (parse_refer_json(args->variables, response, &variables)) {
+               return;
+       }
+
+       send_refer(args->to, args->from, args->refer_to, args->to_self, variables, response);
+       ast_variables_destroy(variables);
+}
+
+void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers,
+       struct ast_ari_endpoints_refer_to_endpoint_args *args,
+       struct ast_ari_response *response)
+{
+       struct ast_variable *variables = NULL;
+       struct ast_endpoint_snapshot *snapshot;
+       char to[128];
+       char *tech = ast_strdupa(args->tech);
+
+       /* Really, we just want to know if this thing exists */
+       snapshot = ast_endpoint_latest_snapshot(args->tech, args->resource);
+       if (!snapshot) {
+               ast_ari_response_error(response, 404, "Not Found",
+                       "Endpoint not found");
+               return;
+       }
+       ao2_ref(snapshot, -1);
+
+       ast_ari_endpoints_refer_to_endpoint_parse_body(args->variables, args);
+
+       if (parse_refer_json(args->variables, response, &variables)) {
+               return;
+       }
+
+       snprintf(to, sizeof(to), "%s:%s", ast_str_to_lower(tech), args->resource);
+
+       send_refer(to, args->from, args->refer_to, args->to_self, variables, response);
+       ast_variables_destroy(variables);
+}
index b5ad634077c432b0eb4bcdbea8a7083363385d5c..3c212b93fa61eff6a4bf5d3370998ca93367ff37 100644 (file)
@@ -58,6 +58,7 @@ struct ast_ari_endpoints_send_message_args {
        const char *from;
        /*! The body of the message */
        const char *body;
+       /*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
        struct ast_json *variables;
 };
 /*!
@@ -79,6 +80,38 @@ int ast_ari_endpoints_send_message_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_endpoints_send_message(struct ast_variable *headers, struct ast_ari_endpoints_send_message_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_endpoints_refer() */
+struct ast_ari_endpoints_refer_args {
+       /*! The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip. */
+       const char *to;
+       /*! The endpoint resource or technology specific identity to refer from. */
+       const char *from;
+       /*! The endpoint resource or technology specific URI to refer to. */
+       const char *refer_to;
+       /*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
+       int to_self;
+       /*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The "display_name" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI. */
+       struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /endpoints/refer.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_endpoints_refer_parse_body(
+       struct ast_json *body,
+       struct ast_ari_endpoints_refer_args *args);
+
+/*!
+ * \brief Refer an endpoint or technology URI to some technology URI or endpoint.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_endpoints_refer(struct ast_variable *headers, struct ast_ari_endpoints_refer_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_endpoints_list_by_tech() */
 struct ast_ari_endpoints_list_by_tech_args {
        /*! Technology of the endpoints (pjsip,iax2,...) */
@@ -117,6 +150,7 @@ struct ast_ari_endpoints_send_message_to_endpoint_args {
        const char *from;
        /*! The body of the message */
        const char *body;
+       /*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
        struct ast_json *variables;
 };
 /*!
@@ -138,5 +172,39 @@ int ast_ari_endpoints_send_message_to_endpoint_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_send_message_to_endpoint_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_endpoints_refer_to_endpoint() */
+struct ast_ari_endpoints_refer_to_endpoint_args {
+       /*! Technology of the endpoint */
+       const char *tech;
+       /*! ID of the endpoint */
+       const char *resource;
+       /*! The endpoint resource or technology specific identity to refer from. */
+       const char *from;
+       /*! The endpoint resource or technology specific URI to refer to. */
+       const char *refer_to;
+       /*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
+       int to_self;
+       /*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers, */
+       struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /endpoints/{tech}/{resource}/refer.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_endpoints_refer_to_endpoint_parse_body(
+       struct ast_json *body,
+       struct ast_ari_endpoints_refer_to_endpoint_args *args);
+
+/*!
+ * \brief Refer an endpoint or technology URI to some technology URI or endpoint.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_refer_to_endpoint_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
index d41096c0e2b27cf6fdd3297875c9d078bcdc3714..b40fd8cebcc2b0af11c4df957a7ba1646663d275 100644 (file)
@@ -188,6 +188,102 @@ static void ast_ari_endpoints_send_message_cb(
        }
 #endif /* AST_DEVMODE */
 
+fin: __attribute__((unused))
+       return;
+}
+int ast_ari_endpoints_refer_parse_body(
+       struct ast_json *body,
+       struct ast_ari_endpoints_refer_args *args)
+{
+       struct ast_json *field;
+       /* Parse query parameters out of it */
+       field = ast_json_object_get(body, "to");
+       if (field) {
+               args->to = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "from");
+       if (field) {
+               args->from = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "refer_to");
+       if (field) {
+               args->refer_to = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "to_self");
+       if (field) {
+               args->to_self = ast_json_is_true(field);
+       }
+       return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /endpoints/refer.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_endpoints_refer_cb(
+       struct ast_tcptls_session_instance *ser,
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+       struct ast_ari_endpoints_refer_args args = {};
+       struct ast_variable *i;
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "to") == 0) {
+                       args.to = (i->value);
+               } else
+               if (strcmp(i->name, "from") == 0) {
+                       args.from = (i->value);
+               } else
+               if (strcmp(i->name, "refer_to") == 0) {
+                       args.refer_to = (i->value);
+               } else
+               if (strcmp(i->name, "to_self") == 0) {
+                       args.to_self = ast_true(i->value);
+               } else
+               {}
+       }
+       args.variables = body;
+       ast_ari_endpoints_refer(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 0: /* Implementation is still a stub, or the code wasn't set */
+               is_valid = response->message == NULL;
+               break;
+       case 500: /* Internal Server Error */
+       case 501: /* Not Implemented */
+       case 400: /* Invalid parameters for referring. */
+       case 404: /* Endpoint not found */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_void(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/refer\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /endpoints/refer\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
        return;
 }
@@ -403,6 +499,104 @@ static void ast_ari_endpoints_send_message_to_endpoint_cb(
        }
 #endif /* AST_DEVMODE */
 
+fin: __attribute__((unused))
+       return;
+}
+int ast_ari_endpoints_refer_to_endpoint_parse_body(
+       struct ast_json *body,
+       struct ast_ari_endpoints_refer_to_endpoint_args *args)
+{
+       struct ast_json *field;
+       /* Parse query parameters out of it */
+       field = ast_json_object_get(body, "from");
+       if (field) {
+               args->from = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "refer_to");
+       if (field) {
+               args->refer_to = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "to_self");
+       if (field) {
+               args->to_self = ast_json_is_true(field);
+       }
+       return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /endpoints/{tech}/{resource}/refer.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_endpoints_refer_to_endpoint_cb(
+       struct ast_tcptls_session_instance *ser,
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+       struct ast_ari_endpoints_refer_to_endpoint_args args = {};
+       struct ast_variable *i;
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "from") == 0) {
+                       args.from = (i->value);
+               } else
+               if (strcmp(i->name, "refer_to") == 0) {
+                       args.refer_to = (i->value);
+               } else
+               if (strcmp(i->name, "to_self") == 0) {
+                       args.to_self = ast_true(i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "tech") == 0) {
+                       args.tech = (i->value);
+               } else
+               if (strcmp(i->name, "resource") == 0) {
+                       args.resource = (i->value);
+               } else
+               {}
+       }
+       args.variables = body;
+       ast_ari_endpoints_refer_to_endpoint(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 0: /* Implementation is still a stub, or the code wasn't set */
+               is_valid = response->message == NULL;
+               break;
+       case 500: /* Internal Server Error */
+       case 501: /* Not Implemented */
+       case 400: /* Invalid parameters for referring. */
+       case 404: /* Endpoint not found */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_void(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}/{resource}/refer\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}/{resource}/refer\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
        return;
 }
@@ -417,6 +611,15 @@ static struct stasis_rest_handlers endpoints_sendMessage = {
        .children = {  }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
+static struct stasis_rest_handlers endpoints_refer = {
+       .path_segment = "refer",
+       .callbacks = {
+               [AST_HTTP_POST] = ast_ari_endpoints_refer_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
        .path_segment = "sendMessage",
        .callbacks = {
@@ -426,14 +629,23 @@ static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
        .children = {  }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
+static struct stasis_rest_handlers endpoints_tech_resource_refer = {
+       .path_segment = "refer",
+       .callbacks = {
+               [AST_HTTP_POST] = ast_ari_endpoints_refer_to_endpoint_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech_resource = {
        .path_segment = "resource",
        .is_wildcard = 1,
        .callbacks = {
                [AST_HTTP_GET] = ast_ari_endpoints_get_cb,
        },
-       .num_children = 1,
-       .children = { &endpoints_tech_resource_sendMessage, }
+       .num_children = 2,
+       .children = { &endpoints_tech_resource_sendMessage,&endpoints_tech_resource_refer, }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech = {
@@ -451,8 +663,8 @@ static struct stasis_rest_handlers endpoints = {
        .callbacks = {
                [AST_HTTP_GET] = ast_ari_endpoints_list_cb,
        },
-       .num_children = 2,
-       .children = { &endpoints_sendMessage,&endpoints_tech, }
+       .num_children = 3,
+       .children = { &endpoints_sendMessage,&endpoints_refer,&endpoints_tech, }
 };
 
 static int unload_module(void)
index d112aa30433981bdd27c786c26f47237c1a9eb06..98a406d74f3eb196a746c089c7e4802dd76f318f 100644 (file)
@@ -27,6 +27,8 @@
 #include <pjmedia/errno.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/strings.h"
+#include "pjsip/sip_parser.h"
 #include "res_pjsip/include/res_pjsip_private.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/logger.h"
@@ -48,6 +50,7 @@
 #include "asterisk/res_pjsip_presence_xml.h"
 #include "asterisk/res_pjproject.h"
 #include "asterisk/utf8.h"
+#include "asterisk/acl.h"
 
 /*** MODULEINFO
        <depend>pjproject</depend>
@@ -558,6 +561,140 @@ int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint
        return result;
 }
 
+pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata)
+{
+       pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+
+       if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
+               return NULL;
+       }
+
+       return pjsip_uri_get_uri(contact->uri);
+}
+
+/*! \brief Callback function for finding the transport the request is going out on */
+static int find_transport_state_in_use(void *obj, void *arg, int flags)
+{
+       struct ast_sip_transport_state *transport_state = obj;
+       struct ast_sip_request_transport_details *details = arg;
+
+       /* If an explicit transport or factory matches then this is what is in use, if we are unavailable
+        * to compare based on that we make sure that the type is the same and the source IP address/port are the same
+        */
+       if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
+               (details->factory && details->factory == transport_state->factory) ||
+               ((details->type == transport_state->type) && (transport_state->factory) &&
+                       !pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
+                       transport_state->factory->addr_name.port == details->local_port))) {
+               return CMP_MATCH | CMP_STOP;
+       }
+
+       return 0;
+}
+
+struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details) {
+       RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
+
+       if (!(transport_states = ast_sip_get_transport_states())) {
+               return NULL;
+       }
+
+       return ao2_callback(transport_states, 0, find_transport_state_in_use, details);
+}
+
+int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata) {
+       RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
+       struct ast_sip_request_transport_details details;
+       pjsip_sip_uri *tmp_uri;
+       pjsip_dialog *dlg;
+       struct ast_sockaddr addr = { { 0, } };
+
+       if ((tmp_uri = ast_sip_get_contact_sip_uri(tdata))) {
+               pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
+               uri->port = tmp_uri->port;
+       } else if ((dlg = pjsip_tdata_get_dlg(tdata))
+               && (tmp_uri = pjsip_uri_get_uri(dlg->local.info->uri))
+               && (PJSIP_URI_SCHEME_IS_SIP(tmp_uri) || PJSIP_URI_SCHEME_IS_SIPS(tmp_uri))) {
+               pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
+               uri->port = tmp_uri->port;
+       }
+
+       if (ast_sip_set_request_transport_details(&details, tdata, 1)
+               || !(transport_state = ast_sip_find_transport_state_in_use(&details))
+               || !(transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))) {
+               return 0;
+       }
+
+       if (transport_state->localnet) {
+               ast_sockaddr_parse(&addr, tdata->tp_info.dst_name, PARSE_PORT_FORBID);
+               ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
+               if (ast_sip_transport_is_local(transport_state, &addr)) {
+                       return 0;
+               }
+       }
+
+       if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
+               pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+       }
+
+       if (transport->external_signaling_port) {
+               uri->port = transport->external_signaling_port;
+       }
+
+       return 0;
+}
+
+int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata,
+       int use_ipv6) {
+       pjsip_sip_uri *uri;
+       pjsip_via_hdr *via;
+       long transport_type;
+
+       if (!details || !tdata) {
+               return -1;
+       }
+
+       /* If IPv6 should be considered, un-set Bit 7 to make TCP6 equal to TCP and TLS6 equal to TLS */
+       transport_type = use_ipv6 ? tdata->tp_info.transport->key.type & ~(PJSIP_TRANSPORT_IPV6)
+               : tdata->tp_info.transport->key.type;
+
+       if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
+               details->transport = tdata->tp_sel.u.transport;
+       } else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
+               details->factory = tdata->tp_sel.u.listener;
+       } else if (transport_type == PJSIP_TRANSPORT_UDP || transport_type == PJSIP_TRANSPORT_UDP6) {
+               /* Connectionless uses the same transport for all requests */
+               details->type = AST_TRANSPORT_UDP;
+               details->transport = tdata->tp_info.transport;
+       } else {
+               if (transport_type == PJSIP_TRANSPORT_TCP) {
+                       details->type = AST_TRANSPORT_TCP;
+               } else if (transport_type == PJSIP_TRANSPORT_TLS) {
+                       details->type = AST_TRANSPORT_TLS;
+               } else {
+                       /* Unknown transport type, we can't map. */
+                       return -1;
+               }
+
+               if ((uri = ast_sip_get_contact_sip_uri(tdata))) {
+                       details->local_address = uri->host;
+                       details->local_port = uri->port;
+               } else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
+                       (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
+                       details->local_address = via->sent_by.host;
+                       details->local_port = via->sent_by.port;
+               } else {
+                       return -1;
+               }
+
+               if (!details->local_port) {
+                       details->local_port = (details->type == AST_TRANSPORT_TLS) ? 5061 : 5060;
+               }
+       }
+       return 0;
+}
+
 int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
        pjsip_sip_uri *sip_uri, char *buf, size_t buf_len)
 {
@@ -835,7 +972,11 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
        pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
        static const pj_str_t HCONTACT = { "Contact", 7 };
 
-       snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
+       if (!ast_begins_with(uri, "<")) {
+               snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
+       } else {
+               snprintf(enclosed_uri, sizeof(enclosed_uri), "%s", uri);
+       }
        pj_cstr(&remote_uri, enclosed_uri);
 
        pj_cstr(&target_uri, uri);
@@ -1130,6 +1271,7 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
 /* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
 static const pjsip_method info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
 static const pjsip_method message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
+static const pjsip_method refer_method = {PJSIP_OTHER_METHOD, {"REFER", 5} };
 
 static struct {
        const char *method;
@@ -1146,6 +1288,7 @@ static struct {
        { "PUBLISH", &pjsip_publish_method },
        { "INFO", &info_method },
        { "MESSAGE", &message_method },
+       { "REFER", &refer_method },
 };
 
 static const pjsip_method *get_pjsip_method(const char *method)
@@ -2727,6 +2870,575 @@ void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const s
        }
 }
 
+/*!
+ * \brief Find a contact and insert a "user@" into its URI.
+ *
+ * \param to Original destination (for error messages only)
+ * \param endpoint_name Endpoint name (for error messages only)
+ * \param aors Command separated list of AORs
+ * \param user The user to insert in the contact URI
+ * \param uri Pointer to buffer in which to return the URI. Must be freed by caller.
+ *
+ * \return  0 Success
+ * \return -1 Fail
+ *
+ * \note If the contact URI found for the endpoint already has a user in
+ * its URI, it will be replaced by the user passed as an argument to this function.
+ */
+static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
+       const char *user, char **uri)
+{
+       RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+       pj_pool_t *pool;
+       pjsip_name_addr *name_addr;
+       pjsip_sip_uri *sip_uri;
+       int err = 0;
+
+       contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
+       if (!contact) {
+               ast_log(LOG_WARNING, "Dest: '%s'. Couldn't find contact for endpoint '%s'\n",
+                       to, endpoint_name);
+               return -1;
+       }
+
+       pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri-user-insert", 128, 128);
+       if (!pool) {
+               ast_log(LOG_WARNING, "Failed to allocate ParseUri endpoint pool.\n");
+               return -1;
+       }
+
+       name_addr = (pjsip_name_addr *) pjsip_parse_uri(pool, (char*)contact->uri, strlen(contact->uri), PJSIP_PARSE_URI_AS_NAMEADDR);
+       if (!name_addr || (!PJSIP_URI_SCHEME_IS_SIP(name_addr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(name_addr->uri))) {
+               ast_log(LOG_WARNING, "Failed to parse URI '%s'\n", contact->uri);
+               err = -1;
+               goto out;
+       }
+
+       ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  ContactURI: '%s'\n", to, user, endpoint_name, contact->uri);
+
+       sip_uri = pjsip_uri_get_uri(name_addr->uri);
+       pj_strset2(&sip_uri->user, (char*)user);
+
+       *uri = ast_malloc(PJSIP_MAX_URL_SIZE);
+       if (!(*uri)) {
+               err = -1;
+               goto out;
+       }
+       pjsip_uri_print(PJSIP_URI_IN_REQ_URI, name_addr, *uri, PJSIP_MAX_URL_SIZE);
+
+out:
+       pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+       return err;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination is only a single token
+ *
+ * "destination" could be one of the following:
+ * \verbatim
+               endpoint_name
+               hostname
+ * \endverbatim
+ *
+ * \param to
+ * \param destination
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ *                            outbound endpoint if no endpoint was found.
+ *                            Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, int get_default_outbound, char **uri) {
+       RAII_VAR(struct ast_sip_contact*, contact, NULL, ao2_cleanup);
+       char *endpoint_name = NULL;
+       struct ast_sip_endpoint *endpoint = NULL;
+
+       /*
+        * If "destination" is just one token, it could be an endpoint name
+        * or a hostname without a scheme.
+        */
+
+       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
+       if (!endpoint) {
+               /*
+                * We can only assume it's a hostname.
+                */
+               char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+               if (!temp_uri) {
+                       goto failure;
+               }
+               sprintf(temp_uri, "sip:%s", destination);
+               *uri = temp_uri;
+               if (get_default_outbound) {
+                       endpoint = ast_sip_default_outbound_endpoint();
+               }
+               ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s'%s\n",
+                       to, *uri, get_default_outbound ? " with default endpoint" : "");
+               return endpoint;
+       }
+
+       /*
+        * It's an endpoint
+        */
+
+       endpoint_name = destination;
+       contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+       if (!contact) {
+               ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find an aor/contact for it\n",
+                       to, endpoint_name);
+               ao2_cleanup(endpoint);
+               goto failure;
+       }
+
+       *uri = ast_strdup(contact->uri);
+       if (!(*uri)) {
+               ao2_cleanup(endpoint);
+               goto failure;
+       }
+
+       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
+               to, endpoint_name, *uri);
+       return endpoint;
+
+failure:
+       *uri = NULL;
+       return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '/'
+ *
+ * "to" could be one of the following:
+ * \verbatim
+               endpoint/aor
+               endpoint/<sip[s]:host>
+               endpoint/<sip[s]:user@host>
+               endpoint/"Bob" <sip[s]:host>
+               endpoint/"Bob" <sip[s]:user@host>
+               endpoint/sip[s]:host
+               endpoint/sip[s]:user@host
+               endpoint/host
+               endpoint/user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
+       char *slash, char *atsign, char *scheme)
+{
+       char *endpoint_name = NULL;
+       struct ast_sip_endpoint *endpoint = NULL;
+       struct ast_sip_contact *contact = NULL;
+       char *user = NULL;
+       char *afterslash = slash + 1;
+       struct ast_sip_aor *aor;
+
+       if (ast_begins_with(destination, "PJSIP/")) {
+               ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
+               /*
+                * This has to be the form PJSIP/user@endpoint
+                */
+               if (!atsign || strchr(afterslash, '/')) {
+                       /*
+                        * If there's no "user@" or there's a slash somewhere after
+                        * "PJSIP/" then we go no further.
+                        */
+                       ast_log(LOG_WARNING,
+                               "Dest: '%s'. Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
+                               to);
+                       goto failure;
+               }
+               *atsign = '\0';
+               user = afterslash;
+               endpoint_name = atsign + 1;
+               ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'\n", to, user, endpoint_name);
+       } else {
+               /*
+                * Either...
+                *      endpoint/aor
+                *      endpoint/uri
+                */
+               *slash = '\0';
+               endpoint_name = destination;
+               ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
+       }
+
+       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+       if (!endpoint) {
+               ast_log(LOG_WARNING, "Dest: '%s'. Didn't find endpoint with name '%s'\n",
+                       to, endpoint_name);
+               goto failure;
+       }
+
+       if (scheme) {
+               /*
+                * If we found a scheme, then everything after the slash MUST be a URI.
+                * We don't need to do any further modification.
+                */
+               *uri = ast_strdup(afterslash);
+               if (!(*uri)) {
+                       goto failure;
+               }
+               ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
+                       to, endpoint_name, *uri);
+               return endpoint;
+       }
+
+       if (user) {
+               /*
+                * This has to be the form PJSIP/user@endpoint
+                */
+               int rc;
+
+               /*
+                * Set the return URI to be the endpoint's contact URI with the user
+                * portion set to the user that was specified before the endpoint name.
+                */
+               rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
+               if (rc != 0) {
+                       /*
+                        * insert_user_in_contact_uri prints the warning message.
+                        */
+                       goto failure;
+               }
+               ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  URI: '%s'\n", to, user,
+                       endpoint_name, *uri);
+
+               return endpoint;
+       }
+
+       /*
+        * We're now left with two possibilities...
+        *      endpoint/aor
+        *      endpoint/uri-without-scheme
+        */
+       aor = ast_sip_location_retrieve_aor(afterslash);
+       if (!aor) {
+               /*
+                * It's probably a URI without a scheme but we don't have a way to tell
+                * for sure.  We're going to assume it is and prepend it with a scheme.
+                */
+               *uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
+               if (!(*uri)) {
+                       goto failure;
+               }
+               sprintf(*uri, "sip:%s", afterslash);
+               ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
+                       to, endpoint_name, *uri);
+               return endpoint;
+       }
+
+       /*
+        * Only one possibility left... There was an aor name after the slash.
+        */
+       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
+               to, endpoint_name, ast_sorcery_object_get_id(aor));
+
+       contact = ast_sip_location_retrieve_first_aor_contact(aor);
+       if (!contact) {
+               ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact for aor '%s'\n",
+                       to, endpoint_name, ast_sorcery_object_get_id(aor));
+               ao2_cleanup(aor);
+               goto failure;
+       }
+
+       *uri = ast_strdup(contact->uri);
+       ao2_cleanup(contact);
+       ao2_cleanup(aor);
+       if (!(*uri)) {
+               goto failure;
+       }
+
+       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
+               to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
+
+       return endpoint;
+
+failure:
+       ao2_cleanup(endpoint);
+       *uri = NULL;
+       return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '@' but no '/' or scheme
+ *
+ * "to" could be one of the following:
+ * \verbatim
+               <sip[s]:user@host>
+               "Bob" <sip[s]:user@host>
+               sip[s]:user@host
+               user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ *                            outbound endpoint if no endpoint was found.
+ *                            Otherwise, return NULL if no endpoint was found.
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
+       char *slash, char *atsign, char *scheme, int get_default_outbound)
+{
+       char *endpoint_name = NULL;
+       struct ast_sip_endpoint *endpoint = NULL;
+       struct ast_sip_contact *contact = NULL;
+       char *afterat = atsign + 1;
+
+       *atsign = '\0';
+       endpoint_name = destination;
+
+       /* Apparently there may be ';<user_options>' after the endpoint name ??? */
+       AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
+       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+       if (!endpoint) {
+               /*
+                * It's probably a uri with a user but without a scheme but we don't have a way to tell.
+                * We're going to assume it is and prepend it with a scheme.
+                */
+               *uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
+               if (!(*uri)) {
+                       goto failure;
+               }
+               sprintf(*uri, "sip:%s", to);
+               if (get_default_outbound) {
+                       endpoint = ast_sip_default_outbound_endpoint();
+               }
+               ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s'%s\n",
+                       to, *uri, get_default_outbound ? " with default endpoint" : "");
+               return endpoint;
+       }
+
+       /*
+        * OK, it's an endpoint and a domain (which we ignore)
+        */
+       contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+       if (!contact) {
+               ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact\n",
+                       to, endpoint_name);
+               goto failure;
+       }
+
+       *uri = ast_strdup(contact->uri);
+       ao2_cleanup(contact);
+       if (!(*uri)) {
+               goto failure;
+       }
+       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
+               to, endpoint_name, *uri, afterat);
+
+       return endpoint;
+
+failure:
+       ao2_cleanup(endpoint);
+       *uri = NULL;
+       return NULL;
+}
+
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri)
+{
+       char *destination;
+       char *slash = NULL;
+       char *atsign = NULL;
+       char *scheme = NULL;
+       struct ast_sip_endpoint *endpoint = NULL;
+
+       destination = ast_strdupa(to);
+
+       slash = strchr(destination, '/');
+       atsign = strchr(destination, '@');
+       scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
+
+       if (!slash && !atsign && !scheme) {
+               /*
+                * If there's only a single token, it can be either...
+                *      endpoint
+                *      host
+                */
+               return handle_single_token(to, destination, get_default_outbound, uri);
+       }
+
+       if (slash) {
+               /*
+                * If there's a '/', then the form must be one of the following...
+                *      PJSIP/user@endpoint
+                *      endpoint/aor
+                *      endpoint/uri
+                */
+               return handle_slash(to, destination, uri, slash, atsign, scheme);
+       }
+
+       if (atsign && !scheme) {
+               /*
+                * If there's an '@' but no scheme then it's either following an endpoint name
+                * and being followed by a domain name (which we discard).
+                * OR is's a user@host uri without a scheme.  It's probably the latter but because
+                * endpoint@domain looks just like user@host, we'll test for endpoint first.
+                */
+               return handle_atsign(to, destination, uri, slash, atsign, scheme, get_default_outbound);
+       }
+
+       /*
+        * If all else fails, we assume it's a URI or just a hostname.
+        */
+       if (scheme) {
+               *uri = ast_strdup(destination);
+               if (!(*uri)) {
+                       goto failure;
+               }
+               ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s'%s\n",
+                       to, *uri, get_default_outbound ? " with default endpoint" : "");
+       } else {
+               *uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+               if (!(*uri)) {
+                       goto failure;
+               }
+               sprintf(*uri, "sip:%s", destination);
+               ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s'%s\n",
+                       to, *uri, get_default_outbound ? " with default endpoint" : "");
+       }
+       if (get_default_outbound) {
+               endpoint = ast_sip_default_outbound_endpoint();
+       }
+
+       return endpoint;
+
+failure:
+       ao2_cleanup(endpoint);
+       *uri = NULL;
+       return NULL;
+}
+
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to)
+{
+       pjsip_name_addr *parsed_name_addr;
+       pjsip_sip_uri *sip_uri;
+       pjsip_name_addr *tdata_name_addr;
+       pjsip_sip_uri *tdata_sip_uri;
+       pjsip_to_hdr *to_hdr;
+       char *buf = NULL;
+#define DEBUG_BUF_SIZE 256
+
+       parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, (char*)to, strlen(to),
+               PJSIP_PARSE_URI_AS_NAMEADDR);
+
+       if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+                       && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
+               ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
+               return -1;
+       }
+
+       sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+       if (DEBUG_ATLEAST(3)) {
+               buf = ast_alloca(DEBUG_BUF_SIZE);
+               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
+               ast_debug(3, "Parsed To: %.*s  %s\n", (int)parsed_name_addr->display.slen,
+                       parsed_name_addr->display.ptr, buf);
+       }
+
+       to_hdr = PJSIP_MSG_TO_HDR(tdata->msg);
+       tdata_name_addr = to_hdr ? (pjsip_name_addr *) to_hdr->uri : NULL;
+       if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
+                       && !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
+               /* Highly unlikely but we have to check */
+               ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
+               return -1;
+       }
+
+       tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
+       if (DEBUG_ATLEAST(3)) {
+               buf[0] = '\0';
+               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
+               ast_debug(3, "Original tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
+                       tdata_name_addr->display.ptr, buf);
+       }
+
+       /* Replace the uri */
+       pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
+       /* The display name isn't part of the URI so we need to replace it separately */
+       pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
+
+       if (DEBUG_ATLEAST(3)) {
+               buf[0] = '\0';
+               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
+               ast_debug(3, "New tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
+                       tdata_name_addr->display.ptr, buf);
+       }
+
+       return 0;
+#undef DEBUG_BUF_SIZE
+}
+
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from)
+{
+       pjsip_name_addr *name_addr;
+       pjsip_sip_uri *uri;
+       pjsip_name_addr *parsed_name_addr;
+       pjsip_from_hdr *from_hdr;
+
+       if (ast_strlen_zero(from)) {
+               return 0;
+       }
+
+       from_hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
+       if (!from_hdr) {
+               return -1;
+       }
+       name_addr = (pjsip_name_addr *) from_hdr->uri;
+       uri = pjsip_uri_get_uri(name_addr);
+
+       parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
+               strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
+       if (parsed_name_addr) {
+               pjsip_sip_uri *parsed_uri;
+
+               if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+                               && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
+                       ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
+                       return -1;
+               }
+
+               parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+
+               if (pj_strlen(&parsed_name_addr->display)) {
+                       pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
+               }
+
+               /* Unlike the To header, we only want to replace the user, host and port */
+               pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
+               pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
+               uri->port = parsed_uri->port;
+
+               return 0;
+       } else {
+               /* assume it is 'user[@domain]' format */
+               char *domain = strchr(from, '@');
+
+               if (domain) {
+                       pj_str_t pj_from;
+
+                       pj_strset3(&pj_from, from, domain);
+                       pj_strdup(tdata->pool, &uri->user, &pj_from);
+
+                       pj_strdup2(tdata->pool, &uri->host, domain + 1);
+               } else {
+                       pj_strdup2(tdata->pool, &uri->user, from);
+               }
+
+               return 0;
+       }
+
+       return -1;
+}
 
 static void remove_request_headers(pjsip_endpoint *endpt)
 {
index 336d392cd40cc19bb438ca61f601f6fa99568954..51d8160b61c49761aa1c64dca766dfd3ad76d14e 100644 (file)
@@ -195,571 +195,6 @@ static enum pjsip_status_code check_content_type_in_dialog(const pjsip_rx_data *
        return res;
 }
 
-/*!
- * \brief Find a contact and insert a "user@" into its URI.
- *
- * \param to Original destination (for error messages only)
- * \param endpoint_name Endpoint name (for error messages only)
- * \param aors Command separated list of AORs
- * \param user The user to insert in the contact URI
- * \param uri Pointer to buffer in which to return the URI
- *
- * \return  0 Success
- * \return -1 Fail
- *
- * \note If the contact URI found for the endpoint already has a user in
- * its URI, it will be replaced.
- */
-static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
-       const char *user, char **uri)
-{
-       char *scheme = NULL;
-       char *contact_uri = NULL;
-       char *after_scheme = NULL;
-       char *host;
-       struct ast_sip_contact *contact = NULL;
-
-
-       contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
-       if (!contact) {
-               /*
-                * We're getting the contact using the same method as
-                * ast_sip_create_request() so if there's no contact
-                * we can never send this message.
-                */
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Couldn't find contact for endpoint '%s'\n",
-                       to, endpoint_name);
-               return -1;
-       }
-
-       contact_uri = ast_strdupa(contact->uri);
-       ao2_cleanup(contact);
-
-       ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  ContactURI: '%s'\n", to, user, endpoint_name, contact_uri);
-
-       /*
-        * Contact URIs must have a scheme so we must insert the user between it and the host.
-        */
-       scheme = contact_uri;
-       after_scheme = strchr(contact_uri, ':');
-       if (!after_scheme) {
-               /* A contact URI without a scheme?  Something's wrong.  Bail */
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: There was no scheme in the contact URI '%s'\n",
-                       to, contact_uri);
-               return -1;
-       }
-       /*
-        * Terminate the scheme.
-        */
-       *after_scheme = '\0';
-       after_scheme++;
-
-       /*
-        * If the contact_uri already has a user, the host starts after the '@', otherwise
-        * the host is at after_scheme.
-        *
-        * We're going to ignore the existing user.
-        */
-       host = strchr(after_scheme, '@');
-       if (host) {
-               host++;
-       } else {
-               host = after_scheme;
-       }
-
-       *uri = ast_malloc(strlen(scheme) + strlen(user) + strlen(host) + 3 /* One for the ':', '@' and terminating NULL */);
-       sprintf(*uri, "%s:%s@%s", scheme, user, host); /* Safe */
-
-       return 0;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination is only a single token
- *
- * "to" could be one of the following:
- * \verbatim
-               endpoint_name
-               hostname
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param destination
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, char **uri) {
-       char *endpoint_name = NULL;
-       struct ast_sip_endpoint *endpoint = NULL;
-       struct ast_sip_contact *contact = NULL;
-
-       /*
-        * If "to" is just one token, it could be an endpoint name
-        * or a hostname without a scheme.
-        */
-
-       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
-       if (!endpoint) {
-               /*
-                * We can only assume it's a hostname.
-                */
-               char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
-               sprintf(temp_uri, "sip:%s", destination);
-               *uri = temp_uri;
-               endpoint = ast_sip_default_outbound_endpoint();
-               ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s' with default endpoint\n",
-                       to, *uri);
-               return endpoint;
-       }
-
-       /*
-        * It's an endpoint
-        */
-
-       endpoint_name = destination;
-       contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
-       if (!contact) {
-               /*
-                * We're getting the contact using the same method as
-                * ast_sip_create_request() so if there's no contact
-                * we can never send this message.
-                */
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find an aor/contact for it\n",
-                       to, endpoint_name);
-               ao2_cleanup(endpoint);
-               *uri = NULL;
-               return NULL;
-       }
-
-       *uri = ast_strdup(contact->uri);
-       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
-               to, endpoint_name, *uri);
-       ao2_cleanup(contact);
-       return endpoint;
-
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '/'
- *
- * "to" could be one of the following:
- * \verbatim
-               endpoint/aor
-               endpoint/<sip[s]:host>
-               endpoint/<sip[s]:user@host>
-               endpoint/"Bob" <sip[s]:host>
-               endpoint/"Bob" <sip[s]:user@host>
-               endpoint/sip[s]:host
-               endpoint/sip[s]:user@host
-               endpoint/host
-               endpoint/user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
-       char *slash, char *atsign, char *scheme)
-{
-       char *endpoint_name = NULL;
-       struct ast_sip_endpoint *endpoint = NULL;
-       struct ast_sip_contact *contact = NULL;
-       char *user = NULL;
-       char *afterslash = slash + 1;
-       struct ast_sip_aor *aor;
-
-       if (ast_begins_with(destination, "PJSIP/")) {
-               ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
-               /*
-                * This has to be the form PJSIP/user@endpoint
-                */
-               if (!atsign || strchr(afterslash, '/')) {
-                       /*
-                        * If there's no "user@" or there's a slash somewhere after
-                        * "PJSIP/" then we go no further.
-                        */
-                       *uri = NULL;
-                       ast_log(LOG_WARNING,
-                               "Dest: '%s' MSG SEND FAIL: Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
-                               to);
-                       return NULL;
-               }
-               *atsign = '\0';
-               user = afterslash;
-               endpoint_name = atsign + 1;
-               ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'\n", to, user, endpoint_name);
-       } else {
-               /*
-                * Either...
-                *      endpoint/aor
-                *      endpoint/uri
-                */
-               *slash = '\0';
-               endpoint_name = destination;
-               ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
-       }
-
-       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-       if (!endpoint) {
-               *uri = NULL;
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Didn't find endpoint with name '%s'\n",
-                       to, endpoint_name);
-               return NULL;
-       }
-
-       if (scheme) {
-               /*
-                * If we found a scheme, then everything after the slash MUST be a URI.
-                * We don't need to do any further modification.
-                */
-               *uri = ast_strdup(afterslash);
-               ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
-                       to, endpoint_name, *uri);
-               return endpoint;
-       }
-
-       if (user) {
-               /*
-                * This has to be the form PJSIP/user@endpoint
-                */
-               int rc;
-
-               /*
-                * Set the return URI to be the endpoint's contact URI with the user
-                * portion set to the user that was specified before the endpoint name.
-                */
-               rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
-               if (rc != 0) {
-                       /*
-                        * insert_user_in_contact_uri prints the warning message.
-                        */
-                       ao2_cleanup(endpoint);
-                       endpoint = NULL;
-                       *uri = NULL;
-               }
-               ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  URI: '%s'\n", to, user,
-                       endpoint_name, *uri);
-
-               return endpoint;
-       }
-
-       /*
-        * We're now left with two possibilities...
-        *      endpoint/aor
-        *      endpoint/uri-without-scheme
-        */
-       aor = ast_sip_location_retrieve_aor(afterslash);
-       if (!aor) {
-               /*
-                * It's probably a URI without a scheme but we don't have a way to tell
-                * for sure.  We're going to assume it is and prepend it with a scheme.
-                */
-               *uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
-               sprintf(*uri, "sip:%s", afterslash);
-               ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
-                       to, endpoint_name, *uri);
-               return endpoint;
-       }
-
-       /*
-        * Only one possibility left... There was an aor name after the slash.
-        */
-       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
-               to, endpoint_name, ast_sorcery_object_get_id(aor));
-
-       contact = ast_sip_location_retrieve_first_aor_contact(aor);
-       if (!contact) {
-               /*
-                * An aor without a contact is useless and since
-                * ast_sip_create_message() won't be able to find one
-                * either, we just need to bail.
-                */
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact for aor '%s'\n",
-                       to, endpoint_name, ast_sorcery_object_get_id(aor));
-               ao2_cleanup(aor);
-               ao2_cleanup(endpoint);
-               *uri = NULL;
-               return NULL;
-       }
-
-       *uri = ast_strdup(contact->uri);
-       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
-               to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
-       ao2_cleanup(contact);
-       ao2_cleanup(aor);
-
-       return endpoint;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '\@' but no '/' or scheme
- *
- * "to" could be one of the following:
- * \verbatim
-               <sip[s]:user@host>
-               "Bob" <sip[s]:user@host>
-               sip[s]:user@host
-               user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
-       char *slash, char *atsign, char *scheme)
-{
-       char *endpoint_name = NULL;
-       struct ast_sip_endpoint *endpoint = NULL;
-       struct ast_sip_contact *contact = NULL;
-       char *afterat = atsign + 1;
-
-       *atsign = '\0';
-       endpoint_name = destination;
-
-       /* Apparently there may be ';<user_options>' after the endpoint name ??? */
-       AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
-       endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-       if (!endpoint) {
-               /*
-                * It's probably a uri with a user but without a scheme but we don't have a way to tell.
-                * We're going to assume it is and prepend it with a scheme.
-                */
-               *uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
-               sprintf(*uri, "sip:%s", to);
-               endpoint = ast_sip_default_outbound_endpoint();
-               ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s' with default endpoint\n",
-                       to, *uri);
-               return endpoint;
-       }
-
-       /*
-        * OK, it's an endpoint and a domain (which we ignore)
-        */
-       contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
-       if (!contact) {
-               /*
-                * We're getting the contact using the same method as
-                * ast_sip_create_request() so if there's no contact
-                * we can never send this message.
-                */
-               ao2_cleanup(endpoint);
-               endpoint = NULL;
-               *uri = NULL;
-               ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact\n",
-                       to, endpoint_name);
-               return NULL;
-       }
-
-       *uri = ast_strdup(contact->uri);
-       ao2_cleanup(contact);
-       ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
-               to, endpoint_name, *uri, afterat);
-
-       return endpoint;
-}
-
-/*!
- * \internal
- * \brief Retrieves an endpoint and URI from the "to" string.
- *
- * This URI is used as the Request URI.
- *
- * Expects the given 'to' to be in one of the following formats:
- * Why we allow so many is a mystery.
- *
- * Basic:
- *
- *      endpoint        : We'll get URI from the default aor/contact
- *      endpoint/aor    : We'll get the URI from the specific aor/contact
- *      endpoint@domain : We toss the domain part and just use the endpoint
- *
- *   These all use the endpoint and specified URI:
- * \verbatim
-        endpoint/<sip[s]:host>
-        endpoint/<sip[s]:user@host>
-        endpoint/"Bob" <sip[s]:host>
-        endpoint/"Bob" <sip[s]:user@host>
-        endpoint/sip[s]:host
-        endpoint/sip[s]:user@host
-        endpoint/host
-        endpoint/user@host
-   \endverbatim
- *
- *   These all use the default endpoint and specified URI:
- * \verbatim
-        <sip[s]:host>
-        <sip[s]:user@host>
-        "Bob" <sip[s]:host>
-        "Bob" <sip[s]:user@host>
-        sip[s]:host
-        sip[s]:user@host
-   \endverbatim
- *
- *   These use the default endpoint and specified host:
- * \verbatim
-        host
-        user@host
-   \endverbatim
- *
- *   This form is similar to a dialstring:
- * \verbatim
-        PJSIP/user@endpoint
-   \endverbatim
- *
- *   In this case, the user will be added to the endpoint contact's URI.
- *   If the contact URI already has a user, it will be replaced.
- *
- * The ones that have the sip[s] scheme are the easiest to parse.
- * The rest all have some issue.
- *
- *      endpoint vs host              : We have to test for endpoint first
- *      endpoint/aor vs endpoint/host : We have to test for aor first
- *                                      What if there's an aor with the same
- *                                      name as the host?
- *      endpoint@domain vs user@host  : We have to test for endpoint first.
- *                                      What if there's an endpoint with the
- *                                      same name as the user?
- *
- * \param to 'To' field with possible endpoint
- * \param uri Pointer to a char* which will be set to the URI.
- *            Must be ast_free'd by the caller.
- *
- * \note  The logic below could probably be condensed but then it wouldn't be
- * as clear.
- */
-static struct ast_sip_endpoint *get_outbound_endpoint(const char *to, char **uri)
-{
-       char *destination;
-       char *slash = NULL;
-       char *atsign = NULL;
-       char *scheme = NULL;
-       struct ast_sip_endpoint *endpoint = NULL;
-
-       destination = ast_strdupa(to);
-       slash = strchr(destination, '/');
-       atsign = strchr(destination, '@');
-       scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
-
-       if (!slash && !atsign && !scheme) {
-               /*
-                * If there's only a single token, it can be either...
-                *      endpoint
-                *      host
-                */
-               return handle_single_token(to, destination, uri);
-       }
-
-       if (slash) {
-               /*
-                * If there's a '/', then the form must be one of the following...
-                *      PJSIP/user@endpoint
-                *      endpoint/aor
-                *      endpoint/uri
-                */
-               return handle_slash(to, destination, uri, slash, atsign, scheme);
-       }
-
-       if (!endpoint && atsign && !scheme) {
-               /*
-                * If there's an '@' but no scheme then it's either following an endpoint name
-                * and being followed by a domain name (which we discard).
-                * OR is's a user@host uri without a scheme.  It's probably the latter but because
-                * endpoint@domain looks just like user@host, we'll test for endpoint first.
-                */
-               return handle_atsign(to, destination, uri, slash, atsign, scheme);
-       }
-
-       /*
-        * If all else fails, we assume it's a URI or just a hostname.
-        */
-       if (scheme) {
-               *uri = ast_strdup(destination);
-               ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s' with default endpoint\n",
-                       to, *uri);
-       } else {
-               *uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
-               sprintf(*uri, "sip:%s", destination);
-               ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s' with default endpoint\n",
-                       to, *uri);
-       }
-       endpoint = ast_sip_default_outbound_endpoint();
-
-       return endpoint;
-}
-
-/*!
- * \internal
- * \brief Replace the To URI in the tdata with the supplied one
- *
- * \param tdata the outbound message data structure
- * \param to URI to replace the To URI with
- *
- * \return 0: success, -1: failure
- */
-static int update_to_uri(pjsip_tx_data *tdata, char *to)
-{
-       pjsip_name_addr *parsed_name_addr;
-       pjsip_sip_uri *sip_uri;
-       pjsip_name_addr *tdata_name_addr;
-       pjsip_sip_uri *tdata_sip_uri;
-       char *buf = NULL;
-#define DEBUG_BUF_SIZE 256
-
-       parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, to, strlen(to),
-               PJSIP_PARSE_URI_AS_NAMEADDR);
-
-       if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
-                       && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
-               ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
-               return -1;
-       }
-
-       sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
-       if (DEBUG_ATLEAST(3)) {
-               buf = ast_alloca(DEBUG_BUF_SIZE);
-               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
-               ast_debug(3, "Parsed To: %.*s  %s\n", (int)parsed_name_addr->display.slen,
-                       parsed_name_addr->display.ptr, buf);
-       }
-
-       tdata_name_addr = (pjsip_name_addr *) PJSIP_MSG_TO_HDR(tdata->msg)->uri;
-       if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
-                       && !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
-               /* Highly unlikely but we have to check */
-               ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
-               return -1;
-       }
-
-       tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
-       if (DEBUG_ATLEAST(3)) {
-               buf[0] = '\0';
-               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
-               ast_debug(3, "Original tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
-                       tdata_name_addr->display.ptr, buf);
-       }
-
-       /* Replace the uri */
-       pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
-       /* The display name isn't part of the URI so we need to replace it separately */
-       pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
-
-       if (DEBUG_ATLEAST(3)) {
-               buf[0] = '\0';
-               pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
-               ast_debug(3, "New tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
-                       tdata_name_addr->display.ptr, buf);
-       }
-
-       return 0;
-#undef DEBUG_BUF_SIZE
-}
-
 /*!
  * \internal
  * \brief Update the display name in the To uri in the tdata with the one from the supplied uri
@@ -790,77 +225,6 @@ static int update_to_display_name(pjsip_tx_data *tdata, char *to)
        return -1;
 }
 
-/*!
- * \internal
- * \brief Overwrite fields in the outbound 'From' header
- *
- * The outbound 'From' header is created/added in ast_sip_create_request with
- * default data.  If available that data may be info specified in the 'from_user'
- * and 'from_domain' options found on the endpoint.  That information will be
- * overwritten with data in the given 'from' parameter.
- *
- * \param tdata the outbound message data structure
- * \param from info to copy into the header
- *
- * \return 0: success, -1: failure
- */
-static int update_from(pjsip_tx_data *tdata, char *from)
-{
-       pjsip_name_addr *name_addr;
-       pjsip_sip_uri *uri;
-       pjsip_name_addr *parsed_name_addr;
-
-       if (ast_strlen_zero(from)) {
-               return 0;
-       }
-
-       name_addr = (pjsip_name_addr *) PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
-       uri = pjsip_uri_get_uri(name_addr);
-
-       parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
-               strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
-       if (parsed_name_addr) {
-               pjsip_sip_uri *parsed_uri;
-
-               if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
-                               && !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
-                       ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
-                       return -1;
-               }
-
-               parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
-
-               if (pj_strlen(&parsed_name_addr->display)) {
-                       pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
-               }
-
-               /* Unlike the To header, we only want to replace the user, host and port */
-               pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
-               pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
-               uri->port = parsed_uri->port;
-
-               return 0;
-       } else {
-               /* assume it is 'user[@domain]' format */
-               char *domain = strchr(from, '@');
-
-               if (domain) {
-                       pj_str_t pj_from;
-
-                       pj_strset3(&pj_from, from, domain);
-                       pj_strdup(tdata->pool, &uri->user, &pj_from);
-
-                       pj_strdup2(tdata->pool, &uri->host, domain + 1);
-               } else {
-                       pj_strdup2(tdata->pool, &uri->user, from);
-               }
-
-               return 0;
-       }
-
-       return -1;
-}
-
 /*!
  * \internal
  * \brief Checks if the given msg var name should be blocked.
@@ -1252,7 +616,7 @@ static int msg_send(void *data)
        ast_debug(3, "mdata From: %s msg From: %s mdata Destination: %s msg To: %s\n",
                mdata->from, ast_msg_get_from(mdata->msg), mdata->destination, ast_msg_get_to(mdata->msg));
 
-       endpoint = get_outbound_endpoint(mdata->destination, &uri);
+       endpoint = ast_sip_get_endpoint(mdata->destination, 1, &uri);
        if (!endpoint) {
                ast_log(LOG_ERROR,
                        "PJSIP MESSAGE - Could not find endpoint '%s' and no default outbound endpoint configured\n",
@@ -1290,7 +654,7 @@ static int msg_send(void *data)
                if (ast_begins_with(msg_to, "pjsip:")) {
                        msg_to += 6;
                }
-               update_to_uri(tdata, msg_to);
+               ast_sip_update_to_uri(tdata, msg_to);
        } else {
                /*
                 * If there was no To in the message, it's still possible
@@ -1301,9 +665,9 @@ static int msg_send(void *data)
        }
 
        if (!ast_strlen_zero(mdata->from)) {
-               update_from(tdata, mdata->from);
+               ast_sip_update_from(tdata, mdata->from);
        } else if (!ast_strlen_zero(ast_msg_get_from(mdata->msg))) {
-               update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
+               ast_sip_update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
        }
 
 #ifdef TEST_FRAMEWORK
index 59223f7bbf08f11db80611440757097409448332..62f8aa0b2364ac0ff1f8fb3df5723ec77255d727 100644 (file)
@@ -235,52 +235,6 @@ static pj_bool_t nat_on_rx_message(pjsip_rx_data *rdata)
        return res;
 }
 
-/*! \brief Structure which contains information about a transport */
-struct request_transport_details {
-       /*! \brief Type of transport */
-       enum ast_transport type;
-       /*! \brief Potential pointer to the transport itself, if UDP */
-       pjsip_transport *transport;
-       /*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
-       pjsip_tpfactory *factory;
-       /*! \brief Local address for transport */
-       pj_str_t local_address;
-       /*! \brief Local port for transport */
-       int local_port;
-};
-
-/*! \brief Callback function for finding the transport the request is going out on */
-static int find_transport_state_in_use(void *obj, void *arg, int flags)
-{
-       struct ast_sip_transport_state *transport_state = obj;
-       struct request_transport_details *details = arg;
-
-       /* If an explicit transport or factory matches then this is what is in use, if we are unavailable
-        * to compare based on that we make sure that the type is the same and the source IP address/port are the same
-        */
-       if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
-               (details->factory && details->factory == transport_state->factory) ||
-               ((details->type == transport_state->type) && (transport_state->factory) &&
-                       !pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
-                       transport_state->factory->addr_name.port == details->local_port))) {
-               return CMP_MATCH;
-       }
-
-       return 0;
-}
-
-/*! \brief Helper function which returns the SIP URI of a Contact header */
-static pjsip_sip_uri *nat_get_contact_sip_uri(pjsip_tx_data *tdata)
-{
-       pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
-
-       if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
-               return NULL;
-       }
-
-       return pjsip_uri_get_uri(contact->uri);
-}
-
 /*! \brief Structure which contains hook details */
 struct nat_hook_details {
        /*! \brief Outgoing message itself */
@@ -363,55 +317,22 @@ static void restore_orig_contact_host(pjsip_tx_data *tdata)
 
 static pj_status_t process_nat(pjsip_tx_data *tdata)
 {
-       RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
        RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
        RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
-       struct request_transport_details details = { 0, };
        pjsip_via_hdr *via = NULL;
+       struct ast_sip_request_transport_details details;
        struct ast_sockaddr addr = { { 0, } };
        pjsip_sip_uri *uri = NULL;
        RAII_VAR(struct ao2_container *, hooks, NULL, ao2_cleanup);
 
-       /* If a transport selector is in use we know the transport or factory, so explicitly find it */
-       if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
-               details.transport = tdata->tp_sel.u.transport;
-       } else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
-               details.factory = tdata->tp_sel.u.listener;
-       } else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP || tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP6) {
-               /* Connectionless uses the same transport for all requests */
-               details.type = AST_TRANSPORT_UDP;
-               details.transport = tdata->tp_info.transport;
-       } else {
-               if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TCP) {
-                       details.type = AST_TRANSPORT_TCP;
-               } else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TLS) {
-                       details.type = AST_TRANSPORT_TLS;
-               } else {
-                       /* Unknown transport type, we can't map and thus can't apply NAT changes */
-                       return PJ_SUCCESS;
-               }
-
-               if ((uri = nat_get_contact_sip_uri(tdata))) {
-                       details.local_address = uri->host;
-                       details.local_port = uri->port;
-               } else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
-                       (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
-                       details.local_address = via->sent_by.host;
-                       details.local_port = via->sent_by.port;
-               } else {
-                       return PJ_SUCCESS;
-               }
-
-               if (!details.local_port) {
-                       details.local_port = (details.type == AST_TRANSPORT_TLS) ? 5061 : 5060;
-               }
-       }
-
-       if (!(transport_states = ast_sip_get_transport_states())) {
+       if (ast_sip_set_request_transport_details(&details, tdata, 0)) {
                return PJ_SUCCESS;
        }
 
-       if (!(transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, &details))) {
+       uri = ast_sip_get_contact_sip_uri(tdata);
+       via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+
+       if (!(transport_state = ast_sip_find_transport_state_in_use(&details))) {
                return PJ_SUCCESS;
        }
 
@@ -443,7 +364,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
                if (!cseq || tdata->msg->type == PJSIP_REQUEST_MSG ||
                        pjsip_method_cmp(&cseq->method, &pjsip_register_method)) {
                        /* We can only rewrite the URI when one is present */
-                       if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
+                       if (uri || (uri = ast_sip_get_contact_sip_uri(tdata))) {
                                pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
                                if (transport->external_signaling_port) {
                                        uri->port = transport->external_signaling_port;
index a20bf6ba4de04da5cc7e68fb0ef9a5a3943e515e..a10a9fde506e2ac2ae1da6f527b52a21aa567601 100644 (file)
 #include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/causes.h"
+#include "asterisk/refer.h"
+
+static struct ast_taskprocessor *refer_serializer;
+
+static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata);
 
 /*! \brief REFER Progress structure */
 struct refer_progress {
@@ -786,6 +791,499 @@ static void refer_blind_callback(struct ast_channel *chan, struct transfer_chann
                ast_channel_unlock((session)->channel);                                                                                 \
        } while (0)                                                                                                                                                     \
 
+struct refer_data {
+       struct ast_refer *refer;
+       char *destination;
+       char *from;
+       char *refer_to;
+       int to_self;
+};
+
+static void refer_data_destroy(void *obj)
+{
+       struct refer_data *rdata = obj;
+
+       ast_free(rdata->destination);
+       ast_free(rdata->from);
+       ast_free(rdata->refer_to);
+
+       ast_refer_destroy(rdata->refer);
+}
+
+static struct refer_data *refer_data_create(const struct ast_refer *refer)
+{
+       char *uri_params;
+       const char *destination;
+       struct refer_data *rdata = ao2_alloc_options(sizeof(*rdata), refer_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+       if (!rdata) {
+               return NULL;
+       }
+
+       /* typecast to suppress const warning */
+       rdata->refer = ast_refer_ref((struct ast_refer *) refer);
+       destination = ast_refer_get_to(refer);
+
+       /* To starts with 'pjsip:' which needs to be removed. */
+       if (!(destination = strchr(destination, ':'))) {
+               goto failure;
+       }
+       ++destination;/* Now skip the ':' */
+
+       rdata->destination = ast_strdup(destination);
+       if (!rdata->destination) {
+               goto failure;
+       }
+
+       rdata->from = ast_strdup(ast_refer_get_from(refer));
+       if (!rdata->from) {
+               goto failure;
+       }
+
+       rdata->refer_to = ast_strdup(ast_refer_get_refer_to(refer));
+       if (!rdata->refer_to) {
+               goto failure;
+       }
+       rdata->to_self = ast_refer_get_to_self(refer);
+
+       /*
+        * Sometimes from URI can contain URI parameters, so remove them.
+        *
+        * sip:user;user-options@domain;uri-parameters
+        */
+       uri_params = strchr(rdata->from, '@');
+       if (uri_params && (uri_params = strchr(uri_params, ';'))) {
+               *uri_params = '\0';
+       }
+       return rdata;
+
+failure:
+       ao2_cleanup(rdata);
+       return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Checks if the given refer var name should be blocked.
+ *
+ * \details Some headers are not allowed to be overridden by the user.
+ *  Determine if the given var header name from the user is blocked for
+ *  an outgoing REFER.
+ *
+ * \param name name of header to see if it is blocked.
+ *
+ * \retval TRUE if the given header is blocked.
+ */
+static int is_refer_var_blocked(const char *name)
+{
+       int i;
+
+       /* Don't block the Max-Forwards header because the user can override it */
+       static const char *hdr[] = {
+               "To",
+               "From",
+               "Via",
+               "Route",
+               "Contact",
+               "Call-ID",
+               "CSeq",
+               "Allow",
+               "Content-Length",
+               "Content-Type",
+               "Request-URI",
+       };
+
+       for (i = 0; i < ARRAY_LEN(hdr); ++i) {
+               if (!strcasecmp(name, hdr[i])) {
+                       /* Block addition of this header. */
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Copies any other refer vars over to the request headers.
+ *
+ * \param refer The refer structure to copy headers from
+ * \param tdata The SIP transmission data
+ */
+static enum pjsip_status_code vars_to_headers(const struct ast_refer *refer, pjsip_tx_data *tdata)
+{
+       const char *name;
+       const char *value;
+       struct ast_refer_var_iterator *iter;
+
+       for (iter = ast_refer_var_iterator_init(refer);
+               ast_refer_var_iterator_next(iter, &name, &value);
+               ast_refer_var_unref_current(iter)) {
+               if (!is_refer_var_blocked(name)) {
+                       ast_sip_add_header(tdata, name, value);
+               }
+       }
+       ast_refer_var_iterator_destroy(iter);
+
+       return PJSIP_SC_OK;
+}
+
+struct refer_out_of_dialog {
+       pjsip_dialog *dlg;
+       int authentication_challenge_count;
+};
+
+/*! \brief REFER Out-of-dialog module, used to attach session data structure to subscription */
+static pjsip_module refer_out_of_dialog_module = {
+       .name = { "REFER Out-of-dialog Module", 26 },
+       .id = -1,
+       .on_tx_request = refer_on_tx_request,
+       /* Ensure that we are called after res_pjsp_nat module and before transport priority */
+       .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 4,
+};
+
+/*! \brief Helper function which returns the name-addr of the Refer-To header or NULL */
+static pjsip_uri *get_refer_to_uri(pjsip_tx_data *tdata)
+{
+       const pj_str_t REFER_TO = { "Refer-To", 8 };
+       pjsip_generic_string_hdr *refer_to;
+       pjsip_uri *parsed_uri;
+
+       if (!(refer_to = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL))
+               || !(parsed_uri = pjsip_parse_uri(tdata->pool, refer_to->hvalue.ptr, refer_to->hvalue.slen, 0))
+               || (!PJSIP_URI_SCHEME_IS_SIP(parsed_uri) && !PJSIP_URI_SCHEME_IS_SIPS(parsed_uri))) {
+               return NULL;
+       }
+
+       return parsed_uri;
+}
+
+static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata) {
+       RAII_VAR(struct ast_str *, refer_to_str, ast_str_create(PJSIP_MAX_URL_SIZE), ast_free_ptr);
+       const pj_str_t REFER_TO = { "Refer-To", 8 };
+       pjsip_generic_string_hdr *refer_to_hdr;
+       pjsip_dialog *dlg;
+       struct refer_data *refer_data;
+       pjsip_uri *parsed_uri;
+       pjsip_sip_uri *refer_to_uri;
+
+       /*
+        * If this is a request in response to a 401/407 Unauthorized challenge, the
+        * Refer-To URI has been rewritten already, so don't attempt to re-write it again.
+        * Checking for presence of the Authorization header is not an ideal solution. We do this because
+        * there exists some race condition where this dialog is not the same as the one used
+        * to send the original request in which case we don't have the correct refer_data.
+        */
+       if (!refer_to_str
+               || pjsip_msg_find_hdr(tdata->msg, PJSIP_H_AUTHORIZATION, NULL)
+               || !(dlg = pjsip_tdata_get_dlg(tdata))
+               || !(refer_data = pjsip_dlg_get_mod_data(dlg, refer_out_of_dialog_module.id))
+               || !refer_data->to_self
+               || !(parsed_uri = get_refer_to_uri(tdata))) {
+               goto out;
+       }
+       refer_to_uri = pjsip_uri_get_uri(parsed_uri);
+       ast_sip_rewrite_uri_to_local(refer_to_uri, tdata);
+
+       pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, parsed_uri, ast_str_buffer(refer_to_str), ast_str_size(refer_to_str));
+       refer_to_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL);
+       pj_strdup2(tdata->pool, &refer_to_hdr->hvalue, ast_str_buffer(refer_to_str));
+
+out:
+       return PJ_SUCCESS;
+}
+
+static int refer_unreference_dialog(void *obj)
+{
+       struct refer_out_of_dialog *data = obj;
+
+       /* This is why we keep the dialog on the subscription. When the subscription
+        * is destroyed, there is no guarantee that the underlying dialog is ready
+        * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+        * either. The dialog could be destroyed before our subscription is. We fix
+        * this problem by keeping a reference to the dialog until it is time to
+        * destroy the subscription.
+        */
+       pjsip_dlg_dec_session(data->dlg, &refer_out_of_dialog_module);
+       data->dlg = NULL;
+
+       return 0;
+}
+/*! \brief Destructor for REFER out of dialog structure */
+static void refer_out_of_dialog_destroy(void *obj) {
+       struct refer_out_of_dialog *data = obj;
+
+       if (data->dlg) {
+               /* ast_sip_push_task_wait_servant should not be called in a destructor,
+                * however in this case it seems to be fine.
+                */
+               ast_sip_push_task_wait_servant(refer_serializer, refer_unreference_dialog, data);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Callback function to report status of implicit REFER-NOTIFY subscription.
+ *
+ * This function will be called on any state change in the REFER-NOTIFY subscription.
+ * Its primary purpose is to report SUCCESS/FAILURE of a refer initiated via
+ * \ref refer_send as well as to terminate the subscription, if necessary.
+ */
+static void refer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
+{
+       pjsip_tx_data *tdata;
+       RAII_VAR(struct ast_sip_endpoint *, endpt, NULL, ao2_cleanup);
+       struct refer_out_of_dialog *refer_data;
+       int refer_success;
+       int res = 0;
+
+       if (!event) {
+               return;
+       }
+
+       refer_data = pjsip_evsub_get_mod_data(sub, refer_out_of_dialog_module.id);
+       if (!refer_data || !refer_data->dlg) {
+               return;
+       }
+
+       endpt = ast_sip_dialog_get_endpoint(refer_data->dlg);
+
+       if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
+               /* Check if subscription is suppressed and terminate and send completion code, if so. */
+               pjsip_rx_data *rdata;
+               pjsip_generic_string_hdr *refer_sub;
+               const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
+
+               ast_debug(3, "Refer accepted by %s\n", endpt ? ast_sorcery_object_get_id(endpt) : "(unknown endpoint)");
+
+               /* Check if response message */
+               if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+                       rdata = event->body.tsx_state.src.rdata;
+
+                       /* Find Refer-Sub header */
+                       refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &REFER_SUB, NULL);
+
+                       /* Check if subscription is suppressed. If it is, the far end will not terminate it,
+                        * and the subscription will remain active until it times out.  Terminating it here
+                        * eliminates the unnecessary timeout.
+                        */
+                       if (refer_sub && !pj_stricmp2(&refer_sub->hvalue, "false")) {
+                               /* Since no subscription is desired, assume that call has been referred successfully
+                                * and terminate subscription.
+                                */
+                               pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
+                               pjsip_evsub_terminate(sub, PJ_TRUE);
+                               res = -1;
+                       }
+               }
+       } else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
+                       pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+               /* Check for NOTIFY complete or error. */
+               pjsip_msg *msg;
+               pjsip_msg_body *body;
+               pjsip_status_line status_line = { .code = 0 };
+               pj_bool_t is_last;
+               pj_status_t status;
+
+               if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+                       pjsip_rx_data *rdata;
+                       pj_str_t refer_str;
+                       pj_cstr(&refer_str, "REFER");
+
+                       rdata = event->body.tsx_state.src.rdata;
+                       msg = rdata->msg_info.msg;
+
+                       if (msg->type == PJSIP_RESPONSE_MSG
+                               && (event->body.tsx_state.tsx->status_code == 401
+                                       || event->body.tsx_state.tsx->status_code == 407)
+                               && pj_stristr(&refer_str, &event->body.tsx_state.tsx->method.name)
+                               && ++refer_data->authentication_challenge_count < MAX_RX_CHALLENGES
+                               && endpt) {
+
+                               if (!ast_sip_create_request_with_auth(&endpt->outbound_auths,
+                                       event->body.tsx_state.src.rdata, event->body.tsx_state.tsx->last_tx, &tdata)) {
+                                       /* Send authed REFER */
+                                       ast_sip_send_request(tdata, refer_data->dlg, NULL, NULL, NULL);
+                                       goto out;
+                               }
+                       }
+
+                       if (msg->type == PJSIP_REQUEST_MSG) {
+                               if (!pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())) {
+                                       body = msg->body;
+                                       if (body && !pj_stricmp2(&body->content_type.type, "message")
+                                               && !pj_stricmp2(&body->content_type.subtype, "sipfrag")) {
+                                               pjsip_parse_status_line((char *)body->data, body->len, &status_line);
+                                       }
+                               }
+                       } else {
+                               status_line.code = msg->line.status.code;
+                               status_line.reason = msg->line.status.reason;
+                       }
+               } else {
+                       status_line.code = 500;
+                       status_line.reason = *pjsip_get_status_text(500);
+               }
+
+               is_last = (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED);
+               /* If the status code is >= 200, the subscription is finished. */
+               if (status_line.code >= 200 || is_last) {
+                       res = -1;
+
+                       refer_success = status_line.code >= 200 && status_line.code < 300;
+
+                       /* If subscription not terminated and subscription is finished (status code >= 200)
+                        * terminate it */
+                       if (!is_last) {
+                               pjsip_tx_data *tdata;
+
+                               status = pjsip_evsub_initiate(sub, pjsip_get_subscribe_method(), 0, &tdata);
+                               if (status == PJ_SUCCESS) {
+                                       pjsip_evsub_send_request(sub, tdata);
+                               }
+                       }
+                       ast_debug(3, "Refer completed: %d %.*s (%s)\n",
+                                       status_line.code,
+                                       (int)status_line.reason.slen, status_line.reason.ptr,
+                                       refer_success ? "Success" : "Failure");
+               }
+       }
+
+out:
+       if (res) {
+               ao2_cleanup(refer_data);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Send a REFER
+ *
+ * \param data The outbound refer data structure
+ *
+ * \return 0: success, -1: failure
+ */
+static int refer_send(void *data)
+{
+       struct refer_data *rdata = data; /* The caller holds a reference */
+       pjsip_tx_data *tdata;
+       pjsip_evsub *sub;
+       pj_str_t tmp;
+       char refer_to_str[PJSIP_MAX_URL_SIZE];
+       char disp_name_escaped[128];
+       struct refer_out_of_dialog *refer;
+       struct pjsip_evsub_user xfer_cb;
+       RAII_VAR(char *, uri, NULL, ast_free);
+       RAII_VAR(char *, tmp_str, NULL, ast_free);
+       RAII_VAR(char *, display_name, NULL, ast_free);
+       RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_sip_endpoint *, refer_to_endpoint, NULL, ao2_cleanup);
+
+       endpoint = ast_sip_get_endpoint(rdata->destination, 1, &uri);
+       if (!endpoint) {
+               ast_log(LOG_ERROR,
+                       "PJSIP REFER - Could not find endpoint '%s' and no default outbound endpoint configured\n",
+                       rdata->destination);
+               return -1;
+       }
+       ast_debug(3, "Request URI: %s\n", uri);
+
+       refer_to_endpoint = ast_sip_get_endpoint(rdata->refer_to, 0, &tmp_str);
+       if (!tmp_str) {
+               ast_log(LOG_WARNING, "PJSIP REFER - Refer to not a valid resource identifier or SIP URI\n");
+               return -1;
+       }
+       if (!(refer = ao2_alloc(sizeof(struct refer_out_of_dialog), refer_out_of_dialog_destroy))) {
+               ast_log(LOG_ERROR, "PJSIP REFER - Could not allocate resources.\n");
+               return -1;
+       }
+       /* The dialog will be terminated in the subscription event callback
+        * when the subscription has terminated. */
+       refer->authentication_challenge_count = 0;
+       refer->dlg = ast_sip_create_dialog_uac(endpoint, uri, NULL);
+       if (!refer->dlg) {
+               ast_log(LOG_WARNING, "PJSIP REFER - Could not create dialog\n");
+               ao2_cleanup(refer);
+               return -1;
+       }
+       ast_sip_dialog_set_endpoint(refer->dlg, endpoint);
+
+       pj_bzero(&xfer_cb, sizeof(xfer_cb));
+       xfer_cb.on_evsub_state = &refer_client_on_evsub_state;
+       if (pjsip_xfer_create_uac(refer->dlg, &xfer_cb, &sub) != PJ_SUCCESS) {
+               ast_log(LOG_WARNING, "PJSIP REFER - Could not create uac\n");
+               ao2_cleanup(refer);
+               return -1;
+       }
+
+       display_name = ast_refer_get_var_and_unlink(rdata->refer, "display_name");
+       if (display_name) {
+               ast_escape_quoted(display_name, disp_name_escaped, sizeof(disp_name_escaped));
+               snprintf(refer_to_str, sizeof(refer_to_str), "\"%s\" <%s>", disp_name_escaped, tmp_str);
+       } else {
+               snprintf(refer_to_str, sizeof(refer_to_str), "%s", tmp_str);
+       }
+
+       /* refer_out_of_dialog_module requires a reference to dlg
+        * which will be released in refer_client_on_evsub_state()
+        * when the implicit REFER subscription terminates */
+       pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, refer);
+       if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, refer_to_str), &tdata) != PJ_SUCCESS) {
+               ast_log(LOG_WARNING, "PJSIP REFER - Could not create request\n");
+               goto failure;
+       }
+
+       if (refer_to_endpoint && rdata->to_self) {
+               pjsip_dlg_add_usage(refer->dlg, &refer_out_of_dialog_module, rdata);
+       }
+
+       ast_sip_update_to_uri(tdata, uri);
+       ast_sip_update_from(tdata, rdata->from);
+
+       /*
+        * This copies any headers found in the refer's variables to
+        * tdata.
+        */
+       vars_to_headers(rdata->refer, tdata);
+       ast_debug(1, "Sending REFER to '%s' (via endpoint %s) from '%s'\n",
+               rdata->destination, ast_sorcery_object_get_id(endpoint), rdata->from);
+
+       if (pjsip_xfer_send_request(sub, tdata) == PJ_SUCCESS) {
+               return 0;
+       }
+
+failure:
+       ao2_cleanup(refer);
+       pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
+       pjsip_evsub_terminate(sub, PJ_FALSE);
+       return -1;
+}
+
+static int sip_refer_send(const struct ast_refer *refer)
+{
+       struct refer_data *rdata;
+       int res;
+
+       if (ast_strlen_zero(ast_refer_get_to(refer))) {
+               ast_log(LOG_ERROR, "SIP REFER - a 'To' URI  must be specified\n");
+               return -1;
+       }
+
+       rdata = refer_data_create(refer);
+       if (!rdata) {
+               return -1;
+       }
+
+       res = ast_sip_push_task_wait_serializer(refer_serializer, refer_send, rdata);
+       ao2_ref(rdata, -1);
+
+       return res;
+}
+
+static const struct ast_refer_tech refer_tech = {
+       .name = "pjsip",
+       .refer_send = sip_refer_send,
+};
+
 static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
        pjsip_param *replaces_param, struct refer_progress *progress)
 {
@@ -1274,6 +1772,17 @@ static int load_module(void)
                pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
        }
 
+       if (ast_refer_tech_register(&refer_tech)) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       refer_serializer = ast_sip_create_serializer("pjsip/refer");
+       if (!refer_serializer) {
+               ast_refer_tech_unregister(&refer_tech);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_sip_register_service(&refer_out_of_dialog_module);
        ast_sip_register_service(&refer_progress_module);
        ast_sip_session_register_supplement(&refer_supplement);
 
@@ -1285,7 +1794,9 @@ static int load_module(void)
 static int unload_module(void)
 {
        ast_sip_session_unregister_supplement(&refer_supplement);
+       ast_sip_unregister_service(&refer_out_of_dialog_module);
        ast_sip_unregister_service(&refer_progress_module);
+       ast_taskprocessor_unreference(refer_serializer);
 
        return 0;
 }
index 80baf97bcc7a7ee14f03eb6273c6028195f84595..3f3f98cec197b29f5dca6072ed1e31cf626ddee8 100644 (file)
@@ -55,7 +55,7 @@
                                                },
                                                {
                                                        "name": "variables",
-                                                       "descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip resource types will add the key/value pairs as SIP headers,",
+                                                       "descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers,",
                                                        "paramType": "body",
                                                        "required": false,
                                                        "dataType": "containers",
                                }
                        ]
                },
+               {
+                       "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": {