/*
 * Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: dnstsk.c,v 1.88 2006/12/29 01:25:35 ca Exp $")
#include "sm/ctype.h"
#include "sm/io.h"	/* sm_fd_nonblock() */
#include "sm/dns.h"
#include "sm/dns-int.h"
#include "sm/dnstsk.h"
#include "dns.h"
#define LIBDNS_LOG_DEFINES 1
#include "log.h"

/*
**  Note: for some unknown reasons the query in the answer packet
**  does not contain a trailing dot even if the original query did.
**  Hence the code that generates a key removes trailing dots.
**  ToDo: It would be nice to put that key generation code into one place
**  (macro/function).
*/

/* when to wakeup dns_tsk_cleanup; should be "never" by default, see comments */
#define TSK_CLEANUP_SLP	60
#define MIN_TSK_CLEANUP_SLP	1
static sm_ret_T	 dns_tsk_cleanup(sm_evthr_task_P _tsk);

#if !MTA_USE_PTHREADS

/*
**  ToDo: Build a version for state threads... add macros to deal with the
**  differences between event threads and state threads...
*/

/* HACK */
# define EVTHR_DEL	1
# define EVTHR_WAITQ	0
# define sm_evthr_task_P void *
#endif /* !MTA_USE_PTHREADS */

/*
**  ToDo:
**	- fix hash key (query + type); done, but it's still ugly.
**	- deal with partial reads (requeue, store state in dns_tsk)
*/

/*
**  DNS_TSK_DEL -- delete a DNS task
**
**	Parameters:
**		dns_tsk -- DNS task context
**
**	Return value:
**		usual sm_error code
*/

sm_ret_T
dns_tsk_del(dns_tsk_P dns_tsk)
{
	if (NULL == dns_tsk)
		return SM_SUCCESS;
	if (is_valid_socket(dns_tsk->dnstsk_fd)) {
		close(dns_tsk->dnstsk_fd);
		dns_tsk->dnstsk_fd = INVALID_SOCKET;
	}
	SM_STR_FREE(dns_tsk->dnstsk_wr);
	SM_STR_FREE(dns_tsk->dnstsk_rd);
#if MTA_USE_PTHREADS
	if (DNS_TSK_IS_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX)) {
		DNS_TSK_CLR_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX);
		(void) pthread_mutex_destroy(&dns_tsk->dnstsk_mutex);
	}
#endif
	sm_free_size(dns_tsk, sizeof(*dns_tsk));
	return SM_SUCCESS;
}

/*
**  DNS_TSK_NEW -- make a dns_tsk
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		flags -- flags
**		ipv4 -- IPv4 address of DNS resolver
**		pdns_tsk -- (pointer to) DNS task context (output)
**
**	Return value:
**		usual sm_error code
*/

sm_ret_T
dns_tsk_new(dns_mgr_ctx_P dns_mgr_ctx, uint flags, uint32_t ipv4, dns_tsk_P *pdns_tsk)
{
	sm_ret_T ret;
	uint u;
	dns_tsk_P dns_tsk;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_REQUIRE(pdns_tsk != NULL);

	u = dns_mgr_ctx->dnsmgr_ntsks;
	if (u >= SM_DNS_MAX_TSKS)
		return sm_error_perm(SM_EM_DNS, SM_E_FULL);
	dns_tsk = (dns_tsk_P) sm_zalloc(sizeof(*dns_tsk));
	if (NULL == dns_tsk)
		return sm_error_temp(SM_EM_DNS, ENOMEM);
	dns_tsk->dnstsk_fd = INVALID_SOCKET;
	dns_tsk->dnstsk_flags = flags & DNS_TSK_FL_OPT_MSK;
	dns_tsk->dnstsk_mgr = dns_mgr_ctx;
	dns_tsk->dnstsk_wr = sm_str_new(NULL, HFIXEDSZ + MAXPACKET,
						MAX_QUERY_SIZE);
	if (NULL == dns_tsk->dnstsk_wr) {
		ret = sm_error_temp(SM_EM_DNS, ENOMEM);
		goto error;
	}
	dns_tsk->dnstsk_rd = sm_str_new(NULL, HFIXEDSZ + MAXPACKET,
						MAX_QUERY_SIZE);
	if (NULL == dns_tsk->dnstsk_rd) {
		ret = sm_error_temp(SM_EM_DNS, ENOMEM);
		goto error;
	}

	/* Set up the socket description */
	sm_memzero(&dns_tsk->dnstsk_sin, sizeof(dns_tsk->dnstsk_sin));
	dns_tsk->dnstsk_sin.sin_family = AF_INET;
	dns_tsk->dnstsk_sin.sin_port = htons(DNS_PORT);
	sm_memcpy(&dns_tsk->dnstsk_sin.sin_addr.s_addr, &ipv4, sizeof(ipv4));

#if MTA_USE_PTHREADS
	r = pthread_mutex_init(&dns_tsk->dnstsk_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_DNS, r);
		goto error;
	}
	DNS_TSK_SET_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX);
#endif /* MTA_USE_PTHREADS */

	dns_tsk->dnstsk_fd = socket(dns_tsk->dnstsk_sin.sin_family,
				(flags & DNS_TSK_FL_USETCP) ? SOCK_STREAM
							 : SOCK_DGRAM, 0);
	if (!is_valid_socket(dns_tsk->dnstsk_fd)) {
		ret = sm_error_temp(SM_EM_DNS, errno);
		goto error;
	}
	if ((flags & (DNS_TSK_FL_USETCP|DNS_TSK_FL_CONNECTUDP)) != 0) {
		if (connect(dns_tsk->dnstsk_fd,
			(sockaddr_P) &dns_tsk->dnstsk_sin,
			sizeof(sockaddr_in_T)) != 0)
		{
			ret = sm_error_temp(SM_EM_DNS, errno);
			goto error;
		}
	}
	ret = sm_fd_nonblock(dns_tsk->dnstsk_fd, true);
	if (sm_is_err(ret))
		goto error;
	DNSTRQL_INIT(dns_tsk);
	dns_tsk->dnstsk_idx = u;
	dns_tsk->dnstsk_maxtimeouts = DNS_MAXTIMEOUTS;
	dns_mgr_ctx->dnsmgr_dnstsks[u] = dns_tsk;
	dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_INIT;
	++dns_mgr_ctx->dnsmgr_ntsks;

	*pdns_tsk = dns_tsk;
	return SM_SUCCESS;

  error:
	(void) dns_tsk_del(dns_tsk);
	return ret;
}

