last-login-plugin.c revision 0c5ed44cf93523f5ae6d118501a79fff6dddf7d7
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk/* Copyright (c) 2014-2016 Dovecot authors, see the included COPYING file */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "lib.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "ioloop.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "dict.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "mail-user.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "mail-namespace.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "mail-storage-private.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "mail-storage-hooks.h"
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk#include "last-login-plugin.h"
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk#define LAST_LOGIN_USER_CONTEXT(obj) \
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk MODULE_CONTEXT(obj, last_login_user_module)
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk#define LAST_LOGIN_DEFAULT_KEY_PREFIX "last-login/"
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenkstruct last_login_user {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk union mail_user_module_context module_ctx;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct dict *dict;
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk struct timeout *to;
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk};
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkconst char *last_login_plugin_version = DOVECOT_ABI_VERSION;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic MODULE_CONTEXT_DEFINE_INIT(last_login_user_module,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk &mail_user_module_register);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic void last_login_dict_deinit(struct mail_user *user)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (luser->dict != NULL) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_wait(luser->dict);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_deinit(&luser->dict);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk /* remove timeout after dict_wait(), which may trigger
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk last_login_dict_commit() */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (luser->to != NULL)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk timeout_remove(&luser->to);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic void last_login_user_deinit(struct mail_user *user)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk last_login_dict_deinit(user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk luser->module_ctx.super.deinit(user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic void
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenklast_login_dict_commit(const struct dict_commit_result *result,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk void *context)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct mail_user *user = context;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk switch(result->ret) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk case DICT_COMMIT_RET_OK:
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk case DICT_COMMIT_RET_NOTFOUND:
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk break;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk case DICT_COMMIT_RET_FAILED:
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk i_error("last_login_dict: Failed to write value for user %s: %s",
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk user->username, result->error);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk break;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk case DICT_COMMIT_RET_WRITE_UNCERTAIN:
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk i_error("last_login_dict: Write was unconfirmed (timeout or disconnect) for user %s: %s",
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk user->username, result->error);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk break;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk };
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk /* don't deinit the dict immediately here, lib-dict will just crash */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk luser->to = timeout_add(0, last_login_dict_deinit, user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic void last_login_mail_user_created(struct mail_user *user)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct mail_user_vfuncs *v = user->vlast;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct last_login_user *luser;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct dict *dict;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct dict_settings set;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct dict_transaction_context *trans;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk const char *dict_value, *key_name, *precision, *error;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (user->autocreated) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk /* we want to handle only logged in users,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk not lda's raw user or accessed shared users */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk return;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (user->session_restored) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk /* This is IMAP unhibernation, not a real login. */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk return;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_value = mail_user_plugin_getenv(user, "last_login_dict");
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (dict_value == NULL || dict_value[0] == '\0')
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk return;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk memset(&set, 0, sizeof(set));
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk set.username = user->username;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk set.base_dir = user->set->base_dir;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (mail_user_get_home(user, &set.home_dir) <= 0)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk set.home_dir = NULL;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (dict_init(dict_value, &set, &dict, &error) < 0) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk i_error("last_login_dict: dict_init(%s) failed: %s",
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_value, error);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk return;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk luser = p_new(user->pool, struct last_login_user, 1);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk luser->module_ctx.super = *v;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk user->vlast = &luser->module_ctx.super;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk v->deinit = last_login_user_deinit;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk luser->dict = dict;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk MODULE_CONTEXT_SET(user, last_login_user_module, luser);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk key_name = mail_user_plugin_getenv(user, "last_login_key");
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (key_name == NULL) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk key_name = t_strdup_printf(LAST_LOGIN_DEFAULT_KEY_PREFIX"%s",
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk user->username);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk key_name = t_strconcat(DICT_PATH_SHARED, key_name, NULL);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk precision = mail_user_plugin_getenv(user, "last_login_precision");
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk trans = dict_transaction_begin(dict);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk if (precision == NULL || strcmp(precision, "s") == 0)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_set(trans, key_name, dec2str(ioloop_time));
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk else if (strcmp(precision, "ms") == 0) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_set(trans, key_name, t_strdup_printf(
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk "%ld%03u", (long)ioloop_timeval.tv_sec,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk (unsigned int)(ioloop_timeval.tv_usec/1000)));
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk } else if (strcmp(precision, "us") == 0) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_set(trans, key_name, t_strdup_printf(
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk "%ld%06u", (long)ioloop_timeval.tv_sec,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk (unsigned int)ioloop_timeval.tv_usec));
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk } else if (strcmp(precision, "ns") == 0) {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_set(trans, key_name, t_strdup_printf(
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk "%ld%06u000", (long)ioloop_timeval.tv_sec,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk (unsigned int)ioloop_timeval.tv_usec));
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk } else {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk i_error("last_login_dict: Invalid last_login_precision '%s'", precision);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk }
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_transaction_no_slowness_warning(trans);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk dict_transaction_commit_async(&trans, last_login_dict_commit, user);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic struct mail_storage_hooks last_login_mail_storage_hooks = {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk .mail_user_created = last_login_mail_user_created
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk};
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkvoid last_login_plugin_init(struct module *module)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk mail_storage_hooks_add(module, &last_login_mail_storage_hooks);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkvoid last_login_plugin_deinit(void)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk mail_storage_hooks_remove(&last_login_mail_storage_hooks);
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk}
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk