/*
 *	DOMAIN.C -- domain name system stub resolver
 *
 *      Original code by Phil Karn, KA9Q.
 *
 *	04-90 -- Bill Simpson added address->name resolution, time-to-live,
 *	thru	 memory caching, generalized multi-record multi-type searches,
 *	10-90	 and many minor changes to conform more closely to the RFCs.
 */

#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <sys/stat.h>
#include "global.h"
#include "mbuf.h"
#include "netuser.h"
#include "ip.h"
#include "socket.h"
#include "cmdparse.h"
#include "proc.h"
#include "domain.h"
#include "commands.h"
#include "files.h"

#undef	DEBUG				/* for certain trace messages */
#undef	DEBUG_PAIN			/* for painful debugging */

extern int main_exit;			/* from main program (flag) */

static struct rr *Dcache = NULLRR;	/* Cache of resource records */
static int Dcache_size = 20;		/* size limit */
static time_t Dcache_time = 0L; 	/* timestamp */

static int Dfile_clean = FALSE; 	/* discard expired records (flag) */
static int Dfile_reading = 0;		/* read interlock (count) */
static int Dfile_writing = 0;		/* write interlock (count) */

struct proc *Dfile_updater = NULLPROC;
static int32 Dfile_wait_absolute = 0L;	/* timeout Clock time */
static int Dfile_wait_relative = 300;	/* timeout file activity (seconds) */

static struct dserver *Dservers = NULLDOM; /* List of potential servers */
static int Dserver_retries = 2;		/* Attempts to reach servers */

static char *Dsuffix = NULLCHAR;	/* Default suffix for names without periods */
static int Dtrace = FALSE;
static char *Dtypes[] = {
	"",
	"A",
	"NS",
	"MD",
	"MF",
	"CNAME",
	"SOA",
	"MB",
	"MG",
	"MR",
	"NULL",
	"WKS",
	"PTR",
	"HINFO",
	"MINFO",
	"MX",
	"TXT"
};
static int Ndtypes = 17;
static char delim[] = " \t\r\n";

static int docache __ARGS((int argc,char *argv[],void *p));
static int dosuffix __ARGS((int argc,char *argv[],void *p));

static int docacheclean __ARGS((int argc,char *argv[],void *p));
static int docachelist __ARGS((int argc,char *argv[],void *p));
static int docachesize __ARGS((int argc,char *argv[],void *p));
static int docachewait __ARGS((int argc,char *argv[],void *p));

static void dlist_add __ARGS((struct dserver *dp));
static void dlist_drop __ARGS((struct dserver *dp));
static int dodnsadd __ARGS((int argc,char *argv[],void *p));
static int dodnsdrop __ARGS((int argc,char *argv[],void *p));
static int dodnslist __ARGS((int argc,char *argv[],void *p));
static int dodnsretry __ARGS((int argc,char *argv[],void *p));
static int dodnstrace __ARGS((int argc,char *argv[],void *p));

static int check_ttl __ARGS((struct rr *rrlp));
static int compare_rr __ARGS((struct rr *search_rrp,struct rr *target_rrp));
static int compare_rr_list __ARGS((struct rr *rrlp,struct rr *target_rrp));
static struct rr *copy_rr __ARGS((struct rr *rrp));
static struct rr *copy_rr_list __ARGS((struct rr *rrlp));
static struct rr *make_rr __ARGS((int source,
	char *dname,int16 class,int16 type,int32 ttl,int16 rdl,void *data));

static void dcache_add __ARGS((struct rr *rrlp));
static void dcache_drop __ARGS((struct rr *rrp));
static struct rr *dcache_search __ARGS((struct rr *rrlp));
static void dcache_update __ARGS((struct rr *rrlp));

static struct rr *get_rr __ARGS((FILE *fp, struct rr *lastrrp));
static void put_rr __ARGS((FILE *fp,struct rr *rrp));
static struct rr *dfile_search __ARGS((struct rr *rrlp));
static void dfile_update __ARGS((int s,void *unused,void *p));

static void dumpdomain __ARGS((struct dhdr *dhp,int32 rtt));
static int dns_makequery __ARGS((int16 op,struct rr *rrp,
	char *buffer,int16 buflen));
static void dns_query __ARGS((struct rr *rrlp));

static int isaddr __ARGS((char *s));
static struct rr *resolver __ARGS((struct rr *rrlp));


/**
 **	Domain Resolver Commands
 **/

static struct cmds Dcmds[] = {
	"addserver",	dodnsadd,	0, 2, "add <hostid>",
	"dropserver",	dodnsdrop,	0, 2, "drop <hostid>",
	"list",		dodnslist,	0, 0, NULLCHAR,
	"retry",	dodnsretry,	0, 0, NULLCHAR,
	"suffix",	dosuffix,	0, 0, NULLCHAR,
	"trace",	dodnstrace,	0, 0, NULLCHAR,
	"cache",	docache,	0, 0, NULLCHAR,
	NULLCHAR,
};

static struct cmds Dcachecmds[] = {
	"clean",	docacheclean,	0, 0, NULLCHAR,
	"list",		docachelist,	0, 0, NULLCHAR,
	"size",		docachesize,	0, 0, NULLCHAR,
	"wait",		docachewait,	0, 0, NULLCHAR,
	NULLCHAR,
};

int
dodomain(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Dcmds,argc,argv,p);
}

static int
docache(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Dcachecmds,argc,argv,p);
}

static int
dosuffix(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc < 2){
		if(Dsuffix != NULLCHAR)
			tprintf("%s\n",Dsuffix);
		return 0;
	}
	free(Dsuffix);
	Dsuffix = strdup(argv[1]);
	return 0;
}

static int
docacheclean(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool( &Dfile_clean, "discard expired records", argc,argv );
}

static int
docachelist(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct rr *rrp;

	(void)dcache_search(NULLRR); /* update ttl */
	rflush();
	for(rrp=Dcache;rrp!=NULLRR;rrp=rrp->next)
	{
		put_rr(stdout,rrp);
	}
	return 0;
}

static int
docachesize(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int newsize;
	int oldsize;
	int result;

	newsize = oldsize = Dcache_size;
	result = setint( &newsize, "memory cache size", argc,argv );

	if(newsize > 0){
		Dcache_size = newsize;
		if(newsize < oldsize){
			(void)dcache_search(NULLRR); /* update size */
		}
	}
	return result;
}

