/*
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. Alternatively, you may use the original license
reproduced below.

Copyright 1999 by Comstar.net, Inc., Atlanta, GA, US.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Comstar.net, Inc.
or COMSTAR not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.

COMSTAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL COMSTAR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/

#include "Python.h"
#include <adns.h>
#include <string.h>
#include <assert.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 },
	{ "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 },
	{ "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(v)
	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(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);
	return o;
}
	
static PyObject *
interpret_answer(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->s_addr));
			}
			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;
				a = Py_BuildValue("iO", v->i,
						  interpret_hostaddr(&v->ha));
			} 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_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;
		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(self, args)
	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(self, args)
	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(self, args)
	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(self, args)
	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, &(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., rbl.maps.vix.com., etc.\n\
flags must specify some kind of PTR query."
;


static PyObject *
ADNS_State_submit_reverse_any(self, args)
	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(self, args)
	ADNS_Stateobject *self;
	PyObject *args;
{
	char *owner;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	int r;
	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(self, args)
	ADNS_Stateobject *self;
	PyObject *args;
{
	fd_set rfds, wfds, efds;
	int r, maxfd;
	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/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(self, args)
	ADNS_Stateobject *self;
	PyObject *args;
{
	char *owner;
	adns_answer *answer_r;
	adns_rrtype type = 0;
	adns_queryflags flags = 0;
	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(self, args)
	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__doc__},
 {"submit_reverse_any",	(PyCFunction)ADNS_State_submit_reverse_any,	METH_VARARGS,	ADNS_State_submit__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(self, name)
	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()
{
	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(self)
	ADNS_Stateobject *self;
{
	Py_BEGIN_ALLOW_THREADS;
	adns_finish(self->state);
	Py_END_ALLOW_THREADS;
	Py_INCREF(Py_None);
	PyMem_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(self, args)
	ADNS_Queryobject *self;
	PyObject *args;
{
	adns_answer *answer_r;
	int r;
	PyObject *o, *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(self, args)
	ADNS_Queryobject *self;
	PyObject *args;
{
	adns_answer *answer_r;
	int r;
	PyObject *o, *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(self, args)
	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(self, name)
	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(state)
	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(self)
	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);
	PyMem_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(obj, f)
	PyObject *obj;
	FILE **f;
{
	*f = PyFile_AsFile(obj);
	return (*f != NULL);
}

static PyObject *
adns__init(self, args)
	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(mdict, type, table)
	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()
{
	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");
}


