/**
 * Resolver.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program 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.
 */

/*
 Preprocessor definitions:
 WINDOWS:
   HAVE_DNS_NAPTR_DATA: Define it if DNS_NAPTR_DATA is defined in windns.h
     If defined, NAPTR query will work at runtime on all supported versions
     If not defined NAPTR query will work only if Windows major version is less then 6
 Non Windows
   NO_RESOLV: Defined if the resolver is not available at compile time
   NO_DN_SKIPNAME: Define it if __dn_skipname() is not available at link time
*/

#include "yateclass.h"

#ifdef _WINDOWS
#include <windns.h>
#elif !defined(NO_RESOLV)
#include <resolv.h>
#include <arpa/nameser.h>
#endif // _WINDOWS

using namespace TelEngine;

// Resolver type names
const TokenDict Resolver::s_types[] = {
    { "SRV", Srv },
    { "NAPTR", Naptr },
    { 0, 0 },
};

#ifdef _WINDOWS

class WindowsVersion
{
public:
    WindowsVersion();
    inline unsigned int major() const
	{ return m_major; }
private:
    unsigned int m_major;
};

WindowsVersion::WindowsVersion()
    : m_major(0)
{
    OSVERSIONINFOA ver;
    ::ZeroMemory(&ver,sizeof(OSVERSIONINFOA));
    ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (::GetVersionEx(&ver))
	m_major = ver.dwMajorVersion;
    if (!m_major)
	Debug(DebugWarn,"Resolver failed to detect Windows version");
}

static WindowsVersion s_winVer;

#endif

// Utility: print the result of dns query
// Return the code
static int printResult(int type, int code, const char* dname, ObjList& result, String* error)
{
#ifdef DEBUG
    if (!code) {
	String s;
	int crt = 0;
	for (ObjList* o = result.skipNull(); o; o = o->skipNext()) {
	    DnsRecord* rec = static_cast<DnsRecord*>(o->get());
	    if (!s)
		s << "\r\n-----";
	    s << "\r\n" << ++crt << ":";
	    rec->dump(s);
	}
	if (s)
	    s << "\r\n-----";
	Debug(DebugAll,"%s query for '%s' got %d records%s",
	    lookup(type,Resolver::s_types),dname,result.count(),s.safe());
    }
    else {
	String dummy;
	if (!error) {
	    error = &dummy;
#ifdef _WINDOWS
	    Thread::errorString(dummy,code);
#elif defined(__NAMESER)
	    dummy = hstrerror(code);
#endif
	}
	Debug(DebugNote,"%s query for '%s' failed with code %d error=%s",
	    lookup(type,Resolver::s_types),dname,code,TelEngine::c_safe(error));
    }
#endif
    return code;
}

// Utility: print a record and insert it in list
static bool insertRecord(ObjList& result, DnsRecord* rec, bool ascPref, const char* str)
{
#ifdef XDEBUG
    if (rec) {
	String s;
	rec->dump(s);
	Debug(DebugAll,"%s inserting %s",str,s.c_str());
    }
#endif
    return DnsRecord::insert(result,rec,ascPref);
}


/*
 * __dn_skipname() not available at link time
*/
#ifdef NO_DN_SKIPNAME

#define RESOLVER_NAME_COMPRESSED 0xc0      // Name compressed mask
#define RESOLVER_NAME_COMPRESSED_EXT 0x40  // RFC 2671 extended label type
#define RESOLVER_NAME_COMPRESSED_EXT_NEXT 0x41  // RFC 2671 extended label type with length in the next byte

#ifndef dn_skipname
#define dn_skipname __dn_skipname
#endif