static int
docachewait(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setint( &Dfile_wait_relative, "time before file update (seconds)", argc,argv );
}

static void
dlist_add(dp)
register struct dserver *dp;
{
	dp->prev = NULLDOM;
	dp->next = Dservers;
	if(Dservers != NULLDOM)
		Dservers->prev = dp;
	Dservers = dp;
}

static void
dlist_drop(dp)
register struct dserver *dp;
{
	if(dp->prev != NULLDOM)
		dp->prev->next = dp->next;
	else
		Dservers = dp->next;
	if(dp->next != NULLDOM)
		dp->next->prev = dp->prev;
}

static int
dodnsadd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct dserver *dp;
	int32 address;

	if((address = resolve(argv[1])) == 0L){
		tprintf("Resolver %s unknown\n",argv[1]);
		return 1;
	}
	dp = (struct dserver *)callocw(1,sizeof(struct dserver));
	dp->address = address;
	dp->srtt = 5000L; /* 5 sec */
	dp->mdev = 0;
	dp->timeout = 2 * dp->mdev + dp->srtt + 3;
	dlist_add(dp);
	return 0;
}

static int
dodnsdrop(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct dserver *dp;
	int32 addr;

	addr = resolve(argv[1]);
	for(dp = Dservers;dp != NULLDOM;dp = dp->next)
		if(addr == dp->address)
			break;

	if(dp == NULLDOM){
		tprintf("Not found\n");
		return 1;
	}

	dlist_drop(dp);
	free((char *)dp);
	return 0;
}

static int
dodnslist(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct dserver *dp;

	tprintf("Server address          srtt    mdev   timeout   queries responses\n");
	for(dp = Dservers;dp != NULLDOM;dp = dp->next){
		tprintf("%-20s%8lu%8lu%10lu%10lu%10lu\n",
		 inet_ntoa(dp->address),
		 dp->srtt,dp->mdev,dp->timeout,
		 dp->queries,dp->responses);
	}
	return 0;
}

static int
dodnsretry(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setint( &Dserver_retries, "server retries", argc,argv );
}

static int
dodnstrace(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Dtrace,"server trace",argc,argv);
}


/**
 **	Domain Resource Record Utilities
 **/

/* check list of resource records for any expired ones.
 * returns number of expired records.
 */
static int
check_ttl(rrlp)
register struct rr *rrlp;
{
	int count = 0;

	while(rrlp != NULLRR){
		if(rrlp->ttl == 0L)
			count++;
		rrlp = rrlp->next;
	}
	return count;
}

/* Compare two resource records.
 * returns 0 if match, nonzero otherwise.
 */
static int
compare_rr(search_rrp,target_rrp)
register struct rr *search_rrp,*target_rrp;
{
	int i;

	if(search_rrp == NULLRR || target_rrp == NULLRR)
		return -32765;

	if(search_rrp->class != target_rrp->class)
		return -32763;
	if(search_rrp->type != target_rrp->type
	&& (search_rrp->source != RR_QUERY
	 || (target_rrp->type != TYPE_CNAME
	  && target_rrp->type != TYPE_PTR)))
		return -32761;

	if(search_rrp->source != RR_INQUERY){
		if((i = strlen(search_rrp->name)) != strlen(target_rrp->name))
			return -32759;
		if((i = strnicmp(search_rrp->name,target_rrp->name,i)) != 0)
			return i;

		/* match negative records so that they are replaced */
		if(target_rrp->rdlength == 0)
			return 0;
	}

	/* if a query has gotten this far, match it */
	if(search_rrp->source == RR_QUERY)
		return 0;

	/* ensure negative records don't replace older records */
	if(search_rrp->rdlength == 0)
		return -32757;

	/* match expired records so that they are replaced */
	if(search_rrp->source != RR_INQUERY){
		if(target_rrp->ttl == 0L)
			return 0;
	}

	/* Note: rdlengths are not compared because they vary depending
	 * on the representation (ASCII or encoded) this record was
	 * generated from.
	 */

	switch(search_rrp->type){
	case TYPE_A:
		i = search_rrp->rdata.addr != target_rrp->rdata.addr;
		break;
	case TYPE_CNAME:
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	case TYPE_NS:
	case TYPE_PTR:
	case TYPE_TXT:
		i = stricmp(search_rrp->rdata.data,target_rrp->rdata.data);
		break;
	case TYPE_HINFO:
		i = strcmp(search_rrp->rdata.hinfo.cpu,target_rrp->rdata.hinfo.cpu) ||
			strcmp(search_rrp->rdata.hinfo.os,target_rrp->rdata.hinfo.os);
		break;
	case TYPE_MX:
		i = stricmp(search_rrp->rdata.mx.exch,target_rrp->rdata.mx.exch);
		break;
	case TYPE_SOA:
		i = search_rrp->rdata.soa.serial != target_rrp->rdata.soa.serial;
		break;
	default:
		i = -32755;	/* unsupported */
	}
	return i;
}

static int
compare_rr_list(rrlp,target_rrp)
register struct rr *rrlp,*target_rrp;
{
	while(rrlp != NULLRR){
		if(compare_rr(rrlp,target_rrp) == 0)
			return 0;
#ifdef DEBUG_PAIN
		if(Dtrace)
			printf("%15d %s\n",
				compare_rr(rrlp,target_rrp),
				target_rrp->name);
#endif
		rrlp = rrlp->next;
	}
	return -32767;
}

