src/modules/usrloc/ul_rpc.c
99b02d1e
 /*
  * usrloc module
  *
  * Copyright (C) 2009 Daniel-Constantin Mierla (asipto.com).
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
cf83221d
 #include "../../core/ip_addr.h"
 #include "../../core/dprint.h"
 #include "../../core/dset.h"
0076124b
 #include "../../core/utils/sruid.h"
99b02d1e
 
 #include "ul_rpc.h"
 #include "dlist.h"
 #include "ucontact.h"
 #include "udomain.h"
10d6b2b7
 #include "usrloc_mod.h"
22076189
 #include "utime.h"
99b02d1e
 
c57575c5
 /*! CSEQ nr used */
 #define RPC_UL_CSEQ 1
 /*! call-id used for ul_add and ul_rm_contact */
90ab9e4f
 static str rpc_ul_cid = str_init("dfjrewr12386fd6-343@kamailio.rpc");
c57575c5
 /*! path used for ul_add and ul_rm_contact */
 static str rpc_ul_path = str_init("dummypath");
 /*! user agent used for ul_add */
90ab9e4f
 static str rpc_ul_ua  = str_init(NAME " SIP Router - RPC Server");
c57575c5
 
 extern sruid_t _ul_sruid;
 
99b02d1e
 static const char* ul_rpc_dump_doc[2] = {
 	"Dump user location tables",
 	0
 };
 
22076189
 
 int rpc_dump_contact(rpc_t* rpc, void* ctx, void *ih, ucontact_t* c)
 {
 	void* vh;
 	str empty_str = {"[not set]", 9};
 	str state_str = {"[not set]", 9};
 	str socket_str = {"[not set]", 9};
 	time_t t;
 
9bfc37fc
 	t = time(0);
22076189
 	if(rpc->struct_add(ih, "{", "Contact", &vh)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error creating contact struct");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Address", &c->c)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding addr");
 		return -1;
 	}
 	if (c->expires == 0) { if(rpc->struct_add(vh, "s", "Expires", "permanent")<0)
90ab9e4f
 		{
22076189
 			rpc->fault(ctx, 500, "Internal error adding expire");
 			return -1;
90ab9e4f
 		}
22076189
 	} else if (c->expires == UL_EXPIRED_TIME) {
 		if(rpc->struct_add(vh, "s", "Expires", "deleted")<0)
 		{
 			rpc->fault(ctx, 500, "Internal error adding expire");
 			return -1;
 		}
 	} else if (t > c->expires) {
 		if(rpc->struct_add(vh, "s", "Expires", "expired")<0)
 		{
 			rpc->fault(ctx, 500, "Internal error adding expire");
 			return -1;
 		}
 	} else {
 		if(rpc->struct_add(vh, "d", "Expires", (int)(c->expires - t))<0)
 		{
 			rpc->fault(ctx, 500, "Internal error adding expire");
 			return -1;
 		}
 	}
 	if (c->state == CS_NEW) {
 		state_str.s = "CS_NEW";
 		state_str.len = 6;
 	} else if (c->state == CS_SYNC) {
 		state_str.s = "CS_SYNC";
 		state_str.len = 7;
 	} else if (c->state== CS_DIRTY) {
 		state_str.s = "CS_DIRTY";
 		state_str.len = 8;
 	} else {
 		state_str.s = "CS_UNKNOWN";
 		state_str.len = 10;
 	}
 	if(c->sock)
 	{
 		socket_str.s = c->sock->sock_str.s;
 		socket_str.len = c->sock->sock_str.len;
 	}
c454e4b0
 	if(rpc->struct_add(vh, "f", "Q", q2double(c->q))<0)
22076189
 	{
 		rpc->fault(ctx, 500, "Internal error adding q");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Call-ID", &c->callid)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding callid");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "d", "CSeq", c->cseq)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding cseq");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "User-Agent",
90ab9e4f
 				(c->user_agent.len)?&c->user_agent: &empty_str)<0)
22076189
 	{
 		rpc->fault(ctx, 500, "Internal error adding user-agent");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Received",
90ab9e4f
 				(c->received.len)?&c->received: &empty_str)<0)