// Retrieve the length of an extended compressed or uncompressed name
static int dn_namelen_ext(const unsigned char* buf)
{
    if (!buf)
	return -1;
    unsigned char val = *buf;
    unsigned char comp = (val & RESOLVER_NAME_COMPRESSED);
    // Compressed but not using extension: error
    if (comp == RESOLVER_NAME_COMPRESSED)
	return -1;
    // Not using extended compression
    if (comp != RESOLVER_NAME_COMPRESSED_EXT)
	return val;
    // Extended compression with length in the next byte
    if (val == RESOLVER_NAME_COMPRESSED_EXT_NEXT) {
	int nBits = buf[1];
	if (!nBits)
	    nBits = 256;
	return (nBits + 7) / 8 + 1;
    }
    DDebug(DebugNote,"dn_namelen_ext(%p) unknown extended compression %xd",buf,val);
    return -1;
}

extern "C" int __dn_skipname(const unsigned char* start, const unsigned char* end)
{
    XDebug(DebugNote,"__dn_skipname(%p,%p)",start,end);
    const unsigned char* buf = start;
    bool ok = true;
    while (buf < end) {
	unsigned char c = *buf++;
	if (!c)
	    break;
	unsigned char comp = (c & RESOLVER_NAME_COMPRESSED);
	// Not compressed
	if (!comp) {
	    buf += c;
	    continue;
	}
	// Compressed
	if (comp == RESOLVER_NAME_COMPRESSED) {
	    buf++;
	    break;
	}
	// Extended compression
	if (comp == RESOLVER_NAME_COMPRESSED_EXT) {
	    int len = dn_namelen_ext(buf - 1);
	    if (len >= 0) {
		buf += len;
		continue;
	    }
	    ok = false;
	    break;
	}
	DDebug(DebugNote,"__dn_skipname: unknown compression type %xd",comp);
	// Unknown compression
	ok = false;
	break;
    }
    if (!ok || buf > end) {
	h_errno = EMSGSIZE;
	return -1;
    }
    return buf - start;
}

#endif // NO_DN_SKIPNAME

// weird but NS_MAXSTRING and dn_string() are NOT part of the resolver API...
#ifndef NS_MAXSTRING
#define NS_MAXSTRING 255
#endif

// copy one string (not domain) from response
static int dn_string(const unsigned char* end, const unsigned char* src, char* dest, int maxlen)
{
    if (!src)
	return 0;
    int n = src[0];
    if (!dest)
	return n + 1;
    maxlen--;
    if (maxlen > n)
	maxlen = n;
    while ((maxlen-- > 0) && (src < end))
	*dest++ = *++src;
    *dest = 0;
    return n + 1;
}

// Dump a record for debug purposes
void DnsRecord::dump(String& buf, const char* sep)
{
    buf.append("order=",sep) << m_order;
    buf << sep << "pref=" << m_pref;
}

// Insert a record into a list in the proper location
// Order records ascending by their order
// Look at ascPref for records with the same order
bool DnsRecord::insert(ObjList& list, DnsRecord* rec, bool ascPref)
{
    if (!rec || list.find(rec))
	return false;
    XDebug(DebugAll,"DnsRecord::insert(%p) order=%d pref=%d",rec,rec->order(),rec->pref());
    ObjList* o = list.skipNull();
    for (; o; o = o->skipNext()) {
	DnsRecord* crt = static_cast<DnsRecord*>(o->get());
	if (rec->order() > crt->order())
	    continue;
	if (rec->order() == crt->order()) {
	    for (; o; o = o->skipNext()) {
		DnsRecord* crt = static_cast<DnsRecord*>(o->get());
		if (crt->order() != rec->order())
		    break;
		if (crt->pref() == rec->pref())
		    continue;
		if (ascPref == (rec->pref() < crt->pref()))
		    break;
	    }
	}
	break;
    }
    if (o)
	o->insert(rec);
    else
	list.append(rec);
    return true;
}


// Dump a record for debug purposes
void SrvRecord::dump(String& buf, const char* sep)
{
    DnsRecord::dump(buf,sep);
    buf.append("address=",sep) << "'" << m_address << "'";
    buf << sep << "port=" << m_port;
}

