]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-lua: Add lua helper library
authorAki Tuomi <aki.tuomi@dovecot.fi>
Sat, 11 Nov 2017 09:55:21 +0000 (11:55 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Fri, 24 Nov 2017 09:02:05 +0000 (11:02 +0200)
Provides a base lua library for lua extensibility

configure.ac
src/Makefile.am
src/lib-lua/Makefile.am [new file with mode: 0644]
src/lib-lua/dlua-compat.c [new file with mode: 0644]
src/lib-lua/dlua-dovecot.c [new file with mode: 0644]
src/lib-lua/dlua-script-private.h [new file with mode: 0644]
src/lib-lua/dlua-script.c [new file with mode: 0644]
src/lib-lua/dlua-script.h [new file with mode: 0644]
src/lib-lua/test-lua.c [new file with mode: 0644]

index 4ee09d8a869296e18f8f25e290f9f35fc2f55b35..b3034cc5a6e7e128b3e470572e25542b23a48cba 100644 (file)
@@ -844,6 +844,7 @@ src/lib-imap-urlauth/Makefile
 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
index e4d602fbd86bd89b4f59a1a092d29e60cc07bbfa..fb8f7b348199acf7deec6bdc2530b3c0ce1fe4b7 100644 (file)
@@ -1,6 +1,9 @@
 if HAVE_LDAP
 LIB_LDAP=lib-ldap
 endif
+if HAVE_LUA
+LIB_LUA=lib-lua
+endif
 
 LIBDOVECOT_SUBDIRS = \
        lib-test \
@@ -29,6 +32,7 @@ SUBDIRS = \
        lib-dict-extra \
        lib-dovecot \
        $(LIB_LDAP) \
+       $(LIB_LUA) \
        lib-fts \
        lib-imap-client \
        lib-imap-urlauth \
diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am
new file mode 100644 (file)
index 0000000..d24475c
--- /dev/null
@@ -0,0 +1,39 @@
+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
diff --git a/src/lib-lua/dlua-compat.c b/src/lib-lua/dlua-compat.c
new file mode 100644 (file)
index 0000000..3b3908f
--- /dev/null
@@ -0,0 +1,25 @@
+/* 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
diff --git a/src/lib-lua/dlua-dovecot.c b/src/lib-lua/dlua-dovecot.c
new file mode 100644 (file)
index 0000000..9f58878
--- /dev/null
@@ -0,0 +1,70 @@
+/* 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);
+}
diff --git a/src/lib-lua/dlua-script-private.h b/src/lib-lua/dlua-script-private.h
new file mode 100644 (file)
index 0000000..6676477
--- /dev/null
@@ -0,0 +1,84 @@
+#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
diff --git a/src/lib-lua/dlua-script.c b/src/lib-lua/dlua-script.c
new file mode 100644 (file)
index 0000000..f949136
--- /dev/null
@@ -0,0 +1,344 @@
+/* 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++;
+       }
+}
diff --git a/src/lib-lua/dlua-script.h b/src/lib-lua/dlua-script.h
new file mode 100644 (file)
index 0000000..8e53f2f
--- /dev/null
@@ -0,0 +1,29 @@
+#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
diff --git a/src/lib-lua/test-lua.c b/src/lib-lua/test-lua.c
new file mode 100644 (file)
index 0000000..c962d4b
--- /dev/null
@@ -0,0 +1,35 @@
+#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);
+}