/*
 * Copyright (c) 2003-2005 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: rcpt.c,v 1.155 2007/11/14 06:03:08 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/ctype.h"
#include "sm/limits.h"
#include "sm/io.h"
#include "sm/net.h"
#include "sm/rcb.h"
#include "sm/common.h"
#include "sm/mta.h"
#include "sm/das.h"
#include "sm/rfc2821.h"
#include "smar.h"
#include "log.h"
#include "sm/qmgrcomm.h"
#include "sm/reccom.h"

/*
**  XXX Try to merge reply functions into one (or at least use fewer)
**	to avoid code duplications.
**	Pass res to reply function, if success send routing info
**	else res (error status).
*/

/*
Protocol:
2004-06-27

RT_PROT_VER, PROT_VER_RT,
RT_R2Q_RCPT_ID, smar_rcpt->arr_id, SMTP_RCPTID_SIZE,

if "global" error:
  RT_R2Q_RCPT_ST, rcpt_status
  stop.

if owners exist:
  RT_R2Q_OWN_N, arrs_owners_n
  for each owner address:
    RT_R2Q_OWN_IDX, smar_rcpt->arr_idx,
    RT_R2Q_OWN_PA, smar_rcpt->arr_pa,

if expanded to multiple addresses (smar_rcpts->arrs_lst_n > 1)
  RT_R2Q_RCPT_AE, smar_rcpts->arrs_lst_n,

for each address:
  if error:
    RT_R2Q_RCPT_ST, rcpt_status
    RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx, [could be optional]
    maybe: RT_R2Q_RCPT_PA, smar_rcpt->arr_pa,
  else
    RT_R2Q_RCPT_DA, smar_rcpt->arr_da
    RT_R2Q_RCPT_PORT, smar_rcpt->arr_port, [optional]
    RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx, [could be optional]
    RT_R2Q_RCPT_FL, smar_rcpt flags [optional]

    maybe: RT_R2Q_RCPT_PA, smar_rcpt->arr_pa,
    RT_R2Q_RCPT_NAR, smar_rcpt->arr_n_A
    for each MX record (smar_rcpt->arr_A_qsent)
      for each A record (smar_dns->ardns_n_A)
        RT_R2Q_RCPT_IPV4, smar_dns->ardns_A_rrs[j],
        RT_R2Q_RCPT_PRE, smar_dns->ardns_pref,
        RT_R2Q_RCPT_TTL, smar_dns->ardns_ttl,

2004-06-30
can this be simplified? too many tests on receiver side.

*/

/*
**  SMAR_RCPT_RE_FIRST -- Send begin of reply for a RCPT address, i.e.,
**	put begin of data into RCB (common to most smar_rcpt_re_*() fcts)
**
**	Parameters:
**		smar_rcpt -- rcpt routing information
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_rcpt_re_all(), smar_rcpt_re_ipv4(), smar_rcpt_re_err()
**
**	Last code review: 2004-03-15 16:31:34
**	Last code change:
*/

static sm_ret_T
smar_rcpt_re_first(smar_rcpt_P smar_rcpt)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	int owners;
	smar_rcpts_P smar_rcpts;

	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_rcpts = smar_rcpt->arr_rcpts;
	SM_IS_SMAR_RCPTS(smar_rcpts);
	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_BUF, RT_R2Q_RCPT_ID, smar_rcpt->arr_id, SMTP_RCPTID_SIZE,
		SM_RCBV_END);

	if (SM_SUCCESS == ret && (owners = smar_rcpts->arrs_owners_n) > 0) {
		ret = sm_rcb_put3uint32(rcb, 4, RT_R2Q_OWN_N, owners);
		if (SM_SUCCESS == ret) {
			int n;
			smar_rcpt_P smar_owner;

			for (smar_owner = OWNER_FIRST(smar_rcpts), n = 0;
			     smar_owner != OWNER_END(smar_rcpts) && n < owners;
			     smar_owner = OWNER_NEXT(smar_owner), ++n)
			{
				ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
					SM_RCBV_INT, RT_R2Q_OWN_IDX, smar_owner->arr_idx,
					SM_RCBV_STR, RT_R2Q_OWN_PA, smar_owner->arr_owner_pa,
					SM_RCBV_END);
			}
			SM_ASSERT(n == owners);
		}
	}

	if (SM_SUCCESS == ret && smar_rcpts->arrs_lst_n > 1)
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_RCPT_AE, smar_rcpts->arrs_lst_n,
			SM_RCBV_END);
	SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_INITRE);
	return ret;
}

/*
**  SMAR_RCPT_RE_ALL -- Complete reply for a RCPT routing request
**
**	Parameters:
**		smar_rcpt -- SMAR RCPT context
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_rcpt_cb()
**
**	Last code review: 2004-03-15 16:52:39; see comments
**	Last code change:
*/

static sm_ret_T
smar_rcpt_re_all(smar_rcpt_P smar_rcpt)
{
	sm_rcb_P rcb;
	int i, j;
	uint n_A, cnt_A;
	sm_ret_T ret;
	smar_dns_P smar_dns;
	smar_rcpts_P smar_rcpts;

	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_rcpts = smar_rcpt->arr_rcpts;
	SM_IS_SMAR_RCPTS(smar_rcpts);
	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND))
		return SM_SUCCESS;

	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;

	/* First: send basic data and number of entries */
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_re_all, entries=%d, sm_rcb_len=%d, sm_rcb_rw=%d\n", smar_rcpt->arr_n_A, rcb->sm_rcb_len, rcb->sm_rcb_rw));

	if (!SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_INITRE)) {
		ret = smar_rcpt_re_first(smar_rcpt);
		if (sm_is_err(ret))
			goto error;
	}
	ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_RCPT_DA, smar_rcpt->arr_da,
			SM_RCBV_INT,
				(0 == smar_rcpt->arr_port) ? RT_NOSEND : RT_R2Q_RCPT_PORT,
				(uint32_t) smar_rcpt->arr_port,
			SM_RCBV_INT, RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx,
			SM_RCBV_INT, SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASVERP)
					? RT_R2Q_RCPT_FL: RT_NOSEND,
				(uint32_t) SMARRT_FL_VERP,
			SM_RCBV_END);

	if (SM_SUCCESS == ret && SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)) {
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_MAP_RES_CNF_RCPT, smar_rcpt->arr_maprescnf,
			SM_RCBV_STR, RT_R2Q_RHS_CNF_RCPT, smar_rcpt->arr_rhs_conf,
			SM_RCBV_END);
	}

	if (SM_SUCCESS == ret &&
	    (smar_rcpts->arrs_lst_n > 1 ||
	     (smar_rcpts->arrs_lst_n == 1
	      && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ALIAS))))
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_STR, RT_R2Q_RCPT_PA, smar_rcpt->arr_pa,
			SM_RCBV_END);
	n_A = smar_rcpt->arr_n_A;
	if (n_A > SM_DNS_A_MAX)
		n_A = SM_DNS_A_MAX;
	if (SM_SUCCESS == ret)
		ret = sm_rcb_put3uint32(rcb, 4, RT_R2Q_RCPT_NAR, n_A);
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "da=%d, n_A=%d, n_MX=%d, putv=%r\n", smar_rcpt->arr_da, n_A, smar_rcpt->arr_A_qsent, ret));
	if (sm_is_err(ret))
		goto error;

	/* Now go through all MX entries */
	/* Note: smar_rcpt->arr_A_qsent > 0 iff smar_rcpt->arr_res != NULL */
	cnt_A = 0;
	for (i = 0; i < smar_rcpt->arr_A_qsent; i++) {
		smar_dns = &(smar_rcpt->arr_res[i]);
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "i=%d, smar_dns=%p\n", i, smar_dns));

		/* and send all A records with the MX data */
		for (j = 0; j < smar_dns->ardns_n_A; j++) {
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "i=%d, j=%d, a=%A\n", i, j, smar_dns->ardns_A_rrs[j]));
#if 0
			/* XXX HACK: ignore addresses with value 0 */
			if (smar_dns->ardns_A_rrs[j] == 0)
				continue;
#endif
			if (cnt_A >= n_A)
				break;
			++cnt_A;

			ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
				SM_RCBV_INT, RT_R2Q_RCPT_IPV4, smar_dns->ardns_A_rrs[j],
				SM_RCBV_INT, RT_R2Q_RCPT_PRE, (uint32_t) smar_dns->ardns_pref,

				/* XXX TTL is from MX record! change it? */
				SM_RCBV_INT, RT_R2Q_RCPT_TTL, smar_dns->ardns_ttl,
				SM_RCBV_END);
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "i=%d, j=%d, putv=%r\n", i, j, ret));
			if (sm_is_err(ret))
				goto error;
		}
		if (cnt_A >= n_A)
			break;
	}
	return ret;

  error:
	/* cleanup? */
	return ret;
}

