|
|
@@ -0,0 +1,358 @@ |
|
|
|
/* |
|
|
|
* Copyright (c) 2009, Rambler media |
|
|
|
* All rights reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions are met: |
|
|
|
* * Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* * Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in the |
|
|
|
* documentation and/or other materials provided with the distribution. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED BY Rambler media ''AS IS'' AND ANY |
|
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
* DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY |
|
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
*/ |
|
|
|
|
|
|
|
/* |
|
|
|
* Store greylisting data in memory |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "config.h" |
|
|
|
#include "util.h" |
|
|
|
#include "main.h" |
|
|
|
#include "protocol.h" |
|
|
|
#include "upstream.h" |
|
|
|
#include "cfg_file.h" |
|
|
|
#include "url.h" |
|
|
|
#include "modules.h" |
|
|
|
#include "message.h" |
|
|
|
#include "greylist.h" |
|
|
|
|
|
|
|
#ifdef WITH_JUDY |
|
|
|
#include <Judy.h> |
|
|
|
#endif |
|
|
|
|
|
|
|
/* Number of insuccessfull bind retries */ |
|
|
|
#define MAX_RETRIES 40 |
|
|
|
|
|
|
|
struct greylist_ctx { |
|
|
|
#ifdef WITH_JUDY |
|
|
|
Pvoid_t jtree; |
|
|
|
#else |
|
|
|
GTree *tree; |
|
|
|
#endif |
|
|
|
time_t greylist_time; |
|
|
|
time_t expire_time; |
|
|
|
}; |
|
|
|
|
|
|
|
#ifndef HAVE_SA_SIGINFO |
|
|
|
static void |
|
|
|
sig_handler (int signo) |
|
|
|
#else |
|
|
|
static void |
|
|
|
sig_handler (int signo, siginfo_t *info, void *unused) |
|
|
|
#endif |
|
|
|
{ |
|
|
|
switch (signo) { |
|
|
|
case SIGINT: |
|
|
|
/* Ignore SIGINT as we should got SIGTERM after it anyway */ |
|
|
|
return; |
|
|
|
case SIGTERM: |
|
|
|
#ifdef WITH_PROFILER |
|
|
|
exit (0); |
|
|
|
#else |
|
|
|
_exit (1); |
|
|
|
#endif |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
sigterm_handler (int fd, short what, void *arg) |
|
|
|
{ |
|
|
|
struct rspamd_worker *worker = (struct rspamd_worker *)arg; |
|
|
|
static struct timeval tv = { |
|
|
|
.tv_sec = 0, |
|
|
|
.tv_usec = 0 |
|
|
|
}; |
|
|
|
|
|
|
|
close (worker->cf->listen_sock); |
|
|
|
(void)event_loopexit (&tv); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Config reload is designed by sending sigusr to active workers and pending shutdown of them |
|
|
|
*/ |
|
|
|
static void |
|
|
|
sigusr_handler (int fd, short what, void *arg) |
|
|
|
{ |
|
|
|
struct rspamd_worker *worker = (struct rspamd_worker *)arg; |
|
|
|
/* Do not accept new connections, preparing to end worker's process */ |
|
|
|
struct timeval tv; |
|
|
|
|
|
|
|
tv.tv_sec = SOFT_SHUTDOWN_TIME; |
|
|
|
tv.tv_usec = 0; |
|
|
|
event_del (&worker->sig_ev); |
|
|
|
event_del (&worker->bind_ev); |
|
|
|
close (worker->cf->listen_sock); |
|
|
|
do_reopen_log = 1; |
|
|
|
msg_info ("worker's shutdown is pending in %d sec", SOFT_SHUTDOWN_TIME); |
|
|
|
event_loopexit (&tv); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
struct greylist_session { |
|
|
|
struct rspamd_worker *worker; |
|
|
|
int fd; |
|
|
|
socklen_t salen; |
|
|
|
struct sockaddr_storage sa; |
|
|
|
guint8 *pos; |
|
|
|
struct rspamd_grey_command cmd; |
|
|
|
}; |
|
|
|
|
|
|
|
static gint |
|
|
|
grey_cmp (gconstpointer a, gconstpointer b, gpointer unused) |
|
|
|
{ |
|
|
|
return memcmp (a, b, CHECKSUM_SIZE); |
|
|
|
} |
|
|
|
|
|
|
|
static gint |
|
|
|
greylist_process_add_command (struct rspamd_grey_command *cmd, struct greylist_ctx *ctx) |
|
|
|
{ |
|
|
|
struct rspamd_grey_reply reply; |
|
|
|
struct rspamd_grey_item *item, **pitem = NULL; |
|
|
|
|
|
|
|
item = g_malloc (sizeof (struct rspamd_grey_item)); |
|
|
|
item->age = time (NULL); |
|
|
|
memcpy (item->data, cmd->data, CHECKSUM_SIZE); |
|
|
|
#ifdef WITH_JUDY |
|
|
|
|
|
|
|
JHSI (pitem, ctx->jtree, item->data, CHECKSUM_SIZE); |
|
|
|
if (pitem == PJERR) { |
|
|
|
reply.reply = GREY_ERR; |
|
|
|
} |
|
|
|
else if (*pitem != 0) { |
|
|
|
g_free (*pitem); |
|
|
|
*pitem = item; |
|
|
|
} |
|
|
|
else { |
|
|
|
*pitem = item; |
|
|
|
} |
|
|
|
#else |
|
|
|
g_tree_insert (ctx->tree, item->data, item); |
|
|
|
reply.reply = GREY_OK; |
|
|
|
#endif |
|
|
|
|
|
|
|
return reply.reply; |
|
|
|
} |
|
|
|
|
|
|
|
static gint |
|
|
|
greylist_process_delete_command (struct rspamd_grey_command *cmd, struct greylist_ctx *ctx) |
|
|
|
{ |
|
|
|
struct rspamd_grey_reply reply; |
|
|
|
#ifdef WITH_JUDY |
|
|
|
int rc; |
|
|
|
struct rspamd_grey_item **pitem = NULL; |
|
|
|
|
|
|
|
JHSG (pitem, ctx->jtree, cmd->data, CHECKSUM_SIZE); |
|
|
|
if (pitem != NULL) { |
|
|
|
g_free (*pitem); |
|
|
|
JHSD (rc, ctx->jtree, cmd->data, CHECKSUM_SIZE); |
|
|
|
if (rc == 1) { |
|
|
|
reply.reply = GREY_OK; |
|
|
|
} |
|
|
|
else { |
|
|
|
reply.reply = GREY_NOT_FOUND; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
reply.reply = GREY_NOT_FOUND; |
|
|
|
} |
|
|
|
#else |
|
|
|
if(g_tree_remove (ctx->tree, cmd->data)) { |
|
|
|
reply.reply = GREY_OK; |
|
|
|
} |
|
|
|
else { |
|
|
|
reply.reply = GREY_NOT_FOUND; |
|
|
|
} |
|
|
|
#endif |
|
|
|
return reply.reply; |
|
|
|
} |
|
|
|
|
|
|
|
static gint |
|
|
|
greylist_process_check_command (struct rspamd_grey_command *cmd, struct greylist_ctx *ctx) |
|
|
|
{ |
|
|
|
struct rspamd_grey_reply reply; |
|
|
|
struct rspamd_grey_item *item = NULL, **pitem = NULL; |
|
|
|
time_t now; |
|
|
|
|
|
|
|
now = time (NULL); |
|
|
|
#ifdef WITH_JUDY |
|
|
|
JHSG (pitem, ctx->jtree, cmd->data, CHECKSUM_SIZE); |
|
|
|
if (pitem != NULL) { |
|
|
|
item = *pitem; |
|
|
|
} |
|
|
|
#else |
|
|
|
item = g_tree_lookup (ctx->tree, cmd->data); |
|
|
|
#endif |
|
|
|
if (item) { |
|
|
|
if (now - item->age > ctx->expire_time) { |
|
|
|
/* Remove expired item */ |
|
|
|
reply.reply = GREY_EXPIRED; |
|
|
|
greylist_process_delete_command (cmd, ctx); |
|
|
|
} |
|
|
|
else if (now - item->age > ctx->greylist_time) { |
|
|
|
reply.reply = GREY_OK; |
|
|
|
} |
|
|
|
else { |
|
|
|
reply.reply = GREY_GREYLISTED; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
reply.reply = GREY_NOT_FOUND; |
|
|
|
} |
|
|
|
|
|
|
|
return reply.reply; |
|
|
|
} |
|
|
|
|
|
|
|
#define CMD_PROCESS(x) \ |
|
|
|
do { \ |
|
|
|
reply.reply = greylist_process_##x##_command (&session->cmd, (struct greylist_ctx *)session->worker->ctx); \ |
|
|
|
if (sendto (session->fd, &reply, sizeof (reply), 0, (struct sockaddr *)&session->sa, session->salen) == -1) { \ |
|
|
|
msg_err ("error while writing reply: %s", strerror (errno)); \ |
|
|
|
} \ |
|
|
|
} while(0) |
|
|
|
|
|
|
|
static void |
|
|
|
process_greylist_command (struct greylist_session *session) |
|
|
|
{ |
|
|
|
struct rspamd_grey_reply reply; |
|
|
|
|
|
|
|
switch (session->cmd.cmd) { |
|
|
|
case GREY_CMD_CHECK: |
|
|
|
CMD_PROCESS (check); |
|
|
|
break; |
|
|
|
case GREY_CMD_ADD: |
|
|
|
CMD_PROCESS (add); |
|
|
|
break; |
|
|
|
case GREY_CMD_DEL: |
|
|
|
CMD_PROCESS (delete); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#undef CMD_PROCESS |
|
|
|
|
|
|
|
/* |
|
|
|
* Accept new connection and construct task |
|
|
|
*/ |
|
|
|
static void |
|
|
|
accept_greylist_socket (int fd, short what, void *arg) |
|
|
|
{ |
|
|
|
struct rspamd_worker *worker = (struct rspamd_worker *)arg; |
|
|
|
struct greylist_session session; |
|
|
|
ssize_t r; |
|
|
|
|
|
|
|
session.worker = worker; |
|
|
|
session.fd = fd; |
|
|
|
session.pos = (guint8 *) & session.cmd; |
|
|
|
session.salen = sizeof (session.sa); |
|
|
|
|
|
|
|
/* Got some data */ |
|
|
|
if (what == EV_READ) { |
|
|
|
if ((r = recvfrom (fd, session.pos, sizeof (struct rspamd_grey_command), MSG_WAITALL, (struct sockaddr *)&session.sa, &session.salen)) == -1) { |
|
|
|
msg_err ("got error while reading from socket: %d, %s", errno, strerror (errno)); |
|
|
|
return; |
|
|
|
} |
|
|
|
else if (r == sizeof (struct rspamd_grey_command)) { |
|
|
|
/* Assume that the whole command was read */ |
|
|
|
process_greylist_command (&session); |
|
|
|
} |
|
|
|
else { |
|
|
|
msg_err ("got incomplete data while reading from socket: %d, %s", errno, strerror (errno)); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static gboolean |
|
|
|
config_greylist_worker (struct rspamd_worker *worker) |
|
|
|
{ |
|
|
|
struct greylist_ctx *ctx; |
|
|
|
char *value; |
|
|
|
|
|
|
|
ctx = g_malloc0 (sizeof (struct greylist_ctx)); |
|
|
|
#ifdef WITH_JUDY |
|
|
|
ctx->jtree = NULL; |
|
|
|
#else |
|
|
|
ctx->tree = g_tree_new_full (grey_cmp, NULL, NULL, g_free); |
|
|
|
#endif |
|
|
|
|
|
|
|
ctx->greylist_time = DEFAULT_GREYLIST_TIME; |
|
|
|
ctx->expire_time = DEFAULT_EXPIRE_TIME; |
|
|
|
|
|
|
|
if ((value = g_hash_table_lookup (worker->cf->params, "greylist_time")) != NULL) { |
|
|
|
ctx->greylist_time = parse_seconds (value) / 1000; |
|
|
|
} |
|
|
|
if ((value = g_hash_table_lookup (worker->cf->params, "expire_time")) != NULL) { |
|
|
|
ctx->expire_time = parse_seconds (value) / 1000; |
|
|
|
} |
|
|
|
worker->ctx = ctx; |
|
|
|
|
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Start worker process |
|
|
|
*/ |
|
|
|
void |
|
|
|
start_greylist_storage (struct rspamd_worker *worker) |
|
|
|
{ |
|
|
|
struct sigaction signals; |
|
|
|
struct event sev; |
|
|
|
int retries = 0; |
|
|
|
|
|
|
|
worker->srv->pid = getpid (); |
|
|
|
worker->srv->type = TYPE_GREYLIST; |
|
|
|
|
|
|
|
event_init (); |
|
|
|
|
|
|
|
init_signals (&signals, sig_handler); |
|
|
|
sigprocmask (SIG_UNBLOCK, &signals.sa_mask, NULL); |
|
|
|
|
|
|
|
/* SIGUSR2 handler */ |
|
|
|
signal_set (&worker->sig_ev, SIGUSR2, sigusr_handler, (void *)worker); |
|
|
|
signal_add (&worker->sig_ev, NULL); |
|
|
|
signal_set (&sev, SIGTERM, sigterm_handler, (void *)worker); |
|
|
|
signal_add (&sev, NULL); |
|
|
|
|
|
|
|
/* Accept event */ |
|
|
|
while ((worker->cf->listen_sock = make_udp_socket (&worker->cf->bind_addr, worker->cf->bind_port, TRUE, TRUE)) == -1) { |
|
|
|
sleep (1); |
|
|
|
if (++retries > MAX_RETRIES) { |
|
|
|
msg_err ("cannot bind to socket, exiting"); |
|
|
|
exit (EXIT_SUCCESS); |
|
|
|
} |
|
|
|
} |
|
|
|
event_set (&worker->bind_ev, worker->cf->listen_sock, EV_READ | EV_PERSIST, accept_greylist_socket, (void *)worker); |
|
|
|
event_add (&worker->bind_ev, NULL); |
|
|
|
|
|
|
|
gperf_profiler_init (worker->srv->cfg, "greylist"); |
|
|
|
|
|
|
|
if (!config_greylist_worker (worker)) { |
|
|
|
msg_err ("cannot configure greylisting worker, exiting"); |
|
|
|
exit (EXIT_SUCCESS); |
|
|
|
} |
|
|
|
|
|
|
|
event_loop (0); |
|
|
|
exit (EXIT_SUCCESS); |
|
|
|
} |