Coverage Report

Created: 2026-01-09 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensips/parser/digest/digest_parser.c
Line
Count
Source
1
/*
2
 * Digest credentials parser
3
 *
4
 * Copyright (C) 2001-2003 FhG Fokus
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
 * History:
23
 * --------
24
 * 2003-03-02: Added parse_domain function (janakj)
25
 */
26
27
28
29
#include "digest_parser.h"
30
#include "../../lib/dassert.h"
31
#include "../../trim.h"    /* trim_leading */
32
#include "../../lib/turbocompare.h" /* turbo_casematch */
33
#include "param_parser.h"  /* Digest parameter name parser */
34
#include "../../ut.h"      /* q_memchr */
35
36
37
8.74k
#define DIGEST_SCHEME "digest"
38
46.6k
#define DIG_LEN 6
39
40
#define QOP_AUTH_STR_LEN (sizeof(QOP_AUTH_STR) - 1)
41
#define QOP_AUTHINT_STR_LEN (sizeof(QOP_AUTHINT_STR) - 1)
42
43
352
#define ALG_MD5_STR_LEN (sizeof(ALG_MD5_STR) - 1)
44
252
#define ALG_MD5SESS_STR_LEN (sizeof(ALG_MD5SESS_STR) - 1)
45
361
#define ALG_SHA256_STR_LEN (sizeof(ALG_SHA256_STR) - 1)
46
271
#define ALG_SHA256SESS_STR_LEN (sizeof(ALG_SHA256SESS_STR) - 1)
47
270
#define ALG_SHA512_256_STR_LEN (sizeof(ALG_SHA512_256_STR) - 1)
48
270
#define ALG_SHA512_256SESS_STR_LEN (sizeof(ALG_SHA512_256SESS_STR) - 1)
49
498
#define ALG_AKAv1_MD5_STR_LEN (sizeof(ALG_AKAv1_MD5_STR) - 1)
50
464
#define ALG_AKAv1_MD5SESS_STR_LEN (sizeof(ALG_AKAv1_MD5SESS_STR) - 1)
51
677
#define ALG_AKAv1_SHA256_STR_LEN (sizeof(ALG_AKAv1_SHA256_STR) - 1)
52
655
#define ALG_AKAv1_SHA256SESS_STR_LEN (sizeof(ALG_AKAv1_SHA256SESS_STR) - 1)
53
490
#define ALG_AKAv1_SHA512_256_STR_LEN (sizeof(ALG_AKAv1_SHA512_256_STR) - 1)
54
468
#define ALG_AKAv1_SHA512_256SESS_STR_LEN (sizeof(ALG_AKAv1_SHA512_256SESS_STR) - 1)
55
#define ALG_AKAv2_MD5_STR_LEN (sizeof(ALG_AKAv2_MD5_STR) - 1)
56
#define ALG_AKAv2_MD5SESS_STR_LEN (sizeof(ALG_AKAv2_MD5SESS_STR) - 1)
57
#define ALG_AKAv2_SHA256_STR_LEN (sizeof(ALG_AKAv2_SHA256_STR) - 1)
58
#define ALG_AKAv2_SHA256SESS_STR_LEN (sizeof(ALG_AKAv2_SHA256SESS_STR) - 1)
59
#define ALG_AKAv2_SHA512_256_STR_LEN (sizeof(ALG_AKAv2_SHA512_256_STR) - 1)
60
#define ALG_AKAv2_SHA512_256SESS_STR_LEN (sizeof(ALG_AKAv2_SHA512_256SESS_STR) - 1)
61
62
/*
63
 * Parse quoted string in a parameter body
64
 * return the string without quotes in _r
65
 * parameter and update _s to point behind the
66
 * closing quote
67
 */
68
static inline int parse_quoted(str* _s, str* _r)
69
36.0k
{
70
36.0k
  char* end_quote;
71
72
       /* The string must have at least
73
        * surrounding quotes
74
        */
75
36.0k
  if (_s->len < 2) {
76
39
    return -1;
77
39
  }
78
79
       /* Skip opening quote */
80
36.0k
  _s->s++;
81
36.0k
  _s->len--;
82
83
84
       /* Find closing quote */
85
36.0k
  end_quote = q_memchr(_s->s, '\"', _s->len);
86
87
       /* Not found, return error */
88
36.0k
  if (!end_quote) {
89
364
    return -2;
90
364
  }
91
92
       /* Let _r point to the string without
93
        * surrounding quotes
94
        */
95
35.6k
  _r->s = _s->s;
96
35.6k
  _r->len = end_quote - _s->s;
97
98
       /* Update _s parameter to point
99
        * behind the closing quote
100
        */
101
35.6k
  _s->len -= (end_quote - _s->s + 1);
102
35.6k
  _s->s = end_quote + 1;
103
104
       /* Everything went OK */
105
35.6k
  return 0;
106
36.0k
}
107
108
109
/*
110
 * Parse unquoted token in a parameter body
111
 * let _r point to the token and update _s
112
 * to point right behind the token
113
 */
