summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormdecimus <mauro@stalw.art>2024-03-30 18:12:40 +0100
committermdecimus <mauro@stalw.art>2024-03-30 18:12:40 +0100
commit35562bb9fdf5fc42235e1bc075258315224d6811 (patch)
tree5f4f9761addbfc55e8b39c9e9b07e32cde655f30
parentcb4d2f15ae2cb871da9a6ec6160a1845fa52e238 (diff)
Use safe defaults when settings are missing
-rw-r--r--crates/common/src/addresses.rs10
-rw-r--r--crates/common/src/config/imap.rs8
-rw-r--r--crates/common/src/config/jmap/settings.rs12
-rw-r--r--crates/common/src/config/mod.rs17
-rw-r--r--crates/common/src/config/network.rs21
-rw-r--r--crates/common/src/config/scripts.rs107
-rw-r--r--crates/common/src/config/server/listener.rs50
-rw-r--r--crates/common/src/config/server/mod.rs20
-rw-r--r--crates/common/src/config/server/tls.rs6
-rw-r--r--crates/common/src/config/smtp/auth.rs95
-rw-r--r--crates/common/src/config/smtp/mod.rs113
-rw-r--r--crates/common/src/config/smtp/queue.rs157
-rw-r--r--crates/common/src/config/smtp/report.rs165
-rw-r--r--crates/common/src/config/smtp/resolver.rs5
-rw-r--r--crates/common/src/config/smtp/session.rs254
-rw-r--r--crates/common/src/config/smtp/throttle.rs10
-rw-r--r--crates/common/src/config/tracers.rs3
-rw-r--r--crates/common/src/expr/if_block.rs50
-rw-r--r--crates/common/src/expr/mod.rs64
-rw-r--r--crates/common/src/expr/tokenizer.rs34
-rw-r--r--crates/common/src/lib.rs5
-rw-r--r--crates/common/src/listener/acme/mod.rs4
-rw-r--r--crates/common/src/listener/acme/resolver.rs6
-rw-r--r--crates/common/src/listener/blocked.rs2
-rw-r--r--crates/common/src/listener/listen.rs14
-rw-r--r--crates/common/src/listener/mod.rs36
-rw-r--r--crates/common/src/scripts/plugins/bayes.rs1
-rw-r--r--crates/directory/src/backend/smtp/config.rs2
-rw-r--r--crates/jmap/src/api/http.rs20
-rw-r--r--crates/smtp/src/core/mod.rs7
-rw-r--r--crates/smtp/src/core/params.rs11
-rw-r--r--crates/smtp/src/core/throttle.rs7
-rw-r--r--crates/smtp/src/inbound/auth.rs5
-rw-r--r--crates/smtp/src/inbound/ehlo.rs7
-rw-r--r--crates/smtp/src/inbound/session.rs25
-rw-r--r--crates/smtp/src/inbound/spawn.rs17
-rw-r--r--crates/smtp/src/inbound/vrfy.rs4
-rw-r--r--crates/smtp/src/outbound/delivery.rs10
-rw-r--r--crates/smtp/src/outbound/lookup.rs2
-rw-r--r--crates/smtp/src/queue/mod.rs16
-rw-r--r--crates/smtp/src/reporting/dkim.rs4
-rw-r--r--crates/smtp/src/reporting/dmarc.rs5
-rw-r--r--crates/smtp/src/reporting/spf.rs4
-rw-r--r--crates/smtp/src/scripts/event_loop.rs12
-rw-r--r--crates/smtp/src/scripts/exec.rs1
-rw-r--r--crates/smtp/src/scripts/mod.rs26
-rw-r--r--crates/store/src/backend/foundationdb/main.rs10
-rw-r--r--crates/store/src/backend/memory/mod.rs8
-rw-r--r--crates/store/src/backend/mysql/main.rs13
-rw-r--r--crates/store/src/backend/postgres/main.rs6
-rw-r--r--crates/store/src/backend/redis/mod.rs175
-rw-r--r--crates/utils/src/config/parser.rs16
-rw-r--r--crates/utils/src/config/utils.rs40
-rw-r--r--crates/utils/src/suffixlist.rs20
-rw-r--r--resources/config/build.py94
-rw-r--r--resources/config/common/cache.toml35
-rw-r--r--resources/config/common/server.toml37
-rw-r--r--resources/config/common/sieve.toml73
-rw-r--r--resources/config/common/store.toml20
-rw-r--r--resources/config/common/tls.toml30
-rw-r--r--resources/config/common/tracing.toml24
-rw-r--r--resources/config/config.toml52
-rw-r--r--resources/config/directory/imap.toml29
-rw-r--r--resources/config/directory/internal.toml20
-rw-r--r--resources/config/directory/ldap.toml60
-rw-r--r--resources/config/directory/lmtp.toml33
-rw-r--r--resources/config/directory/memory.toml59
-rw-r--r--resources/config/directory/sql.toml26
-rw-r--r--resources/config/imap/listener.toml16
-rw-r--r--resources/config/imap/settings.toml22
-rw-r--r--resources/config/jmap/auth.toml6
-rw-r--r--resources/config/jmap/listener.toml14
-rw-r--r--resources/config/jmap/oauth.toml16
-rw-r--r--resources/config/jmap/protocol.toml43
-rw-r--r--resources/config/jmap/push.toml21
-rw-r--r--resources/config/jmap/ratelimit.toml9
-rw-r--r--resources/config/jmap/websockets.toml8
-rw-r--r--resources/config/minimal.toml72
-rw-r--r--resources/config/security.toml19
-rw-r--r--resources/config/smtp/auth.toml28
-rw-r--r--resources/config/smtp/listener.toml21
-rw-r--r--resources/config/smtp/milter.toml26
-rw-r--r--resources/config/smtp/queue.toml49
-rw-r--r--resources/config/smtp/remote.toml18
-rw-r--r--resources/config/smtp/report.toml55
-rw-r--r--resources/config/smtp/resolver.toml13
-rw-r--r--resources/config/smtp/session.toml100
-rw-r--r--resources/config/smtp/signature.toml18
-rw-r--r--resources/config/smtp/spamfilter.toml62
-rw-r--r--resources/config/spamfilter.toml10331
-rw-r--r--resources/config/spamfilter/maps/allow_dmarc.list1
-rw-r--r--resources/config/spamfilter/maps/allow_spf_dkim.list1
-rw-r--r--resources/config/spamfilter/maps/spam_trap.list1
-rw-r--r--resources/config/spamfilter/maps/suffix_list.dat.gzbin80969 -> 0 bytes
-rw-r--r--resources/config/spamfilter/scripts/config.sieve24
-rw-r--r--resources/config/spamfilter/settings.toml20
-rw-r--r--resources/config/store/elasticsearch.toml18
-rw-r--r--resources/config/store/foundationdb.toml20
-rw-r--r--resources/config/store/fs.toml12
-rw-r--r--resources/config/store/mysql.toml37
-rw-r--r--resources/config/store/postgresql.toml39
-rw-r--r--resources/config/store/redis.toml18
-rw-r--r--resources/config/store/rocksdb.toml18
-rw-r--r--resources/config/store/s3.toml18
-rw-r--r--resources/config/store/sqlite.toml31
-rw-r--r--tests/resources/scripts/create_test_env.sh64
-rw-r--r--tests/src/lib.rs4
-rw-r--r--tests/src/smtp/config.rs26
-rw-r--r--tests/src/smtp/inbound/antispam.rs42
-rw-r--r--tests/src/smtp/inbound/auth.rs2
-rw-r--r--tests/src/smtp/inbound/data.rs30
-rw-r--r--tests/src/smtp/inbound/milter.rs4
-rw-r--r--tests/src/smtp/inbound/scripts.rs23
-rw-r--r--tests/src/smtp/lookup/sql.rs8
-rw-r--r--tests/src/smtp/outbound/dane.rs14
-rw-r--r--tests/src/smtp/outbound/extensions.rs8
-rw-r--r--tests/src/smtp/outbound/mod.rs6
-rw-r--r--tests/src/smtp/outbound/mta_sts.rs9
-rw-r--r--tests/src/smtp/outbound/tls.rs3
-rw-r--r--tests/src/smtp/session.rs1
120 files changed, 11733 insertions, 2070 deletions
diff --git a/crates/common/src/addresses.rs b/crates/common/src/addresses.rs
index 1f54c864..3e17d5b3 100644
--- a/crates/common/src/addresses.rs
+++ b/crates/common/src/addresses.rs
@@ -4,8 +4,10 @@ use directory::Directory;
use utils::config::{utils::AsKey, Config};
use crate::{
- config::smtp::{session::AddressMapping, V_RECIPIENT},
- expr::{functions::ResolveVariable, if_block::IfBlock, tokenizer::TokenMap, Variable},
+ config::smtp::session::AddressMapping,
+ expr::{
+ functions::ResolveVariable, if_block::IfBlock, tokenizer::TokenMap, Variable, V_RECIPIENT,
+ },
Core,
};
@@ -130,7 +132,7 @@ impl AddressMapping {
} else if let Some(if_block) = IfBlock::try_parse(
config,
key,
- &TokenMap::default().with_variables([
+ &TokenMap::default().with_variables_map([
("address", V_RECIPIENT),
("email", V_RECIPIENT),
("rcpt", V_RECIPIENT),
@@ -138,7 +140,7 @@ impl AddressMapping {
) {
AddressMapping::Custom(if_block)
} else {
- AddressMapping::Disable
+ AddressMapping::Enable
}
}
}
diff --git a/crates/common/src/config/imap.rs b/crates/common/src/config/imap.rs
index a94307cc..8ccb84aa 100644
--- a/crates/common/src/config/imap.rs
+++ b/crates/common/src/config/imap.rs
@@ -39,8 +39,12 @@ impl ImapConfig {
timeout_idle: config
.property_or_default("imap.timeout.idle", "30m")
.unwrap_or_else(|| Duration::from_secs(1800)),
- rate_requests: config.property_or_default("imap.rate-limit.requests", "2000/1m"),
- rate_concurrent: config.property("imap.rate-limit.concurrent"),
+ rate_requests: config
+ .property_or_default::<Option<Rate>>("imap.rate-limit.requests", "2000/1m")
+ .unwrap_or_default(),
+ rate_concurrent: config
+ .property::<Option<u64>>("imap.rate-limit.concurrent")
+ .unwrap_or_default(),
allow_plain_auth: config
.property_or_default("imap.auth.allow-plain-text", "false")
.unwrap_or(false),
diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs
index 4f0c9338..ec8383ea 100644
--- a/crates/common/src/config/jmap/settings.rs
+++ b/crates/common/src/config/jmap/settings.rs
@@ -144,9 +144,15 @@ impl JmapConfig {
session_cache_ttl: config
.property("cache.session.ttl")
.unwrap_or(Duration::from_secs(3600)),
- rate_authenticated: config.property_or_default("jmap.rate-limit.account", "1000/1m"),
- rate_authenticate_req: config.property_or_default("authentication.rate-limit", "10/1m"),
- rate_anonymous: config.property_or_default("jmap.rate-limit.anonymous", "100/1m"),
+ rate_authenticated: config
+ .property_or_default::<Option<Rate>>("jmap.rate-limit.account", "1000/1m")
+ .unwrap_or_default(),
+ rate_authenticate_req: config
+ .property_or_default::<Option<Rate>>("authentication.rate-limit", "10/1m")
+ .unwrap_or_default(),
+ rate_anonymous: config
+ .property_or_default::<Option<Rate>>("jmap.rate-limit.anonymous", "100/1m")
+ .unwrap_or_default(),
oauth_key: config
.value("oauth.key")
.map(|s| s.to_string())
diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs
index c1920207..352b6627 100644
--- a/crates/common/src/config/mod.rs
+++ b/crates/common/src/config/mod.rs
@@ -5,7 +5,7 @@ use directory::{Directories, Directory};
use store::{BlobBackend, BlobStore, FtsStore, LookupStore, Store, Stores};
use utils::config::Config;
-use crate::{listener::tls::TlsManager, Core, Network};
+use crate::{expr::*, listener::tls::TlsManager, Core, Network};
use self::{
imap::ImapConfig, jmap::settings::JmapConfig, manager::ConfigManager, scripts::Scripting,
@@ -22,6 +22,16 @@ pub mod smtp;
pub mod storage;
pub mod tracers;
+pub(crate) const CONNECTION_VARS: &[u32; 7] = &[
+ V_LISTENER,
+ V_REMOTE_IP,
+ V_REMOTE_PORT,
+ V_LOCAL_IP,
+ V_LOCAL_PORT,
+ V_PROTOCOL,
+ V_TLS,
+];
+
impl Core {
pub async fn parse(config: &mut Config, stores: Stores, config_manager: ConfigManager) -> Self {
let mut data = config
@@ -78,7 +88,7 @@ impl Core {
}
})
.unwrap_or_default();
- let directories = Directories::parse(config, &stores, data.clone()).await;
+ let mut directories = Directories::parse(config, &stores, data.clone()).await;
let directory = config
.value_require("storage.directory")
.map(|id| id.to_string())
@@ -94,6 +104,9 @@ impl Core {
}
})
.unwrap_or_else(|| Arc::new(Directory::default()));
+ directories
+ .directories
+ .insert("*".to_string(), directory.clone());
// If any of the stores are missing, disable all stores to avoid data loss
if matches!(data, Store::None)
diff --git a/crates/common/src/config/network.rs b/crates/common/src/config/network.rs
index f26b4e6d..70918e1f 100644
--- a/crates/common/src/config/network.rs
+++ b/crates/common/src/config/network.rs
@@ -6,14 +6,17 @@ use crate::{
Network,
};
-use super::smtp::*;
+use super::CONNECTION_VARS;
impl Default for Network {
fn default() -> Self {
Self {
blocked_ips: Default::default(),
- hostname: IfBlock::new("localhost".to_string()),
- url: IfBlock::new("http://localhost:8080".to_string()),
+ url: IfBlock::new::<()>(
+ "server.http.url",
+ [],
+ "protocol + '://' + key_get('default', 'hostname') + ':' + local_port",
+ ),
}
}
}
@@ -24,17 +27,9 @@ impl Network {
blocked_ips: BlockedIps::parse(config),
..Default::default()
};
- let token_map = &TokenMap::default().with_smtp_variables(&[
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_HELO_DOMAIN,
- ]);
+ let token_map = &TokenMap::default().with_variables(CONNECTION_VARS);
- for (value, key) in [
- (&mut network.hostname, "server.hostname"),
- (&mut network.url, "server.url"),
- ] {
+ for (value, key) in [(&mut network.url, "server.url")] {
if let Some(if_block) = IfBlock::try_parse(config, key, token_map) {
*value = if_block;
}
diff --git a/crates/common/src/config/scripts.rs b/crates/common/src/config/scripts.rs
index f993d364..bf9fd5a0 100644
--- a/crates/common/src/config/scripts.rs
+++ b/crates/common/src/config/scripts.rs
@@ -13,16 +13,16 @@ use utils::config::Config;
use crate::scripts::{functions::register_functions, plugins::RegisterSievePlugins};
-use super::smtp::parse_server_hostname;
+use super::{if_block::IfBlock, smtp::SMTP_RCPT_TO_VARS, tokenizer::TokenMap};
pub struct Scripting {
pub untrusted_compiler: Compiler,
pub untrusted_runtime: Runtime,
pub trusted_runtime: Runtime,
- pub from_addr: String,
- pub from_name: String,
- pub return_path: String,
- pub sign: Vec<String>,
+ pub from_addr: IfBlock,
+ pub from_name: IfBlock,
+ pub return_path: IfBlock,
+ pub sign: IfBlock,
pub scripts: AHashMap<String, Arc<Sieve>>,
pub bayes_cache: BayesTokenCache,
pub remote_lists: RwLock<AHashMap<String, RemoteList>>,
@@ -182,7 +182,7 @@ impl Scripting {
.unwrap_or("Auto: ")
.to_string(),
)
- .with_env_variable("name", "Stalwart JMAP")
+ .with_env_variable("name", "Stalwart Mail Server")
.with_env_variable("version", env!("CARGO_PKG_VERSION"))
.with_env_variable("location", "MS")
.with_env_variable("phase", "during");
@@ -250,13 +250,11 @@ impl Scripting {
if let Some(value) = config.property::<Duration>("sieve.trusted.limits.duplicate-expiry") {
trusted_runtime.set_default_duplicate_expiry(value.as_secs());
}
- let hostname = if let Some(hostname) = config.value("sieve.trusted.hostname") {
- hostname.to_string()
- } else {
- parse_server_hostname(config)
- .and_then(|h| h.into_default_string())
- .unwrap_or_else(|| "localhost".to_string())
- };
+ let hostname = config
+ .value("sieve.trusted.hostname")
+ .or_else(|| config.value("lookup.default.hostname"))
+ .unwrap_or("localhost")
+ .to_string();
trusted_runtime.set_local_hostname(hostname.clone());
// Parse scripts
@@ -266,19 +264,12 @@ impl Scripting {
.map(|s| s.to_string())
.collect::<Vec<_>>()
{
- // Skip sub-scripts
- if config
- .property(("sieve.trusted.scripts", id.as_str(), "snippet"))
- .unwrap_or(false)
- {
- continue;
- }
-
- let script = config
- .value(("sieve.trusted.scripts", id.as_str(), "contents"))
- .unwrap();
-
- match trusted_compiler.compile(script.as_bytes()) {
+ match trusted_compiler.compile(
+ config
+ .value(("sieve.trusted.scripts", id.as_str(), "contents"))
+ .unwrap()
+ .as_bytes(),
+ ) {
Ok(compiled) => {
scripts.insert(id, compiled.into());
}
@@ -289,26 +280,42 @@ impl Scripting {
}
}
+ let token_map = TokenMap::default().with_variables(SMTP_RCPT_TO_VARS);
+
Scripting {
untrusted_compiler,
untrusted_runtime,
trusted_runtime,
- from_addr: config
- .value("sieve.trusted.from-addr")
- .map(|a| a.to_string())
- .unwrap_or(format!("MAILER-DAEMON@{hostname}")),
- from_name: config
- .value("sieve.trusted.from-name")
- .unwrap_or("Mailer Daemon")
- .to_string(),
- return_path: config
- .value("sieve.trusted.return-path")
- .unwrap_or_default()
- .to_string(),
- sign: config
- .values("sieve.trusted.sign")
- .map(|(_, v)| v.to_string())
- .collect(),
+ from_addr: IfBlock::try_parse(config, "sieve.trusted.from-addr", &token_map)
+ .unwrap_or_else(|| {
+ IfBlock::new::<()>(
+ "sieve.trusted.from-addr",
+ [],
+ "'MAILER-DAEMON@' + key_get('default', 'domain')",
+ )
+ }),
+ from_name: IfBlock::try_parse(config, "sieve.trusted.from-name", &token_map)
+ .unwrap_or_else(|| {
+ IfBlock::new::<()>(
+ "sieve.trusted.from-name",
+ [],
+ "'Mailer Daemon'",
+ )
+ }),
+ return_path: IfBlock::try_parse(config, "sieve.trusted.return-path", &token_map)
+ .unwrap_or_else(|| {
+ IfBlock::empty(
+ "sieve.trusted.return-path",
+ )
+ }),
+ sign: IfBlock::try_parse(config, "sieve.trusted.sign", &token_map)
+ .unwrap_or_else(|| {
+ IfBlock::new::<()>(
+ "sieve.trusted.sign",
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ )
+ }),
scripts,
bayes_cache: BayesTokenCache::new(
config
@@ -332,10 +339,18 @@ impl Default for Scripting {
untrusted_compiler: Compiler::new(),
untrusted_runtime: Runtime::new(),
trusted_runtime: Runtime::new(),
- from_addr: "MAILER-DAEMON@localhost".to_string(),
- from_name: "Mailer Daemon".to_string(),
- return_path: "".to_string(),
- sign: Vec::new(),
+ from_addr: IfBlock::new::<()>(
+ "sieve.trusted.from-addr",
+ [],
+ "'MAILER-DAEMON@' + key_get('default', 'domain')",
+ ),
+ from_name: IfBlock::new::<()>("sieve.trusted.from-name", [], "'Mailer Daemon'"),
+ return_path: IfBlock::empty("sieve.trusted.return-path"),
+ sign: IfBlock::new::<()>(
+ "sieve.trusted.sign",
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ ),
scripts: AHashMap::new(),
bayes_cache: BayesTokenCache::new(
8192,
diff --git a/crates/common/src/config/server/listener.rs b/crates/common/src/config/server/listener.rs
index 1eb755d8..cb4e6b5e 100644
--- a/crates/common/src/config/server/listener.rs
+++ b/crates/common/src/config/server/listener.rs
@@ -21,7 +21,7 @@
* for more details.
*/
-use std::{net::SocketAddr, sync::Arc};
+use std::{net::SocketAddr, sync::Arc, time::Duration};
use rustls::{
crypto::ring::{default_provider, ALL_CIPHER_SUITES},
@@ -144,23 +144,40 @@ impl Servers {
}
}
+ // Set default options
+ if !config.contains_key(("server.listener", id, "socket.reuse-addr")) {
+ let _ = socket.set_reuseaddr(true);
+ }
+
listeners.push(Listener {
socket,
addr,
ttl: config
- .property_or_else(("server.listener", id, "socket.ttl"), "server.socket.ttl"),
- backlog: config.property_or_else(
- ("server.listener", id, "socket.backlog"),
- "server.socket.backlog",
- ),
- linger: config.property_or_else(
- ("server.listener", id, "socket.linger"),
- "server.socket.linger",
- ),
+ .property_or_else::<Option<u32>>(
+ ("server.listener", id, "socket.ttl"),
+ "server.socket.ttl",
+ "false",
+ )
+ .unwrap_or_default(),
+ backlog: config
+ .property_or_else::<Option<u32>>(
+ ("server.listener", id, "socket.backlog"),
+ "server.socket.backlog",
+ "1024",
+ )
+ .unwrap_or_default(),
+ linger: config
+ .property_or_else::<Option<Duration>>(
+ ("server.listener", id, "socket.linger"),
+ "server.socket.linger",
+ "false",
+ )
+ .unwrap_or_default(),
nodelay: config
.property_or_else(
("server.listener", id, "socket.nodelay"),
"server.socket.nodelay",
+ "true",
)
.unwrap_or(true),
});
@@ -190,6 +207,7 @@ impl Servers {
.property_or_else(
("server.listener", id, "max-connections"),
"server.max-connections",
+ "8192",
)
.unwrap_or(8192),
id: id_,
@@ -218,8 +236,8 @@ impl Servers {
let id = id_.as_str();
// Build TLS config
let acceptor = if config
- .property_or_else(("server.listener", id, "tls.enable"), "server.tls.enable")
- .unwrap_or(false)
+ .property_or_default(("server.listener", id, "tls.enable"), "true")
+ .unwrap_or(true)
{
// Parse protocol versions
let mut tls_v2 = true;
@@ -292,6 +310,7 @@ impl Servers {
.property_or_else(
("server.listener", id, "tls.ignore-client-order"),
"server.tls.ignore-client-order",
+ "true",
)
.unwrap_or(true);
@@ -302,11 +321,8 @@ impl Servers {
acme_config: acme_config.clone(),
default_config,
implicit: config
- .property_or_else(
- ("server.listener", id, "tls.implicit"),
- "server.tls.implicit",
- )
- .unwrap_or(true),
+ .property_or_default(("server.listener", id, "tls.implicit"), "false")
+ .unwrap_or(false),
}
} else {
TcpAcceptor::Plain
diff --git a/crates/common/src/config/server/mod.rs b/crates/common/src/config/server/mod.rs
index 3f8e9f1d..4950cb8e 100644
--- a/crates/common/src/config/server/mod.rs
+++ b/crates/common/src/config/server/mod.rs
@@ -46,14 +46,20 @@ pub enum ServerProtocol {
ManageSieve,
}
-impl Display for ServerProtocol {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl ServerProtocol {
+ pub fn as_str(&self) -> &'static str {
match self {
- ServerProtocol::Smtp => write!(f, "smtp"),
- ServerProtocol::Lmtp => write!(f, "lmtp"),
- ServerProtocol::Imap => write!(f, "imap"),
- ServerProtocol::Http => write!(f, "http"),
- ServerProtocol::ManageSieve => write!(f, "managesieve"),
+ ServerProtocol::Smtp => "smtp",
+ ServerProtocol::Lmtp => "lmtp",
+ ServerProtocol::Imap => "imap",
+ ServerProtocol::Http => "http",
+ ServerProtocol::ManageSieve => "managesieve",
}
}
}
+
+impl Display for ServerProtocol {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(self.as_str())
+ }
+}
diff --git a/crates/common/src/config/server/tls.rs b/crates/common/src/config/server/tls.rs
index 9648fb71..429b439d 100644
--- a/crates/common/src/config/server/tls.rs
+++ b/crates/common/src/config/server/tls.rs
@@ -105,6 +105,11 @@ impl TlsManager {
.map(|(_, v)| v.to_string())
.collect::<Vec<_>>();
+ // This ACME manager is the default when SNI is not available
+ let default = config
+ .property::<bool>(("acme", acme_id.as_str(), "default"))
+ .unwrap_or_default();
+
// Add domains for self-signed certificate
subject_names.extend(domains.iter().cloned());
@@ -115,6 +120,7 @@ impl TlsManager {
domains,
contact,
renew_before,
+ default,
) {
Ok(acme_provider) => {
acme_providers.insert(acme_id.to_string(), acme_provider);
diff --git a/crates/common/src/config/smtp/auth.rs b/crates/common/src/config/smtp/auth.rs
index dd842a01..5eabd90f 100644
--- a/crates/common/src/config/smtp/auth.rs
+++ b/crates/common/src/config/smtp/auth.rs
@@ -11,7 +11,10 @@ use utils::config::{
Config,
};
-use crate::expr::{self, if_block::IfBlock, tokenizer::TokenMap, Constant, ConstantValue};
+use crate::{
+ config::CONNECTION_VARS,
+ expr::{self, if_block::IfBlock, tokenizer::TokenMap, Constant, ConstantValue},
+};
use super::*;
@@ -83,22 +86,62 @@ impl Default for MailAuthConfig {
fn default() -> Self {
Self {
dkim: DkimAuthConfig {
- verify: IfBlock::new(VerifyStrategy::Relaxed),
- sign: Default::default(),
+ verify: IfBlock::new::<VerifyStrategy>("auth.dkim.verify", [], "relaxed"),
+ sign: IfBlock::new::<()>(
+ "auth.dkim.sign",
+ [(
+ "local_port != 25",
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ )],
+ "false",
+ ),
},
arc: ArcAuthConfig {
- verify: IfBlock::new(VerifyStrategy::Relaxed),
- seal: Default::default(),
+ verify: IfBlock::new::<VerifyStrategy>("auth.arc.verify", [], "relaxed"),
+ seal: IfBlock::new::<()>(
+ "auth.arc.seal",
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ ),
},
spf: SpfAuthConfig {
- verify_ehlo: IfBlock::new(VerifyStrategy::Relaxed),
- verify_mail_from: IfBlock::new(VerifyStrategy::Relaxed),
+ verify_ehlo: IfBlock::new::<VerifyStrategy>(
+ "auth.spf.verify.ehlo",
+ [("local_port == 25", "relaxed")],
+ #[cfg(not(feature = "test_mode"))]
+ "disable",
+ #[cfg(feature = "test_mode")]
+ "relaxed",
+
+ ),
+ verify_mail_from: IfBlock::new::<VerifyStrategy>(
+ "auth.spf.verify.mail-from",
+ [("local_port == 25", "relaxed")],
+ #[cfg(not(feature = "test_mode"))]
+ "disable",
+ #[cfg(feature = "test_mode")]
+ "relaxed",
+ ),
},
dmarc: DmarcAuthConfig {
- verify: IfBlock::new(VerifyStrategy::Relaxed),
+ verify: IfBlock::new::<VerifyStrategy>(
+ "auth.dmarc.verify",
+ [("local_port == 25", "relaxed")],
+ #[cfg(not(feature = "test_mode"))]
+ "disable",
+ #[cfg(feature = "test_mode")]
+ "relaxed",
+ ),
},
iprev: IpRevAuthConfig {
- verify: IfBlock::new(VerifyStrategy::Relaxed),
+ verify: IfBlock::new::<VerifyStrategy>(
+ "auth.ipref.verify",
+ [("local_port == 25", "relaxed")],
+ #[cfg(not(feature = "test_mode"))]
+ "disable",
+ #[cfg(feature = "test_mode")]
+ "relaxed",
+ ),
},
signers: Default::default(),
sealers: Default::default(),
@@ -108,27 +151,19 @@ impl Default for MailAuthConfig {
impl MailAuthConfig {
pub fn parse(config: &mut Config) -> Self {
- let sender_vars = TokenMap::default()
- .with_smtp_variables(&[
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_AUTHENTICATED_AS,
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- ])
+ let rcpt_vars = TokenMap::default()
+ .with_variables(SMTP_RCPT_TO_VARS)
.with_constants::<VerifyStrategy>();
let conn_vars = TokenMap::default()
- .with_smtp_variables(&[V_LISTENER, V_REMOTE_IP, V_LOCAL_IP])
+ .with_variables(CONNECTION_VARS)
.with_constants::<VerifyStrategy>();
let mut mail_auth = Self::default();
for (value, key, token_map) in [
- (&mut mail_auth.dkim.verify, "auth.dkim.verify", &sender_vars),
- (&mut mail_auth.dkim.sign, "auth.dkim.sign", &sender_vars),
- (&mut mail_auth.arc.verify, "auth.arc.verify", &sender_vars),
- (&mut mail_auth.arc.seal, "auth.arc.seal", &sender_vars),
+ (&mut mail_auth.dkim.verify, "auth.dkim.verify", &rcpt_vars),
+ (&mut mail_auth.dkim.sign, "auth.dkim.sign", &rcpt_vars),
+ (&mut mail_auth.arc.verify, "auth.arc.verify", &rcpt_vars),
+ (&mut mail_auth.arc.seal, "auth.arc.seal", &rcpt_vars),
(
&mut mail_auth.spf.verify_ehlo,
"auth.spf.verify.ehlo",
@@ -139,16 +174,8 @@ impl MailAuthConfig {
"auth.spf.verify.mail-from",
&conn_vars,
),
- (
- &mut mail_auth.dmarc.verify,
- "auth.dmarc.verify",
- &sender_vars,
- ),
- (
- &mut mail_auth.iprev.verify,
- "auth.iprev.verify",
- &sender_vars,
- ),
+ (&mut mail_auth.dmarc.verify, "auth.dmarc.verify", &rcpt_vars),
+ (&mut mail_auth.iprev.verify, "auth.iprev.verify", &conn_vars),
] {
if let Some(if_block) = IfBlock::try_parse(config, key, token_map) {
*value = if_block;
diff --git a/crates/common/src/config/smtp/mod.rs b/crates/common/src/config/smtp/mod.rs
index b173d717..5aca0155 100644
--- a/crates/common/src/config/smtp/mod.rs
+++ b/crates/common/src/config/smtp/mod.rs
@@ -7,13 +7,15 @@ pub mod resolver;
pub mod session;
pub mod throttle;
-use crate::expr::{if_block::IfBlock, tokenizer::TokenMap, Expression, ExpressionItem, Token};
+use crate::expr::{tokenizer::TokenMap, Expression};
use self::{
auth::MailAuthConfig, queue::QueueConfig, report::ReportConfig, resolver::Resolvers,
session::SessionConfig,
};
+use super::*;
+
#[derive(Default, Clone)]
pub struct SmtpConfig {
pub session: SessionConfig,
@@ -43,30 +45,73 @@ pub const THROTTLE_REMOTE_IP: u16 = 1 << 7;
pub const THROTTLE_LOCAL_IP: u16 = 1 << 8;
pub const THROTTLE_HELO_DOMAIN: u16 = 1 << 9;
-pub const V_RECIPIENT: u32 = 0;
-pub const V_RECIPIENT_DOMAIN: u32 = 1;
-pub const V_SENDER: u32 = 2;
-pub const V_SENDER_DOMAIN: u32 = 3;
-pub const V_MX: u32 = 4;
-pub const V_HELO_DOMAIN: u32 = 5;
-pub const V_AUTHENTICATED_AS: u32 = 6;
-pub const V_LISTENER: u32 = 7;
-pub const V_REMOTE_IP: u32 = 8;
-pub const V_LOCAL_IP: u32 = 9;
-pub const V_PRIORITY: u32 = 10;
+pub(crate) const RCPT_DOMAIN_VARS: &[u32; 1] = &[V_RECIPIENT_DOMAIN];
-pub const VARIABLES_MAP: &[(&str, u32)] = &[
- ("rcpt", V_RECIPIENT),
- ("rcpt_domain", V_RECIPIENT_DOMAIN),
- ("sender", V_SENDER),
- ("sender_domain", V_SENDER_DOMAIN),
- ("mx", V_MX),
- ("helo_domain", V_HELO_DOMAIN),
- ("authenticated_as", V_AUTHENTICATED_AS),
- ("listener", V_LISTENER),
- ("remote_ip", V_REMOTE_IP),
- ("local_ip", V_LOCAL_IP),
- ("priority", V_PRIORITY),
+pub(crate) const SMTP_EHLO_VARS: &[u32; 8] = &[
+ V_LISTENER,
+ V_REMOTE_IP,
+ V_REMOTE_PORT,
+ V_LOCAL_IP,
+ V_LOCAL_PORT,
+ V_PROTOCOL,
+ V_TLS,
+ V_HELO_DOMAIN,
+];
+pub(crate) const SMTP_MAIL_FROM_VARS: &[u32; 10] = &[
+ V_LISTENER,
+ V_REMOTE_IP,
+ V_REMOTE_PORT,
+ V_LOCAL_IP,
+ V_LOCAL_PORT,
+ V_PROTOCOL,
+ V_TLS,
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_AUTHENTICATED_AS,
+];
+pub(crate) const SMTP_RCPT_TO_VARS: &[u32; 15] = &[
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_RECIPIENTS,
+ V_RECIPIENT,
+ V_RECIPIENT_DOMAIN,
+ V_AUTHENTICATED_AS,
+ V_LISTENER,
+ V_REMOTE_IP,
+ V_REMOTE_PORT,
+ V_LOCAL_IP,
+ V_LOCAL_PORT,
+ V_PROTOCOL,
+ V_TLS,
+ V_PRIORITY,
+ V_HELO_DOMAIN,
+];
+pub(crate) const SMTP_QUEUE_HOST_VARS: &[u32; 9] = &[
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_RECIPIENT_DOMAIN,
+ V_RECIPIENT,
+ V_RECIPIENTS,
+ V_MX,
+ V_PRIORITY,
+ V_REMOTE_IP,
+ V_LOCAL_IP,
+];
+pub(crate) const SMTP_QUEUE_RCPT_VARS: &[u32; 5] = &[
+ V_RECIPIENT_DOMAIN,
+ V_RECIPIENTS,
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_PRIORITY,
+];
+pub(crate) const SMTP_QUEUE_SENDER_VARS: &[u32; 3] = &[V_SENDER, V_SENDER_DOMAIN, V_PRIORITY];
+pub(crate) const SMTP_QUEUE_MX_VARS: &[u32; 6] = &[
+ V_RECIPIENT_DOMAIN,
+ V_RECIPIENTS,
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_PRIORITY,
+ V_MX,
];
impl SmtpConfig {
@@ -80,23 +125,3 @@ impl SmtpConfig {
}
}
}
-
-impl TokenMap {
- pub fn with_smtp_variables(mut self, variables: &[u32]) -> Self {
- for (name, idx) in VARIABLES_MAP {
- if variables.contains(idx) {
- self.tokens.insert(name, Token::Variable(*idx));
- }
- }
-
- self
- }
-}
-
-pub(crate) fn parse_server_hostname(config: &mut Config) -> Option<IfBlock> {
- IfBlock::try_parse(
- config,
- "server.hostname",
- &TokenMap::default().with_smtp_variables(&[V_LISTENER, V_REMOTE_IP, V_LOCAL_IP]),
- )
-}
diff --git a/crates/common/src/config/smtp/queue.rs b/crates/common/src/config/smtp/queue.rs
index e93c5c66..011511df 100644
--- a/crates/common/src/config/smtp/queue.rs
+++ b/crates/common/src/config/smtp/queue.rs
@@ -1,5 +1,3 @@
-use std::time::Duration;
-
use ahash::AHashMap;
use mail_auth::IpLookupStrategy;
use mail_send::Credentials;
@@ -10,7 +8,7 @@ use utils::config::{
use crate::{
config::server::ServerProtocol,
- expr::{if_block::IfBlock, Constant, ConstantValue, Expression, Variable},
+ expr::{if_block::IfBlock, *},
};
use self::throttle::{parse_throttle, parse_throttle_key};
@@ -121,38 +119,80 @@ pub enum RequireOptional {
impl Default for QueueConfig {
fn default() -> Self {
Self {
- retry: IfBlock::new(Duration::from_secs(5 * 60)),
- notify: IfBlock::new(Duration::from_secs(86400)),
- expire: IfBlock::new(Duration::from_secs(5 * 86400)),
- hostname: IfBlock::new("localhost".to_string()),
- next_hop: Default::default(),
- max_mx: IfBlock::new(5),
- max_multihomed: IfBlock::new(2),
- ip_strategy: IfBlock::new(IpLookupStrategy::Ipv4thenIpv6),
+ retry: IfBlock::new::<()>(
+ "queue.schedule.retry",
+ [],
+ "[2m, 5m, 10m, 15m, 30m, 1h, 2h]",
+ ),
+ notify: IfBlock::new::<()>("queue.schedule.notify", [], "[1d, 3d]"),
+ expire: IfBlock::new::<()>("queue.schedule.expire", [], "5d"),
+ hostname: IfBlock::new::<()>(
+ "queue.outbound.hostname",
+ [],
+ "key_get('default', 'hostname')",
+ ),
+ next_hop: IfBlock::new::<()>(
+ "queue.outbound.next-hop",
+ #[cfg(not(feature = "test_mode"))]
+ [("is_local_domain('*', rcpt_domain)", "'local'")],
+ #[cfg(feature = "test_mode")]
+ [],
+ "false",
+ ),
+ max_mx: IfBlock::new::<()>("queue.outbound.limits.mx", [], "5"),
+ max_multihomed: IfBlock::new::<()>("queue.outbound.limits.multihomed", [], "2"),
+ ip_strategy: IfBlock::new::<IpLookupStrategy>(
+ "queue.outbound.ip-strategy",
+ [],
+ "ipv4_then_ipv6",
+ ),
source_ip: QueueOutboundSourceIp {
- ipv4: Default::default(),
- ipv6: Default::default(),
+ ipv4: IfBlock::empty("queue.outbound.source-ip.v4"),
+ ipv6: IfBlock::empty("queue.outbound.source-ip.v6"),
},
tls: QueueOutboundTls {
- dane: IfBlock::new(RequireOptional::Optional),
- mta_sts: IfBlock::new(RequireOptional::Optional),
- start: IfBlock::new(RequireOptional::Optional),
- invalid_certs: IfBlock::new(false),
+ dane: IfBlock::new::<RequireOptional>("queue.outbound.tls.dane", [], "optional"),
+ mta_sts: IfBlock::new::<RequireOptional>(
+ "queue.outbound.tls.mta-sts",
+ [],
+ "optional",
+ ),
+ start: IfBlock::new::<RequireOptional>(
+ "queue.outbound.tls.starttls",
+ [],
+ #[cfg(not(feature = "test_mode"))]
+ "require",
+ #[cfg(feature = "test_mode")]
+ "optional",
+ ),
+ invalid_certs: IfBlock::new::<()>(
+ "queue.outbound.tls.allow-invalid-certs",
+ [],
+ "false",
+ ),
},
dsn: Dsn {
- name: IfBlock::new("Mail Delivery Subsystem".to_string()),
- address: IfBlock::new("MAILER-DAEMON@localhost".to_string()),
- sign: Default::default(),
+ name: IfBlock::new::<()>("report.dsn.from-name", [], "'Mail Delivery Subsystem'"),
+ address: IfBlock::new::<()>(
+ "report.dsn.from-address",
+ [],
+ "'MAILER-DAEMON@' + key_get('default', 'domain')",
+ ),
+ sign: IfBlock::new::<()>(
+ "report.dsn.sign",
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ ),
},
timeout: QueueOutboundTimeout {
- connect: IfBlock::new(Duration::from_secs(5 * 60)),
- greeting: IfBlock::new(Duration::from_secs(5 * 60)),
- tls: IfBlock::new(Duration::from_secs(3 * 60)),
- ehlo: IfBlock::new(Duration::from_secs(5 * 60)),
- mail: IfBlock::new(Duration::from_secs(5 * 60)),
- rcpt: IfBlock::new(Duration::from_secs(5 * 60)),
- data: IfBlock::new(Duration::from_secs(10 * 60)),
- mta_sts: IfBlock::new(Duration::from_secs(10 * 60)),
+ connect: IfBlock::new::<()>("queue.outbound.timeouts.connect", [], "5m"),
+ greeting: IfBlock::new::<()>("queue.outbound.timeouts.greeting", [], "5m"),
+ tls: IfBlock::new::<()>("queue.outbound.timeouts.tls", [], "3m"),
+ ehlo: IfBlock::new::<()>("queue.outbound.timeouts.ehlo", [], "5m"),
+ mail: IfBlock::new::<()>("queue.outbound.timeouts.mail-from", [], "5m"),
+ rcpt: IfBlock::new::<()>("queue.outbound.timeouts.rcpt-to", [], "5m"),
+ data: IfBlock::new::<()>("queue.outbound.timeouts.data", [], "10m"),
+ mta_sts: IfBlock::new::<()>("queue.outbound.timeouts.mta-sts", [], "10m"),
},
throttle: QueueThrottle {
sender: Default::default(),
@@ -172,39 +212,14 @@ impl Default for QueueConfig {
impl QueueConfig {
pub fn parse(config: &mut Config) -> Self {
let mut queue = QueueConfig::default();
- let rcpt_vars = TokenMap::default().with_smtp_variables(&[
- V_RECIPIENT_DOMAIN,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- ]);
- let sender_vars =
- TokenMap::default().with_smtp_variables(&[V_SENDER, V_SENDER_DOMAIN, V_PRIORITY]);
- let mx_vars = TokenMap::default().with_smtp_variables(&[
- V_RECIPIENT_DOMAIN,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_MX,
- ]);
- let host_vars = TokenMap::default().with_smtp_variables(&[
- V_RECIPIENT_DOMAIN,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_LOCAL_IP,
- V_REMOTE_IP,
- V_MX,
- ]);
+ let rcpt_vars = TokenMap::default().with_variables(SMTP_QUEUE_RCPT_VARS);
+ let sender_vars = TokenMap::default().with_variables(SMTP_QUEUE_SENDER_VARS);
+ let mx_vars = TokenMap::default().with_variables(SMTP_QUEUE_MX_VARS);
+ let host_vars = TokenMap::default().with_variables(SMTP_QUEUE_HOST_VARS);
let ip_strategy_vars = sender_vars.clone().with_constants::<IpLookupStrategy>();
let dane_vars = mx_vars.clone().with_constants::<RequireOptional>();
let mta_sts_vars = rcpt_vars.clone().with_constants::<RequireOptional>();
- // Parse default server hostname
- if let Some(hostname) = parse_server_hostname(config) {
- queue.hostname = hostname.into_default("queue.outbound.hostname");
- }
-
for (value, key, token_map) in [
(&mut queue.retry, "queue.schedule.retry", &host_vars),
(&mut queue.notify, "queue.schedule.notify", &rcpt_vars),
@@ -368,15 +383,7 @@ fn parse_queue_throttle(config: &mut Config) -> QueueThrottle {
let all_throttles = parse_throttle(
config,
"queue.throttle",
- &TokenMap::default().with_smtp_variables(&[
- V_RECIPIENT_DOMAIN,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_MX,
- V_REMOTE_IP,
- V_LOCAL_IP,
- ]),
+ &TokenMap::default().with_variables(SMTP_QUEUE_HOST_VARS),
THROTTLE_RCPT_DOMAIN
| THROTTLE_SENDER
| THROTTLE_SENDER_DOMAIN
@@ -487,22 +494,18 @@ fn parse_queue_quota_item(config: &mut Config, prefix: impl AsKey) -> Option<Que
expr: Expression::try_parse(
config,
(prefix.as_str(), "match"),
- &TokenMap::default().with_smtp_variables(&[
- V_RECIPIENT,
- V_RECIPIENT_DOMAIN,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- ]),
+ &TokenMap::default().with_variables(SMTP_QUEUE_HOST_VARS),
)
.unwrap_or_default(),
keys,
size: config
- .property::<usize>((prefix.as_str(), "size"))
- .filter(|&v| v > 0),
+ .property::<Option<usize>>((prefix.as_str(), "size"))
+ .filter(|&v| v.as_ref().map_or(false, |v| *v > 0))
+ .unwrap_or_default(),
messages: config
- .property::<usize>((prefix.as_str(), "messages"))
- .filter(|&v| v > 0),
+ .property::<Option<usize>>((prefix.as_str(), "messages"))
+ .filter(|&v| v.as_ref().map_or(false, |v| *v > 0))
+ .unwrap_or_default(),
};
// Validate
diff --git a/crates/common/src/config/smtp/report.rs b/crates/common/src/config/smtp/report.rs
index f95c2516..6c0e4aa6 100644
--- a/crates/common/src/config/smtp/report.rs
+++ b/crates/common/src/config/smtp/report.rs
@@ -63,41 +63,17 @@ pub enum AggregateFrequency {
impl ReportConfig {
pub fn parse(config: &mut Config) -> Self {
- let sender_vars = TokenMap::default().with_smtp_variables(&[
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_AUTHENTICATED_AS,
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- ]);
- let rcpt_vars = TokenMap::default().with_smtp_variables(&[
- V_SENDER,
- V_SENDER_DOMAIN,
- V_PRIORITY,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_RECIPIENT_DOMAIN,
- ]);
-
- let default_hostname_if_block = parse_server_hostname(config);
- let default_hostname = default_hostname_if_block
- .as_ref()
- .and_then(|i| i.default_string())
- .unwrap_or("localhost")
- .to_string();
+ let sender_vars = TokenMap::default().with_variables(SMTP_MAIL_FROM_VARS);
+ let rcpt_vars = TokenMap::default().with_variables(SMTP_RCPT_TO_VARS);
Self {
submitter: IfBlock::try_parse(
config,
"report.submitter",
- &TokenMap::default().with_smtp_variables(&[V_RECIPIENT_DOMAIN]),
+ &TokenMap::default().with_variables(RCPT_DOMAIN_VARS),
)
.unwrap_or_else(|| {
- default_hostname_if_block
- .map(|i| i.into_default("report.submitter"))
- .unwrap_or_else(|| IfBlock::new("localhost".to_string()))
+ IfBlock::new::<()>("report.submitter", [], "key_get('default', 'hostname')")
}),
analysis: ReportAnalysis {
addresses: config
@@ -106,40 +82,52 @@ impl ReportConfig {
.map(|(_, m)| m)
.collect(),
forward: config.property("report.analysis.forward").unwrap_or(true),
- store: config.property("report.analysis.store"),
+ store: config
+ .property_or_default::<Option<Duration>>("report.analysis.store", "30d")
+ .unwrap_or_default(),
},
- dkim: Report::parse(config, "dkim", &default_hostname, &sender_vars),
- spf: Report::parse(config, "spf", &default_hostname, &sender_vars),
- dmarc: Report::parse(config, "dmarc", &default_hostname, &sender_vars),
+ dkim: Report::parse(config, "dkim", &rcpt_vars),
+ spf: Report::parse(config, "spf", &sender_vars),
+ dmarc: Report::parse(config, "dmarc", &rcpt_vars),
dmarc_aggregate: AggregateReport::parse(
config,
"dmarc",
- &default_hostname,
- &sender_vars.with_constants::<AggregateFrequency>(),
+ &rcpt_vars.with_constants::<AggregateFrequency>(),
),
tls: AggregateReport::parse(
config,
"tls",
- &default_hostname,
- &rcpt_vars.with_constants::<AggregateFrequency>(),
+ &TokenMap::default()
+ .with_variables(SMTP_QUEUE_HOST_VARS)
+ .with_constants::<AggregateFrequency>(),
),
}
}
}
impl Report {
- pub fn parse(
- config: &mut Config,
- id: &str,
- default_hostname: &str,
- token_map: &TokenMap,
- ) -> Self {
+ pub fn parse(config: &mut Config, id: &str, token_map: &TokenMap) -> Self {
let mut report = Self {
- name: IfBlock::new(format!("{} Reporting", id.to_ascii_uppercase())),
- address: IfBlock::new(format!("MAILER-DAEMON@{default_hostname}")),
- subject: IfBlock::new(format!("{} Report", id.to_ascii_uppercase())),
- sign: Default::default(),
- send: Default::default(),
+ name: IfBlock::new::<()>(format!("report.{id}.from-name"), [], "'Report Subsystem'"),
+ address: IfBlock::new::<()>(
+ format!("report.{id}.from-address"),
+ [],
+ format!("'noreply-{id}@' + key_get('default', 'domain')"),
+ ),
+ subject: IfBlock::new::<()>(
+ format!("report.{id}.subject"),
+ [],
+ format!(
+ "'{} Authentication Failure Report'",
+ id.to_ascii_uppercase()
+ ),
+ ),
+ sign: IfBlock::new::<()>(
+ format!("report.{id}.sign"),
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ ),
+ send: IfBlock::new::<()>(format!("report.{id}.send"), [], "[1, 1d]"),
};
for (value, key) in [
(&mut report.name, "from-name"),
@@ -158,22 +146,37 @@ impl Report {
}
impl AggregateReport {
- pub fn parse(
- config: &mut Config,
- id: &str,
- default_hostname: &str,
- token_map: &TokenMap,
- ) -> Self {
- let rcpt_vars = TokenMap::default().with_smtp_variables(&[V_RECIPIENT_DOMAIN]);
+ pub fn parse(config: &mut Config, id: &str, token_map: &TokenMap) -> Self {
+ let rcpt_vars = TokenMap::default().with_variables(RCPT_DOMAIN_VARS);
let mut report = Self {
- name: IfBlock::new(format!("{} Aggregate Report", id.to_ascii_uppercase())),
- address: IfBlock::new(format!("noreply-{id}@{default_hostname}")),
- org_name: Default::default(),
- contact_info: Default::default(),
- send: IfBlock::new(AggregateFrequency::Never),
- sign: Default::default(),
- max_size: IfBlock::new(25 * 1024 * 1024),
+ name: IfBlock::new::<()>(
+ format!("report.{id}.aggregate.from-name"),
+ [],
+ format!("'{} Aggregate Report'", id.to_ascii_uppercase()),
+ ),
+ address: IfBlock::new::<()>(
+ format!("report.{id}.aggregate.from-address"),
+ [],
+ format!("'noreply-{id}@' + key_get('default', 'domain')"),
+ ),
+ org_name: IfBlock::new::<()>(
+ format!("report.{id}.aggregate.org-name"),
+ [],
+ "key_get('default', 'domain')",
+ ),
+ contact_info: IfBlock::empty(format!("report.{id}.aggregate.contact-info")),
+ send: IfBlock::new::<AggregateFrequency>(
+ format!("report.{id}.aggregate.send"),
+ [],
+ "daily",
+ ),
+ sign: IfBlock::new::<()>(
+ format!("report.{id}.aggregate.sign"),
+ [],
+ "['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
+ ),
+ max_size: IfBlock::new::<()>(format!("report.{id}.aggregate.max-size"), [], "26214400"),
};
for (value, key, token_map) in [
@@ -200,45 +203,7 @@ impl AggregateReport {
impl Default for ReportConfig {
fn default() -> Self {
- Self {
- submitter: IfBlock::new("localhost".to_string()),
- analysis: ReportAnalysis {
- addresses: Default::default(),
- forward: true,
- store: None,
- },
- dkim: Default::default(),
- spf: Default::default(),
- dmarc: Default::default(),
- dmarc_aggregate: Default::default(),
- tls: Default::default(),
- }
- }
-}
-
-impl Default for Report {
- fn default() -> Self {
- Self {
- name: IfBlock::new("Mail Delivery Subsystem".to_string()),
- address: IfBlock::new("MAILER-DAEMON@localhost".to_string()),
- subject: IfBlock::new("Report".to_string()),
- sign: Default::default(),
- send: Default::default(),
- }
- }
-}
-
-impl Default for AggregateReport {
- fn default() -> Self {
- Self {
- name: IfBlock::new("Reporting Subsystem".to_string()),
- address: IfBlock::new("no-replyN@localhost".to_string()),
- org_name: Default::default(),
- contact_info: Default::default(),
- send: IfBlock::new(AggregateFrequency::Never),
- sign: Default::default(),
- max_size: IfBlock::new(25 * 1024 * 1024),
- }
+ Self::parse(&mut Config::default())
}
}
diff --git a/crates/common/src/config/smtp/resolver.rs b/crates/common/src/config/smtp/resolver.rs
index 2638c9c4..d314c848 100644
--- a/crates/common/src/config/smtp/resolver.rs
+++ b/crates/common/src/config/smtp/resolver.rs
@@ -70,10 +70,7 @@ pub struct Policy {
impl Resolvers {
pub async fn parse(config: &mut Config) -> Self {
- let (resolver_config, mut opts) = match config
- .value_require("resolver.type")
- .unwrap_or("system")
- {
+ let (resolver_config, mut opts) = match config.value("resolver.type").unwrap_or("system") {
"cloudflare" => (ResolverConfig::cloudflare(), ResolverOpts::default()),
"cloudflare-tls" => (ResolverConfig::cloudflare_tls(), ResolverOpts::default()),
"quad9" => (ResolverConfig::quad9(), ResolverOpts::default()),
diff --git a/crates/common/src/config/smtp/session.rs b/crates/common/src/config/smtp/session.rs
index 4cf4f152..2be34db5 100644
--- a/crates/common/src/config/smtp/session.rs
+++ b/crates/common/src/config/smtp/session.rs
@@ -6,7 +6,10 @@ use std::{
use smtp_proto::*;
use utils::config::{utils::ParseValue, Config};
-use crate::expr::{if_block::IfBlock, tokenizer::TokenMap, Constant, ConstantValue, Variable};
+use crate::{
+ config::CONNECTION_VARS,
+ expr::{if_block::IfBlock, tokenizer::TokenMap, *},
+};
use self::throttle::parse_throttle;
@@ -37,6 +40,7 @@ pub struct SessionThrottle {
#[derive(Clone)]
pub struct Connect {
+ pub hostname: IfBlock,
pub script: IfBlock,
pub greeting: IfBlock,
}
@@ -67,7 +71,6 @@ pub struct Auth {
pub directory: IfBlock,
pub mechanisms: IfBlock,
pub require: IfBlock,
- pub allow_plain_text: IfBlock,
pub must_match_sender: IfBlock,
pub errors_max: IfBlock,
pub errors_wait: IfBlock,
@@ -160,33 +163,10 @@ pub enum MilterVersion {
impl SessionConfig {
pub fn parse(config: &mut Config) -> Self {
- let has_conn_vars =
- TokenMap::default().with_smtp_variables(&[V_LISTENER, V_REMOTE_IP, V_LOCAL_IP]);
- let has_ehlo_hars = TokenMap::default().with_smtp_variables(&[
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_HELO_DOMAIN,
- ]);
- let has_sender_vars = TokenMap::default().with_smtp_variables(&[
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_SENDER,
- V_SENDER_DOMAIN,
- V_AUTHENTICATED_AS,
- ]);
- let has_rcpt_vars = TokenMap::default().with_smtp_variables(&[
- V_SENDER,
- V_SENDER_DOMAIN,
- V_RECIPIENT,
- V_RECIPIENT_DOMAIN,
- V_AUTHENTICATED_AS,
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_HELO_DOMAIN,
- ]);
+ let has_conn_vars = TokenMap::default().with_variables(CONNECTION_VARS);
+ let has_ehlo_hars = TokenMap::default().with_variables(SMTP_EHLO_VARS);
+ let has_sender_vars = TokenMap::default().with_variables(SMTP_MAIL_FROM_VARS);
+ let has_rcpt_vars = TokenMap::default().with_variables(SMTP_RCPT_TO_VARS);
let mt_priority_vars = has_sender_vars.clone().with_constants::<MtPriority>();
let mechanisms_vars = has_ehlo_hars.clone().with_constants::<Mechanism>();
@@ -223,6 +203,11 @@ impl SessionConfig {
&has_conn_vars,
),
(
+ &mut session.connect.hostname,
+ "session.connect.hostname",
+ &has_conn_vars,
+ ),
+ (
&mut session.connect.greeting,
"session.connect.greeting",
&has_conn_vars,
@@ -318,11 +303,6 @@ impl SessionConfig {
&has_ehlo_hars,
),
(
- &mut session.auth.allow_plain_text,
- "session.auth.allow-plain-text",
- &has_ehlo_hars,
- ),
- (
&mut session.auth.must_match_sender,
"session.auth.must-match-sender",
&has_ehlo_hars,
@@ -438,18 +418,7 @@ impl SessionThrottle {
let all_throttles = parse_throttle(
config,
"session.throttle",
- &TokenMap::default().with_smtp_variables(&[
- V_SENDER,
- V_SENDER_DOMAIN,
- V_RECIPIENT,
- V_RECIPIENT_DOMAIN,
- V_AUTHENTICATED_AS,
- V_LISTENER,
- V_REMOTE_IP,
- V_LOCAL_IP,
- V_PRIORITY,
- V_HELO_DOMAIN,
- ]),
+ &TokenMap::default().with_variables(SMTP_RCPT_TO_VARS),
THROTTLE_LISTENER
| THROTTLE_REMOTE_IP
| THROTTLE_LOCAL_IP
@@ -500,7 +469,9 @@ fn parse_pipe(config: &mut Config, id: &str, token_map: &TokenMap) -> Option<Pip
command: IfBlock::try_parse(config, ("session.data.pipe", id, "command"), token_map)?,
arguments: IfBlock::try_parse(config, ("session.data.pipe", id, "arguments"), token_map)?,
timeout: IfBlock::try_parse(config, ("session.data.pipe", id, "timeout"), token_map)
- .unwrap_or_else(|| IfBlock::new(Duration::from_secs(30))),
+ .unwrap_or_else(|| {
+ IfBlock::new::<()>(format!("session.data.pipe.{id}.timeout"), [], "30s")
+ }),
})
}
@@ -511,7 +482,9 @@ fn parse_milter(config: &mut Config, id: &str, token_map: &TokenMap) -> Option<M
let port = config.property_require(("session.data.milter", id, "port"))?;
Some(Milter {
enable: IfBlock::try_parse(config, ("session.data.milter", id, "enable"), token_map)
- .unwrap_or_default(),
+ .unwrap_or_else(|| {
+ IfBlock::new::<()>(format!("session.data.milter.{id}.enable"), [], "false")
+ }),
addrs: format!("{}:{}", hostname, port)
.to_socket_addrs()
.map_err(|err| {
@@ -573,72 +546,165 @@ fn parse_milter(config: &mut Config, id: &str, token_map: &TokenMap) -> Option<M
impl Default for SessionConfig {
fn default() -> Self {
Self {
- timeout: IfBlock::new(Duration::from_secs(15 * 60)),
- duration: IfBlock::new(Duration::from_secs(5 * 60)),
- transfer_limit: IfBlock::new(250 * 1024 * 1024),
+ timeout: IfBlock::new::<()>("session.timeout", [], "5m"),
+ duration: IfBlock::new::<()>("session.duration", [], "10m"),
+ transfer_limit: IfBlock::new::<()>("session.transfer-limit", [], "262144000"),
throttle: SessionThrottle {
connect: Default::default(),
mail_from: Default::default(),
rcpt_to: Default::default(),
},
connect: Connect {
- script: Default::default(),
- greeting: IfBlock::new("Stalwart ESMTP at your service".to_string()),
+ hostname: IfBlock::new::<()>(
+ "server.connect.hostname",
+ [],
+ "key_get('default', 'hostname')",
+ ),
+ script: IfBlock::empty("session.connect.script"),
+ greeting: IfBlock::new::<()>(
+ "session.connect.greeting",
+ [],
+ "'Stalwart ESMTP at your service'",
+ ),
},
ehlo: Ehlo {
- script: Default::default(),
- require: IfBlock::new(true),
- reject_non_fqdn: IfBlock::new(true),
+ script: IfBlock::empty("session.ehlo.script"),
+ require: IfBlock::new::<()>("session.ehlo.require", [], "true"),
+ reject_non_fqdn: IfBlock::new::<()>(
+ "session.ehlo.reject-non-fqdn",
+ [("local_port == 25", "true")],
+ "false",
+ ),
},
auth: Auth {
- directory: Default::default(),
- mechanisms: Default::default(),
- require: IfBlock::new(false),
- allow_plain_text: IfBlock::new(false),
- must_match_sender: IfBlock::new(true),
- errors_max: IfBlock::new(3),
- errors_wait: IfBlock::new(Duration::from_secs(30)),
+ directory: IfBlock::new::<()>(
+ "session.auth.directory",
+ #[cfg(feature = "test_mode")]
+ [],
+ #[cfg(not(feature = "test_mode"))]
+ [("local_port != 25", "'*'")],
+ "false",
+ ),
+ mechanisms: IfBlock::new::<Mechanism>(
+ "session.auth.mechanisms",
+ [("local_port != 25 && is_tls", "[plain, login]")],
+ "false",
+ ),
+ require: IfBlock::new::<()>(
+ "session.auth.require",
+ #[cfg(feature = "test_mode")]
+ [],
+ #[cfg(not(feature = "test_mode"))]
+ [("local_port != 25", "'*'")],
+ "false",
+ ),
+ must_match_sender: IfBlock::new::<()>("session.auth.must-match-sender", [], "true"),
+ errors_max: IfBlock::new::<()>("session.auth.errors.total", [], "3"),
+ errors_wait: IfBlock::new::<()>("session.auth.errors.wait", [], "5s"),
},
mail: Mail {
- script: Default::default(),
- rewrite: Default::default(),
+ script: IfBlock::empty("session.mail.script"),
+ rewrite: IfBlock::empty("session.mail.rewrite"),
},
rcpt: Rcpt {
- script: Default::default(),
- relay: IfBlock::new(false),
- directory: Default::default(),
- rewrite: Default::default(),
- errors_max: IfBlock::new(10),
- errors_wait: IfBlock::new(Duration::from_secs(30)),
- max_recipients: IfBlock::new(100),
- catch_all: AddressMapping::Disable,
- subaddressing: AddressMapping::Disable,
+ script: IfBlock::empty("session.rcpt."),
+ relay: IfBlock::new::<()>(
+ "session.rcpt.relay",
+ [("!is_empty(authenticated_as)", "true")],
+ "false",
+ ),
+ directory: IfBlock::new::<()>(
+ "session.rcpt.directory",
+ [],
+ #[cfg(feature = "test_mode")]
+ "false",
+ #[cfg(not(feature = "test_mode"))]
+ "'*'",
+ ),
+ rewrite: IfBlock::empty("session.rcpt.rewrite"),
+ errors_max: IfBlock::new::<()>("session.rcpt.errors.total", [], "5"),
+ errors_wait: IfBlock::new::<()>("session.rcpt.errors.wait", [], "5s"),
+ max_recipients: IfBlock::new::<()>("session.rcpt.max-recipients", [], "100"),
+ catch_all: AddressMapping::Enable,
+ subaddressing: AddressMapping::Enable,
},
data: Data {
- script: Default::default(),
+ script: IfBlock::empty("session.data.script"),
pipe_commands: Default::default(),
milters: Default::default(),
- max_messages: IfBlock::new(10),
- max_message_size: IfBlock::new(25 * 1024 * 1024),
- max_received_headers: IfBlock::new(50),
- add_received: IfBlock::new(true),
- add_received_spf: IfBlock::new(true),
- add_return_path: IfBlock::new(true),
- add_auth_results: IfBlock::new(true),
- add_message_id: IfBlock::new(true),
- add_date: IfBlock::new(true),
+ max_messages: IfBlock::new::<()>("session.data.limits.messages", [], "10"),
+ max_message_size: IfBlock::new::<()>("session.data.limits.size", [], "104857600"),
+ max_received_headers: IfBlock::new::<()>(
+ "session.data.limits.received-headers",
+ [],
+ "50",
+ ),
+ add_received: IfBlock::new::<()>(
+ "session.data.add-headers.received",
+ [("local_port == 25", "true")],
+ "false",
+ ),
+ add_received_spf: IfBlock::new::<()>(
+ "session.data.add-headers.received-spf",
+ [("local_port == 25", "true")],
+ "false",
+ ),
+ add_return_path: IfBlock::new::<()>(
+ "session.data.add-headers.return-path",
+ [("local_port == 25", "true")],
+ "false",
+ ),
+ add_auth_results: IfBlock::new::<()>(
+ "session.data.add-headers.auth-results",
+ [("local_port == 25", "true")],
+ "false",
+ ),
+ add_message_id: IfBlock::new::<()>(
+ "session.data.add-headers.message-id",
+ [("local_port == 25", "true")],
+ "false",
+ ),
+ add_date: IfBlock::new::<()>(
+ "session.data.add-headers.date",
+ [("local_port == 25", "true")],
+ "false",
+ ),
},
extensions: Extensions {
- pipelining: IfBlock::new(true),
- chunking: IfBlock::new(true),
- requiretls: IfBlock::new(true),
- dsn: IfBlock::new(false),
- vrfy: IfBlock::new(false),
- expn: IfBlock::new(false),
- no_soliciting: IfBlock::new(false),
- future_release: Default::default(),
- deliver_by: Default::default(),
- mt_priority: Default::default(),
+ pipelining: IfBlock::new::<()>("session.extensions.pipelining", [], "true"),
+ chunking: IfBlock::new::<()>("session.extensions.chunking", [], "true"),
+ requiretls: IfBlock::new::<()>("session.extensions.requiretls", [], "true"),
+ dsn: IfBlock::new::<()>(
+ "session.extensions.dsn",
+ [("!is_empty(authenticated_as)", "true")],
+ "false",
+ ),
+ vrfy: IfBlock::new::<()>(
+ "session.extensions.vrfy",
+ [("!is_empty(authenticated_as)", "true")],
+ "false",
+ ),
+ expn: IfBlock::new::<()>(
+ "session.extensions.expn",
+ [("!is_empty(authenticated_as)", "true")],
+ "false",
+ ),
+ no_soliciting: IfBlock::new::<()>("session.extensions.no-soliciting", [], "''"),
+ future_release: IfBlock::new::<()>(
+ "session.extensions.future-release",
+ [("!is_empty(authenticated_as)", "7d")],
+ "false",
+ ),
+ deliver_by: IfBlock::new::<()>(
+ "session.extensions.deliver-by",
+ [("!is_empty(authenticated_as)", "15d")],
+ "false",
+ ),
+ mt_priority: IfBlock::new::<MtPriority>(
+ "session.extensions.mt-priority",
+ [("!is_empty(authenticated_as)", "mixer")],
+ "false",
+ ),
},
}
}
diff --git a/crates/common/src/config/smtp/throttle.rs b/crates/common/src/config/smtp/throttle.rs
index 0c48ad48..b8bfd835 100644
--- a/crates/common/src/config/smtp/throttle.rs
+++ b/crates/common/src/config/smtp/throttle.rs
@@ -96,11 +96,13 @@ fn parse_throttle_item(
.unwrap_or_default(),
keys,
concurrency: config
- .property::<u64>((prefix.as_str(), "concurrency"))
- .filter(|&v| v > 0),
+ .property::<Option<u64>>((prefix.as_str(), "concurrency"))
+ .filter(|&v| v.as_ref().map_or(false, |v| *v > 0))
+ .unwrap_or_default(),
rate: config
- .property::<Rate>((prefix.as_str(), "rate"))
- .filter(|v| v.requests > 0),
+ .property::<Option<Rate>>((prefix.as_str(), "rate"))
+ .filter(|v| v.as_ref().map_or(false, |r| r.requests > 0))
+ .unwrap_or_default(),
};
// Validate
diff --git a/crates/common/src/config/tracers.rs b/crates/common/src/config/tracers.rs
index 019f2009..05787bbe 100644
--- a/crates/common/src/config/tracers.rs
+++ b/crates/common/src/config/tracers.rs
@@ -5,6 +5,7 @@ use tracing::Level;
use tracing_appender::rolling::RollingFileAppender;
use utils::config::Config;
+#[derive(Debug)]
pub enum Tracer {
Stdout {
level: Level,
@@ -24,11 +25,13 @@ pub enum Tracer {
},
}
+#[derive(Debug)]
pub enum OtelTracer {
Gprc(TonicExporterBuilder),
Http(HttpExporterBuilder),
}
+#[derive(Debug)]
pub struct Tracers {
pub tracers: Vec<Tracer>,
}
diff --git a/crates/common/src/expr/if_block.rs b/crates/common/src/expr/if_block.rs
index ea98a401..c1196fa2 100644
--- a/crates/common/src/expr/if_block.rs
+++ b/crates/common/src/expr/if_block.rs
@@ -28,17 +28,17 @@ use crate::expr::{Constant, Expression};
use super::{
parser::ExpressionParser,
tokenizer::{TokenMap, Tokenizer},
- ExpressionItem,
+ ConstantValue, ExpressionItem,
};
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
#[cfg_attr(feature = "test_mode", derive(PartialEq, Eq))]
pub struct IfThen {
pub expr: Expression,
pub then: Expression,
}
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
#[cfg_attr(feature = "test_mode", derive(PartialEq, Eq))]
pub struct IfBlock {
pub key: String,
@@ -47,11 +47,35 @@ pub struct IfBlock {
}
impl IfBlock {
- pub fn new<T: Into<Constant>>(value: T) -> Self {
+ pub fn new<T: ConstantValue>(
+ key: impl Into<String>,
+ if_thens: impl IntoIterator<Item = (&'static str, &'static str)>,
+ default: impl AsRef<str>,
+ ) -> Self {
+ let token_map = TokenMap::default()
+ .with_all_variables()
+ .with_constants::<T>();
+
Self {
- key: String::new(),
- if_then: Vec::new(),
- default: Expression::from(value),
+ key: key.into(),
+ if_then: if_thens
+ .into_iter()
+ .map(|(if_, then)| IfThen {
+ expr: Expression::parse(&token_map, if_),
+ then: Expression::parse(&token_map, then),
+ })
+ .collect(),
+ default: Expression::parse(&token_map, default.as_ref()),
+ }
+ }
+
+ pub fn empty(key: impl Into<String>) -> Self {
+ Self {
+ key: key.into(),
+ if_then: Default::default(),
+ default: Expression {
+ items: Default::default(),
+ },
}
}
@@ -78,6 +102,12 @@ impl Expression {
None
}
}
+
+ fn parse(token_map: &TokenMap, expr: &str) -> Self {
+ ExpressionParser::new(Tokenizer::new(expr, token_map))
+ .parse()
+ .unwrap()
+ }
}
impl IfBlock {
@@ -91,7 +121,10 @@ impl IfBlock {
// Parse conditions
let mut if_block = IfBlock {
key,
- ..Default::default()
+ if_then: Default::default(),
+ default: Expression {
+ items: Default::default(),
+ },
};
// Try first with a single value
@@ -181,7 +214,6 @@ impl IfBlock {
}
if !found_if {
- config.new_missing_property(if_block.key);
None
} else if !found_then {
config.new_parse_error(
diff --git a/crates/common/src/expr/mod.rs b/crates/common/src/expr/mod.rs
index 0f16d8b1..4b8ea2fb 100644
--- a/crates/common/src/expr/mod.rs
+++ b/crates/common/src/expr/mod.rs
@@ -27,6 +27,42 @@ use std::{
time::Duration,
};
+pub const V_RECIPIENT: u32 = 0;
+pub const V_RECIPIENT_DOMAIN: u32 = 1;
+pub const V_SENDER: u32 = 2;
+pub const V_SENDER_DOMAIN: u32 = 3;
+pub const V_MX: u32 = 4;
+pub const V_HELO_DOMAIN: u32 = 5;
+pub const V_AUTHENTICATED_AS: u32 = 6;
+pub const V_LISTENER: u32 = 7;
+pub const V_REMOTE_IP: u32 = 8;
+pub const V_REMOTE_PORT: u32 = 9;
+pub const V_LOCAL_IP: u32 = 10;
+pub const V_LOCAL_PORT: u32 = 11;
+pub const V_PRIORITY: u32 = 12;
+pub const V_PROTOCOL: u32 = 13;
+pub const V_TLS: u32 = 14;
+pub const V_RECIPIENTS: u32 = 15;
+
+pub const VARIABLES_MAP: &[(&str, u32)] = &[
+ ("rcpt", V_RECIPIENT),
+ ("rcpt_domain", V_RECIPIENT_DOMAIN),
+ ("sender", V_SENDER),
+ ("sender_domain", V_SENDER_DOMAIN),
+ ("mx", V_MX),
+ ("helo_domain", V_HELO_DOMAIN),
+ ("authenticated_as", V_AUTHENTICATED_AS),
+ ("listener", V_LISTENER),
+ ("remote_ip", V_REMOTE_IP),
+ ("local_ip", V_LOCAL_IP),
+ ("priority", V_PRIORITY),
+ ("local_port", V_LOCAL_PORT),
+ ("remote_port", V_REMOTE_PORT),
+ ("protocol", V_PROTOCOL),
+ ("is_tls", V_TLS),
+ ("recipients", V_RECIPIENTS),
+];
+
use regex::Regex;
use utils::config::{utils::ParseValue, Rate};
@@ -67,7 +103,7 @@ pub enum Variable<'x> {
impl Default for Variable<'_> {
fn default() -> Self {
- Variable::Integer(0)
+ Variable::String("".into())
}
}
@@ -185,6 +221,12 @@ impl From<i32> for Variable<'_> {
}
}
+impl From<u16> for Variable<'_> {
+ fn from(value: u16) -> Self {
+ Variable::Integer(value as i64)
+ }
+}
+
impl From<i16> for Variable<'_> {
fn from(value: i16) -> Self {
Variable::Integer(value as i64)
@@ -292,12 +334,32 @@ impl PartialEq for Token {
impl Eq for Token {}
+pub struct NoConstants;
+
pub trait ConstantValue:
ParseValue + for<'x> TryFrom<Variable<'x>> + Into<Constant> + Sized
{
fn add_constants(token_map: &mut TokenMap);
}
+impl ConstantValue for () {
+ fn add_constants(_: &mut TokenMap) {}
+}
+
+impl From<()> for Constant {
+ fn from(_: ()) -> Self {
+ Constant::Integer(0)
+ }
+}
+
+impl<'x> TryFrom<Variable<'x>> for () {
+ type Error = ();
+
+ fn try_from(_: Variable<'x>) -> Result<Self, Self::Error> {
+ Ok(())
+ }
+}
+
impl ConstantValue for Duration {
fn add_constants(_: &mut TokenMap) {}
}
diff --git a/crates/common/src/expr/tokenizer.rs b/crates/common/src/expr/tokenizer.rs
index d60f4c89..9dfcac3f 100644
--- a/crates/common/src/expr/tokenizer.rs
+++ b/crates/common/src/expr/tokenizer.rs
@@ -29,7 +29,7 @@ use utils::config::utils::ParseValue;
use super::{
functions::{ASYNC_FUNCTIONS, FUNCTIONS},
- BinaryOperator, Constant, ConstantValue, Token, UnaryOperator,
+ *,
};
pub struct Tokenizer<'x> {
@@ -348,7 +348,37 @@ impl<'x> Tokenizer<'x> {
}
impl TokenMap {
- pub fn with_variables<I>(mut self, vars: I) -> Self
+ pub fn with_all_variables(self) -> Self {
+ self.with_variables(&[
+ V_RECIPIENT,
+ V_RECIPIENT_DOMAIN,
+ V_SENDER,
+ V_SENDER_DOMAIN,
+ V_MX,
+ V_HELO_DOMAIN,
+ V_AUTHENTICATED_AS,
+ V_LISTENER,
+ V_REMOTE_IP,
+ V_REMOTE_PORT,
+ V_LOCAL_IP,
+ V_LOCAL_PORT,
+ V_PRIORITY,
+ V_PROTOCOL,
+ V_TLS,
+ ])
+ }
+
+ pub fn with_variables(mut self, variables: &[u32]) -> Self {
+ for (name, idx) in VARIABLES_MAP {
+ if variables.contains(idx) {
+ self.tokens.insert(name, Token::Variable(*idx));
+ }
+ }
+
+ self
+ }
+
+ pub fn with_variables_map<I>(mut self, vars: I) -> Self
where
I: IntoIterator<Item = (&'static str, u32)>,
{
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index aed93bfa..83c155e7 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -42,7 +42,7 @@ pub static DAEMON_NAME: &str = concat!("Stalwart Mail Server v", env!("CARGO_PKG
pub type SharedCore = Arc<ArcSwap<Core>>;
-#[derive(Default, Clone)]
+#[derive(Clone, Default)]
pub struct Core {
pub storage: Storage,
pub sieve: Scripting,
@@ -56,7 +56,6 @@ pub struct Core {
#[derive(Clone)]
pub struct Network {
pub blocked_ips: BlockedIps,
- pub hostname: IfBlock,
pub url: IfBlock,
}
@@ -249,7 +248,7 @@ impl Tracers {
| Tracer::Otel { level, .. } => level,
};
- if tracer_level > level {
+ if tracer_level < level {
level = tracer_level;
}
}
diff --git a/crates/common/src/listener/acme/mod.rs b/crates/common/src/listener/acme/mod.rs
index 321f3554..73f463d2 100644
--- a/crates/common/src/listener/acme/mod.rs
+++ b/crates/common/src/listener/acme/mod.rs
@@ -49,6 +49,7 @@ pub struct AcmeProvider {
pub contact: Vec<String>,
renew_before: chrono::Duration,
account_key: ArcSwap<Vec<u8>>,
+ default: bool,
}
pub struct AcmeResolver {
@@ -73,6 +74,7 @@ impl AcmeProvider {
domains: Vec<String>,
contact: Vec<String>,
renew_before: Duration,
+ default: bool,
) -> utils::config::Result<Self> {
Ok(AcmeProvider {
id,
@@ -90,6 +92,7 @@ impl AcmeProvider {
renew_before: chrono::Duration::from_std(renew_before).unwrap(),
domains,
account_key: Default::default(),
+ default,
})
}
}
@@ -139,6 +142,7 @@ impl Clone for AcmeProvider {
contact: self.contact.clone(),
renew_before: self.renew_before,
account_key: ArcSwap::from_pointee(self.account_key.load().as_ref().clone()),
+ default: self.default,
}
}
}
diff --git a/crates/common/src/listener/acme/resolver.rs b/crates/common/src/listener/acme/resolver.rs
index 793bea9b..b5843a5f 100644
--- a/crates/common/src/listener/acme/resolver.rs
+++ b/crates/common/src/listener/acme/resolver.rs
@@ -45,6 +45,12 @@ impl Core {
cert.clone(),
);
}
+
+ // Add default certificate
+ if provider.default {
+ certificates.insert("*".to_string(), cert);
+ }
+
self.tls.certificates.store(certificates.into());
// Remove auth keys
diff --git a/crates/common/src/listener/blocked.rs b/crates/common/src/listener/blocked.rs
index 82e649f1..9da78d79 100644
--- a/crates/common/src/listener/blocked.rs
+++ b/crates/common/src/listener/blocked.rs
@@ -70,7 +70,7 @@ impl BlockedIps {
ip_addresses: RwLock::new(ip_addresses),
has_networks: !ip_networks.is_empty(),
ip_networks,
- limiter_rate: config.property::<Rate>("authentication.fail2ban"),
+ limiter_rate: config.property_or_default::<Rate>("authentication.fail2ban", "100/1d"),
}
}
}
diff --git a/crates/common/src/listener/listen.rs b/crates/common/src/listener/listen.rs
index ba810aeb..9f1426ad 100644
--- a/crates/common/src/listener/listen.rs
+++ b/crates/common/src/listener/listen.rs
@@ -79,7 +79,7 @@ impl Server {
tls = is_tls,
"Starting listener"
);
- let local_ip = listener.addr.ip();
+ let local_addr = listener.addr;
// Obtain TCP options
let opts = SocketOpts {
@@ -131,7 +131,7 @@ impl Server {
.proxied_address()
.map(|addr| addr.source)
.unwrap_or(remote_addr);
- if let Some(session) = instance.build_session(stream, local_ip, remote_addr, &core) {
+ if let Some(session) = instance.build_session(stream, local_addr, remote_addr, &core) {
// Spawn session
manager.spawn(session, is_tls, enable_acme);
}
@@ -146,7 +146,7 @@ impl Server {
}
}
});
- } else if let Some(session) = instance.build_session(stream, local_ip, remote_addr, &core) {
+ } else if let Some(session) = instance.build_session(stream, local_addr, remote_addr, &core) {
// Set socket options
opts.apply(&session.stream);
@@ -183,7 +183,7 @@ trait BuildSession {
fn build_session<T: SessionStream>(
&self,
stream: T,
- local_ip: IpAddr,
+ local_addr: SocketAddr,
remote_addr: SocketAddr,
core: &Core,
) -> Option<SessionData<T>>;
@@ -193,7 +193,7 @@ impl BuildSession for Arc<ServerInstance> {
fn build_session<T: SessionStream>(
&self,
stream: T,
- local_ip: IpAddr,
+ local_addr: SocketAddr,
remote_addr: SocketAddr,
core: &Core,
) -> Option<SessionData<T>> {
@@ -231,9 +231,11 @@ impl BuildSession for Arc<ServerInstance> {
remote.ip = remote_ip.to_string(),
remote.port = remote_port,
),
- local_ip,
+ local_ip: local_addr.ip(),
+ local_port: local_addr.port(),
remote_ip,
remote_port,
+ protocol: self.protocol,
instance: self.clone(),
}
.into()
diff --git a/crates/common/src/listener/mod.rs b/crates/common/src/listener/mod.rs
index d2c74ff2..c0af39ec 100644
--- a/crates/common/src/listener/mod.rs
+++ b/crates/common/src/listener/mod.rs
@@ -33,11 +33,8 @@ use tokio_rustls::{Accept, TlsAcceptor};
use utils::config::ipmask::IpAddrMask;
use crate::{
- config::{
- server::ServerProtocol,
- smtp::{V_LISTENER, V_LOCAL_IP, V_REMOTE_IP},
- },
- expr::functions::ResolveVariable,
+ config::server::ServerProtocol,
+ expr::{functions::ResolveVariable, *},
};
use self::limiter::{ConcurrencyLimiter, InFlight};
@@ -83,8 +80,10 @@ where
pub struct SessionData<T: SessionStream> {
pub stream: T,
pub local_ip: IpAddr,
+ pub local_port: u16,
pub remote_ip: IpAddr,
pub remote_port: u16,
+ pub protocol: ServerProtocol,
pub span: tracing::Span,
pub in_flight: InFlight,
pub instance: Arc<ServerInstance>,
@@ -117,8 +116,10 @@ pub trait SessionManager: Sync + Send + 'static + Clone {
let session = SessionData {
stream,
local_ip: session.local_ip,
+ local_port: session.local_port,
remote_ip: session.remote_ip,
remote_port: session.remote_port,
+ protocol: session.protocol,
span: session.span,
in_flight: session.in_flight,
instance: session.instance,
@@ -157,21 +158,16 @@ pub trait SessionManager: Sync + Send + 'static + Clone {
fn shutdown(&self) -> impl std::future::Future<Output = ()> + Send;
}
-impl ResolveVariable for ServerInstance {
- fn resolve_variable(&self, variable: u32) -> crate::expr::Variable<'_> {
- match variable {
- V_LISTENER => self.id.as_str().into(),
- _ => crate::expr::Variable::default(),
- }
- }
-}
-
impl<T: SessionStream> ResolveVariable for SessionData<T> {
fn resolve_variable(&self, variable: u32) -> crate::expr::Variable<'_> {
match variable {
V_REMOTE_IP => self.remote_ip.to_string().into(),
+ V_REMOTE_PORT => self.remote_port.into(),
V_LOCAL_IP => self.local_ip.to_string().into(),
+ V_LOCAL_PORT => self.local_port.into(),
V_LISTENER => self.instance.id.as_str().into(),
+ V_PROTOCOL => self.protocol.as_str().into(),
+ V_TLS => self.stream.is_tls().into(),
_ => crate::expr::Variable::default(),
}
}
@@ -180,7 +176,17 @@ impl<T: SessionStream> ResolveVariable for SessionData<T> {
impl Debug for TcpAcceptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::Tls { .. } => f.debug_tuple("Tls").finish(),
+ Self::Tls {
+ acme_config,
+ default_config,
+ implicit,
+ ..
+ } => f
+ .debug_struct("Tls")
+ .field("acme_config", acme_config)
+ .field("default_config", default_config)
+ .field("implicit", implicit)
+ .finish(),
Self::Plain => write!(f, "Plain"),
}
}
diff --git a/crates/common/src/scripts/plugins/bayes.rs b/crates/common/src/scripts/plugins/bayes.rs
index 3173a428..c4159763 100644
--- a/crates/common/src/scripts/plugins/bayes.rs
+++ b/crates/common/src/scripts/plugins/bayes.rs
@@ -224,6 +224,7 @@ pub fn exec_classify(ctx: PluginContext<'_>) -> Variable {
context = "sieve:bayes_classify",
event = "skip-classify",
reason = "Not enough training data",
+ min_learns = classifier.min_learns,
spam_learns = %spam_learns,
ham_learns = %ham_learns);
return Variable::default();
diff --git a/crates/directory/src/backend/smtp/config.rs b/crates/directory/src/backend/smtp/config.rs
index 5ef8cd45..48072628 100644
--- a/crates/directory/src/backend/smtp/config.rs
+++ b/crates/directory/src/backend/smtp/config.rs
@@ -57,7 +57,7 @@ impl SmtpDirectory {
is_lmtp,
credentials: None,
local_host: config
- .value("server.hostname")
+ .value("lookup.default.hostname")
.unwrap_or("[127.0.0.1]")
.to_string(),
say_ehlo: false,
diff --git a/crates/jmap/src/api/http.rs b/crates/jmap/src/api/http.rs
index c53aed0a..0b341fe1 100644
--- a/crates/jmap/src/api/http.rs
+++ b/crates/jmap/src/api/http.rs
@@ -24,8 +24,7 @@
use std::{net::IpAddr, sync::Arc};
use common::{
- config::smtp::{V_LISTENER, V_LOCAL_IP, V_REMOTE_IP},
- expr::functions::ResolveVariable,
+ expr::{functions::ResolveVariable, *},
listener::{ServerInstance, SessionData, SessionManager, SessionStream},
Core,
};
@@ -57,7 +56,9 @@ use super::{HtmlResponse, HttpRequest, HttpResponse, JmapSessionManager, JsonRes
pub struct HttpSessionData {
pub instance: Arc<ServerInstance>,
pub local_ip: IpAddr,
+ pub local_port: u16,
pub remote_ip: IpAddr,
+ pub remote_port: u16,
pub is_tls: bool,
}
@@ -366,7 +367,9 @@ impl JMAP {
HttpSessionData {
instance,
local_ip: session.local_ip,
+ local_port: session.local_port,
remote_ip,
+ remote_port: session.remote_port,
is_tls,
},
)
@@ -423,7 +426,11 @@ impl ResolveVariable for HttpSessionData {
fn resolve_variable(&self, variable: u32) -> common::expr::Variable<'_> {
match variable {
V_REMOTE_IP => self.remote_ip.to_string().into(),
+ V_REMOTE_PORT => self.remote_port.into(),
V_LOCAL_IP => self.local_ip.to_string().into(),
+ V_LOCAL_PORT => self.local_port.into(),
+ V_TLS => self.is_tls.into(),
+ V_PROTOCOL => if self.is_tls { "https" } else { "http" }.into(),
V_LISTENER => self.instance.id.as_str().into(),
_ => common::expr::Variable::default(),
}
@@ -434,7 +441,14 @@ impl HttpSessionData {
pub async fn resolve_url(&self, core: &Core) -> String {
core.eval_if(&core.network.url, self)
.await
- .unwrap_or_else(|| format!("http{}://localhost", if self.is_tls { "s" } else { "" }))
+ .unwrap_or_else(|| {
+ format!(
+ "http{}://{}:{}",
+ if self.is_tls { "s" } else { "" },
+ self.local_ip,
+ self.local_port
+ )
+ })
}
}
diff --git a/crates/smtp/src/core/mod.rs b/crates/smtp/src/core/mod.rs
index 5821452d..89f80d3f 100644
--- a/crates/smtp/src/core/mod.rs
+++ b/crates/smtp/src/core/mod.rs
@@ -149,6 +149,7 @@ pub struct Session<T: AsyncWrite + AsyncRead> {
pub struct SessionData {
pub local_ip: IpAddr,
pub local_ip_str: String,
+ pub local_port: u16,
pub remote_ip: IpAddr,
pub remote_ip_str: String,
pub remote_port: u16,
@@ -200,7 +201,6 @@ pub struct SessionParameters {
pub auth_require: bool,
pub auth_errors_max: usize,
pub auth_errors_wait: Duration,
- pub auth_plain_text: bool,
pub auth_match_sender: bool,
// Rcpt parameters
@@ -219,9 +219,10 @@ pub struct SessionParameters {
}
impl SessionData {
- pub fn new(local_ip: IpAddr, remote_ip: IpAddr, remote_port: u16) -> Self {
+ pub fn new(local_ip: IpAddr, local_port: u16, remote_ip: IpAddr, remote_port: u16) -> Self {
SessionData {
local_ip,
+ local_port,
remote_ip,
local_ip_str: local_ip.to_string(),
remote_ip_str: remote_ip.to_string(),
@@ -337,7 +338,6 @@ impl Session<common::listener::stream::NullIo> {
auth_require: Default::default(),
auth_errors_max: Default::default(),
auth_errors_wait: Default::default(),
- auth_plain_text: false,
rcpt_errors_max: Default::default(),
rcpt_errors_wait: Default::default(),
rcpt_max: Default::default(),
@@ -395,6 +395,7 @@ impl SessionData {
local_ip_str: "127.0.0.1".to_string(),
remote_ip_str: "127.0.0.1".to_string(),
remote_port: 0,
+ local_port: 0,
helo_domain: "localhost".into(),
mail_from,
rcpt_to,
diff --git a/crates/smtp/src/core/params.rs b/crates/smtp/src/core/params.rs
index 063e7bba..fff1ed76 100644
--- a/crates/smtp/src/core/params.rs
+++ b/crates/smtp/src/core/params.rs
@@ -23,12 +23,11 @@
use std::time::Duration;
-use common::config::smtp::auth::VerifyStrategy;
-use tokio::io::{AsyncRead, AsyncWrite};
+use common::{config::smtp::auth::VerifyStrategy, listener::SessionStream};
use super::Session;
-impl<T: AsyncRead + AsyncWrite> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn eval_session_params(&mut self) {
let c = &self.core.core.smtp.session;
self.data.bytes_left = self
@@ -111,12 +110,6 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
.eval_if(&ac.errors_wait, self)
.await
.unwrap_or_else(|| Duration::from_secs(30));
- self.params.auth_plain_text = self
- .core
- .core
- .eval_if(&ac.allow_plain_text, self)
- .await
- .unwrap_or(false);
self.params.auth_match_sender = self
.core
.core
diff --git a/crates/smtp/src/core/throttle.rs b/crates/smtp/src/core/throttle.rs
index 79ae1c88..6da347c2 100644
--- a/crates/smtp/src/core/throttle.rs
+++ b/crates/smtp/src/core/throttle.rs
@@ -23,11 +23,10 @@
use common::{
config::smtp::{queue::QueueQuota, *},
- expr::functions::ResolveVariable,
- listener::limiter::ConcurrencyLimiter,
+ expr::{functions::ResolveVariable, *},
+ listener::{limiter::ConcurrencyLimiter, SessionStream},
};
use dashmap::mapref::entry::Entry;
-use tokio::io::{AsyncRead, AsyncWrite};
use utils::config::Rate;
use std::hash::{BuildHasher, Hash, Hasher};
@@ -210,7 +209,7 @@ impl NewKey for Throttle {
}
}
-impl<T: AsyncRead + AsyncWrite> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn is_allowed(&mut self) -> bool {
let throttles = if !self.data.rcpt_to.is_empty() {
&self.core.core.smtp.session.throttle.rcpt_to
diff --git a/crates/smtp/src/inbound/auth.rs b/crates/smtp/src/inbound/auth.rs
index e466faac..a5ef6b22 100644
--- a/crates/smtp/src/inbound/auth.rs
+++ b/crates/smtp/src/inbound/auth.rs
@@ -21,11 +21,10 @@
* for more details.
*/
-use common::AuthResult;
+use common::{listener::SessionStream, AuthResult};
use mail_parser::decoders::base64::base64_decode;
use mail_send::Credentials;
use smtp_proto::{IntoString, AUTH_LOGIN, AUTH_OAUTHBEARER, AUTH_PLAIN, AUTH_XOAUTH2};
-use tokio::io::{AsyncRead, AsyncWrite};
use crate::core::Session;
@@ -65,7 +64,7 @@ impl SaslToken {
}
}
-impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn handle_sasl_response(
&mut self,
token: &mut SaslToken,
diff --git a/crates/smtp/src/inbound/ehlo.rs b/crates/smtp/src/inbound/ehlo.rs
index f8b11226..1d76eba5 100644
--- a/crates/smtp/src/inbound/ehlo.rs
+++ b/crates/smtp/src/inbound/ehlo.rs
@@ -200,12 +200,7 @@ impl<T: SessionStream> Session<T> {
.unwrap_or_default()
.into();
if response.auth_mechanisms != 0 {
- if !self.stream.is_tls() && !self.params.auth_plain_text {
- response.auth_mechanisms &= !(AUTH_PLAIN | AUTH_LOGIN);
- }
- if response.auth_mechanisms != 0 {
- response.capabilities |= EXT_AUTH;
- }
+ response.capabilities |= EXT_AUTH;
}
}
diff --git a/crates/smtp/src/inbound/session.rs b/crates/smtp/src/inbound/session.rs
index 720e8903..6e8f10b5 100644
--- a/crates/smtp/src/inbound/session.rs
+++ b/crates/smtp/src/inbound/session.rs
@@ -22,11 +22,8 @@
*/
use common::{
- config::{
- server::ServerProtocol,
- smtp::{session::Mechanism, *},
- },
- expr::{self, functions::ResolveVariable},
+ config::{server::ServerProtocol, smtp::session::Mechanism},
+ expr::{self, functions::ResolveVariable, *},
listener::SessionStream,
};
use smtp_proto::{
@@ -111,11 +108,6 @@ impl<T: SessionStream> Session<T> {
self.write(b"503 5.5.1 AUTH not allowed.\r\n").await?;
} else if !self.data.authenticated_as.is_empty() {
self.write(b"503 5.5.1 Already authenticated.\r\n").await?;
- } else if mechanism & (AUTH_LOGIN | AUTH_PLAIN) != 0
- && !self.stream.is_tls()
- && !self.params.auth_plain_text
- {
- self.write(b"503 5.5.1 Clear text authentication without TLS is forbidden.\r\n").await?;
} else if let Some(mut token) =
SaslToken::from_mechanism(mechanism & auth)
{
@@ -407,7 +399,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
}
}
-impl<T: AsyncRead + AsyncWrite> ResolveVariable for Session<T> {
+impl<T: SessionStream> ResolveVariable for Session<T> {
fn resolve_variable(&self, variable: u32) -> expr::Variable<'_> {
match variable {
V_RECIPIENT => self
@@ -424,6 +416,13 @@ impl<T: AsyncRead + AsyncWrite> ResolveVariable for Session<T> {
.map(|r| r.domain.as_str())
.unwrap_or_default()
.into(),
+ V_RECIPIENTS => self
+ .data
+ .rcpt_to
+ .iter()
+ .map(|r| Variable::String(r.address_lcase.as_str().into()))
+ .collect::<Vec<_>>()
+ .into(),
V_SENDER => self
.data
.mail_from
@@ -442,8 +441,12 @@ impl<T: AsyncRead + AsyncWrite> ResolveVariable for Session<T> {
V_AUTHENTICATED_AS => self.data.authenticated_as.as_str().into(),
V_LISTENER => self.instance.id.as_str().into(),
V_REMOTE_IP => self.data.remote_ip_str.as_str().into(),
+ V_REMOTE_PORT => self.data.remote_port.into(),
V_LOCAL_IP => self.data.local_ip_str.as_str().into(),
+ V_LOCAL_PORT => self.data.local_port.into(),
+ V_TLS => self.stream.is_tls().into(),
V_PRIORITY => self.data.priority.to_string().into(),
+ V_PROTOCOL => self.instance.protocol.as_str().into(),
_ => expr::Variable::default(),
}
}
diff --git a/crates/smtp/src/inbound/spawn.rs b/crates/smtp/src/inbound/spawn.rs
index 1e0f3993..1cdd0bde 100644
--- a/crates/smtp/src/inbound/spawn.rs
+++ b/crates/smtp/src/inbound/spawn.rs
@@ -46,7 +46,12 @@ impl SessionManager for SmtpSessionManager {
span: session.span,
stream: session.stream,
in_flight: vec![session.in_flight],
- data: SessionData::new(session.local_ip, session.remote_ip, session.remote_port),
+ data: SessionData::new(
+ session.local_ip,
+ session.local_port,
+ session.remote_ip,
+ session.remote_port,
+ ),
params: SessionParameters::default(),
};
@@ -89,11 +94,13 @@ impl<T: SessionStream> Session<T> {
pub async fn init_conn(&mut self) -> bool {
self.eval_session_params().await;
+ let config = &self.core.core.smtp.session.connect;
+
// Sieve filtering
if let Some(script) = self
.core
.core
- .eval_if::<String, _>(&self.core.core.smtp.session.connect.script, self)
+ .eval_if::<String, _>(&config.script, self)
.await
.and_then(|name| self.core.core.get_sieve_script(&name))
{
@@ -115,7 +122,7 @@ impl<T: SessionStream> Session<T> {
self.hostname = self
.core
.core
- .eval_if::<String, _>(&self.core.core.network.hostname, self)
+ .eval_if::<String, _>(&config.hostname, self)
.await
.unwrap_or_default();
if self.hostname.is_empty() {
@@ -124,14 +131,14 @@ impl<T: SessionStream> Session<T> {
event = "hostname",
"No hostname configured, using 'localhost'."
);
- self.hostname = "locahost".to_string();
+ self.hostname = "localhost".to_string();
}
// Obtain greeting
let greeting = self
.core
.core
- .eval_if::<String, _>(&self.core.core.smtp.session.connect.greeting, self)
+ .eval_if::<String, _>(&config.greeting, self)
.await
.filter(|g| !g.is_empty())
.map(|g| format!("220 {}\r\n", g))
diff --git a/crates/smtp/src/inbound/vrfy.rs b/crates/smtp/src/inbound/vrfy.rs
index 4cf024b9..f846b21b 100644
--- a/crates/smtp/src/inbound/vrfy.rs
+++ b/crates/smtp/src/inbound/vrfy.rs
@@ -21,13 +21,13 @@
* for more details.
*/
+use common::listener::SessionStream;
use directory::DirectoryError;
-use tokio::io::{AsyncRead, AsyncWrite};
use crate::core::Session;
use std::fmt::Write;
-impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn handle_vrfy(&mut self, address: String) -> Result<(), ()> {
match self
.core
diff --git a/crates/smtp/src/outbound/delivery.rs b/crates/smtp/src/outbound/delivery.rs
index a2e1701c..f046b37f 100644
--- a/crates/smtp/src/outbound/delivery.rs
+++ b/crates/smtp/src/outbound/delivery.rs
@@ -726,7 +726,15 @@ impl DeliveryAttempt {
.core
.eval_if::<String, _>(&queue_config.hostname, &envelope)
.await
- .unwrap_or_else(|| "localhost".to_string());
+ .filter(|s| !s.is_empty())
+ .unwrap_or_else(|| {
+ tracing::warn!(parent: &span,
+ context = "queue",
+ event = "ehlo",
+ "No outbound hostname configured, using 'local.host'."
+ );
+ "local.host".to_string()
+ });
let params = SessionParams {
span: &span,
core: &core,
diff --git a/crates/smtp/src/outbound/lookup.rs b/crates/smtp/src/outbound/lookup.rs
index c8099d52..a2f70f72 100644
--- a/crates/smtp/src/outbound/lookup.rs
+++ b/crates/smtp/src/outbound/lookup.rs
@@ -26,7 +26,7 @@ use std::{
sync::Arc,
};
-use common::{config::smtp::V_MX, expr::functions::ResolveVariable};
+use common::expr::{functions::ResolveVariable, V_MX};
use mail_auth::{IpLookupStrategy, MX};
use rand::{seq::SliceRandom, Rng};
diff --git a/crates/smtp/src/queue/mod.rs b/crates/smtp/src/queue/mod.rs
index 4fa0d864..b6bcc2ea 100644
--- a/crates/smtp/src/queue/mod.rs
+++ b/crates/smtp/src/queue/mod.rs
@@ -28,8 +28,7 @@ use std::{
};
use common::{
- config::smtp::*,
- expr::{self, functions::ResolveVariable},
+ expr::{self, functions::ResolveVariable, *},
listener::limiter::{ConcurrencyLimiter, InFlight},
};
use serde::{Deserialize, Serialize};
@@ -245,6 +244,13 @@ impl<'x> ResolveVariable for QueueEnvelope<'x> {
V_SENDER => self.message.return_path_lcase.as_str().into(),
V_SENDER_DOMAIN => self.message.return_path_domain.as_str().into(),
V_RECIPIENT_DOMAIN => self.domain.into(),
+ V_RECIPIENTS => self
+ .message
+ .recipients
+ .iter()
+ .map(|r| Variable::from(r.address_lcase.as_str()))
+ .collect::<Vec<_>>()
+ .into(),
V_MX => self.mx.into(),
V_PRIORITY => self.message.priority.into(),
V_REMOTE_IP => self.remote_ip.to_string().into(),
@@ -259,6 +265,12 @@ impl ResolveVariable for Message {
match variable {
V_SENDER => self.return_path_lcase.as_str().into(),
V_SENDER_DOMAIN => self.return_path_domain.as_str().into(),
+ V_RECIPIENTS => self
+ .recipients
+ .iter()
+ .map(|r| Variable::from(r.address_lcase.as_str()))
+ .collect::<Vec<_>>()
+ .into(),
V_PRIORITY => self.priority.into(),
_ => "".into(),
}
diff --git a/crates/smtp/src/reporting/dkim.rs b/crates/smtp/src/reporting/dkim.rs
index cb4ffa00..5aaa2c40 100644
--- a/crates/smtp/src/reporting/dkim.rs
+++ b/crates/smtp/src/reporting/dkim.rs
@@ -21,15 +21,15 @@
* for more details.
*/
+use common::listener::SessionStream;
use mail_auth::{
common::verify::VerifySignature, AuthenticatedMessage, AuthenticationResults, DkimOutput,
};
-use tokio::io::{AsyncRead, AsyncWrite};
use utils::config::Rate;
use crate::core::Session;
-impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn send_dkim_report(
&self,
rcpt: &str,
diff --git a/crates/smtp/src/reporting/dmarc.rs b/crates/smtp/src/reporting/dmarc.rs
index 360f1020..d9e468fa 100644
--- a/crates/smtp/src/reporting/dmarc.rs
+++ b/crates/smtp/src/reporting/dmarc.rs
@@ -24,7 +24,7 @@
use std::collections::hash_map::Entry;
use ahash::AHashMap;
-use common::config::smtp::report::AggregateFrequency;
+use common::{config::smtp::report::AggregateFrequency, listener::SessionStream};
use mail_auth::{
common::verify::VerifySignature,
dmarc::{self, URI},
@@ -36,7 +36,6 @@ use store::{
write::{now, BatchBuilder, Bincode, QueueClass, ReportEvent, ValueClass},
Deserialize, IterateParams, Serialize, ValueKey,
};
-use tokio::io::{AsyncRead, AsyncWrite};
use utils::config::Rate;
use crate::{
@@ -53,7 +52,7 @@ pub struct DmarcFormat {
pub records: Vec<Record>,
}
-impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
+impl<T: SessionStream> Session<T> {
#[allow(clippy::too_many_arguments)]
pub async fn send_dmarc_report(
&self,
diff --git a/crates/smtp/src/reporting/spf.rs b/crates/smtp/src/reporting/spf.rs
index 196bb43e..8ad915cd 100644
--- a/crates/smtp/src/reporting/spf.rs
+++ b/crates/smtp/src/reporting/spf.rs
@@ -21,13 +21,13 @@
* for more details.
*/
+use common::listener::SessionStream;
use mail_auth::{report::AuthFailureType, AuthenticationResults, SpfOutput};
-use tokio::io::{AsyncRead, AsyncWrite};
use utils::config::Rate;
use crate::core::Session;
-impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
+impl<T: SessionStream> Session<T> {
pub async fn send_spf_report(
&self,
rcpt: &str,
diff --git a/crates/smtp/src/scripts/event_loop.rs b/crates/smtp/src/scripts/event_loop.rs
index 7d0e4563..3130a625 100644
--- a/crates/smtp/src/scripts/event_loop.rs
+++ b/crates/smtp/src/scripts/event_loop.rs
@@ -55,8 +55,8 @@ impl SMTP {
.filter(params.message.as_deref().map_or(b"", |m| &m[..]))
.with_vars_env(params.variables)
.with_envelope_list(params.envelope)
- .with_user_address(&self.core.sieve.from_addr)
- .with_user_full_name(&self.core.sieve.from_name);
+ .with_user_address(&params.from_addr)
+ .with_user_full_name(&params.from_name);
let mut input = Input::script("__script", script);
let mut messages: Vec<Vec<u8>> = Vec::new();
@@ -149,10 +149,10 @@ impl SMTP {
message_id,
} => {
// Build message
- let return_path_lcase = self.core.sieve.return_path.to_lowercase();
+ let return_path_lcase = params.return_path.to_lowercase();
let return_path_domain = return_path_lcase.domain_part().to_string();
let mut message = self.new_message(
- self.core.sieve.return_path.clone(),
+ params.return_path.clone(),
return_path_lcase,
return_path_domain,
);
@@ -265,9 +265,9 @@ impl SMTP {
instance.message().raw_message().into()
};
if let Some(raw_message) = raw_message {
- let headers = if !self.core.sieve.sign.is_empty() {
+ let headers = if !params.sign.is_empty() {
let mut headers = Vec::new();
- for dkim in &self.core.sieve.sign {
+ for dkim in &params.sign {
if let Some(dkim) = self.core.get_dkim_signer(dkim) {
match dkim.sign(raw_message) {
Ok(signature) => {
diff --git a/crates/smtp/src/scripts/exec.rs b/crates/smtp/src/scripts/exec.rs
index 11a8003f..10f6086b 100644
--- a/crates/smtp/src/scripts/exec.rs
+++ b/crates/smtp/src/scripts/exec.rs
@@ -139,6 +139,7 @@ impl<T: SessionStream> Session<T> {
pub async fn run_script(&self, script: Arc<Sieve>, params: ScriptParameters) -> ScriptResult {
let core = self.core.clone();
let span = self.span.clone();
+ let params = params.with_envelope(&self.core.core, self).await;
let handle = Handle::current();
self.core
diff --git a/crates/smtp/src/scripts/mod.rs b/crates/smtp/src/scripts/mod.rs
index e8e5ab94..43e65b87 100644
--- a/crates/smtp/src/scripts/mod.rs
+++ b/crates/smtp/src/scripts/mod.rs
@@ -24,7 +24,7 @@
use std::{borrow::Cow, sync::Arc};
use ahash::AHashMap;
-use common::scripts::ScriptModification;
+use common::{expr::functions::ResolveVariable, scripts::ScriptModification, Core};
use sieve::{runtime::Variable, Envelope};
pub mod envelope;
@@ -48,6 +48,10 @@ pub struct ScriptParameters {
message: Option<Arc<Vec<u8>>>,
variables: AHashMap<Cow<'static, str>, Variable>,
envelope: Vec<(Envelope, Variable)>,
+ from_addr: String,
+ from_name: String,
+ return_path: String,
+ sign: Vec<String>,
#[cfg(feature = "test_mode")]
expected_variables: Option<AHashMap<String, Variable>>,
}
@@ -60,9 +64,29 @@ impl ScriptParameters {
message: None,
#[cfg(feature = "test_mode")]
expected_variables: None,
+ from_addr: Default::default(),
+ from_name: Default::default(),
+ return_path: Default::default(),
+ sign: Default::default(),
}
}
+ pub async fn with_envelope(mut self, core: &Core, vars: &impl ResolveVariable) -> Self {
+ for (variable, expr) in [
+ (&mut self.from_addr, &core.sieve.from_addr),
+ (&mut self.from_name, &core.sieve.from_name),
+ (&mut self.return_path, &core.sieve.return_path),
+ ] {
+ if let Some(value) = core.eval_if(expr, vars).await {
+ *variable = value;
+ }
+ }
+ if let Some(value) = core.eval_if(&core.sieve.sign, vars).await {
+ self.sign = value;
+ }
+ self
+ }
+
pub fn with_message(self, message: Arc<Vec<u8>>) -> Self {
Self {
message: message.into(),
diff --git a/crates/store/src/backend/foundationdb/main.rs b/crates/store/src/backend/foundationdb/main.rs
index 871f2836..54c9cceb 100644
--- a/crates/store/src/backend/foundationdb/main.rs
+++ b/crates/store/src/backend/foundationdb/main.rs
@@ -60,7 +60,10 @@ impl FdbStore {
})
.ok()?;
- if let Some(value) = config.property::<Duration>((&prefix, "transaction.timeout")) {
+ if let Some(value) = config
+ .property::<Option<Duration>>((&prefix, "transaction.timeout"))
+ .unwrap_or_default()
+ {
db.set_option(DatabaseOption::TransactionTimeout(value.as_millis() as i32))
.map_err(|err| {
config.new_build_error(
@@ -80,7 +83,10 @@ impl FdbStore {
})
.ok()?;
}
- if let Some(value) = config.property::<Duration>((&prefix, "transaction.max-retry-delay")) {
+ if let Some(value) = config
+ .property::<Option<Duration>>((&prefix, "transaction.max-retry-delay"))
+ .unwrap_or_default()
+ {
db.set_option(DatabaseOption::TransactionMaxRetryDelay(
value.as_millis() as i32
))
diff --git a/crates/store/src/backend/memory/mod.rs b/crates/store/src/backend/memory/mod.rs
index 9da1333d..ccd43d09 100644
--- a/crates/store/src/backend/memory/mod.rs
+++ b/crates/store/src/backend/memory/mod.rs
@@ -93,7 +93,13 @@ impl Stores {
}
if has_others {
- Value::Text(value.to_string().into())
+ if value == "true" {
+ Value::Integer(1.into())
+ } else if value == "false" {
+ Value::Integer(0.into())
+ } else {
+ Value::Text(value.to_string().into())
+ }
} else if has_floats {
value
.parse()
diff --git a/crates/store/src/backend/mysql/main.rs b/crates/store/src/backend/mysql/main.rs
index 27a2cd5a..e41aab1e 100644
--- a/crates/store/src/backend/mysql/main.rs
+++ b/crates/store/src/backend/mysql/main.rs
@@ -49,7 +49,8 @@ impl MysqlStore {
.max_allowed_packet(config.property((&prefix, "max-allowed-packet")))
.wait_timeout(
config
- .property::<Duration>((&prefix, "timeout"))
+ .property::<Option<Duration>>((&prefix, "timeout"))
+ .unwrap_or_default()
.map(|t| t.as_secs() as usize),
);
if let Some(port) = config.property((&prefix, "port")) {
@@ -68,10 +69,16 @@ impl MysqlStore {
// Configure connection pool
let mut pool_min = PoolConstraints::default().min();
let mut pool_max = PoolConstraints::default().max();
- if let Some(n_size) = config.property::<usize>((&prefix, "pool.min-connections")) {
+ if let Some(n_size) = config
+ .property::<usize>((&prefix, "pool.min-connections"))
+ .filter(|&n| n > 0)
+ {
pool_min = n_size;
}
- if let Some(n_size) = config.property::<usize>((&prefix, "pool.max-connections")) {
+ if let Some(n_size) = config
+ .property::<usize>((&prefix, "pool.max-connections"))
+ .filter(|&n| n > 0)
+ {
pool_max = n_size;
}
opts = opts.pool_opts(
diff --git a/crates/store/src/backend/postgres/main.rs b/crates/store/src/backend/postgres/main.rs
index fc2ef964..d1b8c422 100644
--- a/crates/store/src/backend/postgres/main.rs
+++ b/crates/store/src/backend/postgres/main.rs
@@ -21,6 +21,8 @@
* for more details.
*/
+use std::time::Duration;
+
use crate::{
backend::postgres::tls::MakeRustlsConnect, SUBSPACE_BITMAPS, SUBSPACE_BLOBS, SUBSPACE_COUNTERS,
SUBSPACE_INDEXES, SUBSPACE_LOGS, SUBSPACE_VALUES,
@@ -46,7 +48,9 @@ impl PostgresStore {
cfg.user = config.value((&prefix, "user")).map(|s| s.to_string());
cfg.password = config.value((&prefix, "password")).map(|s| s.to_string());
cfg.port = config.property((&prefix, "port"));
- cfg.connect_timeout = config.property((&prefix, "timeout"));
+ cfg.connect_timeout = config
+ .property::<Option<Duration>>((&prefix, "timeout"))
+ .unwrap_or_default();
cfg.manager = Some(ManagerConfig {
recycling_method: RecyclingMethod::Fast,
});
diff --git a/crates/store/src/backend/redis/mod.rs b/crates/store/src/backend/redis/mod.rs
index 9d80aad4..607b827a 100644
--- a/crates/store/src/backend/redis/mod.rs
+++ b/crates/store/src/backend/redis/mod.rs
@@ -67,23 +67,81 @@ impl RedisStore {
return None;
}
- Some(match config.value_require((&prefix, "redis-type"))? {
- "single" => {
- let client = Client::open(urls.into_iter().next().unwrap())
- .map_err(|err| {
- config.new_build_error(
- prefix.as_str(),
- format!("Failed to open Redis client: {err:?}"),
- )
- })
- .ok()?;
- let timeout = config
- .property_or_default((&prefix, "timeout"), "10s")
- .unwrap_or_else(|| Duration::from_secs(10));
-
- Self {
- pool: RedisPool::Single(
- build_pool(config, &prefix, RedisConnectionManager { client, timeout })
+ Some(
+ match config.value((&prefix, "redis-type")).unwrap_or("single") {
+ "single" => {
+ let client = Client::open(urls.into_iter().next().unwrap())
+ .map_err(|err| {
+ config.new_build_error(
+ prefix.as_str(),
+ format!("Failed to open Redis client: {err:?}"),
+ )
+ })
+ .ok()?;
+ let timeout = config
+ .property_or_default((&prefix, "timeout"), "10s")
+ .unwrap_or_default();
+
+ Self {
+ pool: RedisPool::Single(
+ build_pool(config, &prefix, RedisConnectionManager { client, timeout })
+ .map_err(|err| {
+ config.new_build_error(
+ prefix.as_str(),
+ format!("Failed to build Redis pool: {err:?}"),
+ )
+ })
+ .ok()?,
+ ),
+ }
+ }
+ "cluster" => {
+ let mut builder = ClusterClientBuilder::new(urls.into_iter());
+ if let Some(value) = config.property((&prefix, "user")) {
+ builder = builder.username(value);
+ }
+ if let Some(value) = config.property((&prefix, "password")) {
+ builder = builder.password(value);
+ }
+ if let Some(value) = config.property((&prefix, "retry.total")) {
+ builder = builder.retries(value);
+ }
+ if let Some(value) = config
+ .property::<Option<Duration>>((&prefix, "retry.max-wait"))
+ .unwrap_or_default()
+ {
+ builder = builder.max_retry_wait(value.as_millis() as u64);
+ }
+ if let Some(value) = config
+ .property::<Option<Duration>>((&prefix, "retry.min-wait"))
+ .unwrap_or_default()
+ {
+ builder = builder.min_retry_wait(value.as_millis() as u64);
+ }
+ if let Some(true) = config.property::<bool>((&prefix, "read-from-replicas")) {
+ builder = builder.read_from_replicas();
+ }
+
+ let client = builder
+ .build()
+ .map_err(|err| {
+ config.new_build_error(
+ prefix.as_str(),
+ format!("Failed to open Redis client: {err:?}"),
+ )
+ })
+ .ok()?;
+ let timeout = config
+ .property_or_default::<Duration>((&prefix, "timeout"), "10s")
+ .unwrap_or_else(|| Duration::from_secs(10));
+
+ Self {
+ pool: RedisPool::Cluster(
+ build_pool(
+ config,
+ &prefix,
+ RedisClusterConnectionManager { client, timeout },
+ )
.map_err(|err| {
config.new_build_error(
prefix.as_str(),
@@ -91,66 +149,16 @@ impl RedisStore {
)
})
.ok()?,
- ),
- }
- }
- "cluster" => {
- let mut builder = ClusterClientBuilder::new(urls.into_iter());
- if let Some(value) = config.property((&prefix, "user")) {
- builder = builder.username(value);
- }
- if let Some(value) = config.property((&prefix, "password")) {
- builder = builder.password(value);
- }
- if let Some(value) = config.property((&prefix, "retry.total")) {
- builder = builder.retries(value);
+ ),
+ }
}
- if let Some(value) = config.property::<Duration>((&prefix, "retry.max-wait")) {
- builder = builder.max_retry_wait(value.as_millis() as u64);
+ invalid => {
+ let err = format!("Invalid Redis type {invalid:?}");
+ config.new_parse_error((&prefix, "redis-type"), err);
+ return None;
}
- if let Some(value) = config.property::<Duration>((&prefix, "retry.min-wait")) {
- builder = builder.min_retry_wait(value.as_millis() as u64);
- }
- if let Some(true) = config.property::<bool>((&prefix, "read-from-replicas")) {
- builder = builder.read_from_replicas();
- }
-
- let client = builder
- .build()
- .map_err(|err| {
- config.new_build_error(
- prefix.as_str(),
- format!("Failed to open Redis client: {err:?}"),
- )
- })
- .ok()?;
- let timeout = config
- .property_or_default((&prefix, "timeout"), "10s")
- .unwrap_or_else(|| Duration::from_secs(10));
-
- Self {
- pool: RedisPool::Cluster(
- build_pool(
- config,
- &prefix,
- RedisClusterConnectionManager { client, timeout },
- )
- .map_err(|err| {
- config.new_build_error(
- prefix.as_str(),
- format!("Failed to build Redis pool: {err:?}"),
- )
- })
- .ok()?,
- ),
- }
- }
- invalid => {
- let err = format!("Invalid Redis type {invalid:?}");
- config.new_parse_error((&prefix, "redis-type"), err);
- return None;
- }
- })
+ },
+ )
}
}
@@ -168,12 +176,19 @@ fn build_pool<M: Manager>(
)
.create_timeout(
config
- .property_or_default::<Duration>((prefix, "pool.create-timeout"), "30s")
- .unwrap_or_else(|| Duration::from_secs(30))
- .into(),
+ .property_or_default::<Option<Duration>>((prefix, "pool.create-timeout"), "30s")
+ .unwrap_or_default(),
+ )
+ .wait_timeout(
+ config
+ .property_or_default::<Option<Duration>>((prefix, "pool.wait-timeout"), "30s")
+ .unwrap_or_default(),
+ )
+ .recycle_timeout(
+ config
+ .property_or_default::<Option<Duration>>((prefix, "pool.recycle-timeout"), "30s")
+ .unwrap_or_default(),
)
- .wait_timeout(config.property_or_default((prefix, "pool.wait-timeout"), "30s"))
- .recycle_timeout(config.property_or_default((prefix, "pool.recycle-timeout"), "30s"))
.build()
.map_err(|err| {
format!(
diff --git a/crates/utils/src/config/parser.rs b/crates/utils/src/config/parser.rs
index c0bfbe57..a6d9782c 100644
--- a/crates/utils/src/config/parser.rs
+++ b/crates/utils/src/config/parser.rs
@@ -207,17 +207,18 @@ impl<'x, 'y> TomlParser<'x, 'y> {
#[allow(clippy::while_let_on_iterator)]
fn key(&mut self, mut key: String, in_curly: bool) -> Result<(String, char)> {
+ let start_key_len = key.len();
while let Some(ch) = self.iter.next() {
match ch {
'=' => {
- if !key.is_empty() {
+ if start_key_len != key.len() {
return Ok((key, ch));
} else {
return Err(format!("Empty key at line: {}", self.line));
}
}
',' | '}' if in_curly => {
- if !key.is_empty() {
+ if start_key_len != key.len() {
return Ok((key, ch));
} else {
return Err(format!("Empty key at line: {}", self.line));
@@ -236,7 +237,7 @@ impl<'x, 'y> TomlParser<'x, 'y> {
}
'\n' => {
return Err(format!(
- "Unexpected end of line at line: {}",
+ "Unexpected end of line while parsing quoted key at line: {}",
self.line
));
}
@@ -249,7 +250,14 @@ impl<'x, 'y> TomlParser<'x, 'y> {
}
' ' | '\t' | '\r' => (),
'\n' => {
- return Err(format!("Unexpected end of line at line: {}", self.line));
+ if start_key_len == key.len() {
+ self.line += 1;
+ } else {
+ return Err(format!(
+ "Unexpected end of line while parsing key {:?} at line: {}",
+ key, self.line
+ ));
+ }
}
_ => {
return Err(format!(
diff --git a/crates/utils/src/config/utils.rs b/crates/utils/src/config/utils.rs
index bd850f2a..b310f0b1 100644
--- a/crates/utils/src/config/utils.rs
+++ b/crates/utils/src/config/utils.rs
@@ -60,13 +60,7 @@ impl Config {
let key = key.as_key();
let value = match self.keys.get(&key) {
Some(value) => value.as_str(),
- None => {
- self.warnings.insert(
- key.clone(),
- ConfigWarning::AppliedDefault(default.to_string()),
- );
- default
- }
+ None => default,
};
match T::parse_value(value) {
Ok(value) => Some(value),
@@ -80,16 +74,13 @@ impl Config {
pub fn property_or_else<T: ParseValue>(
&mut self,
key: impl AsKey,
- default: impl AsKey,
+ or_else: impl AsKey,
+ default: &str,
) -> Option<T> {
let key = key.as_key();
- let value = match self.value_or_else(key.as_str(), default.clone()) {
+ let value = match self.value_or_else(key.as_str(), or_else.clone()) {
Some(value) => value,
- None => {
- self.warnings
- .insert(default.as_key(), ConfigWarning::Missing);
- return None;
- }
+ None => default,
};
match T::parse_value(value) {
@@ -216,10 +207,10 @@ impl Config {
}
}
- pub fn value_or_else(&self, key: impl AsKey, default: impl AsKey) -> Option<&str> {
+ pub fn value_or_else(&self, key: impl AsKey, or_else: impl AsKey) -> Option<&str> {
self.keys
.get(&key.as_key())
- .or_else(|| self.keys.get(&default.as_key()))
+ .or_else(|| self.keys.get(&or_else.as_key()))
.map(|s| s.as_str())
}
@@ -260,17 +251,6 @@ impl Config {
self.keys.remove(key)
}
- pub fn value_or_warn(&mut self, key: impl AsKey) -> Option<&str> {
- let key = key.as_key();
- match self.keys.get(&key) {
- Some(value) => Some(value.as_str()),
- None => {
- self.warnings.insert(key, ConfigWarning::Missing);
- None
- }
- }
- }
-
pub fn new_parse_error(&mut self, key: impl AsKey, details: impl Into<String>) {
self.errors
.insert(key.as_key(), ConfigError::Parse(details.into()));
@@ -523,6 +503,12 @@ impl ParseValue for Rate {
}
}
+impl ParseValue for () {
+ fn parse_value(_: &str) -> super::Result<Self> {
+ Ok(())
+ }
+}
+
pub trait AsKey: Clone {
fn as_key(&self) -> String;
fn as_prefix(&self) -> String;
diff --git a/crates/utils/src/suffixlist.rs b/crates/utils/src/suffixlist.rs
index fddb8a00..0098e387 100644
--- a/crates/utils/src/suffixlist.rs
+++ b/crates/utils/src/suffixlist.rs
@@ -66,11 +66,18 @@ impl From<&str> for PublicSuffix {
impl PublicSuffix {
#[allow(unused_variables)]
pub async fn parse(config: &mut Config, key: &str) -> PublicSuffix {
- let values = config
+ let mut values = config
.values(key)
.map(|(_, s)| s.to_string())
.collect::<Vec<_>>();
- let has_values = !values.is_empty();
+ if values.is_empty() {
+ values = vec![
+ "https://publicsuffix.org/list/public_suffix_list.dat".to_string(),
+ "https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat"
+ .to_string(),
+ ]
+ }
+
for (idx, value) in values.into_iter().enumerate() {
let bytes = if value.starts_with("https://") || value.starts_with("http://") {
let result = match reqwest::get(&value).await {
@@ -157,14 +164,7 @@ impl PublicSuffix {
}
#[cfg(not(feature = "test_mode"))]
- config.new_build_error(
- key,
- if has_values {
- "Failed to parse public suffixes from any source."
- } else {
- "No public suffixes list was specified."
- },
- );
+ config.new_build_error(key, "Failed to parse public suffixes from any source.");
PublicSuffix::default()
}
diff --git a/resources/config/build.py b/resources/config/build.py
new file mode 100644
index 00000000..a9d428b1
--- /dev/null
+++ b/resources/config/build.py
@@ -0,0 +1,94 @@
+import os
+
+# Define the scripts and their component files
+scripts = {
+ "spam-filter": [
+ "config.sieve",
+ "prelude.sieve",
+ "from.sieve",
+ "recipient.sieve",
+ "subject.sieve",
+ "replyto.sieve",
+ "date.sieve",
+ "messageid.sieve",
+ "received.sieve",
+ "headers.sieve",
+ "bounce.sieve",
+ "html.sieve",
+ "mime.sieve",
+ "dmarc.sieve",
+ "ip.sieve",
+ "helo.sieve",
+ "replies_in.sieve",
+ "spamtrap.sieve",
+ "bayes_classify.sieve",
+ "url.sieve",
+ "rbl.sieve",
+ "pyzor.sieve",
+ "composites.sieve",
+ "scores.sieve",
+ "reputation.sieve",
+ "epilogue.sieve"
+ ],
+ "track-replies": [
+ "config.sieve",
+ "replies_out.sieve"
+ ],
+ "greylist": [
+ "config.sieve",
+ "greylist.sieve"
+ ]
+}
+script_names = {
+ "spam-filter" : "Spam Filter",
+ "track-replies" : "Track Replies",
+ "greylist" : "Greylisting"
+}
+
+maps = ["scores.map",
+ "allow_dmarc.list",
+ "allow_domains.list",
+ "allow_spf_dkim.list",
+ "domains_disposable.list",
+ "domains_free.list",
+ "mime_types.map",
+ "url_redirectors.list"]
+
+
+def read_and_concatenate(files):
+ content = ""
+ for file in files:
+ with open(os.path.join("./spamfilter/scripts", file), "r", encoding="utf-8") as f:
+ content += "\n#### Script " + file + " ####\n\n"
+ content += f.read() + "\n"
+ return content
+
+def read_file(file):
+ with open(file, "r", encoding="utf-8") as f:
+ return f.read() + "\n"
+
+def build_spam_filters(scripts):
+ spam_filter = read_file("./spamfilter/settings.toml")
+ for script_name, file_list in scripts.items():
+ script_content = read_and_concatenate(file_list).replace("'''", "\\'\\'\\'")
+ script_description = script_names[script_name]
+ spam_filter += f"[sieve.trusted.scripts.{script_name}]\nname = \"{script_description}\"\ncontents = '''\n{script_content}'''\n\n"
+
+ spam_filter += "\n[lookup]\n"
+ for map in maps :
+ with open(os.path.join("./spamfilter/maps", map), "r", encoding="utf-8") as f:
+ spam_filter += f.read() + "\n"
+
+ return spam_filter
+
+def main():
+ spam_filter = build_spam_filters(scripts)
+ with open("spamfilter.toml", "w", encoding="utf-8") as toml_file:
+ toml_file.write(spam_filter)
+ config = read_file("./minimal.toml") + read_file("./security.toml") + spam_filter
+ with open("config.toml", "w", encoding="utf-8") as toml_file:
+ toml_file.write(config)
+ print("Stalwart TOML configuration files have been generated.")
+
+if __name__ == "__main__":
+ main()
diff --git a/resources/config/common/cache.toml b/resources/config/common/cache.toml
deleted file mode 100644
index f08eaa74..00000000
--- a/resources/config/common/cache.toml
+++ /dev/null
@@ -1,35 +0,0 @@
-#############################################
-# Cache configuration
-#############################################
-
-[cache]
-capacity = 512
-shard = 32
-
-[cache.session]
-ttl = "1h"
-
-[cache.account]
-size = 2048
-
-[cache.mailbox]
-size = 2048
-
-[cache.thread]
-size = 2048
-
-[cache.bayes]
-capacity = 8192
-
-[cache.bayes.ttl]
-positive = "1h"
-negative = "1h"
-
-[cache.resolver]
-txt = 2048
-mx = 1024
-ipv4 = 1024
-ipv6 = 1024
-ptr = 1024
-tlsa = 1024
-mta-sts = 1024
diff --git a/resources/config/common/server.toml b/resources/config/common/server.toml
deleted file mode 100644
index c80c226c..00000000
--- a/resources/config/common/server.toml
+++ /dev/null
@@ -1,37 +0,0 @@
-#############################################
-# Server configuration
-#############################################
-
-[server]
-hostname = "%{HOST}%"
-max-connections = 8192
-
-#[server.proxy]
-#trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8"]
-
-[authentication]
-fail2ban = "100/1d"
-rate-limit = "10/1m"
-
-[server.run-as]
-user = "stalwart-mail"
-group = "stalwart-mail"
-
-[server.socket]
-nodelay = true
-reuse-addr = true
-#reuse-port = true
-backlog = 1024
-#ttl = 3600
-#send-buffer-size = 65535
-#recv-buffer-size = 65535
-#linger = 1
-#tos = 1
-
-[global]
-#thread-pool = 8
-
-[server.http]
-#headers = ["Access-Control-Allow-Origin: *",
-# "Access-Control-Allow-Methods: POST, GET, PATCH, PUT, DELETE, HEAD, OPTIONS",
-# "Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With"]
diff --git a/resources/config/common/sieve.toml b/resources/config/common/sieve.toml
deleted file mode 100644
index d41cee31..00000000
--- a/resources/config/common/sieve.toml
+++ /dev/null
@@ -1,73 +0,0 @@
-#############################################
-# Sieve untrusted runtime configuration
-#############################################
-
-[sieve.untrusted]
-disable-capabilities = []
-notification-uris = ["mailto"]
-protected-headers = ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]
-
-[sieve.untrusted.limits]
-name-length = 512
-max-scripts = 256
-script-size = 102400
-string-length = 4096
-variable-name-length = 32
-variable-size = 4096
-nested-blocks = 15
-nested-tests = 15
-nested-foreverypart = 3
-match-variables = 30
-local-variables = 128
-header-size = 1024
-includes = 3
-nested-includes = 3
-cpu = 5000
-redirects = 1
-received-headers = 10
-outgoing-messages = 3
-
-[sieve.untrusted.vacation]
-default-subject = "Automated reply"
-subject-prefix = "Auto: "
-
-[sieve.untrusted.default-expiry]
-vacation = "30d"
-duplicate = "7d"
-
-#############################################
-# Sieve trusted runtime configuration
-#############################################
-
-[sieve.trusted]
-from-name = "Automated Message"
-from-addr = "no-reply@%{DEFAULT_DOMAIN}%"
-return-path = ""
-#hostname = "%{HOST}%"
-no-capability-check = true
-sign = ["rsa"]
-
-[sieve.trusted.limits]
-redirects = 3
-out-messages = 5
-received-headers = 50
-cpu = 1048576
-nested-includes = 5
-duplicate-expiry = "7d"
-
-[sieve.trusted.scripts]
-#connect = '''require ["variables", "extlists", "reject"];
-# if string :list "${env.remote_ip}" "default/blocked-ips" {
-# reject "Your IP '${env.remote_ip}' is not welcomed here.";
-# }'''
-
-#ehlo = '''require ["variables", "extlists", "reject"];
-# if string :list "${env.helo_domain}" "default/blocked-domains" {
-# reject "551 5.1.1 Your domain '${env.helo_domain}' has been blacklisted.";
-# }'''
-
-#mail = '''require ["variables", "envelope", "reject"];
-# if envelope :localpart :is "from" "known_spammer" {
-# reject "We do not accept SPAM.";
-# }'''
-
diff --git a/resources/config/common/store.toml b/resources/config/common/store.toml
deleted file mode 100644
index ea96531c..00000000
--- a/resources/config/common/store.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-#############################################
-# Storage configuration
-#############################################
-
-[storage]
-data = "%{DEFAULT_STORE}%"
-fts = "%{DEFAULT_STORE}%"
-blob = "%{DEFAULT_STORE}%"
-lookup = "%{DEFAULT_STORE}%"
-directory = "%{DEFAULT_DIRECTORY}%"
-
-[storage.encryption]
-enable = true
-append = false
-
-[storage.full-text]
-default-language = "en"
-
-[storage.cluster]
-node-id = 1
diff --git a/resources/config/common/tls.toml b/resources/config/common/tls.toml
deleted file mode 100644
index 2c5fe746..00000000
--- a/resources/config/common/tls.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-#############################################
-# TLS default configuration
-#############################################
-
-[server.tls]
-enable = true
-implicit = false
-timeout = "1m"
-certificate = "default"
-#acme = "letsencrypt"
-#protocols = ["TLSv1.2", "TLSv1.3"]
-#ciphers = [ "TLS13_AES_256_GCM_SHA384", "TLS13_AES_128_GCM_SHA256",
-# "TLS13_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
-# "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
-# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
-# "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"]
-ignore-client-order = true
-
-[acme."letsencrypt"]
-directory = "https://acme-v02.api.letsencrypt.org/directory"
-#directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
-contact = ["postmaster@%{DEFAULT_DOMAIN}%"]
-cache = "%{BASE_PATH}%/etc/acme"
-port = 443
-renew-before = "30d"
-
-[certificate."default"]
-sni-subjects = []
-cert = "file://__CERT_PATH__"
-private-key = "file://__PK_PATH__"
diff --git a/resources/config/common/tracing.toml b/resources/config/common/tracing.toml
deleted file mode 100644
index 466172c9..00000000
--- a/resources/config/common/tracing.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-#############################################
-# Tracing & logging configuration
-#############################################
-
-[tracing."stdout"]
-method = "stdout"
-level = "trace"
-enable = false
-
-[tracing."ot"]
-method = "open-telemetry"
-transport = "http"
-endpoint = "https://127.0.0.1/otel"
-headers = ["Authorization: <place_auth_here>"]
-level = "debug"
-enable = false
-
-[tracing."log"]
-method = "log"
-path = "%{BASE_PATH}%/logs"
-prefix = "stalwart.log"
-rotate = "daily"
-level = "info"
-enable = true
diff --git a/resources/config/config.toml b/resources/config/config.toml
deleted file mode 100644
index 63b23694..00000000
--- a/resources/config/config.toml
+++ /dev/null
@@ -1,52 +0,0 @@
-#############################################
-# Stalwart Mail Server Configuration File
-#############################################
-
-[macros]
-host = "__HOST__"
-default_domain = "__DOMAIN__"
-base_path = "__BASE_PATH__"
-default_directory = "__DIRECTORY__"
-default_store = "__STORE__"
-
-[include]
-files = [ "%{BASE_PATH}%/etc/common/server.toml",
- "%{BASE_PATH}%/etc/common/tls.toml",
- "%{BASE_PATH}%/etc/common/store.toml",
- "%{BASE_PATH}%/etc/common/tracing.toml",
- "%{BASE_PATH}%/etc/common/sieve.toml",
- "%{BASE_PATH}%/etc/common/cache.toml",
- "%{BASE_PATH}%/etc/directory/imap.toml",
- "%{BASE_PATH}%/etc/directory/internal.toml",
- "%{BASE_PATH}%/etc/directory/ldap.toml",
- "%{BASE_PATH}%/etc/directory/lmtp.toml",
- "%{BASE_PATH}%/etc/directory/memory.toml",
- "%{BASE_PATH}%/etc/directory/sql.toml",
- "%{BASE_PATH}%/etc/store/elasticsearch.toml",
- "%{BASE_PATH}%/etc/store/fs.toml",
- "%{BASE_PATH}%/etc/store/foundationdb.toml",
- "%{BASE_PATH}%/etc/store/mysql.toml",
- "%{BASE_PATH}%/etc/store/postgresql.toml",
- "%{BASE_PATH}%/etc/store/redis.toml",
- "%{BASE_PATH}%/etc/store/rocksdb.toml",
- "%{BASE_PATH}%/etc/store/s3.toml",
- "%{BASE_PATH}%/etc/store/sqlite.toml",
- "%{BASE_PATH}%/etc/imap/listener.toml",
- "%{BASE_PATH}%/etc/imap/settings.toml",
- "%{BASE_PATH}%/etc/jmap/auth.toml",
- "%{BASE_PATH}%/etc/jmap/listener.toml",
- "%{BASE_PATH}%/etc/jmap/oauth.toml",
- "%{BASE_PATH}%/etc/jmap/protocol.toml",
- "%{BASE_PATH}%/etc/jmap/push.toml",
- "%{BASE_PATH}%/etc/jmap/ratelimit.toml",
- "%{BASE_PATH}%/etc/jmap/websockets.toml",
- "%{BASE_PATH}%/etc/smtp/auth.toml",
- "%{BASE_PATH}%/etc/smtp/listener.toml",
- "%{BASE_PATH}%/etc/smtp/milter.toml",
- "%{BASE_PATH}%/etc/smtp/queue.toml",
- "%{BASE_PATH}%/etc/smtp/remote.toml",
- "%{BASE_PATH}%/etc/smtp/report.toml",
- "%{BASE_PATH}%/etc/smtp/resolver.toml",
- "%{BASE_PATH}%/etc/smtp/session.toml",
- "%{BASE_PATH}%/etc/smtp/signature.toml",
- "%{BASE_PATH}%/etc/smtp/spamfilter.toml" ]
diff --git a/resources/config/directory/imap.toml b/resources/config/directory/imap.toml
deleted file mode 100644
index 912906a5..00000000
--- a/resources/config/directory/imap.toml
+++ /dev/null
@@ -1,29 +0,0 @@
-#############################################
-# IMAP Directory configuration
-#############################################
-
-[directory."imap"]
-type = "imap"
-host = "127.0.0.1"
-port = 993
-disable = true
-
-[directory."imap".pool]
-max-connections = 10
-
-[directory."imap".pool.timeout]
-create = "30s"
-wait = "30s"
-recycle = "30s"
-
-[directory."imap".tls]
-enable = true
-allow-invalid-certs = true
-
-[directory."imap".cache]
-entries = 500
-ttl = {positive = '1h', negative = '10m'}
-
-[directory."imap".lookup]
-domains = ["%{DEFAULT_DOMAIN}%"]
-
diff --git a/resources/config/directory/internal.toml b/resources/config/directory/internal.toml
deleted file mode 100644
index 0ca96d21..00000000
--- a/resources/config/directory/internal.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-#############################################
-# Internal Directory configuration
-#############################################
-
-[directory."internal"]
-type = "internal"
-store = "%{DEFAULT_STORE}%"
-disable = true
-
-[directory."internal".options]
-catch-all = true
-#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
-# { else = false } ]
-subaddressing = true
-#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
-# { else = false } ]
-
-[directory."internal".cache]
-entries = 500
-ttl = {positive = '1h', negative = '10m'}
diff --git a/resources/config/directory/ldap.toml b/resources/config/directory/ldap.toml
deleted file mode 100644
index 6e91a31a..00000000
--- a/resources/config/directory/ldap.toml
+++ /dev/null
@@ -1,60 +0,0 @@
-#############################################
-# LDAP Directory configuration
-#############################################
-
-[directory."ldap"]
-type = "ldap"
-url = "ldap://localhost:389"
-base-dn = "dc=example,dc=org"
-timeout = "30s"
-disable = true
-
-[directory."ldap".bind]
-dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
-secret = "mysecret"
-
-[directory."ldap".bind.auth]
-enable = false
-dn = "cn=?,ou=svcaccts,dc=example,dc=org"
-
-[directory."ldap".tls]
-enable = false
-allow-invalid-certs = false
-
-[directory."ldap".cache]
-entries = 500
-ttl = {positive = '1h', negative = '10m'}
-
-[directory."ldap".options]
-catch-all = true
-#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
-# { else = false } ]
-subaddressing = true
-#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
-# { else = false } ]
-
-[directory."ldap".pool]
-max-connections = 10
-
-[directory."ldap".pool.timeout]
-create = "30s"
-wait = "30s"
-recycle = "30s"
-
-[directory."ldap".filter]
-name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
-email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(mailAlias=?)(mailList=?)))"
-verify = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*?*)(mailAlias=*?*)))"
-expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(mailList=?))"
-domains = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*@?)(mailAlias=*@?)))"
-
-[directory."ldap".attributes]
-name = "uid"
-class = "objectClass"
-description = ["principalName", "description"]
-secret = "userPassword"
-groups = ["memberOf", "otherGroups"]
-email = "mail"
-email-alias = "mailAlias"
-quota = "diskQuota"
-
diff --git a/resources/config/directory/lmtp.toml b/resources/config/directory/lmtp.toml
deleted file mode 100644
index bff3ba4f..00000000
--- a/resources/config/directory/lmtp.toml
+++ /dev/null
@@ -1,33 +0,0 @@
-#############################################
-# LMTP Directory configuration
-#############################################
-
-[directory."lmtp"]
-type = "lmtp"
-host = "127.0.0.1"
-port = 11200
-disable = true
-
-[directory."lmtp".limits]
-auth-errors = 3
-rcpt = 5
-
-[directory."lmtp".pool]
-max-connections = 10
-
-[directory."lmtp".pool.timeout]
-create = "30s"
-wait = "30s"
-recycle = "30s"
-
-[directory."lmtp".tls]
-enable = false
-allow-invalid-certs = true
-
-[directory."lmtp".cache]
-entries = 500
-ttl = {positive = '1h', negative = '10m'}
-
-[directory."lmtp".lookup]
-domains = ["%{DEFAULT_DOMAIN}%"]
-
diff --git a/resources/config/directory/memory.toml b/resources/config/directory/memory.toml
deleted file mode 100644
index d484d999..00000000
--- a/resources/config/directory/memory.toml
+++ /dev/null
@@ -1,59 +0,0 @@
-#############################################
-# In-Memory Directory configuration
-#############################################
-
-[directory."memory"]
-type = "memory"
-disable = true
-
-[directory."memory".options]
-catch-all = true
-#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
-# { else = false } ]
-subaddressing = true
-#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
-# { else = false } ]
-
-[[directory."memory".principals]]
-name = "admin"
-class = "admin"
-description = "Superuser"
-secret = "changeme"
-email = ["postmaster@%{DEFAULT_DOMAIN}%"]
-
-[[directory."memory".principals]]
-name = "john"
-class = "individual"
-description = "John Doe"
-secret = "12345"
-email = ["john@%{DEFAULT_DOMAIN}%", "jdoe@%{DEFAULT_DOMAIN}%", "john.doe@%{DEFAULT_DOMAIN}%"]
-email-list = ["info@%{DEFAULT_DOMAIN}%"]
-member-of = ["sales"]
-
-[[directory."memory".principals]]
-name = "jane"
-class = "individual"
-description = "Jane Doe"
-secret = "abcde"
-email = ["jane@%{DEFAULT_DOMAIN}%", "jane.doe@%{DEFAULT_DOMAIN}%"]
-email-list = ["info@%{DEFAULT_DOMAIN}%"]
-member-of = ["sales", "support"]
-
-[[directory."memory".principals]]
-name = "bill"
-class = "individual"
-description = "Bill Foobar"
-secret = "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe"
-quota = 50000000
-email = ["bill@%{DEFAULT_DOMAIN}%", "bill.foobar@%{DEFAULT_DOMAIN}%"]
-email-list = ["info@%{DEFAULT_DOMAIN}%"]
-
-[[directory."memory".principals]]
-name = "sales"
-class = "group"
-description = "Sales Team"
-
-[[directory."memory".principals]]
-name = "support"
-class = "group"
-description = "Support Team"
diff --git a/resources/config/directory/sql.toml b/resources/config/directory/sql.toml
deleted file mode 100644
index 04a8ea23..00000000
--- a/resources/config/directory/sql.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-#############################################
-# SQL Directory configuration
-#############################################
-
-[directory."sql"]
-type = "sql"
-store = "__SQL_STORE__"
-disable = true
-
-[directory."sql".options]
-catch-all = true
-#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
-# { else = false } ]
-subaddressing = true
-#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
-# { else = false } ]
-
-[directory."sql".cache]
-entries = 500
-ttl = {positive = '1h', negative = '10m'}
-
-[directory."sql".columns]
-class = "type"
-secret = "secret"
-description = "description"
-quota = "quota"
diff --git a/resources/config/imap/listener.toml b/resources/config/imap/listener.toml
deleted file mode 100644
index 8f4d6357..00000000
--- a/resources/config/imap/listener.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-#############################################
-# IMAP server listeners configuration
-#############################################
-
-[server.listener."imap"]
-bind = ["[::]:143"]
-protocol = "imap"
-
-[server.listener."imaptls"]
-bind = ["[::]:993"]
-protocol = "imap"
-tls.implicit = true
-
-[server.listener."sieve"]
-bind = ["[::]:4190"]
-protocol = "managesieve"
diff --git a/resources/config/imap/settings.toml b/resources/config/imap/settings.toml
deleted file mode 100644
index 873ff42a..00000000
--- a/resources/config/imap/settings.toml
+++ /dev/null
@@ -1,22 +0,0 @@
-#############################################
-# IMAP server settings
-#############################################
-
-[imap.request]
-max-size = 52428800
-
-[imap.auth]
-max-failures = 3
-allow-plain-text = false
-
-[imap.folders.name]
-shared = "Shared Folders"
-
-[imap.timeout]
-authenticated = "30m"
-anonymous = "1m"
-idle = "30m"
-
-[imap.rate-limit]
-requests = "2000/1m"
-concurrent = 6
diff --git a/resources/config/jmap/auth.toml b/resources/config/jmap/auth.toml
deleted file mode 100644
index eaf68d83..00000000
--- a/resources/config/jmap/auth.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-#############################################
-# JMAP authentication & session configuration
-#############################################
-
-[jmap.session.purge]
-frequency = "15 * *"
diff --git a/resources/config/jmap/listener.toml b/resources/config/jmap/listener.toml
deleted file mode 100644
index e3186851..00000000
--- a/resources/config/jmap/listener.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-#############################################
-# JMAP server listener configuration
-#############################################
-
-[server.listener."jmap"]
-protocol = "jmap"
-bind = ["[::]:443"]
-url = "https://%{HOST}%"
-
-[server.listener."jmap".tls]
-implicit = true
-
-#bind = ["[::]:8080"]
-#url = "https://%{HOST}%:8080"
diff --git a/resources/config/jmap/oauth.toml b/resources/config/jmap/oauth.toml
deleted file mode 100644
index acb2e594..00000000
--- a/resources/config/jmap/oauth.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-#############################################
-# JMAP OAuth server configuration
-#############################################
-
-[oauth]
-key = "__OAUTH_KEY__"
-
-[oauth.auth]
-max-attempts = 3
-
-[oauth.expiry]
-user-code = "30m"
-auth-code = "10m"
-token = "1h"
-refresh-token = "30d"
-refresh-token-renew = "4d"
diff --git a/resources/config/jmap/protocol.toml b/resources/config/jmap/protocol.toml
deleted file mode 100644
index 336bc4a1..00000000
--- a/resources/config/jmap/protocol.toml
+++ /dev/null
@@ -1,43 +0,0 @@
-#############################################
-# JMAP protocol configuration
-#############################################
-
-[jmap.protocol.get]
-max-objects = 500
-
-[jmap.protocol.set]
-max-objects = 500
-
-[jmap.protocol.request]
-max-concurrent = 4
-max-size = 10000000
-max-calls = 16
-
-[jmap.protocol.query]
-max-results = 5000
-
-[jmap.protocol.upload]
-max-size = 50000000
-max-concurrent = 4
-ttl = "1h"
-
-[jmap.protocol.upload.quota]
-files = 1000
-size = 50000000
-
-[jmap.protocol.changes]
-max-results = 5000
-
-[jmap.mailbox]
-max-depth = 10
-max-name-length = 255
-
-[jmap.email]
-max-attachment-size = 50000000
-max-size = 75000000
-
-[jmap.email.parse]
-max-items = 10
-
-[jmap.principal]
-allow-lookups = true
diff --git a/resources/config/jmap/push.toml b/resources/config/jmap/push.toml
deleted file mode 100644
index 21111c1a..00000000
--- a/resources/config/jmap/push.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-#############################################
-# JMAP Push & EventSource configuration
-#############################################
-
-[jmap.push]
-max-total = 100
-throttle = "1ms"
-
-[jmap.push.attempts]
-interval = "1m"
-max = 3
-
-[jmap.push.retry]
-interval = "1s"
-
-[jmap.push.timeout]
-request = "10s"
-verify = "1s"
-
-[jmap.event-source]
-throttle = "1s"
diff --git a/resources/config/jmap/ratelimit.toml b/resources/config/jmap/ratelimit.toml
deleted file mode 100644
index 4fef99f1..00000000
--- a/resources/config/jmap/ratelimit.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-#############################################
-# JMAP server rate limiter configuration
-#############################################
-
-[jmap.rate-limit]
-account = "1000/1m"
-anonymous = "100/1m"
-use-forwarded = false
diff --git a/resources/config/jmap/websockets.toml b/resources/config/jmap/websockets.toml
deleted file mode 100644
index e9018e1e..00000000
--- a/resources/config/jmap/websockets.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-#############################################
-# JMAP WebSockets server configuration
-#############################################
-
-[jmap.web-sockets]
-throttle = "1s"
-timeout = "10m"
-heartbeat = "1m"
diff --git a/resources/config/minimal.toml b/resources/config/minimal.toml
new file mode 100644
index 00000000..fea40d54
--- /dev/null
+++ b/resources/config/minimal.toml
@@ -0,0 +1,72 @@
+#############################################
+# Stalwart Mail Server Configuration File
+#############################################
+
+[server.listener."smtp"]
+bind = ["[::]:25"]
+protocol = "smtp"
+
+[server.listener."submission"]
+bind = ["[::]:587"]
+protocol = "smtp"
+
+[server.listener."submissions"]
+bind = ["[::]:465"]
+protocol = "smtp"
+tls.implicit = true
+
+[server.listener."imap"]
+bind = ["[::]:143"]
+protocol = "imap"
+
+[server.listener."imaptls"]
+bind = ["[::]:993"]
+protocol = "imap"
+tls.implicit = true
+
+[server.listener."sieve"]
+bind = ["[::]:4190"]
+protocol = "managesieve"
+
+[server.listener."https"]
+protocol = "http"
+bind = ["[::]:443"]
+tls.implicit = true
+
+[storage]
+data = "rocksdb"
+fts = "rocksdb"
+blob = "rocksdb"
+lookup = "rocksdb"
+directory = "internal"
+
+[store."rocksdb"]
+type = "rocksdb"
+path = "%{env:STALWART_PATH}%/data"
+compression = "lz4"
+
+[directory."internal"]
+type = "internal"
+store = "rocksdb"
+
+[lookup.default]
+domain = "%{env:DOMAIN}%"
+hostname = "%{env:HOSTNAME}%"
+
+[oauth]
+key = "%{env:OAUTH_KEY}%"
+
+[tracer."stdout"]
+type = "stdout"
+level = "info"
+ansi = false
+enable = true
+
+#[server.run-as]
+#user = "stalwart-mail"
+#group = "stalwart-mail"
+
+[server.http]
+headers = ["Access-Control-Allow-Origin: *",
+ "Access-Control-Allow-Methods: POST, GET, PATCH, PUT, DELETE, HEAD, OPTIONS",
+ "Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With"]
diff --git a/resources/config/security.toml b/resources/config/security.toml
new file mode 100644
index 00000000..9d73b280
--- /dev/null
+++ b/resources/config/security.toml
@@ -0,0 +1,19 @@
+[[queue.quota]]
+messages = 100000
+size = 10737418240 # 10gb
+enable = true
+
+[[queue.throttle]]
+key = ["rcpt_domain"]
+concurrency = 5
+enable = true
+
+[[session.throttle]]
+key = ["remote_ip"]
+concurrency = 5
+enable = true
+
+[[session.throttle]]
+key = ["sender_domain", "rcpt"]
+rate = "25/1h"
+enable = true
diff --git a/resources/config/smtp/auth.toml b/resources/config/smtp/auth.toml
deleted file mode 100644
index 2e77d92c..00000000
--- a/resources/config/smtp/auth.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-#############################################
-# SMTP DMARC, DKIM, SPF, ARC & IpRev
-#############################################
-
-[auth.iprev]
-verify = [ { if = "listener = 'smtp'", then = "relaxed" },
- { else = "disable" } ]
-
-[auth.dkim]
-verify = "relaxed"
-sign = [ { if = "listener != 'smtp'", then = "['rsa']" },
- { else = false } ]
-
-[auth.spf.verify]
-ehlo = [ { if = "listener = 'smtp'", then = "relaxed" },
- { else = "disable" } ]
-mail-from = [ { if = "listener = 'smtp'", then = "relaxed" },
- { else = "disable" } ]
-
-[auth.arc]
-verify = "relaxed"
-seal = "['rsa']"
-
-[auth.dmarc]
-verify = [ { if = "listener = 'smtp'", then = "relaxed" },
- { else = "disable" } ]
-
diff --git a/resources/config/smtp/listener.toml b/resources/config/smtp/listener.toml
deleted file mode 100644
index f385fb7f..00000000
--- a/resources/config/smtp/listener.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-#############################################
-# SMTP server listener configuration
-#############################################
-
-[server.listener."smtp"]
-bind = ["[::]:25"]
-#greeting = "Stalwart SMTP at your service"
-protocol = "smtp"
-
-[server.listener."submission"]
-bind = ["[::]:587"]
-protocol = "smtp"
-
-[server.listener."submissions"]
-bind = ["[::]:465"]
-protocol = "smtp"
-tls.implicit = true
-
-#[server.listener."management"]
-#bind = ["127.0.0.1:8080"]
-#protocol = "http"
diff --git a/resources/config/smtp/milter.toml b/resources/config/smtp/milter.toml
deleted file mode 100644
index 4086b33a..00000000
--- a/resources/config/smtp/milter.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-#############################################
-# SMTP inbound Milter configuration
-#############################################
-
-#[session.data.milter."rspamd"]
-#enable = [ { if = "listener = 'smtp'", then = true },
-# { else = false } ]
-#hostname = "127.0.0.1"
-#port = 11332
-#tls = false
-#allow-invalid-certs = false
-
-#[session.data.milter."rspamd".timeout]
-#connect = "30s"
-#command = "30s"
-#data = "60s"
-
-#[session.data.milter."rspamd".options]
-#tempfail-on-error = true
-#max-response-size = 52428800 # 50mb
-#version = 6
-
-#[session.data.pipe."spam-assassin"]
-#command = "spamc"
-#arguments = []
-#timeout = "10s"
diff --git a/resources/config/smtp/queue.toml b/resources/config/smtp/queue.toml
deleted file mode 100644
index 79f0f4d0..00000000
--- a/resources/config/smtp/queue.toml
+++ /dev/null
@@ -1,49 +0,0 @@
-#############################################
-# SMTP server queue configuration
-#############################################
-
-[queue.schedule]
-retry = "[2m, 5m, 10m, 15m, 30m, 1h, 2h]"
-notify = "[1d, 3d]"
-expire = "5d"
-
-[queue.outbound]
-#hostname = "%{HOST}%"
-next-hop = [ { if = "is_local_domain('%{DEFAULT_DIRECTORY}%', rcpt_domain)", then = "'local'" },
- { else = false } ]
-ip-strategy = "ipv4_then_ipv6"
-
-[queue.outbound.tls]
-dane = "optional"
-mta-sts = "optional"
-starttls = "require"
-allow-invalid-certs = false
-
-#[queue.outbound.source-ip]
-#v4 = "['10.0.0.10', '10.0.0.11']"
-#v6 = "['a::b', 'a::c']"
-
-[queue.outbound.limits]
-mx = 7
-multihomed = 2
-
-[queue.outbound.timeouts]
-connect = "3m"
-greeting = "3m"
-tls = "2m"
-ehlo = "3m"
-mail-from = "3m"
-rcpt-to = "3m"
-data = "10m"
-mta-sts = "2m"
-
-[[queue.quota]]
-#match = "sender_domain = 'foobar.org'"
-#key = ["rcpt"]
-messages = 100000
-size = 10737418240 # 10gb
-
-[[queue.throttle]]
-key = ["rcpt_domain"]
-#rate = "100/1h"
-concurrency = 5
diff --git a/resources/config/smtp/remote.toml b/resources/config/smtp/remote.toml
deleted file mode 100644
index 27a658f5..00000000
--- a/resources/config/smtp/remote.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# SMTP remote servers configuration
-#############################################
-
-[remote."local"]
-address = "127.0.0.1"
-port = 11200
-protocol = "lmtp"
-concurrency = 10
-timeout = "1m"
-
-[remote."local".tls]
-implicit = false
-allow-invalid-certs = true
-
-#[remote."local".auth]
-#username = ""
-#secret = ""
diff --git a/resources/config/smtp/report.toml b/resources/config/smtp/report.toml
deleted file mode 100644
index 91a9e32e..00000000
--- a/resources/config/smtp/report.toml
+++ /dev/null
@@ -1,55 +0,0 @@
-#############################################
-# SMTP reporting configuration
-#############################################
-
-[report]
-#submitter = "'%{HOST}%'"
-
-[report.analysis]
-addresses = ["dmarc@*", "abuse@*", "postmaster@*"]
-forward = true
-store = "30d"
-
-[report.dsn]
-from-name = "'Mail Delivery Subsystem'"
-from-address = "'MAILER-DAEMON@%{DEFAULT_DOMAIN}%'"
-sign = "['rsa']"
-
-[report.dkim]
-from-name = "'Report Subsystem'"
-from-address = "'noreply-dkim@%{DEFAULT_DOMAIN}%'"
-subject = "'DKIM Authentication Failure Report'"
-sign = "['rsa']"
-send = "[1, 1d]"
-
-[report.spf]
-from-name = "'Report Subsystem'"
-from-address = "'noreply-spf@%{DEFAULT_DOMAIN}%'"
-subject = "'SPF Authentication Failure Report'"
-send = "[1, 1d]"
-sign = "['rsa']"
-
-[report.dmarc]
-from-name = "'Report Subsystem'"
-from-address = "'noreply-dmarc@%{DEFAULT_DOMAIN}%'"
-subject = "'DMARC Authentication Failure Report'"
-send = "[1, 1d]"
-sign = "['rsa']"
-
-[report.dmarc.aggregate]
-from-name = "'DMARC Report'"
-from-address = "'noreply-dmarc@%{DEFAULT_DOMAIN}%'"
-org-name = "'%{DEFAULT_DOMAIN}%'"
-#contact-info = ""
-send = "daily"
-max-size = 26214400 # 25mb
-sign = "['rsa']"
-
-[report.tls.aggregate]
-from-name = "'TLS Report'"
-from-address = "'noreply-tls@%{DEFAULT_DOMAIN}%'"
-org-name = "'%{DEFAULT_DOMAIN}%'"
-#contact-info = ""
-send = "daily"
-max-size = 26214400 # 25 mb
-sign = "['rsa']"
diff --git a/resources/config/smtp/resolver.toml b/resources/config/smtp/resolver.toml
deleted file mode 100644
index 397b22e6..00000000
--- a/resources/config/smtp/resolver.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-#############################################
-# SMTP server resolver configuration
-#############################################
-
-[resolver]
-type = "system"
-#preserve-intermediates = true
-concurrency = 2
-timeout = "5s"
-attempts = 2
-try-tcp-on-error = true
-public-suffix = ["https://publicsuffix.org/list/public_suffix_list.dat",
- "file://%{BASE_PATH}%/etc/spamfilter/maps/suffix_list.dat.gz"]
diff --git a/resources/config/smtp/session.toml b/resources/config/smtp/session.toml
deleted file mode 100644
index b72ba077..00000000
--- a/resources/config/smtp/session.toml
+++ /dev/null
@@ -1,100 +0,0 @@
-#############################################
-# SMTP inbound session configuration
-#############################################
-
-[session]
-timeout = "5m"
-transfer-limit = 262144000 # 250 MB
-duration = "10m"
-
-[session.connect]
-#script = "'connect'"
-
-[session.ehlo]
-require = true
-reject-non-fqdn = [ { if = "listener = 'smtp'", then = true},
- { else = false } ]
-#script = "'ehlo'"
-
-[session.extensions]
-pipelining = true
-chunking = true
-requiretls = true
-no-soliciting = ""
-dsn = [ { if = "!is_empty(authenticated_as)", then = true},
- { else = false } ]
-expn = [ { if = "!is_empty(authenticated_as)", then = true},
- { else = false } ]
-vrfy = [ { if = "!is_empty(authenticated_as)", then = true},
- { else = false } ]
-future-release = [ { if = "!is_empty(authenticated_as)", then = "7d"},
- { else = false } ]
-deliver-by = [ { if = "!is_empty(authenticated_as)", then = "15d"},
- { else = false } ]
-mt-priority = [ { if = "!is_empty(authenticated_as)", then = "mixer"},
- { else = false } ]
-
-[session.auth]
-mechanisms = [ { if = "listener != 'smtp'", then = "[plain, login]"},
- { else = false } ]
-directory = [ { if = "listener != 'smtp'", then = "'%{DEFAULT_DIRECTORY}%'" },
- { else = false } ]
-require = [ { if = "listener != 'smtp'", then = true},
- { else = false } ]
-allow-plain-text = false
-
-[session.auth.errors]
-total = 3
-wait = "5s"
-
-[session.mail]
-#script = "mail-from"
-#rewrite = [ { if = "listener != 'smtp' & matches('^([^.]+)@([^.]+)\\.(.+)$', rcpt)", then = "$1 + '@' + $3" },
-# { else = false } ]
-
-[session.rcpt]
-#script = "greylist"
-relay = [ { if = "!is_empty(authenticated_as)", then = true },
- { else = false } ]
-#rewrite = [ { if = "is_local_domain('%{DEFAULT_DIRECTORY}%', rcpt_domain) & matches('^([^.]+)\\.([^.]+)@(.+)$', rcpt)", then = "$1 + '+' + $2 + '@' + $3" },
-# { else = false } ]
-max-recipients = 25
-directory = "'%{DEFAULT_DIRECTORY}%'"
-
-[session.rcpt.errors]
-total = 5
-wait = "5s"
-
-[session.data]
-script = [ { if = "is_empty(authenticated_as)", then = "'spam-filter'"},
- { else = "'track-replies'" } ]
-
-[session.data.limits]
-messages = 10
-size = 104857600
-received-headers = 50
-
-[session.data.add-headers]
-received = [ { if = "listener = 'smtp'", then = true },
- { else = false } ]
-received-spf = [ { if = "listener = 'smtp'", then = true },
- { else = false } ]
-auth-results = [ { if = "listener = 'smtp'", then = true },
- { else = false } ]
-message-id = [ { if = "listener = 'smtp'", then = false },
- { else = true } ]
-date = [ { if = "listener = 'smtp'", then = false },
- { else = true } ]
-return-path = false
-
-[[session.throttle]]
-#match = "remote_ip = '10.0.0.1'"
-key = ["remote_ip"]
-concurrency = 5
-#rate = "5/1h"
-enable = true
-
-[[session.throttle]]
-key = ["sender_domain", "rcpt"]
-rate = "25/1h"
-enable = true
diff --git a/resources/config/smtp/signature.toml b/resources/config/smtp/signature.toml
deleted file mode 100644
index ef9497b5..00000000
--- a/resources/config/smtp/signature.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# SMTP DKIM & ARC signatures
-#############################################
-
-[signature."rsa"]
-#public-key = "file://%{BASE_PATH}%/etc/dkim/%{DEFAULT_DOMAIN}%.cert"
-private-key = "file://%{BASE_PATH}%/etc/dkim/%{DEFAULT_DOMAIN}%.key"
-domain = "%{DEFAULT_DOMAIN}%"
-selector = "stalwart"
-headers = ["From", "To", "Date", "Subject", "Message-ID"]
-algorithm = "rsa-sha256"
-canonicalization = "relaxed/relaxed"
-#expire = "10d"
-#third-party = ""
-#third-party-algo = ""
-#auid = ""
-set-body-length = false
-report = true
diff --git a/resources/config/smtp/spamfilter.toml b/resources/config/smtp/spamfilter.toml
deleted file mode 100644
index 58fabecb..00000000
--- a/resources/config/smtp/spamfilter.toml
+++ /dev/null
@@ -1,62 +0,0 @@
-#############################################
-# SMTP Spam & Phishing filter configuration
-#############################################
-
-[spam.header]
-add-spam = true
-add-spam-result = true
-is-spam = "X-Spam-Status: Yes"
-
-[spam.autolearn]
-enable = true
-balance = 0.9
-
-[spam.autolearn.ham]
-replies = true
-threshold = -0.5
-
-[spam.autolearn.spam]
-threshold = 6.0
-
-[spam.threshold]
-spam = 5.0
-discard = 0
-reject = 0
-
-[spam.data]
-directory = ""
-lookup = ""
-
-[sieve.trusted.scripts]
-spam-filter = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/prelude.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/from.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/recipient.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/subject.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/replyto.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/date.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/messageid.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/received.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/headers.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/bounce.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/html.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/mime.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/dmarc.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/ip.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/helo.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/replies_in.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/spamtrap.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/bayes_classify.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/url.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/rbl.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/pyzor.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/composites.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/scores.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/reputation.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/epilogue.sieve"]
-
-track-replies = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/replies_out.sieve"]
-
-greylist = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
- "file://%{BASE_PATH}%/etc/spamfilter/scripts/greylist.sieve"]
diff --git a/resources/config/spamfilter.toml b/resources/config/spamfilter.toml
new file mode 100644
index 00000000..9242b846
--- /dev/null
+++ b/resources/config/spamfilter.toml
@@ -0,0 +1,10331 @@
+[spam.header]
+is-spam = "X-Spam-Status: Yes"
+
+[lookup.spam-config]
+add-spam = true
+add-spam-result = true
+learn-enable = true
+learn-balance = "0.9"
+learn-ham-replies = true
+learn-ham-threshold = "-0.5"
+learn-spam-threshold = "6.0"
+threshold-spam = "5.0"
+threshold-discard = "0.0"
+threshold-reject = "0.0"
+directory = ""
+lookup = ""
+
+[session.data]
+script = [ { if = "is_empty(authenticated_as)", then = "'spam-filter'"},
+ { else = "'track-replies'" } ]
+
+[sieve.trusted.scripts.spam-filter]
+name = "Spam Filter"
+contents = '''
+
+#### Script config.sieve ####
+
+# Whether to add an X-Spam-Status header
+let "ADD_HEADER_SPAM" "key_get('spam-config', 'add-spam')";
+
+# Whether to add an X-Spam-Result header
+let "ADD_HEADER_SPAM_RESULT" "key_get('spam-config', 'add-spam-result')";
+
+# Whether message replies from authenticated users should be learned as ham
+let "AUTOLEARN_REPLIES_HAM" "key_get('spam-config', 'learn-ham-replies')";
+
+# Whether the bayes classifier should be trained automatically
+let "AUTOLEARN_ENABLE" "key_get('spam-config', 'learn-enable')";
+
+# When to learn ham (score >= threshold)
+let "AUTOLEARN_HAM_THRESHOLD" "key_get('spam-config', 'learn-ham-threshold')";
+
+# When to learn spam (score <= threshold)
+let "AUTOLEARN_SPAM_THRESHOLD" "key_get('spam-config', 'learn-spam-threshold')";
+
+# Keep difference for spam/ham learns for at least this value
+let "AUTOLEARN_SPAM_HAM_BALANCE" "key_get('spam-config', 'learn-balance')";
+
+# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
+let "SCORE_SPAM_THRESHOLD" "key_get('spam-config', 'threshold-spam')";
+
+# Discard messages with a score above this threshold
+let "SCORE_DISCARD_THRESHOLD" "key_get('spam-config', 'threshold-discard')";
+
+# Reject messages with a score above this threshold
+let "SCORE_REJECT_THRESHOLD" "key_get('spam-config', 'threshold-reject')";
+
+# Directory name to use for local domain lookups (leave empty for default)
+let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
+
+# Store to use for Bayes tokens and ids (leave empty for default)
+let "SPAM_DB" "key_get('spam-config', 'lookup')";
+
+
+#### Script prelude.sieve ####
+
+# Convert body to plain text
+let "text_body" "body.to_text";
+
+# Obtain all URLs in the body
+let "body_urls" "tokenize(text_body, 'uri')";
+
+# Obtain all URLs in href and src attributes
+let "html_body_urls" "html_attrs(body.html, '', ['href', 'src'])";
+
+# Obtain all URLs in the subject, combine them with all other URLs and remove duplicates
+let "urls" "dedup(tokenize(header.subject, 'uri') + body_urls + html_body_urls)";
+
+# Obtain thread name and subject
+let "subject_lc" "to_lowercase(header.subject)";
+let "subject_clean" "thread_name(header.subject)";
+let "body_and_subject" "subject_clean + text_body";
+
+# Obtain all recipients
+let "recipients" "to_lowercase(header.to:cc:bcc[*].addr[*])";
+let "recipients_clean" "winnow(dedup(recipients))";
+let "recipients_to" "header.to[*].addr[*]";
+let "recipients_cc" "header.cc[*].addr[*]";
+
+# Obtain From parts
+let "from_name" "to_lowercase(trim(header.from.name))";
+let "from_addr" "to_lowercase(trim(header.from.addr))";
+let "from_local" "email_part(from_addr, 'local')";
+let "from_domain" "email_part(from_addr, 'domain')";
+let "from_domain_sld" "domain_part(from_domain, 'sld')";
+
+# Obtain Reply-To address
+let "rto_addr" "to_lowercase(header.reply-to.addr)";
+
+# Obtain Envelope From parts
+let "envfrom_local" "email_part(envelope.from, 'local')";
+let "envfrom_domain" "email_part(envelope.from, 'domain')";
+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";
+
+
+#### Script from.sieve ####
+
+let "from_count" "count(header.from[*].raw)";
+let "service_accounts" "['www-data', 'anonymous', 'ftp', 'apache', 'nobody', 'guest', 'nginx', 'web', 'www']";
+
+if eval "from_count > 0" {
+ let "from_raw" "to_lowercase(header.from.raw)";
+
+ if eval "from_count > 1" {
+ let "t.MULTIPLE_FROM" "1";
+ }
+
+ if eval "is_email(from_addr)" {
+ if eval "contains(service_accounts, from_local)" {
+ let "t.FROM_SERVICE_ACCT" "1";
+ }
+ if eval "starts_with(from_domain, 'www.')" {
+ let "t.WWW_DOT_DOMAIN" "1";
+ }
+
+ if eval "key_exists('spam-free', from_domain_sld)" {
+ let "t.FREEMAIL_FROM" "1";
+ } elsif eval "key_exists('spam-disposable', from_domain_sld)" {
+ let "t.DISPOSABLE_FROM" "1";
+ }
+ } else {
+ let "t.FROM_INVALID" "1";
+ }
+
+ if eval "is_empty(from_name)" {
+ let "t.FROM_NO_DN" "1";
+ } elsif eval "eq_ignore_case(from_addr, from_name)" {
+ let "t.FROM_DN_EQ_ADDR" "1";
+ } else {
+ if eval "!t.FROM_INVALID" {
+ let "t.FROM_HAS_DN" "1";
+ }
+
+ if eval "is_email(from_name)" {
+ let "from_name_sld" "domain_part(email_part(from_name, 'domain'), 'sld')";
+ if eval "(!t.FROM_INVALID && from_domain_sld != from_name_sld) ||
+ (!is_empty(envelope.from) && envfrom_domain_sld != from_name_sld) ||
+ (is_empty(envelope.from) && helo_domain_sld != from_name_sld)" {
+ let "t.SPOOF_DISPLAY_NAME" "1";
+ } else {
+ let "t.FROM_NEQ_DISPLAY_NAME" "1";
+ }
+ } else {
+ if eval "contains(from_name, 'mr. ') || contains(from_name, 'ms. ') || contains(from_name, 'mrs. ') || contains(from_name, 'dr. ')" {
+ let "t.FROM_NAME_HAS_TITLE" "1";
+ }
+ if eval "contains(header.from.name, ' ')" {
+ let "t.FROM_NAME_EXCESS_SPACE" "1";
+ }
+ }
+ }
+
+ if eval "is_empty(envelope.from) &&
+ (from_local == 'postmaster' ||
+ from_local == 'mailer-daemon' ||
+ from_local == 'root')" {
+ let "t.FROM_BOUNCE" "1";
+ }
+
+ if eval "(!is_empty(envelope.from) &&
+ eq_ignore_case(from_addr, envelope.from)) ||
+ (t.FROM_BOUNCE &&
+ !is_empty(from_domain) &&
+ from_domain_sld == helo_domain_sld)" {
+ let "t.FROM_EQ_ENVFROM" "1";
+ } elsif eval "!t.FROM_INVALID" {
+ let "t.FORGED_SENDER" "1";
+ let "t.FROM_NEQ_ENVFROM" "1";
+ }
+
+ if eval "contains(from_local, '+')" {
+ let "t.TAGGED_FROM" "1";
+ }
+
+ if eval "count(recipients_to) + count(recipients_cc) == 1" {
+ if eval "eq_ignore_case(recipients_to[0], from_addr)" {
+ let "t.TO_EQ_FROM" "1";
+ } elsif eval "eq_ignore_case(email_part(recipients_to[0], 'domain'), from_domain)" {
+ let "t.TO_DOM_EQ_FROM_DOM" "1";
+ }
+ }
+
+ if eval "!is_ascii(from_raw)" {
+ if eval "!env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" {
+ let "t.FROM_NEEDS_ENCODING" "1";
+ }
+ if eval "!is_header_utf8_valid('From')" {
+ let "t.INVALID_FROM_8BIT" "1";
+ }
+ }
+
+ if eval "is_ascii(header.from) && contains(from_raw, '=?') && contains(from_raw, '?=')" {
+ if eval "contains(from_raw, '?q?')" {
+ # From header is unnecessarily encoded in quoted-printable
+ let "t.FROM_EXCESS_QP" "1";
+ } elsif eval "contains(from_raw, '?b?')" {
+ # From header is unnecessarily encoded in base64
+ let "t.FROM_EXCESS_BASE64" "1";
+ }
+ }
+
+ if eval "!is_empty(from_name) && !is_empty(from_addr) && !contains(from_raw, ' <')" {
+ let "t.R_NO_SPACE_IN_FROM" "1";
+ }
+
+ # Read confirmation address is different to from address
+ let "crt" "header.X-Confirm-Reading-To.addr";
+ if eval "!is_empty(crt) && !eq_ignore_case(from_addr, crt)" {
+ let "t.HEADER_RCONFIRM_MISMATCH" "1";
+ }
+} else {
+ let "t.MISSING_FROM" "1";
+}
+
+if eval "!is_empty(envelope.from)" {
+ if eval "is_email(envelope.from)" {
+ if eval "contains(service_accounts, envfrom_local)" {
+ let "t.ENVFROM_SERVICE_ACCT" "1";
+ }
+ } else {
+ let "t.ENVFROM_INVALID" "1";
+ }
+
+ if eval "!is_empty(envfrom_domain_sld)" {
+ if eval "key_exists('spam-free', envfrom_domain_sld)" {
+ let "t.FREEMAIL_ENVFROM" "1";
+ } elsif eval "key_exists('spam-disposable', envfrom_domain_sld)" {
+ let "t.DISPOSABLE_ENVFROM" "1";
+ }
+
+ # Mail from no resolve to A or MX
+ if eval "!dns_exists(envfrom_domain, 'mx') && !dns_exists(envfrom_domain, 'ip')" {
+ let "t.FROMHOST_NORES_A_OR_MX" "1";
+ }
+ }
+
+ # Read confirmation address is different to return path
+ let "dnt" "header.Disposition-Notification-To.addr";
+ if eval "!is_empty(dnt) && !eq_ignore_case(envelope.from, dnt)" {
+ let "t.HEADER_FORGED_MDN" "1";
+ }
+}
+
+if eval "!t.FROM_SERVICE_ACCT &&
+ (contains_ignore_case(service_accounts, email_part(rto_addr, 'local')) ||
+ contains_ignore_case(service_accounts, email_part(header.sender.addr, 'local')))" {
+ let "t.FROM_SERVICE_ACCT" "1";
+}
+
+if eval "!t.WWW_DOT_DOMAIN &&
+ (contains_ignore_case(rto_addr, '@www.') ||
+ contains_ignore_case(header.sender.addr, '@www.'))" {
+ let "t.WWW_DOT_DOMAIN" "1";
+}
+
+
+
+#### Script recipient.sieve ####
+
+
+let "to_raw" "to_lowercase(header.to.raw)";
+if eval "!is_empty(to_raw)" {
+ if eval "is_ascii(header.to) && contains(to_raw, '=?') && contains(to_raw, '?=')" {
+ if eval "contains(to_raw, '?q?')" {
+ # To header is unnecessarily encoded in quoted-printable
+ let "t.TO_EXCESS_QP" "1";
+ } elsif eval "contains(to_raw, '?b?')" {
+ # To header is unnecessarily encoded in base64
+ let "t.TO_EXCESS_BASE64" "1";
+ }
+ } elsif eval "!is_ascii(to_raw) && !env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" {
+ # To needs encoding
+ let "t.TO_NEEDS_ENCODING" "1";
+ }
+} else {
+ let "t.MISSING_TO" "1";
+}
+
+let "rcpt_count" "count(recipients_clean)";
+
+if eval "rcpt_count > 0" {
+ if eval "rcpt_count == 1" {
+ let "t.RCPT_COUNT_ONE" "1";
+ } elsif eval "rcpt_count == 2" {
+ let "t.RCPT_COUNT_TWO" "1";
+ } elsif eval "rcpt_count == 3" {
+ let "t.RCPT_COUNT_THREE" "1";
+ } elsif eval "rcpt_count <= 5" {
+ let "t.RCPT_COUNT_FIVE" "1";
+ } elsif eval "rcpt_count <= 7" {
+ let "t.RCPT_COUNT_SEVEN" "1";
+ } elsif eval "rcpt_count <= 12" {
+ let "t.RCPT_COUNT_TWELVE" "1";
+ } else {
+ let "t.RCPT_COUNT_GT_50" "1";
+ }
+
+ let "rcpt_name" "to_lowercase(header.to:cc:bcc[*].name[*])";
+ let "i" "count(recipients)";
+ let "to_dn_count" "0";
+ let "to_dn_eq_addr_count" "0";
+ let "to_match_envrcpt" "0";
+
+ while "i != 0" {
+ let "i" "i - 1";
+ let "addr" "recipients[i]";
+
+ if eval "!is_empty(addr)" {
+ let "name" "rcpt_name[i]";
+
+ if eval "!is_empty(name)" {
+ if eval "name == addr" {
+ let "to_dn_eq_addr_count" "to_dn_eq_addr_count + 1";
+ } else {
+ let "to_dn_count" "to_dn_count + 1";
+ if eval "name == 'recipient' || name == 'recipients'" {
+ let "t.TO_DN_RECIPIENTS" "1";
+ }
+ }
+ }
+
+ if eval "contains(envelope.to, addr)" {
+ let "to_match_envrcpt" "to_match_envrcpt + 1";
+ }
+
+ # Check if the local part is present in the subject
+ let "local_part" "email_part(addr, 'local')";
+ if eval "!is_empty(local_part)" {
+ if eval "contains(subject_lc, addr)" {
+ let "t.RCPT_ADDR_IN_SUBJECT" "1";
+ } elsif eval "len(local_part) > 3 && contains(subject_lc, local_part)" {
+ let "t.RCPT_LOCAL_IN_SUBJECT" "1";
+ }
+
+ if eval "contains(local_part, '+')" {
+ let "t.TAGGED_RCPT" "1";
+ }
+ }
+
+ # Check if it is an into to info
+ if eval "!t.INFO_TO_INFO_LU &&
+ local_part == 'info' &&
+ from_local == 'info' &&
+ header.List-Unsubscribe.exists" {
+ let "t.INFO_TO_INFO_LU" "1";
+ }
+
+ # Check for freemail or disposable domains
+ let "domain" "domain_part(email_part(addr, 'domain'), 'sld')";
+ if eval "!is_empty(domain)" {
+ if eval "key_exists('spam-free', domain)" {
+ if eval "!t.FREEMAIL_TO && contains_ignore_case(recipients_to, addr)" {
+ let "t.FREEMAIL_TO" "1";
+ } elsif eval "!t.FREEMAIL_CC && contains_ignore_case(recipients_cc, addr)" {
+ let "t.FREEMAIL_CC" "1";
+ }
+ } elsif eval "key_exists('spam-disposable', domain)" {
+ if eval "!t.DISPOSABLE_TO && contains_ignore_case(recipients_to, addr)" {
+ let "t.DISPOSABLE_TO" "1";
+ } elsif eval "!t.DISPOSABLE_CC && contains_ignore_case(recipients_cc, addr)" {
+ let "t.DISPOSABLE_CC" "1";
+ }
+ }
+ }
+ }
+ }
+
+ if eval "to_dn_count == 0 && to_dn_eq_addr_count == 0" {
+ let "t.TO_DN_NONE" "1";
+ } elsif eval "to_dn_count == rcpt_count" {
+ let "t.TO_DN_ALL" "1";
+ } elsif eval "to_dn_count > 0" {
+ let "t.TO_DN_SOME" "1";
+ }
+
+ if eval "to_dn_eq_addr_count == rcpt_count" {
+ let "t.TO_DN_EQ_ADDR_ALL" "1";
+ } elsif eval "to_dn_eq_addr_count > 0" {
+ let "t.TO_DN_EQ_ADDR_SOME" "1";
+ }
+
+ if eval "to_match_envrcpt == rcpt_count" {
+ let "t.TO_MATCH_ENVRCPT_ALL" "1";
+ } else {
+ if eval "to_match_envrcpt > 0" {
+ let "t.TO_MATCH_ENVRCPT_SOME" "1";
+ }
+
+ if eval "is_empty(header.List-Unsubscribe:List-Id[*])" {
+ let "i" "count(envelope.to)";
+ while "i != 0" {
+ let "i" "i - 1";
+ let "env_rcpt" "envelope.to[i]";
+
+ if eval "!contains(recipients, env_rcpt) && env_rcpt != envelope.from" {
+ let "t.FORGED_RECIPIENTS" "1";
+ break;
+ }
+ }
+ }
+ }
+
+ # Message from bounce and over 1 recipient
+ if eval "rcpt_count > 1 &&
+ (is_empty(envelope.from) ||
+ envfrom_local == 'postmaster' ||
+ envfrom_local == 'mailer-daemon')" {
+ let "t.RCPT_BOUNCEMOREONE" "1";
+ }
+
+ # Check for sorted recipients
+ if eval "rcpt_count >= 7 && sort(recipients_clean, false) == recipients_clean" {
+ let "t.SORTED_RECIPS" "1";
+ }
+
+ # Check for suspiciously similar recipients
+ if eval "!t.SORTED_RECIPS && rcpt_count => 5" {
+ let "i" "rcpt_count";
+ let "hits" "0";
+ let "combinations" "0";
+
+ while "i" {
+ let "i" "i - 1";
+ let "j" "i";
+ while "j" {
+ let "j" "j - 1";
+ let "a" "recipients_clean[i]";
+ let "b" "recipients_clean[j]";
+
+ if eval "levenshtein_distance(email_part(a, 'local'), email_part(b, 'local')) < 3" {
+ let "hits" "hits + 1";
+ }
+
+ let "a" "email_part(a, 'domain')";
+ let "b" "email_part(b, 'domain')";
+
+ if eval "a != b && levenshtein_distance(a, b) < 4" {
+ let "hits" "hits + 1";
+ }
+
+ let "combinations" "combinations + 1";
+ }
+ }
+
+ if eval "hits / combinations > 0.65" {
+ let "t.SUSPICIOUS_RECIPS" "1";
+ }
+ }
+
+ # Check for spaces in recipient addresses
+ let "raw_to" "header.to:cc[*].raw";
+ let "i" "len(raw_to)";
+ while "i != 0" {
+ let "i" "i - 1";
+ let "raw_addr" "rsplit(raw_to[i], '<')[0]";
+ if eval "contains(raw_addr, '>') && (starts_with(raw_addr, ' ' ) || ends_with(raw_addr, ' >'))" {
+ let "t.TO_WRAPPED_IN_SPACES" "1";
+ break;
+ }
+ }
+
+} else {
+ let "t.RCPT_COUNT_ZERO" "1";
+
+ if eval "contains(to_raw, 'undisclosed') && contains(to_raw, 'recipients')" {
+ let "t.R_UNDISC_RCPT" "1";
+ }
+}
+
+
+#### Script subject.sieve ####
+
+
+let "raw_subject_lc" "to_lowercase(header.subject.raw)";
+let "is_ascii_subject" "is_ascii(subject_lc)";
+
+if eval "len(subject_clean) >= 10 && count(tokenize(subject_clean, 'words')) > 1 && is_uppercase(subject_clean)" {
+ # Subject contains mostly capital letters
+ let "t.SUBJ_ALL_CAPS" "1";
+}
+
+if eval "count_chars(subject_clean) > 200" {
+ # Subject is very long
+ let "t.LONG_SUBJ" "1";
+}
+
+if eval "!is_empty(tokenize(subject_lc, 'uri_strict'))" {
+ # Subject contains a URL
+ let "t.URL_IN_SUBJECT" "1";
+}
+
+if eval "!is_ascii(raw_subject_lc) && !env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" {
+ # Subject needs encoding
+ let "t.SUBJECT_NEEDS_ENCODING" "1";
+}
+
+if eval "!header.Subject.exists" {
+ # Missing subject header
+ let "t.MISSING_SUBJECT" "1";
+} elsif eval "is_empty(trim(subject_lc))" {
+ # Subject is empty
+ let "t.EMPTY_SUBJECT" "1";
+}
+
+if eval "is_ascii(subject_lc) && contains(raw_subject_lc, '=?') && contains(raw_subject_lc, '?=')" {
+ if eval "contains(raw_subject_lc, '?q?')" {
+ # Subject header is unnecessarily encoded in quoted-printable
+ let "t.SUBJ_EXCESS_QP" "1";
+ } elsif eval "contains(raw_subject_lc, '?b?')" {
+ # Subject header is unnecessarily encoded in base64
+ let "t.SUBJ_EXCESS_BASE64" "1";
+ }
+}
+
+if eval "starts_with(subject_lc, 're:') && is_empty(header.in-reply-to) && is_empty(header.references)" {
+ # Fake reply
+ let "t.FAKE_REPLY" "1";
+}
+
+let "subject_lc_trim" "trim_end(subject_lc)";
+if eval "subject_lc != subject_lc_trim" {
+ # Subject ends with space characters
+ let "t.SUBJECT_ENDS_SPACES" "1";
+}
+
+if eval "contains(subject_lc, '$') ||
+ contains(subject_lc, '€') ||
+ contains(subject_lc, '£') ||
+ contains(subject_lc, 'Â¥')" {
+ # Subject contains currency symbols
+ let "t.SUBJECT_HAS_CURRENCY" "1";
+}
+
+if eval "ends_with(subject_lc_trim, '!')" {
+ # Subject ends with an exclamation mark
+ let "t.SUBJECT_ENDS_EXCLAIM" "1";
+} elsif eval "ends_with(subject_lc_trim, '?')" {
+ # Subject ends with a question mark
+ let "t.SUBJECT_ENDS_QUESTION" "1";
+}
+
+if eval "contains(subject_lc_trim, '!')" {
+ # Subject contains an exclamation mark
+ let "t.SUBJECT_HAS_EXCLAIM" "1";
+}
+
+if eval "contains(subject_lc_trim, '?')" {
+ # Subject contains a question mark
+ let "t.SUBJECT_HAS_QUESTION" "1";
+}
+
+
+#### Script replyto.sieve ####
+
+let "rto_raw" "to_lowercase(header.reply-to.raw)";
+if eval "!is_empty(rto_raw)" {
+ let "rto_name" "to_lowercase(header.reply-to.name)";
+
+ if eval "is_email(rto_addr)" {
+ let "t.HAS_REPLYTO" "1";
+ let "rto_domain_sld" "domain_part(email_part(rto_addr, 'domain'), 'sld')";
+
+ if eval "eq_ignore_case(header.reply-to, header.from)" {
+ let "t.REPLYTO_EQ_FROM" "1";
+ } else {
+ if eval "rto_domain_sld == from_domain_sld" {
+ let "t.REPLYTO_DOM_EQ_FROM_DOM" "1";
+ } else {
+ let "is_from_list" "!is_empty(header.List-Unsubscribe:List-Id:X-To-Get-Off-This-List:X-List:Auto-Submitted[*])";
+ if eval "!is_from_list && contains_ignore_case(recipients_clean, rto_addr)" {
+ let "t.REPLYTO_EQ_TO_ADDR" "1";
+ } else {
+ let "t.REPLYTO_DOM_NEQ_FROM_DOM" "1";
+ }
+
+ if eval "!is_from_list &&
+ !eq_ignore_case(from_addr, header.to.addr) &&
+ !(count(envelope.to) == 1 && envelope.to[0] == from_addr)" {
+ let "i" "count(envelope.to)";
+ let "found_domain" "0";
+
+ while "i != 0" {
+ let "i" "i - 1";
+
+ if eval "domain_part(email_part(envelope.to[i], 'domain'), 'sld') == from_domain_sld" {
+ let "found_domain" "1";
+ break;
+ }
+ }
+
+ if eval "!found_domain" {
+ let "t.SPOOF_REPLYTO" "1";
+ }
+ }
+ }
+
+ if eval "!is_empty(rto_name) && eq_ignore_case(rto_name, header.from.name)" {
+ let "t.REPLYTO_DN_EQ_FROM_DN" "1";
+ }
+ }
+
+ if eval "rto_addr == envelope.from" {
+ let "t.REPLYTO_ADDR_EQ_FROM" "1";
+ }
+
+ if eval "key_exists('spam-free', rto_domain_sld)" {
+ let "t.FREEMAIL_REPLYTO" "1";
+ if eval "rto_domain_sld != from_domain_sld && key_exists('spam-free', from_domain_sld)" {
+ let "t.FREEMAIL_REPLYTO_NEQ_FROM_DOM" "1";
+ }
+ } elsif eval "key_exists('spam-disposable', rto_domain_sld)" {
+ let "t.DISPOSABLE_REPLYTO" "1";
+ }
+
+ } else {
+ let "t.REPLYTO_UNPARSEABLE" "1";
+ }
+
+ if eval "is_ascii(header.reply-to) && contains(rto_raw, '=?') && contains(rto_raw, '?=')" {
+ if eval "contains(rto_raw, '?q?')" {
+ # Reply-To header is unnecessarily encoded in quoted-printable
+ let "t.REPLYTO_EXCESS_QP" "1";
+ } elsif eval "contains(rto_raw, '?b?')" {
+ # Reply-To header is unnecessarily encoded in base64
+ let "t.REPLYTO_EXCESS_BASE64" "1";
+ }
+ }
+
+ if eval "contains(rto_name, 'mr. ') || contains(rto_name, 'ms. ') || contains(rto_name, 'mrs. ') || contains(rto_name, 'dr. ')" {
+ let "t.REPLYTO_EMAIL_HAS_TITLE" "1";
+ }
+}
+
+
+
+#### Script date.sieve ####
+
+if eval "header.date.exists" {
+ let "date" "header.date.date";
+
+ if eval "date != 0" {
+ let "date_diff" "env.now - date";
+
+ if eval "date_diff > 86400" {
+ # Older than a day
+ let "t.DATE_IN_PAST" "1";
+ } elsif eval "-date_diff > 7200" {
+ # More than 2 hours in the future
+ let "t.DATE_IN_FUTURE" "1";
+ }
+ } else {
+ let "t.INVALID_DATE" "1";
+ }
+} else {
+ let "t.MISSING_DATE" "1";
+}
+
+
+#### Script messageid.sieve ####
+
+let "mid_raw" "trim(header.message-id.raw)";
+
+if eval "!is_empty(mid_raw)" {
+ let "mid_lcase" "to_lowercase(header.message-id)";
+ let "mid_rhs" "email_part(mid_lcase, 'domain')";
+
+ if eval "!is_empty(mid_rhs)" {
+ if eval "starts_with(mid_rhs, '[') && ends_with(mid_rhs, ']') && is_ip_addr(strip_suffix(strip_prefix(mid_rhs, '['), ']'))" {
+ let "t.MID_RHS_IP_LITERAL" "1";
+ } elsif eval "is_ip_addr(mid_rhs)" {
+ let "t.MID_BARE_IP" "1";
+ } elsif eval "!contains(mid_rhs, '.')" {
+ let "t.MID_RHS_NOT_FQDN" "1";
+ }
+
+ if eval "starts_with(mid_rhs, 'www.')" {
+ let "t.MID_RHS_WWW" "1";
+ }
+
+ if eval "!is_ascii(mid_raw) || contains(mid_raw, '(') || starts_with(mid_lcase, '@')" {
+ let "t.INVALID_MSGID" "1";
+ }
+
+ # From address present in Message-ID checks
+ let "sender" "from_addr";
+ if eval "is_empty(sender)" {
+ let "sender" "envelope.from";
+ }
+ if eval "!is_empty(sender)" {
+ if eval "contains(mid_lcase, sender)" {
+ let "t.MID_CONTAINS_FROM" "1";
+ } else {
+ let "from_domain" "email_part(sender, 'domain')";
+ let "mid_sld" "domain_part(mid_rhs, 'sld')";
+
+ if eval "mid_rhs == from_domain" {
+ let "t.MID_RHS_MATCH_FROM" "1";
+ } elsif eval "!is_empty(mid_sld) && domain_part(from_domain, 'sld') == mid_sld" {
+ let "t.MID_RHS_MATCH_FROMTLD" "1";
+ }
+ }
+ }
+
+ # To/Cc addresses present in Message-ID checks
+ let "recipients_len" "count(recipients)";
+ let "i" "0";
+
+ while "i < recipients_len" {
+ let "rcpt" "recipients[i]";
+ let "i" "i + 1";
+ if eval "contains(mid_lcase, rcpt)" {
+ let "t.MID_CONTAINS_TO" "1";
+ } elsif eval "email_part(rcpt, 'domain') == mid_rhs" {
+ let "t.MID_RHS_MATCH_TO" "1";
+ }
+ }
+ } else {
+ let "t.INVALID_MSGID" "1";
+ }
+
+ if eval "!starts_with(mid_raw, '<') || !contains(mid_raw, '>')" {
+ let "t.MID_MISSING_BRACKETS" "1";
+ }
+
+} else {
+ let "t.MISSING_MID" "1";
+}
+
+
+
+#### Script received.sieve ####
+
+let "rcvd_raw" "header.received[*].raw";
+let "rcvd_count" "count(rcvd_raw)";
+
+# Count received headers
+if eval "rcvd_count == 0" {
+ let "t.RCVD_COUNT_ZERO" "1";
+} elsif eval "rcvd_count == 1" {
+ let "t.RCVD_COUNT_ONE" "1";
+} elsif eval "rcvd_count == 2" {
+ let "t.RCVD_COUNT_TWO" "1";
+} elsif eval "rcvd_count == 3" {
+ let "t.RCVD_COUNT_THREE" "1";
+} elsif eval "rcvd_count <= 5" {
+ let "t.RCVD_COUNT_FIVE" "1";
+} elsif eval "rcvd_count <= 7" {
+ let "t.RCVD_COUNT_SEVEN" "1";
+} elsif eval "rcvd_count <= 12" {
+ let "t.RCVD_COUNT_TWELVE" "1";
+}
+
+# Received from an authenticated user
+if eval "!is_empty(env.authenticated_as)" {
+ let "t.RCVD_VIA_SMTP_AUTH" "1";
+}
+
+# Received headers have non-ASCII characters
+if eval "!is_ascii(rcvd_raw)" {
+ let "t.RCVD_ILLEGAL_CHARS" "1";
+}
+
+let "i" "0";
+let "tls_count" "0";
+let "rcvd_from_ip" "0";
+while "i < rcvd_count" {
+ let "i" "i + 1";
+ let "helo_domain" "received_part(i, 'from')";
+
+ # Check for a forged received trail
+ if eval "!t.FORGED_RCVD_TRAIL" {
+ let "iprev" "received_part(i, 'iprev')";
+
+ if eval "!is_empty(iprev) && !is_empty(helo_domain) && !eq_ignore_case(helo_domain, iprev)" {
+ let "t.FORGED_RCVD_TRAIL" "1";
+ }
+ }
+
+ if eval "!t.PREVIOUSLY_DELIVERED" {
+ let "for" "received_part(i, 'for')";
+ # Recipient appears on Received trail
+ if eval "!is_empty(for) && contains_ignore_case(recipients, for)" {
+ let "t.PREVIOUSLY_DELIVERED" "1";
+ }
+ }
+
+ if eval "!t.RCVD_HELO_USER && eq_ignore_case(helo_domain, 'user')" {
+ # Received: HELO contains 'user'
+ let "t.RCVD_HELO_USER" "1";
+ }
+
+ if eval "!is_empty(received_part(i, 'from.ip'))" {
+ # Received from an IP address rather than a FQDN
+ let "rcvd_from_ip" "rcvd_from_ip + 1";
+ }
+
+ if eval "!is_empty(received_part(i, 'tls'))" {
+ # Received with TLS
+ let "tls_count" "tls_count + 1";
+ }
+}
+
+if eval "rcvd_from_ip >= 2 || (rcvd_from_ip == 1 && is_ip_addr(env.helo_domain))" {
+ # Has two or more Received headers containing bare IP addresses
+ let "t.RCVD_DOUBLE_IP_SPAM" "1";
+}
+
+if eval "rcvd_count == 0" {
+ # One received header in a message (currently zero but one header will be added later by the MTA)
+ let "t.ONCE_RECEIVED" "1";
+
+ # Message has been directly delivered from MUA to local MX
+ if eval "header.User-Agent.exists || header.X-Mailer.exists" {
+ let "t.DIRECT_TO_MX" "1";
+ }
+}
+
+# Received with TLS checks
+if eval "rcvd_count > 0 && tls_count == rcvd_count && !is_empty(env.tls.version)" {
+ let "t.RCVD_TLS_ALL" "1";
+} elsif eval "!is_empty(env.tls.version)" {
+ let "t.RCVD_TLS_LAST" "1";
+} else {
+ let "t.RCVD_NO_TLS_LAST" "1";
+}
+
+
+#### Script headers.sieve ####
+
+# Mailing list scores
+let "ml_score" "count(header.List-Id:List-Archive:List-Owner:List-Help:List-Post:X-Loop:List-Subscribe:List-Unsubscribe[*].exists) * 0.125";
+if eval "ml_score < 1" {
+ if eval "header.List-Id.exists" {
+ let "ml_score" "ml_score + 0.50";
+ }
+ if eval "header.List-Subscribe.exists && header.List-Unsubscribe.exists" {
+ let "ml_score" "ml_score + 0.25";
+ }
+ if eval "header.Precedence.exists && (eq_ignore_case(header.Precedence, 'list') || eq_ignore_case(header.Precedence, 'bulk'))" {
+ let "ml_score" "ml_score + 0.25";
+ }
+}
+if eval "ml_score >= 1" {
+ let "t.MAILLIST" "1";
+}
+
+# X-Priority
+if eval "header.x-priority.exists" {
+ let "xp" "header.x-priority";
+ if eval "xp == 0" {
+ let "t.HAS_X_PRIO_ZERO" "1";
+ } elsif eval "xp == 1" {
+ let "t.HAS_X_PRIO_ONE" "1";
+ } elsif eval "xp == 2" {
+ let "t.HAS_X_PRIO_TWO" "1";
+ } elsif eval "xp <= 4" {
+ let "t.HAS_X_PRIO_THREE" "1";
+ } elsif eval "xp >= 5" {
+ let "t.HAS_X_PRIO_FIVE" "1";
+ }
+}
+
+let "unique_header_names" "to_lowercase(header.Content-Type:Content-Transfer-Encoding:Date:From:Sender:Reply-To:To:Cc:Bcc:Message-ID:In-Reply-To:References:Subject[*].raw_name)";
+let "unique_header_names_len" "count(unique_header_names)";
+if eval "unique_header_names_len != count(dedup(unique_header_names))" {
+ let "t.MULTIPLE_UNIQUE_HEADERS" "1";
+} elsif eval "unique_header_names_len == 0" {
+ let "t.MISSING_ESSENTIAL_HEADERS" "1";
+}
+
+# Wrong case X-Mailer
+if eval "header.x-mailer.exists && header.x-mailer.raw_name != 'X-Mailer'" {
+ let "t.XM_CASE" "1";
+}
+
+# Has organization header
+if eval "header.organization:organisation.exists" {
+ let "t.HAS_ORG_HEADER" "1";
+}
+
+# Has X-Originating-IP header
+if eval "header.X-Originating-IP.exists" {
+ let "t.HAS_XOIP" "1";
+}
+
+# Has List-Unsubscribe header
+if eval "header.List-Unsubscribe.exists" {
+ let "t.HAS_LIST_UNSUB" "1";
+}
+
+# Missing version number in X-Mailer or User-Agent headers
+if eval "(header.X-Mailer.exists && !has_digits(header.X-Mailer)) || (header.User-Agent.exists && !has_digits(header.User-Agent))" {
+ let "t.XM_UA_NO_VERSION" "1";
+}
+
+# Precedence is bulk
+if eval "eq_ignore_case(header.Precedence, 'bulk')" {
+ let "t.PRECEDENCE_BULK" "1";
+}
+
+# Upstream SPAM filtering
+if eval "contains_ignore_case(header.X-KLMS-AntiSpam-Status, 'spam')" {
+ # Kaspersky Security for Mail Server says this message is spam
+ let "t.KLMS_SPAM" "1";
+}
+let "spam_hdr" "to_lowercase(header.X-Spam:X-Spam-Flag:X-Spam-Status)";
+if eval "contains(spam_hdr, 'yes') || contains(spam_hdr, 'true') || contains(spam_hdr, 'spam')" {
+ # Message was already marked as spam
+ let "t.SPAM_FLAG" "1";
+}
+if eval "contains_ignore_case(header.X-UI-Filterresults:X-UI-Out-Filterresults, 'junk')" {
+ # United Internet says this message is spam
+ let "t.UNITEDINTERNET_SPAM" "1";
+}
+
+# Compromised hosts
+if eval "header.X-PHP-Originating-Script.exists" {
+ let "t.HAS_X_POS" "1";
+ if eval "contains(header.X-PHP-Originating-Script, 'eval()')" {
+ let "t.X_PHP_EVAL" "1";
+ }
+ if eval "contains(header.X-PHP-Originating-Script, '../')" {
+ let "t.HIDDEN_SOURCE_OBJ" "1";
+ }
+}
+if eval "header.X-PHP-Script.exists" {
+ let "t.HAS_X_PHP_SCRIPT" "1";
+ if eval "contains(header.X-PHP-Script, 'eval()')" {
+ let "t.X_PHP_EVAL" "1";
+ }
+ if eval "contains(header.X-PHP-Script, 'sendmail.php')" {
+ let "t.PHP_XPS_PATTERN" "1";
+ }
+ if eval "contains(header.X-PHP-Script, '../')" {
+ let "t.HIDDEN_SOURCE_OBJ" "1";
+ }
+}
+if eval "contains_ignore_case(header.X-Mailer, 'PHPMailer')" {
+ let "t.HAS_PHPMAILER_SIG" "1";
+}
+if eval "header.X-Source:X-Source-Args:X-Source-Dir.exists" {
+ let "t.HAS_X_SOURCE" "1";
+ if eval "contains(header.X-Source-Args, '../')" {
+ let "t.HIDDEN_SOURCE_OBJ" "1";
+ }
+}
+if eval "contains(header.X-Authenticated-Sender, ': ')" {
+ let "t.HAS_X_AS" "1";
+}
+if eval "contains(header.X-Get-Message-Sender-Via, 'authenticated_id:')" {
+ let "t.HAS_X_GMSV" "1";
+}
+if eval "header.X-AntiAbuse.exists" {
+ let "t.HAS_X_ANTIABUSE" "1";
+}
+if eval "header.X-Authentication-Warning.exists" {
+ let "t.HAS_XAW" "1";
+}
+
+# Check for empty delimiters in raw headers
+let "raw_headers" "header.from:to:cc:subject:reply-to:date[*].raw";
+let "i" "count(raw_headers)";
+while "i > 0" {
+ let "i" "i - 1";
+ if eval "!starts_with(raw_headers[i], ' ')" {
+ let "t.HEADER_EMPTY_DELIMITER" "1";
+ break;
+ }
+}
+
+
+#### Script bounce.sieve ####
+
+
+if eval "(contains(subject_lc, 'delivery') &&
+ (contains(subject_lc, 'failed') ||
+ contains(subject_lc, 'report') ||
+ contains(subject_lc, 'status') ||
+ contains(subject_lc, 'warning'))) ||
+ (contains(subject_lc, 'failure') &&
+ (contains(subject_lc, 'delivery') ||
+ contains(subject_lc, 'notice') ||
+ contains(subject_lc, 'mail') )) ||
+ (contains(subject_lc, 'delivered') &&
+ (contains(subject_lc, 'couldn\\'t be') ||
+ contains(subject_lc, 'could not be') ||
+ contains(subject_lc, 'hasn\\'t been') ||
+ contains(subject_lc, 'has not been'))) ||
+ contains(subject_lc, 'returned mail') ||
+ contains(subject_lc, 'undeliverable') ||
+ contains(subject_lc, 'undelivered')" {
+ # Subject contains words or phrases typical for DSN
+ let "t.SUBJ_BOUNCE_WORDS" "1";
+}
+
+if eval "is_empty(envelope.from)" {
+ if eval "eq_ignore_case(header.content-type, 'multipart/report') &&
+ ( eq_ignore_case(header.content-type.attr.report-type, 'delivery-status') ||
+ eq_ignore_case(header.content-type.attr.report-type, 'disposition-notification'))" {
+ let "t.BOUNCE" "1";
+ } else {
+ let "from" "to_lowercase(header.from)";
+
+ if eval "contains(from, 'mdaemon') && !is_empty(header.X-MDDSN-Message)" {
+ let "t.BOUNCE" "1";
+ } elsif eval "contains(from, 'postmaster') || contains(from, 'mailer-daemon')" {
+ if eval "t.SUBJ_BOUNCE_WORDS" {
+ let "t.BOUNCE" "1";
+ } else {
+ foreverypart {
+ if eval "(eq_ignore_case(header.content-type.type, 'message') ||
+ eq_ignore_case(header.content-type.type, 'text')) &&
+ (eq_ignore_case(header.content-type.subtype, 'rfc822-headers') ||
+ eq_ignore_case(header.content-type.subtype, 'rfc822'))" {
+ let "t.BOUNCE" "1";
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+#### Script html.sieve ####
+
+
+# Message only has text/html MIME parts
+if eval "header.content-type == 'text/html'" {
+ let "t.MIME_HTML_ONLY" "1";
+}
+
+foreverypart {
+ if eval "eq_ignore_case(header.content-type, 'text/html')" {
+ # Tokenize HTML
+ let "is_body_part" "is_body()";
+ let "html_tokens" "tokenize(part.text, 'html')";
+ let "html_tokens_len" "len(html_tokens)";
+ let "html_char_count" "0";
+ let "html_space_count" "0";
+ let "html_img_words" "0";
+ let "html_words" "0";
+ let "has_link_to_img" "0";
+ let "has_uri" "0";
+ let "has_text" "0";
+ let "in_head" "0";
+ let "in_body" "0";
+ let "in_anchor" "0";
+ let "in_anchor_href_ip" "0";
+ let "in_anchor_href" "";
+
+ let "i" "0";
+ while "i < html_tokens_len" {
+ let "token" "html_tokens[i]";
+ let "i" "i + 1";
+
+ # Tokens starting with '_' are text nodes
+ if eval "starts_with(token, '_')" {
+ if eval "in_head == 0" {
+ let "html_char_count" "html_char_count + count_chars(token)";
+ let "html_space_count" "html_space_count + count_spaces(token)";
+
+ let "text" "to_lowercase(trim(strip_prefix(token, '_')))";
+ let "html_words" "html_words + len(tokenize(text, 'words'))";
+
+ let "uris" "tokenize(text, 'uri')";
+
+ if eval "!is_empty(uris)" {
+ let "has_uri" "1";
+ let "uri" "uris[0]";
+
+ if eval "in_anchor && !is_empty(in_anchor_href)" {
+ if eval "contains(text, '://') &&
+ uri_part(uri, 'scheme') != uri_part(in_anchor_href, 'scheme')" {
+ # The anchor text contains a distinct scheme compared to the target URL
+ let "t.HTTP_TO_HTTPS" "1";
+ }
+ if eval "(!in_anchor_href_ip && (domain_part(uri_part(uri, 'host'), 'sld') != domain_part(uri_part(in_anchor_href, 'host'), 'sld'))) ||
+ (in_anchor_href_ip && (uri_part(uri, 'host') != uri_part(in_anchor_href, 'host')))" {
+ let "t.PHISHING" "1";
+ }
+ }
+ } elsif eval "!is_empty(text)" {
+ let "has_text" "1";
+ }
+ }
+ } elsif eval "starts_with(token, '<img')" {
+ if eval "is_body_part" {
+ let "dimensions" "html_attr_size(token, 'width', 800) + html_attr_size(token, 'height', 600)";
+
+ if eval "in_anchor && dimensions >= 210" {
+ let "has_link_to_img" "1";
+ }
+ if eval "dimensions > 100" {
+ # We assume that a single picture 100x200 contains approx 3 words of text
+ let "html_img_words" "html_img_words + dimensions / 100";
+ }
+
+ let "img_src" "html_attr(token, 'src')";
+ if eval "starts_with(img_src, 'data:') && contains(img_src, ';base64,')" {
+ # Has Data URI encoding
+ let "t.HAS_DATA_URI" "1";
+ }
+ }
+ } elsif eval "starts_with(token, '<head')" {
+ let "in_head" "in_head + 1";
+ } elsif eval "starts_with(token, '</head')" {
+ let "in_head" "in_head - 1";
+ } elsif eval "starts_with(token, '<body')" {
+ let "in_body" "in_body + 1";
+ } elsif eval "starts_with(token, '</body')" {
+ let "in_body" "in_body - 1";
+ } elsif eval "starts_with(token, '<a ')" {
+ let "in_anchor" "1";
+ let "in_anchor_href_ip" "0";
+ let "in_anchor_href" "to_lowercase(trim(html_attr(token, 'href')))";
+
+ if eval "is_body_part && starts_with(in_anchor_href, 'data:') && contains(in_anchor_href, ';base64,')" {
+ # Has Data URI encoding
+ let "t.HAS_DATA_URI" "1";
+ if eval "contains(in_anchor_href, 'text/')" {
+ # Uses Data URI encoding to obfuscate plain or HTML in base64
+ let "t.DATA_URI_OBFU" "1";
+ }
+ } elsif eval "is_ip_addr(uri_part(in_anchor_href, 'host'))" {
+ # HTML anchor points to an IP address
+ let "t.HTTP_TO_IP" "1";
+ let "in_anchor_href_ip" "1";
+ }
+ } elsif eval "in_anchor && starts_with(token, '</a')" {
+ let "in_anchor" "0";
+ } elsif eval "starts_with(token, '<meta ')" {
+ if eval "eq_ignore_case(html_attr(token, 'http-equiv'), 'refresh') &&
+ contains_ignore_case(html_attr(token, 'content'), 'url=')" {
+ # HTML meta refresh tag
+ let "t.HTML_META_REFRESH_URL" "1";
+ }
+ } elsif eval "starts_with(token, '<link') && is_body_part &&
+ (contains_ignore_case(html_attr(token, 'rel'), 'stylesheet') ||
+ contains_ignore_case(html_attr(token, 'href'), '.css') )" {
+ let "t.EXT_CSS" "1";
+ }
+ }
+
+ if eval "is_body_part" {
+ # Check for unbalanced tags
+ if eval "in_head != 0 || in_body != 0" {
+ let "t.HTML_UNBALANCED_TAG" "1";
+ }
+
+ # Check for short HTML parts with a link to an image
+ if eval "has_link_to_img" {
+ if eval "html_char_count < 1024" {
+ let "t.HTML_SHORT_LINK_IMG_1" "1";
+ } elsif eval "html_char_count < 1536" {
+ let "t.HTML_SHORT_LINK_IMG_2" "1";
+ } elsif eval "html_char_count < 2048" {
+ let "t.HTML_SHORT_LINK_IMG_3" "1";
+ }
+ }
+
+ if eval "(!has_link_to_img || html_char_count >= 2048) &&
+ (html_img_words / (html_words + html_img_words) > 0.5)" {
+ # Message contains more images than text
+ let "t.HTML_TEXT_IMG_RATIO" "1";
+ }
+
+ if eval "has_uri && !has_text" {
+ let "t.BODY_URI_ONLY" "1";
+ }
+ }
+ }
+}
+
+
+
+#### Script mime.sieve ####
+
+if eval "!header.mime-version.exists" {
+ if eval "header.content-type.exists || header.content-transfer-encoding.exists" {
+ let "t.MISSING_MIME_VERSION" "1";
+ }
+} elsif eval "header.mime-version.raw_name != 'MIME-Version'" {
+ let "t.MV_CASE" "1";
+}
+
+let "has_text_part" "0";
+let "is_encrypted" "0";
+let "parts_num" "0";
+let "parts_max_len" "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)";
+ let "subtype" "to_lowercase(header.content-type.subtype)";
+ let "cte" "header.content-transfer-encoding";
+ let "part_is_attachment" "is_attachment()";
+
+ if eval "cte != '' && !is_lowercase(cte)" {
+ let "cte" "to_lowercase(cte)";
+ let "t.CTE_CASE" "1";
+ }
+
+ if eval "ends_with(header.content-type.raw, ';')" {
+ # Content-Type header ends with a semi-colon
+ let "t.CT_EXTRA_SEMI" "1";
+ }
+
+ if eval "type == 'multipart'" {
+ if eval "subtype == 'alternative'" {
+ let "has_plain_part" "0";
+ let "has_html_part" "0";
+
+ let "text_part_words" "";
+ let "text_part_uris" "0";
+
+ let "html_part_words" "";
+ let "html_part_uris" "0";
+
+ foreverypart {
+ let "ma_ct" "to_lowercase(header.content-type)";
+
+ if eval "!has_plain_part && ma_ct == 'text/plain'" {
+ let "text_part" "part.text";
+ let "text_part_words" "tokenize(text_part, 'words')";
+ let "text_part_uris" "count(tokenize(text_part, 'uri_strict'))";
+ let "has_plain_part" "1";
+ } elsif eval "!has_html_part && ma_ct == 'text/html'" {
+ let "html_part" "html_to_text(part.text)";
+ let "html_part_words" "tokenize(html_part, 'words')";
+ let "html_part_uris" "count(tokenize(html_part, 'uri_strict'))";
+ let "has_html_part" "1";
+ }
+ }
+
+ # Multipart message mostly text/html MIME
+ if eval "has_html_part" {
+ if eval "!has_plain_part" {
+ let "t.MIME_MA_MISSING_TEXT" "1";
+ }
+ } elsif eval "has_plain_part" {
+ let "t.MIME_MA_MISSING_HTML" "1";
+ }
+
+ # HTML and text parts are different
+ if eval "!t.R_PARTS_DIFFER && has_html_part && has_plain_part &&
+ (!is_empty(text_part_words) || !is_empty(html_part_words)) &&
+ cosine_similarity(text_part_words, html_part_words) < 0.95" {
+ let "t.R_PARTS_DIFFER" "1";
+ }
+
+ # Odd URI count between parts
+ if eval "text_part_uris != html_part_uris" {
+ set "t.URI_COUNT_ODD" "1";
+ }
+ } elsif eval "subtype == 'mixed'" {
+ let "num_text_parts" "0";
+ let "has_other_part" "0";
+
+ foreverypart {
+ if eval "eq_ignore_case(header.content-type.type, 'text') && !is_attachment()" {
+ let "num_text_parts" "num_text_parts + 1";
+ } elsif eval "!eq_ignore_case(header.content-type.type, 'multipart')" {
+ let "has_other_part" "1";
+ }
+ }
+
+ # Found multipart/mixed without non-textual part
+ if eval "!has_other_part && num_text_parts < 3" {
+ let "t.CTYPE_MIXED_BOGUS" "1";
+ }
+ } elsif eval "subtype == 'encrypted'" {
+ set "is_encrypted" "1";
+ }
+ } else {
+ if eval "type == 'text'" {
+ # MIME text part claims to be ASCII but isn't
+ if eval "cte == '' || cte == '7bit'" {
+ if eval "!is_ascii(part.raw)" {
+ let "t.R_BAD_CTE_7BIT" "1";
+ }
+ } else {
+ if eval "cte == 'base64'" {
+ if eval "is_ascii(part.text)" {
+ # Has text part encoded in base64 that does not contain any 8bit characters
+ let "t.MIME_BASE64_TEXT_BOGUS" "1";
+ } else {
+ # Has text part encoded in base64
+ let "t.MIME_BASE64_TEXT" "1";
+ }
+ }
+
+ if eval "subtype == 'plain' && is_empty(header.content-type.attr.charset)" {
+ # Charset header is missing
+ let "t.R_MISSING_CHARSET" "1";
+ }
+ }
+ let "has_text_part" "1";
+ } elsif eval "type == 'application'" {
+ if eval "subtype == 'pkcs7-mime'" {
+ let "t.ENCRYPTED_SMIME" "1";
+ let "part_is_attachment" "0";
+ } elsif eval "subtype == 'pkcs7-signature'" {
+ let "t.SIGNED_SMIME" "1";
+ let "part_is_attachment" "0";
+ } elsif eval "subtype == 'pgp-encrypted'" {
+ let "t.ENCRYPTED_PGP" "1";
+ let "part_is_attachment" "0";
+ } elsif eval "subtype == 'pgp-signature'" {
+ let "t.SIGNED_PGP" "1";
+ let "part_is_attachment" "0";
+ } elsif eval "subtype == 'octet-stream'" {
+ if eval "!is_encrypted &&
+ !header.content-id.exists &&
+ (!header.content-disposition.exists ||
+ (!eq_ignore_case(header.content-disposition.type, 'attachment') &&
+ is_empty(header.content-disposition.attr.filename)))" {
+ let "t.CTYPE_MISSING_DISPOSITION" "1";
+ }
+ }
+ }
+
+ # Increase part count
+ let "parts_num" "parts_num + 1";
+ if eval "parts_num == 1" {
+ let "parts_len" "mime_part_len()";
+ if eval "parts_len > parts_max_len" {
+ let "parts_max_len" "parts_len";
+ }
+ }
+ }
+
+ if eval "is_empty(type) && header.content-type.exists" {
+ let "t.BROKEN_CONTENT_TYPE" "1";
+ }
+
+ if eval "part_is_attachment" {
+ # Has a MIME attachment
+ let "t.HAS_ATTACHMENT" "1";
+
+ # Detect and compare mime type
+ let "detected_mime_type" "detect_file_type('mime')";
+ if eval "!is_empty(detected_mime_type)" {
+ if eval "detected_mime_type == content_type" {
+ # Known content-type
+ let "t.MIME_GOOD" "1";
+ } elsif eval "content_type != 'application/octet-stream'" {
+ # Known bad content-type
+ let "t.MIME_BAD" "1";
+ }
+ }
+ }
+
+ # Analyze attachment name
+ let "attach_name" "attachment_name()";
+ if eval "!is_empty(attach_name)" {
+ if eval "has_obscured(attach_name)" {
+ let "t.MIME_BAD_UNICODE" "1";
+ }
+ let "name_parts" "rsplit(to_lowercase(attach_name), '.')";
+ if eval "count(name_parts) > 1" {
+ let "ext_type" "key_get('spam-mime', name_parts[0])";
+ if eval "!is_empty(ext_type)" {
+ let "ext_type_double" "key_get('spam-mime', name_parts[1])";
+ if eval "contains(ext_type, 'BAD')" {
+ # Bad extension
+ if eval "contains(ext_type_double, 'BAD')" {
+ let "t.MIME_DOUBLE_BAD_EXTENSION" "1";
+ } else {
+ let "t.MIME_BAD_EXTENSION" "1";
+ }
+ }
+ if eval "contains(ext_type, 'AR') && contains(ext_type_double, 'AR')" {
+ # Archive in archive
+ let "t.MIME_ARCHIVE_IN_ARCHIVE" "1";
+ }
+
+ if eval "contains(ext_type, '/') &&
+ content_type != 'application/octet-stream' &&
+ !contains(split(ext_type, '|'), content_type)" {
+ # Invalid attachment mime type
+ let "t.MIME_BAD_ATTACHMENT" "1";
+ }
+ }
+ }
+ }
+
+}
+
+# Message contains both text and encrypted parts
+if eval "has_text_part && (t.ENCRYPTED_SMIME || t.SIGNED_SMIME || t.ENCRYPTED_PGP || t.SIGNED_PGP)" {
+ let "t.BOGUS_ENCRYPTED_AND_TEXT" "1";
+}
+
+# Message contains only one short part
+if eval "parts_num == 1 && parts_max_len < 64" {
+ let "t.SINGLE_SHORT_PART" "1";
+} elsif eval "parts_max_len == 0" {
+ let "t.COMPLETELY_EMPTY" "1";
+}
+
+# Check for mixed script in body
+if eval "!is_single_script(text_body)" {
+ let "t.R_MIXED_CHARSET" "1";
+}
+
+
+#### Script dmarc.sieve ####
+
+if eval "env.spf.result == 'pass'" {
+ let "t.SPF_ALLOW" "1";
+} elsif eval "env.spf.result == 'fail'" {
+ let "t.SPF_FAIL" "1";
+} elsif eval "env.spf.result == 'softfail'" {
+ let "t.SPF_SOFTFAIL" "1";
+} elsif eval "env.spf.result == 'neutral'" {
+ let "t.SPF_NEUTRAL" "1";
+} elsif eval "env.spf.result == 'temperror'" {
+ let "t.SPF_DNSFAIL" "1";
+} elsif eval "env.spf.result == 'permerror'" {
+ let "t.SPF_PERMFAIL" "1";
+} else {
+ let "t.SPF_NA" "1";
+}
+
+if eval "env.dkim.result == 'pass'" {
+ let "t.DKIM_ALLOW" "1";
+} elsif eval "env.dkim.result == 'fail'" {
+ let "t.DKIM_REJECT" "1";
+} elsif eval "env.dkim.result == 'temperror'" {
+ let "t.DKIM_TEMPFAIL" "1";
+} elsif eval "env.dkim.result == 'permerror'" {
+ let "t.DKIM_PERMFAIL" "1";
+} else {
+ let "t.DKIM_NA" "1";
+}
+
+if eval "env.arc.result == 'pass'" {
+ let "t.ARC_ALLOW" "1";
+} elsif eval "env.arc.result == 'fail'" {
+ let "t.ARC_REJECT" "1";
+} elsif eval "env.arc.result == 'temperror'" {
+ let "t.ARC_DNSFAIL" "1";
+} elsif eval "env.arc.result == 'permerror'" {
+ let "t.ARC_INVALID" "1";
+} else {
+ let "t.ARC_NA" "1";
+}
+
+if eval "env.dmarc.result == 'pass'" {
+ let "t.DMARC_POLICY_ALLOW" "1";
+} elsif eval "env.dmarc.result == 'temperror'" {
+ let "t.DMARC_DNSFAIL" "1";
+} elsif eval "env.dmarc.result == 'permerror'" {
+ let "t.DMARC_BAD_POLICY" "1";
+} elsif eval "env.dmarc.result == 'fail'" {
+ if eval "env.dmarc.policy == 'quarantine'" {
+ let "t.DMARC_POLICY_QUARANTINE" "1";
+ } elsif eval "env.dmarc.policy == 'reject'" {
+ let "t.DMARC_POLICY_REJECT" "1";
+ } else {
+ let "t.DMARC_POLICY_SOFTFAIL" "1";
+ }
+} else {
+ let "t.DMARC_NA" "1";
+}
+
+if eval "header.DKIM-Signature.exists" {
+ let "t.DKIM_SIGNED" "1";
+ if eval "header.ARC-Seal.exists" {
+ let "t.ARC_SIGNED" "1";
+ }
+}
+
+# Check allowlists
+if eval "key_exists('spam-dmarc', from_domain)" {
+ if eval "t.DMARC_POLICY_ALLOW" {
+ let "t.ALLOWLIST_DMARC" "1";
+ } else {
+ let "t.BLOCKLIST_DMARC" "1";
+ }
+} elsif eval "key_exists('spam-spdk', from_domain)" {
+ let "is_dkim_pass" "contains(env.dkim.domains, from_domain) || t.ARC_ALLOW";
+
+ if eval "is_dkim_pass && t.SPF_ALLOW" {
+ let "t.ALLOWLIST_SPF_DKIM" "1";
+ } elsif eval "is_dkim_pass" {
+ let "t.ALLOWLIST_DKIM" "1";
+ if eval "!t.SPF_DNSFAIL" {
+ let "t.BLOCKLIST_SPF" "1";
+ }
+ } elsif eval "t.SPF_ALLOW" {
+ let "t.ALLOWLIST_SPF" "1";
+ if eval "!t.DKIM_TEMPFAIL" {
+ let "t.BLOCKLIST_DKIM" "1";
+ }
+ } elsif eval "!t.SPF_DNSFAIL && !t.DKIM_TEMPFAIL" {
+ let "t.BLOCKLIST_SPF_DKIM" "1";
+ }
+}
+
+
+#### Script ip.sieve ####
+
+# Reverse ip checks
+if eval "env.iprev.result != ''" {
+ if eval "env.iprev.result == 'temperror'" {
+ let "t.RDNS_DNSFAIL" "1";
+ } elsif eval "env.iprev.result == 'fail' || env.iprev.result == 'permerror'" {
+ let "t.RDNS_NONE" "1";
+ }
+}
+
+
+#### Script helo.sieve ####
+
+if eval "!is_ip_addr(env.helo_domain)" {
+ let "helo" "env.helo_domain";
+
+ if eval "contains(helo, '.')" {
+ if eval "!is_empty(env.iprev.ptr) && !eq_ignore_case(helo, env.iprev.ptr)" {
+ # Helo does not match reverse IP
+ let "t.HELO_IPREV_MISMATCH" "1";
+ }
+ if eval "!dns_exists(helo, 'ip') && !dns_exists(helo, 'mx')" {
+ # Helo no resolve to A or MX
+ let "t.HELO_NORES_A_OR_MX" "1";
+ }
+ } else {
+ if eval "contains(helo, 'user')" {
+ # HELO contains 'user'
+ let "t.RCVD_HELO_USER" "1";
+ }
+
+ # Helo not FQDN
+ let "t.HELO_NOT_FQDN" "1";
+ }
+} else {
+ # Helo host is bare ip
+ let "t.HELO_BAREIP" "1";
+
+ if eval "env.helo_domain != env.remote_ip" {
+ # Helo A IP != hostname IP
+ let "t.HELO_IP_A" "1";
+ }
+}
+
+
+#### Script replies_in.sieve ####
+
+
+let "message_ids" "header.In-Reply-To:References";
+
+let "i" "count(message_ids)";
+while "i > 0" {
+ let "i" "i - 1";
+
+ if eval "key_exists(SPAM_DB, 'm:' + message_ids[i])" {
+ let "t.TRUSTED_REPLY" "1";
+ break;
+ }
+}
+
+
+#### Script spamtrap.sieve ####
+
+
+# Check if the message was sent to a spam trap address
+if eval "AUTOLEARN_ENABLE && key_exists('spam-trap', envelope.to)" {
+ eval "bayes_is_balanced(SPAM_DB, false, AUTOLEARN_SPAM_HAM_BALANCE) && bayes_train(SPAM_DB, body_and_subject, true)";
+ let "t.SPAM_TRAP" "1";
+
+ # Disable autolearn so the classifier is not trained twice
+ let "AUTOLEARN_ENABLE" "0";
+}
+
+
+#### Script bayes_classify.sieve ####
+
+if eval "!t.SPAM_TRAP && !t.TRUSTED_REPLY" {
+
+ # Classification parameters
+ # min_token_hits: 2
+ # min_tokens: 11
+ # min_prob_strength: 0.05
+ # min_learns: 200
+
+ let "bayes_result" "bayes_classify(SPAM_DB, 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";
+ } elsif eval "bayes_result < 0.5" {
+ let "t.BAYES_HAM" "1";
+ }
+ }
+}
+
+
+#### Script url.sieve ####
+
+if eval "(count(body_urls) == 1 || count(html_body_urls) == 1) && count(tokenize(text_body, 'words')) == 0" {
+ let "t.URL_ONLY" "1";
+}
+
+if eval "has_zwsp(urls)" {
+ let "t.ZERO_WIDTH_SPACE_URL" "1";
+} elsif eval "has_obscured(urls)" {
+ let "t.R_SUSPICIOUS_URL" "1";
+}
+
+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)" {
+ let "is_ip" "is_ip_addr(host)";
+ let "host" "puny_decode(host)";
+ let "host_lc" "to_lowercase(host)";
+ let "host_sld" "domain_part(host_lc, 'sld')";
+
+ # Skip local and trusted domains
+ if eval "is_local_domain(DOMAIN_DIRECTORY, host_sld) || key_exists('spam-allow', host_sld)" {
+ continue;
+ }
+
+ if eval "!is_ip &&
+ (!t.REDIRECTOR_URL || !t.URL_REDIRECTOR_NESTED) &&
+ key_exists('spam-redirect', host_sld)" {
+ let "t.REDIRECTOR_URL" "1";
+ let "redir_count" "1";
+
+ while "redir_count <= 5" {
+ # Use a custom user-agent and a 3 second timeout
+ let "url_redirect" "http_header(url, 'Location', 'Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/118.0', 3000)";
+ if eval "!is_empty(url_redirect)" {
+ let "url" "url_redirect";
+ let "host" "uri_part(url, 'host')";
+ let "is_ip" "is_ip_addr(host)";
+ let "host" "puny_decode(host)";
+ let "host_lc" "to_lowercase(host)";
+ let "host_sld" "domain_part(host_lc, 'sld')";
+
+ if eval "!is_ip && key_exists('spam-redirect', host_sld)" {
+ let "redir_count" "redir_count + 1";
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if eval "redir_count > 5" {
+ let "t.URL_REDIRECTOR_NESTED" "1";
+ }
+ }
+
+ let "url_lc" "to_lowercase(url)";
+ let "query" "uri_part(url_lc, 'path_query')";
+ if eval "!is_ip" {
+ if eval "!is_ascii(host)" {
+ let "host_cured" "cure_text(host)";
+ if eval "host_lc != host_cured && dns_exists(host_cured, 'ip')" {
+ let "t.HOMOGRAPH_URL" "1";
+ }
+
+ if eval "!is_single_script(host)" {
+ let "t.MIXED_CHARSET_URL" "1";
+ }
+ } else {
+ if eval "ends_with(host, 'googleusercontent.com') && starts_with(query, '/proxy/')" {
+ let "t.HAS_GUC_PROXY_URI" "1";
+ } elsif eval "ends_with(host, 'firebasestorage.googleapis.com')" {
+ let "t.HAS_GOOGLE_FIREBASE_URL" "1";
+ } elsif eval "starts_with(domain_part(host, 'sld'), 'google.') && contains(query, 'url?') " {
+ let "t.HAS_GOOGLE_REDIR" "1";
+ }
+ }
+
+ if eval "(contains(host_lc, 'ipfs.') || contains(query, '/ipfs')) && contains(query, '/qm')" {
+ # InterPlanetary File System (IPFS) gateway URL, likely malicious
+ let "t.HAS_IPFS_GATEWAY_URL" "1";
+ } elsif eval "ends_with(host_lc, '.onion')" {
+ let "t.HAS_ONION_URI" "1";
+ }
+ } else {
+ # URL is an ip address
+ let "t.R_SUSPICIOUS_URL" "1";
+ }
+
+ if eval "starts_with(query, '/wp-')" {
+ # Contains WordPress URIs
+ let "t.HAS_WP_URI" "1";
+ if eval "starts_with(query, '/wp-content') | starts_with(query, '/wp-includes')" {
+ # URL that is pointing to a compromised WordPress installation
+ let "t.WP_COMPROMISED" "1";
+ }
+ }
+ if eval "contains(query, '/../') && !contains(query, '/well-known') && !contains(query, '/well_known')" {
+ # Message contains URI with a hidden path
+ let "t.URI_HIDDEN_PATH" "1";
+ }
+
+ # Phishing checks (refresh OpenPhish every 12 hours, PhishTank every 6 hours)
+ if eval "key_exists_http('https://openphish.com/feed.txt', url, [43200, 'list'])" {
+ let "t.PHISHED_OPENPHISH" "1";
+ }
+ if eval "key_exists_http('http://data.phishtank.com/data/online-valid.csv', url, [21600, 'csv', 1, ',', true])" {
+ let "t.PHISHED_PHISHTANK" "1";
+ }
+
+ } else {
+ # URL could not be parsed
+ let "t.R_SUSPICIOUS_URL" "1";
+ }
+}
+
+
+
+#### Script rbl.sieve ####
+
+
+# Validate IP addresses
+let "ip_addresses" "dedup(winnow([ env.remote_ip ] + header.received[*].rcvd.ip + header.received[*].rcvd.from.ip + header.received[*].rcvd.by.ip))";
+let "ip_addresses_len" "count(ip_addresses)";
+let "i" "0";
+
+while "i < ip_addresses_len" {
+ let "ip_address" "ip_addresses[i]";
+ let "is_from_addr" "i == 0";
+ let "i" "i + 1";
+
+ if eval "ip_address == '127.0.0.1' || ip_address == '::1'" {
+ continue;
+ }
+
+ # Do not check more than 10 IP addresses
+ if eval "i >= 10" {
+ break;
+ }
+
+ let "ip_reverse" "ip_reverse_name(ip_address)";
+ let "is_ip_v4" "len(ip_reverse) <= 15";
+
+ # Query SPAMHAUS
+ let "result" "rsplit_once(dns_query(ip_reverse + '.zen.spamhaus.org', 'ipv4')[0], '.')";
+ if eval "result[0] == '127.0.0'" {
+ let "result" "result[1]";
+
+ if eval "result == 2" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_SBL" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_SBL" "1";
+ }
+ } elsif eval "result == 3" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_CSS" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_CSS" "1";
+ }
+ } elsif eval "result >= 4 && result <= 7" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_XBL" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_XBL" "1";
+ }
+ } elsif eval "result == 9" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_DROP" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_PBL" "1";
+ }
+ } elsif eval "result == 10 || result == 11" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_PBL" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_PBL" "1";
+ }
+ } elsif eval "result == 254" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_BLOCKED_OPENRESOLVER" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER" "1";
+ }
+ } elsif eval "result == 255" {
+ if eval "is_from_addr" {
+ let "t.RBL_SPAMHAUS_BLOCKED" "1";
+ } else {
+ let "t.RECEIVED_SPAMHAUS_BLOCKED" "1";
+ }
+ } else {
+ # Unrecognized result
+ let "t.RBL_SPAMHAUS" "1";
+ }
+ }
+
+ if eval "is_from_addr" {
+ # Query IP reputation at Mailspike
+ let "result" "rsplit_once(dns_query(ip_reverse + '.rep.mailspike.net', 'ipv4')[0], '.')";
+ if eval "result[0] == '127.0.0'" {
+ let "result" "result[1]";
+
+ if eval "result == 10" {
+ let "t.RBL_MAILSPIKE_WORST" "1";
+ } elsif eval "result == 11" {
+ let "t.RBL_MAILSPIKE_VERYBAD" "1";
+ } elsif eval "result == 12" {
+ let "t.RBL_MAILSPIKE_BAD" "1";
+ } elsif eval "result >= 13 && result <= 16" {
+ let "t.RWL_MAILSPIKE_NEUTRAL" "1";
+ } elsif eval "result == 17" {
+ let "t.RWL_MAILSPIKE_POSSIBLE" "1";
+ } elsif eval "result == 18" {
+ let "t.RWL_MAILSPIKE_GOOD" "1";
+ } elsif eval "result == 19" {
+ let "t.RWL_MAILSPIKE_VERYGOOD" "1";
+ } elsif eval "result == 20" {
+ let "t.RWL_MAILSPIKE_EXCELLENT" "1";
+ }
+ }
+
+ # Query SenderScore
+ if eval "dns_exists(ip_reverse + '.bl.score.senderscore.com', 'ipv4')" {
+ let "t.RBL_SENDERSCORE" "1";
+ }
+
+ # Query SpamEatingMonkey
+ if eval "is_ip_v4 && dns_exists(ip_reverse + '.bl.spameatingmonkey.net', 'ipv4')" {
+ let "t.RBL_SEM" "1";
+ } elsif eval "!is_ip_v4 && dns_exists(ip_reverse + '.bl.ipv6.spameatingmonkey.net', 'ipv4')" {
+ let "t.RBL_SEM_IPV6" "1";
+ }
+
+ # Query VirusFree
+ if eval "dns_query(ip_reverse + '.bip.virusfree.cz', 'ipv4')[0] == '127.0.0.2'" {
+ let "t.RBL_VIRUSFREE_BOTNET" "1";
+ }
+
+ # Query NiX
+ if eval "dns_exists(ip_reverse + '.ix.dnsbl.manitu.net', 'ipv4')" {
+ let "t.RBL_NIXSPAM" "1";
+ }
+
+ # Query Spamcop
+ if eval "dns_exists(ip_reverse + '.bl.spamcop.net', 'ipv4')" {
+ let "t.RBL_SPAMCOP" "1";
+ }
+
+ # Query Barracuda
+ if eval "dns_exists(ip_reverse + '.b.barracudacentral.org', 'ipv4')" {
+ let "t.RBL_BARRACUDA" "1";
+ }
+ }
+
+ # Query Blocklist.de
+ if eval "dns_exists(ip_reverse + '.bl.blocklist.de', 'ipv4')" {
+ if eval "is_from_addr" {
+ let "t.RBL_BLOCKLISTDE" "1";
+ } else {
+ let "t.RECEIVED_BLOCKLISTDE" "1";
+ }
+ }
+
+ # Query DNSWL
+ let "result" "rsplit_once(dns_query(ip_reverse + '.list.dnswl.org', 'ipv4')[0], '.')";
+ if eval "starts_with(result[0], '127.')" {
+ let "result" "result[1]";
+
+ if eval "result == 0" {
+ let "t.RCVD_IN_DNSWL_NONE" "1";
+ } elsif eval "result == 1" {
+ let "t.RCVD_IN_DNSWL_LOW" "1";
+ } elsif eval "result == 2" {
+ let "t.RCVD_IN_DNSWL_MED" "1";
+ } elsif eval "result == 3" {
+ let "t.RCVD_IN_DNSWL_HI" "1";
+ } elsif eval "result == 255" {
+ let "t.DNSWL_BLOCKED" "1";
+ }
+ }
+}
+
+# Validate domain names
+let "emails" "dedup(winnow(to_lowercase([from_addr, rto_addr, envelope.from] + tokenize(text_body, 'email'))))";
+let "emails_len" "count(emails)";
+let "domains" "dedup(winnow(to_lowercase([ env.helo_domain, env.iprev.ptr ] + email_part(emails, 'domain') + puny_decode(uri_part(urls, 'host')))))";
+let "domains_len" "count(domains)";
+let "i" "0";
+
+while "i < domains_len" {
+ let "domain" "domains[i]";
+ let "i" "i + 1";
+
+ # Skip invalid and local domain names
+ if eval "!contains(domain, '.') ||
+ is_ip_addr(domain) ||
+ is_local_domain(DOMAIN_DIRECTORY, domain_part(domain, 'sld')) ||
+ key_exists('spam-allow', domain)" {
+ continue;
+ }
+
+ # Do not check more than 10 domain names
+ if eval "i >= 10" {
+ break;
+ }
+
+ # Query SpamHaus DBL
+ let "result" "rsplit_once(dns_query(domain + '.dbl.spamhaus.org', 'ipv4')[0], '.')";
+ if eval "result[0] == '127.0.1'" {
+ let "result" "result[1]";
+
+ if eval "result == 2" {
+ let "t.DBL_SPAM" "1";
+ } elsif eval "result == 4" {
+ let "t.DBL_PHISH" "1";
+ } elsif eval "result == 5" {
+ let "t.DBL_MALWARE" "1";
+ } elsif eval "result == 6" {
+ let "t.DBL_BOTNET" "1";
+ } elsif eval "result == 102" {
+ let "t.DBL_ABUSE" "1";
+ } elsif eval "result == 103" {
+ let "t.DBL_ABUSE_REDIR" "1";
+ } elsif eval "result == 104" {
+ let "t.DBL_ABUSE_PHISH" "1";
+ } elsif eval "result == 105" {
+ let "t.DBL_ABUSE_MALWARE" "1";
+ } elsif eval "result == 106" {
+ let "t.DBL_ABUSE_BOTNET" "1";
+ } elsif eval "result == 254" {
+ let "t.DBL_BLOCKED_OPENRESOLVER" "1";
+ } elsif eval "result == 255" {
+ let "t.DBL_BLOCKED" "1";
+ }
+ }
+
+ # Query SURBL multi
+ let "result" "rsplit_once(dns_query(domain + '.multi.surbl.org', 'ipv4')[0], '.')";
+ if eval "result[0] == '127.0.0'" {
+ let "result" "result[1]";
+
+ if eval "result == 128" {
+ let "t.CRACKED_SURBL" "1";
+ } elsif eval "result == 64" {
+ let "t.ABUSE_SURBL" "1";
+ } elsif eval "result == 16" {
+ let "t.MW_SURBL_MULTI" "1";
+ } elsif eval "result == 8" {
+ let "t.PH_SURBL_MULTI" "1";
+ } elsif eval "result == 1" {
+ let "t.SURBL_BLOCKED" "1";
+ }
+ }
+
+ # Query URIBL multi
+ let "result" "rsplit_once(dns_query(domain + '.multi.uribl.com', 'ipv4')[0], '.')";
+ if eval "result[0] == '127.0.0'" {
+ let "result" "result[1]";
+
+ if eval "result == 1" {
+ let "t.URIBL_BLOCKED" "1";
+ } elsif eval "result == 2" {
+ let "t.URIBL_BLACK" "1";
+ } elsif eval "result == 4" {
+ let "t.URIBL_GREY" "1";
+ } elsif eval "result == 8" {
+ let "t.URIBL_RED" "1";
+ }
+ }
+
+ # Query SpamEatingMonkey URIBL
+ if eval "dns_query(domain + '.uribl.spameatingmonkey.net', 'ipv4')[0] == '127.0.0.2'" {
+ let "t.SEM_URIBL" "1";
+ }
+
+ # Query SpamEatingMonkey FRESH15
+ if eval "dns_query(domain + '.fresh15.spameatingmonkey.net', 'ipv4')[0] == '127.0.0.2'" {
+ let "t.SEM_URIBL_FRESH15" "1";
+ }
+
+}
+
+# Check DKIM domains that passed validation
+let "i" "count(env.dkim.domains)";
+while "i > 0" {
+ let "i" "i - 1";
+
+ # Query DNSWL
+ let "result" "rsplit_once(dns_query(env.dkim.domains[i] + '.dwl.dnswl.org', 'ipv4')[0], '.')";
+ if eval "starts_with(result[0], '127.')" {
+ let "result" "result[1]";
+
+ if eval "result == 0" {
+ let "t.DWL_DNSWL_NONE" "1";
+ } elsif eval "result == 1" {
+ let "t.DWL_DNSWL_LOW" "1";
+ } elsif eval "result == 2" {
+ let "t.DWL_DNSWL_MED" "1";
+ } elsif eval "result == 3" {
+ let "t.DWL_DNSWL_HI" "1";
+ } elsif eval "result == 255" {
+ let "t.DWL_DNSWL_BLOCKED" "1";
+ }
+ }
+}
+
+# Validate email addresses
+let "i" "0";
+while "i < emails_len" {
+ let "email" "emails[i]";
+ let "i" "i + 1";
+
+ # Skip invalid and local e-mail addresses
+ if eval "!contains(email, '@') || is_local_domain(DOMAIN_DIRECTORY, domain_part(email_part(email, 'domain'), 'sld'))" {
+ continue;
+ }
+
+ # Do not check more than 10 email addresses
+ if eval "i >= 10" {
+ break;
+ }
+
+ # Query MSBL EBL
+ let "result" "rsplit_once(dns_query(hash(email, 'sha1') + '.ebl.msbl.org', 'ipv4')[0], '.')";
+ if eval "result[1] == 2 || result[1] == 3" {
+ if eval "result[0] == '127.0.0'" {
+ let "t.MSBL_EBL" "1";
+ } elsif eval "result[0] == '127.0.1'" {
+ let "t.MSBL_EBL_GREY" "1";
+ }
+ }
+}
+
+
+# Validate URL hashes
+let "i" "0";
+let "urls_len" "count(urls)";
+while "i < urls_len" {
+ let "url" "urls[i]";
+ let "i" "i + 1";
+
+ # Do not check more than 10 URLs
+ if eval "i >= 10" {
+ break;
+ }
+
+ # Skip URLs pointing to local or trusted domains
+ let "domain" "domain_part(uri_part(url, 'host'), 'sld')";
+ if eval "is_local_domain(DOMAIN_DIRECTORY, domain) ||
+ key_exists('spam-allow', domain)" {
+ 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.')" {
+ let "result" "result[1]";
+
+ if eval "result == 8" {
+ let "t.SURBL_HASHBL_PHISH" "1";
+ } elsif eval "result == 16" {
+ let "t.SURBL_HASHBL_MALWARE" "1";
+ } elsif eval "result == 64" {
+ let "t.SURBL_HASHBL_ABUSE" "1";
+ } elsif eval "result == 128" {
+ let "t.SURBL_HASHBL_CRACKED" "1";
+ } elsif eval "result[0] == '127.0.1'" {
+ let "t.SURBL_HASHBL_EMAIL" "1";
+ }
+ }
+}
+
+
+#### Script pyzor.sieve ####
+
+# Check message hash against Pyzor on public.pyzor.org:24441 using a 5 second timeout
+let "pyzor_response" "pyzor_check('public.pyzor.org:24441', 5)";
+
+if eval "!is_empty(pyzor_response) && pyzor_response[0] == 200" {
+ let "count" "pyzor_response[1]";
+ let "wl_count" "pyzor_response[2]";
+
+ if eval "count > 5 && (wl_count < 10 || wl_count / count < 0.2)" {
+ let "t.PYZOR" "1";
+ }
+}
+
+
+#### Script composites.sieve ####
+
+if eval "t.MISSING_ESSENTIAL_HEADERS && t.SINGLE_SHORT_PART" {
+ let "t.SHORT_PART_BAD_HEADERS" "1";
+}
+
+if eval "t.FORGED_RECIPIENTS && t.MAILLIST" {
+ let "t.FORGED_RECIPIENTS_MAILLIST" "1";
+}
+
+if eval "t.FORGED_SENDER && t.MAILLIST" {
+ let "t.FORGED_SENDER_MAILLIST" "1";
+}
+
+if eval "t.DMARC_POLICY_ALLOW && (t.SPF_SOFTFAIL || t.SPF_FAIL || t.DKIM_REJECT)" {
+ let "t.DMARC_POLICY_ALLOW_WITH_FAILURES" "1";
+}
+
+if eval "t.DKIM_NA && t.SPF_NA && t.DMARC_NA && t.ARC_NA" {
+ let "t.AUTH_NA" "1";
+}
+
+if eval "!(t.DKIM_NA && t.SPF_NA && t.DMARC_NA && t.ARC_NA) && (t.DKIM_NA || t.DKIM_TEMPFAIL || t.DKIM_PERMFAIL) && (t.SPF_NA || t.SPF_DNSFAIL) && t.DMARC_NA && (t.ARC_NA || t.ARC_DNSFAIL)" {
+ let "t.AUTH_NA_OR_FAIL" "1";
+}
+
+if eval "(t.AUTH_NA || t.AUTH_NA_OR_FAIL) && (t.BOUNCE || t.SUBJ_BOUNCE_WORDS)" {
+ let "t.BOUNCE_NO_AUTH" "1";
+}
+
+if eval "(t.HAS_X_POS || t.HAS_PHPMAILER_SIG) && t.HAS_WP_URI && (t.PHISHING || t.CRACKED_SURBL || t.PH_SURBL_MULTI || t.DBL_PHISH || t.DBL_ABUSE_PHISH || t.URIBL_BLACK || t.PHISHED_OPENPHISH || t.PHISHED_PHISHTANK)" {
+ let "t.HACKED_WP_PHISHING" "1";
+}
+
+if eval "(t.HAS_XOIP || t.RCVD_FROM_SMTP_AUTH) && t.DCC_BULK" {
+ let "t.COMPROMISED_ACCT_BULK" "1";
+}
+
+if eval "t.DCC_BULK && (t.MISSING_TO || t.R_UNDISC_RCPT)" {
+ let "t.UNDISC_RCPTS_BULK" "1";
+}
+
+if eval "t.RECEIVED_SPAMHAUS_PBL && !t.RCVD_VIA_SMTP_AUTH" {
+ let "t.RCVD_UNAUTH_PBL" "1";
+}
+
+if eval "(t.DKIM_ALLOW || t.ARC_ALLOW) && t.RCVD_IN_DNSWL_MED" {
+ let "t.RCVD_DKIM_ARC_DNSWL_MED" "1";
+}
+
+if eval "(t.DKIM_ALLOW || t.ARC_ALLOW) && t.RCVD_IN_DNSWL_HI" {
+ let "t.RCVD_DKIM_ARC_DNSWL_HI" "1";
+}
+
+if eval "(t.HAS_X_POS || t.HAS_PHPMAILER_SIG || t.HAS_X_PHP_SCRIPT) && (t.SUBJECT_ENDS_QUESTION || t.SUBJECT_ENDS_EXCLAIM || t.MANY_INVISIBLE_PARTS)" {
+ let "t.AUTOGEN_PHP_SPAMMY" "1";
+}
+
+if eval "(t.PHISHING || t.DBL_PHISH || t.PHISHED_OPENPHISH || t.PHISHED_PHISHTANK) && (t.SUBJECT_ENDS_QUESTION || t.SUBJECT_ENDS_EXCLAIM)" {
+ let "t.PHISH_EMOTION" "1";
+}
+
+if eval "t.HAS_GUC_PROXY_URI || t.URIBL_RED || t.DBL_ABUSE_REDIR || t.HAS_ONION_URI" {
+ let "t.HAS_ANON_DOMAIN" "1";
+}
+
+if eval "(t.SPF_FAIL || t.SPF_SOFTFAIL) && (t.RCVD_COUNT_ZERO || t.RCVD_NO_TLS_LAST)" {
+ let "t.VIOLATED_DIRECT_SPF" "1";
+}
+
+if eval "(t.FREEMAIL_FROM || t.FREEMAIL_ENVFROM || t.FREEMAIL_REPLYTO) && (t.TO_DN_RECIPIENTS || t.R_UNDISC_RCPT) && (t.FROM_NAME_HAS_TITLE || t.FREEMAIL_REPLYTO_NEQ_FROM_DOM)" {
+ let "t.FREEMAIL_AFF" "1";
+}
+
+if eval "t.URL_ONLY && t.REDIRECTOR_URL" {
+ let "t.REDIRECTOR_URL_ONLY" "1";
+}
+
+if eval "t.FAKE_REPLY && t.RCVD_VIA_SMTP_AUTH && (!t.RECEIVED_SPAMHAUS_PBL || t.RECEIVED_SPAMHAUS_XBL || t.RECEIVED_SPAMHAUS_SBL)" {
+ let "t.THREAD_HIJACKING_FROM_INJECTOR" "1";
+}
+
+
+#### Script scores.sieve ####
+
+# Add scores
+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" "key_get('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;
+ } elsif eval "tag_score == 'discard'" {
+ discard;
+ stop;
+ }
+}
+
+
+#### Script reputation.sieve ####
+
+# Obtain sender address and domain
+let "rep_from" "envelope.from";
+let "rep_from_domain" "envfrom_domain_sld";
+if eval "is_empty(rep_from)" {
+ let "rep_from" "from_addr";
+ let "rep_from_domain" "from_domain_sld";
+}
+if eval "env.dmarc.result != 'pass'" {
+ # Do not penalize forged domains
+ let "rep_from" "'_' + rep_from";
+ let "rep_from_domain" "'_' + rep_from_domain";
+}
+
+# Lookup ASN
+let "asn" "";
+if eval "len(env.remote_ip.reverse) <= 15" {
+ let "asn" "env.remote_ip.reverse + '.origin.asn.cymru.com'";
+} else {
+ let "asn" "env.remote_ip.reverse + '.origin.asn6.cymru.com'";
+}
+let "asn" "split(dns_query(asn, 'txt'), '|')[0]";
+
+# Generate reputation tokens
+let "token_ids" "";
+if eval "asn > 0" {
+ let "token_ids" "['i:' + env.remote_ip, 'f:' + rep_from, 'd:' + rep_from_domain, 'a:' + asn ]";
+} else {
+ let "token_ids" "['i:' + env.remote_ip, 'f:' + rep_from, 'd:' + rep_from_domain ]";
+}
+
+# Lookup reputation
+let "i" "len(token_ids)";
+let "reputation" "0.0";
+
+while "i > 0" {
+ let "i" "i - 1";
+ let "token_id" "token_ids[i]";
+
+ # Lookup reputation
+ let "token_rep" "key_get(SPAM_DB, token_id)";
+
+ if eval "is_empty(token_rep)" {
+ # Set reputation
+ eval "key_set(SPAM_DB, token_id, [score, 1], 2592000)";
+ continue;
+ }
+
+ # Update reputation
+ let "token_score" "token_rep[0]";
+ let "token_count" "token_rep[1]";
+ let "updated_score" "(token_count + 1) * (score + 0.98 * token_score) / (0.98 * token_count + 1)";
+ eval "key_set(SPAM_DB, token_id, [updated_score, token_count + 1], 2592000)";
+
+ # Assign weight
+ let "weight" "";
+ if eval "starts_with(token_id, 'f:')" {
+ # Sender address has 50% weight
+ let "weight" "0.5";
+ } elsif eval "starts_with(token_id, 'd:')" {
+ # Sender domain has 20% weight
+ let "weight" "0.2";
+ } elsif eval "starts_with(token_id, 'i:')" {
+ # IP has 20% weight
+ let "weight" "0.2";
+ } elsif eval "starts_with(token_id, 'a:')" {
+ # ASN has 10% weight
+ let "weight" "0.1";
+ } else {
+ continue;
+ }
+
+ let "reputation" "reputation + (token_score / token_count * weight)";
+}
+
+# Adjust score using a 0.5 factor
+if eval "reputation > 0" {
+ let "score" "score + (reputation - score) * 0.5";
+}
+
+
+#### Script epilogue.sieve ####
+
+
+# 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(SPAM_DB, is_spam, AUTOLEARN_SPAM_HAM_BALANCE) &&
+ bayes_train(SPAM_DB, body_and_subject, is_spam)";
+}
+
+# Process score actions
+if eval "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;
+} elsif eval "SCORE_DISCARD_THRESHOLD && score >= SCORE_DISCARD_THRESHOLD" {
+ discard;
+ stop;
+} elsif eval "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)";
+ }
+}
+
+
+'''
+
+[sieve.trusted.scripts.track-replies]
+name = "Track Replies"
+contents = '''
+
+#### Script config.sieve ####
+
+# Whether to add an X-Spam-Status header
+let "ADD_HEADER_SPAM" "key_get('spam-config', 'add-spam')";
+
+# Whether to add an X-Spam-Result header
+let "ADD_HEADER_SPAM_RESULT" "key_get('spam-config', 'add-spam-result')";
+
+# Whether message replies from authenticated users should be learned as ham
+let "AUTOLEARN_REPLIES_HAM" "key_get('spam-config', 'learn-ham-replies')";
+
+# Whether the bayes classifier should be trained automatically
+let "AUTOLEARN_ENABLE" "key_get('spam-config', 'learn-enable')";
+
+# When to learn ham (score >= threshold)
+let "AUTOLEARN_HAM_THRESHOLD" "key_get('spam-config', 'learn-ham-threshold')";
+
+# When to learn spam (score <= threshold)
+let "AUTOLEARN_SPAM_THRESHOLD" "key_get('spam-config', 'learn-spam-threshold')";
+
+# Keep difference for spam/ham learns for at least this value
+let "AUTOLEARN_SPAM_HAM_BALANCE" "key_get('spam-config', 'learn-balance')";
+
+# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
+let "SCORE_SPAM_THRESHOLD" "key_get('spam-config', 'threshold-spam')";
+
+# Discard messages with a score above this threshold
+let "SCORE_DISCARD_THRESHOLD" "key_get('spam-config', 'threshold-discard')";
+
+# Reject messages with a score above this threshold
+let "SCORE_REJECT_THRESHOLD" "key_get('spam-config', 'threshold-reject')";
+
+# Directory name to use for local domain lookups (leave empty for default)
+let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
+
+# Store to use for Bayes tokens and ids (leave empty for default)
+let "SPAM_DB" "key_get('spam-config', 'lookup')";
+
+
+#### Script replies_out.sieve ####
+
+
+# This script should be used on authenticated SMTP sessions only
+let "message_id" "header.Message-ID";
+
+if eval "!is_empty(message_id)" {
+ # Store the message ID for 30 days
+ eval "key_set(SPAM_DB, 'm:' + message_id, '', 2592000)";
+
+ if eval "AUTOLEARN_ENABLE && AUTOLEARN_REPLIES_HAM && bayes_is_balanced(SPAM_DB, false, AUTOLEARN_SPAM_HAM_BALANCE)" {
+ eval "bayes_train(SPAM_DB, thread_name(header.subject) + ' ' + body.to_text, false)";
+ }
+}
+
+'''
+
+[sieve.trusted.scripts.greylist]
+name = "Greylisting"
+contents = '''
+
+#### Script config.sieve ####
+
+# Whether to add an X-Spam-Status header
+let "ADD_HEADER_SPAM" "key_get('spam-config', 'add-spam')";
+
+# Whether to add an X-Spam-Result header
+let "ADD_HEADER_SPAM_RESULT" "key_get('spam-config', 'add-spam-result')";
+
+# Whether message replies from authenticated users should be learned as ham
+let "AUTOLEARN_REPLIES_HAM" "key_get('spam-config', 'learn-ham-replies')";
+
+# Whether the bayes classifier should be trained automatically
+let "AUTOLEARN_ENABLE" "key_get('spam-config', 'learn-enable')";
+
+# When to learn ham (score >= threshold)
+let "AUTOLEARN_HAM_THRESHOLD" "key_get('spam-config', 'learn-ham-threshold')";
+
+# When to learn spam (score <= threshold)
+let "AUTOLEARN_SPAM_THRESHOLD" "key_get('spam-config', 'learn-spam-threshold')";
+
+# Keep difference for spam/ham learns for at least this value
+let "AUTOLEARN_SPAM_HAM_BALANCE" "key_get('spam-config', 'learn-balance')";
+
+# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
+let "SCORE_SPAM_THRESHOLD" "key_get('spam-config', 'threshold-spam')";
+
+# Discard messages with a score above this threshold
+let "SCORE_DISCARD_THRESHOLD" "key_get('spam-config', 'threshold-discard')";
+
+# Reject messages with a score above this threshold
+let "SCORE_REJECT_THRESHOLD" "key_get('spam-config', 'threshold-reject')";
+
+# Directory name to use for local domain lookups (leave empty for default)
+let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
+
+# Store to use for Bayes tokens and ids (leave empty for default)
+let "SPAM_DB" "key_get('spam-config', 'lookup')";
+
+
+#### Script greylist.sieve ####
+
+
+set "triplet" "g:${env.remote_ip}.${envelope.from}.${envelope.to}";
+
+if eval "!key_exists(SPAM_DB, triplet)" {
+ # Greylist sender for 30 days
+ eval "key_set(SPAM_DB, triplet, '', 2592000)";
+ reject "422 4.2.2 Greylisted, please try again in a few moments.";
+ stop;
+}
+
+'''
+
+
+[lookup]
+spam-scores = {"ABUSE_SURBL" = "5.0",
+"ALLOWLIST_DKIM" = "-1.0",
+"ALLOWLIST_DMARC" = "-7.0",
+"ALLOWLIST_SPF" = "-1.0",
+"ALLOWLIST_SPF_DKIM" = "-3.0",
+"ARC_ALLOW" = "-1.0",
+"ARC_DNSFAIL" = "0.0",
+"ARC_INVALID" = "0.5",
+"ARC_NA" = "0.0",
+"ARC_REJECT" = "1.0",
+"ARC_SIGNED" = "0.0",
+"AUTH_NA" = "1.0",
+"AUTH_NA_OR_FAIL" = "1.0",
+"AUTOGEN_PHP_SPAMMY" = "1.0",
+"BAYES_HAM" = "-3.0",
+"BAYES_SPAM" = "5.1",
+"BLOCKLIST_DKIM" = "2.0",
+"BLOCKLIST_DMARC" = "6.0",
+"BLOCKLIST_SPF" = "1.0",
+"BLOCKLIST_SPF_DKIM" = "3.0",
+"BODY_URI_ONLY" = "2.0",
+"BOGUS_ENCRYPTED_AND_TEXT" = "10.0",
+"BOUNCE" = "-0.1",
+"BOUNCE_NO_AUTH" = "1.0",
+"BROKEN_CONTENT_TYPE" = "1.5",
+"COMPROMISED_ACCT_BULK" = "3.0",
+"CRACKED_SURBL" = "5.0",
+"CTE_CASE" = "0.5",
+"CTYPE_MISSING_DISPOSITION" = "4.0",
+"CTYPE_MIXED_BOGUS" = "1.0",
+"CT_EXTRA_SEMI" = "1.0",
+"DATA_URI_OBFU" = "2.0",
+"DATE_IN_FUTURE" = "4.0",
+"DATE_IN_PAST" = "1.0",
+"DBL_ABUSE" = "5.0",
+"DBL_ABUSE_BOTNET" = "6.5",
+"DBL_ABUSE_MALWARE" = "6.5",
+"DBL_ABUSE_PHISH" = "6.5",
+"DBL_ABUSE_REDIR" = "5.0",
+"DBL_BLOCKED" = "0.0",
+"DBL_BLOCKED_OPENRESOLVER" = "0.0",
+"DBL_BOTNET" = "7.5",
+"DBL_MALWARE" = "7.5",
+"DBL_PHISH" = "7.5",
+"DBL_SPAM" = "6.5",
+"DCC_BULK" = "3.0",
+"DIRECT_TO_MX" = "0.0",
+"DISPOSABLE_CC" = "0.0",
+"DISPOSABLE_ENVFROM" = "0.0",
+"DISPOSABLE_FROM" = "0.0",
+"DISPOSABLE_REPLYTO" = "0.0",
+"DISPOSABLE_TO" = "0.0",
+"DKIM_SIGNED" = "0.0",
+"DMARC_BAD_POLICY" = "0.5",
+"DMARC_DNSFAIL" = "0.0",
+"DMARC_NA" = "0.0",
+"DMARC_POLICY_ALLOW" = "-0.5",
+"DMARC_POLICY_ALLOW_WITH_FAILURES" = "0.0",
+"DMARC_POLICY_QUARANTINE" = "1.5",
+"DMARC_POLICY_REJECT" = "2.0",
+"DMARC_POLICY_SOFTFAIL" = "0.1",
+"DNSWL_BLOCKED" = "0.0",
+"DWL_DNSWL_BLOCKED" = "0.0",
+"DWL_DNSWL_HI" = "-3.5",
+"DWL_DNSWL_LOW" = "-1.0",
+"DWL_DNSWL_MED" = "-2.0",
+"DWL_DNSWL_NONE" = "0.0",
+"EMPTY_SUBJECT" = "1.0",
+"ENCRYPTED_PGP" = "-0.5",
+"ENCRYPTED_SMIME" = "-0.5",
+"ENVFROM_INVALID" = "2.0",
+"ENVFROM_SERVICE_ACCT" = "1.0",
+"EXT_CSS" = "1.0",
+"FAKE_REPLY" = "1.0",
+"FORGED_RCVD_TRAIL" = "1.0",
+"FORGED_RECIPIENTS" = "2.0",
+"FORGED_RECIPIENTS_MAILLIST" = "0.0",
+"FORGED_SENDER" = "0.3",
+"FORGED_SENDER_MAILLIST" = "0.0",
+"FREEMAIL_AFF" = "4.0",
+"FREEMAIL_CC" = "0.0",
+"FREEMAIL_ENVFROM" = "0.0",
+"FREEMAIL_FROM" = "0.0",
+"FREEMAIL_REPLYTO" = "0.0",
+"FREEMAIL_REPLYTO_NEQ_FROM_DOM" = "3.0",
+"FREEMAIL_TO" = "0.0",
+"FROM_DN_EQ_ADDR" = "1.0",
+"FROM_EQ_ENVFROM" = "0.0",
+"FROM_EXCESS_BASE64" = "1.5",
+"FROM_EXCESS_QP" = "1.2",
+"FROM_HAS_DN" = "0.0",
+"FROM_INVALID" = "2.0",
+"FROM_NAME_EXCESS_SPACE" = "1.0",
+"FROM_NAME_HAS_TITLE" = "1.0",
+"FROM_NEEDS_ENCODING" = "1.0",
+"FROM_NEQ_DISPLAY_NAME" = "4.0",
+"FROM_NEQ_ENVFROM" = "0.0",
+"FROM_NO_DN" = "0.0",
+"FROM_SERVICE_ACCT" = "1.0",
+"HACKED_WP_PHISHING" = "4.5",
+"HAS_ANON_DOMAIN" = "0.1",
+"HAS_ATTACHMENT" = "0.0",
+"HAS_DATA_URI" = "0.0",
+"HAS_GOOGLE_FIREBASE_URL" = "2.0",
+"HAS_GOOGLE_REDIR" = "1.0",
+"HAS_GUC_PROXY_URI" = "1.0",
+"HAS_IPFS_GATEWAY_URL" = "6.0",
+"HAS_LIST_UNSUB" = "-0.01",
+"HAS_ONION_URI" = "0.0",
+"HAS_ORG_HEADER" = "0.0",
+"HAS_PHPMAILER_SIG" = "0.0",
+"HAS_REPLYTO" = "0.0",
+"HAS_WP_URI" = "0.0",
+"HAS_XAW" = "0.0",
+"HAS_XOIP" = "0.0",
+"HAS_X_ANTIABUSE" = "0.0",
+"HAS_X_AS" = "0.0",
+"HAS_X_GMSV" = "0.0",
+"HAS_X_PHP_SCRIPT" = "0.0",
+"HAS_X_POS" = "0.0",
+"HAS_X_PRIO_FIVE" = "0.0",
+"HAS_X_PRIO_ONE" = "0.0",
+"HAS_X_PRIO_THREE" = "0.0",
+"HAS_X_PRIO_TWO" = "0.0",
+"HAS_X_PRIO_ZERO" = "0.0",
+"HAS_X_SOURCE" = "0.0",
+"HEADER_EMPTY_DELIMITER" = "1.0",
+"HEADER_FORGED_MDN" = "2.0",
+"HEADER_RCONFIRM_MISMATCH" = "2.0",
+"FROMHOST_NORES_A_OR_MX" = "1.5",
+"FROM_BOUNCE" = "0.0",
+"HELO_BAREIP" = "3.0",
+"HELO_IP_A" = "1.0",
+"HELO_NORES_A_OR_MX" = "0.3",
+"HELO_NOT_FQDN" = "2.0",
+"HELO_IPREV_MISMATCH" = "1.0",
+"RCPT_BOUNCEMOREONE" = "1.5",
+"URL_ONLY" = "2.2",
+"HIDDEN_SOURCE_OBJ" = "2.0",
+"HTML_META_REFRESH_URL" = "5.0",
+"HTML_SHORT_LINK_IMG_1" = "2.0",
+"HTML_SHORT_LINK_IMG_2" = "1.0",
+"HTML_SHORT_LINK_IMG_3" = "0.5",
+"HTML_TEXT_IMG_RATIO" = "1.0",
+"HTML_UNBALANCED_TAG" = "0.5",
+"HTTP_TO_HTTPS" = "0.5",
+"HTTP_TO_IP" = "1.0",
+"INFO_TO_INFO_LU" = "2.0",
+"INVALID_DATE" = "1.5",
+"INVALID_FROM_8BIT" = "6.0",
+"INVALID_MSGID" = "1.7",
+"KLMS_SPAM" = "5.0",
+"LONG_SUBJ" = "3.0",
+"MAILLIST" = "-0.2",
+"MANY_INVISIBLE_PARTS" = "1.0",
+"MID_BARE_IP" = "2.0",
+"MID_CONTAINS_FROM" = "1.0",
+"MID_CONTAINS_TO" = "1.0",
+"MID_MISSING_BRACKETS" = "1.0",
+"MID_RHS_IP_LITERAL" = "1.0",
+"MID_RHS_MATCH_FROM" = "1.0",
+"MID_RHS_MATCH_FROMTLD" = "1.0",
+"MID_RHS_MATCH_TO" = "1.0",
+"MID_RHS_NOT_FQDN" = "0.5",
+"MID_RHS_WWW" = "0.5",
+"MIME_ARCHIVE_IN_ARCHIVE" = "5.0",
+"MIME_BAD" = "1.0",
+"MIME_BAD_ATTACHMENT" = "4.0",
+"MIME_BAD_EXTENSION" = "2.0",
+"MIME_BAD_UNICODE" = "8.0",
+"MIME_BASE64_TEXT" = "0.1",
+"MIME_BASE64_TEXT_BOGUS" = "1.0",
+"MIME_DOUBLE_BAD_EXTENSION" = "2.0",
+"MIME_GOOD" = "-0.1",
+"MIME_HEADER_CTYPE_ONLY" = "2.0",
+"MIME_HTML_ONLY" = "0.2",
+"MIME_MA_MISSING_HTML" = "1.0",
+"MIME_MA_MISSING_TEXT" = "2.0",
+"MISSING_DATE" = "1.0",
+"MISSING_FROM" = "2.0",
+"MISSING_MID" = "2.5",
+"MISSING_MIME_VERSION" = "2.0",
+"MISSING_SUBJECT" = "2.0",
+"MISSING_TO" = "2.0",
+"MSBL_EBL" = "7.5",
+"MSBL_EBL_GREY" = "0.5",
+"MULTIPLE_FROM" = "8.0",
+"MULTIPLE_UNIQUE_HEADERS" = "7.0",
+"MV_CASE" = "0.5",
+"MW_SURBL_MULTI" = "7.5",
+"HOMOGRAPH_URL" = "5.0",
+"ONCE_RECEIVED" = "0.1",
+"PHISHED_OPENPHISH" = "7.0",
+"PHISHED_PHISHTANK" = "7.0",
+"PHISHING" = "4.0",
+"PHISH_EMOTION" = "1.0",
+"PHP_XPS_PATTERN" = "0.0",
+"PH_SURBL_MULTI" = "7.5",
+"PRECEDENCE_BULK" = "0.0",
+"PREVIOUSLY_DELIVERED" = "0.0",
+"PYZOR" = "3.5",
+"RBL_BARRACUDA" = "4.0",
+"RBL_BLOCKLISTDE" = "4.0",
+"RBL_MAILSPIKE_BAD" = "1.0",
+"RBL_MAILSPIKE_VERYBAD" = "1.5",
+"RBL_MAILSPIKE_WORST" = "2.0",
+"RBL_NIXSPAM" = "4.0",
+"RBL_SEM" = "1.0",
+"RBL_SEM_IPV6" = "1.0",
+"RBL_SENDERSCORE" = "2.0",
+"RBL_SPAMCOP" = "4.0",
+"RBL_SPAMHAUS" = "0.0",
+"RBL_SPAMHAUS_BLOCKED" = "0.0",
+"RBL_SPAMHAUS_BLOCKED_OPENRESOLVER" = "0.0",
+"RBL_SPAMHAUS_CSS" = "2.0",
+"RBL_SPAMHAUS_DROP" = "7.0",
+"RBL_SPAMHAUS_PBL" = "2.0",
+"RBL_SPAMHAUS_SBL" = "4.0",
+"RBL_SPAMHAUS_XBL" = "4.0",
+"RBL_VIRUSFREE_BOTNET" = "2.0",
+"RCPT_ADDR_IN_SUBJECT" = "3.0",
+"RCPT_COUNT_FIVE" = "0.0",
+"RCPT_COUNT_GT_50" = "0.0",
+"RCPT_COUNT_ONE" = "0.0",
+"RCPT_COUNT_SEVEN" = "0.0",
+"RCPT_COUNT_THREE" = "0.0",
+"RCPT_COUNT_TWELVE" = "0.0",
+"RCPT_COUNT_TWO" = "0.0",
+"RCPT_COUNT_ZERO" = "0.0",
+"RCPT_LOCAL_IN_SUBJECT" = "2.0",
+"RCVD_COUNT_FIVE" = "0.0",
+"RCVD_COUNT_ONE" = "0.0",
+"RCVD_COUNT_SEVEN" = "0.0",
+"RCVD_COUNT_THREE" = "0.0",
+"RCVD_COUNT_TWELVE" = "0.0",
+"RCVD_COUNT_TWO" = "0.0",
+"RCVD_COUNT_ZERO" = "0.0",
+"RCVD_DKIM_ARC_DNSWL_HI" = "-1.0",
+"RCVD_DKIM_ARC_DNSWL_MED" = "-0.5",
+"RCVD_DOUBLE_IP_SPAM" = "2.0",
+"RCVD_FROM_SMTP_AUTH" = "0.0",
+"RCVD_HELO_USER" = "3.0",
+"RCVD_ILLEGAL_CHARS" = "4.0",
+"RCVD_IN_DNSWL_HI" = "-0.5",
+"RCVD_IN_DNSWL_LOW" = "-0.1",
+"RCVD_IN_DNSWL_MED" = "-0.2",
+"RCVD_IN_DNSWL_NONE" = "0.0",
+"RCVD_NO_TLS_LAST" = "0.1",
+"RCVD_TLS_ALL" = "0.0",
+"RCVD_TLS_LAST" = "0.0",
+"RCVD_UNAUTH_PBL" = "2.0",
+"RCVD_VIA_SMTP_AUTH" = "0.0",
+"RDNS_DNSFAIL" = "0.0",
+"RDNS_NONE" = "1.0",
+"RECEIVED_BLOCKLISTDE" = "3.0",
+"RECEIVED_SPAMHAUS_BLOCKED" = "0.0",
+"RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER" = "0.0",
+"RECEIVED_SPAMHAUS_CSS" = "1.0",
+"RECEIVED_SPAMHAUS_PBL" = "0.0",
+"RECEIVED_SPAMHAUS_SBL" = "3.0",
+"RECEIVED_SPAMHAUS_XBL" = "1.0",
+"REDIRECTOR_URL" = "0.0",
+"REDIRECTOR_URL_ONLY" = "1.0",
+"REPLYTO_ADDR_EQ_FROM" = "0.0",
+"REPLYTO_DN_EQ_FROM_DN" = "0.0",
+"REPLYTO_DOM_EQ_FROM_DOM" = "0.0",
+"REPLYTO_DOM_NEQ_FROM_DOM" = "0.0",
+"REPLYTO_EMAIL_HAS_TITLE" = "2.0",
+"REPLYTO_EQ_FROM" = "0.0",
+"REPLYTO_EQ_TO_ADDR" = "5.0",
+"REPLYTO_EXCESS_BASE64" = "1.5",
+"REPLYTO_EXCESS_QP" = "1.2",
+"REPLYTO_UNPARSEABLE" = "1.0",
+"RWL_MAILSPIKE_EXCELLENT" = "-0.4",
+"RWL_MAILSPIKE_GOOD" = "-0.1",
+"RWL_MAILSPIKE_NEUTRAL" = "0.0",
+"RWL_MAILSPIKE_POSSIBLE" = "0.0",
+"RWL_MAILSPIKE_VERYGOOD" = "-0.2",
+"R_BAD_CTE_7BIT" = "3.5",
+"DKIM_ALLOW" = "-0.2",
+"DKIM_NA" = "0.0",
+"DKIM_PERMFAIL" = "0.0",
+"DKIM_REJECT" = "1.0",
+"DKIM_TEMPFAIL" = "0.0",
+"R_MISSING_CHARSET" = "0.5",
+"R_MIXED_CHARSET" = "5.0",
+"MIXED_CHARSET_URL" = "7.0",
+"R_NO_SPACE_IN_FROM" = "1.0",
+"R_PARTS_DIFFER" = "1.0",
+"SPF_ALLOW" = "-0.2",
+"SPF_DNSFAIL" = "0.0",
+"SPF_FAIL" = "1.0",
+"SPF_NA" = "0.0",
+"SPF_NEUTRAL" = "0.0",
+"SPF_PERMFAIL" = "0.0",
+"SPF_SOFTFAIL" = "0.0",
+"R_SUSPICIOUS_URL" = "5.0",
+"R_UNDISC_RCPT" = "3.0",
+"SEM_URIBL" = "3.5",
+"SEM_URIBL_FRESH15" = "3.0",
+"SIGNED_PGP" = "-2.0",
+"SIGNED_SMIME" = "-2.0",
+"SORTED_RECIPS" = "3.5",
+"SPAM_FLAG" = "5.0",
+"SPAM_TRAP" = "discard",
+"SPOOF_DISPLAY_NAME" = "8.0",
+"SPOOF_REPLYTO" = "6.0",
+"SUBJECT_ENDS_EXCLAIM" = "0.0",
+"SUBJECT_ENDS_QUESTION" = "1.0",
+"SUBJECT_ENDS_SPACES" = "0.5",
+"SUBJECT_HAS_CURRENCY" = "1.0",
+"SUBJECT_HAS_EXCLAIM" = "0.0",
+"SUBJECT_HAS_QUESTION" = "0.0",
+"SUBJECT_NEEDS_ENCODING" = "1.0",
+"SUBJ_ALL_CAPS" = "3.0",
+"SUBJ_BOUNCE_WORDS" = "0.0",
+"SUBJ_EXCESS_BASE64" = "1.5",
+"SUBJ_EXCESS_QP" = "1.2",
+"SURBL_BLOCKED" = "0.0",
+"SURBL_HASHBL_ABUSE" = "5.0",
+"SURBL_HASHBL_CRACKED" = "5.0",
+"SURBL_HASHBL_EMAIL" = "5.0",
+"SURBL_HASHBL_MALWARE" = "6.5",
+"SURBL_HASHBL_PHISH" = "6.5",
+"SUSPICIOUS_RECIPS" = "1.5",
+"TAGGED_FROM" = "0.0",
+"TAGGED_RCPT" = "0.0",
+"THREAD_HIJACKING_FROM_INJECTOR" = "2.0",
+"TO_DN_ALL" = "0.0",
+"TO_DN_EQ_ADDR_ALL" = "0.0",
+"TO_DN_EQ_ADDR_SOME" = "0.0",
+"TO_DN_NONE" = "0.0",
+"TO_DN_RECIPIENTS" = "2.0",
+"TO_DN_SOME" = "0.0",
+"TO_DOM_EQ_FROM_DOM" = "0.0",
+"TO_EQ_FROM" = "0.0",
+"TO_EXCESS_BASE64" = "1.5",
+"TO_EXCESS_QP" = "1.2",
+"TO_MATCH_ENVRCPT_ALL" = "0.0",
+"TO_MATCH_ENVRCPT_SOME" = "0.0",
+"TO_NEEDS_ENCODING" = "1.0",
+"TO_WRAPPED_IN_SPACES" = "2.0",
+"TRUSTED_REPLY" = "-7.0",
+"UNDISC_RCPTS_BULK" = "3.0",
+"UNITEDINTERNET_SPAM" = "5.0",
+"URIBL_BLACK" = "7.5",
+"URIBL_BLOCKED" = "0.0",
+"URIBL_GREY" = "1.5",
+"URIBL_RED" = "3.5",
+"URI_COUNT_ODD" = "1.0",
+"URI_HIDDEN_PATH" = "1.0",
+"URL_IN_SUBJECT" = "4.0",
+"URL_REDIRECTOR_NESTED" = "1.0",
+"VIOLATED_DIRECT_SPF" = "3.5",
+"WP_COMPROMISED" = "0.0",
+"WWW_DOT_DOMAIN" = "0.5",
+"XM_CASE" = "0.5",
+"XM_UA_NO_VERSION" = "0.01",
+"X_PHP_EVAL" = "4.0",
+"ZERO_WIDTH_SPACE_URL" = "7.0",
+"SHORT_PART_BAD_HEADERS" = "7.0",
+"MISSING_ESSENTIAL_HEADERS" = "7.0",
+"SINGLE_SHORT_PART" = "0.0",
+"COMPLETELY_EMPTY" = "7.0"}
+
+spam-dmarc = {"18f.gov",
+"1password.com",
+"2gis.com",
+"4chan.org",
+"4pda.ru",
+"9-11commission.gov",
+"911.gov",
+"aberdeenshire.gov.uk",
+"abilityone.gov",
+"absolutbank.ru",
+"access-board.gov",
+"acquisition.gov",
+"acus.gov",
+"ada.gov",
+"adf.gov",
+"adidas.co.kr",
+"adidas.com.au",
+"adidas.com.br",
+"adidas.com.hk",
+"adidas.fi",
+"adidas.fr",
+"admongo.gov",
+"adobe.dk",
+"adobe.es",
+"adobeawards.com",
+"adp.com",
+"advice.hmrc.gov.uk",
+"aerocivil.gov.co",
+"afreximbank.com",
+"agingstats.gov",
+"agro.ru",
+"ahcpr.gov",
+"aids.gov",
+"airbnb.co.uk",
+"airbnb.com",
+"airbnb.com.tr",
+"airbnb.cz",
+"airbnb.de",
+"airbnb.fi",
+"airbnb.fr",
+"airbnb.pl",
+"airbnb.ru",
+"airbnb.se",
+"airnow.gov",
+"airtel.in",
+"alfabank.com",
+"alfabank.ru",
+"alfastrah.ru",
+"alibaba.com",
+"aliexpress.com",
+"alipay.com",
+"alkupone.ru",
+"alzheimers.gov",
+"amazon.co.uk",
+"amazon.com",
+"amazon.com.br",
+"amberalert.gov",
+"americanexpress.com",
+"ameslab.gov",
+"angus.gov.uk",
+"anidub.com",
+"annapolis.gov",
+"anstaskforce.gov",
+"apple.com.au",
+"apple.com.cn",
+"apps.gov",
+"archives.gov",
+"arctic.gov",
+"arionbanki.is",
+"asg.com",
+"asic.gov.au",
+"askona.ru",
+"asos.com",
+"assist.ru",
+"atf.gov",
+"avast.com",
+"avg.com",
+"avito.ru",
+"avtoradio.ru",
+"axisbank.com",
+"badoo.com",
+"baltbank.ru",
+"bank.lv",
+"banki.ru",
+"bankofamerica.com",
+"barclaycard.co.uk",
+"barclays.co.uk",
+"barclays.com",
+"battle.net",
+"beeline.kz",
+"beeline.ru",
+"benefits.gov",
+"betfaq.ru",
+"biglion.ru",
+"binary.com",
+"binbank.ru",
+"bioethics.gov",
+"biometrics.gov",
+"biopreferred.gov",
+"birminghampost.net",
+"bishopsmove.com",
+"bitbank.cc",
+"bjs.gov",
+"blizzard.com",
+"blog.gov.uk",
+"bls.gov",
+"bluestarindia.com",
+"boemre.gov",
+"bolsover.gov.uk",
+"bolton.gov.uk",
+"booking.com",
+"bookmate.com",
+"books.ru",
+"bournemouth.gov.uk",
+"box.com",
+"bpa.gov",
+"brandshop.ru",
+"bridgend.gov.uk",
+"brighton-hove.gov.uk",
+"britishembassy.gov.uk",
+"broadbandmap.gov",
+"bromley.gov.uk",
+"bts.gov",
+"business.gov",
+"caerphilly.gov.uk",
+"caixa.gov.br",
+"cambridge-news.co.uk",
+"campaign.gov.uk",
+"cancer.gov",
+"cannockchasedc.gov.uk",
+"capitalone.co.uk",
+"cardiff.gov.uk",
+"carecredit.com",
+"cbp.gov",
+"cdfifund.gov",
+"centralbedfordshire.gov.uk",
+"ceredigion.gov.uk",
+"cesg.gov.uk",
+"cfda.gov",
+"cfo.gov",
+"challenge.gov",
+"change.org",
+"chase.com",
+"chcoc.gov",
+"childstats.gov",
+"cio.gov",
+"circle.com",
+"citibank.ae",
+"citibank.co.in",
+"citibank.co.uk",
+"citibank.com.my",
+"citibank.hu",
+"citibank.pl",
+"cloud.gov",
+"cloudflare.com",
+"cms.gov",
+"co-operativebank.co.uk",
+"colgate.com.br",
+"collegedrinkingprevention.gov",
+"companies-house.gov.uk",
+"comuneap.gov.it",
+"conab.gov.br",
+"concerts.com",
+"consultant.ru",
+"contact-sys.com",
+"copeland.gov.uk",
+"cosla.gov.uk",
+"courtservice.gov.uk",
+"coventry.gov.uk",
+"cre.gov.uk",
+"csosa.gov",
+"cuidadodesalud.gov",
+"culturarecreacionydeporte.gov.co",
+"customs.gov.my",
+"customs.gov.ua",
+"cybercrime.gov",
+"dailypost.co.uk",
+"danskebank.dk",
+"danskebank.fi",
+"danskebank.ie",
+"danskebank.no",
+"dartford.gov.uk",
+"dartmoor.gov.uk",
+"dataprotection.gov.uk",
+"daventrydc.gov.uk",
+"dellin.ru",
+"denbighshire.gov.uk",
+"deutsche-bank.de",
+"deutschebank.be",
+"deutschebank.co.in",
+"dh.gov.uk",
+"dhl.com",
+"dhs.gov",
+"diablo3.com",
+"digital.gov",
+"digitalgov.gov",
+"digitalliteracy.gov",
+"disability.gov",
+"disability.gov.uk",
+"dnfsb.gov",
+"docker.com",
+"docusign.net",
+"doe.gov",
+"doioig.gov",
+"dol.gov",
+"doleta.gov",
+"domofond.ru",
+"drought.gov",
+"drugabuse.gov",
+"dsns.gov.ua",
+"dtv.gov",
+"dudley.gov.uk",
+"dyslexiaida.org",
+"e-boks.dk",
+"e-verify.gov",
+"eastdunbarton.gov.uk",
+"eaststaffsbc.gov.uk",
+"eastsuffolk.gov.uk",
+"ebay.be",
+"ebay.ca",
+"ebay.ch",
+"ebay.co.uk",
+"ebay.com",
+"ebay.com.au",
+"ebay.com.cn",
+"ebay.de",
+"ebay.es",
+"ebay.eu",
+"ebay.fr",
+"ebay.in",
+"ebay.it",
+"ebay.se",
+"economy.gov.tr",
+"econsumer.gov",
+"ed.gov",
+"eftps.gov",
+"ehsni.gov.uk",
+"eia.gov",
+"ejob.gov.tw",
+"elance.com",
+"eldorado.ru",
+"email-ee.co.uk",
+"email.tektorg.ru",
+"emarsys.com",
+"ems.gov",
+"energystar.gov",
+"erewash.gov.uk",
+"esetnod32.ru",
+"essex-fire.gov.uk",
+"eubank.kz",
+"evernote.com",
+"everychildmatters.gov.uk",
+"evus.gov",
+"exist.ru",
+"expediamail.com",
+"facebook.com",
+"facebookmail.com",
+"fbi.gov",
+"fcc.gov",
+"fco.gov.uk",
+"fdic.gov",
+"feb.gov",
+"federalreserve.gov",
+"fedex.com",
+"fedramp.gov",
+"fedshirevets.gov",
+"feedthefuture.gov",
+"fema.gov",
+"ferc.gov",
+"fhfa.gov",
+"fhfaoig.gov",
+"fife.gov.uk",
+"financialresearch.gov",
+"financialstability.gov",
+"firstbankpb.bank",
+"firstbankpb.com",
+"firstnet.gov",
+"firstresponder.gov",
+"fishwatch.gov",
+"fitness.gov",
+"flagma.ua",
+"flamp.ru",
+"fletc.gov",
+"fmc.gov",
+"fmcs.gov",
+"foia.gov",
+"food.gov.uk",
+"force.com",
+"fordlibrarymuseum.gov",
+"foreignassistance.gov",
+"foreigntrade.gov.tr",
+"franklinwi.gov",
+"ftc.gov",
+"ftccomplaintassistant.gov",
+"gamereactor.dk",
+"gap.com",
+"garant.ru",
+"geekbrains.ru",
+"geektimes.ru",
+"getsmartaboutdrugs.gov",
+"gibraltar.gov.uk",
+"gitlab.com",
+"globalentry.gov",
+"globalhealth.gov",
+"globe.gov",
+"gloucestershire.gov.uk",
+"goes-r.gov",
+"gosuslugi.ru",
+"gov.uk",
+"groupon.es",
+"groupon.hk",
+"groupon.it",
+"gsa.gov",
+"gsaadvantage.gov",
+"gsaauctions.gov",
+"gsaig.gov",
+"gtbank.com",
+"guideline.gov",
+"guidelines.gov",
+"gwynedd.gov.uk",
+"habr.com",
+"hambleton.gov.uk",
+"harp.gov",
+"hawaiicounty.gov",
+"hdfcbank.com",
+"hdrezka.ag",
+"healthcare.gov",
+"healthypeople.gov",
+"hertfordshire.gov.uk",
+"hh.kz",
+"hh.ru",
+"highland.gov.uk",
+"highwaycode.gov.uk",
+"hillingdon.gov.uk",
+"hiv.gov",
+"hmrc.gov.uk",
+"homeoffice.gov.uk",
+"homesales.gov",
+"hotels.com",
+"hounslow.gov.uk",
+"howto.gov",
+"hru.gov",
+"huduser.gov",
+"hurricanes.gov",
+"iba.gov.au",
+"ice.gov",
+"idmanagement.gov",
+"ikea.ch",
+"ikea.co.uk",
+"ikea.com",
+"ikea.de",
+"ikea.fr",
+"ikea.gr",
+"ikea.nl",
+"ikea.pl",
+"imgur.com",
+"incometaxindiaefiling.gov.in",
+"ing.com",
+"inl.gov",
+"inlandrevenue.gov.uk",
+"insider.co.uk",
+"insolvency.gov.uk",
+"instagram.com",
+"insurekidsnow.gov",
+"invasivespeciesinfo.gov",
+"investor.gov",
+"irda.gov.in",
+"itunes.com",
+"jccbi.gov",
+"jd.ru",
+"jet.com",
+"jimmycarterlibrary.gov",
+"job.com",
+"johnsonsbaby.co.uk",
+"joybuy.com",
+"jpmorgan.com",
+"jpmorgansecurities.com",
+"judiciary.gov.uk",
+"justice.gov",
+"justice.gov.az",
+"justice.gov.uk",
+"jyskebank.dk",
+"kassy.ru",
+"kent.gov.uk",
+"keys.openpgp.org",
+"kids.gov",
+"kingston.gov.uk",
+"kivra.com",
+"klarna.com",
+"klarna.se",
+"kpk.gov.pl",
+"lacoast.gov",
+"landsbanki.is",
+"lanl.gov",
+"lbhf.gov.uk",
+"lcd.gov.uk",
+"learningcurve.gov.uk",
+"leeds.gov.uk",
+"leroymerlin.es",
+"lichfielddc.gov.uk",
+"lincoln.gov.uk",
+"lincolnshire.gov.uk",
+"linkedin.com",
+"livejournal.com",
+"llnl.gov",
+"lloydsbank.com",
+"locatorplus.gov",
+"lostfilm.tv",
+"louisvilleco.gov",
+"love.ru",
+"lufthansa-group.com",
+"lufthansa.com",
+"mackeeper.com",
+"mailgun.net",
+"mak.com",
+"mandtbank.com",
+"mcc.gov",
+"mcga.gov.uk",
+"mchenrycountyil.gov",
+"mecknc.gov",
+"mediamarkt.se",
+"medicaid.gov",
+"medicare.gov",
+"medium.com",
+"megafon.ru",
+"megaplan.ru",
+"mercadolibre.com.ar",
+"mercadolivre.com.br",
+"merseyfire.gov.uk",
+"merthyr.gov.uk",
+"meshok.ru",
+"messenger.com",
+"microsoft.net",
+"middlesbrough.gov.uk",
+"midlothian.gov.uk",
+"mil.ru",
+"mincit.gov.co",
+"minhacienda.gov.co",
+"minsvyaz.ru",
+"mintic.gov.co",
+"mirrorfootball.co.uk",
+"mkb.ru",
+"mlg.ru",
+"mlg.tv",
+"mns.gov.ua",
+"mod.gov.az",
+"molisa.gov.vn",
+"mos.ru",
+"mosoblbank.ru",
+"mosreg.ru",
+"motinfo.gov.uk",
+"movavi.com",
+"msha.gov",
+"mspb.gov",
+"msport.gov.pl",
+"murfreesborotn.gov",
+"mvideo.ru",
+"mxtoolbox.com",
+"mymoney.gov",
+"myplate.gov",
+"myra.gov",
+"myshared.ru",
+"n-kesteven.gov.uk",
+"n-somerset.gov.uk",
+"nads.gov.ua",
+"nalog.ru",
+"namus.gov",
+"nasa.gov",
+"nationalarchives.gov.uk",
+"nationalservice.gov",
+"nationsreportcard.gov",
+"nbr.gov.bd",
+"nbtbank.com",
+"ncifcrf.gov",
+"ncpw.gov",
+"nctb.gov.bd",
+"ne-derbyshire.gov.uk",
+"nea.gov",
+"nelincs.gov.uk",
+"neobux.com",
+"neolane.net",
+"netflix.com",
+"newegg.com",
+"newmoney.gov",
+"nga.gov",
+"ngu.gov.ua",
+"nhtsa.gov",
+"nic.ru",
+"nidw.gov.bd",
+"nij.gov",
+"nio.gov.uk",
+"niscc.gov.uk",
+"nist.gov",
+"nixonlibrary.gov",
+"nkh.gov.hu",
+"noaa.gov",
+"nordea.dk",
+"nordea.com",
+"nordea.fi",
+"nordea.no",
+"nordea.se",
+"north-ayrshire.gov.uk",
+"north-norfolk.gov.uk",
+"northlincs.gov.uk",
+"norwich.gov.uk",
+"notifications.service.gov.uk",
+"nottinghamcity.gov.uk",
+"nrc-gateway.gov",
+"nrc.gov",
+"nrel.gov",
+"nsf.gov",
+"nsopr.gov",
+"nsopw.gov",
+"nwtrb.gov",
+"oculus.com",
+"ofcm.gov",
+"office.com",
+"officemag.ru",
+"ok.ru",
+"omb.gov",
+"ombudsman.gov.tr",
+"onedrive.com",
+"onguardonline.gov",
+"opengl.org",
+"openinternet.gov",
+"ordsvy.gov.uk",
+"ornl.gov",
+"oshrc.gov",
+"osti.gov",
+"oxfordshire.gov.uk",
+"ozon.ru",
+"paauditor.gov",
+"paccar.com",
+"paddle8.com",
+"pandemicflu.gov",
+"passport.gov.uk",
+"payeer.com",
+"paymentaccuracy.gov",
+"paypal-community.com",
+"paypal.be",
+"paypal.cn",
+"paypal.co.il",
+"paypal.co.uk",
+"paypal.com",
+"paypal.com.au",
+"paypal.com.br",
+"paypal.com.mx",
+"paypal.de",
+"paypal.dk",
+"paypal.es",
+"paypal.fr",
+"paypal.nl",
+"paypal.se",
+"pbgc.gov",
+"pc.gov.au",
+"pch.com",
+"penanghill.gov.my",
+"pepfar.gov",
+"performance.gov",
+"pinterest.co.kr",
+"pinterest.com",
+"pinterest.de",
+"pinterest.jp",
+"pinterest.se",
+"pkc.gov.uk",
+"planeta.ru",
+"platron.ru",
+"plymouth.gov.uk",
+"pm.gov.uk",
+"pmf.gov",
+"pmi.gov",
+"pncbank.com",
+"pokerstars.com",
+"pokerstars.fr",
+"pokerstars.it",
+"pokerstars.net",
+"priorbank.by",
+"privatbank.ua",
+"prospertx.gov",
+"prostocash.com",
+"provident.bank",
+"psbank.ru",
+"psc.gov",
+"punjab.gov.in",
+"puzzle-english.com",
+"qiwi.com",
+"qiwi.ru",
+"rabota.ru",
+"rbkc.gov.uk",
+"ready.gov",
+"reaganlibrary.gov",
+"redbridge.gov.uk",
+"reddit.com",
+"reebok.es",
+"reebok.nl",
+"reginfo.gov",
+"regulations.gov",
+"reisebank.de",
+"renfrewshire.gov.uk",
+"rentonwa.gov",
+"reportband.gov",
+"rgs.ru",
+"richmond.gov.uk",
+"rivers.gov",
+"rkn.gov.ru",
+"ros.gov.uk",
+"roseltorg.ru",
+"rostelecom.ru",
+"roundrocktexas.gov",
+"royalmail.com",
+"rozetka.com.ua",
+"rt.com",
+"rt.ru",
+"rushcliffe.gov.uk",
+"rutubeinfo.ru",
+"sacn.gov.uk",
+"safercar.gov",
+"samhsa.gov",
+"sanmarcostx.gov",
+"sberbank.ru",
+"sberbank-ast.ru",
+"sbir.gov",
+"sbis.ru",
+"scality.com",
+"scdhhs.gov",
+"science360.gov",
+"sciencebase.gov",
+"scijinks.gov",
+"sec.gov",
+"secretservice.gov",
+"section508.gov",
+"semnan.ac.ir",
+"senate.gov",
+"sendgrid.net",
+"seniorcorps.gov",
+"serpro.gov.br",
+"service.gov.uk",
+"sftool.gov",
+"shetland.gov.uk",
+"shropshire-cc.gov.uk",
+"shutterstock.com",
+"sigtarp.gov",
+"sk.ru",
+"skat.dk",
+"skatteverket.se",
+"skbbank.ru",
+"skittles.com",
+"skydio.com",
+"skype.com",
+"slideshare.com",
+"smart.gov",
+"smida.gov.ua",
+"smokefree.gov",
+"snickers.com",
+"solardecathlon.gov",
+"sourceforge.net",
+"south-ayrshire.gov.uk",
+"sovest.ru",
+"spbrealty.ru",
+"sportmaster.ru",
+"squarespace.com",
+"sravni.ru",
+"srs.gov",
+"staffordbc.gov.uk",
+"stat.gov.az",
+"stedmundsbury.gov.uk",
+"sthelens.gov.uk",
+"stihl.ru",
+"stopalcoholabuse.gov",
+"stopfraud.gov",
+"studentloans.gov",
+"subscribe.ru",
+"suffolkcc.gov.uk",
+"suncorpbank.com.au",
+"sundaymirror.co.uk",
+"sunlight.net",
+"superjob.ru",
+"surestart.gov.uk",
+"sutton.gov.uk",
+"swansea.gov.uk",
+"swift.com",
+"symantec.com",
+"synologynotification.com",
+"taobao.com",
+"tatar.ru",
+"tauntondeane.gov.uk",
+"tda.gov.uk",
+"tdk.gov.tr",
+"tdscpc.gov.in",
+"telework.gov",
+"tenders.gov.au",
+"textmagic.com",
+"tfhrc.gov",
+"thebell.io",
+"thecoolspot.gov",
+"thinkroadsafety.gov.uk",
+"tiaabank.com",
+"ticketland.ru",
+"tinder.com",
+"tinkoff.ru",
+"tomsk.gov.ru",
+"torbay.gov.uk",
+"tradingstandards.gov.uk",
+"treas.gov",
+"trial-sport.ru",
+"tsa.gov",
+"tst.gov.br",
+"tuba.gov.tr",
+"turystyka.gov.pl",
+"tutu.ru",
+"twitch.tv",
+"twitter.com",
+"twix.com",
+"uber.com",
+"ucarecdn.com",
+"ucrdatatool.gov",
+"udall.gov",
+"ukvisas.gov.uk",
+"ulmart.ru",
+"unicor.gov",
+"uniras.gov.uk",
+"ups.com",
+"uralairlines.ru",
+"us-cert.gov",
+"usa.gov",
+"usadf.gov",
+"usaid.gov",
+"usap.gov",
+"uscg.gov",
+"usconsulate.gov",
+"usmission.gov",
+"usphs.gov",
+"uspis.gov",
+"usps.com",
+"usps.gov",
+"ustreas.gov",
+"utair.ru",
+"utkonos.ru",
+"vaccines.gov",
+"valeofglamorgan.gov.uk",
+"verizonwireless.com",
+"vigoda.ru",
+"visa.co.uk",
+"visa.com",
+"visa.com.ar",
+"visa.com.br",
+"visa.com.cn",
+"visa.com.tw",
+"visa.pl",
+"vistacampus.gov",
+"vk.com",
+"vkrugudruzei.ru",
+"vkusnyblog.ru",
+"vmc.gov.in",
+"voa.gov.uk",
+"volunteer.gov",
+"vote.gov",
+"walsall.gov.uk",
+"wandsworth.gov.uk",
+"wartimecontracting.gov",
+"warwickdc.gov.uk",
+"wealden.gov.uk",
+"wellingtonfl.gov",
+"west-lindsey.gov.uk",
+"westernunion.com",
+"westernunion.ru",
+"westlothian.gov.uk",
+"whatsapp.com",
+"whistleblowers.gov",
+"wirral.gov.uk",
+"wlga.gov.uk",
+"womenshealth.gov",
+"wrexham.gov.uk",
+"wrigley.com",
+"wrp.gov",
+"yandex-team.ru",
+"york.gov.uk",
+"youla.ru",
+"youth.gov",
+"youthrules.gov",
+"youtube.com",
+"zcts.ru",
+"zendesk.com",
+"zionsbank.com",
+"zomato.com"}
+
+
+spam-allow = {"126.com",
+"163.com",
+"1gost.info",
+"1stnationalbank.com",
+"2o7.net",
+"365online.com",
+"4at1.com",
+"53.com",
+"5iantlavalamp.com",
+"abl.com.pk",
+"about.com",
+"accessbankplc.com",
+"adelphia.net",
+"adib.ae",
+"adobe.com",
+"agora-inc.com",
+"agoramedia.com",
+"aibgb.co.uk",
+"aib.ie",
+"airdriesavingsbank.com",
+"akamai.net",
+"akamaitech.net",
+"aldermore.co.uk",
+"alexa.com",
+"alliancebank.com.my",
+"alliancefg.com",
+"alliantcreditunion.com",
+"alliantcreditunion.org",
+"allianz.de",
+"allybank.com",
+"alterna.ca",
+"amazon.com",
+"americanexpress.ch",
+"americanexpress.com",
+"anadolubank.nl",
+"ancestry.com",
+"anz.com",
+"anz.co.nz",
+"aol.com",
+"apache.org",
+"apple.com",
+"arbuthnotlatham.co.uk",
+"arcamax.com",
+"asb.co.nz",
+"ask.com",
+"astrology.com",
+"atdmt.com",
+"att.net",
+"authorize.net",
+"autorambler.ru",
+"axisbank.co.in",
+"axisbank.com",
+"b2bbank.com",
+"baaderbank.de",
+"baidu.com",
+"baloise.ch",
+"baml.com",
+"banamex.com",
+"bancanetbsc.do",
+"bancanetsantacruz.com.do",
+"bancapulia.it",
+"bancarios.com",
+"bancastato.ch",
+"bancatransilvania.ro",
+"bancobase.com",
+"bancobic.ao",
+"bancobic.pt",
+"bancobpi.pt",
+"banco.bradesco",
+"bancobrasil.com.br",
+"bancochile.cl",
+"bancochile.com",
+"bancoestado.cl",
+"bancofalabella.cl",
+"bancofalabella.com.co",
+"bancofalabella.pe",
+"bancomer.com",
+"bancopopolare.it",
+"bancopostaclick.it",
+"bancoposta.it",
+"bancosantander.es",
+"bancovotorantimcartoes.com.br",
+"bank24.ru",
+"bankalhabib.com",
+"bankaustria.at",
+"bank.barclays.co.uk",
+"bankbgzbnpparibas.pl",
+"bankcardservices.co.uk",
+"bankcomm.com",
+"bankcoop.ch",
+"bankiabancapersonal.es",
+"bankia.com",
+"bankia.es",
+"bankinter.com",
+"bankinter.es",
+"bankmutual.com",
+"bankofamerica.com",
+"bankofcanada.ca",
+"bankofchina.com",
+"bankofcyprus.com",
+"bankofindia.co.nz",
+"bankofireland.com",
+"bank-of-ireland.co.uk",
+"bankofirelanduk.com",
+"bankofoklahoma.com",
+"bankofscotland.co.uk",
+"banksinarmas.com",
+"bankvonroll.ch",
+"bankwest.com.au",
+"banque-casino.fr",
+"banquepopulaire.fr",
+"banquescotia.com",
+"barclaycard.co.uk",
+"barclaycard.de",
+"barclaycard.es",
+"barclays.com",
+"barclays.co.uk",
+"barclayspartnerfinance.com",
+"barclays.sc",
+"barodanzltd.co.nz",
+"basler.ch",
+"bbandt.com",
+"bbc.co.uk",
+"bcentral.com",
+"bci.cl",
+"bcp.com.pe",
+"bcv.ch",
+"bcvs.ch",
+"bekb.ch",
+"bellevue.ch",
+"bellsouth.net",
+"bendigobank.com.au",
+"berliner-bank.de",
+"berliner-sparkasse.de",
+"bfanet.ao",
+"bfi0.com",
+"bgfi.com",
+"bgfionline.com",
+"bgzbnpparibas.pl",
+"billmelater.com",
+"bing.com",
+"bkb.ch",
+"bk.rw",
+"bks.at",
+"blkb.ch",
+"bmocm.com",
+"bmo.com",
+"bmogam.com",
+"bmoharris.com",
+"bmoharrisprivatebankingonline.com",
+"bmoinvestorline.com",
+"bmonesbittburns.com",
+"bnl.it",
+"bnpparibas.com",
+"bnpparibas.fr",
+"boc.cnnz",
+"bonuscard.ch",
+"bpe-gruposantander.com",
+"bpi.pt",
+"bpostbank.be",
+"bradescardonline.com.br",
+"bradesco.com.br",
+"bradescoseguranca.com.br",
+"bridgetrack.com",
+"bridgewaterbank.ca",
+"bsibank.com",
+"btrl.ro",
+"bt-trade.ro",
+"businessonline-boi.com",
+"bzbank.ch",
+"ca-cib.com",
+"ca-egypt.com",
+"cafbank.org",
+"cafe24.com",
+"cafonline.org",
+"caisse-epargne.com",
+"caisse-epargne.fr",
+"caixabank.com",
+"caixa.gov.br",
+"cajasur.es",
+"camsonline.com",
+"canadiandirect.com",
+"capitalone360.com",
+"capitalone.com",
+"capitaloneonline.co.uk",
+"capitecbank.co.za",
+"cariparma.it",
+"carrefour-banque.fr",
+"cartabcc.it",
+"cartabccpos.it",
+"cartasi.it",
+"ca-suisse.com",
+"catalunyacaixa.com",
+"cbg.gm",
+"cbonline.co.uk",
+"cembra.ch",
+"cenbank.org",
+"centralbank.ae",
+"charitybank.org",
+"charter.net",
+"chase.com",
+"chebanca.it",
+"chinatrust.com.tw",
+"cial.ch",
+"cibc.com",
+"cic.ch",
+"cimbclicks.com.my",
+"citibank.ae",
+"citibank.co.in",
+"citibank.com",
+"citibank.co.uk",
+"citibankonline.com",
+"citibusiness.com",
+"citicards.com",
+"citi.com",
+"citi.co.nz",
+"citi.eu",
+"citigroup.com",
+"citizensbank.ca",
+"citizensbank.com",
+"civibank.com",
+"civibank.it",
+"cjb.net",
+"classmates.com",
+"clickbank.net",
+"closebrothers.com",
+"closebrothers.co.uk",
+"clubsc.ch",
+"cnet.com",
+"cnn.com",
+"co.kg",
+"colpatria.com",
+"colpatria.com.co",
+"comcast.net",
+"com.com",
+"commbank.com.au",
+"commerzbank.com",
+"commerzbank.de",
+"com.ne.kr",
+"coopbank.dk",
+"co-operativebank.co.uk",
+"cornerbanca.ch",
+"cornercard.ch",
+"cornercard.com",
+"corner.ch",
+"corporate-ir.net",
+"cosycard.ch",
+"coutts.com",
+"cox.net",
+"craigslist.org",
+"credit-agricole.com",
+"credit-agricole.fr",
+"creditagricole.rs",
+"credit-suisse.com",
+"cs.com",
+"css.ch",
+"ctbcbank.com",
+"ctfs.com",
+"custhelp.com",
+"cwbank.com",
+"cwbankgroup.com",
+"cwt.ca",
+"cybg.com",
+"danskebankas.lt",
+"danskebank.com",
+"danskebank.co.uk",
+"danskebank.de",
+"danskebank.dk",
+"danskebank.ee",
+"danskebank.fi",
+"danskebank.ie",
+"danskebank.no",
+"datatrans.biz",
+"datatrans.ch",
+"daum.net",
+"db.com",
+"dbs.com",
+"dd.se",
+"debian.org",
+"dell.com",
+"demirbank.kg",
+"denizbank.com",
+"desjardins.ca",
+"desjardins.com",
+"deutschebank.be",
+"deutschebank.co.nz",
+"deutsche-bank.de",
+"diamondbank.com",
+"dibpak.com",
+"directnic.com",
+"directtrack.com",
+"discovercard.com",
+"discover.com",
+"discovery.co.za",
+"dnbnord.lt",
+"domain.com",
+"doubleclick.com",
+"dresdner-bank.de",
+"dsbbank.sr",
+"dsbl.org",
+"duncanlawrie.com",
+"earthlink.net",
+"easybank.at",
+"easylnk.com",
+"ebay.com",
+"ebay.co.uk",
+"ebay.de",
+"ebayimg.com",
+"ebaystatic.com",
+"ecobank.com",
+"edgesuite.net",
+"ediets.com",
+"edwardjones.com",
+"egroups.com",
+"e-gulfbank.com",
+"emode.com",
+"esunbank.com.tw",
+"example.com",
+"example.net",
+"example.org",
+"excite.com",
+"facebook.com",
+"fedex.com",
+"fednetbank.com",
+"fidelity.com",
+"fidor.de",
+"finance.com",
+"finansbank.com.tr",
+"finasta.lt",
+"fineco.it",
+"firstbankcard.com",
+"firstmerit.com",
+"firstnational.com",
+"firstnationalmerchantsolutions.com",
+"firsttrustbank.co.uk",
+"flickr.com",
+"fnbc.ca",
+"fnb.co.za",
+"fnb-online.com",
+"freebsd.org",
+"free.fr",
+"friuladria.it",
+"f-secure.com",
+"garantibank.eu",
+"garantibank.nl",
+"garanti.com.tr",
+"gazprombank.ch",
+"gazprombank.ru",
+"generali.es",
+"genevoise.ch",
+"gentoo.org",
+"geocities.com",
+"gkb.ch",
+"gmail.com",
+"gmx.net",
+"go.com",
+"godaddy.com",
+"googleadservices.com",
+"google.co.in",
+"google.com",
+"google.it",
+"google.ru",
+"granitbank.hu",
+"grisoft.com",
+"gtbank.com",
+"halifax.co.uk",
+"hallmark.com",
+"handelsbanken.se",
+"harrodsbank.co.uk",
+"hbl.com",
+"hblibank.com",
+"hblibank.com.pk",
+"hdfcbank.com",
+"heartland.co.nz",
+"hellenicbank.com",
+"hinet.net",
+"hkbea.com",
+"hlb.com.kh",
+"hlb.com.my",
+"hoaresbank.co.uk",
+"home.barclays",
+"hongleongconnect.com.kh",
+"hongleongconnect.com.vn",
+"hongleongconnect.my",
+"hotbar.com",
+"hotmail.com",
+"hotpop.com",
+"hp.com",
+"hsbc.com",
+"hsbc.com.ar",
+"hsbc.com.hk",
+"hsbc.co.nz",
+"hsbc.co.uk",
+"hypovereinsbank.co.uk",
+"hypovereinsbank.de",
+"ibm.com",
+"icbcnz.com",
+"icicibank.co.in",
+"icicibank.com",
+"icicibankprivatebanking.com",
+"icorner.ch",
+"icscards.de",
+"icscards.nl",
+"incredimail.com",
+"ing.be",
+"ing.com",
+"ing-diba.de",
+"ingdirect.ca",
+"ing.lu",
+"ing.nl",
+"ingvysyabank.com",
+"interac.ca",
+"investorplace.com",
+"iobnet.co.in",
+"isbank.com.tr",
+"isbank.de",
+"isbank.ge",
+"isbank.iq",
+"isbankkosova.com",
+"itau.com.br",
+"ivillage.com",
+"joingevalia.com",
+"jpmchase.com",
+"jpmorgan.com",
+"jsafrasarasin.com",
+"julianhodgebank.com",
+"juliusbaer.com",
+"juno.com",
+"jyskebank.dk",
+"kantonalbank.ch",
+"kernel.org",
+"key.com",
+"kiwibank.co.nz",
+"kotak.com",
+"kredytbank.pl",
+"kreissparkasse-schwalm-eder.de",
+"ksklb.de",
+"kutxabank.es",
+"laboralkutxa.com",
+"lacaixa.cat",
+"lacaixa.es",
+"laurentianbank.ca",
+"lbb.de",
+"lcl.com",
+"lcl.fr",
+"li.ru",
+"list.ru",
+"liveinternet.ru",
+"livejournal.com",
+"lloydsbank.com",
+"lloydsbankcommercial.com",
+"lloydsbankinggroup.com",
+"lloydstsb.ch",
+"lloydstsb.co.uk",
+"lombardodier.com",
+"loydsbank.com",
+"lycos.com",
+"m7z.net",
+"mac.com",
+"macromedia.com",
+"maerki-baumann.ch",
+"mail.com",
+"mail.ru",
+"mailscanner.info",
+"mandtbank.com",
+"manulifebank.ca",
+"manulifebankselect.ca",
+"manulife.com",
+"manulifeone.ca",
+"marketwatch.com",
+"mashreqbank.com",
+"mastercard.com",
+"maybank2u.com",
+"maybank2u.com.my",
+"mcafee.com",
+"mchsi.com",
+"mdmbank.com",
+"mechanicsbank.com",
+"medbank.lt",
+"messagelabs.com",
+"metrobankdirect.com",
+"metrobankonline.co.uk",
+"microsoft.com",
+"migbank.com",
+"migrosbank.ch",
+"military.com",
+"mindspring.com",
+"mit.edu",
+"mizuhobank.co.jp",
+"mmwarburg.lu",
+"monster.com",
+"montepio.pt",
+"morganstanley.com",
+"mozilla.com",
+"mps.it",
+"ms.com",
+"msn.com",
+"mufg.jp",
+"myonlineresourcecenter.com",
+"myonlineservices.ch",
+"myspace.com",
+"nate.com",
+"nationalesuisse.ch",
+"nationwide-communications.co.uk",
+"nationwide.co.uk",
+"nationwide-service.co.uk",
+"natwest.com",
+"navyfederal.org",
+"nbc.ca",
+"netflix.com",
+"netscape.com",
+"netscape.net",
+"netzero.net",
+"newyorkfed.org",
+"nibl.com.np",
+"nod32.com",
+"nordea.fi",
+"nordea.lt",
+"nordfynsbank.dk",
+"norisbank.de",
+"norman.com",
+"notenstein.ch",
+"nuvisionfederal.com",
+"nytimes.com",
+"oceanbank.com",
+"onlinesbi.com",
+"openoffice.org",
+"openxmlformats.org",
+"optonline.net",
+"orchardbank.com",
+"osdn.com",
+"ostsaechsische-sparkasse-dresden.de",
+"overstock.com",
+"pacbell.net",
+"pandasoftware.com",
+"passport.com",
+"paylife.at",
+"paypal.be",
+"paypal-brasil.com.br",
+"paypal.ca",
+"paypal.ch",
+"paypal.co.il",
+"paypal.com",
+"paypal.com.au",
+"paypal.com.br",
+"paypal-communication.com",
+"paypal-community.com",
+"paypal.com.mx",
+"paypal.com.pt",
+"paypal.co.uk",
+"paypal-customerfeedback.com",
+"paypal.de",
+"paypal-deutschland.de",
+"paypal.dk",
+"paypal.es",
+"paypal-exchanges.com",
+"paypal.fr",
+"paypal.it",
+"paypal-marketing.co.uk",
+"paypal-marketing.pl",
+"paypal.net",
+"paypal.nl",
+"paypal.no",
+"paypal-notify.com",
+"paypal-now.com",
+"paypalobjects.com",
+"paypal-opwaarderen.nl",
+"paypal-pages.com",
+"paypal.pt",
+"paypal.ru",
+"paypal.se",
+"paypal-search.com",
+"paypal-shopping.co.uk",
+"paypal-techsupport.com",
+"pbebank.com",
+"pcfinancial.ca",
+"peoplepc.com",
+"permanenttsb.ie",
+"plaxo.com",
+"pnc.com",
+"popolarevicenza.it",
+"postbank.de",
+"postepay.it",
+"postfinancearena.ch",
+"postfinance.ch",
+"postfinance.info",
+"price.ru",
+"prodigy.net",
+"publicislamicbank.com.my",
+"rabobank.com",
+"rabobank.co.nz",
+"rabobank.nl",
+"radaruol.com.br",
+"rahnbodmer.ch",
+"raiffeisenbank.rs",
+"raiffeisen.ch",
+"raiffeisen.hu",
+"raiffeisen.li",
+"raiffeisen.ru",
+"rambler-co.ru",
+"rambler.ru",
+"raphaelsbank.com",
+"rbc.com",
+"rbcroyalbank.com",
+"rbs.co.uk",
+"rbssecure.co.uk",
+"rbsworldpay.com",
+"rcb.at",
+"real.com",
+"recordbank.be",
+"redhat.com",
+"rediff.com",
+"regiobank.nl",
+"regions.com",
+"regionsnet.com",
+"renasantbank.com",
+"rhbgroup.com",
+"rogersbank.com",
+"rogers.com",
+"rothschildbank.com",
+"rothschild.com",
+"royalbank.com",
+"rr.com",
+"sagepay.com",
+"sagepay.co.uk",
+"sainsburysbank.co.uk",
+"samba.com",
+"santander.cl",
+"santander.com",
+"santander.com.br",
+"santander.com.mx",
+"santandercorretora.com.br",
+"santander.co.uk",
+"santanderesfera.com.br",
+"santandersantiago.cl",
+"sarasin.ch",
+"sbcglobal.net",
+"sberbank.ch",
+"sbs.net.nz",
+"sc.com",
+"schoellerbank.at",
+"scotiabank.ca",
+"scotiabank.com",
+"scotiamocatta.com",
+"scotiaonline.com",
+"s.de",
+"sec.gov",
+"securetrustbank.com",
+"service-sparkasse.de",
+"serviciobancomer.com",
+"sf.net",
+"shawbrook.co.uk",
+"shaw.ca",
+"shkb.ch",
+"shockwave.com",
+"six-group.com",
+"six-payment-services.com",
+"skrill.com",
+"sls-direkt.de",
+"smithbarney.com",
+"snb.ch",
+"snsbank.nl",
+"societegenerale.fr",
+"sourceforge.net",
+"spamcop.net",
+"sparda-a.de",
+"sparda-bank-hamburg.de",
+"sparda-b.de",
+"sparda-bw.de",
+"sparda-h.de",
+"sparda-hessen.de",
+"sparda-m.de",
+"sparda-ms.de",
+"sparda-n.de",
+"sparda-ostbayern.de",
+"sparda-sw.de",
+"sparda-verband.de",
+"sparda-west.de",
+"sparkasse.at",
+"sparkasse-bank-malta.com",
+"sparkasse-bielefeld.de",
+"sparkasseblog.de",
+"sparkasse-bochum.de",
+"sparkasse.ch",
+"sparkasse.de",
+"sparkasse-gera-greiz.de",
+"sparkasse-hamm.de",
+"sparkasse-heidelberg.de",
+"sparkasse-ingolstadt.de",
+"sparkasse-mittelthueringen.de",
+"speedera.net",
+"sportsline.com",
+"standardbank.com",
+"standardbank.co.za",
+"standardchartered.com.gh",
+"standardchartered.com.my",
+"subscribe.ru",
+"sun.com",
+"suncorpbank.com.au",
+"suntrust.com",
+"swedbank.com",
+"swedbank.ee",
+"swedbank.lt",
+"swedbank.lu",
+"swedbank.se",
+"swisscanto.ch",
+"swisscaution.ch",
+"swissquote.ch",
+"sydbank.dk",
+"sympatico.ca",
+"tails.nl",
+"tangerine.ca",
+"tcb-bank.com.tw",
+"tdbank.com",
+"tdcommercialbanking.com",
+"telus.net",
+"terra.com.br",
+"tescobank.com",
+"ticketmaster.com",
+"tinyurl.com",
+"tiscali.co.uk",
+"tns-counter.ru",
+"tom.com",
+"tone.co.nz",
+"t-online.de",
+"top4top.ru",
+"tsbbank.co.nz",
+"tsb.co.nz",
+"tsb.co.uk",
+"tux.org",
+"twitter.com",
+"ubibanca.com",
+"ubs.com",
+"ulsterbankanytimebanking.co.uk",
+"ulsterbank.co.uk",
+"unibancoconnect.pt",
+"unibanco.pt",
+"unicreditbank.lt",
+"unicredit.eu",
+"unicreditgroup.eu",
+"unicredit.it",
+"unionbankcameroon.com",
+"unionbank.com",
+"unity.co.uk",
+"uob.com.sg",
+"uobgroup.com",
+"uol.com.br",
+"ups.com",
+"usbank.com",
+"valianttrust.com",
+"vaudoise.ch",
+"venetobanca.it",
+"venetobanka.al",
+"verizon.net",
+"versabank.com",
+"videobank.it",
+"virginmoney.com",
+"visa.com.ar",
+"visa.com.br",
+"visaeurope.ch",
+"visaeurope.com",
+"viseca.ch",
+"volksbank.de",
+"volkswagenbank.de",
+"vpbank.com",
+"vr.de",
+"vwbank.de",
+"w3.org",
+"wachovia.com",
+"walmart.com",
+"wamu.com",
+"wanadoo.fr",
+"washingtonpost.com",
+"weatherbug.com",
+"weatherbys.co.uk",
+"web.de",
+"webshots.com",
+"webtv.net",
+"wegelin.ch",
+"wellsfargo.com",
+"wellsfargoemail.com",
+"westernunion.ca",
+"westernunion.com",
+"westernunion.fr",
+"westernunion.se",
+"westpac.com.au",
+"westpac.co.nz",
+"wir.ch",
+"wordpress.com",
+"worldbank.org",
+"worldpay.com",
+"wsj.com",
+"wvb.de",
+"xmlsoap.org",
+"yacht.nl",
+"yahoo.ca",
+"yahoo.co.jp",
+"yahoo.co.kr",
+"yahoo.com",
+"yahoo.com.br",
+"yahoo.co.uk",
+"yahoogroups.com",
+"yandex.net",
+"yandex.ru",
+"ybonline.co.uk",
+"yimg.com",
+"yopi.de",
+"yorkshirebank.co.uk",
+"yourbankcard.com",
+"yoursite.com",
+"youtube.com",
+"zagbank.ca",
+"zdnet.com",
+"zenithbank.com",
+"zkb.ch",
+"zugerkb.ch",
+"vistaprint.dk",
+"vistaprint.com",
+"anpdm.com",
+"dovecot.org",
+"exacttarget.com",
+"github.com",
+"isc.org",
+"lists.isc.org",
+"lists.roundcube.net",
+"svn.apache.org",
+"taggedmail.com",
+"tumblr.com"}
+
+spam-spdk = {"1cfresh.com",
+"4chan.org",
+"6pm.com",
+"about.com",
+"addthis.com",
+"adf.ly",
+"adobe.com",
+"adp.com",
+"adschemist.com",
+"airbnb.com",
+"airtel.in",
+"alibaba.com",
+"aliexpress.com",
+"alipay.com",
+"allrecipes.com",
+"amazon.ca",
+"amazon.cn",
+"amazon.co.jp",
+"amazon.com",
+"amazon.co.uk",
+"amazon.de",
+"amazon.es",
+"amazon.fr",
+"amazon.in",
+"amazon.it",
+"amazon.ru",
+"americanexpress.com",
+"ancestry.com",
+"android.com",
+"apple.com",
+"asana.com",
+"att.com",
+"autohome.com.cn",
+"avg.com",
+"aweber.com",
+"badoo.com",
+"bankofamerica.com",
+"basecamp.com",
+"battle.net",
+"bet365.com",
+"biglobe.ne.jp",
+"bitly.com",
+"bleacherreport.com",
+"blogger.com",
+"bloomberg.com",
+"booking.com",
+"box.com",
+"bt.com",
+"capitalone.com",
+"cdiscount.com",
+"change.org",
+"chase.com",
+"cisco.com",
+"citi.com",
+"costco.com",
+"craigslist.org",
+"custhelp.com",
+"dell.com",
+"delta.com",
+"diply.com",
+"discovercard.com",
+"disqus.com",
+"dropbox.com",
+"drweb.com",
+"e-boks.dk",
+"ebay.ca",
+"ebay.com",
+"ebay.com.au",
+"ebay.co.uk",
+"ebay.de",
+"ebay.fr",
+"ebay.in",
+"ebay.it",
+"ebay.ru",
+"etsy.com",
+"evernote.com",
+"expedia.com",
+"facebook.com",
+"fedex.com",
+"fidelity.com",
+"fishki.net",
+"flickr.com",
+"flirchi.com",
+"force.com",
+"freepik.com",
+"gap.com",
+"gawker.com",
+"github.com",
+"gizmodo.com",
+"godaddy.com",
+"googleadservices.com",
+"googleusercontent.com",
+"groupon.com",
+"hdfcbank.com",
+"hgtv.com",
+"hh.ru",
+"hm.com",
+"houzz.com",
+"hubspot.com",
+"icicibank.com",
+"icloud.com",
+"ign.com",
+"imgur.com",
+"immobilienscout24.de",
+"indeed.com",
+"indiatimes.com",
+"infusionsoft.com",
+"instagram.com",
+"intel.com",
+"irctc.co.in",
+"kayak.com",
+"kickstarter.com",
+"kijiji.ca",
+"kotaku.com",
+"letsencrypt.org",
+"libero.it",
+"lifehacker.com",
+"likes.com",
+"linkedin.com",
+"linux.com",
+"list-manage.com",
+"mackeeper.com",
+"mailchimp.com",
+"mashable.com",
+"match.com",
+"mercadolibre.com.ar",
+"mercadolivre.com.br",
+"messenger.com",
+"microsoft.com",
+"microsoftonline.com",
+"minmyndighetspost.se",
+"moikrug.ru",
+"mts.ru",
+"neobux.com",
+"netflix.com",
+"newegg.com",
+"nhk.or.jp",
+"nifty.com",
+"nikkeibp.co.jp",
+"nyaa.se",
+"nytimes.com",
+"odnoklassniki.ru",
+"ofd.yandex.ru",
+"ok.ru",
+"olx.ua",
+"overstock.com",
+"ozon.ru",
+"ozon.travel",
+"pandora.com",
+"paypal.ca",
+"paypal.cn",
+"paypal.com",
+"paypal.co.uk",
+"paypal.de",
+"paypal.es",
+"paypal.fr",
+"paypal.it",
+"paypal.ru",
+"paytm.com",
+"pch.com",
+"pinterest.com",
+"porn.com",
+"priceline.com",
+"quora.com",
+"rakuten.co.jp",
+"reddit.com",
+"researchgate.net",
+"salesforce.com",
+"sciencedirect.com",
+"shopify.com",
+"skanetrafiken.se",
+"skat.dk",
+"skatteverket.se",
+"slack.com",
+"slideshare.net",
+"so-net.ne.jp",
+"southwest.com",
+"spotify.com",
+"springer.com",
+"squarespace.com",
+"stalker.com",
+"steampowered.com",
+"stumbleupon.com",
+"surveymonkey.com",
+"swagbucks.com",
+"taboola.com",
+"taleo.net",
+"taobao.com",
+"target.com",
+"taringa.net",
+"taxi.yandex.ru",
+"tele2.ru",
+"thekitchn.com",
+"tochka.com",
+"tokopedia.com",
+"trello.com",
+"tribunnews.com",
+"trulia.com",
+"tumblr.com",
+"twitter.com",
+"ultimate-guitar.com",
+"ups.com",
+"usaa.com",
+"usbank.com",
+"usps.com",
+"verizon.com",
+"verizonwireless.com",
+"vimeo.com",
+"vine.co",
+"vk.com",
+"vmware.com",
+"vtb24.ru",
+"wahoofitness.com",
+"walmart.com",
+"wav.tv",
+"wellsfargo.com",
+"whatsapp.com",
+"wikia.com",
+"wikimedia.org",
+"wikipedia.org",
+"wildberries.ru",
+"wix.com",
+"wordpress.com",
+"wordpress.org",
+"wp.com",
+"xuite.net",
+"xvideos.com",
+"yelp.com",
+"youtube.com",
+"yts.to",
+"zappos.com",
+"zendesk.com",
+"zippyshare.com",
+"zomato.com",
+"zoom.us",
+"zulily.com",
+"zwift.com"}
+
+spam-disposable = {"0815.ru",
+"0clickemail.com",
+"0wnd.net",
+"0wnd.org",
+"1054733.mail-temp.com",
+"10m.email",
+"10minutemail.com",
+"10minutesmail.com",
+"1secmail.com",
+"1secmail.net",
+"1secmail.org",
+"20minutemail.com",
+"2emailock.com",
+"2prong.com",
+"33mail.com",
+"3d-magical-magnet.ru",
+"4warding.com",
+"90bit.ru",
+"9ox.net",
+"a-bc.net",
+"afrobacon.com",
+"alaki.ga",
+"alivance.com",
+"amilegit.com",
+"amiri.net",
+"anonymbox.com",
+"antichef.com",
+"antichef.net",
+"antispam.de",
+"audio.now.im",
+"awex.icu",
+"barenshop.ru",
+"barryogorman.com",
+"baxomale.ht.cx",
+"beefmilk.com",
+"binkmail.com",
+"bio-muesli.net",
+"bobmail.info",
+"bofthew.com",
+"brefmail.com",
+"bsnow.net",
+"bspamfree.org",
+"bugmenot.com",
+"casualdx.com",
+"centermail.com",
+"cetpass.com",
+"chammy.info",
+"choicemail1.com",
+"choocho-telegram.ru",
+"cool.fr.nf",
+"courriel.fr.nf",
+"courrieltemporaire.com",
+"cuvox.de",
+"dandikmail.com",
+"dayrep.com",
+"dcemail.com",
+"deadspam.com",
+"desoz.com",
+"devnullmail.com",
+"dfg6.kozow.com",
+"dfgh.net",
+"digitalsanctuary.com",
+"dingbone.com",
+"discardmail.com",
+"discardmail.de",
+"dispomail.win",
+"dispomail.xyz",
+"disposableaddress.com",
+"disposeamail.com",
+"dispostable.com",
+"divismail.ru",
+"dlesha.ru",
+"dmaster39.ru",
+"dodgit.com",
+"domremonta-nv.ru",
+"donemail.ru",
+"dontreg.com",
+"dontsendmespam.de",
+"dumpandjunk.com",
+"e-mail.com",
+"e-mail.org",
+"e4ward.com",
+"edu.aiot.ze.cx",
+"ekholotdeeper.ru",
+"email-24x7.com",
+"email60.com",
+"emailate.com",
+"emailay.com",
+"emailias.com",
+"emailmiser.com",
+"emailsensei.com",
+"emailtemporanea.net",
+"emailtemporario.com.br",
+"emailtex.com",
+"emailwarden.com",
+"emailx.at.hm",
+"emailxfer.com",
+"emz.net",
+"exbts.com",
+"explodemail.com",
+"extremail.ru",
+"eyeemail.com",
+"fakeinbox.com",
+"fakeinformation.com",
+"fantasymail.de",
+"filzmail.com",
+"fls4.gleeze.com",
+"fotosta.ru",
+"frapmail.com",
+"fudgerub.com",
+"funny-mom.ru",
+"furycraft.ru",
+"garliclife.com",
+"get2mail.fr",
+"getonemail.com",
+"gishpuppy.com",
+"gnomi.ru",
+"goplaygame.ru",
+"greensloth.com",
+"grr.la",
+"gsrv.co.uk",
+"guerillamail.com",
+"guerrillamail.biz",
+"guerrillamail.com",
+"guerrillamail.de",
+"guerrillamail.info",
+"guerrillamail.net",
+"guerrillamail.org",
+"guerrillamailblock.com",
+"h2ocoffe.ru",
+"haltospam.com",
+"hatespam.org",
+"hidemail.de",
+"hmamail.com",
+"hochsitze.com",
+"hulapla.de",
+"hydraulics360.ru",
+"i.xcode.ro",
+"imails.info",
+"inboxclean.com",
+"inboxclean.org",
+"irish2me.com",
+"isdaq.com",
+"iwi.net",
+"jetable.com",
+"jetable.fr.nf",
+"jetable.net",
+"jetable.org",
+"justyland.ru",
+"kasmail.com",
+"kaspop.com",
+"kemampuan.me",
+"kikoxltd.com",
+"killmail.com",
+"killmail.net",
+"kismail.ru",
+"kkm35.ru",
+"klassmaster.com",
+"klzlk.com",
+"koszmail.pl",
+"kurzepost.de",
+"kusrc.com",
+"lackmail.ru",
+"laoho.com",
+"ldaho.biz",
+"leeching.net",
+"letthemeatspam.com",
+"lhsdv.com",
+"lifebyfood.com",
+"lifeguru.online",
+"light-marketing.ru",
+"lol.ovpn.to",
+"lookugly.com",
+"lortemail.dk",
+"lr78.com",
+"madecassol78.ru",
+"mail-temporaire.fr",
+"mail.mezimages.net",
+"mail333.com",
+"mailbidon.com",
+"mailblocks.com",
+"mailbucket.org",
+"mailcatch.com",
+"maildrop.cc",
+"maildx.com",
+"mailed.ro",
+"mailfreeonline.com",
+"mailfs.com",
+"mailin8r.com",
+"mailinater.com",
+"mailinator.com",
+"mailinator.net",
+"mailinator2.com",
+"mailincubator.com",
+"mailme.ir",
+"mailme.lv",
+"mailmetal.com",
+"mailmetrash.com",
+"mailmoat.com",
+"mailnesia.com",
+"mailnull.com",
+"mailscrap.com",
+"mailshell.com",
+"mailsiphon.com",
+"mailsoul.com",
+"mailtrash.net",
+"mailzilla.com",
+"makemetheking.com",
+"mbx.cc",
+"mega.zik.dj",
+"meinspamschutz.de",
+"meltmail.com",
+"messagebeamer.de",
+"mineblue.ru",
+"mintemail.com",
+"misha-rosestoy.ru",
+"moboinfo.xyz",
+"moncourrier.fr.nf",
+"monemail.fr.nf",
+"monmail.fr.nf",
+"mor19.uu.gl",
+"mt2009.com",
+"mvrht.com",
+"mycleaninbox.net",
+"mymail-in.net",
+"mypartyclip.de",
+"myphantomemail.com",
+"mytempemail.com",
+"mytrashmail.com",
+"neomailbox.com",
+"nepwk.com",
+"nervmich.net",
+"nervtmich.net",
+"netmails.com",
+"netmails.net",
+"neverbox.com",
+"newfilm24.ru",
+"niepodam.pl",
+"no-spam.ws",
+"nomail.xl.cx",
+"nomorespamemails.com",
+"nospam.ze.tc",
+"nospam4.us",
+"nospammail.net",
+"notmailinator.com",
+"notsharingmy.info",
+"nowmymail.com",
+"nurfuerspam.de",
+"objectmail.com",
+"obobbo.com",
+"officialrolex.ru",
+"oneoffemail.com",
+"onewaymail.com",
+"onlinenet.info",
+"oopi.org",
+"ordinaryamerican.net",
+"otherinbox.com",
+"ovpn.to",
+"owlpic.com",
+"p33.org",
+"pancakemail.com",
+"partner1bizmoney.ru",
+"pchelovodstvo-tut.ru",
+"piratesdelivery.ru",
+"pmlep.de",
+"politikerclub.de",
+"pookmail.com",
+"powerbank-russia.ru",
+"privacy.net",
+"proxymail.eu",
+"prtnx.com",
+"putthisinyourspamdatabase.com",
+"quickinbox.com",
+"razinrocks.me",
+"rcpt.at",
+"reallymymail.com",
+"recode.me",
+"reconmail.com",
+"recursor.net",
+"reloadpoint.ru",
+"rtrtr.com",
+"s-sakamas.ru",
+"s0ny.net",
+"safe-mail.net",
+"safersignup.de",
+"safetymail.info",
+"safetypost.de",
+"samogonda.ru",
+"sendspamhere.com",
+"senseless-entertainment.com",
+"sgbteamreborn.imouto.pro",
+"shar-kov.ru",
+"sharklasers.com",
+"shiftmail.com",
+"shitmail.me",
+"shortmail.net",
+"sibmail.com",
+"slaskpost.se",
+"smellfear.com",
+"sneakemail.com",
+"sofimail.com",
+"sogetthis.com",
+"soodonims.com",
+"spam4.me",
+"spambob.net",
+"spambog.com",
+"spambog.de",
+"spambog.ru",
+"spambooger.com",
+"spambox.us",
+"spambox.win",
+"spambox.xyz",
+"spamcannon.com",
+"spamcannon.net",
+"spamcon.org",
+"spamcorptastic.com",
+"spamcowboy.com",
+"spamcowboy.net",
+"spamcowboy.org",
+"spamday.com",
+"spamex.com",
+"spamfree.eu",
+"spamfree24.com",
+"spamfree24.de",
+"spamfree24.org",
+"spamgourmet.com",
+"spamgourmet.net",
+"spamgourmet.org",
+"spamhereplease.com",
+"spamhole.com",
+"spamify.com",
+"spaml.de",
+"spammotel.com",
+"spamobox.com",
+"spamslicer.com",
+"spamthis.co.uk",
+"speed.1s.fr",
+"squizzy.net",
+"sskstroy.ru",
+"streetwisemail.com",
+"super-auswahl.de",
+"supermailer.jp",
+"suremail.info",
+"tecninja.xyz",
+"teewars.org",
+"teleosaurs.xyz",
+"teleworm.com",
+"temp-mail.org",
+"tempe-mail.com",
+"tempemail.com",
+"tempemail.net",
+"tempinbox.co.uk",
+"tempinbox.com",
+"tempmail.it",
+"tempmail.top",
+"tempmail.win",
+"tempomail.fr",
+"thankyou2010.com",
+"thisisnotmyrealemail.com",
+"thrott.com",
+"throwawayemailaddress.com",
+"tilien.com",
+"titaspaharpur1.gq",
+"tmailinator.com",
+"tradermail.info",
+"trash-mail.at",
+"trash-mail.com",
+"trash-mail.de",
+"trash2009.com",
+"trashdevil.com",
+"trashemail.de",
+"trashinbox.net",
+"trashmail.at",
+"trashmail.com",
+"trashmail.de",
+"trashmail.me",
+"trashmail.net",
+"trashmail.org",
+"trashmail.ws",
+"trashmailer.com",
+"trashymail.com",
+"trbvm.com",
+"trbvn.com",
+"trillianpro.com",
+"tvchd.com",
+"twinmail.de",
+"tyldd.com",
+"uggsrock.com",
+"upliftnow.com",
+"urhen.com",
+"ussv.club",
+"vapecentral.ru",
+"venompen.com",
+"veryrealemail.com",
+"vkusup.ru",
+"voemail.com",
+"vssms.com",
+"weammo.xyz",
+"webcool.club",
+"wegwerfadresse.de",
+"wegwerfemail.com",
+"wegwerfemail.de",
+"wegwerfmail.de",
+"wegwerfmail.net",
+"wegwerfmail.org",
+"wh4f.org",
+"whyspam.me",
+"willhackforfood.biz",
+"willselfdestruct.com",
+"wronghead.com",
+"wwwnew.eu",
+"xemaps.com",
+"xitroo.com",
+"xmaily.com",
+"xoxy.net",
+"xww.ro",
+"yep.it",
+"yevme.com",
+"yopmail.com",
+"yopmail.fr",
+"yopmail.net",
+"yuurok.com",
+"zagorodnyi-domik.ru",
+"zdorovpagh.ru",
+"zehnminutenmail.de",
+"zippymail.info",
+"zoemail.net"}
+
+spam-free = {"0-mail.com",
+"027168.com",
+"0815.su",
+"0sg.net",
+"10mail.org",
+"10minutemail.co.za",
+"123.com",
+"123india.com",
+"123mail.cl",
+"123mail.org",
+"126.com",
+"139.com",
+"150mail.com",
+"150ml.com",
+"15meg4free.com",
+"163.com",
+"16mail.com",
+"188.com",
+"189.cn",
+"1ce.us",
+"1chuan.com",
+"1funplace.com",
+"1internetdrive.com",
+"1mail.net",
+"1me.net",
+"1mum.com",
+"1musicrow.com",
+"1pad.de",
+"1zhuan.com",
+"2-mail.com",
+"20email.eu",
+"20mail.in",
+"20mail.it",
+"212.com",
+"21cn.com",
+"24horas.com",
+"2980.com",
+"2bmail.co.uk",
+"2die4.com",
+"2trom.com",
+"3126.com",
+"321media.com",
+"37.com",
+"3ammagazine.com",
+"3dmail.com",
+"3g.ua",
+"3mail.ga",
+"444.net",
+"4email.net",
+"4mg.com",
+"4warding.net",
+"4x4man.com",
+"50mail.com",
+"5iron.com",
+"60minutemail.com",
+"6ip.us",
+"74.ru",
+"7mail.ml",
+"88.am",
+"8mail.ml",
+"97rock.com",
+"99experts.com",
+"9online.fr",
+"a1.net",
+"a45.in",
+"aaamail.zzn.com",
+"aapt.net.au",
+"aaronkwok.net",
+"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com",
+"abcflash.net",
+"abdulnour.com",
+"aberystwyth.com",
+"about.com",
+"abv.bg",
+"abwesend.de",
+"abyssmail.com",
+"acceso.or.cr",
+"access4less.net",
+"accountant.com",
+"acdcfan.com",
+"acmemail.net",
+"acninc.net",
+"activist.com",
+"adam.com.au",
+"add3000.pp.ua",
+"addcom.de",
+"address.com",
+"adelphia.net",
+"adexec.com",
+"adfarrow.com",
+"adoption.com",
+"ados.fr",
+"adrenalinefreak.com",
+"advalvas.be",
+"advantimo.com",
+"aeiou.pt",
+"africamail.com",
+"africamel.net",
+"agoodmail.com",
+"ahaa.dk",
+"ahk.jp",
+"aichi.com",
+"aim.com",
+"aircraftmail.com",
+"airforce.net",
+"airforceemail.com",
+"airpost.net",
+"ajacied.com",
+"ak47.hu",
+"aknet.kg",
+"albawaba.com",
+"algeria.com",
+"alhilal.net",
+"alibaba.com",
+"alice.it",
+"aliyun.com",
+"allergist.com",
+"allmail.net",
+"allracing.com",
+"allsaintsfan.com",
+"alpenjodel.de",
+"alphafrau.de",
+"alskens.dk",
+"alternativagratis.com",
+"alumni.com",
+"alumnidirector.com",
+"alvilag.hu",
+"amail.com",
+"amele.com",
+"america.hm",
+"ameritech.net",
+"amnetsal.com",
+"amorki.pl",
+"amrer.net",
+"amuro.net",
+"amuromail.com",
+"ananzi.co.za",
+"ancestry.com",
+"andylau.net",
+"angelfire.com",
+"angelic.com",
+"animail.net",
+"animalhouse.com",
+"anjungcafe.com",
+"annsmail.com",
+"ano-mail.net",
+"anonymous.to",
+"anote.com",
+"another.com",
+"anotherdomaincyka.tk",
+"anotherwin95.com",
+"anti-social.com",
+"antisocial.com",
+"antispam24.de",
+"anymoment.com",
+"anytimenow.com",
+"aol.co.uk",
+"aol.com",
+"aol.fr",
+"aon.at",
+"apexmail.com",
+"apmail.com",
+"apollo.lv",
+"aport.ru",
+"aport2000.ru",
+"appraiser.net",
+"arabia.com",
+"arabtop.net",
+"archaeologist.com",
+"arcor.de",
+"arcticmail.com",
+"argentina.com",
+"army.net",
+"armyspy.com",
+"arnet.com.ar",
+"artlover.com",
+"artlover.com.au",
+"asdasd.nl",
+"asean-mail.com",
+"asheville.com",
+"asia-links.com",
+"asia-mail.com",
+"asia.com",
+"asiafind.com",
+"asianavenue.com",
+"asiancityweb.com",
+"asiansonly.net",
+"asianwired.net",
+"asiapoint.net",
+"ass.pp.ua",
+"assala.com",
+"assamesemail.com",
+"astrolover.com",
+"astrosfan.net",
+"asurfer.com",
+"atheist.com",
+"athenachu.net",
+"atina.cl",
+"atozasia.com",
+"atrus.ru",
+"att.net",
+"attglobal.net",
+"attymail.com",
+"au.ru",
+"auctioneer.net",
+"ausi.com",
+"aussiemail.com.au",
+"austin.rr.com",
+"australia.edu",
+"australiamail.com",
+"autoescuelanerja.com",
+"autograf.pl",
+"autorambler.ru",
+"aver.com",
+"avh.hu",
+"awsom.net",
+"axoskate.com",
+"azazazatashkent.tk",
+"azmeil.tk",
+"bachelorboy.com",
+"bachelorgal.com",
+"backpackers.com",
+"backstreet-boys.com",
+"bagherpour.com",
+"baldmama.de",
+"baldpapa.de",
+"ballyfinance.com",
+"bangkok.com",
+"bangkok2000.com",
+"bannertown.net",
+"baptistmail.com",
+"baptized.com",
+"barcelona.com",
+"bareed.ws",
+"bartender.net",
+"baseballmail.com",
+"basketballmail.com",
+"batuta.net",
+"bboy.zzn.com",
+"bcvibes.com",
+"beddly.com",
+"beeebank.com",
+"beenhad.com",
+"beep.ru",
+"beer.com",
+"beethoven.com",
+"belice.com",
+"bell.net",
+"bellair.net",
+"bellsouth.net",
+"berlin.com",
+"berlin.de",
+"bestmail.us",
+"betriebsdirektor.de",
+"bettergolf.net",
+"bharatmail.com",
+"bigassweb.com",
+"bigblue.net.au",
+"bigfoot.com",
+"bigfoot.de",
+"bigger.com",
+"biggerbadder.com",
+"bigmailbox.com",
+"bigmir.net",
+"bigpond.com",
+"bigpond.com.au",
+"bigpond.net.au",
+"bigstring.com",
+"bikemechanics.com",
+"bikeracer.com",
+"bikerider.com",
+"billsfan.com",
+"billsfan.net",
+"bin-wieder-da.de",
+"birdlover.com",
+"bisons.com",
+"bitmail.com",
+"bitpage.net",
+"bizhosting.com",
+"bk.ru",
+"blackplanet.com",
+"blader.com",
+"bladesmail.net",
+"blazemail.com",
+"bleib-bei-mir.de",
+"blockfilter.com",
+"blogmyway.org",
+"bluebottle.com",
+"bluemail.ch",
+"bluemail.dk",
+"blushmail.com",
+"boardermail.com",
+"bodhi.lawlita.com",
+"bol.com.br",
+"bolando.com",
+"bolt.com",
+"boltonfans.com",
+"bombdiggity.com",
+"bootybay.de",
+"boun.cr",
+"bounce.net",
+"bouncr.com",
+"box.az",
+"box.ua",
+"boxemail.com",
+"boxformail.in",
+"boxfrog.com",
+"boximail.com",
+"bradfordfans.com",
+"brasilia.net",
+"brazilmail.com",
+"breathe.com",
+"brennendesreich.de",
+"bresnan.net",
+"brew-master.com",
+"brew-meister.com",
+"briefemail.com",
+"bright.net",
+"britneyclub.com",
+"broadcast.net",
+"brokenvalve.com",
+"brusseler.com",
+"bsdmail.com",
+"btcmail.pw",
+"btinternet.com",
+"buerotiger.de",
+"bullsfan.com",
+"bumpymail.com",
+"bund.us",
+"burnthespam.info",
+"burstmail.info",
+"businessman.net",
+"buyersusa.com",
+"bvimailbox.com",
+"byom.de",
+"c2.hu",
+"c3.hu",
+"c4.com",
+"cabacabana.com",
+"cableone.net",
+"caere.it",
+"calidifontain.be",
+"californiamail.com",
+"callnetuk.com",
+"callsign.net",
+"caltanet.it",
+"camidge.com",
+"canada-11.com",
+"canadianmail.com",
+"canoemail.com",
+"caramail.com",
+"caramail.fr",
+"care2.com",
+"careerbuildermail.com",
+"carioca.net",
+"cartestraina.ro",
+"casablancaresort.com",
+"casema.nl",
+"cash4u.com",
+"casino.com",
+"catchamail.com",
+"catholic.org",
+"catlover.com",
+"cd2.com",
+"cegetel.net",
+"celineclub.com",
+"celtic.com",
+"center-mail.de",
+"centermail.at",
+"centermail.de",
+"centermail.info",
+"centoper.it",
+"centralpets.com",
+"centrum.cz",
+"centrum.sk",
+"centurytel.net",
+"certifiedmail.com",
+"cfl.rr.com",
+"cgac.es",
+"chacuo.net",
+"chaiyomail.com",
+"chammy.info",
+"chandrasekar.net",
+"charmedmail.com",
+"charter.com",
+"charter.net",
+"chat.ru",
+"chattown.com",
+"cheatmail.de",
+"chechnya.conf.work",
+"check.com",
+"check1check.com",
+"cheerful.com",
+"chef.net",
+"chek.com",
+"chello.nl",
+"chemist.com",
+"cheyenneweb.com",
+"chez.com",
+"china.com",
+"chinamail.com",
+"chirk.com",
+"chocaholic.com.au",
+"chong-mail.net",
+"churchusa.com",
+"cia-agent.com",
+"cia.hu",
+"cicciociccio.com",
+"cincinow.net",
+"citiz.net",
+"citlink.net",
+"city-of-birmingham.com",
+"city-of-cambridge.com",
+"city-of-edinburgh.com",
+"city-of-lincoln.com",
+"city-of-liverpool.com",
+"city-of-manchester.com",
+"city-of-oxford.com",
+"city-of-swansea.com",
+"city-of-westminster.com",
+"city-of-westminster.net",
+"city-of-york.net",
+"cityoflondon.org",
+"ckaazaza.tk",
+"claramail.com",
+"classicalfan.com",
+"classicmail.co.za",
+"clerk.com",
+"cliffhanger.com",
+"clixser.com",
+"close2you.net",
+"clrmail.com",
+"club4x4.net",
+"clubalfa.com",
+"clubbers.net",
+"clubducati.com",
+"clubhonda.net",
+"club-internet.fr",
+"clubinternet.fr",
+"clubmember.org",
+"clubnetnoir.com",
+"clubvdo.net",
+"cluemail.com",
+"cmail.net",
+"cmpmail.com",
+"cnnsimail.com",
+"cntv.cn",
+"codec.ro",
+"coder.hu",
+"coid.biz",
+"coldmail.com",
+"collectiblesuperstore.com",
+"collector.org",
+"collegeclub.com",
+"collegemail.com",
+"colleges.com",
+"columbus.rr.com",
+"columbusrr.com",
+"columnist.com",
+"comcast.net",
+"comic.com",
+"communityconnect.com",
+"comprendemail.com",
+"compuserve.com",
+"computer4u.com",
+"computermail.net",
+"conexcol.com",
+"conk.com",
+"connect4free.net",
+"consultant.com",
+"consumerriot.com",
+"contractor.net",
+"coole-files.de",
+"coolgoose.ca",
+"coolgoose.com",
+"coolkiwi.com",
+"coolmail.com",
+"coolmail.net",
+"coolsite.net",
+"cooperation.net",
+"cooperationtogo.net",
+"copacabana.com",
+"cornells.com",
+"corporatedirtbag.com",
+"cotas.net",
+"counsellor.com",
+"cox.com",
+"cox.net",
+"coxinet.net",
+"cracker.hu",
+"crapmail.org",
+"crazedanddazed.com",
+"crazymailing.com",
+"cristianemail.com",
+"critterpost.com",
+"croeso.com",
+"crosshairs.com",
+"crosswinds.net",
+"crwmail.com",
+"cs.com",
+"csinibaba.hu",
+"cuemail.com",
+"curio-city.com",
+"curryworld.de",
+"cute-girl.com",
+"cutey.com",
+"cyber-africa.net",
+"cyber-innovation.club",
+"cyber-matrix.com",
+"cyber-wizard.com",
+"cyber4all.com",
+"cyberbabies.com",
+"cybercafemaui.com",
+"cyberdude.com",
+"cybergal.com",
+"cybergrrl.com",
+"cybermail.net",
+"cybernet.it",
+"cyberservices.com",
+"cyberspace-asia.com",
+"cybertrains.org",
+"cyclefanz.com",
+"cynetcity.com",
+"dabsol.net",
+"dadacasa.com",
+"dailypioneer.com",
+"dallasmail.com",
+"dangerous-minds.com",
+"dasdasdascyka.tk",
+"dawnsonmail.com",
+"dawsonmail.com",
+"dbzmail.com",
+"deadlymob.org",
+"deagot.com",
+"deal-maker.com",
+"dearriba.com",
+"death-star.com",
+"deliveryman.com",
+"deneg.net",
+"depechemode.com",
+"deseretmail.com",
+"desilota.com",
+"deskpilot.com",
+"destin.com",
+"detik.com",
+"deutschland-net.com",
+"devotedcouples.com",
+"dezigner.ru",
+"dfwatson.com",
+"di-ve.com",
+"die-besten-bilder.de",
+"die-genossen.de",
+"die-optimisten.net",
+"diemailbox.de",
+"digibel.be",
+"digital-filestore.de",
+"diplomats.com",
+"directbox.com",
+"dirtracer.com",
+"discard.email",
+"discard.ga",
+"discard.gq",
+"disciples.com",
+"discofan.com",
+"discovery.com",
+"discoverymail.com",
+"disign-revelation.com",
+"dispomail.eu",
+"disposable.com",
+"dispose.it",
+"dm.w3internet.co.uk",
+"dnsmadeeasy.com",
+"docmail.cz",
+"doctor.com",
+"dodo.com.au",
+"dodsi.com",
+"dog.com",
+"dogit.com",
+"doglover.com",
+"dogsnob.net",
+"doityourself.com",
+"domforfb1.tk",
+"domforfb2.tk",
+"domforfb3.tk",
+"domforfb4.tk",
+"domforfb5.tk",
+"domforfb6.tk",
+"domforfb8.tk",
+"domozmail.com",
+"doneasy.com",
+"dontgotmail.com",
+"dontmesswithtexas.com",
+"doramail.com",
+"dostmail.com",
+"dotcom.fr",
+"dotmsg.com",
+"dott.it",
+"download-privat.de",
+"dplanet.ch",
+"dr.com",
+"dropmail.me",
+"dropzone.com",
+"drotposta.hu",
+"dublin.com",
+"dublin.ie",
+"dumpmail.com",
+"dumpmail.de",
+"dumpyemail.com",
+"dunlopdriver.com",
+"dunloprider.com",
+"duno.com",
+"duskmail.com",
+"dutchmail.com",
+"dwp.net",
+"dygo.com",
+"dyndns.org",
+"e-apollo.lv",
+"e-mail.com.tr",
+"e-mail.dk",
+"e-mail.ru",
+"e-mail.ua",
+"e-mailanywhere.com",
+"e-tapaal.com",
+"earthalliance.com",
+"earthcam.net",
+"earthdome.com",
+"earthling.net",
+"earthlink.net",
+"earthonline.net",
+"eastcoast.co.za",
+"eastmail.com",
+"easy.to",
+"easypost.com",
+"easytrashmail.com",
+"ecardmail.com",
+"echina.com",
+"ecompare.com",
+"edmail.com",
+"edtnmail.com",
+"educacao.te.pt",
+"eelmail.com",
+"ehmail.com",
+"einrot.com",
+"eintagsmail.de",
+"eircom.net",
+"elitemail.org",
+"elvis.com",
+"elvisfan.com",
+"email-fake.gq",
+"email-london.co.uk",
+"email.biz",
+"email.com",
+"email.cz",
+"email.ee",
+"email.it",
+"email.nu",
+"email.org",
+"email.ro",
+"email.ru",
+"email.su",
+"email.ua",
+"email2me.net",
+"emailacc.com",
+"emailaccount.com",
+"emailasso.net",
+"emailchoice.com",
+"emailcorner.net",
+"emailem.com",
+"emailengine.net",
+"emailengine.org",
+"emailgo.de",
+"emailgroups.net",
+"emailinfive.com",
+"emailit.com",
+"emailplanet.com",
+"emailplus.org",
+"emailto.de",
+"emailuser.net",
+"emailx.net",
+"embarqmail.com",
+"emeil.in",
+"emeil.ir",
+"emil.com",
+"eml.cc",
+"eml.pp.ua",
+"enel.net",
+"engineer.com",
+"england.com",
+"england.edu",
+"englandmail.com",
+"epage.ru",
+"ephemail.net",
+"epix.net",
+"eposta.hu",
+"eramail.co.za",
+"eresmas.com",
+"eriga.lv",
+"estranet.it",
+"ethos.st",
+"eudoramail.com",
+"europamel.net",
+"europe.com",
+"europemail.com",
+"euroseek.com",
+"eurosport.com",
+"every1.net",
+"everyday.com.kh",
+"everymail.net",
+"everyone.net",
+"everytg.ml",
+"examnotes.net",
+"excite.co.jp",
+"excite.com",
+"excite.it",
+"execs.com",
+"exemail.com.au",
+"expressasia.com",
+"extenda.net",
+"eyepaste.com",
+"ezcybersearch.com",
+"ezrs.com",
+"f-m.fm",
+"f1fans.net",
+"facebook.com",
+"fahr-zur-hoelle.org",
+"fake-email.pp.ua",
+"fake-mail.cf",
+"falseaddress.com",
+"fan.com",
+"fansonlymail.com",
+"fantasticmail.com",
+"farang.net",
+"faroweb.com",
+"fast-email.com",
+"fast-mail.fr",
+"fast-mail.org",
+"fastchevy.com",
+"fastem.com",
+"fastemail.us",
+"fastemailer.com",
+"fastermail.com",
+"fastest.cc",
+"fastimap.com",
+"fastmail.ca",
+"fastmail.cn",
+"fastmail.co.uk",
+"fastmail.com",
+"fastmail.com.au",
+"fastmail.es",
+"fastmail.fm",
+"fastmail.im",
+"fastmail.in",
+"fastmail.jp",
+"fastmail.mx",
+"fastmail.net",
+"fastmail.nl",
+"fastmail.se",
+"fastmail.to",
+"fastmail.tw",
+"fastmail.us",
+"fastmailbox.net",
+"fastmazda.com",
+"fastmessaging.com",
+"fastservice.com",
+"fastsubaru.com",
+"fatflap.com",
+"fathersrightsne.org",
+"fax.ru",
+"fbi.hu",
+"fea.st",
+"federalcontractors.com",
+"feinripptraeger.de",
+"felicitymail.com",
+"femenino.com",
+"fetchmail.co.uk",
+"fettabernett.de",
+"feyenoorder.com",
+"ffanet.com",
+"fiberia.com",
+"ficken.de",
+"fightallspam.com",
+"filipinolinks.com",
+"financemail.net",
+"financier.com",
+"findmail.com",
+"fire-brigade.com",
+"fireman.net",
+"fishburne.org",
+"fishfuse.com",
+"fixmail.tk",
+"fizmail.com",
+"flashbox.5july.org",
+"flashmail.com",
+"flashmail.net",
+"fleckens.hu",
+"flipcode.com",
+"fmail.co.uk",
+"fmailbox.com",
+"fmgirl.com",
+"fmguy.com",
+"fnbmail.co.za",
+"fnmail.com",
+"folkfan.com",
+"foodmail.com",
+"footard.com",
+"football.ua",
+"footballmail.com",
+"for-president.com",
+"force9.co.uk",
+"forgetmail.com",
+"forpresident.com",
+"fortuncity.com",
+"fortunecity.com",
+"forum.dk",
+"foxmail.com",
+"fr33mail.info",
+"francemel.fr",
+"free-online.net",
+"free-org.com",
+"free.com.pe",
+"free.fr",
+"freeaccess.nl",
+"freeaccount.com",
+"freeandsingle.com",
+"freedom.usa.com",
+"freedomlover.com",
+"freegates.be",
+"freelance-france.eu",
+"freeler.nl",
+"freemail.c3.hu",
+"freemail.com.pk",
+"freemail.de",
+"freemail.et",
+"freemail.gr",
+"freemail.hu",
+"freemail.it",
+"freemail.lt",
+"freemail.org.mk",
+"freemails.ga",
+"freemeil.gq",
+"freenet.de",
+"freenet.kg",
+"freeola.com",
+"freeola.net",
+"freestart.hu",
+"freesurf.fr",
+"freesurf.nl",
+"freeuk.com",
+"freeuk.net",
+"freeukisp.co.uk",
+"freeweb.org",
+"freewebemail.com",
+"freeyellow.com",
+"freezone.co.uk",
+"fresnomail.com",
+"freudenkinder.de",
+"freundin.ru",
+"friendlymail.co.uk",
+"friendsfan.com",
+"from-africa.com",
+"from-australia.com",
+"from-europe.com",
+"from-holland.com",
+"from-japan.net",
+"from-mexico.com",
+"from-outerspace.com",
+"from-russia.com",
+"fromalaska.com",
+"fromarizona.com",
+"fromarkansas.com",
+"fromcalifornia.com",
+"fromconnecticut.com",
+"fromgeorgia.com",
+"fromhawaii.net",
+"fromidaho.com",
+"fromindiana.com",
+"fromiowa.com",
+"fromkansas.com",
+"fromlouisiana.com",
+"frommaryland.com",
+"frommassachusetts.com",
+"frommiami.com",
+"frommichigan.com",
+"fromminnesota.com",
+"frommississippi.com",
+"frommissouri.com",
+"fromnevada.com",
+"fromnewhampshire.com",
+"fromnewjersey.com",
+"fromnewmexico.com",
+"fromnorthcarolina.com",
+"fromnorthdakota.com",
+"fromohio.com",
+"fromoklahoma.com",
+"frompennsylvania.com",
+"fromrhodeisland.com",
+"fromru.com",
+"fromsouthcarolina.com",
+"fromtennessee.com",
+"fromtexas.com",
+"fromutah.com",
+"fromvermont.com",
+"fromvirginia.com",
+"fromwashington.com",
+"fromwashingtondc.com",
+"fromwestvirginia.com",
+"fromwisconsin.com",
+"fromwyoming.com",
+"front.ru",
+"frontier.com",
+"frontiernet.net",
+"fsmail.net",
+"ftml.net",
+"fullmail.com",
+"fuorissimo.com",
+"furnitureprovider.com",
+"fuse.net",
+"fut.es",
+"galaxyhit.com",
+"gamebox.net",
+"gamegeek.com",
+"gamespotmail.com",
+"garbage.com",
+"gardener.com",
+"gaybrighton.co.uk",
+"gaza.net",
+"gazeta.pl",
+"gazibooks.com",
+"gci.net",
+"geek.com",
+"geeklife.com",
+"gelitik.in",
+"gencmail.com",
+"gentlemansclub.de",
+"geocities.com",
+"geography.net",
+"geologist.com",
+"geopia.com",
+"germanymail.com",
+"get.pp.ua",
+"get1mail.com",
+"getairmail.com",
+"getairmail.gq",
+"getonemail.net",
+"ghanamail.com",
+"ghostmail.com",
+"ghosttexter.de",
+"gigileung.org",
+"girl4god.com",
+"glay.org",
+"glendale.net",
+"globalfree.it",
+"globalpagan.com",
+"gmail.com",
+"gmail.com.br",
+"gmx.at",
+"gmx.biz",
+"gmx.ch",
+"gmx.co.uk",
+"gmx.com",
+"gmx.de",
+"gmx.eu",
+"gmx.fr",
+"gmx.info",
+"gmx.li",
+"gmx.net",
+"gmx.org",
+"gmx.us",
+"go.com",
+"go.ro",
+"go.ru",
+"gocollege.com",
+"gocubs.com",
+"goldmail.ru",
+"goldtoolbox.com",
+"golfemail.com",
+"golfilla.info",
+"golfmail.be",
+"gonavy.net",
+"goodnewsmail.com",
+"goodstick.com",
+"googlemail.com",
+"goplay.com",
+"gorillaswithdirtyarmpits.com",
+"gospelfan.com",
+"gotmail.com",
+"gotmail.org",
+"gotomy.com",
+"gotti.otherinbox.com",
+"gportal.hu",
+"graduate.org",
+"graffiti.net",
+"gramszu.net",
+"grandmamail.com",
+"graphic-designer.com",
+"grapplers.com",
+"greenmail.net",
+"groupmail.com",
+"grr.la",
+"gtmc.net",
+"gua.net",
+"guessmail.com",
+"guju.net",
+"gustr.com",
+"guy.com",
+"guy2.com",
+"guyanafriends.com",
+"h-mail.us",
+"hab-verschlafen.de",
+"habmalnefrage.de",
+"hacccc.com",
+"hackermail.com",
+"hackermail.net",
+"hailmail.net",
+"hairdresser.net",
+"hamptonroads.com",
+"handbag.com",
+"handleit.com",
+"hanmail.net",
+"happemail.com",
+"happycounsel.com",
+"happypuppy.com",
+"harakirimail.com",
+"hardcorefreak.com",
+"hartbot.de",
+"hawaii.rr.com",
+"hawaiiantel.net",
+"heerschap.com",
+"heesun.net",
+"hello.hu",
+"hello.net.au",
+"hello.to",
+"helter-skelter.com",
+"herediano.com",
+"herp.in",
+"herr-der-mails.de",
+"hetnet.nl",
+"hey.to",
+"hidzz.com",
+"highquality.com",
+"highveldmail.co.za",
+"hilarious.com",
+"hiphopfan.com",
+"hispavista.com",
+"hitmail.com",
+"hitthe.net",
+"hkg.net",
+"hockeymail.com",
+"hollywoodkids.com",
+"home-email.com",
+"home.de",
+"home.nl",
+"home.ro",
+"home.se",
+"homemail.com",
+"homestead.com",
+"honduras.com",
+"hongkong.com",
+"hoopsmail.com",
+"hopemail.biz",
+"hot-shot.com",
+"hot.ee",
+"hotbrev.com",
+"hotletter.com",
+"hotmail.ca",
+"hotmail.ch",
+"hotmail.co.il",
+"hotmail.co.uk",
+"hotmail.com",
+"hotmail.de",
+"hotmail.es",
+"hotmail.fr",
+"hotmail.it",
+"hotmail.kz",
+"hotmail.nl",
+"hotmail.ru",
+"hotpop3.com",
+"hotvoice.com",
+"housemail.com",
+"hsuchi.net",
+"hu2.ru",
+"hughes.net",
+"humanoid.net",
+"hunsa.com",
+"hurting.com",
+"hush.com",
+"hushmail.com",
+"hypernautica.com",
+"i-connect.com",
+"i-mail.com.au",
+"i-p.com",
+"i.am",
+"i.ua",
+"i2pmail.org",
+"iamawoman.com",
+"icestorm.com",
+"ich-bin-verrueckt-nach-dir.de",
+"ich-will-net.de",
+"icloud.com",
+"icmsconsultants.com",
+"icq.com",
+"icqmail.com",
+"icrazy.com",
+"idirect.com",
+"ieh-mail.de",
+"iespana.es",
+"ig.com.br",
+"ihateclowns.com",
+"iinet.net.au",
+"ijustdontcare.com",
+"ikbenspamvrij.nl",
+"ilkposta.com",
+"ilovechocolate.com",
+"ilovejesus.com",
+"ilse.nl",
+"imaginemail.com",
+"imail.org",
+"imail.ru",
+"imap-mail.com",
+"imap.cc",
+"imapmail.org",
+"imel.org",
+"imgof.com",
+"imgv.de",
+"immo-gerance.info",
+"imposter.co.uk",
+"imstations.com",
+"imstressed.com",
+"in-box.net",
+"iname.com",
+"inbax.tk",
+"inbox.com",
+"inbox.net",
+"inbox.ru",
+"inbox.si",
+"inboxalias.com",
+"incamail.com",
+"incredimail.com",
+"index.ua",
+"indexa.fr",
+"india.com",
+"indiatimes.com",
+"indo-mail.com",
+"indomail.com",
+"indyracers.com",
+"inerted.com",
+"info-media.de",
+"info-radio.ml",
+"info66.com",
+"infohq.com",
+"infomail.es",
+"infomart.or.jp",
+"infospacemail.com",
+"infovia.com.ar",
+"inicia.es",
+"inmail.sk",
+"inmail24.com",
+"inmano.com",
+"inmynetwork.tk",
+"innocent.com",
+"inorbit.com",
+"inoutbox.com",
+"insidebaltimore.net",
+"insight.rr.com",
+"instantemailaddress.com",
+"instantmail.fr",
+"instruction.com",
+"instructor.net",
+"insurer.com",
+"interburp.com",
+"interfree.it",
+"interia.pl",
+"interlap.com.ar",
+"intermail.co.il",
+"internet-e-mail.com",
+"internet-mail.org",
+"internet-police.com",
+"internetegypt.com",
+"internetemails.net",
+"internetmailing.net",
+"internode.on.net",
+"inwind.it",
+"iobox.com",
+"iobox.fi",
+"iol.it",
+"iowaemail.com",
+"ip4.pp.ua",
+"ip6.pp.ua",
+"ipoo.org",
+"iprimus.com.au",
+"iqemail.com",
+"irangate.net",
+"ireland.com",
+"irelandmail.com",
+"irj.hu",
+"iroid.com",
+"isellcars.com",
+"iservejesus.com",
+"islamonline.net",
+"isleuthmail.com",
+"ismart.net",
+"isp9.net",
+"israelmail.com",
+"ist-allein.info",
+"ist-einmalig.de",
+"ist-ganz-allein.de",
+"ist-willig.de",
+"italymail.com",
+"itmom.com",
+"ivebeenframed.com",
+"iwmail.com",
+"iwon.com",
+"izadpanah.com",
+"jahoopa.com",
+"jakuza.hu",
+"jazzandjava.com",
+"jazzfan.com",
+"je-recycle.info",
+"jerusalemmail.com",
+"jetable.de",
+"jetable.pp.ua",
+"jetemail.net",
+"jippii.fi",
+"jmail.co.za",
+"job4u.com",
+"jokes.com",
+"journalist.com",
+"jourrapide.com",
+"jovem.te.pt",
+"jpopmail.com",
+"jsrsolutions.com",
+"jubiimail.dk",
+"juniormail.com",
+"junkmail.com",
+"juno.com",
+"justemail.net",
+"justicemail.com",
+"kaazoo.com",
+"kaffeeschluerfer.com",
+"kaffeeschluerfer.de",
+"kaixo.com",
+"kalpoint.com",
+"kansascity.com",
+"karbasi.com",
+"katamail.com",
+"kayafmmail.co.za",
+"kbjrmail.com",
+"kcks.com",
+"keg-party.com",
+"keinpardon.de",
+"keko.com.ar",
+"kellychen.com",
+"keromail.com",
+"keyemail.com",
+"kgb.hu",
+"kickassmail.com",
+"killermail.com",
+"kimo.com",
+"kinglibrary.net",
+"kinki-kids.com",
+"kissfans.com",
+"kittymail.com",
+"kitznet.at",
+"kiwitown.com",
+"km.ru",
+"knol-power.nl",
+"kommespaeter.de",
+"konx.com",
+"korea.com",
+"koreamail.com",
+"kpnmail.nl",
+"krongthip.com",
+"krunis.com",
+"ksanmail.com",
+"ksee24mail.com",
+"kukamail.com",
+"kulturbetrieb.info",
+"kumarweb.com",
+"la.com",
+"ladymail.cz",
+"lagerlouts.com",
+"lags.us",
+"lakmail.com",
+"lamer.hu",
+"land.ru",
+"lankamail.com",
+"laoeq.com",
+"laposte.net",
+"lass-es-geschehen.de",
+"lastmail.co",
+"latemodels.com",
+"lavache.com",
+"law.com",
+"lawyer.com",
+"lazyinbox.com",
+"leehom.net",
+"legalrc.loan",
+"legislator.com",
+"lenta.ru",
+"leonlai.net",
+"letsgomets.net",
+"letterboxes.org",
+"letthemeatspam.com",
+"levele.hu",
+"lex.bg",
+"lexis-nexis-mail.com",
+"libero.it",
+"liberomail.com",
+"lick101.com",
+"liebt-dich.info",
+"linktrader.com",
+"linuxfreemail.com",
+"linuxmail.org",
+"liontrucks.com",
+"liquidinformation.net",
+"list.ru",
+"listomail.com",
+"littleapple.com",
+"littleblueroom.com",
+"live.at",
+"live.ca",
+"live.cl",
+"live.cn",
+"live.co.uk",
+"live.co.za",
+"live.com",
+"live.com.ar",
+"live.com.au",
+"live.com.mx",
+"live.com.pt",
+"live.com.sg",
+"live.de",
+"live.dk",
+"live.fr",
+"live.ie",
+"live.in",
+"live.it",
+"live.jp",
+"live.nl",
+"live.ru",
+"live.se",
+"liveradio.tk",
+"liverpoolfans.com",
+"llandudno.com",
+"llangollen.com",
+"lobbyist.com",
+"localbar.com",
+"locos.com",
+"loh.pp.ua",
+"lolfreak.net",
+"london.com",
+"looksmart.co.uk",
+"looksmart.com",
+"lopezclub.com",
+"louiskoo.com",
+"loveable.com",
+"lovecat.com",
+"lovefall.ml",
+"lovefootball.com",
+"lovemail.com",
+"lover-boy.com",
+"lovesea.gq",
+"lovethebroncos.com",
+"loveyouforever.de",
+"lovingjesus.com",
+"lowandslow.com",
+"lroid.com",
+"luukku.com",
+"lycos.co.uk",
+"lycos.com",
+"lycos.es",
+"lycos.ne.jp",
+"m-hmail.com",
+"m4.org",
+"mac.com",
+"macbox.com",
+"macfreak.com",
+"macmail.com",
+"madonnafan.com",
+"maennerversteherin.com",
+"maennerversteherin.de",
+"maffia.hu",
+"magicmail.co.za",
+"mail-awu.de",
+"mail-box.cz",
+"mail-center.com",
+"mail-central.com",
+"mail-easy.fr",
+"mail-filter.com",
+"mail-me.com",
+"mail-page.com",
+"mail-tester.com",
+"mail.az",
+"mail.be",
+"mail.bulgaria.com",
+"mail.by",
+"mail.co.za",
+"mail.com",
+"mail.com.tr",
+"mail.ee",
+"mail.gr",
+"mail.hitthebeach.com",
+"mail.htl22.at",
+"mail.md",
+"mail.misterpinball.de",
+"mail.nu",
+"mail.org.uk",
+"mail.pf",
+"mail.pt",
+"mail.ru",
+"mail.sisna.com",
+"mail.svenz.eu",
+"mail.usa.com",
+"mail.wtf",
+"mail114.net",
+"mail15.com",
+"mail2007.com",
+"mail2aaron.com",
+"mail2abby.com",
+"mail2abc.com",
+"mail2actor.com",
+"mail2admiral.com",
+"mail2adorable.com",
+"mail2adoration.com",
+"mail2adore.com",
+"mail2adventure.com",
+"mail2aeolus.com",
+"mail2aether.com",
+"mail2affection.com",
+"mail2afghanistan.com",
+"mail2africa.com",
+"mail2agent.com",
+"mail2aha.com",
+"mail2ahoy.com",
+"mail2aim.com",
+"mail2air.com",
+"mail2airbag.com",
+"mail2airforce.com",
+"mail2airport.com",
+"mail2alabama.com",
+"mail2alan.com",
+"mail2alaska.com",
+"mail2albania.com",
+"mail2alcoholic.com",
+"mail2alec.com",
+"mail2alexa.com",
+"mail2algeria.com",
+"mail2alicia.com",
+"mail2alien.com",
+"mail2allan.com",
+"mail2allen.com",
+"mail2allison.com",
+"mail2alpha.com",
+"mail2alyssa.com",
+"mail2amanda.com",
+"mail2amazing.com",
+"mail2amber.com",
+"mail2america.com",
+"mail2american.com",
+"mail2andorra.com",
+"mail2andrea.com",
+"mail2andy.com",
+"mail2anesthesiologist.com",
+"mail2angela.com",
+"mail2angola.com",
+"mail2ann.com",
+"mail2anna.com",
+"mail2anne.com",
+"mail2anthony.com",
+"mail2aphrodite.com",
+"mail2apollo.com",
+"mail2april.com",
+"mail2aquarius.com",
+"mail2arabia.com",
+"mail2arabic.com",
+"mail2architect.com",
+"mail2ares.com",
+"mail2argentina.com",
+"mail2aries.com",
+"mail2arizona.com",
+"mail2arkansas.com",
+"mail2armenia.com",
+"mail2army.com",
+"mail2arnold.com",
+"mail2art.com",
+"mail2arthur.com",
+"mail2artist.com",
+"mail2ashley.com",
+"mail2ask.com",
+"mail2astronomer.com",
+"mail2athena.com",
+"mail2athlete.com",
+"mail2atlas.com",
+"mail2atom.com",
+"mail2attitude.com",
+"mail2auction.com",
+"mail2aunt.com",
+"mail2australia.com",
+"mail2austria.com",
+"mail2azerbaijan.com",
+"mail2baby.com",
+"mail2bahamas.com",
+"mail2bahrain.com",
+"mail2ballerina.com",
+"mail2ballplayer.com",
+"mail2band.com",
+"mail2bangladesh.com",
+"mail2bank.com",
+"mail2banker.com",
+"mail2bankrupt.com",
+"mail2baptist.com",
+"mail2bar.com",
+"mail2barbados.com",
+"mail2barbara.com",
+"mail2barter.com",
+"mail2basketball.com",
+"mail2batter.com",
+"mail2beach.com",
+"mail2beast.com",
+"mail2beatles.com",
+"mail2beauty.com",
+"mail2becky.com",
+"mail2beijing.com",
+"mail2belgium.com",
+"mail2belize.com",
+"mail2ben.com",
+"mail2bernard.com",
+"mail2beth.com",
+"mail2betty.com",
+"mail2beverly.com",
+"mail2beyond.com",
+"mail2biker.com",
+"mail2bill.com",
+"mail2billionaire.com",
+"mail2billy.com",
+"mail2bio.com",
+"mail2biologist.com",
+"mail2black.com",
+"mail2blackbelt.com",
+"mail2blake.com",
+"mail2blind.com",
+"mail2blonde.com",
+"mail2blues.com",
+"mail2bob.com",
+"mail2bobby.com",
+"mail2bolivia.com",
+"mail2bombay.com",
+"mail2bonn.com",
+"mail2bookmark.com",
+"mail2boreas.com",
+"mail2bosnia.com",
+"mail2boston.com",
+"mail2botswana.com",
+"mail2bradley.com",
+"mail2brazil.com",
+"mail2breakfast.com",
+"mail2brian.com",
+"mail2bride.com",
+"mail2brittany.com",
+"mail2broker.com",
+"mail2brook.com",
+"mail2bruce.com",
+"mail2brunei.com",
+"mail2brunette.com",
+"mail2brussels.com",
+"mail2bryan.com",
+"mail2bug.com",
+"mail2bulgaria.com",
+"mail2business.com",
+"mail2buy.com",
+"mail2ca.com",
+"mail2california.com",
+"mail2calvin.com",
+"mail2cambodia.com",
+"mail2cameroon.com",
+"mail2canada.com",
+"mail2cancer.com",
+"mail2capeverde.com",
+"mail2capricorn.com",
+"mail2cardinal.com",
+"mail2cardiologist.com",
+"mail2care.com",
+"mail2caroline.com",
+"mail2carolyn.com",
+"mail2casey.com",
+"mail2cat.com",
+"mail2caterer.com",
+"mail2cathy.com",
+"mail2catlover.com",
+"mail2catwalk.com",
+"mail2cell.com",
+"mail2chad.com",
+"mail2champaign.com",
+"mail2charles.com",
+"mail2chef.com",
+"mail2chemist.com",
+"mail2cherry.com",
+"mail2chicago.com",
+"mail2chile.com",
+"mail2china.com",
+"mail2chinese.com",
+"mail2chocolate.com",
+"mail2christian.com",
+"mail2christie.com",
+"mail2christmas.com",
+"mail2christy.com",
+"mail2chuck.com",
+"mail2cindy.com",
+"mail2clark.com",
+"mail2classifieds.com",
+"mail2claude.com",
+"mail2cliff.com",
+"mail2clinic.com",
+"mail2clint.com",
+"mail2close.com",
+"mail2club.com",
+"mail2coach.com",
+"mail2coastguard.com",
+"mail2colin.com",
+"mail2college.com",
+"mail2color.com",
+"mail2colorado.com",
+"mail2columbia.com",
+"mail2comedian.com",
+"mail2composer.com",
+"mail2computer.com",
+"mail2computers.com",
+"mail2concert.com",
+"mail2congo.com",
+"mail2connect.com",
+"mail2connecticut.com",
+"mail2consultant.com",
+"mail2convict.com",
+"mail2cook.com",
+"mail2cool.com",
+"mail2cory.com",
+"mail2costarica.com",
+"mail2country.com",
+"mail2courtney.com",
+"mail2cowboy.com",
+"mail2cowgirl.com",
+"mail2craig.com",
+"mail2crave.com",
+"mail2crazy.com",
+"mail2create.com",
+"mail2croatia.com",
+"mail2cry.com",
+"mail2crystal.com",
+"mail2cuba.com",
+"mail2culture.com",
+"mail2curt.com",
+"mail2customs.com",
+"mail2cute.com",
+"mail2cutey.com",
+"mail2cynthia.com",
+"mail2cyprus.com",
+"mail2czechrepublic.com",
+"mail2dad.com",
+"mail2dale.com",
+"mail2dallas.com",
+"mail2dan.com",
+"mail2dana.com",
+"mail2dance.com",
+"mail2dancer.com",
+"mail2danielle.com",
+"mail2danny.com",
+"mail2darlene.com",
+"mail2darling.com",
+"mail2darren.com",
+"mail2daughter.com",
+"mail2dave.com",
+"mail2dawn.com",
+"mail2dc.com",
+"mail2dealer.com",
+"mail2deanna.com",
+"mail2dearest.com",
+"mail2debbie.com",
+"mail2debby.com",
+"mail2deer.com",
+"mail2delaware.com",
+"mail2delicious.com",
+"mail2demeter.com",
+"mail2democrat.com",
+"mail2denise.com",
+"mail2denmark.com",
+"mail2dennis.com",
+"mail2dentist.com",
+"mail2derek.com",
+"mail2desert.com",
+"mail2devoted.com",
+"mail2devotion.com",
+"mail2diamond.com",
+"mail2diana.com",
+"mail2diane.com",
+"mail2diehard.com",
+"mail2dilemma.com",
+"mail2dillon.com",
+"mail2dinner.com",
+"mail2dinosaur.com",
+"mail2dionysos.com",
+"mail2diplomat.com",
+"mail2director.com",
+"mail2dirk.com",
+"mail2disco.com",
+"mail2dive.com",
+"mail2diver.com",
+"mail2divorced.com",
+"mail2djibouti.com",
+"mail2doctor.com",
+"mail2doglover.com",
+"mail2dominic.com",
+"mail2dominica.com",
+"mail2dominicanrepublic.com",
+"mail2don.com",
+"mail2donald.com",
+"mail2donna.com",
+"mail2doris.com",
+"mail2dorothy.com",
+"mail2doug.com",
+"mail2dough.com",
+"mail2douglas.com",
+"mail2dow.com",
+"mail2downtown.com",
+"mail2dream.com",
+"mail2dreamer.com",
+"mail2dude.com",
+"mail2dustin.com",
+"mail2dyke.com",
+"mail2dylan.com",
+"mail2earl.com",
+"mail2earth.com",
+"mail2eastend.com",
+"mail2eat.com",
+"mail2economist.com",
+"mail2ecuador.com",
+"mail2eddie.com",
+"mail2edgar.com",
+"mail2edwin.com",
+"mail2egypt.com",
+"mail2electron.com",
+"mail2eli.com",
+"mail2elizabeth.com",
+"mail2ellen.com",
+"mail2elliot.com",
+"mail2elsalvador.com",
+"mail2elvis.com",
+"mail2emergency.com",
+"mail2emily.com",
+"mail2engineer.com",
+"mail2english.com",
+"mail2environmentalist.com",
+"mail2eos.com",
+"mail2eric.com",
+"mail2erica.com",
+"mail2erin.com",
+"mail2erinyes.com",
+"mail2eris.com",
+"mail2eritrea.com",
+"mail2ernie.com",
+"mail2eros.com",
+"mail2estonia.com",
+"mail2ethan.com",
+"mail2ethiopia.com",
+"mail2eu.com",
+"mail2europe.com",
+"mail2eurus.com",
+"mail2eva.com",
+"mail2evan.com",
+"mail2evelyn.com",
+"mail2everything.com",
+"mail2exciting.com",
+"mail2expert.com",
+"mail2fairy.com",
+"mail2faith.com",
+"mail2fanatic.com",
+"mail2fancy.com",
+"mail2fantasy.com",
+"mail2farm.com",
+"mail2farmer.com",
+"mail2fashion.com",
+"mail2fat.com",
+"mail2feeling.com",
+"mail2female.com",
+"mail2fever.com",
+"mail2fighter.com",
+"mail2fiji.com",
+"mail2filmfestival.com",
+"mail2films.com",
+"mail2finance.com",
+"mail2finland.com",
+"mail2fireman.com",
+"mail2firm.com",
+"mail2fisherman.com",
+"mail2flexible.com",
+"mail2florence.com",
+"mail2florida.com",
+"mail2floyd.com",
+"mail2fly.com",
+"mail2fond.com",
+"mail2fondness.com",
+"mail2football.com",
+"mail2footballfan.com",
+"mail2found.com",
+"mail2france.com",
+"mail2frank.com",
+"mail2frankfurt.com",
+"mail2franklin.com",
+"mail2fred.com",
+"mail2freddie.com",
+"mail2free.com",
+"mail2freedom.com",
+"mail2french.com",
+"mail2freudian.com",
+"mail2friendship.com",
+"mail2from.com",
+"mail2fun.com",
+"mail2gabon.com",
+"mail2gabriel.com",
+"mail2gail.com",
+"mail2galaxy.com",
+"mail2gambia.com",
+"mail2games.com",
+"mail2gary.com",
+"mail2gavin.com",
+"mail2gemini.com",
+"mail2gene.com",
+"mail2genes.com",
+"mail2geneva.com",
+"mail2george.com",
+"mail2georgia.com",
+"mail2gerald.com",
+"mail2german.com",
+"mail2germany.com",
+"mail2ghana.com",
+"mail2gilbert.com",
+"mail2gina.com",
+"mail2girl.com",
+"mail2glen.com",
+"mail2gloria.com",
+"mail2goddess.com",
+"mail2gold.com",
+"mail2golfclub.com",
+"mail2golfer.com",
+"mail2gordon.com",
+"mail2government.com",
+"mail2grab.com",
+"mail2grace.com",
+"mail2graham.com",
+"mail2grandma.com",
+"mail2grandpa.com",
+"mail2grant.com",
+"mail2greece.com",
+"mail2green.com",
+"mail2greg.com",
+"mail2grenada.com",
+"mail2gsm.com",
+"mail2guard.com",
+"mail2guatemala.com",
+"mail2guy.com",
+"mail2hades.com",
+"mail2haiti.com",
+"mail2hal.com",
+"mail2handhelds.com",
+"mail2hank.com",
+"mail2hannah.com",
+"mail2harold.com",
+"mail2harry.com",
+"mail2hawaii.com",
+"mail2headhunter.com",
+"mail2heal.com",
+"mail2heather.com",
+"mail2heaven.com",
+"mail2hebe.com",
+"mail2hecate.com",
+"mail2heidi.com",
+"mail2helen.com",
+"mail2hell.com",
+"mail2help.com",
+"mail2helpdesk.com",
+"mail2henry.com",
+"mail2hephaestus.com",
+"mail2hera.com",
+"mail2hercules.com",
+"mail2herman.com",
+"mail2hermes.com",
+"mail2hespera.com",
+"mail2hestia.com",
+"mail2highschool.com",
+"mail2hindu.com",
+"mail2hip.com",
+"mail2hiphop.com",
+"mail2holland.com",
+"mail2holly.com",
+"mail2hollywood.com",
+"mail2homer.com",
+"mail2honduras.com",
+"mail2honey.com",
+"mail2hongkong.com",
+"mail2hope.com",
+"mail2horse.com",
+"mail2hot.com",
+"mail2hotel.com",
+"mail2houston.com",
+"mail2howard.com",
+"mail2hugh.com",
+"mail2human.com",
+"mail2hungary.com",
+"mail2hungry.com",
+"mail2hygeia.com",
+"mail2hyperspace.com",
+"mail2hypnos.com",
+"mail2ian.com",
+"mail2ice-cream.com",
+"mail2iceland.com",
+"mail2idaho.com",
+"mail2idontknow.com",
+"mail2illinois.com",
+"mail2imam.com",
+"mail2in.com",
+"mail2india.com",
+"mail2indian.com",
+"mail2indiana.com",
+"mail2indonesia.com",
+"mail2infinity.com",
+"mail2intense.com",
+"mail2iowa.com",
+"mail2iran.com",
+"mail2iraq.com",
+"mail2ireland.com",
+"mail2irene.com",
+"mail2iris.com",
+"mail2irresistible.com",
+"mail2irving.com",
+"mail2irwin.com",
+"mail2isaac.com",
+"mail2israel.com",
+"mail2italian.com",
+"mail2italy.com",
+"mail2jackie.com",
+"mail2jacob.com",
+"mail2jail.com",
+"mail2jaime.com",
+"mail2jake.com",
+"mail2jamaica.com",
+"mail2james.com",
+"mail2jamie.com",
+"mail2jan.com",
+"mail2jane.com",
+"mail2janet.com",
+"mail2janice.com",
+"mail2japan.com",
+"mail2japanese.com",
+"mail2jasmine.com",
+"mail2jason.com",
+"mail2java.com",
+"mail2jay.com",
+"mail2jazz.com",
+"mail2jed.com",
+"mail2jeffrey.com",
+"mail2jennifer.com",
+"mail2jenny.com",
+"mail2jeremy.com",
+"mail2jerry.com",
+"mail2jessica.com",
+"mail2jessie.com",
+"mail2jesus.com",
+"mail2jew.com",
+"mail2jeweler.com",
+"mail2jim.com",
+"mail2jimmy.com",
+"mail2joan.com",
+"mail2joann.com",
+"mail2joanna.com",
+"mail2jody.com",
+"mail2joe.com",
+"mail2joel.com",
+"mail2joey.com",
+"mail2john.com",
+"mail2join.com",
+"mail2jon.com",
+"mail2jonathan.com",
+"mail2jones.com",
+"mail2jordan.com",
+"mail2joseph.com",
+"mail2josh.com",
+"mail2joy.com",
+"mail2juan.com",
+"mail2judge.com",
+"mail2judy.com",
+"mail2juggler.com",
+"mail2julian.com",
+"mail2julie.com",
+"mail2jumbo.com",
+"mail2junk.com",
+"mail2justin.com",
+"mail2justme.com",
+"mail2k.ru",
+"mail2kansas.com",
+"mail2karate.com",
+"mail2karen.com",
+"mail2karl.com",
+"mail2karma.com",
+"mail2kathleen.com",
+"mail2kathy.com",
+"mail2katie.com",
+"mail2kay.com",
+"mail2kazakhstan.com",
+"mail2keen.com",
+"mail2keith.com",
+"mail2kelly.com",
+"mail2kelsey.com",
+"mail2ken.com",
+"mail2kendall.com",
+"mail2kennedy.com",
+"mail2kenneth.com",
+"mail2kenny.com",
+"mail2kentucky.com",
+"mail2kenya.com",
+"mail2kerry.com",
+"mail2kevin.com",
+"mail2kim.com",
+"mail2kimberly.com",
+"mail2king.com",
+"mail2kirk.com",
+"mail2kiss.com",
+"mail2kosher.com",
+"mail2kristin.com",
+"mail2kurt.com",
+"mail2kuwait.com",
+"mail2kyle.com",
+"mail2kyrgyzstan.com",
+"mail2la.com",
+"mail2lacrosse.com",
+"mail2lance.com",
+"mail2lao.com",
+"mail2larry.com",
+"mail2latvia.com",
+"mail2laugh.com",
+"mail2laura.com",
+"mail2lauren.com",
+"mail2laurie.com",
+"mail2lawrence.com",
+"mail2lawyer.com",
+"mail2lebanon.com",
+"mail2lee.com",
+"mail2leo.com",
+"mail2leon.com",
+"mail2leonard.com",
+"mail2leone.com",
+"mail2leslie.com",
+"mail2letter.com",
+"mail2liberia.com",
+"mail2libertarian.com",
+"mail2libra.com",
+"mail2libya.com",
+"mail2liechtenstein.com",
+"mail2life.com",
+"mail2linda.com",
+"mail2linux.com",
+"mail2lionel.com",
+"mail2lipstick.com",
+"mail2liquid.com",
+"mail2lisa.com",
+"mail2lithuania.com",
+"mail2litigator.com",
+"mail2liz.com",
+"mail2lloyd.com",
+"mail2lois.com",
+"mail2lola.com",
+"mail2london.com",
+"mail2looking.com",
+"mail2lori.com",
+"mail2lost.com",
+"mail2lou.com",
+"mail2louis.com",
+"mail2louisiana.com",
+"mail2lovable.com",
+"mail2love.com",
+"mail2lucky.com",
+"mail2lucy.com",
+"mail2lunch.com",
+"mail2lust.com",
+"mail2luxembourg.com",
+"mail2luxury.com",
+"mail2lyle.com",
+"mail2lynn.com",
+"mail2madagascar.com",
+"mail2madison.com",
+"mail2madrid.com",
+"mail2maggie.com",
+"mail2mail4.com",
+"mail2maine.com",
+"mail2malawi.com",
+"mail2malaysia.com",
+"mail2maldives.com",
+"mail2mali.com",
+"mail2malta.com",
+"mail2mambo.com",
+"mail2man.com",
+"mail2mandy.com",
+"mail2manhunter.com",
+"mail2mankind.com",
+"mail2many.com",
+"mail2marc.com",
+"mail2marcia.com",
+"mail2margaret.com",
+"mail2margie.com",
+"mail2marhaba.com",
+"mail2maria.com",
+"mail2marilyn.com",
+"mail2marines.com",
+"mail2mark.com",
+"mail2marriage.com",
+"mail2married.com",
+"mail2marries.com",
+"mail2mars.com",
+"mail2marsha.com",
+"mail2marshallislands.com",
+"mail2martha.com",
+"mail2martin.com",
+"mail2marty.com",
+"mail2marvin.com",
+"mail2mary.com",
+"mail2maryland.com",
+"mail2mason.com",
+"mail2massachusetts.com",
+"mail2matt.com",
+"mail2matthew.com",
+"mail2maurice.com",
+"mail2mauritania.com",
+"mail2mauritius.com",
+"mail2max.com",
+"mail2maxwell.com",
+"mail2maybe.com",
+"mail2mba.com",
+"mail2me4u.com",
+"mail2mechanic.com",
+"mail2medieval.com",
+"mail2megan.com",
+"mail2mel.com",
+"mail2melanie.com",
+"mail2melissa.com",
+"mail2melody.com",
+"mail2member.com",
+"mail2memphis.com",
+"mail2methodist.com",
+"mail2mexican.com",
+"mail2mexico.com",
+"mail2mgz.com",
+"mail2miami.com",
+"mail2michael.com",
+"mail2michelle.com",
+"mail2michigan.com",
+"mail2mike.com",
+"mail2milan.com",
+"mail2milano.com",
+"mail2mildred.com",
+"mail2milkyway.com",
+"mail2millennium.com",
+"mail2millionaire.com",
+"mail2milton.com",
+"mail2mime.com",
+"mail2mindreader.com",
+"mail2mini.com",
+"mail2minister.com",
+"mail2minneapolis.com",
+"mail2minnesota.com",
+"mail2miracle.com",
+"mail2missionary.com",
+"mail2mississippi.com",
+"mail2missouri.com",
+"mail2mitch.com",
+"mail2model.com",
+"mail2mom.com",
+"mail2monaco.com",
+"mail2money.com",
+"mail2mongolia.com",
+"mail2monica.com",
+"mail2montana.com",
+"mail2monty.com",
+"mail2moon.com",
+"mail2morocco.com",
+"mail2morpheus.com",
+"mail2mors.com",
+"mail2moscow.com",
+"mail2moslem.com",
+"mail2mouseketeer.com",
+"mail2movies.com",
+"mail2mozambique.com",
+"mail2mp3.com",
+"mail2mrright.com",
+"mail2msright.com",
+"mail2museum.com",
+"mail2music.com",
+"mail2musician.com",
+"mail2muslim.com",
+"mail2my.com",
+"mail2myboat.com",
+"mail2mycar.com",
+"mail2mycell.com",
+"mail2mygsm.com",
+"mail2mylaptop.com",
+"mail2mymac.com",
+"mail2mypager.com",
+"mail2mypalm.com",
+"mail2mypc.com",
+"mail2myphone.com",
+"mail2myplane.com",
+"mail2namibia.com",
+"mail2nancy.com",
+"mail2nasdaq.com",
+"mail2nathan.com",
+"mail2nauru.com",
+"mail2navy.com",
+"mail2neal.com",
+"mail2nebraska.com",
+"mail2ned.com",
+"mail2neil.com",
+"mail2nelson.com",
+"mail2nemesis.com",
+"mail2nepal.com",
+"mail2netherlands.com",
+"mail2network.com",
+"mail2nevada.com",
+"mail2newhampshire.com",
+"mail2newjersey.com",
+"mail2newmexico.com",
+"mail2newyork.com",
+"mail2newzealand.com",
+"mail2nicaragua.com",
+"mail2nick.com",
+"mail2nicole.com",
+"mail2niger.com",
+"mail2nigeria.com",
+"mail2nike.com",
+"mail2no.com",
+"mail2noah.com",
+"mail2noel.com",
+"mail2noelle.com",
+"mail2normal.com",
+"mail2norman.com",
+"mail2northamerica.com",
+"mail2northcarolina.com",
+"mail2northdakota.com",
+"mail2northpole.com",
+"mail2norway.com",
+"mail2notus.com",
+"mail2noway.com",
+"mail2nowhere.com",
+"mail2nuclear.com",
+"mail2nun.com",
+"mail2ny.com",
+"mail2oasis.com",
+"mail2oceanographer.com",
+"mail2ohio.com",
+"mail2ok.com",
+"mail2oklahoma.com",
+"mail2oliver.com",
+"mail2oman.com",
+"mail2one.com",
+"mail2onfire.com",
+"mail2online.com",
+"mail2oops.com",
+"mail2open.com",
+"mail2ophthalmologist.com",
+"mail2optometrist.com",
+"mail2oregon.com",
+"mail2oscars.com",
+"mail2oslo.com",
+"mail2painter.com",
+"mail2pakistan.com",
+"mail2pan.com",
+"mail2panama.com",
+"mail2paraguay.com",
+"mail2paralegal.com",
+"mail2paris.com",
+"mail2park.com",
+"mail2parker.com",
+"mail2party.com",
+"mail2passion.com",
+"mail2pat.com",
+"mail2patricia.com",
+"mail2patrick.com",
+"mail2patty.com",
+"mail2paul.com",
+"mail2paula.com",
+"mail2pay.com",
+"mail2peace.com",
+"mail2pediatrician.com",
+"mail2peggy.com",
+"mail2pennsylvania.com",
+"mail2perry.com",
+"mail2persephone.com",
+"mail2persian.com",
+"mail2peru.com",
+"mail2pete.com",
+"mail2peter.com",
+"mail2pharmacist.com",
+"mail2phil.com",
+"mail2philippines.com",
+"mail2phoenix.com",
+"mail2phonecall.com",
+"mail2phyllis.com",
+"mail2pickup.com",
+"mail2pilot.com",
+"mail2pisces.com",
+"mail2planet.com",
+"mail2platinum.com",
+"mail2plato.com",
+"mail2pluto.com",
+"mail2pm.com",
+"mail2podiatrist.com",
+"mail2poet.com",
+"mail2poland.com",
+"mail2policeman.com",
+"mail2policewoman.com",
+"mail2politician.com",
+"mail2pop.com",
+"mail2pope.com",
+"mail2popular.com",
+"mail2portugal.com",
+"mail2poseidon.com",
+"mail2potatohead.com",
+"mail2power.com",
+"mail2presbyterian.com",
+"mail2president.com",
+"mail2priest.com",
+"mail2prince.com",
+"mail2princess.com",
+"mail2producer.com",
+"mail2professor.com",
+"mail2protect.com",
+"mail2psychiatrist.com",
+"mail2psycho.com",
+"mail2psychologist.com",
+"mail2qatar.com",
+"mail2queen.com",
+"mail2rabbi.com",
+"mail2race.com",
+"mail2racer.com",
+"mail2rachel.com",
+"mail2rage.com",
+"mail2rainmaker.com",
+"mail2ralph.com",
+"mail2randy.com",
+"mail2rap.com",
+"mail2rare.com",
+"mail2rave.com",
+"mail2ray.com",
+"mail2raymond.com",
+"mail2realtor.com",
+"mail2rebecca.com",
+"mail2recruiter.com",
+"mail2recycle.com",
+"mail2redhead.com",
+"mail2reed.com",
+"mail2reggie.com",
+"mail2register.com",
+"mail2rent.com",
+"mail2republican.com",
+"mail2resort.com",
+"mail2rex.com",
+"mail2rhodeisland.com",
+"mail2rich.com",
+"mail2richard.com",
+"mail2ricky.com",
+"mail2ride.com",
+"mail2riley.com",
+"mail2rita.com",
+"mail2rob.com",
+"mail2robert.com",
+"mail2roberta.com",
+"mail2robin.com",
+"mail2rock.com",
+"mail2rocker.com",
+"mail2rod.com",
+"mail2rodney.com",
+"mail2romania.com",
+"mail2rome.com",
+"mail2ron.com",
+"mail2ronald.com",
+"mail2ronnie.com",
+"mail2rose.com",
+"mail2rosie.com",
+"mail2roy.com",
+"mail2rss.org",
+"mail2rudy.com",
+"mail2rugby.com",
+"mail2runner.com",
+"mail2russell.com",
+"mail2russia.com",
+"mail2russian.com",
+"mail2rusty.com",
+"mail2ruth.com",
+"mail2rwanda.com",
+"mail2ryan.com",
+"mail2sa.com",
+"mail2sabrina.com",
+"mail2safe.com",
+"mail2sagittarius.com",
+"mail2sail.com",
+"mail2sailor.com",
+"mail2sal.com",
+"mail2salaam.com",
+"mail2sam.com",
+"mail2samantha.com",
+"mail2samoa.com",
+"mail2samurai.com",
+"mail2sandra.com",
+"mail2sandy.com",
+"mail2sanfrancisco.com",
+"mail2sanmarino.com",
+"mail2santa.com",
+"mail2sara.com",
+"mail2sarah.com",
+"mail2sat.com",
+"mail2saturn.com",
+"mail2saudi.com",
+"mail2saudiarabia.com",
+"mail2save.com",
+"mail2savings.com",
+"mail2school.com",
+"mail2scientist.com",
+"mail2scorpio.com",
+"mail2scott.com",
+"mail2sean.com",
+"mail2search.com",
+"mail2seattle.com",
+"mail2secretagent.com",
+"mail2senate.com",
+"mail2senegal.com",
+"mail2sensual.com",
+"mail2seth.com",
+"mail2sevenseas.com",
+"mail2sexy.com",
+"mail2seychelles.com",
+"mail2shane.com",
+"mail2sharon.com",
+"mail2shawn.com",
+"mail2ship.com",
+"mail2shirley.com",
+"mail2shoot.com",
+"mail2shuttle.com",
+"mail2sierraleone.com",
+"mail2simon.com",
+"mail2singapore.com",
+"mail2single.com",
+"mail2site.com",
+"mail2skater.com",
+"mail2skier.com",
+"mail2sky.com",
+"mail2sleek.com",
+"mail2slim.com",
+"mail2slovakia.com",
+"mail2slovenia.com",
+"mail2smile.com",
+"mail2smith.com",
+"mail2smooth.com",
+"mail2soccer.com",
+"mail2soccerfan.com",
+"mail2socialist.com",
+"mail2soldier.com",
+"mail2somalia.com",
+"mail2son.com",
+"mail2song.com",
+"mail2sos.com",
+"mail2sound.com",
+"mail2southafrica.com",
+"mail2southamerica.com",
+"mail2southcarolina.com",
+"mail2southdakota.com",
+"mail2southkorea.com",
+"mail2southpole.com",
+"mail2spain.com",
+"mail2spanish.com",
+"mail2spectrum.com",
+"mail2splash.com",
+"mail2sponsor.com",
+"mail2sports.com",
+"mail2srilanka.com",
+"mail2stacy.com",
+"mail2stan.com",
+"mail2stanley.com",
+"mail2star.com",
+"mail2state.com",
+"mail2stephanie.com",
+"mail2steve.com",
+"mail2steven.com",
+"mail2stewart.com",
+"mail2stlouis.com",
+"mail2stock.com",
+"mail2stockholm.com",
+"mail2stockmarket.com",
+"mail2storage.com",
+"mail2store.com",
+"mail2strong.com",
+"mail2student.com",
+"mail2studio.com",
+"mail2studio54.com",
+"mail2stuntman.com",
+"mail2subscribe.com",
+"mail2sudan.com",
+"mail2superstar.com",
+"mail2surfer.com",
+"mail2suriname.com",
+"mail2susan.com",
+"mail2suzie.com",
+"mail2swaziland.com",
+"mail2sweden.com",
+"mail2sweetheart.com",
+"mail2swim.com",
+"mail2swimmer.com",
+"mail2swiss.com",
+"mail2switzerland.com",
+"mail2sydney.com",
+"mail2sylvia.com",
+"mail2syria.com",
+"mail2taboo.com",
+"mail2taiwan.com",
+"mail2tajikistan.com",
+"mail2tammy.com",
+"mail2tango.com",
+"mail2tanya.com",
+"mail2tanzania.com",
+"mail2tara.com",
+"mail2taurus.com",
+"mail2taxi.com",
+"mail2taxidermist.com",
+"mail2taylor.com",
+"mail2taz.com",
+"mail2teacher.com",
+"mail2technician.com",
+"mail2ted.com",
+"mail2telephone.com",
+"mail2tenderness.com",
+"mail2tennessee.com",
+"mail2tennis.com",
+"mail2tennisfan.com",
+"mail2terri.com",
+"mail2terry.com",
+"mail2test.com",
+"mail2texas.com",
+"mail2thailand.com",
+"mail2therapy.com",
+"mail2think.com",
+"mail2tickets.com",
+"mail2tiffany.com",
+"mail2tim.com",
+"mail2time.com",
+"mail2timothy.com",
+"mail2tina.com",
+"mail2titanic.com",
+"mail2toby.com",
+"mail2todd.com",
+"mail2togo.com",
+"mail2tom.com",
+"mail2tommy.com",
+"mail2tonga.com",
+"mail2tony.com",
+"mail2touch.com",
+"mail2tourist.com",
+"mail2tracey.com",
+"mail2tracy.com",
+"mail2tramp.com",
+"mail2travel.com",
+"mail2traveler.com",
+"mail2travis.com",
+"mail2trekkie.com",
+"mail2trex.com",
+"mail2triallawyer.com",
+"mail2trick.com",
+"mail2trillionaire.com",
+"mail2troy.com",
+"mail2truck.com",
+"mail2trump.com",
+"mail2try.com",
+"mail2tunisia.com",
+"mail2turbo.com",
+"mail2turkey.com",
+"mail2turkmenistan.com",
+"mail2tv.com",
+"mail2tycoon.com",
+"mail2tyler.com",
+"mail2u4me.com",
+"mail2uae.com",
+"mail2uganda.com",
+"mail2uk.com",
+"mail2ukraine.com",
+"mail2uncle.com",
+"mail2unsubscribe.com",
+"mail2uptown.com",
+"mail2uruguay.com",
+"mail2usa.com",
+"mail2utah.com",
+"mail2uzbekistan.com",
+"mail2v.com",
+"mail2vacation.com",
+"mail2valentines.com",
+"mail2valerie.com",
+"mail2valley.com",
+"mail2vamoose.com",
+"mail2vanessa.com",
+"mail2vanuatu.com",
+"mail2venezuela.com",
+"mail2venous.com",
+"mail2venus.com",
+"mail2vermont.com",
+"mail2vickie.com",
+"mail2victor.com",
+"mail2victoria.com",
+"mail2vienna.com",
+"mail2vietnam.com",
+"mail2vince.com",
+"mail2virginia.com",
+"mail2virgo.com",
+"mail2visionary.com",
+"mail2vodka.com",
+"mail2volleyball.com",
+"mail2waiter.com",
+"mail2wallstreet.com",
+"mail2wally.com",
+"mail2walter.com",
+"mail2warren.com",
+"mail2washington.com",
+"mail2wave.com",
+"mail2way.com",
+"mail2waycool.com",
+"mail2wayne.com",
+"mail2webmaster.com",
+"mail2webtop.com",
+"mail2webtv.com",
+"mail2weird.com",
+"mail2wendell.com",
+"mail2wendy.com",
+"mail2westend.com",
+"mail2westvirginia.com",
+"mail2whether.com",
+"mail2whip.com",
+"mail2white.com",
+"mail2whitehouse.com",
+"mail2whitney.com",
+"mail2why.com",
+"mail2wilbur.com",
+"mail2wild.com",
+"mail2willard.com",
+"mail2willie.com",
+"mail2wine.com",
+"mail2winner.com",
+"mail2wired.com",
+"mail2wisconsin.com",
+"mail2woman.com",
+"mail2wonder.com",
+"mail2world.com",
+"mail2worship.com",
+"mail2wow.com",
+"mail2www.com",
+"mail2wyoming.com",
+"mail2xfiles.com",
+"mail2xox.com",
+"mail2yachtclub.com",
+"mail2yahalla.com",
+"mail2yemen.com",
+"mail2yes.com",
+"mail2yugoslavia.com",
+"mail2zack.com",
+"mail2zambia.com",
+"mail2zenith.com",
+"mail2zephir.com",
+"mail2zeus.com",
+"mail2zipper.com",
+"mail2zoo.com",
+"mail2zoologist.com",
+"mail2zurich.com",
+"mail3000.com",
+"mail4trash.com",
+"mail4u.info",
+"mailandftp.com",
+"mailas.com",
+"mailasia.com",
+"mailbolt.com",
+"mailboom.com",
+"mailbox.as",
+"mailbox.co.za",
+"mailbox.gr",
+"mailbox.hu",
+"mailc.net",
+"mailcan.com",
+"mailcat.biz",
+"mailcc.com",
+"mailcity.com",
+"mailclub.fr",
+"maildx.com",
+"mailed.ro",
+"mailexcite.com",
+"mailfa.tk",
+"mailforce.net",
+"mailforspam.com",
+"mailfs.com",
+"mailftp.com",
+"mailgenie.net",
+"mailguard.me",
+"mailhaven.com",
+"mailhood.com",
+"mailimate.com",
+"mailinatar.com",
+"mailinator.org",
+"mailinblack.com",
+"mailingaddress.org",
+"mailingweb.com",
+"mailisent.com",
+"mailismagic.com",
+"mailite.com",
+"mailmate.com",
+"mailme.dk",
+"mailme.gq",
+"mailme24.com",
+"mailmight.com",
+"mailnator.com",
+"mailnew.com",
+"mailoye.com",
+"mailpanda.com",
+"mailpick.biz",
+"mailpost.zzn.com",
+"mailpride.com",
+"mailproxsy.com",
+"mailpuppy.com",
+"mailquack.com",
+"mailrock.biz",
+"mailroom.com",
+"mailru.com",
+"mailsac.com",
+"mailsent.net",
+"mailservice.ms",
+"mailshuttle.com",
+"mailslapping.com",
+"mailstart.com",
+"mailstartplus.com",
+"mailsurf.com",
+"mailtag.com",
+"mailtemp.info",
+"mailtothis.com",
+"mailueberfall.de",
+"mailup.net",
+"mailwire.com",
+"mailworks.org",
+"mailzi.ru",
+"mailzilla.org",
+"malayalamtelevision.net",
+"maltesemail.com",
+"mamber.net",
+"manager.de",
+"mancity.net",
+"mantramail.com",
+"manybrain.com",
+"marchmail.com",
+"mariahc.com",
+"marijuana.com",
+"marijuana.nl",
+"married-not.com",
+"martindalemail.com",
+"masrawy.com",
+"matmail.com",
+"mauimail.com",
+"mauritius.com",
+"maxmail.co.uk",
+"mbox.com.au",
+"me.com",
+"meta.ua",
+"medical.net.au",
+"medscape.com",
+"meetingmall.com",
+"megapoint.com",
+"mehrani.com",
+"mehtaweb.com",
+"meine-dateien.info",
+"meine-diashow.de",
+"meine-fotos.info",
+"meine-urlaubsfotos.de",
+"mekhong.com",
+"merda.flu.cc",
+"merda.igg.biz",
+"merda.nut.cc",
+"merda.usa.cc",
+"message.hu",
+"messages.to",
+"metacrawler.com",
+"metalfan.com",
+"metta.lk",
+"mexicomail.com",
+"mezimages.net",
+"mfsa.ru",
+"mierdamail.com",
+"miesto.sk",
+"mighty.co.za",
+"migmail.net",
+"migmail.pl",
+"miho-nakayama.com",
+"mikrotamanet.com",
+"millionaireintraining.com",
+"millionairemail.com",
+"milmail.com",
+"mindless.com",
+"mindspring.com",
+"minister.com",
+"misery.net",
+"mittalweb.com",
+"mixmail.com",
+"mjfrogmail.com",
+"ml1.net",
+"mm.st",
+"mns.ru",
+"moakt.com",
+"mobileninja.co.uk",
+"mochamail.com",
+"mohammed.com",
+"mohmal.com",
+"moldova.cc",
+"moldova.com",
+"moldovacc.com",
+"momslife.com",
+"monemail.com",
+"money.net",
+"montevideo.com.uy",
+"monumentmail.com",
+"moose-mail.com",
+"mor19.uu.gl",
+"mortaza.com",
+"moscowmail.com",
+"mostlysunny.com",
+"motormania.com",
+"movemail.com",
+"movieluver.com",
+"mox.pp.ua",
+"mp4.it",
+"mr-potatohead.com",
+"msgbox.com",
+"msn.cn",
+"msn.com",
+"msn.nl",
+"mt2015.com",
+"mt2016.com",
+"mttestdriver.com",
+"muehlacker.tk",
+"munich.com",
+"music.com",
+"musician.org",
+"musicscene.org",
+"muskelshirt.de",
+"muslim.com",
+"muslimsonline.com",
+"mutantweb.com",
+"mvrht.com",
+"my.com",
+"my10minutemail.com",
+"mybox.it",
+"mycity.com",
+"mydomain.com",
+"mydotcomaddress.com",
+"myfamily.com",
+"myfastmail.com",
+"mygo.com",
+"myiris.com",
+"mymacmail.com",
+"mynamedot.com",
+"mynet.com",
+"mynetstore.de",
+"myownemail.com",
+"mypacks.net",
+"mypad.com",
+"myplace.com",
+"myrambler.ru",
+"myrealbox.com",
+"myremarq.com",
+"myself.com",
+"myspamless.com",
+"mystupidjob.com",
+"mytemp.email",
+"mythirdage.com",
+"myway.com",
+"myworldmail.com",
+"n2.com",
+"n2baseball.com",
+"n2mail.com",
+"n2soccer.com",
+"n2software.com",
+"nabc.biz",
+"nafe.com",
+"nakedgreens.com",
+"name.com",
+"naplesnews.net",
+"naseej.com",
+"nativeweb.net",
+"naui.net",
+"naver.com",
+"navigator.lv",
+"navy.org",
+"naz.com",
+"nchoicemail.com",
+"neeva.net",
+"nenter.com",
+"neo.rr.com",
+"nervhq.org",
+"net-c.be",
+"net-c.ca",
+"net-c.cat",
+"net-c.com",
+"net-c.es",
+"net-c.fr",
+"net-c.it",
+"net-c.lu",
+"net-c.nl",
+"net-c.pl",
+"net-pager.net",
+"net-shopping.com",
+"net4b.pt",
+"net4you.at",
+"netbounce.com",
+"netbroadcaster.com",
+"netby.dk",
+"netc.eu",
+"netc.fr",
+"netc.it",
+"netc.lu",
+"netc.pl",
+"netcenter-vn.net",
+"netcmail.com",
+"netcourrier.com",
+"netexecutive.com",
+"netexpressway.com",
+"netgenie.com",
+"netian.com",
+"netizen.com.ar",
+"netmongol.com",
+"netnoir.net",
+"netpiper.com",
+"netralink.com",
+"netscape.net",
+"netspace.net.au",
+"netster.com",
+"nettaxi.com",
+"nettemail.com",
+"netterchef.de",
+"netzero.com",
+"netzero.net",
+"neue-dateien.de",
+"neuf.fr",
+"neuro.md",
+"newmail.com",
+"newmail.net",
+"newmail.ru",
+"newsboysmail.com",
+"newyork.com",
+"nextmail.ru",
+"nexxmail.com",
+"nfmail.com",
+"nicebush.com",
+"nicegal.com",
+"nicholastse.net",
+"nicolastse.com",
+"nikopage.com",
+"nimail.com",
+"ninfan.com",
+"nirvanafan.com",
+"nmail.cf",
+"noavar.com",
+"nonpartisan.com",
+"nonspam.eu",
+"nonspammer.de",
+"norika-fujiwara.com",
+"norikomail.com",
+"northgates.net",
+"nowhere.org",
+"ntlhelp.net",
+"ntscan.com",
+"null.net",
+"nullbox.info",
+"nur-fuer-spam.de",
+"nus.edu.sg",
+"nwldx.com",
+"nxt.ru",
+"ny.com",
+"nybella.com",
+"nyc.com",
+"nycmail.com",
+"nzoomail.com",
+"o-tay.com",
+"o2.co.uk",
+"oaklandas-fan.com",
+"oath.com",
+"oceanfree.net",
+"oddpost.com",
+"odmail.com",
+"office-dateien.de",
+"office-email.com",
+"offroadwarrior.com",
+"oicexchange.com",
+"oikrach.com",
+"okbank.com",
+"okhuman.com",
+"okmagic.com",
+"oldies104mail.com",
+"olemail.com",
+"olympist.net",
+"olypmall.ru",
+"omaninfo.com",
+"omen.ru",
+"onebox.com",
+"onenet.com.ar",
+"oneoffmail.com",
+"onet.com.pl",
+"onet.eu",
+"onet.pl",
+"oninet.pt",
+"online.ie",
+"online.ms",
+"online.nl",
+"onlinewiz.com",
+"onmilwaukee.com",
+"onobox.com",
+"op.pl",
+"opayq.com",
+"openmailbox.org",
+"operafan.com",
+"operamail.com",
+"opoczta.pl",
+"optician.com",
+"optonline.net",
+"optusnet.com.au",
+"orange.fr",
+"orbitel.bg",
+"orgmail.net",
+"orthodontist.net",
+"osite.com.br",
+"oso.com",
+"otakumail.com",
+"our-computer.com",
+"our-office.com",
+"our.st",
+"ourbrisbane.com",
+"ournet.md",
+"outgun.com",
+"outlook.at",
+"outlook.be",
+"outlook.cl",
+"outlook.co.id",
+"outlook.co.il",
+"outlook.co.nz",
+"outlook.co.th",
+"outlook.com",
+"outlook.com.au",
+"outlook.com.br",
+"outlook.com.gr",
+"outlook.com.pe",
+"outlook.com.tr",
+"outlook.com.vn",
+"outlook.cz",
+"outlook.de",
+"outlook.dk",
+"outlook.es",
+"outlook.fr",
+"outlook.hu",
+"outlook.ie",
+"outlook.in",
+"outlook.it",
+"outlook.jp",
+"outlook.kr",
+"outlook.lv",
+"outlook.my",
+"outlook.ph",
+"outlook.pt",
+"outlook.sa",
+"outlook.sg",
+"outlook.sk",
+"over-the-rainbow.com",
+"ownmail.net",
+"ozbytes.net.au",
+"ozemail.com.au",
+"pacbell.net",
+"pacific-ocean.com",
+"pacific-re.com",
+"pacificwest.com",
+"pagina.de",
+"pagons.org",
+"pakistanmail.com",
+"pakistanoye.com",
+"parkjiyoon.com",
+"parrot.com",
+"parsmail.com",
+"partlycloudy.com",
+"partybombe.de",
+"partyheld.de",
+"partynight.at",
+"passwordmail.com",
+"pathfindermail.com",
+"pcusers.otherinbox.com",
+"pediatrician.com",
+"penpen.com",
+"peoplepc.com",
+"peopleweb.com",
+"pepbot.com",
+"perfectmail.com",
+"perso.be",
+"personal.ro",
+"personales.com",
+"petlover.com",
+"petml.com",
+"pettypool.com",
+"pezeshkpour.com",
+"pfui.ru",
+"phayze.com",
+"phone.net",
+"photographer.net",
+"phpbb.uu.gl",
+"phreaker.net",
+"physicist.net",
+"pianomail.com",
+"pickupman.com",
+"picusnet.com",
+"pigpig.net",
+"pinoymail.com",
+"piracha.net",
+"pisem.net",
+"pjjkp.com",
+"planet.nl",
+"planetaccess.com",
+"planetarymotion.net",
+"planetearthinter.net",
+"planetmail.com",
+"planetmail.net",
+"planetout.com",
+"playersodds.com",
+"playful.com",
+"plus.com",
+"plusmail.com.br",
+"pmail.net",
+"pobox.sk",
+"pochta.ru",
+"poczta.fm",
+"poczta.onet.pl",
+"poetic.com",
+"pokemail.net",
+"pokemonpost.com",
+"pokepost.com",
+"polandmail.com",
+"polbox.com",
+"politician.com",
+"polizisten-duzer.de",
+"poond.com",
+"popaccount.com",
+"popmail.com",
+"popsmail.com",
+"popstar.com",
+"portugalmail.com",
+"portugalmail.pt",
+"post.com",
+"post.cz",
+"post.sk",
+"posta.ro",
+"postaccesslite.com",
+"postafree.com",
+"postfach.cc",
+"postinbox.com",
+"postino.ch",
+"postmark.net",
+"postmaster.co.uk",
+"postpro.net",
+"powerfan.com",
+"praize.com",
+"premiumservice.com",
+"presidency.com",
+"priest.com",
+"primposta.com",
+"primposta.hu",
+"privy-mail.com",
+"privymail.de",
+"pro.hu",
+"probemail.com",
+"prodigy.net",
+"progetplus.it",
+"programist.ru",
+"programmer.net",
+"proinbox.com",
+"promessage.com",
+"prontomail.com",
+"protestant.com",
+"protonmail.com",
+"prydirect.info",
+"psv-supporter.com",
+"ptd.net",
+"public-files.de",
+"public.usa.com",
+"publicist.com",
+"pulp-fiction.com",
+"purpleturtle.com",
+"put2.net",
+"pwrby.com",
+"q.com",
+"qmail.com",
+"qprfans.com",
+"qq.com",
+"quackquack.com",
+"quakemail.com",
+"qualityservice.com",
+"quantentunnel.de",
+"quickhosts.com",
+"quickmail.nl",
+"quicknet.nl",
+"quickwebmail.com",
+"quiklinks.com",
+"quikmail.com",
+"qv7.info",
+"qwest.net",
+"qwestoffice.net",
+"racedriver.com",
+"racefanz.com",
+"racingmail.com",
+"radicalz.com",
+"radiku.ye.vc",
+"radiologist.net",
+"ragingbull.com",
+"ralib.com",
+"rambler.ru",
+"rambler.ua",
+"ranmamail.com",
+"rastogi.net",
+"ratt-n-roll.com",
+"rattle-snake.com",
+"raubtierbaendiger.de",
+"ravearena.com",
+"ravemail.com",
+"realemail.net",
+"reality-concept.club",
+"reallyfast.biz",
+"reallyfast.info",
+"reallymymail.com",
+"realtyagent.com",
+"reborn.com",
+"reconmail.com",
+"recycler.com",
+"recyclermail.com",
+"rediff.com",
+"rediffmail.com",
+"rediffmailpro.com",
+"rednecks.com",
+"redseven.de",
+"reggaefan.com",
+"registerednurses.com",
+"regspaces.tk",
+"reincarnate.com",
+"religious.com",
+"remail.ga",
+"renren.com",
+"repairman.com",
+"reply.hu",
+"representative.com",
+"rescueteam.com",
+"resgedvgfed.tk",
+"resumemail.com",
+"rezai.com",
+"rhyta.com",
+"richmondhill.com",
+"rickymail.com",
+"rin.ru",
+"riopreto.com.br",
+"rn.com",
+"ro.ru",
+"roadrunner.com",
+"roanokemail.com",
+"rock.com",
+"rocketmail.com",
+"rocketship.com",
+"rockfan.com",
+"rodrun.com",
+"rogers.com",
+"roosh.com",
+"rootprompt.org",
+"royal.net",
+"rr.com",
+"rrohio.com",
+"rsub.com",
+"runbox.com",
+"rushpost.com",
+"ruttolibero.com",
+"rvshop.com",
+"s-mail.com",
+"sacbeemail.com",
+"saeuferleber.de",
+"safrica.com",
+"sagra.lu",
+"sags-per-mail.de",
+"sailormoon.com",
+"saintly.com",
+"salehi.net",
+"salesperson.net",
+"samerica.com",
+"samilan.net",
+"sammimail.com",
+"sandelf.de",
+"sanfranmail.com",
+"sanook.com",
+"sapo.pt",
+"saudia.com",
+"sayhi.net",
+"sbcglobal.net",
+"scandalmail.com",
+"scarlet.nl",
+"schafmail.de",
+"schizo.com",
+"schmusemail.de",
+"schoolmail.com",
+"schoolsucks.com",
+"schreib-doch-mal-wieder.de",
+"sci.fi",
+"scientist.com",
+"scotland.com",
+"scotlandmail.com",
+"scottishmail.co.uk",
+"scubadiving.com",
+"seanet.com",
+"search.ua",
+"searchwales.com",
+"sebil.com",
+"secret-police.com",
+"secretary.net",
+"secretservices.net",
+"secure-mail.biz",
+"secure-mail.cc",
+"seductive.com",
+"seekstoyboy.com",
+"seguros.com.br",
+"selfdestructingmail.com",
+"send.hu",
+"sendme.cz",
+"sendspamhere.com",
+"sent.as",
+"sent.at",
+"sent.com",
+"sentrismail.com",
+"serga.com.ar",
+"servemymail.com",
+"servermaps.net",
+"sesmail.com",
+"sexmagnet.com",
+"seznam.cz",
+"sfr.fr",
+"shaniastuff.com",
+"shared-files.de",
+"sharedmailbox.org",
+"sharmaweb.com",
+"she.com",
+"shieldedmail.com",
+"shinedyoureyes.com",
+"shitaway.cf",
+"shitaway.ga",
+"shitaway.gq",
+"shitaway.ml",
+"shitaway.tk",
+"shitaway.usa.cc",
+"shitmail.de",
+"shitmail.org",
+"shitware.nl",
+"shortmail.com",
+"shotgun.hu",
+"showslow.de",
+"sialkotcity.com",
+"sialkotian.com",
+"sialkotoye.com",
+"sify.com",
+"silkroad.net",
+"sina.cn",
+"sina.com",
+"singles4jesus.com",
+"singmail.com",
+"singnet.com.sg",
+"singpost.com",
+"sinnlos-mail.de",
+"siteposter.net",
+"skafan.com",
+"skeefmail.com",
+"skim.com",
+"skizo.hu",
+"skrx.tk",
+"sky.com",
+"slamdunkfan.com",
+"slave-auctions.net",
+"slingshot.com",
+"slippery.email",
+"slipry.net",
+"slotter.com",
+"smap.4nmv.ru",
+"smapxsmap.net",
+"smashmail.de",
+"smoothmail.com",
+"sms.at",
+"snail-mail.net",
+"snakebite.com",
+"snet.net",
+"sniper.hu",
+"snkmail.com",
+"snoopymail.com",
+"snowboarding.com",
+"snowdonia.net",
+"socceramerica.net",
+"soccermail.com",
+"soccermomz.com",
+"social-mailer.tk",
+"socialworker.net",
+"sociologist.com",
+"sofort-mail.de",
+"sofortmail.de",
+"softhome.net",
+"sogou.com",
+"sohu.com",
+"sol.dk",
+"solcon.nl",
+"soldier.hu",
+"solution4u.com",
+"solvemail.info",
+"songwriter.net",
+"sonnenkinder.org",
+"soodomail.com",
+"soon.com",
+"soulfoodcookbook.com",
+"sp.nl",
+"space-bank.com",
+"space-man.com",
+"space-ship.com",
+"space-travel.com",
+"space.com",
+"spacemart.com",
+"spacewar.com",
+"spainmail.com",
+"spam.2012-2016.ru",
+"spamavert.com",
+"spambob.com",
+"spambooger.com",
+"spamdecoy.net",
+"spameater.com",
+"spamfree24.info",
+"spaminator.de",
+"spaml.com",
+"spamoff.de",
+"spartapiet.com",
+"speedemail.net",
+"speedpost.net",
+"speedrules.com",
+"speedrulz.com",
+"speedymail.org",
+"sperke.net",
+"spils.com",
+"spinfinder.com",
+"spl.at",
+"spoko.pl",
+"spoofmail.de",
+"sportsmail.com",
+"sporttruckdriver.com",
+"spray.se",
+"spybox.de",
+"spymac.com",
+"srilankan.net",
+"ssl-mail.com",
+"st-davids.net",
+"stade.fr",
+"stargateradio.com",
+"starmail.com",
+"starmedia.com",
+"starspath.com",
+"start.com.au",
+"startkeys.com",
+"stinkefinger.net",
+"stipte.nl",
+"stoned.com",
+"stones.com",
+"stop-my-spam.pp.ua",
+"streber24.de",
+"streetwisemail.com",
+"strompost.com",
+"strongguy.com",
+"student.su",
+"studentcenter.org",
+"stuffmail.de",
+"subram.com",
+"sudolife.me",
+"sudolife.net",
+"sudomail.biz",
+"sudomail.com",
+"sudomail.net",
+"sudoverse.com",
+"sudoverse.net",
+"sudoweb.net",
+"sudoworld.com",
+"sudoworld.net",
+"suhabi.com",
+"sukhumvit.net",
+"sunpoint.net",
+"sunrise-sunset.com",
+"sunsgame.com",
+"sunumail.sn",
+"superdada.com",
+"supereva.it",
+"supermail.ru",
+"superrito.com",
+"surf3.net",
+"surfree.com",
+"surfy.net",
+"surgical.net",
+"surimail.com",
+"survivormail.com",
+"svk.jp",
+"swbell.net",
+"sweb.cz",
+"swedenmail.com",
+"sweetxxx.de",
+"swift-mail.com",
+"swiftdesk.com",
+"swingeasyhithard.com",
+"swingfan.com",
+"swipermail.zzn.com",
+"swirve.com",
+"swissmail.com",
+"swissmail.net",
+"switchboardmail.com",
+"sx172.com",
+"syom.com",
+"t-online.de",
+"t.psh.me",
+"t2mail.com",
+"tafmail.com",
+"takuyakimura.com",
+"talk21.com",
+"talkinator.com",
+"tamil.com",
+"tampabay.rr.com",
+"tankpolice.com",
+"tatanova.com",
+"tbwt.com",
+"tds.net",
+"teachermail.net",
+"teachers.org",
+"teamdiscovery.com",
+"teamtulsa.net",
+"tech-center.com",
+"tech4peace.org",
+"techemail.com",
+"techie.com",
+"technisamail.co.za",
+"technologist.com",
+"techscout.com",
+"techspot.com",
+"tele2.at",
+"tele2.nl",
+"teleline.es",
+"telerymd.com",
+"teleworm.us",
+"telfort.nl",
+"telfortglasvezel.nl",
+"telinco.net",
+"telpage.net",
+"telstra.com",
+"telstra.com.au",
+"temp-mail.com",
+"temp-mail.de",
+"temp.headstrong.de",
+"tempail.com",
+"tempemail.biz",
+"tempmail.us",
+"tempmaildemo.com",
+"tempmailer.com",
+"temporarioemail.com.br",
+"temporaryemail.us",
+"tempthe.net",
+"tempymail.com",
+"temtulsa.net",
+"tenchiclub.com",
+"tenderkiss.com",
+"tennismail.com",
+"terminverpennt.de",
+"terra.cl",
+"terra.com",
+"terra.com.ar",
+"terra.com.br",
+"test.com",
+"test.de",
+"tfanus.com.er",
+"tfz.net",
+"thai.com",
+"thaimail.com",
+"thaimail.net",
+"thanksnospam.info",
+"the-african.com",
+"the-aliens.com",
+"the-american.com",
+"the-animal.com",
+"the-astronaut.com",
+"the-beauty.com",
+"the-big-apple.com",
+"the-boss.com",
+"the-captain.com",
+"the-cowboy.com",
+"the-eagles.com",
+"the-fastest.net",
+"the-galaxy.net",
+"the-genius.com",
+"the-gentleman.com",
+"the-german.com",
+"the-italian.com",
+"the-lair.com",
+"the-madman.com",
+"the-marine.com",
+"the-master.com",
+"the-mexican.com",
+"the-monkey.com",
+"the-pentagon.com",
+"the-professional.com",
+"the-quickest.com",
+"the-russian.com",
+"the-spaceman.com",
+"thecriminals.com",
+"thedoghousemail.com",
+"thedorm.com",
+"theend.hu",
+"theglobe.com",
+"thegolfcourse.com",
+"theheadoffice.com",
+"theinternetemail.com",
+"thelanddownunder.com",
+"themail.com",
+"themillionare.net",
+"theplate.com",
+"thepokerface.com",
+"thepostmaster.net",
+"theraces.com",
+"therapist.net",
+"thestreetfighter.com",
+"thewatercooler.com",
+"thewebpros.co.uk",
+"thirdage.com",
+"thisgirl.com",
+"thraml.com",
+"throwam.com",
+"tidni.com",
+"tiscali.co.uk",
+"tiscali.it",
+"tkcity.com",
+"tmail.ws",
+"toast.com",
+"toke.com",
+"tom.com",
+"toolsource.com",
+"toomail.biz",
+"toothfairy.com",
+"topletter.com",
+"topmail-files.de",
+"torontomail.com",
+"tortenboxer.de",
+"totalmail.de",
+"totalmusic.net",
+"tpg.com.au",
+"trash-mail.ml",
+"trashdevil.de",
+"trashymail.net",
+"trayna.com",
+"trialbytrivia.com",
+"trickmail.net",
+"trimix.cn",
+"tritium.net",
+"trmailbox.com",
+"tropicalstorm.com",
+"truckracer.com",
+"truckracers.com",
+"truthmail.com",
+"tsamail.co.za",
+"ttml.co.in",
+"turboprinz.de",
+"turboprinzessin.de",
+"turkey.com",
+"tut.by",
+"tvstar.com",
+"twc.com",
+"twinstarsmail.com",
+"typemail.com",
+"u2club.com",
+"ua.fm",
+"ubbi.com",
+"uboot.com",
+"uk2.net",
+"uk2k.com",
+"uk2net.com",
+"uk7.net",
+"uk8.net",
+"ukbuilder.com",
+"ukcool.com",
+"ukdreamcast.com",
+"ukmail.org",
+"ukmax.com",
+"ukr.net",
+"uku.co.uk",
+"ultapulta.com",
+"ultrapostman.com",
+"ummah.org",
+"umpire.com",
+"unbounded.com",
+"unforgettable.com",
+"uni.de",
+"unican.es",
+"unihome.com",
+"universal.pt",
+"uno.ee",
+"uno.it",
+"unofree.it",
+"unterderbruecke.de",
+"uol.com.br",
+"uol.com.co",
+"uol.com.ve",
+"uomail.com",
+"upc.nl",
+"upcmail.nl",
+"upf.org",
+"uplipht.com",
+"ureach.com",
+"uroid.com",
+"usa.com",
+"usa.net",
+"usaaccess.net",
+"usermail.com",
+"username.e4ward.com",
+"usma.net",
+"usmc.net",
+"uswestmail.net",
+"utanet.at",
+"uymail.com",
+"uyuyuy.com",
+"vaasfc4.tk",
+"vahoo.com",
+"valemail.net",
+"vampirehunter.com",
+"varbizmail.com",
+"vcmail.com",
+"velnet.co.uk",
+"velocall.com",
+"verizon.net",
+"verlass-mich-nicht.de",
+"versatel.nl",
+"veryfast.biz",
+"veryrealemail.com",
+"veryspeedy.net",
+"vfemail.net",
+"vickaentb.tk",
+"videotron.ca",
+"viditag.com",
+"vinbazar.com",
+"violinmakers.co.uk",
+"vip.126.com",
+"vip.163.com",
+"vip.21cn.com",
+"vip.citiz.net",
+"vip.gr",
+"vip.onet.pl",
+"vip.qq.com",
+"vip.sina.com",
+"vipmail.ru",
+"virgilio.it",
+"virgin.net",
+"virginbroadband.com.au",
+"visitweb.com",
+"visto.com",
+"vivavelocity.com",
+"vivianhsu.net",
+"vkcode.ru",
+"vnet.citiz.net",
+"vnn.vn",
+"vodafone.nl",
+"vodafonethuis.nl",
+"volcanomail.com",
+"vollbio.de",
+"volloeko.de",
+"vomoto.com",
+"vorsicht-bissig.de",
+"vorsicht-scharf.de",
+"vote-democrats.com",
+"vote-republicans.com",
+"vote4gop.org",
+"votenet.com",
+"vp.pl",
+"vr9.com",
+"vubby.com",
+"w3.to",
+"wahoye.com",
+"walala.org",
+"wales2000.net",
+"walkmail.net",
+"walkmail.ru",
+"wam.co.za",
+"wanadoo.es",
+"wanadoo.fr",
+"war-im-urlaub.de",
+"warmmail.com",
+"warpmail.net",
+"warrior.hu",
+"wazabi.club",
+"wbdet.com",
+"web-contact.info",
+"web-emailbox.eu",
+"web-mail.com.ar",
+"web-mail.pp.ua",
+"web-police.com",
+"web.de",
+"webave.com",
+"webcity.ca",
+"webcontact-france.eu",
+"webdream.com",
+"webindia123.com",
+"webmail.co.za",
+"webmail.hu",
+"webmails.com",
+"webname.com",
+"webstation.com",
+"websurfer.co.za",
+"webtopmail.com",
+"wee.my",
+"weekonline.com",
+"wefjo.grn.cc",
+"weg-werf-email.de",
+"wegas.ru",
+"wegwerf-emails.de",
+"wegwerfmail.info",
+"wegwerpmailadres.nl",
+"wehshee.com",
+"weibsvolk.de",
+"weibsvolk.org",
+"weinenvorglueck.de",
+"welsh-lady.com",
+"westnet.com.au",
+"wfgdfhj.tk",
+"whale-mail.com",
+"whartontx.com",
+"whatiaas.com",
+"whatpaas.com",
+"wheelweb.com",
+"whipmail.com",
+"whoever.com",
+"whtjddn.33mail.com",
+"wickmail.net",
+"wideopenwest.com",
+"wildmail.com",
+"wilemail.com",
+"will-hier-weg.de",
+"windowslive.com",
+"windstream.net",
+"wingnutz.com",
+"winning.com",
+"wir-haben-nachwuchs.de",
+"wir-sind-cool.org",
+"witty.com",
+"wiz.cc",
+"wkbwmail.com",
+"wmail.cf",
+"wo.com.cn",
+"woh.rr.com",
+"wolke7.net",
+"wombles.com",
+"women-at-work.org",
+"wongfaye.com",
+"wooow.it",
+"worker.com",
+"workmail.com",
+"worldemail.com",
+"worldnet.att.net",
+"wormseo.cn",
+"wosaddict.com",
+"wowgirl.com",
+"wowmail.com",
+"wowway.com",
+"wp.pl",
+"wptamail.com",
+"wrexham.net",
+"writeme.com",
+"writemeback.com",
+"wrongmail.com",
+"www.com",
+"www.e4ward.com",
+"wxs.net",
+"x-mail.net",
+"x-networks.net",
+"x5g.com",
+"xaker.ru",
+"xing886.uu.gl",
+"xmastime.com",
+"xms.nl",
+"xoom.com",
+"xpressmail.zzn.com",
+"xs4all.nl",
+"xsecurity.org",
+"xsmail.com",
+"xtra.co.nz",
+"xuno.com",
+"xww.ro",
+"xy9ce.tk",
+"y7mail.com",
+"ya.ru",
+"yada-yada.com",
+"yahoo.at",
+"yahoo.be",
+"yahoo.ca",
+"yahoo.cn",
+"yahoo.co.id",
+"yahoo.co.il",
+"yahoo.co.in",
+"yahoo.co.jp",
+"yahoo.co.kr",
+"yahoo.co.nz",
+"yahoo.co.th",
+"yahoo.co.uk",
+"yahoo.co.za",
+"yahoo.com",
+"yahoo.com.ar",
+"yahoo.com.au",
+"yahoo.com.br",
+"yahoo.com.cn",
+"yahoo.com.co",
+"yahoo.com.hk",
+"yahoo.com.mx",
+"yahoo.com.my",
+"yahoo.com.ph",
+"yahoo.com.sg",
+"yahoo.com.tr",
+"yahoo.com.tw",
+"yahoo.com.vn",
+"yahoo.cz",
+"yahoo.de",
+"yahoo.dk",
+"yahoo.es",
+"yahoo.fi",
+"yahoo.fr",
+"yahoo.gr",
+"yahoo.hu",
+"yahoo.ie",
+"yahoo.in",
+"yahoo.it",
+"yahoo.jp",
+"yahoo.nl",
+"yahoo.no",
+"yahoo.pl",
+"yahoo.pt",
+"yahoo.ro",
+"yahoo.se",
+"yalla.com",
+"yalla.com.lb",
+"yalook.com",
+"yam.com",
+"yandex.com",
+"yandex.ru",
+"yandex.ua",
+"yapped.net",
+"yawmail.com",
+"yeah.net",
+"yebox.com",
+"yehey.com",
+"yepmail.net",
+"yert.ye.vc",
+"yesey.net",
+"ymail.com",
+"yogotemail.com",
+"yomail.info",
+"yopmail.pp.ua",
+"yopolis.com",
+"yopweb.com",
+"youareadork.com",
+"youmailr.com",
+"your-house.com",
+"your-mail.com",
+"yourname.freeservers.com",
+"yours.com",
+"yoursubdomain.zzn.com",
+"yourteacher.net",
+"yuuhuu.net",
+"yyhmail.com",
+"z1p.biz",
+"za.com",
+"zahadum.com",
+"zaktouni.fr",
+"zeepost.nl",
+"zetmail.com",
+"zhaowei.net",
+"zhouemail.510520.org",
+"ziggo.nl",
+"zionweb.org",
+"zip.net",
+"zipido.com",
+"ziplip.com",
+"zipmail.com",
+"zipmail.com.br",
+"zipmax.com",
+"zmail.ru",
+"zoho.com",
+"zomg.info",
+"zonnet.nl",
+"zoominternet.net",
+"zubee.com",
+"zuzzurello.com",
+"zwallet.com",
+"zweb.in",
+"zxcvbnm.com",
+"zybermail.com",
+"zydecofan.com",
+"zzn.com",
+"zzz.com"}
+
+spam-mime = {
+"bat" = "BAD",
+"chm" = "BAD",
+"com" = "BAD",
+"exe" = "BAD",
+"hta" = "BAD|NZ",
+"iso" = "BAD",
+"jar" = "BAD|NZ",
+"lnk" = "BAD",
+"scr" = "BAD",
+"htm" = "text/html|BAD",
+"html" = "text/html|BAD",
+"shtm" = "text/html|BAD",
+"shtml" = "text/html|BAD",
+"ace" = "BAD|AR",
+"arj" = "BAD|AR",
+"asx" = "BAD",
+"cab" = "BAD|AR",
+"sfx" = "BAD",
+"vst" = "BAD",
+"vss" = "BAD",
+"ade" = "BAD",
+"adp" = "BAD",
+"cmd" = "BAD",
+"cpl" = "BAD",
+"ins" = "BAD",
+"isp" = "BAD",
+"js" = "BAD|NZ",
+"jse" = "BAD",
+"lib" = "BAD",
+"mde" = "BAD",
+"msc" = "BAD",
+"msi" = "BAD",
+"msp" = "BAD",
+"mst" = "BAD",
+"nsh" = "BAD",
+"pif" = "BAD",
+"sct" = "BAD",
+"shb" = "BAD",
+"sys" = "BAD",
+"vb" = "BAD",
+"vbe" = "BAD",
+"vbs" = "BAD|NZ",
+"vxd" = "BAD",
+"wsc" = "BAD",
+"wsh" = "BAD",
+"app" = "BAD",
+"asp" = "BAD",
+"bas" = "BAD",
+"cnt" = "BAD",
+"csh" = "BAD",
+"diagcab" = "BAD",
+"fxp" = "BAD",
+"gadget" = "BAD",
+"grp" = "BAD",
+"hlp" = "BAD",
+"hpj" = "BAD",
+"inf" = "BAD",
+"its" = "BAD",
+"jnlp" = "BAD",
+"ksh" = "BAD",
+"mad" = "BAD",
+"maf" = "BAD",
+"mag" = "BAD",
+"mam" = "BAD",
+"maq" = "BAD",
+"mar" = "BAD",
+"mas" = "BAD",
+"mat" = "BAD",
+"mau" = "BAD",
+"mav" = "BAD",
+"maw" = "BAD",
+"mcf" = "BAD",
+"mda" = "BAD",
+"mdb" = "BAD",
+"mdt" = "BAD",
+"mdw" = "BAD",
+"mdz" = "BAD",
+"msh" = "BAD",
+"msh1" = "BAD",
+"msh2" = "BAD",
+"mshxml" = "BAD",
+"msh1xml" = "BAD",
+"msh2xml" = "BAD",
+"msu" = "BAD",
+"ops" = "BAD",
+"osd" = "BAD",
+"pcd" = "BAD",
+"pl" = "BAD",
+"plg" = "BAD",
+"prf" = "BAD",
+"prg" = "BAD",
+"printerexport" = "BAD",
+"ps1" = "BAD",
+"ps1xml" = "BAD",
+"ps2" = "BAD",
+"ps2xml" = "BAD",
+"psc1" = "BAD",
+"psc2" = "BAD",
+"psd1" = "BAD",
+"psdm1" = "BAD",
+"pst" = "BAD",
+"reg" = "BAD",
+"scf" = "BAD",
+"shs" = "BAD",
+"theme" = "BAD",
+"url" = "BAD",
+"vbp" = "BAD",
+"vsmacros" = "BAD",
+"vsw" = "BAD",
+"webpnp" = "BAD",
+"website" = "BAD",
+"ws" = "BAD",
+"xbap" = "BAD",
+"xll" = "BAD",
+"xnk" = "BAD",
+"docx" = "NZ",
+"pdf" = "application/pdf|application/x-pdf|NZ",
+"pptx" = "NZ",
+"wsf" = "NZ",
+"xlsx" = "NZ",
+"7x" = "AR",
+"alz" = "AR",
+"bz2" = "AR",
+"egg" = "AR",
+"lz" = "AR",
+"rar" = "AR",
+"xz" = "AR",
+"zip" = "AR",
+"txt" = "text/plain|message/disposition-notification|text/rfc822-headers"}
+
+
+spam-redirect = {"000d.ru",
+"0845.com",
+"0c.ru",
+"0lv.ru",
+"0pen.me",
+"0rz.tw",
+"10r.us",
+"123url.org",
+"140.uz",
+"17q.com",
+"1c-bitrix.ru",
+"1cl.in",
+"1ink.in",
+"1ink.ru",
+"1iny.com",
+"1lik.net",
+"1link.in",
+"1url.com",
+"1url.in",
+"1-url.net",
+"1-url.ru",
+"2big.at",
+"2dwww.com",
+"2.gp",
+"2it.info",
+"2.ly",
+"2mb.eu",
+"2qu.ru",
+"2sms.ru",
+"2tu.me",
+"2tu.us",
+"2url.org",
+"307.to",
+"3fw.ru",
+"3le.ru",
+"3.ly",
+"3.vu",
+"3x.si",
+"4.gg",
+"4job.ru",
+"4.ly",
+"4ms.me",
+"4p5.com",
+"4ry.ru",
+"4sq.com",
+"4u.gd",
+"4url.cc",
+"4url.tk",
+"5.gp",
+"5link.tk",
+"5pl.us",
+"5url.net",
+"5z8.info",
+"6fr.ru",
+"6.ly",
+"6pn.com",
+"6url.com",
+"6yo.org",
+"70.ru",
+"74job.ru",
+"7.ly",
+"7ly.ru",
+"7pisem.ru",
+"7ruh.com",
+"7ry.us",
+"7xu.org",
+"8.ly",
+"8q.ro",
+"9mp.com",
+"9-n.org",
+"9xi.ru",
+"a1.tc",
+"a2k.in",
+"aa.cx",
+"aafter.us",
+"abe5.com",
+"access.im",
+"action-emails.ru",
+"ad4.us",
+"adf.ly",
+"adjix.com",
+"adsbeta.net",
+"ad.vu",
+"afx.cc",
+"a.gg",
+"ah.ae",
+"aipro.ru",
+"airs.ru",
+"aka-url.com",
+"alic.at",
+"all.fuseurl.com",
+"allshort.ru",
+"all-top.ru",
+"alturl.com",
+"a.md",
+"amzn.to",
+"a.nf",
+"apeurl.com",
+"api.m3653.net",
+"apsense.cc",
+"apu.sh",
+"ar.gy",
+"arm.in",
+"arst.ch",
+"atiny.me",
+"atto.co.za",
+"atu.ca",
+"autodesk.com",
+"avast.com",
+"avoo.net",
+"azc.cc",
+"b23.ru",
+"b2l.me",
+"backupurl.com",
+"bacn.me",
+"bai.lu",
+"bcool.bz",
+"bezurl.com",
+"bi.gl",
+"binged.it",
+"bin.nu",
+"bitby.net",
+"bit.do",
+"bit.gy",
+"bit.ly",
+"bitleyco.cc",
+"bitly.com",
+"bitrix24.ru",
+"biturl.net",
+"bit.uz",
+"bizj.us",
+"bloat.me",
+"bmu.li",
+"boi.re",
+"bq.ro",
+"bravo.ly",
+"briefurl.pl",
+"bsa.ly",
+"bsndsy.ru",
+"budurl.com",
+"bun.ru",
+"bu.tt",
+"byst.ro",
+"byyb.net",
+"bz9.com",
+"campaign-services.directcrm.ru",
+"canurl.com",
+"capello.linkatty.com",
+"capourl.com",
+"care2share.tk",
+"cbs.so",
+"cbuz.com",
+"cctv.ws",
+"cd.vg",
+"cektkp.com",
+"cha.la",
+"chilp.it",
+"chzb.gr",
+"cjb.net",
+"cjt99.tk",
+"clck.ru",
+"cliccami.info",
+"click2.info",
+"clicks.biletix.ru",
+"clicks.citilink.ru",
+"click.email4customers.com",
+"click.emailinfo.mail.hpe.com",
+"click.icptrack.com",
+"click.mlsend.com",
+"click-me.us",
+"clickthru.ca",
+"clickv.tk",
+"cli.gs",
+"clkit.co",
+"cl.lk",
+"cl.ly",
+"clme.ru",
+"cloakreferer.com",
+"clockurl.com",
+"clop.in",
+"cms.im",
+"cmylink.com",
+"cnect.us",
+"comyonet.com",
+"conta.cc",
+"coolestone.com",
+"cort.as",
+"cortas.elpais.com",
+"cot.ag",
+"cowurl.com",
+"cp.bitrix.ru",
+"cr.am",
+"createurl.com",
+"crks.me",
+"crlf.ru",
+"crop.im",
+"crum.pl",
+"ctvr.us",
+"cug.kr",
+"cut4.me",
+"cut.by",
+"cuthut.com",
+"cutt.us",
+"d2u.us",
+"d8z.ru",
+"dai.ly",
+"da.lc",
+"ddp.net",
+"decenturl.com",
+"delivr.com",
+"dev0.ru",
+"dft.ba",
+"digbig.com",
+"di.gd",
+"digg.com",
+"digidns.net",
+"din.gy",
+"directtrafficlink.com",
+"disq.us",
+"dld.bz",
+"dlvr.it",
+"dmanalytics1.com",
+"doiop.com",
+"do.my",
+"dopen.us",
+"dot.tk",
+"dr2.biz",
+"driz.ru",
+"dr.tl",
+"durlz.info",
+"easyuri.com",
+"easyurl.jp",
+"easyurl.net",
+"eepurl.com",
+"ej.uz",
+"elurl.com",
+"email.account.2gis.com",
+"email.mail.ostrovok.ru",
+"email.mxtoolbox.com",
+"email.news.ostrovok.ru",
+"e.mail.ru",
+"em.digium.com",
+"emap.ws",
+"emlstart.com",
+"etdurl.com",
+"eweri.com",
+"exa.im",
+"f1ru.net",
+"fa.by",
+"fanta.linkatty.com",
+"fav.me",
+"fbi.pp.ua",
+"fb.me",
+"fbshare.me",
+"fff.to",
+"ff.im",
+"ffs.cc",
+"fi.gd",
+"fire.to",
+"firsturl.de",
+"firsturl.net",
+"fishurl.ru",
+"flane.info",
+"flavr.be",
+"flic.kr",
+"flq.us",
+"flx.im",
+"fly2.ws",
+"folo.me",
+"fo.my",
+"fon.gs",
+"forex-trade.be",
+"fqav.com",
+"freak.to",
+"freepl.us",
+"free-redirect.tk",
+"freeurl.me",
+"free-url-redirection.com.ru",
+"fur.ly",
+"fuseurl.com",
+"fuzzy.to",
+"fwd4.me",
+"fwds.me",
+"fwib.net",
+"fyad.org",
+"fyn.im",
+"g00.me",
+"gadaf.fi",
+"game-url.com",
+"gentleurl.net",
+"geteml.com",
+"getlink.info",
+"get.sh",
+"get.tf",
+"gho.co",
+"gig140.com",
+"gizmo.do",
+"gl.am",
+"glink.co",
+"gltw.ru",
+"gmetzner.de",
+"gmy.su",
+"gnu.su",
+"go2-url.com",
+"go.9nl.com",
+"go9.us",
+"goandgrab.info",
+"gog.tc",
+"go.it",
+"go-links.net",
+"golook.at",
+"go.ly",
+"good.ly",
+"goo.gl",
+"goo.pm",
+"go.qb.by",
+"goshrink.com",
+"gosite.in",
+"goto.pattayacitythailand.com",
+"gourl.ca",
+"gourl.gr",
+"gourl.it",
+"go-url.ru",
+"go.usa.gov",
+"gri.bz",
+"groteck.com",
+"g.ro.lt",
+"gtgg.us",
+"g.ua",
+"gu.ma",
+"gurl.es",
+"haqm.com",
+"hex.io",
+"hhvx.com",
+"hiderefer.com",
+"hijw.com",
+"hi.kg",
+"hit.kg",
+"hj.to",
+"hlurl.com",
+"hmm.ph",
+"ho.io",
+"hop.clickbank.net",
+"hopclicks.com",
+"ho.pe",
+"hop.kz",
+"href.in",
+"hsblinks.com",
+"htxt.it",
+"hubb.me",
+"huff.to",
+"hulu.com",
+"hurl.me",
+"hurl.ws",
+"huuk.net",
+"hvmnd.org",
+"i2h.de",
+"i5.be",
+"icanhaz.com",
+"idek.net",
+"idelink.com",
+"ifree.kz",
+"ih3.ru",
+"ikeafamilynews.ru",
+"ilix.in",
+"ilnk.me",
+"informer.ru",
+"innogam.es",
+"ino.me",
+"int.kz",
+"ipsha.ru",
+"ir.pe",
+"is.gd",
+"is.gs",
+"issuu.com",
+"itshrunk.com",
+"its.my",
+"iurlz.com",
+"ix.lt",
+"ixr.be",
+"j3w.it",
+"ja.cx",
+"jdem.cz",
+"jewi.sh",
+"jijr.com",
+"jmb.tw",
+"j.mp",
+"jom.la",
+"joo.ru",
+"just.as",
+"juu.cc",
+"keep2.me",
+"kickurl.com",
+"kipq.com",
+"kisaurl.com",
+"ki.tl",
+"kl.am",
+"klck.me",
+"klik.sihitam.com",
+"klx.co",
+"knb.im",
+"kon.tl",
+"kore.us",
+"korta.nu",
+"kqon.com",
+"kr1n.ru",
+"krunchd.com",
+"krz.ch",
+"ktzr.us",
+"l24.cm",
+"l3ss.me",
+"l9k.net",
+"lat.ms",
+"lavvs.com",
+"lcut.us",
+"leeturl.net",
+"leto.tk",
+"liip.to",
+"liltext.com",
+"lin.io",
+"link2me.ru",
+"link.ac",
+"linkbee.com",
+"linkbun.ch",
+"linkcash.biz",
+"linkde.info",
+"linkee.com",
+"link.from.homecredit.ru",
+"link.hhut.ru",
+"linkl.ru",
+"link.mail.1fd-system.ru",
+"link.mail.e-gazeta-unp.ru",
+"link.mail.e.glavbukh-mail.ru",
+"link.mail.fd-online.ru",
+"link.mail.glavbukh-mail.ru",
+"link.mail.unp-client.ru",
+"link.rengo.ru",
+"link.sendsay.ru",
+"linkslash.ca",
+"linkunion.de",
+"linkx.me",
+"linkyy.com",
+"linkzip.net",
+"lip.tc",
+"li.ru",
+"list-manage1.com",
+"list-manage2.com",
+"list-manage.com",
+"little.im",
+"littleurl.net",
+"liurl.cn",
+"livehoster.org",
+"llinks.net",
+"ln0.ru",
+"ln4.me",
+"lnk.by",
+"lnk.cm",
+"lnk.co",
+"lnk.gd",
+"lnk.in",
+"lnk.ly",
+"lnk.ms",
+"lnk.sk",
+"lnkd.in",
+"lnks.gd",
+"lnks.it",
+"lnkstts.com",
+"lnkurl.com",
+"ln-s.net",
+"ln-s.ru",
+"loh.ru",
+"loo.gl",
+"lovebyt.es",
+"low.cc",
+"l.pr",
+"lr.tc",
+"lru.jp",
+"lrwk.com",
+"ltos.ru",
+"lt.tl",
+"lul.es",
+"lurl.no",
+"lx2.net",
+"ly9.net",
+"m4u.in",
+"m7a.org",
+"macte.ch",
+"mail.rambler.ru",
+"mandrillapp.com",
+"mash.to",
+"mee.la",
+"merky.de",
+"metamark.net",
+"micurl.com",
+"migre.me",
+"miliuner.com",
+"miniurl.com",
+"miniurl.net.ru",
+"miniurl.pl",
+"mimecast.com",
+"minu.me",
+"minurl.fr",
+"minyurl.net",
+"minyurl.org",
+"mislead.in",
+"miud.in",
+"mixe.me",
+"mj.is",
+"mjt.lu",
+"mke.me",
+"mlcampaignru.com",
+"mljt.tech",
+"mlsendru.com",
+"mmt.su",
+"mobotix-news.com",
+"mo.by",
+"moby.to",
+"mockurl.com",
+"moourl.com",
+"mp77.com",
+"mrte.ch",
+"mtp.pl",
+"mty.in",
+"mug.gs",
+"murl.kz",
+"mvp.im",
+"mylink4u.info",
+"mylink.to",
+"myloc.me",
+"myooo.info",
+"mypaqe.com",
+"mypl.us",
+"mytinyurl.net",
+"myurl.in",
+"myurl.si",
+"myxx.me",
+"mzan.si",
+"n3n.in",
+"n3r.ru",
+"nbc.co",
+"nblo.gs",
+"nbold.com",
+"ne1.net",
+"netgod.tk",
+"neuf.tk",
+"newhotlink.com",
+"nexturl.ru",
+"nicesharing.com",
+"nik.im",
+"niurl.com",
+"nl.cr",
+"nn.nf",
+"no1.in",
+"no.io",
+"nonameno.com",
+"normalurl.com",
+"notlong.com",
+"not.my",
+"now.am",
+"n.pr",
+"nsfw.in",
+"nutshellurl.com",
+"nxy.in",
+"nyti.ms",
+"oc1.us",
+"oeeq.com",
+"oiurl.com",
+"o.ly",
+"omf.gd",
+"om.ly",
+"omoikane.net",
+"on.cnn.com",
+"on.mktw.net",
+"oogyah.com",
+"oork.com",
+"opurl.us",
+"orbita.co.il",
+"orz.se",
+"ourgplus.at",
+"out.houseofgaga.ru",
+"ovr.me",
+"ow.ly",
+"o-x.fr",
+"p1.fr",
+"pathto.net",
+"paypal-communication.com",
+"pb8.ru",
+"pburl.com",
+"pcw.ro",
+"pduda.mobi",
+"pechkincensor.ru",
+"pechkinspy.ru",
+"peeep.us",
+"peekurl.com",
+"pendek.in",
+"penting.web.id",
+"pfat.de",
+"pho.se",
+"phpm.ru",
+"php-ru.info",
+"pi90.com",
+"picourl.ru",
+"piks.nl",
+"ping.fm",
+"pli.gs",
+"plink.es",
+"plo.cc",
+"ploshadka.ru",
+"plugin.name",
+"plusphp.com",
+"p.ly",
+"pnt.me",
+"pobierz-film.tk",
+"politi.co",
+"ponyurl.com",
+"poo.pr",
+"post.ly",
+"pot.vg",
+"pp.gg",
+"ppt.cc",
+"pra.im",
+"privacy-surf.com",
+"proext.com",
+"professionali.ru",
+"profile.to",
+"proofpoint.com",
+"prourl.de",
+"ptiturl.com",
+"p.tl",
+"pub.vitrue.com",
+"punyurl.com",
+"purl.org",
+"pvh.me",
+"pw.ly",
+"pxlz.org",
+"py6.ru",
+"pygmyurl.com",
+"pysper.com",
+"q32.ru",
+"qick.ws",
+"qid.in",
+"qkr.cc",
+"qlnk.net",
+"qlql.ru",
+"qnh.pl",
+"qr.cx",
+"qr.ee",
+"qrf.in",
+"qr.net",
+"qru.ru",
+"qte.me",
+"qtwk.com",
+"quik.in",
+"qurl.com",
+"qu.tc",
+"qz.bz",
+"rb6.me",
+"r.delphicomponent.ru",
+"rdrct.us",
+"rdr.to",
+"read.bi",
+"readthis.ca",
+"reallytinyurl.com",
+"redir.ec",
+"redirectingat.com",
+"redirects.ca",
+"redirect.subscribe.ru",
+"redire.ru",
+"redirx.com",
+"reduc.in",
+"referer.us",
+"retweet.cc",
+"retwt.me",
+"reurl.org",
+"rhwm.eu",
+"rickroll.it",
+"r.im",
+"ri.ms",
+"riz.gd",
+"rlu.ru",
+"rmse.ru",
+"rnd.ru",
+"romb.su",
+"r-ss.de",
+"rt.nu",
+"rubyurl.com",
+"ru.ly",
+"rurl.org",
+"rurl.ru",
+"rurls.ru",
+"rustech.org",
+"rww.tw",
+"s0bu.ru",
+"s0e.ru",
+"s4c.in",
+"s7y.us",
+"s8.hk",
+"safe.mn",
+"saf.li",
+"safelinks.protection.outlook.com",
+"sami.24.gg",
+"sayabit.com",
+"sbrf.link.info.sberbank.ru",
+"s.coop",
+"scurtare-url.hi2.ro",
+"sdut.us",
+"securityexpert.ru",
+"securl.ru",
+"sendgrid.net",
+"sendit.in",
+"sendsay.ru",
+"sendurl.info",
+"sg4d.com",
+"sg-url.tk",
+"shadyurl.com",
+"share.flocktory.com",
+"shar.es",
+"shink.de",
+"shiturl.com",
+"shli.de",
+"shorl.com",
+"shortb.net",
+"shorten.im",
+"shorten.ws",
+"short.ie",
+"short.im",
+"shortlinks.co.uk",
+"short-me.com",
+"shortner.com",
+"shortn.me",
+"short.nr",
+"short.su",
+"short.to",
+"shorturl.asia",
+"shorturl.com",
+"short-url.co.uk",
+"shorturls.co.uk",
+"shortz.me",
+"shout.to",
+"show.my",
+"shrinkee.com",
+"shrinkify.com",
+"shrinkr.com",
+"shrten.com",
+"shrt.fr",
+"shrtl.com",
+"shrt.st",
+"shrunkin.com",
+"shx.in",
+"simplesite.com",
+"simurl.com",
+"sitefwd.com",
+"sk9.pl",
+"slate.me",
+"sli.su",
+"slki.ru",
+"sl.to",
+"smallr.com",
+"smallurl.in",
+"smallurl.ru",
+"smalur.com",
+"s-m.co",
+"smsh.me",
+"smurl.ca",
+"smurl.name",
+"sn9.ru",
+"sn.im",
+"snipr.com",
+"snipurl.com",
+"snurl.com",
+"softlinemail.ru",
+"sokrati.ru",
+"somexyz.com",
+"song.ly",
+"sorturl.net",
+"so.vg",
+"sp2.ro",
+"spedr.com",
+"speed-tester.info",
+"sq6.ru",
+"srclick.ru",
+"srcom.net",
+"srnk.net",
+"srs.li",
+"srtn.me",
+"starts.com",
+"starturl.com",
+"stat-pulse.com",
+"stnx.at",
+"stump.ws",
+"su.ly",
+"su.pr",
+"surl.co.uk",
+"surl.hu",
+"surl.me",
+"surs.nl",
+"susurl.com",
+"swturl.com",
+"t1ny.net",
+"ta.gd",
+"ta.gg",
+"tagiturl.com",
+"taourl.com",
+"tbd.ly",
+"t.cn",
+"t.co",
+"tcrn.ch",
+"techto.us",
+"tez.co",
+"tgl.net",
+"tgr.me",
+"tgr.ph",
+"th8.us",
+"thexyz.org",
+"thinfi.com",
+"th.ly",
+"thnlnk.com",
+"thurl.in",
+"tie.ly",
+"tighturl.com",
+"tin.cc",
+"tiniuri.com",
+"tiny9.com",
+"tinyarro.ws",
+"tiny.by",
+"tiny.cc",
+"tinyit.cc",
+"tinylink.ca",
+"tinylink.in",
+"tiny.ly",
+"tiny.pl",
+"tiny.ps",
+"tinyuri.ca",
+"tinyurl.com",
+"tinyurl.ru",
+"tki.me",
+"tldr.in",
+"tl.gd",
+"t.lh.com",
+"tlim.ru",
+"tllg.net",
+"tmi.me",
+"tnij.org",
+"tny.com",
+"tny.tc",
+"to8.cc",
+"togoto.us",
+"to.je",
+"to.ly",
+"toma.ai",
+"tos.co",
+"totc.us",
+"tourl.fr",
+"toysr.us",
+"tozm.com",
+"tpm.ly",
+"tra.kz",
+"track-mail.skbkontur.ru",
+"trg.li",
+"trii.us",
+"tr.im",
+"trimurl.me",
+"trk.emlbest.com",
+"trk.klclick.com",
+"trk.klclick1.com",
+"trk.klclick2.com",
+"trk.klclick3.com",
+"trunc.it",
+"trusteml.com",
+"tty.su",
+"tuckinfo.com",
+"tux-pla.net",
+"tvsl.eu",
+"tweet.ms",
+"tweez.me",
+"twhub.com",
+"twirl.at",
+"twitclicks.com",
+"twitter.com",
+"twitterurl.net",
+"twiturl.de",
+"twiu.ru",
+"twurl.cc",
+"twurl.nl",
+"tyny.me",
+"u2s.in",
+"u76.org",
+"ub0.cc",
+"u.info-mg.ru",
+"ukril.ru",
+"ulk.me",
+"ulmart.ru",
+"ulo.me",
+"ulu.lu",
+"u.mavrev.com",
+"umenytt.se",
+"unfake.it",
+"u.nu",
+"updating.me",
+"ur1.ca",
+"ur3.us",
+"ural-tender.ru",
+"url2.ru",
+"url360.me",
+"url3.ru",
+"url4.eu",
+"url4.ru",
+"url4t.com",
+"url5.ru",
+"url66.com",
+"urla.ru",
+"url.az",
+"url.b24.am",
+"urlbit.us",
+"urlborg.com",
+"urlbrief.com",
+"urlcantik.com",
+"urlclick.ru",
+"url.cn",
+"urlcorta.es",
+"url.co.uk",
+"urlcover.com",
+"urlcut.com",
+"urldefender.com",
+"urldefense.com",
+"urldepo.ru",
+"url.dflix.net",
+"urlel.com",
+"urlenco.de",
+"urle.us",
+"url.g4team.com",
+"urlgator.com",
+"urlgeo.me",
+"urlg.in",
+"url-go.com",
+"urlgo.ru",
+"url.ie",
+"urlin.it",
+"urlink.eu",
+"urli.nl",
+"urlite.de",
+"url.lotpatrol.com",
+"urlmint.com",
+"url.mk.ua",
+"urlms.com",
+"urloid.com",
+"urloo.com",
+"urlot.com",
+"urlredo.com",
+"urlscott.com",
+"urls.co.za",
+"url.shinri.biz",
+"urlshorteningservicefortwitter.com",
+"urlshort.me",
+"urls.im",
+"urlsim.com",
+"urlsnip.com",
+"urlsp.in",
+"urlsqueeze.com",
+"urls.vg",
+"urltwit.com",
+"urlu.ms",
+"urlus.ru",
+"url.vsofte.ru",
+"urlx.ie",
+"urlxs.fr",
+"ur.ly",
+"url.yanclex.com",
+"urlz.at",
+"urlzen.com",
+"url-zip.com",
+"urlz.ro",
+"usat.ly",
+"use.my",
+"u.to",
+"uud.in",
+"uuu.su",
+"uww.me",
+"uyurl.com",
+"vani.sh",
+"vash-repetitor.ru",
+"vb.ly",
+"vc8.net",
+"vgn.am",
+"view.my",
+"vk.cc",
+"vl.am",
+"vll.me",
+"vog.me",
+"vovka.com",
+"vst.tv",
+"w3t.org",
+"w55.de",
+"wa.la",
+"wapo.st",
+"wapurl.co.uk",
+"warble.co",
+"webformyself.com",
+"weblist.kharkov.ua",
+"weburl.me",
+"weeclix.com",
+"wez.su",
+"whspr.it",
+"widg.me",
+"wik.ro",
+"wipi.es",
+"wlatcy-moch.tk",
+"wlink.me.uk",
+"wmturls.com",
+"wom.im",
+"wowurl.com",
+"wp.me",
+"wp.nu",
+"wurl.in",
+"wurl.us",
+"wuw.su",
+"ww.tl",
+"clickmeeting.com",
+"grandstreamnetworks.ru",
+"reg.ru",
+"wz.ae",
+"x2t.com",
+"xaa.su",
+"xav.cc",
+"xcqv.com",
+"xcs.me",
+"xd5.net",
+"xdvn.net",
+"xew.co",
+"xops.fr",
+"xp.cm",
+"xr.com",
+"xrl.in",
+"xrls.tk",
+"xrl.us",
+"x-url.com",
+"xurl.es",
+"xurl.jp",
+"x.vu",
+"xvx.su",
+"xw6.com",
+"xxsurl.de",
+"xxw.me",
+"y.ahoo.it",
+"yatuc.com",
+"ydn.ru",
+"ye.pe",
+"yep.it",
+"yfrog.com",
+"yhoo.it",
+"yi.pe",
+"yiyd.com",
+"ysu.me",
+"yuarel.com",
+"yurl.in",
+"y-url.ru",
+"yurl.ru",
+"z0p.de",
+"z2z.ca",
+"zapt.in",
+"zazi.me",
+"zcom.us",
+"zebratelecom.ru",
+"zeep.in",
+"zi.ma",
+"zi.mu",
+"zio.in",
+"zipmyurl.com",
+"zolp.net",
+"zrps.info",
+"zti.me",
+"zud.me",
+"zurl.ws",
+"zxc9.com",
+"zzang.kr",
+"zz.gd"}
+
+
diff --git a/resources/config/spamfilter/maps/allow_dmarc.list b/resources/config/spamfilter/maps/allow_dmarc.list
index 4943453b..7a973841 100644
--- a/resources/config/spamfilter/maps/allow_dmarc.list
+++ b/resources/config/spamfilter/maps/allow_dmarc.list
@@ -26,7 +26,6 @@ spam-dmarc = {"18f.gov",
"adp.com",
"advice.hmrc.gov.uk",
"aerocivil.gov.co",
-"aerocivil.gov.co",
"afreximbank.com",
"agingstats.gov",
"agro.ru",
diff --git a/resources/config/spamfilter/maps/allow_spf_dkim.list b/resources/config/spamfilter/maps/allow_spf_dkim.list
index 163593e3..25628cdc 100644
--- a/resources/config/spamfilter/maps/allow_spf_dkim.list
+++ b/resources/config/spamfilter/maps/allow_spf_dkim.list
@@ -150,7 +150,6 @@ spam-spdk = {"1cfresh.com",
"paypal.ca",
"paypal.cn",
"paypal.com",
-"paypal.com",
"paypal.co.uk",
"paypal.de",
"paypal.es",
diff --git a/resources/config/spamfilter/maps/spam_trap.list b/resources/config/spamfilter/maps/spam_trap.list
deleted file mode 100644
index cf390a38..00000000
--- a/resources/config/spamfilter/maps/spam_trap.list
+++ /dev/null
@@ -1 +0,0 @@
-spam-trap = {}
diff --git a/resources/config/spamfilter/maps/suffix_list.dat.gz b/resources/config/spamfilter/maps/suffix_list.dat.gz
deleted file mode 100644
index c916d9de..00000000
--- a/resources/config/spamfilter/maps/suffix_list.dat.gz
+++ /dev/null
Binary files differ
diff --git a/resources/config/spamfilter/scripts/config.sieve b/resources/config/spamfilter/scripts/config.sieve
index 29467958..6499f41d 100644
--- a/resources/config/spamfilter/scripts/config.sieve
+++ b/resources/config/spamfilter/scripts/config.sieve
@@ -1,35 +1,35 @@
# Whether to add an X-Spam-Status header
-let "ADD_HEADER_SPAM" "%{cfg:spam.header.add-spam}%";
+let "ADD_HEADER_SPAM" "key_get('spam-config', 'add-spam')";
# Whether to add an X-Spam-Result header
-let "ADD_HEADER_SPAM_RESULT" "%{cfg:spam.header.add-spam-result}%";
+let "ADD_HEADER_SPAM_RESULT" "key_get('spam-config', 'add-spam-result')";
# Whether message replies from authenticated users should be learned as ham
-let "AUTOLEARN_REPLIES_HAM" "%{cfg:spam.autolearn.ham.replies}%";
+let "AUTOLEARN_REPLIES_HAM" "key_get('spam-config', 'learn-ham-replies')";
# Whether the bayes classifier should be trained automatically
-let "AUTOLEARN_ENABLE" "%{cfg:spam.autolearn.enable}%";
+let "AUTOLEARN_ENABLE" "key_get('spam-config', 'learn-enable')";
# When to learn ham (score >= threshold)
-let "AUTOLEARN_HAM_THRESHOLD" "%{cfg:spam.autolearn.ham.threshold}%";
+let "AUTOLEARN_HAM_THRESHOLD" "key_get('spam-config', 'learn-ham-threshold')";
# When to learn spam (score <= threshold)
-let "AUTOLEARN_SPAM_THRESHOLD" "%{cfg:spam.autolearn.spam.threshold}%";
+let "AUTOLEARN_SPAM_THRESHOLD" "key_get('spam-config', 'learn-spam-threshold')";
# Keep difference for spam/ham learns for at least this value
-let "AUTOLEARN_SPAM_HAM_BALANCE" "%{cfg:spam.autolearn.balance}%";
+let "AUTOLEARN_SPAM_HAM_BALANCE" "key_get('spam-config', 'learn-balance')";
# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
-let "SCORE_SPAM_THRESHOLD" "%{cfg:spam.threshold.spam}%";
+let "SCORE_SPAM_THRESHOLD" "key_get('spam-config', 'threshold-spam')";
# Discard messages with a score above this threshold
-let "SCORE_DISCARD_THRESHOLD" "%{cfg:spam.threshold.discard}%";
+let "SCORE_DISCARD_THRESHOLD" "key_get('spam-config', 'threshold-discard')";
# Reject messages with a score above this threshold
-let "SCORE_REJECT_THRESHOLD" "%{cfg:spam.threshold.reject}%";
+let "SCORE_REJECT_THRESHOLD" "key_get('spam-config', 'threshold-reject')";
# Directory name to use for local domain lookups (leave empty for default)
-let "DOMAIN_DIRECTORY" "%{cfg:spam.data.directory}%";
+let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
# Store to use for Bayes tokens and ids (leave empty for default)
-let "SPAM_DB" "%{cfg:spam.data.lookup}%";
+let "SPAM_DB" "key_get('spam-config', 'lookup')";
diff --git a/resources/config/spamfilter/settings.toml b/resources/config/spamfilter/settings.toml
new file mode 100644
index 00000000..f082c270
--- /dev/null
+++ b/resources/config/spamfilter/settings.toml
@@ -0,0 +1,20 @@
+[spam.header]
+is-spam = "X-Spam-Status: Yes"
+
+[lookup.spam-config]
+add-spam = true
+add-spam-result = true
+learn-enable = true
+learn-balance = "0.9"
+learn-ham-replies = true
+learn-ham-threshold = "-0.5"
+learn-spam-threshold = "6.0"
+threshold-spam = "5.0"
+threshold-discard = "0.0"
+threshold-reject = "0.0"
+directory = ""
+lookup = ""
+
+[session.data]
+script = [ { if = "is_empty(authenticated_as)", then = "'spam-filter'"},
+ { else = "'track-replies'" } ]
diff --git a/resources/config/store/elasticsearch.toml b/resources/config/store/elasticsearch.toml
deleted file mode 100644
index f5f93bce..00000000
--- a/resources/config/store/elasticsearch.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# ElasticSearch FTS Store configuration
-#############################################
-
-[store."elasticsearch"]
-type = "elasticsearch"
-url = "https://localhost:9200"
-user = "elastic"
-password = "myelasticpassword"
-#cloud-id = "my-cloud-id"
-disable = true
-
-[store."elasticsearch".tls]
-allow-invalid-certs = true
-
-[store."elasticsearch".index]
-shards = 3
-replicas = 0
diff --git a/resources/config/store/foundationdb.toml b/resources/config/store/foundationdb.toml
deleted file mode 100644
index b52705c8..00000000
--- a/resources/config/store/foundationdb.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-#############################################
-# FoundationDB Store configuration
-#############################################
-
-[store."foundationdb"]
-type = "foundationdb"
-#cluster-file = "/etc/foundationdb/fdb.cluster"
-disable = true
-
-#[store."foundationdb".transaction]
-#timeout = "5s"
-#retry-limit = 10
-#max-retry-delay = "1s"
-
-#[store."foundationdb".ids]
-#machine = "stalwart"
-#data-center = "my-datacenter"
-
-[store."foundationdb".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/fs.toml b/resources/config/store/fs.toml
deleted file mode 100644
index db6d38cd..00000000
--- a/resources/config/store/fs.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-#############################################
-# File System Blob Store configuration
-#############################################
-
-[store."fs"]
-type = "fs"
-path = "%{BASE_PATH}%/data/blobs"
-depth = 2
-disable = true
-
-[store."fs".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/mysql.toml b/resources/config/store/mysql.toml
deleted file mode 100644
index b0fa130d..00000000
--- a/resources/config/store/mysql.toml
+++ /dev/null
@@ -1,37 +0,0 @@
-#############################################
-# MySQL Store configuration
-#############################################
-
-[store."mysql"]
-type = "mysql"
-host = "localhost"
-port = 3307
-database = "stalwart"
-user = "root"
-password = "password"
-disable = true
-#max-allowed-packet = 1073741824
-timeout = "15s"
-
-#[store."mysql".pool]
-#max-connections = 10
-#min-connections = 5
-
-#[store."mysql".init]
-#execute = [
-# "CREATE TABLE IF NOT EXISTS accounts (name VARCHAR(32) PRIMARY KEY, secret VARCHAR(1024), description VARCHAR(1024), type VARCHAR(32) NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
-# "CREATE TABLE IF NOT EXISTS group_members (name VARCHAR(32) NOT NULL, member_of VARCHAR(32) NOT NULL, PRIMARY KEY (name, member_of))",
-# "CREATE TABLE IF NOT EXISTS emails (name VARCHAR(32) NOT NULL, address VARCHAR(128) NOT NULL, type VARCHAR(32), PRIMARY KEY (name, address))"
-#]
-
-[store."mysql".query]
-name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
-members = "SELECT member_of FROM group_members WHERE name = ?"
-recipients = "SELECT name FROM emails WHERE address = ? ORDER BY name ASC"
-emails = "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
-verify = "SELECT address FROM emails WHERE address LIKE CONCAT('%', ?, '%') AND type = 'primary' ORDER BY address LIMIT 5"
-expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
-domains = "SELECT 1 FROM emails WHERE address LIKE CONCAT('%@', ?) LIMIT 1"
-
-[store."mysql".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/postgresql.toml b/resources/config/store/postgresql.toml
deleted file mode 100644
index bf0d2fef..00000000
--- a/resources/config/store/postgresql.toml
+++ /dev/null
@@ -1,39 +0,0 @@
-#############################################
-# PostgreSQL Store configuration
-#############################################
-
-[store."postgresql"]
-type = "postgresql"
-host = "localhost"
-port = 5432
-database = "stalwart"
-user = "postgres"
-password = "mysecretpassword"
-timeout = "15s"
-disable = true
-
-[store."postgresql".tls]
-enable = false
-allow-invalid-certs = false
-
-#[store."postgresql".pool]
-#max-connections = 10
-
-#[store."postgresql".init]
-#execute = [
-# "CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
-# "CREATE TABLE IF NOT EXISTS group_members (name TEXT NOT NULL, member_of TEXT NOT NULL, PRIMARY KEY (name, member_of))",
-# "CREATE TABLE IF NOT EXISTS emails (name TEXT NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (name, address))"
-#]
-
-[store."postgresql".query]
-name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = $1 AND active = true"
-members = "SELECT member_of FROM group_members WHERE name = $1"
-recipients = "SELECT name FROM emails WHERE address = $1 ORDER BY name ASC"
-emails = "SELECT address FROM emails WHERE name = $1 AND type != 'list' ORDER BY type DESC, address ASC"
-verify = "SELECT address FROM emails WHERE address LIKE '%' || $1 || '%' AND type = 'primary' ORDER BY address LIMIT 5"
-expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = $1 AND l.type = 'list' ORDER BY p.address LIMIT 50"
-domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || $1 LIMIT 1"
-
-[store."postgresql".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/redis.toml b/resources/config/store/redis.toml
deleted file mode 100644
index 92f741b8..00000000
--- a/resources/config/store/redis.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# Redis Lookup Store configuration
-#############################################
-
-[store."redis"]
-type = "redis"
-redis-type = "single"
-urls = ["redis://127.0.0.1"]
-user = "my_username"
-password = "secretpassword"
-timeout = "10s"
-#read-from-replicas = false
-disable = true
-
-#[store."redis".retry]
-#total = 3
-#max-wait = "1s"
-#min-wait = "500ms"
diff --git a/resources/config/store/rocksdb.toml b/resources/config/store/rocksdb.toml
deleted file mode 100644
index f2d928f5..00000000
--- a/resources/config/store/rocksdb.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# RocksDB Store configuration
-#############################################
-
-[store."rocksdb"]
-type = "rocksdb"
-path = "%{BASE_PATH}%/data"
-disable = true
-
-[store."rocksdb".settings]
-min-blob-size = 16834
-write-buffer-size = 134217728
-
-#[store."rocksdb".pool]
-#workers = 10
-
-[store."rocksdb".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/s3.toml b/resources/config/store/s3.toml
deleted file mode 100644
index c8c870c0..00000000
--- a/resources/config/store/s3.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-#############################################
-# S3/MinIO Blob Store configuration
-#############################################
-
-[store."s3"]
-type = "s3"
-bucket = "stalwart"
-region = "eu-central-1"
-access-key = "minioadmin"
-secret-key = "minioadmin"
-#endpoint = ""
-#security-token = ""
-#profile = ""
-timeout = "30s"
-disable = true
-
-[store."s3".purge]
-frequency = "0 3 *"
diff --git a/resources/config/store/sqlite.toml b/resources/config/store/sqlite.toml
deleted file mode 100644
index f1703c2e..00000000
--- a/resources/config/store/sqlite.toml
+++ /dev/null
@@ -1,31 +0,0 @@
-#############################################
-# SQLite Store configuration
-#############################################
-
-[store."sqlite"]
-type = "sqlite"
-path = "%{BASE_PATH}%/data/index.sqlite3"
-disable = true
-
-#[store."sqlite".pool]
-#max-connections = 10
-#workers = 10
-
-#[store."sqlite".init]
-#execute = [
-# "CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
-# "CREATE TABLE IF NOT EXISTS group_members (name TEXT NOT NULL, member_of TEXT NOT NULL, PRIMARY KEY (name, member_of))",
-# "CREATE TABLE IF NOT EXISTS emails (name TEXT NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (name, address))"
-#]
-
-[store."sqlite".query]
-name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
-members = "SELECT member_of FROM group_members WHERE name = ?"
-recipients = "SELECT name FROM emails WHERE address = ?"
-emails = "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
-verify = "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type = 'primary' ORDER BY address LIMIT 5"
-expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
-domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || ? LIMIT 1"
-
-[store."sqlite".purge]
-frequency = "0 3 *"
diff --git a/tests/resources/scripts/create_test_env.sh b/tests/resources/scripts/create_test_env.sh
index 671c8c50..2e1ea094 100644
--- a/tests/resources/scripts/create_test_env.sh
+++ b/tests/resources/scripts/create_test_env.sh
@@ -2,66 +2,28 @@
BASE_DIR="/Users/me/Downloads/stalwart-test"
DOMAIN="example.org"
-
-# Stores
-#STORE="foundationdb"
-#FTS_STORE="foundationdb"
-#BLOB_STORE="foundationdb"
-#STORE="rocksdb"
-#FTS_STORE="rocksdb"
-#BLOB_STORE="rocksdb"
-STORE="sqlite"
-FTS_STORE="sqlite"
-BLOB_STORE="sqlite"
-#FEATURES="foundationdb postgres mysql rocks elastic s3 redis"
-FEATURES="sqlite"
-
-# Directories
-DIRECTORY="internal"
-SQL_STORE="sqlite"
+FEATURES="sqlite foundationdb postgres mysql rocks elastic s3 redis"
# Delete previous tests
rm -rf $BASE_DIR
# Create directories
-mkdir -p $BASE_DIR $BASE_DIR/data $BASE_DIR/data/blobs $BASE_DIR/logs
-
-# Copy config files
-cp -r resources/config $BASE_DIR/etc
+mkdir -p $BASE_DIR $BASE_DIR/data $BASE_DIR/etc
-# Copy self-signed certs
-cp -r tests/resources/tls_cert.pem $BASE_DIR/etc
-cp -r tests/resources/tls_privatekey.pem $BASE_DIR/etc
-
-# Replace stores and directories
-sed -i '' -e "s|__SQL_STORE__|$SQL_STORE|g" "$BASE_DIR/etc/directory/sql.toml"
-sed -i '' -e 's/disable = true//g' "$BASE_DIR/etc/directory/$DIRECTORY.toml"
-sed -i '' -e 's/disable = true//g' "$BASE_DIR/etc/store/$STORE.toml"
-sed -i '' -e 's/disable = true//g' "$BASE_DIR/etc/store/$FTS_STORE.toml"
-sed -i '' -e 's/disable = true//g' "$BASE_DIR/etc/store/$BLOB_STORE.toml"
-sed -i '' -e "s/__FTS_STORE__/$FTS_STORE/g" \
- -e "s/__BLOB_STORE__/$BLOB_STORE/g" "$BASE_DIR/etc/common/store.toml"
+# Copy resources
+cp -r resources/config/config.toml $BASE_DIR/etc
# Replace settings
-sed -i '' -e "s/__STORE__/$STORE/g" \
- -e "s/__DIRECTORY__/$DIRECTORY/g" \
- -e "s/__DOMAIN__/$DOMAIN/g" \
- -e "s/__HOST__/mail.$DOMAIN/g" \
- -e "s|__BASE_PATH__|$BASE_DIR|g" "$BASE_DIR/etc/config.toml"
-sed -i '' -e "s|__CERT_PATH__|$BASE_DIR/etc/tls_cert.pem|g" \
- -e "s|__PK_PATH__|$BASE_DIR/etc/tls_privatekey.pem|g" "$BASE_DIR/etc/common/tls.toml"
-sed -i '' -e 's/method = "log"/method = "stdout"/g' \
- -e 's/level = "info"/level = "trace"/g' "$BASE_DIR/etc/common/tracing.toml"
-sed -i '' -e 's/%{HOST}%/127.0.0.1/g' "$BASE_DIR/etc/jmap/listener.toml"
-sed -i '' -e 's/allow-plain-text = false/allow-plain-text = true/g' \
- -e 's/2000\/1m/9999999\/100m/g' \
- -e 's/concurrent = 4/concurrent = 90000/g' "$BASE_DIR/etc/imap/settings.toml"
-sed -i '' -e 's/user = "stalwart-mail"//g' \
- -e 's/group = "stalwart-mail"//g' "$BASE_DIR/etc/common/server.toml"
-# Generate DKIM key
-mkdir -p $BASE_DIR/etc/dkim
-openssl genpkey -algorithm RSA -out $BASE_DIR/etc/dkim/$DOMAIN.key
+sed -i '' -e "s|%{env:STALWART_PATH}%|$BASE_DIR|g" \
+ -e "s|%{env:DOMAIN}%|$DOMAIN|g" \
+ -e "s|%{env:HOSTNAME}%|mail.$DOMAIN|g" \
+ -e "s|%{env:OAUTH_KEY}%|12345|g" \
+ -e 's/level = "info"/level = "trace"/g' "$BASE_DIR/etc/config.toml"
+
+#sed -i '' -e 's/allow-plain-text = false/allow-plain-text = true/g' \
+# -e 's/2000\/1m/9999999\/100m/g' \
+# -e 's/concurrent = 4/concurrent = 90000/g' "$BASE_DIR/etc/imap/settings.toml"
# Create admin user
SET_ADMIN_USER="admin" SET_ADMIN_PASS="secret" cargo run -p mail-server --no-default-features --features "$FEATURES" -- --config=$BASE_DIR/etc/config.toml
diff --git a/tests/src/lib.rs b/tests/src/lib.rs
index 569ae81a..921c6407 100644
--- a/tests/src/lib.rs
+++ b/tests/src/lib.rs
@@ -70,8 +70,8 @@ impl AssertConfig for utils::config::Config {
}
fn assert_no_warnings(self) -> Self {
- if !self.missing.is_empty() {
- panic!("Warnings: {:#?}", self.missing);
+ if !self.warnings.is_empty() {
+ panic!("Warnings: {:#?}", self.warnings);
}
self
}
diff --git a/tests/src/smtp/config.rs b/tests/src/smtp/config.rs
index 53d89ec2..d198de88 100644
--- a/tests/src/smtp/config.rs
+++ b/tests/src/smtp/config.rs
@@ -63,7 +63,7 @@ fn parse_if_blocks() {
// Create context and add some conditions
- let token_map = TokenMap::default().with_smtp_variables(&[
+ let token_map = TokenMap::default().with_variables(&[
V_RECIPIENT,
V_RECIPIENT_DOMAIN,
V_SENDER,
@@ -83,7 +83,7 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(2),
+ ExpressionItem::Variable(V_SENDER),
ExpressionItem::Constant(Constant::String("jdoe".to_string())),
ExpressionItem::BinaryOperator(BinaryOperator::Eq)
]
@@ -95,12 +95,12 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(10),
+ ExpressionItem::Variable(V_PRIORITY),
ExpressionItem::Constant(Constant::Integer(1)),
ExpressionItem::UnaryOperator(UnaryOperator::Minus),
ExpressionItem::BinaryOperator(BinaryOperator::Eq),
ExpressionItem::JmpIf { val: true, pos: 4 },
- ExpressionItem::Variable(0),
+ ExpressionItem::Variable(V_RECIPIENT),
ExpressionItem::Constant(Constant::String("jane".to_string())),
ExpressionItem::Function {
id: 29,
@@ -128,7 +128,7 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(2),
+ ExpressionItem::Variable(V_SENDER),
ExpressionItem::Constant(Constant::String("jdoe".to_string())),
ExpressionItem::BinaryOperator(BinaryOperator::Eq)
]
@@ -145,12 +145,12 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(10),
+ ExpressionItem::Variable(V_PRIORITY),
ExpressionItem::Constant(Constant::Integer(1)),
ExpressionItem::UnaryOperator(UnaryOperator::Minus),
ExpressionItem::BinaryOperator(BinaryOperator::Eq),
ExpressionItem::JmpIf { val: true, pos: 4 },
- ExpressionItem::Variable(0),
+ ExpressionItem::Variable(V_RECIPIENT),
ExpressionItem::Constant(Constant::String("jane".to_string())),
ExpressionItem::Function {
id: 29,
@@ -180,7 +180,7 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(2),
+ ExpressionItem::Variable(V_SENDER),
ExpressionItem::Constant(Constant::String("jdoe".to_string())),
ExpressionItem::BinaryOperator(BinaryOperator::Eq)
]
@@ -197,12 +197,12 @@ fn parse_if_blocks() {
IfThen {
expr: Expression {
items: vec![
- ExpressionItem::Variable(10),
+ ExpressionItem::Variable(V_PRIORITY),
ExpressionItem::Constant(Constant::Integer(1)),
ExpressionItem::UnaryOperator(UnaryOperator::Minus),
ExpressionItem::BinaryOperator(BinaryOperator::Eq),
ExpressionItem::JmpIf { val: true, pos: 4 },
- ExpressionItem::Variable(0),
+ ExpressionItem::Variable(V_RECIPIENT),
ExpressionItem::Constant(Constant::String("jane".to_string())),
ExpressionItem::Function {
id: 29,
@@ -261,7 +261,7 @@ fn parse_throttles() {
let throttle = parse_throttle(
&mut config,
"throttle",
- &TokenMap::default().with_smtp_variables(&[
+ &TokenMap::default().with_variables(&[
V_RECIPIENT,
V_RECIPIENT_DOMAIN,
V_SENDER,
@@ -415,7 +415,7 @@ async fn eval_if() {
let mut config = Config::new(fs::read_to_string(file).unwrap()).unwrap();
let envelope = TestEnvelope::from_config(&mut config);
- let token_map = TokenMap::default().with_smtp_variables(&[
+ let token_map = TokenMap::default().with_variables(&[
V_RECIPIENT,
V_RECIPIENT_DOMAIN,
V_SENDER,
@@ -464,7 +464,7 @@ async fn eval_dynvalue() {
let mut config = Config::new(fs::read_to_string(file).unwrap()).unwrap();
let envelope = TestEnvelope::from_config(&mut config);
- let token_map = TokenMap::default().with_smtp_variables(&[
+ let token_map = TokenMap::default().with_variables(&[
V_RECIPIENT,
V_RECIPIENT_DOMAIN,
V_SENDER,
diff --git a/tests/src/smtp/inbound/antispam.rs b/tests/src/smtp/inbound/antispam.rs
index 9327223a..2490beaf 100644
--- a/tests/src/smtp/inbound/antispam.rs
+++ b/tests/src/smtp/inbound/antispam.rs
@@ -30,28 +30,20 @@ use crate::smtp::{build_smtp, session::TestSession, TempDir};
const CONFIG: &str = r#"
[spam.header]
-add-spam = true
-add-spam-result = true
is-spam = "X-Spam-Status: Yes"
-[spam.autolearn]
-enable = true
-#balance = 0.9
-balance = 0.0
-
-[spam.autolearn.ham]
-replies = true
-threshold = -0.5
-
-[spam.autolearn.spam]
-threshold = 6.0
-
-[spam.threshold]
-spam = 5.0
-discard = 0
-reject = 0
-
-[spam.data]
+[lookup.spam-config]
+add-spam = true
+add-spam-result = true
+learn-enable = true
+#learn-balance = "0.9"
+learn-balance = "0.0"
+learn-ham-replies = true
+learn-ham-threshold = "-0.5"
+learn-spam-threshold = "6.0"
+threshold-spam = "5.0"
+threshold-discard = 0
+threshold-reject = 0
directory = ""
lookup = ""
@@ -59,8 +51,8 @@ lookup = ""
relay = true
[sieve.trusted]
-from-name = "Sieve Daemon"
-from-addr = "sieve@foobar.org"
+from-name = "'Sieve Daemon'"
+from-addr = "'sieve@foobar.org'"
return-path = ""
hostname = "mx.foobar.org"
no-capability-check = true
@@ -110,13 +102,12 @@ public-suffix = "file://{LIST_PATH}/public-suffix.dat"
#[tokio::test(flavor = "multi_thread")]
async fn antispam() {
- /*let disable = true;
- tracing::subscriber::set_global_default(
+ /*tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.parse(
- "smtp=debug,imap=debug,jmap=debug,store=debug,utils=debug,directory=debug,common=debug",
+ "smtp=debug,imap=debug,jmap=debug,store=debug,utils=debug,directory=debug,common=trace",
)
.unwrap(),
)
@@ -212,6 +203,7 @@ async fn antispam() {
config.resolve_macros().await;
let stores = Stores::parse_all(&mut config).await;
let core = Core::parse(&mut config, stores, Default::default()).await;
+ //config.assert_no_errors();
// Add mock DNS entries
for (domain, ip) in [
diff --git a/tests/src/smtp/inbound/auth.rs b/tests/src/smtp/inbound/auth.rs
index 0e783422..a9406319 100644
--- a/tests/src/smtp/inbound/auth.rs
+++ b/tests/src/smtp/inbound/auth.rs
@@ -66,7 +66,7 @@ member-of = ["sales", "support"]
[session.auth]
require = [{if = "remote_ip = '10.0.0.1'", then = true},
{else = false}]
-mechanisms = [{if = "remote_ip = '10.0.0.1'", then = "[plain, login]"},
+mechanisms = [{if = "remote_ip = '10.0.0.1' && is_tls", then = "[plain, login]"},
{else = 0}]
directory = [{if = "remote_ip = '10.0.0.1'", then = "'local'"},
{else = false}]
diff --git a/tests/src/smtp/inbound/data.rs b/tests/src/smtp/inbound/data.rs
index f16bc81f..71befe07 100644
--- a/tests/src/smtp/inbound/data.rs
+++ b/tests/src/smtp/inbound/data.rs
@@ -25,11 +25,14 @@ use common::Core;
use store::Stores;
use utils::config::Config;
-use crate::smtp::{
- build_smtp,
- inbound::TestMessage,
- session::{load_test_message, TestSession, VerifyResponse},
- TempDir, TestSMTP,
+use crate::{
+ smtp::{
+ build_smtp,
+ inbound::TestMessage,
+ session::{load_test_message, TestSession, VerifyResponse},
+ TempDir, TestSMTP,
+ },
+ AssertConfig,
};
use smtp::core::{Inner, Session};
@@ -39,6 +42,7 @@ data = "sqlite"
lookup = "sqlite"
blob = "sqlite"
fts = "sqlite"
+directory = "local"
[store."sqlite"]
type = "sqlite"
@@ -74,11 +78,6 @@ email = "mike@test.com"
[session.rcpt]
directory = "'local'"
-[[queue.quota]]
-match = "sender = 'john@doe.org'"
-key = ['sender']
-messages = 1
-
[session.data.limits]
messages = [{if = "remote_ip = '10.0.0.1'", then = 1},
{else = 100}]
@@ -99,6 +98,11 @@ return-path = [{if = "remote_ip = '10.0.0.3'", then = true},
{else = false}]
[[queue.quota]]
+match = "sender = 'john@doe.org'"
+key = ['sender']
+messages = 1
+
+[[queue.quota]]
match = "rcpt_domain = 'foobar.org'"
key = ['rcpt_domain']
size = 450
@@ -115,9 +119,10 @@ enable = true
#[tokio::test]
async fn data() {
// Enable logging
- /*tracing::subscriber::set_global_default(
+ /*let disable = 1;
+ tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
- .with_max_level(tracing::Level::DEBUG)
+ .with_max_level(tracing::Level::TRACE)
.finish(),
)
.unwrap();*/
@@ -128,6 +133,7 @@ async fn data() {
let mut config = Config::new(tmp_dir.update_config(CONFIG)).unwrap();
let stores = Stores::parse_all(&mut config).await;
let core = Core::parse(&mut config, stores, Default::default()).await;
+ config.assert_no_errors();
let mut qr = inner.init_test_queue(&core);
// Test queue message builder
diff --git a/tests/src/smtp/inbound/milter.rs b/tests/src/smtp/inbound/milter.rs
index 60174000..2bfb9ffb 100644
--- a/tests/src/smtp/inbound/milter.rs
+++ b/tests/src/smtp/inbound/milter.rs
@@ -231,6 +231,7 @@ fn milter_address_modifications() {
let mut data = SessionData::new(
"127.0.0.1".parse().unwrap(),
+ 0,
"127.0.0.1".parse().unwrap(),
0,
);
@@ -335,6 +336,7 @@ fn milter_message_modifications() {
let parsed_test_message = AuthenticatedMessage::parse(test_message.as_bytes()).unwrap();
let mut session_data = SessionData::new(
"127.0.0.1".parse().unwrap(),
+ 0,
"127.0.0.1".parse().unwrap(),
0,
);
@@ -403,7 +405,7 @@ async fn milter_client_test() {
const PORT: u16 = 7357;
let mut client = MilterClient::connect(
&Milter {
- enable: IfBlock::default(),
+ enable: IfBlock::empty(""),
addrs: vec![SocketAddr::from(([127, 0, 0, 1], PORT))],
hostname: "localhost".to_string(),
port: PORT,
diff --git a/tests/src/smtp/inbound/scripts.rs b/tests/src/smtp/inbound/scripts.rs
index 3974192a..aa801582 100644
--- a/tests/src/smtp/inbound/scripts.rs
+++ b/tests/src/smtp/inbound/scripts.rs
@@ -63,11 +63,11 @@ arguments = "['{CFG_PATH}/pipe_me.sh', 'hello', 'world']"
timeout = "10s"
[sieve.trusted]
-from-name = "Sieve Daemon"
-from-addr = "sieve@foobar.org"
-return-path = ""
+from-name = "'Sieve Daemon'"
+from-addr = "'sieve@foobar.org'"
+return-path = "''"
hostname = "mx.foobar.org"
-sign = ["rsa"]
+sign = "['rsa']"
[sieve.trusted.limits]
redirects = 3
@@ -94,11 +94,20 @@ relay = true
[session.data]
script = "'stage_data'"
+[session.data.add-headers]
+received = true
+received-spf = true
+auth-results = true
+message-id = true
+date = true
+return-path = false
+
"#;
#[tokio::test]
async fn sieve_scripts() {
- /*tracing::subscriber::set_global_default(
+ /*let disable = 1;
+ tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish(),
@@ -170,7 +179,9 @@ async fn sieve_scripts() {
let script = script.clone();
let params = session
.build_script_parameters("data")
- .set_variable("from", "john.doe@example.org");
+ .set_variable("from", "john.doe@example.org")
+ .with_envelope(&core.core, &session)
+ .await;
let handle = Handle::current();
let span = span.clone();
let core_ = core.clone();
diff --git a/tests/src/smtp/lookup/sql.rs b/tests/src/smtp/lookup/sql.rs
index c52645c8..e2a9b901 100644
--- a/tests/src/smtp/lookup/sql.rs
+++ b/tests/src/smtp/lookup/sql.rs
@@ -24,8 +24,7 @@
use std::time::{Duration, Instant};
use common::{
- config::smtp::*,
- expr::{tokenizer::TokenMap, Expression},
+ expr::{tokenizer::TokenMap, *},
Core,
};
@@ -52,6 +51,7 @@ data = "sql"
blob = "sql"
fts = "sql"
lookup = "sql"
+directory = "sql"
[store."sql"]
type = "sqlite"
@@ -105,7 +105,7 @@ expect = "mx.foobar.org"
[test."key_get"]
expr = "key_get('sql', 'hello') + '-' + key_exists('sql', 'hello') + '-' + key_set('sql', 'hello', 'world') + '-' + key_get('sql', 'hello') + '-' + key_exists('sql', 'hello')"
-expect = "0-0-1-world-1"
+expect = "-0-1-world-1"
[test."counter_get"]
expr = "counter_get('sql', 'county') + '-' + counter_incr('sql', 'county', 1) + '-' + counter_incr('sql', 'county', 1) + '-' + counter_get('sql', 'county')"
@@ -190,7 +190,7 @@ async fn lookup_sql() {
}
// Test expression functions
- let token_map = TokenMap::default().with_smtp_variables(&[
+ let token_map = TokenMap::default().with_variables(&[
V_RECIPIENT,
V_RECIPIENT_DOMAIN,
V_SENDER,
diff --git a/tests/src/smtp/outbound/dane.rs b/tests/src/smtp/outbound/dane.rs
index 643fcd18..fac56b5f 100644
--- a/tests/src/smtp/outbound/dane.rs
+++ b/tests/src/smtp/outbound/dane.rs
@@ -75,6 +75,8 @@ send = "weekly"
[queue.outbound.tls]
dane = "require"
+starttls = "require"
+
"#;
const REMOTE: &str = "
@@ -83,12 +85,22 @@ reject-non-fqdn = false
[session.rcpt]
relay = true
+
+[session.data.add-headers]
+received = true
+received-spf = true
+auth-results = true
+message-id = true
+date = true
+return-path = false
+
";
#[tokio::test]
#[serial_test::serial]
async fn dane_verify() {
- /*tracing::subscriber::set_global_default(
+ /*let disable = 1;
+ tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish(),
diff --git a/tests/src/smtp/outbound/extensions.rs b/tests/src/smtp/outbound/extensions.rs
index 92c5b9f4..317fc0e2 100644
--- a/tests/src/smtp/outbound/extensions.rs
+++ b/tests/src/smtp/outbound/extensions.rs
@@ -54,6 +54,14 @@ size = 1500
[session.extensions]
dsn = true
requiretls = true
+
+[session.data.add-headers]
+received = true
+received-spf = true
+auth-results = true
+message-id = true
+date = true
+return-path = false
"#;
#[tokio::test]
diff --git a/tests/src/smtp/outbound/mod.rs b/tests/src/smtp/outbound/mod.rs
index 3b22f352..924cb9c7 100644
--- a/tests/src/smtp/outbound/mod.rs
+++ b/tests/src/smtp/outbound/mod.rs
@@ -51,9 +51,9 @@ pub mod throttle;
pub mod tls;
const CONFIG: &str = r#"
-[server]
-hostname = 'mx.example.org'
-greeting = 'Test SMTP instance'
+[session.connect]
+hostname = "'mx.example.org'"
+greeting = "'Test SMTP instance'"
[server.listener.smtp-debug]
bind = ['127.0.0.1:9925']
diff --git a/tests/src/smtp/outbound/mta_sts.rs b/tests/src/smtp/outbound/mta_sts.rs
index ac8f4c53..45347800 100644
--- a/tests/src/smtp/outbound/mta_sts.rs
+++ b/tests/src/smtp/outbound/mta_sts.rs
@@ -62,6 +62,15 @@ reject-non-fqdn = false
[session.rcpt]
relay = true
+
+[session.data.add-headers]
+received = true
+received-spf = true
+auth-results = true
+message-id = true
+date = true
+return-path = false
+
"#;
#[tokio::test]
diff --git a/tests/src/smtp/outbound/tls.rs b/tests/src/smtp/outbound/tls.rs
index 509f8326..0ac13609 100644
--- a/tests/src/smtp/outbound/tls.rs
+++ b/tests/src/smtp/outbound/tls.rs
@@ -59,7 +59,8 @@ chunking = false
#[tokio::test]
#[serial_test::serial]
async fn starttls_optional() {
- /*tracing::subscriber::set_global_default(
+ /*let disable = 1;
+ tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish(),
diff --git a/tests/src/smtp/session.rs b/tests/src/smtp/session.rs
index 1bde62f1..5ee3a74d 100644
--- a/tests/src/smtp/session.rs
+++ b/tests/src/smtp/session.rs
@@ -125,6 +125,7 @@ impl TestSession for Session<DummyIo> {
},
data: SessionData::new(
"127.0.0.1".parse().unwrap(),
+ 0,
"127.0.0.1".parse().unwrap(),
0,
),