/*
* Copyright (c) 2005, 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: greyctl.c,v 1.19 2007/01/26 05:36:24 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/queue.h"
#include "sm/net.h"
#include "sm/greyctl.h"
#include "sm/map.h"
#include "sm/bdb.h"
#if MTA_USE_PTHREADS
#include "sm/pthread.h"
#endif
#include "greyctl.h"
/*
** SM_GREY_COMPARE -- compare two time stamps from DB (callback)
**
** Parameters:
** dbp -- ignored
** d1 -- data 1
** d2 -- data 2
**
** Returns:
** data 1 minus data 2 (as time_t)
*/
int
sm_grey_compare(DB *dbp, const DBT *d1, const DBT *d2)
{
time_t t1, t2;
sm_memcpy(&t1, d1->data, sizeof(time_t));
sm_memcpy(&t2, d2->data, sizeof(time_t));
return (t1 - t2);
}
/*
** SM_GREY_SI -- get secondary index from main index (callback)
**
** Parameters:
** db -- ignored
** pkey -- primary key
** pdata -- primary data
** skey -- secondary key (output)
**
** Returns:
** 0
*/
int
sm_grey_si(DB *db, const DBT *pkey, const DBT *pdata, DBT *skey)
{
SM_REQUIRE(skey != NULL);
SM_REQUIRE(pdata != NULL);
sm_memzero(skey, sizeof(skey));
skey->data = (void *)&(((greyentry_P) pdata->data)->ge_expire);
skey->size = sizeof(((greyentry_P) pdata->data)->ge_expire);
return 0;
}
/*
** SM_GREY_OPENDB -- open databases
**
** Parameters:
** db_name -- name of primary DB
** db -- primary DB (output)
** sdb_name -- name of secondary DB
** sdb -- secondary DB (output)
**
** Returns:
** usual sm_error code
**
** ToDo: error handling
*/
static sm_ret_T
sm_grey_opendb(const char *db_name, DB **pdb, const char *sdb_name, DB **psdb)
{
int ret;
DB *db, *sdb;
#define dbenv NULL
SM_REQUIRE(db_name != NULL);
SM_REQUIRE(pdb != NULL);
SM_REQUIRE(sdb_name != NULL);
SM_REQUIRE(psdb != NULL);
/* create/open primary */
ret = db_create(&db, dbenv, 0);
if (ret != 0)
return BDB_ERR2RET(ret);
ret = db->open(db, NULL, db_name, NULL, DB_BTREE, DB_CREATE, 0600);
if (ret != 0)
return BDB_ERR2RET(ret);
/* create/open secondary */
ret = db_create(&sdb, dbenv, 0);
if (ret != 0)
return BDB_ERR2RET(ret);
ret = sdb->set_bt_compare(sdb, sm_grey_compare);
if (ret != 0)
return BDB_ERR2RET(ret);
ret = sdb->set_flags(sdb, DB_DUP | DB_DUPSORT);
if (ret != 0)
return BDB_ERR2RET(ret);
ret = sdb->open(sdb, NULL, sdb_name, NULL, DB_BTREE, DB_CREATE, 0600);
if (ret != 0)
return BDB_ERR2RET(ret);
/* Associate the secondary with the primary. */
ret = db->associate(db, NULL, sdb, sm_grey_si, 0);
if (ret != 0)
return BDB_ERR2RET(ret);
*pdb = db;
*psdb = sdb;
return SM_SUCCESS;
}
/*
** SM_GREYCTL_FREE -- free grey control context
**
** Parameters:
** greyctx -- connection control context
**
** Returns:
** usual sm_error code
*/
sm_ret_T
sm_greyctl_free(greyctx_P greyctx)
{
#if MTA_USE_PTHREADS
int r;
#endif
if (NULL == greyctx)
return SM_SUCCESS;
#if MTA_USE_PTHREADS
r = pthread_mutex_lock(&greyctx->greyc_mutex);
SM_LOCK_OK(r);
#endif
if (greyctx->greyc_grey_sdb != NULL) {
greyctx->greyc_grey_sdb->close(greyctx->greyc_grey_sdb, 0);
greyctx->greyc_grey_sdb = NULL;
}
if (greyctx->greyc_grey_db != NULL) {
greyctx->greyc_grey_db->close(greyctx->greyc_grey_db, 0);
greyctx->greyc_grey_db = NULL;
}
#if MTA_USE_PTHREADS
r = pthread_mutex_destroy(&greyctx->greyc_mutex);
#endif
sm_free_size(greyctx, sizeof(*greyctx));
#if SM_GREYCTL_CHECK
greyctx->sm_magic = SM_MAGIC_NULL;
#endif
return SM_SUCCESS;
}
/*
** SM_GREYCTL_CRT -- create new grey control context
**
** Parameters:
** pgreyctx -- grey control context (output)
**
** Returns:
** usual sm_error code
*/
sm_ret_T
sm_greyctl_crt(greyctx_P *pgreyctx)
{
sm_ret_T ret;
greyctx_P greyctx;
#if MTA_USE_PTHREADS
int r;
#endif
SM_REQUIRE(pgreyctx != NULL);
ret = SM_SUCCESS;
greyctx = (greyctx_P) sm_zalloc(sizeof(*greyctx));
if (NULL == greyctx)
return sm_err_temp(ENOMEM);
#if MTA_USE_PTHREADS
r = pthread_mutex_init(&greyctx->greyc_mutex, SM_PTHREAD_MUTEXATTR);
if (r != 0) {
ret = sm_err_perm(r);
goto error;
}
#endif
greyctx->greyc_used = 0;
greyctx->greyc_cnf.greycnf_limit = 10000;
greyctx->greyc_cnf.greycnf_min_grey_wait = 60;
greyctx->greyc_cnf.greycnf_max_grey_wait = 60 * 60 * 24;
greyctx->greyc_cnf.greycnf_white_expire = 60 * 60 * 24 * 33;
greyctx->greyc_cnf.greycnf_white_reconfirm = 60 * 60 * 24 * 40;
/* CONF */
greyctx->greyc_cnf.greycnf_grey_name = "grey_grey_m.db";
greyctx->greyc_cnf.greycnf_grey_sname = "grey_grey_s.db";
#if SM_GREYCTL_CHECK
greyctx->sm_magic = SM_GREYCTL_MAGIC;
#endif
*pgreyctx = greyctx;
return ret;
#if MTA_USE_PTHREADS
error:
SM_FREE_SIZE(greyctx, sizeof(*greyctx));
return ret;
#endif
}
/*
** SM_GREYCTL_OPEN -- open grey control context
**
** Parameters:
** greyctx -- grey control context
**
** Returns:
** usual sm_error code
*/
sm_ret_T
sm_greyctl_open(greyctx_P greyctx, greycnf_P greycnf)
{
sm_ret_T ret;
SM_IS_GREYCTX(greyctx);
if (greycnf != NULL)
sm_memcpy(&greyctx->greyc_cnf, greycnf, sizeof(greyctx->greyc_cnf));
if (greyctx->greyc_cnf.greycnf_netmask == 0)
greyctx->greyc_cnf.greycnf_netmask = (ipv4_T) 0xFFFFFFFF;
ret = sm_grey_opendb(
greyctx->greyc_cnf.greycnf_grey_name, &greyctx->greyc_grey_db,
greyctx->greyc_cnf.greycnf_grey_sname, &greyctx->greyc_grey_sdb);
return ret;
}
/*
** SM_GREY_EXPIRE1 -- remove one entry
**
** Parameters:
** db -- main DB
** sdb -- secondary DB
** now -- time of connection
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sm_grey_expire1(DB *db, DB *sdb, time_t now)
{
sm_ret_T ret;
DBC *dbcp;
DBT db_data, db_key;
greyentry_T ge;
ret = db->cursor(sdb, NULL, &dbcp, 0);
if (ret != 0)
return ret;
sm_memzero(&db_key, sizeof(db_key));
sm_memzero(&db_data, sizeof(db_data));
db_data.flags = DB_DBT_USERMEM;
db_data.data = ≥
db_data.ulen = sizeof(ge);
/* this could be done in a loop for some elements */
ret = dbcp->c_get(dbcp, &db_key, &db_data, DB_LAST);
if (0 == ret) {
if (ge.ge_time > now) {
ret = dbcp->c_del(dbcp, 0);
if (0 == ret)
ret = 1;
}
}
ret = dbcp->c_close(dbcp);
return ret;
}
/*
** SM_GREY_EXPIRE -- remove some entries
**
** Parameters:
** greyctx -- connection control context
** t -- time of connection
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sm_grey_expire(greyctx_P greyctx, time_t t)
{
sm_ret_T ret;
ret = sm_grey_expire1(greyctx->greyc_grey_db, greyctx->greyc_grey_sdb, t);
return ret;
}
#define GE_KEY(ge) ((ge)->ge_key)
#define GE_LEN(ge) ((ge)->ge_len)
/*
** SM_GREY_FIND -- find an entry
**
** Parameters:
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sm_grey_find(DB *db, void *key, uint len, greyentry_P ge)
{
sm_ret_T ret;
DBT db_data, db_key;
sm_memzero(&db_key, sizeof(db_key));
sm_memzero(&db_data, sizeof(db_data));
db_key.data = key;
db_key.size = len;
db_data.flags = DB_DBT_USERMEM;
db_data.data = ge;
db_data.ulen = sizeof(*ge);
ret = db->get(db, NULL, &db_key, &db_data, 0);
return ret;
}
/*
** SM_GREY_ADD -- add an entry
**
** Parameters:
** db -- main DB
** ge -- grey_entry to be added
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sm_grey_add(DB *db, greyentry_P ge)
{
sm_ret_T ret;
DBT db_data, db_key;
sm_memzero(&db_key, sizeof(db_key));
sm_memzero(&db_data, sizeof(db_data));
db_key.data = GE_KEY(ge);
db_key.size = GE_LEN(ge);
db_data.data = ge;
db_data.size = sizeof(*ge);
ret = db->put(db, NULL, &db_key, &db_data, 0);
return ret;
}
/*
** SM_GREY_RM -- remove an entry
**
** Parameters:
** db -- main DB
** key -- key to be removed
** len -- length of key
**
** Returns:
** usual sm_error code
*/
sm_ret_T
sm_grey_rm(DB *db, void *key, uint len)
{
sm_ret_T ret;
DBT db_key;
sm_memzero(&db_key, sizeof(db_key));
db_key.data = key;
db_key.size = len;
ret = db->del(db, NULL, &db_key, 0);
return ret;
}
/*
** SM_GREYCTL -- perform greylisting checks
**
** Parameters:
** greyctx -- connection control context
** key -- key
** keylen -- length of key
** t -- time of connection
**
** Returns:
** SM_GREY_OK: ok to accept now (just moved from grey to white)
** SM_GREY_WHITE: entry is whitelisted by now
** SM_GREY_FIRST: new entry
** SM_GREY_WAIT: need to wait
** SM_GREY_AGAIN: was whitelisted, but expired
** <0: usual sm_error code
**
** Question: how to handle overflows of the table?
** just return an error if hash table limit is reached.
*/
#define GREL_REMOVE_WHITE(greyctx, ge) do { \
ret = sm_grey_rm((greyctx)->greyc_grey_db, GE_KEY(ge), GE_LEN(ge));\
if (sm_is_err(ret)) \
goto error; \
} while (0)
#define GREL_REMOVE_GREY(greyctx, ge) do { \
ret = sm_grey_rm((greyctx)->greyc_grey_db, GE_KEY(ge), GE_LEN(ge));\
if (sm_is_err(ret)) \
goto error; \
} while (0)
#define GREL_APPEND_WHITE(greyctx, ge) do { \
ret = sm_grey_add((greyctx)->greyc_grey_db, (ge)); \
if (sm_is_err(ret)) \
goto error; \
} while (0)
#define GREL_APPEND_GREY(greyctx, ge) do { \
ret = sm_grey_add((greyctx)->greyc_grey_db, (ge)); \
if (sm_is_err(ret)) \
goto error; \
} while (0)
sm_ret_T
sm_greyctl(greyctx_P greyctx, uchar *key, uint keylen, time_t t)
{
sm_ret_T ret;
greyentry_T ges;
greyentry_P ge;
size_t len;
#if MTA_USE_PTHREADS
int r;
#endif
if (NULL == greyctx)
return sm_err_perm(SM_E_NOMAP);
SM_IS_GREYCTX(greyctx);
#if MTA_USE_PTHREADS
r = pthread_mutex_lock(&greyctx->greyc_mutex);
SM_LOCK_OK(r);
if (r != 0) {
/* LOG? */
return sm_err_perm(r);
}
#endif
ge = ⩾
ret = sm_grey_find(greyctx->greyc_grey_db, key, keylen, ge);
if (ret != 0) {
/* does not exist: add it and return "wait" */
if (greyctx->greyc_used >= greyctx->greyc_cnf.greycnf_limit)
ret = sm_grey_expire(greyctx, t);
++greyctx->greyc_used;
len = keylen;
if (len >= sizeof(ge->ge_key))
len = sizeof(ge->ge_key);
sm_memcpy(ge->ge_key, key, len);
ge->ge_len = len;
ge->ge_time = t;
ge->ge_expire = t + greyctx->greyc_cnf.greycnf_max_grey_wait;
ge->ge_type = GREY_TYPE_GREY;
ret = sm_grey_add(greyctx->greyc_grey_db, ge);
if (sm_is_err(ret)) {
SM_ASSERT(greyctx->greyc_used > 0);
--greyctx->greyc_used;
goto error;
}
ret = SM_GREY_FIRST;
}
else if (GREY_TYPE_WHITE == ge->ge_type)
{
if (ge->ge_time + greyctx->greyc_cnf.greycnf_white_reconfirm <= t) {
/* it's too old: need to reconfirm */
GREL_REMOVE_WHITE(greyctx, ge);
if (sm_is_err(ret))
goto error;
ge->ge_time = t;
ge->ge_expire = t + greyctx->greyc_cnf.greycnf_max_grey_wait;
ge->ge_type = GREY_TYPE_GREY;
GREL_APPEND_GREY(greyctx, ge);
ret = SM_GREY_AGAIN;
}
else if (ge->ge_time != t) {
/* update time stamps */
GREL_REMOVE_WHITE(greyctx, ge);
ge->ge_time = t;
ge->ge_expire = t + greyctx->greyc_cnf.greycnf_white_expire;
GREL_APPEND_WHITE(greyctx, ge);
ret = SM_GREY_WHITE;
}
else
ret = SM_GREY_WHITE;
}
else if (GREY_TYPE_GREY == ge->ge_type) {
if (ge->ge_time + greyctx->greyc_cnf.greycnf_max_grey_wait <= t) {
/* confirmation window passed; restart it */
GREL_REMOVE_GREY(greyctx, ge);
ge->ge_time = t;
ge->ge_expire = t + greyctx->greyc_cnf.greycnf_max_grey_wait;
GREL_APPEND_GREY(greyctx, ge);
ret = SM_GREY_WAIT;
}
else if (ge->ge_time + greyctx->greyc_cnf.greycnf_min_grey_wait <= t)
{
/* waiting time expired, within confirmation window */
GREL_REMOVE_GREY(greyctx, ge);
ge->ge_type = GREY_TYPE_WHITE;
ge->ge_time = t;
ge->ge_expire = t + greyctx->greyc_cnf.greycnf_white_expire;
GREL_APPEND_WHITE(greyctx, ge);
ret = SM_GREY_OK;
}
else
ret = SM_GREY_WAIT;
}
else
ret = sm_err_perm(SM_E_UNEXPECTED);
/* fall through for unlocking */
error:
#if MTA_USE_PTHREADS
r = pthread_mutex_unlock(&greyctx->greyc_mutex);
SM_ASSERT(0 == r);
/* r isn't checked further; will fail on next iteration */
#endif
return ret;
}
syntax highlighted by Code2HTML, v. 0.9.1