aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/pbx/pbx_lua.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/pbx/pbx_lua.c')
-rw-r--r--trunk/pbx/pbx_lua.c1354
1 files changed, 1354 insertions, 0 deletions
diff --git a/trunk/pbx/pbx_lua.c b/trunk/pbx/pbx_lua.c
new file mode 100644
index 000000000..a23b31ee2
--- /dev/null
+++ b/trunk/pbx/pbx_lua.c
@@ -0,0 +1,1354 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2007, Digium, Inc.
+ *
+ * Matthew Nicholson <mnicholson@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
+ *
+ * \author Matthew Nicholson <mnicholson@digium.com>
+ * \brief Lua PBX Switch
+ *
+ */
+
+/*** MODULEINFO
+ <depend>lua</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/term.h"
+#include "asterisk/paths.h"
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+static char *config = "extensions.lua";
+
+#define LUA_EXT_DATA_SIZE 256
+#define LUA_BUF_SIZE 4096
+
+static char *lua_read_extensions_file(lua_State *L, long *size);
+static int lua_load_extensions(lua_State *L, struct ast_channel *chan);
+static int lua_reload_extensions(lua_State *L);
+static void lua_free_extensions(void);
+static int lua_sort_extensions(lua_State *L);
+static int lua_extension_cmp(lua_State *L);
+static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func);
+static int lua_pbx_findapp(lua_State *L);
+static int lua_pbx_exec(lua_State *L);
+
+static int lua_get_variable_value(lua_State *L);
+static int lua_set_variable_value(lua_State *L);
+static int lua_get_variable(lua_State *L);
+static int lua_set_variable(lua_State *L);
+static int lua_func_read(lua_State *L);
+
+static int lua_autoservice_start(lua_State *L);
+static int lua_autoservice_stop(lua_State *L);
+static int lua_autoservice_status(lua_State *L);
+static int lua_check_hangup(lua_State *L);
+
+static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority);
+static void lua_push_variable_table(lua_State *L, const char *name);
+static void lua_create_app_table(lua_State *L);
+static void lua_create_channel_table(lua_State *L);
+static void lua_create_variable_metatable(lua_State *L);
+static void lua_create_application_metatable(lua_State *L);
+static void lua_create_autoservice_functions(lua_State *L);
+static void lua_create_hangup_function(lua_State *L);
+
+void lua_state_destroy(void *data);
+static lua_State *lua_get_state(struct ast_channel *chan);
+
+static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data);
+static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data);
+static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data);
+static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data);
+
+AST_MUTEX_DEFINE_STATIC(config_file_lock);
+char *config_file_data = NULL;
+long config_file_size = 0;
+
+static const struct ast_datastore_info lua_datastore = {
+ .type = "lua",
+ .destroy = lua_state_destroy,
+};
+
+
+/*!
+ * \brief The destructor for lua_datastore
+ */
+void lua_state_destroy(void *data)
+{
+ if (data)
+ lua_close(data);
+}
+
+/*!
+ * \brief [lua_CFunction] Find an app and return it in a lua table (for access from lua, don't
+ * call directly)
+ *
+ * This function would be called in the following example as it would be found
+ * in extensions.lua.
+ *
+ * \code
+ * app.dial
+ * \endcode
+ */
+static int lua_pbx_findapp(lua_State *L)
+{
+ const char *app_name = luaL_checkstring(L, 2);
+
+ lua_newtable(L);
+
+ lua_pushstring(L, "name");
+ lua_pushstring(L, app_name);
+ lua_settable(L, -3);
+
+ luaL_getmetatable(L, "application");
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] This function is part of the 'application' metatable
+ * and is used to execute applications similar to pbx_exec() (for access from
+ * lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ * \return nothing
+ *
+ * This funciton is executed as the '()' operator for apps accessed through the
+ * 'app' table.
+ *
+ * \code
+ * app.playback('demo-congrats')
+ * \endcode
+ */
+static int lua_pbx_exec(lua_State *L)
+{
+ int res, nargs = lua_gettop(L);
+ char data[LUA_EXT_DATA_SIZE] = "";
+ char *data_next = data, *app_name;
+ char *context, *exten;
+ char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE];
+ int priority, autoservice;
+ size_t data_left = sizeof(data);
+ struct ast_app *app;
+ struct ast_channel *chan;
+
+ lua_getfield(L, 1, "name");
+ app_name = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ if (!(app = pbx_findapp(app_name))) {
+ lua_pushstring(L, "application '");
+ lua_pushstring(L, app_name);
+ lua_pushstring(L, "' not found");
+ lua_concat(L, 3);
+ return lua_error(L);
+ }
+
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "context");
+ context = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "exten");
+ exten = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "priority");
+ priority = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+
+
+ if (nargs > 1) {
+ int i;
+
+ if (!lua_isnil(L, 2))
+ ast_build_string(&data_next, &data_left, "%s", luaL_checkstring(L, 2));
+
+ for (i = 3; i <= nargs; i++) {
+ if (lua_isnil(L, i))
+ ast_build_string(&data_next, &data_left, ",");
+ else
+ ast_build_string(&data_next, &data_left, ",%s", luaL_checkstring(L, i));
+ }
+ }
+
+ ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n",
+ exten, context, priority,
+ term_color(tmp, app_name, COLOR_BRCYAN, 0, sizeof(tmp)),
+ term_color(tmp2, chan->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
+ term_color(tmp3, data, COLOR_BRMAGENTA, 0, sizeof(tmp3)));
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+ autoservice = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+
+ if (autoservice)
+ ast_autoservice_stop(chan);
+
+ res = pbx_exec(chan, app, data);
+
+ if (autoservice)
+ ast_autoservice_start(chan);
+
+ /* error executing an application, report it */
+ if (res) {
+ lua_pushinteger(L, res);
+ return lua_error(L);
+ }
+ return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Used to get the value of a variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * The value of the variable or function is returned. This function is the
+ * 'get()' function in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.variable:get()
+ * \endcode
+ */
+static int lua_get_variable_value(lua_State *L)
+{
+ struct ast_channel *chan;
+ char *value = NULL, *name;
+ char *workspace = alloca(LUA_BUF_SIZE);
+ int autoservice;
+
+ workspace[0] = '\0';
+
+ if (!lua_istable(L, 1)) {
+ lua_pushstring(L, "User probably used '.' instead of ':' for retrieving a channel variable value");
+ return lua_error(L);
+ }
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ lua_getfield(L, 1, "name");
+ name = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+ autoservice = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+
+ if (autoservice)
+ ast_autoservice_stop(chan);
+
+ /* if this is a dialplan function then use ast_func_read(), otherwise
+ * use pbx_retrieve_variable() */
+ if (!ast_strlen_zero(name) && name[strlen(name) - 1] == ')') {
+ value = ast_func_read(chan, name, workspace, LUA_BUF_SIZE) ? NULL : workspace;
+ } else {
+ pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead);
+ }
+
+ if (autoservice)
+ ast_autoservice_start(chan);
+
+ if (value) {
+ lua_pushstring(L, value);
+ } else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Used to set the value of a variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is the 'set()' function in the following example as would be
+ * seen in extensions.lua.
+ *
+ * \code
+ * channel.variable:set("value")
+ * \endcode
+ */
+static int lua_set_variable_value(lua_State *L)
+{
+ const char *name, *value;
+ struct ast_channel *chan;
+ int autoservice;
+
+ if (!lua_istable(L, 1)) {
+ lua_pushstring(L, "User probably used '.' instead of ':' for setting a channel variable");
+ return lua_error(L);
+ }
+
+ lua_getfield(L, 1, "name");
+ name = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ value = luaL_checkstring(L, 2);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+ autoservice = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+
+ if (autoservice)
+ ast_autoservice_stop(chan);
+
+ pbx_builtin_setvar_helper(chan, name, value);
+
+ if (autoservice)
+ ast_autoservice_start(chan);
+
+ return 0;
+}
+
+/*!
+ * \brief Update the lua registry with the given context, exten, and priority.
+ *
+ * \param L the lua_State to use
+ * \param context the new context
+ * \param exten the new exten
+ * \param priority the new priority
+ */
+static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority)
+{
+ lua_pushstring(L, context);
+ lua_setfield(L, LUA_REGISTRYINDEX, "context");
+
+ lua_pushstring(L, exten);
+ lua_setfield(L, LUA_REGISTRYINDEX, "exten");
+
+ lua_pushinteger(L, priority);
+ lua_setfield(L, LUA_REGISTRYINDEX, "priority");
+}
+
+/*!
+ * \brief Push a 'variable' table on the stack for access the channel variable
+ * with the given name.
+ *
+ * \param L the lua_State to use
+ * \param name the name of the variable
+ */
+static void lua_push_variable_table(lua_State *L, const char *name)
+{
+ lua_newtable(L);
+ luaL_getmetatable(L, "variable");
+ lua_setmetatable(L, -2);
+
+ lua_pushstring(L, name);
+ lua_setfield(L, -2, "name");
+
+ lua_pushcfunction(L, &lua_get_variable_value);
+ lua_setfield(L, -2, "get");
+
+ lua_pushcfunction(L, &lua_set_variable_value);
+ lua_setfield(L, -2, "set");
+}
+
+/*!
+ * \brief Create the global 'app' table for executing applications
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_app_table(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_newmetatable(L, "app");
+
+ lua_pushstring(L, "__index");
+ lua_pushcfunction(L, &lua_pbx_findapp);
+ lua_settable(L, -3);
+
+ lua_setmetatable(L, -2);
+ lua_setglobal(L, "app");
+}
+
+/*!
+ * \brief Create the global 'channel' table for accesing channel variables
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_channel_table(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_newmetatable(L, "channel_data");
+
+ lua_pushstring(L, "__index");
+ lua_pushcfunction(L, &lua_get_variable);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "__newindex");
+ lua_pushcfunction(L, &lua_set_variable);
+ lua_settable(L, -3);
+
+ lua_setmetatable(L, -2);
+ lua_setglobal(L, "channel");
+}
+
+/*!
+ * \brief Create the 'variable' metatable, used to retrieve channel variables
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_variable_metatable(lua_State *L)
+{
+ luaL_newmetatable(L, "variable");
+
+ lua_pushstring(L, "__call");
+ lua_pushcfunction(L, &lua_func_read);
+ lua_settable(L, -3);
+
+ lua_pop(L, 1);
+}
+
+/*!
+ * \brief Create the 'application' metatable, used to execute asterisk
+ * applications from lua
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_application_metatable(lua_State *L)
+{
+ luaL_newmetatable(L, "application");
+
+ lua_pushstring(L, "__call");
+ lua_pushcfunction(L, &lua_pbx_exec);
+ lua_settable(L, -3);
+
+ lua_pop(L, 1);
+}
+
+/*!
+ * \brief Create the autoservice functions
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_autoservice_functions(lua_State *L)
+{
+ lua_pushcfunction(L, &lua_autoservice_start);
+ lua_setglobal(L, "autoservice_start");
+
+ lua_pushcfunction(L, &lua_autoservice_stop);
+ lua_setglobal(L, "autoservice_stop");
+
+ lua_pushcfunction(L, &lua_autoservice_status);
+ lua_setglobal(L, "autoservice_status");
+
+ lua_pushboolean(L, 0);
+ lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+}
+
+/*!
+ * \brief Create the hangup check function
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_hangup_function(lua_State *L)
+{
+ lua_pushcfunction(L, &lua_check_hangup);
+ lua_setglobal(L, "check_hangup");
+}
+
+/*!
+ * \brief [lua_CFunction] Return a lua 'variable' object (for access from lua, don't call
+ * directly)
+ *
+ * This function is called to lookup a variable construct a 'variable' object.
+ * It would be called in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.variable
+ * \endcode
+ */
+static int lua_get_variable(lua_State *L)
+{
+ struct ast_channel *chan;
+ char *name = ast_strdupa(luaL_checkstring(L, 2));
+ char *value = NULL;
+ char *workspace = alloca(LUA_BUF_SIZE);
+ workspace[0] = '\0';
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ lua_push_variable_table(L, name);
+
+ /* if this is not a request for a dialplan funciton attempt to retrieve
+ * the value of the variable */
+ if (!ast_strlen_zero(name) && name[strlen(name) - 1] != ')') {
+ pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead);
+ }
+
+ if (value) {
+ lua_pushstring(L, value);
+ lua_setfield(L, -2, "value");
+ }
+
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Set the value of a channel variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is called to set a variable or dialplan function. It would be
+ * called in the following example as would be seen in extensions.lua.
+ *
+ * \code
+ * channel.variable = "value"
+ * \endcode
+ */
+static int lua_set_variable(lua_State *L)
+{
+ struct ast_channel *chan;
+ int autoservice;
+ const char *name = luaL_checkstring(L, 2);
+ const char *value = luaL_checkstring(L, 3);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+ autoservice = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+
+ if (autoservice)
+ ast_autoservice_stop(chan);
+
+ pbx_builtin_setvar_helper(chan, name, value);
+
+ if (autoservice)
+ ast_autoservice_start(chan);
+
+ return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Create a 'variable' object for accessing a dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is called to create a 'variable' object to access a dialplan
+ * function. It would be called in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.func("arg1", "arg2", "arg3")
+ * \endcode
+ *
+ * To actually do anything with the resulting value you must use the 'get()'
+ * and 'set()' methods (the reason is the resulting value is not a value, but
+ * an object in the form of a lua table).
+ */
+static int lua_func_read(lua_State *L)
+{
+ int nargs = lua_gettop(L);
+ char fullname[LUA_EXT_DATA_SIZE] = "";
+ char *fullname_next = fullname, *name;
+ size_t fullname_left = sizeof(fullname);
+
+ lua_getfield(L, 1, "name");
+ name = ast_strdupa(lua_tostring(L, -1));
+ lua_pop(L, 1);
+
+ ast_build_string(&fullname_next, &fullname_left, "%s(", name);
+
+ if (nargs > 1) {
+ int i;
+
+ if (!lua_isnil(L, 2))
+ ast_build_string(&fullname_next, &fullname_left, "%s", luaL_checkstring(L, 2));
+
+ for (i = 3; i <= nargs; i++) {
+ if (lua_isnil(L, i))
+ ast_build_string(&fullname_next, &fullname_left, ",");
+ else
+ ast_build_string(&fullname_next, &fullname_left, ",%s", luaL_checkstring(L, i));
+ }
+ }
+
+ ast_build_string(&fullname_next, &fullname_left, ")");
+
+ lua_push_variable_table(L, fullname);
+
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Tell pbx_lua to maintain an autoservice on this
+ * channel (for access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * This function will set a flag that will cause pbx_lua to maintain an
+ * autoservice on this channel. The autoservice will automatically be stopped
+ * and restarted before calling applications and functions.
+ *
+ * \return This function returns the result of the ast_autoservice_start()
+ * function as a boolean to its lua caller.
+ */
+static int lua_autoservice_start(lua_State *L)
+{
+ struct ast_channel *chan;
+ int res;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ res = ast_autoservice_start(chan);
+
+ lua_pushboolean(L, !res);
+ lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+
+ lua_pushboolean(L, !res);
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Tell pbx_lua to stop maintaning an autoservice on
+ * this channel (for access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * This function will stop any autoservice running and turn off the autoservice
+ * flag. If this function returns false, it's probably because no autoservice
+ * was running to begin with.
+ *
+ * \return This function returns the result of the ast_autoservice_stop()
+ * function as a boolean to its lua caller.
+ */
+static int lua_autoservice_stop(lua_State *L)
+{
+ struct ast_channel *chan;
+ int res;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ res = ast_autoservice_stop(chan);
+
+ lua_pushboolean(L, 0);
+ lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+
+ lua_pushboolean(L, !res);
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Get the status of the autoservice flag (for access
+ * from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * \return This function returns the status of the autoservice flag as a
+ * boolean to its lua caller.
+ */
+static int lua_autoservice_status(lua_State *L)
+{
+ lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+ return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Check if this channel has been hungup or not (for
+ * access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * \return This function returns true if the channel was hungup
+ */
+static int lua_check_hangup(lua_State *L)
+{
+ struct ast_channel *chan;
+ lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+ chan = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ lua_pushboolean(L, ast_check_hangup(chan));
+ return 1;
+}
+
+/*!
+ * \brief Store the sort order of each context
+
+ * In the event of an error, an error string will be pushed onto the lua stack.
+ *
+ * \retval 0 success
+ * \retval 1 failure
+ */
+static int lua_sort_extensions(lua_State *L)
+{
+ int extensions, extensions_order;
+
+ /* create the extensions_order table */
+ lua_newtable(L);
+ lua_setfield(L, LUA_REGISTRYINDEX, "extensions_order");
+ lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order");
+ extensions_order = lua_gettop(L);
+
+ /* sort each context in the extensions table */
+ /* load the 'extensions' table */
+ lua_getglobal(L, "extensions");
+ extensions = lua_gettop(L);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ lua_pushstring(L, "Unable to find 'extensions' table in extensions.lua\n");
+ return 1;
+ }
+
+ /* iterate through the extensions table and create a
+ * matching table (holding the sort order) in the
+ * extensions_order table for each context that is found
+ */
+ for (lua_pushnil(L); lua_next(L, extensions); lua_pop(L, 1)) {
+ int context = lua_gettop(L);
+ int context_name = context - 1;
+ int context_order;
+
+ lua_pushvalue(L, context_name);
+ lua_newtable(L);
+ context_order = lua_gettop(L);
+
+ /* iterate through this context an popluate the corrisponding
+ * table in the extensions_order table */
+ for (lua_pushnil(L); lua_next(L, context); lua_pop(L, 1)) {
+ int exten = lua_gettop(L) - 1;
+
+ lua_pushinteger(L, lua_objlen(L, context_order) + 1);
+ lua_pushvalue(L, exten);
+ lua_settable(L, context_order);
+ }
+ lua_settable(L, extensions_order); /* put the context_order table in the extensions_order table */
+
+ /* now sort the new table */
+
+ /* push the table.sort function */
+ lua_getglobal(L, "table");
+ lua_getfield(L, -1, "sort");
+ lua_remove(L, -2); /* remove the 'table' table */
+
+ /* push the context_order table */
+ lua_pushvalue(L, context_name);
+ lua_gettable(L, extensions_order);
+
+ /* push the comp function */
+ lua_pushcfunction(L, &lua_extension_cmp);
+
+ if (lua_pcall(L, 2, 0, 0)) {
+ lua_insert(L, -5);
+ lua_pop(L, 4);
+ return 1;
+ }
+ }
+
+ /* remove the extensions table and the extensions_order table */
+ lua_pop(L, 2);
+ return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Compare two extensions (for access from lua, don't
+ * call directly)
+ *
+ * This function returns true if the first extension passed should match after
+ * the second. It behaves like the '<' operator.
+ */
+static int lua_extension_cmp(lua_State *L)
+{
+ const char *a = luaL_checkstring(L, -2);
+ const char *b = luaL_checkstring(L, -1);
+
+ if (ast_extension_cmp(a, b) == -1)
+ lua_pushboolean(L, 1);
+ else
+ lua_pushboolean(L, 0);
+
+ return 1;
+}
+
+/*!
+ * \brief Load the extensions.lua file in to a buffer and execute the file
+ *
+ * \param L the lua_State to use
+ * \param size a pointer to store the size of the buffer
+ *
+ * \note The caller is expected to free the buffer at some point.
+ *
+ * \return a pointer to the buffer
+ */
+static char *lua_read_extensions_file(lua_State *L, long *size)
+{
+ FILE *f;
+ char *data;
+ char *path = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2);
+ sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config);
+
+ if (!(f = fopen(path, "r"))) {
+ lua_pushstring(L, "cannot open '");
+ lua_pushstring(L, path);
+ lua_pushstring(L, "' for reading: ");
+ lua_pushstring(L, strerror(errno));
+ lua_concat(L, 4);
+
+ return NULL;
+ }
+
+ fseek(f, 0l, SEEK_END);
+ *size = ftell(f);
+
+ fseek(f, 0l, SEEK_SET);
+
+ if (!(data = ast_malloc(*size))) {
+ *size = 0;
+ fclose(f);
+ lua_pushstring(L, "not enough memory");
+ return NULL;
+ }
+
+ fread(data, sizeof(char), *size, f);
+ fclose(f);
+
+ if (luaL_loadbuffer(L, data, *size, "extensions.lua")
+ || lua_pcall(L, 0, LUA_MULTRET, 0)
+ || lua_sort_extensions(L)) {
+ ast_free(data);
+ data = NULL;
+ *size = 0;
+ }
+ return data;
+}
+
+/*!
+ * \brief Load the extensions.lua file from the internal buffer
+ *
+ * \param L the lua_State to use
+ * \param chan channel to work on
+ *
+ * This function also sets up some constructs used by the extensions.lua file.
+ * In the event of an error, an error string will be pushed onto the lua stack.
+ *
+ * \retval 0 success
+ * \retval 1 failure
+ */
+static int lua_load_extensions(lua_State *L, struct ast_channel *chan)
+{
+
+ /* store a pointer to this channel */
+ lua_pushlightuserdata(L, chan);
+ lua_setfield(L, LUA_REGISTRYINDEX, "channel");
+
+ luaL_openlibs(L);
+
+ /* load and sort extensions */
+ ast_mutex_lock(&config_file_lock);
+ if (luaL_loadbuffer(L, config_file_data, config_file_size, "extensions.lua")
+ || lua_pcall(L, 0, LUA_MULTRET, 0)
+ || lua_sort_extensions(L)) {
+ ast_mutex_unlock(&config_file_lock);
+ return 1;
+ }
+ ast_mutex_unlock(&config_file_lock);
+
+ /* now we setup special tables and functions */
+
+ lua_create_app_table(L);
+ lua_create_channel_table(L);
+
+ lua_create_variable_metatable(L);
+ lua_create_application_metatable(L);
+
+ lua_create_autoservice_functions(L);
+ lua_create_hangup_function(L);
+
+ return 0;
+}
+
+/*!
+ * \brief Reload the extensions file and update the internal buffers if it
+ * loads correctly.
+ *
+ * \warning This function should not be called on a lua_State returned from
+ * lua_get_state().
+ *
+ * \param L the lua_State to use (must be freshly allocated with
+ * luaL_newstate(), don't use lua_get_state())
+ */
+static int lua_reload_extensions(lua_State *L)
+{
+ long size = 0;
+ char *data = NULL;
+
+ luaL_openlibs(L);
+
+ if (!(data = lua_read_extensions_file(L, &size))) {
+ return 1;
+ }
+
+ ast_mutex_lock(&config_file_lock);
+
+ if (config_file_data)
+ ast_free(config_file_data);
+
+ config_file_data = data;
+ config_file_size = size;
+
+ ast_mutex_unlock(&config_file_lock);
+ return 0;
+}
+
+/*!
+ * \brief Free the internal extensions buffer.
+ */
+static void lua_free_extensions()
+{
+ ast_mutex_lock(&config_file_lock);
+ config_file_size = 0;
+ ast_free(config_file_data);
+ ast_mutex_unlock(&config_file_lock);
+}
+
+/*!
+ * \brief Get the lua_State for this channel
+ *
+ * If no channel is passed then a new state is allocated. States with no
+ * channel assocatied with them should only be used for matching extensions.
+ * If the channel does not yet have a lua state associated with it, one will be
+ * created.
+ *
+ * \note If no channel was passed then the caller is expected to free the state
+ * using lua_close().
+ *
+ * \return a lua_State
+ */
+static lua_State *lua_get_state(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore = NULL;
+ lua_State *L;
+
+ if (!chan) {
+ lua_State *L = luaL_newstate();
+ if (!L) {
+ ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
+ return NULL;
+ }
+
+ if (lua_load_extensions(L, NULL)) {
+ const char *error = lua_tostring(L, -1);
+ ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error);
+ lua_close(L);
+ return NULL;
+ }
+ return L;
+ } else {
+ datastore = ast_channel_datastore_find(chan, &lua_datastore, NULL);
+
+ if (!datastore) {
+ /* nothing found, allocate a new lua state */
+ datastore = ast_channel_datastore_alloc(&lua_datastore, NULL);
+ if (!datastore) {
+ ast_log(LOG_ERROR, "Error allocation channel datastore for lua_State\n");
+ return NULL;
+ }
+
+ datastore->data = luaL_newstate();
+ if (!datastore->data) {
+ ast_channel_datastore_free(datastore);
+ ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
+ return NULL;
+ }
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+
+ L = datastore->data;
+
+ if (lua_load_extensions(L, chan)) {
+ const char *error = lua_tostring(L, -1);
+ ast_log(LOG_ERROR, "Error loading extensions.lua for %s: %s\n", chan->name, error);
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_remove(chan, datastore);
+ ast_channel_unlock(chan);
+
+ ast_channel_datastore_free(datastore);
+ return NULL;
+ }
+ }
+
+ return datastore->data;
+ }
+}
+
+static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res;
+ lua_State *L;
+ struct ast_module_user *u = ast_module_user_add(chan);
+ if (!u) {
+ ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n");
+ return 0;
+ }
+
+ L = lua_get_state(chan);
+ if (!L) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ res = lua_find_extension(L, context, exten, priority, &exists, 0);
+
+ if (!chan) lua_close(L);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res;
+ lua_State *L;
+ struct ast_module_user *u = ast_module_user_add(chan);
+ if (!u) {
+ ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n");
+ return 0;
+ }
+
+ L = lua_get_state(chan);
+ if (!L) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ res = lua_find_extension(L, context, exten, priority, &canmatch, 0);
+
+ if (!chan) lua_close(L);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res;
+ lua_State *L;
+ struct ast_module_user *u = ast_module_user_add(chan);
+ if (!u) {
+ ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n");
+ return 0;
+ }
+
+ L = lua_get_state(chan);
+ if (!L) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ res = lua_find_extension(L, context, exten, priority, &matchmore, 0);
+
+ if (!chan) lua_close(L);
+ ast_module_user_remove(u);
+ return res;
+}
+
+
+static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res;
+ lua_State *L;
+ struct ast_module_user *u = ast_module_user_add(chan);
+ if (!u) {
+ ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n");
+ return -1;
+ }
+
+ L = lua_get_state(chan);
+ if (!L) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* push the extension function onto the stack */
+ if (!lua_find_extension(L, context, exten, priority, &exists, 1)) {
+ ast_log(LOG_ERROR, "Could not find extension %s in context %s\n", exten, context);
+ if (!chan) lua_close(L);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ lua_update_registry(L, context, exten, priority);
+
+ lua_pushstring(L, context);
+ lua_pushstring(L, exten);
+
+ res = lua_pcall(L, 2, 0, 0);
+ if (res) {
+ if (res == LUA_ERRRUN) {
+ if (lua_isnumber(L, -1)) {
+ res = lua_tointeger(L, -1);
+ } else if (lua_isstring(L, -1)) {
+ const char *error = lua_tostring(L, -1);
+ ast_log(LOG_ERROR, "Error executing lua extension: %s\n", error);
+ res = -1;
+ }
+ } else {
+ res = -1;
+ }
+ }
+ if (!chan) lua_close(L);
+ ast_module_user_remove(u);
+ return res;
+}
+
+/*!
+ * \brief Locate an extensions and optionally push the matching function on the
+ * stack
+ *
+ * \param L the lua_State to use
+ * \param context the context to look in
+ * \param exten the extension to look up
+ * \param priority the priority to check, '1' is the only valid priority
+ * \param func the calling func, used to adjust matching behavior between,
+ * match, canmatch, and matchmore
+ * \param push_func whether or not to push the lua function for the given
+ * extension onto the stack
+ */
+static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func)
+{
+ int context_table, context_order_table, i;
+
+ ast_debug(2, "Looking up %s@%s:%i\n", exten, context, priority);
+ if (priority != 1)
+ return 0;
+
+ /* load the 'extensions' table */
+ lua_getglobal(L, "extensions");
+ if (lua_isnil(L, -1)) {
+ ast_log(LOG_ERROR, "Unable to find 'extensions' table in extensions.lua\n");
+ lua_pop(L, 1);
+ return 0;
+ }
+
+ /* load the given context */
+ lua_getfield(L, -1, context);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 2);
+ return 0;
+ }
+
+ /* remove the extensions table */
+ lua_remove(L, -2);
+
+ context_table = lua_gettop(L);
+
+ /* load the extensions order table for this context */
+ lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order");
+ lua_getfield(L, -1, context);
+
+ lua_remove(L, -2); /* remove the extensions order table */
+
+ context_order_table = lua_gettop(L);
+
+ /* step through the extensions looking for a match */
+ for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) {
+ int e_index, isnumber, match = 0;
+ const char *e;
+
+ lua_pushinteger(L, i);
+ lua_gettable(L, context_order_table);
+ e_index = lua_gettop(L);
+ isnumber = lua_isnumber(L, e_index);
+
+ if (!(e = lua_tostring(L, e_index))) {
+ lua_pop(L, 1);
+ continue;
+ }
+
+ /* make sure this is not the 'include' extension */
+ if (!strcasecmp(e, "include")) {
+ lua_pop(L, 1);
+ continue;
+ }
+
+ if (func == &matchmore)
+ match = ast_extension_close(e, exten, E_MATCHMORE);
+ else if (func == &canmatch)
+ match = ast_extension_close(e, exten, E_CANMATCH);
+ else
+ match = ast_extension_match(e, exten);
+
+ /* the extension matching functions return 0 on fail, 1 on
+ * match, 2 on earlymatch */
+
+ if (!match) {
+ lua_pop(L, 1);
+ continue; /* keep trying */
+ }
+
+ if (func == &matchmore && match == 2) {
+ /* We match an extension ending in '!'. The decision in
+ * this case is final and counts as no match. */
+ lua_pop(L, 3);
+ return 0;
+ }
+
+ /* remove the context table, the context order table, and the
+ * extension (or replace the extension with the corisponding
+ * function) */
+ if (push_func) {
+ /* here we must convert the exten back to an integer
+ * because lua_tostring will change the value on the
+ * stack to a string */
+ if (isnumber) {
+ int e_int = lua_tointeger(L, e_index);
+ lua_pop(L, 1); /* the exten should be the top of the stack */
+ lua_pushinteger(L, e_int);
+ }
+ lua_gettable(L, context_table);
+ lua_insert(L, -3);
+ lua_pop(L, 2);
+ } else {
+ lua_pop(L, 3);
+ }
+
+ return 1;
+ }
+
+ /* load the includes for this context */
+ lua_getfield(L, context_table, "include");
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 3);
+ return 0;
+ }
+
+ /* remove the context and the order table*/
+ lua_remove(L, context_order_table);
+ lua_remove(L, context_table);
+
+ /* Now try any includes we have in this context */
+ for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
+ const char *c = lua_tostring(L, -1);
+ if (!c)
+ continue;
+
+ if (lua_find_extension(L, c, exten, priority, func, push_func)) {
+ /* remove the value, the key, and the includes table
+ * from the stack. Leave the function behind if
+ * necessary */
+
+ if (push_func)
+ lua_insert(L, -4);
+
+ lua_pop(L, 3);
+ return 1;
+ }
+ }
+
+ /* pop the includes table */
+ lua_pop(L, 1);
+ return 0;
+}
+
+static struct ast_switch lua_switch = {
+ .name = "Lua",
+ .description = "Lua PBX Switch",
+ .exists = exists,
+ .canmatch = canmatch,
+ .exec = exec,
+ .matchmore = matchmore,
+};
+
+
+static int load_or_reload_lua_stuff(void)
+{
+ int res = AST_MODULE_LOAD_SUCCESS;
+
+ lua_State *L = luaL_newstate();
+ if (!L) {
+ ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (lua_reload_extensions(L)) {
+ const char *error = lua_tostring(L, -1);
+ ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error);
+ res = AST_MODULE_LOAD_DECLINE;
+ }
+
+ lua_close(L);
+ return res;
+}
+
+static int unload_module(void)
+{
+ ast_unregister_switch(&lua_switch);
+ lua_free_extensions();
+ return 0;
+}
+
+static int reload(void)
+{
+ return load_or_reload_lua_stuff();
+}
+
+static int load_module(void)
+{
+ int res;
+
+ if ((res = load_or_reload_lua_stuff()))
+ return res;
+
+ if (ast_register_switch(&lua_switch)) {
+ ast_log(LOG_ERROR, "Unable to register LUA PBX switch\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Lua PBX Switch",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+