Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensips/parser/parse_security.c
Line
Count
Source
1
/*
2
 * Security-{Client,Server,Verify} header field body parser
3
 *
4
 * Copyright (c) 2024 OpenSIPS Solutions
5
 *
6
 * This file is part of opensips, a free SIP server.
7
 *
8
 * opensips is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version
12
 *
13
 * opensips is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 */
22
23
#include "../ut.h"
24
#include "../mem/mem.h"
25
#include "parse_security.h"
26
27
static void free_sec_agree_body(sec_agree_body_t *s)
28
0
{
29
0
  sec_agree_param_t *it, *next;
30
0
  for (it = s->params; it; it = next) {
31
0
    next = it->next;
32
0
    pkg_free(it);
33
0
  }
34
0
  s->params = NULL;
35
0
  pkg_free(s);
36
0
}
37
38
39
enum sec_agree_state {
40
  SEC_AGREE_STATE_INVALID,
41
  SEC_AGREE_STATE_START,
42
  SEC_AGREE_STATE_MECHANISM,
43
  SEC_AGREE_STATE_PARAM_NAME_START,
44
  SEC_AGREE_STATE_PARAM_NAME,
45
  SEC_AGREE_STATE_PARAM_START_VALUE,
46
  SEC_AGREE_STATE_PARAM_VALUE,
47
  SEC_AGREE_STATE_PARAM_VALUE_ENC,
48
  SEC_AGREE_STATE_PARAM_VALUE_END,
49
};
50
51
52
void free_sec_agree(sec_agree_body_t **_s)
53
0
{
54
0
  sec_agree_body_t *bit;
55
0
  bit = *_s;
56
0
  while (bit) {
57
0
    *_s = (*_s)->next;
58
0
    free_sec_agree_body(bit);
59
0
    bit = *_s;
60
0
  }
61
0
}
62
63
sec_agree_mechanism_t parse_sec_agree_mechanism(str *mech)
64
0
{
65
0
  LM_DBG("new mechanism %.*s\n", mech->len, mech->s);
66
  /* 3GPP TS 33.203 */
67
0
  if (str_match(mech, _str("ipsec-3gpp")))
68
0
    return SEC_AGREE_MECHANISM_IPSEC_3GPP;
69
  /* RFC 3329 */
70
0
  if (str_match(mech, _str("tls")))
71
0
    return SEC_AGREE_MECHANISM_TLS;
72
0
  if (str_match(mech, _str("digest")))
73
0
    return SEC_AGREE_MECHANISM_DIGEST;
74
0
  if (str_match(mech, _str("ipsec-ike")))
75
0
    return SEC_AGREE_MECHANISM_IPSEC_IKE;
76
0
  if (str_match(mech, _str("ipsec-man")))
77
0
    return SEC_AGREE_MECHANISM_IPSEC_IKE;
78
0
  return SEC_AGREE_MECHANISM_OTHER;
79
0
}
80
81
int parse_sec_agree_parameter_pref(str *value)
82
0
{
83
0
  int i, val = 0;
84
0
  trim(value);
85
0
  if (value->len < 1)
86
0
    goto error;
87
0
  if (value->s[0] == '1') {
88
0
    if (value->len > 1) {
89
0
      if (value->s[1] != '.')
90
0
        goto error;
91
0
      for (i = 2; i < value->len; i++)
92
0
        if (value->s[i] != '0')
93
0
          goto error;
94
0
    }
95
0
    return 1000;
96
0
  } else if (value->s[0] != '0') {
97
0
    goto error;
98
0
  }
99
0
  if (value->len > 1 && value->s[1] != '.')
100
0
    goto error;
101
0
  for (i = 2; i < value->len; i++) {
102
0
    if (value->s[i] < '0' || value->s[i] > '9')
103
0
      goto error;
104
0
    val = 10 * val + (value->s[i] - '0');
105
0
  }
106
0
  val = val * 100;
107
0
  return val;
108
0
error:
109
0
  LM_ERR("invalid preference value [%.*s]\n", value->len, value->s);
110
0
  return -1;
111
0
}
112
113
int parse_sec_agree_parameter(sec_agree_body_t *sa, str *name, str *value)
114
0
{
115
0
  char *err;
116
0
  sec_agree_param_t *sp = NULL;
117
0
  static sec_agree_param_t *last_sp = NULL;
118
0
  unsigned int tmp;
119
120
0
  LM_DBG("parsing %p[%.*s] [%.*s]=[%.*s]\n", sa, sa->mechanism_str.len,
121
0
      sa->mechanism_str.s, name->len, name->s, value->len, value->s);
122
123
0
  switch (sa->mechanism) {
124
0
  case SEC_AGREE_MECHANISM_IPSEC_3GPP:
125
0
    if (str_match(name, _str("q"))) {
126
0
      sa->ts3gpp.pref_str = *value;
127
0
      sa->ts3gpp.pref = parse_sec_agree_parameter_pref(value);
128
0
      if (sa->ts3gpp.pref < 0) {
129
0
        err = "invalid preference value";
130
0
        goto error;
131
0
      }
132
0
      return 0;
133
0
    }
134
    /* TODO: do we need to parse these fields? */
135
0
    if (str_match(name, _str("alg"))) {
136
0
      sa->ts3gpp.alg_str = *value;
137
0
      return 0;
138
0
    }
139
0
    if (str_match(name, _str("mod"))) {
140
0
      sa->ts3gpp.mod_str = *value;
141
0
      return 0;
142
0
    }
143
0
    if (str_match(name, _str("ealg"))) {
144
0
      sa->ts3gpp.ealg_str = *value;
145
0
      return 0;
146
0
    }
147
0
    if (str_match(name, _str("spi-c"))) {
148
0
      sa->ts3gpp.spi_c_str = *value;
149
0
      if (str2int(value, &sa->ts3gpp.spi_c) < 0) {
150
0
        err = "invalid spi-c value";
151
0
        goto error;
152
0
      }
153
0
      return 0;
154
0
    }
155
0
    if (str_match(name, _str("spi-s"))) {
156
0
      sa->ts3gpp.spi_s_str = *value;
157
0
      if (str2int(value, &sa->ts3gpp.spi_s) < 0) {
158
0
        err = "invalid spi-s value";
159
0
        goto error;
160
0
      }
161
0
      return 0;
162
0
    }
163
0
    if (str_match(name, _str("port-c"))) {
164
0
      sa->ts3gpp.port_c_str = *value;
165
0
      if (str2int(value, &tmp) < 0 || tmp == 0 || tmp > 65535) {
166
0
        err = "invalid port-c value";
167
0
        goto error;
168
0
      }
169
0
      sa->ts3gpp.port_c = (unsigned short)tmp;
170
0
      return 0;
171
0
    }
172
0
    if (str_match(name, _str("port-s"))) {
173
0
      sa->ts3gpp.port_s_str = *value;
174
0
      if (str2int(value, &tmp) < 0 || tmp == 0 || tmp > 65535) {
175
0
        err = "invalid port-s value";
176
0
        goto error;
177
0
      }
178
0
      sa->ts3gpp.port_s = (unsigned short)tmp;
179
0
      return 0;
180
0
    }
181
0
    break;
182
0
  default:
183
    /* treat all the other the same */
184
0
    if (str_match(name, _str("q"))) {
185
0
      sa->def.preference_str = *value;
186
0
      sa->def.preference = parse_sec_agree_parameter_pref(value);
187
0
      if (sa->def.preference < 0) {
188
0
        err = "invalid preference value";
189
0
        goto error;
190
0
      }
191
0
      return 0;
192
0
    }
193
0
    if (str_match(name, _str("d-alg"))) {
194
0
      sa->def.algorithm_str = *value;
195
0
      return 0;
196
0
    }
197
0
    if (str_match(name, _str("d-qop"))) {
198
0
      sa->def.qop_str = *value;
199
0
      return 0;
200
0
    }
201
0
    if (str_match(name, _str("d-ver"))) {
202
0
      sa->def.verify_str = *value;
203
0
      return 0;
204
0
    }
205
0
    break;
206
0
  }
207
  
208
0
  sp = pkg_malloc(sizeof *sp);
209
0
  if (!sp) {
210
0
    err = "oom for extra sec-agree param";
211
0
    goto error;
212
0
  }
213
0
  memset(sp, 0, sizeof *sp);
214
0
  sp->name = *name;
215
0
  sp->value = *value;
216
0
  if (sa->params)
217
0
    last_sp->next = sp;
218
0
  else
219
0
    sa->params = sp;
220
0
  last_sp = sp;
221
0
    LM_DBG("adding extra param %p [%.*s]=[%.*s]\n", sa, sp->name.len, sp->name.s,
222
0
      sp->value.len, sp->value.s);
223
0
  return 0;
224
0
error:
225
0
  LM_ERR("%s %.*s=%.*s\n", err, name->len, name->s, value->len, value->s);
226
0
  return -1;
227
0
}
228
229
#define SEC_AGREE_FILL_MECHANISM() \
230
0
  do { \
231
0
    sa->mechanism_str.s = s; \
232
0
    sa->mechanism_str.len = p - s; \
233
0
    trim(&sa->mechanism_str); \
234
0
    if (sa->mechanism_str.len <= 0) { \
235
0
      LM_ERR("invalid sec-agree mechanism\n"); \
236
0
      goto free_sa; \
237
0
    } \
238
0
    sa->mechanism = parse_sec_agree_mechanism(&sa->mechanism_str); \
239
0
    if (!last) \
240
0
      first = sa; \
241
0
    else \
242
0
      last->next = sa; \
243
0
    last = sa; \
244
0
    if (*p == ',') { \
245
0
      state = SEC_AGREE_STATE_START; \
246
0
      sa = NULL; \
247
0
    } else { \
248
0
      state = SEC_AGREE_STATE_PARAM_NAME_START; \
249
0
      s = p + 1; \
250
0
    } \
251
0
  } while (0)
