/*
 * Copyright (c) 2004-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: rcpts.c,v 1.82 2007/11/14 06:03:09 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/rfc2821.h"
#include "sm/ctype.h"
#include "smar.h"
#include "sm/smardef.h"
#include "log.h"

#define MAX_ALIAS_RECURSION	5u
#if SM_ALIASES_LARGE
/* XXX RCB size? */
# define N_ALIASES	1031u
# define MAX_ALIASES	8191u
# ifndef MAXALIASLEN
#  define MAXALIASLEN	(256 * 1024)
# endif /* MAXALIASLEN */
#else /* SM_ALIASES_LARGE */
# define N_ALIASES	211u
# define MAX_ALIASES	1031u
# ifndef MAXALIASLEN
#  define MAXALIASLEN	(64 * 1024)
# endif /* MAXALIASLEN */
#endif /* SM_ALIASES_LARGE */

/*
**  SMAR_RCPTS_NEW -- Create a SMAR RCPT LIST context
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_clt_ctx -- SMAR client context
**		psmar_rcpt -- (pointer to) SMAR RCPT context (output)
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-22 04:46:10
**	Last code change:
*/

sm_ret_T
smar_rcpts_new(smar_ctx_P smar_ctx, smar_clt_ctx_P smar_clt_ctx, smar_rcpts_P *psmar_rcpts)
{
	smar_rcpts_P smar_rcpts;

	SM_REQUIRE(psmar_rcpts != NULL);
	smar_rcpts = (smar_rcpts_P) sm_zalloc(sizeof(*smar_rcpts));
	if (NULL == smar_rcpts)
		return sm_error_temp(SM_EM_AR, ENOMEM);
	smar_rcpts->arrs_rcpts = bht_new(N_ALIASES, MAX_ALIASES);
	if (NULL == smar_rcpts->arrs_rcpts) {
		sm_free_size(smar_rcpts, sizeof(*smar_rcpts));
		return sm_error_temp(SM_EM_AR, ENOMEM);
	}

	RCPTS_INIT(smar_rcpts);
	OWNER_INIT(smar_rcpts);
	smar_rcpts->arrs_smar_ctx = smar_ctx;
	smar_rcpts->arrs_smar_clt_ctx = smar_clt_ctx;
	*psmar_rcpts = smar_rcpts;
	smar_rcpts->sm_magic = SM_SMAR_RCPTS_MAGIC;
	return SM_SUCCESS;
}

/*
**  SMAR_RCPT_FREE_CB -- Callback for bht_destroy
**
**	Parameters:
**		value -- SMAR RCPT context
**		key -- ignored
**		ctx -- SMAR RCPT LIST context
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-22 04:46:31
**	Last code change: 2004-03-24 22:29:08
*/

static void
smar_rcpt_free_cb(void *value, void *key, void *ctx)
{
	smar_rcpt_P smar_rcpt;
	smar_rcpts_P smar_rcpts;

	(void) key;
	smar_rcpt = (smar_rcpt_P) value;
	smar_rcpts = (smar_rcpts_P) ctx;
	SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_free_cb, smar_rcpt=%p\n", smar_rcpt));
	(void) smar_rcpt_free(smar_rcpt, smar_rcpts);
}

/*
**  SMAR_RCPTS_FREE -- Free a SMAR RCPT LIST context
**	including all recipients in hash table and owner-list
**
**	Parameters:
**		smar_rcpts -- SMAR RCPT LIST context
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-22 04:46:50; see below
**	Last code change: 2005-10-02 18:07:53
*/

sm_ret_T
smar_rcpts_free(smar_rcpts_P smar_rcpts)
{
#if 0
	int r;
#endif
	smar_rcpt_P smar_rcpt, smar_rcpt_nxt;

	if (NULL == smar_rcpts)
		return SM_SUCCESS;

	SM_IS_SMAR_RCPTS(smar_rcpts);
	SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpts_free, smar_rcpts=%p\n", smar_rcpts));
	bht_destroy(smar_rcpts->arrs_rcpts, smar_rcpt_free_cb, smar_rcpts);

	/*
	**  Note: arrs_rcpthd is NOT checked, list should be empty.
	**  arrs_pa is not free()d, it is just a pointer to ara_pa2
	*/

	if (smar_rcpts->arrs_rcbe != NULL) {
		sm_rcbe_free(smar_rcpts->arrs_rcbe);
		smar_rcpts->arrs_rcbe = NULL;
	}

	for (smar_rcpt = OWNER_FIRST(smar_rcpts);
	     smar_rcpt != OWNER_END(smar_rcpts);
	     smar_rcpt = smar_rcpt_nxt)
	{
		smar_rcpt_nxt = OWNER_NEXT(smar_rcpt);
		smar_rcpt_free(smar_rcpt, smar_rcpts);
	}
	OWNER_INIT(smar_rcpts);

	/*
	**  This requires that the pointer is set to NULL if the recipient
	**  is free()d or that the flag is cleared (note: a bogus pointer
	**  or a pointer to a free()d value that still has the flag set
	**  will cause problems too!).  Can that be guaranteed??
	*/

	if (smar_rcpts->arrs_rcpt != NULL &&
	    SMARR_IS_FLAG(smar_rcpts->arrs_rcpt, SMARR_FL_ORCPT))
	{
		smar_rcpt_free(smar_rcpts->arrs_rcpt, NULL);
		smar_rcpts->arrs_rcpt = NULL;
	}

#if 0
	r = pthread_mutex_destroy(&smar_rcpts->arrs_mutex);
	smar_rcpts->arrs_smar_ctx = NULL;
	smar_rcpts->arrs_smar_clt_ctx = NULL;
#endif
	smar_rcpts->sm_magic = SM_MAGIC_NULL;
	sm_free_size(smar_rcpts, sizeof(*smar_rcpts));
	return SM_SUCCESS;
}