22076189
 	{
 		rpc->fault(ctx, 500, "Internal error adding received");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Path",
90ab9e4f
 				(c->path.len)?&c->path: &empty_str)<0)
22076189
 	{
 		rpc->fault(ctx, 500, "Internal error adding path");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "State", &state_str)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding state");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "d", "Flags", c->flags)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding flags");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "d", "CFlags", c->cflags)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding cflags");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Socket", &socket_str)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding socket");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "d", "Methods", c->methods)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding methods");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Ruid", (c->ruid.len)?&c->ruid: &empty_str)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding ruid");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "S", "Instance",
90ab9e4f
 				(c->instance.len)?&c->instance: &empty_str)<0)
22076189
 	{
 		rpc->fault(ctx, 500, "Internal error adding instance");
 		return -1;
 	}
 	if(rpc->struct_add(vh, "d", "Reg-Id", c->reg_id)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding reg_id");
 		return -1;
 	}
90ab9e4f
 	if(rpc->struct_add(vh, "d", "Server-Id", c->server_id)<0)
a57465ff
 	{
 		rpc->fault(ctx, 500, "Internal error adding server_id");
 		return -1;
 	}
90ab9e4f
 	if(rpc->struct_add(vh, "d", "Tcpconn-Id", c->tcpconn_id)<0)
a57465ff
 	{
 		rpc->fault(ctx, 500, "Internal error adding tcpconn_id");
 		return -1;
 	}
90ab9e4f
 	if(rpc->struct_add(vh, "d", "Keepalive", c->keepalive)<0)
a57465ff
 	{
 		rpc->fault(ctx, 500, "Internal error adding keepalive");
 		return -1;
 	}
22076189
 	if(rpc->struct_add(vh, "d", "Last-Keepalive", (int)c->last_keepalive)<0)
 	{
a57465ff
 		rpc->fault(ctx, 500, "Internal error adding last_keepalive");
22076189
 		return -1;
 	}
4652a116
 	if(rpc->struct_add(vh, "d", "KA-Roundtrip", (int)c->ka_roundtrip)<0)
 	{
 		rpc->fault(ctx, 500, "Internal error adding keepalive roundtrip");
 		return -1;
 	}
22076189
 	if(rpc->struct_add(vh, "d", "Last-Modified", (int)c->last_modified)<0)
 	{
a57465ff
 		rpc->fault(ctx, 500, "Internal error adding last_modified");
22076189
 		return -1;
 	}
 	return 0;
 }
 
