#include <sys_defs.h>
#include <sys/time.h>
#include <limits.h>
#include <msg.h>
#include <mymalloc.h>
#include <htable.h>
#include <stringops.h>
#include <events.h>
#include <mail_conf.h>
#include <mail_params.h>
#include <mail_version.h>
#include <mail_proto.h>
#include <anvil_clnt.h>
#include <mail_server.h>
int var_anvil_time_unit;
int var_anvil_stat_time;
static HTABLE *anvil_remote_map;
typedef struct {
char *ident;
int count;
int rate;
int mail;
int rcpt;
int ntls;
time_t start;
} ANVIL_REMOTE;
typedef struct {
ANVIL_REMOTE *anvil_remote;
} ANVIL_LOCAL;
#define ANVIL_REMOTE_FIRST_CONN(remote, id) \
do { \
(remote)->ident = mystrdup(id); \
(remote)->count = 1; \
(remote)->rate = 1; \
(remote)->mail = 0; \
(remote)->rcpt = 0; \
(remote)->ntls = 0; \
(remote)->start = event_time(); \
} while(0)
#define ANVIL_REMOTE_FREE(remote) \
do { \
myfree((remote)->ident); \
myfree((char *) (remote)); \
} while(0)
#define ANVIL_REMOTE_RSET_RATE(remote, _start) \
do { \
(remote)->rate = 0; \
(remote)->mail = 0; \
(remote)->rcpt = 0; \
(remote)->ntls = 0; \
(remote)->start = _start; \
} while(0)
#define ANVIL_REMOTE_INCR_RATE(remote, _what) \
do { \
time_t _now = event_time(); \
if ((remote)->start + var_anvil_time_unit < _now) \
ANVIL_REMOTE_RSET_RATE((remote), _now); \
if ((remote)->_what < INT_MAX) \
(remote)->_what += 1; \
} while(0)
#define ANVIL_REMOTE_NEXT_CONN(remote) \
do { \
ANVIL_REMOTE_INCR_RATE((remote), rate); \
if ((remote)->count == 0) \
event_cancel_timer(anvil_remote_expire, (char *) remote); \
(remote)->count++; \
} while(0)
#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail)
#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt)
#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls)
#define ANVIL_REMOTE_DROP_ONE(remote) \
do { \
if ((remote) && (remote)->count > 0) { \
if (--(remote)->count == 0) \
event_request_timer(anvil_remote_expire, (char *) remote, \
var_anvil_time_unit); \
} \
} while(0)
#define ANVIL_LOCAL_INIT(local) \
do { \
(local)->anvil_remote = 0; \
} while(0)
#define ANVIL_LOCAL_ADD_ONE(local, remote) \
do { \
\
if ((local)->anvil_remote) \
ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \
(local)->anvil_remote = (remote); \
} while(0)
#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
((local)->anvil_remote == (remote))
#define ANVIL_LOCAL_DROP_ONE(local, remote) \
do { \
\
if ((local)->anvil_remote == (remote)) \
(local)->anvil_remote = 0; \
} while(0)
#define ANVIL_LOCAL_DROP_ALL(stream, local) \
do { \
\
if ((local)->anvil_remote) \
anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \
} while (0)
typedef struct {
const char *name;
void (*action) (VSTREAM *, const char *);
} ANVIL_REQ_TABLE;
typedef struct {
int value;
char *ident;
time_t when;
} ANVIL_MAX;
static ANVIL_MAX max_conn_count;
static ANVIL_MAX max_conn_rate;
static ANVIL_MAX max_mail_rate;
static ANVIL_MAX max_rcpt_rate;
static ANVIL_MAX max_ntls_rate;
static int max_cache_size;
static time_t max_cache_time;
#define ANVIL_MAX_UPDATE(_max, _value, _ident) \
do { \
_max.value = _value; \
if (_max.ident == 0) { \
_max.ident = mystrdup(_ident); \
} else if (!STREQ(_max.ident, _ident)) { \
myfree(_max.ident); \
_max.ident = mystrdup(_ident); \
} \
_max.when = event_time(); \
} while (0)
#define ANVIL_MAX_RATE_REPORT(_max, _name) \
do { \
if (_max.value > 0) { \
msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \
_max.value, var_anvil_time_unit, \
_max.ident, ctime(&_max.when) + 4); \
_max.value = 0; \
} \
} while (0);
#define ANVIL_MAX_COUNT_REPORT(_max, _name) \
do { \
if (_max.value > 0) { \
msg_info("statistics: max " _name " count %d for (%s) at %.15s", \
_max.value, _max.ident, ctime(&_max.when) + 4); \
_max.value = 0; \
} \
} while (0);
#define STR(x) vstring_str(x)
#define STREQ(x,y) (strcmp((x), (y)) == 0)
static void anvil_remote_expire(int unused_event, char *context)
{
ANVIL_REMOTE *anvil_remote = (ANVIL_REMOTE *) context;
const char *myname = "anvil_remote_expire";
if (msg_verbose)
msg_info("%s %s", myname, anvil_remote->ident);
if (anvil_remote->count != 0)
msg_panic("%s: bad connection count: %d",
myname, anvil_remote->count);
htable_delete(anvil_remote_map, anvil_remote->ident,
(void (*) (char *)) 0);
ANVIL_REMOTE_FREE(anvil_remote);
if (msg_verbose)
msg_info("%s: anvil_remote_map used=%d",
myname, anvil_remote_map->used);
}
static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
const char *myname = "anvil_remote_lookup";
if (msg_verbose)
msg_info("%s fd=%d stream=0x%lx ident=%s",
myname, vstream_fileno(client_stream),
(unsigned long) client_stream, ident);
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_COUNT, 0,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, 0,
ATTR_TYPE_INT, ANVIL_ATTR_MAIL, 0,
ATTR_TYPE_INT, ANVIL_ATTR_RCPT, 0,
ATTR_TYPE_INT, ANVIL_ATTR_NTLS, 0,
ATTR_TYPE_END);
} else {
if (anvil_remote->start != 0
&& anvil_remote->start + var_anvil_time_unit < event_time())
ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_COUNT, anvil_remote->count,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rate,
ATTR_TYPE_INT, ANVIL_ATTR_MAIL, anvil_remote->mail,
ATTR_TYPE_INT, ANVIL_ATTR_RCPT, anvil_remote->rcpt,
ATTR_TYPE_INT, ANVIL_ATTR_NTLS, anvil_remote->ntls,
ATTR_TYPE_END);
}
}
static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
ANVIL_LOCAL *anvil_local;
const char *myname = "anvil_remote_conn_update";
if (msg_verbose)
msg_info("%s fd=%d stream=0x%lx ident=%s",
myname, vstream_fileno(client_stream),
(unsigned long) client_stream, ident);
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
anvil_remote = (ANVIL_REMOTE *) mymalloc(sizeof(*anvil_remote));
ANVIL_REMOTE_FIRST_CONN(anvil_remote, ident);
htable_enter(anvil_remote_map, ident, (char *) anvil_remote);
if (max_cache_size < anvil_remote_map->used) {
max_cache_size = anvil_remote_map->used;
max_cache_time = event_time();
}
} else {
ANVIL_REMOTE_NEXT_CONN(anvil_remote);
}
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) {
anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local));
ANVIL_LOCAL_INIT(anvil_local);
vstream_control(client_stream,
VSTREAM_CTL_CONTEXT, (void *) anvil_local,
VSTREAM_CTL_END);
}
ANVIL_LOCAL_ADD_ONE(anvil_local, anvil_remote);
if (msg_verbose)
msg_info("%s: anvil_local 0x%lx",
myname, (unsigned long) anvil_local);
return (anvil_remote);
}
static void anvil_remote_connect(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
anvil_remote = anvil_remote_conn_update(client_stream, ident);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_COUNT, anvil_remote->count,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rate,
ATTR_TYPE_END);
if (anvil_remote->rate > max_conn_rate.value)
ANVIL_MAX_UPDATE(max_conn_rate, anvil_remote->rate, anvil_remote->ident);
if (anvil_remote->count > max_conn_count.value)
ANVIL_MAX_UPDATE(max_conn_count, anvil_remote->count, anvil_remote->ident);
}
static void anvil_remote_mail(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
anvil_remote = anvil_remote_conn_update(client_stream, ident);
ANVIL_REMOTE_INCR_MAIL(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->mail,
ATTR_TYPE_END);
if (anvil_remote->mail > max_mail_rate.value)
ANVIL_MAX_UPDATE(max_mail_rate, anvil_remote->mail, anvil_remote->ident);
}
static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
anvil_remote = anvil_remote_conn_update(client_stream, ident);
ANVIL_REMOTE_INCR_RCPT(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rcpt,
ATTR_TYPE_END);
if (anvil_remote->rcpt > max_rcpt_rate.value)
ANVIL_MAX_UPDATE(max_rcpt_rate, anvil_remote->rcpt, anvil_remote->ident);
}
static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
anvil_remote = anvil_remote_conn_update(client_stream, ident);
ANVIL_REMOTE_INCR_NTLS(anvil_remote);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->ntls,
ATTR_TYPE_END);
if (anvil_remote->ntls > max_ntls_rate.value)
ANVIL_MAX_UPDATE(max_ntls_rate, anvil_remote->ntls, anvil_remote->ident);
}
static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
int rate;
if ((anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
rate = 0;
}
else {
if (anvil_remote->start != 0
&& anvil_remote->start + var_anvil_time_unit < event_time())
ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
rate = anvil_remote->ntls;
}
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_INT, ANVIL_ATTR_RATE, rate,
ATTR_TYPE_END);
}
static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident)
{
ANVIL_REMOTE *anvil_remote;
ANVIL_LOCAL *anvil_local;
const char *myname = "anvil_remote_disconnect";
if (msg_verbose)
msg_info("%s fd=%d stream=0x%lx ident=%s",
myname, vstream_fileno(client_stream),
(unsigned long) client_stream, ident);
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0
&& (anvil_remote =
(ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) != 0
&& ANVIL_LOCAL_REMOTE_LINKED(anvil_local, anvil_remote)) {
ANVIL_REMOTE_DROP_ONE(anvil_remote);
ANVIL_LOCAL_DROP_ONE(anvil_local, anvil_remote);
}
if (msg_verbose)
msg_info("%s: anvil_local 0x%lx",
myname, (unsigned long) anvil_local);
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
ATTR_TYPE_END);
}
static void anvil_service_done(VSTREAM *client_stream, char *unused_service,
char **unused_argv)
{
ANVIL_LOCAL *anvil_local;
const char *myname = "anvil_service_done";
if (msg_verbose)
msg_info("%s fd=%d stream=0x%lx",
myname, vstream_fileno(client_stream),
(unsigned long) client_stream);
if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) {
if (msg_verbose)
msg_info("%s: anvil_local 0x%lx",
myname, (unsigned long) anvil_local);
ANVIL_LOCAL_DROP_ALL(client_stream, anvil_local);
myfree((char *) anvil_local);
} else if (msg_verbose)
msg_info("client socket not found for fd=%d",
vstream_fileno(client_stream));
}
static void anvil_status_dump(char *unused_name, char **unused_argv)
{
ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection");
ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection");
ANVIL_MAX_RATE_REPORT(max_mail_rate, "message");
ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient");
ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls");
if (max_cache_size > 0) {
msg_info("statistics: max cache size %d at %.15s",
max_cache_size, ctime(&max_cache_time) + 4);
max_cache_size = 0;
}
}
static void anvil_status_update(int unused_event, char *context)
{
anvil_status_dump((char *) 0, (char **) 0);
event_request_timer(anvil_status_update, context, var_anvil_stat_time);
}
static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv)
{
static VSTRING *request;
static VSTRING *ident;
static const ANVIL_REQ_TABLE request_table[] = {
ANVIL_REQ_CONN, anvil_remote_connect,
ANVIL_REQ_MAIL, anvil_remote_mail,
ANVIL_REQ_RCPT, anvil_remote_rcpt,
ANVIL_REQ_NTLS, anvil_remote_newtls,
ANVIL_REQ_DISC, anvil_remote_disconnect,
ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat,
ANVIL_REQ_LOOKUP, anvil_remote_lookup,
0, 0,
};
const ANVIL_REQ_TABLE *rp;
if (argv[0])
msg_fatal("unexpected command-line argument: %s", argv[0]);
if (request == 0) {
request = vstring_alloc(10);
ident = vstring_alloc(10);
}
if (msg_verbose)
msg_info("--- start request ---");
if (attr_scan_plain(client_stream,
ATTR_FLAG_MISSING | ATTR_FLAG_STRICT,
ATTR_TYPE_STR, ANVIL_ATTR_REQ, request,
ATTR_TYPE_STR, ANVIL_ATTR_IDENT, ident,
ATTR_TYPE_END) == 2) {
for (rp = request_table; ; rp++) {
if (rp->name == 0) {
msg_warn("unrecognized request: \"%s\", ignored", STR(request));
attr_print_plain(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_FAIL,
ATTR_TYPE_END);
break;
}
if (STREQ(rp->name, STR(request))) {
rp->action(client_stream, STR(ident));
break;
}
}
vstream_fflush(client_stream);
} else {
multi_server_disconnect(client_stream);
}
if (msg_verbose)
msg_info("--- end request ---");
}
static void post_jail_init(char *unused_name, char **unused_argv)
{
event_request_timer(anvil_status_update, (char *) 0, var_anvil_stat_time);
anvil_remote_map = htable_create(1000);
var_use_limit = 0;
if (var_idle_limit < var_anvil_time_unit)
var_idle_limit = var_anvil_time_unit;
}
MAIL_VERSION_STAMP_DECLARE;
int main(int argc, char **argv)
{
static const CONFIG_TIME_TABLE time_table[] = {
VAR_ANVIL_TIME_UNIT, DEF_ANVIL_TIME_UNIT, &var_anvil_time_unit, 1, 0,
VAR_ANVIL_STAT_TIME, DEF_ANVIL_STAT_TIME, &var_anvil_stat_time, 1, 0,
0,
};
MAIL_VERSION_STAMP_ALLOCATE;
multi_server_main(argc, argv, anvil_service,
MAIL_SERVER_TIME_TABLE, time_table,
MAIL_SERVER_POST_INIT, post_jail_init,
MAIL_SERVER_SOLITARY,
MAIL_SERVER_PRE_DISCONN, anvil_service_done,
MAIL_SERVER_EXIT, anvil_status_dump,
0);
}