/*
 * 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.
 *
 *  $Id: qmgr-int.h,v 1.215 2007/05/05 04:52:30 ca Exp $
 */

#ifndef SM_QMGR_INT_H
#define SM_QMGR_INT_H 1

#include "sm/generic.h"
#include "sm/magic.h"
#include "sm/pthread.h"
#include "sm/note.h"
#include "sm/heap.h"
#include "sm/net.h"
#include "sm/io.h"
#include "sm/evthr.h"
#include "sm/rcb.h"
#include "sm/rcbl.h"
#include "sm/rcbcomm.h"
#include "sm/iqdb.h"
#include "sm/mta.h"
#include "sm/ibdb.h"
#include "sm/qmgrcto.h"
#include "sm/qmgrcomm.h"
#include "sm/qmgr.h"
#include "sm/cdb.h"
#include "sm/actdb-int.h"
#include "sm/dadb.h"
#include "sm/edb.h"
#include "sm/edbc.h"
#include "sm/ssoccstr.h"
#include "sm/occstr.h"
#include "sm/log.h"
#include "sm/qmgrdef.h"
#include "sm/qmgrdbg.h"
#include "sm/qmgrcnf.h"
#include "sm/fs.h"
#include "sm/cdbfs.h"
#include "sm/edbfs.h"
#include "sm/maps.h"
#include "sm/map.h"

#if SM_HEAP_CHECK
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
#else
# define HEAP_CHECK 0
#endif

/* abstraction layer for QMGR */

#if 0

incedb_open(IN name, IN mode, IN size, OUT status, OUT incedb-handle)
open an incoming envelope database.

incedb_close(IN incedb-handle, OUT status)
close an incoming envelope database.

incedb_session_new(IN incedb-handle, IN session-info, OUT status, OUT session-id):
create a new session (client connects succesfully).

incedb_trans_new(IN incedb-handle, IN sender-env-info, OUT status, OUT trans-id):
create a new envelope (done on MAIL command).

incedb_rcpt_add(IN incedb-handle, IN trans-id, IN rcpt-env-info, OUT status):
add a new recipient (RCPT command).

incedb_rcpt_rm(IN incedb-handle, IN trans-id, IN rcpt-id, IN rpct-status,
OUT status):
remove a recipient (mail has been taken care of).
This includes a recipient status,
e.g., successfully delivered, some kind of failure,
and hence the recipient ended up in another queue.
The caller will take care of DSN handling or putting the entry into
another queue if necessary.

incedb_trans_rm(IN incedb-handle, IN trans-id, OUT status):
remove an envelope from the EDB (after all recipients have been taken care of).
Should this be done implicitly when all recipients have been removed?

incedb_commit(IN incedb-handle, IN trans-id, OUT status):
commit envelope information to stable storage.

incedb_session_rm(IN incedb-handle, IN session-id, OUT status):
remove a session from the EDB.

incedb_trans_discard(IN incedb-handle, IN trans-id, OUT status):
discard envelope (connection has been aborted somehow:
RSET, EHLO, connection error on lower level).


incedb_items_cache(IN incedb-handle, OUT items, OUT status):
return the number of items in the memory cache.

incedb_readprep(IN incedb-handle, OUT cursor, OUT status):
prepare to read through EDB.

incedb_getnext(IN cursor, OUT status, OUT record):
Get next entry from EDB.

incedb_readclose(IN incedb-handle, IN cursor, OUT status):
stop reading through EDB.

#endif /* 0 */

#define SM_IS_QMGR_CTX(qmgr_ctx) SM_REQUIRE_ISA((qmgr_ctx), SM_QMGR_CTX_MAGIC)
#define SM_IS_QSS_CTX(qss_ctx)  SM_REQUIRE_ISA((qss_ctx), SM_QSS_CTX_MAGIC)
#define SM_IS_QSC_CTX(qsc_ctx)  SM_REQUIRE_ISA((qsc_ctx), SM_QSC_CTX_MAGIC)
#define SM_IS_QAR_CTX(qar_ctx)  SM_REQUIRE_ISA((qar_ctx), SM_QAR_CTX_MAGIC)

/* HACK .... */
#define MAX_GEN_LI_FD   32  /* must <= 32, see qm_gli_used */

/* used to be: SM_ARRAY_SIZE((qmgr_ctx)->qmgr_gli[QM_SMTPS_GLI].qm_gli_ctx) */
#define QM_N_GLI(qmgr_ctx)      MAX_GEN_LI_FD
#define QM_N_SS_GLI(qmgr_ctx)   MAX_GEN_LI_FD
#define QM_N_SC_GLI(qmgr_ctx)   MAX_GEN_LI_FD

extern cdb_id_P null_cdb_id;

/* QMGR resource flags */
#define QMGR_RFL_NONE   0x00000000U

/*
**  Slow down due to ...
**  Notice: this is used in two ways:
**  1. it's the shift value (1 << FL_x_I)
**  2. it's the index into arrays that contain limits, current values, etc.
**  see qmgr_ctx: qmgr_usage[], qmgr_lower[], qmgr_upper[]
*/

#define IDX2RFL(fl) ((uint32_t)1 << (fl))