// Copy a SrvRecord list into another one
void SrvRecord::copy(ObjList& dest, const ObjList& src)
{
    dest.clear();
    for (ObjList* o = src.skipNull(); o; o = o->skipNext()) {
	SrvRecord* rec = static_cast<SrvRecord*>(o->get());
	dest.append(new SrvRecord(rec->order(),rec->pref(),rec->address(),rec->port()));
    }
}


NaptrRecord::NaptrRecord(int ord, int pref, const char* flags, const char* serv,
    const char* regexp, const char* next)
    : DnsRecord(ord,pref),
    m_flags(flags), m_service(serv), m_next(next)
{
    // use case-sensitive extended regular expressions
    m_regmatch.setFlags(true,false);
    if (!null(regexp)) {
	// look for <sep>regexp<sep>template<sep>
	char sep[2] = { regexp[0], 0 };
	String tmp(regexp+1);
	if (tmp.endsWith(sep)) {
	    int pos = tmp.find(sep);
	    if (pos > 0) {
		m_regmatch = tmp.substr(0,pos);
		m_template = tmp.substr(pos+1,tmp.length()-pos-2);
		XDebug(DebugAll,"NaptrRecord match '%s' template '%s'",
		    m_regmatch.c_str(),m_template.c_str());
	    }
	}
    }
}

// Perform the Regexp replacement, return true if succeeded
bool NaptrRecord::replace(String& str)
{
    if (m_regmatch && str.matches(m_regmatch)) {
	str = str.replaceMatches(m_template);
	return true;
    }
    return false;
}

// Dump a record for debug purposes
void NaptrRecord::dump(String& buf, const char* sep)
{
    DnsRecord::dump(buf,sep);
    buf.append("flags=",sep) << "'" << m_flags << "'";
    buf << sep << "service=" << "'" << m_service << "'";
    buf << sep << "regmatch=" << "'" << m_regmatch << "'";
    buf << sep << "template=" << "'" << m_template << "'";
    buf << sep << "next=" << "'" << m_next << "'";
}


// Runtime check for resolver availability
bool Resolver::available(Type t)
{
#ifdef _WINDOWS
    if (t == Naptr) {
	if (!s_winVer.major())
	    return false;
#ifdef HAVE_DNS_NAPTR_DATA
	return true;
#else
	return s_winVer.major() < 6;
#endif
    }
    return true;
#elif defined(__NAMESER)
    return true;
#endif
    return false;
}

// Initialize the resolver in the current thread
bool Resolver::init(int timeout, int retries)
{
    if (!available())
	return false;
#ifdef _WINDOWS
    return true;
#elif defined(__NAMESER)
    if ((_res.options & RES_INIT) == 0) {
	// need to initialize in current thread
	if (res_init())
	    return false;
    }
    // always set variables
    if (timeout >= 0)
	_res.retrans = timeout;
    if (retries >= 0)
	_res.retry = retries;
    return true;
#endif
    return false;
}

// Make a query
int Resolver::query(Type type, const char* dname, ObjList& result, String* error)
{
    if (type == Srv)
	return srvQuery(dname,result,error);
    if (type == Naptr)
	return naptrQuery(dname,result,error);
    Debug(DebugStub,"Resolver query not implemented for type %d",type);
    return 0;
}

