Coverage Report

Created: 2025-11-03 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-var-expand/expansion-program.c
Line
Count
Source
1
/* Copyright (c) 2024 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str.h"
6
#include "strescape.h"
7
#include "hex-binary.h"
8
#include "var-expand-private.h"
9
#include "var-expand-parser-private.h"
10
#include "var-expand-parser.h"
11
#include "expansion.h"
12
13
extern void var_expand_parser_lex_init_extra(void*, void*);
14
15
static const struct var_expand_params empty_params = {
16
};
17
18
int var_expand_program_create(const char *str,
19
            struct var_expand_program **program_r,
20
            const char **error_r)
21
0
{
22
0
  int ret;
23
0
  struct var_expand_parser_state state;
24
0
  i_zero(&state);
25
0
  pool_t pool =
26
0
    pool_alloconly_create(MEMPOOL_GROWING"var expand program", 1024);
27
0
  state.p = state.plist = p_new(pool, struct var_expand_program, 1);
28
0
  state.p->pool = pool;
29
0
  p_array_init(&state.variables, pool, 1);
30
31
0
  T_BEGIN {
32
0
    state.str = NULL;
33
0
    state.pool =
34
0
      pool_alloconly_create(MEMPOOL_GROWING"var expand parser", 32768);
35
0
    p_array_init(&state.variables, pool, 1);
36
0
    state.input = str;
37
0
    state.left = strlen(str);
38
0
    var_expand_parser_lex_init_extra(&state, &state.scanner);
39
    /* 0 = OK, everything else = something went wrong */
40
0
    ret = var_expand_parser_parse(&state);
41
0
    state.error = t_strdup(state.error);
42
0
  } T_END_PASS_STR_IF(ret != 0, &state.error);
43
44
0
  array_append_space(&state.variables);
45
0
  state.plist->variables = array_front(&state.variables);
46
0
  i_assert(state.plist->variables != NULL);
47
0
  pool_unref(&state.pool);
48
49
0
  if (ret != 0) {
50
0
    *error_r = state.error;
51
0
    var_expand_program_free(&state.plist);
52
0
  } else {
53
0
    *program_r = state.plist;
54
0
  }
55
0
  i_assert(ret == 0 || *error_r != NULL);
56
57
0
  return ret == 0 ? 0 : -1;
58
0
}
59
60
void var_expand_program_dump(const struct var_expand_program *prog, string_t *dest)
61
0
{
62
0
  while (prog != NULL) {
63
0
    struct var_expand_statement *stmt = prog->first;
64
0
    while (stmt != NULL) {
65
0
      const char *or_var = "";
66
0
      if (stmt == prog->first && !prog->only_literal)
67
0
        or_var = " or variable";
68
0
      str_printfa(dest, "function%s %s\n", or_var, stmt->function);
69
0
      struct var_expand_parameter_iter_context *ctx =
70
0
        var_expand_parameter_iter_init(stmt);
71
0
      while (var_expand_parameter_iter_more(ctx)) {
72
0
        const struct var_expand_parameter *par =
73
0
          var_expand_parameter_iter_next(ctx);
74
0
        var_expand_parameter_dump(dest, par);
75
0
      }
76
0
      stmt = stmt->next;
77
0
    }
78
0
    prog = prog->next;
79
0
  }
80
0
}
81
82
int var_expand_program_execute(string_t *dest, const struct var_expand_program *program,
83
             const struct var_expand_params *params, const char **error_r)
