New SIP Channel Driver - Add CLI/AMI initiated NOTIFY requests
authorKevin Harwell <kharwell@digium.com>
Mon, 1 Jul 2013 21:28:32 +0000 (21:28 +0000)
committerKevin Harwell <kharwell@digium.com>
Mon, 1 Jul 2013 21:28:32 +0000 (21:28 +0000)
Added the ability to send unsolicited NOTIFY requests to a particular endpoint
with a configured payload.  Added both CLI and AMI support.  For a given
endpoint, this module will iterate over all its contacts sending the appropriate
NOTIFY request to each.

(closes issue ASTERISK-21436)
Reported by: Matt Jordan
Review: https://reviewboard.asterisk.org/r/2623/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393364 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/res_sip.h
res/res_sip.exports.in
res/res_sip/include/res_sip_private.h
res/res_sip/sip_configuration.c
res/res_sip/sip_options.c
res/res_sip_notify.c [new file with mode: 0644]

index 7c486aa..a250807 100644 (file)
@@ -1181,6 +1181,13 @@ void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size);
 struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata);
 
 /*!
+ * \brief Retrieve any endpoints available to sorcery.
+ *
+ * \retval Endpoints available to sorcery, NULL if no endpoints found.
+ */
+struct ao2_container *ast_sip_get_endpoints(void);
+
+/*!
  * \brief Retrieve relevant SIP auth structures from sorcery
  *
  * \param auth_names The sorcery IDs of auths to retrieve
index 625a02f..f557300 100644 (file)
@@ -29,6 +29,7 @@
                LINKER_SYMBOL_PREFIXast_sip_append_body;
                LINKER_SYMBOL_PREFIXast_sip_get_pjsip_endpoint;
                LINKER_SYMBOL_PREFIXast_sip_endpoint_alloc;
+               LINKER_SYMBOL_PREFIXast_sip_get_endpoints;
                LINKER_SYMBOL_PREFIXast_copy_pj_str;
                LINKER_SYMBOL_PREFIXast_sip_get_sorcery;
                LINKER_SYMBOL_PREFIXast_sip_create_dialog;
index 3625bab..e5f1032 100644 (file)
@@ -55,11 +55,4 @@ int ast_res_sip_init_contact_transports(void);
  */
 int ast_sip_initialize_outbound_authentication(void);
 
-/*!
- * \brief Get the current defined endpoints
- *
- * \retval The current endpoints loaded by res_sip
- */
-struct ao2_container *ast_res_sip_get_endpoints(void);
-
 #endif /* RES_SIP_PRIVATE_H_ */
index 49c2da2..11d4788 100644 (file)
@@ -114,7 +114,7 @@ static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct
                return NULL;
        }
 
-       endpoints = ast_res_sip_get_endpoints();
+       endpoints = ast_sip_get_endpoints();
        if (!endpoints) {
                return CLI_FAILURE;
        }
@@ -738,7 +738,7 @@ void *ast_sip_endpoint_alloc(const char *name)
        return endpoint;
 }
 
-struct ao2_container *ast_res_sip_get_endpoints(void)
+struct ao2_container *ast_sip_get_endpoints(void)
 {
        struct ao2_container *endpoints;
 
index ca5f3bf..7fa2681 100644 (file)
@@ -189,7 +189,7 @@ static int on_endpoint(void *obj, void *arg, int flags)
 static struct ao2_container *find_endpoints(struct ast_sip_contact *contact)
 {
        RAII_VAR(struct ao2_container *, endpoints,
-                ast_res_sip_get_endpoints(), ao2_cleanup);
+                ast_sip_get_endpoints(), ao2_cleanup);
 
        return ao2_callback(endpoints, OBJ_MULTIPLE, on_endpoint, contact);
 }
@@ -736,7 +736,7 @@ static int qualify_and_schedule_permanent_cb(void *obj, void *arg, int flags)
 static void qualify_and_schedule_permanent(void)
 {
        RAII_VAR(struct ao2_container *, endpoints,
-                ast_res_sip_get_endpoints(), ao2_cleanup);
+                ast_sip_get_endpoints(), ao2_cleanup);
 
        ao2_callback(endpoints, OBJ_NODATA,
                     qualify_and_schedule_permanent_cb, NULL);
