/*
 * Domain module
 *
 * Copyright (C) 2002-2003 Juha Heinanen
 *
 * This file is part of sip-router, a free SIP server.
 *
 * sip-router 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
 *
 * sip-router 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
 *
 * History:
 * -------
 * 2003-03-11: New module interface (janakj)
 * 2003-03-16: flags export parameter added (janakj)
 * 2003-04-05: default_uri #define used (jiri)
 * 2003-04-06: db connection closed in mod_init (janakj)
 * 2004-06-06  updated to the new DB api, cleanup: static dbf & handler,
 *              calls to domain_db_{bind,init,close,ver} (andrei)
 */

#include "uid_domain_mod.h"
#include <stdio.h>
#include "../../core/mem/shm_mem.h"
#include "../../core/mem/mem.h"
#include "../../core/sr_module.h"
#include "../../core/ut.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/usr_avp.h"
#include "domain_api.h"
#include "domain_rpc.h"
#include "hash.h"
#include "domain.h"


/*
 * Module management function prototypes
 */
static int mod_init(void);
static void destroy(void);
static int child_init(int rank);

static int is_local(struct sip_msg *msg, char *s1, char *s2);
static int lookup_domain(struct sip_msg *msg, char *s1, char *s2);
static int get_did(str *did, str *domain);

static int lookup_domain_fixup(void **param, int param_no);

MODULE_VERSION

/*
 * Version of domain table required by the module, increment this value if you
 * change the table in a backwards incompatible way
 */
#define DOMAIN_TABLE_VERSION 2
#define DOMATTR_TABLE_VERSION 1

#define DOMAIN_TABLE "uid_domain"
#define DOMAIN_COL "domain"
#define DID_COL "did"
#define FLAGS_COL "flags"

#define DOMATTR_TABLE "uid_domain_attrs"
#define DOMATTR_DID "did"
#define DOMATTR_NAME "name"
#define DOMATTR_TYPE "type"
#define DOMATTR_VALUE "value"
#define DOMATTR_FLAGS "flags"
#define DOMAIN_COL "domain"

int db_mode = 1; /* Enable/disable domain cache */

/*
 * Module parameter variables
 */
static str db_url = STR_STATIC_INIT(DEFAULT_RODB_URL);

str domain_table = STR_STATIC_INIT(DOMAIN_TABLE); /* Name of domain table */
str domain_col = STR_STATIC_INIT(DOMAIN_COL);	  /* Name of domain column */
str did_col = STR_STATIC_INIT(DID_COL);			  /* Domain id */
str flags_col = STR_STATIC_INIT(FLAGS_COL);		  /* Domain flags */

str domattr_table = STR_STATIC_INIT(DOMATTR_TABLE);
str domattr_did = STR_STATIC_INIT(DOMATTR_DID);
str domattr_name = STR_STATIC_INIT(DOMATTR_NAME);
str domattr_type = STR_STATIC_INIT(DOMATTR_TYPE);
str domattr_value = STR_STATIC_INIT(DOMATTR_VALUE);
str domattr_flags = STR_STATIC_INIT(DOMATTR_FLAGS);

int load_domain_attrs = 0; /* Load attributes for each domain by default */

static db_ctx_t *db = NULL;
db_cmd_t *load_domains_cmd = NULL, *get_did_cmd = NULL, *load_attrs_cmd = NULL;

struct hash_entry ***active_hash = 0; /* Pointer to current hash table */
struct hash_entry **hash_1 = 0;		  /* Pointer to hash table 1 */
struct hash_entry **hash_2 = 0;		  /* Pointer to hash table 2 */

domain_t **domains_1 = 0; /* List of domains 1 */
domain_t **domains_2 = 0; /* List of domains 2 */

/* Global domain structure, this one is used to store data retrieved from
 * database when memory cache is disabled. There is one buffer for from and
 * one buffer for to track.
 */
static domain_t dom_buf[2];

/*
 * Exported functions
 */
