Coverage Report

Created: 2026-10-13 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/arg.c
Line
Count
Source
1
/*
2
 * Functions used to parse typed argument lists
3
 *
4
 * Copyright 2012 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 <sys/types.h>
14
#include <sys/socket.h>
15
#include <arpa/inet.h>
16
17
#include <haproxy/arg.h>
18
#include <haproxy/chunk.h>
19
#include <haproxy/global.h>
20
#include <haproxy/regex.h>
21
#include <haproxy/tools.h>
22
23
const char *arg_type_names[ARGT_NBTYPES] = {
24
  [ARGT_STOP] = "end of arguments",
25
  [ARGT_SINT] = "integer",
26
  [ARGT_STR]  = "string",
27
  [ARGT_ID]   = "identifier",
28
  [ARGT_IPV4] = "IPv4 address",
29
  [ARGT_MSK4] = "IPv4 mask",
30
  [ARGT_IPV6] = "IPv6 address",
31
  [ARGT_MSK6] = "IPv6 mask",
32
  [ARGT_TIME] = "delay",
33
  [ARGT_SIZE] = "size",
34
  [ARGT_FE]   = "frontend",
35
  [ARGT_BE]   = "backend",
36
  [ARGT_TAB]  = "table",
37
  [ARGT_SRV]  = "server",
38
  [ARGT_USR]  = "user list",
39
  [ARGT_MAP]  = "map",
40
  [ARGT_REG]  = "regex",
41
  [ARGT_VAR]  = "variable",
42
  [ARGT_PBUF_FNUM] = "Protocol buffers field number",
43
  /* Unassigned types must never happen. Better crash during parsing if they do. */
44
};
45
46
/* This dummy arg list may be used by default when no arg is found, it helps
47
 * parsers by removing pointer checks.
48
 */
49
struct arg empty_arg_list[ARGM_NBARGS] = { };
50
51
/* This function clones a struct arg_list template into a new one which is
52
 * returned.
53
 */
54
struct arg_list *arg_list_clone(const struct arg_list *orig)
55
0
{
56
0
  struct arg_list *new;
57
58
0
  if ((new = calloc(1, sizeof(*new))) != NULL) {
59
    /* ->list will be set by the caller when inserting the element.
60
     * ->arg and ->arg_pos will be set by the caller.
61
     */
62
0
    new->ctx = orig->ctx;
63
0
    new->kw = orig->kw;
64
0
    new->conv = orig->conv;
65
0
    new->file = orig->file;
66
0
    new->line = orig->line;
67
0
  }
68
0
  return new;
69
0
}
70
71
/* This function clones a struct <arg_list> template into a new one which is
72
 * set to point to arg <arg> at pos <pos>, and which is returned if the caller
73
 * wants to apply further changes.
74
 */
75
struct arg_list *arg_list_add(struct arg_list *orig, struct arg *arg, int pos)
76
0
{
77
0
  struct arg_list *new;
78
79
0
  new = arg_list_clone(orig);
80
0
  if (new) {
81
0
    new->arg = arg;
82
0
    new->arg_pos = pos;
83
0
    LIST_APPEND(&orig->list, &new->list);
84
0
  }
85
0
  return new;
86
0
}
87
88
/* This function builds an argument list from a config line, and stops at the
89
 * first non-matching character, which is pointed to in <end_ptr>. A valid arg
90
 * list starts with an opening parenthesis '(', contains a number of comma-
91
 * delimited words, and ends with the closing parenthesis ')'. An empty list
92
 * (with or without the parenthesis) will lead to a valid empty argument if the
93
 * keyword has a mandatory one. The function returns the number of arguments
94
 * emitted, or <0 in case of any error. Everything needed it automatically
95
 * allocated. A pointer to an error message might be returned in err_msg if not
96
 * NULL, in which case it would be allocated and the caller will have to check
97
 * it and free it. The output arg list is returned in argp which must be valid.
98
 * The returned array is always terminated by an arg of type ARGT_STOP (0),
99
 * unless the mask indicates that no argument is supported. Unresolved arguments
100
 * are appended to arg list <al>, which also serves as a template to create new
101
 * entries. <al> may be NULL if unresolved arguments are not allowed. The mask
102
 * is composed of a number of mandatory arguments in its lower ARGM_BITS bits,
103
 * and a concatenation of each argument type in each subsequent ARGT_BITS-bit
104
 * sblock. If <err_msg> is not NULL, it must point to a freeable or NULL
105
 * pointer. The caller is expected to restart the parsing from the new pointer
106
 * set in <end_ptr>, which is the first character considered as not being part
107
 * of the arg list. The input string ends on the first between <len> characters
108
 * (when len is positive) or the first NUL character. Placing -1 in <len> will
109
 * make it virtually unbounded (~2GB long strings).
110
 */
