/*
 * Kamailio H.350 Module
 *
 * Copyright (C) 2007 University of North Carolina
 *
 * Original author: Christian Schlatter, cs@unc.edu
 *
 * 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
 *
 */

#include <regex.h>
#include "ldap.h"

#include "h350_mod.h"
#include "h350_exp_fn.h"

#include "../../core/pvar.h"
#include "../../core/ut.h"
#include "../../core/mem/mem.h"

#define H350_SIPURI_LOOKUP_LDAP_FILTER \
	"(&(objectClass=SIPIdentity)(SIPIdentitySIPURI=%s))"
#define H350_AUTH_FILTER_PATTERN \
	"(&(objectClass=SIPIdentity)(SIPIdentityUserName=%s))"

static str h350_call_pref_name = str_init("callPreferenceURI");
static str h350_sip_pwd_name = str_init("SIPIdentityPassword");
static str h350_service_level_name = str_init("SIPIdentityServiceLevel");

#define H350_CALL_PREF_REGEX "^([^ ]+) +([a-zA-Z]+)(:([0-9]+))?$"

#define SIP_URI_ESCAPED_MAX_LEN 1024
#define AVP_NAME_STR_BUF_LEN 1024
#define DIGEST_USERNAME_BUF_SIZE 2048

static regex_t *call_pref_preg;

int h350_sipuri_lookup(struct sip_msg *_msg, pv_elem_t *_sip_uri)
{
	str sip_uri, sip_uri_escaped;
	int ld_result_count;
	static char sip_uri_escaped_buf[SIP_URI_ESCAPED_MAX_LEN];

	/*
	 * get sip_uri
	 */
	if(pv_printf_s(_msg, _sip_uri, &sip_uri) != 0) {
		LM_ERR("pv_printf_s failed\n");
		return E_H350_INTERNAL;
	}

	/*
	 * ldap filter escape sip_uri
	 */
	sip_uri_escaped.s = sip_uri_escaped_buf;
	sip_uri_escaped.len = SIP_URI_ESCAPED_MAX_LEN - 1;
	if(ldap_api.ldap_rfc4515_escape(&sip_uri, &sip_uri_escaped, 0)) {
		LM_ERR("ldap_rfc4515_escape failed\n");
		return E_H350_INTERNAL;
	}

	/*
	 * do ldap search
	 */
	if(ldap_api.ldap_params_search(&ld_result_count, h350_ldap_session,
			   h350_base_dn, h350_search_scope_int, NULL,
			   H350_SIPURI_LOOKUP_LDAP_FILTER, sip_uri_escaped.s)
			!= 0) {
		LM_ERR("ldap search failed\n");
		return E_H350_INTERNAL;
	}

	if(ld_result_count < 1) {
		return E_H350_NO_SUCCESS;
	}

	return ld_result_count;
}

int h350_auth_lookup(struct sip_msg *_msg, pv_elem_t *_digest_username,
		struct h350_auth_lookup_avp_params *_avp_specs)
{
	str digest_username, digest_username_escaped, digest_password;
	static char digest_username_buf[DIGEST_USERNAME_BUF_SIZE],
			username_avp_name_buf[AVP_NAME_STR_BUF_LEN],
			password_avp_name_buf[AVP_NAME_STR_BUF_LEN];
	struct berval **attr_vals = NULL;
	avp_name_t username_avp_name, password_avp_name;
	avp_value_t avp_val;
	avp_flags_t username_avp_type, password_avp_type;
	int rc, ld_result_count;

	/*
	 * get digest_username str
	 */
	if(_digest_username) {
		if(pv_printf_s(_msg, _digest_username, &digest_username) != 0) {
			LM_ERR("pv_printf_s failed\n");
			return E_H350_INTERNAL;
		}
	} else {
		LM_ERR("empty digest username\n");
		return E_H350_NO_SUCCESS;
	}

	/*
	 * get AVP names for username and password
	 */