/*
**  DNS_TSK_START -- start all(?) DNS tasks
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		evthr_ctx -- Event thread system context
**
**	Return value:
**		usual sm_error code
*/

sm_ret_T
dns_tsk_start(dns_mgr_ctx_P dns_mgr_ctx, sm_evthr_ctx_P evthr_ctx)
{
	sm_ret_T ret;
	uint u;
	sm_evthr_task_P	task;
	timeval_T sleept;

	for (u = 0; u < dns_mgr_ctx->dnsmgr_ntsks; u++) {
		task = NULL;
		ret = evthr_task_new(evthr_ctx, &task, EVTHR_EV_RD,
				dns_mgr_ctx->dnsmgr_dnstsks[u]->dnstsk_fd,
				NULL, dns_comm_tsk,
				(void *) dns_mgr_ctx->dnsmgr_dnstsks[u]);
		if (sm_is_err(ret))
			goto error;
		dns_mgr_ctx->dnsmgr_tsk[u] = task;
		dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_OK;
	}

	ret = evthr_timeval(evthr_ctx, &sleept);
	sleept.tv_sec += TSK_CLEANUP_SLP;
	ret = evthr_task_new(evthr_ctx, &task, EVTHR_EV_SL, INVALID_FD, &sleept,
				dns_tsk_cleanup, (void *) dns_mgr_ctx);
	if (sm_is_err(ret))
		goto error;
	dns_mgr_ctx->dnsmgr_cleanup = task;
	return ret;

  error:
	/* Is this ok?? How to properly terminate task?? */
	for (u = 0; u < dns_mgr_ctx->dnsmgr_ntsks; u++) {
		task = dns_mgr_ctx->dnsmgr_tsk[u];
		if (task != NULL) {
			evthr_task_del(evthr_ctx, task, THR_LOCK_UNLOCK);
			dns_mgr_ctx->dnsmgr_tsk[u] = NULL;
		}
		dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_NONE;
	}
	task = dns_mgr_ctx->dnsmgr_cleanup;
	if (task != NULL) {
		evthr_task_del(evthr_ctx, task, THR_LOCK_UNLOCK);
		dns_mgr_ctx->dnsmgr_cleanup = NULL;
	}
	return ret;
}

/*
**  DNS_REQ_FREE -- free a DNS request,
**		remove it from list if dns_mgr_ctx is not NULL.
**
**	Parameters:
**		dns_req -- DNS request
**		dns_mgr_ctx -- DNS manager context
**
**	Return value:
**		usual sm_error code
**
**	Locking: dns_mgr_ctx must be locked by caller.
*/

static sm_ret_T
dns_req_free(dns_req_P dns_req, dns_mgr_ctx_P dns_mgr_ctx)
{
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_req_free, dns_req=%p, ctx=%p, inlist=%d\n",
		dns_req, dns_mgr_ctx,
		NULL == dns_mgr_ctx ? -1 :  DNSREQ_IS_INANYLIST(dns_req)));
	if (NULL == dns_req)
		return SM_SUCCESS;
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_req_free, dns_req=%p, key=%p, query=%p\n",
		dns_req, dns_req->dnsreq_key, dns_req->dnsreq_query));
	SM_CSTR_FREE(dns_req->dnsreq_query);
	SM_STR_FREE(dns_req->dnsreq_key);
	if (dns_mgr_ctx != NULL) {
		if (DNSREQ_IS_ININCLIST(dns_req))
			DNSIRQL_REMOVE(dns_mgr_ctx, dns_req);
		else if (DNSREQ_IS_INWAITLIST(dns_req))
			DNSWRQL_REMOVE(dns_mgr_ctx, dns_req);
	}
	sm_free_size(dns_req, sizeof(*dns_req));
	return SM_SUCCESS;
}

/*
**  DNS_REQ_DEL -- free a DNS request
**	(wrapper for bht_*(); still necessary?)
**
**	Parameters:
**		value -- DNS request
**		key -- key (ignored)
**		ctx -- DNS manager context
**
**	Return value:
**		usual sm_error code
**
**	Locking: dns_mgr_ctx must be locked by caller.
*/

void
dns_req_del(void *value, void *key, void *ctx)
{
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_req_del, dns_req=%p\n",
		value));
	(void) dns_req_free((dns_req_P) value, (dns_mgr_ctx_P) ctx);
}

/*
**  DNS_CRT_KEY -- create a key for a DNS query
**
**	Parameters:
**		query -- DNS query
**		type -- DNS type
**		pkey -- (pointer to) key (output)
**		pkeylen -- (pointer to) keylen (output)
**
**	Return value:
**		usual sm_error code
*/

static sm_ret_T
dns_crt_key(sm_cstr_P query, dns_type_T type, sm_str_P *pkey, size_t *pkeylen)
{
	sm_str_P key;
	size_t keylen;
	sm_ret_T ret;

	SM_REQUIRE(pkey != NULL);
	SM_REQUIRE(pkeylen != NULL);

	keylen = sm_cstr_getlen(query) + sizeof(type);
	key = sm_str_new(NULL, keylen, keylen + 4);
	if (NULL == key) {
		ret = sm_error_temp(SM_EM_DNS, ENOMEM);
		goto error;
	}
	ret = sm_str_scatn(key, (const char *) sm_cstr_data(query),
			sm_cstr_getlen(query));
	if (sm_is_err(ret))
		goto error;
	ret = sm_str_rm_trail(key, ".");
	if (sm_is_err(ret))
		goto error;
	keylen -= ret;		/* decrement keylen if '.' has been removed */

	/* always lower case?! */
	sm_str2lower(key);
	ret = sm_str_scatn(key, (const char *) &type, sizeof(type));
	if (sm_is_err(ret))
		goto error;
	SM_ASSERT(keylen == sm_str_getlen(key));

	*pkey = key;
	*pkeylen = keylen;
	return SM_SUCCESS;

  error:
	if (key != NULL)
		sm_str_free(key);
	*pkey = NULL;
	*pkeylen = 0;
	return ret;
}

/*
**  DNS_TSK_SELECT -- select a DNS task
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		now -- current time
**		wakeup -- "wake up" the selected DNS task?
**		pidx -- (pointer to) selected DNS task (output)
**		locktype -- kind of locking
**
**	Return value:
**		usual sm_error code
**
**	Locking: locks dns_mgr_ctx if requested
*/

