/**
 * Copyright (C) 2018 Jose Luis Verdeguer
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../../core/sr_module.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/rpc_lookup.h"
#include "../../core/strutils.h"
#include "../../lib/srdb1/db.h"
#include "../../core/dprint.h"
#include "../../core/locking.h"
#include "secfilter.h"
#include "../../core/kemi.h"

MODULE_VERSION

secf_data_p *secf_data = NULL;
secf_data_p secf_data_1 = NULL;
secf_data_p secf_data_2 = NULL;
static gen_lock_t *secf_lock = NULL;
int *secf_stats;
int total_data = 26;

time_t *secf_rpc_reload_time = NULL;

/* Static and shared functions */
static int mod_init(void);
int secf_init_data(void);
static int child_init(int rank);
static int rpc_init(void);
static void free_str_list(struct str_list *l);
static void free_sec_info(secf_info_p info);
void secf_free_data(secf_data_p secf_fdata);
static void mod_destroy(void);
static int sf_check_sqli(str *val, int check_quotes);
static int check_user(struct sip_msg *msg, int type);
void secf_reset_stats(void);
void secf_ht_timer(unsigned int ticks, void *);

/* External functions */
static int w_check_ua(struct sip_msg *msg);
static int w_check_from_hdr(struct sip_msg *msg);
static int w_check_to_hdr(struct sip_msg *msg);
static int w_check_contact_hdr(struct sip_msg *msg);
static int w_check_ip(struct sip_msg *msg);
static int w_check_country(struct sip_msg *msg, char *val);
static int w_check_dst(struct sip_msg *msg, char *val);
static int w_check_sqli_all(struct sip_msg *msg);
static int w_check_sqli_hdr(struct sip_msg *msg, char *val);

/* Exported module parameters - default values */
int secf_dst_exact_match = 1;
str secf_db_url = str_init(DEFAULT_RODB_URL);
str secf_table_name = str_init("secfilter");
str secf_action_col = str_init("action");
str secf_type_col = str_init("type");
str secf_data_col = str_init("data");
int secf_reload_interval = 60;
int secf_reload_delta = 5;

/* clang-format off */
/* Exported commands */
static cmd_export_t cmds[] = {
	{"secf_check_ua", (cmd_function)w_check_ua, 0, 0, 0, ANY_ROUTE},
	{"secf_check_from_hdr", (cmd_function)w_check_from_hdr, 0, 0, 0, ANY_ROUTE},
	{"secf_check_to_hdr", (cmd_function)w_check_to_hdr, 0, 0, 0, ANY_ROUTE},
	{"secf_check_contact_hdr", (cmd_function)w_check_contact_hdr, 0, 0, 0, ANY_ROUTE},
	{"secf_check_ip", (cmd_function)w_check_ip, 0, 0, 0, ANY_ROUTE},
	{"secf_check_country", (cmd_function)w_check_country, 1, 0, 0, ANY_ROUTE},
	{"secf_check_dst", (cmd_function)w_check_dst, 1, 0, 0, ANY_ROUTE},
	{"secf_check_sqli_all", (cmd_function)w_check_sqli_all, 0, 0, 0, ANY_ROUTE},
	{"secf_check_sqli_hdr", (cmd_function)w_check_sqli_hdr, 1, 0, 0, ANY_ROUTE},
	{0, 0, 0, 0, 0, 0}
};

/* Exported module parameters */
static param_export_t params[] = {
	{"db_url", PARAM_STRING, &secf_db_url},
	{"table_name", PARAM_STR, &secf_table_name},
	{"action_col", PARAM_STR, &secf_action_col},
	{"type_col", PARAM_STR, &secf_type_col},
	{"data_col", PARAM_STR, &secf_data_col},
	{"dst_exact_match", PARAM_INT, &secf_dst_exact_match},
	{"reload_delta", PARAM_INT, &secf_reload_delta},
	{"cleanup_interval", PARAM_INT, &secf_reload_interval},
	{0, 0, 0}
};

