/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
#include "array.h"
#include "str.h"
#include "settings.h"
#include "dict-ldap-settings.h"
#include <ctype.h>
static const char *dict_ldap_commonName = "cn";
static const char *dict_ldap_empty_filter = "";
enum section_type {
SECTION_ROOT = 0,
SECTION_MAP,
SECTION_FIELDS
};
struct dict_ldap_map_attribute {
const char *name;
const char *variable;
};
struct setting_parser_ctx {
pool_t pool;
struct dict_ldap_settings *set;
enum section_type type;
struct dict_ldap_map cur_map;
ARRAY(struct dict_ldap_map_attribute) cur_attributes;
};
#undef DEF_STR
#undef DEF_BOOL
#undef DEF_UINT
#define DEF_STR(name) DEF_STRUCT_STR(name, dict_ldap_map)
#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_ldap_map)
#define DEF_UINT(name) DEF_STRUCT_UINT(name ,dict_ldap_map)
static const struct setting_def dict_ldap_map_setting_defs[] = {
DEF_STR(pattern),
DEF_STR(filter),
DEF_STR(filter_iter),
DEF_STR(username_attribute),
DEF_STR(value_attribute),
DEF_STR(base_dn),
DEF_STR(scope),
{ 0, NULL, 0 }
};
static const char *pattern_read_name(const char **pattern)
{
const char *p = *pattern, *name;
if (*p == '{') {
/* ${name} */
name = ++p;
p = strchr(p, '}');
if (p == NULL) {
/* error, but allow anyway */
*pattern += strlen(*pattern);
return "";
}
*pattern = p + 1;
} else {
/* $name - ends at the first non-alnum_ character */
name = p;
for (; *p != '\0'; p++) {
if (!i_isalnum(*p) && *p != '_')
break;
}
*pattern = p;
}
name = t_strdup_until(name, p);
return name;
}
static const char *dict_ldap_attributes_map(struct setting_parser_ctx *ctx)
{
struct dict_ldap_map_attribute *attributes;
string_t *pattern;
const char *p, *name;
unsigned int i, count;
/* go through the variables in the pattern, replace them with plain
'$' character and add its ldap attribute */
pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
attributes = array_get_modifiable(&ctx->cur_attributes, &count);
p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, count);
for (p = ctx->cur_map.pattern; *p != '\0';) {
if (*p != '$') {
str_append_c(pattern, *p);
p++;
continue;
}
p++;
str_append_c(pattern, '$');
name = pattern_read_name(&p);
for (i = 0; i < count; i++) {
if (attributes[i].variable != NULL &&
strcmp(attributes[i].variable, name) == 0)
break;
}
if (i == count) {
return t_strconcat("Missing LDAP attribute for variable: ",
name, NULL);
}
/* mark this attribute as used */
attributes[i].variable = NULL;
array_append(&ctx->cur_map.ldap_attributes,
&attributes[i].name, 1);
}
/* make sure there aren't any unused attributes */
for (i = 0; i < count; i++) {
if (attributes[i].variable != NULL) {
return t_strconcat("Unused variable: ",
attributes[i].variable, NULL);
}
}
if (ctx->set->max_attribute_count < count)
ctx->set->max_attribute_count = count;
ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
return NULL;
}
static const char *dict_ldap_map_finish(struct setting_parser_ctx *ctx)
{
if (ctx->cur_map.pattern == NULL)
return "Missing setting: pattern";
if (ctx->cur_map.filter == NULL)
ctx->cur_map.filter = dict_ldap_empty_filter;
if (*ctx->cur_map.filter != '\0') {
const char *ptr = ctx->cur_map.filter;
if (*ptr != '(')
return "Filter must start with (";
while(*ptr != '\0') ptr++;
ptr--;
if (*ptr != ')')
return "Filter must end with )";
}
if (ctx->cur_map.value_attribute == NULL)
return "Missing setting: value_attribute";
if (ctx->cur_map.username_attribute == NULL) {
/* default to commonName */
ctx->cur_map.username_attribute = dict_ldap_commonName;
}
if (ctx->cur_map.scope == NULL) {
ctx->cur_map.scope_val = 2; /* subtree */
} else {
if (strcasecmp(ctx->cur_map.scope, "one") == 0) ctx->cur_map.scope_val = 1;
else if (strcasecmp(ctx->cur_map.scope, "base") == 0) ctx->cur_map.scope_val = 0;
else if (strcasecmp(ctx->cur_map.scope, "subtree") == 0) ctx->cur_map.scope_val = 2;
else return "Scope must be one, base or subtree";
}
if (!array_is_created(&ctx->cur_map.ldap_attributes)) {
/* no attributes besides value. allocate the array anyway. */
p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, 1);
if (strchr(ctx->cur_map.pattern, '$') != NULL)
return "Missing attributes for pattern variables";
}
array_append(&ctx->set->maps, &ctx->cur_map, 1);
i_zero(&ctx->cur_map);
return NULL;
}
static const char *
parse_setting(const char *key, const char *value,
struct setting_parser_ctx *ctx)
{
struct dict_ldap_map_attribute *attribute;
switch (ctx->type) {
case SECTION_ROOT:
if (strcmp(key, "uri") == 0) {
ctx->set->uri = p_strdup(ctx->pool, value);
return NULL;
}
if (strcmp(key, "bind_dn") == 0) {
ctx->set->bind_dn = p_strdup(ctx->pool, value);
return NULL;
}
if (strcmp(key, "password") == 0) {
ctx->set->password = p_strdup(ctx->pool, value);
return NULL;
}
if (strcmp(key, "timeout") == 0) {
if (str_to_uint(value, &ctx->set->timeout) != 0) {
return "Invalid timeout value";
}
return NULL;
}
if (strcmp(key, "max_idle_time") == 0) {
if (str_to_uint(value, &ctx->set->max_idle_time) != 0) {
return "Invalid max_idle_time value";
}
return NULL;
}
if (strcmp(key, "debug") == 0) {
if (str_to_uint(value, &ctx->set->debug) != 0) {
return "invalid debug value";
}
return NULL;
}
if (strcmp(key, "tls") == 0) {
if (strcasecmp(value, "yes") == 0) {
ctx->set->require_ssl = TRUE;
ctx->set->start_tls = TRUE;
} else if (strcasecmp(value, "no") == 0) {
ctx->set->require_ssl = FALSE;
ctx->set->start_tls = FALSE;
} else if (strcasecmp(value, "try") == 0) {
ctx->set->require_ssl = FALSE;
ctx->set->start_tls = TRUE;
} else {
return "tls must be yes, try or no";
}
return NULL;
}
break;
case SECTION_MAP:
return parse_setting_from_defs(ctx->pool,
dict_ldap_map_setting_defs,
&ctx->cur_map, key, value);
case SECTION_FIELDS:
if (*value != '$') {
return t_strconcat("Value is missing '$' for attribute: ",
key, NULL);
}
attribute = array_append_space(&ctx->cur_attributes);
attribute->name = p_strdup(ctx->pool, key);
attribute->variable = p_strdup(ctx->pool, value + 1);
return NULL;
}
return t_strconcat("Unknown setting: ", key, NULL);
}
static bool
parse_section(const char *type, const char *name ATTR_UNUSED,
struct setting_parser_ctx *ctx, const char **error_r)
{
switch (ctx->type) {
case SECTION_ROOT:
if (type == NULL)
return FALSE;
if (strcmp(type, "map") == 0) {
array_clear(&ctx->cur_attributes);
ctx->type = SECTION_MAP;
return TRUE;
}
break;
case SECTION_MAP:
if (type == NULL) {
ctx->type = SECTION_ROOT;
*error_r = dict_ldap_map_finish(ctx);
return FALSE;
}
if (strcmp(type, "fields") == 0) {
ctx->type = SECTION_FIELDS;
return TRUE;
}
break;
case SECTION_FIELDS:
if (type == NULL) {
ctx->type = SECTION_MAP;
*error_r = dict_ldap_attributes_map(ctx);
return FALSE;
}
break;
}
*error_r = t_strconcat("Unknown section: ", type, NULL);
return FALSE;
}
struct dict_ldap_settings *
dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r)
{
struct setting_parser_ctx ctx;
i_zero(&ctx);
ctx.pool = pool;
ctx.set = p_new(pool, struct dict_ldap_settings, 1);
t_array_init(&ctx.cur_attributes, 16);
p_array_init(&ctx.set->maps, pool, 8);
ctx.set->timeout = 30; /* default timeout */
ctx.set->require_ssl = FALSE; /* try to start SSL */
ctx.set->start_tls = TRUE;
if (!settings_read(path, NULL, parse_setting, parse_section,
&ctx, error_r))
return NULL;
if (ctx.set->uri == NULL) {
*error_r = t_strdup_printf("Error in configuration file %s: "
"Missing ldap uri", path);
return NULL;
}
return ctx.set;
}
#endif