/*

Test domains we should check still work:

 DNS lookup (austinprofessionals.com.au) result (27.50.91.101) fb=0
 Worked
 rhosts=sau-42d7a-or.iproperty.com.au,temp3.sau-42d7a-or.iproperty.com.au,mail.austinprofessionals.com.au,austinprofessionals.com.au,





*/

#define SRCFILE_NUM 3

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include "../sam/tcplib.h"
#include "../sam/lib.h"
#include "../sam/lprintf.h"
#include "dns.h"
#include "dnsx.h"
#define DMID 15
#include "debug.h"
#include "defines.h"
#define dtrace(str) trace_add(__FILE__,__LINE__,str)
int trace_add(char *source, int line, char *info);
#ifndef FALSE
#define FALSE 0
#define TRUE (!FALSE)
#endif
#include "dns.h"
#define iffree(xx) if (xx) {free(xx); (xx) = NULL;}
#ifndef USE_DMALLOC
void dmalloc_ignore_leak(void){}
#endif
#ifdef ISOSF
/*typedef unsigned int u_int16_t;*/
#endif

#ifdef WIN32
#define WIN_SOCK 1
#endif

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

#define MAXPACKET (8*1024)


typedef struct ANSWER {
	char *host; 
	char *addr; 
	int pri; 
	int type; 
	int rid;
	int rnd;
	int used; // if we've processed thsi response...
	struct ANSWER *next;
} Answer;


typedef struct REQUEST {
	char *host; 
	int type;
	int rid;
	struct REQUEST *next;
} Request;
typedef struct MX {
	char *host; 
	char *addr;
	int pri;
	int rnd;
	struct MX *next;
} Mx;

typedef struct DNSX {
	Chan *c;
	char *type;
	int itype;
	char result[BFSZ*3];
	char reason[BFSZ];
	int failed;
	Answer *answer;
	Request *request;
	Request *queued;	// queued requests not sent yet...
	Wordlist *rhost;
	int nqueued;
	Mx *mx;
	int rid;
	int retry; // true if we should retry the request.
	int truncated;  // true if results were truncated, we should retry on tcpip...
	u_char *base; // base of result packet while processing.  
	int nbusy; // number of outstanding requests
	int lasttype;
	int istcp;
	int gotip; // true if mx result was an ip address
	int dnsi;
} Dnsx;
#define imsg dnslog
//#define imsg printf

static int ntimeout;
int dnsx_matches(char *h1, char *h2);
char *dnsx_getserver(int *ni);
void dnsx_reset(Dnsx *x);
int dnsx_next(Dnsx *x, char *whence);
int  dnslog(const char * arg_list, ...);
static int do_ipv6 = FALSE;
int dnsx_add_request(Dnsx *dns,char *host,int xtype, int rid);
int dnsx_add_txt(Dnsx *dns,char *txt);
char *dnsx_get_text(u_char *base, unsigned char **s, u_char *end, char *full, int fullsz);
char *dnsx_rr_aaaa(u_char **answer, char *mxaddr);
char *dnsx_rr_mx(Dnsx *dns,u_char **answer, int *pri, char *tmp);
int dnsx_show_rr(Dnsx *dns, u_char **answer, int rid, u_char *end);
int dnsx_wait(Dnsx *dns, int timeout);
int dnsx_free_head(Mx **phead);
int dnsx_read(Dnsx *x, char *reason) ;
int dnsx_count(Dnsx *dns, char *reason);
int dnsx_start_que(Dnsx *dns,char *name, int type);
int dnsx_start_next(Dnsx *dns);
int dnsx_free_que(Dnsx *dns);

int dnsx_open(Dnsx *dns, int istcp);
Dnsx *dnsx_new(void);
int dnsx_add_txt(Dnsx *dns,char *txt);
int dnsx_set_ipv6(int x);
int dnsx_send(Dnsx *dns, char *name, char *type);
void dnsx_free(Dnsx *x);
int dnsx_start(Dnsx *dns,char *name, int type);
int dnsx_waitread(Dnsx *x, int timeout,char *reason);
int dnsx_reading(Dnsx *dns, u_char *answer, int len);
int dnsx_show_q(Dnsx *dns, u_char **answer, char *name); // question...
char *dnsx_rr_a(u_char **answer, char *result);
char *dnsx_get_string(unsigned char **s, char *full, char *base);
int dnsx_add_answer(Dnsx *dns,int rid, char *host,char *addr,int xtype,int pri);
int dnsx_mkquery(int x, int op, char *dname, int xclass, int type, u_char *buf, int buflen);
int dnsx_add_mx(Mx **head,char *host, char *addr, int pri);
int dnsx_write(Dnsx *dns, char *bf, int n);
static int noisey = TRUE;

int dnsx_setlog(int x)
{
	noisey = x;
	return TRUE;
}
int dnsx_set_ipv6(int x)
{
	do_ipv6 = x;
	return TRUE;
}
static char *dnsservers[10]; //="192.168.1.254";
static int ns_tried[10]; 
static int ns_worked[10]; 
static int nservers;
//static int upto;
//static int nswitch;

static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds);
int dnsx_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}

void dnsx_mwait(void)  // Mutexes are no longer used as each thread has it's own dns structure...
{
	mutex_cb(mutex_i,TRUE,600);
} 
void dnsx_mrelease(void)
{
	mutex_cb(mutex_i,FALSE,0);
}

