#include "error.h"
#include "config.h"
#include "version.h"
#include "ldapdns.h"
#include "env.h"
#include "ip.h"
#include "dns.h"

#include "supervise.h"
#include "profile.h"

#include <stdio.h>
#include <sys/stat.h>

#ifndef LDAP_PORT
#define LDAP_PORT	389
#endif

static void inline add_peer_ns(dns_ctx *c, int inc);

static int default_refresh	= 10800;
static int default_retry	= 7200;
static int default_expire	= 604800;
static int default_minimum	= 86400;

static list_t other_threads = 0;
pthread_mutex_t handler_lock;
pthread_mutex_t log_lock;
pthread_mutex_t host_lock;
list_t host_lp;
config_t ldapdns;
ldap_ctx *ldap_thread;
dns_ctx *handler;

static int engine_message = 0;
static pthread_cond_t engine_pause_cond;
static pthread_mutex_t engine_message_mutex;
static pthread_t eph;

static void handle_messages(void)
{
	pthread_mutex_lock(&engine_message_mutex);
	switch (engine_message) {
	case 0: /* do nothing */
		break;
	case 1: /* paused */
		while (engine_message) {
			pthread_cond_wait(&engine_pause_cond,
					&engine_message_mutex);
			if (engine_message == 2)
				break;
		}
		if (engine_message == 0)
			break;
	case 2: /* please exit */
		pthread_mutex_unlock(&engine_message_mutex);
		pthread_exit(0);
		exit(0);
		break;
	};
	pthread_mutex_unlock(&engine_message_mutex);
}
static void kill_threads(void)
{
	ldap_ctx *o;
	pthread_t *x;
	int i;

#ifdef HAVE_pthread_kill_other_threads_np
	pthread_kill_other_threads_np();
	return;
#endif

	for (i = 0; i < ldapdns.ldap_threads; i++) {
		o = &ldap_thread[i];
		if (o->id != pthread_self())
			pthread_cancel(o->id);
	}
	while ((x = (pthread_t *)list_pop(&other_threads)) != 0) {
		pthread_cancel(*x);
		mem_free(x);
	}
}
static void handle_signal(int signo)
{
	if (pthread_self() != eph) {
		/* we are NOT the main thread */
		switch (signo) {
		case SIGTERM:
		case SIGINT:
			pthread_exit(0);
		};
		return;

	}

	switch (signo) {
	case SIGSTOP:
		if (engine_message == 0) {
			pthread_mutex_lock(&engine_message_mutex);
			engine_message = 1;
			pthread_mutex_unlock(&engine_message_mutex);
			pthread_mutex_lock(&log_lock);
			log(log_info, "pausing");
			pthread_mutex_unlock(&log_lock);
		}
		break;
	case SIGCONT:
		if (engine_message == 1) {
			pthread_mutex_lock(&engine_message_mutex);
			engine_message = 0;
			pthread_cond_broadcast(&engine_pause_cond);
			pthread_mutex_unlock(&engine_message_mutex);
			pthread_mutex_lock(&log_lock);
			log(log_info, "resuming");
			pthread_mutex_unlock(&log_lock);
		}
		break;
	case SIGTERM:
	case SIGINT:
		pthread_mutex_lock(&log_lock);
		log(log_info, "shutting down");
		pthread_mutex_unlock(&log_lock);
		pthread_mutex_lock(&engine_message_mutex);
		if (engine_message == 1) {
			pthread_cond_broadcast(&engine_pause_cond);
		}
		engine_message = 2;
		pthread_mutex_unlock(&engine_message_mutex);
		kill_threads();
		/* ask the parent to die nicely */
		if (getppid() != 1)
			kill(getppid(), SIGTERM);
		pthread_exit(0);
		exit(0);
		break;
	case SIGHUP:
		/* unused signals (for now) */
		break;
	};
}
static void inline cleanup_lists(dns_ctx *c, int ex)
{
	char *x;
#define cleanup_l(LL) if (c->LL) while ((x = list_pop(&c->LL))) mem_free(x)
	if (ex & 1) cleanup_l(subreq_tries);
	if (ex & 1) cleanup_l(subreq_done);
	if (ex & 2) {
		cleanup_l(NS);
		mem_free(c->search_base);
		c->search_base = 0;
		c->adlen = -1;
	}
	cleanup_l(DNSRecord);
	cleanup_l(A);
	cleanup_l(CNAME);
	cleanup_l(ADM);
	cleanup_l(MX);
	cleanup_l(SRV);
	cleanup_l(TXT);
	cleanup_l(PTR);
	cleanup_l(Generic);
#undef cleanup_l
}

static int start_ldap_connection(ldap_ctx *o, char *hostnamestr)
{
	int r;
	char *x;
	int port;
#ifdef LDAP_OPT_PROTOCOL_VERSION
	int version;
#endif

	x = strchr(hostnamestr, ':');
	if (!x || (*(x+1) && *(x+1) == '/')) {
		port = LDAP_PORT;
	} else {
		port = atoi(x+1);
		*x = 0;
	}

	o->message_sent = 0;
	o->message_wait = 0; // XXX
	if (ldap_is_ldap_url(hostnamestr)) {
		ldap_initialize(&o->ldap_con, hostnamestr);
	} else {
		o->ldap_con = ldap_init(hostnamestr, port);
		if (x) *x = ':'; /* egad */
	}

	if (!o->ldap_con)
		return -1;

#ifdef LDAP_OPT_PROTOCOL_VERSION
	o->protocol_version = version = 3;
	if (ldap_set_option(o->ldap_con, LDAP_OPT_PROTOCOL_VERSION, &version)
			!= LDAP_SUCCESS) {

		o->protocol_version = version = 2;
		if (ldap_set_option(o->ldap_con, LDAP_OPT_PROTOCOL_VERSION,
					&version) != LDAP_SUCCESS) {
			ldap_unbind(o->ldap_con);
			o->ldap_con = 0;
		}

		return -1;
	}
#else
	o->protocol_version = 2;
#endif

#ifdef ACCELERATE_CACHE
	if (ldapdns.accelerate_cache)
		ldap_enable_cache(o->ldap_con, ldapdns.accelerate_cache, 0);
#endif

	if (ldapdns.auth_mode == AUTH_MODE_SASL) {
		/* to be tested; prefer the DSA */
		r = ldap_bind_s(o->ldap_con,
				ldapdns.ldap_name,
				ldapdns.ldap_cred,
				LDAP_AUTH_KRBV42);
		if (r != LDAP_SUCCESS) {
			/* then try kerberos against LDAP */
			r = ldap_bind_s(o->ldap_con,
					ldapdns.ldap_name,
					ldapdns.ldap_cred,
					LDAP_AUTH_KRBV41);
		}
	} else if (ldapdns.auth_mode == AUTH_MODE_SIMPLE) {
		r = ldap_simple_bind_s(o->ldap_con,
				ldapdns.ldap_name,
				ldapdns.ldap_cred);
	} else if (ldapdns.auth_mode == AUTH_MODE_ANONYMOUS) {
		r = ldap_simple_bind_s(o->ldap_con, "", "");
	}

	if (r == LDAP_SUCCESS) {
		return 1;
	}

	/* no need to keep synchronous here */
	ldap_unbind(o->ldap_con);
	o->ldap_con = 0;
	return -1;
	
}
static void complete_phase(dns_ctx *c, int flag);
static void restart_ldap_connection(ldap_ctx *o)
{
	list_t lp;
	dns_ctx *x;

	pthread_mutex_lock(&handler_lock);
	x = handler;
	pthread_mutex_unlock(&handler_lock);
	for (; x; x = x->next) {
		if (x->c == o) {
			pthread_mutex_lock(&log_lock);
			warning("handler %d being closed (ldap went away)", x->n);
			pthread_mutex_unlock(&log_lock);

			complete_phase(x, '?');
			x->c = 0; /* give it up */
			x->phase = PHASE_IDLE;
		}
	}

	/* hopefully this will only be called by LDAP_SERVERDOWN */
	if (o->ldap_con)
		ldap_unbind(o->ldap_con);
	o->ldap_con = 0;

	pthread_mutex_lock(&log_lock);
	warning("handler %d was hung up on, restarting", o->n);
	pthread_mutex_unlock(&log_lock);

	pthread_mutex_lock(&host_lock);
top_restart_l:
	for (lp = host_lp; lp; lp = lp->next) {
		if (start_ldap_connection(o, lp->str) == 1)
			goto done_restart_l;
	}
	for (lp = ldapdns.hosts; lp; lp = lp->next) {
		if (start_ldap_connection(o, lp->str) == 1)
			goto done_restart_l;
	}
	goto top_restart_l;
done_restart_l:
	host_lp = lp;
	pthread_mutex_unlock(&host_lock);
}
static void complete_phase(dns_ctx *c, int flag)
{
	str_t out;
	char *q;
	char id[2];

	/* it is possible for these to be uninitialized here */
	if (c->response && caddr(c->response) && clen(c->response))
		tp_write(c);

	/* dynamic */
	if (c->axfr_base) mem_free(c->axfr_base);

	c->message_id = -1;
	c->phase = PHASE_IDLE;

	if (c->c) {
		pthread_mutex_lock(&c->c->load_lock);
		c->c->load--;
		pthread_mutex_unlock(&c->c->load_lock);
	}


	if (ldapdns.always_hangup || (flag != '+' && flag != '-')) {
		/* okay, something "weird" happened
		 * we want to signal a hangup on this hangle
		 */
		tp_close(c);
	}

	if (use_syslog) {
		/* encouragement: don't use syslog :) */
		return;
	}

	pthread_mutex_lock(&log_lock);
	if (c->request_name_alloc) {
		dns_to_name(out, c->request_name_alloc, 0);
		q = str(out);
	} else {
		q = "(unknown)";
	}
	if (c->response && c->response->buf)  {
		id[0] = c->response->buf[0];
		id[1] = c->response->buf[1];
	} else {
		id[0] = c->request_buf[0];
		id[1] = c->request_buf[1];
	}
	status("%02x%02x%02x%02x:%02x%02x:%02x%02x %c %02x%02x %s",
			(unsigned char)c->ip[0],
			(unsigned char)c->ip[1],
			(unsigned char)c->ip[2],
			(unsigned char)c->ip[3],
			(unsigned char)(c->port & 0xFF00) >> 8,
			(unsigned char)(c->port & 0x00FF),
			(unsigned char)id[0],
			(unsigned char)id[1],

			flag,

			(unsigned char)c->request_record[0],
			(unsigned char)c->request_record[1],
			q);

	if (c->request_name_alloc) mem_free(q);
	pthread_mutex_unlock(&log_lock);
}
static void do_zonesearch(dns_ctx *c, char *q);
static void try_subrequest(dns_ctx *c, char *trydomain)
{
	/* put it back into phase 1 with the subrequest flag set... */
	c->subreq++;
	c->subreq_in = c->subreq_in_alloc = trydomain;
	do_zonesearch(c, trydomain);
}
static void finish_subrequest(dns_ctx *c)
{
	char *q;
	list_t lp;

	/* nuke old A list */
	while ((q = list_pop(&c->A))) {
		mem_free(q);
	}
	/* nuke old PTR list */
	while ((q = list_pop(&c->PTR))) {
		mem_free(q);
	}
	/* nuke old Generic list */
	while ((q = list_pop(&c->Generic))) {
		mem_free(q);
	}

	c->subreq--;
	if (c->subreq_in_alloc) {
		mem_free(c->subreq_in_alloc);
		c->subreq_in = 0;
		c->subreq_in_alloc = 0;
	}

	if (c->subreq_tries) {
		for (;;) {
			q = list_pop(&c->subreq_tries);
			/* out of requests */
			if (!q) {
				complete_phase(c, '+');
				return;
			}

			/* make sure we haven't done this guy yet */
			for (lp = c->subreq_done; lp; lp = lp->next) {
				if (str_equali(lp->str, q)) {
					/* skip this round */
					mem_free(q);
					q = 0;
					break;
				}
			}
			/* got one? */
			if (q) {
				list_push(&c->subreq_done, str_dup(q));
				try_subrequest(c, q);
				return;
			}
		}
	} else
		complete_phase(c, '+');
}
static void do_axfrsearch(dns_ctx *c, char *q)
{
	const char *attrs[6] = {
				"mail",
				"nSRecord",
				"sOARecord",
				"modifyTimestamp"
	};
	str_t sa, sb;
	int r;

retry_axfr_search_top_l:
	if (ldapdns.dn_mode == DN_MODE_RFC1279
			|| ldapdns.dn_mode == DN_MODE_MSDNS) {
		attrs[4] = (const char *)"dNSRecord";
		attrs[5] = (const char *)0;
	} else if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {

		response_refuse(c);
		complete_phase(c, '-');
		return;
	} else {
		attrs[4] = (const char *)0;
	}

	/* q is valid: safe */
	dns_to_name(sa, q, 0);
	name_to_ldap(sb, str(sa)); /* calls str_init sb */
	if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
		str_cat(sb, ", ");
		str_cat(sb, ldapdns.ldap_suffix);
	}

	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			str(sb),		/* base */
			LDAP_SCOPE_BASE,	/* scope */
			"(objectClass=*)",	/* ldap filter */
			(char **)attrs,		/* attrs */
			0);			/* attrsonly */
	mem_free(str(sa));
	mem_free(str(sb));

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_axfr_search_top_l;
	}

	//printf("sent query for %d (was %d)\n", r, c->message_id);
	c->message_id = r;
	c->phase = PHASE_AXFRFIRST;

	c->c->message_sent++;
	//warning("axfrfirst %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait)
		pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
}
static void do_simple_search(dns_ctx *c, char *q)
{
	const char *attrs[12];
	str_t sb, sr;
	register int i, j;
	int r;

	str_init(sr);
	str_copy(sr, "(");
	str_cat(sr, c->request_attr);
	str_addch(sr, '=');
	for (i = 0; i < *q; i++) {
		j = q[i+1];
		if (!isdigit(((unsigned int)j))
				&& !isalpha(((unsigned int)j)) && j != '-') {
			/* not even going to try... */
			response_refuse(c);
			complete_phase(c, '-');
			mem_free(str(sr));
			return;
		}
		str_addch(sr, j);
	}
	str_addch(sr, ')');

retry_simple_search_top_l:
	attrs[0] = (const char *)"aRecord";
	attrs[1] = (const char *)"mXRecord";
	attrs[2] = (const char *)"cNAMERecord";
	attrs[3] = (const char *)"seeAlso";
	attrs[4] = (const char *)"description";
	attrs[5] = (const char *)"photo";
	attrs[6] = (const char *)"mail";
	attrs[7] = (const char *)"nSRecord";
	attrs[8] = (const char *)"sOARecord";
	attrs[9] = (const char *)"modifyTimestamp";

	if (ldapdns.dn_mode == DN_MODE_RFC1279
			|| ldapdns.dn_mode == DN_MODE_MSDNS) {
		attrs[10] = (const char *)"dNSRecord";
		attrs[11] = (const char *)0;
	} else {
		attrs[10] = (const char *)0;
	}

	/* q is valid: safe */
	str_init(sb);
	if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
		str_cat(sb, ldapdns.ldap_suffix);
	}

	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			str(sb),		/* base */
			LDAP_SCOPE_ONELEVEL,	/* scope */
			str(sr),		/* ldap filter */
			(char **)attrs,		/* attrs */
			0);			/* attrsonly */
	mem_free(str(sr));
	mem_free(str(sb));

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_simple_search_top_l;
	}

	//printf("sent query for %d (was %d)\n", r, c->message_id);
	c->message_id = r;
	c->phase = PHASE_SIMPLESEARCH;

	c->c->message_sent++;
	//warning("simplesearch %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait)
		pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
}
static void do_attrsearch(dns_ctx *c, char *q, int wild)
{
	const char *attrs[10];
	str_t sa, sb;
	list_t lp;
	int r;

retry_attr_search_top_l:
	if (!q || !*q) {
		if (c->subreq) {
			finish_subrequest(c);
		} else {
			response_nxdomain(c);
			complete_phase(c, '-');
		}
		return;
	}

	attrs[0] = (const char *)"aRecord";
	attrs[1] = (const char *)"mXRecord";
	attrs[2] = (const char *)"cNAMERecord";
	attrs[3] = (const char *)"seeAlso";
	attrs[4] = (const char *)"description";
	attrs[5] = (const char *)"photo";
	attrs[6] = (const char *)"mail";
	attrs[7] = (const char *)"modifyTimestamp";

	if (ldapdns.dn_mode == DN_MODE_RFC1279
			|| ldapdns.dn_mode == DN_MODE_MSDNS) {
		attrs[8] = (const char *)"dNSRecord";
		attrs[9] = (const char *)0;
	} else {
		attrs[8] = (const char *)0;
	}

	dns_to_name(sa, q, 0);
	if (wild) {
		/* wildcard search */
		str_init(sb);
		str_copy(sb, "*.");
		str_cat(sb, str(sa));
		str_copy(sa, str(sb));
		mem_free(str(sb));
	}

	if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
		//printf("sa=%d, ad=%d\n", str_len(sa), c->adlen);
		if (str_len(sa) <= c->adlen) {
			str_init(sb);
			str_copy(sb, c->search_base);
		} else {
			str(sa)[str_len(sa) - c->adlen] = 0;
			name_to_ldap(sb, str(sa)); /* calls str_init sb */
			str_cat(sb, ", ");
			str_cat(sb, c->search_base);
		}
	} else {
		/* q is valid: safe */
		name_to_ldap(sb, str(sa)); /* calls str_init sb */
		if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
			str_cat(sb, ", ");
			str_cat(sb, ldapdns.ldap_suffix);
		}
	}

	// printf("X2 searching (%p) [%s]\n", c, str(sb));
	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			str(sb),		/* base */
			LDAP_SCOPE_BASE,	/* scope */
			"(objectClass=*)",	/* ldap filter */
			(char **)attrs,		/* attrs */
			0);			/* attrsonly */
	mem_free(str(sa));
	mem_free(str(sb));

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_attr_search_top_l;
	}

	//printf("attr: sent query for %d (was %d)\n", r, c->message_id);
	c->message_id = r;
	c->phase = PHASE_ATTRSEARCH;

	c->c->message_sent++;
	//warning("attrsearch %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait)
		pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
}
static void do_zonesearch(dns_ctx *c, char *q)
{
	const char *attrs[5];
	str_t sa, sb;
	char *base;
	char *filter;
	int r;

	if (c->search_base) {
		mem_free(c->search_base);
		c->search_base = 0;
	}
	c->adlen = -1;

retry_zone_search_top_l:
	if (!q || !*q) {
		if (c->subreq) {
			finish_subrequest(c);
		} else {
			response_nxdomain(c);
			complete_phase(c, '-');
		}
		return;
	}

	attrs[0] = (const char *)"nSRecord";
	attrs[1] = (const char *)"sOARecord";
	attrs[2] = (const char *)"modifyTimestamp";
	if (ldapdns.dn_mode == DN_MODE_RFC1279
			|| ldapdns.dn_mode == DN_MODE_MSDNS) {
		attrs[3] = (const char *)"dNSRecord";
		attrs[4] = (const char *)0;
	} else if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
		attrs[3] = (const char *)"associatedDomain";
		attrs[4] = (const char *)0;
	} else {
		attrs[3] = (const char *)0;
	}

	/* q is valid: safe */
	dns_to_name(sa, q, 0);
	if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
		str_init(sb);
		str_copy(sb, "(|(associatedDomain=");
		str_cat(sb, str(sa));
		for (r = 0; str(sa)[r]; r++) {
			if (str(sa)[r] == '.') {
				while (str(sa)[r] == '.') r++;
				str_cat(sb, ")(associatedDomain=");
				str_cat(sb, str(sa)+r);
			}
		}
		str_cat(sb, "))");
		filter = str(sb);

		if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
			base = ldapdns.ldap_suffix;
		} else {
			base = "";
		}
	} else {
		name_to_ldap(sb, str(sa)); /* calls str_init */
		if (! str(sb) || ! str(sb)[0]) {
			mem_free(str(sa));
			mem_free(str(sb));
	
			/* hrm... we're empty... */
			if (c->subreq) {
				finish_subrequest(c);
			} else {
				response_refuse(c);
				complete_phase(c, '-');
			}
			return;
		}


		if (ldapdns.dn_mode == DN_MODE_MSDNS) {
			/* msdns is like dc=@, dc=domain for this phase */
			str_copy(sa, "dc=@, ");
			str_cat(sa, str(sb));
			str_copy(sb, str(sa));
		}
		if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
			str_cat(sb, ", ");
			str_cat(sb, ldapdns.ldap_suffix);
		}
		base = str(sb);
		filter = "(objectClass=*)";
	}

	//printf("searching (%p) [%s]\n", c, str(sb));
	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			base,			/* base */
