Coverage Report

Created: 2026-06-09 06:49

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) 2024 Dovecot authors, see the included 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
  struct tm tm;
200
0
  struct timeval tv;
201
0
  i_gettimeofday(&tv);
202
0
  if (unlikely(localtime_r(&tv.tv_sec, &tm) == NULL))
203
0
    i_panic("localtime_r() failed: %m");
204
205
0
  if (strcmp(key, "hour") == 0)
206
0
    *value_r = t_strftime("%H", &tm);
207
0
  else if (strcmp(key, "min") == 0 ||
208
0
     strcmp(key, "minute") == 0)
209
0
    *value_r = t_strftime("%M", &tm);
210
0
  else if (strcmp(key, "sec") == 0 ||
211
0
     strcmp(key, "second") == 0)
212
0
    *value_r = t_strftime("%S", &tm);
213
0
  else if (strcmp(key, "us") == 0 ||
214
0
     strcmp(key, "usec") == 0)
215
0
    *value_r = dec2str(tv.tv_usec);
216
0
  else
217
0
    ERROR_UNSUPPORTED_KEY(key);
218
0
  return 0;
219
0
}
220
221
static int var_expand_generate(const char *key, const char **value_r,
222
             void *context ATTR_UNUSED, const char **error_r)
223
0
{
224
0
  guid_128_t guid;
225
226
0
  if (strcmp(key, "guid") == 0) {
227
0
    *value_r = guid_generate();
228
0
    return 0;
229
0
  }
230
0
  if (strcmp(key, "guid128") == 0) {
231
0
    guid_128_generate(guid);
232
0
    *value_r = guid_128_to_string(guid);
233
0
    return 0;
234
0
  }
235
0
  if (str_begins(key, "uuid", &key)) {
236
0
    guid_128_uuid4_generate(guid);
237
0
    if (key[0] == '\0' || strcmp(key, ":record") == 0)
238
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_RECORD);
239
0
    else if (strcmp(key, ":compact") == 0)
240
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_COMPACT);
241
0
    else if (strcmp(key, ":microsoft") == 0)
242
0
      *value_r = guid_128_to_uuid_string(guid, FORMAT_MICROSOFT);
243
0
    else
244
0
      ERROR_UNSUPPORTED_KEY(key);
245
0
    return 0;
246
0
  }
247
0
  ERROR_UNSUPPORTED_KEY(key);
248
0
}
249
250
static const struct var_expand_provider internal_providers[] = {
251
  { .key = "process", .func = var_expand_process },
252
  { .key = "system", .func = var_expand_system  },
253
  { .key = "dovecot", .func = var_expand_dovecot  },
254
  { .key = "env", .func = var_expand_env },
255
  { .key = "event", .func = var_expand_event },
256
  { .key = "date", .func = var_expand_date },
257
  { .key = "time", .func = var_expand_time },
258
  { .key = "generate", .func = var_expand_generate },
259
  VAR_EXPAND_TABLE_END
260
};
261
262
bool var_expand_provider_is_builtin(const char *prefix)
263
0
{
264
0
  for (size_t i = 0; internal_providers[i].key != NULL; i++)
265
0
    if (strcmp(prefix, internal_providers[i].key) == 0)
266
0
      return TRUE;
267
0
  return FALSE;
268
0
}
269
270
static int var_expand_table_key_cmp(const char *key,
271
            const struct var_expand_table *elem)
272
0
{
273
0
  return strcmp(key, elem->key);
274
0
}
275
276
struct var_expand_table *
277
var_expand_merge_tables(pool_t pool, const struct var_expand_table *a,
278
          const struct var_expand_table *b)
