/* PowerDNS pipe backend which uses avahi to resolve ipv6 clients.
 * 
 * The idea is to have a forward lookup zone (ZONE) and a matching
 * reverse lookup zone (REVZONE) automagically generate replies
 * with matching forward and reverse from those by looking up the
 * host/address via avahi (zeroconf).
 * (In other words a lame bridge from PowerDNS<->avahi instead of using
 * wide area zeroconf?)
 * 
 * Copyright (c) 2009, Andreas Henriksson <andreas@fatal.se>
 *
 * compile: gcc -o pdns-avahi pdns-avahi.c -g \
 *            `pkg-config --cflags --libs avahi-client`
 *
 * configure: http://doc.powerdns.com/pipebackend-dynamic-resolution.html
 */

#include <stdio.h>
#include <string.h>
#include <assert.h>

#include <avahi-common/simple-watch.h>
#include <avahi-client/client.h>
#include <avahi-client/lookup.h>

/* ----------- EDIT HERE ------------------- */

#define ZONE ".autov6.fatal.se"
#define REVZONE ".1.0.0.0.4.0.5.4.6.b.2.5.2.0.0.2.ip6.arpa"
/* IPBLOCK should match REVZONE. Yes I was to lazy to write code to flip it! */
#define IPBLOCK "2002:52b6:4504:1:"

/* ------------------------------------------ */


static AvahiSimplePoll *simple_poll = NULL;
static AvahiClient *client = NULL;

int is_blank (char c)
{
	if (c == ' ' || c == '\t')
		return 1;
	return 0;
}


static void address_resolver_callback(
		AvahiAddressResolver *r,
		AVAHI_GCC_UNUSED AvahiIfIndex interface,
		AVAHI_GCC_UNUSED AvahiProtocol protocol,
		AvahiResolverEvent event,
		const AvahiAddress *a,
		const char *name,
		AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
		AVAHI_GCC_UNUSED void *userdata)
{
	char address[AVAHI_ADDRESS_STR_MAX];
	char *p, *rev = userdata;

	if (event != AVAHI_RESOLVER_FOUND)
		goto out;

	avahi_address_snprint(address, sizeof(address), a);

	p = strchr(name, '.');
	if (p == NULL)
		goto out;
	*p = '\0';

	printf("DATA\t%s\tIN\tPTR\t3600\t-1\t%s%s\n", rev, name, ZONE);

out:
	avahi_address_resolver_free(r);
	avahi_simple_poll_quit(simple_poll);
}

static void host_name_resolver_callback(
		AvahiHostNameResolver *r,
		AVAHI_GCC_UNUSED AvahiIfIndex interface,
		AVAHI_GCC_UNUSED AvahiProtocol protocol,
		AvahiResolverEvent event,
		const char *name,
		const AvahiAddress *a,
		AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
		AVAHI_GCC_UNUSED void *userdata)
{
	char address[AVAHI_ADDRESS_STR_MAX];
	char *host = userdata;

	if (event != AVAHI_RESOLVER_FOUND)
		goto out;

	avahi_address_snprint(address, sizeof(address), a);

	printf("DATA\t%s%s\tIN\tAAAA\t3600\t-1\t%s\n", host, ZONE,
			address);

out:
	avahi_host_name_resolver_free(r);
	avahi_simple_poll_quit(simple_poll);
}


static int do_avahi_lookup_addr(char *addr, char *reverse)
{
	AvahiAddress a;

//	fprintf(stderr, "DEBUG: avahi-resolve -a %s\n", addr);

	if (!avahi_address_parse(addr, AVAHI_PROTO_INET6, &a))
		return -55;

	/* start simple loop */
	simple_poll = avahi_simple_poll_new();
	if (!simple_poll)
		return -1;

	/* create new client */
	client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, NULL, NULL, NULL);
	if (!client)
		return -2;

	/* hook up query to callback */
	if (!avahi_address_resolver_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, &a, 0, address_resolver_callback, reverse))
		return -3;

	/* wait for query to finish */
	avahi_simple_poll_loop(simple_poll);
	return 0;



#if 0
	/* FIXME: implement the rest */
	return -99;
#endif
}

static int do_avahi_lookup_name(char *name)
{
	char host[1024];

	snprintf(host, sizeof(host), "%s.local", name);

//	fprintf(stderr, "DEBUG: avahi-resolve -n6 %s.local\n", name);

	/* start simple loop */
	simple_poll = avahi_simple_poll_new();
	if (!simple_poll)
		return -1;

	/* create new client */
	client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, NULL, NULL, NULL);
	if (!client)
		return -2;

	/* hook up query to callback */
	if (!avahi_host_name_resolver_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, host, AVAHI_PROTO_INET6, 0, host_name_resolver_callback, name))
		return -3;

	/* wait for query to finish */
	avahi_simple_poll_loop(simple_poll);
	return 0;


#if 0

	/* FIXME: implement the rest */
	return -99;
#endif
}

static int do_ptr(char *host)
{
	int i, offset = strlen(host) - strlen(REVZONE);
	char outbuf[1024], *p;

	if (strcmp(host+offset, REVZONE) != 0)
		return -1;

	memset(outbuf, '\0', sizeof(outbuf));
	strcpy(outbuf, IPBLOCK);
	p = outbuf+strlen(outbuf);

	i=0;
	while (offset > 0 && p < outbuf+sizeof(outbuf)) {
		if (*(host+offset) != '0')
		       *p++ = *(host+offset-1);

		offset -= 2;
		if (++i == 4) {
			*p++ = ':';
			i=0;
		}
	}

	if (*(p-1) == ':')
		*--p = '\0';

	return do_avahi_lookup_addr(outbuf, host);
}

static int do_aaaa(char *host)
{
	int offset = strlen(host) - strlen(ZONE);

	if (strcmp(host+offset, ZONE) != 0)
		return -1;

	*(host+offset) = '\0';

	return do_avahi_lookup_name(host);
}

static int do_query(char *buf)
{
	char *p = buf;
	char *host, *qtype;

	if (*p++ != 'Q' || !is_blank(*p++))
		return -1;
	
	host = p;
	p = strchr(host, '\t');
	if (p == NULL)
		return -2;
	*p++ = '\0';

	if (*p++ != 'I' || *p++ != 'N' || !is_blank(*p++))
		return -3;

	qtype = p;
	p = strchr(qtype, '\t');
	if (p == NULL)
		return -4;
	*p++ = '\0';

	if (strcmp(qtype, "PTR") == 0 || 
			(strcmp(qtype, "ANY") == 0 && strstr(host, "arpa")))
		return do_ptr(host);
	else if (strcmp(qtype, "AAAA") == 0 || strcmp(qtype, "ANY") == 0)
		return do_aaaa(host);
	return -99;
}

int main (int argc, char **argv)
{
	char buf[1024];
	int state = 0;
	char *p1, *p2;

	while (fgets(buf, sizeof(buf), stdin)) {

		switch (state) {
		case 0:
			if (strcmp(buf, "HELO\t1\n") != 0) {
				printf("FAIL avahi autov6 pipe backend only supports protocol version 1, you said: %s\n", buf);
				return 1;
			}

			printf("OK\tavahi autov6 pipe backend ready.\n");
			fflush(stdout);
			state++;

			break;

		case 1:
			/* printf("LOG\t%s", buf); */
			do_query(buf);
			printf("END\n");

			fflush(stdout);
			break;

		default:
			fprintf(stderr, "Error: invalid state.\n");
			return 99;
		}

	}

}
