Coverage Report

Created: 2026-04-03 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/cfgcond.c
Line
Count
Source
1
/*
2
 * Configuration condition preprocessor
3
 *
4
 * Copyright 2000-2021 Willy Tarreau <w@1wt.eu>
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version
9
 * 2 of the License, or (at your option) any later version.
10
 *
11
 */
12
13
#include <haproxy/api.h>
14
#include <haproxy/arg.h>
15
#include <haproxy/cfgcond.h>
16
#include <haproxy/proto_tcp.h>
17
#include <haproxy/tools.h>
18
#include <haproxy/version.h>
19
20
/* supported condition predicates */
21
const struct cond_pred_kw cond_predicates[] = {
22
  { "defined",                 CFG_PRED_DEFINED,                ARG1(1, STR)         },
23
  { "feature",                 CFG_PRED_FEATURE,                ARG1(1, STR)         },
24
  { "streq",                   CFG_PRED_STREQ,                  ARG2(2, STR, STR)    },
25
  { "strneq",                  CFG_PRED_STRNEQ,                 ARG2(2, STR, STR)    },
26
  { "strstr",                  CFG_PRED_STRSTR,                 ARG2(2, STR, STR)    },
27
  { "version_atleast",         CFG_PRED_VERSION_ATLEAST,        ARG1(1, STR)         },
28
  { "version_before",          CFG_PRED_VERSION_BEFORE,         ARG1(1, STR)         },
29
  { "openssl_version_atleast", CFG_PRED_OSSL_VERSION_ATLEAST,   ARG1(1, STR)         },
30
  { "openssl_version_before",  CFG_PRED_OSSL_VERSION_BEFORE,    ARG1(1, STR)         },
31
  { "ssllib_name_startswith",  CFG_PRED_SSLLIB_NAME_STARTSWITH, ARG1(1, STR)         },
32
  { "awslc_api_atleast",       CFG_PRED_AWSLC_API_ATLEAST,      ARG1(1, STR)         },
33
  { "awslc_api_before",        CFG_PRED_AWSLC_API_BEFORE,       ARG1(1, STR)         },
34
  { "enabled",                 CFG_PRED_ENABLED,                ARG1(1, STR)         },
35
  { NULL, CFG_PRED_NONE, 0 }
36
};
37
38
/* looks up a cond predicate matching the keyword in <str>, possibly followed
39
 * by a parenthesis. Returns a pointer to it or NULL if not found.
40
 */
41
const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
42
0
{
43
0
  const struct cond_pred_kw *ret;
44
0
  int len = strcspn(str, " (");
45
46
0
  for (ret = &cond_predicates[0]; ret->word; ret++) {
47
0
    if (len != strlen(ret->word))
48
0
      continue;
49
0
    if (strncmp(str, ret->word, len) != 0)
50
0
      continue;
51
0
    return ret;
52
0
  }
53
0
  return NULL;
54
0
}
55
56
/* Frees <term> and its args. NULL is supported and does nothing. */
57
void cfg_free_cond_term(struct cfg_cond_term *term)
58
0
{
59
0
  if (!term)
60
0
    return;
61
62
0
  if (term->type == CCTT_PAREN) {
63
0
    cfg_free_cond_expr(term->expr);
64
0
    term->expr = NULL;
65
0
  }
66
67
0
  free_args(term->args);
68
0
  free(term->args);
69
0
  free(term);
70
0
}
71
72
/* Parse an indirect input text as a possible config condition term.
73
 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
74
 * success. <term> is allocated and filled with the parsed info, and <text>
75
 * is updated on success to point to the first unparsed character, or is left
76
 * untouched on failure. On success, the caller must free <term> using
77
 * cfg_free_cond_term(). An error will be set in <err> on error, and only
78
 * in this case. In this case the first bad character will be reported in
79
 * <errptr>. <maxdepth> corresponds to the maximum recursion depth permitted,
80
 * it is decremented on each recursive call and the parsing will fail one
81
 * reaching <= 0.
82
 */
