summaryrefslogtreecommitdiffstats
path: root/src/lib-dict-backend/dict-sql-settings.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-dict-backend/dict-sql-settings.c345
1 files changed, 345 insertions, 0 deletions
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 0000000..0d44923
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql-settings.c
@@ -0,0 +1,345 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hash.h"
+#include "settings.h"
+#include "dict-sql-settings.h"
+
+#include <ctype.h>
+
+enum section_type {
+ SECTION_ROOT = 0,
+ SECTION_MAP,
+ SECTION_FIELDS
+};
+
+struct dict_sql_map_field {
+ struct dict_sql_field sql_field;
+ const char *variable;
+};
+
+struct setting_parser_ctx {
+ pool_t pool;
+ struct dict_sql_settings *set;
+ enum section_type type;
+
+ struct dict_sql_map cur_map;
+ ARRAY(struct dict_sql_map_field) cur_fields;
+};
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, dict_sql_map)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_sql_map)
+
+static const struct setting_def dict_sql_map_setting_defs[] = {
+ DEF_STR(pattern),
+ DEF_STR(table),
+ DEF_STR(username_field),
+ DEF_STR(value_field),
+ DEF_STR(value_type),
+ DEF_BOOL(value_hexblob),
+
+ { 0, NULL, 0 }
+};
+
+struct dict_sql_settings_cache {
+ pool_t pool;
+ const char *path;
+ struct dict_sql_settings *set;
+};
+
+static HASH_TABLE(const char *, struct dict_sql_settings_cache *) dict_sql_settings_cache;
+
+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_sql_fields_map(struct setting_parser_ctx *ctx)
+{
+ struct dict_sql_map_field *fields;
+ 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 sql field */
+ pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
+ fields = array_get_modifiable(&ctx->cur_fields, &count);
+
+ p_array_init(&ctx->cur_map.pattern_fields, 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 (fields[i].variable != NULL &&
+ strcmp(fields[i].variable, name) == 0)
+ break;
+ }
+ if (i == count) {
+ return t_strconcat("Missing SQL field for variable: ",
+ name, NULL);
+ }
+
+ /* mark this field as used */
+ fields[i].variable = NULL;
+ array_push_back(&ctx->cur_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) {
+ return t_strconcat("Unused variable: ",
+ fields[i].variable, NULL);
+ }
+ }
+
+ if (ctx->set->max_pattern_fields_count < count)
+ ctx->set->max_pattern_fields_count = count;
+ ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
+ return NULL;
+}
+
+static bool
+dict_sql_value_type_parse(const char *value_type, enum dict_sql_type *type_r)
+{
+ if (strcmp(value_type, "string") == 0)
+ *type_r = DICT_SQL_TYPE_STRING;
+ else if (strcmp(value_type, "hexblob") == 0)
+ *type_r = DICT_SQL_TYPE_HEXBLOB;
+ else if (strcmp(value_type, "int") == 0)
+ *type_r = DICT_SQL_TYPE_INT;
+ else if (strcmp(value_type, "uint") == 0)
+ *type_r = DICT_SQL_TYPE_UINT;
+ else
+ return FALSE;
+ return TRUE;
+}
+
+static const char *dict_sql_map_finish(struct setting_parser_ctx *ctx)
+{
+ unsigned int i;
+
+ if (ctx->cur_map.pattern == NULL)
+ return "Missing setting: pattern";
+ if (ctx->cur_map.table == NULL)
+ return "Missing setting: table";
+ if (ctx->cur_map.value_field == NULL)
+ return "Missing setting: value_field";
+
+ ctx->cur_map.value_fields = (const char *const *)
+ p_strsplit_spaces(ctx->pool, ctx->cur_map.value_field, ",");
+ ctx->cur_map.values_count = str_array_length(ctx->cur_map.value_fields);
+
+ enum dict_sql_type *value_types =
+ p_new(ctx->pool, enum dict_sql_type, ctx->cur_map.values_count);
+ if (ctx->cur_map.value_type != NULL) {
+ const char *const *types =
+ t_strsplit_spaces(ctx->cur_map.value_type, ",");
+ if (str_array_length(types) != ctx->cur_map.values_count)
+ return "Number of fields in value_fields doesn't match value_type";
+ for (i = 0; i < ctx->cur_map.values_count; i++) {
+ if (!dict_sql_value_type_parse(types[i], &value_types[i]))
+ return "Invalid value in value_type";
+ }
+ } else {
+ for (i = 0; i < ctx->cur_map.values_count; i++) {
+ value_types[i] = ctx->cur_map.value_hexblob ?
+ DICT_SQL_TYPE_HEXBLOB : DICT_SQL_TYPE_STRING;
+ }
+ }
+ ctx->cur_map.value_types = value_types;
+
+ if (ctx->cur_map.username_field == NULL) {
+ /* not all queries require this */
+ ctx->cur_map.username_field = "'username_field not set'";
+ }
+
+ if (!array_is_created(&ctx->cur_map.pattern_fields)) {
+ /* no fields besides value. allocate the array anyway. */
+ p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, 1);
+ if (strchr(ctx->cur_map.pattern, '$') != NULL)
+ return "Missing fields for pattern variables";
+ }
+ array_push_back(&ctx->set->maps, &ctx->cur_map);
+ 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_sql_map_field *field;
+ size_t value_len;
+
+ switch (ctx->type) {
+ case SECTION_ROOT:
+ if (strcmp(key, "connect") == 0) {
+ ctx->set->connect = p_strdup(ctx->pool, value);
+ return NULL;
+ }
+ break;
+ case SECTION_MAP:
+ return parse_setting_from_defs(ctx->pool,
+ dict_sql_map_setting_defs,
+ &ctx->cur_map, key, value);
+ case SECTION_FIELDS:
+ if (*value != '$') {
+ return t_strconcat("Value is missing '$' for field: ",
+ key, NULL);
+ }
+ field = array_append_space(&ctx->cur_fields);
+ field->sql_field.name = p_strdup(ctx->pool, key);
+ value_len = strlen(value);
+ if (str_begins(value, "${hexblob:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 10,
+ value_len-10-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_HEXBLOB;
+ } else if (str_begins(value, "${int:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 6,
+ value_len-6-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_INT;
+ } else if (str_begins(value, "${uint:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 7,
+ value_len-7-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_UINT;
+ } else {
+ field->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_fields);
+ ctx->type = SECTION_MAP;
+ return TRUE;
+ }
+ break;
+ case SECTION_MAP:
+ if (type == NULL) {
+ ctx->type = SECTION_ROOT;
+ *error_r = dict_sql_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_sql_fields_map(ctx);
+ return FALSE;
+ }
+ break;
+ }
+ *error_r = t_strconcat("Unknown section: ", type, NULL);
+ return FALSE;
+}
+
+struct dict_sql_settings *
+dict_sql_settings_read(const char *path, const char **error_r)
+{
+ struct setting_parser_ctx ctx;
+ struct dict_sql_settings_cache *cache;
+ pool_t pool;
+
+ if (!hash_table_is_created(dict_sql_settings_cache)) {
+ hash_table_create(&dict_sql_settings_cache, default_pool, 0,
+ str_hash, strcmp);
+ }
+
+ cache = hash_table_lookup(dict_sql_settings_cache, path);
+ if (cache != NULL)
+ return cache->set;
+
+ i_zero(&ctx);
+ pool = pool_alloconly_create("dict sql settings", 1024);
+ ctx.pool = pool;
+ ctx.set = p_new(pool, struct dict_sql_settings, 1);
+ t_array_init(&ctx.cur_fields, 16);
+ p_array_init(&ctx.set->maps, pool, 8);
+
+ if (!settings_read(path, NULL, parse_setting, parse_section,
+ &ctx, error_r)) {
+ pool_unref(&pool);
+ return NULL;
+ }
+
+ if (ctx.set->connect == NULL) {
+ *error_r = t_strdup_printf("Error in configuration file %s: "
+ "Missing connect setting", path);
+ pool_unref(&pool);
+ return NULL;
+ }
+
+ cache = p_new(pool, struct dict_sql_settings_cache, 1);
+ cache->pool = pool;
+ cache->path = p_strdup(pool, path);
+ cache->set = ctx.set;
+
+ hash_table_insert(dict_sql_settings_cache, cache->path, cache);
+ return ctx.set;
+}
+
+void dict_sql_settings_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ struct dict_sql_settings_cache *cache;
+ const char *key;
+
+ if (!hash_table_is_created(dict_sql_settings_cache))
+ return;
+
+ iter = hash_table_iterate_init(dict_sql_settings_cache);
+ while (hash_table_iterate(iter, dict_sql_settings_cache, &key, &cache))
+ pool_unref(&cache->pool);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&dict_sql_settings_cache);
+}