From ce482fbf8e2c1b9bf0cdba0ab61bc26985ee8977 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Fri, 2 Oct 2009 20:23:16 +0400 Subject: [PATCH] * Add reject score setting for hard rejecting of mail (by Anton Nekhoroshin) * Add rspamc protocol 1.1 (3 marks instead of 2) --- rspamc.pl.in | 2 +- src/cfg_file.h | 3 +++ src/cfg_file.l | 1 + src/cfg_file.y | 18 ++++++++++++++- src/cfg_utils.c | 1 + src/filter.c | 12 +++++----- src/filter.h | 1 + src/main.h | 1 + src/protocol.c | 61 +++++++++++++++++++++++++++++++++++++------------ src/protocol.h | 7 ++++-- src/settings.c | 34 +++++++++++++++++++++++++-- src/settings.h | 5 ++-- 12 files changed, 118 insertions(+), 28 deletions(-) diff --git a/rspamc.pl.in b/rspamc.pl.in index e82ec0b47..17e830e3b 100755 --- a/rspamc.pl.in +++ b/rspamc.pl.in @@ -120,7 +120,7 @@ sub do_rspamc_command { print "Sending ". length ($input) ." bytes...\n"; - syswrite $sock, "$cfg{'command'} RSPAMC/1.0 $CRLF"; + syswrite $sock, "$cfg{'command'} RSPAMC/1.1 $CRLF"; if ($cfg{'deliver_to'}) { syswrite $sock, "Deliver-To: " . $cfg{'deliver_to'} . $CRLF; } diff --git a/src/cfg_file.h b/src/cfg_file.h index 527c3f7c6..afa150a4c 100644 --- a/src/cfg_file.h +++ b/src/cfg_file.h @@ -29,6 +29,9 @@ /* 1 worker by default */ #define DEFAULT_WORKERS_NUM 1 +#define DEFAULT_SCORE 10.0 +#define DEFAULT_REJECT_SCORE 999.0 + #define yyerror parse_err #define yywarn parse_warn diff --git a/src/cfg_file.l b/src/cfg_file.l index 7646c87cf..671146be1 100644 --- a/src/cfg_file.l +++ b/src/cfg_file.l @@ -66,6 +66,7 @@ factors return FACTORS; metric return METRIC; name return NAME; required_score return REQUIRED_SCORE; +reject_score return REJECT_SCORE; function return FUNCTION; cache_file return CACHE_FILE; control return CONTROL; diff --git a/src/cfg_file.y b/src/cfg_file.y index 7d9316b60..85dcb9df5 100644 --- a/src/cfg_file.y +++ b/src/cfg_file.y @@ -52,7 +52,7 @@ struct rspamd_view *cur_view = NULL; %token MEMCACHED WORKER TYPE MODULES MODULE_PATH %token MODULE_OPT PARAM VARIABLE %token FILTERS FACTORS METRIC NAME CACHE_FILE -%token REQUIRED_SCORE FUNCTION FRACT COMPOSITES CONTROL PASSWORD +%token REQUIRED_SCORE REJECT_SCORE FUNCTION FRACT COMPOSITES CONTROL PASSWORD %token LOGGING LOG_TYPE LOG_TYPE_CONSOLE LOG_TYPE_SYSLOG LOG_TYPE_FILE %token LOG_LEVEL LOG_LEVEL_DEBUG LOG_LEVEL_INFO LOG_LEVEL_WARNING LOG_LEVEL_ERROR LOG_FACILITY LOG_FILENAME %token STATFILE ALIAS PATTERN WEIGHT STATFILE_POOL_SIZE SIZE TOKENIZER CLASSIFIER @@ -365,6 +365,7 @@ metriccmd: | metricname | metricfunction | metricscore + | metricrjscore | metricclassifier | metriccache ; @@ -409,6 +410,21 @@ metricscore: } ; +metricrjscore: + REJECT_SCORE EQSIGN NUMBER { + if (cur_metric == NULL) { + cur_metric = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct metric)); + } + cur_metric->reject_score = $3; + } + | REQUIRED_SCORE EQSIGN FRACT { + if (cur_metric == NULL) { + cur_metric = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct metric)); + } + cur_metric->reject_score = $3; + } + ; + metricclassifier: CLASSIFIER EQSIGN QUOTEDSTRING { if (cur_metric == NULL) { diff --git a/src/cfg_utils.c b/src/cfg_utils.c index 9387ca36f..a1113f516 100644 --- a/src/cfg_utils.c +++ b/src/cfg_utils.c @@ -520,6 +520,7 @@ post_load_config (struct config_file *cfg) def_metric->func_name = "factors"; def_metric->func = factor_consolidation_func; def_metric->required_score = DEFAULT_SCORE; + def_metric->reject_score = DEFAULT_REJECT_SCORE; def_metric->classifier = get_classifier ("winnow"); cfg->metrics_list = g_list_prepend (cfg->metrics_list, def_metric); g_hash_table_insert (cfg->metrics, DEFAULT_METRIC, def_metric); diff --git a/src/filter.c b/src/filter.c index 0ffec02d5..efda9fa72 100644 --- a/src/filter.c +++ b/src/filter.c @@ -234,12 +234,12 @@ static gboolean check_metric_is_spam (struct worker_task *task, struct metric *metric) { struct metric_result *res; - double ms; + double ms, rs; res = g_hash_table_lookup (task->results, metric->name); if (res) { metric_process_callback_forced (metric->name, res, task); - if (!check_metric_settings (task, metric, &ms)) { + if (!check_metric_settings (task, metric, &ms, &rs)) { ms = metric->required_score; } return res->score >= ms; @@ -572,18 +572,18 @@ insert_metric_header (gpointer metric_name, gpointer metric_value, gpointer data char header_name[128], outbuf[1000]; GList *symbols = NULL, *cur; struct metric_result *metric_res = (struct metric_result *)metric_value; - double ms; + double ms, rs; snprintf (header_name, sizeof (header_name), "X-Spam-%s", metric_res->metric->name); - if (!check_metric_settings (task, metric_res->metric, &ms)) { + if (!check_metric_settings (task, metric_res->metric, &ms, &rs)) { ms = metric_res->metric->required_score; } if (metric_res->score >= ms) { - r += snprintf (outbuf + r, sizeof (outbuf) - r, "yes; %.2f/%.2f; ", metric_res->score, ms); + r += snprintf (outbuf + r, sizeof (outbuf) - r, "yes; %.2f/%.2f/%.2f; ", metric_res->score, ms, rs); } else { - r += snprintf (outbuf + r, sizeof (outbuf) - r, "no; %.2f/%.2f; ", metric_res->score, ms); + r += snprintf (outbuf + r, sizeof (outbuf) - r, "no; %.2f/%.2f/%.2f; ", metric_res->score, ms, rs); } symbols = g_hash_table_get_keys (metric_res->symbols); diff --git a/src/filter.h b/src/filter.h index 6ff3ae1d0..edc9523c6 100644 --- a/src/filter.h +++ b/src/filter.h @@ -41,6 +41,7 @@ struct metric { char *func_name; /**< name of consolidation function */ metric_cons_func func; /**< c consolidation function */ double required_score; /**< required score for this metric */ + double reject_score; /**< reject score for this metric */ struct classifier *classifier; /**< classifier that is used for metric */ struct symbols_cache *cache; /**< symbols cache for metric */ char *cache_filename; /**< filename of cache file */ diff --git a/src/main.h b/src/main.h index cb922252c..55a62c86c 100644 --- a/src/main.h +++ b/src/main.h @@ -169,6 +169,7 @@ struct worker_task { } state; /**< current session state */ size_t content_length; /**< length of user's input */ enum rspamd_protocol proto; /**< protocol (rspamc or spamc) */ + const char *proto_ver; /**< protocol version */ enum rspamd_command cmd; /**< command */ struct custom_command *custom_cmd; /**< custom command if any */ int sock; /**< socket descriptor */ diff --git a/src/protocol.c b/src/protocol.c index a902cb505..6a8f9b8f0 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -195,14 +195,25 @@ parse_command (struct worker_task *task, f_str_t * line) if (strncasecmp (line->begin, RSPAMC_GREETING, sizeof (RSPAMC_GREETING) - 1) == 0) { task->proto = RSPAMC_PROTO; + task->proto_ver = RSPAMC_PROTO_1_0; + if (*(line->begin + sizeof (RSPAMC_GREETING) - 1) == '/') { + /* Extract protocol version */ + token = line->begin + sizeof (RSPAMC_GREETING); + if (strncmp (token, RSPAMC_PROTO_1_1, sizeof (RSPAMC_PROTO_1_1) - 1) == 0) { + task->proto_ver = RSPAMC_PROTO_1_1; + } + } } else if (strncasecmp (line->begin, SPAMC_GREETING, sizeof (SPAMC_GREETING) - 1) == 0) { task->proto = SPAMC_PROTO; + task->proto_ver = RSPAMC_PROTO_1_1; } else { return -1; } + task->state = READ_HEADER; + return 0; } @@ -505,25 +516,32 @@ show_metric_result (gpointer metric_name, gpointer metric_value, void *user_data struct metric_result *metric_res = (struct metric_result *)metric_value; struct metric *m; int is_spam = 0; - double ms; + double ms = 0, rs = 0; if (metric_name == NULL || metric_value == NULL) { m = g_hash_table_lookup (task->cfg->metrics, "default"); - if (!check_metric_settings (task, m, &ms)) { + if (!check_metric_settings (task, m, &ms, &rs)) { ms = m->required_score; + rs = m->reject_score; } if (task->proto == SPAMC_PROTO) { - r = snprintf (outbuf, sizeof (outbuf), "Spam: False ; 0 / %.2f" CRLF, m != NULL ? ms : 0); + r = snprintf (outbuf, sizeof (outbuf), "Spam: False ; 0 / %.2f" CRLF, ms); } else { - r = snprintf (outbuf, sizeof (outbuf), "Metric: default; False; 0 / %.2f" CRLF, m != NULL ? ms : 0); + if (strcmp (task->proto_ver, RSPAMC_PROTO_1_1) == 0) { + r = snprintf (outbuf, sizeof (outbuf), "Metric: default; False; 0 / %.2f / %.2f" CRLF, ms, rs); + } + else { + r = snprintf (outbuf, sizeof (outbuf), "Metric: default; False; 0 / %.2f" CRLF, ms); + } } - cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: F: [0/%.2f] [", "default", m != NULL ? ms : 0); + cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: F: [0/%.2f/%.2f] [", "default", ms, rs); } else { - if (!check_metric_settings (task, metric_res->metric, &ms)) { + if (!check_metric_settings (task, metric_res->metric, &ms, &rs)) { ms = metric_res->metric->required_score; + rs = metric_res->metric->reject_score; } if (metric_res->score >= ms) { is_spam = 1; @@ -532,9 +550,17 @@ show_metric_result (gpointer metric_name, gpointer metric_value, void *user_data r = snprintf (outbuf, sizeof (outbuf), "Spam: %s ; %.2f / %.2f" CRLF, (is_spam) ? "True" : "False", metric_res->score, ms); } else { - r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; %s; %.2f / %.2f" CRLF, (char *)metric_name, (is_spam) ? "True" : "False", metric_res->score, ms); + if (strcmp (task->proto_ver, RSPAMC_PROTO_1_1) == 0) { + r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; %s; %.2f / %.2f / %.2f" CRLF, + (char *)metric_name, (is_spam) ? "True" : "False", metric_res->score, ms, rs); + } + else { + r = snprintf (outbuf, sizeof (outbuf), "Metric: %s; %s; %.2f / %.2f" CRLF, + (char *)metric_name, (is_spam) ? "True" : "False", metric_res->score, ms); + } } - cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: %s: [%.2f/%.2f] [", (char *)metric_name, is_spam ? "T" : "F", metric_res->score, ms); + cd->log_offset += snprintf (cd->log_buf + cd->log_offset, cd->log_size - cd->log_offset, "(%s: %s: [%.2f/%.2f/%.2f] [", + (char *)metric_name, is_spam ? "T" : "F", metric_res->score, ms, rs); } if (task->cmd == CMD_PROCESS) { #ifndef GMIME24 @@ -562,7 +588,8 @@ write_check_reply (struct worker_task *task) struct metric_result *metric_res; struct metric_callback_data cd; - r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, "OK"); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, + task->proto_ver, "OK"); rspamd_dispatcher_write (task->dispatcher, outbuf, r, TRUE, FALSE); cd.task = task; @@ -613,7 +640,9 @@ write_process_reply (struct worker_task *task) struct metric_result *metric_res; struct metric_callback_data cd; - r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF "Content-Length: %zd" CRLF CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, "OK", task->msg->len); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF "Content-Length: %zd" CRLF CRLF, + (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, + task->proto_ver, "OK", task->msg->len); cd.task = task; cd.log_buf = logbuf; @@ -669,11 +698,13 @@ write_reply (struct worker_task *task) if (task->error_code != 0) { /* Write error message and error code to reply */ if (task->proto == SPAMC_PROTO) { - r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF CRLF, SPAMD_REPLY_BANNER, task->error_code, SPAMD_ERROR); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s %d %s" CRLF CRLF, + SPAMD_REPLY_BANNER, task->proto_ver, task->error_code, SPAMD_ERROR); msg_debug ("write_reply: writing error: %s", outbuf); } else { - r = snprintf (outbuf, sizeof (outbuf), "%s %d %s" CRLF "%s: %s" CRLF CRLF, RSPAMD_REPLY_BANNER, task->error_code, SPAMD_ERROR, ERROR_HEADER, task->last_error); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s %d %s" CRLF "%s: %s" CRLF CRLF, + RSPAMD_REPLY_BANNER, task->proto_ver, task->error_code, SPAMD_ERROR, ERROR_HEADER, task->last_error); msg_debug ("write_reply: writing error: %s", outbuf); } /* Write to bufferevent error message */ @@ -691,11 +722,13 @@ write_reply (struct worker_task *task) return write_process_reply (task); break; case CMD_SKIP: - r = snprintf (outbuf, sizeof (outbuf), "%s 0 %s" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, SPAMD_OK); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 %s" CRLF, + (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->proto_ver, SPAMD_OK); rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE); break; case CMD_PING: - r = snprintf (outbuf, sizeof (outbuf), "%s 0 PONG" CRLF, (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER); + r = snprintf (outbuf, sizeof (outbuf), "%s/%s 0 PONG" CRLF, + (task->proto == SPAMC_PROTO) ? SPAMD_REPLY_BANNER : RSPAMD_REPLY_BANNER, task->proto_ver); rspamd_dispatcher_write (task->dispatcher, outbuf, r, FALSE, FALSE); break; case CMD_OTHER: diff --git a/src/protocol.h b/src/protocol.h index b4783cb30..4c12cc481 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -13,11 +13,14 @@ #define RSPAMD_PROTOCOL_ERROR 3 #define RSPAMD_LENGTH_ERROR 4 +#define RSPAMC_PROTO_1_0 "1.0" +#define RSPAMC_PROTO_1_1 "1.1" + /* * Reply messages */ -#define RSPAMD_REPLY_BANNER "RSPAMD/1.0" -#define SPAMD_REPLY_BANNER "SPAMD/1.1" +#define RSPAMD_REPLY_BANNER "RSPAMD" +#define SPAMD_REPLY_BANNER "SPAMD" #define SPAMD_OK "EX_OK" /* XXX: try to convert rspamd errors to spamd errors */ #define SPAMD_ERROR "EX_ERROR" diff --git a/src/settings.c b/src/settings.c index a4dca10dd..bcb40505c 100644 --- a/src/settings.c +++ b/src/settings.c @@ -50,6 +50,9 @@ settings_free (gpointer data) if (s->metric_scores) { g_hash_table_destroy (s->metric_scores); } + if (s->reject_scores) { + g_hash_table_destroy (s->reject_scores); + } g_free (s); } @@ -149,6 +152,7 @@ json_fin_cb (memory_pool_t * pool, struct map_cb_data *data) for (i = 0; i < nelts; i++) { cur_settings = g_malloc (sizeof (struct rspamd_settings)); cur_settings->metric_scores = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + cur_settings->reject_scores = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); cur_settings->factors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); cur_settings->statfile_alias = NULL; cur_settings->want_spam = FALSE; @@ -198,6 +202,21 @@ json_fin_cb (memory_pool_t * pool, struct map_cb_data *data) json_it = json_object_iter_next (cur_nm, json_it); } } + /* Rejects object */ + cur_nm = json_object_get (cur_elt, "rejects"); + if (cur_nm != NULL && json_is_object (cur_nm)) { + json_it = json_object_iter (cur_nm); + while (json_it) { + it_val = json_object_iter_value (json_it); + if (it_val && json_is_number (it_val)) { + score = g_malloc (sizeof (double)); + *score = json_number_value (it_val); + g_hash_table_insert (cur_settings->reject_scores, g_strdup (json_object_iter_key (json_it)), + score); + } + json_it = json_object_iter_next(cur_nm, json_it); + } + } /* Want spam */ cur_nm = json_object_get (cur_elt, "want_spam"); if (cur_nm != NULL) { @@ -284,25 +303,36 @@ check_setting (struct worker_task *task, struct rspamd_settings **user_settings, } gboolean -check_metric_settings (struct worker_task * task, struct metric * metric, double *score) +check_metric_settings (struct worker_task * task, struct metric * metric, double *score, double *rscore) { struct rspamd_settings *us, *ds; - double *sc; + double *sc, *rs; + + *rscore = DEFAULT_REJECT_SCORE; if (check_setting (task, &us, &ds)) { if (us != NULL) { /* First search in user's settings */ + if ((rs = g_hash_table_lookup (us->reject_scores, metric->name)) != NULL) { + *rscore = *rs; + } if ((sc = g_hash_table_lookup (us->metric_scores, metric->name)) != NULL) { *score = *sc; return TRUE; } /* Now check in domain settings */ + if (ds && ((rs = g_hash_table_lookup (ds->reject_scores, metric->name)) != NULL)) { + *rscore = *rs; + } if (ds && (sc = g_hash_table_lookup (ds->metric_scores, metric->name)) != NULL) { *score = *sc; return TRUE; } } else if (ds != NULL) { + if ((rs = g_hash_table_lookup (ds->reject_scores, metric->name)) != NULL) { + *rscore = *rs; + } if ((sc = g_hash_table_lookup (ds->metric_scores, metric->name)) != NULL) { *score = *sc; return TRUE; diff --git a/src/settings.h b/src/settings.h index 696bd9d76..58e877570 100644 --- a/src/settings.h +++ b/src/settings.h @@ -5,7 +5,8 @@ #include "main.h" struct rspamd_settings { - GHashTable *metric_scores; /**< hash table of metric scores for this setting */ + GHashTable *metric_scores; /**< hash table of metric require scores for this setting */ + GHashTable *reject_scores; /**< hash table of metric reject scores for this setting */ GHashTable *factors; /**< hash table of new factors for this setting */ char *statfile_alias; /**< alias for statfile used */ gboolean want_spam; /**< if true disable rspamd checks */ @@ -14,7 +15,7 @@ struct rspamd_settings { gboolean read_settings (const char *path, struct config_file *cfg, GHashTable *table); void init_settings (struct config_file *cfg); -gboolean check_metric_settings (struct worker_task *task, struct metric *metric, double *score); +gboolean check_metric_settings (struct worker_task *task, struct metric *metric, double *score, double *rscore); gboolean check_factor_settings (struct worker_task *task, const char *symbol, double *factor); gboolean check_want_spam (struct worker_task *task); -- 2.39.5