Coverage Report

Created: 2026-03-15 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-settings/settings-parser.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "crc32.h"
6
#include "str.h"
7
#include "str-parse.h"
8
#include "read-full.h"
9
#include "var-expand.h"
10
#include "unichar.h"
11
#include "settings-parser.h"
12
13
#include <sys/stat.h>
14
#include <fcntl.h>
15
#include <unistd.h>
16
17
struct boollist_removal {
18
  ARRAY_TYPE(const_string) *array;
19
  const char *key_suffix;
20
};
21
22
struct setting_parser_context {
23
  pool_t set_pool, parser_pool;
24
  int refcount;
25
        enum settings_parser_flags flags;
26
27
  const struct setting_parser_info *info;
28
29
  /* Pointer to structure containing the values */
30
  void *set_struct;
31
  ARRAY(struct boollist_removal) boollist_removals;
32
33
  char *error;
34
};
35
36
const char *set_value_unknown = "UNKNOWN_VALUE_WITH_VARIABLES";
37
38
#ifdef DEBUG_FAST
39
static const char *boollist_eol_sentry = "boollist-eol";
40
#endif
41
static const char *set_array_stop = "array-stop";
42
43
static void
44
setting_parser_copy_defaults(struct setting_parser_context *ctx,
45
           const struct setting_parser_info *info)
46
0
{
47
0
  const struct setting_define *def;
48
0
  const char *p, **strp;
49
50
0
  if (info->defaults == NULL)
51
0
    return;
52
53
0
  memcpy(ctx->set_struct, info->defaults, info->struct_size);
54
0
  for (def = info->defines; def->key != NULL; def++) {
55
0
    switch (def->type) {
56
0
    case SET_ENUM: {
57
      /* fix enums by dropping everything after the
58
         first ':' */
59
0
      strp = PTR_OFFSET(ctx->set_struct, def->offset);
60
0
      p = strchr(*strp, ':');
61
0
      if (p != NULL)
62
0
        *strp = p_strdup_until(ctx->set_pool, *strp, p);
63
0
      break;
64
0
    }
65
0
    default:
66
0
      break;
67
0
    }
68
0
  }
69
0
}
70
71
struct setting_parser_context *
72
settings_parser_init(pool_t set_pool, const struct setting_parser_info *root,
73
         enum settings_parser_flags flags)
74
0
{
75
0
  struct setting_parser_context *ctx;
76
0
  pool_t parser_pool;
77
78
0
  parser_pool = pool_alloconly_create(MEMPOOL_GROWING"settings parser",
79
0
              1024);
80
0
  ctx = p_new(parser_pool, struct setting_parser_context, 1);
81
0
  ctx->refcount = 1;
82
0
  ctx->set_pool = set_pool;
83
0
  ctx->parser_pool = parser_pool;
84
0
  ctx->flags = flags;
85
86
0
  ctx->info = root;
87
0
  if (root->struct_size > 0) {
88
0
    ctx->set_struct =
89
0
      p_malloc(ctx->set_pool, root->struct_size);
90
0
    setting_parser_copy_defaults(ctx, root);
91
0
  }
92
93
0
  pool_ref(ctx->set_pool);
94
0
  return ctx;
95
0
}
96
97
void settings_parser_ref(struct setting_parser_context *ctx)
98
0
{
99
0
  i_assert(ctx->refcount > 0);
100
0
  ctx->refcount++;
101
0
}
102
103
void settings_parser_unref(struct setting_parser_context **_ctx)
104
0
{
105
0
  struct setting_parser_context *ctx = *_ctx;
106
107
0
  if (ctx == NULL)
108
0
    return;
109
0
  *_ctx = NULL;
110
111
0
  i_assert(ctx->refcount > 0);
112
0
  if (--ctx->refcount > 0)
113
0
    return;
114
0
  i_free(ctx->error);
115
0
  pool_unref(&ctx->set_pool);
116
0
  pool_unref(&ctx->parser_pool);
117
0
}
118
119
unsigned int
120
setting_parser_info_get_define_count(const struct setting_parser_info *info)
121
0
{
122
0
  unsigned int count = 0;
123
0
  while (info->defines[count].key != NULL)
124
0
    count++;
125
0
  return count;
126
0
}
127
128
bool setting_parser_info_find_key(const struct setting_parser_info *info,
129
          const char *key, unsigned int *idx_r)
130
0
{
131
0
  const char *suffix;
132
133
0
  for (unsigned int i = 0; info->defines[i].key != NULL; i++) {
134
0
    if (!str_begins(key, info->defines[i].key, &suffix))
135
0
      ; /* mismatch */
136
0
    else if (suffix[0] == '\0') {
137
      /* full setting */
138
0
      while (i > 0 && info->defines[i].type == SET_ALIAS)
139
0
        i--;
140
0
      *idx_r = i;
141
0
      return TRUE;
142
0
    } else if (suffix[0] == '/' &&
143
0
         (info->defines[i].type == SET_STRLIST ||
144
0
          info->defines[i].type == SET_BOOLLIST)) {
145
      /* strlist key */
146
0
      *idx_r = i;
147
0
      return TRUE;
148
0
    }
149
0
  }
150
0
  return FALSE;
151
0
}
152
153
void *settings_parser_get_set(const struct setting_parser_context *ctx)
154
0
{
155
0
  return ctx->set_struct;
156
0
}
157
158
static void settings_parser_set_error(struct setting_parser_context *ctx,
159
              const char *error)
160
0
{
161
0
  i_free(ctx->error);
162
0
  ctx->error = i_strdup(error);
163
0
}
164
165
const char *settings_parser_get_error(struct setting_parser_context *ctx)
166
0
{
167
0
  return ctx->error;
168
0
}
169
170
static const struct setting_define *
171
setting_define_find(const struct setting_parser_info *info, const char *key)
172
0
{
173
0
  const struct setting_define *list;
174
175
0
  for (list = info->defines; list->key != NULL; list++) {
176
0
    if (strcmp(list->key, key) == 0)
177
0
      return list;
178
0
  }
179
0
  return NULL;
180
0
}
181
182
static int
183
get_bool(struct setting_parser_context *ctx, const char *value, bool *result_r)
184
0
{
185
0
  const char *error;
186
0
  int ret;
187
0
  if ((ret = str_parse_get_bool(value, result_r, &error)) < 0)
188
0
    settings_parser_set_error(ctx, error);
189
0
  return ret;
190
0
}
191
192
static int
193
get_uintmax(struct setting_parser_context *ctx, const char *value,
194
      uintmax_t *result_r)
