/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
*/

#include "Python.h"
#include "pymemcompat.h"
#include <adns.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static PyObject *ErrorObject;
static PyObject *NotReadyError;
static PyObject *LocalError;
static PyObject *RemoteError, *RemoteFailureError, *RemoteTempError,
	*RemoteConfigError;
static PyObject *QueryError;
static PyObject *PermanentError, *NXDomainError, *NoDataError;

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

/* Declarations for objects of type ADNS_State */

typedef struct {
	PyObject_HEAD
	adns_state state;
} ADNS_Stateobject;

staticforward PyTypeObject ADNS_Statetype;



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

/* Declarations for objects of type ADNS_Query */

typedef struct {
	PyObject_HEAD
	ADNS_Stateobject *s;
	adns_query query;
	PyObject *answer;
	PyObject *exc_type;
	PyObject *exc_value;
	PyObject *exc_traceback;
} ADNS_Queryobject;

staticforward PyTypeObject ADNS_Querytype;



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

typedef struct {
	char *name;
	int value;
} _constant_class;

static _constant_class adns_iflags[] = {
	{ "noenv", adns_if_noenv },
	{ "noerrprint", adns_if_noerrprint },
	{ "noserverwarn", adns_if_noserverwarn },
	{ "debug", adns_if_debug },
	{ "logpid", adns_if_logpid },
	{ "noautosys", adns_if_noautosys },
	{ "eintr", adns_if_eintr },
	{ "nosigpipe", adns_if_nosigpipe },
	{ "checkc_entex", adns_if_checkc_entex },
	{ "checkc_freq", adns_if_checkc_freq },
	{ NULL, 0 },
};

static _constant_class adns_qflags[] = {
	{ "search", adns_qf_search },
	{ "usevc", adns_qf_usevc },
	{ "owner", adns_qf_owner },
	{ "quoteok_query", adns_qf_quoteok_query },
	{ "quoteok_cname", adns_qf_quoteok_cname },
	{ "quoteok_anshost", adns_qf_quoteok_anshost },
	{ "quotefail_cname", adns_qf_quotefail_cname },
	{ "cname_loose", adns_qf_cname_loose },
	{ "cname_forbid", adns_qf_cname_forbid },
	{ "internalmask", adns__qf_internalmask },
	{ NULL, 0 },
};

static _constant_class adns_rr[] = {
	{ "typemask", adns_rrt_typemask },
	{ "deref", adns__qtf_deref },
	{ "mail822", adns__qtf_mail822 },
	{ "unknown", adns_r_unknown },
	{ "none", adns_r_none },
	{ "A", adns_r_a },
	{ "NSraw", adns_r_ns_raw },
	{ "NS", adns_r_ns },
	{ "CNAME", adns_r_cname },
	{ "SOAraw", adns_r_soa_raw },
	{ "SOA", adns_r_soa },
	{ "PTRraw", adns_r_ptr_raw },
	{ "PTR", adns_r_ptr },
	{ "HINFO", adns_r_hinfo },
	{ "MXraw", adns_r_mx_raw },
	{ "MX", adns_r_mx },
	{ "TXT", adns_r_txt },
	{ "RPraw", adns_r_rp_raw },
	{ "RP", adns_r_rp },
	{ "SRVraw", adns_r_srv_raw },
	{ "SRV", adns_r_srv },
	{ "ADDR", adns_r_addr },
	{ NULL, 0 }
};

