/*
 * 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) {
		PKG_MEM_ERROR;
		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;
}
