]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: passdb/userdb dict rewrite to support more complex configuration.
authorTimo Sirainen <tss@iki.fi>
Sun, 8 Dec 2013 17:00:31 +0000 (19:00 +0200)
committerTimo Sirainen <tss@iki.fi>
Sun, 8 Dec 2013 17:00:31 +0000 (19:00 +0200)
See the new doc/example-config/dovecot-dict-auth.conf.ext for explanation
how it works. The old configuration format will also stay functional.

doc/example-config/dovecot-dict-auth.conf.ext
src/auth/Makefile.am
src/auth/db-dict-cache-key.c [new file with mode: 0644]
src/auth/db-dict.c
src/auth/db-dict.h
src/auth/passdb-dict.c
src/auth/test-db-dict.c [new file with mode: 0644]
src/auth/userdb-dict.c

index 50617bb396abc8df86137d38c2a149844e4ff4c9..79f43de6ee9fe1724bcf880e77a11a0b821f976c 100644 (file)
@@ -1,24 +1,54 @@
-# This file is commonly accessed via dict {} section in dovecot.conf
+# This file is commonly accessed via passdb {} or userdb {} section in
+# conf.d/auth-dict.conf.ext
 
 # Dictionary URI
 #uri = 
 
-# Key for passdb lookups
-password_key = dovecot/passdb/%u
-
-# Key for userdb lookups
-user_key = dovecot/userdb/%u
-
-# How to parse the value for key=value pairs. Currently we support only JSON
-# format with { "key": "value", ... } object.
-#value_format = json
+# Default password scheme
+default_pass_scheme = MD5
 
 # Username iteration prefix. Keys under this are assumed to contain usernames.
-iterate_prefix = dovecot/userdb/
+iterate_prefix = userdb/
 
 # Should iteration be disabled for this userdb? If this userdb acts only as a
 # cache there's no reason to try to iterate the (partial & duplicate) users.
 #iterate_disable = no
 
-# Default password scheme
-default_pass_scheme = MD5
+# The example here shows how to do multiple dict lookups and merge the replies.
+# The "passdb" and "userdb" keys are JSON objects containing key/value pairs,
+# for example: { "uid": 1000, "gid": 1000, "home": "/home/user" }
+
+key passdb {
+  key = passdb/%u
+  format = json
+}
+key userdb {
+  key = userdb/%u
+  format = json
+}
+key quota {
+  key = userdb/%u/quota
+  #format = value
+  # The default_value is used if the key isn't found. If default_value setting
+  # isn't specified at all (even as empty), the passdb/userdb lookup fails with
+  # "user doesn't exist".
+  default_value = 100M
+}
+
+# Space separated list of keys whose values contain key/value paired objects.
+# All the key/value pairs inside the object are added as passdb fields.
+passdb_objects = passdb
+
+#passdb_fields {
+#}
+
+# Userdb key/value object list.
+userdb_objects = userdb
+
+userdb_fields {
+  # dict:<key> refers to key names
+  quota_rule = *:storage=%{dict:quota}
+
+  # dict:<key>.<objkey> refers to the objkey inside (JSON) object
+  mail = maildir:%{dict:userdb.home}/Maildir
+}
index 6b91d5c87afb3a52c23f33f23dcbd667669c24c6..f485953a1a0e0da428e65f0b63525b828a551c74 100644 (file)
@@ -77,6 +77,7 @@ auth_SOURCES = \
        auth-worker-server.c \
        db-checkpassword.c \
        db-dict.c \
+       db-dict-cache-key.c \
        db-sql.c \
        db-passwd-file.c \
        main.c \
@@ -193,7 +194,8 @@ checkpassword_reply_sources = \
        checkpassword-reply.c
 
 test_programs = \
-       test-auth-cache
+       test-auth-cache \
+       test-db-dict
 
 noinst_PROGRAMS = $(test_programs)
 
@@ -205,6 +207,10 @@ test_auth_cache_SOURCES = test-auth-cache.c
 test_auth_cache_LDADD = auth-cache.o $(test_libs)
 test_auth_cache_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
 