/*
**  SMAR_RCPT_RE_IPV4 -- Send reply for a RCPT routing request
**
**	Parameters:
**		smar_rcpt -- rcpt routing information
**		n_addr -- number of addresses
**		first -- is this the first invocation for this RCB?
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_rcpt_rslv() for mailertable entries with RHS [I.P.V.4]*
**
**	Last code review: 2004-03-15 17:17:27
**	Last code change:
*/

static sm_ret_T
smar_rcpt_re_ipv4(smar_rcpt_P smar_rcpt, uint32_t n_addr, bool first)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	smar_rcpts_P smar_rcpts;

	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_rcpts = smar_rcpt->arr_rcpts;
	SM_IS_SMAR_RCPTS(smar_rcpts);

	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND))
		return SM_SUCCESS;

	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;
	if (!SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_INITRE)) {
		ret = smar_rcpt_re_first(smar_rcpt);
		if (sm_is_err(ret))
			return ret;
	}

	if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ALIAS) || !first
	    || smar_rcpts->arrs_lst_n > 1)
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_RCPT_DA, smar_rcpt->arr_da,
			SM_RCBV_INT,
				(0 == smar_rcpt->arr_port) ? RT_NOSEND : RT_R2Q_RCPT_PORT,
				(uint32_t) smar_rcpt->arr_port,
			SM_RCBV_INT, RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx,
			SM_RCBV_INT, SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASVERP)
					? RT_R2Q_RCPT_FL : RT_NOSEND,
				(uint32_t) SMARRT_FL_VERP,
			SM_RCBV_INT, SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)
				? RT_R2Q_MAP_RES_CNF_RCPT : RT_NOSEND,
				smar_rcpt->arr_maprescnf,
			SM_RCBV_STR, SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)
				? RT_R2Q_RHS_CNF_RCPT : RT_NOSEND,
				smar_rcpt->arr_rhs_conf,
			SM_RCBV_STR, RT_R2Q_RCPT_PA, smar_rcpt->arr_pa,
			SM_RCBV_INT, RT_R2Q_RCPT_NAR, n_addr,
			SM_RCBV_INT, RT_R2Q_RCPT_IPV4, smar_rcpt->arr_ipv4,
			SM_RCBV_INT, RT_R2Q_RCPT_PRE, (uint32_t) 0,
			SM_RCBV_INT, RT_R2Q_RCPT_TTL, SMAR_DEFAULT_TTL,
			SM_RCBV_END);
	else
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_RCPT_DA, smar_rcpt->arr_da,
			SM_RCBV_INT,
				(0 == smar_rcpt->arr_port) ? RT_NOSEND : RT_R2Q_RCPT_PORT,
				(uint32_t) smar_rcpt->arr_port,
			SM_RCBV_INT, RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx,
			SM_RCBV_INT, SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASVERP)
					? RT_R2Q_RCPT_FL: RT_NOSEND,
				(uint32_t) SMARRT_FL_VERP,
			SM_RCBV_INT, SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)
				? RT_R2Q_MAP_RES_CNF_RCPT : RT_NOSEND,
				smar_rcpt->arr_maprescnf,
			SM_RCBV_STR, SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)
				? RT_R2Q_RHS_CNF_RCPT : RT_NOSEND,
				smar_rcpt->arr_rhs_conf,
			SM_RCBV_INT, RT_R2Q_RCPT_NAR, n_addr,
			SM_RCBV_INT, RT_R2Q_RCPT_IPV4, smar_rcpt->arr_ipv4,
			SM_RCBV_INT, RT_R2Q_RCPT_PRE, (uint32_t) 0,
			SM_RCBV_INT, RT_R2Q_RCPT_TTL, SMAR_DEFAULT_TTL,
			SM_RCBV_END);
	return ret;
}

/*
**  SMAR_RCPT_RE_IPV4_MORE -- Add another reply for a RCPT routing request
**	(used to send another IPv4 address)
**
**	Parameters:
**		rcb -- RCB to use for sending back data
**		arr_ipv4 -- resolved IPv4 address
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_rcpt_rslv() for mt entries with RHS [I.P.V.4]+
**
**	Last code review: 2004-03-15 17:18:05
**	Last code change:
*/

static sm_ret_T
smar_rcpt_re_ipv4_more(sm_rcb_P rcb, ipv4_T arr_ipv4)
{
	return sm_rcb_putv(rcb, RCB_PUTV_NONE,
		SM_RCBV_INT, RT_R2Q_RCPT_IPV4, arr_ipv4,
		SM_RCBV_INT, RT_R2Q_RCPT_PRE, (uint32_t) 0,
		SM_RCBV_INT, RT_R2Q_RCPT_TTL, SMAR_DEFAULT_TTL,
		SM_RCBV_END);
}

/*
**  SMAR_RCPT_RE_ERR -- Send an error reply for a RCPT address
**
**	Parameters:
**		smar_rcpt -- rcpt routing information
**		res -- error code
**	XXX this should also send an error text, otherwise QMGR has
**		problem to "reconstruct" the correct error (e.g., during which
**		lookup did the error occur (MX, A, ...))
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-15 17:22:36
**	Last code change: 2004-05-20 21:06:35
*/

static sm_ret_T
smar_rcpt_re_err(smar_rcpt_P smar_rcpt, sm_ret_T res)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	smar_rcpts_P smar_rcpts;

	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_rcpts = smar_rcpt->arr_rcpts;
	SM_IS_SMAR_RCPTS(smar_rcpts);
	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND)) {
		smar_rcpts->arrs_ret = res;
		return SM_SUCCESS;
	}
	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;
	if (!SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_INITRE)) {
		ret = smar_rcpt_re_first(smar_rcpt);
		if (sm_is_err(ret))
			return ret;
	}
	ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_INT, RT_R2Q_RCPT_ST, res,
			SM_RCBV_INT, RT_R2Q_OWN_REF, smar_rcpt->arr_owner_idx,
			SM_RCBV_END);

	/* need to send address? */
	if (SM_SUCCESS == ret &&
	    (smar_rcpts->arrs_lst_n > 1 ||
	     (smar_rcpts->arrs_lst_n == 1
	      && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ALIAS))))
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_STR, RT_R2Q_RCPT_PA, smar_rcpt->arr_pa,
			SM_RCBV_END);
	return ret;
}

/*
**  SMAR_RCPTS_RE_ERR -- Send an error reply for a RCPT list
**
**	Parameters:
**		smar_rcpts -- SMAR RCPT LIST context
**		res -- error code
**	XXX this should also send an error text, otherwise QMGR has
**		problem to "reconstruct" the correct error (e.g., during which
**		lookup did the error occur (MX, A, ...))
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-15 17:22:55
**	Last code change:
*/

static sm_ret_T
smar_rcpts_re_err(smar_rcpts_P smar_rcpts, sm_ret_T res)
{
	sm_rcb_P rcb;
	sm_ret_T ret;

	SM_IS_SMAR_RCPTS(smar_rcpts);
	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_BUF, RT_R2Q_RCPT_ID, smar_rcpts->arrs_rcpt_id, SMTP_RCPTID_SIZE,
		SM_RCBV_INT, RT_R2Q_RCPT_ST, res,
		SM_RCBV_END);
	return ret;
}

/*
**  SMAR_RES_GOOD -- Check whether result is "good enough", i.e.,
**	one of the best MX records has been resolved.
**
**	Parameters:
**		smar_rcpt -- rcpt routing information
**
**	Returns:
**		good enough?
**
**	Last code review: 2004-03-15 17:27:39
**	Last code change:
*/

static bool
smar_res_good(smar_rcpt_P smar_rcpt)
{
	int i;
	ushort pref;
	smar_dns_P smar_dns;

	SM_IS_SMAR_RCPT(smar_rcpt);
	if (NULL == smar_rcpt->arr_res || smar_rcpt->arr_A_qsent <= 0)
		return false;

	pref = smar_rcpt->arr_res[0].ardns_pref;

	/* Now go through all MX entries (note: the entries are sorted!) */
	for (i = 0; i < smar_rcpt->arr_A_qsent; i++) {
		smar_dns = &(smar_rcpt->arr_res[i]);
		if (pref < smar_dns->ardns_pref)
			break;
		if (smar_dns->ardns_n_A > 0 && smar_dns->ardns_A_rrs != NULL)
			return true;
	}
	return false;
}

/*
**  ISLOCALNAME -- Is name a "local" name?
**
**	Parameters:
**		smar_ctx -- SMAR context
**		name -- name of MX host
**
**	Returns:
**		true iff name is the name of the host
**
**	Notes:
**	- this needs to allow for a set of names...
**		should we check mt: if name is in there and RHS is local
**		then return true? [done]
**
**	- this is ugly: it has to check for trailing dot.
**		can we make certain that only one form of names is
**		used as input?
**
**	- this function can fail if there isn't enough memory...
**		that's bad because bool can't be used to return a
**		temporary error. Pass in a str (maybe part of some context?)
**
**	Last code review: 2004-03-15 17:29:27; see comments!
**	Last code change:
*/

