#define SRCFILE_NUM 3
/*
Fixed so it now sends successes if it times out before all loookups
are complete (mx_gotsome function added)


Check channel is allocated befor calling 'fn'
	call mx_freechan(int x) from send_free(n)

*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include "../sam/tcplib.h"
#include "../sam/lib.h"
#include "debug.h"
#include "defines.h"
#ifndef FALSE
#define FALSE 0
#define TRUE (!FALSE)
#endif

#include "dns.h"

#ifdef ISOSF
/*typedef unsigned int u_int16_t;*/
#endif
/* Global functions */

int mx_lookup_que(char *name, void (*fn)(int n, char *s),int n);

static int ini_timeout=90;
static int ini_disk_cache=TRUE;
static char workarea[BFSZ];
#define MAX_SWITCH 7

/* Internal functions */
#ifndef strdup
#ifndef WIN32
char *strdup(const char *s);
#endif
#endif
int bk_lookup(int ch, char *name);
int bk_isbusy(int ch);
int bk_tick(int x, int msec);
int bk_init(int (*vdmsg)(const char * arg_list, ...),int (*vimsg)(const char * arg_list, ...),int (*vemsg)(const char * arg_list, ...));
int bk_start(int x, char *name, int xtype, int isnew);
int bk_get(char *s);
int bk_send(int x);
int bk_nextns(int x, char *reason);
int bk_open(int x);
int bk_read(int x, int msec);
int bk_mkquery(int x, int op, char *dname, int xclass
	    , int type, u_char *data, int datalen);
int mxc_age(int force);

int bk_comp(char *dname, char *cp, int bflen);
void bk_putshort(u_int src, u_char *dst) ;
int mxc_add(char *name, char *result);
int dns_stats(int delay, int worked, int n, int x);
int mxc_addfail(char *name, int invalid);
int mx_any_busy(void);

int dns_disk_write(void);
int dns_disk_init(void);
int dns_disk_lookup(char *name, char *result);
int dns_disk_add(char *name, char *result, int dodisk);
static int (*emsg)(const char * arg_list, ...);
static int (*imsg)(const char * arg_list, ...);
static int (*dmsg)(const char * arg_list, ...);
char *get_text(unsigned char *base, unsigned char **s, unsigned char *end);

#ifdef WIN32
#define WIN_SOCK 1
#endif

#ifndef WIN_SOCK
#define INVALID_SOCKET  -1
#define SOCKET short
#endif

#ifdef NEED_BCOPY
#define bcopy(src,dst,len) memmove(dst,src,len)
#endif
short tcp_openx(char *name);
void do_test(void);
int bk_close(int x, char *reason);
static char bkresult[BFSZ];
static char bkcname[BFSZ];
static int bkns;
static int g1=1;
static int bkns_n;
static int g2=2;
static int bkupto;
static int bktimeout=90; /*90;*/ /* After 10 seconds, try other one */
static int bklen;
static char bkhosts[10][BFSZ];
static int bkid=1000; /*ID of each dns request */
enum {BK_IDLE,BK_START,BK_SEND,BK_READ,BK_READY,BK_FAILED};
#define MAX_CURRENT 200
static SOCKET bksk[MAX_CURRENT];
static Chan *bkch[MAX_CURRENT];
static time_t bk_lastused[MAX_CURRENT];
struct SYMS { int op; char *name; char *description; };
void mx_free(void);
int mxc_addok(char *name, char *result);

int reqid[MAX_CURRENT];
int mx_failed(int x, char *reason);
void mx_freeall(int x);
void mx_success(int x,char *result, int addcache);
typedef struct DONE {char *s; int mask; struct DONE *next;} Done;
typedef struct MXREC_S {char *name; char *addr; double pri; struct MXREC_S *next;} MXREC;
typedef struct MXCHAN {int retry; char *oname; int isorbs; char *stype; int nask; int loop; int at; time_t time_start; int state; int invalid; Done *done; MXREC *mx; int n; char *name; void (*fn)(int n, char *s); int last_type,type,twice,timeout;} Mxchan;
static Mxchan mx[MAX_CURRENT];
static int nchan,mxnew;
void mx_copy(MXREC *dst, MXREC *src);
void mx_free_mx(MXREC *m);
void mx_free_done(Done *d);
int mx_init_one(int i);
#ifdef DNSTEST
static int dns_debug=TRUE;
#else
static int dns_debug=FALSE;
#endif
int mx_gotsome(int x);