static int dns_packet[10];
static int dns_open[10];
static int dns_timeout[10];
static int dns_secure[10];
static int dns_scoreall[10];
int dnsx_score(int i)
{
	return dns_scoreall[i];
}
int dnsx_smooth(void)
{
	int max=1;
	int i;
	int a;
	for (i=0;i<nservers;i++) {
		a = dns_scoreall[i];
		if (a>max) max = a;
	}
	max = max / 5;
	if (max<1) max = 1;
	for (i=0;i<nservers;i++) {
		dns_scoreall[i] = dns_scoreall[i]/max;
	}
	return TRUE;
}
char *dnsx_status(int i, char *bf)
{
	int ni=0;
	char *dhost = dnsx_getserver(&ni);
	if (i>=nservers) return NULL;
	if (i>9) return NULL;

	lprintf(bf,BFSZ-1,"%s Successful=%d/%d p=%d,o=%d,t=%d,s=%d %s",dnsservers[i],ns_worked[i],ns_tried[i]
		,dns_packet[i],dns_open[i],dns_timeout[i],dns_secure[i],ni==i ? "<-Current":"");
	return bf;
}
int dnsx_add_nameserver(char *servers) 
{
	char bf[BFSZ];
	char *s;
	char *upto=NULL;

	nservers = 0;
	ncpy(bf,servers,BFSZ-1);
	for (s = mystrtok(bf,", ",&upto);s!=NULL;s=mystrtok(NULL,", ",&upto)) {
		char *was =  dnsservers[nservers];
//		printf("dnsx: adding server %s \n",nservers,s);
		dnsservers[nservers++] = strdup(s);
		if (nservers>6) break;
		if (was!=NULL) free(was); 
	}
	return TRUE;
}
int dnsx_append_fe(char *bf,int bfsz, char *toadd)
{
	if (*bf==0) {
		ncpy(bf,toadd,bfsz-1);
//		imsg("dnsx: len=%d appended (%s)\n",strlen(bf),toadd);
		return TRUE;
	}
	ncat(bf,"\xfe",bfsz-1); 
	ncat(bf,toadd,bfsz-1);
//	imsg("dnsx: len=%d appended (%s)\n",strlen(bf),toadd);
	return TRUE;
}
int dnsx_append(char *bf, char *toadd)
{
	//imsg("dnsx: Appended (%s)\n",toadd);
	if (*bf==0) {
		ncpy(bf,toadd,BFSZ-1);
		return TRUE;
	}
	ncat(bf," ",BFSZ-1); 
	ncat(bf,toadd,BFSZ-1);
	return TRUE;
}
Request *dnsx_find_request(Dnsx *x, int rid)
{
	Request *r;
	for (r=x->request;r!=NULL;r=r->next) {
		if (r->rid==rid) return r;
	}
	return NULL;
}
int dnsx_add_rhost(Dnsx *dns, char *word)
{
	word_add_nodup(&dns->rhost,word);
	return TRUE;
}