99b02d1e
 static void ul_rpc_dump(rpc_t* rpc, void* ctx)
 {
 	struct urecord* r;
 	dlist_t* dl;
 	udomain_t* dom;
 	str brief = {0, 0};
 	int summary = 0;
 	ucontact_t* c;
 	void* th;
247e9bf2
 	void* dah;
 	void* dh;
99b02d1e
 	void* ah;
e0bcdcdd
 	void* bh;
99b02d1e
 	void* ih;
 	void* sh;
 	int max, n, i;
 
 	rpc->scan(ctx, "*S", &brief);
 
 	if(brief.len==5 && (strncmp(brief.s, "brief", 5)==0))
 		summary = 1;
90ab9e4f
 
247e9bf2
 	if (rpc->add(ctx, "{", &th) < 0)
 	{
 		rpc->fault(ctx, 500, "Internal error creating top rpc");
 		return;
 	}
 	if (rpc->struct_add(th, "[", "Domains", &dah) < 0)
 	{
 		rpc->fault(ctx, 500, "Internal error creating inner struct");
 		return;
 	}
 	
bef747a3
 	for( dl=_ksr_ul_root ; dl ; dl=dl->next ) {
99b02d1e
 		dom = dl->d;
247e9bf2
 
 		if (rpc->struct_add(dah, "{", "Domain", &dh) < 0)
99b02d1e
 		{
247e9bf2
 			rpc->fault(ctx, 500, "Internal error creating inner struct");
99b02d1e
 			return;
 		}
247e9bf2
 
 		if(rpc->struct_add(dh, "Sd[",
99b02d1e
 					"Domain",  &dl->name,
 					"Size",    (int)dom->size,
 					"AoRs",    &ah)<0)
 		{
 			rpc->fault(ctx, 500, "Internal error creating inner struct");
 			return;
 		}
 		for(i=0,n=0,max=0; i<dom->size; i++) {
 			lock_ulslot( dom, i);
 			n += dom->table[i].n;
 			if(max<dom->table[i].n)
 				max= dom->table[i].n;
 			for( r = dom->table[i].first ; r ; r=r->next ) {
 				if(summary==1)
 				{
 					if(rpc->struct_add(ah, "S",
90ab9e4f
 								"AoR", &r->aor)<0)
99b02d1e
 					{
dfcba5fb
 						unlock_ulslot( dom, i);
99b02d1e
 						rpc->fault(ctx, 500, "Internal error creating aor struct");
 						return;
 					}
 				} else {
e0bcdcdd
 					if(rpc->struct_add(ah, "{",
90ab9e4f
 								"Info", &bh)<0)
e0bcdcdd
 					{
 						unlock_ulslot( dom, i);
 						rpc->fault(ctx, 500, "Internal error creating aor struct");
 						return;
 					}
 					if(rpc->struct_add(bh, "Sd[",
90ab9e4f
 								"AoR", &r->aor,
 								"HashID", r->aorhash,
 								"Contacts", &ih)<0)
99b02d1e
 					{
 						unlock_ulslot( dom, i);
 						rpc->fault(ctx, 500, "Internal error creating aor struct");
 						return;
 					}
 					for( c=r->contacts ; c ; c=c->next)
 					{
22076189
 						if (rpc_dump_contact(rpc, ctx, ih, c) == -1) {
 							unlock_ulslot(dom, i);
99b02d1e
 							return;
 						}
 					}
 				}
 			}
 
 			unlock_ulslot( dom, i);
 		}
 
 		/* extra attributes node */
247e9bf2
 		if(rpc->struct_add(dh, "{", "Stats",    &sh)<0)
99b02d1e
 		{
 			rpc->fault(ctx, 500, "Internal error creating stats struct");
 			return;
 		}
 		if(rpc->struct_add(sh, "dd",
90ab9e4f
 					"Records", n,
 					"Max-Slots", max)<0)
99b02d1e
 		{
 			rpc->fault(ctx, 500, "Internal error adding stats");
 			return;
 		}
 	}
 }
 
22076189
 static const char* ul_rpc_lookup_doc[2] = {
 	"Lookup one AOR in the usrloc location table",
 	0
 };
 
 /*!
  * \brief Search a domain in the global domain list
  * \param table domain (table) name
  * \return pointer to domain if found, 0 if not found
  */
 static inline udomain_t* rpc_find_domain(str* table)
 {
 	dlist_t* dom;
 
bef747a3
 	for( dom=_ksr_ul_root ; dom ; dom=dom->next ) {
22076189
 		if ((dom->name.len == table->len) &&
90ab9e4f
 				!memcmp(dom->name.s, table->s, table->len))
22076189
 			return dom->d;
 	}
 	return 0;
 }
 
 /*!
  * \brief Convert address of record
  *
  * Convert an address of record string to lower case, and truncate
  * it when use_domain is not set.
  * \param aor address of record
  * \return 0 on success, -1 on error
  */
 static inline int rpc_fix_aor(str *aor)
 {
 	char *p;
 
 	p = memchr( aor->s, '@', aor->len);
ae2f1fdc
 	if (ul_use_domain) {
22076189
 		if (p==NULL)
 			return -1;
 	} else {
 		if (p)
 			aor->len = p - aor->s;
 	}
e319a22f
 	if(!get_aor_case_sensitive())
 		strlower(aor);
22076189
 
 	return 0;
 }
 
 /*!
  * \brief Dumps the contacts of an AOR
  * \param rpc	Handle to RPC structure
  * \param ctx not used
  * \note expects 2 arguments: the table name and the AOR
  */
 static void ul_rpc_lookup(rpc_t* rpc, void* ctx)
 {
 	udomain_t* dom;
 	str table = {0, 0};
 	str aor = {0, 0};
 	void* th;
e0bcdcdd
 	void* ih;
22076189
 	urecord_t *rec;
 	ucontact_t* con;
 	int ret;
 	int rpl_tree;
 
 	if (rpc->scan(ctx, "S", &table) != 1) {
 		rpc->fault(ctx, 500, "Not enough parameters (table and AOR to lookup)");
 		return;
 	}
 	if (rpc->scan(ctx, "S", &aor) != 1) {
 		rpc->fault(ctx, 500, "Not enough parameters (table and AOR to lookup)");
 		return;
 	}
 
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
 		rpc->fault(ctx, 500, "Domain not found");
 		return;
 	}
 
 	/* process the aor */
 	if ( rpc_fix_aor(&aor) != 0 ) {
 		rpc->fault(ctx, 500, "Domain missing in AOR");
 		return;
 	}
 
 	lock_udomain( dom, &aor);
 
 	ret = get_urecord( dom, &aor, &rec);
 	if (ret == 1) {
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 500, "AOR not found in location table");
 		return;
 	}
 