#define QMGR_RFL_AQ_I   0u  /* AQ */
#define QMGR_RFL_IQD_I  1u  /* IQDB */
#define QMGR_RFL_IBD_I  2u  /* IBDB disk space */
#define QMGR_RFL_MEM_I  3u  /* MEM */
#define QMGR_RFL_CPU_I  4u  /* CPU */
#define QMGR_RFL_EBDC_I 5u  /* EDBC */
#define QMGR_RFL_AR_I   6u  /* AR */
#define QMGR_RFL_CDB_I  7u  /* CDB disk space */
#define QMGR_RFL_EDB_I  8u  /* EDB disk space */
#define QMGR_RFL_LAST_I 8u  /* last index */
/* ... more? */

#define QMGR_RFL_AQ     IDX2RFL(QMGR_RFL_AQ_I)
#define QMGR_RFL_IQD    IDX2RFL(QMGR_RFL_IQD_I)
#define QMGR_RFL_IBD    IDX2RFL(QMGR_RFL_IBD_I)
#define QMGR_RFL_MEM    IDX2RFL(QMGR_RFL_MEM_I)
#define QMGR_RFL_CPU    IDX2RFL(QMGR_RFL_CPU_I)
#define QMGR_RFL_EBDC   IDX2RFL(QMGR_RFL_EBDC_I)
#define QMGR_RFL_AR     IDX2RFL(QMGR_RFL_AR_I)
#define QMGR_RFL_CDB    IDX2RFL(QMGR_RFL_CDB_I)
#define QMGR_RFL_EDB    IDX2RFL(QMGR_RFL_EDB_I)

/* all resources */
#define QMGR_RFL_ALL_RSR (QMGR_RFL_AQ|QMGR_RFL_IQD|QMGR_RFL_IBD|QMGR_RFL_MEM|QMGR_RFL_CPU|QMGR_RFL_EBDC|QMGR_RFL_AR|QMGR_RFL_CDB|QMGR_RFL_EDB)

#define QMGR_SET_RFLAG_I(qmgr_ctx, idx) (qmgr_ctx)->qmgr_rflags |= IDX2RFL(idx)
#define QMGR_CLR_RFLAG_I(qmgr_ctx, idx) (qmgr_ctx)->qmgr_rflags &= ~IDX2RFL(idx)
#define QMGR_IS_RFLAG_I(qmgr_ctx, idx) (((qmgr_ctx)->qmgr_rflags & IDX2RFL(idx)) != 0)

/* is any other resource (flag) than idx "used" (set)? */
#define QMGR_RFL_ANY_RSR_BUT_I(qmgr_ctx, idx) (((qmgr_ctx)->qmgr_rflags & (QMGR_RFL_ALL_RSR^IDX2RFL(idx))) != 0)
#define QMGR_RFL_IS_NO_RSR(qmgr_ctx) (((qmgr_ctx)->qmgr_rflags & QMGR_RFL_ALL_RSR) == 0)

#define QMGR_SET_RFLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_rflags |= (fl)
#define QMGR_CLR_RFLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_rflags &= ~(fl))
#define QMGR_IS_RFLAG(qmgr_ctx, fl) (((qmgr_ctx)->qmgr_rflags & (fl)) != 0)


#define QMGR_R_USE_NONE 0   /* resource is completely unused */
#define QMGR_R_USE_FULL 100 /* resource is completely used */

#define DISK_USAGE(x, qmgr_ctx) \
	(((x) >= (qmgr_ctx)->qmgr_cnf.q_cnf_ok_df) ? QMGR_R_USE_NONE :  \
	 (((x) <= (qmgr_ctx)->qmgr_cnf.q_cnf_min_df) ? QMGR_R_USE_FULL : \
	  ((((qmgr_ctx)->qmgr_cnf.q_cnf_ok_df - (x)) * 100) /  \
	   ((qmgr_ctx)->qmgr_cnf.q_cnf_ok_df - (qmgr_ctx)->qmgr_cnf.q_cnf_min_df))))

/* QMGR status flags */
#define QMGR_SFL_NONE   0x00000000U
#define QMGR_SFL_EDBC   0x00000001U /* EDBC is full (not checked anywhere?) */
#define QMGR_SFL_AR     0x00000002U /* AR is unavailable */
#define QMGR_SFL_DA     0x00000004U /* no DA available */
#define QMGR_SFL_HAD_DA 0x00000008U /* DA was once available */

#define QMGR_SET_SFLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_sflags |= (fl)
#define QMGR_CLR_SFLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_sflags &= ~(fl)
#define QMGR_IS_SFLAG(qmgr_ctx, fl) (((qmgr_ctx)->qmgr_sflags & (fl)) != 0)

/* XXX HACK XXX */
#define IBDB_NAME   "ibd"

#define QCNF_SET_FLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_cnf.q_cnf_flags |= (fl)
#define QCNF_CLR_FLAG(qmgr_ctx, fl) (qmgr_ctx)->qmgr_cnf.q_cnf_flags &= ~(fl)
#define QCNF_IS_FLAG(qmgr_ctx, fl) (((qmgr_ctx)->qmgr_cnf.q_cnf_flags & (fl)) != 0)

#if QMGR_TEST
/*
**  Values to trigger errors in certain places to test error handling.
**  Currently these are used as bitflags which allows for combining them.
**  Alternatively numbers might be used if there are many tests that don't
**  need to be combined.
*/

# define QMGR_TEST_BNC_FAIL     0x00000001  /* cause a bounce failure */

/* cause a failure if rcpt_status is 459 */
# define QMGR_TEST_RCPT_STAT    0x00000002
# define QMGR_TEST_RSR_ST       459

