changeset 2798:54b29901a793 HEAD

Added simple LRU cache for auth requests. Currently only for sql passdb.
author Timo Sirainen <tss@iki.fi>
date Thu, 21 Oct 2004 05:23:09 +0300
parents 322b580e0eb4
children be9a3a706b7a
files dovecot-example.conf src/auth/Makefile.am src/auth/auth-cache.c src/auth/auth-cache.h src/auth/mech.c src/auth/mech.h src/auth/passdb-cache.c src/auth/passdb-cache.h src/auth/passdb-ldap.c src/auth/passdb-passwd-file.c src/auth/passdb-sql.c src/auth/passdb.c src/auth/passdb.h src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h
diffstat 16 files changed, 573 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Thu Oct 21 02:34:34 2004 +0300
+++ b/dovecot-example.conf	Thu Oct 21 05:23:09 2004 +0300
@@ -468,6 +468,12 @@
 # Set max. process size in megabytes.
 #auth_process_size = 256
 
+# Authentication cache size in kilobytes.
+#auth_cache_size = 0
+# Time to live in seconds for cached data. After this many seconds a cached
+# record is forced out of cache.
+#auth_cache_ttl = 3600
+
 # Space separated list of realms for SASL authentication mechanisms that need
 # them. You can leave it empty if you don't want to support multiple realms.
 # Many clients simply use the first one listed here, so keep the default realm
--- a/src/auth/Makefile.am	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/Makefile.am	Thu Oct 21 05:23:09 2004 +0300
@@ -32,6 +32,7 @@
 	$(MODULE_LIBS)
 
 dovecot_auth_SOURCES = \
+	auth-cache.c \
 	auth-client-connection.c \
 	auth-master-connection.c \
 	auth-module.c \
@@ -50,6 +51,7 @@
 	mech-apop.c \
 	passdb.c \
 	passdb-bsdauth.c \
+	passdb-cache.c \
 	passdb-ldap.c \
 	passdb-passwd.c \
 	passdb-passwd-file.c \
@@ -67,6 +69,7 @@
 	userdb-sql.c
 
 noinst_HEADERS = \
+	auth-cache.h \
 	auth-client-connection.h \
 	auth-client-interface.h \
 	auth-master-interface.h \
@@ -79,6 +82,7 @@
 	mech.h \
 	mycrypt.h \
 	passdb.h \
+	passdb-cache.h \
 	password-scheme.h \
 	userdb.h \
 	userdb-vpopmail.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth-cache.c	Thu Oct 21 05:23:09 2004 +0300
