/*
 * Hash functions for cached trusted and address tables
 *
 * Copyright (C) 2003-2012 Juha Heinanen
 *
 * 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 <sys/types.h>
#include <regex.h>
#include "parse_config.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/parser/parse_from.h"
#include "../../core/ut.h"
#include "../../core/hashes.h"
#include "../../core/usr_avp.h"
#include "../../core/ip_addr.h"
#include "../../core/pvar.h"
#include "hash.h"
#include "trusted.h"
#include "address.h"

#define perm_hash(_s) core_hash(&(_s), 0, PERM_HASH_SIZE)


/* tag AVP specs */
static avp_flags_t tag_avp_type;
static avp_name_t tag_avp;

extern int perm_peer_tag_mode;


extern int _perm_max_subnets;
extern int _perm_subnet_match_mode;

#define PERM_MAX_SUBNETS _perm_max_subnets

/*
 * Parse and set tag AVP specs
 */
int init_tag_avp(str *tag_avp_param)
{
	pv_spec_t avp_spec;
	avp_flags_t avp_flags;

	if(tag_avp_param->s && tag_avp_param->len > 0) {
		if(pv_parse_spec(tag_avp_param, &avp_spec) == 0
				|| avp_spec.type != PVT_AVP) {
			LM_ERR("malformed or non "
				   "AVP %.*s peer_tag_avp definition\n",
					tag_avp_param->len, tag_avp_param->s);
			return -1;
		}
		if(pv_get_avp_name(0, &avp_spec.pvp, &tag_avp, &avp_flags) != 0) {
			LM_ERR("[%.*s]- invalid "
				   "peer_tag_avp AVP definition\n",
					tag_avp_param->len, tag_avp_param->s);
			return -1;
		}
		tag_avp_type = avp_flags;
	} else {
		tag_avp.n = 0;
	}
	return 0;
}


/*
 * Gets tag avp specs
 */
void get_tag_avp(int_str *tag_avp_p, int *tag_avp_type_p)
{
	*tag_avp_p = tag_avp;
	*tag_avp_type_p = tag_avp_type;
}


/*
 * Create and initialize a hash table
 */
struct trusted_list **new_hash_table(void)
{
	struct trusted_list **ptr;

	/* Initializing hash tables and hash table variable */
	ptr = (struct trusted_list **)shm_malloc(
			sizeof(struct trusted_list *) * PERM_HASH_SIZE);
	if(!ptr) {
		LM_ERR("no shm memory for hash table\n");
		return 0;
	}

	memset(ptr, 0, sizeof(struct trusted_list *) * PERM_HASH_SIZE);
	return ptr;
}


/*
 * Release all memory allocated for a hash table
 */
void free_hash_table(struct trusted_list **table)
{
	if(!table)
		return;

	empty_hash_table(table);
	shm_free(table);
}


/*
 * Add <src_ip, proto, pattern, ruri_pattern, tag, priority> into hash table, where proto is integer
 * representation of string argument proto.
 */
