Coverage Report

Created: 2025-08-28 06:16

/src/opensips/parser/digest/digest_parser.c
Line
Count
Source (jump to first uncovered line)
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
5.38k
#define DIGEST_SCHEME "digest"
38
28.0k
#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
185
#define ALG_MD5_STR_LEN (sizeof(ALG_MD5_STR) - 1)
44
236
#define ALG_MD5SESS_STR_LEN (sizeof(ALG_MD5SESS_STR) - 1)
45
1.25k
#define ALG_SHA256_STR_LEN (sizeof(ALG_SHA256_STR) - 1)
46
389
#define ALG_SHA256SESS_STR_LEN (sizeof(ALG_SHA256SESS_STR) - 1)
47
125
#define ALG_SHA512_256_STR_LEN (sizeof(ALG_SHA512_256_STR) - 1)
48
119
#define ALG_SHA512_256SESS_STR_LEN (sizeof(ALG_SHA512_256SESS_STR) - 1)
49
1.14k
#define ALG_AKAv1_MD5_STR_LEN (sizeof(ALG_AKAv1_MD5_STR) - 1)
50
1.28k
#define ALG_AKAv1_MD5SESS_STR_LEN (sizeof(ALG_AKAv1_MD5SESS_STR) - 1)
51
481
#define ALG_AKAv1_SHA256_STR_LEN (sizeof(ALG_AKAv1_SHA256_STR) - 1)
52
1.06k
#define ALG_AKAv1_SHA256SESS_STR_LEN (sizeof(ALG_AKAv1_SHA256SESS_STR) - 1)
53
574
#define ALG_AKAv1_SHA512_256_STR_LEN (sizeof(ALG_AKAv1_SHA512_256_STR) - 1)
54
559
#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
15.6k
{
70
15.6k
  char* end_quote;
71
72
       /* The string must have at least
73
        * surrounding quotes
74
        */
75
15.6k
  if (_s->len < 2) {
76
26
    return -1;
77
26
  }
78
79
       /* Skip opening quote */
80
15.6k
  _s->s++;
81
15.6k
  _s->len--;
82
83
84
       /* Find closing quote */
85
15.6k
  end_quote = q_memchr(_s->s, '\"', _s->len);
86
87
       /* Not found, return error */
88
15.6k
  if (!end_quote) {
89
390
    return -2;
90
390
  }
91
92
       /* Let _r point to the string without
93
        * surrounding quotes
94
        */
95
15.2k
  _r->s = _s->s;
96
15.2k
  _r->len = end_quote - _s->s;
97
98
       /* Update _s parameter to point
99
        * behind the closing quote
100
        */
101
15.2k
  _s->len -= (end_quote - _s->s + 1);
102
15.2k
  _s->s = end_quote + 1;
103
104
       /* Everything went OK */
105
15.2k
  return 0;
106
15.6k
}
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
905k
{
116
905k
  int i;
117
118
       /* Save the beginning of the
119
        * token in _r->s
120
        */
121
905k
  _r->s = _s->s;
122
123
       /* Iterate through the
124
        * token body
125
        */
126
16.5M
  for(i = 0; i < _s->len; i++) {
127
128
         /* All LWS characters + ','
129
          * mark end of the token
130
          */
131
16.5M
    if (is_ws(_s->s[i]) || _s->s[i] == ',') {
132
           /* So if you find
133
            * any of them
134
            * stop iterating
135
            */
136
905k
      goto out;
137
905k
    }
138
16.5M
  }
139
905k
 out:
140
       /* Empty token is error */
141
905k
  if (i == 0) {
142
312
          return -2;
143
312
  }
144
145
       /* Save length of the token */
146
905k
        _r->len = i;
147
148
       /* Update _s parameter so it points
149
        * right behind the end of the token
150
        */
151
905k
  _s->s = _s->s + i;
152
905k
  _s->len -= i;
153
154
       /* Everything went OK */
155
905k
  return 0;
156
905k
}
157
158
159
/*
160
 * Parse a digest parameter
161
 */
