src/modules/uac/auth_hdr.c
9fd7fc4e
 /*
  * Copyright (C) 2005 Voice Sistem SRL
  *
27642a08
  * This file is part of Kamailio, a free SIP server.
9fd7fc4e
  *
1d5ae98c
  * Kamailio is free software; you can redistribute it and/or
9fd7fc4e
  * 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.
  *
1d5ae98c
  * Kamailio is distributed in the hope that it will be useful,
9fd7fc4e
  * 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
9e1ff448
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
9fd7fc4e
  */
 
7d7ed6ad
 /*!
  * \file
  * \brief Kamailio uac :: Authentication headers
  * \ingroup uac
  * Module: \ref uac
  */
 
9fd7fc4e
 
 #include "string.h"
 #include "ctype.h"
 
cf83221d
 #include "../../core/dprint.h"
 #include "../../core/str.h"
 #include "../../core/mem/mem.h"
9fd7fc4e
 
 #include "auth_hdr.h"
 #include "auth.h"
 
 
 #define AUTHENTICATE_MD5         (1<<0)
 #define AUTHENTICATE_MD5SESS     (1<<1)
 #define AUTHENTICATE_STALE       (1<<2)
 
 #define AUTHENTICATE_DIGEST_S    "Digest"
 #define AUTHENTICATE_DIGEST_LEN  (sizeof(AUTHENTICATE_DIGEST_S)-1)
 
 #define LOWER1B(_n) \
 	((_n)|0x20)
 #define LOWER4B(_n) \
 	((_n)|0x20202020)
 #define GET4B(_p) \
6293583e
 	((*(_p)<<24) + (*(_p+1)<<16) + (*(_p+2)<<8) + *(_p+3))
9fd7fc4e
 #define GET3B(_p) \
6293583e
 	((*(_p)<<24) + (*(_p+1)<<16) + (*(_p+2)<<8) + 0xff)
9fd7fc4e
 
6293583e
 #define CASE_5B(_hex4,_c5, _new_state, _quoted) \
9fd7fc4e
 	case _hex4: \
 		if (p+5<end && LOWER1B(*(p+4))==_c5 ) \
 		{ \
 			p+=5; \
 			state = _new_state; \
6293583e
 			quoted_val = _quoted; \
9fd7fc4e
 		} else { \
 			p+=4; \
 		} \
 		break;
 
6293583e
 #define CASE_6B(_hex4,_c5,_c6, _new_state, _quoted) \