83
int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr, int maxdepth)
84
0
{
85
0
  struct cfg_cond_term *t;
86
0
  const char *in = *text;
87
0
  const char *end_ptr;
88
0
  int err_arg;
89
0
  int nbargs;
90
0
  char *end;
91
0
  long val;
92
93
0
  while (*in == ' ' || *in == '\t')
94
0
    in++;
95
96
0
  if (!*in) /* empty term does not parse */
97
0
    return 0;
98
99
0
  *term = NULL;
100
0
  if (maxdepth <= 0)
101
0
    goto fail0;
102
103
0
  t = *term = calloc(1, sizeof(**term));
104
0
  if (!t) {
105
0
    memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
106
0
    goto fail1;
107
0
  }
108
109
0
  t->type = CCTT_NONE;
110
0
  t->args = NULL;
111
0
  t->neg  = 0;
112
113
  /* !<term> negates the term. White spaces permitted */
114
0
  while (*in == '!') {
115
0
    t->neg = !t->neg;
116
0
    do { in++; } while (*in == ' ' || *in == '\t');
117
0
  }
118
119
0
  val = strtol(in, &end, 0);
120
0
  if (end != in) {
121
0
    t->type = val ? CCTT_TRUE : CCTT_FALSE;
122
0
    *text = end;
123
0
    return 1;
124
0
  }
125
126
  /* Try to parse '(' EXPR ')' */
127
0
  if (*in == '(') {
128
0
    int ret;
129
130
0
    t->type = CCTT_PAREN;
131
0
    t->args = NULL;
132
133
0
    do { in++; } while (*in == ' ' || *in == '\t');
134
0
    ret = cfg_parse_cond_expr(&in, &t->expr, err, errptr, maxdepth - 1);
135
0
    if (ret == -1)
136
0
      goto fail2;
137
0
    if (ret == 0)
138
0
      goto fail0;
139
140
    /* find the closing ')' */
141
0
    while (*in == ' ' || *in == '\t')
142
0
      in++;
143
0
    if (*in != ')') {
144
0
      memprintf(err, "expected ')' after conditional expression '%s'", *text);
145
0
      goto fail1;
146
0
    }
147
0
    do { in++; } while (*in == ' ' || *in == '\t');
148
0
    *text = in;
149
0
    return 1;
150
0
  }
151
152
  /* below we'll likely all make_arg_list() so we must return only via
153
   * the <done> label which frees the arg list.
154
   */
155
0
  t->pred = cfg_lookup_cond_pred(in);
156
0
  if (t->pred) {
157
0
    t->type = CCTT_PRED;
158
0
    nbargs = make_arg_list(in + strlen(t->pred->word), -1,
159
0
                           t->pred->arg_mask, &t->args, err,
160
0
                           &end_ptr, &err_arg, NULL);
161
0
    if (nbargs < 0) {
162
0
      memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, t->pred->word);
163
0
      if (errptr)
164
0
        *errptr = end_ptr;
165
0
      goto fail2;
166
0
    }
167
0
    *text = end_ptr;
168
0
    return 1;
169
0
  }
170
171
0
 fail0:
172
0
  memprintf(err, "unparsable conditional expression '%s'", *text);
173
0
 fail1:
174
0
  if (errptr)
175
0
    *errptr = *text;
176
0
 fail2:
177
0
  cfg_free_cond_term(*term);
178
0
  *term = NULL;
179
0
  return -1;
180
0
}
181
182
/* evaluate a "enabled" expression. Only a subset of options are matched. It
183
 * returns 1 if the option is enabled. 0 is returned is the option is not
184
 * enabled or if it is not recognized.
185
 */
186
static int cfg_eval_cond_enabled(const char *str)
187
0
{
188
0
  if (strcmp(str, "POLL") == 0)
189
0
    return !!(global.tune.options & GTUNE_USE_POLL);
190
0
  else if (strcmp(str, "EPOLL") == 0)
191
0
    return !!(global.tune.options & GTUNE_USE_EPOLL);
192
0
  else if (strcmp(str, "KQUEUE") == 0)
193
0
    return !!(global.tune.options & GTUNE_USE_EPOLL);
194
0
  else if (strcmp(str, "EVPORTS") == 0)
195
0
    return !!(global.tune.options & GTUNE_USE_EVPORTS);
196
0
  else if (strcmp(str, "SPLICE") == 0)
197
0
    return !!(global.tune.options & GTUNE_USE_SPLICE);
198
0
  else if (strcmp(str, "GETADDRINFO") == 0)
199
0
    return !!(global.tune.options & GTUNE_USE_GAI);
200
0
  else if (strcmp(str, "REUSEPORT") == 0)
201
0
    return !!(proto_tcpv4.flags & PROTO_F_REUSEPORT_SUPPORTED);
202
0
  else if (strcmp(str, "FAST-FORWARD") == 0)
203
0
    return !!(global.tune.options & GTUNE_USE_FAST_FWD);
204
0
  else if (strcmp(str, "SERVER-SSL-VERIFY-NONE") == 0)
205
0
    return !!(global.ssl_server_verify == SSL_SERVER_VERIFY_NONE);
206
0
  return 0;
207
0
}
208
209
/* evaluate a condition term on a .if/.elif line. The condition was already
210
 * parsed in <term>. Returns -1 on error (in which case err is filled with a
211
 * message, and only in this case), 0 if the condition is false, 1 if it's
212
 * true.
213
 */
