/*
 * Copyright (c) 2002-2006 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: edb.c,v 1.134 2007/06/18 04:42:31 ca Exp $")
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/stat.h"
#include "sm/time.h"
#include "sm/heap.h"
#include "sm/assert.h"
#include "sm/str.h"
#include "sm/io.h"
#include "sm/log.h"
#include "sm/fdset.h"
#include "sm/reccom.h"
#include "sm/rcb.h"
#include "sm/cdb.h"
#include "sm/qmgr-int.h"
#include "sm/actdb-int.h"
#include "sm/edb.h"
#include "edb-int.h"
/* Should we provide our own locking or let BDB do it? for now: own */
#include "sm/pthread.h"
#define EDB_LOG_DEFINES 1
#include "log.h"

#define EDB_VRS_NONE	0x00
#define EDB_VRS_WR	0x01
#define EDB_VRS_TXN	0x02

/*
**  Error messages? Use logging instead of printf?
**	dbp->set_errfile(dbp, stderr);
**	dbp->set_errpfx(dbp, EDB_PREFIX);
**	dbp->err(dbp, r, "...");
*/

/*
**  EDB_ERR_CB -- called by BDB in case of errors
**
**	Parameters:
**		dbenv -- BDB environment
**		errpfx -- error prefix
**		msg -- error message
**
**	Returns:
**		none
**
**	Note: this is a HACK. errpfx is expected to be part of edb_ctx_T
**		and hence edb_ctx is passed as context to this function;
**		there doesn't seem to be another way to get a user context.
**
**	Last code review: 2005-03-23 21:21:50; see note.
**	Last code change:
*/

static void
edb_err_cb(
#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3
	const DB_ENV *dbenv, const char *errpfx, const char *msg)
#else
	const char *errpfx, char *msg)
#endif
{
	edb_ctx_P edb_ctx;

	if (NULL == errpfx)
		return;	/* ERROR */

	/* HACK; get edb_ctx */
	edb_ctx = (edb_ctx_P) (errpfx - offsetof(edb_ctx_T, edb_pfx));
	if (NULL == edb_ctx)
		return;	/* ERROR */

	/* "clean up" msg? it doesn't follow the smX logging format... */
	sm_log_write(edb_ctx->edb_lctx,
		EDB_LCAT_EDB, EDB_LMOD_EDB, SM_LOG_ERR, 1,
		"sev=ERROR, %s", msg);
	return;
}

/*
**  EDB_CTX_FREE -- free deferred envelope database context
**
**	Parameters:
**		edb_ctx -- EDB context
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-23 21:22:12
**	Last code change:
*/

static sm_ret_T
edb_ctx_free(edb_ctx_P edb_ctx)
{
	if (NULL == edb_ctx)
		return SM_SUCCESS;
	SM_IS_EDB_CTX(edb_ctx);
	edb_ctx->sm_magic = SM_MAGIC_NULL;
	sm_free_size(edb_ctx, sizeof(*edb_ctx));
	return SM_SUCCESS;
}

/*
**  EDB_RW_VERSION -- read or write version of EDB
**
**	Parameters:
**		edb_ctx -- EDB context
**		flags -- read/write etc
**
**	Returns:
**		usual sm_error code; ENOMEM, SM_E_VER_MIX, et.al.
**
**	Locking:
**		none (must be provided by caller)
**
**	Last code review: 2005-03-23 22:04:08
**	Last code change: 2005-03-23 22:03:34
*/

static sm_ret_T
edb_rw_version(edb_ctx_P edb_ctx, uint flags)
{
	sm_ret_T ret;
	int r;
	DBT db_key, db_data;
	DB_TXN *db_txn;
	sm_rcb_P rcb;

	SM_IS_EDB_CTX(edb_ctx);

	ret = SM_SUCCESS;
	db_txn = NULL;
	rcb = NULL;
	sm_memzero(&db_key, sizeof(db_key));
	sm_memzero(&db_data, sizeof(db_data));
	db_data.flags = DB_DBT_USERMEM;
	db_key.data = EDB_VRS_KEY;
	db_key.size = sizeof(EDB_VRS_KEY);

#define EDB_RCB_VERS_LEN	32
#define EDB_RCB_VERS_MAX	64

	rcb = sm_rcb_new(NULL, EDB_RCB_VERS_LEN, EDB_RCB_VERS_MAX);
	if (NULL == rcb)
		return sm_error_temp(SM_EM_EDB, ENOMEM);

	if (SM_IS_FLAG(flags, EDB_VRS_TXN)) {
		r = edb_ctx->edb_bdbenv->txn_begin(edb_ctx->edb_bdbenv, NULL, &db_txn, 0);
		if (r != 0) {
			ret = sm_error_perm(SM_EM_EDB, r);
			goto error;
		}
	}

	if (SM_IS_FLAG(flags, EDB_VRS_WR)) {
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, EDB_RCB_VERS_LEN,
			SM_RCBV_INT, RT_EDB_VERSION, EDB_VERSION,
			SM_RCBV_END);
		if (sm_is_err(ret))
			goto err0;
		db_data.data = sm_rcb_data(rcb);
		db_data.size = sm_rcb_getlen(rcb);
		r = edb_ctx->edb_bdb->put(edb_ctx->edb_bdb, db_txn, &db_key, &db_data, 0);
	}
	else {
		uint32_t rt, l, v;

		db_data.data = sm_rcb_data(rcb);
		db_data.ulen = sm_rcb_getsize(rcb);
		r = edb_ctx->edb_bdb->get(edb_ctx->edb_bdb, db_txn, &db_key, &db_data, 0);
		if (0 == r) {
			l = db_data.size;
			if (l < sm_rcb_getmax(rcb))
				sm_rcb_setlen(rcb, db_data.size);
			ret = sm_rcb_open_dec(rcb);
			if (sm_is_err(ret))
				goto err0;

			/* Total length of record */
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret) || v > EDB_RCB_VERS_MAX ||
			    v > sm_rcb_getlen(rcb))
				goto err0;

			/* version number */
			ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
			if (sm_is_err(ret) || l != 4 || rt != EDB_VERSION)
				goto err0;
			if (!EDB_VRS_COMPAT(v, EDB_VERSION))
				ret = sm_error_perm(SM_EM_EDB, SM_E_VER_MIX);
		}
	}
	if (r != 0)
		ret = sm_error_perm(SM_EM_EDB, r); /* always perm? */

 err0:
	if (db_txn != NULL) {
		if (sm_is_err(ret)) {
			(void) db_txn->abort(db_txn);
		}
		else {
			r = db_txn->commit(db_txn, 0);
			db_txn = NULL;
			if (r != 0)
				ret = sm_error_perm(SM_EM_EDB, r);
					/* always perm? */
		}
	}

  error:
	if (rcb != NULL)
		sm_rcb_free(rcb);
	return ret;
}