/* cause a failure in qda_upd_dsn() */
# define QMGR_TEST_UPD_DSN      0x00000004

/* cause a failure in qm_bounce_new() at aq_rcpt_add_new() */
# define QMGR_TEST_BNC_RCPT_ADD 0x00000008

/* cause a failure in qm_bounce_new() at allocating aqr_dsns */
# define QMGR_TEST_ALLOC_DSNS   0x00000010

/* cause an abort in qda_update_ta_stat() */
# define QMGR_TEST_UPD_ABORT    0x00000020

/* cause an abort in sched_dlvry() for a "delayed" DSN */
# define QMGR_TEST_DLY_SCHED    0x00000040

/* cause an error in q_store_ddsn() for a "delayed" DSN */
# define QMGR_TEST_DDSN         0x00000080

/* cause a communication error (reading) with smtpc */
# define QMGR_TEST_SC_RD        0x00000100

/* delay start of scheduler */
# define QMGR_TEST_SCHED_DLY    0x00000200

/* use internal source of transactions */
# define QMGR_TEST_INT_SRC      0x00000400
#endif

/* generic description for qmgr to maintain client data (smtps/smtpc) */
typedef sm_ret_T (qmgr_smtp_F)(sm_evthr_task_P _tsk);
struct qm_gli_S
{
	sm_magic_T      sm_magic;
	qmgr_ctx_P      qm_gli_qmgr_ctx;   /* pointer back to main ctx */
	int             qm_gli_lfd;    /* listen fd */
	int             qm_gli_nfd;    /* number of used fds */
	uint32_t        qm_gli_used;   /* bitmask for used elements */

	/* communication function to start as task */
	qmgr_smtp_F    *qm_gli_fct;

	/* client contexts to use for each connected client */
	qsg_ctx_P       qm_gli_ctx[MAX_GEN_LI_FD];
};

#define SM_IS_GLI(qm_gli)   SM_REQUIRE_ISA((qm_gli), SM_GLI_MAGIC)

/*
**  QMGR main context type
*/

struct qmgr_ctx_S
{
	sm_magic_T          sm_magic;
	pthread_mutex_t     qmgr_mutex;
	uint                qmgr_status;   /* see below, QMGR_ST_* */
	time_T              qmgr_st_time;  /* start time */

	/*
	**  XXX more status information?
	**  e.g., how full are the various queues, load, memory usage,
	**  have some subcomponents been slowed down, ...
	*/

	/* Resource flags */
	uint32_t            qmgr_rflags;   /* see QMGR_RFL_* */

	/* Overall value to indicate resource usage 0:free 100:overloaded */
	uint                qmgr_total_usage;

	/* Status flags */
	uint32_t            qmgr_sflags;   /* see QMGR_SFL_* */

	sm_str_P            qmgr_hostname;
	sm_str_P            qmgr_pm_addr;  /* <postmaster@hostname> */

	cdb_ctx_P           qmgr_cdb_ctx;

	/* info about connections? */

	fs_ctx_P            qmgr_fs_ctx;
	cdb_fsctx_P         qmgr_cdb_fsctx;
	ulong               qmgr_cdb_kbfree;
	edb_fsctx_P         qmgr_edb_fsctx;
	ulong               qmgr_edb_kbfree;
	ulong               qmgr_ibdb_kbfree;

	/* generic listeners (currently only for smtpc/smtps) */
	qm_gli_T            qmgr_gli[2];
#define QM_SMTPS_GLI    0
#define QM_SMTPC_GLI    1

#define qmgr_ss_li  qmgr_gli[QM_SMTPS_GLI]
#define qmgr_sc_li  qmgr_gli[QM_SMTPC_GLI]
#define qmgr_li_ss(qmgr_ctx, i) ((qss_ctx_P) (qmgr_ctx)->qmgr_ss_li.qm_gli_ctx[i])
#define qmgr_li_sc(qmgr_ctx, i) ((qsc_ctx_P) (qmgr_ctx)->qmgr_sc_li.qm_gli_ctx[i])
#define qmgr_li_ss_lv(qmgr_ctx, i) (qmgr_ctx)->qmgr_ss_li.qm_gli_ctx[i]
#define qmgr_li_sc_lv(qmgr_ctx, i) (qmgr_ctx)->qmgr_sc_li.qm_gli_ctx[i]

	id_count_T          qmgr_idc;  /* last used SMTP id counter */

	/* SMTPS */
	ssocc_ctx_P         qmgr_ssocc_ctx;

	/* SMTPC */
	occ_ctx_P           qmgr_occ_ctx;

	/* total number of DAs (delivery threads) */
	ulong               qmgr_max_da_threads;
	uint                qmgr_tmo_sched;
	time_T              qmgr_tm_no_da; /* last time of "no DA avail" */

	/* control socket */
	int                 qmgr_ctllfd;   /* listen fd */
	int                 qmgr_ctlfd;    /* control socket fd */
	rcbcom_ctx_T        qmgr_ctl_com;

	sm_evthr_ctx_P      qmgr_ev_ctx;   /* event thread context */

	iqdb_P              qmgr_iqdb; /* rsc for incoming edb */
	ibdb_ctx_P          qmgr_ibdb; /* backup for incoming edb */

	sm_evthr_task_P     qmgr_icommit;  /* task for ibdbc commits */
	qss_opta_P          qmgr_optas;    /* open transactions (commit) */

