/*
 * src/res.c (C)opyright 1992 Darren Reed. All rights reserved. This
 * file may not be distributed without the author's permission in any
 * shape or form. The author takes no responsibility for any damage or
 * loss of property which results from the use of this software.
 */

/* $Id: res.c,v 1.3 2006/01/07 22:13:26 trystanscott Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "res.h"
#include "numeric.h"
#include "h.h"
#include "fds.h"
#include "memcount.h"

#include <signal.h>
#include <sys/time.h>
#include <sys/socket.h>
#include "nameser.h"
#include "resolv.h"
#include "inet.h"

/* ALLOW_CACHE_NAMES
 *
 * If enabled, this allows our resolver code to keep a hash table
 * of names, for which we find in gethost_byname calls.
 * This presents a few problems with anti-spoofing code.
 *
 * Since the majority of our host lookups are reverse, having
 * a cached record for reverse records (addresses) seems useful.
 * If, for some reason, you want this on, you may define it.
 */
#undef ALLOW_CACHE_NAMES

/* SEARCH_CACHE_ADDRESSES
 *
 * All of our records will probably only have one valid IP address.
 * If you want to search for multiple addresses, define this.
 * (In the current implementation, it should not really be possible
 * to get multiple addresses.)
 *
 * If not, it saves CPU as a cache miss does not traverse the
 * entire cache tree for a result.
 */
#undef SEARCH_CACHE_ADDRESSES

#define PROCANSWER_STRANGE   -2 /* invalid answer or query, try again */
#define PROCANSWER_MALICIOUS -3 /* obviously malicious reply, \
				                 * don't do DNS on this ip. */

#undef	DEBUG			/* because theres alot of debug code in here */

extern int  dn_expand(char *, char *, char *, char *, int);
extern int  dn_skipname(char *, char *);
extern int
res_mkquery(int, char *, int, int, char *, int,
	    struct rrec *, char *, int);

#ifndef AIX
extern int  errno, h_errno;
#endif
extern int  highest_fd;
extern aClient *local[];

static char hostbuf[HOSTLEN + 1];
static int  incache = 0;
static CacheTable hashtable[ARES_CACSIZE];
static ResHash idcphashtable[ARES_IDCACSIZE];
aCache *cachetop = NULL;
static ResRQ *last, *first;

static void rem_cache(aCache *);
static void rem_request(ResRQ *);
static int  do_query_name(Link *, char *, ResRQ *);
static int  do_query_number(Link *, struct in_addr *, ResRQ *);
static void resend_query(ResRQ *);
static int  proc_answer(ResRQ *, HEADER *, char *, char *);
static int  query_name(char *, int, int, ResRQ *);
static aCache *make_cache(ResRQ *);
static aCache *find_cache_name(char *);
static aCache *find_cache_number(ResRQ *, char *);
static int  add_request(ResRQ *);
static ResRQ *make_request(Link *);
static int  send_res_msg(char *, int, int);
static ResRQ *find_id(int);
static int  hash_number(unsigned char *);
static unsigned int hash_id(unsigned int);
static unsigned int hash_cp(char *);
static void update_list(ResRQ *, aCache *);
#ifdef ALLOW_CACHE_NAMES
static int  hash_name(char *);
#endif
static struct hostent *getres_err(ResRQ *, char *);

static struct cacheinfo
{
    int         ca_adds;
    int         ca_dels;
    int         ca_expires;
    int         ca_lookups;
    int         ca_na_hits;
    int         ca_nu_hits;
    int         ca_updates;
} cainfo;

static struct resinfo
{
    int         re_errors;
    int         re_nu_look;
    int         re_na_look;
    int         re_replies;
    int         re_requests;
    int         re_resends;
    int         re_sent;
    int         re_timeouts;
    int         re_shortttl;
    int         re_unkrep;
} reinfo;

int init_resolver(int op)
{
    int         ret = 0;
    
#ifdef	LRAND48
    srand48(timeofday);
#endif
    if (op & RES_INITLIST)
    {
	memset((char *) &reinfo, '\0', sizeof(reinfo));
	first = last = NULL;
    }
    if (op & RES_CALLINIT)
    {
	ret = res_init();
	if (!_res.nscount)
	{
	    _res.nscount = 1;
	    _res.nsaddr_list[0].sin_addr.s_addr = inet_addr("127.0.0.1");
	}
    }
    
    if (op & RES_INITSOCK)
    {
	int         on = 0;
	
	ret = resfd = socket(AF_INET, SOCK_DGRAM, 0);
	(void) setsockopt(ret, SOL_SOCKET, SO_BROADCAST,
			  (char *) &on, sizeof(on));
    }
#ifdef DEBUG
    if (op & RES_INITDEBG);
    _res.options |= RES_DEBUG;
#endif
    if (op & RES_INITCACH)
    {
	memset((char *) &cainfo, '\0', sizeof(cainfo));
	memset((char *) hashtable, '\0', sizeof(hashtable));
	memset((char *) idcphashtable, '\0', sizeof(idcphashtable));
    }
    if (op == 0)
	ret = resfd;
    return ret;
}

static int add_request(ResRQ * new)
{
    if (!new)
	return -1;
    if (!first)
	first = last = new;
    else {
	last->next = new;
	last = new;
    }
    new->next = NULL;
    reinfo.re_requests++;
    return 0;
}

static void rem_request_id(ResRQ *req)
{
   unsigned int hv = hash_id(req->id);
   ResRQ *rptr, *r2ptr = NULL;

   for(rptr = idcphashtable[hv].id_list; rptr; r2ptr = rptr, rptr = rptr->id_hashnext)
   {
      if(rptr != req)
         continue;

      if(r2ptr != NULL)
         r2ptr->id_hashnext = req->id_hashnext;
      else
         idcphashtable[hv].id_list = req->id_hashnext;
      break;
   }
}

static void add_request_id(ResRQ *req)
{
   unsigned int hv = hash_id(req->id);

   req->id_hashnext = idcphashtable[hv].id_list;
   idcphashtable[hv].id_list = req;
}

static ResRQ *find_request_id(int id)
{
   unsigned int hv = hash_id(id);
   ResRQ *res = idcphashtable[hv].id_list;
   
   while(res)
   {
      if(res->id == id)
         return res;
      res = res->id_hashnext;
   }
   return NULL;
}

static void rem_request_cp(ResRQ *req)
{
   unsigned int hv = hash_cp(req->cinfo.value.cp);
   ResRQ *rptr, *r2ptr = NULL;

   for(rptr = idcphashtable[hv].cp_list; rptr; r2ptr = rptr, rptr = rptr->cp_hashnext)
   {
      if(rptr != req)
         continue;

      if(r2ptr != NULL)
         r2ptr->cp_hashnext = req->cp_hashnext;
      else
         idcphashtable[hv].cp_list = req->cp_hashnext;
      break;
   }
}

static void add_request_cp(ResRQ *req)
{
   unsigned int hv = hash_cp(req->cinfo.value.cp);

   req->cp_hashnext = idcphashtable[hv].cp_list;
   idcphashtable[hv].cp_list = req;
}

static ResRQ *find_request_cp(char *cp)
{
   unsigned int hv = hash_cp(cp);
   ResRQ *res = idcphashtable[hv].cp_list;
   
   while(res)
   {
      if(res->cinfo.value.cp == cp)
         return res;
      res = res->cp_hashnext;
   }
   return NULL;
}

/*
 * remove a request from the list. This must also free any memory that
 * has been allocated for temporary storage of DNS results.
 */
