auth-cache.c revision ac1c78cce5eaa1298e8a302ec592e3a46a9a254d
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch/* Copyright (c) 2004-2013 Dovecot authors, see the included COPYING file */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "auth-common.h"
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch#include "lib-signals.h"
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch#include "hash.h"
1920ef85b63738a06914e56508049dd0afe38732Timo Sirainen#include "str.h"
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch#include "strescape.h"
74c09aceb0118b564f8443e1276c465738d19c17Timo Sirainen#include "var-expand.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "auth-request.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include "auth-cache.h"
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch#include <time.h>
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstruct auth_cache {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch HASH_TABLE(char *, struct auth_cache_node *) hash;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch struct auth_cache_node *head, *tail;
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainen
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch size_t max_size, size_left;
7944646fad6aa1e7c649c3d33e454c516b0220b6Timo Sirainen unsigned int ttl_secs, neg_ttl_secs;
7944646fad6aa1e7c649c3d33e454c516b0220b6Timo Sirainen
7944646fad6aa1e7c649c3d33e454c516b0220b6Timo Sirainen unsigned int hit_count, miss_count;
7944646fad6aa1e7c649c3d33e454c516b0220b6Timo Sirainen unsigned int pos_entries, neg_entries;
7944646fad6aa1e7c649c3d33e454c516b0220b6Timo Sirainen unsigned long long pos_size, neg_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch};
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstatic bool
56d1345c43bbd28c36b7faa85e4163bd9e874290Timo Sirainenauth_request_var_expand_tab_find(const char *key, unsigned int size,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch unsigned int *idx_r)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen const struct var_expand_table *tab = auth_request_var_expand_static_tab;
35e962a9186b4e9b2001628c1d7b55c24b33ce84Timo Sirainen unsigned int i;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen if (size == 1) {
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen if (key[0] == tab[i].key) {
ede750711f27ca9d9037a7ab9f016411b57f1ad9Stephan Bosch *idx_r = i;
ede750711f27ca9d9037a7ab9f016411b57f1ad9Stephan Bosch return TRUE;
ede750711f27ca9d9037a7ab9f016411b57f1ad9Stephan Bosch }
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch } else if (tab[i].long_key != NULL) {
ede750711f27ca9d9037a7ab9f016411b57f1ad9Stephan Bosch if (strncmp(key, tab[i].long_key, size) == 0 &&
4c4c4a740bbb1b674d4b0dae009d1919f8ad96b7Stephan Bosch tab[i].long_key[size] == '\0') {
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch *idx_r = i;
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch return TRUE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic void
7384b4e78eaab44693c985192276e31322155e32Stephan Boschauth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (str_len(str) > 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append_c(str, '\t');
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append_c(str, '%');
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (len == 1)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append_c(str, data[0]);
ad03049781fc14807248007d524be4daf06c3ee2Stephan Bosch else {
ad03049781fc14807248007d524be4daf06c3ee2Stephan Bosch str_append_c(str, '{');
ad03049781fc14807248007d524be4daf06c3ee2Stephan Bosch str_append_n(str, data, len);
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch str_append_c(str, '}');
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch }
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch}
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic void auth_cache_key_add_tab_idx(string_t *str, unsigned int i)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const struct var_expand_table *tab =
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch &auth_request_var_expand_static_tab[i];
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (str_len(str) > 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append_c(str, '\t');
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append_c(str, '%');
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch if (tab->key != '\0')
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch str_append_c(str, tab->key);
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch else {
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch str_append_c(str, '{');
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch str_append(str, tab->long_key);
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch str_append_c(str, '}');
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch }
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch}
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Boschchar *auth_cache_parse_key(pool_t pool, const char *query)
9d746c6785d17e421c3f3c74cf29d059ae2ab233Stephan Bosch{
fe2b0e3de834dd40b698bb579adc5357d5789ec9Stephan Bosch string_t *str;
fe2b0e3de834dd40b698bb579adc5357d5789ec9Stephan Bosch bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT];
94d1b08c9e20d637db568a3eab3dfc2b9e96e62aStephan Bosch const char *extra_vars;
94d1b08c9e20d637db568a3eab3dfc2b9e96e62aStephan Bosch unsigned int i, idx, size, tab_idx;
fe2b0e3de834dd40b698bb579adc5357d5789ec9Stephan Bosch
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch memset(key_seen, 0, sizeof(key_seen));
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch str = t_str_new(32);
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch for (; *query != '\0'; ) {
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch if (*query != '%') {
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch query++;
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch continue;
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch }
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen var_get_key_range(++query, &idx, &size);
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen if (size == 0) {
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen /* broken %variable ending too early */
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen break;
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen }
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen query += idx;
70505f4839520ac67895992621c97d2480c22e7fTimo Sirainen
70505f4839520ac67895992621c97d2480c22e7fTimo Sirainen if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) {
70505f4839520ac67895992621c97d2480c22e7fTimo Sirainen /* just add the key. it would be nice to prevent
70505f4839520ac67895992621c97d2480c22e7fTimo Sirainen duplicates here as well, but that's just too
a8c4e79ff50fac21b05a7368b052583d410ca15cTimo Sirainen much trouble and probably very rare. */
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch auth_cache_key_add_var(str, query, size);
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch } else {
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch i_assert(tab_idx < N_ELEMENTS(key_seen));
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch key_seen[tab_idx] = TRUE;
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch }
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch query += size;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] &&
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* %n and %d both used -> replace with %u */
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* we rely on these being at the beginning */
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch extra_vars = t_strdup(str_c(str));
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch str_truncate(str, 0);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch for (i = 0; i < N_ELEMENTS(key_seen); i++) {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (key_seen[i])
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch auth_cache_key_add_tab_idx(str, i);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (*extra_vars != '\0') {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (str_len(str) > 0)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch str_append_c(str, '\t');
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch str_append(str, extra_vars);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return p_strdup(pool, str_c(str));
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstatic void
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschauth_cache_node_unlink(struct auth_cache *cache, struct auth_cache_node *node)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (node->prev != NULL)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch node->prev->next = node->next;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch else {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* unlinking tail */
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache->tail = node->next;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (node->next != NULL)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch node->next->prev = node->prev;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch else {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* unlinking head */
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache->head = node->prev;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstatic void
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschauth_cache_node_link_head(struct auth_cache *cache,
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch struct auth_cache_node *node)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch node->prev = cache->head;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch node->next = NULL;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch cache->head = node;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch if (node->prev != NULL)
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch node->prev->next = node;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch else
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch cache->tail = node;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Boschstatic void
7384b4e78eaab44693c985192276e31322155e32Stephan Boschauth_cache_node_destroy(struct auth_cache *cache, struct auth_cache_node *node)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch char *key = node->data;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch auth_cache_node_unlink(cache, node);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch cache->size_left += node->alloc_size;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch hash_table_remove(cache->hash, key);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_free(node);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstatic void sig_auth_cache_clear(const siginfo_t *si ATTR_UNUSED, void *context)
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch{
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch struct auth_cache *cache = context;
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch i_info("SIGHUP received, %u cache entries flushed",
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch auth_cache_clear(cache));
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch}
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Boschstatic void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context)
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch{
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch struct auth_cache *cache = context;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch unsigned int total_count;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch size_t cache_used;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch total_count = cache->hit_count + cache->miss_count;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch i_info("Authentication cache hits %u/%u (%u%%)",
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch cache->hit_count, total_count,
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch total_count == 0 ? 100 : (cache->hit_count * 100 / total_count));
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch i_info("Authentication cache inserts: "
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch "positive: %u entries %llu bytes, "
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch "negative: %u entries %llu bytes",
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch cache->pos_entries, cache->pos_size,
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch cache->neg_entries, cache->neg_size);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch cache_used = cache->max_size - cache->size_left;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch i_info("Authentication cache current size: "
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch "%"PRIuSIZE_T" bytes used of %"PRIuSIZE_T" bytes (%u%%)",
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache_used, cache->max_size,
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch (unsigned int)(cache_used * 100ULL / cache->max_size));
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch /* reset counters */
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch cache->hit_count = cache->miss_count = 0;
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch cache->pos_entries = cache->neg_entries = 0;
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch cache->pos_size = cache->neg_size = 0;
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch}
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Boschstruct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs,
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch unsigned int neg_ttl_secs
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct auth_cache *cache;
fc94140acba51adafedafbc8491a3223a51db7a8Stephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache = i_new(struct auth_cache, 1);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch hash_table_create(&cache->hash, default_pool, 0, str_hash, strcmp);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch cache->max_size = max_size;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache->size_left = max_size;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache->ttl_secs = ttl_secs;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch cache->neg_ttl_secs = neg_ttl_secs;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch lib_signals_set_handler(SIGHUP, LIBSIG_FLAGS_SAFE,
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch sig_auth_cache_clear, cache);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch lib_signals_set_handler(SIGUSR2, LIBSIG_FLAGS_SAFE,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch sig_auth_cache_stats, cache);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return cache;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainenvoid auth_cache_free(struct auth_cache **_cache)
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch struct auth_cache *cache = *_cache;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch *_cache = NULL;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch lib_signals_unset_handler(SIGHUP, sig_auth_cache_clear, cache);
6d573191bea1a64d6046be070487a5705a2d0204Stephan Bosch lib_signals_unset_handler(SIGUSR2, sig_auth_cache_stats, cache);
6d573191bea1a64d6046be070487a5705a2d0204Stephan Bosch
6d573191bea1a64d6046be070487a5705a2d0204Stephan Bosch auth_cache_clear(cache);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch hash_table_destroy(&cache->hash);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch i_free(cache);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschunsigned int auth_cache_clear(struct auth_cache *cache)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch unsigned int ret = hash_table_count(cache->hash);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch while (cache->tail != NULL)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch auth_cache_node_destroy(cache, cache->tail);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch hash_table_clear(cache->hash, FALSE);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return ret;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschstatic bool auth_cache_node_is_user(struct auth_cache_node *node,
7a13cd2268a5a99d2975a1648d6d14ffe1b6ccb0Stephan Bosch const char *username)
7a13cd2268a5a99d2975a1648d6d14ffe1b6ccb0Stephan Bosch{
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch const char *data = node->data;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch unsigned int username_len;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch /* The cache nodes begin with "P"/"U", passdb/userdb ID, "/" and
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch then usually followed by the username. It's too much trouble to
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch keep track of all the cache keys, so we'll just match it as if it
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch was the username. If e.g. '%n' is used in the cache key instead of
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch '%u', it means that cache entries can be removed only when @domain
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch isn't in the username parameter. */
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (*data != 'P' && *data != 'U')
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch data++;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
30f35cf5d1e1374d7fab4231e86144fc106a8e79Stephan Bosch while (*data >= '0' && *data <= '9')
30f35cf5d1e1374d7fab4231e86144fc106a8e79Stephan Bosch data++;
30f35cf5d1e1374d7fab4231e86144fc106a8e79Stephan Bosch if (*data != '/')
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch data++;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch username_len = strlen(username);
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch return strncmp(data, username, username_len) == 0 &&
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch (data[username_len] == '\t' || data[username_len] == '\0');
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch}
de0181258ab66b527ad8dc7e51a8efa76b4658d0Stephan Bosch
de0181258ab66b527ad8dc7e51a8efa76b4658d0Stephan Boschstatic bool auth_cache_node_is_one_of_users(struct auth_cache_node *node,
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch const char *const *usernames)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch unsigned int i;
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Bosch
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Bosch for (i = 0; usernames[i] != NULL; i++) {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch if (auth_cache_node_is_user(node, usernames[i]))
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return TRUE;
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Bosch }
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Bosch return FALSE;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainen
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Boschunsigned int auth_cache_clear_users(struct auth_cache *cache,
2a9cadfccc8fb2c609eedbb929952b49181b6d25Stephan Bosch const char *const *usernames)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch struct auth_cache_node *node, *next;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch unsigned int ret = 0;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch for (node = cache->tail; node != NULL; node = next) {
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch next = node->next;
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainen if (auth_cache_node_is_one_of_users(node, usernames)) {
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch auth_cache_node_destroy(cache, node);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch ret++;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch }
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch return ret;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainen
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainenstatic const char *
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainenauth_cache_escape(const char *string,
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainen const struct auth_request *auth_request ATTR_UNUSED)
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch{
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* cache key %variables are separated by tabs, make sure that there
c3a4c931e95737a52e1cebeeb109a2e1cc4d47d6Timo Sirainen are no tabs in the string */
c3a4c931e95737a52e1cebeeb109a2e1cc4d47d6Timo Sirainen return str_tabescape(string);
c3a4c931e95737a52e1cebeeb109a2e1cc4d47d6Timo Sirainen}
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Boschstatic const char *
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschauth_request_expand_cache_key(const struct auth_request *request,
c3a4c931e95737a52e1cebeeb109a2e1cc4d47d6Timo Sirainen const char *key)
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainen{
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch string_t *str;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch /* Uniquely identify the request's passdb/userdb with the P/U prefix
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch and by "%!", which expands to the passdb/userdb ID number. */
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!/", key, NULL);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch str = t_str_new(256);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch var_expand(str, key,
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch auth_request_get_var_expand_table(request, auth_cache_escape));
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch return str_c(str);
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch}
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschconst char *
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Boschauth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch const char *key, struct auth_cache_node **node_r,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch bool *expired_r, bool *neg_expired_r)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch{
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct auth_cache_node *node;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const char *value;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch unsigned int ttl_secs;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch time_t now;
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch
5a37824675033747fcae3fe3fc3c0dd7ef0ca1cdStephan Bosch *expired_r = FALSE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch *neg_expired_r = FALSE;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch
key = auth_request_expand_cache_key(request, key);
node = hash_table_lookup(cache->hash, key);
if (node == NULL) {
cache->miss_count++;
return NULL;
}
cache->hit_count++;
value = node->data + strlen(node->data) + 1;
ttl_secs = *value == '\0' ? cache->neg_ttl_secs : cache->ttl_secs;
now = time(NULL);
if (node->created < now - (time_t)ttl_secs) {
/* TTL expired */
*expired_r = TRUE;
} else {
/* move to head */
if (node != cache->head) {
auth_cache_node_unlink(cache, node);
auth_cache_node_link_head(cache, node);
}
}
if (node->created < now - (time_t)cache->neg_ttl_secs)
*neg_expired_r = TRUE;
if (node_r != NULL)
*node_r = node;
return value;
}
void auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
const char *key, const char *value, bool last_success)
{
struct auth_cache_node *node;
size_t data_size, alloc_size, key_len, value_len = strlen(value);
char *hash_key, *current_username;
if (*value == '\0' && cache->neg_ttl_secs == 0) {
/* we're not caching negative entries */
return;
}
/* store into cache using the translated username, except if we're doing
a master user login */
current_username = request->user;
if (request->translated_username != NULL &&
request->requested_login_user == NULL)
request->user = t_strdup_noconst(request->translated_username);
key = auth_request_expand_cache_key(request, key);
key_len = strlen(key);
request->user = current_username;
data_size = key_len + 1 + value_len + 1;
alloc_size = sizeof(struct auth_cache_node) -
sizeof(node->data) + data_size;
/* make sure we have enough space */
while (cache->size_left < alloc_size && cache->tail != NULL)
auth_cache_node_destroy(cache, cache->tail);
node = hash_table_lookup(cache->hash, key);
if (node != NULL) {
/* key is already in cache (probably expired), remove it */
auth_cache_node_destroy(cache, node);
}
/* @UNSAFE */
node = i_malloc(alloc_size);
node->created = time(NULL);
node->alloc_size = alloc_size;
node->last_success = last_success;
memcpy(node->data, key, key_len);
memcpy(node->data + key_len + 1, value, value_len);
auth_cache_node_link_head(cache, node);
cache->size_left -= alloc_size;
hash_key = node->data;
hash_table_insert(cache->hash, hash_key, node);
if (*value != '\0') {
cache->pos_entries++;
cache->pos_size += alloc_size;
} else {
cache->neg_entries++;
cache->neg_size += alloc_size;
}
}
void auth_cache_remove(struct auth_cache *cache,
const struct auth_request *request, const char *key)
{
struct auth_cache_node *node;
key = auth_request_expand_cache_key(request, key);
node = hash_table_lookup(cache->hash, key);
if (node == NULL)
return;
auth_cache_node_destroy(cache, node);
}