/*
**  SMAR_RCPT_T2L_CB -- Callback for bht_walk in smar_rcpts_r2l()
**
**	Parameters:
**		entry -- SMAR RCPT context
**		ctx -- smar_rcpts context
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-22 04:52:02
**	Last code change:
*/

static sm_ret_T
smar_rcpt_t2l_cb(bht_entry_P entry, void *ctx)
{
	smar_rcpts_P smar_rcpts;
	smar_rcpt_P smar_rcpt;

	smar_rcpt = (smar_rcpt_P) entry->bhe_value;
	smar_rcpts = (smar_rcpts_P) ctx;
	SM_IS_SMAR_RCPTS(smar_rcpts);
	SM_IS_SMAR_RCPT(smar_rcpt);

SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_t2l_cb, smar_rcpt=%p, pa=%S, flags=%#x\n", smar_rcpt, smar_rcpt->arr_pa, smar_rcpt->arr_flags));
	if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISOWN) ||
	    SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISVERP))
	{
		OWNER_APP(smar_rcpts, smar_rcpt);
		++smar_rcpts->arrs_owners_n;
		SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_INHT);
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INOWNLST);
	}
	else if (!SMARR_IS_FLAG(smar_rcpt, SMARR_FL_EXPD))
	{
		RCPTS_APP(smar_rcpts, smar_rcpt);
		++smar_rcpts->arrs_lst_n;
		SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_INHT);
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INRLST);
	}
	else
		smar_rcpt_free(smar_rcpt, smar_rcpts);
	return SM_SUCCESS;
}

/*
**  SMAR_RCPTS_T2L -- Convert a SMAR RCPT table into a list
**
**	Parameters:
**		smar_rcpts -- SMAR RCPT LIST context
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-24 18:18:32; see comments!
**	Last code change:
*/

sm_ret_T
smar_rcpts_t2l(smar_rcpts_P smar_rcpts)
{
	SM_IS_SMAR_RCPTS(smar_rcpts);
	bht_walk(smar_rcpts->arrs_rcpts, smar_rcpt_t2l_cb, smar_rcpts);

	/*
	**  Content is now in list, destroy table without free()ing elements.
	**  XXX why not use
	bht_destroy(smar_rcpts->arrs_rcpts, smar_rcpt_t2l_cb, smar_rcpts);
	**  and get rid of bht_walk()?
	*/

	bht_destroy(smar_rcpts->arrs_rcpts, NULL, NULL);
	smar_rcpts->arrs_rcpts = NULL;
	return SM_SUCCESS;
}

/*
**  SMAR_RCPTS_ADD2HT -- Add a rcpt to a SMAR rcpt hash table
**
**	Parameters:
**		smar_rcpt -- SMAR RCPT context
**		smar_rcpts -- SMAR RCPT LIST context
**
**	Returns:
**		usual sm_error code,
**		sm_error_perm(SM_EM_AR, EEXIST) if entry exists
**
**	Last code review: 2004-03-22 04:54:51; see comments!
**	Last code change:
*/

