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

/*
**  Implementation of "protected recipients":
**  access map:
**  protectedrpct:address	list of restrictions
**  address is stored in ara_pa and its components are in the various
**  ara_ fields (ara_detail, ara_domain_pa)
**  envelope sender is stored in ara_pa2
**  client ip address is stored in ara_ipv4
**  the restrictions are in ara_rhs
*/

/*
**  SMAR_PROT_LIST -- Check "List:" entry for "protected rcpt"
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_addr -- address context
**		offset -- begin of List: address (without List:)
**			(output: end of address)
**
**	Returns:
**		usual sm_error code
**		if ok: smar_addr->ara_status should have result of lookup(s)
**
**	Locking: smar_addr must be under control of the caller.
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
smar_prot_list(smar_ctx_P smar_ctx, smar_addr_P smar_addr, uint *poffset)
{
	sm_ret_T ret;
	int offset;
	smar_rcpts_P smar_rcpts;
	smar_rcpt_P smar_rcpt;
	sm_a2821_T a_rcpt;
	sm_a2821_P prcpt;

	SM_REQUIRE(poffset != NULL);
	prcpt = &a_rcpt;	/* prcpt is just pointer to a_rcpt */
	A2821_INIT_RP(prcpt, NULL);
	smar_rcpt = NULL;
	smar_rcpts = NULL;

	/* how to find the end of the address? parse it... */

	a2821_free(prcpt);
	A2821_INIT_RP(prcpt, NULL);
	offset = t2821_scan((sm_rdstr_P) smar_addr->ara_rhs, prcpt, *poffset);
	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_prot_list, rhs=%.256S, t2821_scan=%m"
			, smar_addr->ara_rhs, ret);
		goto error;
	}
	ret = t2821_parse(prcpt, R2821_CORRECT);
	if (sm_is_err(ret)) {
		sm_log_write(smar_ctx->smar_lctx,
			AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
			SM_LOG_ERROR, 3,
			"sev=ERROR, func=smar_prot_list, rhs=%.256S, t2821_parse=%m"
			, smar_addr->ara_rhs, ret);
		goto error;
	}

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

	if (NULL == smar_addr->ara_pa2) {
		if (!SMAR_IS_FLAG(smar_ctx, SMAR_FL_PROTMAILCOMPL)) {
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=smar_prot_list, status=got list: restriction but allow_by=sender is not set");

			/* NOT locked! */
			SMAR_SET_FLAG(smar_ctx, SMAR_FL_PROTMAILCOMPL);
		}
		goto done;
	}
	if (SMARA_IS_FLAG(smar_addr, SMARA_FL_PA2EMPTY))
		goto done;

	sm_str_clr(smar_addr->ara_str);
	ret = t2821_str(prcpt, smar_addr->ara_str, 0);
	if (sm_is_err(ret)) {
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_prot_list, t2821_str=%m\n", ret));
		goto error;
	}

	/*
	**  Create these if necessary.
	**  Note: it's not necessary to create all the data, e.g., bht.
	*/

	smar_rcpts = smar_addr->ara_rcpts;
	smar_rcpt = smar_addr->ara_rcpt;
	if (NULL == smar_rcpts) {
		ret = smar_rcpts_new(smar_ctx, smar_addr->ara_smar_clt_ctx,
					&smar_addr->ara_rcpts);
		if (sm_is_err(ret))
			goto error;
		smar_rcpts = smar_addr->ara_rcpts;
	}
	if (NULL == smar_rcpt) {
		ret = smar_rcpt_new(&smar_addr->ara_rcpt);
		if (sm_is_err(ret))
			goto error;
		smar_rcpt = smar_addr->ara_rcpt;
	}
	SM_IS_SMAR_RCPT(smar_rcpt);
	SM_IS_SMAR_RCPTS(smar_rcpts);
	smar_rcpts->arrs_flags = SMARRS_FL_NOREC|SMARRS_FL_NOADD|SMARRS_FL_COMP;
	smar_rcpts->arrs_pa = smar_addr->ara_pa2;
	smar_rcpts->arrs_addr = smar_addr;
	smar_rcpt->arr_pa = smar_addr->ara_pa;
	SMARRQ_SET_FLAG(smar_rcpt, SMARRQ_FL_ALIAS);
	ret = smar_rcpt_expand(smar_addr->ara_rcpts, smar_addr->ara_rcpt, 0, 0, 0);
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_INFO, 13,
		"sev=INFO, func=smar_prot_list, smar_rcpt_expand=%m, found=%d"
		, ret, SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_FOUND));
	if (sm_is_success(ret) && SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_FOUND)) {
		smar_addr->ara_status = SMTP_R_OK;
		smar_addr->ara_mapres = SM_ACC_FOUND;
	}

	/*
	**  clean out smar_addr->ara_rcpts and smar_addr->ara_rcpt ???
	**  for now just free them, reuse later on?
	**  check whether requirements for function are fulfilled!
	**  e.g, SMARR_FL_ORCPT
	*/

  done:
	if (smar_rcpts != NULL && smar_rcpts->arrs_pa != NULL)
		smar_rcpts->arrs_pa = NULL;
	if (smar_rcpt != NULL && smar_rcpt->arr_pa != NULL)
		smar_rcpt->arr_pa = NULL;
	(void) smar_rcpt_free(smar_addr->ara_rcpt, smar_addr->ara_rcpts);
	(void) smar_rcpts_free(smar_addr->ara_rcpts);
	smar_addr->ara_rcpts = NULL;
	smar_addr->ara_rcpt = NULL;
	a2821_free(prcpt);
	return ret;

  error:
	/* identical to "done:", merge those two? */
	if (smar_rcpts != NULL && smar_rcpts->arrs_pa != NULL)
		smar_rcpts->arrs_pa = NULL;
	if (smar_rcpt != NULL && smar_rcpt->arr_pa != NULL)
		smar_rcpt->arr_pa = NULL;
	(void) smar_rcpt_free(smar_addr->ara_rcpt, smar_addr->ara_rcpts);
	(void) smar_rcpts_free(smar_addr->ara_rcpts);
	smar_addr->ara_rcpts = NULL;
	smar_addr->ara_rcpt = NULL;
	a2821_free(prcpt);
	return ret;
}

