diff options
-rw-r--r-- | build_tools/menuselect-deps.in | 1 | ||||
-rw-r--r-- | configs/extensions.lua.sample | 208 | ||||
-rwxr-xr-x | configure | 450 | ||||
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | include/asterisk/autoconfig.h.in | 6 | ||||
-rw-r--r-- | include/asterisk/pbx.h | 14 | ||||
-rw-r--r-- | main/pbx.c | 5 | ||||
-rw-r--r-- | makeopts.in | 3 | ||||
-rw-r--r-- | pbx/pbx_lua.c | 1290 | ||||
-rwxr-xr-x | utils/build-extensions-conf.lua | 81 |
10 files changed, 2053 insertions, 10 deletions
diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index 3504a01b6..fbb4b1583 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -11,6 +11,7 @@ IMAP_TK=@PBX_IMAP_TK@ IXJUSER=@PBX_IXJUSER@ KDE=@PBX_KDE@ LTDL=@PBX_LTDL@ +LUA=@PBX_LUA@ NBS=@PBX_NBS@ NETSNMP=@PBX_NETSNMP@ NEWT=@PBX_NEWT@ diff --git a/configs/extensions.lua.sample b/configs/extensions.lua.sample new file mode 100644 index 000000000..776128120 --- /dev/null +++ b/configs/extensions.lua.sample @@ -0,0 +1,208 @@ + + +CONSOLE = "Console/dsp" -- Console interface for demo +--CONSOLE = "Zap/1" +--CONSOLE = "Phone/phone0" + +IAXINFO = "guest" -- IAXtel username/password +--IAXINFO = "myuser:mypass" + +TRUNK = "Zap/g2" +TRUNKMSD = 1 +-- TRUNK = "IAX2/user:pass@provider" + + +-- +-- Extensions are expected to be defined in a global table named 'extensions'. +-- The 'extensions' table should have a group of tables in it, each +-- representing a context. Extensions are defined in each context. See below +-- for examples. +-- +-- This file can be automatically included in the extensions.conf file using +-- the 'utils/build-extensions-conf.lua' script and a #exec statement in +-- extensions.conf. +-- +-- #exec /usr/bin/utils/build-extensions.conf.lua -c +-- +-- The 'execincludes' option must be set to 'yes' in the [options] section of +-- asterisk.conf for this to work properly. +-- +-- Extension names may be numbers, letters, or combinations thereof. If +-- an extension name is prefixed by a '_' character, it is interpreted as +-- a pattern rather than a literal. In patterns, some characters have +-- special meanings: +-- +-- X - any digit from 0-9 +-- Z - any digit from 1-9 +-- N - any digit from 2-9 +-- [1235-9] - any digit in the brackets (in this example, 1,2,3,5,6,7,8,9) +-- . - wildcard, matches anything remaining (e.g. _9011. matches +-- anything starting with 9011 excluding 9011 itself) +-- ! - wildcard, causes the matching process to complete as soon as +-- it can unambiguously determine that no other matches are possible +-- +-- For example the extension _NXXXXXX would match normal 7 digit +-- dialings, while _1NXXNXXXXXX would represent an area code plus phone +-- number preceded by a one. +-- +-- If your extension has special characters in it such as '.' and '!' you must +-- explicitly make it a string in the tabale definition: +-- +-- ["_special."] = function; +-- ["_special!"] = function; +-- +-- There are no priorities. All extensions to asterisk appear to have a single +-- priority as if they consist of a single priority. +-- +-- Each context is defined as a table in the extensions table. The +-- context names should be strings. +-- +-- One context may be included in another context using the 'includes' +-- extension. This extension should be set to a table containing a list +-- of context names. Do not put references to tables in the includes +-- table. +-- +-- include = {"a", "b", "c"}; +-- +-- Channel variables can be accessed thorugh the global 'channel' table. +-- +-- v = channel.var_name +-- v = channel["var_name"] +-- v.value +-- v:get() +-- +-- channel.var_name = "value" +-- channel["var_name"] = "value" +-- v:set("value") +-- +-- channel.func_name(1,2,3):set("value") +-- value = channel.func_name(1,2,3):get() +-- +-- channel["func_name(1|2|3)"]:set("value") +-- channel["func_name(1|2|3)"] = "value" +-- value = channel["func_name(1|2|3)"]:get() +-- +-- Note the use of the ':' operator to access the get() and set() +-- methods. +-- +-- Also notice the absence of the following constructs from the examples above: +-- channel.func_name(1,2,3) = "value" -- this will NOT work +-- value = channel.func_name(1,2,3) -- this will NOT work as expected +-- +-- +-- Dialplan applications can be accessed through the global 'app' table. +-- +-- app.Dial("Zap/1") +-- app.dial("Zap/1") +-- +-- More examples can be found below. +-- +-- Before starting long running operations, an autoservice should be started +-- using the autoservice_start() function. This autoservice will automatically +-- be stopped before executing applications and dialplan functions and will be +-- restarted afterwards. The autoservice can be stopped using +-- autoservice_stop() and the autoservice_status() function will return true if +-- an autoservice is currently running. +-- + +function outgoing_local(c, e) + app.dial("zap/1/" .. e, "", "") +end + +function demo_instruct() + app.background("demo-instruct") + app.waitexten() +end + +function demo_congrats() + app.background("demo-congrats") + demo_instruct() +end + +-- Answer the chanel and play the demo sound files +function demo_start(context, exten) + app.wait(1) + app.answer() + + channel.TIMEOUT("digit"):set(5) + channel.TIMEOUT("response"):set(10) + -- app.set("TIMEOUT(digit)=5") + -- app.set("TIMEOUT(response)=10") + + demo_congrats(context, exten) +end + +function demo_hangup() + app.playback("demo-thanks") + app.hangup() +end + +extensions = { + demo = { + s = demo_start; + + ["2"] = function() + app.background("demo-moreinfo") + demo_instruct() + end; + ["3"] = function () + channel.LANGUAGE():set("fr") -- set the language to french + demo_congrats() + end; + + ["1000"] = function() + app.goto("default", "s", 1) + end; + + ["1234"] = function() + app.playback("transfer", "skip") + -- do a dial here + end; + + ["1235"] = function() + app.voicemail("1234", "u") + end; + + ["1236"] = function() + app.dial("Console/dsp") + app.voicemail(1234, "b") + end; + + ["#"] = demo_hangup; + t = demo_hangup; + i = function() + app.playback("invalid") + demo_instruct() + end; + + ["500"] = function() + app.playback("demo-abouttotry") + app.dial("IAX2/guest@misery.digium.com/s@default") + app.playback("demo-nogo") + demo_instruct() + end; + + ["600"] = function() + app.playback("demo-echotest") + app.echo() + app.playback("demo-echodone") + demo_instruct() + end; + + ["8500"] = function() + app.voicemailmain() + demo_instruct() + end; + + }; + + default = { + -- by default, do the demo + include = {"demo"}; + }; + + ["local"] = { + ["_NXXXXXX"] = outgoing_local; + }; +} + @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 87325 . +# From configure.ac Revision: 88184 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61. # @@ -765,6 +765,10 @@ LTDL_LIB LTDL_INCLUDE LTDL_DIR PBX_LTDL +LUA_LIB +LUA_INCLUDE +LUA_DIR +PBX_LUA MISDN_LIB MISDN_INCLUDE MISDN_DIR @@ -1537,6 +1541,7 @@ Optional Packages: --with-isdnnet=PATH use ISDN4Linux Library files in PATH --with-kde=PATH use KDE files in PATH --with-ltdl=PATH use libtool files in PATH + --with-lua=PATH use Lua files in PATH --with-misdn=PATH use mISDN User Library files in PATH --with-nbs=PATH use Network Broadcast Sound files in PATH --with-ncurses=PATH use ncurses files in PATH @@ -7991,6 +7996,34 @@ PBX_LTDL=0 +LUA_DESCRIP="Lua" +LUA_OPTION="lua" + +# Check whether --with-lua was given. +if test "${with_lua+set}" = set; then + withval=$with_lua; +case ${withval} in + n|no) + USE_LUA=no + ;; + y|ye|yes) + ac_mandatory_list="${ac_mandatory_list} LUA" + ;; + *) + LUA_DIR="${withval}" + ac_mandatory_list="${ac_mandatory_list} LUA" + ;; +esac + +fi + +PBX_LUA=0 + + + + + + MISDN_DESCRIP="mISDN User Library" MISDN_OPTION="misdn" @@ -47072,6 +47105,399 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $ ac_compiler_gnu=$ac_cv_c_compiler_gnu +LUA_INCLUDE="-I/usr/include/lua5.1" +LUA_LIB="-llua5.1" + +if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then + pbxlibdir="" + if test "x${LUA_DIR}" != "x"; then + if test -d ${LUA_DIR}/lib; then + pbxlibdir="-L${LUA_DIR}/lib" + else + pbxlibdir="-L${LUA_DIR}" + fi + fi + pbxfuncname="luaL_newstate" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_LUA_FOUND=yes + else + as_ac_Lib=`echo "ac_cv_lib_lua5.1_${pbxfuncname}" | $as_tr_sh` +{ echo "$as_me:$LINENO: checking for ${pbxfuncname} in -llua5.1" >&5 +echo $ECHO_N "checking for ${pbxfuncname} in -llua5.1... $ECHO_C" >&6; } +if { as_var=$as_ac_Lib; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-llua5.1 ${pbxlibdir} $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ${pbxfuncname} (); +int +main () +{ +return ${pbxfuncname} (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && + $as_test_x conftest$ac_exeext; then + eval "$as_ac_Lib=yes" +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_Lib=no" +fi + +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +ac_res=`eval echo '${'$as_ac_Lib'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } +if test `eval echo '${'$as_ac_Lib'}'` = yes; then + AST_LUA_FOUND=yes +else + AST_LUA_FOUND=no +fi + + fi + + if test "${AST_LUA_FOUND}" = "yes"; then + LUA_LIB="-llua5.1 " + LUA_HEADER_FOUND="1" + if test "x${LUA_DIR}" != "x"; then + LUA_LIB="${pbxlibdir} ${LUA_LIB}" + LUA_INCLUDE="-I${LUA_DIR}/include" + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} -I${LUA_DIR}/include" + if test "xlua5.1/lua.h" != "x" ; then + as_ac_Header=`echo "ac_cv_header_${LUA_DIR}/include/lua5.1/lua.h" | $as_tr_sh` +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + { echo "$as_me:$LINENO: checking for ${LUA_DIR}/include/lua5.1/lua.h" >&5 +echo $ECHO_N "checking for ${LUA_DIR}/include/lua5.1/lua.h... $ECHO_C" >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +fi +ac_res=`eval echo '${'$as_ac_Header'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } +else + # Is the header compilable? +{ echo "$as_me:$LINENO: checking ${LUA_DIR}/include/lua5.1/lua.h usability" >&5 +echo $ECHO_N "checking ${LUA_DIR}/include/lua5.1/lua.h usability... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <${LUA_DIR}/include/lua5.1/lua.h> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_header_compiler=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_compiler=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +echo "${ECHO_T}$ac_header_compiler" >&6; } + +# Is the header present? +{ echo "$as_me:$LINENO: checking ${LUA_DIR}/include/lua5.1/lua.h presence" >&5 +echo $ECHO_N "checking ${LUA_DIR}/include/lua5.1/lua.h presence... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <${LUA_DIR}/include/lua5.1/lua.h> +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + ac_header_preproc=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi + +rm -f conftest.err conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +echo "${ECHO_T}$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: accepted by the compiler, rejected by the preprocessor!" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: accepted by the compiler, rejected by the preprocessor!" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: proceeding with the compiler's result" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: present but cannot be compiled" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: present but cannot be compiled" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: check for missing prerequisite headers?" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: check for missing prerequisite headers?" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: see the Autoconf documentation" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: see the Autoconf documentation" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: section \"Present But Cannot Be Compiled\"" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: section \"Present But Cannot Be Compiled\"" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: proceeding with the preprocessor's result" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: proceeding with the preprocessor's result" >&2;} + { echo "$as_me:$LINENO: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: in the future, the compiler will take precedence" >&5 +echo "$as_me: WARNING: ${LUA_DIR}/include/lua5.1/lua.h: in the future, the compiler will take precedence" >&2;} + + ;; +esac +{ echo "$as_me:$LINENO: checking for ${LUA_DIR}/include/lua5.1/lua.h" >&5 +echo $ECHO_N "checking for ${LUA_DIR}/include/lua5.1/lua.h... $ECHO_C" >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + eval "$as_ac_Header=\$ac_header_preproc" +fi +ac_res=`eval echo '${'$as_ac_Header'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } + +fi +if test `eval echo '${'$as_ac_Header'}'` = yes; then + LUA_HEADER_FOUND=1 +else + LUA_HEADER_FOUND=0 +fi + + + fi + CPPFLAGS="${saved_cppflags}" + else + if test "xlua5.1/lua.h" != "x" ; then + if test "${ac_cv_header_lua5_1_lua_h+set}" = set; then + { echo "$as_me:$LINENO: checking for lua5.1/lua.h" >&5 +echo $ECHO_N "checking for lua5.1/lua.h... $ECHO_C" >&6; } +if test "${ac_cv_header_lua5_1_lua_h+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +fi +{ echo "$as_me:$LINENO: result: $ac_cv_header_lua5_1_lua_h" >&5 +echo "${ECHO_T}$ac_cv_header_lua5_1_lua_h" >&6; } +else + # Is the header compilable? +{ echo "$as_me:$LINENO: checking lua5.1/lua.h usability" >&5 +echo $ECHO_N "checking lua5.1/lua.h usability... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <lua5.1/lua.h> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_header_compiler=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_compiler=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +echo "${ECHO_T}$ac_header_compiler" >&6; } + +# Is the header present? +{ echo "$as_me:$LINENO: checking lua5.1/lua.h presence" >&5 +echo $ECHO_N "checking lua5.1/lua.h presence... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <lua5.1/lua.h> +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + ac_header_preproc=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi + +rm -f conftest.err conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +echo "${ECHO_T}$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: accepted by the compiler, rejected by the preprocessor!" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: accepted by the compiler, rejected by the preprocessor!" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: proceeding with the compiler's result" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: present but cannot be compiled" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: present but cannot be compiled" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: check for missing prerequisite headers?" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: check for missing prerequisite headers?" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: see the Autoconf documentation" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: see the Autoconf documentation" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: section \"Present But Cannot Be Compiled\"" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: section \"Present But Cannot Be Compiled\"" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: proceeding with the preprocessor's result" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: proceeding with the preprocessor's result" >&2;} + { echo "$as_me:$LINENO: WARNING: lua5.1/lua.h: in the future, the compiler will take precedence" >&5 +echo "$as_me: WARNING: lua5.1/lua.h: in the future, the compiler will take precedence" >&2;} + + ;; +esac +{ echo "$as_me:$LINENO: checking for lua5.1/lua.h" >&5 +echo $ECHO_N "checking for lua5.1/lua.h... $ECHO_C" >&6; } +if test "${ac_cv_header_lua5_1_lua_h+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_cv_header_lua5_1_lua_h=$ac_header_preproc +fi +{ echo "$as_me:$LINENO: result: $ac_cv_header_lua5_1_lua_h" >&5 +echo "${ECHO_T}$ac_cv_header_lua5_1_lua_h" >&6; } + +fi +if test $ac_cv_header_lua5_1_lua_h = yes; then + LUA_HEADER_FOUND=1 +else + LUA_HEADER_FOUND=0 +fi + + + fi + fi + if test "x${LUA_HEADER_FOUND}" = "x0" ; then + LUA_LIB="" + LUA_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + LUA_LIB="" + fi + PBX_LUA=1 + # XXX don't know how to evaluate the description (third argument) in AC_DEFINE_UNQUOTED + +cat >>confdefs.h <<_ACEOF +#define HAVE_LUA 1 +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define HAVE_LUA_VERSION +_ACEOF + + fi + fi +fi + + if test "x${PBX_RADIUS}" != "x1" -a "${USE_RADIUS}" != "no"; then pbxlibdir="" @@ -56493,6 +56919,10 @@ LTDL_LIB!$LTDL_LIB$ac_delim LTDL_INCLUDE!$LTDL_INCLUDE$ac_delim LTDL_DIR!$LTDL_DIR$ac_delim PBX_LTDL!$PBX_LTDL$ac_delim +LUA_LIB!$LUA_LIB$ac_delim +LUA_INCLUDE!$LUA_INCLUDE$ac_delim +LUA_DIR!$LUA_DIR$ac_delim +PBX_LUA!$PBX_LUA$ac_delim MISDN_LIB!$MISDN_LIB$ac_delim MISDN_INCLUDE!$MISDN_INCLUDE$ac_delim MISDN_DIR!$MISDN_DIR$ac_delim @@ -56537,10 +56967,6 @@ PGSQL_LIB!$PGSQL_LIB$ac_delim PGSQL_INCLUDE!$PGSQL_INCLUDE$ac_delim PGSQL_DIR!$PGSQL_DIR$ac_delim PBX_PGSQL!$PBX_PGSQL$ac_delim -PRI_LIB!$PRI_LIB$ac_delim -PRI_INCLUDE!$PRI_INCLUDE$ac_delim -PRI_DIR!$PRI_DIR$ac_delim -PBX_PRI!$PBX_PRI$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then @@ -56582,6 +57008,10 @@ _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF +PRI_LIB!$PRI_LIB$ac_delim +PRI_INCLUDE!$PRI_INCLUDE$ac_delim +PRI_DIR!$PRI_DIR$ac_delim +PBX_PRI!$PBX_PRI$ac_delim SS7_LIB!$SS7_LIB$ac_delim SS7_INCLUDE!$SS7_INCLUDE$ac_delim SS7_DIR!$SS7_DIR$ac_delim @@ -56675,10 +57105,6 @@ AST_DECLARATION_AFTER_STATEMENT!$AST_DECLARATION_AFTER_STATEMENT$ac_delim GSM_INTERNAL!$GSM_INTERNAL$ac_delim KDEINIT!$KDEINIT$ac_delim KDEDIR!$KDEDIR$ac_delim -NETSNMP_CONFIG!$NETSNMP_CONFIG$ac_delim -PG_CONFIG!$PG_CONFIG$ac_delim -PTLIB_CONFIG!$PTLIB_CONFIG$ac_delim -PWLIBDIR!$PWLIBDIR$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then @@ -56720,6 +57146,10 @@ _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF +NETSNMP_CONFIG!$NETSNMP_CONFIG$ac_delim +PG_CONFIG!$PG_CONFIG$ac_delim +PTLIB_CONFIG!$PTLIB_CONFIG$ac_delim +PWLIBDIR!$PWLIBDIR$ac_delim PWLIB_INCDIR!$PWLIB_INCDIR$ac_delim PWLIB_LIBDIR!$PWLIB_LIBDIR$ac_delim PWLIB_PLATFORM!$PWLIB_PLATFORM$ac_delim @@ -56744,7 +57174,7 @@ CURL_CONFIG!$CURL_CONFIG$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 22; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 26; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff --git a/configure.ac b/configure.ac index d07d75b20..f25847f68 100644 --- a/configure.ac +++ b/configure.ac @@ -193,6 +193,7 @@ AST_EXT_LIB_SETUP([IMAP_TK], [UW IMAP Toolkit], [imap]) AST_EXT_LIB_SETUP([ISDNNET], [ISDN4Linux Library], [isdnnet]) AST_EXT_LIB_SETUP([KDE], [KDE], [kde]) AST_EXT_LIB_SETUP([LTDL], [libtool], [ltdl]) +AST_EXT_LIB_SETUP([LUA], [Lua], [lua]) AST_EXT_LIB_SETUP([MISDN], [mISDN User Library], [misdn]) AST_EXT_LIB_SETUP([NBS], [Network Broadcast Sound], [nbs]) AST_EXT_LIB_SETUP([NCURSES], [ncurses], [ncurses]) @@ -980,6 +981,10 @@ fi AC_LANG_POP +LUA_INCLUDE="-I/usr/include/lua5.1" +LUA_LIB="-llua5.1" +AST_EXT_LIB_CHECK([LUA], [lua5.1], [luaL_newstate], [lua5.1/lua.h]) + AST_EXT_LIB_CHECK([RADIUS], [radiusclient-ng], [rc_read_config], [radiusclient-ng.h]) AST_EXT_LIB_CHECK([SPEEX], [speex], [speex_encode], [speex/speex.h], [-lm]) diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index b906bc567..daf8a5ca1 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -409,6 +409,12 @@ /* Define to indicate the ${LTDL_DESCRIP} library version */ #undef HAVE_LTDL_VERSION +/* Define this to indicate the ${LUA_DESCRIP} library */ +#undef HAVE_LUA + +/* Define to indicate the ${LUA_DESCRIP} library version */ +#undef HAVE_LUA_VERSION + /* Define to 1 if you have the <malloc.h> header file. */ #undef HAVE_MALLOC_H diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h index 417b51509..2b6ad75e6 100644 --- a/include/asterisk/pbx.h +++ b/include/asterisk/pbx.h @@ -508,6 +508,20 @@ int ast_extension_match(const char *pattern, const char *extension); int ast_extension_close(const char *pattern, const char *data, int needmore); /*! + * \brief Determine if one extension should match before another + * + * \param a extension to compare with b + * \param b extension to compare with a + * + * Checks whether or extension a should match before extension b + * + * \retval 0 if the two extensions have equal matching priority + * \retval 1 on a > b + * \retval -1 on a < b + */ +int ast_extension_cmp(const char *a, const char *b); + +/*! * \brief Launch a new extension (i.e. new stack) * * \param c not important diff --git a/main/pbx.c b/main/pbx.c index 46fc99392..54e464cbe 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -806,6 +806,11 @@ static int ext_cmp(const char *a, const char *b) return (ret > 0) ? 1 : -1; } +int ast_extension_cmp(const char *a, const char *b) +{ + return ext_cmp(a, b); +} + /*! * \internal * \brief used ast_extension_{match|close} diff --git a/makeopts.in b/makeopts.in index 3c09112d4..76ca94af9 100644 --- a/makeopts.in +++ b/makeopts.in @@ -99,6 +99,9 @@ KDEDIR=@KDEDIR@ KDE_INCLUDE=@KDE_INCLUDE@ KDE_LIB=@KDE_LIB@ +LUA_INCLUDE=@LUA_INCLUDE@ +LUA_LIB=@LUA_LIB@ + NBS_INCLUDE=@NBS_INCLUDE@ NBS_LIB=@NBS_LIB@ diff --git a/pbx/pbx_lua.c b/pbx/pbx_lua.c new file mode 100644 index 000000000..8c566b11a --- /dev/null +++ b/pbx/pbx_lua.c @@ -0,0 +1,1290 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2007, Digium, Inc. + * + * Matthew Nicholson <mnicholson@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \author Matthew Nicholson <mnicholson@digium.com> + * \brief Lua PBX Switch + * + */ + +/*** MODULEINFO + <depend>lua</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/term.h" + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +static char *config = "extensions.lua"; + +#define LUA_EXT_DATA_SIZE 256 +#define LUA_BUF_SIZE 4096 + +static char *lua_read_extensions_file(lua_State *L, long *size); +static int lua_load_extensions(lua_State *L, struct ast_channel *chan); +static int lua_reload_extensions(lua_State *L); +static void lua_free_extensions(void); +static int lua_sort_extensions(lua_State *L); +static int lua_extension_cmp(lua_State *L); +static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func); +static int lua_pbx_findapp(lua_State *L); +static int lua_pbx_exec(lua_State *L); + +static int lua_get_variable_value(lua_State *L); +static int lua_set_variable_value(lua_State *L); +static int lua_get_variable(lua_State *L); +static int lua_set_variable(lua_State *L); +static int lua_func_read(lua_State *L); + +static int lua_autoservice_start(lua_State *L); +static int lua_autoservice_stop(lua_State *L); +static int lua_autoservice_status(lua_State *L); + +static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority); +static void lua_push_variable_table(lua_State *L, const char *name); +static void lua_create_app_table(lua_State *L); +static void lua_create_channel_table(lua_State *L); +static void lua_create_variable_metatable(lua_State *L); +static void lua_create_application_metatable(lua_State *L); +static void lua_create_autoservice_functions(lua_State *L); + +void lua_state_destroy(void *data); +static lua_State *lua_get_state(struct ast_channel *chan); + +static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); + +AST_MUTEX_DEFINE_STATIC(config_file_lock); +char *config_file_data = NULL; +long config_file_size = 0; + +static const struct ast_datastore_info lua_datastore = { + .type = "lua", + .destroy = lua_state_destroy, +}; + + +/*! + * \brief The destructor for lua_datastore + */ +void lua_state_destroy(void *data) +{ + if (data) + lua_close(data); +} + +/*! + * \brief [lua_CFunction] Find an app and return it in a lua table (for access from lua, don't + * call directly) + * + * This function would be called in the following example as it would be found + * in extensions.lua. + * + * \code + * app.dial + * \endcode + */ +static int lua_pbx_findapp(lua_State *L) +{ + const char *app_name = luaL_checkstring(L, 2); + + lua_newtable(L); + + lua_pushstring(L, "name"); + lua_pushstring(L, app_name); + lua_settable(L, -3); + + luaL_getmetatable(L, "application"); + lua_setmetatable(L, -2); + + return 1; +} + +/*! + * \brief [lua_CFunction] This function is part of the 'application' metatable + * and is used to execute applications similar to pbx_exec() (for access from + * lua, don't call directly) + * + * \param L the lua_State to use + * \return nothing + * + * This funciton is executed as the '()' operator for apps accessed through the + * 'app' table. + * + * \code + * app.playback('demo-congrats') + * \endcode + */ +static int lua_pbx_exec(lua_State *L) +{ + int nargs = lua_gettop(L); + char data[LUA_EXT_DATA_SIZE] = ""; + char *data_next = data; + size_t data_left = sizeof(data); + int res; + + lua_getfield(L, 1, "name"); + char *app_name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + struct ast_app *app = pbx_findapp(app_name); + if (!app) { + lua_pushstring(L, "application '"); + lua_pushstring(L, app_name); + lua_pushstring(L, "' not found"); + lua_concat(L, 3); + return lua_error(L); + } + + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + + lua_getfield(L, LUA_REGISTRYINDEX, "context"); + char *context = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "exten"); + char *exten = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "priority"); + int priority = lua_tointeger(L, -1); + lua_pop(L, 1); + + + if (nargs > 1) { + if (!lua_isnil(L, 2)) + ast_build_string(&data_next, &data_left, "%s", luaL_checkstring(L, 2)); + + int i; + for (i = 3; i <= nargs; i++) { + if (lua_isnil(L, i)) + ast_build_string(&data_next, &data_left, ","); + else + ast_build_string(&data_next, &data_left, ",%s", luaL_checkstring(L, i)); + } + } + + char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE]; + ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n", + exten, context, priority, + term_color(tmp, app_name, COLOR_BRCYAN, 0, sizeof(tmp)), + term_color(tmp2, chan->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)), + term_color(tmp3, data, COLOR_BRMAGENTA, 0, sizeof(tmp3))); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + res = pbx_exec(chan, app, data); + + if (autoservice) + ast_autoservice_start(chan); + + /* error executing an application, report it */ + if (res) { + lua_pushinteger(L, res); + return lua_error(L); + } + return 0; +} + +/*! + * \brief [lua_CFunction] Used to get the value of a variable or dialplan + * function (for access from lua, don't call directly) + * + * The value of the variable or function is returned. This function is the + * 'get()' function in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.variable:get() + * \endcode + */ +static int lua_get_variable_value(lua_State *L) +{ + char *value = NULL; + char *workspace = alloca(LUA_BUF_SIZE); + workspace[0] = '\0'; + + if (!lua_istable(L, 1)) { + lua_pushstring(L, "User probably used '.' instead of ':' for retrieving a channel variable value"); + return lua_error(L); + } + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "name"); + char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + /* if this is a dialplan function then use ast_func_read(), otherwise + * use pbx_retrieve_variable() */ + if (!ast_strlen_zero(name) && name[strlen(name) - 1] == ')') { + value = ast_func_read(chan, name, workspace, LUA_BUF_SIZE) ? NULL : workspace; + } else { + pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead); + } + + if (autoservice) + ast_autoservice_start(chan); + + if (value) { + lua_pushstring(L, value); + } else { + lua_pushnil(L); + } + + return 1; +} + +/*! + * \brief [lua_CFunction] Used to set the value of a variable or dialplan + * function (for access from lua, don't call directly) + * + * This function is the 'set()' function in the following example as would be + * seen in extensions.lua. + * + * \code + * channel.variable:set() + * \endcode + */ +static int lua_set_variable_value(lua_State *L) +{ + if (!lua_istable(L, 1)) { + lua_pushstring(L, "User probably used '.' instead of ':' for setting a channel variable"); + return lua_error(L); + } + + lua_getfield(L, 1, "name"); + const char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + const char *value = luaL_checkstring(L, 2); + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + pbx_builtin_setvar_helper(chan, name, value); + + if (autoservice) + ast_autoservice_start(chan); + + return 0; +} + +/*! + * \brief Update the lua registry with the given context, exten, and priority. + * + * \param L the lua_State to use + * \param context the new context + * \param exten the new exten + * \param priority the new priority + */ +static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority) +{ + lua_pushstring(L, context); + lua_setfield(L, LUA_REGISTRYINDEX, "context"); + + lua_pushstring(L, exten); + lua_setfield(L, LUA_REGISTRYINDEX, "exten"); + + lua_pushinteger(L, priority); + lua_setfield(L, LUA_REGISTRYINDEX, "priority"); +} + +/*! + * \brief Push a 'variable' table on the stack for access the channel variable + * with the given name. + * + * \param L the lua_State to use + * \param name the name of the variable + */ +static void lua_push_variable_table(lua_State *L, const char *name) +{ + lua_newtable(L); + luaL_getmetatable(L, "variable"); + lua_setmetatable(L, -2); + + lua_pushstring(L, name); + lua_setfield(L, -2, "name"); + + lua_pushcfunction(L, &lua_get_variable_value); + lua_setfield(L, -2, "get"); + + lua_pushcfunction(L, &lua_set_variable_value); + lua_setfield(L, -2, "set"); +} + +/*! + * \brief Create the global 'app' table for executing applications + * + * \param L the lua_State to use + */ +static void lua_create_app_table(lua_State *L) +{ + lua_newtable(L); + luaL_newmetatable(L, "app"); + + lua_pushstring(L, "__index"); + lua_pushcfunction(L, &lua_pbx_findapp); + lua_settable(L, -3); + + lua_setmetatable(L, -2); + lua_setglobal(L, "app"); +} + +/*! + * \brief Create the global 'channel' table for accesing channel variables + * + * \param L the lua_State to use + */ +static void lua_create_channel_table(lua_State *L) +{ + lua_newtable(L); + luaL_newmetatable(L, "channel_data"); + + lua_pushstring(L, "__index"); + lua_pushcfunction(L, &lua_get_variable); + lua_settable(L, -3); + + lua_pushstring(L, "__newindex"); + lua_pushcfunction(L, &lua_set_variable); + lua_settable(L, -3); + + lua_setmetatable(L, -2); + lua_setglobal(L, "channel"); +} + +/*! + * \brief Create the 'variable' metatable, used to retrieve channel variables + * + * \param L the lua_State to use + */ +static void lua_create_variable_metatable(lua_State *L) +{ + luaL_newmetatable(L, "variable"); + + lua_pushstring(L, "__call"); + lua_pushcfunction(L, &lua_func_read); + lua_settable(L, -3); + + lua_pop(L, 1); +} + +/*! + * \brief Create the 'application' metatable, used to execute asterisk + * applications from lua + * + * \param L the lua_State to use + */ +static void lua_create_application_metatable(lua_State *L) +{ + luaL_newmetatable(L, "application"); + + lua_pushstring(L, "__call"); + lua_pushcfunction(L, &lua_pbx_exec); + lua_settable(L, -3); + + lua_pop(L, 1); +} + +/*! + * \brief Create the autoservice functions + * + * \param L the lua_State to use + */ +static void lua_create_autoservice_functions(lua_State *L) +{ + lua_pushcfunction(L, &lua_autoservice_start); + lua_setglobal(L, "autoservice_start"); + + lua_pushcfunction(L, &lua_autoservice_stop); + lua_setglobal(L, "autoservice_stop"); + + lua_pushcfunction(L, &lua_autoservice_status); + lua_setglobal(L, "autoservice_status"); + + lua_pushboolean(L, 0); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); +} + +/*! + * \brief [lua_CFunction] Return a lua 'variable' object (for access from lua, don't call + * directly) + * + * This function is called to lookup a variable construct a 'variable' object. + * It would be called in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.variable + * \endcode + */ +static int lua_get_variable(lua_State *L) +{ + char *name = ast_strdupa(luaL_checkstring(L, 2)); + char *value = NULL; + char *workspace = alloca(LUA_BUF_SIZE); + workspace[0] = '\0'; + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_push_variable_table(L, name); + + /* if this is not a request for a dialplan funciton attempt to retrieve + * the value of the variable */ + if (!ast_strlen_zero(name) && name[strlen(name) - 1] != ')') { + pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead); + } + + if (value) { + lua_pushstring(L, value); + lua_setfield(L, -2, "value"); + } + + return 1; +} + +/*! + * \brief [lua_CFunction] Set the value of a channel variable or dialplan + * function (for access from lua, don't call directly) + * + * This function is called to set a variable or dialplan function. It would be + * called in the following example as would be seen in extensions.lua. + * + * \code + * channel.variable = "value" + * \endcode + */ +static int lua_set_variable(lua_State *L) +{ + const char *name = luaL_checkstring(L, 2); + const char *value = luaL_checkstring(L, 3); + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + pbx_builtin_setvar_helper(chan, name, value); + + if (autoservice) + ast_autoservice_start(chan); + + return 0; +} + +/*! + * \brief [lua_CFunction] Create a 'variable' object for accessing a dialplan + * function (for access from lua, don't call directly) + * + * This function is called to create a 'variable' object to access a dialplan + * function. It would be called in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.func("arg1", "arg2", "arg3") + * \endcode + * + * To actually do anything with the resulting value you must use the 'get()' + * and 'set()' methods (the reason is the resulting value is not a value, but + * an object in the form of a lua table). + */ +static int lua_func_read(lua_State *L) +{ + int nargs = lua_gettop(L); + char fullname[LUA_EXT_DATA_SIZE] = ""; + char *fullname_next = fullname; + size_t fullname_left = sizeof(fullname); + + lua_getfield(L, 1, "name"); + char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + ast_build_string(&fullname_next, &fullname_left, "%s(", name); + + if (nargs > 1) { + if (!lua_isnil(L, 2)) + ast_build_string(&fullname_next, &fullname_left, "%s", luaL_checkstring(L, 2)); + + int i; + for (i = 3; i <= nargs; i++) { + if (lua_isnil(L, i)) + ast_build_string(&fullname_next, &fullname_left, ","); + else + ast_build_string(&fullname_next, &fullname_left, ",%s", luaL_checkstring(L, i)); + } + } + + ast_build_string(&fullname_next, &fullname_left, ")"); + + lua_push_variable_table(L, fullname); + + return 1; +} + +/*! + * \brief [lua_CFunction] Tell pbx_lua to maintain an autoservice on this + * channel (for access from lua, don't call directly) + * + * \param L the lua_State to use + * + * This function will set a flag that will cause pbx_lua to maintain an + * autoservice on this channel. The autoservice will automatically be stopped + * and restarted before calling applications and functions. + * + * \return This function returns the result of the ast_autoservice_start() + * function as a boolean to its lua caller. + */ +static int lua_autoservice_start(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + int res = ast_autoservice_start(chan); + + lua_pushboolean(L, !res); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); + + lua_pushboolean(L, !res); + return 1; +} + +/*! + * \brief [lua_CFunction] Tell pbx_lua to stop maintaning an autoservice on + * this channel (for access from lua, don't call directly) + * + * \param L the lua_State to use + * + * This function will stop any autoservice running and turn off the autoservice + * flag. If this function returns false, it's probably because no autoservice + * was running to begin with. + * + * \return This function returns the result of the ast_autoservice_stop() + * function as a boolean to its lua caller. + */ +static int lua_autoservice_stop(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + int res = ast_autoservice_stop(chan); + + lua_pushboolean(L, 0); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); + + lua_pushboolean(L, !res); + return 1; +} + +/*! + * \brief [lua_CFunction] Get the status of the autoservice flag (for access + * from lua, don't call directly) + * + * \param L the lua_State to use + * + * \return This function returns the status of the autoservice flag as a + * boolean to its lua caller. + */ +static int lua_autoservice_status(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + return 1; +} + +/*! + * \brief Store the sort order of each context + + * In the event of an error, an error string will be pushed onto the lua stack. + * + * \retval 0 success + * \retval 1 failure + */ +static int lua_sort_extensions(lua_State *L) +{ + /* create the extensions_order table */ + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, "extensions_order"); + lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order"); + int extensions_order = lua_gettop(L); + + /* sort each context in the extensions table */ + /* load the 'extensions' table */ + lua_getglobal(L, "extensions"); + int extensions = lua_gettop(L); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_pushstring(L, "Unable to find 'extensions' table in extensions.lua\n"); + return 1; + } + + /* iterate through the extensions table and create a + * matching table (holding the sort order) in the + * extensions_order table for each context that is found + */ + for (lua_pushnil(L); lua_next(L, extensions); lua_pop(L, 1)) { + int context = lua_gettop(L); + int context_name = context - 1; + + lua_pushvalue(L, context_name); + lua_newtable(L); + int context_order = lua_gettop(L); + + /* iterate through this context an popluate the corrisponding + * table in the extensions_order table */ + for (lua_pushnil(L); lua_next(L, context); lua_pop(L, 1)) { + int exten = lua_gettop(L) - 1; + + lua_pushinteger(L, lua_objlen(L, context_order) + 1); + lua_pushvalue(L, exten); + lua_settable(L, context_order); + } + lua_settable(L, extensions_order); /* put the context_order table in the extensions_order table */ + + /* now sort the new table */ + + /* push the table.sort function */ + lua_getglobal(L, "table"); + lua_getfield(L, -1, "sort"); + lua_remove(L, -2); /* remove the 'table' table */ + + /* push the context_order table */ + lua_pushvalue(L, context_name); + lua_gettable(L, extensions_order); + + /* push the comp function */ + lua_pushcfunction(L, &lua_extension_cmp); + + if (lua_pcall(L, 2, 0, 0)) { + lua_insert(L, -5); + lua_pop(L, 4); + return 1; + } + } + + /* remove the extensions table and the extensions_order table */ + lua_pop(L, 2); + return 0; +} + +/*! + * \brief [lua_CFunction] Compare two extensions (for access from lua, don't + * call directly) + * + * This function returns true if the first extension passed should match after + * the second. It behaves like the '<' operator. + */ +static int lua_extension_cmp(lua_State *L) +{ + const char *a = luaL_checkstring(L, -2); + const char *b = luaL_checkstring(L, -1); + + if (ast_extension_cmp(a, b) == -1) + lua_pushboolean(L, 1); + else + lua_pushboolean(L, 0); + + return 1; +} + +/*! + * \brief Load the extensions.lua file in to a buffer and execute the file + * + * \param L the lua_State to use + * \param size a pointer to store the size of the buffer + * + * \note The caller is expected to free the buffer at some point. + * + * \return a pointer to the buffer + */ +static char *lua_read_extensions_file(lua_State *L, long *size) +{ + char *path = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2); + sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config); + + FILE *f = fopen(path, "r"); + if (!f) { + lua_pushstring(L, "cannot open '"); + lua_pushstring(L, path); + lua_pushstring(L, "' for reading: "); + lua_pushstring(L, strerror(errno)); + lua_concat(L, 4); + + return NULL; + } + + fseek(f, 0l, SEEK_END); + *size = ftell(f); + + fseek(f, 0l, SEEK_SET); + + char *data = ast_malloc(*size); + if (!data) { + *size = 0; + fclose(f); + lua_pushstring(L, "not enough memory"); + return NULL; + } + + fread(data, sizeof(char), *size, f); + fclose(f); + + if (luaL_loadbuffer(L, data, *size, "extensions.lua") + || lua_pcall(L, 0, LUA_MULTRET, 0) + || lua_sort_extensions(L)) { + ast_free(data); + data = NULL; + *size = 0; + } + return data; +} + +/*! + * \brief Load the extensions.lua file from the internal buffer + * + * \param L the lua_State to use + * + * This function also sets up some constructs used by the extensions.lua file. + * + * In the event of an error, an error string will be pushed onto the lua stack. + * + * \retval 0 success + * \retval 1 failure + */ +static int lua_load_extensions(lua_State *L, struct ast_channel *chan) +{ + + /* store a pointer to this channel */ + lua_pushlightuserdata(L, chan); + lua_setfield(L, LUA_REGISTRYINDEX, "channel"); + + luaL_openlibs(L); + + /* load and sort extensions */ + ast_mutex_lock(&config_file_lock); + if (luaL_loadbuffer(L, config_file_data, config_file_size, "extensions.lua") + || lua_pcall(L, 0, LUA_MULTRET, 0) + || lua_sort_extensions(L)) { + ast_mutex_unlock(&config_file_lock); + return 1; + } + ast_mutex_unlock(&config_file_lock); + + /* now we setup special tables and functions */ + + lua_create_app_table(L); + lua_create_channel_table(L); + + lua_create_variable_metatable(L); + lua_create_application_metatable(L); + + lua_create_autoservice_functions(L); + + return 0; +} + +/*! + * \brief Reload the extensions file and update the internal buffers if it + * loads correctly. + * + * \warning This function should not be called on a lua_State returned from + * lua_get_state(). + * + * \param L the lua_State to use (must be freshly allocated with + * luaL_newstate(), don't use lua_get_state()) + */ +static int lua_reload_extensions(lua_State *L) +{ + long size = 0; + char *data = NULL; + + luaL_openlibs(L); + + if (!(data = lua_read_extensions_file(L, &size))) { + return 1; + } + + ast_mutex_lock(&config_file_lock); + + if (config_file_data) + ast_free(config_file_data); + + config_file_data = data; + config_file_size = size; + + ast_mutex_unlock(&config_file_lock); + return 0; +} + +/*! + * \brief Free the internal extensions buffer. + */ +static void lua_free_extensions() +{ + ast_mutex_lock(&config_file_lock); + config_file_size = 0; + ast_free(config_file_data); + ast_mutex_unlock(&config_file_lock); +} + +/*! + * \brief Get the lua_State for this channel + * + * If no channel is passed then a new state is allocated. States with no + * channel assocatied with them should only be used for matching extensions. + * If the channel does not yet have a lua state associated with it, one will be + * created. + * + * \note If no channel was passed then the caller is expected to free the state + * using lua_close(). + * + * \return a lua_State + */ +static lua_State *lua_get_state(struct ast_channel *chan) +{ + struct ast_datastore *datastore = NULL; + if (!chan) { + lua_State *L = luaL_newstate(); + if (!L) { + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return NULL; + } + + if (lua_load_extensions(L, NULL)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error); + lua_close(L); + return NULL; + } + return L; + } else { + datastore = ast_channel_datastore_find(chan, &lua_datastore, NULL); + + if (!datastore) { + /* nothing found, allocate a new lua state */ + datastore = ast_channel_datastore_alloc(&lua_datastore, NULL); + if (!datastore) { + ast_log(LOG_ERROR, "Error allocation channel datastore for lua_State\n"); + return NULL; + } + + datastore->data = luaL_newstate(); + if (!datastore->data) { + ast_channel_datastore_free(datastore); + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return NULL; + } + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + + lua_State *L = datastore->data; + + if (lua_load_extensions(L, chan)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua for %s: %s\n", chan->name, error); + + ast_channel_lock(chan); + ast_channel_datastore_remove(chan, datastore); + ast_channel_unlock(chan); + + ast_channel_datastore_free(datastore); + return NULL; + } + } + + return datastore->data; + } +} + +static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &exists, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &canmatch, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &matchmore, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + + +static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return -1; + } + + int res; + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return -1; + } + + /* push the extension function onto the stack */ + if (!lua_find_extension(L, context, exten, priority, &exists, 1)) { + ast_log(LOG_ERROR, "Could not find extension %s in context %s\n", exten, context); + if (!chan) lua_close(L); + ast_module_user_remove(u); + return -1; + } + + lua_update_registry(L, context, exten, priority); + + lua_pushstring(L, context); + lua_pushstring(L, exten); + + res = lua_pcall(L, 2, 0, 0); + if (res) { + if (res == LUA_ERRRUN) { + if (lua_isnumber(L, -1)) { + res = lua_tointeger(L, -1); + } else if (lua_isstring(L, -1)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error executing lua extension: %s\n", error); + res = -1; + } + } else { + res = -1; + } + } + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +/*! + * \brief Locate an extensions and optionally push the matching function on the + * stack + * + * \param L the lua_State to use + * \param context the context to look in + * \param exten the extension to look up + * \param priority the priority to check, '1' is the only valid priority + * \param func the calling func, used to adjust matching behavior between, + * match, canmatch, and matchmore + * \param push_func whether or not to push the lua function for the given + * extension onto the stack + */ +static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func) +{ + ast_debug(2, "Looking up %s@%s:%i\n", exten, context, priority); + if (priority != 1) + return 0; + + /* load the 'extensions' table */ + lua_getglobal(L, "extensions"); + if (lua_isnil(L, -1)) { + ast_log(LOG_ERROR, "Unable to find 'extensions' table in extensions.lua\n"); + lua_pop(L, 1); + return 0; + } + + /* load the given context */ + lua_getfield(L, -1, context); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + return 0; + } + + /* remove the extensions table */ + lua_remove(L, -2); + + int context_table = lua_gettop(L); + + /* load the extensions order table for this context */ + lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order"); + lua_getfield(L, -1, context); + + lua_remove(L, -2); /* remove the extensions order table */ + + int context_order_table = lua_gettop(L); + + /* step through the extensions looking for a match */ + int i; + for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) { + lua_pushinteger(L, i); + lua_gettable(L, context_order_table); + int e_index = lua_gettop(L); + int isnumber = lua_isnumber(L, e_index); + + const char *e = lua_tostring(L, e_index); + if (!e) { + lua_pop(L, 1); + continue; + } + + /* make sure this is not the 'include' extension */ + if(!strcasecmp(e, "include")) { + lua_pop(L, 1); + continue; + } + + int match = 0; + if (func == &matchmore) + match = ast_extension_close(e, exten, E_MATCHMORE); + else if (func == &canmatch) + match = ast_extension_close(e, exten, E_CANMATCH); + else + match = ast_extension_match(e, exten); + + /* the extension matching functions return 0 on fail, 1 on + * match, 2 on earlymatch */ + + if (!match) { + lua_pop(L, 1); + continue; /* keep trying */ + } + + if (func == &matchmore && match == 2) { + /* We match an extension ending in '!'. The decision in + * this case is final and counts as no match. */ + lua_pop(L, 3); + return 0; + } + + /* remove the context table, the context order table, and the + * extension (or replace the extension with the corisponding + * function) */ + if (push_func) { + /* here we must convert the exten back to an integer + * because lua_tostring will change the value on the + * stack to a string */ + if (isnumber) { + int e_int = lua_tointeger(L, e_index); + lua_pop(L, 1); /* the exten should be the top of the stack */ + lua_pushinteger(L, e_int); + } + lua_gettable(L, context_table); + lua_insert(L, -3); + lua_pop(L, 2); + } else { + lua_pop(L, 3); + } + + return 1; + } + + /* load the includes for this context */ + lua_getfield(L, context_table, "include"); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return 0; + } + + /* remove the context and the order table*/ + lua_remove(L, context_order_table); + lua_remove(L, context_table); + + /* Now try any includes we have in this context */ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *c = lua_tostring(L, -1); + if (!c) + continue; + + if (lua_find_extension(L, c, exten, priority, func, push_func)) { + /* remove the value, the key, and the includes table + * from the stack. Leave the function behind if + * necessary */ + + if (push_func) + lua_insert(L, -4); + + lua_pop(L, 3); + return 1; + } + } + + /* pop the includes table */ + lua_pop(L, 1); + return 0; +} + +static struct ast_switch lua_switch = { + .name = "Lua", + .description = "Lua PBX Switch", + .exists = exists, + .canmatch = canmatch, + .exec = exec, + .matchmore = matchmore, +}; + + +static int load_or_reload_lua_stuff(void) +{ + lua_State *L = luaL_newstate(); + if (!L) { + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return AST_MODULE_LOAD_FAILURE; + } + + int res = AST_MODULE_LOAD_SUCCESS; + + if (lua_reload_extensions(L)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error); + res = AST_MODULE_LOAD_FAILURE; + } + + lua_close(L); + return res; +} + +static int unload_module(void) +{ + ast_unregister_switch(&lua_switch); + lua_free_extensions(); + return 0; +} + +static int reload(void) +{ + return load_or_reload_lua_stuff(); +} + +static int load_module(void) +{ + if (load_or_reload_lua_stuff()) + return AST_MODULE_LOAD_FAILURE; + + if (ast_register_switch(&lua_switch)) { + ast_log(LOG_ERROR, "Unable to register LUA PBX switch\n"); + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Lua PBX Switch", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); + diff --git a/utils/build-extensions-conf.lua b/utils/build-extensions-conf.lua new file mode 100755 index 000000000..a3f159def --- /dev/null +++ b/utils/build-extensions-conf.lua @@ -0,0 +1,81 @@ +#!/usr/bin/env lua +--[[ + +This utility can be used to generate an extensions.conf file to match an +existing extensions.lua file. As an argument it takes the patch of the +extensions.lua file to read from, otherwise it uses +/etc/asterisk/extensions.lua. + +This script can also be used to automatically include extensions.lua in +extensions.conf via a #exec as well. + +#exec /usr/bin/build-extensions-conf.lua -c + +--]] + +usage = [[ + +Usage: + ]] .. arg[0] .. [[ [options] [extensions.lua path] + +This utility can generate an extensions.conf file with all of the contexts in +your extensions.lua file defined as including the Lua switch. This is useful +if you want to use your extensions.lua file exclusively. By using this utility +you dont't have to create each extension in extensions.conf manually. + +The resulting extensions.conf file is printed to standard output. + + --contexts-only, -c Don't print the [global] or [general] sections. This + is useful for including the generated file into an + existing extensions.conf via #include or #exec. + + --help, -h Print this message. + +]] + +extensions_file = "/etc/asterisk/extensions.lua" + +options = {} + +for k, v in ipairs(arg) do + if v:sub(1, 1) == "-" then + if v == "-h" or v == "--help" then + print("match") + options["help"] = true + elseif v == "-c" or v == "--contexts-only" then + options["contexts-only"] = true + end + else + options["extensions-file"] = v + end +end + +if options["help"] then + io.stderr:write(usage) + os.exit(0) +end + +if options["extensions-file"] then + extensions_file = options["extensions-file"] +end + +result, error_message = pcall(dofile, extensions_file) + +if not result then + io.stderr:write(error_message .. "\n") + os.exit(1) +end + +if not extensions then + io.stderr:write("Error: extensions table not found in '" .. extensions_file .. "'\n") + os.exit(1) +end + +if not options["contexts-only"] then + io.stdout:write("[general]\n\n[globals]\n\n") +end + +for context, extens in pairs(extensions) do + io.stdout:write("[" .. tostring(context) .. "]\nswitch => Lua\n\n") +end + |