/* Make a new copy of a resource record */
static struct rr *
copy_rr(rrp)
register struct rr *rrp;
{
	register struct rr *newrr;

	if(rrp == NULLRR)
		return NULLRR;

	newrr = (struct rr *)callocw(1,sizeof(struct rr));
	newrr->source =	rrp->source;
	newrr->name =	strdup(rrp->name);
	newrr->type =	rrp->type;
	newrr->class =	rrp->class;
	newrr->ttl =	rrp->ttl;
	if((newrr->rdlength = rrp->rdlength) == 0)
		return newrr;

	switch(rrp->type){
	case TYPE_A:
		newrr->rdata.addr = rrp->rdata.addr;
		break;
	case TYPE_CNAME:
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	case TYPE_NS:
	case TYPE_PTR:
	case TYPE_TXT:
		newrr->rdata.name = strdup(rrp->rdata.name);
		break;
	case TYPE_HINFO:
		newrr->rdata.hinfo.cpu = strdup(rrp->rdata.hinfo.cpu);
		newrr->rdata.hinfo.os = strdup(rrp->rdata.hinfo.os);
		break;
	case TYPE_MX:
		newrr->rdata.mx.pref = rrp->rdata.mx.pref;
		newrr->rdata.mx.exch = strdup(rrp->rdata.mx.exch);
		break;
	case TYPE_SOA:
		newrr->rdata.soa.mname = 	strdup(rrp->rdata.soa.mname);
		newrr->rdata.soa.rname = 	strdup(rrp->rdata.soa.rname);
		newrr->rdata.soa.serial = 	rrp->rdata.soa.serial;
		newrr->rdata.soa.refresh = 	rrp->rdata.soa.refresh;
		newrr->rdata.soa.retry = 	rrp->rdata.soa.retry;
		newrr->rdata.soa.expire = 	rrp->rdata.soa.expire;
		newrr->rdata.soa.minimum = 	rrp->rdata.soa.minimum;
		break;
	}
	return newrr;
}

static struct rr *
copy_rr_list(rrlp)
register struct rr *rrlp;
{
	register struct rr **rrpp;
	struct rr *result_rrlp;

	rrpp = &result_rrlp;
	while(rrlp != NULLRR){
		*rrpp = copy_rr(rrlp);
		rrpp = &(*rrpp)->next;
		rrlp = rrlp->next;
	}
	*rrpp = NULLRR;
	return result_rrlp;
}

/* Free (list of) resource records */
void
free_rr(rrlp)
register struct rr *rrlp;
{
	register struct rr *rrp;

	while((rrp = rrlp) != NULLRR){
		rrlp = rrlp->next;

		free(rrp->comment);
		free(rrp->name);
		if(rrp->rdlength > 0){
			switch(rrp->type){
			case TYPE_A:
				break;	/* Nothing allocated in rdata section */
			case TYPE_CNAME:
			case TYPE_MB:
			case TYPE_MG:
			case TYPE_MR:
			case TYPE_NS:
			case TYPE_PTR:
			case TYPE_TXT:
				free(rrp->rdata.name);
				break;
			case TYPE_HINFO:
				free(rrp->rdata.hinfo.cpu);
				free(rrp->rdata.hinfo.os);
				break;
			case TYPE_MX:
				free(rrp->rdata.mx.exch);
				break;
			case TYPE_SOA:
				free(rrp->rdata.soa.mname);
				free(rrp->rdata.soa.rname);
				break;
			}
		}
		free((char *)rrp);
	}
}

static struct rr *
make_rr(source,dname,dclass,dtype,ttl,rdl,data)
int source;
char *dname;
int16 dclass;
int16 dtype;
int32 ttl;
int16 rdl;
void *data;
{
	register struct rr *newrr;

	newrr = (struct rr *)callocw(1,sizeof(struct rr));
	newrr->source = source;
	newrr->name = strdup(dname);
	newrr->class = dclass;
	newrr->type = dtype;
	newrr->ttl = ttl;
	if((newrr->rdlength = rdl) == 0)
		return newrr;

	switch(dtype){
	case TYPE_A:
	  {
		register int32 *ap = (int32 *)data;
		newrr->rdata.addr = *ap;
		break;
	  }
	case TYPE_CNAME:
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	case TYPE_NS:
	case TYPE_PTR:
	case TYPE_TXT:
	  {
		newrr->rdata.name = strdup((char *)data);
		break;
	  }
	case TYPE_HINFO:
	  {
		register struct hinfo *hinfop = (struct hinfo *)data;
		newrr->rdata.hinfo.cpu = strdup(hinfop->cpu);
		newrr->rdata.hinfo.os = strdup(hinfop->os);
		break;
	  }
	case TYPE_MX:
	  {
		register struct mx *mxp = (struct mx *)data;
		newrr->rdata.mx.pref = mxp->pref;
		newrr->rdata.mx.exch = strdup(mxp->exch);
		break;
	  }
	case TYPE_SOA:
	  {
		register struct soa *soap = (struct soa *)data;
		newrr->rdata.soa.mname = 	strdup(soap->mname);
		newrr->rdata.soa.rname = 	strdup(soap->rname);
		newrr->rdata.soa.serial = 	soap->serial;
		newrr->rdata.soa.refresh = 	soap->refresh;
		newrr->rdata.soa.retry = 	soap->retry;
		newrr->rdata.soa.expire = 	soap->expire;
		newrr->rdata.soa.minimum = 	soap->minimum;
		break;
	  }
	}
	return newrr;
}


/**
 **	Domain Cache Utilities
 **/

static void
dcache_add(rrlp)
register struct rr *rrlp;
{
	register struct rr *last_rrp;
	struct rr *save_rrp;

	if(rrlp == NULLRR)
		return;

	save_rrp = rrlp;
	last_rrp = NULLRR;
	while(rrlp != NULLRR){
		rrlp->last = last_rrp;
		last_rrp = rrlp;
		rrlp = rrlp->next;
	}
	last_rrp->next = Dcache;
	if(Dcache != NULLRR)
		Dcache->last = last_rrp;
	Dcache = save_rrp;
}

static void
dcache_drop(rrp)
register struct rr *rrp;
{
	if(rrp->last != NULLRR)
		rrp->last->next = rrp->next;
	else
		Dcache = rrp->next;
	if(rrp->next != NULLRR)
		rrp->next->last = rrp->last;
	rrp->last =
	rrp->next = NULLRR;
}

/* Search cache for resource records, removing them from the cache.
 * Also, timeout cache entries, and trim cache to size.
 * (Calling with NULLRR is legal -- will timeout & trim only.)
 * Note that an answer from the cache cannot be authoritative, because
 * we cannot guarantee that all the entries remain from a previous request.
 * Returns RR list, or NULLRR if no record found.
 */
