1 /* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "str.h"
6 #include "hash.h"
7 #include "settings.h"
8 #include "dict-sql-settings.h"
9 
10 #include <ctype.h>
11 
12 enum section_type {
13 	SECTION_ROOT = 0,
14 	SECTION_MAP,
15 	SECTION_FIELDS
16 };
17 
18 struct dict_sql_map_field {
19 	struct dict_sql_field sql_field;
20 	const char *variable;
21 };
22 
23 struct setting_parser_ctx {
24 	pool_t pool;
25 	struct dict_sql_settings *set;
26 	enum section_type type;
27 
28 	struct dict_sql_map cur_map;
29 	ARRAY(struct dict_sql_map_field) cur_fields;
30 };
31 
32 #define DEF_STR(name) DEF_STRUCT_STR(name, dict_sql_map)
33 #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_sql_map)
34 
35 static const struct setting_def dict_sql_map_setting_defs[] = {
36 	DEF_STR(pattern),
37 	DEF_STR(table),
38 	DEF_STR(username_field),
39 	DEF_STR(value_field),
40 	DEF_STR(value_type),
41 	DEF_BOOL(value_hexblob),
42 
43 	{ 0, NULL, 0 }
44 };
45 
46 struct dict_sql_settings_cache {
47 	pool_t pool;
48 	const char *path;
49 	struct dict_sql_settings *set;
50 };
51 
52 static HASH_TABLE(const char *, struct dict_sql_settings_cache *) dict_sql_settings_cache;
53 
pattern_read_name(const char ** pattern)54 static const char *pattern_read_name(const char **pattern)
55 {
56 	const char *p = *pattern, *name;
57 
58 	if (*p == '{') {
59 		/* ${name} */
60 		name = ++p;
61 		p = strchr(p, '}');
62 		if (p == NULL) {
63 			/* error, but allow anyway */
64 			*pattern += strlen(*pattern);
65 			return "";
66 		}
67 		*pattern = p + 1;
68 	} else {
69 		/* $name - ends at the first non-alnum_ character */
70 		name = p;
71 		for (; *p != '\0'; p++) {
72 			if (!i_isalnum(*p) && *p != '_')
73 				break;
74 		}
75 		*pattern = p;
76 	}
77 	name = t_strdup_until(name, p);
78 	return name;
79 }
80 
dict_sql_fields_map(struct setting_parser_ctx * ctx)81 static const char *dict_sql_fields_map(struct setting_parser_ctx *ctx)
82 {
83 	struct dict_sql_map_field *fields;
84 	string_t *pattern;
85 	const char *p, *name;
86 	unsigned int i, count;
87 
88 	/* go through the variables in the pattern, replace them with plain
89 	   '$' character and add its sql field */
90 	pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
91 	fields = array_get_modifiable(&ctx->cur_fields, &count);
92 
93 	p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, count);
94 	for (p = ctx->cur_map.pattern; *p != '\0';) {
95 		if (*p != '$') {
96 			str_append_c(pattern, *p);
97 			p++;
98 			continue;
99 		}
100 		p++;
101 		str_append_c(pattern, '$');
102 
103 		name = pattern_read_name(&p);
104 		for (i = 0; i < count; i++) {
105 			if (fields[i].variable != NULL &&
106 			    strcmp(fields[i].variable, name) == 0)
107 				break;
108 		}
109 		if (i == count) {
110 			return t_strconcat("Missing SQL field for variable: ",
111 					   name, NULL);
112 		}
113 
114 		/* mark this field as used */
115 		fields[i].variable = NULL;
116 		array_push_back(&ctx->cur_map.pattern_fields,
117 				&fields[i].sql_field);
118 	}
119 
120 	/* make sure there aren't any unused fields */
121 	for (i = 0; i < count; i++) {
122 		if (fields[i].variable != NULL) {
123 			return t_strconcat("Unused variable: ",
124 					   fields[i].variable, NULL);
125 		}
126 	}
127 
128 	if (ctx->set->max_pattern_fields_count < count)
129 		ctx->set->max_pattern_fields_count = count;
130 	ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
131 	return NULL;
132 }
133 
134 static bool
dict_sql_value_type_parse(const char * value_type,enum dict_sql_type * type_r)135 dict_sql_value_type_parse(const char *value_type, enum dict_sql_type *type_r)
136 {
137 	if (strcmp(value_type, "string") == 0)
138 		*type_r = DICT_SQL_TYPE_STRING;
139 	else if (strcmp(value_type, "hexblob") == 0)
140 		*type_r = DICT_SQL_TYPE_HEXBLOB;
141 	else if (strcmp(value_type, "int") == 0)
142 		*type_r = DICT_SQL_TYPE_INT;
143 	else if (strcmp(value_type, "uint") == 0)
144 		*type_r = DICT_SQL_TYPE_UINT;
145 	else
146 		return FALSE;
147 	return TRUE;
148 }
149 
dict_sql_map_finish(struct setting_parser_ctx * ctx)150 static const char *dict_sql_map_finish(struct setting_parser_ctx *ctx)
151 {
152 	unsigned int i;
153 
154 	if (ctx->cur_map.pattern == NULL)
155 		return "Missing setting: pattern";
156 	if (ctx->cur_map.table == NULL)
157 		return "Missing setting: table";
158 	if (ctx->cur_map.value_field == NULL)
159 		return "Missing setting: value_field";
160 
161 	ctx->cur_map.value_fields = (const char *const *)
162 		p_strsplit_spaces(ctx->pool, ctx->cur_map.value_field, ",");
163 	ctx->cur_map.values_count = str_array_length(ctx->cur_map.value_fields);
164 
165 	enum dict_sql_type *value_types =
166 		p_new(ctx->pool, enum dict_sql_type, ctx->cur_map.values_count);
167 	if (ctx->cur_map.value_type != NULL) {
168 		const char *const *types =
169 			t_strsplit_spaces(ctx->cur_map.value_type, ",");
170 		if (str_array_length(types) != ctx->cur_map.values_count)
171 			return "Number of fields in value_fields doesn't match value_type";
172 		for (i = 0; i < ctx->cur_map.values_count; i++) {
173 			if (!dict_sql_value_type_parse(types[i], &value_types[i]))
174 				return "Invalid value in value_type";
175 		}
176 	} else {
177 		for (i = 0; i < ctx->cur_map.values_count; i++) {
178 			value_types[i] = ctx->cur_map.value_hexblob ?
179 				DICT_SQL_TYPE_HEXBLOB : DICT_SQL_TYPE_STRING;
180 		}
181 	}
182 	ctx->cur_map.value_types = value_types;
183 
184 	if (ctx->cur_map.username_field == NULL) {
185 		/* not all queries require this */
186 		ctx->cur_map.username_field = "'username_field not set'";
187 	}
188 
189 	if (!array_is_created(&ctx->cur_map.pattern_fields)) {
190 		/* no fields besides value. allocate the array anyway. */
191 		p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, 1);
192 		if (strchr(ctx->cur_map.pattern, '$') != NULL)
193 			return "Missing fields for pattern variables";
194 	}
195 	array_push_back(&ctx->set->maps, &ctx->cur_map);
196 	i_zero(&ctx->cur_map);
197 	return NULL;
198 }
199 
200 static const char *
parse_setting(const char * key,const char * value,struct setting_parser_ctx * ctx)201 parse_setting(const char *key, const char *value,
202 	      struct setting_parser_ctx *ctx)
203 {
204 	struct dict_sql_map_field *field;
205 	size_t value_len;
206 
207 	switch (ctx->type) {
208 	case SECTION_ROOT:
209 		if (strcmp(key, "connect") == 0) {
210 			ctx->set->connect = p_strdup(ctx->pool, value);
211 			return NULL;
212 		}
213 		break;
214 	case SECTION_MAP:
215 		return parse_setting_from_defs(ctx->pool,
216 					       dict_sql_map_setting_defs,
217 					       &ctx->cur_map, key, value);
218 	case SECTION_FIELDS:
219 		if (*value != '$') {
220 			return t_strconcat("Value is missing '$' for field: ",
221 					   key, NULL);
222 		}
223 		field = array_append_space(&ctx->cur_fields);
224 		field->sql_field.name = p_strdup(ctx->pool, key);
225 		value_len = strlen(value);
226 		if (str_begins(value, "${hexblob:") &&
227 		    value[value_len-1] == '}') {
228 			field->variable = p_strndup(ctx->pool, value + 10,
229 						    value_len-10-1);
230 			field->sql_field.value_type = DICT_SQL_TYPE_HEXBLOB;
231 		} else if (str_begins(value, "${int:") &&
232 			   value[value_len-1] == '}') {
233 			field->variable = p_strndup(ctx->pool, value + 6,
234 						    value_len-6-1);
235 			field->sql_field.value_type = DICT_SQL_TYPE_INT;
236 		} else if (str_begins(value, "${uint:") &&
237 			   value[value_len-1] == '}') {
238 			field->variable = p_strndup(ctx->pool, value + 7,
239 						    value_len-7-1);
240 			field->sql_field.value_type = DICT_SQL_TYPE_UINT;
241 		} else {
242 			field->variable = p_strdup(ctx->pool, value + 1);
243 		}
244 		return NULL;
245 	}
246 	return t_strconcat("Unknown setting: ", key, NULL);
247 }
248 
249 static bool
parse_section(const char * type,const char * name ATTR_UNUSED,struct setting_parser_ctx * ctx,const char ** error_r)250 parse_section(const char *type, const char *name ATTR_UNUSED,
251 	      struct setting_parser_ctx *ctx, const char **error_r)
252 {
253 	switch (ctx->type) {
254 	case SECTION_ROOT:
255 		if (type == NULL)
256 			return FALSE;
257 		if (strcmp(type, "map") == 0) {
258 			array_clear(&ctx->cur_fields);
259 			ctx->type = SECTION_MAP;
260 			return TRUE;
261 		}
262 		break;
263 	case SECTION_MAP:
264 		if (type == NULL) {
265 			ctx->type = SECTION_ROOT;
266 			*error_r = dict_sql_map_finish(ctx);
267 			return FALSE;
268 		}
269 		if (strcmp(type, "fields") == 0) {
270 			ctx->type = SECTION_FIELDS;
271 			return TRUE;
272 		}
273 		break;
274 	case SECTION_FIELDS:
275 		if (type == NULL) {
276 			ctx->type = SECTION_MAP;
277 			*error_r = dict_sql_fields_map(ctx);
278 			return FALSE;
279 		}
280 		break;
281 	}
282 	*error_r = t_strconcat("Unknown section: ", type, NULL);
283 	return FALSE;
284 }
285 
286 struct dict_sql_settings *
dict_sql_settings_read(const char * path,const char ** error_r)287 dict_sql_settings_read(const char *path, const char **error_r)
288 {
289 	struct setting_parser_ctx ctx;
290 	struct dict_sql_settings_cache *cache;
291 	pool_t pool;
292 
293 	if (!hash_table_is_created(dict_sql_settings_cache)) {
294 		hash_table_create(&dict_sql_settings_cache, default_pool, 0,
295 				  str_hash, strcmp);
296 	}
297 
298 	cache = hash_table_lookup(dict_sql_settings_cache, path);
299 	if (cache != NULL)
300 		return cache->set;
301 
302 	i_zero(&ctx);
303 	pool = pool_alloconly_create("dict sql settings", 1024);
304 	ctx.pool = pool;
305 	ctx.set = p_new(pool, struct dict_sql_settings, 1);
306 	t_array_init(&ctx.cur_fields, 16);
307 	p_array_init(&ctx.set->maps, pool, 8);
308 
309 	if (!settings_read(path, NULL, parse_setting, parse_section,
310 			   &ctx, error_r)) {
311 		pool_unref(&pool);
312 		return NULL;
313 	}
314 
315 	if (ctx.set->connect == NULL) {
316 		*error_r = t_strdup_printf("Error in configuration file %s: "
317 					   "Missing connect setting", path);
318 		pool_unref(&pool);
319 		return NULL;
320 	}
321 
322 	cache = p_new(pool, struct dict_sql_settings_cache, 1);
323 	cache->pool = pool;
324 	cache->path = p_strdup(pool, path);
325 	cache->set = ctx.set;
326 
327 	hash_table_insert(dict_sql_settings_cache, cache->path, cache);
328 	return ctx.set;
329 }
330 
dict_sql_settings_deinit(void)331 void dict_sql_settings_deinit(void)
332 {
333 	struct hash_iterate_context *iter;
334 	struct dict_sql_settings_cache *cache;
335 	const char *key;
336 
337 	if (!hash_table_is_created(dict_sql_settings_cache))
338 		return;
339 
340 	iter = hash_table_iterate_init(dict_sql_settings_cache);
341 	while (hash_table_iterate(iter, dict_sql_settings_cache, &key, &cache))
342 		pool_unref(&cache->pool);
343 	hash_table_iterate_deinit(&iter);
344 	hash_table_destroy(&dict_sql_settings_cache);
345 }
346