/*
 * 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.
 */

/*
**  Send address (printable recipient address) to SMAR to check whether
**  recipient is acceptable, i.e., if "local" then it must be in aliases.
**  Used by t-archk-0.sh
*/

#include "sm/generic.h"
SM_RCSID("@(#)$Id: t-smar-2.c,v 1.29 2006/04/02 06:34:20 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/rcb.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "sm/dns.h"
#include "sm/smar.h"
#include "sm/reccom.h"
#include "sm/unixsock.h"
#include "sm/test.h"

/* context for requests */
struct t_req_S
{
	char		**rq_rcpt_pa;
	int		  rq_count;
	uint32_t	  rq_lt;
	uint32_t	  rq_flags;
	rcbcom_ctx_P	  rq_qar_com;
	sm_evthr_task_P	  rq_wr_tsk;
};

#define SMAXLEN	256

typedef struct t_req_S	t_req_T, *t_req_P;

static pthread_mutex_t mutex;
static pthread_cond_t cond;

static int Verbose = 0;
static uint MaxCount = (DNS_TIMEOUT + 1) * 10;
static int Requests = 0;
static int aqt_rcpts_ar = 0;
static sm_evthr_ctx_P evthr_ctx;
static rcbcom_ctx_T rcb_com;

static sm_ret_T ar2qmgr(sm_evthr_task_P _tsk);
static sm_ret_T qmgr2ar(sm_evthr_task_P _tsk);

/*
**  FCTS -- "sleep" function: this function terminates the evthr system
**	either after a certain number of iterations or if there are no
**	more outstanding requests.
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
fcts(sm_evthr_task_P tsk)
{
	static uint count = 0;
	struct timeval sleept, delay;

	++count;
	if (Verbose > 3 && (count % 10 == 0))
		fprintf(stderr, "fcts: count=%u/%u, ", count, MaxCount);

	/* Theoretically Requests needs to be protected by a mutex... */
	if (Requests <= 0 || count > MaxCount)
		return EVTHR_DEL|EVTHR_TERM;
	sm_memzero(&sleept, sizeof(sleept));
	gettimeofday(&sleept, NULL);
	delay.tv_sec = 1;
	delay.tv_usec = 0;
	timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
	return EVTHR_SLPQ;
}

/*
**  QMGR_AR -- test program to AR interface
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qmgr_ar(sm_evthr_task_P tsk)
{
	sm_ret_T ret;

	SM_IS_EVTHR_TSK(tsk);
	if (is_valid_fd(tsk->evthr_t_fd))
	{
		ret = EVTHR_WAITQ;
		if (evthr_got_wr(tsk))
		{
			if (Verbose > 3)
				fprintf(stderr, "write\n");
			ret = qmgr2ar(tsk);	/* XXX check ret here? */
			if (sm_is_err(ret) && Verbose > 0)
				fprintf(stderr, "qmgr2ar=%x\n", ret);
		}
		if (evthr_got_rd(tsk))
		{
			if (Verbose > 3)
				fprintf(stderr, "read\n");
			ret = ar2qmgr(tsk);
		}
		if (sm_is_err(ret))
		{
			if (Verbose > 1)
				fprintf(stderr, "qmgr_ar=%x\n", ret);
			return ret;
		}
		return ret;
	}
	return EVTHR_DEL;
}

/*
**  QR2AR -- Send recipient data to AR, i.e., put it into RCB
**
**	Parameters:
**		rcpt_pa -- printable address of recipient
**		rcbe -- RCB entry
**		flags -- flags
**		lt -- lookup type
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qr2ar(sm_str_P rcpt_pa, sm_rcbe_P rcbe, uint32_t flags, uint32_t lt)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	sessta_id_T ta_id;

	ret = SM_SUCCESS;
	rcb = &rcbe->rcbe_rcb;

	sm_snprintf(ta_id, sizeof(ta_id), SMTPS_STID_FORMAT, (ulonglong_T)0, 0);

	/* XXX send rcpt_idx and ta_id separately? */
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_S2A_ID, 1,
		SM_RCBV_BUF, RT_S2A_TAID, ta_id, SMTP_STID_SIZE,
		SM_RCBV_STR, RT_S2A_RCPT, rcpt_pa,
		SM_RCBV_INT, RT_S2A_RCPT_IDX, 1,
		SM_RCBV_INT2, RT_S2A_LTYPE, lt, flags,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	if (Verbose > 4)
		fprintf(stderr,
			"qr2ar, ret=%d, rw=%d, size=%d, len=%d, max=%d\n"
			, ret
			, rcb->sm_rcb_rw
			, (int) rcb->sm_rcb_size
			, (int) rcb->sm_rcb_len
			, (int) rcb->sm_rcb_max
			);
	return ret;

  error:
	/* XXX leave rcb in a consistent state? */
	return ret;
}

/*
**  RCPT2AR -- send a recipient to SMAR
**
**	Parameters:
**		qar_tsk -- evthr task
**		rcpt_pa -- printable address of recipient
**		qar_com -- RCBCOM context
**		flags -- flags
**		lt -- lookup type
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
rcpt2ar(sm_evthr_task_P qar_tsk, sm_str_P rcpt_pa, rcbcom_ctx_P qar_com, uint32_t flags, uint32_t lt)
{
	sm_ret_T ret;
	sm_rcbe_P rcbe;

	ret = sm_rcbe_new_enc(&rcbe, -1, 0);
	if (sm_is_err(ret))
		goto error;
	ret = qr2ar(rcpt_pa, rcbe, flags, lt);
	if (sm_is_err(ret))
		goto error;
	ret = sm_rcbcom_endrep(qar_com, qar_tsk, true, &rcbe);
	if (sm_is_err(ret))
		goto error;
	++aqt_rcpts_ar;
	return SM_SUCCESS;

  error:
	if (rcbe != NULL)
		sm_rcbe_free(rcbe);
	return ret;
}

/*
**  QMGR2AR -- Test program to AR interface
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qmgr2ar(sm_evthr_task_P tsk)
{
	sm_ret_T ret;
	rcbcom_ctx_P qar_com;
	t_req_P t_req;

	SM_IS_EVTHR_TSK(tsk);
	t_req = (t_req_P) tsk->evthr_t_actx;
	qar_com = t_req->rq_qar_com;
	ret = sm_rcbcom2mod(tsk, qar_com);
	if (Verbose > 4)
		fprintf(stderr, "qmgr2ar, ret=%d\n", ret);
	return ret;
}

/*
**  REACT -- Decode data received from AR and act accordingly
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
react(sm_evthr_task_P tsk)
{
	uint32_t v, l, rt, tl;
	bool needeol;
	sm_ret_T ret;
	sm_rcb_P rcb;
	sessta_id_T ta_id;
	t_req_P t_req;
	rcbcom_ctx_P qar_com;

	/* decode rcb */
	needeol = false;
	t_req = (t_req_P) tsk->evthr_t_actx;
	qar_com = t_req->rq_qar_com;
	rcb = qar_com->rcbcom_rdrcb;
	ret = sm_rcb_open_dec(rcb);
	if (sm_is_err(ret))
		goto error;

	/* total length of record */
	ret = sm_rcb_getuint32(rcb, &tl);
	if (sm_is_err(ret) || tl > QM_AR_MAX_REC_LEN ||
	    tl > sm_rcb_getlen(rcb))
		goto err2;

	/* protocol header: version */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret))
		goto err2;
	if (l != 4 || rt != RT_PROT_VER || v != PROT_VER_RT)
		goto err2;

	/* XXX define protocol first in smX docs! */

/*
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,
SM_RCBV_INT, RT_A2S_RCPT_ST, smar_addr->ara_status,
SM_RCBV_STR, RT_A2S_STATT, smar_addr->ara_rhs,
*/

	/* XXX decode data, act accordingly... */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_A2S_ID)
		goto err2;

	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret) || l != SMTP_STID_SIZE || rt != RT_A2S_TAID)
		goto err2;
	ret = sm_rcb_getn(rcb, (uchar *) ta_id, l);
	if (sm_is_err(ret))
		goto err2;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4)
		goto err3;

	SM_TEST(rt == RT_A2S_MAP_RES);
	if (rt == RT_A2S_MAP_RES && v == SM_ACC_FOUND)
	{
		ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
		if (sm_is_err(ret) || l != 4)
			goto err3;
		SM_TEST(rt == RT_A2S_RCPT_ST);
		if (rt == RT_A2S_RCPT_ST)
			fprintf(stderr, "status=%d\n", v);
	}

#if 0
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_R2Q_RCPT_NAR)
		goto err3;
#endif

	while (!SM_RCB_ISEOB(rcb))
	{
		ret = sm_rcb_get2uint32(rcb, &l, &rt);
		switch (rt)
		{
		  case RT_Q2S_STATT:
		  case RT_A2S_STATT:
			break;
		  case RT_A2S_RVRS_ST:
			break;
		  case RT_A2S_RVRS_NAME:
			break;

		  default:
			goto err3;
		}
	}

	ret = sm_rcb_close_dec(qar_com->rcbcom_rdrcb);
	(void) sm_rcb_open_rcv(qar_com->rcbcom_rdrcb);
	--Requests;
	if (needeol)
		fprintf(stderr, "\n");
	return ret;

  err3:
  err2:
	/* use rcb functions that don't do check the state */
	(void) sm_rcb_close_decn(qar_com->rcbcom_rdrcb);
  error:
	/* open rcb for receiving next record */
	(void) sm_rcb_open_rcvn(qar_com->rcbcom_rdrcb);

	if (needeol)
		fprintf(stderr, "\n");
	fprintf(stderr, "ERROR: react=%x\n", ret);
	--Requests;
	return ret;
}

/*
**  AR2QMGR -- AR - test program interface
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
ar2qmgr(sm_evthr_task_P tsk)
{
	int fd;
	sm_ret_T ret;
	t_req_P t_req;
	rcbcom_ctx_P qar_com;

	SM_IS_EVTHR_TSK(tsk);

	t_req = (t_req_P) tsk->evthr_t_actx;
	qar_com = t_req->rq_qar_com;
	fd = tsk->evthr_t_fd;	/* checked in caller */
	ret = sm_rcb_rcv(fd, qar_com->rcbcom_rdrcb, QSS_RC_MINSZ);
	if (Verbose > 4)
		fprintf(stderr, "ar2qmgr, ret=%d, rw=%d, size=%d, len=%d, max=%d\n"
			, ret
			, qar_com->rcbcom_rdrcb->sm_rcb_rw
			, (int) qar_com->rcbcom_rdrcb->sm_rcb_size
			, (int) qar_com->rcbcom_rdrcb->sm_rcb_len
			, (int) qar_com->rcbcom_rdrcb->sm_rcb_max
			);
	else if (Verbose > 1)
		fprintf(stderr, "ar2qmgr, ret=%d\n", ret);
	if (ret > 0)
	{
		return EVTHR_WAITQ;
	}
	else if (ret == 0)
	{
		ret = sm_rcb_close_rcv(qar_com->rcbcom_rdrcb);

		/* start appropriate function ... */
		ret = react(tsk);
		if (sm_is_err(ret))
			goto termit;	/* too harsh? */
		else if (ret == QMGR_R_WAITQ)
			return EVTHR_WAITQ;
		else if (ret == QMGR_R_ASYNC)
			return EVTHR_OK;
		else if (ret == EVTHR_DEL)
			goto termit;
		else
			return ret;
	}
	else if (ret == SM_IO_EOF)
	{
		ret = sm_rcb_close_rcv(qar_com->rcbcom_rdrcb);
  termit:
		close(fd);

		/* XXX see comment in qm_fr_ss() */
		tsk->evthr_t_fd = INVALID_FD;	/* make it invalid */

		return EVTHR_DEL;
	}
	else /* if (ret < 0) */
	{
	}

	return EVTHR_DEL;
}