/*
**  SMAR_PROT_FROM -- Check "from:" entry for "protected rcpt"
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_addr -- address context
**		offset -- begin of From: address (without From:)
**			(output: end of address)
**
**	Returns:
**		usual sm_error code
**		if ok: smar_addr->ara_status should have result of lookup(s)
**
**	Locking: smar_addr must be under control of the caller.
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
smar_prot_from(smar_ctx_P smar_ctx, smar_addr_P smar_addr, uint *poffset)
{
	sm_ret_T ret;
	int offset;
	sm_a2821_T a_rcpt;
	sm_a2821_P prcpt;

	SM_REQUIRE(poffset != NULL);
	prcpt = &a_rcpt;	/* prcpt is just pointer to a_rcpt */
	A2821_INIT_RP(prcpt, NULL);

	/* how to find the end of the address? parse it... */

	a2821_free(prcpt);
	A2821_INIT_RP(prcpt, NULL);
	offset = t2821_scan((sm_rdstr_P) smar_addr->ara_rhs, prcpt, *poffset);
	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_prot_from, rhs=%.256S, t2821_scan=%m"
			, smar_addr->ara_rhs, ret);
		goto error;
	}
	ret = t2821_parse(prcpt, R2821_CORRECT|R2821_EMPTY);
	if (sm_is_err(ret)) {
		sm_log_write(smar_ctx->smar_lctx,
			AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
			SM_LOG_ERROR, 3,
			"sev=ERROR, func=smar_prot_list, rhs=%.256S, t2821_parse=%m"
			, smar_addr->ara_rhs, ret);
		goto error;
	}

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

	if (NULL == smar_addr->ara_pa2) {
		if (!SMAR_IS_FLAG(smar_ctx, SMAR_FL_PROTMAILCOMPL)) {
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=smar_prot_from, status=got from: restriction but allow_by=sender is not set");

			/* NOT locked! */
			SMAR_SET_FLAG(smar_ctx, SMAR_FL_PROTMAILCOMPL);
		}
		goto done;
	}

	sm_str_clr(smar_addr->ara_str);
	ret = t2821_str(prcpt, smar_addr->ara_str,
			(SMARA_IS_LFLAG(smar_addr, SMARA_LFL_PROTMAP) &&
			 !SMARA_IS_FLAG(smar_addr, SMARA_FL_PA2EMPTY))
				? T2821_FL_NOANGLE : 0);
	if (sm_is_err(ret)) {
		SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_prot_list, t2821_str=%m\n", ret));
		goto error;
	}

	/*
	**  ara_str: parsed from:<address> (from rhs)
	**  need to parse ara_pa2 too (should be stored in smar_addr!)
	*/

	if (SMARA_IS_LFLAG(smar_addr, SMARA_LFL_PROTMAP) &&
	    !SMARA_IS_FLAG(smar_addr, SMARA_FL_PA2EMPTY))
	{
		sm_str_clr(smar_addr->ara_rhs2);
		ret = sm_map_setopt(smar_addr->ara_str_map,
			SMPO_STR, smar_addr->ara_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);
		if (sm_is_success(ret)) {
			smar_addr->ara_status = SMTP_R_OK;
			smar_addr->ara_mapres = SM_ACC_FOUND;
		}
		else
			ret = SM_SUCCESS;
	}
	else if (sm_str_getlen(smar_addr->ara_pa2) ==
			sm_str_getlen(smar_addr->ara_str) &&
		 strncmp((char *) sm_str_data(smar_addr->ara_pa2),
			(char *) sm_str_data(smar_addr->ara_str),
			sm_str_getlen(smar_addr->ara_str)) == 0)
	{
		smar_addr->ara_status = SMTP_R_OK;
		smar_addr->ara_mapres = SM_ACC_FOUND;
	}
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_INFO, 13,
		"sev=INFO, func=smar_prot_from, status=%r"
		, smar_addr->ara_status);

  done:
	a2821_free(prcpt);
	return ret;

  error:
	a2821_free(prcpt);
	return ret;
}