9fd7fc4e
 	case _hex4: \
 		if (p+6<end && LOWER1B(*(p+4))==_c5 && LOWER1B(*(p+5))==_c6) \
 		{ \
 			p+=6; \
 			state = _new_state; \
6293583e
 			quoted_val = _quoted; \
9fd7fc4e
 		} else { \
 			p+=4; \
 		} \
 		break;
 
 #define OTHER_STATE      0
 #define QOP_STATE        1
 #define REALM_STATE      2
 #define NONCE_STATE      3
 #define STALE_STATE      4
 #define DOMAIN_STATE     5
 #define OPAQUE_STATE     6
 #define ALGORITHM_STATE  7
 
 
 
 int parse_authenticate_body( str *body, struct authenticate_body *auth)
 {
 	char *p;
 	char *end;
 	int  n;
 	int state;
 	str name;
 	str val;
6293583e
 	int quoted_val;
9fd7fc4e
 
 	if (body->s==0 || *body->s==0 )
 	{
43f9ea0d
 		LM_ERR("empty body\n");
9fd7fc4e
 		goto error;
 	}
 
 	memset( auth, 0, sizeof(struct authenticate_body));
 	p = body->s;
 	end = body->s + body->len;
 
 	/* parse the "digest" */
 	while (p<end && isspace((int)*p)) p++;
 	if (p+AUTHENTICATE_DIGEST_LEN>=end )
 		goto parse_error;
3f5909fd
 	if (strncasecmp(p,AUTHENTICATE_DIGEST_S,AUTHENTICATE_DIGEST_LEN)!=0)
9fd7fc4e
 		goto parse_error;
 	p += AUTHENTICATE_DIGEST_LEN;
 	if (!isspace((int)*p))
 		goto parse_error;
 	p++;
 	while (p<end && isspace((int)*p)) p++;
 	if (p==end)
 		goto parse_error;
 
 	while (p<end)
 	{
 		state = OTHER_STATE;
6293583e
 		quoted_val = 0;
9fd7fc4e
 		/* get name */
 		name.s = p;
 		if (p+4<end)
 		{
 			n = LOWER4B( GET4B(p) );
 			switch(n)
 			{
6293583e
 				CASE_5B( 0x7265616c, 'm', REALM_STATE, 1); /*realm*/
 				CASE_5B( 0x6e6f6e63, 'e', NONCE_STATE, 1); /*nonce*/
f03c1d0b
 				CASE_5B( 0x7374616c, 'e', STALE_STATE, 0); /*stale*/
6293583e
 				CASE_6B( 0x646f6d62, 'i', 'n', DOMAIN_STATE, 1); /*domain*/
 				CASE_6B( 0x6f706171, 'u', 'e', OPAQUE_STATE, 1); /*opaque*/
9fd7fc4e
 				case 0x616c676f: /*algo*/
 					if (p+9<end && LOWER4B(GET4B(p+4))==0x72697468
6293583e
 						&& LOWER1B(*(p+8))=='m' )
9fd7fc4e
 					{
 						p+=9;
 						state = ALGORITHM_STATE;
 					} else {
 						p+=4;
 					}
 					break;
 				default:
 					if ((n|0xff)==0x716f70ff) /*qop*/
 					{
 						state = QOP_STATE;
 						p+=3;
 					}
 			}
 		} else if (p+3<end) {
 			n = LOWER4B( GET3B(p) );
 			if (n==0x716f70ff) /*qop*/
 			{
 				p+=3;
 				state = QOP_STATE;
 			}
 		}
 
 		/* parse to the "=" */
 		for( n=0 ; p<end&&!isspace((int)*p)&&*p!='=' ; n++,p++  );
 		if (p==end)
 			goto parse_error;
 		if (n!=0)
 			state = OTHER_STATE;
 		name.len = p-name.s;
 		/* get the '=' */
 		while (p<end && isspace((int)*p)) p++;
 		if (p==end || *p!='=')
 			goto parse_error;
 		p++;
6293583e
 		/* get the value (quoted or not) */
9fd7fc4e
 		while (p<end && isspace((int)*p)) p++;
6293583e
 		if (p+1>=end || (quoted_val && *p!='\"'))
9fd7fc4e
 			goto parse_error;
6293583e
 		if (!quoted_val && *p=='\"')
 			quoted_val = 1;
 		if (quoted_val)
 		{
 			val.s = ++p;
 			while (p<end && *p!='\"')
 				p++;
 			if (p==end)
 				goto error;
 		} else {
 			val.s = p;
 			while (p<end && !isspace((int)*p) && *p!=',')
 				p++;
 		}
9fd7fc4e
 		val.len = p - val.s;
 		if (val.len==0)
 			val.s = 0;
6293583e
 		/* consume the closing '"' if quoted */
 		p += quoted_val;
9fd7fc4e
 		while (p<end && isspace((int)*p)) p++;
 		if (p<end && *p==',')
 		{
 			p++;
 			while (p<end && isspace((int)*p)) p++;
 		}
 
43f9ea0d
 		LM_DBG("<%.*s>=\"%.*s\" state=%d\n",
9fd7fc4e
 			name.len,name.s,val.len,val.s,state);
 
 		/* process the AVP */
 		switch (state)
 		{
 			case QOP_STATE:
5a21ac26
 				auth->qop = val;
 				if(val.len>=4 && !strncmp(val.s, "auth", 4))
 					auth->flags |= QOP_AUTH;
9fd7fc4e
 				break;
 			case REALM_STATE:
 				auth->realm = val;
 				break;
 			case NONCE_STATE:
 				auth->nonce = val;
 				break;
 			case DOMAIN_STATE:
 				auth->domain = val;
 				break;
 			case OPAQUE_STATE:
 				auth->opaque = val;
 				break;
 			case ALGORITHM_STATE:
 				if (val.len==3)
 				{
 					if ( LOWER4B(GET3B(val.s))==0x6d6435ff) /*MD5*/
 						auth->flags |= AUTHENTICATE_MD5;
 				} else {
43f9ea0d
 					LM_ERR("unsupported algorithm \"%.*s\"\n",val.len,val.s);
9fd7fc4e
 					goto error;
 				}
 				break;
 			case STALE_STATE:
 				if (val.len==4 && LOWER4B(GET4B(val.s))==0x74727565) /*true*/
 				{
 						auth->flags |= AUTHENTICATE_STALE;
6894173b
 				} else if ( !(val.len==5 && LOWER1B(val.s[4])=='e' && 
9fd7fc4e
 					LOWER4B(GET4B(val.s))==0x66616c73) )
 				{
43f9ea0d
 					LM_ERR("unsupported stale value \"%.*s\"\n",val.len,val.s);
9fd7fc4e
 					goto error;
 				}
 				break;
 			default:
 				break;
 		}
 	}
 
 	/* some checkings */
 	if (auth->nonce.s==0 || auth->realm.s==0)
 	{
43f9ea0d
 		LM_ERR("realm or nonce missing\n");
9fd7fc4e
 		goto error;
 	}
 
 	return 0;
 parse_error:
43f9ea0d
 		LM_ERR("parse error in <%.*s> around %ld\n", body->len, body->s, (long)(p-body->s));
9fd7fc4e
 error:
 	return -1;
 }
 
 
 #define AUTHORIZATION_HDR_START       "Authorization: Digest "
 #define AUTHORIZATION_HDR_START_LEN   (sizeof(AUTHORIZATION_HDR_START)-1)
 
 #define PROXY_AUTHORIZATION_HDR_START      "Proxy-Authorization: Digest "
 #define PROXY_AUTHORIZATION_HDR_START_LEN  \
 	(sizeof(PROXY_AUTHORIZATION_HDR_START)-1)
 
 #define USERNAME_FIELD_S         "username=\""
 #define USERNAME_FIELD_LEN       (sizeof(USERNAME_FIELD_S)-1)
 #define REALM_FIELD_S            "realm=\""
 #define REALM_FIELD_LEN          (sizeof(REALM_FIELD_S)-1)
 #define NONCE_FIELD_S            "nonce=\""
 #define NONCE_FIELD_LEN          (sizeof(NONCE_FIELD_S)-1)
 #define URI_FIELD_S              "uri=\""
 #define URI_FIELD_LEN            (sizeof(URI_FIELD_S)-1)
 #define OPAQUE_FIELD_S           "opaque=\""
 #define OPAQUE_FIELD_LEN         (sizeof(OPAQUE_FIELD_S)-1)
 #define RESPONSE_FIELD_S         "response=\""
 #define RESPONSE_FIELD_LEN       (sizeof(RESPONSE_FIELD_S)-1)
