/* $Id$ | |
*/ | |
/* | |
* PJSIP - SIP Stack | |
* (C)2003-2005 Benny Prijono <bennylp@bulukucing.org> | |
* | |
* Author: | |
* Benny Prijono <bennylp@bulukucing.org> | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version. | |
* | |
* This library 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 | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with this library; 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_transport.h> | |
#include <pjsip/sip_endpoint.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> | |
/* 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)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) | |
{ | |
/* 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); | |
pj_snprintf(cred->nc.ptr, 16, "%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; | |
} | |