ae2f1fdc
 	ul_get_act_time();
22076189
 	rpl_tree = 0;
 
 	if (rpc->add(ctx, "{", &th) < 0)
 	{
b97bb772
 		release_urecord(rec);
e0bcdcdd
 		unlock_udomain(dom, &aor);
22076189
 		rpc->fault(ctx, 500, "Internal error creating outer rpc");
 		return;
 	}
e0bcdcdd
 	if(rpc->struct_add(th, "S[",
90ab9e4f
 				"AoR", &aor,
 				"Contacts", &ih)<0)
e0bcdcdd
 	{
b97bb772
 		release_urecord(rec);
e0bcdcdd
 		unlock_udomain(dom, &aor);
 		rpc->fault(ctx, 500, "Internal error creating aor struct");
 		return;
 	}
22076189
 
 	/* We have contacts, list them */
 	for( con=rec->contacts ; con ; con=con->next) {
ae2f1fdc
 		if (VALID_CONTACT(con, ul_act_time)) {
22076189
 			rpl_tree++;
e0bcdcdd
 			if (rpc_dump_contact(rpc, ctx, ih, con) == -1) {
b97bb772
 				release_urecord(rec);
22076189
 				unlock_udomain(dom, &aor);
 				return;
 			}
 		}
 	}
b97bb772
 	release_urecord(rec);
22076189
 	unlock_udomain( dom, &aor);
 
 	if (rpl_tree==0) {
 		rpc->fault(ctx, 500, "AOR has no contacts");
 		return;
 	}
 	return;
 }
 