84
0
 {
85
0
  int ret = 0;
86
0
  struct var_expand_state state;
87
0
  i_zero(&state);
88
89
0
  if (params == NULL)
90
0
    params = &empty_params;
91
92
0
  i_assert((params->table == NULL && params->tables_arr == NULL) ||
93
0
     (params->table != NULL && params->tables_arr == NULL) ||
94
0
     (params->table == NULL && params->tables_arr != NULL));
95
96
0
  i_assert((params->providers == NULL && params->providers_arr == NULL) ||
97
0
     (params->providers != NULL && params->providers_arr == NULL) ||
98
0
     (params->providers == NULL && params->providers_arr != NULL));
99
100
0
  size_t num_tables = 0;
101
0
  if (params->tables_arr != NULL)
102
0
    while (params->tables_arr[num_tables] != NULL)
103
0
      num_tables++;
104
0
  size_t num_providers = 0;
105
0
  if (params->providers_arr != NULL)
106
0
    while (params->providers_arr[num_providers] != NULL)
107
0
         num_providers++;
108
0
  size_t num_contexts = I_MAX(num_tables, num_providers);
109
110
  /* ensure contexts are properly terminated. */
111
0
  i_assert(params->contexts == NULL ||
112
0
     params->contexts[num_contexts] == var_expand_contexts_end);
113
114
0
  state.params = params;
115
0
  state.result = str_new(default_pool, 32);
116
0
  state.transfer = str_new(default_pool, 32);
117
118
0
  *error_r = NULL;
119
120
0
  while (program != NULL) {
121
0
    const struct var_expand_statement *stmt = program->first;
122
0
    if (stmt == NULL) {
123
      /* skip empty programs */
124
0
      program = program->next;
125
0
      continue;
126
0
    }
127
0
    T_BEGIN {
128
0
      while (stmt != NULL) {
129
0
        bool first = stmt == program->first;
130
0
        if (!var_expand_execute_stmt(&state, stmt,
131
0
                   first, error_r)) {
132
0
          ret = -1;
133
0
          break;
134
0
        }
135
0
        stmt = stmt->next;
136
0
      }
137
0
    } T_END_PASS_STR_IF(ret < 0, error_r);
138
0
    if (ret < 0)
139
0
      break;
140
0
    if (state.transfer_binary)
141
0
      var_expand_state_set_transfer(&state, binary_to_hex(state.transfer->data, state.transfer->used));
142
0
    if (state.transfer_set) {
143
0
      if (!program->only_literal && params->escape_func != NULL) {
144
0
        str_append(state.result,
145
0
             params->escape_func(str_c(state.transfer),
146
0
                     params->escape_context));
147
0
      } else
148
0
        str_append_str(state.result, state.transfer);
149
0
    } else {
150
0
      *error_r = t_strdup(state.delayed_error);
151
0
      ret = -1;
152
0
      break;
153
0
    }
154
0
    var_expand_state_unset_transfer(&state);
155
0
    program = program->next;
156
0
  };
157
0
  str_free(&state.transfer);
158
0
  i_free(state.delayed_error);
159
  /* only write to dest on success */
160
0
  if (ret == 0)
161
0
    str_append_str(dest, state.result);
162
0
  str_free(&state.result);
163
0
  i_assert(ret == 0 || *error_r != NULL);
164
165
0
  return ret;
166
0
}
167
168
const char *const *
169
var_expand_program_variables(const struct var_expand_program *program)
170
0
{
171
0
  return program->variables;
172
0
}
173
174
void var_expand_program_free(struct var_expand_program **_program)
175
0
{
176
0
  struct var_expand_program *program = *_program;
177
0
  if (program == NULL)
178
0
    return;
179
0
  *_program = NULL;
180
181
0
  pool_unref(&program->pool);
182
0
}
183
184
/* Export code */
185
186
/* Encodes numbers in 7-bit bytes, using 8th to indicate
187
   that the number continues. Uses little endian encoding
188
   to allow this. */
189
static void export_number(string_t *dest, intmax_t number)
190
0
{
191
0
  unsigned char b;
192
193
  /* fast path - store any non-negative number that's smaller than
194
     127 as number + 1, including 0. */
195
0
  if (number >= 0 && number < 0x7f) {
196
0
    b = number + 1;
197
0
    str_append_c(dest, b);
198
0
    return;
199
0
  }
200
201
  /* Store sign with 0x80, so we can differentiate
202
     from fast path. */
203
0
  if (number < 0) {
204
0
    str_append_c(dest, 0x80 | '-');
205
0
    number = -number;
206
0
  } else
207
0
    str_append_c(dest, 0x80 | '+');
208
209
  /* Store the number in 7 byte chunks
210
     so we can use the 8th bit for indicating
211
     whether the number continues. */
212
0
  while (number > 0) {
213
0
    if (number > 0x7f)
214
0
      b = 0x80;
215
0
    else
216
0
      b = 0x0;
217
0
    b |= number & 0x7f;
218
0
    number >>= 7;
219
0
    str_append_c(dest, b);
220
0
  }
221
0
}
222
223
static void var_expand_program_export_one(const struct var_expand_program *program,
224
            string_t *dest)
