Blob Blame History Raw
/*
 * lftp - file transfer program
 *
 * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * 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.  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, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <stddef.h>
#include "Resolver.h"
#include "SignalHook.h"
#include <errno.h>
#include <unistd.h>
#include "trio.h"
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ctype.h>
#include <fcntl.h>

#include <netinet/in.h>
#ifdef HAVE_ARPA_NAMESER_H
# define class _class // workaround for FreeBSD 3.2.
# include <arpa/nameser.h>
# undef class
#endif
#ifdef HAVE_RESOLV_H
# include <resolv.h>
#endif

#if LIBIDN2
# include <idn2.h>
#endif

#ifdef DNSSEC_LOCAL_VALIDATION
# include "validator/validator.h"
#endif

#include "xstring.h"
#include "ResMgr.h"
#include "log.h"
#include "plural.h"

#ifndef C_IN
# define C_IN 1
#endif
#ifndef T_SRV
# define T_SRV 33
#endif

#if !HAVE_DECL_HSTRERROR
extern "C" { const char *hstrerror(int); }
#endif

#ifdef HAVE_H_ERRNO
#if !HAVE_DECL_H_ERRNO
CDECL int h_errno;
#endif
#endif

#if HAVE_RES_SEARCH && !HAVE_DECL_RES_SEARCH
CDECL int res_search(const char*,int,int,unsigned char*,int);
#endif

#if INET6
# define DEFAULT_ORDER "inet inet6"
#else
# define DEFAULT_ORDER "inet"
#endif


struct address_family
{
   int number;
   const char *name;
};
static const address_family af_list[]=
{
   { AF_INET,  "inet"  },
#if INET6
   { AF_INET6, "inet6" },
#endif
   { -1, 0 }
};

ResolverCache *Resolver::cache;


Resolver::Resolver(const char *h,const char *p,const char *defp,
		   const char *ser,const char *pr)
   : hostname(h), portname(p), service(ser), proto(pr), defport(defp)
{
   port_number=0;

   pipe_to_child[0]=pipe_to_child[1]=-1;
   done=false;
   timeout_timer.SetResource("dns:fatal-timeout",hostname);
   Reconfig();
   use_fork=ResMgr::QueryBool("dns:use-fork",0);

   error=0;

   no_cache=false;
}

Resolver::~Resolver()
{
   if(pipe_to_child[0]!=-1)
      close(pipe_to_child[0]);
   if(pipe_to_child[1]!=-1)
      close(pipe_to_child[1]);

   if(w)
   {
      w->Kill(SIGKILL);
      w.borrow()->Auto();
   }
}

int   Resolver::Do()
{
   if(done)
      return STALL;

   int m=STALL;

   if(!no_cache && cache)
   {
      const sockaddr_u *a;
      int n;
      cache->Find(hostname,portname,defport,service,proto,&a,&n);
      if(a && n>0)
      {
	 LogNote(10,"dns cache hit");
	 addr.nset(a,n);
	 done=true;
	 return MOVED;
      }
      no_cache=true;
   }

   if(use_fork)
   {
      if(pipe_to_child[0]==-1)
      {
	 int res=pipe(pipe_to_child);
	 if(res==-1)
	 {
	    if(NonFatalError(errno))
	       return m;
	    MakeErrMsg("pipe()");
	    return MOVED;
	 }
	 fcntl(pipe_to_child[0],F_SETFL,O_NONBLOCK);
	 fcntl(pipe_to_child[0],F_SETFD,FD_CLOEXEC);
	 fcntl(pipe_to_child[1],F_SETFD,FD_CLOEXEC);
	 m=MOVED;
	 LogNote(4,_("Resolving host address..."));
      }

      if(!w && !buf)
      {
	 pid_t proc=fork();
	 if(proc==-1)
	 {
	    TimeoutS(1);
	    return m;
	 }
	 if(proc==0)
	 {	 // child
	    SignalHook::Ignore(SIGINT);
	    SignalHook::Ignore(SIGTSTP);
	    SignalHook::Ignore(SIGQUIT);
	    SignalHook::Ignore(SIGHUP);
	    close(0);	// no input will be needed.
	    close(pipe_to_child[0]);
	    pipe_to_child[0]=-1;
	    buf=new IOBufferFDStream(new FDStream(pipe_to_child[1],"<pipe-out>"),IOBuffer::PUT);
	    DoGethostbyname();
	    buf->PutEOF();
	    while(buf->Size()>0 && !buf->Error() && !buf->Broken())
	       buf->Roll();  // should flush quickly.
	    _exit(0);
	 }
	 // parent
	 close(pipe_to_child[1]);
	 pipe_to_child[1]=-1;

	 w=new ProcWait(proc);
	 m=MOVED;
      }
      if(!buf)
      {
	 buf=new IOBufferFDStream(new FDStream(pipe_to_child[0],"<pipe-in>"),IOBuffer::GET);
// 	 Roll(buf);
	 m=MOVED;
      }
   }
   else /* !use_fork */
   {
      if(!buf)
      {
	 LogNote(4,_("Resolving host address..."));
	 buf=new IOBuffer(IOBuffer::GET);
	 DoGethostbyname();
	 if(Deleted())
	    return MOVED;
      }
   }

   if(buf->Error())
   {
      err_msg.set(buf->ErrorText());
      done=true;
      return MOVED;
   }

   if(!buf->Eof())   // wait for all data to arrive (not too much)
   {
      if(timeout_timer.Stopped())
      {
	 err_msg.set(_("host name resolve timeout"));
	 done=true;
	 return MOVED;
      }
      return m;
   }

   const char *s;
   char c;
   int n;

   buf->Get(&s,&n);
   if(n<1)
      goto proto_error;
   c=*s;
   buf->Skip(1);
   buf->Get(&s,&n);
   if(c=='E' || c=='P') // error
   {
      const char *tport=portname?portname.get():defport.get();
      err_msg.vset(c=='E'?hostname.get():tport,": ",s,NULL);
      done=true;
      return MOVED;
   }
   if((unsigned)n<addr.get_element_size())
   {
   proto_error:
      if(use_fork)
      {
	 // e.g. under gdb child fails.
	 LogError(4,"child failed, retrying with dns:use-fork=no");
	 use_fork=false;
	 buf=0;
	 return MOVED;
      }
      err_msg.set("BUG: internal class Resolver error");
      done=true;
      return MOVED;
   }
   addr.nset((const sockaddr_u*)s,n/addr.get_element_size());
   done=true;
   if(!cache)
      cache=new ResolverCache;
   cache->Add(hostname,portname,defport,service,proto,addr.get(),addr.count());

   xstring report;
   report.set(xstring::format(plural("%d address$|es$ found",addr.count()),addr.count()));
   if(addr.count()>0) {
      report.append(": ");
      for(int i=0; i<addr.count(); i++) {
	 report.append(addr[i].address());
	 if(i<addr.count()-1)
	    report.append(", ");
      }
   }
   LogNote(4,"%s",report.get());

   return MOVED;
}