int dnsx_process_results(Dnsx *dns)
{
	// Go through the answers, and try and find a valid one, or reque another lookup if needed...
	Answer *a;
	Request *r;
	int ismx=FALSE;

	if (dns->itype==T_MX) ismx = TRUE;

	for (a=dns->answer;a!=NULL;a=a->next) {
		if (a->used) continue;
		
		r = dnsx_find_request(dns,a->rid);
		if (r==NULL) {
			imsg("dnsx: unknown rid so ignoring answer %d %s %s\n",a->rid,a->host,a->addr);
			
			continue;
		}
		a->used = TRUE;
		if (noisey) imsg("dnsx: Answer %s -> %s %d %s (req=%s)\n",nullok(a->host),nullok(a->addr),a->pri,dns_type_name(a->type),dns_type_name(dns->itype));		
		

		//Process answer 10 alt2.gmail-smtp-in.l.google.com. 2607:f8b0:4003:c12::1b
		//dnsx: Answer alt2.gmail-smtp-in.l.google.com. -> 2607:f8b0:4003:c12::1b 0 AAAA (req=MX)
		switch (a->type) {
			case T_A:
				if (ismx) {
					if (noisey) imsg("dnsx: ismx so igonring 'a' response %s\n",a->addr);					
					continue;
				}
				if (strcmpnc(r->host,a->host)!=0) {					
					if (noisey) imsg("dnsx: wrong host %s != %s so ignoring a response \n",r->host,a->host);
					continue; // not the address of this request.
				}
				dnsx_append(dns->result,a->addr);
				break;
			case T_AAAA:
				if (ismx) {
					if (noisey) imsg("dnsx: ismx so igonring 'aaaa' response %s\n",a->addr);					
					continue;
				}
				if (strcmpnc(r->host,a->host)!=0) {
					if (noisey) imsg("dnsx: wrong host %s != %s so ignoring aaaa response \n",r->host,a->host);					
					continue; // not the address of this request.
				}
				if (do_ipv6) {					
					dnsx_append(dns->result,a->addr);
				}
				break;
			case T_MX:
				//if (strcmp(r->host,a->host)!=0) continue; // not the address of this request.
				
				if (ismx) {
					if (strstrnc(a->addr,"nullmx")!=NULL) {
						strcpy(dns->result,"invalid_domain_nullmx");
						break;
					}
					dnsx_add_mx(&(dns->mx),a->addr,NULL,a->pri);
					dnsx_start_que(dns,a->addr,T_A);			
					if (do_ipv6) {
						
						dnsx_start_que(dns,a->addr,T_AAAA);			
					}
				}
				break;
			case T_CNAME:
				dnsx_add_rhost(dns,a->addr);
				dnsx_start_que(dns,a->addr,T_A);
				break;
			case T_PTR:
				strcpy(dns->result,""); // because we only allow a single answer.
				dnsx_append(dns->result,a->addr);
				break;
			default:
				break;
		}
	}
	return TRUE;
}
Mx *dnsx_mx_find(Dnsx *dns, char *host)
{
	Mx *mx;
	for (mx=dns->mx;mx!=NULL;mx=mx->next) {
		if (strcmpnc(host,mx->host)==0) return mx;
		//printf("mx find (%s) (%s)\n",host,mx->host);
	}
	// Next check again usinag aliases


	return NULL;
}
int dnsx_cmp(const void *a, const void *b)
{
	Mx *aa = *(Mx **)a;
	Mx *bb = *(Mx **)b;
	if (aa->pri==bb->pri) return aa->rnd-bb->rnd;
	return aa->pri-bb->pri;
}
int dnsx_rev_alias(Dnsx *dns, char *host, char *rev)
{
	Answer *b;
	for (b=dns->answer;b!=NULL;b=b->next) {
		if (b->type==T_CNAME) {
			if (dnsx_matches(host,b->addr)) {
				ncpy(rev,b->host,BFSZ-1);
				return TRUE;
			}
		}
	}
	return FALSE;
}
int dnsx_build_mx(Dnsx *dns)
{
	// Go through the answers, and try and find a valid one, or reque another lookup if needed...
	Answer *a;
	Request *r;
	Mx *m;
	Mx *head = NULL;
	Mx **table=NULL;
	int nmx = 0;
	int i;
	char rev[BFSZ];

	if (noisey) imsg("dnsx: buildmx started\n");
	if (dns->nbusy!=0) return FALSE;
	if (dns->itype!=T_MX) return FALSE;
	if (noisey) if (strlen(dns->result)>0) imsg("dnsx: dnsx_build_mx started and result already exists (%s)\n",dns->result);
	for (a=dns->answer;a!=NULL;a=a->next) {
		if (noisey) imsg("dnsx: buildmx host=%s addr=%s %d %d\n",a->host,a->addr,a->type,a->rid);
		//printf("dnsx: buildmx host=%s addr=%s %d %d\n",a->host,a->addr,a->type,a->rid);
		r = dnsx_find_request(dns,a->rid); 				if (r==NULL) continue;
		if (noisey) imsg("dnsx: buildmx %s %d rid found\n",a->host,a->type,a->rid);
		if (a->type==T_A || a->type==T_AAAA) {
			// Find this in the 'mx' list, if we can find it, then add to the answer mx list	
			m = dnsx_mx_find(dns,a->host);
			if (m!=NULL) {
				if (noisey) imsg("dnsx: mx found %s -> %s\n",m->host,a->addr);
				dnsx_add_mx(&head,m->host,a->addr,m->pri);
			} else {
				if (noisey) imsg("dnsx_mx_find: failed to find %s -- try aliases..\n",a->host);
				// mail.kodaptix.ca
				// 12 10:54:04.30:Info:24744: dnsx_add_answer CNAME pri=0 relais.time.ca. mail.kodaptix.ca.
				if (a->type==T_A) {
					int ri=0;
					char start[BFSZ];
					ncpy(start,r->host,BFSZ-1);
					for (;dnsx_rev_alias(dns,start,rev);ri++) {
						if (ri>4) break;
						if (noisey) imsg("dnsx: reverse alias found %s->%s\n",start,rev);	
		 				m = dnsx_mx_find(dns,rev);
						if (m!=NULL) {
							dnsx_add_mx(&head,rev,a->addr,m->pri);
							break;
						} else {
							ncpy(rev,start,BFSZ-1);
							if (noisey) imsg("dnsx_mx_find: failed to find %s in aliases (%d deep)\n",start,ri);
						}
					}
				}
			}
		}				
	}
	for (m=head;m!=NULL;m=m->next) nmx++;
	// Now turn into a sortable table...
	table = calloc(nmx,sizeof(Mx *));
	for (m=head,nmx=0;m!=NULL;m=m->next) {
		table[nmx++] = m;
	}
	if (nmx>1) qsort(table,nmx,sizeof(Mx *),dnsx_cmp);
	for (i=0; i<nmx; i++)  {
		if (noisey) imsg("dnsx: append  %s pri=%d\n",table[i]->addr,table[i]->pri);
		dnsx_append(dns->result,table[i]->addr);
	}
	dnsx_free_head(&head)	;
	free(table);
	return TRUE;
}
int dnsx_free_head(Mx **phead)
{
	Mx *m,*next;
	for (m=*phead;m!=NULL;m=next) {
		next = m->next;
		free(m->addr);
		free(m->host);
		free(m);
	}
	*phead = NULL;
	return TRUE;
}
int dnsx_ntimeout(void)
{
	return ntimeout;
}
int dnsx_lookup(char *name, char *type, char *result, int resultsz, char *reason, int timeout, char *rhosts)
{
	static int counter;
	int done_retry=FALSE;
	char xreason[BFSZ];
	int z;
	time_t last = time(NULL);
	// Do the lookup...
	int ret=FALSE;
	int done_resend = FALSE;
	time_t start = time(NULL);
	Dnsx *dns = dnsx_new();
	ncpy(rhosts,"",BFSZ-1);
	if (timeout==0) {
		timeout = 10;
		if (strcmpnc(type,"mx")==0) timeout = 21; // long timeout for mx lookups.  chrisp, reduces from 31, which is too long.
	}
	if (noisey) imsg("dnsx_lookup %s (%s) timeout=%d -----------\n",name,type,timeout);
	dnsx_add_rhost(dns,name);
again:
	dtrace("reset1");
	dnsx_reset(dns);
	if (!dnsx_open(dns,FALSE)) {
		dnsx_next(dns,"Open failed");	
		if (!dnsx_open(dns,FALSE)) {
			dtrace("open1");
			dnsx_free(dns); // could be here
			strcpy(result,"dns_fail_open");
			return FALSE;
		}
	}
	strcpy(result,""); strcpy(reason,"No dnsx issue");
	dnsx_send(dns,name,type);
	start = time(NULL);
	last = time(NULL); // reset last as well so we dont timeout without giving it a chance, chrisp bug fix.
	ns_tried[dns->dnsi]++;
	for (;TRUE;) {
		int elap,elap2;
		if (dns->nbusy==0) {
			dnsx_start_next(dns);
		}
		elap = (int) (time(NULL)-last);
		elap2 = (int) (time(NULL)-start);
		if (  (elap>=timeout) || (elap2>=(timeout+10)) ) { // start is sometimes the same as time()...
			lprintf(reason,BFSZ-1,"Timeout (%d %d)/%d",elap,elap2,timeout);
			ntimeout++;
			imsg("dnsx: %s\n",reason);
			dnsx_build_mx(dns);
			if (strlen(dns->result)>0) {
				imsg("dnsx: Partial response we could use? (%s)",dns->result);
				ncpy(dns->reason,"Partial response used",BFSZ-1);
				goto ok;
			}
			goto done;
		}
		if (dns->nbusy<=0) {
			if (!dnsx_start_next(dns)) break; // all requests done...
		}
		if (!dnsx_waitread(dns,timeout,xreason)) {
			ntimeout++; 
			dnsx_count(dns,"timeout waitread");
			lprintf(reason,BFSZ-1,"Timeout waiting for response %d %s",(int)(time(NULL)-start),xreason);
			imsg("dnsx: Timeout waiting for response %d type=%s\n",(int)(time(NULL)-start),type);
			if (strlen(result)==0) strcpy(result,"dns_timeout"); // record it was a likely timeout issue.  
			if (strcmpnc(type,"mx")!=0) goto done; // only switch dns if mx lookup fails...
			if (!dnsx_next(dns,"timeout waitread")) goto done;
			if (!done_resend) {
				imsg("dnsx: trying next nameserver\n");
				done_resend = TRUE;
				goto again;
			}

			goto done;
		}
		last = time(NULL);
		if (!done_retry) if (dns->truncated) { // resend over tcp...
			dtrace("reset2");
			dnsx_reset(dns);
			if (!dnsx_open(dns,TRUE)) goto failed;
			imsg("Truncated, so re-opening \n");
			strcpy(result,""); strcpy(reason,"");
			dnsx_send(dns,name,type);
			done_retry = TRUE;
			start = time(NULL);
			continue;
		}
		if (dns->failed) break;
		if (dns->istcp) if (!dnsx_open(dns,FALSE)) goto failed; // reopen it as normal udp connection...  but NOT RESET, so we don't forget the answer...
		dnsx_process_results(dns);
		// remove build_mx from here...

		if (dns->itype==T_MX) if (dns->nbusy==0) if (dns->mx==NULL) if (!dns->gotip) {
			dns->itype = T_A; // CHANGE TYPE...			
			imsg("dnsx: Mx lookup returned no useful records, so trying 'a' lookup on %s, itype=A\n",name);				
			dnsx_add_mx(&(dns->mx),name,NULL,255); // add fake mx entry - chrisp boosted priority so it will be used last...
			dnsx_start_que(dns,name,T_A);			
		}


	}
	dnsx_build_mx(dns);
ok:
	ncpy(result,dns->result,resultsz-1);
	ncpy(reason,dns->reason,BFSZ-1);
//	if (!dns->failed) strcpy(reason,"OK Worked");
	if (noisey) imsg("dnsx: %s (%s) -> %s (%s) %s %s\n",name,type,result,reason,strlen(result)==0 ? "BLANK?" : "",dns->reason);
done:
	{
		Wordlist *w;
		strcpy(rhosts,"");
		for (w=dns->rhost;w!=NULL;w=w->next) {
			lib_trim_dot(w->word);
			ncat(rhosts,w->word,BFSZ-1);
			ncat(rhosts,",",BFSZ-1);
//			printf("rhosts: adding %s\n",w->word);
		}

	}
//printf("rhosts: finsihed %s\n",rhosts);
//	ret = !dns->failed;
	ret = TRUE;
	if (noisey) imsg("dnsx: completed %s\n",dns->reason);
	z = dns->dnsi;
	dtrace("open2");
	dnsx_free(dns); // COULD BE HERE
	if (strcmp(result,"invalid_domain")==0) return FALSE;
	if (strcmp(result,"dns_fail")==0) return FALSE;
	if (strcmp(result,"dns_timeout")==0) return FALSE;
	ns_worked[z]++;
	if (strcmp(result,"")==0) return TRUE; // got nothing useful, but return TRUE as the lookups worked. 
	return ret; // 
failed:
	strcpy(result,"dnxopen failed");
	strcpy(reason,result);
	dtrace("open3");
	dnsx_free(dns); // could be here. 
	return FALSE;
}
int dnsx_send(Dnsx *dns, char *name, char *type)
{
	int t = T_A;
	if (strcmpnc(type,"txt")==0) t = T_TXT;
	if (strcmpnc(type,"a")==0) t = T_A;
	if (strcmpnc(type,"aaaa")==0) t = T_AAAA;
	if (strcmpnc(type,"ptr")==0) t = T_PTR;
	if (strcmpnc(type,"mx")==0) t = T_MX;
	if (dns->type==NULL) dns->type = strdup(type); // remember original request type...
	strlwr(dns->type);
	dns->itype = t;
	if (noisey) imsg("dnsx: dnsx_send (%s) itype=%s\n",name,type);
	dnsx_start_que(dns,name,t);
	return TRUE;
}
Dnsx *dnsx_new(void)
{
	Dnsx *dnsx = calloc(1,sizeof(Dnsx));
	return dnsx;
}
int dnsx_open(Dnsx *dns, int usetcp)
{
	int ni=0;
	char *dhost = dnsx_getserver(&ni);

	if (dhost==NULL) return FALSE;
	dns->istcp = usetcp;
	dns->nbusy = 0;
	if (dns->c!=NULL) tcp_free(&dns->c);
	
	tcp_new(&dns->c);
	dns->dnsi = ni;  // the server we are using (dhost)
	if (usetcp) {
		if (noisey) imsg("dns_open: opening tcp connection %s\n",dhost);
		if (!tcp_open_noblock(dns->c,dhost,53,NULL)) {
			imsg("dnsx_open: opening tcp connection - failed \n");
			return FALSE;
		}
		tcp_setblock(dns->c,TRUE);
	} else {
		if (noisey) imsg("dns_open: opening udp connection %s\n",dhost);
		if (!tcp_open_udp(dns->c,dhost,53)) {
			return FALSE;
		}
		tcp_setblock(dns->c,FALSE);
	}
	return TRUE;
}
void dnsx_reset(Dnsx *x)
{
	Answer *a, *next;
	Request *r, *rnext;
	for (a=x->answer;a!=NULL;a=next) {
		next = a->next;
		dtrace("reset1");
		iffree(a->addr); 
		dtrace("reset2");
		iffree(a->host);
		dtrace("reset3");
		free(a);		
	}
	x->answer = NULL;
	dtrace("reset4");
	for (r=x->request;r!=NULL;r=rnext) {
		rnext = r->next;
		iffree(r->host);
		free(r);		
	}
	dtrace("reset5");
	x->request = NULL;
	dnsx_free_head(&x->mx);
	dnsx_free_que(x);

	x->nbusy = 0;
}