int hash_table_insert(struct trusted_list **table, char *src_ip, char *proto,
		char *pattern, char *ruri_pattern, char *tag, int priority)
{
	struct trusted_list *np;
	struct trusted_list *np0 = NULL;
	struct trusted_list *np1 = NULL;
	unsigned int hash_val;

	np = (struct trusted_list *)shm_malloc(sizeof(*np));
	if(np == NULL) {
		LM_ERR("cannot allocate shm memory for table entry\n");
		return -1;
	}

	if(strcasecmp(proto, "any") == 0) {
		np->proto = PROTO_NONE;
	} else if(strcasecmp(proto, "udp") == 0) {
		np->proto = PROTO_UDP;
	} else if(strcasecmp(proto, "tcp") == 0) {
		np->proto = PROTO_TCP;
	} else if(strcasecmp(proto, "tls") == 0) {
		np->proto = PROTO_TLS;
	} else if(strcasecmp(proto, "sctp") == 0) {
		np->proto = PROTO_SCTP;
	} else if(strcasecmp(proto, "ws") == 0) {
		np->proto = PROTO_WS;
	} else if(strcasecmp(proto, "wss") == 0) {
		np->proto = PROTO_WSS;
	} else if(strcasecmp(proto, "none") == 0) {
		shm_free(np);
		return 1;
	} else {
		LM_CRIT("unknown protocol\n");
		shm_free(np);
		return -1;
	}

	np->src_ip.len = strlen(src_ip);
	np->src_ip.s = (char *)shm_malloc(np->src_ip.len + 1);

	if(np->src_ip.s == NULL) {
		LM_CRIT("cannot allocate shm memory for src_ip string\n");
		shm_free(np);
		return -1;
	}

	(void)strncpy(np->src_ip.s, src_ip, np->src_ip.len);
	np->src_ip.s[np->src_ip.len] = 0;

	if(pattern) {
		np->pattern = (char *)shm_malloc(strlen(pattern) + 1);
		if(np->pattern == NULL) {
			LM_CRIT("cannot allocate shm memory for pattern string\n");
			shm_free(np->src_ip.s);
			shm_free(np);
			return -1;
		}
		(void)strcpy(np->pattern, pattern);
	} else {
		np->pattern = 0;
	}

	if(ruri_pattern) {
		np->ruri_pattern = (char *)shm_malloc(strlen(ruri_pattern) + 1);
		if(np->ruri_pattern == NULL) {
			LM_CRIT("cannot allocate shm memory for ruri_pattern string\n");
			shm_free(np->src_ip.s);
			shm_free(np);
			return -1;
		}
		(void)strcpy(np->ruri_pattern, ruri_pattern);
	} else {
		np->ruri_pattern = 0;
	}

	if(tag) {
		np->tag.len = strlen(tag);
		np->tag.s = (char *)shm_malloc((np->tag.len) + 1);
		if(np->tag.s == NULL) {
			LM_CRIT("cannot allocate shm memory for pattern or ruri_pattern "
					"string\n");
			shm_free(np->src_ip.s);
			shm_free(np->pattern);
			shm_free(np->ruri_pattern);
			shm_free(np);
			return -1;
		}
		(void)strcpy(np->tag.s, tag);
	} else {
		np->tag.len = 0;
		np->tag.s = 0;
	}

	np->priority = priority;

	hash_val = perm_hash(np->src_ip);
	if(table[hash_val] == NULL) {
		np->next = NULL;
		table[hash_val] = np;
	} else {
		np1 = NULL;
		np0 = table[hash_val];
		while(np0) {
			if(np0->priority < np->priority)
				break;
			np1 = np0;
			np0 = np0->next;
		}
		if(np1 == NULL) {
			np->next = table[hash_val];
			table[hash_val] = np;
		} else {
			np->next = np1->next;
			np1->next = np;
		}
	}

	return 1;
}


/*
 * Check if an entry exists in hash table that has given src_ip and protocol
 * value and pattern that matches to From URI.  If an entry exists and tag_avp
 * has been defined, tag of the entry is added as a value to tag_avp.
 * Returns number of matches or -1 if none matched.
 */