195
0
{
196
0
  if (str_to_uintmax(value, result_r) < 0) {
197
0
    settings_parser_set_error(ctx, t_strdup_printf(
198
0
      "Invalid number %s: %s", value,
199
0
      str_num_error(value)));
200
0
    return -1;
201
0
  }
202
0
  return 0;
203
0
}
204
205
static int
206
get_uint(struct setting_parser_context *ctx, const char *value,
207
   unsigned int *result_r)
208
0
{
209
0
  if (settings_value_is_unlimited(value)) {
210
0
    *result_r = SET_UINT_UNLIMITED;
211
0
    return 0;
212
0
  }
213
0
  if (str_to_uint(value, result_r) < 0) {
214
0
    settings_parser_set_error(ctx, t_strdup_printf(
215
0
      "Invalid number %s: %s", value,
216
0
      str_num_error(value)));
217
0
    return -1;
218
0
  }
219
0
  return 0;
220
0
}
221
222
static int
223
get_octal(struct setting_parser_context *ctx, const char *value,
224
    unsigned int *result_r)
225
0
{
226
0
  unsigned long long octal;
227
228
0
  if (*value != '0')
229
0
    return get_uint(ctx, value, result_r);
230
231
0
  if (str_to_ullong_oct(value, &octal) < 0) {
232
0
    settings_parser_set_error(ctx,
233
0
      t_strconcat("Invalid number: ", value, NULL));
234
0
    return -1;
235
0
  }
236
0
  *result_r = (unsigned int)octal;
237
0
  return 0;
238
0
}
239
240
static int get_enum(struct setting_parser_context *ctx, const char *value,
241
        char **result_r, const char *allowed_values)
242
0
{
243
0
  const char *p;
244
245
0
  while (allowed_values != NULL) {
246
0
    p = strchr(allowed_values, ':');
247
0
    if (p == NULL) {
248
0
      if (strcmp(allowed_values, value) == 0)
249
0
        break;
250
251
0
      settings_parser_set_error(ctx,
252
0
        t_strconcat("Invalid value: ", value, NULL));
253
0
      return -1;
254
0
    }
255
256
0
    if (strncmp(allowed_values, value, p - allowed_values) == 0 &&
257
0
        value[p - allowed_values] == '\0')
258
0
      break;
259
260
0
    allowed_values = p + 1;
261
0
  }
262
263
0
  *result_r = p_strdup(ctx->set_pool, value);
264
0
  return 0;
265
0
}
266
267
static int
268
get_file(struct setting_parser_context *ctx, bool dup_value, const char **value)
269
0
{
270
0
  if (**value == '\0')
271
0
    return 0;
272
0
  const char *content = strchr(*value, '\n');
273
0
  if (content != NULL) {
274
0
    if (dup_value)
275
0
      *value = p_strdup(ctx->set_pool, *value);
276
0
    return 0;
277
0
  }
278
279
0
  const char *error;
280
0
  if (settings_parse_read_file(*value, *value, ctx->set_pool, NULL,
281
0
             "", value, &error) < 0) {
282
0
    settings_parser_set_error(ctx, error);
283
0
    return -1;
284
0
  }
285
0
  return 0;
286
0
}
287
288
static int
289
get_in_port_zero(struct setting_parser_context *ctx, const char *value,
290
   in_port_t *result_r)
291
0
{
292
0
  if (net_str2port_zero(value, result_r) < 0) {
293
0
    settings_parser_set_error(ctx, t_strdup_printf(
294
0
      "Invalid port number %s", value));
295
0
    return -1;
296
0
  }
297
0
  return 0;
298
0
}
299
300
int settings_parse_read_file(const char *path, const char *value_path,
301
           pool_t pool, struct stat *st_r,
302
           const char *prefix, const char **output_r,
303
           const char **error_r)
304
0
{
305
0
  struct stat st;
306
0
  int fd;
307
308
0
  if ((fd = open(path, O_RDONLY)) == -1) {
309
0
    *error_r = t_strdup_printf("open(%s) failed: %m", path);
310
0
    return -1;
311
0
  }
312
0
  if (fstat(fd, &st) < 0) {
313
0
    *error_r = t_strdup_printf("fstat(%s) failed: %m", path);
314
0
    i_close_fd(&fd);
315
0
    return -1;
316
0
  }
317
0
  size_t prefix_len = strlen(prefix);
318
0
  size_t value_path_len = strlen(value_path);
319
0
  size_t buf_size = MALLOC_ADD3(prefix_len, value_path_len, 1);
320
0
  buf_size = MALLOC_ADD3(buf_size, (size_t)st.st_size, 1);
321
0
  char *buf = p_malloc(pool, buf_size);
322
0
  memcpy(buf, prefix, prefix_len);
323
0
  memcpy(buf + prefix_len, value_path, value_path_len);
324
0
  buf[prefix_len + value_path_len] = '\n';
325
326
0
  int ret = read_full(fd, buf + prefix_len + value_path_len + 1,
327
0
          st.st_size);
328
0
  i_close_fd(&fd);
329
0
  if (ret < 0) {
330
0
    *error_r = t_strdup_printf("read(%s) failed: %m", path);
331
0
    return -1;
332
0
  }
333
0
  if (ret == 0) {
334
0
    *error_r = t_strdup_printf(
335
0
      "read(%s) failed: Unexpected EOF", path);
336
0
    return -1;
337
0
  }
338
0
  if (memchr(buf + prefix_len + value_path_len + 1, '\0', st.st_size) != NULL) {
339
0
    *error_r = t_strdup_printf(
340
0
      "%s contains NUL characters - This is not supported",
341
0
      path);
342
0
    return -1;
343
0
  }
344
345
0
  if (st_r != NULL)
346
0
    *st_r = st;
347
0
  *output_r = buf;
348
0
  return 0;
349
0
}
350
351
static int
352
settings_parse_strlist(struct setting_parser_context *ctx,
353
           ARRAY_TYPE(const_string) *array,
354
           const char *key, const char *value, const char **error_r)
