summaryrefslogtreecommitdiffstats
path: root/src/auth/passdb-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/passdb-cache.c')
-rw-r--r--src/auth/passdb-cache.c214
1 files changed, 214 insertions, 0 deletions
diff --git a/src/auth/passdb-cache.c b/src/auth/passdb-cache.c
new file mode 100644
index 0000000..1fe3b07
--- /dev/null
+++ b/src/auth/passdb-cache.c
@@ -0,0 +1,214 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "restrict-process-size.h"
+#include "auth-request-stats.h"
+#include "auth-worker-server.h"
+#include "password-scheme.h"
+#include "passdb.h"
+#include "passdb-cache.h"
+#include "passdb-blocking.h"
+
+struct auth_cache *passdb_cache = NULL;
+
+static void
+passdb_cache_log_hit(struct auth_request *request, const char *value)
+{
+ const char *p;
+
+ if (!request->set->debug_passwords &&
+ *value != '\0' && *value != '\t') {
+ /* hide the password */
+ p = strchr(value, '\t');
+ value = t_strconcat(PASSWORD_HIDDEN_STR, p, NULL);
+ }
+ e_debug(authdb_event(request), "cache hit: %s", value);
+}
+
+static bool
+passdb_cache_lookup(struct auth_request *request, const char *key,
+ bool use_expired, struct auth_cache_node **node_r,
+ const char **value_r, bool *neg_expired_r)
+{
+ struct auth_stats *stats = auth_request_stats_get(request);
+ const char *value;
+ bool expired;
+
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+
+ /* value = password \t ... */
+ value = auth_cache_lookup(passdb_cache, request, key, node_r,
+ &expired, neg_expired_r);
+ if (value == NULL || (expired && !use_expired)) {
+ stats->auth_cache_miss_count++;
+ e_debug(authdb_event(request),
+ value == NULL ? "cache miss" :
+ "cache expired");
+ return FALSE;
+ }
+ stats->auth_cache_hit_count++;
+ passdb_cache_log_hit(request, value);
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT;
+
+ *value_r = value;
+ return TRUE;
+}
+
+static bool
+passdb_cache_verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ enum passdb_result result;
+
+ result = passdb_blocking_auth_worker_reply_parse(request, reply);
+ if (result != PASSDB_RESULT_OK)
+ auth_fields_rollback(request->fields.extra_fields);
+ auth_request_verify_plain_callback_finish(result, request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+bool passdb_cache_verify_plain(struct auth_request *request, const char *key,
+ const char *password,
+ enum passdb_result *result_r, bool use_expired)
+{
+ const char *value, *cached_pw, *scheme, *const *list;
+ struct auth_cache_node *node;
+ enum passdb_result ret;
+ bool neg_expired;
+
+ if (passdb_cache == NULL || key == NULL)
+ return FALSE;
+
+ if (!passdb_cache_lookup(request, key, use_expired,
+ &node, &value, &neg_expired))
+ return FALSE;
+
+ if (*value == '\0') {
+ /* negative cache entry */
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ *result_r = PASSDB_RESULT_USER_UNKNOWN;
+ auth_request_verify_plain_callback_finish(*result_r, request);
+ return TRUE;
+ }
+
+ list = t_strsplit_tabescaped(value);
+
+ cached_pw = list[0];
+ if (*cached_pw == '\0') {
+ /* NULL password */
+ e_info(authdb_event(request),
+ "Cached NULL password access");
+ ret = PASSDB_RESULT_OK;
+ } else if (request->set->cache_verify_password_with_worker) {
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "PASSW\t%u\t", request->passdb->passdb->id);
+ str_append_tabescaped(str, password);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, cached_pw);
+ str_append_c(str, '\t');
+ auth_request_export(request, str);
+
+ e_debug(authdb_event(request), "cache: "
+ "validating password on worker");
+ auth_request_ref(request);
+ /* Save the extra fields already here, and take a snapshot.
+ If verification fails, roll back fields. */
+ auth_request_set_fields(request, list + 1, NULL);
+ auth_fields_snapshot(request->fields.extra_fields);
+ auth_worker_call(request->pool, request->fields.user, str_c(str),
+ passdb_cache_verify_plain_callback, request);
+ return TRUE;
+ } else {
+ scheme = password_get_scheme(&cached_pw);
+ i_assert(scheme != NULL);
+
+ ret = auth_request_password_verify_log(request, password, cached_pw,
+ scheme, AUTH_SUBSYS_DB,
+ !(node->last_success || neg_expired));
+
+ if (ret == PASSDB_RESULT_PASSWORD_MISMATCH &&
+ (node->last_success || neg_expired)) {
+ /* a) the last authentication was successful. assume
+ that the password was changed and cache is expired.
+ b) negative TTL reached, use it for password
+ mismatches too. */
+ node->last_success = FALSE;
+ return FALSE;
+ }
+ }
+ node->last_success = ret == PASSDB_RESULT_OK;
+
+ /* save the extra_fields only after we know we're using the
+ cached data */
+ auth_request_set_fields(request, list + 1, NULL);
+
+ *result_r = ret;
+
+ auth_request_verify_plain_callback_finish(*result_r, request);
+ return TRUE;
+}
+
+bool passdb_cache_lookup_credentials(struct auth_request *request,
+ const char *key, const char **password_r,
+ const char **scheme_r,
+ enum passdb_result *result_r,
+ bool use_expired)
+{
+ const char *value, *const *list;
+ struct auth_cache_node *node;
+ bool neg_expired;
+
+ if (passdb_cache == NULL)
+ return FALSE;
+
+ if (!passdb_cache_lookup(request, key, use_expired,
+ &node, &value, &neg_expired))
+ return FALSE;
+
+ if (*value == '\0') {
+ /* negative cache entry */
+ *result_r = PASSDB_RESULT_USER_UNKNOWN;
+ *password_r = NULL;
+ *scheme_r = NULL;
+ return TRUE;
+ }
+
+ list = t_strsplit_tabescaped(value);
+ auth_request_set_fields(request, list + 1, NULL);
+
+ *result_r = PASSDB_RESULT_OK;
+ *password_r = *list[0] == '\0' ? NULL : list[0];
+ *scheme_r = password_get_scheme(password_r);
+ i_assert(*scheme_r != NULL || *password_r == NULL);
+ return TRUE;
+}
+
+void passdb_cache_init(const struct auth_settings *set)
+{
+ rlim_t limit;
+
+ if (set->cache_size == 0 || set->cache_ttl == 0)
+ return;
+
+ if (restrict_get_process_size(&limit) == 0 &&
+ set->cache_size > (uoff_t)limit) {
+ i_warning("auth_cache_size (%"PRIuUOFF_T"M) is higher than "
+ "process VSZ limit (%"PRIuUOFF_T"M)",
+ set->cache_size/1024/1024,
+ (uoff_t)(limit/1024/1024));
+ }
+ passdb_cache = auth_cache_new(set->cache_size, set->cache_ttl,
+ set->cache_negative_ttl);
+}
+
+void passdb_cache_deinit(void)
+{
+ if (passdb_cache != NULL)
+ auth_cache_free(&passdb_cache);
+}