static _constant_class adns_s[] = {
	{ "ok", adns_s_ok },
	{ "nomemory", adns_s_nomemory },
	{ "unknownrrtype", adns_s_unknownrrtype },
	{ "systemfail", adns_s_systemfail },
	{ "max_localfail", adns_s_max_localfail },
	{ "timeout", adns_s_timeout },
	{ "allservfail", adns_s_allservfail },
	{ "norecurse", adns_s_norecurse },
	{ "invalidresponse", adns_s_invalidresponse },
	{ "unknownformat", adns_s_unknownformat },
	{ "max_remotefail", adns_s_max_remotefail },
	{ "rcodeservfail", adns_s_rcodeservfail },
	{ "rcodeformaterror", adns_s_rcodeformaterror },
	{ "rcodenotimplemented", adns_s_rcodenotimplemented },
	{ "rcoderefused", adns_s_rcoderefused },
	{ "rcodeunknown", adns_s_rcodeunknown },
	{ "max_tempfail", adns_s_max_tempfail },
	{ "inconsistent", adns_s_inconsistent },
	{ "prohibitedcname", adns_s_prohibitedcname },
	{ "answerdomaininvalid", adns_s_answerdomaininvalid },
	{ "answerdomaintoolong", adns_s_answerdomaintoolong },
	{ "invaliddata", adns_s_invaliddata },
	{ "max_misconfig", adns_s_max_misconfig },
	{ "querydomainwrong", adns_s_querydomainwrong },
	{ "querydomaininvalid", adns_s_querydomaininvalid },
	{ "querydomaintoolong", adns_s_querydomaintoolong },
	{ "max_misquery", adns_s_max_misquery },
	{ "nxdomain", adns_s_nxdomain },
	{ "nodata", adns_s_nodata },
	{ "max_permfail", adns_s_max_permfail },
	{ NULL, 0 }
};

static PyObject *
interpret_addr(
	adns_rr_addr *v
	)
{
	PyObject *o;
	o = Py_BuildValue("is", v->addr.inet.sin_family, 
			  inet_ntoa(v->addr.inet.sin_addr)) ;
	return o;
}

static PyObject *
interpret_hostaddr(
	adns_rr_hostaddr *hostaddr
	)
{
	PyObject *o, *addrs;
	if (hostaddr->naddrs == -1) {
		addrs = Py_None;
		Py_INCREF(addrs);
	} else {
		int i;
		addrs = PyTuple_New(hostaddr->naddrs);
		for (i=0; i<hostaddr->naddrs; i++) {
			adns_rr_addr *v = hostaddr->addrs+i;
			PyTuple_SET_ITEM(addrs,i,interpret_addr(v));
		}
	}
	o = Py_BuildValue("siO", hostaddr->host, hostaddr->astatus,
			  addrs);
	Py_DECREF(addrs);
	return o;
}

static PyObject *
interpret_answer(
	adns_answer *answer
	)
{
	PyObject *o, *rrs;
	int i;
	adns_rrtype t = answer->type & adns_rrt_typemask;
	adns_rrtype td = answer->type & adns__qtf_deref;

	rrs = PyTuple_New(answer->nrrs);
	if (!rrs) return NULL;
	for (i=0; i<answer->nrrs; i++) {
		PyObject *a = NULL;
		switch (t) {
		case adns_r_a:
			if (td) {
				a = interpret_addr((answer->rrs.addr+i));
			} else {
				struct in_addr *v = answer->rrs.inaddr+i;
				a = Py_BuildValue("s", inet_ntoa(*v));
			}
			break;
		case adns_r_hinfo:
			{
				adns_rr_intstrpair *v = \
					answer->rrs.intstrpair+i;
				a = Py_BuildValue("s#s#", v->array[0].str,
						  v->array[0].i,
						  v->array[1].str,
						  v->array[1].i);
			}
			break;
		case adns_r_mx_raw:
			if (td) {
				adns_rr_inthostaddr *v = \
					answer->rrs.inthostaddr+i;
				o = interpret_hostaddr(&v->ha);
				a = Py_BuildValue("iO", v->i, o);
				Py_DECREF(o);
			} else {
				adns_rr_intstr *v = answer->rrs.intstr+i;
				a = Py_BuildValue("is", v->i, v->str);
			}
			break;
		case adns_r_ptr_raw:
		case adns_r_cname:
			{
				char *(*v) = answer->rrs.str+i;
				a = PyString_FromString(*v);
			}
			break;
		case adns_r_txt:
			{
				PyObject *txt;
				int array_len = 0;
				int ai;
				adns_rr_intstr *(*s) = answer->rrs.manyistr+i;

				while ((*s)[array_len].i != -1)
					array_len++;

				if (!(a = PyTuple_New(array_len))) break;
				for (ai = 0; ai < array_len; ai++)
				{
					txt = PyString_FromStringAndSize((*s)[ai].str, (*s)[ai].i);
					if (!txt) {
						Py_DECREF(a);
						a = NULL;
						break;
					}
					PyTuple_SET_ITEM(a, ai, txt);
				}
			}
			break;
		case adns_r_ns_raw:
			if (td) {
				a = interpret_hostaddr(answer->rrs.hostaddr+i);
			} else {
				char *(*v) = answer->rrs.str+i;
				a = PyString_FromString(*v);
			}
			break;
		case adns_r_soa_raw:
			{
				adns_rr_soa *v = answer->rrs.soa+i;
				a = Py_BuildValue("sslllll", v->mname, v->rname,
						  v->serial, v->refresh, v->retry,
						  v->expire, v->minimum);
			}
			break;
		case adns_r_rp:
			{
				adns_rr_strpair *v = answer->rrs.strpair+i;
				a = Py_BuildValue("ss", v->array[0], v->array[1]);
			}
			break;
		case adns_r_srv_raw:
			if (td) {
				adns_rr_srvha *v = answer->rrs.srvha+i;
				o = interpret_hostaddr(&v->ha);
				a = Py_BuildValue("iiiO", v->priority, v->weight, v->port, o);
				Py_DECREF(o);
			} else {
				adns_rr_srvraw *v = answer->rrs.srvraw+i;
				a = Py_BuildValue("iiis", v->priority, v->weight, v->port,
						   v->host);
			}
			break;
		default:
			a = Py_None;
			Py_INCREF(a);
		}
		if (!a) {
			Py_DECREF(rrs);
			return NULL;
		}
		PyTuple_SET_ITEM(rrs, i, a);
	}
	o = Py_BuildValue("isiO", (int) answer->status, answer->cname,
			  answer->expires, rrs);
	Py_DECREF(rrs);
	return o ;
}

