/*
 * 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: lookupaddr.c,v 1.28 2007/01/22 17:24:06 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/str.h"
#include "sm/strexp.h"
#include "map.h"
#include "sm/map.h"
#include "sm/maps.h"
#include "sm/mapc.h"

/*
**  SM_MAP_REPLDIG -- Replace %n pattern in rhs (wrapper for sm_str_expdig())
**
**	Parameters:
**		str -- string for intermediate result
**		rhs -- input/output string
**		argc -- number of valid elements in argv (0 - 10)
**		argv -- array of strings to use for expansion
**
**	Returns:
**		>=0: number of replacements
**		<0: usual sm_error code
*/

static sm_ret_T
sm_map_repldig(sm_str_P str, sm_str_P rhs, uint argc, sm_str_P *argv)
{
	sm_ret_T ret;

	sm_str_clr(str);
	ret = sm_str_expdig(rhs, str, '\0', argc, argv);
	if (sm_is_success(ret)) {
		sm_str_clr(rhs);
		sm_str_cat(rhs, str);
	}
	return ret;
}

/*
**  SM_MAP_EXPMAC -- Expand macro in dst
**	(callback for sm_str_expmac_cb())
**
**	Parameters:
**		src -- input string (containing macro)
**		len -- len of src
**		dst -- string to be expanded (output)
**		mac_begin -- begin of macro in src
**		mac_end -- end of macro in src
**		ctx -- context
**
**	Returns:
**		>=0: number of replacements
**		<0: usual sm_error code
*/

struct addr_macros_S
{
	const char	*am_macro;
	uint		 am_repl;
};

typedef struct addr_macros_S addr_macros_T, *addr_macros_P;

#define ADDR_MAC_NONE	0
#define ADDR_MAC_USER	1
#define ADDR_MAC_DETAIL	2
#define ADDR_MAC_DOMAIN	3
#define ADDR_MAC_TAG	4
#define ADDR_MAC_DELIM	5
#define ADDR_MAC_SUBDOM	6
#define ADDR_MAC_EXT	7

static addr_macros_T
addr_macros[] =
{
	{ "user",	ADDR_MAC_USER	},
	{ "detail",	ADDR_MAC_DETAIL	},
	{ "domain",	ADDR_MAC_DOMAIN	},
	{ "tag",	ADDR_MAC_TAG	},
	{ "delimiter",	ADDR_MAC_DELIM	},
	{ "subdomain",	ADDR_MAC_SUBDOM	},
	{ "extension",	ADDR_MAC_EXT	},
	{ NULL,		ADDR_MAC_NONE	}
};

struct macexp_ctx_S
{
	sm_str_P	macexp_user;
	sm_str_P	macexp_detail;
	sm_str_P	macexp_domain;
	sm_str_P	macexp_tag;
	uchar		macexp_delim;
	uint		macexp_subdom;
};
typedef struct macexp_ctx_S macexp_ctx_T, *macexp_ctx_P;

static sm_ret_T
sm_mac_expmac(const sm_str_P src, uint len, sm_str_P dst, uint mac_begin, uint mac_end, void *ctx)
{
	sm_ret_T ret;
	uint u;
	size_t l;
	sm_str_P repl;
	macexp_ctx_P macexp_ctx;

	if (mac_begin >= mac_end || mac_begin >= len || mac_end > len)
		return sm_error_perm(SM_EM_MAP, SM_E_UNEXPECTED);
	SM_REQUIRE(src != NULL);
	SM_REQUIRE(dst != NULL);
	SM_REQUIRE(ctx != NULL);
	macexp_ctx = (macexp_ctx_P) ctx;
	l = mac_end - mac_begin + 1;
	for (u = 0; addr_macros[u].am_macro != NULL; u++) {
		if (strncmp(addr_macros[u].am_macro,
			(const char *)sm_str_data(src) + mac_begin, l) == 0)
		{
			repl = NULL;
			switch (addr_macros[u].am_repl) {
			  case ADDR_MAC_USER:
				repl = macexp_ctx->macexp_user;
				break;
			  case ADDR_MAC_DETAIL:
				repl = macexp_ctx->macexp_detail;
				break;
			  case ADDR_MAC_DOMAIN:
				repl = macexp_ctx->macexp_domain;
				break;
			  case ADDR_MAC_TAG:
				repl = macexp_ctx->macexp_tag;
				break;
			  case ADDR_MAC_DELIM:
				ret = sm_str_put(dst, macexp_ctx->macexp_delim);
				if (sm_is_err(ret))
					return ret;
				return 1;
				break;
			  case ADDR_MAC_SUBDOM:
				repl = macexp_ctx->macexp_domain;
				if (NULL == repl)
					break;
				else
				{
					sm_str_T subdom;

					sm_str_assign(subdom, NULL, sm_str_data(repl),
						macexp_ctx->macexp_subdom, sm_str_getlen(repl));
					ret = sm_str_cat(dst, &subdom);
					if (sm_is_err(ret))
						return ret;
					return 1;
				}
				break;
			  case ADDR_MAC_EXT:
				repl = macexp_ctx->macexp_detail;
				if (NULL == repl)
					break;
				ret = sm_str_put(dst, macexp_ctx->macexp_delim);
				if (sm_is_err(ret))
					return ret;
				repl = macexp_ctx->macexp_detail;
				break;
			  default:
				break;
			}
			if (NULL == repl)
				return 0;
			ret = sm_str_cat(dst, repl);
			if (sm_is_err(ret))
				return ret;
			return 1;
		}
	}
	return SM_SUCCESS;
	/* return sm_error_perm(SM_EM_MAP, SM_E_NOTFOUND); */
}