static void rem_request(ResRQ * old)
{
    ResRQ **rptr, *r2ptr = NULL;
    int     i;
    char   *s;
    
    if (!old)
	return;

    if(old->id != -1)
    {
        rem_request_id(old);
        old->id = -1;
    }

    if(old->cinfo.value.cp != NULL)
       rem_request_cp(old);

    for (rptr = &first; *rptr; r2ptr = *rptr, rptr = &(*rptr)->next)
	if (*rptr == old)
	{
	    *rptr = old->next;
	    if (last == old)
		last = r2ptr;
	    break;
	}
#ifdef	DEBUG
    Debug((DEBUG_INFO, "rem_request:Remove %#x at %#x %#x",
	   old, *rptr, r2ptr));
#endif
    r2ptr = old;
    
    if (r2ptr->he.h_name)
	MyFree(r2ptr->he.h_name);
    for (i = 0; i < IRC_MAXALIASES; i++)
	if ((s = r2ptr->he.h_aliases[i]))
	    MyFree(s);
    
    if (r2ptr->he_rev.h_name)
	MyFree(r2ptr->he_rev.h_name);
    for (i = 0; i < IRC_MAXALIASES; i++)
	if ((s = r2ptr->he_rev.h_aliases[i]))
	    MyFree(s);
    
    if (r2ptr->name)
	MyFree(r2ptr->name);
    MyFree(r2ptr);

    return;
}

/* Create a DNS request record for the server. */
static ResRQ *make_request(Link *lp)
{
    ResRQ  *nreq;
    
    nreq = (ResRQ *) MyMalloc(sizeof(ResRQ));
    memset((char *) nreq, '\0', sizeof(ResRQ));
    nreq->next = NULL;		/*  where NULL is non-zero */
    nreq->sentat = timeofday;
    nreq->retries = 3;
    nreq->resend = 1;
    nreq->srch = -1;
    nreq->id = -1;
    if (lp)
    {
	memcpy((char *) &nreq->cinfo, (char *) lp, sizeof(Link));
        add_request_cp(nreq);
    }
    else
	memset((char *) &nreq->cinfo, '\0', sizeof(Link));
    
    nreq->timeout = 4;		/* start at 4 and exponential inc. */
    nreq->he.h_addrtype = AF_INET;
    nreq->he.h_name = NULL;
    nreq->he.h_aliases[0] = NULL;
    (void) add_request(nreq);
    return nreq;
}

/*
 * Remove queries from the list which have been there too long without
 * being resolved.
 */
time_t timeout_query_list(time_t now)
{
    ResRQ  *rptr, *r2ptr;
    time_t  next = 0, tout;
    aClient    *cptr;

    Debug((DEBUG_DNS, "timeout_query_list at %s", myctime(now)));
    for (rptr = first; rptr; rptr = r2ptr)
    {
	r2ptr = rptr->next;
	tout = rptr->sentat + rptr->timeout;
	if (now >= tout)
	{
	    if (--rptr->retries <= 0)
	    {
#ifdef DEBUG
		Debug((DEBUG_ERROR, "timeout %x now %d cptr %x",
		       rptr, now, rptr->cinfo.value.cptr));
#endif
		reinfo.re_timeouts++;
		cptr = rptr->cinfo.value.cptr;
		switch (rptr->cinfo.flags)
		{
		case ASYNC_CLIENT:
#ifdef SHOW_HEADERS
		    sendto_one(cptr, REPORT_FAIL_DNS);
#endif
		    ClearDNS(cptr);
                    check_client_fd(cptr);
		    break;

		case ASYNC_CONNECT:
		    sendto_ops("Host %s unknown",
			       rptr->name);
		    break;
		}
		rem_request(rptr);
		continue;
	    }
	    else
	    {
		rptr->sentat = now;
		rptr->timeout += rptr->timeout;
		resend_query(rptr);
#ifdef DEBUG
		Debug((DEBUG_INFO, "r %x now %d retry %d c %x",
		       rptr, now, rptr->retries,
		       rptr->cinfo.value.cptr));
#endif
	    }
	}
	if (!next || tout < next)
	    next = tout;
    }
    return (next > now) ? next : (now + AR_TTL);
}

/*
 * del_queries - called by the server to cleanup outstanding queries
 * for which there no longer exist clients or conf lines.
 */
void del_queries(char *cp)
{
    ResRQ  *ret = find_request_cp(cp);

    if(ret)
       rem_request(ret);
}

/*
 * sends msg to all nameservers found in the "_res" structure. This
 * should reflect /etc/resolv.conf. We will get responses which arent
 * needed but is easier than checking to see if nameserver isnt
 * present. Returns number of messages successfully sent to nameservers
 * or -1 if no successful sends.
 */
static int send_res_msg(char *msg, int len, int rcount)
{
    int     i;
    int         sent = 0, max;

    if (!msg)
	return -1;
    
    max = MIN(_res.nscount, rcount);
    if (_res.options & RES_PRIMARY)
	max = 1;
    if (!max)
	max = 1;

    for (i = 0; i < max; i++)
    {
	_res.nsaddr_list[i].sin_family = AF_INET;
	if (sendto(resfd, msg, len, 0,
		   (struct sockaddr *) &(_res.nsaddr_list[i]),
		   sizeof(struct sockaddr)) == len)
	{
	    reinfo.re_sent++;
	    sent++;
	}
	else
	    Debug((DEBUG_ERROR, "s_r_m:sendto: %d on %d",
		   errno, resfd));
    }
    
    return (sent) ? sent : -1;
}

/* find a dns request id (id is determined by dn_mkquery) */
static ResRQ *find_id(int id)
{
    ResRQ  *ret = find_request_id(id);

    return ret;
}

struct hostent *gethost_byname(char *name, Link *lp)
{
    aCache *cp;
    
    if (name == (char *) NULL)
	return ((struct hostent *) NULL);
    
    reinfo.re_na_look++;
    if ((cp = find_cache_name(name)))
	return (struct hostent *) &(cp->he);
    if (!lp)
	return NULL;
    (void) do_query_name(lp, name, NULL);
    return ((struct hostent *) NULL);
}

struct hostent *gethost_byaddr(char *addr, Link *lp)
{
    aCache     *cp;

    if (addr == (char *) NULL)
	return ((struct hostent *) NULL);

    reinfo.re_nu_look++;
    if ((cp = find_cache_number(NULL, addr)))
	return (struct hostent *) &(cp->he);
    if (!lp)
	return NULL;
    (void) do_query_number(lp, (struct in_addr *) addr, NULL);
    return ((struct hostent *) NULL);
}

static int do_query_name(Link *lp, char *name, ResRQ * rptr)
{
    char        hname[HOSTLEN + 1];
    int         len;
    
    strncpyzt(hname, name, HOSTLEN);
    len = strlen(hname);
    
    if (rptr && !strchr(hname, '.') && _res.options & RES_DEFNAMES)
    {
	if ((sizeof(hname) - len - 1) >= 2)
	{
	    (void) strncat(hname, ".", sizeof(hname) - len - 1);
	    len++;
	    if ((sizeof(hname) - len - 1) >= 1)
		(void) strncat(hname, _res.defdname, sizeof(hname) - len - 1);
	}
    }
    /*
     * Store the name passed as the one to lookup and generate other
     * host names to pass onto the nameserver(s) for lookups.
     */
    if (!rptr)
    {
	rptr = make_request(lp);
	rptr->type = T_A;
	rptr->name = (char *) MyMalloc(strlen(name) + 1);
	(void) strcpy(rptr->name, name);
    }
    return (query_name(hname, C_IN, T_A, rptr));
}

/* Use this to do reverse IP# lookups. */
static int do_query_number(Link *lp, struct in_addr *numb, ResRQ * rptr)
{
    char        ipbuf[32];
    u_char *cp;

    cp = (u_char *) &numb->s_addr;
    (void) ircsprintf(ipbuf, "%u.%u.%u.%u.in-addr.arpa.",
		      (u_int) (cp[3]), (u_int) (cp[2]),
		      (u_int) (cp[1]), (u_int) (cp[0]));

    if (!rptr)
    {
	rptr = make_request(lp);
	rptr->type = T_PTR;
	rptr->addr.s_addr = numb->s_addr;
	memcpy((char *) &rptr->he.h_addr,
	       (char *) &numb->s_addr, sizeof(struct in_addr));
	rptr->he.h_length = sizeof(struct in_addr);
    }
    return (query_name(ipbuf, C_IN, T_PTR, rptr));
}