int match_hash_table(struct trusted_list **table, struct sip_msg *msg,
		char *src_ip_c_str, int proto, char *from_uri)
{
	LM_DBG("match_hash_table src_ip: %s, proto: %d, uri: %s\n", src_ip_c_str,
			proto, from_uri);
	str ruri;
	char ruri_string[MAX_URI_SIZE];
	regex_t preg;
	struct trusted_list *np;
	str src_ip;
	int_str val;
	int count = 0;

	src_ip.s = src_ip_c_str;
	src_ip.len = strlen(src_ip.s);

	if(IS_SIP(msg)) {
		ruri = msg->first_line.u.request.uri;
		if(ruri.len >= MAX_URI_SIZE - 1) {
			LM_ERR("message has Request URI too large\n");
			return -1;
		}
		memcpy(ruri_string, ruri.s, ruri.len);
		ruri_string[ruri.len] = (char)0;
	}

	for(np = table[perm_hash(src_ip)]; np != NULL; np = np->next) {
		if((np->src_ip.len == src_ip.len)
				&& (strncmp(np->src_ip.s, src_ip.s, src_ip.len) == 0)
				&& ((np->proto == PROTO_NONE) || (proto == PROTO_NONE)
						|| (np->proto == proto))) {

			LM_DBG("match_hash_table: %d, %s, %s, %s\n", np->proto,
					(np->pattern ? np->pattern : "null"),
					(np->ruri_pattern ? np->ruri_pattern : "null"),
					(np->tag.s ? np->tag.s : "null"));

			if(IS_SIP(msg)) {
				if(np->pattern) {
					if(regcomp(&preg, np->pattern, REG_NOSUB)) {
						LM_ERR("invalid regular expression\n");
						if(!np->pattern) {
							continue;
						}
					}
					if(regexec(&preg, from_uri, 0, (regmatch_t *)0, 0)) {
						regfree(&preg);
						continue;
					}
					regfree(&preg);
				}
				if(np->ruri_pattern) {
					if(regcomp(&preg, np->ruri_pattern, REG_NOSUB)) {
						LM_ERR("invalid regular expression\n");
						continue;
					}
					if(regexec(&preg, ruri_string, 0, (regmatch_t *)0, 0)) {
						regfree(&preg);
						continue;
					}
					regfree(&preg);
				}
			}
			/* Found a match */
			if(tag_avp.n && np->tag.s) {
				val.s = np->tag;
				if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
					LM_ERR("setting of tag_avp failed\n");
					return -1;
				}
			}
			if(!perm_peer_tag_mode)
				return 1;
			count++;
		}
	}

	if(!count)
		return -1;
	else
		return count;
}


/*! \brief
 * RPC interface :: Print trusted entries stored in hash table
 */
int hash_table_rpc_print(struct trusted_list **hash_table, rpc_t *rpc, void *c)
{
	int i;
	struct trusted_list *np;
	void *th;
	void *ih;

	if(rpc->add(c, "{", &th) < 0) {
		rpc->fault(c, 500, "Internal error creating rpc");
		return -1;
	}

	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = hash_table[i];
		while(np) {
			if(rpc->struct_add(th, "d{", "table", i, "item", &ih) < 0) {
				rpc->fault(c, 500, "Internal error creating rpc ih");
				return -1;
			}

			if(rpc->struct_add(ih, "s", "ip", np->src_ip.s) < 0) {
				rpc->fault(c, 500, "Internal error creating rpc data (ip)");
				return -1;
			}
			if(rpc->struct_add(ih, "dsssd", "proto", np->proto, "pattern",
					   np->pattern ? np->pattern : "NULL", "ruri_pattern",
					   np->ruri_pattern ? np->ruri_pattern : "NULL", "tag",
					   np->tag.len ? np->tag.s : "NULL", "priority",
					   np->priority)
					< 0) {
				rpc->fault(c, 500, "Internal error creating rpc data");
				return -1;
			}
			np = np->next;
		}
	}
	return 0;
}

/*
 * Free contents of hash table, it doesn't destroy the
 * hash table itself
 */
void empty_hash_table(struct trusted_list **table)
{
	int i;
	struct trusted_list *np, *next;

	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = table[i];
		while(np) {
			if(np->src_ip.s)
				shm_free(np->src_ip.s);
			if(np->pattern)
				shm_free(np->pattern);
			if(np->ruri_pattern)
				shm_free(np->ruri_pattern);
			if(np->tag.s)
				shm_free(np->tag.s);
			next = np->next;
			shm_free(np);
			np = next;
		}
		table[i] = 0;
	}
}


/*
 * Create and initialize an address hash table
 */
struct addr_list **new_addr_hash_table(void)
{
	struct addr_list **ptr;

	/* Initializing hash tables and hash table variable */
	ptr = (struct addr_list **)shm_malloc(
			sizeof(struct addr_list *) * PERM_HASH_SIZE);
	if(!ptr) {
		LM_ERR("no shm memory for hash table\n");
		return 0;
	}

	memset(ptr, 0, sizeof(struct addr_list *) * PERM_HASH_SIZE);
	return ptr;
}


/*
 * Release all memory allocated for a hash table
 */
void free_addr_hash_table(struct addr_list **table)
{
	if(!table)
		return;

	empty_addr_hash_table(table);
	shm_free(table);
}


