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