/* Module exports definition */
struct module_exports exports = {
	"secfilter",     /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* exported functions */
	params,          /* exported parameters */
	0,               /* RPC method exports */
	0,               /* exported pseudo-variables */
	0,               /* response handling function */
	mod_init,        /* module initialization function */
	child_init,      /* per-child init function */
	mod_destroy      /* module destroy function */
};

/* RPC exported commands */
static const char *rpc_reload_doc[2] = {"Reload values from database", NULL};
static const char *rpc_print_doc[2] = {"Print values from database", NULL};
static const char *rpc_stats_doc[2] = {"Print statistics of blocked and allowed messages", NULL};
static const char *rpc_stats_reset_doc[2] = {"Reset statistics", NULL};
static const char *rpc_add_dst_doc[2] = {"Add new values to destination blacklist", NULL};
static const char *rpc_del_dst_doc[2] = {"Delete a value from destination blacklist", NULL};
static const char *rpc_add_bl_doc[2] = {"Add new values to blacklist", NULL};
static const char *rpc_del_bl_doc[2] = {"Delete a value from blacklist", NULL};
static const char *rpc_add_wl_doc[2] = {"Add new values to whitelist", NULL};
static const char *rpc_del_wl_doc[2] = {"Delete a value from whitelist", NULL};

rpc_export_t secfilter_rpc[] = {
	{"secfilter.reload", secf_rpc_reload, rpc_reload_doc, 0},
	{"secfilter.print", secf_rpc_print, rpc_print_doc, 0},
	{"secfilter.stats", secf_rpc_stats, rpc_stats_doc, 0},
	{"secfilter.stats_reset", secf_rpc_stats_reset, rpc_stats_reset_doc, 0},
	{"secfilter.add_dst", secf_rpc_add_dst, rpc_add_dst_doc, 0},
	{"secfilter.del_dst", secf_rpc_del_dst, rpc_del_dst_doc, 0},
	{"secfilter.add_bl", secf_rpc_add_bl, rpc_add_bl_doc, 0},
	{"secfilter.del_bl", secf_rpc_del_bl, rpc_del_bl_doc, 0},
	{"secfilter.add_wl", secf_rpc_add_wl, rpc_add_wl_doc, 0},
	{"secfilter.del_wl", secf_rpc_del_wl, rpc_del_wl_doc, 0},
	{0, 0, 0, 0}
};
/* clang-format on */

/***
PREVENT SQL INJECTION
***/

/* External function to search for illegal characters in several headers */
static int ki_check_sqli_all(struct sip_msg *msg)
{
	str ua = STR_NULL;
	str name = STR_NULL;
	str user = STR_NULL;
	str domain = STR_NULL;
	int res;
	int retval = 1;

	/* Find SQLi in user-agent header */
	res = secf_get_ua(msg, &ua);
	if(res == 0) {
		if(sf_check_sqli(&ua, 1) != 1) {
			LM_INFO("Possible SQL injection found in User-agent (%.*s)\n",
					ua.len, ua.s);
			retval = 0;
			goto end_sqli;
		}
	}

	/* Find SQLi in from header */
	res = secf_get_from(msg, &name, &user, &domain);
	if(res == 0) {
		if(name.len > 0) {
			if(sf_check_sqli(&name, 0) != 1) {
				LM_INFO("Possible SQL injection found in From name (%.*s)\n",
						name.len, name.s);
				retval = 0;
				goto end_sqli;
			}
		}

		if(user.len > 0) {
			if(sf_check_sqli(&user, 1) != 1) {
				LM_INFO("Possible SQL injection found in From user (%.*s)\n",
						user.len, user.s);
				retval = 0;
				goto end_sqli;
			}
		}

		if(domain.len > 0) {
			if(sf_check_sqli(&domain, 1) != 1) {
				LM_INFO("Possible SQL injection found in From domain (%.*s)\n",
						domain.len, domain.s);
				retval = 0;
				goto end_sqli;
			}
		}
	}

	/* Find SQLi in to header */
	res = secf_get_to(msg, &name, &user, &domain);
	if(res == 0) {
		if(name.len > 0) {
			if(sf_check_sqli(&name, 0) != 1) {
				LM_INFO("Possible SQL injection found in To name (%.*s)\n",
						name.len, name.s);
				retval = 0;
				goto end_sqli;
			}
		}

		if(user.len > 0) {
			if(sf_check_sqli(&user, 1) != 1) {
				LM_INFO("Possible SQL injection found in To user (%.*s)\n",
						user.len, user.s);
				retval = 0;
				goto end_sqli;
			}
		}

		if(domain.len > 0) {
			if(sf_check_sqli(&domain, 1) != 1) {
				LM_INFO("Possible SQL injection found in To domain (%.*s)\n",
						domain.len, domain.s);
				retval = 0;
				goto end_sqli;
			}
		}
	}

	/* Find SQLi in contact header */
	res = secf_get_contact(msg, &user, &domain);
	if(res == 0) {
		if(user.len > 0) {
			if(sf_check_sqli(&user, 1) != 1) {
				LM_INFO("Possible SQL injection found in Contact user (%.*s)\n",
						user.len, user.s);
				retval = 0;
				goto end_sqli;
			}
		}

		if(domain.len > 0) {
			if(sf_check_sqli(&domain, 1) != 1) {
				LM_INFO("Possible SQL injection found in Contact domain "
						"(%.*s)\n",
						domain.len, domain.s);
				retval = 0;
				goto end_sqli;
			}
		}
	}

end_sqli:
	return retval;
}

