/*
 * $Id$
 *
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fraunhofer FOKUS Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: A lot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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
 *
 * Interrogating-CSCF - Network Domain Security Operations
 *
 *  \author Dragos Vingarzan vingarzan -at- fokus dot fraunhofer dot de
 *
 */
#include "nds.h"

#include "../../core/parser/hf.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_via.h"
#include "../../core/mem/shm_mem.h"
#include "../../modules/sl/sl.h"

#include "ims_icscf_mod.h"
#include "db.h"

extern sl_api_t slb;


static str str_msg_403 = {MSG_403, 9};
static str str_msg_500 = {MSG_500, 46};

/** Defines the untrusted headers */
str untrusted_headers[] = {{"P-Asserted-Identity", 19},
		{"P-Access-Network-Info", 21}, {"P-Charging-Vector", 17},
		{"P-Charging-Function-Addresses", 29}, {0, 0}};

/** The cached list of trusted domains */
static str *trusted_domains = 0;


/**
 * Checks if a request comes from a trusted domain.
 * If not calls function to respond with 403 to REGISTER or clean the message of
 * untrusted headers
 * @param msg - the SIP message
 * @param str1 - not used
 * @param str2 - not used
 * @returns #CSCF_RETURN_TRUE if trusted, #CSCF_RETURN_FALSE if not , #CSCF_RETURN_ERROR on REGISTER or error
 */
int I_NDS_check_trusted(struct sip_msg *msg, char *str1, char *str2)
{
	int result;
	LM_DBG("DBG:I_NDS_check_trusted: Starting ...\n");
	if(msg->first_line.type != SIP_REQUEST) {
		LM_ERR("ERR:I_NDS_check_trusted: The message is not a request\n");
		result = CSCF_RETURN_TRUE;
		goto done;
	}
	if(I_NDS_is_trusted(msg, str1, str2)) {
		LM_DBG("INF:I_NDS_check_trusted: Message comes from a trusted "
			   "domain\n");
		result = CSCF_RETURN_TRUE;
		goto done;
	} else {
		LM_DBG("INF:I_NDS_check_trusted: Message comes from an untrusted "
			   "domain\n");
		result = CSCF_RETURN_FALSE;
		if(msg->first_line.u.request.method.len == 8
				&& memcmp(msg->first_line.u.request.method.s, "REGISTER", 8)
						   == 0) {
			slb.sreply(msg, 403, &str_msg_403);
			LM_DBG("INF:I_NDS_check_trusted: REGISTER request terminated.\n");
		} else {
			if(!I_NDS_strip_headers(msg, str1, str2)) {
				result = CSCF_RETURN_ERROR;
				slb.sreply(msg, 500, &str_msg_500);
				LM_DBG("INF:I_NDS_check_trusted: Stripping untrusted headers "
					   "failed, Responding with 500.\n");
			}
		}
	}

done:
	LM_DBG("DBG:I_NDS_check_trusted: ... Done\n");
	return result;
}

/**
 * Decides if a message comes from a trusted domain.
 * \todo - SOLVE THE LOCKING PROBLEM - THIS IS A READER
 * @param msg - the SIP request message
 * @param str1 - not used
 * @param str2 - not used
 * @returns #CSCF_RETURN_TRUE if trusted, #CSCF_RETURN_FALSE
 */
int I_NDS_is_trusted(struct sip_msg *msg, char *str1, char *str2)
{
	struct via_body *vb;
	str subdomain;
	int i;

	vb = msg->via1;
	if(!vb) {
		LM_ERR("ERR:I_NDS_is_trusted: Error VIA1 hdr not found\n");
		return 0;
	}
	subdomain = vb->host;
	LM_DBG("DBG:I_NDS_is_trusted: Message comes from <%.*s>\n", subdomain.len,
			subdomain.s);

	i = 0;
	while(trusted_domains[i].len) {
		if(trusted_domains[i].len <= subdomain.len) {
			if(strncasecmp(subdomain.s + subdomain.len - trusted_domains[i].len,
					   trusted_domains[i].s, trusted_domains[i].len)
							== 0
					&& (trusted_domains[i].len == subdomain.len
							|| subdomain.s[subdomain.len
										   - trusted_domains[i].len - 1]
									   == '.')) {
				LM_DBG("DBG:I_NDS_is_trusted: <%.*s> matches <%.*s>\n",
						subdomain.len, subdomain.s, trusted_domains[i].len,
						trusted_domains[i].s);
				return CSCF_RETURN_TRUE;
			} else {
				//				LM_DBG("DBG:I_NDS_is_trusted: <%.*s> !matches <%.*s>\n",
				//					subdomain.len,subdomain.s,trusted_domains[i].len,trusted_domains[i].s);
			}
		}
		i++;
	}
	return CSCF_RETURN_FALSE;
}


/**
 * Strips untrusty headers from a SIP request.
 * Searched headers are declared in untrusted_headers
 * @param msg - the SIP request message
 * @param str1 - not used
 * @param str2 - not used
 * @returns the number of headers stripped
 */
int I_NDS_strip_headers(struct sip_msg *msg, char *str1, char *str2)
{
	struct hdr_field *hdr;
	int i, cnt = 0;
	if(parse_headers(msg, HDR_EOH_F, 0) < 0)
		return 0;
	for(hdr = msg->headers; hdr; hdr = hdr->next)
		for(i = 0; untrusted_headers[i].len; i++)
			if(hdr->name.len == untrusted_headers[i].len
					&& strncasecmp(hdr->name.s, untrusted_headers[i].s,
							   hdr->name.len)
							   == 0) {
				//if (!cscf_del_header(msg,hdr)) return 0; TODO
				cnt++;
			}
	LM_DBG("DBG:I_NDS_strip_headers: Deleted %d headers\n", cnt);
	return cnt;
}


/**
 * Refreshes the trusted domain list reading them from the db.
 * Drops the old cache and queries the db
 * \todo - IMPLEMENT A WAY TO PUSH AN EXTERNAL EVENT FOR THIS
 * \todo - SOLVE THE LOCKING PROBLEM - THIS IS A WRITER
 * @returns 1 on success, 0 on failure
 */
int I_NDS_get_trusted_domains()
{
	int i;
	/* free the old cache */
	if(trusted_domains != 0) {
		i = 0;
		while(trusted_domains[i].s) {
			shm_free(trusted_domains[i].s);
			i++;
		}
		shm_free(trusted_domains);
	}
	return ims_icscf_db_get_nds(&trusted_domains);
}