ldapdns.dn_mode == DN_MODE_LDAPDNS ? LDAP_SCOPE_SUBTREE : LDAP_SCOPE_BASE,
			filter,			/* ldap filter */
			(char **)attrs,		/* attrs */
			0);			/* attrsonly */
	mem_free(str(sa));
	mem_free(str(sb));

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_zone_search_top_l;
	}

	c->message_id = r;
	c->phase = PHASE_ZONESEARCH;

	c->c->message_sent++;
	//warning("zonesearch %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait)
		pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
}

static void engine_dns_answer_notify(dns_ctx *c, char header[12])
{
	char *q, qtype[2], qclass[2];
	str_t d;
	int r, pid, status;

	/* query */
	q = 0;
	if (!dns_packet_getname(c, &q)) goto NOQ;
	if (!dns_packet_copy(c, qtype, 2)) goto NOQ;
	if (!dns_packet_copy(c, qclass, 2)) goto NOQ;
	if (!q) goto NOQ; /* offensive */

	/* setup notify response */
	if (!response_notify(c, q, qtype, qclass)) goto NOQ;
	response_id(c, header);

	if (qclass[0] == DNS_C_IN[0] && qclass[1] == DNS_C_IN[1]) {
		c->response->buf[2] |= 4;
	} else if (qclass[0] != DNS_C_ANY[0] || qclass[1] != DNS_C_ANY[1]) {
		goto WEIRDCLASS;
	}
	if (qtype[0] != DNS_T_SOA[0] && qtype[1] != DNS_T_SOA[1]) {
		/* okie... */
		goto NOTIMP;
	}

	/* handle axfr processing on domain (q) */
	dns_to_name(d, q, 0);

	/* double fork */
	pid = fork();
	if (pid == 0) {
		pid = fork();
		if (pid == 0) {
			/* run notify tool */
			char ip[64];
			/* Clib */
			/* IPv4 */
			sprintf(ip, "%d.%d.%d.%d", c->ip[0],
					c->ip[1], c->ip[2], c->ip[3]);
			env_put("REMOTE_ADDR", ip);
			execlp(ldapdns.notify, ldapdns.notify, str(d), 0);
			_exit(127);
		}
		_exit(pid == -1 ? 127 : 0);
	} else if (pid == -1) {
		mem_free(str(d));
		goto SERVFAIL;
	}
	/* wait for first child (almost immediately) */
	while ((r = wait(&status)) != pid && r != -1);
	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
		mem_free(str(d));
		goto SERVFAIL;
	}

	/* respond success */
	mem_free(str(d));
	complete_phase(c, '+');
	return;
SERVFAIL:
	response_rcode(c, DNS_R_SERVFAIL);
	complete_phase(c, 'S');
	return;
NOTIMP:
	response_rcode(c, DNS_R_NOTIMP);
	complete_phase(c, 'I');
	return;
WEIRDCLASS:
	response_rcode(c, DNS_R_FORMERR);
	complete_phase(c, 'C');
	return;