	if(pv_get_avp_name(_msg, &(_avp_specs->username_avp_spec.pvp),
			   &username_avp_name, &username_avp_type)
			!= 0) {
		LM_ERR("error getting AVP name - pv_get_avp_name failed\n");
		return E_H350_INTERNAL;
	}
	if(username_avp_type & AVP_NAME_STR) {
		if(username_avp_name.s.len >= AVP_NAME_STR_BUF_LEN) {
			LM_ERR("username AVP name too long\n");
			return E_H350_INTERNAL;
		}
		strncpy(username_avp_name_buf, username_avp_name.s.s,
				username_avp_name.s.len);
		username_avp_name_buf[username_avp_name.s.len] = '\0';
		username_avp_name.s.s = username_avp_name_buf;
	}

	if(pv_get_avp_name(_msg, &(_avp_specs->password_avp_spec.pvp),
			   &password_avp_name, &password_avp_type)
			!= 0) {
		LM_ERR("error getting AVP name - pv_get_avp_name failed\n");
		return E_H350_INTERNAL;
	}
	if(password_avp_type & AVP_NAME_STR) {
		if(password_avp_name.s.len >= AVP_NAME_STR_BUF_LEN) {
			LM_ERR("password AVP name too long\n");
			return E_H350_INTERNAL;
		}
		strncpy(password_avp_name_buf, password_avp_name.s.s,
				password_avp_name.s.len);
		password_avp_name_buf[password_avp_name.s.len] = '\0';
		password_avp_name.s.s = password_avp_name_buf;
	}


	/*
	 * search for sip digest username in H.350, store digest password
	 */

	/* ldap filter escape digest username */
	digest_username_escaped.s = digest_username_buf;
	digest_username_escaped.len = DIGEST_USERNAME_BUF_SIZE - 1;
	if(ldap_api.ldap_rfc4515_escape(
			   &digest_username, &digest_username_escaped, 0)) {
		LM_ERR("ldap_rfc4515_escape() failed\n");
		return E_H350_INTERNAL;
	}

	/* do ldap search */
	if(ldap_api.ldap_params_search(&ld_result_count, h350_ldap_session,
			   h350_base_dn, h350_search_scope_int, NULL,
			   H350_AUTH_FILTER_PATTERN, digest_username_escaped.s)
			!= 0) {
		LM_ERR("LDAP search failed\n");
		return E_H350_INTERNAL;
	}

	if(ld_result_count < 1) {
		LM_INFO("no H.350 entry found for username [%s]\n",
				digest_username_escaped.s);
		return E_H350_NO_SUCCESS;
	}
	if(ld_result_count > 1) {
		LM_WARN("more than one [%d] H.350 entry found for username [%s]\n",
				ld_result_count, digest_username_escaped.s);
	}

	/* get ldap result values */
	rc = ldap_api.ldap_result_attr_vals(&h350_sip_pwd_name, &attr_vals);
	if(rc < 0) {
		LM_ERR("getting LDAP attribute values failed\n");
		ldap_api.ldap_value_free_len(attr_vals);
		return E_H350_INTERNAL;
	}
	if((rc > 0) || (attr_vals == NULL)) {
		LM_INFO("no values found in LDAP entry for username [%s]\n",
				digest_username_escaped.s);
		ldap_api.ldap_value_free_len(attr_vals);
		return E_H350_INTERNAL;
	}

	digest_password.s = attr_vals[0]->bv_val;
	digest_password.len = attr_vals[0]->bv_len;

	/*
	 * write AVPs
	 */

	avp_val.s = digest_username;
	if(add_avp(username_avp_type | AVP_VAL_STR, username_avp_name, avp_val)
			< 0) {
		LM_ERR("failed to create new AVP\n");
		ldap_api.ldap_value_free_len(attr_vals);
		return E_H350_INTERNAL;
	}

	avp_val.s = digest_password;
	if(add_avp(password_avp_type | AVP_VAL_STR, password_avp_name, avp_val)
			< 0) {
		LM_ERR("failed to create new AVP\n");
		ldap_api.ldap_value_free_len(attr_vals);
		return E_H350_INTERNAL;
	}

	ldap_api.ldap_value_free_len(attr_vals);
	return E_H350_SUCCESS;
}