static sm_ret_T
smar_rcpts_add2ht(smar_rcpts_P smar_rcpts, smar_rcpt_P smar_rcpt)
{
	sm_ret_T ret;
	void *ptr;

	SM_IS_SMAR_RCPT(smar_rcpt);
	SM_IS_SMAR_RCPTS(smar_rcpts);

	/*
	**  XXX Need "address equal" function:
	**  domain part: case insensitive
	**  local part: depends on configuration
	**  The entries in bht must be in "canonical" format...
	**  Note: if that is not the case, then it is not possible to use
	**  a hash table (well, because it's hashed; unless some
	**  "canonificaton" before hashing is done, e.g., lower case the key).
	*/

	ptr = bht_find(smar_rcpts->arrs_rcpts,
			(const char *) sm_str_data(smar_rcpt->arr_pa),
			sm_str_getlen(smar_rcpt->arr_pa));
	if (NULL == ptr) {
		bht_entry_P entry;

		ret = bht_add(smar_rcpts->arrs_rcpts,
			(char *) sm_str_data(smar_rcpt->arr_pa),
			sm_str_getlen(smar_rcpt->arr_pa),
			smar_rcpt, &entry);
		if (SM_SUCCESS == ret) {
			++smar_rcpts->arrs_ht_n;
			smar_rcpt->arr_rcpts = smar_rcpts;
			SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpts_add2ht, smar_rcpt=%p, pa=%S, stat=inht\n", smar_rcpt, smar_rcpt->arr_pa));
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INHT);
		}
		/* else error code ret is returned to caller */
	}
	else
		ret = sm_error_perm(SM_EM_AR, EEXIST);
	return ret;
}

/*
**  SMAR_RCPT_PARTS -- parse recipient parts (user/detail)
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_rcpt -- SMAR RCPT context
**		prcpt -- parsed recipient
**
**	Returns:
**		usual sm_error code,
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
smar_rcpt_parts(smar_ctx_P smar_ctx, smar_rcpt_P smar_rcpt, sm_a2821_P prcpt)
{
	sm_ret_T ret;

	smar_rcpt->arr_user_pa = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == smar_rcpt->arr_user_pa) goto enomem;
	smar_rcpt->arr_detail_pa = sm_str_new(NULL, MAXADDRLEN >> 1, MAXADDRLEN);
	if (NULL == smar_rcpt->arr_detail_pa) goto enomem;

	/* extract localpart */
	ret = t2821_parts(prcpt, smar_ctx->smar_cnf.smar_cnf_addr_delim,
			true, smar_rcpt->arr_user_pa, smar_rcpt->arr_detail_pa, NULL,
			&smar_rcpt->arr_delim);
	if (sm_is_err(ret)) {
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_parse, t2821_parts=%r\n", ret));
		goto error;
	}
	if (ret > 0)
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_HASDET);

	return ret;

  enomem:
	ret = sm_error_temp(SM_EM_AR, ENOMEM);
  error:
	SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=ERROR, func=smar_rcpt_parse, smar_rcpt=%p, flags=%#x\n", smar_rcpt, smar_rcpt == NULL ? 0xffffffff : smar_rcpt->arr_flags));
	return ret;
}

/*
**  SMAR_RCPT_EXPAND -- Expand RCPT address (add to hash table in smar_rcpts)
**
**	Parameters:
**		smar_rcpts -- SMAR RCPT LIST context
**		smar_rcpt -- rcpt routing information
**		owner_idx -- reference to owner (0: none)
**		rflags -- flags
**		level -- recursion level
**
**	Returns:
**		usual sm_error code
**
**	Side Effects:
**		creates and populates smar_rcpt->arr_domain_pa,
**			smar_rcpt_rslv() relies on this.
**		...
**
**	Note: the alias format is very restricted: it must be

local-part1: <full1@address1> <full2@address2> ...
local-part2: other-local-part

**	i.e., delimiters are only whitespace, not comma.
**
**	This function is called recursively.
**
**	The entries are collected in a hash table, see smar_rcpts_t2l()
**	how to get a list of ("valid") entries.
**
**	Last code review: 2004-03-22 05:21:23; see comments!
**	Last code change:
*/

#define SM_MAP_ENTRY_NOTFOUND(ret)	\
	(sm_error_perm(SM_EM_MAP, SM_E_NOTFOUND) == (ret) ||	\
	 sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL) == (ret) ||	\
	 sm_error_perm(SM_EM_MAP, ENOENT) == (ret) ||		\
	 sm_error_perm(SM_EM_MAP, SM_E_UNAVAIL) == (ret) ||	\
	 sm_error_perm(SM_EM_MAP, SM_E_NOMAP) == (ret))

