AMI hook interface
authorDavid Brooks <dbrooks@digium.com>
Tue, 3 Nov 2009 21:26:28 +0000 (21:26 +0000)
committerDavid Brooks <dbrooks@digium.com>
Tue, 3 Nov 2009 21:26:28 +0000 (21:26 +0000)
This patch, originally submitted by jozza, enables custom modules to send actions to AMI
and receive messages from AMI via a hook interface. Included is a simple test module to
illustrate the interface.

(closes issue #14635)
Reported by: jozza

Review: https://reviewboard.asterisk.org/r/412/

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

include/asterisk/manager.h
main/manager.c
tests/test_amihooks.c [new file with mode: 0644]

index 33eb52c..5b83c3e 100644 (file)
@@ -73,6 +73,7 @@
 #define EVENT_FLAG_DIALPLAN            (1 << 11) /* Dialplan events (VarSet, NewExten) */
 #define EVENT_FLAG_ORIGINATE   (1 << 12) /* Originate a call to an extension */
 #define EVENT_FLAG_AGI                 (1 << 13) /* AGI events */
+#define EVENT_FLAG_HOOKRESPONSE                (1 << 14) /* Hook Response */
 /*@} */
 
 /*! \brief Export manager structures */
@@ -107,6 +108,15 @@ void ast_manager_register_hook(struct manager_custom_hook *hook);
 */
 void ast_manager_unregister_hook(struct manager_custom_hook *hook);
 
+/*! \brief Registered hooks can call this function to invoke actions and they will receive responses through registered callback
+ * \param hookid the file identifier specified in manager_custom_hook struct when registering a hook
+ * \param msg ami action mesage string e.g. "Action: SipPeers\r\n"
+
+ * \retval 0 on Success
+ * \retval non-zero on Failure
+*/
+int ast_hook_send_action(struct manager_custom_hook *hook, const char *msg);
+
 struct mansession;
 
 struct message {
index 1983dde..17f487f 100644 (file)
@@ -838,6 +838,7 @@ struct mansession {
        struct ast_tcptls_session_instance *tcptls_session;
        FILE *f;
        int fd;
+       struct manager_custom_hook *hook;
        ast_mutex_t lock;
 };
 
@@ -1597,13 +1598,84 @@ struct ast_variable *astman_get_variables(const struct message *m)
        return head;
 }
 
+/* access for hooks to send action messages to ami */
+
+int ast_hook_send_action(struct manager_custom_hook *hook, const char *msg)
+{
+       const char *action;
+       int ret = 0;
+       struct manager_action *tmp;
+       struct mansession s = {.session = NULL, };
+       struct message m = { 0 };
+       char header_buf[1025] = { '\0' };
+       const char *src = msg;
+       int x = 0;
+       int curlen;
+
+       if (hook == NULL) {
+               return -1;
+       }
+
+       /* convert msg string to message struct */
+       curlen = strlen(msg);
+       for (x = 0; x < curlen; x++) {
+               int cr; /* set if we have \r */
+               if (src[x] == '\r' && x+1 < curlen && src[x+1] == '\n')
+                       cr = 2; /* Found. Update length to include \r\n */
+               else if (src[x] == '\n')
+                       cr = 1; /* also accept \n only */
+               else
+                       continue;
+               /* don't copy empty lines */
+               if (x) {
+                       memmove(header_buf, src, x);    /*... but trim \r\n */
+                       header_buf[x] = '\0';           /* terminate the string */
+                       m.headers[m.hdrcount++] = ast_strdupa(header_buf);
+               }
+               x += cr;
+               curlen -= x;            /* remaining size */
+               src += x;               /* update pointer */
+               x = -1;                 /* reset loop */
+       }
+
+       action = astman_get_header(&m,"Action");
+       if (action && strcasecmp(action,"login")) {
+
+               AST_RWLIST_RDLOCK(&actions);
+               AST_RWLIST_TRAVERSE(&actions, tmp, list) {
+                       if (strcasecmp(action, tmp->action))
+                               continue;
+                       /*
+                       * we have to simulate a session for this action request
+                       * to be able to pass it down for processing
+                       * This is necessary to meet the previous design of manager.c
+                       */
+                       s.hook = hook;
+                       s.f = (void*)1; /* set this to something so our request will make it through all functions that test it*/
+                       ret = tmp->func(&s, &m);
+                       break;
+               }
+               AST_RWLIST_UNLOCK(&actions);
+       }
+       return ret;
+}
+
+
 /*!
  * helper function to send a string to the socket.
  * Return -1 on error (e.g. buffer full).
  */
 static int send_string(struct mansession *s, char *string)
 {
-       if (s->f) {
+       /* It's a result from one of the hook's action invocation */
+       if (s->hook) {
+               /*
+                * to send responses, we're using the same function
+                * as for receiving events. We call the event "HookResponse"
+                */
+               s->hook->helper(EVENT_FLAG_HOOKRESPONSE, "HookResponse", string);
+               return 0;
+       } else if (s->f) {
                return ast_careful_fwrite(s->f, s->fd, string, strlen(string), s->session->writetimeout);
        } else {
                return ast_careful_fwrite(s->session->f, s->session->fd, string, strlen(string), s->session->writetimeout);
diff --git a/tests/test_amihooks.c b/tests/test_amihooks.c
new file mode 100644 (file)
index 0000000..c4940ae
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2009, Digium, Inc.
+ *
+ * David Brooks <dbrooks@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.
+ */
+
+/*! \file
+ *
+ * \brief Test AMI hook
+ *
+ * \author David Brooks <dbrooks@digium.com> based off of code written by Russell Bryant <russell@digium.com>
+ *
+ * This is simply an example or test module illustrating the ability for a custom module
+ * to hook into AMI. Registration for AMI events and sending of AMI actions is shown.
+ */
+
+/*** MODULEINFO
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/manager.h"
+
+/* The helper function is required by struct manager_custom_hook. See __manager_event for details */
+static int amihook_helper(int category, const char *event, char *content)
+{
+       ast_log(LOG_NOTICE, "AMI Event: \nCategory: %d Event: %s\n%s\n", category, event, content);
+       return 0;
+}
+
+static struct manager_custom_hook test_hook = {
+       .file = __FILE__,
+       .helper = &amihook_helper,
+};
+
+static int test_send(struct ast_cli_args *a) {
+       int res;
+
+       /* Send a test action (core show version) to the AMI */
+       res = ast_hook_send_action(&test_hook, "Action: Command\nCommand: core show version\nActionID: 987654321\n");
+
+       return res;
+}
+
+static char *handle_cli_amihook_test_send(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "amihook send test";
+               e->usage = ""
+                       "Usage: amihook send test"
+                       "";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       case CLI_HANDLER:
+               test_send(a);
+               return CLI_SUCCESS;
+       }
+
+       return CLI_FAILURE;
+}
+
+static struct ast_cli_entry cli_amihook_evt[] = {
+       AST_CLI_DEFINE(handle_cli_amihook_test_send, "Test module for AMI hook"),
+};
+
+static int unload_module(void)
+{
+       ast_manager_unregister_hook(&test_hook);
+       return ast_cli_unregister_multiple(cli_amihook_evt, ARRAY_LEN(cli_amihook_evt));
+}
+
+static int load_module(void)
+{
+       int res;
+
+       /* Register the hook for AMI events */
+       ast_manager_register_hook(&test_hook);
+
+       res = ast_cli_register_multiple(cli_amihook_evt, ARRAY_LEN(cli_amihook_evt));
+
+       return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "AMI Hook Test Module");