#if SM_REQL_FLE
/*
**  EDB_REQ_PRE -- allocate some edb requests
**
**	Parameters:
**		edb_ctx -- EDB context
**		n -- number of entries to allocate
**		lockit -- lock edb_ctx? (reql_pool)
**
**	Returns:
**		usual sm_error code; ENOMEM
**
**	Side Effects: edb_reql_fle may have some entries even on error
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
edb_req_pre(edb_ctx_P edb_ctx, uint n)
{
	uint u;
	sm_ret_T ret;
	edb_req_P edb_req;

	ret = SM_SUCCESS;
	for (u = 0; u < n; u++) {
		ret = edb_req_new(edb_ctx, EDB_RQF_ALLOC, &edb_req, false);
		if (sm_is_err(ret))
			break;
		SM_ASSERT(edb_req != NULL);
		EDBREQL_PRE(&edb_ctx->edb_reql_fle, edb_req);
	}
	return ret;
}
#endif

/*
**  EDB_OPEN -- open deferred envelope database on disk
**
**	Parameters:
**		edb_cnf -- EDB configuration data
**		base_path -- path to base directory
**		lctx -- log context
**		pedb_ctx -- pointer to EDB context (output)
**
**	Returns:
**		usual sm_error code; ENOMEM, SM_E_VER_MIX, et.al.
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
edb_open(edb_cnf_P edb_cnf, const char *base_path, sm_log_ctx_P lctx, edb_ctx_P *pedb_ctx)
{
	sm_ret_T ret;
	int r;
	uint flags, oflags;
	int major_v, minor_v, patch_v;
	bool exists;
	char *err_prefix, *dbfile;
	edb_ctx_P edb_ctx;
	DB *dbp;
	DB_ENV *dbenv;
	DB_TXN *db_txn;
	struct stat sb;

#define EDB_PREFIX	"libedb"

#define EDB_CNF_DEF_NULL(field, dflt)	\
	(NULL == edb_cnf || NULL == edb_cnf->field) ? (dflt) : edb_cnf->field
#define EDB_CNF_DEF_0(field, dflt)	\
	(NULL == edb_cnf || edb_cnf->field == 0) ? (dflt) : edb_cnf->field

	SM_ASSERT(pedb_ctx != NULL);
	*pedb_ctx = NULL;
	dbp = NULL;
	dbenv = NULL;
	db_txn = NULL;
	dbfile = NULL;
	exists = false;
	(void) db_version(&major_v, &minor_v, &patch_v);
	if (major_v != DB_VERSION_MAJOR || minor_v != DB_VERSION_MINOR)
		return sm_error_perm(SM_EM_EDB, SM_E_VER_MIX);
	edb_ctx = (edb_ctx_P) sm_zalloc(sizeof(*edb_ctx));
	if (NULL == edb_ctx)
		return sm_error_temp(SM_EM_EDB, ENOMEM);
	strlcat(edb_ctx->edb_pfx, "edb", sizeof(edb_ctx->edb_pfx));
	edb_ctx->edb_lctx = lctx;

	r = pthread_mutex_init(&edb_ctx->edb_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_EDB, r);
		goto error;
	}
	oflags = EDB_CNF_DEF_0(edbcnf_oflags,  0);

	EDBREQL_INIT(&edb_ctx->edb_reql_wr);
	EDBREQL_INIT(&edb_ctx->edb_reql_fls);
	EDBREQL_INIT(&edb_ctx->edb_reql_fln);
#if SM_REQL_FLE
	EDBREQL_INIT(&edb_ctx->edb_reql_fle);

	/* preallocate some entries */
	ret = edb_req_pre(edb_ctx, 2);
	if (sm_is_err(ret))
		goto error;
