]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
mail-lua: Add lua_call filter to var_expand
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 1 Oct 2024 11:44:59 +0000 (14:44 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:40:00 +0000 (10:40 +0200)
When mail-lua is loaded, it registers new var_expand filter
lua_call(fn, parameter, parameter)

This will call function in mail lua script with
fn(mail_user, parameter, parameter, .., state)

fn must (return 0, value) on success, and (-1, errmsg) on error

src/plugins/mail-lua/mail-lua-plugin.c

index 04c45fa895f2741a4c3c0eaa55b3a9059095bffe..8bc73f02fcc20414df231fcf87c79ed009fdde55 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "module-dir.h"
+#include "var-expand-private.h"
 #include "mail-lua-plugin.h"
 #include "mail-storage-lua.h"
 #include "mail-storage-private.h"
@@ -143,6 +144,61 @@ static void mail_lua_user_created(struct mail_user *user)
        MODULE_CONTEXT_SET(user, mail_lua_user_module, luser);
 }
 
+struct mail_lua_script {
+       char *file;
+       struct dlua_script *script;
+};
+
+static ARRAY(struct mail_lua_script) lua_scripts = ARRAY_INIT;
+
+static int
+mail_lua_script_cmp(const char *key, const struct mail_lua_script *script)
+{
+       return strcmp(key, script->file);
+}
+
+static void mail_lua_scripts_free(void)
+{
+       struct mail_lua_script *script;
+       if (array_is_empty(&lua_scripts))
+               return;
+       array_foreach_modifiable(&lua_scripts, script) {
+               i_free(script->file);
+               dlua_script_unref(&script->script);
+       }
+       array_free(&lua_scripts);
+}
+
+static int mail_lua_script_load(const char *file, struct dlua_script **script_r,
+                               const char **error_r)
+{
+       /* check if it's already there */
+       if (!array_is_empty(&lua_scripts)) {
+               const struct mail_lua_script *lookup =
+                       array_lsearch(&lua_scripts, file, mail_lua_script_cmp);
+               if (lookup != NULL) {
+                       *script_r = lookup->script;
+                       return 0;
+               }
+       }
+       struct dlua_script *script;
+       if (dlua_script_create_file(file, &script, NULL, error_r) < 0)
+               return -1;
+       dlua_dovecot_register(script);
+       if (dlua_script_init(script, error_r) < 0) {
+               dlua_script_unref(&script);
+               return -1;
+       }
+       /* register the script */
+       if (!array_is_created(&lua_scripts))
+               i_array_init(&lua_scripts, 1);
+       struct mail_lua_script *entry = array_append_space(&lua_scripts);
+       entry->file = i_strdup(file);
+       entry->script = script;
+       *script_r = script;
+       return 0;
+}
+
 bool mail_lua_plugin_get_script(struct mail_user *user,
                                struct dlua_script **script_r)
 {
@@ -154,6 +210,161 @@ bool mail_lua_plugin_get_script(struct mail_user *user,
        return FALSE;
 }
 
+static int mail_lua_script_call(const char *fn, struct mail_user *user,
+                               ARRAY_TYPE(const_string) *params,
+                               struct var_expand_state *state,
+                               struct dlua_script *script,
+                               const char **error_r)
+{
+       const char *value;
+       const char *error;
+       int npar = 0;
+
+       /* Push user as first parameter */
+       if (user != NULL) {
+               npar++;
+               dlua_push_mail_user(script->L, user);
+       }
+
+       /* Any user provided parameters come next */
+       array_foreach_elem(params, value) {
+               lua_pushstring(script->L, value);
+               npar++;
+       }
+
+       /* If there is state, push that too */
+       if (state->transfer_set) {
+               lua_pushlstring(script->L, state->transfer->data,
+                               state->transfer->used);
+               npar++;
+       }
+
+       /* Call fn(user, provided params.., state) */
+       if (dlua_pcall(script->L, fn, npar, 2, &error) < 0) {
+               *error_r = t_strdup_printf("%s(user) failed: %s", fn, error);
+               return -1;
+       }
+
+       int ret = lua_tonumber(script->L, -2);
+
+       if (ret < 0) {
+               var_expand_state_unset_transfer(state);
+               const char *errmsg = lua_tostring(script->L, -1);
+               *error_r = t_strdup_printf("%s(user) failed: %s",
+                                          fn, errmsg);
+       } else {
+               size_t len;
+               const void *value = lua_tolstring(script->L, -1, &len);
+               if (value == NULL)
+                       value = "";
+               var_expand_state_set_transfer_data(state, value, len);
+       }
+
+       lua_pop(script->L, 2);
+       (void)lua_gc(script->L, LUA_GCCOLLECT, 0);
+
+       return ret;
+}
+
+static int mail_lua_var_expand_lua_file(const struct var_expand_statement *stmt,
+                                       struct var_expand_state *state,
+                                       const char **error_r)
+{
+       const char *file;
+       const char *fn;
+       const char *value;
+
+       ARRAY_TYPE(const_string) params;
+       t_array_init(&params, 1);
+       struct var_expand_parameter_iter_context *iter =
+               var_expand_parameter_iter_init(stmt);
+       while (var_expand_parameter_iter_more(iter)) {
+               const struct var_expand_parameter *par =
+                       var_expand_parameter_iter_next(iter);
+               const char *key = var_expand_parameter_key(par);
+               if (key != NULL) {
+                       *error_r = t_strdup_printf("Unsupported key '%s'", key);
+                       return -1;
+               }
+               switch (var_expand_parameter_idx(par)) {
+               case 0:
+                       if (var_expand_parameter_string_or_var(state, par, &file, error_r) < 0)
+                               return -1;
+                       break;
+               case 1:
+                       if (var_expand_parameter_string_or_var(state, par, &fn, error_r) < 0)
+                               return -1;
+                       break;
+               default:
+                       if (var_expand_parameter_any_or_var(state, par, &value, error_r) < 0)
+                               return -1;
+                       array_push_back(&params, &value);
+               }
+       }
+
+       struct dlua_script *script;
+       if (mail_lua_script_load(file, &script, error_r) < 0)
+               return -1;
+
+       int ret = mail_lua_script_call(fn, NULL, &params, state, script, error_r);
+
+       /* normalize return value */
+       return ret < 0 ? -1 : 0;
+}
+
+static int mail_lua_var_expand_lua_call(const struct var_expand_statement *stmt,
+                                       struct var_expand_state *state,
+                                       const char **error_r)
+{
+       const char *fn;
+       const char *value;
+
+       ARRAY_TYPE(const_string) params;
+       t_array_init(&params, 1);
+       struct var_expand_parameter_iter_context *iter =
+               var_expand_parameter_iter_init(stmt);
+       while (var_expand_parameter_iter_more(iter)) {
+               const struct var_expand_parameter *par =
+                       var_expand_parameter_iter_next(iter);
+               const char *key = var_expand_parameter_key(par);
+               if (key != NULL) {
+                       *error_r = t_strdup_printf("Unsupported key '%s'", key);
+                       return -1;
+               }
+               if (var_expand_parameter_idx(par) == 0) {
+                       if (var_expand_parameter_string_or_var(state, par, &fn, error_r) < 0)
+                               return -1;
+               } else {
+                       if (var_expand_parameter_any_or_var(state, par, &value, error_r) < 0)
+                               return -1;
+                       array_push_back(&params, &value);
+               }
+       }
+
+       if (state->params->event == NULL) {
+               *error_r = "No mail user available";
+               return -1;
+       }
+
+       struct mail_user *user =
+               event_get_ptr(state->params->event, SETTINGS_EVENT_MAIL_USER);
+       if (user == NULL) {
+               *error_r = "No mail user available";
+               return -1;
+       }
+       struct dlua_script *script;
+
+       if (!mail_lua_plugin_get_script(user, &script)) {
+                *error_r = "User has no Lua script loaded";
+                return -1;
+       }
+
+       int ret = mail_lua_script_call(fn, user, &params, state, script, error_r);
+
+       /* normalize return value */
+       return ret < 0 ? -1 : 0;
+}
+
 static const struct mail_storage_hooks mail_lua_hooks = {
        .mail_user_created = mail_lua_user_created,
 };
@@ -161,11 +372,16 @@ static const struct mail_storage_hooks mail_lua_hooks = {
 void mail_lua_plugin_init(struct module *module)
 {
        mail_storage_hooks_add(module, &mail_lua_hooks);
+       var_expand_register_filter("lua_call", mail_lua_var_expand_lua_call);
+       var_expand_register_filter("lua_file", mail_lua_var_expand_lua_file);
 }
 
 void mail_lua_plugin_deinit(void)
 {
        mail_storage_hooks_remove(&mail_lua_hooks);
+       mail_lua_scripts_free();
+       var_expand_unregister_filter("lua_call");
+       var_expand_unregister_filter("lua_file");
 }
 
 const char *mail_lua_plugin_dependencies[] = { NULL };