diff --git a/res/res_sip_notify.c b/res/res_sip_notify.c
new file mode 100644 (file)
index 0000000..746b1c1
--- /dev/null
@@ -0,0 +1,713 @@
+/*
+ * 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>res_sip</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/cli.h"
+#include "asterisk/config.h"
+#include "asterisk/manager.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/sorcery.h"
+
+#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[] = "sip_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;
+       }
+
+       if (!(cfg->notify_options = ao2_container_alloc_options(
+                     AO2_ALLOC_OPT_LOCK_NOLOCK, 20, notify_option_hash,
+                     notify_option_cmp))) {
+               ao2_cleanup(cfg);
+               return NULL;
+       }
+
+       return cfg;
+}
+
+static struct aco_type notify_option = {
+       .type = ACO_ITEM,
+       .name = "notify",
+       .category_match = ACO_BLACKLIST,
+       .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(&notify_option);
+
+static struct aco_file module_conf = {
+       .filename = notify_config,
+       .types = ACO_TYPES(&notify_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);
+}
+
+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 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);
+}
+
+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 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) {
+               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,
+                                  contact->uri, &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)) {
+               pjsip_tx_data_dec_ref(tdata);
+               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 = 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;
+}
+
+enum notify_result {
+       SUCCESS,
+       INVALID_ENDPOINT,
+       ALLOC_ERROR,
+       TASK_PUSH_ERROR
+};
+
+typedef struct notify_data *(*task_data_create)(
+       struct ast_sip_endpoint *, 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 Do completion on the endpoint.
+ */
+static char *cli_complete_endpoint(const char *word, int state)
+{
+       char *result = NULL;
+       int wordlen = strlen(word);
+       int which = 0;
+
+       struct ast_sip_endpoint *endpoint;
+       RAII_VAR(struct ao2_container *, endpoints,
+                ast_sip_get_endpoints(), ao2_cleanup);
+
+       struct ao2_iterator i = ao2_iterator_init(endpoints, 0);
+       while ((endpoint = ao2_iterator_next(&i))) {
+               const char *name = ast_sorcery_object_get_id(endpoint);
+               if (!strncasecmp(word, name, wordlen) && ++which > state) {
+                       result = ast_strdup(name);
+               }
+
+               ao2_cleanup(endpoint);
+               if (result) {
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&i);
+       return result;
+}
+
+/*!
+ * \internal
+ * \brief Do completion on the notify CLI command.
+ */
+static char *cli_complete_notify(const char *line, const char *word,
+                                int pos, int state)
+{
+       char *c = NULL;
+
+       if (pos == 2) {
+               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;
+       }
+       return pos > 2 ? cli_complete_endpoint(word, state) : 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;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "gulp notify";
+               e->usage =
+                       "Usage: gulp notify <type> <peer> [<peer>...]\n"
+                       "       Send a NOTIFY request to an endpoint\n"
+                       "       Message types are defined in sip_notify.conf\n";
+               return NULL;
+       case CLI_GENERATE:
+               return cli_complete_notify(a->line, a->word, a->pos, a->n);
+       }
+
+       if (a->argc < 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       cfg = ao2_global_obj_ref(globals);
+
+       if (!(option = notify_option_find(cfg->notify_options, a->argv[2])))
+       {
+               ast_cli(a->fd, "Unable to find notify type '%s'\n",
+                       a->argv[2]);
+               return CLI_FAILURE;
+       }
+
+       for (i = 3; i < a->argc; ++i) {
+               ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
+                       a->argv[2], a->argv[i]);
+
+               switch (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")
+};
+
+/*!
+ * \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, "Channel");
+       struct ast_variable *vars = astman_get_variables(m);
+
+       if (ast_strlen_zero(endpoint_name)) {
+               astman_send_error(s, m, "GulpNotify requires a channel name");
+               return 0;
+       }
+
+       if (!strncasecmp(endpoint_name, "sip/", 4)) {
+               endpoint_name += 4;
+       }
+
+       switch (push_notify(endpoint_name, vars, notify_ami_data_create)) {
+       case INVALID_ENDPOINT:
+               astman_send_error_va(s, m, "Unable to retrieve endpoint %s\n",
+                       endpoint_name);
+               return 0;
+       case ALLOC_ERROR:
+               astman_send_error(s, m, "Unable to allocate NOTIFY task data\n");
+               return 0;
+       case TASK_PUSH_ERROR:
+               astman_send_error(s, m, "Unable to push NOTIFY task\n");
+               return 0;
+       default:
+               break;
+       }
+
+       astman_send_ack(s, m, "Notify Sent");
+       return 0;
+}
+
+static int load_module(void)
+{
+       if (aco_info_init(&notify_cfg)) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       aco_option_register_custom(&notify_cfg, "^.*$", ACO_REGEX, notify_options,
+                                  "", notify_option_handler, 0);
+
+       if (aco_process_config(&notify_cfg, 0)) {
+               aco_info_destroy(&notify_cfg);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
+       ast_manager_register_xml("GulpNotify", EVENT_FLAG_SYSTEM, manager_notify);
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_module(void)
+{
+       return aco_process_config(&notify_cfg, 1) ?
+               AST_MODULE_LOAD_DECLINE : 0;
+}
+
+static int unload_module(void)
+{
+       ast_manager_unregister("GulpNotify");
+       aco_info_destroy(&notify_cfg);
+
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "CLI/AGI SIP Notify Support",
+               .load = load_module,
+               .reload = reload_module,
+               .unload = unload_module,
+               .load_pri = AST_MODPRI_APP_DEPEND,
+              );