mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
10 KiB
482 lines
10 KiB
/*
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Voice Sistem SRL
|
|
*
|
|
* This file is part of Kamailio, a free SIP server.
|
|
*
|
|
* UAC Kamailio-module 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.
|
|
*
|
|
* UAC Kamailio-module 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.
|
|
*
|
|
*
|
|
* History:
|
|
* ---------
|
|
* 2005-01-31 first version (ramona)
|
|
* 2006-03-02 UAC authentication looks first in AVPs for credential (bogdan)
|
|
*/
|
|
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include "../../lib/kcore/cmpapi.h"
|
|
#include "../../dprint.h"
|
|
#include "../../pvar.h"
|
|
#include "../../data_lump.h"
|
|
#include "../../mem/mem.h"
|
|
#include "../../hashes.h"
|
|
#include "../../dset.h"
|
|
#include "../../modules/tm/tm_load.h"
|
|
|
|
#include "auth.h"
|
|
#include "auth_alg.h"
|
|
#include "auth_hdr.h"
|
|
|
|
|
|
extern struct tm_binds uac_tmb;
|
|
extern pv_spec_t auth_username_spec;
|
|
extern pv_spec_t auth_realm_spec;
|
|
extern pv_spec_t auth_password_spec;
|
|
|
|
|
|
static struct uac_credential *crd_list = 0;
|
|
|
|
|
|
#define duplicate_str(_strd, _strs, _error) \
|
|
do { \
|
|
_strd.s = (char*)pkg_malloc(_strs.len); \
|
|
if (_strd.s==0) \
|
|
{ \
|
|
LM_ERR("no more pkg memory\n");\
|
|
goto _error; \
|
|
} \
|
|
memcpy( _strd.s, _strs.s, _strs.len); \
|
|
_strd.len = _strs.len; \
|
|
}while(0)
|
|
|
|
|
|
#define WWW_AUTH_CODE 401
|
|
#define WWW_AUTH_HDR "WWW-Authenticate"
|
|
#define WWW_AUTH_HDR_LEN (sizeof(WWW_AUTH_HDR)-1)
|
|
#define PROXY_AUTH_CODE 407
|
|
#define PROXY_AUTH_HDR "Proxy-Authenticate"
|
|
#define PROXY_AUTH_HDR_LEN (sizeof(PROXY_AUTH_HDR)-1)
|
|
|
|
static str nc = {"00000001", 8};
|
|
static str cnonce = {"o", 1};
|
|
|
|
int has_credentials(void)
|
|
{
|
|
return (crd_list!=0)?1:0;
|
|
}
|
|
|
|
void free_credential(struct uac_credential *crd)
|
|
{
|
|
if (crd)
|
|
{
|
|
if (crd->realm.s)
|
|
pkg_free(crd->realm.s);
|
|
if (crd->user.s)
|
|
pkg_free(crd->user.s);
|
|
if (crd->passwd.s)
|
|
pkg_free(crd->passwd.s);
|
|
pkg_free(crd);
|
|
}
|
|
}
|
|
|
|
|
|
int add_credential( unsigned int type, void *val)
|
|
{
|
|
struct uac_credential *crd;
|
|
char *p;
|
|
str foo;
|
|
|
|
p = (char*)val;
|
|
crd = 0;
|
|
|
|
if (p==0 || *p==0)
|
|
goto error;
|
|
|
|
crd = (struct uac_credential*)pkg_malloc(sizeof(struct uac_credential));
|
|
if (crd==0)
|
|
{
|
|
LM_ERR("no more pkg mem\n");
|
|
goto error;
|
|
}
|
|
memset( crd, 0, sizeof(struct uac_credential));
|
|
|
|
/*parse the user */
|
|
while (*p && isspace((int)*p)) p++;
|
|
foo.s = p;
|
|
while (*p && *p!=':' && !isspace((int)*p)) p++;
|
|
if (foo.s==p || *p==0)
|
|
/* missing or empty user */
|
|
goto parse_error;
|
|
foo.len = p - foo.s;
|
|
/* dulicate it */
|
|
duplicate_str( crd->user, foo, error);
|
|
|
|
/* parse the ':' separator */
|
|
while (*p && isspace((int)*p)) p++;
|
|
if (*p!=':')
|
|
goto parse_error;
|
|
p++;
|
|
while (*p && isspace((int)*p)) p++;
|
|
if (*p==0)
|
|
goto parse_error;
|
|
|
|
/*parse the realm */
|
|
while (*p && isspace((int)*p)) p++;
|
|
foo.s = p;
|
|
while (*p && *p!=':' && !isspace((int)*p)) p++;
|
|
if (foo.s==p || *p==0)
|
|
/* missing or empty realm */
|
|
goto parse_error;
|
|
foo.len = p - foo.s;
|
|
/* dulicate it */
|
|
duplicate_str( crd->realm, foo, error);
|
|
|
|
/* parse the ':' separator */
|
|
while (*p && isspace((int)*p)) p++;
|
|
if (*p!=':')
|
|
goto parse_error;
|
|
p++;
|
|
while (*p && isspace((int)*p)) p++;
|
|
if (*p==0)
|
|
goto parse_error;
|
|
|
|
/*parse the passwd */
|
|
while (*p && isspace((int)*p)) p++;
|
|
foo.s = p;
|
|
while (*p && !isspace((int)*p)) p++;
|
|
if (foo.s==p)
|
|
/* missing or empty passwd */
|
|
goto parse_error;
|
|
foo.len = p - foo.s;
|
|
/* dulicate it */
|
|
duplicate_str( crd->passwd, foo, error);
|
|
|
|
/* end of string */
|
|
while (*p && isspace((int)*p)) p++;
|
|
if (*p!=0)
|
|
goto parse_error;
|
|
|
|
/* link the new cred struct */
|
|
crd->next = crd_list;
|
|
crd_list = crd;
|
|
|
|
pkg_free(val);
|
|
return 0;
|
|
parse_error:
|
|
LM_ERR("parse error in <%s> "
|
|
"around %ld\n", (char*)val, (long)(p-(char*)val));
|
|
error:
|
|
if (crd)
|
|
free_credential(crd);
|
|
return -1;
|
|
}
|
|
|
|
|
|
void destroy_credentials(void)
|
|
{
|
|
struct uac_credential *foo;
|
|
|
|
while (crd_list)
|
|
{
|
|
foo = crd_list;
|
|
crd_list = crd_list->next;
|
|
free_credential(foo);
|
|
}
|
|
crd_list = 0;
|
|
}
|
|
|
|
|
|
struct hdr_field *get_autenticate_hdr(struct sip_msg *rpl, int rpl_code)
|
|
{
|
|
struct hdr_field *hdr;
|
|
str hdr_name;
|
|
|
|
/* what hdr should we look for */
|
|
if (rpl_code==WWW_AUTH_CODE)
|
|
{
|
|
hdr_name.s = WWW_AUTH_HDR;
|
|
hdr_name.len = WWW_AUTH_HDR_LEN;
|
|
} else if (rpl_code==PROXY_AUTH_CODE) {
|
|
hdr_name.s = PROXY_AUTH_HDR;
|
|
hdr_name.len = PROXY_AUTH_HDR_LEN;
|
|
} else {
|
|
LM_ERR("reply is not an "
|
|
"auth request\n");
|
|
goto error;
|
|
}
|
|
|
|
LM_DBG("looking for header \"%.*s\"\n",
|
|
hdr_name.len, hdr_name.s);
|
|
|
|
/* search the auth hdr, but first parse them all */
|
|
if (parse_headers( rpl, HDR_EOH_F, 0)<0)
|
|
{
|
|
LM_ERR("failed to parse reply\n");
|
|
goto error;
|
|
}
|
|
for( hdr=rpl->headers ; hdr ; hdr=hdr->next )
|
|
{
|
|
if ( rpl_code==WWW_AUTH_CODE && hdr->type==HDR_WWW_AUTHENTICATE_T )
|
|
return hdr;
|
|
if ( rpl_code==PROXY_AUTH_CODE && hdr->type==HDR_PROXY_AUTHENTICATE_T )
|
|
return hdr;
|
|
}
|
|
|
|
LM_ERR("reply has no "
|
|
"auth hdr (%.*s)\n", hdr_name.len, hdr_name.s);
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline struct uac_credential *lookup_realm( str *realm)
|
|
{
|
|
struct uac_credential *crd;
|
|
|
|
for( crd=crd_list ; crd ; crd=crd->next )
|
|
if (realm->len==crd->realm.len &&
|
|
strncmp( realm->s, crd->realm.s, realm->len)==0 )
|
|
return crd;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline struct uac_credential *get_avp_credential(struct sip_msg *msg,
|
|
str *realm)
|
|
{
|
|
static struct uac_credential crd;
|
|
pv_value_t pv_val;
|
|
|
|
if(pv_get_spec_value( msg, &auth_realm_spec, &pv_val)!=0)
|
|
return 0;
|
|
|
|
if (pv_val.flags&PV_VAL_NULL || pv_val.rs.len<=0) {
|
|
/* if realm parameter is empty or NULL, match any realm asked for */
|
|
crd.realm = *realm;
|
|
} else {
|
|
crd.realm = pv_val.rs;
|
|
/* is it the domain we are looking for? */
|
|
if (realm->len!=crd.realm.len ||
|
|
strncmp( realm->s, crd.realm.s, realm->len)!=0 )
|
|
return 0;
|
|
}
|
|
|
|
/* get username and password */
|
|
if(pv_get_spec_value( msg, &auth_username_spec, &pv_val)!=0
|
|
|| pv_val.flags&PV_VAL_NULL || pv_val.rs.len<=0)
|
|
return 0;
|
|
crd.user = pv_val.rs;
|
|
|
|
if(pv_get_spec_value( msg, &auth_password_spec, &pv_val)!=0
|
|
|| pv_val.flags&PV_VAL_NULL || pv_val.rs.len<=0)
|
|
return 0;
|
|
crd.passwd = pv_val.rs;
|
|
|
|
return &crd;
|
|
}
|
|
|
|
|
|
void do_uac_auth(str *method, str *uri,
|
|
struct uac_credential *crd,
|
|
struct authenticate_body *auth,
|
|
HASHHEX response)
|
|
{
|
|
HASHHEX ha1;
|
|
HASHHEX ha2;
|
|
|
|
if((auth->flags&QOP_AUTH) || (auth->flags&QOP_AUTH_INT))
|
|
{
|
|
/* if qop generate nonce-count and cnonce */
|
|
cnonce.s = int2str(get_hash1_raw(auth->nonce.s, auth->nonce.len),
|
|
&cnonce.len);
|
|
|
|
/* do authentication */
|
|
uac_calc_HA1( crd, auth, &cnonce, ha1);
|
|
uac_calc_HA2( method, uri,
|
|
auth, 0/*hentity*/, ha2 );
|
|
|
|
uac_calc_response( ha1, ha2, auth, &nc, &cnonce, response);
|
|
auth->nc = &nc;
|
|
auth->cnonce = &cnonce;
|
|
} else {
|
|
/* do authentication */
|
|
uac_calc_HA1( crd, auth, 0/*cnonce*/, ha1);
|
|
uac_calc_HA2( method, uri,
|
|
auth, 0/*hentity*/, ha2 );
|
|
|
|
uac_calc_response( ha1, ha2, auth, 0/*nc*/, 0/*cnonce*/, response);
|
|
}
|
|
}
|
|
|
|
|
|
static inline int apply_urihdr_changes( struct sip_msg *req,
|
|
str *uri, str *hdr)
|
|
{
|
|
struct lump* anchor;
|
|
|
|
/* add the uri - move it to branch directly FIXME (bogdan)*/
|
|
if (req->new_uri.s)
|
|
{
|
|
pkg_free(req->new_uri.s);
|
|
req->new_uri.len=0;
|
|
}
|
|
req->parsed_uri_ok=0;
|
|
req->new_uri.s = (char*)pkg_malloc(uri->len+1);
|
|
if (req->new_uri.s==0)
|
|
{
|
|
LM_ERR("no more pkg\n");
|
|
goto error;
|
|
}
|
|
memcpy( req->new_uri.s, uri->s, uri->len);
|
|
req->new_uri.s[uri->len]=0;
|
|
req->new_uri.len=uri->len;
|
|
ruri_mark_new();
|
|
|
|
/* add the header */
|
|
if (parse_headers(req, HDR_EOH_F, 0) == -1)
|
|
{
|
|
LM_ERR("failed to parse message\n");
|
|
goto error;
|
|
}
|
|
|
|
anchor = anchor_lump(req, req->unparsed - req->buf, 0, 0);
|
|
if (anchor==0)
|
|
{
|
|
LM_ERR("failed to get anchor\n");
|
|
goto error;
|
|
}
|
|
|
|
if (insert_new_lump_before(anchor, hdr->s, hdr->len, 0) == 0)
|
|
{
|
|
LM_ERR("faield to insert lump\n");
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
pkg_free( hdr->s );
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
int uac_auth( struct sip_msg *msg)
|
|
{
|
|
static struct authenticate_body auth;
|
|
struct uac_credential *crd;
|
|
int code, branch;
|
|
struct sip_msg *rpl;
|
|
struct cell *t;
|
|
struct hdr_field *hdr;
|
|
HASHHEX response;
|
|
str *new_hdr;
|
|
|
|
/* get transaction */
|
|
t = uac_tmb.t_gett();
|
|
if (t==T_UNDEFINED || t==T_NULL_CELL)
|
|
{
|
|
LM_CRIT("no current transaction found\n");
|
|
goto error;
|
|
}
|
|
|
|
/* get the selected branch */
|
|
branch = uac_tmb.t_get_picked_branch();
|
|
if (branch<0) {
|
|
LM_CRIT("no picked branch (%d)\n",branch);
|
|
goto error;
|
|
}
|
|
|
|
rpl = t->uac[branch].reply;
|
|
code = t->uac[branch].last_received;
|
|
LM_DBG("picked reply is %p, code %d\n",rpl,code);
|
|
|
|
if (rpl==0)
|
|
{
|
|
LM_CRIT("empty reply on picked branch\n");
|
|
goto error;
|
|
}
|
|
if (rpl==FAKED_REPLY)
|
|
{
|
|
LM_ERR("cannot process a FAKED reply\n");
|
|
goto error;
|
|
}
|
|
|
|
hdr = get_autenticate_hdr( rpl, code);
|
|
if (hdr==0)
|
|
{
|
|
LM_ERR("failed to extract authenticate hdr\n");
|
|
goto error;
|
|
}
|
|
|
|
LM_DBG("header found; body=<%.*s>\n",
|
|
hdr->body.len, hdr->body.s);
|
|
|
|
if (parse_authenticate_body( &hdr->body, &auth)<0)
|
|
{
|
|
LM_ERR("failed to parse auth hdr body\n");
|
|
goto error;
|
|
}
|
|
|
|
/* can we authenticate this realm? */
|
|
crd = 0;
|
|
/* first look into AVP, if set */
|
|
if ( auth_realm_spec.type!=PVT_NONE )
|
|
crd = get_avp_credential( msg, &auth.realm );
|
|
/* if not found, look into predefined credentials */
|
|
if (crd==0)
|
|
crd = lookup_realm( &auth.realm );
|
|
/* found? */
|
|
if (crd==0)
|
|
{
|
|
LM_DBG("no credential for realm \"%.*s\"\n",
|
|
auth.realm.len, auth.realm.s);
|
|
goto error;
|
|
}
|
|
|
|
/* do authentication */
|
|
do_uac_auth( &msg->first_line.u.request.method,
|
|
&t->uac[branch].uri, crd, &auth, response);
|
|
|
|
/* build the authorization header */
|
|
new_hdr = build_authorization_hdr( code, &t->uac[branch].uri,
|
|
crd, &auth, response);
|
|
if (new_hdr==0)
|
|
{
|
|
LM_ERR("failed to build authorization hdr\n");
|
|
goto error;
|
|
}
|
|
|
|
/* so far, so good -> add the header and set the proper RURI */
|
|
if ( apply_urihdr_changes( msg, &t->uac[branch].uri, new_hdr)<0 )
|
|
{
|
|
LM_ERR("failed to apply changes\n");
|
|
goto error;
|
|
}
|
|
|
|
/* increas the Cseq nr */
|
|
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
|
|
|