/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 | } |