void Resolver::MakeErrMsg(const char *f)
{
   const char *e=strerror(errno);
   err_msg.vset(f,": ",e,NULL);
   done=true;
}

void Resolver::AddAddress(int family,const char *address,int len, unsigned int scope)
{
   sockaddr_u add;
   memset(&add,0,sizeof(add));

   add.sa.sa_family=family;
   switch(family)
   {
   case AF_INET:
      if(sizeof(add.in.sin_addr) != len)
         return;
      memcpy(&add.in.sin_addr,address,len);
      add.in.sin_port=port_number;
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
      add.sa.sa_len=sizeof(add.in);
#endif
      break;

#if INET6
   case AF_INET6:
      if(sizeof(add.in6.sin6_addr) != len)
         return;
      memcpy(&add.in6.sin6_addr,address,len);
      if(IN6_IS_ADDR_LINKLOCAL(&add.in6.sin6_addr) && scope==0) {
	 error=_("Link-local IPv6 address should have a scope");
	 return;
      }
      add.in6.sin6_port=port_number;
# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
      add.in6.sin6_scope_id=scope;
# endif
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
      add.sa.sa_len=sizeof(add.in6);
# endif
      break;
#endif

   default:
      return;
   }
   if(addr.count()>0 && addr.last()==add)
      return;
   addr.append(add);
}

int Resolver::FindAddressFamily(const char *name)
{
   for(const address_family *f=af_list; f->name; f++)
   {
      if(!strcasecmp(name,f->name))
	 return f->number;
   }
   return -1;
}

