#include <u.h>
#include <libc.h>
#include <ip.h>
#include <bio.h>
#include <ndb.h>
#include "dns.h"

/* get a notification from another system of a changed zone */
void
dnnotify(DNSmsg *reqp, DNSmsg *repp, Request *r)
{
	RR *tp;
	Area *a;

	USED(r);
	/* move one question from reqp to repp */
	memset(repp, 0, sizeof(*repp));
	tp = reqp->qd;
	reqp->qd = tp->next;
	tp->next = 0;
	repp->qd = tp;
	repp->id = reqp->id;
	repp->flags = Fresp  | Onotify | Fauth;

	/* anything to do? */
	if(zonerefreshprogram == nil)
		return;

	/* make sure its the right type */
	if(repp->qd->type != Tsoa)
		return;

syslog(0, logfile, "notification for %s", repp->qd->owner->name);

	/* is it something we care about? */
	a = inmyarea(repp->qd->owner->name);
	if(a == nil)
		return;

syslog(0, logfile, "serial old %lud new %lud", a->soarr->soa->serial, repp->qd->soa->serial);

	/* do nothing if it didn't change */
	if(a->soarr->soa->serial== repp->qd->soa->serial)
		return;

	a->needrefresh = 1;
}

/*
 * this isn't going to work as a thread!
 */

static void
ding(void *u, char *msg)
{
	USED(u);

	if(strstr(msg, "alarm"))
		noted(NCONT);
	else
		noted(NDFLT);
}

/* notify a slave that an area has changed. */
static void
send_notify(char *slave, RR *soa, Request *req)
{
	int i, len, n, reqno, status, fd;
	uchar obuf[Maxudp+Udphdrsize];
	uchar ibuf[Maxudp+Udphdrsize];
	RR *rp;
	Udphdr *up = (Udphdr*)obuf;
	char *err;
	DNSmsg repmsg;

	/* create the request */
	reqno = rand();
	n = mkreq(soa->owner, Cin, obuf, Fauth | Onotify, reqno);

	/* get an address */
	if(strcmp(ipattr(slave), "ip") == 0) {
		parseip(up->raddr, slave);
	} else {
		rp = dnresolve(slave, Cin, Ta, req, nil, 0, 1, 1, &status);
		if(rp == nil)
			return;
		parseip(up->raddr, rp->ip->name);
		rrfree(rp);
	}

	fd = udpport();
	if(fd < 0)
		return;

	notify(ding);

	/* send 3 times or until we get anything back */
	for(i = 0; i < 3; i++){
syslog(0, logfile, "sending %d byte notify to %s/%I.%d about %s", n, slave, up->raddr, nhgets(up->rport), soa->owner->name);
		if(udpwrite(fd, (Udphdr*)obuf, obuf+Udphdrsize, n) != n)
			break;
		alarm(2*1000);
		len = udpread(fd, (Udphdr*)ibuf, ibuf+Udphdrsize, Maxudp);
		alarm(0);
		if(len <= Udphdrsize)
			continue;
		err = convM2DNS(&ibuf[Udphdrsize], len, &repmsg);
		if(err != nil)
			continue;
		if(repmsg.id == reqno && (repmsg.flags & Omask) == Onotify)
			break;
	}

	close(fd);
}

/* send notifies for any updated areas */
static void
notify_areas(Area *a, Request *req)
{
	Server *s;

	for(; a != nil; a = a->next){
		if(!a->neednotify)
			continue;

		/* send notifies to all slaves */
		for(s = a->soarr->soa->slaves; s != nil; s = s->next)
			send_notify(s->name, a->soarr, req);
		a->neednotify = 0;
	}
}

/*
 *  process to notify other servers of changes
 *  (also reads in new databases)
 */
void
notifyproc(void *v)
{
	Request req;

	USED(v);

	for(;;){
		getactivity(&req);
		notify_areas(owned, &req);
		putactivity();
		sleep(60*1000);
	}
}