/*
 * 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: t-edbr-1.c,v 1.38 2007/06/18 04:42:30 ca Exp $")

#include "sm/assert.h"
#include "sm/magic.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/test.h"
#include "sm/bhtable.h"
#include "sm/edb.h"
#include "sm/actdb-int.h"
#include "sm/regex.h"
#define QMGR_DEBUG_DEFINE 1
#include "sm/qmgrdbg.h"

/*
**  Print and check content of DEFEDB.
**  This is almost a "mailq" command.
*/

/* Hack ... */
#define RCPT_MAX_LEN	256
#define MAX_STRLEN	1024
#define ERRBUF_SIZE	1024
#define MAX_MATCH	256
#define T_BHT_SIZE	1023

#define TA_TYPE		'T'
#define RCPT_TYPE	'R'
#define BOTH_TYPE	'B'	/* seen both entries */

static int Verbose;
static regex_t IdReg;
static regex_t AddrReg;
static bool IdPat;
static bool AddrPat;

static int
b_regmatch(const regex_t *preg, const char *string, int eflags)
{
	int r;
	size_t nmatch;
	regmatch_t pmatch[MAX_MATCH];

	nmatch = 0;
	r = regexec(preg, string, nmatch, pmatch, eflags);
	return r;
}

static aq_rcpt_P
new_rcpt(void)
{
	aq_rcpt_P aq_rcpt;

	aq_rcpt = (aq_rcpt_P) sm_zalloc(sizeof(*aq_rcpt));
	SM_TEST(aq_rcpt != NULL);
	if (aq_rcpt == NULL)
		return NULL;
#if AQ_RCPT_CHECK
	aq_rcpt->sm_magic = SM_AQ_RCPT_MAGIC;
#endif /* AQ_TA_CHECK */
	return aq_rcpt;
}

static sm_ret_T
aq_rcpt_clr(aq_rcpt_P aq_rcpt)
{
	if (aq_rcpt == NULL)
		return SM_SUCCESS;
	if (aq_rcpt->aqr_addrs != NULL
	    && !AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR)
	    && aq_rcpt->aqr_addrs != &(aq_rcpt->aqr_addr_mf))
		SM_FREE(aq_rcpt->aqr_addrs);
	SM_STR_FREE(aq_rcpt->aqr_pa);
	SM_STR_FREE(aq_rcpt->aqr_msg);
	SM_STR_FREE(aq_rcpt->aqr_dsn_msg);
	SM_FREE(aq_rcpt->aqr_dsns);

	/* clear entire struct? */
	aq_rcpt->aqr_err_st = 0;
	aq_rcpt->aqr_addr_fail = 0;
	aq_rcpt->aqr_dsn_idx = 0;

	return SM_SUCCESS;
}


static void
printtime(time_T t, char *name)
{
	if (Verbose > 1)
		sm_io_fprintf(smioout, "\t%s=%ld  %s", name, (long) t,
			ctime(&t));
	else
		sm_io_fprintf(smioout, "\t%s=%ld\n", name, (long) t);
}

static aq_ta_P
new_ta(void)
{
	aq_ta_P aq_ta;

	aq_ta = (aq_ta_P) sm_zalloc(sizeof(*aq_ta));
	SM_TEST(aq_ta != NULL);
	if (aq_ta == NULL)
		return NULL;
	aq_ta->aqt_mail = (aq_mail_P) sm_zalloc(sizeof(*(aq_ta->aqt_mail)));
	SM_TEST(aq_ta->aqt_mail != NULL);
	if (aq_ta->aqt_mail == NULL)
		return NULL;
#if AQ_TA_CHECK
	aq_ta->sm_magic = SM_AQ_TA_MAGIC;
#endif /* AQ_TA_CHECK */
	return aq_ta;
}

/* ARGSUSED1 */
static void
free_fn(void *str, void *key, void *ctx)
{
	(void) key;
	(void) ctx;
	if (str != NULL)
		sm_free(str);
}

/* ARGSUSED0 */
static void
free_aqs(void *aq, void *key, void *ctx)
{
	/* memory leak ... */
	/* should free aq */
	(void) aq;
	(void) key;
	(void) ctx;
}

/* ARGSUSED1 */
static sm_ret_T	 
walk_fn(bht_entry_P be, void *ctx)
{
	char *type;

	(void) ctx;
	type = (char *) be->bhe_value;
	SM_TEST(type != NULL);
	if (type == NULL)
		return sm_err_perm(ENOENT);
	SM_TEST(*type == BOTH_TYPE);
	if (*type != BOTH_TYPE)
	{
		if (Verbose > 0)
			sm_io_fprintf(smioerr, "type=%c, id=%s\n",
					*type, be->bhe_key);
		return sm_err_perm(ENOENT);
	}
	return SM_SUCCESS;
}

/* ARGSUSED1 */
static sm_ret_T	 
walk_ta(bht_entry_P be, void *ctx)
{
	aq_ta_P aq_ta;

	(void) ctx;
	aq_ta = (aq_ta_P) be->bhe_value;
	SM_TEST(aq_ta != NULL);
	if (aq_ta == NULL)
		return sm_err_perm(ENOENT);
	SM_TEST(aq_ta->aqt_rcpts_inaq == 0);
	if (aq_ta->aqt_rcpts_inaq != 0)
	{
		if (Verbose > 0)
			sm_io_fprintf(smioerr, "ta=%s, rcpts_missing=%d\n",
				aq_ta->aqt_ss_ta_id,
				aq_ta->aqt_rcpts_inaq);
	}
	return SM_SUCCESS;
}


/* add some logic to check counters, e.g., tried, total, left, ... */
static bool
lookup(bht_P tn, char *id, size_t len, char type)
{
	sm_ret_T ret;
	char *str, *str2;
	bht_entry_P bi;

	str = bht_find(tn, id, len);
	if (str == NULL)
	{
		str = sm_malloc(len);
		SM_TEST(str != NULL);
		if (str == NULL)
			return false;
		str2 = sm_malloc(1);
		SM_TEST(str2 != NULL);
		if (str2 == NULL)
			return false;
		*str2 = type;
		ret = bht_add(tn, id, len, str2, &bi);
		SM_TEST(ret == SM_SUCCESS);
		SM_TEST(bi != NULL);
	}
	else
	{
		if (type == TA_TYPE)
		{
			/* There can be only one.... transaction */
			if (*str == type)
			{
				SM_TEST(*str != type);
				return false;
			}
		}

		/* If it's a different type (TA/RCPT) then set it to BOTH */
		if (*str != type)
			*str = BOTH_TYPE;
	}
	return true;
}


static void
edbr1(uint flags)
{
	sm_ret_T ret, rt;
	uint u;
	bool ok;
	edb_ctx_P edb_ctx;
	edb_cnf_T edb_cnf;
	aq_ctx_P aq_ctx;
	aq_ctx_T aq_ctx_s;
	aq_rcpt_P aq_rcpt;
	aq_ta_P aq_ta, aq_ta_f;
	edb_req_P edb_req;
	edb_cursor_P edb_cursor;
	bht_P tn, t_aq;
	bht_entry_P bi;

	aq_rcpt = NULL;
	aq_ta = NULL;
	edb_req = NULL;
	tn = NULL;
	t_aq = NULL;
	edb_cursor = NULL;
	edb_ctx = NULL;
	aq_ctx = &aq_ctx_s;
	sm_memzero(&edb_cnf, sizeof(edb_cnf));
	edb_cnf.edbcnf_oflags = flags;
	ret = edb_open(&edb_cnf, NULL, NULL, &edb_ctx);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
	{
		if (Verbose > 0)
			sm_io_fprintf(smioerr, "edb_open=%r\n", ret);
		return;
	}
	ret = aq_open(NULL, &aq_ctx, 256, 0);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
		goto error;
	aq_rcpt = new_rcpt();
	SM_TEST(aq_rcpt != NULL);
	if (aq_rcpt == NULL)
		goto error;

	ret = edb_req_new(edb_ctx, EDB_RQF_NONE, &edb_req, THR_NO_LOCK);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
		goto error;

	tn = bht_new(T_BHT_SIZE, T_BHT_SIZE * 2);
	SM_TEST(tn != NULL);
	if (tn == NULL)
		goto error;

	t_aq = bht_new(T_BHT_SIZE, T_BHT_SIZE * 2);
	SM_TEST(t_aq != NULL);
	if (t_aq == NULL)
		goto error;

	ret = edb_rd_open(edb_ctx, &edb_cursor);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
		goto error;
	for (;;)
	{
		ret = edb_rd_next(edb_ctx, edb_cursor, edb_req);
		if (ret == sm_error_perm(SM_EM_EDB, DB_NOTFOUND))
			break;
		SM_TEST(ret == SM_SUCCESS);
		if (sm_is_err(ret))
			goto error;
		rt = edb_get_type(edb_req);
		if (sm_is_err(rt))
			goto error;
		if (rt == EDB_REQ_TA)
		{
			aq_ta = new_ta();
			SM_TEST(aq_ta != NULL);
			if (aq_ta == NULL)
				goto error;
			ret = edb_ta_dec(edb_req, aq_ta);
			SM_TEST(ret == SM_SUCCESS);
			if (sm_is_err(ret))
				goto error;
			aq_ta->aqt_ss_ta_id[SMTP_STID_SIZE - 1] = '\0';
			if (IdPat &&
			    b_regmatch(&IdReg, aq_ta->aqt_ss_ta_id, 0) != 0)
				continue;
			if (AddrPat &&
			    b_regmatch(&AddrReg,
					(const char *) sm_str_getdata(aq_ta->aqt_mail->aqm_pa), 0) != 0)
				continue;

			sm_io_fprintf(smioout, "got transaction\n");
			sm_io_fprintf(smioout, "\tta_id=%s\n",
				aq_ta->aqt_ss_ta_id);
			sm_io_fprintf(smioout, "\tmail=%S\n",
				aq_ta->aqt_mail->aqm_pa);
			printtime(aq_ta->aqt_st_time, "time");
			sm_io_fprintf(smioout, "\tcdb=%s\n",
				sm_cstr_data(aq_ta->aqt_cdb_id));
			sm_io_fprintf(smioout, "\trcpts_tot=%u\n",
				aq_ta->aqt_rcpts_tot);
			sm_io_fprintf(smioout, "\trcpts_left=%u\n",
				aq_ta->aqt_rcpts_left);
			sm_io_fprintf(smioout, "\trcpts_temp=%u\n",
				aq_ta->aqt_rcpts_temp);
			sm_io_fprintf(smioout, "\trcpts_perm=%u\n",
				aq_ta->aqt_rcpts_perm);
			if (Verbose > 0)
			{
				sm_io_fprintf(smioout, "\trcpts_tried=%u\n",
					aq_ta->aqt_rcpts_tried);
				sm_io_fprintf(smioout, "\tnxt_idx=%u\n",
					aq_ta->aqt_nxt_idx);
			}
			sm_io_fprintf(smioout, "\tstate=%d\n",
				aq_ta->aqt_state);
			sm_io_fprintf(smioout, "\taqt_rcpts_ar=%d\n",
				aq_ta->aqt_rcpts_ar);
			sm_io_fprintf(smioout, "\taqt_owners_n=%d\n",
				aq_ta->aqt_owners_n);
			for (u = 0; u < aq_ta->aqt_owners_n; u++)
			{
				sm_io_fprintf(smioout, "\towner[%u]=%S\n",
					u, aq_ta->aqt_owners_pa[u]);
			}

			/* for checks... */
			aq_ta->aqt_rcpts_inaq = aq_ta->aqt_rcpts_left;

			ok = lookup(tn, aq_ta->aqt_ss_ta_id,
				sizeof(aq_ta->aqt_ss_ta_id), TA_TYPE);
			if (!ok)
				goto error;
			ret = bht_add(t_aq, aq_ta->aqt_ss_ta_id,
				sizeof(aq_ta->aqt_ss_ta_id), aq_ta, &bi);
			SM_TEST(ret == SM_SUCCESS);
			SM_TEST(bi != NULL);
		}
		else if (rt == EDB_REQ_RCPT)
		{
			aq_rcpt_clr(aq_rcpt);
			ret = edb_rcpt_dec(edb_req, aq_rcpt);
			SM_TEST(ret == SM_SUCCESS);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_ss_ta_id[SMTP_STID_SIZE - 1] = '\0';
			if (IdPat &&
			    b_regmatch(&IdReg, aq_rcpt->aqr_ss_ta_id, 0) != 0)
				continue;
			if (AddrPat &&
			    b_regmatch(&AddrReg,
					(const char *) sm_str_getdata(aq_rcpt->aqr_pa), 0) != 0)
				continue;

			sm_io_fprintf(smioout, "got recipient\n");
			sm_io_fprintf(smioout, "\tta_id=%s\n",
				aq_rcpt->aqr_ss_ta_id);
			sm_io_fprintf(smioout, "\trcpt=%S\n",
				aq_rcpt->aqr_pa);
			sm_io_fprintf(smioout, "\taqr_rcpt_idx=%u\n",
				aq_rcpt->aqr_idx);
			if (Verbose > 0)
				sm_io_fprintf(smioout, "\taqr_tries=%u\n",
					aq_rcpt->aqr_tries);
			if (Verbose > 0 && aq_rcpt->aqr_port != 0)
				sm_io_fprintf(smioout, "\taqr_port=%hd\n",
					aq_rcpt->aqr_port);
			if (aq_rcpt->aqr_addr_max > 0)
			{
				if (Verbose > 0)
				{
					for (u = 0; u < aq_rcpt->aqr_addr_max;
					     ++u)
					{
						sm_io_fprintf(smioout,
							"\tsrv_ip4=%A\n",
							aq_rcpt->aqr_addrs[u].aqra_ipv4);
						printtime(aq_rcpt->aqr_addrs[u].aqra_expt,
							"expt");
						sm_io_fprintf(smioout,
							"\tpref=%hu\n",
							aq_rcpt->aqr_addrs[u].aqra_pref);
					}
				}
				else
				{
					sm_io_fprintf(smioout, "\tsrv_ip4=%A\n",
						aq_rcpt->aqr_addrs[0].aqra_ipv4);
				}
			}
			else
				sm_io_fprintf(smioout, "\tsrv_ip4=undef\n");
			sm_io_fprintf(smioout, "\taqr_da_idx=%u\n",
				aq_rcpt->aqr_da_idx);
			printtime(aq_rcpt->aqr_st_time, "aqr_st_time ");
			if (Verbose > 0)
			{
				printtime(aq_rcpt->aqr_last_try,
					"aqr_last_try");
				printtime(aq_rcpt->aqr_next_try,
					"aqr_next_try");
			}
			sm_io_fprintf(smioout, "\taqr_status=%d\n",
				aq_rcpt->aqr_status);
			if (Verbose > 0)
			{
				sm_io_fprintf(smioout, "\tflags=0x%x\n",
					aq_rcpt->aqr_flags);
			}
			if (Verbose > 0 && aq_rcpt->aqr_msg != NULL)
			{
				sm_io_fprintf(smioout, "\ttext=%S\n",
					aq_rcpt->aqr_msg);
			}
			if (Verbose > 0 && aq_rcpt->aqr_dsn_msg != NULL)
			{
				sm_io_fprintf(smioout, "\tdsn_msg=%S\n",
					aq_rcpt->aqr_dsn_msg);
			}

			if (Verbose > 0 && aq_rcpt->aqr_err_st != 0)
			{
				sm_io_fprintf(smioout, "\terr_st=0x%x\n",
							aq_rcpt->aqr_err_st);
			}

			if (Verbose > 0 && aq_rcpt->aqr_addr_fail != 0)
			{
				sm_io_fprintf(smioout, "\thost=0x%x\n",
							aq_rcpt->aqr_addr_fail);
			}
			if (Verbose > 0 && aq_rcpt_has_bounce(aq_rcpt))
			{
				sm_io_fprintf(smioout, "\tbounce_idx=%u\n",
						aq_rcpt->aqr_dsn_idx);
			}
			if (Verbose > 0 && aq_rcpt_has_owner(aq_rcpt))
			{
				sm_io_fprintf(smioout, "\towner_idx=%u\n",
						aq_rcpt->aqr_owner_idx);
			}

#if MTA_USE_TLS
			if (Verbose > 1 && aq_rcpt->aqr_conf != NULL) {
				sm_io_fprintf(smioout, "\tconf=%S\n", aq_rcpt->aqr_conf);
				sm_io_fprintf(smioout, "\tmaprescnf=%#x\n",
					aq_rcpt->aqr_maprescnf);
			}
#endif /* MTA_USE_TLS */

			ok = lookup(tn, aq_rcpt->aqr_ss_ta_id,
				sizeof(aq_rcpt->aqr_ss_ta_id), RCPT_TYPE);
			if (!ok)
				goto error;
			aq_ta_f = bht_find(t_aq, aq_rcpt->aqr_ss_ta_id,
				sizeof(aq_rcpt->aqr_ss_ta_id));

			/* this fails if the rcpt is found before the TA */
			SM_TEST(aq_ta_f != NULL);
			if (aq_ta_f != NULL)
			{
				SM_TEST(aq_ta_f->aqt_rcpts_inaq > 0);
				--aq_ta_f->aqt_rcpts_inaq;
			}
		}
		else if (rt != EDB_REQ_VRS)
		{
			sm_io_fprintf(smioout, "got what? %x\n", rt);
			SM_TEST(false);
			if (sm_is_err(ret))
				goto error;
		}
	}

	bht_walk(t_aq, walk_ta, NULL);
	bht_walk(tn, walk_fn, NULL);
  error:
	SM_FREE(aq_rcpt);
	if (aq_ta != NULL)
	{
		SM_STR_FREE(aq_ta->aqt_mail->aqm_pa);
		SM_FREE(aq_ta->aqt_mail);
		SM_CSTR_FREE(aq_ta->aqt_cdb_id);
		sm_free(aq_ta);
	}
	sm_io_flush(smioout);
	if (edb_ctx != NULL && edb_cursor != NULL)
	{
		ret = edb_rd_close(edb_ctx, edb_cursor);
		SM_TEST(ret == SM_SUCCESS);
	}

	if (aq_ctx != NULL)
	{
		ret = aq_close(aq_ctx);
		SM_TEST(ret == SM_SUCCESS);
	}
	if (edb_ctx != NULL)
	{
		ret = edb_close(edb_ctx);
		SM_TEST(ret == SM_SUCCESS);
	}
	if (tn != NULL)
		bht_destroy(tn, free_fn, NULL);
	if (t_aq != NULL)
		bht_destroy(t_aq, free_aqs, NULL);
	return;
}