static sm_ret_T
dns_tsk_select(dns_mgr_ctx_P dns_mgr_ctx, time_T now, bool wakeup, ushort *pidx, thr_lock_T locktype)
{
	sm_ret_T ret;
#if MTA_USE_PTHREADS
	int r;
#endif
	ushort u, j, i;
	time_T chg;

#if MTA_USE_PTHREADS
	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_CRIT, 4,
				"sev=CRIT, func=dns_tsk_select, lock=%d", r);
			return sm_error_perm(SM_EM_DNS, r);
		}
	}
#endif /* MTA_USE_PTHREADS */

	/* round robin?? or select server with least open queries? */
	chg = TIME_T_MAX;
	i = USHRT_MAX;
	u = dns_mgr_ctx->dnsmgr_ctsk;
	ret = SM_SUCCESS;
	for (j = 0; j < SM_DNS_MAX_TSKS; j++) {
		/* found a working DNS task? */
		if (dns_mgr_ctx->dnsmgr_tsk[u] != NULL &&
		    dns_mgr_ctx->dnsmgr_tskstatus[u] == DNSTSK_ST_OK)
			break;
		if (dns_mgr_ctx->dnsmgr_tskchg[u] > 0 &&
		    dns_mgr_ctx->dnsmgr_tskstatus[u] == DNSTSK_ST_DEAD
		    && dns_mgr_ctx->dnsmgr_tskchg[u] < chg)
		{
			chg = dns_mgr_ctx->dnsmgr_tskchg[u];
			i = u;
		}
		u = ++dns_mgr_ctx->dnsmgr_ctsk;
		if (u >= dns_mgr_ctx->dnsmgr_ntsks)
			u = dns_mgr_ctx->dnsmgr_ctsk = 0;
	}
	if (j >= SM_DNS_MAX_TSKS) {
		if (i < dns_mgr_ctx->dnsmgr_ntsks) {
			u = i;
			dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_OK;
			dns_mgr_ctx->dnsmgr_tskchg[u] = now;
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER,
				LIBDNS_LMOD_RESOLVER,
				SM_LOG_WARN, 4,
				"sev=WARN, func=dns_req_add, status=all dns servers considered unresponsive, action=selected one with oldest status change");
		}
		else {
			u = dns_mgr_ctx->dnsmgr_ctsk;
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER,
				LIBDNS_LMOD_RESOLVER,
				SM_LOG_ERROR, 1,
				"sev=ERROR, func=dns_req_add, status=all dns servers considered unresponsive, action=selected one round robin");
		}
	}

	/*
	**  Ignore return value: in the worst case the request will
	**  not be sent to a DNS server but a timeout error will be
	**  returned to the caller, hence we'll ignore any error
	**  (for now).
	*/

	if (wakeup) {
		dns_tsk_P dns_tsk;

		dns_tsk = dns_mgr_ctx->dnsmgr_dnstsks[u];
#if MTA_USE_PTHREADS
		if (thr_lock_it_s1(locktype)) {
			r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex);
			SM_LOCK_OK(r);
			if (r != 0) {
				sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
					LIBDNS_LCAT_RESOLVER,
					LIBDNS_LMOD_RESOLVER,
					SM_LOG_CRIT, 4,
					"sev=CRIT, func=dns_tsk_select, lock_tsk=%d"
					, r);
				ret = sm_error_perm(SM_EM_DNS, r);
				goto unlock;
			}
		}
#endif /* MTA_USE_PTHREADS */

		/* todo: access should be locked */
		if (!DNS_TSK_IS_FLAG(dns_tsk, DNS_TSK_FL_WR_EN)) {
			(void) evthr_en_wr(dns_mgr_ctx->dnsmgr_tsk[u]);
			DNS_TSK_SET_FLAG(dns_tsk, DNS_TSK_FL_WR_EN);
		}
#if MTA_USE_PTHREADS
		if (thr_unl_always_s1(locktype)) {
			r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex);
			SM_ASSERT(r == 0);
		}
#endif /* MTA_USE_PTHREADS */
	}

#if MTA_USE_PTHREADS
  unlock:
	if (thr_unl_always(locktype)) {
		r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
		SM_ASSERT(r == 0);
	}
#endif /* MTA_USE_PTHREADS */

	if (pidx != NULL)
		*pidx = u;
	return ret;
}

/*
**  DNS_REQ_ADD -- Add a DNS request to the queue.
**	This adds a DNS request to the hash table and if it isn't yet in the
**	request list also to that.
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		query -- DNS query (MUST be '\0' terminated; SM_CSTR_DUP()ed)
**		type -- DNS type
**		timeout -- timeout (if 0: use default)
**		fct -- callback to invoke with DNS result
**		ctx -- context to pass to callback
**
**	Return value:
**		usual sm_error code: an error will only be returned
**		if the request was NOT added to the request list and hence
**		the caller can't expect a reply.
**
**	Locking: dns_mgr_ctx is locked as necessary.
*/

sm_ret_T
dns_req_add(dns_mgr_ctx_P dns_mgr_ctx, sm_cstr_P query, dns_type_T type, uint timeout, dns_callback_F *fct, void *ctx)
{
	sm_ret_T ret;
#if MTA_USE_PTHREADS
	int r;
#endif
	bool add2ht;
	dns_req_P dns_req;
	bht_entry_P bhte;
	sm_str_P key;
	size_t keylen;
	time_T now;
	timeval_T nowt;

	SM_REQUIRE(query != NULL);
	dns_req = (dns_req_P) sm_zalloc(sizeof(*dns_req));
	if (NULL == dns_req)
		return sm_error_temp(SM_EM_DNS, ENOMEM);
	ret = SM_SUCCESS;
	key = NULL;
	add2ht = false;
	dns_req->dnsreq_query = SM_CSTR_DUP(query);
	dns_req->dnsreq_type = type;

	(void) evthr_timeval(dns_mgr_ctx->dnsmgr_cleanup->evthr_t_ctx,
				&nowt);
#if DNS_STATISTICS
	dns_req->dnsreq_startt = nowt;
#endif

	now = nowt.tv_sec;
	dns_req->dnsreq_endt = now + ((timeout == 0)
					? dns_mgr_ctx->dnsmgr_timeout
					: timeout);

	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_req_add, now=%ld, endt=%ld\n",
		(long) now, (long) dns_req->dnsreq_endt));

	/*
	**  Create key from query and type... this is still ugly...
	**  Note: we can't use the output of res_mkquery...
	*/

	ret = dns_crt_key(query, type, &key, &keylen);
	if (sm_is_err(ret))
		goto error;
	dns_req->dnsreq_key = key;
	dns_req->dnsreq_fct = fct;
	dns_req->dnsreq_ctx = ctx;
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
			LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
			SM_LOG_CRIT, 4,
			"sev=CRIT, func=dns_req_add, lock=%d", r);
		ret = sm_error_perm(SM_EM_DNS, r);
		goto error;
	}