static cmd_export_t cmds[] = {
		{"is_local", is_local, 1, fixup_var_str_1, 0,
				REQUEST_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE},
		{"lookup_domain", lookup_domain, 2, lookup_domain_fixup, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"get_did", (cmd_function)get_did, 0, 0, 0, 0},
		{"bind_domain", (cmd_function)bind_domain, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0}};


/*
 * Exported parameters
 */
static param_export_t params[] = {{"db_url", PARAM_STR, &db_url},
		{"db_mode", PARAM_INT, &db_mode},
		{"domain_table", PARAM_STR, &domain_table},
		{"domain_col", PARAM_STR, &domain_col},
		{"did_col", PARAM_STR, &did_col}, {"flags_col", PARAM_STR, &flags_col},
		{"domattr_table", PARAM_STR, &domattr_table},
		{"domattr_did", PARAM_STR, &domattr_did},
		{"domattr_name", PARAM_STR, &domattr_name},
		{"domattr_type", PARAM_STR, &domattr_type},
		{"domattr_value", PARAM_STR, &domattr_value},
		{"domattr_flags", PARAM_STR, &domattr_flags},
		{"load_domain_attrs", PARAM_INT, &load_domain_attrs}, {0, 0, 0}};


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


static int init_db(void)
{
	db_fld_t load_domains_columns[] = {{.name = did_col.s, DB_STR},
			{.name = domain_col.s, DB_STR}, {.name = flags_col.s, DB_BITMAP},
			{.name = NULL}};
	db_fld_t get_did_columns[] = {{.name = did_col.s, DB_STR}, {.name = NULL}};
	db_fld_t load_attrs_columns[] = {{.name = domattr_name.s, .type = DB_STR},
			{.name = domattr_type.s, .type = DB_INT},
			{.name = domattr_value.s, .type = DB_STR},
			{.name = domattr_flags.s, .type = DB_BITMAP}, {.name = NULL}};
	db_fld_t get_did_match[] = {{.name = domain_col.s, DB_STR}, {.name = NULL}};
	db_fld_t load_attrs_match[] = {
			{.name = domattr_did.s, .type = DB_STR}, {.name = NULL}};

	db = db_ctx("domain");
	if(db == NULL) {
		ERR("Error while initializing database layer\n");
		return -1;
	}
	if(db_add_db(db, db_url.s) < 0)
		return -1;
	if(db_connect(db) < 0)
		return -1;

	DBG("prepare load_domains_cmd\n");
	load_domains_cmd = db_cmd(
			DB_GET, db, domain_table.s, load_domains_columns, NULL, NULL);
	if(load_domains_cmd == NULL) {
		ERR("Error while preparing load_domains database command\n");
		return -1;
	}

	DBG("prepare get_did_cmd\n");
	get_did_cmd = db_cmd(
			DB_GET, db, domain_table.s, get_did_columns, get_did_match, NULL);
	if(get_did_cmd == NULL) {
		ERR("Error while preparing get_did database command\n");
		return -1;
	}

	if(load_domain_attrs) {
		DBG("prepare load_attrs_cmd\n");
		load_attrs_cmd = db_cmd(DB_GET, db, domattr_table.s, load_attrs_columns,
				load_attrs_match, NULL);
		if(load_attrs_cmd == NULL) {
			ERR("Error while preparing load_attrs database command\n");
			return -1;
		}
	}

	return 0;
}


static int allocate_tables(void)
{
	active_hash =
			(struct hash_entry ***)shm_malloc(sizeof(struct hash_entry **));
	hash_1 = (struct hash_entry **)shm_malloc(
			sizeof(struct hash_entry *) * HASH_SIZE);
	hash_2 = (struct hash_entry **)shm_malloc(
			sizeof(struct hash_entry *) * HASH_SIZE);
	domains_1 = (domain_t **)shm_malloc(sizeof(domain_t *));
	domains_2 = (domain_t **)shm_malloc(sizeof(domain_t *));

	if(!hash_1 || !hash_2 || !active_hash || !domains_1 || !domains_2) {
		SHM_MEM_ERROR;
		return -1;
	}
	memset(hash_1, 0, sizeof(struct hash_entry *) * HASH_SIZE);
	memset(hash_2, 0, sizeof(struct hash_entry *) * HASH_SIZE);
	*active_hash = hash_1;
	*domains_1 = 0;
	*domains_2 = 0;
	return 0;
}

