#include <sys_defs.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <msg.h>
#include <mymalloc.h>
#include <htable.h>
#include <dict_ht.h>
#include <dict.h>
#include <split_at.h>
#include <stringops.h>
#include <set_eugid.h>
#include <mail_conf.h>
#include <mail_params.h>
#include <mail_version.h>
#include <mail_proto.h>
#include <post_mail.h>
#include <data_redirect.h>
#include <sacl_cache_clnt.h>
#include <mail_server.h>
int var_sacl_cache_pos_exp;
int var_sacl_cache_neg_exp;
int var_sacl_cache_dis_exp;
static DICT *sacl_cache_map;
static int sacl_enabled = -1;
static long sacl_updated = 0;
static unsigned int sacl_generation = 0;
#define STR(x) vstring_str(x)
#define STREQ(x,y) (strcmp(x,y) == 0)
#define STATUS_FROM_RAW_ENTRY(e) atoi(e)
static void sacl_cache_make_entry(VSTRING *buf, unsigned int generation,
int status, long updated)
{
vstring_sprintf(buf, "%u:%d:%ld", generation, status, updated);
}
static int sacl_cache_parse_entry(char *buf, unsigned int *generation,
int *status, long *updated)
{
char *status_text;
char *updated_text;
if ((status_text = split_at(buf, ':')) != 0
&& (updated_text = split_at(status_text, ':')) != 0
&& alldig(buf)
&& alldig(status_text)
&& alldig(updated_text)) {
*generation = (unsigned int) atol(buf);
*status = atoi(status_text);
*updated = atol(updated_text);
if ((*status == SACL_CHECK_STATUS_AUTHORIZED ||
*status == SACL_CHECK_STATUS_UNAUTHORIZED) && *updated)
return (0);
}
msg_warn("bad sacl-cache table entry: %.100s", buf);
return (-1);
}
static void sacl_cache_put(VSTREAM *client_stream)
{
VSTRING *buf = vstring_alloc(10);
VSTRING *addr = vstring_alloc(10);
int sacl_status;
long updated;
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
ATTR_TYPE_INT, MAIL_ATTR_SACL_STATUS, &sacl_status,
ATTR_TYPE_END) == 2) {
translit(STR(addr), ":", "_");
if (sacl_status != SACL_CHECK_STATUS_AUTHORIZED &&
sacl_status != SACL_CHECK_STATUS_UNAUTHORIZED) {
msg_warn("bad sacl-cache status %d for recipient %s",
sacl_status, STR(addr));
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, SACL_CACHE_STAT_BAD,
ATTR_TYPE_END);
} else {
updated = (long) time((time_t *) 0);
sacl_cache_make_entry(buf, sacl_generation, sacl_status, updated);
if (msg_verbose)
msg_info("PUT %s gen=%u status=%d updated=%ld",
STR(addr), sacl_generation, sacl_status, updated);
dict_put(sacl_cache_map, STR(addr), STR(buf));
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, SACL_CACHE_STAT_OK,
ATTR_TYPE_END);
sacl_enabled = 1;
sacl_updated = updated;
}
}
vstring_free(buf);
vstring_free(addr);
}
static void sacl_cache_get(VSTREAM *client_stream)
{
VSTRING *addr = vstring_alloc(10);
VSTRING *get_buf = 0;
const char *raw_data;
unsigned int generation = sacl_generation;
int sacl_status = -1;
long updated = 0;
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
ATTR_TYPE_END) == 1) {
long now = (long) time((time_t *) 0);
#define POSITIVE_ENTRY_EXPIRED(sacl_status, updated) \
(sacl_status == SACL_CHECK_STATUS_AUTHORIZED && updated + var_sacl_cache_pos_exp < now)
#define NEGATIVE_ENTRY_EXPIRED(sacl_status, updated) \
(sacl_status != SACL_CHECK_STATUS_AUTHORIZED && updated + var_sacl_cache_neg_exp < now)
translit(STR(addr), ":", "_");
if (!sacl_enabled && sacl_updated + var_sacl_cache_dis_exp >= now) {
sacl_status = SACL_CHECK_STATUS_NO_SACL;
} else if ((raw_data = dict_get(sacl_cache_map,
STR(addr))) == 0
|| ((get_buf = vstring_alloc(10)),
vstring_strcpy(get_buf, raw_data),
sacl_cache_parse_entry(STR(get_buf), &generation, &sacl_status,
&updated) < 0)
|| generation != sacl_generation
|| POSITIVE_ENTRY_EXPIRED(sacl_status, updated)
|| NEGATIVE_ENTRY_EXPIRED(sacl_status, updated)) {
sacl_status = SACL_CHECK_STATUS_UNKNOWN;
updated = 0;
}
if (msg_verbose)
msg_info("GOT %s status=%d updated=%ld",
STR(addr), sacl_status, updated);
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, SACL_CACHE_STAT_OK,
ATTR_TYPE_INT, MAIL_ATTR_SACL_STATUS, sacl_status,
ATTR_TYPE_END);
}
vstring_free(addr);
if (get_buf)
vstring_free(get_buf);
}
static void sacl_cache_no_sacl(VSTREAM *client_stream)
{
if (sacl_enabled) {
sacl_enabled = 0;
sacl_generation++;
}
sacl_updated = (long) time((time_t *) 0);
if (msg_verbose)
msg_info("NO-SACL");
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, SACL_CACHE_STAT_OK,
ATTR_TYPE_END);
}
static void sacl_cache_service(VSTREAM *client_stream, char *unused_service,
char **argv)
{
VSTRING *request = vstring_alloc(10);
if (argv[0])
msg_fatal("unexpected command-line argument: %s", argv[0]);
if (attr_scan(client_stream,
ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_REQ, request,
ATTR_TYPE_END) == 1) {
if (STREQ(STR(request), SACL_CACHE_REQ_PUT)) {
sacl_cache_put(client_stream);
} else if (STREQ(STR(request), SACL_CACHE_REQ_GET)) {
sacl_cache_get(client_stream);
} else if (STREQ(STR(request), SACL_CACHE_REQ_NO_SACL)) {
sacl_cache_no_sacl(client_stream);
} else {
msg_warn("unrecognized request: \"%s\", ignored", STR(request));
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, SACL_CACHE_STAT_BAD,
ATTR_TYPE_END);
}
}
vstream_fflush(client_stream);
vstring_free(request);
}
static void post_jail_init(char *unused_name, char **unused_argv)
{
var_use_limit = 0;
var_idle_limit = 0;
}
static void pre_jail_init(char *unused_name, char **unused_argv)
{
setsid();
sacl_cache_map = dict_ht_open("sacl-cache", O_CREAT | O_RDWR, 0);
}
MAIL_VERSION_STAMP_DECLARE;
int main(int argc, char **argv)
{
static const CONFIG_TIME_TABLE time_table[] = {
VAR_SACL_CACHE_POS_EXP, DEF_SACL_CACHE_POS_EXP, &var_sacl_cache_pos_exp, 1, 0,
VAR_SACL_CACHE_NEG_EXP, DEF_SACL_CACHE_NEG_EXP, &var_sacl_cache_neg_exp, 1, 0,
VAR_SACL_CACHE_DIS_EXP, DEF_SACL_CACHE_DIS_EXP, &var_sacl_cache_dis_exp, 1, 0,
0,
};
MAIL_VERSION_STAMP_ALLOCATE;
multi_server_main(argc, argv, sacl_cache_service,
MAIL_SERVER_TIME_TABLE, time_table,
MAIL_SERVER_PRE_INIT, pre_jail_init,
MAIL_SERVER_POST_INIT, post_jail_init,
MAIL_SERVER_SOLITARY,
0);
}