225
0
{
226
0
  const struct var_expand_statement *stmt = program->first;
227
0
  while (stmt != NULL) {
228
0
    str_append(dest, stmt->function);
229
0
    str_append_c(dest, '\1');
230
0
    const struct var_expand_parameter *param = stmt->params;
231
0
    while (param != NULL) {
232
0
      if (param->key != NULL)
233
0
        str_append(dest, param->key);
234
0
      str_append_c(dest, '\1');
235
0
      switch (param->value_type) {
236
0
      case VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING:
237
0
        str_append_c(dest, 's');
238
0
        str_append_tabescaped(dest, param->value.str);
239
0
        str_append_c(dest, '\r');
240
0
        break;
241
0
      case VAR_EXPAND_PARAMETER_VALUE_TYPE_INT:
242
0
        str_append_c(dest, 'i');
243
0
        export_number(dest, param->value.num);
244
0
        break;
245
0
      case VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE:
246
0
        str_append_c(dest, 'v');
247
0
        str_append_tabescaped(dest, param->value.str);
248
0
        str_append_c(dest, '\r');
249
0
        break;
250
0
      default:
251
0
        i_unreached();
252
0
      }
253
0
      param = param->next;
254
0
      if (param != NULL)
255
0
        str_append_c(dest, '\1');
256
0
    }
257
0
    str_append_c(dest, '\t');
258
0
    stmt = stmt->next;
259
0
    if (stmt != NULL)
260
0
      str_append_c(dest, '\1');
261
0
    else
262
0
      str_append_c(dest, '\t');
263
0
  }
264
0
  const char *const *vars = program->variables;
265
266
0
  for (; vars != NULL && *vars != NULL; vars++) {
267
    /* ensure variable has no \1 in name */
268
0
    i_assert(strchr(*vars, '\1') == NULL);
269
0
    str_append(dest, *vars);
270
0
    str_append_c(dest, '\1');
271
0
  }
272
0
  str_append_c(dest, '\t');
273
0
}
274
275
void var_expand_program_export_append(string_t *dest,
276
              const struct var_expand_program *program)
277
0
{
278
0
  i_assert(program != NULL);
279
0
  i_assert(dest != NULL);
280
281
0
  while (program != NULL) {
282
0
    if (program->only_literal) {
283
0
      i_assert(program->first->params->value_type ==
284
0
         VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING);
285
0
      str_append_c(dest, '\1');
286
0
      str_append_tabescaped(dest, program->first->params->value.str);
287
0
      str_append_c(dest, '\r');
288
0
    } else {
289
0
      str_append_c(dest, '\2');
290
0
      var_expand_program_export_one(program, dest);
291
0
    }
292
293
0
    program = program->next;
294
0
  }
295
0
}
296
297
const char *var_expand_program_export(const struct var_expand_program *program)
298
0
{
299
0
  string_t *dest = t_str_new(64);
300
0
  var_expand_program_export_append(dest, program);
301
0
  return str_c(dest);
302
0
}
303
304
/* Import code */
305
306
static int extract_name(char *data, size_t size,
307
      const char **value_r, const char **error_r)
308
0
{
309
0
  char *ptr = memchr(data, '\1', size);
310
0
  if (ptr == NULL) {
311
0
    *error_r = "Missing end of name";
312
0
    return -1;
313
0
  }
314
0
  size_t len = ptr - data;
315
0
  if (len == 0) {
316
0
    *value_r = NULL;
317
0
    return 1;
318
0
  }
319
0
  *value_r = data;
320
0
  *ptr = '\0';
321
0
  return len + 1;
322
323
0
}
324
325
static int extract_value(char *data, size_t size,
326
       const char **value_r, const char **error_r)
327
0
{
328
0
  char *ptr = memchr(data, '\r', size);
329
0
  if (ptr == NULL) {
330
0
    *error_r = "Missing end of string";
331
0
    return -1;
332
0
  }
333
0
  size_t len = ptr - data;
334
0
  *ptr = '\0';
335
0
  *value_r = str_tabunescape(data);
336
  /* make sure we end up in right place. */
337
0
  return len + 1;
338
0
}
339
340
static int extract_number(const char *data, size_t size, intmax_t *value_r,
341
        const char **error_r)
