/*
 * Copyright (c) 2002-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: qss_nseid.c,v 1.69 2007/10/16 04:19:36 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/reccom.h"
#include "sm/ssocc.h"
#include "qmgr.h"
#include "log.h"

/*
**  QM_SS_NSEID -- decide whether to accept NSEID, put qss_sess into IQDB
**
**	Parameters:
**		qss_ctx -- QMGR/SMTPS context
**		qss_sess -- session context
**
**	Returns:
**		<0: usual sm_error code (won't happen in this impl.)
**		>=0: acceptance/rejection code
**
**	Side Effects:
**		if IQDB is full qss_control() is called.
**
**	Last code review: 2003-10-22 16:59:20, see below
**	Last code change: 2005-03-16 00:53:46
*/

sm_ret_T
qm_ss_nseid(qss_ctx_P qss_ctx, qss_sess_P qss_sess)
{
	sm_ret_T ret, rate;
	uint limit;
	int r;
	uint fct_state;
	qss_sess_P rqss_sess;
	ssocc_entry_P ssocc_entry;
	ssocc_ctx_P ssocc_ctx;
	sm_str_P str, tag, rhs;

#define FST_LOCKED	0x01	/* ssocc_entry is locked */
#define FST_INCR	0x02	/* ssocce_open_se has been increased */
	SM_IS_QSS_CTX(qss_ctx);
	SM_IS_QS_SE(qss_sess);
	rqss_sess = NULL;
	rhs = str = tag = NULL;
	fct_state = 0;

	/* do some checks here: is it ok to accept this session? */

	/* XXX this should be in some other function? (ssocc_new_se()) */
	ssocc_entry = NULL;
	ssocc_ctx = qss_ctx->qss_qmgr_ctx->qmgr_ssocc_ctx;

	ret = ssocc_entry_find(ssocc_ctx, qss_sess->qsess_client.s_addr,
			&ssocc_entry, THR_LOCK_IT);
	SM_SET_FLAG(fct_state, FST_LOCKED);

	rate = sm_connctl(ssocc_ctx->ssocc_cctx, qss_sess->qsess_client,
			time(NULLT));

	if (sm_is_err(ret)) {
		/* add it... */
		ret = ssocc_entry_new(ssocc_ctx, qss_sess->qsess_client.s_addr,
			&ssocc_entry, THR_UNL_IF_ERR);
		if (sm_is_err(ret)) {
			SM_CLR_FLAG(fct_state, FST_LOCKED);
			QM_LEV_DPRINTFC(QDC_S2Q, 1, (QM_DEBFP, "sev=ERROR, func=qm_ss_nseid, ss_sess=%s, ssocc_entry=failed, ret=%r\n", qss_sess->qsses_id, ret));
			goto reject;
		}
#if 0
/* TEST */
ret = sm_error_temp(SM_EM_DA, SM_E_FULL);
goto reject;
#endif /* 0 */
	}

QM_LEV_DPRINTFC(QDC_S2Q, 5, (QM_DEBFP, "func=qm_ss_nseid, ss_sess=%s, ipv4=%A, open_se=%d, rate=%d\n", qss_sess->qsses_id, (ipv4_T) qss_sess->qsess_client.s_addr, ssocc_entry->ssocce_open_se, rate));

	str = sm_str_new(NULL, 10, 16);	/* check size */
	tag = sm_str_new(NULL, 8, 16);	/* check size */
	rhs = sm_str_new(NULL, 14, 16);	/* check size */

	limit = qss_ctx->qss_qmgr_ctx->qmgr_cnf.q_cnf_max_open_se;
	if (qss_ctx->qss_qmgr_ctx->qmgr_conf_map != NULL) {
		if (str != NULL && tag != NULL && rhs != NULL) {
/* incoming connections max */
#define ICMTAG "icm:"
			sm_str_clr(tag);
			sm_str_clr(str);
			if (sm_str_scat(tag, ICMTAG) == SM_SUCCESS
			    && sm_inet_inaddr2str(qss_sess->qsess_client, str) == SM_SUCCESS
			    && sm_map_lookup_ip(qss_ctx->qss_qmgr_ctx->qmgr_conf_map,
			                        str, tag, SMMAP_LFL_SUBNETS|SMMAP_LFL_TAG,
			                        rhs) == SM_SUCCESS
			    && sm_str_getlen(rhs) > 0)
			{
				ulong v;

				errno = 0;
				v = strtoul((char *) sm_str_getdata(rhs), NULL, 0);
				if (v != ULONG_MAX && errno != ERANGE)
					limit = (uint) v;
			}
QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "func=qm_ss_nseid, ss_sess=%s, ipv4=%A, %slookup=%r, rhs=%S, open_se_max=%d\n", qss_sess->qsses_id, (ipv4_T) qss_sess->qsess_client.s_addr, ICMTAG, ret, rhs, limit));
		}
	}

	if (ssocc_entry->ssocce_open_se >= limit) {
		++ssocc_entry->ssocce_open_se_exc;
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_INFO, 10,
			"sev=INFO, func=qm_ss_nseid, ss_sess=%s, ipv4=%A, open_se=%d, max=%u, status=exceeded, times=%u"
			, qss_sess->qsses_id
			, (ipv4_T) qss_sess->qsess_client.s_addr
			, ssocc_entry->ssocce_open_se, limit
			, ssocc_entry->ssocce_open_se_exc);
		ret = SMTP_R_SSD|SMTP_R_QUICK;	/* Better error code?? */
		goto errunl;
	}

	/* Look up individual connection rate. */
	limit = qss_ctx->qss_qmgr_ctx->qmgr_cnf.q_cnf_max_conn_rate;
	if (qss_ctx->qss_qmgr_ctx->qmgr_conf_map != NULL) {
		if (str != NULL && tag != NULL && rhs != NULL) {
#define ICRTAG "icr:"
			sm_str_clr(tag);
			sm_str_clr(str);
			if (sm_str_scat(tag, ICRTAG) == SM_SUCCESS
			    && sm_inet_inaddr2str(qss_sess->qsess_client, str) == SM_SUCCESS
			    && sm_map_lookup_ip(qss_ctx->qss_qmgr_ctx->qmgr_conf_map,
			                        str, tag, SMMAP_LFL_SUBNETS|SMMAP_LFL_TAG,
			                        rhs) == SM_SUCCESS
			    && sm_str_getlen(rhs) > 0)
			{
				ulong v;

				errno = 0;
				v = strtoul((char *) sm_str_getdata(rhs), NULL, 0);
				if (v != ULONG_MAX && errno != ERANGE)
					limit = (uint) v;
			}
QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "func=qm_ss_nseid, ss_sess=%s, ipv4=%A, %slookup=%r, rhs=%S, icr_max=%d\n", qss_sess->qsses_id, (ipv4_T) qss_sess->qsess_client.s_addr, ICRTAG, ret, rhs, limit));
		}
	}
	else
QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "conf_map=%p\n", qss_ctx->qss_qmgr_ctx->qmgr_conf_map));

	SM_STR_FREE(str);
	SM_STR_FREE(tag);
	SM_STR_FREE(rhs);

	/* connection rate control */
	if (rate > 0 && (uint)rate > limit) {
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_INFO, 10,
			"sev=INFO, func=qm_ss_nseid, ss_sess=%s, ipv4=%A, rate=%d, max=%u, status=exceeded"
			, qss_sess->qsses_id
			, (ipv4_T) qss_sess->qsess_client.s_addr
			, rate, limit);
		ret = SMTP_R_SSD|SMTP_R_QUICK;	/* Better error code?? */
		goto errunl;
	}
	++ssocc_entry->ssocce_open_se;
	SM_SET_FLAG(fct_state, FST_INCR);
	r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
	if (r != 0) {
		QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_ss_nseid, unlock(ssocc_ctx)=%d\n", r));
	}
	else
		SM_CLR_FLAG(fct_state, FST_LOCKED);

	ret = iqdb_session_new(qss_ctx->qss_qmgr_ctx->qmgr_iqdb,
				qss_sess->qsses_id, SMTP_STID_SIZE,
				qss_sess, (void **) &rqss_sess, THR_LOCK_UNLOCK);
	if (sm_is_err(ret)) {
QM_LEV_DPRINTFC(QDC_S2Q, 2, (QM_DEBFP, "sev=WARN, func=qm_ss_nseid, ss_sess=%s, iqdb_session_new=%r\n", qss_sess->qsses_id, ret));
		if (sm_error_value(ret) == SM_E_FULL
		    || sm_error_value(ret) == ENOMEM)
			(void) qss_control(qss_ctx, QMGR_THROTTLE, 100,
					QMGR_RFL_IQD_I, THR_LOCK_UNLOCK);
		goto reject;
	}
	++qss_ctx->qss_cur_session;
