/*
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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 core :: DNS resolver
 * \ingroup core
 * Module: \ref core
 */


#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <string.h>

/*
 * Older glibc < 2.25 does not include T_OPT in nameser_compat.h yet.
 * On alpine linux musl library it is also not defined. There is no
 * musl feature test macro, so we look for glibc instead.
 */
#if(defined __GLIBC__ && __GLIBC__ == 2 && __GLIBC_MINOR__ < 25) \
		|| !defined __GLIBC__
#ifndef T_OPT
#define T_OPT ns_t_opt
#endif
#endif

#include "resolve.h"
#include "compiler_opt.h"
#include "dprint.h"
#include "mem/mem.h"
#include "ip_addr.h"
#include "error.h"
#include "globals.h" /* tcp_disable, tls_disable a.s.o */
#include "cfg_core.h"
#include "socket_info.h"

#ifdef USE_DNS_CACHE
#include "dns_cache.h"
#endif

#define KSR_IPADDR_LIST_SIZE 6
static ip_addr_t _ksr_ipaddr_list[KSR_IPADDR_LIST_SIZE];
static int _ksr_ipaddr_list_idx = 0;

static ip_addr_t *get_next_ipaddr_buf(void)
{
	ip_addr_t *ipb;

	ipb = &_ksr_ipaddr_list[_ksr_ipaddr_list_idx];
	_ksr_ipaddr_list_idx = (_ksr_ipaddr_list_idx + 1) % KSR_IPADDR_LIST_SIZE;

	return ipb;
}

/* counters framework */
struct dns_counters_h dns_cnts_h;
counter_def_t dns_cnt_defs[] = {
		{&dns_cnts_h.failed_dns_req, "failed_dns_request", 0, 0, 0,
				"incremented each time a DNS request has failed."},
		{&dns_cnts_h.slow_dns_req, "slow_dns_request", 0, 0, 0,
				"incremented each time a DNS request took longer than "
				"dns_slow_query_ms."},
		{0, 0, 0, 0, 0, 0}};


#ifdef USE_NAPTR
static int naptr_proto_pref[PROTO_LAST + 1];
#endif
static int srv_proto_pref[PROTO_LAST + 1];

#ifdef USE_NAPTR
static void init_naptr_proto_prefs()
{
	int ignore_rfc, udp, tcp, tls, sctp;

	if((PROTO_UDP > PROTO_LAST) || (PROTO_TCP > PROTO_LAST)
			|| (PROTO_TLS > PROTO_LAST) || (PROTO_SCTP > PROTO_LAST)) {
		BUG("init_naptr_proto_prefs: array too small \n");
		return;
	}

	ignore_rfc = cfg_get(core, core_cfg, dns_naptr_ignore_rfc);
	udp = cfg_get(core, core_cfg, dns_udp_pref);
	tcp = cfg_get(core, core_cfg, dns_tcp_pref);
	tls = cfg_get(core, core_cfg, dns_tls_pref);
	sctp = cfg_get(core, core_cfg, dns_sctp_pref);

	/* Old implementation ignored the Order field in the NAPTR RR and
	 * thus violated a MUST in RFC 2915. Currently still the default. */
	if(ignore_rfc) {
		naptr_proto_pref[PROTO_UDP] = udp;
		naptr_proto_pref[PROTO_TCP] = tcp;
		naptr_proto_pref[PROTO_TLS] = tls;
		naptr_proto_pref[PROTO_SCTP] = sctp;
	} else {
		/* If value is less than 0, proto is disabled, otherwise
		 * ignored. */
		naptr_proto_pref[PROTO_UDP] = udp < 0 ? udp : 1;
		naptr_proto_pref[PROTO_TCP] = tcp < 0 ? tcp : 1;
		naptr_proto_pref[PROTO_TLS] = tls < 0 ? tls : 1;
		naptr_proto_pref[PROTO_SCTP] = sctp < 0 ? sctp : 1;
	}
}

#endif /* USE_NAPTR */

static void init_srv_proto_prefs()
{
	if((PROTO_UDP > PROTO_LAST) || (PROTO_TCP > PROTO_LAST)
			|| (PROTO_TLS > PROTO_LAST) || (PROTO_SCTP > PROTO_LAST)) {
		BUG("init_srv_proto_prefs: array too small \n");
		return;
	}

	srv_proto_pref[PROTO_UDP] = cfg_get(core, core_cfg, dns_udp_pref);
	srv_proto_pref[PROTO_TCP] = cfg_get(core, core_cfg, dns_tcp_pref);
	srv_proto_pref[PROTO_TLS] = cfg_get(core, core_cfg, dns_tls_pref);
	srv_proto_pref[PROTO_SCTP] = cfg_get(core, core_cfg, dns_sctp_pref);
}

#ifdef DNS_WATCHDOG_SUPPORT
static on_resolv_reinit on_resolv_reinit_cb = NULL;

/* register the callback function */
int register_resolv_reinit_cb(on_resolv_reinit cb)
{
	if(on_resolv_reinit_cb) {
		LM_ERR("callback function has been already registered\n");
		return -1;
	}
	on_resolv_reinit_cb = cb;
	return 0;
}
#endif

/* counter init function
  must be called before fork
*/
static int stat_init(void)
{
	if(counter_register_array("dns", dns_cnt_defs) < 0)
		goto error;
	return 0;
error:
	return -1;
}

/** init. the resolver
 * params: retr_time  - time before retransmitting (must be >0)
 *         retr_no    - retransmissions number
 *         servers_no - how many dns servers will be used
 *                      (from the one listed in /etc/resolv.conf)
 *         search     - if 0 the search list in /etc/resolv.conf will
 *                      be ignored (HINT: even if you don't have a
 *                      search list in resolv.conf, it's still better
 *                      to set search to 0, because an empty seachlist
 *                      means in fact search "" => it takes more time)
 * If any of the parameters <0, the default (system specific) value
 * will be used. See also resolv.conf(5).
 * returns: 0 on success, -1 on error
 */
static int _resolv_init(void)
{
	dns_func.sr_res_init();
#ifdef HAVE_RESOLV_RES
	if(cfg_get(core, core_cfg, dns_retr_time) > 0)
		_res.retrans = cfg_get(core, core_cfg, dns_retr_time);
	if(cfg_get(core, core_cfg, dns_retr_no) > 0)
		_res.retry = cfg_get(core, core_cfg, dns_retr_no);
	if((cfg_get(core, core_cfg, dns_servers_no) >= 0)
			&& (cfg_get(core, core_cfg, dns_servers_no) < _res.nscount))
		_res.nscount = cfg_get(core, core_cfg, dns_servers_no);
	if(cfg_get(core, core_cfg, dns_search_list) == 0)
		_res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
#else
#warning "no resolv timeout support"
	LM_WARN("no resolv options support - resolv options will be ignored\n");
#endif
	return 0;
}

/** wrapper function to initialize the resolver at startup */
int resolv_init(void)
{
	int res = -1;
	_resolv_init();

	reinit_proto_prefs(NULL, NULL);
	/* init counter API only at startup
	 * This function must be called before DNS cache init method (if available)
	 */
	res = stat_init();
	return res;
}

/** wrapper function to reinitialize the resolver
 * This function must be called by each child process whenever
 * a resolver option changes
 */
void resolv_reinit(str *gname, str *name)
{
	_resolv_init();

#ifdef DNS_WATCHDOG_SUPPORT
	if(on_resolv_reinit_cb)
		on_resolv_reinit_cb(name);
#endif
	LM_DBG("DNS resolver has been reinitialized\n");
}

/** fixup function for dns_reinit variable
 * (resets the variable to 0)
 */
int dns_reinit_fixup(void *handle, str *gname, str *name, void **val)
{
	*val = (void *)(long)0;
	return 0;
}

/** wrapper function to recalculate the naptr and srv protocol preferences */
void reinit_proto_prefs(str *gname, str *name)
{
#ifdef USE_NAPTR
	init_naptr_proto_prefs();
#endif
	init_srv_proto_prefs();
}

/** fixup function for dns_try_ipv6
 * verifies that Kamailio really listens on an ipv6 interface
 */
