view src/lib-dict/dict-sql-settings.c @ 21322:5ab8dc1a4a6f

global: Change string position/length from unsigned int to size_t Mainly to avoid truncating >4GB strings, which might potentially cause some security holes. Normally there are other limits, which prevent such excessive strings from being created in the first place. I'm sure this didn't find everything. Maybe everything could be found with compiler warnings. -Wconversion kind of does it, but it gives way too many unnecessary warnings. These were mainly found with: grep " = strlen" egrep "unsigned int.*(size|len)"
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Mon, 12 Dec 2016 07:19:55 +0200
parents f0d35d358f1b
children 59437f8764c6
line wrap: on
line source

/* Copyright (c) 2008-2016 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.sql_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_append(&ctx->cur_map.sql_fields,
			     &fields[i].sql_field, 1);
	}

	/* 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_field_count < count)
		ctx->set->max_field_count = count;
	ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
	return NULL;
}

static const char *dict_sql_map_finish(struct setting_parser_ctx *ctx)
{
	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";
	if (ctx->cur_map.value_type != NULL) {
		if (strcmp(ctx->cur_map.value_type, "string") != 0 &&
		    strcmp(ctx->cur_map.value_type, "hexblob") != 0 &&
		    strcmp(ctx->cur_map.value_type, "uint") != 0)
			return "Invalid value in value_type";
	}

	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.sql_fields)) {
		/* no fields besides value. allocate the array anyway. */
		p_array_init(&ctx->cur_map.sql_fields, ctx->pool, 1);
		if (strchr(ctx->cur_map.pattern, '$') != NULL)
			return "Missing fields for pattern variables";
	}
	array_append(&ctx->set->maps, &ctx->cur_map, 1);
	memset(&ctx->cur_map, 0, sizeof(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 (strncmp(value, "${hexblob:", 10) == 0 &&
		    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 (strncmp(value, "${uint:", 7) == 0 &&
			   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;

	memset(&ctx, 0, sizeof(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);
}