 /*
 *      DOMAIN.C -- domain name system stub resolver
 *
 *      Original code by Phil Karn, KA9Q.
 *
 *      Apr 90  Bill Simpson added address->name resolution, time-to-live,
 *      thru    memory caching, generalized multi-record multi-type searches,
 *      Oct 90  and many minor changes to conform more closely to the RFCs.
 *      Feb 91  Bill Simpson added "query" command and TYPE_ANY processing.
 *      Jul 91  Bill Simpson added "more" sessions for query and cache list.
 *
 *      ATARI Version by David Nash - dnash@chaos.demon.co.uk
 */

/****************************************************************************
*       $Id: domain.c 1.5 94/01/04 14:09:08 ROOT_DOS Exp $

*  07 Jun 92	1.2	GT Reset DNS timeout on DNS failure.
*    	Oct 92     -   mt@kram.demon.co.uk (G7LEU) merged NOS WG7J104 DNS code.
*    	Feb 93     -   lonewolf@sphere.demon.co.uk fixed DNS reply to TYPE_ANY
*							query.  Added domain qtype <n> to set the query type used
*                		by domain query. Useful if your server doesn't understand
*                 	TYPE_ANY changed all remaining printf to tprintf.
*	17 May 93	1.4	GT	Fix warnings.	
*	24 Dec 93	1.5	GT	Fix conditionals.
****************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <time.h>
#ifdef ATARI
#include <sys/types.h>
#endif
#include <sys/stat.h>
#ifndef ATARI
#include <dir.h>
#else
#include <sys/dir.h>
#endif
#include <dos.h>
#include <string.h>
#include <stdarg.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "proc.h"
#include "ip.h"
#include "netuser.h"
#include "session.h"
#include "socket.h"
#include "cmdparse.h"
#include "commands.h"
#include "files.h"
#include "main.h"
#include "domain.h"

#undef  DEBUG                           /* for certain trace messages */
#undef  DEBUG_PAIN                      /* for painful debugging */

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 int32 Dserver_maxwait = 0L;      /* maximum server timeout limit (seconds) */

static char *Dsuffix = NULLCHAR;        /* Default suffix for names without periods */
static int Dtrace = FALSE;
static int qtype = 255;

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";

#ifdef DSERVER
static struct DOM_FILE {
	struct DOM_FILE *next;
	char type;
	char *filename;
	char *domain;
	int32 source;
	struct soa dom_soa;
	} *dom_file = NULL;
struct proc *Drx;
int Dsocket;
extern char *Dzones;
extern char *Dboot;
#endif

extern int are_we_an_mx;

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 __stdargs 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 dodnsmaxw __ARGS((int argc,char *argv[],void *p));
static int __stdargs dodnsquery __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 __stdargs doqtype __ARGS((int argc,char *argv[],void *p));

static char * dtype __ARGS((int value));
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, char *filename));
static void __stdargs 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 char *checksuffix __ARGS((char *dname));
static struct rr *resolver __ARGS((struct rr *rrlp));

#ifdef DSERVER
static void free_dhdr __ARGS((struct dhdr *));
static void __stdargs proc_query __ARGS((int,void *,void *));
static void __stdargs drx __ARGS((int,void *,void *));
static int dodnsserver __ARGS((int argc,char *argv[],void *p));
static int dostopdnsserver __ARGS((int argc,char *argv[],void *p));
#endif

/**
 **     Domain Resolver Commands
 **/

static struct cmds Dcmds[] = {
	{ "addserver",    dodnsadd,       0, 2, "add <hostid>" },
	{ "dropserver",   dodnsdrop,      0, 2, "drop <hostid>" },
	{ "list",         dodnslist,      0, 0, NULLCHAR },
	{ "maxwait",      dodnsmaxw,      0, 0, NULLCHAR },
	{ "query",        dodnsquery,   512, 2, "query <hostid>" },
	{ "qtype",        doqtype,        0, 0, NULLCHAR },
	{ "retry",        dodnsretry,     0, 0, NULLCHAR },
	{ "suffix",       dosuffix,       0, 0, NULLCHAR },
#ifdef DSERVER
    { "startdns", dodnsserver,0, 0, NULLCHAR },
    { "stopdns", dostopdnsserver,0, 0, NULLCHAR },
#endif
	{ "trace",        dodnstrace,     0, 0, NULLCHAR },
	{ "cache",        docache,        0, 0, NULLCHAR },
	{ NULLCHAR },
};