NOQ:
	complete_phase(c, '/');
	return;
}
static int translate_netbios(dns_ctx *c, char *q)
{
	register int i, j;

	/* convert in place if it makes sense */
	if (q[0] != 32) {
		warning("i'm configured to do NETBIOS but this packet isn't right");
		return 0;
	}

	/* convert to ascii :) */
	for (i = j = 1; i < 33; i++, j++, j++) {
		q[i] = ((q[j] - 0x41) << 4) | ((q[j+1] - 0x41));
	}
	q[0] = 16; q[17] = 0;

	/* spaces are nulls */
	for (i = 1; i < 17; i++) {
		if (q[i] == 0x20 || q[i] == 0) {
			q[i+1] = 0;
			q[0] = i;
			break;
		}
	}
	/* slide over the rest of the query */
	for (j = 33; q[j]; i++, j++) {
		q[i] = q[j];
	}
	return 1;
}
static void engine_dns_answer_query(dns_ctx *c, char header[12])
{
	char *q, *x, *y, qtype[2], qclass[2];

	/* query */
	q = 0;
	if (!dns_packet_getname(c, &q)) goto NOQ;
	if (!dns_packet_copy(c, qtype, 2)) goto NOQ;
	if (!dns_packet_copy(c, qclass, 2)) goto NOQ;
	if (!q) goto NOQ; /* offensive */

	if (c->request_name_alloc) {
		mem_free(c->request_name_alloc);
		c->request_name_alloc = 0;
	}

	/* if this is a netbios request, do funny things */
	if (ldapdns.netbios
			/* these numbers are magic to NBT */
			&& qtype[0] == 0
				&& (qtype[1] == 0x20 || qtype[1] == 0x21)
			&& qclass[0] == 0 && qclass[1] == 0x01) {
		if (!translate_netbios(c, q))
			goto WEIRDCLASS;

		/* mark this lookup as netbios */
		c->protnum = PROT_NETBIOS;
	} else
		c->protnum = PROT_DNS;

	/* set default SOA */
	c->refresh = default_refresh;
	c->retry = default_retry;
	c->expire = default_expire;
	c->minimum = default_minimum;
	c->ttl = default_minimum;

	c->subreq = 0;
	c->subreq_valid = 0;
	c->request_name_zone = 0;
	c->subreq_in = c->subreq_in_alloc = 0;
	c->wantdie = 0;
	c->search_base = 0;
	c->adlen = -1;

	cleanup_lists(c, 3);
	while ((x = list_pop(&c->ns))) {
		mem_free(x);
	}
	c->ns = 0;

	if (!response_query(c, q, qtype, qclass)) goto NOQ;
	response_id(c, header);
	if (qclass[0] == DNS_C_IN[0] && qclass[1] == DNS_C_IN[1]) {
		c->response->buf[2] |= 4;
	} else if (qclass[0] != DNS_C_ANY[0] || qclass[1] != DNS_C_ANY[1]) {
		goto WEIRDCLASS;
	}

	c->response->buf[3] &= ~128;

	/* QR flag: set response */
	if (!(header[2] & 1)) c->response->buf[2] &= ~1;

	/* lowercase domainname */
	dns_domain_lower(q);

	/* fill this up */
	c->request_name_alloc = c->request_name = q;
	c->request_record[0] = qtype[0];
	c->request_record[1] = qtype[1];

	/* cache? */
#if 0
	if (ldapdns.roots && header[2] & 1) {
		/* recursion desired */
		/* XXX: cache hooks start here */
		return;
	}
#endif
	/* try to retarget this */
	if (*q) {
		y = q + (*q)+1;
		x = ht_fetch(&ldapdns.search, y, __str_clen(y));
		if (x) {
			/* simple rewritten search */
			c->request_attr = x;
			do_simple_search(c, q);
			return;
		}
	}

	if ((qtype[0] == DNS_T_AXFR[0] && qtype[1] == DNS_T_AXFR[1])
	|| (qtype[0] == DNS_T_IXFR[0] && qtype[1] == DNS_T_IXFR[1])) {
		if (!c->axfr_base) {
			/* no AXFR allowed on this session */
			warning("no AXFR base");
			goto NOTIMP;
		}
		if (c->axfr_base[0] == '\0') {
			/* anywhere: single dot */
			do_axfrsearch(c, q);
			return;
		}

		/* q MUST be a valid dns domain */
		for (x = q; *x; x += ((*x) + 1)) {
			if (str_equali(x, c->axfr_base)) {
				/* okay! this is nice */
				do_axfrsearch(c, q);
				return;
			}
		}
		goto NOTIMP;
	}

	/* zone search */
	do_zonesearch(c, q);
	return;
NOTIMP:
	response_rcode(c, DNS_R_NOTIMP);
	complete_phase(c, 'I');
	return;
WEIRDCLASS:
	response_rcode(c, DNS_R_FORMERR);
	complete_phase(c, 'C');
	return;
NOQ:
	complete_phase(c, '/');
	return;
}
static int do_update_section(dns_ctx *c, char *d, char header[12], int section, list_t *p)
{
	char *x, xtype[2], xclass[2], xttl[4];
	unsigned short n, m;
	bin_t r;
	int i, j;

	/* read off sections */
	/* Clib */
	memcpy(&n, header + section, 2); n = ntohs(n);
	for (i = 0; i < n; i++) {
		x = 0;
		if (!(j=dns_packet_getname(c, &x))) return 0;
		if (!dns_packet_copy(c, xtype, 2)) return 0;
		if (!dns_packet_copy(c, xclass, 2)) return 0;
		if (!dns_packet_copy(c, xttl, 4)) return 0;
		if (!dns_packet_copy(c, (char *)&m, sizeof(m))) return 0;
		m = ntohs(m); /* m = length of packet; Clib */

		bin_init(r);
		bin_copy(r, xclass, 2);
		bin_cat(r, xtype, 2);
		bin_cat(r, xttl, 2);
		bin_cat(r, x, __str_clen(x));
		bin_cat(r, d, __str_clen(d)+1);/* also get \0 */
		mem_free(x);
		bin_cat(r, (char *)&m, sizeof(m)); /* length of rrdata */
		if (m > 0) {
			j = clen(r);
			bin_needplus(r, m);
			if (!dns_packet_copy(c, caddr(r)+j, m)) return 0;
		}

		list_push(p, caddr(r));
	}
	return 1;
}
static int inline handle_generic_compare(dns_ctx *c, const unsigned char *p,
		const unsigned char *pdata, unsigned int plen) {
	unsigned short glen;
	int nl;

	glen = *(unsigned short *)(p+2);
	p += 4;
	while (glen > 0) {
		if (*p == 0xFF) {
			glen--; p++;
			if (glen <= 0) {
				return 0;
			}
			if (*p == 0x00) {
				/* expecting a \0377 */
				if (*pdata != 0xFF) {
					return 0;
				}
				pdata++; plen--;
				glen--; p++;
			} else {
				/* dns-encoded name */
				for (;;) {
					if (*pdata != *p)
						return 0;
					if (*p == 0) {
						glen--; p++;
						pdata++; plen--;
						break;
					}
					if (glen <= 0)
						return 0;
					nl = ((*p)+1);
					pdata += nl; plen -= nl;
					p += nl; glen -= nl;
				}
			}
		} else if (*p == *pdata) {
			glen--; p++;
			pdata++; plen--;
		} else {
			return 0;
		}
	}
	return 1;
}
static int rrset_test(dns_ctx *c, char *pdata)
{
	char *xtype = pdata;
	char *domain, *q;
	unsigned short n;
	list_t *r;
	int offset, i;

	pdata++;pdata++; /* TTL */
	pdata++;pdata++; /* domain */
	domain = pdata;
	while (*pdata) pdata++; pdata++;
	memcpy(&n, pdata, sizeof(n)); /* length of rrdata */
	pdata++; pdata++;

	/* Generic test first */
	if (c->Generic) {
		/* Generic first; should handle compression ! */
		while ((q = list_pop(&c->Generic))) {
			if (q[0] == xtype[0] && q[1] == xtype[1]) {
				i = handle_generic_compare(c, q, pdata, n);
				if (i != -1)
					return i;
			} else
				mem_free(q);
		}
	}

	offset = 0;
	if (xtype[0] == DNS_T_A[0] && xtype[1] == DNS_T_A[1]) {
		/* A record test */
		if (c->A) {
			if (n == 0) return 1;
			if (n != 4) return 0;
			while ((q = list_pop(&c->A))) {
				/* Clib */
				if (memcmp(q, pdata, 4) == 0) {
					mem_free(q);
					return 1;
				}
				mem_free(q);
			}
		}
		return 0;
	} else if (xtype[0] == DNS_T_MX[0] && xtype[1] == DNS_T_MX[1]) {
		/* ignore preference (it's easier) */
		offset = 2;
		r = &c->MX;
	} else if (xtype[0] == DNS_T_CNAME[0] && xtype[1] == DNS_T_CNAME[1]) {
		r = &c->CNAME;
	} else if (xtype[0] == DNS_T_TXT[0] && xtype[1] == DNS_T_TXT[1]) {
		r = &c->TXT;
	} else if (xtype[0] == DNS_T_PTR[0] && xtype[1] == DNS_T_PTR[1]) {
		r = &c->PTR;
	} else {
		return 0;
	}

	if (!*r) return 0;
	if (n == 0) return 1;
	while ((q = list_pop(r))) {
		for (i = offset; i < n; i++) {
			if (q[i] != pdata[i])
				break;
		}
		mem_free(q);
		if (i == n)
			return 1;
	}
	return 0;
}
static void start_next_update_operation(dns_ctx *c)
{
	str_t s;
	char *p,*d;
	int r;

next_l:
	p = list_pop(&c->sec_update);
	if (!p) {
		/* all done? */
		response_rcode(c, DNS_R_NOERROR);
		complete_phase(c, '+');
		return;
	}

	d = p+6;
	r = -1;
	dns_to_name(s, d, 0);
	/* XXX this will actually do the work */
	if (p[0] == DNS_C_ANY[0] && p[1] == DNS_C_ANY[1]) {
		if (p[2] == DNS_T_ANY[0] && p[2] == DNS_T_ANY[1]) {
			/* delete all rrs from a name */
			warning("NS-UPDATE: Delete all RR's from: %s", str(s));
		} else {
			/* delete all of an rr */
			warning("NS-UPDATE: Delete all RR of a type from: %s", str(s));
		}
	} else if (p[0] == DNS_C_NONE[0] && p[1] == DNS_C_NONE[1]) {
		/* delete rr from rrset */
		if (p[2] == DNS_T_ANY[0] && p[2] == DNS_T_ANY[1]) {
			response_rcode(c, DNS_R_FORMERR);
			complete_phase(c, 'C');
			return;
		}
		warning("NS-UPDATE: Delete an RR in an rrset from: %s", str(s));
	} else {
		/* add rrset */
		warning("NS-UPDATE: Add an RR to: %s", str(s));
	}
	if (r == -1) goto next_l;
}
static void ldapdns_process_update(dns_ctx *c)
{
	int err_code;
	int exists;
	str_t sa, sb;
	char *p;
	int r;

	if (!c->message) {
		goto start_next_l;
	}

	//printf("phase1\n");
	ldap_parse_result(c->c->ldap_con, c->message,
			&err_code,
			0,0,0,0,0);
	if (err_code != LDAP_SUCCESS) {
		if (err_code == LDAP_SERVER_DOWN) {
			pthread_mutex_lock(&c->c->lock);
			restart_ldap_connection(c->c);
			pthread_mutex_unlock(&c->c->lock);
			/* we need to resend query */
			c->request_name = c->request_name_alloc;
			do_zonesearch(c, c->request_name_alloc);
			return;
		} else if (err_code == LDAP_TIMEOUT) {
			/*response_refuse(c);*/
			complete_phase(c, '?');
			return;
		} else if (err_code == LDAP_NO_SUCH_OBJECT
		|| err_code == LDAP_LOOP_DETECT
		|| err_code == LDAP_UNWILLING_TO_PERFORM
		|| err_code == LDAP_ALIAS_PROBLEM
		|| err_code == LDAP_INVALID_SYNTAX
		|| err_code == LDAP_INAPPROPRIATE_MATCHING
		|| err_code == LDAP_NO_SUCH_ATTRIBUTE) {
			/* okay did not exist... */
			exists = 0;
		} else
			fatal("ldap_result (nsupdate): %s", ldap_err2string(err_code));
	} else {
		exists = 1;

		/* we have results... process SOA/NS */
		ldap_load_dns_attributes(c, 0, 1);
	}
	p = list_pop(&c->sec_prereq);
	if (p) {
		/* test on exists */
		if (p[0] == DNS_C_NONE[0] && p[1] == DNS_C_NONE[1]) {
			if (exists) {
				if (p[2] == DNS_T_ANY[0] && p[3] == DNS_T_ANY[1]) {
					/* nxdomain */
					response_rcode(c, DNS_R_NXDOMAIN);
					response_nxdomain(c);
					complete_phase(c, 'N');
					return;
				} else if (rrset_test(c, p+2)) {
					/* nxrrset */
					response_rcode(c, DNS_R_NXRRSET);
					response_nxdomain(c);
					complete_phase(c, 'n');
					return;
				}
			}
			/* succeeded */
		} else if ((p[0] == DNS_C_IN[0] && p[1] == DNS_C_IN[1])
				|| (p[0] == DNS_C_ANY[0] && p[1] == DNS_C_ANY[1])) {
			if (!exists) {
				/* nxdomain */
				response_rcode(c, DNS_R_YXDOMAIN);
				response_nxdomain(c);
				complete_phase(c, 'Y');
				return;
			}
			if (p[2] != DNS_T_ANY[0] && p[3] != DNS_T_ANY[1]) {
				if (!rrset_test(c, p+2)) {
					/* yxrrset */
					response_rcode(c, DNS_R_YXRRSET);
					response_nxdomain(c);
					complete_phase(c, 'y');
					return;
				}
			}
		}
	}
	
start_next_l:
	cleanup_lists(c, 3); /* keep NS data (if we got any) */

	if (c->sec_prereq && c->sec_prereq->str) {
		p = c->sec_prereq->str + 6;
	} else if (c->sec_update && c->sec_update->str) {
		start_next_update_operation(c);
		return;
	} else {
		/* all done? */
		response_rcode(c, DNS_R_NOERROR);
		complete_phase(c, '+');
		return;
	}

retry_nsupdate_top_l:
	/* start request on domain name p */
	/* q is valid: safe */
	dns_to_name(sa, p, 0);
	name_to_ldap(sb, str(sa)); /* calls str_init sb */
	if (ldapdns.ldap_suffix && *ldapdns.ldap_suffix) {
		str_cat(sb, ", ");
		str_cat(sb, ldapdns.ldap_suffix);
	}

	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			str(sb),		/* base */
			LDAP_SCOPE_BASE,	/* scope */
			"(objectClass=*)",	/* ldap filter */
			NULL,			/* all attributes */
			0);			/* attrsonly */
	mem_free(str(sa));
	mem_free(str(sb));

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_nsupdate_top_l;
	}

	//printf("sent query for %d (was %d)\n", r, c->message_id);
	c->message_id = r;
	c->phase = PHASE_NSUPDATE;

	c->c->message_sent++;
	//warning("nsupdate %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait) pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
}
static void engine_dns_answer_update(dns_ctx *c, int op, char header[12])
{
	char *q, qtype[2], qclass[2];
	char *z, ztype[2], zclass[2], zttl[4], zrd[2], zip[4];
	bin_t b;
	int i;

	/* flush this */
	while (list_pop(&c->sec_prereq));
	while (list_pop(&c->sec_update));

	/* query */
	q = 0;
	if (!dns_packet_getname(c, &q)) goto NOQ;
	if (!dns_packet_copy(c, qtype, 2)) goto NOQ;
	if (!dns_packet_copy(c, qclass, 2)) goto NOQ;
	if (!q) goto NOQ; /* offensive */

	if (c->request_name_alloc) {
		mem_free(c->request_name_alloc);
		c->request_name_alloc = 0;
	}
	c->request_name_alloc = q;

	/* in netbios mode, the sections are faked
	 * we verify that the address being added/removed
	 * is the one the client is on
	 *
	 * NB NAME REGISTRATION encodes data in the question+additional parts
	 * NS UPDATE encodes data in the answer+authority parts
	 *
	 * kind of sick, yeah?
	 */
	if (ldapdns.netbios
			/* these numbers are magic to NBT :) */
			&& qtype[0] == 0
				&& (qtype[1] == 0x20 || qtype[1] == 0x21)
			&& qclass[0] == 0 && qclass[1] == 0x01) {
		/* we need to fast-forward to additional section
		 *
		 * skip len at position
		 *
		 */
		z = 0;
		if (!dns_packet_getname(c, &z)) goto NOQ;
		if (!z) goto NOQ;
		if (str_diff(z, q)) goto NOQ;
		if (!dns_packet_copy(c, ztype, 2)) goto NOQ;
		if (!dns_packet_copy(c, zclass, 2)) goto NOQ;

		if (ztype[0] != qtype[0]) goto NOQ;
		if (ztype[1] != qtype[1]) goto NOQ;
		if (zclass[0] != qclass[0]) goto NOQ;
		if (zclass[1] != qclass[1]) goto NOQ;

		if (!dns_packet_copy(c, zttl, 4)) goto NOQ;
		if (!dns_packet_copy(c, zrd, 2)) goto NOQ;
		if (zrd[0] != 0 && zrd[1] != 6) goto NOQ;

		/* load the flags */
		if (!dns_packet_copy(c, zrd, 2)) goto NOQ;

		/* requested node IP */
		if (!dns_packet_copy(c, zip, 4)) goto NOQ;

		/* standard protection: TODO make this possible from gateway */
		if (zip[0] != c->ip[0]) goto NOQ;
		if (zip[1] != c->ip[1]) goto NOQ;
		if (zip[2] != c->ip[2]) goto NOQ;
		if (zip[3] != c->ip[3]) goto NOQ;

		/* translate the name */
		if (!translate_netbios(c, q))
			goto NOQ;

		/* *-* translate the sections *-* */

		if (op == DNS_O_RELEASE) {
			/* UPDATE: NB record */
			bin_init(b);
			bin_addch(b, DNS_C_NONE[0]);
			bin_addch(b, DNS_C_NONE[1]);
			bin_addch(b, DNS_T_NB[0]);
			bin_addch(b, DNS_T_NB[1]);
			bin_addch(b, 0);bin_addch(b, 6);
			bin_addch(b, zrd[0]);
			bin_addch(b, zrd[1]);
			bin_cat(b, zip, 4);
			list_push(&c->sec_update, caddr(b));
	
			/* UPDATE: A record */
			bin_init(b);
			bin_addch(b, DNS_C_NONE[0]);
			bin_addch(b, DNS_C_NONE[1]);
			bin_addch(b, DNS_T_A[0]);
			bin_addch(b, DNS_T_A[1]);
			bin_addch(b, 0);bin_addch(b, 4);
			bin_cat(b, zip, 4);
			list_push(&c->sec_update, caddr(b));
		} else {
			if (op == DNS_O_UPDATE) {
				/* NXRRSET; address record */
				bin_init(b);
				bin_addch(b, DNS_C_NONE[0]);
				bin_addch(b, DNS_C_NONE[1]);
				bin_addch(b, DNS_T_A[0]);
				bin_addch(b, DNS_T_A[1]);
				bin_addch(b, 0);bin_addch(b, 4);
				bin_cat(b, c->ip, 4);
				list_push(&c->sec_update, caddr(b));

				/* NXRRSET: NB record */
				bin_init(b);
				bin_addch(b, DNS_C_NONE[0]);
				bin_addch(b, DNS_C_NONE[1]);
				bin_addch(b, DNS_T_NB[0]);
				bin_addch(b, DNS_T_NB[1]);
				bin_addch(b, 0);bin_addch(b, 6);
				bin_addch(b, '\x00');	/* NB mode explicitly ignores this */
				bin_addch(b, '\x00');
				bin_cat(b, zip, 4);
				list_push(&c->sec_update, caddr(b));
			}

			/* UPDATE: NB record */
			bin_init(b);
			bin_addch(b, DNS_C_IN[0]);
			bin_addch(b, DNS_C_IN[1]);
			bin_addch(b, DNS_T_NB[0]);
			bin_addch(b, DNS_T_NB[1]);
			bin_addch(b, 0);bin_addch(b, 6);
			bin_addch(b, zrd[0]);
			bin_addch(b, zrd[1]);
			bin_cat(b, zip, 4);
			list_push(&c->sec_update, caddr(b));
	
			/* UPDATE: A record */
			bin_init(b);
			bin_addch(b, DNS_C_IN[0]);
			bin_addch(b, DNS_C_IN[1]);
			bin_addch(b, DNS_T_A[0]);
			bin_addch(b, DNS_T_A[1]);
			bin_addch(b, 0);bin_addch(b, 4);
			bin_cat(b, zip, 4);
			list_push(&c->sec_update, caddr(b));
		}
	
		/* mark this lookup as netbios */
		c->protnum = PROT_NETBIOS;
	} else {
		c->protnum = PROT_DNS;

		/* load sections */
		if (!do_update_section(c, q, header,
				REQUEST_PRE, &c->sec_prereq)) goto NOQ;
		if (!do_update_section(c, q, header,
				REQUEST_UPDATE, &c->sec_update)) goto NOQ;

		/* reverse these... (order MAY be important...) */
		list_reverse(&c->sec_prereq);
		list_reverse(&c->sec_update);
	}

	/* copy response... */
	c->response->used = 0;
	response_addbytes(c, header, 12);
	/* turn off question bit */
	c->response->buf[2] &= ~1;
	c->response->buf[2] |= 128;
	c->response->buf[2] &= ~(0x0F<<3);
	c->response->buf[2] |= 5<<3;
	c->response->buf[3] = 0;
	for (i = 4; i < 12; i++) c->response->buf[i] = 0;
	c->response->used = 12;
	response_id(c, header);

	/* okay, for each entry, start the appropriate ldap query */
	c->message = 0;
	c->phase = PHASE_NSUPDATE;
	ldapdns_process_update(c); /* this also starts up the request */
	return;
NOQ:
	c->response->used = 0;
	complete_phase(c, '/');
	return;
}
static void engine_dns_answer(dns_ctx *c, time_t now)
{
	/* first, we need to setup/parse the headers of the dns request */
	char header[12];
	int op;

	/* default serial is always */

	c->serial = now;
	c->soahack = 0;

	/* copy the header and make sure this is infact a question */
	if (!dns_packet_copy(c, header, 12)) goto NOQ;
	if (header[2] & 128) goto NOQ; /* response flag */

	/* do something with the opcode? */
	op = (header[2] >> 3) & 0xF;
	switch (op) {
	case DNS_O_QUERY:
		/* query; there must be exactly 1 query */
		if (header[4]) goto NOQ;
		if (header[5] != 1) goto NOQ;
		engine_dns_answer_query(c, header);
		return;
	case DNS_O_UPDATE:
	case DNS_O_RELEASE:
		if (!ldapdns.update)
			goto NOTIMP;
		/* only one zone allowed (duh) */
		if (header[4]) goto NOQ;
		if (header[5] != 1) goto NOQ;
		engine_dns_answer_update(c, op, header);
		return;
	case DNS_O_NOTIFY:
		if (!ldapdns.notify)
			goto NOTIMP;
		/* do we have a notify helper-program? */
		if (header[4]) goto NOQ;
		if (header[5] != 1) goto NOQ;
		engine_dns_answer_notify(c, header);
		return;

	case DNS_O_IQUERY:
	case DNS_O_STATUS:
		goto NOTIMP;
	};
NOTIMP:
	/* copy the entire query */
	c->response->used = 0;
	response_addbytes(c, c->request_buf, c->request_len);
	/* turn off question bit */
	c->response->buf[2] &= ~1;
	c->response->buf[3] &= ~128;
	response_rcode(c, DNS_R_NOTIMP);
	complete_phase(c, 'I');
	return;
NOQ:
	complete_phase(c, '/');
	return;
}
static void ldapdns_process_zonesearch(dns_ctx *c)
{
	int err_code;
	char *x;

	list_t saved_NS;
	unsigned long saved_soa[6];
	int saved_wantdie;
	int saved_adlen;

	/* process NS/SOA search results; possibly rerun */
	if (!c->message) {
		/* odd... no message waiting? */
		fatal("assertion: zonesearch processing without message!");
	}

	//printf("phase1\n");
	ldap_parse_result(c->c->ldap_con, c->message,
			&err_code,
			0,0,0,0,0);
	if (err_code != LDAP_SUCCESS) {
		if (err_code == LDAP_SERVER_DOWN) {
			pthread_mutex_lock(&c->c->lock);
			restart_ldap_connection(c->c);
			pthread_mutex_unlock(&c->c->lock);
			/* we need to resend query */
			c->request_name = c->request_name_alloc;
			do_zonesearch(c, c->request_name_alloc);
			return;
		} else if (err_code == LDAP_TIMEOUT) {
			/*response_refuse(c);*/
			complete_phase(c, '?');
			return;
		} else if (err_code == LDAP_NO_SUCH_OBJECT
		|| err_code == LDAP_LOOP_DETECT
		|| err_code == LDAP_UNWILLING_TO_PERFORM
		|| err_code == LDAP_ALIAS_PROBLEM
		|| err_code == LDAP_INVALID_SYNTAX
		|| err_code == LDAP_INAPPROPRIATE_MATCHING
		|| err_code == LDAP_NO_SUCH_ATTRIBUTE) {
			/* doesn't exist... let's chop the base up */
			//printf("shoten\n");

			if (c->subreq) {
				/* skip the first dot and try again */
				if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
					finish_subrequest(c);
				} else {
					c->subreq_in += ((*c->subreq_in)+1);
					do_zonesearch(c, c->subreq_in);
				}
			} else if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
				response_nxdomain(c);
				complete_phase(c, '-');
			} else {
				c->request_name += ((*c->request_name)+1);
				do_zonesearch(c, c->request_name);
			}
			return;
		}

		fatal("ldap_result (zonesearch): %s", ldap_err2string(err_code));
	}

	/* we have results... process SOA/NS */
	if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
		/* save list pointers */
		if (c->adlen > -1) {
			saved_NS = c->NS;
			saved_soa[0] = c->serial;
			saved_soa[1] = c->refresh;
			saved_soa[2] = c->retry;
			saved_soa[3] = c->expire;
			saved_soa[4] = c->minimum;
			saved_soa[5] = c->ttl;
			saved_wantdie = c->wantdie;

			c->refresh = default_refresh;
			c->retry = default_retry;
			c->expire = default_expire;
			c->minimum = default_minimum;
			c->ttl = c->minimum;

			c->NS = 0;
			c->wantdie = 0;
			c->serial = c->lastt;
		}
		saved_adlen = c->adlen;
		c->adlen = -1;
	}

	ldap_load_dns_attributes(c, &c->search_base, 0);

	if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