static struct rr *
dcache_search(rrlp)
struct rr *rrlp;
{
	register struct rr *rrp, *test_rrp;
	struct rr **rrpp, *result_rrlp;
	int32 elapsed;
	time_t now;
	int count = 0;

#ifdef DEBUG
	if(Dtrace && rrlp != NULLRR){
		printf("dcache_search: searching for %s\n",rrlp->name);
	}
#endif

	elapsed = (int32)(time(&now) - Dcache_time);
	Dcache_time = now;

	rrpp = &result_rrlp;
	for(rrp = Dcache; (test_rrp = rrp) != NULLRR;){
		rrp = rrp->next;
					/* timeout entries */
		if(test_rrp->ttl > 0L
		&& (test_rrp->ttl -= elapsed) <= 0L)
			test_rrp->ttl = 0L;

		if(compare_rr_list(rrlp,test_rrp) == 0){
			dcache_drop( *rrpp = test_rrp );
			rrpp = &(*rrpp)->next;
		} else if(test_rrp->source == RR_FILE && ++count > Dcache_size){
			dcache_drop(test_rrp);
			free_rr(test_rrp);
		}
	}
	*rrpp = NULLRR;
	return result_rrlp;
}

/* Move a list of resource records to the cache, removing duplicates. */
static void
dcache_update(rrlp)
register struct rr *rrlp;
{
	if(rrlp == NULLRR)
		return;

	free_rr(dcache_search(rrlp));	/* remove duplicates, first */
	dcache_add(rrlp);
}


/**
 **	File Utilities
 **/

static struct rr *
get_rr(fp,lastrrp)
FILE *fp;
struct rr *lastrrp;
{
	char *line,*lp,*strtok();
	struct rr *rrp;
	char *name,*ttl,*class,*type,*data;
	int i;

	line = mallocw(256);
	if(fgets(line,256,fp) == NULL){
		free(line);
		return NULLRR;
	}

	rrp = (struct rr *)callocw(1,sizeof(struct rr));
	rrp->source = RR_FILE;

	if(line[0] == '\0' || line[0] == '#' || line[0] == ';'){
		rrp->comment = line;
		return rrp;
	}

	if(!isspace(line[0]) || lastrrp == NULLRR){
		name = strtok(line,delim);
		lp = NULLCHAR;
	}
	else {	/* Name field is missing */
		name = lastrrp->name;
		lp = line;
	}
	if(name == NULLCHAR || (name != NULLCHAR && (i = strlen(name)) == 0)){
		rrp->comment = strdup("\n");
		free(line);
		return rrp;
	}

	if(name[i-1] != '.'){
		/* Tack on a trailing period if it's not there */
		/* !!! need to implement $ORIGIN suffix here */
		rrp->name = mallocw(i+2);
		strcpy(rrp->name,name);
		strcat(rrp->name,".");
	} else
		rrp->name = strdup(name);

	ttl = strtok(lp,delim);

	if(ttl == NULLCHAR || (!isdigit(ttl[0]) && ttl[0] != '-')){
		/* Optional ttl field is missing */
		rrp->ttl = TTL_MISSING;
		class = ttl;
	} else {
		rrp->ttl = atol(ttl);
		class = strtok(NULLCHAR,delim);
	}

	if(class == NULLCHAR){
		/* we're in trouble, but keep processing */
		rrp->class = CLASS_MISSING;
		type = class;
	} else if(class[0] == '<'){
		rrp->class = atoi(&class[1]);
		type = strtok(NULLCHAR,delim);
	} else if(stricmp(class,"IN") == 0){
		rrp->class = CLASS_IN;
		type = strtok(NULLCHAR,delim);
	} else {
		/* Optional class field is missing; assume IN */
		rrp->class = CLASS_IN;
		type = class;
	}

	if(type == NULLCHAR){
		/* we're in trouble, but keep processing */
		rrp->type = TYPE_MISSING;
		data = type;
	} else if(type[0] == '{'){
		rrp->type = atoi(&class[1]);
		data = strtok(NULLCHAR,delim);
	} else {
		rrp->type = TYPE_MISSING;
		for(i=1;i<Ndtypes;i++){
			if(stricmp(type,Dtypes[i]) == 0){
				rrp->type = i;
				data = strtok(NULLCHAR,delim);
				break;
			}
		}
	}

	if(rrp->type == TYPE_MISSING){
		data = NULLCHAR;
	}

	if(data == NULLCHAR){
		/* Empty record, just return */
		free(line);
		return rrp;
	}
	switch(rrp->type){
	case TYPE_A:
		rrp->rdlength = 4;
		rrp->rdata.addr = aton(data);
		break;
	case TYPE_CNAME:
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	case TYPE_NS:
	case TYPE_PTR:
	case TYPE_TXT:
		rrp->rdlength = strlen(data);
		rrp->rdata.name = strdup(data);
		break;
	case TYPE_HINFO:
		rrp->rdlength = strlen(data);
		rrp->rdata.hinfo.cpu = strdup(data);
		if((data = strtok(NULLCHAR,delim)) != NULLCHAR){
			rrp->rdlength += strlen(data);
			rrp->rdata.hinfo.os = strdup(data);
		}
		break;
	case TYPE_MX:
		rrp->rdata.mx.pref = atoi(data);
		rrp->rdlength = 2;

		/* Get domain name of exchanger */
		if((data = strtok(NULLCHAR,delim)) != NULLCHAR){
			rrp->rdlength += strlen(data);
			rrp->rdata.mx.exch = strdup(data);
		}
		break;
	case TYPE_SOA:
		/* Get domain name of master name server */
		rrp->rdlength = strlen(data);
		rrp->rdata.soa.mname = strdup(data);

		/* Get domain name of irresponsible person */
		if((data = strtok(NULLCHAR,delim)) != NULLCHAR){
			rrp->rdata.soa.rname = strdup(data);
			rrp->rdlength += strlen(data);
		}
		data = strtok(NULLCHAR,delim);
		rrp->rdata.soa.serial = atol(data);
		data = strtok(NULLCHAR,delim);
		rrp->rdata.soa.refresh = atol(data);
		data = strtok(NULLCHAR,delim);
		rrp->rdata.soa.retry = atol(data);
		data = strtok(NULLCHAR,delim);
		rrp->rdata.soa.expire = atol(data);
		data = strtok(NULLCHAR,delim);
		rrp->rdata.soa.minimum = atol(data);
		rrp->rdlength += 20;
		break;
	}