111
int make_arg_list(const char *in, int len, uint64_t mask, struct arg **argp,
112
                  char **err_msg, const char **end_ptr, int *err_arg,
113
                  struct arg_list *al)
114
13.0k
{
115
13.0k
  int nbarg;
116
13.0k
  int pos;
117
13.0k
  struct arg *arg;
118
13.0k
  const char *beg;
119
13.0k
  const char *ptr_err = NULL;
120
13.0k
  int min_arg;
121
13.0k
  int empty;
122
13.0k
  struct arg_list *new_al = al;
123
124
13.0k
  *argp = NULL;
125
126
13.0k
  empty = 0;
127
13.0k
  if (!len || *in != '(') {
128
    /* it's already not for us, stop here */
129
895
    empty = 1;
130
895
    len = 0;
131
12.2k
  } else {
132
    /* skip opening parenthesis */
133
12.2k
    len--;
134
12.2k
    in++;
135
12.2k
  }
136
137
13.0k
  min_arg = mask & ARGM_MASK;
138
13.0k
  mask >>= ARGM_BITS;
139
140
13.0k
  pos = 0;
141
  /* find between 0 and NBARGS the max number of args supported by the mask */
142
27.5k
  for (nbarg = 0; nbarg < ARGM_NBARGS && ((mask >> (nbarg * ARGT_BITS)) & ARGT_MASK); nbarg++);
143
144
13.0k
  if (!nbarg)
145
201
    goto end_parse;
146
147
  /* Note: an empty input string contains an empty argument if this argument
148
   * is marked mandatory. Otherwise we can ignore it.
149
   */
150
12.8k
  if (empty && !min_arg)
151
0
    goto end_parse;
152
153
12.8k
  arg = *argp = calloc(nbarg + 1, sizeof(**argp));
154
155
12.8k
  if (!arg)
156
0
    goto alloc_err;
157
158
  /* Note: empty arguments after a comma always exist. */
159
14.3k
  while (pos < nbarg) {
160
14.3k
    unsigned int uint;
161
14.3k
    int squote = 0, dquote = 0;
162
14.3k
    char *out;
163
164
14.3k
    chunk_reset(&trash);
165
14.3k
    out = trash.area;
166
167
19.1M
    while (len && *in && trash.data < trash.size - 1) {
168
19.1M
      if (*in == '"' && !squote) {  /* double quote outside single quotes */
169
965
        if (dquote)
170
467
          dquote = 0;
171
498
        else
172
498
          dquote = 1;
173
965
        in++; len--;
174
965
        continue;
175
965
      }
176
19.1M
      else if (*in == '\'' && !dquote) { /* single quote outside double quotes */
177
1.92k
        if (squote)
178
944
          squote = 0;
179
980
        else
180
980
          squote = 1;
181
1.92k
        in++; len--;
182
1.92k
        continue;
183
1.92k
      }
184
19.1M
      else if (*in == '\\' && !squote && len != 1) {
185
        /* '\', ', ' ', '"' support being escaped by '\' */
186
3.73k
        if (in[1] == 0)
187
14
          goto unquote_err;
188
189
3.71k
        if (in[1] == '\\' || in[1] == ' ' || in[1] == '"' || in[1] == '\'') {
190
2.24k
          in++; len--;
191
2.24k
          *out++ = *in;
192
2.24k
        }
193
1.46k
        else if (in[1] == 'r') {
194
201
          in++; len--;
195
201
          *out++ = '\r';
196
201
        }
197
1.26k
        else if (in[1] == 'n') {
198
368
          in++; len--;
199
368
          *out++ = '\n';
200
368
        }
201
900
        else if (in[1] == 't') {
202
205
          in++; len--;
203
205
          *out++ = '\t';
204
205
        }
205
695
        else {
206
          /* just a lone '\' */
207
695
          *out++ = *in;
208
695
        }
209
3.71k
        in++; len--;
210
3.71k
      }
211
19.1M
      else {
212
19.1M
        if (!squote && !dquote && (*in == ',' || *in == ')')) {
213
          /* end of argument */
214
13.4k
          break;
215
13.4k
        }
216
        /* verbatim copy */
217
19.1M
        *out++ = *in++;
218
19.1M
        len--;
219
19.1M
      }
220
19.1M
      trash.data = out - trash.area;
221
19.1M
    }
222
223
14.3k
    if (len && *in && *in != ',' && *in != ')')
224
51
      goto buffer_err;
225
226
14.2k
    trash.area[trash.data] = 0;
227
228
14.2k
    arg->type = (mask >> (pos * ARGT_BITS)) & ARGT_MASK;
229
230
14.2k
    switch (arg->type) {
231
0
    case ARGT_SINT:
232
0
      if (!trash.data)   // empty number
233
0
        goto empty_err;
234
0
      beg = trash.area;
235
0
      arg->data.sint = read_int64(&beg, trash.area + trash.data);
236
0
      if (beg < trash.area + trash.data)
237
0
        goto parse_err;
238
0
      arg->type = ARGT_SINT;
239
0
      break;
240
241
0
    case ARGT_ID:
242
0
    case ARGT_FE:
243
0
    case ARGT_BE:
244
0
    case ARGT_TAB:
245
0
    case ARGT_SRV:
246
0
    case ARGT_USR:
247
0
    case ARGT_REG:
248
      /* These argument types need to be stored as strings during
249
       * parsing then resolved later.
250
       */
251
0
      if (!al)
252
0
        goto resolve_err;
253
0
      arg->unresolved = 1;
254
0
      new_al = arg_list_add(al, arg, pos);
255
0
      __fallthrough;
256
257
14.2k
    case ARGT_STR:
258
      /* all types that must be resolved are stored as strings
259
       * during the parsing. The caller must at one point resolve
260
       * them and free the string.
261
       */
262
14.2k
      arg->data.str.area = my_strndup(trash.area, trash.data);
263
14.2k
      arg->data.str.data = trash.data;
264
14.2k
      arg->data.str.size = trash.data + 1;
265
14.2k
      break;
266
267
0
    case ARGT_IPV4:
268
0
      if (!trash.data)    // empty address
269
0
        goto empty_err;
270
271
0
      if (inet_pton(AF_INET, trash.area, &arg->data.ipv4) <= 0)
272
0
        goto parse_err;
273
0
      break;
274
275
0
    case ARGT_MSK4:
276
0
      if (!trash.data)    // empty mask
277
0
        goto empty_err;
278
279
0
      if (!str2mask(trash.area, &arg->data.ipv4))
280
0
        goto parse_err;
281
282
0
      arg->type = ARGT_IPV4;
283
0
      break;
284
285
0
    case ARGT_IPV6:
286
0
      if (!trash.data)    // empty address
287
0
        goto empty_err;
288
289
0
      if (inet_pton(AF_INET6, trash.area, &arg->data.ipv6) <= 0)
290
0
        goto parse_err;
291
0
      break;
292
293
0
    case ARGT_MSK6:
294
0
      if (!trash.data)    // empty mask
295
0
        goto empty_err;
296
297
0
      if (!str2mask6(trash.area, &arg->data.ipv6))
298
0
        goto parse_err;
299
300
0
      arg->type = ARGT_IPV6;
301
0
      break;
302
303
0
    case ARGT_TIME:
304
0
      if (!trash.data)    // empty time
305
0
        goto empty_err;
306
307
0
      ptr_err = parse_time_err(trash.area, &uint, TIME_UNIT_MS);
308
0
      if (ptr_err) {
309
0
        if (ptr_err == PARSE_TIME_OVER || ptr_err == PARSE_TIME_UNDER)
310
0
          ptr_err = trash.area;
311
0
        goto parse_err;
312
0
      }
313
0
      arg->data.sint = uint;
314
0
      arg->type = ARGT_SINT;
315
0
      break;
316
317
0
    case ARGT_SIZE:
318
0
      if (!trash.data)    // empty size
319
0
        goto empty_err;
320
321
0
      ptr_err = parse_size_err(trash.area, &uint);
322
0
      if (ptr_err)
323
0
        goto parse_err;
324
325
0
      arg->data.sint = uint;
326
0
      arg->type = ARGT_SINT;
327
0
      break;
328
329
0
    case ARGT_PBUF_FNUM:
330
0
      if (!trash.data)
331
0
        goto empty_err;
332
333
0
      if (!parse_dotted_uints(trash.area, &arg->data.fid.ids, &arg->data.fid.sz))
334
0
        goto parse_err;
335
336
0
      break;
337
338
      /* FIXME: other types need to be implemented here */
339
0
    default:
340
0
      goto not_impl;
341
14.2k
    }
342
343
14.2k
    pos++;
344
14.2k
    arg++;
345
346
    /* don't go back to parsing if we reached end */
347
14.2k
    if (!len || !*in || *in == ')' || pos >= nbarg)
348
12.8k
      break;
349
350
    /* skip comma */
351
1.44k
    in++; len--;
352
1.44k
  }
353
354
13.0k
 end_parse:
355
13.0k
  if (pos < min_arg) {
356
    /* not enough arguments */
357
66
    memprintf(err_msg,
358
66
              "missing arguments (got %d/%d), type '%s' expected",
359
66
              pos, min_arg, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]);
360
66
    goto err;
361
66
  }