/* generate a query based on class, type and name. */
static int query_name(char *name, int class, int type, ResRQ * rptr)
{
    struct timeval tv;
    char        buf[MAXPACKET];
    int         r, s, k = 0;
    HEADER     *hptr;

    memset(buf, '\0', sizeof(buf));
    r = res_mkquery(QUERY, name, class, type, NULL, 0, NULL,
		    buf, sizeof(buf));
    if (r <= 0)
    {
	h_errno = NO_RECOVERY;
	return r;
    }

    if(rptr->id != -1)
        rem_request_id(rptr);

    hptr = (HEADER *) buf;
#ifdef LRAND48
    do
    {
	hptr->id = htons(ntohs(hptr->id) + k + lrand48() & 0xffff);
#else
	(void) gettimeofday(&tv, NULL);
    do
    {
#if 0 /* emacs kludge */
    }
#endif
        hptr->id = htons(ntohs(hptr->id) + k +
			 (u_short) (tv.tv_usec & 0xffff));
#endif /* LRAND48 */
	k++;
    } while (find_id(ntohs(hptr->id)));
    rptr->id = ntohs(hptr->id);
    add_request_id(rptr);
    rptr->sends++;
    s = send_res_msg(buf, r, rptr->sends);
    if (s == -1)
    {
	h_errno = TRY_AGAIN;
	return -1;
    }
    else
	rptr->sent += s;
    return 0;
}
    
static void resend_query(ResRQ * rptr)
{
    if (rptr->resend == 0)
	return;
    reinfo.re_resends++;
    switch (rptr->type)
    {
    case T_PTR:
	(void) do_query_number(NULL, &rptr->addr, rptr);
	break;
    case T_A:
	(void) do_query_name(NULL, rptr->name, rptr);
	break;
    default:
	break;
    }
    return;
}

/* returns 0 on failure, nonzero on success */
int arpa_to_ip(char *arpastring, unsigned int *saddr)
{
    int idx = 0, onum = 0;
    char ipbuf[HOSTLEN + 1];
    char *fragptr[4];
    u_char *ipptr;
         
    strcpy(ipbuf, arpastring);

    /* ipbuf should contain a string in the format of 4.3.2.1.in-addr.arpa */
    
    fragptr[onum++] = ipbuf;

    while(ipbuf[idx])
    {
	if(ipbuf[idx] == '.')
	{
	    ipbuf[idx++] = '\0';
	    if(onum == 4)
		break;
	    fragptr[onum++] = ipbuf + idx;
	}
	else
	    idx++;
    }

    if(onum != 4)
	return 0;

    if(mycmp(ipbuf + idx, "in-addr.arpa"))
	return 0;

    ipptr = (u_char *) saddr;

    ipptr[0] = (u_char) atoi(fragptr[3]);
    ipptr[1] = (u_char) atoi(fragptr[2]);
    ipptr[2] = (u_char) atoi(fragptr[1]);
    ipptr[3] = (u_char) atoi(fragptr[0]);
    return 1;
}

#undef DNS_ANS_DEBUG_MAX
#undef DNS_ANS_DEBUG

#define MAX_ACCEPTABLE_ANS 10

static char acceptable_answers[MAX_ACCEPTABLE_ANS][HOSTLEN + 1];
static int num_acc_answers = 0;

#define add_acceptable_answer(x) do { \
           if(num_acc_answers < MAX_ACCEPTABLE_ANS) \
           strcpy(acceptable_answers[num_acc_answers++], x); } while (0);
	   
static inline char *is_acceptable_answer(char *h)
{
    int i;

    for (i = 0; i < num_acc_answers; i++) 
    {
	if(mycmp(acceptable_answers[i], h) == 0)
	    return acceptable_answers[i];
    }
    return 0;
}

#ifdef DNS_ANS_DEBUG_MAX
static char dhostbuf[HOSTLEN + 1];
#endif

