summaryrefslogtreecommitdiffstats
path: root/src/director/user-directory.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/director/user-directory.c
parentInitial commit. (diff)
downloaddovecot-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.c349
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);
+}