355
0
{
356
0
  const char *const *items;
357
0
  const char *vkey, *vvalue;
358
0
  unsigned int i, count;
359
360
  /* If the next element after the visible array is set_array_stop, then
361
     the strlist should not be modified any further. */
362
0
  if (array_is_created(array)) {
363
0
    items = array_get(array, &count);
364
0
    if (items[count] == set_array_stop)
365
0
      return 0;
366
0
  }
367
368
0
  const char *suffix = strchr(key, SETTINGS_SEPARATOR);
369
0
  if (suffix == NULL) {
370
0
    if (value[0] == '\0') {
371
      /* clear out the whole strlist */
372
0
      if (array_is_created(array))
373
0
        array_clear(array);
374
0
      return 0;
375
0
    }
376
377
0
    *error_r = t_strdup_printf(
378
0
      "Setting is a string list, use %s/key=value'", key);
379
0
    return -1;
380
0
  }
381
0
  key = settings_section_unescape(suffix + 1);
382
0
  vvalue = p_strdup(ctx->set_pool, value);
383
384
0
  if (!array_is_created(array))
385
0
    p_array_init(array, ctx->set_pool, 4);
386
387
  /* replace if it already exists */
388
0
  items = array_get(array, &count);
389
0
  for (i = 0; i < count; i += 2) {
390
0
    if (strcmp(items[i], key) == 0) {
391
0
      array_idx_set(array, i + 1, &vvalue);
392
0
      return 0;
393
0
    }
394
0
  }
395
396
0
  vkey = p_strdup(ctx->set_pool, key);
397
0
  array_push_back(array, &vkey);
398
0
  array_push_back(array, &vvalue);
399
0
  return 0;
400
0
}
401
402
int settings_parse_boollist_string(const char *value, pool_t pool,
403
           ARRAY_TYPE(const_string) *dest,
404
           const char **error_r)
405
0
{
406
0
  string_t *elem = t_str_new(32);
407
0
  const char *elem_dup;
408
0
  bool quoted = FALSE, end_of_quote = FALSE;
409
0
  for (unsigned int i = 0; value[i] != '\0'; i++) {
410
0
    switch (value[i]) {
411
0
    case '"':
412
0
      if (!quoted) {
413
        /* beginning of a string */
414
0
        if (str_len(elem) != 0) {
415
0
          *error_r = "'\"' in the middle of a string";
416
0
          return -1;
417
0
        }
418
0
        quoted = TRUE;
419
0
      } else if (end_of_quote) {
420
0
        *error_r = "Expected ',' or ' ' after '\"'";
421
0
        return -1;
422
0
      } else {
423
        /* end of a string */
424
0
        end_of_quote = TRUE;
425
0
      }
426
0
      break;
427
0
    case ' ':
428
0
    case ',':
429
0
      if (quoted && !end_of_quote) {
430
        /* inside a "quoted string" */
431
0
        str_append_c(elem, value[i]);
432
0
        break;
433
0
      }
434
435
0
      if (quoted || str_len(elem) > 0) {
436
0
        elem_dup = p_strdup(pool,
437
0
          settings_section_unescape(str_c(elem)));
438
0
        array_push_back(dest, &elem_dup);
439
0
        str_truncate(elem, 0);
440
0
      }
441
0
      quoted = FALSE;
442
0
      end_of_quote = FALSE;
443
0
      break;
444
0
    case '\\':
445
0
      if (quoted) {
446
0
        i++;
447
0
        if (value[i] == '\0') {
448
0
          *error_r = "Value ends with '\\'";
449
0
          return -1;
450
0
        }
451
0
      }
452
      /* fall through */
453
0
    default:
454
0
      if (end_of_quote) {
455
0
        *error_r = "Expected ',' or ' ' after '\"'";
456
0
        return -1;
457
0
      }
458
0
      str_append_c(elem, value[i]);
459
0
      break;
460
0
    }
461
0
  }
462
0
  if (quoted && !end_of_quote) {
463
0
    *error_r = "Missing ending '\"'";
464
0
    return -1;
465
0
  }
466
0
  if (quoted || str_len(elem) > 0) {
467
0
    elem_dup = p_strdup(pool, settings_section_unescape(str_c(elem)));
468
0
    array_push_back(dest, &elem_dup);
469
0
  }
470
0
  return 0;
471
0
}
472
473
const char *const *settings_boollist_get(const ARRAY_TYPE(const_string) *array)
474
0
{
475
0
  const char *const *strings = empty_str_array;
476
0
  unsigned int count;
477
478
0
  if (array_not_empty(array)) {
479
0
    strings = array_get(array, &count);
480
0
    i_assert(strings[count] == NULL);
481
#ifdef DEBUG_FAST
482
  i_assert(strings[count+1] == boollist_eol_sentry ||
483
     (strings[count+1] == set_array_stop &&
484
      strings[count+2] == boollist_eol_sentry));
485
#endif
486
0
  }
487
0
  return strings;
488
489
0
}
490
491
void settings_file_get(const char *value, pool_t path_pool,
492
           struct settings_file *file_r)
493
0
{
494
0
  const char *p;
495
496
0
  if (*value == '\0') {
497
0
    file_r->path = "";
498
0
    file_r->content = "";
499
0
    return;
500
0
  }
501
502
0
  p = strchr(value, '\n');
503
0
  if (p == NULL)
504
0
    i_panic("Settings file value is missing LF");
505
0
  file_r->path = p_strdup_until(path_pool, value, p);
506
0
  file_r->content = p + 1;
507
0
}
508
509
bool settings_file_has_path(const char *value)
510
0
{
511
  /* value must be in <path><LF><content> format */
512
0
  const char *p = strchr(value, '\n');
513
0
  if (p == NULL)
514
0
    i_panic("Settings file value is missing LF");
515
0
  return p != value;
516
0
}
517
518
const char *settings_file_get_value(pool_t pool,
519
            const struct settings_file *file)