214
int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
215
0
{
216
0
  int ret = -1;
217
218
0
  if (term->type == CCTT_FALSE)
219
0
    ret = 0;
220
0
  else if (term->type == CCTT_TRUE)
221
0
    ret = 1;
222
0
  else if (term->type == CCTT_PRED) {
223
    /* here we know we have a valid predicate with valid arguments
224
     * placed in term->args (which the caller will free).
225
     */
226
0
    switch (term->pred->prd) {
227
0
    case CFG_PRED_DEFINED:  // checks if arg exists as an environment variable
228
0
      ret = getenv(term->args[0].data.str.area) != NULL;
229
0
      break;
230
231
0
    case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
232
0
      const char *p;
233
234
0
      ret = 0; // assume feature not found
235
0
      for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
236
0
        if (p > build_features &&
237
0
            (p[term->args[0].data.str.data] == ' ' ||
238
0
             p[term->args[0].data.str.data] == 0)) {
239
0
          if (*(p-1) == '+') {       // e.g. "+OPENSSL"
240
0
            ret = 1;
241
0
            break;
242
0
          }
243
0
          else if (*(p-1) == '-') {  // e.g. "-OPENSSL"
244
0
            ret = 0;
245
0
            break;
246
0
          }
247
          /* it was a sub-word, let's restart from next place */
248
0
        }
249
0
      }
250
0
      break;
251
0
    }
252
0
    case CFG_PRED_STREQ:    // checks if the two arg are equal
253
0
      ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
254
0
      break;
255
256
0
    case CFG_PRED_STRNEQ:   // checks if the two arg are different
257
0
      ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
258
0
      break;
259
260
0
    case CFG_PRED_STRSTR:   // checks if the 2nd arg is found in the first one
261
0
      ret = strstr(term->args[0].data.str.area, term->args[1].data.str.area) != NULL;
262
0
      break;
263
264
0
    case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
265
0
      ret = compare_current_version(term->args[0].data.str.area) <= 0;
266
0
      break;
267
268
0
    case CFG_PRED_VERSION_BEFORE:  // checks if the current version is older than this one
269
0
      ret = compare_current_version(term->args[0].data.str.area) > 0;
270
0
      break;
271
272
0
    case CFG_PRED_OSSL_VERSION_ATLEAST: { // checks if the current openssl version is at least this one
273
0
      int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
274
275
0
      if (opensslret < -1) /* can't parse the string or no openssl available */
276
0
        ret = -1;
277
0
      else
278
0
        ret = opensslret <= 0;
279
0
      break;
280
0
    }
281
0
    case CFG_PRED_OSSL_VERSION_BEFORE: { // checks if the current openssl version is older than this one
282
0
      int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
283
284
0
      if (opensslret < -1) /* can't parse the string or no openssl available */
285
0
        ret = -1;
286
0
      else
287
0
        ret = opensslret > 0;
288
0
      break;
289
0
    }
290
0
    case CFG_PRED_AWSLC_API_ATLEAST: { // checks if the current AWSLC API is at least this one
291
0
      int awslcret = awslc_compare_current_api(term->args[0].data.str.area);
292
293
0
      if (awslcret < -1) /* can't parse the string or no AWS-LC available */
294
0
        ret = -1;
295
0
      else
296
0
        ret = awslcret <= 0;
297
0
      break;
298
0
    }
299
0
    case CFG_PRED_AWSLC_API_BEFORE: { // checks if the current AWSLC API is older than this one
300
0
      int awslcret = awslc_compare_current_api(term->args[0].data.str.area);
301
302
0
      if (awslcret < -1) /* can't parse the string or no AWS-LC available */
303
0
        ret = -1;
304
0
      else
305
0
        ret = awslcret > 0;
306
0
      break;
307
0
    }
308
0
    case CFG_PRED_SSLLIB_NAME_STARTSWITH: { // checks if the current SSL library's name starts with a specified string (can be used to distinguish OpenSSL from LibreSSL or BoringSSL)
309
0
      ret = openssl_compare_current_name(term->args[0].data.str.area) == 0;
310
0
      break;
311
0
    }
312
0
    case CFG_PRED_ENABLED: { // checks if the arg matches on a subset of enabled options
313
0
      ret = cfg_eval_cond_enabled(term->args[0].data.str.area) != 0;
314
0
      break;
315
0
    }
316
0
    default:
317
0
      memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
318
0
      break;
319
0
    }
