blob: 91e918624af7dc6a138ad4d20cfa65e8bc940c87 [file] [log] [blame]
/* $Id$
*
*/
#include <pjsip/sip_auth.h>
#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
#include <pjsip/sip_transport.h>
#include <pjsip/sip_endpoint.h>
#include <pj/md5.h>
#include <pj/log.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/guid.h>
/* Length of digest string. */
#define MD5STRLEN 32
/* Maximum stack size we use for storing username+realm+password etc. */
#define MAX_TEMP 128
/* A macro just to get rid of type mismatch between char and unsigned char */
#define MD5_APPEND(pms,buf,len) md5_append(pms, (const unsigned char*)buf, len)
/* Logging. */
#define THIS_FILE "sip_auth.c"
#if 0
# define AUTH_TRACE_(expr) PJ_LOG(3, expr)
#else
# define AUTH_TRACE_(expr)
#endif
static const char hex[] = "0123456789abcdef";
/* Transform digest to string.
* output must be at least MD5STRLEN+1 bytes.
*
* NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
*/
static void digest2str(const unsigned char digest[], char *output)
{
char *p = output;
int i;
for (i = 0; i<16; ++i) {
int val = digest[i];
*p++ = hex[val >> 4];
*p++ = hex[val & 0x0F];
}
}
/*
* Create response digest based on the parameters and store the
* digest ASCII in 'result'.
*/
static void 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 pjsip_cred_info *cred_info,
const pj_str_t *method)
{
char ha1[MD5STRLEN];
char ha2[MD5STRLEN];
unsigned char digest[16];
md5_state_t pms;
pj_assert(result->slen >= MD5STRLEN);
AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
if (cred_info->data_type == PJSIP_CRED_DATA_PLAIN_PASSWD) {
/***
*** ha1 = MD5(username ":" realm ":" password)
***/
md5_init(&pms);
MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
MD5_APPEND( &pms, ":", 1);
MD5_APPEND( &pms, cred_info->realm.ptr, cred_info->realm.slen);
MD5_APPEND( &pms, ":", 1);
MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
md5_finish(&pms, digest);
digest2str(digest, ha1);
} else if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
pj_assert(cred_info->data.slen == 32);
pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
}
AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1));
/***
*** ha2 = MD5(method ":" req_uri)
***/
md5_init(&pms);
MD5_APPEND( &pms, method->ptr, method->slen);
MD5_APPEND( &pms, ":", 1);
MD5_APPEND( &pms, uri->ptr, uri->slen);
md5_finish(&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)
***/
md5_init(&pms);
MD5_APPEND( &pms, ha1, 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, MD5STRLEN);
/* This is the final response digest. */
md5_finish(&pms, digest);
/* Convert digest to string and store in chal->response. */
result->slen = 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)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)
{
/* Check algorithm is supported. We only support MD5. */
if (chal->algorithm.slen && pj_stricmp(&chal->algorithm, &pjsip_MD5_STR))
{
PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
chal->algorithm.slen, chal->algorithm.ptr));
return -1;
}
/* 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);
cred->algorithm = pjsip_MD5_STR;
pj_strdup(pool, &cred->opaque, &chal->opaque);
/* Allocate memory. */
cred->response.ptr = pj_pool_alloc(pool, MD5STRLEN);
cred->response.slen = MD5STRLEN;
if (chal->qop.slen == 0) {
/* Server doesn't require quality of protection. */
/* Convert digest to string and store in chal->response. */
create_digest( &cred->response, &cred->nonce, NULL, NULL, NULL,
uri, 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 = pj_pool_alloc(pool, 16);
sprintf(cred->nc.ptr, "%06u", 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);
}
create_digest( &cred->response, &cred->nonce, &cred->nc, cnonce,
&pjsip_AUTH_STR, uri, 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 -1;
}
return 0;
}
#if PJSIP_AUTH_QOP_SUPPORT
/*
* Update authentication session with a challenge.
*/
static void update_digest_session( pj_pool_t *ses_pool,
pjsip_auth_session *auth_sess,
const pjsip_www_authenticate_hdr *hdr )
{
if (hdr->challenge.digest.qop.slen == 0)
return;
/* Initialize cnonce and qop if not present. */
if (auth_sess->cnonce.slen == 0) {
/* Save the whole challenge */
auth_sess->last_chal = pjsip_hdr_clone(ses_pool, hdr);
/* Create cnonce */
pj_create_unique_string( ses_pool, &auth_sess->cnonce );
/* Initialize nonce-count */
auth_sess->nc = 1;
/* Save realm. */
pj_assert(auth_sess->realm.slen != 0);
if (auth_sess->realm.slen == 0) {
pj_strdup(ses_pool, &auth_sess->realm,
&hdr->challenge.digest.realm);
}
} else {
/* Update last_nonce and nonce-count */
if (!pj_strcmp(&hdr->challenge.digest.nonce,
&auth_sess->last_chal->challenge.digest.nonce))
{
/* Same nonce, increment nonce-count */
++auth_sess->nc;
} else {
/* Server gives new nonce. */
pj_strdup(ses_pool, &auth_sess->last_chal->challenge.digest.nonce,
&hdr->challenge.digest.nonce);
/* Has the opaque changed? */
if (pj_strcmp(&auth_sess->last_chal->challenge.digest.opaque,
&hdr->challenge.digest.opaque))
{
pj_strdup(ses_pool,
&auth_sess->last_chal->challenge.digest.opaque,
&hdr->challenge.digest.opaque);
}
auth_sess->nc = 1;
}
}
}
#endif /* PJSIP_AUTH_QOP_SUPPORT */
/* Find authentication session in the list. */
static pjsip_auth_session *find_session( pjsip_auth_session *sess_list,
const pj_str_t *realm )
{
pjsip_auth_session *sess = sess_list->next;
while (sess != sess_list) {
if (pj_stricmp(&sess->realm, realm) == 0)
return sess;
sess = sess->next;
}
return NULL;
}
/*
* Create Authorization/Proxy-Authorization response header based on the challege
* in WWW-Authenticate/Proxy-Authenticate header.
*/
PJ_DEF(pjsip_authorization_hdr*)
pjsip_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_auth_session *auth_sess)
{
pjsip_authorization_hdr *auth;
char tmp[PJSIP_MAX_URL_SIZE];
pj_str_t uri_str;
pj_pool_t *pool;
pj_assert(hdr != NULL);
pj_assert(uri != NULL);
pj_assert(cred_info != NULL);
pj_assert(method != NULL);
/* 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) {
pj_assert(!"URL is too long!");
PJ_LOG(4,(THIS_FILE, "Unable to authorize: URI is too long!"));
return NULL;
}
# 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)
auth = pjsip_authorization_hdr_create(pool);
else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
auth = pjsip_proxy_authorization_hdr_create(pool);
else {
pj_assert(0);
return NULL;
}
/* Only support digest scheme at the moment. */
if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
pj_status_t rc;
pj_str_t *cnonce = NULL;
pj_uint32_t nc = 1;
/* Update the session (nonce-count etc) if required. */
# if PJSIP_AUTH_QOP_SUPPORT
{
if (auth_sess) {
update_digest_session( sess_pool, auth_sess, hdr );
cnonce = &auth_sess->cnonce;
nc = auth_sess->nc;
}
}
# endif /* PJSIP_AUTH_QOP_SUPPORT */
auth->scheme = pjsip_DIGEST_STR;
rc = respond_digest( pool, &auth->credential.digest,
&hdr->challenge.digest, &uri_str, cred_info,
cnonce, nc, &method->name);
if (rc != 0)
return NULL;
/* Set qop type in auth session the first time only. */
if (hdr->challenge.digest.qop.slen != 0 && auth_sess) {
if (auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) {
pj_str_t *qop_val = &auth->credential.digest.qop;
if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
auth_sess->qop_value = PJSIP_AUTH_QOP_AUTH;
} else {
auth_sess->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
}
}
}
} else {
auth = NULL;
}
/* Keep the new authorization header in the cache, only
* if no qop is not present.
*/
# if PJSIP_AUTH_HEADER_CACHING
{
if (auth && auth_sess && auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) {
pjsip_cached_auth_hdr *cached_hdr;
/* Delete old header with the same method. */
cached_hdr = auth_sess->cached_hdr.next;
while (cached_hdr != &auth_sess->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 != &auth_sess->cached_hdr) {
cached_hdr->hdr = auth;
} else {
cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
pjsip_method_copy( pool, &cached_hdr->method, method);
cached_hdr->hdr = auth;
pj_list_insert_before( &auth_sess->cached_hdr, cached_hdr );
}
}
}
# endif
return auth;
}
/* Verify incoming Authorization/Proxy-Authorization header against existing
* credentials. Will return TRUE if the authorization request matches any of
* the credential.
*/
PJ_DEF(pj_bool_t) pjsip_auth_verify(const pjsip_authorization_hdr *hdr,
const pj_str_t *method,
const pjsip_cred_info *cred_info )
{
if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
char digest_buf[MD5STRLEN];
pj_str_t digest;
const pjsip_digest_credential *dig = &hdr->credential.digest;
/* Check that username match. */
if (pj_strcmp(&dig->username, &cred_info->username) != 0)
return PJ_FALSE;
/* Check that realm match. */
if (pj_strcmp(&dig->realm, &cred_info->realm) != 0)
return PJ_FALSE;
/* Prepare for our digest calculation. */
digest.ptr = digest_buf;
digest.slen = MD5STRLEN;
/* Create digest for comparison. */
create_digest( &digest,
&hdr->credential.digest.nonce,
&hdr->credential.digest.nc,
&hdr->credential.digest.cnonce,
&hdr->credential.digest.qop,
&hdr->credential.digest.uri,
cred_info,
method );
return pj_stricmp(&digest, &hdr->credential.digest.response) == 0;
} else {
pj_assert(0);
return PJ_FALSE;
}
}
/* Find credential to use for the specified realm and scheme. */
PJ_DEF(const pjsip_cred_info*) pjsip_auth_find_cred( unsigned count,
const pjsip_cred_info cred[],
const pj_str_t *realm,
const pj_str_t *scheme)
{
unsigned i;
PJ_UNUSED_ARG(scheme)
for (i=0; i<count; ++i) {
if (pj_stricmp(&cred[i].realm, realm) == 0)
return &cred[i];
}
return NULL;
}
#if PJSIP_AUTH_AUTO_SEND_NEXT
static void new_auth_for_req( pjsip_tx_data *tdata,
pj_pool_t *sess_pool,
pjsip_auth_session *sess,
int cred_count,
const pjsip_cred_info cred_info[])
{
const pjsip_cred_info *cred;
pjsip_authorization_hdr *hauth;
pj_assert(sess->last_chal != NULL);
cred = pjsip_auth_find_cred( cred_count, cred_info, &sess->realm,
&sess->last_chal->scheme );
if (!cred)
return;
hauth = pjsip_auth_respond( tdata->pool, sess->last_chal,
tdata->msg->line.req.uri,
cred, &tdata->msg->line.req.method,
sess_pool, sess);
if (hauth) {
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
}
}
#endif
/*
* Initialize new request message with authorization headers.
* This function will put Authorization/Proxy-Authorization headers to the
* outgoing request message. If caching is enabled (PJSIP_AUTH_HEADER_CACHING)
* and the session has previously sent Authorization/Proxy-Authorization header
* with the same method, then the same Authorization/Proxy-Authorization header
* will be resent from the cache only if qop is not present. If the stack is
* configured to automatically generate next Authorization/Proxy-Authorization
* headers (PJSIP_AUTH_AUTO_SEND_NEXT flag), then new Authorization/Proxy-
* Authorization headers are calculated and generated when they are not present
* in the case or if authorization session has qop.
*
* If both PJSIP_AUTH_HEADER_CACHING flag and PJSIP_AUTH_AUTO_SEND_NEXT flag
* are not set, this function will do nothing. The stack then will only send
* Authorization/Proxy-Authorization to respond 401/407 response.
*/
PJ_DEF(pj_status_t) pjsip_auth_init_req( pj_pool_t *sess_pool,
pjsip_tx_data *tdata,
pjsip_auth_session *sess_list,
int cred_count,
const pjsip_cred_info cred_info[])
{
pjsip_auth_session *sess;
pjsip_method *method = &tdata->msg->line.req.method;
pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG);
if (!sess_list)
return 0;
sess = sess_list->next;
while (sess != sess_list) {
if (sess->qop_value == PJSIP_AUTH_QOP_NONE) {
# if (PJSIP_AUTH_HEADER_CACHING)
{
pjsip_cached_auth_hdr *entry = sess->cached_hdr.next;
while (entry != &sess->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);
} else {
# if (PJSIP_AUTH_AUTO_SEND_NEXT)
{
new_auth_for_req( tdata, sess_pool, sess,
cred_count, cred_info);
}
# else
{
PJ_UNUSED_ARG(sess_pool);
PJ_UNUSED_ARG(cred_count);
PJ_UNUSED_ARG(cred_info);
}
# endif /* PJSIP_AUTH_AUTO_SEND_NEXT */
}
entry = entry->next;
}
}
# elif (PJSIP_AUTH_AUTO_SEND_NEXT)
{
new_auth_for_req( tdata, sess_pool, sess,
cred_count, cred_info);
}
# else
{
PJ_UNUSED_ARG(sess_pool);
PJ_UNUSED_ARG(cred_count);
PJ_UNUSED_ARG(cred_info);
}
# endif /* PJSIP_AUTH_HEADER_CACHING */
}
# if (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
else if (sess->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;
cred = pjsip_auth_find_cred( cred_count, cred_info,
&sess->realm,
&sess->last_chal->scheme);
if (!cred) {
sess = sess->next;
continue;
}
hauth = pjsip_auth_respond( tdata->pool, sess->last_chal,
tdata->msg->line.req.uri,
cred,
&tdata->msg->line.req.method,
sess_pool, sess );
if (hauth) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
}
}
# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
sess = sess->next;
}
return 0;
}
/* Process authorization challenge */
static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool,
const pjsip_www_authenticate_hdr *hchal,
const pjsip_uri *uri,
pjsip_tx_data *tdata,
int cred_count,
const pjsip_cred_info cred_info[],
pj_pool_t *ses_pool,
pjsip_auth_session *auth_sess)
{
const pjsip_cred_info *cred;
pjsip_authorization_hdr *sent_auth = NULL, *hauth;
pjsip_hdr *hdr;
/* 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)
{
break;
}
}
hdr = hdr->next;
}
/* If we have sent, see if server rejected because of stale nonce or
* other causes.
*/
if (hdr != &tdata->msg->hdr) {
if (hchal->challenge.digest.stale == 0) {
/* Our credential is rejected. No point in trying to re-supply
* the same credential.
*/
PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s",
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 NULL;
}
/* 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 = pjsip_auth_find_cred( cred_count, cred_info,
&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 NULL;
}
/* Respond to authorization challenge. */
hauth = pjsip_auth_respond( req_pool, hchal, uri, cred,
&tdata->msg->line.req.method,
ses_pool, auth_sess);
return hauth;
}
/* 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(pjsip_tx_data*) pjsip_auth_reinit_req( pjsip_endpoint *endpt,
pj_pool_t *ses_pool,
pjsip_auth_session *sess_list,
int cred_count,
const pjsip_cred_info cred_info[],
pjsip_tx_data *tdata,
const pjsip_rx_data *rdata)
{
const pjsip_hdr *hdr;
pjsip_via_hdr *via;
PJ_UNUSED_ARG(endpt)
pj_assert(rdata->msg->type == PJSIP_RESPONSE_MSG);
pj_assert(rdata->msg->line.status.code == 401 ||
rdata->msg->line.status.code == 407 );
/*
* Respond to each authentication challenge.
*/
hdr = rdata->msg->hdr.next;
while (hdr != &rdata->msg->hdr) {
pjsip_auth_session *sess;
const pjsip_www_authenticate_hdr *hchal;
pjsip_authorization_hdr *hauth;
/* Find WWW-Authenticate or Proxy-Authenticate header. */
while (hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
hdr->type != PJSIP_H_PROXY_AUTHENTICATE &&
hdr != &rdata->msg->hdr)
{
hdr = hdr->next;
}
if (hdr == &rdata->msg->hdr)
break;
hchal = (const pjsip_www_authenticate_hdr*) hdr;
/* Find authentication session for this realm, create a new one
* if not present.
*/
sess = find_session(sess_list, &hchal->challenge.common.realm );
if (!sess) {
sess = pj_pool_calloc( ses_pool, 1, sizeof(*sess));
pj_strdup( ses_pool, &sess->realm, &hchal->challenge.common.realm);
sess->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
# if (PJSIP_AUTH_HEADER_CACHING)
{
pj_list_init(&sess->cached_hdr);
}
# endif
pj_list_insert_before( sess_list, sess );
}
/* Create authorization header for this challenge, and update
* authorization session.
*/
hauth = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri,
tdata, cred_count, cred_info, ses_pool, sess );
if (!hauth)
return NULL;
/* Add to the message. */
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
/* Process next header. */
hdr = hdr->next;
}
/* Remove branch param in Via header. */
via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
via->branch_param.slen = 0;
/* Increment reference counter. */
pjsip_tx_data_add_ref(tdata);
/* Done. */
return tdata;
}