#endif

	/* does DB exist? */
	r = stat(EDB_NAME_RD, &sb);
	exists = (0 == r);	/* check for other errors? */

	err_prefix = EDB_CNF_DEF_NULL(edbcnf_err_prefix, EDB_PREFIX);
	if (!SM_IS_FLAG(oflags, EDB_OPEN_NOENV)) {
		size_t l;
		char dbh[PATH_MAX];

		dbh[0] = '\0';
		r = db_env_create(&dbenv, 0);
		if (r != 0) {
			dbenv->err(dbenv, r, "db_env_create");
			ret = sm_error_perm(SM_EM_EDB, r);
			goto error;
		}
#if 0
		dbenv->set_errfile(dbenv, stderr);
		dbenv->set_errpfx(dbenv, err_prefix);
#else
		dbenv->set_errpfx(dbenv, edb_ctx->edb_pfx);
		dbenv->set_errcall(dbenv, edb_err_cb);
#endif
		r = dbenv->set_cachesize(dbenv, 0,
			EDB_CNF_DEF_0(edbcnf_cachesize, EDB_CACHE), 0);

		if (SM_IS_FLAG(oflags, EDB_OPEN_RDONLY))
			flags = DB_INIT_MPOOL | DB_PRIVATE /*| DB_INIT_LOCK*/;
		else {
			flags = DB_CREATE | DB_INIT_MPOOL | DB_INIT_TXN |
				/* DB_INIT_LOCK | */ DB_PRIVATE;
			if (SM_IS_FLAG(oflags, EDB_OPEN_RECOVER))
				flags |= DB_RECOVER;
			if (SM_IS_FLAG(oflags, EDB_OPEN_RECOVER_FATAL))
				flags |= DB_RECOVER_FATAL;
		}

		if (base_path != NULL && *base_path != '\0' &&
			(NULL == edb_cnf || NULL == edb_cnf->edbcnf_basedir ||
			 *edb_cnf->edbcnf_basedir != '/'))
		{
			l = strlcpy(dbh, base_path, sizeof(dbh));
			if (l >= sizeof(dbh)) {
				ret = sm_error_perm(SM_EM_EDB, SM_E_2BIG);
				goto error;
			}
		}
		l = strlcat(dbh,
				(NULL == edb_cnf || NULL == edb_cnf->edbcnf_basedir) ?
					EDB_HOME : edb_cnf->edbcnf_basedir,
				sizeof(dbh));
		if (l >= sizeof(dbh)) {
			ret = sm_error_perm(SM_EM_EDB, SM_E_2BIG);
			goto error;
		}

		r  = dbenv->open(dbenv, dbh, flags, 0600);
		if (r != 0) {
			dbenv->err(dbenv, r,
				"where=DB_ENV->open, dbhome=%s", dbh);
			ret = sm_error_perm(SM_EM_EDB, r);
			goto err2;
		}
		if (edb_cnf != NULL && edb_cnf->edbcnf_logdir != NULL &&
		    *edb_cnf->edbcnf_logdir != '\0' &&
		    (r = dbenv->set_lg_dir(dbenv, edb_cnf->edbcnf_logdir)) != 0)
		{
			dbenv->err(dbenv, r, "where=DB_ENV->set_lg_dir, logdir=%s",
				edb_cnf->edbcnf_logdir);
			ret = sm_error_perm(SM_EM_EDB, r);
			goto err2;
		}
		if (SM_IS_FLAG(oflags, EDB_LOG_AUTOREMOVE) &&
		    (r = dbenv->set_flags(dbenv, DB_LOG_AUTOREMOVE, 1)) != 0)
		{
			dbenv->err(dbenv, r,
				"where=DB_ENV->set_flags, flags=DB_LOG_AUTOREMOVE",
				edb_cnf->edbcnf_logdir);
			ret = sm_error_perm(SM_EM_EDB, r);
			goto err2;
		}
	}

	/* Create and initialize database object, open the database. */
	r = db_create(&dbp, dbenv, 0);
	if (r != 0) {
		sm_log_write(lctx,
			EDB_LCAT_EDB, EDB_LMOD_EDB, SM_LOG_ERR, 4,
			"sev=ERROR, func=edb_open, db_create=%s",
			db_strerror(r));
		ret = sm_error_perm(SM_EM_EDB, r);
		goto err2;
	}
	if (SM_IS_FLAG(oflags, EDB_OPEN_NOENV)) {
		dbp->set_errpfx(dbp, edb_ctx->edb_pfx);
		dbp->set_errcall(dbp, edb_err_cb);
	}
	if (edb_cnf != NULL && edb_cnf->edbcnf_pagesize != 0
	    && (r = dbp->set_pagesize(dbp, edb_cnf->edbcnf_pagesize)) != 0)
	{
		dbp->err(dbp, r, "where=set_pagesize");
		ret = sm_error_perm(SM_EM_EDB, r);
		goto err2;
	}

	db_txn = NULL;
	if (!SM_IS_FLAG(oflags, EDB_OPEN_NOENV)) {
		r = dbenv->txn_begin(dbenv, NULL, &db_txn, 0);
		if (r != 0) {
			ret = sm_error_perm(SM_EM_EDB, r);
			goto err2;
		}
	}

	dbfile = SM_IS_FLAG(oflags, EDB_OPEN_NOENV) ? EDB_NAME_RD : EDB_NAME;
	flags = SM_IS_FLAG(oflags, EDB_OPEN_RDONLY) ? DB_RDONLY : DB_CREATE;
	r = dbp->open(dbp, db_txn, dbfile, NULL, DB_BTREE, flags, 0600);
	if (r != 0) {
		dbp->err(dbp, r, "where=db->open, db=%s", EDB_NAME);
		if (db_txn != NULL) {
			(void) db_txn->abort(db_txn);
			db_txn = NULL;
		}
		ret = sm_error_perm(SM_EM_EDB, r);
		goto err2;
	}

	/* for edb_rw_version() */
	flags = exists ? EDB_VRS_NONE : EDB_VRS_WR;
	if (db_txn != NULL) {
		flags |= EDB_VRS_TXN;
		r = db_txn->commit(db_txn, 0);
		db_txn = NULL;
		if (r != 0) {
			ret = sm_error_perm(SM_EM_EDB, r);
			goto err3;
		}
	}

	edb_ctx->sm_magic = SM_EDB_CTX_MAGIC;
	edb_ctx->edb_bdb = dbp;
	edb_ctx->edb_bdbenv = dbenv;
	ret = edb_rw_version(edb_ctx, flags);
	if (sm_is_err(ret) && !SM_IS_FLAG(oflags, EDB_OPEN_NOVERSION))
		goto err3;

#if 0
	/* only if writing? */
	edb_ctx->edb_version = EDB_VERSION;
	edb_ctx->edb_maxsize = size;
	edb_ctx->edb_mode = mode;
/*
	edb_ctx->edb_created;
	edb_ctx->edb_touched;
*/
#endif /* 0 */
	edb_ctx->edb_chkpt_kb = (NULL == edb_cnf) ? 0 :
					edb_cnf->edbcnf_chkpt_kb;
	edb_ctx->edb_chkpt_delay = (NULL == edb_cnf) ? 0 :
					edb_cnf->edbcnf_chkpt_delay;
	*pedb_ctx = edb_ctx;
	return SM_SUCCESS;

  err3:
	if (dbp != NULL)
		(void) dbp->close(dbp, 0);
  err2:
	if (dbenv != NULL)
		(void) dbenv->close(dbenv, 0);
  error:
	/* clean up... close DB etc */

	/* XXX clean up mutex, cond, ... */

	edb_ctx->sm_magic = SM_MAGIC_NULL;
	sm_free_size(edb_ctx, sizeof(*edb_ctx));
	return ret;
}

/*
**  EDB_HDRMODL_WR -- write header modification list to EDB
**
**	Parameters:
**		sm_hdrmodhd -- header of header modification list
**		rcb -- RCB to fill in
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
edb_hdrmodl_wr(sm_hdrmodhd_P sm_hdrmodhd, sm_rcb_P rcb)
{
	return sm_hdrmodl_wr(sm_hdrmodhd, rcb, RT_EDB_HM_T_P, RT_EDB_HM_HDR);
}

/*
**  EDB_TA_APP -- append transaction (status) to request list
**
**	Parameters:
**		edb_ctx -- EDB context
**		aq_ta -- transaction (sender) data
**		edb_req_hd -- EDB request list (used if not NULL)
**		status -- transaction status
**
**	Returns:
**		usual sm_error code; ENOMEM, EINVAL, SM_E_OVFLW_SC,
**			SM_E_OVFLW_NS, etc
**
**	Side Effects: none on error (except if unlock fails)
**		if ok: appends new rcb to edb_req_hd or edb_ctx list
**
**	Locking:
**		locks edb_ctx during operation
**
**	Last code review: 2005-03-23 22:43:59
**	Last code change:
*/

sm_ret_T
edb_ta_app(edb_ctx_P edb_ctx, aq_ta_P aq_ta, edb_req_hd_P edb_req_hd, int status)
{
	sm_ret_T ret;
	int r;
	uint u;
	sm_rcb_P rcb;
	edb_req_P edb_req;

	SM_IS_EDB_CTX(edb_ctx);
	SM_IS_AQ_TA(aq_ta);

	r = pthread_mutex_lock(&edb_ctx->edb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_EDB, r);
	}

	edb_req = NULL;
	ret = edb_req_new(edb_ctx, EDB_RQF_NONE, &edb_req, false);
	if (sm_is_err(ret))
		goto error;
	rcb = edb_req->edb_req_rcb;
	ret = sm_rcb_open_enc(rcb, -1);
	if (sm_is_err(ret))
		goto error;
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
	/* transaction status */
		SM_RCBV_INT, RT_EDB_TA_ST, status,
	/* CDB ID */
		SM_RCBV_CSTR, RT_EDB_CDBID, aq_ta->aqt_cdb_id,
		SM_RCBV_OFF, RT_EDB_SIZE_B, aq_ta->aqt_msg_sz_b,
		SM_RCBV_INT, RT_EDB_TA_ST_TI, aq_ta->aqt_st_time,
		SM_RCBV_INT, RT_EDB_TA_FL,
			(aq_ta->aqt_flags & AQ_TA_FL_MASK) | AQ_TA_FL_DEFEDB,
	/* nrcpts */
		SM_RCBV_INT, RT_EDB_TA_R_TOT, aq_ta->aqt_rcpts_tot,
		SM_RCBV_INT, RT_EDB_TA_R_LEFT, aq_ta->aqt_rcpts_left,
		SM_RCBV_INT, RT_EDB_TA_R_TEMP, aq_ta->aqt_rcpts_temp,
		SM_RCBV_INT, RT_EDB_TA_R_PERM, aq_ta->aqt_rcpts_perm,
		SM_RCBV_INT, RT_EDB_TA_R_TRIED, aq_ta->aqt_rcpts_tried,
		SM_RCBV_INT, RT_EDB_TA_R_NXT, (uint32_t) aq_ta->aqt_nxt_idx,
