diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c index 9e939a0f44dbbaeb61e5977e8c530e01f7b51ec8..6a80651cf858627602dfbf148e93772fe82c505d 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 c207e24fec999682cf8175c920fb91366c4692f7..689b43a05a901620fc1394a3625263f3baa24bda 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 da1035e87852b445cbeff4343bf17c1312ec6160..0000000000000000000000000000000000000000 --- 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 0adde37f2024e6a8c91d4beb935b13b5624f565b..8b4672b25de7b6035886b4e6f9120c4895118bbb 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 a4ded62ca9e81813fd25ebe979345ef35f4a235b..0000000000000000000000000000000000000000 --- 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 8b2718fc3502314a2c7514189ce3814ea2eb23bb..0000000000000000000000000000000000000000 --- 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 dd8da8dffcce94a547ab872fd06838d987d6092c..34cd6d6458549bf6a24ec69f8c2cab7702e36e14 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 b48ed9f8218ad90f8401c40f18cd59c49ff1804f..7c486aa68956cc5dc851f8a77872c9bd78306cb4 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 */ @@ -552,6 +626,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 * @@ -610,6 +694,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 * @@ -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 0000000000000000000000000000000000000000..62662f9308b0a86bfed3172d61ca0539f2d699df --- /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 33614b2853c1aa054da3a56d60de47fd600661c4..be443299ca2990d7268990ffd679b3bf9ba9011a 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 cbed5262108fe9fb95af67a26e2b2d0b16e4d40c..e4b05f7c3e3f5ef20cd18c8e5d9cf950e85ba1af 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); @@ -288,6 +301,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 * @@ -451,6 +471,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 * @@ -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 0000000000000000000000000000000000000000..9b92e0e3f0f6373c7c762c278a0e49c33417b1fe --- /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 af988dcf961efa46afcf781f5462309e2ff70666..db737327f71770e84380fd63c3c21bc80c375a5e 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 similarity index 68% rename from channels/sip/sdp_crypto.c rename to main/sdp_srtp.c index c27e882c277696f4145dc2a3b3d921c266bc8700..85dc108a6097e25b5eb4fd48b5a76f4eab1dbe90 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 1af3fb78e5ee5d81046dda04d2b031b07c442ef5..ebbf596de50ec670151e659936f0575aeedcb536 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 010f90cb1db9b80f227e1dcb106670b0cfca4f84..625a02f7ec7f17c7fb7f22c292b317d9a7ee87c0 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 0df8c66adf329ca1913a4866a205a77d4436832d..1d60274b79e3bbb83cee2ad589dfdf1703bf6b79 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 318510aae0254d3c2fd89dc14c2d1ab4e57fe45d..3625bab3138fd588e6743c59f277c637fcb24c4e 100644 --- a/res/res_sip/include/res_sip_private.h +++ b/res/res_sip/include/res_sip_private.h @@ -39,6 +39,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 * diff --git a/res/res_sip/location.c b/res/res_sip/location.c index 91521c813575ce87fd1cd218a23c4500d49a8544..d0b0a28c95c8e0d3e5c8743f254380fbe42a917d 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 0000000000000000000000000000000000000000..068e8551ffb5a03fe2b1dcc0560d13bc20a8af39 --- /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 3488d527e050dfaeb795a77f593b91318b61c881..5864bdeecc5143f8ecd22d9e79082453de69d561 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 7662610897755217f83f0b34681558ea44481e2a..db36b61821fd25e4a353f3835567ff0518e15d27 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 5e3f8edcacf584e69b42ec476fa0dda21c52bedb..4c8a9f6a7936b88f80cdaf51678efc3bab6b34a9 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 22ece0436e46d390703552c2c75d7f61f1f04329..2f40473512519078f7f9bcc3506da7d16174fd2a 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 0000000000000000000000000000000000000000..70b1fc508453fc246e6f11867305ea3b3b54d078 --- /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 c8b03d5098823b6a2025a14fd3feb27e8d54ba2f..1954c695eaed35e2de31ee598aea94c72483ee53 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 0000000000000000000000000000000000000000..6f947e1a1429a43a99d7e4ab02e7af28c845c5e0 --- /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 0000000000000000000000000000000000000000..069343439f118d59fe63b1c5ea7590f694b9c790 --- /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 0000000000000000000000000000000000000000..0cce6f6dda223d89a44b47579fc90fc0a6fa5e61 --- /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 0000000000000000000000000000000000000000..10d47047f15eaa42cb266310bacb9db6c2dc0e78 --- /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 0000000000000000000000000000000000000000..b574b304c234178d128b4176178534a794efb168 --- /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 9d73f37d55ce2658b67f28ef71442e2f94376e42..203ecfc5a863359f74b4ef008493464ef688350c 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 0000000000000000000000000000000000000000..78633da9a5a52b70b5f79f6cece4a6023cafb76c --- /dev/null +++ b/res/res_sip_pidf.c @@ -0,0 +1,341 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_sip</depend> + <depend>res_sip_pubsub</depend> + <depend>res_sip_exten_state</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_simple.h> +#include <pjlib.h> + +#include "asterisk/module.h" +#include "asterisk/res_sip.h" +#include "asterisk/res_sip_exten_state.h" + +enum state { + NOTIFY_OPEN, + NOTIFY_INUSE, + NOTIFY_CLOSED +}; + +static void exten_state_to_str(int state, char **statestring, char **pidfstate, + char **pidfnote, int *local_state) +{ + switch (state) { + case AST_EXTENSION_RINGING: + *statestring = "early"; + *local_state = NOTIFY_INUSE; + *pidfstate = "busy"; + *pidfnote = "Ringing"; + break; + case AST_EXTENSION_INUSE: + *statestring = "confirmed"; + *local_state = NOTIFY_INUSE; + *pidfstate = "busy"; + *pidfnote = "On the phone"; + break; + case AST_EXTENSION_BUSY: + *statestring = "confirmed"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "busy"; + *pidfnote = "On the phone"; + break; + case AST_EXTENSION_UNAVAILABLE: + *statestring = "terminated"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "away"; + *pidfnote = "Unavailable"; + break; + case AST_EXTENSION_ONHOLD: + *statestring = "confirmed"; + *local_state = NOTIFY_CLOSED; + *pidfstate = "busy"; + *pidfnote = "On hold"; + break; + case AST_EXTENSION_NOT_INUSE: + default: + /* Default setting */ + *statestring = "terminated"; + *local_state = NOTIFY_OPEN; + *pidfstate = "--"; + *pidfnote ="Ready"; + + break; + } +} + +static pj_xml_attr *create_attr(pj_pool_t *pool, pj_xml_node *node, + const char *name, const char *value) +{ + pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr); + + pj_strdup2(pool, &attr->name, name); + pj_strdup2(pool, &attr->value, value); + + pj_xml_add_attr(node, attr); + return attr; +} + +static pj_xml_node *create_node(pj_pool_t *pool, pj_xml_node *parent, + const char* name) +{ + pj_xml_node *node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + + pj_strdup2(pool, &node->name, name); + + node->content.ptr = NULL; + node->content.slen = 0; + + pj_xml_add_node(parent, node); + return node; +} + +static pj_xml_attr *find_node_attr(pj_pool_t* pool, pj_xml_node *parent, + const char *node_name, const char *attr_name) +{ + pj_str_t name; + pj_xml_node *node; + pj_xml_attr *attr; + + if (!(node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) { + node = create_node(pool, parent, node_name); + } + + if (!(attr = pj_xml_find_attr(node, pj_cstr(&name, attr_name), NULL))) { + attr = create_attr(pool, node, attr_name, ""); + } + + return attr; +} + +/*! + * \internal + * \brief Adds non standard elements to the xml body + * + * This is some code that was part of the original chan_sip implementation + * that is not part of the RFC 3863 definition, but we are keeping available + * for backward compatability. The original comment stated that Eyebeam + * supports this format. + + */ +static void add_non_standard(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate) +{ + static const char *XMLNS_PP = "xmlns:pp"; + static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person"; + + static const char *XMLNS_ES = "xmlns:es"; + static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status"; + + static const char *XMLNS_EP = "xmlns:ep"; + static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person"; + + pj_xml_node *person = create_node(pool, node, "pp:person"); + pj_xml_node *status = create_node(pool, person, "status"); + + if (pidfstate[0] != '-') { + pj_xml_node *activities = create_node(pool, status, "ep:activities"); + pj_strdup2(pool, &activities->content, "ep:"); + pj_strcat2(&activities->content, pidfstate); + } + + create_attr(pool, node, XMLNS_PP, XMLNS_PERSON); + create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS); + create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON); +} + +static void release_pool(void *obj) +{ + pj_pool_t *pool = obj; + + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); +} + +static int pidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local, + const char *remote, struct ast_str **body_text) +{ + pjpidf_pres *pres; + pjpidf_tuple *tuple; + pj_str_t entity, note, id, contact, priority; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + int local_state, size; + + RAII_VAR(pj_pool_t *, pool, + pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "pidf", 1024, 1024), release_pool); + + exten_state_to_str(data->exten_state, &statestring, &pidfstate, + &pidfnote, &local_state); + + if (!(pres = pjpidf_create(pool, pj_cstr(&entity, local)))) { + ast_log(LOG_WARNING, "Unable to create PIDF presence\n"); + return -1; + } + + add_non_standard(pool, pres, pidfstate); + + if (!pjpidf_pres_add_note(pool, pres, pj_cstr(¬e, pidfnote))) { + ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n"); + return -1; + } + + if (!(tuple = pjpidf_pres_add_tuple(pool, pres, pj_cstr(&id, data->exten)))) { + ast_log(LOG_WARNING, "Unable to create PIDF tuple\n"); + return -1; + } + + pjpidf_tuple_set_contact(pool, tuple, pj_cstr(&contact, remote)); + pjpidf_tuple_set_contact_prio(pool, tuple, pj_cstr(&priority, "1")); + pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple), + (pidfstate[0] == 'b') || (local_state != NOTIFY_CLOSED)); + + if (!(size = pjpidf_print(pres, ast_str_buffer(*body_text), + ast_str_size(*body_text)))) { + ast_log(LOG_WARNING, "PIDF body text too large\n"); + return -1; + } + *(ast_str_buffer(*body_text) + size) = '\0'; + ast_str_update(*body_text); + + return 0; +} + +static struct ast_sip_exten_state_provider pidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "pidf+xml", + .body_type = "application/pidf+xml", + .create_body = pidf_xml_create_body +}; + +static int xpidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local, + const char *remote, struct ast_str **body_text) +{ + static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 }; + pjxpidf_pres *pres; + pj_xml_attr *attr; + pj_str_t name, uri; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + int local_state, size; + + RAII_VAR(pj_pool_t *, pool, + pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "pidf", 1024, 1024), release_pool); + + exten_state_to_str(data->exten_state, &statestring, &pidfstate, + &pidfnote, &local_state); + + if (!(pres = pjxpidf_create(pool, pj_cstr(&name, local)))) { + ast_log(LOG_WARNING, "Unable to create PIDF presence\n"); + return -1; + } + + attr = find_node_attr(pool, pres, "atom", "id"); + pj_strdup2(pool, &attr->value, data->exten); + + attr = find_node_attr(pool, pres, "address", "uri"); + + uri.ptr = (char*) pj_pool_alloc(pool, strlen(remote) + STR_ADDR_PARAM.slen); + pj_strcpy2( &uri, remote); + pj_strcat( &uri, &STR_ADDR_PARAM); + pj_strdup(pool, &attr->value, &uri); + + create_attr(pool, pj_xml_find_node(pres, pj_cstr(&name, "address")), + "priority", "0.80000"); + + attr = find_node_attr(pool, pres, "status", "status"); + pj_strdup2(pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "open" : + (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); + + attr = find_node_attr(pool, pres, "msnsubstatus", "substatus"); + pj_strdup2(pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "online" : + (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); + + if (!(size = pjxpidf_print(pres, ast_str_buffer(*body_text), + ast_str_size(*body_text)))) { + ast_log(LOG_WARNING, "XPIDF body text too large\n"); + return -1; + } + + *(ast_str_buffer(*body_text) + size) = '\0'; + ast_str_update(*body_text); + + return 0; +} + +static struct ast_sip_exten_state_provider xpidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "xpidf+xml", + .body_type = "application/xpidf+xml", + .create_body = xpidf_xml_create_body +}; + +static struct ast_sip_exten_state_provider cpim_pidf_xml_provider = { + .event_name = "presence", + .type = "application", + .subtype = "cpim-pidf+xml", + .body_type = "application/cpim-pidf+xml", + .create_body = xpidf_xml_create_body, +}; + +static int load_module(void) +{ + if (ast_sip_register_exten_state_provider(&pidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + pidf_xml_provider.event_name, pidf_xml_provider.body_type); + } + + if (ast_sip_register_exten_state_provider(&xpidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + xpidf_xml_provider.event_name, xpidf_xml_provider.body_type); + } + + if (ast_sip_register_exten_state_provider(&cpim_pidf_xml_provider)) { + ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s", + cpim_pidf_xml_provider.event_name, cpim_pidf_xml_provider.body_type); + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_unregister_exten_state_provider(&cpim_pidf_xml_provider); + ast_sip_unregister_exten_state_provider(&xpidf_xml_provider); + ast_sip_unregister_exten_state_provider(&pidf_xml_provider); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Extension State PIDF Provider", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_sip_pubsub.c b/res/res_sip_pubsub.c index c8a76a602bd2a97ac49e1fccaae5e87a1d5e5feb..590c96c49ac79f8bcd32468d8c4669ab7de983cb 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 55308746a817bafaa5841ce3c9698e2a9fb51e1a..0ef193d8a65f332d4c6d7dbf6edfd0de168a580a 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 0000000000000000000000000000000000000000..dfc35a3f4b3520ff6734b1661eae7c14acdc201c --- /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 c3315d0becda2c2979290c49e1f7bf5db36a5896..7661f8d9377a5453f899b063530c2a087ddd9e94 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 0000000000000000000000000000000000000000..bbfa7e118199582f8558fc1eb5023903b182e94d --- /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 b0c8ae31caf5ad3fd286ee1d47136992ac981ce4..bc150ed4a2d99be7438aa9b06cae9eb11e667f27 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 7be75ab1d88311d42f525108ebfb46e36c6c949f..9668b73e9ee1d0c60d1531d03d90cf0467931952 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 08c6f3937933b5f8195f4b1574b2b1ec971e1e79..28ed0b23998c6aef773349500e199eed3c66065c 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 0000000000000000000000000000000000000000..e83011cfcc91b18b58f2c55c7875f69ffc2e7b7f --- /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, + );