mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-03 20:38:59 +00:00 
			
		
		
		
	git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.6.1@153710 65c4cc65-6c06-0410-ace0-fbb531ad65f3
		
			
				
	
	
		
			1476 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1476 lines
		
	
	
		
			37 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 "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 <lua5.1/lua.h>
 | 
						|
#include <lua5.1/lauxlib.h>
 | 
						|
#include <lua5.1/lualib.h>
 | 
						|
 | 
						|
static char *config = "extensions.lua";
 | 
						|
static char *registrar = "pbx_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_register_switches(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, 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 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,
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/*!
 | 
						|
 * \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 [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);
 | 
						|
 | 
						|
	lua_getglobal(L, "debug");
 | 
						|
	lua_getfield(L, -1, "traceback");
 | 
						|
	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);
 | 
						|
 | 
						|
	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 Register dialplan switches for our pbx_lua contexs.
 | 
						|
 *
 | 
						|
 * 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 [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;
 | 
						|
	}
 | 
						|
 | 
						|
	if (fread(data, sizeof(char), *size, f) != *size) {
 | 
						|
		ast_log(LOG_WARNING, "fread() failed: %s\n", strerror(errno));
 | 
						|
	}
 | 
						|
	fclose(f);
 | 
						|
 | 
						|
	if (luaL_loadbuffer(L, data, *size, "extensions.lua")
 | 
						|
			|| lua_pcall(L, 0, LUA_MULTRET, 0)
 | 
						|
			|| lua_sort_extensions(L)
 | 
						|
			|| lua_register_switches(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;
 | 
						|
	
 | 
						|
	/* 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) {
 | 
						|
		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 {
 | 
						|
		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", chan->name, 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_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);
 | 
						|
			} 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);
 | 
						|
	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, e_index_copy, match = 0;
 | 
						|
		const char *e;
 | 
						|
 | 
						|
		lua_pushinteger(L, i);
 | 
						|
		lua_gettable(L, context_order_table);
 | 
						|
		e_index = 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;
 | 
						|
 | 
						|
	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_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_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,
 | 
						|
	       );
 | 
						|
 |