static char current_dns[BFSZ];
static int current_nok;
static int noisey=TRUE;
int dns_set_quiet(int x)
{
	noisey = !x;
	return TRUE;
}
int dns_corrupt(void)
{
	int ret=FALSE;
	if (g1!=1) ret = TRUE;
	if (g2!=2) ret = TRUE;
	if (bkns_n<0) ret = TRUE;
	if (bkns_n>12) ret = TRUE;
	if (ret) {
		imsg("dns: corrupt %d %d %d\n",g1,g2,bkns_n);
	}
	return ret;
}
int mx_lookup_que(char *xname, void (*fn)(int n, char *s),int n)
{
	int i;
	char name[BFSZ];
	ncpy(name,xname,BFSZ-1);
	if (name[strlen(name)-1]!='.') ncat(name,".",BFSZ-1);
	for (i=0; i<MAX_CURRENT; i++) if (mx[i].state==BK_IDLE) break;
	if (i>=100) { emsg("dns: Could not lookup MX records as limit exceeded\n"); return -1;}
	if (noisey)  imsg("dns: mx lookup que %d: %s\n",i,name);
	mx[i].n = n;
	if (mx[i].name != NULL) free(mx[i].name);
	if (mx[i].oname != NULL) free(mx[i].oname);
	mx[i].name = strdup(name);
	mx[i].oname = strdup(name);
	mx[i].fn = fn;
	mx[i].retry = 0;
	mx[i].invalid = FALSE;
	mx[i].timeout = 0;
	mx[i].time_start = time(NULL);
	mx[i].at = time(NULL);
	mx[i].isorbs = FALSE;
	mx[i].stype = "";
	mx_init_one(i);
	if (i>=nchan) nchan = i+1;
	return i;
}
int mx_set_timeout(int x, int secs)
{
	mx[x].timeout = secs;
	return TRUE;
}
int mx_set_orbs(int x, int isorbs)
{
	mx[x].isorbs = isorbs;
	if (noisey)  imsg("dns_stats[%d] %s lookup %s\n",x,isorbs ? "ORBS" : "Norm",mx[x].oname);
	return TRUE;
}
int mx_set_type(int x, char *type)
{
	mx[x].stype = type;
	if (strlen(type)>0) mx[x].isorbs = TRUE;
	return TRUE;
}
int mx_init_one(int i)
{
	mxnew = TRUE;
	mx[i].state = BK_START;
	mx[i].loop = 0;
	mx[i].nask = 0;
	mx[i].twice = FALSE;
	mx_free_done(mx[i].done); mx[i].done = NULL;
	mx_free_mx(mx[i].mx); mx[i].mx = NULL;
	return TRUE;
}
static int mx_nok,mx_nfail,mx_start,mx_ncached,mx_ncached_fails;
static int dns_fails;
void mx_success(int x,char *result, int addcache)
{
	char result_copy[BFSZ];
	void (*fn)(int n, char *s);
	mx_nok++;
	dns_fails = 0;
	strncpy(result_copy,result,BFSZ-10);
	result_copy[BFSZ-10] = 0;
	if (addcache) if (mx[x].isorbs) addcache = FALSE;
	if (noisey)  imsg("dns: mx lookup success for channel %d (%s)\n",mx[x].n, result);
	if (addcache)
	{
		mxc_addok(mx[x].oname,result);
		dns_disk_add(mx[x].oname,result,TRUE);
		dns_stats(time(NULL)-mx[x].time_start,TRUE,bkns,x);
	}
	else {
		;
	}
	fn = mx[x].fn;
	(*fn)(mx[x].n,result_copy);
	mx[x].fn = NULL;
	mx_freeall(x);
}
static int disk_disable=FALSE;
int mx_failed(int x, char *reason)
{
	char bf[BFSZ];
	void (*fn)(int n, char *s);
	mx_nfail++;
	if (noisey)  imsg("dns: (%s) mx lookup failed %s %d\n",current_dns,reason,x);
	if (x<0) return FALSE; /* We don't know which request so let it time out */

	fn = mx[x].fn;
	sprintf(bf,"FAILED %.90s",mx[x].oname);
	dns_stats(time(NULL)-mx[x].time_start,FALSE,bkns,x);
	if (strstr(reason,"timedout")!=NULL) {
		if (noisey)  imsg("dns: Timeout so closing dns mx channel %d\n",x);
		bk_close(x,"Timeout so closing it");
	}
	imsg("dns: fail %s %d %d\n",mx[x].oname,ini_disk_cache,mx[x].isorbs);
	if (ini_disk_cache && !mx[x].isorbs ) {
		if (strcmp(reason,"Cached failure")==0) goto cont;
		imsg("dns: doing disk lookup %s\n",mx[x].oname);
		if (dns_disk_lookup(mx[x].oname,bf)) {
			if (noisey) imsg("dns Using disk cache %.90s %.90s\n",mx[x].oname,bf);
			mx_success(x,bf,FALSE);
			return FALSE;
		}
	}
cont:
	strcpy(bf,"");
	mxc_addfail(mx[x].oname,mx[x].invalid);

	if (time(NULL)-mx[x].time_start < 20) {
		// Only define a domain as invalid if we get a result in less than 20 seconds.
		// chrisp not sure if this is sane or not.
		if (mx[x].invalid) strcpy(bf,"invalid_domain1");
	}
	if (mx[x].n>=0) {
		(*fn)(mx[x].n,bf);
		mx[x].fn = NULL;
	} else {
		emsg("dns: Tried to call mx_failed when 'n' was invalid\n");
	}
	mx_freeall(x);
	return FALSE;
}
static int stat_nok[100];
static int stat_nbad[100];
static int stat_oktime[100];
static int stat_badtime[100];
int dns_stats(int delay, int worked, int n,int x)
{
	if (n<0) return FALSE;
	if (n>10) return FALSE;
	if (noisey)  imsg("dns_stats2[%d] %d seconds, %s, %s %s\n",x,delay,worked ? "Worked" : "Failed" ,mx[x].isorbs ? "ORBS" : "Normal",mx[x].oname);
	if (worked) {
		stat_nok[n]++;
		stat_oktime[n]+=delay;
	} else {
		stat_nbad[n]++;
		stat_badtime[n]+=delay;
	}
	return TRUE;
}
int dns_status_vars(int i, char *host, int bfsz, int *nok, int *oktime, int *nbad, int *badtime)
{
	if (i>=bkns_n) return FALSE;
	ncpy(host,bkhosts[i],BFSZ-1);
	*nok = stat_nok[i];
	*oktime = stat_oktime[i];
	*nbad = stat_nbad[i];
	*badtime = stat_badtime[i];
	return TRUE;
}
char *dns_status(int i, char *tmp)
{
	int nok,nbad;
	if (i>=bkns_n) return NULL;
	if (bkns_n>8) {
		if (noisey)  imsg("bkns_n corrupted %d\n",bkns_n);
		bkns_n = 4;
		return NULL;
	}
	nok = stat_nok[i];
	nbad = stat_nbad[i];
	if (nok==0) nok = 1;
	if (nbad==0) nbad = 1;
	sprintf(tmp,"%.10sDNS %.50s, nok=%d %3.1f nbad=%d %3.1f",bkns==i ? "->" : "",bkhosts[i]
		,stat_nok[i],(double) stat_oktime[i]/(double) nok
		,stat_nbad[i],(double) stat_badtime[i]/(double) nbad);
	if (noisey)  imsg("badtime %d nbad %d\n",stat_badtime[i],stat_nbad[i]);
	return tmp;
}
char *mx_status(void)
{
	static char bf[BFSZ];
	sprintf(bf,"dns nok=%d nlookups=%d nfailed=%d cfails=%d Total speed=%4.2f dns speed=%4.2f",mx_nok,mx_nok-mx_ncached,mx_nfail,mx_ncached_fails,(double) (mx_nok+mx_nfail)/(time(NULL)-mx_start),(double) (mx_nok+mx_nfail-mx_ncached)/(time(NULL)-mx_start));
	return bf;
}
void mx_freechan(int x)
{
	int i;
	for (i=0; i<MAX_CURRENT; i++) {
		if (mx[i].n==x) mx_freeall(i);
	}

}
void mx_freeall(int x)
{
	mx[x].n = -1;
	mx[x].state = BK_IDLE;
	mx_free_mx(mx[x].mx);
	mx[x].mx = NULL;
	mx_free_done(mx[x].done);
	mx[x].done = NULL;
	mx[x].fn = NULL;
}
int mx_showall(Chan *c)
{
	int i;
	for (i=0; i<MAX_CURRENT; i++) {
		if (mx[i].state!=0 || i<10) tcp_printf(c," mx[%d] n=%d state=%d tout=%d\r\n",i,mx[i].n,mx[i].state,time(NULL) - mx[i].time_start);
	}
	return TRUE;
}

/* Should increase probably at some point, and make it a hash table */
#define CACHE_SIZE 3000
static char *mxc_result[CACHE_SIZE];
static char *mxc_name[CACHE_SIZE];
static time_t mxc_time[CACHE_SIZE];
static int mxc_nfail[CACHE_SIZE];
static time_t mxc_lastfail[CACHE_SIZE];
static int mxc_next;

void mxc_exit()
{
	mxc_age(TRUE);
}

int mxc_find_raw(char *s)
{
	int i;
	for (i=0; i<CACHE_SIZE; i++) {
		if (mxc_name[i]==NULL) continue;
		if (strcmp(mxc_name[i],s)==0) return i;
	}
	return -1;
}
int mxc_find(char *s)
{
	int i;
	time_t t;
	t = time(NULL);
	for (i=0; i<CACHE_SIZE; i++) {
		if (mxc_name[i]==NULL) continue;
		if (strlen(mxc_result[i])==0) {
			if (mxc_nfail[i]<5) continue;
			if ((t-mxc_time[i])>900) continue;
//			continue; // always ignore cached failures, they are too dangerous to use. 
		}
		if (strcmp(mxc_name[i],s)==0) return i;
	}
	return -1;
}
int mxc_age(int force)
{
	static time_t last,now;
	int i;
	time(&now);
	if (((now-last)>(60*60*2))||(force)) {
		last = now;
		dmsg("dns: ** Clearing mx cache (had %d entries)\n", mxc_next);
		for (i=0; i<CACHE_SIZE; i++) {
			if (mxc_name[i]!=NULL) free(mxc_name[i]);
			if (mxc_result[i]!=NULL) free(mxc_result[i]);
			mxc_name[i] = NULL;
			mxc_result[i] = NULL;
		}
		mxc_next=0;
	}
	return TRUE;
}