#endif

	/* Is there already a request for this? */
	bhte = bht_locate(dns_mgr_ctx->dnsmgr_req_ht,
			(const char *) sm_str_data(key),
			(uint) sm_str_getlen(key));

	add2ht = (NULL == bhte);

	/* Always add the request to the hash table so it can be answered */
	ret = bht_add(dns_mgr_ctx->dnsmgr_req_ht,
			(char *) sm_str_data(dns_req->dnsreq_key),
			(uint) sm_str_getlen(dns_req->dnsreq_key),
			dns_req, &bhte);
	if (sm_is_err(ret)) {
		sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
			LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
			SM_LOG_ERROR, 4,
			"sev=ERROR, func=dns_req_add, bhte=%p, bht_add=%r, key='%s'[%p], len=%d\n",
			bhte, ret, sm_str_data(dns_req->dnsreq_key),
			dns_req->dnsreq_key,
			sm_str_getlen(dns_req->dnsreq_key));
	}
	else if (add2ht) {
		/* entry was not in hash table: add it to the list */
		DNSIRQL_INSERT_TAIL(dns_mgr_ctx, dns_req);
		DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_INC);
	}

	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_req_add, bht_add bhte=%p, ret=%x, key='%s'[%p], len=%d\n",
		bhte, ret, sm_str_data(dns_req->dnsreq_key),
		dns_req->dnsreq_key, sm_str_getlen(dns_req->dnsreq_key)));

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_ASSERT(r == 0);
	/* ignore error; see Return values above for an explanation */

	if (sm_is_success(ret) && add2ht)
		(void) dns_tsk_select(dns_mgr_ctx, now, true, NULL,
				 THR_LOCK_UNLOCK|THR_LOCK_UNLOCK_S1);
	if (sm_is_success(ret) && dns_mgr_ctx->dnsmgr_cleanup != NULL) {
		timeval_T slpt, nowt, newt;

		/* set timeout for cleanup task */
		(void) evthr_timeval(dns_mgr_ctx->dnsmgr_cleanup->evthr_t_ctx,
				&nowt);
		slpt.tv_usec = 0;
		slpt.tv_sec = dns_mgr_ctx->dnsmgr_timeout;
		timeradd(&nowt, &slpt, &newt);
		(void) evthr_new_sl(dns_mgr_ctx->dnsmgr_cleanup, newt, false);
	}
#endif /* MTA_USE_PTHREADS */

	return ret;

  error:
	if (dns_req != NULL)
		dns_req_free(dns_req, NULL);
	return ret;
}

/* context for dns_bht2reql_fct() */
struct bht2reql_S
{
	dns_mgr_ctx_P	 b2r_dns_mgr_ctx;
	dns_rql_P	 b2r_rql_hd;
};

typedef struct bht2reql_S	bht2reql_T, *bht2reql_P;

/*
**  DNS_BHT2REQL_FCT -- move DNS requests from hash table into local list.
**	callback for bht_rm_all() used in dns_bht2reql() below.
**
**	Parameters:
**		value -- DNS request
**		key -- key (ignored, for compatibility with bhfree_F)
**		ctx -- context (see above: bht2reql_P)
**
**	Return value:
**		usual sm_error code
**
**	Locking: dns_mgr_ctx must be locked by caller.
*/

static void
dns_bht2reql_fct(void *value, void *key, void *ctx)
{
	dns_req_P dns_req;
	bht2reql_P bht2reql;
	dns_mgr_ctx_P dns_mgr_ctx;
	dns_rql_P rql_hd;

	dns_req = (dns_req_P) value;
	bht2reql = (bht2reql_P) ctx;
	if (NULL == dns_req || bht2reql == NULL) {
		/* abort for now */
		SM_ASSERT(dns_req != NULL);
		SM_ASSERT(bht2reql != NULL);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=ERROR, func=dns_bht2reql_fct, dns_req=%p, bht2reql=%p\n",
			dns_req, bht2reql));
		return;
	}
	dns_mgr_ctx = bht2reql->b2r_dns_mgr_ctx;
	rql_hd = bht2reql->b2r_rql_hd;
	if (NULL == dns_mgr_ctx || rql_hd == NULL) {
		/* abort for now */
		SM_ASSERT(dns_mgr_ctx != NULL);
		SM_ASSERT(rql_hd != NULL);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=ERROR, func=dns_bht2reql_fct, dns_mgr_ctx=%p, rql_hd=%p\n",
			dns_mgr_ctx, rql_hd));
		return;
	}

	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_bht2reql_fct, dns_req=%p, inlist=%d\n",
		dns_req, DNSREQ_IS_INANYLIST(dns_req)));

	/* Remove entry from dns_mgr list (if it is in that list) */
	if (DNSREQ_IS_ININCLIST(dns_req)) {
		DNSIRQL_REMOVE(dns_mgr_ctx, dns_req);
		DNSREQ_CLR_INLIST(dns_req);
	}
	else if (DNSREQ_IS_INWAITLIST(dns_req)) {	/* possible?? */
		DNSWRQL_REMOVE(dns_mgr_ctx, dns_req);
		DNSREQ_CLR_INLIST(dns_req);
	}

	/* Add to local list */
	RQL_INSERT_TAIL(rql_hd, dns_req);
}

/*
**  DNS_BHT2REQL -- move DNS requests that match key from hash table
**		into local list.
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		key -- key to match
**		keylen -- length of key
**		rql_hd -- head of DNS request list to which items are moved
**
**	Return value:
**		usual sm_error code
**
**	Locking: dns_mgr_ctx must be locked by caller.
*/