279
0
{
280
0
  ARRAY(struct var_expand_table) table;
281
0
  size_t a_size = var_expand_table_size(a);
282
0
  size_t b_size = var_expand_table_size(b);
283
0
  p_array_init(&table, pool, a_size + b_size + 1);
284
0
  for (size_t i = 0; i < a_size; i++) {
285
0
    struct var_expand_table *entry =
286
0
      array_append_space(&table);
287
0
    entry->value = p_strdup(pool, a[i].value);
288
0
    entry->key = p_strdup(pool, a[i].key);
289
0
  }
290
0
  for (size_t i = 0; i < b_size; i++) {
291
    /* check if it's there first */
292
0
    struct var_expand_table *entry =
293
0
      array_lsearch_modifiable(&table, b[i].key,
294
0
             var_expand_table_key_cmp);
295
0
    if (entry != NULL) {
296
0
      entry->value = b->value;
297
0
      continue;
298
0
    }
299
300
0
    entry = array_append_space(&table);
301
0
    entry->value = p_strdup(pool, b[i].value);
302
0
    entry->key = p_strdup(pool, b[i].key);
303
0
  }
304
0
  array_append_zero(&table);
305
0
  return array_front_modifiable(&table);
306
0
}
307
308
void var_expand_state_set_transfer_data(struct var_expand_state *state,
309
          const void *value, size_t len)
310
0
{
311
  /* Ensure we are not using value from transfer data */
312
0
  i_assert((const char *)value < (const char *)state->transfer->data ||
313
0
     (const char *)value > (const char *)state->transfer->data +
314
0
                 state->transfer->used);
315
0
  str_truncate(state->transfer, 0);
316
0
  str_append_data(state->transfer, value, len);
317
0
  state->transfer_set = TRUE;
318
0
}
319
320
void var_expand_state_set_transfer_binary(struct var_expand_state *state,
321
            const void *value, size_t len)
322
0
{
323
0
  var_expand_state_set_transfer_data(state, value, len);
324
0
  state->transfer_binary = TRUE;
325
0
}
326
327
void var_expand_state_set_transfer(struct var_expand_state *state, const char *value)
328
0
{
329
0
  size_t len;
330
0
  if (value == NULL)
331
0
    len = 0;
332
0
  else
333
0
    len = strlen(value);
334
0
  var_expand_state_set_transfer_data(state, value, len);
335
0
  state->transfer_binary = FALSE;
336
0
}
337
338
void var_expand_state_unset_transfer(struct var_expand_state *state)
339
0
{
340
0
  str_truncate(state->transfer, 0);
341
0
  state->transfer_safe = FALSE;
342
0
  state->transfer_set = FALSE;
343
0
}
344
345
static int call_provider_table(const struct var_expand_provider *prov,
346
             void *prov_context,
347
             const char *prefix, const char *key,
348
             const char **value_r, bool *found_r,
349
             const char **error_r)
350
0
{
351
0
  i_assert(prov_context != var_expand_contexts_end);
352
0
  for (; prov != NULL && prov->key != NULL; prov++) {
353
0
    if (strcmp(prov->key, prefix) == 0) {
354
0
      *found_r = TRUE;
355
0
      return prov->func(key, value_r, prov_context, error_r);
356
0
    }
357
0
  }
358
0
  *found_r = FALSE;
359
0
  return -1;
360
0
}
361
362
static int call_value_provider(const struct var_expand_state *state,
363
             const char *prefix, const char *key,
364
             const char **value_r, const char **error_r)
365
0
{
366
0
  bool found;
367
0
  int ret = call_provider_table(internal_providers, (void*)state->params, prefix, key, value_r,
368
0
              &found, error_r);
369
0
  if (found)
370
0
    ; /* pass */
371
0
  else if (state->params->providers_arr != NULL) {
372
0
    void *context = state->params->context;
373
0
    void *const *contexts = state->params->contexts;
374
0
    for (const struct var_expand_provider *const *prov = state->params->providers_arr;
375
0
         *prov != NULL; prov++) {
376
0
      if (contexts != NULL) {
377
0
        context = *contexts;
378
0
        contexts++;
379
0
      }
380
0
      ret = call_provider_table(*prov, context, prefix, key, value_r,
381
0
              &found, error_r);
382
0
      if (found)
383
0
        break;
384
0
    }
385
0
  } else {
386
0
    ret = call_provider_table(state->params->providers,
387
0
            state->params->context,
388
0
            prefix, key, value_r, &found, error_r);
389
0
  }
390
391
0
  if (!found) {
392
0
    *error_r = t_strdup_printf("Unsupported prefix '%s'", prefix);
393
0
    ret = -1;
394
0
  } else if (ret == -1) {
395
    /* Add prefix to errors */
396
0
    *error_r = t_strdup_printf("%s: %s", prefix, *error_r);
397
0
  }
398
399
0
  i_assert(*value_r != NULL || ret == -1);
400
401
0
  return ret;
402
0
}
403
404
static int lookup_table(const struct var_expand_table *table,
405
      void *context, const char *name,
406
      const char **result_r, bool *found_r, const char **error_r)
