/*
 * Copyright (C) 2005 Voice Sistem SRL
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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.
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/*!
 * \file
 * \brief Kamailio uac :: Authentication headers
 * \ingroup uac
 * Module: \ref uac
 */


#include "string.h"
#include "ctype.h"

#include "../../core/dprint.h"
#include "../../core/str.h"
#include "../../core/mem/mem.h"

#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) \
	((*(_p)<<24) + (*(_p+1)<<16) + (*(_p+2)<<8) + *(_p+3))
#define GET3B(_p) \
	((*(_p)<<24) + (*(_p+1)<<16) + (*(_p+2)<<8) + 0xff)

#define CASE_5B(_hex4,_c5, _new_state, _quoted) \
	case _hex4: \
		if (p+5<end && LOWER1B(*(p+4))==_c5 ) \
		{ \
			p+=5; \
			state = _new_state; \
			quoted_val = _quoted; \
		} else { \
			p+=4; \
		} \
		break;

#define CASE_6B(_hex4,_c5,_c6, _new_state, _quoted) \
	case _hex4: \
		if (p+6<end && LOWER1B(*(p+4))==_c5 && LOWER1B(*(p+5))==_c6) \
		{ \
			p+=6; \
			state = _new_state; \
			quoted_val = _quoted; \
		} 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;
	int quoted_val;

	if (body->s==0 || *body->s==0 )
	{
		LM_ERR("empty body\n");
		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;
	if (strncasecmp(p,AUTHENTICATE_DIGEST_S,AUTHENTICATE_DIGEST_LEN)!=0)
		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;
		quoted_val = 0;
		/* get name */
		name.s = p;
		if (p+4<end)
		{
			n = LOWER4B( GET4B(p) );
			switch(n)
			{
				CASE_5B( 0x7265616c, 'm', REALM_STATE, 1); /*realm*/
				CASE_5B( 0x6e6f6e63, 'e', NONCE_STATE, 1); /*nonce*/
				CASE_5B( 0x7374616c, 'e', STALE_STATE, 0); /*stale*/
				CASE_6B( 0x646f6d62, 'i', 'n', DOMAIN_STATE, 1); /*domain*/
				CASE_6B( 0x6f706171, 'u', 'e', OPAQUE_STATE, 1); /*opaque*/
				case 0x616c676f: /*algo*/
					if (p+9<end && LOWER4B(GET4B(p+4))==0x72697468
						&& LOWER1B(*(p+8))=='m' )
					{
						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++;
		/* get the value (quoted or not) */
		while (p<end && isspace((int)*p)) p++;
		if (p+1>=end || (quoted_val && *p!='\"'))
			goto parse_error;
		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++;
		}
		val.len = p - val.s;
		if (val.len==0)
			val.s = 0;
		/* consume the closing '"' if quoted */
		p += quoted_val;
		while (p<end && isspace((int)*p)) p++;
		if (p<end && *p==',')
		{
			p++;
			while (p<end && isspace((int)*p)) p++;
		}

		LM_DBG("<%.*s>=\"%.*s\" state=%d\n",
			name.len,name.s,val.len,val.s,state);

		/* process the AVP */
		switch (state)
		{
			case QOP_STATE:
				auth->qop = val;
				if(val.len>=4 && !strncmp(val.s, "auth", 4))
					auth->flags |= QOP_AUTH;
				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 {
					LM_ERR("unsupported algorithm \"%.*s\"\n",val.len,val.s);
					goto error;
				}
				break;
			case STALE_STATE:
				if (val.len==4 && LOWER4B(GET4B(val.s))==0x74727565) /*true*/
				{
						auth->flags |= AUTHENTICATE_STALE;
				} else if ( !(val.len==5 && LOWER1B(val.s[4])=='e' && 
					LOWER4B(GET4B(val.s))==0x66616c73) )
				{
					LM_ERR("unsupported stale value \"%.*s\"\n",val.len,val.s);
					goto error;
				}
				break;
			default:
				break;
		}
	}

	/* some checkings */
	if (auth->nonce.s==0 || auth->realm.s==0)
	{
		LM_ERR("realm or nonce missing\n");
		goto error;
	}

	return 0;
parse_error:
		LM_ERR("parse error in <%.*s> around %ld\n", body->len, body->s, (long)(p-body->s));
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)
#define ALGORITHM_FIELD_S        "algorithm=MD5"
#define ALGORITHM_FIELD_LEN       (sizeof(ALGORITHM_FIELD_S)-1)
#define FIELD_SEPARATOR_S        "\", "
#define FIELD_SEPARATOR_LEN      (sizeof(FIELD_SEPARATOR_S)-1)
#define FIELD_SEPARATOR_UQ_S     ", "
#define FIELD_SEPARATOR_UQ_LEN   (sizeof(FIELD_SEPARATOR_UQ_S)-1)

#define QOP_FIELD_S              "qop="
#define QOP_FIELD_LEN            (sizeof(QOP_FIELD_S)-1)
#define NC_FIELD_S               "nc="
#define NC_FIELD_LEN             (sizeof(NC_FIELD_S)-1)
#define CNONCE_FIELD_S           "cnonce=\""
#define CNONCE_FIELD_LEN         (sizeof(CNONCE_FIELD_S)-1)

#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)
{
	static str _uac_auth_hdr;
	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;
	if((auth->flags&QOP_AUTH) || (auth->flags&QOP_AUTH_INT))
		len += QOP_FIELD_LEN + 4 /*auth*/ + FIELD_SEPARATOR_UQ_LEN +
				NC_FIELD_LEN + auth->nc->len + FIELD_SEPARATOR_UQ_LEN +
				CNONCE_FIELD_LEN + auth->cnonce->len + FIELD_SEPARATOR_LEN;

	_uac_auth_hdr.s = (char*)pkg_malloc( len + 1);
	if (_uac_auth_hdr.s==0)
	{
		LM_ERR("no more pkg mem\n");
		goto error;
	}

	p = _uac_auth_hdr.s;
	/* 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);
	}
	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);
		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);
	}
	/* 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);

	_uac_auth_hdr.len = p - _uac_auth_hdr.s;

	if (_uac_auth_hdr.len!=len)
	{
		LM_CRIT("BUG: bad buffer computation "
			"(%d<>%d)\n",len,_uac_auth_hdr.len);
		pkg_free( _uac_auth_hdr.s );
		goto error;
	}

	LM_DBG("hdr is <%.*s>\n",
		_uac_auth_hdr.len,_uac_auth_hdr.s);

	return &_uac_auth_hdr;
error:
	return 0;
}