void dnsx_free(Dnsx *x)
{
	word_free(&x->rhost);
	dnsx_reset(x);
	x->request = NULL;
	free(x->type); 
	tcp_free(&x->c);
	free(x);
}

int dnsx_free_que(Dnsx *dns)
{
	Request *r, *next;
	for (r=dns->queued;r!=NULL;r=next) {
		next = r->next;
		free(r->host);
		free(r);
	}
	dns->queued = NULL;
	dns->nqueued = 0;
	return TRUE;
}
int dnsx_start_next(Dnsx *dns)
{
	Request *r = dns->queued;
	if (r==NULL) return FALSE;
	dns->queued = r->next;
	dns->nqueued--;
	dnsx_start(dns,r->host,r->type);
	free(r->host); free(r);
	return TRUE;
}
int dnsx_start_que(Dnsx *dns,char *name, int type)
{

	Request *r = calloc(1,sizeof(Request));
	r->host = strdup(name);
	r->type = type;
	r->next = dns->queued;
	dns->queued = r;
	dns->nqueued++;
	return TRUE;
}
int dnsx_start(Dnsx *dns,char *name, int type)
{
	int n;
	int r;
	int ret=FALSE;
	u_char *buf;
	buf = calloc(1,MAXPACKET);
	if (noisey) imsg("dnsx_start %s %s\n",name,dns_type_name(type));
	n = dnsx_mkquery(++(dns->rid),QUERY, name, C_IN,type, buf, MAXPACKET);
	dnsx_add_request(dns,name,type,dns->rid);
	dns->lasttype = type;
	if (n <= 0) goto done;
	
	r = dnsx_write(dns,buf,n);
	if (r!=n) {
		lprintf(dns->reason,BFSZ-1,"dnsx_write failed");
		goto done;
	}
	ret = TRUE;
done:
	free(buf);
	return ret;
}
int dnsx_write(Dnsx *dns, char *bf, int n)
{
	short ss;
	int r;
	ss = htons((short) n);
	if (dns->istcp) {
		if (!tcp_wait_write(dns->c,5000)) {			
			return -1;
		}
		r = tcp_write_timed(dns->c,(char *) &ss,2,2000);
		r = tcp_write_timed(dns->c,bf,n,2000);
	}
	else r = tcp_udp_write(dns->c,bf,n);
	

	return r;
}
int dnsx_waitread(Dnsx *dns, int timeout, char *reason)
{
	int r;
	time_t start = time(NULL);
	strcpy(reason,"nofault");
	r= dnsx_wait(dns,timeout);
	if (r) {
		dns->nbusy--; // we are reading another one...
		if (noisey) imsg("dnsx_reading: nbusy=%d\n",dns->nbusy);
		dnsx_read(dns,reason);
		dnsx_start_next(dns); // start new request if we have one...

	} else {
		strcpy(reason,"dnsx_wait timedout");
		if (noisey) imsg("dnsx_wait: failed so nbusy=%d\n",dns->nbusy);
	}
	
	return r;
}
int dnsx_wait(Dnsx *dns, int timeout)
{
	// Wait until channel is ready, or timeout occurs.
	int r = tcp_waitfor(dns->c,timeout*1000); // returns 'false' on timeout...
	return r;
}
char *dnsx_getserver(int *ni)
{
	int i;
	int a;
	int best = 10000000;
	int besti = 0;
	// Figure out best one.  
	for (i=0;i<nservers;i++) {
		a = dns_scoreall[i];
		if (a<best) {
			best = a;
			besti = i;
		}
	}
	*ni = besti;
	return dnsservers[besti];
	/*
	dnsx_mwait();
	s = dnsservers[upto];
	*ni = upto;
	dnsx_mrelease();
	return s;
	*/
}
int dnsx_count(Dnsx *dns, char *reason)
{
	int i = dns->dnsi;
	if (i<0) return TRUE;
	if (i>9) return TRUE;
//intf("dnx_count %d %s\n",i,reason);
	if (strstrnc(reason,"packet")!=NULL) dns_packet[i]++; 
	if (strstrnc(reason,"open")!=NULL)  dns_open[i]++; 
	if (strstrnc(reason,"timeout")!=NULL)  dns_timeout[i]++; 
	if (strstrnc(reason,"security")!=NULL)  dns_secure[i]++; 
	dns_scoreall[i]++;
	return TRUE;
}
int dnsx_next(Dnsx *dns, char *whence)
{
	dnsx_count(dns,whence);
	return TRUE;
	/*
	int ni;
	dnsx_mwait();
	upto++;

	if (upto>=nservers) upto = 0;
	dnsx_mrelease();
	nswitch++;
	imsg("dnsx: Switching to server %d %s due to %s\n",upto,dnsx_getserver(&ni),whence);
	if (nservers==1) return FALSE;
	return TRUE;
	*/
}

