diff --git a/include/asterisk/res_pjsip_body_generator_types.h b/include/asterisk/res_pjsip_body_generator_types.h new file mode 100644 index 0000000000..f27e8968a1 --- /dev/null +++ b/include/asterisk/res_pjsip_body_generator_types.h @@ -0,0 +1,61 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014, Digium, Inc. + * + * Mark Michelson + * + * 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_PJSIP_BODY_GENERATOR_TYPES_H +#define _RES_PJSIP_BODY_GENERATOR_TYPES_H + +#include "asterisk/pbx.h" + +/*! + * \brief structure used for presence XML bodies + * + * This is used for the following body types: + * \li application/pidf+xml + * \li application/xpidf+xml + * \li application/cpim-pidf+xml + */ +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; + /*! Local dialog URI */ + char local[PJSIP_MAX_URL_SIZE]; + /*! Remote dialog URI */ + char remote[PJSIP_MAX_URL_SIZE]; + /*! Allocation pool */ + pj_pool_t *pool; +}; + +/*! + * \brief Message counter used for message-summary XML bodies + * + * This is used for application/simple-message-summary bodies. + */ +struct ast_sip_message_accumulator { + /*! Number of old messages */ + int old_msgs; + /*! Number of new messages */ + int new_msgs; +}; + +#endif /* _RES_PJSIP_BODY_GENERATOR_TYPES_H */ diff --git a/include/asterisk/res_pjsip_exten_state.h b/include/asterisk/res_pjsip_exten_state.h deleted file mode 100644 index 6a324a4f44..0000000000 --- a/include/asterisk/res_pjsip_exten_state.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Asterisk -- An open source telephony toolkit. - * - * Copyright (C) 2013, Digium, Inc. - * - * Kevin Harwell - * - * 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_PJSIP_EXTEN_STATE_H -#define _RES_PJSIP_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 /* _RES_PJSIP_EXTEN_STATE_H */ diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h index 2f676f1938..0b0a49e66e 100644 --- a/include/asterisk/res_pjsip_pubsub.h +++ b/include/asterisk/res_pjsip_pubsub.h @@ -226,23 +226,22 @@ struct ast_sip_subscription_response_data { struct ast_sip_subscription_handler { /*! The name of the event this handler deals with */ const char *event_name; - /*! The types of body this handler accepts */ + /*! The types of body this handler accepts. + * + * \note This option has no bearing when the handler is used in the + * notifier role. When in a subscriber role, this header is used to + * populate the Accept: header of an outbound SUBSCRIBE request + */ const char *accept[AST_SIP_MAX_ACCEPT]; /*! - * \brief Indicates if this handler can be used as a default handler for an event type. + * \brief Default body type defined for the event package this handler handles. * * Typically, a SUBSCRIBE request will contain one or more Accept headers that tell * what format they expect the body of NOTIFY requests to use. However, every event * package is required to define a default body format type to be used if a SUBSCRIBE * request for the event contains no Accept header. - * - * If this value is non-zero, then this handler provides the default body format for - * the event package and can handle SUBSCRIBES with no Accept headers present. - * If this value is zero, then this handler provides an alternative body format - * from the default for the event package and cannot handle SUBSCRIBEs with no - * Accept header. */ - unsigned int handles_default_accept; + const char *default_accept; /*! * \brief Called when a subscription is to be destroyed * @@ -537,4 +536,176 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h */ void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler); +/*! + * \brief Pubsub body generator + * + * A body generator is responsible for taking Asterisk content + * and converting it into a body format to be placed in an outbound + * SIP NOTIFY or PUBLISH request. + */ +struct ast_sip_pubsub_body_generator { + /*! + * \brief Content type + * In "plain/text", "plain" is the type + */ + const char *type; + /*! + * \brief Content subtype + * In "plain/text", "text" is the subtype + */ + const char *subtype; + /*! + * \brief allocate body structure. + * + * Body generators will have this method called when a NOTIFY + * or PUBLISH body needs to be created. The type returned depends on + * the type of content being produced for the body. The data parameter + * is provided by the subscription handler and will vary between different + * event types. + * + * \param data The subscription data provided by the event handler + * \retval non-NULL The allocated body + * \retval NULL Failure + */ + void *(*allocate_body)(void *data); + /*! + * \brief Add content to the body of a SIP request + * + * The body of the request has already been allocated by the body generator's + * allocate_body callback. + * + * \param body The body of the SIP request. The type is determined by the + * content type. + * \param data The subscription data used to populate the body. The type is + * determined by the content type. + */ + int (*generate_body_content)(void *body, void *data); + /*! + * \brief Convert the body to a string. + * + * \param body The request body. + * \param str The converted string form of the request body + */ + void (*to_string)(void *body, struct ast_str **str); + /*! + * \brief Deallocate resources created for the body + * + * Optional callback to destroy resources allocated for the + * message body. + * + * \param body Body to be destroyed + */ + void (*destroy_body)(void *body); + AST_LIST_ENTRY(ast_sip_pubsub_body_generator) list; +}; + +/*! + * \brief Body supplement + * + * Body supplements provide additions to bodies not already + * provided by body generators. This may include proprietary + * extensions, optional content, or other nonstandard fare. + */ +struct ast_sip_pubsub_body_supplement { + /*! + * \brief Content type + * In "plain/text", "plain" is the type + */ + const char *type; + /*! + * \brief Content subtype + * In "plain/text", "text" is the subtype + */ + const char *subtype; + /*! + * \brief Add additional content to a SIP request body. + * + * A body generator will have already allocated a body and populated + * it with base data for the event. The supplement's duty is, if desired, + * to extend the body to have optional data beyond what a base RFC specifies. + * + * \param body The body of the SIP request. The type is determined by the + * body generator that allocated the body. + * \param data The subscription data used to populate the body. The type is + * determined by the content type. + */ + int (*supplement_body)(void *body, void *data); + AST_LIST_ENTRY(ast_sip_pubsub_body_supplement) list; +}; + +/*! + * \since 13.0.0 + * \brief Generate body content for a PUBLISH or NOTIFY + * + * This function takes a pre-allocated body and calls into registered body + * generators in order to fill in the body with appropriate details. + * The primary body generator will be called first, followed by the + * supplementary body generators + * + * \param content_type The content type of the body + * \param content_subtype The content subtype of the body + * \param data The data associated with body generation. + * \param[out] str The string representation of the generated body + * \retval 0 Success + * \retval non-zero Failure + */ +int ast_sip_pubsub_generate_body_content(const char *content_type, + const char *content_subtype, void *data, struct ast_str **str); + +/*! + * \since 13.0.0 + * \brief Register a body generator with the pubsub core. + * + * This may fail if an attempt is made to register a primary body supplement + * for a given content type if a primary body supplement for that content type + * has already been registered. + * + * \param generator Body generator to register + * \retval 0 Success + * \retval -1 Failure + */ +int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator); + +/*! + * \since 13.0.0 + * \brief Unregister a body generator with the pubsub core. + * + * \param generator Body generator to unregister + */ +void ast_sip_pubsub_unregister_body_generator(struct ast_sip_pubsub_body_generator *generator); + +/*! + * \since 13.0.0 + * \brief Register a body generator with the pubsub core. + * + * This may fail if an attempt is made to register a primary body supplement + * for a given content type if a primary body supplement for that content type + * has already been registered. + * + * \param generator Body generator to register + * \retval 0 Success + * \retval -1 Failure + */ +int ast_sip_pubsub_register_body_supplement(struct ast_sip_pubsub_body_supplement *supplement); + +/*! + * \since 13.0.0 + * \brief Unregister a body generator with the pubsub core. + * + * \param generator Body generator to unregister + */ +void ast_sip_pubsub_unregister_body_supplement(struct ast_sip_pubsub_body_supplement *supplement); + +/*! + * \since 13.0.0 + * \brief Get the body type used for this subscription + */ +const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub); + +/*! + * \since 13.0.0 + * \brief Get the body subtype used for this subscription + */ +const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub); + #endif /* RES_PJSIP_PUBSUB_H */ diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c new file mode 100644 index 0000000000..31e06eba4a --- /dev/null +++ b/res/res_pjsip/presence_xml.c @@ -0,0 +1,166 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell + * + * 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 + pjproject + res_pjsip + res_pjsip_pubsub + res_pjsip_exten_state + core + ***/ + +#include "asterisk.h" + +#include +#include +#include + +#include "asterisk/module.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_presence_xml.h" +#include "asterisk/res_pjsip_body_generator_types.h" + +void ast_sip_sanitize_xml(const char *input, char *output, size_t len) +{ + char *copy = ast_strdupa(input); + char *break_point; + + output[0] = '\0'; + + while ((break_point = strpbrk(copy, "<>\"&'"))) { + char to_escape = *break_point; + + *break_point = '\0'; + strncat(output, copy, len); + + switch (to_escape) { + case '<': + strncat(output, "<", len); + break; + case '>': + strncat(output, ">", len); + break; + case '"': + strncat(output, """, len); + break; + case '&': + strncat(output, "&", len); + break; + case '\'': + strncat(output, "'", len); + break; + }; + + copy = break_point + 1; + } + + /* Be sure to copy everything after the final bracket */ + if (*copy) { + strncat(output, copy, len); + } +} + +void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **pidfstate, + char **pidfnote, enum ast_sip_pidf_state *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; + } +} + +pj_xml_attr *ast_sip_presence_xml_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; +} + +pj_xml_node *ast_sip_presence_xml_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; +} + +void ast_sip_presence_xml_find_node_attr(pj_pool_t* pool, + pj_xml_node *parent, const char *node_name, const char *attr_name, + pj_xml_node **node, pj_xml_attr **attr) +{ + pj_str_t name; + + if (!(*node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) { + *node = ast_sip_presence_xml_create_node(pool, parent, node_name); + } + + if (!(*attr = pj_xml_find_attr(*node, pj_cstr(&name, attr_name), NULL))) { + *attr = ast_sip_presence_xml_create_attr(pool, *node, attr_name, ""); + } +} diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c index f312a522c8..819155f00c 100644 --- a/res/res_pjsip_exten_state.c +++ b/res/res_pjsip_exten_state.c @@ -31,7 +31,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_pubsub.h" -#include "asterisk/res_pjsip_exten_state.h" +#include "asterisk/res_pjsip_body_generator_types.h" #include "asterisk/module.h" #include "asterisk/logger.h" #include "asterisk/astobj2.h" @@ -41,43 +41,6 @@ #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 * @@ -90,12 +53,6 @@ struct exten_state_subscription { 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 */ @@ -104,42 +61,38 @@ struct exten_state_subscription { enum ast_extension_states last_exten_state; }; +#define DEFAULT_PRESENCE_BODY "application/pidf+xml" + +static void subscription_shutdown(struct ast_sip_subscription *sub); +static struct ast_sip_subscription *new_subscribe(struct ast_sip_endpoint *endpoint, + pjsip_rx_data *rdata); +static void resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata, + struct ast_sip_subscription_response_data *response_data); +static void subscription_timeout(struct ast_sip_subscription *sub); +static void subscription_terminated(struct ast_sip_subscription *sub, + pjsip_rx_data *rdata); +static void to_ami(struct ast_sip_subscription *sub, + struct ast_str **buf); + +struct ast_sip_subscription_handler presence_handler = { + .event_name = "presence", + .accept = { DEFAULT_PRESENCE_BODY, }, + .default_accept = DEFAULT_PRESENCE_BODY, + .subscription_shutdown = subscription_shutdown, + .new_subscribe = new_subscribe, + .resubscribe = resubscribe, + .subscription_timeout = subscription_timeout, + .subscription_terminated = subscription_terminated, + .to_ami = to_ami, +}; + 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 @@ -157,11 +110,6 @@ static void copy_body_types(pjsip_rx_data *rdata, 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); @@ -169,19 +117,8 @@ static struct exten_state_subscription *exten_state_subscription_alloc( 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))) { + &presence_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; @@ -204,27 +141,13 @@ static void create_send_notify(struct exten_state_subscription *exten_state_sub, 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)); + body.type = ast_sip_subscription_get_body_type(exten_state_sub->sip_sub); + body.subtype = ast_sip_subscription_get_body_subtype(exten_state_sub->sip_sub); - if (provider->create_body(exten_state_data, local, remote, &body_text)) { + if (ast_sip_pubsub_generate_body_content(body.type, body.subtype, + exten_state_data, &body_text)) { ast_log(LOG_ERROR, "Unable to create body on NOTIFY request\n"); return; } @@ -262,13 +185,19 @@ static void send_notify(struct exten_state_subscription *exten_state_sub, const { RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup); char *subtype = NULL, *message = NULL; - + pjsip_dialog *dlg; 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), }; + dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub); + ast_copy_pj_str(exten_state_data.local, &dlg->local.info_str, + sizeof(exten_state_data.local)); + ast_copy_pj_str(exten_state_data.remote, &dlg->remote.info_str, + sizeof(exten_state_data.remote)); + if ((exten_state_data.exten_state = ast_extension_state_extended( NULL, exten_state_sub->context, exten_state_sub->exten, &info)) < 0) { @@ -277,8 +206,12 @@ static void send_notify(struct exten_state_subscription *exten_state_sub, const return; } + exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "exten_state", 1024, 1024); + exten_state_data.device_state_info = info; create_send_notify(exten_state_sub, reason, evsub_state, &exten_state_data); + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), exten_state_data.pool); } struct notify_task_data { @@ -300,6 +233,7 @@ static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten { struct notify_task_data *task_data = ao2_alloc(sizeof(*task_data), notify_task_data_destructor); + struct pjsip_dialog *dlg; if (!task_data) { ast_log(LOG_WARNING, "Unable to create notify task data\n"); @@ -320,6 +254,12 @@ static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten ao2_ref(task_data->exten_state_data.device_state_info, +1); } + dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub); + ast_copy_pj_str(task_data->exten_state_data.local, &dlg->local.info_str, + sizeof(task_data->exten_state_data.local)); + ast_copy_pj_str(task_data->exten_state_data.remote, &dlg->remote.info_str, + sizeof(task_data->exten_state_data.remote)); + if ((info->exten_state == AST_EXTENSION_DEACTIVATED) || (info->exten_state == AST_EXTENSION_REMOVED)) { task_data->evsub_state = PJSIP_EVSUB_STATE_TERMINATED; @@ -334,9 +274,16 @@ static int notify_task(void *obj) { RAII_VAR(struct notify_task_data *, task_data, obj, ao2_cleanup); + /* Pool allocation has to happen here so that we allocate within a PJLIB thread */ + task_data->exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "exten_state", 1024, 1024); + 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); + + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), + task_data->exten_state_data.pool); return 0; } @@ -527,107 +474,19 @@ static void to_ami(struct ast_sip_subscription *sub, exten_state_sub->last_exten_state)); } -#define DEFAULT_PRESENCE_BODY "application/pidf+xml" - -/*! - * \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) +static int load_module(void) { - struct ast_sip_subscription_handler *handler = - ao2_alloc(sizeof(*handler), NULL); - - if (!handler) { - return NULL; - } - - handler->event_name = event_name; - handler->accept[0] = accept; - if (!strcmp(accept, DEFAULT_PRESENCE_BODY)) { - handler->handles_default_accept = 1; - } - - handler->subscription_shutdown = subscription_shutdown; - handler->new_subscribe = new_subscribe; - handler->resubscribe = resubscribe; - handler->subscription_timeout = subscription_timeout; - handler->subscription_terminated = subscription_terminated; - handler->to_ami = to_ami; - - if (ast_sip_register_subscription_handler(handler)) { + if (ast_sip_register_subscription_handler(&presence_handler)) { ast_log(LOG_WARNING, "Unable to register subscription handler %s\n", - handler->event_name); - ao2_cleanup(handler); - return NULL; + presence_handler.event_name); + return AST_MODULE_LOAD_DECLINE; } - - 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) { + ast_sip_unregister_subscription_handler(&presence_handler); return 0; } diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c index bb12aa5e9f..9137b677b6 100644 --- a/res/res_pjsip_mwi.c +++ b/res/res_pjsip_mwi.c @@ -31,6 +31,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_body_generator_types.h" #include "asterisk/module.h" #include "asterisk/logger.h" #include "asterisk/astobj2.h" @@ -43,6 +44,10 @@ AO2_GLOBAL_OBJ_STATIC(unsolicited_mwi); #define STASIS_BUCKETS 13 #define MWI_BUCKETS 53 + +#define MWI_TYPE "application" +#define MWI_SUBTYPE "simple-message-summary" + static void mwi_subscription_shutdown(struct ast_sip_subscription *sub); static struct ast_sip_subscription *mwi_new_subscribe(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata); @@ -58,8 +63,8 @@ static void mwi_to_ami(struct ast_sip_subscription *sub, struct ast_str **buf); static struct ast_sip_subscription_handler mwi_handler = { .event_name = "message-summary", - .accept = { "application/simple-message-summary", }, - .handles_default_accept = 1, + .accept = { MWI_TYPE"/"MWI_SUBTYPE, }, + .default_accept = MWI_TYPE"/"MWI_SUBTYPE, .subscription_shutdown = mwi_subscription_shutdown, .new_subscribe = mwi_new_subscribe, .resubscribe = mwi_resubscribe, @@ -223,17 +228,11 @@ static int mwi_sub_cmp(void *obj, void *arg, int flags) return strcmp(mwi_sub1->id, mwi_sub2->id) ? 0 : CMP_MATCH; } -struct message_accumulator { - int old_msgs; - int new_msgs; - const char *reason; -}; - static int get_message_count(void *obj, void *arg, int flags) { RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); struct mwi_stasis_subscription *mwi_stasis = obj; - struct message_accumulator *counter = arg; + struct ast_sip_message_accumulator *counter = arg; struct ast_mwi_state *mwi_state; msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), mwi_stasis->mailbox); @@ -366,11 +365,7 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub, pjsip_evsu static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state state, const char *reason) { const pj_str_t *reason_str_ptr = NULL; - static pjsip_media_type mwi_type = { - .type = { "application", 11 }, - .subtype = { "simple-message-summary", 22 }, - }; - struct message_accumulator counter = { + struct ast_sip_message_accumulator counter = { .old_msgs = 0, .new_msgs = 0, }; @@ -378,6 +373,13 @@ static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state stat pjsip_tx_data *tdata; pj_str_t reason_str; pj_str_t pj_body; + const char *type = sub->is_solicited ? + ast_sip_subscription_get_body_type(sub->sip_sub) : + MWI_TYPE; + const char *subtype = sub->is_solicited ? + ast_sip_subscription_get_body_subtype(sub->sip_sub) : + MWI_SUBTYPE; + pjsip_media_type mwi_type = { { 0,}, }; ao2_callback(sub->stasis_subs, OBJ_NODATA, get_message_count, &counter); @@ -385,11 +387,17 @@ static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state stat pj_cstr(&reason_str, reason); reason_str_ptr = &reason_str; } - ast_str_append(&body, 0, "Messages-Waiting: %s\r\n", counter.new_msgs ? "yes" : "no"); - ast_str_append(&body, 0, "Voice-Message: %d/%d (0/0)\r\n", counter.new_msgs, counter.old_msgs); + + if (ast_sip_pubsub_generate_body_content(type, subtype, &counter, &body)) { + ast_log(LOG_WARNING, "Unable to generate SIP MWI NOTIFY body.\n"); + return; + } + pj_cstr(&pj_body, ast_str_buffer(body)); + pj_cstr(&mwi_type.type, type); + pj_cstr(&mwi_type.subtype, subtype); - ast_debug(5, "Sending %s MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n", + ast_debug(5, "Sending %s MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n", sub->is_solicited ? "solicited" : "unsolicited", sub->id, counter.new_msgs, counter.old_msgs); diff --git a/res/res_pjsip_mwi_body_generator.c b/res/res_pjsip_mwi_body_generator.c new file mode 100644 index 0000000000..9a721dbf47 --- /dev/null +++ b/res/res_pjsip_mwi_body_generator.c @@ -0,0 +1,112 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014, Digium, Inc. + * + * Mark Michelson + * + * 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 + pjproject + res_pjsip + res_pjsip_pubsub + core + ***/ + +#include "asterisk.h" + +#include +#include +#include + +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_body_generator_types.h" +#include "asterisk/module.h" +#include "asterisk/strings.h" + +#define MWI_TYPE "application" +#define MWI_SUBTYPE "simple-message-summary" + +static void *mwi_allocate_body(void *data) +{ + struct ast_str **mwi_str; + + mwi_str = ast_malloc(sizeof(*mwi_str)); + if (!mwi_str) { + return NULL; + } + *mwi_str = ast_str_create(64); + if (!*mwi_str) { + ast_free(mwi_str); + return NULL; + } + return mwi_str; +} + +static int mwi_generate_body_content(void *body, void *data) +{ + struct ast_str **mwi = body; + struct ast_sip_message_accumulator *counter = data; + + ast_str_append(mwi, 0, "Messages-Waiting: %s\r\n", + counter->new_msgs ? "yes" : "no"); + ast_str_append(mwi, 0, "Voice-Message: %d/%d (0/0)\r\n", + counter->new_msgs, counter->old_msgs); + + return 0; +} + +static void mwi_to_string(void *body, struct ast_str **str) +{ + struct ast_str **mwi = body; + + ast_str_set(str, 0, "%s", ast_str_buffer(*mwi)); +} + +static void mwi_destroy_body(void *body) +{ + struct ast_str **mwi = body; + + ast_free(*mwi); + ast_free(mwi); +} + +static struct ast_sip_pubsub_body_generator mwi_generator = { + .type = MWI_TYPE, + .subtype = MWI_SUBTYPE, + .allocate_body = mwi_allocate_body, + .generate_body_content = mwi_generate_body_content, + .to_string = mwi_to_string, + .destroy_body = mwi_destroy_body, +}; + +static int load_module(void) +{ + if (ast_sip_pubsub_register_body_generator(&mwi_generator)) { + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_pubsub_unregister_body_generator(&mwi_generator); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP MWI resource", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_pjsip_pidf.c b/res/res_pjsip_pidf.c deleted file mode 100644 index 2a22c134a7..0000000000 --- a/res/res_pjsip_pidf.c +++ /dev/null @@ -1,398 +0,0 @@ -/* - * asterisk -- An open source telephony toolkit. - * - * Copyright (C) 2013, Digium, Inc. - * - * Kevin Harwell - * - * 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 - pjproject - res_pjsip - res_pjsip_pubsub - res_pjsip_exten_state - core - ***/ - -#include "asterisk.h" - -#include -#include -#include - -#include "asterisk/module.h" -#include "asterisk/res_pjsip.h" -#include "asterisk/res_pjsip_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 void find_node_attr(pj_pool_t* pool, pj_xml_node *parent, - const char *node_name, const char *attr_name, - pj_xml_node **node, pj_xml_attr **attr) -{ - pj_str_t name; - - 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, ""); - } -} - -/*! - * \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"); - size_t str_size = sizeof("ep:") + strlen(pidfstate); - - activities->content.ptr = pj_pool_alloc(pool, str_size); - activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size, - "ep:%s", 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); -} - -/*! - * \internal - * \brief Convert angle brackets in input into escaped forms suitable for XML - * - * \param input Raw input string - * \param output Sanitized string - * \param len Size of output buffer - */ -static void sanitize_xml(const char *input, char *output, size_t len) -{ - char *copy = ast_strdupa(input); - char *break_point; - - output[0] = '\0'; - - while ((break_point = strpbrk(copy, "<>\"&'"))) { - char to_escape = *break_point; - - *break_point = '\0'; - strncat(output, copy, len); - - switch (to_escape) { - case '<': - strncat(output, "<", len); - break; - case '>': - strncat(output, ">", len); - break; - case '"': - strncat(output, """, len); - break; - case '&': - strncat(output, "&", len); - break; - case '\'': - strncat(output, "'", len); - break; - }; - - copy = break_point + 1; - } - - /* Be sure to copy everything after the final bracket */ - if (*copy) { - strncat(output, copy, len); - } -} - -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; - char sanitized[PJSIP_MAX_URL_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; - } - - sanitize_xml(remote, sanitized, sizeof(sanitized)); - pjpidf_tuple_set_contact(pool, tuple, pj_cstr(&contact, sanitized)); - pjpidf_tuple_set_contact_prio(pool, tuple, pj_cstr(&priority, "1")); - pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple), - local_state == NOTIFY_OPEN); - - 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; - char sanitized[PJSIP_MAX_URL_SIZE]; - pj_xml_node *atom; - pj_xml_node *address; - pj_xml_node *status; - pj_xml_node *msnsubstatus; - - 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; - } - - find_node_attr(pool, pres, "atom", "id", &atom, &attr); - pj_strdup2(pool, &attr->value, data->exten); - - find_node_attr(pool, atom, "address", "uri", &address, &attr); - - sanitize_xml(remote, sanitized, sizeof(sanitized)); - - uri.ptr = (char*) pj_pool_alloc(pool, strlen(sanitized) + STR_ADDR_PARAM.slen); - pj_strcpy2( &uri, sanitized); - - pj_strcat( &uri, &STR_ADDR_PARAM); - pj_strdup(pool, &attr->value, &uri); - - create_attr(pool, address, "priority", "0.80000"); - - find_node_attr(pool, address, "status", "status", &status, &attr); - pj_strdup2(pool, &attr->value, - (local_state == NOTIFY_OPEN) ? "open" : - (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); - - find_node_attr(pool, address, "msnsubstatus", "substatus", &msnsubstatus, &attr); - 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, "PJSIP Extension State PIDF Provider", - .load = load_module, - .unload = unload_module, - .load_pri = AST_MODPRI_CHANNEL_DEPEND, -); diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c new file mode 100644 index 0000000000..50e8be9e77 --- /dev/null +++ b/res/res_pjsip_pidf_body_generator.c @@ -0,0 +1,135 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014, Digium, Inc. + * + * Mark Michelson + * + * 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 + pjproject + res_pjsip + res_pjsip_pubsub + core + ***/ + +#include "asterisk.h" + +#include +#include +#include + +#include "asterisk/module.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_presence_xml.h" +#include "asterisk/res_pjsip_body_generator_types.h" + +static void *pidf_allocate_body(void *data) +{ + struct ast_sip_exten_state_data *state_data = data; + pjpidf_pres *pres; + pj_str_t entity; + + pres = pjpidf_create(state_data->pool, pj_cstr(&entity, state_data->local)); + + return pres; +} + +static int pidf_generate_body_content(void *body, void *data) +{ + pjpidf_tuple *tuple; + pj_str_t note, id, contact, priority; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + enum ast_sip_pidf_state local_state; + char sanitized[PJSIP_MAX_URL_SIZE]; + pjpidf_pres *pres = body; + struct ast_sip_exten_state_data *state_data = data; + + ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, + &pidfstate, &pidfnote, &local_state); + + if (!pjpidf_pres_add_note(state_data->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(state_data->pool, pres, + pj_cstr(&id, state_data->exten)))) { + ast_log(LOG_WARNING, "Unable to create PIDF tuple\n"); + return -1; + } + + ast_sip_sanitize_xml(state_data->remote, sanitized, sizeof(sanitized)); + pjpidf_tuple_set_contact(state_data->pool, tuple, pj_cstr(&contact, sanitized)); + pjpidf_tuple_set_contact_prio(state_data->pool, tuple, pj_cstr(&priority, "1")); + pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple), + local_state == NOTIFY_OPEN); + + return 0; +} + +#define MAX_STRING_GROWTHS 3 + +static void pidf_to_string(void *body, struct ast_str **str) +{ + int size; + int growths = 0; + pjpidf_pres *pres = body; + + do { + size = pjpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str) - 1); + if (size < 0) { + ast_str_make_space(str, ast_str_size(*str) * 2); + ++growths; + return; + } + } while (size < 0 && growths < MAX_STRING_GROWTHS); + + if (size < 0) { + ast_log(LOG_WARNING, "PIDF body text too large\n"); + return; + } + + *(ast_str_buffer(*str) + size) = '\0'; + ast_str_update(*str); +} + +static struct ast_sip_pubsub_body_generator pidf_body_generator = { + .type = "application", + .subtype = "pidf+xml", + .allocate_body = pidf_allocate_body, + .generate_body_content = pidf_generate_body_content, + .to_string = pidf_to_string, + /* No need for a destroy_body callback since we use a pool */ +}; + +static int load_module(void) +{ + if (ast_sip_pubsub_register_body_generator(&pidf_body_generator)) { + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_pubsub_unregister_body_generator(&pidf_body_generator); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State PIDF Provider", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +); diff --git a/res/res_pjsip_pidf_eyebeam_body_supplement.c b/res/res_pjsip_pidf_eyebeam_body_supplement.c new file mode 100644 index 0000000000..042cbf5e89 --- /dev/null +++ b/res/res_pjsip_pidf_eyebeam_body_supplement.c @@ -0,0 +1,113 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014, Digium, Inc. + * + * Mark Michelson + * + * 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 + pjproject + res_pjsip + res_pjsip_pubsub + core + ***/ + +#include "asterisk.h" + +#include +#include +#include + +#include "asterisk/module.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_presence_xml.h" +#include "asterisk/res_pjsip_body_generator_types.h" + +/*! + * \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_eyebeam(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 = ast_sip_presence_xml_create_node(pool, node, "pp:person"); + pj_xml_node *status = ast_sip_presence_xml_create_node(pool, person, "status"); + + if (pidfstate[0] != '-') { + pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, status, "ep:activities"); + size_t str_size = sizeof("ep:") + strlen(pidfstate); + + activities->content.ptr = pj_pool_alloc(pool, str_size); + activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size, + "ep:%s", pidfstate); + } + + ast_sip_presence_xml_create_attr(pool, node, XMLNS_PP, XMLNS_PERSON); + ast_sip_presence_xml_create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS); + ast_sip_presence_xml_create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON); +} + +static int pidf_supplement_body(void *body, void *data) +{ + pjpidf_pres *pres = body; + struct ast_sip_exten_state_data *state_data = data; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + enum ast_sip_pidf_state local_state; + + ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, + &pidfstate, &pidfnote, &local_state); + + add_eyebeam(state_data->pool, pres, pidfstate); + return 0; +} + +static struct ast_sip_pubsub_body_supplement pidf_supplement = { + .type = "application", + .subtype = "pidf+xml", + .supplement_body = pidf_supplement_body, +}; + +static int load_module(void) +{ + if (ast_sip_pubsub_register_body_supplement(&pidf_supplement)) { + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_pubsub_unregister_body_supplement(&pidf_supplement); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP PIDF Eyebeam supplement", + .load = load_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 5bc2cb4684..f10bf41fc3 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -81,6 +81,8 @@ static struct pjsip_module pubsub_module = { .on_rx_request = pubsub_on_rx_request, }; +#define MOD_DATA_BODY_GENERATOR "sub_body_generator" + static const pj_str_t str_event_name = { "Event", 5 }; /*! \brief Scheduler used for automatically expiring publications */ @@ -195,6 +197,8 @@ struct ast_sip_subscription { pjsip_evsub *evsub; /*! The underlying PJSIP dialog */ pjsip_dialog *dlg; + /*! Body generaator for NOTIFYs */ + struct ast_sip_pubsub_body_generator *body_generator; /*! Next item in the list */ AST_LIST_ENTRY(ast_sip_subscription) next; }; @@ -206,6 +210,9 @@ static const char *sip_subscription_roles_map[] = { AST_RWLIST_HEAD_STATIC(subscriptions, ast_sip_subscription); +AST_RWLIST_HEAD_STATIC(body_generators, ast_sip_pubsub_body_generator); +AST_RWLIST_HEAD_STATIC(body_supplements, ast_sip_pubsub_body_supplement); + static void add_subscription(struct ast_sip_subscription *obj) { SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); @@ -402,6 +409,8 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su ao2_ref(sub, -1); return NULL; } + sub->body_generator = ast_sip_mod_data_get(rdata->endpt_info.mod_data, + pubsub_module.id, MOD_DATA_BODY_GENERATOR); sub->role = role; if (role == AST_SIP_NOTIFIER) { dlg = ast_sip_create_dialog_uas(endpoint, rdata); @@ -631,31 +640,35 @@ static void sub_add_handler(struct ast_sip_subscription_handler *handler) ast_module_ref(ast_module_info->self); } -static int sub_handler_exists_for_event_name(const char *event_name) +static struct ast_sip_subscription_handler *find_sub_handler_for_event_name(const char *event_name) { struct ast_sip_subscription_handler *iter; SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) { if (!strcmp(iter->event_name, event_name)) { - return 1; + break; } } - return 0; + return iter; } int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler) { + pj_str_t event; pj_str_t accept[AST_SIP_MAX_ACCEPT]; + struct ast_sip_subscription_handler *existing; int i; if (ast_strlen_zero(handler->event_name)) { - ast_log(LOG_ERROR, "No event package specifief for subscription handler. Cannot register\n"); + ast_log(LOG_ERROR, "No event package specified for subscription handler. Cannot register\n"); return -1; } - if (ast_strlen_zero(handler->accept[0])) { - ast_log(LOG_ERROR, "Subscription handler must supply at least one 'Accept' format\n"); + existing = find_sub_handler_for_event_name(handler->event_name); + if (existing) { + ast_log(LOG_ERROR, "Unable to register subscription handler for event %s." + "A handler is already registered\n", handler->event_name); return -1; } @@ -663,19 +676,12 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h pj_cstr(&accept[i], handler->accept[i]); } - if (!sub_handler_exists_for_event_name(handler->event_name)) { - pj_str_t event; - - pj_cstr(&event, handler->event_name); + 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(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept); - } + if (!strcmp(handler->event_name, "message-summary")) { + pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance()); } else { - pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module, PJSIP_H_ACCEPT, NULL, - i, accept); + pjsip_evsub_register_pkg(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept); } sub_add_handler(handler); @@ -696,48 +702,52 @@ void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler AST_RWLIST_TRAVERSE_SAFE_END; } -static struct ast_sip_subscription_handler *find_sub_handler(const char *event, char accept[AST_SIP_MAX_ACCEPT][64], size_t num_accept) +static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *content_type, + const char *content_subtype) { - struct ast_sip_subscription_handler *iter; - int match = 0; - SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); - AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) { - int i; - int j; - if (strcmp(event, iter->event_name)) { - ast_debug(3, "Event %s does not match %s\n", event, iter->event_name); - continue; - } - ast_debug(3, "Event name match: %s = %s\n", event, iter->event_name); - if (!num_accept && iter->handles_default_accept) { - /* The SUBSCRIBE contained no Accept headers, and this subscription handler - * provides the default body type, so it's a match! - */ + struct ast_sip_pubsub_body_generator *iter; + SCOPED_LOCK(lock, &body_generators, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + + AST_LIST_TRAVERSE(&body_generators, iter, list) { + if (!strcmp(iter->type, content_type) && + !strcmp(iter->subtype, content_subtype)) { break; } - for (i = 0; i < num_accept; ++i) { - for (j = 0; j < num_accept; ++j) { - if (ast_strlen_zero(iter->accept[i])) { - ast_debug(3, "Breaking because subscription handler has run out of 'accept' types\n"); - break; - } - if (!strcmp(accept[j], iter->accept[i])) { - ast_debug(3, "Accept headers match: %s = %s\n", accept[j], iter->accept[i]); - match = 1; - break; - } - ast_debug(3, "Accept %s does not match %s\n", accept[j], iter->accept[i]); - } - if (match) { - break; - } - } - if (match) { + }; + + return iter; +} + +static struct ast_sip_pubsub_body_generator *find_body_generator_accept(const char *accept) +{ + char *accept_copy = ast_strdupa(accept); + char *subtype = accept_copy; + char *type = strsep(&subtype, "/"); + + if (ast_strlen_zero(type) || ast_strlen_zero(subtype)) { + return NULL; + } + + return find_body_generator_type_subtype(type, subtype); +} + +static struct ast_sip_pubsub_body_generator *find_body_generator(char accept[AST_SIP_MAX_ACCEPT][64], + size_t num_accept) +{ + int i; + struct ast_sip_pubsub_body_generator *generator = NULL; + + for (i = 0; i < num_accept; ++i) { + generator = find_body_generator_accept(accept[i]); + if (generator) { + ast_debug(3, "Body generator %p found for accept type %s\n", generator, accept[i]); break; + } else { + ast_debug(3, "No body generator found for accept type %s\n", accept[i]); } } - return iter; + return generator; } static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) @@ -751,6 +761,7 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); struct ast_sip_subscription *sub; size_t num_accept_headers; + struct ast_sip_pubsub_body_generator *generator; endpoint = ast_pjsip_rdata_get_endpoint(rdata); ast_assert(endpoint != NULL); @@ -778,6 +789,13 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) } ast_copy_pj_str(event, &event_header->event_type, sizeof(event)); + handler = find_sub_handler_for_event_name(event); + if (!handler) { + ast_log(LOG_WARNING, "No registered subscribe handler for event %s\n", event); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL); + return PJ_TRUE; + } + accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, rdata->msg_info.msg->hdr.next); if (accept_header) { int i; @@ -787,15 +805,22 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) } num_accept_headers = accept_header->count; } else { - num_accept_headers = 0; + /* If a SUBSCRIBE contains no Accept headers, then we must assume that + * the default accept type for the event package is to be used. + */ + ast_copy_string(accept[0], handler->default_accept, sizeof(accept[0])); + num_accept_headers = 1; } - handler = find_sub_handler(event, accept, num_accept_headers); - if (!handler) { - ast_log(LOG_WARNING, "No registered subscribe handler for event %s\n", event); + generator = find_body_generator(accept, num_accept_headers); + if (!generator) { pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL); return PJ_TRUE; } + + ast_sip_mod_data_set(rdata->tp_info.pool, rdata->endpt_info.mod_data, + pubsub_module.id, MOD_DATA_BODY_GENERATOR, generator); + sub = handler->new_subscribe(endpoint, rdata); if (!sub) { pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata); @@ -1063,6 +1088,137 @@ pj_status_t ast_sip_publication_send_response(struct ast_sip_publication *pub, p return pjsip_tsx_send_msg(tsx, tdata); } +int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator) +{ + struct ast_sip_pubsub_body_generator *existing; + pj_str_t accept; + pj_size_t accept_len; + + existing = find_body_generator_type_subtype(generator->type, generator->subtype); + if (existing) { + ast_log(LOG_WARNING, "Cannot register body generator of %s/%s." + "One is already registered.\n", generator->type, generator->subtype); + return -1; + } + + AST_RWLIST_WRLOCK(&body_generators); + AST_LIST_INSERT_HEAD(&body_generators, generator, list); + AST_RWLIST_UNLOCK(&body_generators); + + /* Lengths of type and subtype plus space for a slash. pj_str_t is not + * null-terminated, so there is no need to allocate for the extra null + * byte + */ + accept_len = strlen(generator->type) + strlen(generator->subtype) + 1; + + accept.ptr = alloca(accept_len); + accept.slen = accept_len; + /* Safe use of sprintf */ + sprintf(accept.ptr, "%s/%s", generator->type, generator->subtype); + pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module, + PJSIP_H_ACCEPT, NULL, 1, &accept); + + return 0; +} + +void ast_sip_pubsub_unregister_body_generator(struct ast_sip_pubsub_body_generator *generator) +{ + struct ast_sip_pubsub_body_generator *iter; + SCOPED_LOCK(lock, &body_generators, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&body_generators, iter, list) { + if (iter == generator) { + AST_LIST_REMOVE_CURRENT(list); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; +} + +int ast_sip_pubsub_register_body_supplement(struct ast_sip_pubsub_body_supplement *supplement) +{ + AST_RWLIST_WRLOCK(&body_supplements); + AST_RWLIST_INSERT_TAIL(&body_supplements, supplement, list); + AST_RWLIST_UNLOCK(&body_supplements); + + return 0; +} + +void ast_sip_pubsub_unregister_body_supplement(struct ast_sip_pubsub_body_supplement *supplement) +{ + struct ast_sip_pubsub_body_supplement *iter; + SCOPED_LOCK(lock, &body_supplements, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&body_supplements, iter, list) { + if (iter == supplement) { + AST_LIST_REMOVE_CURRENT(list); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; +} + +const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub) +{ + return sub->body_generator->type; +} + +const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub) +{ + return sub->body_generator->subtype; +} + +int ast_sip_pubsub_generate_body_content(const char *type, const char *subtype, + void *data, struct ast_str **str) +{ + struct ast_sip_pubsub_body_supplement *supplement; + struct ast_sip_pubsub_body_generator *generator; + int res; + void *body; + + generator = find_body_generator_type_subtype(type, subtype); + if (!generator) { + ast_log(LOG_WARNING, "Unable to find a body generator for %s/%s\n", + type, subtype); + return -1; + } + + body = generator->allocate_body(data); + if (!body) { + ast_log(LOG_WARNING, "Unable to allocate a NOTIFY body of type %s/%s\n", + type, subtype); + return -1; + } + + if (generator->generate_body_content(body, data)) { + res = -1; + goto end; + } + + AST_RWLIST_RDLOCK(&body_supplements); + AST_RWLIST_TRAVERSE(&body_supplements, supplement, list) { + if (!strcmp(generator->type, supplement->type) && + !strcmp(generator->subtype, supplement->subtype)) { + res = supplement->supplement_body(body, data); + if (res) { + break; + } + } + } + AST_RWLIST_UNLOCK(&body_supplements); + + if (!res) { + generator->to_string(body, str); + } + +end: + if (generator->destroy_body) { + generator->destroy_body(body); + } + + return res; +} + 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())) { diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in index 15838f2dc8..f279b54337 100644 --- a/res/res_pjsip_pubsub.exports.in +++ b/res/res_pjsip_pubsub.exports.in @@ -21,6 +21,14 @@ LINKER_SYMBOL_PREFIXast_sip_publication_add_datastore; LINKER_SYMBOL_PREFIXast_sip_publication_get_datastore; LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore; + LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore; + LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_generator; + LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_generator; + LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_supplement; + LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_supplement; + LINKER_SYMBOL_PREFIXast_sip_pubsub_generate_body_content; + LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_type; + LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_subtype; local: *; }; diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c new file mode 100644 index 0000000000..b68c9bba6c --- /dev/null +++ b/res/res_pjsip_xpidf_body_generator.c @@ -0,0 +1,177 @@ +/* + * asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014, Digium, Inc. + * + * Mark Michelson + * + * 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 + pjproject + res_pjsip + res_pjsip_pubsub + res_pjsip_exten_state + core + ***/ + +#include "asterisk.h" + +#include +#include +#include + +#include "asterisk/module.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_pubsub.h" +#include "asterisk/res_pjsip_presence_xml.h" +#include "asterisk/res_pjsip_body_generator_types.h" + +static void *xpidf_allocate_body(void *data) +{ + struct ast_sip_exten_state_data *state_data = data; + pjxpidf_pres *pres; + pj_str_t name; + + pres = pjxpidf_create(state_data->pool, pj_cstr(&name, state_data->local)); + return pres; +} + +static int xpidf_generate_body_content(void *body, void *data) +{ + pjxpidf_pres *pres = body; + struct ast_sip_exten_state_data *state_data = data; + static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 }; + char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL; + pj_xml_attr *attr; + enum ast_sip_pidf_state local_state; + pj_str_t uri; + char sanitized[PJSIP_MAX_URL_SIZE]; + pj_xml_node *atom; + pj_xml_node *address; + pj_xml_node *status; + pj_xml_node *msnsubstatus; + + ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring, + &pidfstate, &pidfnote, &local_state); + + ast_sip_presence_xml_find_node_attr(state_data->pool, pres, "atom", "id", + &atom, &attr); + pj_strdup2(state_data->pool, &attr->value, state_data->exten); + + ast_sip_presence_xml_find_node_attr(state_data->pool, atom, "address", + "uri", &address, &attr); + + ast_sip_sanitize_xml(state_data->remote, sanitized, sizeof(sanitized)); + + uri.ptr = (char*) pj_pool_alloc(state_data->pool, + strlen(sanitized) + STR_ADDR_PARAM.slen); + pj_strcpy2( &uri, sanitized); + pj_strcat( &uri, &STR_ADDR_PARAM); + pj_strdup(state_data->pool, &attr->value, &uri); + + ast_sip_presence_xml_create_attr(state_data->pool, address, "priority", "0.80000"); + + ast_sip_presence_xml_find_node_attr(state_data->pool, address, + "status", "status", &status, &attr); + pj_strdup2(state_data->pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "open" : + (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); + + ast_sip_presence_xml_find_node_attr(state_data->pool, address, + "msnsubstatus", "substatus", &msnsubstatus, &attr); + pj_strdup2(state_data->pool, &attr->value, + (local_state == NOTIFY_OPEN) ? "online" : + (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); + + return 0; +} + +#define MAX_STRING_GROWTHS 3 + +static void xpidf_to_string(void *body, struct ast_str **str) +{ + pjxpidf_pres *pres = body; + int growths = 0; + int size; + + do { + + size = pjxpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str)); + if (size < 0) { + ast_str_make_space(str, ast_str_size(*str) * 2); + ++growths; + return; + } + } while (size < 0 && growths < MAX_STRING_GROWTHS); + + if (size < 0) { + ast_log(LOG_WARNING, "XPIDF body text too large\n"); + return; + } + + *(ast_str_buffer(*str) + size) = '\0'; + ast_str_update(*str); +} + +static struct ast_sip_pubsub_body_generator xpidf_body_generator = { + .type = "application", + .subtype = "xpidf+xml", + .allocate_body = xpidf_allocate_body, + .generate_body_content = xpidf_generate_body_content, + .to_string = xpidf_to_string, + /* No need for a destroy_body callback since we use a pool */ +}; + +static struct ast_sip_pubsub_body_generator cpim_pidf_body_generator = { + .type = "application", + .subtype = "cpim-pidf+xml", + .allocate_body = xpidf_allocate_body, + .generate_body_content = xpidf_generate_body_content, + .to_string = xpidf_to_string, + /* No need for a destroy_body callback since we use a pool */ +}; + +static void unregister_all(void) +{ + ast_sip_pubsub_unregister_body_generator(&cpim_pidf_body_generator); + ast_sip_pubsub_unregister_body_generator(&xpidf_body_generator); +} + +static int load_module(void) +{ + if (ast_sip_pubsub_register_body_generator(&xpidf_body_generator)) { + goto fail; + } + + if (ast_sip_pubsub_register_body_generator(&cpim_pidf_body_generator)) { + goto fail; + } + + return AST_MODULE_LOAD_SUCCESS; + +fail: + unregister_all(); + return AST_MODULE_LOAD_DECLINE; +} + +static int unload_module(void) +{ + unregister_all(); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State PIDF Provider", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, +);