@@ -0,0 +1,181 @@
+/* Copyright (C) 2004 Timo Sirainen */
+
+#include "common.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "mech.h"
+#include "auth-cache.h"
+
+#include <time.h>
+
+struct cache_node {
+	struct cache_node *prev, *next;
+	time_t created;
+	uint32_t alloc_size;
+	char data[4]; /* key \0 value \0 */
+};
+
+struct auth_cache {
+	struct hash_table *hash;
+	struct cache_node *head, *tail;
+
+	size_t size_left;
+	unsigned int ttl_secs;
+};
+
+char *auth_cache_parse_key(const char *query)
+{
+	string_t *str;
+
+	str = str_new(default_pool, 32);
+	for (; *query != '\0'; query++) {
+		if (*query == '%' && query[1] != '\0') {
+			query++;
+			if (*query != '%') {
+				if (str_len(str) != 0)
+					str_append_c(str, '\t');
+				str_append_c(str, '%');
+				str_append_c(str, *query);
+			}
+		}
+	}
+	return str_c_modifyable(str);
+}
+
+static void
+auth_cache_node_unlink(struct auth_cache *cache, struct cache_node *node)
+{
+	if (node->prev != NULL)
+		node->prev->next = node->next;
+	else {
+		/* unlinking tail */
+		cache->tail = node->next;
+	}
+
+	if (node->next != NULL)
+		node->next->prev = node->prev;
+	else {
+		/* unlinking head */
+		cache->head = node->prev;
+	}
+}
+
+static void
+auth_cache_node_link_head(struct auth_cache *cache, struct cache_node *node)
+{
+	node->prev = cache->head;
+	cache->head = node;
+	if (node->prev != NULL)
+		node->prev->next = node;
+	else
+		cache->tail = node;
+}
+
+static void
+auth_cache_node_destroy(struct auth_cache *cache, struct cache_node *node)
+{
+	auth_cache_node_unlink(cache, node);
+
+	cache->size_left += node->alloc_size;
+	hash_remove(cache->hash, node->data);
+	i_free(node);
+}
+
+struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs)
+{
+	struct auth_cache *cache;
+
+	cache = i_new(struct auth_cache, 1);
+	cache->hash = hash_create(default_pool, default_pool, 0, str_hash,
+				  (hash_cmp_callback_t *)strcmp);
+	cache->size_left = max_size;
+	cache->ttl_secs = ttl_secs;
+	return cache;
+}
+
+void auth_cache_free(struct auth_cache *cache)
+{
+        auth_cache_clear(cache);
+	hash_destroy(cache->hash);
+	i_free(cache);
+}
+
+void auth_cache_clear(struct auth_cache *cache)
+{
+	while (cache->tail != NULL)
+		auth_cache_node_destroy(cache, cache->tail);
+	hash_clear(cache->hash, FALSE);
+}
+
+const char *auth_cache_lookup(struct auth_cache *cache,
+			      const struct auth_request *request,
+			      const char *key)
+{
+	string_t *str;
+        struct cache_node *node;
+
+	str = t_str_new(256);
+	var_expand(str, key,
+		   auth_request_get_var_expand_table(request, str_escape));
+
+	node = hash_lookup(cache->hash, str_c(str));
+	if (node == NULL)
+		return NULL;
+
+	if (node->created < time(NULL) - (time_t)cache->ttl_secs) {
+		/* TTL expired, destroy */
+		auth_cache_node_destroy(cache, node);
+		return NULL;
+	}
+
+	/* move to head */
+	if (node != cache->head) {
+		auth_cache_node_unlink(cache, node);
+		auth_cache_node_link_head(cache, node);
+	}
+
+	return node->data + strlen(node->data) + 1;
+}
+
+void auth_cache_insert(struct auth_cache *cache,
+		       const struct auth_request *request,
+		       const char *key, const char *value)
+{
+	string_t *str;
+	time_t now, ttl_time;
+        struct cache_node *node;
+	size_t data_size, alloc_size, value_len = strlen(value);
+
+	now = time(NULL);
+
+	str = t_str_new(256);
+	var_expand(str, key,
+		   auth_request_get_var_expand_table(request, str_escape));
+
+	data_size = str_len(str) + 1 + value_len + 1;
+	alloc_size = sizeof(struct cache_node) - sizeof(node->data) + data_size;
+
+	ttl_time = now - cache->ttl_secs;
+	while (cache->tail != NULL && cache->tail->created < ttl_time) {
+		/* TTL expired, destroy */
+		auth_cache_node_destroy(cache, cache->tail);
+	}
+
+	/* make sure we have enough space */
+	while (cache->size_left < alloc_size)
+		auth_cache_node_destroy(cache, cache->tail);
+
+	/* @UNSAFE */
+	node = i_malloc(alloc_size);
+	node->created = now;
+	node->alloc_size = alloc_size;
+	memcpy(node->data, str_data(str), str_len(str));
+	memcpy(node->data + str_len(str) + 1, value, value_len);
+
+	auth_cache_node_link_head(cache, node);
+
+	cache->size_left -= alloc_size;
+	hash_insert(cache->hash, node->data, node);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth-cache.h	Thu Oct 21 05:23:09 2004 +0300
@@ -0,0 +1,30 @@
+#ifndef __AUTH_CACHE_H
+#define __AUTH_CACHE_H
+
+struct auth_cache;
+struct auth_request;
+
+/* Parses all %x variables from query and compresses them into tab-separated
+   list, so it can be used as a cache key. */
+char *auth_cache_parse_key(const char *query);
+
+/* Create a new cache. max_size specifies the maximum amount of memory in
+   bytes to use for cache (it's not fully exact). ttl_secs specifies time to
+   live for cache record, requests older than that are not used. */
+struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs);
+void auth_cache_free(struct auth_cache *cache);
+
+/* Clear the cache. */
+void auth_cache_clear(struct auth_cache *cache);
+
+/* Look key from cache. key should be the same string as returned by
+   auth_cache_parse_key(). */
+const char *auth_cache_lookup(struct auth_cache *cache,
+			      const struct auth_request *request,
+			      const char *key);
+/* Insert key => value into cache. */
+void auth_cache_insert(struct auth_cache *cache,
+		       const struct auth_request *request,
+		       const char *key, const char *value);
+
+#endif
--- a/src/auth/mech.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/mech.c	Thu Oct 21 05:23:09 2004 +0300
@@ -5,14 +5,22 @@
 #include "buffer.h"
 #include "hash.h"
 #include "mech.h"