#define PACKETSIZE (8192+1000)
#define TCPPACKETSIZE (65535+1000)
int dnsx_read(Dnsx *dns, char *reason)
{
	int len;
	char tmp2[BFSZ];
	char *udpin = NULL;
	int ret=FALSE;
	strcpy(reason,"allgood");
	if (dns->istcp) { 
		short slen;
		int rlen;
		int n;
		udpin = calloc(1,TCPPACKETSIZE);
		n = tcp_read_timed(dns->c,(char *) &slen,2,2000);
		if (n<2) {
			strcpy(reason,"tcp_read_timed failed in dnsx_read count");
			lprintf(dns->reason,BFSZ-1,"Could not read packet length %d %s",n,tcp_error(dns->c,tmp2));
			goto failed;
		}
//		rlen = htons(slen);
		rlen = ntohs(slen);
		if (rlen>=TCPPACKETSIZE || rlen<0) {
			lprintf(reason,BFSZ-1,"Bad tcp packet length %d",rlen);
			lprintf(dns->reason,BFSZ-1,"%s",reason);
			goto failed;
		}
		
		len = tcp_read_all(dns->c,udpin,rlen,10000); // wait 1 second for the data packet
		if (len!=rlen) imsg("tcp_read wanted %d bytes, but got %d\n",rlen,len);
	} else {
		udpin = calloc(1,PACKETSIZE);
		len = tcp_udp_read(dns->c,udpin,PACKETSIZE);
		
	}
	tcp_lasterror(dns->c);
	if ((len<=0) ) {
		lprintf(dns->reason,BFSZ-1,"Got bad packet length %d bytes %s",len,tcp_error(dns->c,tmp2));
		if (!dns->istcp) dnsx_next(dns,"Got bad packet"); 
		strcpy(reason,"tcp_udb_read failed in dnsx_read");
		goto failed;
	}
	ret = dnsx_reading(dns,udpin,len);
failed:
	free(udpin); udpin = NULL;
	return ret;
}
int dnsx_reading(Dnsx *dns, u_char *answer, int len)
{
	int rcode;
	int   id,op,qdcount,ancount,nscount,arcount;
	int i;

	dns->base = answer;
	id = get_short(&answer);
	op = get_short(&answer);
	rcode = op & 0x0f;
	
	if (!dns->istcp) {
		if ((op & 512) >0) {
			dns->truncated = TRUE; 
			imsg("dnsx: truncated packet\n");
			return FALSE;
		}
	}
	
	if (rcode==2) {
		imsg("dnsx: retry dns lookup\n");
		strcpy(dns->reason,"Retry dns lookup");
		strcpy(dns->result,"retry");
		return FALSE;
	}

	if (rcode==3) {
		//imsg("dnsx: domain doesn't exist rcode=3\n");
		strcpy(dns->reason,"Domain doesn't exist rcode=3");
		// It should only do this, if it's an mx lookup...
		if (dns->lasttype == T_MX) {
			imsg("dnsx: invalid domain response on 'mx' lookup\n");
			strcpy(dns->result,"invalid_domain");
		} else {
			if (noisey) imsg("dnsx: Ignoring invalid_domain response as it wasn't on primary mx lookup\n");
		}
		return TRUE;
	}
	if (rcode==5) {
		imsg("dnsx: Security error with dns server, CODE 5\n");
		strcpy(dns->reason,"DNS security error, dns server miss configured, CODE 5");
		strcpy(dns->result,"dns_fail");
		dnsx_count(dns,"security");

		return TRUE;
	}
	
	qdcount = get_short(&answer);
	ancount = get_short(&answer);
	nscount = get_short(&answer);
	arcount = get_short(&answer);

	if (qdcount>100) {
		strcpy(dns->reason,"qdcount bad");
		return FALSE;
	}
	
	if ((ancount+nscount+arcount)>5000) {
		lprintf(dns->reason,BFSZ-1,"counts bad %d %d %d",ancount,nscount,arcount);
		return FALSE;
	}
	for (i=0; i<qdcount; i++) {
		char qname[BFSZ];
		if (!dnsx_show_q(dns,&answer,qname)) {
			strcpy(dns->reason,"bad packet questions");
			return FALSE;
		}
		if (noisey) imsg("dns: query name was %s\n",qname);
		if (answer>dns->base+PACKETSIZE-1000) {
			strcpy(dns->reason,"bad packet question length");
			return FALSE;
		}
	}
	for (i=0; i<(ancount+nscount+arcount);i++) {
		if (!dnsx_show_rr(dns,&answer,id,dns->base+len)) {
			strcpy(dns->reason,"bad rr data");
			imsg("bad_rr response\n");
			return TRUE;
			return FALSE;
		}
		if (answer>dns->base+len) {
			imsg("bad_rr length\n");
			return TRUE;
			lprintf(dns->reason,BFSZ-1,"bad rr length %d all=%d left=%d",i,ancount+nscount+arcount,(int) (answer-(dns->base+len)));
			return FALSE;
		}
	}
	return TRUE;
}