520
0
{
521
0
  const char *path = file->path != NULL ? file->path : "";
522
0
  size_t path_len = strlen(path);
523
0
  size_t content_len = strlen(file->content);
524
0
  size_t value_size = MALLOC_ADD3(path_len, 1, content_len);
525
0
  value_size = MALLOC_ADD(value_size, 1);
526
527
0
  char *value = p_malloc(pool, value_size);
528
0
  memcpy(value, path, path_len);
529
0
  value[path_len] = '\n';
530
0
  memcpy(value + path_len + 1, file->content, content_len);
531
0
  return value;
532
0
}
533
534
void settings_boollist_finish(ARRAY_TYPE(const_string) *array, bool stop)
535
0
{
536
0
  array_append_zero(array);
537
0
  if (stop)
538
0
    array_push_back(array, &set_array_stop);
539
#ifdef DEBUG_FAST
540
  array_push_back(array, &boollist_eol_sentry);
541
  array_pop_back(array);
542
#endif
543
0
  if (stop)
544
0
    array_pop_back(array);
545
0
  array_pop_back(array);
546
0
}
547
548
bool settings_boollist_is_stopped(const ARRAY_TYPE(const_string) *array)
549
0
{
550
  /* The first element after the visible array is NULL. If the next element
551
     after the NULL is set_array_stop, then the boollist is stopped. */
552
0
  unsigned int count;
553
0
  const char *const *values = array_get(array, &count);
554
0
  i_assert(values[count] == NULL);
555
0
  return values[count + 1] == set_array_stop;
556
0
}
557
558
static int
559
settings_parse_boollist(struct setting_parser_context *ctx,
560
      ARRAY_TYPE(const_string) *array,
561
      const char *key, const char *value)
562
0
{
563
0
  const char *const *elem, *error;
564
565
0
  if (!array_is_created(array))
566
0
    p_array_init(array, ctx->set_pool, 5);
567
0
  else {
568
    /* If the array is stopped, then the boollist should not
569
       be modified any further. */
570
0
    if (settings_boollist_is_stopped(array))
571
0
      return 0;
572
0
  }
573
574
0
  key = strrchr(key, SETTINGS_SEPARATOR);
575
0
  if (key == NULL) {
576
    /* replace the whole boollist */
577
0
    array_clear(array);
578
0
    if (settings_parse_boollist_string(value, ctx->set_pool,
579
0
               array, &error) < 0) {
580
0
      settings_parser_set_error(ctx, error);
581
0
      return -1;
582
0
    }
583
    /* keep it NULL-terminated for each access */
584
0
    settings_boollist_finish(array, FALSE);
585
0
    return 0;
586
0
  }
587
0
  key = settings_section_unescape(key + 1);
588
589
0
  bool value_bool;
590
0
  if (get_bool(ctx, value, &value_bool) < 0)
591
0
    return -1;
592
593
0
  elem = array_lsearch(array, &key, i_strcmp_p);
594
0
  if (elem == NULL && value_bool) {
595
    /* add missing element */
596
0
    key = p_strdup(ctx->set_pool, key);
597
0
    array_push_back(array, &key);
598
0
  } else if (!value_bool) {
599
    /* remove unwanted element */
600
0
    if (elem != NULL) {
601
0
      key = *elem;
602
0
      array_delete(array, array_ptr_to_idx(array, elem), 1);
603
0
    } else {
604
0
      key = p_strdup(ctx->parser_pool, key);
605
0
    }
606
    /* remember the removal for settings_parse_list_has_key() */
607
0
    if (!array_is_created(&ctx->boollist_removals))
608
0
      p_array_init(&ctx->boollist_removals, ctx->parser_pool, 2);
609
0
    struct boollist_removal *removal =
610
0
      array_append_space(&ctx->boollist_removals);
611
0
    removal->array = array;
612
0
    removal->key_suffix = key;
613
0
  }
614
  /* keep it NULL-terminated for each access */
615
0
  settings_boollist_finish(array, FALSE);
616
0
  return 0;
617
0
}
618
619
static int
620
settings_parse(struct setting_parser_context *ctx,
621
         const struct setting_define *def,
622
         const char *key, const char *value, bool dup_value)
