summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/chan_gulp.c461
-rw-r--r--channels/chan_sip.c136
-rw-r--r--channels/sip/include/sdp_crypto.h85
-rw-r--r--channels/sip/include/sip.h6
-rw-r--r--channels/sip/include/srtp.h59
-rw-r--r--channels/sip/srtp.c55
-rw-r--r--configs/res_sip.conf.sample2
-rw-r--r--include/asterisk/res_sip.h178
-rw-r--r--include/asterisk/res_sip_exten_state.h94
-rw-r--r--include/asterisk/res_sip_pubsub.h17
-rw-r--r--include/asterisk/res_sip_session.h42
-rw-r--r--include/asterisk/sdp_srtp.h125
-rw-r--r--main/pbx.c2
-rw-r--r--main/sdp_srtp.c (renamed from channels/sip/sdp_crypto.c)130
-rw-r--r--res/res_sip.c204
-rw-r--r--res/res_sip.exports.in10
-rw-r--r--res/res_sip/config_transport.c7
-rw-r--r--res/res_sip/include/res_sip_private.h8
-rw-r--r--res/res_sip/location.c66
-rw-r--r--res/res_sip/security_events.c234
-rw-r--r--res/res_sip/sip_configuration.c181
-rw-r--r--res/res_sip/sip_distributor.c14
-rw-r--r--res/res_sip/sip_options.c791
-rw-r--r--res/res_sip_caller_id.c2
-rw-r--r--res/res_sip_diversion.c346
-rw-r--r--res/res_sip_dtmf_info.c3
-rw-r--r--res/res_sip_endpoint_identifier_anonymous.c125
-rw-r--r--res/res_sip_exten_state.c620
-rw-r--r--res/res_sip_exten_state.exports.in7
-rw-r--r--res/res_sip_messaging.c660
-rw-r--r--res/res_sip_one_touch_record_info.c118
-rw-r--r--res/res_sip_outbound_registration.c4
-rw-r--r--res/res_sip_pidf.c341
-rw-r--r--res/res_sip_pubsub.c55
-rw-r--r--res/res_sip_pubsub.exports.in1
-rw-r--r--res/res_sip_refer.c860
-rw-r--r--res/res_sip_registrar.c14
-rw-r--r--res/res_sip_registrar_expire.c227
-rw-r--r--res/res_sip_sdp_rtp.c188
-rw-r--r--res/res_sip_session.c190
-rw-r--r--res/res_sip_session.exports.in3
-rw-r--r--res/res_sip_transport_websocket.c402
42 files changed, 6425 insertions, 648 deletions
diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c
index 9e939a0f4..6a80651cf 100644
--- a/channels/chan_gulp.c
+++ b/channels/chan_gulp.c
@@ -53,6 +53,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/musiconhold.h"
#include "asterisk/causes.h"
#include "asterisk/taskprocessor.h"
+#include "asterisk/dsp.h"
#include "asterisk/stasis_endpoints.h"
#include "asterisk/stasis_channels.h"
@@ -79,6 +80,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Returns a properly formatted dial string for dialing all contacts on an AOR.</para>
</description>
</function>
+ <function name="GULP_MEDIA_OFFER" language="en_US">
+ <synopsis>
+ Media and codec offerings to be set on an outbound SIP channel prior to dialing.
+ </synopsis>
+ <syntax>
+ <parameter name="media" required="true">
+ <para>types of media offered</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Returns the codecs offered based upon the media choice</para>
+ </description>
+ </function>
***/
static const char desc[] = "Gulp SIP Channel";
@@ -128,6 +142,7 @@ static int gulp_answer(struct ast_channel *ast);
static struct ast_frame *gulp_read(struct ast_channel *ast);
static int gulp_write(struct ast_channel *ast, struct ast_frame *f);
static int gulp_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int gulp_transfer(struct ast_channel *ast, const char *target);
static int gulp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static int gulp_devicestate(const char *data);
@@ -147,6 +162,7 @@ static struct ast_channel_tech gulp_tech = {
.write_video = gulp_write,
.exception = gulp_read,
.indicate = gulp_indicate,
+ .transfer = gulp_transfer,
.fixup = gulp_fixup,
.devicestate = gulp_devicestate,
.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
@@ -255,6 +271,105 @@ static struct ast_custom_function gulp_dial_contacts_function = {
.read = gulp_dial_contacts,
};
+static int media_offer_read_av(struct ast_sip_session *session, char *buf,
+ size_t len, enum ast_format_type media_type)
+{
+ int i, size = 0;
+ struct ast_format fmt;
+ const char *name;
+
+ for (i = 0; ast_codec_pref_index(&session->override_prefs, i, &fmt); ++i) {
+ if (AST_FORMAT_GET_TYPE(fmt.id) != media_type) {
+ continue;
+ }
+
+ name = ast_getformatname(&fmt);
+
+ if (ast_strlen_zero(name)) {
+ ast_log(LOG_WARNING, "GULP_MEDIA_OFFER unrecognized format %s\n", name);
+ continue;
+ }
+
+ /* add one since we'll include a comma */
+ size = strlen(name) + 1;
+ len -= size;
+ if ((len) < 0) {
+ break;
+ }
+
+ /* no reason to use strncat here since we have already ensured buf has
+ enough space, so strcat can be safely used */
+ strcat(buf, name);
+ strcat(buf, ",");
+ }
+
+ if (size) {
+ /* remove the extra comma */
+ buf[strlen(buf) - 1] = '\0';
+ }
+ return 0;
+}
+
+struct media_offer_data {
+ struct ast_sip_session *session;
+ enum ast_format_type media_type;
+ const char *value;
+};
+
+static int media_offer_write_av(void *obj)
+{
+ struct media_offer_data *data = obj;
+ int i;
+ struct ast_format fmt;
+ /* remove all of the given media type first */
+ for (i = 0; ast_codec_pref_index(&data->session->override_prefs, i, &fmt); ++i) {
+ if (AST_FORMAT_GET_TYPE(fmt.id) == data->media_type) {
+ ast_codec_pref_remove(&data->session->override_prefs, &fmt);
+ }
+ }
+ ast_format_cap_remove_bytype(data->session->req_caps, data->media_type);
+ ast_parse_allow_disallow(&data->session->override_prefs, data->session->req_caps, data->value, 1);
+
+ return 0;
+}
+
+static int media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+
+ if (!strcmp(data, "audio")) {
+ return media_offer_read_av(pvt->session, buf, len, AST_FORMAT_TYPE_AUDIO);
+ } else if (!strcmp(data, "video")) {
+ return media_offer_read_av(pvt->session, buf, len, AST_FORMAT_TYPE_VIDEO);
+ }
+
+ return 0;
+}
+
+static int media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+ struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+
+ struct media_offer_data mdata = {
+ .session = pvt->session,
+ .value = value
+ };
+
+ if (!strcmp(data, "audio")) {
+ mdata.media_type = AST_FORMAT_TYPE_AUDIO;
+ } else if (!strcmp(data, "video")) {
+ mdata.media_type = AST_FORMAT_TYPE_VIDEO;
+ }
+
+ return ast_sip_push_task_synchronous(pvt->session->serializer, media_offer_write_av, &mdata);
+}
+
+static struct ast_custom_function media_offer_function = {
+ .name = "GULP_MEDIA_OFFER",
+ .read = media_offer_read,
+ .write = media_offer_write
+};
+
/*! \brief Function called by RTP engine to get local audio RTP peer */
static enum ast_rtp_glue_result gulp_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
@@ -402,7 +517,11 @@ static int gulp_set_rtp_peer(struct ast_channel *chan,
if (changed) {
ao2_ref(session, +1);
- ast_sip_push_task(session->serializer, send_direct_media_request, session);
+
+
+ if (ast_sip_push_task(session->serializer, send_direct_media_request, session)) {
+ ao2_cleanup(session);
+ }
}
return 0;
@@ -467,6 +586,12 @@ static struct ast_channel *gulp_new(struct ast_sip_session *session, int state,
ast_channel_exten_set(chan, S_OR(exten, "s"));
ast_channel_priority_set(chan, 1);
+ ast_channel_callgroup_set(chan, session->endpoint->callgroup);
+ ast_channel_pickupgroup_set(chan, session->endpoint->pickupgroup);
+
+ ast_channel_named_callgroups_set(chan, session->endpoint->named_callgroups);
+ ast_channel_named_pickupgroups_set(chan, session->endpoint->named_pickupgroups);
+
ast_endpoint_add_channel(session->endpoint->persistent, chan);
return chan;
@@ -513,6 +638,7 @@ static int gulp_answer(struct ast_channel *ast)
static struct ast_frame *gulp_read(struct ast_channel *ast)
{
struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+ struct ast_sip_session *session = pvt->session;
struct ast_frame *f;
struct ast_sip_session_media *media = NULL;
int rtcp = 0;
@@ -539,14 +665,27 @@ static struct ast_frame *gulp_read(struct ast_channel *ast)
return &ast_null_frame;
}
- f = ast_rtp_instance_read(media->rtp, rtcp);
+ if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {
+ return f;
+ }
+
+ if (f->frametype != AST_FRAME_VOICE) {
+ return f;
+ }
- if (f && f->frametype == AST_FRAME_VOICE) {
- if (!(ast_format_cap_iscompatible(ast_channel_nativeformats(ast), &f->subclass.format))) {
- ast_debug(1, "Oooh, format changed to %s\n", ast_getformatname(&f->subclass.format));
- ast_format_cap_set(ast_channel_nativeformats(ast), &f->subclass.format);
- ast_set_read_format(ast, ast_channel_readformat(ast));
- ast_set_write_format(ast, ast_channel_writeformat(ast));
+ if (!(ast_format_cap_iscompatible(ast_channel_nativeformats(ast), &f->subclass.format))) {
+ ast_debug(1, "Oooh, format changed to %s\n", ast_getformatname(&f->subclass.format));
+ ast_format_cap_set(ast_channel_nativeformats(ast), &f->subclass.format);
+ ast_set_read_format(ast, ast_channel_readformat(ast));
+ ast_set_write_format(ast, ast_channel_writeformat(ast));
+ }
+
+ if (session->dsp) {
+ f = ast_dsp_process(ast, session->dsp, f);
+
+ if (f && (f->frametype == AST_FRAME_DTMF)) {
+ ast_debug(3, "* Detected inband DTMF '%c' on '%s'\n", f->subclass.integer,
+ ast_channel_name(ast));
}
}
@@ -769,7 +908,7 @@ static int transmit_info_with_vidupdate(void *data)
.body_text = xml
};
- struct ast_sip_session *session = data;
+ RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
struct pjsip_tx_data *tdata;
if (ast_sip_create_request("INFO", session->inv_session->dlg, session->endpoint, NULL, &tdata)) {
@@ -785,6 +924,40 @@ static int transmit_info_with_vidupdate(void *data)
return 0;
}
+/*! \brief Update connected line information */
+static int update_connected_line_information(void *data)
+{
+ RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
+
+ if ((ast_channel_state(session->channel) != AST_STATE_UP) && (session->inv_session->role == PJSIP_UAS_ROLE)) {
+ int response_code = 0;
+
+ if (ast_channel_state(session->channel) == AST_STATE_RING) {
+ response_code = !session->endpoint->inband_progress ? 180 : 183;
+ } else if (ast_channel_state(session->channel) == AST_STATE_RINGING) {
+ response_code = 183;
+ }
+
+ if (response_code) {
+ struct pjsip_tx_data *packet = NULL;
+
+ if (pjsip_inv_answer(session->inv_session, response_code, NULL, NULL, &packet) == PJ_SUCCESS) {
+ ast_sip_session_send_response(session, packet);
+ }
+ }
+ } else {
+ enum ast_sip_session_refresh_method method = session->endpoint->connected_line_method;
+
+ if (session->inv_session->invite_tsx && (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE)) {
+ method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE;
+ }
+
+ ast_sip_session_refresh(session, NULL, NULL, method, 0);
+ }
+
+ return 0;
+}
+
/*! \brief Function called by core to ask the channel to indicate some sort of condition */
static int gulp_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
{
@@ -797,7 +970,12 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
switch (condition) {
case AST_CONTROL_RINGING:
if (ast_channel_state(ast) == AST_STATE_RING) {
- response_code = 180;
+ if (session->endpoint->inband_progress) {
+ response_code = 183;
+ res = -1;
+ } else {
+ response_code = 180;
+ }
} else {
res = -1;
}
@@ -841,9 +1019,20 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
case AST_CONTROL_VIDUPDATE:
media = pvt->media[SIP_MEDIA_VIDEO];
if (media && media->rtp) {
- ast_sip_push_task(session->serializer, transmit_info_with_vidupdate, session);
- } else
+ ao2_ref(session, +1);
+
+ if (ast_sip_push_task(session->serializer, transmit_info_with_vidupdate, session)) {
+ ao2_cleanup(session);
+ }
+ } else {
res = -1;
+ }
+ break;
+ case AST_CONTROL_CONNECTED_LINE:
+ ao2_ref(session, +1);
+ if (ast_sip_push_task(session->serializer, update_connected_line_information, session)) {
+ ao2_cleanup(session);
+ }
break;
case AST_CONTROL_UPDATE_RTP_PEER:
case AST_CONTROL_PVT_CAUSE_CODE:
@@ -858,6 +1047,13 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
break;
case AST_CONTROL_SRCCHANGE:
break;
+ case AST_CONTROL_REDIRECTING:
+ if (ast_channel_state(ast) != AST_STATE_UP) {
+ response_code = 181;
+ } else {
+ res = -1;
+ }
+ break;
case -1:
res = -1;
break;
@@ -867,16 +1063,12 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
break;
}
- if (!res && response_code) {
+ if (response_code) {
struct indicate_data *ind_data = indicate_data_alloc(session, condition, response_code, data, datalen);
- if (ind_data) {
- res = ast_sip_push_task(session->serializer, indicate, ind_data);
- if (res) {
- ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n",
- response_code, ast_sorcery_object_get_id(session->endpoint));
- ao2_cleanup(ind_data);
- }
- } else {
+ if (!ind_data || ast_sip_push_task(session->serializer, indicate, ind_data)) {
+ ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n",
+ response_code, ast_sorcery_object_get_id(session->endpoint));
+ ao2_cleanup(ind_data);
res = -1;
}
}
@@ -884,6 +1076,130 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
return res;
}
+struct transfer_data {
+ struct ast_sip_session *session;
+ char *target;
+};
+
+static void transfer_data_destroy(void *obj)
+{
+ struct transfer_data *trnf_data = obj;
+
+ ast_free(trnf_data->target);
+ ao2_cleanup(trnf_data->session);
+}
+
+static struct transfer_data *transfer_data_alloc(struct ast_sip_session *session, const char *target)
+{
+ struct transfer_data *trnf_data = ao2_alloc(sizeof(*trnf_data), transfer_data_destroy);
+
+ if (!trnf_data) {
+ return NULL;
+ }
+
+ if (!(trnf_data->target = ast_strdup(target))) {
+ ao2_ref(trnf_data, -1);
+ return NULL;
+ }
+
+ ao2_ref(session, +1);
+ trnf_data->session = session;
+
+ return trnf_data;
+}
+
+static void transfer_redirect(struct ast_sip_session *session, const char *target)
+{
+ pjsip_tx_data *packet;
+ enum ast_control_transfer message = AST_TRANSFER_SUCCESS;
+ pjsip_contact_hdr *contact;
+ pj_str_t tmp;
+
+ if (pjsip_inv_end_session(session->inv_session, 302, NULL, &packet) != PJ_SUCCESS) {
+ message = AST_TRANSFER_FAILED;
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+
+ return;
+ }
+
+ if (!(contact = pjsip_msg_find_hdr(packet->msg, PJSIP_H_CONTACT, NULL))) {
+ contact = pjsip_contact_hdr_create(packet->pool);
+ }
+
+ pj_strdup2_with_null(packet->pool, &tmp, target);
+ if (!(contact->uri = pjsip_parse_uri(packet->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR))) {
+ message = AST_TRANSFER_FAILED;
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+ pjsip_tx_data_dec_ref(packet);
+
+ return;
+ }
+ pjsip_msg_add_hdr(packet->msg, (pjsip_hdr *) contact);
+
+ ast_sip_session_send_response(session, packet);
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+}
+
+static void transfer_refer(struct ast_sip_session *session, const char *target)
+{
+ pjsip_evsub *sub;
+ enum ast_control_transfer message = AST_TRANSFER_SUCCESS;
+ pj_str_t tmp;
+ pjsip_tx_data *packet;
+
+ if (pjsip_xfer_create_uac(session->inv_session->dlg, NULL, &sub) != PJ_SUCCESS) {
+ message = AST_TRANSFER_FAILED;
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+
+ return;
+ }
+
+ if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, target), &packet) != PJ_SUCCESS) {
+ message = AST_TRANSFER_FAILED;
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+ pjsip_evsub_terminate(sub, PJ_FALSE);
+
+ return;
+ }
+
+ pjsip_xfer_send_request(sub, packet);
+ ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+}
+
+static int transfer(void *data)
+{
+ struct transfer_data *trnf_data = data;
+
+ if (ast_channel_state(trnf_data->session->channel) == AST_STATE_RING) {
+ transfer_redirect(trnf_data->session, trnf_data->target);
+ } else {
+ transfer_refer(trnf_data->session, trnf_data->target);
+ }
+
+ ao2_ref(trnf_data, -1);
+ return 0;
+}
+
+/*! \brief Function called by core for Asterisk initiated transfer */
+static int gulp_transfer(struct ast_channel *chan, const char *target)
+{
+ struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+ struct ast_sip_session *session = pvt->session;
+ struct transfer_data *trnf_data = transfer_data_alloc(session, target);
+
+ if (!trnf_data) {
+ return -1;
+ }
+
+ if (ast_sip_push_task(session->serializer, transfer, trnf_data)) {
+ ast_log(LOG_WARNING, "Error requesting transfer\n");
+ ao2_cleanup(trnf_data);
+ return -1;
+ }
+
+ return 0;
+}
+
/*! \brief Function called by core to start a DTMF digit */
static int gulp_digit_begin(struct ast_channel *chan, char digit)
{
@@ -1014,18 +1330,18 @@ static int gulp_digit_end(struct ast_channel *ast, char digit, unsigned int dura
static int call(void *data)
{
- pjsip_tx_data *packet;
struct ast_sip_session *session = data;
+ pjsip_tx_data *tdata;
+
+ int res = ast_sip_session_create_invite(session, &tdata);
- if (pjsip_inv_invite(session->inv_session, &packet) != PJ_SUCCESS) {
+ if (res) {
ast_queue_hangup(session->channel);
} else {
- ast_sip_session_send_request(session, packet);
+ ast_sip_session_send_request(session, tdata);
}
-
ao2_ref(session, -1);
-
- return 0;
+ return res;
}
/*! \brief Function called by core to actually start calling a remote party */
@@ -1128,7 +1444,8 @@ static int hangup(void *data)
struct ast_sip_session *session = pvt->session;
int cause = h_data->cause;
- if (((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) {
+ if (!session->defer_terminate &&
+ ((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) {
if (packet->msg->type == PJSIP_RESPONSE_MSG) {
ast_sip_session_send_response(session, packet);
} else {
@@ -1255,9 +1572,66 @@ static struct ast_channel *gulp_request(const char *type, struct ast_format_cap
return session->channel;
}
+struct sendtext_data {
+ struct ast_sip_session *session;
+ char text[0];
+};
+
+static void sendtext_data_destroy(void *obj)
+{
+ struct sendtext_data *data = obj;
+ ao2_ref(data->session, -1);
+}
+
+static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text)
+{
+ int size = strlen(text) + 1;
+ struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy);
+
+ if (!data) {
+ return NULL;
+ }
+
+ data->session = session;
+ ao2_ref(data->session, +1);
+ ast_copy_string(data->text, text, size);
+ return data;
+}
+
+static int sendtext(void *obj)
+{
+ RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup);
+ pjsip_tx_data *tdata;
+
+ const struct ast_sip_body body = {
+ .type = "text",
+ .subtype = "plain",
+ .body_text = data->text
+ };
+
+ /* NOT ast_strlen_zero, because a zero-length message is specifically
+ * allowed by RFC 3428 (See section 10, Examples) */
+ if (!data->text) {
+ return 0;
+ }
+
+ ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, &tdata);
+ ast_sip_add_body(tdata, &body);
+ ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint);
+
+ return 0;
+}
+
/*! \brief Function called by core to send text on Gulp session */
static int gulp_sendtext(struct ast_channel *ast, const char *text)
{
+ struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+ struct sendtext_data *data = sendtext_data_create(pvt->session, text);
+
+ if (!data || ast_sip_push_task(pvt->session->serializer, sendtext, data)) {
+ ao2_ref(data, -1);
+ return -1;
+ }
return 0;
}
@@ -1391,7 +1765,6 @@ static void gulp_session_end(struct ast_sip_session *session)
static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{
pjsip_tx_data *packet = NULL;
- int res = AST_PBX_FAILED;
if (session->channel) {
return 0;
@@ -1405,6 +1778,14 @@ static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_r
ast_log(LOG_ERROR, "Failed to allocate new GULP channel on incoming SIP INVITE\n");
return -1;
}
+ /* channel gets created on incoming request, but we wait to call start
+ so other supplements have a chance to run */
+ return 0;
+}
+
+static int pbx_start_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ int res;
res = ast_pbx_start(session->channel);
@@ -1429,6 +1810,12 @@ static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_r
return (res == AST_PBX_SUCCESS) ? 0 : -1;
}
+static struct ast_sip_session_supplement pbx_start_supplement = {
+ .method = "INVITE",
+ .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_LAST,
+ .incoming_request = pbx_start_incoming_request,
+};
+
/*! \brief Function called when a response is received on the session */
static void gulp_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{
@@ -1496,13 +1883,24 @@ static int load_module(void)
goto end;
}
+ if (ast_custom_function_register(&media_offer_function)) {
+ ast_log(LOG_WARNING, "Unable to register GULP_MEDIA_OFFER dialplan function\n");
+ }
+
if (ast_sip_session_register_supplement(&gulp_supplement)) {
ast_log(LOG_ERROR, "Unable to register Gulp supplement\n");
goto end;
}
+ if (ast_sip_session_register_supplement(&pbx_start_supplement)) {
+ ast_log(LOG_ERROR, "Unable to register Gulp pbx start supplement\n");
+ ast_sip_session_unregister_supplement(&gulp_supplement);
+ goto end;
+ }
+
if (ast_sip_session_register_supplement(&gulp_ack_supplement)) {
ast_log(LOG_ERROR, "Unable to register Gulp ACK supplement\n");
+ ast_sip_session_unregister_supplement(&pbx_start_supplement);
ast_sip_session_unregister_supplement(&gulp_supplement);
goto end;
}
@@ -1510,6 +1908,7 @@ static int load_module(void)
return 0;
end:
+ ast_custom_function_unregister(&media_offer_function);
ast_custom_function_unregister(&gulp_dial_contacts_function);
ast_channel_unregister(&gulp_tech);
ast_rtp_glue_unregister(&gulp_rtp_glue);
@@ -1526,7 +1925,11 @@ static int reload(void)
/*! \brief Unload the Gulp channel from Asterisk */
static int unload_module(void)
{
+ ast_custom_function_unregister(&media_offer_function);
+
ast_sip_session_unregister_supplement(&gulp_supplement);
+ ast_sip_session_unregister_supplement(&pbx_start_supplement);
+
ast_custom_function_unregister(&gulp_dial_contacts_function);
ast_channel_unregister(&gulp_tech);
ast_rtp_glue_unregister(&gulp_rtp_glue);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index c207e24fe..689b43a05 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -285,8 +285,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "sip/include/config_parser.h"
#include "sip/include/reqresp_parser.h"
#include "sip/include/sip_utils.h"
-#include "sip/include/srtp.h"
-#include "sip/include/sdp_crypto.h"
+#include "asterisk/sdp_srtp.h"
#include "asterisk/ccss.h"
#include "asterisk/xml.h"
#include "sip/include/dialog.h"
@@ -1490,8 +1489,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno);
/*------ SRTP Support -------- */
-static int setup_srtp(struct sip_srtp **srtp);
-static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a);
+static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a);
/*------ T38 Support --------- */
static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans);
@@ -5918,7 +5916,7 @@ static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket
}
/*! \brief Initialize DTLS-SRTP support on an RTP instance */
-static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct sip_srtp **srtp)
+static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp)
{
struct ast_rtp_engine_dtls *dtls;
@@ -5943,7 +5941,7 @@ static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_
return -1;
}
- if (!(*srtp = sip_srtp_alloc())) {
+ if (!(*srtp = ast_sdp_srtp_alloc())) {
ast_log(LOG_ERROR, "Failed to create required SRTP structure on RTP instance '%p'\n",
rtp);
return -1;
@@ -6418,17 +6416,17 @@ static int sip_call(struct ast_channel *ast, const char *dest, int timeout)
ast_clear_flag(&p->flags[0], SIP_REINVITE);
}
- if (p->rtp && !p->srtp && setup_srtp(&p->srtp) < 0) {
+ if (p->rtp && !p->srtp && !(p->srtp = ast_sdp_srtp_alloc())) {
ast_log(LOG_WARNING, "SRTP audio setup failed\n");
return -1;
}
- if (p->vrtp && !p->vsrtp && setup_srtp(&p->vsrtp) < 0) {
+ if (p->vrtp && !p->vsrtp && !(p->vsrtp = ast_sdp_srtp_alloc())) {
ast_log(LOG_WARNING, "SRTP video setup failed\n");
return -1;
}
- if (p->trtp && !p->tsrtp && setup_srtp(&p->tsrtp) < 0) {
+ if (p->trtp && !p->tsrtp && !(p->tsrtp = ast_sdp_srtp_alloc())) {
ast_log(LOG_WARNING, "SRTP text setup failed\n");
return -1;
}
@@ -6690,17 +6688,17 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
destroy_msg_headers(p);
if (p->srtp) {
- sip_srtp_destroy(p->srtp);
+ ast_sdp_srtp_destroy(p->srtp);
p->srtp = NULL;
}
if (p->vsrtp) {
- sip_srtp_destroy(p->vsrtp);
+ ast_sdp_srtp_destroy(p->vsrtp);
p->vsrtp = NULL;
}
if (p->tsrtp) {
- sip_srtp_destroy(p->tsrtp);
+ ast_sdp_srtp_destroy(p->tsrtp);
p->tsrtp = NULL;
}
@@ -10154,7 +10152,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
secure_audio = 1;
if (p->srtp) {
- ast_set_flag(p->srtp, SRTP_CRYPTO_OFFER_OK);
+ ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) {
secure_audio = 1;
@@ -10235,8 +10233,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
} else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) {
secure_video = 1;
- if (p->vsrtp || (p->vsrtp = sip_srtp_alloc())) {
- ast_set_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK);
+ if (p->vsrtp || (p->vsrtp = ast_sdp_srtp_alloc())) {
+ ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK);
}
} else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) {
secure_video = 1;
@@ -10516,7 +10514,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
goto process_sdp_cleanup;
}
- if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)))) {
+ if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)))) {
ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n");
res = -1;
goto process_sdp_cleanup;
@@ -10528,7 +10526,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
goto process_sdp_cleanup;
}
- if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK)))) {
+ if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK)))) {
ast_log(LOG_WARNING, "Can't provide secure video requested in SDP offer\n");
res = -1;
goto process_sdp_cleanup;
@@ -12993,52 +12991,20 @@ static void get_our_media_address(struct sip_pvt *p, int needvideo, int needtext
}
}
-static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const char **a_crypto)
+static char *crypto_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32)
{
- int taglen = 80;
+ char *a_crypto;
+ char *orig_crypto;
- /* Set encryption properties */
- if (srtp) {
- if (!srtp->crypto) {
- srtp->crypto = sdp_crypto_setup();
- }
-
- if (p->dtls_cfg.enabled) {
- /* If DTLS-SRTP is enabled the key details will be pulled from TLS */
- return;
- }
-
- /* set the key length based on INVITE or settings */
- if (ast_test_flag(srtp, SRTP_CRYPTO_TAG_80)) {
- taglen = 80;
- } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32) ||
- ast_test_flag(srtp, SRTP_CRYPTO_TAG_32)) {
- taglen = 32;
- }
-
- if (srtp->crypto && (sdp_crypto_offer(srtp->crypto, taglen) >= 0)) {
- *a_crypto = sdp_crypto_attrib(srtp->crypto);
- }
-
- if (!*a_crypto) {
- ast_log(LOG_WARNING, "No SRTP key management enabled\n");
- }
+ if (!srtp) {
+ return NULL;
}
-}
-static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure, struct ast_rtp_instance *instance)
-{
- struct ast_rtp_engine_dtls *dtls;
-
- if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) {
- return ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF) ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP";
- } else {
- if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
- return secure ? "RTP/SAVPF" : "RTP/AVPF";
- } else {
- return secure ? "RTP/SAVP" : "RTP/AVP";
- }
+ orig_crypto = ast_strdupa(ast_sdp_srtp_get_attrib(srtp, dtls_enabled, default_taglen_32));
+ if (ast_asprintf(&a_crypto, "a=crypto:%s\r\n", orig_crypto) == -1) {
+ return NULL;
}
+ return a_crypto;
}
/*! \brief Add Session Description Protocol message
@@ -13079,9 +13045,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
struct ast_str *a_video = ast_str_create(256); /* Attributes for video */
struct ast_str *a_text = ast_str_create(256); /* Attributes for text */
struct ast_str *a_modem = ast_str_alloca(1024); /* Attributes for modem */
- const char *a_crypto = NULL;
- const char *v_a_crypto = NULL;
- const char *t_a_crypto = NULL;
+ RAII_VAR(char *, a_crypto, NULL, ast_free);
+ RAII_VAR(char *, v_a_crypto, NULL, ast_free);
+ RAII_VAR(char *, t_a_crypto, NULL, ast_free);
int x;
struct ast_format tmp_fmt;
@@ -13199,9 +13165,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
/* Ok, we need video. Let's add what we need for video and set codecs.
Video is handled differently than audio since we can not transcode. */
if (needvideo) {
- get_crypto_attrib(p, p->vsrtp, &v_a_crypto);
+ v_a_crypto = crypto_get_attrib(p->vsrtp, p->dtls_cfg.enabled,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest),
- get_sdp_rtp_profile(p, v_a_crypto ? 1 : 0, p->vrtp));
+ ast_sdp_get_rtp_profile(v_a_crypto ? 1 : 0, p->vrtp,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
/* Build max bitrate string */
if (p->maxcallbitrate)
@@ -13224,9 +13192,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
if (needtext) {
if (sipdebug_text)
ast_verbose("Lets set up the text sdp\n");
- get_crypto_attrib(p, p->tsrtp, &t_a_crypto);
+ t_a_crypto = crypto_get_attrib(p->tsrtp, p->dtls_cfg.enabled,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest),
- get_sdp_rtp_profile(p, t_a_crypto ? 1 : 0, p->trtp));
+ ast_sdp_get_rtp_profile(t_a_crypto ? 1 : 0, p->trtp,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
if (debug) { /* XXX should I use tdest below ? */
ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
}
@@ -13245,9 +13215,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
/* We break with the "recommendation" and send our IP, in order that our
peer doesn't have to ast_gethostbyname() us */
- get_crypto_attrib(p, p->srtp, &a_crypto);
+ a_crypto = crypto_get_attrib(p->srtp, p->dtls_cfg.enabled,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest),
- get_sdp_rtp_profile(p, a_crypto ? 1 : 0, p->rtp));
+ ast_sdp_get_rtp_profile(a_crypto ? 1 : 0, p->rtp,
+ ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
/* Now, start adding audio codecs. These are added in this order:
- First what was requested by the calling channel
@@ -25767,7 +25739,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)));
} else if ((p->t38.state == T38_DISABLED) || (p->t38.state == T38_REJECTED)) {
/* If this is not a re-invite or something to ignore - it's critical */
- if (p->srtp && !ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)) {
+ if (p->srtp && !ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)) {
ast_log(LOG_WARNING, "Target does not support required crypto\n");
transmit_response_reliable(p, "488 Not Acceptable Here (crypto)", req);
} else {
@@ -32791,22 +32763,7 @@ static void sip_send_all_mwi_subscriptions(void)
} while (0));
}
-/* SRTP */
-static int setup_srtp(struct sip_srtp **srtp)
-{
- if (!ast_rtp_engine_srtp_is_registered()) {
- ast_debug(1, "No SRTP module loaded, can't setup SRTP session.\n");
- return -1;
- }
-
- if (!(*srtp = sip_srtp_alloc())) { /* Allocate SRTP data structure */
- return -1;
- }
-
- return 0;
-}
-
-static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a)
+static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a)
{
struct ast_rtp_engine_dtls *dtls;
@@ -32819,27 +32776,28 @@ static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struc
if (strncasecmp(a, "crypto:", 7)) {
return FALSE;
}
+ /* skip "crypto:" */
+ a += strlen("crypto:");
+
if (!*srtp) {
if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
ast_log(LOG_WARNING, "Ignoring unexpected crypto attribute in SDP answer\n");
return FALSE;
}
- if (setup_srtp(srtp) < 0) {
+ if (!(*srtp = ast_sdp_srtp_alloc())) {
return FALSE;
}
}
- if (!(*srtp)->crypto && !((*srtp)->crypto = sdp_crypto_setup())) {
+ if (!(*srtp)->crypto && !((*srtp)->crypto = ast_sdp_crypto_alloc())) {
return FALSE;
}
- if (sdp_crypto_process((*srtp)->crypto, a, rtp, *srtp) < 0) {
+ if (ast_sdp_crypto_process(rtp, *srtp, a) < 0) {
return FALSE;
}
- ast_set_flag(*srtp, SRTP_CRYPTO_OFFER_OK);
-
if ((dtls = ast_rtp_instance_get_dtls(rtp))) {
dtls->stop(rtp);
p->dtls_cfg.enabled = 0;
diff --git a/channels/sip/include/sdp_crypto.h b/channels/sip/include/sdp_crypto.h
deleted file mode 100644
index da1035e87..000000000
--- a/channels/sip/include/sdp_crypto.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * 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 sdp_crypto.h
- *
- * \brief SDP Security descriptions
- *
- * Specified in RFC 4568
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-#ifndef _SDP_CRYPTO_H
-#define _SDP_CRYPTO_H
-
-#include <asterisk/rtp_engine.h>
-
-struct sdp_crypto;
-struct sip_srtp;
-
-/*! \brief Initialize an return an sdp_crypto struct
- *
- * \details
- * This function allocates a new sdp_crypto struct and initializes its values
- *
- * \retval NULL on failure
- * \retval a pointer to a new sdp_crypto structure
- */
-struct sdp_crypto *sdp_crypto_setup(void);
-
-/*! \brief Destroy a previously allocated sdp_crypto struct */
-void sdp_crypto_destroy(struct sdp_crypto *crypto);
-
-/*! \brief Parse the a=crypto line from SDP and set appropriate values on the
- * sdp_crypto struct.
- *
- * \param p A valid sdp_crypto struct
- * \param attr the a:crypto line from SDP
- * \param rtp The rtp instance associated with the SDP being parsed
- * \param srtp SRTP structure
- *
- * \retval 0 success
- * \retval nonzero failure
- */
-int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp, struct sip_srtp *srtp);
-
-
-/*! \brief Generate an SRTP a=crypto offer
- *
- * \details
- * The offer is stored on the sdp_crypto struct in a_crypto
- *
- * \param p A valid sdp_crypto struct
- * \param taglen Length
- *
- * \retval 0 success
- * \retval nonzero failure
- */
-int sdp_crypto_offer(struct sdp_crypto *p, int taglen);
-
-
-/*! \brief Return the a_crypto value of the sdp_crypto struct
- *
- * \param p An sdp_crypto struct that has had sdp_crypto_offer called
- *
- * \retval The value of the a_crypto for p
- */
-const char *sdp_crypto_attrib(struct sdp_crypto *p);
-
-#endif /* _SDP_CRYPTO_H */
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index 0adde37f2..8b4672b25 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -1165,9 +1165,9 @@ struct sip_pvt {
AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */
struct sip_invite_param *options; /*!< Options for INVITE */
struct sip_st_dlg *stimer; /*!< SIP Session-Timers */
- struct sip_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */
- struct sip_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */
- struct sip_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */
+ struct ast_sdp_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */
+ struct ast_sdp_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */
+ struct ast_sdp_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */
int red; /*!< T.140 RTP Redundancy */
int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */
diff --git a/channels/sip/include/srtp.h b/channels/sip/include/srtp.h
deleted file mode 100644
index a4ded62ca..000000000
--- a/channels/sip/include/srtp.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * 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 srtp.h
- *
- * \brief SIP Secure RTP (SRTP)
- *
- * Specified in RFC 3711
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-#ifndef _SIP_SRTP_H
-#define _SIP_SRTP_H
-
-#include "sdp_crypto.h"
-
-/* SRTP flags */
-#define SRTP_ENCR_OPTIONAL (1 << 1) /* SRTP encryption optional */
-#define SRTP_CRYPTO_ENABLE (1 << 2)
-#define SRTP_CRYPTO_OFFER_OK (1 << 3)
-#define SRTP_CRYPTO_TAG_32 (1 << 4)
-#define SRTP_CRYPTO_TAG_80 (1 << 5)
-
-/*! \brief structure for secure RTP audio */
-struct sip_srtp {
- unsigned int flags;
- struct sdp_crypto *crypto;
-};
-
-/*!
- * \brief allocate a sip_srtp structure
- * \retval a new malloc'd sip_srtp structure on success
- * \retval NULL on failure
-*/
-struct sip_srtp *sip_srtp_alloc(void);
-
-/*!
- * \brief free a sip_srtp structure
- * \param srtp a sip_srtp structure
-*/
-void sip_srtp_destroy(struct sip_srtp *srtp);
-
-#endif /* _SIP_SRTP_H */
diff --git a/channels/sip/srtp.c b/channels/sip/srtp.c
deleted file mode 100644
index 8b2718fc3..000000000
--- a/channels/sip/srtp.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * 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 srtp.c
- *
- * \brief SIP Secure RTP (SRTP)
- *
- * Specified in RFC 3711
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-/*** MODULEINFO
- <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/utils.h"
-#include "include/srtp.h"
-
-struct sip_srtp *sip_srtp_alloc(void)
-{
- struct sip_srtp *srtp;
-
- srtp = ast_calloc(1, sizeof(*srtp));
-
- return srtp;
-}
-
-void sip_srtp_destroy(struct sip_srtp *srtp)
-{
- if (srtp->crypto) {
- sdp_crypto_destroy(srtp->crypto);
- }
- srtp->crypto = NULL;
- ast_free(srtp);
-}
diff --git a/configs/res_sip.conf.sample b/configs/res_sip.conf.sample
index dd8da8dff..34cd6d645 100644
--- a/configs/res_sip.conf.sample
+++ b/configs/res_sip.conf.sample
@@ -22,3 +22,5 @@ dtmfmode=rfc4733 ; Supported DTMF modes are rfc4733, inband, info, and
;rtp_ipv6=yes ; Force IPv6 for RTP transport
;rtp_symmetric=yes ; Enable symmetric RTP support
;use_ptime=yes ; Whether to use the ptime value received from the endpoint or not
+;media_encryption=no ; Options for media encryption are no, and sdes
+;use_avpf=no ; Whether to force usage of AVPF transport for this endpoint
diff --git a/include/asterisk/res_sip.h b/include/asterisk/res_sip.h
index b48ed9f82..7c486aa68 100644
--- a/include/asterisk/res_sip.h
+++ b/include/asterisk/res_sip.h
@@ -139,6 +139,47 @@ struct ast_sip_contact {
);
/*! Absolute time that this contact is no longer valid after */
struct timeval expiration_time;
+ /*! Frequency to send OPTIONS requests to contact. 0 is disabled. */
+ unsigned int qualify_frequency;
+ /*! If true authenticate the qualify if needed */
+ int authenticate_qualify;
+};
+
+#define CONTACT_STATUS "contact_status"
+
+/*!
+ * \brief Status type for a contact.
+ */
+enum ast_sip_contact_status_type {
+ UNAVAILABLE,
+ AVAILABLE
+};
+
+/*!
+ * \brief A contact's status.
+ *
+ * \detail Maintains a contact's current status and round trip time
+ * if available.
+ */
+struct ast_sip_contact_status {
+ SORCERY_OBJECT(details);
+ /*! Current status for a contact (default - unavailable) */
+ enum ast_sip_contact_status_type status;
+ /*! The round trip start time set before sending a qualify request */
+ struct timeval rtt_start;
+ /*! The round trip time in microseconds */
+ int64_t rtt;
+};
+
+/*!
+ * \brief A transport to be used for messages to a contact
+ */
+struct ast_sip_contact_transport {
+ AST_DECLARE_STRING_FIELDS(
+ /*! Full URI of the contact */
+ AST_STRING_FIELD(uri);
+ );
+ pjsip_transport *transport;
};
/*!
@@ -157,6 +198,10 @@ struct ast_sip_aor {
unsigned int maximum_expiration;
/*! Default contact expiration if one is not provided in the contact */
unsigned int default_expiration;
+ /*! Frequency to send OPTIONS requests to AOR contacts. 0 is disabled. */
+ unsigned int qualify_frequency;
+ /*! If true authenticate the qualify if needed */
+ int authenticate_qualify;
/*! Maximum number of external contacts, 0 to disable */
unsigned int max_contacts;
/*! Whether to remove any existing contacts not related to an incoming REGISTER when it comes in */
@@ -245,6 +290,17 @@ enum ast_sip_direct_media_glare_mitigation {
AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING,
};
+enum ast_sip_session_media_encryption {
+ /*! Invalid media encryption configuration */
+ AST_SIP_MEDIA_TRANSPORT_INVALID = 0,
+ /*! Do not allow any encryption of session media */
+ AST_SIP_MEDIA_ENCRYPT_NONE,
+ /*! Offer SDES-encrypted session media */
+ AST_SIP_MEDIA_ENCRYPT_SDES,
+ /*! Offer encrypted session media with datagram TLS key exchange */
+ AST_SIP_MEDIA_ENCRYPT_DTLS,
+};
+
/*!
* \brief An entity with which Asterisk communicates
*/
@@ -306,14 +362,14 @@ struct ast_sip_endpoint {
unsigned int sess_expires;
/*! List of outbound registrations */
AST_LIST_HEAD_NOLOCK(, ast_sip_registration) registrations;
- /*! Frequency to send OPTIONS requests to endpoint. 0 is disabled. */
- unsigned int qualify_frequency;
/*! Method(s) by which the endpoint should be identified. */
enum ast_sip_endpoint_identifier_type ident_method;
/*! Boolean indicating if direct_media is permissible */
unsigned int direct_media;
/*! When using direct media, which method should be used */
enum ast_sip_session_refresh_method direct_media_method;
+ /*! When performing connected line update, which method should be used */
+ enum ast_sip_session_refresh_method connected_line_method;
/*! Take steps to mitigate glare for direct media */
enum ast_sip_direct_media_glare_mitigation direct_media_glare_mitigation;
/*! Do not attempt direct media session refreshes if a media NAT is detected */
@@ -326,8 +382,26 @@ struct ast_sip_endpoint {
unsigned int send_pai;
/*! Do we send Remote-Party-ID headers to this endpoint? */
unsigned int send_rpid;
+ /*! Do we add Diversion headers to applicable outgoing requests/responses? */
+ unsigned int send_diversion;
/*! Should unsolicited MWI be aggregated into a single NOTIFY? */
unsigned int aggregate_mwi;
+ /*! Do we use media encryption? what type? */
+ enum ast_sip_session_media_encryption media_encryption;
+ /*! Do we use AVPF exclusively for this endpoint? */
+ unsigned int use_avpf;
+ /*! Is one-touch recording permitted? */
+ unsigned int one_touch_recording;
+ /*! Boolean indicating if ringing should be sent as inband progress */
+ unsigned int inband_progress;
+ /*! Call group */
+ ast_group_t callgroup;
+ /*! Pickup group */
+ ast_group_t pickupgroup;
+ /*! Named call group */
+ struct ast_namedgroups *named_callgroups;
+ /*! Named pickup group */
+ struct ast_namedgroups *named_pickupgroups;
/*! Pointer to the persistent Asterisk endpoint */
struct ast_endpoint *persistent;
/*! The number of channels at which busy device state is returned */
@@ -553,6 +627,16 @@ struct ast_sorcery *ast_sip_get_sorcery(void);
int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery);
/*!
+ * \brief Initialize qualify support on a sorcery instance
+ *
+ * \param sorcery The sorcery instance
+ *
+ * \retval -1 failure
+ * \retval 0 success
+ */
+int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery);
+
+/*!
* \brief Initialize location support on a sorcery instance
*
* \param sorcery The sorcery instance
@@ -611,6 +695,37 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name);
/*!
+ * \brief Add a transport for a contact to use
+ */
+
+void ast_sip_location_add_contact_transport(struct ast_sip_contact_transport *ct);
+
+/*!
+ * \brief Delete a transport for a contact that went away
+ */
+void ast_sip_location_delete_contact_transport(struct ast_sip_contact_transport *ct);
+
+/*!
+ * \brief Retrieve a contact_transport, by URI
+ *
+ * \param contact_uri URI of the contact
+ *
+ * \retval NULL if not found
+ * \retval non-NULL if found
+ */
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_uri(const char *contact_uri);
+
+/*!
+ * \brief Retrieve a contact_transport, by transport
+ *
+ * \param transport transport the contact uses
+ *
+ * \retval NULL if not found
+ * \retval non-NULL if found
+ */
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_transport(pjsip_transport *transport);
+
+/*!
* \brief Add a new contact to an AOR
*
* \param aor Pointer to the AOR
@@ -1045,7 +1160,7 @@ int ast_sip_append_body(pjsip_tx_data *tdata, const char *body_text);
* \param src The pj_str_t to copy
* \param size The size of the destination buffer.
*/
-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);
/*!
* \brief Get the looked-up endpoint on an out-of dialog request or response
@@ -1085,4 +1200,61 @@ int ast_sip_retrieve_auths(const char *auth_names[], size_t num_auths, struct as
*/
void ast_sip_cleanup_auths(struct ast_sip_auth *auths[], size_t num_auths);
+/*!
+ * \brief Checks if the given content type matches type/subtype.
+ *
+ * Compares the pjsip_media_type with the passed type and subtype and
+ * returns the result of that comparison. The media type parameters are
+ * ignored.
+ *
+ * \param content_type The pjsip_media_type structure to compare
+ * \param type The media type to compare
+ * \param subtype The media subtype to compare
+ * \retval 0 No match
+ * \retval -1 Match
+ */
+int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype);
+
+/*!
+ * \brief Send a security event notification for when an invalid endpoint is requested
+ *
+ * \param name Name of the endpoint requested
+ * \param rdata Received message
+ */
+void ast_sip_report_invalid_endpoint(const char *name, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when an ACL check fails
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ * \param name Name of the ACL
+ */
+void ast_sip_report_failed_acl(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *name);
+
+/*!
+ * \brief Send a security event notification for when a challenge response has failed
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ */
+void ast_sip_report_auth_failed_challenge_response(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when authentication succeeds
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ */
+void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when an authentication challenge is sent
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ * \param tdata Sent message
+ */
+void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata);
+
#endif /* _RES_SIP_H */
diff --git a/include/asterisk/res_sip_exten_state.h b/include/asterisk/res_sip_exten_state.h
new file mode 100644
index 000000000..62662f930
--- /dev/null
+++ b/include/asterisk/res_sip_exten_state.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#ifndef _RES_SIP_EXTEN_STATE_H
+#define _RES_SIP_EXTEN_STATE_H
+
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/presencestate.h"
+
+
+/*!
+ * \brief Contains information pertaining to extension/device state changes.
+ */
+struct ast_sip_exten_state_data {
+ /*! The extension of the current state change */
+ const char *exten;
+ /*! The extension state of the change */
+ enum ast_extension_states exten_state;
+ /*! The presence state of the change */
+ enum ast_presence_state presence_state;
+ /*! Current device state information */
+ struct ao2_container *device_state_info;
+};
+
+/*!
+ * \brief Extension state provider.
+ */
+struct ast_sip_exten_state_provider {
+ /*! The name of the event this provider registers for */
+ const char *event_name;
+ /*! Type of the body, ex: "application" */
+ const char *type;
+ /*! Subtype of the body, ex: "pidf+xml" */
+ const char *subtype;
+ /*! Type/Subtype together - ex: application/pidf+xml */
+ const char *body_type;
+ /*! Subscription handler to be used and associated with provider */
+ struct ast_sip_subscription_handler *handler;
+
+ /*!
+ * \brief Create the body text of a NOTIFY request.
+ *
+ * Implementors use this to create body information within the given
+ * ast_str. That information is then added to the NOTIFY request.
+ *
+ * \param data Current extension state changes
+ * \param local URI of the dialog's local party, e.g. 'from'
+ * \param remote URI of the dialog's remote party, e.g. 'to'
+ * \param body_text Out parameter used to populate the NOTIFY msg body
+ * \retval 0 Successfully created the body's text
+ * \retval -1 Failed to create the body's text
+ */
+ int (*create_body)(struct ast_sip_exten_state_data *data, const char *local,
+ const char *remote, struct ast_str **body_text);
+
+ /*! Next item in the list */
+ AST_LIST_ENTRY(ast_sip_exten_state_provider) next;
+};
+
+/*!
+ * \brief Registers an extension state provider.
+ *
+ * \param obj An extension state provider
+ * \retval 0 Successfully registered the extension state provider
+ * \retval -1 Failed to register the extension state provider
+ */
+int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj);
+
+/*!
+ * \brief Unregisters an extension state provider.
+ *
+ * \param obj An extension state provider
+ */
+void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj);
+
+#endif
diff --git a/include/asterisk/res_sip_pubsub.h b/include/asterisk/res_sip_pubsub.h
index 33614b285..be443299c 100644
--- a/include/asterisk/res_sip_pubsub.h
+++ b/include/asterisk/res_sip_pubsub.h
@@ -261,7 +261,22 @@ struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_sub
* \retval non-NULL The underlying pjsip_evsub
*/
pjsip_evsub *ast_sip_subscription_get_evsub(struct ast_sip_subscription *sub);
-
+
+/*!
+ * \brief Get the underlying PJSIP dialog structure
+ *
+ * Call this function when information needs to be retrieved from the
+ * underlying pjsip dialog.
+ *
+ * This function, as well as all methods called on the pjsip_evsub should
+ * be done in a SIP servant thread.
+ *
+ * \param sub The subscription
+ * \retval NULL Failure
+ * \retval non-NULL The underlying pjsip_dialog
+ */
+pjsip_dialog *ast_sip_subscription_get_dlg(struct ast_sip_subscription *sub);
+
/*!
* \brief Send a request created via a PJSIP evsub method
*
diff --git a/include/asterisk/res_sip_session.h b/include/asterisk/res_sip_session.h
index cbed52621..e4b05f7c3 100644
--- a/include/asterisk/res_sip_session.h
+++ b/include/asterisk/res_sip_session.h
@@ -26,6 +26,8 @@
#include "asterisk/channel.h"
/* Needed for ast_sockaddr struct */
#include "asterisk/netsock.h"
+/* Neeed for ast_sdp_srtp struct */
+#include "asterisk/sdp_srtp.h"
/* Forward declarations */
struct ast_sip_endpoint;
@@ -41,6 +43,7 @@ struct ast_party_id;
struct pjmedia_sdp_media;
struct pjmedia_sdp_session;
struct ast_rtp_instance;
+struct ast_dsp;
struct ast_sip_session_sdp_handler;
@@ -54,6 +57,8 @@ struct ast_sip_session_media {
struct ast_sockaddr direct_media_addr;
/*! \brief SDP handler that setup the RTP */
struct ast_sip_session_sdp_handler *handler;
+ /*! \brief Holds SRTP information */
+ struct ast_sdp_srtp *srtp;
/*! \brief Stream is on hold */
unsigned int held:1;
/*! \brief Stream type this session media handles */
@@ -97,10 +102,18 @@ struct ast_sip_session {
pj_timer_entry rescheduled_reinvite;
/* Format capabilities pertaining to direct media */
struct ast_format_cap *direct_media_cap;
+ /* When we need to forcefully end the session */
+ pj_timer_entry scheduled_termination;
/* Identity of endpoint this session deals with */
struct ast_party_id id;
/* Requested capabilities */
struct ast_format_cap *req_caps;
+ /* Codecs overriden by dialplan on an outgoing request */
+ struct ast_codec_pref override_prefs;
+ /* Optional DSP, used only for inband DTMF detection if configured */
+ struct ast_dsp *dsp;
+ /* Whether the termination of the session should be deferred */
+ unsigned int defer_terminate:1;
};
typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
@@ -289,6 +302,13 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint, const char *location, const char *request_user, struct ast_format_cap *req_caps);
/*!
+ * \brief Defer local termination of a session until remote side terminates, or an amount of time passes
+ *
+ * \param session The session to defer termination on
+ */
+void ast_sip_session_defer_termination(struct ast_sip_session *session);
+
+/*!
* \brief Register an SDP handler
*
* An SDP handler is responsible for parsing incoming SDP streams and ensuring that
@@ -452,6 +472,14 @@ void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_dat
void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data *tdata);
/*!
+ * \brief Creates an INVITE request.
+ *
+ * \param session Starting session for the INVITE
+ * \param tdata The created request.
+ */
+int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data **tdata);
+
+/*!
* \brief Send a SIP request and get called back when a response is received
*
* This will send the request out exactly the same as ast_sip_send_request() does.
@@ -465,4 +493,18 @@ void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data
void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata,
ast_sip_session_response_cb on_response);
+/*!
+ * \brief Retrieves a session from a dialog
+ *
+ * \param dlg The dialog to retrieve the session from
+ *
+ * \retval non-NULL if session exists
+ * \retval NULL if no session
+ *
+ * \note The reference count of the session is increased when returned
+ *
+ * \note This function *must* be called with the dialog locked
+ */
+struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg);
+
#endif /* _RES_SIP_SESSION_H */
diff --git a/include/asterisk/sdp_srtp.h b/include/asterisk/sdp_srtp.h
new file mode 100644
index 000000000..9b92e0e3f
--- /dev/null
+++ b/include/asterisk/sdp_srtp.h
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006 - 2007, Mikael Magnusson
+ *
+ * Mikael Magnusson <mikma@users.sourceforge.net>
+ *
+ * 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 sdp_srtp.h
+ *
+ * \brief SRTP and SDP Security descriptions
+ *
+ * Specified in RFC 4568
+ * Specified in RFC 3711
+ *
+ * \author Mikael Magnusson <mikma@users.sourceforge.net>
+ */
+
+#ifndef _SDP_SRTP_H
+#define _SDP_SRTP_H
+
+#include <asterisk/rtp_engine.h>
+
+struct ast_sdp_crypto;
+
+/*! \brief structure for secure RTP audio */
+struct ast_sdp_srtp {
+ unsigned int flags;
+ struct ast_sdp_crypto *crypto;
+};
+
+/* SRTP flags */
+#define AST_SRTP_CRYPTO_OFFER_OK (1 << 1)
+#define AST_SRTP_CRYPTO_TAG_32 (1 << 2)
+#define AST_SRTP_CRYPTO_TAG_80 (1 << 3)
+
+/*!
+ * \brief allocate a ast_sdp_srtp structure
+ * \retval a new malloc'd ast_sdp_srtp structure on success
+ * \retval NULL on failure
+*/
+struct ast_sdp_srtp *ast_sdp_srtp_alloc(void);
+
+/*!
+ * \brief free a ast_sdp_srtp structure
+ * \param srtp a ast_sdp_srtp structure
+*/
+void ast_sdp_srtp_destroy(struct ast_sdp_srtp *srtp);
+
+/*! \brief Initialize an return an ast_sdp_crypto struct
+ *
+ * \details
+ * This function allocates a new ast_sdp_crypto struct and initializes its values
+ *
+ * \retval NULL on failure
+ * \retval a pointer to a new ast_sdp_crypto structure
+ */
+struct ast_sdp_crypto *ast_sdp_crypto_alloc(void);
+
+/*! \brief Destroy a previously allocated ast_sdp_crypto struct */
+void ast_sdp_crypto_destroy(struct ast_sdp_crypto *crypto);
+
+/*! \brief Parse the a=crypto line from SDP and set appropriate values on the
+ * ast_sdp_crypto struct.
+ *
+ * The attribute line should already have "a=crypto:" removed.
+ *
+ * \param p A valid ast_sdp_crypto struct
+ * \param attr the a:crypto line from SDP
+ * \param rtp The rtp instance associated with the SDP being parsed
+ * \param srtp SRTP structure
+ *
+ * \retval 0 success
+ * \retval nonzero failure
+ */
+int ast_sdp_crypto_process(struct ast_rtp_instance *rtp, struct ast_sdp_srtp *srtp, const char *attr);
+
+/*! \brief Generate an SRTP a=crypto offer
+ *
+ * \details
+ * The offer is stored on the ast_sdp_crypto struct in a_crypto
+ *
+ * \param p A valid ast_sdp_crypto struct
+ * \param taglen Length
+ *
+ * \retval 0 success
+ * \retval nonzero failure
+ */
+int ast_sdp_crypto_build_offer(struct ast_sdp_crypto *p, int taglen);
+
+
+/*! \brief Get the crypto attribute line for the srtp structure
+ *
+ * The attribute line does not contain the initial "a=crypto:" and does
+ * not terminate with "\r\n".
+ *
+ * \param srtp The ast_sdp_srtp structure for which to get an attribute line
+ * \param dtls_enabled Whether this connection is encrypted with datagram TLS
+ * \param default_taglen_32 Whether to default to a tag length of 32 instead of 80
+ *
+ * \retval An attribute line containing cryptographic information
+ * \retval NULL if the srtp structure does not require an attribute line containing crypto information
+ */
+const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32);
+
+/*! \brief Get the RTP profile in use by a media session
+ *
+ * \param sdes_active Whether the media session is using SDES-SRTP
+ * \param instance The RTP instance associated with this media session
+ * \param using_avpf Whether the media session is using early feedback (AVPF)
+ *
+ * \retval A non-allocated string describing the profile in use (does not need to be freed)
+ */
+char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf);
+#endif /* _SDP_CRYPTO_H */
diff --git a/main/pbx.c b/main/pbx.c
index af988dcf9..db737327f 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -11814,7 +11814,7 @@ int ast_pbx_init(void)
hintdevices = ao2_container_alloc(HASH_EXTENHINT_SIZE, hintdevice_hash_cb, hintdevice_cmp_multiple);
statecbs = ao2_container_alloc(1, NULL, statecbs_cmp);
- ast_register_atexit(pbx_shutdown);
+ ast_register_cleanup(pbx_shutdown);
return (hints && hintdevices && statecbs) ? 0 : -1;
}
diff --git a/channels/sip/sdp_crypto.c b/main/sdp_srtp.c
index c27e882c2..85dc108a6 100644
--- a/channels/sip/sdp_crypto.c
+++ b/main/sdp_srtp.c
@@ -16,10 +16,11 @@
* at the top of the source tree.
*/
-/*! \file sdp_crypto.c
+/*! \file ast_sdp_crypto.c
*
- * \brief SDP Security descriptions
+ * \brief SRTP and SDP Security descriptions
*
+ * Specified in RFC 3711
* Specified in RFC 4568
*
* \author Mikael Magnusson <mikma@users.sourceforge.net>
@@ -35,8 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/options.h"
#include "asterisk/utils.h"
-#include "include/sdp_crypto.h"
-#include "include/srtp.h"
+#include "asterisk/sdp_srtp.h"
#define SRTP_MASTER_LEN 30
#define SRTP_MASTERKEY_LEN 16
@@ -46,7 +46,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
extern struct ast_srtp_res *res_srtp;
extern struct ast_srtp_policy_res *res_srtp_policy;
-struct sdp_crypto {
+struct ast_sdp_srtp *ast_sdp_srtp_alloc(void)
+{
+ if (!ast_rtp_engine_srtp_is_registered()) {
+ ast_debug(1, "No SRTP module loaded, can't setup SRTP session.\n");
+ return NULL;
+ }
+
+ return ast_calloc(1, sizeof(struct ast_sdp_srtp));
+}
+
+void ast_sdp_srtp_destroy(struct ast_sdp_srtp *srtp)
+{
+ if (srtp->crypto) {
+ ast_sdp_crypto_destroy(srtp->crypto);
+ }
+ srtp->crypto = NULL;
+ ast_free(srtp);
+}
+
+struct ast_sdp_crypto {
char *a_crypto;
unsigned char local_key[SRTP_MASTER_LEN];
char *tag;
@@ -56,12 +75,7 @@ struct sdp_crypto {
static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, const unsigned char *master_key, unsigned long ssrc, int inbound);
-static struct sdp_crypto *sdp_crypto_alloc(void)
-{
- return ast_calloc(1, sizeof(struct sdp_crypto));
-}
-
-void sdp_crypto_destroy(struct sdp_crypto *crypto)
+void ast_sdp_crypto_destroy(struct ast_sdp_crypto *crypto)
{
ast_free(crypto->a_crypto);
crypto->a_crypto = NULL;
@@ -70,9 +84,9 @@ void sdp_crypto_destroy(struct sdp_crypto *crypto)
ast_free(crypto);
}
-struct sdp_crypto *sdp_crypto_setup(void)
+struct ast_sdp_crypto *ast_sdp_crypto_alloc(void)
{
- struct sdp_crypto *p;
+ struct ast_sdp_crypto *p;
int key_len;
unsigned char remote_key[SRTP_MASTER_LEN];
@@ -80,12 +94,12 @@ struct sdp_crypto *sdp_crypto_setup(void)
return NULL;
}
- if (!(p = sdp_crypto_alloc())) {
+ if (!(p = ast_calloc(1, sizeof(*p)))) {
return NULL;
}
if (res_srtp->get_random(p->local_key, sizeof(p->local_key)) < 0) {
- sdp_crypto_destroy(p);
+ ast_sdp_crypto_destroy(p);
return NULL;
}
@@ -133,7 +147,7 @@ static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, cons
return 0;
}
-static int sdp_crypto_activate(struct sdp_crypto *p, int suite_val, unsigned char *remote_key, struct ast_rtp_instance *rtp)
+static int crypto_activate(struct ast_sdp_crypto *p, int suite_val, unsigned char *remote_key, struct ast_rtp_instance *rtp)
{
struct ast_srtp_policy *local_policy = NULL;
struct ast_srtp_policy *remote_policy = NULL;
@@ -189,7 +203,7 @@ err:
return res;
}
-int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp, struct sip_srtp *srtp)
+int ast_sdp_crypto_process(struct ast_rtp_instance *rtp, struct ast_sdp_srtp *srtp, const char *attr)
{
char *str = NULL;
char *tag = NULL;
@@ -204,6 +218,7 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
int suite_val = 0;
unsigned char remote_key[SRTP_MASTER_LEN];
int taglen = 0;
+ struct ast_sdp_crypto *crypto = srtp->crypto;
if (!ast_rtp_engine_srtp_is_registered()) {
return -1;
@@ -211,7 +226,6 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
str = ast_strdupa(attr);
- strsep(&str, ":");
tag = strsep(&str, " ");
suite = strsep(&str, " ");
key_params = strsep(&str, " ");
@@ -229,11 +243,11 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80")) {
suite_val = AST_AES_CM_128_HMAC_SHA1_80;
- ast_set_flag(srtp, SRTP_CRYPTO_TAG_80);
+ ast_set_flag(srtp, AST_SRTP_CRYPTO_TAG_80);
taglen = 80;
} else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) {
suite_val = AST_AES_CM_128_HMAC_SHA1_32;
- ast_set_flag(srtp, SRTP_CRYPTO_TAG_32);
+ ast_set_flag(srtp, AST_SRTP_CRYPTO_TAG_32);
taglen = 32;
} else {
ast_log(LOG_WARNING, "Unsupported crypto suite: %s\n", suite);
@@ -271,48 +285,98 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
return -1;
}
- if (!memcmp(p->remote_key, remote_key, sizeof(p->remote_key))) {
+ if (!memcmp(crypto->remote_key, remote_key, sizeof(crypto->remote_key))) {
ast_debug(1, "SRTP remote key unchanged; maintaining current policy\n");
+ ast_set_flag(srtp, AST_SRTP_CRYPTO_OFFER_OK);
return 0;
}
- memcpy(p->remote_key, remote_key, sizeof(p->remote_key));
+ memcpy(crypto->remote_key, remote_key, sizeof(crypto->remote_key));
- if (sdp_crypto_activate(p, suite_val, remote_key, rtp) < 0) {
+ if (crypto_activate(crypto, suite_val, remote_key, rtp) < 0) {
return -1;
}
- if (!p->tag) {
- ast_log(LOG_DEBUG, "Accepting crypto tag %s\n", tag);
- p->tag = ast_strdup(tag);
- if (!p->tag) {
+ if (!crypto->tag) {
+ ast_debug(1, "Accepting crypto tag %s\n", tag);
+ crypto->tag = ast_strdup(tag);
+ if (!crypto->tag) {
ast_log(LOG_ERROR, "Could not allocate memory for tag\n");
return -1;
}
}
/* Finally, rebuild the crypto line */
- return sdp_crypto_offer(p, taglen);
+ if (ast_sdp_crypto_build_offer(crypto, taglen)) {
+ return -1;
+ }
+
+ ast_set_flag(srtp, AST_SRTP_CRYPTO_OFFER_OK);
+ return 0;
}
-int sdp_crypto_offer(struct sdp_crypto *p, int taglen)
+int ast_sdp_crypto_build_offer(struct ast_sdp_crypto *p, int taglen)
{
/* Rebuild the crypto line */
if (p->a_crypto) {
ast_free(p->a_crypto);
}
- if (ast_asprintf(&p->a_crypto, "a=crypto:%s AES_CM_128_HMAC_SHA1_%i inline:%s\r\n",
+ if (ast_asprintf(&p->a_crypto, "%s AES_CM_128_HMAC_SHA1_%i inline:%s",
p->tag ? p->tag : "1", taglen, p->local_key64) == -1) {
ast_log(LOG_ERROR, "Could not allocate memory for crypto line\n");
return -1;
}
- ast_log(LOG_DEBUG, "Crypto line: %s", p->a_crypto);
+ ast_debug(1, "Crypto line: a=crypto:%s\n", p->a_crypto);
return 0;
}
-const char *sdp_crypto_attrib(struct sdp_crypto *p)
+const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32)
+{
+ int taglen = default_taglen_32 ? 32 : 80;
+
+ if (!srtp) {
+ return NULL;
+ }
+
+ /* Set encryption properties */
+ if (!srtp->crypto) {
+ srtp->crypto = ast_sdp_crypto_alloc();
+ }
+
+ if (dtls_enabled) {
+ /* If DTLS-SRTP is enabled the key details will be pulled from TLS */
+ return NULL;
+ }
+
+ /* set the key length based on INVITE or settings */
+ if (ast_test_flag(srtp, AST_SRTP_CRYPTO_TAG_80)) {
+ taglen = 80;
+ } else if (ast_test_flag(srtp, AST_SRTP_CRYPTO_TAG_32)) {
+ taglen = 32;
+ }
+
+ if (srtp->crypto && (ast_sdp_crypto_build_offer(srtp->crypto, taglen) >= 0)) {
+ return srtp->crypto->a_crypto;
+ }
+
+ ast_log(LOG_WARNING, "No SRTP key management enabled\n");
+ return NULL;
+}
+
+char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf)
{
- return p->a_crypto;
+ struct ast_rtp_engine_dtls *dtls;
+
+ if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) {
+ return using_avpf ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP";
+ } else {
+ if (using_avpf) {
+ return sdes_active ? "RTP/SAVPF" : "RTP/AVPF";
+ } else {
+ return sdes_active ? "RTP/SAVP" : "RTP/AVP";
+ }
+ }
}
+
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(&note, 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,
+ );