static sm_ret_T
dns_bht2reql(dns_mgr_ctx_P dns_mgr_ctx, sm_str_P key, size_t keylen, dns_rql_P rql_hd)
{
	bht2reql_T bht2reql;

	SM_ASSERT(dns_mgr_ctx != NULL);
	SM_ASSERT(rql_hd != NULL);
	SM_ASSERT(key != NULL);

	bht2reql.b2r_dns_mgr_ctx = dns_mgr_ctx;
	bht2reql.b2r_rql_hd = rql_hd;

	/*
	**  Remove all entries that match key/keylen from hash table;
	**  remove them from DNS manager list, add to local list.
	**  Use the bht_rm_all() function callback to achieve this
	**  instead of accessing the data directly.
	*/

	bht_rm_all(dns_mgr_ctx->dnsmgr_req_ht,
		(const char *) sm_str_data(key), keylen,
		dns_bht2reql_fct, &bht2reql);

	return SM_SUCCESS;
}

/*
**  DNS_TSK_RD -- DNS communication task: read replies from DNS server
**
**	Parameters:
**		tsk -- evthr task
**
**	Return value:
**		usual sm_error code
**
**	Locking: dns_mgr_ctx is locked when necessary.
*/

static sm_ret_T
dns_tsk_rd(sm_evthr_task_P tsk)
{
	sm_ret_T ret, res;
	int r;
	dns_tsk_P dns_tsk;
	dns_res_P dns_res;
	dns_req_P dns_req, dns_req_next;
	dns_mgr_ctx_P dns_mgr_ctx;
	bht_entry_P bhte;
	uchar query[MAXHOSTNAMELEN];
	ushort type;
	sm_str_P key;
	size_t keylen;
	dns_rql_T rql_hd;

#if MTA_USE_PTHREADS
	SM_IS_EVTHR_TSK(tsk);
#endif
	dns_tsk = (dns_tsk_P) tsk->evthr_t_actx;
	if (NULL == dns_tsk)
		return EVTHR_DEL;
	if (!is_valid_fd(tsk->evthr_t_fd))
		return EVTHR_DEL;
	key = NULL;
	res = dns_receive(dns_tsk);
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_tsk_rd, receive=%x\n", res));
	if (sm_is_err(res)) {
		/* XXX deal with it... */;
		ret = res;
		goto error;
	}
	if (res == 0)
		goto done;

	ret = dns_res_new(MAXRESHOSTS, &dns_res);
	if (sm_is_err(ret)) {
		/* XXX deal with it... */;
		goto error;
	}

	ret = dns_decode(dns_tsk->dnstsk_rd, query, sizeof(query), &type, dns_res);
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_tsk_rd, decode=%x, query=%s\n", ret, query));

#if MTA_LIBDNS_TEST
	/* introduce some errors for some requests; ToDo: add others! */
#define SMDT_ARPA_TEMP	"190.0.0.127.in-addr.arpa"
#define SMDT_ARPA_PERM	"191.0.0.127.in-addr.arpa"
#define SMDT_ARPA_EINVAL	"192.0.0.127.in-addr.arpa"
#define SMDT_MX_EINVAL	"mxinvalid.sm9.org"
	if (sm_memeq(query, SMDT_ARPA_TEMP, sizeof(SMDT_ARPA_TEMP) - 1))
		dns_res->dnsres_ret = DNSR_TEMP;
	else if (sm_memeq(query, SMDT_ARPA_PERM, sizeof(SMDT_ARPA_PERM) - 1))
		dns_res->dnsres_ret = DNSR_PERM;
	else if (sm_memeq(query, SMDT_ARPA_EINVAL,
			sizeof(SMDT_ARPA_EINVAL) - 1))
		dns_res->dnsres_ret = sm_error_perm(SM_EM_DNS, EINVAL);
	else if (sm_memeq(query, SMDT_MX_EINVAL, sizeof(SMDT_MX_EINVAL) - 1))
		dns_res->dnsres_ret = DNSR_MXINVALID;
#endif /* MTA_LIBDNS_TEST */

	dns_mgr_ctx = dns_tsk->dnstsk_mgr;

	/* Create key from query and type... this is still ugly... */
	keylen = strlen((char *) query) + sizeof(type);
	key = sm_str_new(NULL, keylen, keylen + 4);
	if (NULL == key) {
		ret = sm_error_temp(SM_EM_DNS, ENOMEM);
		goto error;
	}
	ret = sm_str_scatn(key, (const char *) query, strlen((char *) query));
	if (sm_is_err(ret))
		goto error;
	ret = sm_str_rm_trail(key, ".");
	if (sm_is_err(ret))
		goto error;
	keylen -= ret;		/* decrement keylen if '.' has been removed */

	/* always lower case?! */
	sm_str2lower(key);
	ret = sm_str_scatn(key, (const char *) &type, sizeof(type));
	if (sm_is_err(ret))
		goto error;
	SM_ASSERT(keylen == sm_str_getlen(key));
	RQL_INIT(&rql_hd);

#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
			LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
			SM_LOG_CRIT, 4,
			"sev=CRIT, func=dns_tsk_rd, lock=%d", r);
		ret = sm_error_perm(SM_EM_DNS, r);
		goto error;
	}
#endif /* MTA_USE_PTHREADS */

	/*
	**  A two step approach is required here:
	**  1. Build a new list of requests (removing them from dns_mgr_ctx:
	**	request list and hash table)
	**  2. Use that list to invoke the callbacks
	**  This is required to get around locking/concurrent access issues:
	**  the table/list can't be locked when invoking the callback
	**  since otherwise the callback can't invoke a function like
	**  dns_req_add() which locks the dns_mgr_ctx table/list.
	**
	**  First lookup the entry and check whether the request id
	**  matches the result id. If it doesn't: ignore it.
	**  Note: bhtable prepends new entries, hence it is necessary to
	**  walk to the end of the chain. This is ugly and slow; a better
	**  method should be used!
	*/

	bhte = bht_locate(dns_mgr_ctx->dnsmgr_req_ht,
			(const char *) sm_str_data(key), keylen);

	dns_req = NULL;
	while (bhte != NULL) {
		dns_req = (dns_req_P) (bhte->bhe_value);
		SM_ASSERT(dns_req != NULL);
		if (dns_req->dnsreq_id == dns_res->dnsres_id)
			break;
		bhte = bhte->bhe_next;
#if 0
		if (dns_req->dnsreq_id != 0) {
sm_io_fprintf(smioerr, "sev=WARN, func=dns_tsk_rd, dns_req=%p, dns_req_id=%d, dnsres_id=%d, status=id_mismatch, where=in_while\n",
dns_req, NULL == dns_req ? -1 : dns_req->dnsreq_id, dns_res->dnsres_id);
		}
#endif
	}

	if (NULL == bhte || dns_req == NULL) {
#if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
		SM_ASSERT(r == 0);
		/* COMPLAIN if error */
#endif
		if (dns_req != NULL) {
sm_io_fprintf(smioerr, "sev=WARN, func=dns_tsk_rd, dns_req=%p, dns_req_id=%d, dnsres_id=%d, status=id_mismatch/not_found, key=%#N\n",
dns_req, NULL == dns_req ? -1 : dns_req->dnsreq_id, dns_res->dnsres_id, key);
		}
		goto freeit;
	}

	ret = dns_bht2reql(dns_mgr_ctx, key, keylen, &rql_hd);
	/* result ignored for now; it's always SUCCESS */

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_ASSERT(r == 0);
	/* COMPLAIN if error */
