From d87006ca1c2a6dee92782ea832783a90df21c00e Mon Sep 17 00:00:00 2001 From: David Brooks Date: Tue, 3 Nov 2009 21:26:28 +0000 Subject: AMI hook interface 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 | 10 +++++ main/manager.c | 74 +++++++++++++++++++++++++++++++- tests/test_amihooks.c | 104 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 tests/test_amihooks.c diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h index 33eb52c04..5b83c3ef5 100644 --- a/include/asterisk/manager.h +++ b/include/asterisk/manager.h @@ -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 { diff --git a/main/manager.c b/main/manager.c index 1983ddeb2..17f487fe2 100644 --- a/main/manager.c +++ b/main/manager.c @@ -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 index 000000000..c4940ae98 --- /dev/null +++ b/tests/test_amihooks.c @@ -0,0 +1,104 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * David Brooks + * + * 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 based off of code written by Russell Bryant + * + * 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 + no + ***/ + +#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"); -- cgit v1.2.3