1269 lines
36 KiB
C
1269 lines
36 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
|
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <pjsip/sip_auth.h>
|
|
#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
|
|
#include <pjsip/sip_auth_aka.h>
|
|
#include <pjsip/sip_transport.h>
|
|
#include <pjsip/sip_endpoint.h>
|
|
#include <pjsip/sip_errno.h>
|
|
#include <pjsip/sip_util.h>
|
|
#include <pjlib-util/md5.h>
|
|
#include <pj/log.h>
|
|
#include <pj/string.h>
|
|
#include <pj/pool.h>
|
|
#include <pj/guid.h>
|
|
#include <pj/assert.h>
|
|
#include <pj/ctype.h>
|
|
|
|
|
|
|
|
/* A macro just to get rid of type mismatch between char and unsigned char */
|
|
#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, \
|
|
(unsigned)len)
|
|
|
|
/* Logging. */
|
|
#define THIS_FILE "sip_auth_client.c"
|
|
#if 0
|
|
# define AUTH_TRACE_(expr) PJ_LOG(3, expr)
|
|
#else
|
|
# define AUTH_TRACE_(expr)
|
|
#endif
|
|
|
|
#define PASSWD_MASK 0x000F
|
|
#define EXT_MASK 0x00F0
|
|
|
|
|
|
static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src)
|
|
{
|
|
dst->slen = src->slen;
|
|
|
|
if (dst->slen) {
|
|
dst->ptr = (char*) pj_pool_alloc(pool, src->slen);
|
|
pj_memcpy(dst->ptr, src->ptr, src->slen);
|
|
} else {
|
|
dst->ptr = NULL;
|
|
}
|
|
}
|
|
|
|
PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool,
|
|
pjsip_cred_info *dst,
|
|
const pjsip_cred_info *src)
|
|
{
|
|
pj_memcpy(dst, src, sizeof(pjsip_cred_info));
|
|
|
|
pj_strdup_with_null(pool, &dst->realm, &src->realm);
|
|
pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
|
|
pj_strdup_with_null(pool, &dst->username, &src->username);
|
|
pj_strdup_with_null(pool, &dst->data, &src->data);
|
|
|
|
if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
|
|
dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k);
|
|
dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op);
|
|
dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf);
|
|
}
|
|
}
|
|
|
|
|
|
PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
|
|
const pjsip_cred_info *cred2)
|
|
{
|
|
int result;
|
|
|
|
result = pj_strcmp(&cred1->realm, &cred2->realm);
|
|
if (result) goto on_return;
|
|
result = pj_strcmp(&cred1->scheme, &cred2->scheme);
|
|
if (result) goto on_return;
|
|
result = pj_strcmp(&cred1->username, &cred2->username);
|
|
if (result) goto on_return;
|
|
result = pj_strcmp(&cred1->data, &cred2->data);
|
|
if (result) goto on_return;
|
|
result = (cred1->data_type != cred2->data_type);
|
|
if (result) goto on_return;
|
|
|
|
if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
|
|
result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
|
|
if (result) goto on_return;
|
|
result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
|
|
if (result) goto on_return;
|
|
result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
|
|
if (result) goto on_return;
|
|
}
|
|
|
|
on_return:
|
|
return result;
|
|
}
|
|
|
|
PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool,
|
|
pjsip_auth_clt_pref *dst,
|
|
const pjsip_auth_clt_pref *src)
|
|
{
|
|
pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref));
|
|
pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm);
|
|
}
|
|
|
|
|
|
/* Transform digest to string.
|
|
* output must be at least PJSIP_MD5STRLEN+1 bytes.
|
|
*
|
|
* NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
|
|
*/
|
|
static void digest2str(const unsigned char digest[], char *output)
|
|
{
|
|
int i;
|
|
for (i = 0; i<16; ++i) {
|
|
pj_val_to_hex_digit(digest[i], output);
|
|
output += 2;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Create response digest based on the parameters and store the
|
|
* digest ASCII in 'result'.
|
|
*/
|
|
PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
|
|
const pj_str_t *nonce,
|
|
const pj_str_t *nc,
|
|
const pj_str_t *cnonce,
|
|
const pj_str_t *qop,
|
|
const pj_str_t *uri,
|
|
const pj_str_t *realm,
|
|
const pjsip_cred_info *cred_info,
|
|
const pj_str_t *method)
|
|
{
|
|
char ha1[PJSIP_MD5STRLEN];
|
|
char ha2[PJSIP_MD5STRLEN];
|
|
unsigned char digest[16];
|
|
pj_md5_context pms;
|
|
|
|
pj_assert(result->slen >= PJSIP_MD5STRLEN);
|
|
|
|
AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
|
|
|
|
if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) {
|
|
/***
|
|
*** ha1 = MD5(username ":" realm ":" password)
|
|
***/
|
|
pj_md5_init(&pms);
|
|
MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, realm->ptr, realm->slen);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
|
|
pj_md5_final(&pms, digest);
|
|
|
|
digest2str(digest, ha1);
|
|
|
|
} else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
|
|
pj_assert(cred_info->data.slen == 32);
|
|
pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
|
|
} else {
|
|
pj_assert(!"Invalid data_type");
|
|
}
|
|
|
|
AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1));
|
|
|
|
/***
|
|
*** ha2 = MD5(method ":" req_uri)
|
|
***/
|
|
pj_md5_init(&pms);
|
|
MD5_APPEND( &pms, method->ptr, method->slen);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, uri->ptr, uri->slen);
|
|
pj_md5_final(&pms, digest);
|
|
digest2str(digest, ha2);
|
|
|
|
AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2));
|
|
|
|
/***
|
|
*** When qop is not used:
|
|
*** response = MD5(ha1 ":" nonce ":" ha2)
|
|
***
|
|
*** When qop=auth is used:
|
|
*** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
|
|
***/
|
|
pj_md5_init(&pms);
|
|
MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, nonce->ptr, nonce->slen);
|
|
if (qop && qop->slen != 0) {
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, nc->ptr, nc->slen);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, qop->ptr, qop->slen);
|
|
}
|
|
MD5_APPEND( &pms, ":", 1);
|
|
MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
|
|
|
|
/* This is the final response digest. */
|
|
pj_md5_final(&pms, digest);
|
|
|
|
/* Convert digest to string and store in chal->response. */
|
|
result->slen = PJSIP_MD5STRLEN;
|
|
digest2str(digest, result->ptr);
|
|
|
|
AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr));
|
|
AUTH_TRACE_((THIS_FILE, "Digest created"));
|
|
}
|
|
|
|
/*
|
|
* Finds out if qop offer contains "auth" token.
|
|
*/
|
|
static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
|
|
{
|
|
pj_str_t qop;
|
|
char *p;
|
|
|
|
pj_strdup_with_null( pool, &qop, qop_offer);
|
|
p = qop.ptr;
|
|
while (*p) {
|
|
*p = (char)pj_tolower(*p);
|
|
++p;
|
|
}
|
|
|
|
p = qop.ptr;
|
|
while (*p) {
|
|
if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
|
|
int e = *(p+4);
|
|
if (e=='"' || e==',' || e==0)
|
|
return PJ_TRUE;
|
|
else
|
|
p += 4;
|
|
} else {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
return PJ_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Generate response digest.
|
|
* Most of the parameters to generate the digest (i.e. username, realm, uri,
|
|
* and nonce) are expected to be in the credential. Additional parameters (i.e.
|
|
* password and method param) should be supplied in the argument.
|
|
*
|
|
* The resulting digest will be stored in cred->response.
|
|
* The pool is used to allocate 32 bytes to store the digest in cred->response.
|
|
*/
|
|
static pj_status_t respond_digest( pj_pool_t *pool,
|
|
pjsip_digest_credential *cred,
|
|
const pjsip_digest_challenge *chal,
|
|
const pj_str_t *uri,
|
|
const pjsip_cred_info *cred_info,
|
|
const pj_str_t *cnonce,
|
|
pj_uint32_t nc,
|
|
const pj_str_t *method)
|
|
{
|
|
const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
|
|
|
|
/* Check algorithm is supported. We support MD5 and AKAv1-MD5. */
|
|
if (chal->algorithm.slen==0 ||
|
|
(pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 ||
|
|
pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0))
|
|
{
|
|
;
|
|
}
|
|
else {
|
|
PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
|
|
chal->algorithm.slen, chal->algorithm.ptr));
|
|
return PJSIP_EINVALIDALGORITHM;
|
|
}
|
|
|
|
/* Build digest credential from arguments. */
|
|
pj_strdup(pool, &cred->username, &cred_info->username);
|
|
pj_strdup(pool, &cred->realm, &chal->realm);
|
|
pj_strdup(pool, &cred->nonce, &chal->nonce);
|
|
pj_strdup(pool, &cred->uri, uri);
|
|
pj_strdup(pool, &cred->algorithm, &chal->algorithm);
|
|
pj_strdup(pool, &cred->opaque, &chal->opaque);
|
|
|
|
/* Allocate memory. */
|
|
cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN);
|
|
cred->response.slen = PJSIP_MD5STRLEN;
|
|
|
|
if (chal->qop.slen == 0) {
|
|
/* Server doesn't require quality of protection. */
|
|
|
|
if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
|
|
/* Call application callback to create the response digest */
|
|
return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
|
|
method, cred);
|
|
}
|
|
else {
|
|
/* Convert digest to string and store in chal->response. */
|
|
pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
|
|
NULL, NULL, uri, &chal->realm,
|
|
cred_info, method);
|
|
}
|
|
|
|
} else if (has_auth_qop(pool, &chal->qop)) {
|
|
/* Server requires quality of protection.
|
|
* We respond with selecting "qop=auth" protection.
|
|
*/
|
|
cred->qop = pjsip_AUTH_STR;
|
|
cred->nc.ptr = (char*) pj_pool_alloc(pool, 16);
|
|
cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc);
|
|
|
|
if (cnonce && cnonce->slen) {
|
|
pj_strdup(pool, &cred->cnonce, cnonce);
|
|
} else {
|
|
pj_str_t dummy_cnonce = { "b39971", 6};
|
|
pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
|
|
}
|
|
|
|
if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
|
|
/* Call application callback to create the response digest */
|
|
return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
|
|
method, cred);
|
|
}
|
|
else {
|
|
pjsip_auth_create_digest( &cred->response, &cred->nonce,
|
|
&cred->nc, &cred->cnonce, &pjsip_AUTH_STR,
|
|
uri, &chal->realm, cred_info, method );
|
|
}
|
|
|
|
} else {
|
|
/* Server requires quality protection that we don't support. */
|
|
PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s",
|
|
chal->qop.slen, chal->qop.ptr));
|
|
return PJSIP_EINVALIDQOP;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
|
|
/*
|
|
* Update authentication session with a challenge.
|
|
*/
|
|
static void update_digest_session( pjsip_cached_auth *cached_auth,
|
|
const pjsip_www_authenticate_hdr *hdr )
|
|
{
|
|
if (hdr->challenge.digest.qop.slen == 0) {
|
|
#if PJSIP_AUTH_AUTO_SEND_NEXT!=0
|
|
if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) {
|
|
cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
|
|
pjsip_hdr_clone(cached_auth->pool, hdr);
|
|
} else {
|
|
/* Only update if the new challenge is "significantly different"
|
|
* than the one in the cache, to reduce memory usage.
|
|
*/
|
|
const pjsip_digest_challenge *d1 =
|
|
&cached_auth->last_chal->challenge.digest;
|
|
const pjsip_digest_challenge *d2 = &hdr->challenge.digest;
|
|
|
|
if (pj_strcmp(&d1->domain, &d2->domain) ||
|
|
pj_strcmp(&d1->realm, &d2->realm) ||
|
|
pj_strcmp(&d1->nonce, &d2->nonce) ||
|
|
pj_strcmp(&d1->opaque, &d2->opaque) ||
|
|
pj_strcmp(&d1->algorithm, &d2->algorithm) ||
|
|
pj_strcmp(&d1->qop, &d2->qop))
|
|
{
|
|
cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
|
|
pjsip_hdr_clone(cached_auth->pool, hdr);
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* Initialize cnonce and qop if not present. */
|
|
if (cached_auth->cnonce.slen == 0) {
|
|
/* Save the whole challenge */
|
|
cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
|
|
pjsip_hdr_clone(cached_auth->pool, hdr);
|
|
|
|
/* Create cnonce */
|
|
pj_create_unique_string( cached_auth->pool, &cached_auth->cnonce );
|
|
#if defined(PJSIP_AUTH_CNONCE_USE_DIGITS_ONLY) && \
|
|
PJSIP_AUTH_CNONCE_USE_DIGITS_ONLY!=0
|
|
if (pj_strchr(&cached_auth->cnonce, '-')) {
|
|
/* remove hyphen character. */
|
|
int w, r, len = pj_strlen(&cached_auth->cnonce);
|
|
char *s = cached_auth->cnonce.ptr;
|
|
|
|
w = r = 0;
|
|
for (; r < len; r++) {
|
|
if (s[r] != '-')
|
|
s[w++] = s[r];
|
|
}
|
|
s[w] = '\0';
|
|
cached_auth->cnonce.slen = w;
|
|
}
|
|
#endif
|
|
|
|
/* Initialize nonce-count */
|
|
cached_auth->nc = 1;
|
|
|
|
/* Save realm. */
|
|
/* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061)
|
|
pj_assert(cached_auth->realm.slen != 0);
|
|
*/
|
|
if (cached_auth->realm.slen == 0) {
|
|
pj_strdup(cached_auth->pool, &cached_auth->realm,
|
|
&hdr->challenge.digest.realm);
|
|
}
|
|
|
|
} else {
|
|
/* Update last_nonce and nonce-count */
|
|
if (!pj_strcmp(&hdr->challenge.digest.nonce,
|
|
&cached_auth->last_chal->challenge.digest.nonce))
|
|
{
|
|
/* Same nonce, increment nonce-count */
|
|
++cached_auth->nc;
|
|
} else {
|
|
/* Server gives new nonce. */
|
|
pj_strdup(cached_auth->pool,
|
|
&cached_auth->last_chal->challenge.digest.nonce,
|
|
&hdr->challenge.digest.nonce);
|
|
/* Has the opaque changed? */
|
|
if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
|
|
&hdr->challenge.digest.opaque))
|
|
{
|
|
pj_strdup(cached_auth->pool,
|
|
&cached_auth->last_chal->challenge.digest.opaque,
|
|
&hdr->challenge.digest.opaque);
|
|
}
|
|
cached_auth->nc = 1;
|
|
}
|
|
}
|
|
}
|
|
#endif /* PJSIP_AUTH_QOP_SUPPORT */
|
|
|
|
|
|
/* Find cached authentication in the list for the specified realm. */
|
|
static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
|
|
const pj_str_t *realm )
|
|
{
|
|
pjsip_cached_auth *auth = sess->cached_auth.next;
|
|
while (auth != &sess->cached_auth) {
|
|
if (pj_stricmp(&auth->realm, realm) == 0)
|
|
return auth;
|
|
auth = auth->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Find credential to use for the specified realm and auth scheme. */
|
|
static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
|
|
const pj_str_t *realm,
|
|
const pj_str_t *auth_scheme)
|
|
{
|
|
unsigned i;
|
|
int wildcard = -1;
|
|
|
|
PJ_UNUSED_ARG(auth_scheme);
|
|
|
|
for (i=0; i<sess->cred_cnt; ++i) {
|
|
if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
|
|
return &sess->cred_info[i];
|
|
else if (sess->cred_info[i].realm.slen == 1 &&
|
|
sess->cred_info[i].realm.ptr[0] == '*')
|
|
{
|
|
wildcard = i;
|
|
}
|
|
}
|
|
|
|
/* No matching realm. See if we have credential with wildcard ('*')
|
|
* as the realm.
|
|
*/
|
|
if (wildcard != -1)
|
|
return &sess->cred_info[wildcard];
|
|
|
|
/* Nothing is suitable */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Init client session. */
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess,
|
|
pjsip_endpoint *endpt,
|
|
pj_pool_t *pool,
|
|
unsigned options)
|
|
{
|
|
PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
|
|
|
|
sess->pool = pool;
|
|
sess->endpt = endpt;
|
|
sess->cred_cnt = 0;
|
|
sess->cred_info = NULL;
|
|
pj_list_init(&sess->cached_auth);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Deinit client session. */
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_deinit(pjsip_auth_clt_sess *sess)
|
|
{
|
|
pjsip_cached_auth *auth;
|
|
|
|
PJ_ASSERT_RETURN(sess && sess->endpt, PJ_EINVAL);
|
|
|
|
auth = sess->cached_auth.next;
|
|
while (auth != &sess->cached_auth) {
|
|
pjsip_endpt_release_pool(sess->endpt, auth->pool);
|
|
auth = auth->next;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Clone session. */
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool,
|
|
pjsip_auth_clt_sess *sess,
|
|
const pjsip_auth_clt_sess *rhs )
|
|
{
|
|
unsigned i;
|
|
|
|
PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL);
|
|
|
|
pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0);
|
|
|
|
sess->cred_cnt = rhs->cred_cnt;
|
|
sess->cred_info = (pjsip_cred_info*)
|
|
pj_pool_alloc(pool,
|
|
sess->cred_cnt*sizeof(pjsip_cred_info));
|
|
for (i=0; i<rhs->cred_cnt; ++i) {
|
|
pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm);
|
|
pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme);
|
|
pj_strdup(pool, &sess->cred_info[i].username,
|
|
&rhs->cred_info[i].username);
|
|
sess->cred_info[i].data_type = rhs->cred_info[i].data_type;
|
|
pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data);
|
|
}
|
|
|
|
/* TODO note:
|
|
* Cloning the full authentication client is quite a big task.
|
|
* We do only the necessary bits here, i.e. cloning the credentials.
|
|
* The drawback of this basic approach is, a forked dialog will have to
|
|
* re-authenticate itself on the next request because it has lost the
|
|
* cached authentication headers.
|
|
*/
|
|
PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Set client credentials. */
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
|
|
int cred_cnt,
|
|
const pjsip_cred_info *c)
|
|
{
|
|
PJ_ASSERT_RETURN(sess && c, PJ_EINVAL);
|
|
|
|
if (cred_cnt == 0) {
|
|
sess->cred_cnt = 0;
|
|
} else {
|
|
int i;
|
|
sess->cred_info = (pjsip_cred_info*)
|
|
pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
|
|
for (i=0; i<cred_cnt; ++i) {
|
|
sess->cred_info[i].data_type = c[i].data_type;
|
|
|
|
/* When data_type is PJSIP_CRED_DATA_EXT_AKA,
|
|
* callback must be specified.
|
|
*/
|
|
if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
|
|
|
|
#if !PJSIP_HAS_DIGEST_AKA_AUTH
|
|
if (!PJSIP_HAS_DIGEST_AKA_AUTH) {
|
|
pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled");
|
|
return PJSIP_EAUTHINAKACRED;
|
|
}
|
|
#endif
|
|
|
|
/* Callback must be specified */
|
|
PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL);
|
|
|
|
/* Verify K len */
|
|
PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN,
|
|
PJSIP_EAUTHINAKACRED);
|
|
|
|
/* Verify OP len */
|
|
PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN,
|
|
PJSIP_EAUTHINAKACRED);
|
|
|
|
/* Verify AMF len */
|
|
PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN,
|
|
PJSIP_EAUTHINAKACRED);
|
|
|
|
sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb;
|
|
pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k,
|
|
&c[i].ext.aka.k);
|
|
pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op,
|
|
&c[i].ext.aka.op);
|
|
pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf,
|
|
&c[i].ext.aka.amf);
|
|
}
|
|
|
|
pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme);
|
|
pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm);
|
|
pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username);
|
|
pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data);
|
|
}
|
|
sess->cred_cnt = cred_cnt;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the preference for the client authentication session.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess,
|
|
const pjsip_auth_clt_pref *p)
|
|
{
|
|
PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
|
|
|
|
pj_memcpy(&sess->pref, p, sizeof(*p));
|
|
pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm);
|
|
//if (sess->pref.algorithm.slen == 0)
|
|
// sess->pref.algorithm = pj_str("md5");
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the preference for the client authentication session.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess,
|
|
pjsip_auth_clt_pref *p)
|
|
{
|
|
PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
|
|
|
|
pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref));
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create Authorization/Proxy-Authorization response header based on the challege
|
|
* in WWW-Authenticate/Proxy-Authenticate header.
|
|
*/
|
|
static pj_status_t auth_respond( pj_pool_t *req_pool,
|
|
const pjsip_www_authenticate_hdr *hdr,
|
|
const pjsip_uri *uri,
|
|
const pjsip_cred_info *cred_info,
|
|
const pjsip_method *method,
|
|
pj_pool_t *sess_pool,
|
|
pjsip_cached_auth *cached_auth,
|
|
pjsip_authorization_hdr **p_h_auth)
|
|
{
|
|
pjsip_authorization_hdr *hauth;
|
|
char tmp[PJSIP_MAX_URL_SIZE];
|
|
pj_str_t uri_str;
|
|
pj_pool_t *pool;
|
|
pj_status_t status;
|
|
|
|
/* Verify arguments. */
|
|
PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
|
|
sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
|
|
|
|
/* Print URL in the original request. */
|
|
uri_str.ptr = tmp;
|
|
uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
|
|
if (uri_str.slen < 1) {
|
|
return PJSIP_EURITOOLONG;
|
|
}
|
|
|
|
# if (PJSIP_AUTH_HEADER_CACHING)
|
|
{
|
|
pool = sess_pool;
|
|
PJ_UNUSED_ARG(req_pool);
|
|
}
|
|
# else
|
|
{
|
|
pool = req_pool;
|
|
PJ_UNUSED_ARG(sess_pool);
|
|
}
|
|
# endif
|
|
|
|
if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
|
|
hauth = pjsip_authorization_hdr_create(pool);
|
|
else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
|
|
hauth = pjsip_proxy_authorization_hdr_create(pool);
|
|
else {
|
|
return PJSIP_EINVALIDHDR;
|
|
}
|
|
|
|
/* Only support digest scheme at the moment. */
|
|
if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
|
|
pj_str_t *cnonce = NULL;
|
|
pj_uint32_t nc = 1;
|
|
|
|
/* Update the session (nonce-count etc) if required. */
|
|
# if PJSIP_AUTH_QOP_SUPPORT
|
|
{
|
|
if (cached_auth) {
|
|
update_digest_session( cached_auth, hdr );
|
|
|
|
cnonce = &cached_auth->cnonce;
|
|
nc = cached_auth->nc;
|
|
}
|
|
}
|
|
# endif /* PJSIP_AUTH_QOP_SUPPORT */
|
|
|
|
hauth->scheme = pjsip_DIGEST_STR;
|
|
status = respond_digest( pool, &hauth->credential.digest,
|
|
&hdr->challenge.digest, &uri_str, cred_info,
|
|
cnonce, nc, &method->name);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Set qop type in auth session the first time only. */
|
|
if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
|
|
if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
|
|
pj_str_t *qop_val = &hauth->credential.digest.qop;
|
|
if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
|
|
cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
|
|
} else {
|
|
cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return PJSIP_EINVALIDAUTHSCHEME;
|
|
}
|
|
|
|
/* Keep the new authorization header in the cache, only
|
|
* if no qop is not present.
|
|
*/
|
|
# if PJSIP_AUTH_HEADER_CACHING
|
|
{
|
|
if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
|
|
pjsip_cached_auth_hdr *cached_hdr;
|
|
|
|
/* Delete old header with the same method. */
|
|
cached_hdr = cached_auth->cached_hdr.next;
|
|
while (cached_hdr != &cached_auth->cached_hdr) {
|
|
if (pjsip_method_cmp(method, &cached_hdr->method)==0)
|
|
break;
|
|
cached_hdr = cached_hdr->next;
|
|
}
|
|
|
|
/* Save the header to the list. */
|
|
if (cached_hdr != &cached_auth->cached_hdr) {
|
|
cached_hdr->hdr = hauth;
|
|
} else {
|
|
cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
|
|
pjsip_method_copy( pool, &cached_hdr->method, method);
|
|
cached_hdr->hdr = hauth;
|
|
pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
|
|
}
|
|
}
|
|
|
|
# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
|
|
if (hdr != cached_auth->last_chal) {
|
|
cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr);
|
|
}
|
|
# endif
|
|
}
|
|
# endif
|
|
|
|
*p_h_auth = hauth;
|
|
return PJ_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
|
|
static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
|
|
pjsip_auth_clt_sess *sess,
|
|
pjsip_cached_auth *auth,
|
|
pjsip_authorization_hdr **p_h_auth)
|
|
{
|
|
const pjsip_cred_info *cred;
|
|
pjsip_authorization_hdr *hauth;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
|
|
|
|
cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
|
|
if (!cred)
|
|
return PJSIP_ENOCREDENTIAL;
|
|
|
|
status = auth_respond( tdata->pool, auth->last_chal,
|
|
tdata->msg->line.req.uri,
|
|
cred, &tdata->msg->line.req.method,
|
|
sess->pool, auth, &hauth);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
|
|
|
|
if (p_h_auth)
|
|
*p_h_auth = hauth;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* Find credential in list of (Proxy-)Authorization headers */
|
|
static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list,
|
|
const pj_str_t *realm)
|
|
{
|
|
pjsip_authorization_hdr *h;
|
|
|
|
h = (pjsip_authorization_hdr*)hdr_list->next;
|
|
while (h != (pjsip_authorization_hdr*)hdr_list) {
|
|
if (pj_stricmp(&h->credential.digest.realm, realm)==0)
|
|
return h;
|
|
h = h->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Initialize outgoing request. */
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
|
|
pjsip_tx_data *tdata )
|
|
{
|
|
const pjsip_method *method;
|
|
pjsip_cached_auth *auth;
|
|
pjsip_hdr added;
|
|
|
|
PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
|
|
PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
|
|
PJSIP_ENOTREQUESTMSG);
|
|
|
|
/* Init list */
|
|
pj_list_init(&added);
|
|
|
|
/* Get the method. */
|
|
method = &tdata->msg->line.req.method;
|
|
PJ_UNUSED_ARG(method); /* Warning about unused var caused by #if below */
|
|
|
|
auth = sess->cached_auth.next;
|
|
while (auth != &sess->cached_auth) {
|
|
/* Reset stale counter */
|
|
auth->stale_cnt = 0;
|
|
|
|
if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
|
|
# if defined(PJSIP_AUTH_HEADER_CACHING) && \
|
|
PJSIP_AUTH_HEADER_CACHING!=0
|
|
{
|
|
pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
|
|
while (entry != &auth->cached_hdr) {
|
|
if (pjsip_method_cmp(&entry->method, method)==0) {
|
|
pjsip_authorization_hdr *hauth;
|
|
hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
|
|
//pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
|
|
pj_list_push_back(&added, hauth);
|
|
break;
|
|
}
|
|
entry = entry->next;
|
|
}
|
|
|
|
# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
|
|
PJSIP_AUTH_AUTO_SEND_NEXT!=0
|
|
{
|
|
if (entry == &auth->cached_hdr)
|
|
new_auth_for_req( tdata, sess, auth, NULL);
|
|
}
|
|
# endif
|
|
|
|
}
|
|
# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
|
|
PJSIP_AUTH_AUTO_SEND_NEXT!=0
|
|
{
|
|
new_auth_for_req( tdata, sess, auth, NULL);
|
|
}
|
|
# endif
|
|
|
|
}
|
|
# if defined(PJSIP_AUTH_QOP_SUPPORT) && \
|
|
defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
|
|
(PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
|
|
else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
|
|
/* For qop="auth", we have to re-create the authorization header.
|
|
*/
|
|
const pjsip_cred_info *cred;
|
|
pjsip_authorization_hdr *hauth;
|
|
pj_status_t status;
|
|
|
|
cred = auth_find_cred(sess, &auth->realm,
|
|
&auth->last_chal->scheme);
|
|
if (!cred) {
|
|
auth = auth->next;
|
|
continue;
|
|
}
|
|
|
|
status = auth_respond( tdata->pool, auth->last_chal,
|
|
tdata->msg->line.req.uri,
|
|
cred,
|
|
&tdata->msg->line.req.method,
|
|
sess->pool, auth, &hauth);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
//pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
|
|
pj_list_push_back(&added, hauth);
|
|
}
|
|
# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
|
|
|
|
auth = auth->next;
|
|
}
|
|
|
|
if (sess->pref.initial_auth == PJ_FALSE) {
|
|
pjsip_hdr *h;
|
|
|
|
/* Don't want to send initial empty Authorization header, so
|
|
* just send whatever available in the list (maybe empty).
|
|
*/
|
|
|
|
h = added.next;
|
|
while (h != &added) {
|
|
pjsip_hdr *next = h->next;
|
|
pjsip_msg_add_hdr(tdata->msg, h);
|
|
h = next;
|
|
}
|
|
} else {
|
|
/* For each realm, add either the cached authorization header
|
|
* or add an empty authorization header.
|
|
*/
|
|
unsigned i;
|
|
pj_str_t uri;
|
|
|
|
uri.ptr = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
|
|
uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
|
|
tdata->msg->line.req.uri,
|
|
uri.ptr, PJSIP_MAX_URL_SIZE);
|
|
if (uri.slen < 1 || uri.slen >= PJSIP_MAX_URL_SIZE)
|
|
return PJSIP_EURITOOLONG;
|
|
|
|
for (i=0; i<sess->cred_cnt; ++i) {
|
|
pjsip_cred_info *c = &sess->cred_info[i];
|
|
pjsip_authorization_hdr *h;
|
|
|
|
h = get_header_for_realm(&added, &c->realm);
|
|
if (h) {
|
|
pj_list_erase(h);
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
|
|
} else {
|
|
pjsip_authorization_hdr *hs;
|
|
|
|
hs = pjsip_authorization_hdr_create(tdata->pool);
|
|
pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
|
|
if (pj_stricmp(&c->scheme, &pjsip_BEARER_STR)==0) {
|
|
pj_strdup(tdata->pool, &hs->credential.oauth.username,
|
|
&c->username);
|
|
pj_strdup(tdata->pool, &hs->credential.oauth.realm,
|
|
&c->realm);
|
|
pj_strdup(tdata->pool, &hs->credential.oauth.token,
|
|
&c->data);
|
|
} else { //if (pj_stricmp(&c->scheme, &pjsip_DIGEST_STR)==0)
|
|
pj_strdup(tdata->pool, &hs->credential.digest.username,
|
|
&c->username);
|
|
pj_strdup(tdata->pool, &hs->credential.digest.realm,
|
|
&c->realm);
|
|
pj_strdup(tdata->pool,&hs->credential.digest.uri, &uri);
|
|
pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
|
|
&sess->pref.algorithm);
|
|
}
|
|
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
|
|
}
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
static void recreate_cached_auth_pool( pjsip_endpoint *endpt,
|
|
pjsip_cached_auth *auth )
|
|
{
|
|
pj_pool_t *auth_pool = pjsip_endpt_create_pool(endpt, "auth_cli%p", 1024,
|
|
1024);
|
|
|
|
if (auth->realm.slen) {
|
|
pj_str_t realm;
|
|
pj_strdup(auth_pool, &realm, &auth->realm);
|
|
pj_strassign(&auth->realm, &realm);
|
|
}
|
|
|
|
if (auth->cnonce.slen) {
|
|
pj_str_t cnonce;
|
|
pj_strdup(auth_pool, &cnonce, &auth->cnonce);
|
|
pj_strassign(&auth->cnonce, &cnonce);
|
|
}
|
|
|
|
if (auth->last_chal) {
|
|
auth->last_chal = (pjsip_www_authenticate_hdr*)
|
|
pjsip_hdr_clone(auth_pool, auth->last_chal);
|
|
}
|
|
|
|
pjsip_endpt_release_pool(endpt, auth->pool);
|
|
auth->pool = auth_pool;
|
|
}
|
|
|
|
/* Process authorization challenge */
|
|
static pj_status_t process_auth( pj_pool_t *req_pool,
|
|
const pjsip_www_authenticate_hdr *hchal,
|
|
const pjsip_uri *uri,
|
|
pjsip_tx_data *tdata,
|
|
pjsip_auth_clt_sess *sess,
|
|
pjsip_cached_auth *cached_auth,
|
|
pjsip_authorization_hdr **h_auth)
|
|
{
|
|
const pjsip_cred_info *cred;
|
|
pjsip_authorization_hdr *sent_auth = NULL;
|
|
pjsip_hdr *hdr;
|
|
pj_status_t status;
|
|
|
|
/* See if we have sent authorization header for this realm */
|
|
hdr = tdata->msg->hdr.next;
|
|
while (hdr != &tdata->msg->hdr) {
|
|
if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
|
|
hdr->type == PJSIP_H_AUTHORIZATION) ||
|
|
(hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
|
|
hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
|
|
{
|
|
sent_auth = (pjsip_authorization_hdr*) hdr;
|
|
if (pj_stricmp(&hchal->challenge.common.realm,
|
|
&sent_auth->credential.common.realm )==0)
|
|
{
|
|
/* If this authorization has empty response, remove it. */
|
|
if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
|
|
sent_auth->credential.digest.response.slen == 0)
|
|
{
|
|
/* This is empty authorization, remove it. */
|
|
hdr = hdr->next;
|
|
pj_list_erase(sent_auth);
|
|
continue;
|
|
} else {
|
|
/* Found previous authorization attempt */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
/* If we have sent, see if server rejected because of stale nonce or
|
|
* other causes.
|
|
*/
|
|
if (hdr != &tdata->msg->hdr) {
|
|
pj_bool_t stale;
|
|
|
|
/* Detect "stale" state */
|
|
stale = hchal->challenge.digest.stale;
|
|
if (!stale) {
|
|
/* If stale is false, check is nonce has changed. Some servers
|
|
* (broken ones!) want to change nonce but they fail to set
|
|
* stale to true.
|
|
*/
|
|
stale = pj_strcmp(&hchal->challenge.digest.nonce,
|
|
&sent_auth->credential.digest.nonce);
|
|
}
|
|
|
|
if (stale == PJ_FALSE) {
|
|
/* Our credential is rejected. No point in trying to re-supply
|
|
* the same credential.
|
|
*/
|
|
PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
|
|
"server rejected with stale=false",
|
|
sent_auth->credential.digest.username.slen,
|
|
sent_auth->credential.digest.username.ptr,
|
|
sent_auth->credential.digest.realm.slen,
|
|
sent_auth->credential.digest.realm.ptr));
|
|
return PJSIP_EFAILEDCREDENTIAL;
|
|
}
|
|
|
|
cached_auth->stale_cnt++;
|
|
if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) {
|
|
/* Our credential is rejected. No point in trying to re-supply
|
|
* the same credential.
|
|
*/
|
|
PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
|
|
"maximum number of stale retries exceeded",
|
|
sent_auth->credential.digest.username.slen,
|
|
sent_auth->credential.digest.username.ptr,
|
|
sent_auth->credential.digest.realm.slen,
|
|
sent_auth->credential.digest.realm.ptr));
|
|
return PJSIP_EAUTHSTALECOUNT;
|
|
}
|
|
|
|
/* Otherwise remove old, stale authorization header from the mesasge.
|
|
* We will supply a new one.
|
|
*/
|
|
pj_list_erase(sent_auth);
|
|
}
|
|
|
|
/* Find credential to be used for the challenge. */
|
|
cred = auth_find_cred( sess, &hchal->challenge.common.realm,
|
|
&hchal->scheme);
|
|
if (!cred) {
|
|
const pj_str_t *realm = &hchal->challenge.common.realm;
|
|
PJ_LOG(4,(THIS_FILE,
|
|
"Unable to set auth for %s: can not find credential for %.*s/%.*s",
|
|
tdata->obj_name,
|
|
realm->slen, realm->ptr,
|
|
hchal->scheme.slen, hchal->scheme.ptr));
|
|
return PJSIP_ENOCREDENTIAL;
|
|
}
|
|
|
|
/* Respond to authorization challenge. */
|
|
status = auth_respond( req_pool, hchal, uri, cred,
|
|
&tdata->msg->line.req.method,
|
|
sess->pool, cached_auth, h_auth);
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Reinitialize outgoing request after 401/407 response is received.
|
|
* The purpose of this function is:
|
|
* - to add a Authorization/Proxy-Authorization header.
|
|
* - to put the newly created Authorization/Proxy-Authorization header
|
|
* in cached_list.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess,
|
|
const pjsip_rx_data *rdata,
|
|
pjsip_tx_data *old_request,
|
|
pjsip_tx_data **new_request )
|
|
{
|
|
pjsip_tx_data *tdata;
|
|
const pjsip_hdr *hdr;
|
|
unsigned chal_cnt;
|
|
pjsip_via_hdr *via;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
|
|
PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
|
|
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
|
|
PJSIP_ENOTRESPONSEMSG);
|
|
PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
|
|
PJSIP_ENOTREQUESTMSG);
|
|
PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
|
|
rdata->msg_info.msg->line.status.code == 407,
|
|
PJSIP_EINVALIDSTATUS);
|
|
|
|
tdata = old_request;
|
|
tdata->auth_retry = PJ_FALSE;
|
|
|
|
/*
|
|
* Respond to each authentication challenge.
|
|
*/
|
|
hdr = rdata->msg_info.msg->hdr.next;
|
|
chal_cnt = 0;
|
|
while (hdr != &rdata->msg_info.msg->hdr) {
|
|
pjsip_cached_auth *cached_auth;
|
|
const pjsip_www_authenticate_hdr *hchal;
|
|
pjsip_authorization_hdr *hauth;
|
|
|
|
/* Find WWW-Authenticate or Proxy-Authenticate header. */
|
|
while (hdr != &rdata->msg_info.msg->hdr &&
|
|
hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
|
|
hdr->type != PJSIP_H_PROXY_AUTHENTICATE)
|
|
{
|
|
hdr = hdr->next;
|
|
}
|
|
if (hdr == &rdata->msg_info.msg->hdr)
|
|
break;
|
|
|
|
hchal = (const pjsip_www_authenticate_hdr*)hdr;
|
|
++chal_cnt;
|
|
|
|
/* Find authentication session for this realm, create a new one
|
|
* if not present.
|
|
*/
|
|
cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm);
|
|
if (!cached_auth) {
|
|
cached_auth = PJ_POOL_ZALLOC_T(sess->pool, pjsip_cached_auth);
|
|
cached_auth->pool = pjsip_endpt_create_pool(sess->endpt,
|
|
"auth_cli%p",
|
|
1024,
|
|
1024);
|
|
pj_strdup(cached_auth->pool, &cached_auth->realm,
|
|
&hchal->challenge.common.realm);
|
|
cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
|
|
# if (PJSIP_AUTH_HEADER_CACHING)
|
|
{
|
|
pj_list_init(&cached_auth->cached_hdr);
|
|
}
|
|
# endif
|
|
pj_list_insert_before(&sess->cached_auth, cached_auth);
|
|
}
|
|
|
|
/* Create authorization header for this challenge, and update
|
|
* authorization session.
|
|
*/
|
|
status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri,
|
|
tdata, sess, cached_auth, &hauth);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
if (pj_pool_get_used_size(cached_auth->pool) >
|
|
PJSIP_AUTH_CACHED_POOL_MAX_SIZE)
|
|
{
|
|
recreate_cached_auth_pool(sess->endpt, cached_auth);
|
|
}
|
|
|
|
/* Add to the message. */
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
|
|
|
|
/* Process next header. */
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
/* Check if challenge is present */
|
|
if (chal_cnt == 0)
|
|
return PJSIP_EAUTHNOCHAL;
|
|
|
|
/* Remove branch param in Via header. */
|
|
via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
|
|
via->branch_param.slen = 0;
|
|
|
|
/* Restore strict route set.
|
|
* See http://trac.pjsip.org/repos/ticket/492
|
|
*/
|
|
pjsip_restore_strict_route_set(tdata);
|
|
|
|
/* Must invalidate the message! */
|
|
pjsip_tx_data_invalidate_msg(tdata);
|
|
|
|
/* Retrying.. */
|
|
tdata->auth_retry = PJ_TRUE;
|
|
|
|
/* Increment reference counter. */
|
|
pjsip_tx_data_add_ref(tdata);
|
|
|
|
/* Done. */
|
|
*new_request = tdata;
|
|
return PJ_SUCCESS;
|
|
|
|
}
|
|
|