static int w_check_sqli_all(struct sip_msg *msg)
{
	return ki_check_sqli_all(msg);
}


/* External function to search for illegal characters in some header */
static int ki_check_sqli_hdr(struct sip_msg *msg, str *cval)
{
	return sf_check_sqli(cval, 1);
}

static int w_check_sqli_hdr(struct sip_msg *msg, char *cval)
{
	str val;

	val.s = cval;
	val.len = strlen(cval);

	return sf_check_sqli(&val, 1);
}

/* Search for illegal characters */
static int sf_check_sqli(str *val, int check_quotes)
{
	char *cval = NULL;
	int res = 1;

	cval = (char *)pkg_malloc(val->len + 1);
	if(cval == NULL) {
		PKG_MEM_CRITICAL;
		return -2;
	}
	memset(cval, 0, val->len + 1);
	memcpy(cval, val->s, val->len);

	if(check_quotes == 1) {
		if(strstr(cval, "'") || strstr(cval, "\"") || strstr(cval, "--")
				|| strstr(cval, "%27") || strstr(cval, "%22")
				|| strstr(cval, "%60")) {
			/* Illegal characters found */
			lock_get(secf_lock);
			secf_stats[BL_SQL]++;
			lock_release(secf_lock);
			res = -1;
			goto end;
		}
	} else {
		if(strstr(cval, "--") || strstr(cval, "%60")) {
			/* Illegal characters found */
			lock_get(secf_lock);
			secf_stats[BL_SQL]++;
			lock_release(secf_lock);
			res = -1;
			goto end;
		}
	}

end:
	if(cval)
		pkg_free(cval);

	return res;
}


/***
BLACKLIST AND WHITELIST
***/

/* Check if the current destination is allowed */
static int ki_check_dst(struct sip_msg *msg, str *val)
{
	str dst;
	struct str_list *list;

	dst.s = val->s;
	dst.len = val->len;

	list = (*secf_data)->bl.dst;
	while(list) {
		if(secf_dst_exact_match == 1) {
			/* Exact match */
			if(list->s.len == dst.len) {
				if(cmpi_str(&list->s, &dst) == 0) {
					lock_get(secf_lock);
					secf_stats[BL_DST]++;
					lock_release(secf_lock);
					return -2;
				}
			}
		} else {
			/* Any match */
			dst.len = val->len;
			if(dst.len > list->s.len)
				dst.len = list->s.len;
			if(cmpi_str(&list->s, &dst) == 0) {
				lock_get(secf_lock);
				secf_stats[BL_DST]++;
				lock_release(secf_lock);
				return -2;
			}
		}
		list = list->next;
	}

	return 1;
}

static int w_check_dst(struct sip_msg *msg, char *val)
{
	str dst;
	dst.s = val;
	dst.len = strlen(val);
	return ki_check_dst(msg, &dst);
}

