dovecot-2.1: auth: Added CACHE-FLUSH command to flush some/all u...

dovecot at dovecot.org dovecot at dovecot.org
Wed Jul 4 10:58:52 EEST 2012


details:   http://hg.dovecot.org/dovecot-2.1/rev/007bf0047ab0
changeset: 14599:007bf0047ab0
user:      Timo Sirainen <tss at iki.fi>
date:      Wed Jul 04 10:56:53 2012 +0300
description:
auth: Added CACHE-FLUSH command to flush some/all users from auth cache.

diffstat:

 src/auth/auth-cache.c             |  228 +++++++++++++++++++++++++++----------
 src/auth/auth-cache.h             |    6 +-
 src/auth/auth-master-connection.c |   27 ++++
 src/auth/auth-request.h           |    4 +
 src/auth/auth.c                   |    9 +
 src/auth/test-auth-cache.c        |   18 ++-
 6 files changed, 226 insertions(+), 66 deletions(-)

diffs (truncated from 491 to 300 lines):

diff -r c1a97e799b43 -r 007bf0047ab0 src/auth/auth-cache.c
--- a/src/auth/auth-cache.c	Tue Jul 03 05:15:14 2012 +0300
+++ b/src/auth/auth-cache.c	Wed Jul 04 10:56:53 2012 +0300
@@ -23,36 +23,72 @@
 	unsigned long long pos_size, neg_size;
 };
 
-static const struct var_expand_table *
-auth_request_var_expand_tab_find(const char *key, unsigned int size)
+static bool
+auth_request_var_expand_tab_find(const char *key, unsigned int size,
+				 unsigned int *idx_r)
 {
 	const struct var_expand_table *tab = auth_request_var_expand_static_tab;
 	unsigned int i;
 
 	for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
 		if (size == 1) {
-			if (key[0] == tab[i].key)
-				return &tab[i];
+			if (key[0] == tab[i].key) {
+				*idx_r = i;
+				return TRUE;
+			}
 		} else if (tab[i].long_key != NULL) {
 			if (strncmp(key, tab[i].long_key, size) == 0 &&
-			    tab[i].long_key[size] == '\0')
-				return &tab[i];
+			    tab[i].long_key[size] == '\0') {
+				*idx_r = i;
+				return TRUE;
+			}
 		}
 	}
-	return NULL;
+	return FALSE;
+}
+
+static void
+auth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
+{
+	if (str_len(str) > 0)
+		str_append_c(str, '\t');
+	str_append_c(str, '%');
+	if (len == 1)
+		str_append_c(str, data[0]);
+	else {
+		str_append_c(str, '{');
+		str_append_n(str, data, len);
+		str_append_c(str, '}');
+	}
+}
+
+static void auth_cache_key_add_tab_idx(string_t *str, unsigned int i)
+{
+	const struct var_expand_table *tab =
+		&auth_request_var_expand_static_tab[i];
+
+	if (str_len(str) > 0)
+		str_append_c(str, '\t');
+	str_append_c(str, '%');
+	if (tab->key != '\0')
+		str_append_c(str, tab->key);
+	else {
+		str_append_c(str, '{');
+		str_append(str, tab->long_key);
+		str_append_c(str, '}');
+	}
 }
 
 char *auth_cache_parse_key(pool_t pool, const char *query)
 {
-	const struct var_expand_table *tab;
 	string_t *str;
-	bool key_seen[100];
-	unsigned int idx, size, tab_idx;
-	bool add_key;
+	bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT];
+	const char *extra_vars;
+	unsigned int i, idx, size, tab_idx;
 
 	memset(key_seen, 0, sizeof(key_seen));
 
-	str = str_new(pool, 32);
+	str = t_str_new(32);
 	for (; *query != '\0'; ) {
 		if (*query != '%') {
 			query++;
@@ -66,34 +102,45 @@
 		}
 		query += idx;
 
-		tab = auth_request_var_expand_tab_find(query, size);
-		if (tab == NULL) {
+		if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) {
 			/* just add the key. it would be nice to prevent
 			   duplicates here as well, but that's just too
 			   much trouble and probably very rare. */
-			add_key = TRUE;
+			auth_cache_key_add_var(str, query, size);
 		} else {
-			tab_idx = tab - auth_request_var_expand_static_tab;
 			i_assert(tab_idx < N_ELEMENTS(key_seen));
-			/* @UNSAFE */
-			add_key = !key_seen[tab_idx];
 			key_seen[tab_idx] = TRUE;
 		}
-		if (add_key) {
-			if (str_len(str) != 0)
-				str_append_c(str, '\t');
-			str_append_c(str, '%');
-			if (size == 1)
-				str_append_c(str, query[0]);
-			else {
-				str_append_c(str, '{');
-				str_append_n(str, query, size);
-				str_append_c(str, '}');
-			}
-		}
 		query += size;
 	}