/* process name server reply. */
static int proc_answer(ResRQ * rptr, HEADER *hptr, char *buf, char *eob)
{
    char   *cp, **alias, *acc;
    struct hent *hp;
    int class, type, dlen, len, ans = 0, n, origtype = rptr->type;
    int adr = 0;
    struct in_addr ptrrep, dr;

    num_acc_answers = 0;
    
    cp = buf + sizeof(HEADER);
    hp = (struct hent *) &(rptr->he);

    while ((hp->h_addr_list[adr].s_addr) && (adr < IRC_MAXADDRS))
	adr++;

    alias = hp->h_aliases;
    while (*alias)
	alias++;

    if(hptr->qdcount != 1)
    {
	sendto_realops_lev(DEBUG_LEV,
			   "DNS packet with question count of %d ",
			   hptr->qdcount);
	return -1;
    }

    /*
     * ensure the question we're getting a reply for
     * is a the right question.
     */

    if((n = dn_expand(buf, eob, cp, hostbuf, sizeof(hostbuf))) <= 0)
    {
	/* broken dns packet, toss it out */
	return -1;
    }
    else
    {
	int strangeness = 0;
	char tmphost[HOSTLEN];

	hostbuf[HOSTLEN] = '\0';
	cp += n;
	type = (int) _getshort(cp);
	cp += sizeof(short);
	class = (int) _getshort(cp);
	cp += sizeof(short);
	if(class != C_IN)
	{
	    sendto_realops_lev(DEBUG_LEV,
			       "Expected DNS packet class C_IN, got %d ",
			       class);
	    strangeness++;
	}

	if(type != rptr->type)
	{
	    sendto_realops_lev(DEBUG_LEV,
			       "Expected DNS packet type %d, got %d ",
			       rptr->type, type);
	    strangeness++;
	}

	if(rptr->type == T_A && rptr->name)
	{
	    strcpy(tmphost, rptr->name);
	}
	else if(rptr->type == T_PTR)
	{
	    u_char *ipp;
	    
	    ipp = (u_char *) &rptr->addr.s_addr;
	    ircsprintf(tmphost, "%u.%u.%u.%u.in-addr.arpa",
		       (u_int) (ipp[3]), (u_int) (ipp[2]),
		       (u_int) (ipp[1]), (u_int) (ipp[0]));  
	}
	else
	{
	    sendto_realops_lev(DEBUG_LEV,
			       "rptr->type is unknown type %d! "
			       "(rptr->name == %x)", 
			       rptr->type, rptr->name);
	    return -1;
	}    

	if(mycmp(tmphost, hostbuf) != 0)
	{
	    sendto_realops_lev(DEBUG_LEV, "Asked question for %s, but got "
			       "reply about question %s (!!!)",
			       tmphost, hostbuf);
	    strangeness++;
	}
	
	if(strangeness)
	    return PROCANSWER_STRANGE;
    }

    /* proccess each answer sent to us blech. */
    while (hptr->ancount-- > 0 && cp && cp < eob) 
    {
	n = dn_expand(buf, eob, cp, hostbuf, sizeof(hostbuf));
	hostbuf[HOSTLEN] = '\0';
	
	if (n <= 0)
	    break;
	cp += n;
	type = (int) _getshort(cp);
	cp += sizeof(short);
	
	class = (int) _getshort(cp);
	cp += sizeof(short);
	
	rptr->ttl = _getlong(cp);
	cp += sizeof(rptr->ttl);
	dlen = (int) _getshort(cp);
	cp += sizeof(short);
	
	/* Wait to set rptr->type until we verify this structure */

	len = strlen(hostbuf);
	/* name server never returns with trailing '.' */
	if (!strchr(hostbuf, '.') && (_res.options & RES_DEFNAMES))
	{
	    (void) strcat(hostbuf, ".");
	    len++;
	    if ((len + 2) < sizeof(hostbuf))
	    {
		strncpy(hostbuf, _res.defdname,
			sizeof(hostbuf) - 1 - len);
		hostbuf[HOSTLEN] = '\0';
		len = MIN(len + strlen(_res.defdname),
			  sizeof(hostbuf)) - 1;
	    }
	}
	
#ifdef DNS_ANS_DEBUG_MAX
	strcpy(dhostbuf, hostbuf);
#endif
	
	switch (type)
	{
	case T_A:
	    if(rptr->name == NULL)
	    {
		sendto_realops_lev(DEBUG_LEV,"Received DNS_A answer, but null "
				   "rptr->name!");
		return PROCANSWER_STRANGE;
	    }
	    if(mycmp(rptr->name, hostbuf) != 0)
	    {
		if(!num_acc_answers || !(acc = is_acceptable_answer(hostbuf)))
		{
#ifdef DNS_ANS_DEBUG
		    sendto_realops_lev(DEBUG_LEV,
				       "Received DNS_A answer for %s, but "
				       "asked question for %s", hostbuf,
				       rptr->name);
#endif
		    return PROCANSWER_STRANGE;
		}
#ifdef DNS_ANS_DEBUG
		sendto_realops_lev(DEBUG_LEV,
				   "DNS_A answer from an acceptable (%s)",
				   acc);
#endif
	    }
	    hp->h_length = dlen;
	    if (ans == 1)
		hp->h_addrtype = (class == C_IN) ? AF_INET : AF_UNSPEC;
	    /* from Christophe Kalt <kalt@stealth.net> */
	    if (dlen != sizeof(dr)) 
	    {
		sendto_realops("Bad IP length (%d) returned for %s",
			       dlen, hostbuf);
		Debug((DEBUG_DNS, "Bad IP length (%d) returned for %s",
		       dlen, hostbuf));
		return PROCANSWER_MALICIOUS;
	    }

	    if(adr < IRC_MAXADDRS)
	    {
		/* ensure we never go over the bounds of our adr array */
		memcpy((char *)&dr, cp, sizeof(dr));
		hp->h_addr_list[adr].s_addr = dr.s_addr;
		Debug((DEBUG_INFO, "got ip # %s for %s",
		       inetntoa((char *) &hp->h_addr_list[adr]), hostbuf));
		
#ifdef DNS_ANS_DEBUG_MAX
		sendto_realops_lev(DEBUG_LEV, "%s A %s", dhostbuf,
				   inetntoa((char *) &hp->h_addr_list[adr]));
#endif
		adr++;
	    }
	    
	    if (!hp->h_name) 
	    {
		hp->h_name = (char *) MyMalloc(len + 1);
		strcpy(hp->h_name, hostbuf);
	    }
	    ans++;
	    cp += dlen;
	    rptr->type = type;
	    break;
	    
	case T_PTR:
	    acc = NULL;
	    if(!num_acc_answers || !(acc = is_acceptable_answer(hostbuf)))
	    {
		if(!(arpa_to_ip(hostbuf, &ptrrep.s_addr)))
		{
#ifdef DNS_ANS_DEBUG
		    sendto_realops_lev(DEBUG_LEV, 
				       "Received strangely formed PTR answer "
				       "for %s (asked for %s) -- ignoring", 
				       hostbuf, inetntoa((char *)&rptr->addr));
#endif
		    return PROCANSWER_STRANGE;
		}
		
		if(ptrrep.s_addr != rptr->addr.s_addr)
		{
#ifdef DNS_ANS_DEBUG
		    char ipbuf[16];
		    
		    strcpy(ipbuf, inetntoa((char *)&ptrrep));
		    sendto_realops_lev(DEBUG_LEV,
				       "Received DNS_PTR answer for %s, "
				       "but asked question for %s", 
				       ipbuf, inetntoa((char*)&rptr->addr));
#endif
		    return PROCANSWER_STRANGE;
		}
	    }
	    
#ifdef DNS_ANS_DEBUG
	    if(acc)
		sendto_realops_lev(DEBUG_LEV, 
				   "DNS_PTR from an acceptable (%s)", acc);
#endif
	    
	    if ((n = dn_expand(buf, eob, cp, hostbuf,
			       sizeof(hostbuf))) < 0) 
	    {
		cp = NULL;
		break;
	    }
	    
	    /*
	     * This comment is based on analysis by Shadowfax,
	     * Jolo and johan, not me. (Dianora) I am only
	     * commenting it.
	     * 
	     * dn_expand is guaranteed to not return more than
	     * sizeof(hostbuf) but do all implementations of
	     * dn_expand also guarantee buffer is terminated with
	     * null byte? Lets not take chances. -Dianora
	     */
	    hostbuf[HOSTLEN] = '\0';
	    cp += n;
	    len = strlen(hostbuf);
	    
#ifdef DNS_ANS_DEBUG_MAX
	    sendto_realops_lev(DEBUG_LEV, "%s PTR %s", dhostbuf, hostbuf);
#endif
	    
	    Debug((DEBUG_INFO, "got host %s", hostbuf));
	    /*
	     * copy the returned hostname into the host name or
	     * alias field if there is a known hostname already.
	     */
	    if (hp->h_name) 
	    {
		/*
		 * This is really fishy. In fact, so fishy,
		 * that I say we just don't do this in this case.
		 *
		 * seems to happen with a whole host of .my addresses.
		 * interesting. - lucas
		 */
		
		if (alias >= &(hp->h_aliases[IRC_MAXALIASES - 1]))
		    break;
		*alias = (char *) MyMalloc(len + 1);
		strcpy(*alias++, hostbuf);
		*alias = NULL;
	    }
	    else 
	    {
		hp->h_name = (char *) MyMalloc(len + 1);
		strcpy(hp->h_name, hostbuf);
	    }
	    ans++;
	    rptr->type = type;
	    break;
	    
	case T_CNAME:
	    acc = NULL;
	    
	    if(origtype == T_PTR)
	    {
		if(!num_acc_answers || !(acc = is_acceptable_answer(hostbuf)))
		{
		    if(!(arpa_to_ip(hostbuf, &ptrrep.s_addr)))
		    {
#ifdef DNS_ANS_DEBUG
			sendto_realops_lev(DEBUG_LEV,
					   "Received strangely formed "
					   "CNAME(PTR) answer for %s (asked "
					   "for %s) -- ignoring", 
					   hostbuf,
					   inetntoa((char *)&rptr->addr));
#endif
			return PROCANSWER_STRANGE;
		    }

		    if(ptrrep.s_addr != rptr->addr.s_addr)
		    {
#ifdef DNS_ANS_DEBUG
			char ipbuf[16];
			
			strcpy(ipbuf, inetntoa((char *)&ptrrep));
			sendto_realops_lev(DEBUG_LEV, "Received "
					   "DNS_CNAME(PTR) answer for %s, "
					   "but asked question for %s", 
					   ipbuf, 
					   inetntoa((char*)&rptr->addr));
#endif
			return PROCANSWER_STRANGE;
		    }
		}
#ifdef DNS_ANS_DEBUG
		if(acc)
		    sendto_realops_lev(DEBUG_LEV, "DNS_CNAME (PTR) answer "
				       "from an acceptable (%s)", acc);
#endif
	    }
	    else if(origtype == T_A)
	    {
		if(mycmp(rptr->name, hostbuf) != 0)
		{
		    if(!num_acc_answers || !(acc = is_acceptable_answer(hostbuf)))
		    {
#ifdef DNS_ANS_DEBUG
			sendto_realops_lev(DEBUG_LEV, "Received DNS_CNAME(A) "
					   "answer for %s, but asked "
					   "question for %s", 
					   hostbuf, rptr->name);
#endif
			return PROCANSWER_STRANGE;
		    }
#ifdef DNS_ANS_DEBUG
		    sendto_realops_lev(DEBUG_LEV, "DNS_CNAME (A) answer from "
				       "an acceptable (%s)", acc);
#endif
		}
	    }
	    
	    Debug((DEBUG_INFO, "got cname %s", hostbuf));
	    
	    if (alias >= &(hp->h_aliases[IRC_MAXALIASES - 1]))
		break;
	    *alias = (char *) MyMalloc(len + 1);
	    strcpy(*alias++, hostbuf);
	    *alias = NULL;
	    ans++;
	    rptr->type = type;
	    
	    if ((n = dn_expand(buf, eob, cp, hostbuf, sizeof(hostbuf))) < 0)
	    {
		cp = NULL;
		break;
	    }
	    
	    hostbuf[HOSTLEN] = '\0';
	    cp += n;
	    
	    add_acceptable_answer(hostbuf);
	    
#ifdef DNS_ANS_DEBUG_MAX
	    sendto_realops_lev(DEBUG_LEV, "%s CNAME %s", dhostbuf, hostbuf);
#endif
	    
	    break;
	    
	default:
#ifdef DEBUG
	    Debug((DEBUG_INFO, "proc_answer: type:%d for:%s",
		   type, hostbuf));
#endif
	    break;
	}
    }
    return ans;
}