static bool
islocalname(smar_ctx_P smar_ctx, sm_cstr_P name)
{
	sm_str_T str;
	sm_str_P rhs;
	size_t len;
	bool islocal;

	SM_IS_SMAR_CTX(smar_ctx);
	SM_REQUIRE(name != NULL);

	islocal = false;
	len = sm_cstr_getlen(name);
	if (len > 1 && sm_cstr_rd_elem(name, len - 1) == '.')
		--len;
	if (sm_str_getlen(smar_ctx->smar_hostname) == len &&
	    strncasecmp((const char *) sm_str_data(smar_ctx->smar_hostname),
			(const char *) sm_cstr_data(name), len) == 0)
		return true;

	/* XXX Check other maps/...? */

	sm_str_assign(str, NULL, sm_cstr_data(name), len, len);
	rhs = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == rhs)
		return false;	/* XXX ??? */
	if (sm_map_lookup(smar_ctx->smar_mt_map, 0, &str, rhs) == SM_SUCCESS)
		islocal = strcmp((const char *) sm_str_getdata(rhs), LMTP_IPV4_S2) == 0
			  || strcmp((const char *) sm_str_getdata(rhs), LMTP_MT) == 0;
	sm_str_free(rhs);
	return islocal;
}

/*
**  GOTA -- Is A record already in list?
**
**	Parameters:
**		smar_rcpt -- SMAR recipient
**		ipv4 -- IPv4 address to check
**
**	Returns:
**		true iff ipv4 address is already in list
**
**	Last code review:
**	Last code change:
*/

static bool
gotA(smar_rcpt_P smar_rcpt, ipv4_T ipv4)
{
	int i, j;

	for (i = 0; i < smar_rcpt->arr_A_qsent; i++) {
		ipv4_T *A_rrs;

		A_rrs = smar_rcpt->arr_res[i].ardns_A_rrs;
		if (NULL == A_rrs)
			continue;
		for (j = 0; j < smar_rcpt->arr_res[i].ardns_n_A; j++) {
			if (A_rrs[j] == ipv4)
				return true;
		}
	}
	return false;
}

/*
**  SMAR_RCPT_CB -- Callback function for DNS resolver
**
**	Parameters:
**		dns_res -- DNS resolver result
**		ctx -- context: smar_rcpt
**
**	Returns:
**		usual sm_error code
**
**	Locking:
**		locks smar_ctx
**
**	Used as callback for dns_req_add() by smar_rcpt_rslv() and by itself.
**
**	To return results to the client this function calls:
**	- smar_rcpt_re_all()
**	- smar_rcpt_re_err()
**	- smar_rcpts_re_err()
**
**	ToDo: check error handling
**
**	This function needs to keep track of the number of open requests.
**	Only when it received all answers it must send them back (in an RCB)
**	to the client. Unfortunately there are a lot of error cases to handle.
**	Nevertheless, the function requires that all outstanding requests
**	are answered (even if the DNS resolver encounters an error), there
**	is no timeout in here (can't be: it's a callback function).
**	The number of recipients is stored in smar_rcpts->arrs_lst_n,
**	which is also the initial number of (MX) requests.
**	For each successful MX lookup this function starts DNS requests for
**	the correspoding A records (number of submitted queries: arr_A_qsent,
**	number of received results: arr_A_rrcvd).
**	Hence there is one counter per recipient (smar_rcpt->arr_A_rrcvd)
**	and one counter per recipient list (smar_rcpts->arrs_lst_n).
**
**	There's a rather complex handling of "acceptable" errors, e.g.,
**	if some A records don't resolve.
**	If all requests for a single recipient have been fulfilled, then
**	arrs_resolved is incremented. When that value reaches arrs_lst_n
**	then the created RCB is sent.
**
**	Problems:
**	- RCB is full: what to do? It is still necessary to collect all
**	responses, but there should be an error returned to the caller:
**	add a state flag in rcpts?
**	- others?
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
smar_rcpt_cb(dns_res_P dns_res, void *ctx)
{
	dns_type_T dnstype;
	sm_ret_T ret, dnsres;
	uint n_entries, i;
	size_t n;
	int r;
	uint8_t flags;
	sm_rcb_P rcb;
	smar_rcpts_P smar_rcpts;
	smar_rcpt_P smar_rcpt;
	dns_rese_P dns_rese;
	smar_ctx_P smar_ctx;
	dns_mgr_ctx_P dns_mgr_ctx;
	smar_clt_ctx_P smar_clt_ctx;
	sm_cstr_P q;

#define SM_INCR_C_MX		0x01
#define SM_INCR_RESOLVED	0x02
#define SM_NOSEND		0x10
#define SM_NOFREE		0x20

	if (NULL == dns_res || ctx == NULL) {
SMAR_LEV_DPRINTF(0, (SMAR_DEBFP, "sev=ERROR, func=smar_rcpt_cb, dns_res=%p, ctx=%p\n", dns_res, ctx));
		/* XXX Oops... how to do logging if there is no context? */
		sm_log_write(NULL, AR_LCAT_RESOLVER,
			AR_LMOD_RCPT_CB, SM_LOG_ERROR, 0,
			"sev=ERROR, func=smar_rcpt_cb, dns_res=%p, ctx=%p", dns_res, ctx);
		return SM_FAILURE;
	}

	smar_rcpt = (smar_rcpt_P) ctx;
	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_rcpts = smar_rcpt->arr_rcpts;
	SM_IS_SMAR_RCPTS(smar_rcpts);
	smar_clt_ctx = smar_rcpts->arrs_smar_clt_ctx;
	SM_IS_SMAR_CLT_CTX(smar_clt_ctx);
	smar_ctx = smar_rcpts->arrs_smar_ctx;
	SM_IS_SMAR_CTX(smar_ctx);
	flags = 0;

	/*
	**  Check whether SMAR is shutting down: if yes: don't
	**  do anything with the replies since the tasks are invalid.
	*/

	r = pthread_mutex_lock(&smar_ctx->smar_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG; XXX What to do in this error case? */
		sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
			AR_LMOD_RCPT_CB, SM_LOG_CRIT, 1,
			"sev=CRIT, func=smar_rcpt_cb, lock=%d", r);
		return SM_FAILURE;
	}

	if (smar_is_stop(smar_ctx)) {
		sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
			AR_LMOD_RCPT_CB, SM_LOG_NOTICE, 10,
			"sev=NOTICE, func=smar_rcpt_cb, shutting down");

		/* XXX free smar_rcpts? */

		r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
		return SM_SUCCESS;
	}

	dns_mgr_ctx = smar_ctx->smar_dns_mgr_ctx;
	SM_REQUIRE(dns_mgr_ctx != NULL);
	rcb = &smar_rcpts->arrs_rcbe->rcbe_rcb;

	ret = SM_SUCCESS;
	dnsres = dns_res->dnsres_ret;
	dnstype = dns_res->dnsres_qtype;
	n_entries = dns_res->dnsres_entries;

	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND))
		SM_SET_FLAG(flags, SM_NOSEND);
	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOFREE))
		SM_SET_FLAG(flags, SM_NOFREE);

SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, dnsres=%r, dnstype=%d, flags=%#x, entries=%d\n", smar_rcpt->arr_pa, dnsres, dnstype, smar_rcpt->arr_flags, n_entries));

	if (DNSR_NOTFOUND == dnsres && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4MX)
	    && !SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4A))
	{
		/* MX record not found -> try A (see RFC 2821(?)) */
		dnstype = T_MX;
		dnsres = SM_SUCCESS;	/* pretend it's ok */
		n_entries = 0; /* got no entries, MX code has a case for this */
	}
	else if (sm_is_err(dnsres)) {
		/* Some other DNS error */
		if (DNSR_REFUSED == dnsres)
			sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
				AR_LMOD_RCPT_CB, SM_LOG_CRIT, 6,
				"sev=CRIT, func=smar_rcpt_cb, pa=%#S, query=%.256C, dnstype=%s, error=%m",
				smar_rcpt->arr_pa, dns_res->dnsres_query,
				dnstype2txt(dnstype), dnsres);
		else
			sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
				AR_LMOD_RCPT_CB, SM_LOG_NOTICE, 7,
				"sev=NOTICE, func=smar_rcpt_cb, pa=%S, query=%.256C, dnstype=%s, error=%m",
				smar_rcpt->arr_pa, dns_res->dnsres_query,
				dnstype2txt(dnstype), dnsres);

		/*
		**  This needs to deal with errors for A records of MX records,
		**  i.e., partial results!
		*/

		if (T_A == dnstype || SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4A)) {
			/* count it as a reply */
			smar_rcpt->arr_A_rrcvd++;
			SM_SET_FLAG(flags, SM_INCR_C_MX);
		}

		switch (dnsres) {
		  case DNSR_TEMP:
		  case DNSR_TIMEOUT:
			/* change error code? */
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_TEMP);
			break;
		  case DNSR_REFUSED:
		  case DNSR_NOTFOUND:
		  case DNSR_PERM:
		  case DNSR_NO_DATA:
		  case DNSR_MXINVALID:
		  case DNSR_PTRINVALID:
		  case DNSR_CNINVALID:
		  case sm_error_perm(SM_EM_DNS, EINVAL):
			/* change error code? */
			break;
		  default:
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RCPT_CB,
				SM_LOG_ERROR, 0,
				"sev=ERROR, func=smar_rcpt_cb, unknown_error=%#x",
				dns_res->dnsres_ret);

			/* Which error to return to caller? */
			ret = dnsres; /* sm_error_perm(SM_EM_AR, EINVAL); */
			goto error;
		}

		/*
		**  Error out iff
		**  this query was for an MX record  or
		**  this query was for an A record
		**	and all records have been received
		**	and	(there was a temporary error or
		**		there was only one (failed) query or
		**		there are no results (A records))
		**	and !(the first result is valid)
		**
		**  What about errors only for "less important" data, e.g.,
		**  A MX 1 A1
		**  A MX 2 A2
		**  Lookup for A1 is ok, for A2 (temp) fails.
		**  It might be good enough to send A1 back, BUT
		**  if delivery to A1 fails, A2 should be tried, which
		**  won't happen until TTL expires...
		**  We could add a flag indicating a temporary failure
		**  or simply reduce TTL (cheating...) to force another lookup
		**  later on.
		**
		**  libdns sends the MX records sorted according to preference,
		**  hence we only need to check whether the first entry in
		**  arr_res is valid, e.g., arr_res[0]->ardns_n_A > 0 &&
		**  arr_res[0]->ardns_A_rrs != NULL
		**  not really: the first entries could have the same
		**  preference... need to check them (while not valid but
		**  preference is the same as first entry...)
		*/