-	return str_free_without_data(&str);
+
+	if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] &&
+	    key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) {
+		/* %n and %d both used -> replace with %u */
+		key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE;
+		key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE;
+		key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE;
+	}
+
+	/* we rely on these being at the beginning */
+	i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0);
+	i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1);
+	i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2);
+
+	extra_vars = t_strdup(str_c(str));
+	str_truncate(str, 0);
+	for (i = 0; i < N_ELEMENTS(key_seen); i++) {
+		if (key_seen[i])
+			auth_cache_key_add_tab_idx(str, i);
+	}
+
+	if (*extra_vars != '\0') {
+		if (str_len(str) > 0)
+			str_append_c(str, '\t');
+		str_append(str, extra_vars);
+	}
+
+	return p_strdup(pool, str_c(str));
 }
 
 static void
@@ -142,8 +189,8 @@
 {
 	struct auth_cache *cache = context;
 
-	i_info("SIGHUP received, clearing cache");
-	auth_cache_clear(cache);
+	i_info("SIGHUP received, %u cache entries flushed",
+	       auth_cache_clear(cache));
 }
 
 static void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context)
@@ -200,11 +247,69 @@
 	i_free(cache);
 }
 
-void auth_cache_clear(struct auth_cache *cache)
+unsigned int auth_cache_clear(struct auth_cache *cache)
 {
+	unsigned int ret = hash_table_count(cache->hash);
+
 	while (cache->tail != NULL)
 		auth_cache_node_destroy(cache, cache->tail);
 	hash_table_clear(cache->hash, FALSE);
+	return ret;
+}
+
+static bool auth_cache_node_is_user(struct auth_cache_node *node,
+				    const char *username)
+{
+	const char *data = node->data;
+	unsigned int username_len;
+
+	/* The cache nodes begin with "P"/"U", passdb/userdb ID, "/" and
+	   then usually followed by the username. It's too much trouble to
+	   keep track of all the cache keys, so we'll just match it as if it
+	   was the username. If e.g. '%n' is used in the cache key instead of
+	   '%u', it means that cache entries can be removed only when @domain
+	   isn't in the username parameter. */
+	if (*data != 'P' && *data != 'U')
+		return FALSE;
+	data++;
+
+	while (*data >= '0' && *data <= '9')
+		data++;
+	if (*data != '/')
+		return FALSE;
+	data++;
+
+	username_len = strlen(username);
+	return strncmp(data, username, username_len) == 0 &&
+		(data[username_len] == '\t' || data[username_len] == '\0');
+}
+
+static bool auth_cache_node_is_one_of_users(struct auth_cache_node *node,
+					    const char *const *usernames)
+{
+	unsigned int i;
+
+	for (i = 0; usernames[i] != NULL; i++) {
+		if (auth_cache_node_is_user(node, usernames[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+unsigned int auth_cache_clear_users(struct auth_cache *cache,
+				    const char *const *usernames)
+{
+	struct auth_cache_node *node, *next;
+	unsigned int ret = 0;
+
+	for (node = cache->tail; node != NULL; node = next) {
+		next = node->next;
+		if (auth_cache_node_is_one_of_users(node, usernames)) {
+			auth_cache_node_destroy(cache, cache->tail);
+			ret++;
+		}
+	}
+	return ret;
 }
 
 static const char *
@@ -216,12 +321,27 @@
 	return str_tabescape(string);
 }
 
+static const char *
+auth_request_expand_cache_key(const struct auth_request *request,
+			      const char *key)
+{
+	string_t *str;
+
+	/* Uniquely identify the request's passdb/userdb with the P/U prefix
+	   and by "%!", which expands to the passdb/userdb ID number. */
+	key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!/", key, NULL);
+
+	str = t_str_new(256);
+	var_expand(str, key,
+		   auth_request_get_var_expand_table(request, auth_cache_escape));
+	return str_c(str);
+}
+
 const char *
 auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
 		  const char *key, struct auth_cache_node **node_r,
 		  bool *expired_r, bool *neg_expired_r)
 {
-	string_t *str;
 	struct auth_cache_node *node;
 	const char *value;
 	unsigned int ttl_secs;
@@ -230,13 +350,8 @@
 	*expired_r = FALSE;
 	*neg_expired_r = FALSE;
 
-	/* %! is prepended automatically. it contains the passdb ID number. */
-	str = t_str_new(256);
-	var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P",
-				    "%!/", key, NULL),
-		   auth_request_get_var_expand_table(request, auth_cache_escape));
-
-	node = hash_table_lookup(cache->hash, str_c(str));
+	key = auth_request_expand_cache_key(request, key);
+	node = hash_table_lookup(cache->hash, key);
 	if (node == NULL) {
 		cache->miss_count++;
 		return NULL;
@@ -269,9 +384,8 @@
 void auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
 		       const char *key, const char *value, bool last_success)
 {
-	string_t *str;
         struct auth_cache_node *node;
-	size_t data_size, alloc_size, value_len = strlen(value);
+	size_t data_size, alloc_size, key_len, value_len = strlen(value);
 	char *current_username;
 
 	if (*value == '\0' && cache->neg_ttl_secs == 0) {
@@ -286,15 +400,12 @@
 	    request->requested_login_user == NULL)
 		request->user = t_strdup_noconst(request->translated_username);
 
-	/* %! is prepended automatically. it contains the db ID number. */
-	str = t_str_new(256);
-	var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P",
-				    "%!/", key, NULL),
-		   auth_request_get_var_expand_table(request, auth_cache_escape));


More information about the dovecot-cvs mailing list