summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/res_pjsip_pubsub.h19
-rw-r--r--res/res_pjsip_publish_asterisk.c927
-rw-r--r--res/res_pjsip_pubsub.c163
-rw-r--r--res/res_pjsip_pubsub.exports.in2
4 files changed, 1096 insertions, 15 deletions
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index bd55a448f..8ad133471 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -58,9 +58,10 @@ struct ast_sip_publish_handler {
*
* \param endpoint The endpoint from whom the PUBLISH arrived.
* \param resource The resource whose state is being published.
+ * \param event_configuration The name of the event type configuration to use for this resource.
* \return Response code for the incoming PUBLISH
*/
- int (*new_publication)(struct ast_sip_endpoint *endpoint, const char *resource);
+ int (*new_publication)(struct ast_sip_endpoint *endpoint, const char *resource, const char *event_configuration);
/*!
* \brief Called when a publication has reached its expiration.
*/
@@ -99,6 +100,22 @@ struct ast_sip_publish_handler {
struct ast_sip_endpoint *ast_sip_publication_get_endpoint(struct ast_sip_publication *pub);
/*!
+ * \brief Given a publication, get the resource the publication is to
+ *
+ * \param pub The publication
+ * \return The resource
+ */
+const char *ast_sip_publication_get_resource(const struct ast_sip_publication *pub);
+
+/*!
+ * \brief Given a publication, get the configuration name for the event type in use
+ *
+ * \param pub The publication
+ * \return The configuration name
+ */
+const char *ast_sip_publication_get_event_configuration(const struct ast_sip_publication *pub);
+
+/*!
* \brief Register a publish handler
*
* \retval 0 Handler was registered successfully
diff --git a/res/res_pjsip_publish_asterisk.c b/res/res_pjsip_publish_asterisk.c
new file mode 100644
index 000000000..8d4d8510d
--- /dev/null
+++ b/res/res_pjsip_publish_asterisk.c
@@ -0,0 +1,927 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, 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_pjsip</depend>
+ <depend>res_pjsip_outbound_publish</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <regex.h>
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_outbound_publish.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+ <configInfo name="res_pjsip_publish_asterisk" language="en_US">
+ <synopsis>SIP resource for inbound and outbound Asterisk event publications</synopsis>
+ <description><para>
+ <emphasis>Inbound and outbound Asterisk event publication</emphasis>
+ </para>
+ <para>This module allows <literal>res_pjsip</literal> to send and receive Asterisk event publications.</para>
+ </description>
+ <configFile name="pjsip.conf">
+ <configObject name="asterisk-publication">
+ <synopsis>The configuration for inbound Asterisk event publication</synopsis>
+ <description><para>
+ Publish is <emphasis>COMPLETELY</emphasis> separate from the rest of
+ <literal>pjsip.conf</literal>.
+ </para></description>
+ <configOption name="devicestate_publish">
+ <synopsis>Optional name of a publish item that can be used to publish a request for full device state information.</synopsis>
+ </configOption>
+ <configOption name="mailboxstate_publish">
+ <synopsis>Optional name of a publish item that can be used to publish a request for full mailbox state information.</synopsis>
+ </configOption>
+ <configOption name="device_state" default="no">
+ <synopsis>Whether we should permit incoming device state events.</synopsis>
+ </configOption>
+ <configOption name="device_state_filter">
+ <synopsis>Optional regular expression used to filter what devices we accept events for.</synopsis>
+ </configOption>
+ <configOption name="mailbox_state" default="no">
+ <synopsis>Whether we should permit incoming mailbox state events.</synopsis>
+ </configOption>
+ <configOption name="mailbox_state_filter">
+ <synopsis>Optional regular expression used to filter what mailboxes we accept events for.</synopsis>
+ </configOption>
+ <configOption name="type">
+ <synopsis>Must be of type 'asterisk-publication'.</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+ ***/
+
+/*! \brief Structure which contains Asterisk device state publisher state information */
+struct asterisk_devicestate_publisher_state {
+ /*! \brief The publish client to send PUBLISH messages on */
+ struct ast_sip_outbound_publish_client *client;
+ /*! \brief Device state subscription */
+ struct stasis_subscription *device_state_subscription;
+ /*! \brief Regex used for filtering outbound device state */
+ regex_t device_state_regex;
+ /*! \brief Device state should be filtered */
+ unsigned int device_state_filter;
+};
+
+/*! \brief Structure which contains Asterisk mailbox publisher state information */
+struct asterisk_mwi_publisher_state {
+ /*! \brief The publish client to send PUBLISH messages on */
+ struct ast_sip_outbound_publish_client *client;
+ /*! \brief Mailbox state subscription */
+ struct stasis_subscription *mailbox_state_subscription;
+ /*! \brief Regex used for filtering outbound mailbox state */
+ regex_t mailbox_state_regex;
+ /*! \brief Mailbox state should be filtered */
+ unsigned int mailbox_state_filter;
+};
+
+/*! \brief Structure which contains Asterisk publication information */
+struct asterisk_publication_config {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Stringfields */
+ AST_DECLARE_STRING_FIELDS(
+ /*! \brief Optional name of a device state publish item, used to request the remote side update us */
+ AST_STRING_FIELD(devicestate_publish);
+ /*! \brief Optional name of a mailbox state publish item, used to request the remote side update us */
+ AST_STRING_FIELD(mailboxstate_publish);
+ );
+ /*! \brief Accept inbound device state events */
+ unsigned int device_state;
+ /*! \brief Regex used for filtering inbound device state */
+ regex_t device_state_regex;
+ /*! \brief Device state should be filtered */
+ unsigned int device_state_filter;
+ /*! \brief Accept inbound mailbox state events */
+ unsigned int mailbox_state;
+ /*! \brief Regex used for filtering inbound mailbox state */
+ regex_t mailbox_state_regex;
+ /*! \brief Mailbox state should be filtered */
+ unsigned int mailbox_state_filter;
+};
+
+/*! \brief Destroy callback for Asterisk devicestate publisher state information from datastore */
+static void asterisk_devicestate_publisher_state_destroy(void *obj)
+{
+ struct asterisk_devicestate_publisher_state *publisher_state = obj;
+
+ ao2_cleanup(publisher_state->client);
+
+ if (publisher_state->device_state_filter) {
+ regfree(&publisher_state->device_state_regex);
+ }
+}
+
+/*! \brief Datastore for attaching devicestate publisher state information */
+static const struct ast_datastore_info asterisk_devicestate_publisher_state_datastore = {
+ .type = "asterisk-devicestate-publisher",
+ .destroy = asterisk_devicestate_publisher_state_destroy,
+};
+
+/*! \brief Destroy callback for Asterisk mwi publisher state information from datastore */
+static void asterisk_mwi_publisher_state_destroy(void *obj)
+{
+ struct asterisk_mwi_publisher_state *publisher_state = obj;
+
+ ao2_cleanup(publisher_state->client);
+
+ if (publisher_state->mailbox_state_filter) {
+ regfree(&publisher_state->mailbox_state_regex);
+ }
+}
+
+/*! \brief Datastore for attaching devicestate publisher state information */
+static const struct ast_datastore_info asterisk_mwi_publisher_state_datastore = {
+ .type = "asterisk-mwi-publisher",
+ .destroy = asterisk_mwi_publisher_state_destroy,
+};
+
+/*!
+ * \brief Callback function for device state events
+ * \param ast_event
+ * \param data void pointer to ast_client structure
+ * \return void
+ */
+static void asterisk_publisher_devstate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
+{
+ struct ast_datastore *datastore = data;
+ struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
+ struct ast_device_state_message *dev_state;
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!stasis_subscription_is_subscribed(sub) || ast_device_state_message_type() != stasis_message_type(msg)) {
+ return;
+ }
+
+ dev_state = stasis_message_data(msg);
+ if (!dev_state->eid || ast_eid_cmp(&ast_eid_default, dev_state->eid)) {
+ /* If the event is aggregate or didn't originate from this server, don't send it out. */
+ return;
+ }
+
+ if (publisher_state->device_state_filter && regexec(&publisher_state->device_state_regex, dev_state->device, 0, NULL, 0)) {
+ /* Outgoing device state has been filtered and the device name does not match */
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s, s: s, s: i, s:s }",
+ "type", "devicestate",
+ "device", dev_state->device,
+ "state", ast_devstate_str(dev_state->state),
+ "cachable", dev_state->cachable,
+ "eid", eid_str);
+ if (!json) {
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ return;
+ }
+ body.body_text = text;
+
+ ast_sip_publish_client_send(publisher_state->client, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+}
+
+/*!
+ * \brief Callback function for mailbox state events
+ * \param ast_event
+ * \param data void pointer to ast_client structure
+ * \return void
+ */
+static void asterisk_publisher_mwistate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
+{
+ struct ast_datastore *datastore = data;
+ struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
+ struct ast_mwi_state *mwi_state;
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!stasis_subscription_is_subscribed(sub) || ast_mwi_state_type() != stasis_message_type(msg)) {
+ return;
+ }
+
+ mwi_state = stasis_message_data(msg);
+ if (ast_eid_cmp(&ast_eid_default, &mwi_state->eid)) {
+ /* If the event is aggregate or didn't originate from this server, don't send it out. */
+ return;
+ }
+
+ if (publisher_state->mailbox_state_filter && regexec(&publisher_state->mailbox_state_regex, mwi_state->uniqueid, 0, NULL, 0)) {
+ /* Outgoing mailbox state has been filtered and the uniqueid does not match */
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s, s: i, s: i, s:s }",
+ "type", "mailboxstate",
+ "uniqueid", mwi_state->uniqueid,
+ "old", mwi_state->old_msgs,
+ "new", mwi_state->new_msgs,
+ "eid", eid_str);
+ if (!json) {
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ return;
+ }
+ body.body_text = text;
+
+ ast_sip_publish_client_send(publisher_state->client, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+}
+
+static int cached_devstate_cb(void *obj, void *arg, int flags)
+{
+ struct stasis_message *msg = obj;
+ struct ast_datastore *datastore = arg;
+ struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
+
+ asterisk_publisher_devstate_cb(arg, publisher_state->device_state_subscription, msg);
+
+ return 0;
+}
+
+static int cached_mwistate_cb(void *obj, void *arg, int flags)
+{
+ struct stasis_message *msg = obj;
+ struct ast_datastore *datastore = arg;
+ struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
+
+ asterisk_publisher_mwistate_cb(arg, publisher_state->mailbox_state_subscription, msg);
+
+ return 0;
+}
+
+static int build_regex(regex_t *regex, const char *text)
+{
+ int res;
+
+ if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
+ size_t len = regerror(res, regex, NULL, 0);
+ char buf[len];
+ regerror(res, regex, buf, len);
+ ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int asterisk_start_devicestate_publishing(struct ast_sip_outbound_publish *configuration,
+ struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ struct asterisk_devicestate_publisher_state *publisher_state;
+ const char *value;
+ struct ao2_container *cached;
+
+ datastore = ast_sip_publish_client_alloc_datastore(&asterisk_devicestate_publisher_state_datastore,
+ "asterisk-devicestate-publisher");
+ if (!datastore) {
+ return -1;
+ }
+
+ publisher_state = ast_calloc(1, sizeof(struct asterisk_devicestate_publisher_state));
+ if (!publisher_state) {
+ return -1;
+ }
+ datastore->data = publisher_state;
+
+ value = ast_sorcery_object_get_extended(configuration, "device_state_filter");
+ if (!ast_strlen_zero(value)) {
+ if (build_regex(&publisher_state->device_state_regex, value)) {
+ return -1;
+ }
+ publisher_state->device_state_filter = 1;
+ }
+
+ publisher_state->client = ao2_bump(client);
+
+ if (ast_sip_publish_client_add_datastore(client, datastore)) {
+ return -1;
+ }
+
+ publisher_state->device_state_subscription = stasis_subscribe(ast_device_state_topic_all(),
+ asterisk_publisher_devstate_cb, ao2_bump(datastore));
+ if (!publisher_state->device_state_subscription) {
+ ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
+ ao2_ref(datastore, -1);
+ return -1;
+ }
+
+ cached = stasis_cache_dump(ast_device_state_cache(), NULL);
+ ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
+ ao2_ref(cached, -1);
+
+ return 0;
+}
+
+static int asterisk_stop_devicestate_publishing(struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher"),
+ ao2_cleanup);
+ struct asterisk_devicestate_publisher_state *publisher_state;
+
+ if (!datastore) {
+ return 0;
+ }
+
+ publisher_state = datastore->data;
+ if (publisher_state->device_state_subscription) {
+ stasis_unsubscribe_and_join(publisher_state->device_state_subscription);
+ ao2_ref(datastore, -1);
+ }
+
+ ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
+
+ return 0;
+}
+
+struct ast_sip_event_publisher_handler asterisk_devicestate_publisher_handler = {
+ .event_name = "asterisk-devicestate",
+ .start_publishing = asterisk_start_devicestate_publishing,
+ .stop_publishing = asterisk_stop_devicestate_publishing,
+};
+
+static int asterisk_start_mwi_publishing(struct ast_sip_outbound_publish *configuration,
+ struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ struct asterisk_mwi_publisher_state *publisher_state;
+ const char *value;
+ struct ao2_container *cached;
+
+ datastore = ast_sip_publish_client_alloc_datastore(&asterisk_mwi_publisher_state_datastore, "asterisk-mwi-publisher");
+ if (!datastore) {
+ return -1;
+ }
+
+ publisher_state = ast_calloc(1, sizeof(struct asterisk_mwi_publisher_state));
+ if (!publisher_state) {
+ return -1;
+ }
+ datastore->data = publisher_state;
+
+ value = ast_sorcery_object_get_extended(configuration, "mailbox_state_filter");
+ if (!ast_strlen_zero(value)) {
+ if (build_regex(&publisher_state->mailbox_state_regex, value)) {
+ return -1;
+ }
+ publisher_state->mailbox_state_filter = 1;
+ }
+
+ publisher_state->client = ao2_bump(client);
+
+ if (ast_sip_publish_client_add_datastore(client, datastore)) {
+ return -1;
+ }
+
+ publisher_state->mailbox_state_subscription = stasis_subscribe(ast_mwi_topic_all(),
+ asterisk_publisher_mwistate_cb, ao2_bump(datastore));
+ if (!publisher_state->mailbox_state_subscription) {
+ ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
+ ao2_ref(datastore, -1);
+ return -1;
+ }
+
+ cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
+ ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
+ ao2_ref(cached, -1);
+
+ return 0;
+}
+
+static int asterisk_stop_mwi_publishing(struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher"),
+ ao2_cleanup);
+ struct asterisk_mwi_publisher_state *publisher_state;
+
+ if (!datastore) {
+ return 0;
+ }
+
+ publisher_state = datastore->data;
+ if (publisher_state->mailbox_state_subscription) {
+ stasis_unsubscribe_and_join(publisher_state->mailbox_state_subscription);
+ ao2_ref(datastore, -1);
+ }
+
+ ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
+
+ return 0;
+}
+
+struct ast_sip_event_publisher_handler asterisk_mwi_publisher_handler = {
+ .event_name = "asterisk-mwi",
+ .start_publishing = asterisk_start_mwi_publishing,
+ .stop_publishing = asterisk_stop_mwi_publishing,
+};
+
+static int asterisk_publication_new(struct ast_sip_endpoint *endpoint, const char *resource, const char *event_configuration)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ event_configuration), ao2_cleanup);
+
+ /* If no inbound Asterisk publication configuration exists reject the PUBLISH */
+ if (!config) {
+ return 404;
+ }
+
+ return 200;
+}
+
+static int asterisk_publication_devicestate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
+ struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ const char *device = ast_json_string_get(ast_json_object_get(json, "device"));
+ const char *state = ast_json_string_get(ast_json_object_get(json, "state"));
+ int cachable = ast_json_integer_get(ast_json_object_get(json, "cachable"));
+
+ if (!config->device_state) {
+ ast_debug(2, "Received device state event for resource '%s' but it is not configured to accept them\n",
+ ast_sorcery_object_get_id(config));
+ return 0;
+ }
+
+ if (ast_strlen_zero(device) || ast_strlen_zero(state)) {
+ ast_debug(1, "Received incomplete device state event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ if (config->device_state_filter && regexec(&config->device_state_regex, device, 0, NULL, 0)) {
+ ast_debug(2, "Received device state on resource '%s' for device '%s' but it has been filtered out\n",
+ ast_sorcery_object_get_id(config), device);
+ return 0;
+ }
+
+ ast_publish_device_state_full(device, ast_devstate_val(state),
+ cachable == AST_DEVSTATE_CACHABLE ? AST_DEVSTATE_CACHABLE : AST_DEVSTATE_NOT_CACHABLE,
+ pubsub_eid);
+
+ return 0;
+}
+
+static int asterisk_publication_mailboxstate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
+ struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ const char *uniqueid = ast_json_string_get(ast_json_object_get(json, "uniqueid"));
+ int old_msgs = ast_json_integer_get(ast_json_object_get(json, "old"));
+ int new_msgs = ast_json_integer_get(ast_json_object_get(json, "new"));
+ char *item_id;
+ const char *mailbox;
+
+ if (!config->mailbox_state) {
+ ast_debug(2, "Received mailbox state event for resource '%s' but it is not configured to accept them\n",
+ ast_sorcery_object_get_id(config));
+ return 0;
+ }
+
+ if (ast_strlen_zero(uniqueid)) {
+ ast_debug(1, "Received incomplete mailbox state event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ if (config->mailbox_state_filter && regexec(&config->mailbox_state_regex, uniqueid, 0, NULL, 0)) {
+ ast_debug(2, "Received mailbox state on resource '%s' for uniqueid '%s' but it has been filtered out\n",
+ ast_sorcery_object_get_id(config), uniqueid);
+ return 0;
+ }
+
+ item_id = ast_strdupa(uniqueid);
+ mailbox = strsep(&item_id, "@");
+
+ ast_publish_mwi_state_full(mailbox, item_id, new_msgs, old_msgs, NULL, pubsub_eid);
+
+ return 0;
+}
+
+static int asterisk_publication_devicestate_refresh(struct ast_sip_publication *pub,
+ struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ struct ast_sip_outbound_publish_client *client;
+ struct ast_datastore *datastore;
+ struct ao2_container *cached;
+
+ if (ast_strlen_zero(config->devicestate_publish)) {
+ return 0;
+ }
+
+ client = ast_sip_publish_client_get(config->devicestate_publish);
+ if (!client) {
+ ast_log(LOG_ERROR, "Received refresh request for devicestate on publication '%s' but publish '%s' is not available\n",
+ ast_sorcery_object_get_id(config), config->devicestate_publish);
+ return 0;
+ }
+
+ datastore = ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher");
+ if (!datastore) {
+ ao2_ref(client, -1);
+ return 0;
+ }
+
+ cached = stasis_cache_dump(ast_device_state_cache(), NULL);
+ if (cached) {
+ ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
+ ao2_ref(cached, -1);
+ }
+ ao2_ref(client, -1);
+ ao2_ref(datastore, -1);
+
+ return 0;
+}
+
+static int asterisk_publication_devicestate_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
+ enum ast_sip_publish_state state)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+ const char *eid, *type;
+ struct ast_eid pubsub_eid;
+ int res = -1;
+
+ /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
+ if (!config) {
+ return -1;
+ }
+
+ /* If no body exists this is a refresh and can be ignored */
+ if (!body) {
+ return 0;
+ }
+
+ /* We only accept JSON for content */
+ if (pj_strcmp2(&body->content_type.type, "application") ||
+ pj_strcmp2(&body->content_type.subtype, "json")) {
+ ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ json = ast_json_load_buf(body->data, body->len, NULL);
+ if (!json) {
+ ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ eid = ast_json_string_get(ast_json_object_get(json, "eid"));
+ if (!eid) {
+ ast_debug(1, "Received event without eid for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+ ast_str_to_eid(&pubsub_eid, eid);
+
+ type = ast_json_string_get(ast_json_object_get(json, "type"));
+ if (!type) {
+ ast_debug(1, "Received event without type for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ } else if (!strcmp(type, "devicestate")) {
+ res = asterisk_publication_devicestate(pub, config, &pubsub_eid, json);
+ } else if (!strcmp(type, "refresh")) {
+ res = asterisk_publication_devicestate_refresh(pub, config, &pubsub_eid, json);
+ }
+
+ return res;
+}
+
+static int asterisk_publication_mwi_refresh(struct ast_sip_publication *pub,
+ struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ struct ast_sip_outbound_publish_client *client;
+ struct ast_datastore *datastore;
+ struct ao2_container *cached;
+
+ if (ast_strlen_zero(config->mailboxstate_publish)) {
+ return 0;
+ }
+
+ client = ast_sip_publish_client_get(config->mailboxstate_publish);
+ if (!client) {
+ ast_log(LOG_ERROR, "Received refresh request for mwi state on publication '%s' but publish '%s' is not available\n",
+ ast_sorcery_object_get_id(config), config->mailboxstate_publish);
+ return 0;
+ }
+
+ datastore = ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher");
+ if (!datastore) {
+ ao2_ref(client, -1);
+ return 0;
+ }
+
+ cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
+ if (cached) {
+ ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
+ ao2_ref(cached, -1);
+ }
+ ao2_ref(client, -1);
+ ao2_ref(datastore, -1);
+
+ return 0;
+}
+
+static int asterisk_publication_mwi_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
+ enum ast_sip_publish_state state)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+ const char *eid, *type;
+ struct ast_eid pubsub_eid;
+ int res = -1;
+
+ /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
+ if (!config) {
+ return -1;
+ }
+
+ /* If no body exists this is a refresh and can be ignored */
+ if (!body) {
+ return 0;
+ }
+
+ /* We only accept JSON for content */
+ if (pj_strcmp2(&body->content_type.type, "application") ||
+ pj_strcmp2(&body->content_type.subtype, "json")) {
+ ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ json = ast_json_load_buf(body->data, body->len, NULL);
+ if (!json) {
+ ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ eid = ast_json_string_get(ast_json_object_get(json, "eid"));
+ if (!eid) {
+ ast_debug(1, "Received event without eid for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+ ast_str_to_eid(&pubsub_eid, eid);
+
+ type = ast_json_string_get(ast_json_object_get(json, "type"));
+ if (!type) {
+ ast_debug(1, "Received event without type for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ } else if (!strcmp(type, "mailboxstate")) {
+ res = asterisk_publication_mailboxstate(pub, config, &pubsub_eid, json);
+ } else if (!strcmp(type, "refresh")) {
+ res = asterisk_publication_mwi_refresh(pub, config, &pubsub_eid, json);
+ }
+
+ return res;
+}
+
+static int send_refresh_cb(void *obj, void *arg, int flags)
+{
+ struct asterisk_publication_config *config = obj;
+ struct ast_sip_outbound_publish_client *client;
+
+ if (!ast_strlen_zero(config->devicestate_publish)) {
+ client = ast_sip_publish_client_get(config->devicestate_publish);
+ if (client) {
+ ast_sip_publish_client_send(client, arg);
+ ao2_ref(client, -1);
+ }
+ }
+
+ if (!ast_strlen_zero(config->mailboxstate_publish)) {
+ client = ast_sip_publish_client_get(config->mailboxstate_publish);
+ if (client) {
+ ast_sip_publish_client_send(client, arg);
+ ao2_ref(client, -1);
+ }
+ }
+
+ return 0;
+}
+
+/*! \brief Internal function to send refresh requests to all publications */
+static void asterisk_publication_send_refresh(void)
+{
+ struct ao2_container *publications = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "asterisk-publication", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!publications) {
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s }",
+ "type", "refresh",
+ "eid", eid_str);
+ if (!json) {
+ ao2_ref(publications, -1);
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ ao2_ref(publications, -1);
+ return;
+ }
+ body.body_text = text;
+
+ ao2_callback(publications, OBJ_NODATA, send_refresh_cb, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+ ao2_ref(publications, -1);
+}
+
+struct ast_sip_publish_handler asterisk_devicestate_publication_handler = {
+ .event_name = "asterisk-devicestate",
+ .new_publication = asterisk_publication_new,
+ .publication_state_change = asterisk_publication_devicestate_state_change,
+};
+
+struct ast_sip_publish_handler asterisk_mwi_publication_handler = {
+ .event_name = "asterisk-mwi",
+ .new_publication = asterisk_publication_new,
+ .publication_state_change = asterisk_publication_mwi_state_change,
+};
+
+/*! \brief Destructor function for Asterisk publication configuration */
+static void asterisk_publication_config_destroy(void *obj)
+{
+ struct asterisk_publication_config *config = obj;
+
+ ast_string_field_free_memory(config);
+}
+
+/*! \brief Allocator function for Asterisk publication configuration */
+static void *asterisk_publication_config_alloc(const char *name)
+{
+ struct asterisk_publication_config *config = ast_sorcery_generic_alloc(sizeof(*config),
+ asterisk_publication_config_destroy);
+
+ if (!config || ast_string_field_init(config, 256)) {
+ ao2_cleanup(config);
+ return NULL;
+ }
+
+ return config;
+}
+
+static int regex_filter_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct asterisk_publication_config *config = obj;
+ int res = -1;
+
+ if (ast_strlen_zero(var->value)) {
+ return 0;
+ }
+
+ if (!strcmp(var->name, "device_state_filter")) {
+ if (!(res = build_regex(&config->device_state_regex, var->value))) {
+ config->device_state_filter = 1;
+ }
+ } else if (!strcmp(var->name, "mailbox_state_filter")) {
+ if (!(res = build_regex(&config->mailbox_state_regex, var->value))) {
+ config->mailbox_state_filter = 1;
+ }
+ }
+
+ return res;
+}
+
+static int load_module(void)
+{
+ ast_sorcery_apply_default(ast_sip_get_sorcery(), "asterisk-publication", "config", "pjsip.conf,criteria=type=asterisk-publication");
+
+ if (ast_sorcery_object_register(ast_sip_get_sorcery(), "asterisk-publication", asterisk_publication_config_alloc, NULL, NULL)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "devicestate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, devicestate_publish));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailboxstate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, mailboxstate_publish));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "device_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, device_state));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "device_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, mailbox_state));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
+
+ if (ast_sip_register_publish_handler(&asterisk_devicestate_publication_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
+ asterisk_devicestate_publication_handler.event_name);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_publish_handler(&asterisk_mwi_publication_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
+ asterisk_mwi_publication_handler.event_name);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_event_publisher_handler(&asterisk_devicestate_publisher_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
+ asterisk_devicestate_publisher_handler.event_name);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_event_publisher_handler(&asterisk_mwi_publisher_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
+ asterisk_mwi_publisher_handler.event_name);
+ ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ asterisk_publication_send_refresh();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_module(void)
+{
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
+ asterisk_publication_send_refresh();
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ ast_sip_unregister_event_publisher_handler(&asterisk_devicestate_publisher_handler);
+ ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Asterisk Event PUBLISH Support",
+ .load = load_module,
+ .reload = reload_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 89dd14086..6a8ec12db 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -108,6 +108,15 @@
<synopsis>The time at which the subscription expires</synopsis>
</configOption>
</configObject>
+ <configObject name="inbound-publication">
+ <synopsis>The configuration for inbound publications</synopsis>
+ <configOption name="endpoint" default="">
+ <synopsis>Optional name of an endpoint that is only allowed to publish to this resource</synopsis>
+ </configOption>
+ <configOption name="type">
+ <synopsis>Must be of type 'inbound-publication'.</synopsis>
+ </configOption>
+ </configObject>
</configFile>
</configInfo>
***/
@@ -217,6 +226,12 @@ struct ast_sip_publication {
int expires;
/*! \brief Scheduled item for expiration of publication */
int sched_id;
+ /*! \brief The resource the publication is to */
+ char *resource;
+ /*! \brief The name of the event type configuration */
+ char *event_configuration_name;
+ /*! \brief Data containing the above */
+ char data[0];
};
@@ -330,6 +345,18 @@ struct ast_sip_subscription {
char resource[0];
};
+/*!
+ * \brief Structure representing a publication resource
+ */
+struct ast_sip_publication_resource {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Optional name of an endpoint that is only allowed to publish to this resource */
+ char *endpoint;
+ /*! \brief Mapping for event types to configuration */
+ struct ast_variable *events;
+};
+
static const char *sip_subscription_roles_map[] = {
[AST_SIP_SUBSCRIBER] = "Subscriber",
[AST_SIP_NOTIFIER] = "Notifier"
@@ -350,6 +377,21 @@ static pjsip_dialog *sip_subscription_get_dlg(const struct ast_sip_subscription
return sub->reality.real.dlg;
}
+/*! \brief Destructor for publication resource */
+static void publication_resource_destroy(void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+
+ ast_free(resource->endpoint);
+ ast_variables_destroy(resource->events);
+}
+
+/*! \brief Allocator for publication resource */
+static void *publication_resource_alloc(const char *name)
+{
+ return ast_sorcery_generic_alloc(sizeof(struct ast_sip_publication_resource), publication_resource_destroy);
+}
+
/*! \brief Destructor for subscription persistence */
static void subscription_persistence_destroy(void *obj)
{
@@ -1105,7 +1147,7 @@ struct ast_datastore *ast_sip_subscription_get_datastore(struct ast_sip_subscrip
void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscription, const char *name)
{
- ao2_callback(subscription->datastores, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, NULL, (void *) name);
+ ao2_find(subscription->datastores, name, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
}
int ast_sip_publication_add_datastore(struct ast_sip_publication *publication, struct ast_datastore *datastore)
@@ -1454,14 +1496,17 @@ static void publication_destroy_fn(void *obj)
ao2_cleanup(publication->endpoint);
}
-static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
+ const char *resource, const char *event_configuration_name)
{
struct ast_sip_publication *publication;
pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+ size_t resource_len = strlen(resource) + 1, event_configuration_name_len = strlen(event_configuration_name) + 1;
+ char *dst;
ast_assert(endpoint != NULL);
- if (!(publication = ao2_alloc(sizeof(*publication), publication_destroy_fn))) {
+ if (!(publication = ao2_alloc(sizeof(*publication) + resource_len + event_configuration_name_len, publication_destroy_fn))) {
return NULL;
}
@@ -1475,6 +1520,10 @@ static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoin
publication->endpoint = endpoint;
publication->expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
publication->sched_id = -1;
+ dst = publication->data;
+ publication->resource = strcpy(dst, resource);
+ dst += resource_len;
+ publication->event_configuration_name = strcpy(dst, event_configuration_name);
return publication;
}
@@ -1521,8 +1570,10 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
struct ast_sip_publish_handler *handler)
{
struct ast_sip_publication *publication;
- char *resource;
+ char *resource_name;
size_t resource_size;
+ RAII_VAR(struct ast_sip_publication_resource *, resource, NULL, ao2_cleanup);
+ struct ast_variable *event_configuration_name = NULL;
pjsip_uri *request_uri;
pjsip_sip_uri *request_uri_sip;
int resp;
@@ -1540,17 +1591,39 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource = alloca(resource_size);
- ast_copy_pj_str(resource, &request_uri_sip->user, resource_size);
+ resource_name = alloca(resource_size);
+ ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size);
- resp = handler->new_publication(endpoint, resource);
+ resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
+ if (!resource) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+ return NULL;
+ }
- if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
+ if (!ast_strlen_zero(resource->endpoint) && strcmp(resource->endpoint, ast_sorcery_object_get_id(endpoint))) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return NULL;
+ }
+
+ for (event_configuration_name = resource->events; event_configuration_name; event_configuration_name = event_configuration_name->next) {
+ if (!strcmp(event_configuration_name->name, handler->event_name)) {
+ break;
+ }
+ }
+
+ if (!event_configuration_name) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+ return NULL;
+ }
+
+ resp = handler->new_publication(endpoint, resource_name, event_configuration_name->value);
+
+ if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
return NULL;
}
- publication = sip_create_publication(endpoint, rdata);
+ publication = sip_create_publication(endpoint, rdata, S_OR(resource_name, ""), event_configuration_name->value);
if (!publication) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, NULL, NULL);
@@ -1574,7 +1647,9 @@ static int publish_expire_callback(void *data)
{
RAII_VAR(struct ast_sip_publication *, publication, data, ao2_cleanup);
- publication->handler->publish_expire(publication);
+ if (publication->handler->publish_expire) {
+ publication->handler->publish_expire(publication);
+ }
return 0;
}
@@ -1603,7 +1678,7 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
pjsip_generic_string_hdr *etag_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_sip_if_match, NULL);
enum sip_publish_type publish_type;
RAII_VAR(struct ast_sip_publication *, publication, NULL, ao2_cleanup);
- int expires = 0, entity_id;
+ int expires = 0, entity_id, response = 0;
endpoint = ast_pjsip_rdata_get_endpoint(rdata);
ast_assert(endpoint != NULL);
@@ -1647,19 +1722,18 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
publication = publish_request_initial(endpoint, rdata, handler);
break;
case SIP_PUBLISH_REFRESH:
- sip_publication_respond(publication, 200, rdata);
case SIP_PUBLISH_MODIFY:
if (handler->publication_state_change(publication, rdata->msg_info.msg->body,
AST_SIP_PUBLISH_STATE_ACTIVE)) {
/* If an error occurs we want to terminate the publication */
expires = 0;
}
- sip_publication_respond(publication, 200, rdata);
+ response = 200;
break;
case SIP_PUBLISH_REMOVE:
handler->publication_state_change(publication, rdata->msg_info.msg->body,
AST_SIP_PUBLISH_STATE_TERMINATED);
- sip_publication_respond(publication, 200, rdata);
+ response = 200;
break;
case SIP_PUBLISH_UNKNOWN:
default:
@@ -1678,6 +1752,10 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
}
}
+ if (response) {
+ sip_publication_respond(publication, response, rdata);
+ }
+
return PJ_TRUE;
}
@@ -1686,6 +1764,15 @@ struct ast_sip_endpoint *ast_sip_publication_get_endpoint(struct ast_sip_publica
return pub->endpoint;
}
+const char *ast_sip_publication_get_resource(const struct ast_sip_publication *pub)
+{
+ return pub->resource;
+}
+
+const char *ast_sip_publication_get_event_configuration(const struct ast_sip_publication *pub)
+{
+ return pub->event_configuration_name;
+}
int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator)
{
@@ -2047,6 +2134,40 @@ static int persistence_expires_struct2str(const void *obj, const intptr_t *args,
return (ast_asprintf(buf, "%ld", persistence->expires.tv_sec) < 0) ? -1 : 0;
}
+static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+
+ ast_free(resource->endpoint);
+ resource->endpoint = ast_strdup(var->value);
+
+ return 0;
+}
+
+static int resource_event_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+ /* The event configuration name starts with 'event_' so skip past it to get the real name */
+ const char *event = var->name + 6;
+ struct ast_variable *item;
+
+ if (ast_strlen_zero(event) || ast_strlen_zero(var->value)) {
+ return -1;
+ }
+
+ item = ast_variable_new(event, var->value, "");
+ if (!item) {
+ return -1;
+ }
+
+ if (resource->events) {
+ item->next = resource->events;
+ }
+ resource->events = item;
+
+ return 0;
+}
+
static int load_module(void)
{
static const pj_str_t str_PUBLISH = { "PUBLISH", 7 };
@@ -2103,6 +2224,20 @@ static int load_module(void)
ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
persistence_expires_str2struct, persistence_expires_struct2str, NULL, 0, 0);
+ ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
+ if (ast_sorcery_object_register(sorcery, "inbound-publication", publication_resource_alloc,
+ NULL, NULL)) {
+ ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
+ ast_sip_unregister_service(&pubsub_module);
+ ast_sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_sorcery_object_field_register(sorcery, "inbound-publication", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, "inbound-publication", "endpoint", "",
+ resource_endpoint_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_fields_register(sorcery, "inbound-publication", "^event_", resource_event_handler, NULL);
+ ast_sorcery_reload_object(sorcery, "inbound-publication");
+
if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
ast_sip_push_task(NULL, subscription_persistence_load, NULL);
} else {
diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in
index ca165af92..2a6b75f00 100644
--- a/res/res_pjsip_pubsub.exports.in
+++ b/res/res_pjsip_pubsub.exports.in
@@ -15,6 +15,8 @@
LINKER_SYMBOL_PREFIXast_sip_unregister_subscription_handler;
LINKER_SYMBOL_PREFIXast_sip_create_publication;
LINKER_SYMBOL_PREFIXast_sip_publication_get_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_publication_get_resource;
+ LINKER_SYMBOL_PREFIXast_sip_publication_get_event_configuration;
LINKER_SYMBOL_PREFIXast_sip_publication_create_response;
LINKER_SYMBOL_PREFIXast_sip_publication_send_response;
LINKER_SYMBOL_PREFIXast_sip_register_publish_handler;