/*
**  SMAR_PROT_CLTADDR -- Check "cltaddr:" entry for "protected rcpt"
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_addr -- address context
**		offset -- begin of cltaddr: address (without cltaddr:)
**			(output: end of address)
**
**	Returns:
**		usual sm_error code
**		if ok: smar_addr->ara_status should have result of lookup(s)
**
**	Locking: smar_addr must be under control of the caller.
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
smar_prot_cltaddr(smar_ctx_P smar_ctx, smar_addr_P smar_addr, uint *poffset)
{
	sm_ret_T ret;
	uint l, offset;
	uchar uc;
	sm_str_P ipv4str;

	SM_REQUIRE(poffset != NULL);
	ret = SM_SUCCESS;
	ipv4str = NULL;
	offset = *poffset;
	l = sm_str_getlen(smar_addr->ara_rhs);
	sm_str_clr(smar_addr->ara_str);
	while (offset < l && sm_is_success(ret))
	{
		uc = sm_str_rd_elem(smar_addr->ara_rhs, offset);
		if (!(ISDIGIT(uc) || uc == (uchar)'.'))
			break;
		ret = sm_str_put(smar_addr->ara_str, uc);
		++offset;
	}
	if (offset > l || sm_is_err(ret))
		goto error;
	ret = sm_str_term(smar_addr->ara_str);
	if (sm_is_err(ret))
		goto error;

	/* SM_ASSERT(offset >= 0); */
	*poffset = offset; /* set new index to end of current address */
	if (!SMARA_IS_FLAG(smar_addr, SMARA_FL_RCVDIPV4)) {
		if (!SMAR_IS_FLAG(smar_ctx, SMAR_FL_PROTIPCOMPL)) {
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=smar_prot_cltaddr, status=got cltaddr: restriction but allow_by=client_ip is not set");

			/* NOT locked! */
			SMAR_SET_FLAG(smar_ctx, SMAR_FL_PROTIPCOMPL);
		}
		return SM_SUCCESS;
	}

	if (NULL == smar_addr->ara_ipv4_str) {
		ipv4str = sm_str_new(NULL, 18, 20);
		smar_addr->ara_ipv4_str = ipv4str;
	}
	else
		ipv4str = smar_addr->ara_ipv4_str;
	sm_str_clr(ipv4str);
	ret = sm_inet_ipv4str(smar_addr->ara_ipv4, ipv4str);
	if (sm_is_err(ret))
		goto error;

	/*
	**  ara_str: parsed cltaddr:IP.ADD.RE.SS (from rhs)
	*/

	if (SMARA_IS_LFLAG(smar_addr, SMARA_LFL_PROTMAP)) {
		sm_str_clr(smar_addr->ara_rhs2);
		ret = sm_map_setopt(smar_addr->ara_str_map,
			SMPO_STR, smar_addr->ara_str, SMPO_END);
		if (sm_is_err(ret))
			goto error;
		ret = sm_map_lookup_ip(smar_addr->ara_str_map,
		                 ipv4str, NULL, SMMAP_LFL_SUBNETS, smar_addr->ara_rhs2);
		if (sm_is_success(ret)) {
			smar_addr->ara_status = SMTP_R_OK;
			smar_addr->ara_mapres = SM_ACC_FOUND;
		}
		else
			ret = SM_SUCCESS;
	}
	else if (sm_str_getlen(ipv4str) == sm_str_getlen(smar_addr->ara_str) &&
		 strncmp((char *) sm_str_data(ipv4str),
			(char *) sm_str_data(smar_addr->ara_str),
			sm_str_getlen(smar_addr->ara_str)) == 0)
	{
		/* scan the IPv4 address and just compare those?? */
		smar_addr->ara_status = SMTP_R_OK;
		smar_addr->ara_mapres = SM_ACC_FOUND;
	}
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_INFO, 13,
		"sev=INFO, func=smar_prot_cltaddr, ipv4=%#S, str=%#S, status=%r"
		, ipv4str, smar_addr->ara_str, smar_addr->ara_status);

	return ret;

  error:
	if (sm_is_success(ret))
		ret = sm_err_perm(EINVAL); /* XXX */
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_INFO, 13,
		"sev=INFO, func=smar_prot_cltaddr, ret=%m", ret);
	return ret;
}