int h350_call_preferences(struct sip_msg *_msg, pv_elem_t *_avp_name_prefix)
{
	int rc, i, avp_count = 0;
	struct berval **attr_vals;
	size_t nmatch = 5;
	regmatch_t pmatch[5];
	int_str avp_name, avp_val;
	str avp_val_str, avp_name_str, avp_name_prefix_str, call_pref_timeout_str;
	int call_pref_timeout;
	static char call_pref_avp_name[AVP_NAME_STR_BUF_LEN];

	/*
         * get avp_name_prefix_str
         */
	if(pv_printf_s(_msg, _avp_name_prefix, &avp_name_prefix_str) != 0) {
		LM_ERR("pv_printf_s failed\n");
		return E_H350_INTERNAL;
	}


	/*
	 * get LDAP attribute values
	 */
	if((rc = ldap_api.ldap_result_attr_vals(&h350_call_pref_name, &attr_vals))
			< 0) {
		LM_ERR("Getting LDAP attribute values failed\n");
		return E_H350_INTERNAL;
	}

	if(rc > 0) {
		/* no LDAP values found */
		return E_H350_NO_SUCCESS;
	}

	/*
	 * loop through call pref values and add AVP(s)
	 */

	/* copy avp name prefix into call_pref_avp_name */
	if(avp_name_prefix_str.len < AVP_NAME_STR_BUF_LEN) {
		memcpy(call_pref_avp_name, avp_name_prefix_str.s,
				avp_name_prefix_str.len);
	} else {
		LM_ERR("AVP name prefix too long [%d] (max [%d])",
				avp_name_prefix_str.len, AVP_NAME_STR_BUF_LEN);
		return E_H350_INTERNAL;
	}

	for(i = 0; attr_vals[i] != NULL; i++) {
		if((rc = regexec(
					call_pref_preg, attr_vals[i]->bv_val, nmatch, pmatch, 0))
				!= 0) {
			switch(rc) {
				case REG_NOMATCH:
					LM_INFO("no h350 call preference regex match for [%s]\n",
							attr_vals[i]->bv_val);
					continue;
				case REG_ESPACE:
					LM_ERR("regexec returned REG_ESPACE - out of memory\n");
					ldap_api.ldap_value_free_len(attr_vals);
					return E_H350_INTERNAL;
				default:
					LM_ERR("regexec failed\n");
					ldap_api.ldap_value_free_len(attr_vals);
					return E_H350_INTERNAL;
			}
		}

		/* calculate call preference sip uri */
		if(avp_name_prefix_str.len + pmatch[2].rm_eo - pmatch[2].rm_so
				>= AVP_NAME_STR_BUF_LEN) {
			LM_ERR("AVP name too long for [%s]", attr_vals[i]->bv_val);
			continue;
		}
		avp_val_str.s = attr_vals[i]->bv_val + pmatch[1].rm_so;
		avp_val_str.len = pmatch[1].rm_eo - pmatch[1].rm_so;

		avp_val.s = avp_val_str;

		/* calculate call preference avp name */
		memcpy(call_pref_avp_name + avp_name_prefix_str.len,
				attr_vals[i]->bv_val + pmatch[2].rm_so,
				pmatch[2].rm_eo - pmatch[2].rm_so);

		avp_name_str.s = call_pref_avp_name;
		avp_name_str.len =
				avp_name_prefix_str.len + pmatch[2].rm_eo - pmatch[2].rm_so;

		avp_name.s = avp_name_str;

		/* add avp */
		if(add_avp(AVP_NAME_STR | AVP_VAL_STR, avp_name, avp_val) < 0) {
			LM_ERR("failed to create new AVP\n");
			ldap_api.ldap_value_free_len(attr_vals);
			return E_H350_INTERNAL;
		}

		avp_count++;

		/* check for call preference timeout */
		if((pmatch[4].rm_eo - pmatch[4].rm_so) == 0) {
			continue;
		}

		/* calculate call preference timeout avp name */
		memcpy(avp_name_str.s + avp_name_str.len, "_t", 2);
		avp_name_str.len += 2;
		avp_name.s = avp_name_str;

		/* calculate timeout avp value */
		call_pref_timeout_str.s = attr_vals[i]->bv_val + pmatch[4].rm_so;
		call_pref_timeout_str.len = pmatch[4].rm_eo - pmatch[4].rm_so;
		if(str2sint(&call_pref_timeout_str, &call_pref_timeout) != 0) {
			LM_ERR("str2sint failed\n");
			ldap_api.ldap_value_free_len(attr_vals);
			return E_H350_INTERNAL;
		}
		call_pref_timeout = call_pref_timeout / 1000;

		/* add timeout avp */
		avp_val.n = call_pref_timeout;
		if(add_avp(AVP_NAME_STR, avp_name, avp_val) < 0) {
			LM_ERR("failed to create new AVP\n");
			ldap_api.ldap_value_free_len(attr_vals);
			return E_H350_INTERNAL;
		}
	}

	ldap_api.ldap_value_free_len(attr_vals);
	if(avp_count > 0) {
		return avp_count;
	} else {
		return E_H350_NO_SUCCESS;
	}
}