0aef2d51
 #define ALGORITHM_FIELD_S        "algorithm=MD5"
9fd7fc4e
 #define ALGORITHM_FIELD_LEN       (sizeof(ALGORITHM_FIELD_S)-1)
 #define FIELD_SEPARATOR_S        "\", "
 #define FIELD_SEPARATOR_LEN      (sizeof(FIELD_SEPARATOR_S)-1)
0aef2d51
 #define FIELD_SEPARATOR_UQ_S     ", "
 #define FIELD_SEPARATOR_UQ_LEN   (sizeof(FIELD_SEPARATOR_UQ_S)-1)
9fd7fc4e
 
0aef2d51
 #define QOP_FIELD_S              "qop="
5a21ac26
 #define QOP_FIELD_LEN            (sizeof(QOP_FIELD_S)-1)
0aef2d51
 #define NC_FIELD_S               "nc="
5a21ac26
 #define NC_FIELD_LEN             (sizeof(NC_FIELD_S)-1)
 #define CNONCE_FIELD_S           "cnonce=\""
 #define CNONCE_FIELD_LEN         (sizeof(CNONCE_FIELD_S)-1)
 
9fd7fc4e
 #define add_string( _p, _s, _l) \
 	do {\
 		memcpy( _p, _s, _l);\
 		_p += _l; \
 	}while(0)
 
 
 str* build_authorization_hdr(int code, str *uri, 
 		struct uac_credential *crd, struct authenticate_body *auth,
 		char *response)
 {
1b7941a6
 	static str _uac_auth_hdr;
9fd7fc4e
 	char *p;
 	int len;
 	int response_len;
 
 	response_len = strlen(response);
 
 	/* compile then len */
 	len = (code==401?
 		AUTHORIZATION_HDR_START_LEN:PROXY_AUTHORIZATION_HDR_START_LEN) +
 		USERNAME_FIELD_LEN + crd->user.len + FIELD_SEPARATOR_LEN +
 		REALM_FIELD_LEN + crd->realm.len + FIELD_SEPARATOR_LEN +
 		NONCE_FIELD_LEN + auth->nonce.len + FIELD_SEPARATOR_LEN +
 		URI_FIELD_LEN + uri->len + FIELD_SEPARATOR_LEN +
 		(auth->opaque.len?
 			(OPAQUE_FIELD_LEN + auth->opaque.len + FIELD_SEPARATOR_LEN):0) +
 		RESPONSE_FIELD_LEN + response_len + FIELD_SEPARATOR_LEN +
 		ALGORITHM_FIELD_LEN + CRLF_LEN;
5a21ac26
 	if((auth->flags&QOP_AUTH) || (auth->flags&QOP_AUTH_INT))
0aef2d51
 		len += QOP_FIELD_LEN + 4 /*auth*/ + FIELD_SEPARATOR_UQ_LEN +
 				NC_FIELD_LEN + auth->nc->len + FIELD_SEPARATOR_UQ_LEN +
5a21ac26
 				CNONCE_FIELD_LEN + auth->cnonce->len + FIELD_SEPARATOR_LEN;
9fd7fc4e
 
1b7941a6
 	_uac_auth_hdr.s = (char*)pkg_malloc( len + 1);
 	if (_uac_auth_hdr.s==0)
9fd7fc4e
 	{
9a132d30
 		PKG_MEM_ERROR;
9fd7fc4e
 		goto error;
 	}
 
1b7941a6
 	p = _uac_auth_hdr.s;
9fd7fc4e
 	/* header start */
 	if (code==401)
 	{
 		add_string( p, AUTHORIZATION_HDR_START USERNAME_FIELD_S,
 			AUTHORIZATION_HDR_START_LEN+USERNAME_FIELD_LEN);
 	} else {
 		add_string( p, PROXY_AUTHORIZATION_HDR_START USERNAME_FIELD_S,
 			PROXY_AUTHORIZATION_HDR_START_LEN+USERNAME_FIELD_LEN);
 	}
 	/* username */
 	add_string( p, crd->user.s, crd->user.len);
 	/* REALM */
 	add_string( p, FIELD_SEPARATOR_S REALM_FIELD_S,
 		FIELD_SEPARATOR_LEN+REALM_FIELD_LEN);
 	add_string( p, crd->realm.s, crd->realm.len);
 	/* NONCE */
 	add_string( p, FIELD_SEPARATOR_S NONCE_FIELD_S, 
 		FIELD_SEPARATOR_LEN+NONCE_FIELD_LEN);
 	add_string( p, auth->nonce.s, auth->nonce.len);
 	/* URI */
 	add_string( p, FIELD_SEPARATOR_S URI_FIELD_S,
 		FIELD_SEPARATOR_LEN+URI_FIELD_LEN);
 	add_string( p, uri->s, uri->len);
 	/* OPAQUE */
 	if (auth->opaque.len )
 	{
 		add_string( p, FIELD_SEPARATOR_S OPAQUE_FIELD_S, 
 			FIELD_SEPARATOR_LEN+OPAQUE_FIELD_LEN);
 		add_string( p, auth->opaque.s, auth->opaque.len);
 	}
5a21ac26
 	if((auth->flags&QOP_AUTH) || (auth->flags&QOP_AUTH_INT))
 	{
 		add_string( p, FIELD_SEPARATOR_S QOP_FIELD_S, 
 			FIELD_SEPARATOR_LEN+QOP_FIELD_LEN);
 		add_string( p, "auth", 4);
0aef2d51
 		add_string( p, FIELD_SEPARATOR_UQ_S NC_FIELD_S, 
 			FIELD_SEPARATOR_UQ_LEN+NC_FIELD_LEN);
 		add_string( p, auth->nc->s, auth->nc->len);
 		add_string( p, FIELD_SEPARATOR_UQ_S CNONCE_FIELD_S, 
 			FIELD_SEPARATOR_UQ_LEN+CNONCE_FIELD_LEN);
 		add_string( p, auth->cnonce->s, auth->cnonce->len);
5a21ac26
 	}
9fd7fc4e
 	/* RESPONSE */
 	add_string( p, FIELD_SEPARATOR_S RESPONSE_FIELD_S,
 		FIELD_SEPARATOR_LEN+RESPONSE_FIELD_LEN);
 	add_string( p, response, response_len);
 	/* ALGORITHM */
 	add_string( p, FIELD_SEPARATOR_S ALGORITHM_FIELD_S CRLF,
 		FIELD_SEPARATOR_LEN+ALGORITHM_FIELD_LEN+CRLF_LEN);
 
1b7941a6
 	_uac_auth_hdr.len = p - _uac_auth_hdr.s;
9fd7fc4e
 
1b7941a6
 	if (_uac_auth_hdr.len!=len)
9fd7fc4e
 	{
5a21ac26
 		LM_CRIT("BUG: bad buffer computation "
1b7941a6
 			"(%d<>%d)\n",len,_uac_auth_hdr.len);
 		pkg_free( _uac_auth_hdr.s );
9fd7fc4e
 		goto error;
 	}
 
43f9ea0d
 	LM_DBG("hdr is <%.*s>\n",
1b7941a6
 		_uac_auth_hdr.len,_uac_auth_hdr.s);
9fd7fc4e
 
1b7941a6
 	return &_uac_auth_hdr;
9fd7fc4e
 error:
 	return 0;
 }