/* Check if the current user-agent is allowed
Return codes:
 2 = user-agent whitelisted
 1 = not found
-1 = error
-2 = user-agent blacklisted
*/
static int ki_check_ua(struct sip_msg *msg)
{
	int res, len;
	str ua;
	struct str_list *list;

	res = secf_get_ua(msg, &ua);
	if(res != 0)
		return res;

	len = ua.len;

	/* User-agent whitelisted */
	list = (*secf_data)->wl.ua;
	while(list) {
		if(ua.len > list->s.len)
			ua.len = list->s.len;
		res = cmpi_str(&list->s, &ua);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[WL_UA]++;
			lock_release(secf_lock);
			return 2;
		}
		list = list->next;
		ua.len = len;
	}

	/* User-agent blacklisted */
	list = (*secf_data)->bl.ua;
	while(list) {
		if(ua.len > list->s.len)
			ua.len = list->s.len;
		res = cmpi_str(&list->s, &ua);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[BL_UA]++;
			lock_release(secf_lock);
			return -2;
		}
		list = list->next;
		ua.len = len;
	}

	return 1;
}

static int w_check_ua(struct sip_msg *msg)
{
	return ki_check_ua(msg);
}


static int ki_check_from_hdr(struct sip_msg *msg)
{
	return check_user(msg, 1);
}

/* Check if the current from user is allowed */
static int w_check_from_hdr(struct sip_msg *msg)
{
	return check_user(msg, 1);
}

static int ki_check_to_hdr(struct sip_msg *msg)
{
	return check_user(msg, 2);
}

/* Check if the current to user is allowed */
static int w_check_to_hdr(struct sip_msg *msg)
{
	return check_user(msg, 2);
}

static int ki_check_contact_hdr(struct sip_msg *msg)
{
	return check_user(msg, 3);
}

/* Check if the current contact user is allowed */
static int w_check_contact_hdr(struct sip_msg *msg)
{
	return check_user(msg, 3);
}

