/* PowerDNS pipe backend with secret hostname.
 *
 * The idea is to only allow regular hostname lookups if they originate
 * from a host that has previously done a reverse lookup (noone else).
 * The reason would be to keep the ip a secret for those who don't
 * already know it.
 *
 * Copyright (c) 2009, Andreas Henriksson <andreas@fatal.se>
 *
 * compile: gcc -o pdns-secret pdns-secret.c -g
 *
 * configure: http://doc.powerdns.com/pipebackend-dynamic-resolution.html
 *
 * # secret backend needs to disable cache.
 * query-cache-ttl=0
 * cache-ttl=0
 * recursive-cache-ttl=0
 */

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


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

#ifndef ZONE
#	define ZONE "secret.fatal.se"
#endif
#ifndef SECRETREV
#	define SECRETREV "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.4.3.3.3.2.1.1.1.2.0.0.2.ip6.arpa"
#endif
/* should match REV. Yes I was to lazy to write code to flip it! */
#ifndef SECRETIP
#	define SECRETIP "2002:1112:3334:99::"
#endif

#ifndef SECRETTXT
#	define SECRETTXT "Have a great day! Come back again!"
#endif

#ifndef FAKEIP
#	define FAKEIP "::1"
#endif

#ifndef FAKETXT
#	define FAKETXT "Treat the reply with care!"
#endif

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

#define WHITELIST "/etc/powerdns/pdns-secret.whitelist"

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

static void chomp (char *str)
{
	int len = strlen(str);
	while (len > 0 && str[len-1] == '\n')
		str[--len] = '\0';
}

static int is_whitelisted (char *remote)
{
	int found = 0;
	char buf[1024];
	FILE *f;

	/* verify remote is in whitelist */
	f = fopen(WHITELIST, "r");
	if (f == NULL)
		return -2;

	while (fgets(buf, sizeof(buf), f) != NULL) {
		chomp(buf);

		if (strcmp(remote, buf) == 0) {
			found = 1;
			break;
		}
	}

	fclose(f);

	return found;
}

static void add_whitelist (char *remote)
{
	FILE *f;
	int len;

	/* avoid duplicates */
	if (is_whitelisted(remote))
		return;

	/* whitelist remote */
	f = fopen(WHITELIST, "a");
	if (!f) {
		fprintf(stderr, "ERR: Could not open whitelist\n", remote);
		perror("fopen");
		return;
	}

	len = strlen(remote);

	if (write(fileno(f), remote, len) != len) {
		fprintf(stderr, "ERR: failed to write '%s' (%d) to %s (%d)\n",
				remote, len, WHITELIST, fileno(f));
		perror("write");
	}
	else
		write(fileno(f), "\n", 1);
	fflush(f);
	fclose(f);
}

static int do_ptr(char *host, char *remote)
{
	/* verify the query is for this backend */
	if (strcasecmp(host, SECRETREV) != 0)
		return -1;

	add_whitelist(remote);

	/* reply */
	printf("DATA\t%s\tIN\tPTR\t10\t1\t%s\n", SECRETREV, ZONE);

	return 0;
}

static int do_aaaa(char *host, char *remote)
{
	/* verify query is for this backend */
	if (strcasecmp(host, ZONE) != 0)
		return -1;

	/* reply */
	if (is_whitelisted(remote)) {
		printf("DATA\t%s\tIN\tAAAA\t10\t1\t%s\n", ZONE, SECRETIP);
		printf("DATA\t%s\tIN\tTXT\t10\t1\t%s\n", ZONE, SECRETTXT);
		return 0;
	} else {
		printf("DATA\t%s\tIN\tAAAA\t10\t1\t%s\n", ZONE, FAKEIP);
		printf("DATA\t%s\tIN\tTXT\t10\t1\t%s\n", ZONE, FAKETXT);
		return 1;
	}
}

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

	/* must be a Query */
	if (*p++ != 'Q' || !is_blank(*p++))
		return -1;

	/* save host */
	host = p;
	p = strchr(host, '\t');
	if (p == NULL)
		return -2;
	*p++ = '\0';

	/* must be type INternet */
	if (*p++ != 'I' || *p++ != 'N' || !is_blank(*p++))
		return -3;

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

	/* skip TTL */
	p = strchr(p, '\t');
	if (p == NULL)
		return -5;
	p++;

	/* save remote */
	remote = p;
	chomp(remote);

	if (strcmp(qtype, "PTR") == 0 || 
			(strcmp(qtype, "ANY") == 0 && strstr(host, "arpa")))
		return do_ptr(host, remote);
	else if (strcmp(qtype, "AAAA") == 0 || strcmp(qtype, "ANY") == 0)
		return do_aaaa(host, remote);
	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 secret pipe backend only supports protocol version 1, you said: %s\n", buf);
				return 1;
			}

			printf("OK\tsecret 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;
		}

	}

}
