#include <u.h>
#include <libc.h>
#include <ip.h>
#include "dat.h"
#include "protos.h"
#include "../../ndb/dns.h"

/* names of RR types - /sys/src/cmd/ndb/dn.c:/rrtname */
char *rrtname[] =
{
[Ta]		"ip",
[Tns]		"ns",
[Tmd]		"md",
[Tmf]		"mf",
[Tcname]	"cname",
[Tsoa]		"soa",
[Tmb]		"mb",
[Tmg]		"mg",
[Tmr]		"mr",
[Tnull]		"null",
[Twks]		"wks",
[Tptr]		"ptr",
[Thinfo]	"hinfo",
[Tminfo]	"minfo",
[Tmx]		"mx",
[Ttxt]		"txt",
[Trp]		"rp",
[Tafsdb]	"afsdb",
[Tx25]		"x.25",
[Tisdn]		"isdn",
[Trt]		"rt",
[Tnsap]		"nsap",
[Tnsapptr]	"nsap-ptr",
[Tsig]		"sig",
[Tkey]		"key",
[Tpx]		"px",
[Tgpos]		"gpos",
[Taaaa]		"ipv6",
[Tloc]		"loc",
[Tnxt]		"nxt",
[Teid]		"eid",
[Tnimloc]	"nimrod",
[Tsrv]		"srv",
[Tatma]		"atma",
[Tnaptr]	"naptr",
[Tkx]		"kx",
[Tcert]		"cert",
[Ta6]		"a6",
[Tdname]	"dname",
[Tsink]		"sink",
[Topt]		"opt",
[Tapl]		"apl",
[Tds]		"ds",
[Tsshfp]	"sshfp",
[Tipseckey]	"ipseckey",
[Trrsig]	"rrsig",
[Tnsec]		"nsec",
[Tdnskey]	"dnskey",
[Tspf]		"spf",
[Tuinfo]	"uinfo",
[Tuid]		"uid",
[Tgid]		"gid",
[Tunspec]	"unspec",
[Ttkey]		"tkey",
[Ttsig]		"tsig",
[Tixfr]		"ixfr",
[Taxfr]		"axfr",
[Tmailb]	"mailb",
[Tmaila]	"maila",
[Tall]		"all",
[Tcaa]		"caa",
		0,
};
static char*
rrtypestr(int t)
{
	static char buf[20];

	if(t >= 0 && t < nelem(rrtname) && rrtname[t])
		return rrtname[t];
	snprint(buf, sizeof buf, "type%d", t);
	return buf;
}

static void
fmtrr(Msg *m, RR **rrp, int quest)
{
	Txt *t;
	RR *rr;

	rr = *rrp;
	if(rr == nil)
		return;
	*rrp = rr->next;
	if(rr->type == Topt){
		m->p = seprint(m->p, m->e, "opt eflags=%#lux udpsize=%d data=%.*H",
			rr->eflags, rr->udpsize,
			rr->opt->dlen, rr->opt->data);
		rrfree(rr);
		return;
	}
	m->p = seprint(m->p, m->e, "%s name=%s ttl=%lud",
		rrtypestr(rr->type),
		rr->owner->name, rr->ttl);
	if(!quest)
	switch(rr->type){
	case Thinfo:
		m->p = seprint(m->p, m->e, " cpu=%s os=%s",
			rr->cpu->name, rr->os->name);
		break;
	case Tcname:
	case Tmb:
	case Tmd:
	case Tmf:
	case Tns:
		m->p = seprint(m->p, m->e, " host=%s", rr->host->name);
		break;
	case Tmg:
	case Tmr:
		m->p = seprint(m->p, m->e, " mb=%s", rr->mb->name);
		break;
	case Tminfo:
		m->p = seprint(m->p, m->e, " rmb=%s", rr->rmb->name);
		m->p = seprint(m->p, m->e, " mb=%s", rr->mb->name);
		break;
	case Tmx:
		m->p = seprint(m->p, m->e, " pref=%lud", rr->pref);
		m->p = seprint(m->p, m->e, " host=%s", rr->host->name);
		break;
	case Ta:
	case Taaaa:
		m->p = seprint(m->p, m->e, " ip=%s", rr->ip->name);
		break;
	case Tptr:
		m->p = seprint(m->p, m->e, " ptr=%s", rr->ptr->name);
		break;
	case Tsoa:
		m->p = seprint(m->p, m->e, " host=%s", rr->host->name);
		m->p = seprint(m->p, m->e, " rmb=%s", rr->rmb->name);
		m->p = seprint(m->p, m->e, " soa.serial=%lud", rr->soa->serial);
		m->p = seprint(m->p, m->e, " soa.refresh=%lud", rr->soa->refresh);
		m->p = seprint(m->p, m->e, " soa.retry=%lud", rr->soa->retry);
		m->p = seprint(m->p, m->e, " soa.expire=%lud", rr->soa->expire);
		m->p = seprint(m->p, m->e, " soa.minttl=%lud", rr->soa->minttl);
		break;
	case Ttxt:
		for(t=rr->txt; t; t=t->next)
			m->p = seprint(m->p, m->e, " txt=\"%.*s\"",
				t->dlen, (char*)t->data);
		break;
	case Tnull:
		m->p = seprint(m->p, m->e, " null=%.*H",
			rr->null->dlen, rr->null->data);
		break;
	case Trp:
		m->p = seprint(m->p, m->e, " rmb=%s", rr->rmb->name);
		m->p = seprint(m->p, m->e, " rp=%s", rr->rp->name);
		break;
	case Tkey:
		m->p = seprint(m->p, m->e, " flags=%d proto=%d alg=%d data=%.*H",
			rr->key->flags, rr->key->proto, rr->key->alg,
			rr->key->dlen, rr->key->data);
		break;
	case Tsig:
		m->p = seprint(m->p, m->e,
" type=%d alg=%d labels=%d ttl=%lud exp=%lud incep=%lud tag=%d signer=%s data=%.*H",
			rr->sig->type, rr->sig->alg, rr->sig->labels,
			rr->sig->ttl, rr->sig->exp, rr->sig->incep, rr->sig->tag,
			rr->sig->signer->name, rr->sig->dlen, rr->sig->data);
		break;
	case Tcert:
		m->p = seprint(m->p, m->e, " type=%d tag=%d alg=%d data=%.*H",
			rr->cert->type, rr->cert->tag, rr->cert->alg,
			rr->cert->dlen, rr->cert->data);
		break;
	case Tcaa:
		m->p = seprint(m->p, m->e, " flags=%d tag=%s caa=\"%.*s\"",
			rr->caa->flags, rr->caa->tag->name,
			rr->caa->dlen, (char*)rr->caa->data);
		break;
	default:
		if(rrsupported(rr->type))
			break;
		m->p = seprint(m->p, m->e, " unknown=%.*H",
			rr->unknown->dlen, rr->unknown->data);
	}
	rrfree(rr);
}

