📄 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 + -