//		if (c->adlen > -1) 
		add_peer_ns(c, 1);

	}

	if (ldapdns.dn_mode == DN_MODE_LDAPDNS && c->adlen > -1
			&& saved_adlen > -1) {
		if (saved_adlen > c->adlen) {
			/* okay, we already had a better shot... free the
			 * new ones and restore */
			while ((x = list_pop(&c->NS))) mem_free(x);

			c->NS = saved_NS;
			c->serial = saved_soa[0];
			c->refresh = saved_soa[1];
			c->retry = saved_soa[2];
			c->expire = saved_soa[3];
			c->minimum = saved_soa[4];
			c->ttl = saved_soa[5];
			c->wantdie = saved_wantdie;
			c->adlen = saved_adlen;

		} else {
			/* this one is better... free the saved */
			while ((x = list_pop(&saved_NS))) mem_free(x);
		}
	}
	if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
		/* more on the way... */
		if (ldap_msgtype(c->message) == LDAP_RES_SEARCH_RESULT)
			return;
	}
	
	/* try lowering ourselves... */
	if (c->subreq) {
		//printf("lower ourselves\n");
		if (c->subreq_valid < c->subreq) {
			if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
				finish_subrequest(c);
			} else {
				c->subreq_in += ((*c->subreq_in)+1);
				do_zonesearch(c, c->subreq_in);
			}
			return;
		}
	} else if (!c->NS) {
		/* still not good enough */
		/* skip the first dot and try again */
		if (ldapdns.dn_mode == DN_MODE_LDAPDNS) {
			response_nxdomain(c);
			complete_phase(c, '-');
		} else {
			c->request_name += ((*c->request_name)+1);
			do_zonesearch(c, c->request_name);
		}
		return;
	}

	/* okay, back up to original request and send it */
	if (c->subreq) {
		c->subreq_in = c->subreq_in_alloc;
		c->attr_wild = 0;
		do_attrsearch(c, c->subreq_in, 0);
	} else {
		//printf("okay, move back up to %s\n", c->request_name_alloc);
		c->request_name_zone = c->request_name;
		c->request_name = c->request_name_alloc;
		if (c->axfr) {
			do_axfrsearch(c, c->request_name);
		} else {
			c->attr_wild = 0;
			do_attrsearch(c, c->request_name, 0);
		}
	}
}
static void inline push_subreq(dns_ctx *c, char *q)
{
	list_t lp;

	/* make sure we haven't done this guy yet (save ourselves time) */
	for (lp = c->subreq_done; lp; lp = lp->next) {
		if (str_equali(lp->str, q)) return;
	}

	/* add to the lists */
	list_push(&c->subreq_tries, str_dup(q));
}