#endif

	/* Go through list of "done" requests and invoke callbacks */
	for (dns_req = RQL_FIRST(&rql_hd);
	     dns_req != RQL_END(&rql_hd);
	     dns_req = dns_req_next)
	{
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_tsk_rd, dns_req=%p, inlist=%d\n",
			dns_req, DNSREQ_IS_INANYLIST(dns_req)));
		dns_req_next = RQL_NEXT(dns_req);
		if (dns_req->dnsreq_id != 0 &&
		    dns_req->dnsreq_id != dns_res->dnsres_id)
		{
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_WARN, 7,
				"sev=WARN, func=dns_tsk_rd, dns_req_id=%d, dnsrpl_id=%d\n",
				dns_req->dnsreq_id, dns_res->dnsres_id);
		}

		if (dns_req->dnsreq_fct != NULL) {
			dns_res->dnsres_query = dns_req->dnsreq_query;

			/* Ignore return value? */
			dns_req->dnsreq_fct(dns_res, dns_req->dnsreq_ctx);
		}

		/* Unlink entry from list */
		RQL_REMOVE(&rql_hd, dns_req);
		ret = dns_req_free(dns_req, NULL);
	}

  freeit:
	/* Free dns_res */
	ret = dns_res_free(dns_res);
	sm_str_free(key);

  done:
	return EVTHR_WAITQ;

  error:
	MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=ERROR, func=dns_tsk_rd, ret=%x\n",
		ret));
	if (key != NULL)
		sm_str_free(key);
	return ret;
}

/*
**  DNS_REQ_INS_WRQL -- insert DNS request into wait list
**
**	Parameters:
**		dns_mgr_ctx -- DNS manager context
**		dns_req -- DNS request
**
**	Return value:
**		SM_SUCCESS
**
**	Locking: dns_mgr_ctx must be locked by caller.
*/

sm_ret_T
dns_req_ins_wrql(dns_mgr_ctx_P dns_mgr_ctx, dns_req_P dns_req)
{
	dns_req_P dns_reqh;

	if (DNSWRQL_EMPTY(dns_mgr_ctx)) {
		DNSWRQL_INSERT_TAIL(dns_mgr_ctx, dns_req);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_tsk_wr, writelist=empty\n"));
	}
	else {
		/*
		**  Insert dns_req at the right place in the list:
		**  start from the back and go to the front until
		**  an entry is reached whose "end time" is not
		**  greater than the one of the entry to be inserted.
		*/

		dns_reqh = DNSWRQL_LAST(dns_mgr_ctx);
		while (dns_reqh != DNSWRQL_FIRST(dns_mgr_ctx) &&
		       dns_req->dnsreq_endt < dns_reqh->dnsreq_endt)
		{
			dns_reqh = DNSWRQL_PREV(dns_reqh);
		}
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_tsk_wr, req=%p, reqh=%p, req->endt=%ld, reqh->ednt=%ld\n",
			dns_req, dns_reqh, (long) dns_req->dnsreq_endt,
			(long) dns_reqh->dnsreq_endt));
		if  (dns_req->dnsreq_endt >= dns_reqh->dnsreq_endt)
			DNSWRQL_INSERT_AFTER(dns_mgr_ctx, dns_reqh, dns_req);
		else
			DNSWRQL_INSERT_BEFORE(dns_mgr_ctx, dns_reqh, dns_req);
	}
	DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_WAIT);
	return SM_SUCCESS;
}

/*
**  DNS_TSK_WR -- DNS communication task: send queries to DNS server
**
**	Parameters:
**		tsk -- evthr task
**
**	Return value:
**		usual sm_error code
*/

static sm_ret_T
dns_tsk_wr(sm_evthr_task_P tsk)
{
	sm_ret_T ret;
	int r;
	dns_tsk_P dns_tsk;
	dns_mgr_ctx_P dns_mgr_ctx;
	dns_req_P dns_req;
	bool empty;

	SM_IS_EVTHR_TSK(tsk);
	dns_tsk = (dns_tsk_P) tsk->evthr_t_actx;
	if (NULL == dns_tsk)
		return EVTHR_DEL;
	if (!is_valid_fd(tsk->evthr_t_fd))
		return EVTHR_DEL;
	dns_mgr_ctx = dns_tsk->dnstsk_mgr;
	dns_req = NULL;

	do {
		/* get value alternatively from the two lists?? */
#if MTA_USE_PTHREADS
		r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_CRIT, 4,
				"sev=CRIT, func=dns_tsk_wr, lock=%d", r);
			ret = sm_error_perm(SM_EM_DNS, r);
			goto error;
		}
#endif

#if MTA_USE_DNSTSK_LIST
		/*
		**  Disabled: currently no entries are added
		**  to the task-specific queues, hence we don't need to
		**  check them.
		*/
#if MTA_USE_PTHREADS
		r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_CRIT, 4,
				"sev=CRIT, func=dns_tsk_wr, lock=%d", r);
			ret = sm_error_perm(SM_EM_DNS, r);
			goto error;
		}
#endif
		/* first: try to get entry from task-specific list */
		empty = DNSTRQL_EMPTY(dns_tsk);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_tsk_wr, empty=%d\n",
			empty));
		if (!empty) {
			/* move first element from incoming to wait list */
			dns_req = DNSTRQL_FIRST(dns_tsk);
			DNSTRQL_REMOVE(dns_tsk, dns_req);
			dns_req->dnsreq_idx = dns_tsk->dnstsk_idx;

			(void) dns_req_ins_wrql(dns_mgr_ctx, dns_req);
		}