320
0
  }
321
0
  else if (term->type == CCTT_PAREN) {
322
0
    ret = cfg_eval_cond_expr(term->expr, err);
323
0
  }
324
0
  else {
325
0
    memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
326
0
  }
327
328
0
  if (ret >= 0 && term->neg)
329
0
    ret = !ret;
330
0
  return ret;
331
0
}
332
333
334
/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
335
void cfg_free_cond_and(struct cfg_cond_and *expr)
336
0
{
337
0
  struct cfg_cond_and *prev;
338
339
0
  while (expr) {
340
0
    cfg_free_cond_term(expr->left);
341
0
    prev = expr;
342
0
    expr = expr->right;
343
0
    free(prev);
344
0
  }
345
0
}
346
347
/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
348
void cfg_free_cond_expr(struct cfg_cond_expr *expr)
349
0
{
350
0
  struct cfg_cond_expr *prev;
351
352
0
  while (expr) {
353
0
    cfg_free_cond_and(expr->left);
354
0
    prev = expr;
355
0
    expr = expr->right;
356
0
    free(prev);
357
0
  }
358
0
}
359
360
/* Parse an indirect input text as a possible config condition sub-expr.
361
 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
362
 * success. <expr> is filled with the parsed info, and <text> is updated on
363
 * success to point to the first unparsed character, or is left untouched
364
 * on failure. On success, the caller will have to free all lower-level
365
 * allocated structs using cfg_free_cond_expr(). An error will be set in
366
 * <err> on error, and only in this case. In this case the first bad
367
 * character will be reported in <errptr>. <maxdepth> corresponds to the
368
 * maximum recursion depth permitted, it is decremented on each recursive
369
 * call and the parsing will fail one reaching <= 0.
370
 */
371
int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr, int maxdepth)
372
0
{
373
0
  struct cfg_cond_and *e;
374
0
  const char *in = *text;
375
0
  int ret = -1;
376
377
0
  if (!*in) /* empty expr does not parse */
378
0
    return 0;
379
380
0
  *expr = NULL;
381
0
  if (maxdepth <= 0) {
382
0
    memprintf(err, "unparsable conditional sub-expression '%s'", in);
383
0
    if (errptr)
384
0
      *errptr = in;
385
0
    goto done;
386
0
  }
387
388
0
  e = *expr = calloc(1, sizeof(**expr));
389
0
  if (!e) {
390
0
    memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
391
0
    goto done;
392
0
  }
393
394
0
  ret = cfg_parse_cond_term(&in, &e->left, err, errptr, maxdepth - 1);
395
0
  if (ret == -1) // parse error, error already reported
396
0
    goto done;
397
398
0
  if (ret == 0) {
399
    /* ret == 0, no other way to parse this */
400
0
    memprintf(err, "unparsable conditional sub-expression '%s'", in);
401
0
    if (errptr)
402
0
      *errptr = in;
403
0
    ret = -1;
404
0
    goto done;
405
0
  }
406
407
  /* ret=1, we have a term in the left hand set */
408
409
  /* find an optional '&&' */
410
0
  while (*in == ' ' || *in == '\t')
411
0
    in++;
412
413
0
  *text = in;
414
0
  if (in[0] != '&' || in[1] != '&')
415
0
    goto done;
416
417
  /* we have a '&&', let's parse the right handset's subexp */
418
0
  in += 2;
419
0
  while (*in == ' ' || *in == '\t')
420
0
    in++;
421
422
0
  ret = cfg_parse_cond_and(&in, &e->right, err, errptr, maxdepth - 1);
423
0
  if (ret > 0)
424
0
    *text = in;
425
0
 done:
426
0
  if (ret < 0) {
427
0
    cfg_free_cond_and(*expr);
428
0
    *expr = NULL;
429
0
  }
430
0
  return ret;
431
0
}
432
433
/* Parse an indirect input text as a possible config condition term.
434
 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
435
 * success. <expr> is filled with the parsed info, and <text> is updated on
436
 * success to point to the first unparsed character, or is left untouched
437
 * on failure. On success, the caller will have to free all lower-level
438
 * allocated structs using cfg_free_cond_expr(). An error will be set in
439
 * <err> on error, and only in this case. In this case the first bad
440
 * character will be reported in <errptr>. <maxdepth> corresponds to the
441
 * maximum recursion depth permitted, it is decremented on each recursive call
442
 * and the parsing will fail one reaching <= 0.
443
 */