static void inline
response_generic(dns_ctx *c, int ax, char *q, unsigned char *p, unsigned long ttl)
{
	unsigned short glen;
	int dlen;

	glen = *(unsigned short *)(p+2);
	if (ax) {
		if (!response_axstart(c, 0, q, p, DNS_C_IN, ttl)) {
			fatal("could not construct generic");
		}
	} else {
		if (!response_rstart(c, q, p, ttl)) {
			fatal("could not construct generic");
		}
	}
	p += 4;
	while (glen > 0) {
		if (*p == 0xFF) {
			glen--; p++;
			if (glen <= 0) {
				fatal("could not construct generic");
			}
			if (*p == 0x00) {
				if (!response_addbytes(c, "\377", 1)) {
					fatal("could not construct generic");
				}
				glen--; p++;
			} else {
				/* dns-encoded name */
				if (!response_addname(c, p)) {
					fatal("could not construct generic");
				}
				dlen = dns_domain_length(p);
				/* do we want to do subrequests? */
				if (!ldapdns.no_additionals)
					push_subreq(c, p);
				glen -= dlen; p += dlen;
			}
		} else {
			if (!response_addbytes(c, p, 1)) {
				fatal("could not construct generic");
			}
			glen--; p++;
		}
	}
}
static void inline add_peer_ns(dns_ctx *c, int inc) {
	list_t lp;
	char *x;

	if (inc) {
		/* then also attach peer_ns and self_ns */
		for (lp = ldapdns.peer_ns; lp; lp = lp->next) {
			list_push(&c->NS, str_dup(lp->str));
		}
		if (ldapdns.self_ns)
			list_push(&c->NS, str_dup(ldapdns.self_ns));
	}
	
	/* don't add nameservers unless we've already got one (including @) */
	if (c->NS) {
		/* make sure only unique nameservers are listed */
		ldapdns_list_unique(&c->NS);
		
		lp = 0;
		while ((x = list_pop(&c->NS))) {
			if (x[0] == 1 && x[1] == '@' && x[2] == 0) {
				mem_free(x);
				continue;
			}
			list_push(&lp, x);
		}
		list_reverse(&lp);
		c->NS = lp;
	}
}
static void ldapdns_process_attrsearch(dns_ctx *c, int no_wild)
{
	int err_code;
	char *dat;
	list_t lp;
	int ext_records, selfmatch;
	int didany = 0;

	/* process NS/SOA search results; possibly rerun */
	if (!c->message) {
		/* odd... no message waiting? */
		fatal("assertion: attrsearch processing without message!");
	}

	//printf("attrsearch\n");
	ldap_parse_result(c->c->ldap_con, c->message,
			&err_code,
			0,0,0,0,0);
	if (err_code != LDAP_SUCCESS) {
		if (err_code == LDAP_SERVER_DOWN) {
			pthread_mutex_lock(&c->c->lock);
			restart_ldap_connection(c->c);
			pthread_mutex_unlock(&c->c->lock);
			if (no_wild) {
				/* resend original */
				do_simple_search(c, c->request_name);
			} else
			/* resend phase-2 query */
			if (c->subreq) {
				do_attrsearch(c, c->subreq_in, c->attr_wild);
			} else {
				do_attrsearch(c, c->request_name, c->attr_wild);
			}
			return;
		} else if (err_code == LDAP_TIMEOUT) {
			/*response_refuse(c);*/
			complete_phase(c, '?');
			return;
		} else if (err_code == LDAP_NO_SUCH_OBJECT
		|| err_code == LDAP_LOOP_DETECT
		|| err_code == LDAP_UNWILLING_TO_PERFORM
		|| err_code == LDAP_ALIAS_PROBLEM
		|| err_code == LDAP_INVALID_SYNTAX
		|| err_code == LDAP_INAPPROPRIATE_MATCHING
		|| err_code == LDAP_NO_SUCH_ATTRIBUTE) {
			/* doesn't exist... let's chop the base up */
			/* and put it in wildmode */
			//printf("did not exist?\n");
			//
			if (no_wild) {
				/* this is a real failure */
				response_refuse(c);
				complete_phase(c, '-');
				return;
			}

			/* skip the first dot and try again */
			c->attr_wild = 1;
			if (c->subreq) {
				c->subreq_in += ((*c->subreq_in)+1);
				do_attrsearch(c, c->subreq_in, 1);
			} else {
				c->request_name += ((*c->request_name)+1);
				do_attrsearch(c, c->request_name, 1);
			}
			return;
		}

		fatal("ldap_result (zonesearch): %s", ldap_err2string(err_code));
	}

	/* we have results... load them into lists*/
	//printf("go in here?\n");
	ldap_load_dns_attributes(c, 0, 1);
	//printf("go out here?\n");

	/* randomize arecords if requested */
	switch (ldapdns.schedule_arecord) {
	case SCHEDULE_A_RANDOM:
		list_randomize(&c->A);
		break;
	}

	if (c->subreq) {
		//printf("eating addresses?\n");
		if (c->A) {
			while ((dat = list_pop(&c->A))) {
				if (!response_rstart(c, c->subreq_in_alloc,
							DNS_T_A, c->ttl)
				|| !response_addbytes(c, dat, 4)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ADDITIONAL);
				mem_free(dat);
			}
		}
		finish_subrequest(c);
		return;
	}

	/* makes it "easy" to kill records */
	if (c->wantdie) {
		response_refuse(c);
		complete_phase(c, '-');
		return;
	}

#define _eq2(a) (a[0] == c->request_record[0] && a[1] == c->request_record[1])
	if (_eq2(DNS_T_SOA)) {
		/* SOA needs: NS1, original query, and hostmaster */
		if (!c->NS) {
			response_refuse(c);
			complete_phase(c, '-');
			return;
		}

		add_peer_ns(c, 0);
		response_aa(c, 1);

		if (!response_rstart(c, c->request_name_alloc, DNS_T_SOA, c->ttl)
		|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
		|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
		|| !response_addulong(c, c->serial)
		|| !response_addulong(c, c->refresh)
		|| !response_addulong(c, c->retry)
		|| !response_addulong(c, c->expire)
		|| !response_addulong(c, c->minimum)) {
			fatal("could not construct SOA");
		}
		response_rfinish(c, RESPONSE_ANSWER);
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone, DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}
		if (no_wild)
			complete_phase(c, '+');
		else if (c->subreq_tries) {
			c->subreq++;
			finish_subrequest(c);
		} else
			complete_phase(c, '+');
		return;
	}

	ext_records = 0;
	if (c->Generic) {
		/* Generic first */
		while ((dat = list_pop(&c->Generic))) {
			if (dat[0] != DNS_T_NS[0] && dat[1] != DNS_T_NS[1])
				ext_records++;

			if (_eq2(dat)) {
				response_generic(c, 0, c->request_name_alloc,
						dat, c->ttl);
				response_rfinish(c, RESPONSE_ANSWER);
			}

			mem_free(dat);
		}
	}

	/* flip the AA bit */
	add_peer_ns(c, 0);
	response_aa(c, 1);

	/* address-style requests */
	if (_eq2(DNS_T_ANY)) {
		/* CNAME:A and MX */
		if (c->A) {
			/* so we don't say it again */
			if (!ldapdns.no_additionals)
				list_push(&c->subreq_done,
					str_dup(c->request_name_alloc));

			/* pop off each entry and add it's response */
			while ((dat = list_pop(&c->A))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_A, c->ttl)
				|| !response_addbytes(c, dat, 4)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				mem_free(dat);
			}
		} else if (c->CNAME) {
			while ((dat = list_pop(&c->CNAME))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_CNAME, c->ttl)
				|| !response_addname(c, dat)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				if (!ldapdns.no_additionals)
					push_subreq(c, dat);
				mem_free(dat);
			}
		}

		if (c->PTR) {
			/* this is silly */
			while ((dat = list_pop(&c->PTR))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_PTR, c->ttl)
				|| !response_addname(c, dat)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				mem_free(dat);
			}
		}

		if (c->MX) {
			while ((dat = list_pop(&c->MX))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_MX, c->ttl)
				|| !response_addbytes(c, dat, 2)
				|| !response_addname(c, dat+2)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				if (!ldapdns.no_additionals)
					push_subreq(c, dat+2);
				mem_free(dat);
			}
		}

		/* return a SOA */
		if ((no_wild && ldapdns.self_ns) || c->NS) {
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}

		/* also included SOA */
		for (lp = c->NS; lp; lp = lp->next) {
			dat = lp->str;
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_ANSWER);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
		}

		/* also included SOA */
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}


	} else if (_eq2(DNS_T_A)) {
		/* CNAME:A */
		if (c->A) {
			if (!ldapdns.no_additionals)
				list_push(&c->subreq_done,
						str_dup(c->request_name_alloc));
			while ((dat = list_pop(&c->A))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_A, c->ttl)
				|| !response_addbytes(c, dat, 4)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				didany++;
				mem_free(dat);
			}
		} else if (c->CNAME) {
			while ((dat = list_pop(&c->CNAME))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_CNAME, c->ttl)
				|| !response_addname(c, dat)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				if (!ldapdns.no_additionals)
					push_subreq(c, dat);
				didany++;
				mem_free(dat);
			}
		}

		if (!didany && c->NS) {
			/* if no answers, but we're NS, we give SOA */
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}

		/* also included SOA */
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}

	} else if (ldapdns.netbios && _eq2(DNS_T_NB)) {
		if (c->A) {
			/* okay, normally these things are generic.
			 * but we'll translate A records for them
			 */
			while ((dat = list_pop(&c->A))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_NB, c->ttl)
				/* netbios flags:
				 * 	0x80 (unique)		'G'
				 * 	0x20 P-node		'ont'
				 */
				|| !response_addbytes(c, "\x60\x00", 2)
				|| !response_addbytes(c, dat, 4)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				mem_free(dat);
			}
		}

	} else if (_eq2(DNS_T_MX)) {
		/* CNAME:A:MX */
		selfmatch = 1;
		if (c->MX) {
			selfmatch = 0;
			while ((dat = list_pop(&c->MX))) {
				if (!response_rstart(c, c->request_name_alloc,
							DNS_T_MX, c->ttl)
				|| !response_addbytes(c, dat, 2)
				|| !response_addname(c, dat+2)) {
					fatal("could not construct address");
				}
				response_rfinish(c, RESPONSE_ANSWER);
				if (str_equal(c->request_name_alloc, dat+2)) {
					selfmatch = 2;
				} else if (!ldapdns.no_additionals)
					push_subreq(c, dat+2);
				didany++;
				mem_free(dat);
			}
		}

		if (selfmatch == 1) {
			if (c->A) {
				if (!ldapdns.no_additionals)
					list_push(&c->subreq_done,
							str_dup(c->request_name_alloc));
				while ((dat = list_pop(&c->A))) {
					if (!response_rstart(c, c->request_name_alloc,
								DNS_T_A, c->ttl)
					|| !response_addbytes(c, dat, 4)) {
						fatal("could not construct address");
					}
					didany++;
					response_rfinish(c, RESPONSE_ANSWER);
					mem_free(dat);
				}
			} else if (c->CNAME) {
				while ((dat = list_pop(&c->CNAME))) {
					if (!response_rstart(c, c->request_name_alloc,
								DNS_T_CNAME, c->ttl)
					|| !response_addname(c, dat)) {
						fatal("could not construct address");
					}
					response_rfinish(c, RESPONSE_ANSWER);
					didany++;
					if (!ldapdns.no_additionals)
						push_subreq(c, dat);
					mem_free(dat);
				}
			}
		}
		if (!didany && c->NS) {
			/* if no answers, but we're NS, we give SOA */
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}

		/* also included SOA */
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}

		if (selfmatch == 2) {
			if (c->A) {
				if (!ldapdns.no_additionals)
					list_push(&c->subreq_done,
							str_dup(c->request_name_alloc));
				while ((dat = list_pop(&c->A))) {
					if (!response_rstart(c, c->request_name_alloc,
								DNS_T_A, c->ttl)
					|| !response_addbytes(c, dat, 4)) {
						fatal("could not construct address");
					}
					response_rfinish(c, RESPONSE_ADDITIONAL);
					mem_free(dat);
				}
			} else if (c->CNAME) {
				while ((dat = list_pop(&c->CNAME))) {
					if (!response_rstart(c, c->request_name_alloc,
								DNS_T_CNAME, c->ttl)
					|| !response_addname(c, dat)) {
						fatal("could not construct address");
					}
					response_rfinish(c, RESPONSE_ADDITIONAL);
					if (!ldapdns.no_additionals)
						push_subreq(c, dat);
					mem_free(dat);
				}
			}
		}

	} else if (_eq2(DNS_T_CNAME)) {
		while ((dat = list_pop(&c->CNAME))) {
			if (!response_rstart(c, c->request_name_alloc, DNS_T_CNAME, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct CNAME");
			}
			didany++;
			response_rfinish(c, RESPONSE_ANSWER);
			mem_free(dat);
		}

		if (!didany && c->NS) {
			/* if no answers, but we're NS, we give SOA */
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}

		/* also included SOA */
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}

	} else if (_eq2(DNS_T_PTR)) {
		while ((dat = list_pop(&c->PTR))) {
			if (!response_rstart(c, c->request_name_alloc,
						DNS_T_PTR, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct address");
			}
			didany++;
			response_rfinish(c, RESPONSE_ANSWER);
			mem_free(dat);
		}
		if (!didany && c->NS) {
			/* if no answers, but we're NS, we give SOA */
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}

		/* also included SOA */
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone,
						DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}
	} else if (_eq2(DNS_T_NS)) {
		/* NS returns both answer and authority (since we asked nicely) */
		for (lp = c->NS; lp; lp = lp->next) {
			dat = lp->str;
			if (!response_rstart(c, c->request_name_alloc, DNS_T_NS, c->ttl)
			|| !response_addname(c, lp->str)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone, DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}
	} else if (_eq2(DNS_T_TXT)) {
		/* TXT */
		while ((dat = list_pop(&c->TXT))) {
			if (!response_rstart(c, c->request_name_alloc, DNS_T_TXT, c->ttl)
			|| !response_addbytes(c, dat, dns_domain_length(dat)-1)) {
				fatal("could not construct TXT");
			}
			response_rfinish(c, RESPONSE_ANSWER);
			didany++;
			mem_free(dat);
		}
		if (!didany && c->NS) {
			/* if no answers, but we're NS, we give SOA */
			if (!response_rstart(c, c->request_name_zone, DNS_T_SOA, c->ttl)
			|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : (c->NS ? c->NS->str : ""))
			|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
			|| !response_addulong(c, c->serial)
			|| !response_addulong(c, c->refresh)
			|| !response_addulong(c, c->retry)
			|| !response_addulong(c, c->expire)
			|| !response_addulong(c, c->minimum)) {
				fatal("could not construct SOA");
			}
			response_rfinish(c, RESPONSE_ANSWER);
		}
		while ((dat = list_pop(&c->NS))) {
			if (!response_rstart(c, c->request_name_zone, DNS_T_NS, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			response_rfinish(c, RESPONSE_AUTHORITY);
			if (!ldapdns.no_additionals && !ldapdns.no_additionals_ns)
				push_subreq(c, dat);
			mem_free(dat);
		}
	}

#undef _eq2
	if (no_wild)
		complete_phase(c, '+');
	else if (c->subreq_tries) {
		c->subreq++;
		finish_subrequest(c);
	} else
		complete_phase(c, '+');
	//printf("outta here\n");
}
static void ldapdns_process_axfrsearch_mid(dns_ctx *c, char *autodn)
{
	int err_code;
	char *dn, *dat;
	str_t rn, n;
	list_t y;

	if (autodn) {
		dn = autodn;
	} else {
		/* process multi-record results... blech */
		if (!c->message) {
			/* odd... no message waiting? */
			fatal("assertion: axfrsearch processing without message!");
		}
	
		//printf("phase3\n");
		ldap_parse_result(c->c->ldap_con, c->message,
				&err_code,
				0,0,0,0,0);
		if (err_code != LDAP_SUCCESS) {
			if (err_code == LDAP_SERVER_DOWN) {
				pthread_mutex_lock(&c->c->lock);
				restart_ldap_connection(c->c);
				pthread_mutex_unlock(&c->c->lock);
				/* resend axfr-query */
				do_axfrsearch(c, c->request_name);
				return;
			} else if (err_code == LDAP_TIMEOUT) {
				/*response_refuse(c);*/
				complete_phase(c, '?');
				return;
			} else if (err_code == LDAP_NO_SUCH_OBJECT
			|| err_code == LDAP_LOOP_DETECT
			|| err_code == LDAP_UNWILLING_TO_PERFORM
			|| err_code == LDAP_ALIAS_PROBLEM
			|| err_code == LDAP_INVALID_SYNTAX
			|| err_code == LDAP_INAPPROPRIATE_MATCHING
			|| err_code == LDAP_NO_SUCH_ATTRIBUTE) {
				/* doesn't exist... VERY strange... */
				//printf("did not exist?\n");
				response_refuse(c);
				complete_phase(c, '-');
				return;
			}
	
			fatal("ldap_result (zonesearch): %s", ldap_err2string(err_code));
		}
	
		if (!ldap_load_dns_attributes(c, &dn, 0)) {
			goto finish_axfr_l;
		}
	}
			
	ldap_to_name(n, dn);
	mem_free(dn);

	if (! *(str(n))) /* invalid name? */
		goto pass_this_round_l;

	name_to_dns(rn, str(n));
	mem_free(str(n));

	/* nameservers */
	if (!autodn) {
		while ((dat = list_pop(&c->NS))) {
			if (!response_axstart(c, 0, str(rn), DNS_T_NS, DNS_C_IN, c->ttl)
			|| !response_addname(c, dat)) {
				fatal("could not construct NS");
			}
			mem_free(dat);
			response_axfinish(c);
		}
	}

	/* addresses */
	while ((dat = list_pop(&c->A))) {
		if (!response_axstart(c, 0, str(rn), DNS_T_A, DNS_C_IN, c->ttl)
		|| !response_addbytes(c, dat, 4)) {
			fatal("could not construct address");
		}
		mem_free(dat);
		response_axfinish(c);
	}

	/* cnames */
	while ((dat = list_pop(&c->CNAME))) {
		if (!response_axstart(c, 0, str(rn),
				DNS_T_CNAME, DNS_C_IN, c->ttl)
		|| !response_addname(c, dat)) {
			fatal("could not construct address");
		}
		response_axfinish(c);
		mem_free(dat);
	}

	/* exchanges */
	while ((dat = list_pop(&c->MX))) {
		if (!response_axstart(c, 0, str(rn),
					DNS_T_MX, DNS_C_IN, c->ttl)
		|| !response_addbytes(c, dat, 2)
		|| !response_addname(c, dat+2)) {
			fatal("could not construct address");
		}
		response_axfinish(c);
		mem_free(dat);
	}
	/* text */
	while ((dat = list_pop(&c->TXT))) {
		if (!response_axstart(c, 0, str(rn), DNS_T_TXT,
					DNS_C_IN, c->ttl)
		|| !response_addbytes(c, dat, dns_domain_length(dat)-1)) {
			fatal("could not construct TXT");
		}
		response_axfinish(c);
		mem_free(dat);
	}
	/* pointers */
	while ((dat = list_pop(&c->PTR))) {
		if (!response_axstart(c, 0, str(rn), DNS_T_PTR,
					DNS_C_IN, c->ttl)
		|| !response_addname(c, dat)) {
			fatal("could not construct PTR");
		}
		response_axfinish(c);
		mem_free(dat);
	}
	/* srv and everything else */
	while ((dat = list_pop(&c->Generic))) {
		response_generic(c, 1, c->request_name_alloc,
				dat, c->ttl);
		response_axfinish(c);
		mem_free(dat);
	}

	mem_free(str(rn));

pass_this_round_l:
	c->still_using_message = 1;
	return;

finish_axfr_l:
	if (!response_axstart(c, 1, c->request_name, DNS_T_SOA, DNS_C_IN, c->ttl)
	|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : c->ns->str)
	|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
	|| !response_addulong(c, c->serial)
	|| !response_addulong(c, c->refresh)
	|| !response_addulong(c, c->retry)
	|| !response_addulong(c, c->expire)
	|| !response_addulong(c, c->minimum)) {
		fatal("could not construct SOA");
	}
	while ((dat = list_pop(&c->ns))) {
		mem_free(dat);
	}
	c->ns = 0;
	response_axfinish(c);
	complete_phase(c, '+');
}
static void ldapdns_process_axfrsearch_start(dns_ctx *c)
{
	const char *attrs[11] = {
				"aRecord",
				"mXRecord",
				"cNAMERecord",
				"seeAlso",
				"description",
				"photo",
				"mail",
				"nSRecord",
				"modifyTimestamp"
	};
	int r;
	int err_code;
	char *dn, *dat;
	list_t y;

	/* process multi-record results... blech */
	if (!c->message) {
		/* odd... no message waiting? */
		fatal("assertion: axfrsearch processing without message!");
	}

	//printf("phase3\n");
	ldap_parse_result(c->c->ldap_con, c->message,
			&err_code,
			0,0,0,0,0);
	if (err_code != LDAP_SUCCESS) {
		if (err_code == LDAP_SERVER_DOWN) {
			pthread_mutex_lock(&c->c->lock);
			restart_ldap_connection(c->c);
			pthread_mutex_unlock(&c->c->lock);
			/* resend axfr-query */
			do_axfrsearch(c, c->request_name);
			return;
		} else if (err_code == LDAP_TIMEOUT) {
			/*response_refuse(c);*/
			complete_phase(c, '?');
			return;
		} else if (err_code == LDAP_NO_SUCH_OBJECT
		|| err_code == LDAP_LOOP_DETECT
		|| err_code == LDAP_UNWILLING_TO_PERFORM
		|| err_code == LDAP_ALIAS_PROBLEM
		|| err_code == LDAP_INVALID_SYNTAX
		|| err_code == LDAP_INAPPROPRIATE_MATCHING
		|| err_code == LDAP_NO_SUCH_ATTRIBUTE) {
			/* doesn't exist... VERY strange... */
			//printf("did not exist?\n");
			response_refuse(c);
			complete_phase(c, '-');
			return;
		}

		fatal("ldap_result (zonesearch): %s", ldap_err2string(err_code));
	}

	/* we have results... load them into lists*/
	if (!ldap_load_dns_attributes(c, &dn, 1) || !c->NS) {
		response_refuse(c);
		complete_phase(c, '-');
		return;
	}

	add_peer_ns(c, 0);
//	response_aa(c, 1);

	/* add these nameservers to plan-for-authority */
	c->ns = c->NS; c->NS = 0;

	/* before you can call any axfr calls */
	response_axfr(c);

	/* SOA comes first...
	 */
	if (!response_axstart(c, 1, c->request_name, DNS_T_SOA, DNS_C_IN, c->ttl)
	|| !response_addname(c, ldapdns.self_ns ? ldapdns.self_ns : c->ns->str)
	|| !response_addname(c, c->ADM ? c->ADM->str : ldapdns.hostmaster)
	|| !response_addulong(c, c->serial)
	|| !response_addulong(c, c->refresh)
	|| !response_addulong(c, c->retry)
	|| !response_addulong(c, c->expire)
	|| !response_addulong(c, c->minimum)) {
		fatal("could not construct SOA");
	}
	response_axfinish(c);

retry_axfr_next_top_l:
	if (ldapdns.dn_mode == DN_MODE_RFC1279
			|| ldapdns.dn_mode == DN_MODE_MSDNS) {
		attrs[9] = (const char *)"dNSRecord";
		attrs[10] = (const char *)0;
	} else {
		attrs[9] = (const char *)0;
	}

	pthread_mutex_lock(&c->c->lock);
	r = ldap_search(c->c->ldap_con,
			dn,			/* base */
			LDAP_SCOPE_SUBTREE,	/* scope */
			"(objectClass=*)",	/* ldap filter */
			(char **)attrs,		/* attrs */
			0);			/* attrsonly */

	if (r == -1) {
		/* uh oh: possibly an out of memory error */
		restart_ldap_connection(c->c);
		pthread_mutex_unlock(&c->c->lock);
		goto retry_axfr_next_top_l;
	}

	//printf("sent query for %d (was %d)\n", r, c->message_id);
	c->message_id = r;
	c->phase = PHASE_AXFRSEARCH;

	c->c->message_sent++;
	//warning("axfrfirst %d / %d", c->c->message_sent, c->c->message_wait);
	if (c->c->message_wait)
		pthread_cond_broadcast(&c->c->active);

	pthread_mutex_unlock(&c->c->lock);
	c->still_using_message = 0;

	return;
}