/*
 * Add <grp, ip_addr, port> into hash table
 */
int addr_hash_table_insert(struct addr_list **table, unsigned int grp,
		ip_addr_t *addr, unsigned int port, str *tagv)
{
	struct addr_list *np;
	unsigned int hash_val;
	str addr_str;
	int len;

	len = sizeof(struct addr_list);
	if(tagv != NULL && tagv->s != NULL) {
		len += tagv->len + 1;
	}

	np = (struct addr_list *)shm_malloc(len);
	if(np == NULL) {
		LM_ERR("no shm memory for table entry\n");
		return -1;
	}

	memset(np, 0, len);

	np->grp = grp;
	memcpy(&np->addr, addr, sizeof(ip_addr_t));
	np->port = port;
	if(tagv != NULL && tagv->s != NULL) {
		np->tag.s = (char *)np + sizeof(struct addr_list);
		np->tag.len = tagv->len;
		memcpy(np->tag.s, tagv->s, tagv->len);
		np->tag.s[np->tag.len] = '\0';
	}

	addr_str.s = (char *)addr->u.addr;
	addr_str.len = 4;
	hash_val = perm_hash(addr_str);
	np->next = table[hash_val];
	table[hash_val] = np;

	return 1;
}


/*
 * Check if an entry exists in hash table that has given group, ip_addr, and
 * port.  Port 0 in hash table matches any port.
 */
int match_addr_hash_table(struct addr_list **table, unsigned int group,
		ip_addr_t *addr, unsigned int port)
{
	struct addr_list *np;
	str addr_str;
	avp_value_t val;

	addr_str.s = (char *)addr->u.addr;
	addr_str.len = 4;

	for(np = table[perm_hash(addr_str)]; np != NULL; np = np->next) {
		if((np->grp == group) && ((np->port == 0) || (np->port == port))
				&& ip_addr_cmp(&np->addr, addr)) {

			if(tag_avp.n && np->tag.s) {
				val.s = np->tag;
				if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
					LM_ERR("setting of tag_avp failed\n");
					return -1;
				}
			}

			return 1;
		}
	}

	return -1;
}


/*
 * Check if an ip_addr/port entry exists in hash table in any group.
 * Returns first group in which ip_addr/port is found.
 * Port 0 in hash table matches any port.
 */
int find_group_in_addr_hash_table(
		struct addr_list **table, ip_addr_t *addr, unsigned int port)
{
	struct addr_list *np;
	str addr_str;
	avp_value_t val;

	addr_str.s = (char *)addr->u.addr;
	addr_str.len = 4;

	for(np = table[perm_hash(addr_str)]; np != NULL; np = np->next) {
		if(((np->port == 0) || (np->port == port))
				&& ip_addr_cmp(&np->addr, addr)) {

			if(tag_avp.n && np->tag.s) {
				val.s = np->tag;
				if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
					LM_ERR("setting of tag_avp failed\n");
					return -1;
				}
			}

			return np->grp;
		}
	}

	return -1;
}


/*! \brief
 * RPC: Print addresses stored in hash table
 */
int addr_hash_table_rpc_print(struct addr_list **table, rpc_t *rpc, void *c)
{
	int i;
	void *th;
	void *ih;
	struct addr_list *np;


	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = table[i];
		while(np) {
			if(rpc->add(c, "{", &th) < 0) {
				rpc->fault(c, 500, "Internal error creating rpc");
				return -1;
			}

			if(rpc->struct_add(
					   th, "dd{", "table", i, "group", np->grp, "item", &ih)
					< 0) {
				rpc->fault(c, 500, "Internal error creating rpc ih");
				return -1;
			}

			if(rpc->struct_add(ih, "s", "ip", ip_addr2a(&np->addr)) < 0) {
				rpc->fault(c, 500, "Internal error creating rpc data (ip)");
				return -1;
			}
			if(rpc->struct_add(ih, "ds", "port", np->port, "tag",
					   np->tag.len ? np->tag.s : "NULL")
					< 0) {
				rpc->fault(c, 500, "Internal error creating rpc data");
				return -1;
			}
			np = np->next;
		}
	}
	return 0;
}


