Coverage Report

Created: 2026-10-17 06:21

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