void Resolver::ParseOrder(const char *s,int *o)
{
   const char * const delim="\t ";
   char *s1=alloca_strdup(s);
   int idx=0;

   for(s1=strtok(s1,delim); s1; s1=strtok(0,delim))
   {
      int af=FindAddressFamily(s1);
      if(af!=-1 && idx<15)
      {
	 if(o) o[idx]=af;
	 idx++;
      }
   }
   if(o) o[idx]=-1;
}

#if defined(HAVE_DN_EXPAND) && !HAVE_DECL_DN_EXPAND
CDECL int dn_expand(const unsigned char *msg,const unsigned char *eomorig,const unsigned char *comp_dn,char *exp_dn,int length);
CDECL int dn_skipname(const unsigned char *msg,const unsigned char *eomorig);
#endif

#ifdef HAVE_RES_SEARCH
static
int extract_domain(const unsigned char *answer,const unsigned char *scan,int len,
		     char *store,int store_len)
{
#ifdef HAVE_DN_EXPAND // newer resolver versions have dn_expand and dn_skipname
   if(store)
      dn_expand(answer,scan+len,scan,store,store_len);
   return dn_skipname(scan,scan+len);
#else // ...older don't.
   int count=1;	  // reserve space for \0
   int refs=0;
   int consumed=0;
   const unsigned char *start=scan;
   for(;;)
   {
      if(len<=0)
	 break;
      int label_len=*scan;
      scan++;
      len--;

      if((label_len & 0xC0) == 0xC0)   // compression
      {
	 if(len<=0)
	    break;
	 int offset=((label_len&0x3F)<<8) + *scan;
	 scan++;
	 len--;

	 if(refs==0)
	    consumed=scan-start;

	 if(answer+offset>=scan+len)
	    break; // error

	 len=scan+len-answer+offset;
	 scan=answer+offset;
	 if(++refs > 256)
	    break;   // too many hops.
	 continue;
      }

      if(label_len==0)
	 break;

      while(label_len>0)
      {
	 if(len<=0)
	    break;
	 if(store && count<store_len)
	    *store++=*scan;
	 count++;
	 scan++;
	 len--;
	 label_len--;
      }
      if(store && count<store_len)
	 *store++='.';
      count++;
   }
   if(store)
      *store=0;
   if(refs==0)
      consumed=scan-start;
   return consumed;
#endif // DN_EXPAND
}

#ifndef NS_MAXDNAME
# define NS_MAXDNAME 1025
#endif
#ifndef NS_HFIXEDSZ
# define NS_HFIXEDSZ 12
#endif

struct SRV
{
   char domain[NS_MAXDNAME];
   int port;
   int priority;
   int weight;
   int order;
};

static
int SRV_compare(const SRV *sa,const SRV *sb)
{
   if(sa->priority < sb->priority)
      return -1;
   if(sa->priority > sb->priority)
      return 1;
   if(sa->order < sb->order)
      return -1;
   if(sa->order > sb->order)
      return 1;
   if(sa->weight > sb->weight)
      return -1;
   if(sa->weight < sb->weight)
      return 1;
   return 0;
}
#endif // RES_SEARCH

