]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: Fix stale cache entry blocking auth after password change in worker path
authorFred Morcos <fred.morcos@open-xchange.com>
Thu, 30 Apr 2026 14:44:27 +0000 (16:44 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 5 May 2026 14:45:12 +0000 (17:45 +0300)
When passdb cache verification runs on an auth worker, a successful
prior login plus a subsequent password change leaves a stale cached
password. The non-worker code path already handles this: if the
cached password mismatches and the entry's last_success flag (or
neg_expired) indicates the cache is likely stale, fall back to a
fresh passdb lookup. The worker callback was missing this handling,
so users would fail to authenticate until the cache entry expired.

After the worker returns a PASSWORD_MISMATCH, re-lookup the cache
node and apply the same passdb_cache_use_password_mismatch() rule
to decide whether to invoke the fallback. The node pointer cannot
be carried across the worker round-trip (see auth_cache_node
lifetime), so the lookup is redone here. orig_cache_result is
preserved so the re-lookup does not clobber the request's recorded
cache result.

If the re-lookup misses because the entry was evicted while the
worker was busy, fall back unconditionally instead of delivering
the mismatch as-is: a password change would otherwise be reported
as a mismatch instead of being retried, and the cached evidence
that would have driven the stale-cache rule no longer exists, so
an extra fresh lookup is the right behavior.

src/auth/passdb-cache.c

index 917b890b708b82039aa9f86888ba12b8d2b7c2cb..8690887b6a0c397b795b6bab3919726077572f37 100644 (file)
@@ -88,6 +88,34 @@ passdb_cache_verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUS
        result = passdb_blocking_auth_worker_reply_parse(request, args);
        if (result != PASSDB_RESULT_OK)
                auth_fields_rollback(request->fields.extra_fields);
+
+       if (result == PASSDB_RESULT_PASSWORD_MISMATCH) {
+               /* The cache may have evicted our entry while the worker was
+                  processing the request, so re-lookup the node. If it's
+                  still present, apply the stale-cache rule. If it's gone,
+                  fall back anyway: otherwise a password change could be
+                  reported as a mismatch instead of being retried, and the
+                  cached evidence we would have used is gone. The extra
+                  lookup is harmless because the cache entry no longer
+                  exists. */
+               enum auth_request_cache_result orig_cache_result =
+                       request->passdb_cache_result;
+               struct auth_cache_node *node;
+               const char *value;
+               bool neg_expired;
+               bool found = passdb_cache_lookup(request, ctx->key,
+                                                ctx->use_expired, &node,
+                                                &value, &neg_expired);
+               bool do_fallback = !found ||
+                       !passdb_cache_use_password_mismatch(result, node,
+                                                           neg_expired);
+               request->passdb_cache_result = orig_cache_result;
+               if (do_fallback) {
+                       ctx->fallback(request);
+                       auth_request_unref(&request);
+                       return TRUE;
+               }
+       }
        auth_request_verify_plain_callback_finish(result, request);
        auth_request_unref(&request);
        return TRUE;