static struct cmds Dcachecmds[] = {
	{ "clean",        docacheclean,   0, 0, NULLCHAR },
	{ "list",         docachelist,  512, 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
dodnsmaxw(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setlong(&Dserver_maxwait,"Server response timeout limit (sec)",argc,argv);
}

static int
docacheclean(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool( &Dfile_clean, "discard expired records", argc,argv );
}

static int __stdargs
docachelist(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct rr *rrp;
	struct session *sp;
	int row = MOREROWS;

	if((sp = newsession(argv[1],DCLIST)) == NULLSESSION){
		return -1;
	}

	(void)dcache_search(NULLRR); /* update ttl */

	/* Put tty into raw mode so single-char responses will work */
	sp->ttystate.echo = sp->ttystate.edit = 0;

	for(rrp=Dcache;rrp!=NULLRR;rrp=rrp->next)
	{
		put_rr(stdout,rrp);
		if(--row == 0){
			row = keywait("--More--",0);
			switch(row){
			case -1:
			case 'q':
			case 'Q':
				rrp = NULLRR;
				break;
			case '\n':
			case '\r':
				row = 1;
				break;
			case ' ':
			default:
				row = MOREROWS;
			};
		}
	}
	fflush(stdout);
	keywait(NULLCHAR,1);
	freesession(sp);
	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;
{
	int32 address;

	if((address = resolve(argv[1])) == 0L){
		tprintf("Resolver %s unknown\n",argv[1]);
		return 1;
	}
	return add_nameserver(address);
}
int
add_nameserver(address)
int32 address;
{
	struct dserver *dp;

	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;
{
#ifdef DSERVER
	struct DOM_FILE *t_dom_file;
#endif
	register struct dserver *dp;

	tprintf("Server address          srtt    mdev    timeout  queries responses timeouts\n");
	for(dp = Dservers;dp != NULLDOM;dp = dp->next){
		tprintf("%-20s%8lu%8lu%10lu%10lu%10lu%10lu\n",
		 inet_ntoa(dp->address),
		 dp->srtt,dp->mdev,dp->timeout,
		 dp->queries,dp->responses,dp->timeouts);
	}
#ifdef DSERVER
	if (Drx == NULLPROC)
		tprintf("Domain Name Server not running.\n");
	else tprintf("Domain Name Server running.\n");
	t_dom_file = dom_file;
	while(Drx != NULLPROC && t_dom_file)
		{
		if (t_dom_file->type == '1')
			tprintf("We are primary for %s\n", t_dom_file->domain);
		else if (t_dom_file->type == '2')
			tprintf("We are secondary for %s (from %s)\n",
							t_dom_file->domain, inet_ntoa(t_dom_file->source));
		t_dom_file = t_dom_file->next;
		}
#endif
	return 0;
}

static int __stdargs
doqtype(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setint(&qtype,"Query type",argc,argv);
}

static int __stdargs
dodnsquery(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct rr *rrp;
	struct rr *result_rrlp;
	char *sname;
	struct session *sp;
	int row = MOREROWS;

	if((sp = newsession(argv[1],DQUERY)) == NULLSESSION){
		return -1;
	}

	if ( isaddr( argv[1] ) ) {
		result_rrlp = inverse_a( aton( argv[1] ) );
	} else {
		sname = checksuffix( argv[1] );
		rrp = make_rr(RR_QUERY,sname,CLASS_IN,qtype,0,0,NULL);
		free(sname);

		dns_query(rrp);
		result_rrlp = dcache_search(rrp);
		free_rr(rrp);
	}

	/* Put tty into raw mode so single-char responses will work */
	sp->ttystate.echo = sp->ttystate.edit = 0;

	for( rrp=result_rrlp; rrp!=NULLRR; rrp=rrp->next)
	{
		put_rr(stdout,rrp);
		if(--row == 0){
			row = keywait("--More--",0);
			switch(row){
			case -1:
			case 'q':
			case 'Q':
				rrp = NULLRR;
				break;
			case '\n':
			case '\r':
				row = 1;
				break;
			case ' ':
			default:
				row = MOREROWS;
			};
		}
	}
	fflush(stdout);
	free_rr(result_rrlp);
	keywait(NULLCHAR,1);
	freesession(sp);
	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
 **/

static char *
dtype(value)
int value;
{
	static char buf[10];

	if (value < Ndtypes)
		return Dtypes[value];

	sprintf( buf, "{%d}", value);
	return buf;
}

/* 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 != TYPE_ANY
	&& 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;
*/
		i = strlen(target_rrp->name);
		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)
			tprintf("%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;

#ifdef ATARI
#pragma ignore 94     /* ignore uninitialised auto warninge for rrlp */
#endif

	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(
	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:
	case TYPE_AXFR: /*##*/
	  {
		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){
		tprintf("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;
{
#ifndef ATARI
	char *line,*lp,*strtok();
#else
	char *line, *lp;
#endif
	struct rr *rrp;
	char *name,*ttl,*class,*type,*data;
	int i;

	line = mallocw(500);
	if(fgets(line,500,fp) == NULL){
		free(line);
		return NULLRR;
		}

	if (strchr(line, '('))
		{
		int done;
		char *big_line, *p;
		p = line;
		while (*p && *p != '(')
			p++;
		*p = '\0';
		big_line = mallocw(500);
		if (big_line == NULL)
			{
			free(line);
			return NULLRR;
			}
		strcpy(big_line, line);
		done = FALSE;
		while (fgets(line, 500, fp) && !done)
			{
			if (strchr(line, ')'))
				done = TRUE;
			p = line;
			while (*p && *p != ')' && *p != ';')
				p++;
			*p = '\0';
			p = line;
			while (*p && (*p == ' ' || *p == '\t'))
				p++;
			strcat(big_line, " ");
			strcat(big_line, p);
			}
		strcpy(line, big_line);
		free(big_line);
		}

	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 || (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;
}

static void sofprintf(FILE *fp, char * fmt, ...)
{ 
 /*
    This routine behaves like the standard fprintf() except
    for when the destination is stdout. In this case write
    to the current process' output socket.
 */
 char * buf;
 va_list args;
 buf = mallocw(1024); /* Big enough for most strings... (cf sockuser.c) */
 va_start(args, fmt);
 vsprintf(buf, fmt, args);
 va_end(args);
 if (fp == stdout)
    usputs(Curproc->output, buf);
 else fputs(buf, fp);
 free(buf);
}

/* Print a resource record */
static void
put_rr(fp,rrp)
FILE *fp;
struct rr *rrp;
{
	char * stuff;

	if(fp == NULLFILE || rrp == NULLRR)
		return;

	if(rrp->name == NULLCHAR && rrp->comment != NULLCHAR){
		sofprintf(fp,"%s",rrp->comment);
		return;
	}

	sofprintf(fp,"%s",rrp->name);
	if(rrp->ttl != TTL_MISSING)
		sofprintf(fp,"\t%ld",rrp->ttl);
	if(rrp->class == CLASS_IN)
		sofprintf(fp,"\tIN");
	else
		sofprintf(fp,"\t<%u>",rrp->class);

	stuff = dtype(rrp->type);
	sofprintf(fp,"\t%s",stuff);
	if(rrp->rdlength == 0){
		/* Null data portion, indicates nonexistent record */
		/* or unsupported type.  Hopefully, these will filter */
		/* as time goes by. */
		sofprintf(fp,"\n");
		return;
	}
	switch(rrp->type){
	case TYPE_A:
		sofprintf(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 */
		sofprintf(fp,"\t%s\n",rrp->rdata.data);
		break;
	case TYPE_HINFO:
		sofprintf(fp,"\t%s\t%s\n",
		 rrp->rdata.hinfo.cpu,
		 rrp->rdata.hinfo.os);
		break;
	case TYPE_MX:
		sofprintf(fp,"\t%u\t%s\n",
		 rrp->rdata.mx.pref,
		 rrp->rdata.mx.exch);
		break;
	case TYPE_SOA:
		sofprintf(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:
		sofprintf(fp,"\n");
		break;
	}
}

/* Search local database for resource records.
 * Returns RR list, or NULLRR if no record found.
 */
static struct rr *
dfile_search(rrlp, filename)
struct rr *rrlp;
char *filename;
{
	int counter;
	register struct rr *frrp;
	struct rr **rrpp, *result_rrlp, *oldrrp;
	int32 elapsed;
	FILE *dbase;
	struct stat dstat;
	char *this_dfile;

#ifdef DEBUG
	if(Dtrace){
		tprintf("dfile_search: searching for %s\n",rrlp->name);
	}
#endif

#ifdef DSERVER
	if (filename)
		{
		if(Dtrace) {
			tprintf("dfile_search: searching local zone file %s\n", filename);
			}
		this_dfile = mallocw(strlen(Dzones) + 15);
		sprintf(this_dfile, "%s/%s", Dzones, filename);
		}
	else
#endif
		this_dfile = Dfile;

	while(Dfile_writing > 0)
		pwait(&Dfile_reading);
	Dfile_reading++;

	if((dbase = fopen(this_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;
	counter = 0;
	while((frrp = get_rr(dbase,oldrrp)) != NULLRR){
		if (++counter == 100)
			{
			pwait(NULL);
			counter = 0;
			}
		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.

				Unless we're primary!
			*/
			if(rrlp->type != TYPE_ANY
			&& rrlp->next == NULLRR
			&& result_rrlp != NULLRR
#ifdef DSERVER
			&& filename == NULL
#endif
			)
				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);
	}

#ifdef DSERVER
	if (filename)
		free(this_dfile);
#endif

	return result_rrlp;
}

/* Process which will add new resource records from the cache
 * to the local file, eliminating duplicates while it goes.
 */
static void __stdargs
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);
	usesock(Curproc->input = Cmdpp->input);
	close_s(Curproc->output);
	usesock(Curproc->output = Cmdpp->output);

	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*1000L);
				pwait(&Dfile_wait_absolute);
				alarm(0L);
			}
		}

		log(-1,"update Domain.txt");

		/* create new file for copy */
		if((new_fp = fopen(newname,WRITE_TEXT)) == NULLFILE){
			tprintf("dfile_update: can't create %s!\n",newname);
			break;
		}
		if(fstat(fileno(new_fp),&new_stat) != 0){
			tprintf("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){
			tprintf("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;
	char * stuff;

	tprintf("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);
	tprintf("%u questions:\n",dhp->qdcount);
	for(rrp = dhp->questions; rrp != NULLRR; rrp = rrp->next){
		stuff = dtype(rrp->type);
		tprintf("%s type %s class %u\n",rrp->name,
		 stuff,rrp->class);
	}
	tprintf("%u answers:\n",dhp->ancount);
	for(rrp = dhp->answers; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	tprintf("%u authority:\n",dhp->nscount);
	for(rrp = dhp->authority; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	tprintf("%u additional:\n",dhp->arcount);
	for(rrp = dhp->additional; rrp != NULLRR; rrp = rrp->next){
		put_rr(stdout,rrp);
	}
	fflush(stdout);
}

static int
dns_makequery(
	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 (int) (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){
			tprintf("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){
			tprintf("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->timeouts++;
		dp->timeout <<= 1;
		/* There may be a limit to how long we will wait */
		if(Dserver_maxwait && dp->timeout > (Dserver_maxwait * 1000L))
										dp->timeout = Dserver_maxwait * 1000L;

		if((dp = dp->next) == NULLDOM){
			dp = Dservers;
			if(Dserver_retries > 0 && ++tried > Dserver_retries)
				{
				if (Dserver_maxwait == 0)
					/* Only if not specified the maximum wait - MT Oct 92 */
					{
					/* Reset timeouts  - GT 07 Jun 92. */
					while (dp != NULLDOM)
						{
						dp->timeout = 2 * dp->mdev + dp->srtt + 3;
						dp = dp->next;
						}
					}       
				dp = Dservers;
				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;
}

/* Return "normalized" domain name, with default suffix and trailing '.'
 */
static char *
checksuffix(dname)
char *dname;
{
	char *sname, *tname;

	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;
	}
	return sname;
}

#ifdef DSERVER

char *zone_filename(char *domain, int32 *expire)
{
	struct DOM_FILE *t_dom_file;
	char *name;
	int len;
	len = strlen(domain);
	name = mallocw(len + 2);
	strcpy(name, domain);
	if (name[len - 1] != '.')
		{
		strcat(name, ".");
		len++;
		}
	t_dom_file = dom_file;
	while (Drx != NULLPROC && t_dom_file)
		{
		if (stricmp(t_dom_file->domain, name + len
										- strlen(t_dom_file->domain)) == 0)
			{
			if (expire)
				*expire = t_dom_file->dom_soa.expire;
			if (Dtrace)
				tprintf("DNS: Using file %s\n", t_dom_file->filename);
			free(name);
			return t_dom_file->filename;
			}
		t_dom_file = t_dom_file->next;
		}
	free(name);
	return NULL;
}

#endif

/*
	resolver - Search for resource records.
              Returns RR list, or NULLRR if no record found.
*/

static struct rr *resolver(struct rr *rrlp)
{
	struct rr *result_rrlp;
#ifdef DSERVER
	char *t;
	int32 expire;
	struct rr *t_rr;
#endif

#ifdef DSERVER
	/*
	Are we primary for the domain? If so, we must answer from the
	file cos we can't guarantee that a full set of records for a
	particular query will be in the cache.
	*/
	if ((t = zone_filename(rrlp->name, &expire)) != NULL)
		{
		result_rrlp = dfile_search(rrlp, t);
		if (result_rrlp != NULLRR)
			{
			t_rr = result_rrlp;
			while (t_rr)
				{
				t_rr->ttl = expire;
				t_rr = t_rr->next;
				}
/*
If we never use the cache to answer things we're primary for then there's
no point in caching the results! MT - G7LEU
			dcache_add(copy_rr_list(result_rrlp));
*/
			}
		return result_rrlp;
		}
#endif

	if((result_rrlp = dcache_search(rrlp)) == NULLRR){
		result_rrlp = dfile_search(rrlp, NULL);
	}
	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(
	char *dname,
	int16 dtype)
{
	struct rr *qrrp;
	struct rr *result_rrlp;
	char *sname;
	int looping = MAXCNAME;

	if(dname == NULLCHAR)
		return NULLRR;

	sname = checksuffix(dname);
	qrrp = make_rr(RR_QUERY,sname,CLASS_IN,dtype,0,0,NULL);
	free(sname);

	while(looping > 0){

		/* Change this condition to include TYPE_ANY. ILP 17/2/93 */

		if((result_rrlp=resolver(qrrp)) == NULLRR
		|| result_rrlp->type == dtype
		|| dtype == TYPE_ANY)
			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;
		/* choose the best of a set of routes */
		while(rrp != NULLRR) {
			if(rrp->rdlength > 0
			 && (rp = rt_lookup(rrp->rdata.addr)) != NULLROUTE
#if	0
			 && rp->metric <= cost) {
#endif
			 && 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;
	int our_domain = 0;

	are_we_an_mx = 0;

#ifdef DSERVER
	if (zone_filename(name, NULL))
		our_domain = 1;
#endif

	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 && (addr = resolve(rrp->rdata.mx.exch)) != 0L)
				{
				if (addr == Ip_addr)
					are_we_an_mx = 1;
				if (rrp->rdata.mx.pref <= pref)
					{
					pref = rrp->rdata.mx.pref;
					ip_address = addr;
					}
				}
			rrp = rrp->next;
		}
		free_rr(arrp);
		if(ip_address != 0)
			break;
		/* If this is our domain we can say no at this point */
		if (our_domain)
			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);
	if (Dtrace)
		tprintf("resolve_mx %s returns %s\n", name, inet_ntoa(ip_address));
	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, NULL);
	}
	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;
}

#ifdef DSERVER

/* Domain Name Server - based on the server in GRI-Nos 910828
 * ported to current NOS code by Johan. K. Reinalda, WG7J/PA3DIS
 * Merged into Demon Internet Services version - MT (g7leu) Sept 1992.
 * Added primary nameserver - MT (g7leu) October 1992.
 */

/* Process a query received by the DNS server */
static void __stdargs
proc_query(unused,d,b)
int unused;
void *d;
void *b;
{
    struct dserver *dp;
    struct dhdr *dhdr;
    int i,len;
    char *buf;
    struct sockaddr_in server;
    struct rr *rrp, *rrans, *rrtmp;
#ifdef	notdef
	struct rr *rrns, *rradd, *ap;
#endif
    struct rr *qp;
	int32 expire;

    dp = (struct dserver *) d;      /* The query address */
    dhdr = (struct dhdr *) b;       /* The query in host format */

    rrans = NULLRR;
#ifdef notdef
	rrns = rradd = NULLRR;
#endif
    qp = dhdr->questions;

    /* This loop does NOT support multiple questions yet */
#ifdef notdef
    for(i=0;i<dhdr->qdcount;i++){
#endif

	if(zone_filename(qp->name, &expire))
	    dhdr->aa = 1;
	else dhdr->aa = 0;

	switch(qp->type) {
	case TYPE_A:
	case TYPE_MX:
	case TYPE_CNAME:
	case TYPE_HINFO:
	case TYPE_PTR:
	case TYPE_NS:
	case TYPE_SOA:

		/* So we can do a dom q <dom.name> from another ka9q ILP 17/2/93 */
	case TYPE_ANY:

	  /* Not all of the below types are implemented in resolve_rr() */
#ifdef notdef
	case TYPE_TXT:
#endif
	    if((rrp = resolve_rr(qp->name,qp->type)) != NULLRR) {
		/* we found an entry, go tell him */
		dhdr->rcode = NO_ERROR;
		dhdr->qr = RESPONSE;
				rrp->ttl = expire;
	    } else {
		/* we did not find an entry, go tell him */
		rrp = (struct rr *)callocw(1,sizeof(struct rr));
		rrp->name = strdup(qp->name);
		rrp->type = qp->type;
		rrp->class = qp->class;
		rrp->ttl = 500L;
		rrp->rdata.addr = 0L;
		rrp->rdlength = 4;  /* size of addr data */
		dhdr->rcode = NAME_ERROR;
		dhdr->qr = RESPONSE;
	    }
	    rrans = rrp;
	    break;
	/* Search only the local cache and domain file for these next few */
	/* Is this a good idea ??? */
	case TYPE_MB:
	case TYPE_MG:
	case TYPE_MR:
	    rrp = make_rr(RR_QUERY,qp->name,CLASS_IN,qp->type,0,0,NULL);
	    if((rrans = dcache_search(rrp)) == NULLRR){
		rrans = dfile_search(rrp, NULL);
	    }
	    free_rr(rrp);
	    break;
#ifdef notdef
	/* Not implemented, yet (?) */
	case TYPE_MD:
	case TYPE_MF:
	case TYPE_WKS:
	case TYPE_NULL:
	case TYPE_MINFO:
#endif
	default:
	    dhdr->rcode = NOT_IMPL;
	    dhdr->qr = RESPONSE;
	}
    /* This loop does NOT support multiple questions yet */
#ifdef notdef
	qp = qp->next;  /* next question */
    }
#endif

    /* Find the number of answer records */
    i = 0;
    rrtmp = rrans;
    while(rrtmp != NULLRR) {
	i++;
	rrtmp = rrtmp->next;
    }
    dhdr->ancount = i;
    dhdr->answers = rrans;

    /* Authority & Additional RR's not implemented yet. */
    dhdr->authority = NULLRR;
    dhdr->nscount = 0;
    dhdr->arcount = 0;
    dhdr->additional = NULLRR;

#ifdef notdef
    /* Find the number of authority records */
    i = 0;
    rrtmp = rrns;
    while(rrtmp != NULLRR) {
	i++;
	rrtmp = rrtmp->next;
    }
    dhdr->nscount = i;
    dhdr->authority = rrns;

    /* Find the number of additional records */
    i = 0;
    rrtmp = rradd;
    while(rrtmp != NULLRR) {
	i++;
	rrtmp = rrtmp->next;
    }
    dhdr->arcount = i;
    dhdr->additional = rradd;
#endif

    if(Dtrace) {
	tputs("DNS: replying");
	dumpdomain(dhdr,0);
    }

    /* Maximum reply size is 512, see rfc1034/1035 */
    buf = mallocw(512);
    len = htondomain(dhdr,buf,512);
    free_dhdr(dhdr);

    server.sin_family = AF_INET;
    server.sin_port = dp->port;
    server.sin_addr.s_addr = dp->address;
    sendto(Dsocket,buf,len,0,(char *)&server,sizeof(server));
    free(buf);
    free((char *)dp);
}


/* Process to receive all domain server related messages */
static void __stdargs
drx(unused,u,p)
int unused;
void *u;
void *p;
{
	struct sockaddr_in sock,from;
	int fromlen;
	struct mbuf *bp;
	struct dhdr *dhdr;
	struct dserver *dp;
	int foo;

	Dsocket = socket(AF_INET,SOCK_DGRAM,0);
	sock.sin_family = AF_INET;
	sock.sin_addr.s_addr = INADDR_ANY;
	sock.sin_port = IPPORT_DOMAIN;
	if(bind(Dsocket,(char *)&sock,sizeof(sock)) == -1) {
	tputs("DNS: can't bind\n");
		Drx = NULLPROC;
		return;
	}
    /* Now loop forever, processing queries */
	for(;;){
		fromlen = sizeof(from);
	if((foo = recv_mbuf(Dsocket,&bp,0,(char *)&from,&fromlen)) == -1)
	    break;  /* Server closing */
		if(foo == 0)
			continue;
		dhdr = mallocw(sizeof(struct dhdr));
		ntohdomain(dhdr,&bp);
	if(Dtrace) {
	    tprintf("DNS: %u bytes from %s\n",foo,
		psocket((struct sockaddr *)&from));
	    dumpdomain(dhdr,0);
	}
	/* Process queries only */
	if(dhdr->qr == QUERY) {
	    /* Queries from ourself will cause a loop ! */
	    if(ismyaddr(from.sin_addr.s_addr) != NULLIF) {
		if(Dtrace)
		    tputs("DNS: question from myself ignored\n");
		free_dhdr(dhdr);
		continue;
	    } else {
		dp=(struct dserver *)callocw(1,sizeof(struct dserver));
		dp->address = from.sin_addr.s_addr;
		dp->srtt = (Dserver_maxwait * 1000L) / MSPTICK;
		dp->timeout = dp->srtt * 2;
		dp->port = from.sin_port;
		if(dhdr->opcode == ZONEINIT) {
		    /* ZONEINIT not implemented */
		    free_dhdr(dhdr);
		    free(dp);
		 } else
		    newproc("Domain server",1024,proc_query
				,0,(void *)dp,(void *)dhdr,0);
			}
		}
	}
}
				
static int
dodnsserver(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct DOM_FILE *t_dom_file;
	FILE *fp, *bfp;
	char *t, *t1, *t2,*t3, *t4;
	int done;
	int32 source;
	struct rr *frrp, *oldrrp;
#if	0
	struct soa *soap;
#endif
	char type, *delim = " \t";
    if(Drx == NULLPROC)
		{
		/* Get rid of any old data */
		t_dom_file = dom_file;
		while (t_dom_file)
			{
			free(t_dom_file->filename);
			free(t_dom_file->domain);
			free(t_dom_file);
			t_dom_file = t_dom_file->next;
			}
		dom_file = NULL;
		/*
		Find out where we're primary for (if anywhere)
		*/
		t = mallocw(200);
		t2 = mallocw(200);
		t4 = mallocw(13);
		bfp = fopen(Dboot, READ_TEXT);
		if (bfp)
			while (1)
				{
				if (feof(bfp) || !fgets(t, 199, bfp))
					{
					fclose(bfp);
					break;
					}
				t1 = strtok(t, delim);
				if (!t1)
					continue;
				source = 0;
				type = '0';
				if (stricmp(t1, "primary") == 0)
					type = '1';
				if (stricmp(t1, "secondary") == 0)
					type = '2';
				if (type == '0')
					continue;
				t1 = strtok(NULL, delim);
				if (!t1)
					continue;
				strlwr(t1);
				strcpy(t2, t1);
				t1 = strtok(NULL, delim);
				if (!t1)
					continue;
				if (type == '2')
					{
					source = resolve(t1);
					t1 = strtok(NULL, delim);
					if (!t1)
						continue;
					}
				rip(t1);
				strcpy(t4, t1);
				sprintf(t, "%s/%s", Dzones, t1);
				t3 = t - 1;
				while (*++t3)
					if (*t3 == '\\')
						*t3 = '/';
				if (Dtrace)
					tprintf("DNS: Looking for SOA record in %s\n", t);
				fp = fopen(t, READ_TEXT);
				if (fp)
					{
					done = 0;
					oldrrp = NULLRR;
					while(done == 0
						&& (frrp = get_rr(fp,oldrrp)) != NULLRR)
						{
						oldrrp = frrp;
						if (frrp->type == TYPE_SOA)
							{
							done = 1;
							if (dom_file == NULL)
								{
								dom_file = mallocw(sizeof(struct DOM_FILE));
								dom_file->next = NULL;
								}
							else {
								t_dom_file = mallocw(sizeof(struct DOM_FILE));
								t_dom_file->next = dom_file;
								dom_file = t_dom_file;
								}
							dom_file->filename = mallocw(13);
							strcpy(dom_file->filename, t4);
							dom_file->domain = mallocw(strlen(t2) + 2);
							sprintf(dom_file->domain, "%s.", t2);
							dom_file->dom_soa = frrp->rdata.soa;
							dom_file->type = type;
							dom_file->source = source;
							if (Dtrace)
								{
								tprintf(" filename %s\n", dom_file->filename);
								tprintf(" mname %s\n", dom_file->dom_soa.mname);
								tprintf(" rname %s\n", dom_file->dom_soa.rname);
								tprintf(" serial %lu\n", dom_file->dom_soa.serial);
								tprintf(" refresh %lu\n", dom_file->dom_soa.refresh);
								tprintf(" retry %lu\n", dom_file->dom_soa.retry);
								tprintf(" expire %lu\n", dom_file->dom_soa.expire);
								tprintf(" minimum %lu\n", dom_file->dom_soa.minimum);
								}
							}
						}
					fclose(fp);
					}
				else tprintf("failed to open\n");
				}
	/* Start domain server task */
		free(t4);
		free(t2);
		free(t);
	Drx = newproc("Domain listener",1024,drx,0,NULL,NULL,0);
		}
    else tputs("DNS already running\n");
    return 0;
}

static int
dostopdnsserver(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(Drx == NULLPROC)
	/* Start domain server task */
	tprintf("DNS not running\n");
	killproc(Drx);
	Drx = NULLPROC;
    return 0;
}

/* Free a domain message */
static void
free_dhdr(dp)
struct dhdr *dp;
{
    if(dp->qdcount != 0)
	free_rr(dp->questions);
    if(dp->ancount != 0)
	free_rr(dp->answers);
    if(dp->nscount != 0)
	free_rr(dp->authority);
    if(dp->arcount != 0)
	free_rr(dp->additional);
    free((char *)dp);
}

#endif