#if 0
sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
AR_LMOD_RCPT_CB, SM_LOG_WARN, 1,
"sev=WARN, func=smar_rcpt_cb, pa=%S, dnstype=%d, flags=%#x, error=%#x, arr_A_rrcvd=%d, arr_A_qsent=%d, smar_res_good=%d",
smar_rcpt->arr_pa, dnstype, smar_rcpt->arr_flags, dnsres, smar_rcpt->arr_A_qsent, smar_rcpt->arr_A_rrcvd, smar_res_good(smar_rcpt));
#endif /* 0 */

		if ((SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4MX) &&
		     !SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4A))
		    ||
		    (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd &&
		     SMARR_IS_FLAG(smar_rcpt, SMARR_FL_A4A) &&
		     (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_TEMP) ||
		      1 == smar_rcpt->arr_A_qsent || 0 == smar_rcpt->arr_n_A) &&
		     !smar_res_good(smar_rcpt)
		    )
		   )
		{
			ret = smar_rcpt_re_err(smar_rcpt, dnsres);
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup here? */
			}
		}
		else if (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd) {
			ret = smar_rcpt_re_all(smar_rcpt);
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup here? */
			}
		}

		/* got all results for this rcpt? */
		if (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd) {
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
			++smar_rcpts->arrs_resolved;
			SM_SET_FLAG(flags, SM_INCR_RESOLVED);
		}

		/* fall through: dnsres != SM_SUCCESS -> reply part below */
	}

	if (SM_SUCCESS == dnsres && n_entries == 1 &&
	    (dns_rese = DRESL_FIRST(dns_res)) != NULL &&
	    T_CNAME == dns_rese->dnsrese_type)
	{
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, got=CNAME, entries=%d\n", smar_rcpt->arr_pa, n_entries));

		if (T_MX == dns_res->dnsres_qtype) {
			if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_C_MX)) {
				/* don't want two CNAMEs */
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_MX_L);
				/* XXX set error code? */
			}
			else
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_MX);
		}
		else if (T_A == dns_res->dnsres_qtype) {
			if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_C_A)) {
				/* don't want two CNAMEs */
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_A_L);
				/* XXX set error code? */
			}
			else
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_A);
		}
		if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_C_MX_L|SMARR_FL_C_A_L)) {
			/* got CNAME before */
			ret = smar_rcpt_re_err(smar_rcpt, sm_error_perm(SM_EM_AR, ELOOP));
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup? */
			}
			++smar_rcpts->arrs_resolved;
			SM_SET_FLAG(flags, SM_INCR_RESOLVED);
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
			goto done;
		}
		else if (T_MX == dns_res->dnsres_qtype || dns_res->dnsres_qtype == T_A)
		{
			q = dns_rese->dnsrese_val.dnsresu_name;
			ret = dns_req_add(dns_mgr_ctx, q, dns_res->dnsres_qtype,
					smar_rcpt->arr_timeout, smar_rcpt_cb, ctx);
			if (sm_is_err(ret)) {
				/* XXX more cleanup? */
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
			}
		}

#if 0
		if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_C_MX)) {
			/* got CNAME before */
			ret = smar_rcpt_re_err(smar_rcpt, sm_error_perm(SM_EM_AR, ELOOP));
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup? */
			}
			++smar_rcpts->arrs_resolved;
			SM_SET_FLAG(flags, SM_INCR_RESOLVED);
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
			goto done;
		}
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_MX);
		q = dns_rese->dnsrese_val.dnsresu_name;
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, MX=%.256s\n", smar_rcpt->arr_pa, sm_cstr_data(q)));
		ret = dns_req_add(dns_mgr_ctx, q, T_MX, smar_rcpt->arr_timeout,
				smar_rcpt_cb, ctx);
		if (sm_is_err(ret)) {
			SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
		}
#endif /* 0 */
	}
	else if (SM_SUCCESS == dnsres && dnstype == T_MX) {
		uint n_MX;
		int cutoff_pref;

		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_GOTMX);
		n_MX = (0 == n_entries) ? 1 : n_entries;

		/* Check whether local name is in the list */
		cutoff_pref = -1;
		for (dns_rese = DRESL_FIRST(dns_res), i = 0;
		     !SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_NOMXCUT)
			&& dns_rese != DRESL_END(dns_res) && i < n_entries;
		     dns_rese = DRESL_NEXT(dns_rese), i++)
		{
			/* XXX ignore other RR types... CNAME handling!?!? */
			if (dns_rese->dnsrese_type != T_MX)
				continue;
			q = dns_rese->dnsrese_val.dnsresu_name;
			if (islocalname(smar_ctx, q)) {
				cutoff_pref = dns_rese->dnsrese_pref;
				break;
			}
		}

		/* if it is: count number of entries with lower preference */
		if (cutoff_pref != -1) {
			n_MX = 0;
			for (dns_rese = DRESL_FIRST(dns_res), i = 0;
			     dns_rese != DRESL_END(dns_res) && i < n_entries;
			     dns_rese = DRESL_NEXT(dns_rese), i++)
			{
				/* XXX ignore other RR types... CNAME!?!? */
				if (dns_rese->dnsrese_type != T_MX)
					continue;
				if (cutoff_pref > dns_rese->dnsrese_pref)
					++n_MX;
			}
			if (0 == n_MX) {
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, cutoff_pref=%d, n_MX=%d\n", smar_rcpt->arr_pa, cutoff_pref, n_MX));

				/* Oops... "Mail loops back to me"? */
				ret = smar_rcpt_re_err(smar_rcpt,
					sm_error_perm(SM_EM_AR, SM_E_MXEMPTY));
				if (sm_is_err(ret)) {
					SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
					goto error; /* XXX need more cleanup? */
				}
				++smar_rcpts->arrs_resolved;
				SM_SET_FLAG(flags, SM_INCR_RESOLVED);
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
				goto done;
			}
		}

SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, nMX=%d\n", n_entries));

		/* restrict the number of entries to some sane value */
		if (n_MX > SM_DNS_MX_MAX)
			n_MX = SM_DNS_MX_MAX;

		n = n_MX * sizeof(*(smar_rcpt->arr_res));
		if (n < n_MX || n < sizeof(*(smar_rcpt->arr_res))) {
			SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
			ret = sm_error_perm(SM_EM_AR, SM_E_OVFLW_SC);
			goto error; /* XXX need more cleanup here? */
		}
		smar_rcpt->arr_res = (smar_dns_T *) sm_zalloc(n);
		if (NULL == smar_rcpt->arr_res) {
			SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
			ret = sm_error_perm(SM_EM_AR, ENOMEM);
			goto error; /* XXX need more cleanup here? */
		}

		for (dns_rese = DRESL_FIRST(dns_res), i = 0;
		     dns_rese != DRESL_END(dns_res) && i < n_entries;
		     dns_rese = DRESL_NEXT(dns_rese))
		{
			/* XXX ignore other RR types... CNAME handling!?!? */
			if (dns_rese->dnsrese_type != T_MX) {
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, i=%d, pa=%S, CNAME=%.256C, skipped\n", i, smar_rcpt->arr_pa, (T_CNAME == dns_rese->dnsrese_type) ? dns_rese->dnsrese_val.dnsresu_name : NULL));
				continue;
			}
			if (cutoff_pref != -1 && cutoff_pref <= dns_rese->dnsrese_pref)
				continue;
			if (i >= n_MX)
				continue;

			q = dns_rese->dnsrese_val.dnsresu_name;
			smar_rcpt->arr_res[i].ardns_ttl = dns_rese->dnsrese_ttl;
			smar_rcpt->arr_res[i].ardns_pref = dns_rese->dnsrese_pref;
			smar_rcpt->arr_res[i].ardns_name = SM_CSTR_DUP(q);
			smar_rcpt->arr_res[i].ardns_n_A = -1;
			smar_rcpt->arr_res[i].ardns_A_rrs = NULL;
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, i=%d, pa=%S, MX=%.256C\n", i, smar_rcpt->arr_pa, q));
			ret = dns_req_add(dns_mgr_ctx, q, T_A, smar_rcpt->arr_timeout,
					smar_rcpt_cb, ctx);
SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, MX=%.256C, dns_req_add=%#x\n", smar_rcpt->arr_pa, q, ret));
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
				smar_rcpt->arr_A_qsent = i;
				break; /* XXX need more cleanup here? */
			}
			++smar_rcpt->arr_A_qsent;
			++i;
		}
		SM_ASSERT(smar_rcpt->arr_A_qsent <= n_MX);

		/* got no MX record but look for A record? */
		if (n_entries == 0 || i == 0) {
			q = dns_res->dnsres_query;
			SM_ASSERT(n_MX > 0);
			smar_rcpt->arr_res[0].ardns_ttl = SMAR_DEFAULT_TTL;
			smar_rcpt->arr_res[0].ardns_pref = 0;
			smar_rcpt->arr_res[0].ardns_name = SM_CSTR_DUP(q);
			smar_rcpt->arr_res[0].ardns_n_A = -1;
			smar_rcpt->arr_res[0].ardns_A_rrs = NULL;
			ret = dns_req_add(dns_mgr_ctx, q, T_A,
				smar_rcpt->arr_timeout, smar_rcpt_cb, ctx);
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
				goto error; /* XXX need more cleanup here? */
			}
			++smar_rcpt->arr_A_qsent;
			SM_ASSERT(smar_rcpt->arr_A_qsent <= n_MX);
		}

		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_A4A);
	}
	/* XXX code review: continue here 2004-03-16 17:32:37 */
	else if (SM_SUCCESS == dnsres && dnstype == T_A) {
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_GOTA);
		if (n_entries > 0) {
			uint n_A;
			smar_dns_P smar_dns;

SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, nA_entries=%d, nMX=%d\n", n_entries, smar_rcpt->arr_A_qsent));

			/* XXX Count this always? */
			smar_rcpt->arr_A_rrcvd++;
			if (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd)
				SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);

			/* Find matching entry in list of MX records */
			for (i = 0; i < smar_rcpt->arr_A_qsent; i++) {
				q = smar_rcpt->arr_res[i].ardns_name;
SMAR_LEV_DPRINTF(5, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, i=%d, MX=%.256C, compare=%.256C\n", i, dns_res->dnsres_query, q));
				if (q != NULL && SM_CSTR_CASEQ(q, dns_res->dnsres_query))
					break;
			}
			if (i >= smar_rcpt->arr_A_qsent) {
				sm_log_write(smar_ctx->smar_lctx,
					AR_LCAT_RESOLVER, AR_LMOD_RCPT_CB,
					SM_LOG_ERROR, 0,
					"sev=ERROR, func=smar_rcpt_cb, MX=%.256C, status=cannot_find_MX, entries=%d",
					dns_res->dnsres_query,
					smar_rcpt->arr_A_qsent);

				/*
				**  XXX What now? Abort query somehow?
				*/

				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
				ret = sm_error_perm(SM_EM_AR, SM_E_NOTFOUND);
				goto error; /* XXX more cleanup! better handling */
			}
			smar_dns = &(smar_rcpt->arr_res[i]);
			SM_SET_FLAG(flags, SM_INCR_C_MX);

			n_A = (n_entries < SM_DNS_A_PER_MX_MAX)
				? n_entries : SM_DNS_A_PER_MX_MAX;
			n = n_A * sizeof(*(smar_dns->ardns_A_rrs));
			if (n < n_A || n < sizeof(*(smar_dns->ardns_A_rrs))) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
				ret = sm_error_perm(SM_EM_AR, SM_E_OVFLW_SC);
				goto error; /* XXX need more cleanup here? */
			}
			smar_dns->ardns_A_rrs = (ipv4_T *) sm_zalloc(n);
			if (NULL == smar_dns->ardns_A_rrs) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
				ret = sm_error_perm(SM_EM_AR, ENOMEM);
				goto error; /* XXX need more cleanup here? */
			}

			smar_dns->ardns_n_A = 0;
			for (dns_rese = DRESL_FIRST(dns_res), i = 0;
			     dns_rese != DRESL_END(dns_res) && i < n_entries;
			     dns_rese = DRESL_NEXT(dns_rese))
			{
				/* XXX ignore other RR types... CNAME!?!? */
				if (dns_rese->dnsrese_type != T_A)
					continue;
				if (i >= n_A)
					continue;

				/* got the IP address already? */
				if (gotA(smar_rcpt, dns_rese->dnsrese_val.dnsresu_a))
					continue;

				sm_log_write(smar_ctx->smar_lctx,
					AR_LCAT_RESOLVER, AR_LMOD_RCPT_CB,
					SM_LOG_INFO, 14,
					"sev=INFO, func=smar_rcpt_cb, i=%d, pa=%S, ip=%A",
					i, smar_rcpt->arr_pa,
					dns_rese->dnsrese_val.dnsresu_a);
				smar_dns->ardns_A_rrs[i] = dns_rese->dnsrese_val.dnsresu_a;
				++i;
				++smar_rcpt->arr_n_A;
				++smar_dns->ardns_n_A;
			}

			/* Got all entries? */
			if (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd) {
				ret = smar_rcpt_re_all(smar_rcpt);
				if (sm_is_err(ret)) {
					SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
					goto error; /* XXX need more cleanup? */
				}

				++smar_rcpts->arrs_resolved;
				SM_SET_FLAG(flags, SM_INCR_RESOLVED);
			}
		}
		else {
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RCPT_CB,
				SM_LOG_ERROR, 0,
				"sev=ERROR, func=smar_rcpt_cb, dnstype=T_A, query=%.256C, entries=0",
				dns_res->dnsres_query);
		}
#if 0
		sm_log_write(smar_ctx->smar_lctx,
			AR_LCAT_RESOLVER, AR_LMOD_RCPT_CB,
			SM_LOG_INFO, 12,
			"sev=INFO, func=smar_rcpt_cb, pa=%S, ip=%A",
			smar_rcpt->arr_pa,
			smar_rcpt->arr_ipv4);
#endif /* 0 */

#if 0
sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
AR_LMOD_RCPT_CB, SM_LOG_WARN, 1,
"sev=WARN, func=smar_rcpt_cb, pa=%S, dnstype=%d, arr_A_rrcvd=%d, arr_A_qsent=%d",
smar_rcpt->arr_pa, dnstype, smar_rcpt->arr_A_qsent, smar_rcpt->arr_A_rrcvd);
#endif /* 0 */
	}
	else if (SM_SUCCESS == dnsres && dnstype == T_CNAME) {
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, got=CNAME, entries=%d\n", smar_rcpt->arr_pa, n_entries));
		if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_C_MX) || n_entries != 1) {
			/* got CNAME before or got not exactly one result */
			ret = smar_rcpt_re_err(smar_rcpt, sm_error_perm(SM_EM_AR, ELOOP));
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup? */
			}
			++smar_rcpts->arrs_resolved;
			SM_SET_FLAG(flags, SM_INCR_RESOLVED);
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_FREEIT);
			goto done;
		}
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_C_MX);
		dns_rese = DRESL_FIRST(dns_res);
		q = dns_rese->dnsrese_val.dnsresu_name;