+test_db_dict_SOURCES = test-db-dict.c
+test_db_dict_LDADD = db-dict-cache-key.o $(test_libs)
+test_db_dict_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+
 check: check-am check-test
 check-test: all-am
        for bin in $(test_programs); do \
diff --git a/src/auth/db-dict-cache-key.c b/src/auth/db-dict-cache-key.c
new file mode 100644 (file)
index 0000000..b562ac1
--- /dev/null
@@ -0,0 +1,65 @@
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "db-dict.h"
+
+const struct db_dict_key *
+db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name)
+{
+       const struct db_dict_key *key;
+
+       array_foreach(keys, key) {
+               if (strcmp(key->name, name) == 0)
+                       return key;
+       }
+       return NULL;
+}
+
+
+const char *
+db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys,
+                       const ARRAY_TYPE(db_dict_field) *fields,
+                       const ARRAY_TYPE(db_dict_key_p) *objects)
+{
+       const struct db_dict_field *field;
+       const struct db_dict_key *key;
+       const struct db_dict_key *const *keyp;
+       const char *p, *name;
+       unsigned int idx, size;
+       string_t *str = t_str_new(128);
+
+       array_foreach(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_set_key_find(keys, name);
+                               if (key != NULL)
+                                       str_printfa(str, "\t%s", key->key);
+                       } else if (size == 1) {
+                               str_printfa(str, "\t%%%c", p[0]);
+                       } else {
+                               str_append(str, "\t%{");
+                               str_append_n(str, p, size);
+                               str_append_c(str, '}');
+                       }
+                       p += size;
+               }
+       }
+       array_foreach(objects, keyp)
+               str_printfa(str, "\t%s", (*keyp)->key);
+       return str_c(str);
+}
index dc8892b610c0c901bae21d5be336b4a78c0acb90..1ca61e60c6b236b4705e4418bd8d1124ef56d5da 100644 (file)
@@ -2,11 +2,12 @@
 
 #include "auth-common.h"
 
-#include "settings.h"
-#include "dict.h"
-#include "json-parser.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>
 #include <stdlib.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(password_key),
-       DEF_STR(user_key),
+       DEF_STR(default_pass_scheme),
        DEF_STR(iterate_prefix),
-       DEF_STR(value_format),
        DEF_BOOL(iterate_disable),
-       DEF_STR(default_pass_scheme),
 
+       DEF_STR(passdb_objects),
+       DEF_STR(userdb_objects),
        { 0, NULL, 0 }
 };
 
 static struct db_dict_settings default_dict_settings = {
        .uri = NULL,
-       .password_key = "",
-       .user_key = "",
+       .default_pass_scheme = "MD5",
        .iterate_prefix = "",
        .iterate_disable = FALSE,
-       .value_format = "json",
-       .default_pass_scheme = "MD5"
+       .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;
@@ -53,15 +105,167 @@ static struct dict_connection *dict_conn_find(const char *config_path)
        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_append(&ctx->conn->set.parsed_passdb_objects, &dbkey, 1);
+               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_append(&ctx->conn->set.parsed_userdb_objects, &dbkey, 1);
+               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_connection *conn)
+                                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;
+       }
+       return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static bool parse_section(const char *type, const char *name,
+                         struct dict_settings_parser_ctx *ctx,
+                         const char **errormsg)
 {
-       return parse_setting_from_defs(conn->pool, setting_defs,
-                                      &conn->set, key, value);
+       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 {
+                               return t_strconcat("Unknown key format: ",
+                                                  ctx->cur_key->format, NULL);
+                       }
+               }
+               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)
+                       return "Key section is missing name";
+               if (strchr(name, '.') != NULL)
+                       return "Key section names must not contain '.'";
+               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_append(&set->parsed_passdb_objects, &key, 1);
+       }
+
+       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_append(&set->parsed_userdb_objects, &key, 1);
+       }
 }
 
 struct dict_connection *db_dict_init(const char *config_path)
 {
+       struct dict_settings_parser_ctx ctx;
        struct dict_connection *conn;
        const char *error;
        pool_t pool;
@@ -83,15 +287,21 @@ struct dict_connection *db_dict_init(const char *config_path)
 
        conn->config_path = p_strdup(pool, config_path);
        conn->set = default_dict_settings;
-       if (!settings_read_nosection(config_path, parse_setting, conn, &error))
+       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);
+
+       memset(&ctx, 0, sizeof(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);
-       if (strcmp(conn->set.value_format, "json") != 0) {
-               i_fatal("dict %s: Unsupported value_format %s in ",
-                       config_path, conn->set.value_format);
-       }
        if (dict_init(conn->set.uri, DICT_DATA_TYPE_STRING, "",
                      global_auth_settings->base_dir, &conn->dict, &error) < 0)
                i_fatal("dict %s: Failed to init dict: %s", config_path, error);
@@ -113,37 +323,160 @@ void db_dict_unref(struct dict_connection **_conn)
        pool_unref(&conn->pool);
 }
 