/*
Check if the current user is allowed

Return codes:
 4 = name whitelisted
 3 = domain whitelisted
 2 = user whitelisted
 1 = not found
-1 = error
-2 = user blacklisted
-3 = domain blacklisted
-4 = name blacklisted
*/
static int check_user(struct sip_msg *msg, int type)
{
	str name = STR_NULL;
	str user = STR_NULL;
	str domain = STR_NULL;
	int res = 0;
	int nlen, ulen, dlen;
	struct str_list *list = NULL;

	switch(type) {
		case 1:
			res = secf_get_from(msg, &name, &user, &domain);
			break;
		case 2:
			res = secf_get_to(msg, &name, &user, &domain);
			break;
		case 3:
			res = secf_get_contact(msg, &user, &domain);
			break;
		default:
			return -1;
	}
	if(res != 0) {
		return res;
	}

	if(user.s == NULL || domain.s == NULL) {
		return -1;
	}

	nlen = name.len;
	ulen = user.len;
	dlen = domain.len;

	/* User whitelisted */
	list = (*secf_data)->wl.user;
	while(list) {
		if(name.len > list->s.len)
			name.len = list->s.len;
		if(name.s != NULL) {
			res = cmpi_str(&list->s, &name);
			if(res == 0) {
				lock_get(secf_lock);
				switch(type) {
					case 1:
						secf_stats[WL_FNAME]++;
						break;
					case 2:
						secf_stats[WL_TNAME]++;
						break;
					case 3:
						secf_stats[WL_CNAME]++;
						break;
				}
				lock_release(secf_lock);
				return 4;
			}
		}
		if(user.len > list->s.len)
			user.len = list->s.len;
		res = cmpi_str(&list->s, &user);
		if(res == 0) {
			lock_get(secf_lock);
			switch(type) {
				case 1:
					secf_stats[WL_FUSER]++;
					break;
				case 2:
					secf_stats[WL_TUSER]++;
					break;
				case 3:
					secf_stats[WL_CUSER]++;
					break;
			}
			lock_release(secf_lock);
			return 2;
		}
		list = list->next;
		name.len = nlen;
		user.len = ulen;
	}
	/* User blacklisted */
	list = (*secf_data)->bl.user;
	while(list) {
		if(name.len > list->s.len)
			name.len = list->s.len;
		if(name.s != NULL) {
			res = cmpi_str(&list->s, &name);
			if(res == 0) {
				lock_get(secf_lock);
				switch(type) {
					case 1:
						secf_stats[BL_FNAME]++;
						break;
					case 2:
						secf_stats[BL_TNAME]++;
						break;
					case 3:
						secf_stats[BL_CNAME]++;
						break;
				}
				lock_release(secf_lock);
				return -4;
			}
		}
		if(user.len > list->s.len)
			user.len = list->s.len;
		res = cmpi_str(&list->s, &user);
		if(res == 0) {
			lock_get(secf_lock);
			switch(type) {
				case 1:
					secf_stats[BL_FUSER]++;
					break;
				case 2:
					secf_stats[BL_TUSER]++;
					break;
				case 3:
					secf_stats[BL_CUSER]++;
					break;
			}
			lock_release(secf_lock);
			return -2;
		}
		list = list->next;
		name.len = nlen;
		user.len = ulen;
	}

	/* Domain whitelisted */
	list = (*secf_data)->wl.domain;
	while(list) {
		if(domain.len > list->s.len)
			domain.len = list->s.len;
		res = cmpi_str(&list->s, &domain);
		if(res == 0) {
			lock_get(secf_lock);
			switch(type) {
				case 1:
					secf_stats[WL_FDOMAIN]++;
					break;
				case 2:
					secf_stats[WL_TDOMAIN]++;
					break;
				case 3:
					secf_stats[WL_CDOMAIN]++;
					break;
			}
			lock_release(secf_lock);
			return 3;
		}
		list = list->next;
		domain.len = dlen;
	}
	/* Domain blacklisted */
	list = (*secf_data)->bl.domain;
	while(list) {
		if(domain.len > list->s.len)
			domain.len = list->s.len;
		res = cmpi_str(&list->s, &domain);
		if(res == 0) {
			lock_get(secf_lock);
			switch(type) {
				case 1:
					secf_stats[BL_FDOMAIN]++;
					break;
				case 2:
					secf_stats[BL_TDOMAIN]++;
					break;
				case 3:
					secf_stats[BL_CDOMAIN]++;
					break;
			}
			lock_release(secf_lock);
			return -3;
		}
		list = list->next;
		domain.len = dlen;
	}

	return 1;
}


/* Check if the current IP is allowed

Return codes:
 2 = IP address whitelisted
 1 = not found
-1 = error
-2 = IP address blacklisted
*/
static int ki_check_ip(struct sip_msg *msg)
{
	int res, len;
	str ip;
	struct str_list *list;

	if(msg == NULL)
		return -1;

	ip.s = ip_addr2a(&msg->rcv.src_ip);
	ip.len = strlen(ip.s);

	len = ip.len;

	/* IP address whitelisted */
	list = (*secf_data)->wl.ip;
	while(list) {
		if(ip.len > list->s.len)
			ip.len = list->s.len;
		res = cmpi_str(&list->s, &ip);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[WL_IP]++;
			lock_release(secf_lock);
			return 2;
		}
		list = list->next;
		ip.len = len;
	}
	/* IP address blacklisted */
	list = (*secf_data)->bl.ip;
	while(list) {
		if(ip.len > list->s.len)
			ip.len = list->s.len;
		res = cmpi_str(&list->s, &ip);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[BL_IP]++;
			lock_release(secf_lock);
			return -2;
		}
		list = list->next;
		ip.len = len;
	}

	return 1;
}

static int w_check_ip(struct sip_msg *msg)
{
	return ki_check_ip(msg);
}