/*
**  SM_MAP_REPLMAC -- Replace ${macro} pattern in rhs
**	(wrapper for sm_str_expmac_cb())
**
**	Parameters:
**		str -- string for intermediate result
**		rhs -- input/output string
**		macexp_ctx -- macro expansion context
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_map_replmac(sm_str_P str, sm_str_P rhs, macexp_ctx_P macexp_ctx)
{
	sm_ret_T ret;

	sm_str_clr(str);
	ret = sm_str_expmac_cb(rhs, str, 0, sm_mac_expmac, macexp_ctx);
	if (ret > 0) {
		sm_str_clr(rhs);
		sm_str_cat(rhs, str);
	}
	return ret;
}

/*
**  Add flags (also for lookup_ip):
**  lookup also .domain
**  lookup only Tag:
**  others?
*/

/*
**  SM_MAP_LOOKUP_ADDR -- Lookup RFC 2821 address
**
**	Parameters:
**		map -- map
**		user -- user part of address
**		detail -- detail part of address
**			must be NULL iff there is no "delim" in the address
**		domain -- domain part of address
**		tag -- tag including delimiter (NULL for no tag)
**		delim -- delimiter
**		flags -- flags to control lookup
**		rhs -- rhs of matching entry (output)
**
**	Returns:
**		<0: usual sm_error code
**		>=0: number of macro replacements
**	should it also return which case matched?
**
**	Note: the lookup cases are controlled by flags and whether domain or
**		detail are NULL. That is, there is no flag to say to lookup an
**		address with or without domain, that is implicit by passing
**		a domain part (or NULL).
*/

#define UCH_AT		((uchar) '@')
#define UCH_STAR	((uchar) '*')
#define UCH_PLUS	((uchar) '+')
#define SM_MAX_REPL	4

sm_ret_T
sm_map_lookup_addr(sm_map_P map, sm_str_P user, sm_str_P detail, sm_str_P domain, sm_str_P tag, uchar delim, uint32_t flags, sm_str_P rhs)
{
	sm_ret_T ret;
	uint len, len_domain, j;
	uint32_t mapflags;
	sm_mapc_P mapc;
	sm_str_P str, delimstr, subdom;
	sm_str_P argv[SM_MAX_REPL];
	macexp_ctx_T macexp_ctx;

	if (NULL == map)
		return sm_error_perm(SM_EM_MAP, SM_E_NOMAP);
	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	if (NULL == mapc->sm_mapc_lookupf)
		return sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL);
	delimstr = NULL;
	subdom = NULL;

	len = 2;
	if (user != NULL)
		len += sm_str_getlen(user);
	if (detail != NULL)
		len += sm_str_getlen(detail) + 1;
	if (domain != NULL) {
		len_domain = sm_str_getlen(domain);
		len += len_domain + 1;
		--len_domain;
	}
	else
		len_domain = 0;
	if (tag != NULL)
		len += sm_str_getlen(tag);

	j = sm_str_getmax(rhs);
	str = sm_str_new(NULL, len,
			SMMAP_IS_LFL(flags, SMMAP_LFL_REPL|SMMAP_LFL_MCR_REPL)
				? j :  len + 2);
	if (NULL == str)
		return sm_error_perm(SM_EM_MAP, ENOMEM);

	if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
		delimstr = sm_str_scpyn(NULL, " ", 2, 4);
		if (NULL == delimstr) {
			ret = sm_error_perm(SM_EM_MAP, ENOMEM);
			goto error;
		}
		ret = sm_str_wr_elem(delimstr, 0, delim);
		if (len_domain > 0) {
			subdom = sm_str_new(NULL, len_domain, len_domain + 2);
			if (NULL == subdom) {
				ret = sm_error_perm(SM_EM_MAP, ENOMEM);
				goto error;
			}
		}
	}

	if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
		macexp_ctx.macexp_user = user;
		macexp_ctx.macexp_detail = detail;
		macexp_ctx.macexp_domain = domain;
		macexp_ctx.macexp_tag = tag;
		macexp_ctx.macexp_delim = delim;
	}

	ret = sm_error_perm(SM_EM_MAP, SM_E_NOTFOUND);