static char adns_exception__doc__[] = \
"exception(s)\n\
\n\
Checks the status code of an answer and raises an exception if necessary.\n";

static PyObject*
adns_exception(
	PyObject *self,
	PyObject *args
	)
{
	PyObject *e, *m;
	adns_status status;
	if (!PyArg_ParseTuple(args, "i", &status))
		return NULL;
	switch (status) {
	case adns_s_ok:
		Py_INCREF(Py_None);
		return Py_None;
	case adns_s_nomemory:
		return PyErr_NoMemory();
	case adns_s_unknownrrtype:
	case adns_s_systemfail:
		e = LocalError;
		break;
	case adns_s_timeout:
	case adns_s_allservfail:
	case adns_s_norecurse:
	case adns_s_invalidresponse:
	case adns_s_unknownformat:
		e = RemoteFailureError;
		break;
	case adns_s_rcodeservfail:
	case adns_s_rcodeformaterror:
	case adns_s_rcodenotimplemented:
	case adns_s_rcoderefused:
	case adns_s_rcodeunknown:
		e = RemoteTempError;
		break;
	case adns_s_inconsistent:
	case adns_s_prohibitedcname:
	case adns_s_answerdomaininvalid:
	case adns_s_invaliddata:
		e = RemoteConfigError;
	case adns_s_querydomainwrong:
	case adns_s_querydomaininvalid:
	case adns_s_querydomaintoolong:
		e = QueryError;
		break;
	case adns_s_nxdomain:
		e = NXDomainError;
		break;
	case adns_s_nodata:
		e = NoDataError;
		break;
	default:
		e = ErrorObject;
	}
	if (!(m=Py_BuildValue("is", status,
				 adns_strerror((adns_status) status))))
		return NULL;
	PyErr_SetObject(e, m);
	Py_DECREF(m);
	return NULL;
}

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

static char ADNS_State_synchronous__doc__[] = 
"s.synchronous(name,type[,flags]\n\
\n\
Perform a query on name synchronously for RR type, returning the answer.\n\
Answers returned as (status, cname, expires, rrs).\n\
rrs is an n-tuple, each element is a RR. Format varies by\n\
RR and query.\n"
;

static PyObject *
ADNS_State_synchronous(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	char *owner;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	adns_answer *answer_r;
	int r;
	PyObject *o;
	if (!PyArg_ParseTuple(args, "si|i", &owner, &type, &flags))
		return NULL;
	Py_BEGIN_ALLOW_THREADS;
	r = adns_synchronous(self->state, owner, type, flags, &answer_r);
	Py_END_ALLOW_THREADS;
	if (r) {
		PyErr_SetString(ErrorObject, strerror(r));
		return NULL;
	}
	o = interpret_answer(answer_r);
	free(answer_r);
	return o;
}