362
363
12.9k
  if (empty) {
364
    /* nothing to do */
365
12.0k
  } else if (*in == ')') {
366
    /* skip the expected closing parenthesis */
367
11.9k
    in++;
368
11.9k
  } else {
369
    /* the caller is responsible for freeing this message */
370
118
    char *word = (len > 0) ? my_strndup(in, len) : (char *)in;
371
372
118
    if (*word)
373
14
      memprintf(err_msg, "expected ')' before '%s'", word);
374
104
    else
375
104
      memprintf(err_msg, "expected ')'");
376
377
118
    if (len > 0)
378
0
      free(word);
379
    /* when we're missing a right paren, the empty part preceding
380
     * already created an empty arg, adding one to the position, so
381
     * let's fix the reporting to avoid being confusing.
382
     */
383
118
    if (pos > 1)
384
6
      pos--;
385
118
    goto err;
386
118
  }
387
388
  /* note that pos might be < nbarg and this is not an error, it's up to the
389
   * caller to decide what to do with optional args.
390
   */
391
12.8k
  if (err_arg)
392
12.8k
    *err_arg = pos;
393
12.8k
  if (end_ptr)
394
12.8k
    *end_ptr = in;
395
12.8k
  return pos;
396
397
249
 err:
398
249
  if (new_al == al) {
399
    /* only free the arg area if we have not queued unresolved args
400
     * still pointing to it.
401
     */
402
249
    free_args(*argp);
403
249
    free(*argp);
404
249
  }
