freebsd-dev/usr.sbin/nscd/config.c
Stefan Eßer a397989d4e Add the possibility to specify a threshold for the number of negative cache
results required to have the cache return lookup failure.

A new configuration parameter is introduced, which must be set to a value
greater than 1 to activate this feature. The default behavior is unchanged.

The purpose of this change is to allow probes for the existence of an entry
(which are expected to fail), before that entry is added to one of the
queried databases, without the cache returning the stale information from
the probe query until that cache entry expires. If, for example, a new user
account is created after checking that the new account name is available,
the negative cache entry would prevent immediate access to the account.

For that example, the new configuration option

negative-confidence-threshold passwd 2

will require a second negative query result to consider the negative cache
entry for a passwd entry valid, but if the user account has been created
between the queries, then the positive query result from the second query
will be cached and returned.
2012-07-04 09:02:12 +00:00

589 lines
16 KiB
C

/*-
* Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
#include <math.h>
#include <nsswitch.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "debug.h"
#include "log.h"
/*
* Default entries, which always exist in the configuration
*/
const char *c_default_entries[6] = {
NSDB_PASSWD,
NSDB_GROUP,
NSDB_HOSTS,
NSDB_SERVICES,
NSDB_PROTOCOLS,
NSDB_RPC
};
static int configuration_entry_cmp(const void *, const void *);
static int configuration_entry_sort_cmp(const void *, const void *);
static int configuration_entry_cache_mp_sort_cmp(const void *, const void *);
static int configuration_entry_cache_mp_cmp(const void *, const void *);
static int configuration_entry_cache_mp_part_cmp(const void *, const void *);
static struct configuration_entry *create_configuration_entry(const char *,
struct timeval const *, struct timeval const *,
struct common_cache_entry_params const *,
struct common_cache_entry_params const *,
struct mp_cache_entry_params const *);
static int
configuration_entry_sort_cmp(const void *e1, const void *e2)
{
return (strcmp((*((struct configuration_entry **)e1))->name,
(*((struct configuration_entry **)e2))->name
));
}
static int
configuration_entry_cmp(const void *e1, const void *e2)
{
return (strcmp((const char *)e1,
(*((struct configuration_entry **)e2))->name
));
}
static int
configuration_entry_cache_mp_sort_cmp(const void *e1, const void *e2)
{
return (strcmp((*((cache_entry *)e1))->params->entry_name,
(*((cache_entry *)e2))->params->entry_name
));
}
static int
configuration_entry_cache_mp_cmp(const void *e1, const void *e2)
{
return (strcmp((const char *)e1,
(*((cache_entry *)e2))->params->entry_name
));
}
static int
configuration_entry_cache_mp_part_cmp(const void *e1, const void *e2)
{
return (strncmp((const char *)e1,
(*((cache_entry *)e2))->params->entry_name,
strlen((const char *)e1)
));
}
static struct configuration_entry *
create_configuration_entry(const char *name,
struct timeval const *common_timeout,
struct timeval const *mp_timeout,
struct common_cache_entry_params const *positive_params,
struct common_cache_entry_params const *negative_params,
struct mp_cache_entry_params const *mp_params)
{
struct configuration_entry *retval;
size_t size;
int res;
TRACE_IN(create_configuration_entry);
assert(name != NULL);
assert(positive_params != NULL);
assert(negative_params != NULL);
assert(mp_params != NULL);
retval = calloc(1,
sizeof(*retval));
assert(retval != NULL);
res = pthread_mutex_init(&retval->positive_cache_lock, NULL);
if (res != 0) {
free(retval);
LOG_ERR_2("create_configuration_entry",
"can't create positive cache lock");
TRACE_OUT(create_configuration_entry);
return (NULL);
}
res = pthread_mutex_init(&retval->negative_cache_lock, NULL);
if (res != 0) {
pthread_mutex_destroy(&retval->positive_cache_lock);
free(retval);
LOG_ERR_2("create_configuration_entry",
"can't create negative cache lock");
TRACE_OUT(create_configuration_entry);
return (NULL);
}
res = pthread_mutex_init(&retval->mp_cache_lock, NULL);
if (res != 0) {
pthread_mutex_destroy(&retval->positive_cache_lock);
pthread_mutex_destroy(&retval->negative_cache_lock);
free(retval);
LOG_ERR_2("create_configuration_entry",
"can't create negative cache lock");
TRACE_OUT(create_configuration_entry);
return (NULL);
}
memcpy(&retval->positive_cache_params, positive_params,
sizeof(struct common_cache_entry_params));
memcpy(&retval->negative_cache_params, negative_params,
sizeof(struct common_cache_entry_params));
memcpy(&retval->mp_cache_params, mp_params,
sizeof(struct mp_cache_entry_params));
size = strlen(name);
retval->name = calloc(1, size + 1);
assert(retval->name != NULL);
memcpy(retval->name, name, size);
memcpy(&retval->common_query_timeout, common_timeout,
sizeof(struct timeval));
memcpy(&retval->mp_query_timeout, mp_timeout,
sizeof(struct timeval));
asprintf(&retval->positive_cache_params.cep.entry_name, "%s+", name);
assert(retval->positive_cache_params.cep.entry_name != NULL);
asprintf(&retval->negative_cache_params.cep.entry_name, "%s-", name);
assert(retval->negative_cache_params.cep.entry_name != NULL);
asprintf(&retval->mp_cache_params.cep.entry_name, "%s*", name);
assert(retval->mp_cache_params.cep.entry_name != NULL);
TRACE_OUT(create_configuration_entry);
return (retval);
}
/*
* Creates configuration entry and fills it with default values
*/
struct configuration_entry *
create_def_configuration_entry(const char *name)
{
struct common_cache_entry_params positive_params, negative_params;
struct mp_cache_entry_params mp_params;
struct timeval default_common_timeout, default_mp_timeout;
struct configuration_entry *res = NULL;
TRACE_IN(create_def_configuration_entry);
memset(&positive_params, 0,
sizeof(struct common_cache_entry_params));
positive_params.cep.entry_type = CET_COMMON;
positive_params.cache_entries_size = DEFAULT_CACHE_HT_SIZE;
positive_params.max_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE;
positive_params.satisf_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE / 2;
positive_params.max_lifetime.tv_sec = DEFAULT_POSITIVE_LIFETIME;
positive_params.confidence_threshold = DEFAULT_POSITIVE_CONF_THRESH;
positive_params.policy = CPT_LRU;
memcpy(&negative_params, &positive_params,
sizeof(struct common_cache_entry_params));
negative_params.max_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE;
negative_params.satisf_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE / 2;
negative_params.max_lifetime.tv_sec = DEFAULT_NEGATIVE_LIFETIME;
negative_params.confidence_threshold = DEFAULT_NEGATIVE_CONF_THRESH;
negative_params.policy = CPT_FIFO;
memset(&default_common_timeout, 0, sizeof(struct timeval));
default_common_timeout.tv_sec = DEFAULT_COMMON_ENTRY_TIMEOUT;
memset(&default_mp_timeout, 0, sizeof(struct timeval));
default_mp_timeout.tv_sec = DEFAULT_MP_ENTRY_TIMEOUT;
memset(&mp_params, 0,
sizeof(struct mp_cache_entry_params));
mp_params.cep.entry_type = CET_MULTIPART;
mp_params.max_elemsize = DEFAULT_MULTIPART_ELEMENTS_SIZE;
mp_params.max_sessions = DEFAULT_MULITPART_SESSIONS_SIZE;
mp_params.max_lifetime.tv_sec = DEFAULT_MULITPART_LIFETIME;
res = create_configuration_entry(name, &default_common_timeout,
&default_mp_timeout, &positive_params, &negative_params,
&mp_params);
TRACE_OUT(create_def_configuration_entry);
return (res);
}
void
destroy_configuration_entry(struct configuration_entry *entry)
{
TRACE_IN(destroy_configuration_entry);
assert(entry != NULL);
pthread_mutex_destroy(&entry->positive_cache_lock);
pthread_mutex_destroy(&entry->negative_cache_lock);
pthread_mutex_destroy(&entry->mp_cache_lock);
free(entry->name);
free(entry->positive_cache_params.cep.entry_name);
free(entry->negative_cache_params.cep.entry_name);
free(entry->mp_cache_params.cep.entry_name);
free(entry->mp_cache_entries);
free(entry);
TRACE_OUT(destroy_configuration_entry);
}
int
add_configuration_entry(struct configuration *config,
struct configuration_entry *entry)
{
TRACE_IN(add_configuration_entry);
assert(entry != NULL);
assert(entry->name != NULL);
if (configuration_find_entry(config, entry->name) != NULL) {
TRACE_OUT(add_configuration_entry);
return (-1);
}
if (config->entries_size == config->entries_capacity) {
struct configuration_entry **new_entries;
config->entries_capacity *= 2;
new_entries = calloc(1,
sizeof(*new_entries) *
config->entries_capacity);
assert(new_entries != NULL);
memcpy(new_entries, config->entries,
sizeof(struct configuration_entry *) *
config->entries_size);
free(config->entries);
config->entries = new_entries;
}
config->entries[config->entries_size++] = entry;
qsort(config->entries, config->entries_size,
sizeof(struct configuration_entry *),
configuration_entry_sort_cmp);
TRACE_OUT(add_configuration_entry);
return (0);
}
size_t
configuration_get_entries_size(struct configuration *config)
{
TRACE_IN(configuration_get_entries_size);
assert(config != NULL);
TRACE_OUT(configuration_get_entries_size);
return (config->entries_size);
}
struct configuration_entry *
configuration_get_entry(struct configuration *config, size_t index)
{
TRACE_IN(configuration_get_entry);
assert(config != NULL);
assert(index < config->entries_size);
TRACE_OUT(configuration_get_entry);
return (config->entries[index]);
}
struct configuration_entry *
configuration_find_entry(struct configuration *config,
const char *name)
{
struct configuration_entry **retval;
TRACE_IN(configuration_find_entry);
retval = bsearch(name, config->entries, config->entries_size,
sizeof(struct configuration_entry *), configuration_entry_cmp);
TRACE_OUT(configuration_find_entry);
return ((retval != NULL) ? *retval : NULL);
}
/*
* All multipart cache entries are stored in the configuration_entry in the
* sorted array (sorted by names). The 3 functions below manage this array.
*/
int
configuration_entry_add_mp_cache_entry(struct configuration_entry *config_entry,
cache_entry c_entry)
{
cache_entry *new_mp_entries, *old_mp_entries;
TRACE_IN(configuration_entry_add_mp_cache_entry);
++config_entry->mp_cache_entries_size;
new_mp_entries = malloc(sizeof(*new_mp_entries) *
config_entry->mp_cache_entries_size);
assert(new_mp_entries != NULL);
new_mp_entries[0] = c_entry;
if (config_entry->mp_cache_entries_size - 1 > 0) {
memcpy(new_mp_entries + 1,
config_entry->mp_cache_entries,
(config_entry->mp_cache_entries_size - 1) *
sizeof(cache_entry));
}
old_mp_entries = config_entry->mp_cache_entries;
config_entry->mp_cache_entries = new_mp_entries;
free(old_mp_entries);
qsort(config_entry->mp_cache_entries,
config_entry->mp_cache_entries_size,
sizeof(cache_entry),
configuration_entry_cache_mp_sort_cmp);
TRACE_OUT(configuration_entry_add_mp_cache_entry);
return (0);
}
cache_entry
configuration_entry_find_mp_cache_entry(
struct configuration_entry *config_entry, const char *mp_name)
{
cache_entry *result;
TRACE_IN(configuration_entry_find_mp_cache_entry);
result = bsearch(mp_name, config_entry->mp_cache_entries,
config_entry->mp_cache_entries_size,
sizeof(cache_entry), configuration_entry_cache_mp_cmp);
if (result == NULL) {
TRACE_OUT(configuration_entry_find_mp_cache_entry);
return (NULL);
} else {
TRACE_OUT(configuration_entry_find_mp_cache_entry);
return (*result);
}
}
/*
* Searches for all multipart entries with names starting with mp_name.
* Needed for cache flushing.
*/
int
configuration_entry_find_mp_cache_entries(
struct configuration_entry *config_entry, const char *mp_name,
cache_entry **start, cache_entry **finish)
{
cache_entry *result;
TRACE_IN(configuration_entry_find_mp_cache_entries);
result = bsearch(mp_name, config_entry->mp_cache_entries,
config_entry->mp_cache_entries_size,
sizeof(cache_entry), configuration_entry_cache_mp_part_cmp);
if (result == NULL) {
TRACE_OUT(configuration_entry_find_mp_cache_entries);
return (-1);
}
*start = result;
*finish = result + 1;
while (*start != config_entry->mp_cache_entries) {
if (configuration_entry_cache_mp_part_cmp(mp_name, *start - 1) == 0)
*start = *start - 1;
else
break;
}
while (*finish != config_entry->mp_cache_entries +
config_entry->mp_cache_entries_size) {
if (configuration_entry_cache_mp_part_cmp(
mp_name, *finish) == 0)
*finish = *finish + 1;
else
break;
}
TRACE_OUT(configuration_entry_find_mp_cache_entries);
return (0);
}
/*
* Configuration entry uses rwlock to handle access to its fields.
*/
void
configuration_lock_rdlock(struct configuration *config)
{
TRACE_IN(configuration_lock_rdlock);
pthread_rwlock_rdlock(&config->rwlock);
TRACE_OUT(configuration_lock_rdlock);
}
void
configuration_lock_wrlock(struct configuration *config)
{
TRACE_IN(configuration_lock_wrlock);
pthread_rwlock_wrlock(&config->rwlock);
TRACE_OUT(configuration_lock_wrlock);
}
void
configuration_unlock(struct configuration *config)
{
TRACE_IN(configuration_unlock);
pthread_rwlock_unlock(&config->rwlock);
TRACE_OUT(configuration_unlock);
}
/*
* Configuration entry uses 3 mutexes to handle cache operations. They are
* acquired by configuration_lock_entry and configuration_unlock_entry
* functions.
*/
void
configuration_lock_entry(struct configuration_entry *entry,
enum config_entry_lock_type lock_type)
{
TRACE_IN(configuration_lock_entry);
assert(entry != NULL);
switch (lock_type) {
case CELT_POSITIVE:
pthread_mutex_lock(&entry->positive_cache_lock);
break;
case CELT_NEGATIVE:
pthread_mutex_lock(&entry->negative_cache_lock);
break;
case CELT_MULTIPART:
pthread_mutex_lock(&entry->mp_cache_lock);
break;
default:
/* should be unreachable */
break;
}
TRACE_OUT(configuration_lock_entry);
}
void
configuration_unlock_entry(struct configuration_entry *entry,
enum config_entry_lock_type lock_type)
{
TRACE_IN(configuration_unlock_entry);
assert(entry != NULL);
switch (lock_type) {
case CELT_POSITIVE:
pthread_mutex_unlock(&entry->positive_cache_lock);
break;
case CELT_NEGATIVE:
pthread_mutex_unlock(&entry->negative_cache_lock);
break;
case CELT_MULTIPART:
pthread_mutex_unlock(&entry->mp_cache_lock);
break;
default:
/* should be unreachable */
break;
}
TRACE_OUT(configuration_unlock_entry);
}
struct configuration *
init_configuration(void)
{
struct configuration *retval;
TRACE_IN(init_configuration);
retval = calloc(1, sizeof(*retval));
assert(retval != NULL);
retval->entries_capacity = INITIAL_ENTRIES_CAPACITY;
retval->entries = calloc(1,
sizeof(*retval->entries) *
retval->entries_capacity);
assert(retval->entries != NULL);
pthread_rwlock_init(&retval->rwlock, NULL);
TRACE_OUT(init_configuration);
return (retval);
}
void
fill_configuration_defaults(struct configuration *config)
{
size_t len, i;
TRACE_IN(fill_configuration_defaults);
assert(config != NULL);
if (config->socket_path != NULL)
free(config->socket_path);
len = strlen(DEFAULT_SOCKET_PATH);
config->socket_path = calloc(1, len + 1);
assert(config->socket_path != NULL);
memcpy(config->socket_path, DEFAULT_SOCKET_PATH, len);
len = strlen(DEFAULT_PIDFILE_PATH);
config->pidfile_path = calloc(1, len + 1);
assert(config->pidfile_path != NULL);
memcpy(config->pidfile_path, DEFAULT_PIDFILE_PATH, len);
config->socket_mode = S_IFSOCK | S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
config->force_unlink = 1;
config->query_timeout = DEFAULT_QUERY_TIMEOUT;
config->threads_num = DEFAULT_THREADS_NUM;
for (i = 0; i < config->entries_size; ++i)
destroy_configuration_entry(config->entries[i]);
config->entries_size = 0;
TRACE_OUT(fill_configuration_defaults);
}
void
destroy_configuration(struct configuration *config)
{
unsigned int i;
TRACE_IN(destroy_configuration);
assert(config != NULL);
free(config->pidfile_path);
free(config->socket_path);
for (i = 0; i < config->entries_size; ++i)
destroy_configuration_entry(config->entries[i]);
free(config->entries);
pthread_rwlock_destroy(&config->rwlock);
free(config);
TRACE_OUT(destroy_configuration);
}