📄 sip_auth_client.c
字号:
/* $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 + -