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