/*
 * Domain table related functions
 *
 * 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
 */

#include <string.h>
#include "uid_domain_mod.h"
#include "hash.h"
#include "../../core/dprint.h"
#include "../../core/mem/shm_mem.h"
#include "../../lib/srdb2/db.h"
#include "../../core/ut.h"


/*
 * Search the list of domains for domain with given did
 */
static domain_t* domain_search(domain_t* list, str* did)
{
	while(list) {
		if (list->did.len == did->len &&
			!memcmp(list->did.s, did->s, did->len)) {
			return list;
		}
		list = list->next;
	}
	return 0;
}


/*
 * Add a new domain name to did
 */
static int domain_add(domain_t* d, str* domain, unsigned int flags)
{
	str* p1;
	unsigned int* p2;
	str dom;

	if (!d || !domain) {
		ERR("Invalid parameter value\n");
		return -1;
	}

	dom.s = shm_malloc(domain->len);
	if (!dom.s) goto error;
	memcpy(dom.s, domain->s, domain->len);
	dom.len = domain->len;
	strlower(&dom);

	p1 = (str*)shm_realloc(d->domain, sizeof(str) * (d->n + 1));
	if (!p1) goto error;
	p2 = (unsigned int*)shm_realloc(d->flags,
									sizeof(unsigned int) * (d->n + 1));
	if (!p2) goto error;

	d->domain = p1;
	d->domain[d->n] = dom;
	d->flags = p2;
	d->flags[d->n] = flags;
	d->n++;
	return 0;

 error:
	ERR("Unable to add new domain name (out of memory)\n");
	if (dom.s) shm_free(dom.s);
	return -1;
}


/*
 * Release all memory allocated for given domain structure
 */
static void free_domain(domain_t* d)
{
	int i;
	if (!d) return;
	if (d->did.s) shm_free(d->did.s);

	for(i = 0; i < d->n; i++) {
		if (d->domain[i].s) shm_free(d->domain[i].s);
	}
	shm_free(d->domain);
	shm_free(d->flags);
	if (d->attrs) destroy_avp_list(&d->attrs);
	shm_free(d);
}




/*
 * Create a new domain structure which will initialy have
 * one domain name
 */
static domain_t* new_domain(str* did, str* domain, unsigned int flags)
{
	domain_t* d;
	int_str name, val;
	str name_s = STR_STATIC_INIT(AVP_DID);

	d = (domain_t*)shm_malloc(sizeof(domain_t));
	if (!d) goto error;
	memset(d, 0, sizeof(domain_t));
	d->did.s = shm_malloc(did->len);
	if (!d->did.s) goto error;
	memcpy(d->did.s, did->s, did->len);
	d->did.len = did->len;

	d->domain = (str*)shm_malloc(sizeof(str));
	if (!d->domain) goto error;
	d->domain[0].s = shm_malloc(domain->len);
	if (!d->domain[0].s) goto error;
	memcpy(d->domain[0].s, domain->s, domain->len);
	d->domain[0].len = domain->len;
	strlower(d->domain);

	d->flags = (unsigned int*)shm_malloc(sizeof(unsigned int));
	if (!d->flags) goto error;
	d->flags[0] = flags;
	d->n = 1;

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

	return d;

 error:
	ERR("Unable to create new domain structure\n");
	free_domain(d);
	return 0;
}


/*
 * Release all memory allocated for entire domain list
 */
void free_domain_list(domain_t* list)
{
	domain_t* ptr;
	if (!list) return;

	while(list) {
		ptr = list;
		list = list->next;
		free_domain(ptr);
	}
}


/*
 * Load attributes from domain_attrs table
 */
int db_load_domain_attrs(domain_t* d)
{
	int_str name, v;
	str avp_val;
	db_res_t* res;
	db_rec_t* rec;
	unsigned short flags;

	load_attrs_cmd->match[0].v.lstr = d->did;

	if (db_exec(&res, load_attrs_cmd) < 0) {
		ERR("Error while querying database\n");
		return -1;
	}

	rec = db_first(res);
	while(rec) {
		if (rec->fld[0].flags & DB_NULL ||
			rec->fld[1].flags & DB_NULL ||
			rec->fld[3].flags & DB_NULL) {
			ERR("Skipping row containing NULL entries\n");
			goto skip;
		}

		if ((rec->fld[3].v.int4 & SRDB_LOAD_SER) == 0) goto skip;

		/* Get AVP name */
		name.s = rec->fld[0].v.lstr;

		/* Test for NULL value */
		if (rec->fld[2].flags & DB_NULL) {
			avp_val.s = 0;
			avp_val.len = 0;
		} else {
			avp_val = rec->fld[2].v.lstr;
		}

		flags = AVP_CLASS_DOMAIN | AVP_NAME_STR;
		if (rec->fld[1].v.int4 == AVP_VAL_STR) {
			/* String AVP */
			v.s = avp_val;
			flags |= AVP_VAL_STR;
		} else {
			/* Integer AVP */
			str2int(&avp_val, (unsigned*)&v.n);
		}

		if (add_avp_list(&d->attrs, flags, name, v) < 0) {
			ERR("Error while adding domain attribute %.*s to domain %.*s, "
				"skipping\n", name.s.len, ZSW(name.s.s),
				d->did.len, ZSW(d->did.s));
		}

	skip:
		rec = db_next(res);
	}
	db_res_free(res);
	return 0;
}