+#include "safe-memset.h"
 #include "str.h"
 #include "str-sanitize.h"
 #include "var-expand.h"
 #include "auth-client-connection.h"
 #include "auth-master-connection.h"
+#include "passdb-cache.h"
 
 #include <stdlib.h>
 
+struct auth_request_extra {
+	struct auth_request *request;
+	string_t *str;
+	char *user_password, *password;
+};
+
 struct mech_module_list *mech_modules;
 buffer_t *mech_handshake;
 
@@ -187,6 +195,103 @@
 	return FALSE;
 }
 
+struct auth_request_extra *
+auth_request_extra_begin(struct auth_request *request,
+			 const char *user_password)
+{
+	struct auth_request_extra *extra;
+
+	extra = i_new(struct auth_request_extra, 1);
+	extra->request = request;
+	extra->user_password = i_strdup(user_password);
+	return extra;
+}
+
+void auth_request_extra_next(struct auth_request_extra *extra,
+			     const char *name, const char *value)
+{
+	string_t *str;
+
+	i_assert(value != NULL);
+
+	if (strcmp(name, "password") == 0) {
+		i_assert(extra->password == NULL);
+		extra->password = i_strdup(value);
+		return;
+	}
+
+	if (strcmp(name, "nodelay") == 0) {
+		/* don't delay replying to client of the failure */
+		extra->request->no_failure_delay = TRUE;
+		return;
+	}
+
+	str = extra->str;
+	if (str == NULL)
+		extra->str = str = str_new(extra->request->pool, 64);
+
+	if (strcmp(name, "nologin") == 0) {
+		/* user can't actually login - don't keep this
+		   reply for master */
+		extra->request->no_login = TRUE;
+		if (str_len(str) > 0)
+			str_append_c(str, '\t');
+		str_append(str, name);
+	} else if (strcmp(name, "proxy") == 0) {
+		/* we're proxying authentication for this user. send
+		   password back if using plaintext authentication. */
+		extra->request->proxy = TRUE;
+		if (str_len(str) > 0)
+			str_append_c(str, '\t');
+		str_append(str, name);
+	} else {
+		if (str_len(str) > 0)
+			str_append_c(str, '\t');
+		str_printfa(str, "%s=%s", name, value);
+	}
+}
+
+void auth_request_extra_finish(struct auth_request_extra *extra,
+			       const char *cache_key)
+{
+	string_t *str;
+
+	if (passdb_cache != NULL && cache_key != NULL) {
+		str = t_str_new(64);
+		str_append_str(str, extra->str);
+		if (extra->request->no_failure_delay) {
+			if (str_len(str) > 0)
+				str_append_c(str, '\t');
+			str_append(str, "nodelay");
+		}
+		auth_cache_insert(passdb_cache, extra->request, cache_key,
+				  t_strconcat(extra->password == NULL ? "" :
+					      extra->password, "\t",
+					      str_c(str), NULL));
+	}
+
+	if (extra->user_password != NULL) {
+		if (extra->request->proxy) {
+			/* we're proxying - send back the password that was
+			   sent by user (not the password in passdb). */
+			str_printfa(extra->str, "\tpass=%s",
+				    extra->user_password);
+		}
+		safe_memset(extra->user_password, 0,
+			    strlen(extra->user_password));
+		i_free(extra->user_password);
+	}
+
+	if (extra->str != NULL)
+		extra->request->extra_fields = str_c(extra->str);
+
+	if (extra->password != NULL) {
+		safe_memset(extra->password, 0, strlen(extra->password));
+		i_free(extra->password);
+	}
+	i_free(extra);
+}
+
 static const char *escape_none(const char *str)
 {
 	return str;
--- a/src/auth/mech.h	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/mech.h	Thu Oct 21 05:23:09 2004 +0300
@@ -89,6 +89,13 @@
 void auth_request_ref(struct auth_request *request);
 int auth_request_unref(struct auth_request *request);
 
+struct auth_request_extra *
+auth_request_extra_begin(struct auth_request *request, const char *password);
+void auth_request_extra_next(struct auth_request_extra *extra,
+			     const char *name, const char *value);
+void auth_request_extra_finish(struct auth_request_extra *extra,
+			       const char *cache_key);
+
 const struct var_expand_table *
 auth_request_get_var_expand_table(const struct auth_request *auth_request,
 				  const char *(*escape_func)(const char *));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/passdb-cache.c	Thu Oct 21 05:23:09 2004 +0300
@@ -0,0 +1,143 @@
+/* Copyright (C) 2004 Timo Sirainen */
+
+#include "common.h"
+#include "password-scheme.h"
+#include "passdb.h"
+#include "passdb-cache.h"
+
+#include <stdlib.h>
+
+struct auth_cache *passdb_cache = NULL;
+
+static void list_save(struct auth_request *request, const char *password,
+		      const char *const *list)
+{
+	struct auth_request_extra *extra;
+	const char *name, *value;
+
+	if (*list == NULL)
+		return;
+
+	extra = auth_request_extra_begin(request, password);
+	for (; *list != NULL; list++) {
+		t_push();
+		value = strchr(*list, '=');
+		if (value == NULL) {
+			name = *list;
+			value = "";
+		} else {
+			name = t_strcut(*list, '=');
+			value++;
+		}
+
+		auth_request_extra_next(extra, name, value);
+		t_pop();
+	}
+	auth_request_extra_finish(extra, NULL);
+}
+
+int passdb_cache_verify_plain(struct auth_request *request, const char *key,
+			      const char *password, const char *default_scheme,
+			      enum passdb_result *result_r)
+{
+	const char *value, *cached_pw, *scheme, *const *list;
+	int ret;
+
+	i_assert(default_scheme != NULL);
+
+	if (passdb_cache == NULL)
+		return FALSE;
+
+	/* value = password \t ... */
+	value = auth_cache_lookup(passdb_cache, request, key);
+	if (value == NULL)
+		return FALSE;
+
+	if (*value == '\0') {
+		/* negative cache entry */
+		*result_r = PASSDB_RESULT_USER_UNKNOWN;
+		return TRUE;
+	}
+
+	list = t_strsplit(value, "\t");
+	cached_pw = list[0];
+
+	scheme = password_get_scheme(&cached_pw);
+	if (scheme == NULL)
+		scheme = default_scheme;
+        list_save(request, password, list+1);
+
+	ret = password_verify(password, cached_pw, scheme, request->user);
+	if (ret < 0) {
+		i_error("cache(%s): Unknown password scheme %s",
+			get_log_prefix(request), scheme);
+	} else if (ret == 0) {
+		if (verbose) {
+			i_info("cache(%s): Password mismatch",
+			       get_log_prefix(request));
+		}
+	}
+
+	*result_r = ret > 0 ? PASSDB_RESULT_OK :
+		PASSDB_RESULT_PASSWORD_MISMATCH;
+	return TRUE;
+}
+
+int passdb_cache_lookup_credentials(struct auth_request *request,
+				    const char *key, const char **result_r,
+				    const char **scheme_r)
+{
+	const char *value, *const *list;
+	const char *cached_pw;
+
+	if (passdb_cache == NULL)
+		return FALSE;
+
+	value = auth_cache_lookup(passdb_cache, request, key);
+	if (value == NULL)
+		return FALSE;
+
+	if (*value == '\0') {
+		/* negative cache entry */
+		*result_r = NULL;
+		*scheme_r = NULL;
+		return TRUE;
+	}
+
+	list = t_strsplit(value, "\t");
+        list_save(request, NULL, list+1);
+
+	*result_r = list[0];
+	*scheme_r = password_get_scheme(&cached_pw);
+	return TRUE;
+}
+
+void passdb_cache_init(void)
+{
+	const char *env;
+	size_t max_size;
+	unsigned int cache_ttl;
+
+	env = getenv("CACHE_SIZE");
+	if (env == NULL)
+		return;
+
+	max_size = (size_t)strtoul(env, NULL, 10) * 1024;
+	if (max_size == 0)
+		return;
+
+	env = getenv("CACHE_TTL");
+	if (env == NULL)
+		return;
+
+	cache_ttl = (unsigned int)strtoul(env, NULL, 10);
+	if (cache_ttl == 0)
+		return;
+
+	passdb_cache = auth_cache_new(max_size, cache_ttl);
+}
+
+void passdb_cache_deinit(void)
+{
+	auth_cache_free(passdb_cache);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/passdb-cache.h	Thu Oct 21 05:23:09 2004 +0300
@@ -0,0 +1,19 @@
+#ifndef __PASSDB_CACHE_H
+#define __PASSDB_CACHE_H
+
+#include "auth-cache.h"
+
+enum passdb_result;
+extern struct auth_cache *passdb_cache;
+
+int passdb_cache_verify_plain(struct auth_request *request, const char *key,
+			      const char *password, const char *default_scheme,
+			      enum passdb_result *result_r);
+int passdb_cache_lookup_credentials(struct auth_request *request,
+				    const char *key, const char **result_r,
+				    const char **scheme_r);
+
+void passdb_cache_init(void);
+void passdb_cache_deinit(void);
+
+#endif
--- a/src/auth/passdb-ldap.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/passdb-ldap.c	Thu Oct 21 05:23:09 2004 +0300
@@ -11,6 +11,7 @@
 #include "password-scheme.h"
 #include "db-ldap.h"
 #include "passdb.h"
+#include "passdb-cache.h"
 
 #include <ldap.h>
 #include <stdlib.h>
@@ -45,6 +46,7 @@
 };
 
 static struct passdb_ldap_connection *passdb_ldap_conn;
+static char *passdb_ldap_cache_key;
 
 static void handle_request(struct ldap_connection *conn,
 			   struct ldap_request *request, LDAPMessage *res)
@@ -116,7 +118,7 @@
 
 	if (ldap_request->credentials != -1) {
 		passdb_handle_credentials(ldap_request->credentials,
-			user, password, scheme,
+			password, scheme,
 			ldap_request->callback.lookup_credentials,
 			auth_request);
 		return;
@@ -177,7 +179,15 @@
 ldap_verify_plain(struct auth_request *request, const char *password,
 		  verify_plain_callback_t *callback)
 {
+	struct ldap_connection *conn = passdb_ldap_conn->conn;
 	struct passdb_ldap_request *ldap_request;
+	enum passdb_result result;
+
+	if (passdb_cache_verify_plain(request, passdb_ldap_cache_key, password,
+				      conn->set.default_pass_scheme, &result)) {
+		callback(result, request);
+		return;
+	}
 
 	ldap_request = i_malloc(sizeof(struct passdb_ldap_request) +
 				strlen(password));
@@ -193,6 +203,18 @@
 				    lookup_credentials_callback_t *callback)
 {
 	struct passdb_ldap_request *ldap_request;
+	const char *result, *scheme;
+
+	if (passdb_cache_lookup_credentials(request, passdb_ldap_cache_key,
+					    &result, &scheme)) {
+		if (scheme == NULL) {
+			scheme = passdb_ldap_conn->conn->set.
+				default_pass_scheme;
+		}
+		passdb_handle_credentials(credentials, result, scheme,
+					  callback, request);
+		return;
+	}
 
 	ldap_request = i_new(struct passdb_ldap_request, 1);
 	ldap_request->credentials = credentials;
@@ -212,6 +234,7 @@
 			  conn->set.pass_attrs : DEFAULT_ATTRIBUTES,
 			  &passdb_ldap_conn->attrs,
 			  &passdb_ldap_conn->attr_names);
+	passdb_ldap_cache_key = auth_cache_parse_key(conn->set.pass_filter);
 }
 
 static void passdb_ldap_init(const char *args __attr_unused__)
@@ -222,6 +245,7 @@
 static void passdb_ldap_deinit(void)
 {
 	db_ldap_unref(passdb_ldap_conn->conn);
+	i_free(passdb_ldap_cache_key);
 	i_free(passdb_ldap_conn);
 }
 
--- a/src/auth/passdb-passwd-file.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/passdb-passwd-file.c	Thu Oct 21 05:23:09 2004 +0300
@@ -63,7 +63,7 @@
 	crypted_pass = pu->password;
 	scheme = password_get_scheme(&crypted_pass);
 
-	passdb_handle_credentials(credentials, request->user, crypted_pass,
+	passdb_handle_credentials(credentials, crypted_pass,
 				  scheme, callback, request);
 }
 
--- a/src/auth/passdb-sql.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/passdb-sql.c	Thu Oct 21 05:23:09 2004 +0300
@@ -12,6 +12,7 @@
 #include "password-scheme.h"
 #include "db-sql.h"
 #include "passdb.h"
+#include "passdb-cache.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -28,67 +29,28 @@
 };
 
 static struct sql_connection *passdb_sql_conn;
