From 9698cd24356147d8a5471260062ea7e10e692fdf Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 8 Nov 2017 15:42:32 +0200 Subject: [PATCH] auth: Add lua passdb/userdb support --- src/auth/Makefile.am | 28 +- src/auth/auth-request-var-expand.h | 2 + src/auth/db-lua.c | 692 +++++++++++++++++++++++++++++ src/auth/db-lua.h | 31 ++ src/auth/passdb-lua.c | 175 ++++++++ src/auth/passdb.c | 6 + src/auth/test-auth.h | 4 + src/auth/test-lua.c | 36 ++ src/auth/test-main.c | 3 + src/auth/test-mock.c | 80 +++- src/auth/userdb-lua.c | 125 ++++++ src/auth/userdb.c | 6 + src/lib-lua/dlua-script-private.h | 2 +- 13 files changed, 1186 insertions(+), 4 deletions(-) create mode 100644 src/auth/db-lua.c create mode 100644 src/auth/db-lua.h create mode 100644 src/auth/passdb-lua.c create mode 100644 src/auth/test-lua.c create mode 100644 src/auth/userdb-lua.c diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index b2c6a7f4b9..9ab626e8e1 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -12,9 +12,14 @@ if LDAP_PLUGIN LDAP_LIB = libauthdb_ldap.la endif +if AUTH_LUA_PLUGIN +LUA_LIB = libauthdb_lua.la +endif + auth_module_LTLIBRARIES = \ $(GSSAPI_LIB) \ $(LDAP_LIB) \ + $(LUA_LIB) \ libauthdb_imap.la pkglibexecdir = $(libexecdir)/dovecot @@ -36,9 +41,11 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-oauth2 \ -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-lua \ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ + $(LUA_CFLAGS) \ $(AUTH_CFLAGS) auth_LDFLAGS = -export-dynamic @@ -65,12 +72,17 @@ auth_libs = \ $(LIBDOVECOT_SQL) \ $(LIBSODIUM_LIBS) +if !AUTH_LUA_PLUGIN +auth_libs += ../lib-lua/libdovecot-lua.la +endif + auth_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) auth_LDADD = $(auth_libs) $(LIBDOVECOT) $(AUTH_LIBS) $(BINARY_LDFLAGS) auth_DEPENDENCIES = $(auth_libs) $(LIBDOVECOT_DEPS) auth_SOURCES = main.c ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c +lua_sources = db-lua.c passdb-lua.c userdb-lua.c libauth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) libauth_la_SOURCES = \ @@ -142,7 +154,8 @@ libauth_la_SOURCES = \ userdb-vpopmail.c \ userdb-sql.c \ userdb-template.c \ - $(ldap_sources) + $(ldap_sources) \ + $(lua_sources) headers = \ auth.h \ @@ -197,6 +210,13 @@ libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD libauthdb_ldap_la_SOURCES = $(ldap_sources) endif +if AUTH_LUA_PLUGIN +libauthdb_lua_la_LDFLAGS = -module -avoid-version +libauthdb_lua_la_LIBADD = ../lib-lua/libdovecot-lua.la $(LUA_LIBS) +libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD +libauthdb_lua_la_SOURCES = $(lua_sources) +endif + libauthdb_imap_la_LDFLAGS = -module -avoid-version libauthdb_imap_la_LIBADD = \ ../lib-imap-client/libimap_client.la \ @@ -232,7 +252,7 @@ test_programs = \ noinst_PROGRAMS = $(test_programs) -noinst_HEADERS = test-auth.h crypt-blowfish.h +noinst_HEADERS = test-auth.h crypt-blowfish.h db-lua.h test_libs = \ ../lib-dovecot/libdovecot.la @@ -263,6 +283,10 @@ test_auth_SOURCES = \ test-db-dict.c \ test-mock.c \ test-main.c +if !AUTH_LUA_PLUGIN +test_auth_SOURCES += test-lua.c +endif + test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) diff --git a/src/auth/auth-request-var-expand.h b/src/auth/auth-request-var-expand.h index 5d450d05f6..70535a3376 100644 --- a/src/auth/auth-request-var-expand.h +++ b/src/auth/auth-request-var-expand.h @@ -12,6 +12,8 @@ auth_request_escape_func_t(const char *string, extern const struct var_expand_table auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT+1]; +extern const struct var_expand_func_table auth_request_var_funcs_table[]; + const struct var_expand_table * auth_request_get_var_expand_table(const struct auth_request *auth_request, auth_request_escape_func_t *escape_func) diff --git a/src/auth/db-lua.c b/src/auth/db-lua.c new file mode 100644 index 0000000000..3039561ccc --- /dev/null +++ b/src/auth/db-lua.c @@ -0,0 +1,692 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) + +#include "llist.h" +#include "istream.h" +#include "array.h" +#include "sha1.h" +#include "hex-binary.h" +#include "auth.h" +#include "passdb.h" +#include "userdb.h" +#include "auth-request.h" +#include "userdb-template.h" +#include "passdb-template.h" +#include "password-scheme.h" +#include "auth-request-var-expand.h" + +#define AUTH_LUA_PASSDB_LOOKUP "auth_passdb_lookup" +#define AUTH_LUA_USERDB_LOOKUP "auth_userdb_lookup" +#define AUTH_LUA_USERDB_ITERATE "auth_userdb_iterate" + +#define AUTH_LUA_DOVECOT_AUTH "dovecot_auth" +#define AUTH_LUA_AUTH_REQUEST "auth_request*" + +#include "db-lua.h" +#include "dlua-script-private.h" + +struct auth_lua_userdb_iterate_context { + struct userdb_iterate_context ctx; + pool_t pool; + unsigned int idx; + ARRAY_TYPE(const_string) users; +}; + +static struct auth_request * +auth_lua_check_auth_request(struct dlua_script *script, int arg); + +static int +auth_request_lua_do_var_expand(struct auth_request *req, const char *tpl, + const char **value_r, const char **error_r) +{ + const char *error; + if (t_auth_request_var_expand(tpl, req, NULL, value_r, &error) < 0) { + *error_r = t_strdup_printf("var_expand(%s) failed: %s", + tpl, error); + return -1; + } + return 0; +} + +static int auth_request_lua_var_expand(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *req = auth_lua_check_auth_request(script, 1); + const char *tpl = luaL_checkstring(L, 2); + const char *value, *error; + + if (auth_request_lua_do_var_expand(req, tpl, &value, &error) < 0) { + luaL_error(L, error); + } else { + lua_pushstring(L, value); + } + return 1; +} + +static const char *const * +auth_request_template_build(struct auth_request *req, const char *str, + unsigned int *count_r) +{ + if (req->userdb_lookup) { + struct userdb_template *tpl = + userdb_template_build(pool_datastack_create(), "lua", str); + if (userdb_template_is_empty(tpl)) + return NULL; + return userdb_template_get_args(tpl, count_r); + } else { + struct passdb_template *tpl = + passdb_template_build(pool_datastack_create(), str); + if (passdb_template_is_empty(tpl)) + return NULL; + return passdb_template_get_args(tpl, count_r); + } +} + +static int auth_request_lua_response_from_template(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *req = auth_lua_check_auth_request(script, 1); + const char *tplstr = luaL_checkstring(L, 2); + const char *error,*expanded; + unsigned int count,i; + + const char *const *fields = auth_request_template_build(req, tplstr, &count); + + /* push new table to stack */ + lua_newtable(L); + + if (fields == NULL) + return 1; + + i_assert((count % 2) == 0); + + for(i = 0; i < count; i+=2) { + const char *key = fields[i]; + const char *value = fields[i+1]; + + if (value == NULL) { + lua_pushnil(L); + } else if (auth_request_lua_do_var_expand(req, value, &expanded, &error) < 0) { + luaL_error(L, error); + break; + } else { + lua_pushstring(L, expanded); + } + lua_setfield(L, -2, key); + } + + /* stack should be left with table */ + return 1; +} + +static int auth_request_lua_log_debug(lua_State *L) +{ + if (global_auth_settings->debug) { + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *request = auth_lua_check_auth_request(script, 1); + const char *msg = luaL_checkstring(L, 2); + auth_request_log_debug(request, AUTH_SUBSYS_DB, "db-lua: %s", msg); + } + return 0; +} + +static int auth_request_lua_log_info(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *request = auth_lua_check_auth_request(script, 1); + const char *msg = luaL_checkstring(L, 2); + auth_request_log_info(request, AUTH_SUBSYS_DB, "db-lua: %s", msg); + return 0; +} + +static int auth_request_lua_log_warning(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *request = auth_lua_check_auth_request(script, 1); + const char *msg = luaL_checkstring(L, 2); + auth_request_log_warning(request, AUTH_SUBSYS_DB, "db-lua: %s", msg); + return 0; +} + +static int auth_request_lua_log_error(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *request = auth_lua_check_auth_request(script, 1); + const char *msg = luaL_checkstring(L, 2); + auth_request_log_error(request, AUTH_SUBSYS_DB, "db-lua: %s", msg); + return 0; +} + +static int auth_request_lua_var_expand_func(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *request = auth_lua_check_auth_request(script, 1); + const char *key = luaL_checkstring(L, 2); + const char *param = luaL_checkstring(L, 3); + const char *value, *error; + /* allows setting parameters for the call */ + const char *tpl = + t_strdup_printf("%%{%s%s%s}", + key, + (*param == ';' ? "" : ":"), + param); + + if (auth_request_lua_do_var_expand(request, tpl, &value, &error) < 0) { + luaL_error(L, error); + return 1; + } + lua_pushstring(L, value); + + return 1; +} + +/* put all methods here */ +static const luaL_Reg auth_request_methods[] ={ + { "var_expand", auth_request_lua_var_expand }, + { "response_from_template", auth_request_lua_response_from_template }, + { "log_debug", auth_request_lua_log_debug }, + { "log_info", auth_request_lua_log_info }, + { "log_warning", auth_request_lua_log_warning }, + {" log_error", auth_request_lua_log_error }, + { NULL, NULL } +}; + +static int auth_request_lua_index(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + struct auth_request *req = auth_lua_check_auth_request(script, 1); + const char *key = luaL_checkstring(L, 2); + lua_pop(L, 1); + + const struct var_expand_table *table = + auth_request_get_var_expand_table(req, NULL); + + /* check if it's variable */ + for(unsigned int i = 0; i < AUTH_REQUEST_VAR_TAB_COUNT; i++) { + if (null_strcmp(table[i].long_key, key) == 0) { + if (table[i].value != NULL) + lua_pushstring(L, table[i].value); + else + lua_pushnil(L); + return 1; + } + } + + /* check if it's a variable function */ + const struct var_expand_func_table *ftable = auth_request_var_funcs_table; + while(ftable->func != NULL) { + if (null_strcmp(key, ftable->key) == 0) { + lua_pushcfunction(L, auth_request_lua_var_expand_func); + return 1; + } + ftable++; + } + + /* check if it's function, then */ + const luaL_Reg *ptr = auth_request_methods; + while(ptr->name != NULL) { + if (null_strcmp(key, ptr->name) == 0) { + lua_pushcfunction(L, ptr->func); + return 1; + } + ptr++; + } + + luaL_error(L, "no such method"); + return 1; +} + +static void auth_lua_push_auth_request(struct dlua_script *script, struct auth_request *req) +{ + /* we want to store a pointer, not the actual request */ + struct auth_request **bp = + (struct auth_request**)lua_newuserdata(script->L, sizeof(req)); + *bp = req; + /* associate metatable */ + luaL_setmetatable(script->L, AUTH_LUA_AUTH_REQUEST); +} + +static struct auth_request * +auth_lua_check_auth_request(struct dlua_script *script, int arg) +{ + struct auth_request **bp = + (struct auth_request**)luaL_checkudata(script->L, arg, AUTH_LUA_AUTH_REQUEST); + return *bp; +} + +static void auth_lua_auth_request_register(struct dlua_script *script) +{ + luaL_newmetatable(script->L, AUTH_LUA_AUTH_REQUEST); + lua_pushcfunction(script->L, auth_request_lua_index); + lua_setfield(script->L, -2, "__index"); + lua_pop(script->L, 1); +} + +static struct dlua_table_values auth_lua_dovecot_auth_values[] = { + DLUA_TABLE_ENUM(PASSDB_RESULT_INTERNAL_FAILURE), + DLUA_TABLE_ENUM(PASSDB_RESULT_SCHEME_NOT_AVAILABLE), + DLUA_TABLE_ENUM(PASSDB_RESULT_USER_UNKNOWN), + DLUA_TABLE_ENUM(PASSDB_RESULT_USER_DISABLED), + DLUA_TABLE_ENUM(PASSDB_RESULT_PASS_EXPIRED), + DLUA_TABLE_ENUM(PASSDB_RESULT_NEXT), + DLUA_TABLE_ENUM(PASSDB_RESULT_PASSWORD_MISMATCH), + DLUA_TABLE_ENUM(PASSDB_RESULT_OK), + + DLUA_TABLE_ENUM(USERDB_RESULT_INTERNAL_FAILURE), + DLUA_TABLE_ENUM(USERDB_RESULT_USER_UNKNOWN), + DLUA_TABLE_ENUM(USERDB_RESULT_OK), + + DLUA_TABLE_END +}; +static luaL_Reg auth_lua_dovecot_auth_methods[] = { + { NULL, NULL } +}; + +static void auth_lua_dovecot_auth_register(struct dlua_script *script) +{ + dlua_getdovecot(script); + /* Create new table for holding values */ + lua_newtable(script->L); + + /* register constants */ + dlua_setmembers(script, auth_lua_dovecot_auth_values, -1); + + /* push new metatable to stack */ + luaL_newmetatable(script->L, AUTH_LUA_DOVECOT_AUTH); + /* this will register functions to the metatable itself */ + luaL_setfuncs(script->L, auth_lua_dovecot_auth_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); + + /* put this as "dovecot.auth" */ + lua_setfield(script->L, -2, "auth"); +} + +int auth_lua_script_init(struct dlua_script *script, const char **error_r) +{ + dlua_dovecot_register(script); + auth_lua_dovecot_auth_register(script); + auth_lua_auth_request_register(script); + return dlua_script_init(script, error_r); +} + +static int auth_lua_call_lookup(struct dlua_script *script, const char *fn, + struct auth_request *req, const char **error_r) +{ + int err = 0; + + i_assert(script != NULL); + + /* call lua function passdb_lookup, it is expected to return fields */ + lua_getglobal(script->L, fn); + if (!lua_isfunction(script->L, -1)) { + lua_pop(script->L, 1); + *error_r = t_strdup_printf("%s is not a function", fn); + return -1; + } + + if (req->debug) + auth_request_log_debug(req, AUTH_SUBSYS_DB, "Calling %s", fn); + + /* call with auth request as parameter */ + auth_lua_push_auth_request(script, req); + if (lua_pcall(script->L, 1, 2, 0) != 0) { + *error_r = t_strdup_printf("db-lua: %s(req) failed: %s", + fn, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + return -1; + } else if (!lua_isnumber(script->L, -2)) { + *error_r = t_strdup_printf("db-lua: %s(req) invalid return value " + "(expected number got %s)", + fn, luaL_typename(script->L, -2)); + err = -1; + } else if (!lua_isstring(script->L, -1) && !lua_istable(script->L, -1)) { + *error_r = t_strdup_printf("db-lua: %s(req) invalid return value " + "(expected string or table, got %s)", + fn, luaL_typename(script->L, -1)); + err = -1; + } + + if (err != 0) { + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + return 0; +} + +static void +auth_lua_export_fields(struct auth_request *req, + const char *str, + const char **scheme_r, const char **password_r) +{ + const char *const *fields = t_strsplit_spaces(str, " "); + while(*fields != NULL) { + const char *value = strchr(*fields, '='); + const char *key; + + if (value == NULL) { + key = *fields; + value = ""; + } else { + key = t_strdup_until(*fields, value++); + } + + if (password_r != NULL && strcmp(key, "password") == 0 && + req->userdb_lookup) { + *scheme_r = password_get_scheme(&value); + *password_r = value; + } else if (req->userdb_lookup) { + auth_request_set_userdb_field(req, key, value); + } else { + auth_request_set_field(req, key, value, STATIC_PASS_SCHEME); + } + fields++; + } +} + +static void auth_lua_export_table(struct dlua_script *script, struct auth_request *req, + const char **scheme_r, const char **password_r) +{ + lua_pushvalue(script->L, -1); + lua_pushnil(script->L); + while (lua_next(script->L, -2) != 0) { + const char *key = t_strdup(lua_tostring(script->L, -2)); + const char *value; + if (lua_isnumber(script->L, -1)) { + value = dec2str(lua_tointeger(script->L, -1)); + } else if (lua_isboolean(script->L, -1)) { + value = lua_toboolean(script->L, -1) ? "yes" : "no"; + } else if (lua_isstring(script->L, -1)) { + value = t_strdup(lua_tostring(script->L, -1)); + } else if (lua_isnil(script->L, -1)) { + value = ""; + } else { + auth_request_log_warning(req, AUTH_SUBSYS_DB, + "db-lua: '%s' has invalid value - ignoring", + key); + value = ""; + } + + if (password_r != NULL && strcmp(key, "password") == 0 && + !req->userdb_lookup) { + *scheme_r = password_get_scheme(&value); + *password_r = value; + } else if (req->userdb_lookup) { + auth_request_set_userdb_field(req, key, value); + } else { + auth_request_set_field(req, key, value, STATIC_PASS_SCHEME); + } + lua_pop(script->L, 1); + } + + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); +} + +static enum userdb_result +auth_lua_export_userdb_table(struct dlua_script *script, struct auth_request *req, + const char **error_r) +{ + enum userdb_result ret = lua_tointeger(script->L, -2); + + if (ret != USERDB_RESULT_OK) { + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + *error_r = "userdb failed"; + return ret; + } + + auth_lua_export_table(script, req, NULL, NULL); + return USERDB_RESULT_OK; +} + +static enum passdb_result +auth_lua_export_passdb_table(struct dlua_script *script, struct auth_request *req, + const char **scheme_r, const char **password_r, + const char **error_r) +{ + enum passdb_result ret = lua_tointeger(script->L, -2); + + if (ret != PASSDB_RESULT_OK) { + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + *error_r = "passb failed"; + return ret; + } + + auth_lua_export_table(script, req, scheme_r, password_r); + return PASSDB_RESULT_OK; +} + +static enum passdb_result +auth_lua_call_lookup_finish(struct dlua_script *script, struct auth_request *req, + const char **username_r, const char **password_r, + const char **error_r) +{ + if (lua_istable(script->L, -1)) { + return auth_lua_export_passdb_table(script, req, NULL, NULL, error_r); + } + + enum passdb_result ret = lua_tointeger(script->L, -2); + const char *str = t_strdup(lua_tostring(script->L, -1)); + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + + if (ret != PASSDB_RESULT_OK && ret != PASSDB_RESULT_NEXT) { + *error_r = str; + } else { + auth_lua_export_fields(req, str, username_r, password_r); + } + + return ret; +} + +enum passdb_result +auth_lua_call_password_verify(struct dlua_script *script, + struct auth_request *req, const char *password, const char **error_r) +{ + int err = 0; + i_assert(script != NULL); + + lua_getglobal(script->L, AUTH_LUA_PASSWORD_VERIFY); + if (!lua_isfunction(script->L, -1)) { + lua_pop(script->L, 1); + *error_r = t_strdup_printf("%s is not a function", AUTH_LUA_PASSWORD_VERIFY); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + if (req->debug) + auth_request_log_debug(req, AUTH_SUBSYS_DB, "Calling %s", AUTH_LUA_PASSWORD_VERIFY); + + /* call with auth request, password as parameters */ + auth_lua_push_auth_request(script, req); + lua_pushstring(script->L, password); + if (lua_pcall(script->L, 2, 2, 0) != 0) { + *error_r = t_strdup_printf("db-lua: %s(req, password) failed: %s", + AUTH_LUA_PASSWORD_VERIFY, + lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + return PASSDB_RESULT_INTERNAL_FAILURE; + } else if (!lua_isnumber(script->L, -2)) { + *error_r = t_strdup_printf("db-lua: %s invalid return value " + "(expected number got %s)", + AUTH_LUA_PASSWORD_VERIFY, + luaL_typename(script->L, -2)); + err = -1; + } else if (!lua_isstring(script->L, -1) && !lua_istable(script->L, -1)) { + *error_r = t_strdup_printf("db-lua: %s invalid return value " + "(expected string or table, got %s)", + AUTH_LUA_PASSWORD_VERIFY, + luaL_typename(script->L, -1)); + err = -1; + } + + if (err != 0) { + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + + return auth_lua_call_lookup_finish(script, req, NULL, NULL, error_r); +} + + +enum passdb_result +auth_lua_call_passdb_lookup(struct dlua_script *script, + struct auth_request *req, const char **scheme_r, + const char **password_r, const char **error_r) +{ + *scheme_r = *password_r = NULL; + if (auth_lua_call_lookup(script, AUTH_LUA_PASSDB_LOOKUP, req, error_r) < 0) { + lua_gc(script->L, LUA_GCCOLLECT, 0); + return PASSDB_RESULT_INTERNAL_FAILURE; + } + + return auth_lua_call_lookup_finish(script, req, scheme_r, password_r, error_r); +} + + +enum userdb_result +auth_lua_call_userdb_lookup(struct dlua_script *script, + struct auth_request *req, const char **error_r) +{ + if (auth_lua_call_lookup(script, AUTH_LUA_USERDB_LOOKUP, req, error_r) < 0) { + lua_gc(script->L, LUA_GCCOLLECT, 0); + return USERDB_RESULT_INTERNAL_FAILURE; + } + + if (lua_istable(script->L, -1)) { + return auth_lua_export_userdb_table(script, req, error_r); + } + + enum userdb_result ret = lua_tointeger(script->L, -2); + const char *str = t_strdup(lua_tostring(script->L, -1)); + lua_pop(script->L, 2); + lua_gc(script->L, LUA_GCCOLLECT, 0); + + if (ret != USERDB_RESULT_OK) { + *error_r = str; + return ret; + } + auth_lua_export_fields(req, str, NULL, NULL); + + return USERDB_RESULT_OK; +} + +struct userdb_iterate_context * +auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req, + userdb_iter_callback_t *callback, void *context) +{ + pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"lua userdb iterate", 128); + struct auth_lua_userdb_iterate_context *actx = + p_new(pool, struct auth_lua_userdb_iterate_context, 1); + int ret; + + actx->pool = pool; + + lua_getglobal(script->L, AUTH_LUA_USERDB_ITERATE); + if (!lua_isfunction(script->L, -1)) { + actx->ctx.failed = TRUE; + return &actx->ctx; + } + + if (req->debug) + auth_request_log_debug(req, AUTH_SUBSYS_DB, "Calling %s", AUTH_LUA_USERDB_ITERATE); + + if ((ret = lua_pcall(script->L, 0, 1, 0)) != 0) { + auth_request_log_error(req, AUTH_SUBSYS_DB, "db-lua: " AUTH_LUA_USERDB_ITERATE " failed: %s", + lua_tostring(script->L, -1)); + actx->ctx.failed = TRUE; + lua_pop(script->L, 1); + return &actx->ctx; + } + + if (!lua_istable(script->L, -1)) { + auth_request_log_error(req, AUTH_SUBSYS_DB, "db-lua: Cannot iterate, return value is not table"); + actx->ctx.failed = TRUE; + lua_pop(script->L, 1); + lua_gc(script->L, LUA_GCCOLLECT, 0); + return &actx->ctx; + } + + p_array_init(&actx->users, pool, 8); + + lua_pushvalue(script->L, -1); + lua_pushnil(script->L); + while (lua_next(script->L, -2) != 0) { + lua_pushvalue(script->L, -2); + if (!lua_isstring(script->L, -1)) { + auth_request_log_error(req, AUTH_SUBSYS_DB, "db-lua: Value is not string"); + actx->ctx.failed = TRUE; + lua_pop(script->L, 1); + lua_gc(script->L, LUA_GCCOLLECT, 0); + return &actx->ctx; + } + const char *str = p_strdup(pool, lua_tostring(script->L, -2)); + array_append(&actx->users, &str, 1); + lua_pop(script->L, 2); + } + + lua_gc(script->L, LUA_GCCOLLECT, 0); + + actx->ctx.auth_request = req; + actx->ctx.callback = callback; + actx->ctx.context = context; + + return &actx->ctx; +} + +void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx) +{ + struct auth_lua_userdb_iterate_context *actx = + container_of(ctx, struct auth_lua_userdb_iterate_context, ctx); + + if (ctx->failed || actx->idx >= array_count(&actx->users)) { + ctx->callback(NULL, ctx->context); + return; + } + + const char *const *user = array_idx(&actx->users, actx->idx++); + ctx->callback(*user, ctx->context); +} + +int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx) +{ + struct auth_lua_userdb_iterate_context *actx = + container_of(ctx, struct auth_lua_userdb_iterate_context, ctx); + + int ret = ctx->failed ? -1 : 0; + pool_unref(&actx->pool); + return ret; +} + +#ifndef BUILTIN_LUA +/* Building a plugin */ +extern struct passdb_module_interface passdb_lua_plugin; +extern struct userdb_module_interface userdb_lua_plugin; + +void authdb_lua_init(void); +void authdb_lua_deinit(void); + +void authdb_lua_init(void) +{ + passdb_register_module(&passdb_lua_plugin); + userdb_register_module(&userdb_lua_plugin); + +} +void authdb_lua_deinit(void) +{ + passdb_unregister_module(&passdb_lua_plugin); + userdb_unregister_module(&userdb_lua_plugin); +} +#endif + +#endif diff --git a/src/auth/db-lua.h b/src/auth/db-lua.h new file mode 100644 index 0000000000..c9b622bc82 --- /dev/null +++ b/src/auth/db-lua.h @@ -0,0 +1,31 @@ +#ifndef DB_LUA_H +#define DB_LUA_H 1 + +#include "dlua-script.h" + +#define AUTH_LUA_PASSWORD_VERIFY "auth_password_verify" + +struct dlua_script; + +int auth_lua_script_init(struct dlua_script *script, const char **error_r); + +int auth_lua_call_password_verify(struct dlua_script *script, + struct auth_request *req, const char *password, + const char **error_r); + +enum passdb_result +auth_lua_call_passdb_lookup(struct dlua_script *script, + struct auth_request *req, const char **scheme_r, + const char **password_r, const char **error_r); + +enum userdb_result +auth_lua_call_userdb_lookup(struct dlua_script *script, + struct auth_request *req, const char **error_r); + +struct userdb_iterate_context * +auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req, + userdb_iter_callback_t *callback, void *context); +void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx); +int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx); + +#endif diff --git a/src/auth/passdb-lua.c b/src/auth/passdb-lua.c new file mode 100644 index 0000000000..032f4eafef --- /dev/null +++ b/src/auth/passdb-lua.c @@ -0,0 +1,175 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "passdb.h" + +#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) + +#include "db-lua.h" + +struct dlua_passdb_module { + struct passdb_module module; + struct dlua_script *script; + const char *file; + bool has_password_verify; +}; + +static enum passdb_result +passdb_lua_verify_password(struct dlua_passdb_module *module, + struct auth_request *request, const char *password) +{ + const char *error = NULL; + enum passdb_result result = + auth_lua_call_password_verify(module->script, request, + password, &error); + if (result == PASSDB_RESULT_PASSWORD_MISMATCH) { + auth_request_log_password_mismatch(request, AUTH_SUBSYS_DB); + } else if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) { + auth_request_log_error(request, AUTH_SUBSYS_DB, "passdb-lua: %s", + error); + } + return result; +} + +static enum passdb_result +passdb_lua_lookup(struct auth_request *request, + const char **scheme_r, const char **password_r) +{ + const char *error = NULL; + enum passdb_result result; + struct dlua_passdb_module *module = + (struct dlua_passdb_module *)request->passdb->passdb; + + *scheme_r = *password_r = NULL; + + result = auth_lua_call_passdb_lookup(module->script, request, scheme_r, + password_r, &error); + + if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) { + auth_request_log_error(request, AUTH_SUBSYS_DB, "db-lua: %s", error); + } else if (result != PASSDB_RESULT_OK) { + /* skip next bit */ + } else if (!auth_fields_exists(request->extra_fields, "nopassword")) { + if (*password_r == NULL || **password_r == '\0') { + auth_request_log_info(request, AUTH_SUBSYS_DB, + "No password returned (and no nopassword)"); + result = PASSDB_RESULT_PASSWORD_MISMATCH; + } + } else if (*password_r == NULL || **password_r != '\0') { + auth_request_log_info(request, AUTH_SUBSYS_DB, + "nopassword given and password is not empty"); + result = PASSDB_RESULT_PASSWORD_MISMATCH; + } + return result; +} + +static void +passdb_lua_lookup_credentials(struct auth_request *request, + lookup_credentials_callback_t *callback) +{ + const char *lua_password, *lua_scheme; + enum passdb_result result = + passdb_lua_lookup(request, &lua_scheme, &lua_password); + + passdb_handle_credentials(result, lua_password, lua_scheme, callback, request); +} + +static void +passdb_lua_verify_plain(struct auth_request *request, const char *password, + verify_plain_callback_t *callback) +{ + struct dlua_passdb_module *module = + (struct dlua_passdb_module *)request->passdb->passdb; + const char *lua_password, *lua_scheme; + enum passdb_result result; + + if (module->has_password_verify) { + result = passdb_lua_verify_password(module, request, password); + } else { + result = passdb_lua_lookup(request, &lua_scheme, &lua_password); + if (result == PASSDB_RESULT_OK) { + if ((auth_request_password_verify(request, password, lua_password, + lua_scheme, AUTH_SUBSYS_DB)) <=0) { + result = PASSDB_RESULT_PASSWORD_MISMATCH; + } + } + } + callback(result, request); +} + +static struct passdb_module * +passdb_lua_preinit(pool_t pool, const char *args) +{ + struct dlua_passdb_module *module; + bool blocking = TRUE; + + module = p_new(pool, struct dlua_passdb_module, 1); + const char *const *fields = t_strsplit_spaces(args, " "); + while(*fields != NULL) { + if (strncmp(*fields, "file=", 5) == 0) { + module->file = p_strdup(pool, (*fields)+5); + } else if (strncmp(*fields, "blocking=", 9) == 0) { + const char *value = (*fields)+9; + if (strcmp(value, "yes") == 0) { + blocking = TRUE; + } else if (strcmp(value, "no") == 0) { + blocking = FALSE; + } else { + i_fatal("Invalid value %s. " + "Field blocking must be yes or no", + value); + } + } else { + i_fatal("Unsupported parameter %s", *fields); + } + fields++; + } + + if (module->file == NULL) + i_fatal("passdb-lua: Missing mandatory file= parameter"); + + module->module.blocking = blocking; + return &module->module; +} + +static void passdb_lua_init(struct passdb_module *_module) +{ + struct dlua_passdb_module *module = + (struct dlua_passdb_module *)_module; + const char *error; + + if (dlua_script_create_file(module->file, &module->script, &error) < 0 || + auth_lua_script_init(module->script, &error) < 0) + i_fatal("passdb-lua: initialization failed: %s", error); + module->has_password_verify = + dlua_script_has_function(module->script, AUTH_LUA_PASSWORD_VERIFY); +} + +static void passdb_lua_deinit(struct passdb_module *_module) +{ + struct dlua_passdb_module *module = + (struct dlua_passdb_module *)_module; + dlua_script_unref(&module->script); +} + +#ifndef PLUGIN_BUILD +struct passdb_module_interface passdb_lua = +#else +struct passdb_module_interface passdb_lua_plugin = +#endif +{ + "lua", + + passdb_lua_preinit, + passdb_lua_init, + passdb_lua_deinit, + + passdb_lua_verify_plain, + passdb_lua_lookup_credentials, + NULL +}; +#else +struct passdb_module_interface passdb_lua = { + .name = "lua" +}; +#endif diff --git a/src/auth/passdb.c b/src/auth/passdb.c index 3078bf832b..49b50552d9 100644 --- a/src/auth/passdb.c +++ b/src/auth/passdb.c @@ -289,6 +289,9 @@ void passdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]) extern struct passdb_module_interface passdb_passwd; extern struct passdb_module_interface passdb_bsdauth; extern struct passdb_module_interface passdb_dict; +#ifdef HAVE_LUA +extern struct passdb_module_interface passdb_lua; +#endif extern struct passdb_module_interface passdb_shadow; extern struct passdb_module_interface passdb_passwd_file; extern struct passdb_module_interface passdb_pam; @@ -307,6 +310,9 @@ void passdbs_init(void) passdb_register_module(&passdb_passwd); passdb_register_module(&passdb_bsdauth); passdb_register_module(&passdb_dict); +#ifdef HAVE_LUA + passdb_register_module(&passdb_lua); +#endif passdb_register_module(&passdb_passwd_file); passdb_register_module(&passdb_pam); passdb_register_module(&passdb_checkpassword); diff --git a/src/auth/test-auth.h b/src/auth/test-auth.h index ae059da216..f20aa87c17 100644 --- a/src/auth/test-auth.h +++ b/src/auth/test-auth.h @@ -6,9 +6,13 @@ #include "lib.h" #include "test-common.h" +struct auth_passdb; + void test_auth_request_var_expand(void); void test_db_dict_parse_cache_key(void); void test_username_filter(void); +void test_db_lua(void); +struct auth_passdb *passdb_mock(void); #endif diff --git a/src/auth/test-lua.c b/src/auth/test-lua.c new file mode 100644 index 0000000000..9b2ef767b3 --- /dev/null +++ b/src/auth/test-lua.c @@ -0,0 +1,36 @@ +#include "test-auth.h" +#include "istream.h" +#include "auth-settings.h" +#include "auth-request.h" +#include "db-lua.h" + +void test_db_lua(void) +{ + const char *scheme,*pass; + struct auth_settings set; + global_auth_settings = &set; + passdbs_init(); + + struct auth_request *req = auth_request_new_dummy(); + req->passdb = passdb_mock(); + req->debug = TRUE; + req->user = "testuser"; + + static const char *luascript = +"function auth_passdb_lookup(req)\n" +" req:log_debug(\"user \" .. req.user)\n" +" return dovecot.auth.PASSDB_RESULT_OK, req:var_expand(\"password=pass\")\n" +"end\n"; + const char *error = NULL; + struct dlua_script *script = NULL; + + test_begin("auth db lua"); + + test_assert(dlua_script_create_string(luascript, &script, &error) == 0); + test_assert(auth_lua_script_init(script, &error) == 0); + if (script != NULL) { + test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1); + } + dlua_script_unref(&script); + test_end(); +} diff --git a/src/auth/test-main.c b/src/auth/test-main.c index 941fab9d3c..fddf5e1ecd 100644 --- a/src/auth/test-main.c +++ b/src/auth/test-main.c @@ -11,6 +11,9 @@ int main(int argc, const char *argv[]) TEST_NAMED(test_auth_request_var_expand) TEST_NAMED(test_db_dict_parse_cache_key) TEST_NAMED(test_username_filter) +#if defined(BUILTIN_LUA) + TEST_NAMED(test_db_lua) +#endif { NULL, NULL } }; diff --git a/src/auth/test-mock.c b/src/auth/test-mock.c index c4e0e6b423..b24e47be9a 100644 --- a/src/auth/test-mock.c +++ b/src/auth/test-mock.c @@ -1,13 +1,91 @@ /* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ -#include "lib.h" +#include "test-auth.h" #include "auth-common.h" +#include "passdb.h" struct auth_penalty *auth_penalty; time_t process_start_time; bool worker, worker_restart_request; +static struct passdb_module *mock_passdb_mod = NULL; + void auth_module_load(const char *names ATTR_UNUSED) { } void auth_refresh_proctitle(void) { } + +static void passdb_mock_init(struct passdb_module *module ATTR_UNUSED) +{ +} +static void passdb_mock_deinit(struct passdb_module *module ATTR_UNUSED) +{ +} +static void passdb_mock_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, + verify_plain_callback_t *callback) +{ + callback(PASSDB_RESULT_OK, request); +} + +static struct passdb_module_interface mock_interface = { + .name = "mock", + .init = passdb_mock_init, + .deinit = passdb_mock_deinit, + .verify_plain = passdb_mock_verify_plain, +}; + +static struct auth_passdb_settings set = { + .name = "mock", + .driver = "mock", + .args = "", + .default_fields = "", + .override_fields = "", + .mechanisms = "", + .username_filter = "", + .skip = "never", + .result_success = "return-ok", + .result_failure = "continue", + .result_internalfail = "continue", + .deny = FALSE, + .pass = FALSE, + .master = FALSE, + .auth_verbose = "default" +}; + +static void passdb_mock_mod_init(void) +{ + if (mock_passdb_mod != NULL) + return; + + passdb_register_module(&mock_interface); + + struct auth_passdb_settings set = { + .name = "mock", + .driver = "mock", + .args = "", + .default_fields = "", + .override_fields = "", + .mechanisms = "", + .username_filter = "", + + .skip = "never", + .result_success = "return-ok", + .result_failure = "continue", + .result_internalfail = "continue", + + .deny = FALSE, + .pass = FALSE, + .master = FALSE, + .auth_verbose = "default" + }; + mock_passdb_mod = passdb_preinit(default_pool, &set); +} + +struct auth_passdb *passdb_mock(void) +{ + passdb_mock_mod_init(); + struct auth_passdb *ret = i_new(struct auth_passdb, 1); + ret->set = &set; + ret->passdb = mock_passdb_mod; + return ret; +} diff --git a/src/auth/userdb-lua.c b/src/auth/userdb-lua.c new file mode 100644 index 0000000000..15840e6762 --- /dev/null +++ b/src/auth/userdb-lua.c @@ -0,0 +1,125 @@ +/* Copyright (c) 2003-2017 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "userdb.h" + +#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) + +#include "db-lua.h" + +struct dlua_userdb_module { + struct userdb_module module; + struct dlua_script *script; + const char *file; +}; + +static void userdb_lua_lookup(struct auth_request *auth_request, + userdb_callback_t *callback) +{ + struct userdb_module *_module = auth_request->userdb->userdb; + struct dlua_userdb_module *module = + (struct dlua_userdb_module *)_module; + const char *error; + enum userdb_result result = + auth_lua_call_userdb_lookup(module->script, auth_request, &error); + callback(result, auth_request); +} + +static struct userdb_module * +userdb_lua_preinit(pool_t pool, const char *args) +{ + struct dlua_userdb_module *module; + bool blocking = TRUE; + + module = p_new(pool, struct dlua_userdb_module, 1); + const char *const *fields = t_strsplit_spaces(args, " "); + while(*fields != NULL) { + if (strncmp(*fields, "file=", 5) == 0) { + module->file = p_strdup(pool, (*fields)+5); + } else if (strncmp(*fields, "blocking=", 9) == 0) { + const char *value = (*fields)+9; + if (strcmp(value, "yes") == 0) { + blocking = TRUE; + } else if (strcmp(value, "no") == 0) { + blocking = FALSE; + } else { + i_fatal("Invalid value %s. " + "Field blocking must be yes or no", + value); + } + } else { + i_fatal("Unsupported parameter %s", *fields); + } + fields++; + } + + if (module->file == NULL) + i_fatal("passdb-lua: Missing mandatory file= parameter"); + + module->module.blocking = blocking; + return &module->module; +} + +static void userdb_lua_init(struct userdb_module *_module) +{ + struct dlua_userdb_module *module = + (struct dlua_userdb_module *)_module; + const char *error; + + if (dlua_script_create_file(module->file, &module->script, &error) < 0 || + auth_lua_script_init(module->script, &error) < 0) + i_fatal("userdb-lua: initialization failed: %s", error); +} + +static void userdb_lua_deinit(struct userdb_module *_module) +{ + struct dlua_userdb_module *module = + (struct dlua_userdb_module *)_module; + dlua_script_unref(&module->script); +} + +static struct userdb_iterate_context * +userdb_lua_iterate_init(struct auth_request *auth_request, + userdb_iter_callback_t *callback, + void *context) +{ + struct userdb_module *_module = auth_request->userdb->userdb; + struct dlua_userdb_module *module = + (struct dlua_userdb_module *)_module; + return auth_lua_call_userdb_iterate_init(module->script, auth_request, + callback, context); +} + +static void userdb_lua_iterate_next(struct userdb_iterate_context *ctx) +{ + auth_lua_userdb_iterate_next(ctx); +} + +static int userdb_lua_iterate_deinit(struct userdb_iterate_context *ctx) +{ + return auth_lua_userdb_iterate_deinit(ctx); +} + +#ifndef PLUGIN_BUILD +struct userdb_module_interface userdb_lua = +#else +struct userdb_module_interface userdb_lua_plugin = +#endif +{ + "lua", + + userdb_lua_preinit, + userdb_lua_init, + userdb_lua_deinit, + + userdb_lua_lookup, + + userdb_lua_iterate_init, + userdb_lua_iterate_next, + userdb_lua_iterate_deinit +}; +#else +struct userdb_module_interface userdb_lua = { + .name = "lua" +}; +#endif diff --git a/src/auth/userdb.c b/src/auth/userdb.c index c45eda499e..a2187e36c3 100644 --- a/src/auth/userdb.c +++ b/src/auth/userdb.c @@ -220,6 +220,9 @@ extern struct userdb_module_interface userdb_ldap; extern struct userdb_module_interface userdb_sql; extern struct userdb_module_interface userdb_checkpassword; extern struct userdb_module_interface userdb_dict; +#ifdef HAVE_LUA +extern struct userdb_module_interface userdb_lua; +#endif void userdbs_init(void) { @@ -234,6 +237,9 @@ void userdbs_init(void) userdb_register_module(&userdb_sql); userdb_register_module(&userdb_checkpassword); userdb_register_module(&userdb_dict); +#ifdef HAVE_LUA + userdb_register_module(&userdb_lua); +#endif } void userdbs_deinit(void) diff --git a/src/lib-lua/dlua-script-private.h b/src/lib-lua/dlua-script-private.h index 6676477434..4914c91923 100644 --- a/src/lib-lua/dlua-script-private.h +++ b/src/lib-lua/dlua-script-private.h @@ -44,7 +44,7 @@ struct dlua_script { struct istream *in; ssize_t last_read; - unsigned int ref; + int ref; bool init:1; }; -- 2.47.3