sm_ret_T
smar_rcpt_expand(smar_rcpts_P smar_rcpts, smar_rcpt_P smar_rcpt, rcpt_idx_T owner_idx, uint rflags, uint level)
{
	sm_ret_T ret, hasdetail, offset;
	uint idx, flags;
	uint32_t lfl;
	char *ipv4s;
	sm_a2821_T a_rcpt, a_domain;
	sm_a2821_P prcpt, pdomain;
	sm_str_P mtstr, str, rhs, owner, ownerrhs;
	smar_rcpt_P smar_rcpt_a;
	smar_ctx_P smar_ctx;
	bool islocaldomain;

/* address is identical to one already in hash table */
#define SRE_FL_IDENTICAL	0x01
#define SRE_FL_CHK_LU		0x02	/* check local user */
#define SRE_FL_USE_MAP		0x04	/* full match using a map */

	SM_IS_SMAR_RCPT(smar_rcpt);
	SM_IS_SMAR_RCPTS(smar_rcpts);
	ret = SM_SUCCESS;
	mtstr = str = rhs = owner = ownerrhs = NULL;
	smar_ctx = smar_rcpts->arrs_smar_ctx;
	pdomain = &a_domain;	/* pdomain is just pointer to a_domain */
	prcpt = &a_rcpt;	/* prcpt is just pointer to a_rcpt */
	A2821_INIT_RP(prcpt, NULL);
	A2821_INIT_RP(pdomain, NULL);
	smar_rcpt_a = NULL;
	islocaldomain = false;
	flags = 0;
	hasdetail = 0;

	SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt=%p, level=%u\n", smar_rcpt, level));
	if (level >= MAX_ALIAS_RECURSION) {
		ret = sm_error_perm(SM_EM_AR, SM_E_ALIAS_REC);
		goto error;
	}

	/* check recipient address */
	ret = t2821_scan((sm_rdstr_P) smar_rcpt->arr_pa, prcpt, 0);
	if (sm_is_err(ret)) goto error;
	ret = t2821_parse(prcpt, R2821_CORRECT);
	if (sm_is_err(ret)) goto error;

	if (!SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOADD)) {
		ret = smar_rcpts_add2ht(smar_rcpts, smar_rcpt);
		if (ret == sm_error_perm(SM_EM_AR, EEXIST)) {
			if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_TAKEIT)) {
				smar_rcpt_free(smar_rcpt, smar_rcpts);
				smar_rcpt = NULL;
			}
			a2821_free(pdomain);
			a2821_free(prcpt);
			return SM_SUCCESS;
		}
		if (sm_is_err(ret)) goto error;
		SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_TAKEIT);
	}

	/* assign next index */
	smar_rcpt->arr_idx = ++smar_rcpts->arrs_idx;
	if (owner_idx > 0)
		smar_rcpt->arr_owner_idx = owner_idx;
	if (SM_IS_FLAG(rflags, SMARR_FL_ISVERP)) {
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_HASVERP);
		rflags &= ~SMARR_FL_ISVERP;
	}

	SM_ASSERT(NULL == smar_rcpt->arr_domain_pa);
	smar_rcpt->arr_domain_pa = sm_str_new(NULL, MAXDOMAINLEN, MAXDOMAINLEN + 2);
	if (NULL == smar_rcpt->arr_domain_pa) goto enomem;

	/* extract domain out of rcpt_a ... and look it up */
	ret = t2821_extract_domain(NULL, prcpt, &pdomain);
	if (sm_is_err(ret)) goto error;
	ret = t2821_str(pdomain, smar_rcpt->arr_domain_pa, 0);
	if (sm_is_err(ret)) goto error;
	a2821_free(pdomain);
	pdomain = NULL;
	sm_str2lower(smar_rcpt->arr_domain_pa);

	/* don't expand aliases? (just parse first recipient etc) */
	if (!SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_ALIAS))
		goto done;

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

	if (SMAR_MT_FULL_LOOKUP(smar_ctx) ||
	    SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF))
	{
		hasdetail = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt);
		if (sm_is_err(hasdetail)) {
			SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt_parts=%r\n", hasdetail));
			goto error;
		}
	}

	if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF) &&
		smar_ctx->smar_access != NULL)
	{
		lfl = smar_rcpt->arr_cnf_lfl;
		if (hasdetail <= 0)
			lfl &= ~SMMAP_LFL_DET;

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

		/* slight abuse of mtstr... */
		sm_str_scat(mtstr, SC_RCPT_CONF_TAG);
		smar_rcpt->arr_maprescnf = sm_map_lookup_addr(smar_ctx->smar_access,
				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 */ mtstr,
				smar_rcpt->arr_delim,
				lfl, smar_rcpt->arr_rhs_conf);
SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, pa=%S, lfl=%#x, conf=%#T, ret=%r\n", smar_rcpt->arr_pa, lfl, smar_rcpt->arr_rhs_conf, smar_rcpt->arr_maprescnf));
		sm_str_clr(mtstr);
	}

	if (SMAR_MT_FULL_LOOKUP(smar_ctx)) {
		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(smar_ctx->smar_mt_map, 0, smar_rcpt->arr_domain_pa,
		                    mtstr);
	if (SM_SUCCESS == ret)
		ipv4s = (char *) sm_str_getdata(mtstr);
	else {
		ret = SM_SUCCESS;
		ipv4s = NULL;
	}

	SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, pa=%S, domain=%S, ipv4s=%.256s, rq_flags=%#x, flags=%#x, ali_flags=%#x\n", smar_rcpt->arr_pa, smar_rcpt->arr_domain_pa, ipv4s, smar_rcpt->arr_rqflags, smar_rcpt->arr_flags, smar_ctx->smar_cnf.smar_cnf_alias_fl));

	/* no entry found but domain is a literal? */
	if (ipv4s == NULL && sm_str_rd_elem(smar_rcpt->arr_domain_pa, 0) == '[') {
		/* use it... XXX what about free()ing data? */
		ipv4s = (char *) sm_str_getdata(smar_rcpt->arr_domain_pa);
	}

	/* XXX HACK ahead, see also smtpc/smtpc.c */
	islocaldomain = ipv4s != NULL &&
			(strcmp(ipv4s, LMTP_IPV4_S2) == 0 || strcmp(ipv4s, LMTP_MT) == 0);

	/*
	**  do alias expansion if
	**  asking for (local) alias and local address
	**  or
	**  asking for remote alias
	**  <=>
	**  do not perform alias expansion if
	**  !(asking for (local) alias and local address)
	**  and
	**  !(asking for remote alias)
	*/

	if (!(SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_LP|SMARCNF_FL_MAP_LD)
	      && islocaldomain)
	    && !SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_ALL))
	{
		if (islocaldomain && SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CHK_LU))
			SM_SET_FLAG(flags, SRE_FL_CHK_LU);
		else
			goto done;
	}

	if (NULL == smar_rcpt->arr_user_pa)
	{
		hasdetail = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt);
		if (sm_is_err(hasdetail)) {
			SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt_parts=%r\n", hasdetail));
			goto error;
		}
	}

	str = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == str) goto enomem;
	rhs = sm_str_new(NULL, MAXADDRLEN, MAXALIASLEN);
	if (NULL == rhs) goto enomem;
	if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_OWNER)) {
		owner = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN);
		if (NULL == owner) goto enomem;
		ownerrhs = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN);
		if (NULL == ownerrhs) goto enomem;
	}

	/*
	**  local address but no alias expansion?
	**  then check whether user exists.
	*/

	if (SM_IS_FLAG(flags, SRE_FL_CHK_LU)) {
		uint32_t status;

		status = SMTP_R_OK;
		ret = smar_addr_lu(smar_ctx, smar_rcpt->arr_user_pa,
				smar_rcpt->arr_delim,
				(hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL,
				smar_rcpt->arr_domain_pa, rhs, &status);
		SM_CLR_FLAG(flags, SRE_FL_CHK_LU);

		/* how to reject an address? */
		if (status == SMTP_R_TEMP || status == SMTP_R_REJECT)
			smar_rcpt->arr_ret = status;

		goto done;
	}

	lfl = smar_ctx->smar_alias_lfl;
	if (hasdetail <= 0)
		lfl &= ~SMMAP_LFL_DET;
	ret = sm_map_lookup_addr(smar_ctx->smar_aliases, smar_rcpt->arr_user_pa,
		(hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL,
		SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl,
			SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL)
			? smar_rcpt->arr_domain_pa : NULL,
		/* tag */ NULL,
		smar_rcpt->arr_delim,
		lfl, rhs);
	SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, local=%S, domain=%S, rhs=%.256S, len=%u, lfl=%#x, ret=%r\n", smar_rcpt->arr_user_pa, smar_rcpt->arr_domain_pa, rhs, sm_str_getlen(rhs), lfl, ret));

	/* not found? XXX need to deal with tempfail etc! */
	if (sm_is_err(ret)) {
		if (!SM_MAP_ENTRY_NOTFOUND(ret)) goto error;

		if (islocaldomain && SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CHK_LU)) {
			lfl = smar_ctx->smar_lum_lfl;
			if (hasdetail == 0)
				lfl &= ~SMMAP_LFL_DET;
			ret = sm_map_lookup_addr(smar_ctx->smar_lum,
				smar_rcpt->arr_user_pa,
				(hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL,
				(smar_rcpt->arr_domain_pa == NULL ||
				 sm_str_getlen(smar_rcpt->arr_domain_pa) == 0)
					? NULL
					: smar_rcpt->arr_domain_pa,
				/* tag */ NULL,
				smar_rcpt->arr_delim,
				lfl, rhs);

SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "func=smar_rcpt_expand, local=%S, detail=%S, rhs=%.256S, local_map_lookup=%r\n", smar_rcpt->arr_user_pa, smar_rcpt->arr_detail_pa, rhs, ret));
			if (SM_MAP_ENTRY_NOTFOUND(ret))
				smar_rcpt->arr_ret = SMTP_R_REJECT;
			else if (sm_is_err(ret)) goto error;
		}
		goto done;
	}

	SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISALIAS);