162
static inline int parse_digest_param(str* _s, dig_cred_t* _c)
163
925k
{
164
925k
  dig_par_t t;
165
925k
  str* ptr;
166
925k
  str dummy;
167
168
       /* Get type of the parameter */
169
925k
  if (parse_param_name(_s, &t) < 0) {
170
3.70k
    return -1;
171
3.70k
  }
172
173
921k
  _s->s++;  /* skip = */
174
921k
  _s->len--;
175
176
       /* Find the beginning of body */
177
921k
  trim_leading(_s);
178
179
921k
  if (_s->len == 0) {
180
182
    return -2;
181
182
  }
182
183
       /* Decide in which attribute the
184
        * body content will be stored
185
        */
186
921k
  switch(t) {
187
8.79k
  case PAR_USERNAME:  ptr = &_c->username.whole;  break;
188
34.9k
  case PAR_REALM:     ptr = &_c->realm;           break;
189
23.2k
  case PAR_NONCE:     ptr = &_c->nonce;           break;
190
1.47k
  case PAR_URI:       ptr = &_c->uri;             break;
191
92.1k
  case PAR_RESPONSE:  ptr = &_c->response;        break;
192
9.20k
  case PAR_CNONCE:    ptr = &_c->cnonce;          break;
193
24.3k
  case PAR_OPAQUE:    ptr = &_c->opaque;          break;
194
37.8k
  case PAR_QOP:       ptr = &_c->qop.qop_str;     break;
195
14.3k
  case PAR_NC:        ptr = &_c->nc;              break;
196
19.7k
  case PAR_ALGORITHM: ptr = &_c->alg.alg_str;     break;
197
376
  case PAR_AUTS:      ptr = &_c->auts;            break;
198
655k
  case PAR_OTHER:     ptr = &dummy;               break;
199
0
  default:            ptr = &dummy;               break;
200
921k
  }
201
202
       /* If the first character is quote, it is
203
        * a quoted string, otherwise it is a token
204
        */
205
921k
  if (_s->s[0] == '\"') {
206
15.6k
    if (parse_quoted(_s, ptr) < 0) {
207
416
      return -3;
208
416
    }
209
905k
  } else {
210
905k
    if (parse_token(_s, ptr) < 0) {
211
312
      return -4;
212
312
    }
213
905k
  }
214
215
920k
  return 0;
216
921k
}
217
218
219
/*
220
 * Parse qop parameter body
221
 */
222
static inline void parse_qop(struct qp* _q)
223
107
{
224
107
  str s;
225
226
107
  s.s = _q->qop_str.s;
227
107
  s.len = _q->qop_str.len;
228
229
107
  trim(&s);
230
231
107
  if (turbo_strcasematch(&s, QOP_AUTH_STR, QOP_AUTH_STR_LEN)) {
232
1
    _q->qop_parsed = QOP_AUTH_D;
233
106
  } else if (turbo_strcasematch(&s, QOP_AUTHINT_STR, QOP_AUTHINT_STR_LEN)) {
234
1
    _q->qop_parsed = QOP_AUTHINT_D;
235
105
  } else {
236
105
    _q->qop_parsed = QOP_OTHER_D;
237
105
  }
238
107
}
239
240
#define CASE_ALG(alg, sptr) \
241
252
  case ALG_##alg##_STR_LEN: \