// Make a SRV query
int Resolver::srvQuery(const char* dname, ObjList& result, String* error)
{
    int code = 0;
    XDebug(DebugAll,"Starting %s query for '%s'",lookup(Srv,s_types),dname);
#ifdef _WINDOWS
    DNS_RECORD* srv = 0;
    code = (int)::DnsQuery_UTF8(dname,DNS_TYPE_SRV,DNS_QUERY_STANDARD,NULL,&srv,NULL);
    if (code == ERROR_SUCCESS) {
    	for (DNS_RECORD* dr = srv; dr; dr = dr->pNext) {
	    if (dr->wType != DNS_TYPE_SRV || dr->wDataLength != sizeof(DNS_SRV_DATA))
		continue;
	    DNS_SRV_DATA& d = dr->Data.SRV;
	    insertRecord(result,new SrvRecord(d.wPriority,d.wWeight,
		d.pNameTarget,d.wPort),false,"srvQuery");
	}
    }
    else if (error)
	Thread::errorString(*error,code);
    if (srv)
	::DnsRecordListFree(srv,DnsFreeRecordList);
#elif defined(__NAMESER)
    unsigned char buf[512];
    int r = res_query(dname,ns_c_in,ns_t_srv,buf,sizeof(buf));
    if (r <= 0 || r > (int)sizeof(buf)) {
	if (r) {
	    code = h_errno;
	    if (error)
		*error = hstrerror(code);
	}
	return printResult(Srv,code,dname,result,error);
    }
    int queryCount = 0;
    int answerCount = 0;
    unsigned char* p = buf + NS_QFIXEDSZ;
    unsigned char* e = buf + r;
    NS_GET16(queryCount,p);
    NS_GET16(answerCount,p);
    p = buf + NS_HFIXEDSZ;
    // Skip queries
    for (; queryCount > 0; queryCount--) {
	int n = dn_skipname(p,e);
	if (n < 0)
	    break;
	p += (n + NS_QFIXEDSZ);
    }
    for (int i = 0; i < answerCount; i++) {
	char name[NS_MAXLABEL + 1];
	// Skip this answer's query
	int n = dn_expand(buf,e,p,name,sizeof(name));
	if ((n <= 0) || (n > NS_MAXLABEL))
	    break;
	buf[n] = 0;
	p += n;
	// Get record type, class, ttl, length
	int rrType, rrClass, rrTtl, rrLen;
	NS_GET16(rrType,p);
	NS_GET16(rrClass,p);
	NS_GET32(rrTtl,p);
	NS_GET16(rrLen,p);
	// Remember current pointer and skip to next answer
	unsigned char* l = p;
	p += rrLen;
	// Skip non SRV answers
	if (rrType != (int)ns_t_srv)
	    continue;
	// Now get record priority, weight, port, label
	int prio, weight, port;
	NS_GET16(prio,l);
	NS_GET16(weight,l);
	NS_GET16(port,l);
	n = dn_expand(buf,e,l,name,sizeof(name));
	if ((n <= 0) || (n > NS_MAXLABEL))
	    break;
	insertRecord(result,new SrvRecord(prio,weight,name,port),false,"srvQuery");
    }
#endif
    return printResult(Srv,code,dname,result,error);
}

