Coverage Report

Created: 2026-09-01 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-var-expand/var-expand.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 "cpu-count.h"
6
#include "guid.h"
7
#include "hostpid.h"
8
#include "str.h"
9
#include "time-util.h"
10
#include "var-expand-private.h"
11
#include "var-expand-parser.h"
12
#include "expansion.h"
13
#include "dovecot-version.h"
14
15
#include <unistd.h>
16
#include <stdint.h>
17
#include <time.h>
18
19
#ifdef HAVE_SYS_UTSNAME_H
20
#  include <sys/utsname.h>
21
#endif
22
23
0
#define ENV_CPU_COUNT "NCPU"
24
enum os_default_type {
25
  OS_DEFAULT_TYPE_SYSNAME,
26
  OS_DEFAULT_TYPE_RELEASE,
27
};
28
29
const void *const var_expand_contexts_end = POINTER_CAST(UINTPTR_MAX);
30
31
static int
32
var_expand_process(const char *field, const char **result_r,
33
       void *context ATTR_UNUSED, const char **error_r)
34
0
{
35
0
  if (strcmp(field, "pid") == 0)
36
0
    *result_r = my_pid;
37
0
  else if (strcmp(field, "uid") == 0)
38
0
    *result_r = dec2str(geteuid());
39
0
  else if (strcmp(field, "gid") == 0)
40
0
    *result_r = dec2str(getegid());
41
0
  else {
42
0
    *error_r = t_strdup_printf("Unsupported field '%s'",
43
0
             field);
44
0
    return -1;
45
0
  }
46
0
  return 0;
47
0
}
48
49
static struct utsname utsname_result;
50
static bool utsname_set = FALSE;
51
52
static int
53
var_expand_system_os(enum os_default_type type,
54
         const char **value_r, const char **error_r)
55
0
{
56
0
  if (!utsname_set) {
57
0
    utsname_set = TRUE;
58
59
0
    if (uname(&utsname_result) < 0) {
60
0
      *error_r = t_strdup_printf("uname() failed: %m");
61
0
      i_zero(&utsname_result);
62
0
      return -1;
63
0
    }
64
0
  }
65
66
0
  switch (type) {
67
0
  case OS_DEFAULT_TYPE_SYSNAME:
68
0
    *value_r = utsname_result.sysname;
69
0
    return 0;
70
0
  case OS_DEFAULT_TYPE_RELEASE:
71
0
    *value_r = utsname_result.release;
72
0
    return 0;
73
0
  default:
74
0
    break;
75
0
  }
76
77
0
  i_unreached();
78
0
}
79
80
static int
81
var_expand_system(const char *field, const char **result_r,
82
      void *context ATTR_UNUSED, const char **error_r)
83
0
{
84
0
  if (strcmp(field, "cpu_count") == 0) {
85
0
    int ncpus;
86
0
    const char *cpuenv = getenv(ENV_CPU_COUNT);
87
0
    if (cpuenv != NULL) {
88
0
      *result_r = cpuenv;
89
0
      return 0;
90
0
    }
91
0
    if (cpu_count_get(&ncpus, error_r) < 0)
92
0
      return -1;
93
0
    *result_r = dec2str(ncpus);
94
0
    return 0;
95
0
  } else if (strcmp(field, "hostname") == 0) {
96
0
    *result_r = my_hostname;
97
0
    return 0;
98
0
  } else if (strcmp(field, "os") == 0)
99
0
    return var_expand_system_os(OS_DEFAULT_TYPE_SYSNAME, result_r,
100
0
              error_r);
101
0
  else if (strcmp(field, "os-version") == 0)
102
0
    return var_expand_system_os(OS_DEFAULT_TYPE_RELEASE, result_r,
103
0
              error_r);
104
0
  *error_r = t_strdup_printf("Unsupported field '%s'", field);
105
0
  return -1;
106
0
}
107
108
static int
109
var_expand_dovecot(const char *field, const char **result_r,
110
       void *context ATTR_UNUSED, const char **error_r)
