diff options
Diffstat (limited to 'src/plugins/push-notification/push-notification-driver-lua.c')
-rw-r--r-- | src/plugins/push-notification/push-notification-driver-lua.c | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/src/plugins/push-notification/push-notification-driver-lua.c b/src/plugins/push-notification/push-notification-driver-lua.c new file mode 100644 index 0000000..e1178fa --- /dev/null +++ b/src/plugins/push-notification/push-notification-driver-lua.c @@ -0,0 +1,663 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "hash.h" +#include "dlua-script.h" +#include "dlua-script-private.h" + +#include "mail-storage.h" +#include "mail-user.h" +#include "mail-lua-plugin.h" +#include "mail-storage-lua.h" + +#include "push-notification-plugin.h" +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-message-common.h" +#include "push-notification-txn-mbox.h" +#include "push-notification-txn-msg.h" + +#include "push-notification-event-flagsclear.h" +#include "push-notification-event-flagsset.h" +#include "push-notification-event-mailboxcreate.h" +#include "push-notification-event-mailboxdelete.h" +#include "push-notification-event-mailboxrename.h" +#include "push-notification-event-mailboxsubscribe.h" +#include "push-notification-event-mailboxunsubscribe.h" +#include "push-notification-event-messageappend.h" +#include "push-notification-event-message-common.h" +#include "push-notification-event-messageexpunge.h" +#include "push-notification-event-messagenew.h" +#include "push-notification-event-messageread.h" +#include "push-notification-event-messagetrash.h" + +#define DLUA_LOG_USERENV_KEY "push_notification_lua_script_file" + +#define DLUA_FN_BEGIN_TXN "dovecot_lua_notify_begin_txn" +#define DLUA_FN_EVENT_PREFIX "dovecot_lua_notify_event" +#define DLUA_FN_END_TXN "dovecot_lua_notify_end_txn" + +#define DLUA_CALL_FINISHED "push_notification_lua_call_finished" + +struct dlua_push_notification_context { + struct dlua_script *script; + struct event *event; + bool debug; + + struct push_notification_event_messagenew_config config_mn; + struct push_notification_event_messageappend_config config_ma; + struct push_notification_event_flagsclear_config config_fc; + struct push_notification_event_flagsset_config config_fs; +}; + +struct dlua_push_notification_txn_context { + int tx_ref; +}; + +#define DLUA_DEFAULT_EVENTS (\ + PUSH_NOTIFICATION_MESSAGE_HDR_FROM | \ + PUSH_NOTIFICATION_MESSAGE_HDR_TO | \ + PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | \ + PUSH_NOTIFICATION_MESSAGE_HDR_DATE | \ + PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET | \ + PUSH_NOTIFICATION_MESSAGE_FLAGS | \ + PUSH_NOTIFICATION_MESSAGE_KEYWORDS | \ + PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID) + +static const char *push_notification_driver_lua_to_fn(const char *evname); + +static int +push_notification_driver_lua_init( + struct push_notification_driver_config *config, struct mail_user *user, + pool_t pool, void **context, const char **error_r) +{ + struct dlua_push_notification_context *ctx; + const char *tmp, *file; + struct event *event = event_create(user->event); + event_add_category(event, push_notification_get_event_category()); + event_set_append_log_prefix(event, "lua: "); + + if ((tmp = mail_user_plugin_getenv(user, DLUA_LOG_USERENV_KEY)) == NULL) + tmp = hash_table_lookup(config->config, (const char *)"file"); + + if (tmp == NULL) { + struct dlua_script *script; + /* If there is a script loaded, use the same context */ + if (mail_lua_plugin_get_script(user, &script)) { + dlua_script_ref(script); + ctx = p_new( + pool, struct dlua_push_notification_context, 1); + ctx->script = script; + ctx->event = event; + *context = ctx; + return 0; + } + + event_unref(&event); + *error_r = "No file in config and no " + DLUA_LOG_USERENV_KEY " set"; + return -1; + } + file = tmp; + + ctx = p_new(pool, struct dlua_push_notification_context, 1); + ctx->event = event; + + e_debug(ctx->event, "Loading %s", file); + + if (dlua_script_create_file(file, &ctx->script, event, error_r) < 0) { + /* There is a T_POP after this, which will break errors */ + event_unref(&event); + *error_r = p_strdup(pool, *error_r); + return -1; + } + + /* Register dovecot helpers */ + dlua_dovecot_register(ctx->script); + dlua_register_mail_storage(ctx->script); + + e_debug(ctx->event, "Calling script_init"); + + /* Initialize script */ + if (dlua_script_init(ctx->script, error_r) < 0) { + *error_r = p_strdup(pool, *error_r); + event_unref(&event); + dlua_script_unref(&ctx->script); + return -1; + } + + *context = ctx; + return 0; +} + +static bool +push_notification_driver_lua_init_events( + struct push_notification_driver_txn *dtxn) +{ + struct dlua_push_notification_context *ctx = dtxn->duser->context; + const struct push_notification_event *event; + ctx->config_mn.flags = DLUA_DEFAULT_EVENTS; + ctx->config_ma.flags = DLUA_DEFAULT_EVENTS; + ctx->config_fc.store_old = TRUE; + bool found_one = FALSE; + + /* Register *all* events that are present in Lua */ + array_foreach_elem(push_notification_get_events(), event) { + const char *name = event->name; + const char *fn = push_notification_driver_lua_to_fn(name); + if (!dlua_script_has_function(ctx->script, fn)) + continue; + + found_one = TRUE; + + e_debug(ctx->event, "Found %s, handling %s event", fn, name); + + if (strcmp(name, "MessageNew") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_mn); + } else if (strcmp(name, "MessageAppend") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_ma); + } else if (strcmp(name, "FlagsSet") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_fs); + } else if (strcmp(name, "FlagsClear") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_fc); + } else if (event->init.default_config != NULL) { + void *config = event->init.default_config(); + push_notification_event_init(dtxn, name, config); + } else { + push_notification_event_init(dtxn, name, NULL); + } + } + + return found_one; +} + +static bool +push_notification_driver_lua_begin_txn( + struct push_notification_driver_txn *dtxn) +{ + struct mail_user *user = dtxn->ptxn->muser; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct event *event = event_create(ctx->event); + const char *error; + + event_set_name(event, DLUA_CALL_FINISHED); + event_add_str(event, "function_name", DLUA_FN_BEGIN_TXN); + + if (!dlua_script_has_function(ctx->script, DLUA_FN_BEGIN_TXN)) { + event_add_str(event, "error", + "Missing function " DLUA_FN_BEGIN_TXN); + e_error(event, "Missing function " DLUA_FN_BEGIN_TXN); + event_unref(&event); + return FALSE; + } + + if (!push_notification_driver_lua_init_events(dtxn)) { + e_debug(event, "No event handlers found in script"); + event_unref(&event); + return FALSE; + } + + e_debug(ctx->event, "Calling " DLUA_FN_BEGIN_TXN "(%s)", + user->username); + + /* Push mail user as argument */ + dlua_push_mail_user(ctx->script->L, user); + if (dlua_pcall(ctx->script->L, DLUA_FN_BEGIN_TXN, 1, 1, &error) < 0) { + event_add_str(event, "error", error); + e_error(event, "%s", error); + return FALSE; + } + + e_debug(event, "Called " DLUA_FN_BEGIN_TXN); + event_unref(&event); + + /* Store the result */ + struct dlua_push_notification_txn_context *tctx = + p_new(dtxn->ptxn->pool, + struct dlua_push_notification_txn_context, 1); + + tctx->tx_ref = luaL_ref(ctx->script->L, LUA_REGISTRYINDEX); + dtxn->context = tctx; + mail_user_ref(user); + + return TRUE; +} + +/* This function only works here, it converts MessageType to event_message_type + */ +static const char *push_notification_driver_lua_to_fn(const char *evname) +{ + /* Camelcase to event_event_name (most events have two underscores) */ + string_t *fn = t_str_new(strlen(evname) + + strlen(DLUA_FN_EVENT_PREFIX) + 2); + str_append(fn, DLUA_FN_EVENT_PREFIX); + + for(; *evname != '\0'; evname++) { + if (*evname >= 'A' && *evname <= 'Z') { + str_append_c(fn, '_'); + str_append_c(fn, (*evname) - 'A' + 'a'); + } else { + str_append_c(fn, *evname); + } + } + + return str_c(fn); +} + +/* Pushes lua list of flags */ +static void dlua_push_flags(struct dlua_script *script, enum mail_flags flags) +{ + lua_newtable(script->L); + int idx = 1; + + if ((flags & MAIL_ANSWERED) != 0) { + lua_pushliteral(script->L, "\\Answered"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_FLAGGED) != 0) { + lua_pushliteral(script->L, "\\Flagged"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_DELETED) != 0) { + lua_pushliteral(script->L, "\\Deleted"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_SEEN) != 0) { + lua_pushliteral(script->L, "\\Seen"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_DRAFT) != 0) { + lua_pushliteral(script->L, "\\Draft"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_RECENT) != 0) { + lua_pushliteral(script->L, "\\Recent"); + lua_rawseti(script->L, -2, idx++); + } +} + +static void +dlua_push_keywords(struct dlua_script *script, const char *const *keywords, + unsigned int count) +{ + lua_newtable(script->L); + if (keywords == NULL) + return; + for (unsigned int idx = 0; idx < count; idx++) { + lua_pushstring(script->L, keywords[idx]); + lua_rawseti(script->L, -2, idx+1); + } +} + +static void +push_notification_lua_push_flagsclear( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + /* Push cleared flags */ + unsigned int size = 0; + struct push_notification_event_flagsclear_data *data = event->data; + + dlua_push_flags(script, data->flags_clear); + lua_setfield(script->L, -2, "flags"); + dlua_push_flags(script, data->flags_old); + lua_setfield(script->L, -2, "flags_old"); + + if (array_is_created(&data->keywords_clear)) { + const char *const *kw = array_get(&data->keywords_clear, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords"); + } + + if (array_is_created(&data->keywords_old)) { + const char *const *kw = array_get(&data->keywords_old, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords_old"); + } +} + +static void +push_notification_lua_push_flagsset( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + /* push set flags */ + unsigned int size = 0; + struct push_notification_event_flagsset_data *data = event->data; + + dlua_push_flags(script, data->flags_set); + lua_setfield(script->L, -2, "flags"); + + if (array_is_created(&data->keywords_set)) { + const char *const *kw = array_get(&data->keywords_set, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords"); + } +} + +static void +push_notification_lua_push_mailboxrename( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_mailboxrename_data *data = event->data; + + lua_pushstring(script->L, data->old_mbox); + lua_setfield(script->L, -2, "mailbox_old"); +} + +#define push_notification_lua_push_string(L, value) \ + lua_pushstring((L), (value) == NULL ? "" : (value)) + +static void +push_notification_lua_push_message_ext( + const struct push_notification_message_ext *ext, + struct dlua_script *script) +{ + push_notification_lua_push_string(script->L, ext->from_address); + lua_setfield(script->L, -2, "from_address"); + push_notification_lua_push_string(script->L, ext->from_display_name_utf8); + lua_setfield(script->L, -2, "from_display_name"); + + push_notification_lua_push_string(script->L, ext->to_address); + lua_setfield(script->L, -2, "to_address"); + push_notification_lua_push_string(script->L, ext->to_display_name_utf8); + lua_setfield(script->L, -2, "to_display_name"); + + lua_pushstring(script->L, ext->subject_utf8); + lua_setfield(script->L, -2, "subject"); +} + +static void +push_notification_lua_push_messageappend( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_messageappend_data *data = event->data; + + lua_pushnumber(script->L, data->date); + lua_setfield(script->L, -2, "date"); + + lua_pushnumber(script->L, data->date_tz); + lua_setfield(script->L, -2, "tz"); + + push_notification_lua_push_string(script->L, data->from); + lua_setfield(script->L, -2, "from"); + + push_notification_lua_push_string(script->L, data->to); + lua_setfield(script->L, -2, "to"); + + lua_pushstring(script->L, data->snippet); + lua_setfield(script->L, -2, "snippet"); + + dlua_push_flags(script, data->flags); + lua_setfield(script->L, -2, "flags"); + + dlua_push_keywords(script, data->keywords, + str_array_length(data->keywords)); + lua_setfield(script->L, -2, "keywords"); + + lua_pushstring(script->L, data->message_id); + lua_setfield(script->L, -2, "message_id"); + + push_notification_lua_push_message_ext(&data->ext, script); +} + +static void +push_notification_lua_push_messagenew( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_messagenew_data *data = event->data; + + lua_pushnumber(script->L, data->date); + lua_setfield(script->L, -2, "date"); + + lua_pushnumber(script->L, data->date_tz); + lua_setfield(script->L, -2, "tz"); + + push_notification_lua_push_string(script->L, data->from); + lua_setfield(script->L, -2, "from"); + + push_notification_lua_push_string(script->L, data->to); + lua_setfield(script->L, -2, "to"); + + lua_pushstring(script->L, data->snippet); + lua_setfield(script->L, -2, "snippet"); + + dlua_push_flags(script, data->flags); + lua_setfield(script->L, -2, "flags"); + + dlua_push_keywords(script, data->keywords, + str_array_length(data->keywords)); + lua_setfield(script->L, -2, "keywords"); + + lua_pushstring(script->L, data->message_id); + lua_setfield(script->L, -2, "message_id"); + + push_notification_lua_push_message_ext(&data->ext, script); +} + +/* Events that need special treatment */ +static struct push_notification_event_to_lua { + const char *event_name; + void (*push)(const struct push_notification_txn_event *event, + struct dlua_script *script); +} event_to_push_table[] = { + { + .event_name = "FlagsClear", + .push = push_notification_lua_push_flagsclear + }, + { + .event_name = "FlagsSet", + .push = push_notification_lua_push_flagsset + }, + { + .event_name = "MailboxRename", + .push = push_notification_lua_push_mailboxrename + }, + { + .event_name = "MessageAppend", + .push = push_notification_lua_push_messageappend + }, + { + .event_name = "MessageNew", + .push = push_notification_lua_push_messagenew + }, +}; + +static void +push_notification_driver_lua_push_event( + const struct push_notification_txn_event *event, + struct dlua_push_notification_context *ctx) +{ + struct dlua_script *script = ctx->script; + const char *name = event->event->event->name; + + /* Create a table */ + lua_newtable(script->L); + + /* Event name */ + lua_pushstring(script->L, name); + lua_setfield(script->L, -2, "name"); + + for(size_t i = 0; i < N_ELEMENTS(event_to_push_table); i++) + if (strcmp(event_to_push_table[i].event_name, name) == 0) + event_to_push_table[i].push(event, script); +} + +static void +push_notification_driver_lua_call( + struct dlua_push_notification_context *ctx, + struct dlua_push_notification_txn_context *tctx, + const struct push_notification_txn_event *event, + const struct push_notification_txn_mbox *mbox, + struct push_notification_txn_msg *msg) +{ + const char *error; + const char *fn = + push_notification_driver_lua_to_fn(event->event->event->name); + struct event *e = event_create(ctx->event); + event_set_name(e, DLUA_CALL_FINISHED); + event_add_str(e, "event_name", event->event->event->name); + event_add_str(e, "function_name", fn); + + /* Push context */ + lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + + /* Push event + common fields */ + push_notification_driver_lua_push_event(event, ctx); + + if (mbox != NULL) { + lua_pushstring(ctx->script->L, mbox->mailbox); + lua_setfield(ctx->script->L, -2, "mailbox"); + e_debug(ctx->event, + "Calling %s(ctx, event[name=%s,mailbox=%s])", + fn, event->event->event->name, mbox->mailbox); + event_add_str(e, "mailbox", mbox->mailbox); + } else if (msg != NULL) { + lua_pushstring(ctx->script->L, msg->mailbox); + lua_setfield(ctx->script->L, -2, "mailbox"); + lua_pushnumber(ctx->script->L, msg->uid); + lua_setfield(ctx->script->L, -2, "uid"); + lua_pushnumber(ctx->script->L, msg->uid_validity); + lua_setfield(ctx->script->L, -2, "uid_validity"); + e_debug(ctx->event, + "Calling %s(ctx, event[name=%s,mailbox=%s,uid=%u])", + fn, event->event->event->name, msg->mailbox, msg->uid); + event_add_str(e, "mailbox", msg->mailbox); + event_add_int(e, "uid", msg->uid); + } else + i_unreached(); + + /* Perform call */ + if (dlua_pcall(ctx->script->L, fn, 2, 0, &error) < 0) { + event_add_str(e, "error", error); + e_error(e, "%s", error); + } else { + e_debug(e, "Called %s", fn); + } + event_unref(&e); +} + +static void +push_notification_driver_lua_process_mbox( + struct push_notification_driver_txn *dtxn, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_txn_event *event; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + + if (array_is_created(&mbox->eventdata)) { + array_foreach_elem(&mbox->eventdata, event) { + push_notification_driver_lua_call(ctx, tctx, + event, mbox, NULL); + } + } +} + +static void +push_notification_driver_lua_process_msg( + struct push_notification_driver_txn *dtxn, + struct push_notification_txn_msg *msg) +{ + struct push_notification_txn_event *event; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + + if (array_is_created(&msg->eventdata)) { + array_foreach_elem(&msg->eventdata, event) { + push_notification_driver_lua_call(ctx, tctx, + event, NULL, msg); + } + } +} + +static void +push_notification_driver_lua_end_txn(struct push_notification_driver_txn *dtxn, + bool success) +{ + /* Call end txn */ + const char *error; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + struct mail_user *user = dtxn->ptxn->muser; + struct event *event = event_create(ctx->event); + event_set_name(event, DLUA_CALL_FINISHED); + event_add_str(event, "function_name", DLUA_FN_END_TXN); + + if (!dlua_script_has_function(ctx->script, DLUA_FN_END_TXN)) { + e_error(event, "Missing function " DLUA_FN_END_TXN); + } else { + e_debug(ctx->event, "Calling " DLUA_FN_END_TXN); + lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + lua_pushboolean(ctx->script->L, success); + if (dlua_pcall(ctx->script->L, DLUA_FN_END_TXN, 2, 0, &error) < 0) { + event_add_str(event, "error", error); + e_error(event, "%s", error); + } else { + e_debug(event, "Called " DLUA_FN_END_TXN); + } + } + + event_unref(&event); + /* Release context */ + luaL_unref(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + /* Call gc here */ + (void)lua_gc(ctx->script->L, LUA_GCCOLLECT, 1); + mail_user_unref(&user); +} + +static void +push_notification_driver_lua_deinit(struct push_notification_driver_user *duser) +{ + /* Call lua deinit */ + struct dlua_push_notification_context *ctx = duser->context; + dlua_script_unref(&ctx->script); + event_unref(&ctx->event); +} + +static void push_notification_driver_lua_cleanup(void) +{ + /* noop */ +} + +/* Driver definition */ + +struct push_notification_driver push_notification_driver_lua = { + .name = "lua", + .v = { + .init = push_notification_driver_lua_init, + .begin_txn = push_notification_driver_lua_begin_txn, + .process_mbox = push_notification_driver_lua_process_mbox, + .process_msg = push_notification_driver_lua_process_msg, + .end_txn = push_notification_driver_lua_end_txn, + .deinit = push_notification_driver_lua_deinit, + .cleanup = push_notification_driver_lua_cleanup, + }, +}; + +void push_notification_lua_plugin_init(struct module *module); +void push_notification_lua_plugin_deinit(void); + +void push_notification_lua_plugin_init(struct module *module ATTR_UNUSED) +{ + push_notification_driver_register(&push_notification_driver_lua); +} + +void push_notification_lua_plugin_deinit(void) +{ + push_notification_driver_unregister(&push_notification_driver_lua); +} + +const char *push_notification_lua_plugin_version = DOVECOT_ABI_VERSION; +const char *push_notification_lua_plugin_dependencies[] = + { "push_notification", "mail_lua", NULL}; |