]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-storage: Split off the mail user lua code
authorJosef 'Jeff' Sipek <jeff.sipek@open-xchange.com>
Tue, 1 Dec 2020 17:16:04 +0000 (12:16 -0500)
committerJosef 'Jeff' Sipek <jeff.sipek@open-xchange.com>
Tue, 1 Dec 2020 17:16:04 +0000 (12:16 -0500)
Move the code into a separate file to mirror the native C code layout.

src/lib-storage/Makefile.am
src/lib-storage/mail-storage-lua-private.h
src/lib-storage/mail-storage-lua.c
src/lib-storage/mail-user-lua.c [new file with mode: 0644]

index 4cc8b721a9cc6b0c54dcf8e456f7acae26f567f2..d52ab7892407e76c9f1a033884750e1251110d50 100644 (file)
@@ -139,6 +139,7 @@ if HAVE_LUA
 pkglib_LTLIBRARIES += libdovecot-storage-lua.la
 libdovecot_storage_lua_la_SOURCES = \
        mail-lua.c \
+       mail-user-lua.c \
        mail-storage-lua.c \
        mailbox-lua.c \
        mailbox-attribute-lua.c
index da641570b478d4322c44780a17a52bcac5aa76f2..dcae42b581ca7eb6fabc91b6d944fb5381dfec0e 100644 (file)
@@ -13,6 +13,7 @@ struct lua_storage_keyvalue {
 ARRAY_DEFINE_TYPE(lua_storage_keyvalue, struct lua_storage_keyvalue);
 
 void lua_storage_mail_register(struct dlua_script *script);
+void lua_storage_mail_user_register(struct dlua_script *script);
 void lua_storage_mailbox_register(struct dlua_script *script);
 
 int lua_storage_cmp(struct dlua_script *script);
index 095104b1d6e819b0f9eadfcf915fdfc4b6732596..615c1c693fa687710f0e81a3f509fc01461e0019 100644 (file)
 #include "mail-user.h"
 
 #define LUA_SCRIPT_STORAGE "storage"
-#define LUA_STORAGE_MAIL_USER "struct mail_user"
-
-/** MAIL USER
- */
-
-static int lua_storage_mail_user_unref(lua_State *L);
-
-void dlua_push_mail_user(struct dlua_script *script, struct mail_user *user)
-{
-       luaL_checkstack(script->L, 20, "out of memory");
-       /* create a table for holding few things */
-       lua_createtable(script->L, 0, 20);
-       luaL_setmetatable(script->L, LUA_STORAGE_MAIL_USER);
-
-       mail_user_ref(user);
-       struct mail_user **ptr = lua_newuserdata(script->L, sizeof(struct mail_user*));
-       *ptr = user;
-       lua_createtable(script->L, 0, 1);
-       lua_pushcfunction(script->L, lua_storage_mail_user_unref);
-       lua_setfield(script->L, -2, "__gc");
-       lua_setmetatable(script->L, -2);
-       lua_setfield(script->L, -2, "item");
-
-#undef LUA_TABLE_SETNUMBER
-#define LUA_TABLE_SETNUMBER(field) \
-       lua_pushnumber(script->L, user->field); \
-       lua_setfield(script->L, -2, #field);
-#undef LUA_TABLE_SETBOOL
-#define LUA_TABLE_SETBOOL(field) \
-       lua_pushboolean(script->L, user->field); \
-       lua_setfield(script->L, -2, #field);
-#undef LUA_TABLE_SETSTRING
-#define LUA_TABLE_SETSTRING(field) \
-       lua_pushstring(script->L, user->field); \
-       lua_setfield(script->L, -2, #field);
-
-       const char *home = NULL;
-       (void)mail_user_get_home(user, &home);
-
-       lua_pushstring(script->L, home);
-       lua_setfield(script->L, -2, "home");
-
-       LUA_TABLE_SETSTRING(username);
-       LUA_TABLE_SETNUMBER(uid);
-       LUA_TABLE_SETNUMBER(gid);
-       LUA_TABLE_SETSTRING(service);
-       LUA_TABLE_SETSTRING(session_id);
-       LUA_TABLE_SETNUMBER(session_create_time);
-
-       LUA_TABLE_SETBOOL(nonexistent);
-       LUA_TABLE_SETBOOL(anonymous);
-       LUA_TABLE_SETBOOL(autocreated);
-       LUA_TABLE_SETBOOL(mail_debug);
-       LUA_TABLE_SETBOOL(fuzzy_search);
-       LUA_TABLE_SETBOOL(dsyncing);
-       LUA_TABLE_SETBOOL(admin);
-       LUA_TABLE_SETBOOL(session_restored);
-}
-
-static struct mail_user *
-lua_check_storage_mail_user(struct dlua_script *script, int arg)
-{
-       if (!lua_istable(script->L, arg)) {
-               (void)luaL_error(script->L, "Bad argument #%d, expected %s got %s",
-                                arg, LUA_STORAGE_MAIL_USER,
-                                lua_typename(script->L, lua_type(script->L, arg)));
-       }
-       lua_pushliteral(script->L, "item");
-       lua_rawget(script->L, arg);
-       struct mail_user **bp = lua_touserdata(script->L, -1);
-       lua_pop(script->L, 1);
-       return *bp;
-}
-
-static int lua_storage_mail_user_tostring(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 1);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-
-       lua_pushstring(L, user->username);
-       return 1;
-}
-
-int lua_storage_cmp(struct dlua_script *script)
-{
-       const char *name_a, *name_b;
-       name_a = lua_tostring(script->L, 1);
-       name_b = lua_tostring(script->L, 2);
-
-       return strcmp(name_a, name_b);
-}
-
-static int lua_storage_mail_user_eq(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       bool res = lua_storage_cmp(script) == 0;
-       lua_pushboolean(script->L, res);
-       return 1;
-}
-
-static int lua_storage_mail_user_lt(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       bool res = lua_storage_cmp(script) <= 0;
-       lua_pushboolean(script->L, res);
-       return 1;
-}
-
-static int lua_storage_mail_user_le(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       bool res = lua_storage_cmp(script) < 0;
-       lua_pushboolean(script->L, res);
-       return 1;
-}
-
-static int lua_storage_mail_user_var_expand(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const char *error;
-       const char *format = luaL_checkstring(script->L, 2);
-       const struct var_expand_table *table = mail_user_var_expand_table(user);
-       string_t *str = t_str_new(128);
-       if (var_expand_with_funcs(str, format, table, mail_user_var_expand_func_table,
-                                 user, &error) < 0) {
-               return luaL_error(script->L, "var_expand(%s) failed: %s",
-                                 format, error);
-       }
-       lua_pushlstring(script->L, str->data, str->used);
-       return 1;
-}
-
-static int lua_storage_mail_user_plugin_getenv(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const char *set = lua_tostring(script->L, 2);
-       const char *val = mail_user_plugin_getenv(user, set);
-       lua_pushstring(script->L, val);
-       return 1;
-}
-
-static int lua_storage_mail_user_mailbox_alloc(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS_IN(script, 2, 3);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const char *mboxname = luaL_checkstring(script->L, 2);
-       enum mailbox_flags flags = 0;
-       if (lua_gettop(script->L) >= 3)
-               flags = luaL_checkinteger(script->L, 3);
-       struct mail_namespace *ns = mail_namespace_find(user->namespaces, mboxname);
-       if (ns == NULL) {
-               return luaL_error(script->L, "No namespace found for mailbox %s",
-                                 mboxname);
-       }
-       struct mailbox *mbox = mailbox_alloc(ns->list, mboxname, flags);
-       dlua_push_mailbox(script, mbox);
-       return 1;
-}
-
-static int lua_storage_mail_user_unref(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       struct mail_user **ptr = lua_touserdata(script->L, 1);
-       if (*ptr != NULL)
-               mail_user_unref(ptr);
-       *ptr = NULL;
-       return 0;
-}
-
-static const char *lua_storage_mail_user_metadata_key(const char *key)
-{
-       if (str_begins(key, "/private/")) {
-               return t_strdup_printf("/private/%s%s",
-                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
-                                      key + 9);
-       } else if (str_begins(key, "/shared/")) {
-               return t_strdup_printf("/shared/%s%s",
-                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
-                                      key + 8);
-       }
-       return NULL;
-}
-
-static int lua_storage_mail_user_metadata_get(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       if (lua_gettop(script->L) < 2)
-               return luaL_error(script->L, "expecting at least 1 parameter");
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-
-       const char *value, *error;
-       size_t value_len;
-       int ret, i, top = lua_gettop(script->L);
-
-       /* fetch INBOX, as user metadata is stored there */
-       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
-       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX",
-                                            MAILBOX_FLAG_READONLY);
-
-       if (mailbox_open(mbox) < 0) {
-               error = mailbox_get_last_error(mbox, NULL);
-               mailbox_free(&mbox);
-               return luaL_error(script->L, "Cannot open INBOX: %s", error);
-       }
-
-       ret = 0;
-       for(i = 2; i <= top; i++) {
-               /* reformat key */
-               const char *key = lua_tostring(script->L, i);
-
-               if (key == NULL) {
-                       ret = -1;
-                       error = t_strdup_printf("expected string at #%d", i);
-                       break;
-               }
-
-               if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
-                       ret = -1;
-                       error = "Invalid key prefix, must be "
-                               "/private/ or /shared/";
-                       break;
-               }
-
-               if ((ret = lua_storage_mailbox_attribute_get(mbox, key, &value,
-                                                            &value_len, &error)) < 0) {
-                       break;
-               } else if (ret == 0) {
-                       lua_pushnil(script->L);
-               } else {
-                       lua_pushlstring(script->L, value, value_len);
-               }
-       }
-
-       mailbox_free(&mbox);
-
-       if (ret < 0)
-               return luaL_error(script->L, "%s", error);
-
-       i_assert(i>=2);
-       return i-2;
-}
-
-static int
-lua_storage_mail_user_set_metadata_unset(struct dlua_script *script,
-                                        struct mail_user *user,
-                                        const char *key, const char *value,
-                                        size_t value_len)
-{
-       const char *error;
-
-       /* reformat key */
-       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
-               return luaL_error(script->L, "Invalid key prefix, must be "
-                                            "/private/ or /shared/");
-       }
-
-       /* fetch INBOX, as user metadata is stored there */
-       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
-       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
-
-       if (mailbox_open(mbox) < 0) {
-               error = mailbox_get_last_error(mbox, NULL);
-               mailbox_free(&mbox);
-               return luaL_error(script->L,
-                                 "Cannot open INBOX: %s", error);
-       }
-
-       if (lua_storage_mailbox_attribute_set(mbox, key, value, value_len,
-                                             &error) < 0) {
-               mailbox_free(&mbox);
-               return luaL_error(script->L,
-                                 "Cannot get attribute: %s", error);
-       }
-
-       mailbox_free(&mbox);
-       return 0;
-}
-
-static int lua_storage_mail_user_metadata_set(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 3);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const char *key = luaL_checkstring(script->L, 2);
-       const char *value;
-       size_t value_len;
-
-       value = lua_tolstring(script->L, 3, &value_len);
-
-       return lua_storage_mail_user_set_metadata_unset(script, user, key,
-                                                       value, value_len);
-}
-
-static int lua_storage_mail_user_metadata_unset(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       DLUA_REQUIRE_ARGS(script, 2);
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const char *key = luaL_checkstring(script->L, 2);
-
-       return lua_storage_mail_user_set_metadata_unset(script, user, key, NULL, 0);
-}
-
-static int lua_storage_mail_user_metadata_list(lua_State *L)
-{
-       struct dlua_script *script = dlua_script_from_state(L);
-       if (lua_gettop(script->L) < 2)
-               return luaL_error(script->L, "expecting at least 1 parameter");
-       struct mail_user *user = lua_check_storage_mail_user(script, 1);
-       const struct lua_storage_keyvalue *item;
-       const char *error;
-       ARRAY_TYPE(lua_storage_keyvalue) items;
-       int i, ret;
-
-       /* fetch INBOX, as user metadata is stored there */
-       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
-       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
-
-       if (mailbox_open(mbox) < 0) {
-               error = mailbox_get_last_error(mbox, NULL);
-               mailbox_free(&mbox);
-               return luaL_error(script->L,
-                                 "Cannot open INBOX: %s", error);
-       }
-
-       T_BEGIN {
-               t_array_init(&items, 1);
-
-               ret = 0;
-               for(i = 2; i <= lua_gettop(script->L); i++) {
-                       const char *key = lua_tostring(script->L, i);
-
-                       if (key == NULL) {
-                               ret = -1;
-                               error = t_strdup_printf("expected string at #%d", i);
-                               break;
-                       }
-
-                       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
-                               ret = -1;
-                               error = "Invalid key prefix, must be "
-                                       "/private/ or /shared/";
-                               break;
-                       }
-
-                       if (lua_storage_mailbox_attribute_list(mbox, key, &items,
-                                                              &error) < 0) {
-                               ret = -1;
-                               break;
-                       }
-               }
-
-               if (ret == 0) {
-                       lua_createtable(script->L, 0, array_count(&items));
-                       array_foreach(&items, item) {
-                               char *ptr;
-                               char *key = t_strdup_noconst(item->key);
-                               if ((ptr = strstr(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) != NULL) {
-                                       const char *endp = ptr+strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER);
-                                       memmove(ptr, endp, strlen(endp));
-                                       memset(ptr+strlen(endp), '\0', 1);
-                               }
-                               /* push value */
-                               lua_pushlstring(script->L, item->value,
-                                               item->value_len);
-                               /* set field */
-                               lua_setfield(script->L, -2, key);
-                       }
-               }
-       } T_END;
-
-       mailbox_free(&mbox);
-
-       if (ret == -1)
-               return luaL_error(script->L, "%s", error);
-
-       /* stack should have table with items */
-       return 1;
-}
-
-static luaL_Reg lua_storage_mail_user_methods[] = {
-       { "__tostring", lua_storage_mail_user_tostring },
-       { "__eq", lua_storage_mail_user_eq },
-       { "__lt", lua_storage_mail_user_lt },
-       { "__le", lua_storage_mail_user_le },
-       { "plugin_getenv", lua_storage_mail_user_plugin_getenv },
-       { "var_expand", lua_storage_mail_user_var_expand },
-       { "mailbox", lua_storage_mail_user_mailbox_alloc },
-       { "metadata_get", lua_storage_mail_user_metadata_get },
-       { "metadata_set", lua_storage_mail_user_metadata_set },
-       { "metadata_unset", lua_storage_mail_user_metadata_unset },
-       { "metadata_list", lua_storage_mail_user_metadata_list },
-       { NULL, NULL }
-};
-
-static void lua_storage_mail_user_register(struct dlua_script *script)
-{
-       luaL_newmetatable(script->L, LUA_STORAGE_MAIL_USER);
-       lua_pushvalue(script->L, -1);
-       lua_setfield(script->L, -2, "__index");
-       luaL_setfuncs(script->L, lua_storage_mail_user_methods, 0);
-       lua_pop(script->L, 1);
-}
-
-/** End of MAIL USER
- */
 
 static struct dlua_table_values lua_storage_mail_storage_flags[] = {
        DLUA_TABLE_ENUM(STATUS_MESSAGES),
diff --git a/src/lib-storage/mail-user-lua.c b/src/lib-storage/mail-user-lua.c
new file mode 100644 (file)
index 0000000..2799527
--- /dev/null
@@ -0,0 +1,424 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "array.h"
+#include "var-expand.h"
+#include "dlua-script.h"
+#include "dlua-script-private.h"
+#include "mail-storage.h"
+#include "mailbox-attribute.h"
+#include "mail-storage-lua.h"
+#include "mail-storage-lua-private.h"
+#include "mail-user.h"
+
+#define LUA_STORAGE_MAIL_USER "struct mail_user"
+
+static int lua_storage_mail_user_unref(lua_State *L);
+
+void dlua_push_mail_user(struct dlua_script *script, struct mail_user *user)
+{
+       luaL_checkstack(script->L, 20, "out of memory");
+       /* create a table for holding few things */
+       lua_createtable(script->L, 0, 20);
+       luaL_setmetatable(script->L, LUA_STORAGE_MAIL_USER);
+
+       mail_user_ref(user);
+       struct mail_user **ptr = lua_newuserdata(script->L, sizeof(struct mail_user*));
+       *ptr = user;
+       lua_createtable(script->L, 0, 1);
+       lua_pushcfunction(script->L, lua_storage_mail_user_unref);
+       lua_setfield(script->L, -2, "__gc");
+       lua_setmetatable(script->L, -2);
+       lua_setfield(script->L, -2, "item");
+
+#undef LUA_TABLE_SETNUMBER
+#define LUA_TABLE_SETNUMBER(field) \
+       lua_pushnumber(script->L, user->field); \
+       lua_setfield(script->L, -2, #field);
+#undef LUA_TABLE_SETBOOL
+#define LUA_TABLE_SETBOOL(field) \
+       lua_pushboolean(script->L, user->field); \
+       lua_setfield(script->L, -2, #field);
+#undef LUA_TABLE_SETSTRING
+#define LUA_TABLE_SETSTRING(field) \
+       lua_pushstring(script->L, user->field); \
+       lua_setfield(script->L, -2, #field);
+
+       const char *home = NULL;
+       (void)mail_user_get_home(user, &home);
+
+       lua_pushstring(script->L, home);
+       lua_setfield(script->L, -2, "home");
+
+       LUA_TABLE_SETSTRING(username);
+       LUA_TABLE_SETNUMBER(uid);
+       LUA_TABLE_SETNUMBER(gid);
+       LUA_TABLE_SETSTRING(service);
+       LUA_TABLE_SETSTRING(session_id);
+       LUA_TABLE_SETNUMBER(session_create_time);
+
+       LUA_TABLE_SETBOOL(nonexistent);
+       LUA_TABLE_SETBOOL(anonymous);
+       LUA_TABLE_SETBOOL(autocreated);
+       LUA_TABLE_SETBOOL(mail_debug);
+       LUA_TABLE_SETBOOL(fuzzy_search);
+       LUA_TABLE_SETBOOL(dsyncing);
+       LUA_TABLE_SETBOOL(admin);
+       LUA_TABLE_SETBOOL(session_restored);
+}
+
+static struct mail_user *
+lua_check_storage_mail_user(struct dlua_script *script, int arg)
+{
+       if (!lua_istable(script->L, arg)) {
+               (void)luaL_error(script->L, "Bad argument #%d, expected %s got %s",
+                                arg, LUA_STORAGE_MAIL_USER,
+                                lua_typename(script->L, lua_type(script->L, arg)));
+       }
+       lua_pushliteral(script->L, "item");
+       lua_rawget(script->L, arg);
+       struct mail_user **bp = lua_touserdata(script->L, -1);
+       lua_pop(script->L, 1);
+       return *bp;
+}
+
+static int lua_storage_mail_user_tostring(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 1);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+
+       lua_pushstring(L, user->username);
+       return 1;
+}
+
+int lua_storage_cmp(struct dlua_script *script)
+{
+       const char *name_a, *name_b;
+       name_a = lua_tostring(script->L, 1);
+       name_b = lua_tostring(script->L, 2);
+
+       return strcmp(name_a, name_b);
+}
+
+static int lua_storage_mail_user_eq(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       bool res = lua_storage_cmp(script) == 0;
+       lua_pushboolean(script->L, res);
+       return 1;
+}
+
+static int lua_storage_mail_user_lt(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       bool res = lua_storage_cmp(script) <= 0;
+       lua_pushboolean(script->L, res);
+       return 1;
+}
+
+static int lua_storage_mail_user_le(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       bool res = lua_storage_cmp(script) < 0;
+       lua_pushboolean(script->L, res);
+       return 1;
+}
+
+static int lua_storage_mail_user_var_expand(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *error;
+       const char *format = luaL_checkstring(script->L, 2);
+       const struct var_expand_table *table = mail_user_var_expand_table(user);
+       string_t *str = t_str_new(128);
+       if (var_expand_with_funcs(str, format, table, mail_user_var_expand_func_table,
+                                 user, &error) < 0) {
+               return luaL_error(script->L, "var_expand(%s) failed: %s",
+                                 format, error);
+       }
+       lua_pushlstring(script->L, str->data, str->used);
+       return 1;
+}
+
+static int lua_storage_mail_user_plugin_getenv(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *set = lua_tostring(script->L, 2);
+       const char *val = mail_user_plugin_getenv(user, set);
+       lua_pushstring(script->L, val);
+       return 1;
+}
+
+static int lua_storage_mail_user_mailbox_alloc(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS_IN(script, 2, 3);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *mboxname = luaL_checkstring(script->L, 2);
+       enum mailbox_flags flags = 0;
+       if (lua_gettop(script->L) >= 3)
+               flags = luaL_checkinteger(script->L, 3);
+       struct mail_namespace *ns = mail_namespace_find(user->namespaces, mboxname);
+       if (ns == NULL) {
+               return luaL_error(script->L, "No namespace found for mailbox %s",
+                                 mboxname);
+       }
+       struct mailbox *mbox = mailbox_alloc(ns->list, mboxname, flags);
+       dlua_push_mailbox(script, mbox);
+       return 1;
+}
+
+static int lua_storage_mail_user_unref(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       struct mail_user **ptr = lua_touserdata(script->L, 1);
+       if (*ptr != NULL)
+               mail_user_unref(ptr);
+       *ptr = NULL;
+       return 0;
+}
+
+static const char *lua_storage_mail_user_metadata_key(const char *key)
+{
+       if (str_begins(key, "/private/")) {
+               return t_strdup_printf("/private/%s%s",
+                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
+                                      key + 9);
+       } else if (str_begins(key, "/shared/")) {
+               return t_strdup_printf("/shared/%s%s",
+                                      MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
+                                      key + 8);
+       }
+       return NULL;
+}
+
+static int lua_storage_mail_user_metadata_get(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+
+       const char *value, *error;
+       size_t value_len;
+       int ret, i, top = lua_gettop(script->L);
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX",
+                                            MAILBOX_FLAG_READONLY);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L, "Cannot open INBOX: %s", error);
+       }
+
+       ret = 0;
+       for(i = 2; i <= top; i++) {
+               /* reformat key */
+               const char *key = lua_tostring(script->L, i);
+
+               if (key == NULL) {
+                       ret = -1;
+                       error = t_strdup_printf("expected string at #%d", i);
+                       break;
+               }
+
+               if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+                       ret = -1;
+                       error = "Invalid key prefix, must be "
+                               "/private/ or /shared/";
+                       break;
+               }
+
+               if ((ret = lua_storage_mailbox_attribute_get(mbox, key, &value,
+                                                            &value_len, &error)) < 0) {
+                       break;
+               } else if (ret == 0) {
+                       lua_pushnil(script->L);
+               } else {
+                       lua_pushlstring(script->L, value, value_len);
+               }
+       }
+
+       mailbox_free(&mbox);
+
+       if (ret < 0)
+               return luaL_error(script->L, "%s", error);
+
+       i_assert(i>=2);
+       return i-2;
+}
+
+static int
+lua_storage_mail_user_set_metadata_unset(struct dlua_script *script,
+                                        struct mail_user *user,
+                                        const char *key, const char *value,
+                                        size_t value_len)
+{
+       const char *error;
+
+       /* reformat key */
+       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+               return luaL_error(script->L, "Invalid key prefix, must be "
+                                            "/private/ or /shared/");
+       }
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot open INBOX: %s", error);
+       }
+
+       if (lua_storage_mailbox_attribute_set(mbox, key, value, value_len,
+                                             &error) < 0) {
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot get attribute: %s", error);
+       }
+
+       mailbox_free(&mbox);
+       return 0;
+}
+
+static int lua_storage_mail_user_metadata_set(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 3);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+       const char *value;
+       size_t value_len;
+
+       value = lua_tolstring(script->L, 3, &value_len);
+
+       return lua_storage_mail_user_set_metadata_unset(script, user, key,
+                                                       value, value_len);
+}
+
+static int lua_storage_mail_user_metadata_unset(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       DLUA_REQUIRE_ARGS(script, 2);
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const char *key = luaL_checkstring(script->L, 2);
+
+       return lua_storage_mail_user_set_metadata_unset(script, user, key, NULL, 0);
+}
+
+static int lua_storage_mail_user_metadata_list(lua_State *L)
+{
+       struct dlua_script *script = dlua_script_from_state(L);
+       if (lua_gettop(script->L) < 2)
+               return luaL_error(script->L, "expecting at least 1 parameter");
+       struct mail_user *user = lua_check_storage_mail_user(script, 1);
+       const struct lua_storage_keyvalue *item;
+       const char *error;
+       ARRAY_TYPE(lua_storage_keyvalue) items;
+       int i, ret;
+
+       /* fetch INBOX, as user metadata is stored there */
+       struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+       struct mailbox *mbox = mailbox_alloc(ns->list, "INBOX", 0);
+
+       if (mailbox_open(mbox) < 0) {
+               error = mailbox_get_last_error(mbox, NULL);
+               mailbox_free(&mbox);
+               return luaL_error(script->L,
+                                 "Cannot open INBOX: %s", error);
+       }
+
+       T_BEGIN {
+               t_array_init(&items, 1);
+
+               ret = 0;
+               for(i = 2; i <= lua_gettop(script->L); i++) {
+                       const char *key = lua_tostring(script->L, i);
+
+                       if (key == NULL) {
+                               ret = -1;
+                               error = t_strdup_printf("expected string at #%d", i);
+                               break;
+                       }
+
+                       if ((key = lua_storage_mail_user_metadata_key(key)) == NULL) {
+                               ret = -1;
+                               error = "Invalid key prefix, must be "
+                                       "/private/ or /shared/";
+                               break;
+                       }
+
+                       if (lua_storage_mailbox_attribute_list(mbox, key, &items,
+                                                              &error) < 0) {
+                               ret = -1;
+                               break;
+                       }
+               }
+
+               if (ret == 0) {
+                       lua_createtable(script->L, 0, array_count(&items));
+                       array_foreach(&items, item) {
+                               char *ptr;
+                               char *key = t_strdup_noconst(item->key);
+                               if ((ptr = strstr(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) != NULL) {
+                                       const char *endp = ptr+strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER);
+                                       memmove(ptr, endp, strlen(endp));
+                                       memset(ptr+strlen(endp), '\0', 1);
+                               }
+                               /* push value */
+                               lua_pushlstring(script->L, item->value,
+                                               item->value_len);
+                               /* set field */
+                               lua_setfield(script->L, -2, key);
+                       }
+               }
+       } T_END;
+
+       mailbox_free(&mbox);
+
+       if (ret == -1)
+               return luaL_error(script->L, "%s", error);
+
+       /* stack should have table with items */
+       return 1;
+}
+
+static luaL_Reg lua_storage_mail_user_methods[] = {
+       { "__tostring", lua_storage_mail_user_tostring },
+       { "__eq", lua_storage_mail_user_eq },
+       { "__lt", lua_storage_mail_user_lt },
+       { "__le", lua_storage_mail_user_le },
+       { "plugin_getenv", lua_storage_mail_user_plugin_getenv },
+       { "var_expand", lua_storage_mail_user_var_expand },
+       { "mailbox", lua_storage_mail_user_mailbox_alloc },
+       { "metadata_get", lua_storage_mail_user_metadata_get },
+       { "metadata_set", lua_storage_mail_user_metadata_set },
+       { "metadata_unset", lua_storage_mail_user_metadata_unset },
+       { "metadata_list", lua_storage_mail_user_metadata_list },
+       { NULL, NULL }
+};
+
+void lua_storage_mail_user_register(struct dlua_script *script)
+{
+       luaL_newmetatable(script->L, LUA_STORAGE_MAIL_USER);
+       lua_pushvalue(script->L, -1);
+       lua_setfield(script->L, -2, "__index");
+       luaL_setfuncs(script->L, lua_storage_mail_user_methods, 0);
+       lua_pop(script->L, 1);
+}