/* RT_EDB_TA_R_TRD */
	/* mail XXX this may exceed the record size??? */
		SM_RCBV_STR, RT_EDB_MAIL_PA,
			aq_ta->aqt_mail->aqm_pa,
		SM_RCBV_INT, RT_EDB_OWN_N, (uint32_t) aq_ta->aqt_owners_n,
		SM_RCBV_END);
	for (u = 0; SM_SUCCESS == ret && u < aq_ta->aqt_owners_n; u++) {
		ret = sm_rcb_putv(rcb, RCB_PUTV_NONE,
			SM_RCBV_STR, RT_EDB_OWN_PA, aq_ta->aqt_owners_pa[u],
			SM_RCBV_END);
	}
	ret = edb_hdrmodl_wr(aq_ta->aqt_hdrmodhd, rcb);
	(void) sm_rcb_close_enc(rcb);
	if (sm_is_err(ret))
		goto error;
	SESSTA_COPY(edb_req->edb_req_id, aq_ta->aqt_ss_ta_id);
	edb_req->edb_req_type = EDB_REQ_TA;
	if (NULL == edb_req_hd)
		EDBREQL_APP(&edb_ctx->edb_reql_wr, edb_req);
	else
		EDBREQL_APP(edb_req_hd, edb_req);
	r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_EDB, r);
	return ret;

  error:
	r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_EDB, r);
	if (edb_req != NULL) {
		if (sm_is_err(ret) && sm_error_value(ret) == ENOMEM)
			(void) edb_req_free(edb_req);
		else
			(void) edb_req_rel(edb_ctx, edb_req, 0, THR_NO_LOCK);
	}

	/* cleanup? */
	return ret;
}

/*
**  EDB_RCPT_APP -- append recipient (status) to request list
**
**	Parameters:
**		edb_ctx -- EDB context
**		aq_rcpt -- recipient data
**		edb_req_hd -- EDB request list (used if not NULL)
**		status -- recipient status
**
**	Returns:
**		usual sm_error code; ENOMEM, EINVAL, SM_E_OVFLW_SC,
**			SM_E_OVFLW_NS, etc
**
**	Side Effects: none on error (except if unlock fails)
**		if ok: appends new rcb to edb_req_hd or edb_ctx list
**
**	Locking:
**		locks edb_ctx during operation
**
**	Last code review: 2005-03-23 22:56:17
**	Last code change:
*/

sm_ret_T
edb_rcpt_app(edb_ctx_P edb_ctx, aq_rcpt_P aq_rcpt, edb_req_hd_P edb_req_hd, int status)
{
	edb_req_P edb_req;
	sm_rcb_P rcb;
	sm_ret_T ret;
	int r;

	SM_IS_EDB_CTX(edb_ctx);
	SM_IS_AQ_RCPT(aq_rcpt);

	r = pthread_mutex_lock(&edb_ctx->edb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_EDB, r);
	}

	edb_req = NULL;
	ret = edb_req_new(edb_ctx, EDB_RQF_NONE, &edb_req, THR_NO_LOCK);
	if (sm_is_err(ret))
		goto error;
	rcb = edb_req->edb_req_rcb;
	ret = sm_rcb_open_enc(rcb, -1);
	if (sm_is_err(ret))
		goto error;
	ret = sm_rcb_putv(rcb, RCB_PUTV_FIRST,
	/* recipient status */
		SM_RCBV_INT, RT_EDBR_ST, status,
	/* recipient index */
		SM_RCBV_INT, RT_EDBR_IDX, aq_rcpt->aqr_idx,
	/* reference to owner */
		SM_RCBV_INT, RT_EDBR_OWN, aq_rcpt->aqr_owner_idx,
	/* transaction ID */
		SM_RCBV_BUF, RT_EDBR_TAID,
			(uchar *) aq_rcpt->aqr_ss_ta_id,
			SMTP_STID_SIZE,

	/* aq_rcpt XXX this may exceed the record size??? put strs at end? */
		SM_RCBV_STR, RT_EDBR_PA, aq_rcpt->aqr_pa,

		SM_RCBV_INT, RT_EDBR_TRIES, aq_rcpt->aqr_tries,
		SM_RCBV_INT, RT_EDBR_ST_TI, aq_rcpt->aqr_st_time,
		SM_RCBV_INT, RT_EDBR_LA_TI, aq_rcpt->aqr_last_try,
		SM_RCBV_INT, RT_EDBR_NX_TI, aq_rcpt->aqr_next_try,
		SM_RCBV_INT, RT_EDBR_FL,
			(aq_rcpt->aqr_flags & AQR_FL_MASK) | AQR_FL_DEFEDB,
		SM_RCBV_INT, RT_EDBR_DA, aq_rcpt->aqr_da_idx,
		SM_RCBV_INT,
			(0 == aq_rcpt->aqr_port)
				? RT_NOSEND : RT_EDBR_PORT,
			(uint32_t) aq_rcpt->aqr_port,
		SM_RCBV_END);

	/*
	**  Resolved address... XXX needs more details
	**  Note: this depends on whether QMGR does DNS lookups (MX, A).
	**  In that case only the "resolved address" from SMAR must be
	**  written to DEFEDB.
	*/

	if (!sm_is_err(ret)) {
		uint u;

		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_ADDRS, aq_rcpt->aqr_addr_max);
		if (!sm_is_err(ret) && aq_rcpt->aqr_addrs != NULL) {
			for (u = 0; u < aq_rcpt->aqr_addr_max && !sm_is_err(ret); ++u) {
				ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_IPV4,
					aq_rcpt->aqr_addrs[u].aqra_ipv4);
				if (sm_is_err(ret))
					break;
				ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_EXPT,
					aq_rcpt->aqr_addrs[u].aqra_expt);
				if (sm_is_err(ret))
					break;
				ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_PREF,
					(uint32_t) aq_rcpt->aqr_addrs[u].aqra_pref);
			}
		}
	}

	if (!sm_is_err(ret))
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_DSNFL, aq_rcpt->aqr_dsn_flags);

	if (!sm_is_err(ret) && aq_rcpt->aqr_msg != NULL) {
		/* recipient status text */
		ret = sm_rcb_put2uint32(rcb, sm_str_getlen(aq_rcpt->aqr_msg),
					RT_EDBR_STT);
		if (!sm_is_err(ret))
			ret = sm_rcb_putn(rcb, sm_str_data(aq_rcpt->aqr_msg),
				sm_str_getlen(aq_rcpt->aqr_msg));
	}

	if (!sm_is_err(ret) && aq_rcpt->aqr_err_st != 0) {
		/* error state */
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_ERR_ST, aq_rcpt->aqr_err_st);
	}

	if (!sm_is_err(ret) && aq_rcpt->aqr_addr_fail != 0) {
		/* contacted host */
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_F_IPV4, aq_rcpt->aqr_addr_fail);
	}

	if (!sm_is_err(ret) && aq_rcpt_is_alias(aq_rcpt)) {
		/* recipient index of original address */
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_IDX_A, aq_rcpt->aqr_alias_idx);
	}

	if (!sm_is_err(ret) && aq_rcpt_has_bounce(aq_rcpt)) {
		/* bounce recipient index */
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_IDX_B, aq_rcpt->aqr_dsn_idx);
	}

	if (!sm_is_err(ret) && aq_rcpt_has_delay(aq_rcpt)) {
		/* delay recipient index */
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_IDX_D, aq_rcpt->aqr_dly_idx);
	}

	if (!sm_is_err(ret) && aq_rcpt->aqr_dsn_msg != NULL) {
		/* bounce message (for bounce recipient) */
		ret = sm_rcb_put2uint32(rcb, sm_str_getlen(aq_rcpt->aqr_dsn_msg),
					RT_EDBR_B_MSG);
		if (!sm_is_err(ret))
			ret = sm_rcb_putn(rcb, sm_str_data(aq_rcpt->aqr_dsn_msg),
				sm_str_getlen(aq_rcpt->aqr_dsn_msg));
	}