342
0
{
343
0
  const unsigned char *ptr = (const unsigned char*)data;
344
0
  bool negative;
345
0
  size_t len = 1;
346
347
0
  if ((*ptr & 0x80) == 0) {
348
    /* fast path for small positive number */
349
0
    intmax_t number = *ptr;
350
0
    *value_r = number - 1;
351
0
    return 1;
352
0
  }
353
354
0
  const char sign = *ptr - 0x80;
355
0
  if (sign == '+') {
356
0
    negative = FALSE;
357
0
  } else if (sign == '-') {
358
0
    negative = TRUE;
359
0
  } else {
360
0
    *error_r = "Unknown number";
361
0
    return -1;
362
0
  }
363
0
  ptr++;
364
365
0
  intmax_t value = 0;
366
0
  intmax_t shift = 0;
367
368
  /* a number can be at most 9 bytes */
369
0
  for (size_t i = 0; i < I_MIN(size, 9); i++) {
370
0
    len++;
371
0
    value |= ((*(ptr) & 0x7fLL) << shift);
372
    /* if high byte is set, the number continues */
373
0
    if ((*ptr & 0x80) == 0)
374
0
      break;
375
0
    shift += 7;
376
0
    ptr++;
377
0
  }
378
379
0
  if ((*ptr & 0x80) != 0) {
380
0
    *error_r = "Unfinished number";
381
0
    return -1;
382
0
  }
383
384
0
  if (negative)
385
0
    value = -value;
386
387
0
  *value_r = value;
388
389
0
  return len;
390
0
}
391
392
#define ADVANCE_INPUT(count) \
393
0
  if (unlikely(size < (size_t)count)) { \
394
0
    *error_r = "Premature end of data"; \
395
0
    return -1; \
396
0
  }\
397
0
  data = data + (count); \
398
0
  size = size - (size_t)(count);
399
400
static int var_expand_program_import_stmt(char *data, size_t size,
401
            struct var_expand_program *program,
402
            const char **error_r)
403
0
{
404
0
  const char *name;
405
0
  const char *value;
406
0
  size_t orig_size = size;
407
408
  /* normal program, starts with filter name */
409
0
  int ret = extract_name(data, size, &name, error_r);
410
0
  if (ret < 0)
411
0
    return -1;
412
0
  if (name == NULL) {
413
0
    *error_r = "missing function name";
414
0
    return -1;
415
0
  }
416
0
  ADVANCE_INPUT(ret);
417
418
0
  struct var_expand_statement *stmt =
419
0
    p_new(program->pool, struct var_expand_statement, 1);
420
421
0
  if (program->first == NULL)
422
0
    program->first = stmt;
423
0
  else {
424
0
     struct var_expand_statement *ptr = program->first;
425
0
     while (ptr->next != NULL) ptr = ptr->next;
426
0
     ptr->next = stmt;
427
0
  }
428
429
0
  stmt->function = name;
430
0
  struct var_expand_parameter *prev = NULL;
431
0
  int idx = -1;
432
433
0
  while (size > 0 && *data != '\t') {
434
0
    struct var_expand_parameter *param =
435
0
      p_new(program->pool, struct var_expand_parameter, 1);
436
    /* check if it's named parameter */
437
0
    if (*data == '\1') {
438
0
      param->idx = ++idx;
439
0
      ADVANCE_INPUT(1);
440
0
    } else {
441
0
      ret = extract_name(data, size,
442
0
             &name, error_r);
443
0
      if (ret < 0)
444
0
        return -1;
445
0
      ADVANCE_INPUT(ret);
446
0
      param->key = name;
447
0
    }
448
449
    /* check the parameter type */
450
0
    switch (*data) {
451
0
    case 's':
452
0
      param->value_type =
453
0
        VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING;
454
0
      break;
455
0
    case 'i':
456
0
      param->value_type =
457
0
        VAR_EXPAND_PARAMETER_VALUE_TYPE_INT;
458
0
      break;
459
0
    case 'v':
460
0
      param->value_type =
461
0
        VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE;
462
0
      break;
463
0
    default:
464
0
      *error_r = "Unsupported parameter type";
465
0
      return -1;
466
0
    }
467
0
    ADVANCE_INPUT(1);
468
469
0
    if (param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING ||
470
0
        param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE) {
471
0
      ret = extract_value(data, size,
472
0
              &value, error_r);
473
0
      if (ret < 0)
474
0
        return -1;
475
0
      ADVANCE_INPUT(ret);
476
0
      param->value.str = value;
477
0
    } else if (param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_INT) {
478
0
      ret = extract_number(data, size, &param->value.num,
479
0
               error_r);
480
0
      if (ret < 0)
481
0
        return -1;
482
483
0
      ADVANCE_INPUT(ret);
484
0
    } else {
485
0
      *error_r = "Unsupported value type";
486
0
      return -1;
487
0
    }
488
489
0
    if (prev == NULL)
490
0
      stmt->params = param;
491
0
    else
492
0
      prev->next = param;
493
0
    prev = param;
494
495
0
    if (*data == '\t') {
496
0
      break;
497
0
    } else if (*data == '\1') {
498
0
      ADVANCE_INPUT(1);
499
0
    } else {
500
0
      *error_r = "Missing parameter end";
501
0
      return -1;
502
0
    }
503
0
  }
504
505
0
  if (*data != '\t')
506
0
    *error_r = "Missing parameter statement end";
507
508
0
  ADVANCE_INPUT(1);
509
510
0
  return orig_size - size;
511
0
}
512
513
static int var_expand_program_import_one(char **_data, size_t *_size,
514
           struct var_expand_program *program,
515
           const char **error_r)