int dnsx_show_q(Dnsx *dns, u_char **answer, char *name) // question...
{
	char *s;
	int t,c;
	s = dnsx_get_string(answer,name,dns->base);
	t = (int)get_short(answer);
	c = (int)get_short(answer);
	if (lib_strlen(s)<3) if (t==0) if (c==0) return FALSE;
	return TRUE;
}

char *dnsx_rr_a(u_char **answer, char *result)
{
	struct in_addr in;
	in.s_addr = htonl(get_long(answer)); /* Double convert as none is needed */
	ncpy(result,inet_ntoa(in),BFSZ-1);
	return result;
}
char *dnsx_rr_aaaa(u_char **answer, char *mxaddr)	// 128 bit raw internet address...
{
	char tmp[BFSZ];
	strcpy(tmp,"ipv6disabled");
#ifdef DO_IPV6
	{
		struct in6_addr addr;
		memcpy(&addr,*answer,16); (*answer) += 16;
		tcp_ipv6_to_string(&addr,tmp);
	}
#endif 
	ncpy(mxaddr,tmp,BFSZ-1);
	return mxaddr;
}


int dnsx_show_rr(Dnsx *dns, u_char **answer, int rid, u_char *end)
{
	int rdlen;
	char host[BFSZ];
	int pri;
	u_char *s,*save;
	char bf[3*BFSZ];
	int xtype,xclass,ttl;
	char *cn;
	char *txt;
	int ret=TRUE;
	char mxaddr[BFSZ];
	s = dnsx_get_string(answer,bf,dns->base);
	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>10000 || rdlen<0) {
		return FALSE;
	}
	save = *answer;
	if (noisey) imsg("dnsx: show_rr response type=%d\n",xtype);
	if (xtype==0) { // invalid
		imsg("Bad DNS answer type, ignore rest %d\n",xtype);
		ret = FALSE;
	} else if (xtype==T_A) {
		dnsx_rr_a(answer,mxaddr);
		if (noisey) imsg("dnsx: show_rr response t_a %s %s\n",host,mxaddr);
		dnsx_add_answer(dns,rid,host,mxaddr,xtype,0); // Add host/address to answers...
	} else if (xtype==T_AAAA) {
		dnsx_rr_aaaa(answer,mxaddr);
		dnsx_add_answer(dns,rid,host,mxaddr,xtype,0); // Add host/address to answers...
	} else if (xtype==T_CNAME) { // translate one name to another...
		s = dnsx_get_string(answer,bf,dns->base);
		dnsx_add_rhost(dns,s);
		dnsx_add_answer(dns,rid,host,s,xtype,0);
	} else if (xtype==T_MX) {
		cn = dnsx_rr_mx(dns,answer,&pri,bf);
		dnsx_add_rhost(dns,cn);
		if (noisey) imsg("dnsx: show_rr Result t_mx cn=(%s)\n",cn);
		if (lib_isip(cn)) {
			lib_trim_dot(cn);
			imsg("Result is IP address, so adding as t_a %s\n",cn);
			dnsx_add_answer(dns,rid,host,"madeup.host.name",xtype,pri);  // HERE WE ADD a fake mx record we can find later with matching address...
			dnsx_add_answer(dns,rid,"madeup.host.name",cn,T_A,pri);
			dns->gotip = TRUE;
		} else {
			dnsx_add_answer(dns,rid,host,cn,xtype,pri);
		}
	} else if (xtype==T_TXT) {
		char *start=*answer;
		int loop=0;
		txt = dnsx_get_text(dns->base,answer,start+rdlen,bf,sizeof(bf)-1);
		dnsx_add_txt(dns,txt);
	} else if (xtype==T_PTR) {
		s = dnsx_get_string(answer,bf,dns->base);
		dnsx_add_answer(dns,rid,host,s,xtype,0);
	} else if (xtype==T_NS) {
		s = dnsx_get_string(answer,bf,dns->base);
	} else {
		if (noisey) imsg("ignore unknown DNS Answer %d\n",xtype);
	}

	*answer = save;
	*answer += rdlen;
	if (*answer > end) {
		imsg("Read past end of packet %d bytes last one %d\n",*answer - end,rdlen);
		return FALSE;
	}
	return ret;
}
char *dnsx_get_text(u_char *base, unsigned char **s, u_char *end, char *full, int fullsz)
{
	char tmp[BFSZ];
	int nloop=0;
	int jmp = -1;
	u_char *xend=NULL;
	strcpy(full,"");
	for (;*s < end;) {
		u_char *j = *s;
		unsigned int n = *j;
		if (n<0) break;
		if (n>255) break;
		ncpy(tmp,j+1,n);
		ncat(full,tmp,fullsz-1);
		*s += n+1;
	}

	return full;	
}
char *dnsx_rr_mx(Dnsx *dns,u_char **answer, int *pri, char *tmp)
{
	char *name;
	*pri = get_short(answer);
	name = dnsx_get_string(answer,tmp,dns->base);
	if (lib_strlen(name)==0) {
		ncpy(tmp,"(nullmx)",BFSZ-1);
	}
	return name;
}