SMAR_LEV_DPRINTF(1, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_cb, pa=%S, MX=%.256s\n", smar_rcpt->arr_pa, sm_cstr_data(q)));
		ret = dns_req_add(dns_mgr_ctx, q, T_MX,
			smar_rcpt->arr_timeout, smar_rcpt_cb, ctx);
		if (sm_is_err(ret))
			SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_TEMP);
	}
	else if (SM_SUCCESS == dnsres) {
		/* XXX other T_*: ignore for now, deal with them later! */
	}

  done:
	if (smar_rcpt != NULL && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_FREEIT)
	    && !SM_IS_FLAG(flags, SM_NOFREE))
	{
		ret = smar_rcpt_free(smar_rcpt, smar_rcpts);
		smar_rcpt = NULL;
	}
	if (smar_rcpts->arrs_resolved == smar_rcpts->arrs_lst_n) {
		if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_ERCB|SMARRS_FL_TEMP)) {
			/*
			**  Some error occurred earlier on which causes
			**  a bogus result.  Replace the data in RCB with
			**  an appropriate error message.
			*/

			SMARRS_CLR_FLAG(smar_rcpts, SMARRS_FL_INITRE);
			ret = smar_rcpts_re_err(smar_rcpts, SM_E_ALIASEXP);
			/* XXX what to do if this fails? */
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_FAIL);
				goto error;
			}
		}
		if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND)) {
			SM_REQUIRE(smar_rcpts->arrs_cbf != NULL);
			ret = smar_rcpts->arrs_cbf(smar_rcpts, smar_rcpts->arrs_cb_ctx);
			if (sm_is_err(ret))
				goto error;
		}
		else {
			ret = sm_rcbcom_endrep(&smar_clt_ctx->smac_com_ctx,
				smar_clt_ctx->smac_com_ctx.rcbcom_tsk,
				false, &smar_rcpts->arrs_rcbe);
			if (sm_is_err(ret))
				goto error;
		}
		if (!SM_IS_FLAG(flags, SM_NOFREE)) {
			ret = smar_rcpts_free(smar_rcpts); /* XXX */
			smar_rcpts = NULL;
		}
	}

	r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_AR, r);
	return ret;

  error:
	/* XXX Cleanup? */
	sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
		AR_LMOD_RCPT_CB, SM_LOG_ERROR, 0,
		"sev=ERROR, func=smar_rcpt_cb, ret=%m", ret);

	if (T_A == dnstype && !SM_IS_FLAG(flags, SM_INCR_C_MX) && smar_rcpt != NULL)
	{
		smar_rcpt->arr_A_rrcvd++;
		SM_SET_FLAG(flags, SM_INCR_C_MX);
	}
	if (T_MX == dnstype ||
	    (smar_rcpt != NULL &&
	     (smar_rcpt->arr_A_qsent == smar_rcpt->arr_A_rrcvd
	      || SMARR_IS_FLAG(smar_rcpt, SMARR_FL_FREEIT))))
	{
		sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
			AR_LMOD_RCPT_CB, SM_LOG_WARN, 1,
			"sev=ERROR, func=smar_rcpt_cb, pa=%S, dnstype=%s, error=%#x, arr_A_rrcvd=%d, arr_A_qsent=%d, flags=%#x"
			, smar_rcpt->arr_pa, dnstype2txt(dnstype), ret
			, smar_rcpt->arr_A_qsent, smar_rcpt->arr_A_rrcvd
			, flags);
		if (!SM_IS_FLAG(flags, SM_NOFREE)) {
			ret = smar_rcpt_free(smar_rcpt, smar_rcpts);
			smar_rcpt = NULL;
		}

		if (!SM_IS_FLAG(flags, SM_INCR_RESOLVED) && smar_rcpts != NULL) {
			/* Really?? */
			++smar_rcpts->arrs_resolved;
			SM_SET_FLAG(flags, SM_INCR_RESOLVED);
		}
		if (smar_rcpts != NULL &&
		    smar_rcpts->arrs_resolved == smar_rcpts->arrs_lst_n)
		{
			if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_ERCB|SMARRS_FL_TEMP)
			    && !SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_FAIL))
			{
				ret = smar_rcpts_re_err(smar_rcpts, SM_E_ALIASEXP);
				if (SM_SUCCESS == ret) {
					if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND)) {
						SM_REQUIRE(smar_rcpts->arrs_cbf != NULL);
						ret = smar_rcpts->arrs_cbf(smar_rcpts,
							smar_rcpts->arrs_cb_ctx);
					}
					else {
						ret = sm_rcbcom_endrep(&smar_clt_ctx->smac_com_ctx,
							smar_clt_ctx->smac_com_ctx.rcbcom_tsk,
							false, &smar_rcpts->arrs_rcbe);
					}
				}
			}
			if (!SM_IS_FLAG(flags, SM_NOFREE)) {
				ret = smar_rcpts_free(smar_rcpts); /* XXX */
				smar_rcpts = NULL;
			}
		}
	}
	r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_AR, r);
	return ret;
}

/*
**  SMAR_RCPT_RSLV -- Resolve RCPT address
**
**	Parameters:
**		smar_ctx -- SMTPC context
**		smar_rcpts -- SMAR RCPT LIST context
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_react()
**		smar_access_chk()
**
**	Side Effects:
**	smar_rcpts is freed before returning unless SMARRS_FL_NOFREE is set.
**
**	To return results to the client this function calls:
**	- smar_rcpt_re_ipv4()
**	- smar_rcpt_re_ipv4_more()
**	- smar_rcpt_re_err()
**	and it invokes the dns resolver library with smar_rcpt_cb() as callback
**
**	ToDo: check error handling
**	allow this function to be used as subroutine by others, i.e., do not
**		create a reply (RCB), but return some result(s) instead.
**		compare smar_rvrs_cb()
**	Note: as soon as the DNS resolver comes into play, this
**		operates asynchronously.
*/

sm_ret_T
smar_rcpt_rslv(smar_ctx_P smar_ctx, smar_rcpts_P smar_rcpts)
{
	sm_ret_T ret;
	char *ipv4s, *eos;
#if 0
	int r;
	bool locked;
#endif
	bool incremented, gotport;
	uint8_t flags;
	dns_mgr_ctx_P dns_mgr_ctx;
	sm_cstr_P q;
	smar_clt_ctx_P smar_clt_ctx;
	sm_str_P mtstr;
	smar_rcpt_P smar_rcpt, smar_rcpt_nxt;
#if SMAR_TEST
	long randval;
#endif

	SM_IS_SMAR_CTX(smar_ctx);
	SM_IS_SMAR_RCPTS(smar_rcpts);

	/* either nosend must be set or RCB must exist */
	SM_REQUIRE(SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND) ||
		smar_rcpts->arrs_rcbe != NULL);

	smar_rcpt = smar_rcpts->arrs_rcpt;
	SM_IS_SMAR_RCPT(smar_rcpt);
	smar_clt_ctx = smar_rcpts->arrs_smar_clt_ctx;
	SM_IS_SMAR_CLT_CTX(smar_clt_ctx);
	ret = SM_SUCCESS;
	SMARR_SET_FLAG(smar_rcpt, SMARR_FL_A4MT);
	smar_rcpt->arr_da = 0;	/* XXX HACK DA */
	mtstr = NULL;
	q = NULL;
#if 0
	locked = false;
#endif
	incremented = false;
	smar_rcpts->arrs_ret = SM_SUCCESS;

	/* just in case something goes wrong early on */
	smar_rcpt->arr_rcpts = smar_rcpts;
	flags = 0;

	/* expand rcpt according to aliases (also parses address) */
	ret = smar_rcpt_expand(smar_rcpts, smar_rcpt, 0, 0, 0);
	if (sm_is_err(ret))
		goto error;
	ret = smar_rcpts_t2l(smar_rcpts);
	if (sm_is_err(ret))
		goto error;

#if 0
	r = pthread_mutex_lock(&smar_rcpts->arrs_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_AR, r);
		goto error;
	}
	locked = true;
