bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen HASH_TABLE(char *, struct auth_cache_node *) hash;
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenauth_request_var_expand_tab_find(const char *key, unsigned int size,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen unsigned int *idx_r)
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen const struct var_expand_table *tab = auth_request_var_expand_static_tab;
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen unsigned int i;
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen if (strncmp(key, tab[i].long_key, size) == 0 &&
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenauth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenstatic void auth_cache_key_add_tab_idx(string_t *str, unsigned int i)
12a34eb68607d0f8c7c3dc973ee605e89d44ba30Timo Sirainenchar *auth_cache_parse_key(pool_t pool, const char *query)
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen /* broken %variable ending too early */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) {
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen /* just add the key. it would be nice to prevent
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen duplicates here as well, but that's just too
f2e7603c5d53b3dda364e93dd926d74d6047cb25Timo Sirainen much trouble and probably very rare. */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] &&
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen /* %n and %d both used -> replace with %u */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE;
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE;
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE;
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen /* we rely on these being at the beginning */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1);
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2);
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainenauth_cache_node_unlink(struct auth_cache *cache, struct auth_cache_node *node)
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* unlinking tail */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* unlinking head */
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainenauth_cache_node_link_head(struct auth_cache *cache,
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainenauth_cache_node_destroy(struct auth_cache *cache, struct auth_cache_node *node)
1c7fa51b35231f375998f66d5756f214519218f8Timo Sirainenstatic void sig_auth_cache_clear(const siginfo_t *si ATTR_UNUSED, void *context)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen i_info("SIGHUP received, %u cache entries flushed",
1c7fa51b35231f375998f66d5756f214519218f8Timo Sirainenstatic void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context)
321221ddc2dedc4ad79839770765adc40d311a0dTimo Sirainen total_count = cache->hit_count + cache->miss_count;
321221ddc2dedc4ad79839770765adc40d311a0dTimo Sirainen i_info("Authentication cache hits %u/%u (%u%%)",
cdc7e0c3492f8b68699e4d19f0c96ba67f880e48Timo Sirainen total_count == 0 ? 100 : (cache->hit_count * 100 / total_count));
ac1c78cce5eaa1298e8a302ec592e3a46a9a254dTimo Sirainen "positive: %u entries %llu bytes, "
ac1c78cce5eaa1298e8a302ec592e3a46a9a254dTimo Sirainen "negative: %u entries %llu bytes",
ac1c78cce5eaa1298e8a302ec592e3a46a9a254dTimo Sirainen cache_used = cache->max_size - cache->size_left;
ac1c78cce5eaa1298e8a302ec592e3a46a9a254dTimo Sirainen "%"PRIuSIZE_T" bytes used of %"PRIuSIZE_T" bytes (%u%%)",
ac1c78cce5eaa1298e8a302ec592e3a46a9a254dTimo Sirainen (unsigned int)(cache_used * 100ULL / cache->max_size));
20df5a94b36be8e6c2d0e481973cab6715c61bb8Timo Sirainen /* reset counters */
b522f391ef6aa4e527f2eeb5f7daa3da188790b0Timo Sirainenstruct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs,
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen hash_table_create(&cache->hash, default_pool, 0, str_hash, strcmp);
5512d420d826a2f9d4e7cb4e4919e1864fe688b0Timo Sirainen lib_signals_set_handler(SIGHUP, LIBSIG_FLAGS_SAFE,
5512d420d826a2f9d4e7cb4e4919e1864fe688b0Timo Sirainen lib_signals_set_handler(SIGUSR2, LIBSIG_FLAGS_SAFE,
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainenvoid auth_cache_free(struct auth_cache **_cache)
321221ddc2dedc4ad79839770765adc40d311a0dTimo Sirainen lib_signals_unset_handler(SIGHUP, sig_auth_cache_clear, cache);
321221ddc2dedc4ad79839770765adc40d311a0dTimo Sirainen lib_signals_unset_handler(SIGUSR2, sig_auth_cache_stats, cache);
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenunsigned int auth_cache_clear(struct auth_cache *cache)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen unsigned int ret = hash_table_count(cache->hash);
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenstatic bool auth_cache_node_is_user(struct auth_cache_node *node,
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen /* The cache nodes begin with "P"/"U", passdb/userdb ID, optional
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen "+" master user, "\t" and then usually followed by the username.
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen It's too much trouble to keep track of all the cache keys, so we'll
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen just match it as if it was the username. If e.g. '%n' is used in the
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen cache key instead of '%u', it means that cache entries can be
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen removed only when @domain isn't in the username parameter. */
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen /* skip over +master_user */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen return strncmp(data, username, username_len) == 0 &&
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen (data[username_len] == '\t' || data[username_len] == '\0');
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenstatic bool auth_cache_node_is_one_of_users(struct auth_cache_node *node,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen const char *const *usernames)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen unsigned int i;
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen if (auth_cache_node_is_user(node, usernames[i]))
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenunsigned int auth_cache_clear_users(struct auth_cache *cache,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen const char *const *usernames)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen for (node = cache->tail; node != NULL; node = next) {
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen if (auth_cache_node_is_one_of_users(node, usernames)) {
ca283b86783042541e15cfd17ef253a2ce48aa71Timo Sirainenstatic const char *
ca283b86783042541e15cfd17ef253a2ce48aa71Timo Sirainen const struct auth_request *auth_request ATTR_UNUSED)
ca283b86783042541e15cfd17ef253a2ce48aa71Timo Sirainen /* cache key %variables are separated by tabs, make sure that there
ca283b86783042541e15cfd17ef253a2ce48aa71Timo Sirainen are no tabs in the string */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenstatic const char *
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainenauth_request_expand_cache_key(const struct auth_request *request,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen const char *key)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen /* Uniquely identify the request's passdb/userdb with the P/U prefix
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen and by "%!", which expands to the passdb/userdb ID number. */
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!",
d31c77e63713a6cf3687a4b38ff8daf6d6c7a3ddTimo Sirainen request->master_user == NULL ? "" : "+%{master_user}",
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen /* It's fine to have unknown %variables in the cache key.
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen For example db-ldap can have pass_attrs containing
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen %{ldap:fields} which are used for output, not as part of
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen the input needed for cache_key. Those could in theory be
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen filtered out early in the cache_key, but that gets more
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen problematic when it needs to support also filtering out
9c77606aef2dadad72e1a97f6d820dadb05d1d44Timo Sirainen e.g. %{sha256:ldap:fields}. */
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen if (t_auth_request_var_expand(key, request, auth_cache_escape,
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen i_error("Failed to expand auth cache key %s: %s", key, error);
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainenauth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen const char *key, struct auth_cache_node **node_r,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen key = auth_request_expand_cache_key(request, key);
b522f391ef6aa4e527f2eeb5f7daa3da188790b0Timo Sirainen ttl_secs = *value == '\0' ? cache->neg_ttl_secs : cache->ttl_secs;
42061dd44f742de25d0a52ed1ef766a56df421f1Timo Sirainen /* TTL expired */
42061dd44f742de25d0a52ed1ef766a56df421f1Timo Sirainen /* move to head */
145d2eef238ed8bbff635e3b06951a83f0ee5a03Timo Sirainen if (node->created < now - (time_t)cache->neg_ttl_secs)
814b1e8e301bf58cc03ddf35a380b43c581af4dfTimo Sirainenvoid auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
8759adc67109b5a12a7af3ed717c7040622a0a04Timo Sirainen const char *key, const char *value, bool last_success)
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen size_t data_size, alloc_size, key_len, value_len = strlen(value);
b522f391ef6aa4e527f2eeb5f7daa3da188790b0Timo Sirainen if (*value == '\0' && cache->neg_ttl_secs == 0) {
b522f391ef6aa4e527f2eeb5f7daa3da188790b0Timo Sirainen /* we're not caching negative entries */
d24a5c09373ec890da5736882c1756aa7fa651ebTimo Sirainen /* store into cache using the translated username, except if we're doing
814b1e8e301bf58cc03ddf35a380b43c581af4dfTimo Sirainen a master user login */
d24a5c09373ec890da5736882c1756aa7fa651ebTimo Sirainen request->user = t_strdup_noconst(request->translated_username);
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen key = auth_request_expand_cache_key(request, key);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* make sure we have enough space */
1e50a4f930a028ab5fcf690503bfd13b54ff6787Timo Sirainen while (cache->size_left < alloc_size && cache->tail != NULL)
e361b5386c77ee25685b5ad2bd2519a077dea882Timo Sirainen /* key is already in cache (probably expired), remove it */
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen /* @UNSAFE */
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen memcpy(node->data + key_len + 1, value, value_len);
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen hash_table_insert(cache->hash, hash_key, node);
121221dd2970a6f5ec601abb3b1c505c238e0b60Timo Sirainenvoid auth_cache_remove(struct auth_cache *cache,
39dcbe101c4538ee25d8b196eab30e5f0faa22eeTimo Sirainen const struct auth_request *request, const char *key)