// Make a NAPTR query
int Resolver::naptrQuery(const char* dname, ObjList& result, String* error)
{
    int code = 0;
    XDebug(DebugAll,"Starting %s query for '%s'",lookup(Naptr,s_types),dname);
#ifdef _WINDOWS
    DNS_RECORD* naptr = 0;
    if (available(Naptr))
	code = (int)::DnsQuery_UTF8(dname,DNS_TYPE_NAPTR,DNS_QUERY_STANDARD,NULL,&naptr,NULL);
    if (code == ERROR_SUCCESS) {
    	for (DNS_RECORD* dr = naptr; dr; dr = dr->pNext) {
	    if (dr->wType != DNS_TYPE_NAPTR)
		continue;
	    // version >= 6: DnsQuery will set the fields in DNS_NAPTR_DATA
	    // else: DnsQuery puts the raw response
	    if (s_winVer.major() >= 6) {
#ifdef HAVE_DNS_NAPTR_DATA
		if (dr->wDataLength != sizeof(DNS_NAPTR_DATA))
		    continue;
		DNS_NAPTR_DATA& d = dr->Data.NAPTR;
		insertRecord(result,new NaptrRecord(d.wOrder,d.wPreference,d.pFlags,
		    d.pService,d.pRegularExpression,d.pReplacement),true,"naptrQuery");
#endif
		continue;
	    }
	    int len = dr->wDataLength - 4;
	    if (len <= 0)
		continue;
	    unsigned char* b = (unsigned char*)&dr->Data;
	    int ord = (b[0] << 8) | b[1];
	    int pr = (b[2] << 8) | b[3];
	    unsigned char* tmp = new unsigned char[len + 1];
	    ::memcpy(tmp,b + 4,len);
	    tmp[len] = 0;
	    unsigned char* buf = tmp;
	    unsigned char* end = buf + len;
	    if (!buf)
		continue;
	    char fla[DNS_MAX_NAME_BUFFER_LENGTH];
	    char ser[DNS_MAX_NAME_BUFFER_LENGTH];
	    char reg[DNS_MAX_NAME_BUFFER_LENGTH];
	    buf += dn_string(end,buf,fla,sizeof(fla));;
	    buf += dn_string(end,buf,ser,sizeof(ser));
	    buf += dn_string(end,buf,reg,sizeof(reg));
	    insertRecord(result,new NaptrRecord(ord,pr,fla,ser,reg,0),true,"naptrQuery");
	}
    }
    else if (error)
	Thread::errorString(*error,code);
    if (naptr)
	::DnsRecordListFree(naptr,DnsFreeRecordList);
#elif defined(__NAMESER)
    unsigned char buf[2048];
    int r,q,a;
    unsigned char *p, *e;
    r = res_query(dname,ns_c_in,ns_t_naptr,buf,sizeof(buf));
    if ((r < 0) || (r > (int)sizeof(buf))) {
	code = h_errno;
	if (error)
	    *error = hstrerror(code);
	return printResult(Naptr,code,dname,result,error);
    }
    p = buf+NS_QFIXEDSZ;
    NS_GET16(q,p);
    NS_GET16(a,p);
    XDebug(DebugAll,"Resolver::naptrQuery(%s) questions: %d, answers: %d",dname,q,a);
    p = buf + NS_HFIXEDSZ;
    e = buf + r;
    for (; q > 0; q--) {
	int n = dn_skipname(p,e);
	if (n < 0)
	    return printResult(Naptr,code,dname,result,error);
	p += (n + NS_QFIXEDSZ);
    }
    XDebug(DebugAll,"Resolver::naptrQuery(%s) skipped questions",dname);
    for (; a > 0; a--) {
	int ty,cl,sz;
	long int tt;
	char name[NS_MAXLABEL+1];
	unsigned char* l;
	int n = dn_expand(buf,e,p,name,sizeof(name));
	if ((n <= 0) || (n > NS_MAXLABEL))
	    break;
	buf[n] = 0;
	p += n;
	NS_GET16(ty,p);
	NS_GET16(cl,p);
	NS_GET32(tt,p);
	NS_GET16(sz,p);
	XDebug(DebugAll,"Resolver::naptrQuery(%s) found '%s' type %d size %d",dname,name,ty,sz);
	l = p;
	p += sz;
	if (ty != ns_t_naptr)
	    continue;
	int ord,pr;
	char fla[NS_MAXSTRING+1];
	char ser[NS_MAXSTRING+1];
	char reg[NS_MAXSTRING+1];
	char rep[NS_MAXLABEL+1];
	NS_GET16(ord,l);
	NS_GET16(pr,l);
	n = dn_string(e,l,fla,sizeof(fla));
	l += n;
	n = dn_string(e,l,ser,sizeof(ser));
	l += n;
	n = dn_string(e,l,reg,sizeof(reg));
	l += n;
	n = dn_expand(buf,e,l,rep,sizeof(rep));
	l += n;
	insertRecord(result,new NaptrRecord(ord,pr,fla,ser,reg,rep),true,"naptrQuery");
    }
#endif
    return printResult(Naptr,code,dname,result,error);
}

/* vi: set ts=8 sw=4 sts=4 noet: */
