/*
 * 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: addr.c,v 1.78 2007/11/14 06:03:08 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/io.h"
#include "sm/net.h"
#include "sm/rcb.h"
#include "sm/mta.h"
#include "sm/rfc2821.h"
#include "smar.h"
#include "log.h"
#include "sm/reccom.h"

/*
**  Note: it might be useful to keep a small "cache" of these struct's
**	around for reuse to avoid all those alloc()/free() calls.
*/

/*
**  SMAR_ADDR_NEW -- Create a SMAR ADDR context
**
**	Parameters:
**		smar_clt_ctx -- SMAR client context
**		psmar_addr -- (pointer to) SMAR ADDR context (output)
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2004-03-11 17:57:46
**	Last code change:
*/

sm_ret_T
smar_addr_new(smar_clt_ctx_P smar_clt_ctx, smar_addr_P *psmar_addr)
{
	smar_addr_P smar_addr;

	SM_REQUIRE(psmar_addr != NULL);
	smar_addr = (smar_addr_P) sm_zalloc(sizeof(*smar_addr));
	if (NULL == smar_addr)
		return sm_error_temp(SM_EM_AR, ENOMEM);
	smar_addr->ara_smar_clt_ctx = smar_clt_ctx;
	smar_addr->ara_rpool = sm_rpool_new(NULL);
	if (NULL == smar_addr->ara_rpool)
	{
		sm_free_size(smar_addr, sizeof(*smar_addr));
		return sm_error_temp(SM_EM_AR, ENOMEM);
	}
#if 0
	/* set larger poolsize? */
	sm_rpool_setsizes(smar_addr->ara_rpool, poolsize, 0, 0);
#endif
	smar_addr->sm_magic = SM_SMAR_ADDR_MAGIC;
	*psmar_addr = smar_addr;
	return SM_SUCCESS;
}

/*
**  SMAR_ADDR_FREE -- Free a SMAR ADDR context
**
**	Parameters:
**		smar_addr -- SMAR ADDR context
**
**	Returns:
**		usual sm_error code
**
**	Locking: none, smar_addr must be under control of caller
**
**	Last code review: 2004-03-11 18:00:01
**	Last code change: 2006-03-31 06:24:40
*/

sm_ret_T
smar_addr_free(smar_addr_P smar_addr)
{
	int r;

	if (NULL == smar_addr)
		return SM_SUCCESS;
	SM_IS_SMAR_ADDR(smar_addr);

	/* free other data... */
	SM_STR_FREE(smar_addr->ara_pa);
	SM_STR_FREE(smar_addr->ara_pa2);
	SM_STR_FREE(smar_addr->ara_str);
	SM_STR_FREE(smar_addr->ara_tag);
	SM_STR_FREE(smar_addr->ara_detail);
	SM_STR_FREE(smar_addr->ara_domain_pa);
	SM_STR_FREE(smar_addr->ara_rhs);
	SM_STR_FREE(smar_addr->ara_user2);
	SM_STR_FREE(smar_addr->ara_detail2);
	SM_STR_FREE(smar_addr->ara_domain_pa2);
	SM_STR_FREE(smar_addr->ara_rhs2);
	SM_STR_FREE(smar_addr->ara_rhs_conf);
	SM_STR_FREE(smar_addr->ara_ipv4_str);
	SM_CSTR_FREE(smar_addr->ara_rvrs_hostname);

	for (r = 0; r < SM_MAX_DNSBL; r++)
		SM_FREE(smar_addr->ara_dnsblres[r].sdbr_ipv4s);

	if (smar_addr->ara_rcbe != NULL) {
		sm_rcbe_free(smar_addr->ara_rcbe);
		smar_addr->ara_rcbe = NULL;
	}
	if (smar_addr->ara_str_map != NULL) {
		sm_map_close(smar_addr->ara_str_map, 0);
		smar_addr->ara_str_map = NULL;
	}
	if (SMARA_IS_FLAG(smar_addr, SMARA_FL_HASMUT)) {
		r = pthread_mutex_destroy(&smar_addr->ara_mutex);
		SM_ASSERT(0 == r);
		SMARA_CLR_FLAG(smar_addr, SMARA_FL_HASMUT);
	}
	if (SMARA_IS_FLAG(smar_addr, SMARA_FL_HASCOND)) {
		r = pthread_cond_destroy(&smar_addr->ara_cond);
		SM_ASSERT(0 == r);
		SMARA_CLR_FLAG(smar_addr, SMARA_FL_HASCOND);
	}

	/*
	**  Note: do not free
	**  smar_addr->ara_rcpt nor smar_addr->ara_rcpts
	**  here!
	*/

	sm_rpool_delete(smar_addr->ara_rpool);
	smar_addr->sm_magic = SM_MAGIC_NULL;
	sm_free_size(smar_addr, sizeof(*smar_addr));
	return SM_SUCCESS;
}

/*
**  SMAR_ADDR_RE -- Send reply for an address check
**
**	Parameters:
**		smar_addr -- address context
**		off -- possible offset in return string
**
**	Returns:
**		usual sm_error code
**
**	Locking: none, smar_addr must be under control of caller
**
**	Last code review: 2004-03-11 18:00:35
**	Last code change:
*/