static char ADNS_State_submit__doc__[] = 
"s.submit(name,type[,flags])\n\
\n\
Submit a query. Returns a ADNS_Query object.\n"
;

static ADNS_Queryobject *newADNS_Queryobject(ADNS_Stateobject *state);

static PyObject *
ADNS_State_submit(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	char *owner;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	int r;
	ADNS_Queryobject *o;
	if (!PyArg_ParseTuple(args, "si|i", &owner, &type, &flags))
		return NULL;
	o = newADNS_Queryobject(self);
	Py_BEGIN_ALLOW_THREADS;
	r = adns_submit(self->state, owner, type, flags, o, &o->query);
	Py_END_ALLOW_THREADS;
	if (r) {
		PyErr_SetString(ErrorObject, strerror(r));
		return NULL;
	}
	return (PyObject *) o;
}


static char ADNS_State_submit_reverse__doc__[] = 
"s.submit_reverse(name,type[,flags])\n\
\n\
Submit a query. Returns a ADNS_Query object.\n\
flags must specify some kind of PTR query."
;


static PyObject *
ADNS_State_submit_reverse(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	char *owner;
	struct sockaddr_in addr;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	int r;
	ADNS_Queryobject *o;
	if (!PyArg_ParseTuple(args, "si|i", &owner, &type, &flags))
		return NULL;
        r = inet_aton(owner, (struct in_addr *) &(addr.sin_addr));
        if (!r) {
                PyErr_SetString(ErrorObject, "invalid IP address");
                return NULL;
        }
	addr.sin_family = AF_INET;
	o = newADNS_Queryobject(self);
	Py_BEGIN_ALLOW_THREADS;
	r = adns_submit_reverse(self->state, (struct sockaddr *)&addr, type, flags, o, &o->query);
	Py_END_ALLOW_THREADS;
	if (r) {
		PyErr_SetString(ErrorObject, strerror(r));
		return NULL;
	}
	return (PyObject *) o;
}

static char ADNS_State_submit_reverse_any__doc__[] = 
"s.submit_reverse_any(name,zone,type[,flags])\n\
\n\
Submit a query. Returns a ADNS_Query object.\n\
zone is in-addr.arpa., etc.\n\
flags must specify some kind of PTR query."
;


static PyObject *
ADNS_State_submit_reverse_any(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	char *owner, *zone;
	struct sockaddr_in addr;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	int r;
	ADNS_Queryobject *o;
	if (!PyArg_ParseTuple(args, "ssi|i", &owner, &zone, &type, &flags))
		return NULL;
        r = inet_aton(owner, &(addr.sin_addr));
        if (!r) {
                PyErr_SetString(ErrorObject, "invalid IP address");
                return NULL;
        }
	addr.sin_family = AF_INET;
	o = newADNS_Queryobject(self);
	Py_BEGIN_ALLOW_THREADS;
	r = adns_submit_reverse_any(self->state, (struct sockaddr *)&addr, zone, type, flags, o, &o->query);
	Py_END_ALLOW_THREADS;
	if (r) {
		PyErr_SetString(ErrorObject, strerror(r));
		return NULL;
	}
	return (PyObject *) o;
}


static char ADNS_State_allqueries__doc__[] = 
"s.allqueries()\n\
\n\
Returns a list of all pending queries.\n"
;


static PyObject *
ADNS_State_allqueries(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	ADNS_Queryobject *o;
	adns_query q;
	PyObject *l;

	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	if (!(l = PyList_New(0))) return NULL;
	for (adns_forallqueries_begin(self->state);
	     (q = adns_forallqueries_next(self->state, (void *)&o));
		) {
		if (PyList_Append(l, (PyObject *) o)) {
			Py_DECREF(l);
			return NULL;
		}
	}
	return l;
}



static char ADNS_State_select__doc__[] = 
"s.select(timeout=0)\n\
\n\
Like the select() system call, except it works only on\n\
pending queries, i.e. internally it does:\n\
1) adns_beforeselect\n\
2) select\n\
3) adns_afterselect\n"
;


