Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | /* $Id$ */ |
| 2 | /* |
| 3 | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| 4 | * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 | */ |
| 20 | |
| 21 | #include <pjsip/sip_auth.h> |
| 22 | #include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */ |
| 23 | #include <pjsip/sip_auth_msg.h> |
| 24 | #include <pjsip/sip_errno.h> |
| 25 | #include <pjsip/sip_transport.h> |
| 26 | #include <pj/string.h> |
| 27 | #include <pj/assert.h> |
| 28 | |
| 29 | |
| 30 | /* |
| 31 | * Initialize server authorization session data structure to serve the |
| 32 | * specified realm and to use lookup_func function to look for the credential |
| 33 | * info. |
| 34 | */ |
| 35 | PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool, |
| 36 | pjsip_auth_srv *auth_srv, |
| 37 | const pj_str_t *realm, |
| 38 | pjsip_auth_lookup_cred *lookup, |
| 39 | unsigned options ) |
| 40 | { |
| 41 | PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL); |
| 42 | |
| 43 | pj_bzero(auth_srv, sizeof(*auth_srv)); |
| 44 | pj_strdup( pool, &auth_srv->realm, realm); |
| 45 | auth_srv->lookup = lookup; |
| 46 | auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY); |
| 47 | |
| 48 | return PJ_SUCCESS; |
| 49 | } |
| 50 | |
| 51 | /* |
| 52 | * Initialize server authorization session data structure to serve the |
| 53 | * specified realm and to use lookup_func function to look for the credential |
| 54 | * info. |
| 55 | */ |
| 56 | PJ_DEF(pj_status_t) pjsip_auth_srv_init2( |
| 57 | pj_pool_t *pool, |
| 58 | pjsip_auth_srv *auth_srv, |
| 59 | const pjsip_auth_srv_init_param *param) |
| 60 | { |
| 61 | PJ_ASSERT_RETURN(pool && auth_srv && param, PJ_EINVAL); |
| 62 | |
| 63 | pj_bzero(auth_srv, sizeof(*auth_srv)); |
| 64 | pj_strdup( pool, &auth_srv->realm, param->realm); |
| 65 | auth_srv->lookup2 = param->lookup2; |
| 66 | auth_srv->is_proxy = (param->options & PJSIP_AUTH_SRV_IS_PROXY); |
| 67 | |
| 68 | return PJ_SUCCESS; |
| 69 | } |
| 70 | |
| 71 | |
| 72 | /* Verify incoming Authorization/Proxy-Authorization header against the |
| 73 | * specified credential. |
| 74 | */ |
| 75 | static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, |
| 76 | const pj_str_t *method, |
| 77 | const pjsip_cred_info *cred_info ) |
| 78 | { |
| 79 | if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) { |
| 80 | char digest_buf[PJSIP_MD5STRLEN]; |
| 81 | pj_str_t digest; |
| 82 | const pjsip_digest_credential *dig = &hdr->credential.digest; |
| 83 | |
| 84 | /* Check that username and realm match. |
| 85 | * These checks should have been performed before entering this |
| 86 | * function. |
| 87 | */ |
| 88 | PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0, |
| 89 | PJ_EINVALIDOP); |
| 90 | PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0, |
| 91 | PJ_EINVALIDOP); |
| 92 | |
| 93 | /* Prepare for our digest calculation. */ |
| 94 | digest.ptr = digest_buf; |
| 95 | digest.slen = PJSIP_MD5STRLEN; |
| 96 | |
| 97 | /* Create digest for comparison. */ |
| 98 | pjsip_auth_create_digest(&digest, |
| 99 | &hdr->credential.digest.nonce, |
| 100 | &hdr->credential.digest.nc, |
| 101 | &hdr->credential.digest.cnonce, |
| 102 | &hdr->credential.digest.qop, |
| 103 | &hdr->credential.digest.uri, |
| 104 | &cred_info->realm, |
| 105 | cred_info, |
| 106 | method ); |
| 107 | |
| 108 | /* Compare digest. */ |
| 109 | return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? |
| 110 | PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; |
| 111 | |
| 112 | } else { |
| 113 | pj_assert(!"Unsupported authentication scheme"); |
| 114 | return PJSIP_EINVALIDAUTHSCHEME; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | |
| 119 | /* |
| 120 | * Request the authorization server framework to verify the authorization |
| 121 | * information in the specified request in rdata. |
| 122 | */ |
| 123 | PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, |
| 124 | pjsip_rx_data *rdata, |
| 125 | int *status_code) |
| 126 | { |
| 127 | pjsip_authorization_hdr *h_auth; |
| 128 | pjsip_msg *msg = rdata->msg_info.msg; |
| 129 | pjsip_hdr_e htype; |
| 130 | pj_str_t acc_name; |
| 131 | pjsip_cred_info cred_info; |
| 132 | pj_status_t status; |
| 133 | |
| 134 | PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL); |
| 135 | PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); |
| 136 | |
| 137 | htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION : |
| 138 | PJSIP_H_AUTHORIZATION; |
| 139 | |
| 140 | /* Initialize status with 200. */ |
| 141 | *status_code = 200; |
| 142 | |
| 143 | /* Find authorization header for our realm. */ |
| 144 | h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL); |
| 145 | while (h_auth) { |
| 146 | if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm)) |
| 147 | break; |
| 148 | |
| 149 | h_auth = h_auth->next; |
| 150 | if (h_auth == (void*) &msg->hdr) { |
| 151 | h_auth = NULL; |
| 152 | break; |
| 153 | } |
| 154 | |
| 155 | h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth); |
| 156 | } |
| 157 | |
| 158 | if (!h_auth) { |
| 159 | *status_code = auth_srv->is_proxy ? 407 : 401; |
| 160 | return PJSIP_EAUTHNOAUTH; |
| 161 | } |
| 162 | |
| 163 | /* Check authorization scheme. */ |
| 164 | if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0) |
| 165 | acc_name = h_auth->credential.digest.username; |
| 166 | else { |
| 167 | *status_code = auth_srv->is_proxy ? 407 : 401; |
| 168 | return PJSIP_EINVALIDAUTHSCHEME; |
| 169 | } |
| 170 | |
| 171 | /* Find the credential information for the account. */ |
| 172 | if (auth_srv->lookup2) { |
| 173 | pjsip_auth_lookup_cred_param param; |
| 174 | |
| 175 | pj_bzero(¶m, sizeof(param)); |
| 176 | param.realm = auth_srv->realm; |
| 177 | param.acc_name = acc_name; |
| 178 | param.rdata = rdata; |
| 179 | status = (*auth_srv->lookup2)(rdata->tp_info.pool, ¶m, &cred_info); |
| 180 | if (status != PJ_SUCCESS) { |
| 181 | *status_code = PJSIP_SC_FORBIDDEN; |
| 182 | return status; |
| 183 | } |
| 184 | } else { |
| 185 | status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm, |
| 186 | &acc_name, &cred_info); |
| 187 | if (status != PJ_SUCCESS) { |
| 188 | *status_code = PJSIP_SC_FORBIDDEN; |
| 189 | return status; |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | /* Authenticate with the specified credential. */ |
| 194 | status = pjsip_auth_verify(h_auth, &msg->line.req.method.name, |
| 195 | &cred_info); |
| 196 | if (status != PJ_SUCCESS) { |
| 197 | *status_code = PJSIP_SC_FORBIDDEN; |
| 198 | } |
| 199 | return status; |
| 200 | } |
| 201 | |
| 202 | |
| 203 | /* |
| 204 | * Add authentication challenge headers to the outgoing response in tdata. |
| 205 | * Application may specify its customized nonce and opaque for the challenge, |
| 206 | * or can leave the value to NULL to make the function fills them in with |
| 207 | * random characters. |
| 208 | */ |
| 209 | PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, |
| 210 | const pj_str_t *qop, |
| 211 | const pj_str_t *nonce, |
| 212 | const pj_str_t *opaque, |
| 213 | pj_bool_t stale, |
| 214 | pjsip_tx_data *tdata) |
| 215 | { |
| 216 | pjsip_www_authenticate_hdr *hdr; |
| 217 | char nonce_buf[16]; |
| 218 | pj_str_t random; |
| 219 | |
| 220 | PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL ); |
| 221 | |
| 222 | random.ptr = nonce_buf; |
| 223 | random.slen = sizeof(nonce_buf); |
| 224 | |
| 225 | /* Create the header. */ |
| 226 | if (auth_srv->is_proxy) |
| 227 | hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool); |
| 228 | else |
| 229 | hdr = pjsip_www_authenticate_hdr_create(tdata->pool); |
| 230 | |
| 231 | /* Initialize header. |
| 232 | * Note: only support digest authentication now. |
| 233 | */ |
| 234 | hdr->scheme = pjsip_DIGEST_STR; |
| 235 | hdr->challenge.digest.algorithm = pjsip_MD5_STR; |
| 236 | if (nonce) { |
| 237 | pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce); |
| 238 | } else { |
| 239 | pj_create_random_string(nonce_buf, sizeof(nonce_buf)); |
| 240 | pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); |
| 241 | } |
| 242 | if (opaque) { |
| 243 | pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque); |
| 244 | } else { |
| 245 | pj_create_random_string(nonce_buf, sizeof(nonce_buf)); |
| 246 | pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); |
| 247 | } |
| 248 | if (qop) { |
| 249 | pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop); |
| 250 | } else { |
| 251 | hdr->challenge.digest.qop.slen = 0; |
| 252 | } |
| 253 | pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm); |
| 254 | hdr->challenge.digest.stale = stale; |
| 255 | |
| 256 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); |
| 257 | |
| 258 | return PJ_SUCCESS; |
| 259 | } |
| 260 | |