#if MTA_USE_TLS
	if (!sm_is_err(ret) && aq_rcpt->aqr_conf != NULL) {
		ret = sm_rcb_put3uint32(rcb, 4, RT_EDBR_MAP_RES_CNF_RCPT,
				aq_rcpt->aqr_maprescnf);
		ret = sm_rcb_put2uint32(rcb, sm_str_getlen(aq_rcpt->aqr_conf),
					RT_EDBR_RHS_CNF_RCPT);
		if (!sm_is_err(ret))
			ret = sm_rcb_putn(rcb, sm_str_data(aq_rcpt->aqr_conf),
				sm_str_getlen(aq_rcpt->aqr_conf));
	}
#endif /* MTA_USE_TLS */

	(void) sm_rcb_close_enc(rcb);
	if (sm_is_err(ret))
		goto error;
	SESSTA_CLR(edb_req->edb_req_id);
	sm_snprintf(edb_req->edb_req_id, SMTP_RCPTID_SIZE,
			SMTP_RCPTID_FORMAT, aq_rcpt->aqr_ss_ta_id, aq_rcpt->aqr_idx);
	edb_req->edb_req_type = EDB_REQ_RCPT;
	if (NULL == edb_req_hd)
		EDBREQL_APP(&edb_ctx->edb_reql_wr, edb_req);
	else
		EDBREQL_APP(edb_req_hd, edb_req);
	r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_EDB, r);
	return ret;

  error:
	r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_EDB, r);
	if (edb_req != NULL) {
		if (sm_is_err(ret) && sm_error_value(ret) == ENOMEM)
			(void) edb_req_free(edb_req);
		else
			(void) edb_req_rel(edb_ctx, edb_req, 0, THR_NO_LOCK);
	}

	/* more cleanup? */
	return ret;
}

/*
**  EDB_WR_STATUS -- write status (request list)
**
**	Parameters:
**		edb_ctx -- EDB context
**		edb_req_hd -- EDB request list (used if not NULL)
**
**	Returns:
**		usual sm_error code; DB errors,
**
**	Side Effects: none on error (except if unlock or BDB transaction fails)
**
**	Locking:
**		locks edb_ctx during operation
**
**	Last code review: 2005-03-23 23:25:14
**	Last code change: 2005-03-23 23:16:05
*/