/* Check if the current country is allowed

Return codes:
 2 = Country whitelisted
 1 = not found
-2 = Country blacklisted
*/
static int ki_check_country(struct sip_msg *msg, str *val)
{
	int res, len;
	str country;
	struct str_list *list;

	country.s = val->s;
	country.len = val->len;

	len = country.len;

	/* Country whitelisted */
	list = (*secf_data)->wl.country;
	while(list) {
		if(country.len > list->s.len)
			country.len = list->s.len;
		res = cmpi_str(&list->s, &country);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[WL_COUNTRY]++;
			lock_release(secf_lock);
			return 2;
		}
		list = list->next;
		country.len = len;
	}
	/* Country blacklisted */
	list = (*secf_data)->bl.country;
	while(list) {
		if(country.len > list->s.len)
			country.len = list->s.len;
		res = cmpi_str(&list->s, &country);
		if(res == 0) {
			lock_get(secf_lock);
			secf_stats[BL_COUNTRY]++;
			lock_release(secf_lock);
			return -2;
		}
		list = list->next;
		country.len = len;
	}

	return 1;
}

static int w_check_country(struct sip_msg *msg, char *val)
{
	str country;

	country.s = val;
	country.len = strlen(val);

	return ki_check_country(msg, &country);
}

void secf_reset_stats(void)
{
	lock_get(secf_lock);
	memset(secf_stats, 0, total_data * sizeof(int));
	lock_release(secf_lock);
}


/***
INIT AND DESTROY FUNCTIONS
***/

/* Initialize data */
int secf_init_data(void)
{
	secf_data_1 = (secf_data_p)shm_malloc(sizeof(secf_data_t));
	if(!secf_data_1) {
		SHM_MEM_ERROR;
		return -1;
	}
	memset(secf_data_1, 0, sizeof(secf_data_t));

	secf_data_2 = (secf_data_p)shm_malloc(sizeof(secf_data_t));
	if(!secf_data_2) {
		SHM_MEM_ERROR;
		shm_free(secf_data_1);
		return -1;
	}
	memset(secf_data_2, 0, sizeof(secf_data_t));

	secf_data = shm_malloc(sizeof(secf_data_t));
	if(secf_data == NULL) {
		SHM_MEM_ERROR;
		shm_free(secf_data_1);
		shm_free(secf_data_2);
		return -1;
	}

	secf_stats = shm_malloc(total_data * sizeof(int));
	if(!secf_stats) {
		SHM_MEM_ERROR;
		shm_free(secf_data_1);
		shm_free(secf_data_2);
		shm_free(secf_data);
		return -1;
	}
	memset(secf_stats, 0, total_data * sizeof(int));

	if(secf_dst_exact_match != 0)
		secf_dst_exact_match = 1;

	if(register_timer(secf_ht_timer, NULL, secf_reload_interval) < 0)
		return -1;

	return 0;
}


/* Module init function */
static int mod_init(void)
{
	LM_DBG("SECFILTER module init\n");
	/* Init data to store database values */
	if(secf_init_data() == -1)
		return -1;
	/* Reload time */
	secf_rpc_reload_time = shm_malloc(sizeof(time_t));
	if(secf_rpc_reload_time == NULL) {
		SHM_MEM_ERROR;
		return -1;
	}
	*secf_rpc_reload_time = 0;

	if(secf_reload_delta < 0)
		secf_reload_delta = 5;

	/* Init RPC */
	if(rpc_init() < 0)
		return -1;
	/* Init locks */
	if(lock_init(&secf_data_1->lock) == 0) {
		LM_CRIT("cannot initialize lock.\n");
		return -1;
	}
	if(lock_init(&secf_data_2->lock) == 0) {
		LM_CRIT("cannot initialize lock.\n");
		return -1;
	}

	secf_lock = lock_alloc();
	if(!secf_lock) {
		LM_CRIT("cannot allocate memory for lock.\n");
		return -1;
	}
	if(lock_init(secf_lock) == 0) {
		LM_CRIT("cannot initialize lock.\n");
		return -1;
	}
	/* Init database connection and check version */
	if(secf_init_db() == -1)
		return -1;
	/* Load data from database */
	if(secf_load_db() == -1) {
		LM_ERR("Error loading data from database\n");
		return -1;
	}

	return 0;
}


/* Module child init function */
static int child_init(int rank)
{
	if(rank == PROC_INIT || rank == PROC_MAIN || rank == PROC_TCP_MAIN)
		return 0; /* do nothing for the main process */

	return 0;
}