char *mxc_strdup(char *s)
{
	char *b = malloc(strlen(s)+1);
	strcpy(b,s);
	return b;
}
int mxc_addfail(char *name, int invalid)
{
	int r;
	r = mxc_find_raw(name);
	if (r<0) return mxc_add(name,"");
	if (time(NULL)-mxc_lastfail[r] > 300) {
		if (invalid) mxc_nfail[r]++;
		mxc_lastfail[r] = time(NULL);
	}
	if (noisey)  imsg("Added failure for domain %s to cache %d failures recorded\n",name,mxc_nfail[r]);
	return r;
}
int mxc_addok(char *name, char *result)
{
	int i;
	i = mxc_find_raw(name);
	if (i<0) return mxc_add(name,result);
	if (noisey)  imsg("dns: Replacing cache entry with good result %s %s\n",name,result);
	free(mxc_result[i]);
	mxc_result[i] = mxc_strdup(result);
	mxc_time[i] = time(NULL);
	mxc_nfail[i] = 0;
	return i;
}
int mxc_add(char *name, char *result)
{
	int i = mxc_next++;
	char *n,*r;
	if (mxc_next>=CACHE_SIZE) mxc_next = 0;
	n = strdup(name);
	r = mxc_strdup(result);

	if (mxc_name[i]!=NULL) {
		free(mxc_name[i]);
		free(mxc_result[i]);
	}
	mxc_name[i] = n;
	mxc_result[i] = r;
	mxc_time[i] = time(NULL);
	mxc_nfail[i] = 0;
	if (strlen(result)==0) mxc_nfail[i]=1;
	mxc_lastfail[i] = time(NULL);
	return i;
}
int mx_fail_all(void)
{
	int i;
	for (i=0; i<nchan; i++) {
		if (mx[i].state!=BK_IDLE) mx_failed(i,"Nameserver not responding");
	}
	return TRUE;
}
int mx_any_busy(void)
{
	int i;
	for (i=0; i<nchan; i++) {
		if (mx[i].state!=BK_IDLE) return TRUE;
	}
	return FALSE;
}
int mx_retry_all(int i)
{
	if (mx[i].state!=BK_IDLE) {
		mx_init_one(i);
		mx[i].retry++;
		if ((mx[i].retry>bkns_n) && (mx[i].retry>3)) {
			mx_failed(i,"Tried all name servers");
		}
	}
	return TRUE;
}
static int nopen;
int mx_bkgnd(int thischan, int msec)
{
	int c;
	int i;

	if (mxnew) {
//		for (i=0; i<nchan; i++) {
		i = thischan;

			if (mx[i].state == BK_START) {
				c = mxc_find(mx[i].oname);
//c = -1;
				if ((c>=0) && (mx[i].type==T_MX) && (strlen(mx[i].stype)==0)) {
				 if (strlen(mxc_result[c])==0) {
				   if (noisey)  imsg("dns: mx got cached response OF FAILURE ch=%d c=%d (%s)\n",mx[i].n,c,mxc_result[c]);
				   mx[i].invalid = TRUE;
				   mx_failed(i,"Cached failure");
				   mx_ncached_fails++;
				 } else {
				   if (noisey)  imsg("** mx got cached response ch=%d c=%d (%s)\n",mx[i].n,c,mxc_result[c]);
				   mx_success(i,mxc_result[c],FALSE);
				 }
				 mx_ncached++;
				}
				else if (nopen<70) bk_lookup(i,mx[i].oname); else imsg("nopen exceeded %d\n",nopen);
			}
//		}
		mxnew = FALSE;
	}
//	for (i=0; i<nchan; i++) bk_tick(i,msec);
	bk_tick(thischan,msec);
	mxc_age(FALSE);
	return TRUE;
}
int bk_lookup(int x, char *name)
{
	mx_free();
	if(name[strlen(name)-1]=='\n') name[strlen(name)-1]=0;
	if (strcmp(mx[x].stype,"txt")==0) return bk_start(x,name,T_TXT,TRUE);
	if (strcmp(mx[x].stype,"a")==0) return bk_start(x,name,T_A,TRUE);
	if (strcmp(mx[x].stype,"ptr")==0) return bk_start(x,name,T_PTR,TRUE);
	if (strcmp(mx[x].stype,"mx")==0) return bk_start(x,name,T_MX,TRUE);
	if (mx[x].isorbs) return bk_start(x,name,T_A,TRUE);
	return bk_start(x,name,T_MX,TRUE);
}
int bk_set_timeout(int x)
{
	ini_timeout = x;
	return TRUE;
}
int bk_set_workarea(char *work)
{
	ncpy(workarea,work,BFSZ-1);
	return TRUE;
}
int bk_set_disk_cache(int x)
{
	ini_disk_cache = x;
	return TRUE;
}
int bk_clear_nameserver(void)
{
	bkns_n = 0;
	return TRUE;
}
int bk_add_nameserver(char *s)
{
	char bf[BFSZ];
	char *upto;
	int i;
	if (bkns_n>6) return FALSE;
	ncpy(bf,s,BFSZ-1);
	s = mystrtok(bf,", ",&upto);
	for (;s!=NULL;) {
		for (i=0; i<bkns_n; i++) {
			if (strcmp(bkhosts[i],s)==0) goto next;
		}
		ncpy(bkhosts[bkns_n++], s,BFSZ-1);
next:		s = mystrtok(NULL,", ",&upto);
	}
	return TRUE;
}
static char g_home[BFSZ];
int bk_init(int (*vdmsg)(const char * arg_list, ...),int (*vimsg)(const char * arg_list, ...),int (*vemsg)(const char * arg_list, ...))
{
	static int done;
	int x;
	if (!done) {
		for (x=0; x<MAX_CURRENT; x++) {
			bksk[x] = INVALID_SOCKET;
			tcp_new(&bkch[x]);
		}
	}
	done = TRUE;
	mx_start = time(NULL);
	bkns_n=0;
	bkns = 0;
	dmsg = vdmsg;
	imsg = vimsg;
	emsg = vemsg;
	bktimeout = ini_timeout;
	if (bktimeout<35) {
		/*TRW2.8d; imsg("dns: Adjusting timeout to %d seconds, as %d is too low\n",bktimeout,35);*/
		imsg("dns: Adjusting timeout to %d seconds, as %d is too low\n",35,bktimeout);
		bktimeout = 35;
	}
	dns_disk_init();
//	bk_default_dns();

	if(bkns_n==0) return FALSE;
	return TRUE;
}
int bk_start(int x, char *name, int xtype,int isnew)
{
	if (noisey) imsg("dns:  mx start(%d,%s,%d,%d) %s\n",x,name,xtype,isnew,mx[x].stype);
	mx[x].nask++;
	if (mx[x].name != NULL) free(mx[x].name);
	mx[x].name = strdup(name);
	mx[x].at = time(NULL);
	mx[x].state = BK_SEND;
	mx[x].last_type = xtype;
	mx[x].type = xtype;

	if (bksk[x]==INVALID_SOCKET) if (!bk_open(x)) return TRUE;
	return bk_send(x);
}

int bk_comp(char *dname, char *cp, int bflen)
{
	char bf[BFSZ];
	char *s,*cpstart=cp;
	int n;

	ncpy(bf,dname,BFSZ-1);
	s = strtok(bf,".");
	for (;s!=NULL;) {
		n = strlen(s);
		*cp++ = (char ) n;
		bflen--;
		ncpy(cp,s,bflen-1);
		cp += n;
		bflen -= n;
		s = strtok(NULL,".");
	}
	*cp = 0;
	cp++;
	return cp-cpstart;
}

#define MAXPACKET (8*1024)
static char bkname[BFSZ];
int bk_send(int x) /* Send the current request to the current name server*/
{
	char guard1[100];
	u_char buf[MAXPACKET];
	char guard2[100];
	int n;
	short ss;
	char tmp[BFSZ];
	int r;
	int i;
	for (i=0; i<100; i++) {
		guard1[i] = 1;
		guard2[i] = 1;
	}

	mx[x].state = BK_SEND;

	ncpy(bkname,mx[x].name,BFSZ-1);
	reqid[x]++;
	if (reqid[x]>99) reqid[x] = 2;
	n = bk_mkquery(x*100+reqid[x],QUERY, bkname, C_IN,mx[x].type, buf, sizeof(buf));
	for (i=0; i<100; i++) {
		if (guard1[i] != 1) abort();
		if (guard2[i] != 1) abort();
	}


	if (n <= 0) { 	mx_failed(x,"mkquery failed"); 	return FALSE;}
	errno = 0;
	if (!bk_open(x)) return TRUE;
	if (bksk[x]==INVALID_SOCKET) return TRUE;
	ss = htons((short) n);
	r = socket_write(bksk[x],(char *) &ss,2);
	tcp_lasterror(bkch[x]);
	if (r!=2) goto xxx;
	r = socket_write(bksk[x],buf,n);
	tcp_lasterror(bkch[x]);
xxx:	if (r!=n) {

	if (noisey) imsg("socket_write failed %d %d %s\n",r,bkch[x]->xerrno,tcp_error(bkch[x],tmp));
	// Lets test the socket to see if it's closed.
		if (r>0) {
			imsg("dns: Partial socket write to dns failure\n");
			goto nextone;
		}
		if (tcp_not_connected(bkch[x])) {
			if (noisey)  imsg("dns: not connected - [%d] maybe should give up %d sec\n",x,time(NULL)-mx[x].time_start);
			n = socket_read(bksk[x],tmp,1);
			//imsg("socket_read %d %s\n",n,tcp_error(bkch[x],tmp));
			if (n==0) {
					emsg("dns: not connected closed remote %s\n",tcp_error(bkch[x],tmp));
					goto nextone; // If we can't send request for 5 seconds fail and try next name server
			}
			tcp_lasterror(bkch[x]);
			if (n<0 && !tcp_wouldblock(bkch[x])) {
					emsg("dns: not connected closed hard %s\n",tcp_error(bkch[x],tmp));
					goto nextone; // If we can't send request for 5 seconds fail and try next name server
			}
			if (time(NULL)-mx[x].time_start>7) { // Can't connect so try again.
				emsg("dns: not connected - [%d] maybe should give up %d sec\n",x,time(NULL)-mx[x].time_start);
				goto nextone; // If we can't send request for 5 seconds fail and try next name server
			}
			return TRUE;
		}
		if (tcp_wouldblock(bkch[x])) {
			if (noisey)  imsg("dns: Waiting to send request %d sec",time(NULL)-mx[x].time_start);
			if (time(NULL)-mx[x].time_start>20) {
				emsg("dns: Waiting to send request %d sec",time(NULL)-mx[x].time_start);
				goto nextone; // If we can't send request for 5 seconds fail and try next name server
			}
			return TRUE;
		}
		imsg("*** Error *** dns socket_write failed %s\n",tcp_error(bkch[x],tmp));
nextone:
		if (bk_nextns(x,"socket_write")) return TRUE;
		if(r==0)
			return TRUE;
		else
			return FALSE;
	}
	mx[x].at = time(NULL);
	mx[x].state = BK_READ;
	return TRUE;
}
int bk_close(int x, char *reason)
{
	if (bksk[x]!=INVALID_SOCKET) {
		socket_close(bksk[x]);
		bksk[x] = INVALID_SOCKET;
		nopen--;
	}
	bksk[x] = INVALID_SOCKET;
	return TRUE;
}
int bk_nextns(int x, char *reason)
{
	if (noisey)  imsg("dns: bk_nextns[%d] (%s) ns=%d nok=%d fails=%d\n",x,reason,bkns_n,current_nok,dns_fails);
	bk_close(x,reason);
	if (bkns_n<2) return FALSE;
	if (current_nok>0) if (dns_fails<MAX_SWITCH) return FALSE;
	bkns++;
	current_nok = 0;
	imsg("**dns Trying next name server due to (%s) from %s\n",reason,current_dns);
	if (bkns>=bkns_n) {
		bkns = 0;
		mx_retry_all(x);
		return FALSE;
	}
	mx_retry_all(x);
	return TRUE;
}
int dns_reset_main(void)
{
	imsg("**dns resetting to main dns server\n");
	bkns = 0;
	return TRUE;
}