405
249
  *argp = NULL;
406
249
  if (err_arg)
407
249
    *err_arg = pos;
408
249
  if (end_ptr)
409
249
    *end_ptr = in;
410
249
  return -1;
411
412
0
 empty_err:
413
  /* If we've only got an empty set of parenthesis with nothing
414
   * in between, there is no arg at all.
415
   */
416
0
  if (!pos) {
417
0
    ha_free(argp);
418
0
  }
419
420
0
  if (pos >= min_arg)
421
0
    goto end_parse;
422
423
0
  memprintf(err_msg, "expected type '%s' at position %d, but got nothing",
424
0
            arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1);
425
0
  goto err;
426
427
0
 parse_err:
428
  /* come here with the word attempted to parse in trash */
429
0
  memprintf(err_msg, "failed to parse '%s' as type '%s' at position %d",
430
0
            trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1);
431
0
  goto err;
432
433
0
 not_impl:
434
0
  memprintf(err_msg, "parsing for type '%s' was not implemented, please report this bug",
435
0
            arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]);
436
0
  goto err;
437
438
51
 buffer_err:
439
51
  memprintf(err_msg, "too small buffer size to store decoded argument %d, increase bufsize ?",
440
51
            pos + 1);
441
51
  goto err;
442
443
14
 unquote_err:
444
  /* come here with the parsed part in <trash.area>:<trash.data> and the
445
   * unparsable part in <in>.
446
   */
447
14
  trash.area[trash.data] = 0;
448
14
  memprintf(err_msg, "failed to parse '%s' after '%s' as type '%s' at position %d",
449
14
            in, trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1);
450
14
  goto err;
451
452
0
alloc_err:
453
0
  memprintf(err_msg, "out of memory");
454
0
  goto err;
455
456
0
 resolve_err:
457
0
  memprintf(err_msg, "unresolved argument of type '%s' at position %d not allowed",
458
0
            arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1);
459
0
  goto err;
460
0
}
461
462
/* Free all args of an args array, taking care of unresolved arguments as well.
463
 * It stops at the ARGT_STOP, which must be present. The array itself is not
464
 * freed, it's up to the caller to do it. However it is returned, allowing to
465
 * call free(free_args(argptr)). It is valid to call it with a NULL args, and
466
 * nothing will be done).
467
 */
468
struct arg *free_args(struct arg *args)
469
28.4k
{
470
28.4k
  struct arg *arg;
471
472
42.7k
  for (arg = args; arg && arg->type != ARGT_STOP; arg++) {
473
14.2k
    if (arg->type == ARGT_STR || arg->unresolved)
474
14.2k
      chunk_destroy(&arg->data.str);
475
0
    else if (arg->type == ARGT_REG)
476
0
      regex_free(arg->data.reg);
477
0
    else if (arg->type == ARGT_PBUF_FNUM)
478
      ha_free(&arg->data.fid.ids);
479
14.2k
  }
480
28.4k
  return args;
481
28.4k
}