623
0
{
624
0
  void *ptr;
625
0
  const void *ptr2;
626
0
  const char *error;
627
0
  int ret;
628
629
0
  if (value == set_value_unknown) {
630
    /* setting value is unknown - preserve the exact pointer */
631
0
    dup_value = FALSE;
632
0
  }
633
634
0
  i_free(ctx->error);
635
636
0
  while (def->type == SET_ALIAS) {
637
0
    i_assert(def != ctx->info->defines);
638
0
    def--;
639
0
  }
640
641
0
  ptr = PTR_OFFSET(ctx->set_struct, def->offset);
642
0
  switch (def->type) {
643
0
  case SET_BOOL:
644
0
    if (get_bool(ctx, value, (bool *)ptr) < 0)
645
0
      return -1;
646
0
    break;
647
0
  case SET_UINTMAX:
648
0
    if (get_uintmax(ctx, value, (uintmax_t *)ptr) < 0)
649
0
      return -1;
650
0
    break;
651
0
  case SET_UINT:
652
0
    if (get_uint(ctx, value, (unsigned int *)ptr) < 0)
653
0
      return -1;
654
0
    break;
655
0
  case SET_UINT_OCT:
656
0
    if (get_octal(ctx, value, (unsigned int *)ptr) < 0)
657
0
      return -1;
658
0
    break;
659
0
  case SET_TIME:
660
0
    if (settings_value_is_unlimited(value)) {
661
0
      *(unsigned int *)ptr = SET_TIME_INFINITE;
662
0
      return 0;
663
0
    }
664
0
    if (str_parse_get_interval(value, (unsigned int *)ptr, &error) < 0) {
665
0
      settings_parser_set_error(ctx, error);
666
0
      return -1;
667
0
    }
668
0
    break;
669
0
  case SET_TIME_MSECS:
670
0
    if (settings_value_is_unlimited(value)) {
671
0
      *(unsigned int *)ptr = SET_TIME_MSECS_INFINITE;
672
0
      return 0;
673
0
    }
674
0
    if (str_parse_get_interval_msecs(value, (unsigned int *)ptr, &error) < 0) {
675
0
      settings_parser_set_error(ctx, error);
676
0
      return -1;
677
0
    }
678
0
    break;
679
0
  case SET_SIZE:
680
0
    if (settings_value_is_unlimited(value)) {
681
0
      *(uoff_t *)ptr = SET_SIZE_UNLIMITED;
682
0
      return 0;
683
0
    }
684
0
    if (str_parse_get_size(value, (uoff_t *)ptr, &error) < 0) {
685
0
      settings_parser_set_error(ctx, error);
686
0
      return -1;
687
0
    }
688
0
    break;
689
0
  case SET_IN_PORT:
690
0
    if (get_in_port_zero(ctx, value, (in_port_t *)ptr) < 0)
691
0
      return -1;
692
0
    break;
693
0
  case SET_STR:
694
0
  case SET_STR_NOVARS:
695
0
    if (HAS_ANY_BITS(def->flags, SET_FLAG_UNICODE_NFC) &&
696
0
        value != set_value_unknown) {
697
0
      if (uni_utf8_to_nfc(value, strlen(value), &value) < 0) {
698
0
        settings_parser_set_error(ctx,
699
0
          "Value contains invalid Unicode code point");
700
0
        return -1;
701
0
      }
702
0
      dup_value = TRUE;
703
0
    }
704
0
    if (dup_value)
705
0
      value = p_strdup(ctx->set_pool, value);
706
0
    *((const char **)ptr) = value;
707
0
    break;
708
0
  case SET_FILE: {
709
    /* only expand first line, if there */
710
0
    const char *path = t_strcut(value, '\n');
711
0
    if (strstr(path, "%{") != NULL) {
712
0
      if (t_var_expand(value, NULL, &value, &error) < 0) {
713
0
        settings_parser_set_error(ctx, error);
714
0
        return -1;
715
0
      }
716
0
    }
717
    /* Read the file directly to get the content */
718
0
    if (get_file(ctx, dup_value, &value) < 0) {
719
      /* We may be running settings_check()s in doveconf at a
720
         time when the file couldn't yet be opened. To avoid
721
         unnecessary errors, set the value unknown. */
722
0
      *((const char **)ptr) = set_value_unknown;
723
0
      return -1;
724
0
    }
725
0
    *((const char **)ptr) = value;
726
0
    break;
727
0
  }
728
0
  case SET_ENUM:
729
    /* get the available values from default string */
730
0
    i_assert(ctx->info->defaults != NULL);
731
0
    ptr2 = CONST_PTR_OFFSET(ctx->info->defaults, def->offset);
732
0
    if (get_enum(ctx, value, (char **)ptr,
733
0
           *(const char *const *)ptr2) < 0)
734
0
      return -1;
735
0
    break;
736
0
  case SET_STRLIST:
737
0
    T_BEGIN {
738
0
      ret = settings_parse_strlist(ctx, ptr, key, value,
739
0
                 &error);
740
0
      if (ret < 0)
741
0
        settings_parser_set_error(ctx, error);
742
0
    } T_END;
743
0
    if (ret < 0)
744
0
      return -1;
745
0
    break;
746
0
  case SET_BOOLLIST:
747
0
    T_BEGIN {
748
0
      ret = settings_parse_boollist(ctx, ptr, key, value);
749
0
    } T_END;
750
0
    if (ret < 0)
751
0
      return -1;
752
0
    break;
753
0
  case SET_FILTER_ARRAY: {
754
    /* Add filter names to the array. Userdb can add more simply
755
       by giving e.g. "namespace+=newname" without it removing the
756
       existing ones. */
757
0
    ARRAY_TYPE(const_string) *arr = ptr;
758
0
    const char *const *list =
759
0
      t_strsplit(value, SETTINGS_FILTER_ARRAY_SEPARATORS);
760
0
    unsigned int i, count = str_array_length(list);
761
0
    if (!array_is_created(arr))
762
0
      p_array_init(arr, ctx->set_pool, count);
763
0
    else {
764
      /* If the next element after the visible array is
765
         set_array_stop, then the named list filter
766
         should not be modified any further. */
767
0
      unsigned int old_count;
768
0
      const char *const *old_values =
769
0
        array_get(arr, &old_count);
770
0
      if (old_values[old_count] == set_array_stop)
771
0
        break;
772
0
    }
773
0
    unsigned int insert_pos = 0;
774
0
    for (i = 0; i < count; i++) {
775
0
      const char *value = p_strdup(ctx->set_pool,
776
0
        settings_section_unescape(list[i]));
777
0
      if (array_lsearch(arr, &value, i_strcmp_p) != NULL)
778
0
        continue; /* ignore duplicates */
779
0
      if ((ctx->flags & SETTINGS_PARSER_FLAG_INSERT_FILTERS) != 0)
780
0
        array_insert(arr, insert_pos++, &value, 1);
781
0
      else
782
0
        array_push_back(arr, &value);
783
0
    }
784
    /* Make sure the next element after the array is accessible for
785
       the set_array_stop check. */
786
0
    array_append_zero(arr);
787
0
    array_pop_back(arr);
788
0
    break;
789
0
  }
790
0
  case SET_FILTER_NAME:
791
0
    settings_parser_set_error(ctx, t_strdup_printf(
792
0
      "Setting is a named filter, use '%s {'", key));
793
0
    return -1;
794
0
  case SET_ALIAS:
795
0
    i_unreached();
796
0
  }
797
0
  return 0;
798
0
}
799
800
static bool
801
settings_find_key(struct setting_parser_context *ctx, const char *key,
802
      bool allow_filter_name, const struct setting_define **def_r)