252
253
254
sec_agree_body_t *parse_sec_agree_body(const str *body)
255
0
{
256
0
  sec_agree_body_t *sa = NULL, *last = NULL, *first = NULL;
257
0
  str body_copy = *body;
258
0
  char *p, *s = NULL, *end = body_copy.s + body_copy.len;
259
0
  enum sec_agree_state state = SEC_AGREE_STATE_START;
260
0
  str name, value;
261
262
0
  LM_DBG("parsing %.*s\n", body_copy.len, body_copy.s);
263
0
  for (p = body_copy.s; p < end; p++) {
264
0
    switch (state) {
265
0
    case SEC_AGREE_STATE_START:
266
267
0
      if (sa) {
268
0
        sec_agree_param_t *sp;
269
0
        for (sp = sa->params; sp; sp = sp->next) {
270
0
          LM_DBG("extra param %p [%.*s]=[%.*s]\n", sa, sp->name.len, sp->name.s,
271
0
              sp->value.len, sp->value.s);
272
0
        }
273
0
      }
274
0
      sa = pkg_malloc(sizeof *sa);
275
0
      if (!sa) {
276
0
        LM_ERR("oom for new sec-agree field\n");
277
0
        goto out;
278
0
      }
279
0
      memset(sa, 0, sizeof *sa);
280
0
      s = p;
281
0
      state = SEC_AGREE_STATE_MECHANISM;
282
0
      break;
283
284
0
    case SEC_AGREE_STATE_MECHANISM:
285
0
      switch(*p) {
286
0
      case ',':
287
0
      case ';':
288
0
        SEC_AGREE_FILL_MECHANISM();
289
0
        break;
290
0
      default: /* valid character */
291
0
        break;
292
0
      }
293
0
      break;
294
0
    case SEC_AGREE_STATE_PARAM_NAME_START:
295
0
      switch (*p) {
296
0
      case ' ':
297
0
      case '\r':
298
0
      case '\n':
299
0
      case '\t':
300
0
        s = p;
301
0
        break;
302
0
      case ',':
303
0
        state = SEC_AGREE_STATE_START;
304
0
        break;
305
0
      default:
306
0
        s = p;
307
0
        state = SEC_AGREE_STATE_PARAM_NAME;
308
0
        break;
309
0
      }
310
0
      break;
311
0
    case SEC_AGREE_STATE_PARAM_NAME:
312
0
      switch (*p) {
313
0
      case '=':
314
0
        name.s = s;
315
0
        name.len = p - s;
316
0
        trim(&name);
317
0
        if (name.len <= 0) {
318
0
          LM_ERR("invalid sec-agree param name!\n");
319
0
          state = SEC_AGREE_STATE_INVALID;
320
0
        } else {
321
0
          state = SEC_AGREE_STATE_PARAM_START_VALUE;
322
0
        }
323
0
      }
324
0
      break;
325
0
    case SEC_AGREE_STATE_PARAM_START_VALUE:
326
0
      switch (*p) {
327
0
      case ' ':
328
0
      case '\r':
329
0
      case '\n':
330
0
      case '\t':
331
0
        s = p;
332
0
        break;
333
0
      case ',':
334
0
        state = SEC_AGREE_STATE_START;
335
0
        break;
336
0
      case '"':
337
0
        state = SEC_AGREE_STATE_PARAM_VALUE_ENC;
338
0
        s = p + 1;
339
0
        break;
340
0
      default:
341
0
        state = SEC_AGREE_STATE_PARAM_VALUE;
342
0
        s = p;
343
0
        break;
344
0
      }
345
0
      break;
346
0
    case SEC_AGREE_STATE_PARAM_VALUE_ENC:
347
0
      switch (*p) {
348
0
      case '"':
349
0
        if (*(p - 1) == '/')
350
0
          break;
351
        /* got to the end of parameter */
352
0
        value.s = s;
353
0
        value.len = p - s - 1;
354
0
        if (value.len <= 0) {
355
0
          LM_ERR("invalid parameter %.*s value len (enc)\n", name.len, name.s);
356
0
          state = SEC_AGREE_STATE_INVALID;
357
0
        } else {
358
0
          state = SEC_AGREE_STATE_PARAM_VALUE_END;
359
0
        }
360
0
        break;
361
0
      }
362
0
      break;
363
0
    case SEC_AGREE_STATE_PARAM_VALUE_END:
364
0
      switch (*p) {
365
0
      case ' ':
366
0
      case '\r':
367
0
      case '\n':
368
0
      case '\t':
369
0
        break;
370
0
      case ',':
371
0
        if (!parse_sec_agree_parameter(sa, &name, &value)) {
372
0
          state = SEC_AGREE_STATE_START;
373
0
          sa = NULL;
374
0
        } else {
375
0
          state = SEC_AGREE_STATE_INVALID;
376
0
        }
377
0
        break;
378
0
      case ';':
379
0
        if (!parse_sec_agree_parameter(sa, &name, &value)) {
380
0
          state = SEC_AGREE_STATE_PARAM_NAME_START;
381
0
          s = p + 1;
382
0
        } else {
383
0
          state = SEC_AGREE_STATE_INVALID;
384
0
        }
385
0
        break;
386
0
      default:
387
0
        LM_ERR("invalid parameter %.*s value extra [%.*s]\n",
388
0
            name.len, name.s, (int)(p - s), s);
389
0
        state = SEC_AGREE_STATE_INVALID;
390
0
        break;
391
0
      }
392
0
      break;
393
0
    case SEC_AGREE_STATE_PARAM_VALUE:
394
0
      switch (*p) {
395
0
      case ',':
396
0
      case ';':
397
        /* got to the end of parameter */
398
0
        value.s = s;
399
0
        value.len = p - s;
400
0
        if (value.len <= 0) {
401
0
          LM_ERR("invalid parameter %.*s value len\n", name.len, name.s);
402
0
          state = SEC_AGREE_STATE_INVALID;
403
0
        } else {
404
0
          if (!parse_sec_agree_parameter(sa, &name, &value))
405
0
            if (*p == ',')
406
0
              state = SEC_AGREE_STATE_START;
407
0
            else
408
0
              state = SEC_AGREE_STATE_PARAM_NAME_START;
409
0
          else
410
0
            state = SEC_AGREE_STATE_INVALID;
411
0
        }
412
0
        break;
413
0
      }
414
0
      break;
415
0
    case SEC_AGREE_STATE_INVALID:
416
0
      sa->invalid = 1;
417
      /* best effort - search for next param */
418
0
      switch (*p) {
419
0
      case ',':
420
0
        sa = NULL;
421
0
        state = SEC_AGREE_STATE_START;
422
0
        break;
423
0
      case ';':
424
0
        state = SEC_AGREE_STATE_PARAM_NAME_START;
425
0
        break;
426
0
      }
427
0
      break;
428
0
    }
429
0
  }
430
431
  /* final states */
432
0
  switch (state) {
433
0
    case SEC_AGREE_STATE_START:
434
0
      goto free_sa;
435
0
    case SEC_AGREE_STATE_MECHANISM:
436
0
      SEC_AGREE_FILL_MECHANISM();
437
0
      break;
438
0
    case SEC_AGREE_STATE_PARAM_NAME_START:
439
0
      break;
440
0
    case SEC_AGREE_STATE_PARAM_NAME:
441
0
      LM_ERR("invalid parameter %.*s without name\n", (int)(p - s), s);
442
0
      goto invalid;
443
0
    case SEC_AGREE_STATE_PARAM_START_VALUE:
444
0
      LM_ERR("invalid parameter %.*s without value\n", (int)(p - s), s);
445
0
      goto invalid;
446
0
    case SEC_AGREE_STATE_INVALID:
447
0
      goto invalid;
448
0
    case SEC_AGREE_STATE_PARAM_VALUE:
449
0
      value.s = s;
450
0
      value.len = p - s;
451
      /* fall through */
452
0
    case SEC_AGREE_STATE_PARAM_VALUE_END:
453
0
      if (value.len <= 0 || parse_sec_agree_parameter(sa, &name, &value) < 0) {
454
0
        LM_ERR("invalid parameter %.*s value len\n", name.len, name.s);
455
0
        goto invalid;
456
0
      }
457
0
      break;
458
0
    case SEC_AGREE_STATE_PARAM_VALUE_ENC:
459
0
      LM_ERR("invalid parameter %.*s value (not enclosed)\n", (int)(p - s), s);
460
0
      goto invalid;
461
0
  }
462
0
out:
463
0
  if (!first) {
464
0
    if (sa) /* should not be reached */
465
0
      free_sec_agree_body(sa);
466
0
    return NULL;
467
0
  }
468
0
  return first;
469
0
invalid:
470
0
  if (sa)
471
0
    sa->invalid = 1;
472
0
  return first;
473
0
free_sa:
474
0
  if (sa)
475
0
    free_sec_agree_body(sa);
476
0
  return first;
477
0
}
478
#undef SEC_AGREE_FILL_MECHANISM
479
480
int parse_sec_agree(struct hdr_field* _h)
481
0
{
482
0
  sec_agree_body_t *sa = NULL;
483
0
  if (_h->parsed)
484
0
    return 0;
485
0
  sa = parse_sec_agree_body(&_h->body);
486
0
  if (!sa) {
487
0
    LM_ERR("could not parse header [%.*s]!\n",
488
0
        _h->body.len, _h->body.s);
489
0
    return -1;
490
0
  }
491
0
  _h->parsed = sa;
492
0
  return 0;
493
0
}