static void destroy_tables(void)
{
	free_table(hash_1);
	free_table(hash_2);
	if(active_hash)
		shm_free(active_hash);

	if(domains_1) {
		free_domain_list(*domains_1);
		shm_free(domains_1);
	}

	if(domains_2) {
		free_domain_list(*domains_2);
		shm_free(domains_2);
	}
}


static int mod_init(void)
{
	/* Check if cache needs to be loaded from domain table */
	if(db_mode) {
		if(init_db() < 0)
			goto error;

		if(allocate_tables() < 0)
			goto error;
		if(reload_domain_list() < 0)
			goto error;

		db_cmd_free(load_domains_cmd);
		load_domains_cmd = NULL;

		db_cmd_free(load_attrs_cmd);
		load_attrs_cmd = NULL;

		db_cmd_free(get_did_cmd);
		get_did_cmd = NULL;

		if(db)
			db_disconnect(db);
		db_ctx_free(db);
		db = NULL;
	}

	return 0;

error:
	if(get_did_cmd) {
		db_cmd_free(get_did_cmd);
		get_did_cmd = NULL;
	}
	if(load_domains_cmd) {
		db_cmd_free(load_domains_cmd);
		load_domains_cmd = NULL;
	}
	if(load_attrs_cmd) {
		db_cmd_free(load_attrs_cmd);
		load_attrs_cmd = NULL;
	}
	if(db)
		db_disconnect(db);
	db_ctx_free(db);
	db = NULL;
	return -1;
}


static int child_init(int rank)
{
	/* Check if database is needed by child */
	if(rank > 0 || rank == PROC_RPC || rank == PROC_UNIXSOCK) {
		if(init_db() < 0)
			return -1;
	}

	return 0;
}


static void free_old_domain(domain_t *d)
{
	int i;

	if(!d)
		return;
	if(d->did.s) {
		pkg_free(d->did.s);
		d->did.s = NULL;
	}

	if(d->domain) {
		for(i = 0; i < d->n; i++) {
			if(d->domain[i].s)
				pkg_free(d->domain[i].s);
		}
		pkg_free(d->domain);
		d->domain = NULL;
	}

	if(d->flags) {
		pkg_free(d->flags);
		d->flags = NULL;
	}

	if(d->attrs) {
		destroy_avp_list(&d->attrs);
	}
}


static void destroy(void)
{
	/* Destroy is called from the main process only, there is no need to close
	 * database here because it is closed in mod_init already
	 */
	if(!db_mode) {
		free_old_domain(&dom_buf[0]);
		free_old_domain(&dom_buf[1]);
	}

	if(load_domains_cmd)
		db_cmd_free(load_domains_cmd);
	if(get_did_cmd)
		db_cmd_free(get_did_cmd);
	if(load_attrs_cmd)
		db_cmd_free(load_attrs_cmd);

	if(db) {
		db_disconnect(db);
		db_ctx_free(db);
	}

	destroy_tables();
}


/*
 * Check if domain is local
 */
static int is_local(struct sip_msg *msg, char *fp, char *s2)
{
	str domain;

	if(get_str_fparam(&domain, msg, (fparam_t *)fp) != 0) {
		ERR("Unable to get domain to check\n");
		return -1;
	}

	return is_domain_local(&domain);
}