114
static inline int parse_token(str* _s, str* _r)
115
726k
{
116
726k
  int i;
117
118
       /* Save the beginning of the
119
        * token in _r->s
120
        */
121
726k
  _r->s = _s->s;
122
123
       /* Iterate through the
124
        * token body
125
        */
126
11.2M
  for(i = 0; i < _s->len; i++) {
127
128
         /* All LWS characters + ','
129
          * mark end of the token
130
          */
131
11.2M
    if (is_ws(_s->s[i]) || _s->s[i] == ',') {
132
           /* So if you find
133
            * any of them
134
            * stop iterating
135
            */
136
726k
      goto out;
137
726k
    }
138
11.2M
  }
139
726k
 out:
140
       /* Empty token is error */
141
726k
  if (i == 0) {
142
390
          return -2;
143
390
  }
144
145
       /* Save length of the token */
146
726k
        _r->len = i;
147
148
       /* Update _s parameter so it points
149
        * right behind the end of the token
150
        */
151
726k
  _s->s = _s->s + i;
152
726k
  _s->len -= i;
153
154
       /* Everything went OK */
155
726k
  return 0;
156
726k
}
157
158
159
/*
160
 * Parse a digest parameter
161
 */
162
static inline int parse_digest_param(str* _s, dig_cred_t* _c)
163
769k
{
164
769k
  dig_par_t t;
165
769k
  str* ptr;
166
769k
  str dummy;
167
168
       /* Get type of the parameter */
169
769k
  if (parse_param_name(_s, &t) < 0) {
170
6.56k
    return -1;
171
6.56k
  }
172
173
762k
  _s->s++;  /* skip = */
174
762k
  _s->len--;
175
176
       /* Find the beginning of body */
177
762k
  trim_leading(_s);
178
179
762k
  if (_s->len == 0) {
180
273
    return -2;
181
273
  }
182
183
       /* Decide in which attribute the
184
        * body content will be stored
185
        */
186
762k
  switch(t) {
187
3.86k
  case PAR_USERNAME:  ptr = &_c->username.whole;  break;
188
61.5k
  case PAR_REALM:     ptr = &_c->realm;           break;
189
42.1k
  case PAR_NONCE:     ptr = &_c->nonce;           break;
190
379
  case PAR_URI:       ptr = &_c->uri;             break;
191
7.59k
  case PAR_RESPONSE:  ptr = &_c->response;        break;
192
299
  case PAR_CNONCE:    ptr = &_c->cnonce;          break;
193
399
  case PAR_OPAQUE:    ptr = &_c->opaque;          break;
194
25.6k
  case PAR_QOP:       ptr = &_c->qop.qop_str;     break;
195
6.53k
  case PAR_NC:        ptr = &_c->nc;              break;
196
42.4k
  case PAR_ALGORITHM: ptr = &_c->alg.alg_str;     break;
197
322
  case PAR_AUTS:      ptr = &_c->auts;            break;
198
571k
  case PAR_OTHER:     ptr = &dummy;               break;
199
0
  default:            ptr = &dummy;               break;
200
762k
  }
201
202
       /* If the first character is quote, it is
203
        * a quoted string, otherwise it is a token
204
        */
205
762k
  if (_s->s[0] == '\"') {
206
36.0k
    if (parse_quoted(_s, ptr) < 0) {
207
403
      return -3;
208
403
    }
209
726k
  } else {
210
726k
    if (parse_token(_s, ptr) < 0) {
211
390
      return -4;
212
390
    }
213
726k
  }
214
215
761k
  return 0;
216
762k
}
217
218
219
/*
220
 * Parse qop parameter body
221
 */
222
static inline void parse_qop(struct qp* _q)
223
156
{
224
156
  str s;
225
226
156
  s.s = _q->qop_str.s;
227
156
  s.len = _q->qop_str.len;
228
229
156
  trim(&s);
230
231
156
  if (turbo_strcasematch(&s, QOP_AUTH_STR, QOP_AUTH_STR_LEN)) {
232
1
    _q->qop_parsed = QOP_AUTH_D;
233
155
  } else if (turbo_strcasematch(&s, QOP_AUTHINT_STR, QOP_AUTHINT_STR_LEN)) {
234
1
    _q->qop_parsed = QOP_AUTHINT_D;
235
154
  } else {
236
154
    _q->qop_parsed = QOP_OTHER_D;
237
154
  }
238
156
}
239
240
#define CASE_ALG(alg, sptr) \
241
1.77k
  case ALG_##alg##_STR_LEN: \