#define ADDR_IS_LOCAL	"local:"

	/* don't try further if rhs is ADDR_IS_LOCAL */
	if (sm_str_getlen(rhs) == sizeof(ADDR_IS_LOCAL) - 1
	    && strncasecmp((const char*) sm_str_data(rhs),
			ADDR_IS_LOCAL, sm_str_getlen(rhs)) == 0)
	{
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_LU);
		goto done;
	}

	/* check for error: (even before testing "NOEXP"?) */
	if (sm_str_getlen(rhs) >= RHS_ERROR_LEN
	    && strncasecmp((const char*) sm_str_data(rhs),
			RHS_ERROR, RHS_ERROR_LEN) == 0)
	{
		/* don't accept mail for this address */
		if (sm_str_getlen(rhs) >= RHS_ERROR_4_LEN
		    && strncasecmp((const char*) sm_str_data(rhs),
			RHS_ERROR_4, RHS_ERROR_4_LEN) == 0)
		{
			ret = SMTP_R_TEMP;
		}
		else
			ret = SMTP_R_REJECT;
		goto error;
	}

	/* don't rewrite address if no expand flag is set */
	if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_NOEXP))
		goto done;

#define SM_A_OWNER "owner-"
	/* check for owner- */
	if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_OWNER)) {
		sm_str_clr(owner);
		ret = sm_str_scat(owner, SM_A_OWNER);
		if (sm_is_err(ret)) goto error;
		ret = sm_str_cat(owner, smar_rcpt->arr_user_pa);
		if (sm_is_err(ret)) goto error;

		sm_str_clr(ownerrhs);
		ret = sm_map_lookup_addr(smar_ctx->smar_aliases, owner,
			(hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL,
			SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl,
				SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL)
				? smar_rcpt->arr_domain_pa : NULL,
			/* tag */ NULL,
			smar_rcpt->arr_delim,
			lfl & ~SMMAP_LFL_DOMAIN, ownerrhs);
			/* must match owner-, not just @domain */

		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, owner=%S, domain=%S, ownerrhs=%.256S, ret=%r\n", owner, smar_rcpt->arr_domain_pa, ownerrhs, ret));

		/* XXX need to deal with tempfail etc */
		if (sm_is_success(ret) &&
		    !(sm_str_getlen(ownerrhs) >= RHS_ERROR_LEN
		      && strncasecmp((const char*) sm_str_data(ownerrhs),
					RHS_ERROR, RHS_ERROR_LEN) == 0))
		{
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISOWN);
			owner_idx = smar_rcpt->arr_idx;
			smar_rcpt->arr_owner_pa = sm_str_new(NULL, MAXADDRLEN,
							MAXADDRLEN);
			if (NULL == smar_rcpt->arr_owner_pa) goto enomem; /* XXX cleanup? */
			ret = sm_strprintf(smar_rcpt->arr_owner_pa,
					"<%s%S@%S>",
					SM_A_OWNER, smar_rcpt->arr_user_pa,
					smar_rcpt->arr_domain_pa);
			if (sm_is_err(ret) || ret >= sm_str_getmax(smar_rcpt->arr_owner_pa))
				goto error;	/* XXX cleanup? */
		}
	}