	/* !!! need to handle trailing comments */
	free(line);
	return rrp;
}

/* Print a resource record */
static void
put_rr(fp,rrp)
FILE *fp;
struct rr *rrp;
{
	if(fp == NULLFILE || rrp == NULLRR)
		return;

	if(rrp->name == NULLCHAR && rrp->comment != NULLCHAR){
		fprintf(fp,"%s",rrp->comment);
		return;
	}

	fprintf(fp,"%s",rrp->name);
	if(rrp->ttl != TTL_MISSING)
		fprintf(fp,"\t%ld",rrp->ttl);
	if(rrp->class == CLASS_IN)
		fprintf(fp,"\tIN");
	else
		fprintf(fp,"\t<%u>",rrp->class);
	if(rrp->type < Ndtypes)
		fprintf(fp,"\t%s",Dtypes[rrp->type]);
	else
		fprintf(fp,"\t{%u}",rrp->type);
	if(rrp->rdlength == 0){
		/* Null data portion, indicates nonexistent record */
		/* or unsupported type.  Hopefully, these will filter */
		/* as time goes by. */
		fprintf(fp,"\n");
		return;
	}
	switch(rrp->type){
	case TYPE_A:
		fprintf(fp,"\t%s\n",inet_ntoa(rrp->rdata.addr));
		break;
	case TYPE_CNAME:
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	case TYPE_NS:
	case TYPE_PTR:
	case TYPE_TXT:
		/* These are all printable text strings */
		fprintf(fp,"\t%s\n",rrp->rdata.data);
		break;
	case TYPE_HINFO:
		fprintf(fp,"\t%s\t%s\n",
		 rrp->rdata.hinfo.cpu,
		 rrp->rdata.hinfo.os);
		break;
	case TYPE_MX:
		fprintf(fp,"\t%u\t%s\n",
		 rrp->rdata.mx.pref,
		 rrp->rdata.mx.exch);
		break;
	case TYPE_SOA:
		fprintf(fp,"\t%s\t%s\t%lu\t%lu\t%lu\t%lu\t%lu\n",
		 rrp->rdata.soa.mname,rrp->rdata.soa.rname,
		 rrp->rdata.soa.serial,rrp->rdata.soa.refresh,
		 rrp->rdata.soa.retry,rrp->rdata.soa.expire,
		 rrp->rdata.soa.minimum);
		break;
	default:
		fprintf(fp,"\n");
		break;
	}
}

/* Search local database for resource records.
 * Returns RR list, or NULLRR if no record found.
 */
static struct rr *
dfile_search(rrlp)
struct rr *rrlp;
{
	register struct rr *frrp;
	struct rr **rrpp, *result_rrlp, *oldrrp;
	int32 elapsed;
	FILE *dbase;
	struct stat dstat;

#ifdef DEBUG
	if(Dtrace){
		printf("dfile_search: searching for %s\n",rrlp->name);
	}
#endif

	while(Dfile_writing > 0)
		pwait(&Dfile_reading);
	Dfile_reading++;

	if((dbase = fopen(Dfile,READ_TEXT)) == NULLFILE){
		Dfile_reading--;
		return NULLRR;
	}
	if(fstat(fileno(dbase),&dstat) != 0){
		tprintf("dfile_search: can't get file status\n");
		fclose(dbase);
		Dfile_reading--;
		return NULLRR;
	}
	if((elapsed = (int32)(Dcache_time - (time_t)dstat.st_ctime)) < 0L)
		elapsed = -elapsed;	/* arbitrary time mismatch */

	result_rrlp = NULLRR;		/* for contiguous test below */
	oldrrp = NULLRR;
	rrpp = &result_rrlp;
	while((frrp = get_rr(dbase,oldrrp)) != NULLRR){
		free_rr(oldrrp);
		if(frrp->type != TYPE_MISSING
		&& frrp->rdlength > 0
		&& compare_rr_list(rrlp,frrp) == 0){
			if(frrp->ttl > 0L
			&& (frrp->ttl -= elapsed) <= 0L)
				frrp->ttl = 0L;
			*rrpp = frrp;
			rrpp = &(*rrpp)->next;
			oldrrp = copy_rr(frrp);
		} else {
			oldrrp = frrp;
			/*
				All records of the same name and the same type
				are contiguous.  Therefore, for a single query,
				we can stop searching.  Multiple queries must
				read the whole file.
			*/
			if(rrlp->next == NULLRR && result_rrlp != NULLRR)
				break;
		}
		if(!main_exit)
			pwait(NULL);	/* run multiple sessions */
	}
	free_rr(oldrrp);
	*rrpp = NULLRR;

	fclose(dbase);

	if(--Dfile_reading <= 0){
		Dfile_reading = 0;
		psignal(&Dfile_writing,0);
	}

	return result_rrlp;
}

/* Process which will add new resource records from the cache
 * to the local file, eliminating duplicates while it goes.
 */