int bk_open(int x)
{
	int dst_port=53;
    struct sockaddr_in xsin;
	char name[BFSZ];
	char tmp[BFSZ];
    int st;
	int w = TRUE;
	time_t used;
	struct hostent *hp;

	used = time(NULL)-bk_lastused[x];
	if (bksk[x]!=INVALID_SOCKET) {
		if (used>5) imsg("dns: bk_open: last used %d seconds ago\n",used);
		if (used>30) {
			bk_close(x,"Close as idle for 30 seconds");
		}
	}
	bk_lastused[x] = time(NULL);
	if (bksk[x]!=INVALID_SOCKET) {
//		imsg("bk_open[%d] - already open\n",x);
		return TRUE;
	}
	if (bkns>=bkns_n) {
		imsg("dns: bk_open [%d] failing because bkns exceeded %d\n",x,bkns);
		return FALSE;
	}
	ncpy(name,bkhosts[bkns],BFSZ-1);
	imsg("[%d] dns: ** dns Connect to (%s)\n",x,name);
	ncpy(current_dns,name,100);
	memset(&xsin,0,sizeof(xsin));
	if(lib_isip(name))
	{
		xsin.sin_family = AF_INET;
		xsin.sin_addr.s_addr = inet_addr(name);
	}
	else
	{
		time_t xstart;
		xsin.sin_addr.s_addr = 0;
		xstart = time(NULL);
		hp = gethostbyname(name);
		if (time(NULL)-xstart > 2) emsg("USE AN IP NUMBER IN DNS_HOST SETTING (%d)\n",time(NULL)-xstart);
		if (hp==NULL) {
			imsg("dns: host lookup of dns server (%s) failed",name);
			return FALSE;
		}
		safe_memcpy(&(xsin.sin_addr),hp->h_addr,  hp->h_length,&(xsin.sin_addr),8);
		xsin.sin_family = hp->h_addrtype;
	}
	bksk[x] = socket(xsin.sin_family,SOCK_STREAM, 0);
	tcp_lasterror(bkch[x]);
	if (bksk[x]==INVALID_SOCKET) {
	        emsg("*** Error *** dns Can't open socket to {%s} ",name);
	        return FALSE;
	}
	nopen++;
	xsin.sin_port = htons((unsigned short) dst_port);
	if (socket_ioctl(bksk[x],FIONBIO,&w)!=0) {
		emsg("*** Error *** dns Can't set socket as non blocking");
		socket_close(bksk[x]);
		bksk[x] = INVALID_SOCKET;
		nopen--;
		return FALSE;
	}

        st = connect(bksk[x],(struct sockaddr *)   &xsin, sizeof(xsin));
	tcp_lasterror(bkch[x]);
     	if (st!=0) {
		if (tcp_wouldblock(bkch[x])) {
			if (noisey)  imsg("bk_open[%d] but wouldblock\n",x);
			return TRUE;
		}
		emsg("*** Error *** dns Can't connect to (%s) %s",name,tcp_error(bkch[x],tmp));
		socket_close(bksk[x]);
		nopen--;
		bksk[x] = INVALID_SOCKET;
		bk_nextns(x,"connect");
		return FALSE;
        } else {
			imsg("bk_open connected ok\n");
		}

	if (noisey)  imsg("bk_open[%d] successful\n",x);
	return TRUE;
}
int is_exit(void);
int bk_timeout(int x)
{
	int r;
	int delay=0;
	int timeout=FALSE;
	static time_t last_timeout;
	char bf[BFSZ];
	int max_switch =MAX_SWITCH;
	if (mx[x].state==BK_IDLE) return FALSE;
	if (mx[x].state==BK_READY) return FALSE;
	if (mx[x].state==BK_FAILED) return FALSE;
	if (mx[x].timeout>0) if ((time(NULL) - mx[x].at)>mx[x].timeout) {
		timeout = TRUE;
	}
	if (is_exit()) timeout = TRUE;
	if (timeout || ((time(NULL) - mx[x].at)>bktimeout)) {
		delay = time(NULL) - mx[x].at;
		sprintf(bf,"timedout %d",delay);
		mx[x].at = time(NULL);
		if (mx_gotsome(x)) {
			if (noisey)  imsg("dns: timedout but gotsome was true, so not switching\n");
			return FALSE;
		}
		if (max_switch>0) mx_failed(x,bf);
		if (delay < 20) {
			if (noisey)  imsg("dns: timedout but timeout less than 20 seconds so not switching\n");
			return FALSE; /* Short timeout, so don't switch name servers */
		}
		if ((time(NULL)-last_timeout)>30) {
		 if (dns_fails>max_switch || current_nok==0) {
			if ((bkns_n>1) || (dns_fails>100) || current_nok==0) {
				if (noisey)  imsg("dns: switching to next server %d %d\n",dns_fails,current_nok);
				last_timeout = time(NULL);
				r = bk_nextns(x,"timeout");
				return !r;
			} else {
				if (noisey) imsg("dns: Not switching to next dns server as we only have the one\n");
			}
		 } else {
			 if (noisey)  imsg("dns: Not switching as only had %d/%d fails see dns_switch_nfails\n",dns_fails,max_switch);
		 }
		} else {
			if (noisey)  imsg("dns: Not switching to next server yet as did that %d seconds ago\n",time(NULL)-last_timeout);
		}
		dns_fails++;
	}
	return FALSE;
}
int bk_tick(int x, int msec)
{
	if (bk_timeout(x))
	{
		return TRUE;
	}
	switch (mx[x].state) {
	  case BK_IDLE:
		break;
	  case BK_SEND:
		bk_send(x);
		break;
	  case BK_READ:
		bk_read(x,msec);
		break;
	  case BK_READY:
		return TRUE;
	  case BK_FAILED:
		return TRUE;
	}

	return FALSE;
}
static char bkans[32*1024];
int bk_reading(int x);