/*
 * read a dns reply from the nameserver and process it.
 */
struct hostent *get_res(char *lp)
{
    static char buf[sizeof(HEADER) + MAXPACKET];
    HEADER *hptr;
    ResRQ  *rptr = NULL;
    aCache     *cp = (aCache *) NULL;
    struct sockaddr_in sin;
    int         rc, a, max;
    socklen_t len = sizeof(sin);
    
    rc = recvfrom(resfd, buf, sizeof(buf), 0, (struct sockaddr *) &sin, &len);
    if (rc <= sizeof(HEADER))
	return getres_err(rptr, lp);
    
    /*
     * convert DNS reply reader from Network byte order to CPU byte
     * order.
     */
    hptr = (HEADER *) buf;
    hptr->id = ntohs(hptr->id);
    hptr->ancount = ntohs(hptr->ancount);
    hptr->qdcount = ntohs(hptr->qdcount);
    hptr->nscount = ntohs(hptr->nscount);
    hptr->arcount = ntohs(hptr->arcount);
#ifdef	DEBUG
    Debug((DEBUG_NOTICE, "get_res:id = %d rcode = %d ancount = %d",
	   hptr->id, hptr->rcode, hptr->ancount));
#endif
    reinfo.re_replies++;
    /*
     * response for an id which we have already received an answer for
     * just ignore this response.
     */
    rptr = find_id(hptr->id);
    if (!rptr)
	return getres_err(rptr, lp);
    /*
     * check against possibly fake replies
     */
    max = MIN(_res.nscount, rptr->sends);
    if (!max)
	max = 1;

    for (a = 0; a < max; a++)
	if (!_res.nsaddr_list[a].sin_addr.s_addr ||
	    !memcmp((char *) &sin.sin_addr,
		    (char *) &_res.nsaddr_list[a].sin_addr,
		    sizeof(struct in_addr)))
	    break;

    if (a == max) 
    {
	reinfo.re_unkrep++;
	return getres_err(rptr, lp);
    }

    if ((hptr->rcode != NOERROR) || (hptr->ancount == 0))
    {
	switch (hptr->rcode)
	{
	case NXDOMAIN:
	    h_errno = TRY_AGAIN;
	    break;
	case SERVFAIL:
	    h_errno = TRY_AGAIN;
	    break;
	case NOERROR:
	    h_errno = NO_DATA;
	    break;
	case FORMERR:
	case NOTIMP:
	case REFUSED:
	default:
	    h_errno = NO_RECOVERY;
	    break;
	}
	reinfo.re_errors++;
	/*
	 * If a bad error was returned, we stop here and dont send
	 * send any more (no retries granted).
	 */
	if (h_errno != TRY_AGAIN)
	{
	    Debug((DEBUG_DNS, "Fatal DNS error %d for %d",
		   h_errno, hptr->rcode));
	    rptr->resend = 0;
	    rptr->retries = 0;
	}
	return getres_err(rptr, lp);
    }
    a = proc_answer(rptr, hptr, buf, buf + rc);
    
#ifdef DEBUG
    Debug((DEBUG_INFO, "get_res:Proc answer = %d", a));
#endif

    switch(a)
    {
    case PROCANSWER_STRANGE:
	rptr->resend = 1;
	rptr->retries--;
	if(rptr->retries <= 0)
	{
	    h_errno = TRY_AGAIN; /* fail this lookup.. */
	    return getres_err(rptr, lp);
	}
	else 
	    resend_query(rptr);
	return NULL;
	
    case PROCANSWER_MALICIOUS:
	if (lp)
	    memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));
	rem_request(rptr);
	return NULL;
	
    default:
	break;
    }
    
    if (a > 0 && rptr->type == T_PTR) 
    {
	struct hostent *hp2 = NULL;
	
	Debug((DEBUG_DNS, "relookup %s <-> %s",
	       rptr->he.h_name, inetntoa((char *) &rptr->he.h_addr)));
	/*
	 * Lookup the 'authoritive' name that we were given for the ip#.
	 * By using this call rather than regenerating the type we
	 * automatically gain the use of the cache with no extra kludges.
	 */
	if ((hp2 = gethost_byname(rptr->he.h_name, &rptr->cinfo)))
	    if (lp)
		memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));
	
	if(!hp2)
	{
	    memcpy(&last->he_rev, &rptr->he, sizeof(struct hent));
	    memset(&rptr->he, 0, sizeof(struct hent));
	    last->has_rev = 1;
	}

	rem_request(rptr);
	return hp2;
    }

    if(a > 0 && rptr->type == T_A)
    {
	if(rptr->has_rev == 0)
	{
	    sendto_realops_lev(DEBUG_LEV, "Blindly accepting dns result for %s", 
			   rptr->he.h_name ? rptr->he.h_name : 
			   inetntoa((char *)&rptr->addr));
	}
	else
	{
	    int invalid_parms_name = 0;
	    int invalid_parms_ip = 0;
	    int found_match_ip = 0;
	    int nidx, tidx;
	    int numaddr, numnewaddr;
	    struct in_addr new_addr_list[IRC_MAXADDRS];

	    if(!(rptr->he.h_name && rptr->he_rev.h_name))
		invalid_parms_name++;
	    
	    if(!(rptr->he.h_addr_list[0].s_addr && 
		 rptr->he_rev.h_addr_list[0].s_addr))
		invalid_parms_ip++;

	    if(invalid_parms_name || invalid_parms_ip)
	    {
		sendto_realops_lev(DEBUG_LEV, 
			       "DNS query missing things! name: %s ip: %s",
			       invalid_parms_name ? "MISSING" :
			       rptr->he.h_name,
			       invalid_parms_ip ? "MISSING" :
			       inetntoa((char *)&rptr->he.h_addr_list[0]));
		if (lp)
		    memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));
		rem_request(rptr);
		return NULL;
	    }

	    /* 
	     * This must ensure that all IPs in the forward query (he)
	     * are also in the reverse query (he_rev).
	     * Those not in the reverse query must be zeroed out!
	     */
	    
	    for(numaddr = numnewaddr = nidx = 0; nidx < IRC_MAXADDRS; nidx++)
	    {
		int does_match;
		
		if(rptr->he.h_addr_list[nidx].s_addr == 0)
		    break;
		
		numaddr++;
		
		for(tidx = does_match = 0; tidx < IRC_MAXADDRS; tidx++)
		{
		    if(rptr->he_rev.h_addr_list[tidx].s_addr == 0)
			break;
		    
		    if(rptr->he_rev.h_addr_list[tidx].s_addr == 
		       rptr->he.h_addr_list[nidx].s_addr) /* MATCH */
		    {
			found_match_ip++;
			does_match = 1;
			break;
		    }
		}
            
		if(does_match)
		{
		    new_addr_list[numnewaddr++].s_addr =
			rptr->he.h_addr_list[nidx].s_addr;
		    new_addr_list[numnewaddr].s_addr = 0;
		}
	    }
         
	    if(!found_match_ip)
	    {
		char ntoatmp_r[64];
		char ntoatmp_f[64];

		strcpy(ntoatmp_f, inetntoa((char *)&rptr->he.h_addr_list[0]));
		strcpy(ntoatmp_r, 
		       inetntoa((char *)&rptr->he_rev.h_addr_list[0]));
#ifdef DNS_ANS_DEBUG
		sendto_realops_lev(DEBUG_LEV, "Forward and Reverse queries do "
			       "not have matching IP! %s<>%s %s<>%s",
			       rptr->he.h_name, rptr->he_rev.h_name,
			       ntoatmp_f, ntoatmp_r);
#endif
		if(rptr->cinfo.flags == ASYNC_CLIENT && rptr->cinfo.value.cptr)
		{
		    sendto_one(rptr->cinfo.value.cptr,
			       ":%s NOTICE AUTH :*** Your forward and "
			       "reverse DNS do not match, "
			       "ignoring hostname. [%s != %s]",
			       me.name, ntoatmp_f, ntoatmp_r);
		}
		
		if (lp)
		    memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));
		
		rem_request(rptr);
		return NULL;
	    }
	    
	    if(numnewaddr != numaddr)
	    {
		memcpy(rptr->he.h_addr_list, new_addr_list,
		       sizeof(struct in_addr) * IRC_MAXADDRS);
#ifdef DNS_ANS_DEBUG
		sendto_realops_lev(DEBUG_LEV, "numaddr = %d, numnewaddr = %d",
			       numaddr, numnewaddr);
#endif
	    }
	    
	    /*
	     * Our DNS query was made based on the hostname, so the hostname
	     * part should be fine.
	     */
	}
    }
    
    if (a > 0)
    {
	if (lp)
	    memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));

	cp = make_cache(rptr);