803
0
{
804
0
  const struct setting_define *def;
805
0
  const char *end, *parent_key;
806
807
  /* try to find the exact key */
808
0
  def = setting_define_find(ctx->info, key);
809
0
  if (def != NULL && (def->type != SET_FILTER_NAME ||
810
0
          allow_filter_name)) {
811
0
    *def_r = def;
812
0
    return TRUE;
813
0
  }
814
815
  /* try to find list/key prefix */
816
0
  end = strrchr(key, SETTINGS_SEPARATOR);
817
0
  if (end == NULL)
818
0
    return FALSE;
819
820
0
  parent_key = t_strdup_until(key, end);
821
0
  def = setting_define_find(ctx->info, parent_key);
822
0
  if (def != NULL && (def->type == SET_STRLIST ||
823
0
          def->type == SET_BOOLLIST)) {
824
0
    *def_r = def;
825
0
    return TRUE;
826
0
  }
827
0
  return FALSE;
828
0
}
829
830
static int
831
settings_parse_keyvalue_real(struct setting_parser_context *ctx,
832
           const char *key, const char *value, bool dup_value)
833
0
{
834
0
  const struct setting_define *def;
835
836
0
  if (!settings_find_key(ctx, key, FALSE, &def)) {
837
0
    settings_parser_set_error(ctx,
838
0
      t_strconcat("Unknown setting: ", key, NULL));
839
0
    return 0;
840
0
  }
841
842
0
  if (settings_parse(ctx, def, key, value, dup_value) < 0)
843
0
    return -1;
844
0
  return 1;
845
0
}
846
847
int settings_parse_keyvalue(struct setting_parser_context *ctx,
848
          const char *key, const char *value)
849
0
{
850
0
  return settings_parse_keyvalue_real(ctx, key, value, TRUE);
851
0
}
852
853
int settings_parse_keyidx_value(struct setting_parser_context *ctx,
854
        unsigned int key_idx, const char *key,
855
        const char *value)
856
0
{
857
0
  return settings_parse(ctx, &ctx->info->defines[key_idx],
858
0
            key, value, TRUE);
859
0
}
860
861
int settings_parse_keyvalue_nodup(struct setting_parser_context *ctx,
862
          const char *key, const char *value)
863
0
{
864
0
  return settings_parse_keyvalue_real(ctx, key, value, FALSE);
865
0
}
866
867
int settings_parse_keyidx_value_nodup(struct setting_parser_context *ctx,
868
              unsigned int key_idx, const char *key,
869
              const char *value)
870
0
{
871
0
  return settings_parse(ctx, &ctx->info->defines[key_idx],
872
0
            key, value, FALSE);
873
0
}
874
875
void settings_parse_array_stop(struct setting_parser_context *ctx,
876
             unsigned int key_idx)
877
0
{
878
0
  i_assert(ctx->info->defines[key_idx].type == SET_FILTER_ARRAY ||
879
0
     ctx->info->defines[key_idx].type == SET_BOOLLIST ||
880
0
     ctx->info->defines[key_idx].type == SET_STRLIST);
881
882
0
  ARRAY_TYPE(const_string) *arr =
883
0
    PTR_OFFSET(ctx->set_struct, ctx->info->defines[key_idx].offset);
884
0
  if (!array_is_created(arr))
885
0
    p_array_init(arr, ctx->set_pool, 1);
886
887
0
  if (ctx->info->defines[key_idx].type == SET_BOOLLIST)
888
0
    settings_boollist_finish(arr, TRUE);
889
0
  else {
890
    /* Use the next element hidden after the array to keep
891
       the stop-state */
892
0
    array_push_back(arr, &set_array_stop);
893
0
    array_pop_back(arr);
894
0
  }
895
0
}
896
897
static int boollist_removal_cmp(const struct boollist_removal *r1,
898
        const struct boollist_removal *r2)
899
0
{
900
0
  if (r1->array != r2->array)
901
0
    return 1;
902
0
  return strcmp(r1->key_suffix, r2->key_suffix);
903
0
}
904
905
bool settings_parse_list_has_key(struct setting_parser_context *ctx,
906
         unsigned int key_idx,
907
         const char *key_suffix)
908
0
{
909
0
  const struct setting_define *def = &ctx->info->defines[key_idx];
910
0
  unsigned int skip = UINT_MAX;
911
912
0
  switch (def->type) {
913
0
  case SET_STRLIST:
914
0
    skip = 2;
915
0
    break;
916
0
  case SET_BOOLLIST:
917
0
    skip = 1;
918
0
    if (!array_is_created(&ctx->boollist_removals))
919
0
      break;
920
921
0
    struct boollist_removal lookup = {
922
0
      .array = PTR_OFFSET(ctx->set_struct, def->offset),
923
0
      .key_suffix = key_suffix,
924
0
    };
925
0
    if (array_lsearch(&ctx->boollist_removals, &lookup,
926
0
          boollist_removal_cmp) != NULL)
927
0
      return TRUE;
928
0
    break;
929
0
  default:
930
0
    i_unreached();
931
0
  }
932
933
0
  ARRAY_TYPE(const_string) *array =
934
0
    PTR_OFFSET(ctx->set_struct, def->offset);
935
0
  if (!array_is_created(array))
936
0
    return FALSE;
937
938
0
  unsigned int i, count;
939
0
  const char *const *items = array_get(array, &count);
940
0
  for (i = 0; i < count; i += skip) {
941
0
    if (strcmp(items[i], key_suffix) == 0)
942
0
      return TRUE;
943
0
  }
944
0
  return FALSE;
945
0
}
946
947
const void *
948
settings_parse_get_value(struct setting_parser_context *ctx,
949
       const char **key, enum setting_type *type_r)
950
0
{
951
0
  const struct setting_define *def;
952
953
0
  if (!settings_find_key(ctx, *key, TRUE, &def))
954
0
    return NULL;
955
956
0
  while (def->type == SET_ALIAS) {
957
0
    i_assert(def != ctx->info->defines);
958
0
    def--;
959
    /* Replace the key with the unaliased key. We assume here that
960
       lists don't have aliases, because the key replacement
961
       would only need to replace the key prefix then. */
962
0
    i_assert(def->type != SET_STRLIST && def->type != SET_BOOLLIST);
963
0
    *key = def->key;
964
0
  }
965
0
  *type_r = def->type;
966
0
  return PTR_OFFSET(ctx->set_struct, def->offset);
967
0
}
968
969
bool settings_check(struct event *event, const struct setting_parser_info *info,
970
        pool_t pool, void *set, const char **error_r)