int bk_read(int x, int msec)
{
	/* Try and read, if we get data then process it
	   send new request if CNAME is response */
	int n;
	char tmp[BFSZ];
	short len;
	if (!bk_open(x)) {
		return FALSE;
	}

//	if (!tcp_isopen(bkch)) return FALSE;
//	if (!tcp_waitfor(bkch[x],0)) {
//		return TRUE;
//	}

	n = socket_read(bksk[x],(char *) &len,2);
	tcp_lasterror(bkch[x]);
	// New code, next 4 lines, this is much more efficient than doing a select on it.
	if (n==0) goto close_it;
	if (n<0) {
		if (tcp_wouldblock(bkch[x])) return TRUE;
	}
	if (n<2) {
		if (tcp_wouldblock(bkch[x])) return TRUE;
close_it:
		imsg("dns: Socket read got only %d of %d bytes wanted from %s %s\n",n,2,current_dns,tcp_error(bkch[x],tmp));
		mx_failed(x,"bk_read failed");
		return bk_nextns(x,"bk_read"); /* Try next if exists */
	}
//imsg("dns Packet length %d -> %d  size (%d)\n",len,(int) htons(len),(int) sizeof(bkans));
	len = htons(len);

	if ((len<0) || (len>sizeof(bkans))) {
		emsg("dns: Lookup got packet length %d %d %s\n",len,(int) sizeof(bkans),tcp_error(bkch[x],tmp));
		mx_failed(x,"bk_read2 failed");
		bk_nextns(x,"packet size");
		return FALSE;
	}
	bklen = len;
	bkupto = 0;
	return bk_reading(x);
}
int bk_reading_real(int x);
void bk_process(char *ans, int len);
int bk_reading(int x)
{
	int r;
	r = bk_reading_real(x);
	return r;
}
int bk_reading_real(int x)
{
	int n;
	int tot=0;
	int wanted;

again:	wanted = bklen-bkupto;
	if (bksk[x]==INVALID_SOCKET) if (!bk_open(x)) return FALSE;
	n = socket_read(bksk[x],bkans+bkupto,wanted);

	tcp_lasterror(bkch[x]);
	if (n<(wanted)) {
		if (tcp_wouldblock(bkch[x]) || (n>0)) {
			if (n>0) bkupto += n;
			lib_msleep(20);
			tot++;
			if (tot>100) {
				bk_nextns(x,"got_rubish_from_dns_timeout");
				imsg("dns: Socket read dns returned garbage, trying next name server %d/%d %d/%d\n",n,wanted,bkupto,bklen);
				return FALSE;
			}
			goto again;
		}
		bk_nextns(x,"socket_read");
		imsg("dns: Socket read failed, trying next name server %d/%d %d/%d\n",n,wanted,bkupto,bklen);
		if (n>0) imsg("dns: Socket read failed, PROBABLE FAULT IN dsmtp dns code\n");
		return FALSE;
	}
	if (tot>0) if (noisey) imsg("dns: waited %d/100ths of a second for the packet to arrive\n",tot);
	bk_process(bkans,bklen);
	return TRUE;
}
static int base_len;
void show_query(unsigned char *answer);
void bk_process(char *ans, int len)
{
	strcpy(bkresult,"");
	base_len = len;
	show_query((u_char *) ans);
}
/*---------------------------------------------------------*/
/*------My decoding STUFF ----------------------------*/
char *type_name(int x);
int get_short(unsigned char **s);
char *get_string(unsigned char **s);
int  show_q(u_char **answer);
int get_long(unsigned char **s);
int show_rr(u_char **answer);
static u_char *base;

#define MAXMX 100
static MXREC mxrecs[MAXMX+100];
static int nmx;
static int ismx;
void mx_replace(char *name, char *newname)
{
	MXREC *m;
	int i;
	if (dns_debug) dmsg("dns: mx replace %s -> %s\n",name,newname);
	for (i=0; i<nmx; i++) {
		m = &mxrecs[i];
		if (dns_debug) dmsg("dns mx replace %d %s\n",i,m->name);
		if (strcmpnc(name,m->name)==0) {
			if (dns_debug) dmsg("dns Replacing mx entry [%d] %s = %s\n",i,m->name,newname);
			free(m->name);
			m->name = strdup(newname);
		}
	}
}
void mx_free(void)
{
	MXREC *m;
	int i;
	for (i=0; i<nmx; i++) {
		m = &mxrecs[i];
		if (m->name!=NULL) free(m->name);
		if (m->addr!=NULL) free(m->addr);
		m->name = NULL;
		m->addr = NULL;
	}
	nmx = 0;
}
int is_digits(char *s)
{
	for (;*s!=0;s++) {
		if (isdigit(*s)) continue;
		if (*s=='.') continue;
		return FALSE;
	}
	return TRUE;

}
int mx_add_name(char *name, int pri)
{
	int i;
	char *s;
	int r;
//printf("dns: mx_add_name(%s,%d) \n",name,pri);
	dmsg("dns: mx_add_name(%s,%d) \n",name,pri);
	for (i=0; i<nmx; i++) {
		if (strcmpnc(name,mxrecs[i].name)==0) {
			if (pri<mxrecs[i].pri) mxrecs[i].pri = pri;
			return i;
		}
	}
	if ((nmx+1)>=MAXMX) {
		emsg("Too many MX entries for host %s\n",name);
		return 0;
	}
	mxrecs[nmx].name = strdup(name);
	mxrecs[nmx].addr = NULL;
	if (is_digits(name)) {
		mxrecs[nmx].addr = strdup(name);
		s = mxrecs[nmx].addr;
		if (strlen(s)>1) s[strlen(s)-1] = 0;
	}
	// Randomize order as we are supposed to.
	r = rand()%20; if (r<0) r = -r;
//  r = 0;
	mxrecs[nmx++].pri = pri + (r)/30.0;
	return nmx-1;
}
int mx_cmp(const void *aa, const void *bb);
int mx_cmp(const void *aa, const void *bb)
{
	MXREC *a= (MXREC *) aa;
	MXREC *b= (MXREC *) bb;
	return (int) (10.0*(a->pri - b->pri));
}

void mx_add_addr(char *name, char *addr)
{
	int i;
	char old[BFSZ];

	for (i=0; i<nmx; i++) {
		if (strcmpnc(mxrecs[i].name,name)==0) {
			strcpy(old,"");
			if (mxrecs[i].addr!=NULL) {
				ncpy(old,mxrecs[i].addr,BFSZ-1);
				ncat(old," ",BFSZ-1);
			}
			if (mxrecs[i].addr!=NULL) free(mxrecs[i].addr);
			if (strstr(old,addr)==NULL) {
				ncat(old,addr,BFSZ-1);	
			}
			mxrecs[i].addr = strdup(old);
		}
	}
	/* (check if name is the original name) */
	if (strcmpnc(name,bkname)==0) {
		i = mx_add_name(name,120);
		strcpy(old,"");
		if (mxrecs[i].addr!=NULL) {
			ncpy(old,mxrecs[i].addr,BFSZ-1);
			ncat(old," ",BFSZ-1);
			ncat(old,addr,BFSZ-1);
			free(mxrecs[i].addr);
		} else ncat(old,addr,BFSZ-1);
		mxrecs[i].addr = strdup(old);
	}
}

int bk_havedone(int x, char *name, int mask)
{
	Done *d;
	for (d=mx[x].done;d!=NULL;d=d->next) {
		if (strcmp(d->s,name)==0) {
			if ((d->mask & mask)>0) return TRUE;
			else return FALSE;
		}
	}
	return FALSE;
}

