src/lib-index/Makefile
src/lib-lda/Makefile
src/lib-ldap/Makefile
+src/lib-lua/Makefile
src/lib-mail/Makefile
src/lib-master/Makefile
src/lib-ntlm/Makefile
if HAVE_LDAP
LIB_LDAP=lib-ldap
endif
+if HAVE_LUA
+LIB_LUA=lib-lua
+endif
LIBDOVECOT_SUBDIRS = \
lib-test \
lib-dict-extra \
lib-dovecot \
$(LIB_LDAP) \
+ $(LIB_LUA) \
lib-fts \
lib-imap-client \
lib-imap-urlauth \
--- /dev/null
+noinst_LTLIBRARIES = libdovecot-lua.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ $(LUA_CFLAGS)
+
+libdovecot_lua_la_SOURCES = \
+ dlua-script.c \
+ dlua-dovecot.c \
+ dlua-compat.c
+libdovecot_lua_la_DEPENDENCIES = ../lib-dovecot/libdovecot.la
+libdovecot_lua_la_LIBADD = ../lib-dovecot/libdovecot.la $(LUA_LIBS)
+
+headers = \
+ dlua-script.h \
+ dlua-script-private.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = test-lua
+
+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)
+test_lua_DEPENDENCIES = $(test_libs)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
--- /dev/null
+/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
+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);
+}
+
+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
--- /dev/null
+/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+#define LUA_SCRIPT_DOVECOT "dovecot"
+
+static int dlua_i_debug(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *msg = luaL_checkstring(script->L, 1);
+ i_debug("%s", msg);
+ return 0;
+}
+
+static int dlua_i_info(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *msg = luaL_checkstring(script->L, 1);
+ i_info("%s", msg);
+ return 0;
+}
+
+static int dlua_i_warning(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *msg = luaL_checkstring(script->L, 1);
+ i_warning("%s", msg);
+ return 0;
+}
+
+static int dlua_i_error(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *msg = luaL_checkstring(script->L, 1);
+ i_error("%s", msg);
+ 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 },
+ { NULL, NULL }
+};
+
+void dlua_getdovecot(struct dlua_script *script)
+{
+ lua_getglobal(script->L, LUA_SCRIPT_DOVECOT);
+}
+
+void dlua_dovecot_register(struct dlua_script *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);
+}
--- /dev/null
+#ifndef LUA_SCRIPT_PRIVATE_H
+#define LUA_SCRIPT_PRIVATE_H 1
+
+#include "dlua-script.h"
+#include "lualib.h"
+#include "lauxlib.h"
+
+#if !defined(LUA_VERSION_NUM)
+#define lua_setfield(L, i, k) (lua_pushstring(L, k), lua_settable(L, i))
+#define lua_getref(L, ref) lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
+#define luaL_unref(L, ref) luaL_unref(L, LUA_REGISTRYINDEX, ref);
+#endif
+
+#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
+#define luaL_newmetatable(L, tn) \
+ (luaL_newmetatable(L, tn) ? (lua_pushstring(L, tn), lua_setfield(L, -2, "__name"), 1) : 0)
+#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))
+#define lua_load(L, r, s, fn, m) lua_load(L, r, s, fn)
+void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);
+void luaL_setmetatable (lua_State *L, const char *tname);
+#endif
+
+/* 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 DLUA_TABLE_STRING(n, s) { .name = n, .type = DLUA_TABLE_VALUE_STRING, .v.s = s }
+#define DLUA_TABLE_INTEGER(n, i) { .name = n, .type = DLUA_TABLE_VALUE_INTEGER, .v.i = i }
+#define DLUA_TABLE_ENUM(n) { .name = #n, .type = DLUA_TABLE_VALUE_INTEGER, .v.i = n }
+#define DLUA_TABLE_DOUBLE(n, d) { .name = n, .type = DLUA_TABLE_VALUE_DOUBLE, .v.d = d }
+#define DLUA_TABLE_BOOLEAN(n, b) { .name = n, .type = DLUA_TABLE_VALUE_BOOLEAN, .v.b = b }
+#define DLUA_TABLE_NULL(n, s) { .name = n, .type = DLUA_TABLE_VALUE_NULL }
+#define DLUA_TABLE_END { .name = NULL }
+
+struct dlua_script {
+ struct dlua_script *prev,*next;
+ pool_t pool;
+
+ lua_State *L;
+
+ const char *filename;
+ struct istream *in;
+ ssize_t last_read;
+
+ unsigned 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;
+};
+
+/* 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_getdovecot(struct dlua_script *script);
+
+/* assign values to table on idx */
+void dlua_setmembers(struct dlua_script *script,
+ const struct dlua_table_values *values, int idx);
+
+
+#endif
--- /dev/null
+/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "istream.h"
+#include "sha1.h"
+#include "hex-binary.h"
+#include "eacces-error.h"
+#include "dlua-script-private.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#define LUA_SCRIPT_INIT_FN "script_init"
+#define LUA_SCRIPT_DEINIT_FN "script_deinit"
+
+static struct dlua_script *dlua_scripts = NULL;
+
+static const char *dlua_errstr(int err)
+{
+ switch(err) {
+#ifdef LUA_OK
+ case LUA_OK:
+ return "ok";
+#endif
+ case LUA_YIELD:
+ return "yield";
+ case LUA_ERRRUN:
+ return "runtime error";
+ case LUA_ERRSYNTAX:
+ return "syntax error";
+ case LUA_ERRMEM:
+ return "out of memory";
+#ifdef LUA_ERRGCMM
+ case LUA_ERRGCMM:
+ return "gc management error";
+#endif
+ case LUA_ERRERR:
+ return "error while handling error";
+#ifdef LUA_ERRFILE
+ case LUA_ERRFILE:
+ return "error loading file";
+#endif
+ default:
+ return "unknown error";
+ }
+}
+
+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, t_strdup_printf("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;
+ for(script = dlua_scripts; script != NULL; script = script->next)
+ if (script->L == L)
+ return script;
+ i_unreached();
+}
+
+int dlua_script_init(struct dlua_script *script, const char **error_r)
+{
+ int ret = 0;
+
+ if (script->init)
+ return 0;
+ script->init = TRUE;
+
+ /* see if there is a symbol for init */
+ lua_getglobal(script->L, LUA_SCRIPT_INIT_FN);
+
+ if (lua_isfunction(script->L, -1)) {
+ ret = lua_pcall(script->L, 0, 1, 0);
+ if (ret != 0) {
+ *error_r = t_strdup_printf("lua_pcall("LUA_SCRIPT_INIT_FN") failed: %s",
+ lua_tostring(script->L, -1));
+ ret = -1;
+ } else if (lua_isnumber(script->L, -1)) {
+ ret = lua_tointeger(script->L, -1);
+ if (ret != 0)
+ *error_r = "Script init failed";
+ } else {
+ *error_r = t_strdup_printf(LUA_SCRIPT_INIT_FN "() returned non-number");
+ ret = -1;
+ }
+ }
+
+ lua_pop(script->L, 1);
+ return ret;
+}
+
+static struct dlua_script *dlua_create_script(const char *name)
+{
+ 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;
+ luaL_openlibs(script->L);
+
+ return script;
+}
+
+static int dlua_run_script(struct dlua_script *script, const char **error_r)
+{
+ int err = lua_pcall(script->L, 0, 0, 0);
+ if (err != 0) {
+ *error_r = t_strdup_printf("lua_pcall(%s) failed: %s",
+ script->filename,
+ lua_tostring(script->L, -1));
+ lua_pop(script->L,1);
+ return -1;
+ }
+ return 0;
+}
+
+static struct dlua_script *
+dlua_script_find_previous_script(const char *filename)
+{
+ struct dlua_script *script;
+ for(script = dlua_scripts; script != NULL; script = script->next)
+ if (strcmp(script->filename, filename)==0)
+ return script;
+ return NULL;
+}
+
+static int
+dlua_script_create_finish(struct dlua_script *script, struct dlua_script **script_r,
+ const char **error_r)
+{
+ if (dlua_run_script(script, error_r) < 0) {
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ DLLIST_PREPEND(&dlua_scripts, script);
+
+ *script_r = script;
+
+ return 0;
+}
+
+int dlua_script_create_string(const char *str, struct dlua_script **script_r,
+ const char **error_r)
+{
+ struct dlua_script *script;
+ int err;
+ 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));
+
+ if ((script = dlua_script_find_previous_script(fn)) != NULL) {
+ dlua_script_ref(script);
+ *script_r = script;
+ return 0;
+ }
+
+ script = dlua_create_script(fn);
+ if ((err = luaL_loadstring(script->L, str)) != 0) {
+ *error_r = t_strdup_printf("lua_load(<string>) failed: %s",
+ dlua_errstr(err));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ return dlua_script_create_finish(script, script_r, error_r);
+}
+
+int dlua_script_create_file(const char *file, struct dlua_script **script_r,
+ const char **error_r)
+{
+ struct dlua_script *script;
+ int err;
+
+ if ((script = dlua_script_find_previous_script(file)) != NULL) {
+ dlua_script_ref(script);
+ *script_r = script;
+ return 0;
+ }
+
+ /* 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);
+ if ((err = luaL_loadfile(script->L, file)) != 0) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ file, dlua_errstr(err));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ return dlua_script_create_finish(script, script_r, error_r);
+}
+
+int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r,
+ const char **error_r)
+{
+ struct dlua_script *script;
+ const char *filename = i_stream_get_name(is);
+ int err;
+
+ i_assert(filename != NULL && *filename != '\0');
+
+ if ((script = dlua_script_find_previous_script(filename)) != NULL) {
+ dlua_script_ref(script);
+ *script_r = script;
+ return 0;
+ }
+
+ script = dlua_create_script(filename);
+ script->in = is;
+ script->filename = p_strdup(script->pool, filename);
+ if ((err = lua_load(script->L, dlua_reader, script, filename, 0)) < 0) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ filename, dlua_errstr(err));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ return dlua_script_create_finish(script, script_r, error_r);
+}
+
+static void dlua_script_destroy(struct dlua_script *script)
+{
+ /* courtesy call */
+ int ret;
+ /* see if there is a symbol for deinit */
+ lua_getglobal(script->L, LUA_SCRIPT_DEINIT_FN);
+ if (lua_isfunction(script->L, -1)) {
+ ret = lua_pcall(script->L, 0, 0, 0);
+ if (ret != 0) {
+ i_warning("lua_pcall("LUA_SCRIPT_DEINIT_FN") failed: %s",
+ lua_tostring(script->L, -1));
+ lua_pop(script->L, 1);
+ }
+ } else {
+ lua_pop(script->L, 1);
+ }
+ lua_close(script->L);
+ /* 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, fn);
+ bool ret = lua_isfunction(script->L, -1);
+ lua_pop(script->L, 1);
+ return ret;
+}
+
+void dlua_setmembers(struct dlua_script *script,
+ const struct dlua_table_values *values, int idx)
+{
+ i_assert(script != NULL);
+ i_assert(lua_istable(script->L, idx));
+ while(values->name != NULL) {
+ switch(values->type) {
+ case DLUA_TABLE_VALUE_STRING:
+ lua_pushstring(script->L, values->v.s);
+ break;
+ case DLUA_TABLE_VALUE_INTEGER:
+ lua_pushnumber(script->L, values->v.i);
+ break;
+ case DLUA_TABLE_VALUE_DOUBLE:
+ lua_pushnumber(script->L, values->v.d);
+ break;
+ case DLUA_TABLE_VALUE_BOOLEAN:
+ lua_pushboolean(script->L, values->v.b ? 1 : 0);
+ break;
+ case DLUA_TABLE_VALUE_NULL:
+ lua_pushnil(script->L);
+ break;
+ default:
+ i_unreached();
+ }
+ lua_setfield(script->L, idx-1, values->name);
+ values++;
+ }
+}
--- /dev/null
+#ifndef LUA_SCRIPT_H
+#define LUA_SCRIPT_H 1
+
+struct dlua_script;
+
+/* Parse and load a lua script. Will reuse an existing script
+ if found. */
+int dlua_script_create_string(const char *str, struct dlua_script **script_r,
+ const char **error_r);
+int dlua_script_create_file(const char *file, struct dlua_script **script_r,
+ 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,
+ 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
--- /dev/null
+#include "test-lib.h"
+#include "dlua-script-private.h"
+
+static void test_lua(void)
+{
+ static const char *luascript =
+"function script_init(req)\n"
+" dovecot.i_debug(\"lua script init called\")\n"
+" return 0\n"
+"end\n"
+"function lua_function()\n"
+"end\n";
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("lua script");
+
+ test_assert(dlua_script_create_string(luascript, &script, &error) == 0);
+ dlua_dovecot_register(script);
+ test_assert(dlua_script_init(script, &error) == 0);
+ test_assert(dlua_script_has_function(script, "lua_function"));
+
+ dlua_script_unref(&script);
+
+ test_end();
+}
+
+int main(void) {
+ void (*tests[])(void) = {
+ test_lua,
+ NULL
+ };
+
+ return test_run(tests);
+}