	sm_evthr_task_P     qmgr_sched;    /* scheduling task */
	aq_ctx_P            qmgr_aq;   /* active envelope db */
	edb_ctx_P           qmgr_edb;  /* deferred envelope db */
	edbc_ctx_P          qmgr_edbc; /* cache for envelope db */

	sm_evthr_task_P     qmgr_tsk_cleanup; /* task for cleanup */
	qcleanup_ctx_P      qmgr_cleanup_ctx;

	sm_maps_P           qmgr_maps; /* map system context */
	sm_map_P            qmgr_conf_map; /* "configuration" map */


	/* AR */
	sm_evthr_task_P     qmgr_ar_tsk;   /* address resolver task */
	int                 qmgr_ar_fd;    /* communication fd */
	qar_ctx_P           qmgr_ar_ctx;

	sm_rcbh_T           qmgr_rcbh; /* head for RCB list */
	uint                qmgr_rcbn; /* number of entries in RCB list */
	/* currently protected by qmgr_mutex */

	/* Maybe use _P instead? */
	qmgr_cnf_T          qmgr_cnf;

	sm_log_ctx_P        qmgr_lctx;
	sm_logconfig_P      qmgr_lcfg;

	uint8_t             qmgr_usage[QMGR_RFL_LAST_I + 1];
	uint8_t             qmgr_lower[QMGR_RFL_LAST_I + 1];
	uint8_t             qmgr_upper[QMGR_RFL_LAST_I + 1];

#if QMGR_STATS
	uint8_t             qmgr_max_use[QMGR_RFL_LAST_I + 1];

	/* some statistics */
	ulong               qmgr_rcpts_rcvd;
	ulong               qmgr_tas_rcvd;
	ulong               qmgr_rcpts_sent;
	ulong               qmgr_tas_sent;
#endif /* QMGR_STATS */
#if EVTHR_DEBUG
	uint                qmgr_evthr_dbg_lvl;
#endif
};
NOTE(MUTEX_PROTECTS_DATA(qmgr_ctx_S::qmgr_mutex, qmgr_ctx_S::))

#if QMGR_STATS
#define QMGR_SET_USE_MAX(resource)  do {            \
	if (qmgr_ctx->qmgr_usage[resource] > qmgr_ctx->qmgr_max_use[resource]) \
	   qmgr_ctx->qmgr_max_use[resource] = qmgr_ctx->qmgr_usage[resource]; \
	} while (0)
#else
#define QMGR_SET_USE_MAX(resource)  SM_NOOP
#endif

/*
**  XXX do we want to dynamically create these or do we want to
**  preallocate them in qmgr_ctx? The former is more flexible, the
**  latter 'safer' (can't fail during operation). If we limit the
**  number of connections then we may as well preallocate the data.
**  Note: limiting might be used as a DoS attack as well as unlimited;
**  the former to prevent legitimate connections, the latter to exhaust
**  memory. It would be very useful to restrict access to the communication
**  channel to "authorized" programs (Unix sockets: permissions, others?).
**  However, if some people want to run "many" SMTPS (instead of relying
**  on threads), then a list would be more useful.
*/

typedef uint8_t qss_status_T;
typedef uint8_t qsc_status_T;
typedef uint8_t qar_status_T;

/* generic data shared between qss_ctx_S and qsc_ctx_S */
struct qsm_gen_S
{
	sm_magic_T    sm_magic;
	rcbcom_ctx_T  qsm_gen_com;
	qmgr_ctx_P    qsm_gen_qmgr_ctx;  /* pointer back to main ctx */
	int           qsm_gen_id;        /* id */
	uint32_t      qsm_gen_bit;       /* bit for q_smtp_used */
	qss_status_T  qsm_gen_status;    /* status */
};

/*
**  Task context QMGR/SMTPS
**  Generally access to this structure is restricted since it is associated
**  with exactly one fd and hence one (evthr) task.
**  However, see below for details which parts are protected by mutexes.
**  Note: the task for the qss context is available as:
**  qss_ctx->qss_com.rcbcom_tsk
*/

struct qss_ctx_S
{
	sm_magic_T      sm_magic;
	rcbcom_ctx_T    qss_com;
	qmgr_ctx_P      qss_qmgr_ctx;  /* pointer back to main ctx */
	int             qss_id; /* SMTPS id, must be signed (QSS_ID_NONE) */
	uint32_t        qss_bit;   /* bit for qmgr_ssctx */

	qss_status_T    qss_status;    /* status of SMTPS */
	/* more data ... see docs? */

	sessta_id_T     qss_ta_id;  /* last SMTPS TA id  */

#if MTA_USE_SMTPS_MUTEX
	pthread_mutex_t qss_mutex;
#endif
	uint            qss_max_thrs;  /* upper limit for threads */
	uint            qss_max_cur_thrs;  /* current limit for threads */
	uint            qss_cur_session;   /* current # of sessions */
#if QMGR_STATS
	uint            qss_max_session;   /* max # of sessions */
#endif
};

/*
**  Task context QMGR/SMTPC
**
**  Items that can be changed during runtime are:
**  qsc_status
**  qsc_curactive   protected by qsc_mutex
**  qsc_maxthreads  protected by qsc_mutex
**  qsc_id_cnt  protected by dadb, see qmgr/id.c
*/

struct qsc_ctx_S
{
	sm_magic_T      sm_magic;
	rcbcom_ctx_T    qsc_com;
	qmgr_ctx_P      qsc_qmgr_ctx;  /* pointer back to main ctx */
	int             qsc_id;    /* SMTPC id (set by smtpc) */
	uint32_t        qsc_bit;   /* bit for qmgr_scctx */

