/*
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

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

#include <ctype.h>
#include <string.h>

#include "../../core/strutils.h"
#include "../../core/dprint.h"
#include "../../core/pvar.h"
#include "../../core/data_lump.h"
#include "../../core/mem/mem.h"
#include "../../core/hashes.h"
#include "../../core/dset.h"
#include "../../core/srapi.h"
#include "../../core/parser/parse_cseq.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) {                       \
			PKG_MEM_ERROR;                       \
			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) {
		PKG_MEM_ERROR;
		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_authenticate_hdr(struct sip_msg *rpl, int rpl_code)
{
	struct hdr_field *hdr;
	str hdr_name;
	struct authenticate_body auth;
	int auth_hdr_found = 0;

	/* 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)
				|| (rpl_code == PROXY_AUTH_CODE
						&& hdr->type == HDR_PROXY_AUTHENTICATE_T)) {
			auth_hdr_found = 1;
			if(parse_authenticate_body(&hdr->body, &auth) == 0) {
				return hdr;
			}
			LM_DBG("skipping unsupported authenticate header body <%.*s>\n",
					hdr->body.len, hdr->body.s);
		}
	}

	if(auth_hdr_found) {
		LM_ERR("reply has no supported auth hdr (%.*s)\n", hdr_name.len,
				hdr_name.s);
	} else {
		LM_ERR("reply has no auth hdr (%.*s)\n", hdr_name.len, hdr_name.s);
	}
error:
	return NULL;
}


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) {
		PKG_MEM_ERROR;
		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("failed to insert lump\n");
		goto error;
	}

	return 0;
error:
	pkg_free(hdr->s);
	return -1;
}

/**
 *
 */
int uac_auth_mode(sip_msg_t *msg, int mode)
{
	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;
	sr_cfgenv_t *cenv = NULL;

	/* 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_authenticate_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_INFO("no credential for realm \"%.*s\"\n", auth.realm.len,
				auth.realm.s);
		goto error;
	}

	if(mode & UACAUTH_MODE_HA1) {
		crd->aflags |= UAC_FLCRED_HA1;
	}

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

	/* mark request in T with uac auth for increase of cseq via dialog
	 * - this function is executed in failure route, msg_flags will be
	 *   reset afterwards by tm fake env */
	if(t->uas.request) {
		t->uas.request->msg_flags |= FL_UAC_AUTH;
		cenv = sr_cfgenv_get();
		if(cenv->cb_cseq_update != NULL) {
			if(cenv->cb_cseq_update(msg) < 0) {
				goto error;
			}
		}
	}

	return 0;
error:
	return -1;
}

/**
 *
 */
int uac_auth(sip_msg_t *msg)
{
	return uac_auth_mode(msg, 0);
}
