diff options
Diffstat (limited to 'pjsip/src/pjsip/sip_auth.c')
-rw-r--r-- | pjsip/src/pjsip/sip_auth.c | 1608 |
1 files changed, 804 insertions, 804 deletions
diff --git a/pjsip/src/pjsip/sip_auth.c b/pjsip/src/pjsip/sip_auth.c index b3375867..9b7c52bb 100644 --- a/pjsip/src/pjsip/sip_auth.c +++ b/pjsip/src/pjsip/sip_auth.c @@ -1,804 +1,804 @@ -/* $Id$ */
-/*
- * Copyright (C) 2003-2006 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_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) pj_md5_update(pms, (const pj_uint8_t*)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];
- pj_md5_context 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)
- ***/
- pj_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);
- pj_md5_final(&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)
- ***/
- 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, 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. */
- pj_md5_final(&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_info.msg->type == PJSIP_RESPONSE_MSG);
- pj_assert(rdata->msg_info.msg->line.status.code == 401 ||
- rdata->msg_info.msg->line.status.code == 407 );
-
- /*
- * Respond to each authentication challenge.
- */
- hdr = rdata->msg_info.msg->hdr.next;
- while (hdr != &rdata->msg_info.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_info.msg->hdr)
- {
- hdr = hdr->next;
- }
- if (hdr == &rdata->msg_info.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;
-}
-
+/* $Id$ */ +/* + * Copyright (C) 2003-2006 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_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) pj_md5_update(pms, (const pj_uint8_t*)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]; + pj_md5_context 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) + ***/ + pj_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); + pj_md5_final(&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) + ***/ + 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, 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. */ + pj_md5_final(&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_info.msg->type == PJSIP_RESPONSE_MSG); + pj_assert(rdata->msg_info.msg->line.status.code == 401 || + rdata->msg_info.msg->line.status.code == 407 ); + + /* + * Respond to each authentication challenge. + */ + hdr = rdata->msg_info.msg->hdr.next; + while (hdr != &rdata->msg_info.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_info.msg->hdr) + { + hdr = hdr->next; + } + if (hdr == &rdata->msg_info.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; +} + |