diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c index 2ea5f89dd1..c89d383855 100644 --- a/res/res_pjsip_mwi.c +++ b/res/res_pjsip_mwi.c @@ -42,6 +42,7 @@ struct mwi_subscription; static struct ao2_container *unsolicited_mwi; +static struct ao2_container *solicited_mwi; static char *default_voicemail_extension; @@ -119,6 +120,8 @@ struct mwi_subscription { char *aors; /*! Is the MWI solicited (i.e. Initiated with an external SUBSCRIBE) ? */ unsigned int is_solicited; + /*! True if this subscription is to be terminated */ + unsigned int terminate; /*! Identifier for the subscription. * The identifier is the same as the corresponding endpoint's stasis ID. * Used as a hash key @@ -665,7 +668,7 @@ static void send_mwi_notify(struct mwi_subscription *sub) ao2_cleanup(aor); ao2_cleanup(endpoint); - ast_sip_subscription_notify(sub->sip_sub, &data, 0); + ast_sip_subscription_notify(sub->sip_sub, &data, sub->terminate); return; } @@ -676,18 +679,22 @@ static void send_mwi_notify(struct mwi_subscription *sub) static int unsubscribe_stasis(void *obj, void *arg, int flags) { struct mwi_stasis_subscription *mwi_stasis = obj; + if (mwi_stasis->mwi_subscriber) { ast_debug(3, "Removing stasis subscription to mailbox %s\n", mwi_stasis->mailbox); mwi_stasis->mwi_subscriber = ast_mwi_unsubscribe_and_join(mwi_stasis->mwi_subscriber); } - return CMP_MATCH; } +static int create_unsolicited_mwi_subscriptions(struct ast_sip_endpoint *endpoint, + int recreate, int send_now); + static void mwi_subscription_shutdown(struct ast_sip_subscription *sub) { struct mwi_subscription *mwi_sub; struct ast_datastore *mwi_datastore; + struct ast_sip_endpoint *endpoint = NULL; mwi_datastore = ast_sip_subscription_get_datastore(sub, MWI_DATASTORE); if (!mwi_datastore) { @@ -695,10 +702,25 @@ static void mwi_subscription_shutdown(struct ast_sip_subscription *sub) } mwi_sub = mwi_datastore->data; + ao2_callback(mwi_sub->stasis_subs, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe_stasis, NULL); ast_sip_subscription_remove_datastore(sub, MWI_DATASTORE); + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", mwi_sub->id); ao2_ref(mwi_datastore, -1); + ao2_unlink(solicited_mwi, mwi_sub); + + /* + * When a solicited subscription is removed it's possible an unsolicited one + * needs to be [re-]created. Attempt to establish unsolicited MWI. + */ + if (unsolicited_mwi && endpoint) { + ao2_lock(unsolicited_mwi); + create_unsolicited_mwi_subscriptions(endpoint, 1, 1); + ao2_unlock(unsolicited_mwi); + } + + ao2_cleanup(endpoint); } static void mwi_ds_destroy(void *data) @@ -734,43 +756,165 @@ static int add_mwi_datastore(struct mwi_subscription *sub) } /*! - * \brief Determines if an endpoint is receiving unsolicited MWI for a particular mailbox. + * \internal + * \brief Determine if an MWI subscription already exists for the given endpoint/mailbox + * + * Search the given container, and attempt to find out if the given endpoint has a + * current subscription within. If so pass back the associated mwi_subscription and + * mwi_stasis_subscription objects. + * + * \note If a subscription is located then the caller is responsible for removing the + * references to the passed back mwi_subscription and mwi_stasis_subscription objects. + * + * \note Must be called with the given container already locked. * - * \param endpoint The endpoint to check - * \param mailbox The candidate mailbox - * \retval 0 The endpoint does not receive unsolicited MWI for this mailbox - * \retval 1 The endpoint receives unsolicited MWI for this mailbox + * \param container The ao2_container to search + * \param endpoint The endpoint to find + * \param mailbox The mailbox potentially subscribed + * \param mwi_sub [out] May contain the located mwi_subscription + * \param mwi_stasis [out] May contain the located mwi_stasis_subscription + * + * \retval 1 if a subscription was located, 0 otherwise */ -static int endpoint_receives_unsolicited_mwi_for_mailbox(struct ast_sip_endpoint *endpoint, - const char *mailbox) +static int has_mwi_subscription(struct ao2_container *container, + struct ast_sip_endpoint *endpoint, const char *mailbox, + struct mwi_subscription **mwi_sub, struct mwi_stasis_subscription **mwi_stasis) { struct ao2_iterator *mwi_subs; - struct mwi_subscription *mwi_sub; - const char *endpoint_id = ast_sorcery_object_get_id(endpoint); - int ret = 0; - mwi_subs = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_MULTIPLE); + *mwi_sub = NULL; + *mwi_stasis = NULL; + + mwi_subs = ao2_find(container, ast_sorcery_object_get_id(endpoint), + OBJ_SEARCH_KEY | OBJ_MULTIPLE | OBJ_NOLOCK); if (!mwi_subs) { return 0; } - for (; (mwi_sub = ao2_iterator_next(mwi_subs)) && !ret; ao2_cleanup(mwi_sub)) { - struct mwi_stasis_subscription *mwi_stasis; - - mwi_stasis = ao2_find(mwi_sub->stasis_subs, mailbox, OBJ_SEARCH_KEY); - if (mwi_stasis) { - if (endpoint->subscription.mwi.subscribe_replaces_unsolicited) { - unsubscribe_stasis(mwi_stasis, NULL, 0); - ao2_unlink(mwi_sub->stasis_subs, mwi_stasis); - } else { - ret = 1; - } - ao2_cleanup(mwi_stasis); + while ((*mwi_sub = ao2_iterator_next(mwi_subs))) { + *mwi_stasis = ao2_find((*mwi_sub)->stasis_subs, mailbox, OBJ_SEARCH_KEY); + if (*mwi_stasis) { + /* If found then caller is responsible for unrefs of passed back objects */ + break; } + ao2_ref(*mwi_sub, -1); } ao2_iterator_destroy(mwi_subs); - return ret; + return *mwi_stasis ? 1 : 0; +} + +/*! + * \internal + * \brief Allow and/or replace the unsolicited subscription + * + * Checks to see if solicited subscription is allowed. If allowed, and an + * unsolicited one exists then prepare for replacement by removing the + * current unsolicited subscription. + * + * \param endpoint The endpoint + * \param mailbox The mailbox + * + * \retval 1 if a solicited subscription is allowed for the endpoint/mailbox + * 0 otherwise + */ +static int allow_and_or_replace_unsolicited(struct ast_sip_endpoint *endpoint, const char *mailbox) +{ + struct mwi_subscription *mwi_sub; + struct mwi_stasis_subscription *mwi_stasis; + + if (!has_mwi_subscription(unsolicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) { + /* If no unsolicited subscription then allow the solicited one */ + return 1; + } + + if (!endpoint->subscription.mwi.subscribe_replaces_unsolicited) { + /* Has unsolicited subscription and can't replace, so disallow */ + ao2_ref(mwi_stasis, -1); + ao2_ref(mwi_sub, -1); + return 0; + } + + /* + * The unsolicited subscription exists, and it is allowed to be replaced. + * So, first remove the unsolicited stasis subscription, and if aggregation + * is not enabled then also remove the mwi_subscription object as well. + */ + ast_debug(1, "Unsolicited subscription being replaced by solicited for " + "endpoint '%s' mailbox '%s'\n", ast_sorcery_object_get_id(endpoint), mailbox); + + unsubscribe_stasis(mwi_stasis, NULL, 0); + ao2_unlink(mwi_sub->stasis_subs, mwi_stasis); + + if (!endpoint->subscription.mwi.aggregate) { + ao2_unlink(unsolicited_mwi, mwi_sub); + } + + ao2_ref(mwi_stasis, -1); + ao2_ref(mwi_sub, -1); + + /* This solicited subscription is replacing an unsolicited one, so allow */ + return 1; +} + +static int send_notify(void *obj, void *arg, int flags); + +/*! + * \internal + * \brief Determine if an unsolicited MWI subscription is allowed + * + * \param endpoint The endpoint + * \param mailbox The mailbox + * + * \retval 1 if an unsolicited subscription is allowed for the endpoint/mailbox + * 0 otherwise + */ +static int is_unsolicited_allowed(struct ast_sip_endpoint *endpoint, const char *mailbox) +{ + struct mwi_subscription *mwi_sub; + struct mwi_stasis_subscription *mwi_stasis; + + if (ast_strlen_zero(mailbox)) { + return 0; + } + + /* + * First check if an unsolicited subscription exists. If it does then we don't + * want to add another one. + */ + if (has_mwi_subscription(unsolicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) { + ao2_ref(mwi_stasis, -1); + ao2_ref(mwi_sub, -1); + return 0; + } + + /* + * If there is no unsolicited subscription, next check to see if a solicited + * subscription exists for the endpoint/mailbox. If not, then allow. + */ + if (!has_mwi_subscription(solicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) { + return 1; + } + + /* + * If however, a solicited subscription does exist then we'll need to see if that + * subscription is allowed to replace the unsolicited one. If is allowed to replace + * then disallow the unsolicited one. + */ + if (endpoint->subscription.mwi.subscribe_replaces_unsolicited) { + ao2_ref(mwi_stasis, -1); + ao2_ref(mwi_sub, -1); + return 0; + } + + /* Otherwise, shutdown the solicited subscription and allow the unsolicited */ + mwi_sub->terminate = 1; + send_notify(mwi_sub, NULL, 0); + + ao2_ref(mwi_stasis, -1); + ao2_ref(mwi_sub, -1); + + return 1; } /*! @@ -796,19 +940,23 @@ static int mwi_validate_for_aor(void *obj, void *arg, int flags) return 0; } + /* A reload could be taking place so lock while checking if allowed */ + ao2_lock(unsolicited_mwi); mailboxes = ast_strdupa(aor->mailboxes); while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) { if (ast_strlen_zero(mailbox)) { continue; } - if (endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox)) { + if (!allow_and_or_replace_unsolicited(endpoint, mailbox)) { ast_debug(1, "Endpoint '%s' already configured for unsolicited MWI for mailbox '%s'. " "Denying MWI subscription to %s\n", ast_sorcery_object_get_id(endpoint), mailbox, ast_sorcery_object_get_id(aor)); + ao2_unlock(unsolicited_mwi); return -1; } } + ao2_unlock(unsolicited_mwi); return 0; } @@ -954,6 +1102,7 @@ static int mwi_subscription_established(struct ast_sip_subscription *sip_sub) ast_sip_subscription_remove_datastore(sip_sub, MWI_DATASTORE); } + ao2_link(solicited_mwi, sub); ao2_cleanup(sub); ao2_cleanup(endpoint); return 0; @@ -1090,12 +1239,25 @@ static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub, } } -/*! \note Called with the unsolicited_mwi container lock held. */ -static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags) +/*! + * \internal + * \brief Create unsolicited MWI subscriptions for an endpoint + * + * \note Call with the unsolicited_mwi container lock held. + * + * \param endpoint An endpoint object + * \param recreate Whether or not unsolicited subscriptions are potentially being recreated + * \param send_now Whether or not to send a notify once the subscription is created + * + * \retval 0 + */ +static int create_unsolicited_mwi_subscriptions(struct ast_sip_endpoint *endpoint, + int recreate, int send_now) { RAII_VAR(struct mwi_subscription *, aggregate_sub, NULL, ao2_cleanup); - struct ast_sip_endpoint *endpoint = obj; - char *mailboxes, *mailbox; + char *mailboxes; + char *mailbox; + int sub_added = 0; if (ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) { return 0; @@ -1104,45 +1266,83 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags if (endpoint->subscription.mwi.aggregate) { const char *endpoint_id = ast_sorcery_object_get_id(endpoint); - /* Check if subscription exists */ + /* Check if aggregate subscription exists */ aggregate_sub = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_NOLOCK); - if (aggregate_sub) { + + /* + * If enabled there should only ever exist a single aggregate subscription object + * for an endpoint. So if it exists just return unless subscriptions are potentially + * being added back in. If that's the case then continue. + */ + if (aggregate_sub && !recreate) { return 0; } - aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL); + if (!aggregate_sub) { - return 0; + aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL); + if (!aggregate_sub) { + return 0; /* No MWI aggregation for you */ + } } } + /* Lock solicited so we don't potentially add to both containers */ + ao2_lock(solicited_mwi); + mailboxes = ast_strdupa(endpoint->subscription.mwi.mailboxes); while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) { struct mwi_subscription *sub; struct mwi_stasis_subscription *mwi_stasis_sub; - /* check if subscription exists */ - if (ast_strlen_zero(mailbox) || - (!aggregate_sub && endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox))) { + if (!is_unsolicited_allowed(endpoint, mailbox)) { continue; } sub = aggregate_sub ?: mwi_subscription_alloc(endpoint, 0, NULL); + if (!sub) { + continue; + } + mwi_stasis_sub = mwi_stasis_subscription_alloc(mailbox, sub); if (mwi_stasis_sub) { ao2_link(sub->stasis_subs, mwi_stasis_sub); ao2_ref(mwi_stasis_sub, -1); } - if (!aggregate_sub && sub) { + if (!aggregate_sub) { ao2_link_flags(unsolicited_mwi, sub, OBJ_NOLOCK); + if (send_now) { + send_notify(sub, NULL, 0); + } ao2_ref(sub, -1); } + + if (aggregate_sub && !sub_added) { + /* If aggregation track if at least one subscription has been added */ + sub_added = 1; + } } + if (aggregate_sub) { - ao2_link_flags(unsolicited_mwi, aggregate_sub, OBJ_NOLOCK); + if (ao2_container_count(aggregate_sub->stasis_subs)) { + ao2_link_flags(unsolicited_mwi, aggregate_sub, OBJ_NOLOCK); + if (send_now && sub_added) { + send_notify(aggregate_sub, NULL, 0); + } + } else { + /* No stasis subscriptions then no MWI data to aggregate */ + ao2_ref(aggregate_sub, -1); + } } + + ao2_unlock(solicited_mwi); return 0; } +static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags) +{ + return create_unsolicited_mwi_subscriptions(obj, 0, 0); +} + static int unsubscribe(void *obj, void *arg, int flags) { struct mwi_subscription *mwi_sub = obj; @@ -1347,11 +1547,20 @@ static int load_module(void) ast_log(AST_LOG_WARNING, "Failed to create MWI serializer pool. The default SIP pool will be used for MWI\n"); } + solicited_mwi = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MWI_BUCKETS, + mwi_sub_hash, NULL, mwi_sub_cmp); + if (!solicited_mwi) { + mwi_serializer_pool_shutdown(); + ast_sip_unregister_subscription_handler(&mwi_handler); + return AST_MODULE_LOAD_DECLINE; + } + unsolicited_mwi = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MWI_BUCKETS, mwi_sub_hash, NULL, mwi_sub_cmp); if (!unsolicited_mwi) { mwi_serializer_pool_shutdown(); ast_sip_unregister_subscription_handler(&mwi_handler); + ao2_ref(solicited_mwi, -1); return AST_MODULE_LOAD_DECLINE; } @@ -1384,6 +1593,8 @@ static int unload_module(void) ao2_ref(unsolicited_mwi, -1); unsolicited_mwi = NULL; + ao2_cleanup(solicited_mwi); + mwi_serializer_pool_shutdown(); ast_sip_unregister_subscription_handler(&mwi_handler);