static PyObject *
ADNS_State_select(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	fd_set rfds, wfds, efds;
	int r, maxfd=0;
	double ft = 0;
	struct timeval **tv_mod, tv_buf, now, timeout;
	struct timezone tz;

	if (!PyArg_ParseTuple(args, "|d", &ft))
		return NULL;
	timeout.tv_sec = (int) ft;
	timeout.tv_usec = (int) ((ft - timeout.tv_sec) * 1e6);
	tv_mod = NULL;
	if (gettimeofday(&now, &tz))
		return PyErr_SetFromErrno(ErrorObject);
	FD_ZERO(&rfds);
	FD_ZERO(&wfds);
	FD_ZERO(&efds);
	adns_beforeselect(self->state, &maxfd, &rfds, &wfds, &efds,
			  tv_mod, &tv_buf, &now);
	Py_BEGIN_ALLOW_THREADS;
	r = select(maxfd, &rfds, &wfds, &efds, &timeout);
	Py_END_ALLOW_THREADS;
	if (r == -1) return PyErr_SetFromErrno(ErrorObject);
	if (gettimeofday(&now, &tz))
		return PyErr_SetFromErrno(ErrorObject);
	adns_afterselect(self->state, maxfd, &rfds, &wfds, &efds, &now);
	Py_INCREF(Py_None);
	return Py_None;
}


static char ADNS_State_completed__doc__[] = 
"s.allqueries()\n\
\n\
Returns a list of all completed queries.\n"
;


static PyObject *
ADNS_State_completed(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	adns_answer *answer_r;
	int r;
	ADNS_Queryobject *o, *o2;
	adns_query q;
	PyObject *l;

	if (!(l = ADNS_State_select(self, args))) return NULL;
	Py_DECREF(l);
	if (!(l = PyList_New(0))) return NULL;
	for (adns_forallqueries_begin(self->state);
	     (q = adns_forallqueries_next(self->state, (void *)&o));
		) {
		r = adns_check(self->state, &q, &answer_r, (void *) &o2);
		if (r) {
			if (r == EWOULDBLOCK)
				continue;
			else {
				PyErr_SetString(ErrorObject, strerror(r));
				PyErr_Fetch(&(o->exc_type),
					    &(o->exc_value),
					    &(o->exc_traceback));
				continue;
			}
		}
		assert(o == o2);
		o->answer = interpret_answer(answer_r);
		free(answer_r);
		o->query = NULL;
		if (PyList_Append(l, (PyObject *) o)) {
			Py_DECREF(l);
			return NULL;
		}
	}
	return l;
}



static char ADNS_State_globalsystemfailure__doc__[] = 
""
;

static PyObject *
ADNS_State_globalsystemfailure(
	ADNS_Stateobject *self,
	PyObject *args
	)
{
	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	adns_globalsystemfailure(self->state);
	Py_INCREF(Py_None);
	return Py_None;
}


static struct PyMethodDef ADNS_State_methods[] = {
	{"synchronous",	(PyCFunction)ADNS_State_synchronous,	METH_VARARGS,	ADNS_State_synchronous__doc__},
 {"submit",	(PyCFunction)ADNS_State_submit,	METH_VARARGS,	ADNS_State_submit__doc__},
 {"submit_reverse",	(PyCFunction)ADNS_State_submit_reverse,	METH_VARARGS,	ADNS_State_submit_reverse__doc__},
 {"submit_reverse_any",	(PyCFunction)ADNS_State_submit_reverse_any,	METH_VARARGS,	ADNS_State_submit_reverse_any__doc__},
 {"allqueries",	(PyCFunction)ADNS_State_allqueries,	METH_VARARGS,	ADNS_State_allqueries__doc__},
 {"completed",	(PyCFunction)ADNS_State_completed,	METH_VARARGS,	ADNS_State_completed__doc__},
 {"select",	(PyCFunction)ADNS_State_select,	METH_VARARGS,	ADNS_State_select__doc__},
 {"globalsystemfailure",	(PyCFunction)ADNS_State_globalsystemfailure,	METH_VARARGS,	ADNS_State_globalsystemfailure__doc__},
 
