dect
/
asterisk
Archived
13
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
asterisk/pbx/pbx_lua.c

1291 lines
32 KiB
C

/*
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#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 <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 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);
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 nargs = lua_gettop(L);
char data[LUA_EXT_DATA_SIZE] = "";
char *data_next = data;
size_t data_left = sizeof(data);
int res;
lua_getfield(L, 1, "name");
char *app_name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
struct ast_app *app = pbx_findapp(app_name);
if (!app) {
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");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "context");
char *context = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "exten");
char *exten = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "priority");
int priority = lua_tointeger(L, -1);
lua_pop(L, 1);
if (nargs > 1) {
if (!lua_isnil(L, 2))
ast_build_string(&data_next, &data_left, "%s", luaL_checkstring(L, 2));
int i;
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));
}
}
char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE];
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");
int 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)
{
char *value = NULL;
char *workspace = alloca(LUA_BUF_SIZE);
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");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "name");
char *name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
int 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()
* \endcode
*/
static int lua_set_variable_value(lua_State *L)
{
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");
const char *name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
const char *value = luaL_checkstring(L, 2);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
int 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 [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)
{
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");
struct ast_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)
{
const char *name = luaL_checkstring(L, 2);
const char *value = luaL_checkstring(L, 3);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
int 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;
size_t fullname_left = sizeof(fullname);
lua_getfield(L, 1, "name");
char *name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
ast_build_string(&fullname_next, &fullname_left, "%s(", name);
if (nargs > 1) {
if (!lua_isnil(L, 2))
ast_build_string(&fullname_next, &fullname_left, "%s", luaL_checkstring(L, 2));
int i;
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)
{
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
int 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)
{
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
struct ast_channel *chan = lua_touserdata(L, -1);
lua_pop(L, 1);
int 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 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)
{
/* create the extensions_order table */
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, "extensions_order");
lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order");
int extensions_order = lua_gettop(L);
/* sort each context in the extensions table */
/* load the 'extensions' table */
lua_getglobal(L, "extensions");
int 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;
lua_pushvalue(L, context_name);
lua_newtable(L);
int 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)
{
char *path = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2);
sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config);
FILE *f = fopen(path, "r");
if (!f) {
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);
char *data = ast_malloc(*size);
if (!data) {
*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
*
* 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);
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;
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);
lua_State *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)
{
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;
}
lua_State *L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
int 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)
{
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;
}
lua_State *L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
int 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)
{
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;
}
lua_State *L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
int 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)
{
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;
}
int res;
lua_State *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)
{
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);
int 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 */
int context_order_table = lua_gettop(L);
/* step through the extensions looking for a match */
int i;
for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) {
lua_pushinteger(L, i);
lua_gettable(L, context_order_table);
int e_index = lua_gettop(L);
int isnumber = lua_isnumber(L, e_index);
const char *e = lua_tostring(L, e_index);
if (!e) {
lua_pop(L, 1);
continue;
}
/* make sure this is not the 'include' extension */
if(!strcasecmp(e, "include")) {
lua_pop(L, 1);
continue;
}
int match = 0;
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)
{
lua_State *L = luaL_newstate();
if (!L) {
ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
return AST_MODULE_LOAD_FAILURE;
}
int res = AST_MODULE_LOAD_SUCCESS;
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_FAILURE;
}
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)
{
if (load_or_reload_lua_stuff())
return AST_MODULE_LOAD_FAILURE;
if (ast_register_switch(&lua_switch)) {
ast_log(LOG_ERROR, "Unable to register LUA PBX switch\n");
return AST_MODULE_LOAD_FAILURE;
}
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,
);