#define SM_A_VERP "verp-"
	/* check for verp- (almost identical to owner- check) */
	if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_VERP) &&
	    !SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISOWN))
	{
		sm_str_clr(owner);
		ret = sm_str_scat(owner, SM_A_VERP);
		if (sm_is_err(ret)) goto error;
		ret = sm_str_cat(owner, smar_rcpt->arr_user_pa);
		if (sm_is_err(ret)) goto error;

		sm_str_clr(ownerrhs);
		ret = sm_map_lookup_addr(smar_ctx->smar_aliases, owner,
			(hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL,
			SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl,
				SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL)
				? smar_rcpt->arr_domain_pa : NULL,
			/* tag */ NULL,
			smar_rcpt->arr_delim,
			lfl & ~SMMAP_LFL_DOMAIN, ownerrhs);
			/* must match owner-, not just @domain */

		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, verp=%S, domain=%S, verprhs=%.256S, ret=%r\n", owner, smar_rcpt->arr_domain_pa, ownerrhs, ret));

		/* XXX need to deal with tempfail etc */
		if (sm_is_success(ret) &&
		    !(sm_str_getlen(ownerrhs) >= RHS_ERROR_LEN
		      && strncasecmp((const char*) sm_str_data(ownerrhs),
					RHS_ERROR, RHS_ERROR_LEN) == 0))
		{
			SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISVERP);
			rflags |= SMARR_FL_ISVERP;
			owner_idx = smar_rcpt->arr_idx;
			smar_rcpt->arr_owner_pa = sm_str_new(NULL, MAXADDRLEN,
							MAXADDRLEN);
			if (NULL == smar_rcpt->arr_owner_pa) goto enomem; /* XXX cleanup? */
			ret = sm_strprintf(smar_rcpt->arr_owner_pa,
					"<%s%S@%S>",
					SM_A_OWNER, smar_rcpt->arr_user_pa,
					smar_rcpt->arr_domain_pa);
			if (sm_is_err(ret) || ret >= sm_str_getmax(smar_rcpt->arr_owner_pa))
				goto error;	/* XXX cleanup? */
		}
	}

	/* rewrite address... */

	/*
	**  RHS must be an RFC2821 address or just a localpart.
	*/

	idx = 0;
	SM_CLR_FLAG(flags, SRE_FL_IDENTICAL);
	do {
		/* skip over leading spaces */
		while (sm_str_getlen(rhs) > idx && ISSPACE(sm_str_rd_elem(rhs, idx)))
			++idx;

		/* no more data? */
		if (sm_str_getlen(rhs) <= idx)
			break;

		/*
		**  Convert localpart to <localpart@MY.HOST.NAME>
		**  (or use the domain of the original address)
		**  Note: This is broken for
		**  alias: local <other@address>
		**  A real address parser should be used which supports
		**  unqualified addresses!
		*/

		if (sm_str_getlen(rhs) > idx
		    && sm_str_rd_elem(rhs, idx) != (uchar) '<')
		{
			sm_str_P exchg;
			bool preserve_domain;

			preserve_domain = SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl,
			                             SMARCNF_FL_MAP_PD) &&
				sm_str_getlen(smar_rcpt->arr_domain_pa) > 0;
			sm_str_clr(str);
			if (sm_str_put(str, (uchar) '<') != SM_SUCCESS
			    || sm_str_cat(str, rhs) != SM_SUCCESS
			    || sm_str_put(str, (uchar) '@') != SM_SUCCESS
			    || sm_str_cat(str, preserve_domain ? smar_rcpt->arr_domain_pa
			                       : smar_ctx->smar_hostname) != SM_SUCCESS
			    || sm_str_put(str, (uchar) '>') != SM_SUCCESS)
			{
				SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, convert_localpart=%m\n", ret));
				goto error;
			}

			/* exchange rhs and str */
			exchg = rhs;
			rhs = str;
			str = exchg;
		}

		a2821_free(prcpt);
		A2821_INIT_RP(prcpt, NULL);
		offset = t2821_scan((sm_rdstr_P) rhs, prcpt, idx);
		if (sm_is_err(offset)) {
			ret = offset;
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 3,
				"sev=ERROR, func=smar_rcpt_expand, alias=%.256S, t2821_scan=%m"
				, rhs, ret);
			goto error;
		}
		if (sm_is_err(ret = t2821_parse(prcpt, R2821_CORRECT))) {
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 3,
				"sev=ERROR, func=smar_rcpt_expand, alias=%.256S, t2821_parse=%m"
				, rhs, ret);
			goto error;
		}

		SM_ASSERT(offset >= 0);
		idx = offset;	/* set new index to end of current address */

		if (smar_rcpts->arrs_addr != NULL &&
		    SMARA_IS_LFLAG(smar_rcpts->arrs_addr, SMARA_LFL_PROTMAP)
				? T2821_FL_NOANGLE : 0)
			SM_SET_FLAG(flags, SRE_FL_USE_MAP);
		sm_str_clr(str);
		ret = t2821_str(prcpt, str, SM_IS_FLAG(flags, SRE_FL_USE_MAP) ? T2821_FL_NOANGLE : 0);
		if (sm_is_err(ret)) {
			SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, t2821_str=%r\n", ret));
			goto error;
		}
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, new=%.256S, len=%d, old=%.256S, len=%d, flags=%#x\n", str, sm_str_getlen(str), smar_rcpt->arr_pa, sm_str_getlen(smar_rcpt->arr_pa), flags));

		/*
		**  Compare new address with old address?
		**  Stop if they are identical (instead of
		**  relying on recursion limit).
		**  XXX Need "address equal" function:
		**  domain part: case insensitive
		**  local part: depends on configuration
		*/

		if (sm_str_getlen(smar_rcpt->arr_pa) == sm_str_getlen(str) &&
		    strncmp((char *) sm_str_data(smar_rcpt->arr_pa),
		            (char *) sm_str_data(str), sm_str_getlen(str)) == 0)
		{
			SM_SET_FLAG(flags, SRE_FL_IDENTICAL);
			continue;
		}

		if (SM_IS_FLAG(flags, SRE_FL_USE_MAP)) {
			smar_addr_P smar_addr;

			smar_addr = smar_rcpts->arrs_addr;
			sm_str_clr(smar_addr->ara_rhs2);
			ret = sm_map_setopt(smar_addr->ara_str_map, SMPO_STR, str, SMPO_END);
			if (sm_is_err(ret)) goto error;
			ret = sm_map_lookup_addr(smar_addr->ara_str_map,
				smar_addr->ara_user2, smar_addr->ara_detail2,
				smar_addr->ara_domain_pa2, NULL,
				smar_addr->ara_delim,

				/* Need to let caller (client) pass in other flags? */
				SMMAP_LFL_ALL|SMMAP_LFL_NOAT|
				(SMARA_IS_LFLAG(smar_addr, SMARA_LFL_PROTIMPLDET)
					? SMMAP_LFL_IMPLDET : 0),
				smar_addr->ara_rhs2);
SMAR_LEV_DPRINTF(9, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=lookup, user2=%S, ret=%r\n", smar_addr->ara_user2, ret));
			if (sm_is_success(ret)) {
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_FOUND);
				goto done;
			}
			ret = SM_SUCCESS;
			continue;
		}
		if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_COMP)) {
SMAR_LEV_DPRINTF(9, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=compare, pa=%#S, str=%#S\n", smar_rcpts->arrs_pa, str));
			if (sm_str_getlen(smar_rcpts->arrs_pa) ==
				sm_str_getlen(str) &&
			    strncasecmp((char *) sm_str_data(smar_rcpts->arrs_pa),
			                (char *) sm_str_data(str), sm_str_getlen(str)) == 0)
			{
SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=compare, pa=%#S, str=%#S, status=found\n", smar_rcpts->arrs_pa, str));
				SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_FOUND);
				goto done;
			}
			ret = SM_SUCCESS;
			continue;
		}

		if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOREC))
			continue;

		ret = smar_rcpt_new(&smar_rcpt_a);
		if (sm_is_err(ret)) goto error; /* XXX more cleanup? */
		SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=smar_rcpt_new, smar_rcpt_a=%p\n", smar_rcpt_a));

		/* XXX fill in data... */
