From: Timo Sirainen Date: Wed, 13 Mar 2024 21:35:25 +0000 (+0200) Subject: dict-sql: Implement new init() API X-Git-Tag: 2.4.1~832 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=963e9657588198328fcbbf3707c5958f1de14875;p=thirdparty%2Fdovecot%2Fcore.git dict-sql: Implement new init() API --- diff --git a/src/lib-dict-backend/Makefile.am b/src/lib-dict-backend/Makefile.am index 770f597afc..42c7ef7cc8 100644 --- a/src/lib-dict-backend/Makefile.am +++ b/src/lib-dict-backend/Makefile.am @@ -22,6 +22,7 @@ libdict_backend_la_SOURCES = \ dict-cdb.c \ dict-sql.c \ dict-sql-legacy-settings.c \ + dict-sql-settings.c \ $(ldap_sources) libdict_backend_la_LIBADD = diff --git a/src/lib-dict-backend/dict-sql-legacy-settings.c b/src/lib-dict-backend/dict-sql-legacy-settings.c index 1b363f0530..bed784ddaa 100644 --- a/src/lib-dict-backend/dict-sql-legacy-settings.c +++ b/src/lib-dict-backend/dict-sql-legacy-settings.c @@ -51,16 +51,6 @@ struct dict_sql_legacy_settings_cache { static HASH_TABLE(const char *, struct dict_sql_legacy_settings_cache *) dict_sql_legacy_settings_cache; -static const char *dict_sql_type_names[] = { - "string", - "int", - "uint", - "double", - "hexblob", - "uuid", -}; -static_assert_array_size(dict_sql_type_names, DICT_SQL_TYPE_COUNT); - static const char *pattern_read_name(const char **pattern) { const char *p = *pattern, *name; diff --git a/src/lib-dict-backend/dict-sql-settings.c b/src/lib-dict-backend/dict-sql-settings.c new file mode 100644 index 0000000000..69299a56fb --- /dev/null +++ b/src/lib-dict-backend/dict-sql-settings.c @@ -0,0 +1,330 @@ +/* Copyright (c) 2024 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "settings.h" +#include "settings-parser.h" +#include "dict-sql-settings.h" + +#include + +/* */ +#define DICT_MAP_FIELD_TYPES_ENUM \ + "string:int:uint:double:hexblob:uuid" +/* */ + +struct dict_sql_map_key_field { + struct dict_sql_field sql_field; + const char *variable; +}; + +const char *dict_sql_type_names[] = { + "string", + "int", + "uint", + "double", + "hexblob", + "uuid", +}; +static_assert_array_size(dict_sql_type_names, DICT_SQL_TYPE_COUNT); + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("dict_map_key_field_"#name, name, struct dict_map_key_field_settings) +static const struct setting_define dict_map_key_field_setting_defines[] = { + DEF(STR, name), + DEF(STR, value), + DEF(ENUM, type), + + SETTING_DEFINE_LIST_END +}; +static const struct dict_map_key_field_settings dict_map_key_field_default_settings = { + .name = "", + .type = DICT_MAP_FIELD_TYPES_ENUM, + .value = "", +}; +const struct setting_parser_info dict_map_key_field_setting_parser_info = { + .name = "dict_map_key_field", + + .defines = dict_map_key_field_setting_defines, + .defaults = &dict_map_key_field_default_settings, + + .struct_size = sizeof(struct dict_map_key_field_settings), + .pool_offset1 = 1 + offsetof(struct dict_map_key_field_settings, pool), +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("dict_map_value_field_"#name, name, struct dict_map_value_field_settings) +static const struct setting_define dict_map_value_field_setting_defines[] = { + DEF(STR, name), + DEF(ENUM, type), + + SETTING_DEFINE_LIST_END +}; +static const struct dict_map_value_field_settings dict_map_value_field_default_settings = { + .name = "", + .type = DICT_MAP_FIELD_TYPES_ENUM, +}; +const struct setting_parser_info dict_map_value_field_setting_parser_info = { + .name = "dict_map_value_field", + + .defines = dict_map_value_field_setting_defines, + .defaults = &dict_map_value_field_default_settings, + + .struct_size = sizeof(struct dict_map_value_field_settings), + .pool_offset1 = 1 + offsetof(struct dict_map_value_field_settings, pool), +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("dict_map_"#name, name, struct dict_map_settings) +static const struct setting_define dict_map_setting_defines[] = { + DEF(STR, pattern), + DEF(STR, sql_table), + DEF(STR, username_field), + DEF(STR, expire_field), + { .type = SET_FILTER_ARRAY, .key = "dict_map_key_field", + .offset = offsetof(struct dict_map_settings, fields), + .filter_array_field_name = "dict_map_key_field_name", }, + { .type = SET_FILTER_ARRAY, .key = "dict_map_value_field", + .offset = offsetof(struct dict_map_settings, values), + .filter_array_field_name = "dict_map_value_field_name", }, + + { .type = SET_FILTER_ARRAY, .key = "dict_map", + .offset = offsetof(struct dict_map_settings, maps), + .filter_array_field_name = "dict_map_pattern", }, + + SETTING_DEFINE_LIST_END +}; +static const struct dict_map_settings dict_map_default_settings = { + .pattern = "", + .sql_table = "", + .username_field = "", + .expire_field = "", +}; +const struct setting_parser_info dict_map_setting_parser_info = { + .name = "dict_map", + + .defines = dict_map_setting_defines, + .defaults = &dict_map_default_settings, + + .struct_size = sizeof(struct dict_map_settings), + .pool_offset1 = 1 + offsetof(struct dict_map_settings, pool), +}; + +static enum dict_sql_type dict_sql_type_parse(const char *value_type) +{ + for (enum dict_sql_type type = DICT_SQL_TYPE_STRING; + type < DICT_SQL_TYPE_COUNT; type++) { + if (strcmp(value_type, dict_sql_type_names[type]) == 0) + return type; + } + /* settings parsing should have failed before getting here */ + i_panic("BUG: Unknown dict_sql_type '%s'", value_type); +} + +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_strconcat("$", t_strdup_until(name, p), NULL); + return name; +} + +static int dict_sql_fields_map(struct event *event, + struct dict_sql_map_settings *set, + const struct dict_map_settings *map_set, + struct dict_sql_map *map, + const char **error_r) +{ + struct dict_sql_map_key_field *fields; + string_t *pattern; + const char *p, *name, *const *field_names; + unsigned int i, count; + + /* go through the variables in the pattern, replace them with plain + '$' character and add its sql field */ + pattern = t_str_new(strlen(map->pattern) + 1); + + if (!array_is_empty(&map_set->fields)) + field_names = array_get(&map_set->fields, &count); + else { + field_names = NULL; + count = 0; + } + fields = count == 0 ? NULL : + t_new(struct dict_sql_map_key_field, count); + for (i = 0; i < count; i++) { + const struct dict_map_key_field_settings *field_set; + if (settings_get_filter(event, "dict_map_key_field", + field_names[i], + &dict_map_key_field_setting_parser_info, + 0, &field_set, error_r) < 0) + return -1; + pool_add_external_ref(set->pool, field_set->pool); + fields[i].sql_field.name = field_set->name; + fields[i].sql_field.value_type = + dict_sql_type_parse(field_set->type); + fields[i].variable = field_set->value; + settings_free(field_set); + } + + p_array_init(&map->pattern_fields, set->pool, count); + for (p = 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 (fields[i].variable != NULL && + strcmp(fields[i].variable, name) == 0) + break; + } + if (i == count) { + *error_r = t_strconcat("Missing SQL field for variable: ", + name, NULL); + return -1; + } + + /* mark this field as used */ + fields[i].variable = NULL; + array_push_back(&map->pattern_fields, + &fields[i].sql_field); + } + + /* make sure there aren't any unused fields */ + for (i = 0; i < count; i++) { + if (fields[i].variable != NULL) { + *error_r = t_strconcat("Unused variable: ", + fields[i].variable, NULL); + return -1; + } + } + + if (set->max_pattern_fields_count < count) + set->max_pattern_fields_count = count; + map->pattern = p_strdup(set->pool, str_c(pattern)); + return 0; +} + +static int +dict_sql_map_settings_get(struct event *event, + struct dict_sql_map_settings *set, + const char **error_r) +{ + const struct dict_map_settings *map_set; + const struct dict_map_value_field_settings *value_set; + const char *name; + + if (settings_get(event, &dict_map_setting_parser_info, 0, + &map_set, error_r) < 0) + return -1; + pool_add_external_ref(set->pool, map_set->pool); + pool_t pool_copy = map_set->pool; + pool_unref(&pool_copy); + + if (array_is_empty(&map_set->values)) { + *error_r = "dict_map_value_field { .. } named list filter is missing"; + return -1; + } + + struct dict_sql_map *map = array_append_space(&set->maps); + map->pattern = map_set->pattern; + map->table = map_set->sql_table; + map->username_field = map_set->username_field; + map->expire_field = map_set->expire_field[0] != '\0' ? + map_set->expire_field : NULL; + map->values_count = array_count(&map_set->values); + if (dict_sql_fields_map(event, set, map_set, map, error_r) < 0) + return -1; + + string_t *value_field = t_str_new(32); + ARRAY(enum dict_sql_type) value_types; + p_array_init(&value_types, map_set->pool, map->values_count); + + array_foreach_elem(&map_set->values, name) { + if (settings_get_filter(event, "dict_map_value_field", name, + &dict_map_value_field_setting_parser_info, + 0, &value_set, error_r) < 0) + return -1; + + if (str_len(value_field) > 0) + str_append_c(value_field, ','); + str_append(value_field, value_set->name); + enum dict_sql_type field_type = + dict_sql_type_parse(value_set->type); + array_push_back(&value_types, &field_type); + settings_free(value_set); + } + map->value_field = p_strdup(set->pool, str_c(value_field)); + map->value_types = array_front(&value_types); + return 0; +} + +int dict_sql_settings_get(struct event *event, + struct dict_sql_map_settings **set_r, + const char **error_r) +{ + const struct dict_map_settings *maps_set; + struct dict_sql_map_settings *set; + const char *name, *error; + int ret = 0; + + pool_t pool = pool_alloconly_create("dict sql map settings", 128); + set = p_new(pool, struct dict_sql_map_settings, 1); + set->pool = pool; + p_array_init(&set->maps, pool, 8); + + if (settings_get(event, &dict_map_setting_parser_info, 0, + &maps_set, error_r) < 0) { + pool_unref(&pool); + return -1; + } + if (array_is_created(&maps_set->maps)) { + array_foreach_elem(&maps_set->maps, name) { + struct event *map_event = event_create(event); + event_add_str(map_event, "dict_map", name); + if (dict_sql_map_settings_get(map_event, set, &error) < 0) { + *error_r = t_strdup_printf( + "Failed to get dict_map %s: %s", name, error); + ret = -1; + } + event_unref(&map_event); + if (ret < 0) + break; + + } + } + settings_free(maps_set); + if (ret < 0) + pool_unref(&pool); + else + *set_r = set; + return ret; +} diff --git a/src/lib-dict-backend/dict-sql-settings.h b/src/lib-dict-backend/dict-sql-settings.h index 838071eb9d..60191320bb 100644 --- a/src/lib-dict-backend/dict-sql-settings.h +++ b/src/lib-dict-backend/dict-sql-settings.h @@ -34,6 +34,34 @@ struct dict_sql_map { const enum dict_sql_type *value_types; }; +struct dict_map_key_field_settings { + pool_t pool; + + const char *name; + const char *type; + const char *value; +}; + +struct dict_map_value_field_settings { + pool_t pool; + + const char *name; + const char *type; +}; + +struct dict_map_settings { + pool_t pool; + + const char *pattern; + const char *sql_table; + const char *username_field; + const char *expire_field; + ARRAY_TYPE(const_string) fields; + ARRAY_TYPE(const_string) values; + + ARRAY_TYPE(const_string) maps; +}; + struct dict_sql_map_settings { pool_t pool; unsigned int max_pattern_fields_count; @@ -45,6 +73,15 @@ struct dict_sql_legacy_settings { struct dict_sql_map_settings map_set; }; +extern const char *dict_sql_type_names[]; +extern const struct setting_parser_info dict_map_key_field_setting_parser_info; +extern const struct setting_parser_info dict_map_value_field_setting_parser_info; +extern const struct setting_parser_info dict_map_setting_parser_info; + +int dict_sql_settings_get(struct event *event, + struct dict_sql_map_settings **set_r, + const char **error_r); + struct dict_sql_legacy_settings * dict_sql_legacy_settings_read(const char *path, const char **error_r); diff --git a/src/lib-dict-backend/dict-sql.c b/src/lib-dict-backend/dict-sql.c index b2ccbb2dcb..7408397e9c 100644 --- a/src/lib-dict-backend/dict-sql.c +++ b/src/lib-dict-backend/dict-sql.c @@ -95,6 +95,30 @@ static void sql_dict_prev_set_flush(struct sql_dict_transaction_context *ctx); static void sql_dict_prev_inc_free(struct sql_dict_transaction_context *ctx); static void sql_dict_prev_set_free(struct sql_dict_transaction_context *ctx); +static int +sql_dict_init(const struct dict *dict_driver, struct event *event, + struct dict **dict_r, const char **error_r) +{ + struct dict_sql_map_settings *map_set; + + if (dict_sql_settings_get(event, &map_set, error_r) < 0) + return -1; + + pool_t pool = pool_alloconly_create("sql dict", 2048); + struct sql_dict *dict = p_new(pool, struct sql_dict, 1); + dict->pool = pool; + dict->dict = *dict_driver; + dict->set = map_set; + + if (sql_init_auto(event, &dict->db, error_r) <= 0) { + pool_unref(&map_set->pool); + pool_unref(&pool); + return -1; + } + *dict_r = &dict->dict; + return 0; +} + static int sql_dict_init_legacy(struct dict *driver, const char *uri, const struct dict_legacy_settings *set, @@ -1657,6 +1681,7 @@ static struct dict sql_dict = { .name = "sql", .flags = DICT_DRIVER_FLAG_SUPPORT_EXPIRE_SECS, .v = { + .init = sql_dict_init, .init_legacy = sql_dict_init_legacy, .deinit = sql_dict_deinit, .wait = sql_dict_wait, @@ -1684,7 +1709,9 @@ void dict_sql_register(void) dict_sql_db_cache = sql_db_cache_init_legacy(DICT_SQL_MAX_UNUSED_CONNECTIONS); - /* @UNSAFE */ + dict_driver_register(&sql_dict); + + /* @UNSAFE - FIXME: remove these when dict_legacy is dropped */ drivers = array_get(&sql_drivers, &count); dict_sql_drivers = i_new(struct dict, count + 1); @@ -1700,6 +1727,7 @@ void dict_sql_unregister(void) { int i; + dict_driver_unregister(&sql_dict); for (i = 0; dict_sql_drivers[i].name != NULL; i++) dict_driver_unregister(&dict_sql_drivers[i]); i_free(dict_sql_drivers);