void bk_donedid(int x, char *name, int mask)
{
	Done *d;
	for (d=mx[x].done;d!=NULL;d=d->next) {
		if (strcmp(d->s,name)==0) {
			d->mask = d->mask | mask;
			dmsg("dns: donedid %d %s mask=%d (%d)\n",x,name,d->mask,mask);
			return;
		}
	}
	d = malloc(sizeof(Done));
	d->next = mx[x].done;
	d->s = strdup(name);
	d->mask = mask;
	dmsg("dns: donedid %d %s mask=%d (%d)\n",x,name,d->mask,mask);
	mx[x].done = d;
}
void mx_free_done(Done *d)
{
	Done *nxt;

	for (;d!=NULL;d=nxt) {
		nxt = d->next;
		free(d->s);
		free(d);
	}

}
void mx_copy(MXREC *dst, MXREC *src)
{
	if (src->name!=NULL) dst->name = strdup(src->name);
	if (src->addr!=NULL) dst->addr = strdup(src->addr);
	dst->pri = src->pri;

}
void mx_free_mx(MXREC *m)
{
	MXREC *nxt;
	for (;m!=NULL; m=nxt) {
		nxt = m->next;
		if (m->name!=NULL) free(m->name);
		if (m->addr!=NULL) free(m->addr);
		m->name = NULL;
		m->addr = NULL;
		free(m);
	}
}
static char cur_question[BFSZ];
static char bkcaddr[BFSZ];
static int g_x;
void show_query(unsigned char *answer)
{
	int   id,op,qdcount,ancount,nscount,arcount;
	int i, do_mx_again;
	int fails[100];
	char tmp[BFSZ];
	int x;
	int oldnmx;
	MXREC *amx;

	strcpy(cur_question,"(NONE)");
	base = answer;
	id = get_short(&answer);
	x = id / 100;
	g_x = x;
	if ( (x<0) || (x>=nchan) ) {
		dmsg("*** Warning *** Discarding old dns packet id=%d x=%d\n",id,x);
		return;
	}
	if ( reqid[x] != (id%100) ) {
		dmsg("*** Warning *** Discarding old dns packet id=%d x=%d\n",id,x);
		return;
	}
	if ( mx[x].state != BK_READ ) {
		dmsg("*** Warning *** Discarding old dns packet id=%d in state %d\n",id,mx[id].state);
		return;
	}
	mx[x].nask--;
	dmsg("dns: REQUEST ID %d:%d %s oname=%s nask=%d\n",x,id,mx[x].name,mx[x].oname,mx[x].nask);
	strcpy(bkcname,""); /* Clear the cname */
	ncpy(bkname,mx[x].name,BFSZ-1);

	op = get_short(&answer);
	qdcount = get_short(&answer);
	ancount = get_short(&answer);
	nscount = get_short(&answer);
	arcount = get_short(&answer);

	/* Add each MX name to an array, with priority and number */
	mx_free();
	nmx = 0;
	ismx = FALSE;
	for (amx = mx[x].mx; amx!=NULL; amx = amx->next) {
		if (nmx+4>MAXMX) {
			emsg("Too many MX entries for host %.80s\n",bkname);
		} else {
			mx_copy(&mxrecs[nmx++],amx);
		}
	}
	if (qdcount>100) {
		if (noisey) imsg("dns: show_q Bad data qdcount, discarding packet\n");
		socket_close(bksk[x]);
		nopen--;
		bksk[x] = INVALID_SOCKET;
		return;
	}
	if ((ancount+nscount+arcount)>200) {
		imsg("dns: show_q Bad data qdcount, discarding packet\n");
		socket_close(bksk[x]);
		nopen--;
		bksk[x] = INVALID_SOCKET;
		return;
	}
	for (i=0; i<qdcount; i++) {
		if (!show_q(&answer)) {
			imsg("dns: show_q Bad data, discarding packet\n");
			socket_close(bksk[x]);
			nopen--;
			bksk[x] = INVALID_SOCKET;
			return;
		}
		if ( (char *)(answer + 1000) >= (char *)(bkans + sizeof(bkans)) ) {
			imsg("dns: Gone past end of packet\n");
			socket_close(bksk[x]);
			nopen--;
			bksk[x] = INVALID_SOCKET;
			return;
		}
	}
	oldnmx = nmx;
	for (i=0; i<(ancount+nscount+arcount);i++) {
		if (!show_rr(&answer)) {
			imsg("dns: show_rr Bad data, discarding packet\n");
			socket_close(bksk[x]);
			nopen--;
			bksk[x] = INVALID_SOCKET;
			return;
		}
		if ( (char *)(answer+1000) >= (char *)(bkans + sizeof(bkans)) ) {
			imsg("dns: Gone past end of packet\n");
			socket_close(bksk[x]);
			nopen--;
			bksk[x] = INVALID_SOCKET;
			return;
		}
	}
	if (nmx==oldnmx) if (ismx) {
		if (noisey)  imsg("dns Got no mx records back from mx lookup took %d seconds\n",time(NULL) - mx[x].at);
		if ((time(NULL) - mx[x].at)>20) {
			if (!mx[x].twice) {
				/* Repeat the original request, as this brain dead dns server will respond the next time*/
				if (noisey)  imsg("dns: Doing SECOND MX query for MX host %s\n",mx[x].name);
				mx[x].twice = TRUE;
				ncpy(tmp,mx[x].name,200);
				bk_start(x,tmp,T_MX,FALSE);
				return;
			}
		}
	}
	for (i=0; i<100; i++) fails[i] = FALSE;
	strcpy(bkresult,"");
	strcpy(bkcaddr,"");
	if (nmx>1) {
		qsort(mxrecs,nmx,sizeof(MXREC),mx_cmp);
	}
	do_mx_again=FALSE;
	for (i=0; i<nmx; i++)
	{
		if (mxrecs[i].addr==NULL)
		{
			if (noisey) imsg("dns: MX[%d] %s = %s  pri=%g\n",i,mxrecs[i].name,"UNKNOWN",mxrecs[i].pri);
			if (!bk_havedone(x,mxrecs[i].name,1)) {
				fails[i]=TRUE;
				do_mx_again=TRUE;
			} else {
				if (dns_debug) dmsg("dns Already looked up that mx record once");
			}
		}
		else
		{
			if (noisey) imsg("dns: MX[%d] %s = %s  pri=%g\n",i,mxrecs[i].name,mxrecs[i].addr,mxrecs[i].pri);
			ncat(bkresult,mxrecs[i].addr,BFSZ-1);
			ncat(bkresult," ",BFSZ-1);
		}
	}
	if (!do_mx_again) if (strlen(bkresult)!=0) {
		dmsg("dns: bkresult and !domxagain (%s)\n",bkresult);
		mx_success(x,bkresult,TRUE);
		return;
	}

	mx[x].loop++;
	if (mx[x].loop>5)
	{
		if (strlen(bkresult)>0) {
			if (noisey)  imsg("dns: mx lookup loop locked (%s), but se seem to have an answer\n",bkname);
			mx_success(x,bkresult,TRUE);
			return;
		} else {
			if (noisey)  imsg("dns: mx lookup loop locked (%s), and no answer\n",bkname);
			mx_failed(x,"dns loop");
			return;
		}
	}
//	if (!mx[x].isorbs )
	  if (do_mx_again) {
		for(i=0;i<nmx;i++) {
			/* Send plain query about mx host, should do this for each one. */
			if(fails[i]) if(!bk_havedone(x,mxrecs[i].name,1)) {
				if (noisey)  imsg("dns: Doing A query for MX host %s\n",mxrecs[i].name);
				bk_donedid(x,mxrecs[i].name,1);
				bk_start(x,mxrecs[i].name,T_A,FALSE);
				goto done;
			}
		}
	  }
	if (strlen(bkcname)>0)
	{
		/* Send MX query about cname */
		if (!bk_havedone(x,bkcname,2)) {
			i = mx_add_name(bkcname,120);
			if (mxrecs[i].addr!=NULL) free(mxrecs[i].addr);
			bk_donedid(x,bkcname,2);
			if (noisey)  imsg("dns: Doing mx query with cname %s\n",bkcname);
			bk_start(x,bkcname,T_MX,FALSE);
			goto done;
		} else goto doplain;
	}
	else
	{
		/* Send plain query for original name*/
doplain:	if (mx[x].last_type==T_A || (mx[x].isorbs) )
		{
			if (noisey) imsg("*** Warning *** dns No address for %s\n",bkname);
			if (mx[x].nask<=0) {
				goto try_mx;
			}
			goto done;
		}
		if (noisey)  imsg("dns: Doing A query with original name %s ID was %d:%d, qdcount=%d (%s)\n",bkname,x,id,qdcount,cur_question);
		bk_start(x,bkname,T_A,FALSE);
		goto done;
	}

try_mx:
	if (mx[x].nask<=0) {
		if (!mx[x].isorbs )  for(i=0;i<nmx;i++) {
			/* Send plain query about mx host, should do this for each one. */
			if(!bk_havedone(x,mxrecs[i].name,2)) {
				dmsg("dns: Doing MX query for MX host %s\n",mxrecs[i].name);
				bk_donedid(x,mxrecs[i].name,2);
				bk_start(x,mxrecs[i].name,T_MX,FALSE);
				goto done;
			}
		}
		mx[x].invalid = TRUE;
		mx_failed(x,"Nothing outstanding so finished");
		return;
	}

done:
	/* Copy array of mxrecs back into linked list */
	mx_free_mx(mx[x].mx); mx[x].mx = NULL;
	for (i=0; i<nmx; i++) {
		amx = malloc(sizeof(MXREC)); memset(amx,0,sizeof(MXREC));
		mx_copy(amx,&mxrecs[i]);
		amx->next = mx[x].mx;
		mx[x].mx = amx;
	}

}