	/* split this in status and flags? */
	qsc_status_T    qsc_status;    /* status of SMTPC */

	uint8_t         qsc_idx;   /* SMTPC index (set by qmgr) */
	dadb_ctx_P      qsc_dadb_ctx;  /* pointer to DA DB context */

	/* more data ... see docs? */

#if 0
	/*
	**  Also in dadb_ctx. Do we need two different data structures?
	**  Can SMTPC have multiple DAs? Currently no...
	**  So why not merge these two structures into one?
	**  Maybe the other way around? Several SMTPC can implement
	**  the same DA, hence they should "share" a DA DB ctx.
	**  In that case these counters are needed...
	*/

	pthread_mutex_t qsc_mutex;
	uint            qsc_maxthreads;
	uint            qsc_curactive;
#endif /* 0 */

	uint32_t        qsc_id_cnt;
};

union qsg_ctx_U
{
	qsm_gen_T       qsg_gen_ctx;
	qss_ctx_T       qsg_ss_ctx;
	qsc_ctx_T       qsg_sc_ctx;
};

#if 0
should this be instead like this:

struct qsg_ctx_S
{
	sm_magic_T      sm_magic;
	rcbcom_ctx_T    qsm_gen_com;
	qmgr_ctx_P      qsm_gen_qmgr_ctx;
	int             qsm_gen_id;
	uint32_t        qsm_gen_bit;
	qss_status_T    qsm_gen_status;

	union qsg_parts_U
	{

	   struct qss_part_S
	   {
#if MTA_USE_SMTPS_MUTEX
	       pthread_mutex_t  qss_mutex;
#endif
	       uint             qss_max_thrs;
	       uint             qss_max_cur_thrs;
	       uint             qss_cur_session;
#if QMGR_STATS
	       uint             qss_max_session;
#endif
	   } qsg_qss_part;

	   struct qsc_part_S
	   {
	       uint8_t          qsc_idx;
	       dadb_ctx_P       qsc_dadb_ctx;
	       uint32_t         qsc_id_cnt;
	   } qsg_qsc_part;

	};
};
#endif /* 0 */

/* QMGR status */
#define QMGR_ST_NONE    0x00    /* not yet OK */
#define QMGR_ST_INIT0   0x01    /* initialized */
#define QMGR_ST_CONF    0x02    /* configured */
#define QMGR_ST_INIT1   0x04    /* initialized */
#define QMGR_ST_START   0x08    /* started */

#define QMGR_ST_OK      0x10    /* initialized, running normal */

#define QMGR_ST_SLOW    0x20    /* slow down */

#define QMGR_ST_RSR     0x40    /* resource problem, prepare to terminate */

#define QMGR_ST_SH_DOWN 0x80    /* shutting down */
#define QMGR_ST_STOPPED 0x81    /* stopped: almost terminated */

/* qmgr is about to terminate, don't start anything new */
#define qmgr_is_term(qmgr_ctx)  ((qmgr_ctx)->qmgr_status >= QMGR_ST_SH_RSR)

/* qmgr is stopping, abort everything */
#define qmgr_is_stop(qmgr_ctx)  ((qmgr_ctx)->qmgr_status >= QMGR_ST_SH_DOWN)

/* SMTPS status (in QMGR) */
#define QSS_ST_NONE     0x00    /* not yet OK */
#define QSS_ST_START    0x01    /* started */
#define QSS_ST_OK       0x10    /* initialized, running normal */
#define QSS_ST_SLOW_0   0x20    /* slow down */
#define QSS_ST_SLOW_1   0x21    /* slow down more */
#define QSS_ST_SLOW_2   0x22    /* slow down a lot */
#define QSS_ST_SLOW_3   0x23    /* slow down to stand still */
#define QSS_ST_SH_DOWN  0x80    /* shutting down */
#define QSS_ST_STOPPED  0x81    /* stopped: almost terminated */

#define QSS_ID_NONE (-1)    /* no id (yet) */

/* for qX_control: direction */
#define QMGR_THROTTLE       1
#define QMGR_UN_THROTTLE    (-1)
#define QMGR_ANY_THROTTLE   0
#define qmgr_is_throttle(dir)   ((dir) >= 0)
#define qmgr_is_un_throttle(dir)    ((dir) <= 0)

/* SMTPC status (in QMGR) */
#define QSC_ST_NONE     0x00    /* not yet OK */
#define QSC_ST_START    0x01    /* started */
#define QSC_ST_OK       0x10    /* initialized, running normal */
#define QSC_ST_SLOW_0   0x20    /* slow down */
#define QSC_ST_SLOW_1   0x21    /* slow down more */
#define QSC_ST_SLOW_2   0x22    /* slow down a lot */
#define QSC_ST_SLOW_3   0x23    /* slow down to stand still */
#define QSC_ST_SH_DOWN  0x80    /* shutting down */
#define QSC_ST_STOPPED  0x81    /* stopped: almost terminated */

#define QSC_IS_RUNNING(qsc_ctx) (((qsc_ctx)->qsc_status >= QSC_ST_OK) \
	&& ((qsc_ctx)->qsc_status <= QSC_ST_SLOW_2))

#define QSC_ID_NONE (-1)    /* no id (yet) */

/* return codes from QMGR modules that decode/handle requests */
#define QMGR_R_WAITQ    SM_SUCCESS  /* default: put back in waitq */
#define QMGR_R_ASYNC    1   /* do nothing, task has been put in waitq */

/*
**  XXX define struct for iqdb; check sm-9 docs
**  that struct also defines (more or less) the corresponding RCB types

session-id  & session identifier
client-host & identification of connecting host
	   & IP address, host name, ident
features    & features offered: AUTH, TLS, EXPN, ...
workarounds & work around bugs in client (?)
transaction-id  & current transaction
reject-msg  & message to use for rejections (needed?)
auth        & AUTH information
starttls    & TLS information
n-bad-cmds  & number of bad SMTP commands
n-transactions  & number of transactions
n-rcpts     & total number of recipients
n-bad-rcpts & number of bad recipients

Transaction:

transaction-id  & transaction identifier
start-time  & date/time of transaction
mail        & address, arguments (decoded?)
n-rcpts     & number of recipients
rcpt-list   & addresses, arguments (decoded?)
cdb-id      & CDB identifier (obtained from cdb?)
msg-size    & message size
n-bad-cmds  & number of bad SMTP commands (necessary?)
n-rcpts     & number of valid recipients
n-bad-rcpts & number of bad recipients
session-id  & (pointer back to) session
end-time    & end of transaction

**  XXX define create/delete functions (which ctx to use?)
*/

/*
**  QMGR SMTPS session context; only stored in iqdb.
*/

struct qss_sess_S
{
#if QS_SE_CHECK
	sm_magic_T      sm_magic;
#endif
	sessta_id_T     qsses_id;  /* SMTPS session id  */
	time_T          qsses_st_time; /* start time */
	sm_rpool_P      qsess_rpool;
	in_addr_T       qsess_client;  /* XXX use a generic struct! */
#if 0
	/*
	**  XXX we should have one data type for this...
	** IPv4/IPv6 address ({client_addr})
	** authinfo (?)
	** resolved hostname ({client_name})
	** info about resolve ({client_resolve})
	**  XXX other means to identify client host?
	*/

	sm_str_P        qsses_clt_name;
#endif /* 0 */
	/* uint8_t      qsses_locked; not yet used */
};

/* we need only the external representation of addresses here */
struct qss_mail_S
{
	sm_str_P       qsm_pa; /* printable addr */
	/* XXX parameters? */
};

struct qss_rcpt_S
{
	sm_str_P           qsr_pa; /* printable addr */
	smtp_status_T      qsr_status; /* status */
	rcpt_idx_T         qsr_idx;    /* rcpt idx */
	rcpt_id_T          qsr_id; /* rcpt id */
	uint               qsr_flags;  /* flags */
	/* XXX parameters? */
	TAILQ_ENTRY(qss_rcpt_S)        qsr_link;   /* links */
};

TAILQ_HEAD(qss_rcpts_S, qss_rcpt_S);

/* Operations on qss rcpt lists */
#define QSRCPTS_INIT(rcpts) TAILQ_INIT(rcpts)
#define QSRCPTS_EMPTY(rcpts) TAILQ_EMPTY(rcpts)
#define QSRCPTS_FIRST(rcpts) TAILQ_FIRST(rcpts)
#define QSRCPTS_END(rcpts)  TAILQ_END(rcpts)
#define QSRCPTS_NEXT(rcpts) TAILQ_NEXT(rcpts, qsr_link)
#define QSRCPTS_PREV(rcpts) TAILQ_PREV(rcpts, qss_rcpts_S, qsr_link)
#define QSRCPTS_INSERT_TAIL(addr, rcpt) TAILQ_INSERT_TAIL(addr, rcpt, qsr_link)
#define QSRCPTS_INSERT_HEAD(addr, rcpt) TAILQ_INSERT_HEAD(addr, rcpt, qsr_link)
#define QSRCPTS_REMOVE(addr, rcpt)  TAILQ_REMOVE(addr, rcpt, qsr_link)
#define QSRCPTS_REMOVE_FREE(ta, addr, rcpt)     \
	do                     \
	{                      \
	   TAILQ_REMOVE((addr), (rcpt), qsr_link); \
	   qsr_rcpt_free((ta), (rcpt));        \
	} while (0)

/* qss recipient flags */
#define QSRCPT_FL_NONE  0x00000000
#define QSRCPT_FL_TA    0x00000001  /* added to qss_ta rcpts list */
#define QSRCPT_FL_IQDB  0x00000002  /* in iqdb */
#define QSRCPT_FL_IBDB  0x00000004  /* in ibdb */
#define QSRCPT_FL_T_SS  0x00000100  /* sent reply to SMTPS */
/* XXX more? How about QSRCPT_FL_IQDB_RM list for QSS_TA_FL_? */

#define QSRCPT_SET_FLAG(qsr, fl) (qsr)->qsr_flags |= (fl)
#define QSRCPT_CLR_FLAG(qsr, fl) (qsr)->qsr_flags &= ~(fl)
#define QSRCPT_IS_FLAG(qsr, fl) (((qsr)->qsr_flags & (fl)) != 0)
/* XXX other macros to manipulate the flag field in other structures? */

/*
**  QMGR SMTPS transaction context
**
**  qss_ta can be stored in iqdb, optas, aq (see below), and in ibdb.
**  Hence it is not clear when qss_ta can be free()d.  To avoid
**  race conditions, flags are used which indicate what happened to
**  qss_ta so far (i.e., in which places is it stored, where has it
**  been removed, etc).
**
**  About qss_ta in AQ: see qda_upd_iqdb(), currently it uses qss_ta to update
**  IBDB; maybe this can be changed to use data from AQ?
*/

struct qss_ta_S
{
#if QS_TA_CHECK
	sm_magic_T      sm_magic;
#endif
	sm_rpool_P      qssta_rpool;
	time_T          qssta_st_time;
	qss_mail_P      qssta_mail;    /* mail from */
	qss_rcpts_T     qssta_rcpts;   /* rcpts */
	uint            qssta_rcpts_tot;   /* total number of recipients */
	uint            qssta_flags;
	sessta_id_T     qssta_id;
	cdb_id_P        qssta_cdb_id;
	off_t           qssta_msg_sz_b;
	qss_ctx_P       qssta_ssctx;   /* pointer back to SMTPS ctx */

	sm_hdrmodhd_P   qssta_hdrmodhd;

	/*
	**  do we need a pointer to the sesssion?
	**  might be useful but non-trivial to achieve.
	**  the protocol would have to transfer the session id when a new
	**  transaction is created (NTAID).
	**  qss_sess_P  qssta_sess;    * pointer to sesssion
	*/
#if 0
cmd-failures    /* number of failures for certain commands  */
#endif

	pthread_mutex_t     qssta_mutex;
	/* uint8_t      qssta_locked; not yet used */
};
NOTE(MUTEX_PROTECTS_DATA(qss_ta_S::qssta_mutex, qss_ta_S::))

/*
**  qss_ta flags
*/

#define QSS_TA_FL_NONE      0x00000000
#define QSS_TA_FL_VERP      0x00000001

#define QSS_TA_FL_DB_MASK   0x0000001f  /* mask for DB flags */

/* XXX See below: QSS_TA_OK_FREE() */
#define QSS_TA_FL_IQDB      0x00000010  /* in iqdb */
#define QSS_TA_FL_OPTA      0x00000020  /* in optas */
#define QSS_TA_FL_IBDB      0x00000040  /* in ibdb */
/* #define QSS_TA_FL_IBDB_C 0x00000080  * committed to ibdb */
#define QSS_TA_FL_AQ        0x00000100  /* referenced from AQ */
#define QSS_TA_FL_DB        0x000001f0  /* in some DB */
#define QSS_TA_FL_ADDED(flags)  (((flags) & QSS_TA_FL_DB) >> 4)

#define QSS_TA_FL_IQDB_RM   0x00001000  /* removed from iqdb */
#define QSS_TA_FL_OPTA_RM   0x00002000  /* removed from optas */
#define QSS_TA_FL_IBDB_RM   0x00004000  /* "removed" from ibdb */
/* #define QSS_TA_FL_IBDB_C_RM  0x00008000 * committed "removed" from ibdb */
#define QSS_TA_FL_AQ_RM     0x00010000  /* no more ref. from AQ */
#define QSS_TA_FL_DB_RM     0x0001f000  /* removed from some DB */
#define QSS_TA_FL_RMED(flags)   (((flags) & QSS_TA_FL_DB_RM) >> 12)

/*
**  Two different flags: added to IBDB, committed to IBDB?
**  It's hard to determine when an entry is actually committed;
**  for open transactions this information is in optas, but for
**  transactions that are closed, this information isn't anywhere (is it?).
**  Therefore the "committed" flags are not (yet) used.
*/

/*
**  OK to free iff QSS_TA_FL_xdb (A) == QSS_TA_FL_xdb_RM (R)
**  That's not really correct: for every flag QSS_TA_FL_xdb the corresponding
**  _RM flag must be set too. That's simply "implies": A=>B == !A || B.
*/

#define QSS_TA_OK_FREE(flags)   \
	(((~QSS_TA_FL_ADDED(flags) & QSS_TA_FL_DB_MASK) | \
	 QSS_TA_FL_RMED(flags)) == QSS_TA_FL_DB_MASK)
#if 0
	(QSS_TA_FL_ADDED(flags) == QSS_TA_FL_RMED(flags))
	(((~QSS_TA_FL_ADDED(flags) & 0x1f) || QSS_TA_FL_RMED(flags)) != 0)
#endif

/* more? */

#define QSS_TA_SET_FLAG(qss_ta, fl) (qss_ta)->qssta_flags |= (fl)
#define QSS_TA_CLR_FLAG(qss_ta, fl) (qss_ta)->qssta_flags &= ~(fl)
#define QSS_TA_IS_FLAG(qss_ta, fl)  (((qss_ta)->qssta_flags & (fl)) != 0)

/*
**  Flags for qss_ta_free()
**  The first two aren't really used, they are useful for logging;
**  only the last one is used: it overrides the requirement (see above)
**  for free()ing qss_ta.
*/

#define QSS_TA_FREE_DA      0x00001 /* DA: remove qss_ta */
#define QSS_TA_FREE_OP      0x00002 /* OPtas: remove qss_ta */
#define QSS_TA_FREE_ALWAYS  0x00008 /* unconditionally free qss_ta */

/*
**  Convert a status code from SMTPC/DA to an SMTP status code
**  Note: this is a hack... it is not clear
**  - who should do the conversion
**  - whether pseudo SMTP reply codes should be used at all
**      (see qmgr/sched.c for another example)
**  - whether all error code should be treated as temporary
**    or whether some are actually permanent.
**  XXX Do a "real" conversion from error code to pseudo SMTP reply code.
*/