/*
XXX some of this data should only be in rcpts, not in each rcpt
that requires to rewrite the functions that access that data via rcpt
*/

		smar_rcpt_a->arr_pa = str;
		str = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2);
		if (NULL == str) goto enomem;
		smar_rcpt_a->arr_timeout = smar_rcpt->arr_timeout;
		smar_rcpt_a->arr_rqflags = smar_rcpt->arr_rqflags;
		RCPT_ID_COPY(smar_rcpt_a->arr_id, smar_rcpt->arr_id);
		SMARR_SET_FLAG(smar_rcpt_a, SMARR_FL_TAKEIT);

		ret = smar_rcpt_expand(smar_rcpts, smar_rcpt_a, owner_idx, rflags, level + 1);
		smar_rcpt_a = NULL;
		if (sm_is_err(ret)) goto error;

	} while (sm_is_success(ret) && sm_str_getlen(rhs) > idx);

	/*
	**  The recipient has only been replaced if there wasn't an identical
	**  address on the RHS.
	*/

	if (!SM_IS_FLAG(flags, SRE_FL_IDENTICAL))
		SMARR_SET_FLAG(smar_rcpt, SMARR_FL_EXPD);

  done:
	/*
	 *  Hack: try to figure out whether smar_rcpt_parts() should be called.
	 *  Note: prcpt can change but by then smar_rcpt_parts() was called and
	 *  hence arr_user_pa is set.
	 */

	if (SMAR_MT_FULL_LOOKUP(smar_ctx) && NULL == smar_rcpt->arr_user_pa) {
		ret = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt);
		if (sm_is_err(ret)) goto error;
	}
	SM_STR_FREE(str);
	SM_STR_FREE(rhs);
	SM_STR_FREE(mtstr);
	SM_STR_FREE(owner);
	SM_STR_FREE(ownerrhs);
	a2821_free(prcpt);
	prcpt = NULL;
	return SM_SUCCESS;

  enomem:
	ret = sm_error_temp(SM_EM_AR, ENOMEM);
  error:
	SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=ERROR, func=smar_rcpt_expand, smar_rcpt=%p, flags=%#x\n", smar_rcpt, smar_rcpt == NULL ? 0xffffffff : smar_rcpt->arr_flags));
	SM_STR_FREE(str);
	SM_STR_FREE(rhs);
	SM_STR_FREE(mtstr);
	SM_STR_FREE(owner);
	SM_STR_FREE(ownerrhs);
	a2821_free(pdomain);
	pdomain = NULL;
	a2821_free(prcpt);
	prcpt = NULL;
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_ERROR, 0,
		"sev=ERROR, func=smar_rcpt_expand, rcpt_pa=%.256S, ret=%m"
		, smar_rcpt == NULL ? 0 : smar_rcpt->arr_pa, ret);
	if (smar_rcpt_a != NULL) {
		smar_rcpt_free(smar_rcpt_a, smar_rcpts);
		smar_rcpt_a = NULL;
	}
	if (smar_rcpt != NULL && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_TAKEIT)) {
		smar_rcpt_free(smar_rcpt, smar_rcpts);
		smar_rcpt = NULL;
	}
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1