#ifdef	DEBUG
	Debug((DEBUG_INFO, "get_res:cp=%#x rptr=%#x (made)", cp, rptr));
#endif
	
	rem_request(rptr);
    }
    else if (!rptr->sent)
	rem_request(rptr);
    return cp ? (struct hostent *) &cp->he : NULL;
}

static struct hostent *getres_err(ResRQ * rptr, char *lp)
{
    /*
     * Reprocess an error if the nameserver didnt tell us to
     * "TRY_AGAIN".
     */
    if (rptr)
    {
	if (h_errno != TRY_AGAIN)
	{
	    /*
	     * If we havent tried with the default domain and its set,
	     * then give it a try next.
	     */
	    if (_res.options & RES_DEFNAMES && ++rptr->srch == 0)
	    {
		rptr->retries = _res.retry;
		rptr->sends = 0;
		rptr->resend = 1;
		resend_query(rptr);
	    }
	    else
		resend_query(rptr);
	}
	else if (lp)
	    memcpy(lp, (char *) &rptr->cinfo, sizeof(Link));
    }
    return (struct hostent *) NULL;
}

static int hash_number(unsigned char *ip)
{
    u_int   hashv = 0;

    /* could use loop but slower */
    hashv += (int) *ip++;
    hashv += hashv + (int) *ip++;
    hashv += hashv + (int) *ip++;
    hashv += hashv + (int) *ip++;
    hashv %= ARES_CACSIZE;
    return (hashv);
}

#ifdef ALLOW_CACHE_NAMES
static int hash_name(char *name)
{
    u_int   hashv = 0;
    
    for (; *name && *name != '.'; name++)
	hashv += *name;
    hashv %= ARES_CACSIZE;
    return (hashv);
}
#endif

static unsigned int hash_id(unsigned int id)
{
   return id % ARES_IDCACSIZE;
}

static unsigned int hash_cp(char *cp)
{
   return ((unsigned int) cp) % ARES_IDCACSIZE;
}

/* Add a new cache item to the queue and hash table. */
static aCache *add_to_cache(aCache * ocp)
{
    aCache *cp = NULL;
    int     hashv;
    
#ifdef DEBUG
    Debug((DEBUG_INFO,
	   "add_to_cache:ocp %#x he %#x name %#x addrl %#x 0 %#x",
	   ocp, &ocp->he, ocp->he.h_name, ocp->he.h_addr_list,
	   ocp->he.h_addr_list[0]));
#endif
    ocp->list_next = cachetop;
    cachetop = ocp;
    /* Make sure non-bind resolvers don't blow up (Thanks to Yves) */
    if (!ocp)
	return NULL;
    if (!(ocp->he.h_name))
	return NULL;
    if (!(ocp->he.h_addr))
	return NULL;
    
#ifdef ALLOW_CACHE_NAMES
    hashv = hash_name(ocp->he.h_name);
    
    ocp->hname_next = hashtable[hashv].name_list;
    hashtable[hashv].name_list = ocp;
#endif
    
    hashv = hash_number((u_char *) ocp->he.h_addr);
    
    ocp->hnum_next = hashtable[hashv].num_list;
    hashtable[hashv].num_list = ocp;
    
#ifdef	DEBUG
    Debug((DEBUG_INFO, "add_to_cache:added %s[%08x] cache %#x.",
	   ocp->he.h_name, ocp->he.h_addr_list[0], ocp));
    Debug((DEBUG_INFO,
	   "add_to_cache:h1 %d h2 %x lnext %#x namnext %#x numnext %#x",
	   hash_name(ocp->he.h_name), hashv, ocp->list_next,
	   ocp->hname_next, ocp->hnum_next));
#endif
    /* LRU deletion of excessive cache entries. */
    if (++incache > IRC_MAXCACHED)
    {
	for (cp = cachetop; cp->list_next; cp = cp->list_next);
	rem_cache(cp);
    }
    cainfo.ca_adds++;

    return ocp;
}

/*
 * update_list does not alter the cache structure passed. It is
 * assumed that * it already contains the correct expire time, if it is
 * a new entry. Old * entries have the expirey time updated.
 */
static void update_list(ResRQ * rptr, aCache * cachep)
{
    aCache **cpp, *cp = cachep;
    char   *s, *t, **base;
    int     i, j;
    int     addrcount;

    /*
     * search for the new cache item in the cache list by hostname. *
     * If found, move the entry to the top of the list and return.
     */
    cainfo.ca_updates++;

    for (cpp = &cachetop; *cpp; cpp = &((*cpp)->list_next))
	if (cp == *cpp)
	    break;
    if (!*cpp)
	return;
    *cpp = cp->list_next;
    cp->list_next = cachetop;
    cachetop = cp;
    if (!rptr)
	return;
    
#ifdef	DEBUG
    Debug((DEBUG_DEBUG, "u_l:cp %#x na %#x al %#x ad %#x",
	   cp, cp->he.h_name, cp->he.h_aliases, cp->he.h_addr));
    Debug((DEBUG_DEBUG, "u_l:rptr %#x h_n %#x", rptr, rptr->he.h_name));
#endif
    /*
     * Compare the cache entry against the new record.  Add any
     * previously missing names for this entry.
     */
    for (i = 0; cp->he.h_aliases[i]; i++);
    addrcount = i;
    for (i = 0, s = rptr->he.h_name; s && i < IRC_MAXALIASES;
	 s = rptr->he.h_aliases[i++])
    {
	for (j = 0, t = cp->he.h_name; t && j < IRC_MAXALIASES;
	     t = cp->he.h_aliases[j++])
	    if (!mycmp(t, s))
		break;
	if (!t && j < IRC_MAXALIASES - 1)
	{
	    base = cp->he.h_aliases;
	    
	    addrcount++;
	    base = (char **) MyRealloc(base,
				       sizeof(char *) * (addrcount + 1));
	    
	    cp->he.h_aliases = base;
#ifdef	DEBUG
	    Debug((DEBUG_DNS, "u_l:add name %s hal %x ac %d",
		   s, cp->he.h_aliases, addrcount));
#endif
	    base[addrcount - 1] = s;
	    base[addrcount] = NULL;
	    if (i)
		rptr->he.h_aliases[i - 1] = NULL;
	    else
		rptr->he.h_name = NULL;
	}
    }
    for (i = 0; cp->he.h_addr_list[i]; i++);
    addrcount = i;
    /* Do the same again for IP#'s. */
    for (s = (char *) &rptr->he.h_addr.s_addr;
	 ((struct in_addr *) s)->s_addr; s += sizeof(struct in_addr)) {
	for (i = 0; (t = cp->he.h_addr_list[i]); i++)
	    if (!memcmp(s, t, sizeof(struct in_addr)))
		break;

	if (i >= IRC_MAXADDRS || addrcount >= IRC_MAXADDRS)
	    break;
	/*
	 * Oh man this is bad...I *HATE* it. -avalon
	 * 
	 * Whats it do ?  Reallocate two arrays, one of pointers to "char *"
	 * and the other of IP addresses.  Contents of the IP array *MUST*
	 * be preserved and the pointers into it recalculated.
	 */
	if (!t)
	{
	    base = cp->he.h_addr_list;
	    addrcount++;
	    t = (char *) MyRealloc(*base,
				   addrcount * sizeof(struct in_addr));
	    
	    base = (char **) MyRealloc(base,
				       (addrcount + 1) * sizeof(char *));
	    
	    cp->he.h_addr_list = base;
#ifdef	DEBUG
	    Debug((DEBUG_DNS, "u_l:add IP %x hal %x ac %d",
		   ntohl(((struct in_addr *) s)->s_addr),
		   cp->he.h_addr_list,
		   addrcount));
#endif
	    for (; addrcount; addrcount--)
	    {
		*base++ = t;
		t += sizeof(struct in_addr);
	    }
	    *base = NULL;
	    memcpy(*--base, s, sizeof(struct in_addr));
	}
    }
    return;
}

