summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--conf/metrics.conf272
-rw-r--r--rules/regexp/drugs.lua12
-rw-r--r--rules/regexp/fraud.lua4
-rw-r--r--rules/regexp/headers.lua511
-rw-r--r--rules/regexp/lotto.lua2
-rw-r--r--src/controller.c2
-rw-r--r--src/libserver/CMakeLists.txt1
-rw-r--r--src/libserver/cfg_file.h3
-rw-r--r--src/libserver/cfg_utils.c1
-rw-r--r--src/libserver/monitored.c2
-rw-r--r--src/libserver/redis_pool.c378
-rw-r--r--src/libserver/redis_pool.h70
-rw-r--r--src/lua/lua_redis.c60
-rw-r--r--src/plugins/lua/dmarc.lua83
-rw-r--r--src/worker.c2
-rw-r--r--test/functional/cases/150_rspamadm.robot9
17 files changed, 984 insertions, 431 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e0be84d60..f0749f806 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -476,7 +476,8 @@ ENDMACRO()
# Initial set
# Prefer local include dirs to system ones
-INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/src"
+INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/"
+ "${CMAKE_SOURCE_DIR}/src"
"${CMAKE_SOURCE_DIR}/src/libutil"
"${CMAKE_SOURCE_DIR}/src/libserver"
"${CMAKE_SOURCE_DIR}/src/libmime"
diff --git a/conf/metrics.conf b/conf/metrics.conf
index 0bab8ea51..1294ca2f1 100644
--- a/conf/metrics.conf
+++ b/conf/metrics.conf
@@ -28,99 +28,23 @@ metric {
group "excessqp" {
max_score = 2.4;
- symbol "FROM_EXCESS_QP" {
- weight = 1.2;
- description = "From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
- }
- symbol "TO_EXCESS_QP" {
- weight = 1.2;
- description = "To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
- }
- symbol "REPLYTO_EXCESS_QP" {
- weight = 1.2;
- description = "Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
- }
- symbol "CC_EXCESS_QP" {
- weight = 1.2;
- description = "Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
- }
}
group "excessb64" {
max_score = 3.0;
- symbol "FROM_EXCESS_BASE64" {
- weight = 1.5;
- description = "From that contains encoded characters while base 64 is not needed as all symbols are 7bit";
- }
- symbol "TO_EXCESS_BASE64" {
- weight = 1.5;
- description = "To that contains encoded characters while base 64 is not needed as all symbols are 7bit";
- }
- symbol "REPLYTO_EXCESS_BASE64" {
- weight = 1.5;
- description = "Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit";
- }
- symbol "CC_EXCESS_BASE64" {
- weight = 1.5;
- description = "Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit";
- }
}
group "header" {
- symbol "MISSING_SUBJECT" {
- weight = 2.0;
- description = "Subject is missing inside message";
- }
- symbol "FORGED_OUTLOOK_TAGS" {
- weight = 2.100000;
- description = "Message pretends to be send from Outlook but has 'strange' tags ";
- }
symbol "FORGED_SENDER" {
weight = 0.30;
description = "Sender is forged (different From: header and smtp MAIL FROM: addresses)";
}
- symbol "SUSPICIOUS_RECIPS" {
- weight = 1.500000;
- description = "Recipients seems to be autogenerated (works if recipients count is more than 5)";
- }
symbol "MIME_HTML_ONLY" {
weight = 0.2;
description = "Messages that have only HTML part";
}
- symbol "FORGED_MSGID_YAHOO" {
- weight = 2.0;
- description = "Forged yahoo msgid";
- }
- symbol "FORGED_MUA_THEBAT_BOUN" {
- weight = 2.0;
- description = "Forged The Bat! MUA headers";
- }
- symbol "R_MISSING_CHARSET" {
- weight = 2.5;
- description = "Charset is missing in a message";
- }
- symbol "RCVD_DOUBLE_IP_SPAM" {
- weight = 2.0;
- description = "Two received headers with ip addresses";
- }
- symbol "FORGED_OUTLOOK_HTML" {
- weight = 5.0;
- description = "Forged outlook HTML signature";
- }
- symbol "R_UNDISC_RCPT" {
- weight = 3.0;
- description = "Recipients are absent or undisclosed";
- }
symbol "FM_FAKE_HELO_VERIZON" {
weight = 2.0;
description = "Fake helo for verizon provider";
}
- symbol "REPTO_QUOTE_YAHOO" {
- weight = 2.0;
- description = "Quoted reply-to from yahoo (seems to be forged)";
- }
- symbol "MISSING_MIMEOLE" {
- weight = 2.0;
- description = "Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)";
- }
symbol "MISSING_TO" {
weight = 2.0;
description = "To header is missing";
@@ -135,42 +59,6 @@ metric {
description = "Mixed characters in a URL inside message";
one_shot = true;
}
- symbol "SORTED_RECIPS" {
- weight = 3.500000;
- description = "Recipients list seems to be sorted";
- }
- symbol "R_RCVD_SPAMBOTS" {
- weight = 3.0;
- description = "Spambots signatures in received headers";
- }
- symbol "SUBJECT_NEEDS_ENCODING" {
- weight = 1.0;
- description = "Subject needs encoding";
- }
- symbol "TRACKER_ID" {
- weight = 3.84;
- description = "Spam string at the end of message to make statistics faults 0";
- }
- symbol "R_NO_SPACE_IN_FROM" {
- weight = 1.0;
- description = "No space in from header";
- }
- symbol "R_SAJDING" {
- weight = 8.0;
- description = "Subject seems to be spam";
- }
- symbol "R_BAD_CTE_7BIT" {
- weight = 3.0;
- description = "Detects bad content-transfer-encoding for text parts";
- }
- symbol "INVALID_MSGID" {
- weight = 1.7;
- description = "Message id is incorrect";
- }
- symbol "MISSING_MID" {
- weight = 2.5;
- description = "Message id is missing ";
- }
symbol "FORGED_RECIPIENTS" {
weight = 2.0;
description = "Recipients are not the same as RCPT TO: mail command";
@@ -183,14 +71,6 @@ metric {
weight = 0.0;
description = "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist";
}
- symbol "RATWARE_MS_HASH" {
- weight = 2.0;
- description = "Forged Exchange messages";
- }
- symbol "STOX_REPLY_TYPE" {
- weight = 1.0;
- description = "Reply-type in content-type";
- }
symbol "ONCE_RECEIVED" {
weight = 0.1;
description = "One received header in a message";
@@ -203,99 +83,15 @@ metric {
weight = 4.0;
description = "One received header with 'bad' patterns inside";
}
- symbol "MIME_HEADER_CTYPE_ONLY" {
- weight = 2.0;
- description = "Only Content-Type header without other MIME headers";
- }
symbol "MAILLIST" {
weight = -0.2;
description = "Message seems to be from maillist";
}
- symbol "HEADER_FROM_DELIMITER_TAB" {
- weight = 1.0;
- description = "Header From begins with tab";
- }
- symbol "HEADER_TO_DELIMITER_TAB" {
- weight = 1.0;
- description = "Header To begins with tab";
- }
- symbol "HEADER_CC_DELIMITER_TAB" {
- weight = 1.0;
- description = "Header Cc begins with tab";
- }
- symbol "HEADER_REPLYTO_DELIMITER_TAB" {
- weight = 1.0;
- description = "Header Reply-To begins with tab";
- }
- symbol "HEADER_DATE_DELIMITER_TAB" {
- weight = 1.0;
- description = "Header Date begins with tab";
- }
- symbol "HEADER_FROM_EMPTY_DELIMITER" {
- weight = 1.0;
- description = "Header From has no delimiter between header name and header value";
- }
- symbol "HEADER_TO_EMPTY_DELIMITER" {
- weight = 1.0;
- description = "Header To has no delimiter between header name and header value";
- }
- symbol "HEADER_CC_EMPTY_DELIMITER" {
- weight = 1.0;
- description = "Header Cc has no delimiter between header name and header value";
- }
- symbol "HEADER_REPLYTO_EMPTY_DELIMITER" {
- weight = 1.0;
- description = "Header Reply-To has no delimiter between header name and header value";
- }
- symbol "HEADER_DATE_EMPTY_DELIMITER" {
- weight = 1.0;
- description = "Header Date has no delimiter between header name and header value";
- }
- symbol "RCVD_ILLEGAL_CHARS" {
- weight = 4.0;
- description = "Header Received has raw illegal character";
- }
- symbol "FAKE_RECEIVED_mail_ru" {
- weight = 4.0;
- description = "Fake helo mail.ru in header Received from non mail.ru sender address";
- }
- symbol "FAKE_RECEIVED_smtp_yandex_ru" {
- weight = 4.0;
- description = "Fake smtp.yandex.ru Received";
- }
- symbol "FORGED_GENERIC_RECEIVED" {
- weight = 3.6;
- description = "Forged generic Received";
- }
- symbol "FORGED_GENERIC_RECEIVED2" {
- weight = 3.6;
- description = "Forged generic Received";
- }
- symbol "FORGED_GENERIC_RECEIVED3" {
- weight = 3.6;
- description = "Forged generic Received";
- }
- symbol "FORGED_GENERIC_RECEIVED4" {
- weight = 3.6;
- description = "Forged generic Received";
- }
- symbol "FORGED_GENERIC_RECEIVED5" {
- weight = 4.6;
- description = "Forged generic Received";
- }
- symbol "INVALID_POSTFIX_RECEIVED" {
- weight = 3.0;
- description = "Invalid Postfix Received";
- }
}
group "subject" {
max_score = 6.0;
- symbol "FAKE_REPLY_C" {
- weight = 6.0;
- description = "Fake reply (has RE in subject, but has not References header)";
- }
symbol "LONG_SUBJ" {
weight = 6.0;
description = "Subject is too long";
@@ -307,58 +103,6 @@ metric {
}
group "mua" {
- symbol "FORGED_MUA_THEBAT_MSGID" {
- weight = 4.0;
- description = "Message pretends to be send from The Bat! but has forged Message-ID";
- }
- symbol "FORGED_MUA_THEBAT_MSGID_UNKNOWN" {
- weight = 3.0;
- description = "Message pretends to be send from The Bat! but has forged Message-ID";
- }
- symbol "FORGED_MUA_KMAIL_MSGID" {
- weight = 3.0;
- description = "Message pretends to be send from KMail but has forged Message-ID";
- }
- symbol "FORGED_MUA_KMAIL_MSGID_UNKNOWN" {
- weight = 2.5;
- description = "Message pretends to be send from KMail but has forged Message-ID";
- }
- symbol "FORGED_MUA_OPERA_MSGID" {
- weight = 4.0;
- description = "Message pretends to be send from Opera Mail but has forged Message-ID";
- }
- symbol "SUSPICIOUS_OPERA_10W_MSGID" {
- weight = 4.0;
- description = "Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail";
- }
- symbol "FORGED_MUA_MOZILLA_MAIL_MSGID" {
- weight = 4.0;
- description = "Message pretends to be send from Mozilla Mail but has forged Message-ID";
- }
- symbol "FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN" {
- weight = 2.5;
- description = "Message pretends to be send from Mozilla Mail but has forged Message-ID";
- }
- symbol "FORGED_MUA_THUNDERBIRD_MSGID" {
- weight = 4.0;
- description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID";
- }
- symbol "FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN" {
- weight = 2.5;
- description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID";
- }
- symbol "FORGED_MUA_SEAMONKEY_MSGID" {
- weight = 4.0;
- description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID";
- }
- symbol "FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN" {
- weight = 2.5;
- description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID";
- }
- symbol "FORGED_MUA_OUTLOOK" {
- weight = 3.0;
- description = "Forged outlook MUA";
- }
symbol "FORGED_MUA_MAILLIST" {
weight = 0.0;
description = "Avoid false positives for FORGED_MUA_* in maillist";
@@ -382,22 +126,6 @@ metric {
weight = 0.5;
description = "Short html part with a link to an image";
}
- symbol "SUSPICIOUS_BOUNDARY" {
- weight = 5.0;
- description = "Suspicious boundary in header Content-Type";
- }
- symbol "SUSPICIOUS_BOUNDARY2" {
- weight = 4.0;
- description = "Suspicious boundary in header Content-Type";
- }
- symbol "SUSPICIOUS_BOUNDARY3" {
- weight = 3.0;
- description = "Suspicious boundary in header Content-Type";
- }
- symbol "SUSPICIOUS_BOUNDARY4" {
- weight = 4.0;
- description = "Suspicious boundary in header Content-Type";
- }
symbol "R_PARTS_DIFFER" {
weight = 1.0;
description = "Text and HTML parts differ";
diff --git a/rules/regexp/drugs.lua b/rules/regexp/drugs.lua
index 7af31cd69..774c326a1 100644
--- a/rules/regexp/drugs.lua
+++ b/rules/regexp/drugs.lua
@@ -31,7 +31,7 @@ local drugs_diet7 = '/\\b_{0,3}t[_\\W]?[e3\\xE8-\\xEB][_\\W]?n[_\\W]?u[_\\W]?a[_
local drugs_diet8 = '/\\b_{0,3}d[_\\W]?[i1!|l\\xEC-\\xEF][_\\W]?d[_\\W]?r[_\\W][e3\\xE8-\\xEB[_\\W]?xx?_{0,3}\\b/irP'
local drugs_diet9 = '/\\b_{0,3}a[_\\W]?d[_\\W]?[i1!|l\\xEC-\\xEF][_\\W]?p[_\\W]?[e3\\xE8-\\xEB][_\\W]?x_{0,3}\\b/irP'
local drugs_diet10 = '/\\b_{0,3}x?x[_\\W]?[e3\\xE8-\\xEB][_\\W]?n[_\\W]?[i1!|l\\xEC-\\xEF][_\\W]?c[_\\W]?[a4\\xE0-\\xE6@][_\\W]?l_{0,3}\\b/irP'
-reconf['DRUGS_DIET'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], drugs_diet1, drugs_diet2, drugs_diet3, drugs_diet4, drugs_diet5, drugs_diet6, drugs_diet7, drugs_diet8, drugs_diet9, drugs_diet10)
+reconf['DRUGS_DIET'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], drugs_diet1, drugs_diet2, drugs_diet3, drugs_diet4, drugs_diet5, drugs_diet6, drugs_diet7, drugs_diet8, drugs_diet9, drugs_diet10)
local drugs_erectile1 = '/(?:\\b|\\s)[_\\W]{0,3}(?:\\\\\\/|V)[_\\W]{0,3}[ij1!|l\\xEC\\xED\\xEE\\xEF][_\\W]{0,3}[a40\\xE0-\\xE6@][_\\W]{0,3}[xyz]?[gj][_\\W]{0,3}r[_\\W]{0,3}[a40\\xE0-\\xE6@][_\\W]{0,3}x?[_\\W]{0,3}(?:\\b|\\s)/irP'
local drugs_erectile2 = '/\\bV(?:agira|igara|iaggra|iaegra)\\b/irP'
local drugs_erectile3 = '/(?:\\A|[\\s\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f])[_\\W]{0,3}C[_\\W]{0,3}[ij1!|l\\xEC\\xED\\xEE\\xEF][_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}l?[l!|1][_\\W]{0,3}[i1!|l\\xEC-\\xEF][_\\W]{0,3}s[_\\W]{0,3}(?:\\b|\\s)/irP'
@@ -41,7 +41,7 @@ local drugs_erectile6 = '/\\b_{0,3}L[_\\W]?[e3\\xE8-\\xEB][_\\W]?(?:\\\\\\/|V)[_
local drugs_erectile8 = '/\\b_{0,3}T[_\\W]?[a4\\xE0-\\xE6@][_\\W]?d[_\\W]?[a4\\xE0-\\xE6@][_\\W]?l[_\\W]?[a4\\xE0-\\xE6@][_\\W]?f[_\\W]?[i1!|l\\xEC-\\xEF][_\\W]?l_{0,3}\\b/irP'
local drugs_erectile10 = '/\\b_{0,3}V[_\\W]?(?:i|\\&iuml\\;)[_\\W]?(?:a|\\&agrave|\\&aring)\\;?[_\\W]?g[_\\W]?r[_\\W]?(?:a|\\&agrave|\\&aring)\\b/irP'
local drugs_erectile11 = '/(?:\\b|\\s)_{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}p[_\\W]{0,3}c[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}[l!|1][_\\W]{0,3}[i1!|l\\xEC-\\xEF][_\\W]{0,3}s_{0,3}\\b/irP'
-reconf['DRUGS_ERECTILE'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], drugs_erectile1, drugs_erectile2, drugs_erectile3, drugs_erectile4, drugs_erectile5, drugs_erectile6, drugs_erectile8, drugs_erectile10, drugs_erectile11)
+reconf['DRUGS_ERECTILE'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], drugs_erectile1, drugs_erectile2, drugs_erectile3, drugs_erectile4, drugs_erectile5, drugs_erectile6, drugs_erectile8, drugs_erectile10, drugs_erectile11)
local drugs_anxiety1 = '/(?:\\b|\\s)[_\\W]{0,3}x?x[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}n[_\\W]{0,3}[ea4\\xE1\\xE2\\xE3@][_\\W]{0,3}xx?_{0,3}\\b/irP'
local drugs_anxiety2 = '/\\bAlprazolam\\b/irP'
local drugs_anxiety3 = '/(?:\\b|\\s)[_\\W]{0,3}(?:\\\\\\/|V)[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}[l|][_\\W]{0,3}[i1!|l\\xEC-\\xEF][_\\W]{0,3}[u\\xB5\\xF9-\\xFC][_\\W]{0,3}m\\b/irP'
@@ -51,7 +51,7 @@ local drugs_anxiety6 = '/\\b_{0,3}l[_\\W]?[o0\\xF2-\\xF6][_\\W]?r[_\\W]?[a4\\xE0
local drugs_anxiety7 = '/\\b_{0,3}c[_\\W]?l[_\\W]?[o0\\xF2-\\xF6][_\\W]?n[_\\W]?[a4\\xE0-\\xE6@][_\\W]?z[_\\W]?e[_\\W]?p[_\\W]?[a4\\xE0-\\xE6@][_\\W]?m\\b/irP'
local drugs_anxiety8 = '/\\bklonopin\\b/irP'
local drugs_anxiety9 = '/\\brivotril\\b/irP'
-reconf['DRUGS_ANXIETY'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], drugs_anxiety1, drugs_anxiety2, drugs_anxiety3, drugs_anxiety4, drugs_anxiety5, drugs_anxiety6, drugs_anxiety7, drugs_anxiety8, drugs_anxiety9)
+reconf['DRUGS_ANXIETY'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], drugs_anxiety1, drugs_anxiety2, drugs_anxiety3, drugs_anxiety4, drugs_anxiety5, drugs_anxiety6, drugs_anxiety7, drugs_anxiety8, drugs_anxiety9)
reconf['DRUGS_ANXIETY_EREC'] = string.format('(%s) & (%s)', reconf['DRUGS_ERECTILE'], reconf['DRUGS_ANXIETY'])
local drugs_pain1 = '/\\b_{0,3}h[_\\W]?y[_\\W]?d[_\\W]?r[_\\W]?[o0\\xF2-\\xF6][_\\W]?c[_\\W]?[o0\\xF2-\\xF6][_\\W]?d[_\\W]?[o0\\xF2-\\xF6][_\\W]?n[_\\W]?e_{0,3}\\b/irP'
local drugs_pain2 = '/\\b_{0,3}c[o0\\xF2-\\xF6]deine_{0,3}\\b/irP'
@@ -67,7 +67,7 @@ local drugs_pain11 = '/\\bzebutal\\b/irP'
local drugs_pain12 = '/\\besgic plus\\b/irP'
local drugs_pain13 = '/\\bD[_\\W]?[a4\\xE0-\\xE6@][_\\W]?r[_\\W]?v[_\\W]?[o0\\xF2-\\xF6][_\\W]?n\\b/irP'
local drugs_pain14 = '/N[o0\\xF2-\\xF6]rc[o0\\xF2-\\xF6]/irP'
-local drugs_pain = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) || (%s) | (%s))', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], drugs_pain1, drugs_pain2, drugs_pain3, drugs_pain4, drugs_pain5, drugs_pain6, drugs_pain7, drugs_pain8, drugs_pain9, drugs_pain10, drugs_pain11, drugs_pain12, drugs_pain13, drugs_pain14)
+local drugs_pain = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) || (%s) | (%s))', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], drugs_pain1, drugs_pain2, drugs_pain3, drugs_pain4, drugs_pain5, drugs_pain6, drugs_pain7, drugs_pain8, drugs_pain9, drugs_pain10, drugs_pain11, drugs_pain12, drugs_pain13, drugs_pain14)
local drugs_sleep1 = '/(?:\\b|\\s)[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}m[_\\W]{0,3}b[_\\W]{0,3}[i1!|l\\xEC-\\xEF][_\\W]{0,3}[e3\\xE8-\\xEB][_\\W]{0,3}n[_\\W]{0,3}(?:\\b|\\s)/irP'
local drugs_sleep2 = '/(?:\\b|\\s)[_\\W]{0,3}S[_\\W]{0,3}[o0\\xF2-\\xF6][_\\W]{0,3}n[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}t[_\\W]{0,3}[a4\\xE0-\\xE6@][_\\W]{0,3}(?:\\b|\\s)/irP'
local drugs_sleep3 = '/\\b_{0,3}R[_\\W]?[e3\\xE8-\\xEB][_\\W]?s[_\\W]?t[_\\W]?[o0\\xF2-\\xF6][_\\W]?r[_\\W]?i[_\\W]?l_{0,3}\\b/irP'
@@ -78,6 +78,6 @@ local drugs_muscle2 = '/\\b_{0,3}cycl[o0\\xF2-\\xF6]b[e3\\xE8-\\xEB]nz[a4\\xE0-\
local drugs_muscle3 = '/\\b_{0,3}f[_\\W]?l[_\\W]?[e3\\xE8-\\xEB][_\\W]?x[_\\W]?[e3\\xE8-\\xEB][_\\W]?r[_\\W]?[i1!|l\\xEC-\\xEF]_{0,3}[_\\W]?l_{0,3}\\b/irP'
local drugs_muscle4 = '/\\b_{0,3}z[_\\W]?a[_\\W]?n[_\\W]?a[_\\W]?f[_\\W]?l[_\\W]?e[_\\W]?x_{0,3}\\b/irP'
local drugs_muscle5 = '/\\bskelaxin\\b/irP'
-reconf['DRUGS_MUSCLE'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], drugs_muscle1, drugs_muscle2, drugs_muscle3, drugs_muscle4, drugs_muscle5)
-reconf['DRUGS_MANYKINDS'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 3)', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], reconf['DRUGS_ERECTILE'], reconf['DRUGS_DIET'], drugs_pain, drugs_sleep, reconf['DRUGS_MUSCLE'], reconf['DRUGS_ANXIETY'])
+reconf['DRUGS_MUSCLE'] = string.format('((%s) | (%s) | (%s)) & ((%s) | (%s) | (%s) | (%s) | (%s))', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], drugs_muscle1, drugs_muscle2, drugs_muscle3, drugs_muscle4, drugs_muscle5)
+reconf['DRUGS_MANYKINDS'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 3)', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], reconf['DRUGS_ERECTILE'], reconf['DRUGS_DIET'], drugs_pain, drugs_sleep, reconf['DRUGS_MUSCLE'], reconf['DRUGS_ANXIETY'])
diff --git a/rules/regexp/fraud.lua b/rules/regexp/fraud.lua
index 2571a8712..441aca5de 100644
--- a/rules/regexp/fraud.lua
+++ b/rules/regexp/fraud.lua
@@ -70,5 +70,5 @@ local fraud_yqv = '/nigerian? (?:national|government)/irP'
local fraud_yja = '/over-invoice/irP'
local fraud_ypo = '/the total sum/irP'
local fraud_uoq = '/vital documents/irP'
-reconf['ADVANCE_FEE_2'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 2)', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], fraud_kjv, fraud_irj, fraud_neb, fraud_xjr, fraud_ezy, fraud_zfj, fraud_kdt, fraud_bgp, fraud_fbi, fraud_jbu, fraud_jyg, fraud_xvw, fraud_snt, fraud_ltx, fraud_mcq, fraud_pvn, fraud_fvu, fraud_ckf, fraud_fcw, fraud_mqo, fraud_tcc, fraud_gbw, fraud_nrg, fraud_rlx, fraud_axf, fraud_thj, fraud_yqv, fraud_yja, fraud_ypo, fraud_uoq, fraud_dbi, fraud_bep, fraud_dpr, fraud_qxx, fraud_qfy, fraud_pts, fraud_tdp, fraud_gan, fraud_ipk, fraud_aon, fraud_wny, fraud_aum, fraud_wfc, fraud_yww, fraud_ulk, fraud_iou, fraud_jnb, fraud_irt, fraud_etx, fraud_wdr, fraud_uuy, fraud_mly)
-reconf['ADVANCE_FEE_3'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 3)', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], fraud_kjv, fraud_irj, fraud_neb, fraud_xjr, fraud_ezy, fraud_zfj, fraud_kdt, fraud_bgp, fraud_fbi, fraud_jbu, fraud_jyg, fraud_xvw, fraud_snt, fraud_ltx, fraud_mcq, fraud_pvn, fraud_fvu, fraud_ckf, fraud_fcw, fraud_mqo, fraud_tcc, fraud_gbw, fraud_nrg, fraud_rlx, fraud_axf, fraud_thj, fraud_yqv, fraud_yja, fraud_ypo, fraud_uoq, fraud_dbi, fraud_bep, fraud_dpr, fraud_qxx, fraud_qfy, fraud_pts, fraud_tdp, fraud_gan, fraud_ipk, fraud_aon, fraud_wny, fraud_aum, fraud_wfc, fraud_yww, fraud_ulk, fraud_iou, fraud_jnb, fraud_irt, fraud_etx, fraud_wdr, fraud_uuy, fraud_mly)
+reconf['ADVANCE_FEE_2'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 2)', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], fraud_kjv, fraud_irj, fraud_neb, fraud_xjr, fraud_ezy, fraud_zfj, fraud_kdt, fraud_bgp, fraud_fbi, fraud_jbu, fraud_jyg, fraud_xvw, fraud_snt, fraud_ltx, fraud_mcq, fraud_pvn, fraud_fvu, fraud_ckf, fraud_fcw, fraud_mqo, fraud_tcc, fraud_gbw, fraud_nrg, fraud_rlx, fraud_axf, fraud_thj, fraud_yqv, fraud_yja, fraud_ypo, fraud_uoq, fraud_dbi, fraud_bep, fraud_dpr, fraud_qxx, fraud_qfy, fraud_pts, fraud_tdp, fraud_gan, fraud_ipk, fraud_aon, fraud_wny, fraud_aum, fraud_wfc, fraud_yww, fraud_ulk, fraud_iou, fraud_jnb, fraud_irt, fraud_etx, fraud_wdr, fraud_uuy, fraud_mly)
+reconf['ADVANCE_FEE_3'] = string.format('((%s) | (%s) | (%s)) & ((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) >= 3)', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], fraud_kjv, fraud_irj, fraud_neb, fraud_xjr, fraud_ezy, fraud_zfj, fraud_kdt, fraud_bgp, fraud_fbi, fraud_jbu, fraud_jyg, fraud_xvw, fraud_snt, fraud_ltx, fraud_mcq, fraud_pvn, fraud_fvu, fraud_ckf, fraud_fcw, fraud_mqo, fraud_tcc, fraud_gbw, fraud_nrg, fraud_rlx, fraud_axf, fraud_thj, fraud_yqv, fraud_yja, fraud_ypo, fraud_uoq, fraud_dbi, fraud_bep, fraud_dpr, fraud_qxx, fraud_qfy, fraud_pts, fraud_tdp, fraud_gan, fraud_ipk, fraud_aon, fraud_wny, fraud_aum, fraud_wfc, fraud_yww, fraud_ulk, fraud_iou, fraud_jnb, fraud_irt, fraud_etx, fraud_wdr, fraud_uuy, fraud_mly)
diff --git a/rules/regexp/headers.lua b/rules/regexp/headers.lua
index 6ec37181f..b13274055 100644
--- a/rules/regexp/headers.lua
+++ b/rules/regexp/headers.lua
@@ -27,21 +27,35 @@ local subject_encoded_qp = 'Subject=/=\\?\\S+\\?Q\\?/iX'
-- Define whether subject must be encoded (contains non-7bit characters)
local subject_needs_mime = 'Subject=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/X'
-- Final rule
-reconf['SUBJECT_NEEDS_ENCODING'] = string.format('!(%s) & !(%s) & (%s)', subject_encoded_b64, subject_encoded_qp, subject_needs_mime)
+reconf['SUBJECT_NEEDS_ENCODING'] = {
+ re = string.format('!(%s) & !(%s) & (%s)', subject_encoded_b64, subject_encoded_qp, subject_needs_mime),
+ score = 1.0,
+ description = 'Subject needs encoding',
+ group = 'header'
+}
-- Detects that there is no space in From header (e.g. Some Name<some@host>)
-reconf['R_NO_SPACE_IN_FROM'] = 'From=/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/X'
+reconf['R_NO_SPACE_IN_FROM'] = {
+ re = 'From=/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/X',
+ score = 1.0,
+ description = 'No space in from header',
+ group = 'header'
+}
+rspamd_config.MISSING_SUBJECT = {
+ score = 2.0,
+ description = 'Subject is missing inside message',
+ group = 'header',
+ callback = function(task)
+ local hdr = task:get_header('Subject')
-rspamd_config.MISSING_SUBJECT = function(task)
- local hdr = task:get_header('Subject')
+ if not hdr or #hdr == 0 then
+ return true
+ end
- if not hdr or #hdr == 0 then
- return true
+ return false
end
-
- return false
-end
+}
-- Detects bad content-transfer-encoding for text parts
-- For text parts (text/plain and text/html mainly)
@@ -50,27 +64,62 @@ local r_ctype_text = 'content_type_is_type(text)'
local r_cte_7bit = 'compare_transfer_encoding(7bit)'
-- And body contains 8bit characters
local r_body_8bit = '/[^\\x01-\\x7f]/Pr'
-reconf['R_BAD_CTE_7BIT'] = string.format('(%s) & (%s) & (%s)', r_ctype_text, r_cte_7bit, r_body_8bit)
+reconf['R_BAD_CTE_7BIT'] = {
+ re = string.format('(%s) & (%s) & (%s)', r_ctype_text, r_cte_7bit, r_body_8bit),
+ score = 3.0,
+ description = 'Detects bad content-transfer-encoding for text parts',
+ group = 'header'
+}
-- Detects missing To header
-reconf['MISSING_TO']= '!raw_header_exists(To)';
+reconf['MISSING_TO'] = {
+ re = '!raw_header_exists(To)',
+ score = 2.0,
+ description = 'To header is missing',
+ group = 'header'
+}
-- Detects undisclosed recipients
local undisc_rcpt = 'To=/^<?undisclosed[- ]recipient/Hi'
-reconf['R_UNDISC_RCPT'] = string.format('(%s)', undisc_rcpt)
+reconf['R_UNDISC_RCPT'] = {
+ re = string.format('(%s)', undisc_rcpt),
+ score = 3.0,
+ description = 'Recipients are absent or undisclosed',
+ group = 'header'
+}
-- Detects missing Message-Id
local has_mid = 'header_exists(Message-Id)'
-reconf['MISSING_MID'] = '!header_exists(Message-Id)';
+reconf['MISSING_MID'] = {
+ re = '!header_exists(Message-Id)',
+ score = 2.5,
+ description = 'Message id is missing',
+ group = 'header'
+}
-- Received seems to be fake
-reconf['R_RCVD_SPAMBOTS'] = 'Received=/^from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by [-.\\w+]{5,255}; [SMTWF][a-z][a-z], [\\s\\d]?\\d [JFMAJSOND][a-z][a-z] \\d{4} \\d{2}:\\d{2}:\\d{2} [-+]\\d{4}$/mH'
+reconf['R_RCVD_SPAMBOTS'] = {
+ re = 'Received=/^from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by [-.\\w+]{5,255}; [SMTWF][a-z][a-z], [\\s\\d]?\\d [JFMAJSOND][a-z][a-z] \\d{4} \\d{2}:\\d{2}:\\d{2} [-+]\\d{4}$/mH',
+ score = 3.0,
+ description = 'Spambots signatures in received headers',
+ group = 'header'
+}
-- Charset is missing in message
-reconf['R_MISSING_CHARSET']= string.format('content_type_is_type(text) & !content_type_has_param(charset) & !%s', r_cte_7bit);
+reconf['R_MISSING_CHARSET'] = {
+ re = string.format('content_type_is_type(text) & !content_type_has_param(charset) & !%s', r_cte_7bit),
+ score = 2.5,
+ description = 'Charset is missing in a message',
+ group = 'header'
+}
-- Subject seems to be spam
-reconf['R_SAJDING'] = 'Subject=/\\bsajding(?:om|a)?\\b/iH'
+reconf['R_SAJDING'] = {
+ re = 'Subject=/\\bsajding(?:om|a)?\\b/iH',
+ score = 8.0,
+ description = 'Subject seems to be spam',
+ group = 'header'
+}
-- Find forged Outlook MUA
-- Yahoo groups messages
@@ -78,16 +127,36 @@ local yahoo_bulk = 'Received=/from \\[\\S+\\] by \\S+\\.(?:groups|scd|dcn)\\.yah
-- Outlook MUA
local outlook_mua = 'X-Mailer=/^Microsoft Outlook\\b/H'
local any_outlook_mua = 'X-Mailer=/^Microsoft Outlook\\b/H'
-reconf['FORGED_OUTLOOK_HTML'] = string.format('!%s & %s & %s', yahoo_bulk, outlook_mua, 'has_only_html_part()')
+reconf['FORGED_OUTLOOK_HTML'] = {
+ re = string.format('!%s & %s & %s', yahoo_bulk, outlook_mua, 'has_only_html_part()'),
+ score = 5.0,
+ description = 'Forged outlook HTML signature',
+ group = 'header'
+}
-- Recipients seems to be likely with each other (only works when recipients count is more than 5 recipients)
-reconf['SUSPICIOUS_RECIPS'] = 'compare_recipients_distance(0.65)'
+reconf['SUSPICIOUS_RECIPS'] = {
+ re = 'compare_recipients_distance(0.65)',
+ score = 1.5,
+ description = 'Recipients seems to be autogenerated (works if recipients count is more than 5)',
+ group = 'header'
+}
-- Recipients list seems to be sorted
-reconf['SORTED_RECIPS'] = 'is_recipients_sorted()'
+reconf['SORTED_RECIPS'] = {
+ re = 'is_recipients_sorted()',
+ score = 3.5,
+ description = 'Recipients list seems to be sorted',
+ group = 'header'
+}
-- Spam string at the end of message to make statistics faults
-reconf['TRACKER_ID'] = '/^[a-z0-9]{6,24}[-_a-z0-9]{12,36}[a-z0-9]{6,24}\\s*\\z/isPr'
+reconf['TRACKER_ID'] = {
+ re = '/^[a-z0-9]{6,24}[-_a-z0-9]{12,36}[a-z0-9]{6,24}\\s*\\z/isPr',
+ score = 3.84,
+ description = 'Spam string at the end of message to make statistics fault',
+ group = 'header'
+}
-- From that contains encoded characters while base 64 is not needed as all symbols are 7bit
@@ -96,13 +165,23 @@ local from_encoded_b64 = 'From=/\\=\\?\\S+\\?B\\?/iX'
-- From contains only 7bit characters (parsed headers are used)
local from_needs_mime = 'From=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
-- Final rule
-reconf['FROM_EXCESS_BASE64'] = string.format('%s & !%s', from_encoded_b64, from_needs_mime)
+reconf['FROM_EXCESS_BASE64'] = {
+ re = string.format('%s & !%s', from_encoded_b64, from_needs_mime),
+ score = 1.5,
+ description = 'From that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+ group = 'excessb64'
+}
-- From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
-- Regexp that checks that From header is encoded with quoted-printable (search in raw headers)
local from_encoded_qp = 'From=/\\=\\?\\S+\\?Q\\?/iX'
-- Final rule
-reconf['FROM_EXCESS_QP'] = string.format('%s & !%s', from_encoded_qp, from_needs_mime)
+reconf['FROM_EXCESS_QP'] = {
+ re = string.format('%s & !%s', from_encoded_qp, from_needs_mime),
+ score = 1.2,
+ description = 'From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+ group = 'excessqp'
+}
-- To that contains encoded characters while base 64 is not needed as all symbols are 7bit
-- Regexp that checks that To header is encoded with base64 (search in raw headers)
@@ -110,13 +189,23 @@ local to_encoded_b64 = 'To=/\\=\\?\\S+\\?B\\?/iX'
-- To contains only 7bit characters (parsed headers are used)
local to_needs_mime = 'To=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
-- Final rule
-reconf['TO_EXCESS_BASE64'] = string.format('%s & !%s', to_encoded_b64, to_needs_mime)
+reconf['TO_EXCESS_BASE64'] = {
+ re = string.format('%s & !%s', to_encoded_b64, to_needs_mime),
+ score = 1.5,
+ description = 'To that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+ group = 'excessb64'
+}
-- To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
-- Regexp that checks that To header is encoded with quoted-printable (search in raw headers)
local to_encoded_qp = 'To=/\\=\\?\\S+\\?Q\\?/iX'
-- Final rule
-reconf['TO_EXCESS_QP'] = string.format('%s & !%s', to_encoded_qp, to_needs_mime)
+reconf['TO_EXCESS_QP'] = {
+ re = string.format('%s & !%s', to_encoded_qp, to_needs_mime),
+ score = 1.2,
+ description = 'To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+ group = 'excessqp'
+}
-- Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit
-- Regexp that checks that Reply-To header is encoded with base64 (search in raw headers)
@@ -124,13 +213,23 @@ local replyto_encoded_b64 = 'Reply-To=/\\=\\?\\S+\\?B\\?/iX'
-- Reply-To contains only 7bit characters (parsed headers are used)
local replyto_needs_mime = 'Reply-To=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
-- Final rule
-reconf['REPLYTO_EXCESS_BASE64'] = string.format('%s & !%s', replyto_encoded_b64, replyto_needs_mime)
+reconf['REPLYTO_EXCESS_BASE64'] = {
+ re = string.format('%s & !%s', replyto_encoded_b64, replyto_needs_mime),
+ score = 1.5,
+ description = 'Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+ group = 'excessb64'
+}
-- Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
-- Regexp that checks that Reply-To header is encoded with quoted-printable (search in raw headers)
local replyto_encoded_qp = 'Reply-To=/\\=\\?\\S+\\?Q\\?/iX'
-- Final rule
-reconf['REPLYTO_EXCESS_QP'] = string.format('%s & !%s', replyto_encoded_qp, replyto_needs_mime)
+reconf['REPLYTO_EXCESS_QP'] = {
+ re = string.format('%s & !%s', replyto_encoded_qp, replyto_needs_mime),
+ score = 1.2,
+ description = 'Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+ group = 'excessqp'
+}
-- Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit
-- Regexp that checks that Cc header is encoded with base64 (search in raw headers)
@@ -138,13 +237,23 @@ local cc_encoded_b64 = 'Cc=/\\=\\?\\S+\\?B\\?/iX'
-- Co contains only 7bit characters (parsed headers are used)
local cc_needs_mime = 'Cc=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
-- Final rule
-reconf['CC_EXCESS_BASE64'] = string.format('%s & !%s', cc_encoded_b64, cc_needs_mime)
+reconf['CC_EXCESS_BASE64'] = {
+ re = string.format('%s & !%s', cc_encoded_b64, cc_needs_mime),
+ score = 1.5,
+ description = 'Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+ group = 'excessb64'
+}
-- Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
-- Regexp that checks that Cc header is encoded with quoted-printable (search in raw headers)
local cc_encoded_qp = 'Cc=/\\=\\?\\S+\\?Q\\?/iX'
-- Final rule
-reconf['CC_EXCESS_QP'] = string.format('%s & !%s', cc_encoded_qp, cc_needs_mime)
+reconf['CC_EXCESS_QP'] = {
+ re = string.format('%s & !%s', cc_encoded_qp, cc_needs_mime),
+ score = 1.2,
+ description = 'Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+ group = 'excessqp'
+}
-- Detect forged outlook headers
@@ -182,8 +291,13 @@ local forged_outlook_dollars = string.format('(%s & !%s & !%s & !%s & !%s & !%s)
local fmo_excl_o3416 = 'X-Mailer=/^Microsoft Outlook, Build 10.0.3416$/H'
local fmo_excl_oe3790 = 'X-Mailer=/^Microsoft Outlook Express 6.00.3790.3959$/H'
-- Summary rule for forged outlook
-reconf['FORGED_MUA_OUTLOOK'] = string.format('(%s | %s) & !%s & !%s & !%s',
- forged_oe, forged_outlook_dollars, fmo_excl_o3416, fmo_excl_oe3790, vista_msgid)
+reconf['FORGED_MUA_OUTLOOK'] = {
+ re = string.format('(%s | %s) & !%s & !%s & !%s',
+ forged_oe, forged_outlook_dollars, fmo_excl_o3416, fmo_excl_oe3790, vista_msgid),
+ score = 3.0,
+ description = 'Forged outlook MUA',
+ group = 'mua'
+}
-- HTML outlook signs
local mime_html = 'content_type_is_type(text) & content_type_is_subtype(/.?html/)'
@@ -191,20 +305,45 @@ local tag_exists_html = 'has_html_tag(html)'
local tag_exists_head = 'has_html_tag(head)'
local tag_exists_meta = 'has_html_tag(meta)'
local tag_exists_body = 'has_html_tag(body)'
-reconf['FORGED_OUTLOOK_TAGS'] = string.format('!%s & %s & %s & !(%s & %s & %s & %s)',
- yahoo_bulk, any_outlook_mua, mime_html, tag_exists_html, tag_exists_head,
- tag_exists_meta, tag_exists_body)
+reconf['FORGED_OUTLOOK_TAGS'] = {
+ re = string.format('!%s & %s & %s & !(%s & %s & %s & %s)',
+ yahoo_bulk, any_outlook_mua, mime_html, tag_exists_html, tag_exists_head,
+ tag_exists_meta, tag_exists_body),
+ score = 2.1,
+ description = "Message pretends to be send from Outlook but has 'strange' tags",
+ group = 'header'
+}
-- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY'] = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(00EBFFA4|0102FFA4|32C6FFA4|3302FFA4)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY'] = {
+ re = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(00EBFFA4|0102FFA4|32C6FFA4|3302FFA4)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX',
+ score = 5.0,
+ description = 'Suspicious boundary in header Content-Type',
+ group = 'mua'
+}
-- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY2'] = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(01C6527E)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY2'] = {
+ re = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(01C6527E)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX',
+ score = 4.0,
+ description = 'Suspicious boundary in header Content-Type',
+ group = 'mua'
+}
-- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY3'] = 'Content-Type=/^\\s*multipart.+boundary="-----000-00\\d\\d-01C[\\dA-F]{5}-[\\dA-F]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY3'] = {
+ re = 'Content-Type=/^\\s*multipart.+boundary="-----000-00\\d\\d-01C[\\dA-F]{5}-[\\dA-F]{8}"[\\r\\n]*$/siX',
+ score = 3.0,
+ description = 'Suspicious boundary in header Content-Type',
+ group = 'mua'
+}
-- Forged OE/MSO boundary
local suspicious_boundary_01C4 = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_01C4[\\dA-F]{4}\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
local suspicious_boundary_01C4_date = 'Date=/^\\s*\\w\\w\\w,\\s+\\d+\\s+\\w\\w\\w 20(0[56789]|1\\d)/'
-reconf['SUSPICIOUS_BOUNDARY4'] = string.format('(%s) & (%s)', suspicious_boundary_01C4, suspicious_boundary_01C4_date)
+reconf['SUSPICIOUS_BOUNDARY4'] = {
+ re = string.format('(%s) & (%s)', suspicious_boundary_01C4, suspicious_boundary_01C4_date),
+ score = 4.0,
+ description = 'Suspicious boundary in header Content-Type',
+ group = 'mua'
+}
-- Detect forged The Bat! headers
-- The Bat! X-Mailer header
@@ -214,10 +353,19 @@ local thebat_msgid_common = 'Message-ID=/^<?\\d+\\.\\d+\\@\\S+>?$/mH'
-- Correct The Bat! Message-ID template
local thebat_msgid = 'Message-ID=/^<?\\d+\\.(19[789]\\d|20\\d\\d)(0\\d|1[012])([012]\\d|3[01])([0-5]\\d)([0-5]\\d)([0-5]\\d)\\@\\S+>?/mH'
-- Summary rule for forged The Bat! Message-ID header
-reconf['FORGED_MUA_THEBAT_MSGID'] = string.format('(%s) & !(%s) & (%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid)
+reconf['FORGED_MUA_THEBAT_MSGID'] = {
+ re = string.format('(%s) & !(%s) & (%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid),
+ score = 4.0,
+ description = 'Message pretends to be send from The Bat! but has forged Message-ID',
+ group = 'mua'
+}
-- Summary rule for forged The Bat! Message-ID header with unknown template
-reconf['FORGED_MUA_THEBAT_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid)
-
+reconf['FORGED_MUA_THEBAT_MSGID_UNKNOWN'] = {
+ re = string.format('(%s) & !(%s) & !(%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid),
+ score = 3.0,
+ description = 'Message pretends to be send from The Bat! but has forged Message-ID',
+ group = 'mua'
+}
-- Detect forged KMail headers
-- KMail User-Agent header
@@ -235,9 +383,19 @@ function kmail_msgid (task)
return false
end
-- Summary rule for forged KMail Message-ID header
-reconf['FORGED_MUA_KMAIL_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, 'kmail_msgid', unusable_msgid)
+reconf['FORGED_MUA_KMAIL_MSGID'] = {
+ re = string.format('(%s) & (%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, 'kmail_msgid', unusable_msgid),
+ score = 3.0,
+ description = 'Message pretends to be send from KMail but has forged Message-ID',
+ group = 'mua'
+}
-- Summary rule for forged KMail Message-ID header with unknown template
-reconf['FORGED_MUA_KMAIL_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, unusable_msgid)
+reconf['FORGED_MUA_KMAIL_MSGID_UNKNOWN'] = {
+ re = string.format('(%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, unusable_msgid),
+ score = 2.5,
+ description = 'Message pretends to be send from KMail but has forged Message-ID',
+ group = 'mua'
+}
-- Detect forged Opera Mail headers
-- Opera Mail User-Agent header
@@ -249,10 +407,19 @@ local suspicious_opera10w_mua = 'User-Agent=/^\\s*Opera Mail\\/10\\.\\d+ \\(Wind
-- Suspicious Opera Mail Message-ID, apparently from KMail
local suspicious_opera10w_msgid = 'Message-Id=/^<?2009\\d{8}\\.\\d+\\.\\S+\\@\\S+?>$/H'
-- Summary rule for forged Opera Mail User-Agent header and Message-ID header from KMail
-reconf['SUSPICIOUS_OPERA_10W_MSGID'] = string.format('(%s) & (%s)', suspicious_opera10w_mua, suspicious_opera10w_msgid)
+reconf['SUSPICIOUS_OPERA_10W_MSGID'] = {
+ re = string.format('(%s) & (%s)', suspicious_opera10w_mua, suspicious_opera10w_msgid),
+ score = 4.0,
+ description = 'Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail',
+ group = 'mua'
+}
-- Summary rule for forged Opera Mail Message-ID header
-reconf['FORGED_MUA_OPERA_MSGID'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', opera1x_mua, opera1x_msgid, reconf['SUSPICIOUS_OPERA_10W_MSGID'], unusable_msgid)
-
+reconf['FORGED_MUA_OPERA_MSGID'] = {
+ re = string.format('(%s) & !(%s) & !(%s) & !(%s)', opera1x_mua, opera1x_msgid, reconf['SUSPICIOUS_OPERA_10W_MSGID']['re'], unusable_msgid),
+ score = 4.0,
+ description = 'Message pretends to be send from Opera Mail but has forged Message-ID',
+ group = 'mua'
+}
-- Detect forged Mozilla Mail/Thunderbird/Seamonkey headers
-- Mozilla based X-Mailer
@@ -265,20 +432,56 @@ local mozilla_msgid_common = 'Message-ID=/^\\s*<[\\dA-F]{8}\\.\\d{1,7}\\@([^>\\.
local mozilla_msgid_common_sec = 'Message-ID=/^\\s*<[\\da-f]{8}-([\\da-f]{4}-){3}[\\da-f]{12}\\@([^>\\.]+\\.)+[^>\\.]+>$/H'
local mozilla_msgid = 'Message-ID=/^\\s*<(3[3-9A-F]|4[\\dA-F]|5[\\dA-F])[\\dA-F]{6}\\.(\\d0){1,4}\\d\\@([^>\\.]+\\.)+[^>\\.]+>$/H'
-- Summary rule for forged Mozilla Mail Message-ID header
-reconf['FORGED_MUA_MOZILLA_MAIL_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_MOZILLA_MAIL_MSGID'] = {
+ re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+ score = 4.0,
+ description = 'Message pretends to be send from Mozilla Mail but has forged Message-ID',
+ group = 'mua'
+}
+reconf['FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN'] = {
+ re = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+ score = 2.5,
+ description = 'Message pretends to be send from Mozilla Mail but has forged Message-ID',
+ group = 'mua'
+}
+
-- Summary rule for forged Thunderbird Message-ID header
-reconf['FORGED_MUA_THUNDERBIRD_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN'] = string.format('(%s) & !((%s) | (%s)) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid_common_sec, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_THUNDERBIRD_MSGID'] = {
+ re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+ score = 4.0,
+ description = 'Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID',
+ group = 'mua'
+}
+reconf['FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN'] = {
+ re = string.format('(%s) & !((%s) | (%s)) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid_common_sec, mozilla_msgid, unusable_msgid),
+ score = 2.5,
+ description = 'Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID',
+ group = 'mua'
+}
-- Summary rule for forged Seamonkey Message-ID header
-reconf['FORGED_MUA_SEAMONKEY_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_SEAMONKEY_MSGID'] = {
+ re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+ score = 4.0,
+ description = 'Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID',
+ group = 'mua'
+}
+reconf['FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN'] = {
+ re = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+ score = 2.5,
+ description = 'Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID',
+ group = 'mua'
+}
-- Message id validity
local sane_msgid = 'Message-Id=/^<?[^<>\\\\ \\t\\n\\r\\x0b\\x80-\\xff]+\\@[^<>\\\\ \\t\\n\\r\\x0b\\x80-\\xff]+>?\\s*$/H'
local msgid_comment = 'Message-Id=/\\(.*\\)/H'
-reconf['INVALID_MSGID'] = string.format('(%s) & !((%s) | (%s))', has_mid, sane_msgid, msgid_comment)
+reconf['INVALID_MSGID'] = {
+ re = string.format('(%s) & !((%s) | (%s))', has_mid, sane_msgid, msgid_comment),
+ score = 1.7,
+ description = 'Message id is incorrect',
+ group = 'header'
+}
-- Only Content-Type header without other MIME headers
@@ -287,17 +490,32 @@ local cte = 'header_exists(Content-Transfer-Encoding)'
local ct = 'header_exists(Content-Type)'
local mime_version = 'raw_header_exists(MIME-Version)'
local ct_text_plain = 'content_type_is_type(text) & content_type_is_subtype(plain)'
-reconf['MIME_HEADER_CTYPE_ONLY'] = string.format('!(%s) & !(%s) & (%s) & !(%s) & !(%s)', cd, cte, ct, mime_version, ct_text_plain)
+reconf['MIME_HEADER_CTYPE_ONLY'] = {
+ re = string.format('!(%s) & !(%s) & (%s) & !(%s) & !(%s)', cd, cte, ct, mime_version, ct_text_plain),
+ score = 2.0,
+ description = 'Only Content-Type header without other MIME headers',
+ group = 'header'
+}
-- Forged Exchange messages
local msgid_dollars_ok = 'Message-Id=/[0-9a-f]{4,}\\$[0-9a-f]{4,}\\$[0-9a-f]{4,}\\@\\S+/H'
local mimeole_ms = 'X-MimeOLE=/^Produced By Microsoft MimeOLE/H'
local rcvd_with_exchange = 'Received=/with Microsoft Exchange Server/H'
-reconf['RATWARE_MS_HASH'] = string.format('(%s) & !(%s) & !(%s)', msgid_dollars_ok, mimeole_ms, rcvd_with_exchange)
+reconf['RATWARE_MS_HASH'] = {
+ re = string.format('(%s) & !(%s) & !(%s)', msgid_dollars_ok, mimeole_ms, rcvd_with_exchange),
+ score = 2.0,
+ description = 'Forged Exchange messages',
+ group = 'header'
+}
-- Reply-type in content-type
-reconf['STOX_REPLY_TYPE'] = 'Content-Type=/text\\/plain; .* reply-type=original/H'
+reconf['STOX_REPLY_TYPE'] = {
+ re = 'Content-Type=/text\\/plain; .* reply-type=original/H',
+ score = 1.0,
+ description = 'Reply-type in content-type',
+ group = 'header'
+}
-- Fake Verizon headers
local fhelo_verizon = 'X-Spam-Relays-Untrusted=/^[^\\]]+ helo=[^ ]+verizon\\.net /iH'
@@ -308,7 +526,12 @@ reconf['FM_FAKE_HELO_VERIZON'] = string.format('(%s) & !(%s)', fhelo_verizon, fh
local at_yahoo_msgid = 'Message-Id=/\\@yahoo\\.com\\b/iH'
local at_yahoogroups_msgid = 'Message-Id=/\\@yahoogroups\\.com\\b/iH'
local from_yahoo_com = 'From=/\\@yahoo\\.com\\b/iH'
-reconf['FORGED_MSGID_YAHOO'] = string.format('(%s) & !(%s)', at_yahoo_msgid, from_yahoo_com)
+reconf['FORGED_MSGID_YAHOO'] = {
+ re = string.format('(%s) & !(%s)', at_yahoo_msgid, from_yahoo_com),
+ score = 2.0,
+ description = 'Forged yahoo msgid',
+ group = 'header'
+}
local r_from_yahoo_groups = 'From=/rambler.ru\\@returns\\.groups\\.yahoo\\.com\\b/iH'
local r_from_yahoo_groups_ro = 'From=/ro.ru\\@returns\\.groups\\.yahoo\\.com\\b/iH'
@@ -317,18 +540,33 @@ local thebat_mua_v1 = 'X-Mailer=/^The Bat! \\(v1\\./H'
local ctype_has_boundary = 'Content-Type=/boundary/iH'
local bat_boundary = 'Content-Type=/boundary=\\"?-{10}/H'
local mailman_21 = 'X-Mailman-Version=/\\d/H'
-reconf['FORGED_MUA_THEBAT_BOUN'] = string.format('(%s) & (%s) & !(%s) & !(%s)', thebat_mua_v1, ctype_has_boundary, bat_boundary, mailman_21)
+reconf['FORGED_MUA_THEBAT_BOUN'] = {
+ re = string.format('(%s) & (%s) & !(%s) & !(%s)', thebat_mua_v1, ctype_has_boundary, bat_boundary, mailman_21),
+ score = 2.0,
+ description = 'Forged The Bat! MUA headers',
+ group = 'header'
+}
-- Two received headers with ip addresses
local double_ip_spam_1 = 'Received=/from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by \\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} with/H'
local double_ip_spam_2 = 'Received=/from\\s+\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\s+by\\s+\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3};/H'
-reconf['RCVD_DOUBLE_IP_SPAM'] = string.format('(%s) | (%s)', double_ip_spam_1, double_ip_spam_2)
+reconf['RCVD_DOUBLE_IP_SPAM'] = {
+ re = string.format('(%s) | (%s)', double_ip_spam_1, double_ip_spam_2),
+ score = 2.0,
+ description = 'Two received headers with ip addresses',
+ group = 'header'
+}
-- Quoted reply-to from yahoo (seems to be forged)
local repto_quote = 'Reply-To=/\\".*\\"\\s*\\</H'
local from_yahoo_com = 'From=/\\@yahoo\\.com\\b/iH'
local at_yahoo_msgid = 'Message-Id=/\\@yahoo\\.com\\b/iH'
-reconf['REPTO_QUOTE_YAHOO'] = string.format('(%s) & ((%s) | (%s))', repto_quote, from_yahoo_com, at_yahoo_msgid)
+reconf['REPTO_QUOTE_YAHOO'] = {
+ re = string.format('(%s) & ((%s) | (%s))', repto_quote, from_yahoo_com, at_yahoo_msgid),
+ score = 2.0,
+ description = 'Quoted reply-to from yahoo (seems to be forged)',
+ group = 'header'
+}
-- MUA definitions
local xm_gnus = 'X-Mailer=/^Gnus v/H'
@@ -349,18 +587,28 @@ local subj_re = 'Subject=/^R[eE]:/H'
local has_ref = 'header_exists(References)'
local missing_ref = string.format('!(%s)', has_ref)
-- Fake reply (has RE in subject, but has not References header)
-reconf['FAKE_REPLY_C'] = string.format('(%s) & (%s) & (%s) & !(%s)', subj_re, missing_ref, no_inr_yes_ref, xm_msoe6)
+reconf['FAKE_REPLY_C'] = {
+ re = string.format('(%s) & (%s) & (%s) & !(%s)', subj_re, missing_ref, no_inr_yes_ref, xm_msoe6),
+ score = 6.0,
+ description = 'Fake reply (has RE in subject, but has not References header)',
+ group = 'subject'
+}
-- Mime-OLE is needed but absent (e.g. fake Outlook or fake Ecxchange)
local has_msmail_pri = 'header_exists(X-MSMail-Priority)'
local has_mimeole = 'header_exists(X-MimeOLE)'
local has_squirrelmail_in_mailer = 'X-Mailer=/SquirrelMail\\b/H'
local has_office12145_in_mailer = 'X-Mailer=/^Microsoft (?:Office )?Outlook 1[245]\\.0/'
-reconf['MISSING_MIMEOLE'] = string.format('(%s) & !(%s) & !(%s) & !(%s)',
- has_msmail_pri,
- has_mimeole,
- has_squirrelmail_in_mailer,
- has_office12145_in_mailer)
+reconf['MISSING_MIMEOLE'] = {
+ re = string.format('(%s) & !(%s) & !(%s) & !(%s)',
+ has_msmail_pri,
+ has_mimeole,
+ has_squirrelmail_in_mailer,
+ has_office12145_in_mailer),
+ score = 2.0,
+ description = 'Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)',
+ group = 'header'
+}
-- Header delimiters
local yandex_from = 'From=/\\@(yandex\\.ru|yandex\\.net|ya\\.ru)/iX'
@@ -375,11 +623,36 @@ function check_header_delimiter_tab(task, header_name)
end
return false
end
-reconf['HEADER_FROM_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(From)', yandex)
-reconf['HEADER_TO_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(To)', yandex)
-reconf['HEADER_CC_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Cc)', yandex)
-reconf['HEADER_REPLYTO_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Reply-To)', yandex)
-reconf['HEADER_DATE_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Date)', yandex)
+reconf['HEADER_FROM_DELIMITER_TAB'] = {
+ re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(From)', yandex),
+ score = 1.0,
+ description = 'Header From begins with tab',
+ group = 'header'
+}
+reconf['HEADER_TO_DELIMITER_TAB'] = {
+ re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(To)', yandex),
+ score = 1.0,
+ description = 'Header To begins with tab',
+ group = 'header'
+}
+reconf['HEADER_CC_DELIMITER_TAB'] = {
+ re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Cc)', yandex),
+ score = 1.0,
+ description = 'Header To begins with tab',
+ group = 'header'
+}
+reconf['HEADER_REPLYTO_DELIMITER_TAB'] = {
+ re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Reply-To)', yandex),
+ score = 1.0,
+ description = 'Header Reply-To begins with tab',
+ group = 'header'
+}
+reconf['HEADER_DATE_DELIMITER_TAB'] = {
+ re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Date)', yandex),
+ score = 1.0,
+ description = 'Header Date begins with tab',
+ group = 'header'
+}
-- Empty delimiters between header names and header values
function check_header_delimiter_empty(task, header_name)
for _,rh in ipairs(task:get_header_full(header_name)) do
@@ -387,21 +660,56 @@ function check_header_delimiter_empty(task, header_name)
end
return false
end
-reconf['HEADER_FROM_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(From)')
-reconf['HEADER_TO_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(To)')
-reconf['HEADER_CC_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Cc)')
-reconf['HEADER_REPLYTO_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Reply-To)')
-reconf['HEADER_DATE_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Date)')
+reconf['HEADER_FROM_EMPTY_DELIMITER'] = {
+ re = string.format('(%s)', 'check_header_delimiter_empty(From)'),
+ score = 1.0,
+ description = 'Header From has no delimiter between header name and header value',
+ group = 'header'
+}
+reconf['HEADER_TO_EMPTY_DELIMITER'] = {
+ re = string.format('(%s)', 'check_header_delimiter_empty(To)'),
+ score = 1.0,
+ description = 'Header To has no delimiter between header name and header value',
+ group = 'header'
+}
+reconf['HEADER_CC_EMPTY_DELIMITER'] = {
+ re = string.format('(%s)', 'check_header_delimiter_empty(Cc)'),
+ score = 1.0,
+ description = 'Header Cc has no delimiter between header name and header value',
+ group = 'header'
+}
+reconf['HEADER_REPLYTO_EMPTY_DELIMITER'] = {
+ re = string.format('(%s)', 'check_header_delimiter_empty(Reply-To)'),
+ score = 1.0,
+ description = 'Header Reply-To has no delimiter between header name and header value',
+ group = 'header'
+}
+reconf['HEADER_DATE_EMPTY_DELIMITER'] = {
+ re = string.format('(%s)', 'check_header_delimiter_empty(Date)'),
+ score = 1.0,
+ description = 'Header Date has no delimiter between header name and header value',
+ group = 'header'
+}
-- Definitions of received headers regexp
-reconf['RCVD_ILLEGAL_CHARS'] = 'Received=/[\\x80-\\xff]/X'
+reconf['RCVD_ILLEGAL_CHARS'] = {
+ re = 'Received=/[\\x80-\\xff]/X',
+ score = 4.0,
+ description = 'Header Received has raw illegal character',
+ group = 'header'
+}
local MAIL_RU_Return_Path = 'Return-path=/^\\s*<.+\\@mail\\.ru>$/iX'
local MAIL_RU_X_Envelope_From = 'X-Envelope-From=/^\\s*<.+\\@mail\\.ru>$/iX'
local MAIL_RU_From = 'From=/\\@mail\\.ru>?$/iX'
local MAIL_RU_Received = 'Received=/from mail\\.ru \\(/mH'
-reconf['FAKE_RECEIVED_mail_ru'] = string.format('(%s) & !(((%s) | (%s)) & (%s))', MAIL_RU_Received, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, MAIL_RU_From)
+reconf['FAKE_RECEIVED_mail_ru'] = {
+ re = string.format('(%s) & !(((%s) | (%s)) & (%s))', MAIL_RU_Received, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, MAIL_RU_From),
+ score = 4.0,
+ description = 'Fake helo mail.ru in header Received from non mail.ru sender address',
+ group = 'header'
+}
local GMAIL_COM_Return_Path = 'Return-path=/^\\s*<.+\\@gmail\\.com>$/iX'
local GMAIL_COM_X_Envelope_From = 'X-Envelope-From=/^\\s*<.+\\@gmail\\.com>$/iX'
@@ -421,19 +729,54 @@ local RECEIVED_smtp_yandex_ru_7 = 'Received=/from \\S+ \\(HELO smtp\\.yandex\\.r
local RECEIVED_smtp_yandex_ru_8 = 'Received=/from \\S+ \\(HELO smtp\\.yandex\\.ru\\) \\(\\d+\\.\\d+\\.\\d+\\.\\d+\\)/iX'
local RECEIVED_smtp_yandex_ru_9 = 'Received=/from \\S+ \\(\\[\\d+\\.\\d+\\.\\d+\\.\\d+\\] helo=smtp\\.yandex\\.ru\\)/iX'
-reconf['FAKE_RECEIVED_smtp_yandex_ru'] = string.format('(((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s)))) & (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s)', MAIL_RU_From, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, GMAIL_COM_From, GMAIL_COM_Return_Path, GMAIL_COM_X_Envelope_From, UKR_NET_From, UKR_NET_Return_Path, UKR_NET_X_Envelope_From, RECEIVED_smtp_yandex_ru_1, RECEIVED_smtp_yandex_ru_2, RECEIVED_smtp_yandex_ru_3, RECEIVED_smtp_yandex_ru_4, RECEIVED_smtp_yandex_ru_5, RECEIVED_smtp_yandex_ru_6, RECEIVED_smtp_yandex_ru_7, RECEIVED_smtp_yandex_ru_8, RECEIVED_smtp_yandex_ru_9)
+reconf['FAKE_RECEIVED_smtp_yandex_ru'] = {
+ re = string.format('(((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s)))) & (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s)', MAIL_RU_From, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, GMAIL_COM_From, GMAIL_COM_Return_Path, GMAIL_COM_X_Envelope_From, UKR_NET_From, UKR_NET_Return_Path, UKR_NET_X_Envelope_From, RECEIVED_smtp_yandex_ru_1, RECEIVED_smtp_yandex_ru_2, RECEIVED_smtp_yandex_ru_3, RECEIVED_smtp_yandex_ru_4, RECEIVED_smtp_yandex_ru_5, RECEIVED_smtp_yandex_ru_6, RECEIVED_smtp_yandex_ru_7, RECEIVED_smtp_yandex_ru_8, RECEIVED_smtp_yandex_ru_9),
+ score = 4.0,
+ description = 'Fake smtp.yandex.ru Received',
+ group = 'header'
+}
-reconf['FORGED_GENERIC_RECEIVED'] = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by (([\\w\\d-]+\\.)+[a-zA-Z]{2,6}|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}); \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X'
+reconf['FORGED_GENERIC_RECEIVED'] = {
+ re = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by (([\\w\\d-]+\\.)+[a-zA-Z]{2,6}|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}); \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X',
+ score = 3.6,
+ description = 'Forged generic Received',
+ group = 'header'
+}
-reconf['FORGED_GENERIC_RECEIVED2'] = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by ([\\w\\d-]+\\.)+[a-z]{2,6} id [\\w\\d]{12}; \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X'
+reconf['FORGED_GENERIC_RECEIVED2'] = {
+ re = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by ([\\w\\d-]+\\.)+[a-z]{2,6} id [\\w\\d]{12}; \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X',
+ score = 3.6,
+ description = 'Forged generic Received',
+ group = 'header'
+}
-reconf['FORGED_GENERIC_RECEIVED3'] = 'Received=/^\\s*(.+\\n)*by \\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} with SMTP id [a-zA-Z]{14}\\.\\d{13};[\\r\\n\\s]*\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0 \\(GMT\\)/X'
+reconf['FORGED_GENERIC_RECEIVED3'] = {
+ re = 'Received=/^\\s*(.+\\n)*by \\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} with SMTP id [a-zA-Z]{14}\\.\\d{13};[\\r\\n\\s]*\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0 \\(GMT\\)/X',
+ score = 3.6,
+ description = 'Forged generic Received',
+ group = 'header'
+}
-reconf['FORGED_GENERIC_RECEIVED4'] = 'Received=/^\\s*(.+\\n)*from localhost by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0[\\s\\r\\n]*$/X'
+reconf['FORGED_GENERIC_RECEIVED4'] = {
+ re = 'Received=/^\\s*(.+\\n)*from localhost by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0[\\s\\r\\n]*$/X',
+ score = 3.6,
+ description = 'Forged generic Received',
+ group = 'header'
+}
-reconf['FORGED_GENERIC_RECEIVED5'] = 'Received=/\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$/X'
+reconf['FORGED_GENERIC_RECEIVED5'] = {
+ re = 'Received=/\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$/X',
+ score = 4.6,
+ description = 'Forged generic Received',
+ group = 'header'
+}
-reconf['INVALID_POSTFIX_RECEIVED'] = 'Received=/ \\(Postfix\\) with ESMTP id [A-Z\\d]+([\\s\\r\\n]+for <\\S+?>)?;[\\s\\r\\n]*[A-Z][a-z]{2}, \\d{1,2} [A-Z][a-z]{2} \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d [\\+\\-]\\d\\d\\d\\d$/X'
+reconf['INVALID_POSTFIX_RECEIVED'] = {
+ re = 'Received=/ \\(Postfix\\) with ESMTP id [A-Z\\d]+([\\s\\r\\n]+for <\\S+?>)?;[\\s\\r\\n]*[A-Z][a-z]{2}, \\d{1,2} [A-Z][a-z]{2} \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d [\\+\\-]\\d\\d\\d\\d$/X',
+ score = 3.0,
+ description = 'Invalid Postfix Received',
+ group = 'header'
+}
reconf['X_PHP_EVAL'] = {
re = "X-PHP-Originating-Script=/ : eval\\(\\)'d code$/X",
diff --git a/rules/regexp/lotto.lua b/rules/regexp/lotto.lua
index c5cdff4c6..df0f2577a 100644
--- a/rules/regexp/lotto.lua
+++ b/rules/regexp/lotto.lua
@@ -28,4 +28,4 @@ local kam_lotto3 = '/(won|claim|cash prize|pounds? sterling)/isrP'
local kam_lotto4 = '/(claims (officer|agent)|lottery coordinator|fiduciary (officer|agent)|fiduaciary claims)/isrP'
local kam_lotto5 = '/(freelotto group|Royal Heritage Lottery|UK National (Online)? Lottery|U\\.?K\\.? Grand Promotions|Lottery Department UK|Euromillion Loteria|Luckyday International Lottery|International Lottery)/isrP'
local kam_lotto6 = '/(Dear Lucky Winner|Winning Notification|Attention:Winner|Dear Winner)/isrP'
-reconf['R_LOTTO'] = string.format('((%s) | (%s) | (%s)) & (((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s)) >= 3)', reconf['R_UNDISC_RCPT'], reconf['R_BAD_CTE_7BIT'], reconf['R_NO_SPACE_IN_FROM'], r_lotto_from, r_lotto_subject, r_lotto_body, kam_lotto1, kam_lotto2, kam_lotto3, kam_lotto4, kam_lotto5, kam_lotto6)
+reconf['R_LOTTO'] = string.format('((%s) | (%s) | (%s)) & (((%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s) + (%s)) >= 3)', reconf['R_UNDISC_RCPT']['re'], reconf['R_BAD_CTE_7BIT']['re'], reconf['R_NO_SPACE_IN_FROM']['re'], r_lotto_from, r_lotto_subject, r_lotto_body, kam_lotto1, kam_lotto2, kam_lotto3, kam_lotto4, kam_lotto5, kam_lotto6)
diff --git a/src/controller.c b/src/controller.c
index 7280d9951..2b19a7dd7 100644
--- a/src/controller.c
+++ b/src/controller.c
@@ -2836,6 +2836,8 @@ start_controller_worker (struct rspamd_worker *worker)
rspamd_upstreams_library_config (worker->srv->cfg, worker->srv->cfg->ups_ctx,
ctx->ev_base, ctx->resolver->r);
+ rspamd_redis_pool_config (worker->srv->cfg->redis_pool,
+ worker->srv->cfg, ctx->ev_base);
/* Maps events */
rspamd_map_watch (worker->srv->cfg, ctx->ev_base, ctx->resolver);
rspamd_symbols_cache_start_refresh (worker->srv->cfg->cache, ctx->ev_base);
diff --git a/src/libserver/CMakeLists.txt b/src/libserver/CMakeLists.txt
index 49e4e6d25..295ad59c8 100644
--- a/src/libserver/CMakeLists.txt
+++ b/src/libserver/CMakeLists.txt
@@ -13,6 +13,7 @@ SET(LIBRSPAMDSERVERSRC
${CMAKE_CURRENT_SOURCE_DIR}/monitored.c
${CMAKE_CURRENT_SOURCE_DIR}/protocol.c
${CMAKE_CURRENT_SOURCE_DIR}/proxy.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/redis_pool.c
${CMAKE_CURRENT_SOURCE_DIR}/re_cache.c
${CMAKE_CURRENT_SOURCE_DIR}/roll_history.c
${CMAKE_CURRENT_SOURCE_DIR}/spf.c
diff --git a/src/libserver/cfg_file.h b/src/libserver/cfg_file.h
index 48c3c812f..7ce7f98a4 100644
--- a/src/libserver/cfg_file.h
+++ b/src/libserver/cfg_file.h
@@ -16,6 +16,8 @@
#include "libserver/re_cache.h"
#include "ref.h"
#include "libutil/radix.h"
+#include "monitored.h"
+#include "redis_pool.h"
#define DEFAULT_BIND_PORT 11333
#define DEFAULT_CONTROL_PORT 11334
@@ -406,6 +408,7 @@ struct rspamd_config {
struct rspamd_external_libs_ctx *libs_ctx; /**< context for external libraries */
struct rspamd_monitored_ctx *monitored_ctx; /**< context for monitored resources */
+ struct rspamd_redis_pool *redis_pool; /**< redis connectiosn pool */
struct rspamd_re_cache *re_cache; /**< static regexp cache */
diff --git a/src/libserver/cfg_utils.c b/src/libserver/cfg_utils.c
index decd33156..a50986c80 100644
--- a/src/libserver/cfg_utils.c
+++ b/src/libserver/cfg_utils.c
@@ -166,6 +166,7 @@ rspamd_config_new (void)
cfg->ssl_ciphers = "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4";
cfg->max_message = DEFAULT_MAX_MESSAGE;
cfg->monitored_ctx = rspamd_monitored_ctx_init ();
+ cfg->redis_pool = rspamd_redis_pool_init ();
REF_INIT_RETAIN (cfg, rspamd_config_free);
diff --git a/src/libserver/monitored.c b/src/libserver/monitored.c
index b69261234..ab6922e73 100644
--- a/src/libserver/monitored.c
+++ b/src/libserver/monitored.c
@@ -65,7 +65,7 @@ struct rspamd_monitored {
};
#define msg_err_mon(...) rspamd_default_log_function (G_LOG_LEVEL_CRITICAL, \
- "map", m->tag, \
+ "monitored", m->tag, \
G_STRFUNC, \
__VA_ARGS__)
#define msg_warn_mon(...) rspamd_default_log_function (G_LOG_LEVEL_WARNING, \
diff --git a/src/libserver/redis_pool.c b/src/libserver/redis_pool.c
new file mode 100644
index 000000000..f3f64d2f5
--- /dev/null
+++ b/src/libserver/redis_pool.c
@@ -0,0 +1,378 @@
+/*-
+ * Copyright 2016 Vsevolod Stakhov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <event.h>
+#include "redis_pool.h"
+#include "cfg_file.h"
+#include "contrib/hiredis/hiredis.h"
+#include "contrib/hiredis/async.h"
+#include "contrib/hiredis/adapters/libevent.h"
+#include "cryptobox.h"
+#include "ref.h"
+#include "logger.h"
+
+struct rspamd_redis_pool_elt;
+
+struct rspamd_redis_pool_connection {
+ struct redisAsyncContext *ctx;
+ struct rspamd_redis_pool_elt *elt;
+ GList *entry;
+ struct event timeout;
+ gboolean active;
+ gchar tag[MEMPOOL_UID_LEN];
+ ref_entry_t ref;
+};
+
+struct rspamd_redis_pool_elt {
+ struct rspamd_redis_pool *pool;
+ guint64 key;
+ GQueue *active;
+ GQueue *inactive;
+};
+
+struct rspamd_redis_pool {
+ struct event_base *ev_base;
+ struct rspamd_config *cfg;
+ GHashTable *elts_by_key;
+ GHashTable *elts_by_ctx;
+ gdouble timeout;
+ guint max_conns;
+};
+
+static const gdouble default_timeout = 60.0;
+static const guint default_max_conns = 100;
+
+#define msg_err_rpool(...) rspamd_default_log_function (G_LOG_LEVEL_CRITICAL, \
+ "redis_pool", conn->tag, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_warn_rpool(...) rspamd_default_log_function (G_LOG_LEVEL_WARNING, \
+ "redis_pool", conn->tag, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_info_rpool(...) rspamd_default_log_function (G_LOG_LEVEL_INFO, \
+ "redis_pool", conn->tag, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_debug_rpool(...) rspamd_default_log_function (G_LOG_LEVEL_DEBUG, \
+ "redis_pool", conn->tag, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+
+static inline guint64
+rspamd_redis_pool_get_key (const gchar *db, const gchar *password,
+ const char *ip, int port)
+{
+ rspamd_cryptobox_fast_hash_state_t st;
+
+ rspamd_cryptobox_fast_hash_init (&st, rspamd_hash_seed ());
+
+ if (db) {
+ rspamd_cryptobox_fast_hash_update (&st, db, strlen (db));
+ }
+ if (password) {
+ rspamd_cryptobox_fast_hash_update (&st, password, strlen (password));
+ }
+
+ rspamd_cryptobox_fast_hash_update (&st, ip, strlen (ip));
+ rspamd_cryptobox_fast_hash_update (&st, &port, sizeof (port));
+
+ return rspamd_cryptobox_fast_hash_final (&st);
+}
+
+
+static void
+rspamd_redis_pool_conn_dtor (struct rspamd_redis_pool_connection *conn)
+{
+ if (conn->active) {
+ msg_debug_rpool ("active connection removed");
+
+ if (conn->ctx) {
+ g_hash_table_remove (conn->elt->pool->elts_by_ctx, conn->ctx);
+ redisAsyncFree (conn->ctx);
+ }
+
+ g_queue_unlink (conn->elt->active, conn->entry);
+ }
+ else {
+ msg_debug_rpool ("inactive connection removed");
+
+ if (event_get_base (&conn->timeout)) {
+ event_del (&conn->timeout);
+ }
+
+ g_queue_unlink (conn->elt->inactive, conn->entry);
+ }
+
+
+ g_list_free (conn->entry);
+ g_slice_free1 (sizeof (*conn), conn);
+}
+
+static void
+rspamd_redis_pool_elt_dtor (gpointer p)
+{
+ GList *cur;
+ struct rspamd_redis_pool_elt *elt = p;
+ struct rspamd_redis_pool_connection *c;
+
+ for (cur = elt->active->head; cur != NULL; cur = g_list_next (cur)) {
+ c = cur->data;
+ REF_RELEASE (c);
+ }
+
+ for (cur = elt->inactive->head; cur != NULL; cur = g_list_next (cur)) {
+ c = cur->data;
+ REF_RELEASE (c);
+ }
+
+ g_queue_free (elt->active);
+ g_queue_free (elt->inactive);
+ g_slice_free1 (sizeof (*elt), elt);
+}
+
+static void
+rspamd_redis_conn_timeout (gint fd, short what, gpointer p)
+{
+ struct rspamd_redis_pool_connection *conn = p;
+
+ msg_debug_rpool ("scheduled removal of connection");
+ REF_RELEASE (conn);
+}
+
+static void
+rspamd_redis_pool_schedule_timeout (struct rspamd_redis_pool_connection *conn)
+{
+ struct timeval tv;
+ gdouble real_timeout;
+ guint active_elts;
+
+ active_elts = g_queue_get_length (conn->elt->active);
+
+ if (active_elts > conn->elt->pool->max_conns) {
+ real_timeout = conn->elt->pool->timeout / 2.0;
+ real_timeout = rspamd_time_jitter (real_timeout, real_timeout / 4.0);
+ }
+ else {
+ real_timeout = conn->elt->pool->timeout;
+ real_timeout = rspamd_time_jitter (real_timeout, real_timeout / 2.0);
+ }
+
+ msg_debug_rpool ("scheduled connection cleanup in %.1f seconds",
+ real_timeout);
+ double_to_tv (real_timeout, &tv);
+ event_set (&conn->timeout, -1, EV_TIMEOUT, rspamd_redis_conn_timeout, conn);
+ event_base_set (conn->elt->pool->ev_base, &conn->timeout);
+ event_add (&conn->timeout, &tv);
+}
+
+static struct rspamd_redis_pool_connection *
+rspamd_redis_pool_new_connection (struct rspamd_redis_pool *pool,
+ struct rspamd_redis_pool_elt *elt,
+ const char *db,
+ const char *password,
+ const char *ip,
+ gint port)
+{
+ struct rspamd_redis_pool_connection *conn;
+ struct redisAsyncContext *ctx;
+
+ ctx = redisAsyncConnect (ip, port);
+
+ if (ctx) {
+
+ if (ctx->err != REDIS_OK) {
+ redisAsyncFree (ctx);
+
+ return NULL;
+ }
+ else {
+ conn = g_slice_alloc0 (sizeof (*conn));
+ conn->entry = g_list_prepend (NULL, conn);
+ conn->elt = elt;
+ conn->active = TRUE;
+
+ g_hash_table_insert (elt->pool->elts_by_ctx, ctx, conn);
+ g_queue_push_head_link (elt->active, conn->entry);
+ conn->ctx = ctx;
+ rspamd_random_hex (conn->tag, sizeof (conn->tag));
+ REF_INIT_RETAIN (conn, rspamd_redis_pool_conn_dtor);
+ msg_debug_rpool ("created new connection to %s:%d", ip, port);
+
+ redisLibeventAttach (ctx, pool->ev_base);
+
+ if (password) {
+ redisAsyncCommand (ctx, NULL, NULL, "AUTH %s", password);
+ }
+ if (db) {
+ redisAsyncCommand (ctx, NULL, NULL, "SELECT %s", db);
+ }
+ }
+
+ return conn;
+ }
+
+ return NULL;
+}
+
+static struct rspamd_redis_pool_elt *
+rspamd_redis_pool_new_elt (struct rspamd_redis_pool *pool)
+{
+ struct rspamd_redis_pool_elt *elt;
+
+ elt = g_slice_alloc0 (sizeof (*elt));
+ elt->active = g_queue_new ();
+ elt->inactive = g_queue_new ();
+ elt->pool = pool;
+
+ return elt;
+}
+
+struct rspamd_redis_pool *
+rspamd_redis_pool_init (void)
+{
+ struct rspamd_redis_pool *pool;
+
+ pool = g_slice_alloc0 (sizeof (*pool));
+ pool->elts_by_key = g_hash_table_new_full (g_int64_hash, g_int64_equal, NULL,
+ rspamd_redis_pool_elt_dtor);
+ pool->elts_by_ctx = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ return pool;
+}
+
+void
+rspamd_redis_pool_config (struct rspamd_redis_pool *pool,
+ struct rspamd_config *cfg,
+ struct event_base *ev_base)
+{
+ g_assert (pool != NULL);
+
+ pool->ev_base = ev_base;
+ pool->cfg = cfg;
+ pool->timeout = default_timeout;
+ pool->max_conns = default_max_conns;
+}
+
+
+struct redisAsyncContext*
+rspamd_redis_pool_connect (struct rspamd_redis_pool *pool,
+ const gchar *db, const gchar *password,
+ const char *ip, int port)
+{
+ guint64 key;
+ struct rspamd_redis_pool_elt *elt;
+ GList *conn_entry;
+ struct rspamd_redis_pool_connection *conn;
+
+ g_assert (pool != NULL);
+ g_assert (pool->ev_base != NULL);
+ g_assert (ip != NULL);
+
+ key = rspamd_redis_pool_get_key (db, password, ip, port);
+ elt = g_hash_table_lookup (pool->elts_by_key, &key);
+
+ if (elt) {
+ if (g_queue_get_length (elt->inactive) > 0) {
+ conn_entry = g_queue_pop_head_link (elt->inactive);
+ conn = conn_entry->data;
+
+ if (event_get_base (&conn->timeout)) {
+ event_del (&conn->timeout);
+ }
+
+ conn->active = TRUE;
+ g_queue_push_tail_link (elt->active, conn_entry);
+ msg_debug_rpool ("reused existing connection to %s:%d", ip, port);
+
+ }
+ else {
+ /* Need to create connection */
+ conn = rspamd_redis_pool_new_connection (pool, elt,
+ db, password, ip, port);
+ }
+ }
+ else {
+ elt = rspamd_redis_pool_new_elt (pool);
+ elt->key = key;
+ g_hash_table_insert (pool->elts_by_key, &elt->key, elt);
+
+ conn = rspamd_redis_pool_new_connection (pool, elt,
+ db, password, ip, port);
+ }
+
+ REF_RETAIN (conn);
+
+ return conn->ctx;
+}
+
+
+void
+rspamd_redis_pool_release_connection (struct rspamd_redis_pool *pool,
+ struct redisAsyncContext *ctx, gboolean is_fatal)
+{
+ struct rspamd_redis_pool_connection *conn;
+
+ g_assert (pool != NULL);
+ g_assert (ctx != NULL);
+
+ conn = g_hash_table_lookup (pool->elts_by_ctx, ctx);
+ if (conn != NULL) {
+ REF_RELEASE (conn);
+
+ if (is_fatal || ctx->err == REDIS_ERR_IO || ctx->err == REDIS_ERR_EOF) {
+ /* We need to terminate connection forcefully */
+ msg_debug_rpool ("closed connection forcefully");
+ REF_RELEASE (conn);
+ }
+ else {
+ /* Just move it to the inactive queue */
+ g_queue_unlink (conn->elt->active, conn->entry);
+ g_queue_push_head_link (conn->elt->inactive, conn->entry);
+ conn->active = FALSE;
+ rspamd_redis_pool_schedule_timeout (conn);
+ msg_debug_rpool ("mark connection inactive");
+ }
+ }
+ else {
+ g_assert_not_reached ();
+ }
+}
+
+
+void
+rspamd_redis_pool_destroy (struct rspamd_redis_pool *pool)
+{
+ struct rspamd_redis_pool_elt *elt;
+ GHashTableIter it;
+ gpointer k, v;
+
+ g_assert (pool != NULL);
+
+ g_hash_table_iter_init (&it, pool->elts_by_key);
+
+ while (g_hash_table_iter_next (&it, &k, &v)) {
+ elt = v;
+ rspamd_redis_pool_elt_dtor (elt);
+ g_hash_table_iter_steal (&it);
+ }
+
+ g_hash_table_unref (pool->elts_by_ctx);
+ g_hash_table_unref (pool->elts_by_key);
+
+ g_slice_free1 (sizeof (*pool), pool);
+}
diff --git a/src/libserver/redis_pool.h b/src/libserver/redis_pool.h
new file mode 100644
index 000000000..5e5dc0b5d
--- /dev/null
+++ b/src/libserver/redis_pool.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright 2016 Vsevolod Stakhov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef SRC_LIBSERVER_REDIS_POOL_H_
+#define SRC_LIBSERVER_REDIS_POOL_H_
+
+#include "config.h"
+
+struct rspamd_redis_pool;
+struct rspamd_config;
+struct redisAsyncContext;
+struct event_base;
+
+/**
+ * Creates new redis pool
+ * @return
+ */
+struct rspamd_redis_pool *rspamd_redis_pool_init (void);
+
+/**
+ * Configure redis pool and binds it to a specific event base
+ * @param cfg
+ * @param ev_base
+ */
+void rspamd_redis_pool_config (struct rspamd_redis_pool *pool,
+ struct rspamd_config *cfg,
+ struct event_base *ev_base);
+
+
+/**
+ * Create or reuse the specific redis connection
+ * @param pool
+ * @param db
+ * @param password
+ * @param ip
+ * @param port
+ * @return
+ */
+struct redisAsyncContext* rspamd_redis_pool_connect (
+ struct rspamd_redis_pool *pool,
+ const gchar *db, const gchar *password,
+ const char *ip, int port);
+
+/**
+ * Release a connection to the pool
+ * @param pool
+ * @param ctx
+ */
+void rspamd_redis_pool_release_connection (struct rspamd_redis_pool *pool,
+ struct redisAsyncContext *ctx, gboolean is_fatal);
+
+/**
+ * Stops redis pool and destroys it
+ * @param pool
+ */
+void rspamd_redis_pool_destroy (struct rspamd_redis_pool *pool);
+
+#endif /* SRC_LIBSERVER_REDIS_POOL_H_ */
diff --git a/src/lua/lua_redis.c b/src/lua/lua_redis.c
index c35d9614b..815192d27 100644
--- a/src/lua/lua_redis.c
+++ b/src/lua/lua_redis.c
@@ -17,10 +17,8 @@
#include "dns.h"
#include "utlist.h"
-#ifdef WITH_HIREDIS
-#include "hiredis.h"
-#include "adapters/libevent.h"
-#endif
+#include "contrib/hiredis/hiredis.h"
+#include "contrib/hiredis/async.h"
#define REDIS_DEFAULT_TIMEOUT 1.0
@@ -155,6 +153,7 @@ lua_redis_dtor (struct lua_redis_ctx *ctx)
struct lua_redis_userdata *ud;
struct lua_redis_specific_userdata *cur, *tmp;
gboolean is_connected = FALSE;
+ struct redisAsyncContext *ac;
if (ctx->async) {
msg_debug ("desctructing %p", ctx);
@@ -168,7 +167,10 @@ lua_redis_dtor (struct lua_redis_ctx *ctx)
* still be alive here!
*/
ctx->ref.refcount = 100500;
- redisAsyncFree (ud->ctx);
+ ac = ud->ctx;
+ ud->ctx = NULL;
+ rspamd_redis_pool_release_connection (ud->task->cfg->redis_pool,
+ ac, FALSE);
ctx->ref.refcount = 0;
is_connected = TRUE;
}
@@ -384,8 +386,9 @@ lua_redis_callback (redisAsyncContext *c, gpointer r, gpointer priv)
ac = ud->ctx;
ud->ctx = NULL;
- if (ac != NULL) {
- redisAsyncFree (ac);
+ if (ac) {
+ rspamd_redis_pool_release_connection (ud->task->cfg->redis_pool,
+ ac, FALSE);
}
}
@@ -413,7 +416,8 @@ lua_redis_timeout (int fd, short what, gpointer u)
* This will call all callbacks pending so the entire context
* will be destructed
*/
- redisAsyncFree (ac);
+ rspamd_redis_pool_release_connection (sp_ud->c->task->cfg->redis_pool,
+ ac, TRUE);
}
REDIS_RELEASE (ctx);
}
@@ -464,22 +468,6 @@ lua_redis_parse_args (lua_State *L, gint idx, const gchar *cmd,
*nargs = top;
}
-static void
-lua_redis_connect_cb (const struct redisAsyncContext *c, int status)
-{
- /*
- * Workaround to prevent double close:
- * https://groups.google.com/forum/#!topic/redis-db/mQm46XkIPOY
- */
-#if defined(HIREDIS_MAJOR) && HIREDIS_MAJOR == 0 && HIREDIS_MINOR <= 11
- struct redisAsyncContext *nc = (struct redisAsyncContext *)c;
- if (status == REDIS_ERR) {
- nc->c.fd = -1;
- }
-#endif
-}
-
-
/***
* @function rspamd_redis.make_request({params})
@@ -662,14 +650,15 @@ lua_redis_make_request (lua_State *L)
if (ret) {
ud->terminated = 0;
ud->timeout = timeout;
- ud->ctx = redisAsyncConnect (rspamd_inet_address_to_string (addr->addr),
+ ud->ctx = rspamd_redis_pool_connect (task->cfg->redis_pool,
+ dbname, password,
+ rspamd_inet_address_to_string (addr->addr),
rspamd_inet_address_get_port (addr->addr));
if (ud->ctx == NULL || ud->ctx->err) {
if (ud->ctx) {
msg_err_task_check ("cannot connect to redis: %s",
ud->ctx->errstr);
- redisAsyncFree (ud->ctx);
ud->ctx = NULL;
}
else {
@@ -683,16 +672,6 @@ lua_redis_make_request (lua_State *L)
return 2;
}
- redisAsyncSetConnectCallback (ud->ctx, lua_redis_connect_cb);
- redisLibeventAttach (ud->ctx, ud->task->ev_base);
-
- if (password) {
- redisAsyncCommand (ud->ctx, NULL, NULL, "AUTH %s", password);
- }
- if (dbname) {
- redisAsyncCommand (ud->ctx, NULL, NULL, "SELECT %s", dbname);
- }
-
ret = redisAsyncCommandArgv (ud->ctx,
lua_redis_callback,
sp_ud,
@@ -719,7 +698,8 @@ lua_redis_make_request (lua_State *L)
}
else {
msg_info_task_check ("call to redis failed: %s", ud->ctx->errstr);
- redisAsyncFree (ud->ctx);
+ rspamd_redis_pool_release_connection (task->cfg->redis_pool,
+ ud->ctx, FALSE);
ud->ctx = NULL;
REDIS_RELEASE (ctx);
ret = FALSE;
@@ -936,7 +916,9 @@ lua_redis_connect (lua_State *L)
if (ret && ctx) {
ud->terminated = 0;
ud->timeout = timeout;
- ud->ctx = redisAsyncConnect (rspamd_inet_address_to_string (addr->addr),
+ ud->ctx = rspamd_redis_pool_connect (task->cfg->redis_pool,
+ NULL, NULL,
+ rspamd_inet_address_to_string (addr->addr),
rspamd_inet_address_get_port (addr->addr));
if (ud->ctx == NULL || ud->ctx->err) {
@@ -948,8 +930,6 @@ lua_redis_connect (lua_State *L)
return 1;
}
- redisAsyncSetConnectCallback (ud->ctx, lua_redis_connect_cb);
- redisLibeventAttach (ud->ctx, ud->task->ev_base);
pctx = lua_newuserdata (L, sizeof (ctx));
*pctx = ctx;
rspamd_lua_setclass (L, "rspamd{redis}", -1);
diff --git a/src/plugins/lua/dmarc.lua b/src/plugins/lua/dmarc.lua
index 6c34b1198..fc85c4766 100644
--- a/src/plugins/lua/dmarc.lua
+++ b/src/plugins/lua/dmarc.lua
@@ -29,11 +29,24 @@ local symbols = {
spf_softfail_symbol = 'R_SPF_SOFTFAIL',
spf_neutral_symbol = 'R_SPF_NEUTRAL',
spf_tempfail_symbol = 'R_SPF_DNSFAIL',
+ spf_na_symbol = 'R_SPF_NA',
dkim_allow_symbol = 'R_DKIM_ALLOW',
dkim_deny_symbol = 'R_DKIM_REJECT',
dkim_tempfail_symbol = 'R_DKIM_TEMPFAIL',
+ dkim_na_symbol = 'R_DKIM_NA',
}
+
+local dmarc_symbols = {
+ allow = 'DMARC_POLICY_ALLOW',
+ badpolicy = 'DMARC_BAD_POLICY',
+ dnsfail = 'DMARC_DNSFAIL',
+ na = 'DMARC_NA',
+ reject = 'DMARC_POLICY_REJECT',
+ softfail = 'DMARC_POLICY_SOFTFAIL',
+ quarantine = 'DMARC_POLICY_QUARANTINE',
+}
+
-- Default port for redis upstreams
local redis_params = nil
local dmarc_redis_key_prefix = "dmarc_"
@@ -42,13 +55,6 @@ local elts_re = rspamd_regexp.create_cached("\\s*\\\\{0,1};\\s*")
local dmarc_reporting = false
local dmarc_actions = {}
-local function maybe_force_action(disposition)
- local force_action = dmarc_actions[disposition]
- if force_action then
- task:set_pre_result(force_action, 'Action set by DMARC')
- end
-end
-
local function dmarc_report(task, spf_ok, dkim_ok, disposition)
local ip = task:get_from_ip()
if not ip:is_valid() then
@@ -62,6 +68,12 @@ local function dmarc_report(task, spf_ok, dkim_ok, disposition)
end
local function dmarc_callback(task)
+ local function maybe_force_action(disposition)
+ local force_action = dmarc_actions[disposition]
+ if force_action then
+ task:set_pre_result(force_action, 'Action set by DMARC')
+ end
+ end
local from = task:get_from(2)
local dmarc_domain
local ip_addr = task:get_ip()
@@ -73,7 +85,8 @@ local function dmarc_callback(task)
if from and from[1] and from[1]['domain'] and not from[2] then
dmarc_domain = rspamd_util.get_tld(from[1]['domain'])
else
- return
+ task:insert_result(dmarc_symbols['na'], 1.0, 'No From header')
+ return maybe_force_action('na')
end
local function dmarc_report_cb(task, err, data)
@@ -90,11 +103,11 @@ local function dmarc_callback(task)
local lookup_domain = string.sub(to_resolve, 8)
if err and err ~= 'requested record is not found' then
- task:insert_result('DMARC_DNSFAIL', 1.0, lookup_domain .. ' : ' .. err)
+ task:insert_result(dmarc_symbols['dnsfail'], 1.0, lookup_domain .. ' : ' .. err)
return maybe_force_action('dnsfail')
elseif err == 'requested record is not found' and
lookup_domain == dmarc_domain then
- task:insert_result('DMARC_NA', 1.0, lookup_domain)
+ task:insert_result(dmarc_symbols['na'], 1.0, lookup_domain)
return maybe_force_action('na')
end
@@ -109,7 +122,7 @@ local function dmarc_callback(task)
return
end
- task:insert_result('DMARC_NA', 1.0, lookup_domain)
+ task:insert_result(dmarc_symbols['na'], 1.0, lookup_domain)
return maybe_force_action('na')
end
@@ -213,14 +226,14 @@ local function dmarc_callback(task)
return
else
- task:insert_result('DMARC_NA', 1.0, lookup_domain)
+ task:insert_result(dmarc_symbols['na'], 1.0, lookup_domain)
return maybe_force_action('na')
end
end
local res = 0.5
if failed_policy then
- task:insert_result('DMARC_BAD_POLICY', res, lookup_domain .. ' : ' .. failed_policy)
+ task:insert_result(dmarc_symbols['badpolicy'], res, lookup_domain .. ' : ' .. failed_policy)
return maybe_force_action('badpolicy')
end
@@ -260,24 +273,24 @@ local function dmarc_callback(task)
local spf_tmpfail = task:get_symbol(symbols['spf_tempfail_symbol'])
local dkim_tmpfail = task:get_symbol(symbols['dkim_tempfail_symbol'])
if (spf_tmpfail or dkim_tmpfail) then
- task:insert_result('DMARC_DNSFAIL', 1.0, lookup_domain .. ' : ' .. 'SPF/DKIM temp error')
+ task:insert_result(dmarc_symbols['dnsfail'], 1.0, lookup_domain .. ' : ' .. 'SPF/DKIM temp error')
return maybe_force_action('dnsfail')
end
if quarantine_policy then
if not pct or pct == 100 or (math.random(100) <= pct) then
- task:insert_result('DMARC_POLICY_QUARANTINE', res, lookup_domain)
+ task:insert_result(dmarc_symbols['quarantine'], res, lookup_domain)
disposition = "quarantine"
end
elseif strict_policy then
if not pct or pct == 100 or (math.random(100) <= pct) then
- task:insert_result('DMARC_POLICY_REJECT', res, lookup_domain)
+ task:insert_result(dmarc_symbols['reject'], res, lookup_domain)
disposition = "reject"
end
else
- task:insert_result('DMARC_POLICY_SOFTFAIL', res, lookup_domain)
+ task:insert_result(dmarc_symbols['softfail'], res, lookup_domain)
end
else
- task:insert_result('DMARC_POLICY_ALLOW', res, lookup_domain)
+ task:insert_result(dmarc_symbols['allow'], res, lookup_domain)
end
if rua and redis_params and dmarc_reporting then
@@ -315,6 +328,14 @@ if not opts or type(opts) ~= 'table' then
return
end
+if opts['symbols'] then
+ for k,_ in pairs(dmarc_symbols) do
+ if opts['symbols'][k] then
+ dmarc_symbols[k] = opts['symbols'][k]
+ end
+ end
+end
+
if opts['reporting'] == true then
dmarc_reporting = true
end
@@ -344,12 +365,16 @@ if spf_opts then
check_mopt('spf_allow_symbol', spf_opts, 'symbol_allow')
check_mopt('spf_softfail_symbol', spf_opts, 'symbol_softfail')
check_mopt('spf_neutral_symbol', spf_opts, 'symbol_neutral')
+ check_mopt('spf_tempfail_symbol', spf_opts, 'symbol_dnsfail')
+ check_mopt('spf_na_symbol', spf_opts, 'symbol_na')
end
local dkim_opts = rspamd_config:get_all_opt('dkim')
if dkim_opts then
- check_mopt('dkim_deny_symbol', 'symbol_reject')
- check_mopt('dkim_allow_symbol', 'symbol_allow')
+ check_mopt('dkim_deny_symbol', dkim_opts, 'symbol_reject')
+ check_mopt('dkim_allow_symbol', dkim_opts, 'symbol_allow')
+ check_mopt('dkim_tempfail_symbol', dkim_opts, 'symbol_tempfail')
+ check_mopt('dkim_na_symbol', dkim_opts, 'symbol_na')
end
local id = rspamd_config:register_symbol({
@@ -358,23 +383,33 @@ local id = rspamd_config:register_symbol({
callback = dmarc_callback
})
rspamd_config:register_symbol({
- name = 'DMARC_POLICY_ALLOW',
+ name = dmarc_symbols['allow'],
flags = 'nice',
parent = id,
type = 'virtual'
})
rspamd_config:register_symbol({
- name = 'DMARC_POLICY_REJECT',
+ name = dmarc_symbols['reject'],
+ parent = id,
+ type = 'virtual'
+})
+rspamd_config:register_symbol({
+ name = dmarc_symbols['quarantine'],
+ parent = id,
+ type = 'virtual'
+})
+rspamd_config:register_symbol({
+ name = dmarc_symbols['softfail'],
parent = id,
type = 'virtual'
})
rspamd_config:register_symbol({
- name = 'DMARC_POLICY_QUARANTINE',
+ name = dmarc_symbols['dnsfail'],
parent = id,
type = 'virtual'
})
rspamd_config:register_symbol({
- name = 'DMARC_POLICY_SOFTFAIL',
+ name = dmarc_symbols['na'],
parent = id,
type = 'virtual'
})
diff --git a/src/worker.c b/src/worker.c
index 362849136..0e16922f3 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -589,6 +589,8 @@ start_worker (struct rspamd_worker *worker)
ctx->ev_base, ctx->resolver->r);
rspamd_monitored_ctx_config (worker->srv->cfg->monitored_ctx,
worker->srv->cfg, ctx->ev_base, ctx->resolver->r);
+ rspamd_redis_pool_config (worker->srv->cfg->redis_pool,
+ worker->srv->cfg, ctx->ev_base);
/* XXX: stupid default */
ctx->keys_cache = rspamd_keypair_cache_new (256);
diff --git a/test/functional/cases/150_rspamadm.robot b/test/functional/cases/150_rspamadm.robot
new file mode 100644
index 000000000..bebcb0e0e
--- /dev/null
+++ b/test/functional/cases/150_rspamadm.robot
@@ -0,0 +1,9 @@
+*** Settings ***
+Library Process
+
+*** Test Cases ***
+Config Test
+ ${result} = Run Process ${RSPAMADM} configtest
+ Should Match Regexp ${result.stderr} ^$
+ Should Match Regexp ${result.stdout} ^syntax OK$
+ Should Be Equal As Integers ${result.rc} 0