242
2.30k
    if (turbo_casematch((sptr)->s, ALG_##alg##_STR, (sptr)->len)) \
243
2.05k
      return ALG_##alg; \
244
252
    break;
245
246
#define CASE_ALG2(alg1, alg2, sptr) \
247
363
  case ALG_##alg1##_STR_LEN: \
248
5.10k
    if (turbo_casematch((sptr)->s, ALG_##alg1##_STR, (sptr)->len)) \
249
2.99k
      return ALG_##alg1; \
250
2.11k
    if (turbo_casematch((sptr)->s, ALG_##alg2##_STR, (sptr)->len)) \
251
2.11k
      return ALG_##alg2; \
252
2.11k
    break;
253
254
/*
255
 * Parse algorithm parameter body
256
 */
257
alg_t parse_digest_algorithm(const str *sp)
258
7.48k
{
259
260
7.48k
  switch (sp->len) {
261
185
  CASE_ALG(MD5, sp);
262
236
  CASE_ALG(MD5SESS, sp);
263
1.25k
  CASE_ALG(SHA256, sp);
264
389
  CASE_ALG(SHA256SESS, sp);
265
125
  CASE_ALG(SHA512_256, sp);
266
119
  CASE_ALG(SHA512_256SESS, sp);
267
1.53k
  CASE_ALG2(AKAv1_MD5, AKAv2_MD5, sp);
268
1.61k
  CASE_ALG2(AKAv1_MD5SESS, AKAv2_MD5SESS, sp);
269
907
  CASE_ALG2(AKAv1_SHA256, AKAv2_SHA256, sp);
270
1.43k
  CASE_ALG2(AKAv1_SHA256SESS, AKAv2_SHA256SESS, sp);
271
875
  CASE_ALG2(AKAv1_SHA512_256, AKAv2_SHA512_256, sp);
272
852
  CASE_ALG2(AKAv1_SHA512_256SESS, AKAv2_SHA512_256SESS, sp);
273
78
  default:
274
78
    break;
275
7.48k
  }
276
693
  return ALG_OTHER;
277
7.48k
}
278
279
const str *print_digest_algorithm(alg_t alg)
280
296
{
281
296
  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
4
  case ALG_SHA256:
287
4
    return _str(ALG_SHA256_STR);
288
4
  case ALG_SHA256SESS:
289
4
    return _str(ALG_SHA256SESS_STR);
290
5
  case ALG_SHA512_256:
291
5
    return _str(ALG_SHA512_256_STR);
292
1
  case ALG_SHA512_256SESS:
293
1
    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
4
  case ALG_AKAv2_MD5SESS:
309
4
    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
2
  case ALG_AKAv2_SHA512_256:
315
2
    return _str(ALG_AKAv2_SHA512_256_STR);
316
4
  case ALG_AKAv2_SHA512_256SESS:
317
4
    return _str(ALG_AKAv2_SHA512_256SESS_STR);
318
0
  default:
319
0
  case ALG_OTHER:
320
239
  case ALG_UNSPEC:
321
239
    return _str("Unknown");
322
296
  }
323
296
}
324
325
/*
326
 * Parse username for user and domain parts
327
 */
328
static inline void parse_username(struct username* _u)
329
34
{
330
34
  char* d;
331
332
34
  _u->user = _u->whole;
333
34
  if (_u->whole.len <= 2) return;
334
335
30
  d = q_memchr(_u->whole.s, '@', _u->whole.len);
336
337
30
  if (d) {
338
14
    _u->domain.s = d + 1;
339
14
    _u->domain.len = _u->whole.len - (d - _u->whole.s) - 1;
340
14
    _u->user.len = d - _u->user.s;
341
14
  }
342
30
}
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
5.02k
{
350
5.02k
  char* comma;
351
352
925k
  do {
353
         /* Parse the first parameter */
354
925k
    if (parse_digest_param(_s, _c) < 0) {
355
4.61k
      return -1;
356
4.61k
    }
357
358
         /* Try to find the next parameter */
359
920k
    comma = q_memchr(_s->s, ',', _s->len);
360
920k
    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
920k
      _s->len -= comma - _s->s + 1;
367
920k
      _s->s = comma + 1;
368
920k
      trim_leading(_s);
369
920k
    }
370
920k
  } while(comma); /* Repeat while there are next parameters */
371
372
       /* Parse QOP body if the parameter was present */
373
414
  if (_c->qop.qop_str.len > 0) {
374
107
    parse_qop(&_c->qop);
375
107
  }
376
377
       /* Parse algorithm body if the parameter was present */
378
414
  if (_c->alg.alg_str.len > 0) {
379
106
    trim(&(_c->alg.alg_str));
380
106
    _c->alg.alg_parsed = parse_digest_algorithm(&(_c->alg.alg_str));
381
308
  } else {
382
    /* No algorithm specified */
383
308
    DASSERT(_c->alg.alg_parsed == ALG_UNSPEC);
384
308
  }
385
386
414
  if (_c->username.whole.len > 0) {
387
34
    parse_username(&_c->username);
388
34
  }
389
390
414
  return 0;
391
414
}
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
5.79k
{
404
5.79k
  str tmp;
405
406
       /* Make a temporary copy, we are
407
        * going to modify it
408
        */
409
5.79k
  tmp.s = _s->s;
410
5.79k
  tmp.len = _s->len;
411
412
       /* Remove any leading spaces, tabs, \r and \n */
413
5.79k
  trim_leading(&tmp);
414
415
       /* Check the string length */
416
5.79k
  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
5.38k
  if (turbo_casematch(tmp.s, DIGEST_SCHEME, DIG_LEN) &&
422
      /* Test for one of LWS chars + ',' */
423
5.38k
      (is_ws(tmp.s[DIG_LEN]) || (tmp.s[DIG_LEN] == ','))) {
424
         /* Scheme is Digest */
425
5.02k
    tmp.s += DIG_LEN + 1;
426
5.02k
    tmp.len -= DIG_LEN + 1;
427
428
         /* Again, skip all white-spaces */
429
5.02k
    trim_leading(&tmp);
430
431
         /* And parse digest parameters */
432
5.02k
    if (parse_digest_params(&tmp, _c) < 0) {
433
4.61k
      return -2; /* We must not return -1 in this function ! */
434
4.61k
    } else {
435
414
      return 0;
436
414
    }
437
5.02k
  } else {
438
351
    return 1; /* Unknown scheme */
439
351
  }
440
5.38k
}
441
442
443
/*
444
 * Initialize a digest credentials structure
445
 */
446
void init_dig_cred(dig_cred_t* _c)
447
5.79k
{
448
5.79k
  memset(_c, 0, sizeof(dig_cred_t));
449
5.79k
}