/*
 * Create domain list from domain table
 */
int load_domains(domain_t** dest)
{
	db_res_t* res = NULL;
	db_rec_t* rec;
	unsigned int flags;
	domain_t* d, *list;

	list = 0;

	if ((db_exec(&res, load_domains_cmd) < 0) || (res==NULL)) {
		ERR("Error while querying database\n");
		return -1;
	}

	rec = db_first(res);

	while(rec) {
		/* Do not assume that the database server performs any constrain
		 * checking (dbtext does not) and perform sanity checks here to
		 * make sure that we only load good entried
		 */
		if (rec->fld[0].flags & DB_NULL ||
			rec->fld[1].flags & DB_NULL ||
			rec->fld[2].flags & DB_NULL) {
			ERR("Row with NULL column(s), skipping\n");
			goto skip;
		}

		flags = rec->fld[2].v.int4;

		/* Skip entries that are disabled/scheduled for removal */
		if (flags & SRDB_DISABLED) goto skip;
		/* Skip entries that are for serweb/ser-ctl only */
		if (!(flags & SRDB_LOAD_SER)) goto skip;

		DBG("Processing entry (%.*s, %.*s, %u)\n",
			rec->fld[0].v.lstr.len, ZSW(rec->fld[0].v.lstr.s),
			rec->fld[1].v.lstr.len, ZSW(rec->fld[1].v.lstr.s),
			flags);

		d = domain_search(list, &rec->fld[0].v.lstr);
		if (d) {
			/* DID exists in the list, update it */
			if (domain_add(d, &rec->fld[1].v.lstr, flags) < 0) goto error;
		} else {
			/* DID does not exist yet, create a new entry */
			d = new_domain(&rec->fld[0].v.lstr, &rec->fld[1].v.lstr, flags);
			if (!d) goto error;
			d->next = list;
			list = d;
		}

	skip:
		rec = db_next(res);
	}

	db_res_free(res);
	res = NULL;

	if (load_domain_attrs) {
		d = list;
		while(d) {
			if (db_load_domain_attrs(d) < 0) goto error;
			d = d->next;
		}
	}

	*dest = list;
	return 0;

 error:
	if (res) db_res_free(res);
	free_domain_list(list);
	return 1;
}


/* Retrieve did directly from database, without using memory cache. Use 0 as
 * the value of first parameter if you only want to know whether the entry is
 * in the database. The function returns 1 if there is such entry, 0 if not,
 * and -1 on error.  The result is allocated using pkg_malloc and must be
 * freed.
 */
int db_get_did(str* did, str* domain)
{
	db_res_t* res = NULL;
	db_rec_t* rec;

	if (!domain) {
		ERR("BUG:Invalid parameter value\n");
		goto err;
	}

	get_did_cmd->match[0].v.lstr = *domain;

	if (db_exec(&res, get_did_cmd) < 0) {
		ERR("Error in database query\n");
		goto err;
	}

	rec = db_first(res);
	if (rec) {
		/* Test flags first, we are only interested in rows
		 * that are not disabled
		 */
		if (rec->fld[1].flags & DB_NULL || (rec->fld[1].v.bitmap &
											SRDB_DISABLED)) {
			db_res_free(res);
			return 0;
		}

		if (did) {
			if (rec->fld[0].flags & DB_NULL) {
				did->len = 0;
				did->s = 0;
				WARN("Domain '%.*s' has NULL did\n",
					 domain->len, ZSW(domain->s));
			} else {
				did->s = pkg_malloc(rec->fld[0].v.lstr.len);
				if (!did->s) {
					ERR("No memory left\n");
					goto err;
				}
				memcpy(did->s, rec->fld[0].v.lstr.s, rec->fld[0].v.lstr.len);
				did->len = rec->fld[0].v.lstr.len;
			}
		}

		db_res_free(res);
		return 1;
	} else {
		db_res_free(res);
		return 0;
	}

 err:
	if (res) db_res_free(res);
	return -1;
}


/* Check if the domain name given in the parameter is one
 * of the locally configured domain names.
 * Returns 1 if yes and -1 otherwise
 */
int is_domain_local(str* domain)
{
	str tmp;

	/* Make a temporary copy, domain name comparisons are always
	 * case insensitive
	 */
	tmp.s = pkg_malloc(domain->len);
	if (!tmp.s) {
		ERR("No memory left\n");
		return -1;
	}
	memcpy(tmp.s, domain->s, domain->len);
	tmp.len = domain->len;
	strlower(&tmp);

	if (!db_mode) {
		switch(db_get_did(0, &tmp)) {
		case 1:  goto found;
		default: goto not_found;
		}
	} else {
		if (hash_lookup(0, *active_hash, &tmp) == 1) goto found;
		else goto not_found;
	}

 found:
	pkg_free(tmp.s);
	return 1;
 not_found:
	pkg_free(tmp.s);
	return -1;
}