sm_ret_T
smar_addr_re(smar_addr_P smar_addr, uint off)
{
	sm_rcb_P rcb;
	sm_ret_T ret;

	SM_IS_SMAR_ADDR(smar_addr);
	rcb = &smar_addr->ara_rcbe->rcbe_rcb;
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_A2S_ID, smar_addr->ara_id,
		SM_RCBV_BUF, RT_A2S_TAID, smar_addr->ara_taid, SMTP_STID_SIZE,

		/* dummy for consistency */
		SM_RCBV_INT, RT_A2S_MAP_RES, SM_ACC_FOUND,
		SM_RCBV_INT, RT_A2S_RCPT_ST, smar_addr->ara_status|smar_addr->ara_rflags,
		SM_RCBV_END);

	if (ret == SM_SUCCESS && smar_addr->ara_rhs != NULL
	    && sm_str_getlen(smar_addr->ara_rhs) > 3)
	{
		if (off > 0) {
			SM_ASSERT(sm_str_getlen(smar_addr->ara_rhs) > off);
			ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
				SM_RCBV_BUF, RT_A2S_STATT,
				sm_str_getdata(smar_addr->ara_rhs) + off,
				sm_str_getlen(smar_addr->ara_rhs) - off,
				SM_RCBV_END);
		}
		else {
			ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
				SM_RCBV_STR, RT_A2S_STATT, smar_addr->ara_rhs,
				SM_RCBV_END);
		}
	}
	return ret;
}

/*
**  SMAR_ADDR_CHK -- Check address
**	If an address maps (via mt) to LMTP (see hack), then check
**	whether the local part is in the "alias" or "local user" map.
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_addr -- address context
**
**	Returns:
**		usual sm_error code
**		if ok: smar_addr->ara_status should have result of lookup(s)
**
**	Called by:
**
**	Locking: smar_addr must be under control of the caller
**
**	Last code review: 2004-03-11 18:06:39; see comments
**	Last code change:
*/

sm_ret_T
smar_addr_chk(smar_ctx_P smar_ctx, smar_addr_P smar_addr)
{
	sm_ret_T ret;
	smar_clt_ctx_P smar_clt_ctx;
	char *ipv4s;
	int flags;
	bool islocal, hasdetail;
	uchar delim;
	sm_str_P str, rhs, detail, domain, mtstr;
	sm_a2821_T a_rcpt, a_domain, a_local;
	sm_a2821_P pdomain, plocal;

	SM_IS_SMAR_ADDR(smar_addr);
	SM_IS_SMAR_CTX(smar_ctx);
	smar_clt_ctx = smar_addr->ara_smar_clt_ctx;
	SM_IS_SMAR_CLT_CTX(smar_clt_ctx);
	ret = SM_SUCCESS;
	A2821_INIT_RP(&a_rcpt, smar_addr->ara_rpool);
	A2821_INIT_RP(&a_domain, smar_addr->ara_rpool);
	A2821_INIT_RP(&a_local, smar_addr->ara_rpool);
	pdomain = &a_domain;	/* pdomain is just pointer to a_domain! */
	plocal = &a_local;	/* plocal is just pointer to a_local! */
	smar_addr->ara_status = SMTP_R_REJECT;
	str = rhs = detail = domain = mtstr = NULL;
	islocal = false;
	hasdetail = false;

	ret = t2821_scan((sm_rdstr_P) smar_addr->ara_pa, &a_rcpt, 0);
	if (sm_is_err(ret)) goto done;
	flags = R2821_CORRECT & ~R2821_AT;
	ret = t2821_parse(&a_rcpt, flags);
	if (sm_is_err(ret)) goto done;

	/* fixme: should these be part of smar_addr? */
	str = sm_str_new(smar_addr->ara_rpool, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == str) goto done; /* temp fail? */
	domain = sm_str_new(smar_addr->ara_rpool, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == domain) goto done; /* temp fail? */
	detail = sm_str_new(smar_addr->ara_rpool, MAXADDRLEN >> 1, MAXADDRLEN);
	if (NULL == detail) goto done; /* temp fail? */
	rhs = sm_str_new(smar_addr->ara_rpool, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == rhs) goto done; /* temp fail? */

	/* fixme: use different length? */
	mtstr = sm_str_new(smar_addr->ara_rpool, MAXADDRLEN, MAXADDRLEN + 2);
	if (NULL == mtstr) goto done; /* temp fail? */

	hasdetail = t2821_parts(&a_rcpt, smar_ctx->smar_cnf.smar_cnf_addr_delim,
					true, str, detail, NULL, &delim);
	if (sm_is_err(hasdetail)) {
SMAR_LEV_DPRINTF(5, (SMAR_DEBFP, "func=smar_addr_chk, t2821_parts=%r\n", hasdetail));
		goto done;
	}

	/* extract domain out of rcpt_a ... and look it up */
	ret = t2821_extract_domain(smar_addr->ara_rpool, &a_rcpt, &pdomain);
	if (sm_is_err(ret)) {
		if (ret == sm_error_perm(SM_EM_ADDR, R2821_ERR_FQDN))
			islocal = true;
	}
	else {
		ret = t2821_str(pdomain, domain, 0);
		if (sm_is_err(ret)) goto done;
		sm_str2lower(domain);

		sm_str_clr(mtstr);
		if (SMAR_MT_FULL_LOOKUP(smar_ctx))
		{
			uint32_t lfl;

			lfl = smar_ctx->smar_mt_lfl;
			if (hasdetail <= 0)
				lfl &= ~SMMAP_LFL_DET;
			ret = sm_map_lookup_addr(smar_ctx->smar_mt_map, str,
				(hasdetail > 0) ? detail : NULL, domain, /* tag */ NULL,
				delim, lfl, mtstr);
		}
		else
			ret = sm_map_lookup(smar_ctx->smar_mt_map, 0, domain, mtstr);
		if (SM_SUCCESS == ret)
			ipv4s = (char *) sm_str_getdata(mtstr);
		else
			ipv4s = NULL;
SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "func=smar_addr_chk, domain=%S, ipv4s=%.256s\n", domain, ipv4s));

		/* HACK ahead, see also smtpc/smtpc.c */
		if (ipv4s == NULL ||
		    (strcmp(ipv4s, LMTP_IPV4_S2) != 0 && strcmp(ipv4s, LMTP_MT) != 0))
		{
			/* entry not found: "CONTINUE" further checks */
			smar_addr->ara_status = SMTP_R_CONT;
		}
		else
			islocal = true;
	}

	if (islocal) {
		/*
		**  Extract localpart and look it up to decide whether it
		**  should be accepted.
		*/


		/*
		**  Check localpart.
		**  another map is required, or some different format, e.g.,
		**  To:localpart@	OK
		**  should be used (which, however, causes problems if
		**  a different map like passwd is used).
		**  Specify some bdb map?
		**  We could simply use aliases for now:
		**  if it exists: ok, otherwise: reject
		**  aliases can then also be used for the next step.
		**  ToDo: Enhance dealing with temporary errors.
		*/

#if 0
		/* lower case local parts! */
		sm_str2lower(str);
		sm_str2lower(detail);
#endif
		ret = smar_addr_lu(smar_ctx, str, delim, hasdetail > 0 ? detail : NULL,
				domain, rhs, &smar_addr->ara_status);
		if (SM_IS_FLAG(smar_addr->ara_ltype, SMARA_LT_RCPT_LCL_R))
			SMAR_SET_RFL(smar_addr, SMAR_R_LOC_RCPT);
	}

  done:
SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "func=smar_addr_chk, where=reply, ret=%r, status=%d\n", ret, smar_addr->ara_status));
	a2821_free(&a_domain);
	a2821_free(&a_rcpt);
	SM_STR_FREE(str);
	SM_STR_FREE(domain);
	SM_STR_FREE(detail);
	SM_STR_FREE(rhs);
	SM_STR_FREE(mtstr);
	return SM_SUCCESS;
}

/*
**  SMAR_ADDR_CHECK -- Check address; send reply
**	If an address maps (via mt) to LMTP (see hack), then check
**	whether the local part is in the "alias" or "local user" map.
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_addr -- address context
**
**	Returns:
**		usual sm_error code
**
**	Called by: smar_react()
**
**	Locking: smar_addr has been created by smar_react(),
**		this is the only function that has been given access
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
smar_addr_check(smar_ctx_P smar_ctx, smar_addr_P smar_addr)
{
	sm_ret_T ret;
	smar_clt_ctx_P smar_clt_ctx;

	SM_IS_SMAR_ADDR(smar_addr);
	SM_IS_SMAR_CTX(smar_ctx);
	smar_clt_ctx = smar_addr->ara_smar_clt_ctx;
	SM_IS_SMAR_CLT_CTX(smar_clt_ctx);

	ret = smar_addr_chk(smar_ctx, smar_addr);
	if (sm_is_err(ret)) goto error;

	if (smar_addr->ara_status == SMTP_R_CONT &&
	    SM_IS_FLAG(smar_addr->ara_ltype, SMARA_LT_RCPT_ACC|SMARA_LT_RCPT_PROT))
	{
		/* NOTE: This does double work, e.g., scanning, parsing */
		ret = smar_access_chk(smar_ctx, smar_addr);
		if (sm_is_err(ret)) goto error;
	}

SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "func=smar_addr_check, where=reply, ret=%r, status=%d, ltype=%#x\n", ret, smar_addr->ara_status, smar_addr->ara_ltype));
	ret = smar_addr_re(smar_addr, smar_addr->ara_rhstagoff);
	ret = sm_rcbcom_endrep(&smar_clt_ctx->smac_com_ctx,
			smar_clt_ctx->smac_com_ctx.rcbcom_tsk,
			false, &smar_addr->ara_rcbe);
	if (sm_is_err(ret)) goto error;
	smar_addr_free(smar_addr);
	return ret;

  error:
	smar_addr_free(smar_addr);
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_ERROR, 0,
		"sev=ERROR, func=smar_addr_check, ret=%m", ret);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1