b2f24cbf29af3883b4071aacbc36b94a7c536600
[asterisk/asterisk.git] / res / res_pjsip_notify.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Kevin Harwell <kharwell@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*** MODULEINFO
20         <depend>pjproject</depend>
21         <depend>res_pjsip</depend>
22         <support_level>core</support_level>
23  ***/
24
25 #include "asterisk.h"
26
27 #include <pjsip.h>
28
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"
36
37 /*** DOCUMENTATION
38         <manager name="PJSIPNotify" language="en_US">
39                 <synopsis>
40                         Send a NOTIFY to an endpoint.
41                 </synopsis>
42                 <syntax>
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>
46                         </parameter>
47                 </syntax>
48                 <description>
49                         <para>Send a NOTIFY to an endpoint.</para>
50                         <para>Parameters will be placed into the notify as SIP headers.</para>
51                 </description>
52         </manager>
53         <configInfo name="res_pjsip_notify" language="en_US">
54                 <synopsis>Module that supports sending NOTIFY requests to endpoints from external sources</synopsis>
55                 <configFile name="pjsip_notify.conf">
56                         <configObject name="general">
57                                 <synopsis>Unused, but reserved.</synopsis>
58                         </configObject>
59                         <configObject name="notify">
60                                 <synopsis>Configuration of a NOTIFY request.</synopsis>
61                                 <description>
62                                         <para>Each key-value pair in a <literal>notify</literal>
63                                         configuration section defines either a SIP header to send
64                                         in the request or a line of content in the request message
65                                         body. A key of <literal>Content</literal> is treated
66                                         as part of the message body and is appended in sequential
67                                         order; any other header is treated as part of the SIP
68                                         request.</para>
69                                 </description>
70                                 <configOption name="^.*$">
71                                         <synopsis>A key/value pair to add to a NOTIFY request.</synopsis>
72                                         <description>
73                                                 <para>If the key is <literal>Content</literal>,
74                                                 it will be treated as part of the message body. Otherwise,
75                                                 it will be added as a header in the NOTIFY request.</para>
76                                                 <para>The following headers are reserved and cannot be
77                                                 specified:</para>
78                                                 <enumlist>
79                                                         <enum name="Call-ID" />
80                                                         <enum name="Contact" />
81                                                         <enum name="CSeq" />
82                                                         <enum name="To" />
83                                                         <enum name="From" />
84                                                         <enum name="Record-Route" />
85                                                         <enum name="Route" />
86                                                         <enum name="Via" />
87                                                 </enumlist>
88                                         </description>
89                                 </configOption>
90                         </configObject>
91                 </configFile>
92         </configInfo>
93  ***/
94
95 #define CONTENT_TYPE_SIZE 64
96 #define CONTENT_SIZE 512
97
98 /*!
99  * \internal
100  * \brief The configuration file containing NOTIFY payload types to send.
101  */
102 static const char notify_config[] = "pjsip_notify.conf";
103
104 struct notify_option_item {
105         const char *name;
106         const char *value;
107         char buf[0];
108 };
109
110 struct notify_option {
111         /*! Contains header and/or content information */
112         struct ao2_container *items;
113         /*! The name of the notify option */
114         char name[0];
115 };
116
117 static int notify_option_hash(const void *obj, int flags)
118 {
119         const struct notify_option *option = obj;
120         return ast_str_case_hash(flags & OBJ_KEY ? obj : option->name);
121 }
122
123 static int notify_option_cmp(void *obj, void *arg, int flags)
124 {
125         struct notify_option *option1 = obj;
126         struct notify_option *option2 = arg;
127         const char *key = flags & OBJ_KEY ? arg : option2->name;
128
129         return strcasecmp(option1->name, key) ? 0 : CMP_MATCH;
130 }
131
132 static void notify_option_destroy(void *obj)
133 {
134         struct notify_option *option = obj;
135         ao2_cleanup(option->items);
136 }
137
138 static void *notify_option_alloc(const char *category)
139 {
140         int category_size = strlen(category) + 1;
141
142         struct notify_option *option = ao2_alloc(
143                 sizeof(*option) + category_size, notify_option_destroy);
144
145         if (!option) {
146                 return NULL;
147         }
148
149         ast_copy_string(option->name, category, category_size);
150
151         if (!(option->items = ao2_container_alloc_list(
152                       AO2_ALLOC_OPT_LOCK_NOLOCK,
153                       AO2_CONTAINER_ALLOC_OPT_DUPS_ALLOW, NULL, NULL))) {
154                 ao2_cleanup(option);
155                 return NULL;
156         }
157
158         return option;
159 }
160
161 static void *notify_option_find(struct ao2_container *container, const char *category)
162 {
163         return ao2_find(container, category, OBJ_KEY);
164 }
165
166 static int notify_option_handler(const struct aco_option *opt,
167                                  struct ast_variable *var, void *obj)
168 {
169         struct notify_option *option = obj;
170
171         int name_size = strlen(var->name) + 1;
172         int value_size = strlen(var->value) + 1;
173
174         RAII_VAR(struct notify_option_item *, item,
175                  ao2_alloc(sizeof(*item) + name_size + value_size,
176                            NULL), ao2_cleanup);
177
178         item->name = item->buf;
179         item->value = item->buf + name_size;
180
181         ast_copy_string(item->buf, var->name, name_size);
182         ast_copy_string(item->buf + name_size, var->value, value_size);
183
184         if (!ao2_link(option->items, item)) {
185                 return -1;
186         }
187
188         return 0;
189 }
190
191 struct notify_cfg {
192         struct ao2_container *notify_options;
193 };
194
195 static void notify_cfg_destroy(void *obj)
196 {
197         struct notify_cfg *cfg = obj;
198         ao2_cleanup(cfg->notify_options);
199 }
200
201 static void *notify_cfg_alloc(void)
202 {
203         struct notify_cfg *cfg;
204
205         if (!(cfg = ao2_alloc(sizeof(*cfg), notify_cfg_destroy))) {
206                 return NULL;
207         }
208
209         if (!(cfg->notify_options = ao2_container_alloc_options(
210                       AO2_ALLOC_OPT_LOCK_NOLOCK, 20, notify_option_hash,
211                       notify_option_cmp))) {
212                 ao2_cleanup(cfg);
213                 return NULL;
214         }
215
216         return cfg;
217 }
218
219 static struct aco_type notify_option = {
220         .type = ACO_ITEM,
221         .name = "notify",
222         .category_match = ACO_BLACKLIST,
223         .category = "^general$",
224         .item_offset = offsetof(struct notify_cfg, notify_options),
225         .item_alloc = notify_option_alloc,
226         .item_find = notify_option_find
227 };
228
229 static struct aco_type *notify_options[] = ACO_TYPES(&notify_option);
230
231 static struct aco_file module_conf = {
232         .filename = notify_config,
233         .types = ACO_TYPES(&notify_option),
234 };
235
236 AO2_GLOBAL_OBJ_STATIC(globals);
237
238 CONFIG_INFO_STANDARD(notify_cfg, globals, notify_cfg_alloc,
239         .files = ACO_FILES(&module_conf)
240 );
241
242 /*!
243  * \internal
244  * \brief Structure to hold task data for notifications.
245  */
246 struct notify_data {
247         /*! The endpoint being notified */
248         struct ast_sip_endpoint *endpoint;
249         /*! The info of headers, types and content */
250         void *info;
251         /*! Function to help build notify request */
252         void (*build_notify)(pjsip_tx_data *, void *);
253 };
254
255 /*!
256  * \internal
257  * \brief Destroy the notify CLI data releasing any resources.
258  */
259 static void notify_cli_data_destroy(void *obj)
260 {
261         struct notify_data *data = obj;
262
263         ao2_cleanup(data->endpoint);
264         ao2_cleanup(data->info);
265 }
266
267 static void build_cli_notify(pjsip_tx_data *tdata, void *info);
268
269 /*!
270  * \internal
271  * \brief Construct a notify data object for CLI.
272  */
273 static struct notify_data* notify_cli_data_create(
274         struct ast_sip_endpoint *endpoint, void *info)
275 {
276         struct notify_data *data = ao2_alloc(sizeof(*data),
277                                              notify_cli_data_destroy);
278         if (!data) {
279                 return NULL;
280         }
281
282         data->endpoint = endpoint;
283         ao2_ref(data->endpoint, +1);
284
285         data->info = info;
286         ao2_ref(data->info, +1);
287
288         data->build_notify = build_cli_notify;
289
290         return data;
291 }
292
293 /*!
294  * \internal
295  * \brief Destroy the notify AMI data releasing any resources.
296  */
297 static void notify_ami_data_destroy(void *obj)
298 {
299         struct notify_data *data = obj;
300         struct ast_variable *info = data->info;
301
302         ao2_cleanup(data->endpoint);
303         ast_variables_destroy(info);
304 }
305
306 static void build_ami_notify(pjsip_tx_data *tdata, void *info);
307
308 /*!
309  * \internal
310  * \brief Construct a notify data object for AMI.
311  */
312 static struct notify_data* notify_ami_data_create(
313         struct ast_sip_endpoint *endpoint, void *info)
314 {
315         struct notify_data *data = ao2_alloc(sizeof(*data),
316                                              notify_ami_data_destroy);
317         if (!data) {
318                 return NULL;
319         }
320
321         data->endpoint = endpoint;
322         ao2_ref(data->endpoint, +1);
323
324         data->info = info;
325         data->build_notify = build_ami_notify;
326
327         return data;
328 }
329
330 /*!
331  * \internal
332  * \brief Checks if the given header name is not allowed.
333  *
334  * \details Some headers are not allowed to be set by the user within the
335  *          scope of a NOTIFY request.  If the given var header name is
336  *          found in the "not allowed" list then return true.
337  */
338 static int not_allowed(const char *name)
339 {
340         int i;
341         static const char *names[] = {
342                 "Call-ID",
343                 "Contact",
344                 "CSeq",
345                 "To",
346                 "From",
347                 "Record-Route",
348                 "Route",
349                 "Request-URI",
350                 "Via",
351         };
352
353         for (i = 0; i < ARRAY_LEN(names); ++i) {
354                 if (!strcasecmp(name, names[i])) {
355                         return 1;
356                 }
357         }
358         return 0;
359 }
360
361 /*!
362  * \internal
363  * \brief If a content type was specified add it and the content body to the
364  *        NOTIFY request.
365  */
366 static void build_notify_body(pjsip_tx_data *tdata, struct ast_str *content_type,
367                               struct ast_str *content)
368 {
369         if (content_type) {
370                 char *p;
371                 struct ast_sip_body body;
372
373                 if (content) {
374                         body.body_text = ast_str_buffer(content);
375                 }
376
377                 body.type = ast_str_buffer(content_type);
378                 if ((p = strchr(body.type, '/'))) {
379                         *p++ = '\0';
380                         body.subtype = p;
381                 }
382                 ast_sip_add_body(tdata, &body);
383         }
384 }
385
386 /*!
387  * \internal
388  * \brief Build the NOTIFY request adding content or header info.
389  */
390 static void build_notify(pjsip_tx_data *tdata, const char *name, const char *value,
391                          struct ast_str **content_type, struct ast_str **content)
392 {
393         if (not_allowed(name)) {
394                 ast_log(LOG_WARNING, "Cannot specify %s header, "
395                         "ignoring\n", name);
396                 return;
397         }
398
399         if (!strcasecmp(name, "Content-type")) {
400                 if (!(*content_type)) {
401                         *content_type = ast_str_create(CONTENT_TYPE_SIZE);
402                 }
403                 ast_str_set(content_type, 0,"%s", value);
404         } else if (!strcasecmp(name, "Content")) {
405                 if (!(*content)) {
406                         *content = ast_str_create(CONTENT_SIZE);
407                 }
408
409                 if (ast_str_strlen(*content)) {
410                         ast_str_append(content, 0, "\r\n");
411                 }
412                 ast_str_append(content, 0, "%s", value);
413         } else {
414                 ast_sip_add_header(tdata, name, value);
415         }
416 }
417
418 /*!
419  * \internal
420  * \brief Build the NOTIFY request from CLI info adding header and content
421  *        when specified.
422  */
423 static void build_cli_notify(pjsip_tx_data *tdata, void *info)
424 {
425         struct notify_option *option = info;
426         RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
427         RAII_VAR(struct ast_str *, content, NULL, ast_free);
428
429         struct notify_option_item *item;
430         struct ao2_iterator i = ao2_iterator_init(option->items, 0);
431
432         while ((item = ao2_iterator_next(&i))) {
433                 build_notify(tdata, item->name, item->value,
434                              &content_type, &content);
435                 ao2_cleanup(item);
436         }
437         ao2_iterator_destroy(&i);
438
439         build_notify_body(tdata, content_type, content);
440 }
441
442 /*!
443  * \internal
444  * \brief Build the NOTIFY request from AMI info adding header and content
445  *        when specified.
446  */
447 static void build_ami_notify(pjsip_tx_data *tdata, void *info)
448 {
449         struct ast_variable *vars = info;
450         RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
451         RAII_VAR(struct ast_str *, content, NULL, ast_free);
452         struct ast_variable *i;
453
454         for (i = vars; i; i = i->next) {
455                 build_notify(tdata, i->name, i->value,
456                              &content_type, &content);
457         }
458
459         build_notify_body(tdata, content_type, content);
460 }
461
462 /*!
463  * \internal
464  * \brief Build and send a NOTIFY request to a contact.
465  */
466 static int notify_contact(void *obj, void *arg, int flags)
467 {
468         struct ast_sip_contact *contact = obj;
469         struct notify_data *data = arg;
470         pjsip_tx_data *tdata;
471
472         if (ast_sip_create_request("NOTIFY", NULL, data->endpoint,
473                                    NULL, contact, &tdata)) {
474                 ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
475                         "contact %s\n", contact->uri);
476                 return -1;
477         }
478
479         ast_sip_add_header(tdata, "Subscription-State", "terminated");
480         data->build_notify(tdata, data->info);
481
482         if (ast_sip_send_request(tdata, NULL, data->endpoint, NULL, NULL)) {
483                 ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
484                         "contact %s\n", contact->uri);
485                 return -1;
486         }
487
488         return 0;
489 }
490
491 /*!
492  * \internal
493  * \brief Send a NOTIFY request to the endpoint.
494  *
495  * \detail Iterates over an endpoint's AORs sending a NOTIFY request
496  *         with the appropriate payload information to each contact.
497  */
498 static int notify_endpoint(void *obj)
499 {
500         RAII_VAR(struct notify_data *, data, obj, ao2_cleanup);
501         char *aor_name, *aors;
502
503         if (ast_strlen_zero(data->endpoint->aors)) {
504                 ast_log(LOG_WARNING, "Unable to NOTIFY - "
505                         "endpoint has no configured AORs\n");
506                 return -1;
507         }
508
509         aors = ast_strdupa(data->endpoint->aors);
510
511         while ((aor_name = strsep(&aors, ","))) {
512                 RAII_VAR(struct ast_sip_aor *, aor,
513                          ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
514                 RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
515
516                 if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
517                         continue;
518                 }
519
520                 ao2_callback(contacts, OBJ_NODATA, notify_contact, data);
521         }
522
523         return 0;
524 }
525
526 enum notify_result {
527         SUCCESS,
528         INVALID_ENDPOINT,
529         ALLOC_ERROR,
530         TASK_PUSH_ERROR
531 };
532
533 typedef struct notify_data *(*task_data_create)(
534         struct ast_sip_endpoint *, void *info);
535 /*!
536  * \internal
537  * \brief Send a NOTIFY request to the endpoint within a threaded task.
538  */
539 static enum notify_result push_notify(const char *endpoint_name, void *info,
540                                       task_data_create data_create)
541 {
542         RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
543         struct notify_data *data;
544
545         if (!(endpoint = ast_sorcery_retrieve_by_id(
546                       ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
547                 return INVALID_ENDPOINT;
548         }
549
550         if (!(data = data_create(endpoint, info))) {
551                 return ALLOC_ERROR;
552         }
553
554         if (ast_sip_push_task(NULL, notify_endpoint, data)) {
555                 ao2_cleanup(data);
556                 return TASK_PUSH_ERROR;
557         }
558
559         return SUCCESS;
560 }
561
562 /*!
563  * \internal
564  * \brief Do completion on the endpoint.
565  */
566 static char *cli_complete_endpoint(const char *word, int state)
567 {
568         char *result = NULL;
569         int wordlen = strlen(word);
570         int which = 0;
571
572         struct ast_sip_endpoint *endpoint;
573         RAII_VAR(struct ao2_container *, endpoints,
574                  ast_sip_get_endpoints(), ao2_cleanup);
575
576         struct ao2_iterator i = ao2_iterator_init(endpoints, 0);
577         while ((endpoint = ao2_iterator_next(&i))) {
578                 const char *name = ast_sorcery_object_get_id(endpoint);
579                 if (!strncasecmp(word, name, wordlen) && ++which > state) {
580                         result = ast_strdup(name);
581                 }
582
583                 ao2_cleanup(endpoint);
584                 if (result) {
585                         break;
586                 }
587         }
588         ao2_iterator_destroy(&i);
589         return result;
590 }
591
592 /*!
593  * \internal
594  * \brief Do completion on the notify CLI command.
595  */
596 static char *cli_complete_notify(const char *line, const char *word,
597                                  int pos, int state)
598 {
599         char *c = NULL;
600
601         if (pos == 3) {
602                 int which = 0;
603                 int wordlen = strlen(word);
604
605                 RAII_VAR(struct notify_cfg *, cfg,
606                          ao2_global_obj_ref(globals), ao2_cleanup);
607                 struct notify_option *option;
608
609                 /* do completion for notify type */
610                 struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
611                 while ((option = ao2_iterator_next(&i))) {
612                         if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
613                                 c = ast_strdup(option->name);
614                         }
615
616                         ao2_cleanup(option);
617                         if (c) {
618                                 break;
619                         }
620                 }
621                 ao2_iterator_destroy(&i);
622                 return c;
623         }
624         return pos > 3 ? cli_complete_endpoint(word, state) : NULL;
625 }
626
627 /*!
628  * \internal
629  * \brief CLI command to send a SIP notify to an endpoint.
630  *
631  * \details Attempts to match the "type" given in the CLI command to a
632  *          configured one.  If found, sends a NOTIFY to the endpoint
633  *          with the associated payload.
634  */
635 static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
636 {
637         RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
638         RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
639
640         int i;
641
642         switch (cmd) {
643         case CLI_INIT:
644                 e->command = "pjsip send notify";
645                 e->usage =
646                         "Usage: pjsip send notify <type> <peer> [<peer>...]\n"
647                         "       Send a NOTIFY request to an endpoint\n"
648                         "       Message types are defined in sip_notify.conf\n";
649                 return NULL;
650         case CLI_GENERATE:
651                 return cli_complete_notify(a->line, a->word, a->pos, a->n);
652         }
653
654         if (a->argc < 5) {
655                 return CLI_SHOWUSAGE;
656         }
657
658         cfg = ao2_global_obj_ref(globals);
659
660         if (!(option = notify_option_find(cfg->notify_options, a->argv[3])))
661         {
662                 ast_cli(a->fd, "Unable to find notify type '%s'\n",
663                         a->argv[3]);
664                 return CLI_FAILURE;
665         }
666
667         for (i = 4; i < a->argc; ++i) {
668                 ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
669                         a->argv[3], a->argv[i]);
670
671                 switch (push_notify(a->argv[i], option,
672                                     notify_cli_data_create)) {
673                 case INVALID_ENDPOINT:
674                         ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
675                                 a->argv[i]);
676                         break;
677                 case ALLOC_ERROR:
678                         ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
679                         return CLI_FAILURE;
680                 case TASK_PUSH_ERROR:
681                         ast_cli(a->fd, "Unable to push NOTIFY task\n");
682                         return CLI_FAILURE;
683                 default:
684                         break;
685                 }
686         }
687
688         return CLI_SUCCESS;
689 }
690
691 static struct ast_cli_entry cli_options[] = {
692         AST_CLI_DEFINE(cli_notify, "Send a NOTIFY request to a SIP endpoint")
693 };
694
695 /*!
696  * \internal
697  * \brief AMI entry point to send a SIP notify to an endpoint.
698  */
699 static int manager_notify(struct mansession *s, const struct message *m)
700 {
701         const char *endpoint_name = astman_get_header(m, "Endpoint");
702         struct ast_variable *vars = astman_get_variables(m);
703
704         if (ast_strlen_zero(endpoint_name)) {
705                 astman_send_error(s, m, "PJSIPNotify requires an endpoint name");
706                 return 0;
707         }
708
709         if (!strncasecmp(endpoint_name, "sip/", 4)) {
710                 endpoint_name += 4;
711         }
712
713         switch (push_notify(endpoint_name, vars, notify_ami_data_create)) {
714         case INVALID_ENDPOINT:
715                 astman_send_error_va(s, m, "Unable to retrieve endpoint %s\n",
716                         endpoint_name);
717                 return 0;
718         case ALLOC_ERROR:
719                 astman_send_error(s, m, "Unable to allocate NOTIFY task data\n");
720                 return 0;
721         case TASK_PUSH_ERROR:
722                 astman_send_error(s, m, "Unable to push NOTIFY task\n");
723                 return 0;
724         default:
725                 break;
726         }
727
728         astman_send_ack(s, m, "NOTIFY sent");
729         return 0;
730 }
731
732 static int load_module(void)
733 {
734         if (aco_info_init(&notify_cfg)) {
735                 return AST_MODULE_LOAD_DECLINE;
736         }
737
738         aco_option_register_custom(&notify_cfg, "^.*$", ACO_REGEX, notify_options,
739                                    "", notify_option_handler, 0);
740
741         if (aco_process_config(&notify_cfg, 0)) {
742                 aco_info_destroy(&notify_cfg);
743                 return AST_MODULE_LOAD_DECLINE;
744         }
745
746         ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
747         ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
748
749         return AST_MODULE_LOAD_SUCCESS;
750 }
751
752 static int reload_module(void)
753 {
754         return aco_process_config(&notify_cfg, 1) ?
755                 AST_MODULE_LOAD_DECLINE : 0;
756 }
757
758 static int unload_module(void)
759 {
760         ast_manager_unregister("PJSIPNotify");
761         aco_info_destroy(&notify_cfg);
762
763         return 0;
764 }
765
766 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "CLI/AMI PJSIP NOTIFY Support",
767                 .load = load_module,
768                 .reload = reload_module,
769                 .unload = unload_module,
770                 .load_pri = AST_MODPRI_APP_DEPEND,
771                );