242
1.77k
    if (turbo_casematch((sptr)->s, ALG_##alg##_STR, (sptr)->len)) \
243
1.49k
      return ALG_##alg; \
244
282
    break;
245
246
#define CASE_ALG2(alg1, alg2, sptr) \
247
3.25k
  case ALG_##alg1##_STR_LEN: \
248
3.25k
    if (turbo_casematch((sptr)->s, ALG_##alg1##_STR, (sptr)->len)) \
249
1.55k
      return ALG_##alg1; \
250
1.69k
    if (turbo_casematch((sptr)->s, ALG_##alg2##_STR, (sptr)->len)) \
251
1.69k
      return ALG_##alg2; \
252
1.69k
    break;
253
254
/*
255
 * Parse algorithm parameter body
256
 */
257
alg_t parse_digest_algorithm(const str *sp)
258
5.11k
{
259
260
5.11k
  switch (sp->len) {
261
352
  CASE_ALG(MD5, sp);
262
252
  CASE_ALG(MD5SESS, sp);
263
361
  CASE_ALG(SHA256, sp);
264
271
  CASE_ALG(SHA256SESS, sp);
265
270
  CASE_ALG(SHA512_256, sp);
266
270
  CASE_ALG(SHA512_256SESS, sp);
267
772
  CASE_ALG2(AKAv1_MD5, AKAv2_MD5, sp);
268
705
  CASE_ALG2(AKAv1_MD5SESS, AKAv2_MD5SESS, sp);
269
969
  CASE_ALG2(AKAv1_SHA256, AKAv2_SHA256, sp);
270
1.03k
  CASE_ALG2(AKAv1_SHA256SESS, AKAv2_SHA256SESS, sp);
271
763
  CASE_ALG2(AKAv1_SHA512_256, AKAv2_SHA512_256, sp);
272
712
  CASE_ALG2(AKAv1_SHA512_256SESS, AKAv2_SHA512_256SESS, sp);
273
82
  default:
274
82
    break;
275
5.11k
  }
276
626
  return ALG_OTHER;
277
5.11k
}
278
279
const str *print_digest_algorithm(alg_t alg)
280
325
{
281
325
  switch (alg) {
282
3
  case ALG_MD5:
283
3
    return _str(ALG_MD5_STR);
284
3
  case ALG_MD5SESS:
285
3
    return _str(ALG_MD5SESS_STR);
286
3
  case ALG_SHA256:
287
3
    return _str(ALG_SHA256_STR);
288
3
  case ALG_SHA256SESS:
289
3
    return _str(ALG_SHA256SESS_STR);
290
1
  case ALG_SHA512_256:
291
1
    return _str(ALG_SHA512_256_STR);
292
3
  case ALG_SHA512_256SESS:
293
3
    return _str(ALG_SHA512_256SESS_STR);
294
3
  case ALG_AKAv1_MD5:
295
3
    return _str(ALG_AKAv1_MD5_STR);
296
3
  case ALG_AKAv1_MD5SESS:
297
3
    return _str(ALG_AKAv1_MD5SESS_STR);
298
3
  case ALG_AKAv1_SHA256:
299
3
    return _str(ALG_AKAv1_SHA256_STR);
300
3
  case ALG_AKAv1_SHA256SESS:
301
3
    return _str(ALG_AKAv1_SHA256SESS_STR);
302
3
  case ALG_AKAv1_SHA512_256:
303
3
    return _str(ALG_AKAv1_SHA512_256_STR);
304
3
  case ALG_AKAv1_SHA512_256SESS:
305
3
    return _str(ALG_AKAv1_SHA512_256SESS_STR);
306
3
  case ALG_AKAv2_MD5:
307
3
    return _str(ALG_AKAv2_MD5_STR);
308
3
  case ALG_AKAv2_MD5SESS:
309
3
    return _str(ALG_AKAv2_MD5SESS_STR);
310
3
  case ALG_AKAv2_SHA256:
311
3
    return _str(ALG_AKAv2_SHA256_STR);
312
3
  case ALG_AKAv2_SHA256SESS:
313
3
    return _str(ALG_AKAv2_SHA256SESS_STR);
314
3
  case ALG_AKAv2_SHA512_256:
315
3
    return _str(ALG_AKAv2_SHA512_256_STR);
316
3
  case ALG_AKAv2_SHA512_256SESS:
317
3
    return _str(ALG_AKAv2_SHA512_256SESS_STR);
318
0
  default:
319
0
  case ALG_OTHER:
320
273
  case ALG_UNSPEC:
321
273
    return _str("Unknown");
322
325
  }
323
325
}
324
325
/*
326
 * Parse username for user and domain parts
327
 */
328
static inline void parse_username(struct username* _u)
329
35
{
330
35
  char* d;
331
332
35
  _u->user = _u->whole;
333
35
  if (_u->whole.len <= 2) return;
334
335
34
  d = q_memchr(_u->whole.s, '@', _u->whole.len);
336
337
34
  if (d) {
338
9
    _u->domain.s = d + 1;
339
9
    _u->domain.len = _u->whole.len - (d - _u->whole.s) - 1;
340
9
    _u->user.len = d - _u->user.s;
341
9
  }
342
34
}
343
344
345
/*
346
 * Parse Digest credentials parameter, one by one
347
 */
348
static inline int parse_digest_params(str* _s, dig_cred_t* _c)
349
8.15k
{
350
8.15k
  char* comma;
351
352
769k
  do {
353
         /* Parse the first parameter */
354
769k
    if (parse_digest_param(_s, _c) < 0) {
355
7.63k
      return -1;
356
7.63k
    }
357
358
         /* Try to find the next parameter */
359
761k
    comma = q_memchr(_s->s, ',', _s->len);
360
761k
    if (comma) {
361
           /* Yes, there is another,
362
            * remove any leading white-spaces
363
            * and let _s point to the next
364
            * parameter name
365
            */
366
761k
      _s->len -= comma - _s->s + 1;
367
761k
      _s->s = comma + 1;
368
761k
      trim_leading(_s);
369
761k
    }
370
761k
  } while(comma); /* Repeat while there are next parameters */
371
372
       /* Parse QOP body if the parameter was present */
373
528
  if (_c->qop.qop_str.len > 0) {
374
156
    parse_qop(&_c->qop);
375
156
  }
376
377
       /* Parse algorithm body if the parameter was present */
378
528
  if (_c->alg.alg_str.len > 0) {
379
158
    trim(&(_c->alg.alg_str));
380
158
    _c->alg.alg_parsed = parse_digest_algorithm(&(_c->alg.alg_str));
381
370
  } else {
382
    /* No algorithm specified */
383
370
    DASSERT(_c->alg.alg_parsed == ALG_UNSPEC);
384
370
  }
385
386
528
  if (_c->username.whole.len > 0) {
387
35
    parse_username(&_c->username);
388
35
  }
389
390
528
  return 0;
391
528
}
392
393
394
/*
395
 * We support Digest authentication only
396
 *
397
 * Returns:
398
 *  0 - if everything is OK
399
 * -1 - Error while parsing
400
 *  1 - Unknown scheme
401
 */
402
int parse_digest_cred(str* _s, dig_cred_t* _c)
403
9.16k
{
404
9.16k
  str tmp;
405
406
       /* Make a temporary copy, we are
407
        * going to modify it
408
        */
409
9.16k
  tmp.s = _s->s;
410
9.16k
  tmp.len = _s->len;
411
412
       /* Remove any leading spaces, tabs, \r and \n */
413
9.16k
  trim_leading(&tmp);
414
415
       /* Check the string length */
416
9.16k
  if (tmp.len < (DIG_LEN + 1)) return 1; /* Too short, unknown scheme */
417
418
       /* Now test, if it is digest scheme, since it is the only
419
        * scheme we are able to parse here
420
        */
421
8.74k
  if (turbo_casematch(tmp.s, DIGEST_SCHEME, DIG_LEN) &&
422
      /* Test for one of LWS chars + ',' */
423
8.21k
      (is_ws(tmp.s[DIG_LEN]) || (tmp.s[DIG_LEN] == ','))) {
424
         /* Scheme is Digest */
425
8.15k
    tmp.s += DIG_LEN + 1;
426
8.15k
    tmp.len -= DIG_LEN + 1;
427
428
         /* Again, skip all white-spaces */
429
8.15k
    trim_leading(&tmp);
430
431
         /* And parse digest parameters */
432
8.15k
    if (parse_digest_params(&tmp, _c) < 0) {
433
7.63k
      return -2; /* We must not return -1 in this function ! */
434
7.63k
    } else {
435
528
      return 0;
436
528
    }
437
8.15k
  } else {
438
585
    return 1; /* Unknown scheme */
439
585
  }
440
8.74k
}
441
442
443
/*
444
 * Initialize a digest credentials structure
445
 */
446
void init_dig_cred(dig_cred_t* _c)
447
9.16k
{
448
9.16k
  memset(_c, 0, sizeof(dig_cred_t));
449
9.16k
}