This commit is contained in:
mdecimus
2023-10-17 20:56:48 +02:00
parent d448ac6353
commit 30092ce92c
29 changed files with 1215 additions and 95 deletions

View File

@@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
## [0.x.x] - 2023-xx-xx
## [0.3.10] - 2023-10-17
## Added
- Option to allow invalid certificates on outbound SMTP connections.
@@ -13,7 +13,6 @@ All notable changes to this project will be documented in this file. This projec
### Fixed
## [0.3.9] - 2023-10-07
## Added

82
Cargo.lock generated
View File

@@ -341,9 +341,9 @@ dependencies = [
[[package]]
name = "aws-region"
version = "0.25.3"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "056557a61427d0e5ba29dd931031c8ffed4ee7a550e7cd55692a9d8deb0a9dba"
checksum = "42fed2b9fca70f2908268d057a607f2a906f47edbf856ea8587de9038d264e22"
dependencies = [
"thiserror",
]
@@ -526,9 +526,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
dependencies = [
"serde",
]
@@ -2147,7 +2147,7 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.0.0"
source = "git+https://github.com/hyperium/hyper-util#63e84bf0603df79bf86b733e7376f1317bf2eefe"
source = "git+https://github.com/hyperium/hyper-util#9a17089cb5097d3617e30019e5202f181e058eb9"
dependencies = [
"bytes",
"futures-channel",
@@ -2166,16 +2166,16 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.57"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
"windows-core",
]
[[package]]
@@ -2224,7 +2224,7 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]]
name = "imap"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"ahash 0.8.3",
"dashmap",
@@ -2408,7 +2408,7 @@ dependencies = [
[[package]]
name = "jmap"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"aes",
"aes-gcm",
@@ -2730,7 +2730,7 @@ dependencies = [
"mail-parser",
"parking_lot",
"quick-xml 0.30.0",
"ring 0.17.3",
"ring 0.17.4",
"rustls-pemfile",
"serde",
"serde_json",
@@ -2772,7 +2772,7 @@ dependencies = [
[[package]]
name = "mail-server"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"directory",
"imap",
@@ -2979,7 +2979,7 @@ dependencies = [
[[package]]
name = "nlp"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"ahash 0.8.3",
"bincode",
@@ -3137,7 +3137,7 @@ version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"cfg-if",
"foreign-types",
"libc",
@@ -3802,9 +3802,9 @@ dependencies = [
[[package]]
name = "rasn"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22b7f7ff0508dae62e1be69fe02f32eb88523090b50ac850637947853cf5b6d"
checksum = "bb030bce0cb6578f484f4b2f9f0bd038d0562e235c856730cd845947c86fbd82"
dependencies = [
"arrayvec",
"bitvec",
@@ -3824,9 +3824,9 @@ dependencies = [
[[package]]
name = "rasn-cms"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b33fab229bc35b33782880040bfc71ecad5dffb040752e7ceb20f6bddbc33c1"
checksum = "97180cf43564a824df365b53339e2dc967db608b2aa2b7a247ccb619e723f990"
dependencies = [
"rasn",
"rasn-pkix",
@@ -3834,9 +3834,9 @@ dependencies = [
[[package]]
name = "rasn-derive"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e6ddbc9ada563036d59c322cb0886a9b08b346904eebbcd20af2e01caecee7"
checksum = "e6d71113bb47670ae208b1db7308aa35c676db8bb7b7a8f49b0c1e208af5ebf4"
dependencies = [
"either",
"itertools 0.10.5",
@@ -3849,9 +3849,9 @@ dependencies = [
[[package]]
name = "rasn-pkix"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96a8f3a9c45f9e76eff1b5d72385296cbfe2c2c22ecaccb8f493f4b60be524d"
checksum = "cfdf5c1f5213088c8552ed4dc61dc07dc8211cb4aa5e7c49eb9161b32d005a55"
dependencies = [
"rasn",
]
@@ -3907,13 +3907,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.1"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.2",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
]
@@ -3928,9 +3928,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
@@ -4035,9 +4035,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.3"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
checksum = "fce3045ffa7c981a6ee93f640b538952e155f1ae3a1a02b84547fc7a56b7059a"
dependencies = [
"cc",
"getrandom 0.2.10",
@@ -4126,7 +4126,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -4213,7 +4213,7 @@ version = "0.38.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
@@ -4678,7 +4678,7 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "smtp"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"ahash 0.8.3",
"blake3",
@@ -4912,7 +4912,7 @@ checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
dependencies = [
"atoi",
"base64 0.21.4",
"bitflags 2.4.0",
"bitflags 2.4.1",
"byteorder",
"bytes",
"crc",
@@ -4954,7 +4954,7 @@ checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
dependencies = [
"atoi",
"base64 0.21.4",
"bitflags 2.4.0",
"bitflags 2.4.1",
"byteorder",
"crc",
"dotenvy",
@@ -5009,7 +5009,7 @@ dependencies = [
[[package]]
name = "stalwart-cli"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"clap",
"console",
@@ -5031,7 +5031,7 @@ dependencies = [
[[package]]
name = "stalwart-install"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"base64 0.21.4",
"clap",
@@ -5893,7 +5893,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "utils"
version = "0.3.9"
version = "0.3.10"
dependencies = [
"ahash 0.8.3",
"dashmap",
@@ -6059,7 +6059,7 @@ version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring 0.17.3",
"ring 0.17.4",
"untrusted 0.9.0",
]
@@ -6153,10 +6153,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
name = "windows-core"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
"windows-targets 0.48.5",
]

View File

@@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
license = "AGPL-3.0-only"
repository = "https://github.com/stalwartlabs/cli"
homepage = "https://github.com/stalwartlabs/cli"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
readme = "README.md"
resolver = "2"

View File

@@ -1,6 +1,6 @@
[package]
name = "imap"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
license = "AGPL-3.0-only"
repository = "https://github.com/stalwartlabs/mail-server"
homepage = "https://github.com/stalwartlabs/mail-server"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
readme = "README.md"
resolver = "2"

View File

@@ -1,6 +1,6 @@
[package]
name = "jmap"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -7,7 +7,7 @@ homepage = "https://stalw.art"
keywords = ["imap", "jmap", "smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -1,6 +1,6 @@
[package]
name = "nlp"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -7,7 +7,7 @@ homepage = "https://stalw.art/smtp"
keywords = ["smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -23,7 +23,7 @@
use std::time::Duration;
use nlp::bayes::{cache::BayesTokenCache, BayesClassifier};
use nlp::bayes::cache::BayesTokenCache;
use sieve::{compiler::grammar::Capability, Compiler, Runtime};
use crate::{
@@ -44,7 +44,6 @@ pub trait ConfigSieve {
#[derive(Default)]
pub struct SieveContext {
pub psl: PublicSuffix,
pub bayes_classify: BayesClassifier,
pub bayes_cache: BayesTokenCache,
}
@@ -54,12 +53,6 @@ impl ConfigSieve for Config {
let mut fnc_map = register_functions().register_plugins();
let sieve_ctx = SieveContext {
psl: self.parse_public_suffix()?,
bayes_classify: BayesClassifier {
min_token_hits: self.property_or_static("bayes.min-token-hits", "2")?,
min_tokens: self.property_or_static("bayes.min-tokens", "11")?,
min_prob_strength: self.property_or_static("bayes.min-prob-strength", "0.05")?,
min_learns: self.property_or_static("bayes.min-learns", "200")?,
},
bayes_cache: BayesTokenCache::new(
self.property_or_static("bayes.cache.capacity", "8192")?,
self.property_or_static("bayes.cache.ttl.positive", "1h")?,

View File

@@ -41,6 +41,10 @@ pub fn fn_is_empty<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Va
.into()
}
pub fn fn_is_number<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable {
matches!(&v[0], Variable::Integer(_) | Variable::Float(_)).into()
}
pub fn fn_is_ip_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec<Variable>) -> Variable {
v[0].to_string().parse::<std::net::IpAddr>().is_ok().into()
}

View File

@@ -47,6 +47,7 @@ pub fn register_functions() -> FunctionMap<SieveContext> {
.with_function("len", fn_len)
.with_function("count", fn_count)
.with_function("is_empty", fn_is_empty)
.with_function("is_number", fn_is_number)
.with_function("is_ascii", fn_is_ascii)
.with_function("to_lowercase", fn_to_lowercase)
.with_function("to_uppercase", fn_to_uppercase)

View File

@@ -23,7 +23,10 @@
use directory::{DatabaseColumn, Lookup};
use nlp::{
bayes::{cache::BayesTokenCache, tokenize::BayesTokenizer, BayesModel, TokenHash, Weights},
bayes::{
cache::BayesTokenCache, tokenize::BayesTokenizer, BayesClassifier, BayesModel, TokenHash,
Weights,
},
tokenizers::osb::{OsbToken, OsbTokenizer},
};
use sieve::{runtime::Variable, FunctionMap};
@@ -42,7 +45,11 @@ pub fn register_untrain(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>)
}
pub fn register_classify(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) {
fnc_map.set_external_function("bayes_classify", plugin_id, 2);
fnc_map.set_external_function("bayes_classify", plugin_id, 3);
}
pub fn register_is_balanced(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) {
fnc_map.set_external_function("bayes_is_balanced", plugin_id, 3);
}
pub fn exec_train(ctx: PluginContext<'_>) -> Variable {
@@ -150,6 +157,24 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable {
if text.is_empty() {
return Variable::default();
}
// Create classifier from defaults
let mut classifier = BayesClassifier::default();
if let Some(params) = ctx.arguments[2].as_array() {
if let Some(Variable::Integer(value)) = params.get(0) {
classifier.min_token_hits = *value as u32;
}
if let Some(Variable::Integer(value)) = params.get(1) {
classifier.min_tokens = *value as u32;
}
if let Some(Variable::Float(value)) = params.get(2) {
classifier.min_prob_strength = *value;
}
if let Some(Variable::Integer(value)) = params.get(3) {
classifier.min_learns = *value as u32;
}
}
let handle = ctx.handle;
let ctx = ctx.core.sieve.runtime.context();
@@ -160,16 +185,29 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable {
{
(weights.spam, weights.ham)
} else {
tracing::warn!(
parent: span,
context = "sieve:classify",
event = "failed",
reason = "Failed to obtain training counts",
);
return Variable::default();
};
// Make sure we have enough training data
if spam_learns < ctx.bayes_classify.min_learns || ham_learns < ctx.bayes_classify.min_learns {
if spam_learns < classifier.min_learns || ham_learns < classifier.min_learns {
tracing::debug!(
parent: span,
context = "sieve:bayes_classify",
event = "skip-classify",
reason = "Not enough training data",
spam_learns = %spam_learns,
ham_learns = %ham_learns);
return Variable::default();
}
// Classify the text
ctx.bayes_classify
classifier
.classify(
OsbTokenizer::<_, TokenHash>::new(BayesTokenizer::new(text.as_ref(), &ctx.psl), 5)
.filter_map(|t| {
@@ -188,6 +226,75 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable {
.unwrap_or_default()
}
pub fn exec_is_balanced(ctx: PluginContext<'_>) -> Variable {
let min_balance = match &ctx.arguments[2] {
Variable::Float(n) => *n,
Variable::Integer(n) => *n as f64,
_ => 0.0,
};
if min_balance == 0.0 {
return true.into();
}
let span = ctx.span;
let lookup_id = ctx.arguments[0].to_string();
let lookup_classify =
if let Some(lookup_classify) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) {
lookup_classify
} else {
tracing::warn!(
parent: span,
context = "sieve:bayes_is_balanced",
event = "failed",
reason = "Unknown lookup id",
lookup_id = %lookup_id,
);
return Variable::default();
};
let learn_spam = ctx.arguments[2].to_bool();
// Obtain training counts
let handle = ctx.handle;
let ctx = ctx.core.sieve.runtime.context();
let (spam_learns, ham_learns) = if let Some(weights) =
ctx.bayes_cache
.get_or_update(TokenHash::default(), handle, lookup_classify)
{
(weights.spam as f64, weights.ham as f64)
} else {
tracing::warn!(
parent: span,
context = "sieve:bayes_is_balanced",
event = "failed",
reason = "Failed to obtain training counts",
);
return Variable::default();
};
let result = if spam_learns > 0.0 || ham_learns > 0.0 {
if learn_spam {
(spam_learns / (ham_learns + 1.0)) <= 1.0 / min_balance
} else {
(ham_learns / (spam_learns + 1.0)) <= 1.0 / min_balance
}
} else {
true
};
tracing::debug!(
parent: span,
context = "sieve:bayes_is_balanced",
event = "result",
is_balanced = %result,
learn_spam = %learn_spam,
min_balance = %min_balance,
spam_learns = %spam_learns,
ham_learns = %ham_learns);
result.into()
}
trait LookupOrInsert {
fn get_or_update(
&self,

View File

@@ -40,6 +40,10 @@ pub fn register_map_many(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>
fnc_map.set_external_function("lookup_map_many", plugin_id, 2);
}
pub fn register_local_domain(plugin_id: u32, fnc_map: &mut FunctionMap<SieveContext>) {
fnc_map.set_external_function("is_local_domain", plugin_id, 2);
}
pub fn exec(ctx: PluginContext<'_>) -> Variable {
let lookup_id = ctx.arguments[0].to_string();
let span = ctx.span;
@@ -135,3 +139,28 @@ pub fn exec_map_many(ctx: PluginContext<'_>) -> Variable {
Variable::default()
}
}
pub fn exec_local_domain(ctx: PluginContext<'_>) -> Variable {
let directory_id = ctx.arguments[0].to_string();
let domain = ctx.arguments[0].to_string();
if !directory_id.is_empty() && !domain.is_empty() {
if let Some(dir) = ctx.core.sieve.config.directories.get(directory_id.as_ref()) {
return ctx
.handle
.block_on(dir.is_local_domain(domain.as_ref()))
.unwrap_or_default()
.into();
} else {
tracing::warn!(
parent: ctx.span,
context = "sieve:is_local_domain",
event = "failed",
reason = "Unknown directory",
lookup_id = %directory_id,
);
}
}
Variable::default()
}

View File

@@ -46,32 +46,36 @@ pub struct PluginContext<'x> {
pub arguments: Vec<Variable>,
}
const PLUGINS_EXEC: [ExecPluginFnc; 12] = [
const PLUGINS_EXEC: [ExecPluginFnc; 14] = [
query::exec,
exec::exec,
lookup::exec,
lookup::exec_map,
lookup::exec_map_many,
lookup::exec_local_domain,
dns::exec,
dns::exec_exists,
http::exec_header,
bayes::exec_train,
bayes::exec_untrain,
bayes::exec_classify,
bayes::exec_is_balanced,
pyzor::exec,
];
const PLUGINS_REGISTER: [RegisterPluginFnc; 12] = [
const PLUGINS_REGISTER: [RegisterPluginFnc; 14] = [
query::register,
exec::register,
lookup::register,
lookup::register_map,
lookup::register_map_many,
lookup::register_local_domain,
dns::register,
dns::register_exists,
http::register_header,
bayes::register_train,
bayes::register_untrain,
bayes::register_classify,
bayes::register_is_balanced,
pyzor::register,
];

View File

@@ -1,6 +1,6 @@
[package]
name = "utils"
version = "0.3.9"
version = "0.3.10"
edition = "2021"
resolver = "2"

View File

@@ -1,5 +1,12 @@
if eval "!t.SPAM_TRAP && !t.TRUSTED_REPLY" {
let "bayes_result" "bayes_classify('spamdb/token-lookup', body_and_subject)";
# Classification parameters
# min_token_hits: 2
# min_tokens: 11
# min_prob_strength: 0.05
# min_learns: 200
let "bayes_result" "bayes_classify('spamdb/token-lookup', body_and_subject, [2, 11, 0.05, 200])";
if eval "!is_empty(bayes_result)" {
if eval "bayes_result > 0.7" {
let "t.BAYES_SPAM" "1";

View File

@@ -0,0 +1,33 @@
# Whether to add an X-Spam-Status header
let "ADD_HEADER_SPAM" "true";
# Whether to add an X-Spam-Result header
let "ADD_HEADER_SPAM_RESULT" "true";
# Whether message replies from authenticated users should be learned as ham
let "AUTOLEARN_REPLIES_HAM" "true";
# Whether the bayes classifier should be trained automatically
let "AUTOLEARN_ENABLE" "true";
# When to learn ham (score >= threshold)
let "AUTOLEARN_HAM_THRESHOLD" "-0.5";
# When to learn spam (score <= threshold)
let "AUTOLEARN_SPAM_THRESHOLD" "6.0";
# Keep difference for spam/ham learns for at least this value
let "AUTOLEARN_SPAM_HAM_BALANCE" "0.9";
# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
let "SCORE_SPAM_THRESHOLD" "5.0";
# Discard messages with a score above this threshold
let "SCORE_DISCARD_THRESHOLD" "0";
# Reject messages with a score above this threshold
let "SCORE_REJECT_THRESHOLD" "0";
# Directory name to use for local domain lookups
let "DOMAIN_DIRECTORY" "'default'";

View File

@@ -0,0 +1,58 @@
# Train the bayes classifier automatically
if eval "AUTOLEARN_ENABLE && (score >= AUTOLEARN_SPAM_THRESHOLD || score <= AUTOLEARN_HAM_THRESHOLD)" {
let "is_spam" "score >= AUTOLEARN_SPAM_THRESHOLD";
eval "bayes_is_balanced('spamdb/token-lookup', is_spam, AUTOLEARN_SPAM_HAM_BALANCE) &&
bayes_train('spamdb/token-insert', body_and_subject, is_spam)";
}
add balance
# Iterate over tags
let "tags" "var_names()";
let "i" "count(tags)";
let "spam_result" "";
while "i > 0" {
let "i" "i - 1";
let "tag" "tags[i]";
let "tag_score" "map('spam/scores', tag)";
if eval "is_number(tag_score)" {
let "score" "score + tag_score";
if eval "ADD_HEADER_SPAM_RESULT" {
if eval "!is_empty(spam_result)" {
let "spam_result" "spam_result + ',\r\n\t' + tag + ' (' + tag_score + ')'";
} else {
let "spam_result" "spam_result + tag + ' (' + tag_score + ')'";
}
}
} elsif eval "tag_score == 'reject' {
let "SCORE_REJECT_THRESHOLD" "1";
let "score" "2";
break;
} else if eval "tag_score == 'discard'" {
discard;
stop;
}
}
# Process score actions
if "SCORE_REJECT_THRESHOLD && score >= SCORE_REJECT_THRESHOLD" {
reject "Your message has been rejected because it has an excessive spam score. If you feel this is an error, please contact the postmaster.";
stop;
} else if "SCORE_DISCARD_THRESHOLD && score >= SCORE_DISCARD_THRESHOLD" {
discard;
stop;
} else if "ADD_HEADER_SPAM" {
let "spam_status" "";
if eval "score >= SCORE_SPAM_THRESHOLD" {
let "spam_status" "'Yes, score=' + score";
} else {
let "spam_status" "'No, score=' + score";
}
eval "add-header('X-Spam-Status', spam_status)";
if eval "!is_empty(spam_result)" {
eval "add-header('X-Spam-Result', spam_result)";
}
}

View File

@@ -0,0 +1,9 @@
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
if eval "!lookup('spamdb/id-lookup', triplet)" {
# Greylist sender for 30 days
eval "lookup_map('spamdb/id-insert', [triplet, 2592000])";
reject "422 4.2.2 Greylisted, please try again in a few moments.";
stop;
}

View File

@@ -9,6 +9,11 @@ if eval "!header.mime-version.exists" {
let "has_text_part" "0";
let "is_encrypted" "0";
if eval "header.Content-Type.exists && !header.Content-Disposition:Content-Transfer-Encoding:MIME-Version.exists && !eq_ignore_case(header.Content-Type, 'text/plain')" {
# Only Content-Type header without other MIME headers
let "t.MIME_HEADER_CTYPE_ONLY" "1";
}
foreverypart {
let "content_type" "to_lowercase(header.content-type)";
let "type" "to_lowercase(header.content-type.type)";
@@ -138,13 +143,8 @@ foreverypart {
}
}
if eval "is_empty(type)" {
if eval "header.content-type.exists" {
let "t.BROKEN_CONTENT_TYPE" "1";
}
} elsif eval "!header.Content-Disposition:Content-Transfer-Encoding:MIME-Version.exists && (type != 'text' || subtype != 'plain')" {
# Only Content-Type header without other MIME headers
let "t.MIME_HEADER_CTYPE_ONLY" "1";
if eval "is_empty(type) && header.content-type.exists" {
let "t.BROKEN_CONTENT_TYPE" "1";
}
if eval "part_is_attachment" {

View File

@@ -38,3 +38,6 @@ let "envfrom_domain_sld" "domain_part(envfrom_domain, 'sld')";
# Obtain HELO domain SLD
let "helo_domain_sld" "domain_part(env.helo_domain, 'sld')";
# Create score variable
let "score" "0.0";

View File

@@ -171,7 +171,8 @@ while "i < domains_len" {
let "domain" "domains[i]";
let "i" "i + 1";
if eval "!contains(domain, '.') || is_ip_addr(domain)" {
# Skip invalid and local domain names
if eval "!contains(domain, '.') || is_ip_addr(domain) || is_local_domain(DOMAIN_DIRECTORY, domain_part(domain, 'sld'))" {
continue;
}
@@ -286,7 +287,8 @@ while "i < emails_len" {
let "email" "emails[i]";
let "i" "i + 1";
if eval "!contains(email, '@')" {
# Skip invalid and local e-mail addresses
if eval "!contains(email, '@') || is_local_domain(DOMAIN_DIRECTORY, domain_part(email_part(email, 'domain'), 'sld'))" {
continue;
}
@@ -319,6 +321,11 @@ while "i < urls_len" {
break;
}
# Skip URLs pointing to local domains
if eval "is_local_domain(DOMAIN_DIRECTORY, domain_part(uri_part(url, 'host'), 'sld'))" {
continue;
}
# Query SURBL HASHBL
let "result" "rsplit_once(dns_query(hash(url, 'md5') + '.hashbl.surbl.org', 'ipv4')[0], '.')";
if eval "starts_with(result[0], '127.0.')" {

View File

@@ -3,9 +3,10 @@
let "message_id" "header.Message-ID";
if eval "!is_empty(message_id)" {
eval "lookup('spamdb/id-insert', message_id)";
# Store the message ID for 30 days
eval "lookup_map('spamdb/id-insert', [message_id, 2592000])";
if eval "lookup('spam/options', 'AUTOLEARN_REPLIES')" {
if eval "AUTOLEARN_ENABLE && AUTOLEARN_REPLIES_HAM && bayes_is_balanced('spamdb/token-lookup', false, AUTOLEARN_SPAM_HAM_BALANCE)" {
eval "bayes_train('spamdb/token-insert', thread_name(header.subject) + ' ' + body.to_text, false)";
}
}

View File

View File

@@ -1,6 +1,9 @@
# Check if the message was sent to a spam trap address
if eval "lookup('spam/trap-address', envelope.to)" {
eval "bayes_train('spamdb/token-insert', body_and_subject, true)";
if eval "AUTOLEARN_ENABLE && lookup('spam/trap-address', envelope.to)" {
eval "bayes_is_balanced('spamdb/token-lookup', false, AUTOLEARN_SPAM_HAM_BALANCE) && bayes_train('spamdb/token-insert', body_and_subject, true)";
let "t.SPAM_TRAP" "1";
# Disable autolearn so the classifier is not trained twice
let "AUTOLEARN_ENABLE" "0";
}

View File

@@ -12,6 +12,12 @@ let "i" "count(urls)";
while "i > 0" {
let "i" "i - 1";
let "url" "urls[i]";
# Skip non-URLs such as 'data:' and 'mailto:'
if eval "!contains(url, '://')" {
continue;
}
let "host" "uri_part(url, 'host')";
if eval "!is_empty(host)" {
@@ -20,6 +26,10 @@ while "i > 0" {
let "host_lc" "to_lowercase(host)";
let "host_sld" "domain_part(host_lc, 'sld')";
if eval "is_local_domain(DOMAIN_DIRECTORY, host_sld)" {
continue;
}
if eval "!is_ip &&
(!t.REDIRECTOR_URL || !t.URL_REDIRECTOR_NESTED) &&
lookup('spam/redirectors', host_sld)" {

View File

@@ -0,0 +1,827 @@
envelope_from noreply@tetheer.com
envelope_to licensing@stalw.art
helo_domain yphoo.vps.wbsprt.com
iprev.result permerror
spf.result none
spf_ehlo.result none
dmarc.result none
remote_ip 195.210.29.48
expect hfilter_helo_nores_a_or_mx once_received mid_rhs_match_from r_spf_na has_data_uri arc_na subject_has_exclaim subject_ends_exclaim rdns_dnsfail mime_html_only html_short_link_img_1 to_dn_none rcpt_count_one to_match_envrcpt_all hfilter_fromhost_nores_a_or_mx rcvd_count_zero from_eq_envfrom r_dkim_na rcvd_no_tls_last from_has_dn date_in_past
From: Client Services <noreply@tetheer.com>
To: licensing@stalw.art
Subject: Tether Important Update !
Date: 16 Oct 2023 06:40:52 +0200
Message-ID: <20231016064052.403F7FEF5F005EFB@tetheer.com>
MIME-Version: 1.0
Content-Type: text/html
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE HTML>
<html><head><title></title>
<meta http-equiv=3D"X-UA-Compatible" content=3D"IE=3Dedge">
</head>
<body style=3D"margin: 0.4em;"><a title=3D"CASHBACK_REWARDS" style=3D'text-=
transform: none; text-indent: 0px; letter-spacing: normal; font-family: "Ti=
mes New Roman"; font-size: medium; font-style: normal; font-weight: 400; wo=
rd-spacing: 0px; white-space: normal; orphans: 2; widows: 2; font-variant-l=
igatures: normal; font-variant-caps: normal; -webkit-text-stroke-width: 0px=
;' href=3D"https://metaskwap.online/" target=3D"_blank" rel=3D"noopener">
<img width=3D"55%" style=3D"margin-right: auto; margin-left: auto; float: l=
eft; display: block;" alt=3D"If you can't read this message please click he=
re to open it in your browser."=20
src=3D"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABFAAAAV4CAYAAACHDynTAA=
AgAElEQVR4XuzdB2CV1fnH8SeDJOwpICCgoAgOFAQXOIqKOLFu68BWBbVarXsPnDjr+rfWqnVvF=
OteuCc4AZGN7L3Jzv953ptzc3Nzb3JvBpybfF9KgeQd5/2cN2nfX855TlqJbsKGAAIIIIAAAggg=
"></a></body></html>
<!-- NEXT TEST -->
envelope_from l.chant@tenthrevolution.com
envelope_to joe@domain.org
helo_domain eu-smtp-delivery-181.mimecast.com
iprev.result pass
spf.result pass
spf_ehlo.result pass
dkim.result pass
dkim.domains tenthrevolution.com
dmarc.result pass
remote_ip 185.58.86.181
tls.version TLSv1.3
expect from_eq_envfrom from_has_dn hfilter_helo_nores_a_or_mx forged_rcvd_trail date_in_past arc_na uri_count_odd dkim_signed has_attachment r_spf_allow rcvd_tls_last rcpt_count_one mime_good subject_ends_spaces hfilter_fromhost_nores_a_or_mx to_dn_eq_addr_all r_dkim_allow dmarc_policy_allow rcvd_count_three to_match_envrcpt_all
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tenthrevolution.com;
s=mimecast20200102; t=1669138703;
h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
to:to:cc:mime-version:mime-version:content-type:content-type;
bh=OiZa5h2Agb47dTPnaYIMS7L31ZUnIkX1UTz8yUhK3ME=;
b=Z0erZ14U5BYcY0CGysOw3K0A7wjF9qqRlOaI4+0XGUmM5QgmgN6UVJc6J5AkypPgwEfOWx
vsCbMrq14SF61IevT2cPrOwaphTL7s3Yf9YqKkk4N9bMiBVeikq1ks0kxJ8pbE8vYsiASn
GEkv9T3YWfRMQR/iH+oD1dVRnCljTxQ=
Received: from EUR01-DB5-obe.outbound.protection.outlook.com
(mail-db5eur01lp2053.outbound.protection.outlook.com [104.47.2.53]) by
relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
uk-mta-233-uW3qPnzqMdi1lL-mEjQyCA-4; Tue, 22 Nov 2022 17:38:17 +0000
X-MC-Unique: uW3qPnzqMdi1lL-mEjQyCA-4
Received: from AS8PR04MB8071.eurprd04.prod.outlook.com (2603:10a6:20b:3f9::15)
by DU2PR04MB8952.eurprd04.prod.outlook.com (2603:10a6:10:2e3::24) with
Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5834.9; Tue, 22 Nov
2022 17:37:46 +0000
Received: from AS8PR04MB8071.eurprd04.prod.outlook.com
([fe80::eb0e:b5f3:368c:ff6e]) by AS8PR04MB8071.eurprd04.prod.outlook.com
([fe80::eb0e:b5f3:368c:ff6e%5]) with mapi id 15.20.5834.015; Tue, 22 Nov 2022
17:37:46 +0000
From: Lee Chant <l.chant@tenthrevolution.com>
To: "joe@domain.org" <joe@domain.org>
Subject: =?Windows-1252?Q?You=92re_missing_out_=96_nominate_yourself_or_your_team_?=
=?Windows-1252?Q?for_a_Digital_Revolution_Award_?=
Thread-Topic: =?Windows-1252?Q?You=92re_missing_out_=96_nominate_yourself_or_your_team_?=
=?Windows-1252?Q?for_a_Digital_Revolution_Award_?=
Thread-Index: Adj+l09eH9aJFGDfS8upOMyJ89uCyw==
Date: Tue, 22 Nov 2022 17:37:46 +0000
Message-ID: <AS8PR04MB8071BB5854E10B6EA7161159EF0D9@AS8PR04MB8071.eurprd04.prod.outlook.com>
Accept-Language: en-GB, en-US
X-MS-Has-Attach: yes
X-MS-TNEF-Correlator:
x-ms-publictraffictype: Email
x-ms-traffictypediagnostic: AS8PR04MB8071:EE_|DU2PR04MB8952:EE_
x-ms-office365-filtering-correlation-id: 320b349d-2736-406a-45c9-08daccb044b5
x-ms-exchange-senderadcheck: 1
x-ms-exchange-antispam-relay: 0
x-microsoft-antispam: BCL:0
x-microsoft-antispam-message-info: RFrNGr7IhMmS4iNfprhPqmj60ReOFkq3iNebFuTE+OovNlEuRaCUYK1lMSdb44RdkJKhfVU53K+aSMHjZ0V9Xdnq9igSL5NKbMPTOCvraH5VB+OYVuXbBG/+Ex2FCJ1HuHYab6Rwjy3ln/pisH9uDVF/Wpn1w6MFZcyTcMXl+5RQ79GOb12a5ybKmmvtxVTjOq53mA10D7qRiWXsxOPTYf7vjmuV6X+J+IF0lQ35AiAJjGAgJrYtu8datK9t3d/k1aIWms2B1tzpL76de0mre2dGVgQIfzu4hhfU99Hejj6opP+ISn2yDsEDRZKPC1FSbggeOMATGLdi7uMSLW3sIMYRm8M6kZ436e9vY718GaCGtYS9cdEaXKjQUeJg/M0MVYDtBdsgxpgOjusyuPj5Wf/KACGdvuNui+SeS+LCjCGrNhe4+uy1cx1XioD9YhNmcrJOHUBmqkmshClu+TdMRqCORGkVGNoRN97KFg0RlX/aYr6erDFDyphdZl65map16nZBjFw+h4JuPI0ZsBd4lWe0ep7a17rcT+xUESmo1/2chtCC6viPwDyGzqelSeZhxRyHa2Tu/VzstxElXSqgs9S8yTsaQ+nsjsRRAnfs5bKbjYzKX2QpXtAEEdQo5R8UnexDRLiB4MupsWkRM24dvSRkyc0Pt2eKO7vVLMSW/RUUxbSNLzTjh2MYOfPCl9WGXKXbJvn4+gZx4iLFZnbQxTxw4Y2lNX/EPDtn80ZpGz5Kab2p36tx/8KAOIYOw3zo5EnTHeHQBjUSmr6qS2cT2GZVyd89bzEZvOSsgsFqT1U=
x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AS8PR04MB8071.eurprd04.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230022)(39860400002)(366004)(136003)(396003)(346002)(376002)(451199015)(45080400002)(38100700002)(478600001)(33656002)(41300700001)(76116006)(66946007)(64756008)(66556008)(66446008)(66476007)(166002)(316002)(186003)(38070700005)(55016003)(83380400001)(4743002)(26005)(6506007)(7696005)(86362001)(9686003)(71200400001)(55236004)(99936003)(122000001)(6916009)(66899015)(2906002)(8936002)(5660300002)(52536014)(36394005);DIR:OUT;SFP:1101
x-ms-exchange-antispam-messagedata-chunkcount: 1
x-ms-exchange-antispam-messagedata-0: =?Windows-1252?Q?4P/FtE9B736TzSCbgF3J1iOGsQRezh6u4SgvpddFWvWNSVWTLtRmRvpF?=
=?Windows-1252?Q?2jqjFWq7ft5r7M4S0JynXShWhP/1VAPc6lZMAfiyyfoQknuWz0rPQEid?=
=?Windows-1252?Q?ay8qW5pzl7Dt3M/2MonJpXaeupRlkkhjREQspgY3DpMRnd8fCMq4O2we?=
=?Windows-1252?Q?sd+rG6VpQ2irOU45M5WrVBGskNcX8C2gJnZzv2IF4BMaYCO9DI9xM6io?=
=?Windows-1252?Q?BKvAgIM3b4Jm3WqwPMmT5bWEnJgRv4OI92cE4eZSzkRpI4u2DlxF4qx5?=
=?Windows-1252?Q?QNX2XKfGHH002dn2lUpYXzMPF4f5jGaX401MDw1iGQWf9M2ZAOVhZoaP?=
=?Windows-1252?Q?Y1BKjdodCrMTCvb1+0fLXxLHfAs4ku2e1gDUez44llQ2POHI6+JtsObx?=
=?Windows-1252?Q?avS9SGfhtFkXDm6x/PLQAuuNhskxxSzuuKbrDQYWbP+LnhbsVVXsQhER?=
=?Windows-1252?Q?qJgff0GlO8M3ICrjY3+moEVqND9Dgmj/opQvPX/J2OW/ULmGddsmUQIi?=
=?Windows-1252?Q?XnwSNTZjtCf13KsGuXu2IIzCZ0vnE6RLJu9vjFJ8zx132jlBmYRfLkBy?=
=?Windows-1252?Q?qNtny11BJny8+bVuodhuohKAYQY3HCGmtE/5GLBs1w3KbkA0rFWMSdg5?=
=?Windows-1252?Q?0gb3egkCIkDN6fHHxo6vlo3Gay63TEGdiIoueTyLPJiMAs6DswGBhRB1?=
=?Windows-1252?Q?B4c34FKxKQXIbCRrVHJME6wZMIfqgjhU6nfC39qMuqn4E/NCfLjoVUTm?=
=?Windows-1252?Q?5IjQS3n+9ObjGWXNHu5nhgUJCGhgeKOcDxAHNL5bRQmruzUm9a5DedWI?=
=?Windows-1252?Q?vlSEzriwrCtwOcwU6ycP0t9XzNTB+TjOv6DPGtSMzlaxRl2mmCgl0sqK?=
=?Windows-1252?Q?1h5vOiAbw6hs/ENFK2n6pSNmFEDyDrM1DZ+eXTqKQixnSvbRLJfsxyyh?=
=?Windows-1252?Q?RARROgIC3Q3grmk8keC1VN5IOjgpXF3tzUo9lKoC/PB0DRJp444jjJYK?=
=?Windows-1252?Q?QpcGxSGUsAvbEK4JkfjFjzQucbW6kewoDp/vwl00JobRePzZymmgnwO3?=
=?Windows-1252?Q?/sRlewjFoih9gkx2G3BD6RBp/UT0rSKlSFdLdOgsXwBea2HtjJYCQbcc?=
=?Windows-1252?Q?qT/zqI8dMeiw2u+BIEcyttiiE95FfgCZ+Jw1eXXSPrKgbheuM3siCQ4l?=
=?Windows-1252?Q?3idTUoQRfVOSGhdi7s9UTyioCQj9cQ6rRNEGoZsBb9rQdEj+IrOirbP5?=
=?Windows-1252?Q?oUpMesdPuX1nS61a63hoYmZNO1o73kgeNrjnQKf4QXycHbucQOmDVrlv?=
=?Windows-1252?Q?4oN+RZRmZ9IQ2cMKxNkwZgJqmPG5SAf31ZF5of8OrfYooS2jwz0Fzerq?=
=?Windows-1252?Q?hLxLTtNQp6FtoVMA7uv7zpP6Nb5HYiCu5qY/kfqhS61Cqo5ulicay35G?=
=?Windows-1252?Q?iAyY5WLHp7+ybc2Zn6eHXZrNzZ5S4E6rCiTcqE3NumT4Ax7vH/fDhT3f?=
=?Windows-1252?Q?ShvqDg98354C9ZEtoV/t1x5vNporSrn/Sbb/FMjXSXOgGE3kxpPV65RB?=
=?Windows-1252?Q?29Fl9M+Z8JmBacbtpZoGnqQ5nKttFjnXtj899djqwoYVuN48/hO4TgnU?=
=?Windows-1252?Q?KqteFtHKVcQDNpdL8vdgo7uFVb9HFQvOU0ECxgTg0F8CpAchtGM3siVN?=
=?Windows-1252?Q?M964A6hx5NPb8YH2C3Pq7IvbCiPf41CC7MSaAMLb04j1VXOKFdTzTQ?=
=?Windows-1252?Q?=3D=3D?=
MIME-Version: 1.0
X-OriginatorOrg: tenthrevolution.com
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-AuthSource: AS8PR04MB8071.eurprd04.prod.outlook.com
X-MS-Exchange-CrossTenant-Network-Message-Id: 320b349d-2736-406a-45c9-08daccb044b5
X-MS-Exchange-CrossTenant-originalarrivaltime: 22 Nov 2022 17:37:46.5232
(UTC)
X-MS-Exchange-CrossTenant-fromentityheader: Hosted
X-MS-Exchange-CrossTenant-id: 9575ad69-b859-42ee-bf2c-7263b3236a16
X-MS-Exchange-CrossTenant-mailboxtype: HOSTED
X-MS-Exchange-CrossTenant-userprincipalname: uaZau1MOBnlwTPYfr6/hV4Tn/rVEIPMPe31SJJq5jXeno2HbSjV5ElWexVy8mvfaALxr+kV60KBJgJFqS4ftLeUsiciMybCqKPuVBsmtEFg=
X-MS-Exchange-Transport-CrossTenantHeadersStamped: DU2PR04MB8952
X-Mimecast-Spam-Score: 0
X-Mimecast-Originator: tenthrevolution.com
Content-Language: en-US
Content-Type: multipart/related;
boundary="_004_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_";
type="multipart/alternative"
--_004_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_
Content-Type: multipart/alternative;
boundary="_000_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_"
--_000_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_
Content-Type: text/plain; charset=WINDOWS-1252
Content-Transfer-Encoding: quoted-printable
Hi Joe
My name is Lee and I=92m the Managing Director of Global Customer Solutions=
at Tenth Revolution Group<https://www.tenthrevolution.com/>.
I wanted to drop you a note to personally let you know that we have extende=
d the nomination deadline for the Digital Revolution Awards & Fundraiser<ht=
tps://www.digitalrevolutionawards.com/> until 30th November 2022.
These not-for-profit awards celebrate excellence in the cloud and bring tog=
ether the tech community for a night of celebration and fundraising for som=
e truly worthy causes. Founded back in 2020, we have garnered support from =
IBM, AWS, Salesforce and Microsoft and it=92s a perfect opportunity to netw=
ork with likeminded individuals like yourself.
Our 20 specialist categories<https://www.digitalrevolutionawards.com/award-=
categories/> cover topics from tech for good to the climate emergency, ED&I=
, allyship, outstanding leadership to name but a few. If you or your team w=
ould be interested in discussing a nomination for the Digital Revolution Aw=
ards, do let me know.
With kind regards,
Lee
Lee Chant
MD - Global Customer Solutions
Mobile: +44 (0)7971 373432
Email: l.chant@tenthrevolution.com<mailto:l.chant@tenthrevolution.com>
Website: www.tenthrevolution.com<https://www.tenthrevolution.com>
[Tenth Revolution Group]
Disclaimer
This email and any attachments are confidential and intended for the use of=
the named recipient only. If you have received this email and any attachme=
nts in error, please inform us immediately and then delete it. Any views or=
opinions are solely those of the author and do not necessarily represent t=
hose of Frank Recruitment Group Services Limited or its affiliates, divisio=
ns or brands. Company Registration No. 08142375.
Registered Office: Floor 2, The St. Nicholas Building, St. Nicholas Street,=
Newcastle Upon Tyne, Tyne and Wear, NE1 1RF.
Business registration information of Frank Recruitment Group Services Ltd c=
ompanies and associated brands in the UK, Europe, Singapore, Australia, Jap=
an and North America can be found here.
<https://www.frankgroup.com/group-company-details>Our Privacy Notice can be=
found at www.tenthrevolution.com/privacy-notice
<https://www.tenthrevolution.com/privacy-notice/>
--_000_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_
Content-Type: text/html; charset=WINDOWS-1252
Content-Transfer-Encoding: quoted-printable
<html xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-micr=
osoft-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:office:word" =
xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml" xmlns=3D"http:=
//www.w3.org/TR/REC-html40">
<head>
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3DWindows-1=
252">
<meta name=3D"Generator" content=3D"Microsoft Word 15 (filtered medium)">
<!--[if !mso]><style>v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
</style><![endif]--><style><!--
/* Font Definitions */
@font-face
=09{font-family:PMingLiU;
=09panose-1:2 1 6 1 0 1 1 1 1 1;}
@font-face
=09{font-family:"Cambria Math";
=09panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
=09{font-family:Calibri;
=09panose-1:2 15 5 2 2 2 4 3 2 4;}
@font-face
=09{font-family:"\@PMingLiU";
=09panose-1:2 1 6 1 0 1 1 1 1 1;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
=09{margin-top:0cm;
=09margin-right:0cm;
=09margin-bottom:8.0pt;
=09margin-left:0cm;
=09line-height:107%;
=09font-size:11.0pt;
=09font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
=09{mso-style-priority:99;
=09color:#0563C1;
=09text-decoration:underline;}
.MsoChpDefault
=09{mso-style-type:export-only;
=09font-family:"Calibri",sans-serif;}
.MsoPapDefault
=09{mso-style-type:export-only;
=09margin-bottom:8.0pt;
=09line-height:107%;}
/* Page Definitions */
@page
=09{mso-endnote-separator:url("cid:header.htm\@01D8FE97.4F85B870") es;
=09mso-endnote-continuation-separator:url("cid:header.htm\@01D8FE97.4F85B87=
0") ecs;
=09mso-endnote-continuation-notice:url("cid:header.htm\@01D8FE97.4F85B870")=
ecn;}
@page WordSection1
=09{size:595.3pt 841.9pt;
=09margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
=09{page:WordSection1;}
/* List Definitions */
ol
=09{margin-bottom:0cm;}
ul
=09{margin-bottom:0cm;}
--></style><!--[if gte mso 9]><xml>
<o:shapedefaults v:ext=3D"edit" spidmax=3D"2050" />
</xml><![endif]--><!--[if gte mso 9]><xml>
<o:shapelayout v:ext=3D"edit">
<o:idmap v:ext=3D"edit" data=3D"2" />
</o:shapelayout></xml><![endif]-->
</head>
<body lang=3D"EN-GB" link=3D"#0563C1" vlink=3D"#954F72" style=3D"word-wrap:=
break-word">
<div class=3D"WordSection1">
<p class=3D"MsoNormal">Hi Joe </p>
<p class=3D"MsoNormal">My name is Lee and I=92m the Managing Director of Gl=
obal Customer Solutions at
<a href=3D"https://www.tenthrevolution.com/">Tenth Revolution Group</a>. <s=
pan lang=3D"EN-US">
<o:p></o:p></span></p>
<p class=3D"MsoNormal">I wanted to drop you a note to personally let you kn=
ow that we have extended the nomination deadline for the
<a href=3D"https://www.digitalrevolutionawards.com/">Digital Revolution Awa=
rds &amp; Fundraiser</a> until 30<sup>th</sup> November 2022.
<span lang=3D"EN-US">&nbsp;<o:p></o:p></span></p>
<p class=3D"MsoNormal">These not-for-profit awards celebrate excellence in =
the cloud and bring together the tech community for a night of celebration =
and fundraising for some truly worthy causes. Founded back in 2020, we have=
garnered support from IBM, AWS, Salesforce
and Microsoft and it=92s a perfect opportunity to network with likeminded =
individuals like yourself.
</p>
<p class=3D"MsoNormal">Our 20 specialist <a href=3D"https://www.digitalrevo=
lutionawards.com/award-categories/">
categories</a> cover topics from tech for good to the climate emergency, ED=
&amp;I, allyship, outstanding leadership to name but a few. If you or your =
team would be interested in discussing a nomination for the Digital Revolut=
ion Awards, do let me know.
</p>
<p class=3D"MsoNormal">With kind regards, </p>
<p class=3D"MsoNormal">Lee </p>
<p><strong><span style=3D"font-size:10.0pt;font-family:&quot;Calibri&quot;,=
sans-serif;color:black">Lee Chant</span></strong><br>
<span style=3D"font-size:10.0pt;color:#212121">MD - Global Customer Solutio=
ns</span></p>
<p><span lang=3D"FR" style=3D"font-size:10.0pt;color:#595959">Mobile: +44 (=
0)7971 373432<br>
Email: </span><a href=3D"mailto:l.chant@tenthrevolution.com"><span lang=3D"=
FR" style=3D"font-size:10.0pt">l.chant@tenthrevolution.com</span></a><span =
lang=3D"FR" style=3D"font-size:10.0pt;color:#595959"><br>
Website: </span><a href=3D"https://www.tenthrevolution.com"><span lang=3D"F=
R" style=3D"font-size:10.0pt">www.tenthrevolution.com</span></a><span lang=
=3D"FR"><o:p></o:p></span></p>
<p><span style=3D"color:#595959"><img border=3D"0" width=3D"450" height=3D"=
101" id=3D"Picture_x0020_1" src=3D"cid:image001.jpg@01D8FE97.4F85B870" alt=
=3D"Tenth Revolution Group"></span></p>
<p><strong><span style=3D"font-size:8.0pt;font-family:&quot;Calibri&quot;,s=
ans-serif;color:#595959">Disclaimer</span></strong><b><span style=3D"font-s=
ize:8.0pt;color:#595959"><br>
</span></b><span style=3D"font-size:8.0pt;color:#595959">This email and any=
attachments are confidential and intended for the use of the named recipie=
nt only. If you have received this email and any attachments in error, plea=
se inform us immediately and then
delete it. Any views or opinions are solely those of the author and do not=
necessarily represent those of Frank Recruitment Group Services Limited or=
its affiliates, divisions or brands. Company Registration No. 08142375.
<br>
Registered Office: Floor 2, The St. Nicholas Building, St. Nicholas Street,=
Newcastle Upon Tyne, Tyne and Wear, NE1 1RF.<br>
Business registration information of Frank Recruitment Group Services Ltd c=
ompanies and associated brands in the UK, Europe, Singapore, Australia, Jap=
an and North America can be found&nbsp;</span><a href=3D"https://www.frankg=
roup.com/group-company-details"><span style=3D"font-size:8.0pt">here.</span=
><span style=3D"font-size:8.0pt;color:#0563C1"><br>
</span></a><span style=3D"font-size:8.0pt;color:#595959">Our Privacy Notice=
can be found at
</span><a href=3D"https://www.tenthrevolution.com/privacy-notice/"><span st=
yle=3D"font-size:8.0pt">www.tenthrevolution.com/privacy-notice</span><span =
style=3D"font-size:8.0pt;color:#0563C1"><br>
</span></a><span class=3D"MsoHyperlink"><o:p></o:p></span></p>
<p class=3D"MsoNormal"><o:p>&nbsp;</o:p></p>
<p class=3D"MsoNormal"><o:p>&nbsp;</o:p></p>
<p class=3D"MsoNormal"><o:p>&nbsp;</o:p></p>
<p class=3D"MsoNormal"><o:p>&nbsp;</o:p></p>
<p class=3D"MsoNormal"><o:p>&nbsp;</o:p></p>
</div>
</body>
</html>
--_000_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_--
--_004_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_
Content-Type: image/jpeg; name="image001.jpg"
Content-Description: image001.jpg
Content-Disposition: inline; filename="image001.jpg"; size=13809;
creation-date="Tue, 22 Nov 2022 17:37:45 GMT";
modification-date="Tue, 22 Nov 2022 17:37:46 GMT"
Content-ID: <image001.jpg@01D8FE97.4F85B870>
Content-Transfer-Encoding: base64
/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+EDLGh0dHA6Ly9ucy5hZG9i
ZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6
TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0i
QWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0MzUyLCAyMDIwLzAxLzMwLTE1OjUwOjM4ICAg
ICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjIt
cmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBN
TT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9u
cy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDov
L25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4ODIyQUZF
QTc3RkIxMUVBOUQ1REY1N0FCOTE5REQwQiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4ODIy
QUZFOTc3RkIxMUVBOUQ1REY1N0FCOTE5REQwQiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90
b3Nob3AgMjEuMCAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJ
RD0ieG1wLmlpZDozRDE0N0RCRDMzOEExMUVBQTVDMEQyQjIxRUU5OTM5QyIgc3RSZWY6ZG9jdW1l
bnRJRD0ieG1wLmRpZDozRDE0N0RCRTMzOEExMUVBQTVDMEQyQjIxRUU5OTM5QyIvPiA8L3JkZjpE
ZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/u
AA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwMDAwMEAwO
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
CgFAKAUAoBQCgFAKA//Z
--_004_AS8PR04MB8071BB5854E10B6EA7161159EF0D9AS8PR04MB8071eurp_--
<!-- NEXT TEST -->
envelope_from noreply@grupokonecta.net
envelope_to joe@domain.org
helo_domain rheology.yeloweditions.com
iprev.result fail
dkim.result pass
dkim.domains rheology.yeloweditions.com
spf.result softfail
spf_ehlo.result softfail
dmarc.result fail
dmarc.policy reject
remote_ip 51.89.165.39
tls.version TLS1_2
expect has_replyto once_received r_parts_differ mid_rhs_match_from hfilter_fromhost_nores_a_or_mx from_has_dn r_dkim_allow date_in_past to_match_envrcpt_all html_short_link_img_1 rcpt_count_one arc_na hfilter_helo_nores_a_or_mx r_spf_softfail rcvd_tls_last rcvd_count_zero replyto_dom_eq_from_dom to_dn_none has_list_unsub dkim_signed rdns_none from_eq_envfrom dmarc_policy_reject
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=sectionalism; d=grupokonecta.net;
h=To:Subject:Message-ID:Date:From:Reply-To:MIME-Version:List-Unsubscribe:
Content-Type:Content-Transfer-Encoding; i=noreply@grupokonecta.net;
bh=dP5ixF9HZsWlj/p3qHOLHJE+ZBdEajVqh0aowOaVxc4=;
b=nxR6ACs3hInMKEyEK9yQIEgu4RuffMNmnA6a2mC6Z1c9Qbao8AsmuWct1i7bvn5lU1mmDSXx6PwO
/w2Z6TSZIW1jZkhQwDo5sJWO/0f26zabNJ3bAsZnN/Yyy0Lf4oUJuaZ97cwHiMPlDIPF++gyuY6s
30mpjo29dX3ZCSCkMkcmox55AsQSK2nJ2ZIv8dqep02kiXjldaiW5hIRzHwjeh+REy3Mb5zCuIVJ
4dcKomEGK3JsnnY1mdMVHB1ghlVMnms+HSu2AYi7186j0QSdobMxNuBzAKqzDRfunSljG1IL1im8
bnZoghgIpHHfEKY5+b5hStqgBQoPTh2CJMAGWNDkdvoFK3m606RRRiKIOP69M4v5a1UjBBoyP7QO
AUNbgcjHWQCl83a1ofW2LKSDPcyfj2TVK4yjQlPAH3Q0TJgeig1beio7Ahnz581H22kkyxPtMB56
YVA2cG7FvBB40HLmuX1YKu4TAwJ8ZJdCOnt2xZ9lYLb8LuduCFrd6RZH/44lUu/DpnM1lN3Q3oXe
moOQCtyUsI77tu7rzy0vgzZME4v0j3S57CYHJktZGvk/3uFHTWhStYlWNk+Ks/rCfkQjSmQV1u+T
ozHF7td0rxpD0b8534vjwABr/NwpjlSLIgBvt9d0nBtipYYOumSdXpCj9mB6uZbGVq9K0mdusbY=
DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; s=sectionalism;
d=rheology.yeloweditions.com;
h=To:Subject:Message-ID:Date:From:Reply-To:MIME-Version:List-Unsubscribe:
Content-Type:Content-Transfer-Encoding;
bh=BwKu50FuSdWv9iToeKQHRPlPqN0=;
b=UT4zz+mBbtBTs9S/YEiEH7B3N9gklIJBZLKAyaCOvddh0wWb33fT3qyqCKo9Vfyhp+gBHlQ/pfvZ
xLRSO8SjfAOLs/0Xs0sy5uVLuNXh5UfdT+IwG5KSgA+NpRvOTmHvfUzFP5DoiykHP0eutX2TxkRh
dJHjLIthv+EISFEubGu1ZgFUBReFTCvhZCrYPYJlxze/tmWWChRK7r804EBR3gJiLEvgXQho5Qh5
ksdnvvqAAVTHNOWAFbxoojBmNCs/gn0BbXHeJGuR/ZRXUbC1ZzQ4hw/9xNSCHI9n24IoD+vfhldb
k2hSVGQisRxn9vTISgDaNjEgFdr+0hUTqSTvrVEDdJYjCmYK/HrJrb8oeh+G/mz30pYQJlJUYIuJ
VcGkqXdFQdenNJPPD1UccGCpWblaaSlqY2AVk3mjX08Du2jbuzEFnX2g5UA5okGOmUi3h5C1v3vG
zLMuOT3686ZovcbxlW+K1GkOL1IZTkgXiw2X4EDZ6nE191uyVpIi3NTBkRIIWsni2lpDfg9+qq/J
/viVYHHmyG7xpB+gmS9r46HZqZYcYA9g5NQuTaYPIzngwlODdoSVsDb9X7/aU2Mhvp+ayq5g5/fa
4cbe7mAJMaCifxx5YA2KqkYLqRS3Cm0D2AJGqk9DpkpncHL+g8+jOAtoWCPFa5L+yVnQIexiJqE=
DomainKey-Signature: a=rsa-sha1; c=nofws; q=dns; s=sectionalism; d=grupokonecta.net;
b=k7FzASi0Vd8+owLAAzg7gG2fHkmp7q+kS7fNXMORbpbqrny/kgSjEkfLUDJdmPFyY2PlDQ+/dZcs
W6kMg8n/+wKKEMenIz1O0RSCbKoV5N2gchXV1nEevqyZ+Ndf4XwLhHY+Ttlh6dnlMNblWnXZcxjs
2bvwWSAVakpbSIbRkYeMHJOfQTw7efqEEhKRqXCWHnHJuC9zkf4C3bUe6WcTjBmlCe12dU+HD18K
lkc7TJKwIm81vBgE+HXpVC3YuJsfsNLM4bb/bZmt3yu5SgfEWss6DgOsOa2yNzLWMx6byVhx3zvg
u9k+c+seOdotD0Rphz6F2GaW3LJwOz+Uuhntwir3FZ45GEeyn2V0jiXwVpg8L2RREs3bqCUm+THD
8p0gSDNbB3SExCuvRmRnvPCogYvO9XGbwX6eh4gl9dGMrmVSxpjnIoJjoAF/AZSPV2FGBhwFWzjr
P+lD8QOzpAbKXSArKcDmJU6ehafQW1fknB1colA97NVan2HCj6B1YdaKv7BtPPjh+L1hwVd3mFaD
Kp4/hGJnW/UE12ZUaLIFVsioZWf6TcNvFaZQzTlL8U5fShX8iQInbAWh9+c0YGGLzcGcjqUV3BCA
ZXLFzE9yrfkpMDPInBqLOXbIiWzIUopFoCPHuEyEDpalYQQx3rXbUKsNnGs8xEipZueJWmdhaUc=;
To: joe@domain.org
Subject: =?UTF-8?B?UGVkw60gb25saW5lIHR1IFRhcmpldGEgQkJWQSAxMDAlIEJPTklGSUNBREEu?=
Message-ID: <61805635a4a8f8915503cb518cbbaeecc82ce04e861805635@grupokonecta.net>
Return-Path: noreply@grupokonecta.net
Date: Tue, 10 Oct 2023 11:52:03 +0000
From: "BBVA" <noreply@grupokonecta.net>
Reply-To: noreply@grupokonecta.net
MIME-Version: 1.0
List-Unsubscribe: <https://notifications.google.com/grVIU61805635/5SRhf6e497d2459dceb342666f9b49a11b7b_WbfS366/FtbW17880.html>,<mailto:abuse@net?subject=NjE4MDU2MzU=>
Content-Type: multipart/alternative; charset="UTF-8"; boundary="b1_3d217f30a568faa9ce3dd7dc73399561"
Content-Transfer-Encoding: quoted-printable
--b1_3d217f30a568faa9ce3dd7dc73399561
Content-Type: text/plain; format=flowed; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
Tarjeta de cr=C3=A9dito BBVA
La tarjeta de cr=C3=A9dito para viajar con tus consumos
Pedila 100% online y empez=C3=A1 a disfrutar
Conocer oferta
Un mundo de beneficios con las tarjetas de cr=C3=A9dito BBVA
compras en cuotas
Compras en cuotas
Pod=C3=A9s disfrutar hoy de los productos =E2=80=A8que quer=C3=A9s y pagarl=
os en cuotas
descuentos y reintegros
Descuentos y reintegros
Entretenimiento, gastronom=C3=ADa, farmacia, ropa =E2=80=A8y m=C3=A1s rubro=
s con promociones exclusivas
puntos bbva
Viajes con Puntos BBVA
Vuelos, alojamientos y mucho m=C3=A1s canjeando Puntos BBVA que sum=C3=
=A1s con tus compras
Conocer oferta
Descubr=C3=AD la tarjeta que mejor se adapta a vos
Todas las tarjetas
Black
Platinum
Gold
Internacional
Todas las tarjetas
visa black
Tarjeta Visa Signature
L=C3=ADmites desde $600.000
15% extra en acumulaci=C3=B3n de Puntos BBVA
Acceso a salas VIP en aeropuertos
Asistencia en viajes con cobertura de hasta 250.000 USD
Extracci=C3=B3n de efectivo en el exterior
Seguro de robo en cajero y compra protegida
Atenci=C3=B3n personalizada para resolver tus consultas
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $200.000
Conocer m=C3=A1s
mastercard black
Tarjeta Mastercard Black
L=C3=ADmites desde $600.000
15% extra en acumulaci=C3=B3n de Puntos BBVA
Acceso a salas VIP en aeropuertos
Asistencia en viajes con cobertura de hasta 250.000 USD
Extracci=C3=B3n de efectivo en el exterior
Seguro de robo en cajero y compra protegida
Atenci=C3=B3n personalizada para resolver tus consultas
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $200.000
Conocer m=C3=A1s
tarjeta platinum visa
Tarjeta Visa Platinum
L=C3=ADmites desde $350.000
5% extra en acumulaci=C3=B3n de Puntos BBVA
Asistencia en viajes con cobertura de hasta 170.000 USD
Extracci=C3=B3n de efectivo en el exterior
Atenci=C3=B3n personalizada para resolver tus consultas
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $120.000
Conocer m=C3=A1s
tarjeta platinum mastercard
Tarjeta Mastercard Platinum
L=C3=ADmites desde $350.000
5% extra en acumulaci=C3=B3n de Puntos BBVA
Asistencia en viajes con cobertura de hasta 50.000 USD y 30.000 EUR
Extracci=C3=B3n de efectivo en el exterior
Atenci=C3=B3n personalizada para resolver tus consultas
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $120.000
Conocer m=C3=A1s
tarjeta gold visa
Tarjeta Visa Gold
L=C3=ADmites desde $100.000
Puntos BBVA para viajar
Extracci=C3=B3n de efectivo en el exterior
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $20.000
Conocer m=C3=A1s
tarjeta gold mastercard
Tarjeta Mastercard Gold
L=C3=ADmites desde $100.000
Puntos BBVA para viajar
Extracci=C3=B3n de efectivo en el exterior
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $35.000
Conocer m=C3=A1s
Tarjeta Visa Internacional
L=C3=ADmites desde $10.000
Puntos BBVA para viajar
Extracci=C3=B3n de efectivo en el exterior
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $20.000
Conocer m=C3=A1s
Tarjeta Mastercard Internacional
L=C3=ADmites desde $10.000
Puntos BBVA para viajar
Extracci=C3=B3n de efectivo en el exterior
Tarjetas adicionales sin costo
Es necesario un ingreso m=C3=ADnimo mensual de $20.000
Conocer m=C3=A1s
--b1_3d217f30a568faa9ce3dd7dc73399561
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<head>
</head>
<body>
<div style=3D"max-width: 600px; margin: 0 auto;">
<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">
<tbody>
<tr><th><a href=3D"http://track.leadsinbx.com/aff_c?offer_id=3D2906&amp;aff=
_id=3D1980&amp;url_id=3D21948"> <img src=3D"https://i.imgur.com/1wg1VQH.jpg=
" width=3D"100%" alt=3D"CLICK AQU&Iacute; CLICK AQU&Iacute; CLICK AQU&Iacut=
e; CLICK AQU&Iacute; CLICK AQU&Iacute; CLICK AQU&Iacute; CLICK AQU&Iacute;"=
title=3D"Es la oportunidad que estabas esperando" /> </a></th></tr>
</tbody>
</table>
</div>
</body>
</html>
--b1_3d217f30a568faa9ce3dd7dc73399561--

View File

@@ -57,14 +57,20 @@ token-insert = "INSERT INTO bayes_tokens (h1, h2, ws, wh) VALUES (?, ?, ?, ?)
ON CONFLICT(h1, h2)
DO UPDATE SET ws = ws + excluded.ws, wh = wh + excluded.wh"
token-lookup = "SELECT ws, wh FROM bayes_tokens WHERE h1 = ? AND h2 = ?"
id-insert = "INSERT INTO id_timestamps (id, last_hit) VALUES (?, CURRENT_TIMESTAMP)"
id-lookup = "SELECT 1 FROM id_timestamps WHERE id = ?"
id-cleanup = "DELETE FROM id_timestamps WHERE (CURRENT_TIMESTAMP - last_hit) < ?"
id-insert = "INSERT INTO seen_ids (id, ttl) VALUES (?, datetime('now', ? || ' seconds'))"
id-lookup = "SELECT 1 FROM seen_ids WHERE id = ? AND ttl > CURRENT_TIMESTAMP"
id-cleanup = "DELETE FROM seen_ids WHERE ttl < CURRENT_TIMESTAMP"
reputation-insert = "INSERT INTO reputation (token, score, count, last_hit) VALUES (?, ?, 1, CURRENT_TIMESTAMP)
ON CONFLICT(token)
DO UPDATE SET score = (count + 1) * (excluded.score + 0.98 * score) / (0.98 * count + 1), count = count + 1, last_hit = CURRENT_TIMESTAMP"
reputation-lookup = "SELECT score, count FROM reputation WHERE token = ?"
[directory."default"]
type = "memory"
[directory."default".lookup]
domains = ["local-domain.org"]
[directory."spam"]
type = "memory"
@@ -119,16 +125,13 @@ type = "glob"
comment = '#'
values = ["spamtrap@*"]
[directory."spam".lookup."options"]
type = "list"
values = ["AUTOLEARN_REPLIES"]
[directory."spam".lookup."scores"]
type = "map"
values = ["SPAM_TRAP discard"]
[resolver]
public-suffix = "file://%LIST_PATH%/public-suffix.dat"
[bayes]
min-learns = 10
[sieve.scripts]
"#;
@@ -140,15 +143,15 @@ ws INTEGER,
wh INTEGER,
PRIMARY KEY (h1, h2)
)",
"CREATE TABLE IF NOT EXISTS id_timestamps (
"CREATE TABLE IF NOT EXISTS seen_ids (
id STRING NOT NULL PRIMARY KEY,
last_hit TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
ttl DATETIME NOT NULL
)",
"CREATE TABLE IF NOT EXISTS reputation (
token STRING NOT NULL PRIMARY KEY,
score FLOAT NOT NULL DEFAULT '0',
count INT(11) NOT NULL DEFAULT '0',
last_hit TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
last_hit DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)",
];
@@ -156,7 +159,13 @@ last_hit TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
async fn antispam() {
/*tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.parse(
"smtp=debug,imap=debug,jmap=debug,store=debug,utils=debug,directory=debug",
)
.unwrap(),
)
.finish(),
)
.unwrap();*/
@@ -206,9 +215,15 @@ async fn antispam() {
.join("resources")
.join("config")
.join("sieve");
let prelude = fs::read_to_string(base_path.join("prelude.sieve")).unwrap();
let script_config = fs::read_to_string(base_path.join("config.sieve")).unwrap();
let script_prelude = fs::read_to_string(base_path.join("prelude.sieve")).unwrap();
let mut all_scripts = script_config.clone() + "\n" + script_prelude.as_str();
for test_name in tests {
let mut script = fs::read_to_string(base_path.join(format!("{test_name}.sieve"))).unwrap();
if !["reputation", "replies_out", "pyzor"].contains(&test_name) {
all_scripts = all_scripts + "\n" + script.as_str();
}
if test_name == "reputation" {
script = "let \"score\" \"env.score\";\n\n".to_string()
+ script.as_str()
@@ -216,10 +231,17 @@ async fn antispam() {
"\n\nif eval \"score != env.final_score\" ",
"{let \"t.INVALID_SCORE\" \"score\";}\n"
);
} else if test_name == "bayes_classify" {
script = script.replace("200", "10");
}
config.push_str(&format!("{test_name} = '''{prelude}\n{script}\n'''\n"));
config.push_str(&format!(
"{test_name} = '''{script_config}\n{script_prelude}\n{script}\n'''\n"
));
}
config.push_str(&format!("combined = '''{all_scripts}\n'''\n"));
// Parse config
let config = Config::parse(&config).unwrap();
let mut ctx = ConfigContext::new(&[]);
@@ -297,7 +319,10 @@ async fn antispam() {
.join("smtp")
.join("antispam");
let span = tracing::info_span!("sieve_antispam");
for test_name in tests {
for &test_name in tests.iter().chain(&["combined"]) {
/*if test_name != "combined" {
continue;
}*/
println!("===== {test_name} =====");
let script = ctx.scripts.remove(test_name).unwrap();