summaryrefslogtreecommitdiffstats
path: root/src/lib-lua
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-lua
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-lua')
-rw-r--r--src/lib-lua/Makefile.am68
-rw-r--r--src/lib-lua/Makefile.in971
-rw-r--r--src/lib-lua/dlua-compat.c157
-rw-r--r--src/lib-lua/dlua-compat.h69
-rw-r--r--src/lib-lua/dlua-dovecot-http.c522
-rw-r--r--src/lib-lua/dlua-dovecot.c681
-rw-r--r--src/lib-lua/dlua-error.c13
-rw-r--r--src/lib-lua/dlua-pushstring.c26
-rw-r--r--src/lib-lua/dlua-resume.c208
-rw-r--r--src/lib-lua/dlua-script-private.h264
-rw-r--r--src/lib-lua/dlua-script.c453
-rw-r--r--src/lib-lua/dlua-script.h28
-rw-r--r--src/lib-lua/dlua-table.c301
-rw-r--r--src/lib-lua/dlua-thread.c276
-rw-r--r--src/lib-lua/dlua-wrapper.h186
-rw-r--r--src/lib-lua/test-dict-lua.c99
-rw-r--r--src/lib-lua/test-lua.c473
17 files changed, 4795 insertions, 0 deletions
diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am
new file mode 100644
index 0000000..3416489
--- /dev/null
+++ b/src/lib-lua/Makefile.am
@@ -0,0 +1,68 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-master \
+ $(LUA_CFLAGS)
+
+pkglib_LTLIBRARIES = libdovecot-lua.la
+libdovecot_lua_la_SOURCES = \
+ dlua-script.c \
+ dlua-pushstring.c \
+ dlua-error.c \
+ dlua-dovecot.c \
+ dlua-dovecot-http.c \
+ dlua-compat.c \
+ dlua-resume.c \
+ dlua-table.c \
+ dlua-thread.c
+
+test_programs = test-lua
+
+LIBDICT_LUA=
+if DLUA_WITH_YIELDS
+LIBDICT_LUA += ../lib-dict/libdict_lua.la
+test_programs += test-dict-lua
+endif
+
+# Note: the only things this lib should depend on are libdovecot and lua.
+libdovecot_lua_la_DEPENDENCIES = \
+ ../lib-dovecot/libdovecot.la \
+ $(LIBDICT_LUA)
+libdovecot_lua_la_LIBADD = \
+ ../lib-dovecot/libdovecot.la \
+ $(LIBDICT_LUA) \
+ $(LUA_LIBS)
+libdovecot_lua_la_LDFLAGS = -export-dynamic
+
+headers = \
+ dlua-compat.h \
+ dlua-script.h \
+ dlua-script-private.h \
+ dlua-wrapper.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs =\
+ libdovecot-lua.la \
+ ../lib-dovecot/libdovecot.la
+
+test_lua_SOURCES = test-lua.c
+test_lua_CFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+test_lua_LDFLAGS = $(BINARY_LDFLAGS)
+test_lua_LDADD = $(test_libs) $(LUA_LIBS)
+test_lua_DEPENDENCIES = $(test_libs)
+
+test_dict_lua_SOURCES = test-dict-lua.c
+test_dict_lua_LDADD = $(test_libs) $(LUA_LIBS)
+test_dict_lua_DEPENDENCIES = $(test_libs)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-lua/Makefile.in b/src/lib-lua/Makefile.in
new file mode 100644
index 0000000..6e7f153
--- /dev/null
+++ b/src/lib-lua/Makefile.in
@@ -0,0 +1,971 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@DLUA_WITH_YIELDS_TRUE@am__append_1 = ../lib-dict/libdict_lua.la
+@DLUA_WITH_YIELDS_TRUE@am__append_2 = test-dict-lua
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib-lua
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@DLUA_WITH_YIELDS_TRUE@am__EXEEXT_1 = test-dict-lua$(EXEEXT)
+am__EXEEXT_2 = test-lua$(EXEEXT) $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkglibdir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(pkglib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+am_libdovecot_lua_la_OBJECTS = dlua-script.lo dlua-pushstring.lo \
+ dlua-error.lo dlua-dovecot.lo dlua-dovecot-http.lo \
+ dlua-compat.lo dlua-resume.lo dlua-table.lo dlua-thread.lo
+libdovecot_lua_la_OBJECTS = $(am_libdovecot_lua_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libdovecot_lua_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libdovecot_lua_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+am_test_dict_lua_OBJECTS = test-dict-lua.$(OBJEXT)
+test_dict_lua_OBJECTS = $(am_test_dict_lua_OBJECTS)
+am_test_lua_OBJECTS = test_lua-test-lua.$(OBJEXT)
+test_lua_OBJECTS = $(am_test_lua_OBJECTS)
+test_lua_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_lua_CFLAGS) \
+ $(CFLAGS) $(test_lua_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/dlua-compat.Plo \
+ ./$(DEPDIR)/dlua-dovecot-http.Plo ./$(DEPDIR)/dlua-dovecot.Plo \
+ ./$(DEPDIR)/dlua-error.Plo ./$(DEPDIR)/dlua-pushstring.Plo \
+ ./$(DEPDIR)/dlua-resume.Plo ./$(DEPDIR)/dlua-script.Plo \
+ ./$(DEPDIR)/dlua-table.Plo ./$(DEPDIR)/dlua-thread.Plo \
+ ./$(DEPDIR)/test-dict-lua.Po ./$(DEPDIR)/test_lua-test-lua.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdovecot_lua_la_SOURCES) $(test_dict_lua_SOURCES) \
+ $(test_lua_SOURCES)
+DIST_SOURCES = $(libdovecot_lua_la_SOURCES) $(test_dict_lua_SOURCES) \
+ $(test_lua_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-master \
+ $(LUA_CFLAGS)
+
+pkglib_LTLIBRARIES = libdovecot-lua.la
+libdovecot_lua_la_SOURCES = \
+ dlua-script.c \
+ dlua-pushstring.c \
+ dlua-error.c \
+ dlua-dovecot.c \
+ dlua-dovecot-http.c \
+ dlua-compat.c \
+ dlua-resume.c \
+ dlua-table.c \
+ dlua-thread.c
+
+test_programs = test-lua $(am__append_2)
+LIBDICT_LUA = $(am__append_1)
+
+# Note: the only things this lib should depend on are libdovecot and lua.
+libdovecot_lua_la_DEPENDENCIES = \
+ ../lib-dovecot/libdovecot.la \
+ $(LIBDICT_LUA)
+
+libdovecot_lua_la_LIBADD = \
+ ../lib-dovecot/libdovecot.la \
+ $(LIBDICT_LUA) \
+ $(LUA_LIBS)
+
+libdovecot_lua_la_LDFLAGS = -export-dynamic
+headers = \
+ dlua-compat.h \
+ dlua-script.h \
+ dlua-script-private.h \
+ dlua-wrapper.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_libs = \
+ libdovecot-lua.la \
+ ../lib-dovecot/libdovecot.la
+
+test_lua_SOURCES = test-lua.c
+test_lua_CFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+test_lua_LDFLAGS = $(BINARY_LDFLAGS)
+test_lua_LDADD = $(test_libs) $(LUA_LIBS)
+test_lua_DEPENDENCIES = $(test_libs)
+test_dict_lua_SOURCES = test-dict-lua.c
+test_dict_lua_LDADD = $(test_libs) $(LUA_LIBS)
+test_dict_lua_DEPENDENCIES = $(test_libs)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-lua/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-lua/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \
+ }
+
+uninstall-pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \
+ done
+
+clean-pkglibLTLIBRARIES:
+ -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
+ @list='$(pkglib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libdovecot-lua.la: $(libdovecot_lua_la_OBJECTS) $(libdovecot_lua_la_DEPENDENCIES) $(EXTRA_libdovecot_lua_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdovecot_lua_la_LINK) -rpath $(pkglibdir) $(libdovecot_lua_la_OBJECTS) $(libdovecot_lua_la_LIBADD) $(LIBS)
+
+test-dict-lua$(EXEEXT): $(test_dict_lua_OBJECTS) $(test_dict_lua_DEPENDENCIES) $(EXTRA_test_dict_lua_DEPENDENCIES)
+ @rm -f test-dict-lua$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_dict_lua_OBJECTS) $(test_dict_lua_LDADD) $(LIBS)
+
+test-lua$(EXEEXT): $(test_lua_OBJECTS) $(test_lua_DEPENDENCIES) $(EXTRA_test_lua_DEPENDENCIES)
+ @rm -f test-lua$(EXEEXT)
+ $(AM_V_CCLD)$(test_lua_LINK) $(test_lua_OBJECTS) $(test_lua_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-compat.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-dovecot-http.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-dovecot.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-error.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-pushstring.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-resume.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-table.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-thread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-dict-lua.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lua-test-lua.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_lua-test-lua.o: test-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -MT test_lua-test-lua.o -MD -MP -MF $(DEPDIR)/test_lua-test-lua.Tpo -c -o test_lua-test-lua.o `test -f 'test-lua.c' || echo '$(srcdir)/'`test-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lua-test-lua.Tpo $(DEPDIR)/test_lua-test-lua.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lua.c' object='test_lua-test-lua.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -c -o test_lua-test-lua.o `test -f 'test-lua.c' || echo '$(srcdir)/'`test-lua.c
+
+test_lua-test-lua.obj: test-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -MT test_lua-test-lua.obj -MD -MP -MF $(DEPDIR)/test_lua-test-lua.Tpo -c -o test_lua-test-lua.obj `if test -f 'test-lua.c'; then $(CYGPATH_W) 'test-lua.c'; else $(CYGPATH_W) '$(srcdir)/test-lua.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lua-test-lua.Tpo $(DEPDIR)/test_lua-test-lua.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lua.c' object='test_lua-test-lua.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -c -o test_lua-test-lua.obj `if test -f 'test-lua.c'; then $(CYGPATH_W) 'test-lua.c'; else $(CYGPATH_W) '$(srcdir)/test-lua.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ clean-pkglibLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/dlua-compat.Plo
+ -rm -f ./$(DEPDIR)/dlua-dovecot-http.Plo
+ -rm -f ./$(DEPDIR)/dlua-dovecot.Plo
+ -rm -f ./$(DEPDIR)/dlua-error.Plo
+ -rm -f ./$(DEPDIR)/dlua-pushstring.Plo
+ -rm -f ./$(DEPDIR)/dlua-resume.Plo
+ -rm -f ./$(DEPDIR)/dlua-script.Plo
+ -rm -f ./$(DEPDIR)/dlua-table.Plo
+ -rm -f ./$(DEPDIR)/dlua-thread.Plo
+ -rm -f ./$(DEPDIR)/test-dict-lua.Po
+ -rm -f ./$(DEPDIR)/test_lua-test-lua.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/dlua-compat.Plo
+ -rm -f ./$(DEPDIR)/dlua-dovecot-http.Plo
+ -rm -f ./$(DEPDIR)/dlua-dovecot.Plo
+ -rm -f ./$(DEPDIR)/dlua-error.Plo
+ -rm -f ./$(DEPDIR)/dlua-pushstring.Plo
+ -rm -f ./$(DEPDIR)/dlua-resume.Plo
+ -rm -f ./$(DEPDIR)/dlua-script.Plo
+ -rm -f ./$(DEPDIR)/dlua-table.Plo
+ -rm -f ./$(DEPDIR)/dlua-thread.Plo
+ -rm -f ./$(DEPDIR)/test-dict-lua.Po
+ -rm -f ./$(DEPDIR)/test_lua-test-lua.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstPROGRAMS clean-pkglibLTLIBRARIES cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-pkglibLTLIBRARIES install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS \
+ uninstall-pkglibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-lua/dlua-compat.c b/src/lib-lua/dlua-compat.c
new file mode 100644
index 0000000..c676186
--- /dev/null
+++ b/src/lib-lua/dlua-compat.c
@@ -0,0 +1,157 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "strnum.h"
+#include "dlua-script-private.h"
+
+#if LUA_VERSION_NUM == 502
+# error "Lua 5.2 is not supported. Use Lua 5.1 or 5.3 instead."
+#endif
+
+#ifndef HAVE_LUAL_SETFUNCS
+void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
+{
+ luaL_checkstack(L, nup + 1, "too many upvalues");
+ for (; l->name != NULL; l++) {
+ int i;
+ lua_pushstring(L, l->name);
+ for (i = 0; i < nup; i++)
+ lua_pushvalue(L, -(nup + 1));
+ lua_pushcclosure(L, l->func, nup);
+ lua_settable(L, -(nup + 3));
+ }
+ lua_pop(L, nup);
+}
+#endif
+
+#ifndef HAVE_LUAL_SETMETATABLE
+void luaL_setmetatable(lua_State *L, const char *tname)
+{
+ luaL_checkstack(L, 1, "not enough stack slots");
+ luaL_getmetatable(L, tname);
+ lua_setmetatable(L, -2);
+}
+#endif
+
+#ifndef HAVE_LUA_ISINTEGER
+# if LUA_VERSION_NUM >= 503
+# error "Lua 5.3+ should have lua_isinteger()"
+# endif
+/*
+ * Lua 5.3 added lua_isinteger() which tells us whether or not the input is
+ * an integer. In Lua 5.1 and 5.2, we have to emulate it.
+ */
+#undef lua_isinteger
+int lua_isinteger(lua_State *L, int idx)
+{
+ int isnum;
+
+ if (lua_type(L, idx) != LUA_TNUMBER)
+ return 0;
+
+ (void) lua_tointegerx(L, idx, &isnum);
+
+ return isnum;
+}
+#endif
+
+#ifndef HAVE_LUA_SETI
+void lua_seti(lua_State *L, int index, lua_Integer n)
+{
+ /* stack: value (top) */
+ lua_pushinteger(L, n);
+ /* stack: value, n (top) */
+ lua_insert(L, -2);
+ /* stack: n, value (top) */
+
+ /* adjust relative stack position */
+ if (index < 0)
+ index--;
+
+ lua_settable(L, index);
+}
+#endif
+
+#ifndef HAVE_LUA_TOINTEGERX
+# if LUA_VERSION_NUM >= 502
+# error "Lua 5.2+ should have lua_tointegerx()"
+# endif
+/*
+ * Lua 5.2 added lua_tointegerx() which tells us whether or not the
+ * input was an integer. In Lua 5.1, we have to emulate it to the best of
+ * our ability.
+ */
+lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum_r)
+{
+ lua_Integer integer;
+ lua_Number number;
+ const char *str;
+
+ /*
+ * Unfortunately, Lua 5.1 doesn't provide MIN/MAX value macros for
+ * the lua_Integer type, so we hardcode the assumption that it is
+ * the same size as ptrdiff_t. This matches what Lua does by
+ * default.
+ *
+ * If this compile-time assertion fails, don't forget to change the
+ * PTRDIFF_{MIN,MAX} usage below as well.
+ */
+ (void) COMPILE_ERROR_IF_TRUE(sizeof(lua_Integer) != sizeof(ptrdiff_t));
+
+ switch (lua_type(L, idx)) {
+ case LUA_TSTRING:
+ /* convert using str_to_long() */
+ str = lua_tostring(L, idx);
+
+ if (strncasecmp(str, "0x", 2) == 0) {
+ /* hex */
+ uintmax_t tmp;
+
+ /* skip over leading 0x */
+ str += 2;
+
+ if (str_to_uintmax_hex(str, &tmp) < 0)
+ break;
+
+ *isnum_r = (tmp <= PTRDIFF_MAX) ? 1 : 0;
+ return tmp;
+ } else {
+ /* try decimal */
+ intmax_t tmp;
+
+ if (str_to_intmax(str, &tmp) < 0)
+ break;
+
+ *isnum_r = ((tmp >= PTRDIFF_MIN) && (tmp <= PTRDIFF_MAX)) ? 1 : 0;
+ return tmp;
+ }
+
+ break;
+ case LUA_TNUMBER:
+ /* use lua helper macro */
+ number = lua_tonumber(L, idx);
+
+ /* Lua 5.1-only macro from luaconf.h */
+ lua_number2integer(integer, number);
+
+ *isnum_r = (((lua_Number) integer) == number) ? 1 : 0;
+
+ return integer;
+ default:
+ break;
+ }
+
+ /* not an integer */
+ *isnum_r = 0;
+ return 0;
+}
+#endif
+
+#if LUA_VERSION_NUM > 501 && LUA_VERSION_NUM < 504
+# undef lua_resume
+int lua_resume_compat(lua_State *L, lua_State *from, int nargs, int *nresults)
+{
+ *nresults = 1;
+ return lua_resume(L, from, nargs);
+}
+#endif
diff --git a/src/lib-lua/dlua-compat.h b/src/lib-lua/dlua-compat.h
new file mode 100644
index 0000000..052bb83
--- /dev/null
+++ b/src/lib-lua/dlua-compat.h
@@ -0,0 +1,69 @@
+#ifndef DLUA_COMPAT_H
+#define DLUA_COMPAT_H
+
+/*
+ * In general, make whatever Lua version we have behave more like Lua 5.3.
+ */
+
+#if !defined(LUA_OK)
+# define LUA_OK 0
+#endif
+
+/* functionality missing from <= 5.2 */
+#if LUA_VERSION_NUM <= 502
+# define luaL_newmetatable(L, tn) \
+ ((luaL_newmetatable(L, tn) != 0) ? \
+ (lua_pushstring((L), (tn)), lua_setfield((L), -2, "__name"), 1) : \
+ 0)
+#endif
+
+/* functionality missing from <= 5.1 */
+#if LUA_VERSION_NUM <= 501
+# define lua_load(L, r, s, fn, m) lua_load(L, r, s, fn)
+# define luaL_newlibtable(L, l) (lua_createtable(L, 0, sizeof(l)/sizeof(*(l))-1))
+# define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_register(L, NULL, l))
+#endif
+
+#ifndef HAVE_LUAL_SETFUNCS
+void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);
+#endif
+
+#ifndef HAVE_LUAL_SETMETATABLE
+void luaL_setmetatable (lua_State *L, const char *tname);
+#endif
+
+#ifndef HAVE_LUA_ISINTEGER
+/*
+ * Lua 5.3 can actually keep track of intergers vs. numbers. As a
+ * consequence, lua_isinteger() tells us if the internal representation of
+ * the number is an integer (vs. a number). In previous versions, there was
+ * no way to check for this and our compatibility wrapper is not quite
+ * capable of matching the 5.3 behavior exactly. Therefore, it returns 1
+ * when the number is representable as an integer instead.
+ */
+int lua_isinteger(lua_State *L, int idx);
+#endif
+
+#ifndef HAVE_LUA_SETI
+void lua_seti(lua_State *L, int index, lua_Integer n);
+#endif
+
+#ifndef HAVE_LUA_TOINTEGERX
+/*
+ * Lua 5.2 and 5.3 both have lua_tointegerx(), but their behavior is subtly
+ * different. Our compatibility wrapper matches the 5.3 behavior.
+ */
+lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum_r);
+#endif
+
+#if LUA_VERSION_NUM > 501 && LUA_VERSION_NUM < 504
+/*
+ * lua_resume() compatibility function. Lua 5.4 expects an extra "nresults"
+ * argeument.
+ */
+# define lua_resume(L, from, nargs, nresults) \
+ lua_resume_compat(L, from, nargs, nresults)
+int lua_resume_compat(lua_State *L, lua_State *from, int nargs, int *nresults);
+#endif
+
+#endif
diff --git a/src/lib-lua/dlua-dovecot-http.c b/src/lib-lua/dlua-dovecot-http.c
new file mode 100644
index 0000000..a0feefd
--- /dev/null
+++ b/src/lib-lua/dlua-dovecot-http.c
@@ -0,0 +1,522 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "dlua-script-private.h"
+#include "http-url.h"
+#include "http-client.h"
+#include "http-client-private.h"
+#include "istream.h"
+#include "iostream-ssl.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "master-service-ssl-settings.h"
+
+#define DLUA_DOVECOT_HTTP "http"
+#define DLUA_HTTP_CLIENT "struct http_client"
+#define DLUA_HTTP_CLIENT_REQUEST "struct http_client_request"
+#define DLUA_HTTP_RESPONSE "struct dlua_http_response"
+
+struct dlua_http_response {
+ unsigned char version_major;
+ unsigned char version_minor;
+ unsigned int status;
+ const char *reason;
+ const char *location;
+ string_t *payload;
+ time_t date, retry_after;
+ ARRAY_TYPE(http_header_field) headers;
+ pool_t pool;
+ const char *error;
+ struct event *event;
+};
+
+struct dlua_http_response_payload_context {
+ struct io *io;
+ struct istream *payload_istream;
+ string_t *payload_str;
+ char *error;
+ struct event *event;
+ pool_t pool;
+};
+
+static struct http_client_request *
+dlua_check_http_request(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, DLUA_HTTP_CLIENT_REQUEST,
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushliteral(L, "item");
+ lua_rawget(L, arg);
+ struct http_client_request **bp = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return *bp;
+}
+
+static int dlua_http_request_gc(lua_State *L)
+{
+ struct http_client_request **req = lua_touserdata(L, 1);
+ http_client_request_unref(req);
+ return 0;
+}
+
+static int dlua_http_request_add_header(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+
+ struct http_client_request *req = dlua_check_http_request(L, 1);
+
+ const char *name = luaL_checkstring(L, 2);
+ const char *value = luaL_checkstring(L, 3);
+ http_client_request_add_header(req, name, value);
+ return 0;
+}
+
+static int dlua_http_request_remove_header(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+
+ struct http_client_request *req = dlua_check_http_request(L, 1);
+
+ const char *name = luaL_checkstring(L, 2);
+ http_client_request_remove_header(req, name);
+ return 0;
+}
+
+static int dlua_http_request_set_payload(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+
+ struct http_client_request *req = dlua_check_http_request(L, 1);
+ struct istream *payload_istream;
+
+ const char *payload = luaL_checkstring(L, 2);
+ payload_istream = i_stream_create_copy_from_data(payload,
+ strlen(payload));
+ http_client_request_set_payload(req, payload_istream, TRUE);
+ i_stream_unref(&payload_istream);
+ return 0;
+}
+
+static int dlua_http_request_submit(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+
+ struct http_client_request *req = dlua_check_http_request(L, 1);
+
+ /* Clear the GC hook for this request. It will be freed after it's
+ submitted. */
+ lua_getfield(L, -1, "item");
+ if (lua_getmetatable(L, -1) != 1)
+ return luaL_error(L, "Cound't get metatable for the request");
+ lua_pushnil(L);
+ lua_setfield(L, -2, "__gc");
+ lua_pop(L, 2);
+
+ http_client_request_submit(req);
+ http_client_wait(req->client);
+ return 1;
+}
+
+static luaL_Reg lua_dovecot_http_request_methods[] = {
+ { "add_header", dlua_http_request_add_header },
+ { "remove_header", dlua_http_request_remove_header },
+ { "set_payload", dlua_http_request_set_payload },
+ { "submit", dlua_http_request_submit },
+ { NULL, NULL }
+};
+
+static void dlua_push_http_request(lua_State *L, struct http_client_request *req)
+{
+ luaL_checkstack(L, 3, "out of memory");
+ lua_createtable(L, 0, 1);
+ luaL_setmetatable(L, DLUA_HTTP_CLIENT_REQUEST);
+
+ /* we need to attach gc to userdata to support older lua*/
+ struct http_client_request **ptr = lua_newuserdata(L, sizeof(struct http_client_request*));
+ *ptr = req;
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, dlua_http_request_gc);
+ lua_setfield(L, -2, "__gc");
+ lua_setmetatable(L, -2);
+ lua_setfield(L, -2, "item");
+
+ luaL_setfuncs(L, lua_dovecot_http_request_methods, 0);
+}
+
+
+static struct http_client *dlua_check_http_client(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, DLUA_HTTP_CLIENT,
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushliteral(L, "item");
+ lua_rawget(L, arg);
+ struct http_client **bp = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return *bp;
+}
+
+static int dlua_http_client_gc(lua_State *L)
+{
+ struct http_client **_client = lua_touserdata(L, 1);
+ http_client_deinit(_client);
+ return 0;
+}
+static int dlua_http_resp_gc(lua_State *L)
+{
+ struct dlua_http_response **_resp = lua_touserdata(L, 1);
+ array_free(&(*_resp)->headers);
+ pool_unref(&(*_resp)->pool);
+ return 0;
+}
+
+static struct dlua_http_response *dlua_check_http_response(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, DLUA_HTTP_RESPONSE,
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushliteral(L, "item");
+ lua_rawget(L, arg);
+ struct dlua_http_response **bp = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return *bp;
+}
+
+static int dlua_http_response_get_status(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+
+ const struct dlua_http_response *resp = dlua_check_http_response(L, 1);
+ lua_pushinteger(L, resp->status);
+ return 1;
+}
+
+static int dlua_http_response_get_payload(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+
+ const struct dlua_http_response *resp = dlua_check_http_response(L, 1);
+ lua_pushlstring(L, resp->payload->data, resp->payload->used);
+ return 1;
+}
+
+static int dlua_http_response_get_header(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+
+ const struct dlua_http_response *resp = dlua_check_http_response(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ const char *value = "";
+
+ const struct http_header_field *hfield;
+ array_foreach(&resp->headers, hfield) {
+ if (http_header_field_is(hfield, name)) {
+ value = hfield->value;
+ break;
+ }
+ }
+
+ lua_pushstring(L, value);
+ return 1;
+}
+
+static int dlua_http_response_get_reason(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+
+ const struct dlua_http_response *resp = dlua_check_http_response(L, 1);
+ lua_pushstring(L, resp->reason);
+ return 1;
+}
+
+static const luaL_Reg dovecot_http_response_methods[] = {
+ { "status", dlua_http_response_get_status },
+ { "payload", dlua_http_response_get_payload },
+ { "header", dlua_http_response_get_header },
+ { "reason", dlua_http_response_get_reason },
+ { NULL, NULL }
+};
+
+static void
+dlua_push_http_response(lua_State *L, const struct dlua_http_response *resp)
+{
+ luaL_checkstack(L, 3, "out of memory");
+ lua_createtable(L, 0, 1);
+ luaL_setmetatable(L, DLUA_HTTP_RESPONSE);
+
+ /* we need to attach gc to userdata to support older lua*/
+ const struct dlua_http_response **ptr = lua_newuserdata(L, sizeof(struct dlua_http_response*));
+ *ptr = resp;
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, dlua_http_resp_gc);
+ lua_setfield(L, -2, "__gc");
+ lua_setmetatable(L, -2);
+ lua_setfield(L, -2, "item");
+
+ luaL_setfuncs(L, dovecot_http_response_methods, 0);
+}
+
+static void dlua_http_response_input_payload(struct dlua_http_response_payload_context *ctx)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ /* read payload */
+ while ((ret=i_stream_read_more(ctx->payload_istream, &data, &size)) > 0) {
+ str_append_data(ctx->payload_str, data, size);
+ i_stream_skip(ctx->payload_istream, size);
+ }
+
+ if (ctx->payload_istream->stream_errno != 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Response payload read error: %s",
+ i_stream_get_error(ctx->payload_istream));
+ }
+ if (ret == 0) {
+ e_debug(ctx->event, "DEBUG: REQUEST: NEED MORE DATA");
+ /* we will be called again for this request */
+ } else {
+ if (ctx->payload_istream->stream_errno != 0) {
+ e_error(ctx->event, "ERROR: REQUEST PAYLOAD READ ERROR: %s",
+ i_stream_get_error(ctx->payload_istream));
+ } else
+ e_debug(ctx->event, "DEBUG: REQUEST: Finished");
+ io_remove(&ctx->io);
+ i_free(ctx);
+ }
+}
+
+static void dlua_http_response_read_payload(const struct http_response *response,
+ struct dlua_http_response *dlua_resp)
+{
+ struct dlua_http_response_payload_context *ctx =
+ i_new(struct dlua_http_response_payload_context ,1);
+ ctx->payload_istream = response->payload;
+ ctx->io = io_add_istream(response->payload,
+ dlua_http_response_input_payload, ctx);
+ ctx->payload_str = dlua_resp->payload;
+ ctx->pool = dlua_resp->pool;
+ ctx->event = dlua_resp->event;
+ dlua_http_response_input_payload(ctx);
+}
+
+static void
+dlua_http_request_callback(const struct http_response *response, lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+
+ /* we need a keep a copy of http_response, otherwise the data will be
+ * lost when the object is freed. */
+ pool_t pool = pool_alloconly_create("http_response", 1024);
+ struct dlua_http_response *resp = p_new(pool, struct dlua_http_response, 1);
+ resp->pool = pool;
+ resp->date = response->date;
+ resp->version_major = response->version_major;
+ resp->version_minor = response->version_minor;
+ resp->status = response->status;
+ resp->reason = p_strdup(resp->pool, response->reason);
+ resp->location = p_strdup(resp->pool, response->location);
+ resp->date = response->date;
+ resp->retry_after = response->retry_after;
+ resp->payload = str_new(resp->pool, 528);
+ resp->event = script->event;
+ p_array_init(&resp->headers, resp->pool, 2);
+
+ const ARRAY_TYPE(http_header_field) *hdrs;
+ const struct http_header_field *hdr;
+ struct http_header_field *hdr_cpy;
+
+ hdrs = http_response_header_get_fields(response);
+ if (hdrs != NULL) {
+ array_foreach(hdrs, hdr) {
+ hdr_cpy = array_append_space(&resp->headers);
+ hdr_cpy->name = p_strdup(resp->pool, hdr->name);
+ hdr_cpy->size = hdr->size;
+ hdr_cpy->value = p_strdup(resp->pool, hdr->value);
+ }
+ }
+
+ if (response->payload != NULL) {
+ /* got payload */
+ dlua_http_response_read_payload(response, resp);
+ }
+
+ dlua_push_http_response(L, resp);
+}
+
+static int dlua_http_request_new(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+
+ const char *url, *method = "GET";
+ struct http_client_request *http_req;
+ struct http_url *http_url;
+ const char *error;
+ struct http_client *client = dlua_check_http_client(L, 1);
+
+ luaL_checktype(L, 2, LUA_TTABLE);
+
+ lua_getfield(L, -1, "url");
+ if (lua_isnil(L, -1))
+ return luaL_error(L, "cannot create request: url not specified");
+ else
+ url = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, -1, "method");
+ if (!lua_isnil(L, -1))
+ method = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+
+ if (http_url_parse(url, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool_datastack_create(),
+ &http_url, &error) < 0) {
+ return luaL_error(L, "Failed to parse url %s: %s", url, error);
+ return -1;
+ }
+
+ if (http_url->have_ssl && client->set.ssl == NULL) {
+ return luaL_error(L, "TLS not enabled, cannot submit https request");
+ }
+ http_req = http_client_request_url(client, method, http_url,
+ dlua_http_request_callback, L);
+
+ dlua_push_http_request(L, http_req);
+ return 1;
+}
+
+static const luaL_Reg dovecot_http_client_methods[] = {
+ { "request", dlua_http_request_new },
+ { NULL, NULL }
+};
+
+static void dlua_push_http_client(lua_State *L, struct http_client *client)
+{
+ luaL_checkstack(L, 3, "out of memory");
+ lua_createtable(L, 0, 1);
+ luaL_setmetatable(L, DLUA_HTTP_CLIENT);
+
+ /* we need to attach gc to userdata to support older lua*/
+ struct http_client **ptr = lua_newuserdata(L, sizeof(struct http_client*));
+ *ptr = client;
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, dlua_http_client_gc);
+ lua_setfield(L, -2, "__gc");
+ lua_setmetatable(L, -2);
+ lua_setfield(L, -2, "item");
+
+ luaL_setfuncs(L, dovecot_http_client_methods, 0);
+}
+
+#define CLIENT_SETTING_STR(field) \
+ if (dlua_table_get_string_by_str(L, -1, #field, &(set->field)) < 0) { \
+ *error_r = t_strdup_printf("%s: string expected", #field); return -1; }
+#define CLIENT_SETTING_UINT(field) \
+ if (dlua_table_get_uint_by_str(L, -1, #field, &(set->field)) < 0) { \
+ *error_r = t_strdup_printf("%s: non-negative number expected", #field); return -1; }
+#define CLIENT_SETTING_BOOL(field) \
+ if (dlua_table_get_bool_by_str(L, -1, #field, &(set->field)) < 0) { \
+ *error_r = t_strdup_printf("%s: boolean expected", #field); return -1; }
+
+static int parse_client_settings(lua_State *L, struct http_client_settings *set,
+ const char **error_r)
+{
+ struct http_url *parsed_url;
+ const char *proxy_url;
+ const struct master_service_settings *master_set =
+ master_service_settings_get(master_service);
+ /* need to figure out socket dir */
+ set->dns_client_socket_path = t_strconcat(master_set->base_dir, "/dns-client", NULL);
+ CLIENT_SETTING_STR(user_agent);
+ CLIENT_SETTING_STR(rawlog_dir);
+ CLIENT_SETTING_UINT(max_idle_time_msecs);
+/* FIXME: Enable when asynchronous calls are supported
+* CLIENT_SETTING_UINT(max_parallel_connections);
+* CLIENT_SETTING_UINT(max_pipelined_requests);
+*/
+ CLIENT_SETTING_BOOL(no_auto_redirect);
+ CLIENT_SETTING_BOOL(no_auto_retry);
+ CLIENT_SETTING_UINT(max_redirects);
+ CLIENT_SETTING_UINT(max_attempts);
+ CLIENT_SETTING_UINT(max_connect_attempts);
+ CLIENT_SETTING_UINT(connect_backoff_time_msecs);
+ CLIENT_SETTING_UINT(connect_backoff_max_time_msecs);
+ CLIENT_SETTING_UINT(request_absolute_timeout_msecs);
+ CLIENT_SETTING_UINT(request_timeout_msecs);
+ CLIENT_SETTING_UINT(connect_timeout_msecs);
+ CLIENT_SETTING_UINT(soft_connect_timeout_msecs);
+ CLIENT_SETTING_UINT(max_auto_retry_delay_secs);
+ CLIENT_SETTING_BOOL(debug);
+
+ if (dlua_table_get_string_by_str(L, -1, "proxy_url", &proxy_url) > 0) {
+ if (http_url_parse(proxy_url, NULL, HTTP_URL_ALLOW_USERINFO_PART,
+ pool_datastack_create(), &parsed_url, error_r) < 0) {
+ *error_r = t_strdup_printf("proxy_url is invalid: %s",
+ *error_r);
+ return -1;
+ }
+ set->proxy_url = parsed_url;
+ set->proxy_username = parsed_url->user;
+ set->proxy_password = parsed_url->password;
+ }
+
+ lua_getfield(L, -1, "event_parent");
+ if (!lua_isnil(L, -1))
+ set->event_parent = dlua_check_event(L, -1);
+
+ return 0;
+}
+
+static int dlua_http_client_new(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ struct http_client *client;
+ struct http_client_settings http_set;
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ i_zero(&http_set);
+
+ if (parse_client_settings(L, &http_set, &error) < 0)
+ luaL_error(L, "Invalid HTTP client setting: %s", error);
+
+ const struct master_service_ssl_settings *master_ssl_set =
+ master_service_ssl_settings_get(master_service);
+ master_service_ssl_client_settings_to_iostream_set(master_ssl_set,
+ pool_datastack_create(), &ssl_set);
+ http_set.ssl = &ssl_set;
+
+ client = http_client_init(&http_set);
+ dlua_push_http_client(L, client);
+ return 1;
+}
+
+static const luaL_Reg dovecot_http_methods[] = {
+ { "client", dlua_http_client_new },
+ { NULL, NULL }
+};
+
+void dlua_dovecot_http_register(struct dlua_script *script)
+{
+ i_assert(script != NULL);
+
+ lua_State *L = script->L;
+
+ /* push dovecot table on the stack */
+ dlua_get_dovecot(L);
+
+ /* populate http methods in a table and add them as dovecot.http */
+ lua_newtable(L);
+ luaL_setfuncs(L, dovecot_http_methods, 0);
+ lua_setfield(script->L, -2, DLUA_DOVECOT_HTTP);
+ lua_pop(script->L, 1);
+}
diff --git a/src/lib-lua/dlua-dovecot.c b/src/lib-lua/dlua-dovecot.c
new file mode 100644
index 0000000..124f533
--- /dev/null
+++ b/src/lib-lua/dlua-dovecot.c
@@ -0,0 +1,681 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "dlua-script-private.h"
+
+#include <libgen.h>
+
+#define LUA_SCRIPT_DOVECOT "dovecot"
+#define DLUA_EVENT_PASSTHROUGH "struct event_passthrough"
+#define DLUA_EVENT "struct event"
+
+static void dlua_event_log(lua_State *L, struct event *event,
+ enum log_type log_type, const char *str);
+
+static void dlua_get_file_line(lua_State *L, int arg, const char **file_r,
+ unsigned int *line_r)
+{
+ const char *ptr;
+ lua_Debug ar;
+ lua_getstack(L, arg, &ar);
+ lua_getinfo(L, "Sl", &ar);
+ /* basename would be better, but basename needs memory
+ allocation, since it might modify the buffer contents,
+ so we use this which is good enough */
+ if (ar.source[0] != '@')
+ ptr = "<non-file location>";
+ else if ((ptr = strrchr(ar.source, '/')) == NULL)
+ ptr = ar.source;
+ else
+ ptr++;
+ *file_r = ptr;
+ *line_r = ar.currentline;
+}
+
+static struct event_passthrough *
+dlua_check_event_passthrough(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, DLUA_EVENT,
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushliteral(L, "item");
+ lua_rawget(L, arg);
+ void *bp = (void*)lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return (struct event_passthrough*)bp;
+}
+
+static void dlua_push_event_passthrough(lua_State *L,
+ struct event_passthrough *event)
+{
+ luaL_checkstack(L, 3, "out of memory");
+ lua_createtable(L, 0, 1);
+ luaL_setmetatable(L, DLUA_EVENT_PASSTHROUGH);
+
+ lua_pushlightuserdata(L, event);
+ lua_setfield(L, -2, "item");
+}
+
+static int dlua_event_pt_append_log_prefix(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *prefix = luaL_checkstring(L, 2);
+
+ event->append_log_prefix(prefix);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_replace_log_prefix(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *prefix = luaL_checkstring(L, 2);
+
+ event->replace_log_prefix(prefix);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_set_name(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+
+ event->set_name(name);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+
+static int dlua_event_pt_set_always_log_source(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+
+ event->set_always_log_source();
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_add_str(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ const char *value = luaL_checkstring(L, 3);
+
+ event->add_str(name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_add_int(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 3);
+
+ event->add_int(name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_add_timeval(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ /* this is time in seconds */
+ lua_Integer value = luaL_checkinteger(L, 3);
+ struct timeval tv = {
+ .tv_sec = value,
+ };
+
+ event->add_timeval(name, &tv);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_inc_int(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 3);
+
+ event->inc_int(name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_log_debug(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event->event(), LOG_TYPE_DEBUG, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_log_info(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event->event(), LOG_TYPE_INFO, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_log_warning(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event->event(), LOG_TYPE_WARNING, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_pt_log_error(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event_passthrough *event = dlua_check_event_passthrough(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event->event(), LOG_TYPE_ERROR, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static const luaL_Reg event_passthrough_methods[] ={
+ { "append_log_prefix", dlua_event_pt_append_log_prefix },
+ { "replace_log_prefix", dlua_event_pt_replace_log_prefix },
+ { "set_always_log_source", dlua_event_pt_set_always_log_source },
+ { "set_name", dlua_event_pt_set_name },
+ { "add_str", dlua_event_pt_add_str },
+ { "add_int", dlua_event_pt_add_int },
+ { "add_timeval", dlua_event_pt_add_timeval },
+ { "inc_int", dlua_event_pt_inc_int },
+ { "log_debug", dlua_event_pt_log_debug },
+ { "log_info", dlua_event_pt_log_info },
+ { "log_warning", dlua_event_pt_log_warning },
+ { "log_error", dlua_event_pt_log_error },
+ { NULL, NULL }
+};
+
+static int dlua_event_gc(lua_State *L)
+{
+ struct event **event = lua_touserdata(L, 1);
+ event_unref(event);
+ return 0;
+}
+
+struct event *dlua_check_event(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, DLUA_EVENT,
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushliteral(L, "item");
+ lua_rawget(L, arg);
+ struct event **bp = (void*)lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return *bp;
+}
+
+void dlua_push_event(lua_State *L, struct event *event)
+{
+ luaL_checkstack(L, 3, "out of memory");
+ lua_createtable(L, 0, 1);
+ luaL_setmetatable(L, DLUA_EVENT);
+
+ /* we need to attach gc to userdata to support older lua*/
+ struct event **ptr = lua_newuserdata(L, sizeof(struct event*));
+ *ptr = event;
+ event_ref(event);
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, dlua_event_gc);
+ lua_setfield(L, -2, "__gc");
+ lua_setmetatable(L, -2);
+ lua_setfield(L, -2, "item");
+}
+
+static int dlua_event_append_log_prefix(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *prefix = luaL_checkstring(L, 2);
+
+ event_set_append_log_prefix(event, prefix);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_replace_log_prefix(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *prefix = luaL_checkstring(L, 2);
+
+ event_replace_log_prefix(event, prefix);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_set_name(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+
+ event_set_name(event, name);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+
+static int dlua_event_set_always_log_source(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ struct event *event = dlua_check_event(L, 1);
+
+ event_set_always_log_source(event);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_add_str(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event *event = dlua_check_event(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ const char *value = luaL_checkstring(L, 3);
+
+ event_add_str(event, name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_add_int(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event *event = dlua_check_event(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 3);
+
+ event_add_int(event, name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_add_timeval(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event *event = dlua_check_event(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ /* this is time in seconds */
+ lua_Integer value = luaL_checkinteger(L, 3);
+ struct timeval tv = {
+ .tv_sec = value,
+ };
+
+ event_add_timeval(event, name, &tv);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_inc_int(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ struct event *event = dlua_check_event(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 3);
+
+ event_inc_int(event, name, value);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_log_debug(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event, LOG_TYPE_DEBUG, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_log_info(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event, LOG_TYPE_INFO, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_log_warning(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event, LOG_TYPE_WARNING, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int dlua_event_log_error(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ struct event *event = dlua_check_event(L, 1);
+ const char *str = luaL_checkstring(L, 2);
+
+ dlua_event_log(L, event, LOG_TYPE_ERROR, str);
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+#undef event_create_passthrough
+#undef event_create
+static int dlua_event_passthrough_event(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ struct event *event = dlua_check_event(L, 1);
+ const char *file;
+ unsigned int line;
+
+ dlua_get_file_line(L, 1, &file, &line);
+ struct event_passthrough *e =
+ event_create_passthrough(event, file, line);
+ dlua_push_event_passthrough(L, e);
+
+ return 1;
+}
+
+static int dlua_event_new(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ DLUA_REQUIRE_ARGS_IN(L, 0, 1);
+ struct event *event, *parent = script->event;
+ const char *file;
+ unsigned int line;
+
+ if (lua_gettop(L) == 1)
+ parent = dlua_check_event(L, 1);
+ dlua_get_file_line(L, 1, &file, &line);
+ event = event_create(parent, file, line);
+ dlua_push_event(L, event);
+ event_unref(&event);
+ return 1;
+}
+
+static const luaL_Reg event_methods[] ={
+ { "append_log_prefix", dlua_event_append_log_prefix },
+ { "replace_log_prefix", dlua_event_replace_log_prefix },
+ { "set_always_log_source", dlua_event_set_always_log_source },
+ { "set_name", dlua_event_set_name },
+ { "add_str", dlua_event_add_str },
+ { "add_int", dlua_event_add_int },
+ { "add_timeval", dlua_event_add_timeval },
+ { "inc_int", dlua_event_inc_int },
+ { "log_debug", dlua_event_log_debug },
+ { "log_info", dlua_event_log_info },
+ { "log_warning", dlua_event_log_warning },
+ { "log_error", dlua_event_log_error },
+ { "passthrough_event", dlua_event_passthrough_event },
+ { NULL, NULL }
+};
+
+static void dlua_event_register(struct dlua_script *script){
+ i_assert(script != NULL);
+
+ luaL_newmetatable(script->L, DLUA_EVENT_PASSTHROUGH);
+ lua_pushvalue(script->L, -1);
+ lua_setfield(script->L, -2, "__index");
+ luaL_setfuncs(script->L, event_passthrough_methods, 0);
+ lua_pop(script->L, 1);
+
+ luaL_newmetatable(script->L, DLUA_EVENT);
+ lua_pushvalue(script->L, -1);
+ lua_setfield(script->L, -2, "__index");
+ luaL_setfuncs(script->L, event_methods, 0);
+ lua_pop(script->L, 1);
+}
+
+static int dlua_i_debug(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ const char *msg = luaL_checkstring(L, 1);
+ i_debug("%s", msg);
+ return 0;
+}
+
+static int dlua_i_info(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ const char *msg = luaL_checkstring(L, 1);
+ i_info("%s", msg);
+ return 0;
+}
+
+static int dlua_i_warning(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ const char *msg = luaL_checkstring(L, 1);
+ i_warning("%s", msg);
+ return 0;
+}
+
+static int dlua_i_error(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+ const char *msg = luaL_checkstring(L, 1);
+ i_error("%s", msg);
+ return 0;
+}
+
+static int dlua_has_flag(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ /* we rather deal with unsigned value here */
+ lua_Integer value = luaL_checkinteger(L, 1);
+ lua_Integer flag = luaL_checkinteger(L, 2);
+
+ lua_pushboolean(L, (value & flag) == flag);
+ return 1;
+}
+
+static int dlua_set_flag(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 1);
+ lua_Integer flag = luaL_checkinteger(L, 2);
+
+ lua_pushinteger(L, value | flag);
+ return 1;
+}
+
+static int dlua_clear_flag(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ lua_Integer value = luaL_checkinteger(L, 1);
+ lua_Integer flag = luaL_checkinteger(L, 2);
+
+ lua_pushinteger(L, value & (lua_Integer)~flag);
+ return 1;
+}
+
+static int dlua_script_strict_index(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 2);
+ const char *name = luaL_checkstring(L, 2);
+ return luaL_error(L, "attempt to write to read undeclared global variable %s",
+ name);
+}
+
+static int dlua_script_strict_newindex(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 3);
+ if (lua_type(L, 3) == LUA_TFUNCTION) {
+ /* allow defining global functions */
+ lua_rawset(L, 1);
+ } else {
+ const char *name = luaL_checkstring(L, 2);
+ return luaL_error(L, "attempt to write to undeclared global variable %s",
+ name);
+ }
+ return 0;
+}
+
+static luaL_Reg env_strict_metamethods[] = {
+ { "__index", dlua_script_strict_index },
+ { "__newindex", dlua_script_strict_newindex },
+ { NULL, NULL }
+};
+
+static int dlua_restrict_global_variables(lua_State *L)
+{
+ DLUA_REQUIRE_ARGS(L, 1);
+
+ if (lua_toboolean(L, 1)) {
+ /* disable defining global variables */
+ lua_getglobal(L, "_G");
+ lua_newtable(L);
+ luaL_setfuncs(L, env_strict_metamethods, 0);
+ } else {
+ /* revert restrictions */
+ lua_getglobal(L, "_G");
+ lua_newtable(L);
+ }
+ lua_setmetatable(L, -2);
+ lua_pop(L, 1);
+ return 0;
+}
+
+static luaL_Reg lua_dovecot_methods[] = {
+ { "i_debug", dlua_i_debug },
+ { "i_info", dlua_i_info },
+ { "i_warning", dlua_i_warning },
+ { "i_error", dlua_i_error },
+ { "event", dlua_event_new },
+ { "has_flag", dlua_has_flag },
+ { "set_flag", dlua_set_flag },
+ { "clear_flag", dlua_clear_flag },
+ { "restrict_global_variables", dlua_restrict_global_variables },
+ { NULL, NULL }
+};
+
+void dlua_get_dovecot(lua_State *L)
+{
+ lua_getglobal(L, LUA_SCRIPT_DOVECOT);
+}
+
+void dlua_dovecot_register(struct dlua_script *script)
+{
+ i_assert(script != NULL);
+
+ dlua_event_register(script);
+
+ /* Create table for holding values */
+ lua_newtable(script->L);
+
+ /* push new metatable to stack */
+ luaL_newmetatable(script->L, LUA_SCRIPT_DOVECOT);
+ /* this will register functions to the metatable itself */
+ luaL_setfuncs(script->L, lua_dovecot_methods, 0);
+ /* point __index to self */
+ lua_pushvalue(script->L, -1);
+ lua_setfield(script->L, -1, "__index");
+ /* set table's metatable, pops stack */
+ lua_setmetatable(script->L, -2);
+
+ /* register table as global */
+ lua_setglobal(script->L, LUA_SCRIPT_DOVECOT);
+
+ /* register http methods */
+ dlua_dovecot_http_register(script);
+}
+
+#undef event_want_level
+static void dlua_event_log(lua_State *L, struct event *event,
+ enum log_type log_type, const char *str)
+{
+ struct event_log_params parms;
+ i_zero(&parms);
+ parms.log_type = log_type;
+ dlua_get_file_line(L, 1, &parms.source_filename, &parms.source_linenum);
+ if (log_type != LOG_TYPE_DEBUG ||
+ event_want_level(event, LOG_TYPE_DEBUG, parms.source_filename,
+ parms.source_linenum)) {
+ event_log(event, &parms, "%s", str);
+ } else {
+ event_send_abort(event);
+ }
+}
diff --git a/src/lib-lua/dlua-error.c b/src/lib-lua/dlua-error.c
new file mode 100644
index 0000000..90011ed
--- /dev/null
+++ b/src/lib-lua/dlua-error.c
@@ -0,0 +1,13 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+int dluaL_error(lua_State *L, const char *fmt, ...)
+{
+ va_list argp;
+ va_start(argp, fmt);
+ (void)dlua_push_vfstring(L, fmt, argp);
+ va_end(argp);
+ return lua_error(L);
+}
diff --git a/src/lib-lua/dlua-pushstring.c b/src/lib-lua/dlua-pushstring.c
new file mode 100644
index 0000000..436fd29
--- /dev/null
+++ b/src/lib-lua/dlua-pushstring.c
@@ -0,0 +1,26 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "dlua-script-private.h"
+
+const char *dlua_push_vfstring(lua_State *L, const char *fmt, va_list argp)
+{
+ const char *str;
+ T_BEGIN {
+ str = t_strdup_vprintf(fmt, argp);
+ (void)lua_pushstring(L, str);
+ str = lua_tostring(L, -1);
+ } T_END;
+ return str;
+}
+
+const char *dlua_push_fstring(lua_State *L, const char *fmt, ...)
+{
+ const char *str;
+ va_list argp;
+ va_start(argp, fmt);
+ str = dlua_push_vfstring(L, fmt, argp);
+ va_end(argp);
+ return str;
+}
diff --git a/src/lib-lua/dlua-resume.c b/src/lib-lua/dlua-resume.c
new file mode 100644
index 0000000..ac46f10
--- /dev/null
+++ b/src/lib-lua/dlua-resume.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dlua-script-private.h"
+
+#define PCALL_RESUME_STATE "pcall-resume-state"
+
+#define RESUME_TIMEOUT "resume-timeout"
+#define RESUME_NARGS "resume-nargs"
+
+struct dlua_pcall_resume_state {
+ dlua_pcall_yieldable_callback_t *callback;
+ void *context;
+ struct timeout *to;
+ int status;
+};
+
+#ifdef DLUA_WITH_YIELDS
+static void call_resume_callback(lua_State *L)
+{
+ struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE);
+
+ timeout_remove(&state->to);
+
+ dlua_tls_clear(L, PCALL_RESUME_STATE);
+
+ state->callback(L, state->context, state->status);
+
+ i_free(state);
+}
+
+static void queue_resume_callback(lua_State *L, int status)
+{
+ struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE);
+
+ i_assert(status != LUA_YIELD);
+
+ if (status != LUA_OK) {
+ int ret;
+
+ /* error occured: run debug.traceback() */
+
+ /* stack: ..., error (top) */
+ lua_getglobal(L, "debug");
+
+ /* stack: ..., error, debug table (top) */
+ lua_getfield(L, -1, "traceback");
+
+ /* stack: ..., error, debug table, traceback function (top) */
+ lua_remove(L, -2);
+
+ /* stack: ..., error, traceback function (top) */
+ lua_pushvalue(L, -2); /* duplicate original error */
+
+ /* stack: ..., error, traceback function, error (top) */
+
+ /*
+ * Note that we kept the original error on the stack as well
+ * as passed it to debug.traceback(). The reason for that
+ * is that debug.traceback() itself can fail. If it fails,
+ * it'll generate its own error - which, ultimately, we
+ * don't care about. For example, consider the following
+ * function:
+ *
+ * function foo()
+ * debug.traceback = nil
+ * error("abc")
+ * end
+ *
+ * If we executed this function, it would error out - but
+ * it'd also cause our pcall to debug.traceback() to fail
+ * with "attempt to call a nil value". We want to discard
+ * the nil error, and just use the original ("abc"). This
+ * is ok because debug.traceback() simply "improves" the
+ * passed in error message to include a traceback and no
+ * traceback is better than a very mysterious error message.
+ */
+ ret = lua_pcall(L, 1, 1, 0);
+
+ /* stack: ..., orig error, traceback result/error (top) */
+
+ if (ret != LUA_OK) {
+ /* traceback failed, remove its error */
+ lua_remove(L, -1);
+ } else {
+ /* traceback succeeded, remove original error */
+ lua_remove(L, -2);
+ }
+ }
+
+ /*
+ * Mangle the passed in status to match dlua_pcall(). Namely, turn
+ * it into -1 on error, and 0+ to indicate the number of return
+ * values.
+ */
+ if (status == LUA_OK)
+ state->status = lua_gettop(L);
+ else
+ state->status = -1;
+
+ i_assert(state->to == NULL);
+ state->to = timeout_add_short(0, call_resume_callback, L);
+}
+
+static void dlua_pcall_yieldable_continue(lua_State *L)
+{
+ struct timeout *to;
+ int nargs, nresults;
+ int ret;
+
+ nargs = dlua_tls_get_int(L, RESUME_NARGS);
+ to = dlua_tls_get_ptr(L, RESUME_TIMEOUT);
+
+ timeout_remove(&to);
+
+ dlua_tls_clear(L, RESUME_TIMEOUT);
+ dlua_tls_clear(L, RESUME_NARGS);
+
+ ret = lua_resume(L, L, nargs, &nresults);
+ if (ret == LUA_YIELD) {
+ /*
+ * thread yielded - nothing to do
+ *
+ * We assume something will call lua_resume(). We don't
+ * care if it is a io related callback or just a timeout.
+ */
+ } else if (ret == LUA_OK) {
+ /* thread completed - invoke callback */
+ queue_resume_callback(L, ret);
+ } else {
+ /* error occurred - invoke callback */
+ queue_resume_callback(L, ret);
+ }
+}
+
+void dlua_pcall_yieldable_resume(lua_State *L, int nargs)
+{
+ struct timeout *to;
+
+ to = timeout_add_short(0, dlua_pcall_yieldable_continue, L);
+
+ dlua_tls_set_ptr(L, RESUME_TIMEOUT, to);
+ dlua_tls_set_int(L, RESUME_NARGS, nargs);
+}
+
+/*
+ * Call a function with nargs arguments in a way that supports yielding.
+ * When the function execution completes, the passed in callback is called.
+ *
+ * Returns -1 on error or 0 on success.
+ */
+#undef dlua_pcall_yieldable
+int dlua_pcall_yieldable(lua_State *L, const char *func_name, int nargs,
+ dlua_pcall_yieldable_callback_t *callback,
+ void *context, const char **error_r)
+{
+ struct dlua_pcall_resume_state *state;
+ int ret;
+ int nresults;
+
+ i_assert(lua_status(L) == LUA_OK);
+
+ lua_getglobal(L, func_name);
+
+ if (!lua_isfunction(L, -1)) {
+ /* clean up the stack - function + arguments */
+ lua_pop(L, nargs + 1);
+ *error_r = t_strdup_printf("'%s' is not a function", func_name);
+ return -1;
+ }
+
+ /* allocate and stash in TLS callback state */
+ state = i_new(struct dlua_pcall_resume_state, 1);
+ state->callback = callback;
+ state->context = context;
+
+ dlua_tls_set_ptr(L, PCALL_RESUME_STATE, state);
+
+ /* stack: args, func (top) */
+ lua_insert(L, -(nargs + 1));
+
+ /* stack: func, args (top) */
+ ret = lua_resume(L, L, nargs, &nresults);
+ if (ret == LUA_YIELD) {
+ /*
+ * thread yielded - nothing to do
+ *
+ * We assume something will call lua_resume(). We don't
+ * care if it is a io related callback or just a timeout.
+ */
+ } else {
+ /*
+ * thread completed / errored
+ *
+ * Since there is nothing that will come back to this lua
+ * thread, we need to make sure the callback is called.
+ *
+ * We handle errors the same as successful completion in
+ * order to avoid forcing the callers to check for lua
+ * errors in two places - the call here and in the callback.
+ */
+ queue_resume_callback(L, ret);
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/lib-lua/dlua-script-private.h b/src/lib-lua/dlua-script-private.h
new file mode 100644
index 0000000..e6c8273
--- /dev/null
+++ b/src/lib-lua/dlua-script-private.h
@@ -0,0 +1,264 @@
+#ifndef LUA_SCRIPT_PRIVATE_H
+#define LUA_SCRIPT_PRIVATE_H 1
+
+#include "dlua-script.h"
+#include "lualib.h"
+#include "lauxlib.h"
+#include "dlua-compat.h"
+
+/* consistency helpers */
+#define lua_isstring(L, n) (lua_isstring((L), (n)) == 1)
+#define lua_isnumber(L, n) (lua_isnumber((L), (n)) == 1)
+#define lua_toboolean(L, n) (lua_toboolean((L), (n)) == 1)
+#define lua_pushboolean(L, b) lua_pushboolean((L), (b) ? 1 : 0)
+#define lua_isinteger(L, n) (lua_isinteger((L), (n)) == 1)
+
+#define DLUA_TABLE_STRING(n, val) { .name = (n),\
+ .type = DLUA_TABLE_VALUE_STRING, .v.s = (val) }
+#define DLUA_TABLE_INTEGER(n, val) { .name = (n), \
+ .type = DLUA_TABLE_VALUE_INTEGER, .v.i = (val) }
+#define DLUA_TABLE_ENUM(n) { .name = #n, \
+ .type = DLUA_TABLE_VALUE_INTEGER, .v.i = (n) }
+#define DLUA_TABLE_DOUBLE(n, val) { .name = (n), \
+ .type = DLUA_TABLE_VALUE_DOUBLE, .v.d = (val) }
+#define DLUA_TABLE_BOOLEAN(n, val) { .name = (n), \
+ .type = DLUA_TABLE_VALUE_BOOLEAN, .v.b = (val) }
+#define DLUA_TABLE_NULL(n, s) { .name = (n), \
+ .type = DLUA_TABLE_VALUE_NULL }
+#define DLUA_TABLE_END { .name = NULL }
+
+#define DLUA_REQUIRE_ARGS_IN(L, x, y) \
+ STMT_START { \
+ if (lua_gettop(L) < (x) || lua_gettop(L) > (y)) { \
+ return luaL_error((L), "expected %d to %d arguments, got %d", \
+ (x), (y), lua_gettop(L)); \
+ } \
+ } STMT_END
+#define DLUA_REQUIRE_ARGS(L, x) \
+ STMT_START { \
+ if (lua_gettop(L) != (x)) { \
+ return luaL_error((L), "expected %d arguments, got %d", \
+ (x), lua_gettop(L)); \
+ } \
+ } STMT_END
+
+struct dlua_script {
+ struct dlua_script *prev,*next;
+ pool_t pool;
+
+ lua_State *L; /* base lua context */
+
+ struct event *event;
+ const char *filename;
+ struct istream *in;
+ ssize_t last_read;
+
+ int ref;
+ bool init:1;
+};
+
+enum dlua_table_value_type {
+ DLUA_TABLE_VALUE_STRING = 0,
+ DLUA_TABLE_VALUE_INTEGER,
+ DLUA_TABLE_VALUE_DOUBLE,
+ DLUA_TABLE_VALUE_BOOLEAN,
+ DLUA_TABLE_VALUE_NULL
+};
+
+struct dlua_table_values {
+ const char *name;
+ enum dlua_table_value_type type;
+ union {
+ const char *s;
+ ptrdiff_t i;
+ double d;
+ bool b;
+ } v;
+};
+
+typedef void dlua_pcall_yieldable_callback_t(lua_State *L, void *context, int status);
+
+extern struct event_category event_category_lua;
+
+/* assorted wrappers for lua_foo(), but operating on a struct dlua_script */
+void dlua_register(struct dlua_script *script, const char *name,
+ lua_CFunction f);
+
+/* Get dlua_script from lua_State */
+struct dlua_script *dlua_script_from_state(lua_State *L);
+
+/* register 'dovecot' global */
+void dlua_dovecot_register(struct dlua_script *script);
+
+/* push 'dovecot' global on top of stack */
+void dlua_get_dovecot(lua_State *L);
+
+/* register 'http' methods to 'dovecot' */
+void dlua_dovecot_http_register(struct dlua_script *script);
+
+/* assign values to table on idx */
+void dlua_set_members(lua_State *L, const struct dlua_table_values *values, int idx);
+
+/* push event to top of stack */
+void dlua_push_event(lua_State *L, struct event *event);
+
+/* get event from given stack position */
+struct event *dlua_check_event(lua_State *L, int arg);
+
+/* improved lua_pushfstring, can handle full C format support */
+const char *dlua_push_vfstring(lua_State *L, const char *fmt, va_list argp) ATTR_FORMAT(2, 0);
+const char *dlua_push_fstring(lua_State *L, const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+/* improved luaL_error, can handle full C format support */
+int dluaL_error(lua_State *L, const char *fmt, ...) ATTR_FORMAT(2, 3);
+#define luaL_error(...) dluaL_error(__VA_ARGS__)
+
+/*
+ * Returns field from a Lua table
+ *
+ * There are different variants of these that allow for different key types
+ * and different value types. In general, the function name scheme is:
+ *
+ * dlua_table_get_<return type>_by_<key type>
+ *
+ * The _by_{str,int} variants use the supplied field value as the table key.
+ *
+ * The _by_thread variants use the current thread's thread object as the
+ * table key.
+ *
+ * Returns:
+ * -1 = incompatible value type
+ * 0 = nil or not found
+ * 1 = value found
+ */
+int dlua_table_get_luainteger_by_str(lua_State *L, int idx, const char *field, lua_Integer *value_r);
+int dlua_table_get_int_by_str(lua_State *L, int idx, const char *field, int *value_r);
+int dlua_table_get_intmax_by_str(lua_State *L, int idx, const char *field, intmax_t *value_r);
+int dlua_table_get_uint_by_str(lua_State *L, int idx, const char *field, unsigned int *value_r);
+int dlua_table_get_uintmax_by_str(lua_State *L, int idx, const char *field, uintmax_t *value_r);
+int dlua_table_get_number_by_str(lua_State *L, int idx, const char *field, lua_Number *value_r);
+int dlua_table_get_bool_by_str(lua_State *L, int idx, const char *field, bool *value_r);
+int dlua_table_get_string_by_str(lua_State *L, int idx, const char *field, const char **value_r);
+int dlua_table_get_data_by_str(lua_State *L, int idx, const char *field, const unsigned char **value_r, size_t *len_r);
+
+int dlua_table_get_luainteger_by_int(lua_State *L, int idx, lua_Integer field, lua_Integer *value_r);
+int dlua_table_get_int_by_int(lua_State *L, int idx, lua_Integer field, int *value_r);
+int dlua_table_get_intmax_by_int(lua_State *L, int idx, lua_Integer field, intmax_t *value_r);
+int dlua_table_get_uint_by_int(lua_State *L, int idx, lua_Integer field, unsigned int *value_r);
+int dlua_table_get_uintmax_by_int(lua_State *L, int idx, lua_Integer field, uintmax_t *value_r);
+int dlua_table_get_number_by_int(lua_State *L, int idx, lua_Integer field, lua_Number *value_r);
+int dlua_table_get_bool_by_int(lua_State *L, int idx, lua_Integer field, bool *value_r);
+int dlua_table_get_string_by_int(lua_State *L, int idx, lua_Integer field, const char **value_r);
+int dlua_table_get_data_by_int(lua_State *L, int idx, lua_Integer field, const unsigned char **value_r, size_t *len_r);
+
+int dlua_table_get_luainteger_by_thread(lua_State *L, int idx, lua_Integer *value_r);
+int dlua_table_get_int_by_thread(lua_State *L, int idx, int *value_r);
+int dlua_table_get_intmax_by_thread(lua_State *L, int idx, intmax_t *value_r);
+int dlua_table_get_uint_by_thread(lua_State *L, int idx, unsigned int *value_r);
+int dlua_table_get_uintmax_by_thread(lua_State *L, int idx, uintmax_t *value_r);
+int dlua_table_get_number_by_thread(lua_State *L, int idx, lua_Number *value_r);
+int dlua_table_get_bool_by_thread(lua_State *L, int idx, bool *value_r);
+int dlua_table_get_string_by_thread(lua_State *L, int idx, const char **value_r);
+int dlua_table_get_data_by_thread(lua_State *L, int idx, const unsigned char **value_r, size_t *len_r);
+
+/*
+ * Pushes onto the stack the value t[k], where t is the value at the given
+ * index and k is field argument. Unlike lua_gettable(), this function
+ * checks the type of the retrieved value against the passed in type.
+ * [-1,+0..1,e]
+ *
+ * There are different variants of these that allow for different key types.
+ * In general, the function name scheme is:
+ *
+ * dlua_table_get_by_<key type>
+ *
+ * The _by_{str,int} variants use the supplied field value as the table key.
+ *
+ * The _by_thread variants use the current thread's thread object as the
+ * table key.
+ *
+ * Returns:
+ * -1 = incompatible value type (nothing is pushed)
+ * 0 = nil or not found (nothing is pushed)
+ * 1 = value found (retrieved value is pushed to the top of the stack)
+ */
+int dlua_table_get_by_str(lua_State *L, int idx, int type, const char *field);
+int dlua_table_get_by_int(lua_State *L, int idx, int type, lua_Integer field);
+int dlua_table_get_by_thread(lua_State *L, int idx, int type);
+
+/* call a function in a script.
+
+ **NOTE**: This function works differently than lua_pcall:
+
+ return value:
+ -1 = error
+ 0+ = number of result(s)
+
+*/
+int dlua_pcall(lua_State *L, const char *func_name, int nargs, int nresults,
+ const char **error_r);
+
+/* dumps current stack as i_debug lines */
+void dlua_dump_stack(lua_State *L);
+
+/* Create new thread and keep track of it. */
+lua_State *dlua_script_new_thread(struct dlua_script *script);
+
+/* Close thread. */
+void dlua_script_close_thread(struct dlua_script *script, lua_State **_L);
+
+#ifdef DLUA_WITH_YIELDS
+/*
+ * Call a function with nargs in a way that supports yielding.
+ *
+ * When the specified function returns, the callback will be called with the
+ * supplied context pointer and a status integer indicating whether an error
+ * occurred (-1) or whether execution completed successfully (0+). In the
+ * case of a successful completion, the status will indicate the number of
+ * results returned by the function. On failure, the top of the stack
+ * contains the error object.
+ *
+ * Returns:
+ * -1 = if function name refers to a non-function type
+ * 0 = function called, callback will be called in the future
+ */
+int dlua_pcall_yieldable(lua_State *L, const char *func_name, int nargs,
+ dlua_pcall_yieldable_callback_t *callback,
+ void *context, const char **error_r);
+#define dlua_pcall_yieldable(L, func_name, nargs, callback, context, error_r) \
+ dlua_pcall_yieldable(L, TRUE ? func_name : \
+ CALLBACK_TYPECHECK(callback, void (*)(lua_State *, typeof(context), int)), \
+ nargs, (dlua_pcall_yieldable_callback_t *)callback, context, error_r)
+/*
+ * Resume yielded function execution.
+ *
+ * The nargs argument indicates how many items from the top of the stack
+ * should be "returned" by the yield.
+ *
+ * This function is to be called from other API callbacks to resume
+ * execution of the Lua script. For example, if a Lua script invokes a
+ * function to perform I/O, the function would start the async I/O and yield
+ * from the script. Eventually, the I/O completion callback executes, which
+ * would call dlua_pcall_yieldable_resume() to continue executing the Lua
+ * script with the supplied arguments.
+ *
+ * Note: The actual execution doesn't resume immediately. Rather, it is
+ * scheduled to start in the near future via a timeout.
+ */
+void dlua_pcall_yieldable_resume(lua_State *L, int nargs);
+#endif
+
+/* initialize/free script's thread table */
+void dlua_init_thread_table(struct dlua_script *script);
+void dlua_free_thread_table(struct dlua_script *script);
+
+/* thread local storage (TLS) getters & setters */
+void dlua_tls_set_ptr(lua_State *L, const char *name, void *ptr);
+void *dlua_tls_get_ptr(lua_State *L, const char *name);
+void dlua_tls_set_int(lua_State *L, const char *name, lua_Integer i);
+lua_Integer dlua_tls_get_int(lua_State *L, const char *name);
+
+/* free a thread local storage (TLS) value */
+void dlua_tls_clear(lua_State *L, const char *name);
+
+#endif
diff --git a/src/lib-lua/dlua-script.c b/src/lib-lua/dlua-script.c
new file mode 100644
index 0000000..48fe286
--- /dev/null
+++ b/src/lib-lua/dlua-script.c
@@ -0,0 +1,453 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "istream.h"
+#include "sha1.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "eacces-error.h"
+#include "ioloop.h"
+#include "dlua-script-private.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+/* the registry entry with a pointer to struct dlua_script */
+#define LUA_SCRIPT_REGISTRY_KEY "DLUA_SCRIPT"
+
+#define LUA_SCRIPT_INIT_FN "script_init"
+#define LUA_SCRIPT_DEINIT_FN "script_deinit"
+
+struct event_category event_category_lua = {
+ .name = "lua",
+};
+
+static struct dlua_script *dlua_scripts = NULL;
+
+static int
+dlua_script_create_finish(struct dlua_script *script, const char **error_r);
+
+static void *dlua_alloc(void *ctx, void *ptr, size_t osize, size_t nsize)
+{
+ struct dlua_script *script =
+ (struct dlua_script*)ctx;
+
+ if (nsize == 0) {
+ p_free(script->pool, ptr);
+ return NULL;
+ } else {
+ return p_realloc(script->pool, ptr, osize, nsize);
+ }
+}
+
+static const char *dlua_reader(lua_State *L, void *ctx, size_t *size_r)
+{
+ struct dlua_script *script =
+ (struct dlua_script*)ctx;
+ const unsigned char *data;
+ i_stream_skip(script->in, script->last_read);
+ if (i_stream_read_more(script->in, &data, size_r) == -1 &&
+ script->in->stream_errno != 0) {
+ luaL_error(L, "read(%s) failed: %s",
+ script->filename,
+ i_stream_get_error(script->in));
+ *size_r = 0;
+ return NULL;
+ }
+ script->last_read = *size_r;
+ return (const char*)data;
+}
+
+struct dlua_script *dlua_script_from_state(lua_State *L)
+{
+ struct dlua_script *script;
+
+ /* get light pointer from globals */
+ lua_pushstring(L, LUA_SCRIPT_REGISTRY_KEY);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ script = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ i_assert(script != NULL);
+
+ return script;
+}
+
+int dlua_pcall(lua_State *L, const char *func_name, int nargs, int nresults,
+ const char **error_r)
+{
+ /* record the stack position */
+ int ret = 0, debugh_idx, top = lua_gettop(L) - nargs;
+
+ lua_getglobal(L, func_name);
+
+ if (lua_isfunction(L, -1)) {
+ /* stack on entry
+ args
+ func <-- top
+ */
+ /* move func name before arguments */
+ lua_insert(L, -(nargs + 1));
+ /* stack now
+ func
+ args <-- top
+ */
+ lua_getglobal(L, "debug");
+ lua_getfield(L, -1, "traceback");
+ lua_replace(L, -2);
+ /* stack now
+ func
+ args
+ traceback <-- top
+ */
+ /* move error handler before func name */
+ lua_insert(L, -(nargs + 2));
+ /* stack now
+ traceback
+ func
+ args <-- top
+ */
+ /* record where traceback is so it's easy to get rid of even
+ if LUA_MULTRET is used. */
+ debugh_idx = lua_gettop(L) - nargs - 1;
+ ret = lua_pcall(L, nargs, nresults, -(nargs + 2));
+ if (ret != LUA_OK) {
+ *error_r = t_strdup_printf("lua_pcall(%s, %d, %d) failed: %s",
+ func_name, nargs, nresults,
+ lua_tostring(L, -1));
+ /* Remove error and debug handler */
+ lua_pop(L, 2);
+ ret = -1;
+ } else {
+ /* remove debug handler from known location */
+ lua_remove(L, debugh_idx);
+ if (nresults == LUA_MULTRET)
+ nresults = lua_gettop(L) - top;
+ ret = nresults;
+ }
+ } else {
+ /* ensure stack is clean, remove function and arguments */
+ lua_pop(L, nargs + 1);
+ *error_r = t_strdup_printf("'%s' is not a function",
+ func_name);
+ ret = -1;
+ }
+#ifdef DEBUG
+ if ((ret == -1 && lua_gettop(L) != top) ||
+ (ret >= 0 &&
+ lua_gettop(L) != top + ret)) {
+ i_debug("LUA STACK UNCLEAN BEGIN for %s", func_name);
+ dlua_dump_stack(L);
+ i_debug("LUA STACK UNCLEAN END");
+ }
+#endif
+ /* enforce that stack is clean after call */
+ if (ret == -1)
+ i_assert(lua_gettop(L) == top);
+ else
+ i_assert(ret >= 0 && lua_gettop(L) == top + ret);
+ return ret;
+}
+
+static void dlua_call_deinit_function(struct dlua_script *script)
+{
+ const char *error;
+ if (!dlua_script_has_function(script, LUA_SCRIPT_DEINIT_FN))
+ return;
+ if (dlua_pcall(script->L, LUA_SCRIPT_DEINIT_FN, 0, 0, &error) < 0)
+ e_error(script->event, LUA_SCRIPT_DEINIT_FN"() failed: %s",
+ error);
+}
+
+int dlua_script_init(struct dlua_script *script, const char **error_r)
+{
+ if (script->init)
+ return 0;
+ script->init = TRUE;
+
+ if (dlua_script_create_finish(script, error_r) < 0)
+ return -1;
+
+ /* lets not fail on missing function... */
+ if (!dlua_script_has_function(script, LUA_SCRIPT_INIT_FN))
+ return 0;
+
+ int ret = 0;
+
+ if (dlua_pcall(script->L, LUA_SCRIPT_INIT_FN, 0, 1, error_r) < 0)
+ return -1;
+
+ if (lua_isinteger(script->L, -1)) {
+ ret = lua_tointeger(script->L, -1);
+ if (ret != 0) {
+ *error_r = "Script init failed";
+ ret = -1;
+ }
+ } else {
+ *error_r = LUA_SCRIPT_INIT_FN"() returned non-number";
+ ret = -1;
+ }
+
+ lua_pop(script->L, 1);
+
+ i_assert(lua_gettop(script->L) == 0);
+ return ret;
+}
+
+static int dlua_atpanic(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *error = lua_tostring(script->L, -1);
+ i_panic("Lua script '%s': %s", script->filename, error);
+}
+
+static struct dlua_script *dlua_create_script(const char *name,
+ struct event *event_parent)
+{
+ pool_t pool = pool_allocfree_create(t_strdup_printf("lua script %s", name));
+ struct dlua_script *script = p_new(pool, struct dlua_script, 1);
+ script->pool = pool;
+ script->filename = p_strdup(pool, name);
+ /* lua API says that lua_newstate will return NULL only if it's out of
+ memory. this cannot really happen with our allocator as it will
+ call i_fatal_status anyways if it runs out of memory */
+ script->L = lua_newstate(dlua_alloc, script);
+ i_assert(script->L != NULL);
+ script->ref = 1;
+ lua_atpanic(script->L, dlua_atpanic);
+ luaL_openlibs(script->L);
+ script->event = event_create(event_parent);
+ event_add_str(script->event, "script", script->filename);
+ event_add_category(script->event, &event_category_lua);
+
+ dlua_init_thread_table(script);
+
+ DLLIST_PREPEND(&dlua_scripts, script);
+ return script;
+}
+
+static int dlua_run_script(struct dlua_script *script, const char **error_r)
+{
+ /* put the error handler before script being called */
+ lua_getglobal(script->L, "debug");
+ lua_getfield(script->L, -1, "traceback");
+ lua_replace(script->L, -2);
+ lua_insert(script->L, -2);
+
+ /* we don't want anything to be returned here */
+ /* stack before lua_pcall
+ debug.traceback
+ loaded script as function
+ */
+ int err = lua_pcall(script->L, 0, 0, 1);
+ if (err != LUA_OK) {
+ *error_r = t_strdup_printf("lua_pcall(%s) failed: %s",
+ script->filename,
+ lua_tostring(script->L, -1));
+ /* pop error and debug handler */
+ lua_pop(script->L, 2);
+ err = -1;
+ } else {
+ /* pop debug handler */
+ lua_pop(script->L, 1);
+ }
+ return err;
+}
+
+static int
+dlua_script_create_finish(struct dlua_script *script, const char **error_r)
+{
+ /* store pointer as light data to registry before calling the script */
+ lua_pushstring(script->L, LUA_SCRIPT_REGISTRY_KEY);
+ lua_pushlightuserdata(script->L, script);
+ lua_settable(script->L, LUA_REGISTRYINDEX);
+
+ if (dlua_run_script(script, error_r) < 0)
+ return -1;
+ i_assert(lua_gettop(script->L) == 0);
+ return 0;
+}
+
+int dlua_script_create_string(const char *str, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+ unsigned char scripthash[SHA1_RESULTLEN];
+ const char *fn;
+
+ *script_r = NULL;
+ sha1_get_digest(str, strlen(str), scripthash);
+ fn = binary_to_hex(scripthash, sizeof(scripthash));
+
+ script = dlua_create_script(fn, event_parent);
+ if (luaL_loadstring(script->L, str) == LUA_OK) {
+ *script_r = script;
+ return 0;
+ }
+ *error_r = t_strdup_printf("lua_load(<string>) failed: %s",
+ lua_tostring(script->L, -1));
+ lua_pop(script->L, 1);
+ dlua_script_unref(&script);
+ return -1;
+}
+
+int dlua_script_create_file(const char *file, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+
+ /* lua reports file access errors poorly */
+ if (access(file, O_RDONLY) < 0) {
+ if (errno == EACCES)
+ *error_r = eacces_error_get("access", file);
+ else
+ *error_r = t_strdup_printf("access(%s) failed: %m",
+ file);
+ return -1;
+ }
+
+ script = dlua_create_script(file, event_parent);
+ if (luaL_loadfile(script->L, file) != LUA_OK) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ file, lua_tostring(script->L, -1));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ *script_r = script;
+ return 0;
+}
+
+int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+ const char *filename = i_stream_get_name(is);
+
+ i_assert(filename != NULL && *filename != '\0');
+
+ script = dlua_create_script(filename, event_parent);
+ script->in = is;
+ script->filename = p_strdup(script->pool, filename);
+ if (lua_load(script->L, dlua_reader, script, filename, 0) != LUA_OK) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ filename, lua_tostring(script->L, -1));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ *script_r = script;
+ return 0;
+}
+
+static void dlua_script_destroy(struct dlua_script *script)
+{
+ dlua_call_deinit_function(script);
+
+ /* close all threads */
+ dlua_free_thread_table(script);
+
+ /* close base lua */
+ lua_close(script->L);
+
+ /* remove from list */
+ DLLIST_REMOVE(&dlua_scripts, script);
+
+ event_unref(&script->event);
+ /* then just release memory */
+ pool_unref(&script->pool);
+}
+
+void dlua_script_ref(struct dlua_script *script)
+{
+ i_assert(script->ref > 0);
+ script->ref++;
+}
+
+void dlua_script_unref(struct dlua_script **_script)
+{
+ struct dlua_script *script = *_script;
+ *_script = NULL;
+
+ if (script == NULL) return;
+
+ i_assert(script->ref > 0);
+ if (--script->ref > 0)
+ return;
+
+ dlua_script_destroy(script);
+}
+
+bool dlua_script_has_function(struct dlua_script *script, const char *fn)
+{
+ i_assert(script != NULL);
+ lua_getglobal(script->L, "_G");
+ lua_pushstring(script->L, fn);
+ lua_rawget(script->L, -2);
+ bool ret = lua_isfunction(script->L, -1);
+ lua_pop(script->L, 2);
+ return ret;
+}
+
+void dlua_set_members(lua_State *L, const struct dlua_table_values *values,
+ int idx)
+{
+ i_assert(L != NULL);
+ i_assert(lua_istable(L, idx));
+ while(values->name != NULL) {
+ switch(values->type) {
+ case DLUA_TABLE_VALUE_STRING:
+ lua_pushstring(L, values->v.s);
+ break;
+ case DLUA_TABLE_VALUE_INTEGER:
+ lua_pushnumber(L, values->v.i);
+ break;
+ case DLUA_TABLE_VALUE_DOUBLE:
+ lua_pushnumber(L, values->v.d);
+ break;
+ case DLUA_TABLE_VALUE_BOOLEAN:
+ lua_pushboolean(L, values->v.b);
+ break;
+ case DLUA_TABLE_VALUE_NULL:
+ lua_pushnil(L);
+ break;
+ default:
+ i_unreached();
+ }
+ lua_setfield(L, idx - 1, values->name);
+ values++;
+ }
+}
+
+void dlua_dump_stack(lua_State *L)
+{
+ /* get everything in stack */
+ int top = lua_gettop(L);
+ for (int i = 1; i <= top; i++) T_BEGIN { /* repeat for each level */
+ int t = lua_type(L, i);
+ string_t *line = t_str_new(32);
+ str_printfa(line, "#%d: ", i);
+ switch (t) {
+ case LUA_TSTRING: /* strings */
+ str_printfa(line, "`%s'", lua_tostring(L, i));
+ break;
+ case LUA_TBOOLEAN: /* booleans */
+ str_printfa(line, "`%s'", lua_toboolean(L, i) ? "true" : "false");
+ break;
+ case LUA_TNUMBER: /* numbers */
+ str_printfa(line, "%g", lua_tonumber(L, i));
+ break;
+ default: /* other values */
+ str_printfa(line, "%s", lua_typename(L, t));
+ break;
+ }
+ i_debug("%s", str_c(line));
+ } T_END;
+}
+
+/* assorted wrappers */
+void dlua_register(struct dlua_script *script, const char *name,
+ lua_CFunction f)
+{
+ lua_register(script->L, name, f);
+}
diff --git a/src/lib-lua/dlua-script.h b/src/lib-lua/dlua-script.h
new file mode 100644
index 0000000..015475b
--- /dev/null
+++ b/src/lib-lua/dlua-script.h
@@ -0,0 +1,28 @@
+#ifndef LUA_SCRIPT_H
+#define LUA_SCRIPT_H 1
+
+struct dlua_script;
+
+/* Parse and load a lua script, without actually running it. */
+int dlua_script_create_string(const char *str, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r);
+int dlua_script_create_file(const char *file, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r);
+/* Remember to set script name using i_stream_set_name */
+int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r);
+
+/* run dlua_script_init function */
+int dlua_script_init(struct dlua_script *script, const char **error_r);
+
+/* Reference lua script */
+void dlua_script_ref(struct dlua_script *script);
+
+/* Unreference a script, calls deinit and frees when no more
+ references exist */
+void dlua_script_unref(struct dlua_script **_script);
+
+/* see if particular function is registered */
+bool dlua_script_has_function(struct dlua_script *script, const char *fn);
+
+#endif
diff --git a/src/lib-lua/dlua-table.c b/src/lib-lua/dlua-table.c
new file mode 100644
index 0000000..8467a7e
--- /dev/null
+++ b/src/lib-lua/dlua-table.c
@@ -0,0 +1,301 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+/*
+ * Adjust the index by the specified delta.
+ *
+ * In a couple of places we need to adjust the passed in index to reflect
+ * additional items pushed onto the stack. We cannot blindly adjust the
+ * index because the index could be one of three things and only one of them
+ * is supposed to be ajusted:
+ *
+ * 1. negative number: index relative to top of stack, adjust
+ * 2. positive number: absolute index, don't adjust
+ * 3. special registry index: don't adjust
+ */
+static inline int adj(int idx, int delta)
+{
+ if ((idx == LUA_REGISTRYINDEX) || (idx > 0))
+ return idx;
+ else
+ return idx - delta;
+}
+
+/*
+ * Pushes onto the stack the value t[k], where t is the value at the given
+ * index and k is the value at the top of the stack. Unlike lua_gettable(),
+ * this function checks the type of the retreived value against the passed
+ * in type. [-1,+0..1,e]
+ *
+ * Return value:
+ * -1 = incompatible type
+ * 0 = nil or none
+ * 1 = found
+ */
+static int dlua_table_get(lua_State *L, int idx, int type)
+{
+ /* can only work with tables */
+ if (!lua_istable(L, idx))
+ return -1;
+
+ lua_gettable(L, idx);
+
+ /* check if the field was there */
+ if (lua_isnoneornil(L, -1)) {
+ lua_pop(L, 1);
+ return 0;
+ }
+
+ /* check that the field is the expected type */
+ if (lua_type(L, -1) != type) {
+ lua_pop(L, 1);
+ return -1;
+ }
+
+ return 1;
+}
+
+/* Get by string name [-0,+1,e] */
+int dlua_table_get_by_str(lua_State *L, int idx, int type, const char *field)
+{
+ /* push the key */
+ lua_pushstring(L, field);
+
+ return dlua_table_get(L, adj(idx, 1), type);
+}
+
+/* Get by int name [-0,+1,e] */
+int dlua_table_get_by_int(lua_State *L, int idx, int type, lua_Integer field)
+{
+ /* push the key */
+ lua_pushinteger(L, field);
+
+ return dlua_table_get(L, adj(idx, 1), type);
+}
+
+/* Get by thread [-0,+1,e] */
+int dlua_table_get_by_thread(lua_State *L, int idx, int type)
+{
+ /* push the key */
+ lua_pushthread(L);
+
+ return dlua_table_get(L, adj(idx, 1), type);
+}
+
+/* generate a set of functions to access fields of an integral data type */
+#define GET_INTTYPE(fxn, ctype, minval, maxval, unsigned_check) \
+int fxn##_by_str(lua_State *L, int idx, const char *field, \
+ ctype *value_r) \
+{ \
+ lua_Integer tmp; \
+ int ret; \
+ \
+ ret = dlua_table_get_luainteger_by_str(L, idx, field, &tmp); \
+ if (ret < 1) \
+ return ret; \
+ \
+ if (unsigned_check) { \
+ if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \
+ return -1; \
+ } else { \
+ if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \
+ return -1; \
+ } \
+ \
+ *value_r = (ctype) tmp; \
+ \
+ return 1; \
+} \
+int fxn##_by_int(lua_State *L, int idx, lua_Integer field, \
+ ctype *value_r) \
+{ \
+ lua_Integer tmp; \
+ int ret; \
+ \
+ ret = dlua_table_get_luainteger_by_int(L, idx, field, &tmp); \
+ if (ret < 1) \
+ return ret; \
+ \
+ if (unsigned_check) { \
+ if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \
+ return -1; \
+ } else { \
+ if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \
+ return -1; \
+ } \
+ \
+ *value_r = (ctype) tmp; \
+ \
+ return 1; \
+} \
+int fxn##_by_thread(lua_State *L, int idx, ctype *value_r) \
+{ \
+ lua_Integer tmp; \
+ int ret; \
+ \
+ ret = dlua_table_get_luainteger_by_thread(L, idx, &tmp); \
+ if (ret < 1) \
+ return ret; \
+ \
+ if (unsigned_check) { \
+ if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \
+ return -1; \
+ } else { \
+ if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \
+ return -1; \
+ } \
+ \
+ *value_r = (ctype) tmp; \
+ \
+ return 1; \
+}
+
+/* generate a set of functions to access fields of a binary data type */
+#define GET_DATAPTR(fxn) \
+int fxn##_by_str(lua_State *L, int idx, const char *field, \
+ const unsigned char **value_r, size_t *len_r) \
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_str(L, idx, LUA_TSTRING, field); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+} \
+int fxn##_by_int(lua_State *L, int idx, lua_Integer field, \
+ const unsigned char **value_r, size_t *len_r) \
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_int(L, idx, LUA_TSTRING, field); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+} \
+int fxn##_by_thread(lua_State *L, int idx, \
+ const unsigned char **value_r, size_t *len_r) \
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_thread(L, idx, LUA_TSTRING); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+}
+
+/* generate a set of functions to access fields of a generic-ish type */
+#define GET_GENERIC(fxn, ctype, ltype, cvt) \
+int fxn##_by_str(lua_State *L, int idx, const char *field, ctype *value_r)\
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_str(L, idx, (ltype), field); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = cvt(L, -1); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+} \
+int fxn##_by_int(lua_State *L, int idx, lua_Integer field, ctype *value_r)\
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_int(L, idx, (ltype), field); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = cvt(L, -1); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+} \
+int fxn##_by_thread(lua_State *L, int idx, ctype *value_r) \
+{ \
+ int ret; \
+ \
+ ret = dlua_table_get_by_thread(L, idx, (ltype)); \
+ if (ret < 1) \
+ return ret; \
+ \
+ *value_r = cvt(L, -1); \
+ lua_pop(L, 1); \
+ \
+ return 1; \
+}
+
+GET_INTTYPE(dlua_table_get_int, int, INT_MIN, INT_MAX, FALSE);
+GET_INTTYPE(dlua_table_get_intmax, intmax_t, INTMAX_MIN, INTMAX_MAX, FALSE);
+GET_INTTYPE(dlua_table_get_uint, unsigned int, 0, UINT_MAX, TRUE);
+GET_INTTYPE(dlua_table_get_uintmax, uintmax_t, 0, UINTMAX_MAX, TRUE);
+
+/* we need to use lua_tointegerx which takes an extra argument */
+int dlua_table_get_luainteger_by_str(lua_State *L, int idx, const char *field,
+ lua_Integer *value_r)
+{
+ int isnum;
+ int ret;
+
+ ret = dlua_table_get_by_str(L, idx, LUA_TNUMBER, field);
+ if (ret < 1)
+ return ret;
+
+ *value_r = lua_tointegerx(L, -1, &isnum);
+ lua_pop(L, 1);
+
+ return (isnum == 1) ? 1 : -1;
+}
+
+/* we need to use lua_tointegerx which takes an extra argument */
+int dlua_table_get_luainteger_by_int(lua_State *L, int idx, lua_Integer field,
+ lua_Integer *value_r)
+{
+ int isnum;
+ int ret;
+
+ ret = dlua_table_get_by_int(L, idx, LUA_TNUMBER, field);
+ if (ret < 1)
+ return ret;
+
+ *value_r = lua_tointegerx(L, -1, &isnum);
+ lua_pop(L, 1);
+
+ return (isnum == 1) ? 1 : -1;
+}
+
+/* we need to use lua_tointegerx which takes an extra argument */
+int dlua_table_get_luainteger_by_thread(lua_State *L, int idx,
+ lua_Integer *value_r)
+{
+ int isnum;
+ int ret;
+
+ ret = dlua_table_get_by_thread(L, idx, LUA_TNUMBER);
+ if (ret < 1)
+ return ret;
+
+ *value_r = lua_tointegerx(L, -1, &isnum);
+ lua_pop(L, 1);
+
+ return (isnum == 1) ? 1 : -1;
+}
+
+GET_GENERIC(dlua_table_get_number, lua_Number, LUA_TNUMBER, lua_tonumber);
+GET_GENERIC(dlua_table_get_bool, bool, LUA_TBOOLEAN, lua_toboolean);
+GET_GENERIC(dlua_table_get_string, const char *, LUA_TSTRING, lua_tostring);
+GET_DATAPTR(dlua_table_get_data);
diff --git a/src/lib-lua/dlua-thread.c b/src/lib-lua/dlua-thread.c
new file mode 100644
index 0000000..a0441f8
--- /dev/null
+++ b/src/lib-lua/dlua-thread.c
@@ -0,0 +1,276 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+/*
+ * dlua support for threads & thread local storage (TLS)
+ *
+ * The following code keeps a table in (global) registry. This table is
+ * indexed by the thread objects, each mapping to another table used to hold
+ * thread local storage. That is:
+ *
+ * registry[thread] = {} -- threads table
+ * registry[thread]["foo"] = ... -- TLS value for "foo"
+ *
+ * This serves two purposes:
+ *
+ * (1) It provides TLS.
+ * (2) It acts as a reference to the thread object, preventing it from
+ * being garbage collected.
+ *
+ * The table is allocated during struct dlua_script's creation and is freed
+ * during the scripts destruction. Any lua threads created using
+ * dlua_script_new_thread() will automatically get added to this table.
+ */
+
+/* the registry entry with a table with all the lua threads */
+#define LUA_THREAD_REGISTRY_KEY "DLUA_THREADS"
+
+static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L);
+static void get_tls_table(lua_State *L);
+
+void dlua_init_thread_table(struct dlua_script *script)
+{
+ lua_newtable(script->L);
+ lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+ /*
+ * Note that we are *not* adding the main lua state since it is not
+ * a thread. This implies that it will not have any TLS.
+ */
+}
+
+static void warn_about_leaked_threads(struct dlua_script *script)
+{
+ lua_State *L = script->L;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+ i_assert(lua_type(L, -1) == LUA_TTABLE);
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ /* stack: table, thread, per-thread table */
+
+ /* check the key */
+ if (lua_type(L, -2) != LUA_TTHREAD) {
+ e_error(script->event, "Unexpected %s key in thread table",
+ lua_typename(L, lua_type(L, -2)));
+ } else {
+ e_error(script->event, "Lua thread %p leaked", lua_tothread(L, -2));
+ }
+
+ /* check the value */
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ e_error(script->event, "Unexpected %s value in thread table",
+ lua_typename(L, lua_type(L, -1)));
+ } else {
+ warn_about_tls_leaks(script, L);
+ }
+
+ /* pop the value for lua_next() */
+ lua_pop(L, 1);
+ }
+
+ lua_pop(L, 1);
+}
+
+void dlua_free_thread_table(struct dlua_script *script)
+{
+ /* all threads should have been closed by now */
+ warn_about_leaked_threads(script);
+
+ /* set the thread table to nil - letting GC clean everything up */
+ lua_pushnil(script->L);
+ lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+}
+
+lua_State *dlua_script_new_thread(struct dlua_script *script)
+{
+ lua_State *thread;
+
+ /* get the threads table */
+ lua_getfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+ /* allocate a new thread */
+ thread = lua_newthread(script->L);
+ i_assert(thread != NULL);
+
+ /* allocate new TLS table */
+ lua_newtable(script->L);
+
+ /* stack: threads-table, thread, TLS-table (top) */
+
+ /* threads-table[thread] = TLS-table */
+ lua_settable(script->L, -3);
+
+ /* pop threads table */
+ lua_pop(script->L, 1);
+
+ return thread;
+}
+
+static void log_tls_leak(struct dlua_script *script, lua_State *L, bool full)
+{
+ const char *name = NULL;
+
+ /* stack: TLS key, TLS value (top) */
+
+ if (full) {
+ lua_getmetatable(L, -1);
+
+ if (dlua_table_get_string_by_str(L, -1, "__name", &name) < 0)
+ name = NULL;
+
+ lua_pop(L, 1); /* pop the metatable */
+ }
+
+ e_error(script->event, "Lua TLS data in %p thread leaked: key '%s', "
+ "value %s %p (%s)", L, lua_tostring(L, -2),
+ full ? "userdata" : "lightuserdata",
+ lua_touserdata(L, -1), (name != NULL) ? name : "<no name>");
+}
+
+static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L)
+{
+ i_assert(lua_type(L, -1) == LUA_TTABLE);
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ /* stack: table, key, value (top) */
+
+ switch (lua_type(L, -1)) {
+ case LUA_TNIL:
+ case LUA_TNUMBER:
+ case LUA_TBOOLEAN:
+ case LUA_TSTRING:
+ case LUA_TFUNCTION:
+ case LUA_TTHREAD:
+ /* these are trivially freed by the Lua GC */
+ break;
+ case LUA_TTABLE:
+ /* recurse into the table */
+ warn_about_tls_leaks(script, L);
+ break;
+ case LUA_TUSERDATA:
+ log_tls_leak(script, L, TRUE);
+ break;
+ case LUA_TLIGHTUSERDATA:
+ log_tls_leak(script, L, FALSE);
+ break;
+ }
+
+ /* pop the value for lua_next() */
+ lua_pop(L, 1);
+ }
+}
+
+void dlua_script_close_thread(struct dlua_script *script, lua_State **_L)
+{
+ if (*_L == NULL)
+ return;
+
+ /* log any TLS userdata leaks */
+ get_tls_table(*_L);
+ warn_about_tls_leaks(script, *_L);
+ lua_pop(*_L, 1);
+
+ /* get the threads table */
+ lua_getfield(*_L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+ /* push the thread to destroy */
+ i_assert(lua_pushthread(*_L) != 1);
+
+ lua_pushnil(*_L);
+
+ /* stack: threads-table, thread, nil (top) */
+
+ /*
+ * threads-table[thread] = nil
+ *
+ * This assignment (1) frees all TLS for the thread, and (2) removes
+ * the reference to the thread saving it from GC.
+ */
+ lua_settable(*_L, -3);
+
+ /* pop threads table */
+ lua_pop(*_L, 1);
+
+ *_L = NULL;
+}
+
+/* get the current thread's TLS table */
+static void get_tls_table(lua_State *L)
+{
+ int ret;
+
+ /* get the threads table */
+ ret = dlua_table_get_by_str(L, LUA_REGISTRYINDEX, LUA_TTABLE,
+ LUA_THREAD_REGISTRY_KEY);
+ if (ret < 1)
+ luaL_error(L, "lua threads table is %s",
+ (ret == 0) ? "missing" : "not a table");
+
+ /* get the TLS-table */
+ ret = dlua_table_get_by_thread(L, -1, LUA_TTABLE);
+ if (ret < 1)
+ luaL_error(L, "lua TLS table for thread %p is not a table", L);
+
+ /* stack: threads-table, TLS-table (top) */
+
+ /* remove threads-table from stack */
+ lua_remove(L, -2);
+}
+
+void dlua_tls_set_ptr(lua_State *L, const char *name, void *ptr)
+{
+ get_tls_table(L);
+ lua_pushlightuserdata(L, ptr);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}
+
+void *dlua_tls_get_ptr(lua_State *L, const char *name)
+{
+ void *ptr;
+
+ get_tls_table(L);
+ lua_getfield(L, -1, name);
+
+ ptr = lua_touserdata(L, -1);
+
+ lua_pop(L, 2);
+
+ return ptr;
+}
+
+void dlua_tls_set_int(lua_State *L, const char *name, lua_Integer i)
+{
+ get_tls_table(L);
+ lua_pushinteger(L, i);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}
+
+lua_Integer dlua_tls_get_int(lua_State *L, const char *name)
+{
+ lua_Integer i;
+
+ get_tls_table(L);
+ lua_getfield(L, -1, name);
+
+ i = lua_tointeger(L, -1);
+
+ lua_pop(L, 2);
+
+ return i;
+}
+
+void dlua_tls_clear(lua_State *L, const char *name)
+{
+ get_tls_table(L);
+ lua_pushnil(L);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}
diff --git a/src/lib-lua/dlua-wrapper.h b/src/lib-lua/dlua-wrapper.h
new file mode 100644
index 0000000..3a9f459
--- /dev/null
+++ b/src/lib-lua/dlua-wrapper.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef DLUA_WRAPPER_H
+#define DLUA_WRAPPER_H
+
+/*
+ * The following macro generates everything necessary to wrap a C structure
+ * and easily push it onto Lua stacks as well as check that a value on the
+ * stack is of this type.
+ *
+ * To generate the necessary API, simply use the macro in your .c file. The
+ * arguments consist of:
+ *
+ * <typename> = the name for the structure to use in generated symbols
+ * <type> = the exposed structure's C type
+ * <putref> = the function to remove a reference from the C structure,
+ * called from the automatically generated __gc metamethod
+ * <extra_fxns_arg> = a C array of luaL_Reg structs passed to luaL_setfuncs
+ * to add Lua methods to the type
+ *
+ * For example, to expose struct timespec with a tostring method, one would
+ * use the following in a .c file:
+ *
+ * // struct timespec isn't refcounted
+ * static inline void timespec_putref(struct timespec *ts)
+ * {
+ * }
+ *
+ * static int timespec_tostring(lua_State *L);
+ *
+ * static const luaL_Reg timespec_fxns[] = {
+ * { "__tostring", timespec_tostring },
+ * { NULL, NULL },
+ * };
+ *
+ * DLUA_WRAP_C_DATA(timespec, struct timespec, timespec_putref, timespec_fxns)
+ *
+ * static int timespec_tostring(lua_State *L)
+ * {
+ * struct timespec *ts;
+ *
+ * ts = xlua_timespec_getptr(L, -1, NULL);
+ *
+ * lua_pushfstring(L, "%d.%09ld", ts->tv_sec, ts->tv_nsec);
+ *
+ * return 1;
+ * }
+ *
+ *
+ * The two functions making up the exposed structure API are:
+ *
+ * static void xlua_push<typename>(lua_State *, <type> *, bool);
+ * static inline <type> *xlua_<typename>_getptr(lua_State *, int, bool *);
+ *
+ * The first pushes the supplied pointer onto the Lua stack, while the
+ * second returns the previously pushed C pointer (or generates a Lua error
+ * if there is a type mismatch).
+ *
+ * The push function tracks the passed in bool argument alongside the C
+ * pointer itself. The getptr function fills in the bool pointer (if not
+ * NULL) with the pushed bool value. While this bool isn't used directly by
+ * the generated code and therefore it can be used for anything, the
+ * intention is to allow the API consumers to mark certain pointers as
+ * "read-only" to prevent Lua scripts from attempting to mutate them. This
+ * allows one to push const pointers while "notifying" the methods that
+ * mutation of any of the members is undefined behavior.
+ *
+ * Also note that the functions are static. That is, they are intended to
+ * only be used in the file where they are generated since they are somewhat
+ * low-level functions. If some public form of a push/get function is
+ * desired, it is up to the API consumer to write wrappers around these and
+ * expose them to the rest of the codebase.
+ *
+ * Revisiting the struct timespec example above, the generated API would
+ * be:
+ *
+ * static void xlua_pushtimespec(lua_State *, struct timespec *, bool);
+ * static inline struct timespec *xlua_timespec_getptr(lua_State *, int,
+ * bool *);
+ */
+#define DLUA_WRAP_C_DATA(typename, type, putref, extra_fxns_arg) \
+struct lua_wrapper_##typename { \
+ type *ptr; \
+ bool ro; \
+}; \
+ \
+static inline type *xlua_##typename##_getptr(lua_State *state, int idx, \
+ bool *ro_r) \
+{ \
+ struct lua_wrapper_##typename *wrapper; \
+ \
+ wrapper = luaL_checkudata(state, idx, #type); \
+ \
+ if (ro_r != NULL) \
+ *ro_r = wrapper->ro; \
+ \
+ return wrapper->ptr; \
+} \
+ \
+static int xlua_wrapper_##typename##_gc(lua_State *state) \
+{ \
+ putref(xlua_##typename##_getptr(state, -1, NULL)); \
+ \
+ return 0; \
+} \
+ \
+static const luaL_Reg provided_##typename##_fxns[] = { \
+ { "__gc", xlua_wrapper_##typename##_gc }, \
+ { NULL, NULL }, \
+}; \
+ \
+/* push [-0,+1,e] */ \
+static void xlua_push##typename(lua_State *state, type *ptr, bool ro) \
+{ \
+ struct lua_wrapper_##typename *wrapper; \
+ \
+ if (ptr == NULL) { \
+ lua_pushnil(state); \
+ return; \
+ } \
+ \
+ wrapper = lua_newuserdata(state, sizeof(struct lua_wrapper_##typename)); \
+ i_assert(wrapper != NULL); \
+ \
+ wrapper->ptr = (ptr); \
+ wrapper->ro = ro; \
+ \
+ /* get the current metatable */ \
+ luaL_getmetatable(state, #type); \
+ if (lua_type(state, -1) != LUA_TTABLE) { \
+ /* initialize a new metatable */ \
+ const luaL_Reg *extra_fxns = (extra_fxns_arg); \
+ lua_CFunction index; \
+ \
+ lua_pop(state, 1); \
+ luaL_newmetatable(state, #type); \
+ luaL_setfuncs(state, provided_##typename##_fxns, 0); \
+ \
+ index = NULL; \
+ if (extra_fxns != NULL) { \
+ unsigned i; \
+ \
+ luaL_setfuncs(state, extra_fxns, 0); \
+ \
+ for (i = 0; extra_fxns[i].name != NULL; i++) { \
+ if (strcmp(extra_fxns[i].name, \
+ "__index") == 0) { \
+ index = extra_fxns[i].func; \
+ break; \
+ } \
+ } \
+ } \
+ \
+ if (index == NULL) { \
+ /* set __index == metatable */ \
+ lua_pushliteral(state, "__index"); \
+ lua_pushvalue(state, -2); /* dup the table */ \
+ lua_settable(state, -3); \
+ } \
+ } \
+ \
+ /* set the metatable */ \
+ lua_setmetatable(state, -2); \
+}
+
+#endif
diff --git a/src/lib-lua/test-dict-lua.c b/src/lib-lua/test-dict-lua.c
new file mode 100644
index 0000000..66d958e
--- /dev/null
+++ b/src/lib-lua/test-dict-lua.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dlua-script-private.h"
+#include "dict-private.h"
+#include "dict-lua.h"
+#include "test-common.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+static void test_dict_register(void)
+{
+ dict_driver_register(&dict_driver_file);
+}
+
+static void test_dict_finished(lua_State *L, struct ioloop *ioloop, int res)
+{
+ if (res < 0)
+ i_error("%s", lua_tostring(L, -1));
+ io_loop_stop(ioloop);
+}
+
+static void test_dict_lua(void)
+{
+ static const char *luascript =
+"function test_dict(dict)\n"
+" local trans = dict:transaction_begin()\n"
+" trans:set('shared/testkey', 'testvalue')\n"
+" trans:set('shared/testkey2', 'testvalue2')\n"
+" trans:commit()\n"
+"\n"
+" assert(dict:lookup('shared/testkey')[1] == 'testvalue')\n"
+" assert(dict:lookup('shared/testkey2')[1] == 'testvalue2')\n"
+"\n"
+" local key, values\n"
+" local table = {}\n"
+" for key, values in dict:iterate('shared/', 0) do\n"
+" assert(#values == 1)\n"
+" table[key] = values[1]\n"
+" end\n"
+" assert(table['shared/testkey'] == 'testvalue')\n"
+" assert(table['shared/testkey2'] == 'testvalue2')\n"
+"\n"
+" trans = dict:transaction_begin()\n"
+" trans:set_timestamp({['tv_sec'] = 1631278269, ['tv_nsec'] = 999999999})\n"
+" trans:set('shared/testkey', 'updated')\n"
+" trans:unset('shared/testkey2')\n"
+" trans:commit()\n"
+"\n"
+" assert(dict:lookup('shared/testkey')[1] == 'updated')\n"
+" assert(dict:lookup('shared/testkey2') == nil)\n"
+"end\n";
+ struct dict_settings set = {
+ .base_dir = NULL,
+ };
+ struct dict *dict;
+ const char *error;
+
+ test_begin("dict lua");
+ struct ioloop *ioloop = io_loop_create();
+ i_unlink_if_exists(".test.dict");
+ if (dict_init("file:.test.dict", &set, &dict, &error) < 0)
+ i_fatal("dict_init(.test.dict) failed: %s", error);
+
+ struct dlua_script *script;
+ if (dlua_script_create_string(luascript, &script, NULL, &error) < 0)
+ i_fatal("dlua_script_create_string() failed: %s", error);
+ dlua_dovecot_register(script);
+ if (dlua_script_init(script, &error) < 0)
+ i_fatal("dlua_script_init() failed: %s", error);
+
+ lua_State *thread = dlua_script_new_thread(script);
+ dlua_push_dict(thread, dict);
+ if (dlua_pcall_yieldable(thread, "test_dict", 1, test_dict_finished,
+ ioloop, &error) < 0)
+ i_fatal("dlua_pcall() failed: %s", error);
+ io_loop_run(ioloop);
+ i_assert(lua_gettop(thread) == 0);
+ dlua_script_close_thread(script, &thread);
+
+ dlua_script_unref(&script);
+ dict_deinit(&dict);
+ io_loop_destroy(&ioloop);
+
+ i_unlink(".test.dict");
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_dict_register,
+ test_dict_lua,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-lua/test-lua.c b/src/lib-lua/test-lua.c
new file mode 100644
index 0000000..ff5abe8
--- /dev/null
+++ b/src/lib-lua/test-lua.c
@@ -0,0 +1,473 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "dlua-script-private.h"
+
+#include <math.h>
+
+static int dlua_test_assert(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *what = luaL_checkstring(script->L, 1);
+ bool cond = lua_toboolean(script->L, 2);
+
+ if (!cond) {
+ lua_Debug ar;
+ i_assert(lua_getinfo(L, ">Sl", &ar) == 0);
+ test_assert_failed(what, ar.source, ar.currentline);
+ }
+
+ return 0;
+}
+
+#define GENERATE_GETTERS(name, ctype) \
+static void check_table_get_##name##_ok(struct dlua_script *script, \
+ int idx, ctype expected_value, \
+ const char *str_key, \
+ lua_Integer int_key) \
+{ \
+ ctype value; \
+ int ret; \
+ \
+ /* check string key */ \
+ ret = dlua_table_get_##name##_by_str(script->L, idx, \
+ str_key, &value); \
+ test_assert(ret == 1); \
+ test_assert(value == expected_value); \
+ \
+ /* check int key */ \
+ ret = dlua_table_get_##name##_by_int(script->L, idx, \
+ int_key, &value); \
+ test_assert(ret == 1); \
+ test_assert(value == expected_value); \
+} \
+static void check_table_get_##name##_err(struct dlua_script *script, \
+ int idx, int expected_ret, \
+ const char *str_key, \
+ lua_Integer int_key) \
+{ \
+ ctype value; \
+ int ret; \
+ \
+ /* check string key */ \
+ ret = dlua_table_get_##name##_by_str(script->L, idx, \
+ str_key, &value); \
+ test_assert(ret == expected_ret); \
+ \
+ /* check int key */ \
+ ret = dlua_table_get_##name##_by_int(script->L, idx, \
+ int_key, &value); \
+ test_assert(ret == expected_ret); \
+}
+
+GENERATE_GETTERS(luainteger, lua_Integer);
+GENERATE_GETTERS(int, int);
+GENERATE_GETTERS(intmax, intmax_t);
+GENERATE_GETTERS(uint, unsigned int);
+GENERATE_GETTERS(uintmax, uintmax_t);
+GENERATE_GETTERS(number, lua_Number);
+GENERATE_GETTERS(bool, bool);
+
+/* the string comparison requires us to open-code this */
+static void check_table_get_string_ok(struct dlua_script *script,
+ int idx, const char *expected_value,
+ const char *str_key,
+ lua_Integer int_key)
+{
+ const char *value;
+ int ret;
+
+ /* check string key */
+ ret = dlua_table_get_string_by_str(script->L, idx,
+ str_key, &value);
+ test_assert(ret == 1);
+ test_assert_strcmp(value, expected_value);
+
+ /* check int key */
+ ret = dlua_table_get_string_by_int(script->L, idx,
+ int_key, &value);
+ test_assert(ret == 1);
+ test_assert_strcmp(value, expected_value);
+
+ /* TODO: check thread key, which is difficult */
+}
+
+/* the string comparison of the _ok function requires us to open-code this */
+static void check_table_get_string_err(struct dlua_script *script,
+ int idx, int expected_ret,
+ const char *str_key,
+ lua_Integer int_key)
+{
+ const char *value;
+ int ret;
+
+ /* check string key */
+ ret = dlua_table_get_string_by_str(script->L, idx,
+ str_key, &value);
+ test_assert(ret == expected_ret);
+
+ /* check int key */
+ ret = dlua_table_get_string_by_int(script->L, idx,
+ int_key, &value);
+ test_assert(ret == expected_ret);
+
+ /* TODO: check thread key, which is difficult */
+}
+
+static void check_table_missing(struct dlua_script *script, int idx,
+ const char *str_key,
+ lua_Integer int_key)
+{
+ check_table_get_luainteger_err(script, idx, 0, str_key, int_key);
+ check_table_get_int_err(script, idx, 0, str_key, int_key);
+ check_table_get_intmax_err(script, idx, 0, str_key, int_key);
+ check_table_get_uint_err(script, idx, 0, str_key, int_key);
+ check_table_get_uintmax_err(script, idx, 0, str_key, int_key);
+ check_table_get_number_err(script, idx, 0, str_key, int_key);
+ check_table_get_bool_err(script, idx, 0, str_key, int_key);
+ check_table_get_string_err(script, idx, 0, str_key, int_key);
+}
+
+static void test_lua(void)
+{
+ static const char *luascript =
+"function script_init(req)\n"
+" dovecot.i_debug(\"lua script init called\")\n"
+" local e = dovecot.event()\n"
+" e:log_debug(\"lua script init called from event\")\n"
+" return 0\n"
+"end\n"
+"function lua_function()\n"
+"end\n"
+"function lua_test_flags()\n"
+" local flag = 0\n"
+" flag = dovecot.set_flag(flag, 2)\n"
+" flag = dovecot.set_flag(flag, 4)\n"
+" flag = dovecot.set_flag(flag, 16)\n"
+" test_assert(\"has_flag(flag, 8) == false\", dovecot.has_flag(flag, 8) == false)\n"
+" test_assert(\"has_flag(flag, 4) == true\", dovecot.has_flag(flag, 4) == true)\n"
+" flag = dovecot.clear_flag(flag, 4)\n"
+" test_assert(\"has_flag(flag, 4) == false\", dovecot.has_flag(flag, 4) == false)\n"
+" test_assert(\"has_flag(flag, 16) == true\", dovecot.has_flag(flag, 16) == true)\n"
+"end\n"
+"function lua_test_get_table()\n"
+" t = {}\n"
+" -- zero\n"
+" t[\"zero\"] = 0\n"
+" t[-2] = 0\n"
+" -- small positive values\n"
+" t[\"small-positive-int\"] = 1\n"
+" t[-1] = 1\n"
+" -- small negative values\n"
+" t[\"small-negative-int\"] = -5\n"
+" t[0] = -5\n"
+" -- large positive float\n"
+" t[\"large-positive-int\"] = 2^48\n"
+" t[1] = 2^48\n"
+" -- large negative float\n"
+" t[\"large-negative-int\"] = -2^48\n"
+" t[2] = -2^48\n"
+" -- small float\n"
+" t[\"small-float\"] = 1.525\n"
+" t[3] = 1.525\n"
+" -- bool: true\n"
+" t[\"bool-true\"] = true\n"
+" t[4] = true\n"
+" -- bool: false\n"
+" t[\"bool-false\"] = false\n"
+" t[5] = false\n"
+" -- string\n"
+" t[\"str\"] = \"string\"\n"
+" t[6] = \"string\"\n"
+" return t\n"
+"end\n";
+
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("lua script");
+
+ test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0);
+ if (error != NULL)
+ i_fatal("dlua_script_init failed: %s", error);
+
+ dlua_dovecot_register(script);
+
+ dlua_register(script, "test_assert", dlua_test_assert);
+
+ test_assert(dlua_script_init(script, &error) == 0);
+ test_assert(dlua_script_has_function(script, "lua_function"));
+
+ test_assert(dlua_pcall(script->L, "lua_test_flags", 0, 0, &error) == 0);
+
+ lua_getglobal(script->L, "lua_test_get_table");
+ test_assert(lua_pcall(script->L, 0, 1, 0) == 0);
+
+ /*
+ * Check table getters
+ */
+
+ /* lua_Integer */
+ check_table_get_luainteger_ok(script, -1, 0, "zero", -2);
+ check_table_get_luainteger_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_luainteger_ok(script, -1, -5, "small-negative-int", 0);
+ check_table_get_luainteger_ok(script, -1, 1ll<<48, "large-positive-int", 1);
+ check_table_get_luainteger_ok(script, -1, -(1ll<<48), "large-negative-int", 2);
+ check_table_get_luainteger_err(script, -1, -1, "small-float", 3);
+ check_table_get_luainteger_err(script, -1, -1, "bool-true", 4);
+ check_table_get_luainteger_err(script, -1, -1, "bool-false", 5);
+ check_table_get_luainteger_err(script, -1, -1, "str", 6);
+
+ /* int */
+ check_table_get_int_ok(script, -1, 0, "zero", -2);
+ check_table_get_int_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_int_ok(script, -1, -5, "small-negative-int", 0);
+ check_table_get_int_err(script, -1, -1, "large-positive-int", 1);
+ check_table_get_int_err(script, -1, -1, "large-negative-int", 2);
+ check_table_get_int_err(script, -1, -1, "small-float", 3);
+ check_table_get_int_err(script, -1, -1, "bool-true", 4);
+ check_table_get_int_err(script, -1, -1, "bool-false", 5);
+ check_table_get_int_err(script, -1, -1, "str", 6);
+
+ /* intmax_t */
+ check_table_get_intmax_ok(script, -1, 0, "zero", -2);
+ check_table_get_intmax_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_intmax_ok(script, -1, -5, "small-negative-int", 0);
+ check_table_get_intmax_ok(script, -1, 1ll<<48, "large-positive-int", 1);
+ check_table_get_intmax_ok(script, -1, -(1ll<<48), "large-negative-int", 2);
+ check_table_get_intmax_err(script, -1, -1, "small-float", 3);
+ check_table_get_intmax_err(script, -1, -1, "bool-true", 4);
+ check_table_get_intmax_err(script, -1, -1, "bool-false", 5);
+ check_table_get_intmax_err(script, -1, -1, "str", 6);
+
+ /* unsigned int */
+ check_table_get_uint_ok(script, -1, 0, "zero", -2);
+ check_table_get_uint_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_uint_err(script, -1, -1, "small-negative-int", 0);
+ check_table_get_uint_err(script, -1, -1, "large-positive-int", 1);
+ check_table_get_uint_err(script, -1, -1, "large-negative-int", 2);
+ check_table_get_uint_err(script, -1, -1, "small-float", 3);
+ check_table_get_uint_err(script, -1, -1, "bool-true", 4);
+ check_table_get_uint_err(script, -1, -1, "bool-false", 5);
+ check_table_get_uint_err(script, -1, -1, "str", 6);
+
+ /* uintmax_t */
+ check_table_get_uintmax_ok(script, -1, 0, "zero", -2);
+ check_table_get_uintmax_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_uintmax_err(script, -1, -1, "small-negative-int", 0);
+ check_table_get_uintmax_ok(script, -1, 1ll<<48, "large-positive-int", 1);
+ check_table_get_uintmax_err(script, -1, -1, "large-negative-int", 2);
+ check_table_get_uintmax_err(script, -1, -1, "small-float", 3);
+ check_table_get_uintmax_err(script, -1, -1, "bool-true", 4);
+ check_table_get_uintmax_err(script, -1, -1, "bool-false", 5);
+ check_table_get_uintmax_err(script, -1, -1, "str", 6);
+
+ /* lua_Number */
+ check_table_get_number_ok(script, -1, 0, "zero", -2);
+ check_table_get_number_ok(script, -1, 1, "small-positive-int", -1);
+ check_table_get_number_ok(script, -1, -5, "small-negative-int", 0);
+ check_table_get_number_ok(script, -1, 1ll<<48, "large-positive-int", 1);
+ check_table_get_number_ok(script, -1, -(1ll<<48), "large-negative-int", 2);
+ check_table_get_number_ok(script, -1, 1.525, "small-float", 3);
+ check_table_get_number_err(script, -1, -1, "bool-true", 4);
+ check_table_get_number_err(script, -1, -1, "bool-false", 5);
+ check_table_get_number_err(script, -1, -1, "str", 6);
+
+ /* bool */
+ check_table_get_bool_err(script, -1, -1, "zero", -2);
+ check_table_get_bool_err(script, -1, -1, "small-positive-int", -1);
+ check_table_get_bool_err(script, -1, -1, "small-negative-int", 0);
+ check_table_get_bool_err(script, -1, -1, "large-positive-int", 1);
+ check_table_get_bool_err(script, -1, -1, "large-negative-int", 2);
+ check_table_get_bool_err(script, -1, -1, "small-float", 3);
+ check_table_get_bool_ok(script, -1, TRUE, "bool-true", 4);
+ check_table_get_bool_ok(script, -1, FALSE, "bool-false", 5);
+ check_table_get_bool_err(script, -1, -1, "str", 6);
+
+ /* const char * */
+ check_table_get_string_err(script, -1, -1, "zero", -2);
+ check_table_get_string_err(script, -1, -1, "small-positive-int", -1);
+ check_table_get_string_err(script, -1, -1, "small-negative-int", 0);
+ check_table_get_string_err(script, -1, -1, "large-positive-int", 1);
+ check_table_get_string_err(script, -1, -1, "large-negative-int", 2);
+ check_table_get_string_err(script, -1, -1, "small-float", 3);
+ check_table_get_string_err(script, -1, -1, "bool-true", 4);
+ check_table_get_string_err(script, -1, -1, "bool-false", 5);
+ check_table_get_string_ok(script, -1, "string", "str", 6);
+
+ check_table_missing(script, -1, "missing", -10);
+
+ lua_pop(script->L, 1);
+
+ dlua_script_unref(&script);
+
+ test_end();
+}
+
+static void test_tls(void)
+{
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+ lua_State *L1, *L2;
+
+ test_begin("lua thread local storage");
+
+ test_assert(dlua_script_create_string("", &script, NULL, &error) == 0);
+ if (error != NULL)
+ i_fatal("dlua_script_init failed: %s", error);
+
+ L1 = dlua_script_new_thread(script);
+ L2 = dlua_script_new_thread(script);
+
+ dlua_tls_set_ptr(L1, "ptr", L1);
+ test_assert(dlua_tls_get_ptr(L1, "ptr") == L1);
+ test_assert(dlua_tls_get_ptr(L2, "ptr") == NULL);
+ test_assert(dlua_tls_get_int(L1, "int") == 0);
+ test_assert(dlua_tls_get_int(L2, "int") == 0);
+
+ dlua_tls_set_ptr(L2, "ptr", L2);
+ test_assert(dlua_tls_get_ptr(L1, "ptr") == L1);
+ test_assert(dlua_tls_get_ptr(L2, "ptr") == L2);
+ test_assert(dlua_tls_get_int(L1, "int") == 0);
+ test_assert(dlua_tls_get_int(L2, "int") == 0);
+
+ dlua_tls_set_int(L1, "int", 1);
+ dlua_tls_set_int(L2, "int", 2);
+ test_assert(dlua_tls_get_int(L1, "int") == 1);
+ test_assert(dlua_tls_get_int(L2, "int") == 2);
+
+ dlua_tls_clear(L1, "ptr");
+ test_assert(dlua_tls_get_ptr(L1, "ptr") == NULL);
+ test_assert(dlua_tls_get_ptr(L2, "ptr") == L2);
+ test_assert(dlua_tls_get_int(L1, "int") == 1);
+ test_assert(dlua_tls_get_int(L2, "int") == 2);
+
+ dlua_script_close_thread(script, &L1);
+
+ test_assert(dlua_tls_get_ptr(L2, "ptr") == L2);
+ test_assert(dlua_tls_get_int(L2, "int") == 2);
+
+ dlua_tls_clear(L2, "ptr");
+ dlua_script_close_thread(script, &L2);
+
+ dlua_script_unref(&script);
+
+ test_end();
+}
+
+/* check lua_tointegerx against top-of-stack item */
+static void check_tointegerx_compat(lua_State *L, bool expected_isnum,
+ bool expected_isint,
+ lua_Integer expected_value)
+{
+ lua_Integer value;
+ int isnum;
+
+ value = lua_tointegerx(L, -1, &isnum);
+ test_assert((isnum == 1) == expected_isnum);
+
+ if (isnum == 1)
+ test_assert(value == expected_value);
+
+ test_assert(lua_isinteger(L, -1) == expected_isint);
+
+ lua_pop(L, 1);
+}
+
+static void test_compat_tointegerx_and_isinteger(void)
+{
+ static const struct {
+ const char *input;
+ lua_Integer output;
+ bool isnum;
+ } str_tests[] = {
+ { "-1", -1, TRUE },
+ { "0", 0, TRUE },
+ { "1", 1, TRUE },
+ { "-2147483648", -2147483648, TRUE },
+ { "2147483647", 2147483647, TRUE },
+ { "0x123", 0x123, TRUE },
+ { "0123", 123, TRUE }, /* NB: lua doesn't use leading zero for octal */
+ { "0xabcdef", 0xabcdef, TRUE },
+ { "0xabcdefg", 0, FALSE },
+ { "abc", 0, FALSE },
+ { "1.525", 0, FALSE },
+ { "52.51", 0, FALSE },
+ };
+ static const struct {
+ lua_Number input;
+ lua_Integer output;
+ bool isnum;
+ } num_tests[] = {
+ { -1, -1, TRUE },
+ { 0, 0, TRUE },
+ { 1, 1, TRUE },
+ { INT_MIN, INT_MIN, TRUE },
+ { INT_MAX, INT_MAX, TRUE },
+ { 1.525, 0, FALSE },
+ { 52.51, 0, FALSE },
+ { NAN, 0, FALSE },
+ { +INFINITY, 0, FALSE },
+ { -INFINITY, 0, FALSE },
+ };
+ static const struct {
+ lua_Integer input;
+ lua_Integer output;
+ } int_tests[] = {
+ { -1, -1 },
+ { 0, 0 },
+ { 1, 1 },
+ { INT_MIN, INT_MIN },
+ { INT_MAX, INT_MAX },
+ };
+ struct dlua_script *script;
+ const char *error;
+ size_t i;
+
+ test_begin("lua compat tostringx/isinteger");
+
+ test_assert(dlua_script_create_string("", &script, NULL, &error) == 0);
+
+ for (i = 0; i < N_ELEMENTS(str_tests); i++) {
+ lua_pushstring(script->L, str_tests[i].input);
+ check_tointegerx_compat(script->L, str_tests[i].isnum, FALSE,
+ str_tests[i].output);
+ }
+
+ for (i = 0; i < N_ELEMENTS(num_tests); i++) {
+ bool isint;
+
+ /* See lua_isinteger() comment in dlua-compat.h */
+#if LUA_VERSION_NUM >= 503
+ isint = FALSE;
+#else
+ isint = num_tests[i].isnum;
+#endif
+
+ lua_pushnumber(script->L, num_tests[i].input);
+ check_tointegerx_compat(script->L, num_tests[i].isnum,
+ isint,
+ num_tests[i].output);
+ }
+
+ for (i = 0; i < N_ELEMENTS(int_tests); i++) {
+ lua_pushinteger(script->L, int_tests[i].input);
+ check_tointegerx_compat(script->L, TRUE, TRUE,
+ int_tests[i].output);
+ }
+
+ dlua_script_unref(&script);
+
+ test_end();
+}
+
+int main(void) {
+ void (*tests[])(void) = {
+ test_lua,
+ test_tls,
+ test_compat_tointegerx_and_isinteger,
+ NULL
+ };
+
+ return test_run(tests);
+}