int dns_try_ipv6_fixup(void *handle, str *gname, str *name, void **val)
{
	if((int)(long)(*val) && !(socket_types & SOCKET_T_IPV6)) {
		LM_ERR("SER does not listen on any ipv6 interface, "
			   "there is no point in resolving ipv6 addresses\n");
		return -1;
	}
	return 0;
}

/**  skips over a domain name in a dns message
 *  (it can be  a sequence of labels ending in \0, a pointer or
 *   a sequence of labels ending in a pointer -- see rfc1035
 *   returns pointer after the domain name or null on error*/
unsigned char *dns_skipname(unsigned char *p, unsigned char *end)
{
	while(p < end) {
		/* check if \0 (root label length) */
		if(*p == 0) {
			p += 1;
			break;
		}
		/* check if we found a pointer */
		if(((*p) & 0xc0) == 0xc0) {
			/* if pointer skip over it (2 bytes) & we found the end */
			p += 2;
			break;
		}
		/* normal label */
		p += *p + 1;
	}
	return (p > end) ? 0 : p;
}


/** parses the srv record into a srv_rdata structure
 *   msg   - pointer to the dns message
 *   end   - pointer to the end of the message
 *   eor   - pointer to the end of the record/rdata
 *   rdata - pointer  to the rdata part of the srv answer
 * returns 0 on error, or a dyn. alloc'ed srv_rdata structure
 *
 * SRV rdata format:
 *            111111
 *  0123456789012345
 * +----------------+
 * |     priority   |
 * |----------------|
 * |     weight     |
 * |----------------|
 * |   port number  |
 * |----------------|
 * |                |
 * ~      name      ~
 * |                |
 * +----------------+
 */
