diff options
Diffstat (limited to 'res')
28 files changed, 5391 insertions, 290 deletions
diff --git a/res/res_sip.c b/res/res_sip.c index 1af3fb78e..ebbf596de 100644 --- a/res/res_sip.c +++ b/res/res_sip.c @@ -53,7 +53,7 @@ It contains the core SIP related options only, endpoints are <emphasis>NOT</emphasis> dialable entries of their own. Communication with another SIP device is accomplished via Addresses of Record (AoRs) which have one or more - contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to + contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to use a <literal>transport</literal> will default to first transport found in <filename>res_sip.conf</filename> that matches its type. </para> @@ -62,6 +62,12 @@ first transport that matches the type. A SIP URI of <literal>sip:5000@[11::33]</literal> will use the first IPv6 transport and try to send the request. </para> + <para>If the anonymous endpoint identifier is in use an endpoint with the name + "anonymous@domain" will be searched for as a last resort. If this is not found + it will fall back to searching for "anonymous". If neither endpoints are found + the anonymous endpoint identifier will not return an endpoint and anonymous + calling will not be possible. + </para> </description> <configOption name="100rel" default="yes"> <synopsis>Allow support for RFC3262 provisional ACK tags</synopsis> @@ -161,6 +167,19 @@ </enumlist> </description> </configOption> + <configOption name="connected_line_method" default="invite"> + <synopsis>Connected line method type</synopsis> + <description> + <para>Method used when updating connected line information.</para> + <enumlist> + <enum name="invite" /> + <enum name="reinvite"> + <para>Alias for the <literal>invite</literal> value.</para> + </enum> + <enum name="update" /> + </enumlist> + </description> + </configOption> <configOption name="direct_media" default="yes"> <synopsis>Determines whether media may flow directly between endpoints.</synopsis> </configOption> @@ -223,13 +242,6 @@ <configOption name="outbound_proxy"> <synopsis>Proxy through which to send requests</synopsis> </configOption> - <configOption name="qualify_frequency" default="0"> - <synopsis>Interval at which to qualify an endpoint</synopsis> - <description><para> - Interval between attempts to qualify the endpoint for reachability. - If <literal>0</literal> never qualify. Time in seconds. - </para></description> - </configOption> <configOption name="rewrite_contact"> <synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis> </configOption> @@ -298,6 +310,75 @@ <configOption name="use_ptime" default="no"> <synopsis>Use Endpoint's requested packetisation interval</synopsis> </configOption> + <configOption name="use_avpf" default="no"> + <synopsis>Determines whether res_sip will use and enforce usage of AVPF for this + endpoint.</synopsis> + <description><para> + If set to <literal>yes</literal>, res_sip will use use the AVPF or SAVPF RTP + profile for all media offers on outbound calls and media updates and will + decline media offers not using the AVPF or SAVPF profile. + </para><para> + If set to <literal>no</literal>, res_sip will use use the AVP or SAVP RTP + profile for all media offers on outbound calls and media updates and will + decline media offers not using the AVP or SAVP profile. + </para></description> + </configOption> + <configOption name="media_encryption" default="no"> + <synopsis>Determines whether res_sip will use and enforce usage of media encryption + for this endpoint.</synopsis> + <description> + <enumlist> + <enum name="no"><para> + res_sip will offer no encryption and allow no encryption to be setup. + </para></enum> + <enum name="sdes"><para> + res_sip will offer standard SRTP setup via in-SDP keys. Encrypted SIP + transport should be used in conjunction with this option to prevent + exposure of media encryption keys. + </para></enum> + </enumlist> + </description> + </configOption> + <configOption name="inband_progress" default="no"> + <synopsis>Determines whether chan_gulp will indicate ringing using inband + progress.</synopsis> + <description><para> + If set to <literal>yes</literal>, chan_gulp will send a 183 Session Progress + when told to indicate ringing and will immediately start sending ringing + as audio. + </para><para> + If set to <literal>no</literal>, chan_gulp will send a 180 Ringing when told + to indicate ringing and will NOT send it as audio. + </para></description> + </configOption> + <configOption name="callgroup"> + <synopsis>The numeric pickup groups for a channel.</synopsis> + <description><para> + Can be set to a comma separated list of numbers or ranges between the values + of 0-63 (maximum of 64 groups). + </para></description> + </configOption> + <configOption name="pickupgroup"> + <synopsis>The numeric pickup groups that a channel can pickup.</synopsis> + <description><para> + Can be set to a comma separated list of numbers or ranges between the values + of 0-63 (maximum of 64 groups). + </para></description> + </configOption> + <configOption name="namedcallgroup"> + <synopsis>The named pickup groups for a channel.</synopsis> + <description><para> + Can be set to a comma separated list of case sensitive strings limited by + supported line length. + </para></description> + </configOption> + <configOption name="namedpickupgroup"> + <synopsis>The named pickup groups that a channel can pickup.</synopsis> + <description><para> + Can be set to a comma separated list of case sensitive strings limited by + supported line length. + </para></description> + </configOption> <configOption name="devicestate_busy_at" default="0"> <synopsis>The number of in-use channels which will cause busy to be returned as device state</synopsis> <description><para> @@ -479,6 +560,35 @@ Time to keep alive a contact. String style specification. </para></description> </configOption> + <configOption name="qualify_frequency" default="0"> + <synopsis>Interval at which to qualify a contact</synopsis> + <description><para> + Interval between attempts to qualify the contact for reachability. + If <literal>0</literal> never qualify. Time in seconds. + </para></description> + </configOption> + </configObject> + <configObject name="contact_status"> + <synopsis>Status for a contact</synopsis> + <description><para> + The contact status keeps track of whether or not a contact is reachable + and how long it took to qualify the contact (round trip time). + </para></description> + <configOption name="status"> + <synopsis>A contact's status</synopsis> + <description> + <enumlist> + <enum name="AVAILABLE" /> + <enum name="UNAVAILABLE" /> + </enumlist> + </description> + </configOption> + <configOption name="rtt"> + <synopsis>Round trip time</synopsis> + <description><para> + The time, in microseconds, it took to qualify the contact. + </para></description> + </configOption> </configObject> <configObject name="aor"> <synopsis>The configuration for a location of an endpoint</synopsis> @@ -549,6 +659,20 @@ <configOption name="type"> <synopsis>Must be of type 'aor'.</synopsis> </configOption> + <configOption name="qualify_frequency" default="0"> + <synopsis>Interval at which to qualify an AoR</synopsis> + <description><para> + Interval between attempts to qualify the AoR for reachability. + If <literal>0</literal> never qualify. Time in seconds. + </para></description> + </configOption> + <configOption name="authenticate_qualify" default="no"> + <synopsis>Authenticates a qualify request if needed</synopsis> + <description><para> + If true and a qualify request receives a challenge or authenticate response + authentication is attempted before declaring the contact available. + </para></description> + </configOption> </configObject> </configFile> </configInfo> @@ -782,7 +906,7 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u } /* If the host is IPv6 turn the transport into an IPv6 version */ - if (pj_strchr(&sip_uri->host, ':')) { + if (pj_strchr(&sip_uri->host, ':') && type < PJSIP_TRANSPORT_START_OTHER) { type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6); } @@ -792,8 +916,8 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u return -1; } - /* If IPv6 was not specified in the host but is in the transport, set the proper type */ - if (!pj_strchr(&sip_uri->host, ':') && pj_strchr(&local_addr, ':')) { + /* If IPv6 was specified in the transport, set the proper type */ + if (pj_strchr(&local_addr, ':') && type < PJSIP_TRANSPORT_START_OTHER) { type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6); } @@ -828,10 +952,10 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo return -1; } - if (transport->type == AST_TRANSPORT_UDP) { + if (transport->state->transport) { selector->type = PJSIP_TPSELECTOR_TRANSPORT; selector->u.transport = transport->state->transport; - } else if (transport->type == AST_TRANSPORT_TCP || transport->type == AST_TRANSPORT_TLS) { + } else if (transport->state->factory) { selector->type = PJSIP_TPSELECTOR_LISTENER; selector->u.listener = transport->state->factory; } else { @@ -841,6 +965,22 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo return 0; } +static int sip_get_tpselector_from_uri(const char *uri, pjsip_tpselector *selector) +{ + RAII_VAR(struct ast_sip_contact_transport *, contact_transport, NULL, ao2_cleanup); + + contact_transport = ast_sip_location_retrieve_contact_transport_by_uri(uri); + + if (!contact_transport) { + return -1; + } + + selector->type = PJSIP_TPSELECTOR_TRANSPORT; + selector->u.transport = contact_transport->transport; + + return 0; +} + pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, const char *uri, const char *request_user) { pj_str_t local_uri = { "sip:temp@temp", 13 }, remote_uri; @@ -855,7 +995,7 @@ pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, con return NULL; } - if (sip_get_tpselector_from_endpoint(endpoint, &selector)) { + if (sip_get_tpselector_from_uri(uri, &selector) && sip_get_tpselector_from_endpoint(endpoint, &selector)) { pjsip_dlg_terminate(dlg); return NULL; } @@ -905,6 +1045,7 @@ pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, con /* PJSIP doesn't know about the INFO method, so we have to define it ourselves */ const pjsip_method pjsip_info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} }; +const pjsip_method pjsip_message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} }; static struct { const char *method; @@ -920,6 +1061,7 @@ static struct { { "NOTIFY", &pjsip_notify_method }, { "PUBLISH", &pjsip_publish_method }, { "INFO", &pjsip_info_method }, + { "MESSAGE", &pjsip_message_method }, }; static const pjsip_method *get_pjsip_method(const char *method) @@ -953,6 +1095,11 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, }; if (ast_strlen_zero(uri)) { + if (!endpoint) { + ast_log(LOG_ERROR, "An endpoint and/or uri must be specified\n"); + return -1; + } + contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors); if (!contact || ast_strlen_zero(contact->uri)) { ast_log(LOG_ERROR, "Unable to retrieve contact for endpoint %s\n", @@ -965,10 +1112,12 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s pj_cstr(&remote_uri, uri); } - if (sip_get_tpselector_from_endpoint(endpoint, &selector)) { - ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n", + if (endpoint) { + if (sip_get_tpselector_from_endpoint(endpoint, &selector)) { + ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n", ast_sorcery_object_get_id(endpoint)); - return -1; + return -1; + } } pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound request", 256, 256); @@ -1073,7 +1222,7 @@ int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value pj_str_t hdr_name; pj_str_t hdr_value; pjsip_generic_string_hdr *hdr; - + pj_cstr(&hdr_name, name); pj_cstr(&hdr_value, value); @@ -1092,7 +1241,7 @@ static pjsip_msg_body *ast_body_to_pjsip_body(pj_pool_t *pool, const struct ast_ pj_cstr(&type, body->type); pj_cstr(&subtype, body->subtype); pj_cstr(&body_text, body->body_text); - + return pjsip_msg_body_create(pool, &type, &subtype, &body_text); } @@ -1213,13 +1362,26 @@ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*si return std.fail; } -void ast_copy_pj_str(char *dest, pj_str_t *src, size_t size) +void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size) { size_t chars_to_copy = MIN(size - 1, pj_strlen(src)); memcpy(dest, pj_strbuf(src), chars_to_copy); dest[chars_to_copy] = '\0'; } +int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype) +{ + pjsip_media_type compare; + + if (!content_type) { + return 0; + } + + pjsip_media_type_init2(&compare, type, subtype); + + return pjsip_media_type_cmp(content_type, &compare, 0) ? -1 : 0; +} + pj_caching_pool caching_pool; pj_pool_t *memory_pool; pj_thread_t *monitor_thread; @@ -1352,6 +1514,8 @@ static int load_module(void) ast_res_sip_init_options_handling(0); + ast_res_sip_init_contact_transports(); + return AST_MODULE_LOAD_SUCCESS; error: diff --git a/res/res_sip.exports.in b/res/res_sip.exports.in index 010f90cb1..625a02f7e 100644 --- a/res/res_sip.exports.in +++ b/res/res_sip.exports.in @@ -37,6 +37,10 @@ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_from_aor_list; LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor_contacts; LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact; + LINKER_SYMBOL_PREFIXast_sip_location_add_contact_transport; + LINKER_SYMBOL_PREFIXast_sip_location_delete_contact_transport; + LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_uri; + LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_transport; LINKER_SYMBOL_PREFIXast_sip_location_add_contact; LINKER_SYMBOL_PREFIXast_sip_location_update_contact; LINKER_SYMBOL_PREFIXast_sip_location_delete_contact; @@ -47,6 +51,12 @@ LINKER_SYMBOL_PREFIXast_sip_dialog_get_endpoint; LINKER_SYMBOL_PREFIXast_sip_retrieve_auths; LINKER_SYMBOL_PREFIXast_sip_cleanup_auths; + LINKER_SYMBOL_PREFIXast_sip_is_content_type; + LINKER_SYMBOL_PREFIXast_sip_report_invalid_endpoint; + LINKER_SYMBOL_PREFIXast_sip_report_failed_acl; + LINKER_SYMBOL_PREFIXast_sip_report_auth_failed_challenge_response; + LINKER_SYMBOL_PREFIXast_sip_report_auth_success; + LINKER_SYMBOL_PREFIXast_sip_report_auth_challenge_sent; local: *; }; diff --git a/res/res_sip/config_transport.c b/res/res_sip/config_transport.c index 0df8c66ad..1d60274b7 100644 --- a/res/res_sip/config_transport.c +++ b/res/res_sip/config_transport.c @@ -145,6 +145,8 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj) transport->tls.password = pj_str((char*)transport->password); res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory); + } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) { + res = PJ_SUCCESS; } if (res != PJ_SUCCESS) { @@ -168,8 +170,11 @@ static int transport_protocol_handler(const struct aco_option *opt, struct ast_v transport->type = AST_TRANSPORT_TCP; } else if (!strcasecmp(var->value, "tls")) { transport->type = AST_TRANSPORT_TLS; + } else if (!strcasecmp(var->value, "ws")) { + transport->type = AST_TRANSPORT_WS; + } else if (!strcasecmp(var->value, "wss")) { + transport->type = AST_TRANSPORT_WSS; } else { - /* TODO: Implement websockets */ return -1; } diff --git a/res/res_sip/include/res_sip_private.h b/res/res_sip/include/res_sip_private.h index 318510aae..3625bab31 100644 --- a/res/res_sip/include/res_sip_private.h +++ b/res/res_sip/include/res_sip_private.h @@ -40,6 +40,14 @@ int ast_res_sip_reload_configuration(void); int ast_res_sip_init_options_handling(int reload); /*! + * \brief Initialize transport storage for contacts. + * + * \retval 0 on success + * \retval other on failure + */ +int ast_res_sip_init_contact_transports(void); + +/*! * \brief Initialize outbound authentication support * * \retval 0 Success diff --git a/res/res_sip/location.c b/res/res_sip/location.c index 91521c813..d0b0a28c9 100644 --- a/res/res_sip/location.c +++ b/res/res_sip/location.c @@ -24,6 +24,10 @@ #include "asterisk/logger.h" #include "asterisk/astobj2.h" #include "asterisk/sorcery.h" +#include "include/res_sip_private.h" + +#define CONTACT_TRANSPORTS_BUCKETS 7 +static struct ao2_container *contact_transports; /*! \brief Destructor for AOR */ static void aor_destroy(void *obj) @@ -70,6 +74,48 @@ static void *contact_alloc(const char *name) return contact; } +/*! \brief Callback function for finding a contact_transport by URI */ +static int contact_transport_find_by_uri(void *obj, void *arg, int flags) +{ + struct ast_sip_contact_transport *ct = obj; + const char *contact_uri = arg; + + return (!strcmp(ct->uri, contact_uri)) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Callback function for finding a contact_transport by transport */ +static int contact_transport_find_by_transport(void *obj, void *arg, int flags) +{ + struct ast_sip_contact_transport *ct = obj; + pjsip_transport *transport = arg; + + return (ct->transport == transport) ? CMP_MATCH | CMP_STOP : 0; +} + +void ast_sip_location_add_contact_transport(struct ast_sip_contact_transport *ct) +{ + ao2_link(contact_transports, ct); + + return; +} + +void ast_sip_location_delete_contact_transport(struct ast_sip_contact_transport *ct) +{ + ao2_unlink(contact_transports, ct); + + return; +} + +struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_uri(const char *contact_uri) +{ + return ao2_callback(contact_transports, 0, contact_transport_find_by_uri, (void *)contact_uri); +} + +struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_transport(pjsip_transport *transport) +{ + return ao2_callback(contact_transports, 0, contact_transport_find_by_transport, transport); +} + struct ast_sip_aor *ast_sip_location_retrieve_aor(const char *aor_name) { return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", aor_name); @@ -189,6 +235,8 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri, struc ast_string_field_set(contact, uri, uri); contact->expiration_time = expiration_time; + contact->qualify_frequency = aor->qualify_frequency; + contact->authenticate_qualify = aor->authenticate_qualify; return ast_sorcery_create(ast_sip_get_sorcery(), contact); } @@ -248,11 +296,15 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery) ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0); ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri)); ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, 0, 0); + ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T, + PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400); ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0); ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration)); ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration)); ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration)); + ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400); + ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify)); ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts)); ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing)); ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0); @@ -260,3 +312,17 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery) return 0; } + +int ast_res_sip_init_contact_transports(void) +{ + if (contact_transports) { + ao2_t_ref(contact_transports, -1, "Remove old contact transports"); + } + + contact_transports = ao2_t_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, CONTACT_TRANSPORTS_BUCKETS, NULL, NULL, "Create container for contact transports"); + if (!contact_transports) { + return -1; + } + + return 0; +} diff --git a/res/res_sip/security_events.c b/res/res_sip/security_events.c new file mode 100644 index 000000000..068e8551f --- /dev/null +++ b/res/res_sip/security_events.c @@ -0,0 +1,234 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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 Generate security events in the PJSIP channel + * + * \author Joshua Colp <jcolp@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <pjsip.h> + +#include "asterisk/res_sip.h" +#include "asterisk/security_events.h" + +static int find_transport_in_use(void *obj, void *arg, int flags) +{ + struct ast_sip_transport *transport = obj; + pjsip_rx_data *rdata = arg; + + if ((transport->state->transport == rdata->tp_info.transport) || + (transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) && + transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +static enum ast_transport security_event_get_transport(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); + + /* It should be impossible for these to fail as the transport has to exist for the message to exist */ + transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); + + ast_assert(transports != NULL); + + transport = ao2_callback(transports, 0, find_transport_in_use, rdata); + + ast_assert(transport != NULL); + + return transport->type; +} + +static void security_event_populate(pjsip_rx_data *rdata, char *call_id, size_t call_id_size, struct ast_sockaddr *local, struct ast_sockaddr *remote) +{ + char host[NI_MAXHOST]; + + ast_copy_pj_str(call_id, &rdata->msg_info.cid->id, call_id_size); + + ast_copy_pj_str(host, &rdata->tp_info.transport->local_name.host, sizeof(host)); + ast_sockaddr_parse(local, host, PARSE_PORT_FORBID); + ast_sockaddr_set_port(local, rdata->tp_info.transport->local_name.port); + + ast_sockaddr_parse(remote, rdata->pkt_info.src_name, PARSE_PORT_FORBID); + ast_sockaddr_set_port(remote, rdata->pkt_info.src_port); +} + +void ast_sip_report_invalid_endpoint(const char *name, pjsip_rx_data *rdata) +{ + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_inval_acct_id inval_acct_id = { + .common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID, + .common.version = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION, + .common.service = "PJSIP", + .common.account_id = name, + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); +} + +void ast_sip_report_failed_acl(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *name) +{ + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_failed_acl failed_acl_event = { + .common.event_type = AST_SECURITY_EVENT_FAILED_ACL, + .common.version = AST_SECURITY_EVENT_FAILED_ACL_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .acl_name = name, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&failed_acl_event)); +} + +void ast_sip_report_auth_failed_challenge_response(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata) +{ + pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + char nonce[64] = "", response[256] = ""; + struct ast_sockaddr local, remote; + + struct ast_security_event_chal_resp_failed chal_resp_failed = { + .common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED, + .common.version = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + + .challenge = nonce, + .response = response, + .expected_response = "", + }; + + if (auth && !pj_strcmp2(&auth->scheme, "digest")) { + ast_copy_pj_str(nonce, &auth->credential.digest.nonce, sizeof(nonce)); + ast_copy_pj_str(response, &auth->credential.digest.response, sizeof(response)); + } + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata) +{ + pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_successful_auth successful_auth = { + .common.event_type = AST_SECURITY_EVENT_SUCCESSFUL_AUTH, + .common.version = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .using_password = auth ? (uint32_t *)1 : (uint32_t *)0, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&successful_auth)); +} + +void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata) +{ + pjsip_www_authenticate_hdr *auth = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_WWW_AUTHENTICATE, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char nonce[64] = "", call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_chal_sent chal_sent = { + .common.event_type = AST_SECURITY_EVENT_CHAL_SENT, + .common.version = AST_SECURITY_EVENT_CHAL_SENT_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .challenge = nonce, + }; + + if (auth && !pj_strcmp2(&auth->scheme, "digest")) { + ast_copy_pj_str(nonce, &auth->challenge.digest.nonce, sizeof(nonce)); + } + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} diff --git a/res/res_sip/sip_configuration.c b/res/res_sip/sip_configuration.c index 3488d527e..5864bdeec 100644 --- a/res/res_sip/sip_configuration.c +++ b/res/res_sip/sip_configuration.c @@ -134,8 +134,93 @@ static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct return CLI_SUCCESS; } +static int show_contact(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ast_cli_args *a = arg; + RAII_VAR(struct ast_sip_contact_status *, status, ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)), ao2_cleanup); + + ast_cli(a->fd, "\tContact %s:\n", contact->uri); + + if (!status) { + ast_cli(a->fd, "\tStatus not found!\n"); + return 0; + } + + ast_cli(a->fd, "\t\tavailable = %s\n", status->status ? "yes" : "no"); + + if (status->status) { + ast_cli(a->fd, "\t\tRTT = %lld microseconds\n", (long long)status->rtt); + } + + return 0; +} + +static void show_endpoint(struct ast_sip_endpoint *endpoint, struct ast_cli_args *a) +{ + char *aor_name, *aors; + + if (ast_strlen_zero(endpoint->aors)) { + return; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } + + ast_cli(a->fd, "AOR %s:\n", ast_sorcery_object_get_id(aor)); + ao2_callback(contacts, OBJ_NODATA, show_contact, a); + } + + return; +} + +static char *cli_show_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + const char *endpoint_name; + + switch (cmd) { + case CLI_INIT: + e->command = "sip show endpoint"; + e->usage = + "Usage: sip show endpoint <endpoint>\n" + " Show the given SIP endpoint.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + endpoint_name = a->argv[3]; + + if (!(endpoint = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), "endpoint", endpoint_name))) { + ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Endpoint %s:\n", endpoint_name); + show_endpoint(endpoint, a); + + return CLI_SUCCESS; +} + static struct ast_cli_entry cli_commands[] = { AST_CLI_DEFINE(handle_cli_show_endpoints, "Show SIP Endpoints"), + AST_CLI_DEFINE(cli_show_endpoint, "Show SIP Endpoint") }; static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) @@ -291,6 +376,22 @@ static int direct_media_method_handler(const struct aco_option *opt, struct ast_ return 0; } +static int connected_line_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp(var->value, "invite") || !strcasecmp(var->value, "reinvite")) { + endpoint->connected_line_method = AST_SIP_SESSION_REFRESH_METHOD_INVITE; + } else if (!strcasecmp(var->value, "update")) { + endpoint->connected_line_method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE; + } else { + ast_log(LOG_NOTICE, "Unrecognized option value %s for %s on endpoint %s\n", + var->value, var->name, ast_sorcery_object_get_id(endpoint)); + return -1; + } + return 0; +} + static int direct_media_glare_mitigation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct ast_sip_endpoint *endpoint = obj; @@ -353,6 +454,65 @@ static int caller_id_tag_handler(const struct aco_option *opt, struct ast_variab return endpoint->id.tag ? 0 : -1; } +static int media_encryption_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp("no", var->value)) { + endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_NONE; + } else if (!strcasecmp("sdes", var->value)) { + endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_SDES; + /*} else if (!strcasecmp("dtls", var->value)) { + endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_DTLS;*/ + } else { + return -1; + } + + return 0; +} + +static int group_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strncmp(var->name, "callgroup", 9)) { + if (!(endpoint->callgroup = ast_get_group(var->value))) { + return -1; + } + } else if (!strncmp(var->name, "pickupgroup", 11)) { + if (!(endpoint->pickupgroup = ast_get_group(var->value))) { + return -1; + } + } else { + return -1; + } + + return 0; +} + +static int named_groups_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strncmp(var->name, "namedcallgroup", 14)) { + if (!(endpoint->named_callgroups = + ast_get_namedgroups(var->value))) { + return -1; + } + } else if (!strncmp(var->name, "namedpickupgroup", 16)) { + if (!(endpoint->named_pickupgroups = + ast_get_namedgroups(var->value))) { + return -1; + } + } else { + return -1; + } + + return 0; +} + static void *sip_nat_hook_alloc(const char *name) { return ao2_alloc(sizeof(struct ast_sip_nat_hook), NULL); @@ -450,7 +610,6 @@ int ast_res_sip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, prefs, codecs)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, prefs, codecs)); - ast_sorcery_object_field_register(sip_sorcery, "endpoint", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, qualify_frequency), 0, 86400); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmfmode", "rfc4733", dtmf_handler, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_ipv6)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_symmetric)); @@ -472,6 +631,7 @@ int ast_res_sip_initialize_configuration(void) ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username,location", ident_handler, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, direct_media)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disable_direct_media_on_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, disable_direct_media_on_nat)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, NULL, 0, 0); @@ -481,8 +641,17 @@ int ast_res_sip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, trust_id_outbound)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_pai)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_rpid)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_diversion)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mailboxes)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, aggregate_mwi)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, use_avpf)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, one_touch_recording)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callgroup", "", group_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickupgroup", "", group_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedcallgroup", "", named_groups_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedpickupgroup", "", named_groups_handler, NULL, 0, 0); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "devicestate_busy_at", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, devicestate_busy_at)); if (ast_sip_initialize_sorcery_transport(sip_sorcery)) { @@ -499,6 +668,13 @@ int ast_res_sip_initialize_configuration(void) return -1; } + if (ast_sip_initialize_sorcery_qualify(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer); if (ast_sip_initialize_sorcery_domain_alias(sip_sorcery)) { @@ -539,6 +715,8 @@ static void endpoint_destructor(void* obj) destroy_auths(endpoint->sip_inbound_auths, endpoint->num_inbound_auths); destroy_auths(endpoint->sip_outbound_auths, endpoint->num_outbound_auths); ast_party_id_free(&endpoint->id); + endpoint->named_callgroups = ast_unref_namedgroups(endpoint->named_callgroups); + endpoint->named_pickupgroups = ast_unref_namedgroups(endpoint->named_pickupgroups); ao2_cleanup(endpoint->persistent); } @@ -596,4 +774,3 @@ struct ast_sorcery *ast_sip_get_sorcery(void) { return sip_sorcery; } - diff --git a/res/res_sip/sip_distributor.c b/res/res_sip/sip_distributor.c index 766261089..db36b6182 100644 --- a/res/res_sip/sip_distributor.c +++ b/res/res_sip/sip_distributor.c @@ -140,11 +140,21 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) } if (!endpoint && !is_ack) { + char name[AST_UUID_STR_LEN] = ""; + pjsip_uri *from = rdata->msg_info.from->uri; + /* XXX When we do an alwaysauthreject-like option, we'll need to take that into account * for this response. Either that, or have a pseudo-endpoint to pass along so that authentication * will fail */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + + if (PJSIP_URI_SCHEME_IS_SIP(from) || PJSIP_URI_SCHEME_IS_SIPS(from)) { + pjsip_sip_uri *sip_from = pjsip_uri_get_uri(from); + ast_copy_pj_str(name, &sip_from->user, sizeof(name)); + } + + ast_sip_report_invalid_endpoint(name, rdata); return PJ_TRUE; } rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint; @@ -164,16 +174,20 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata) switch (ast_sip_check_authentication(endpoint, rdata, tdata)) { case AST_SIP_AUTHENTICATION_CHALLENGE: /* Send the 401 we created for them */ + ast_sip_report_auth_challenge_sent(endpoint, rdata, tdata); pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); return PJ_TRUE; case AST_SIP_AUTHENTICATION_SUCCESS: + ast_sip_report_auth_success(endpoint, rdata); pjsip_tx_data_dec_ref(tdata); return PJ_FALSE; case AST_SIP_AUTHENTICATION_FAILED: + ast_sip_report_auth_failed_challenge_response(endpoint, rdata); pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); return PJ_TRUE; case AST_SIP_AUTHENTICATION_ERROR: + ast_sip_report_auth_failed_challenge_response(endpoint, rdata); pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); return PJ_TRUE; diff --git a/res/res_sip/sip_options.c b/res/res_sip/sip_options.c index 5e3f8edca..4c8a9f6a7 100644 --- a/res/res_sip/sip_options.c +++ b/res/res_sip/sip_options.c @@ -1,8 +1,19 @@ /* - * sip_options.c + * Asterisk -- An open source telephony toolkit. * - * Created on: Jan 25, 2013 - * Author: mjordan + * Copyright (C) 2013, Digium, Inc. + * + * Matt Jordan <mjordan@digium.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. */ #include "asterisk.h" @@ -16,41 +27,429 @@ #include "asterisk/pbx.h" #include "asterisk/astobj2.h" #include "asterisk/cli.h" +#include "asterisk/time.h" #include "include/res_sip_private.h" #define DEFAULT_LANGUAGE "en" #define DEFAULT_ENCODING "text/plain" #define QUALIFIED_BUCKETS 211 -/*! \brief Scheduling context for qualifies */ -static struct ast_sched_context *sched; /* XXX move this to registrar */ +static int qualify_contact(struct ast_sip_contact *contact); + +/*! + * \internal + * \brief Create a ast_sip_contact_status object. + */ +static void *contact_status_alloc(const char *name) +{ + struct ast_sip_contact_status *status = ao2_alloc_options( + sizeof(*status), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + + if (!status) { + ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n"); + return NULL; + } + + status->status = UNAVAILABLE; + + return status; +} + +/*! + * \internal + * \brief Retrieve a ast_sip_contact_status object from sorcery creating + * one if not found. + */ +static struct ast_sip_contact_status *find_or_create_contact_status(const struct ast_sip_contact *contact) +{ + struct ast_sip_contact_status *status = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)); + + if (status) { + return status; + } + + if (!(status = ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)))) { + + ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s\n", + contact->uri); + return NULL; + } + + if (ast_sorcery_create(ast_sip_get_sorcery(), status)) { + ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n", + contact->uri); + return NULL; + } + + return status; +} + +/*! + * \internal + * \brief Update an ast_sip_contact_status's elements. + */ +static void update_contact_status(const struct ast_sip_contact *contact, + enum ast_sip_contact_status_type value) +{ + RAII_VAR(struct ast_sip_contact_status *, status, + find_or_create_contact_status(contact), ao2_cleanup); + + RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(status)), ao2_cleanup); + + if (!update) { + ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n", + contact->uri); + return; + } + + update->status = value; + + /* if the contact is available calculate the rtt as + the diff between the last start time and "now" */ + update->rtt = update->status ? + ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0; + + update->rtt_start = ast_tv(0, 0); + + if (ast_sorcery_update(ast_sip_get_sorcery(), update)) { + ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +/*! + * \internal + * \brief Initialize the start time on a contact status so the round + * trip time can be calculated upon a valid response. + */ +static void init_start_time(const struct ast_sip_contact *contact) +{ + RAII_VAR(struct ast_sip_contact_status *, status, + find_or_create_contact_status(contact), ao2_cleanup); + + RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(status)), ao2_cleanup); + + if (!update) { + ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n", + contact->uri); + return; + } + + update->rtt_start = ast_tvnow(); + + if (ast_sorcery_update(ast_sip_get_sorcery(), update)) { + ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +/*! + * \internal + * \brief For an endpoint try to match on a given contact. + */ +static int on_endpoint(void *obj, void *arg, int flags) +{ + struct ast_sip_endpoint *endpoint = obj; + char *aor_name, *aors; + + if (!arg || ast_strlen_zero(endpoint->aors)) { + return 0; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } + + if (ao2_find(contacts, arg, OBJ_NODATA | OBJ_POINTER)) { + return CMP_MATCH; + } + } + + return 0; +} + +/*! + * \internal + * \brief Find endpoints associated with the given contact. + */ +static struct ao2_container *find_endpoints(struct ast_sip_contact *contact) +{ + RAII_VAR(struct ao2_container *, endpoints, + ast_res_sip_get_endpoints(), ao2_cleanup); + + return ao2_callback(endpoints, OBJ_MULTIPLE, on_endpoint, contact); +} + +/*! + * \internal + * \brief Receive an response to the qualify contact request. + */ +static void qualify_contact_cb(void *token, pjsip_event *e) +{ + RAII_VAR(struct ast_sip_contact *, contact, token, ao2_cleanup); + RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_rx_data *challenge = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + + switch(e->body.tsx_state.type) { + case PJSIP_EVENT_TRANSPORT_ERROR: + case PJSIP_EVENT_TIMER: + update_contact_status(contact, UNAVAILABLE); + return; + default: + break; + } + + if (!contact->authenticate_qualify || (tsx->status_code != 401 && + tsx->status_code != 407)) { + update_contact_status(contact, AVAILABLE); + return; + } + + /* try to find endpoints that are associated with the contact */ + if (!(endpoints = find_endpoints(contact))) { + ast_log(LOG_ERROR, "No endpoints found for contact %s, cannot authenticate", + contact->uri); + return; + } + + /* find "first" endpoint in order to authenticate - actually any + endpoint should do that matched on the contact */ + endpoint = ao2_callback(endpoints, 0, NULL, NULL); + + if (!ast_sip_create_request_with_auth(endpoint->sip_outbound_auths, + endpoint->num_outbound_auths, + challenge, tsx, &tdata)) { + pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, + -1, NULL, NULL); + } +} + +/*! + * \internal + * \brief Attempt to qualify the contact + * + * \detail Sends a SIP OPTIONS request to the given contact in order to make + * sure that contact is available. + */ +static int qualify_contact(struct ast_sip_contact *contact) +{ + pjsip_tx_data *tdata; + + if (ast_sip_create_request("OPTIONS", NULL, NULL, contact->uri, &tdata)) { + ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n", + contact->uri); + return -1; + } + + init_start_time(contact); + + ao2_ref(contact, +1); + if (pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), + tdata, -1, contact, qualify_contact_cb) != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n", + contact->uri); + ao2_ref(contact, -1); + return -1; + } -struct ao2_container *scheduled_qualifies; + return 0; +} + +/*! + * \internal + * \brief Scheduling context for sending QUALIFY request at specified intervals. + */ +static struct ast_sched_context *sched; -struct qualify_info { - AST_DECLARE_STRING_FIELDS( - AST_STRING_FIELD(endpoint_id); - ); - char *scheduler_data; - int scheduler_id; +/*! + * \internal + * \brief Container to hold all actively scheduled qualifies. + */ +static struct ao2_container *sched_qualifies; + +/*! + * \internal + * \brief Structure to hold qualify contact scheduling information. + */ +struct sched_data { + /*! The scheduling id */ + int id; + /*! The the contact being checked */ + struct ast_sip_contact *contact; }; -static pj_bool_t options_module_start(void); -static pj_bool_t options_module_stop(void); -static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata); -static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata); +/*! + * \internal + * \brief Destroy the scheduled data and remove from scheduler. + */ +static void sched_data_destructor(void *obj) +{ + struct sched_data *data = obj; + ao2_cleanup(data->contact); +} +/*! + * \internal + * \brief Create the scheduling data object. + */ +static struct sched_data *sched_data_create(struct ast_sip_contact *contact) +{ + struct sched_data *data = ao2_alloc(sizeof(*data), sched_data_destructor); -static pjsip_module options_module = { - .name = {"Options Module", 14}, - .id = -1, - .priority = PJSIP_MOD_PRIORITY_APPLICATION, - .start = options_module_start, - .stop = options_module_stop, - .on_rx_request = options_module_on_rx_request, - .on_rx_response = options_module_on_rx_response, + if (!data) { + ast_log(LOG_ERROR, "Unable to create schedule qualify data\n"); + return NULL; + } + + data->contact = contact; + ao2_ref(data->contact, +1); + + return data; +} + +/*! + * \internal + * \brief Send a qualify contact request within a threaded task. + */ +static int qualify_contact_task(void *obj) +{ + RAII_VAR(struct ast_sip_contact *, contact, obj, ao2_cleanup); + return qualify_contact(contact); +} + +/*! + * \internal + * \brief Send a scheduled qualify contact request. + */ +static int qualify_contact_sched(const void *obj) +{ + struct sched_data *data = (struct sched_data *)obj; + + ao2_ref(data->contact, +1); + if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) { + ao2_ref(data->contact, -1); + ao2_cleanup(data); + return 0; + } + + return data->contact->qualify_frequency * 1000; +} + +/*! + * \internal + * \brief Set up a scheduled qualify contact check. + */ +static void schedule_qualify(struct ast_sip_contact *contact) +{ + RAII_VAR(struct sched_data *, data, sched_data_create(contact), ao2_cleanup); + + if (!data) { + return; + } + + ao2_ref(data, +1); + if ((data->id = ast_sched_add_variable( + sched, contact->qualify_frequency * 1000, + qualify_contact_sched, data, 1)) < 0) { + + ao2_ref(data, -1); + ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n", + contact->uri); + return; + } + + ao2_link(sched_qualifies, data); +} + +/*! + * \internal + * \brief Remove the contact from the scheduler. + */ +static void unschedule_qualify(struct ast_sip_contact *contact) +{ + RAII_VAR(struct sched_data *, data, ao2_find( + sched_qualifies, contact, OBJ_UNLINK), ao2_cleanup); + + if (!data) { + return; + } + + AST_SCHED_DEL_UNREF(sched, data->id, ao2_cleanup(data)); +} + +/*! + * \internal + * \brief Qualify the given contact and set up scheduling if configured. + */ +static void qualify_and_schedule(struct ast_sip_contact *contact) +{ + unschedule_qualify(contact); + + if (contact->qualify_frequency) { + ao2_ref(contact, +1); + ast_sip_push_task(NULL, qualify_contact_task, contact); + + schedule_qualify(contact); + } +} + +/*! + * \internal + * \brief A new contact has been created make sure it is available. + */ +static void contact_created(const void *obj) +{ + qualify_and_schedule((struct ast_sip_contact *)obj); +} + +/*! + * \internal + * \brief A contact has been deleted remove status tracking. + */ +static void contact_deleted(const void *obj) +{ + struct ast_sip_contact *contact = (struct ast_sip_contact *)obj; + RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup); + + unschedule_qualify(contact); + + if (!(status = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)))) { + return; + } + + if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) { + ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +struct ast_sorcery_observer contact_observer = { + .created = contact_created, + .deleted = contact_deleted }; -static pj_bool_t options_module_start(void) +static pj_bool_t options_start(void) { if (!(sched = ast_sched_context_create()) || ast_sched_start_thread(sched)) { @@ -60,9 +459,11 @@ static pj_bool_t options_module_start(void) return PJ_SUCCESS; } -static pj_bool_t options_module_stop(void) +static pj_bool_t options_stop(void) { - ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop"); + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer); + + ao2_t_ref(sched_qualifies, -1, "Remove scheduled qualifies on module stop"); if (sched) { ast_sched_context_destroy(sched); @@ -71,18 +472,20 @@ static pj_bool_t options_module_stop(void) return PJ_SUCCESS; } -static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code) +static pj_status_t send_options_response(pjsip_rx_data *rdata, int code) { pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); - pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata); + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata); pjsip_tx_data *tdata; const pjsip_hdr *hdr; pjsip_response_addr res_addr; pj_status_t status; /* Make the response object */ - status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata); - if (status != PJ_SUCCESS) { + if ((status = pjsip_endpt_create_response( + endpt, rdata, code, NULL, &tdata) != PJ_SUCCESS)) { + ast_log(LOG_ERROR, "Unable to create response (%d)\n", status); return status; } @@ -106,262 +509,267 @@ static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_ ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING); ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE); - if (pj_dlg && pj_trans) { - status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata); + if (dlg && trans) { + status = pjsip_dlg_send_response(dlg, trans, tdata); } else { /* Get where to send request. */ - status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); - if (status != PJ_SUCCESS) { + if ((status = pjsip_get_response_addr( + tdata->pool, rdata, &res_addr)) != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to get response address (%d)\n", + status); + pjsip_tx_data_dec_ref(tdata); return status; } - status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL); + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + } + + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to send response (%d)\n", status); } return status; } -static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata) +static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) { RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); - pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); pjsip_uri *ruri; pjsip_sip_uri *sip_ruri; char exten[AST_MAX_EXTENSION]; - if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) { + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_options_method)) { + return PJ_FALSE; + } + + if (!(endpoint = ast_pjsip_rdata_get_endpoint(rdata))) { return PJ_FALSE; } - endpoint = ast_pjsip_rdata_get_endpoint(rdata); - ast_assert(endpoint != NULL); ruri = rdata->msg_info.msg->line.req.uri; if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) { - send_options_response(rdata, dlg, 416); + send_options_response(rdata, 416); return -1; } - + sip_ruri = pjsip_uri_get_uri(ruri); ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten)); if (ast_shutting_down()) { - send_options_response(rdata, dlg, 503); + send_options_response(rdata, 503); } else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) { - send_options_response(rdata, dlg, 404); + send_options_response(rdata, 404); } else { - send_options_response(rdata, dlg, 200); + send_options_response(rdata, 200); } return PJ_TRUE; } -static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata) -{ +static pjsip_module options_module = { + .name = {"Options Module", 14}, + .id = -1, + .priority = PJSIP_MOD_PRIORITY_APPLICATION, + .start = options_start, + .stop = options_stop, + .on_rx_request = options_on_rx_request, +}; - return PJ_SUCCESS; +/*! + * \internal + * \brief Send qualify request to the given contact. + */ +static int cli_on_contact(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ast_cli_args *a = arg; + ast_cli(a->fd, " contact %s\n", contact->uri); + qualify_contact(contact); + return 0; } -static int qualify_info_hash_fn(const void *obj, int flags) +/*! + * \internal + * \brief For an endpoint iterate over and qualify all aors/contacts + */ +static void cli_qualify_contacts(struct ast_cli_args *a, const char *endpoint_name, + struct ast_sip_endpoint *endpoint) { - const struct qualify_info *info = obj; - const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id; + char *aor_name, *aors; - return ast_str_hash(endpoint_id); -} + if (ast_strlen_zero(endpoint->aors)) { + ast_cli(a->fd, "Endpoint %s has no AoR's configured\n", + endpoint_name); + return; + } -static int qualify_info_cmp_fn(void *obj, void *arg, int flags) -{ - struct qualify_info *left = obj; - struct qualify_info *right = arg; - const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id; + aors = ast_strdupa(endpoint->aors); - return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP; -} + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } -static void qualify_info_destructor(void *obj) -{ - struct qualify_info *info = obj; - if (!info) { - return; - } - ast_string_field_free_memory(info); - /* Cancel the qualify */ - if (!AST_SCHED_DEL(sched, info->scheduler_id)) { - /* If we successfully deleted the qualify, we got it before it - * fired. We can safely delete the data that was passed to it. - * Otherwise, we're getting deleted while this is firing - don't - * touch that memory! - */ - ast_free(info->scheduler_data); + ast_cli(a->fd, "Sending qualify to endpoint %s", endpoint_name); + ao2_callback(contacts, OBJ_NODATA, cli_on_contact, a); } } -static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint) +static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct qualify_info *info; + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + const char *endpoint_name; - info = ao2_alloc(sizeof(*info), qualify_info_destructor); - if (!info) { + switch (cmd) { + case CLI_INIT: + e->command = "sip qualify"; + e->usage = + "Usage: sip qualify <endpoint>\n" + " Send a SIP OPTIONS request to all contacts on the endpoint.\n"; + return NULL; + case CLI_GENERATE: return NULL; } - if (ast_string_field_init(info, 64)) { - ao2_ref(info, -1); - return NULL; + if (a->argc != 3) { + return CLI_SHOWUSAGE; } - ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint)); - return info; + endpoint_name = a->argv[2]; + + if (!(endpoint = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), "endpoint", endpoint_name))) { + ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name); + return CLI_FAILURE; + } + + /* send a qualify for all contacts registered with the endpoint */ + cli_qualify_contacts(a, endpoint_name, endpoint); + + return CLI_SUCCESS; } -static int send_qualify_request(void *data) +static struct ast_cli_entry cli_options[] = { + AST_CLI_DEFINE(cli_qualify, "Send an OPTIONS request to a SIP endpoint") +}; + +static int sched_qualifies_hash_fn(const void *obj, int flags) { - struct ast_sip_endpoint *endpoint = data; - pjsip_tx_data *tdata; - /* YAY! Send an OPTIONS request. */ + const struct sched_data *data = obj; - ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata); - ast_sip_send_request(tdata, NULL, endpoint); + return ast_str_hash(ast_sorcery_object_get_id(data->contact)); +} - ao2_cleanup(endpoint); - return 0; +static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags) +{ + struct sched_data *data = obj; + + return !strcmp(ast_sorcery_object_get_id(data->contact), + ast_sorcery_object_get_id(arg)); } -static int qualify_endpoint_scheduler_cb(const void *data) +int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery) { - RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); - struct ast_sorcery *sorcery; - char *endpoint_id = (char *)data; + /* initialize sorcery ast_sip_contact_status resource */ + ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL); - sorcery = ast_sip_get_sorcery(); - if (!sorcery) { - ast_free(endpoint_id); - return 0; + if (ast_sorcery_object_register(sorcery, CONTACT_STATUS, + contact_status_alloc, NULL, NULL)) { + ast_log(LOG_ERROR, "Unable to register ast_sip_contact_status in sorcery\n"); + return -1; } - endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id); - if (!endpoint) { - /* Whoops, endpoint went away */ - ast_free(endpoint_id); - return 0; - } + ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T, + 1, FLDSET(struct ast_sip_contact_status, status)); + ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T, + 1, FLDSET(struct ast_sip_contact_status, rtt)); - ast_sip_push_task(NULL, send_qualify_request, endpoint); + if (ast_sorcery_observer_add(sorcery, "contact", &contact_observer)) { + ast_log(LOG_WARNING, "Unable to add contact observer\n"); + return -1; + } - return 1; + return 0; } -static void schedule_qualifies(void) +static int qualify_and_schedule_cb(void *obj, void *arg, int flags) { - RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup); - struct ao2_iterator it_endpoints; - struct ast_sip_endpoint *endpoint; - struct qualify_info *info; - char *endpoint_id; + struct ast_sip_contact *contact = obj; + struct ast_sip_aor *aor = arg; - endpoints = ast_res_sip_get_endpoints(); - if (!endpoints) { - return; - } + contact->qualify_frequency = aor->qualify_frequency; + qualify_and_schedule(contact); - it_endpoints = ao2_iterator_init(endpoints, 0); - while ((endpoint = ao2_iterator_next(&it_endpoints))) { - if (endpoint->qualify_frequency) { - /* XXX TODO: This really should only qualify registered peers, - * which means we need a registrar. We should check the - * registrar to see if this endpoint has registered and, if - * not, pass on it. - * - * Actually, all of this should just get moved into the registrar. - * Otherwise, the registar will have to kick this off when a - * new endpoint registers, so it just makes sense to have it - * all live there. - */ - info = create_qualify_info(endpoint); - if (!info) { - ao2_ref(endpoint, -1); - break; - } - endpoint_id = ast_strdup(info->endpoint_id); - if (!endpoint_id) { - ao2_t_ref(info, -1, "Dispose of info on off nominal"); - ao2_ref(endpoint, -1); - break; - } - info->scheduler_data = endpoint_id; - info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1); - ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container"); - ao2_t_ref(info, -1, "Dispose of creation ref"); - } - ao2_t_ref(endpoint, -1, "Dispose of iterator ref"); - } - ao2_iterator_destroy(&it_endpoints); + return 0; } -static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +/*! + * \internal + * \brief Qualify and schedule an endpoint's permanent contacts + * + * \detail For the given endpoint retrieve its list of aors, qualify all + * permanent contacts, and schedule for checks if configured. + */ +static int qualify_and_schedule_permanent_cb(void *obj, void *arg, int flags) { - RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); - const char *endpoint_name; - pjsip_tx_data *tdata; + struct ast_sip_endpoint *endpoint = obj; + char *aor_name, *aors; - switch (cmd) { - case CLI_INIT: - e->command = "sip send options"; - e->usage = - "Usage: sip send options <endpoint>\n" - " Send a SIP OPTIONS request to the specified endpoint.\n"; - return NULL; - case CLI_GENERATE: - return NULL; + if (ast_strlen_zero(endpoint->aors)) { + return 0; } - if (a->argc != 4) { - return CLI_SHOWUSAGE; - } + aors = ast_strdupa(endpoint->aors); - endpoint_name = a->argv[3]; + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); - endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name); - if (!endpoint) { - ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name); - return CLI_FAILURE; + if (!aor || !aor->permanent_contacts) { + continue; + } + ao2_callback(aor->permanent_contacts, OBJ_NODATA, qualify_and_schedule_cb, aor); } - if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) { - ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name); - return CLI_FAILURE; - } + return 0; +} - if (ast_sip_send_request(tdata, NULL, endpoint)) { - ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name); - return CLI_FAILURE; - } +static void qualify_and_schedule_permanent(void) +{ + RAII_VAR(struct ao2_container *, endpoints, + ast_res_sip_get_endpoints(), ao2_cleanup); - return CLI_SUCCESS; + ao2_callback(endpoints, OBJ_NODATA, + qualify_and_schedule_permanent_cb, NULL); } -static struct ast_cli_entry cli_options[] = { - AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"), -}; - int ast_res_sip_init_options_handling(int reload) { const pj_str_t STR_OPTIONS = { "OPTIONS", 7 }; - if (scheduled_qualifies) { - ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies"); + if (sched_qualifies) { + ao2_t_ref(sched_qualifies, -1, "Remove old scheduled qualifies"); } - scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies"); - if (!scheduled_qualifies) { + + if (!(sched_qualifies = ao2_t_container_alloc( + QUALIFIED_BUCKETS, sched_qualifies_hash_fn, sched_qualifies_cmp_fn, + "Create container for scheduled qualifies"))) { + return -1; } if (reload) { + qualify_and_schedule_permanent(); return 0; } if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) { - options_module_stop(); + options_stop(); return -1; } @@ -370,9 +778,8 @@ int ast_res_sip_init_options_handling(int reload) return -1; } + qualify_and_schedule_permanent(); ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options)); - schedule_qualifies(); - return 0; } diff --git a/res/res_sip_caller_id.c b/res/res_sip_caller_id.c index 22ece0436..2f4047351 100644 --- a/res/res_sip_caller_id.c +++ b/res/res_sip_caller_id.c @@ -688,7 +688,7 @@ static void caller_id_outgoing_response(struct ast_sip_session *session, pjsip_t } static struct ast_sip_session_supplement caller_id_supplement = { - .method = "INVITE", + .method = "INVITE,UPDATE", .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL - 1000, .incoming_request = caller_id_incoming_request, .incoming_response = caller_id_incoming_response, diff --git a/res/res_sip_diversion.c b/res/res_sip_diversion.c new file mode 100644 index 000000000..70b1fc508 --- /dev/null +++ b/res/res_sip_diversion.c @@ -0,0 +1,346 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_session</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_ua.h> + +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_session.h" +#include "asterisk/callerid.h" +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/strings.h" + +static const pj_str_t diversion_name = { "Diversion", 9 }; + +/*! \brief Diversion header reasons + * + * The core defines a bunch of constants used to define + * redirecting reasons. This provides a translation table + * between those and the strings which may be present in + * a SIP Diversion header + */ +static const struct reasons { + enum AST_REDIRECTING_REASON code; + char *const text; +} reason_table[] = { + { AST_REDIRECTING_REASON_UNKNOWN, "unknown" }, + { AST_REDIRECTING_REASON_USER_BUSY, "user-busy" }, + { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer" }, + { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable" }, + { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional" }, + { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day" }, + { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" }, + { AST_REDIRECTING_REASON_DEFLECTION, "deflection" }, + { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" }, + { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" }, + { AST_REDIRECTING_REASON_AWAY, "away" }, + { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"}, + { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"}, +}; + +static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason) +{ + int code = reason->code; + + /* use specific string if given */ + if (!ast_strlen_zero(reason->str)) { + return reason->str; + } + + if (code >= 0 && code < ARRAY_LEN(reason_table)) { + return reason_table[code].text; + } + + return "unknown"; +} + +static enum AST_REDIRECTING_REASON reason_str_to_code(const char *text) +{ + enum AST_REDIRECTING_REASON code = AST_REDIRECTING_REASON_UNKNOWN; + int i; + + for (i = 0; i < ARRAY_LEN(reason_table); ++i) { + if (!strcasecmp(text, reason_table[i].text)) { + code = reason_table[i].code; + break; + } + } + + return code; +} + +static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata) +{ + static const pj_str_t from_name = { "From", 4 }; + + pjsip_generic_string_hdr *hdr; + pj_str_t value; + int size; + + if (!(hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &diversion_name, NULL))) { + return NULL; + } + + pj_strdup_with_null(rdata->tp_info.pool, &value, &hdr->hvalue); + + /* parse as a fromto header */ + return pjsip_parse_hdr(rdata->tp_info.pool, &from_name, value.ptr, + pj_strlen(&value), &size); +} + +static void set_redirecting_value(char **dst, const pj_str_t *src) +{ + ast_free(*dst); + *dst = ast_malloc(pj_strlen(src) + 1); + ast_copy_pj_str(*dst, src, pj_strlen(src) + 1); +} + +static void set_redirecting_id(pjsip_name_addr *name_addr, struct ast_party_id *data, + struct ast_set_party_id *update) +{ + pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr->uri); + + if (pj_strlen(&uri->user)) { + update->number = 1; + data->number.valid = 1; + set_redirecting_value(&data->number.str, &uri->user); + } + + if (pj_strlen(&name_addr->display)) { + update->name = 1; + data->name.valid = 1; + set_redirecting_value(&data->name.str, &name_addr->display); + } +} + +static void copy_redirecting_id(struct ast_party_id *dst, const struct ast_party_id *src, + struct ast_set_party_id *update) +{ + ast_party_id_copy(dst, src); + + if (dst->number.valid) { + update->number = 1; + } + + if (dst->name.valid) { + update->name = 1; + } +} + +static void set_redirecting_reason(pjsip_fromto_hdr *hdr, + struct ast_party_redirecting_reason *data) +{ + static const pj_str_t reason_name = { "reason", 6 }; + pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name); + + if (!reason) { + return; + } + + set_redirecting_value(&data->str, &reason->value); + data->code = reason_str_to_code(data->str); +} + +static void set_redirecting(struct ast_sip_session *session, + pjsip_fromto_hdr *from_info, + pjsip_name_addr *to_info) +{ + struct ast_party_redirecting data; + struct ast_set_party_redirecting update; + + if (!session->channel) { + return; + } + + ast_party_redirecting_init(&data); + memset(&update, 0, sizeof(update)); + + if (from_info) { + set_redirecting_id((pjsip_name_addr*)from_info->uri, + &data.from, &update.from); + set_redirecting_reason(from_info, &data.reason); + } else { + copy_redirecting_id(&data.from, &session->id, &update.from); + } + + set_redirecting_id(to_info, &data.to, &update.to); + + ast_set_party_id_all(&update.priv_orig); + ast_set_party_id_all(&update.priv_from); + ast_set_party_id_all(&update.priv_to); + ++data.count; + + ast_channel_set_redirecting(session->channel, &data, &update); + ast_party_redirecting_free(&data); +} + +static int diversion_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + pjsip_fromto_hdr *hdr = get_diversion_header(rdata); + + if (hdr) { + set_redirecting(session, hdr, (pjsip_name_addr*) + PJSIP_MSG_TO_HDR(rdata->msg_info.msg)->uri); + } + + return 0; +} + +static void diversion_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + static const pj_str_t contact_name = { "Contact", 7 }; + + pjsip_status_line status = rdata->msg_info.msg->line.status; + pjsip_fromto_hdr *div_hdr; + pjsip_contact_hdr *contact_hdr; + + if ((status.code != 302) && (status.code != 181)) { + return; + } + + /* use the diversion header info if there is one. if not one then use the + session caller id info. if that doesn't exist use info from the To hdr*/ + if (!(div_hdr = get_diversion_header(rdata)) && !session->id.number.valid) { + div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg); + } + + contact_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &contact_name, NULL); + + set_redirecting(session, div_hdr, contact_hdr ? (pjsip_name_addr*)contact_hdr->uri : + (pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri); +} + +/*! + * \internal + * \brief Adds diversion header information to an outbound SIP message + * + * \param tdata The outbound message + * \param data The redirecting data used to fill parts of the diversion header + */ +static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data) +{ + pjsip_fromto_hdr *hdr; + pjsip_name_addr *name_addr; + pjsip_sip_uri *uri; + pjsip_param *param; + + struct ast_party_id *id = &data->from; + pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri; + + if (!id->number.valid || ast_strlen_zero(id->number.str)) { + return; + } + + hdr = pjsip_from_hdr_create(tdata->pool); + hdr->type = PJSIP_H_OTHER; + pj_strdup(tdata->pool, &hdr->name, &diversion_name); + hdr->sname.slen = 0; + + name_addr = pjsip_uri_clone(tdata->pool, base); + uri = pjsip_uri_get_uri(name_addr->uri); + + pj_strdup2(tdata->pool, &name_addr->display, id->name.str); + pj_strdup2(tdata->pool, &uri->user, id->number.str); + + param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); + param->name = pj_str("reason"); + param->value = pj_str((char*)reason_code_to_str(&data->reason)); + pj_list_insert_before(&hdr->other_param, param); + + hdr->uri = (pjsip_uri *) name_addr; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); +} + +static void get_redirecting_add_diversion(struct ast_sip_session *session, pjsip_tx_data *tdata) +{ + struct ast_party_redirecting *data; + + if (session->channel && session->endpoint->send_diversion && + (data = ast_channel_redirecting(session->channel))->count) { + add_diversion_header(tdata, data); + } +} + +/*! + * \internal + * \brief Adds a diversion header to an outgoing INVITE request if + * redirecting information is available. + * + * \param session The session on which the INVITE request is to be sent + * \param tdata The outbound INVITE request + */ +static void diversion_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata) +{ + get_redirecting_add_diversion(session, tdata); +} + +/*! + * \internal + * \brief Adds a diversion header to an outgoing 3XX response + * + * \param session The session on which the INVITE response is to be sent + * \param tdata The outbound INVITE response + */ +static void diversion_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata) +{ + struct pjsip_status_line status = tdata->msg->line.status; + + /* add to 302 and 181 */ + if (PJSIP_IS_STATUS_IN_CLASS(status.code, 300) || (status.code == 181)) { + get_redirecting_add_diversion(session, tdata); + } +} + +static struct ast_sip_session_supplement diversion_supplement = { + .method = "INVITE", + /* this supplement needs to be called after caller id + and after the channel has been created */ + .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL + 100, + .incoming_request = diversion_incoming_request, + .incoming_response = diversion_incoming_response, + .outgoing_request = diversion_outgoing_request, + .outgoing_response = diversion_outgoing_response, +}; + +static int load_module(void) +{ + ast_sip_session_register_supplement(&diversion_supplement); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_session_unregister_supplement(&diversion_supplement); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Add Diversion Header Support", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_sip_dtmf_info.c b/res/res_sip_dtmf_info.c index c8b03d509..1954c695e 100644 --- a/res/res_sip_dtmf_info.c +++ b/res/res_sip_dtmf_info.c @@ -46,8 +46,7 @@ static int dtmf_info_incoming_request(struct ast_sip_session *session, struct pj char event = '\0'; unsigned int duration = 0; - if (pj_strcmp2(&body->content_type.type, "application") || - pj_strcmp2(&body->content_type.subtype, "dtmf-relay")) { + if (!ast_sip_is_content_type(&body->content_type, "application", "dtmf-relay")) { return 0; } diff --git a/res/res_sip_endpoint_identifier_anonymous.c b/res/res_sip_endpoint_identifier_anonymous.c new file mode 100644 index 000000000..6f947e1a1 --- /dev/null +++ b/res/res_sip_endpoint_identifier_anonymous.c @@ -0,0 +1,125 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.com> + * Joshua Colp <jcolp@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> + +#include "asterisk/res_sip.h" +#include "asterisk/module.h" + +static int get_endpoint_details(pjsip_rx_data *rdata, char *domain, size_t domain_size) +{ + pjsip_uri *from = rdata->msg_info.from->uri; + pjsip_sip_uri *sip_from; + if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) { + return -1; + } + sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from); + ast_copy_pj_str(domain, &sip_from->host, domain_size); + return 0; +} + +static int find_transport_in_use(void *obj, void *arg, int flags) +{ + struct ast_sip_transport *transport = obj; + pjsip_rx_data *rdata = arg; + + if ((transport->state->transport == rdata->tp_info.transport) || + (transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) && + transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata) +{ + char domain_name[64], id[AST_UUID_STR_LEN]; + struct ast_sip_endpoint *endpoint; + RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); + + if (get_endpoint_details(rdata, domain_name, sizeof(domain_name))) { + return NULL; + } + + /* Attempt to find the endpoint given the name and domain provided */ + snprintf(id, sizeof(id), "anonymous@%s", domain_name); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + goto done; + } + + /* See if an alias exists for the domain provided */ + if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { + snprintf(id, sizeof(id), "anonymous@%s", alias->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + goto done; + } + } + + /* See if the transport this came in on has a provided domain */ + if ((transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) && + (transport = ao2_callback(transports, 0, find_transport_in_use, rdata)) && + !ast_strlen_zero(transport->domain)) { + snprintf(id, sizeof(id), "anonymous@%s", transport->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + goto done; + } + } + + /* Fall back to no domain */ + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", "anonymous"); + +done: + if (endpoint) { + ast_debug(3, "Retrieved anonymous endpoint '%s'\n", ast_sorcery_object_get_id(endpoint)); + } + return endpoint; +} + +static struct ast_sip_endpoint_identifier anonymous_identifier = { + .identify_endpoint = anonymous_identify, +}; + +static int load_module(void) +{ + ast_sip_register_endpoint_identifier(&anonymous_identifier); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_unregister_endpoint_identifier(&anonymous_identifier); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Anonymous endpoint identifier", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_DEFAULT, + ); diff --git a/res/res_sip_exten_state.c b/res/res_sip_exten_state.c new file mode 100644 index 000000000..069343439 --- /dev/null +++ b/res/res_sip_exten_state.c @@ -0,0 +1,620 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_pubsub</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_simple.h> +#include <pjlib.h> + +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_pubsub.h" +#include "asterisk/res_sip_exten_state.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/astobj2.h" +#include "asterisk/sorcery.h" +#include "asterisk/app.h" + +#define BODY_SIZE 1024 +#define EVENT_TYPE_SIZE 50 + +AST_RWLIST_HEAD_STATIC(providers, ast_sip_exten_state_provider); + +/*! + * \internal + * \brief Find a provider based on the given accept body type. + */ +static struct ast_sip_exten_state_provider *provider_by_type(const char *type) +{ + struct ast_sip_exten_state_provider *i; + SCOPED_LOCK(lock, &providers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) { + if (!strcmp(i->body_type, type)) { + return i; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + return NULL; +} + +/*! + * \internal + * \brief Find a provider based on the given accept body types. + */ +static struct ast_sip_exten_state_provider *provider_by_types(const char *event_name, + char **types, int count) +{ + int i; + struct ast_sip_exten_state_provider *res; + for (i = 0; i < count; ++i) { + if ((res = provider_by_type(types[i])) && + !strcmp(event_name, res->event_name)) { + return res; + } + } + return NULL; +} + +/*! + * \brief A subscription for extension state + * + * This structure acts as the owner for the underlying SIP subscription. It + * also keeps a pointer to an associated "provider" so when a state changes + * a notify data creator is quickly accessible. + */ +struct exten_state_subscription { + /*! Watcher id when registering for extension state changes */ + int id; + /*! The SIP subscription */ + struct ast_sip_subscription *sip_sub; + /*! The name of the event the subscribed to */ + char event_name[EVENT_TYPE_SIZE]; + /*! The number of body types */ + int body_types_count; + /*! The subscription body types */ + char **body_types; + /*! Context in which subscription looks for updates */ + char context[AST_MAX_CONTEXT]; + /*! Extension within the context to receive updates from */ + char exten[AST_MAX_EXTENSION]; + /*! The last known extension state */ + enum ast_extension_states last_exten_state; +}; + +static void exten_state_subscription_destructor(void *obj) +{ + struct exten_state_subscription *sub = obj; + int i; + + for (i = 0; i < sub->body_types_count; ++i) { + ast_free(sub->body_types[i]); + } + + ast_free(sub->body_types); + ao2_cleanup(sub->sip_sub); +} + +/*! + * \internal + * \brief Copies the body types the message wishes to subscribe to. + */ +static void copy_body_types(pjsip_rx_data *rdata, + struct exten_state_subscription *exten_state_sub) +{ + int i; + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + + exten_state_sub->body_types_count = hdr->count; + exten_state_sub->body_types = ast_malloc(hdr->count * sizeof(char*)); + + for (i = 0; i < hdr->count; ++i) { + exten_state_sub->body_types[i] = + ast_malloc(hdr->values[i].slen * sizeof(char*) + 1); + + ast_copy_string(exten_state_sub->body_types[i], + pj_strbuf(&hdr->values[i]), hdr->values[i].slen + 1); + } +} + +/*! + * \internal + * \brief Initialize the last extension state to something outside + * its usual states. + */ +#define INITIAL_LAST_EXTEN_STATE -3 + +/*! + * \internal + * \brief Allocates an exten_state_subscription object. + * + * Creates the underlying SIP subscription for the given request. First makes + * sure that there are registered handler and provider objects available. + */ +static struct exten_state_subscription *exten_state_subscription_alloc( + struct ast_sip_endpoint *endpoint, enum ast_sip_subscription_role role, pjsip_rx_data *rdata) +{ + static const pj_str_t event_name = { "Event", 5 }; + pjsip_event_hdr *hdr = (pjsip_event_hdr*)pjsip_msg_find_hdr_by_name( + rdata->msg_info.msg, &event_name, NULL); + + struct ast_sip_exten_state_provider *provider; + RAII_VAR(struct exten_state_subscription *, exten_state_sub, + ao2_alloc(sizeof(*exten_state_sub), exten_state_subscription_destructor), ao2_cleanup); + + if (!exten_state_sub) { + return NULL; + } + + ast_copy_pj_str(exten_state_sub->event_name, &hdr->event_type, + sizeof(exten_state_sub->event_name)); + + copy_body_types(rdata, exten_state_sub); + if (!(provider = provider_by_types(exten_state_sub->event_name, + exten_state_sub->body_types, + exten_state_sub->body_types_count))) { + ast_log(LOG_WARNING, "Unable to locate subscription handler\n"); + return NULL; + } + + if (!(exten_state_sub->sip_sub = ast_sip_create_subscription( + provider->handler, role, endpoint, rdata))) { + ast_log(LOG_WARNING, "Unable to create SIP subscription for endpoint %s\n", + ast_sorcery_object_get_id(endpoint)); + return NULL; + } + + exten_state_sub->last_exten_state = INITIAL_LAST_EXTEN_STATE; + + ao2_ref(exten_state_sub, +1); + return exten_state_sub; +} + +/*! + * \internal + * \brief Create and send a NOTIFY request to the subscriber. + */ +static void create_send_notify(struct exten_state_subscription *exten_state_sub, const char *reason, + pjsip_evsub_state evsub_state, struct ast_sip_exten_state_data *exten_state_data) +{ + RAII_VAR(struct ast_str *, body_text, ast_str_create(BODY_SIZE), ast_free_ptr); + pj_str_t reason_str; + const pj_str_t *reason_str_ptr = NULL; + pjsip_tx_data *tdata; + pjsip_dialog *dlg; + char local[PJSIP_MAX_URL_SIZE], remote[PJSIP_MAX_URL_SIZE]; + struct ast_sip_body body; + + struct ast_sip_exten_state_provider *provider = provider_by_types( + exten_state_sub->event_name, exten_state_sub->body_types, + exten_state_sub->body_types_count); + + if (!provider) { + ast_log(LOG_ERROR, "Unable to locate provider for subscription\n"); + return; + } + + body.type = provider->type; + body.subtype = provider->subtype; + + dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub); + ast_copy_pj_str(local, &dlg->local.info_str, sizeof(local)); + ast_copy_pj_str(remote, &dlg->remote.info_str, sizeof(remote)); + + if (provider->create_body(exten_state_data, local, remote, &body_text)) { + ast_log(LOG_ERROR, "Unable to create body on NOTIFY request\n"); + return; + } + + body.body_text = ast_str_buffer(body_text); + + if (reason) { + pj_cstr(&reason_str, reason); + reason_str_ptr = &reason_str; + } + + if (pjsip_evsub_notify(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), + evsub_state, NULL, reason_str_ptr, &tdata) != PJ_SUCCESS) { + ast_log(LOG_WARNING, "Unable to create NOTIFY request\n"); + return; + } + + if (ast_sip_add_body(tdata, &body)) { + ast_log(LOG_WARNING, "Unable to add body to NOTIFY request\n"); + pjsip_tx_data_dec_ref(tdata); + return; + } + + if (ast_sip_subscription_send_request(exten_state_sub->sip_sub, tdata) != PJ_SUCCESS) { + ast_log(LOG_WARNING, "Unable to send NOTIFY request\n"); + pjsip_tx_data_dec_ref(tdata); + } +} + +/*! + * \internal + * \brief Get device state information and send notification to the subscriber. + */ +static void send_notify(struct exten_state_subscription *exten_state_sub, const char *reason, + pjsip_evsub_state evsub_state) +{ + RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup); + char *subtype = NULL, *message = NULL; + + struct ast_sip_exten_state_data exten_state_data = { + .exten = exten_state_sub->exten, + .presence_state = ast_hint_presence_state(NULL, exten_state_sub->context, + exten_state_sub->exten, &subtype, &message), + }; + + if ((exten_state_data.exten_state = ast_extension_state_extended( + NULL, exten_state_sub->context, exten_state_sub->exten, &info)) < 0) { + + ast_log(LOG_WARNING, "Unable to get device hint/info for extension %s\n", + exten_state_sub->exten); + return; + } + + exten_state_data.device_state_info = info; + create_send_notify(exten_state_sub, reason, evsub_state, &exten_state_data); +} + +struct notify_task_data { + struct ast_sip_exten_state_data exten_state_data; + struct exten_state_subscription *exten_state_sub; + pjsip_evsub_state evsub_state; +}; + +static void notify_task_data_destructor(void *obj) +{ + struct notify_task_data *task_data = obj; + + ao2_ref(task_data->exten_state_sub, -1); + ao2_cleanup(task_data->exten_state_data.device_state_info); +} + +static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten_state_subscription *exten_state_sub, + struct ast_state_cb_info *info) +{ + struct notify_task_data *task_data = + ao2_alloc(sizeof(*task_data), notify_task_data_destructor); + + if (!task_data) { + ast_log(LOG_WARNING, "Unable to create notify task data\n"); + return NULL; + } + + task_data->evsub_state = PJSIP_EVSUB_STATE_ACTIVE; + task_data->exten_state_sub = exten_state_sub; + task_data->exten_state_sub->last_exten_state = info->exten_state; + ao2_ref(task_data->exten_state_sub, +1); + + task_data->exten_state_data.exten = exten_state_sub->exten; + task_data->exten_state_data.exten_state = info->exten_state; + task_data->exten_state_data.presence_state = info->presence_state; + task_data->exten_state_data.device_state_info = info->device_state_info; + + if (task_data->exten_state_data.device_state_info) { + ao2_ref(task_data->exten_state_data.device_state_info, +1); + } + + if ((info->exten_state == AST_EXTENSION_DEACTIVATED) || + (info->exten_state == AST_EXTENSION_REMOVED)) { + task_data->evsub_state = PJSIP_EVSUB_STATE_TERMINATED; + ast_log(LOG_WARNING, "Watcher for hint %s %s\n", exten, info->exten_state + == AST_EXTENSION_REMOVED ? "removed" : "deactivated"); + } + + return task_data; +} + +static int notify_task(void *obj) +{ + RAII_VAR(struct notify_task_data *, task_data, obj, ao2_cleanup); + + create_send_notify(task_data->exten_state_sub, task_data->evsub_state == + PJSIP_EVSUB_STATE_TERMINATED ? "noresource" : NULL, + task_data->evsub_state, &task_data->exten_state_data); + return 0; +} + +/*! + * \internal + * \brief Callback for exten/device state changes. + * + * Upon state change, send the appropriate notification to the subscriber. + */ +static int state_changed(char *context, char *exten, + struct ast_state_cb_info *info, void *data) +{ + struct notify_task_data *task_data; + struct exten_state_subscription *exten_state_sub = data; + + if (exten_state_sub->last_exten_state == info->exten_state) { + return 0; + } + + if (!(task_data = alloc_notify_task_data(exten, exten_state_sub, info))) { + return -1; + } + + /* safe to push this async since we copy the data from info and + add a ref for the device state info */ + if (ast_sip_push_task(ast_sip_subscription_get_serializer(task_data->exten_state_sub->sip_sub), + notify_task, task_data)) { + ao2_cleanup(task_data); + return -1; + } + return 0; +} + +static void state_changed_destroy(int id, void *data) +{ + struct exten_state_subscription *exten_state_sub = data; + ao2_cleanup(exten_state_sub); +} + +static struct ast_datastore_info ds_info = { }; +static const char ds_name[] = "exten state datastore"; + +/*! + * \internal + * \brief Add a datastore for exten exten_state_subscription. + * + * Adds the exten_state_subscription wrapper object to a datastore so it can be retrieved + * later based upon its association with the ast_sip_subscription. + */ +static int add_datastore(struct exten_state_subscription *exten_state_sub) +{ + RAII_VAR(struct ast_datastore *, datastore, + ast_sip_subscription_alloc_datastore(&ds_info, ds_name), ao2_cleanup); + + if (!datastore) { + return -1; + } + + datastore->data = exten_state_sub; + ast_sip_subscription_add_datastore(exten_state_sub->sip_sub, datastore); + ao2_ref(exten_state_sub, +1); + return 0; +} + +/*! + * \internal + * \brief Get the exten_state_subscription object associated with the given + * ast_sip_subscription in the datastore. + */ +static struct exten_state_subscription *get_exten_state_sub( + struct ast_sip_subscription *sub) +{ + RAII_VAR(struct ast_datastore *, datastore, + ast_sip_subscription_get_datastore(sub, ds_name), ao2_cleanup); + + return datastore ? datastore->data : NULL; +} + +static void subscription_shutdown(struct ast_sip_subscription *sub) +{ + struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub); + + if (!exten_state_sub) { + return; + } + + ast_extension_state_del(exten_state_sub->id, state_changed); + ast_sip_subscription_remove_datastore(exten_state_sub->sip_sub, ds_name); + /* remove data store reference */ + ao2_cleanup(exten_state_sub); +} + +static struct ast_sip_subscription *new_subscribe(struct ast_sip_endpoint *endpoint, + pjsip_rx_data *rdata) +{ + pjsip_uri *uri = rdata->msg_info.msg->line.req.uri; + pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri); + RAII_VAR(struct exten_state_subscription *, exten_state_sub, NULL, ao2_cleanup); + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) { + ast_log(LOG_WARNING, "Attempt to SUBSCRIBE to a non-SIP URI\n"); + return NULL; + } + + if (!(exten_state_sub = exten_state_subscription_alloc(endpoint, AST_SIP_NOTIFIER, rdata))) { + return NULL; + } + + ast_copy_string(exten_state_sub->context, endpoint->context, sizeof(exten_state_sub->context)); + ast_copy_pj_str(exten_state_sub->exten, &sip_uri->user, sizeof(exten_state_sub->exten)); + + if ((exten_state_sub->id = ast_extension_state_add_destroy_extended( + exten_state_sub->context, exten_state_sub->exten, + state_changed, state_changed_destroy, exten_state_sub)) < 0) { + ast_log(LOG_WARNING, "Unable to subscribe extension %s\n", + exten_state_sub->exten); + pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE); + return NULL; + } + + /* bump the ref since ast_extension_state_add holds a reference */ + ao2_ref(exten_state_sub, +1); + + if (add_datastore(exten_state_sub)) { + ast_log(LOG_WARNING, "Unable to add to subscription datastore.\n"); + pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE); + return NULL; + } + + if (pjsip_evsub_accept(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), + rdata, 200, NULL) != PJ_SUCCESS) { + ast_log(LOG_WARNING, "Unable to accept the incoming extension state subscription.\n"); + pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE); + return NULL; + } + + send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_ACTIVE); + return exten_state_sub->sip_sub; +} + +static void resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata, + struct ast_sip_subscription_response_data *response_data) +{ + struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub); + + if (!exten_state_sub) { + return; + } + + send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_ACTIVE); +} + +static void subscription_timeout(struct ast_sip_subscription *sub) +{ + struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub); + + if (!exten_state_sub) { + return; + } + + ast_verbose(VERBOSE_PREFIX_3 "Subscription has timed out.\n"); + send_notify(exten_state_sub, "timeout", PJSIP_EVSUB_STATE_TERMINATED); +} + +static void subscription_terminated(struct ast_sip_subscription *sub, + pjsip_rx_data *rdata) +{ + struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub); + + if (!exten_state_sub) { + return; + } + + ast_verbose(VERBOSE_PREFIX_3 "Subscription has been terminated.\n"); + send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_TERMINATED); +} + +/*! + * \internal + * \brief Create and register a subscription handler. + * + * Creates a subscription handler that can be registered with the pub/sub + * framework for the given event_name and accept value. + */ +static struct ast_sip_subscription_handler *create_and_register_handler( + const char *event_name, const char *accept) +{ + struct ast_sip_subscription_handler *handler = + ao2_alloc(sizeof(*handler), NULL); + + if (!handler) { + return NULL; + } + + handler->event_name = event_name; + handler->accept[0] = accept; + + handler->subscription_shutdown = subscription_shutdown; + handler->new_subscribe = new_subscribe; + handler->resubscribe = resubscribe; + handler->subscription_timeout = subscription_timeout; + handler->subscription_terminated = subscription_terminated; + + if (ast_sip_register_subscription_handler(handler)) { + ast_log(LOG_WARNING, "Unable to register subscription handler %s\n", + handler->event_name); + ao2_cleanup(handler); + return NULL; + } + + return handler; +} + +int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj) +{ + if (ast_strlen_zero(obj->type)) { + ast_log(LOG_WARNING, "Type not specified on provider for event %s\n", + obj->event_name); + return -1; + } + + if (ast_strlen_zero(obj->subtype)) { + ast_log(LOG_WARNING, "Subtype not specified on provider for event %s\n", + obj->event_name); + return -1; + } + + if (!obj->create_body) { + ast_log(LOG_WARNING, "Body handler not specified on provide for event %s\n", + obj->event_name); + return -1; + } + + if (!(obj->handler = create_and_register_handler(obj->event_name, obj->body_type))) { + ast_log(LOG_WARNING, "Handler could not be registered for provider event %s\n", + obj->event_name); + return -1; + } + + /* scope to avoid mix declarations */ + { + SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + AST_RWLIST_INSERT_TAIL(&providers, obj, next); + ast_module_ref(ast_module_info->self); + } + + return 0; +} + +void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj) +{ + struct ast_sip_exten_state_provider *i; + SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) { + if (i == obj) { + ast_sip_unregister_subscription_handler(i->handler); + ao2_cleanup(i->handler); + AST_RWLIST_REMOVE_CURRENT(next); + ast_module_unref(ast_module_info->self); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; +} + +static int load_module(void) +{ + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "SIP Extension State Notifications", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_sip_exten_state.exports.in b/res/res_sip_exten_state.exports.in new file mode 100644 index 000000000..0cce6f6dd --- /dev/null +++ b/res/res_sip_exten_state.exports.in @@ -0,0 +1,7 @@ +{ + global: + LINKER_SYMBOL_PREFIXast_sip_register_exten_state_provider; + LINKER_SYMBOL_PREFIXast_sip_unregister_exten_state_provider; + local: + *; +}; diff --git a/res/res_sip_messaging.c b/res/res_sip_messaging.c new file mode 100644 index 000000000..10d47047f --- /dev/null +++ b/res/res_sip_messaging.c @@ -0,0 +1,660 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_session</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include "pjsua-lib/pjsua.h" + +#include "asterisk/message.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_session.h" + +const pjsip_method pjsip_message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} }; + +#define MAX_HDR_SIZE 512 +#define MAX_BODY_SIZE 1024 +#define MAX_EXTEN_SIZE 256 +#define MAX_USER_SIZE 128 + +/*! + * \internal + * \brief Determine where in the dialplan a call should go + * + * \details This uses the username in the request URI to try to match + * an extension in an endpoint's context in order to route the call. + * + * \param rdata The SIP request + * \param context The context to use + * \param exten The extension to use + */ +static enum pjsip_status_code get_destination(const pjsip_rx_data *rdata, const char *context, char *exten) +{ + pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri; + pjsip_sip_uri *sip_ruri; + + if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) { + return PJSIP_SC_UNSUPPORTED_URI_SCHEME; + } + + sip_ruri = pjsip_uri_get_uri(ruri); + ast_copy_pj_str(exten, &sip_ruri->user, MAX_EXTEN_SIZE); + + if (ast_exists_extension(NULL, context, exten, 1, NULL)) { + return PJSIP_SC_OK; + } + return PJSIP_SC_NOT_FOUND; +} + +/*! + * \internal + * \brief Checks to make sure the request has the correct content type. + * + * \details This module supports the following media types: "text/plain". + * Return unsupported otherwise. + * + * \param rdata The SIP request + */ +static enum pjsip_status_code check_content_type(const pjsip_rx_data *rdata) +{ + if (ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type, + "text", + "plain")) { + return PJSIP_SC_OK; + } else { + return PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; + } +} + +/*! + * \internal + * \brief Puts pointer past 'sip[s]:' string that should be at the + * front of the given 'fromto' parameter + * + * \param fromto 'From' or 'To' field containing 'sip:' + */ +static const char* skip_sip(const char *fromto) +{ + const char *p; + + /* need to be one past 'sip:' or 'sips:' */ + if (!(p = strstr(fromto, "sip"))) { + return fromto; + } + + p += 3; + if (*p == 's') { + ++p; + } + return ++p; +} + +/*! + * \internal + * \brief Retrieves an endpoint if specified in the given 'fromto' + * + * Expects the given 'fromto' to be in one of the following formats: + * sip[s]:endpoint[/aor] + * sip[s]:endpoint[/uri] + * + * If an optional aor is given it will try to find an associated uri + * to return. If an optional uri is given then that will be returned, + * otherwise uri will be NULL. + * + * \param fromto 'From' or 'To' field with possible endpoint + * \param uri Optional uri to return + */ +static struct ast_sip_endpoint* get_endpoint(const char *fromto, char **uri) +{ + const char *name = skip_sip(fromto); + struct ast_sip_endpoint* endpoint; + struct ast_sip_aor *aor; + + if ((*uri = strchr(name, '/'))) { + *(*uri)++ = '\0'; + } + + /* endpoint is required */ + if (ast_strlen_zero(name)) { + return NULL; + } + + if (!(endpoint = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), "endpoint", name))) { + return NULL; + } + + if (*uri && (aor = ast_sip_location_retrieve_aor(*uri))) { + *uri = (char*)ast_sip_location_retrieve_first_aor_contact(aor)->uri; + } + + return endpoint; +} + +/*! + * \internal + * \brief Updates fields in an outgoing 'From' header. + * + * \param tdata The outgoing message data structure + * \param from Info to potentially copy into the 'From' header + */ +static void update_from(pjsip_tx_data *tdata, const char *from) +{ + /* static const pj_str_t hname = { "From", 4 }; */ + pjsip_name_addr *from_name_addr; + pjsip_sip_uri *from_uri; + pjsip_uri *parsed; + char *uri; + + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + + if (ast_strlen_zero(from)) { + return; + } + + if (!(endpoint = get_endpoint(from, &uri))) { + return; + } + + if (ast_strlen_zero(uri)) { + /* if no aor/uri was specified get one from the endpoint */ + uri = (char*)ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors)->uri; + } + + /* get current 'from' hdr & uri - going to overwrite some fields */ + from_name_addr = (pjsip_name_addr *)PJSIP_MSG_FROM_HDR(tdata->msg)->uri; + from_uri = pjsip_uri_get_uri(from_name_addr); + + /* check to see if uri is in 'name <sip:user@domain>' format */ + if ((parsed = pjsip_parse_uri(tdata->pool, uri, strlen(uri), PJSIP_PARSE_URI_AS_NAMEADDR))) { + pjsip_name_addr *name_addr = (pjsip_name_addr *)parsed; + pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(name_addr->uri); + + pj_strdup(tdata->pool, &from_name_addr->display, &name_addr->display); + pj_strdup(tdata->pool, &from_uri->user, &sip_uri->user); + pj_strdup(tdata->pool, &from_uri->host, &sip_uri->host); + from_uri->port = sip_uri->port; + } else { + /* assume it is 'user[@domain]' format */ + char *domain = strchr(uri, '@'); + if (domain) { + *domain++ = '\0'; + pj_strdup2(tdata->pool, &from_uri->host, domain); + } + pj_strdup2(tdata->pool, &from_uri->user, uri); + } +} + +/*! + * \internal + * \brief Checks if the given msg var name should be blocked. + * + * \details Some headers are not allowed to be overriden by the user. + * Determine if the given var header name from the user is blocked for + * an outgoing MESSAGE. + * + * \param name name of header to see if it is blocked. + * + * \retval TRUE if the given header is blocked. + */ +static int is_msg_var_blocked(const char *name) +{ + int i; + + /* + * Don't block Content-Type or Max-Forwards headers because the + * user can override them. + */ + static const char *hdr[] = { + "To", + "From", + "Via", + "Route", + "Contact", + "Call-ID", + "CSeq", + "Allow", + "Content-Length", + "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 msg vars over to the request headers. + * + * \param msg The msg structure to copy headers from + * \param tdata The SIP transmission data + */ +static enum pjsip_status_code vars_to_headers(const struct ast_msg *msg, pjsip_tx_data *tdata) +{ + const char *name; + const char *value; + int max_forwards; + + RAII_VAR(struct ast_msg_var_iterator *, i, ast_msg_var_iterator_init(msg), ast_msg_var_iterator_destroy); + while (ast_msg_var_iterator_next(msg, i, &name, &value)) { + if (!strcasecmp(name, "Max-Forwards")) { + /* Decrement Max-Forwards for SIP loop prevention. */ + if (sscanf(value, "%30d", &max_forwards) != 1 || --max_forwards == 0) { + ast_log(LOG_NOTICE, "MESSAGE(Max-Forwards) reached zero. MESSAGE not sent.\n"); + return -1; + } + sprintf((char*)value, "%d", max_forwards); + ast_sip_add_header(tdata, name, value); + } + else if (!is_msg_var_blocked(name)) { + ast_sip_add_header(tdata, name, value); + } + ast_msg_var_unref_current(i); + } + return PJSIP_SC_OK; +} + +/*! + * \internal + * \brief Copies any other request header data over to ast_msg structure. + * + * \param rdata The SIP request + * \param msg The msg structure to copy headers into + */ +static int headers_to_vars(const pjsip_rx_data *rdata, struct ast_msg *msg) +{ + char *c; + char buf[MAX_HDR_SIZE]; + int res = 0; + pjsip_hdr *h = rdata->msg_info.msg->hdr.next; + pjsip_hdr *end= &rdata->msg_info.msg->hdr; + + while (h != end) { + if ((res = pjsip_hdr_print_on(h, buf, sizeof(buf)-1)) > 0) { + buf[res] = '\0'; + if ((c = strchr(buf, ':'))) { + ast_copy_string(buf, ast_skip_blanks(c + 1), sizeof(buf)-(c-buf)); + } + + if ((res = ast_msg_set_var(msg, pj_strbuf(&h->name), buf)) != 0) { + break; + } + } + h = h->next; + } + return 0; +} + +/*! + * \internal + * \brief Prints the message body into the given char buffer. + * + * \details Copies body content from the received data into the given + * character buffer removing any extra carriage return/line feeds. + * + * \param rdata The SIP request + * \param buf Buffer to fill + * \param len The length of the buffer + */ +static int print_body(pjsip_rx_data *rdata, char *buf, int len) +{ + int res = rdata->msg_info.msg->body->print_body( + rdata->msg_info.msg->body, buf, len); + + if (res < 0) { + return res; + } + + /* remove any trailing carriage return/line feeds */ + while (res > 0 && ((buf[--res] == '\r') || (buf[res] == '\n'))); + + buf[++res] = '\0'; + + return res; +} + +/*! + * \internal + * \brief Converts a pjsip_rx_data structure to an ast_msg structure. + * + * \details Attempts to fill in as much information as possible into the given + * msg structure copied from the given request data. + * + * \param rdata The SIP request + * \param msg The asterisk message structure to fill in. + */ +static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct ast_msg *msg) +{ + +#define CHECK_RES(z_) do { if (z_) { ast_msg_destroy(msg); \ + return PJSIP_SC_INTERNAL_SERVER_ERROR; } } while (0) + + int size; + char buf[MAX_BODY_SIZE]; + pjsip_name_addr *name_addr; + const char *field; + pjsip_status_code code; + struct ast_sip_endpoint *endpt = ast_pjsip_rdata_get_endpoint(rdata); + + /* make sure there is an appropriate context and extension*/ + if ((code = get_destination(rdata, endpt->context, buf)) != PJSIP_SC_OK) { + return code; + } + + CHECK_RES(ast_msg_set_context(msg, "%s", endpt->context)); + CHECK_RES(ast_msg_set_exten(msg, "%s", buf)); + + /* to header */ + name_addr = (pjsip_name_addr *)rdata->msg_info.to->uri; + if ((size = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, name_addr, buf, sizeof(buf)-1)) > 0) { + buf[size] = '\0'; + CHECK_RES(ast_msg_set_to(msg, "%s", buf)); + } + + /* from header */ + name_addr = (pjsip_name_addr *)rdata->msg_info.from->uri; + if ((size = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, name_addr, buf, sizeof(buf)-1)) > 0) { + buf[size] = '\0'; + CHECK_RES(ast_msg_set_from(msg, "%s", buf)); + } + + /* contact header */ + if ((size = pjsip_hdr_print_on(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL), buf, sizeof(buf)-1)) > 0) { + buf[size] = '\0'; + CHECK_RES(ast_msg_set_var(msg, "SIP_FULLCONTACT", buf)); + } + + /* receive address */ + field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf)-1, 1); + CHECK_RES(ast_msg_set_var(msg, "SIP_RECVADDR", field)); + + /* body */ + if (print_body(rdata, buf, sizeof(buf) - 1) > 0) { + CHECK_RES(ast_msg_set_body(msg, "%s", buf)); + } + + /* endpoint name */ + if (endpt->id.name.valid) { + CHECK_RES(ast_msg_set_var(msg, "SIP_PEERNAME", endpt->id.name.str)); + } + + CHECK_RES(headers_to_vars(rdata, msg)); + + return PJSIP_SC_OK; +} + +struct msg_data { + struct ast_msg *msg; + char *to; + char *from; +}; + +static void msg_data_destroy(void *obj) +{ + struct msg_data *mdata = obj; + + ast_free(mdata->from); + ast_free(mdata->to); + + ast_msg_destroy(mdata->msg); +} + +static struct msg_data* msg_data_create(const struct ast_msg *msg, const char *to, const char *from) +{ + char *tag; + struct msg_data *mdata = ao2_alloc(sizeof(*mdata), msg_data_destroy); + + if (!mdata) { + return NULL; + } + + /* typecast to suppress const warning */ + mdata->msg = ast_msg_ref((struct ast_msg*)msg); + + mdata->to = ast_strdup(to); + mdata->from = ast_strdup(from); + + /* sometimes from can still contain the tag at this point, so remove it */ + if ((tag = strchr(mdata->from, ';'))) { + *tag = '\0'; + } + + return mdata; +} + +static int msg_send(void *data) +{ + RAII_VAR(struct msg_data *, mdata, data, ao2_cleanup); + + const struct ast_sip_body body = { + .type = "text", + .subtype = "plain", + .body_text = ast_msg_get_body(mdata->msg) + }; + + pjsip_tx_data *tdata; + char *uri; + + RAII_VAR(struct ast_sip_endpoint *, endpoint, get_endpoint( + mdata->to, &uri), ao2_cleanup); + if (!endpoint) { + ast_log(LOG_ERROR, "SIP MESSAGE - Endpoint not found in %s\n", mdata->to); + return -1; + } + + if (ast_sip_create_request("MESSAGE", NULL, endpoint, uri, &tdata)) { + ast_log(LOG_ERROR, "SIP MESSAGE - Could not create request\n"); + return -1; + } + + if (ast_sip_add_body(tdata, &body)) { + pjsip_tx_data_dec_ref(tdata); + ast_log(LOG_ERROR, "SIP MESSAGE - Could not add body to request\n"); + return -1; + } + + update_from(tdata, mdata->from); + vars_to_headers(mdata->msg, tdata); + if (ast_sip_send_request(tdata, NULL, endpoint)) { + pjsip_tx_data_dec_ref(tdata); + ast_log(LOG_ERROR, "SIP MESSAGE - Could not send request\n"); + return -1; + } + + return PJ_SUCCESS; +} + +static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from) +{ + struct msg_data *mdata; + + if (ast_strlen_zero(to)) { + ast_log(LOG_ERROR, "SIP MESSAGE - a 'To' URI must be specified\n"); + return -1; + } + + if (!(mdata = msg_data_create(msg, to, from)) || + ast_sip_push_task(NULL, msg_send, mdata)) { + ao2_ref(mdata, -1); + return -1; + } + return 0; +} + +static const struct ast_msg_tech msg_tech = { + .name = "sip", + .msg_send = sip_msg_send, +}; + +static pj_status_t send_response(pjsip_rx_data *rdata, enum pjsip_status_code code, + pjsip_dialog *dlg, pjsip_transaction *tsx) +{ + pjsip_tx_data *tdata; + pj_status_t status; + pjsip_response_addr res_addr; + + pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); + + status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata); + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to create response (%d)\n", status); + return status; + } + + if (dlg && tsx) { + status = pjsip_dlg_send_response(dlg, tsx, tdata); + } else { + /* Get where to send request. */ + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to get response address (%d)\n", status); + return status; + } + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL); + } + + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to send response (%d)\n", status); + } + + return status; +} + +static pj_bool_t module_on_rx_request(pjsip_rx_data *rdata) +{ + enum pjsip_status_code code; + struct ast_msg *msg; + + /* if not a MESSAGE, don't handle */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_message_method)) { + return PJ_FALSE; + } + + msg = ast_msg_alloc(); + if (!msg) { + send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL); + return PJ_TRUE; + } + + if ((code = check_content_type(rdata)) != PJSIP_SC_OK) { + send_response(rdata, code, NULL, NULL); + return PJ_TRUE; + } + + if ((code = rx_data_to_ast_msg(rdata, msg)) == PJSIP_SC_OK) { + /* send it to the dialplan */ + ast_msg_queue(msg); + code = PJSIP_SC_ACCEPTED; + } + + send_response(rdata, code, NULL, NULL); + return PJ_TRUE; +} + +static int incoming_in_dialog_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) +{ + char buf[MAX_BODY_SIZE]; + enum pjsip_status_code code; + struct ast_frame f; + + pjsip_dialog *dlg = session->inv_session->dlg; + pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + + if ((code = check_content_type(rdata)) != PJSIP_SC_OK) { + send_response(rdata, code, dlg, tsx); + return 0; + } + + if (print_body(rdata, buf, sizeof(buf)-1) < 1) { + /* invalid body size */ + return 0; + } + + memset(&f, 0, sizeof(f)); + f.frametype = AST_FRAME_TEXT; + f.subclass.integer = 0; + f.offset = 0; + f.data.ptr = buf; + f.datalen = strlen(buf) + 1; + ast_queue_frame(session->channel, &f); + + send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx); + return 0; +} + +static struct ast_sip_session_supplement messaging_supplement = { + .method = "MESSAGE", + .incoming_request = incoming_in_dialog_request +}; + +static pjsip_module messaging_module = { + .name = {"Messaging Module", 16}, + .id = -1, + .priority = PJSIP_MOD_PRIORITY_APPLICATION, + .on_rx_request = module_on_rx_request, +}; + +static int load_module(void) +{ + if (ast_sip_register_service(&messaging_module) != PJ_SUCCESS) { + return AST_MODULE_LOAD_DECLINE; + } + + if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), + NULL, PJSIP_H_ALLOW, NULL, 1, + &pjsip_message_method.name) != PJ_SUCCESS) { + + ast_sip_unregister_service(&messaging_module); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_msg_tech_register(&msg_tech)) { + ast_sip_unregister_service(&messaging_module); + return AST_MODULE_LOAD_DECLINE; + } + + ast_sip_session_register_supplement(&messaging_supplement); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_session_unregister_supplement(&messaging_supplement); + ast_msg_tech_unregister(&msg_tech); + ast_sip_unregister_service(&messaging_module); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Messaging Support", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_sip_one_touch_record_info.c b/res/res_sip_one_touch_record_info.c new file mode 100644 index 000000000..b574b304c --- /dev/null +++ b/res/res_sip_one_touch_record_info.c @@ -0,0 +1,118 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, malleable, llc. + * + * Sean Bright <sean@malleable.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_session</depend> + <support_level>core</support_level> +***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_ua.h> + +#include "asterisk/features.h" +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_session.h" +#include "asterisk/module.h" +#include "asterisk/features_config.h" + +static void send_response(struct ast_sip_session *session, int code, struct pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + + if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, code, NULL, &tdata) == PJ_SUCCESS) { + struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + + pjsip_dlg_send_response(session->inv_session->dlg, tsx, tdata); + } +} + +static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) +{ + static const pj_str_t rec_str = { "Record", 6 }; + pjsip_generic_string_hdr *record; + int feature_res; + char feature_code[AST_FEATURE_MAX_LEN]; + char *digit; + + record = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &rec_str, NULL); + + /* If we don't have Record header, we have nothing to do */ + if (!record || (pj_stricmp2(&record->hvalue, "on") && pj_stricmp2(&record->hvalue, "off"))) { + return 0; + } + + if (!session->channel) { + send_response(session, 481, rdata); + return 0; + } + + /* Is this endpoint configured with One Touch Recording? */ + if (!session->endpoint->one_touch_recording) { + send_response(session, 403, rdata); + return 0; + } + + ast_channel_lock(session->channel); + feature_res = ast_get_builtin_feature(session->channel, "automixmon", feature_code, sizeof(feature_code)); + ast_channel_unlock(session->channel); + + if (feature_res || ast_strlen_zero(feature_code)) { + send_response(session, 403, rdata); + return 0; + } + + for (digit = feature_code; *digit; ++digit) { + struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit, .len = 100 }; + ast_queue_frame(session->channel, &f); + } + + send_response(session, 200, rdata); + + return 0; +} + +static struct ast_sip_session_supplement info_supplement = { + .method = "INFO", + .incoming_request = handle_incoming_request, +}; + +static int load_module(void) +{ + if (ast_sip_session_register_supplement(&info_supplement)) { + ast_log(LOG_ERROR, "Unable to register One Touch Recording supplement\n"); + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_session_unregister_supplement(&info_supplement); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP INFO One Touch Recording Support", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_sip_outbound_registration.c b/res/res_sip_outbound_registration.c index 9d73f37d5..203ecfc5a 100644 --- a/res/res_sip_outbound_registration.c +++ b/res/res_sip_outbound_registration.c @@ -600,10 +600,10 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo return -1; } - if (transport->type == AST_TRANSPORT_UDP) { + if (transport->state->transport) { selector.type = PJSIP_TPSELECTOR_TRANSPORT; selector.u.transport = transport->state->transport; - } else if (transport->type == AST_TRANSPORT_TCP || transport->type == AST_TRANSPORT_TLS) { + } else if (transport->state->factory) { selector.type = PJSIP_TPSELECTOR_LISTENER; selector.u.listener = transport->state->factory; } else { diff --git a/res/res_sip_pidf.c b/res/res_sip_pidf.c new file mode 100644 index 000000000..78633da9a --- /dev/null +++ b/res/res_sip_pidf.c @@ -0,0 +1,341 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_pubsub</depend> + <depend>res_sip_exten_state</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_simple.h> +#include <pjlib.h> + +#include "asterisk/module.h" +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_exten_state.h" + +enum state { + NOTIFY_OPEN, + NOTIFY_INUSE, + NOTIFY_CLOSED +}; + +static void exten_state_to_str(int state, char **statestring, char **pidfstate, + char **pidfnote, int *local_state) +{ + switch (state) { + case AST_EXTENSION_RINGING: + *statestring = "early"; + *local_state = NOTIFY_INUSE; + *pidfstate = "busy"; + *pidfnote = "Ringing"; + break; + case AST_EXTENSION_INUSE: + *statestring = "confirmed"; + *local_state = NOTIFY_INUSE; + *pidfstate = "busy"; + *pidfnote = "On the phone"; + break; + case AST_EXTENSION_BUSY: + *statestring = "confirmed"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "busy"; + *pidfnote = "On the phone"; + break; + case AST_EXTENSION_UNAVAILABLE: + *statestring = "terminated"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "away"; + *pidfnote = "Unavailable"; + break; + case AST_EXTENSION_ONHOLD: + *statestring = "confirmed"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "busy"; + *pidfnote = "On hold"; + break; + case AST_EXTENSION_NOT_INUSE: + default: + /* Default setting */ + *statestring = "terminated"; + *local_state = NOTIFY_OPEN; + *pidfstate = "--"; + *pidfnote ="Ready"; + + break; + } +} + +static pj_xml_attr *create_attr(pj_pool_t *pool, pj_xml_node *node, + const char *name, const char *value) +{ + pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr); + + pj_strdup2(pool, &attr->name, name); + pj_strdup2(pool, &attr->value, value); + + pj_xml_add_attr(node, attr); + return attr; +} + +static pj_xml_node *create_node(pj_pool_t *pool, pj_xml_node *parent, + const char* name) +{ + pj_xml_node *node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + + pj_strdup2(pool, &node->name, name); + + node->content.ptr = NULL; + node->content.slen = 0; + + pj_xml_add_node(parent, node); + return node; +} + +static pj_xml_attr *find_node_attr(pj_pool_t* pool, pj_xml_node *parent, + const char *node_name, const char *attr_name) +{ + pj_str_t name; + pj_xml_node *node; + pj_xml_attr *attr; + + if (!(node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) { + node = create_node(pool, parent, node_name); + } + + if (!(attr = pj_xml_find_attr(node, pj_cstr(&name, attr_name), NULL))) { + attr = create_attr(pool, node, attr_name, ""); + } + + return attr; +} + +/*! + * \internal + * \brief Adds non standard elements to the xml body + * + * This is some code that was part of the original chan_sip implementation + * that is not part of the RFC 3863 definition, but we are keeping available + * for backward compatability. The original comment stated that Eyebeam + * supports this format. + + */ +static void add_non_standard(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate) +{ + static const char *XMLNS_PP = "xmlns:pp"; + static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person"; + + static const char *XMLNS_ES = "xmlns:es"; + static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status"; + + static const char *XMLNS_EP = "xmlns:ep"; + static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person"; + + pj_xml_node *person = create_node(pool, node, "pp:person"); + pj_xml_node *status = create_node(pool, person, "status"); + + if (pidfstate[0] != '-') { + pj_xml_node *activities = create_node(pool, status, "ep:activities"); + pj_strdup2(pool, &activities->content, "ep:"); + pj_strcat2(&activities->content, pidfstate); + } + + create_attr(pool, node, XMLNS_PP, XMLNS_PERSON); + create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS); + create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON); +} + +static void release_pool(void *obj) +{ + pj_pool_t *pool = obj; + + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); +} + +static int pidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local, + const char *remote, struct ast_str **body_text) +{ + pjpidf_pres *pres; + pjpidf_tuple *tuple; + pj_str_t entity, note, id, contact, priority; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + int local_state, size; + + RAII_VAR(pj_pool_t *, pool, + pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "pidf", 1024, 1024), release_pool); + + exten_state_to_str(data->exten_state, &statestring, &pidfstate, + &pidfnote, &local_state); + + if (!(pres = pjpidf_create(pool, pj_cstr(&entity, local)))) { + ast_log(LOG_WARNING, "Unable to create PIDF presence\n"); + return -1; + } + + add_non_standard(pool, pres, pidfstate); + + if (!pjpidf_pres_add_note(pool, pres, pj_cstr(¬e, pidfnote))) { + ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n"); + return -1; + } + + if (!(tuple = pjpidf_pres_add_tuple(pool, pres, pj_cstr(&id, data->exten)))) { + ast_log(LOG_WARNING, "Unable to create PIDF tuple\n"); + return -1; + } + + pjpidf_tuple_set_contact(pool, tuple, pj_cstr(&contact, remote)); + pjpidf_tuple_set_contact_prio(pool, tuple, pj_cstr(&priority, "1")); + pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple), + (pidfstate[0] == 'b') || (local_state != NOTIFY_CLOSED)); + + if (!(size = pjpidf_print(pres, ast_str_buffer(*body_text), + ast_str_size(*body_text)))) { + ast_log(LOG_WARNING, "PIDF body text too large\n"); + return -1; + } + *(ast_str_buffer(*body_text) + size) = '\0'; + ast_str_update(*body_text); + + return 0; +} + +static struct ast_sip_exten_state_provider pidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "pidf+xml", + .body_type = "application/pidf+xml", + .create_body = pidf_xml_create_body +}; + +static int xpidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local, + const char *remote, struct ast_str **body_text) +{ + static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 }; + pjxpidf_pres *pres; + pj_xml_attr *attr; + pj_str_t name, uri; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + int local_state, size; + + RAII_VAR(pj_pool_t *, pool, + pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "pidf", 1024, 1024), release_pool); + + exten_state_to_str(data->exten_state, &statestring, &pidfstate, + &pidfnote, &local_state); + + if (!(pres = pjxpidf_create(pool, pj_cstr(&name, local)))) { + ast_log(LOG_WARNING, "Unable to create PIDF presence\n"); + return -1; + } + + attr = find_node_attr(pool, pres, "atom", "id"); + pj_strdup2(pool, &attr->value, data->exten); + + attr = find_node_attr(pool, pres, "address", "uri"); + + uri.ptr = (char*) pj_pool_alloc(pool, strlen(remote) + STR_ADDR_PARAM.slen); + pj_strcpy2( &uri, remote); + pj_strcat( &uri, &STR_ADDR_PARAM); + pj_strdup(pool, &attr->value, &uri); + + create_attr(pool, pj_xml_find_node(pres, pj_cstr(&name, "address")), + "priority", "0.80000"); + + attr = find_node_attr(pool, pres, "status", "status"); + pj_strdup2(pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "open" : + (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); + + attr = find_node_attr(pool, pres, "msnsubstatus", "substatus"); + pj_strdup2(pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "online" : + (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); + + if (!(size = pjxpidf_print(pres, ast_str_buffer(*body_text), + ast_str_size(*body_text)))) { + ast_log(LOG_WARNING, "XPIDF body text too large\n"); + return -1; + } + + *(ast_str_buffer(*body_text) + size) = '\0'; + ast_str_update(*body_text); + + return 0; +} + +static struct ast_sip_exten_state_provider xpidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "xpidf+xml", + .body_type = "application/xpidf+xml", + .create_body = xpidf_xml_create_body +}; + +static struct ast_sip_exten_state_provider cpim_pidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "cpim-pidf+xml", + .body_type = "application/cpim-pidf+xml", + .create_body = xpidf_xml_create_body, +}; + +static int load_module(void) +{ + if (ast_sip_register_exten_state_provider(&pidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + pidf_xml_provider.event_name, pidf_xml_provider.body_type); + } + + if (ast_sip_register_exten_state_provider(&xpidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + xpidf_xml_provider.event_name, xpidf_xml_provider.body_type); + } + + if (ast_sip_register_exten_state_provider(&cpim_pidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + cpim_pidf_xml_provider.event_name, cpim_pidf_xml_provider.body_type); + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_unregister_exten_state_provider(&cpim_pidf_xml_provider); + ast_sip_unregister_exten_state_provider(&xpidf_xml_provider); + ast_sip_unregister_exten_state_provider(&pidf_xml_provider); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Extension State PIDF Provider", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_sip_pubsub.c b/res/res_sip_pubsub.c index c8a76a602..590c96c49 100644 --- a/res/res_sip_pubsub.c +++ b/res/res_sip_pubsub.c @@ -149,16 +149,12 @@ static pjsip_evsub *allocate_evsub(const char *event, enum ast_sip_subscription_ if (role == AST_SIP_NOTIFIER) { if (!strcmp(event, "message-summary")) { pjsip_mwi_create_uas(dlg, &pubsub_cb, rdata, &evsub); - } else if (!strcmp(event, "presence")) { - pjsip_pres_create_uas(dlg, &pubsub_cb, rdata, &evsub); } else { pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &evsub); } } else { if (!strcmp(event, "message-summary")) { pjsip_mwi_create_uac(dlg, &pubsub_cb, 0, &evsub); - } else if (!strcmp(event, "presence")) { - pjsip_pres_create_uac(dlg, &pubsub_cb, 0, &evsub); } else { pj_str_t pj_event; pj_cstr(&pj_event, event); @@ -239,6 +235,11 @@ pjsip_evsub *ast_sip_subscription_get_evsub(struct ast_sip_subscription *sub) return sub->evsub; } +pjsip_dialog *ast_sip_subscription_get_dlg(struct ast_sip_subscription *sub) +{ + return sub->dlg; +} + int ast_sip_subscription_send_request(struct ast_sip_subscription *sub, pjsip_tx_data *tdata) { return pjsip_evsub_send_request(ast_sip_subscription_get_evsub(sub), @@ -340,7 +341,6 @@ static int handler_exists_for_event_name(const char *event_name) int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler) { - pj_str_t event; pj_str_t accept[AST_SIP_MAX_ACCEPT]; int i; @@ -354,29 +354,29 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h return -1; } - if (handler_exists_for_event_name(handler->event_name)) { - ast_log(LOG_ERROR, "A subscription handler for event %s already exists. Not registering " - "new subscription handler\n", handler->event_name); - return -1; - } - - pj_cstr(&event, handler->event_name); for (i = 0; i < AST_SIP_MAX_ACCEPT && !ast_strlen_zero(handler->accept[i]); ++i) { pj_cstr(&accept[i], handler->accept[i]); } - if (!strcmp(handler->event_name, "message-summary")) { - pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance()); - } else if (!strcmp(handler->event_name, "presence")) { - pjsip_pres_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance()); + if (!handler_exists_for_event_name(handler->event_name)) { + pj_str_t event; + + pj_cstr(&event, handler->event_name); + + if (!strcmp(handler->event_name, "message-summary")) { + pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance()); + } else { + pjsip_evsub_register_pkg(&sub_module, &event, DEFAULT_EXPIRES, i, accept); + } } else { - pjsip_evsub_register_pkg(&sub_module, &event, DEFAULT_EXPIRES, i, accept); + pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &sub_module, PJSIP_H_ACCEPT, NULL, + i, accept); } add_handler(handler); return 0; } - + void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler) { struct ast_sip_subscription_handler *iter; @@ -475,7 +475,19 @@ static pj_bool_t sub_on_rx_request(pjsip_rx_data *rdata) } sub = handler->new_subscribe(endpoint, rdata); if (!sub) { - pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata); + + if (trans) { + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_tx_data *tdata; + + if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, &tdata) != PJ_SUCCESS) { + return PJ_TRUE; + } + pjsip_dlg_send_response(dlg, trans, tdata); + } else { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + } } return PJ_TRUE; } @@ -515,7 +527,8 @@ static void pubsub_on_tsx_state(pjsip_evsub *evsub, pjsip_transaction *tsx, pjsi return; } - if (tsx->role == PJSIP_ROLE_UAC && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + if (sub->handler->notify_response && tsx->role == PJSIP_ROLE_UAC && + event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { sub->handler->notify_response(sub, event->body.tsx_state.src.rdata); } } @@ -599,7 +612,7 @@ static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p .status_code = 200, }; - if (!sub|| !sub->handler->notify_request) { + if (!sub || !sub->handler->notify_request) { return; } diff --git a/res/res_sip_pubsub.exports.in b/res/res_sip_pubsub.exports.in index 55308746a..0ef193d8a 100644 --- a/res/res_sip_pubsub.exports.in +++ b/res/res_sip_pubsub.exports.in @@ -4,6 +4,7 @@ LINKER_SYMBOL_PREFIXast_sip_subsription_get_endpoint; LINKER_SYMBOL_PREFIXast_sip_subscription_get_serializer; LINKER_SYMBOL_PREFIXast_sip_subscription_get_evsub; + LINKER_SYMBOL_PREFIXast_sip_subscription_get_dlg; LINKER_SYMBOL_PREFIXast_sip_subscription_send_request; LINKER_SYMBOL_PREFIXast_sip_subscription_alloc_datastore; LINKER_SYMBOL_PREFIXast_sip_subscription_add_datastore; diff --git a/res/res_sip_refer.c b/res/res_sip_refer.c new file mode 100644 index 000000000..dfc35a3f4 --- /dev/null +++ b/res/res_sip_refer.c @@ -0,0 +1,860 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_session</depend> + <depend>res_sip_pubsub</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_ua.h> + +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_session.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/taskprocessor.h" +#include "asterisk/bridging.h" +#include "asterisk/framehook.h" + +/*! \brief REFER Progress structure */ +struct refer_progress { + /*! \brief Subscription to provide updates on */ + pjsip_evsub *sub; + /*! \brief Dialog for subscription */ + pjsip_dialog *dlg; + /*! \brief Received packet, used to construct final response in case no subscription exists */ + pjsip_rx_data *rdata; + /*! \brief Frame hook for monitoring REFER progress */ + int framehook; + /*! \brief Last received subclass in frame hook */ + int subclass; + /*! \brief Serializer for notifications */ + struct ast_taskprocessor *serializer; +}; + +/*! \brief REFER Progress notification structure */ +struct refer_progress_notification { + /*! \brief Refer progress structure to send notification on */ + struct refer_progress *progress; + /*! \brief SIP response code to send */ + int response; + /*! \brief Subscription state */ + pjsip_evsub_state state; +}; + +/*! \brief REFER Progress module, used to attach REFER progress structure to subscriptions */ +static pjsip_module refer_progress_module = { + .name = { "REFER Progress", 14 }, + .id = -1, +}; + +/*! \brief Destructor for REFER Progress notification structure */ +static void refer_progress_notification_destroy(void *obj) +{ + struct refer_progress_notification *notification = obj; + + ao2_cleanup(notification->progress); +} + +/*! \brief Allocator for REFER Progress notification structure */ +static struct refer_progress_notification *refer_progress_notification_alloc(struct refer_progress *progress, int response, + pjsip_evsub_state state) +{ + struct refer_progress_notification *notification = ao2_alloc(sizeof(*notification), refer_progress_notification_destroy); + + if (!notification) { + return NULL; + } + + ao2_ref(progress, +1); + notification->progress = progress; + notification->response = response; + notification->state = state; + + return notification; +} + +/*! \brief Serialized callback for subscription notification */ +static int refer_progress_notify(void *data) +{ + RAII_VAR(struct refer_progress_notification *, notification, data, ao2_cleanup); + pjsip_evsub *sub; + pjsip_tx_data *tdata; + + /* If the subscription has already been terminated we can't send a notification */ + if (!(sub = notification->progress->sub)) { + ast_debug(3, "Not sending NOTIFY of response '%d' and state '%d' on progress monitor '%p' as subscription has been terminated\n", + notification->response, notification->state, notification->progress); + return 0; + } + + /* If the subscription is being terminated we want to actually remove the progress structure here to + * stop a deadlock from occurring - basically terminated changes the state which queues a synchronous task + * but we are already running a task... thus it would deadlock */ + if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) { + ast_debug(3, "Subscription '%p' is being terminated as a result of a NOTIFY, removing REFER progress structure early on progress monitor '%p'\n", + notification->progress->sub, notification->progress); + pjsip_dlg_inc_lock(notification->progress->dlg); + pjsip_evsub_set_mod_data(notification->progress->sub, refer_progress_module.id, NULL); + pjsip_dlg_dec_lock(notification->progress->dlg); + + /* This is for dropping the reference on the subscription */ + ao2_cleanup(notification->progress); + + notification->progress->sub = NULL; + } + + ast_debug(3, "Sending NOTIFY with response '%d' and state '%d' on subscription '%p' and progress monitor '%p'\n", + notification->response, notification->state, sub, notification->progress); + + /* Actually send the notification */ + if (pjsip_xfer_notify(sub, notification->state, notification->response, NULL, &tdata) == PJ_SUCCESS) { + pjsip_xfer_send_request(sub, tdata); + } + + return 0; +} + +/*! \brief Progress monitoring frame hook - examines frames to determine state of transfer */ +static struct ast_frame *refer_progress_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data) +{ + struct refer_progress *progress = data; + struct refer_progress_notification *notification = NULL; + + /* We only care about frames *to* the channel */ + if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) { + return f; + } + + /* Determine the state of the REFER based on the control frames (or voice frames) passing */ + if (f->frametype == AST_FRAME_VOICE && !progress->subclass) { + /* Media is passing without progress, this means the call has been answered */ + notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED); + } else if (f->frametype == AST_FRAME_CONTROL) { + progress->subclass = f->subclass.integer; + + /* Based on the control frame being written we can send a NOTIFY advising of the progress */ + if ((f->subclass.integer == AST_CONTROL_RING) || (f->subclass.integer == AST_CONTROL_RINGING)) { + notification = refer_progress_notification_alloc(progress, 180, PJSIP_EVSUB_STATE_ACTIVE); + } else if (f->subclass.integer == AST_CONTROL_BUSY) { + notification = refer_progress_notification_alloc(progress, 486, PJSIP_EVSUB_STATE_TERMINATED); + } else if (f->subclass.integer == AST_CONTROL_CONGESTION) { + notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED); + } else if (f->subclass.integer == AST_CONTROL_PROGRESS) { + notification = refer_progress_notification_alloc(progress, 183, PJSIP_EVSUB_STATE_ACTIVE); + } else if (f->subclass.integer == AST_CONTROL_PROCEEDING) { + notification = refer_progress_notification_alloc(progress, 100, PJSIP_EVSUB_STATE_ACTIVE); + } else if (f->subclass.integer == AST_CONTROL_ANSWER) { + notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED); + } + } + + /* If a notification is due to be sent push it to the thread pool */ + if (notification) { + if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) { + ao2_cleanup(notification); + } + + /* If the subscription is being terminated we don't need the frame hook any longer */ + if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) { + ast_debug(3, "Detaching REFER progress monitoring hook from '%s' as subscription is being terminated\n", + ast_channel_name(chan)); + ast_framehook_detach(chan, progress->framehook); + } + + } + + return f; +} + +/*! \brief Destroy callback for monitoring framehook */ +static void refer_progress_framehook_destroy(void *data) +{ + struct refer_progress *progress = data; + struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED); + + if (notification && ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) { + ao2_cleanup(notification); + } + + ao2_cleanup(progress); +} + +/*! \brief Serialized callback for subscription termination */ +static int refer_progress_terminate(void *data) +{ + struct refer_progress *progress = data; + + /* The subscription is no longer valid */ + progress->sub = NULL; + + return 0; +} + +/*! \brief Callback for REFER subscription state changes */ +static void refer_progress_on_evsub_state(pjsip_evsub *sub, pjsip_event *event) +{ + struct refer_progress *progress = pjsip_evsub_get_mod_data(sub, refer_progress_module.id); + + /* If being destroyed queue it up to the serializer */ + if (progress && (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)) { + /* To prevent a deadlock race condition we unlock the dialog so other serialized tasks can execute */ + ast_debug(3, "Subscription '%p' has been remotely terminated, waiting for other tasks to complete on progress monitor '%p'\n", + sub, progress); + + /* It's possible that a task is waiting to remove us already, so bump the refcount of progress so it doesn't get destroyed */ + ao2_ref(progress, +1); + pjsip_dlg_dec_lock(progress->dlg); + ast_sip_push_task_synchronous(progress->serializer, refer_progress_terminate, progress); + pjsip_dlg_inc_lock(progress->dlg); + ao2_ref(progress, -1); + + ast_debug(3, "Subscription '%p' removed from progress monitor '%p'\n", sub, progress); + + /* Since it was unlocked it is possible for this to have been removed already, so check again */ + if (pjsip_evsub_get_mod_data(sub, refer_progress_module.id)) { + pjsip_evsub_set_mod_data(sub, refer_progress_module.id, NULL); + ao2_cleanup(progress); + } + } +} + +/*! \brief Callback structure for subscription */ +static pjsip_evsub_user refer_progress_evsub_cb = { + .on_evsub_state = refer_progress_on_evsub_state, +}; + +/*! \brief Destructor for REFER progress sutrcture */ +static void refer_progress_destroy(void *obj) +{ + struct refer_progress *progress = obj; + + ast_taskprocessor_unreference(progress->serializer); +} + +/*! \brief Internal helper function which sets up a refer progress structure if needed */ +static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *rdata, struct refer_progress **progress) +{ + const pj_str_t str_refer_sub = { "Refer-Sub", 9 }; + pjsip_generic_string_hdr *refer_sub = NULL; + const pj_str_t str_true = { "true", 4 }; + pjsip_tx_data *tdata; + pjsip_hdr hdr_list; + + *progress = NULL; + + /* Grab the optional Refer-Sub header, it can be used to suppress the implicit subscription */ + refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL); + if ((refer_sub && pj_strnicmp(&refer_sub->hvalue, &str_true, 4))) { + return 0; + } + + if (!(*progress = ao2_alloc(sizeof(struct refer_progress), refer_progress_destroy))) { + return -1; + } + + ast_debug(3, "Created progress monitor '%p' for transfer occurring from channel '%s' and endpoint '%s'\n", + progress, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + + (*progress)->framehook = -1; + + /* To prevent a potential deadlock we need the dialog so we can lock/unlock */ + (*progress)->dlg = session->inv_session->dlg; + + if (!((*progress)->serializer = ast_sip_create_serializer())) { + goto error; + } + + /* Create the implicit subscription for monitoring of this transfer */ + if (pjsip_xfer_create_uas(session->inv_session->dlg, &refer_progress_evsub_cb, rdata, &(*progress)->sub) != PJ_SUCCESS) { + goto error; + } + + /* Associate the REFER progress structure with the subscription */ + ao2_ref(*progress, +1); + pjsip_evsub_set_mod_data((*progress)->sub, refer_progress_module.id, *progress); + + pj_list_init(&hdr_list); + if (refer_sub) { + pjsip_hdr *hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(session->inv_session->dlg->pool, &str_refer_sub, &str_true); + + pj_list_push_back(&hdr_list, hdr); + } + + /* Accept the REFER request */ + ast_debug(3, "Accepting REFER request for progress monitor '%p'\n", *progress); + pjsip_xfer_accept((*progress)->sub, rdata, 202, &hdr_list); + + /* Send initial NOTIFY Request */ + ast_debug(3, "Sending initial 100 Trying NOTIFY for progress monitor '%p'\n", *progress); + if (pjsip_xfer_notify((*progress)->sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) { + pjsip_xfer_send_request((*progress)->sub, tdata); + } + + return 0; + +error: + ao2_cleanup(*progress); + *progress = NULL; + return -1; +} + +/*! \brief Structure for attended transfer task */ +struct refer_attended { + /*! \brief Transferer session */ + struct ast_sip_session *transferer; + /*! \brief Transferer channel */ + struct ast_channel *transferer_chan; + /*! \brief Second transferer session */ + struct ast_sip_session *transferer_second ; + /*! \brief Optional refer progress structure */ + struct refer_progress *progress; +}; + +/*! \brief Destructor for attended transfer task */ +static void refer_attended_destroy(void *obj) +{ + struct refer_attended *attended = obj; + + ao2_cleanup(attended->transferer); + ast_channel_unref(attended->transferer_chan); + ao2_cleanup(attended->transferer_second); +} + +/*! \brief Allocator for attended transfer task */ +static struct refer_attended *refer_attended_alloc(struct ast_sip_session *transferer, struct ast_sip_session *transferer_second, + struct refer_progress *progress) +{ + struct refer_attended *attended = ao2_alloc(sizeof(*attended), refer_attended_destroy); + + if (!attended) { + return NULL; + } + + ao2_ref(transferer, +1); + attended->transferer = transferer; + ast_channel_ref(transferer->channel); + attended->transferer_chan = transferer->channel; + ao2_ref(transferer_second, +1); + attended->transferer_second = transferer_second; + + if (progress) { + ao2_ref(progress, +1); + attended->progress = progress; + } + + return attended; +} + +/*! \brief Task for attended transfer */ +static int refer_attended(void *data) +{ + RAII_VAR(struct refer_attended *, attended, data, ao2_cleanup); + int response = 0; + + ast_debug(3, "Performing a REFER attended transfer - Transferer #1: %s Transferer #2: %s\n", + ast_channel_name(attended->transferer_chan), ast_channel_name(attended->transferer_second->channel)); + + switch (ast_bridge_transfer_attended(attended->transferer_chan, attended->transferer_second->channel)) { + case AST_BRIDGE_TRANSFER_INVALID: + response = 400; + break; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + response = 403; + break; + case AST_BRIDGE_TRANSFER_FAIL: + response = 500; + break; + case AST_BRIDGE_TRANSFER_SUCCESS: + response = 200; + ast_sip_session_defer_termination(attended->transferer); + break; + } + + ast_debug(3, "Final response for REFER attended transfer - Transferer #1: %s Transferer #2: %s is '%d'\n", + ast_channel_name(attended->transferer_chan), ast_channel_name(attended->transferer_second->channel), response); + + if (attended->progress && response) { + struct refer_progress_notification *notification = refer_progress_notification_alloc(attended->progress, response, PJSIP_EVSUB_STATE_TERMINATED); + + if (notification) { + refer_progress_notify(notification); + } + } + + return 0; +} + +/*! \brief Structure for blind transfer callback details */ +struct refer_blind { + /*! \brief Context being used for transfer */ + const char *context; + /*! \brief Optional progress structure */ + struct refer_progress *progress; + /*! \brief REFER message */ + pjsip_rx_data *rdata; + /*! \brief Optional Replaces header */ + pjsip_replaces_hdr *replaces; + /*! \brief Optional Refer-To header */ + pjsip_sip_uri *refer_to; +}; + +/*! \brief Blind transfer callback function */ +static void refer_blind_callback(struct ast_channel *chan, void *user_data, enum ast_transfer_type transfer_type) +{ + struct refer_blind *refer = user_data; + const pj_str_t str_referred_by = { "Referred-By", 11 }; + pjsip_generic_string_hdr *referred_by = pjsip_msg_find_hdr_by_name(refer->rdata->msg_info.msg, &str_referred_by, NULL); + + pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes"); + + /* If progress monitoring is being done attach a frame hook so we can monitor it */ + if (refer->progress) { + struct ast_framehook_interface hook = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = refer_progress_framehook, + .destroy_cb = refer_progress_framehook_destroy, + .data = refer->progress, + }; + + /* We need to bump the reference count up on the progress structure since it is in the frame hook now */ + ao2_ref(refer->progress, +1); + + /* If we can't attach a frame hook for whatever reason send a notification of success immediately */ + if ((refer->progress->framehook = ast_framehook_attach(chan, &hook)) < 0) { + struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200, + PJSIP_EVSUB_STATE_TERMINATED); + + ast_log(LOG_WARNING, "Could not attach REFER transfer progress monitoring hook to channel '%s' - assuming success\n", + ast_channel_name(chan)); + + if (notification) { + refer_progress_notify(notification); + } + + ao2_cleanup(refer->progress); + } + } + + if (!ast_strlen_zero(refer->context)) { + pbx_builtin_setvar_helper(chan, "SIPREFERRINGCONTEXT", refer->context); + } + + if (referred_by) { + char *uri = referred_by->hvalue.ptr; + + uri[referred_by->hvalue.slen] = '\0'; + pbx_builtin_setvar_helper(chan, "SIPREFERREDBYHDR", uri); + } + + if (refer->replaces) { + char replaces[512]; + + pjsip_hdr_print_on(refer->replaces, replaces, sizeof(replaces)); + pbx_builtin_setvar_helper(chan, "SIPREPLACESHDR", replaces); + } + + if (refer->refer_to) { + char refer_to[PJSIP_MAX_URL_SIZE]; + + pjsip_uri_print(PJSIP_URI_IN_REQ_URI, refer->refer_to, refer_to, sizeof(refer_to)); + pbx_builtin_setvar_helper(chan, "SIPREFERTOHDR", refer_to); + } +} + +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) +{ + const pj_str_t str_replaces = { "Replaces", 8 }; + pj_str_t replaces_content; + pjsip_replaces_hdr *replaces; + int parsed_len; + pjsip_dialog *dlg; + + pj_strdup_with_null(rdata->tp_info.pool, &replaces_content, &replaces_param->value); + + /* Parsing the parameter as a Replaces header easily grabs the needed information */ + if (!(replaces = pjsip_parse_hdr(rdata->tp_info.pool, &str_replaces, replaces_content.ptr, + pj_strlen(&replaces_content), &parsed_len))) { + ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' with invalid Replaces header, rejecting\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 400; + } + + /* See if the dialog is local, or remote */ + if ((dlg = pjsip_ua_find_dialog(&replaces->call_id, &replaces->to_tag, &replaces->from_tag, PJ_TRUE))) { + RAII_VAR(struct ast_sip_session *, other_session, ast_sip_dialog_get_session(dlg), ao2_cleanup); + struct refer_attended *attended; + + pjsip_dlg_dec_lock(dlg); + + if (!other_session) { + ast_debug(3, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but no session exists on it\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 603; + } + + /* We defer actually doing the attended transfer to the other session so no deadlock can occur */ + if (!(attended = refer_attended_alloc(session, other_session, progress))) { + ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but could not allocate structure to complete, rejecting\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 500; + } + + /* Push it to the other session, which will have both channels with minimal locking */ + if (ast_sip_push_task(other_session->serializer, refer_attended, attended)) { + ao2_cleanup(attended); + return 500; + } + + ast_debug(3, "Attended transfer from '%s' pushed to second channel serializer\n", + ast_channel_name(session->channel)); + + return 200; + } else { + const char *context = (session->channel ? pbx_builtin_getvar_helper(session->channel, "TRANSFER_CONTEXT") : ""); + struct refer_blind refer = { 0, }; + + if (ast_strlen_zero(context)) { + context = session->endpoint->context; + } + + if (!ast_exists_extension(NULL, context, "external_replaces", 1, NULL)) { + ast_log(LOG_ERROR, "Received REFER for remote session on channel '%s' from endpoint '%s' but 'external_replaces' context does not exist for handling\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 404; + } + + refer.context = context; + refer.progress = progress; + refer.rdata = rdata; + refer.replaces = replaces; + refer.refer_to = target_uri; + + switch (ast_bridge_transfer_blind(session->channel, "external_replaces", context, refer_blind_callback, &refer)) { + case AST_BRIDGE_TRANSFER_INVALID: + return 400; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + return 403; + case AST_BRIDGE_TRANSFER_FAIL: + return 500; + case AST_BRIDGE_TRANSFER_SUCCESS: + ast_sip_session_defer_termination(session); + return 200; + } + + return 503; + } + + return 0; +} + +static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target, + struct refer_progress *progress) +{ + const char *context = (session->channel ? pbx_builtin_getvar_helper(session->channel, "TRANSFER_CONTEXT") : ""); + char exten[AST_MAX_EXTENSION]; + struct refer_blind refer = { 0, }; + + /* If no explicit transfer context has been provided use their configured context */ + if (ast_strlen_zero(context)) { + context = session->endpoint->context; + } + + /* Using the user portion of the target URI see if it exists as a valid extension in their context */ + ast_copy_pj_str(exten, &target->user, sizeof(exten)); + if (!ast_exists_extension(NULL, context, exten, 1, NULL)) { + ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted blind transfer to '%s@%s' but target does not exist\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), exten, context); + return 404; + } + + refer.context = context; + refer.progress = progress; + refer.rdata = rdata; + + switch (ast_bridge_transfer_blind(session->channel, exten, context, refer_blind_callback, &refer)) { + case AST_BRIDGE_TRANSFER_INVALID: + return 400; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + return 403; + case AST_BRIDGE_TRANSFER_FAIL: + return 500; + case AST_BRIDGE_TRANSFER_SUCCESS: + ast_sip_session_defer_termination(session); + return 200; + } + + return 503; +} + +/*! \brief Structure used to retrieve channel from another session */ +struct invite_replaces { + /*! \brief Session we want the channel from */ + struct ast_sip_session *session; + /*! \brief Channel from the session (with reference) */ + struct ast_channel *channel; + /*! \brief Bridge the channel is in */ + struct ast_bridge *bridge; +}; + +/*! \brief Task for invite replaces */ +static int invite_replaces(void *data) +{ + struct invite_replaces *invite = data; + + if (!invite->session->channel) { + return -1; + } + + ast_channel_ref(invite->session->channel); + invite->channel = invite->session->channel; + + ast_channel_lock(invite->channel); + invite->bridge = ast_channel_get_bridge(invite->channel); + ast_channel_unlock(invite->channel); + + return 0; +} + +static int refer_incoming_invite_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) +{ + pjsip_dialog *other_dlg = NULL; + pjsip_tx_data *packet; + int response = 0; + RAII_VAR(struct ast_sip_session *, other_session, NULL, ao2_cleanup); + struct invite_replaces invite; + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + + /* If a Replaces header is present make sure it is valid */ + if (pjsip_replaces_verify_request(rdata, &other_dlg, PJ_TRUE, &packet) != PJ_SUCCESS) { + response = packet->msg->line.status.code; + pjsip_tx_data_dec_ref(packet); + goto end; + } + + /* If no other dialog exists then this INVITE request does not have a Replaces header */ + if (!other_dlg) { + return 0; + } + + other_session = ast_sip_dialog_get_session(other_dlg); + pjsip_dlg_dec_lock(other_dlg); + + if (!other_session) { + response = 481; + ast_debug(3, "INVITE with Replaces received on channel '%s' from endpoint '%s', but requested session does not exist\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + goto end; + } + + invite.session = other_session; + + if (ast_sip_push_task_synchronous(other_session->serializer, invite_replaces, &invite)) { + response = 481; + goto end; + } + + ast_setstate(session->channel, AST_STATE_RING); + ast_raw_answer(session->channel); + + if (!invite.bridge) { + struct ast_channel *chan = session->channel; + + /* This will use a synchronous task but we aren't operating in the serializer at this point in time, so it + * won't deadlock */ + if (!ast_channel_move(invite.channel, session->channel)) { + ast_hangup(chan); + } else { + response = 500; + } + } else { + if (ast_bridge_impart(invite.bridge, session->channel, invite.channel, NULL, 1)) { + response = 500; + } + } + + if (!response) { + ast_debug(3, "INVITE with Replaces successfully completed on channels '%s' and '%s'\n", + ast_channel_name(session->channel), ast_channel_name(invite.channel)); + } + + ast_channel_unref(invite.channel); + ao2_cleanup(invite.bridge); + +end: + if (response) { + ast_debug(3, "INVITE with Replaces failed on channel '%s', sending response of '%d'\n", + ast_channel_name(session->channel), response); + session->defer_terminate = 1; + ast_hangup(session->channel); + session->channel = NULL; + + if (pjsip_inv_end_session(session->inv_session, response, NULL, &packet) == PJ_SUCCESS) { + ast_sip_session_send_response(session, packet); + } + } + + return 1; +} + +static int refer_incoming_refer_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) +{ + const pj_str_t str_refer_to = { "Refer-To", 8 }; + pjsip_generic_string_hdr *refer_to; + char *uri; + const pj_str_t str_to = { "To", 2 }; + pjsip_fromto_hdr *target; + pjsip_sip_uri *target_uri; + RAII_VAR(struct refer_progress *, progress, NULL, ao2_cleanup); + const pj_str_t str_replaces = { "Replaces", 8 }; + pjsip_param *replaces; + int response; + + /* A Refer-To header is required */ + if (!(refer_to = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL))) { + pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL); + ast_debug(3, "Received a REFER without Refer-To on channel '%s' from endpoint '%s'\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 0; + } + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; + + /* Parse the provided URI string as a To header so we can get the target */ + if (!(target = pjsip_parse_hdr(rdata->tp_info.pool, &str_to, refer_to->hvalue.ptr, refer_to->hvalue.slen, NULL)) || + (!PJSIP_URI_SCHEME_IS_SIP(target->uri) && !PJSIP_URI_SCHEME_IS_SIPS(target->uri))) { + pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL); + ast_debug(3, "Received a REFER without a parseable Refer-To ('%s') on channel '%s' from endpoint '%s'\n", + uri, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 0; + } + target_uri = pjsip_uri_get_uri(target->uri); + + /* Set up REFER progress subscription if requested/possible */ + if (refer_progress_alloc(session, rdata, &progress)) { + pjsip_dlg_respond(session->inv_session->dlg, rdata, 500, NULL, NULL, NULL); + ast_debug(3, "Could not set up subscription for REFER on channel '%s' from endpoint '%s'\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 0; + } + + /* Determine if this is an attended or blind transfer */ + if ((replaces = pjsip_param_find(&target_uri->header_param, &str_replaces)) || + (replaces = pjsip_param_find(&target_uri->other_param, &str_replaces))) { + response = refer_incoming_attended_request(session, rdata, target_uri, replaces, progress); + } else { + response = refer_incoming_blind_request(session, rdata, target_uri, progress); + } + + if (!progress) { + /* The transferer has requested no subscription, so send a final response immediately */ + pjsip_tx_data *tdata; + const pj_str_t str_refer_sub = { "Refer-Sub", 9 }; + const pj_str_t str_false = { "false", 5 }; + pjsip_hdr *hdr; + + ast_debug(3, "Progress monitoring not requested for REFER on channel '%s' from endpoint '%s', sending immediate response of '%d'\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), response); + + if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, response, NULL, &tdata) != PJ_SUCCESS) { + pjsip_dlg_respond(session->inv_session->dlg, rdata, response, NULL, NULL, NULL); + return 0; + } + + hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub, &str_false); + pjsip_msg_add_hdr(tdata->msg, hdr); + + pjsip_dlg_send_response(session->inv_session->dlg, pjsip_rdata_get_tsx(rdata), tdata); + } else if (response != 200) { + /* Since this failed we can send a final NOTIFY now and terminate the subscription */ + struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, response, PJSIP_EVSUB_STATE_TERMINATED); + + if (notification) { + /* The refer_progress_notify function will call ao2_cleanup on this for us */ + refer_progress_notify(notification); + } + } + + return 0; +} + +static int refer_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_refer_method())) { + return refer_incoming_refer_request(session, rdata); + } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_invite_method)) { + return refer_incoming_invite_request(session, rdata); + } else { + return 0; + } +} + +static void refer_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata) +{ + const char *replaces; + + if (pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_invite_method) || + !session->channel || + (session->inv_session->state != PJSIP_INV_STATE_CALLING) || + !(replaces = pbx_builtin_getvar_helper(session->channel, "SIPREPLACESHDR"))) { + return; + } + + ast_sip_add_header(tdata, "Replaces", replaces); +} + +static struct ast_sip_session_supplement refer_supplement = { + .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL + 1, + .incoming_request = refer_incoming_request, + .outgoing_request = refer_outgoing_request, +}; + +static int load_module(void) +{ + const pj_str_t str_norefersub = { "norefersub", 10 }; + + pjsip_replaces_init_module(ast_sip_get_pjsip_endpoint()); + pjsip_xfer_init_module(ast_sip_get_pjsip_endpoint()); + pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub); + + ast_sip_register_service(&refer_progress_module); + ast_sip_session_register_supplement(&refer_supplement); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_session_unregister_supplement(&refer_supplement); + ast_sip_unregister_service(&refer_progress_module); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Blind and Attended Transfer Support", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_sip_registrar.c b/res/res_sip_registrar.c index c3315d0be..7661f8d93 100644 --- a/res/res_sip_registrar.c +++ b/res/res_sip_registrar.c @@ -90,12 +90,12 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co } while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) { - int expiration; + int expiration = registrar_get_expiration(aor, contact, rdata); RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup); if (contact->star) { /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */ - if ((contact->expires != 0) || previous) { + if ((expiration != 0) || previous) { pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } @@ -111,7 +111,6 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co } details.uri = pjsip_uri_get_uri(contact->uri); - expiration = registrar_get_expiration(aor, contact, rdata); /* Determine if this is an add, update, or delete for policy enforcement purposes */ if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) { @@ -199,11 +198,13 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) if (ast_strlen_zero(endpoint->aors)) { /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors"); return PJ_TRUE; } if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) { pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received"); return PJ_TRUE; } @@ -238,12 +239,14 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) { /* The provided AOR name was not found (be it within the configuration or sorcery itself) */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_requested_aor_not_found"); return PJ_TRUE; } if (!aor->max_contacts) { /* Registration is not permitted for this AOR */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_registration_permitted"); return PJ_TRUE; } @@ -256,12 +259,14 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) { /* The provided Contact headers do not conform to the specification */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_contacts_provided"); return PJ_TRUE; } if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) { /* Enforce the maximum number of contacts */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts"); return PJ_TRUE; } @@ -304,8 +309,9 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) contact_uri, aor_name, expiration); } else if (expiration) { RAII_VAR(struct ast_sip_contact *, updated, ast_sorcery_copy(ast_sip_get_sorcery(), contact), ao2_cleanup); - updated->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)); + updated->qualify_frequency = aor->qualify_frequency; + updated->authenticate_qualify = aor->authenticate_qualify; ast_sip_location_update_contact(updated); ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n", diff --git a/res/res_sip_registrar_expire.c b/res/res_sip_registrar_expire.c new file mode 100644 index 000000000..bbfa7e118 --- /dev/null +++ b/res/res_sip_registrar_expire.c @@ -0,0 +1,227 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> + +#include "asterisk/res_sip.h" +#include "asterisk/module.h" +#include "asterisk/sched.h" + +#define CONTACT_AUTOEXPIRE_BUCKETS 977 + +static struct ao2_container *contact_autoexpire; + +/*! \brief Scheduler used for automatically expiring contacts */ +static struct ast_sched_context *sched; + +/*! \brief Structure used for contact auto-expiration */ +struct contact_expiration { + /*! \brief Contact that is being auto-expired */ + struct ast_sip_contact *contact; + + /*! \brief Scheduled item for performing expiration */ + int sched; +}; + +/*! \brief Destructor function for contact auto-expiration */ +static void contact_expiration_destroy(void *obj) +{ + struct contact_expiration *expiration = obj; + + ao2_cleanup(expiration->contact); +} + +/*! \brief Hashing function for contact auto-expiration */ +static int contact_expiration_hash(const void *obj, const int flags) +{ + const struct contact_expiration *expiration = obj; + const char *id = obj; + + return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(expiration->contact)); +} + +/*! \brief Comparison function for contact auto-expiration */ +static int contact_expiration_cmp(void *obj, void *arg, int flags) +{ + struct contact_expiration *expiration1 = obj, *expiration2 = arg; + const char *id = arg; + + return !strcmp(ast_sorcery_object_get_id(expiration1->contact), flags & OBJ_KEY ? id : + ast_sorcery_object_get_id(expiration2->contact)) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Scheduler function which deletes a contact */ +static int contact_expiration_expire(const void *data) +{ + RAII_VAR(struct contact_expiration *, expiration, (void*)data, ao2_cleanup); + + expiration->sched = -1; + + /* This will end up invoking the deleted observer callback, which will perform the unlinking and such */ + ast_sorcery_delete(ast_sip_get_sorcery(), expiration->contact); + + return 0; +} + +/*! \brief Observer callback for when a contact is created */ +static void contact_expiration_observer_created(const void *object) +{ + const struct ast_sip_contact *contact = object; + RAII_VAR(struct contact_expiration *, expiration, NULL, ao2_cleanup); + int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow())); + + if (ast_tvzero(contact->expiration_time)) { + return; + } + + if (!(expiration = ao2_alloc_options(sizeof(*expiration), contact_expiration_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK))) { + return; + } + + expiration->contact = (struct ast_sip_contact*)contact; + ao2_ref(expiration->contact, +1); + + ao2_ref(expiration, +1); + if ((expiration->sched = ast_sched_add(sched, expires, contact_expiration_expire, expiration)) < 0) { + ao2_cleanup(expiration); + ast_log(LOG_ERROR, "Scheduled expiration for contact '%s' could not be performed, contact may persist past life\n", + ast_sorcery_object_get_id(contact)); + return; + } + + ao2_link(contact_autoexpire, expiration); +} + +/*! \brief Observer callback for when a contact is updated */ +static void contact_expiration_observer_updated(const void *object) +{ + const struct ast_sip_contact *contact = object; + RAII_VAR(struct contact_expiration *, expiration, ao2_find(contact_autoexpire, ast_sorcery_object_get_id(contact), OBJ_KEY), ao2_cleanup); + int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow())); + + if (!expiration) { + return; + } + + AST_SCHED_REPLACE_UNREF(expiration->sched, sched, expires, contact_expiration_expire, expiration, ao2_cleanup(expiration), ao2_cleanup(expiration), ao2_ref(expiration, +1)); +} + +/*! \brief Observer callback for when a contact is deleted */ +static void contact_expiration_observer_deleted(const void *object) +{ + RAII_VAR(struct contact_expiration *, expiration, ao2_find(contact_autoexpire, ast_sorcery_object_get_id(object), OBJ_KEY | OBJ_UNLINK), ao2_cleanup); + + if (!expiration) { + return; + } + + AST_SCHED_DEL_UNREF(sched, expiration->sched, ao2_cleanup(expiration)); +} + +/*! \brief Observer callbacks for autoexpiring contacts */ +static struct ast_sorcery_observer contact_expiration_observer = { + .created = contact_expiration_observer_created, + .updated = contact_expiration_observer_updated, + .deleted = contact_expiration_observer_deleted, +}; + +/*! \brief Callback function which deletes a contact if it has expired or sets up auto-expiry */ +static int contact_expiration_setup(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow())); + + if (!expires) { + ast_sorcery_delete(ast_sip_get_sorcery(), contact); + } else { + contact_expiration_observer_created(contact); + } + + return 0; +} + +/*! \brief Initialize auto-expiration of any existing contacts */ +static void contact_expiration_initialize_existing(void) +{ + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!(contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) { + return; + } + + ao2_callback(contacts, OBJ_NODATA, contact_expiration_setup, NULL); +} + +static int load_module(void) +{ + if (!(contact_autoexpire = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, CONTACT_AUTOEXPIRE_BUCKETS, + contact_expiration_hash, contact_expiration_cmp))) { + ast_log(LOG_ERROR, "Could not create container for contact auto-expiration\n"); + return AST_MODULE_LOAD_FAILURE; + } + + if (!(sched = ast_sched_context_create())) { + ast_log(LOG_ERROR, "Could not create scheduler for contact auto-expiration\n"); + goto error; + } + + if (ast_sched_start_thread(sched)) { + ast_log(LOG_ERROR, "Could not start scheduler thread for contact auto-expiration\n"); + goto error; + } + + contact_expiration_initialize_existing(); + + if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_expiration_observer)) { + ast_log(LOG_ERROR, "Could not add observer for notifications about contacts for contact auto-expiration\n"); + goto error; + } + + return AST_MODULE_LOAD_SUCCESS; + +error: + if (sched) { + ast_sched_context_destroy(sched); + } + + ao2_cleanup(contact_autoexpire); + return AST_MODULE_LOAD_FAILURE; +} + +static int unload_module(void) +{ + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_expiration_observer); + ast_sched_context_destroy(sched); + ao2_cleanup(contact_autoexpire); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Contact Auto-Expiration", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_sip_sdp_rtp.c b/res/res_sip_sdp_rtp.c index b0c8ae31c..bc150ed4a 100644 --- a/res/res_sip_sdp_rtp.c +++ b/res/res_sip_sdp_rtp.c @@ -47,6 +47,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/causes.h" #include "asterisk/sched.h" #include "asterisk/acl.h" +#include "asterisk/sdp_srtp.h" #include "asterisk/res_sip.h" #include "asterisk/res_sip_session.h" @@ -117,6 +118,10 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp), session_media->rtp, &session->endpoint->prefs); + if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) { + ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND); + } + if (!session->endpoint->ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) { ice->stop(session_media->rtp); } @@ -289,6 +294,18 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format * return attr; } +static int codec_pref_has_type(struct ast_codec_pref *prefs, enum ast_format_type media_type) +{ + int i; + struct ast_format fmt; + for (i = 0; ast_codec_pref_index(prefs, i, &fmt); ++i) { + if (AST_FORMAT_GET_TYPE(fmt.id) == media_type) { + return 1; + } + } + return 0; +} + /*! \brief Function which adds ICE attributes to a media stream */ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) { @@ -460,6 +477,101 @@ static void apply_packetization(struct ast_sip_session *session, struct ast_sip_ session_media->rtp, pref); } +/*! \brief figure out media transport encryption type from the media transport string */ +static enum ast_sip_session_media_encryption get_media_encryption_type(pj_str_t transport) +{ + RAII_VAR(char *, transport_str, ast_strndup(transport.ptr, transport.slen), ast_free); + if (strstr(transport_str, "UDP/TLS")) { + return AST_SIP_MEDIA_ENCRYPT_DTLS; + } else if (strstr(transport_str, "SAVP")) { + return AST_SIP_MEDIA_ENCRYPT_SDES; + } else { + return AST_SIP_MEDIA_ENCRYPT_NONE; + } +} + +/*! + * \brief Checks whether the encryption offered in SDP is compatible with the endpoint's configuration + * \internal + * + * \param endpoint_encryption Media encryption configured for the endpoint + * \param stream pjmedia_sdp_media stream description + * + * \retval AST_SIP_MEDIA_TRANSPORT_INVALID on encryption mismatch + * \retval The encryption requested in the SDP + */ +static enum ast_sip_session_media_encryption check_endpoint_media_transport( + struct ast_sip_endpoint *endpoint, + const struct pjmedia_sdp_media *stream) +{ + enum ast_sip_session_media_encryption incoming_encryption; + + if (endpoint->use_avpf) { + char transport_end = stream->desc.transport.ptr[stream->desc.transport.slen - 1]; + if (transport_end != 'F') { + return AST_SIP_MEDIA_TRANSPORT_INVALID; + } + } + + incoming_encryption = get_media_encryption_type(stream->desc.transport); + if (incoming_encryption == AST_SIP_MEDIA_ENCRYPT_DTLS) { + /* DTLS not yet supported */ + return AST_SIP_MEDIA_TRANSPORT_INVALID; + } + + if (incoming_encryption == endpoint->media_encryption) { + return incoming_encryption; + } + + return AST_SIP_MEDIA_TRANSPORT_INVALID; +} + +static int setup_sdes_srtp(struct ast_sip_session_media *session_media, + const struct pjmedia_sdp_media *stream) +{ + int i; + + for (i = 0; i < stream->attr_count; i++) { + pjmedia_sdp_attr *attr; + RAII_VAR(char *, crypto_str, NULL, ast_free); + + /* check the stream for the required crypto attribute */ + attr = stream->attr[i]; + if (pj_strcmp2(&attr->name, "crypto")) { + continue; + } + + crypto_str = ast_strndup(attr->value.ptr, attr->value.slen); + if (!crypto_str) { + return -1; + } + + if (!session_media->srtp) { + session_media->srtp = ast_sdp_srtp_alloc(); + if (!session_media->srtp) { + return -1; + } + } + + if (!session_media->srtp->crypto) { + session_media->srtp->crypto = ast_sdp_crypto_alloc(); + if (!session_media->srtp->crypto) { + return -1; + } + } + + if (!ast_sdp_crypto_process(session_media->rtp, session_media->srtp, crypto_str)) { + /* found a valid crypto attribute */ + return 0; + } + + ast_debug(1, "Ignoring crypto offer with unsupported parameters: %s\n", crypto_str); + } + + /* no usable crypto attributes found */ + return -1; +} + /*! \brief Function which negotiates an incoming media stream */ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream) @@ -467,12 +579,19 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct char host[NI_MAXHOST]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr); enum ast_format_type media_type = stream_to_media_type(session_media->stream_type); + enum ast_sip_session_media_encryption incoming_encryption; /* If no type formats have been configured reject this stream */ if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) { return 0; } + /* Ensure incoming transport is compatible with the endpoint's configuration */ + incoming_encryption = check_endpoint_media_transport(session->endpoint, stream); + if (incoming_encryption == AST_SIP_MEDIA_TRANSPORT_INVALID) { + return -1; + } + ast_copy_pj_str(host, stream->conn ? &stream->conn->addr : &sdp->conn->addr, sizeof(host)); /* Ensure that the address provided is valid */ @@ -486,9 +605,42 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct return -1; } + if (incoming_encryption == AST_SIP_MEDIA_ENCRYPT_SDES + && setup_sdes_srtp(session_media, stream)) { + return -1; + } + return set_caps(session, session_media, stream); } +static int add_crypto_to_stream(struct ast_sip_session *session, + struct ast_sip_session_media *session_media, + pj_pool_t *pool, pjmedia_sdp_media *media) +{ + pj_str_t stmp; + pjmedia_sdp_attr *attr; + const char *crypto_attribute; + + if (!session_media->srtp && session->endpoint->media_encryption != AST_SIP_MEDIA_ENCRYPT_NONE) { + session_media->srtp = ast_sdp_srtp_alloc(); + if (!session_media->srtp) { + return -1; + } + } + + crypto_attribute = ast_sdp_srtp_get_attrib(session_media->srtp, + 0 /* DTLS can not be enabled for res_sip */, + 0 /* don't prefer 32byte tag length */); + if (!crypto_attribute) { + /* No crypto attribute to add */ + return -1; + } + + attr = pjmedia_sdp_attr_create(pool, "crypto", pj_cstr(&stmp, crypto_attribute)); + media->attr[media->attr_count++] = attr; + return 0; +} + /*! \brief Function which creates an outgoing stream */ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp) @@ -497,7 +649,6 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as static const pj_str_t STR_IN = { "IN", 2 }; static const pj_str_t STR_IP4 = { "IP4", 3}; static const pj_str_t STR_IP6 = { "IP6", 3}; - static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; pjmedia_sdp_media *media; char hostip[PJ_INET6_ADDRSTRLEN+2]; @@ -508,14 +659,19 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as int index = 0, min_packet_size = 0, noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733) ? AST_RTP_DTMF : 0; int rtp_code; struct ast_format format; - struct ast_format compat_format; RAII_VAR(struct ast_format_cap *, caps, NULL, ast_format_cap_destroy); enum ast_format_type media_type = stream_to_media_type(session_media->stream_type); + int crypto_res; int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) && !ast_format_cap_is_empty(session->direct_media_cap); - if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) { + int use_override_prefs = session->override_prefs.formats[0].id; + struct ast_codec_pref *prefs = use_override_prefs ? + &session->override_prefs : &session->endpoint->prefs; + + if ((use_override_prefs && !codec_pref_has_type(&session->override_prefs, media_type)) || + (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->codecs, media_type))) { /* If no type formats are configured don't add a stream */ return 0; } else if (!session_media->rtp && create_rtp(session, session_media, session->endpoint->rtp_ipv6)) { @@ -527,9 +683,11 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as return -1; } - /* TODO: This should eventually support SRTP */ + crypto_res = add_crypto_to_stream(session, session_media, pool, media); + media->desc.media = pj_str(session_media->stream_type); - media->desc.transport = STR_RTP_AVP; + media->desc.transport = pj_str(ast_sdp_get_rtp_profile( + !crypto_res, session_media->rtp, session->endpoint->use_avpf)); /* Add connection level details */ if (direct_media_enabled) { @@ -565,36 +723,36 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as } else if (ast_format_cap_is_empty(session->req_caps) || !ast_format_cap_has_joint(session->req_caps, session->endpoint->codecs)) { ast_format_cap_copy(caps, session->endpoint->codecs); } else { - ast_format_cap_joint_copy(session->endpoint->codecs, session->req_caps, caps); + ast_format_cap_copy(caps, session->req_caps); } - for (index = 0; ast_codec_pref_index(&session->endpoint->prefs, index, &format); ++index) { + for (index = 0; ast_codec_pref_index(prefs, index, &format); ++index) { struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref; if (AST_FORMAT_GET_TYPE(format.id) != media_type) { continue; } - if (!ast_format_cap_get_compatible_format(caps, &format, &compat_format)) { + if (!use_override_prefs && !ast_format_cap_get_compatible_format(caps, &format, &format)) { continue; } - if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, &compat_format, 0)) == -1) { + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, &format, 0)) == -1) { return -1; } - if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 1, &compat_format, 0))) { + if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 1, &format, 0))) { continue; } media->attr[media->attr_count++] = attr; - if ((attr = generate_fmtp_attr(pool, &compat_format, rtp_code))) { + if ((attr = generate_fmtp_attr(pool, &format, rtp_code))) { media->attr[media->attr_count++] = attr; } if (pref && media_type != AST_FORMAT_TYPE_VIDEO) { - struct ast_format_list fmt = ast_codec_pref_getsize(pref, &compat_format); + struct ast_format_list fmt = ast_codec_pref_getsize(pref, &format); if (fmt.cur_ms && ((fmt.cur_ms < min_packet_size) || !min_packet_size)) { min_packet_size = fmt.cur_ms; } @@ -768,9 +926,9 @@ static int video_info_incoming_request(struct ast_sip_session *session, struct p struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); pjsip_tx_data *tdata; - if (pj_strcmp2(&rdata->msg_info.msg->body->content_type.type, "application") || - pj_strcmp2(&rdata->msg_info.msg->body->content_type.subtype, "media_control+xml")) { - + if (!ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type, + "application", + "media_control+xml")) { return 0; } diff --git a/res/res_sip_session.c b/res/res_sip_session.c index 7be75ab1d..9668b73e9 100644 --- a/res/res_sip_session.c +++ b/res/res_sip_session.c @@ -40,6 +40,8 @@ #include "asterisk/pbx.h" #include "asterisk/taskprocessor.h" #include "asterisk/causes.h" +#include "asterisk/sdp_srtp.h" +#include "asterisk/dsp.h" #define SDP_HANDLER_BUCKETS 11 @@ -335,10 +337,33 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd char media[20]; struct ast_sip_session_sdp_handler *handler; RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); /* We need a null-terminated version of the media string */ ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media)); + session_media = ao2_find(session->media, media, OBJ_KEY); + if (!session_media) { + /* if the session_media doesn't exist, there weren't + * any handlers at the time of its creation */ + continue; + } + + if (session_media->handler) { + int res; + handler = session_media->handler; + res = handler->negotiate_incoming_sdp_stream( + session, session_media, sdp, sdp->media[i]); + if (res <= 0) { + /* Catastrophic failure or ignored by assigned handler. Abort! */ + return -1; + } + if (res > 0) { + /* Handled by this handler. Move to the next stream */ + continue; + } + } + handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); if (!handler_list) { ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); @@ -346,9 +371,7 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd } AST_LIST_TRAVERSE(&handler_list->list, handler, next) { int res; - RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); - session_media = ao2_find(session->media, handler_list->stream_type, OBJ_KEY); - if (!session_media || session_media->handler) { + if (session_media->handler) { /* There is only one slot for this stream type and it has already been claimed * so it will go unhandled */ break; @@ -710,7 +733,7 @@ int ast_sip_session_refresh(struct ast_sip_session *session, return 0; } - if (inv_session->invite_tsx) { + if ((method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) && inv_session->invite_tsx) { /* We can't send a reinvite yet, so delay it */ ast_debug(3, "Delaying sending reinvite to %s because of outstanding transaction...\n", ast_sorcery_object_get_id(session->endpoint)); @@ -787,6 +810,23 @@ void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data ast_sip_session_send_request_with_cb(session, tdata, NULL); } +int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data **tdata) +{ + pjmedia_sdp_session *offer; + + if (!(offer = create_local_sdp(session->inv_session, session, NULL))) { + pjsip_inv_terminate(session->inv_session, 500, PJ_FALSE); + return -1; + } + + pjsip_inv_set_local_sdp(session->inv_session, offer); + pjmedia_sdp_neg_set_prefer_remote_codec_order(session->inv_session->neg, PJ_FALSE); + if (pjsip_inv_invite(session->inv_session, tdata) != PJ_SUCCESS) { + return -1; + } + return 0; +} + /*! * \brief Called when the PJSIP core loads us * @@ -859,6 +899,9 @@ static void session_media_dtor(void *obj) if (session_media->handler) { session_media->handler->stream_destroy(session_media); } + if (session_media->srtp) { + ast_sdp_srtp_destroy(session_media->srtp); + } } static void session_destructor(void *obj) @@ -888,6 +931,10 @@ static void session_destructor(void *obj) ao2_cleanup(session->endpoint); ast_format_cap_destroy(session->req_caps); + if (session->dsp) { + ast_dsp_free(session->dsp); + } + if (session->inv_session) { pjsip_dlg_dec_session(session->inv_session->dlg, &session_module); } @@ -956,6 +1003,14 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, session->inv_session = inv_session; session->req_caps = ast_format_cap_alloc_nolock(); + if (endpoint->dtmf == AST_SIP_DTMF_INBAND) { + if (!(session->dsp = ast_dsp_new())) { + return NULL; + } + + ast_dsp_set_features(session->dsp, DSP_FEATURE_DIGIT_DETECT); + } + if (add_supplements(session)) { return NULL; } @@ -991,7 +1046,6 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint pjsip_dialog *dlg; struct pjsip_inv_session *inv_session; RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); - pjmedia_sdp_session *offer; /* If no location has been provided use the AOR list from the endpoint itself */ location = S_OR(location, endpoint->aors); @@ -1033,16 +1087,68 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint } ast_format_cap_copy(session->req_caps, req_caps); - if ((pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) || - !(offer = create_local_sdp(inv_session, session, NULL))) { + if ((pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS)) { pjsip_inv_terminate(inv_session, 500, PJ_FALSE); return NULL; } - pjsip_inv_set_local_sdp(inv_session, offer); - pjmedia_sdp_neg_set_prefer_remote_codec_order(inv_session->neg, PJ_FALSE); + ao2_ref(session, +1); + return session; +} + +static int session_termination_task(void *data) +{ + RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup); + pjsip_tx_data *packet = NULL; + + if (!session->inv_session) { + return 0; + } + + if (pjsip_inv_end_session(session->inv_session, 603, NULL, &packet) == PJ_SUCCESS) { + ast_sip_session_send_request(session, packet); + } + + return 0; +} + +static void session_termination_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + struct ast_sip_session *session = entry->user_data; + + if (ast_sip_push_task(session->serializer, session_termination_task, session)) { + ao2_cleanup(session); + } +} + +void ast_sip_session_defer_termination(struct ast_sip_session *session) +{ + pj_time_val delay = { .sec = 60, }; + + session->defer_terminate = 1; + + session->scheduled_termination.id = 0; + ao2_ref(session, +1); + session->scheduled_termination.user_data = session; + session->scheduled_termination.cb = session_termination_cb; + + if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &session->scheduled_termination, &delay) != PJ_SUCCESS) { + ao2_ref(session, -1); + } +} + +struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg) +{ + pjsip_inv_session *inv_session = pjsip_dlg_get_inv_session(dlg); + struct ast_sip_session *session; + + if (!inv_session || + !(session = inv_session->mod_data[session_module.id])) { + return NULL; + } ao2_ref(session, +1); + return session; } @@ -1102,11 +1208,11 @@ static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct a return NULL; } if (pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg) != PJ_SUCCESS) { - pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); - return NULL; + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + return NULL; } if (pjsip_inv_create_uas(dlg, rdata, NULL, 0, &inv_session) != PJ_SUCCESS) { - pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); pjsip_dlg_terminate(dlg); return NULL; } @@ -1223,7 +1329,20 @@ static void handle_new_invite_request(pjsip_rx_data *rdata) handle_incoming_request(session, rdata); } -static int has_supplement(struct ast_sip_session *session, pjsip_rx_data *rdata) +static pj_bool_t does_method_match(const pj_str_t *message_method, const char *supplement_method) +{ + pj_str_t method; + + if (ast_strlen_zero(supplement_method)) { + return PJ_TRUE; + } + + pj_cstr(&method, supplement_method); + + return pj_stristr(&method, message_method) ? PJ_TRUE : PJ_FALSE; +} + +static pj_bool_t has_supplement(const struct ast_sip_session *session, const pjsip_rx_data *rdata) { struct ast_sip_session_supplement *supplement; struct pjsip_method *method = &rdata->msg_info.msg->line.req.method; @@ -1233,7 +1352,7 @@ static int has_supplement(struct ast_sip_session *session, pjsip_rx_data *rdata) } AST_LIST_TRAVERSE(&session->supplements, supplement, next) { - if (!supplement->method || !pj_strcmp2(&method->name, supplement->method)) { + if (does_method_match(&method->name, supplement->method)) { return PJ_TRUE; } } @@ -1383,9 +1502,10 @@ static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_da ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); AST_LIST_TRAVERSE(&session->supplements, supplement, next) { - if (supplement->incoming_request && ( - !supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) { - supplement->incoming_request(session, rdata); + if (supplement->incoming_request && does_method_match(&req.method.name, supplement->method)) { + if (supplement->incoming_request(session, rdata)) { + break; + } } } } @@ -1399,8 +1519,7 @@ static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_d pj_strbuf(&status.reason)); AST_LIST_TRAVERSE(&session->supplements, supplement, next) { - if (supplement->incoming_response && ( - !supplement->method || !pj_strcmp2(&rdata->msg_info.cseq->method.name, supplement->method))) { + if (supplement->incoming_response && does_method_match(&rdata->msg_info.cseq->method.name, supplement->method)) { supplement->incoming_response(session, rdata); } } @@ -1427,8 +1546,7 @@ static void handle_outgoing_request(struct ast_sip_session *session, pjsip_tx_da ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); AST_LIST_TRAVERSE(&session->supplements, supplement, next) { - if (supplement->outgoing_request && - (!supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) { + if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) { supplement->outgoing_request(session, tdata); } } @@ -1438,15 +1556,13 @@ static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_d { struct ast_sip_session_supplement *supplement; struct pjsip_status_line status = tdata->msg->line.status; - ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason), - pj_strbuf(&status.reason)); + pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + ast_debug(3, "Method is %.*s, Response is %d %.*s\n", (int) pj_strlen(&cseq->method.name), + pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason), + pj_strbuf(&status.reason)); AST_LIST_TRAVERSE(&session->supplements, supplement, next) { - /* XXX Not sure how to get the method from a response. - * For now, just call supplements on all responses, no - * matter the method. This is less than ideal - */ - if (supplement->outgoing_response) { + if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) { supplement->outgoing_response(session, tdata); } } @@ -1467,6 +1583,11 @@ static int session_end(struct ast_sip_session *session) { struct ast_sip_session_supplement *iter; + /* Stop the scheduled termination */ + if (pj_timer_heap_cancel(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()), &session->scheduled_termination)) { + ao2_ref(session, -1); + } + /* Session is dead. Let's get rid of the reference to the session */ AST_LIST_TRAVERSE(&session->supplements, iter, next) { if (iter->session_end) { @@ -1714,8 +1835,17 @@ static void session_inv_on_media_update(pjsip_inv_session *inv, pj_status_t stat static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e) { - /* XXX STUB */ - return PJSIP_REDIRECT_REJECT; + struct ast_sip_session *session = inv->mod_data[session_module.id]; + + if (PJSIP_URI_SCHEME_IS_SIP(target) || PJSIP_URI_SCHEME_IS_SIPS(target)) { + const pjsip_sip_uri *uri = pjsip_uri_get_uri(target); + char exten[AST_MAX_EXTENSION]; + + ast_copy_pj_str(exten, &uri->user, sizeof(exten)); + ast_channel_call_forward_set(session->channel, exten); + } + + return PJSIP_REDIRECT_STOP; } static pjsip_inv_callback inv_callback = { diff --git a/res/res_sip_session.exports.in b/res/res_sip_session.exports.in index 08c6f3937..28ed0b239 100644 --- a/res/res_sip_session.exports.in +++ b/res/res_sip_session.exports.in @@ -1,5 +1,6 @@ { global: + LINKER_SYMBOL_PREFIXast_sip_session_defer_termination; LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler; LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler; LINKER_SYMBOL_PREFIXast_sip_session_register_supplement; @@ -12,7 +13,9 @@ LINKER_SYMBOL_PREFIXast_sip_session_refresh; LINKER_SYMBOL_PREFIXast_sip_session_send_response; LINKER_SYMBOL_PREFIXast_sip_session_send_request; + LINKER_SYMBOL_PREFIXast_sip_session_create_invite; LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing; + LINKER_SYMBOL_PREFIXast_sip_dialog_get_session; local: *; }; diff --git a/res/res_sip_transport_websocket.c b/res/res_sip_transport_websocket.c new file mode 100644 index 000000000..e83011cfc --- /dev/null +++ b/res/res_sip_transport_websocket.c @@ -0,0 +1,402 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jason Parker <jparker@digium.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. + */ + +/*! + * \brief WebSocket transport module + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_http_websocket</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_ua.h> + +#include "asterisk/module.h" +#include "asterisk/http_websocket.h" +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_session.h" +#include "asterisk/taskprocessor.h" + +static int transport_type_ws; +static int transport_type_wss; + +/*! + * \brief Wrapper for pjsip_transport, for storing the WebSocket session + */ +struct ws_transport { + pjsip_transport transport; + pjsip_rx_data rdata; + struct ast_websocket *ws_session; +}; + +/*! + * \brief Send a message over the WebSocket connection. + * + * Called by pjsip transport manager. + */ +static pj_status_t ws_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback) +{ + struct ws_transport *wstransport = (struct ws_transport *)transport; + + if (ast_websocket_write(wstransport->ws_session, AST_WEBSOCKET_OPCODE_TEXT, tdata->buf.start, (int)(tdata->buf.cur - tdata->buf.start))) { + return PJ_EUNKNOWN; + } + + return PJ_SUCCESS; +} + +/*! + * \brief Destroy the pjsip transport. + * + * Called by pjsip transport manager. + */ +static pj_status_t ws_destroy(pjsip_transport *transport) +{ + struct ws_transport *wstransport = (struct ws_transport *)transport; + + if (wstransport->transport.ref_cnt) { + pj_atomic_destroy(wstransport->transport.ref_cnt); + } + + if (wstransport->transport.lock) { + pj_lock_destroy(wstransport->transport.lock); + } + + pjsip_endpt_release_pool(wstransport->transport.endpt, wstransport->transport.pool); + + return PJ_SUCCESS; +} + +static int transport_shutdown(void *data) +{ + RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup); + pjsip_transport *transport = data; + + if ((ct = ast_sip_location_retrieve_contact_transport_by_transport(transport))) { + ast_sip_location_delete_contact_transport(ct); + } + + pjsip_transport_shutdown(transport); + return 0; +} + +struct transport_create_data { + struct ws_transport *transport; + struct ast_websocket *ws_session; +}; + +/*! + * \brief Create a pjsip transport. + */ +static int transport_create(void *data) +{ + struct transport_create_data *create_data = data; + struct ws_transport *newtransport; + + pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); + struct pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(endpt); + + pj_pool_t *pool; + + pj_str_t buf; + + if (!(pool = pjsip_endpt_create_pool(endpt, "ws", 512, 512))) { + ast_log(LOG_ERROR, "Failed to allocate WebSocket endpoint pool.\n"); + return -1; + } + + if (!(newtransport = PJ_POOL_ZALLOC_T(pool, struct ws_transport))) { + ast_log(LOG_ERROR, "Failed to allocate WebSocket transport.\n"); + pjsip_endpt_release_pool(endpt, pool); + return -1; + } + + newtransport->ws_session = create_data->ws_session; + + pj_atomic_create(pool, 0, &newtransport->transport.ref_cnt); + pj_lock_create_recursive_mutex(pool, pool->obj_name, &newtransport->transport.lock); + + newtransport->transport.pool = pool; + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, ast_sockaddr_stringify(ast_websocket_remote_address(newtransport->ws_session))), &newtransport->transport.key.rem_addr); + newtransport->transport.key.rem_addr.addr.sa_family = pj_AF_INET(); + newtransport->transport.key.type = ast_websocket_is_secure(newtransport->ws_session) ? transport_type_wss : transport_type_ws; + + newtransport->transport.addr_len = pj_sockaddr_get_len(&newtransport->transport.key.rem_addr); + + pj_sockaddr_cp(&newtransport->transport.local_addr, &newtransport->transport.key.rem_addr); + + newtransport->transport.local_name.host.ptr = (char *)pj_pool_alloc(pool, newtransport->transport.addr_len+4); + pj_sockaddr_print(&newtransport->transport.key.rem_addr, newtransport->transport.local_name.host.ptr, newtransport->transport.addr_len+4, 0); + newtransport->transport.local_name.host.slen = pj_ansi_strlen(newtransport->transport.local_name.host.ptr); + newtransport->transport.local_name.port = pj_sockaddr_get_port(&newtransport->transport.key.rem_addr); + + newtransport->transport.type_name = (char *)pjsip_transport_get_type_name(newtransport->transport.key.type); + newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type); + newtransport->transport.info = (char *)pj_pool_alloc(newtransport->transport.pool, 64); + + newtransport->transport.endpt = endpt; + newtransport->transport.tpmgr = tpmgr; + newtransport->transport.send_msg = &ws_send_msg; + newtransport->transport.destroy = &ws_destroy; + + pjsip_transport_register(newtransport->transport.tpmgr, (pjsip_transport *)newtransport); + + create_data->transport = newtransport; + return 0; +} + +struct transport_read_data { + struct ws_transport *transport; + char *payload; + uint64_t payload_len; +}; + +/*! + * \brief Pass WebSocket data into pjsip transport manager. + */ +static int transport_read(void *data) +{ + struct transport_read_data *read_data = data; + struct ws_transport *newtransport = read_data->transport; + struct ast_websocket *session = newtransport->ws_session; + + pjsip_rx_data *rdata = &newtransport->rdata; + int recvd; + pj_str_t buf; + + rdata->tp_info.pool = newtransport->transport.pool; + rdata->tp_info.transport = &newtransport->transport; + + pj_gettimeofday(&rdata->pkt_info.timestamp); + + pj_memcpy(rdata->pkt_info.packet, read_data->payload, sizeof(rdata->pkt_info.packet)); + rdata->pkt_info.len = read_data->payload_len; + rdata->pkt_info.zero = 0; + + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, ast_sockaddr_stringify(ast_websocket_remote_address(session))), &rdata->pkt_info.src_addr); + rdata->pkt_info.src_addr.addr.sa_family = pj_AF_INET(); + + rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr); + + pj_ansi_strcpy(rdata->pkt_info.src_name, ast_sockaddr_stringify_host(ast_websocket_remote_address(session))); + rdata->pkt_info.src_port = ast_sockaddr_port(ast_websocket_remote_address(session)); + + recvd = pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, rdata); + + return (read_data->payload_len == recvd) ? 0 : -1; +} + +/*! + \brief WebSocket connection handler. + */ +static void websocket_cb(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers) +{ + struct ast_taskprocessor *serializer = NULL; + struct transport_create_data create_data; + struct ws_transport *transport = NULL; + + if (ast_websocket_set_nonblock(session)) { + ast_websocket_unref(session); + return; + } + + if (!(serializer = ast_sip_create_serializer())) { + ast_websocket_unref(session); + return; + } + + create_data.ws_session = session; + + if (ast_sip_push_task_synchronous(serializer, transport_create, &create_data)) { + ast_log(LOG_ERROR, "Could not create WebSocket transport.\n"); + ast_websocket_unref(session); + return; + } + + transport = create_data.transport; + + while (ast_wait_for_input(ast_websocket_fd(session), -1) > 0) { + struct transport_read_data read_data; + enum ast_websocket_opcode opcode; + int fragmented; + + if (ast_websocket_read(session, &read_data.payload, &read_data.payload_len, &opcode, &fragmented)) { + break; + } + + if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) { + read_data.transport = transport; + + ast_sip_push_task(serializer, transport_read, &read_data); + } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) { + break; + } + } + + ast_sip_push_task_synchronous(serializer, transport_shutdown, transport); + + ast_taskprocessor_unreference(serializer); + ast_websocket_unref(session); +} + +/*! + * \brief Session supplement handler for avoiding DNS lookup on bogus address. + */ +static void websocket_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata) +{ + char contact_uri[PJSIP_MAX_URL_SIZE] = { 0, }; + RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup); + pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_TRANSPORT, }; + + const pjsip_sip_uri *request_uri = pjsip_uri_get_uri(tdata->msg->line.req.uri); + + if (pj_stricmp2(&request_uri->transport_param, "WS") && pj_stricmp2(&request_uri->transport_param, "WSS")) { + return; + } + + pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, contact_uri, sizeof(contact_uri)); + + if (!(ct = ast_sip_location_retrieve_contact_transport_by_uri(contact_uri))) { + return; + } + + selector.u.transport = ct->transport; + + pjsip_tx_data_set_transport(tdata, &selector); + + tdata->dest_info.addr.count = 1; + tdata->dest_info.addr.entry[0].type = ct->transport->key.type; + tdata->dest_info.addr.entry[0].addr = ct->transport->key.rem_addr; + tdata->dest_info.addr.entry[0].addr_len = ct->transport->addr_len; +} + +static struct ast_sip_session_supplement websocket_supplement = { + .outgoing_request = websocket_outgoing_request, +}; + +/*! + * \brief Destructor for ast_sip_contact_transport + */ +static void contact_transport_destroy(void *obj) +{ + struct ast_sip_contact_transport *ct = obj; + + ast_string_field_free_memory(ct); +} + +static void *contact_transport_alloc(void) +{ + struct ast_sip_contact_transport *ct = ao2_alloc(sizeof(*ct), contact_transport_destroy); + + if (!ct) { + return NULL; + } + + if (ast_string_field_init(ct, 256)) { + ao2_cleanup(ct); + return NULL; + } + + return ct; +} + +/*! + * \brief Store the transport a message came in on, so it can be used for outbound messages to that contact. + */ +static pj_bool_t websocket_on_rx_msg(pjsip_rx_data *rdata) +{ + pjsip_contact_hdr *contact_hdr = NULL; + + long type = rdata->tp_info.transport->key.type; + + if (type != (long)transport_type_ws && type != (long)transport_type_wss) { + return PJ_FALSE; + } + + if ((contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL))) { + RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup); + char contact_uri[PJSIP_MAX_URL_SIZE]; + + pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, pjsip_uri_get_uri(contact_hdr->uri), contact_uri, sizeof(contact_uri)); + + if (!(ct = ast_sip_location_retrieve_contact_transport_by_uri(contact_uri))) { + if (!(ct = contact_transport_alloc())) { + return PJ_FALSE; + } + + ast_string_field_set(ct, uri, contact_uri); + ct->transport = rdata->tp_info.transport; + + ast_sip_location_add_contact_transport(ct); + } + } + + return PJ_FALSE; +} + +static pjsip_module websocket_module = { + .name = { "WebSocket Transport Module", 26 }, + .id = -1, + .priority = PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, + .on_rx_request = websocket_on_rx_msg, +}; + +static int load_module(void) +{ + pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WS", 5060, &transport_type_ws); + pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WSS", 5060, &transport_type_wss); + + if (ast_sip_register_service(&websocket_module) != PJ_SUCCESS) { + return AST_MODULE_LOAD_DECLINE; + } + + ast_sip_session_register_supplement(&websocket_supplement); + + if (ast_websocket_add_protocol("sip", websocket_cb)) { + ast_sip_unregister_service(&websocket_module); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_unregister_service(&websocket_module); + ast_sip_session_unregister_supplement(&websocket_supplement); + ast_websocket_remove_protocol("sip", websocket_cb); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP WebSocket Transport Support", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); |