c57575c5
 static void ul_rpc_rm_aor(rpc_t* rpc, void* ctx)
 {
 	udomain_t* dom;
 	str table = {0, 0};
 	str aor = {0, 0};
 
 	if (rpc->scan(ctx, "SS", &table, &aor) != 2) {
 		rpc->fault(ctx, 500, "Not enough parameters (table and AOR to lookup)");
 		return;
 	}
 
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
 		rpc->fault(ctx, 500, "Domain not found");
 		return;
 	}
 
 	/* process the aor */
 	if ( rpc_fix_aor(&aor) != 0 ) {
 		rpc->fault(ctx, 500, "Domain missing in AOR");
 		return;
 	}
 
 	lock_udomain( dom, &aor);
 	if (delete_urecord( dom, &aor, 0) < 0) {
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 500, "Failed to delete AOR");
 		return;
 	}
 
 	unlock_udomain( dom, &aor);
 	return;
 }
 
 static const char* ul_rpc_rm_aor_doc[2] = {
 	"Delete a address of record including its contacts",
 	0
 };
 
 static void ul_rpc_rm_contact(rpc_t* rpc, void* ctx)
 {
 	udomain_t* dom;
 	str table = {0, 0};
 	str aor = {0, 0};
 	str contact = {0, 0};
 	urecord_t *rec;
 	ucontact_t* con;
 	int ret;
 
 	if (rpc->scan(ctx, "SSS", &table, &aor, &contact) != 3) {
 		rpc->fault(ctx, 500, "Not enough parameters (table, AOR and contact)");
 		return;
 	}
 
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
 		rpc->fault(ctx, 500, "Domain not found");
 		return;
 	}
 
 	/* process the aor */
 	if ( rpc_fix_aor(&aor) != 0 ) {
 		rpc->fault(ctx, 500, "Domain missing in AOR");
 		return;
 	}
 
 	lock_udomain( dom, &aor);
 
 	ret = get_urecord( dom, &aor, &rec);
 	if (ret == 1) {
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 404, "AOR not found");
 		return;
 	}
 
 	ret = get_ucontact( rec, &contact, &rpc_ul_cid, &rpc_ul_path, RPC_UL_CSEQ+1, &con);
 	if (ret < 0) {
b97bb772
 		release_urecord(rec);
c57575c5
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 500, "Internal error (can't get contact)");
 		return;
 	}
 	if (ret > 0) {
b97bb772
 		release_urecord(rec);
c57575c5
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 404, "Contact not found");
 		return;
 	}
 
 	if (delete_ucontact(rec, con) < 0) {
b97bb772
 		release_urecord(rec);
c57575c5
 		unlock_udomain( dom, &aor);
 		rpc->fault(ctx, 500, "Internal error (can't delete contact)");
 		return;
 	}
 
 	release_urecord(rec);
 	unlock_udomain( dom, &aor);
 	return;
 }
 
 static const char* ul_rpc_rm_contact_doc[2] = {
 	"Delete a contact from an AOR record",
 	0
 };
 
 static void ul_rpc_flush(rpc_t* rpc, void* ctx)
 {
3cbae684
 	if(synchronize_all_udomains(0, 1) != 0) {
 		rpc->fault(ctx, 500, "Operation failed");
 	}
c57575c5
 	return;
 }
 
 static const char* ul_rpc_flush_doc[2] = {
 	"Flush the usrloc memory cache to DB",
 	0
 };
 
dc8644a0
 /**
  * test if a rpc parameter is set
  * - used for optional parameters which can be given '.' or '0' value
  *   in order to indicate is not intended to be set
  * - return: 1 if parameter is set; 0 if parameter is not set
  */
 int ul_rpc_is_param_set(str *p)
 {
 	if(p==NULL || p->len==0 || p->s==NULL)
 		return 0;
 	if(p->len>1)
 		return 1;
 	if((strncmp(p->s, ".", 1)==0) || (strncmp(p->s, "0", 1)==0))
 		return 0;
 	return 1;
 }
 
c57575c5
 /*!
  * \brief Add a new contact for an address of record
  * \note Expects 9 parameters: table name, AOR, contact, expires, Q,
ad0e5daa
  * path, flags, cflags, methods
c57575c5
  */
 static void ul_rpc_add(rpc_t* rpc, void* ctx)
 {
 	str table = {0, 0};
 	str aor = {0, 0};
 	str contact = {0, 0};
ad0e5daa
 	str path = {0, 0};
9ede3f27
 	str received = {0, 0};
dc8644a0
 	str socket = {0, 0};
ad0e5daa
 	str temp = {0, 0};
c57575c5
 	double dtemp;
 	ucontact_info_t ci;
 	urecord_t* r;
 	ucontact_t* c;
 	udomain_t *dom;
 	int ret;
 
e0b82270
 	memset(&ci, 0, sizeof(ucontact_info_t));
c57575c5
 
2c622627
 	ret = rpc->scan(ctx, "SSSdfSuuu*SS", &table, &aor, &contact, &ci.expires,
9ede3f27
 			&dtemp, &path, &ci.flags, &ci.cflags, &ci.methods, &received,
e0b82270
 			&socket);
 	if (ret < 9) {
7ffa6a4f
 		LM_ERR("not enough parameters - read so far: %d\n", ret);
0954a069
 		rpc->fault(ctx, 500, "Not enough parameters or wrong format");
 		return;
 	}
dc8644a0
 	if(ul_rpc_is_param_set(&path)) {
ad0e5daa
 		ci.path = &path;
dc8644a0
 	} else {
 		LM_DBG("path == 0 -> unset\n");
ad0e5daa
 	}
e0b82270
 	if(ret>9) {
 		/* received parameter */
9ede3f27
 		if(ul_rpc_is_param_set(&received)) {
 			ci.received.s = received.s;
 			ci.received.len = received.len;
e0b82270
 		}
 	}
 	if(ret>10) {
 		/* socket parameter */
 		if(ul_rpc_is_param_set(&socket)) {
 			ci.sock = lookup_local_socket(&socket);
 		}
 	}
0954a069
 	LM_DBG("ret: %d table:%.*s aor:%.*s contact:%.*s expires:%d"
 			" dtemp:%f path:%.*s flags:%d bflags:%d methods:%d\n",
90ab9e4f
 			ret, table.len, table.s, aor.len, aor.s, contact.len, contact.s,
0954a069
 			(int) ci.expires, dtemp, (ci.path)?ci.path->len:0,
 			(ci.path && ci.path->s)?ci.path->s:"", ci.flags, ci.cflags,
 			(int) ci.methods);
c57575c5
 	ci.q = double2q(dtemp);
ad0e5daa
 	temp.s = q2str(ci.q, (unsigned int*)&temp.len);
 	LM_DBG("q:%.*s\n", temp.len, temp.s);
c57575c5
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
 		rpc->fault(ctx, 500, "Domain not found");
 		return;
 	}
 
 	/* process the aor */