+static char *passdb_sql_cache_key;
 
 static void result_save_extra_fields(struct sql_result *result,
-                                     struct passdb_sql_request *sql_request,
+				     struct passdb_sql_request *sql_request,
 				     struct auth_request *auth_request)
 {
+	struct auth_request_extra *extra;
 	unsigned int i, fields_count;
 	const char *name, *value;
-	string_t *str;
+
+	extra = auth_request_extra_begin(auth_request, sql_request->password);
 
 	fields_count = sql_result_get_fields_count(result);
-	if (fields_count == 1)
-		return;
-
-	str = NULL;
 	for (i = 0; i < fields_count; i++) {
 		name = sql_result_get_field_name(result, i);
 		value = sql_result_get_field_value(result, i);
 
-		if (value == NULL)
-			continue;
-
-		if (strcmp(name, "password") == 0)
-			continue;
-
-		if (strcmp(name, "nodelay") == 0) {
-			/* don't delay replying to client of the failure */
-			auth_request->no_failure_delay = TRUE;
-			continue;
-		}
-
-		if (str == NULL)
-			str = str_new(auth_request->pool, 64);
-
-		if (strcmp(name, "nologin") == 0) {
-			/* user can't actually login - don't keep this
-			   reply for master */
-			auth_request->no_login = TRUE;
-			if (str_len(str) > 0)
-				str_append_c(str, '\t');
-			str_append(str, name);
-		} else if (strcmp(name, "proxy") == 0) {
-			/* we're proxying authentication for this user. send
-			   password back if using plaintext authentication. */
-			auth_request->proxy = TRUE;
-			if (str_len(str) > 0)
-				str_append_c(str, '\t');
-			str_append(str, name);
-
-			if (*sql_request->password != '\0') {
-				str_printfa(str, "\tpass=%s",
-					    sql_request->password);
-			}
-		} else {
-			if (str_len(str) > 0)
-				str_append_c(str, '\t');
-			str_printfa(str, "%s=%s", name, value);
-		}
+		if (value != NULL)
+			auth_request_extra_next(extra, name, value);
 	}
 
-	if (str != NULL)
-		auth_request->extra_fields = str_c(str);
+	auth_request_extra_finish(extra, passdb_sql_cache_key);
 }
 
 static void sql_query_callback(struct sql_result *result, void *context)