#define STATUS2SMTPCODE(st) ((sm_is_err(st)) ? SMTPC_TEMP_ST : (st))

/* type checks */
#if QS_TA_CHECK
# define SM_IS_QS_TA(qss_ta)    SM_REQUIRE_ISA((qss_ta), SM_QSS_TA_MAGIC)
#else
# define SM_IS_QS_TA(qss_ta)    SM_REQUIRE((qss_ta) != NULL)
#endif
#if QS_SE_CHECK
# define SM_IS_QS_SE(qss_sess)  SM_REQUIRE_ISA((qss_sess), SM_QSS_SE_MAGIC)
#else
# define SM_IS_QS_SE(qss_sess)  SM_REQUIRE((qss_sess) != NULL)
#endif
#define SM_IS_QS_RCPT(qss_rcpt) SM_REQUIRE((qss_rcpt) != NULL)

/*
**  Use a list of transaction ids to commit?
**  That list could contain just the entries in the iqdb
**  which should contain the transactions to notify of the commit.
**  We could even use a fixed size list and trigger a commit whenever
**  some limit is reached (in addition to a short timeout).
**  Currently implemented as fixed size queue, see <sm/fxszq.h>
**  Alternatively add a list entry to qss_ta_S and make this structure
**  just the head of the list (including counter and mutex).
*/

/* QMGR open transaction context (from SMTPS) */
struct qss_opta_S
{
	uint             qot_max;   /* allocated size */
	uint             qot_cur;   /* currently used (basically last-first) */
	uint             qot_first; /* first index to read */
	uint             qot_last;  /* last index to read (first to write) */
	pthread_mutex_t  qot_mutex;
	qss_ta_P        *qot_tas;   /* array of open transactions */
};
NOTE(MUTEX_PROTECTS_DATA(qss_opta_S::qot_mutex, qss_opta_S::))

#define SM_IS_QS_OPTAS(qss_optas)   SM_REQUIRE((qss_optas) != NULL)

/*
**  Task context QMGR/AR
*/

struct qar_ctx_S
{
	sm_magic_T      sm_magic;
	rcbcom_ctx_T    qar_com;
	qmgr_ctx_P      qar_qmgr_ctx;  /* pointer back to main ctx */

	/* split this in status and flags? */
	qar_status_T    qar_status;    /* status of AR */
#if 0
	uint32_t        qar_last_id;   /* last used id */
#endif
	/* we'll abuse qar_wrmutex to protect access to this... */
};


/*
available threads (at least number)
tasks
waitqueue
runqueue
*/

/* DA (SMTPC) */

/* Return flags from various functions, must be less than 0x8000000 */
#define QDA_FL_ACT_SCHED    0x0001  /* activate scheduler */
#define QDA_FL_ACT_SMAR     0x0002  /* activate SMAR */
#define QDA_FL_ACT_DA       0x0004  /* activate DA */

#define QDA_ACT_SCHED(ret)  (((ret) & QDA_FL_ACT_SCHED) != 0)
#define QDA_ACT_SMAR(ret)   (((ret) & QDA_FL_ACT_SMAR) != 0)
#define QDA_ACT_DA(ret)     (((ret) & QDA_FL_ACT_DA) != 0)

/* prototypes */

sm_ret_T qda_update_ta_stat(qmgr_ctx_P _qmgr_ctx
	       , sessta_id_T _da_ta_id
	       , sm_ret_T _status
	       , uint _err_st
	       , dadb_ctx_P dadb_ctx
	       , dadb_entry_P _dadb_entry
	       , aq_ta_P _aq_ta
	       , aq_rcpt_P _aq_rcpt
	       , sm_str_P errmsg
	       , thr_lock_T _locktype
	       );

/* SMTPC ids */
sm_ret_T qsc_id_init(qsc_ctx_P _scctx, uint32_t _id_val);
sm_ret_T qsc_id_end(qsc_ctx_P _scctx);
sm_ret_T qsc_id_next(qsc_ctx_P _qsc_ctx, uint _daprocidx, uint _dathreadidx, sessta_id_P _id);

sm_ret_T qsc_ctx_find(qmgr_ctx_P _qmgr_ctx, uint _sc_id, qsc_ctx_P *_pqsc_ctx);

/* AR */
sm_ret_T qmgr_rcpt2ar(qmgr_ctx_P _qmgr_ctx, aq_rcpt_P _aq_rcpt, thr_lock_T _locktype);

/* cleanup functions */
sm_ret_T qmgr_set_aq_cleanup(qcleanup_ctx_P _qcleanup_ctx, time_T _when, bool _changewakeup);

sm_ret_T qm_rcpt_da_expire(qmgr_ctx_P _qmgr_ctx, aq_rcpt_P _aq_rcpt, time_T _startt, time_T *_pexpire);

sm_ret_T qm_test_fill_aq(sm_log_ctx_P _lctx, aq_ctx_P _aq_ctx, time_T _time_now, ulong _tot_tas, uint32_t _once_tas, uint _max_fill_aq, uint _rate, sm_str_P _defaultdomain, uint32_t *_ptas_added, thr_lock_T _locktype);
#if QMGR_TEST
sm_ret_T qm_tst_fill_aq(qmgr_ctx_P _qmgr_ctx);
#endif

#endif /* SM_QMGR_INT_H */


syntax highlighted by Code2HTML, v. 0.9.1