#if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex);
		SM_ASSERT(r == 0);
		/* COMPLAIN if error */
#endif
#else /* MTA_USE_DNSTSK_LIST */
		empty = true;
#endif /* MTA_USE_DNSTSK_LIST */

		if (empty) {
			/* if task-specific list is empty, try "global" list */
			empty = DNSIRQL_EMPTY(dns_mgr_ctx);
			MTA_LIBDNS_DBG_DPRINTF((smioerr,
				"sev=DBG, func=dns_tsk_wr, empty=%d\n",
				empty));
			if (!empty) {
				/* move element from incoming to wait list */
				dns_req = DNSIRQL_FIRST(dns_mgr_ctx);
				DNSIRQL_REMOVE(dns_mgr_ctx, dns_req);
				dns_req->dnsreq_idx = dns_tsk->dnstsk_idx;

				(void) dns_req_ins_wrql(dns_mgr_ctx, dns_req);
			}
		}
#if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
		SM_ASSERT(r == 0);
		/* COMPLAIN if error */
#endif

		if (empty) {
#if MTA_USE_PTHREADS
			r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex);
			SM_LOCK_OK(r);
			if (r != 0) {
				sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
					LIBDNS_LCAT_RESOLVER,
					LIBDNS_LMOD_RESOLVER,
					SM_LOG_CRIT, 4,
					"sev=CRIT, func=dns_tsk_select, lock_tsk=%d"
					, r);
			}
			DNS_TSK_CLR_FLAG(dns_tsk, DNS_TSK_FL_WR_EN);
			r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex);
			SM_ASSERT(r == 0);
#endif /* MTA_USE_PTHREADS */

			/* Turn off WR, dns_req_add() will turn it on if req. */
			return EVTHR_WAITQ|evthr_r_no(EVTHR_EV_WR);
		}

		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_wr, req=%p\n",
			dns_req));

		/* Extract query from dns_req and send it to DNS server */
		do {
			/* Note: dnsreq_query must be '\0' terminated! */
			r = res_mkquery(QUERY,
				(const char *) sm_cstr_data(
							dns_req->dnsreq_query),
				C_IN, dns_req->dnsreq_type, NULL, 0,
				NULL, sm_str_data(dns_tsk->dnstsk_wr),
				(int) sm_str_getsize(dns_tsk->dnstsk_wr));
			if (r == -1) {
				size_t len;

				len = sm_str_getsize(dns_tsk->dnstsk_wr) * 2;
				ret = sm_str_space(dns_tsk->dnstsk_wr, len);
				if (sm_is_err(ret))
					goto error;
			}
			else {
				HEADER *hp;

				hp = (HEADER *) sm_str_data(dns_tsk->dnstsk_wr);
				dns_req->dnsreq_id = hp->id;
			}
		} while (r == -1);
		SM_ASSERT(r >= 0);
		SM_STR_SETLEN(dns_tsk->dnstsk_wr, (uint)r);

		ret = dns_send(dns_tsk);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_wr, send=%x\n",
			ret));

	} while (sm_is_success(ret) &&
		 0 == sm_write_wait(dns_tsk->dnstsk_fd, 0));
#if 0
	/*
	**  Don't terminate the task just because dns_send() failed
	**  How to deal with this properly? (see below)
	*/

	if (sm_is_err(ret))
		goto error;
#endif /* 0 */

	return EVTHR_WAITQ;

  error:
	/*
	**  XXX need to determine whether this is "real" error,
	**	i.e., one that actually will persist and hence it makes
	**	sense to terminate this task.
	*/

	return EVTHR_WAITQ;
	/* return ret; */
}

#if MTA_LIBDNS_DEBUG
sm_ret_T
printreq(bht_entry_P e, void *ctx)
{
	if (e != NULL)
		sm_io_fprintf(smioerr, "e=%p, ctx=%p\n", e, ctx);
	return 0;
}
#endif

/*
**  DNS_TSK_CLEANUP -- deal with requests that have not been answered.
**	This should be run whenever a request times out.
**
**	Parameters:
**		tsk -- evthr task
**
**	Return value:
**		usual sm_error code
*/

static sm_ret_T
dns_tsk_cleanup(sm_evthr_task_P tsk)
{
	sm_ret_T ret;
	int r;
	dns_mgr_ctx_P dns_mgr_ctx;
	dns_req_P dns_req, dns_req_next;
	dns_res_T dns_res;
	time_T now;
	timeval_T sleept;
	uint delay;
	sm_str_P key;
	size_t keylen;
	dns_rql_T rql_hd;

	SM_IS_EVTHR_TSK(tsk);
	dns_mgr_ctx = (dns_mgr_ctx_P) tsk->evthr_t_actx;
	if (NULL == dns_mgr_ctx)
		return EVTHR_DEL;
	ret = evthr_timeval(tsk->evthr_t_ctx, &sleept);
	now = sleept.tv_sec;

	delay = 0;
	tsk->evthr_t_sleep.tv_usec = sleept.tv_usec;
	RQL_INIT(&rql_hd);
	dns_req_next = NULL;

#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
			LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
			SM_LOG_CRIT, 4,
			"sev=CRIT, func=dns_tsk_cleanup, lock=%d", r);
		ret = sm_error_perm(SM_EM_DNS, r);
		goto error;
	}