@@ -111,6 +73,10 @@
 			i_info("sql(%s): Unknown user",
 			       get_log_prefix(auth_request));
 		}
+		if (passdb_cache != NULL) {
+			auth_cache_insert(passdb_cache, auth_request,
+					  passdb_sql_cache_key, "");
+		}
 	} else if ((idx = sql_result_find_field(result, "password")) < 0) {
 		i_error("sql(%s): Password query didn't return password",
 			get_log_prefix(auth_request));
@@ -136,7 +102,7 @@
 
 	if (sql_request->credentials != -1) {
 		passdb_handle_credentials(sql_request->credentials,
-			user, password, scheme,
+			password, scheme,
 			sql_request->callback.lookup_credentials,
 			auth_request);
 		return;
@@ -182,6 +148,14 @@
 			     verify_plain_callback_t *callback)
 {
 	struct passdb_sql_request *sql_request;
+	enum passdb_result result;
+
+	if (passdb_cache_verify_plain(request, passdb_sql_cache_key, password,
+				      passdb_sql_conn->set.default_pass_scheme,
+				      &result)) {
+		callback(result, request);
+		return;
+	}
 
 	sql_request = i_malloc(sizeof(struct passdb_sql_request) +
 			       strlen(password));
@@ -198,6 +172,16 @@
 				   lookup_credentials_callback_t *callback)
 {
 	struct passdb_sql_request *sql_request;
+	const char *result, *scheme;
+
+	if (passdb_cache_lookup_credentials(request, passdb_sql_cache_key,
+					    &result, &scheme)) {
+		if (scheme == NULL)
+			scheme = passdb_sql_conn->set.default_pass_scheme;
+		passdb_handle_credentials(credentials, result, scheme,
+					  callback, request);
+		return;
+	}
 
 	sql_request = i_new(struct passdb_sql_request, 1);
 	sql_request->auth_request = request;
@@ -210,6 +194,8 @@
 static void passdb_sql_preinit(const char *args)
 {
 	passdb_sql_conn = db_sql_init(args);
+	passdb_sql_cache_key =
+		auth_cache_parse_key(passdb_sql_conn->set.password_query);
 }
 
 static void passdb_sql_init(const char *args __attr_unused__)
@@ -220,6 +206,7 @@
 static void passdb_sql_deinit(void)
 {
 	db_sql_unref(passdb_sql_conn);
+	i_free(passdb_sql_cache_key);
 }
 
 struct passdb_module passdb_sql = {
--- a/src/auth/passdb.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/passdb.c	Thu Oct 21 05:23:09 2004 +0300
@@ -5,6 +5,7 @@
 #include "auth-module.h"
 #include "password-scheme.h"
 #include "passdb.h"
+#include "passdb-cache.h"
 
 #include <stdlib.h>
 
@@ -41,8 +42,7 @@
 }
 
 void passdb_handle_credentials(enum passdb_credentials credentials,
-			       const char *user, const char *password,
-			       const char *scheme,
+			       const char *password, const char *scheme,
 			       lookup_credentials_callback_t *callback,
                                struct auth_request *auth_request)
 {
@@ -63,13 +63,15 @@
 			    strcasecmp(scheme, "CLEARTEXT") == 0) {
 				/* we can generate anything out of plaintext
 				   passwords */
-				password = password_generate(password, user,
+				password = password_generate(password,
+							     auth_request->user,
 							     wanted_scheme);
 			} else {
 				if (verbose) {
 					i_info("password(%s): Requested %s "
 					       "scheme, but we have only %s",
-					       user, wanted_scheme, scheme);
+					       auth_request->user,
+					       wanted_scheme, scheme);
 				}
 				password = NULL;
 			}
@@ -173,6 +175,7 @@
 
 void passdb_init(void)
 {
+	passdb_cache_init();
 	if (passdb->init != NULL)
 		passdb->init(passdb_args);
 }
@@ -185,5 +188,6 @@
 	if (passdb_module != NULL)
                 auth_module_close(passdb_module);
 #endif
+	passdb_cache_deinit();
 	i_free(passdb_args);
 }
--- a/src/auth/passdb.h	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/auth/passdb.h	Thu Oct 21 05:23:09 2004 +0300
@@ -49,8 +49,7 @@
 };
 
 void passdb_handle_credentials(enum passdb_credentials credentials,
-			       const char *user, const char *password,
-			       const char *scheme,
+			       const char *password, const char *scheme,
 			       lookup_credentials_callback_t *callback,
                                struct auth_request *auth_request);
 