void freealldn(void);
static Proto dnsqd, dnsan, dnsns, dnsar;

static void donext(Msg*);
static void dolast(Msg*);
static DNSmsg dm;

static int
p_seprint(Msg *m)
{
	char *e;

	if((e = convM2DNS(m->ps, m->pe-m->ps, &dm, nil)) != nil){
		m->p = seprint(m->p, m->e, "error: %s", e);
		free(e);
		dolast(m);
		return 0;
	}
	m->p = seprint(m->p, m->e, "id=%d flags=%#ux", dm.id, dm.flags);
	donext(m);
	return 0;
}

static void
donext(Msg *m)
{
	if(dm.qd)
		m->pr = &dnsqd;
	else if(dm.an)
		m->pr = &dnsan;
	else if(dm.ns)
		m->pr = &dnsns;
	else if(dm.ar)
		m->pr = &dnsar;
	else
		dolast(m);
}

static void
dolast(Msg *m)
{
	freealldn();
	memset(&dm, 0, sizeof dm);
	m->pr = nil;
}

static int
p_seprintqd(Msg *m)
{
	fmtrr(m, &dm.qd, 1);
	donext(m);
	return 0;
}

static int
p_seprintan(Msg *m)
{
	fmtrr(m, &dm.an, 0);
	donext(m);
	return 0;
}

static int
p_seprintns(Msg *m)
{
	fmtrr(m, &dm.ns, 1);
	donext(m);
	return 0;
}

static int
p_seprintar(Msg *m)
{
	fmtrr(m, &dm.ar, 1);
	donext(m);
	return 0;
}

Proto dns =
{
	"dns",
	nil,
	nil,
	p_seprint,
	nil,
	nil,
	nil,
	defaultframer,
};

static Proto dnsqd =
{
	"dns.qd",
	nil,
	nil,
	p_seprintqd,
	nil,
	nil,
	nil,
	defaultframer,
};

static Proto dnsan =
{
	"dns.an",
	nil,
	nil,
	p_seprintan,
	nil,
	nil,
	nil,
	defaultframer,
};

static Proto dnsns =
{
	"dns.ns",
	nil,
	nil,
	p_seprintns,
	nil,
	nil,
	nil,
	defaultframer,
};

static Proto dnsar =
{
	"dns.ar",
	nil,
	nil,
	p_seprintar,
	nil,
	nil,
	nil,
	defaultframer,
};


void*
emalloc(int n)
{
	void *v;

	v = mallocz(n, 1);
	if(v == nil)
		sysfatal("out of memory");
	return v;
}

char*
estrdup(char *s)
{
	s = strdup(s);
	if(s == nil)
		sysfatal("out of memory");
	return s;
}

DN *alldn;

DN*
dnlookup(char *name, int class, int)
{
	DN *dn;
	int n;

	n = strlen(name) + 1;
	dn = emalloc(sizeof(*dn) + n);
	memmove(dn->name, name, n);
	dn->class = class;
	dn->next = alldn;
	alldn = dn;
	return dn;
}

void
freealldn(void)
{
	DN *dn;

	while(dn = alldn){
		alldn = dn->next;
		free(dn);
	}
}

void
dnslog(char *fmt, ...)			/* don't log */
{
	USED(fmt);
}