407
0
{
408
0
  i_assert(context != var_expand_contexts_end);
409
0
  for (size_t i = 0; table != NULL && table[i].key != NULL; i++) {
410
0
    if (strcmp(table[i].key, name) == 0) {
411
0
      *found_r = TRUE;
412
0
      if (table[i].func != NULL) {
413
0
        int ret = table[i].func(name, result_r,
414
0
              context, error_r);
415
0
        i_assert(ret >= 0 || *error_r != NULL);
416
0
        return ret >= 0 ? 0 : -1;
417
0
      } else
418
0
        *result_r = table[i].value == NULL ? "" : table[i].value;
419
0
      return 0;
420
0
    }
421
0
  };
422
423
0
  *error_r = t_strdup_printf("Unknown variable '%s'", name);
424
0
  return -1;
425
0
}
426
427
static int lookup_tables(const struct var_expand_state *state, const char *name,
428
       const char **result_r, const char **error_r)
429
0
{
430
0
  int ret;
431
0
  bool found;
432
433
0
  if (state->params->tables_arr != NULL) {
434
0
    void *context = state->params->context;
435
0
    void *const *contexts = state->params->contexts;
436
0
    for (const struct var_expand_table *const *table = state->params->tables_arr;
437
0
         *table != NULL; table++) {
438
0
      if (contexts != NULL) {
439
0
        context = *contexts;
440
0
        contexts++;
441
0
      }
442
0
      found = FALSE;
443
0
      ret = lookup_table(*table, context, name, result_r, &found, error_r);
444
0
      if (found)
445
0
        return ret;
446
0
    }
447
0
    *error_r = t_strdup_printf("Unknown variable '%s'", name);
448
0
    return -1;
449
0
  }
450
451
0
  return lookup_table(state->params->table, state->params->context,
452
0
          name, result_r, &found, error_r);
453
0
}
454
455
int var_expand_state_lookup_variable(const struct var_expand_state *state,
456
             const char *name, const char **result_r,
457
             const char **error_r)
458
0
{
459
0
  const char *prefix = name;
460
0
  name = strchr(name, ':');
461
462
0
  if (name == NULL) {
463
0
    name = prefix;
464
0
    prefix = NULL;
465
0
  } else {
466
0
    prefix = t_strdup_until(prefix, name);
467
0
    name++;
468
0
  }
469
470
0
  if (prefix != NULL) {
471
0
    return call_value_provider(state, prefix, name, result_r, error_r);
472
0
  } else {
473
0
    return lookup_tables(state, name, result_r, error_r);
474
0
  }
475
0
}
476
477
int var_expand(string_t *dest, const char *str,
478
         const struct var_expand_params *params,
479
         const char **error_r)
480
0
{
481
0
  struct var_expand_program *program = NULL;
482
0
  if (var_expand_program_create(str, &program, error_r) != 0)
483
0
    return -1;
484
0
  i_assert(program != NULL);
485
0
  int ret = var_expand_program_execute(dest, program, params, error_r);
486
0
  var_expand_program_free(&program);
487
488
0
  return ret;
489
0
}
490
491
int t_var_expand(const char *str, const struct var_expand_params *params,
492
     const char **result_r, const char **error_r)
493
0
{
494
495
0
  string_t *dest = t_str_new(32);
496
0
  int ret = var_expand(dest, str, params, error_r);
497
0
  if (ret < 0)
498
0
    return ret;
499
0
  *result_r = str_c(dest);
500
0
  return 0;
501
0
}