static void
dfile_update(s,unused,p)
int s;
void *unused;
void *p;
{
	struct rr **rrpp, *rrlp, *oldrrp;
	char *newname;
	FILE *old_fp, *new_fp;
	struct stat old_stat, new_stat;

	log(-1,"update Domain.txt initiated");

	close_s(Curproc->input);
	Curproc->input = -1;
	close_s(Curproc->output);
	Curproc->output = -1;

	newname = strdup(Dfile);
	strcpy(&newname[strlen(newname)-3],"tmp");

	while(Dfile_wait_absolute != 0L && !main_exit){
		register struct rr *frrp;
		int32 elapsed;

		while(Dfile_wait_absolute != 0L){
			elapsed = Dfile_wait_absolute - secclock();
			Dfile_wait_absolute = 0L;
			if(elapsed > 0L && !main_exit){
				alarm(elapsed*1000);
				pwait(&Dfile_wait_absolute);
				alarm(0L);
			}
		}

		log(-1,"update Domain.txt");

		/* create new file for copy */
		if((new_fp = fopen(newname,WRITE_TEXT)) == NULLFILE){
			printf("dfile_update: can't create %s!\n",newname);
			break;
		}
		if(fstat(fileno(new_fp),&new_stat) != 0){
			printf("dfile_update: can't get new_file status!\n");
			fclose(new_fp);
			break;
		}

		pwait(NULL);	/* file operations can be slow */

		/* timeout the cache one last time before writing */
		(void)dcache_search(NULLRR);

		/* copy new RRs out to the new file */
		/* (can't wait here, the cache might change) */
		rrpp = &rrlp;
		for(frrp = Dcache; frrp != NULLRR; frrp = frrp->next ){
			switch(frrp->source){
			case RR_QUESTION:
			case RR_ANSWER:
			case RR_AUTHORITY:
			case RR_ADDITIONAL:
				*rrpp = copy_rr(frrp);
				if(frrp->type != TYPE_MISSING
				&& frrp->rdlength > 0)
					put_rr(new_fp,frrp);
				rrpp = &(*rrpp)->next;
				frrp->source = RR_FILE;
				break;
			}
		}
		*rrpp = NULLRR;

		/* open up the old file, concurrently with everyone else */
		if((old_fp = fopen(Dfile,READ_TEXT)) == NULLFILE){
			/* great! no old file, so we're ready to go. */
			fclose(new_fp);
			rename(newname,Dfile);
			free_rr(rrlp);
			break;
		}
		if(fstat(fileno(old_fp),&old_stat) != 0){
			printf("dfile_update: can't get old_file status!\n");
			fclose(new_fp);
			fclose(old_fp);
			free_rr(rrlp);
			break;
		}
		if((elapsed = (int32)(new_stat.st_ctime - old_stat.st_ctime)) < 0L)
			elapsed = -elapsed;	/* file times are inconsistant */

		/* Now append any non-duplicate records */
		oldrrp = NULLRR;
		while((frrp = get_rr(old_fp,oldrrp)) != NULLRR){
			free_rr(oldrrp);
			if(frrp->name == NULLCHAR
			&& frrp->comment != NULLCHAR)
				put_rr(new_fp,frrp);
			if(frrp->type != TYPE_MISSING
			&& frrp->rdlength > 0
			&& compare_rr_list(rrlp,frrp) != 0){
				if(frrp->ttl > 0L
				&& (frrp->ttl -= elapsed) <= 0L)
					frrp->ttl = 0L;
				if(frrp->ttl != 0 || !Dfile_clean)
					put_rr(new_fp,frrp);
			}
			oldrrp = frrp;
			if(!main_exit)
				pwait(NULL);	/* run in background */
		}
		free_rr(oldrrp);
		fclose(new_fp);
		fclose(old_fp);
		free_rr(rrlp);

		/* wait for everyone else to finish reading */
		Dfile_writing++;
		while(Dfile_reading > 0)
			pwait(&Dfile_writing);

		unlink(Dfile);
		rename(newname,Dfile);

		Dfile_writing = 0;
		psignal(&Dfile_reading,0);
	}
	free(newname);

	log(-1,"update Domain.txt finished");
	Dfile_updater = NULLPROC;
}


/**
 **	Domain Server Utilities
 **/