-struct db_dict_value_iter {
-       struct json_parser *parser;
-       string_t *key;
-       const char *error;
-};
+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;
+}
 
-struct db_dict_value_iter *
-db_dict_value_iter_init(struct dict_connection *conn, const char *value)
+static void db_dict_iter_find_used_keys(struct db_dict_value_iter *iter)
 {
-       struct db_dict_value_iter *iter;
-       struct istream *input;
+       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 *const *keyp;
+       struct db_dict_iter_key *key;
+
+       array_foreach(iter->objects, keyp) {
+               key = db_dict_iter_find_key(iter, (*keyp)->name);
+               i_assert(key != NULL); /* checked at init */
+               i_assert(key->key->parsed_format != DB_DICT_VALUE_FORMAT_VALUE);
+               key->used = TRUE;
+       }
+}
 
-       i_assert(strcmp(conn->set.value_format, "json") == 0);
+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);
+}
 
-       /* hardcoded for now for JSON value. make it more modular when other
-          value types are supported. */
-       iter = i_new(struct db_dict_value_iter, 1);
-       iter->key = str_new(default_pool, 64);
-       input = i_stream_create_from_data(value, strlen(value));
-       iter->parser = json_parser_init(input);
-       i_stream_unref(&input);
-       return iter;
+static int db_dict_iter_lookup_key_values(struct db_dict_value_iter *iter)
+{
+       struct db_dict_iter_key *key;
+       string_t *path;
+       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);
+
+       array_foreach_modifiable(&iter->keys, key) {
+               if (!key->used)
+                       continue;
+
+               str_truncate(path, strlen(DICT_PATH_SHARED));
+               var_expand(path, key->key->key, iter->var_expand_table);
+               ret = dict_lookup(iter->conn->dict, iter->pool,
+                                 str_c(path), &key->value);
+               if (ret > 0) {
+                       auth_request_log_debug(iter->auth_request, "dict",
+                                              "Lookup: %s = %s", str_c(path),
+                                              key->value);
+               } else if (ret < 0) {
+                       auth_request_log_error(iter->auth_request, "dict",
+                               "Failed to lookup key %s", str_c(path));
+                       return -1;
+               } else if (key->key->default_value != NULL) {
+                       auth_request_log_debug(iter->auth_request, "dict",
+                               "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;
 }
 
-bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+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("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);
+               iterkey->key = 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->parser, &type, &value) < 0)
+       if (json_parse_next(iter->json_parser, &type, &value) < 0)
                return FALSE;
        if (type != JSON_TYPE_OBJECT_KEY) {
                iter->error = "Object expected";
@@ -153,10 +486,10 @@ bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
                iter->error = "Empty object key";
                return FALSE;
        }
-       str_truncate(iter->key, 0);
-       str_append(iter->key, value);
+       str_truncate(tmpstr, 0);
+       str_append(tmpstr, value);
 
-       if (json_parse_next(iter->parser, &type, &value) < 0) {
+       if (json_parse_next(iter->json_parser, &type, &value) < 0) {
                iter->error = "Missing value";
                return FALSE;
        }
@@ -164,11 +497,106 @@ bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
                iter->error = "Nested objects not supported";
                return FALSE;
        }
-       *key_r = str_c(iter->key);
+       *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 *const *keyp;
+       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;
+
+       keyp = array_idx(iter->objects, iter->object_idx);
+       key = db_dict_iter_find_key(iter, (*keyp)->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 const char *
+db_dict_field_find(const char *data, void *context)
+{
+       struct db_dict_value_iter *iter = context;
+       struct db_dict_iter_key *key;
+       const char *name, *value, *ret, *dotname = strchr(data, '.');
+       string_t *tmpstr;
+
+       if (dotname != NULL)
+               data = t_strdup_until(data, dotname++);
+       key = db_dict_iter_find_key(iter, data);
+       if (key == NULL)
+               return NULL;
+
+       switch (key->key->parsed_format) {
+       case DB_DICT_VALUE_FORMAT_VALUE:
+               return dotname != NULL ? NULL :
+                       (key->value == NULL ? "" : key->value);
+       case DB_DICT_VALUE_FORMAT_JSON:
+               if (dotname == NULL)
+                       return NULL;
+               db_dict_value_iter_json_init(iter, key->value);
+               ret = "";
+               tmpstr = t_str_new(64);
+               while (db_dict_value_iter_json_next(iter, tmpstr, &name, &value)) {
+                       if (strcmp(name, dotname) == 0) {
+                               ret = t_strdup(value);
+                               break;
+                       }
+               }
+               (void)json_parser_deinit(&iter->json_parser, &iter->error);
+               return ret;
+       }
+       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;
+
+       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);
+       var_expand_with_funcs(iter->tmpstr, field->value,
+                             iter->var_expand_table, var_funcs_table, iter);
+       *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)
 {
@@ -177,10 +605,12 @@ int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter,
        *_iter = NULL;
 
        *error_r = iter->error;
-       if (json_parser_deinit(&iter->parser, &iter->error) < 0 &&
-           *error_r == NULL)
-               *error_r = iter->error;
-       str_free(&iter->key);
-       i_free(iter);
+       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;
 }
index dbba4209dcc2b2fa3811efdb302a4fa6f2056840..f4feb0aee26dfa2cbbc594e1d5ee8a3b1f244fd9 100644 (file)
@@ -3,14 +3,46 @@
 
 #include "sql-api.h"
 
+struct auth_request;
+struct db_dict_value_iter;
+
+enum db_dict_value_format {
+       DB_DICT_VALUE_FORMAT_VALUE = 0,
+       DB_DICT_VALUE_FORMAT_JSON
+};
+
+struct db_dict_key {
+       const char *name;
+       const char *key;
+       const char *format;
+       const char *default_value;
+
+       enum db_dict_value_format parsed_format;
+};
+ARRAY_DEFINE_TYPE(db_dict_key, struct db_dict_key);
+ARRAY_DEFINE_TYPE(db_dict_key_p, const struct db_dict_key *);
+
+struct db_dict_field {
+       const char *name;
+       const char *value;
+};
+ARRAY_DEFINE_TYPE(db_dict_field, struct db_dict_field);
+
 struct db_dict_settings {
        const char *uri;
-       const char *password_key;
-       const char *user_key;
+       const char *default_pass_scheme;
        const char *iterate_prefix;
        bool iterate_disable;
-       const char *value_format;
-       const char *default_pass_scheme;
+
+       ARRAY_TYPE(db_dict_key) keys;
+
+       const char *passdb_objects;
+       const char *userdb_objects;
+       ARRAY_TYPE(db_dict_field) passdb_fields;
+       ARRAY_TYPE(db_dict_field) userdb_fields;
+
+       ARRAY_TYPE(db_dict_key_p) parsed_passdb_objects;
+       ARRAY_TYPE(db_dict_key_p) parsed_userdb_objects;
 };
 
 struct dict_connection {
@@ -27,11 +59,24 @@ struct dict_connection {
 struct dict_connection *db_dict_init(const char *config_path);
 void db_dict_unref(struct dict_connection **conn);
 
-struct db_dict_value_iter *
-db_dict_value_iter_init(struct dict_connection *conn, const char *value);
+/* Returns 1 if ok, 0 if a key without default_value wasn't returned
+   ("user doesn't exist"), -1 if internal error */
+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);
 bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
                             const char **key_r, const char **value_r);
 int db_dict_value_iter_deinit(struct db_dict_value_iter **iter,
                              const char **error_r);
 
+const char *db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys,
+                                   const ARRAY_TYPE(db_dict_field) *fields,
+                                   const ARRAY_TYPE(db_dict_key_p) *objects);
+
+/* private: */
+const struct db_dict_key *
+db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name);
+
 #endif