#if QMGR_STATS
	if (qss_ctx->qss_cur_session > qss_ctx->qss_max_session) {
		qss_ctx->qss_max_session = qss_ctx->qss_cur_session;
		QM_LEV_DPRINTFC(QDC_S2Q, 5, (QM_DEBFP, "func=qm_ss_nseid, id=%d, max_sess=%d\n", qss_ctx->qss_id, qss_ctx->qss_max_session));
	}
#endif /* QMGR_STATS */
	return SMTP_R_OK;

  reject:
	/* more appropriate error code?? */
	ret = SMTP_R_SSD|SMTP_R_QUICK;
  errunl:
	if ((ret & ~SMTP_R_QUICK) == SMTP_R_SSD) {
		if (ssocc_entry != NULL) {
			if (!SM_IS_FLAG(fct_state, FST_LOCKED)) {
				r = pthread_mutex_lock(&ssocc_ctx->ssocc_mutex);
				SM_LOCK_OK(r);
				if (r != 0) {
					/* OOOPS */
QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_ss_nseid, ss_sess=%s, lock=%d\n", qss_sess->qsses_id, r));
					goto failed;
				}
				SM_SET_FLAG(fct_state, FST_LOCKED);
			}

			if (SM_IS_FLAG(fct_state, FST_INCR) &&
			    ssocc_entry->ssocce_open_se > 0)
			{
				--ssocc_entry->ssocce_open_se;
				SM_CLR_FLAG(fct_state, FST_INCR);
			}
			if (ssocc_entry->ssocce_open_se == 0)
				(void) ssocc_entry_free(ssocc_ctx, ssocc_entry, THR_NO_LOCK);
		}
	}
	if (SM_IS_FLAG(fct_state, FST_LOCKED)) {
		r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
		if (r != 0) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_ss_nseid, unlock(ssocc_ctx)=%d\n", r));
		}
	}

  failed:
#if 0
	/*
	**  No cleanup needed. iqdb_session_new() does not add the entry
	**  if there is a problem. if more functions are called above
	**  after iqdb_session_new() then iqdb_session_rm() would be
	**  required [if (rqss_sess != NULL)]
	**
	**  What about ssocc? If the session isn't accepted, shouldn't
	**  it be removed from the open session cache?
	*/
#else /* 0 */
	SM_ASSERT(rqss_sess == NULL);
#endif /* 0 */
	return ret;
}

/*
**  QM_2SS_NSEID -- Reply to NSEID, put data into RCB entry
**
**	Parameters:
**		qss_ctx -- QMGR/SMTPS context
**		rcbe -- RCB entry (must be open for encoding)
**		sessid -- session id
**		status -- session status (accepted, rejected, ...)
**
**	Returns:
**		usual sm_error code
**
**	Last code review: 2003-10-22 16:59:20
*/

sm_ret_T
qm_2ss_nseid(qss_ctx_P qss_ctx, sm_rcbe_P rcbe, uchar *sessid, int status)
{
	sm_rcb_P rcb;

	rcb = &rcbe->rcbe_rcb;
	return sm_rcb_putv(rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_Q2S_ID, qss_ctx->qss_id,
		SM_RCBV_BUF, RT_Q2S_SEID, sessid, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_Q2S_STAT, (uint32_t) status,
		SM_RCBV_END);
}


syntax highlighted by Code2HTML, v. 0.9.1