444
int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr, int maxdepth)
445
0
{
446
0
  struct cfg_cond_expr *e;
447
0
  const char *in = *text;
448
0
  int ret = -1;
449
450
0
  if (!*in) /* empty expr does not parse */
451
0
    return 0;
452
453
0
  *expr = NULL;
454
0
  if (maxdepth <= 0) {
455
0
    memprintf(err, "unparsable conditional expression '%s'", in);
456
0
    if (errptr)
457
0
      *errptr = in;
458
0
    goto done;
459
0
  }
460
461
0
  e = *expr = calloc(1, sizeof(**expr));
462
0
  if (!e) {
463
0
    memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
464
0
    goto done;
465
0
  }
466
467
0
  ret = cfg_parse_cond_and(&in, &e->left, err, errptr, maxdepth - 1);
468
0
  if (ret == -1) // parse error, error already reported
469
0
    goto done;
470
471
0
  if (ret == 0) {
472
    /* ret == 0, no other way to parse this */
473
0
    memprintf(err, "unparsable conditional expression '%s'", in);
474
0
    if (errptr)
475
0
      *errptr = in;
476
0
    ret = -1;
477
0
    goto done;
478
0
  }
479
480
  /* ret=1, we have a sub-expr in the left hand set */
481
482
  /* find an optional '||' */
483
0
  while (*in == ' ' || *in == '\t')
484
0
    in++;
485
486
0
  *text = in;
487
0
  if (in[0] != '|' || in[1] != '|')
488
0
    goto done;
489
490
  /* we have a '||', let's parse the right handset's subexp */
491
0
  in += 2;
492
0
  while (*in == ' ' || *in == '\t')
493
0
    in++;
494
495
0
  ret = cfg_parse_cond_expr(&in, &e->right, err, errptr, maxdepth - 1);
496
0
  if (ret > 0)
497
0
    *text = in;
498
0
 done:
499
0
  if (ret < 0) {
500
0
    cfg_free_cond_expr(*expr);
501
0
    *expr = NULL;
502
0
  }
503
0
  return ret;
504
0
}
505
506
/* evaluate an sub-expression on a .if/.elif line. The expression is valid and
507
 * was already parsed in <expr>. Returns -1 on error (in which case err is
508
 * filled with a message, and only in this case), 0 if the condition is false,
509
 * 1 if it's true.
510
 */
511
int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err)
512
0
{
513
0
  int ret;
514
515
  /* AND: loop on terms and sub-exp's terms as long as they're TRUE
516
   * (stop on FALSE and ERROR).
517
   */
518
0
  while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right)
519
0
    expr = expr->right;
520
0
  return ret;
521
0
}
522
523
/* evaluate an expression on a .if/.elif line. The expression is valid and was
524
 * already parsed in <expr>. Returns -1 on error (in which case err is filled
525
 * with a message, and only in this case), 0 if the condition is false, 1 if
526
 * it's true.
527
 */
528
int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err)
529
0
{
530
0
  int ret;
531
532
  /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */
533
0
  while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right)
534
0
    expr = expr->right;
535
0
  return ret;
536
0
}
537
538
/* evaluate a condition on a .if/.elif line. The condition is already tokenized
539
 * in <err>. Returns -1 on error (in which case err is filled with a message,
540
 * and only in this case), 0 if the condition is false, 1 if it's true. If
541
 * <errptr> is not NULL, it's set to the first invalid character on error.
542
 */
543
int cfg_eval_condition(char **args, char **err, const char **errptr)
544
0
{
545
0
  struct cfg_cond_expr *expr = NULL;
546
0
  const char *text = args[0];
547
0
  int ret = -1;
548
549
0
  if (!*text) /* note: empty = false */
550
0
    return 0;
551
552
0
  ret = cfg_parse_cond_expr(&text, &expr, err, errptr, MAX_CFG_RECURSION);
553
0
  if (ret != 0) {
554
0
    if (ret == -1) // parse error, error already reported
555
0
      goto done;
556
0
    while (*text == ' ' || *text == '\t')
557
0
      text++;
558
559
0
    if (*text) {
560
0
      ret = -1;
561
0
      memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'",
562
0
          *text, args[0]);
563
0
      goto fail;
564
0
    }
565
566
0
    ret = cfg_eval_cond_expr(expr, err);
567
0
    goto done;
568
0
  }
569
570
  /* ret == 0, no other way to parse this */
571
0
  ret = -1;
572
0
  memprintf(err, "unparsable conditional expression '%s'", args[0]);
573
0
 fail:
574
0
  if (errptr)
575
0
    *errptr = text;
576
0
 done:
577
0
  cfg_free_cond_expr(expr);
578
0
  return ret;
579
0
}