diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/director/user-directory.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/director/user-directory.c')
-rw-r--r-- | src/director/user-directory.c | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/director/user-directory.c b/src/director/user-directory.c new file mode 100644 index 0000000..ea7f6e0 --- /dev/null +++ b/src/director/user-directory.c @@ -0,0 +1,349 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "hash.h" +#include "llist.h" +#include "mail-host.h" +#include "director.h" + +/* n% of timeout_secs */ +#define USER_NEAR_EXPIRING_PERCENTAGE 10 +/* but min/max. of this many secs */ +#define USER_NEAR_EXPIRING_MIN 3 +#define USER_NEAR_EXPIRING_MAX 30 +/* This shouldn't matter what it is exactly, just try it sometimes later. */ +#define USER_BEING_KILLED_EXPIRE_RETRY_SECS 60 + +struct user_directory_iter { + struct user_directory *dir; + struct user *pos, *stop_after_tail; +}; + +struct user_directory { + struct director *director; + + /* unsigned int username_hash => user */ + HASH_TABLE(void *, struct user *) hash; + /* sorted by time. may be unsorted while handshakes are going on. */ + struct user *head, *tail; + + ARRAY(struct user_directory_iter *) iters; + user_free_hook_t *user_free_hook; + + unsigned int timeout_secs; + /* If user's expire time is less than this many seconds away, + don't assume that other directors haven't yet expired it */ + unsigned int user_near_expiring_secs; + struct timeout *to_expire; + time_t to_expire_timestamp; + + bool sort_pending; +}; + +static void user_move_iters(struct user_directory *dir, struct user *user) +{ + struct user_directory_iter *iter; + + array_foreach_elem(&dir->iters, iter) { + if (iter->pos == user) + iter->pos = user->next; + if (iter->stop_after_tail == user) { + iter->stop_after_tail = + user->prev != NULL ? user->prev : user->next; + } + } +} + +static void user_free(struct user_directory *dir, struct user *user) +{ + i_assert(user->host->user_count > 0); + user->host->user_count--; + + if (dir->user_free_hook != NULL) + dir->user_free_hook(user); + user_move_iters(dir, user); + + hash_table_remove(dir->hash, POINTER_CAST(user->username_hash)); + DLLIST2_REMOVE(&dir->head, &dir->tail, user); + i_free(user); +} + +static bool user_directory_user_has_connections(struct user_directory *dir, + struct user *user, + time_t *expire_timestamp_r) +{ + time_t expire_timestamp = user->timestamp + dir->timeout_secs; + + if (expire_timestamp > ioloop_time) { + *expire_timestamp_r = expire_timestamp; + return TRUE; + } + + if (USER_IS_BEING_KILLED(user)) { + /* don't free this user until the kill is finished */ + *expire_timestamp_r = ioloop_time + + USER_BEING_KILLED_EXPIRE_RETRY_SECS; + return TRUE; + } + + if (user->weak) { + if (expire_timestamp + USER_NEAR_EXPIRING_MAX > ioloop_time) { + *expire_timestamp_r = expire_timestamp + + USER_NEAR_EXPIRING_MAX; + return TRUE; + } + + e_warning(dir->director->event, + "User %u weakness appears to be stuck, removing it", + user->username_hash); + } + return FALSE; +} + +static void user_directory_drop_expired(struct user_directory *dir) +{ + time_t expire_timestamp = 0; + + while (dir->head != NULL && + !user_directory_user_has_connections(dir, dir->head, &expire_timestamp)) { + user_free(dir, dir->head); + expire_timestamp = 0; + } + i_assert(expire_timestamp > ioloop_time || expire_timestamp == 0); + + if (expire_timestamp != dir->to_expire_timestamp) { + timeout_remove(&dir->to_expire); + if (expire_timestamp != 0) { + struct timeval tv = { .tv_sec = expire_timestamp }; + dir->to_expire_timestamp = tv.tv_sec; + dir->to_expire = timeout_add_absolute(&tv, + user_directory_drop_expired, dir); + } + } +} + +unsigned int user_directory_count(struct user_directory *dir) +{ + return hash_table_count(dir->hash); +} + +struct user *user_directory_lookup(struct user_directory *dir, + unsigned int username_hash) +{ + struct user *user; + time_t expire_timestamp; + + user_directory_drop_expired(dir); + user = hash_table_lookup(dir->hash, POINTER_CAST(username_hash)); + if (user != NULL && !user_directory_user_has_connections(dir, user, &expire_timestamp)) { + user_free(dir, user); + user = NULL; + } + return user; +} + +struct user * +user_directory_add(struct user_directory *dir, unsigned int username_hash, + struct mail_host *host, time_t timestamp) +{ + struct user *user; + + /* make sure we don't add timestamps higher than ioloop time */ + if (timestamp > ioloop_time) + timestamp = ioloop_time; + + user = i_new(struct user, 1); + user->username_hash = username_hash; + user->host = host; + user->host->user_count++; + user->timestamp = timestamp; + DLLIST2_APPEND(&dir->head, &dir->tail, user); + + if (dir->to_expire == NULL) { + struct timeval tv = { .tv_sec = ioloop_time + dir->timeout_secs }; + dir->to_expire_timestamp = tv.tv_sec; + dir->to_expire = timeout_add_absolute(&tv, user_directory_drop_expired, dir); + } + hash_table_insert(dir->hash, POINTER_CAST(user->username_hash), user); + return user; +} + +void user_directory_refresh(struct user_directory *dir, struct user *user) +{ + user_move_iters(dir, user); + + user->timestamp = ioloop_time; + DLLIST2_REMOVE(&dir->head, &dir->tail, user); + DLLIST2_APPEND(&dir->head, &dir->tail, user); +} + +void user_directory_remove_host(struct user_directory *dir, + struct mail_host *host) +{ + struct user *user, *next; + + for (user = dir->head; user != NULL; user = next) { + next = user->next; + + if (user->host == host) + user_free(dir, user); + } +} + +static int user_timestamp_cmp(struct user *const *user1, + struct user *const *user2) +{ + if ((*user1)->timestamp < (*user2)->timestamp) + return -1; + if ((*user1)->timestamp > (*user2)->timestamp) + return 1; + return 0; +} + +void user_directory_sort(struct user_directory *dir) +{ + ARRAY(struct user *) users; + struct user *user; + unsigned int i, users_count = hash_table_count(dir->hash); + + dir->sort_pending = FALSE; + + if (users_count == 0) { + i_assert(dir->head == NULL); + return; + } + + if (array_count(&dir->iters) > 0) { + /* We can't sort the directory while there are iterators + or they'll skip users. Do the sort after there are no more + iterators. */ + dir->sort_pending = TRUE; + return; + } + + /* place all users into array and sort it */ + i_array_init(&users, users_count); + user = dir->head; + for (i = 0; i < users_count; i++, user = user->next) + array_push_back(&users, &user); + i_assert(user == NULL); + array_sort(&users, user_timestamp_cmp); + + /* recreate the linked list */ + dir->head = dir->tail = NULL; + array_foreach_elem(&users, user) + DLLIST2_APPEND(&dir->head, &dir->tail, user); + i_assert(dir->head != NULL && + dir->head->timestamp <= dir->tail->timestamp); + array_free(&users); +} + +bool user_directory_user_is_recently_updated(struct user_directory *dir, + struct user *user) +{ + return (time_t)(user->timestamp + dir->timeout_secs/2) >= ioloop_time; +} + +bool user_directory_user_is_near_expiring(struct user_directory *dir, + struct user *user) +{ + time_t expire_timestamp; + + expire_timestamp = user->timestamp + + (dir->timeout_secs - dir->user_near_expiring_secs); + return expire_timestamp < ioloop_time; +} + +struct user_directory * +user_directory_init(struct director *director, unsigned int timeout_secs, + user_free_hook_t *user_free_hook) +{ + struct user_directory *dir; + + i_assert(timeout_secs > USER_NEAR_EXPIRING_MIN); + + dir = i_new(struct user_directory, 1); + dir->director = director; + dir->timeout_secs = timeout_secs; + dir->user_near_expiring_secs = + timeout_secs * USER_NEAR_EXPIRING_PERCENTAGE / 100; + dir->user_near_expiring_secs = + I_MIN(dir->user_near_expiring_secs, USER_NEAR_EXPIRING_MAX); + dir->user_near_expiring_secs = + I_MAX(dir->user_near_expiring_secs, USER_NEAR_EXPIRING_MIN); + i_assert(dir->timeout_secs/2 > dir->user_near_expiring_secs); + + dir->user_free_hook = user_free_hook; + hash_table_create_direct(&dir->hash, default_pool, 0); + i_array_init(&dir->iters, 8); + return dir; +} + +void user_directory_deinit(struct user_directory **_dir) +{ + struct user_directory *dir = *_dir; + + *_dir = NULL; + + i_assert(array_count(&dir->iters) == 0); + + while (dir->head != NULL) + user_free(dir, dir->head); + timeout_remove(&dir->to_expire); + hash_table_destroy(&dir->hash); + array_free(&dir->iters); + i_free(dir); +} + +struct user_directory_iter * +user_directory_iter_init(struct user_directory *dir, + bool iter_until_current_tail) +{ + struct user_directory_iter *iter; + + iter = i_new(struct user_directory_iter, 1); + iter->dir = dir; + iter->pos = dir->head; + iter->stop_after_tail = iter_until_current_tail ? dir->tail : NULL; + array_push_back(&dir->iters, &iter); + user_directory_drop_expired(dir); + return iter; +} + +struct user *user_directory_iter_next(struct user_directory_iter *iter) +{ + struct user *user; + + user = iter->pos; + if (user == NULL) + return NULL; + + iter->pos = user->next; + if (user == iter->stop_after_tail) { + /* this is the last user we want to iterate */ + iter->pos = NULL; + } + return user; +} + +void user_directory_iter_deinit(struct user_directory_iter **_iter) +{ + struct user_directory_iter *iter = *_iter; + struct user_directory_iter *const *iters; + unsigned int i, count; + + *_iter = NULL; + + iters = array_get(&iter->dir->iters, &count); + for (i = 0; i < count; i++) { + if (iters[i] == iter) { + array_delete(&iter->dir->iters, i, 1); + break; + } + } + if (array_count(&iter->dir->iters) == 0 && iter->dir->sort_pending) + user_directory_sort(iter->dir); + i_free(iter); +} |