1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243 |
- /*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2013, Digium, Inc.
- *
- * Kevin Harwell <kharwell@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
- /*** MODULEINFO
- <depend>pjproject</depend>
- <depend>res_pjsip</depend>
- <support_level>core</support_level>
- ***/
- #include "asterisk.h"
- #include <pjsip.h>
- #include <pjsip_ua.h>
- #include "asterisk/cli.h"
- #include "asterisk/config.h"
- #include "asterisk/manager.h"
- #include "asterisk/module.h"
- #include "asterisk/pbx.h"
- #include "asterisk/res_pjsip.h"
- #include "asterisk/res_pjsip_session.h"
- #include "asterisk/sorcery.h"
- /*** DOCUMENTATION
- <manager name="PJSIPNotify" language="en_US">
- <synopsis>
- Send a NOTIFY to either an endpoint, an arbitrary URI, or inside a SIP dialog.
- </synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
- <parameter name="Endpoint" required="false">
- <para>The endpoint to which to send the NOTIFY.</para>
- </parameter>
- <parameter name="URI" required="false">
- <para>Abritrary URI to which to send the NOTIFY.</para>
- </parameter>
- <parameter name="channel" required="false">
- <para>Channel name to send the NOTIFY. Must be a PJSIP channel.</para>
- </parameter>
- <parameter name="Variable" required="true">
- <para>Appends variables as headers/content to the NOTIFY. If the variable is
- named <literal>Content</literal>, then the value will compose the body
- of the message if another variable sets <literal>Content-Type</literal>.
- <replaceable>name</replaceable>=<replaceable>value</replaceable></para>
- </parameter>
- </syntax>
- <description>
- <para>Sends a NOTIFY to an endpoint, an arbitrary URI, or inside a SIP dialog.</para>
- <para>All parameters for this event must be specified in the body of this
- request via multiple <literal>Variable: name=value</literal> sequences.</para>
- <note><para>One (and only one) of <literal>Endpoint</literal>,
- <literal>URI</literal>, or <literal>Channel</literal> must be specified.
- If <literal>URI</literal> is used, the default outbound endpoint will be used
- to send the message. If the default outbound endpoint isn't configured, this command
- can not send to an arbitrary URI.</para></note>
- </description>
- </manager>
- <configInfo name="res_pjsip_notify" language="en_US">
- <synopsis>Module that supports sending NOTIFY requests to endpoints from external sources</synopsis>
- <configFile name="pjsip_notify.conf">
- <configObject name="general">
- <synopsis>Unused, but reserved.</synopsis>
- </configObject>
- <configObject name="notify">
- <synopsis>Configuration of a NOTIFY request.</synopsis>
- <description>
- <para>Each key-value pair in a <literal>notify</literal>
- configuration section defines either a SIP header to send
- in the request or a line of content in the request message
- body. A key of <literal>Content</literal> is treated
- as part of the message body and is appended in sequential
- order; any other header is treated as part of the SIP
- request.</para>
- </description>
- <configOption name="">
- <synopsis>A key/value pair to add to a NOTIFY request.</synopsis>
- <description>
- <para>If the key is <literal>Content</literal>,
- it will be treated as part of the message body. Otherwise,
- it will be added as a header in the NOTIFY request.</para>
- <para>The following headers are reserved and cannot be
- specified:</para>
- <enumlist>
- <enum name="Call-ID" />
- <enum name="Contact" />
- <enum name="CSeq" />
- <enum name="To" />
- <enum name="From" />
- <enum name="Record-Route" />
- <enum name="Route" />
- <enum name="Via" />
- </enumlist>
- </description>
- </configOption>
- </configObject>
- </configFile>
- </configInfo>
- ***/
- #define CONTENT_TYPE_SIZE 64
- #define CONTENT_SIZE 512
- /*!
- * \internal
- * \brief The configuration file containing NOTIFY payload types to send.
- */
- static const char notify_config[] = "pjsip_notify.conf";
- struct notify_option_item {
- const char *name;
- const char *value;
- char buf[0];
- };
- struct notify_option {
- /*! Contains header and/or content information */
- struct ao2_container *items;
- /*! The name of the notify option */
- char name[0];
- };
- static int notify_option_hash(const void *obj, int flags)
- {
- const struct notify_option *option = obj;
- return ast_str_case_hash(flags & OBJ_KEY ? obj : option->name);
- }
- static int notify_option_cmp(void *obj, void *arg, int flags)
- {
- struct notify_option *option1 = obj;
- struct notify_option *option2 = arg;
- const char *key = flags & OBJ_KEY ? arg : option2->name;
- return strcasecmp(option1->name, key) ? 0 : CMP_MATCH;
- }
- static void notify_option_destroy(void *obj)
- {
- struct notify_option *option = obj;
- ao2_cleanup(option->items);
- }
- static void *notify_option_alloc(const char *category)
- {
- int category_size = strlen(category) + 1;
- struct notify_option *option = ao2_alloc(
- sizeof(*option) + category_size, notify_option_destroy);
- if (!option) {
- return NULL;
- }
- ast_copy_string(option->name, category, category_size);
- if (!(option->items = ao2_container_alloc_list(
- AO2_ALLOC_OPT_LOCK_NOLOCK,
- AO2_CONTAINER_ALLOC_OPT_DUPS_ALLOW, NULL, NULL))) {
- ao2_cleanup(option);
- return NULL;
- }
- return option;
- }
- static void *notify_option_find(struct ao2_container *container, const char *category)
- {
- return ao2_find(container, category, OBJ_KEY);
- }
- static int notify_option_handler(const struct aco_option *opt,
- struct ast_variable *var, void *obj)
- {
- struct notify_option *option = obj;
- int name_size = strlen(var->name) + 1;
- int value_size = strlen(var->value) + 1;
- RAII_VAR(struct notify_option_item *, item,
- ao2_alloc(sizeof(*item) + name_size + value_size,
- NULL), ao2_cleanup);
- item->name = item->buf;
- item->value = item->buf + name_size;
- ast_copy_string(item->buf, var->name, name_size);
- ast_copy_string(item->buf + name_size, var->value, value_size);
- if (!ao2_link(option->items, item)) {
- return -1;
- }
- return 0;
- }
- struct notify_cfg {
- struct ao2_container *notify_options;
- };
- static void notify_cfg_destroy(void *obj)
- {
- struct notify_cfg *cfg = obj;
- ao2_cleanup(cfg->notify_options);
- }
- static void *notify_cfg_alloc(void)
- {
- struct notify_cfg *cfg;
- if (!(cfg = ao2_alloc(sizeof(*cfg), notify_cfg_destroy))) {
- return NULL;
- }
- cfg->notify_options = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
- 20, notify_option_hash, NULL, notify_option_cmp);
- if (!cfg->notify_options) {
- ao2_cleanup(cfg);
- return NULL;
- }
- return cfg;
- }
- static struct aco_type notify_option = {
- .type = ACO_ITEM,
- .name = "notify",
- .category_match = ACO_BLACKLIST_EXACT,
- .category = "general",
- .item_offset = offsetof(struct notify_cfg, notify_options),
- .item_alloc = notify_option_alloc,
- .item_find = notify_option_find
- };
- static struct aco_type *notify_options[] = ACO_TYPES(¬ify_option);
- static struct aco_file module_conf = {
- .filename = notify_config,
- .types = ACO_TYPES(¬ify_option),
- };
- AO2_GLOBAL_OBJ_STATIC(globals);
- CONFIG_INFO_STANDARD(notify_cfg, globals, notify_cfg_alloc,
- .files = ACO_FILES(&module_conf)
- );
- /*!
- * \internal
- * \brief Structure to hold task data for notifications.
- */
- struct notify_data {
- /*! The endpoint being notified */
- struct ast_sip_endpoint *endpoint;
- /*! The info of headers, types and content */
- void *info;
- /*! Function to help build notify request */
- void (*build_notify)(pjsip_tx_data *, void *);
- };
- /*!
- * \internal
- * \brief Destroy the notify CLI data releasing any resources.
- */
- static void notify_cli_data_destroy(void *obj)
- {
- struct notify_data *data = obj;
- ao2_cleanup(data->endpoint);
- ao2_cleanup(data->info);
- }
- /*!
- * \internal
- * \brief Structure to hold task data for notifications (URI variant)
- */
- struct notify_uri_data {
- char *uri;
- void *info;
- void (*build_notify)(pjsip_tx_data *, void *);
- };
- /*!
- * \internal
- * \brief Structure to hold task data for notifications (channel variant)
- */
- struct notify_channel_data {
- struct ast_sip_session *session;
- void *info;
- void (*build_notify)(pjsip_tx_data *, void *);
- };
- static void notify_cli_uri_data_destroy(void *obj)
- {
- struct notify_uri_data *data = obj;
- ast_free(data->uri);
- ao2_cleanup(data->info);
- }
- /*!
- * \internal
- * \brief Destroy the notify CLI data releasing any resources (URI variant)
- */
- static void build_cli_notify(pjsip_tx_data *tdata, void *info);
- /*!
- * \internal
- * \brief Construct a notify data object for CLI.
- */
- static struct notify_data* notify_cli_data_create(
- struct ast_sip_endpoint *endpoint, void *info)
- {
- struct notify_data *data = ao2_alloc(sizeof(*data),
- notify_cli_data_destroy);
- if (!data) {
- return NULL;
- }
- data->endpoint = endpoint;
- ao2_ref(data->endpoint, +1);
- data->info = info;
- ao2_ref(data->info, +1);
- data->build_notify = build_cli_notify;
- return data;
- }
- /*!
- * \internal
- * \brief Construct a notify URI data object for CLI.
- */
- static struct notify_uri_data* notify_cli_uri_data_create(
- const char *uri, void *info)
- {
- struct notify_uri_data *data = ao2_alloc(sizeof(*data),
- notify_cli_uri_data_destroy);
- if (!data) {
- return NULL;
- }
- data->uri = ast_strdup(uri);
- if (!data->uri) {
- ao2_ref(data, -1);
- return NULL;
- }
- data->info = info;
- ao2_ref(data->info, +1);
- data->build_notify = build_cli_notify;
- return data;
- }
- /*!
- * \internal
- * \brief Destroy the notify AMI data releasing any resources.
- */
- static void notify_ami_data_destroy(void *obj)
- {
- struct notify_data *data = obj;
- struct ast_variable *info = data->info;
- ao2_cleanup(data->endpoint);
- ast_variables_destroy(info);
- }
- /*!
- * \internal
- * \brief Destroy the notify AMI URI data releasing any resources.
- */
- static void notify_ami_uri_data_destroy(void *obj)
- {
- struct notify_uri_data *data = obj;
- struct ast_variable *info = data->info;
- ast_free(data->uri);
- ast_variables_destroy(info);
- }
- /*!
- * \internal
- * \brief Destroy the notify AMI channel data releasing any resources.
- */
- static void notify_ami_channel_data_destroy(void *obj)
- {
- struct notify_channel_data *data = obj;
- struct ast_variable *info = data->info;
- ao2_cleanup(data->session);
- ast_variables_destroy(info);
- }
- static void build_ami_notify(pjsip_tx_data *tdata, void *info);
- /*!
- * \internal
- * \brief Construct a notify data object for AMI.
- */
- static struct notify_data* notify_ami_data_create(
- struct ast_sip_endpoint *endpoint, void *info)
- {
- struct notify_data *data = ao2_alloc(sizeof(*data),
- notify_ami_data_destroy);
- if (!data) {
- return NULL;
- }
- data->endpoint = endpoint;
- ao2_ref(data->endpoint, +1);
- data->info = info;
- data->build_notify = build_ami_notify;
- return data;
- }
- /*!
- * \internal
- * \brief Construct a notify URI data object for AMI.
- */
- static struct notify_uri_data* notify_ami_uri_data_create(
- const char *uri, void *info)
- {
- struct notify_uri_data *data = ao2_alloc(sizeof(*data),
- notify_ami_uri_data_destroy);
- if (!data) {
- return NULL;
- }
- data->uri = ast_strdup(uri);
- if (!data->uri) {
- ao2_ref(data, -1);
- return NULL;
- }
- data->info = info;
- data->build_notify = build_ami_notify;
- return data;
- }
- /*!
- * \internal
- * \brief Construct a notify channel data object for AMI.
- */
- static struct notify_channel_data *notify_ami_channel_data_create(
- struct ast_sip_session *session, void *info)
- {
- struct notify_channel_data *data;
- data = ao2_alloc_options(sizeof(*data), notify_ami_channel_data_destroy,
- AO2_ALLOC_OPT_LOCK_NOLOCK);
- if (!data) {
- return NULL;
- }
- data->session = session;
- data->info = info;
- data->build_notify = build_ami_notify;
- return data;
- }
- /*!
- * \internal
- * \brief Checks if the given header name is not allowed.
- *
- * \details Some headers are not allowed to be set by the user within the
- * scope of a NOTIFY request. If the given var header name is
- * found in the "not allowed" list then return true.
- */
- static int not_allowed(const char *name)
- {
- int i;
- static const char *names[] = {
- "Call-ID",
- "Contact",
- "CSeq",
- "To",
- "From",
- "Record-Route",
- "Route",
- "Request-URI",
- "Via",
- };
- for (i = 0; i < ARRAY_LEN(names); ++i) {
- if (!strcasecmp(name, names[i])) {
- return 1;
- }
- }
- return 0;
- }
- /*!
- * \internal
- * \brief If a content type was specified add it and the content body to the
- * NOTIFY request.
- */
- static void build_notify_body(pjsip_tx_data *tdata, struct ast_str *content_type,
- struct ast_str *content)
- {
- if (content_type) {
- char *p;
- struct ast_sip_body body;
- if (content) {
- body.body_text = ast_str_buffer(content);
- }
- body.type = ast_str_buffer(content_type);
- if ((p = strchr(body.type, '/'))) {
- *p++ = '\0';
- body.subtype = p;
- }
- ast_sip_add_body(tdata, &body);
- }
- }
- /*!
- * \internal
- * \brief Build the NOTIFY request adding content or header info.
- */
- static void build_notify(pjsip_tx_data *tdata, const char *name, const char *value,
- struct ast_str **content_type, struct ast_str **content)
- {
- if (not_allowed(name)) {
- ast_log(LOG_WARNING, "Cannot specify %s header, "
- "ignoring\n", name);
- return;
- }
- if (!strcasecmp(name, "Content-type")) {
- if (!(*content_type)) {
- *content_type = ast_str_create(CONTENT_TYPE_SIZE);
- }
- ast_str_set(content_type, 0,"%s", value);
- } else if (!strcasecmp(name, "Content")) {
- if (!(*content)) {
- *content = ast_str_create(CONTENT_SIZE);
- }
- if (ast_str_strlen(*content)) {
- ast_str_append(content, 0, "\r\n");
- }
- ast_str_append(content, 0, "%s", value);
- } else {
- ast_sip_add_header(tdata, name, value);
- }
- }
- /*!
- * \internal
- * \brief Build the NOTIFY request from CLI info adding header and content
- * when specified.
- */
- static void build_cli_notify(pjsip_tx_data *tdata, void *info)
- {
- struct notify_option *option = info;
- RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
- RAII_VAR(struct ast_str *, content, NULL, ast_free);
- struct notify_option_item *item;
- struct ao2_iterator i = ao2_iterator_init(option->items, 0);
- while ((item = ao2_iterator_next(&i))) {
- build_notify(tdata, item->name, item->value,
- &content_type, &content);
- ao2_cleanup(item);
- }
- ao2_iterator_destroy(&i);
- build_notify_body(tdata, content_type, content);
- }
- /*!
- * \internal
- * \brief Build the NOTIFY request from AMI info adding header and content
- * when specified.
- */
- static void build_ami_notify(pjsip_tx_data *tdata, void *info)
- {
- struct ast_variable *vars = info;
- RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
- RAII_VAR(struct ast_str *, content, NULL, ast_free);
- struct ast_variable *i;
- for (i = vars; i; i = i->next) {
- if (!strcasecmp(i->name, "Content-Length")) {
- ast_log(LOG_NOTICE, "It is not necessary to specify Content-Length, ignoring.\n");
- continue;
- }
- build_notify(tdata, i->name, i->value,
- &content_type, &content);
- }
- build_notify_body(tdata, content_type, content);
- }
- /*!
- * \internal
- * \brief Build and send a NOTIFY request to a contact.
- */
- static int notify_contact(void *obj, void *arg, int flags)
- {
- struct ast_sip_contact *contact = obj;
- struct notify_data *data = arg;
- pjsip_tx_data *tdata;
- if (ast_sip_create_request("NOTIFY", NULL, data->endpoint,
- NULL, contact, &tdata)) {
- ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
- "contact %s\n", contact->uri);
- return -1;
- }
- ast_sip_add_header(tdata, "Subscription-State", "terminated");
- data->build_notify(tdata, data->info);
- if (ast_sip_send_request(tdata, NULL, data->endpoint, NULL, NULL)) {
- ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
- "contact %s\n", contact->uri);
- return -1;
- }
- return 0;
- }
- /*!
- * \internal
- * \brief Send a NOTIFY request to the endpoint.
- *
- * \detail Iterates over an endpoint's AORs sending a NOTIFY request
- * with the appropriate payload information to each contact.
- */
- static int notify_endpoint(void *obj)
- {
- RAII_VAR(struct notify_data *, data, obj, ao2_cleanup);
- char *aor_name, *aors;
- if (ast_strlen_zero(data->endpoint->aors)) {
- ast_log(LOG_WARNING, "Unable to NOTIFY - "
- "endpoint has no configured AORs\n");
- return -1;
- }
- aors = ast_strdupa(data->endpoint->aors);
- while ((aor_name = ast_strip(strsep(&aors, ",")))) {
- RAII_VAR(struct ast_sip_aor *, aor,
- ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
- RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
- if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
- continue;
- }
- ao2_callback(contacts, OBJ_NODATA, notify_contact, data);
- }
- return 0;
- }
- /*!
- * \internal
- * \brief Send a notify request to the URI.
- */
- static int notify_uri(void *obj)
- {
- RAII_VAR(struct notify_uri_data *, data, obj, ao2_cleanup);
- RAII_VAR(struct ast_sip_endpoint *, endpoint,
- ast_sip_default_outbound_endpoint(), ao2_cleanup);
- pjsip_tx_data *tdata;
- if (!endpoint) {
- ast_log(LOG_WARNING, "No default outbound endpoint set, can not send "
- "NOTIFY requests to arbitrary URIs.\n");
- return -1;
- }
- if (ast_strlen_zero(data->uri)) {
- ast_log(LOG_WARNING, "Unable to NOTIFY - URI is blank.\n");
- return -1;
- }
- if (ast_sip_create_request("NOTIFY", NULL, endpoint,
- data->uri, NULL, &tdata)) {
- ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
- "uri %s\n", data->uri);
- return -1;
- }
- ast_sip_add_header(tdata, "Subscription-State", "terminated");
- data->build_notify(tdata, data->info);
- if (ast_sip_send_request(tdata, NULL, endpoint, NULL, NULL)) {
- ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
- "uri %s\n", data->uri);
- return -1;
- }
- return 0;
- }
- /*!
- * \internal
- * \brief Send a notify request to a channel.
- */
- static int notify_channel(void *obj)
- {
- RAII_VAR(struct notify_channel_data *, data, obj, ao2_cleanup);
- pjsip_tx_data *tdata;
- struct pjsip_dialog *dlg;
- if (!data->session->channel
- || !data->session->inv_session
- || data->session->inv_session->state < PJSIP_INV_STATE_EARLY
- || data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
- return -1;
- }
- ast_debug(1, "Sending notify on channel %s\n", ast_channel_name(data->session->channel));
- dlg = data->session->inv_session->dlg;
- if (ast_sip_create_request("NOTIFY", dlg, NULL, NULL, NULL, &tdata)) {
- return -1;
- }
- ast_sip_add_header(tdata, "Subscription-State", "terminated");
- data->build_notify(tdata, data->info);
- if (ast_sip_send_request(tdata, dlg, NULL, NULL, NULL)) {
- return -1;
- }
- return 0;
- }
- enum notify_result {
- SUCCESS,
- INVALID_ENDPOINT,
- INVALID_CHANNEL,
- ALLOC_ERROR,
- TASK_PUSH_ERROR
- };
- typedef struct notify_data *(*task_data_create)(
- struct ast_sip_endpoint *, void *info);
- typedef struct notify_uri_data *(*task_uri_data_create)(
- const char *uri, void *info);
- typedef struct notify_channel_data *(*task_channel_data_create)(
- struct ast_sip_session *session, void *info);
- /*!
- * \internal
- * \brief Send a NOTIFY request to the endpoint within a threaded task.
- */
- static enum notify_result push_notify(const char *endpoint_name, void *info,
- task_data_create data_create)
- {
- RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
- struct notify_data *data;
- if (!(endpoint = ast_sorcery_retrieve_by_id(
- ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
- return INVALID_ENDPOINT;
- }
- if (!(data = data_create(endpoint, info))) {
- return ALLOC_ERROR;
- }
- if (ast_sip_push_task(NULL, notify_endpoint, data)) {
- ao2_cleanup(data);
- return TASK_PUSH_ERROR;
- }
- return SUCCESS;
- }
- /*!
- * \internal
- * \brief Send a NOTIFY request to the URI within an threaded task.
- */
- static enum notify_result push_notify_uri(const char *uri, void *info,
- task_uri_data_create data_create)
- {
- struct notify_uri_data *data;
- if (!(data = data_create(uri, info))) {
- return ALLOC_ERROR;
- }
- if (ast_sip_push_task(NULL, notify_uri, data)) {
- ao2_cleanup(data);
- return TASK_PUSH_ERROR;
- }
- return SUCCESS;
- }
- /*!
- * \internal
- * \brief Send a NOTIFY request in a channel within an threaded task.
- */
- static enum notify_result push_notify_channel(const char *channel_name, void *info,
- task_channel_data_create data_create)
- {
- struct notify_channel_data *data;
- struct ast_channel *ch;
- struct ast_sip_session *session;
- struct ast_sip_channel_pvt *ch_pvt;
- /* note: this increases the refcount of the channel */
- ch = ast_channel_get_by_name(channel_name);
- if (!ch) {
- ast_debug(1, "No channel found with name %s", channel_name);
- return INVALID_CHANNEL;
- }
- if (strcmp(ast_channel_tech(ch)->type, "PJSIP")) {
- ast_log(LOG_WARNING, "Channel was a non-PJSIP channel: %s\n", channel_name);
- ast_channel_unref(ch);
- return INVALID_CHANNEL;
- }
- ast_channel_lock(ch);
- ch_pvt = ast_channel_tech_pvt(ch);
- session = ch_pvt->session;
- if (!session || !session->inv_session
- || session->inv_session->state < PJSIP_INV_STATE_EARLY
- || session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
- ast_debug(1, "No active session for channel %s\n", channel_name);
- ast_channel_unlock(ch);
- ast_channel_unref(ch);
- return INVALID_CHANNEL;
- }
- ao2_ref(session, +1);
- ast_channel_unlock(ch);
- /* don't keep a reference to the channel, we've got a reference to the session */
- ast_channel_unref(ch);
- /*
- * data_create will take ownership of the session,
- * and take care of releasing the ref.
- */
- data = data_create(session, info);
- if (!data) {
- ao2_ref(session, -1);
- return ALLOC_ERROR;
- }
- if (ast_sip_push_task(session->serializer, notify_channel, data)) {
- ao2_ref(data, -1);
- return TASK_PUSH_ERROR;
- }
- return SUCCESS;
- }
- /*!
- * \internal
- * \brief Do completion on the endpoint.
- */
- static char *cli_complete_endpoint(const char *word)
- {
- int wordlen = strlen(word);
- struct ao2_container * endpoints;
- struct ast_sip_endpoint *endpoint;
- struct ao2_iterator i;
- endpoints = ast_sorcery_retrieve_by_prefix(ast_sip_get_sorcery(),
- "endpoint", word, wordlen);
- if (endpoints == NULL) {
- return NULL;
- }
- i = ao2_iterator_init(endpoints, 0);
- while ((endpoint = ao2_iterator_next(&i))) {
- ast_cli_completion_add(
- ast_strdup(ast_sorcery_object_get_id(endpoint)));
- ao2_cleanup(endpoint);
- }
- ao2_iterator_destroy(&i);
- ao2_ref(endpoints, -1);
- return NULL;
- }
- /*!
- * \internal
- * \brief Do completion on the notify CLI command.
- */
- static char *cli_complete_notify(const char *line, const char *word,
- int pos, int state, int using_uri)
- {
- char *c = NULL;
- if (pos == 3) {
- int which = 0;
- int wordlen = strlen(word);
- RAII_VAR(struct notify_cfg *, cfg,
- ao2_global_obj_ref(globals), ao2_cleanup);
- struct notify_option *option;
- /* do completion for notify type */
- struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
- while ((option = ao2_iterator_next(&i))) {
- if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
- c = ast_strdup(option->name);
- }
- ao2_cleanup(option);
- if (c) {
- break;
- }
- }
- ao2_iterator_destroy(&i);
- return c;
- }
- if (pos == 4) {
- int wordlen = strlen(word);
- if (ast_strlen_zero(word)) {
- if (state == 0) {
- c = ast_strdup("endpoint");
- } else if (state == 1) {
- c = ast_strdup("uri");
- }
- } else if (state == 0) {
- if (!strncasecmp(word, "endpoint", wordlen)) {
- c = ast_strdup("endpoint");
- } else if (!strncasecmp(word, "uri", wordlen)) {
- c = ast_strdup("uri");
- }
- }
- return c;
- }
- return pos > 4 && !using_uri ? cli_complete_endpoint(word) : NULL;
- }
- /*!
- * \internal
- * \brief CLI command to send a SIP notify to an endpoint.
- *
- * \details Attempts to match the "type" given in the CLI command to a
- * configured one. If found, sends a NOTIFY to the endpoint
- * with the associated payload.
- */
- static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
- RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
- int i;
- int using_uri = 0;
- switch (cmd) {
- case CLI_INIT:
- e->command = "pjsip send notify";
- e->usage =
- "Usage: pjsip send notify <type> {endpoint|uri} <peer> [<peer>...]\n"
- " Send a NOTIFY request to an endpoint\n"
- " Message types are defined in pjsip_notify.conf\n";
- return NULL;
- case CLI_GENERATE:
- if (a->argc > 4 && (!strcasecmp(a->argv[4], "uri"))) {
- using_uri = 1;
- }
- return cli_complete_notify(a->line, a->word, a->pos, a->n, using_uri);
- }
- if (a->argc < 6) {
- return CLI_SHOWUSAGE;
- }
- if (!strcasecmp(a->argv[4], "uri")) {
- using_uri = 1;
- } else if (strcasecmp(a->argv[4], "endpoint")) {
- return CLI_SHOWUSAGE;
- }
- cfg = ao2_global_obj_ref(globals);
- if (!(option = notify_option_find(cfg->notify_options, a->argv[3])))
- {
- ast_cli(a->fd, "Unable to find notify type '%s'\n",
- a->argv[3]);
- return CLI_FAILURE;
- }
- for (i = 5; i < a->argc; ++i) {
- ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
- a->argv[3], a->argv[i]);
- switch (using_uri ? push_notify_uri(a->argv[i], option, notify_cli_uri_data_create) :
- push_notify(a->argv[i], option, notify_cli_data_create)) {
- case INVALID_ENDPOINT:
- ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
- a->argv[i]);
- break;
- case ALLOC_ERROR:
- ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
- return CLI_FAILURE;
- case TASK_PUSH_ERROR:
- ast_cli(a->fd, "Unable to push NOTIFY task\n");
- return CLI_FAILURE;
- default:
- break;
- }
- }
- return CLI_SUCCESS;
- }
- static struct ast_cli_entry cli_options[] = {
- AST_CLI_DEFINE(cli_notify, "Send a NOTIFY request to a SIP endpoint")
- };
- /*!
- * \interanl
- * \brief Completes SIPNotify AMI command in Endpoint mode.
- */
- static void manager_notify_endpoint(struct mansession *s,
- const struct message *m, const char *endpoint_name)
- {
- struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
- if (!strncasecmp(endpoint_name, "sip/", 4)) {
- endpoint_name += 4;
- }
- if (!strncasecmp(endpoint_name, "pjsip/", 6)) {
- endpoint_name += 6;
- }
- switch (push_notify(endpoint_name, vars, notify_ami_data_create)) {
- case INVALID_CHANNEL:
- /* Shouldn't be possible. */
- ast_assert(0);
- break;
- case INVALID_ENDPOINT:
- ast_variables_destroy(vars);
- astman_send_error_va(s, m, "Unable to retrieve endpoint %s",
- endpoint_name);
- break;
- case ALLOC_ERROR:
- ast_variables_destroy(vars);
- astman_send_error(s, m, "Unable to allocate NOTIFY task data");
- break;
- case TASK_PUSH_ERROR:
- /* Don't need to destroy vars since it is handled by cleanup in push_notify */
- astman_send_error(s, m, "Unable to push NOTIFY task");
- break;
- case SUCCESS:
- astman_send_ack(s, m, "NOTIFY sent");
- break;
- }
- }
- /*!
- * \internal
- * \brief Completes SIPNotify AMI command in URI mode.
- */
- static void manager_notify_uri(struct mansession *s,
- const struct message *m, const char *uri)
- {
- struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
- switch (push_notify_uri(uri, vars, notify_ami_uri_data_create)) {
- case INVALID_CHANNEL:
- /* Shouldn't be possible. */
- ast_assert(0);
- break;
- case INVALID_ENDPOINT:
- /* Shouldn't be possible. */
- ast_assert(0);
- break;
- case ALLOC_ERROR:
- ast_variables_destroy(vars);
- astman_send_error(s, m, "Unable to allocate NOTIFY task data");
- break;
- case TASK_PUSH_ERROR:
- /* Don't need to destroy vars since it is handled by cleanup in push_notify_uri */
- astman_send_error(s, m, "Unable to push Notify task");
- break;
- case SUCCESS:
- astman_send_ack(s, m, "NOTIFY sent");
- break;
- }
- }
- /*!
- * \internal
- * \brief Completes SIPNotify AMI command in channel mode.
- */
- static void manager_notify_channel(struct mansession *s,
- const struct message *m, const char *channel)
- {
- struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
- switch (push_notify_channel(channel, vars, notify_ami_channel_data_create)) {
- case INVALID_CHANNEL:
- ast_variables_destroy(vars);
- astman_send_error(s, m, "Channel not found");
- break;
- case INVALID_ENDPOINT:
- /* Shouldn't be possible. */
- ast_assert(0);
- break;
- case ALLOC_ERROR:
- ast_variables_destroy(vars);
- astman_send_error(s, m, "Unable to allocate NOTIFY task data");
- break;
- case TASK_PUSH_ERROR:
- /* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */
- astman_send_error(s, m, "Unable to push Notify task");
- break;
- case SUCCESS:
- astman_send_ack(s, m, "NOTIFY sent");
- break;
- }
- }
- /*!
- * \internal
- * \brief AMI entry point to send a SIP notify to an endpoint.
- */
- static int manager_notify(struct mansession *s, const struct message *m)
- {
- const char *endpoint_name = astman_get_header(m, "Endpoint");
- const char *uri = astman_get_header(m, "URI");
- const char *channel = astman_get_header(m, "Channel");
- int count = 0;
- if (!ast_strlen_zero(endpoint_name)) {
- ++count;
- }
- if (!ast_strlen_zero(uri)) {
- ++count;
- }
- if (!ast_strlen_zero(channel)) {
- ++count;
- }
- if (1 < count) {
- astman_send_error(s, m,
- "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel. "
- "You must use only one of them.");
- } else if (!ast_strlen_zero(endpoint_name)) {
- manager_notify_endpoint(s, m, endpoint_name);
- } else if (!ast_strlen_zero(uri)) {
- manager_notify_uri(s, m, uri);
- } else if (!ast_strlen_zero(channel)) {
- manager_notify_channel(s, m, channel);
- } else {
- astman_send_error(s, m,
- "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel.");
- }
- return 0;
- }
- static int load_module(void)
- {
- CHECK_PJSIP_MODULE_LOADED();
- if (aco_info_init(¬ify_cfg)) {
- return AST_MODULE_LOAD_DECLINE;
- }
- aco_option_register_custom(¬ify_cfg, "", ACO_PREFIX, notify_options,
- "", notify_option_handler, 0);
- if (aco_process_config(¬ify_cfg, 0)) {
- aco_info_destroy(¬ify_cfg);
- return AST_MODULE_LOAD_DECLINE;
- }
- ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
- ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
- return AST_MODULE_LOAD_SUCCESS;
- }
- static int reload_module(void)
- {
- if (aco_process_config(¬ify_cfg, 1) == ACO_PROCESS_ERROR) {
- return AST_MODULE_LOAD_DECLINE;
- }
- return 0;
- }
- static int unload_module(void)
- {
- ast_manager_unregister("PJSIPNotify");
- ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));
- aco_info_destroy(¬ify_cfg);
- ao2_global_obj_release(globals);
- return 0;
- }
- AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "CLI/AMI PJSIP NOTIFY Support",
- .support_level = AST_MODULE_SUPPORT_CORE,
- .load = load_module,
- .reload = reload_module,
- .unload = unload_module,
- .load_pri = AST_MODPRI_APP_DEPEND,
- );
|