111
0
{
112
0
  if (strcmp(field, "name") == 0) {
113
0
    *result_r = PACKAGE_NAME;
114
0
    return 0;
115
0
  } else if (strcmp(field, "version") == 0) {
116
0
    *result_r = PACKAGE_VERSION;
117
0
    return 0;
118
0
  } else if (strcmp(field, "support-url") == 0) {
119
0
    *result_r = PACKAGE_WEBPAGE;
120
0
    return 0;
121
0
  } else if (strcmp(field, "support-email") == 0) {
122
0
    *result_r = PACKAGE_BUGREPORT;
123
0
    return 0;
124
0
  } else if (strcmp(field, "revision") == 0) {
125
0
    *result_r = DOVECOT_REVISION;
126
0
    return 0;
127
0
  }
128
129
0
  *error_r = t_strdup_printf("Unsupported field '%s'", field);
130
0
  return -1;
131
0
}
132
133
static int var_expand_env(const char *key, const char **value_r,
134
        void *context ATTR_UNUSED, const char **error_r)
135
0
{
136
0
  if (*key == '\0') {
137
0
    *error_r = "Missing field";
138
0
    return -1;
139
0
  }
140
141
0
  const char *value = getenv(key);
142
143
  /* never fail with env, it would make code too hard */
144
0
  if (value == NULL)
145
0
    value = "";
146
147
0
  *value_r = value;
148
0
  return 0;
149
0
}
150
151
static int var_expand_event(const char *key, const char **value_r, void *context,
152
          const char **error_r)
153
0
{
154
0
  const struct var_expand_params *params = context;
155
0
  struct event *event = params->event;
156
157
0
  if (event == NULL)
158
0
    event = event_get_global();
159
0
  if (event == NULL) {
160
0
    *error_r = "No event available";
161
0
    return -1;
162
0
  }
163
164
0
  const char *value = event_find_field_recursive_str(event, key);
165
166
0
  if (value == NULL) {
167
0
    *error_r = t_strdup_printf("No such field '%s' in event", key);
168
0
    return -1;
169
0
  }
170
171
0
  *value_r = value;
172
173
0
  return 0;
174
0
}
175
176
static int var_expand_date(const char *key, const char **value_r,
177
         void *context ATTR_UNUSED, const char **error_r)
178
0
{
179
0
  struct tm tm;
180
0
  struct timeval tv;
181
0
  i_gettimeofday(&tv);
182
0
  if (unlikely(localtime_r(&tv.tv_sec, &tm) == NULL))
183
0
    i_panic("localtime_r() failed: %m");
184
185
0
  if (strcmp(key, "year") == 0)
186
0
    *value_r = t_strftime("%Y", &tm);
187
0
  else if (strcmp(key, "month") == 0)
188
0
    *value_r = t_strftime("%m", &tm);
189
0
  else if (strcmp(key, "day") == 0)
190
0
    *value_r = t_strftime("%d", &tm);
191
0
  else
192
0
    ERROR_UNSUPPORTED_KEY(key);
193
0
  return 0;
194
0
}
195
196
static int var_expand_time(const char *key, const char **value_r,
197
         void *context ATTR_UNUSED, const char **error_r)