void Resolver::LookupSRV_RR()
{
   if(!ResMgr::QueryBool("dns:SRV-query",hostname))
      return;
#ifdef HAVE_RES_SEARCH
   const char *tproto=proto?proto.get():"tcp";
   time_t try_time;
   unsigned char answer[0x1000];
   const char *srv_name=xstring::format("_%s._%s.%s",service.get(),tproto,hostname.get());
   srv_name=alloca_strdup(srv_name);

   int retries=0;
   int max_retries=ResMgr::Query("dns:max-retries",hostname);
   int len;
   for(;;)
   {
      if(!use_fork)
      {
	 Schedule();
	 if(Deleted())
	    return;
      }
      time(&try_time);

#ifndef DNSSEC_LOCAL_VALIDATION
      len=res_search(srv_name, C_IN, T_SRV, answer, sizeof(answer));
      if(len>=0)
	 break;
#else
      val_status_t val_status;
      bool require_trust = ResMgr::QueryBool("dns:strict-dnssec",hostname);
      len=val_res_search(NULL, srv_name, C_IN, T_SRV, answer, sizeof(answer), &val_status);
      if(len>=0) {
          if(require_trust && !val_istrusted(val_status))
              return;
          else
              break;
      }
#endif
#ifdef HAVE_H_ERRNO
      if(h_errno!=TRY_AGAIN)
	 return;
      if(++retries>=max_retries && max_retries)
	 return;
      time_t t=time(0);
      if(t-try_time<5)
	 sleep(5-(t-try_time));
#else // no h_errno
      return;
#endif
   }

   if(len>(int)sizeof(answer))
      len=sizeof(answer);

   if(len<NS_HFIXEDSZ)
      return;

   int question_count=(answer[4]<<8)+answer[5];
   int answer_count  =(answer[6]<<8)+answer[7];

   // skip header
   unsigned char *scan=answer+NS_HFIXEDSZ;
   len-=NS_HFIXEDSZ;

   // skip questions section
   for( ; question_count>0; question_count--)
   {
      int dom_len=extract_domain(answer,scan,len,0,0);
      if(dom_len<0)
	 return;
      scan+=dom_len;
      len-=dom_len;
      if(len<4)
	 return;
      scan+=4;
      len-=4;
   }

   xarray<SRV> SRVs;

   // now process answers section
   for( ; answer_count>0; answer_count--)
   {
      int dom_len=extract_domain(answer,scan,len,0,0);
      if(dom_len<0)
	 return;
      scan+=dom_len;
      len-=dom_len;
      if(len<8)
	 return;
      scan+=8;
      len-=8;  // skip type,class,ttl

      if(len<2)
	 return;

      int data_len=(scan[0]<<8)+scan[1];
      scan+=2;
      len-=2;

      if(len<data_len)
	 return;

      if(data_len<6)
	 return;

      struct SRV t;
      t.priority=(scan[0]<<8)+scan[1];
      t.weight  =(scan[2]<<8)+scan[3];
      t.port    =(scan[4]<<8)+scan[5];
      t.order=0;

      scan+=6;
      len-=6;

      dom_len=extract_domain(answer,scan,len,t.domain,sizeof(t.domain));
      if(dom_len<0)
	 return;

      scan+=dom_len;
      len-=dom_len;

      // add unless the service is decidedly not available at this domain.
      if(strcmp(t.domain,"."))
	 SRVs.append(t);
   }

   // now sort and randomize the list.
   SRVs.qsort(SRV_compare);

   srand(time(0));

   int SRVscan;
   int base=0;
   int curr_priority=-1;
   int weight_sum=0;
   for(SRVscan=0; ; SRVscan++)
   {
      if(SRVscan==SRVs.count() || SRVs[SRVscan].priority!=curr_priority)
      {
	 if(base)
	 {
	    int o=1;
	    int s;
	    while(weight_sum>0)
	    {
	       int r=int(rand()/(RAND_MAX+1.0)*weight_sum);
	       if(r>=weight_sum)
		  r=weight_sum-1;
	       int w=0;
	       for(s=base; s<SRVscan; s++)
	       {
		  if(SRVs[s].order!=0)
		     continue;
		  w+=SRVs[s].weight;
		  if(r<w)
		  {
		     SRVs[s].order=o;
		     o++;
		     weight_sum-=SRVs[s].weight;
		     break;
		  }
	       }
	    }
	 }
	 if(SRVscan==SRVs.count())
	    break;
	 base=SRVscan;
	 curr_priority=SRVs[SRVscan].priority;
	 weight_sum=0;
      }
      weight_sum+=SRVs[SRVscan].weight;
   }

   SRVs.qsort(SRV_compare);

   int oldport=port_number;
   for(SRVscan=0; SRVscan<SRVs.count(); SRVscan++)
   {
      port_number=htons(SRVs[SRVscan].port);
      LookupOne(SRVs[SRVscan].domain);
   }
   port_number=oldport;

#endif // HAVE_RES_SEARCH
}

