⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sip_auth_client.c

📁 一个开源SIP协议栈
💻 C
📖 第 1 页 / 共 2 页
字号:
/* $Id: sip_auth_client.c 974 2007-02-19 01:13:53Z bennylp $ */
/* 
 * Copyright (C) 2003-2007 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 <pjsip/sip_errno.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, 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


/* 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'. 
 */
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 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 == 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, 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)
{
    /* 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 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);
    cred->algorithm = pjsip_MD5_STR;
    pj_strdup(pool, &cred->opaque, &chal->opaque);
    
    /* Allocate memory. */
    cred->response.ptr = pj_pool_alloc(pool, PJSIP_MD5STRLEN);
    cred->response.slen = PJSIP_MD5STRLEN;

    if (chal->qop.slen == 0) {
	/* Server doesn't require quality of protection. */

	/* Convert digest to string and store in chal->response. */
	pjsip_auth_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);
	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);
	}

	pjsip_auth_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 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( pj_pool_t *ses_pool, 
				   pjsip_cached_auth *cached_auth,
				   const pjsip_www_authenticate_hdr *hdr )
{
    if (hdr->challenge.digest.qop.slen == 0)
	return;

    /* Initialize cnonce and qop if not present. */
    if (cached_auth->cnonce.slen == 0) {
	/* Save the whole challenge */
	cached_auth->last_chal = pjsip_hdr_clone(ses_pool, hdr);

	/* Create cnonce */
	pj_create_unique_string( ses_pool, &cached_auth->cnonce );

	/* Initialize nonce-count */
	cached_auth->nc = 1;

	/* Save realm. */
	pj_assert(cached_auth->realm.slen != 0);
	if (cached_auth->realm.slen == 0) {
	    pj_strdup(ses_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(ses_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(ses_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;
    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];
    }
    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;
}


/* 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);

    sess->pool = pool;
    sess->endpt = (pjsip_endpoint*)rhs->endpt;
    sess->cred_cnt = rhs->cred_cnt;
    sess->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 = 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;
	    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;
}


/* 
 * 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)
{

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -