/**
 * Copyright (C) 2010 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of kamailio, a free SIP server.
 *
 * 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
 */

/*!
 * \file
 * \brief Kamailio uac :: The SIP UA registration client module
 * \ingroup uac
 * Module: \ref uac
 */

#include <time.h>

#include "../../core/dprint.h"
#include "../../core/timer.h"

#include "../../core/mem/shm_mem.h"
#include "../../lib/srdb1/db.h"
#include "../../core/ut.h"
#include "../../core/trim.h"
#include "../../core/hashes.h"
#include "../../core/locking.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_to.h"
#include "../../core/parser/parse_expires.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"
#include "../../core/rand/kam_rand.h"

#include "../../modules/tm/tm_load.h"

#include "auth.h"
#include "auth_hdr.h"
#include "uac_reg.h"

#define UAC_REG_DISABLED	(1<<0) /* registration disabled */
#define UAC_REG_ONGOING		(1<<1) /* registration on progress */
#define UAC_REG_ONLINE		(1<<2) /* registered */
#define UAC_REG_AUTHSENT	(1<<3) /* registration with auth in progress */
#define UAC_REG_INIT		(1<<4) /* registration initialized */

#define MAX_UACH_SIZE 2048
#define UAC_REG_TM_CALLID_SIZE 90
#define UAC_REG_DB_COLS_NUM 15

int _uac_reg_gc_interval = 150;

typedef struct _reg_uac
{
	unsigned int h_uuid;
	unsigned int h_user;
	str   l_uuid;
	str   l_username;
	str   l_domain;
	str   r_username;
	str   r_domain;
	str   realm;
	str   auth_proxy;
	str   auth_username;
	str   auth_password;
	str   auth_ha1;
	str   callid;
	str   contact_addr;
	str   socket;
	unsigned int cseq;
	unsigned int flags;
	unsigned int expires;
	time_t timer_expires;
	unsigned int reg_delay;
	time_t reg_init;
	gen_lock_t *lock;
} reg_uac_t;

typedef struct _reg_item
{
	reg_uac_t *r;
	struct _reg_item *next;
} reg_item_t;


typedef struct _reg_entry
{
	unsigned int isize;
	unsigned int usize;
	reg_item_t *byuser;
	reg_item_t *byuuid;
	gen_lock_t lock;
} reg_entry_t;

typedef struct _reg_ht
{
	unsigned int htsize;
	time_t stime;
	reg_entry_t *entries;
} reg_ht_t;

static reg_ht_t *_reg_htable = NULL;
static reg_ht_t *_reg_htable_gc = NULL;
static gen_lock_t *_reg_htable_gc_lock = NULL;

int _uac_reg_use_domain = 0;
int reg_timer_interval = 90;
int reg_retry_interval = 0;
int reg_htable_size = 4;
int reg_fetch_rows = 1000;
int reg_keep_callid = 0;
int reg_random_delay = 0;
int *reg_active = NULL;
str reg_contact_addr = STR_NULL;
str reg_db_url = STR_NULL;
str reg_db_table = str_init("uacreg");

str l_uuid_column = str_init("l_uuid");
str l_username_column = str_init("l_username");
str l_domain_column = str_init("l_domain");
str r_username_column = str_init("r_username");
str r_domain_column = str_init("r_domain");
str realm_column = str_init("realm");
str auth_username_column = str_init("auth_username");
str auth_password_column = str_init("auth_password");
str auth_ha1_column = str_init("auth_ha1");
str auth_proxy_column = str_init("auth_proxy");
str expires_column = str_init("expires");
str flags_column = str_init("flags");
str reg_delay_column = str_init("reg_delay");
str socket_column = str_init("socket");
str contact_addr_column = str_init("contact_addr");

str str_empty = str_init("");


extern struct tm_binds uac_tmb;
extern pv_spec_t auth_username_spec;
extern pv_spec_t auth_realm_spec;
extern pv_spec_t auth_password_spec;

counter_handle_t regtotal;         /* Total number of registrations in memory */
counter_handle_t regactive;        /* Active registrations - 200 OK */
counter_handle_t regdisabled;      /* Disabled registrations */

/* Init reg active mode */
int reg_active_init(int mode)
{
	if(reg_active!=NULL) {
		/* already allocated */
		*reg_active = mode;
		return 0;
	}
	reg_active = (int*)shm_malloc(sizeof(int));
	if(reg_active==NULL) {
		SHM_MEM_ERROR;
		return -1;
	}
	*reg_active = mode;
	return 0;
}

/* Init counters */
static void uac_reg_counter_init()
{
	LM_DBG("*** Initializing UAC reg counters\n");
	counter_register(&regtotal, "uac", "regtotal", 0, 0, 0, "Total number of registration accounts in memory", 0);
	counter_register(&regactive, "uac", "regactive", 0, 0, 0, "Number of successfully registered accounts (200 OK)", 0);
	counter_register(&regdisabled, "uac", "regdisabled", 0, 0, 0, "Counter of failed registrations (not 200 OK)", 0);
}


/**
 * Init the in-memory registration database in hash table
 */
int uac_reg_init_ht(unsigned int sz)
{
	int i;

	_reg_htable_gc_lock = (gen_lock_t*)shm_malloc(sizeof(gen_lock_t));
	if(_reg_htable_gc_lock == NULL)
	{
		SHM_MEM_ERROR;
		return -1;
	}
	if(lock_init(_reg_htable_gc_lock)==0)
	{
		LM_ERR("cannot init global lock\n");
		shm_free((void*)_reg_htable_gc_lock);
		return -1;
	}
	_reg_htable_gc = (reg_ht_t*)shm_malloc(sizeof(reg_ht_t));
	if(_reg_htable_gc==NULL)
	{
		SHM_MEM_ERROR;
		lock_destroy(_reg_htable_gc_lock);
		shm_free((void*)_reg_htable_gc_lock);
		return -1;
	}
	memset(_reg_htable_gc, 0, sizeof(reg_ht_t));
	_reg_htable_gc->htsize = sz;

	_reg_htable_gc->entries =
		(reg_entry_t*)shm_malloc(_reg_htable_gc->htsize*sizeof(reg_entry_t));
	if(_reg_htable_gc->entries==NULL)
	{
		SHM_MEM_ERROR;
		shm_free(_reg_htable_gc);
		lock_destroy(_reg_htable_gc_lock);
		shm_free((void*)_reg_htable_gc_lock);
		return -1;
	}
	memset(_reg_htable_gc->entries, 0, _reg_htable_gc->htsize*sizeof(reg_entry_t));


	_reg_htable = (reg_ht_t*)shm_malloc(sizeof(reg_ht_t));
	if(_reg_htable==NULL)
	{
		SHM_MEM_ERROR;
		shm_free(_reg_htable_gc->entries);
		shm_free(_reg_htable_gc);
		lock_destroy(_reg_htable_gc_lock);
		shm_free((void*)_reg_htable_gc_lock);
		return -1;
	}
	memset(_reg_htable, 0, sizeof(reg_ht_t));
	_reg_htable->htsize = sz;

	_reg_htable->entries =
		(reg_entry_t*)shm_malloc(_reg_htable->htsize*sizeof(reg_entry_t));
	if(_reg_htable->entries==NULL)
	{
		SHM_MEM_ERROR;
		shm_free(_reg_htable_gc->entries);
		shm_free(_reg_htable_gc);
		shm_free(_reg_htable);
		lock_destroy(_reg_htable_gc_lock);
		shm_free((void*)_reg_htable_gc_lock);
		return -1;
	}
	memset(_reg_htable->entries, 0, _reg_htable->htsize*sizeof(reg_entry_t));
	for(i=0; i<_reg_htable->htsize; i++)
	{
		if(lock_init(&_reg_htable->entries[i].lock)==0)
		{
			LM_ERR("cannot initialize lock[%d] n", i);
			i--;
			while(i>=0)
			{
				lock_destroy(&_reg_htable->entries[i].lock);
				i--;
			}
			shm_free(_reg_htable->entries);
			shm_free(_reg_htable);
			shm_free(_reg_htable_gc->entries);
			shm_free(_reg_htable_gc);
			lock_destroy(_reg_htable_gc_lock);
			shm_free((void*)_reg_htable_gc_lock);
			return -1;
		}
	}

	/* Initialize uac reg counters */
	uac_reg_counter_init();

	return 0;
}

/**
 *
 */
int uac_reg_free_ht(void)
{
	int i;
	reg_item_t *it = NULL;
	reg_item_t *it0 = NULL;

	if(_reg_htable_gc_lock != NULL)
	{
		lock_destroy(_reg_htable_gc_lock);
		shm_free((void*)_reg_htable_gc_lock);
		_reg_htable_gc_lock = NULL;
	}
	if(_reg_htable_gc!=NULL)
	{
		for(i=0; i<_reg_htable_gc->htsize; i++)
		{
			it = _reg_htable_gc->entries[i].byuuid;
			while(it)
			{
				it0 = it;
				it = it->next;
				shm_free(it0);
			}
			it = _reg_htable_gc->entries[i].byuser;
			while(it)
			{
				it0 = it;
				it = it->next;
				shm_free(it0->r);
				shm_free(it0);
			}
		}
		shm_free(_reg_htable_gc->entries);
		shm_free(_reg_htable_gc);
		_reg_htable_gc = NULL;
	}

	if(_reg_htable==NULL)
	{
		LM_DBG("no hash table\n");
		return -1;
	}
	for(i=0; i<_reg_htable->htsize; i++)
	{
		lock_get(&_reg_htable->entries[i].lock);
		/* free entries */
		it = _reg_htable->entries[i].byuuid;
		while(it)
		{
			it0 = it;
			it = it->next;
			shm_free(it0);
		}
		it = _reg_htable->entries[i].byuser;
		while(it)
		{
			it0 = it;
			it = it->next;
			shm_free(it0->r);
			shm_free(it0);
		}
		lock_destroy(&_reg_htable->entries[i].lock);
	}
	shm_free(_reg_htable->entries);
	shm_free(_reg_htable);
	_reg_htable = NULL;
	return 0;
}

/**
 *
 */
int uac_reg_reset_ht_gc(void)
{
	int i;
	reg_item_t *it = NULL;
	reg_item_t *it0 = NULL;

	if(_reg_htable_gc==NULL)
	{
		LM_DBG("no hash table\n");
		return -1;
	}
	for(i=0; i<_reg_htable_gc->htsize; i++)
	{
		/* free entries */
		it = _reg_htable_gc->entries[i].byuuid;
		while(it)
		{
			it0 = it;
			it = it->next;
			shm_free(it0);
		}
		_reg_htable_gc->entries[i].byuuid = NULL;
		_reg_htable_gc->entries[i].isize=0;
		it = _reg_htable_gc->entries[i].byuser;
		while(it)
		{
			it0 = it;
			it = it->next;
			shm_free(it0->r);
			shm_free(it0);
		}
		_reg_htable_gc->entries[i].byuser = NULL;
		_reg_htable_gc->entries[i].usize = 0;
	}
	/* Reset all counters */
	counter_reset(regtotal);
	counter_reset(regactive);
	counter_reset(regdisabled);
	return 0;
}

/**
 *
 */
int uac_reg_ht_shift(void)
{
	time_t tn;
	int i;

	if(_reg_htable==NULL || _reg_htable_gc==NULL)
	{
		LM_ERR("data struct invalid\n");
		return -1;
	}
	tn = time(NULL);

	lock_get(_reg_htable_gc_lock);
	if(_reg_htable_gc->stime > tn - _uac_reg_gc_interval) {
		lock_release(_reg_htable_gc_lock);
		LM_ERR("shifting in-memory table is not possible in less than %d secs\n",
				_uac_reg_gc_interval);
		return -1;
	}
	uac_reg_reset_ht_gc();
	for(i=0; i<_reg_htable->htsize; i++)
	{
		/* shift entries */
		_reg_htable_gc->entries[i].byuuid = _reg_htable->entries[i].byuuid;
		_reg_htable_gc->entries[i].byuser = _reg_htable->entries[i].byuser;
		_reg_htable_gc->stime = time(NULL);

		/* reset active table entries */
		_reg_htable->entries[i].byuuid = NULL;
		_reg_htable->entries[i].isize=0;
		_reg_htable->entries[i].byuser = NULL;
		_reg_htable->entries[i].usize = 0;
	}
	lock_release(_reg_htable_gc_lock);
	return 0;
}

#define reg_compute_hash(_s)		get_hash1_raw((_s)->s,(_s)->len)
#define reg_get_entry(_h,_size)		((_h)&((_size)-1))

/**
 *
 */
int reg_ht_add_byuuid(reg_uac_t *reg)
{
	unsigned int slot;
	reg_item_t *ri = NULL;

	if(_reg_htable==NULL)
	{
		LM_ERR("reg hash table not initialized\n");
		return -1;
	}

	ri = (reg_item_t*)shm_malloc(sizeof(reg_item_t));
	if(ri==NULL)
	{
		SHM_MEM_ERROR;
		return -1;
	}
	memset(ri, 0, sizeof(reg_item_t));
	slot = reg_get_entry(reg->h_uuid, _reg_htable->htsize);
	ri->r = reg;
	lock_get(&_reg_htable->entries[slot].lock);
	ri->next = _reg_htable->entries[slot].byuuid;
	_reg_htable->entries[slot].byuuid = ri;
	_reg_htable->entries[slot].isize++;
	lock_release(&_reg_htable->entries[slot].lock);
	return 0;
}

/**
 *
 */
int reg_ht_add_byuser(reg_uac_t *reg)
{
	unsigned int slot;
	reg_item_t *ri = NULL;

	if(_reg_htable==NULL)
	{
		LM_ERR("reg hash table not initialized\n");
		return -1;
	}

	ri = (reg_item_t*)shm_malloc(sizeof(reg_item_t));
	if(ri==NULL)
	{
		SHM_MEM_ERROR;
		return -1;
	}
	memset(ri, 0, sizeof(reg_item_t));
	slot = reg_get_entry(reg->h_user, _reg_htable->htsize);
	ri->r = reg;
	lock_get(&_reg_htable->entries[slot].lock);
	ri->next = _reg_htable->entries[slot].byuser;
	_reg_htable->entries[slot].byuser = ri;
	_reg_htable->entries[slot].usize++;
	lock_release(&_reg_htable->entries[slot].lock);
	return 0;
}

#define reg_copy_shm(dst, src, bsize) do { \
		if((src)->s!=NULL) { \
			(dst)->s = p; \
			strncpy((dst)->s, (src)->s, (src)->len); \
			(dst)->len = (src)->len; \
			(dst)->s[(dst)->len] = '\0'; \
			p = p + ((bsize)!=0?(bsize):(dst)->len) + 1; \
		} \
	} while(0);

/**
 *
 */
int reg_ht_add(reg_uac_t *reg)
{
	int len;
	reg_uac_t *nr = NULL;
	char *p;
	int i;

	if(reg==NULL || _reg_htable==NULL)
	{
		LM_ERR("bad parameters: %p/%p\n", reg, _reg_htable);
		return -1;
	}

	if (!reg->contact_addr.s || reg->contact_addr.len == 0)
	{
		reg->contact_addr=reg_contact_addr;
	}

	len = reg->l_uuid.len + 1
		+ reg->l_username.len + 1
		+ reg->l_domain.len + 1
		+ reg->r_username.len + 1
		+ reg->r_domain.len + 1
		+ reg->realm.len + 1
		+ reg->auth_proxy.len + 1
		+ reg->auth_username.len + 1
		+ reg->auth_password.len + 1
		+ reg->auth_ha1.len + 1
		+ reg->socket.len + 1
		+ reg->contact_addr.len + 1
		+ (reg_keep_callid ? UAC_REG_TM_CALLID_SIZE : 0) + 1;
	nr = (reg_uac_t*)shm_malloc(sizeof(reg_uac_t) + len);
	if(nr==NULL)
	{
		SHM_MEM_ERROR;
		return -1;
	}
	memset(nr, 0, sizeof(reg_uac_t) + len);
	nr->expires = reg->expires;
	nr->flags   = reg->flags;
	if (reg->reg_delay)
		nr->reg_delay = reg->reg_delay;
	else if (reg_random_delay>0)
		nr->reg_delay = kam_rand() % reg_random_delay;
	nr->reg_init  = time(NULL);
	nr->h_uuid = reg_compute_hash(&reg->l_uuid);
	nr->h_user = reg_compute_hash(&reg->l_username);

	p = (char*)nr + sizeof(reg_uac_t);

	reg_copy_shm(&nr->l_uuid, &reg->l_uuid, 0);
	reg_copy_shm(&nr->l_username, &reg->l_username, 0);
	reg_copy_shm(&nr->l_domain, &reg->l_domain, 0);
	reg_copy_shm(&nr->r_username, &reg->r_username, 0);
	reg_copy_shm(&nr->r_domain, &reg->r_domain, 0);
	reg_copy_shm(&nr->realm, &reg->realm, 0);
	reg_copy_shm(&nr->auth_proxy, &reg->auth_proxy, 0);
	reg_copy_shm(&nr->auth_username, &reg->auth_username, 0);
	reg_copy_shm(&nr->auth_password, &reg->auth_password, 0);
	reg_copy_shm(&nr->auth_ha1, &reg->auth_ha1, 0);
	reg_copy_shm(&nr->socket, &reg->socket, 0);
	reg_copy_shm(&nr->contact_addr, &reg->contact_addr, 0)
	reg_copy_shm(&nr->callid, &str_empty, reg_keep_callid ? UAC_REG_TM_CALLID_SIZE : 0);

	for(i=0; i<nr->auth_ha1.len; i++) {
		/* ha1 to lowercase */
		if(nr->auth_ha1.s[i] >= 'A' && nr->auth_ha1.s[i] <= 'F') {
			nr->auth_ha1.s[i] += 32;
		}
	}

	reg_ht_add_byuser(nr);
	reg_ht_add_byuuid(nr);
	counter_inc(regtotal);

	LM_DBG("added uuid: %.*s - l_user: %.*s\n", nr->l_uuid.len, nr->l_uuid.s,
			nr->l_username.len, nr->l_username.s);
	return 0;
}


/**
 *
 */
int reg_ht_rm(reg_uac_t *reg)
{
	unsigned int slot1, slot2;
	reg_item_t *it = NULL;
	reg_item_t *prev = NULL;
	int found = 0;

	if (reg == NULL)
	{
		LM_ERR("bad parameter\n");
		return -1;
	}

	/* by uuid */
	slot1 = reg_get_entry(reg->h_uuid, _reg_htable->htsize);
	it = _reg_htable->entries[slot1].byuuid;
	while (it)
	{
		if (it->r == reg)
		{
			if (prev)
				prev->next=it->next;
			else
				_reg_htable->entries[slot1].byuuid = it->next;
			_reg_htable->entries[slot1].isize--;
			shm_free(it);
			found = 1;
			break;
		}
		prev = it;
		it = it->next;
	}

	/* by user */
	prev = NULL;
	slot2 = reg_get_entry(reg->h_user, _reg_htable->htsize);
	if (slot2 != slot1) {
		lock_get(&_reg_htable->entries[slot2].lock);
	}
	it = _reg_htable->entries[slot2].byuser;
	while (it)
	{
		if (it->r == reg)
		{
			if (prev)
				prev->next=it->next;
			else
				_reg_htable->entries[slot2].byuser = it->next;
			_reg_htable->entries[slot2].usize--;
			shm_free(it);
			break;
		}
		prev = it;
		it = it->next;
	}

	shm_free(reg);
	if (slot2 != slot1) {
		lock_release(&_reg_htable->entries[slot2].lock);
	}
	lock_release(&_reg_htable->entries[slot1].lock);

	if (found) {
		counter_add(regtotal, -1);
		if(reg->flags & UAC_REG_ONLINE)
			counter_add(regactive, -1);
		if(reg->flags & UAC_REG_DISABLED)
			counter_add(regdisabled, -1);
	}
	return 0;
}


reg_uac_t *reg_ht_get_byuuid(str *uuid)
{
	unsigned int hash;
	unsigned int slot;
	reg_item_t *it = NULL;

	if(_reg_htable==NULL)
	{
		LM_ERR("reg hash table not initialized\n");
		return NULL;
	}

	hash = reg_compute_hash(uuid);
	slot = reg_get_entry(hash, _reg_htable->htsize);
	lock_get(&_reg_htable->entries[slot].lock);
	it = _reg_htable->entries[slot].byuuid;
	while(it)
	{
		if((it->r->h_uuid==hash) && (it->r->l_uuid.len==uuid->len)
				&& (strncmp(it->r->l_uuid.s, uuid->s, uuid->len)==0))
		{
			it->r->lock = &_reg_htable->entries[slot].lock;
			return it->r;
		}
		it = it->next;
	}
	lock_release(&_reg_htable->entries[slot].lock);
	return NULL;
}

/**
 *
 */
reg_uac_t *reg_ht_get_byuser(str *user, str *domain)
{
	unsigned int hash;
	unsigned int slot;
	reg_item_t *it = NULL;

	if(_reg_htable==NULL)
	{
		LM_ERR("reg hash table not initialized\n");
		return NULL;
	}

	hash = reg_compute_hash(user);
	slot = reg_get_entry(hash, _reg_htable->htsize);
	lock_get(&_reg_htable->entries[slot].lock);
	it = _reg_htable->entries[slot].byuser;
	while(it)
	{
		if((it->r->h_user==hash) && (it->r->l_username.len==user->len)
				&& (strncmp(it->r->l_username.s, user->s, user->len)==0))
		{
			if(domain!=NULL && domain->s!=NULL)
			{
				if((it->r->l_domain.len==domain->len)
						&& (strncmp(it->r->l_domain.s, domain->s, domain->len)==0))
				{
					it->r->lock = &_reg_htable->entries[slot].lock;
					return it->r;
				}
			} else {
				it->r->lock = &_reg_htable->entries[slot].lock;
				return it->r;
			}
		}
		it = it->next;
	}
	lock_release(&_reg_htable->entries[slot].lock);
	return NULL;
}

int reg_ht_get_byfilter(reg_uac_t **reg, str *attr, str *val)
{
	int i;
	str *rval;
	reg_item_t *it;

	/* try to use the hash table indices */
	if(attr->len==6 && strncmp(attr->s, "l_uuid", 6)==0) {
		*reg = reg_ht_get_byuuid(val);
		return *reg != NULL;
	}
	if(attr->len==10 && strncmp(attr->s, "l_username", 10)==0) {
		*reg = reg_ht_get_byuser(val, NULL);
		return *reg != NULL;
	}

	/* check _all_ records */
	for(i=0; i<_reg_htable->htsize; i++)
	{
		lock_get(&_reg_htable->entries[i].lock);
		/* walk through entries */
		it = _reg_htable->entries[i].byuuid;
		while(it)
		{
			if(attr->len==10 && strncmp(attr->s, "r_username", 10)==0) {
				rval = &it->r->r_username;
			} else if(attr->len==13 && strncmp(attr->s, "auth_username", 13)==0) {
				rval = &it->r->auth_username;
			} else {
				lock_release(&_reg_htable->entries[i].lock);
				LM_ERR("unsupported filter attribute %.*s\n", attr->len, attr->s);
				return -1;
			}

			if(rval->len==val->len && strncmp(val->s, rval->s, val->len)==0) {
				/* found */
				*reg = it->r;
				(*reg)->lock = &_reg_htable->entries[i].lock;
				return 1;
			}
			it = it->next;
		}
		lock_release(&_reg_htable->entries[i].lock);
	}
	*reg = NULL;
	return 0;
}

int uac_reg_tmdlg(dlg_t *tmdlg, sip_msg_t *rpl)
{
	if(tmdlg==NULL || rpl==NULL)
		return -1;

	if (parse_headers(rpl, HDR_EOH_F, 0) < 0) {
		LM_ERR("error while parsing all headers in the reply\n");
		return -1;
	}
	if(parse_to_header(rpl)<0 || parse_from_header(rpl)<0) {
		LM_ERR("error while parsing From/To headers in the reply\n");
		return -1;
	}
	memset(tmdlg, 0, sizeof(dlg_t));

	str2int(&(get_cseq(rpl)->number), &tmdlg->loc_seq.value);
	tmdlg->loc_seq.is_set = 1;

	tmdlg->id.call_id = rpl->callid->body;
	trim(&tmdlg->id.call_id);

	if (get_from(rpl)->tag_value.len) {
		tmdlg->id.loc_tag = get_from(rpl)->tag_value;
	}
	tmdlg->loc_uri = get_from(rpl)->uri;
	tmdlg->rem_uri = get_to(rpl)->uri;
	tmdlg->state= DLG_CONFIRMED;
	return 0;
}

void uac_reg_tm_callback( struct cell *t, int type, struct tmcb_params *ps)
{
	char *uuid;
	str suuid;
	reg_uac_t *ri = NULL;
	contact_t* c;
	int expires;
	struct sip_uri puri;
	struct hdr_field *hdr;
	HASHHEX response;
	str *new_auth_hdr = NULL;
	static struct authenticate_body auth;
	uac_credential_t cred;
	char  b_ruri[MAX_URI_SIZE];
	str   s_ruri;
	char  b_hdrs[MAX_UACH_SIZE];
	str   s_hdrs;
	uac_req_t uac_r;
	str method = {"REGISTER", 8};
	int ret;
	dlg_t tmdlg;

	if(ps->param==NULL || *ps->param==0)
	{
		LM_DBG("uuid not received\n");
		return;
	}
	uuid = *((char**)ps->param);
	LM_DBG("completed with status %d [uuid: %s]\n",
			ps->code, uuid);
	suuid.s = uuid;
	suuid.len = strlen(suuid.s);
	ri = reg_ht_get_byuuid(&suuid);

	if(ri==NULL)
	{
		LM_DBG("no user with uuid %s\n", uuid);
		goto done;
	}

	if(ps->code == 200)
	{
		if (parse_headers(ps->rpl, HDR_EOH_F, 0) == -1)
		{
			LM_ERR("failed to parse headers\n");
			goto error;
		}
		if (ps->rpl->contact==NULL)
		{
			LM_ERR("no Contact found\n");
			goto error;
		}
		if (parse_contact(ps->rpl->contact) < 0)
		{
			LM_ERR("failed to parse Contact HF\n");
			goto error;
		}
		if (((contact_body_t*)ps->rpl->contact->parsed)->star)
		{
			LM_DBG("* Contact found\n");
			goto done;
		}

		if (contact_iterator(&c, ps->rpl, 0) < 0)
			goto done;
		while(c)
		{
			if(parse_uri(c->uri.s, c->uri.len, &puri)!=0)
			{
				LM_ERR("failed to parse c-uri\n");
				goto error;
			}
			if(suuid.len==puri.user.len
					&& (strncmp(puri.user.s, suuid.s, suuid.len)==0))
			{
				/* calculate expires */
				expires=0;
				if(c->expires==NULL || c->expires->body.len<=0)
				{
					if(ps->rpl->expires!=NULL && parse_expires(ps->rpl->expires)==0)
						expires = ((exp_body_t *)ps->rpl->expires->parsed)->val;
				} else {
					str2int(&c->expires->body, (unsigned int*)(&expires));
				}
				ri->timer_expires = ri->timer_expires + expires;
				ri->flags |= UAC_REG_ONLINE;
				if (reg_keep_callid && ps->rpl->callid->body.len < UAC_REG_TM_CALLID_SIZE) {
					ri->callid.len = ps->rpl->callid->body.len;
					memcpy(ri->callid.s, ps->rpl->callid->body.s, ri->callid.len);
					str2int(&(get_cseq(ps->rpl)->number), &ri->cseq);
				}
				goto done;
			}
			if (contact_iterator(&c, ps->rpl, c) < 0)
			{
				LM_DBG("local contact not found\n");
				goto done;
			}
		}

		LM_DBG("sip response %d while registering [%.*s] with no match\n",
				ps->code, ri->l_uuid.len, ri->l_uuid.s);
		goto done;
	}

	if(ps->code == 401 || ps->code == 407)
	{
		if(ri->flags & UAC_REG_AUTHSENT)
		{
			LM_ERR("authentication failed for <%.*s>\n",
					ri->l_uuid.len, ri->l_uuid.s);
			goto error;
		}
		hdr = get_autenticate_hdr(ps->rpl, ps->code);
		if (hdr==0)
		{
			LM_ERR("failed to extract authenticate hdr\n");
			goto error;
		}

		LM_DBG("auth header body [%.*s]\n",
				hdr->body.len, hdr->body.s);

		if (parse_authenticate_body(&hdr->body, &auth)<0)
		{
			LM_ERR("failed to parse auth hdr body\n");
			goto error;
		}
		if (ri->realm.len>0) {
			/* only check if realms match if it is non-empty */
			if(auth.realm.len!=ri->realm.len
					|| strncmp(auth.realm.s, ri->realm.s, ri->realm.len)!=0)
			{
				LM_ERR("realms do not match. requested realm: [%.*s]\n",
						auth.realm.len, auth.realm.s);
				goto error;
			}
		}
		cred.aflags = 0;
		cred.realm = auth.realm;
		cred.user = ri->auth_username;
		if(ri->auth_ha1.len > 0) {
			cred.passwd = ri->auth_ha1;
			cred.aflags |= UAC_FLCRED_HA1;
		} else {
			cred.passwd = ri->auth_password;
		}
		cred.next = NULL;

		snprintf(b_ruri, MAX_URI_SIZE, "sip:%.*s",
				ri->r_domain.len, ri->r_domain.s);
		s_ruri.s = b_ruri; s_ruri.len = strlen(s_ruri.s);

		do_uac_auth(&method, &s_ruri, &cred, &auth, response);
		new_auth_hdr=build_authorization_hdr(ps->code, &s_ruri, &cred,
				&auth, response);
		if (new_auth_hdr==0)
		{
			LM_ERR("failed to build authorization hdr\n");
			goto error;
		}

		snprintf(b_hdrs, MAX_UACH_SIZE,
				"Contact: <sip:%.*s@%.*s>\r\n"
				"Expires: %d\r\n"
				"%.*s",
				ri->l_uuid.len, ri->l_uuid.s,
				ri->contact_addr.len, ri->contact_addr.s,
				ri->expires,
				new_auth_hdr->len, new_auth_hdr->s);
		s_hdrs.s = b_hdrs; s_hdrs.len = strlen(s_hdrs.s);
		pkg_free(new_auth_hdr->s);

		memset(&uac_r, 0, sizeof(uac_r));
		if(uac_reg_tmdlg(&tmdlg, ps->rpl)<0)
		{
			LM_ERR("failed to build tm dialog\n");
			goto error;
		}
		tmdlg.rem_target = s_ruri;
		if(ri->auth_proxy.len)
			tmdlg.dst_uri = ri->auth_proxy;
		uac_r.method = &method;
		uac_r.headers = &s_hdrs;
		uac_r.dialog = &tmdlg;
		uac_r.cb_flags = TMCB_LOCAL_COMPLETED;
		/* Callback function */
		uac_r.cb  = uac_reg_tm_callback;
		/* Callback parameter */
		uac_r.cbp = (void*)uuid;
		ret = uac_tmb.t_request_within(&uac_r);

		if(ret<0) {
			LM_ERR("failed to send request with authentication for [%.*s]",
					ri->l_uuid.len, ri->l_uuid.s);
			goto error;
		}

		ri->flags |= UAC_REG_AUTHSENT;
		lock_release(ri->lock);
		return;
	}

	if (ps->code == 423)   /* Interval too brief, retry with longer expiry */
	{
		if (parse_headers(ps->rpl, HDR_EOH_F, 0) == -1) {
			LM_ERR("failed to parse headers\n");
			goto error;
		}
		if(ps->rpl->min_expires!=NULL && parse_expires(ps->rpl->min_expires)==0) {
			ri->expires = ((exp_body_t *)ps->rpl->min_expires->parsed)->val;
		} else {
			ri->expires *= 2;
		}
		LM_DBG("got 423 response while registering [%.*s], set new expires to %d\n",
				ri->l_uuid.len, ri->l_uuid.s, ri->expires);
		/* Retry will be done on next timer interval */
		goto done;
	} else
	{
		LM_ERR("got sip response %d while registering [%.*s]\n",
				ps->code, ri->l_uuid.len, ri->l_uuid.s);
		goto error;
	}

error:
	if(reg_retry_interval) {
		ri->timer_expires = time(NULL) + reg_retry_interval;
	} else {
		ri->flags |= UAC_REG_DISABLED;
		counter_inc(regdisabled);
	}
	ri->cseq = 0;
done:
	if(ri) {
		ri->flags &= ~(UAC_REG_ONGOING|UAC_REG_AUTHSENT);
		lock_release(ri->lock);
	}
	shm_free(uuid);
	counter_inc(regactive);
}


/**
 *
 */
int uac_reg_send(reg_uac_t *reg, time_t tn)
{
	char *uuid;
	uac_req_t uac_r;
	str method = {"REGISTER", 8};
	int ret;
	char  b_ruri[MAX_URI_SIZE];
	str   s_ruri;
	char  b_turi[MAX_URI_SIZE];
	str   s_turi;
	char  b_hdrs[MAX_UACH_SIZE];
	str   s_hdrs;
	dlg_t tmdlg;

	uuid = (char*)shm_malloc(reg->l_uuid.len+1);
	if(uuid==NULL) {
		SHM_MEM_ERROR;
		return -1;
	}
	reg->timer_expires = tn;
	reg->flags |= UAC_REG_ONGOING;
	counter_add(regactive, -1);		/* Take it out of the active pool while re-registering */
	memcpy(uuid, reg->l_uuid.s, reg->l_uuid.len);
	uuid[reg->l_uuid.len] = '\0';

	snprintf(b_ruri, MAX_URI_SIZE, "sip:%.*s",
			reg->r_domain.len, reg->r_domain.s);
	s_ruri.s = b_ruri; s_ruri.len = strlen(s_ruri.s);

	snprintf(b_turi, MAX_URI_SIZE, "sip:%.*s@%.*s",
			reg->r_username.len, reg->r_username.s,
			reg->r_domain.len, reg->r_domain.s);
	s_turi.s = b_turi; s_turi.len = strlen(s_turi.s);

	snprintf(b_hdrs, MAX_UACH_SIZE,
			"Contact: <sip:%.*s@%.*s>\r\n"
			"Expires: %d\r\n",
			reg->l_uuid.len, reg->l_uuid.s,
			reg->contact_addr.len, reg->contact_addr.s,
			reg->expires);
	s_hdrs.s = b_hdrs; s_hdrs.len = strlen(s_hdrs.s);

	memset(&uac_r, '\0', sizeof(uac_r));
	uac_r.method = &method;
	uac_r.headers = &s_hdrs;
	uac_r.cb_flags = TMCB_LOCAL_COMPLETED;
	/* Callback function */
	uac_r.cb  = uac_reg_tm_callback;
	/* Callback parameter */
	uac_r.cbp = (void*)uuid;

	if(reg->socket.s != NULL && reg->socket.len > 0) {
		/* custom socket */
		LM_DBG("using custom socket %.*s to send request\n",
			reg->socket.len, reg->socket.s);
		uac_r.ssock = &reg->socket;
	} else {
		/* default socket */
		if(uac_default_socket.s != NULL && uac_default_socket.len > 0) {
			LM_DBG("using configured default_socket to send request\n");
			uac_r.ssock = &uac_default_socket;
		}
	}

	if (reg_keep_callid && reg->flags & UAC_REG_ONLINE
				&& reg->cseq > 0 && reg->cseq < 2147483638
				&& reg->callid.len > 0) {
		/* reregister, reuse callid and cseq */
		memset(&tmdlg, 0, sizeof(dlg_t));
		tmdlg.id.call_id = reg->callid;
		tmdlg.loc_seq.value = reg->cseq;
		tmdlg.loc_seq.is_set = 1;
		tmdlg.rem_target = s_ruri;
		tmdlg.loc_uri = s_turi;
		tmdlg.rem_uri = s_turi;
		tmdlg.state= DLG_CONFIRMED;
		if(reg->auth_proxy.len)
			tmdlg.dst_uri = reg->auth_proxy;
		uac_r.dialog = &tmdlg;

		ret = uac_tmb.t_request_within(&uac_r);
	} else {
		ret = uac_tmb.t_request(&uac_r,  /* UAC Req */
				&s_ruri, /* Request-URI */
				&s_turi, /* To */
				&s_turi, /* From */
				(reg->auth_proxy.len)?&reg->auth_proxy:NULL /* outbound uri */
				);
	}
	reg->flags &= ~UAC_REG_ONLINE;

	if(ret<0)
	{
		LM_ERR("failed to send request for [%.*s]", reg->l_uuid.len, reg->l_uuid.s);
		shm_free(uuid);
		if (reg_retry_interval) {
			reg->timer_expires = (tn ? tn : time(NULL)) + reg_retry_interval;
		} else {
			reg->flags |= UAC_REG_DISABLED;
			counter_inc(regdisabled);
		}
		reg->flags &= ~UAC_REG_ONGOING;
		return -1;
	}
	return 0;
}


/**
 *
 */
int uac_reg_update(reg_uac_t *reg, time_t tn)
{
	if(uac_tmb.t_request==NULL)
		return -1;
	if(reg->expires==0)
		return 1;
	if(reg->flags&UAC_REG_ONGOING) {
		if (reg->timer_expires > tn - reg_retry_interval)
			return 2;
		LM_DBG("record marked as ongoing registration (%d) - resetting\n",
				(int)reg->flags);
		reg->flags &= ~(UAC_REG_ONLINE|UAC_REG_AUTHSENT);
	}
	if(reg_active && *reg_active == 0)
		return 4;
	if(reg->flags&UAC_REG_DISABLED)
		return 4;

	if(!(reg->flags & UAC_REG_INIT)) {
		if(reg->reg_delay>0) {
			if(tn < reg->reg_init+reg->reg_delay) {
				return 2;
			}
		}
		reg->flags |= UAC_REG_INIT;
	}

	if(reg->timer_expires > tn + reg_timer_interval + 3)
		return 3;

	return uac_reg_send(reg, tn);
}

/**
 *
 */
void uac_reg_timer(unsigned int ticks)
{
	int i;
	reg_item_t *it = NULL;
	time_t tn;

	if(_reg_htable==NULL)
		return;

	tn = time(NULL);
	for(i=0; i<_reg_htable->htsize; i++)
	{
		/* walk through entries */
		lock_get(&_reg_htable->entries[i].lock);
		it = _reg_htable->entries[i].byuuid;
		while(it)
		{
			uac_reg_update(it->r, tn);
			it = it->next;
		}
		lock_release(&_reg_htable->entries[i].lock);
	}

	if(_reg_htable_gc!=NULL)
	{
		lock_get(_reg_htable_gc_lock);
		if(_reg_htable_gc->stime!=0
				&& _reg_htable_gc->stime < tn - _uac_reg_gc_interval)
			uac_reg_reset_ht_gc();
		lock_release(_reg_htable_gc_lock);
	}
}

static int uac_reg_check_password(reg_uac_t *reg)
{
	int i;

	if(reg->auth_password.len<=0 && reg->auth_ha1.len<=0) {
		LM_ERR("no password value provided - ignoring record\n");
		return -1;
	}

	if(reg->auth_ha1.len > 0 && reg->auth_ha1.len != HASHHEXLEN) {
		LM_ERR("invalid HA2 length: %d - ignoring record\n", reg->auth_ha1.len);
		return -1;
	}
	for(i=0; i<reg->auth_ha1.len; i++) {
		if(!((reg->auth_ha1.s[i]>='0' && reg->auth_ha1.s[i]<='9')
				|| (reg->auth_ha1.s[i]>='a' && reg->auth_ha1.s[i]<='f')
				|| (reg->auth_ha1.s[i]>='A' && reg->auth_ha1.s[i]<='F'))) {
			LM_ERR("invalid char %d in HA1 string: %.*s\n", i,
					reg->auth_ha1.len, reg->auth_ha1.s);
			return -1;
		}
	}

	return 0;
}

#define reg_db_set_attr(attr, pos, eval) do { \
	if(!VAL_NULL(&RES_ROWS(db_res)[i].values[pos])) { \
		reg->attr.s = \
				(char*)(RES_ROWS(db_res)[i].values[pos].val.string_val); \
		reg->attr.len = strlen(reg->attr.s); \
		if(reg->attr.len == 0) { \
			if(eval == 0) { \
				LM_ERR("empty value not allowed for column[%d]='%.*s' - ignoring record\n", \
						pos, db_cols[pos]->len, db_cols[pos]->s); \
				return -1; \
			}  else { \
				reg->attr.s = NULL; \
			} \
		} \
	} \
} while(0);


static int uac_reg_db_to_reg(reg_uac_t *reg, db1_res_t* db_res, int i, db_key_t *db_cols)
{
	memset(reg, 0, sizeof(reg_uac_t));;
	/* check for NULL values ?!?! */
	reg_db_set_attr(l_uuid, 0, 0);
	reg_db_set_attr(l_username, 1, 0);
	reg_db_set_attr(l_domain, 2, 0);
	reg_db_set_attr(r_username, 3, 0);
	reg_db_set_attr(r_domain, 4, 0);
	/* realm may be empty */
	reg_db_set_attr(realm, 5, 1);
	reg_db_set_attr(auth_username, 6, 0);
	reg_db_set_attr(auth_password, 7, 1);
	reg_db_set_attr(auth_ha1, 8, 1);
	if(uac_reg_check_password(reg) < 0) {
		return -1;
	}
	reg_db_set_attr(auth_proxy, 9, 0);
	reg->expires = (unsigned int)RES_ROWS(db_res)[i].values[10].val.int_val;
	reg->flags = (unsigned int)RES_ROWS(db_res)[i].values[11].val.int_val;
	reg->reg_delay = (unsigned int)RES_ROWS(db_res)[i].values[12].val.int_val;

	/*contact may be empty */
	reg_db_set_attr(contact_addr, 13, 1);
	/* socket may be empty */
	reg_db_set_attr(socket, 14, 1);

	return 0;
}


/**
 *
 */
int uac_reg_load_db(void)
{
	db1_con_t *reg_db_con = NULL;
	db_func_t reg_dbf;
	reg_uac_t reg;
	db_key_t db_cols[UAC_REG_DB_COLS_NUM] = {
		&l_uuid_column,
		&l_username_column,
		&l_domain_column,
		&r_username_column,
		&r_domain_column,
		&realm_column,
		&auth_username_column,
		&auth_password_column,
		&auth_ha1_column,
		&auth_proxy_column,
		&expires_column,
		&flags_column,
		&reg_delay_column,
		&contact_addr_column,
		&socket_column
	};
	db1_res_t* db_res = NULL;
	int i, ret;

	/* binding to db module */
	if(reg_db_url.s==NULL)
	{
		LM_ERR("no db url\n");
		return -1;
	}

	if(db_bind_mod(&reg_db_url, &reg_dbf))
	{
		LM_ERR("database module not found\n");
		return -1;
	}

	if (!DB_CAPABILITY(reg_dbf, DB_CAP_ALL))
	{
		LM_ERR("database module does not "
				"implement all functions needed by the module\n");
		return -1;
	}

	/* open a connection with the database */
	reg_db_con = reg_dbf.init(&reg_db_url);
	if(reg_db_con==NULL)
	{
		LM_ERR("failed to connect to the database\n");
		return -1;
	}

	if(db_check_table_version(&reg_dbf, reg_db_con, &reg_db_table, UACREG_TABLE_VERSION) < 0) {
		DB_TABLE_VERSION_ERROR(reg_db_table);
		return -1;
	}

	if (reg_dbf.use_table(reg_db_con, &reg_db_table) < 0)
	{
		LM_ERR("failed to use_table\n");
		return -1;
	}

	if (DB_CAPABILITY(reg_dbf, DB_CAP_FETCH)) {
		if(reg_dbf.query(reg_db_con, 0, 0, 0, db_cols, 0, UAC_REG_DB_COLS_NUM, 0, 0) < 0)
		{
			LM_ERR("Error while querying db\n");
			return -1;
		}
		if(reg_dbf.fetch_result(reg_db_con, &db_res, reg_fetch_rows)<0)
		{
			LM_ERR("Error while fetching result\n");
			if (db_res)
				reg_dbf.free_result(reg_db_con, db_res);
			goto error;
		} else {
			if(RES_ROW_N(db_res)==0)
			{
				goto done;
			}
		}
	} else {
		if((ret=reg_dbf.query(reg_db_con, NULL, NULL, NULL, db_cols,
						0, UAC_REG_DB_COLS_NUM, 0, &db_res))!=0
				|| RES_ROW_N(db_res)<=0 )
		{
			reg_dbf.free_result(reg_db_con, db_res);
			if( ret==0)
			{
				return 0;
			} else {
				goto error;
			}
		}
	}

	do {
		for(i=0; i<RES_ROW_N(db_res); i++)
		{
			if(uac_reg_db_to_reg(&reg, db_res, i, db_cols) < 0)
				continue;
			if(reg_ht_add(&reg)<0)
			{
				LM_ERR("Error adding reg to htable\n");
				goto error;
			}
		}
		if (DB_CAPABILITY(reg_dbf, DB_CAP_FETCH)) {
			if(reg_dbf.fetch_result(reg_db_con, &db_res, reg_fetch_rows)<0) {
				LM_ERR("Error while fetching!\n");
				if (db_res)
					reg_dbf.free_result(reg_db_con, db_res);
				goto error;
			}
		} else {
			break;
		}
	}  while(RES_ROW_N(db_res)>0);
	reg_dbf.free_result(reg_db_con, db_res);
	reg_dbf.close(reg_db_con);

done:
	return 0;

error:
	if (reg_db_con) {
		reg_dbf.free_result(reg_db_con, db_res);
		reg_dbf.close(reg_db_con);
	}
	return -1;
}

/**
 *
 */
int uac_reg_db_refresh(str *pl_uuid)
{
	db1_con_t *reg_db_con = NULL;
	db_func_t reg_dbf;
	reg_uac_t reg;
	reg_uac_t *cur_reg;
	db_key_t db_cols[UAC_REG_DB_COLS_NUM] = {
		&l_uuid_column,
		&l_username_column,
		&l_domain_column,
		&r_username_column,
		&r_domain_column,
		&realm_column,
		&auth_username_column,
		&auth_password_column,
		&auth_ha1_column,
		&auth_proxy_column,
		&expires_column,
		&flags_column,
		&reg_delay_column,
		&contact_addr_column,
		&socket_column
	};
	db_key_t db_keys[1] = {&l_uuid_column};
	db_val_t db_vals[1];

	db1_res_t* db_res = NULL;
	int ret;

	/* binding to db module */
	if(reg_db_url.s==NULL)
	{
		LM_ERR("no db url\n");
		return -1;
	}

	if(db_bind_mod(&reg_db_url, &reg_dbf))
	{
		LM_ERR("database module not found\n");
		return -1;
	}

	if (!DB_CAPABILITY(reg_dbf, DB_CAP_ALL))
	{
		LM_ERR("database module does not "
				"implement all functions needed by the module\n");
		return -1;
	}

	/* open a connection with the database */
	reg_db_con = reg_dbf.init(&reg_db_url);
	if(reg_db_con==NULL)
	{
		LM_ERR("failed to connect to the database\n");
		return -1;
	}
	if (reg_dbf.use_table(reg_db_con, &reg_db_table) < 0)
	{
		LM_ERR("failed to use_table\n");
		return -1;
	}

	db_vals[0].type = DB1_STR;
	db_vals[0].nul = 0;
	db_vals[0].val.str_val.s = pl_uuid->s;
	db_vals[0].val.str_val.len = pl_uuid->len;

	if((ret=reg_dbf.query(reg_db_con, db_keys, NULL, db_vals, db_cols,
					1 /*nr keys*/, UAC_REG_DB_COLS_NUM /*nr cols*/, 0, &db_res))!=0
			|| RES_ROW_N(db_res)<=0 )
	{
		reg_dbf.free_result(reg_db_con, db_res);
		if( ret==0)
		{
			return 0;
		} else {
			goto error;
		}
	}

	if (uac_reg_db_to_reg(&reg, db_res, 0, db_cols)==0)
	{
		lock_get(_reg_htable_gc_lock);
		if((cur_reg=reg_ht_get_byuuid(pl_uuid))!=NULL)
		{
			reg.flags |= (cur_reg->flags & (UAC_REG_ONGOING | UAC_REG_AUTHSENT));
			reg_ht_rm(cur_reg);
		}
		if(reg_ht_add(&reg)<0)
		{
			lock_release(_reg_htable_gc_lock);
			LM_ERR("Error adding reg to htable\n");
			goto error;
		}
		lock_release(_reg_htable_gc_lock);
	}

	reg_dbf.free_result(reg_db_con, db_res);
	reg_dbf.close(reg_db_con);

	return 1;

error:
	if (reg_db_con) {
		reg_dbf.free_result(reg_db_con, db_res);
		reg_dbf.close(reg_db_con);
	}
	return -1;
}

/**
 *
 */
int uac_reg_refresh(sip_msg_t *msg, str *l_uuid)
{
	int ret;

	if(l_uuid==NULL || l_uuid->s==NULL || l_uuid->len<=0) {
		LM_ERR("invalid parameters\n");
		return -1;
	}

	ret = uac_reg_db_refresh(l_uuid);
	if(ret==0) {
		LM_WARN("record not found: %.*s\n", l_uuid->len, l_uuid->s);
		return -1;
	} else if(ret<0) {
		LM_WARN("failed to refresh record: %.*s - check log messages\n",
				l_uuid->len, l_uuid->s);
		return -1;
	}

	return 1;
}

/**
 *
 */
int  uac_reg_lookup(struct sip_msg *msg, str *src, pv_spec_t *dst, int mode)
{
	char  b_ruri[MAX_URI_SIZE];
	str   s_ruri;
	struct sip_uri puri;
	reg_uac_t *reg = NULL;
	pv_value_t val;

	if(!pv_is_w(dst))
	{
		LM_ERR("dst is not w/\n");
		return -1;
	}
	if(mode==0)
	{
		reg = reg_ht_get_byuuid(src);
		if(reg==NULL)
		{
			LM_DBG("no uuid: %.*s\n", src->len, src->s);
			return -1;
		}
		snprintf(b_ruri, MAX_URI_SIZE, "sip:%.*s@%.*s",
				reg->l_username.len, reg->l_username.s,
				reg->l_domain.len, reg->l_domain.s);
		s_ruri.s = b_ruri; s_ruri.len = strlen(s_ruri.s);
	} else {
		if(parse_uri(src->s, src->len, &puri)!=0)
		{
			LM_ERR("failed to parse uri\n");
			return -2;
		}
		reg = reg_ht_get_byuser(&puri.user, (_uac_reg_use_domain)?&puri.host:NULL);
		if(reg==NULL)
		{
			LM_DBG("no user: %.*s\n", src->len, src->s);
			return -1;
		}
		snprintf(b_ruri, MAX_URI_SIZE, "%.*s",
				reg->l_uuid.len, reg->l_uuid.s);
		s_ruri.s = b_ruri; s_ruri.len = strlen(s_ruri.s);
	}
	lock_release(reg->lock);
	memset(&val, 0, sizeof(pv_value_t));
	val.flags |= PV_VAL_STR;
	val.rs = s_ruri;
	if(pv_set_spec_value(msg, dst, 0, &val)!=0)
		return -1;

	return 1;
}

/**
 *
 */
int uac_reg_status(struct sip_msg *msg, str *src, int mode)
{
	struct sip_uri puri;
	reg_uac_t *reg = NULL;
	int ret;

	if(mode==0)
	{
		reg = reg_ht_get_byuuid(src);
		if(reg==NULL)
		{
			LM_DBG("no uuid: %.*s\n", src->len, src->s);
			return -1;
		}
	} else {
		if(parse_uri(src->s, src->len, &puri)!=0)
		{
			LM_ERR("failed to parse uri\n");
			return -1;
		}
		reg = reg_ht_get_byuser(&puri.user, (_uac_reg_use_domain)?&puri.host:NULL);
		if(reg==NULL)
		{
			LM_DBG("no user: %.*s\n", src->len, src->s);
			return -1;
		}
	}

	if ((reg->flags & UAC_REG_ONLINE) && (reg->timer_expires > time(NULL)))
		ret = 1;
	else if (reg->flags & UAC_REG_ONGOING)
		ret = -2;
	else if (reg->flags & UAC_REG_DISABLED)
		ret = -3;
	else
		ret = -99;

	lock_release(reg->lock);
	return ret;
}

/**
 *
 */
int uac_reg_request_to(struct sip_msg *msg, str *src, unsigned int mode)
{
	char ruri[MAX_URI_SIZE];
	struct sip_uri puri;
	reg_uac_t *reg = NULL;
	pv_value_t val;
	struct action act;
	struct run_act_ctx ra_ctx;
	unsigned int umode;
	unsigned int amode;

	umode = mode & UACREG_REQTO_MASK_USER;
	amode = mode & UACREG_REQTO_MASK_AUTH;

	switch(umode)
	{
		case 0:
			reg = reg_ht_get_byuuid(src);
			break;
		case 1:
			if(_uac_reg_use_domain)
			{
				if (parse_uri(src->s, src->len, &puri)!=0)
				{
					LM_ERR("failed to parse uri\n");
					return -2;
				}
				reg = reg_ht_get_byuser(&puri.user, &puri.host);
			} else {
				reg = reg_ht_get_byuser(src, NULL);
			}
			break;
		default:
			LM_ERR("unknown mode: %d\n", mode);
			return -1;
	}

	if(reg==NULL)
	{
		LM_DBG("no user: %.*s\n", src->len, src->s);
		return -1;
	}

	// Set uri ($ru)
	snprintf(ruri, MAX_URI_SIZE, "sip:%.*s@%.*s",
			reg->r_username.len, reg->r_username.s,
			reg->r_domain.len, reg->r_domain.s);
	memset(&act, 0, sizeof(act));
	act.type = SET_URI_T;
	act.val[0].type = STRING_ST;
	act.val[0].u.string = ruri;
	init_run_actions_ctx(&ra_ctx);
	if (do_action(&ra_ctx, &act, msg) < 0) {
		LM_ERR("error while setting request uri\n");
		goto error;
	}

	// Set auth_proxy ($du)
	if (set_dst_uri(msg, &reg->auth_proxy) < 0) {
		LM_ERR("error while setting outbound proxy\n");
		goto error;
	}

	memset(&val, 0, sizeof(pv_value_t));
	val.flags |= PV_VAL_STR;

	// Set auth_realm
	val.rs = reg->realm;
	if(pv_set_spec_value(msg, &auth_realm_spec, 0, &val)!=0) {
		LM_ERR("error while setting auth_realm\n");
		goto error;
	}

	// Set auth_username
	val.rs = reg->auth_username;
	if(pv_set_spec_value(msg, &auth_username_spec, 0, &val)!=0) {
		LM_ERR("error while setting auth_username\n");
		goto error;
	}

	if(amode) {
		// set auth_ha1
		val.rs = reg->auth_ha1;
	} else {
		// set auth_password
		val.rs = reg->auth_password;
	}
	if(pv_set_spec_value(msg, &auth_password_spec, 0, &val)!=0) {
		LM_ERR("error while setting auth password (mode: %d)\n", amode);
		goto error;
	}
	lock_release(reg->lock);
	return 1;

error:
	lock_release(reg->lock);
	return -1;
}

/**
 *
 */
static int uac_reg_update_flag(str *attr, str *val, int mode, int fval)
{
	reg_uac_t *reg = NULL;
	int ret;

	if(_reg_htable==NULL) {
		LM_ERR("uac remote registrations not enabled\n");
		return -1;
	}

	if(attr->len<=0 || attr->s==NULL || val->len<=0 || val->s==NULL) {
		LM_ERR("bad parameter values\n");
		return -1;
	}

	ret = reg_ht_get_byfilter(&reg, attr, val);
	if (ret == 0) {
		LM_DBG("record not found for %.*s = %.*s\n", attr->len, attr->s,
				val->len, val->s);
		return -2;
	} else if (ret < 0) {
		LM_DBG("unsupported filter attribute %.*s = %.*s\n", attr->len, attr->s,
				val->len, val->s);
		return -3;
	}

	if(mode==1) {
		reg->flags |= fval;
	} else {
		reg->flags &= ~fval;
	}
	reg->timer_expires = time(NULL) + 1;

	lock_release(reg->lock);
	return 1;
}

/**
 *
 */
int uac_reg_enable(sip_msg_t *msg, str *attr, str *val)
{
	counter_add(regdisabled, -1);
	return uac_reg_update_flag(attr, val, 0, UAC_REG_DISABLED);
}

/**
 *
 */
int uac_reg_disable(sip_msg_t *msg, str *attr, str *val)
{
	counter_inc(regdisabled);
	return uac_reg_update_flag(attr, val, 1, UAC_REG_DISABLED);
}

/**
 *
 */
static int rpc_uac_reg_add_node_helper(rpc_t* rpc, void* ctx, reg_uac_t *reg, time_t tn)
{
	void* th;
	str none = {"none", 4};

	/* add entry node */
	if (rpc->add(ctx, "{", &th) < 0)
	{
		rpc->fault(ctx, 500, "Internal error creating rpc");
		return -1;
	}
	if (rpc->struct_add(th, "SSSSSSSSSSddddddSS",
				"l_uuid",        &reg->l_uuid,
				"l_username",    &reg->l_username,
				"l_domain",      &reg->l_domain,
				"r_username",    &reg->r_username,
				"r_domain",      &reg->r_domain,
				"realm",         &reg->realm,
				"auth_username", &reg->auth_username,
				"auth_password", (reg->auth_password.len)?
										&reg->auth_password:&none,
				"auth_ha1",      (reg->auth_ha1.len)?
										&reg->auth_ha1:&none,
				"auth_proxy",    (reg->auth_proxy.len)?
										&reg->auth_proxy:&none,
				"expires",       (int)reg->expires,
				"flags",         (int)reg->flags,
				"diff_expires",  (int)(reg->timer_expires - tn),
				"timer_expires", (int)reg->timer_expires,
				"reg_init",      (int)reg->reg_init,
				"reg_delay",     (int)reg->reg_delay,
				"contact_addr",  (reg->contact_addr.len)?
										&reg->contact_addr:&none,
				"socket",        &reg->socket
				)<0) {
		rpc->fault(ctx, 500, "Internal error adding item");
		return -1;
	}
	return 0;
}

static const char* rpc_uac_reg_dump_doc[2] = {
	"Dump the contents of user registrations table.",
	0
};

static void rpc_uac_reg_dump(rpc_t* rpc, void* ctx)
{
	int i;
	reg_item_t *reg = NULL;
	time_t tn;

	if(_reg_htable==NULL)
	{
		rpc->fault(ctx, 500, "Not enabled");
		return;
	}

	tn = time(NULL);

	for(i=0; i<_reg_htable->htsize; i++)
	{
		lock_get(&_reg_htable->entries[i].lock);
		/* walk through entries */
		reg = _reg_htable->entries[i].byuuid;
		while(reg)
		{
			if (rpc_uac_reg_add_node_helper(rpc, ctx, reg->r, tn)<0)
			{
				lock_release(&_reg_htable->entries[i].lock);
				return;
			}
			reg = reg->next;
		}
		lock_release(&_reg_htable->entries[i].lock);
	}
}

static const char* rpc_uac_reg_info_doc[2] = {
	"Return the details of registration for a particular record.",
	0
};

static void rpc_uac_reg_info(rpc_t* rpc, void* ctx)
{
	reg_uac_t *reg = NULL;
	str attr = {0};
	str val = {0};
	int ret;

	if(_reg_htable==NULL)
	{
		rpc->fault(ctx, 500, "Not enabled");
		return;
	}

	if(rpc->scan(ctx, "S.S", &attr, &val)<2)
	{
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}
	if(attr.len<=0 || attr.s==NULL || val.len<=0 || val.s==NULL)
	{
		LM_ERR("bad parameter values\n");
		rpc->fault(ctx, 400, "Invalid Parameter Values");
		return;
	}

	ret = reg_ht_get_byfilter(&reg, &attr, &val);
	if (ret == 0) {
		rpc->fault(ctx, 404, "Record not found");
		return;
	} else if (ret < 0) {
		rpc->fault(ctx, 400, "Unsupported filter attribute");
		return;
	}

	rpc_uac_reg_add_node_helper(rpc, ctx, reg, time(NULL));
	lock_release(reg->lock);
	return;
}


static void rpc_uac_reg_update_flag(rpc_t* rpc, void* ctx, int mode, int fval)
{
	reg_uac_t *reg = NULL;
	str attr = {0};
	str val = {0};
	int ret;

	if(_reg_htable==NULL)
	{
		rpc->fault(ctx, 500, "Not enabled");
		return;
	}

	if(rpc->scan(ctx, "S.S", &attr, &val)<2)
	{
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}
	if(attr.len<=0 || attr.s==NULL || val.len<=0 || val.s==NULL)
	{
		LM_ERR("bad parameter values\n");
		rpc->fault(ctx, 400, "Invalid Parameter Values");
		return;
	}

	ret = reg_ht_get_byfilter(&reg, &attr, &val);
	if (ret == 0) {
		rpc->fault(ctx, 404, "Record not found");
		return;
	} else if (ret < 0) {
		rpc->fault(ctx, 400, "Unsupported filter attribute");
		return;
	}

	if(mode==1) {
		reg->flags |= fval;
	} else {
		reg->flags &= ~fval;
	}
	reg->timer_expires = time(NULL) + 1;

	lock_release(reg->lock);
	return;
}

static const char* rpc_uac_reg_enable_doc[2] = {
	"Enable registration for a particular record.",
	0
};

static void rpc_uac_reg_enable(rpc_t* rpc, void* ctx)
{
	rpc_uac_reg_update_flag(rpc, ctx, 0, UAC_REG_DISABLED);
	counter_add(regdisabled, -1);
}

static const char* rpc_uac_reg_disable_doc[2] = {
	"Disable registration for a particular record.",
	0
};

static void rpc_uac_reg_disable(rpc_t* rpc, void* ctx)
{
	rpc_uac_reg_update_flag(rpc, ctx, 1, UAC_REG_DISABLED);
	counter_inc(regdisabled);
}

static const char* rpc_uac_reg_reload_doc[2] = {
	"Reload records from database.",
	0
};

static void rpc_uac_reg_reload(rpc_t* rpc, void* ctx)
{
	int ret;
	if(uac_reg_ht_shift()<0) {
		rpc->fault(ctx, 500, "Failed to shift records - check log messages");
		return;
	}
	lock_get(_reg_htable_gc_lock);
	ret =  uac_reg_load_db();
	lock_release(_reg_htable_gc_lock);
	if(ret<0) {
		rpc->fault(ctx, 500, "Failed to reload records - check log messages");
		return;
	}
}

static const char* rpc_uac_reg_refresh_doc[2] = {
	"Refresh a record from database.",
	0
};

static void rpc_uac_reg_refresh(rpc_t* rpc, void* ctx)
{
	int ret;
	str l_uuid;

	if(rpc->scan(ctx, "S", &l_uuid)<1)
	{
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}

	ret =  uac_reg_db_refresh(&l_uuid);
	if(ret==0) {
		rpc->fault(ctx, 404, "Record not found");
		return;
	} else if(ret<0) {
		rpc->fault(ctx, 500, "Failed to refresh record - check log messages");
		return;
	}
}

static const char* rpc_uac_reg_remove_doc[2] = {
	"Remove a record from memory.",
	0
};

static void rpc_uac_reg_remove(rpc_t* rpc, void* ctx)
{
	int ret;
	str l_uuid;
	reg_uac_t *reg;

	if(rpc->scan(ctx, "S", &l_uuid)<1)
	{
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}
	reg = reg_ht_get_byuuid(&l_uuid);
	if (!reg) {
		rpc->fault(ctx, 404, "Record not found");
		return;
	}

	ret = reg_ht_rm(reg);
	if(ret<0) {
		rpc->fault(ctx, 500, "Failed to remove record - check log messages");
		return;
	}
}

static const char* rpc_uac_reg_add_doc[2] = {
	"Add a record to memory.",
	0
};

static void rpc_uac_reg_add(rpc_t* rpc, void* ctx)
{
	int ret;
	reg_uac_t reg;
	reg_uac_t *cur_reg;

	if(rpc->scan(ctx, "SSSSSSSSSSdddSS",
				&reg.l_uuid,
				&reg.l_username,
				&reg.l_domain,
				&reg.r_username,
				&reg.r_domain,
				&reg.realm,
				&reg.auth_username,
				&reg.auth_password,
				&reg.auth_ha1,
				&reg.auth_proxy,
				&reg.expires,
				&reg.flags,
				&reg.reg_delay,
				&reg.socket,
				&reg.contact_addr
			)<1)
	{
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}

	if(reg.auth_password.len==1 && reg.auth_password.s[0] == '.') {
		reg.auth_password.s = NULL;
		reg.auth_password.len = 0;
	}

	if(reg.auth_ha1.len==1 && reg.auth_ha1.s[0] == '.') {
		reg.auth_ha1.s = NULL;
		reg.auth_ha1.len = 0;
	}

	if(reg.contact_addr.len==1 && reg.contact_addr.s[0] == '.') {
		reg.contact_addr = reg_contact_addr;
	}

	if(uac_reg_check_password(&reg) < 0) {
		rpc->fault(ctx, 500, "Failed to add record - invalid password or ha1");
		return;
	}

	cur_reg = reg_ht_get_byuuid(&reg.l_uuid);
	if (cur_reg) {
		lock_release(cur_reg->lock);
		rpc->fault(ctx, 409, "uuid already exists");
		return;
	}

	ret = reg_ht_add(&reg);
	if(ret<0) {
		rpc->fault(ctx, 500, "Failed to add record - check log messages");
		return;
	}
}


static const char* rpc_uac_reg_active_doc[2] = {
	"Set remote registration active or inactive for all records.",
	0
};

static void rpc_uac_reg_active(rpc_t* rpc, void* ctx)
{
	int omode;
	int nmode;
	void* th;

	if(reg_active==NULL) {
		rpc->fault(ctx, 500, "Not initialized");
		return;
	}
	if(rpc->scan(ctx, "d", &nmode)<1) {
		LM_ERR("missing parameter");
		rpc->fault(ctx, 500, "Missing parameter");
		return;
	}
	omode = *reg_active;
	*reg_active = (nmode)?1:0;

	/* add entry node */
	if (rpc->add(ctx, "{", &th) < 0) {
		rpc->fault(ctx, 500, "Internal error creating rpc struct");
		return;
	}
	if(rpc->struct_add(th, "dd", "omode", omode, "nmode", nmode)<0) {
		rpc->fault(ctx, 500, "Internal error creating response");
		return;
	}
}

static const char* rpc_uac_reg_unregister_doc[2] = {
	"Send a register request with expires 0.",
	0
};


static void rpc_uac_reg_unregister(rpc_t* rpc, void* ctx)
{
	reg_uac_t *reg = NULL;
	str attr = {0};
	str val = {0};
	int ret;

	if(_reg_htable==NULL) {
		rpc->fault(ctx, 500, "Not enabled");
		return;
	}

	if(rpc->scan(ctx, "S.S", &attr, &val)<2) {
		rpc->fault(ctx, 400, "Invalid Parameters");
		return;
	}
	if(attr.len<=0 || attr.s==NULL || val.len<=0 || val.s==NULL) {
		LM_ERR("bad parameter values\n");
		rpc->fault(ctx, 400, "Invalid Parameter Values");
		return;
	}

	ret = reg_ht_get_byfilter(&reg, &attr, &val);
	if (ret == 0) {
		rpc->fault(ctx, 404, "Record not found");
		return;
	} else if (ret < 0) {
		rpc->fault(ctx, 400, "Unsupported filter attribute");
		return;
	}

	reg->expires = 0;
	uac_reg_send(reg, time(NULL));

	lock_release(reg->lock);
	return;
}

rpc_export_t uac_reg_rpc[] = {
	{"uac.reg_dump", rpc_uac_reg_dump, rpc_uac_reg_dump_doc, RET_ARRAY},
	{"uac.reg_info", rpc_uac_reg_info, rpc_uac_reg_info_doc, 0},
	{"uac.reg_enable",  rpc_uac_reg_enable,  rpc_uac_reg_enable_doc,  0},
	{"uac.reg_disable", rpc_uac_reg_disable, rpc_uac_reg_disable_doc, 0},
	{"uac.reg_reload",  rpc_uac_reg_reload,  rpc_uac_reg_reload_doc,  0},
	{"uac.reg_refresh", rpc_uac_reg_refresh, rpc_uac_reg_refresh_doc, 0},
	{"uac.reg_remove", rpc_uac_reg_remove, rpc_uac_reg_remove_doc, 0},
	{"uac.reg_add", rpc_uac_reg_add, rpc_uac_reg_add_doc, 0},
	{"uac.reg_active", rpc_uac_reg_active, rpc_uac_reg_active_doc, 0},
	{"uac.reg_unregister", rpc_uac_reg_unregister, rpc_uac_reg_unregister_doc, 0},
	{0, 0, 0, 0}
};

int uac_reg_init_rpc(void)
{
	if (rpc_register_array(uac_reg_rpc)!=0)
	{
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}