static void
dumpdomain(dhp,rtt)
struct dhdr *dhp;
int32 rtt;
{
	struct rr *rrp;

	printf("response id %u (rtt %lu sec) qr %u opcode %u aa %u tc %u rd %u ra %u rcode %u\n",
	 dhp->id,(long)rtt /1000L,
	 dhp->qr,dhp->opcode,dhp->aa,dhp->tc,dhp->rd,
	 dhp->ra,dhp->rcode);
	printf("%u questions:\n",dhp->qdcount);
	for(rrp = dhp->questions; rrp != NULLRR; rrp = rrp->next){
		printf("%s type %s class %u\n",rrp->name,
		 Dtypes[rrp->type],rrp->class);
	}
	printf("%u answers:\n",dhp->ancount);
	for(rrp = dhp->answers; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	printf("%u authority:\n",dhp->nscount);
	for(rrp = dhp->authority; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	printf("%u additional:\n",dhp->arcount);
	for(rrp = dhp->additional; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	fflush(stdout);
}

static int
dns_makequery(op,srrp,buffer,buflen)
int16 op;	/* operation */
struct rr *srrp;/* Search RR */
char *buffer;	/* Area for query */
int16 buflen;	/* Length of same */
{
	char *cp,*cp1;
	char *dname, *sname;
	int16 parameter;
	int16 dlen,len;

	cp = buffer;
	/* Use millisecond clock for timestamping */
	cp = put16(cp,(int16)msclock());
	parameter = (op << 11)
			| 0x0100;	/* Recursion desired */
	cp = put16(cp,parameter);
	cp = put16(cp,1);
	cp = put16(cp,0);
	cp = put16(cp,0);
	cp = put16(cp,0);

	sname = strdup(srrp->name);
	dname = sname;
	dlen = strlen(dname);
	for(;;){
		/* Look for next dot */
		cp1 = strchr(dname,'.');
		if(cp1 != NULLCHAR)
			len = cp1-dname;	/* More to come */
		else
			len = dlen;	/* Last component */
		*cp++ = len;		/* Write length of component */
		if(len == 0)
			break;
		/* Copy component up to (but not including) dot */
		strncpy(cp,dname,len);
		cp += len;
		if(cp1 == NULLCHAR){
			*cp++ = 0;	/* Last one; write null and finish */
			break;
		}
		dname += len+1;
		dlen -= len+1;
	}
	free(sname);
	cp = put16(cp,srrp->type);
	cp = put16(cp,srrp->class);
	return cp - buffer;
}

/* domain server resolution loop
 * returns: any answers in cache.
 *	(future features)
 *	multiple queries.
 *	inverse queries.
 */
static void
dns_query(rrlp)
struct rr *rrlp;
{
	struct mbuf *bp;
	struct dhdr *dhp;
	struct dserver *dp;	/* server list */
	int32 rtt,abserr;
	int tried = 0;		/* server list has been retried (count) */

	if((dp = Dservers) == NULLDOM){
		return;
	}

	for(;;){
		char *buf;
		int len;
		struct sockaddr_in server_in;
		int s;
		int rval;

		dp->queries++;

		s = socket(AF_INET,SOCK_DGRAM,0);
		server_in.sin_family = AF_INET;
		server_in.sin_port = IPPORT_DOMAIN;
		server_in.sin_addr.s_addr = dp->address;

		if(Dtrace){
			printf("dns_query: querying server %s for %s\n",
			 inet_ntoa(dp->address),rrlp->name);
		}

		buf = mallocw(512);
		len = dns_makequery(0,rrlp,buf,512);
		sendto(s,buf,len,0,(char *)&server_in,sizeof(server_in));
		free(buf);
		alarm(max(dp->timeout,100));
		/* Wait for something to happen */
		rval = recv_mbuf(s,&bp,0,NULLCHAR,0);
		alarm(0L);
		close_s(s);

		if(Dtrace){
			printf("dns_query: received message length %d, errno %d\n", rval, errno );
		}

		if(rval > 0)
			break;

		if(errno == EABORT){
			return;		/* Killed by "reset" command */
		}

		/* Timeout; back off this one and try another server */
		dp->timeout <<= 1;
		if((dp = dp->next) == NULLDOM){
			dp = Dservers;
			if(Dserver_retries > 0 && ++tried > Dserver_retries)
				return;
		}
	}

	/* got a response */
	dp->responses++;
	dhp = (struct dhdr *) mallocw(sizeof(struct dhdr));
	ntohdomain(dhp,&bp);	/* Convert to local format */

	/* Compute and update the round trip time */
	rtt = (int32) ((int16)msclock() - dhp->id);
	abserr = rtt > dp->srtt ? rtt - dp->srtt : dp->srtt - rtt;
	dp->srtt = ((AGAIN-1) * dp->srtt + rtt + (AGAIN/2)) >> LAGAIN;
	dp->mdev = ((DGAIN-1) * dp->mdev + abserr + (DGAIN/2)) >> LDGAIN;
	dp->timeout = 4 * dp->mdev + dp->srtt;

	/* move to top of list for next time */
	if(dp->prev != NULLDOM){
		dlist_drop(dp);
		dlist_add(dp);
	}

	if(Dtrace)
		dumpdomain(dhp,rtt);

	/* Add negative reply to answers.  This assumes that there was
	 * only one question, which is true for all questions we send.
	 */
	if(dhp->aa && (dhp->rcode == NAME_ERROR || dhp->ancount == 0)){
		register struct rr *rrp;
		long ttl = 600L; /* Default TTL for negative records */

		/* look for SOA ttl */
		for(rrp = dhp->authority; rrp != NULLRR; rrp = rrp->next){
			if(rrp->type == TYPE_SOA)
				ttl = rrp->ttl;
		}

		/* make the questions the negative answers */
		for(rrp = dhp->questions; rrp != NULLRR; rrp = rrp->next)
			rrp->ttl = ttl;
	} else {
		free_rr(dhp->questions);
		dhp->questions = NULLRR;
	}

	/* post in reverse order to maintain original order */
	dcache_update(dhp->additional);
	dcache_update(dhp->authority);
	dcache_update(dhp->answers);
	dcache_update(dhp->questions);

	Dfile_wait_absolute = secclock() + Dfile_wait_relative;
	if(Dfile_updater == NULLPROC){
		Dfile_updater = newproc("domain update",
			512,dfile_update,0,NULL,NULL,0);
	}

#ifdef DEBUG
	if(Dtrace)
		keywait(NULLCHAR,1);	/* so we can look around */
#endif
	free((char *)dhp);
	return;
}


/**
 **	Resolver Utilities
 **/

/* Return TRUE if string appears to be an IP address in dotted decimal;
 * return FALSE otherwise (i.e., if string is a domain name)
 */
static int
isaddr(s)
register char *s;
{
	char c;

	if(s == NULLCHAR)
		return TRUE;	   /* Can't happen */

	while((c = *s++) != '\0'){
		if(c != '[' && c != ']' && !isdigit(c) && c != '.')
			return FALSE;
	}
	return TRUE;
}

/* Search for resource records.
 * Returns RR list, or NULLRR if no record found.
 */
static struct rr *
resolver(rrlp)
register struct rr *rrlp;
{
	register struct rr *result_rrlp;

	if((result_rrlp = dcache_search(rrlp)) == NULLRR){
		result_rrlp = dfile_search(rrlp);
	}
	if(result_rrlp == NULLRR || check_ttl(result_rrlp) != 0){
		dcache_add(result_rrlp); 	/* save any expired RRs */
		dns_query(rrlp);
		result_rrlp = dcache_search(rrlp);
	}
	dcache_add(copy_rr_list(result_rrlp));
	return result_rrlp;
}

/* general entry point for address -> domain name resolution.
 * Returns RR list, or NULLRR if no record found.
 */
struct rr *
inverse_a(ip_address)
int32 ip_address;
{
	struct rr *prrp;
	struct rr *result_rrlp;
	char pname[30];

	if(ip_address == 0L)
		return NULLRR;

	sprintf( pname, "%u.%u.%u.%u.IN-ADDR.ARPA.",
			lobyte(loword(ip_address)),
			hibyte(loword(ip_address)),
			lobyte(hiword(ip_address)),
			hibyte(hiword(ip_address)) );

	prrp = make_rr(RR_QUERY,pname,CLASS_IN,TYPE_PTR,0,0,NULL);

	prrp->next = 		/* make list to speed search */
		make_rr(RR_INQUERY,NULLCHAR,CLASS_IN,TYPE_A,0,4,&ip_address);

	result_rrlp = resolver(prrp);

	free_rr(prrp);
	return result_rrlp;
}

/* general entry point for domain name -> resource resolution.
 * Returns RR list, or NULLRR if no record found.
 */
struct rr *
resolve_rr(dname,dtype)
char *dname;
int16 dtype;
{
	struct rr *qrrp;
	struct rr *result_rrlp;
	char *sname, *tname;
	int looping = MAXCNAME;

	if(dname == NULLCHAR)
		return NULLRR;

	sname = strdup(dname);
	if(strchr(sname,'.') == NULLCHAR && Dsuffix != NULLCHAR){
		/* Append default suffix */
		tname = mallocw(strlen(sname)+strlen(Dsuffix)+2);
		sprintf(tname,"%s.%s",sname,Dsuffix);
		free(sname);
		sname = tname;
	}
	if(sname[strlen(sname)-1] != '.'){
		/* Append trailing dot */
		tname = mallocw(strlen(sname)+2);
		sprintf(tname,"%s.",sname);
		free(sname);
		sname = tname;
	}

	qrrp = make_rr(RR_QUERY,sname,CLASS_IN,dtype,0,0,NULL);
	free(sname);

	while(looping > 0){
		if((result_rrlp=resolver(qrrp)) == NULLRR
		|| result_rrlp->type == dtype)
			break;
#ifdef DEBUG
		if(Dtrace)
			put_rr(stdout,result_rrlp);
#endif
		/* Should be CNAME or PTR record */
		/* Replace name and try again */
		free(qrrp->name);
		qrrp->name = strdup(result_rrlp->rdata.name);
		free_rr(result_rrlp);
		result_rrlp = NULLRR;
		looping--;
	}
	free_rr(qrrp);
	return result_rrlp;
}

/* main entry point for address -> domain name resolution.
 * Returns string, or NULLCHAR if no name found.
 */
char *
resolve_a(ip_address,shorten)
int32 ip_address;		/* search address */
int shorten;			/* return only first part of name (flag)*/
{
	struct rr *save_rrlp, *rrlp;
	char *result = NULLCHAR;

	for( rrlp = save_rrlp = inverse_a(ip_address);
	     rrlp != NULLRR && result == NULLCHAR;
	     rrlp = rrlp->next ){
		if(rrlp->rdlength > 0){
			switch(rrlp->type){
			case TYPE_PTR:
				result = strdup(rrlp->rdata.name);
				break;
			case TYPE_A:
				result = strdup(rrlp->name);
				break;
			}
		}
	}
	free_rr(save_rrlp);

	if(result != NULLCHAR && shorten){
		int dot;
		char *shortened;

		if((dot = strcspn(result, ".")) == 0){
			shortened = mallocw(dot+1);
			strncpy(shortened, result, dot);
			shortened[dot] = '\0';
			free(result);
			result = shortened;
		}
	}
	return result;
}

/* Main entry point for domain name -> address resolution.
 * Returns 0 if name is currently unresolvable.
 */
int32
resolve(name)
char *name;
{
	register struct rr *rrlp;
	int32 ip_address = 0;

	if(name == NULLCHAR)
		return 0;

	if(isaddr(name))
		return aton(name);

	if((rrlp = resolve_rr(name,TYPE_A)) != NULLRR
	 && rrlp->rdlength > 0)
		ip_address = rrlp->rdata.addr;

	/* multi-homed hosts are handled here */
	if(rrlp != NULLRR && rrlp->next != NULLRR) {
		register struct rr *rrp;
		register struct route *rp;
		int16 cost = MAXINT16;
		rrp = rrlp;
		while(rrp != NULLRR) { /* choose the best of a set of routes */
			if(rrp->rdlength > 0 &&
			   (rp = rt_lookup(rrp->rdata.addr)) != NULLROUTE &&
			   rp->metric <= cost) {
				ip_address = rrp->rdata.addr;
				cost = rp->metric;
			}
			rrp = rrp->next;
		}
	}

	free_rr(rrlp);
	return ip_address;
}


/* Main entry point for MX record lookup.
 * Returns 0 if name is currently unresolvable.
 */
int32
resolve_mx(name)
char *name;
{
	register struct rr *rrp, *arrp;
	char *sname, *tmp, *cp;
	int32 addr, ip_address = 0;
	int16 pref = MAXINT16;

	if(name == NULLCHAR)
		return 0;

	if(isaddr(name)){
		if((sname = resolve_a(aton(name),FALSE)) == NULLCHAR)
			return 0;
	}
	else
		sname = strdup(name);

	cp = sname;
	while(1){
		rrp = arrp = resolve_rr(sname,TYPE_MX);
		/* Search this list of rr's for an MX record */
		while(rrp != NULLRR){
			if(rrp->rdlength > 0 && rrp->rdata.mx.pref <= pref &&
			   (addr = resolve(rrp->rdata.mx.exch)) != 0L){
				pref = rrp->rdata.mx.pref;
				ip_address = addr;
			}
			rrp = rrp->next;
		}
		free_rr(arrp);
		if(ip_address != 0)
			break;
		/* Compose wild card one level up */
		if((cp = strchr(cp,'.')) == NULLCHAR)
			break;
		tmp = mallocw(strlen(cp)+2);
		sprintf(tmp,"*%s",cp);		/* wildcard expansion */
		free(sname);
		sname = tmp;
		cp = sname + 2;
	}
	free(sname);
	return ip_address;
}

/* Search for local records of the MB, MG and MR type. Returns list of
 * matching records.
 */
struct rr *
resolve_mailb(name)
char *name;		/* local username, without trailing dot */
{
	register struct rr *result_rrlp;
	struct rr *rrlp;
	char *sname;

	/* Append trailing dot */
	sname = mallocw(strlen(name)+2);
	sprintf(sname,"%s.",name);
	rrlp = make_rr(RR_QUERY,sname,CLASS_IN,TYPE_MB,0,0,NULL);
	rrlp->next = make_rr(RR_QUERY,sname,CLASS_IN,TYPE_MG,0,0,NULL);
	rrlp->next->next = make_rr(RR_QUERY,sname,CLASS_IN,TYPE_MR,0,0,NULL);
	free(sname);
	if((result_rrlp = dcache_search(rrlp)) == NULLRR){
		result_rrlp = dfile_search(rrlp);
	}
	free_rr(rrlp);
	if(Dsuffix != NULLCHAR){
		rrlp = result_rrlp;
		while(rrlp != NULLRR){	/* add domain suffix to data */
			if(rrlp->rdlength > 0 &&
			   rrlp->rdata.name[rrlp->rdlength-1] != '.'){
				sname = mallocw(rrlp->rdlength +
					strlen(Dsuffix)+2);
				sprintf(sname,"%s.%s",rrlp->rdata.name,Dsuffix);
				free(rrlp->rdata.name);
				rrlp->rdata.name = sname;
				rrlp->rdlength = strlen(sname);
			}
			rrlp = rrlp->next;
		}
	}
	dcache_add(copy_rr_list(result_rrlp));
	return result_rrlp;
}