static void inline initialize_handler(int i, dns_ctx *c)
{
	c->n = i;
	if (pthread_mutex_init(&c->lock, NULL) != 0)
		cfatal("pthread_mutex_init(h%d): %s", i);

	c->sec_update = 0;
	c->sec_prereq = 0;
	c->sock = -1;
	c->swm = 0;
	c->axfr = 0;
	c->axfr_base = 0;
	c->ns = 0;
	c->phase = PHASE_IDLE;
	c->subreq = c->subreq_valid = 0;
	c->subreq_in = c->subreq_in_alloc = 0;
	c->response_names.b = 0;
	c->response_names.s = 0;
	c->request_name_alloc = 0;
	c->request_attr = 0;
	c->message = 0;
//	c->message_entry = 0;
	c->NS = 0;
	c->adlen = -1;
	c->wantdie = 0;
	c->subreq_tries = 0;
	c->subreq_done = 0;
	c->DNSRecord = c->A = c->CNAME = c->MX = c->SRV = c->TXT =
		c->ADM = c->PTR = c->Generic = 0;
	bin_init(c->response);
}
static void *one2one_msgwait_loop(void *o_void)
{
	struct timeval tv;
	LDAPMessage *result;
	dns_ctx *c;
	ldap_ctx *o;
	list_t lp;
	time_t now;
	int r;

	/* setup linkage */
	o = (ldap_ctx *)o_void;
	for (c = handler; c; c = c->next) {
		if (c->n == o->n) break;
	}
	if (!c) {
		fatal("Could not find matching 1:1 handler (%d)", o->n);
	}

	c->c = o;

	for (;;) {
		/* do upkeep */
		time(&now);
		tp_housekeeping((long *)&now);
		handle_messages();
		if (tp_read(c) < 1) continue;

		/* switch it */
		c->swm = 0;
		for (lp = ldapdns.swm; lp; lp = lp->next) {
			if (!lp->str) continue;
			if (lp->str[0] == 0x04) {
				if (ipv4_in_subnet(lp->str+1, c->ip)) {
					c->swm = lp->str + 9;
					if (!ipv4_null(lp->str+1))
						break;
				}
#ifdef HAVE_IPV6
			} else if (lp->str[0] == 0x06) {
				if (ipv6_in_subnet(lp->str+1, c->ip)) {
					c->swm = lp->str + 33;
					if (!ipv6_null(lp->str+1))
						break;
				}
#endif
			}
		}

		o->load++;

		/* start phase 1 */
		time(&c->lastt);
		engine_dns_answer(c, c->lastt);

		/* phase is now zonesearch */
		while (c->phase != PHASE_IDLE) {
			tv.tv_usec = 0;
			tv.tv_sec = 14;
			pthread_mutex_lock(&o->lock);
			r = ldap_result(o->ldap_con, c->message_id, 0,
					&tv, &result);

			if (r == -1) {
				complete_phase(c, '?');
				c->c = 0; /* give it up */
				c->phase = PHASE_IDLE;
				restart_ldap_connection(o);
				pthread_mutex_unlock(&o->lock);
				break;
			}

			pthread_mutex_unlock(&o->lock);
			time(&now);

			if (r == 0 && now - c->lastt > 12) {
				pthread_mutex_lock(&log_lock);
				warning("handler %d has waited too long", c->n);
				pthread_mutex_unlock(&log_lock);

				/* whoops; abort this biotch */
				if (c->message_id > -1) {
					pthread_mutex_lock(&o->lock);
					/* we don't want to do this if we're
					 * using the openldap client-side
					 * cache
					 */
#ifdef ACCELERATE_CACHE
					if (!ldapdns.accelerate_cache)
#endif
						ldap_abandon(o->ldap_con,
							c->message_id);
					c->message_id = -1;
					pthread_mutex_unlock(&o->lock);
				}

				complete_phase(c, '?');
				break;
			}

			c->message = result;
			c->still_using_message = 0;
			switch (c->phase) {
			case PHASE_ZONESEARCH:
				ldapdns_process_zonesearch(c);
				break;
			case PHASE_ATTRSEARCH:
				ldapdns_process_attrsearch(c, 0);
				break;
			case PHASE_AXFRFIRST:
				ldapdns_process_axfrsearch_start(c);
				break;
			case PHASE_AXFRSEARCH:
				ldapdns_process_axfrsearch_mid(c, 0);
				break;
			case PHASE_NSUPDATE:
				ldapdns_process_update(c);
				break;
			case PHASE_SIMPLESEARCH:
				ldapdns_process_attrsearch(c, 1);
				break;
			};
			if (!c->still_using_message) {
				if (ldap_msgfree(result) == -1) {
					cfatal("ldap_msgfree: %s");
				}
				c->message = 0;
				o->message_sent--;
			}

			c->lastt = now;
		}
	}
}
static void *dns_msgwait_loop(void *d_void)
{
	time_t now;
	int need_second_round, did_second_round;
	dns_ctx *c;
	int j, p, noavail;
	list_t lp;

	/* we don't do anything with d_void */
	for (;;) {
		/* do upkeep */
		time(&now);
		tp_housekeeping((long *)&now);
		handle_messages();
		pthread_mutex_lock(&handler_lock);
		c = handler;
		pthread_mutex_unlock(&handler_lock);
		need_second_round = did_second_round = 0;
		noavail = 1;
round_top_l:
		for (; c; c = c->next) {
			if ((volatile)c->phase != PHASE_IDLE)
				continue;
			break;
		}
		if (!c) {
			if (!noavail) {/* restart from top  */
				if (need_second_round && !did_second_round) {
					did_second_round = 1;
					pthread_mutex_lock(&handler_lock);
					c = handler;
					pthread_mutex_unlock(&handler_lock);
					noavail = 1;
					goto round_top_l;
				}
				continue;
			}

			c = mem_alloc(sizeof(dns_ctx));
			if (!c) {
				cfatal("new_handler:mem_alloc: %s");
			}
			pthread_mutex_lock(&handler_lock);
			initialize_handler(ldapdns.handlers, c);
			ldapdns.handlers++;
			c->next = handler;
			c->prev = 0;
			handler = c;
			if (c->next) /* should never fail */
				c->next->prev = c;
			pthread_mutex_unlock(&handler_lock);
			continue;
		}
		noavail = 0;
		/* this blocks until data can be read */
		//warning("tp read");
		switch (tp_read(c)) {
		case -1: /* they need a second round just-in-case */
			need_second_round = 1;
			/* fall through */
		case 0:
			c = c->next;
			goto round_top_l;
		};

		/* switch it */
		c->swm = 0;
		for (lp = ldapdns.swm; lp; lp = lp->next) {
			if (!lp->str) continue;
			if (lp->str[0] == 0x04) {
				if (ipv4_in_subnet(lp->str+1, c->ip)) {
					c->swm = lp->str + 9;
					if (!ipv4_null(lp->str+1))
						break;
				}
#ifdef HAVE_IPV6
			} else if (lp->str[0] == 0x06) {
				if (ipv6_in_subnet(lp->str+1, c->ip)) {
					c->swm = lp->str + 33;
					if (!ipv6_null(lp->str+1))
						break;
				}
#endif
			}
		}

		/* find the lowest-loaded thread */
		for (j = 0, p = -1; j < ldapdns.ldap_threads; j++) {
			if (ldap_thread[j].load == 0)	/* 0 is good :) */
				break;
			if (p == -1 || ldap_thread[p].load > ldap_thread[j].load)
				p = j;
		}
		if (j == ldapdns.ldap_threads) {
			j = p;
			if (p == -1) {
				/* this should never happen */
				fatal("ldapdns.ldap_threads got zero'd somehow");
			}
		}

		/* assign it to a thread */
		c->c = &ldap_thread[j];

		/* and increase the load */
		pthread_mutex_lock(&ldap_thread[j].load_lock);
		ldap_thread[j].load++;
		pthread_mutex_unlock(&ldap_thread[j].load_lock);

		/* start phase 1 */
		time(&c->lastt);
		engine_dns_answer(c, c->lastt);
		/* (phase will be set to PHASE_ZONESEARCH) */

		c = c->next;
		goto round_top_l;
	}

	return 0;
}
static void *ldap_msgwait_loop(void *c_void)
{
	LDAPMessage *result, *exresult;
	struct timeval tv;
	int r, i, msgid, answered;
	time_t now;
	ldap_ctx *c;
	dns_ctx *x;

	/* here's our context */
	c = c_void;

	/* message loop */
	for (answered = 0;;) {
		pthread_mutex_lock(&c->lock);
		//warning("%d answered", answered);
		c->message_sent -= answered; answered = 0;
did_restart_l:
		//warning("enter sent-block %d", c->message_sent);
		while (!c->message_sent) {
			c->message_wait++;
			pthread_cond_wait(&c->active, &c->lock);
			c->message_wait--;
		}
		//warning("exit sent-block %d", c->message_sent);
		tv.tv_usec = 0;
		tv.tv_sec = 14;
		result = 0;
		//warning("ldap_result (wait)");
		r = ldap_result(c->ldap_con, LDAP_RES_ANY, 0, &tv, &result);
		//warning("ldap_result (done)");
		if (r == -1) {
			/* it remains locked */
			restart_ldap_connection(c);
			goto did_restart_l;
		}
		pthread_mutex_unlock(&c->lock);

		if (r == 0)
			msgid = -1;
		else {
			msgid = ldap_msgid(result);
			if (msgid == -1) {
				/* err... */
				ldap_msgfree(result);
				pthread_mutex_lock(&log_lock);
				warning("msgid on handler %d returned -1", i);
				pthread_mutex_unlock(&log_lock);
				continue;
			}
		}

		time(&now);
		pthread_mutex_lock(&handler_lock);
		x = handler;
		pthread_mutex_unlock(&handler_lock);
		for (; x; x = x->next) {
			/* not _our_ running handler */
			if (x->c != c)
				continue;

			/* not a running handler */
			if ((volatile)x->phase == PHASE_IDLE)
				continue;
			/* bind's resolver waits 10 seconds...
			 * we'll wait 12...
			 */
			if (msgid != -1 && msgid == x->message_id) {
				/* do nothing; fall though */
				x->message = result;
				msgid = -1;
			} else if (now - x->lastt > 12) {
				pthread_mutex_lock(&log_lock);
				warning("handler %d has waited too long", x->n);
				pthread_mutex_unlock(&log_lock);

				/* whoops; abort this biotch */
				if (x->message_id > -1) {
					pthread_mutex_lock(&c->lock);
					/* we don't want to do this if we're
					 * using the openldap client-side
					 * cache
					 */
#ifdef ACCELERATE_CACHE
					if (!ldapdns.accelerate_cache)
#endif
						ldap_abandon(c->ldap_con,
							x->message_id);
					x->message_id = -1;
					c->message_sent--;
					pthread_mutex_unlock(&c->lock);
				}

				complete_phase(x, '?');
				continue;
			} else if (x->message_id == -1) {
				continue;
			} else if (c->message_sent > 0) {
				tv.tv_sec = 0;
				tv.tv_usec = 0;
				//pthread_mutex_lock(&c->lock);
				exresult = 0;
				r = ldap_result(c->ldap_con, x->message_id,
						0, &tv, &exresult);
				if (r == 0) {
					//pthread_mutex_unlock(&c->lock);
					continue;
				}
				if (r == -1) {
					pthread_mutex_lock(&c->lock);
					restart_ldap_connection(c);
					pthread_mutex_unlock(&c->lock);
					goto did_restart_l;
				}

				x->message = exresult;
				//pthread_mutex_unlock(&c->lock);
				/* fall through */
			} else {
				continue;
			}

			x->still_using_message = 0;
			switch (x->phase) {
			case PHASE_ZONESEARCH:
				ldapdns_process_zonesearch(x);
				break;
			case PHASE_ATTRSEARCH:
				ldapdns_process_attrsearch(x, 0);
				break;
			case PHASE_AXFRFIRST:
				ldapdns_process_axfrsearch_start(x);
				break;
			case PHASE_AXFRSEARCH:
				ldapdns_process_axfrsearch_mid(x, 0);
				break;
			case PHASE_NSUPDATE:
				ldapdns_process_update(x);
				break;
			case PHASE_SIMPLESEARCH:
				ldapdns_process_attrsearch(x, 1);
				break;
			};
			/* update lastt timer */
			x->lastt = now;

			if (!x->still_using_message) {
				if (ldap_msgfree(x->message) == -1) {
					cfatal("ldap_msgfree: %s");
				}
				x->message = 0;
				answered++;
			}
		}
	}
}
static void parse_read_search(char *d)
{
	char *x;
	str_t p;

	if (!d) return;

	x = d;
	while (*x && !isspace((unsigned int)*x)) x++;
	if (*x == 0) return;
	*x = 0; x++;
	while (isspace((unsigned int)*x)) x++;

	name_to_dns(p, d);

	switch (ht_store(&ldapdns.search, str(p), str_len(p), str_dup(x))) {
	case -1: cfatal("parse_read_search: %s");
	case 0: fatal("cannot (presently) search for %s twice", d);
	case 1: break;
	};
	mem_free(str(p));	/* ht_store makes it's own copy */
}
static unsigned long __search_hash(const void *str, unsigned len)
{
	const char *h;
	unsigned long d;
	unsigned i;

	d = 5431;
	for (i = 0, h = str; i < len; i++) {
		/* Clib */
		d = (d << 5) ^ (tolower((int)h[i]));
	}

	return d;
}
static int root_read_search(void)
{
	FILE *fp;
	str_t line;
	int c;

	ht_init(&ldapdns.search, 7, __search_hash);

	fp = fopen("search", "r");
	if (!fp)
		return 0;

	str_init(line);
	/* could hang if we attached to a device node... but it's in
	 * initialization only, so np here
	 */
	while ((c = fgetc(fp)) != EOF) {
		if (c == '\r' || c == '\n') {
			if (str(line)[0] && str(line)[0] != '#') {
				/* parse out this data */
				parse_read_search(str(line));
			}
			str_copy(line, "");
		} else {
			str_addch(line, c);
		}
	}
	fclose(fp);
	return 1;
}
static int root_read_sw(const char *filename, list_t *p)
{
	FILE *fp;
	str_t line;
	bin_t res;
	unsigned char *x, cidr[IP_LEN*2];
	int c;

	fp = fopen(filename, "r");
	if (!fp)
		return 0;

	str_init(line);
	bin_init(res);
	/* could hang if we attached to a device node... but it's in
	 * initialization only, so np here
	 */
	while ((c = fgetc(fp)) != EOF) {
		if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
			if (str(line)[0] && str(line)[0] != '#') {
				/* parse as follows */
				/* key=cidr */
				for (x = str(line); *x && *x != '='; x++);
				if (*x == '=') {
					*x = 0;
					x++;
#ifdef HAVE_IPV6
					if (ipv6_cidr(x, cidr)) {
						bin_copy(res, "\x06", 1);
						bin_cat(res, cidr, 32);
						bin_cat(res, str(line), str_len(line));
						bin_0(res);
						list_push(p, caddr(res));
						bin_init(res);
					} else
#endif
					if (ipv4_cidr(x, cidr)) {
						bin_copy(res, "\x04", 1);
						bin_cat(res, cidr, 8);
						bin_cat(res, str(line), str_len(line));
						bin_0(res);
						list_push(p, caddr(res));
						bin_init(res);
					}
				}

			}
			str_copy(line, "");
		} else {
			str_addch(line, c);
		}
	}
	fclose(fp);
	return 1;
}