e0b82270
 	if (rpc_fix_aor(&aor) != 0 ) {
c57575c5
 		rpc->fault(ctx, 500, "Domain missing in AOR");
 		return;
 	}
 
fd30ccf4
 	if(sruid_next_safe(&_ul_sruid)<0)
c57575c5
 	{
 		rpc->fault(ctx, 500, "Can't obtain next uid");
 		return;
 	}
 	ci.ruid = _ul_sruid.uid;
c681aded
 	ci.server_id = server_id;
c57575c5
 
e0b82270
 	lock_udomain(dom, &aor);
c57575c5
 
e0b82270
 	ret = get_urecord(dom, &aor, &r);
c57575c5
 	if(ret==1) {
e0b82270
 		if (insert_urecord(dom, &aor, &r) < 0)
c57575c5
 		{
e0b82270
 			unlock_udomain(dom, &aor);
c57575c5
 			rpc->fault(ctx, 500, "Can't insert record");
 			return;
 		}
 		c = 0;
 	} else {
0954a069
 		if (get_ucontact( r, &contact, &rpc_ul_cid, &rpc_ul_path,
 					RPC_UL_CSEQ+1, &c) < 0)
c57575c5
 		{
e0b82270
 			unlock_udomain(dom, &aor);
c57575c5
 			rpc->fault(ctx, 500, "Can't get record");
 			return;
 		}
 	}
 
ae2f1fdc
 	ul_get_act_time();
c57575c5
 
 	ci.callid = &rpc_ul_cid;
 	ci.user_agent = &rpc_ul_ua;
 	ci.cseq = RPC_UL_CSEQ;
 	/* 0 expires means permanent contact */
 	if (ci.expires!=0)
ae2f1fdc
 		ci.expires += ul_act_time;
c57575c5
 
 	if (c) {
 		if (update_ucontact( r, c, &ci) < 0)
 		{
 			release_urecord(r);
e0b82270
 			unlock_udomain(dom, &aor);
c57575c5
 			rpc->fault(ctx, 500, "Can't update contact");
 			return;
 		}
 	} else {
e0b82270
 		if ( insert_ucontact(r, &contact, &ci, &c) < 0 )
c57575c5
 		{
 			release_urecord(r);
e0b82270
 			unlock_udomain(dom, &aor);
c57575c5
 			rpc->fault(ctx, 500, "Can't insert contact");
 			return;
 		}
 	}
 
 	release_urecord(r);
e0b82270
 	unlock_udomain(dom, &aor);
c57575c5
 	return;
 }
 
 static const char* ul_rpc_add_doc[2] = {
 	"Add a new contact for an address of record",
 	0
 };
 
d0d29807
 #define QUERY_LEN 256
 
45227df1
 static void ul_rpc_db_users(rpc_t* rpc, void* ctx)