/*
**  Macros to put '@' into the right place (after localpart or before domain)
**  depending on the flag SMMAP_LFL_NOAT.
*/

/* put an '@' as separator after localpart */
#define SM_AT_LOCAL	\
	(SMMAP_IS_LFL(flags, SMMAP_LFL_NOAT)	\
		? false : sm_is_err(sm_str_put(str, UCH_AT)))

/* put an '@' as separator before domain */
#define SM_AT_DOM	\
	(SMMAP_IS_LFL(flags, SMMAP_LFL_NOAT)	\
		? sm_is_err(sm_str_put(str, UCH_AT)) : false)

	j = 0;
	do {
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL))
			macexp_ctx.macexp_subdom = j;
		mapflags = SMMAP_FL_LWR_KEY
			| (tag != NULL ? SMMAP_FL_HAS_TAG : 0)
			| (user != NULL ? SMMAP_FL_HAS_LOCALPART : 0)
			| ((detail != NULL && sm_str_getlen(detail) > 0)
				? SMMAP_FL_HAS_DETAIL : 0)
			| (domain != NULL ? SMMAP_FL_HAS_DOMAIN : 0)
			;

		/* complete lookup (tag user delim detail @ domain) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_FULL)
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_cat(str, detail))))
			   || SM_AT_LOCAL
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
						len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL) &&
				    j > 0 && subdom != NULL)
				{
					sm_str_clr(subdom);
					ret = sm_str_catpart(subdom, (sm_rdstr_P) domain, 0, j);
					if (sm_is_err(ret))
						goto error;
					argv[0] = subdom;
					ret = sm_map_repldig(str, rhs, 1, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					macexp_ctx.macexp_subdom = j;
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/* lookup with ++ (tag user delim + @ domain) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_DETPLUS)
		    && detail != NULL && sm_str_getlen(detail) > 0
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_put(str, UCH_PLUS))))
			   || SM_AT_LOCAL
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
					len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = detail;
					ret = sm_map_repldig(str, rhs, 1, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/* lookup with +* (tag user delim * @ domain) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_DETSTAR)
		    && detail != NULL
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_put(str, UCH_STAR))))
			   || SM_AT_LOCAL
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
						len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = detail;
					ret = sm_map_repldig(str, rhs, 1, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/* lookup with * (tag user * @ domain) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_STAR)
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || sm_is_err(sm_str_put(str, UCH_STAR))
			   || SM_AT_LOCAL
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
						len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = delimstr;
					argv[1] = detail;
					ret = sm_map_repldig(str, rhs, 2, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/*
		**  lookup without detail (tag user @ domain).
		**  This does not match if +detail exists (i.e., if delim
		**  is in the address) unless SMMAP_LFL_IMPLDET is set.
		*/

		if (SMMAP_IS_LFL(flags, SMMAP_LFL_USER)
		    && SMMAP_LT_M_CAPS(map, mapflags)
		    && (NULL == detail || SMMAP_IS_LFL(flags, SMMAP_LFL_IMPLDET)))
		{
			mapflags = SMMAP_FL_LWR_KEY
				| (tag != NULL ? SMMAP_FL_HAS_TAG : 0)
				| (user != NULL ? SMMAP_FL_HAS_LOCALPART : 0)
				| ((detail != NULL && sm_str_getlen(detail) > 0)
					? SMMAP_FL_HAS_DETAIL : 0)
				| (domain != NULL ? SMMAP_FL_HAS_DOMAIN : 0)
				;
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || SM_AT_LOCAL
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
						len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = delimstr;
					argv[1] = detail;
					ret = sm_map_repldig(str, rhs, NULL == detail ? 1 : 2, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

/*
**  XXX This might not be the best order!
**	user@sub.domain - sub.domain - user@.domain - .domain
**  Wouldn't this be better:
**	user@sub.domain - user@.domain - sub.domain - .domain
*/
		mapflags = SMMAP_FL_LWR_KEY
			| (tag != NULL ? SMMAP_FL_HAS_TAG : 0)
			| (domain != NULL ? SMMAP_FL_HAS_DOMAIN : 0)
			;

		/* lookup just domain (@ domain) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_DOMAIN)
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (domain != NULL &&
				(SM_AT_DOM ||
				 sm_is_err(sm_str_catpart(str, (sm_rdstr_P) domain, j,
						len_domain))))
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, SMMAP_FL_LWR_KEY, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = user;
					argv[1] = detail;
					ret = sm_map_repldig(str, rhs, 2, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		if (domain != NULL && SMMAP_IS_LFL(flags, SMMAP_LFL_DOTSUBDOM)) {
			++j;

			/* search for next '.' */
			while (j < len_domain
			       && sm_str_rd_elem(domain, j) != '.')
				++j;
		}

	} while (domain != NULL && SMMAP_IS_LFL(flags, SMMAP_LFL_DOTSUBDOM)
		 && j < len_domain);

	/*
	**  This needs only to be done if domain is not NULL otherwise
	**  it's the same as above!
	*/

	if (domain != NULL && SMMAP_IS_LFL(flags, SMMAP_LFL_LOCAL)) {
		mapflags = SMMAP_FL_LWR_KEY
			| (tag != NULL ? SMMAP_FL_HAS_TAG : 0)
			| (user != NULL ? SMMAP_FL_HAS_LOCALPART : 0)
			| ((detail != NULL && sm_str_getlen(detail) > 0)
				? SMMAP_FL_HAS_DETAIL : 0)
			;

		/* complete lookup (tag user delim detail) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_FULL)
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_cat(str, detail))))
			   || SM_AT_LOCAL
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret)
				goto done;
		}

		/* lookup with ++ (tag user delim +)*/
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_DETPLUS)
		    && detail != NULL && sm_str_getlen(detail) > 0
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_put(str, UCH_PLUS))))
			   || SM_AT_LOCAL
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = detail;
					ret = sm_map_repldig(str, rhs, 1, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/* lookup with +* (tag user delim *) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_DETSTAR) && detail != NULL
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || (detail != NULL && (sm_is_err(sm_str_put(str, delim))
					|| sm_is_err(sm_str_put(str, UCH_STAR))))
			   || SM_AT_LOCAL
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = detail;
					ret = sm_map_repldig(str, rhs, 1, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}

		/* lookup with * (tag user *) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_STAR)
		    && SMMAP_LT_M_CAPS(map, mapflags))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || sm_is_err(sm_str_put(str, UCH_STAR))
			   || SM_AT_LOCAL
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = delimstr;
					argv[1] = detail;
					ret = sm_map_repldig(str, rhs, 2, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}


		mapflags = SMMAP_FL_LWR_KEY
			| (tag != NULL ? SMMAP_FL_HAS_TAG : 0)
			| (user != NULL ? SMMAP_FL_HAS_LOCALPART : 0)
			;

		/* lookup without detail (tag user) */
		if (SMMAP_IS_LFL(flags, SMMAP_LFL_USER)
		    && SMMAP_LT_M_CAPS(map, mapflags)
		    && (NULL == detail || SMMAP_IS_LFL(flags, SMMAP_LFL_IMPLDET)))
		{
			sm_str_clr(str);
			if (
			      (tag != NULL && sm_is_err(sm_str_cat(str, tag)))
			   || (user != NULL && sm_is_err(sm_str_cat(str, user)))
			   || SM_AT_LOCAL
			   )
			{
				ret = sm_error_perm(SM_EM_MAP, EINVAL);	/* XXX */
				goto error;
			}
			ret = mapc->sm_mapc_lookupf(map, mapflags, str, rhs);
			if (SM_MAP_DATA2BIG == ret)
				goto done;
			if (SM_SUCCESS == ret) {
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_REPL)) {
					argv[0] = user;
					argv[1] = detail;
					ret = sm_map_repldig(str, rhs, 2, argv);
					if (sm_is_err(ret))
						goto error;
				}
				if (SMMAP_IS_LFL(flags, SMMAP_LFL_MCR_REPL)) {
					ret = sm_map_replmac(str, rhs, &macexp_ctx);
					if (sm_is_err(ret))
						goto error;
				}
				goto done;
			}
		}
	}

  done:
	SM_STR_FREE(delimstr);
	SM_STR_FREE(subdom);
	sm_str_free(str);
	return ret;

  error:
	SM_STR_FREE(delimstr);
	SM_STR_FREE(subdom);
	sm_str_free(str);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1