static int root_read(const char *filename, char **buf, int secure)
{
	str_t retbuf;
	FILE *fp;
	int c, sp;

	fp = fopen(filename, "r");
	if (!fp)
		return 0;

	if (secure) {
		struct stat sb;

		if (fstat(fileno(fp), &sb) == -1) {
			fclose(fp);
			return 0;
		}
		if (sb.st_mode & 0377) {
			fclose(fp);
			fatal("$ROOT/%s must have mode 0400", filename);
		}
	}

	str_init(retbuf);
	sp = 0;
	/* could hang if we attached to a device node... but it's in
	 * initialization only, so np here
	 */
	while ((c = fgetc(fp)) != EOF) {
		if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
			sp = 1;
		} else {
			if (sp) {
				sp = 0;
				str_addch(retbuf, ' ');
			}
			str_addch(retbuf, c);
		}
	}
	fclose(fp);
	*buf = str(retbuf);
	return 1;
}
static int gc_ldap_cache(void *ignore)
{
	register int i;

#ifdef ACCELERATE_CACHE
	if (!ldapdns.accelerate_cache)
#endif
		return 0;

#ifdef ACCELERATE_CACHE
	pthread_mutex_lock(&host_lock);
	for (i = 0; i < ldapdns.ldap_threads; i++) {
		pthread_mutex_lock(&ldap_thread[i].lock);
		ldap_destroy_cache(ldap_thread[i].ldap_con);
		ldap_enable_cache(ldap_thread[i].ldap_con, ldapdns.accelerate_cache, 0);
		pthread_mutex_unlock(&ldap_thread[i].lock);
	}
	pthread_mutex_unlock(&host_lock);
#endif

	return 1;
}
int main(int argc, char *argv[])
{
	char *x, *y, *h;
	int i, j, p;
	static pthread_attr_t helper_thread_attr;
	str_t retbuf, hbuf;
	char spbuf[32];
	pthread_t dummy;
	list_t lp;
	bin_t res;

	/* try and get logging up first */
	x = env_get("LOG");
	log_init(x);

	/* allow self-supervise */
	x = env_get("SUPERVISE");
	if (x) {
		supervise(x);
	}

	/* setup signals */
	signal(SIGSTOP, handle_signal);
	signal(SIGCONT, handle_signal);
	signal(SIGTERM, handle_signal);
	signal(SIGHUP, handle_signal);
	signal(SIGINT, handle_signal);

	/* load AXFR early */
	x = env_get("AXFR");
	if (!x)
		x = env_get("LDAP_AXFR");
	if (x && *x) {
		/* save it in dns form... i'm just silly like that */
		name_to_dns(retbuf, x);
		ldapdns.axfr_base = str(retbuf);
	} else
		ldapdns.axfr_base = 0;

	if (pthread_attr_init(&helper_thread_attr) != 0)
		cfatal("pthread_attr_init: %s");
	if (pthread_attr_setdetachstate(&helper_thread_attr,
				PTHREAD_CREATE_DETACHED))
		cfatal("pthread_attr_setdetachstate: %s");

	ldapdns.ldap_threads = -1;
	ldapdns.dns_threads = -1;
	x = env_get("THREADS");
	if (x) {
		ldapdns.ldap_threads = atoi(x);
		if (ldapdns.ldap_threads > 1) {
			ldapdns.dns_threads = ldapdns.ldap_threads / 2;
			if (ldapdns.ldap_threads % 2 == 1)
				ldapdns.dns_threads++;
		}
	}

	x = env_get("DEFAULT_REFRESH"); if (x) default_refresh = atoi(x);
	x = env_get("DEFAULT_RETRY"); if (x) default_retry = atoi(x);
	x = env_get("DEFAULT_EXPIRE"); if (x) default_expire = atoi(x);
	x = env_get("DEFAULT_MINIMUM"); if (x) default_minimum = atoi(x);

	x = env_get("LDAP_THREADS");
	if (x) ldapdns.ldap_threads = atoi(x);

	x = env_get("DNS_THREADS");
	if (x) ldapdns.dns_threads = atoi(x);

	if (ldapdns.ldap_threads < 1)
		ldapdns.ldap_threads = 1;
	if (ldapdns.dns_threads < 1)
		ldapdns.dns_threads = 1;

	ldapdns.handlers = (ldapdns.ldap_threads + ldapdns.dns_threads)*2;
	if (ldapdns.handlers < 128) ldapdns.handlers = 128;

	x = env_get("HANDLERS");
	if (x) {
		j = atoi(x);
		if (j > 0)
			ldapdns.handlers = j;
	}

	/* setup TCP options */
	x = env_get("TIMEOUT_TCP");
	if (!x)
		x = env_get("TIMEOUT");
	if (x) {
		/* barg */
		ldapdns.timeout_tcp = atoi(x);
	} else
		ldapdns.timeout_tcp = 0;

	x = env_get("ALWAYS_HANGUP_TCP");
	if (!x)
		x = env_get("ALWAYS_HANGUP");
	ldapdns.always_hangup = (x ? 1 : 0);

	/* get listening socket */
	tp_initialize();
		/* !!! tcpserver modifies ldapdns.handlers */

	if (ldapdns.handlers == 1) {
		/* special handler mode... */
		ldapdns.one2one_mode = 1;
		if (ldapdns.dns_threads > ldapdns.ldap_threads) {
			/* silly... */
			ldapdns.handlers = ldapdns.ldap_threads = ldapdns.dns_threads;
		} else {
			ldapdns.handlers = ldapdns.dns_threads = ldapdns.ldap_threads;
		}
	} else {
		ldapdns.one2one_mode = 0;
	}

	ldap_thread = mem_alloc(ldapdns.ldap_threads * sizeof(ldap_ctx));
	if (!ldap_thread)
		cfatal("thread:mem_alloc: %s");
	for (i = 0; i <ldapdns.ldap_threads; i++) {
		ldap_thread[i].ldap_con = 0;

		/* one2one mode doesn't need these ... */
		if (pthread_mutex_init(&ldap_thread[i].lock, NULL) != 0)
			cfatal("pthread_mutex_init(t%d): %s", i);
		if (pthread_mutex_init(&ldap_thread[i].load_lock, NULL) != 0)
			cfatal("pthread_mutex_init(t%d): %s", i);
		if (pthread_cond_init(&ldap_thread[i].active, NULL) != 0)
			cfatal("pthread_cond_init(t%d): %s", i);
	}
	if (pthread_mutex_init(&log_lock, NULL) != 0)
		cfatal("pthread_mutex_init(log_lock): %s");
	if (pthread_mutex_init(&host_lock, NULL) != 0)
		cfatal("pthread_mutex_init(host_lock): %s");
	if (pthread_mutex_init(&handler_lock, NULL) != 0)
		cfatal("pthread_mutex_init(handler_lock): %s");
	if (pthread_mutex_init(&engine_message_mutex, NULL) != 0)
		cfatal("pthread_mutex_init(pause_lock): %s");
	if (pthread_cond_init(&engine_pause_cond, NULL) != 0)
		cfatal("pthread_cond_init(pause_cond): %s");

	x = env_get("ROOT");
	if (!x)
		fatal("$ROOT not set");
	if (chdir(x) == -1)
		cfatal("unable to chdir to %s: %s", x);

	/* 0 is ok. */
	ldapdns.notify = env_get("HELPER_NOTIFY");

	ldapdns.dn_mode = DN_MODE_COSINE;
	x = env_get("SCHEMA");
	if (x) {
		if (str_equali(x, "rfc1279"))
			ldapdns.dn_mode = DN_MODE_RFC1279;
		else if (str_equal(x, "msdns") || str_equal(x, "ad"))
			ldapdns.dn_mode = DN_MODE_MSDNS;
		else if (str_equal(x, "cosine") || str_equali(x, "ldapdns1")
					|| str_equali(x, "ldapdns-1"))
			ldapdns.dn_mode = DN_MODE_COSINE;
		else if (str_equal(x, "ldapdns") || str_equali(x, "ldapdns2")
					|| str_equali(x, "ldapdns-2"))
			ldapdns.dn_mode = DN_MODE_LDAPDNS;
		else
			fatal("$SCHEMA set to an invalid setting");
	} else {
		x = env_get("RFC1279");
		if (x)
			ldapdns.dn_mode = DN_MODE_RFC1279;
		else {
			x = env_get("DNSRECORD");
			if (x) {
				if (str_equali(x, "rfc1279"))
					ldapdns.dn_mode = DN_MODE_RFC1279;
				else if (str_equal(x, "msdns") || str_equal(x, "ad"))
					ldapdns.dn_mode = DN_MODE_MSDNS;
				else
					fatal("$DNSRECORD set to an invalid setting");
			}
		}
	}

	x = env_get("NSUPDATE");
	if (x) {
		name_to_dns(retbuf, x);
		ldapdns.update = str(retbuf);
	} else
		ldapdns.update = 0;

	x = env_get("LDAP_SUFFIX");
	if (!x)
		ldapdns.ldap_suffix = "";
	else {
		/* ldap_suffix is dynamically generated */
		str_init(retbuf);
		str_copy(retbuf, x);
		ldapdns.ldap_suffix = str(retbuf);
		
		/* x came from env; so it's ASCIIZ */
		lp = ldap_into_parts(x);
		/* reverse the list: the interesting stuff is at the other end */
		list_reverse(&lp);
		y = list_pop(&lp);
		mem_free(y);

		y = list_pop(&lp);
		/* test for the microsoft DNS suffix */
		if (y && str_equali(y, "cn=system")) {
			mem_free(y);
			y = list_pop(&lp);
			if (y && str_equali(y, "cn=microsoftdns")) {
				mem_free(y);
				ldapdns.dn_mode = DN_MODE_MSDNS;
			} else
				mem_free(y);
		} else
			mem_free(y);
		while ((y = list_pop(&lp)))
			mem_free(y);
	}

	/* this is a performance thing; PDNS claims 10k requests,
	 * i think it's a bad idea...
	 */
	x = env_get("NO_ADDITIONALS");
	if (!x) {
		x = env_get("NO_ADDITIONALS_NS");
		if (x)
			ldapdns.no_additionals = ldapdns.no_additionals_ns = 1;
		else
			ldapdns.no_additionals = ldapdns.no_additionals_ns = 0;
	} else {
		ldapdns.no_additionals = 1;
		ldapdns.no_additionals_ns = 0;
	}

	ldapdns.schedule_arecord = SCHEDULE_A_NONE;
	x = env_get("SCHEDULE_ARECORD");
	if (x) {
		if (str_equali(x, "random")) {
			ldapdns.schedule_arecord = SCHEDULE_A_RANDOM;
		}
	}

	/* this should be fine. */
	srand(time(NULL)^(getpid() << 5)^(getppid()));

	/* mwahahahah */
	x = env_get("NETBIOS");
	if (x)
		ldapdns.netbios = 1;
	else
		ldapdns.netbios = 0;

	/* self nameserver */
	ldapdns.self_ns = 0;
	x = env_get("NS_SELF");
	if (!x) x = env_get("NSSELF");
	if (!x) x = env_get("SELFNS");
	if (!x) x = env_get("SELF_NS");
	if (x) {
		str_init(retbuf);
		name_to_dns(retbuf, x);
		ldapdns.self_ns = str(retbuf);
	}

	/* this adds nameservers */
	ldapdns.peer_ns = 0;
	x = env_get("NS");
	if (!x) {
		for (i = 1;; i++) {
			sprintf(spbuf, "NS%d", i);
			x = env_get(spbuf);
			if (!x) break;
			str_init(retbuf);
			name_to_dns(retbuf, x);
			list_push(&ldapdns.peer_ns, str(retbuf));
		}
	} else {
		for (i = j = 0; x[i]; i++) {
			if (x[i] == '\r' || x[i] == '\n' || x[i] == ' '
					|| x[i] == '\t') {
				if (j < i-1) {
					str_init(retbuf);
					str_catb(retbuf, x+j, i-j);

					str_init(hbuf);
					name_to_dns(hbuf, str(retbuf));
					mem_free(str(retbuf));

					list_push(&ldapdns.peer_ns, str(hbuf));
				}
				j = i+1;
			}
		}
		if (j < i-1) {
			str_init(retbuf);
			name_to_dns(retbuf, x+j);
			list_push(&ldapdns.peer_ns, str(retbuf));
		}
	}

	/* you know what you doing... */
	ldapdns.accelerate_cache = 0;
	x = env_get("ACCELERATE_CACHE");
	if (!x)
		x = env_get("CACHE");
	if (x)
		ldapdns.accelerate_cache = atoi(x);
	if (ldapdns.accelerate_cache < 0)
		ldapdns.accelerate_cache = 0;
#ifdef ACCELERATE_CACHE
	if (ldapdns.accelerate_cache) {
		/* register garbage collection */
		mem_register_gc(0, gc_ldap_cache);
	}
#endif

	handler = mem_alloc(ldapdns.handlers * sizeof(dns_ctx));
	if (!handler)
		cfatal("handler:mem_alloc: %s");
	for (i = 0; i < ldapdns.handlers; i++) {
		/* flush handlers */
		dns_ctx *c = &handler[i];
		initialize_handler(i, c);

		c->prev = (i == 0 ? 0 : &handler[i-1]);
		c->next = (i == ldapdns.handlers-1 ? 0 : &handler[i+1]);
	}

/* core */
	x = env_get("HOSTMASTER");
	if (!x)
		fatal("$HOSTMASTER not set");
	str_init(retbuf);
	name_to_dns_fix(retbuf, x, 2);
	ldapdns.hostmaster = str(retbuf);

	ldapdns.auth_mode = AUTH_MODE_ANONYMOUS;
	x = env_get("LDAP_AUTH");
	if (x) {
		if (str_equali(x, "simple"))
			ldapdns.auth_mode = AUTH_MODE_SIMPLE;
		else if (str_equali(x, "sasl"))
			ldapdns.auth_mode = AUTH_MODE_SASL;
	}

	x = env_get("LDAP_SASL");
	if (x) {
		ldapdns.auth_mode = AUTH_MODE_SASL;
		ldapdns.ldap_name = x;
	} else {
		x = env_get("LDAP_AUTH_NAME");
		if (!x)
			x = env_get("LDAP_BINDDN");
		if (x && *x) {
			ldapdns.ldap_name = x;
			/* fixup for no $LDAP_AUTH */
			if (ldapdns.auth_mode == AUTH_MODE_ANONYMOUS)
				ldapdns.auth_mode = AUTH_MODE_SIMPLE;
		}
	}

	x = env_get("RELATIVE_NAMES");
	ldapdns.relative_names = (x ? 1 : 0);

	if (!root_read("password", &ldapdns.ldap_cred, 1)) {
		if (ldapdns.auth_mode == AUTH_MODE_SIMPLE) {
			/* no password file */
			fatal("$ROOT/password is empty");
		}
	}
	/* load the search table (if present) */
	root_read_search();

	/* read switch values (if present) */
	ldapdns.swm = 0;
	root_read_sw("switch", &ldapdns.swm);
	ldapdns.swaxfr = 0;
	root_read_sw("axfr", &ldapdns.swaxfr);

	/* try both host and hosts */
	x = env_get("LDAP_HOSTS");
	if (!x)
		x = env_get("LDAP_HOST");
	if (!x)
		fatal("$LDAP_HOST not set");

	/* load connection host/strings */
	ldapdns.hosts = 0;
	str_init(retbuf);
	str_copy(retbuf, x);
	for (h = str(retbuf); h && *h;) { /* on init: np if it infinites */
		int uri=0;
		for (x = h; *x && *x != ',' && *x != ' '
				&& *x != ';' && *x != '\t'; x++);
		if (*x) {
			*x = 0;
			x++;
		} else
			x = 0;

		p = LDAP_PORT;
		for (y = h; *y && *y != ':'; y++);
		if (*y && *y == ':') {
			if (*(y+1) && *(y+1) == '/') { /* detect ldap:// uri */
				uri=1;
			} else {
				*y = 0;
				y++;
				p = atoi(y);
				if (p == -1)
					p = LDAP_PORT;
			}
		}

		str_init(hbuf);
		str_copy(hbuf, h);
		if (!uri) {
			str_addch(hbuf, ':');
			sprintf(spbuf, "%d", p);
			str_cat(hbuf, spbuf);
		}

		list_push(&ldapdns.hosts, str(hbuf));
		h = x;
	}

	if (!ldapdns.hosts) {
		fatal("No hosts were loaded");
	}

	j = 1;
	for (i = 0, lp=ldapdns.hosts; i < ldapdns.ldap_threads; lp = lp->next) {
		if (!lp) {
			lp = ldapdns.hosts;
			if (!j) {
				fatal("cannot contact any LDAP servers (thread %d)", i);
			}
			j = 0;
		}

		if (start_ldap_connection(&ldap_thread[i], lp->str) != -1) {
			i++;
			j = 1;
		}
	}
	/* save last used */
	host_lp = lp;
	if (!lp) host_lp = ldapdns.hosts;

	/* chroot */
	if (chroot(".") == -1)
		cfatal("chroot: %s");

	x = env_get("UID");
	if (!x)
		fatal("$UID not set");
	i = atoi(x);
	if (i < 0)
		fatal("$UID invalid (not numeric)");
	x = env_get("GID");
	if (!x)
		fatal("$GID not set");
	j = atoi(x);
	if (j < 0)
		fatal("$GID invalid (not numeric)");

	x = env_get("I_AM_STUPID_LET_ME_RUN_LDAPDNS_AS_ROOT");
	if (x)
		warning("doing something very stupid (running as root)");
	else if (i == 0 || j == 0)
		fatal("refuse to run ldapdns as root");

	/* drop privs */
	if (j != getgid() && setgid(j) == -1) cfatal("setgid: %s");
	if (i != getuid() && setuid(i) == -1) cfatal("setuid: %s");

	/* setup signals */
	signal(SIGPIPE, SIG_IGN); /* i ignore this... but it's okay... */

	eph = pthread_self();
	if (ldapdns.one2one_mode) {
#ifdef ACCELERATE_CACHE
		if (ldapdns.accelerate_cache)
			log(log_info, "ldap client caching enabled!");
#endif

		log(log_info, "starting ldapdns %s (1:1/%d)", VERSION,
				ldapdns.handlers);

		/* alternate message loop */
		for (i = 0; i < ldapdns.dns_threads-1; i++) {
			ldap_thread[i].n = i;
			if (pthread_create(&ldap_thread[i].id, &helper_thread_attr,
					one2one_msgwait_loop, &ldap_thread[i]) != 0) {
				cfatal("pthread_create(%d): %s", i);
			}
		}
		/* use the main thread for something :) */
		ldap_thread[i].n = i;
		ldap_thread[i].id = pthread_self();
		one2one_msgwait_loop(&ldap_thread[i]);
		return 0;
	}

	/* start helper thread */
	for (i = 0; i < ldapdns.ldap_threads; i++) {

		/* save index */
		ldap_thread[i].n = i;
		/* null these out here */
		ldap_thread[i].message_wait = 0;
		ldap_thread[i].message_sent = 0;
		if (pthread_create(&ldap_thread[i].id, &helper_thread_attr,
					ldap_msgwait_loop, &ldap_thread[i]) != 0) {
			cfatal("pthread_create(%d): %s", i);
		}
	}

	if (ldapdns.accelerate_cache)
		log(log_info, "ldap client caching enabled!");

	log(log_info, "starting ldapdns %s (%d:%d/%d)", VERSION,
				ldapdns.ldap_threads, ldapdns.dns_threads,
				ldapdns.handlers);

	for (i = 0; i < ldapdns.dns_threads-1; i++) {
		if (pthread_create(&dummy, &helper_thread_attr,
					dns_msgwait_loop, 0) != 0) {
			cfatal("pthread_create(%d): %s", i);
		}
		bin_init(res);
		bin_copy(res, (char *)&dummy, sizeof(pthread_t));
		list_push(&other_threads, caddr(res));
	}
	dns_msgwait_loop(0);
	return 0; /* never reached */
}



syntax highlighted by Code2HTML, v. 0.9.1