| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 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_auth_msg.h> |
| #include <pjsip/sip_errno.h> |
| #include <pjsip/sip_transport.h> |
| #include <pj/string.h> |
| #include <pj/assert.h> |
| |
| |
| /* |
| * Initialize server authorization session data structure to serve the |
| * specified realm and to use lookup_func function to look for the credential |
| * info. |
| */ |
| PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool, |
| pjsip_auth_srv *auth_srv, |
| const pj_str_t *realm, |
| pjsip_auth_lookup_cred *lookup, |
| unsigned options ) |
| { |
| PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL); |
| |
| pj_bzero(auth_srv, sizeof(*auth_srv)); |
| pj_strdup( pool, &auth_srv->realm, realm); |
| auth_srv->lookup = lookup; |
| auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* |
| * Initialize server authorization session data structure to serve the |
| * specified realm and to use lookup_func function to look for the credential |
| * info. |
| */ |
| PJ_DEF(pj_status_t) pjsip_auth_srv_init2( |
| pj_pool_t *pool, |
| pjsip_auth_srv *auth_srv, |
| const pjsip_auth_srv_init_param *param) |
| { |
| PJ_ASSERT_RETURN(pool && auth_srv && param, PJ_EINVAL); |
| |
| pj_bzero(auth_srv, sizeof(*auth_srv)); |
| pj_strdup( pool, &auth_srv->realm, param->realm); |
| auth_srv->lookup2 = param->lookup2; |
| auth_srv->is_proxy = (param->options & PJSIP_AUTH_SRV_IS_PROXY); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* Verify incoming Authorization/Proxy-Authorization header against the |
| * specified credential. |
| */ |
| static pj_status_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[PJSIP_MD5STRLEN]; |
| pj_str_t digest; |
| const pjsip_digest_credential *dig = &hdr->credential.digest; |
| |
| /* Check that username and realm match. |
| * These checks should have been performed before entering this |
| * function. |
| */ |
| PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0, |
| PJ_EINVALIDOP); |
| PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0, |
| PJ_EINVALIDOP); |
| |
| /* Prepare for our digest calculation. */ |
| digest.ptr = digest_buf; |
| digest.slen = PJSIP_MD5STRLEN; |
| |
| /* Create digest for comparison. */ |
| pjsip_auth_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->realm, |
| cred_info, |
| method ); |
| |
| /* Compare digest. */ |
| return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? |
| PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; |
| |
| } else { |
| pj_assert(!"Unsupported authentication scheme"); |
| return PJSIP_EINVALIDAUTHSCHEME; |
| } |
| } |
| |
| |
| /* |
| * Request the authorization server framework to verify the authorization |
| * information in the specified request in rdata. |
| */ |
| PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, |
| pjsip_rx_data *rdata, |
| int *status_code) |
| { |
| pjsip_authorization_hdr *h_auth; |
| pjsip_msg *msg = rdata->msg_info.msg; |
| pjsip_hdr_e htype; |
| pj_str_t acc_name; |
| pjsip_cred_info cred_info; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL); |
| PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); |
| |
| htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION : |
| PJSIP_H_AUTHORIZATION; |
| |
| /* Initialize status with 200. */ |
| *status_code = 200; |
| |
| /* Find authorization header for our realm. */ |
| h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL); |
| while (h_auth) { |
| if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm)) |
| break; |
| |
| h_auth = h_auth->next; |
| if (h_auth == (void*) &msg->hdr) { |
| h_auth = NULL; |
| break; |
| } |
| |
| h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth); |
| } |
| |
| if (!h_auth) { |
| *status_code = auth_srv->is_proxy ? 407 : 401; |
| return PJSIP_EAUTHNOAUTH; |
| } |
| |
| /* Check authorization scheme. */ |
| if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0) |
| acc_name = h_auth->credential.digest.username; |
| else { |
| *status_code = auth_srv->is_proxy ? 407 : 401; |
| return PJSIP_EINVALIDAUTHSCHEME; |
| } |
| |
| /* Find the credential information for the account. */ |
| if (auth_srv->lookup2) { |
| pjsip_auth_lookup_cred_param param; |
| |
| pj_bzero(¶m, sizeof(param)); |
| param.realm = auth_srv->realm; |
| param.acc_name = acc_name; |
| param.rdata = rdata; |
| status = (*auth_srv->lookup2)(rdata->tp_info.pool, ¶m, &cred_info); |
| if (status != PJ_SUCCESS) { |
| *status_code = PJSIP_SC_FORBIDDEN; |
| return status; |
| } |
| } else { |
| status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm, |
| &acc_name, &cred_info); |
| if (status != PJ_SUCCESS) { |
| *status_code = PJSIP_SC_FORBIDDEN; |
| return status; |
| } |
| } |
| |
| /* Authenticate with the specified credential. */ |
| status = pjsip_auth_verify(h_auth, &msg->line.req.method.name, |
| &cred_info); |
| if (status != PJ_SUCCESS) { |
| *status_code = PJSIP_SC_FORBIDDEN; |
| } |
| return status; |
| } |
| |
| |
| /* |
| * Add authentication challenge headers to the outgoing response in tdata. |
| * Application may specify its customized nonce and opaque for the challenge, |
| * or can leave the value to NULL to make the function fills them in with |
| * random characters. |
| */ |
| PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, |
| const pj_str_t *qop, |
| const pj_str_t *nonce, |
| const pj_str_t *opaque, |
| pj_bool_t stale, |
| pjsip_tx_data *tdata) |
| { |
| pjsip_www_authenticate_hdr *hdr; |
| char nonce_buf[16]; |
| pj_str_t random; |
| |
| PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL ); |
| |
| random.ptr = nonce_buf; |
| random.slen = sizeof(nonce_buf); |
| |
| /* Create the header. */ |
| if (auth_srv->is_proxy) |
| hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool); |
| else |
| hdr = pjsip_www_authenticate_hdr_create(tdata->pool); |
| |
| /* Initialize header. |
| * Note: only support digest authentication now. |
| */ |
| hdr->scheme = pjsip_DIGEST_STR; |
| hdr->challenge.digest.algorithm = pjsip_MD5_STR; |
| if (nonce) { |
| pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce); |
| } else { |
| pj_create_random_string(nonce_buf, sizeof(nonce_buf)); |
| pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); |
| } |
| if (opaque) { |
| pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque); |
| } else { |
| pj_create_random_string(nonce_buf, sizeof(nonce_buf)); |
| pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); |
| } |
| if (qop) { |
| pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop); |
| } else { |
| hdr->challenge.digest.qop.slen = 0; |
| } |
| pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm); |
| hdr->challenge.digest.stale = stale; |
| |
| pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); |
| |
| return PJ_SUCCESS; |
| } |
| |