971
0
{
972
0
  bool valid;
973
974
0
  if (info->check_func != NULL) {
975
0
    T_BEGIN {
976
0
      valid = info->check_func(set, pool, error_r);
977
0
    } T_END_PASS_STR_IF(!valid, error_r);
978
0
    if (!valid)
979
0
      return FALSE;
980
0
  }
981
0
  if (info->ext_check_func != NULL) {
982
0
    T_BEGIN {
983
0
      valid = info->ext_check_func(event, set, pool, error_r);
984
0
    } T_END_PASS_STR_IF(!valid, error_r);
985
0
    if (!valid)
986
0
      return FALSE;
987
0
  }
988
0
  return TRUE;
989
0
}
990
991
bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool,
992
         struct event *event, const char **error_r)
993
0
{
994
0
  return settings_check(event, ctx->info, pool,
995
0
            ctx->set_struct, error_r);
996
0
}
997
998
unsigned int settings_hash(const struct setting_parser_info *info,
999
         const void *set, const char *const *except_fields)
1000
0
{
1001
0
  unsigned int crc = 0;
1002
1003
0
  for (unsigned int i = 0; info->defines[i].key != NULL; i++) {
1004
0
    if (except_fields != NULL &&
1005
0
        str_array_find(except_fields, info->defines[i].key))
1006
0
      continue;
1007
1008
0
    const void *p = CONST_PTR_OFFSET(set, info->defines[i].offset);
1009
0
    switch (info->defines[i].type) {
1010
0
    case SET_BOOL: {
1011
0
      const bool *b = p;
1012
0
      crc = crc32_data_more(crc, b, sizeof(*b));
1013
0
      break;
1014
0
    }
1015
0
    case SET_UINTMAX: {
1016
0
      const uintmax_t *i = p;
1017
0
      crc = crc32_data_more(crc, i, sizeof(*i));
1018
0
      break;
1019
0
    }
1020
0
    case SET_UINT:
1021
0
    case SET_UINT_OCT:
1022
0
    case SET_TIME:
1023
0
    case SET_TIME_MSECS: {
1024
0
      const unsigned int *i = p;
1025
0
      crc = crc32_data_more(crc, i, sizeof(*i));
1026
0
      break;
1027
0
    }
1028
0
    case SET_SIZE: {
1029
0
      const uoff_t *s = p;
1030
0
      crc = crc32_data_more(crc, s, sizeof(*s));
1031
0
      break;
1032
0
    }
1033
0
    case SET_IN_PORT: {
1034
0
      const in_port_t *port = p;
1035
0
      crc = crc32_data_more(crc, port, sizeof(*port));
1036
0
      break;
1037
0
    }
1038
0
    case SET_STR:
1039
0
    case SET_STR_NOVARS:
1040
0
    case SET_ENUM: {
1041
0
      const char *const *str = p;
1042
0
      crc = crc32_str_more(crc, *str);
1043
0
      break;
1044
0
    }
1045
0
    case SET_FILE: {
1046
0
      const char *const *str = p;
1047
0
      const char *lf = strchr(*str, '\n');
1048
0
      if (lf == NULL)
1049
0
        i_panic("Settings file value is missing LF");
1050
0
      if (lf == *str) {
1051
        /* no filename - need to hash the content */
1052
0
        crc = crc32_str_more(crc, *str + 1);
1053
0
      } else {
1054
        /* hashing the filename is enough */
1055
0
        crc = crc32_data_more(crc, *str, lf - *str);
1056
0
      }
1057
0
      break;
1058
0
    }
1059
0
    case SET_STRLIST:
1060
0
    case SET_BOOLLIST:
1061
0
    case SET_FILTER_ARRAY: {
1062
0
      const ARRAY_TYPE(const_string) *list = p;
1063
0
      if (array_is_created(list)) {
1064
0
        const char *str;
1065
0
        array_foreach_elem(list, str)
1066
0
          crc = crc32_str_more(crc, str);
1067
0
      }
1068
0
      break;
1069
0
    }
1070
0
    case SET_ALIAS:
1071
0
    case SET_FILTER_NAME:
1072
0
      break;
1073
0
    }
1074
0
  }
1075
0
  return crc;
1076
0
}
1077
1078
bool settings_equal(const struct setting_parser_info *info,
1079
        const void *set1, const void *set2,
1080
        const char *const *except_fields)