void Resolver::LookupOne(const char *name)
{
   time_t try_time;
   int af_index=0;
   int af_order[16];

   const char *order=ResMgr::Query("dns:order",name);

   const char *proto_delim=strchr(name,',');
   if(proto_delim)
   {
      char *o=string_alloca(proto_delim-name+1);
      memcpy(o,name,proto_delim-name);
      o[proto_delim-name]=0;
      // check if the protocol name is valid.
      if(FindAddressFamily(o)!=-1)
	 order=o;
      name=proto_delim+1;
   }

#if LIBIDN2
   xstring_c ascii_name;
   int rc=idn2_lookup_ul(name,ascii_name.buf_ptr(),0);
   if(rc!=IDN2_OK) {
      error=idn2_strerror(rc);
      return;
   }
   name=ascii_name;
#endif//LIBIDN2

   ParseOrder(order,af_order);

   int retries=0;
   int max_retries=ResMgr::Query("dns:max-retries",name);
   for(;;)
   {
      if(!use_fork)
      {
	 Schedule();
	 if(Deleted())
	    return;
      }

      time(&try_time);

// Prefer getaddrinfo over gethostbyname2 and getipnodebyname, as
// approach with multiple lookups works badly when host name is in hosts file
// and no dns servers are reachable.
#if defined(HAVE_GETADDRINFO) && INET6
/* && !defined(HAVE_GETHOSTBYNAME2) \
   && !defined(HAVE_GETIPNODEBYNAME) */

      // getaddrinfo support by Brandon Hume
      struct addrinfo	    *ainfo=0,
			    *a_res,
			    a_hint;
      int		    ainfo_res;
      struct sockaddr	    *sockname;
      struct sockaddr_in    *inet_addr;
      struct sockaddr_in6   *inet6_addr;
      const char	    *addr_data;
      int		    addr_len;
      unsigned int          addr_scope;

      memset(&a_hint, 0, sizeof(a_hint));
      a_hint.ai_flags	    = AI_PASSIVE;
      a_hint.ai_family	    = PF_UNSPEC;

#ifndef DNSSEC_LOCAL_VALIDATION
      ainfo_res	= getaddrinfo(name, NULL, &a_hint, &ainfo);
#else
      val_status_t val_status;
      bool require_trust=ResMgr::QueryBool("dns:strict-dnssec",name);
      ainfo_res	= val_getaddrinfo(NULL, name, NULL, &a_hint, &ainfo,
                                  &val_status);
      if(VAL_GETADDRINFO_HAS_STATUS(ainfo_res) && !val_istrusted(val_status))
      {
          if(require_trust) {
              // untrusted answer
              error = _("DNS resolution not trusted.");
              break;
          } else {
              fprintf(stderr,"\nWARNING: DNS lookup failed validation: %s\n",
                      p_val_status(val_status));
	      fflush(stderr);
          }
      }
#endif

      if(ainfo_res == 0)
      {
        // by lav: add addresses in specified order.
	for(int af=af_order[af_index]; af!=-1; af=af_order[++af_index])
	{
	    for(a_res = ainfo; a_res != NULL; a_res = a_res->ai_next)
	    {
	       if(a_res->ai_family!=af)
		  continue;

	       sockname	= a_res->ai_addr;
	       addr_scope = 0;

	       switch(a_res->ai_family)
	       {
	       case AF_INET:
		  inet_addr   = (sockaddr_in *)sockname;
		  addr_data   = (const char *)&(inet_addr->sin_addr.s_addr);
		  addr_len    = sizeof(inet_addr->sin_addr.s_addr);
		  break;
	       case AF_INET6:
		  inet6_addr  = (sockaddr_in6 *)sockname;
		  addr_data   = (const char *)&(inet6_addr->sin6_addr.s6_addr);
#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
		  addr_scope  = inet6_addr->sin6_scope_id;
#endif
		  addr_len    = sizeof(inet6_addr->sin6_addr.s6_addr);
		  break;
	       default:
		  continue;
	       }
	       AddAddress(a_res->ai_family, addr_data, addr_len, addr_scope);
	    }
	 }

	 freeaddrinfo(ainfo);
	 break;
      }

      if(ainfo_res != EAI_AGAIN
      || (++retries>=max_retries && max_retries))
      {
	 error = gai_strerror(ainfo_res);
	 break;
      }

#else // !HAVE_GETADDRINFO

      int af=af_order[af_index];
      if(af==-1)
	 break;

      struct hostent *ha;
# if defined(HAVE_GETIPNODEBYNAME)
#  ifndef HAVE_H_ERRNO
#   define HAVE_H_ERRNO 1
#  endif
#  undef h_errno // it could be a macro, but we want it to be local variable.
      int h_errno=0;
      ha=getipnodebyname(name,af,0,&h_errno);
# elif defined(HAVE_GETHOSTBYNAME2)
      ha=gethostbyname2(name,af);
# else
      if(af==AF_INET)
	 ha=gethostbyname(name);
      else
      {
	 retries=0;
	 af_index++;
	 continue;
      }
# endif

      if(ha)
      {
	 const char * const *a;
	 for(a=ha->h_addr_list; *a; a++)
	    AddAddress(ha->h_addrtype, *a, ha->h_length, 0);
	 retries=0;
	 af_index++;
# if defined(HAVE_GETIPNODEBYNAME)
	 freehostent(ha);
# endif
	 continue;
      }

# ifdef HAVE_H_ERRNO
      if(h_errno!=TRY_AGAIN
      || (++retries>=max_retries && max_retries))
# endif
      {
	 if(error==0)
	 {
# ifdef HAVE_H_ERRNO
	    error=hstrerror(h_errno);
# else
	    error=_("Host name lookup failure");
# endif
	 }
	 retries=0;
	 af_index++;
	 continue; // try other address families
      }
#endif /* HAVE_GETADDRINFO */

      time_t t;
      if((t=time(0))-try_time<5)
	 sleep(5-(t-try_time));
   }
}

