Coverage Report

Created: 2025-04-11 06:59

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