summaryrefslogtreecommitdiffstats
path: root/src/auth/db-dict.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/db-dict.c')
-rw-r--r--src/auth/db-dict.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/src/auth/db-dict.c b/src/auth/db-dict.c
new file mode 100644
index 0000000..96b1a63
--- /dev/null
+++ b/src/auth/db-dict.c
@@ -0,0 +1,654 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#include "array.h"
+#include "istream.h"
+#include "str.h"
+#include "json-parser.h"
+#include "settings.h"
+#include "dict.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+#include "db-dict.h"
+
+#include <stddef.h>
+
+enum dict_settings_section {
+ DICT_SETTINGS_SECTION_ROOT = 0,
+ DICT_SETTINGS_SECTION_KEY,
+ DICT_SETTINGS_SECTION_PASSDB,
+ DICT_SETTINGS_SECTION_USERDB
+};
+
+struct dict_settings_parser_ctx {
+ struct dict_connection *conn;
+ enum dict_settings_section section;
+ struct db_dict_key *cur_key;
+};
+
+struct db_dict_iter_key {
+ const struct db_dict_key *key;
+ bool used;
+ const char *value;
+};
+
+struct db_dict_value_iter {
+ pool_t pool;
+ struct auth_request *auth_request;
+ struct dict_connection *conn;
+ const struct var_expand_table *var_expand_table;
+ ARRAY(struct db_dict_iter_key) keys;
+
+ const ARRAY_TYPE(db_dict_field) *fields;
+ const ARRAY_TYPE(db_dict_key_p) *objects;
+ unsigned int field_idx;
+ unsigned int object_idx;
+
+ struct json_parser *json_parser;
+ string_t *tmpstr;
+ const char *error;
+};
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_dict_settings)
+static struct setting_def setting_defs[] = {
+ DEF_STR(uri),
+ DEF_STR(default_pass_scheme),
+ DEF_STR(iterate_prefix),
+ DEF_BOOL(iterate_disable),
+
+ DEF_STR(passdb_objects),
+ DEF_STR(userdb_objects),
+ { 0, NULL, 0 }
+};
+
+static struct db_dict_settings default_dict_settings = {
+ .uri = NULL,
+ .default_pass_scheme = "MD5",
+ .iterate_prefix = "",
+ .iterate_disable = FALSE,
+ .passdb_objects = "",
+ .userdb_objects = ""
+};
+
+#undef DEF_STR
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_key)
+static struct setting_def key_setting_defs[] = {
+ DEF_STR(name),
+ DEF_STR(key),
+ DEF_STR(format),
+ DEF_STR(default_value),
+
+ { 0, NULL, 0 }
+};
+
+static struct db_dict_key default_key_settings = {
+ .name = NULL,
+ .key = "",
+ .format = "value",
+ .default_value = NULL
+};
+
+static struct dict_connection *connections = NULL;
+
+static struct dict_connection *dict_conn_find(const char *config_path)
+{
+ struct dict_connection *conn;
+
+ for (conn = connections; conn != NULL; conn = conn->next) {
+ if (strcmp(conn->config_path, config_path) == 0)
+ return conn;
+ }
+
+ return NULL;
+}
+
+static bool
+parse_obsolete_setting(const char *key, const char *value,
+ struct dict_settings_parser_ctx *ctx,
+ const char **error_r)
+{
+ const struct db_dict_key *dbkey;
+
+ if (strcmp(key, "password_key") == 0) {
+ /* key passdb { key=<value> format=json }
+ passdb_objects = passdb */
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = "passdb";
+ ctx->cur_key->format = "json";
+ ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON;
+ ctx->cur_key->key = p_strdup(ctx->conn->pool, value);
+
+ dbkey = ctx->cur_key;
+ array_push_back(&ctx->conn->set.parsed_passdb_objects, &dbkey);
+ return TRUE;
+ }
+ if (strcmp(key, "user_key") == 0) {
+ /* key userdb { key=<value> format=json }
+ userdb_objects = userdb */
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = "userdb";
+ ctx->cur_key->format = "json";
+ ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON;
+ ctx->cur_key->key = p_strdup(ctx->conn->pool, value);
+
+ dbkey = ctx->cur_key;
+ array_push_back(&ctx->conn->set.parsed_userdb_objects, &dbkey);
+ return TRUE;
+ }
+ if (strcmp(key, "value_format") == 0) {
+ if (strcmp(value, "json") == 0)
+ return TRUE;
+ *error_r = "Deprecated value_format must be 'json'";
+ return FALSE;
+ }
+ return FALSE;
+}
+
+static const char *parse_setting(const char *key, const char *value,
+ struct dict_settings_parser_ctx *ctx)
+{
+ struct db_dict_field *field;
+ const char *error = NULL;
+
+ switch (ctx->section) {
+ case DICT_SETTINGS_SECTION_ROOT:
+ if (parse_obsolete_setting(key, value, ctx, &error))
+ return NULL;
+ if (error != NULL)
+ return error;
+ return parse_setting_from_defs(ctx->conn->pool, setting_defs,
+ &ctx->conn->set, key, value);
+ case DICT_SETTINGS_SECTION_KEY:
+ return parse_setting_from_defs(ctx->conn->pool, key_setting_defs,
+ ctx->cur_key, key, value);
+ case DICT_SETTINGS_SECTION_PASSDB:
+ field = array_append_space(&ctx->conn->set.passdb_fields);
+ field->name = p_strdup(ctx->conn->pool, key);
+ field->value = p_strdup(ctx->conn->pool, value);
+ return NULL;
+ case DICT_SETTINGS_SECTION_USERDB:
+ field = array_append_space(&ctx->conn->set.userdb_fields);
+ field->name = p_strdup(ctx->conn->pool, key);
+ field->value = p_strdup(ctx->conn->pool, value);
+ return NULL;
+ }
+ i_unreached();
+}
+
+static bool parse_section(const char *type, const char *name,
+ struct dict_settings_parser_ctx *ctx,
+ const char **errormsg)
+{
+ if (type == NULL) {
+ ctx->section = DICT_SETTINGS_SECTION_ROOT;
+ if (ctx->cur_key != NULL) {
+ if (strcmp(ctx->cur_key->format, "value") == 0) {
+ ctx->cur_key->parsed_format =
+ DB_DICT_VALUE_FORMAT_VALUE;
+ } else if (strcmp(ctx->cur_key->format, "json") == 0) {
+ ctx->cur_key->parsed_format =
+ DB_DICT_VALUE_FORMAT_JSON;
+ } else {
+ *errormsg = t_strconcat("Unknown key format: ",
+ ctx->cur_key->format, NULL);
+ return FALSE;
+ }
+ }
+ ctx->cur_key = NULL;
+ return TRUE;
+ }
+ if (ctx->section != DICT_SETTINGS_SECTION_ROOT) {
+ *errormsg = "Nested sections not supported";
+ return FALSE;
+ }
+ if (strcmp(type, "key") == 0) {
+ if (name == NULL) {
+ *errormsg = "Key section is missing name";
+ return FALSE;
+ }
+ if (strchr(name, '.') != NULL) {
+ *errormsg = "Key section names must not contain '.'";
+ return FALSE;
+ }
+ ctx->section = DICT_SETTINGS_SECTION_KEY;
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = p_strdup(ctx->conn->pool, name);
+ return TRUE;
+ }
+ if (strcmp(type, "passdb_fields") == 0) {
+ ctx->section = DICT_SETTINGS_SECTION_PASSDB;
+ return TRUE;
+ }
+ if (strcmp(type, "userdb_fields") == 0) {
+ ctx->section = DICT_SETTINGS_SECTION_USERDB;
+ return TRUE;
+ }
+ *errormsg = "Unknown section";
+ return FALSE;
+}
+
+static void
+db_dict_settings_parse(struct db_dict_settings *set)
+{
+ const struct db_dict_key *key;
+ const char *const *tmp;
+
+ tmp = t_strsplit_spaces(set->passdb_objects, " ");
+ for (; *tmp != NULL; tmp++) {
+ key = db_dict_set_key_find(&set->keys, *tmp);
+ if (key == NULL) {
+ i_fatal("dict: passdb_objects refers to key %s, "
+ "which doesn't exist", *tmp);
+ }
+ if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) {
+ i_fatal("dict: passdb_objects refers to key %s, "
+ "but it's in value-only format", *tmp);
+ }
+ array_push_back(&set->parsed_passdb_objects, &key);
+ }
+
+ tmp = t_strsplit_spaces(set->userdb_objects, " ");
+ for (; *tmp != NULL; tmp++) {
+ key = db_dict_set_key_find(&set->keys, *tmp);
+ if (key == NULL) {
+ i_fatal("dict: userdb_objects refers to key %s, "
+ "which doesn't exist", *tmp);
+ }
+ if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) {
+ i_fatal("dict: userdb_objects refers to key %s, "
+ "but it's in value-only format", *tmp);
+ }
+ array_push_back(&set->parsed_userdb_objects, &key);
+ }
+}
+
+struct dict_connection *db_dict_init(const char *config_path)
+{
+ struct dict_settings dict_set;
+ struct dict_settings_parser_ctx ctx;
+ struct dict_connection *conn;
+ const char *error;
+ pool_t pool;
+
+ conn = dict_conn_find(config_path);
+ if (conn != NULL) {
+ conn->refcount++;
+ return conn;
+ }
+
+ if (*config_path == '\0')
+ i_fatal("dict: Configuration file path not given");
+
+ pool = pool_alloconly_create("dict_connection", 1024);
+ conn = p_new(pool, struct dict_connection, 1);
+ conn->pool = pool;
+
+ conn->refcount = 1;
+
+ conn->config_path = p_strdup(pool, config_path);
+ conn->set = default_dict_settings;
+ p_array_init(&conn->set.keys, pool, 8);
+ p_array_init(&conn->set.passdb_fields, pool, 8);
+ p_array_init(&conn->set.userdb_fields, pool, 8);
+ p_array_init(&conn->set.parsed_passdb_objects, pool, 2);
+ p_array_init(&conn->set.parsed_userdb_objects, pool, 2);
+
+ i_zero(&ctx);
+ ctx.conn = conn;
+ if (!settings_read(config_path, NULL, parse_setting,
+ parse_section, &ctx, &error))
+ i_fatal("dict %s: %s", config_path, error);
+ db_dict_settings_parse(&conn->set);
+
+ if (conn->set.uri == NULL)
+ i_fatal("dict %s: Empty uri setting", config_path);
+
+ i_zero(&dict_set);
+ dict_set.base_dir = global_auth_settings->base_dir;
+ dict_set.event_parent = auth_event;
+ if (dict_init(conn->set.uri, &dict_set, &conn->dict, &error) < 0)
+ i_fatal("dict %s: Failed to init dict: %s", config_path, error);
+
+ conn->next = connections;
+ connections = conn;
+ return conn;
+}
+
+void db_dict_unref(struct dict_connection **_conn)
+{
+ struct dict_connection *conn = *_conn;
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return;
+
+ dict_deinit(&conn->dict);
+ pool_unref(&conn->pool);
+}
+
+static struct db_dict_iter_key *
+db_dict_iter_find_key(struct db_dict_value_iter *iter, const char *name)
+{
+ struct db_dict_iter_key *key;
+
+ array_foreach_modifiable(&iter->keys, key) {
+ if (strcmp(key->key->name, name) == 0)
+ return key;
+ }
+ return NULL;
+}
+
+static void db_dict_iter_find_used_keys(struct db_dict_value_iter *iter)
+{
+ const struct db_dict_field *field;
+ struct db_dict_iter_key *key;
+ const char *p, *name;
+ unsigned int idx, size;
+
+ array_foreach(iter->fields, field) {
+ for (p = field->value; *p != '\0'; ) {
+ if (*p != '%') {
+ p++;
+ continue;
+ }
+
+ var_get_key_range(++p, &idx, &size);
+ if (size == 0) {
+ /* broken %variable ending too early */
+ break;
+ }
+ p += idx;
+ if (size > 5 && memcmp(p, "dict:", 5) == 0) {
+ name = t_strcut(t_strndup(p+5, size-5), ':');
+ key = db_dict_iter_find_key(iter, name);
+ if (key != NULL)
+ key->used = TRUE;
+ }
+ p += size;
+ }
+ }
+}
+
+static void db_dict_iter_find_used_objects(struct db_dict_value_iter *iter)
+{
+ const struct db_dict_key *dict_key;
+ struct db_dict_iter_key *key;
+
+ array_foreach_elem(iter->objects, dict_key) {
+ key = db_dict_iter_find_key(iter, dict_key->name);
+ i_assert(key != NULL); /* checked at init */
+ i_assert(key->key->parsed_format != DB_DICT_VALUE_FORMAT_VALUE);
+ key->used = TRUE;
+ }
+}
+
+static int
+db_dict_iter_key_cmp(const struct db_dict_iter_key *k1,
+ const struct db_dict_iter_key *k2)
+{
+ return null_strcmp(k1->key->default_value, k2->key->default_value);
+}
+
+static int db_dict_iter_lookup_key_values(struct db_dict_value_iter *iter)
+{
+ struct db_dict_iter_key *key;
+ string_t *path;
+ const char *error;
+ int ret;
+
+ /* sort the keys so that we'll first lookup the keys without
+ default value. if their lookup fails, the user doesn't exist. */
+ array_sort(&iter->keys, db_dict_iter_key_cmp);
+
+ path = t_str_new(128);
+ str_append(path, DICT_PATH_SHARED);
+
+ struct dict_op_settings set = {
+ .username = iter->auth_request->fields.user,
+ };
+
+ array_foreach_modifiable(&iter->keys, key) {
+ if (!key->used)
+ continue;
+
+ str_truncate(path, strlen(DICT_PATH_SHARED));
+ str_append(path, key->key->key);
+ ret = dict_lookup(iter->conn->dict, &set, iter->pool,
+ str_c(path), &key->value, &error);
+ if (ret > 0) {
+ e_debug(authdb_event(iter->auth_request),
+ "Lookup: %s = %s", str_c(path),
+ key->value);
+ } else if (ret < 0) {
+ e_error(authdb_event(iter->auth_request),
+ "Failed to lookup key %s: %s", str_c(path), error);
+ return -1;
+ } else if (key->key->default_value != NULL) {
+ e_debug(authdb_event(iter->auth_request),
+ "Lookup: %s not found, using default value %s",
+ str_c(path), key->key->default_value);
+ key->value = key->key->default_value;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int db_dict_value_iter_init(struct dict_connection *conn,
+ struct auth_request *auth_request,
+ const ARRAY_TYPE(db_dict_field) *fields,
+ const ARRAY_TYPE(db_dict_key_p) *objects,
+ struct db_dict_value_iter **iter_r)
+{
+ struct db_dict_value_iter *iter;
+ struct db_dict_iter_key *iterkey;
+ const struct db_dict_key *key;
+ pool_t pool;
+ int ret;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"auth dict lookup", 1024);
+ iter = p_new(pool, struct db_dict_value_iter, 1);
+ iter->pool = pool;
+ iter->conn = conn;
+ iter->fields = fields;
+ iter->objects = objects;
+ iter->tmpstr = str_new(pool, 128);
+ iter->auth_request = auth_request;
+ iter->var_expand_table = auth_request_get_var_expand_table(auth_request, NULL);
+
+ /* figure out what keys we need to lookup, and lookup them */
+ p_array_init(&iter->keys, pool, array_count(&conn->set.keys));
+ array_foreach(&conn->set.keys, key) {
+ iterkey = array_append_space(&iter->keys);
+ struct db_dict_key *new_key = p_new(iter->pool, struct db_dict_key, 1);
+ memcpy(new_key, key, sizeof(struct db_dict_key));
+ string_t *expanded_key = str_new(iter->pool, strlen(key->key));
+ const char *error;
+ if (auth_request_var_expand_with_table(expanded_key, key->key, auth_request,
+ iter->var_expand_table,
+ NULL, &error) <= 0) {
+ e_error(authdb_event(iter->auth_request),
+ "Failed to expand key %s: %s", key->key, error);
+ pool_unref(&pool);
+ return -1;
+ }
+ new_key->key = str_c(expanded_key);
+ iterkey->key = new_key;
+ }
+ T_BEGIN {
+ db_dict_iter_find_used_keys(iter);
+ db_dict_iter_find_used_objects(iter);
+ ret = db_dict_iter_lookup_key_values(iter);
+ } T_END;
+ if (ret <= 0) {
+ pool_unref(&pool);
+ return ret;
+ }
+ *iter_r = iter;
+ return 1;
+}
+
+static bool
+db_dict_value_iter_json_next(struct db_dict_value_iter *iter,
+ string_t *tmpstr,
+ const char **key_r, const char **value_r)
+{
+ enum json_type type;
+ const char *value;
+
+ if (json_parse_next(iter->json_parser, &type, &value) < 0)
+ return FALSE;
+ if (type != JSON_TYPE_OBJECT_KEY) {
+ iter->error = "Object expected";
+ return FALSE;
+ }
+ if (*value == '\0') {
+ iter->error = "Empty object key";
+ return FALSE;
+ }
+ str_truncate(tmpstr, 0);
+ str_append(tmpstr, value);
+
+ if (json_parse_next(iter->json_parser, &type, &value) < 0) {
+ iter->error = "Missing value";
+ return FALSE;
+ }
+ if (type == JSON_TYPE_OBJECT) {
+ iter->error = "Nested objects not supported";
+ return FALSE;
+ }
+ *key_r = str_c(tmpstr);
+ *value_r = value;
+ return TRUE;
+}
+
+static void
+db_dict_value_iter_json_init(struct db_dict_value_iter *iter, const char *data)
+{
+ struct istream *input;
+
+ i_assert(iter->json_parser == NULL);
+
+ input = i_stream_create_from_data(data, strlen(data));
+ iter->json_parser = json_parser_init(input);
+ i_stream_unref(&input);
+}
+
+static bool
+db_dict_value_iter_object_next(struct db_dict_value_iter *iter,
+ const char **key_r, const char **value_r)
+{
+ const struct db_dict_key *dict_key;
+ struct db_dict_iter_key *key;
+
+ if (iter->json_parser != NULL)
+ return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r);
+ if (iter->object_idx == array_count(iter->objects))
+ return FALSE;
+
+ dict_key = array_idx_elem(iter->objects, iter->object_idx);
+ key = db_dict_iter_find_key(iter, dict_key->name);
+ i_assert(key != NULL); /* checked at init */
+
+ switch (key->key->parsed_format) {
+ case DB_DICT_VALUE_FORMAT_VALUE:
+ i_unreached();
+ case DB_DICT_VALUE_FORMAT_JSON:
+ db_dict_value_iter_json_init(iter, key->value);
+ return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r);
+ }
+ i_unreached();
+}
+
+static int
+db_dict_field_find(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct db_dict_value_iter *iter = context;
+ struct db_dict_iter_key *key;
+ const char *name, *value, *dotname = strchr(data, '.');
+ string_t *tmpstr;
+
+ *value_r = NULL;
+
+ if (dotname != NULL)
+ data = t_strdup_until(data, dotname++);
+ key = db_dict_iter_find_key(iter, data);
+ if (key == NULL)
+ return 1;
+
+ switch (key->key->parsed_format) {
+ case DB_DICT_VALUE_FORMAT_VALUE:
+ *value_r = dotname != NULL ? NULL :
+ (key->value == NULL ? "" : key->value);
+ return 1;
+ case DB_DICT_VALUE_FORMAT_JSON:
+ if (dotname == NULL)
+ return 1;
+ db_dict_value_iter_json_init(iter, key->value);
+ *value_r = "";
+ tmpstr = t_str_new(64);
+ while (db_dict_value_iter_json_next(iter, tmpstr, &name, &value)) {
+ if (strcmp(name, dotname) == 0) {
+ *value_r = t_strdup(value);
+ break;
+ }
+ }
+ (void)json_parser_deinit(&iter->json_parser, &iter->error);
+ return 1;
+ }
+ i_unreached();
+}
+
+bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+ const char **key_r, const char **value_r)
+{
+ static struct var_expand_func_table var_funcs_table[] = {
+ { "dict", db_dict_field_find },
+ { NULL, NULL }
+ };
+ const struct db_dict_field *field;
+ const char *error;
+
+ if (iter->field_idx == array_count(iter->fields))
+ return db_dict_value_iter_object_next(iter, key_r, value_r);
+ field = array_idx(iter->fields, iter->field_idx++);
+
+ str_truncate(iter->tmpstr, 0);
+ if (var_expand_with_funcs(iter->tmpstr, field->value,
+ iter->var_expand_table, var_funcs_table,
+ iter, &error) <= 0) {
+ iter->error = p_strdup_printf(iter->pool,
+ "Failed to expand %s=%s: %s",
+ field->name, field->value, error);
+ return FALSE;
+ }
+ *key_r = field->name;
+ *value_r = str_c(iter->tmpstr);
+ return TRUE;
+}
+
+int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter,
+ const char **error_r)
+{
+ struct db_dict_value_iter *iter = *_iter;
+
+ *_iter = NULL;
+
+ *error_r = iter->error;
+ if (iter->json_parser != NULL) {
+ if (json_parser_deinit(&iter->json_parser, &iter->error) < 0 &&
+ *error_r == NULL)
+ *error_r = iter->error;
+ }
+
+ pool_unref(&iter->pool);
+ return *error_r != NULL ? -1 : 0;
+}