1707 lines
43 KiB
C
1707 lines
43 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>
|
||
|
<support_level>extended</support_level>
|
||
|
***/
|
||
|
|
||
|
#include "asterisk.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 "asterisk/paths.h"
|
||
|
#include "asterisk/hashtab.h"
|
||
|
|
||
|
#include <lua.h>
|
||
|
#include <lauxlib.h>
|
||
|
#include <lualib.h>
|
||
|
|
||
|
static const char *config = "extensions.lua";
|
||
|
static const char *registrar = "pbx_lua";
|
||
|
|
||
|
#ifdef LOW_MEMORY
|
||
|
#define LUA_EXT_DATA_SIZE 256
|
||
|
#else
|
||
|
#define LUA_EXT_DATA_SIZE 8192
|
||
|
#endif
|
||
|
#define LUA_BUF_SIZE 4096
|
||
|
|
||
|
/* This value is used by the lua engine to signal that a Goto or dialplan jump
|
||
|
* was detected. Ensure this value does not conflict with any values dialplan
|
||
|
* applications might return */
|
||
|
#define LUA_GOTO_DETECTED 5
|
||
|
|
||
|
static char *lua_read_extensions_file(lua_State *L, size_t *size, int *file_not_openable);
|
||
|
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_register_switches(lua_State *L);
|
||
|
static int lua_register_hints(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 int lua_error_function(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);
|
||
|
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);
|
||
|
static void lua_concat_args(lua_State *L, int start, int nargs);
|
||
|
|
||
|
static void lua_state_destroy(void *data);
|
||
|
static void lua_datastore_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
|
||
|
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);
|
||
|
static char *config_file_data = NULL;
|
||
|
static size_t config_file_size = 0;
|
||
|
|
||
|
static struct ast_context *local_contexts = NULL;
|
||
|
static struct ast_hashtab *local_table = NULL;
|
||
|
|
||
|
static const struct ast_datastore_info lua_datastore = {
|
||
|
.type = "lua",
|
||
|
.destroy = lua_state_destroy,
|
||
|
.chan_fixup = lua_datastore_fixup,
|
||
|
};
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \brief The destructor for lua_datastore
|
||
|
*/
|
||
|
static void lua_state_destroy(void *data)
|
||
|
{
|
||
|
if (data)
|
||
|
lua_close(data);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief The fixup function for the lua_datastore.
|
||
|
* \param data the datastore data, in this case it will be a lua_State
|
||
|
* \param old_chan the channel we are moving from
|
||
|
* \param new_chan the channel we are moving to
|
||
|
*
|
||
|
* This function updates our internal channel pointer.
|
||
|
*/
|
||
|
static void lua_datastore_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
|
||
|
{
|
||
|
lua_State *L = data;
|
||
|
lua_pushlightuserdata(L, new_chan);
|
||
|
lua_setfield(L, LUA_REGISTRYINDEX, "channel");
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \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
|
||
|
*
|
||
|
* This funciton is executed as the '()' operator for apps accessed through the
|
||
|
* 'app' table.
|
||
|
*
|
||
|
* \return LUA error
|
||
|
*
|
||
|
* \code
|
||
|
* app.playback('demo-congrats')
|
||
|
* \endcode
|
||
|
*/
|
||
|
static int lua_pbx_exec(lua_State *L)
|
||
|
{
|
||
|
int res, nargs = lua_gettop(L);
|
||
|
const char *data = "";
|
||
|
char *app_name, *context, *exten;
|
||
|
char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE];
|
||
|
int priority, autoservice;
|
||
|
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);
|
||
|
|
||
|
context = ast_strdupa(ast_channel_context(chan));
|
||
|
exten = ast_strdupa(ast_channel_exten(chan));
|
||
|
priority = ast_channel_priority(chan);
|
||
|
|
||
|
lua_concat_args(L, 2, nargs);
|
||
|
data = lua_tostring(L, -1);
|
||
|
|
||
|
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, ast_channel_name(chan), 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);
|
||
|
|
||
|
lua_pop(L, 1); /* pop data */
|
||
|
data = "";
|
||
|
|
||
|
if (autoservice)
|
||
|
ast_autoservice_start(chan);
|
||
|
|
||
|
/* error executing an application, report it */
|
||
|
if (res) {
|
||
|
lua_pushinteger(L, res);
|
||
|
return lua_error(L);
|
||
|
}
|
||
|
|
||
|
if (strcmp(context, ast_channel_context(chan))) {
|
||
|
lua_pushstring(L, context);
|
||
|
lua_pushstring(L, ast_channel_context(chan));
|
||
|
lua_pushliteral(L, "context");
|
||
|
} else if (strcmp(exten, ast_channel_exten(chan))) {
|
||
|
lua_pushstring(L, exten);
|
||
|
lua_pushstring(L, ast_channel_exten(chan));
|
||
|
lua_pushliteral(L, "exten");
|
||
|
} else if (priority != ast_channel_priority(chan)) {
|
||
|
lua_pushinteger(L, priority);
|
||
|
lua_pushinteger(L, ast_channel_priority(chan));
|
||
|
lua_pushliteral(L, "priority");
|
||
|
} else {
|
||
|
/* no goto - restore the original position back
|
||
|
* to lua state, in case this was a recursive dialplan
|
||
|
* call (a dialplan application re-entering dialplan) */
|
||
|
lua_update_registry(L, context, exten, priority);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* goto detected - construct error message */
|
||
|
lua_insert(L, -3);
|
||
|
|
||
|
lua_pushliteral(L, " changed from ");
|
||
|
lua_insert(L, -3);
|
||
|
|
||
|
lua_pushliteral(L, " to ");
|
||
|
lua_insert(L, -2);
|
||
|
|
||
|
lua_concat(L, 5);
|
||
|
|
||
|
ast_debug(2, "Goto detected: %s\n", lua_tostring(L, -1));
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
/* let the lua engine know it needs to return control to the pbx */
|
||
|
lua_pushinteger(L, LUA_GOTO_DETECTED);
|
||
|
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.
|
||
|
*
|
||
|
* \return LUA error
|
||
|
*
|
||
|
* \code
|
||
|
* channel.variable:get()
|
||
|
* \endcode
|
||
|
*/
|
||
|
static int lua_get_variable_value(lua_State *L)
|
||
|
{
|
||
|
struct ast_channel *chan;
|
||
|
char *value = NULL, *name;
|
||
|
char *workspace = ast_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, ast_channel_varshead(chan));
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
*
|
||
|
* \return LUA error
|
||
|
*
|
||
|
* \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.
|
||
|
*
|
||
|
* The value on the top of the stack is popped and used as the name.
|
||
|
*
|
||
|
* \param L the lua_State to use
|
||
|
*/
|
||
|
static void lua_push_variable_table(lua_State *L)
|
||
|
{
|
||
|
lua_newtable(L);
|
||
|
luaL_getmetatable(L, "variable");
|
||
|
lua_setmetatable(L, -2);
|
||
|
|
||
|
lua_insert(L, -2); /* move the table after the 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 accessing 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, 1);
|
||
|
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;
|
||
|
const char *name = luaL_checkstring(L, 2);
|
||
|
char *value = NULL;
|
||
|
char *workspace = ast_alloca(LUA_BUF_SIZE);
|
||
|
workspace[0] = '\0';
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
|
||
|
chan = lua_touserdata(L, -1);
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
lua_pushvalue(L, 2);
|
||
|
lua_push_variable_table(L);
|
||
|
|
||
|
/* 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, ast_channel_varshead(chan));
|
||
|
}
|
||
|
|
||
|
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 Concatenate a list of lua function arguments into a comma separated
|
||
|
* string.
|
||
|
* \param L the lua_State to use
|
||
|
* \param start the index of the first argument
|
||
|
* \param nargs the number of args
|
||
|
*
|
||
|
* The resulting string will be left on the top of the stack.
|
||
|
*/
|
||
|
static void lua_concat_args(lua_State *L, int start, int nargs) {
|
||
|
int concat = 0;
|
||
|
int i = start + 1;
|
||
|
|
||
|
if (start <= nargs && !lua_isnil(L, start)) {
|
||
|
lua_pushvalue(L, start);
|
||
|
concat += 1;
|
||
|
}
|
||
|
|
||
|
for (; i <= nargs; i++) {
|
||
|
if (lua_isnil(L, i)) {
|
||
|
lua_pushliteral(L, ",");
|
||
|
concat += 1;
|
||
|
} else {
|
||
|
lua_pushliteral(L, ",");
|
||
|
lua_pushvalue(L, i);
|
||
|
concat += 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lua_concat(L, concat);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \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);
|
||
|
|
||
|
/* build a string in the form of "func_name(arg1,arg2,arg3)" */
|
||
|
lua_getfield(L, 1, "name");
|
||
|
lua_pushliteral(L, "(");
|
||
|
lua_concat_args(L, 2, nargs);
|
||
|
lua_pushliteral(L, ")");
|
||
|
lua_concat(L, 4);
|
||
|
|
||
|
lua_push_variable_table(L);
|
||
|
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.
|
||
|
*/
|
||
|
static int lua_autoservice_start(lua_State *L)
|
||
|
{
|
||
|
struct ast_channel *chan;
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
if (lua_toboolean(L, -1)) {
|
||
|
/* autservice already running */
|
||
|
lua_pop(L, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
|
||
|
chan = lua_touserdata(L, -1);
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
ast_autoservice_start(chan);
|
||
|
|
||
|
lua_pushboolean(L, 1);
|
||
|
lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief [lua_CFunction] Tell pbx_lua to stop maintaining 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.
|
||
|
*/
|
||
|
static int lua_autoservice_stop(lua_State *L)
|
||
|
{
|
||
|
struct ast_channel *chan;
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
if (!lua_toboolean(L, -1)) {
|
||
|
/* no autservice running */
|
||
|
lua_pop(L, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
|
||
|
chan = lua_touserdata(L, -1);
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
ast_autoservice_stop(chan);
|
||
|
|
||
|
lua_pushboolean(L, 0);
|
||
|
lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \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 [lua_CFunction] Handle lua errors (for access from lua, don't call
|
||
|
* directly)
|
||
|
*
|
||
|
* \param L the lua_State to use
|
||
|
*/
|
||
|
static int lua_error_function(lua_State *L)
|
||
|
{
|
||
|
int message_index;
|
||
|
|
||
|
/* pass number arguments right through back to asterisk*/
|
||
|
if (lua_isnumber(L, -1)) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* if we are here then we have a string error message, let's attach a
|
||
|
* backtrace to it */
|
||
|
message_index = lua_gettop(L);
|
||
|
|
||
|
/* prepare to prepend a new line to the traceback */
|
||
|
lua_pushliteral(L, "\n");
|
||
|
|
||
|
lua_getglobal(L, "debug");
|
||
|
if (!lua_istable(L, -1)) {
|
||
|
/* Have no 'debug' table for whatever reason */
|
||
|
lua_pop(L, 2);
|
||
|
/* Original err message is on stack top now */
|
||
|
return 1;
|
||
|
}
|
||
|
lua_getfield(L, -1, "traceback");
|
||
|
if (!lua_isfunction(L, -1)) {
|
||
|
/* Same here for traceback function */
|
||
|
lua_pop(L, 3);
|
||
|
/* Original err message is on stack top now */
|
||
|
return 1;
|
||
|
}
|
||
|
lua_remove(L, -2); /* remove the 'debug' table */
|
||
|
|
||
|
lua_pushvalue(L, message_index);
|
||
|
lua_remove(L, message_index);
|
||
|
|
||
|
lua_pushnumber(L, 2);
|
||
|
|
||
|
lua_call(L, 2, 1);
|
||
|
|
||
|
/* prepend the new line we prepared above */
|
||
|
lua_concat(L, 2);
|
||
|
|
||
|
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;
|
||
|
|
||
|
/* copy the context_name to be used as the key for the
|
||
|
* context_order table in the extensions_order table later */
|
||
|
lua_pushvalue(L, context_name);
|
||
|
|
||
|
/* create the context_order table */
|
||
|
lua_newtable(L);
|
||
|
context_order = lua_gettop(L);
|
||
|
|
||
|
/* iterate through this context an populate 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;
|
||
|
#if LUA_VERSION_NUM < 502
|
||
|
lua_pushinteger(L, lua_objlen(L, context_order) + 1);
|
||
|
#else
|
||
|
lua_pushinteger(L, lua_rawlen(L, context_order) + 1);
|
||
|
#endif
|
||
|
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 Register dialplan switches for our pbx_lua contexts.
|
||
|
*
|
||
|
* 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_register_switches(lua_State *L)
|
||
|
{
|
||
|
int extensions;
|
||
|
struct ast_context *con = NULL;
|
||
|
|
||
|
/* create the hash table for our contexts */
|
||
|
/* XXX do we ever need to destroy this? pbx_config does not */
|
||
|
if (!local_table)
|
||
|
local_table = ast_hashtab_create(17, ast_hashtab_compare_contexts, ast_hashtab_resize_java, ast_hashtab_newsize_java, ast_hashtab_hash_contexts, 0);
|
||
|
|
||
|
/* 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 register a context and
|
||
|
* dialplan switch for each lua context
|
||
|
*/
|
||
|
for (lua_pushnil(L); lua_next(L, extensions); lua_pop(L, 1)) {
|
||
|
int context = lua_gettop(L);
|
||
|
int context_name = context - 1;
|
||
|
const char *context_str = lua_tostring(L, context_name);
|
||
|
|
||
|
/* find or create this context */
|
||
|
con = ast_context_find_or_create(&local_contexts, local_table, context_str, registrar);
|
||
|
if (!con) {
|
||
|
/* remove extensions table and context key and value */
|
||
|
lua_pop(L, 3);
|
||
|
lua_pushstring(L, "Failed to find or create context\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* register the switch */
|
||
|
if (ast_context_add_switch2(con, "Lua", "", 0, registrar)) {
|
||
|
/* remove extensions table and context key and value */
|
||
|
lua_pop(L, 3);
|
||
|
lua_pushstring(L, "Unable to create switch for context\n");
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* remove the extensions table */
|
||
|
lua_pop(L, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Register dialplan hints for our pbx_lua contexts.
|
||
|
*
|
||
|
* 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_register_hints(lua_State *L)
|
||
|
{
|
||
|
int hints;
|
||
|
struct ast_context *con = NULL;
|
||
|
|
||
|
/* create the hash table for our contexts */
|
||
|
/* XXX do we ever need to destroy this? pbx_config does not */
|
||
|
if (!local_table)
|
||
|
local_table = ast_hashtab_create(17, ast_hashtab_compare_contexts, ast_hashtab_resize_java, ast_hashtab_newsize_java, ast_hashtab_hash_contexts, 0);
|
||
|
|
||
|
/* load the 'hints' table */
|
||
|
lua_getglobal(L, "hints");
|
||
|
hints = lua_gettop(L);
|
||
|
if (lua_isnil(L, -1)) {
|
||
|
/* hints table not found, move along */
|
||
|
lua_pop(L, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* iterate through the hints table and register each context and
|
||
|
* the hints that go along with it
|
||
|
*/
|
||
|
for (lua_pushnil(L); lua_next(L, hints); lua_pop(L, 1)) {
|
||
|
int context = lua_gettop(L);
|
||
|
int context_name = context - 1;
|
||
|
const char *context_str = lua_tostring(L, context_name);
|
||
|
|
||
|
/* find or create this context */
|
||
|
con = ast_context_find_or_create(&local_contexts, local_table, context_str, registrar);
|
||
|
if (!con) {
|
||
|
/* remove hints table and context key and value */
|
||
|
lua_pop(L, 3);
|
||
|
lua_pushstring(L, "Failed to find or create context\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* register each hint */
|
||
|
for (lua_pushnil(L); lua_next(L, context); lua_pop(L, 1)) {
|
||
|
const char *hint_value = lua_tostring(L, -1);
|
||
|
const char *hint_name;
|
||
|
|
||
|
/* the hint value is not a string, ignore it */
|
||
|
if (!hint_value) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* copy the name then convert it to a string */
|
||
|
lua_pushvalue(L, -2);
|
||
|
if (!(hint_name = lua_tostring(L, -1))) {
|
||
|
/* ignore non-string value */
|
||
|
lua_pop(L, 1);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (ast_add_extension2(con, 0, hint_name, PRIORITY_HINT, NULL, NULL, hint_value, NULL, NULL, registrar, NULL, 0)) {
|
||
|
/* remove hints table, hint name, hint value,
|
||
|
* key copy, context name, and contex table */
|
||
|
lua_pop(L, 6);
|
||
|
lua_pushstring(L, "Error creating hint\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* pop the name copy */
|
||
|
lua_pop(L, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* remove the hints table */
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
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
|
||
|
* \param file_not_openable a pointer to store if config file could be opened
|
||
|
*
|
||
|
* \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, size_t *size, int *file_not_openable)
|
||
|
{
|
||
|
FILE *f;
|
||
|
int error_func;
|
||
|
char *data;
|
||
|
char *path = ast_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);
|
||
|
|
||
|
*file_not_openable = 1;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (fseek(f, 0l, SEEK_END)) {
|
||
|
fclose(f);
|
||
|
lua_pushliteral(L, "error determining the size of the config file");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
*size = ftell(f);
|
||
|
|
||
|
if (fseek(f, 0l, SEEK_SET)) {
|
||
|
*size = 0;
|
||
|
fclose(f);
|
||
|
lua_pushliteral(L, "error reading config file");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (!(data = ast_malloc(*size))) {
|
||
|
*size = 0;
|
||
|
fclose(f);
|
||
|
lua_pushstring(L, "not enough memory");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (fread(data, sizeof(char), *size, f) != *size) {
|
||
|
*size = 0;
|
||
|
fclose(f);
|
||
|
lua_pushliteral(L, "problem reading configuration file");
|
||
|
return NULL;
|
||
|
}
|
||
|
fclose(f);
|
||
|
|
||
|
lua_pushcfunction(L, &lua_error_function);
|
||
|
error_func = lua_gettop(L);
|
||
|
|
||
|
if (luaL_loadbuffer(L, data, *size, "extensions.lua")
|
||
|
|| lua_pcall(L, 0, LUA_MULTRET, error_func)
|
||
|
|| lua_sort_extensions(L)
|
||
|
|| lua_register_switches(L)
|
||
|
|| lua_register_hints(L)) {
|
||
|
ast_free(data);
|
||
|
data = NULL;
|
||
|
*size = 0;
|
||
|
}
|
||
|
|
||
|
lua_remove(L, error_func);
|
||
|
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)
|
||
|
{
|
||
|
size_t size = 0;
|
||
|
char *data = NULL;
|
||
|
int file_not_openable = 0;
|
||
|
|
||
|
luaL_openlibs(L);
|
||
|
|
||
|
if (!(data = lua_read_extensions_file(L, &size, &file_not_openable))) {
|
||
|
if (file_not_openable) {
|
||
|
return -1;
|
||
|
}
|
||
|
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;
|
||
|
|
||
|
/* merge our new contexts */
|
||
|
ast_merge_contexts_and_delete(&local_contexts, local_table, registrar);
|
||
|
/* merge_contexts_and_delete will actually, at the correct moment,
|
||
|
set the global dialplan pointers to your local_contexts and local_table.
|
||
|
It then will free up the old tables itself. Just be sure not to
|
||
|
hang onto the pointers. */
|
||
|
local_table = NULL;
|
||
|
local_contexts = NULL;
|
||
|
|
||
|
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) {
|
||
|
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 {
|
||
|
ast_channel_lock(chan);
|
||
|
datastore = ast_channel_datastore_find(chan, &lua_datastore, NULL);
|
||
|
ast_channel_unlock(chan);
|
||
|
|
||
|
if (!datastore) {
|
||
|
/* nothing found, allocate a new lua state */
|
||
|
datastore = ast_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_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", ast_channel_name(chan), error);
|
||
|
|
||
|
ast_channel_lock(chan);
|
||
|
ast_channel_datastore_remove(chan, datastore);
|
||
|
ast_channel_unlock(chan);
|
||
|
|
||
|
ast_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, error_func;
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
lua_pushcfunction(L, &lua_error_function);
|
||
|
error_func = lua_gettop(L);
|
||
|
|
||
|
/* push the extension function onto the stack */
|
||
|
if (!lua_find_extension(L, context, exten, priority, &exists, 1)) {
|
||
|
lua_pop(L, 1); /* pop the debug function */
|
||
|
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_getfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
if (lua_toboolean(L, -1)) {
|
||
|
ast_autoservice_start(chan);
|
||
|
}
|
||
|
lua_pop(L, 1);
|
||
|
|
||
|
lua_update_registry(L, context, exten, priority);
|
||
|
|
||
|
lua_pushstring(L, context);
|
||
|
lua_pushstring(L, exten);
|
||
|
|
||
|
res = lua_pcall(L, 2, 0, error_func);
|
||
|
if (res) {
|
||
|
if (res == LUA_ERRRUN) {
|
||
|
res = -1;
|
||
|
if (lua_isnumber(L, -1)) {
|
||
|
res = lua_tointeger(L, -1);
|
||
|
|
||
|
if (res == LUA_GOTO_DETECTED) {
|
||
|
res = 0;
|
||
|
}
|
||
|
} else if (lua_isstring(L, -1)) {
|
||
|
const char *error = lua_tostring(L, -1);
|
||
|
ast_log(LOG_ERROR, "Error executing lua extension: %s\n", error);
|
||
|
}
|
||
|
} else if (res == LUA_ERRERR) {
|
||
|
res = -1;
|
||
|
ast_log(LOG_ERROR, "Error in the lua error handler (this is probably a bug in pbx_lua)\n");
|
||
|
} else if (res == LUA_ERRMEM) {
|
||
|
res = -1;
|
||
|
ast_log(LOG_ERROR, "Memory allocation error\n");
|
||
|
}
|
||
|
lua_pop(L, 1);
|
||
|
}
|
||
|
lua_remove(L, error_func);
|
||
|
|
||
|
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
|
||
|
if (lua_toboolean(L, -1)) {
|
||
|
ast_autoservice_stop(chan);
|
||
|
}
|
||
|
lua_pop(L, 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;
|
||
|
size_t 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 */
|
||
|
#if LUA_VERSION_NUM < 502
|
||
|
for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) {
|
||
|
#else
|
||
|
for (i = 1; i < lua_rawlen(L, context_order_table) + 1; i++) {
|
||
|
#endif
|
||
|
int e_index_copy, match = 0;
|
||
|
const char *e;
|
||
|
|
||
|
lua_pushinteger(L, i);
|
||
|
lua_gettable(L, context_order_table);
|
||
|
lua_gettop(L);
|
||
|
|
||
|
/* copy the key at the top of the stack for use later */
|
||
|
lua_pushvalue(L, -1);
|
||
|
e_index_copy = lua_gettop(L);
|
||
|
|
||
|
if (!(e = lua_tostring(L, e_index_copy))) {
|
||
|
lua_pop(L, 2);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* make sure this is not the 'include' extension */
|
||
|
if (!strcasecmp(e, "include")) {
|
||
|
lua_pop(L, 2);
|
||
|
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) {
|
||
|
/* pop the copy and the extension */
|
||
|
lua_pop(L, 2);
|
||
|
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, 4);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* remove the context table, the context order table, the
|
||
|
* extension, and the extension copy (or replace the extension
|
||
|
* with the corresponding function) */
|
||
|
if (push_func) {
|
||
|
lua_pop(L, 1); /* pop the copy */
|
||
|
lua_gettable(L, context_table);
|
||
|
lua_insert(L, -3);
|
||
|
lua_pop(L, 2);
|
||
|
} else {
|
||
|
lua_pop(L, 4);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
int loaded = 0;
|
||
|
|
||
|
lua_State *L = luaL_newstate();
|
||
|
if (!L) {
|
||
|
ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
|
||
|
return AST_MODULE_LOAD_FAILURE;
|
||
|
}
|
||
|
|
||
|
loaded = lua_reload_extensions(L);
|
||
|
if (loaded) {
|
||
|
const char *error = lua_tostring(L, -1);
|
||
|
ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error);
|
||
|
|
||
|
if (loaded < 0) {
|
||
|
res = AST_MODULE_LOAD_DECLINE;
|
||
|
} else {
|
||
|
res = AST_MODULE_LOAD_FAILURE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lua_close(L);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int unload_module(void)
|
||
|
{
|
||
|
ast_context_destroy(NULL, registrar);
|
||
|
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_FAILURE;
|
||
|
}
|
||
|
|
||
|
return AST_MODULE_LOAD_SUCCESS;
|
||
|
}
|
||
|
|
||
|
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Lua PBX Switch",
|
||
|
.support_level = AST_MODULE_SUPPORT_EXTENDED,
|
||
|
.load = load_module,
|
||
|
.unload = unload_module,
|
||
|
.reload = reload,
|
||
|
);
|