/*************************************************
 * Everything below here is copied from /sys/src/cmd/ndb/dn.c
 * without modification and can be recopied to update.
 */
int
rrsupported(int type)
{
	return type >= 0 && type < nelem(rrtname) && rrtname[type] != nil;
}

/*
 *  convert an integer RR type to it's ascii name
 */
char*
rrname(int type, char *buf, int len)
{
	char *t;

	t = nil;
	if(type >= 0 && type < nelem(rrtname))
		t = rrtname[type];
	if(t==nil){
		snprint(buf, len, "%d", type);
		t = buf;
	}
	return t;
}

/*
 *  free a list of resource records and any related structs
 */
void
rrfreelist(RR *rp)
{
	RR *next;

	for(; rp; rp = next){
		next = rp->next;
		rrfree(rp);
	}
}
void
freeserverlist(Server *s)
{
	Server *next;

	for(; s != nil; s = next){
		next = s->next;
		free(s);
	}
}

/*
 *  allocate a resource record of a given type
 */
RR*
rralloc(int type)
{
	RR *rp;

	rp = emalloc(sizeof(*rp));
	rp->pc = getcallerpc(&type);
	rp->type = type;
	setmalloctag(rp, rp->pc);
	switch(type){
	case Tsoa:
		rp->soa = emalloc(sizeof(*rp->soa));
		rp->soa->slaves = nil;
		setmalloctag(rp->soa, rp->pc);
		break;
	case Tsrv:
		rp->srv = emalloc(sizeof(*rp->srv));
		setmalloctag(rp->srv, rp->pc);
		break;
	case Tdnskey:
	case Tkey:
		rp->key = emalloc(sizeof(*rp->key));
		setmalloctag(rp->key, rp->pc);
		break;
	case Tcaa:
		rp->caa = emalloc(sizeof(*rp->caa));
		setmalloctag(rp->caa, rp->pc);
		break;
	case Tcert:
		rp->cert = emalloc(sizeof(*rp->cert));
		setmalloctag(rp->cert, rp->pc);
		break;
	case Tsig:
		rp->sig = emalloc(sizeof(*rp->sig));
		setmalloctag(rp->sig, rp->pc);
		break;
	case Tnull:
		rp->null = emalloc(sizeof(*rp->null));
		setmalloctag(rp->null, rp->pc);
		break;
	case Topt:
		rp->opt = emalloc(sizeof(*rp->opt));
		setmalloctag(rp->opt, rp->pc);
		break;
	default:
		if(rrsupported(rp->type))
			break;
		rp->unknown = emalloc(sizeof(*rp->unknown));
		setmalloctag(rp->unknown, rp->pc);
	}
	rp->ttl = 0;
	rp->expire = 0;
	rp->next = 0;
	return rp;
}

/*
 *  free a resource record and any related structs
 */
void
rrfree(RR *rp)
{
	DN *dp;
	RR *nrp;
	Txt *t;

	assert(!rp->cached);

	dp = rp->owner;
	if(dp){
		for(nrp = dp->rr; nrp; nrp = nrp->next)
			assert(nrp != rp);	/* "rrfree of live rr" */
	}

	switch(rp->type){
	case Tsoa:
		freeserverlist(rp->soa->slaves);
		memset(rp->soa, 0, sizeof *rp->soa);	/* cause trouble */
		free(rp->soa);
		break;
	case Tsrv:
		memset(rp->srv, 0, sizeof *rp->srv);	/* cause trouble */
		free(rp->srv);
		break;
	case Tdnskey:
	case Tkey:
		free(rp->key->data);
		memset(rp->key, 0, sizeof *rp->key);	/* cause trouble */
		free(rp->key);
		break;
	case Tcert:
		free(rp->cert->data);
		memset(rp->cert, 0, sizeof *rp->cert);	/* cause trouble */
		free(rp->cert);
		break;
	case Tsig:
		free(rp->sig->data);
		memset(rp->sig, 0, sizeof *rp->sig);	/* cause trouble */
		free(rp->sig);
		break;
	case Tnull:
		free(rp->null->data);
		memset(rp->null, 0, sizeof *rp->null);	/* cause trouble */
		free(rp->null);
		break;
	case Tcaa:
		free(rp->caa->data);
		memset(rp->caa, 0, sizeof *rp->caa);	/* cause trouble */
		free(rp->caa);
		break;
	case Ttxt:
		while(t = rp->txt){
			rp->txt = t->next;
			free(t->data);
			memset(t, 0, sizeof *t);	/* cause trouble */
			free(t);
		}
		break;
	case Topt:
		free(rp->opt->data);
		memset(rp->opt, 0, sizeof *rp->opt);	/* cause trouble */
		free(rp->opt);
		break;
	default:
		if(rrsupported(rp->type))
			break;
		free(rp->unknown->data);
		memset(rp->unknown, 0, sizeof *rp->unknown);	/* cause trouble */
		free(rp->unknown);
		break;
	}

	memset(rp, 0, sizeof *rp);		/* cause trouble */
	free(rp);
}