char *dnsx_get_string(unsigned char **s, char *full, char *base)
{
	int jmp = -1;
	int nloop = 0;
	char bf[BFSZ];
	u_char *j = *s;
	u_char *xend=NULL;
	unsigned int n = *j;
	strcpy(full,"");
	for (;n>0;) {
		nloop++;
		if (nloop>80) 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;
}
int dnsx_add_answer(Dnsx *dns, int rid,char *host,char *addr,int xtype,int pri)
{
	Answer *a = calloc(1,sizeof(Answer));
	a->host = strdup(host);
	a->addr = strdup(addr);
	a->type = xtype;
	a->rid = rid;
	a->pri = pri;
	a->rnd = myrand();
	a->next = dns->answer;
	dns->answer = a;
	if (noisey) imsg("dnsx_add_answer %s pri=%d %s %s\n",dns_type_name(xtype),pri,host,addr);	
	return TRUE;
}
char *dnsx_add_dot(char *bf);
int dnsx_matches(char *h1, char *h2)
{
	char bf1[BFSZ];
	char bf2[BFSZ];
	ncpy(bf1,h1,BFSZ-1);
	ncpy(bf2,h2,BFSZ-1);
	dnsx_add_dot(bf1);
	dnsx_add_dot(bf2);
	if (strcmpnc(bf1,bf2)==0) return TRUE;
	return FALSE;
}
char *dnsx_add_dot(char *bf)
{
	if (strlen(bf)>0) if (bf[strlen(bf)-1]!='.') ncat(bf,".",BFSZ-1); // trailing dot required. 
	return bf;
}
int dnsx_add_request(Dnsx *dns,char *host,int xtype, int rid)
{
	Request *a = calloc(1,sizeof(Request)); // crash freeing this??? 
	char bf[BFSZ];
	ncpy(bf,host,BFSZ-1);
	if (strlen(bf)>0) if (bf[strlen(bf)-1]!='.') ncat(bf,".",BFSZ-1); // trailing dot required. 
	a->host = strdup(bf);
	a->type = xtype;
	a->rid = rid;
	a->next = dns->request;
	dns->request = a;
	dns->nbusy++;
	if (noisey) imsg("dnsx_added_request %s %s %d busy=%d\n",host,dns_type_name(xtype),rid,dns->nbusy);	
	return TRUE;
}
int dnsx_add_mx(Mx **head,char *host, char *addr, int pri)
{
	Mx *m;
	Mx *a;
	int nmx=0;
	if (addr!=NULL) for (m=*head;m!=NULL;m=m->next)  {
		if (m->pri==pri) if (strcmpnc(m->addr,addr)==0) return TRUE; // ignore duplicates.
	}
	a = calloc(1,sizeof(Mx));
	a->host = strdup(host);
	if (addr!=NULL) a->addr = strdup(addr);
	a->pri = pri;
	a->next = *head;
	*head = a;
	if (noisey) imsg("dnsx_added_mx %s %d\n",host,pri);	

	return TRUE;
}
int dnsx_add_txt(Dnsx *dns,char *txt)
{
	dnsx_append_fe(dns->result,sizeof(dns->result)-1,txt);
	return TRUE;
}

int dns_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 = lib_strlen(s);

		*cp++ = (char ) n;
		bflen--;
		ncpy(cp,s,bflen-1);
		cp += n;
		bflen -= n;
		s = strtok(NULL,".");
	}
	*cp = 0;
	cp++;
	return (int)(cp-cpstart);
}

int dnsx_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;
	if ((buf == NULL) || (buflen < HFIXEDSZ)) {
		imsg("dns_mkquer fatal fault 1\n");
		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];
	if ((buflen -= QFIXEDSZ) < 0) 	{
		imsg("dns_mkquer fatal fault 2\n");
		return (-1);
	}
	n = dns_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 (int)(cp - buf);
}




