index f4b4dc70193bffa67d788f13d6e73794707bf866..cb322cedf2e73e6e7dd10fa6677947345e2ab2df 100644 (file)
@@ -3,6 +3,7 @@
 #include "auth-common.h"
 #include "passdb.h"
 
+#include "array.h"
 #include "str.h"
 #include "var-expand.h"
 #include "dict.h"
@@ -29,12 +30,11 @@ struct passdb_dict_request {
 
 static int
 dict_query_save_results(struct auth_request *auth_request,
-                       struct dict_connection *conn, const char *result)
+                       struct dict_connection *conn,
+                       struct db_dict_value_iter *iter)
 {
-       struct db_dict_value_iter *iter;
        const char *key, *value, *error;
 
-       iter = db_dict_value_iter_init(conn, result);
        while (db_dict_value_iter_next(iter, &key, &value)) {
                if (value != NULL) {
                        auth_request_set_field(auth_request, key, value,
@@ -42,9 +42,7 @@ dict_query_save_results(struct auth_request *auth_request,
                }
        }
        if (db_dict_value_iter_deinit(&iter, &error) < 0) {
-               auth_request_log_error(auth_request, "dict",
-                       "Value '%s' not in valid %s format: %s",
-                       result, conn->set.value_format, error);
+               auth_request_log_error(auth_request, "dict", "%s", error);
                return -1;
        }
        return 0;
@@ -52,24 +50,22 @@ dict_query_save_results(struct auth_request *auth_request,
 
 static enum passdb_result
 passdb_dict_lookup_key(struct auth_request *auth_request,
-                      struct dict_passdb_module *module, const char *key)
+                      struct dict_passdb_module *module)
 {
-       const char *value;
+       struct db_dict_value_iter *iter;
        int ret;
 
-       auth_request_log_debug(auth_request, "dict", "lookup %s", key);
-       ret = dict_lookup(module->conn->dict, pool_datastack_create(),
-                         key, &value);
-       if (ret < 0) {
-               auth_request_log_error(auth_request, "dict", "Lookup failed");
+       ret = db_dict_value_iter_init(module->conn, auth_request,
+                                     &module->conn->set.passdb_fields,
+                                     &module->conn->set.parsed_passdb_objects,
+                                     &iter);
+       if (ret < 0)
                return PASSDB_RESULT_INTERNAL_FAILURE;
-       else if (ret == 0) {
+       else if (ret == 0) {
                auth_request_log_unknown_user(auth_request, "dict");
                return PASSDB_RESULT_USER_UNKNOWN;
        } else {
-               auth_request_log_debug(auth_request, "dict",
-                                      "result: %s", value);
-               if (dict_query_save_results(auth_request, module->conn, value) < 0)
+               if (dict_query_save_results(auth_request, module->conn, iter) < 0)
                        return PASSDB_RESULT_INTERNAL_FAILURE;
 
                if (auth_request->passdb_password == NULL &&
@@ -89,23 +85,17 @@ static void passdb_dict_lookup_pass(struct passdb_dict_request *dict_request)
        struct passdb_module *_module = auth_request->passdb->passdb;
        struct dict_passdb_module *module =
                (struct dict_passdb_module *)_module;
-       string_t *key;
        const char *password = NULL, *scheme = NULL;
        enum passdb_result passdb_result;
        int ret;
 
-       key = t_str_new(512);
-       str_append(key, DICT_PATH_SHARED);
-       var_expand(key, module->conn->set.password_key,
-                  auth_request_get_var_expand_table(auth_request, NULL));
-
-       if (*module->conn->set.password_key == '\0') {
+       if (array_count(&module->conn->set.passdb_fields) == 0 &&
+           array_count(&module->conn->set.parsed_passdb_objects) == 0) {
                auth_request_log_error(auth_request, "dict",
-                                      "password_key not specified");
+                       "No passdb_objects or passdb_fields specified");
                passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
        } else {
-               passdb_result = passdb_dict_lookup_key(auth_request, module,
-                                                      str_c(key));
+               passdb_result = passdb_dict_lookup_key(auth_request, module);
        }
 
        if (passdb_result == PASSDB_RESULT_OK) {
@@ -170,8 +160,9 @@ passdb_dict_preinit(pool_t pool, const char *args)
        module->conn = conn = db_dict_init(args);
 
        module->module.blocking = TRUE;
-       module->module.cache_key =
-               auth_cache_parse_key(pool, conn->set.password_key);
+       module->module.cache_key = auth_cache_parse_key(pool,
+               db_dict_parse_cache_key(&conn->set.keys, &conn->set.passdb_fields,
+                                       &conn->set.parsed_passdb_objects));
        module->module.default_pass_scheme = conn->set.default_pass_scheme;
        return &module->module;
 }
diff --git a/src/auth/test-db-dict.c b/src/auth/test-db-dict.c
new file mode 100644 (file)
index 0000000..f5ea33f
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "db-dict.h"
+#include "test-common.h"
+
+static void test_db_dict_parse_cache_key(void)
+{
+       struct db_dict_key keys[] = {
+               { "key0", "%d and %n", NULL, NULL, 0 },
+               { "key1", "%{foo}%r%{bar}", NULL, NULL, 0 },
+               { "key2", "%{test1}/path", NULL, NULL, 0 },
+               { "key3", "path2/%{test2}", NULL, NULL, 0 },
+               { "key4", "%{plop}", NULL, NULL, 0 },
+               { "key5", "%{unused}", NULL, NULL, 0 }
+       };
+       struct db_dict_field fields[] = {
+               { "name1", "hello %{dict:key0} %l and %{dict:key1}" },
+               { "name2", "%{dict:key2} also %{extra} plus" }
+       };
+       const struct db_dict_key *objects[] = {
+               &keys[3], &keys[4]
+       };
+       buffer_t keybuf, fieldbuf, objectbuf;
+       ARRAY_TYPE(db_dict_key) keyarr;
+       ARRAY_TYPE(db_dict_field) fieldarr;
+       ARRAY_TYPE(db_dict_key_p) objectarr;
+
+       test_begin("db dict parse cache key");
+
+       buffer_create_from_const_data(&keybuf, keys, sizeof(keys));
+       buffer_create_from_const_data(&fieldbuf, fields, sizeof(fields));
+       buffer_create_from_const_data(&objectbuf, objects, sizeof(objects));
+       array_create_from_buffer(&keyarr, &keybuf, sizeof(keys[0]));
+       array_create_from_buffer(&fieldarr, &fieldbuf, sizeof(fields[0]));
+       array_create_from_buffer(&objectarr, &objectbuf, sizeof(objects[0]));
+
+       test_assert(strcmp(db_dict_parse_cache_key(&keyarr, &fieldarr, &objectarr),
+                          "\t%d and %n\t%l\t%{foo}%r%{bar}\t%{test1}/path\t%{extra}\tpath2/%{test2}\t%{plop}") == 0);
+       test_end();
+}
+
+int main(void)
+{
+       static void (*test_functions[])(void) = {
+               test_db_dict_parse_cache_key,
+               NULL
+       };
+       return test_run(test_functions);
+}
index 169b5ccb3eb3498e5038c1c74c72ad1900c1baac..8ceedf1df7227415e6d5ef6a0c0b9a74d8823814 100644 (file)
@@ -30,22 +30,18 @@ struct dict_userdb_iterate_context {
 
 static int
 dict_query_save_results(struct auth_request *auth_request,
-                       struct dict_connection *conn, const char *result)
+                       struct db_dict_value_iter *iter)
 {
-       struct db_dict_value_iter *iter;
        const char *key, *value, *error;
 
        auth_request_init_userdb_reply(auth_request);
 
-       iter = db_dict_value_iter_init(conn, result);
        while (db_dict_value_iter_next(iter, &key, &value)) {
                if (value != NULL)
                        auth_request_set_userdb_field(auth_request, key, value);
        }
        if (db_dict_value_iter_deinit(&iter, &error) < 0) {
-               auth_request_log_error(auth_request, "dict",
-                       "Value '%s' not in valid %s format: %s",
-                       result, conn->set.value_format, error);
+               auth_request_log_error(auth_request, "dict", "%s", error);
                return -1;
        }
        return 0;
@@ -57,36 +53,29 @@ static void userdb_dict_lookup(struct auth_request *auth_request,
        struct userdb_module *_module = auth_request->userdb->userdb;
        struct dict_userdb_module *module =
                (struct dict_userdb_module *)_module;
-       string_t *key;
+       struct db_dict_value_iter *iter;
        enum userdb_result userdb_result;
-       const char *value;
        int ret;
 
-       if (*module->conn->set.user_key == '\0') {
+       if (array_count(&module->conn->set.userdb_fields) == 0 &&
+           array_count(&module->conn->set.parsed_userdb_objects) == 0) {
                auth_request_log_error(auth_request, "dict",
-                                      "user_key not specified");
+                       "No userdb_objects or userdb_fields specified");
                callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
                return;
        }
 
-       key = t_str_new(512);
-       str_append(key, DICT_PATH_SHARED);
-       var_expand(key, module->conn->set.user_key,
-                  auth_request_get_var_expand_table(auth_request, NULL));
-
-       auth_request_log_debug(auth_request, "dict", "lookup %s", str_c(key));
-       ret = dict_lookup(module->conn->dict, pool_datastack_create(),
-                         str_c(key), &value);
-       if (ret < 0) {
-               auth_request_log_error(auth_request, "dict", "Lookup failed");
+       ret = db_dict_value_iter_init(module->conn, auth_request,
+                                     &module->conn->set.userdb_fields,
+                                     &module->conn->set.parsed_userdb_objects,
+                                     &iter);
+       if (ret < 0)
                userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
-       else if (ret == 0) {
+       else if (ret == 0) {
                auth_request_log_unknown_user(auth_request, "dict");
                userdb_result = USERDB_RESULT_USER_UNKNOWN;
        } else {
-               auth_request_log_debug(auth_request, "dict",
-                                      "result: %s", value);
-               if (dict_query_save_results(auth_request, module->conn, value) < 0)
+               if (dict_query_save_results(auth_request, iter) < 0)
                        userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
                else
                        userdb_result = USERDB_RESULT_OK;
@@ -178,8 +167,9 @@ userdb_dict_preinit(pool_t pool, const char *args)
        module->conn = conn = db_dict_init(args);
 
        module->module.blocking = TRUE;
-       module->module.cache_key =
-               auth_cache_parse_key(pool, conn->set.user_key);
+       module->module.cache_key = auth_cache_parse_key(pool,
+               db_dict_parse_cache_key(&conn->set.keys, &conn->set.userdb_fields,
+                                       &conn->set.parsed_userdb_objects));
        return &module->module;
 }