int show_q(u_char **answer)
{
/* if we get a cname, then resend mx request with new name */
	char *s;
	int t,c;
	s = get_string(answer);
	t = (int)get_short(answer);
	c = (int)get_short(answer);
	/*if (dns_debug) */
	if (noisey) imsg("dns: Question: %s, type=%x, class=%x\n", s, t,c);
	if (t==T_MX) ismx = TRUE;
	strncpy(cur_question,s,90);
	cur_question[90] = 0;
	if (strlen(s)<3) if (t==0) if (c==0) return FALSE;
	return TRUE;
}
char * rr_a(u_char **answer);
char * rr_mx(u_char **answer, int *pri);
char * rr_cname(u_char **answer);
static char mxaddr[BFSZ];
int show_rr(u_char **answer)
{
	int rdlen;
	char bf2[BFSZ];
	char host[BFSZ];
	int pri;
	u_char *s,*save;
	int xtype,xclass,ttl;
	char *cn;
	char *txt;
	s = get_string(answer);
	if (s==NULL) strcpy(host,"(NULL)");
	else ncpy(host,s,BFSZ-1);
	xtype = get_short(answer);
	xclass = get_short(answer);
	ttl = get_long(answer);
	rdlen = get_short(answer);

	if (rdlen>1000) {
		return FALSE;
	}
	save = *answer;
	sprintf(bf2,"%.90s t=%x c=%d ttl=%d len=%d ",host,xtype,xclass,ttl,rdlen);
	if (xtype==T_A) {
		rr_a(answer);
		if (noisey) imsg("dns: Got T_A address %s %s\n",s,mxaddr);
		if (strcmp(s,bkcname)==0) ncpy(bkcaddr,mxaddr,BFSZ-1);
		mx_add_addr(s,mxaddr);
	} else if (xtype==T_CNAME) {
		cn = rr_cname(answer);
		dmsg("dns: GOT CNAME (%s) \n",cn);
		if (strcmpnc(bkname	,host)==0) {
			ncpy(bkcname,cn,BFSZ-1);
			mx_replace(bkname,bkcname);
		} else {
			if (noisey)  imsg("*** Warning *** Dont match (%s) (%s) \n",host,bkname);
		}
		dmsg("dns: %s CNAME %s\n",bf2,cn);
	} else if (xtype==T_MX) {
		cn = rr_mx(answer,&pri);
		if (dns_debug) dmsg("dns: %s MX %d %s\n",bf2,pri,cn);
		if (noisey) imsg("dns: MX %s %d (%s)\n",bf2,pri,cn);
	} else if (xtype==T_TXT) {
		char *start=*answer;
		int loop=0;
		txt = get_text(base,answer,start+rdlen);
		mx_add_addr(s,txt);
	} else if (xtype==T_PTR) {
		txt = rr_cname(answer);
//printf("ptr result (%s) (%s)\n",s,txt);
		mx_add_addr(bkname,txt);
	} else if (xtype==T_NS) {
		txt = rr_cname(answer);
		if (noisey) imsg("dns: Name Server (%s)\n",txt);
	} else {
//printf("IGNORED RECORD %d\n",xtype);
//		if (noisey) imsg("ignore %d\n",xtype);
		if (dns_debug) dmsg("dns: %s Ignored\n",bf2);
	}
	*answer = save;
	*answer += rdlen;
	return TRUE;
}
char *rr_a(u_char **answer)
{
	struct in_addr in;
	in.s_addr = htonl(get_long(answer)); /* Double convert as none is needed */
	ncpy(mxaddr,inet_ntoa(in),BFSZ-1);
	return mxaddr;
}
char *rr_cname(u_char **answer)
{
	char *s;
	s = get_string(answer);
	return s;
}
char *rr_mx(u_char **answer, int *pri)
{
	char *name;
	*pri = get_short(answer);
	name = get_string(answer);
	if (strlen(name)==0) {
		imsg("dns: Stupid blank mx response, replaced with name itself (%s)\n",bkname);
		name = bkname;
	}
	mx_add_name(name,*pri);
	return name;
}
char *get_string(unsigned char **s)
{
/*
	Trying to describe this sytnax. 
	
	
	*/
	static char full[1024];
	static char bf[1024];
	int jmp = -1;
	int nloop = 0;
	u_char *j = *s;
	u_char *xend=NULL;
	unsigned int n = *j;
	strcpy(full,"");
	for (;n>0;) {
		nloop++;
		if (nloop>10) break;
		if (n>64) {
			jmp = get_short(&j);
			if (xend==NULL) xend = j;
			jmp = jmp & 0x3fff;
			j = jmp+base;
			n = *j;
			if (n>60) break;
		}
		if (n<0) n = 0;
		if (n>BFSZ-1) n = 0;
		strncpy(bf,j+1,n);
		bf[n] = 0;
		ncat(full,bf,BFSZ-1);
		ncat(full,".",BFSZ-1);
		j += n+1;
		n = *j;
	}
	j++;
	if (xend!=NULL) j = xend;
	*s = j;
	return full;
}


char *get_text(u_char *base, unsigned char **s, u_char *end)
{
	static char full[1024];
	static char bf[1024];
	int nloop=0;
	int jmp = -1;
	u_char *j = *s;
	u_char *xend=NULL;
	unsigned int n = *j;
	strcpy(full,"");
	if (n<0) return full;
	if (n>255) return full;
	ncpy(full,j+1,n);
	*s += n+1;
	return full;	
}


int get_short(unsigned char **s)
{
	unsigned short x;
	memcpy(&x,*s,sizeof(short));
	x = (unsigned) htons(x);
	*s += 2;
	return x;
}
int get_long(unsigned char **s)
{
	int x;
	memcpy(&x,*s,sizeof(int));
	x = htonl(x);
	*s += 4;
	return x;
}

struct SYMS  rrtypes[] = {
	{T_A,		"A",		"address"},
	{T_NS,		"NS",		"name server"},
	{T_MD,		"MD",		"mail destination (deprecated)"},
	{T_MF,		"MF",		"mail forwarder (deprecated)"},
	{T_CNAME,	"CNAME",	"canonical name"},
	{T_SOA,		"SOA",		"start of authority"},
	{T_MB,		"MB",		"mailbox"},
	{T_MG,		"MG",		"mail group member"},
	{T_MR,		"MR",		"mail rename"},
	{T_NULL,	"NULL",		"null"},
	{T_WKS,		"WKS",		"well-known service (deprecated)"},
	{T_PTR,		"PTR",		"domain name pointer"},
	{T_HINFO,	"HINFO",	"host information"},
	{T_MINFO,	"MINFO",	"mailbox information"},
	{T_MX,		"MX",		"mail exchanger"},
	{T_TXT,		"TXT",		"text"},
	{T_RP,		"RP",		"responsible person"},
	{T_AFSDB,	"AFSDB",	"DCE or AFS server"},
	{T_X25,		"X25",		"X25 address"},
	{T_ISDN,	"ISDN",		"ISDN address"},
	{T_RT,		"RT",		"router"},
	{T_NSAP,	"NSAP",		"nsap address"},
	{T_NSAP_PTR,	"NSAP_PTR",	"domain name pointer"},
	{T_SIG,		"SIG",		"signature"},
	{T_KEY,		"KEY",		"key"},
	{T_PX,		"PX",		"mapping information"},
	{T_GPOS,	"GPOS",		"geographical position (withdrawn)"},
	{T_AAAA,	"AAAA",		"IPv6 address"},
	{T_LOC,		"LOC",		"location"},
#ifdef FREDXXX
	{T_NXT,		"NXT",		"next valid name (unimplemented)"},
	{T_EID,		"EID",		"endpoint identifier (unimplemented)"},
	{T_NIMLOC,	"NIMLOC",	"NIMROD locator (unimplemented)"},
	{T_SRV,		"SRV",		"server selection"},
	{T_ATMA,	"ATMA",		"ATM address (unimplemented)"},
	{T_IXFR,	"IXFR",		"incremental zone transfer"},
#endif
	{T_AXFR,	"AXFR",		"zone transfer"},
	{T_MAILB,	"MAILB",	"mailbox-related data (deprecated)"},
	{T_MAILA,	"MAILA",	"mail agent (deprecated)"},
#ifdef FREDXXX
	{T_NAPTR,	"NAPTR",	"URN Naming Authority"},
#endif
	{T_ANY,		"ANY",		"\"any\""},
	{0, 		NULL,		NULL}
};


char *type_name(int x)
{
	int i;
	for (i=0;rrtypes[i].name!=NULL;i++) {
		if (rrtypes[i].op==x) return rrtypes[i].name;
	}
	return "Unknown";
}
/*------RESOLVER LIBRARY STUFF ----------------------------*/
int bk_mkquery(int x, int op, char *dname, int xclass
	    , int type, u_char *buf, int buflen)
{
	register HEADER *hp;
	register u_char *cp;
	register int n;
	u_char *dnptrs[20], **dpp, **lastdnptr;

	/*
	 * Initialize header fields.
	 */
//	if (type==T_PTR) op = IQUERY;
	if ((buf == NULL) || (buflen < HFIXEDSZ))
		return (-1);
	memset(buf, 0, HFIXEDSZ);
	hp = (HEADER *) buf;
	hp->id = htons((short) x);
	hp->opcode = op;
	hp->rd = 1; /* Recursion */
	hp->rcode = NOERROR;
	cp = buf + HFIXEDSZ;
	buflen -= HFIXEDSZ;
	dpp = dnptrs;
	*dpp++ = buf;
	*dpp++ = NULL;
	lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0];
	/*
	 * perform opcode specific processing
	 */

	if ((buflen -= QFIXEDSZ) < 0)
		return (-1);
	n = bk_comp(dname,cp,buflen);
	cp += n;
	buflen -= n;
	bk_putshort((u_short) type, cp);
	cp += INT16SZ;
	bk_putshort((u_short) xclass, cp);
	cp += INT16SZ;
	hp->qdcount = htons(1);
	return (cp - buf);
}