/*
**  SMAR_PROT_RHS -- Go through elements in the RHS of a "protected rcpt"
**
**	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)
**
**	Locking: smar_addr must be under control of the caller.
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
smar_prot_rhs(smar_ctx_P smar_ctx, smar_addr_P smar_addr)
{
	sm_ret_T ret;
	uint rhslen, rhsoff;
	char *rhsc;

	rhsc = (char *) sm_str_getdata(smar_addr->ara_rhs);
	if (NULL == rhsc)
		return sm_error_temp(SM_EM_AR, ENOMEM);

	rhslen = sm_str_getlen(smar_addr->ara_rhs);
	rhsoff = 0;
	ret = SM_SUCCESS;
	smar_addr->ara_status = SMTP_R_CONT;
	while (rhsoff < rhslen && smar_addr->ara_status == SMTP_R_CONT
	       && sm_is_success(ret))
	{
		while (rhsoff < rhslen && ISSPACE(*(rhsc + rhsoff)))
			++rhsoff;
		if (rhsoff >= rhslen)
			break;
		if ((rhslen > RHS_LIST_TAG_LEN + rhsoff)
		    && strncasecmp(rhsc + rhsoff, RHS_LIST_TAG, RHS_LIST_TAG_LEN) == 0)
		{
			rhsoff += RHS_LIST_TAG_LEN;
			ret = smar_prot_list(smar_ctx, smar_addr, &rhsoff);
		}
		else if ((rhslen > MAIL_TAG_LEN + rhsoff)
		    && strncasecmp(rhsc + rhsoff, MAIL_TAG, MAIL_TAG_LEN) == 0)
		{
			rhsoff += MAIL_TAG_LEN;
			ret = smar_prot_from(smar_ctx, smar_addr, &rhsoff);
		}
		else if ((rhslen > CLT_A_TAG_LEN + rhsoff)
		    && strncasecmp(rhsc + rhsoff, CLT_A_TAG, CLT_A_TAG_LEN) == 0)
		{
			rhsoff += CLT_A_TAG_LEN;
			ret = smar_prot_cltaddr(smar_ctx, smar_addr, &rhsoff);
		}
		else
		{
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
				SM_LOG_ERROR, 8,
				"sev=ERROR, func=smar_prot_rhs, rhs=%.256S, status=unknown_entry"
				, smar_addr->ara_rhs);
			ret = sm_err_perm(EINVAL);
			smar_addr->ara_status = SMTP_R_TEMP;
		}
	}

	/* default: reject */
	if (SMTP_R_CONT == smar_addr->ara_status) {
		/* set some status text to include ESC?? */
		/* 5.7.2 for mailing list, 5.7.1 for others */
		smar_addr->ara_status = SMTP_R_REJECT;
		smar_addr->ara_mapres = SM_ACC_FOUND;
	}
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1