static int db_load_domain(domain_t **d, unsigned long flags, str *domain)
{
	int ret;
	int_str name, val;
	domain_t *p;
	str name_s = STR_STATIC_INIT(AVP_DID);

	if(flags & AVP_TRACK_FROM) {
		p = &dom_buf[0];
	} else {
		p = &dom_buf[1];
	}

	free_old_domain(p);

	ret = db_get_did(&p->did, domain);
	if(ret != 1)
		return ret;
	if(load_domain_attrs) {
		if(db_load_domain_attrs(p) < 0)
			return -1;
	}

	/* Create an attribute containing did of the domain */
	name.s = name_s;
	val.s = p->did;
	if(add_avp_list(&p->attrs, AVP_CLASS_DOMAIN | AVP_NAME_STR | AVP_VAL_STR,
			   name, val)
			< 0)
		return -1;

	*d = p;
	return 0;
}


static int lookup_domain(struct sip_msg *msg, char *flags, char *fp)
{
	str domain, tmp;
	domain_t *d = NULL;
	int ret = -1;

	if(get_str_fparam(&domain, msg, (fparam_t *)fp) != 0) {
		DBG("lookup_domain: Cannot get the domain name to lookup\n");
		return -1;
	}

	tmp.s = pkg_malloc(domain.len);
	if(!tmp.s) {
		PKG_MEM_ERROR;
		return -1;
	}
	memcpy(tmp.s, domain.s, domain.len);
	tmp.len = domain.len;
	strlower(&tmp);

	if(db_mode) {
		if(hash_lookup(&d, *active_hash, &tmp) == 1) {
			set_avp_list((unsigned long)flags, &d->attrs);
			ret = 1;
		}
	} else {
		if(db_load_domain(&d, (unsigned long)flags, &tmp) == 0) {
			set_avp_list((unsigned long)flags, &d->attrs);
			ret = 1;
		}
	}

	pkg_free(tmp.s);
	return ret;
}


static int get_did(str *did, str *domain)
{
	str tmp;
	domain_t *d;

	if(!db_mode) {
		ERR("lookup_domain only works in cache mode\n");
		return -1;
	}

	tmp.s = pkg_malloc(domain->len);
	if(!tmp.s) {
		PKG_MEM_ERROR;
		return -1;
	}
	memcpy(tmp.s, domain->s, domain->len);
	tmp.len = domain->len;
	strlower(&tmp);

	if(hash_lookup(&d, *active_hash, &tmp) == 1) {
		*did = d->did;
		pkg_free(tmp.s);
		return 1;
	} else {
		pkg_free(tmp.s);
		return -1;
	}
}


int reload_domain_list(void)
{
	struct hash_entry **new_table;
	domain_t **new_list;

	/* Choose new hash table and free its old contents */
	if(*active_hash == hash_1) {
		free_table(hash_2);
		new_table = hash_2;
		new_list = domains_2;
	} else {
		free_table(hash_1);
		new_table = hash_1;
		new_list = domains_1;
	}

	if(load_domains(new_list) < 0)
		goto error;
	if(gen_domain_table(new_table, *new_list) < 0)
		goto error;
	*active_hash = new_table;
	return 0;

error:
	free_table(new_table);
	free_domain_list(*new_list);
	return -1;
}


static int lookup_domain_fixup(void **param, int param_no)
{
	unsigned long flags = 0;
	char *s;

	if(param_no == 1) {
		/* Determine the track and class of attributes to be loaded */
		s = (char *)*param;
		if(*s != '$' || (strlen(s) != 3)) {
			ERR("Invalid parameter value, $xy expected\n");
			return -1;
		}
		switch((s[1] << 8) + s[2]) {
			case 0x4644: /* $fd */
			case 0x6664:
			case 0x4664:
			case 0x6644:
				flags = AVP_TRACK_FROM | AVP_CLASS_DOMAIN;
				break;

			case 0x5444: /* $td */
			case 0x7464:
			case 0x5464:
			case 0x7444:
				flags = AVP_TRACK_TO | AVP_CLASS_DOMAIN;
				break;

			default:
				ERR("Invalid parameter value: '%s'\n", s);
				return -1;
		}

		pkg_free(*param);
		*param = (void *)flags;
	} else if(param_no == 2) {
		return fixup_var_str_12(param, 2);
	}

	return 0;
}