d0d29807
 {
90ab9e4f
 	str table = {0, 0};
 	char query[QUERY_LEN];
 	str query_str;
00ce103d
 	db1_res_t* res = NULL;
 	int count = 0;
d0d29807
 
ae2f1fdc
 	if (ul_db_mode == NO_DB) {
90ab9e4f
 		rpc->fault(ctx, 500, "Command is not supported in db_mode=0");
 		return;
 	}
d0d29807
 
90ab9e4f
 	if (rpc->scan(ctx, "S", &table) != 1) {
 		rpc->fault(ctx, 500, "Not enough parameters (table to lookup)");
 		return;
 	}
d0d29807
 
ae2f1fdc
 	if (ul_user_col.len + ul_domain_col.len + table.len + 32 > QUERY_LEN) {
90ab9e4f
 		rpc->fault(ctx, 500, "Too long database query");
 		return;
 	}
 
 	if (!DB_CAPABILITY(ul_dbf, DB_CAP_RAW_QUERY)) {
 		rpc->fault(ctx, 500, "Database does not support raw queries");
 		return;
 	}
 	if (ul_dbf.use_table(ul_dbh, &table) < 0) {
 		rpc->fault(ctx, 500, "Failed to use table");
 		return;
 	}
 
 	memset(query, 0, QUERY_LEN);
 	query_str.len = snprintf(query, QUERY_LEN,
 			"SELECT COUNT(DISTINCT %.*s, %.*s) FROM %.*s WHERE (UNIX_TIMESTAMP(expires) = 0) OR (expires > NOW())",
ae2f1fdc
 			ul_user_col.len, ul_user_col.s,
 			ul_domain_col.len, ul_domain_col.s,
90ab9e4f
 			table.len, table.s);
 	query_str.s = query;
00ce103d
 	if (ul_dbf.raw_query(ul_dbh, &query_str, &res) < 0 || res==NULL) {
90ab9e4f
 		rpc->fault(ctx, 500, "Failed to query AoR count");
 		return;
 	}
00ce103d
 	if (RES_ROW_N(res) > 0) {
 		count = (int)VAL_INT(ROW_VALUES(RES_ROWS(res)));
 	}
90ab9e4f
 	ul_dbf.free_result(ul_dbh, res);
d0d29807
 
90ab9e4f
 	rpc->add(ctx, "d", count);
d0d29807
 }
 
45227df1
 static const char* ul_rpc_db_users_doc[2] = {
7cb062ce
 	"Tell number of different unexpired users (AoRs) in database table (db_mode!=0 only)",
d0d29807
 	0
 };
 
45227df1
 static void ul_rpc_db_contacts(rpc_t* rpc, void* ctx)
d0d29807
 {
90ab9e4f
 	str table = {0, 0};
 	char query[QUERY_LEN];
 	str query_str;
00ce103d
 	db1_res_t* res = NULL;
 	int count = 0;
d0d29807
 
ae2f1fdc
 	if (ul_db_mode == NO_DB) {
90ab9e4f
 		rpc->fault(ctx, 500, "Command is not supported in db_mode=0");
 		return;
 	}
d0d29807
 
90ab9e4f
 	if (rpc->scan(ctx, "S", &table) != 1) {
 		rpc->fault(ctx, 500, "Not enough parameters (table to lookup)");
 		return;
 	}
d0d29807
 
90ab9e4f
 	if (table.len + 22 > QUERY_LEN) {
 		rpc->fault(ctx, 500, "Too long database query");
 		return;
 	}
 
 	if (!DB_CAPABILITY(ul_dbf, DB_CAP_RAW_QUERY)) {
 		rpc->fault(ctx, 500, "Database does not support raw queries");
 		return;
 	}
 	if (ul_dbf.use_table(ul_dbh, &table) < 0) {
 		rpc->fault(ctx, 500, "Failed to use table");
 		return;
 	}
d0d29807
 
90ab9e4f
 	memset(query, 0, QUERY_LEN);
 	query_str.len = snprintf(query, QUERY_LEN, "SELECT COUNT(*) FROM %.*s WHERE (UNIX_TIMESTAMP(expires) = 0) OR (expires > NOW())",
 			table.len, table.s);
 	query_str.s = query;
00ce103d
 	if (ul_dbf.raw_query(ul_dbh, &query_str, &res) < 0 || res==NULL) {
90ab9e4f
 		rpc->fault(ctx, 500, "Failed to query contact count");
 		return;
 	}
d0d29807
 
00ce103d
 	if (RES_ROW_N(res) > 0) {
 		count = (int)VAL_INT(ROW_VALUES(RES_ROWS(res)));
 	}
90ab9e4f
 	ul_dbf.free_result(ul_dbh, res);
 
 	rpc->add(ctx, "d", count);
d0d29807
 }
 