/*
 * Free contents of hash table, it doesn't destroy the
 * hash table itself
 */
void empty_addr_hash_table(struct addr_list **table)
{
	int i;
	struct addr_list *np, *next;

	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = table[i];
		while(np) {
			next = np->next;
			shm_free(np);
			np = next;
		}
		table[i] = 0;
	}
}


/*
 * Create and initialize a subnet table
 */
struct subnet *new_subnet_table(void)
{
	struct subnet *ptr;

	/* subnet record [PERM_MAX_SUBNETS] contains in its grp field
	 * the number of subnet records in the subnet table */
	ptr = (struct subnet *)shm_malloc(
			sizeof(struct subnet) * (PERM_MAX_SUBNETS + 1));
	if(!ptr) {
		LM_ERR("no shm memory for subnet table\n");
		return 0;
	}
	memset(ptr, 0, sizeof(struct subnet) * (PERM_MAX_SUBNETS + 1));
	return ptr;
}


/*
 * Add <grp, subnet, mask, port, tag> into subnet table so that table is
 * kept in increasing ordered according to grp.
 */
int subnet_table_insert(struct subnet *table, unsigned int grp,
		ip_addr_t *subnet, unsigned int mask, unsigned int port, str *tagv)
{
	int i;
	unsigned int count;
	str tags;

	count = table[PERM_MAX_SUBNETS].grp;

	if(count == PERM_MAX_SUBNETS) {
		LM_CRIT("subnet table is full\n");
		return 0;
	}

	if(tagv == NULL || tagv->s == NULL) {
		tags.s = NULL;
		tags.len = 0;
	} else {
		tags.len = tagv->len;
		tags.s = (char *)shm_malloc(tags.len + 1);
		if(tags.s == NULL) {
			LM_ERR("No more shared memory\n");
			return 0;
		}
		memcpy(tags.s, tagv->s, tags.len);
		tags.s[tags.len] = '\0';
	}

	i = count - 1;

	while((i >= 0) && (table[i].grp > grp)) {
		table[i + 1] = table[i];
		i--;
	}

	table[i + 1].grp = grp;
	memcpy(&table[i + 1].subnet, subnet, sizeof(ip_addr_t));
	table[i + 1].port = port;
	table[i + 1].mask = mask;
	table[i + 1].tag = tags;

	table[PERM_MAX_SUBNETS].grp = count + 1;

	return 1;
}


/*
 * Check if an entry exists in subnet table that matches given group, ip_addr,
 * and port.  Port 0 in subnet table matches any port.
 */
int match_subnet_table(struct subnet *table, unsigned int grp, ip_addr_t *addr,
		unsigned int port)
{
	unsigned int count, i;
	avp_value_t val;
	int best_idx = -1;
	unsigned int best_mask = 0;

	count = table[PERM_MAX_SUBNETS].grp;

	i = 0;
	while((i < count) && (table[i].grp < grp))
		i++;

	if(i == count)
		return -1;

	while((i < count) && (table[i].grp == grp)) {
		if(((table[i].port == port) || (table[i].port == 0))
				&& (ip_addr_match_net(addr, &table[i].subnet, table[i].mask)
						== 0)) {
			if(table[i].mask > best_mask) {
				best_mask = table[i].mask;
				best_idx = i;
			}
			if(_perm_subnet_match_mode == 0) {
				/* use the first match */
				break;
			}
		}
		i++;
	}

	if(best_idx >= 0) {
		if(tag_avp.n && table[best_idx].tag.s) {
			val.s = table[best_idx].tag;
			if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
				LM_ERR("setting of tag_avp failed\n");
				return -1;
			}
		}
		return 1;
	}

	return -1;
}


/*
 * Check if an entry exists in subnet table that matches given ip_addr,
 * and port.  Port 0 in subnet table matches any port.  Return group of
 * first match or -1 if no match is found.
 */
int find_group_in_subnet_table(
		struct subnet *table, ip_addr_t *addr, unsigned int port)
{
	unsigned int count, i;
	avp_value_t val;
	int best_idx = -1;
	unsigned int best_mask = 0;