void Resolver::DoGethostbyname()
{
   if(port_number==0)
   {
      const char *tproto=proto?proto.get():"tcp";
      const char *tport=portname?portname.get():defport.get();

      if(isdigit((unsigned char)tport[0]))
	 port_number=htons(atoi(tport));
      else
      {
	 struct servent *se=getservbyname(tport,tproto);
	 if(se)
	    port_number=se->s_port;
	 else
	 {
	    buf->Put("P");
	    buf->Format(_("no such %s service"),tproto);
	    return;
	 }
      }
   }

   if(service && !portname && !isdigit((unsigned char)hostname[0]))
      LookupSRV_RR();

   if(!use_fork && Deleted())
      return;

   const char *h=ResMgr::Query("dns:name",hostname);
   if(!h || !*h)
      h=hostname;
   char *hs=alloca_strdup(h);
   char *tok;
   for(hs=strtok_r(hs,",",&tok); hs; hs=strtok_r(NULL,",",&tok))
      LookupOne(hs);

   if(!use_fork && Deleted())
      return;

   if(addr.count()==0)
   {
      buf->Put("E");
      if(error==0)
	 error=_("No address found");
      buf->Put(error);
      return;
   }
   buf->Put("O");
   buf->Put((const char*)addr.get(),addr.count()*addr.get_element_size());
   addr.unset();
}

void Resolver::Reconfig(const char *name)
{
   if(!name || strncmp(name,"dns:",4))
      return;
}


ResolverCache::ResolverCache()
   : Cache(ResMgr::FindRes("dns:cache-size"),ResMgr::FindRes("dns:cache-enable"))
{
}
void ResolverCache::Reconfig(const char *r)
{
   if(!xstrcmp(r,"dns:SRV-query")
   || !xstrcmp(r,"dns:order"))
      Flush();
}
ResolverCacheEntry *ResolverCache::Find(const char *h,const char *p,const char *defp,const char *ser,const char *pr)
{
   for(ResolverCacheEntry *c=IterateFirst(); c; c=IterateNext())
   {
      if(c->Matches(h,p,defp,ser,pr))
	 return c;
   }
   return 0;
}
void ResolverCache::Add(const char *h,const char *p,const char *defp,
	 const char *ser,const char *pr,const sockaddr_u *a,int n)
{
   Trim();
   ResolverCacheEntry *c=Find(h,p,defp,ser,pr);
   if(c)
      c->SetData(a,n);
   else
   {
      if(!IsEnabled(h))
	 return;
      AddCacheEntry(new ResolverCacheEntry(h,p,defp,ser,pr,a,n));
   }
}
bool ResolverCacheEntryLoc::Matches(const char *h,const char *p,
	 const char *defp,const char *ser,const char *pr)
{
   return (!xstrcasecmp(hostname,h)
      && !xstrcmp(portname,p)
      && !xstrcmp(defport,defp)
      && !xstrcmp(service,ser)
      && !xstrcmp(proto,pr));
}
void ResolverCache::Find(const char *h,const char *p,const char *defp,
	 const char *ser,const char *pr,const sockaddr_u **a,int *n)
{
   *a=0;
   *n=0;

   // if cache is disabled for this host, return nothing.
   if(!IsEnabled(h))
      return;

   ResolverCacheEntry *c=Find(h,p,defp,ser,pr);
   if(c)
   {
      if(c->Stopped())
      {
	 Trim();
	 return;
      }
      c->GetData(a,n);
   }
}