198
0
{
199
0
  if (strcmp(key, "unix") == 0) {
200
    /* Current time as a "<seconds>.<nanoseconds>" UNIX timestamp.
201
       Use the epoch and date filters to convert it. */
202
0
    uint64_t ns = i_nanoseconds();
203
0
    *value_r = t_strdup_printf("%ju.%09ju",
204
0
             (uintmax_t)(ns / 1000000000),
205
0
             (uintmax_t)(ns % 1000000000));
206
0
    return 0;
207
0
  }
208
209
0
  struct tm tm;
210
0
  struct timeval tv;
211
0
  i_gettimeofday(&tv);
212
0
  if (unlikely(localtime_r(&tv.tv_sec, &tm) == NULL))
213
0
    i_panic("localtime_r() failed: %m");
214
215
0
  if (strcmp(key, "hour") == 0)
216
0
    *value_r = t_strftime("%H", &tm);
217
0
  else if (strcmp(key, "min") == 0 ||
218
0
     strcmp(key, "minute") == 0)
219
0
    *value_r = t_strftime("%M", &tm);
220
0
  else if (strcmp(key, "sec") == 0 ||
221
0
     strcmp(key, "second") == 0)
222
0
    *value_r = t_strftime("%S", &tm);
223
0
  else if (strcmp(key, "us") == 0 ||
224
0
     strcmp(key, "usec") == 0)
225
0
    *value_r = dec2str(tv.tv_usec);
226
0
  else
227
0
    ERROR_UNSUPPORTED_KEY(key);
228
0
  return 0;
229
0
}
230
231
static int var_expand_generate(const char *key, const char **value_r,
232
             void *context ATTR_UNUSED, const char **error_r)
233
0
{
234
0
  guid_128_t guid;
235
236
0
  if (strcmp(key, "guid") == 0) {
237
0
    *value_r = guid_generate();
238
0
    return 0;
239
0
  }
240
0
  if (strcmp(key, "guid128") == 0) {
241
0
    guid_128_generate(guid);
242
0
    *value_r = guid_128_to_string(guid);
243
0
    return 0;
244
0
  }
245
0
  if (str_begins(key, "uuid", &key)) {
246
0
    guid_128_uuid4_generate(guid);
247
0
    if (key[0] == '\0' || strcmp(key, ":record") == 0)
248
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_RECORD);
249
0
    else if (strcmp(key, ":compact") == 0)
250
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_COMPACT);
251
0
    else if (strcmp(key, ":microsoft") == 0)
252
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_MICROSOFT);
253
0
    else
254
0
      ERROR_UNSUPPORTED_KEY(key);
255
0
    return 0;
256
0
  }
257
0
  ERROR_UNSUPPORTED_KEY(key);
258
0
}
259
260
static const struct var_expand_provider internal_providers[] = {
261
  { .key = "process", .func = var_expand_process },
262
  { .key = "system", .func = var_expand_system  },
263
  { .key = "dovecot", .func = var_expand_dovecot  },
264
  { .key = "env", .func = var_expand_env },
265
  { .key = "event", .func = var_expand_event },
266
  { .key = "date", .func = var_expand_date },
267
  { .key = "time", .func = var_expand_time },
268
  { .key = "generate", .func = var_expand_generate },
269
  VAR_EXPAND_TABLE_END
270
};
271
272
bool var_expand_provider_is_builtin(const char *prefix)
273
0
{
274
0
  for (size_t i = 0; internal_providers[i].key != NULL; i++)
275
0
    if (strcmp(prefix, internal_providers[i].key) == 0)
276
0
      return TRUE;
277
0
  return FALSE;
278
0
}
279
280
static int var_expand_table_key_cmp(const char *key,
281
            const struct var_expand_table *elem)
282
0
{
283
0
  return strcmp(key, elem->key);
284
0
}
285
286
struct var_expand_table *
287
var_expand_merge_tables(pool_t pool, const struct var_expand_table *a,
288
          const struct var_expand_table *b)
289
0
{
290
0
  ARRAY(struct var_expand_table) table;
291
0
  size_t a_size = var_expand_table_size(a);
292
0
  size_t b_size = var_expand_table_size(b);
293
0
  p_array_init(&table, pool, a_size + b_size + 1);
294
0
  for (size_t i = 0; i < a_size; i++) {
295
0
    struct var_expand_table *entry =
296
0
      array_append_space(&table);
297
0
    entry->value = p_strdup(pool, a[i].value);
298
0
    entry->key = p_strdup(pool, a[i].key);
299
0
  }
300
0
  for (size_t i = 0; i < b_size; i++) {
301
    /* check if it's there first */
302
0
    struct var_expand_table *entry =
303
0
      array_lsearch_modifiable(&table, b[i].key,
304
0
             var_expand_table_key_cmp);
305
0
    if (entry != NULL) {
306
0
      entry->value = b->value;
307
0
      continue;
308
0
    }
309
310
0
    entry = array_append_space(&table);
311
0
    entry->value = p_strdup(pool, b[i].value);
312
0
    entry->key = p_strdup(pool, b[i].key);
313
0
  }
314
0
  array_append_zero(&table);
315
0
  return array_front_modifiable(&table);
316
0
}
317
318
void var_expand_state_set_transfer_data(struct var_expand_state *state,
319
          const void *value, size_t len)