/* Module destroy function */
static void mod_destroy(void)
{
	LM_DBG("SECFILTER module destroy\n");
}


/* RPC init */
static int rpc_init(void)
{
	/* Register RPC commands */
	if(rpc_register_array(secfilter_rpc) != 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}


/* Free shared data */
static void free_str_list(struct str_list *l)
{
	struct str_list *i;
	while(l) {
		i = l->next;
		LM_DBG("free '%.*s'[%p] next:'%p'\n", l->s.len, l->s.s, l, i);
		shm_free(l->s.s);
		shm_free(l);
		l = i;
	}
}


static void free_sec_info(secf_info_p info)
{
	LM_DBG("freeing ua[%p]\n", info->ua);
	free_str_list(info->ua);
	LM_DBG("freeing country[%p]\n", info->country);
	free_str_list(info->country);
	LM_DBG("freeing domain[%p]\n", info->domain);
	free_str_list(info->domain);
	LM_DBG("freeing user[%p]\n", info->user);
	free_str_list(info->user);
	LM_DBG("freeing ip[%p]\n", info->ip);
	free_str_list(info->ip);
	LM_DBG("freeing dst[%p]\n", info->dst);
	free_str_list(info->dst);
	LM_DBG("zeroed info[%p]\n", info);
	memset(info, 0, sizeof(secf_info_t));
}


void secf_ht_timer(unsigned int ticks, void *param)
{
	if(secf_rpc_reload_time == NULL)
		return;

	if(*secf_rpc_reload_time != 0
			&& *secf_rpc_reload_time > time(NULL) - secf_reload_interval)
		return;

	LM_DBG("cleaning old data list\n");
	if(*secf_data == secf_data_1) {
		secf_free_data(secf_data_2);
	} else {
		secf_free_data(secf_data_1);
	}
}


void secf_free_data(secf_data_p secf_fdata)
{
	lock_get(&secf_fdata->lock);

	LM_DBG("freeing wl\n");
	free_sec_info(&secf_fdata->wl);
	memset(&secf_fdata->wl_last, 0, sizeof(secf_info_t));
	LM_DBG("so, ua[%p] should be NULL\n", secf_fdata->wl.ua);

	LM_DBG("freeing bl\n");
	free_sec_info(&secf_fdata->bl);
	memset(&secf_fdata->bl_last, 0, sizeof(secf_info_t));
	LM_DBG("so, ua[%p] should be NULL\n", secf_fdata->bl.ua);

	lock_release(&secf_fdata->lock);
}

/**
 * KEMI exports
 */
/* clang-format off */
static sr_kemi_t sr_kemi_secfilter_exports[] = {
	{ str_init("secfilter"), str_init("secf_check_dst"),
		SR_KEMIP_INT, ki_check_dst,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}},
	{ str_init("secfilter"), str_init("secf_check_ip"),
		SR_KEMIP_INT, ki_check_ip,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}},
	{ str_init("secfilter"), str_init("secf_check_ua"),
		SR_KEMIP_INT, ki_check_ua,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}},
	{ str_init("secfilter"), str_init("secf_check_country"),
		SR_KEMIP_INT, ki_check_country,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ str_init("secfilter"), str_init("secf_check_from_hdr"),
		SR_KEMIP_INT, ki_check_from_hdr,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ str_init("secfilter"), str_init("secf_check_to_hdr"),
		SR_KEMIP_INT, ki_check_to_hdr,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ str_init("secfilter"), str_init("secf_check_contact_hdr"),
		SR_KEMIP_INT, ki_check_contact_hdr,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ str_init("secfilter"), str_init("secf_sqli_hdr"),
		SR_KEMIP_INT, ki_check_sqli_hdr,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ str_init("secfilter"), str_init("secf_check_sqli_all"),
		SR_KEMIP_INT, ki_check_sqli_all,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE}
	},
	{ {0, 0}, {0, 0}, 0, NULL, {0, 0, 0, 0, 0, 0} }
};
/* clang-format on */

/**
 *
 */
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_secfilter_exports);
	return 0;
}
