/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2007, Digium, Inc. * * Matthew Nicholson * * 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 * \brief Lua PBX Switch * */ /*** MODULEINFO lua extended ***/ #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 #include #include static char *config = "extensions.lua"; static 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, long *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 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, .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 * \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); 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. * * \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. * * \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 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, 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 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. */ 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"); 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); /* 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 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; #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 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 Register dialplan hints 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_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, long *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) { long 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, 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, );