320
0
{
321
  /* Ensure we are not using value from transfer data */
322
0
  i_assert((const char *)value < (const char *)state->transfer->data ||
323
0
     (const char *)value > (const char *)state->transfer->data +
324
0
                 state->transfer->used);
325
0
  str_truncate(state->transfer, 0);
326
0
  str_append_data(state->transfer, value, len);
327
0
  state->transfer_set = TRUE;
328
0
}
329
330
void var_expand_state_set_transfer_binary(struct var_expand_state *state,
331
            const void *value, size_t len)
332
0
{
333
0
  var_expand_state_set_transfer_data(state, value, len);
334
0
  state->transfer_binary = TRUE;
335
0
}
336
337
void var_expand_state_set_transfer(struct var_expand_state *state, const char *value)
338
0
{
339
0
  size_t len;
340
0
  if (value == NULL)
341
0
    len = 0;
342
0
  else
343
0
    len = strlen(value);
344
0
  var_expand_state_set_transfer_data(state, value, len);
345
0
  state->transfer_binary = FALSE;
346
0
}
347
348
void var_expand_state_unset_transfer(struct var_expand_state *state)
349
0
{
350
0
  str_truncate(state->transfer, 0);
351
0
  state->transfer_safe = FALSE;
352
0
  state->transfer_set = FALSE;
353
0
}
354
355
static int call_provider_table(const struct var_expand_provider *prov,
356
             void *prov_context,
357
             const char *prefix, const char *key,
358
             const char **value_r, bool *found_r,
359
             const char **error_r)
360
0
{
361
0
  i_assert(prov_context != var_expand_contexts_end);
362
0
  for (; prov != NULL && prov->key != NULL; prov++) {
363
0
    if (strcmp(prov->key, prefix) == 0) {
364
0
      *found_r = TRUE;
365
0
      return prov->func(key, value_r, prov_context, error_r);
366
0
    }
367
0
  }
368
0
  *found_r = FALSE;
369
0
  return -1;
370
0
}
371
372
static int call_value_provider(const struct var_expand_state *state,
373
             const char *prefix, const char *key,
374
             const char **value_r, const char **error_r)
375
0
{
376
0
  bool found;
377
0
  int ret = call_provider_table(internal_providers, (void*)state->params, prefix, key, value_r,
378
0
              &found, error_r);
379
0
  if (found)
380
0
    ; /* pass */
381
0
  else if (state->params->providers_arr != NULL) {
382
0
    void *context = state->params->context;
383
0
    void *const *contexts = state->params->contexts;
384
0
    for (const struct var_expand_provider *const *prov = state->params->providers_arr;
385
0
         *prov != NULL; prov++) {
386
0
      if (contexts != NULL) {
387
0
        context = *contexts;
388
0
        contexts++;
389
0
      }
390
0
      ret = call_provider_table(*prov, context, prefix, key, value_r,
391
0
              &found, error_r);
392
0
      if (found)
393
0
        break;
394
0
    }
395
0
  } else {
396
0
    ret = call_provider_table(state->params->providers,
397
0
            state->params->context,
398
0
            prefix, key, value_r, &found, error_r);
399
0
  }
400
401
0
  if (!found) {
402
0
    *error_r = t_strdup_printf("Unsupported prefix '%s'", prefix);
403
0
    ret = -1;
404
0
  } else if (ret == -1) {
405
    /* Add prefix to errors */
406
0
    *error_r = t_strdup_printf("%s: %s", prefix, *error_r);
407
0
  }
408
409
0
  i_assert(*value_r != NULL || ret == -1);
410
411
0
  return ret;
412
0
}
413
414
static int lookup_table(const struct var_expand_table *table,
415
      void *context, const char *name,
416
      const char **result_r, bool *found_r, const char **error_r)
