blob: b337e1effca2ea5a2e66f18d06369a8b52ec3058 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#include <pjsip/sip_auth.h>
22#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
23#include <pjsip/sip_auth_aka.h>
24#include <pjsip/sip_transport.h>
25#include <pjsip/sip_endpoint.h>
26#include <pjsip/sip_errno.h>
27#include <pjsip/sip_util.h>
28#include <pjlib-util/md5.h>
29#include <pj/log.h>
30#include <pj/string.h>
31#include <pj/pool.h>
32#include <pj/guid.h>
33#include <pj/assert.h>
34#include <pj/ctype.h>
35
36
37
38/* A macro just to get rid of type mismatch between char and unsigned char */
39#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, \
40 (unsigned)len)
41
42/* Logging. */
43#define THIS_FILE "sip_auth_client.c"
44#if 0
45# define AUTH_TRACE_(expr) PJ_LOG(3, expr)
46#else
47# define AUTH_TRACE_(expr)
48#endif
49
50#define PASSWD_MASK 0x000F
51#define EXT_MASK 0x00F0
52
53
54static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src)
55{
56 dst->slen = src->slen;
57
58 if (dst->slen) {
59 dst->ptr = (char*) pj_pool_alloc(pool, src->slen);
60 pj_memcpy(dst->ptr, src->ptr, src->slen);
61 } else {
62 dst->ptr = NULL;
63 }
64}
65
66PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool,
67 pjsip_cred_info *dst,
68 const pjsip_cred_info *src)
69{
70 pj_memcpy(dst, src, sizeof(pjsip_cred_info));
71
72 pj_strdup_with_null(pool, &dst->realm, &src->realm);
73 pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
74 pj_strdup_with_null(pool, &dst->username, &src->username);
75 pj_strdup_with_null(pool, &dst->data, &src->data);
76
77 if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
78 dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k);
79 dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op);
80 dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf);
81 }
82}
83
84
85PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
86 const pjsip_cred_info *cred2)
87{
88 int result;
89
90 result = pj_strcmp(&cred1->realm, &cred2->realm);
91 if (result) goto on_return;
92 result = pj_strcmp(&cred1->scheme, &cred2->scheme);
93 if (result) goto on_return;
94 result = pj_strcmp(&cred1->username, &cred2->username);
95 if (result) goto on_return;
96 result = pj_strcmp(&cred1->data, &cred2->data);
97 if (result) goto on_return;
98 result = (cred1->data_type != cred2->data_type);
99 if (result) goto on_return;
100
101 if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
102 result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
103 if (result) goto on_return;
104 result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
105 if (result) goto on_return;
106 result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
107 if (result) goto on_return;
108 }
109
110on_return:
111 return result;
112}
113
114PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool,
115 pjsip_auth_clt_pref *dst,
116 const pjsip_auth_clt_pref *src)
117{
118 pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref));
119 pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm);
120}
121
122
123/* Transform digest to string.
124 * output must be at least PJSIP_MD5STRLEN+1 bytes.
125 *
126 * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
127 */
128static void digest2str(const unsigned char digest[], char *output)
129{
130 int i;
131 for (i = 0; i<16; ++i) {
132 pj_val_to_hex_digit(digest[i], output);
133 output += 2;
134 }
135}
136
137
138/*
139 * Create response digest based on the parameters and store the
140 * digest ASCII in 'result'.
141 */
142PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
143 const pj_str_t *nonce,
144 const pj_str_t *nc,
145 const pj_str_t *cnonce,
146 const pj_str_t *qop,
147 const pj_str_t *uri,
148 const pj_str_t *realm,
149 const pjsip_cred_info *cred_info,
150 const pj_str_t *method)
151{
152 char ha1[PJSIP_MD5STRLEN];
153 char ha2[PJSIP_MD5STRLEN];
154 unsigned char digest[16];
155 pj_md5_context pms;
156
157 pj_assert(result->slen >= PJSIP_MD5STRLEN);
158
159 AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
160
161 if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) {
162 /***
163 *** ha1 = MD5(username ":" realm ":" password)
164 ***/
165 pj_md5_init(&pms);
166 MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
167 MD5_APPEND( &pms, ":", 1);
168 MD5_APPEND( &pms, realm->ptr, realm->slen);
169 MD5_APPEND( &pms, ":", 1);
170 MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
171 pj_md5_final(&pms, digest);
172
173 digest2str(digest, ha1);
174
175 } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
176 pj_assert(cred_info->data.slen == 32);
177 pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
178 } else {
179 pj_assert(!"Invalid data_type");
180 }
181
182 AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1));
183
184 /***
185 *** ha2 = MD5(method ":" req_uri)
186 ***/
187 pj_md5_init(&pms);
188 MD5_APPEND( &pms, method->ptr, method->slen);
189 MD5_APPEND( &pms, ":", 1);
190 MD5_APPEND( &pms, uri->ptr, uri->slen);
191 pj_md5_final(&pms, digest);
192 digest2str(digest, ha2);
193
194 AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2));
195
196 /***
197 *** When qop is not used:
198 *** response = MD5(ha1 ":" nonce ":" ha2)
199 ***
200 *** When qop=auth is used:
201 *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
202 ***/
203 pj_md5_init(&pms);
204 MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
205 MD5_APPEND( &pms, ":", 1);
206 MD5_APPEND( &pms, nonce->ptr, nonce->slen);
207 if (qop && qop->slen != 0) {
208 MD5_APPEND( &pms, ":", 1);
209 MD5_APPEND( &pms, nc->ptr, nc->slen);
210 MD5_APPEND( &pms, ":", 1);
211 MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
212 MD5_APPEND( &pms, ":", 1);
213 MD5_APPEND( &pms, qop->ptr, qop->slen);
214 }
215 MD5_APPEND( &pms, ":", 1);
216 MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
217
218 /* This is the final response digest. */
219 pj_md5_final(&pms, digest);
220
221 /* Convert digest to string and store in chal->response. */
222 result->slen = PJSIP_MD5STRLEN;
223 digest2str(digest, result->ptr);
224
225 AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr));
226 AUTH_TRACE_((THIS_FILE, "Digest created"));
227}
228
229/*
230 * Finds out if qop offer contains "auth" token.
231 */
232static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
233{
234 pj_str_t qop;
235 char *p;
236
237 pj_strdup_with_null( pool, &qop, qop_offer);
238 p = qop.ptr;
239 while (*p) {
240 *p = (char)pj_tolower(*p);
241 ++p;
242 }
243
244 p = qop.ptr;
245 while (*p) {
246 if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
247 int e = *(p+4);
248 if (e=='"' || e==',' || e==0)
249 return PJ_TRUE;
250 else
251 p += 4;
252 } else {
253 ++p;
254 }
255 }
256
257 return PJ_FALSE;
258}
259
260/*
261 * Generate response digest.
262 * Most of the parameters to generate the digest (i.e. username, realm, uri,
263 * and nonce) are expected to be in the credential. Additional parameters (i.e.
264 * password and method param) should be supplied in the argument.
265 *
266 * The resulting digest will be stored in cred->response.
267 * The pool is used to allocate 32 bytes to store the digest in cred->response.
268 */
269static pj_status_t respond_digest( pj_pool_t *pool,
270 pjsip_digest_credential *cred,
271 const pjsip_digest_challenge *chal,
272 const pj_str_t *uri,
273 const pjsip_cred_info *cred_info,
274 const pj_str_t *cnonce,
275 pj_uint32_t nc,
276 const pj_str_t *method)
277{
278 const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
279
280 /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */
281 if (chal->algorithm.slen==0 ||
282 (pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 ||
283 pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0))
284 {
285 ;
286 }
287 else {
288 PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
289 chal->algorithm.slen, chal->algorithm.ptr));
290 return PJSIP_EINVALIDALGORITHM;
291 }
292
293 /* Build digest credential from arguments. */
294 pj_strdup(pool, &cred->username, &cred_info->username);
295 pj_strdup(pool, &cred->realm, &chal->realm);
296 pj_strdup(pool, &cred->nonce, &chal->nonce);
297 pj_strdup(pool, &cred->uri, uri);
298 pj_strdup(pool, &cred->algorithm, &chal->algorithm);
299 pj_strdup(pool, &cred->opaque, &chal->opaque);
300
301 /* Allocate memory. */
302 cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN);
303 cred->response.slen = PJSIP_MD5STRLEN;
304
305 if (chal->qop.slen == 0) {
306 /* Server doesn't require quality of protection. */
307
308 if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
309 /* Call application callback to create the response digest */
310 return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
311 method, cred);
312 }
313 else {
314 /* Convert digest to string and store in chal->response. */
315 pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
316 NULL, NULL, uri, &chal->realm,
317 cred_info, method);
318 }
319
320 } else if (has_auth_qop(pool, &chal->qop)) {
321 /* Server requires quality of protection.
322 * We respond with selecting "qop=auth" protection.
323 */
324 cred->qop = pjsip_AUTH_STR;
325 cred->nc.ptr = (char*) pj_pool_alloc(pool, 16);
326 cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc);
327
328 if (cnonce && cnonce->slen) {
329 pj_strdup(pool, &cred->cnonce, cnonce);
330 } else {
331 pj_str_t dummy_cnonce = { "b39971", 6};
332 pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
333 }
334
335 if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
336 /* Call application callback to create the response digest */
337 return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
338 method, cred);
339 }
340 else {
341 pjsip_auth_create_digest( &cred->response, &cred->nonce,
342 &cred->nc, cnonce, &pjsip_AUTH_STR,
343 uri, &chal->realm, cred_info, method );
344 }
345
346 } else {
347 /* Server requires quality protection that we don't support. */
348 PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s",
349 chal->qop.slen, chal->qop.ptr));
350 return PJSIP_EINVALIDQOP;
351 }
352
353 return PJ_SUCCESS;
354}
355
356#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
357/*
358 * Update authentication session with a challenge.
359 */
360static void update_digest_session( pj_pool_t *ses_pool,
361 pjsip_cached_auth *cached_auth,
362 const pjsip_www_authenticate_hdr *hdr )
363{
364 if (hdr->challenge.digest.qop.slen == 0) {
365#if PJSIP_AUTH_AUTO_SEND_NEXT!=0
366 if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) {
367 cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
368 pjsip_hdr_clone(ses_pool, hdr);
369 } else {
370 /* Only update if the new challenge is "significantly different"
371 * than the one in the cache, to reduce memory usage.
372 */
373 const pjsip_digest_challenge *d1 =
374 &cached_auth->last_chal->challenge.digest;
375 const pjsip_digest_challenge *d2 = &hdr->challenge.digest;
376
377 if (pj_strcmp(&d1->domain, &d2->domain) ||
378 pj_strcmp(&d1->realm, &d2->realm) ||
379 pj_strcmp(&d1->nonce, &d2->nonce) ||
380 pj_strcmp(&d1->opaque, &d2->opaque) ||
381 pj_strcmp(&d1->algorithm, &d2->algorithm) ||
382 pj_strcmp(&d1->qop, &d2->qop))
383 {
384 cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
385 pjsip_hdr_clone(ses_pool, hdr);
386 }
387 }
388#endif
389 return;
390 }
391
392 /* Initialize cnonce and qop if not present. */
393 if (cached_auth->cnonce.slen == 0) {
394 /* Save the whole challenge */
395 cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
396 pjsip_hdr_clone(ses_pool, hdr);
397
398 /* Create cnonce */
399 pj_create_unique_string( ses_pool, &cached_auth->cnonce );
400
401 /* Initialize nonce-count */
402 cached_auth->nc = 1;
403
404 /* Save realm. */
405 /* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061)
406 pj_assert(cached_auth->realm.slen != 0);
407 */
408 if (cached_auth->realm.slen == 0) {
409 pj_strdup(ses_pool, &cached_auth->realm,
410 &hdr->challenge.digest.realm);
411 }
412
413 } else {
414 /* Update last_nonce and nonce-count */
415 if (!pj_strcmp(&hdr->challenge.digest.nonce,
416 &cached_auth->last_chal->challenge.digest.nonce))
417 {
418 /* Same nonce, increment nonce-count */
419 ++cached_auth->nc;
420 } else {
421 /* Server gives new nonce. */
422 pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
423 &hdr->challenge.digest.nonce);
424 /* Has the opaque changed? */
425 if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
426 &hdr->challenge.digest.opaque))
427 {
428 pj_strdup(ses_pool,
429 &cached_auth->last_chal->challenge.digest.opaque,
430 &hdr->challenge.digest.opaque);
431 }
432 cached_auth->nc = 1;
433 }
434 }
435}
436#endif /* PJSIP_AUTH_QOP_SUPPORT */
437
438
439/* Find cached authentication in the list for the specified realm. */
440static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
441 const pj_str_t *realm )
442{
443 pjsip_cached_auth *auth = sess->cached_auth.next;
444 while (auth != &sess->cached_auth) {
445 if (pj_stricmp(&auth->realm, realm) == 0)
446 return auth;
447 auth = auth->next;
448 }
449
450 return NULL;
451}
452
453/* Find credential to use for the specified realm and auth scheme. */
454static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
455 const pj_str_t *realm,
456 const pj_str_t *auth_scheme)
457{
458 unsigned i;
459 int wildcard = -1;
460
461 PJ_UNUSED_ARG(auth_scheme);
462
463 for (i=0; i<sess->cred_cnt; ++i) {
464 if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
465 return &sess->cred_info[i];
466 else if (sess->cred_info[i].realm.slen == 1 &&
467 sess->cred_info[i].realm.ptr[0] == '*')
468 {
469 wildcard = i;
470 }
471 }
472
473 /* No matching realm. See if we have credential with wildcard ('*')
474 * as the realm.
475 */
476 if (wildcard != -1)
477 return &sess->cred_info[wildcard];
478
479 /* Nothing is suitable */
480 return NULL;
481}
482
483
484/* Init client session. */
485PJ_DEF(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess,
486 pjsip_endpoint *endpt,
487 pj_pool_t *pool,
488 unsigned options)
489{
490 PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
491
492 sess->pool = pool;
493 sess->endpt = endpt;
494 sess->cred_cnt = 0;
495 sess->cred_info = NULL;
496 pj_list_init(&sess->cached_auth);
497
498 return PJ_SUCCESS;
499}
500
501
502/* Clone session. */
503PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool,
504 pjsip_auth_clt_sess *sess,
505 const pjsip_auth_clt_sess *rhs )
506{
507 unsigned i;
508
509 PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL);
510
511 pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0);
512
513 sess->cred_cnt = rhs->cred_cnt;
514 sess->cred_info = (pjsip_cred_info*)
515 pj_pool_alloc(pool,
516 sess->cred_cnt*sizeof(pjsip_cred_info));
517 for (i=0; i<rhs->cred_cnt; ++i) {
518 pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm);
519 pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme);
520 pj_strdup(pool, &sess->cred_info[i].username,
521 &rhs->cred_info[i].username);
522 sess->cred_info[i].data_type = rhs->cred_info[i].data_type;
523 pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data);
524 }
525
526 /* TODO note:
527 * Cloning the full authentication client is quite a big task.
528 * We do only the necessary bits here, i.e. cloning the credentials.
529 * The drawback of this basic approach is, a forked dialog will have to
530 * re-authenticate itself on the next request because it has lost the
531 * cached authentication headers.
532 */
533 PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION);
534
535 return PJ_SUCCESS;
536}
537
538
539/* Set client credentials. */
540PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
541 int cred_cnt,
542 const pjsip_cred_info *c)
543{
544 PJ_ASSERT_RETURN(sess && c, PJ_EINVAL);
545
546 if (cred_cnt == 0) {
547 sess->cred_cnt = 0;
548 } else {
549 int i;
550 sess->cred_info = (pjsip_cred_info*)
551 pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
552 for (i=0; i<cred_cnt; ++i) {
553 sess->cred_info[i].data_type = c[i].data_type;
554
555 /* When data_type is PJSIP_CRED_DATA_EXT_AKA,
556 * callback must be specified.
557 */
558 if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
559
560#if !PJSIP_HAS_DIGEST_AKA_AUTH
561 if (!PJSIP_HAS_DIGEST_AKA_AUTH) {
562 pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled");
563 return PJSIP_EAUTHINAKACRED;
564 }
565#endif
566
567 /* Callback must be specified */
568 PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL);
569
570 /* Verify K len */
571 PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN,
572 PJSIP_EAUTHINAKACRED);
573
574 /* Verify OP len */
575 PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN,
576 PJSIP_EAUTHINAKACRED);
577
578 /* Verify AMF len */
579 PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN,
580 PJSIP_EAUTHINAKACRED);
581
582 sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb;
583 pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k,
584 &c[i].ext.aka.k);
585 pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op,
586 &c[i].ext.aka.op);
587 pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf,
588 &c[i].ext.aka.amf);
589 }
590
591 pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme);
592 pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm);
593 pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username);
594 pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data);
595 }
596 sess->cred_cnt = cred_cnt;
597 }
598
599 return PJ_SUCCESS;
600}
601
602
603/*
604 * Set the preference for the client authentication session.
605 */
606PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess,
607 const pjsip_auth_clt_pref *p)
608{
609 PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
610
611 pj_memcpy(&sess->pref, p, sizeof(*p));
612 pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm);
613 //if (sess->pref.algorithm.slen == 0)
614 // sess->pref.algorithm = pj_str("md5");
615
616 return PJ_SUCCESS;
617}
618
619
620/*
621 * Get the preference for the client authentication session.
622 */
623PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess,
624 pjsip_auth_clt_pref *p)
625{
626 PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
627
628 pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref));
629 return PJ_SUCCESS;
630}
631
632
633/*
634 * Create Authorization/Proxy-Authorization response header based on the challege
635 * in WWW-Authenticate/Proxy-Authenticate header.
636 */
637static pj_status_t auth_respond( pj_pool_t *req_pool,
638 const pjsip_www_authenticate_hdr *hdr,
639 const pjsip_uri *uri,
640 const pjsip_cred_info *cred_info,
641 const pjsip_method *method,
642 pj_pool_t *sess_pool,
643 pjsip_cached_auth *cached_auth,
644 pjsip_authorization_hdr **p_h_auth)
645{
646 pjsip_authorization_hdr *hauth;
647 char tmp[PJSIP_MAX_URL_SIZE];
648 pj_str_t uri_str;
649 pj_pool_t *pool;
650 pj_status_t status;
651
652 /* Verify arguments. */
653 PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
654 sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
655
656 /* Print URL in the original request. */
657 uri_str.ptr = tmp;
658 uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
659 if (uri_str.slen < 1) {
660 pj_assert(!"URL is too long!");
661 return PJSIP_EURITOOLONG;
662 }
663
664# if (PJSIP_AUTH_HEADER_CACHING)
665 {
666 pool = sess_pool;
667 PJ_UNUSED_ARG(req_pool);
668 }
669# else
670 {
671 pool = req_pool;
672 PJ_UNUSED_ARG(sess_pool);
673 }
674# endif
675
676 if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
677 hauth = pjsip_authorization_hdr_create(pool);
678 else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
679 hauth = pjsip_proxy_authorization_hdr_create(pool);
680 else {
681 pj_assert(!"Invalid response header!");
682 return PJSIP_EINVALIDHDR;
683 }
684
685 /* Only support digest scheme at the moment. */
686 if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
687 pj_str_t *cnonce = NULL;
688 pj_uint32_t nc = 1;
689
690 /* Update the session (nonce-count etc) if required. */
691# if PJSIP_AUTH_QOP_SUPPORT
692 {
693 if (cached_auth) {
694 update_digest_session( sess_pool, cached_auth, hdr );
695
696 cnonce = &cached_auth->cnonce;
697 nc = cached_auth->nc;
698 }
699 }
700# endif /* PJSIP_AUTH_QOP_SUPPORT */
701
702 hauth->scheme = pjsip_DIGEST_STR;
703 status = respond_digest( pool, &hauth->credential.digest,
704 &hdr->challenge.digest, &uri_str, cred_info,
705 cnonce, nc, &method->name);
706 if (status != PJ_SUCCESS)
707 return status;
708
709 /* Set qop type in auth session the first time only. */
710 if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
711 if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
712 pj_str_t *qop_val = &hauth->credential.digest.qop;
713 if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
714 cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
715 } else {
716 cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
717 }
718 }
719 }
720 } else {
721 return PJSIP_EINVALIDAUTHSCHEME;
722 }
723
724 /* Keep the new authorization header in the cache, only
725 * if no qop is not present.
726 */
727# if PJSIP_AUTH_HEADER_CACHING
728 {
729 if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
730 pjsip_cached_auth_hdr *cached_hdr;
731
732 /* Delete old header with the same method. */
733 cached_hdr = cached_auth->cached_hdr.next;
734 while (cached_hdr != &cached_auth->cached_hdr) {
735 if (pjsip_method_cmp(method, &cached_hdr->method)==0)
736 break;
737 cached_hdr = cached_hdr->next;
738 }
739
740 /* Save the header to the list. */
741 if (cached_hdr != &cached_auth->cached_hdr) {
742 cached_hdr->hdr = hauth;
743 } else {
744 cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
745 pjsip_method_copy( pool, &cached_hdr->method, method);
746 cached_hdr->hdr = hauth;
747 pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
748 }
749 }
750
751# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
752 if (hdr != cached_auth->last_chal) {
753 cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr);
754 }
755# endif
756 }
757# endif
758
759 *p_h_auth = hauth;
760 return PJ_SUCCESS;
761
762}
763
764
765#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
766static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
767 pjsip_auth_clt_sess *sess,
768 pjsip_cached_auth *auth,
769 pjsip_authorization_hdr **p_h_auth)
770{
771 const pjsip_cred_info *cred;
772 pjsip_authorization_hdr *hauth;
773 pj_status_t status;
774
775 PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
776 PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
777
778 cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
779 if (!cred)
780 return PJSIP_ENOCREDENTIAL;
781
782 status = auth_respond( tdata->pool, auth->last_chal,
783 tdata->msg->line.req.uri,
784 cred, &tdata->msg->line.req.method,
785 sess->pool, auth, &hauth);
786 if (status != PJ_SUCCESS)
787 return status;
788
789 pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
790
791 if (p_h_auth)
792 *p_h_auth = hauth;
793
794 return PJ_SUCCESS;
795}
796#endif
797
798
799/* Find credential in list of (Proxy-)Authorization headers */
800static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list,
801 const pj_str_t *realm)
802{
803 pjsip_authorization_hdr *h;
804
805 h = (pjsip_authorization_hdr*)hdr_list->next;
806 while (h != (pjsip_authorization_hdr*)hdr_list) {
807 if (pj_stricmp(&h->credential.digest.realm, realm)==0)
808 return h;
809 h = h->next;
810 }
811
812 return NULL;
813}
814
815
816/* Initialize outgoing request. */
817PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
818 pjsip_tx_data *tdata )
819{
820 const pjsip_method *method;
821 pjsip_cached_auth *auth;
822 pjsip_hdr added;
823
824 PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
825 PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
826 PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
827 PJSIP_ENOTREQUESTMSG);
828
829 /* Init list */
830 pj_list_init(&added);
831
832 /* Get the method. */
833 method = &tdata->msg->line.req.method;
834
835 auth = sess->cached_auth.next;
836 while (auth != &sess->cached_auth) {
837 /* Reset stale counter */
838 auth->stale_cnt = 0;
839
840 if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
841# if defined(PJSIP_AUTH_HEADER_CACHING) && \
842 PJSIP_AUTH_HEADER_CACHING!=0
843 {
844 pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
845 while (entry != &auth->cached_hdr) {
846 if (pjsip_method_cmp(&entry->method, method)==0) {
847 pjsip_authorization_hdr *hauth;
848 hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
849 //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
850 pj_list_push_back(&added, hauth);
851 break;
852 }
853 entry = entry->next;
854 }
855
856# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
857 PJSIP_AUTH_AUTO_SEND_NEXT!=0
858 {
859 if (entry == &auth->cached_hdr)
860 new_auth_for_req( tdata, sess, auth, NULL);
861 }
862# endif
863
864 }
865# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
866 PJSIP_AUTH_AUTO_SEND_NEXT!=0
867 {
868 new_auth_for_req( tdata, sess, auth, NULL);
869 }
870# endif
871
872 }
873# if defined(PJSIP_AUTH_QOP_SUPPORT) && \
874 defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
875 (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
876 else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
877 /* For qop="auth", we have to re-create the authorization header.
878 */
879 const pjsip_cred_info *cred;
880 pjsip_authorization_hdr *hauth;
881 pj_status_t status;
882
883 cred = auth_find_cred(sess, &auth->realm,
884 &auth->last_chal->scheme);
885 if (!cred) {
886 auth = auth->next;
887 continue;
888 }
889
890 status = auth_respond( tdata->pool, auth->last_chal,
891 tdata->msg->line.req.uri,
892 cred,
893 &tdata->msg->line.req.method,
894 sess->pool, auth, &hauth);
895 if (status != PJ_SUCCESS)
896 return status;
897
898 //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
899 pj_list_push_back(&added, hauth);
900 }
901# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
902
903 auth = auth->next;
904 }
905
906 if (sess->pref.initial_auth == PJ_FALSE) {
907 pjsip_hdr *h;
908
909 /* Don't want to send initial empty Authorization header, so
910 * just send whatever available in the list (maybe empty).
911 */
912
913 h = added.next;
914 while (h != &added) {
915 pjsip_hdr *next = h->next;
916 pjsip_msg_add_hdr(tdata->msg, h);
917 h = next;
918 }
919 } else {
920 /* For each realm, add either the cached authorization header
921 * or add an empty authorization header.
922 */
923 unsigned i;
924 pj_str_t uri;
925
926 uri.ptr = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
927 uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
928 tdata->msg->line.req.uri,
929 uri.ptr, PJSIP_MAX_URL_SIZE);
930 if (uri.slen < 1 || uri.slen >= PJSIP_MAX_URL_SIZE)
931 return PJSIP_EURITOOLONG;
932
933 for (i=0; i<sess->cred_cnt; ++i) {
934 pjsip_cred_info *c = &sess->cred_info[i];
935 pjsip_authorization_hdr *h;
936
937 h = get_header_for_realm(&added, &c->realm);
938 if (h) {
939 pj_list_erase(h);
940 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
941 } else {
942 pjsip_authorization_hdr *hs;
943
944 hs = pjsip_authorization_hdr_create(tdata->pool);
945 pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
946 pj_strdup(tdata->pool, &hs->credential.digest.username,
947 &c->username);
948 pj_strdup(tdata->pool, &hs->credential.digest.realm,
949 &c->realm);
950 pj_strdup(tdata->pool, &hs->credential.digest.uri, &uri);
951 pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
952 &sess->pref.algorithm);
953
954 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
955 }
956 }
957 }
958
959 return PJ_SUCCESS;
960}
961
962
963/* Process authorization challenge */
964static pj_status_t process_auth( pj_pool_t *req_pool,
965 const pjsip_www_authenticate_hdr *hchal,
966 const pjsip_uri *uri,
967 pjsip_tx_data *tdata,
968 pjsip_auth_clt_sess *sess,
969 pjsip_cached_auth *cached_auth,
970 pjsip_authorization_hdr **h_auth)
971{
972 const pjsip_cred_info *cred;
973 pjsip_authorization_hdr *sent_auth = NULL;
974 pjsip_hdr *hdr;
975 pj_status_t status;
976
977 /* See if we have sent authorization header for this realm */
978 hdr = tdata->msg->hdr.next;
979 while (hdr != &tdata->msg->hdr) {
980 if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
981 hdr->type == PJSIP_H_AUTHORIZATION) ||
982 (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
983 hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
984 {
985 sent_auth = (pjsip_authorization_hdr*) hdr;
986 if (pj_stricmp(&hchal->challenge.common.realm,
987 &sent_auth->credential.common.realm )==0)
988 {
989 /* If this authorization has empty response, remove it. */
990 if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
991 sent_auth->credential.digest.response.slen == 0)
992 {
993 /* This is empty authorization, remove it. */
994 hdr = hdr->next;
995 pj_list_erase(sent_auth);
996 continue;
997 } else {
998 /* Found previous authorization attempt */
999 break;
1000 }
1001 }
1002 }
1003 hdr = hdr->next;
1004 }
1005
1006 /* If we have sent, see if server rejected because of stale nonce or
1007 * other causes.
1008 */
1009 if (hdr != &tdata->msg->hdr) {
1010 pj_bool_t stale;
1011
1012 /* Detect "stale" state */
1013 stale = hchal->challenge.digest.stale;
1014 if (!stale) {
1015 /* If stale is false, check is nonce has changed. Some servers
1016 * (broken ones!) want to change nonce but they fail to set
1017 * stale to true.
1018 */
1019 stale = pj_strcmp(&hchal->challenge.digest.nonce,
1020 &sent_auth->credential.digest.nonce);
1021 }
1022
1023 if (stale == PJ_FALSE) {
1024 /* Our credential is rejected. No point in trying to re-supply
1025 * the same credential.
1026 */
1027 PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
1028 "server rejected with stale=false",
1029 sent_auth->credential.digest.username.slen,
1030 sent_auth->credential.digest.username.ptr,
1031 sent_auth->credential.digest.realm.slen,
1032 sent_auth->credential.digest.realm.ptr));
1033 return PJSIP_EFAILEDCREDENTIAL;
1034 }
1035
1036 cached_auth->stale_cnt++;
1037 if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) {
1038 /* Our credential is rejected. No point in trying to re-supply
1039 * the same credential.
1040 */
1041 PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
1042 "maximum number of stale retries exceeded",
1043 sent_auth->credential.digest.username.slen,
1044 sent_auth->credential.digest.username.ptr,
1045 sent_auth->credential.digest.realm.slen,
1046 sent_auth->credential.digest.realm.ptr));
1047 return PJSIP_EAUTHSTALECOUNT;
1048 }
1049
1050 /* Otherwise remove old, stale authorization header from the mesasge.
1051 * We will supply a new one.
1052 */
1053 pj_list_erase(sent_auth);
1054 }
1055
1056 /* Find credential to be used for the challenge. */
1057 cred = auth_find_cred( sess, &hchal->challenge.common.realm,
1058 &hchal->scheme);
1059 if (!cred) {
1060 const pj_str_t *realm = &hchal->challenge.common.realm;
1061 PJ_LOG(4,(THIS_FILE,
1062 "Unable to set auth for %s: can not find credential for %.*s/%.*s",
1063 tdata->obj_name,
1064 realm->slen, realm->ptr,
1065 hchal->scheme.slen, hchal->scheme.ptr));
1066 return PJSIP_ENOCREDENTIAL;
1067 }
1068
1069 /* Respond to authorization challenge. */
1070 status = auth_respond( req_pool, hchal, uri, cred,
1071 &tdata->msg->line.req.method,
1072 sess->pool, cached_auth, h_auth);
1073 return status;
1074}
1075
1076
1077/* Reinitialize outgoing request after 401/407 response is received.
1078 * The purpose of this function is:
1079 * - to add a Authorization/Proxy-Authorization header.
1080 * - to put the newly created Authorization/Proxy-Authorization header
1081 * in cached_list.
1082 */
1083PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess,
1084 const pjsip_rx_data *rdata,
1085 pjsip_tx_data *old_request,
1086 pjsip_tx_data **new_request )
1087{
1088 pjsip_tx_data *tdata;
1089 const pjsip_hdr *hdr;
1090 unsigned chal_cnt;
1091 pjsip_via_hdr *via;
1092 pj_status_t status;
1093
1094 PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
1095 PJ_EINVAL);
1096 PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
1097 PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
1098 PJSIP_ENOTRESPONSEMSG);
1099 PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
1100 PJSIP_ENOTREQUESTMSG);
1101 PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
1102 rdata->msg_info.msg->line.status.code == 407,
1103 PJSIP_EINVALIDSTATUS);
1104
1105 tdata = old_request;
1106 tdata->auth_retry = PJ_FALSE;
1107
1108 /*
1109 * Respond to each authentication challenge.
1110 */
1111 hdr = rdata->msg_info.msg->hdr.next;
1112 chal_cnt = 0;
1113 while (hdr != &rdata->msg_info.msg->hdr) {
1114 pjsip_cached_auth *cached_auth;
1115 const pjsip_www_authenticate_hdr *hchal;
1116 pjsip_authorization_hdr *hauth;
1117
1118 /* Find WWW-Authenticate or Proxy-Authenticate header. */
1119 while (hdr != &rdata->msg_info.msg->hdr &&
1120 hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
1121 hdr->type != PJSIP_H_PROXY_AUTHENTICATE)
1122 {
1123 hdr = hdr->next;
1124 }
1125 if (hdr == &rdata->msg_info.msg->hdr)
1126 break;
1127
1128 hchal = (const pjsip_www_authenticate_hdr*) hdr;
1129 ++chal_cnt;
1130
1131 /* Find authentication session for this realm, create a new one
1132 * if not present.
1133 */
1134 cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm );
1135 if (!cached_auth) {
1136 cached_auth = PJ_POOL_ZALLOC_T( sess->pool, pjsip_cached_auth);
1137 pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm);
1138 cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
1139# if (PJSIP_AUTH_HEADER_CACHING)
1140 {
1141 pj_list_init(&cached_auth->cached_hdr);
1142 }
1143# endif
1144 pj_list_insert_before( &sess->cached_auth, cached_auth );
1145 }
1146
1147 /* Create authorization header for this challenge, and update
1148 * authorization session.
1149 */
1150 status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri,
1151 tdata, sess, cached_auth, &hauth);
1152 if (status != PJ_SUCCESS)
1153 return status;
1154
1155 /* Add to the message. */
1156 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
1157
1158 /* Process next header. */
1159 hdr = hdr->next;
1160 }
1161
1162 /* Check if challenge is present */
1163 if (chal_cnt == 0)
1164 return PJSIP_EAUTHNOCHAL;
1165
1166 /* Remove branch param in Via header. */
1167 via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
1168 via->branch_param.slen = 0;
1169
1170 /* Restore strict route set.
1171 * See http://trac.pjsip.org/repos/ticket/492
1172 */
1173 pjsip_restore_strict_route_set(tdata);
1174
1175 /* Must invalidate the message! */
1176 pjsip_tx_data_invalidate_msg(tdata);
1177
1178 /* Retrying.. */
1179 tdata->auth_retry = PJ_TRUE;
1180
1181 /* Increment reference counter. */
1182 pjsip_tx_data_add_ref(tdata);
1183
1184 /* Done. */
1185 *new_request = tdata;
1186 return PJ_SUCCESS;
1187
1188}
1189