516
0
{
517
0
  char *data = *_data;
518
0
  size_t size = *_size;
519
0
  const char *value;
520
0
  int ret;
521
522
  /* Only literal */
523
0
  if (*data == '\1') {
524
0
    ADVANCE_INPUT(1);
525
0
    ret = extract_value(data, size, &value, error_r);
526
0
    if (ret < 0)
527
0
      return -1;
528
0
    ADVANCE_INPUT(ret);
529
530
    /* just literal data */
531
0
    struct var_expand_statement *stmt =
532
0
      p_new(program->pool, struct var_expand_statement, 1);
533
0
    struct var_expand_parameter *param =
534
0
      p_new(program->pool, struct var_expand_parameter, 1);
535
0
    param->idx = 0;
536
0
    param->value_type = VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING;
537
0
    param->value.str = value;
538
0
    stmt->params = param;
539
0
    stmt->function = "literal";
540
0
    program->first = stmt;
541
0
    program->only_literal = TRUE;
542
  /* A full program */
543
0
  } else if (*data == '\2') {
544
0
    ADVANCE_INPUT(1);
545
0
    while (*data != '\t' && size > 0) {
546
0
      int ret = var_expand_program_import_stmt(data, size, program, error_r);
547
0
      if (ret < 0)
548
0
        return -1;
549
0
      ADVANCE_INPUT(ret);
550
0
      if (*data == '\t') {
551
0
        ADVANCE_INPUT(1);
552
0
        break;
553
0
      } else if (*data != '\1') {
554
0
        *error_r = "Missing statement end";
555
0
        return -1;
556
0
      }
557
0
      ADVANCE_INPUT(1);
558
0
    }
559
    /* And finally there should be variables */
560
0
    if (*data != '\t') {
561
0
      const char *ptr = memchr(data, '\t', size);
562
0
      if (ptr == NULL) {
563
0
        *error_r = "Missing variables end";
564
0
        return -1;
565
0
      }
566
0
      size_t len = ptr - data;
567
0
      program->variables = (const char *const *)
568
0
        p_strsplit(program->pool, data, "\1");
569
0
      ADVANCE_INPUT(len + 1);
570
0
    } else {
571
0
      ADVANCE_INPUT(1);
572
0
    }
573
0
  } else {
574
0
    *error_r = "Unknown input";
575
0
    return -1;
576
0
  }
577
0
  *_data = data;
578
0
  *_size = size;
579
580
0
  return 0;
581
0
}
582
583
int var_expand_program_import_sized(const char *data, size_t size,
584
            struct var_expand_program **program_r,
585
            const char **error_r)
586
0
{
587
0
  i_assert(data != NULL);
588
589
  /* The absolute minimum program is \2 \t or \1 \r. */
590
0
  if (size < 2) {
591
0
    *error_r = "Too short";
592
0
    return -1;
593
0
  }
594
595
0
  pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"var expand program", size);
596
0
  struct var_expand_program *prev = NULL;
597
0
  struct var_expand_program *first = NULL;
598
0
  int ret;
599
0
  char *copy_data = p_strndup(pool, data, size);
600
601
0
  while (size > 0) {
602
0
    struct var_expand_program *program =
603
0
      p_new(pool, struct var_expand_program, 1);
604
0
    program->pool = pool;
605
0
    T_BEGIN {
606
0
      ret = var_expand_program_import_one(&copy_data, &size,
607
0
                  program, error_r);
608
0
    } T_END;
609
0
    if (ret < 0)
610
0
      break;
611
0
    if (first == NULL)
612
0
      first = program;
613
0
    if (prev != NULL)
614
0
      prev->next = program;
615
0
    prev = program;
616
0
  }
617
618
0
  if (ret < 0)
619
0
    pool_unref(&pool);
620
0
  else
621
0
    *program_r = first;
622
623
0
  return ret;
624
0
}
625
626
int var_expand_program_import(const char *data,
627
            struct var_expand_program **program_r,
628
            const char **error_r)
629
0
{
630
0
  i_assert(data != NULL);
631
0
  return var_expand_program_import_sized(data, strlen(data), program_r,
632
0
                 error_r);
633
0
}