static void
usage(const char *prg)
{
	fprintf(stderr, "usage: %s [options]\n"
		"Print content of main mail queue and"
		" test it for consistency.\n"
		"options:\n"
		"-I regex: print only entries whose ID match regex\n"
		"-R regex: print only entries whose address match regex\n"
		"-V: increase verbosity (can be used multiple times)\n"
		"-w: open DB environment read/write\n"
		, prg);
	exit(0);
}

static int
b_regcomp(regex_t *preg, const char *pattern, int pflags)
{
	int regerr;

	regerr = regcomp(preg, pattern, pflags);
	if (regerr != 0)
	{
		/* Errorhandling */
		char errbuf[ERRBUF_SIZE];

		(void) regerror(regerr, preg, errbuf, sizeof errbuf);
		fprintf(stderr, "pattern-compile-error: %s", errbuf);
	}
	return regerr;
}

int
main(int argc, char *argv[])
{
	int r, c;
	char *prg;
	int regopts;
	uint flags;

	regopts = REG_EXTENDED;
	opterr = 0;
	Verbose = 0;
	prg = argv[0];
	IdPat = false;
	AddrPat = false;
	flags = EDB_OPEN_RDONLY|EDB_OPEN_NOENV;
	while ((r = getopt(argc, argv, "I:R:rVvw")) != -1)
	{
		switch (r)
		{
		  case 'I':
			c = b_regcomp(&IdReg, optarg, regopts);
			if (c != 0)
			{
				fprintf(stderr, "regcomp(%s) failed=%d\n",
					optarg, c);
				return 1;
			}
			IdPat = true;
			break;
		  case 'R':
			c = b_regcomp(&AddrReg, optarg, regopts);
			if (c != 0)
			{
				fprintf(stderr, "regcomp(%s) failed=%d\n",
					optarg, c);
				return 1;
			}
			AddrPat = true;
			break;
		  case 'r':
			flags = EDB_OPEN_RDONLY;
			break;
		  case 'v':
			flags |= EDB_OPEN_NOVERSION;
			break;
		  case 'V':
			++Verbose;
			break;
		  case 'w':
			flags = EDB_OPEN_RDWR;
			break;
		  default:
			usage(prg);

			/* NOTREACHED */
			break;
		}
	}
	sm_test_begin(argc, argv, "test edbr 1");
	argc -= optind;
	argv += optind;
	edbr1(flags);
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1