/*
**  LOOKUP -- queue recipient address lookups to SMAR
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
lookup(sm_evthr_task_P tsk)
{
	t_req_P t_req;
	int n;
	sm_ret_T ret;
	char *rcpt;
	sm_str_P rcpt_pa;

	SM_REQUIRE(tsk != NULL);
	t_req = (t_req_P) tsk->evthr_t_actx;
	if (t_req == NULL)
		return EVTHR_DEL|EVTHR_TERM;
	rcpt_pa = NULL;

#if 0
	n = pthread_mutex_lock(&mutex);
	SM_TEST(n == 0);
	n = pthread_cond_wait(&cond, &mutex);
	SM_TEST(n == 0);
#endif
	if (Verbose > 1)
		fprintf(stderr, "lookup, count=%d\n", t_req->rq_count);
	for (n = 0; n < t_req->rq_count; n++)
	{
		rcpt = t_req->rq_rcpt_pa[n];
		if (rcpt == NULL || *rcpt == '\0')
			return EVTHR_DEL|EVTHR_TERM;
		if (rcpt_pa == NULL)
		{
			rcpt_pa = sm_str_scpy(NULL, rcpt, SMAXLEN);
			SM_TEST(rcpt_pa != NULL);
			if (rcpt_pa == NULL)
				goto error;
		}
		else
		{
			sm_str_clr(rcpt_pa);
			ret = sm_str_scat(rcpt_pa, rcpt);
			SM_TEST(ret == SM_SUCCESS);
			if (sm_is_err(ret))
				goto error;
		}
		if (Verbose > 1)
			fprintf(stderr, "rcpt_pa[%d]=%.256s\n", n, rcpt);
		ret = rcpt2ar(tsk, rcpt_pa, t_req->rq_qar_com,
				t_req->rq_flags, t_req->rq_lt);
		if (sm_is_err(ret))
			goto error;
	}

	/* Wakeup write task */
	SM_TEST(t_req->rq_wr_tsk != NULL);
	if (t_req->rq_wr_tsk != NULL)
	{
		ret = evthr_en_wr(t_req->rq_wr_tsk);
		if (Verbose > 1)
			fprintf(stderr, "wakeup=%x\n", ret);
		SM_TEST(sm_is_success(ret));
	}

	/* Paranoia... */
	t_req->rq_count = 0;
	SM_STR_FREE(rcpt_pa);

	/* We're done */
	return EVTHR_DEL;

  error:
	SM_STR_FREE(rcpt_pa);
	return EVTHR_DEL|EVTHR_TERM;
}

/*
**  TESTSMAR -- Test SMAR
**
**	Parameters:
**		t_req -- test context
**
**	Returns:
**		none.
*/