	{NULL,		NULL}		/* sentinel */
};

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


static PyObject *
ADNS_State_getattr(
	ADNS_Stateobject *self,
	char *name
	)
{
	/* XXXX Add your own getattr code here */
	return Py_FindMethod(ADNS_State_methods, (PyObject *)self, name);
}

static ADNS_Stateobject *
newADNS_Stateobject(void)
{
	ADNS_Stateobject *self;
	
	self = PyObject_New(ADNS_Stateobject, &ADNS_Statetype);
	if (self == NULL)
		return NULL;
	self->state = NULL;
	return self;
}


static void
ADNS_State_dealloc(ADNS_Stateobject *self)
{
	Py_BEGIN_ALLOW_THREADS;
	adns_finish(self->state);
	Py_END_ALLOW_THREADS;
	Py_INCREF(Py_None);
	PyObject_Del(self);
}

static char ADNS_Statetype__doc__[] = 
"Contains state information for adns session."
;

static PyTypeObject ADNS_Statetype = {
	PyObject_HEAD_INIT(&PyType_Type)
	0,				/*ob_size*/
	"ADNS_State",			/*tp_name*/
	sizeof(ADNS_Stateobject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	(destructor)ADNS_State_dealloc,	/*tp_dealloc*/
	(printfunc)0,		/*tp_print*/
	(getattrfunc)ADNS_State_getattr,	/*tp_getattr*/
	(setattrfunc)0,	/*tp_setattr*/
	(cmpfunc)0,		/*tp_compare*/
	(reprfunc)0,		/*tp_repr*/
	0,			/*tp_as_number*/
	0,		/*tp_as_sequence*/
	0,		/*tp_as_mapping*/
	(hashfunc)0,		/*tp_hash*/
	(ternaryfunc)0,		/*tp_call*/
	(reprfunc)0,		/*tp_str*/

	/* Space for future expansion */
	0L,0L,0L,0L,
	ADNS_Statetype__doc__ /* Documentation string */
};

/* End of code for ADNS_State objects */
/* -------------------------------------------------------- */


static char ADNS_Query_check__doc__[] = 
"answer = q.check()\n\
\n\
Check for an answer. Return value same as for\n\
s.synchronous.\n"
;

static PyObject *
ADNS_Query_check(
	ADNS_Queryobject *self,
	PyObject *args
	)
{
	adns_answer *answer_r;
	int r;
	PyObject *o2=(PyObject *)self;

	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	if (self->exc_type) {
		PyErr_Restore(self->exc_type, self->exc_value, self->exc_traceback);
		self->exc_type = self->exc_value = self->exc_traceback = NULL;
		return NULL;
	}
	if (self->answer) goto ret_answer;
	if (!(self->query)) {
		PyErr_SetString(ErrorObject, "query invalidated");
		return NULL;
	}
	r = adns_check(self->s->state, &self->query, &answer_r, (void *) &o2);
	if (r) {
		if (r == EWOULDBLOCK)
			PyErr_SetString(NotReadyError, strerror(r));
		else {
			PyErr_SetString(ErrorObject, strerror(r));
			self->query = NULL;
		}
		return NULL;
	}
	assert(o2 == (PyObject *) self);
	self->answer = interpret_answer(answer_r);
	self->query = NULL;
	free(answer_r);
  ret_answer:
	Py_INCREF(self->answer);
	return self->answer;
}


static char ADNS_Query_wait__doc__[] = 
"answer=q.wait()\n\
\n\
Wait for an answer. Return value same as for\n\
s.synchronous.\n"
;

static PyObject *
ADNS_Query_wait(
	ADNS_Queryobject *self,
	PyObject *args
	)
{
	adns_answer *answer_r;
	int r;
	PyObject *o2=(PyObject *)self;

	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	if (self->exc_type) {
		PyErr_Restore(self->exc_type, self->exc_value, self->exc_traceback);
		self->exc_type = self->exc_value = self->exc_traceback = NULL;
		return NULL;
	}
	if (self->answer) goto ret_answer;
	if (!(self->query)) {
		PyErr_SetString(ErrorObject, "query invalidated");
		return NULL;
	}
	Py_BEGIN_ALLOW_THREADS;
	r = adns_wait(self->s->state, &self->query, &answer_r, (void *) &o2);
	Py_END_ALLOW_THREADS;
	if (r) {
		if (r == EWOULDBLOCK)
			PyErr_SetString(NotReadyError, strerror(r));
		else {
			PyErr_SetString(ErrorObject, strerror(r));
			self->query = NULL;
		}
		return NULL;
	}
	assert(o2 == (PyObject *) self);
	self->answer = interpret_answer(answer_r);
	self->query = NULL;
	free(answer_r);
  ret_answer:
	Py_INCREF(self->answer);
	return self->answer;
}


static char ADNS_Query_cancel__doc__[] = 
"q.cancel()\n\
\n\
Cancel a pending query.\n"
;

static PyObject *
ADNS_Query_cancel(
	ADNS_Queryobject *self,
	PyObject *args
	)
{
	if (!PyArg_ParseTuple(args, ""))
		return NULL;
	if (!(self->query)) {
		PyErr_SetString(ErrorObject, "query invalidated");
		return NULL;
	}
	Py_BEGIN_ALLOW_THREADS;
	adns_cancel(self->query);
	Py_END_ALLOW_THREADS;
	Py_INCREF(Py_None);
	self->query = NULL;
	return Py_None;
}


static struct PyMethodDef ADNS_Query_methods[] = {
	{"check",	(PyCFunction)ADNS_Query_check,	METH_VARARGS,	ADNS_Query_check__doc__},
 {"wait",	(PyCFunction)ADNS_Query_wait,	METH_VARARGS,	ADNS_Query_wait__doc__},
 {"cancel",	(PyCFunction)ADNS_Query_cancel,	METH_VARARGS,	ADNS_Query_cancel__doc__},
 
	{NULL,		NULL}		/* sentinel */
};

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


static PyObject *
ADNS_Query_getattr(
	ADNS_Queryobject *self,
	char *name
	)
{
	/* XXXX Add your own getattr code here */
	return Py_FindMethod(ADNS_Query_methods, (PyObject *)self, name);
}

static ADNS_Queryobject *
newADNS_Queryobject(ADNS_Stateobject *state)
{
	ADNS_Queryobject *self;
	
	self = PyObject_New(ADNS_Queryobject, &ADNS_Querytype);
	if (self == NULL)
		return NULL;
	Py_INCREF(state);
	self->s = state;
	self->query = NULL;
	self->answer = NULL;
	self->exc_type = NULL;
	self->exc_value = NULL;
	self->exc_traceback = NULL;
	return self;
}


static void
ADNS_Query_dealloc(ADNS_Queryobject *self)
{
	Py_DECREF(self->s);
	Py_XDECREF(self->answer);
	Py_XDECREF(self->exc_type);
	Py_XDECREF(self->exc_value);
	Py_XDECREF(self->exc_traceback);
	PyObject_Del(self);
}

static char ADNS_Querytype__doc__[] = 
"A query currently being processed by adns."
;

static PyTypeObject ADNS_Querytype = {
	PyObject_HEAD_INIT(&PyType_Type)
	0,				/*ob_size*/
	"ADNS_Query",			/*tp_name*/
	sizeof(ADNS_Queryobject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	(destructor)ADNS_Query_dealloc,	/*tp_dealloc*/
	(printfunc)0,		/*tp_print*/
	(getattrfunc)ADNS_Query_getattr,	/*tp_getattr*/
	(setattrfunc)0,	/*tp_setattr*/
	(cmpfunc)0,		/*tp_compare*/
	(reprfunc)0,		/*tp_repr*/
	0,			/*tp_as_number*/
	0,		/*tp_as_sequence*/
	0,		/*tp_as_mapping*/
	(hashfunc)0,		/*tp_hash*/
	(ternaryfunc)0,		/*tp_call*/
	(reprfunc)0,		/*tp_str*/

	/* Space for future expansion */
	0L,0L,0L,0L,
	ADNS_Querytype__doc__ /* Documentation string */
};

/* End of code for ADNS_Query objects */
/* -------------------------------------------------------- */


static char adns_init__doc__[] =
"s=adns.init([initflags,debugfileobj=stderr,configtext=''])\n\
\n\
Initialize an ADNS_State object, which contains state information\n\
used internally by adns."
;

int
_file_converter(
	PyObject *obj,
	FILE **f
	)
{
	*f = PyFile_AsFile(obj);
	return (*f != NULL);
}

static PyObject *
adns__init(
	PyObject *self,	/* Not used */
	PyObject *args
	)
{
	adns_initflags flags = 0;
	int status;
	FILE *diagfile = NULL;
	char *configtext = NULL;
	ADNS_Stateobject *s;

	if (!PyArg_ParseTuple(args, "|iO&s",
			      &flags, _file_converter, &diagfile, &configtext))
		return NULL;
	if (!(s = newADNS_Stateobject())) return NULL;
	if (configtext)
		status = adns_init_strcfg(&s->state, flags,
					  diagfile, configtext);
	else
		status = adns_init(&s->state, flags, diagfile);
	if (status) {
		PyErr_SetFromErrno(ErrorObject);
		ADNS_State_dealloc(s);
		return NULL;
	}
	return (PyObject *) s;
}

/* List of methods defined in the module */

static struct PyMethodDef adns_methods[] = {
	{"init",	(PyCFunction)adns__init,	METH_VARARGS,	adns_init__doc__},
	{"exception",(PyCFunction)adns_exception, METH_VARARGS, adns_exception__doc__},
 
	{NULL,	 (PyCFunction)NULL, 0, NULL}		/* sentinel */
};


/* Initialization function for the module (*must* be called initadns) */

static char adns_module_documentation[] = 
""
;

static PyObject *
_new_exception(
	PyObject *dict,
	char *name,
	PyObject *base
	)
{
	PyObject *v;
	char longname[256];
	
	sprintf(longname, "adns.%s", name);
	if ((v = PyErr_NewException(longname, base, NULL)) == NULL)
		return NULL;
	if (PyDict_SetItemString(dict, name, v)) return NULL;
	return v;
}

static int
_new_constant_class(
	PyObject *mdict,
	char *type,
	_constant_class *table
	)
{
	PyObject *d, *c, *v;
	int i;
	/* XXX This leaks memory if it fails, but then the whole module
	   fails to import, so probably no big deal */
	if (!(d = PyDict_New())) goto error;
	for (i = 0; table[i].name; i++) {
		if (!(v = PyInt_FromLong((long)table[i].value))) goto error;
		if (PyDict_SetItemString(d, table[i].name, v)) goto error;
	}
	if (!(c = PyClass_New(NULL,d,PyString_InternFromString(type)))) goto error;
	if (PyDict_SetItemString(mdict, type, c)) goto error;
	return 0;
  error:
	Py_XDECREF(d);
	return -1;
}

void
initadns(void)
{
	PyObject *m, *d;

	/* Create the module and add the functions */
	m = Py_InitModule4("adns", adns_methods,
		adns_module_documentation,
		(PyObject*)NULL,PYTHON_API_VERSION);

	/* Add some symbolic constants to the module */
	d = PyModule_GetDict(m);
	ErrorObject = _new_exception(d, "Error", PyExc_StandardError);
	NotReadyError = _new_exception(d, "NotReady", ErrorObject);
	LocalError = _new_exception(d, "LocalError", ErrorObject);
	RemoteError = _new_exception(d, "RemoteError", ErrorObject);
	RemoteFailureError = _new_exception(d, "RemoteFailureError", RemoteError);
	RemoteTempError = _new_exception(d, "RemoteTempError", RemoteError);
	RemoteConfigError = _new_exception(d, "RemoteConfigError", RemoteError);
	QueryError = _new_exception(d, "QueryError", ErrorObject);
	PermanentError = _new_exception(d, "PermanentError", ErrorObject);
	NXDomainError = _new_exception(d, "NXDomain", PermanentError);
	NoDataError = _new_exception(d, "NoData", PermanentError);

	/* XXXX Add constants here */
	_new_constant_class(d, "iflags", adns_iflags);
	_new_constant_class(d, "qflags", adns_qflags);
	_new_constant_class(d, "rr", adns_rr);
	_new_constant_class(d, "status", adns_s);
	/* Check for errors */
	if (PyErr_Occurred())
		Py_FatalError("can't initialize module adns");
}


