user-directory.c revision 430d256a6244b2a780c298febc66e74d6b6e8cc2
0N/A/* Copyright (c) 2010-2013 Dovecot authors, see the included COPYING file */
2362N/A
0N/A#include "lib.h"
0N/A#include "ioloop.h"
0N/A#include "array.h"
0N/A#include "hash.h"
2362N/A#include "llist.h"
0N/A#include "mail-user-hash.h"
2362N/A#include "mail-host.h"
0N/A#include "user-directory.h"
0N/A
0N/A/* n% of timeout_secs */
0N/A#define USER_NEAR_EXPIRING_PERCENTAGE 10
0N/A/* but min/max. of this many secs */
0N/A#define USER_NEAR_EXPIRING_MIN 3
0N/A#define USER_NEAR_EXPIRING_MAX 30
0N/A
0N/Astruct user_directory_iter {
0N/A struct user_directory *dir;
0N/A struct user *pos;
2362N/A};
2362N/A
2362N/Astruct user_directory {
0N/A /* unsigned int username_hash => user */
0N/A HASH_TABLE(void *, struct user *) hash;
0N/A /* sorted by time */
0N/A struct user *head, *tail;
0N/A struct user *prev_insert_pos;
0N/A
0N/A ARRAY(struct user_directory_iter *) iters;
0N/A
0N/A char *username_hash_fmt;
0N/A unsigned int timeout_secs;
0N/A /* If user's expire time is less than this many seconds away,
0N/A don't assume that other directors haven't yet expired it */
0N/A unsigned int user_near_expiring_secs;
0N/A};
0N/A
0N/Astatic void user_move_iters(struct user_directory *dir, struct user *user)
0N/A{
0N/A struct user_directory_iter *const *iterp;
0N/A
0N/A array_foreach(&dir->iters, iterp) {
0N/A if ((*iterp)->pos == user)
0N/A (*iterp)->pos = user->next;
0N/A }
0N/A
0N/A if (dir->prev_insert_pos == user)
0N/A dir->prev_insert_pos = user->next;
0N/A}
0N/A
0N/Astatic void user_free(struct user_directory *dir, struct user *user)
0N/A{
0N/A i_assert(user->host->user_count > 0);
0N/A user->host->user_count--;
0N/A
0N/A user_move_iters(dir, user);
5976N/A
0N/A hash_table_remove(dir->hash, POINTER_CAST(user->username_hash));
5976N/A DLLIST2_REMOVE(&dir->head, &dir->tail, user);
5976N/A i_free(user);
5976N/A}
5976N/A
5976N/Astatic bool user_directory_user_has_connections(struct user_directory *dir,
0N/A struct user *user)
0N/A{
5976N/A time_t expire_timestamp = user->timestamp + dir->timeout_secs;
5976N/A
5976N/A if (expire_timestamp > ioloop_time)
5976N/A return TRUE;
0N/A
0N/A if (user->kill_state != USER_KILL_STATE_NONE) {
28N/A /* don't free this user until the kill is finished */
28N/A return TRUE;
0N/A }
0N/A
0N/A if (user->weak) {
0N/A if (expire_timestamp + USER_NEAR_EXPIRING_MAX >= ioloop_time)
0N/A return TRUE;
28N/A
28N/A i_warning("User %u weakness appears to be stuck, removing it",
0N/A user->username_hash);
0N/A }
0N/A return FALSE;
0N/A}
0N/A
0N/Astatic void user_directory_drop_expired(struct user_directory *dir)
0N/A{
0N/A while (dir->head != NULL &&
0N/A !user_directory_user_has_connections(dir, dir->head))
0N/A user_free(dir, dir->head);
0N/A}
0N/A
0N/Astruct user *user_directory_lookup(struct user_directory *dir,
0N/A unsigned int username_hash)
0N/A{
0N/A struct user *user;
0N/A
0N/A user_directory_drop_expired(dir);
0N/A user = hash_table_lookup(dir->hash, POINTER_CAST(username_hash));
0N/A if (user != NULL && !user_directory_user_has_connections(dir, user)) {
0N/A user_free(dir, user);
0N/A user = NULL;
0N/A }
0N/A return user;
0N/A}
0N/A
0N/Astatic void
0N/Auser_directory_insert_backwards(struct user_directory *dir,
0N/A struct user *pos, struct user *user)
0N/A{
0N/A for (; pos != NULL; pos = pos->prev) {
0N/A if (pos->timestamp <= user->timestamp)
0N/A break;
0N/A }
0N/A if (pos == NULL)
0N/A DLLIST2_PREPEND(&dir->head, &dir->tail, user);
0N/A else {
0N/A user->prev = pos;
0N/A user->next = pos->next;
0N/A user->prev->next = user;
0N/A if (user->next != NULL)
0N/A user->next->prev = user;
0N/A else
0N/A dir->tail = user;
0N/A }
0N/A}
0N/A
0N/Astatic void
0N/Auser_directory_insert_forwards(struct user_directory *dir,
0N/A struct user *pos, struct user *user)
0N/A{
0N/A for (; pos != NULL; pos = pos->next) {
0N/A if (pos->timestamp >= user->timestamp)
0N/A break;
0N/A }
0N/A if (pos == NULL)
0N/A DLLIST2_APPEND(&dir->head, &dir->tail, user);
0N/A else {
0N/A user->prev = pos->prev;
28N/A user->next = pos;
0N/A if (user->prev != NULL)
0N/A user->prev->next = user;
0N/A else
0N/A dir->head = user;
0N/A user->next->prev = user;
0N/A }
0N/A}
0N/A
0N/Astruct user *
0N/Auser_directory_add(struct user_directory *dir, unsigned int username_hash,
0N/A struct mail_host *host, time_t timestamp)
0N/A{
0N/A struct user *user;
0N/A
0N/A /* make sure we don't add timestamps higher than ioloop time */
0N/A if (timestamp > ioloop_time)
0N/A timestamp = ioloop_time;
0N/A
0N/A user = i_new(struct user, 1);
0N/A user->username_hash = username_hash;
0N/A user->host = host;
0N/A user->host->user_count++;
28N/A user->timestamp = timestamp;
0N/A
28N/A if (dir->tail == NULL || (time_t)dir->tail->timestamp <= timestamp)
0N/A DLLIST2_APPEND(&dir->head, &dir->tail, user);
0N/A else {
0N/A /* need to insert to correct position. we should get here
0N/A only when handshaking. the handshaking USER requests should
0N/A come sorted by timestamp. so keep track of the previous
0N/A insert position, the next USER should be inserted after
0N/A it. */
0N/A if (dir->prev_insert_pos == NULL) {
0N/A /* find the position starting from tail */
0N/A user_directory_insert_backwards(dir, dir->tail, user);
0N/A } else if (timestamp < (time_t)dir->prev_insert_pos->timestamp) {
0N/A user_directory_insert_backwards(dir, dir->prev_insert_pos,
0N/A user);
0N/A } else {
0N/A user_directory_insert_forwards(dir, dir->prev_insert_pos,
0N/A user);
0N/A }
0N/A }
0N/A
0N/A dir->prev_insert_pos = user;
0N/A hash_table_insert(dir->hash, POINTER_CAST(user->username_hash), user);
0N/A return user;
0N/A}
0N/A
0N/Avoid user_directory_refresh(struct user_directory *dir, struct user *user)
0N/A{
0N/A user_move_iters(dir, user);
0N/A
0N/A user->timestamp = ioloop_time;
0N/A DLLIST2_REMOVE(&dir->head, &dir->tail, user);
0N/A DLLIST2_APPEND(&dir->head, &dir->tail, user);
0N/A}
0N/A
0N/Avoid user_directory_remove_host(struct user_directory *dir,
0N/A struct mail_host *host)
0N/A{
0N/A struct user *user, *next;
0N/A
0N/A for (user = dir->head; user != NULL; user = next) {
0N/A next = user->next;
0N/A
0N/A if (user->host == host)
0N/A user_free(dir, user);
0N/A }
0N/A}
0N/A
0N/Astatic int user_timestamp_cmp(struct user *const *user1,
0N/A struct user *const *user2)
0N/A{
0N/A if ((*user1)->timestamp < (*user2)->timestamp)
0N/A return -1;
0N/A if ((*user1)->timestamp > (*user2)->timestamp)
0N/A return 1;
0N/A return 0;
0N/A}
0N/A
0N/Avoid user_directory_sort(struct user_directory *dir)
0N/A{
0N/A ARRAY(struct user *) users;
0N/A struct user *user, *const *userp;
0N/A unsigned int i, users_count = hash_table_count(dir->hash);
0N/A
0N/A if (users_count == 0) {
0N/A i_assert(dir->head == NULL);
0N/A return;
0N/A }
0N/A
0N/A /* place all users into array and sort it */
0N/A i_array_init(&users, users_count);
0N/A user = dir->head;
0N/A for (i = 0; i < users_count; i++, user = user->next)
0N/A array_append(&users, &user, 1);
0N/A i_assert(user == NULL);
0N/A array_sort(&users, user_timestamp_cmp);
0N/A
0N/A /* recreate the linked list */
0N/A dir->head = dir->tail = NULL;
0N/A array_foreach(&users, userp)
0N/A DLLIST2_APPEND(&dir->head, &dir->tail, *userp);
0N/A i_assert(dir->head != NULL &&
0N/A dir->head->timestamp <= dir->tail->timestamp);
0N/A array_free(&users);
0N/A}
0N/A
0N/Aunsigned int user_directory_get_username_hash(struct user_directory *dir,
0N/A const char *username)
0N/A{
0N/A return mail_user_hash(username, dir->username_hash_fmt);
0N/A}
0N/A
0N/Abool user_directory_user_is_recently_updated(struct user_directory *dir,
0N/A struct user *user)
0N/A{
0N/A return (time_t)(user->timestamp + dir->timeout_secs/2) >= ioloop_time;
0N/A}
0N/A
0N/Abool user_directory_user_is_near_expiring(struct user_directory *dir,
0N/A struct user *user)
0N/A{
0N/A time_t expire_timestamp;
0N/A
0N/A expire_timestamp = user->timestamp +
0N/A (dir->timeout_secs - dir->user_near_expiring_secs);
0N/A return expire_timestamp < ioloop_time;
0N/A}
0N/A
0N/Astruct user_directory *
0N/Auser_directory_init(unsigned int timeout_secs, const char *username_hash_fmt)
0N/A{
0N/A struct user_directory *dir;
0N/A
0N/A i_assert(timeout_secs > USER_NEAR_EXPIRING_MIN);
0N/A
0N/A dir = i_new(struct user_directory, 1);
0N/A dir->timeout_secs = timeout_secs;
0N/A dir->user_near_expiring_secs =
0N/A timeout_secs * USER_NEAR_EXPIRING_PERCENTAGE / 100;
0N/A dir->user_near_expiring_secs =
0N/A I_MIN(dir->user_near_expiring_secs, USER_NEAR_EXPIRING_MAX);
0N/A dir->user_near_expiring_secs =
28N/A I_MAX(dir->user_near_expiring_secs, USER_NEAR_EXPIRING_MIN);
0N/A i_assert(dir->timeout_secs/2 > dir->user_near_expiring_secs);
0N/A
0N/A dir->username_hash_fmt = i_strdup(username_hash_fmt);
0N/A hash_table_create_direct(&dir->hash, default_pool, 0);
0N/A i_array_init(&dir->iters, 8);
0N/A return dir;
0N/A}
0N/A
0N/Avoid user_directory_deinit(struct user_directory **_dir)
0N/A{
0N/A struct user_directory *dir = *_dir;
0N/A
0N/A *_dir = NULL;
0N/A
0N/A i_assert(array_count(&dir->iters) == 0);
0N/A
0N/A while (dir->head != NULL)
0N/A user_free(dir, dir->head);
0N/A hash_table_destroy(&dir->hash);
0N/A array_free(&dir->iters);
0N/A i_free(dir->username_hash_fmt);
0N/A i_free(dir);
0N/A}
0N/A
0N/Astruct user_directory_iter *
0N/Auser_directory_iter_init(struct user_directory *dir)
0N/A{
0N/A struct user_directory_iter *iter;
0N/A
0N/A iter = i_new(struct user_directory_iter, 1);
0N/A iter->dir = dir;
0N/A iter->pos = dir->head;
0N/A array_append(&dir->iters, &iter, 1);
0N/A user_directory_drop_expired(dir);
0N/A return iter;
0N/A}
0N/A
0N/Astruct user *user_directory_iter_next(struct user_directory_iter *iter)
0N/A{
0N/A struct user *user;
user = iter->pos;
if (user == NULL)
return FALSE;
iter->pos = user->next;
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;
}
}
i_free(iter);
}