417
0
{
418
0
  i_assert(context != var_expand_contexts_end);
419
0
  for (size_t i = 0; table != NULL && table[i].key != NULL; i++) {
420
0
    if (strcmp(table[i].key, name) == 0) {
421
0
      *found_r = TRUE;
422
0
      if (table[i].func != NULL) {
423
0
        int ret = table[i].func(name, result_r,
424
0
              context, error_r);
425
0
        i_assert(ret >= 0 || *error_r != NULL);
426
0
        return ret >= 0 ? 0 : -1;
427
0
      } else
428
0
        *result_r = table[i].value == NULL ? "" : table[i].value;
429
0
      return 0;
430
0
    }
431
0
  };
432
433
0
  *error_r = t_strdup_printf("Unknown variable '%s'", name);
434
0
  return -1;
435
0
}
436
437
static int lookup_tables(const struct var_expand_state *state, const char *name,
438
       const char **result_r, const char **error_r)
439
0
{
440
0
  int ret;
441
0
  bool found;
442
443
0
  if (state->params->tables_arr != NULL) {
444
0
    void *context = state->params->context;
445
0
    void *const *contexts = state->params->contexts;
446
0
    for (const struct var_expand_table *const *table = state->params->tables_arr;
447
0
         *table != NULL; table++) {
448
0
      if (contexts != NULL) {
449
0
        context = *contexts;
450
0
        contexts++;
451
0
      }
452
0
      found = FALSE;
453
0
      ret = lookup_table(*table, context, name, result_r, &found, error_r);
454
0
      if (found)
455
0
        return ret;
456
0
    }
457
0
    *error_r = t_strdup_printf("Unknown variable '%s'", name);
458
0
    return -1;
459
0
  }
460
461
0
  return lookup_table(state->params->table, state->params->context,
462
0
          name, result_r, &found, error_r);
463
0
}
464
465
int var_expand_state_lookup_variable(const struct var_expand_state *state,
466
             const char *name, const char **result_r,
467
             const char **error_r)
468
0
{
469
0
  const char *prefix = name;
470
0
  name = strchr(name, ':');
471
472
0
  if (name == NULL) {
473
0
    name = prefix;
474
0
    prefix = NULL;
475
0
  } else {
476
0
    prefix = t_strdup_until(prefix, name);
477
0
    name++;
478
0
  }
479
480
0
  if (prefix != NULL) {
481
0
    return call_value_provider(state, prefix, name, result_r, error_r);
482
0
  } else {
483
0
    return lookup_tables(state, name, result_r, error_r);
484
0
  }
485
0
}
486
487
int var_expand(string_t *dest, const char *str,
488
         const struct var_expand_params *params,
489
         const char **error_r)
490
0
{
491
0
  struct var_expand_program *program = NULL;
492
0
  if (var_expand_program_create(str, &program, error_r) != 0)
493
0
    return -1;
494
0
  i_assert(program != NULL);
495
0
  int ret = var_expand_program_execute(dest, program, params, error_r);
496
0
  var_expand_program_free(&program);
497
498
0
  return ret;
499
0
}
500
501
int t_var_expand(const char *str, const struct var_expand_params *params,
502
     const char **result_r, const char **error_r)
503
0
{
504
505
0
  string_t *dest = t_str_new(32);
506
0
  int ret = var_expand(dest, str, params, error_r);
507
0
  if (ret < 0)
508
0
    return ret;
509
0
  *result_r = str_c(dest);
510
0
  return 0;
511
0
}