1081
0
{
1082
0
  for (unsigned int i = 0; info->defines[i].key != NULL; i++) {
1083
0
    if (except_fields != NULL &&
1084
0
        str_array_find(except_fields, info->defines[i].key))
1085
0
      continue;
1086
1087
0
    const void *p1 = CONST_PTR_OFFSET(set1, info->defines[i].offset);
1088
0
    const void *p2 = CONST_PTR_OFFSET(set2, info->defines[i].offset);
1089
0
    switch (info->defines[i].type) {
1090
0
    case SET_BOOL: {
1091
0
      const bool *b1 = p1, *b2 = p2;
1092
0
      if (*b1 != *b2)
1093
0
        return FALSE;
1094
0
      break;
1095
0
    }
1096
0
    case SET_UINTMAX: {
1097
0
      const uintmax_t *i1 = p1, *i2 = p2;
1098
0
      if (*i1 != *i2)
1099
0
        return FALSE;
1100
0
      break;
1101
0
    }
1102
0
    case SET_UINT:
1103
0
    case SET_UINT_OCT:
1104
0
    case SET_TIME:
1105
0
    case SET_TIME_MSECS: {
1106
0
      const unsigned int *i1 = p1, *i2 = p2;
1107
0
      if (*i1 != *i2)
1108
0
        return FALSE;
1109
0
      break;
1110
0
    }
1111
0
    case SET_SIZE: {
1112
0
      const uoff_t *s1 = p1, *s2 = p2;
1113
0
      if (*s1 != *s2)
1114
0
        return FALSE;
1115
0
      break;
1116
0
    }
1117
0
    case SET_IN_PORT: {
1118
0
      const in_port_t *port1 = p1, *port2 = p2;
1119
0
      if (*port1 != *port2)
1120
0
        return FALSE;
1121
0
      break;
1122
0
    }
1123
0
    case SET_STR:
1124
0
    case SET_STR_NOVARS:
1125
0
    case SET_ENUM:
1126
0
    case SET_FILE: {
1127
0
      const char *const *str1 = p1, *const *str2 = p2;
1128
0
      if (strcmp(*str1, *str2) != 0)
1129
0
        return FALSE;
1130
0
      break;
1131
0
    }
1132
0
    case SET_STRLIST:
1133
0
    case SET_BOOLLIST:
1134
0
    case SET_FILTER_ARRAY: {
1135
0
      const ARRAY_TYPE(const_string) *list1 = p1, *list2 = p2;
1136
0
      if (array_is_empty(list1)) {
1137
0
        if (!array_is_empty(list2))
1138
0
          return FALSE;
1139
0
        break;
1140
0
      }
1141
0
      if (array_is_empty(list2))
1142
0
        return FALSE;
1143
1144
0
      unsigned int i, count1, count2;
1145
0
      const char *const *str1 = array_get(list1, &count1);
1146
0
      const char *const *str2 = array_get(list2, &count2);
1147
0
      if (count1 != count2)
1148
0
        return FALSE;
1149
0
      for (i = 0; i < count1; i++) {
1150
0
        if (strcmp(str1[i], str2[i]) != 0)
1151
0
          return FALSE;
1152
0
      }
1153
0
      break;
1154
0
    }
1155
0
    case SET_ALIAS:
1156
0
    case SET_FILTER_NAME:
1157
0
      break;
1158
0
    }
1159
0
  }
1160
0
  return TRUE;
1161
0
}
1162
1163
void *settings_defaults_dup(pool_t pool, const struct setting_parser_info *info)
1164
0
{
1165
0
  void *dup = p_malloc(pool, info->struct_size);
1166
0
  memcpy(dup, info->defaults, info->struct_size);
1167
0
  memcpy(PTR_OFFSET(dup, info->pool_offset1 - 1), &pool, sizeof(pool));
1168
0
  return dup;
1169
0
}
1170
1171
const char *settings_section_escape(const char *name)
1172
0
{
1173
0
#define CHAR_NEED_ESCAPE(c) \
1174
0
  ((c) == '=' || (c) == SETTINGS_SEPARATOR || (c) == '\\' || (c) == ' ' || (c) == ',')
1175
0
  string_t *str;
1176
0
  unsigned int i;
1177
1178
0
  for (i = 0; name[i] != '\0'; i++) {
1179
0
    if (CHAR_NEED_ESCAPE(name[i]))
1180
0
      break;
1181
0
  }
1182
0
  if (name[i] == '\0') {
1183
0
    if (i == 0)
1184
0
      return "\\.";
1185
0
    return name;
1186
0
  }
1187
1188
0
  str = t_str_new(i + strlen(name+i) + 8);
1189
0
  str_append_data(str, name, i);
1190
0
  for (; name[i] != '\0'; i++) {
1191
0
    switch (name[i]) {
1192
0
    case '=':
1193
0
      str_append(str, "\\e");
1194
0
      break;
1195
0
    case SETTINGS_SEPARATOR:
1196
0
      str_append(str, "\\s");
1197
0
      break;
1198
0
    case '\\':
1199
0
      str_append(str, "\\\\");
1200
0
      break;
1201
0
    case ' ':
1202
0
      str_append(str, "\\_");
1203
0
      break;
1204
0
    case ',':
1205
0
      str_append(str, "\\+");
1206
0
      break;
1207
0
    default:
1208
0
      str_append_c(str, name[i]);
1209
0
      break;
1210
0
    }
1211
0
  }
1212
0
  return str_c(str);
1213
0
}
1214
1215
const char *settings_section_unescape(const char *name)
1216
0
{
1217
0
  const char *p = strchr(name, '\\');
1218
0
  if (p == NULL)
1219
0
    return name;
1220
1221
0
  string_t *str = t_str_new(strlen(name));
1222
0
  str_append_data(str, name, p - name);
1223
0
  while (p[1] != '\0') {
1224
0
    switch (p[1]) {
1225
0
    case 'e':
1226
0
      str_append_c(str, '=');
1227
0
      break;
1228
0
    case 's':
1229
0
      str_append_c(str, SETTINGS_SEPARATOR);
1230
0
      break;
1231
0
    case '\\':
1232
0
      str_append_c(str, '\\');
1233
0
      break;
1234
0
    case '_':
1235
0
      str_append_c(str, ' ');
1236
0
      break;
1237
0
    case '+':
1238
0
      str_append_c(str, ',');
1239
0
      break;
1240
0
    case '.':
1241
      /* empty string */
1242
0
      break;
1243
0
    default:
1244
      /* not supposed to happen */
1245
0
      str_append_c(str, '\\');
1246
0
      str_append_c(str, p[1]);
1247
0
      break;
1248
0
    }
1249
0
    name = p+2;
1250
0
    p = strchr(name, '\\');
1251
0
    if (p == NULL) {
1252
0
      str_append(str, name);
1253
0
      return str_c(str);
1254
0
    }
1255
0
    str_append_data(str, name, p - name);
1256
0
  }
1257
  /* ends with '\\' - not supposed to happen */
1258
0
  str_append_c(str, '\\');
1259
0
  return str_c(str);
1260
0
}
1261
1262
static enum settings_binary config_binary = SETTINGS_BINARY_OTHER;
1263
1264
void settings_set_config_binary(enum settings_binary binary)
1265
0
{
1266
0
  config_binary = binary;
1267
0
}
1268
1269
enum settings_binary settings_get_config_binary(void)
1270
0
{
1271
0
  return config_binary;
1272
0
}
1273