	count = table[PERM_MAX_SUBNETS].grp;

	i = 0;
	while(i < count) {
		if(((table[i].port == port) || (table[i].port == 0))
				&& (ip_addr_match_net(addr, &table[i].subnet, table[i].mask)
						== 0)) {
			if(table[i].mask > best_mask) {
				best_mask = table[i].mask;
				best_idx = i;
			}
			if(_perm_subnet_match_mode == 0) {
				/* use the first match */
				break;
			}
		}
		i++;
	}

	if(best_idx >= 0) {
		if(tag_avp.n && table[best_idx].tag.s) {
			val.s = table[best_idx].tag;
			if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
				LM_ERR("setting of tag_avp failed\n");
				return -1;
			}
		}
		return table[best_idx].grp;
	}

	return -1;
}


/*! \brief
 * RPC interface :: Print subnet entries stored in hash table
 */
int subnet_table_rpc_print(struct subnet *table, rpc_t *rpc, void *c)
{
	int i;
	int count;
	void *th;
	void *ih;

	count = table[PERM_MAX_SUBNETS].grp;

	for(i = 0; i < count; i++) {
		if(rpc->add(c, "{", &th) < 0) {
			rpc->fault(c, 500, "Internal error creating rpc");
			return -1;
		}

		if(rpc->struct_add(
				   th, "dd{", "id", i, "group", table[i].grp, "item", &ih)
				< 0) {
			rpc->fault(c, 500, "Internal error creating rpc ih");
			return -1;
		}

		if(rpc->struct_add(ih, "s", "ip", ip_addr2a(&table[i].subnet)) < 0) {
			rpc->fault(c, 500, "Internal error creating rpc data (subnet)");
			return -1;
		}
		if(rpc->struct_add(ih, "dds", "mask", table[i].mask, "port",
				   table[i].port, "tag",
				   (table[i].tag.s == NULL) ? "" : table[i].tag.s)
				< 0) {
			rpc->fault(c, 500, "Internal error creating rpc data");
			return -1;
		}
	}
	return 0;
}


/*
 * Empty contents of subnet table
 */
void empty_subnet_table(struct subnet *table)
{
	int i;
	table[PERM_MAX_SUBNETS].grp = 0;
	for(i = 0; i < PERM_MAX_SUBNETS; i++) {
		if(table[i].tag.s != NULL) {
			shm_free(table[i].tag.s);
			table[i].tag.s = NULL;
			table[i].tag.len = 0;
		}
	}
}


/*
 * Release memory allocated for a subnet table
 */
void free_subnet_table(struct subnet *table)
{
	int i;
	if(!table)
		return;
	for(i = 0; i < PERM_MAX_SUBNETS; i++) {
		if(table[i].tag.s != NULL) {
			shm_free(table[i].tag.s);
			table[i].tag.s = NULL;
			table[i].tag.len = 0;
		}
	}

	shm_free(table);
}

/*
 * Create and initialize a domain_name table
 */
struct domain_name_list **new_domain_name_table(void)
{
	struct domain_name_list **ptr;

	/* Initializing hash tables and hash table variable */
	ptr = (struct domain_name_list **)shm_malloc(
			sizeof(struct domain_name_list *) * PERM_HASH_SIZE);
	if(!ptr) {
		LM_ERR("no shm memory for hash table\n");
		return 0;
	}

	memset(ptr, 0, sizeof(struct domain_name *) * PERM_HASH_SIZE);
	return ptr;
}


/*
 * Free contents of hash table, it doesn't destroy the
 * hash table itself
 */
void empty_domain_name_table(struct domain_name_list **table)
{
	int i;
	struct domain_name_list *np, *next;

	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = table[i];
		while(np) {
			next = np->next;
			shm_free(np);
			np = next;
		}
		table[i] = 0;
	}
}

/*
 * Release all memory allocated for a hash table
 */
void free_domain_name_table(struct domain_name_list **table)
{
	if(!table)
		return;

	empty_domain_name_table(table);
	shm_free(table);
}


/*
 * Check if an entry exists in hash table that has given group, domain_name, and
 * port.  Port 0 in hash table matches any port.
 */