sm_ret_T
edb_wr_status(edb_ctx_P edb_ctx, edb_req_hd_P edb_req_hd)
{
	sm_ret_T ret;
	int r;
	uint fct_state;
	bool delete;
	edb_req_P edb_req;
	sm_rcb_P rcb;
	DBT db_key, db_data;
	DB_TXN *db_txn;

/* function state flags */
#define FST_EDB_CTX_LCK	0x01	/* edb_ctx is locked */
#define FST_REQ_TYPE	0x02	/* bogus request type: abort */

	SM_IS_EDB_CTX(edb_ctx);
	fct_state = 0;
	edb_req = NULL;
	ret = SM_SUCCESS;

	if (edb_req_hd != NULL && EDBREQL_EMPTY(edb_req_hd))
		return SM_SUCCESS;
	if (NULL == edb_req_hd) {
		r = pthread_mutex_lock(&edb_ctx->edb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			ret = sm_error_perm(SM_EM_EDB, r);
			goto error;
		}
		SM_SET_FLAG(fct_state, FST_EDB_CTX_LCK);
		edb_req_hd = &edb_ctx->edb_reql_wr;
	}
	if (EDBREQL_EMPTY(edb_req_hd))
		goto done;

	if (!SM_IS_FLAG(fct_state, FST_EDB_CTX_LCK)) {
		r = pthread_mutex_lock(&edb_ctx->edb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			ret = sm_error_perm(SM_EM_EDB, r);
			goto error;
		}
		SM_SET_FLAG(fct_state, FST_EDB_CTX_LCK);
	}

	db_txn = NULL;
	r = edb_ctx->edb_bdbenv->txn_begin(edb_ctx->edb_bdbenv, NULL, &db_txn, 0);
	if (r != 0 || NULL == db_txn) {
		ret = sm_error_perm(SM_EM_EDB, r == 0 ? SM_E_UNEXPECTED : r);
		goto error;
	}

	/* walk through request list and write them to DEFEDB */
	for (edb_req = EDBREQL_FIRST(edb_req_hd);
	     edb_req != EDBREQL_END(edb_req_hd);
	     edb_req = EDBREQL_NEXT(edb_req))
	{
		rcb = edb_req->edb_req_rcb;
		sm_memzero(&db_key, sizeof(db_key));
		sm_memzero(&db_data, sizeof(db_data));
		delete = false;
		db_key.data = edb_req->edb_req_id;

		/* Check type */
		switch (edb_req->edb_req_type) {
		   case EDB_REQ_RCPT_DEL:
			delete = true;
			/* FALLTHROUGH */
		   case EDB_REQ_RCPT:
			db_key.size = SMTP_RCPTID_SIZE;
			break;
		   case EDB_REQ_TA_DEL:
			delete = true;
			/* FALLTHROUGH */
		   case EDB_REQ_TA:
			db_key.size = SMTP_STID_SIZE;
			break;
		   default:
			ret = sm_error_perm(SM_EM_EDB, SM_E_UNEXPECTED);
			SM_SET_FLAG(fct_state, FST_REQ_TYPE);
			(void) db_txn->abort(db_txn);
			goto error;
		}
		if (delete)
			r = edb_ctx->edb_bdb->del(edb_ctx->edb_bdb, db_txn, &db_key, 0);
		else {
			db_data.data = sm_rcb_data(rcb);
			db_data.size = sm_rcb_getlen(rcb);
			r = edb_ctx->edb_bdb->put(edb_ctx->edb_bdb, db_txn, &db_key, &db_data, 0);
		}

		if (r != 0) {
			EDB_DPRINTF((smioerr, "sev=ERROR, func=edb_wr_status, delete=%d, op=%d, key=%s\n", delete, r, (char *) db_key.data));
			ret = sm_error_perm(SM_EM_EDB, r);
			break;
		}
		else
			++edb_ctx->edb_writes;
	}
	if (sm_is_err(ret)) {
		/* check result? */
		(void) db_txn->abort(db_txn);
		goto error;
	}
	r = db_txn->commit(db_txn, 0);
	db_txn = NULL;
	if (r != 0) {
		ret = sm_error_perm(SM_EM_EDB, r);
		goto error;
	}
#if 0
	else {
		/* currently not used anywhere */
		/* better way to get time? */
		edb_ctx->edb_last_write = time(NULLT);
	}
#endif /* 0 */

	/* Everything is ok: remove requests from list */
	while (!EDBREQL_EMPTY(edb_req_hd)) {
		edb_req = EDBREQL_FIRST(edb_req_hd);
		EDBREQL_REMOVE(edb_req_hd);
		(void) edb_req_rel(edb_ctx, edb_req, 0, THR_NO_LOCK);
		/* always ok */
	}

	/* Fall through for unlocking etc */

  done:
	edb_req = NULL;
  error:
	if (SM_IS_FLAG(fct_state, FST_EDB_CTX_LCK)) {
		r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_EDB, r);
		SM_CLR_FLAG(fct_state, FST_EDB_CTX_LCK);
	}
	if (SM_IS_FLAG(fct_state, FST_REQ_TYPE)) {
		sm_abort("wrong edb_req_type %u",
			edb_req != NULL ? edb_req->edb_req_type : UINT_MAX);
	}
	return ret;
}

/*
**  EDB_HDRMODL_RD -- read header modification list
**
**	Parameters:
**		sm_hdrmodhd -- header of header modification list
**		rcb -- RCB
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
edb_hdrmodl_rd(sm_hdrmodhd_P sm_hdrmodhd, sm_rcb_P rcb)
{
	return sm_hdrmodl_rd(sm_hdrmodhd, rcb, RT_EDB_HM_T_P, RT_EDB_HM_HDR);
}

/*
**  EDB_TA_DEC -- decode edb_req into transaction structure
**
**	Parameters:
**		edb_req -- request: contains data to decode
**		aq_ta -- TA data (must exist, will be populated) (output)
**
**	Returns:
**		usual sm_error code; ENOMEM, EINVAL (protocol errors)
**
**	Side Effects: none on error
**		allocates: aqt_cdb_id, aqt_mail->aqm_pa,
**		maybe: aqt_owners_pa and strings in that array
**
**	Last code review: 2005-03-18 23:57:11
**	Last code change:
*/

sm_ret_T
edb_ta_dec(edb_req_P edb_req, aq_ta_P aq_ta)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	uint u;
	uint32_t v, l, rt, tl;
	off_t off;
	size_t owners_size;
	sm_str_P *owners_pa;

	SM_ASSERT(edb_req != NULL);
	SM_IS_AQ_TA(aq_ta);

	rcb = edb_req->edb_req_rcb;
	(void) sm_rcb_open_dec(rcb);	/* OK */
	owners_pa = NULL;
	owners_size = 0;

	/* Total length of record */
	ret = sm_rcb_getuint32(rcb, &tl);
	if (sm_is_err(ret) || tl > EDB_RC_MAXSZ || tl > sm_rcb_getlen(rcb))
		goto error;

	/* transaction status */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_ST)
		goto error;
	aq_ta->aqt_state = v;

	/* CDB ID */
	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret) || l >= tl || rt != RT_EDB_CDBID)
		goto error;
	ret = sm_rcb_getncstr(rcb, &aq_ta->aqt_cdb_id, l);
	if (sm_is_err(ret))
		goto error;

	ret = sm_rcb_get3off_t(rcb, &l, &rt, &off);
	if (sm_is_err(ret) || l != SIZEOF_OFF_T || rt != RT_EDB_SIZE_B)
		goto error;
	aq_ta->aqt_msg_sz_b = off;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_ST_TI)
		goto error;
	aq_ta->aqt_st_time = v;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_FL)
		goto error;
	aq_ta->aqt_flags = v;

	/* nrcpts */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_TOT)
		goto error;
	aq_ta->aqt_rcpts_tot = v;

	/* rcpts_left */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_LEFT)
		goto error;
	aq_ta->aqt_rcpts_left = v;

	/* rcpts_temp */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_TEMP)
		goto error;
	aq_ta->aqt_rcpts_temp = v;

	/* rcpts_perm */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_PERM)
		goto error;
	aq_ta->aqt_rcpts_perm = v;

	/* rcpts_tried */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_TRIED)
		goto error;
	aq_ta->aqt_rcpts_tried = v;

	/* next rcpt idx */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_TA_R_NXT)
		goto error;
	aq_ta->aqt_nxt_idx = v;

	/* mail_pa */
	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret) || l >= tl || rt != RT_EDB_MAIL_PA)
		goto error;
	ret = sm_rcb_getnstr(rcb, &aq_ta->aqt_mail->aqm_pa, l);
	if (sm_is_err(ret))
		goto error;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDB_OWN_N)
		goto error;
	aq_ta->aqt_owners_n = v;
	if (v > 0) {
		owners_size = aq_ta->aqt_owners_n * sizeof(*owners_pa);
		if (owners_size < aq_ta->aqt_owners_n) {
			ret = sm_error_perm(SM_EM_EDB, SM_E_OVFLW_SC);
			goto error;
		}
		owners_pa = sm_zalloc(owners_size);
		if (NULL == owners_pa) {
			ret = sm_error_temp(SM_EM_EDB, ENOMEM);
			goto error;
		}
		for (u = 0; SM_SUCCESS == ret && u < aq_ta->aqt_owners_n; u++) {
			ret = sm_rcb_get2uint32(rcb, &l, &rt);
			if (sm_is_err(ret) || l >= tl || rt != RT_EDB_OWN_PA)
				goto error;
			ret = sm_rcb_getnstr(rcb, &owners_pa[u], l);
			if (sm_is_err(ret))
				goto error;
		}
		aq_ta->aqt_owners_pa = owners_pa;
		owners_pa = NULL;
	}
	ret = edb_hdrmodl_rd(aq_ta->aqt_hdrmodhd, rcb);

	(void) sm_rcb_close_dec(rcb);
	if (sm_is_err(ret))
		goto error;
	SESSTA_COPY(aq_ta->aqt_ss_ta_id, edb_req->edb_req_id);
	return ret;

  error:
	if (!sm_is_err(ret))
		ret = sm_error_perm(SM_EM_EDB, EINVAL);
	(void) sm_rcb_close_decn(rcb);

	/* cleanup? */
	if (owners_pa != NULL) {
		for (u = 0; u < aq_ta->aqt_owners_n; u++)
			SM_STR_FREE(owners_pa[u]);
		SM_ASSERT(owners_size > 0);
		SM_FREE_SIZE(owners_pa, owners_size);
	}
	SM_STR_FREE(aq_ta->aqt_mail->aqm_pa);
	SM_CSTR_FREE(aq_ta->aqt_cdb_id);
	return ret;
}

