diff --git a/CHANGES b/CHANGES index 0b440f5628..dc665ac2a8 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,10 @@ res_pjsip when dnsmgr refreshes are enabled will be automatically updated with the new IP address of a given hostname. + * A new endpoint parameter "incoming_mwi_mailbox" allows Asterisk to receive + unsolicited MWI NOTIFY requests and make them available to other modules via + the stasis message bus. + res_musiconhold ------------------ * By default, when res_musiconhold reloads or unloads, it sends a HUP signal diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 6510fcf6aa..7f30fdd0f1 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -782,6 +782,12 @@ ; The value "yes" is useful for some SIP phones ; (Cisco SPA) to be able to indicate and pick up ; ringing devices. +;incoming_mwi_mailbox = ; Mailbox name to use when incoming MWI NOTIFYs are + ; received. + ; If an MWI NOTIFY is received FROM this endpoint, + ; this mailbox will be used when notifying other modules + ; of MWI status changes. If not set, incoming MWI + ; NOTIFYs are ignored. ;==========================AUTH SECTION OPTIONS========================= ;[auth] diff --git a/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py b/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py new file mode 100644 index 0000000000..86c307ea54 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py @@ -0,0 +1,21 @@ +"""add_incoming_mwi_mailbox + +Revision ID: a1698e8bb9c5 +Revises: b83645976fdd +Create Date: 2017-09-08 13:45:18.937571 + +""" + +# revision identifiers, used by Alembic. +revision = 'a1698e8bb9c5' +down_revision = 'b83645976fdd' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('ps_endpoints', sa.Column('incoming_mwi_mailbox', sa.String(40))) + +def downgrade(): + op.drop_column('ps_endpoints', 'incoming_mwi_mailbox') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 18661dffe9..4ce966ea3c 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -792,6 +792,8 @@ struct ast_sip_endpoint { unsigned int refer_blind_progress; /*! Whether to notifies dialog-info 'early' on INUSE && RINGING state */ unsigned int notify_early_inuse_ringing; + /*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */ + AST_STRING_FIELD_EXTENDED(incoming_mwi_mailbox); }; /*! URI parameter for symmetric transport */ diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 1200eb983c..963519dc75 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -1383,4 +1383,24 @@ char *ast_generate_random_string(char *buf, size_t size); */ int ast_strings_match(const char *left, const char *op, const char *right); +/*! + * \brief Read lines from a string buffer + * \since 13.18.0 + * + * \param buffer [IN/OUT] A pointer to a char * string with either Unix or Windows line endings + * + * \return The "next" line + * + * \warning The original string and *buffer will be modified. + * + * \details + * Both '\n' and '\r\n' are treated as single delimiters but consecutive occurrances of + * the delimiters are NOT considered to be a single delimiter. This preserves blank + * lines in the input. + * + * MacOS line endings ('\r') are not supported at this time. + * + */ +char *ast_read_line_from_buffer(char **buffer); + #endif /* _ASTERISK_STRINGS_H */ diff --git a/main/strings.c b/main/strings.c index feb67ecc63..19aa2d373b 100644 --- a/main/strings.c +++ b/main/strings.c @@ -371,4 +371,23 @@ equals: return 0; } +char *ast_read_line_from_buffer(char **buffer) +{ + char *start = *buffer; + + if (!buffer || !*buffer || *(*buffer) == '\0') { + return NULL; + } + + while (*(*buffer) && *(*buffer) != '\n' ) { + (*buffer)++; + } + + *(*buffer) = '\0'; + if (*(*buffer - 1) == '\r') { + *(*buffer - 1) = '\0'; + } + (*buffer)++; + return start; +} diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 0d46a157b7..2986e1a5c9 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -988,6 +988,14 @@ on Ringing when already INUSE. + + Mailbox name to use when incoming MWI NOTIFYs are received + + If an MWI NOTIFY is received from this endpoint, + this mailbox will be used when notifying other modules of MWI status + changes. If not set, incoming MWI NOTIFYs are ignored. + + Authentication type diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 4d3c616458..437476631f 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -1958,6 +1958,7 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "incoming_mwi_mailbox", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, incoming_mwi_mailbox)); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); @@ -2121,6 +2122,9 @@ void *ast_sip_endpoint_alloc(const char *name) ao2_cleanup(endpoint); return NULL; } + + ast_string_field_init_extended(endpoint, incoming_mwi_mailbox); + if (!(endpoint->media.codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ao2_cleanup(endpoint); return NULL; diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 9f0eae241c..81b25ac2e4 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -31,6 +31,7 @@ #include #include +#include "asterisk/app.h" #include "asterisk/res_pjsip_pubsub.h" #include "asterisk/module.h" #include "asterisk/linkedlists.h" @@ -3402,12 +3403,144 @@ end: return res; } +struct simple_message_summary { + int messages_waiting; + int voice_messages_new; + int voice_messages_old; + int voice_messages_urgent_new; + int voice_messages_urgent_old; + char message_account[PJSIP_MAX_URL_SIZE]; +}; + +static int parse_simple_message_summary(char *body, + struct simple_message_summary *summary) +{ + char *line; + char *buffer; + int found_counts = 0; + + if (ast_strlen_zero(body) || !summary) { + return -1; + } + + buffer = ast_strdupa(body); + memset(summary, 0, sizeof(*summary)); + + while ((line = ast_read_line_from_buffer(&buffer))) { + line = ast_str_to_lower(line); + + if (sscanf(line, "voice-message: %d/%d (%d/%d)", + &summary->voice_messages_new, &summary->voice_messages_old, + &summary->voice_messages_urgent_new, &summary->voice_messages_urgent_old)) { + found_counts = 1; + } else { + sscanf(line, "message-account: %s", summary->message_account); + } + } + + return !found_counts; +} + +static pj_bool_t pubsub_on_rx_mwi_notify_request(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + struct simple_message_summary summary; + const char *endpoint_name; + char *atsign; + char *context; + char *body; + char *mailbox; + int rc; + + endpoint = ast_pjsip_rdata_get_endpoint(rdata); + if (!endpoint) { + ast_debug(1, "Incoming MWI: Endpoint not found in rdata (%p)\n", rdata); + rc = 404; + goto error; + } + + endpoint_name = ast_sorcery_object_get_id(endpoint); + ast_debug(1, "Incoming MWI: Found endpoint: %s\n", endpoint_name); + if (ast_strlen_zero(endpoint->incoming_mwi_mailbox)) { + ast_debug(1, "Incoming MWI: No incoming mailbox specified for endpoint '%s'\n", endpoint_name); + ast_test_suite_event_notify("PUBSUB_NO_INCOMING_MWI_MAILBOX", + "Endpoint: %s", endpoint_name); + rc = 404; + goto error; + } + + mailbox = ast_strdupa(endpoint->incoming_mwi_mailbox); + atsign = strchr(mailbox, '@'); + if (!atsign) { + ast_debug(1, "Incoming MWI: No '@' found in endpoint %s's incoming mailbox '%s'. Can't parse context\n", + endpoint_name, endpoint->incoming_mwi_mailbox); + rc = 404; + goto error; + } + + *atsign = '\0'; + context = atsign + 1; + + body = ast_alloca(rdata->msg_info.msg->body->len + 1); + rdata->msg_info.msg->body->print_body(rdata->msg_info.msg->body, body, + rdata->msg_info.msg->body->len + 1); + + if (parse_simple_message_summary(body, &summary) != 0) { + ast_debug(1, "Incoming MWI: Endpoint: '%s' There was an issue getting message info from body '%s'\n", + ast_sorcery_object_get_id(endpoint), body); + rc = 404; + goto error; + } + + if (ast_publish_mwi_state(mailbox, context, + summary.voice_messages_new, summary.voice_messages_old)) { + ast_log(LOG_ERROR, "Incoming MWI: Endpoint: '%s' Could not publish MWI to stasis. " + "Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n", + endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account, + summary.voice_messages_new, summary.voice_messages_old, + summary.voice_messages_urgent_new, summary.voice_messages_urgent_old); + rc = 404; + } else { + ast_debug(1, "Incoming MWI: Endpoint: '%s' Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n", + endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account, + summary.voice_messages_new, summary.voice_messages_old, + summary.voice_messages_urgent_new, summary.voice_messages_urgent_old); + ast_test_suite_event_notify("PUBSUB_INCOMING_MWI_PUBLISH", + "Endpoint: %s\r\n" + "Mailbox: %s\r\n" + "MessageAccount: %s\r\n" + "VoiceMessagesNew: %d\r\n" + "VoiceMessagesOld: %d\r\n" + "VoiceMessagesUrgentNew: %d\r\n" + "VoiceMessagesUrgentOld: %d", + endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account, + summary.voice_messages_new, summary.voice_messages_old, + summary.voice_messages_urgent_new, summary.voice_messages_urgent_old); + rc = 200; + } + +error: + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, rc, NULL, NULL, NULL); + return PJ_TRUE; +} + +static pj_bool_t pubsub_on_rx_notify_request(pjsip_rx_data *rdata) +{ + if (pj_stricmp2(&rdata->msg_info.msg->body->content_type.type, "application") == 0 && + pj_stricmp2(&rdata->msg_info.msg->body->content_type.subtype, "simple-message-summary") == 0) { + return pubsub_on_rx_mwi_notify_request(rdata); + } + return PJ_FALSE; +} + static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata) { if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) { return pubsub_on_rx_subscribe_request(rdata); } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_publish_method)) { return pubsub_on_rx_publish_request(rdata); + } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_notify_method)) { + return pubsub_on_rx_notify_request(rdata); } return PJ_FALSE;