/* RFC2136 DNS inform - necessary for Win2k3 DNS servers */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ndb.h>
#include <ip.h>
#include "dns.h"

enum {
	FQDNMAX	= 255,
};

char *errmsgs[] = {
	[0]  "ok",
	[1]  "request format error",
	[2]  "internal server error",
	[3]  "domain name does not exist",
	[4]  "request not supported",
	[5]  "permission denied",
	[6]  "domain name already exists",
	[7]  "resource record already exists",
	[8]  "resource record does not exist",
	[9]  "server not authoritative",
	[10] "domain name not in zone",
};

static uchar loopbacknet[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0xff, 0xff,
	127, 0, 0, 0
};
static uchar loopbackmask[IPaddrlen] = {
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0xff, 0, 0, 0
};
static uchar loopback6[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 1
};

void
usage(void)
{
	fprint(2, "usage: %s [-x netmtpt]\n", argv0);
	exits("usage");
}

void
ding(void *, char *msg)
{
	if(strstr(msg, "alarm") != nil)
		noted(NCONT);
	noted(NDFLT);
}

int
g16(uchar **p)
{
	int n;

	n  = *(*p)++ << 8;
	n |= *(*p)++;
	return n;
}

void
p16(uchar **p, int n)
{
	*(*p)++ = n >> 8;
	*(*p)++ = n;
}

void
p32(uchar **p, int n)
{
	*(*p)++ = n >> 24;
	*(*p)++ = n >> 16;
	*(*p)++ = n >> 8;
	*(*p)++ = n;
}

void
pmem(uchar **p, void *v, int len)
{
	memmove(*p, v, len);
	*p += len;
}

void
pname(uchar **p, char *s)
{
	uchar *len;

	while (*s){
		len = (*p)++;
		while(*s && *s != '.')
			*(*p)++ = *s++;
		*len = *p - len - 1;
		if(*s == '.')
			s++;
	}
	*(*p)++ = 0;
}

void
main(int argc, char *argv[])
{
	static char *query[] = { "dom", "dnsdomain", "ns", "inform" };
	char *sysname, *dnsdomain, *dom, *inform, *ns, net[32], dn[256], ds[256];
	int debug, len, fd;
	uint err;
	uchar *p, buf[4096], mynet[IPaddrlen];
	ushort txid;
	Ndb *db;
	Ndbtuple *t, *tt;
	Ipifc *ifc;
	Iplifc *lifc;

	fmtinstall('I', eipfmt);
	fmtinstall('V', eipfmt);
	setnetmtpt(net, sizeof net, nil);

	debug = 0;
	ns = nil;
	dom = nil;
	inform = nil;
	dnsdomain = nil;
	ARGBEGIN{
	case 'd':
		debug = 1;
		break;
	case 'x':
		setnetmtpt(net, sizeof net, EARGF(usage()));
		break;
	default:
		usage();
	}ARGEND;

	if(argc != 0)
		usage();

	if((sysname = getenv("sysname")) == nil)
		sysfatal("$sysname not set");

	if((db = ndbopen(nil)) == nil)
		sysfatal("can't open ndb: %r");
	tt = ndbipinfo(db, "sys", sysname, query, nelem(query));
	for(t = tt; t; t = t->entry){
		if(strcmp(t->attr, "ns") == 0)
			ns = t->val;
		else if(strcmp(t->attr, "dom") == 0)
			dom = t->val;
		else if(strcmp(t->attr, "dnsdomain") == 0)
			dnsdomain = t->val;
		else if(strcmp(t->attr, "inform") == 0)
			inform = t->val;
	}

	ndbfree(tt);
	ndbclose(db);

	if(inform)
		dom = inform;
	if(!ns)
		sysfatal("no relevant ns=");
	if(!dom)
		sysfatal("no relevant dom=");
	if(!dnsdomain)
		sysfatal("no relevant dnsdomain=");


	if(utf2idn(dom, dn, sizeof(dn)) <= 0)
		sysfatal("cannot convert dom");

	if(utf2idn(dnsdomain, ds, sizeof(ds)) <= 0)
		sysfatal("cannot convert dnsdomain");

	if(debug){
		print("ns=%s\n", ns);
		print("dnsdomain=%s\n", dnsdomain);
		print("dom=%s\n", dom);
	}

	if((fd = dial(netmkaddr(ns, "udp", "dns"), 0, 0, 0)) < 0)
		sysfatal("can't dial %s: %r", ns);

	txid = time(nil) + getpid();

	p = buf;
	p16(&p, txid);		/* ID */
	p16(&p, 5<<11);		/* flags */
	p16(&p, 1);		/* # Zones */
	p16(&p, 0);		/* # prerequisites */
	p16(&p, 2);		/* # updates */
	p16(&p, 0);		/* # additionals */

        pname(&p, ds);		/* zone */
	p16(&p, Tsoa);		/* zone type */
	p16(&p, Cin);		/* zone class */

	/* delete old name */
   	pname(&p, dn);		/* name */
	p16(&p, Ta);		/* type: v4 addr */
	p16(&p, Call);		/* class */
	p32(&p, 0);		/* TTL */
	p16(&p, 0);		/* data len */

   	pname(&p, dn);		/* name */
	p16(&p, Taaaa);		/* type: v6 addr */
	p16(&p, Call);		/* class */
	p32(&p, 0);		/* TTL */
	p16(&p, 0);		/* data len */

	for(ifc = readipifc(net, nil, -1); ifc != nil; ifc = ifc->next){
		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
			/* unspecified */
			if(ipcmp(lifc->ip, IPnoaddr) == 0)
				continue;

			/* ipv6 loopback */
			if(ipcmp(lifc->ip, loopback6) == 0)
				continue;

			/* ipv4 loopback */
			maskip(lifc->ip, loopbackmask, mynet);
			if(ipcmp(mynet, loopbacknet) == 0)
				continue;

			if(debug)
				print("ip=%I\n", lifc->ip);

			/* add new A record */
			pname(&p, dn);		/* name */
			p16(&p, isv4(lifc->ip)?Ta:Taaaa);
			p16(&p, Cin);		/* class */
			p32(&p, 60*60*25);	/* TTL (25 hours) */
			if(isv4(lifc->ip)){
				p16(&p, IPv4addrlen);
				pmem(&p, lifc->ip+IPv4off, IPv4addrlen);
			} else {
				p16(&p, IPaddrlen);
				pmem(&p, lifc->ip, IPaddrlen);
			}
		}
	}

	len = p - buf;
	if(write(fd, buf, len) != len)
		sysfatal("write failed: %r");

	notify(ding);
	alarm(3000);
	do{
		if(read(fd, buf, sizeof buf) < 0)
			sysfatal("timeout");
		p = buf;
	}while(g16(&p) != txid);
	alarm(0);

	err = g16(&p) & 7;
	if(err != 0 && err != 7)	/* err==7 is just a "yes, I know" warning */
		if(err < nelem(errmsgs))
			sysfatal("%s", errmsgs[err]);
		else
			sysfatal("unknown dns server error %d", err);
	exits(nil);
}