static aCache *find_cache_name(char *name)
{
#ifdef ALLOW_CACHE_NAMES
    aCache *cp;
    char   *s;
    int     hashv, i;
    
    if (name == (char *) NULL)
	return (aCache *) NULL;
    hashv = hash_name(name);
    
    cp = hashtable[hashv].name_list;
#ifdef	DEBUG
    Debug((DEBUG_DNS, "find_cache_name:find %s : hashv = %d", name, hashv));
#endif
    
    for (; cp; cp = cp->hname_next)
	for (i = 0, s = cp->he.h_name; s; s = cp->he.h_aliases[i++])
	    if (mycmp(s, name) == 0)
	    {
		cainfo.ca_na_hits++;
		update_list(NULL, cp);
		return cp;
	    }
    
    for (cp = cachetop; cp; cp = cp->list_next)
    {
	/*
	 * if no aliases or the hash value matches, we've already done
	 * this entry and all possiblilities concerning it.
	 */
	if (!*cp->he.h_aliases)
	    continue;
	if (cp->he.h_name == (char *) NULL)	/*
						 * don't trust anything
						 * -Dianora 
						 */
	    continue;
	if (hashv == hash_name(cp->he.h_name))
	    continue;
	for (i = 0, s = cp->he.h_aliases[i]; s && i < IRC_MAXALIASES; i++)
	    if (!mycmp(name, s))
	    {
		cainfo.ca_na_hits++;
		update_list(NULL, cp);
		return cp;
	    }
    }
#endif
    return NULL;
}

/* find a cache entry by ip# and update its expire time */
static aCache *
find_cache_number(ResRQ * rptr, char *numb)
{
    aCache *cp;
    int     hashv, i;
    struct in_addr *ip = (struct in_addr *) numb;

    if ((u_char *) numb == (u_char *) NULL)
	return ((aCache *) NULL);
    hashv = hash_number((u_char *) numb);
    cp = hashtable[hashv].num_list;
#ifdef DEBUG
    Debug((DEBUG_DNS, "find_cache_number:find %s[%08x]: hashv = %d",
	   inetntoa(numb), ntohl(ip->s_addr), hashv));
#endif

    for (; cp; cp = cp->hnum_next)
    {
	for (i = 0; cp->he.h_addr_list[i]; i++)
	{
	    /* 
	     * A 32 bit integer compare should be faster than this...
	     *  if (!memcmp(cp->he.h_addr_list[i], numb,
	     *	   sizeof(struct in_addr))) 
	     */
	    if(((struct in_addr *)cp->he.h_addr_list[i])->s_addr == ip->s_addr)
	    {
		cainfo.ca_nu_hits++;
		update_list(NULL, cp);
		return cp;
	    }
	}
    }
    
#ifdef SEARCH_CACHE_ADDRESSES
    for (cp = cachetop; cp; cp = cp->list_next)
    {
	/*
	 * single address entry...would have been done by hashed search 
	 * above...
	 */
	if (!cp->he.h_addr_list[1])
	    continue;
	/*
	 * if the first IP# has the same hashnumber as the IP# we are
	 * looking for, its been done already.
	 */
	if (hashv == hash_number((u_char *) cp->he.h_addr_list[0]))
	    continue;
	for (i = 1; cp->he.h_addr_list[i]; i++)
	    if (!memcmp(cp->he.h_addr_list[i], numb,
			sizeof(struct in_addr)))
	    {
		cainfo.ca_nu_hits++;
		update_list(NULL, cp);
		return cp;
	    }
    }
#endif
    return NULL;
}

static aCache *make_cache(ResRQ * rptr)
{
    aCache *cp;
    int     i, n;
    struct hostent *hp;
    char   *s, **t;

    /* shouldn't happen but it just might... */
    if (!rptr->he.h_name || !rptr->he.h_addr.s_addr)
	return NULL;
    /*
     * Make cache entry.  First check to see if the cache already
     * exists and if so, return a pointer to it.
     */
    if ((cp = find_cache_number(rptr, (char *) &rptr->he.h_addr.s_addr)))
	return cp;
    for (i = 1; rptr->he.h_addr_list[i].s_addr && i < IRC_MAXADDRS; i++)
	if ((cp = 
	     find_cache_number(rptr,
			       (char *) &(rptr->he.h_addr_list[i].s_addr))))
	    return cp;
    /* a matching entry wasnt found in the cache so go and make one up. */
    cp = (aCache *) MyMalloc(sizeof(aCache));
    memset((char *) cp, '\0', sizeof(aCache));
    hp = &cp->he;
    for (i = 0; i < IRC_MAXADDRS; i++)
	if (!rptr->he.h_addr_list[i].s_addr)
	    break;
    /* build two arrays, one for IP#'s, another of pointers to them. */
    t = hp->h_addr_list = (char **) MyMalloc(sizeof(char *) * (i + 1));
    memset((char *) t, '\0', sizeof(char *) * (i + 1));
    
    s = (char *) MyMalloc(sizeof(struct in_addr) * i);
    memset(s, '\0', sizeof(struct in_addr) * i);
    
    for (n = 0; n < i; n++, s += sizeof(struct in_addr))
    {
	*t++ = s;
	memcpy(s, (char *) &(rptr->he.h_addr_list[n].s_addr),
	       sizeof(struct in_addr));
    }
    *t = (char *) NULL;
    /* an array of pointers to CNAMEs. */
    for (i = 0; i < IRC_MAXALIASES; i++)
	if (!rptr->he.h_aliases[i])
	    break;
    i++;
    t = hp->h_aliases = (char **) MyMalloc(sizeof(char *) * i);
    
    for (n = 0; n < i; n++, t++)
    {
	*t = rptr->he.h_aliases[n];
	rptr->he.h_aliases[n] = NULL;
    }
    
    hp->h_addrtype = rptr->he.h_addrtype;
    hp->h_length = rptr->he.h_length;
    hp->h_name = rptr->he.h_name;
    if (rptr->ttl < 600)
    {
	reinfo.re_shortttl++;
	cp->ttl = 600;
    }
    else
	cp->ttl = rptr->ttl;
    cp->expireat = timeofday + cp->ttl;
    rptr->he.h_name = NULL;
#ifdef DEBUG
    Debug((DEBUG_INFO, "make_cache:made cache %#x", cp));
#endif
    return add_to_cache(cp);
}

/*
 * rem_cache delete a cache entry from the cache structures and lists
 * and return all memory used for the cache back to the memory pool.
 */