#endif /* 0 */

	if (RCPTS_EMPTY(smar_rcpts)) {
		ret = sm_error_perm(SM_EM_AR, EINVAL);
		goto error;
	}

	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND))
		SM_SET_FLAG(flags, SM_NOSEND);
	if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOFREE))
		SM_SET_FLAG(flags, SM_NOFREE);

	/* XXX use different length? */
	mtstr = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == mtstr)
		goto enomem;

	/* loop through all recipients in list */
	for (smar_rcpt = RCPTS_FIRST(smar_rcpts);
	     smar_rcpt != RCPTS_END(smar_rcpts);
	     smar_rcpt = smar_rcpt_nxt)
	{
		smar_rcpt_nxt = RCPTS_NEXT(smar_rcpt);

		/* XXX HACK XXX */
		if (smar_rcpts->arrs_rcpt != smar_rcpt && smar_rcpts->arrs_lst_n == 1) {
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, 1-1-replace: pa=%S\n", smar_rcpt->arr_pa));
			/*
			**  free() original recipient unless it is referenced
			**  from the list or the hash table.
			*/

			if (smar_rcpts->arrs_rcpt != NULL &&
			    !SMARR_IS_FLAG(smar_rcpts->arrs_rcpt,
					SMARR_FL_INRLST|SMARR_FL_INOWNLST|SMARR_FL_INHT))
			{
				SM_ASSERT(SMARR_IS_FLAG(smar_rcpts->arrs_rcpt, SMARR_FL_ORCPT));
				SMARR_CLR_FLAG(smar_rcpts->arrs_rcpt, SMARR_FL_ORCPT);
				smar_rcpt_free(smar_rcpts->arrs_rcpt, NULL);
			}
			smar_rcpts->arrs_rcpt = smar_rcpt;
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ALIAS|SMARR_FL_ORCPT);
		}

		/* is there an error already? */
		if (smar_rcpt->arr_ret != SMTP_R_OK) {
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, pa=%S, arr_ret=%d\n", smar_rcpt->arr_pa, smar_rcpt->arr_ret));
			ret = smar_rcpt_re_err(smar_rcpt, smar_rcpt->arr_ret);
			if (sm_is_err(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
				goto error; /* XXX need more cleanup here? */
			}
			goto endcom;
		}

#if SMAR_TEST
		if (smar_ctx->smar_rand_err > 0 &&
		    smar_ctx->smar_rand_err < (randval = random()))
		{
			ret = smar_rcpt_re_err(smar_rcpt,
				((randval & 3) == 0) ? DNSR_PERM : DNSR_TEMP);

			/*
			**  XXX WRONG... but not important:
			**  TEST only
			**  should be set error-flag; continue;
			*/

			if (sm_is_err(ret))
				goto error;
			goto endcom;
		}
#endif /* SMAR_TEST */


		/* scanning/parsing has been done by smar_rcpt_expand() */
		SM_ASSERT(smar_rcpt->arr_domain_pa != NULL);
		sm_str2lower(smar_rcpt->arr_domain_pa);

		/* Lookup domain in mailertable */
		sm_str_clr(mtstr);
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, pa=%S, mt_lfl=%#x, user_pa=%S, detail=%S, delim=%c\n", smar_rcpt->arr_pa, smar_ctx->smar_mt_lfl, smar_rcpt->arr_user_pa, smar_rcpt->arr_detail_pa, smar_rcpt->arr_delim));
		if (SMAR_MT_FULL_LOOKUP(smar_ctx) &&
		    smar_rcpt->arr_user_pa != NULL && smar_rcpt->arr_detail_pa != NULL)
		{
			uint32_t lfl;

			lfl = smar_ctx->smar_mt_lfl;
			if (!SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASDET))
				lfl &= ~SMMAP_LFL_DET;
			ret = sm_map_lookup_addr(smar_ctx->smar_mt_map,
				smar_rcpt->arr_user_pa,
				SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASDET) ? smar_rcpt->arr_detail_pa : NULL,
				smar_rcpt->arr_domain_pa,
				/* tag */ NULL,
				smar_rcpt->arr_delim,
				lfl, mtstr);
		}
		else
			ret = sm_map_lookup_domain(smar_ctx->smar_mt_map,
				(sm_rdstr_P) smar_rcpt->arr_domain_pa,
				NULL, SMMAP_LFL_DOT, mtstr);
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, pa=%S, domain=%S, lookup=%#x, mtstr=%S\n", smar_rcpt->arr_pa, smar_rcpt->arr_domain_pa, ret, mtstr));
		if (SM_SUCCESS == ret) {
			/* just the delimiter? -> use DNS lookups */
			if (sm_str_getlen(mtstr) == 1 &&
			    sm_str_rd_elem(mtstr, 0) == SMAR_RHS_PROT_C)
				ipv4s = NULL;
			else
				ipv4s = (char *) sm_str_getdata(mtstr);
		}
		else if (sm_str_rd_elem(smar_rcpt->arr_domain_pa, 0) == '[')
			ipv4s = (char *) sm_str_getdata(smar_rcpt->arr_domain_pa);
		else {
			ret = SM_SUCCESS;
			ipv4s = NULL;
		}
		gotport = false;

		/* port ? */
		if (ipv4s != NULL && ISDIGIT(*ipv4s)
#if SMAR_TEST
	/* change this to a "mailer" name? temp: perm: noreply: */
		     && !((*ipv4s == '4' || *ipv4s == '5' || *ipv4s == '6')
			 && *(ipv4s + 1) == '\0')
#endif /* SMAR_TEST */
		   )
		{
			char *endptr;
			ulong v;

			v = strtoul(ipv4s, &endptr, 10);
			if (v > SHRT_MAX || *endptr != SMAR_RHS_PORT_C) {
				/* log and return some error */
				sm_log_write(smar_ctx->smar_lctx,
					AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
					SM_LOG_ERROR, 1,
					"sev=ERROR, func=smar_rcpt_rslv, mt_rhs=%.256s, status=invalid_syntax",
					ipv4s);

				/* XXX better error code! */
				ret = smar_rcpt_re_err(smar_rcpt,
					sm_error_perm(SM_EM_AR, EINVAL));
				if (sm_is_err(ret)) {
					SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);
					goto error; /* XXX need more cleanup here? */
				}

				/* XXX what's the correct next step? */
				continue;
			}
			ipv4s = endptr + 1;
			SM_ASSERT(ipv4s <= (char *) sm_str_data(mtstr)
					+ sm_str_getlen(mtstr));
			smar_rcpt->arr_port = (short) v;
			gotport = true;
		}

#define LMTP_MT_LEN	(sizeof(LMTP_MT) - 1)
		/* lmtp: ? */
		if (ipv4s != NULL && strncmp(ipv4s, LMTP_MT, LMTP_MT_LEN) == 0) {
			smar_rcpt->arr_da = gotport ? DA_IDX_LMTP_INET : DA_IDX_LMTP_UNIX;
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_GOTMT);
			smar_rcpt->arr_ipv4 = LMTP_IPV4_ADDR;
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_INFO, 10,
				"sev=INFO, func=smar_rcpt_rslv, status=resolved, pa=%S, mailer=%s",
				smar_rcpt->arr_pa,
				(DA_IDX_LMTP_INET == smar_rcpt->arr_da) ?
					"lmtp_inet" :  "lmtp_unix");
			if (DA_IDX_LMTP_UNIX == smar_rcpt->arr_da) {
				ret = smar_rcpt_re_ipv4(smar_rcpt, 1,
					smar_rcpts->arrs_resolved == 0);
				if (sm_is_err(ret)) {
					sm_log_write(smar_ctx->smar_lctx,
						AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
						SM_LOG_ERROR, 0,
						"sev=ERROR, func=smar_rcpt_rslv, called=resolved, pa=%S, mailer=lmtp, ret=%m",
						smar_rcpt->arr_pa, ret);
					SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);

					/* XXX OK? need more cleanup? */
					goto error;
				}
				goto endcom;
			}
			ipv4s += LMTP_MT_LEN;
			SM_ASSERT(ipv4s <= (char *) sm_str_data(mtstr)
					+ sm_str_getlen(mtstr));
		}