/*
**  EDB_RCPT_DEC -- decode edb_req into recipient structure
**
**	Parameters:
**		edb_req -- request: contains data to decode
**		aq_rcpt -- RCPT data (must exist, will be populated) (output)
**
**	Returns:
**		usual sm_error code; ENOMEM, EINVAL (protocol errors)
**
**	Side Effects: some scalars in aq_rcpt might be set
**		if ok: allocates: aqr_pa, aqr_addrs, maybe: aqr_dsn_msg, aqr_msg
**
**	Last code review: 2005-03-23 23:40:23
**	Last code change: 2005-03-18 18:46:17
*/

sm_ret_T
edb_rcpt_dec(edb_req_P edb_req, aq_rcpt_P aq_rcpt)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	uint32_t v, l, rt, tl, naddrs;
	uint u;
	bool expired;
	time_T now;
	size_t addrs_size;

	SM_ASSERT(edb_req != NULL);
	SM_IS_AQ_RCPT(aq_rcpt);

	now = time(NULLT);	/* better way to get time? */
	addrs_size = 0;
	rcb = edb_req->edb_req_rcb;
	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 > EDB_RC_MAXSZ || tl > sm_rcb_getlen(rcb))
		goto error;

	/* recipient status */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_ST)
		goto error;
	aq_rcpt->aqr_status = v;

	/* recipient index */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_IDX)
		goto error;
	aq_rcpt->aqr_idx = v;	/* XXX check with rcpt_id? */

	/* reference to owner */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_OWN)
		goto error;
	aq_rcpt->aqr_owner_idx = v;

	/* transaction ID */
	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret) || l != SMTP_STID_SIZE || rt != RT_EDBR_TAID)
		goto error;
	ret = sm_rcb_getn(rcb, (uchar *) aq_rcpt->aqr_ss_ta_id, l);
	if (sm_is_err(ret))
		goto error;

	/* aq_rcpt_pa */
	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret) || l >= tl || rt != RT_EDBR_PA)
		goto error;
	ret = sm_rcb_getnstr(rcb, &aq_rcpt->aqr_pa, l);
	if (sm_is_err(ret))
		goto error;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_TRIES)
		goto error;
	aq_rcpt->aqr_tries = v;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_ST_TI)
		goto error;
	aq_rcpt->aqr_st_time = v;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_LA_TI)
		goto error;
	aq_rcpt->aqr_last_try = v;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_NX_TI)
		goto error;
	aq_rcpt->aqr_next_try = v;

	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_FL)
		goto error;
	aq_rcpt->aqr_flags = v;

	/* optional: RT_EDBR_DA */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4)
		goto error;
	if (RT_EDBR_DA == rt) {
		aq_rcpt->aqr_da_idx = v;
		ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	}

	if (RT_EDBR_PORT == rt) {
		aq_rcpt->aqr_port = (short) v;
		ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	}

	/* resolved address... needs more details?? which? */
	if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_ADDRS)
		goto error;
	naddrs = v;
	aq_rcpt->aqr_addr_cur = 0; /* XXX (why??) */
	if (0 == naddrs) {
		aq_rcpt->aqr_addr_max = 0;
		aq_rcpt->aqr_addrs = NULL;
	}
	else {
		addrs_size = naddrs * sizeof(*(aq_rcpt->aqr_addrs));
		if (addrs_size < naddrs) {
			ret = sm_error_perm(SM_EM_EDB, SM_E_OVFLW_SC);
			goto error;
		}
		aq_rcpt->aqr_addrs = (aq_raddr_P) sm_malloc(addrs_size);
		if (NULL == aq_rcpt->aqr_addrs) {
			AQR_SET_FLAG(aq_rcpt, AQR_FL_MEMAR);
			aq_rcpt->aqr_addr_max = 1;
			aq_rcpt->aqr_addrs = &aq_rcpt->aqr_addr_mf;
			addrs_size = 0;
		}

		expired = false;
		for (u = 0; u < naddrs; u++) {
			ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
			if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_IPV4)
				goto error;
			if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR) || u == 0)
				aq_rcpt->aqr_addrs[u].aqra_ipv4 = v;
			ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
			if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_EXPT)
				goto error;
			if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR) || u == 0) {
				aq_rcpt->aqr_addrs[u].aqra_expt = v;
				if (!expired)
					expired = v < now;
			}
			ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
			if (sm_is_err(ret) || l != 4 || rt != RT_EDBR_PREF)
				goto error;
			if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR) || u == 0)
				aq_rcpt->aqr_addrs[u].aqra_pref = v;
		}

		/* is one of the addresses expired? */
		if (expired) {
			/* yes: just ignore everything */
			if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR))
				sm_free_size(aq_rcpt->aqr_addrs, addrs_size);
			aq_rcpt->aqr_addr_max = 0;
			aq_rcpt->aqr_addrs = NULL;
			addrs_size = 0;
		}
		else if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_MEMAR)) {
			aq_rcpt->aqr_addr_max = naddrs;
			if (naddrs > 0)
				AQR_SET_FLAG(aq_rcpt,
					AQR_FL_RCVD4AR|AQR_FL_RDY4DLVRY);
		}
	}

	while (!SM_RCB_ISEOB(rcb)) {
		ret = sm_rcb_get2uint32(rcb, &l, &rt);
		if (sm_is_err(ret))
			goto error;
		switch (rt) {
		  case RT_EDBR_DSNFL:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_dsn_flags = v;
			break;

		  case RT_EDBR_B_MSG:
			if (l >= tl)
				goto error;
			ret = sm_rcb_getnstr(rcb, &aq_rcpt->aqr_dsn_msg, l);
			if (sm_is_err(ret))
				goto error;
			break;

		  case RT_EDBR_STT:
			if (l >= tl)
				goto error;
			ret = sm_rcb_getnstr(rcb, &aq_rcpt->aqr_msg, l);
			if (sm_is_err(ret))
				goto error;
			break;

		  case RT_EDBR_ERR_ST:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_err_st = v;
			break;

		  case RT_EDBR_F_IPV4:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_addr_fail = v;
			break;

		  case RT_EDBR_IDX_A:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_alias_idx = v;
			break;

		  case RT_EDBR_IDX_B:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_dsn_idx = v;
			break;

		  case RT_EDBR_IDX_D:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_dly_idx = v;
			break;

#if MTA_USE_TLS
		  case RT_EDBR_MAP_RES_CNF_RCPT:
			if (l != 4)
				goto error;
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret))
				goto error;
			aq_rcpt->aqr_maprescnf = v;
			break;

		  case RT_EDBR_RHS_CNF_RCPT:
			if (l >= tl)
				goto error;
			ret = sm_rcb_getnstr(rcb, &aq_rcpt->aqr_conf, l);
			if (sm_is_err(ret))
				goto error;
			break;