int match_domain_name_table(struct domain_name_list **table, unsigned int group,
		str *domain_name, unsigned int port)
{
	struct domain_name_list *np;
	avp_value_t val;

	for(np = table[perm_hash(*domain_name)]; np != NULL; np = np->next) {
		if((np->grp == group) && ((np->port == 0) || (np->port == port))
				&& np->domain.len == domain_name->len
				&& strncmp(np->domain.s, domain_name->s, domain_name->len)
						   == 0) {

			if(tag_avp.n && np->tag.s) {
				val.s = np->tag;
				if(add_avp(tag_avp_type | AVP_VAL_STR, tag_avp, val) != 0) {
					LM_ERR("setting of tag_avp failed\n");
					return -1;
				}
			}

			return 1;
		}
	}

	return -1;
}


/*
 * Check if a domain_name/port entry exists in hash table in any group.
 * Returns first group in which ip_addr/port is found.
 * Port 0 in hash table matches any port.
 */
int find_group_in_domain_name_table(
		struct domain_name_list **table, str *domain_name, unsigned int port)
{
	struct domain_name_list *np;

	for(np = table[perm_hash(*domain_name)]; np != NULL; np = np->next) {
		if(((np->port == 0) || (np->port == port))
				&& np->domain.len == domain_name->len
				&& strncmp(np->domain.s, domain_name->s, domain_name->len)
						   == 0) {
			return np->grp;
		}
	}

	return -1;
}


/*
 * Add <grp, domain_name, port> into hash table
 */
int domain_name_table_insert(struct domain_name_list **table, unsigned int grp,
		str *domain_name, unsigned int port, str *tagv)
{
	struct domain_name_list *np;
	unsigned int hash_val;
	int len;

	len = sizeof(struct domain_name_list) + domain_name->len;
	if(tagv != NULL && tagv->s != NULL) {
		len += tagv->len + 1;
	}

	np = (struct domain_name_list *)shm_malloc(len);
	if(np == NULL) {
		LM_ERR("no shm memory for table entry\n");
		return -1;
	}

	memset(np, 0, len);

	np->grp = grp;
	np->domain.s = (char *)np + sizeof(struct domain_name_list);
	memcpy(np->domain.s, domain_name->s, domain_name->len);
	np->domain.len = domain_name->len;
	np->port = port;
	if(tagv != NULL && tagv->s != NULL) {
		np->tag.s =
				(char *)np + sizeof(struct domain_name_list) + domain_name->len;
		np->tag.len = tagv->len;
		memcpy(np->tag.s, tagv->s, np->tag.len);
		np->tag.s[np->tag.len] = '\0';
	}

	LM_DBG("** Added domain name: %.*s\n", np->domain.len, np->domain.s);

	hash_val = perm_hash(*domain_name);
	np->next = table[hash_val];
	table[hash_val] = np;

	return 1;
}


/*! \brief
 * RPC: Print addresses stored in hash table
 */
int domain_name_table_rpc_print(
		struct domain_name_list **table, rpc_t *rpc, void *c)
{
	int i;
	void *th;
	void *ih;
	struct domain_name_list *np;


	if(rpc->add(c, "{", &th) < 0) {
		rpc->fault(c, 500, "Internal error creating rpc");
		return -1;
	}

	for(i = 0; i < PERM_HASH_SIZE; i++) {
		np = table[i];
		while(np) {
			if(rpc->struct_add(
					   th, "dd{", "table", i, "group", np->grp, "item", &ih)
					< 0) {
				rpc->fault(c, 500, "Internal error creating rpc ih");
				return -1;
			}

			if(rpc->struct_add(ih, "S", "domain_name", &np->domain) < 0) {
				rpc->fault(c, 500, "Internal error creating rpc data (ip)");
				return -1;
			}
			if(rpc->struct_add(ih, "ds", "port", np->port, "tag",
					   np->tag.len ? np->tag.s : "NULL")
					< 0) {
				rpc->fault(c, 500, "Internal error creating rpc data");
				return -1;
			}
			np = np->next;
		}
	}
	return 0;
}