--- a/src/master/auth-process.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/master/auth-process.c	Thu Oct 21 05:23:09 2004 +0300
@@ -442,6 +442,8 @@
 			    group->set->username_translation, NULL));
 	env_put(t_strconcat("ANONYMOUS_USERNAME=",
 			    group->set->anonymous_username, NULL));
+	env_put(t_strdup_printf("CACHE_SIZE=%u", group->set->cache_size));
+	env_put(t_strdup_printf("CACHE_TTL=%u", group->set->cache_ttl));
 
 	for (as = group->set->sockets, i = 1; as != NULL; as = as->next, i++) {
 		if (strcmp(as->type, "listen") != 0)
--- a/src/master/master-settings.c	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/master/master-settings.c	Thu Oct 21 05:23:09 2004 +0300
@@ -139,6 +139,8 @@
 	DEF(SET_STR, default_realm),
 	DEF(SET_STR, userdb),
 	DEF(SET_STR, passdb),
+	DEF(SET_INT, cache_size),
+	DEF(SET_INT, cache_ttl),
 	DEF(SET_STR, executable),
 	DEF(SET_STR, user),
 	DEF(SET_STR, chroot),
@@ -310,6 +312,8 @@
 	MEMBER(default_realm) NULL,
 	MEMBER(userdb) "passwd",
 	MEMBER(passdb) "pam",
+	MEMBER(cache_size) 0,
+	MEMBER(cache_ttl) 3600,
 	MEMBER(executable) PKG_LIBEXECDIR"/dovecot-auth",
 	MEMBER(user) "root",
 	MEMBER(chroot) NULL,
--- a/src/master/master-settings.h	Thu Oct 21 02:34:34 2004 +0300
+++ b/src/master/master-settings.h	Thu Oct 21 05:23:09 2004 +0300
@@ -129,6 +129,8 @@
 	const char *default_realm;
 	const char *userdb;
 	const char *passdb;
+	unsigned int cache_size;
+	unsigned int cache_ttl;
 	const char *executable;
 	const char *user;
 	const char *chroot;