#define ESMTP_MT	"esmtp:"
#define ESMTP_MT_LEN	6	/* strlen(ESMTP_MT) */
		if (ipv4s != NULL && strncmp(ipv4s, ESMTP_MT, ESMTP_MT_LEN) == 0) {
			ipv4s += ESMTP_MT_LEN;
			SM_ASSERT(ipv4s <= (char *) sm_str_data(mtstr)
					+ sm_str_getlen(mtstr));
			if (DA_IDX_LMTP_INET == smar_rcpt->arr_da) {
				sm_log_write(smar_ctx->smar_lctx,
					AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
					SM_LOG_ERROR, 4,
					"sev=ERROR, func=smar_rcpt_rslv, status=bogus_RHS_in_mt, RHS=%@S"
					, mtstr);
			}
			smar_rcpt->arr_da = DA_IDX_ESMTP; /* default anyway */
		}

		/*
		**  If RHS starts with '[': it should be an IP address (IPv4)
		**  otherwise it should be a host/domainname which is given
		**  to the DNS resolver (see below).
		**  Note: NO recursive mailertable lookups!
		**  Problem:
		**  the DNS resolver currently deals only with one domain,
		**  it's not possible to have multiple...
		*/

		if (ipv4s != NULL &&
		    (*ipv4s == '['
#if SMAR_TEST
		     || ((*ipv4s == '4' || *ipv4s == '5' || *ipv4s == '6')
			 && *(ipv4s + 1) == '\0')
#endif
		   ))
		{
			uint32_t n_addr, i;
			char *addr, *endaddr;

			n_addr = 1;
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_GOTMT);

			/*
			**  Allow multiple entries?
			**  First: count number of entries.
			*/

			for (addr = ipv4s; *addr != '\0'; ++addr) {
				if (*addr == SMAR_RHS_SEP_C &&
				    *(addr + 1) != '\0' &&
				    *(addr + 1) != SMAR_RHS_SEP_C)
					++n_addr;
			}
			eos = addr;	/* end of string */
			for (addr = ipv4s, i = 0;
			     *addr != '\0' && addr < eos;
			     ++addr, ++i)
			{
				while (*addr != '\0' && *addr == SMAR_RHS_SEP_C && addr < eos)
					++addr;
				if (*addr == '\0' || addr >= eos)
					break;
#if SMAR_TEST
				if ((*addr == '4' || *addr == '5') && *(addr + 1) == '\0') {
					smar_rcpt->arr_ipv4 = atoi(addr);
					ret = smar_rcpt_re_err(smar_rcpt,
						(*addr == '4') ? DNSR_TEMP : DNSR_PERM);

					/*
					**  XXX WRONG... but not important:
					**  TEST only
					**  should be set error-flag; continue;
					*/

					if (sm_is_err(ret))
						goto error;
					goto endcom;
				}
				if (*addr == '6' && *(addr + 1) == '\0') {
					/* don't reply (timeout for caller) */
					sm_rcb_close_n(&smar_rcpts->arrs_rcbe->rcbe_rcb);
					/* XXX is this a leak? free rcbe? */

					/* XXX only works for one entry!  */
					smar_rcpt_free(smar_rcpt, smar_rcpts);

					if (!SM_IS_FLAG(flags, SM_NOFREE)) {
						/* XXX */
						smar_rcpts_free(smar_rcpts);
					}
					return SM_SUCCESS;
				}
#endif /* SMAR_TEST */

				ret = sm_inet_a2ipv4(addr, &endaddr, &smar_rcpt->arr_ipv4);
				if (sm_is_err(ret)) {
					sm_log_write(smar_ctx->smar_lctx,
						AR_LCAT_INIT, AR_LMOD_CONFIG,
						SM_LOG_ERROR, 0,
						"sev=ERROR, func=smar_rcpt_rslv, not_an_IPv4_address=\"%.256s\"",
						addr);
					smar_rcpt->arr_ipv4 = INADDR_NONE;
				}

#if 0
SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, resolved: pa=%S, ipv4=%A, addr=%.256s, i=%d/%d\n", smar_rcpt->arr_pa, (ipv4_T) smar_rcpt->arr_ipv4, addr, i, n_addr));
#endif /* 0 */

				/*
				**  note: addr may point to a list of addresses
				**  should that really be logged? for example:
				**  addr=[1.0.0.65]    [1.0.0.66]     ,
				*/

				sm_log_write(smar_ctx->smar_lctx,
					AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
					SM_LOG_INFO, 13,
					"sev=INFO, func=smar_rcpt_rslv, status=resolved, pa=%S, ipv4=%A, addr=%.256s, port=%hd, i=%d/%d",
					smar_rcpt->arr_pa,
					(ipv4_T) smar_rcpt->arr_ipv4,
					addr, smar_rcpt->arr_port, i, n_addr);
				if (SM_SUCCESS == ret && endaddr != NULL && endaddr <= eos)
					addr = endaddr;

				if (0 == i) {
					if (n_addr == 0 || INADDR_NONE == smar_rcpt->arr_ipv4) {
						smar_rcpt->arr_ipv4 = inet_addr("127.0.0.1");
						n_addr = 1;
					}
					ret = smar_rcpt_re_ipv4(smar_rcpt, n_addr,
						smar_rcpts->arrs_resolved == 0); if (sm_is_err(ret))
					{
						sm_log_write(smar_ctx->smar_lctx,
							AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
							SM_LOG_ERROR, 0,
							"sev=ERROR, func=smar_rcpt_rslv, called=resolved, pa=%S, ip=%A, addr=%.256s, i=%d, ret=%m",
							smar_rcpt->arr_pa,
							smar_rcpt->arr_ipv4,
							addr, i, ret);
						SMARRS_SET_FLAG(smar_rcpts,
							SMARRS_FL_ERCB);

						/* XXX OK? need more cleanup? */
						goto error;
					}
				}
				else if (smar_rcpt->arr_ipv4 != INADDR_NONE &&
					 !SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND))
				{
					/* Write answer to RCB */
					ret = smar_rcpt_re_ipv4_more(
						&smar_rcpts->arrs_rcbe->rcbe_rcb, smar_rcpt->arr_ipv4);
					if (sm_is_err(ret)) {
						sm_log_write(smar_ctx->smar_lctx,
							AR_LCAT_RESOLVER,
							AR_LMOD_RESOLVER,
							SM_LOG_ERROR, 0,
							"sev=ERROR, func=smar_rcpt_rslv, called=more, pa=%S, ip=%A, i=%d, ret=%m",
							smar_rcpt->arr_pa,
							smar_rcpt->arr_ipv4,
							i, ret);
						SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_ERCB);

						/* XXX OK? need more cleanup? */
						goto error;
					}
				}
				while (*addr != '\0' && *addr != SMAR_RHS_SEP_C && addr < eos)
					++addr;
			}

  endcom:
			++smar_rcpts->arrs_resolved;
			incremented = true;
SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, status=resolved, pa=%S, ip=%A\n", smar_rcpt->arr_pa, (ipv4_T) smar_rcpt->arr_ipv4));

			if (!SM_IS_FLAG(flags, SM_NOFREE)) {
				smar_rcpt_free(smar_rcpt, smar_rcpts);
				smar_rcpt = NULL;
			}

			if (smar_rcpts->arrs_resolved >= smar_rcpts->arrs_lst_n) {
				if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND)) {
					SM_REQUIRE(smar_rcpts->arrs_cbf != NULL);
					ret = smar_rcpts->arrs_cbf(smar_rcpts,
						smar_rcpts->arrs_cb_ctx);
				}
				else {
					ret = sm_rcbcom_endrep(&smar_clt_ctx->smac_com_ctx,
						smar_clt_ctx->smac_com_ctx.rcbcom_tsk, false,
						&smar_rcpts->arrs_rcbe);
				}

				/* XXX add a flag "result already sent"? */
				if (sm_is_err(ret))
					goto error;

				if (!SM_IS_FLAG(flags, SM_NOFREE))
					smar_rcpts_free(smar_rcpts); /* XXX */
				SM_STR_FREE(mtstr);
				return ret;
			}
			continue;
		}
		/* no else, return is used in then part by default */

		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_NOMT);
		dns_mgr_ctx = smar_ctx->smar_dns_mgr_ctx;

		if (ipv4s != NULL && *ipv4s != '[') {
			/* do a "syntax" check for ipv4s? valid_domain()? */
			q = sm_cstr_scpyn0((const uchar *) ipv4s, strlen(ipv4s));
		}
		else {
			uchar *domain;

			/* q MUST be '\0' terminated!  See dns_tsk_wr() */
			domain = sm_str_getdata(smar_rcpt->arr_domain_pa);
			if (domain != NULL) {
				q = sm_cstr_crt(domain, sm_str_getlen(smar_rcpt->arr_domain_pa));
				if (q != NULL) {
					/* Hack: give up "ownership" of data */
					sm_str_data(smar_rcpt->arr_domain_pa) = NULL;
					SM_STR_SETLEN(smar_rcpt->arr_domain_pa, 0);
				}
			}
			else
				q = NULL;
		}
		if (NULL == q)
			goto enomem;

		/*
		**  Set flag before passing smar_rcpt to dns_req_add()
		**  because smar_rcpt_cb() will free() smar_rcpt.
		*/

		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_A4MX);
		ret = dns_req_add(dns_mgr_ctx, q, T_MX, smar_rcpt->arr_timeout,
				smar_rcpt_cb, smar_rcpt);
		/* XXX check return? */

		/*
		**  query has been "copied" by dns_req_add().
		**  Maybe we should let dns_req_add() just "own" the query
		**  instead of "copying" it?
		*/

#if 0
		if (locked) {
			r = pthread_mutex_unlock(&smar_rcpts->arrs_mutex);
			SM_ASSERT(0 == r);
			if (0 == r)
				locked = false;
		}
#endif /* 0 */

		SM_CSTR_FREE(q);
	}

	SM_STR_FREE(mtstr);
#if 0
	if (sm_is_err(ret))
		break;
#endif
	return ret;

  enomem:
	ret = sm_error_temp(SM_EM_AR, ENOMEM);
  error:

	/* send a reply in any case? */
	if (smar_rcpt != NULL) {
		if (smar_rcpts != NULL && SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_ERCB)) {
			SMARRS_CLR_FLAG(smar_rcpts, SMARRS_FL_INITRE);
			ret = smar_rcpts_re_err(smar_rcpts, ret);
		}
		else {
			/* don't overwrite ret */
			(void) smar_rcpt_re_err(smar_rcpt, ret);
		}
SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_rslv, smar_rcpt=%p\n", smar_rcpt));
		if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_INRLST|SMARR_FL_INOWNLST)
		    && !SM_IS_FLAG(flags, SM_NOFREE))
		{
			smar_rcpt_free(smar_rcpt, smar_rcpts);
			smar_rcpt = NULL;
		}
		if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOSEND)) {
			SM_REQUIRE(smar_rcpts->arrs_cbf != NULL);
			(void) smar_rcpts->arrs_cbf(smar_rcpts, smar_rcpts->arrs_cb_ctx);
		}
		else {
			(void) sm_rcbcom_endrep(&smar_clt_ctx->smac_com_ctx,
				smar_clt_ctx->smac_com_ctx.rcbcom_tsk,
				false, &smar_rcpts->arrs_rcbe);
		}
	}
	if (!SM_IS_FLAG(flags, SM_NOFREE)) {
		smar_rcpts_free(smar_rcpts);	/* XXX */
	}

#if 0
	if (locked) {
		r = pthread_mutex_unlock(&smar_rcpts->arrs_mutex);
		SM_ASSERT(0 == r);
		if (0 == r)
			locked = false;
	}
#endif /* 0 */
	SM_STR_FREE(mtstr);
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_ERROR, 0,
		"sev=ERROR, func=smar_rcpt_rslv, ret=%m", ret);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1