struct srv_rdata *dns_srv_parser(unsigned char *msg, unsigned char *end,
		unsigned char *eor, unsigned char *rdata)
{
	struct srv_rdata *srv;
	unsigned short priority;
	unsigned short weight;
	unsigned short port;
	int len;
	char name[MAX_DNS_NAME];

	srv = 0;
	if((rdata + 6 + 1) > eor)
		goto error;

	memcpy((void *)&priority, rdata, 2);
	memcpy((void *)&weight, rdata + 2, 2);
	memcpy((void *)&port, rdata + 4, 2);
	rdata += 6;
	if(dn_expand(msg, end, rdata, name, MAX_DNS_NAME - 1) < 0)
		goto error;
	len = strlen(name);
	if(len > 255)
		goto error;
	/* alloc enought space for the struct + null terminated name */
	srv = pkg_malloc(sizeof(struct srv_rdata) - 1 + len + 1);
	if(srv == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	srv->priority = ntohs(priority);
	srv->weight = ntohs(weight);
	srv->port = ntohs(port);
	srv->name_len = len;
	memcpy(srv->name, name, srv->name_len);
	srv->name[srv->name_len] = 0;

	return srv;
error:
	if(srv)
		pkg_free(srv);
	return 0;
}


/** parses the naptr record into a naptr_rdata structure
 *   msg   - pointer to the dns message
 *   end   - pointer to the end of the message
 *   eor   - pointer to the end of the record/rdata
 *   rdata - pointer  to the rdata part of the naptr answer
 * returns 0 on error, or a dyn. alloc'ed naptr_rdata structure */

/* NAPTR rdata format:
 *            111111
 *  0123456789012345
 * +----------------+
 * |      order     |
 * |----------------|
 * |   preference   |
 * |----------------|
 * ~     flags      ~
 * |   (string)     |
 * |----------------|
 * ~    services    ~
 * |   (string)     |
 * |----------------|
 * ~    regexp      ~
 * |   (string)     |
 * |----------------|
 * ~  replacement   ~
   |    (name)      |
 * +----------------+
 */
struct naptr_rdata *dns_naptr_parser(unsigned char *msg, unsigned char *end,
		unsigned char *eor, unsigned char *rdata)
{
	struct naptr_rdata *naptr;
	unsigned char *flags;
	unsigned char *services;
	unsigned char *regexp;
	unsigned short order;
	unsigned short pref;
	unsigned char flags_len;
	unsigned char services_len;
	unsigned char regexp_len;
	int len;
	char repl[MAX_DNS_NAME];

	naptr = 0;
	if((rdata + 7 + 1) > eor)
		goto error;

	memcpy((void *)&order, rdata, 2);
	memcpy((void *)&pref, rdata + 2, 2);
	flags_len = rdata[4];
	if((rdata + 7 + 1 + flags_len) > eor)
		goto error;
	flags = rdata + 5;
	services_len = rdata[5 + flags_len];
	if((rdata + 7 + 1 + flags_len + services_len) > eor)
		goto error;
	services = rdata + 6 + flags_len;
	regexp_len = rdata[6 + flags_len + services_len];
	if((rdata + 7 + 1 + flags_len + services_len + regexp_len) > eor)
		goto error;
	regexp = rdata + 7 + flags_len + services_len;
	rdata = rdata + 7 + flags_len + services_len + regexp_len;
	if(dn_expand(msg, end, rdata, repl, MAX_DNS_NAME - 1) == -1)
		goto error;
	len = strlen(repl);
	if(len > 255)
		goto error;
	naptr = pkg_malloc(sizeof(struct naptr_rdata) + flags_len + services_len
					   + regexp_len + len + 1 - 1);
	if(naptr == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	naptr->skip_record = 0;
	naptr->order = ntohs(order);
	naptr->pref = ntohs(pref);

	naptr->flags = &naptr->str_table[0];
	naptr->flags_len = flags_len;
	memcpy(naptr->flags, flags, naptr->flags_len);
	naptr->services = &naptr->str_table[flags_len];
	naptr->services_len = services_len;
	memcpy(naptr->services, services, naptr->services_len);
	naptr->regexp = &naptr->str_table[flags_len + services_len];
	naptr->regexp_len = regexp_len;
	memcpy(naptr->regexp, regexp, naptr->regexp_len);
	naptr->repl = &naptr->str_table[flags_len + services_len + regexp_len];
	naptr->repl_len = len;
	memcpy(naptr->repl, repl, len);
	naptr->repl[len] = 0; /* null term. */

	return naptr;
error:
	if(naptr)
		pkg_free(naptr);
	return 0;
}


/** parses a CNAME record into a cname_rdata structure */
struct cname_rdata *dns_cname_parser(
		unsigned char *msg, unsigned char *end, unsigned char *rdata)
{
	struct cname_rdata *cname;
	int len;
	char name[MAX_DNS_NAME];

	cname = 0;
	if(dn_expand(msg, end, rdata, name, MAX_DNS_NAME - 1) == -1)
		goto error;
	len = strlen(name);
	if(len > 255)
		goto error;
	/* alloc sizeof struct + space for the null terminated name */
	cname = pkg_malloc(sizeof(struct cname_rdata) - 1 + len + 1);
	if(cname == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	cname->name_len = len;
	memcpy(cname->name, name, cname->name_len);
	cname->name[cname->name_len] = 0;
	return cname;
error:
	if(cname)
		pkg_free(cname);
	return 0;
}


/** parses an A record rdata into an a_rdata structure
 * returns 0 on error or a dyn. alloc'ed a_rdata struct
 */
struct a_rdata *dns_a_parser(unsigned char *rdata, unsigned char *eor)
{
	struct a_rdata *a;

	if(rdata + 4 > eor)
		goto error;
	a = (struct a_rdata *)pkg_malloc(sizeof(struct a_rdata));
	if(a == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	memcpy(a->ip, rdata, 4);
	return a;
error:
	return 0;
}


/** parses an AAAA (ipv6) record rdata into an aaaa_rdata structure
 * returns 0 on error or a dyn. alloc'ed aaaa_rdata struct */
struct aaaa_rdata *dns_aaaa_parser(unsigned char *rdata, unsigned char *eor)
{
	struct aaaa_rdata *aaaa;

	if(rdata + 16 > eor)
		goto error;
	aaaa = (struct aaaa_rdata *)pkg_malloc(sizeof(struct aaaa_rdata));
	if(aaaa == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	memcpy(aaaa->ip6, rdata, 16);
	return aaaa;
error:
	return 0;
}


/** parses a TXT record into a txt_rdata structure.
 *   @param msg   - pointer to the dns message
 *   @param end   - pointer to the end of the record (rdata end)
 *   @param rdata - pointer  to the rdata part of the txt answer
 * returns 0 on error, or a dyn. alloc'ed txt_rdata structure */
/*  TXT rdata format:
 *
 * one or several character strings:
 *  01234567
 * +--------------------+
 * | len    | string   / ...
 * |------------------+
 */
static struct txt_rdata *dns_txt_parser(
		unsigned char *msg, unsigned char *end, unsigned char *rdata)
{
	struct txt_rdata *txt;
	int len, n, i;
	int str_size;
	unsigned char *p;
	unsigned char *st;

	txt = 0;
	if(unlikely((rdata + 1) > end))
		goto error;
	n = 0;
	str_size = 0;
	/* count the number of strings */
	p = rdata;
	do {
		len = *p;
		p += len + 1;
		str_size += len + 1; /* 1 for the term. 0 */
		if(unlikely(p > end))
			goto error;
		n++;
	} while(p < end);
	/* alloc sizeof struct + space for the dns_cstr array + space for
	   the strings */
	txt = pkg_malloc(sizeof(struct txt_rdata)
					 + (n - 1) * sizeof(struct dns_cstr) + str_size);
	if(unlikely(txt == 0)) {
		PKG_MEM_ERROR;
		goto error;
	}
	/* string table */
	st = (unsigned char *)txt + sizeof(struct txt_rdata)
		 + (n - 1) * sizeof(struct dns_cstr);
	txt->cstr_no = n;
	txt->tslen = str_size;
	/* fill the structure */
	p = rdata;
	for(i = 0; i < n; i++) {
		len = *p;
		memcpy(st, p + 1, len);
		st[len] = 0;
		txt->txt[i].cstr_len = len;
		txt->txt[i].cstr = (char *)st;
		st += len + 1;
		p += len + 1;
	}
	return txt;
error:
	if(txt)
		pkg_free(txt);
	return 0;
}


/** parses an EBL record into a txt_rdata structure.
 *   @param msg   - pointer to the dns message
 *   @param end   - pointer to the end of the dns message
 *   @param eor   - pointer to the end of the record (rdata end)
 *   @param rdata - pointer  to the rdata part of the txt answer
 * returns 0 on error, or a dyn. alloc'ed txt_rdata structure */
/*  EBL rdata format:
 *  (see http://tools.ietf.org/html/draft-ietf-enum-branch-location-record-03)
 * one or several character strings:
 *  01234567
 * +---------+
 * | position|
 * +-----------+
 * / separator /
 * +-----------+
 * /   apex    /
 * +----------+
 *
 * where separator is a character string ( 8 bit len, followed by len chars)
 * and apex is a domain-name.
 */
static struct ebl_rdata *dns_ebl_parser(unsigned char *msg, unsigned char *end,
		unsigned char *eor, unsigned char *rdata)
{
	struct ebl_rdata *ebl;
	int sep_len;
	int apex_len;
	char apex[MAX_DNS_NAME];

	ebl = 0;
	/* check if len is at least 4 chars (minimum possible):
	     pos (1 byte) +  sep. (min 1 byte) + apex (min. 2 bytes)
	   and also check if rdata+1 (pos) + 1 (sep. len) + sep_len + 1 is ok*/
	if(unlikely(((rdata + 4) > eor) || ((rdata + 1 + 1 + rdata[1] + 2) > eor)))
		goto error;
	sep_len = rdata[1];
	if(unlikely(dn_expand(msg, end, rdata + 1 + 1 + sep_len, apex,
						MAX_DNS_NAME - 1)
				== -1))
		goto error;
	apex_len = strlen(apex);
	/* alloc sizeof struct + space for the 2 null-terminated strings */
	ebl = pkg_malloc(sizeof(struct ebl_rdata) - 1 + sep_len + 1 + apex_len + 1);
	if(ebl == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	ebl->position = rdata[0];
	ebl->separator = &ebl->str_table[0];
	ebl->apex = ebl->separator + sep_len + 1;
	ebl->separator_len = sep_len;
	ebl->apex_len = apex_len;
	memcpy(ebl->separator, rdata + 2, sep_len);
	ebl->separator[sep_len] = 0;
	memcpy(ebl->apex, apex, apex_len);
	ebl->apex[apex_len] = 0;

	return ebl;
error:
	if(ebl)
		pkg_free(ebl);
	return 0;
}


/** parses a PTR record into a ptr_rdata structure */
struct ptr_rdata *dns_ptr_parser(
		unsigned char *msg, unsigned char *end, unsigned char *rdata)
{
	struct ptr_rdata *pname;
	int len;
	char name[MAX_DNS_NAME];

	pname = 0;
	if(dn_expand(msg, end, rdata, name, MAX_DNS_NAME - 1) == -1)
		goto error;
	len = strlen(name);
	if(len > 255)
		goto error;
	/* alloc sizeof struct + space for the null terminated name */
	pname = pkg_malloc(sizeof(struct ptr_rdata) - 1 + len + 1);
	if(pname == 0) {
		PKG_MEM_ERROR;
		goto error;
	}
	pname->ptrdname_len = len;
	memcpy(pname->ptrdname, name, pname->ptrdname_len);
	pname->ptrdname[pname->ptrdname_len] = 0;
	return pname;
error:
	if(pname)
		pkg_free(pname);
	return 0;
}


/** frees completely a struct rdata list */
void free_rdata_list(struct rdata *head)
{
	struct rdata *l;
	struct rdata *next_l;
	l = head;
	while(l != 0) {
		next_l = l->next;
		/* free the parsed rdata*/
		if(l->rdata)
			pkg_free(l->rdata);
		pkg_free(l);
		l = next_l;
	}
}

#ifdef HAVE_RESOLV_RES
/** checks whether supplied name exists in the resolver search list
 * returns 1 if found
 *         0 if not found
 */
int match_search_list(const struct __res_state *res, char *name)
{
	int i;
	for(i = 0; (i < MAXDNSRCH) && (res->dnsrch[i]); i++) {
		if(strcasecmp(name, res->dnsrch[i]) == 0)
			return 1;
	}
	return 0;
}
#endif

#define SR_DNS_MAX_QNO 10
#define SR_DNS_MAX_ANO 100

/** gets the DNS records for name:type
 * returns a dyn. alloc'ed struct rdata linked list with the parsed responses
 * or 0 on error
 * see rfc1035 for the query/response format */
struct rdata *get_record(char *name, int type, int flags)
{
	int size;
	int skip;
	unsigned short qno, answers_no, r;
	int i;
	static union dns_query buff;
	unsigned char *p;
	unsigned char *end;
	unsigned char *rd_end;
	static char rec_name[MAX_DNS_NAME]; /* placeholder for the record name */
	int rec_name_len;
	unsigned short rtype, class, rdlength;
	unsigned int ttl;
	struct rdata *head;
	struct rdata **crt;
	struct rdata **last;
	struct rdata *rd;
	struct srv_rdata *srv_rd;
	struct srv_rdata *crt_srv;
	int search_list_used;
	int name_len;
	struct rdata *fullname_rd;
	char c;
	struct timeval start, stop;
	int slow_query_ms = cfg_get(core, core_cfg, dns_slow_query_ms);

	name_len = strlen(name);

	for(i = 0; i < name_len; i++) {
		c = name[i];
		if(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))
				|| ((c >= '0') && (c <= '9')) || (name[i] == '.')
				|| (name[i] == '-') || (name[i] == '_'))
			continue;
		LM_DBG("'%s' is not domain name\n", name);
		return 0;
	}

	if(cfg_get(core, core_cfg, dns_search_list) == 0) {
		search_list_used = 0;
		name_len = 0;
	} else {
		search_list_used = 1;
	}
	fullname_rd = 0;

	if(slow_query_ms > 0)
		gettimeofday(&start, NULL);

	size = dns_func.sr_res_search(name, C_IN, type, buff.buff, sizeof(buff));

	if(slow_query_ms > 0) {
		gettimeofday(&stop, NULL);
		int latency_ms = (stop.tv_sec - start.tv_sec) * 1000
						 + (stop.tv_usec - start.tv_usec) / 1000;
		if(slow_query_ms < latency_ms) {
			LOG(cfg_get(core, core_cfg, latency_log),
					"res_search[%d][%s]elapsed[%dms]\n", type, name,
					latency_ms);
			counter_inc(dns_cnts_h.slow_dns_req);
		}
	}

	if(unlikely(size < 0)) {
		LM_DBG("lookup(%s, %d) failed\n", name, type);
		goto not_found;
	} else if(unlikely(size > sizeof(buff)))
		size = sizeof(buff);
	head = rd = 0;
	last = crt = &head;

	p = buff.buff + DNS_HDR_SIZE;
	end = buff.buff + size;
	if(unlikely(p >= end))
		goto error_boundary;
	qno = ntohs((unsigned short)buff.hdr.qdcount);

	if(qno != 1) {
		/* usually the query is with a single domain name */
		LM_INFO("dns questions number is: %u\n", (uint32_t)qno);
		if(qno > SR_DNS_MAX_QNO) {
			/* early safe check against broken results */
			LM_ERR("dns questions number is too high: %u\n", (uint32_t)qno);
			goto error;
		}
	}
	for(r = 0; r < qno; r++) {
		/* skip the name of the question */
		if(unlikely((p = dns_skipname(p, end)) == 0)) {
			LM_ERR("skipname==0\n");
			goto error;
		}
		p += 2 + 2; /* skip QCODE & QCLASS */
#if 0
		for (;(p<end && (*p)); p++);
		p+=1+2+2; /* skip the ending  '\0, QCODE and QCLASS */
#endif
		if(unlikely(p > end)) {
			LM_ERR("p>=end\n");
			goto error;
		}
	}
	answers_no = ntohs((unsigned short)buff.hdr.ancount);
	if(answers_no > SR_DNS_MAX_ANO) {
		/* early safety check on answers number */
		LM_ERR("dns answers number is too high: %u\n", (uint32_t)answers_no);
		goto error;
	}
again:
	for(r = 0; (r < answers_no) && (p < end); r++) {
#if 0
		/*  ignore it the default domain name */
		if ((p=dns_skipname(p, end))==0) {
			LM_ERR("get_record: skip_name=0 (#2)\n");
			goto error;
		}
#else
		if(unlikely((skip = dn_expand(
							 buff.buff, end, p, rec_name, MAX_DNS_NAME - 1))
					== -1)) {
			LM_ERR("dn_expand(rec_name) failed\n");
			goto error;
		}
#endif
		p += skip;
		rec_name_len = strlen(rec_name);
		if(unlikely(rec_name_len > MAX_DNS_NAME - 2)) {
			LM_ERR("dn_expand(rec_name): name too long (%d)\n", rec_name_len);
			goto error;
		}
		/* check if enough space is left for type, class, ttl & size */
		if(unlikely((p + 2 + 2 + 4 + 2) > end))
			goto error_boundary;
		/* get type */
		memcpy((void *)&rtype, (void *)p, 2);
		rtype = ntohs(rtype);
		p += 2;
		/* get  class */
		memcpy((void *)&class, (void *)p, 2);
		class = ntohs(class);
		p += 2;
		/* get ttl*/
		memcpy((void *)&ttl, (void *)p, 4);
		ttl = ntohl(ttl);
		p += 4;
		/* get size */
		memcpy((void *)&rdlength, (void *)p, 2);
		rdlength = ntohs(rdlength);
		p += 2;
		rd_end = p + rdlength;
		if(unlikely((rd_end) > end))
			goto error_boundary;
		if((flags & RES_ONLY_TYPE) && (rtype != type)) {
			/* skip */
			p = rd_end;
			continue;
		}
		/* expand the "type" record  (rdata)*/

		rd = (struct rdata *)pkg_malloc(
				sizeof(struct rdata) + rec_name_len + 1 - 1);
		if(rd == 0) {
			PKG_MEM_ERROR;
			goto error;
		}
		rd->type = rtype;
		rd->pclass = class;
		rd->ttl = ttl;
		rd->next = 0;
		memcpy(rd->name, rec_name, rec_name_len);
		rd->name[rec_name_len] = 0;
		rd->name_len = rec_name_len;
		/* check if full name matches */
		if((search_list_used == 1) && (fullname_rd == 0)
				&& (rec_name_len >= name_len)
				&& (strncasecmp(rec_name, name, name_len) == 0)) {
			/* now we have record whose name is the same (up-to the
			 * name_len with the searched one):
			 * if the length is the same - we found full match, no fake
			 *  cname needed, just clear the flag
			 * if the length of the name differs - it has matched using
			 *  search list remember the rd, so we can create fake CNAME
			 *  record when all answers are used and no better match found
			 */
			if(rec_name_len == name_len)
				search_list_used = 0;
			/* this is safe.... here was rec_name_len > name_len */
			else if(rec_name[name_len] == '.') {
#ifdef HAVE_RESOLV_RES
				if((cfg_get(core, core_cfg, dns_search_fmatch) == 0)
						|| (match_search_list(&_res, rec_name + name_len + 1)
								!= 0))
#endif
					fullname_rd = rd;
			}
		}
		switch(rtype) {
			case T_SRV:
				srv_rd = dns_srv_parser(buff.buff, end, rd_end, p);
				rd->rdata = (void *)srv_rd;
				if(unlikely(srv_rd == 0))
					goto error_parse;

				/* insert sorted into the list */
				for(crt = &head; *crt; crt = &((*crt)->next)) {
					if((*crt)->type != T_SRV)
						continue;
					crt_srv = (struct srv_rdata *)(*crt)->rdata;
					if((srv_rd->priority < crt_srv->priority)
							|| ((srv_rd->priority == crt_srv->priority)
									&& (srv_rd->weight > crt_srv->weight))) {
						/* insert here */
						goto skip;
					}
				}
				last = &(rd->next); /*end of for => this will be the last
									element*/
			skip:
				/* insert here */
				rd->next = *crt;
				*crt = rd;
				break;
			case T_A:
				rd->rdata = (void *)dns_a_parser(p, rd_end);
				if(unlikely(rd->rdata == 0))
					goto error_parse;
				*last = rd; /* last points to the last "next" or the list
							 	head*/
				last = &(rd->next);
				break;
			case T_AAAA:
				rd->rdata = (void *)dns_aaaa_parser(p, rd_end);
				if(unlikely(rd->rdata == 0))
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_CNAME:
				rd->rdata = (void *)dns_cname_parser(buff.buff, end, p);
				if(unlikely(rd->rdata == 0))
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_NAPTR:
				rd->rdata = (void *)dns_naptr_parser(buff.buff, end, rd_end, p);
				if(unlikely(rd->rdata == 0))
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_TXT:
				rd->rdata = dns_txt_parser(buff.buff, rd_end, p);
				if(rd->rdata == 0)
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_EBL:
				rd->rdata = dns_ebl_parser(buff.buff, end, rd_end, p);
				if(rd->rdata == 0)
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_PTR:
				rd->rdata = (void *)dns_ptr_parser(buff.buff, end, p);
				if(unlikely(rd->rdata == 0))
					goto error_parse;
				*last = rd;
				last = &(rd->next);
				break;
			case T_OPT:
				/* skip DNS extensions, e.g. EDNS0 */
				rd->rdata = 0;
				*last = rd;
				last = &(rd->next);
				break;
			default:
				LM_ERR("unknown type %d\n", rtype);
				rd->rdata = 0;
				*last = rd;
				last = &(rd->next);
		}

		p += rdlength;
	}
	if(flags & RES_AR) {
		flags &= ~RES_AR;
		answers_no = ntohs((unsigned short)buff.hdr.nscount);
		LM_DBG("skipping %d NS (p=%p, end=%p)\n", answers_no, p, end);
		for(r = 0; (r < answers_no) && (p < end); r++) {
			/* skip over the ns records */
			if((p = dns_skipname(p, end)) == 0) {
				LM_ERR("skip_name=0 (#3)\n");
				goto error;
			}
			/* check if enough space is left for type, class, ttl & size */
			if(unlikely((p + 2 + 2 + 4 + 2) > end))
				goto error_boundary;
			memcpy((void *)&rdlength, (void *)(p + 2 + 2 + 4), 2);
			p += 2 + 2 + 4 + 2 + ntohs(rdlength);
		}
		answers_no = ntohs((unsigned short)buff.hdr.arcount);
		LM_DBG("parsing %d ARs (p=%p, end=%p)\n", answers_no, p, end);
		goto again; /* add also the additional records */
	}

	/* if the name was expanded using DNS search list
	 * create fake CNAME record to convert the short name
	 * (queried) to long name (answered)
	 */
	if((search_list_used == 1) && (fullname_rd != 0)) {
		rd = (struct rdata *)pkg_malloc(
				sizeof(struct rdata) + name_len + 1 - 1);
		if(unlikely(rd == 0)) {
			PKG_MEM_ERROR;
			goto error;
		}
		rd->type = T_CNAME;
		rd->pclass = fullname_rd->pclass;
		rd->ttl = fullname_rd->ttl;
		rd->next = head;
		memcpy(rd->name, name, name_len);
		rd->name[name_len] = 0;
		rd->name_len = name_len;
		/* alloc sizeof struct + space for the null terminated name */
		rd->rdata = (void *)pkg_malloc(
				sizeof(struct cname_rdata) - 1 + head->name_len + 1);
		if(unlikely(rd->rdata == 0)) {
			PKG_MEM_ERROR;
			goto error_rd;
		}
		((struct cname_rdata *)(rd->rdata))->name_len = fullname_rd->name_len;
		memcpy(((struct cname_rdata *)(rd->rdata))->name, fullname_rd->name,
				fullname_rd->name_len);
		((struct cname_rdata *)(rd->rdata))->name[head->name_len] = 0;
		head = rd;
	}

	return head;
error_boundary:
	LM_ERR("end of query buff reached\n");
	if(head)
		free_rdata_list(head);
	return 0;
error_parse:
	LM_ERR("rdata parse error (%s, %d), %p-%p"
		   " rtype=%d, class=%d, ttl=%d, rdlength=%d\n",
			name, type, p, end, rtype, class, ttl, rdlength);
error_rd:
	if(rd)
		pkg_free(rd); /* rd->rdata=0 & rd is not linked yet into
								   the list */
error:
	LM_ERR("get_record\n");
	if(head)
		free_rdata_list(head);
not_found:
	/* increment error counter */
	counter_inc(dns_cnts_h.failed_dns_req);
	return 0;
}

#ifdef USE_NAPTR

/* service matching constants, lowercase */
#define SIP_SCH 0x2b706973
#define SIPS_SCH 0x73706973
#define SIP_D2U 0x00753264
#define SIP_D2T 0x00743264
#define SIP_D2S 0x00733264
#define SIPS_D2T 0x7432642b


/** get protocol from a naptr rdata and check for validity
 * returns > 0 (PROTO_UDP, PROTO_TCP, PROTO_SCTP or PROTO_TLS)
 *         <=0  on error
 */
char naptr_get_sip_proto(struct naptr_rdata *n)
{
	unsigned int s;
	char proto;

	proto = -1;

	if((n->flags_len != 1) || ((*n->flags | 0x20) != 's'))
		return -1;
	if(n->regexp_len != 0)
		return -1;
	/* SIP+D2U, SIP+D2T, SIP+D2S, SIPS+D2T */
	if(n->services_len == 7) { /* SIP+D2X */
		s = n->services[0] + (n->services[1] << 8) + (n->services[2] << 16)
			+ (n->services[3] << 24);
		s |= 0x20202020;
		if(s == SIP_SCH) {
			s = n->services[4] + (n->services[5] << 8) + (n->services[6] << 16);
			s |= 0x00202020;
			switch(s) {
				case SIP_D2U:
					proto = PROTO_UDP;
					break;
				case SIP_D2T:
					proto = PROTO_TCP;
					break;
				case SIP_D2S:
					proto = PROTO_SCTP;
					break;
				default:
					return -1;
			}
		} else {
			return -1;
		}
	} else if(n->services_len == 8) { /*SIPS+D2T */
		s = n->services[0] + (n->services[1] << 8) + (n->services[2] << 16)
			+ (n->services[3] << 24);
		s |= 0x20202020;
		if(s == SIPS_SCH) {
			s = n->services[4] + (n->services[5] << 8) + (n->services[6] << 16)
				+ (n->services[7] << 24);
			s |= 0x20202020;
			if(s == SIPS_D2T) {
				proto = PROTO_TLS;
			}
		} else {
			return -1;
		}
	} else {
		return -1;
	}
	return proto;
}


inline static int naptr_proto_pref_score(char proto)
{
	if((proto >= PROTO_UDP) && (proto <= PROTO_LAST))
		return naptr_proto_pref[(int)proto];
	return 0;
}

inline static int srv_proto_pref_score(char proto)
{
	if((proto >= PROTO_UDP) && (proto <= PROTO_LAST))
		return srv_proto_pref[(int)proto];
	return 0;
}


/** returns true if we support the protocol */
int naptr_proto_supported(char proto)
{
	if(naptr_proto_pref_score(proto) < 0)
		return 0;
	switch(proto) {
		case PROTO_UDP:
			return 1;
#ifdef USE_TCP
		case PROTO_TCP:
			return !tcp_disable;
#ifdef USE_TLS
		case PROTO_TLS:
			return !tls_disable;
#endif /* USE_TLS */
#endif /* USE_TCP */
#ifdef USE_SCTP
		case PROTO_SCTP:
			return !sctp_disable;
#endif
	}
	return 0;
}


/** returns true if new_proto is preferred over old_proto */
int naptr_proto_preferred(char new_proto, char old_proto)
{
	return naptr_proto_pref_score(new_proto)
		   > naptr_proto_pref_score(old_proto);
}


/** choose between 2 naptr records, should take into account local
 * preferences too
 * returns 1 if the new record was selected, 0 otherwise */
int naptr_choose(struct naptr_rdata **crt, char *crt_proto,
		struct naptr_rdata *n, char n_proto)
{
	LM_DBG("o:%d w:%d p:%d, o:%d w:%d p:%d\n", *crt ? (int)(*crt)->order : -1,
			*crt ? (int)(*crt)->pref : -1, (int)*crt_proto, (int)n->order,
			(int)n->pref, (int)n_proto);
	if((*crt == 0)
			|| ((*crt_proto != n_proto)
					&& (naptr_proto_preferred(n_proto, *crt_proto))))
		goto change;
	if(!naptr_proto_preferred(*crt_proto, n_proto)
			&& ((n->order < (*crt)->order)
					|| ((n->order == (*crt)->order)
							&& (n->pref < (*crt)->pref)))) {
		goto change;
	}
	LM_DBG("no change\n");
	return 0;
change:
	LM_DBG("changed\n");
	*crt_proto = n_proto;
	*crt = n;
	return 1;
}
#endif /* USE_NAPTR */


/** internal sip srv resolver: resolves a host name trying:
 * - SRV lookup if the address is not an ip *port==0. The result of the SRV
 *   query will be used for an A/AAAA lookup.
 *  - normal A/AAAA lookup (either fallback from the above or if *port!=0
 *   and *proto!=0 or port==0 && proto==0)
 * when performing SRV lookup (*port==0) it will use *proto to look for
 * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
 * If zt is set, name will be assumed to be 0 terminated and some copy
 * operations will be avoided.
 * If is_srv is set it will assume name has the srv prefixes for sip already
 *  appended and it's already 0-term'ed; if not it will append them internally.
 * If ars !=0, it will first try to look through them and only if the SRV
 *   record is not found it will try doing a DNS query  (ars will not be
 *   freed, the caller should take care of them)
 * returns: hostent struct & *port filled with the port from the SRV record;
 *  0 on error
 */
struct hostent *srv_sip_resolvehost(str *name, int zt, unsigned short *port,
		char *proto, int is_srv, struct rdata *ars)
{
	struct hostent *he;
	struct ip_addr *ip;
	static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups and
	                                  null. term  strings */
	struct rdata *l;
	struct srv_rdata *srv;
	struct rdata *srv_head;
	char *srv_target;
	char srv_proto;

	/* init */
	srv_head = 0;
	srv_target = 0;
	if(name->len >= MAX_DNS_NAME) {
		LM_ERR("domain name too long\n");
		he = 0;
		goto end;
	}
	LM_DBG("%.*s:%d proto=%d\n", name->len, name->s, port ? (int)*port : -1,
			proto ? (int)*proto : -1);
	if(is_srv) {
		/* skip directly to srv resolving */
		srv_proto = (proto) ? *proto : 0;
		if(port)
			*port = (srv_proto == PROTO_TLS) ? SIPS_PORT : SIP_PORT;
		if(zt) {
			srv_target = name->s; /* name.s must be 0 terminated in
								  this case */
		} else {
			memcpy(tmp, name->s, name->len);
			tmp[name->len] = '\0';
			srv_target = tmp;
		}
		goto do_srv; /* skip to the actual srv query */
	}
	if(proto) { /* makes sure we have a protocol set*/
		if(*proto == 0)
			*proto = srv_proto = PROTO_UDP; /* default */
		else
			srv_proto = *proto;
	} else {
		srv_proto = PROTO_UDP;
	}
	/* try SRV if no port specified (draft-ietf-sip-srv-06) */
	if((port) && (*port == 0)) {
		*port = (srv_proto == PROTO_TLS) ? SIPS_PORT
										 : SIP_PORT; /* just in case we
														  don't find another */
		/* check if it's an ip address */
		if(((ip = str2ip(name)) != 0) || ((ip = str2ip6(name)) != 0)) {
			/* we are lucky, this is an ip address */
			he = ip_addr2he(name, ip);
			goto end;
		}
		if((name->len + SRV_MAX_PREFIX_LEN + 1) > MAX_DNS_NAME) {
			LM_WARN("domain name too long (%d), unable to perform SRV lookup\n",
					name->len);
		} else {

			switch(srv_proto) {
				case PROTO_UDP:
				case PROTO_TCP:
				case PROTO_TLS:
				case PROTO_SCTP:
					create_srv_name(srv_proto, name, tmp);
					break;
				default:
					LM_CRIT("unknown proto %d\n", srv_proto);
					he = 0;
					goto end;
			}
			srv_target = tmp;
		do_srv:
			/* try to find the SRV records inside previous ARs  first*/
			for(l = ars; l; l = l->next) {
				if(l->type != T_SRV)
					continue;
				srv = (struct srv_rdata *)l->rdata;
				if(srv == 0) {
					LM_CRIT("null rdata\n");
					/* cleanup on exit only */
					break;
				}
				he = resolvehost(srv->name);
				if(he != 0) {
					/* we found it*/
					LM_DBG("found SRV(%s) = %s:%d in AR\n", srv_target,
							srv->name, srv->port);
					if(port)
						*port = srv->port;
					/* cleanup on exit */
					goto end;
				}
			}
			srv_head = get_record(srv_target, T_SRV, RES_ONLY_TYPE);
			for(l = srv_head; l; l = l->next) {
				if(l->type != T_SRV)
					continue; /*should never happen*/
				srv = (struct srv_rdata *)l->rdata;
				if(srv == 0) {
					LM_CRIT("null rdata\n");
					/* cleanup on exit only */
					break;
				}
				he = resolvehost(srv->name);
				if(he != 0) {
					/* we found it*/
					LM_DBG("SRV(%s) = %s:%d\n", srv_target, srv->name,
							srv->port);
					if(port)
						*port = srv->port;
					/* cleanup on exit */
					goto end;
				}
			}
			if(is_srv) {
				/* if the name was already into SRV format it doesn't make
				 * any sense to fall back to A/AAAA */
				he = 0;
				goto end;
			}
			/* cleanup on exit */
			LM_DBG("no SRV record found for %.*s,"
				   " trying 'normal' lookup...\n",
					name->len, name->s);
		}
	}
	if(likely(!zt)) {
		memcpy(tmp, name->s, name->len);
		tmp[name->len] = '\0';
		he = resolvehost(tmp);
	} else {
		he = resolvehost(name->s);
	}
end:
	LM_DBG("returning %p (%.*s:%d proto=%d)\n", he, name->len, name->s,
			port ? (int)*port : -1, proto ? (int)*proto : -1);
	if(srv_head)
		free_rdata_list(srv_head);
	return he;
}


#ifdef USE_NAPTR


/** iterates over a naptr rr list, returning each time a "good" naptr record
 * is found.( srv type, no regex and a supported protocol)
 * params:
 *         naptr_head - naptr rr list head
 *         tried      - bitmap used to keep track of the already tried records
 *                      (no more than sizeof(tried)*8 valid records are
 *                      ever walked
 *         srv_name   - if successful, it will be set to the selected record
 *                      srv name (naptr repl.)
 *         proto      - if successful it will be set to the selected record
 *                      protocol
 * returns  0 if no more records found or a pointer to the selected record
 *  and sets  protocol and srv_name
 * WARNING: when calling first time make sure you run first
 *           naptr_iterate_init(&tried)
 */
struct rdata *naptr_sip_iterate(struct rdata *naptr_head, naptr_bmp_t *tried,
		str *srv_name, char *proto)
{
	int i, idx;
	struct rdata *l;
	struct rdata *l_saved;
	struct naptr_rdata *naptr;
	struct naptr_rdata *naptr_saved;
	char saved_proto;
	char naptr_proto;

	idx = 0;
	naptr_proto = PROTO_NONE;
	naptr_saved = 0;
	l_saved = 0;
	saved_proto = 0;
	i = 0;
	for(l = naptr_head; l && (i < MAX_NAPTR_RRS); l = l->next) {
		if(l->type != T_NAPTR)
			continue;
		naptr = (struct naptr_rdata *)l->rdata;
		if(naptr == 0) {
			LM_CRIT("null rdata\n");
			goto end;
		}
		/* check if valid and get proto */
		if((naptr_proto = naptr_get_sip_proto(naptr)) <= 0)
			continue;
		if(*tried & (1 << i)) {
			i++;
			continue; /* already tried */
		}
		LM_DBG("found a valid sip NAPTR rr %.*s, proto %d\n", naptr->repl_len,
				naptr->repl, (int)naptr_proto);
		if((naptr_proto_supported(naptr_proto))) {
			if(naptr_choose(&naptr_saved, &saved_proto, naptr, naptr_proto)) {
				idx = i;
				l_saved = l;
			}
		}
		i++;
	}
	if(naptr_saved) {
		/* found something */
		LM_DBG("choosed NAPTR rr %.*s, proto %d tried: 0x%x\n",
				naptr_saved->repl_len, naptr_saved->repl, (int)saved_proto,
				*tried);
		*tried |= 1 << idx;
		*proto = saved_proto;
		srv_name->s = naptr_saved->repl;
		srv_name->len = naptr_saved->repl_len;
		return l_saved;
	}
end:
	return 0;
}

/** Prepend srv prefix according to the proto. */
void create_srv_name(char proto, str *name, char *srv)
{
	switch(proto) {
		case PROTO_UDP:
			memcpy(srv, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
			memcpy(srv + SRV_UDP_PREFIX_LEN, name->s, name->len);
			srv[SRV_UDP_PREFIX_LEN + name->len] = '\0';
			break;
		case PROTO_TCP:
			memcpy(srv, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
			memcpy(srv + SRV_TCP_PREFIX_LEN, name->s, name->len);
			srv[SRV_TCP_PREFIX_LEN + name->len] = '\0';
			break;
		case PROTO_TLS:
			memcpy(srv, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
			memcpy(srv + SRV_TLS_PREFIX_LEN, name->s, name->len);
			srv[SRV_TLS_PREFIX_LEN + name->len] = '\0';
			break;
		case PROTO_SCTP:
			memcpy(srv, SRV_SCTP_PREFIX, SRV_SCTP_PREFIX_LEN);
			memcpy(srv + SRV_SCTP_PREFIX_LEN, name->s, name->len);
			srv[SRV_SCTP_PREFIX_LEN + name->len] = '\0';
			break;
		default:
			LM_CRIT("%s: unknown proto %d\n", __func__, proto);
	}
}

size_t create_srv_pref_list(char *proto, struct dns_srv_proto *list)
{
	struct dns_srv_proto tmp;
	size_t i, j, list_len;
	int default_order = 1, max;

	/* if proto available, then add only the forced protocol to the list */
	if(proto && *proto != PROTO_NONE) {
		list[0].proto = *proto;
		list_len = 1;
	} else {
		list_len = 0;
		/*get protocols and preference scores, and add available protocol(s) and score(s) to the list*/
		for(i = PROTO_UDP; i < PROTO_LAST; i++) {
			tmp.proto_pref = srv_proto_pref_score(i);
			/* if -1 so disabled continue with next protocol*/
			if(naptr_proto_supported(i) == 0) {
				continue;
			} else {
				list[list_len].proto_pref = tmp.proto_pref;
				list[list_len].proto = i;
				list_len++;
			}
		};

		/* if all protocol preference scores equal, then set the preference to default values: udp,tcp,tls,sctp */
		for(i = 1; i < list_len; i++) {
			if(list[0].proto_pref != list[i].proto_pref) {
				default_order = 0;
			}
		}
		if(default_order) {
			for(i = 0; i < list_len; i++) {
				list[i].proto_pref = srv_proto_pref_score(i);
			}
		}

		/* sorting the list */
		for(i = 0; i < list_len - 1; i++) {
			max = i;
			for(j = i + 1; j < list_len; j++) {
				if(list[j].proto_pref > list[max].proto_pref) {
					max = j;
				}
			}
			if(i != max) {
				tmp = list[i];
				list[i] = list[max];
				list[max] = tmp;
			}
		}
	}
	return list_len;
}

/** Resolves SRV if no naptr found.
 * It reuse dns_pref values and according that resolves supported protocols.
 * If dns_pref are equal then it use udp,tcp,tls,sctp order.
 * returns: hostent struct & *port filled with the port from the SRV record;
 *  0 on error
 */
struct hostent *no_naptr_srv_sip_resolvehost(
		str *name, unsigned short *port, char *proto)
{
	struct dns_srv_proto srv_proto_list[PROTO_LAST];
	struct hostent *he;
	struct ip_addr *ip;
	str srv_name;
	static char tmp_srv[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
	size_t i, list_len;
	/* init variables */
	he = 0;

	/* check if it's an ip address */
	if(((ip = str2ip(name)) != 0) || ((ip = str2ip6(name)) != 0)) {
		/* we are lucky, this is an ip address */
		/* set proto if needed - default udp */
		if((proto) && (*proto == PROTO_NONE))
			*proto = PROTO_UDP;
		/* set port if needed - default 5060/5061 */
		if((port) && (*port == 0))
			*port = ((proto) && (*proto == PROTO_TLS)) ? SIPS_PORT : SIP_PORT;
		he = ip_addr2he(name, ip);
		return he;
	}

	if((name->len + SRV_MAX_PREFIX_LEN + 1) > MAX_DNS_NAME) {
		LM_WARN("domain name too long (%d), unable to perform SRV lookup\n",
				name->len);
	} else {
		/* looping on the ordered list until we found a protocol what has srv record */
		list_len = create_srv_pref_list(proto, srv_proto_list);
		for(i = 0; i < list_len; i++) {
			switch(srv_proto_list[i].proto) {
				case PROTO_UDP:
				case PROTO_TCP:
				case PROTO_TLS:
				case PROTO_SCTP:
					create_srv_name(srv_proto_list[i].proto, name, tmp_srv);
					break;
				default:
					LM_CRIT("unknown proto %d\n", (int)srv_proto_list[i].proto);
					return 0;
			}
			/* set default port */
			if((port) && (*port == 0)) {
				*port = (srv_proto_list[i].proto == PROTO_TLS)
								? SIPS_PORT
								: SIP_PORT; /* just in case we don't find another */
			}
			if((proto) && (*proto == 0)) {
				*proto = PROTO_UDP;
			}
			srv_name.s = tmp_srv;
			srv_name.len = strlen(tmp_srv);
#ifdef USE_DNS_CACHE
			if(dns_cache_init) {
				he = dns_srv_get_he(&srv_name, port, dns_flags);
			} else {
				he = srv_sip_resolvehost(&srv_name, 0, port, proto, 1, 0);
			}
#else
			he = srv_sip_resolvehost(&srv_name, 0, port, proto, 1, 0);
#endif
			if(he != 0) {
				if(proto)
					*proto = srv_proto_list[i].proto;
				return he;
			}
		}
	}
	return 0;
}

/** internal sip naptr resolver function: resolves a host name trying:
 * - NAPTR lookup if the address is not an ip and *proto==0 and *port==0.
 *   The result of the NAPTR query will be used for a SRV lookup
 * - SRV lookup if the address is not an ip *port==0. The result of the SRV
 *   query will be used for an A/AAAA lookup.
 *  - normal A/AAAA lookup (either fallback from the above or if *port!=0
 *   and *proto!=0 or port==0 && proto==0)
 * when performing SRV lookup (*port==0) it will use proto to look for
 * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
 * returns: hostent struct & *port filled with the port from the SRV record;
 *  0 on error
 */
struct hostent *naptr_sip_resolvehost(
		str *name, unsigned short *port, char *proto)
{
	struct hostent *he;
	struct ip_addr *ip;
	static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups and
	                                  null. term  strings */
	struct rdata *l;
	struct rdata *naptr_head;
	char n_proto;
	str srv_name;
	str *name_copy = 0;
	naptr_bmp_t tried_bmp; /* tried bitmap */
	char origproto = PROTO_NONE;

	if(proto)
		origproto = *proto;
	naptr_head = 0;
	he = 0;
	if(name->len >= MAX_DNS_NAME) {
		LM_ERR("domain name too long\n");
		goto end;
	}
	/* try NAPTR if no port or protocol is specified and NAPTR lookup is
	 * enabled */
	if(port && proto && (*proto == 0) && (*port == 0)) {
		*proto = PROTO_UDP; /* just in case we don't find another */
		if(((ip = str2ip(name)) != 0) || ((ip = str2ip6(name)) != 0)) {
			/* we are lucky, this is an ip address */
			he = ip_addr2he(name, ip);
			*port = SIP_PORT;
			goto end;
		}
		memcpy(tmp, name->s, name->len);
		tmp[name->len] = '\0';
		naptr_head = get_record(tmp, T_NAPTR, RES_AR);
		naptr_iterate_init(&tried_bmp);
		while((l = naptr_sip_iterate(
					   naptr_head, &tried_bmp, &srv_name, &n_proto))
				!= 0) {
			if((he = srv_sip_resolvehost(&srv_name, 1, port, proto, 1, l))
					!= 0) {
				*proto = n_proto;
				return he;
			}
		}
		/*clean up on exit*/
		LM_DBG("no NAPTR record found for %.*s, trying SRV lookup...\n",
				name->len, name->s);
	}
	/* fallback to srv lookup */
	if(proto)
		*proto = origproto;
	he = no_naptr_srv_sip_resolvehost(name, port, proto);
	/* fallback all the way down to A/AAAA */
	if(he == 0) {
		if(dns_cache_init) {
			he = dns_get_he(name, dns_flags);
		} else {
			/* We need a zero terminated char* */
			name_copy = shm_malloc(name->len + 1);
			shm_str_dup(name_copy, name);
			he = resolvehost(name_copy->s);
			shm_free(name_copy);
		}
	}
end:
	if(naptr_head)
		free_rdata_list(naptr_head);
	return he;
}
#endif /* USE_NAPTR */


/** resolves a host name trying:
 * - NAPTR lookup if enabled, the address is not an ip and *proto==0 and
 *   *port==0. The result of the NAPTR query will be used for a SRV lookup
 * - SRV lookup if the address is not an ip *port==0. The result of the SRV
 *   query will be used for an A/AAAA lookup.
 *  - normal A/AAAA lookup (either fallback from the above or if *port!=0
 *   and *proto!=0 or port==0 && proto==0)
 * when performing SRV lookup (*port==0) it will use *proto to look for
 * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
 *
 * returns: hostent struct & *port filled with the port from the SRV record;
 *  0 on error
 */
struct hostent *_sip_resolvehost(str *name, unsigned short *port, char *proto)
{
	struct hostent *res = NULL;
#ifdef USE_NAPTR
	if(cfg_get(core, core_cfg, dns_try_naptr))
		res = naptr_sip_resolvehost(name, port, proto);
	else
#endif
		res = srv_sip_resolvehost(name, 0, port, proto, 0, 0);
	if(unlikely(!res)) {
		/* failed DNS request */
		counter_inc(dns_cnts_h.failed_dns_req);
	}
	return res;
}


/** resolve host, port, proto using sip rules (e.g. use SRV if port=0 a.s.o)
 *  and write the result in the sockaddr_union to
 *  returns -1 on error (resolve failed), 0 on success */
int sip_hostport2su(
		union sockaddr_union *su, str *name, unsigned short port, char *proto)
{
	struct hostent *he;

	he = sip_resolvehost(name, &port, proto);
	if(he == 0) {
		ser_error = E_BAD_ADDRESS;
		LM_ERR("could not resolve hostname: \"%.*s\"\n", name->len, name->s);
		goto error;
	}
	/* port filled by sip_resolvehost if empty*/
	if(hostent2su(su, he, 0, port) < 0) {
		ser_error = E_BAD_ADDRESS;
		goto error;
	}
	return 0;
error:
	return -1;
}

/* converts a str to an ipv4 address struct stored in ipb
 * - ipb must be already allocated
 * - return 0 on success; <0 on failure */
int str2ipbuf(str *st, ip_addr_t *ipb)
{
	int i;
	unsigned char *limit;
	unsigned char *s;

	/* just in case that e.g. the VIA parser get confused */
	if(unlikely(!st->s || st->len <= 0)) {
		LM_ERR("invalid name (%p,%d), no conversion to IP address possible\n",
				st->s, st->len);
		return -1;
	}
	s = (unsigned char *)st->s;

	/*init*/
	ipb->u.addr32[0] = 0;
	i = 0;
	limit = (unsigned char *)(st->s + st->len);

	for(; s < limit; s++) {
		if(*s == '.') {
			i++;
			if(i > 3)
				goto error_dots;
		} else if((*s <= '9') && (*s >= '0')) {
			ipb->u.addr[i] = ipb->u.addr[i] * 10 + *s - '0';
		} else {
			//error unknown char
			goto error_char;
		}
	}
	if(i < 3)
		goto error_dots;
	ipb->af = AF_INET;
	ipb->len = 4;

	return 0;

error_dots:
	DBG("error - too %s dots in [%.*s]\n", (i > 3) ? "many" : "few", st->len,
			st->s);
	return -1;
error_char:
	/*
	DBG("warning - unexpected char %c in [%.*s]\n", *s, st->len, st->s);
	*/
	return -2;
}

/* converts a str to an ipv4 address, returns the address or 0 on error
   Warning: the result is a pointer to a statically allocated structure */
ip_addr_t *str2ip(str *st)
{
	ip_addr_t *ipb;

	ipb = get_next_ipaddr_buf();
	if(str2ipbuf(st, ipb) < 0) {
		return NULL;
	}

	return ipb;
}

/*
* Resolve a host name to a hostent.
* @param[in] name: the host name to resolve
* @return the hostent structure or NULL on error
*
* @note
* This function is a wrapper to choose between the DNS cache and the
* system resolver. If the DNS cache is enabled, it will use the DNS cache
* to resolve the host name. Otherwise, it will use the system resolver.
*/
struct hostent *__resolvehost(char *name)
{
	if(dns_cache_init) {
		return dns_resolvehost(name);
	} else {
		return _resolvehost(name);
	}
}

/*
* Resolve a host name to a hostent.
* @param[in] name: the host name to resolve
* @param[in] port: the port number
* @param[in] proto: the protocol
* @return the hostent structure or NULL on error
*
* @note
* This function is a wrapper to choose between the DNS cache and the
* system resolver. If the DNS cache is enabled, it will use the DNS cache
* to resolve the host name. Otherwise, it will use the system resolver.
*/
struct hostent *__sip_resolvehost(str *name, unsigned short *port, char *proto)
{
	if(dns_cache_init) {
		return dns_sip_resolvehost(name, port, proto);
	} else {
		return _sip_resolvehost(name, port, proto);
	}
}
/* converts a str to an ipv6 address struct stored in ipb
 * - ipb must be already allocated
 * - return 0 on success; <0 on failure */
int str2ip6buf(str *st, ip_addr_t *ipb)
{
	int i, idx1, rest;
	int no_colons;
	int double_colon;
	int hex;
	unsigned short *addr_start;
	unsigned short addr_end[8];
	unsigned short *addr;
	unsigned char *limit;
	unsigned char *s;

	/* just in case that e.g. the VIA parser get confused */
	if(unlikely(!st->s || st->len <= 0)) {
		LM_ERR("invalid name (%p,%d), no conversion to IP address possible\n",
				st->s, st->len);
		return -1;
	}
	/* init */
	if((st->len) && (st->s[0] == '[')) {
		/* skip over [ ] */
		if(st->s[st->len - 1] != ']')
			goto error_char;
		s = (unsigned char *)(st->s + 1);
		limit = (unsigned char *)(st->s + st->len - 1);
	} else {
		s = (unsigned char *)st->s;
		limit = (unsigned char *)(st->s + st->len);
	}
	i = idx1 = rest = 0;
	double_colon = 0;
	no_colons = 0;
	ipb->af = AF_INET6;
	ipb->len = 16;
	addr_start = ipb->u.addr16;
	addr = addr_start;
	memset(addr_start, 0, 8 * sizeof(unsigned short));
	memset(addr_end, 0, 8 * sizeof(unsigned short));
	for(; s < limit; s++) {
		if(*s == ':') {
			no_colons++;
			if(no_colons > 7)
				goto error_too_many_colons;
			if(double_colon) {
				idx1 = i;
				i = 0;
				if(addr == addr_end)
					goto error_colons;
				addr = addr_end;
			} else {
				double_colon = 1;
				addr[i] = htons(addr[i]);
				i++;
			}
		} else if((hex = HEX2I(*s)) >= 0) {
			addr[i] = addr[i] * 16 + hex;
			double_colon = 0;
		} else {
			/* error, unknown char */
			goto error_char;
		}
	}
	if(!double_colon) { /* not ending in ':' */
		addr[i] = htons(addr[i]);
		i++;
	}
	/* if address contained '::' fix it */
	if(addr == addr_end) {
		rest = 8 - i - idx1;
		memcpy(addr_start + idx1 + rest, addr_end, i * sizeof(unsigned short));
	} else {
		/* no double colons inside */
		if(no_colons < 7)
			goto error_too_few_colons;
	}
	/*
	DBG("idx1=%d, rest=%d, no_colons=%d, hex=%x\n",
			idx1, rest, no_colons, hex);
	DBG("address %x:%x:%x:%x:%x:%x:%x:%x\n",
			addr_start[0], addr_start[1], addr_start[2],
			addr_start[3], addr_start[4], addr_start[5],
			addr_start[6], addr_start[7] );
*/
	return 0;

error_too_many_colons:
	DBG("error - too many colons in [%.*s]\n", st->len, st->s);
	return -1;

error_too_few_colons:
	DBG("error - too few colons in [%.*s]\n", st->len, st->s);
	return -2;

error_colons:
	DBG("error - too many double colons in [%.*s]\n", st->len, st->s);
	return -3;

error_char:
	/*
	DBG("warning - unexpected char %c in  [%.*s]\n", *s, st->len,
			st->s);*/
	return -4;
}

/* returns an ip_addr struct.; on error returns 0
 * the ip_addr struct is static, so subsequent calls will destroy its content*/
ip_addr_t *str2ip6(str *st)
{
	ip_addr_t *ipb;

	ipb = get_next_ipaddr_buf();
	if(str2ip6buf(st, ipb) < 0) {
		return NULL;
	}

	return ipb;
}

/* converts a str to an ipvv/6 address struct stored in ipb
 * - ipb must be already allocated
 * - return 0 on success; <0 on failure */
int str2ipxbuf(str *st, ip_addr_t *ipb)
{
	if(str2ipbuf(st, ipb) < 0) {
		if(str2ip6buf(st, ipb) < 0) {
			return -1;
		}
	}

	return 0;
}

/* returns an ip_addr struct converted from ipv4/6 str; on error returns 0
 * the ip_addr struct is static, so subsequent calls will destroy its content*/
struct ip_addr *str2ipx(str *st)
{
	ip_addr_t *ipb;

	ipb = get_next_ipaddr_buf();
	if(str2ipbuf(st, ipb) < 0) {
		if(str2ip6buf(st, ipb) < 0) {
			return NULL;
		}
	}

	return ipb;
}
