2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Kevin Harwell <kharwell@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
20 <depend>pjproject</depend>
21 <depend>res_pjsip</depend>
22 <support_level>core</support_level>
29 #include "asterisk/cli.h"
30 #include "asterisk/config.h"
31 #include "asterisk/manager.h"
32 #include "asterisk/module.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/res_pjsip.h"
35 #include "asterisk/sorcery.h"
38 <manager name="PJSIPNotify" language="en_US">
40 Send a NOTIFY to an endpoint.
43 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
44 <parameter name="Endpoint" required="true">
45 <para>The endpoint to which to send the NOTIFY.</para>
47 <parameter name="Variable" required="true">
48 <para>Appends variables as headers/content to the NOTIFY. If the variable is
49 named <literal>Content</literal>, then the value will compose the body
50 of the message if another variable sets <literal>Content-Type</literal>.
51 <replaceable>name</replaceable>=<replaceable>value</replaceable></para>
55 <para>Sends a NOTIFY to an endpoint.</para>
56 <para>All parameters for this event must be specified in the body of this request
57 via multiple <literal>Variable: name=value</literal> sequences.</para>
60 <configInfo name="res_pjsip_notify" language="en_US">
61 <synopsis>Module that supports sending NOTIFY requests to endpoints from external sources</synopsis>
62 <configFile name="pjsip_notify.conf">
63 <configObject name="general">
64 <synopsis>Unused, but reserved.</synopsis>
66 <configObject name="notify">
67 <synopsis>Configuration of a NOTIFY request.</synopsis>
69 <para>Each key-value pair in a <literal>notify</literal>
70 configuration section defines either a SIP header to send
71 in the request or a line of content in the request message
72 body. A key of <literal>Content</literal> is treated
73 as part of the message body and is appended in sequential
74 order; any other header is treated as part of the SIP
77 <configOption name="^.*$">
78 <synopsis>A key/value pair to add to a NOTIFY request.</synopsis>
80 <para>If the key is <literal>Content</literal>,
81 it will be treated as part of the message body. Otherwise,
82 it will be added as a header in the NOTIFY request.</para>
83 <para>The following headers are reserved and cannot be
86 <enum name="Call-ID" />
87 <enum name="Contact" />
91 <enum name="Record-Route" />
102 #define CONTENT_TYPE_SIZE 64
103 #define CONTENT_SIZE 512
107 * \brief The configuration file containing NOTIFY payload types to send.
109 static const char notify_config[] = "pjsip_notify.conf";
111 struct notify_option_item {
117 struct notify_option {
118 /*! Contains header and/or content information */
119 struct ao2_container *items;
120 /*! The name of the notify option */
124 static int notify_option_hash(const void *obj, int flags)
126 const struct notify_option *option = obj;
127 return ast_str_case_hash(flags & OBJ_KEY ? obj : option->name);
130 static int notify_option_cmp(void *obj, void *arg, int flags)
132 struct notify_option *option1 = obj;
133 struct notify_option *option2 = arg;
134 const char *key = flags & OBJ_KEY ? arg : option2->name;
136 return strcasecmp(option1->name, key) ? 0 : CMP_MATCH;
139 static void notify_option_destroy(void *obj)
141 struct notify_option *option = obj;
142 ao2_cleanup(option->items);
145 static void *notify_option_alloc(const char *category)
147 int category_size = strlen(category) + 1;
149 struct notify_option *option = ao2_alloc(
150 sizeof(*option) + category_size, notify_option_destroy);
156 ast_copy_string(option->name, category, category_size);
158 if (!(option->items = ao2_container_alloc_list(
159 AO2_ALLOC_OPT_LOCK_NOLOCK,
160 AO2_CONTAINER_ALLOC_OPT_DUPS_ALLOW, NULL, NULL))) {
168 static void *notify_option_find(struct ao2_container *container, const char *category)
170 return ao2_find(container, category, OBJ_KEY);
173 static int notify_option_handler(const struct aco_option *opt,
174 struct ast_variable *var, void *obj)
176 struct notify_option *option = obj;
178 int name_size = strlen(var->name) + 1;
179 int value_size = strlen(var->value) + 1;
181 RAII_VAR(struct notify_option_item *, item,
182 ao2_alloc(sizeof(*item) + name_size + value_size,
185 item->name = item->buf;
186 item->value = item->buf + name_size;
188 ast_copy_string(item->buf, var->name, name_size);
189 ast_copy_string(item->buf + name_size, var->value, value_size);
191 if (!ao2_link(option->items, item)) {
199 struct ao2_container *notify_options;
202 static void notify_cfg_destroy(void *obj)
204 struct notify_cfg *cfg = obj;
205 ao2_cleanup(cfg->notify_options);
208 static void *notify_cfg_alloc(void)
210 struct notify_cfg *cfg;
212 if (!(cfg = ao2_alloc(sizeof(*cfg), notify_cfg_destroy))) {
216 if (!(cfg->notify_options = ao2_container_alloc_options(
217 AO2_ALLOC_OPT_LOCK_NOLOCK, 20, notify_option_hash,
218 notify_option_cmp))) {
226 static struct aco_type notify_option = {
229 .category_match = ACO_BLACKLIST,
230 .category = "^general$",
231 .item_offset = offsetof(struct notify_cfg, notify_options),
232 .item_alloc = notify_option_alloc,
233 .item_find = notify_option_find
236 static struct aco_type *notify_options[] = ACO_TYPES(¬ify_option);
238 static struct aco_file module_conf = {
239 .filename = notify_config,
240 .types = ACO_TYPES(¬ify_option),
243 AO2_GLOBAL_OBJ_STATIC(globals);
245 CONFIG_INFO_STANDARD(notify_cfg, globals, notify_cfg_alloc,
246 .files = ACO_FILES(&module_conf)
251 * \brief Structure to hold task data for notifications.
254 /*! The endpoint being notified */
255 struct ast_sip_endpoint *endpoint;
256 /*! The info of headers, types and content */
258 /*! Function to help build notify request */
259 void (*build_notify)(pjsip_tx_data *, void *);
264 * \brief Destroy the notify CLI data releasing any resources.
266 static void notify_cli_data_destroy(void *obj)
268 struct notify_data *data = obj;
270 ao2_cleanup(data->endpoint);
271 ao2_cleanup(data->info);
274 static void build_cli_notify(pjsip_tx_data *tdata, void *info);
278 * \brief Construct a notify data object for CLI.
280 static struct notify_data* notify_cli_data_create(
281 struct ast_sip_endpoint *endpoint, void *info)
283 struct notify_data *data = ao2_alloc(sizeof(*data),
284 notify_cli_data_destroy);
289 data->endpoint = endpoint;
290 ao2_ref(data->endpoint, +1);
293 ao2_ref(data->info, +1);
295 data->build_notify = build_cli_notify;
302 * \brief Destroy the notify AMI data releasing any resources.
304 static void notify_ami_data_destroy(void *obj)
306 struct notify_data *data = obj;
307 struct ast_variable *info = data->info;
309 ao2_cleanup(data->endpoint);
310 ast_variables_destroy(info);
313 static void build_ami_notify(pjsip_tx_data *tdata, void *info);
317 * \brief Construct a notify data object for AMI.
319 static struct notify_data* notify_ami_data_create(
320 struct ast_sip_endpoint *endpoint, void *info)
322 struct notify_data *data = ao2_alloc(sizeof(*data),
323 notify_ami_data_destroy);
328 data->endpoint = endpoint;
329 ao2_ref(data->endpoint, +1);
332 data->build_notify = build_ami_notify;
339 * \brief Checks if the given header name is not allowed.
341 * \details Some headers are not allowed to be set by the user within the
342 * scope of a NOTIFY request. If the given var header name is
343 * found in the "not allowed" list then return true.
345 static int not_allowed(const char *name)
348 static const char *names[] = {
360 for (i = 0; i < ARRAY_LEN(names); ++i) {
361 if (!strcasecmp(name, names[i])) {
370 * \brief If a content type was specified add it and the content body to the
373 static void build_notify_body(pjsip_tx_data *tdata, struct ast_str *content_type,
374 struct ast_str *content)
378 struct ast_sip_body body;
381 body.body_text = ast_str_buffer(content);
384 body.type = ast_str_buffer(content_type);
385 if ((p = strchr(body.type, '/'))) {
389 ast_sip_add_body(tdata, &body);
395 * \brief Build the NOTIFY request adding content or header info.
397 static void build_notify(pjsip_tx_data *tdata, const char *name, const char *value,
398 struct ast_str **content_type, struct ast_str **content)
400 if (not_allowed(name)) {
401 ast_log(LOG_WARNING, "Cannot specify %s header, "
406 if (!strcasecmp(name, "Content-type")) {
407 if (!(*content_type)) {
408 *content_type = ast_str_create(CONTENT_TYPE_SIZE);
410 ast_str_set(content_type, 0,"%s", value);
411 } else if (!strcasecmp(name, "Content")) {
413 *content = ast_str_create(CONTENT_SIZE);
416 if (ast_str_strlen(*content)) {
417 ast_str_append(content, 0, "\r\n");
419 ast_str_append(content, 0, "%s", value);
421 ast_sip_add_header(tdata, name, value);
427 * \brief Build the NOTIFY request from CLI info adding header and content
430 static void build_cli_notify(pjsip_tx_data *tdata, void *info)
432 struct notify_option *option = info;
433 RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
434 RAII_VAR(struct ast_str *, content, NULL, ast_free);
436 struct notify_option_item *item;
437 struct ao2_iterator i = ao2_iterator_init(option->items, 0);
439 while ((item = ao2_iterator_next(&i))) {
440 build_notify(tdata, item->name, item->value,
441 &content_type, &content);
444 ao2_iterator_destroy(&i);
446 build_notify_body(tdata, content_type, content);
451 * \brief Build the NOTIFY request from AMI info adding header and content
454 static void build_ami_notify(pjsip_tx_data *tdata, void *info)
456 struct ast_variable *vars = info;
457 RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
458 RAII_VAR(struct ast_str *, content, NULL, ast_free);
459 struct ast_variable *i;
461 for (i = vars; i; i = i->next) {
462 if (!strcasecmp(i->name, "Content-Length")) {
463 ast_log(LOG_NOTICE, "It is not necessary to specify Content-Length, ignoring.\n");
466 build_notify(tdata, i->name, i->value,
467 &content_type, &content);
470 build_notify_body(tdata, content_type, content);
475 * \brief Build and send a NOTIFY request to a contact.
477 static int notify_contact(void *obj, void *arg, int flags)
479 struct ast_sip_contact *contact = obj;
480 struct notify_data *data = arg;
481 pjsip_tx_data *tdata;
483 if (ast_sip_create_request("NOTIFY", NULL, data->endpoint,
484 NULL, contact, &tdata)) {
485 ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
486 "contact %s\n", contact->uri);
490 ast_sip_add_header(tdata, "Subscription-State", "terminated");
491 data->build_notify(tdata, data->info);
493 if (ast_sip_send_request(tdata, NULL, data->endpoint, NULL, NULL)) {
494 ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
495 "contact %s\n", contact->uri);
504 * \brief Send a NOTIFY request to the endpoint.
506 * \detail Iterates over an endpoint's AORs sending a NOTIFY request
507 * with the appropriate payload information to each contact.
509 static int notify_endpoint(void *obj)
511 RAII_VAR(struct notify_data *, data, obj, ao2_cleanup);
512 char *aor_name, *aors;
514 if (ast_strlen_zero(data->endpoint->aors)) {
515 ast_log(LOG_WARNING, "Unable to NOTIFY - "
516 "endpoint has no configured AORs\n");
520 aors = ast_strdupa(data->endpoint->aors);
522 while ((aor_name = strsep(&aors, ","))) {
523 RAII_VAR(struct ast_sip_aor *, aor,
524 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
525 RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
527 if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
531 ao2_callback(contacts, OBJ_NODATA, notify_contact, data);
544 typedef struct notify_data *(*task_data_create)(
545 struct ast_sip_endpoint *, void *info);
548 * \brief Send a NOTIFY request to the endpoint within a threaded task.
550 static enum notify_result push_notify(const char *endpoint_name, void *info,
551 task_data_create data_create)
553 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
554 struct notify_data *data;
556 if (!(endpoint = ast_sorcery_retrieve_by_id(
557 ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
558 return INVALID_ENDPOINT;
561 if (!(data = data_create(endpoint, info))) {
565 if (ast_sip_push_task(NULL, notify_endpoint, data)) {
567 return TASK_PUSH_ERROR;
575 * \brief Do completion on the endpoint.
577 static char *cli_complete_endpoint(const char *word, int state)
580 int wordlen = strlen(word);
583 struct ast_sip_endpoint *endpoint;
584 RAII_VAR(struct ao2_container *, endpoints,
585 ast_sip_get_endpoints(), ao2_cleanup);
587 struct ao2_iterator i = ao2_iterator_init(endpoints, 0);
588 while ((endpoint = ao2_iterator_next(&i))) {
589 const char *name = ast_sorcery_object_get_id(endpoint);
590 if (!strncasecmp(word, name, wordlen) && ++which > state) {
591 result = ast_strdup(name);
594 ao2_cleanup(endpoint);
599 ao2_iterator_destroy(&i);
605 * \brief Do completion on the notify CLI command.
607 static char *cli_complete_notify(const char *line, const char *word,
614 int wordlen = strlen(word);
616 RAII_VAR(struct notify_cfg *, cfg,
617 ao2_global_obj_ref(globals), ao2_cleanup);
618 struct notify_option *option;
620 /* do completion for notify type */
621 struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
622 while ((option = ao2_iterator_next(&i))) {
623 if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
624 c = ast_strdup(option->name);
632 ao2_iterator_destroy(&i);
635 return pos > 3 ? cli_complete_endpoint(word, state) : NULL;
640 * \brief CLI command to send a SIP notify to an endpoint.
642 * \details Attempts to match the "type" given in the CLI command to a
643 * configured one. If found, sends a NOTIFY to the endpoint
644 * with the associated payload.
646 static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
648 RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
649 RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
655 e->command = "pjsip send notify";
657 "Usage: pjsip send notify <type> <peer> [<peer>...]\n"
658 " Send a NOTIFY request to an endpoint\n"
659 " Message types are defined in sip_notify.conf\n";
662 return cli_complete_notify(a->line, a->word, a->pos, a->n);
666 return CLI_SHOWUSAGE;
669 cfg = ao2_global_obj_ref(globals);
671 if (!(option = notify_option_find(cfg->notify_options, a->argv[3])))
673 ast_cli(a->fd, "Unable to find notify type '%s'\n",
678 for (i = 4; i < a->argc; ++i) {
679 ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
680 a->argv[3], a->argv[i]);
682 switch (push_notify(a->argv[i], option,
683 notify_cli_data_create)) {
684 case INVALID_ENDPOINT:
685 ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
689 ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
691 case TASK_PUSH_ERROR:
692 ast_cli(a->fd, "Unable to push NOTIFY task\n");
702 static struct ast_cli_entry cli_options[] = {
703 AST_CLI_DEFINE(cli_notify, "Send a NOTIFY request to a SIP endpoint")
708 * \brief AMI entry point to send a SIP notify to an endpoint.
710 static int manager_notify(struct mansession *s, const struct message *m)
712 const char *endpoint_name = astman_get_header(m, "Endpoint");
713 struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
715 if (ast_strlen_zero(endpoint_name)) {
716 astman_send_error(s, m, "PJSIPNotify requires an endpoint name");
717 ast_variables_destroy(vars);
721 if (!strncasecmp(endpoint_name, "sip/", 4)) {
725 if (!strncasecmp(endpoint_name, "pjsip/", 6)) {
729 switch (push_notify(endpoint_name, vars, notify_ami_data_create)) {
730 case INVALID_ENDPOINT:
731 ast_variables_destroy(vars);
732 astman_send_error_va(s, m, "Unable to retrieve endpoint %s\n",
736 ast_variables_destroy(vars);
737 astman_send_error(s, m, "Unable to allocate NOTIFY task data\n");
739 case TASK_PUSH_ERROR:
740 /* Don't need to destroy vars since it is handled by cleanup in push_notify */
741 astman_send_error(s, m, "Unable to push NOTIFY task\n");
744 astman_send_ack(s, m, "NOTIFY sent");
751 static int load_module(void)
753 if (aco_info_init(¬ify_cfg)) {
754 return AST_MODULE_LOAD_DECLINE;
757 aco_option_register_custom(¬ify_cfg, "^.*$", ACO_REGEX, notify_options,
758 "", notify_option_handler, 0);
760 if (aco_process_config(¬ify_cfg, 0)) {
761 aco_info_destroy(¬ify_cfg);
762 return AST_MODULE_LOAD_DECLINE;
765 ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
766 ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
768 return AST_MODULE_LOAD_SUCCESS;
771 static int reload_module(void)
773 return aco_process_config(¬ify_cfg, 1) ?
774 AST_MODULE_LOAD_DECLINE : 0;
777 static int unload_module(void)
779 ast_manager_unregister("PJSIPNotify");
780 aco_info_destroy(¬ify_cfg);
785 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "CLI/AMI PJSIP NOTIFY Support",
787 .reload = reload_module,
788 .unload = unload_module,
789 .load_pri = AST_MODPRI_APP_DEPEND,