#endif /* MTA_USE_TLS */

		  default:
			/* COMPLAIN */
			goto error;
			break;
		}
	}

	(void) sm_rcb_close_dec(rcb);
	if (sm_is_err(ret))
		goto error;
	return ret;

  error:
	if (!sm_is_err(ret))
		ret = sm_error_perm(SM_EM_EDB, EINVAL);
	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_SIZE(aq_rcpt->aqr_addrs, addrs_size);
	aq_rcpt->aqr_addrs = NULL;
	SM_STR_FREE(aq_rcpt->aqr_pa);
	SM_STR_FREE(aq_rcpt->aqr_dsn_msg);
	SM_STR_FREE(aq_rcpt->aqr_msg);
	(void) sm_rcb_close_decn(rcb);

	/* cleanup? */
	return ret;
}

/*
**  EDB_RD_REQ -- read one entry from EDB
**
**	Parameters:
**		edb_ctx -- EDB context
**		edb_req -- request: contains key to read, rcb to fill (I/O)
**			edb_req_type and edb_req_id must be set.
**
**	Returns:
**		usual sm_error code; BDB error
**
**	Side Effects: writes data into rcb of edb_req (maybe even on error)
**
**	Locking:
**		locks edb_ctx during operation
**
**	Last code review: 2005-03-18 05:46:49
**	Last code change:
*/

sm_ret_T
edb_rd_req(edb_ctx_P edb_ctx, edb_req_P edb_req)
{
	sm_rcb_P rcb;
	sm_ret_T ret;
	int r;
	size_t l;
	DBT db_key, db_data;
	DB_TXN *db_txn;

	SM_IS_EDB_CTX(edb_ctx);
	SM_REQUIRE(edb_req != NULL);
	r = pthread_mutex_lock(&edb_ctx->edb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_EDB, r);
	}

	/* Should this be a transaction?? Check BDB doc */
	db_txn = NULL;
	ret = SM_SUCCESS;
	rcb = edb_req->edb_req_rcb;
	sm_memzero(&db_key, sizeof(db_key));
	sm_memzero(&db_data, sizeof(db_data));

	switch (edb_req->edb_req_type) {
	   case EDB_REQ_RCPT:
		db_key.data = edb_req->edb_req_id;
		db_key.size = SMTP_RCPTID_SIZE;
		break;
	   case EDB_REQ_TA:
		db_key.data = edb_req->edb_req_id;
		db_key.size = SMTP_STID_SIZE;
		break;
	   default:
		sm_abort("edb_rd_req: unknown edb_req_type %d", edb_req->edb_req_type);
		break;
	}
	db_data.flags = DB_DBT_USERMEM;
	db_data.data = sm_rcb_data(rcb);
	db_data.ulen = sm_rcb_getsize(rcb);

	ret = SM_SUCCESS;
	r = edb_ctx->edb_bdb->get(edb_ctx->edb_bdb, db_txn, &db_key, &db_data, 0);
	l = db_data.size;
	if (DB_BUFFER_SMALL == r && l < EDB_RC_MAXSZ) {
		l = (l + 1023) & ~1023;	/* round to 1024; see BDB docs */
		if (!sm_is_err(sm_rcb_resize_data(rcb, l))) {
			db_data.data = sm_rcb_data(rcb);
			db_data.ulen = sm_rcb_getsize(rcb);
			r = edb_ctx->edb_bdb->get(edb_ctx->edb_bdb, db_txn,
						&db_key, &db_data, 0);
			l = db_data.size;
		}
	}
	if (r != 0)
		ret = sm_error_perm(SM_EM_EDB, r);
	else if (l < sm_rcb_getmax(rcb))
		sm_rcb_setlen(edb_req->edb_req_rcb, db_data.size);

	r = pthread_mutex_unlock(&edb_ctx->edb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_EDB, r);
	return ret;
}

/*
**  EDB_CLOSE -- close edb
**
**	Parameters:
**		edb_ctx -- edb context
**
**	Returns:
**		usual sm_error code; BDB error
**
**	Last code review: 2005-03-23 23:45:55
**	Last code change: 2005-03-23 23:45:39
*/

sm_ret_T
edb_close(edb_ctx_P edb_ctx)
{
	sm_ret_T ret;
	int r;

	if (NULL == edb_ctx)
		return SM_SUCCESS;
	ret = edb_reqls_free(edb_ctx);	/* OK */
	(void) pthread_mutex_destroy(&edb_ctx->edb_mutex);

	/* Close DB */
	if (edb_ctx->edb_bdb != NULL) {
		r = edb_ctx->edb_bdb->close(edb_ctx->edb_bdb, 0);
		edb_ctx->edb_bdb = NULL;
		if (r != 0) {
			sm_log_write(edb_ctx->edb_lctx,
				EDB_LCAT_EDB, EDB_LMOD_EDB, SM_LOG_ERR, 4,
				"sev=ERROR, func=edb_close, db->close=%s",
				db_strerror(r));
			ret = sm_error_perm(SM_EM_EDB, r);
		}
	}

	/* Close DB Environment */
	if  (edb_ctx->edb_bdbenv != NULL) {
		r = edb_ctx->edb_bdbenv->close(edb_ctx->edb_bdbenv, 0);
		edb_ctx->edb_bdbenv = NULL;
		if (r != 0) {
			sm_log_write(edb_ctx->edb_lctx,
				EDB_LCAT_EDB, EDB_LMOD_EDB, SM_LOG_ERR, 4,
				"sev=ERROR, func=edb_close, dbenv->close=%s",
				db_strerror(r));
			ret = sm_error_perm(SM_EM_EDB, r);
		}
	}
	(void) edb_ctx_free(edb_ctx);	/* OK */
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1