static void
testsmar(t_req_P t_req)
{
	sm_ret_T ret;
	sm_evthr_task_P	task, task2, task3;
	struct timeval sleept;
	int lfd, n;
	rcbcom_ctx_P qar_com;

	evthr_ctx = NULL;
	task = task3 = NULL;

	ret = thr_init();
	SM_TEST(sm_is_success(ret));
	if (sm_is_err(ret))
		goto errq;
	ret = evthr_init(&evthr_ctx, 1, 6, 10);
	SM_TEST(sm_is_success(ret));
	if (sm_is_err(ret))
		goto errt1;
	SM_TEST(evthr_ctx != NULL);

	n = pthread_cond_init(&cond, NULL);
	SM_TEST(n == 0);
	n = pthread_mutex_init(&mutex, NULL);
	SM_TEST(n == 0);

	qar_com = t_req->rq_qar_com;
	ret = sm_rcbcom_open(qar_com);
	if (sm_is_err(ret))
		goto error;
	SM_IS_RCB(qar_com->rcbcom_rdrcb);

#if SMAR_TCP_NET
	(void) net_client_connect(smarip, smarport, &lfd);
#else
	(void) unix_client_connect(smarsock, &lfd);
#endif
	if (lfd < 0)
	{
		ret = sm_error_perm(SM_EM_AR, errno);
		goto error;
	}
	ret = sm_rcb_open_rcv(qar_com->rcbcom_rdrcb);
	if (sm_is_err(ret))
		goto error;

	ret = sm_fd_nonblock(lfd, true);
	if (sm_is_err(ret))
		goto error;	/* XXX COMPLAIN */
	ret = evthr_task_new(evthr_ctx, &task, EVTHR_EV_RD,
			lfd, NULL, qmgr_ar, (void *) t_req);
	if (sm_is_err(ret))
		goto error;
	t_req->rq_wr_tsk = task;

	n = gettimeofday(&sleept, NULL);
	SM_TEST(n == 0);
	sleept.tv_usec += 1000;
	ret = evthr_task_new(evthr_ctx, &task3, EVTHR_EV_SL, -1, &sleept,
				fcts, (void *) NULL);
	SM_TEST(sm_is_success(ret));
	SM_TEST(task3 != NULL);


	sleept.tv_usec += 1;
	ret = evthr_task_new(evthr_ctx, &task2, EVTHR_EV_SL, -1, &sleept,
				lookup, (void *) t_req);
	SM_TEST(sm_is_success(ret));
	SM_TEST(task2 != NULL);

	ret = evthr_loop(evthr_ctx);
	SM_TEST(sm_is_success(ret));
	goto done;

  error:
	fprintf(stderr, "ERROR\n");
  done:
	pthread_cond_destroy(&cond);
	pthread_mutex_destroy(&mutex);
	ret = evthr_stop(evthr_ctx);
	SM_TEST(sm_is_success(ret));
  errt1:
	ret = thr_stop();
	SM_TEST(sm_is_success(ret));
  errq:
	return;
}

/*
**  USAGE -- usage message
**
**	Parameters:
**		prg -- program name
**
**	Returns:
**		none
*/

static void
usage(const char *prg)
{
	fprintf(stderr, "usage: %s [options] addresses...\n"
		"Check whether recipient is acceptable, i.e.,\n"
		"if local: must be valid (currently: aliases)\n"
		"-l       Check local recipient in access map\n"
		"-R n     wait n times 0.1s\n"
		"-V       increase verbosity\n"
		, prg
		);
	return;
}

/*
**  MAIN -- guess...
**
**	Parameters:
**		argc -- arg counter
**		argv -- arg vector
**
**	Returns:
**		usual exit code
*/

int
main(int argc, char *argv[])
{
	int r;
	t_req_T t_req;
	char **h;
	void *ptr;

	ptr = NULL;
	h = NULL;
	t_req.rq_flags = SMARA_LFL_2821;
	t_req.rq_lt = SMARA_LT_RCPT_LOCAL;
	while ((r = getopt(argc, argv, "lR:V")) != -1)
	{
		switch (r)
		{
		  case 'l':
			t_req.rq_lt |= SMARA_LT_RCPT_ACC;
			break;

		  case 'R':
			MaxCount = atoi(optarg);
			break;

		  case 'V':
			Verbose++;
#if RCBCOMM_DEBUG
			rcbcomm_debug++;
#endif
			break;
		  default:
			usage(argv[0]);
			return 1;
		}
	}

	sm_test_begin(argc, argv, "test smar 0");
	t_req.rq_count = argc - optind;
	h = (char **) sm_malloc(sizeof(char *) * t_req.rq_count);
	ptr = h;
	SM_TEST(h != NULL);
	if (h == NULL)
		goto error;
	t_req.rq_rcpt_pa = h;
	t_req.rq_qar_com = &rcb_com;
	for (r = optind; r < argc; r++)
	{
		*h = argv[r];
		++h;
		++Requests;
	}
	testsmar(&t_req);
  error:
	if (ptr != NULL)
		sm_free(ptr);
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1