#endif

	while (!DNSWRQL_EMPTY(dns_mgr_ctx)) {
		dns_req = DNSWRQL_FIRST(dns_mgr_ctx);
		if (dns_req == dns_req_next) {
			sm_log_write(dns_mgr_ctx->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=dns_cleanup, status=loop, dns_req=%p, dns_req_next=%p\n",
				dns_req, dns_req_next);
			SM_ASSERT(dns_req != dns_req_next);
			break;
		}
		dns_req_next = dns_req;

		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_cleanup, dns_req=%p, dns_req_next=%p\n",
			dns_req, dns_req_next));
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_cleanup, now=%ld, endt=%ld\n",
			(long) now, (long) dns_req->dnsreq_endt));

		/* Stop the loop if the timeout hasn't been reached yet */
		if (now <= dns_req->dnsreq_endt) {
			delay = dns_req->dnsreq_endt - now;
			if (delay == 0)
				delay = MIN_TSK_CLEANUP_SLP;
			break;
		}

		if (dns_req->dnsreq_tries < dns_mgr_ctx->dnsmgr_retries) {
#if DNS_STATISTICS
			dns_req->dnsreq_startt = sleept;
#endif
			dns_req->dnsreq_endt = now +
						dns_mgr_ctx->dnsmgr_timeout;
			DNSWRQL_REMOVE(dns_mgr_ctx, dns_req);
			DNSREQ_CLR_INLIST(dns_req);
			DNSIRQL_INSERT_TAIL(dns_mgr_ctx, dns_req);
			DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_INC);
			++dns_req->dnsreq_tries;

			/* how to avoid repeated activations? */
			(void) dns_tsk_select(dns_mgr_ctx, now, true, NULL,
				 THR_NO_LOCK|THR_LOCK_UNLOCK_S1);
			continue;
		}

		/* Create key from query and type... still ugly... */
		ret = dns_crt_key(dns_req->dnsreq_query, dns_req->dnsreq_type,
				&key, &keylen);
		if (sm_is_err(ret))
			goto errunl;

		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_cleanup, dns_req=%p, key='%s', keylen=%d\n",
			dns_req, sm_str_data(key), keylen));

		/*
		**  Remove dns_req from hash table.
		**  Notice: this removes all entries that match the key
		**  and moves them to our local list.
		**
		**  Note: this creates a problem: should we requeue
		**  those requests? This is just a timeout,
		**  maybe later on it actually works.
		*/

		ret = dns_bht2reql(dns_mgr_ctx, key, keylen, &rql_hd);
#if MTA_LIBDNS_DEBUG
		if (dns_req == DNSWRQL_FIRST(dns_mgr_ctx))
			bht_walk(dns_mgr_ctx->dnsmgr_req_ht, printreq, key);
#endif
		SM_ASSERT(dns_req != DNSWRQL_FIRST(dns_mgr_ctx));
		sm_str_free(key);
	}

	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_cleanup, empty=%d, delay=%d\n",
		DNSWRQL_EMPTY(dns_mgr_ctx), delay));
	if (delay == 0 && !DNSWRQL_EMPTY(dns_mgr_ctx)) {
		dns_req = DNSWRQL_FIRST(dns_mgr_ctx);

		/* make sure the result will be > 0 [unsigned arithmetic] */
		if (now < dns_req->dnsreq_endt)
			delay = dns_req->dnsreq_endt - now;
		if (delay == 0)
			delay = MIN_TSK_CLEANUP_SLP;

		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_cleanup, where=not-empty, now=%ld, endt=%ld\n",
			(long) now, (long) dns_req->dnsreq_endt));
	}
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_cleanup, where=end, delay=%d\n",
		delay));
	if (delay == 0)
		delay = TSK_CLEANUP_SLP;

#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_ASSERT(r == 0);
	/* COMPLAIN if error */
#endif

	/* Go through list of "done" requests and invoke callbacks */
	for (dns_req = RQL_FIRST(&rql_hd);
	     dns_req != RQL_END(&rql_hd);
	     dns_req = dns_req_next)
	{
		ushort u;
		dns_tsk_P dns_tsk;

		dns_req_next = RQL_NEXT(dns_req);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"sev=DBG, func=dns_cleanup, dns_req=%p, fct=%p\n",
			dns_req, dns_req->dnsreq_fct));

		if ((u = dns_req->dnsreq_idx) < dns_mgr_ctx->dnsmgr_ntsks &&
		    (dns_tsk = dns_mgr_ctx->dnsmgr_dnstsks[u]) != NULL &&
		    dns_tsk->dnstsk_maxtimeouts > 0 &&
		    (++dns_tsk->dnstsk_timeouts > dns_tsk->dnstsk_maxtimeouts))
		{
			dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_DEAD;
			dns_mgr_ctx->dnsmgr_tskchg[u] = now;
			dns_tsk->dnstsk_timeouts = 0;
		}

		/* Unlink entry from list */
		if (dns_req->dnsreq_fct != NULL) {
			/* Return a timeout to caller... fill in more data? */
			sm_memzero(&dns_res, sizeof(dns_res));
			dns_res.dnsres_query = dns_req->dnsreq_query;
			dns_res.dnsres_ret = DNSR_TIMEOUT;
			dns_res.dnsres_qtype = dns_req->dnsreq_type;
			DRESL_INIT(&dns_res);

			/* Ignore return value? */
			dns_req->dnsreq_fct(&dns_res, dns_req->dnsreq_ctx);
		}
		RQL_REMOVE(&rql_hd, dns_req);
		ret = dns_req_free(dns_req, NULL);
	}

	tsk->evthr_t_sleep.tv_sec = sleept.tv_sec + delay;
	return EVTHR_SLPQ;

  errunl:
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex);
	SM_ASSERT(r == 0);
	/* COMPLAIN if error */
#endif
  error:
	/* Cleanup? */
	tsk->evthr_t_sleep.tv_sec = sleept.tv_sec + delay;
	return EVTHR_SLPQ;	/* XXX Always? */
}

/*
**  DNS_COMM_TSK -- DNS communication task: calls functions to perform
**		reads and writes.
**	This runs as task in the event thread system.
**
**	Parameters:
**		tsk -- evthr task
**
**	Return value:
**		usual sm_error code
*/

sm_ret_T
dns_comm_tsk(sm_evthr_task_P tsk)
{
	sm_ret_T ret;
	dns_tsk_P dns_tsk;

	SM_IS_EVTHR_TSK(tsk);
	dns_tsk = (dns_tsk_P) tsk->evthr_t_actx;
	MTA_LIBDNS_DBG_DPRINTF((smioerr,
		"sev=DBG, func=dns_comm, dns_tsk=%p, want=%x, got=%x\n",
		dns_tsk, tsk->evthr_t_rqevf, tsk->evthr_t_evocc));

	if (NULL == dns_tsk)
		return EVTHR_DEL;
	if (is_valid_fd(tsk->evthr_t_fd)) {
		ret = EVTHR_WAITQ;
		if (evthr_got_wr(tsk))
			ret = dns_tsk_wr(tsk);	/* XXX check ret here? */
		/* XXX This may turn off WR; change the order? */

		if (evthr_got_rd(tsk))
			ret = dns_tsk_rd(tsk);
		if (sm_is_err(ret)) {
			/* complain? do some cleanup? */
			return ret;
		}
		return ret;
	}
	return EVTHR_DEL;
}


syntax highlighted by Code2HTML, v. 0.9.1