void bk_putshort(u_int src, u_char *dst)
{
	register u_int16_t t_s = (u_int16_t)(src);
	register u_char *t_cp = (u_char *)(dst);
	*t_cp++ = (u_char) (t_s >> 8);
	*t_cp   = (u_char) t_s;
	(dst) += NS_INT16SZ;
}


/*---------------------------------------------------------*/
#ifdef WIN32
int getdtablesize(void);
#endif


int mx_gotsome(int x)
{
	MXREC *amx;
	int i;

	mx_free();
	nmx = 0;
	for (amx = mx[x].mx; amx!=NULL; amx = amx->next) {
		if (nmx+4>MAXMX) {
			emsg("Too many MX entries for host %.80s\n",bkname);
		} else {
			mx_copy(&mxrecs[nmx++],amx);
		}
	}

	strcpy(bkresult,"");
	if (nmx>1) {
		qsort(mxrecs,nmx,sizeof(MXREC),mx_cmp);
	}
	for (i=0; i<nmx; i++)	{
		if (mxrecs[i].addr!=NULL) {
			ncat(bkresult,mxrecs[i].addr,BFSZ-1);
			if (strlen(mx[i].stype)>0) {
				ncat(bkresult,"\xfe",BFSZ-1);
			} else {
				ncat(bkresult," ",BFSZ-1);
			}			
		}
	}
	mx_free();
	if (strlen(bkresult)!=0) {
		dmsg("dns: gotsome (%s)",bkresult);
		mx_success(x,bkresult,TRUE);
		return TRUE;
	}
	return FALSE;
}
/*
3000 of them, 3000 * 80 = 240K
File format of disk cache.

  Name|result
  Name|result
  Name|result

Index in memory
  Name, Offset

Add
	Find, if different delete old entry
		Add new entry at end of file
Find, Seek and read line.

On startup, when data file wastage exceeds 30% re-write.



    idx file.



    #name

A single file.

dns name, cache string



*/
#define DISK_CACHE dns_file("dns_disk.dat")
typedef struct DCACHE {char *name; char *result; time_t when;  struct DCACHE *next; }Dcache ;
#define DHASH 1000
static Dcache *hcache[DHASH];
static int ndisk;

char *dns_file(char *fname)
{
	static char bf[BFSZ];

	if (strlen(workarea)==0) strcpy(workarea,".");
	sprintf(bf,"%s/%s", workarea, fname);
	return bf;
}
#define CACHESZ 3000
int dns_free_some(Dcache *last)
{
	Dcache *d,*next;
	for (d=last;d!=NULL;d=next) {
		next = d->next;
		ndisk--;
		free(d->name);
		free(d->result);
		free(d);
	} 
	return TRUE;
}
int dns_exit(void)
{
	int i;

	for (i=0; i<nchan; i++) {
		if (mx[i].state==BK_READY) continue;
		if (mx[i].state==BK_FAILED) continue;
		if (mx[i].state!=BK_IDLE) {
			imsg("dns_exit chan %d state %d\n",i,mx[i].state);
			mx_failed(i,"Shutting down dns_exit called");
		}
	}	
	for (i=0; i<DHASH; i++) {
		dns_free_some(hcache[i]);
		hcache[i] = NULL;
	}

  for (i=0; i<MAX_CURRENT; i++) {
	  if (mx[i].state==BK_IDLE) {
		if (bkch[i]) tcp_free(&(bkch[i]));
	  }
	}
/*
	these belong to the threads in question, so should not be freed here.
  for (x=0; x<MAX_CURRENT; x++) {
		if (bkch[x]) tcp_free(&(bkch[x]));
		if (mx[x].name != NULL) free(mx[x].name);
		if (mx[x].oname != NULL) free(mx[x].oname);
		mx[x].name=NULL;
		mx[x].oname=NULL;

	}
*/
	return TRUE;
}
int dns_disk_trim(void)
{
	static int upto;
	int i;
	if (ndisk<CACHESZ) return TRUE;
	for (i=upto; i<DHASH; i++) {
		dns_free_some(hcache[i]);
		hcache[i] = NULL;
		if (ndisk<CACHESZ) return TRUE;
	}
	upto = 0;
	for (i=upto; i<DHASH; i++) {
		dns_free_some(hcache[i]);
		hcache[i] = NULL;
		if (ndisk<CACHESZ) return TRUE;
	}
	return TRUE;
}
int dns_disk_add(char *name, char *result, int dodisk)
{
	/* Find in memory if exists and result same return */
	/* If changed then append to file and change in memory */
	/* If not in memory add to memory and append to file  */
	static int nfix;
	Dcache *d;
	FILE *f;
	int n=0;
	int h;

	dns_disk_trim();
	if (strlen(name)<=1) return FALSE;
	if (strlen(name)>100) return FALSE;
	if (strlen(result)>BFSZ-100) return FALSE;
	h = lib_str_hash(name,DHASH);
	for (d=hcache[h];d!=NULL;d=d->next) {
		if (strcmp(name,d->name)==0) {
			if (strcmp(result,d->result)==0) return TRUE;
			if (dodisk) if (time(NULL)- d->when < 60*60) return TRUE; // Don't update if changed in last hour
			free(d->result);
			d->when = time(NULL);
			d->result = strdup(result);
			nfix++;
			goto add_disk;
		}
	}
	ndisk++;
	d = calloc(1,sizeof(Dcache));
	d->when = time(NULL);
	d->name = strdup(name);
	d->result = strdup(result);
	d->next = hcache[h];
	hcache[h] = d;
add_disk:
	if (dodisk) {
		f = fopen(DISK_CACHE,"a");
		if (f==NULL) {
			emsg("dns: Couldn't write to %s %s\n",DISK_CACHE,strerror(errno));
			return FALSE;
		}
		fprintf(f,"%s|%s\n",name,result);
		fclose(f);
		if (nfix>500) {
			nfix = 0;
			if (noisey)  imsg("dns: Writing compressed dns disk cache\n");
			dns_disk_write();
		}
	}
	return TRUE;
}
int dns_disk_lookup(char *name, char *result)
{
	/* Find in memory, if exists return */
	Dcache *d;
	int h;
	imsg("dns: Cache lookup %d entries\n",ndisk);
	h = lib_str_hash(name,DHASH);
	for (d=hcache[h];d!=NULL;d=d->next) {
		if (strcmp(name,d->name)==0) {
			ncpy(result,d->result,BFSZ-1);
			imsg("dns: Cache lookup %d entries - found (%s) (%s)\n",ndisk,name,result);
			return TRUE;
		}
	}
	return FALSE;
}
int dns_disk_init(void)
{
	/* Read in disk cache, write out compressed */
	FILE *f;
	char name[BFSZ];
	char result[BFSZ];
	char bf[BFSZ];
	char *s;
	int nread=0;

	dmsg("dns: dns_disk_init called\n");
	f = fopen(DISK_CACHE,"r");
	if (f==NULL) {
		dmsg("dns Could not read dns disk cache %s %s\n",DISK_CACHE,strerror(errno));
		return FALSE;
	}
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-1,f)==NULL) break;
		nread++;
		s = strtok(bf,"|\r\n"); if (s==NULL) continue;
		ncpy(name,s,BFSZ-1);
		s = strtok(NULL,"|\r\n"); if (s==NULL) continue;
		ncpy(result,s,BFSZ-1);
		dns_disk_add(name,result,FALSE);
	}
	fclose(f);
	dmsg("dns: dns_disk_init rewriting %d read %d\n",ndisk,nread);
	dns_disk_write();
	dmsg("dns: dns_disk_init ndisk %d read %d\n",ndisk,nread);
	return TRUE;
}
int dns_disk_write(void)
{
	FILE *f;
	Dcache *d;
	int i;
	f = fopen(DISK_CACHE,"w");
	if (f==NULL) {
		emsg("dns: Could not write to %s %s\n",DISK_CACHE,strerror(errno));
		return FALSE;
	}
	for (i=0; i<DHASH; i++) {
		for (d=hcache[i];d!=NULL;d=d->next) {
			fprintf(f,"%s|%s\n",d->name,d->result);
		}
	}
	fclose(f);
	return TRUE;
}