45227df1
 static const char* ul_rpc_db_contacts_doc[2] = {
7cb062ce
 	"Tell number of unexpired contacts in database table (db_mode=3 only)",
 	0
 };
 
 static void ul_rpc_db_expired_contacts(rpc_t* rpc, void* ctx)
 {
90ab9e4f
 	str table = {0, 0};
 	char query[QUERY_LEN];
 	str query_str;
00ce103d
 	db1_res_t* res = NULL;
 	int count = 0;
7cb062ce
 
ae2f1fdc
 	if (ul_db_mode == NO_DB) {
90ab9e4f
 		rpc->fault(ctx, 500, "Command is not supported in db_mode=0");
 		return;
 	}
7cb062ce
 
90ab9e4f
 	if (rpc->scan(ctx, "S", &table) != 1) {
 		rpc->fault(ctx, 500, "Not enough parameters (table to lookup)");
 		return;
 	}
7cb062ce
 
90ab9e4f
 	if (table.len + 22 > QUERY_LEN) {
 		rpc->fault(ctx, 500, "Too long database query");
 		return;
 	}
 
 	if (!DB_CAPABILITY(ul_dbf, DB_CAP_RAW_QUERY)) {
 		rpc->fault(ctx, 500, "Database does not support raw queries");
 		return;
 	}
 	if (ul_dbf.use_table(ul_dbh, &table) < 0) {
 		rpc->fault(ctx, 500, "Failed to use table");
 		return;
 	}
 
 	memset(query, 0, QUERY_LEN);
 	query_str.len = snprintf(query, QUERY_LEN, "SELECT COUNT(*) FROM %.*s WHERE (UNIX_TIMESTAMP(expires) > 0) AND (expires <= NOW())",
 			table.len, table.s);
 	query_str.s = query;
00ce103d
 	if (ul_dbf.raw_query(ul_dbh, &query_str, &res) < 0 || res==NULL) {
90ab9e4f
 		rpc->fault(ctx, 500, "Failed to query contact count");
 		return;
 	}
7cb062ce
 
00ce103d
 	if (RES_ROW_N(res) > 0) {
 		count = (int)VAL_INT(ROW_VALUES(RES_ROWS(res)));
 	}
90ab9e4f
 	ul_dbf.free_result(ul_dbh, res);
7cb062ce
 
90ab9e4f
 	rpc->add(ctx, "d", count);
7cb062ce
 }
 
 static const char* ul_rpc_db_expired_contacts_doc[2] = {
 	"Tell number of expired contacts in database table (db_mode=3 only)",
d0d29807
 	0
 };
 
99b02d1e
 rpc_export_t ul_rpc[] = {
 	{"ul.dump",   ul_rpc_dump,   ul_rpc_dump_doc,   0},
22076189
 	{"ul.lookup",   ul_rpc_lookup,   ul_rpc_lookup_doc,   0},
c57575c5
 	{"ul.rm", ul_rpc_rm_aor, ul_rpc_rm_aor_doc, 0},
 	{"ul.rm_contact", ul_rpc_rm_contact, ul_rpc_rm_contact_doc, 0},
 	{"ul.flush", ul_rpc_flush, ul_rpc_flush_doc, 0},
 	{"ul.add", ul_rpc_add, ul_rpc_add_doc, 0},
45227df1
 	{"ul.db_users", ul_rpc_db_users, ul_rpc_db_users_doc, 0},
 	{"ul.db_contacts", ul_rpc_db_contacts, ul_rpc_db_contacts_doc, 0},
7cb062ce
 	{"ul.db_expired_contacts", ul_rpc_db_expired_contacts, ul_rpc_db_expired_contacts_doc, 0},
99b02d1e
 	{0, 0, 0, 0}
 };