static void rem_cache(aCache * ocp)
{
    aCache **cp;
    struct hostent *hp = &ocp->he;
    int     hashv;
    aClient *cptr;
    
#ifdef	DEBUG
    Debug((DEBUG_DNS, "rem_cache: ocp %#x hp %#x l_n %#x aliases %#x",
	   ocp, hp, ocp->list_next, hp->h_aliases));
#endif
    /*
     * * Cleanup any references to this structure by destroying the *
     * pointer.
     */
    for (hashv = highest_fd; hashv >= 0; hashv--)
	if ((cptr = local[hashv]) && (cptr->hostp == hp))
	    cptr->hostp = NULL;
    /*
     * remove cache entry from linked list
     */
    for (cp = &cachetop; *cp; cp = &((*cp)->list_next))
	if (*cp == ocp)
	{
	    *cp = ocp->list_next;
	    break;
	}
    /* remove cache entry from hashed name lists */
    if (hp->h_name == (char *) NULL)
	return;
#ifdef ALLOW_CACHE_NAMES
    hashv = hash_name(hp->h_name);
    
# ifdef	DEBUG
    Debug((DEBUG_DEBUG, "rem_cache: h_name %s hashv %d next %#x first %#x",
	   hp->h_name, hashv, ocp->hname_next,
	   hashtable[hashv].name_list));
# endif
    for (cp = &hashtable[hashv].name_list; *cp; cp = &((*cp)->hname_next))
	if (*cp == ocp)
	{
	    *cp = ocp->hname_next;
	    break;
	}
#endif
    /* remove cache entry from hashed number list */
    hashv = hash_number((u_char *) hp->h_addr);
    if (hashv < 0)
	return;
#ifdef	DEBUG
    Debug((DEBUG_DEBUG, "rem_cache: h_addr %s hashv %d next %#x first %#x",
	   inetntoa(hp->h_addr), hashv, ocp->hnum_next,
	   hashtable[hashv].num_list));
#endif
    for (cp = &hashtable[hashv].num_list; *cp; cp = &((*cp)->hnum_next))
	if (*cp == ocp)
	{
	    *cp = ocp->hnum_next;
	    break;
	}
    /*
     * free memory used to hold the various host names and the array of
     * alias pointers.
     */
    if (hp->h_name)
	MyFree(hp->h_name);
    if (hp->h_aliases)
    {
	for (hashv = 0; hp->h_aliases[hashv]; hashv++)
	    MyFree(hp->h_aliases[hashv]);
	MyFree(hp->h_aliases);
    }
    /* free memory used to hold ip numbers and the array of them. */
    if (hp->h_addr_list)
    {
	if (*hp->h_addr_list)
	    MyFree(*hp->h_addr_list);
	MyFree(hp->h_addr_list);
    }
    
    MyFree(ocp);
    
    incache--;
    cainfo.ca_dels++;
    
    return;
}

/*
 * removes entries from the cache which are older than their expirey
 * times. returns the time at which the server should next poll the
 * cache.
 */
time_t expire_cache(time_t now)
{
    aCache *cp, *cp2;
    time_t  next = 0;
    time_t  mmax = now + AR_TTL;

    for (cp = cachetop; cp; cp = cp2)
    {
	cp2 = cp->list_next;
	
	if (now >= cp->expireat)
	{
	    cainfo.ca_expires++;
	    rem_cache(cp);
	}
	else if (!next || next > cp->expireat)
	    next = cp->expireat;
    }
    /*
     * don't let one DNS record that happens to be first
     * stop others from expiring.
     */
    return (next > now) ? (next < mmax ? next : mmax) : mmax;
}

/* remove all dns cache entries. */
void flush_cache()
{
    aCache *cp;
    
    while ((cp = cachetop))
	rem_cache(cp);
}

int m_dns(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    aCache *cp;
    int     i;
    
    if (parv[1] && *parv[1] == 'l')
    {
        if (!MyClient(sptr) || !IsAdmin(sptr))
        {
          sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
          return 0;
        }
	for (cp = cachetop; cp; cp = cp->list_next)
	{
	    sendto_one(sptr, "NOTICE %s :Ex %d ttl %d host %s(%s)",
		       parv[0], cp->expireat - timeofday, cp->ttl,
		       cp->he.h_name, inetntoa(cp->he.h_addr));
	    for (i = 0; cp->he.h_aliases[i]; i++)
		sendto_one(sptr, "NOTICE %s : %s = %s (CN)",
			   parv[0], cp->he.h_name,
			   cp->he.h_aliases[i]);
	    for (i = 1; cp->he.h_addr_list[i]; i++)
		sendto_one(sptr, "NOTICE %s : %s = %s (IP)",
			   parv[0], cp->he.h_name,
			   inetntoa(cp->he.h_addr_list[i]));
	}
	return 0;
    }
    sendto_one(sptr, "NOTICE %s :Ca %d Cd %d Ce %d Cl %d Ch %d:%d Cu %d",
	       sptr->name,
	       cainfo.ca_adds, cainfo.ca_dels, cainfo.ca_expires,
	       cainfo.ca_lookups,
	       cainfo.ca_na_hits, cainfo.ca_nu_hits, cainfo.ca_updates);
    
    sendto_one(sptr, "NOTICE %s :Re %d Rl %d/%d Rp %d Rq %d",
	       sptr->name, reinfo.re_errors, reinfo.re_nu_look,
	       reinfo.re_na_look, reinfo.re_replies, reinfo.re_requests);
    sendto_one(sptr, "NOTICE %s :Ru %d Rsh %d Rs %d(%d) Rt %d", sptr->name,
	       reinfo.re_unkrep, reinfo.re_shortttl, reinfo.re_sent,
	       reinfo.re_resends, reinfo.re_timeouts);
    return 0;
}

u_long
memcount_res(MCres *mc)
{
    ResRQ *rq;
    aCache *ce;
    int i;

    mc->file = __FILE__;

    for (rq = first; rq; rq = rq->next)
    {
        mc->requests.c++;
        mc->requests.m += sizeof(*rq);

        if (rq->name)
            mc->requests.m += strlen(rq->name) + 1;

        if (rq->he.h_name)
            mc->requests.m += strlen(rq->he.h_name) + 1;

        for (i = 0; rq->he.h_aliases[i]; i++)
            mc->requests.m += strlen(rq->he.h_aliases[i]) + 1;

        if (rq->he_rev.h_name)
            mc->requests.m += strlen(rq->he_rev.h_name) + 1;

        for (i = 0; rq->he_rev.h_aliases[i]; i++)
            mc->requests.m += strlen(rq->he_rev.h_aliases[i]) + 1;
    }

    for (ce = cachetop; ce; ce = ce->list_next)
    {
        mc->cached.c++;
        mc->cached.m += sizeof(*ce);

        if (ce->he.h_name)
            mc->cached.m += strlen(ce->he.h_name) + 1;

        if (ce->he.h_aliases)
        {
            for (i = 0; ce->he.h_aliases[i]; i++)
            {
                mc->cached.m += sizeof(char *);
                mc->cached.m += strlen(ce->he.h_aliases[i]) + 1;
            }
            mc->cached.m += sizeof(char *);
        }

        if (ce->he.h_addr_list)
        {
            for (i = 0; ce->he.h_addr_list[i]; i++)
            {
                mc->cached.m += sizeof(char *);
                mc->cached.m += ce->he.h_length;
            }
            mc->cached.m += sizeof(char *);
        }
    }

    mc->s_cachehash.c = sizeof(hashtable) / sizeof(hashtable[0]);
    mc->s_cachehash.m = sizeof(hashtable);
    mc->s_requesthash.c = sizeof(idcphashtable) / sizeof(idcphashtable[0]);
    mc->s_requesthash.m = sizeof(idcphashtable);

    mc->total.c = mc->requests.c + mc->cached.c;
    mc->total.m = mc->requests.m + mc->cached.m;

    return mc->total.m;
}



syntax highlighted by Code2HTML, v. 0.9.1