int h350_service_level(struct sip_msg *_msg, pv_elem_t *_avp_name_prefix)
{
	int i, rc, avp_count = 0;
	str avp_name_prefix;
	int_str avp_name, avp_val;
	struct berval **attr_vals;
	static char service_level_avp_name[AVP_NAME_STR_BUF_LEN];

	/*
         * get service_level
         */
	if(pv_printf_s(_msg, _avp_name_prefix, &avp_name_prefix) != 0) {
		LM_ERR("pv_printf_s failed\n");
		return E_H350_INTERNAL;
	}

	/*
         * get LDAP attribute values
         */
	if((rc = ldap_api.ldap_result_attr_vals(
				&h350_service_level_name, &attr_vals))
			< 0) {
		LM_ERR("Getting LDAP attribute values failed\n");
		return E_H350_INTERNAL;
	}
	if(rc > 0) {
		/* no LDAP values found */
		return E_H350_NO_SUCCESS;
	}

	/* copy avp name prefix into service_level_avp_name */
	if(avp_name_prefix.len < AVP_NAME_STR_BUF_LEN) {
		memcpy(service_level_avp_name, avp_name_prefix.s, avp_name_prefix.len);
	} else {
		LM_ERR("AVP name prefix too long [%d] (max [%d])\n",
				avp_name_prefix.len, AVP_NAME_STR_BUF_LEN);
		ldap_api.ldap_value_free_len(attr_vals);
		return E_H350_INTERNAL;
	}


	/*
	 * loop through service level values and add AVP(s)
	 */

	for(i = 0; attr_vals[i] != NULL; i++) {
		/* get avp name */
		if(avp_name_prefix.len + attr_vals[i]->bv_len >= AVP_NAME_STR_BUF_LEN) {
			LM_ERR("AVP name too long for [%s]\n", attr_vals[i]->bv_val);
			continue;
		}
		memcpy(service_level_avp_name + avp_name_prefix.len,
				attr_vals[i]->bv_val, attr_vals[i]->bv_len);
		avp_name.s.s = service_level_avp_name;
		avp_name.s.len = avp_name_prefix.len + attr_vals[i]->bv_len;

		/* avp value = 1 */
		avp_val.n = 1;

		if(add_avp(AVP_NAME_STR, avp_name, avp_val) < 0) {
			LM_ERR("failed to create new AVP\n");
			ldap_api.ldap_value_free_len(attr_vals);
			return E_H350_INTERNAL;
		}
		avp_count++;
	}

	ldap_api.ldap_value_free_len(attr_vals);
	if(avp_count > 0) {
		return avp_count;
	} else {
		return E_H350_NO_SUCCESS;
	}
}

int h350_exp_fn_init(void)
{
	int rc;

	if((call_pref_preg = pkg_malloc(sizeof(regex_t))) == 0) {
		LM_ERR("allocating memory for regex failed\n");
		return -1;
	}

	if((rc = regcomp(call_pref_preg, H350_CALL_PREF_REGEX, REG_EXTENDED))
			!= 0) {
		pkg_free(call_pref_preg);
		LM_ERR("regcomp failed - returned [%d]\n", rc);
		return -1;
	}
	return 0;
}
