From 8438435fbe8bbf5181023e65f9b9f3d7436bcd26 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Sat, 18 Jan 2025 19:09:02 +0100 Subject: [PATCH] Removed local concurrency limiters, switch to global rate limiting --- Cargo.lock | 19 - crates/common/Cargo.toml | 1 - crates/common/src/auth/access_token.rs | 34 +- crates/common/src/auth/mod.rs | 7 +- crates/common/src/config/inner.rs | 36 +- crates/common/src/config/jmap/capabilities.rs | 6 +- crates/common/src/config/jmap/settings.rs | 16 +- crates/common/src/config/smtp/mod.rs | 5 +- crates/common/src/config/smtp/queue.rs | 122 +- crates/common/src/config/smtp/session.rs | 68 +- crates/common/src/config/smtp/throttle.rs | 75 +- crates/common/src/ipc.rs | 23 +- crates/common/src/lib.rs | 26 +- crates/common/src/listener/limiter.rs | 83 +- crates/common/src/listener/listen.rs | 6 +- crates/common/src/manager/boot.rs | 17 +- crates/imap/Cargo.toml | 1 - crates/imap/src/core/client.rs | 27 +- crates/imap/src/op/authenticate.rs | 15 +- crates/jmap/Cargo.toml | 1 - crates/jmap/src/auth/authenticate.rs | 4 +- crates/jmap/src/auth/rate_limit.rs | 77 +- crates/jmap/src/services/housekeeper.rs | 40 +- crates/managesieve/src/op/authenticate.rs | 38 +- crates/pop3/src/op/authenticate.rs | 38 +- crates/smtp/Cargo.toml | 1 - crates/smtp/src/core/mod.rs | 4 +- crates/smtp/src/core/throttle.rs | 108 +- crates/smtp/src/inbound/spawn.rs | 3 +- crates/smtp/src/outbound/delivery.rs | 114 +- crates/smtp/src/outbound/mod.rs | 20 +- crates/smtp/src/queue/manager.rs | 13 +- crates/smtp/src/queue/mod.rs | 17 +- crates/smtp/src/queue/spool.rs | 9 +- crates/smtp/src/queue/throttle.rs | 111 +- resources/config/spamfilter.toml | 10508 ---------------- tests/Cargo.toml | 1 - tests/resources/acme/config.toml | 10 - tests/resources/smtp/antispam/rbl.test | 2 +- tests/resources/smtp/config/throttle.toml | 3 +- tests/src/directory/imap.rs | 2 +- tests/src/directory/smtp.rs | 2 +- tests/src/jmap/auth_limits.rs | 1 - tests/src/jmap/mod.rs | 4 +- tests/src/smtp/config.rs | 17 +- tests/src/smtp/inbound/mail.rs | 2 +- tests/src/smtp/inbound/mod.rs | 23 +- tests/src/smtp/inbound/rcpt.rs | 2 +- tests/src/smtp/inbound/throttle.rs | 25 +- tests/src/smtp/outbound/lmtp.rs | 4 +- tests/src/smtp/outbound/smtp.rs | 4 +- tests/src/smtp/outbound/throttle.rs | 114 +- tests/src/smtp/queue/retry.rs | 6 +- tests/src/smtp/session.rs | 1 - 54 files changed, 439 insertions(+), 11477 deletions(-) delete mode 100644 resources/config/spamfilter.toml diff --git a/Cargo.lock b/Cargo.lock index 80fa4c7c..718478c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,7 +1212,6 @@ dependencies = [ "bincode", "biscuit", "chrono", - "dashmap", "decancer", "directory", "dns-update", @@ -1629,20 +1628,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-encoding" version = "2.6.0" @@ -3254,7 +3239,6 @@ version = "0.11.2" dependencies = [ "ahash 0.8.11", "common", - "dashmap", "directory", "email", "imap_proto", @@ -3510,7 +3494,6 @@ dependencies = [ "bincode", "chrono", "common", - "dashmap", "directory", "email", "form-data", @@ -6459,7 +6442,6 @@ dependencies = [ "blake3", "chrono", "common", - "dashmap", "directory", "email", "form_urlencoded", @@ -6846,7 +6828,6 @@ dependencies = [ "chrono", "common", "csv", - "dashmap", "directory", "ece", "email", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 7cdf81a9..46036b97 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -60,7 +60,6 @@ zip = "2.1" pwhash = "1.0.0" xxhash-rust = { version = "0.8.5", features = ["xxh3"] } psl = "2" -dashmap = "6.0" aes-gcm-siv = "0.11.1" biscuit = "0.7.0" rsa = "0.9.2" diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 33e22b76..21537475 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -28,7 +28,10 @@ use utils::map::{ vec_map::VecMap, }; -use crate::{Server, KV_TOKEN_REVISION}; +use crate::{ + listener::limiter::{ConcurrencyLimiter, LimiterResult}, + Server, KV_TOKEN_REVISION, +}; use super::{roles::RolePermissions, AccessToken, ResourceToken, TenantInfo}; @@ -122,6 +125,17 @@ impl Server { .unwrap_or_default(), quota: principal.quota(), permissions, + concurrent_imap_requests: self.core.imap.rate_concurrent.map(ConcurrencyLimiter::new), + concurrent_http_requests: self + .core + .jmap + .request_max_concurrent + .map(ConcurrencyLimiter::new), + concurrent_uploads: self + .core + .jmap + .upload_max_concurrent + .map(ConcurrencyLimiter::new), obj_size: 0, revision, }; @@ -647,6 +661,24 @@ impl AccessToken { } } + pub fn is_http_request_allowed(&self) -> LimiterResult { + self.concurrent_http_requests + .as_ref() + .map_or(LimiterResult::Disabled, |limiter| limiter.is_allowed()) + } + + pub fn is_imap_request_allowed(&self) -> LimiterResult { + self.concurrent_imap_requests + .as_ref() + .map_or(LimiterResult::Disabled, |limiter| limiter.is_allowed()) + } + + pub fn is_upload_allowed(&self) -> LimiterResult { + self.concurrent_uploads + .as_ref() + .map_or(LimiterResult::Disabled, |limiter| limiter.is_allowed()) + } + pub fn update_size(mut self) -> Self { self.obj_size = (std::mem::size_of::() + (self.member_of.len() * std::mem::size_of::()) diff --git a/crates/common/src/auth/mod.rs b/crates/common/src/auth/mod.rs index 39b8af25..5778e293 100644 --- a/crates/common/src/auth/mod.rs +++ b/crates/common/src/auth/mod.rs @@ -17,14 +17,14 @@ use utils::{ map::{bitmap::Bitmap, vec_map::VecMap}, }; -use crate::Server; +use crate::{listener::limiter::ConcurrencyLimiter, Server}; pub mod access_token; pub mod oauth; pub mod roles; pub mod sasl; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct AccessToken { pub primary_id: u32, pub member_of: Vec, @@ -35,6 +35,9 @@ pub struct AccessToken { pub quota: u64, pub permissions: Permissions, pub tenant: Option, + pub concurrent_http_requests: Option, + pub concurrent_imap_requests: Option, + pub concurrent_uploads: Option, pub revision: u64, pub obj_size: u64, } diff --git a/crates/common/src/config/inner.rs b/crates/common/src/config/inner.rs index a418f253..f424e374 100644 --- a/crates/common/src/config/inner.rs +++ b/crates/common/src/config/inner.rs @@ -9,9 +9,8 @@ use std::{ sync::Arc, }; -use ahash::{AHashMap, AHashSet, RandomState}; +use ahash::{AHashMap, AHashSet}; use arc_swap::ArcSwap; -use dashmap::DashMap; use mail_auth::{Parameters, Txt, MX}; use mail_send::smtp::tls::build_tls_connector; use nlp::bayes::{TokenHash, Weights}; @@ -28,7 +27,7 @@ use crate::{ listener::blocked::BlockedIps, manager::webadmin::WebAdminManager, Account, AccountId, Caches, Data, Mailbox, MailboxId, MailboxState, NextMailboxState, Threads, - ThrottleKeyHasherBuilder, TlsConnectors, + TlsConnectors, }; use super::server::tls::{build_self_signed_cert, parse_certificates}; @@ -43,13 +42,6 @@ impl Data { subject_names.insert("localhost".to_string()); } - // Parse capacities - let shard_amount = config - .property::("limiter.shard") - .unwrap_or_else(|| (num_cpus::get() * 2) as u64) - .next_power_of_two() as usize; - let capacity = config.property("limiter.capacity").unwrap_or(100); - // Parse id generator let id_generator = config .property::("cluster.node-id") @@ -78,27 +70,7 @@ impl Data { .map(|path| WebAdminManager::new(path.into())) .unwrap_or_default(), config_version: 0.into(), - jmap_limiter: DashMap::with_capacity_and_hasher_and_shard_amount( - capacity, - RandomState::default(), - shard_amount, - ), - imap_limiter: DashMap::with_capacity_and_hasher_and_shard_amount( - capacity, - RandomState::default(), - shard_amount, - ), logos: Default::default(), - smtp_session_throttle: DashMap::with_capacity_and_hasher_and_shard_amount( - capacity, - ThrottleKeyHasherBuilder::default(), - shard_amount, - ), - smtp_queue_throttle: DashMap::with_capacity_and_hasher_and_shard_amount( - capacity, - ThrottleKeyHasherBuilder::default(), - shard_amount, - ), smtp_connectors: TlsConnectors::default(), asn_geo_data: Default::default(), } @@ -248,11 +220,7 @@ impl Default for Data { queue_status: true.into(), webadmin: Default::default(), config_version: Default::default(), - jmap_limiter: Default::default(), - imap_limiter: Default::default(), logos: Default::default(), - smtp_session_throttle: Default::default(), - smtp_queue_throttle: Default::default(), smtp_connectors: Default::default(), asn_geo_data: Default::default(), } diff --git a/crates/common/src/config/jmap/capabilities.rs b/crates/common/src/config/jmap/capabilities.rs index df7f58ed..de1013fc 100644 --- a/crates/common/src/config/jmap/capabilities.rs +++ b/crates/common/src/config/jmap/capabilities.rs @@ -24,9 +24,11 @@ impl JmapConfig { Capability::Core, Capabilities::Core(CoreCapabilities { max_size_upload: self.upload_max_size, - max_concurrent_upload: self.upload_max_concurrent as usize, + max_concurrent_upload: self.upload_max_concurrent.unwrap_or(u32::MAX as u64) + as usize, max_size_request: self.request_max_size, - max_concurrent_requests: self.request_max_concurrent as usize, + max_concurrent_requests: self.request_max_concurrent.unwrap_or(u32::MAX as u64) + as usize, max_calls_in_request: self.request_max_calls, max_objects_in_get: self.get_max_objects, max_objects_in_set: self.set_max_objects, diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs index 6a654915..2a16ab6c 100644 --- a/crates/common/src/config/jmap/settings.rs +++ b/crates/common/src/config/jmap/settings.rs @@ -21,13 +21,13 @@ pub struct JmapConfig { pub request_max_size: usize, pub request_max_calls: usize, - pub request_max_concurrent: u64, + pub request_max_concurrent: Option, pub get_max_objects: usize, pub set_max_objects: usize, pub upload_max_size: usize, - pub upload_max_concurrent: u64, + pub upload_max_concurrent: Option, pub upload_tmp_quota_size: usize, pub upload_tmp_quota_amount: usize, @@ -72,7 +72,6 @@ pub struct JmapConfig { pub encrypt_append: bool, pub capabilities: BaseCapabilities, - pub session_purge_frequency: SimpleCron, pub account_purge_frequency: SimpleCron, } @@ -254,8 +253,8 @@ impl JmapConfig { .property("jmap.protocol.request.max-calls") .unwrap_or(16), request_max_concurrent: config - .property("jmap.protocol.request.max-concurrent") - .unwrap_or(4), + .property_or_default::>("jmap.protocol.request.max-concurrent", "4") + .unwrap_or(Some(4)), get_max_objects: config .property("jmap.protocol.get.max-objects") .unwrap_or(500), @@ -266,8 +265,8 @@ impl JmapConfig { .property("jmap.protocol.upload.max-size") .unwrap_or(50000000), upload_max_concurrent: config - .property("jmap.protocol.upload.max-concurrent") - .unwrap_or(4), + .property_or_default::>("jmap.protocol.upload.max-concurrent", "4") + .unwrap_or(Some(4)), upload_tmp_quota_size: config .property("jmap.protocol.upload.quota.size") .unwrap_or(50000000), @@ -346,9 +345,6 @@ impl JmapConfig { push_throttle: config .property_or_default("jmap.push.throttle", "1s") .unwrap_or_else(|| Duration::from_secs(1)), - session_purge_frequency: config - .property_or_default::("jmap.session.purge.frequency", "15 * *") - .unwrap_or_else(|| SimpleCron::parse_value("15 * *").unwrap()), account_purge_frequency: config .property_or_default::("jmap.account.purge.frequency", "0 0 *") .unwrap_or_else(|| SimpleCron::parse_value("0 0 *").unwrap()), diff --git a/crates/common/src/config/smtp/mod.rs b/crates/common/src/config/smtp/mod.rs index 9d2e507b..eab742ff 100644 --- a/crates/common/src/config/smtp/mod.rs +++ b/crates/common/src/config/smtp/mod.rs @@ -33,12 +33,11 @@ pub struct SmtpConfig { #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "test_mode", derive(PartialEq, Eq))] -pub struct Throttle { +pub struct QueueRateLimiter { pub id: String, pub expr: Expression, pub keys: u16, - pub concurrency: Option, - pub rate: Option, + pub rate: Rate, } pub const THROTTLE_RCPT: u16 = 1 << 0; diff --git a/crates/common/src/config/smtp/queue.rs b/crates/common/src/config/smtp/queue.rs index 17c37a29..e2231f79 100644 --- a/crates/common/src/config/smtp/queue.rs +++ b/crates/common/src/config/smtp/queue.rs @@ -7,17 +7,15 @@ use ahash::AHashMap; use mail_auth::IpLookupStrategy; use mail_send::Credentials; -use utils::config::{ - utils::{AsKey, ParseValue}, - Config, -}; +use throttle::parse_queue_rate_limiter_key; +use utils::config::{utils::ParseValue, Config}; use crate::{ config::server::ServerProtocol, expr::{if_block::IfBlock, *}, }; -use self::throttle::{parse_throttle, parse_throttle_key}; +use self::throttle::parse_queue_rate_limiter; use super::*; @@ -41,9 +39,11 @@ pub struct QueueConfig { // Timeouts pub timeout: QueueOutboundTimeout, - // Throttle and Quotas - pub throttle: QueueThrottle, + // Rate limits + pub inbound_limiters: QueueRateLimiters, + pub outbound_limiters: QueueRateLimiters, pub quota: QueueQuotas, + pub max_threads: usize, // Relay hosts pub relay_hosts: AHashMap, @@ -82,15 +82,14 @@ pub struct QueueOutboundTimeout { pub mta_sts: IfBlock, } -#[derive(Debug, Clone)] -pub struct QueueThrottle { - pub outbound_concurrency: usize, - pub sender: Vec, - pub rcpt: Vec, - pub host: Vec, +#[derive(Debug, Clone, Default)] +pub struct QueueRateLimiters { + pub sender: Vec, + pub rcpt: Vec, + pub remote: Vec, } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct QueueQuotas { pub sender: Vec, pub rcpt: Vec, @@ -201,17 +200,10 @@ impl Default for QueueConfig { data: IfBlock::new::<()>("queue.outbound.timeouts.data", [], "10m"), mta_sts: IfBlock::new::<()>("queue.outbound.timeouts.mta-sts", [], "10m"), }, - throttle: QueueThrottle { - outbound_concurrency: 25, - sender: Default::default(), - rcpt: Default::default(), - host: Default::default(), - }, - quota: QueueQuotas { - sender: Default::default(), - rcpt: Default::default(), - rcpt_domain: Default::default(), - }, + max_threads: 25, + inbound_limiters: QueueRateLimiters::default(), + outbound_limiters: QueueRateLimiters::default(), + quota: QueueQuotas::default(), relay_hosts: Default::default(), } } @@ -324,8 +316,13 @@ impl QueueConfig { } } - // Parse queue quotas and throttles - queue.throttle = parse_queue_throttle(config); + // Parse rate limiters + queue.max_threads = config + .property_or_default::("queue.threads.remote", "25") + .unwrap_or(25) + .max(1); + queue.inbound_limiters = parse_inbound_rate_limters(config); + queue.outbound_limiters = parse_outbound_rate_limiters(config); queue.quota = parse_queue_quota(config); // Parse relay hosts @@ -380,21 +377,60 @@ fn parse_relay_host(config: &mut Config, id: &str) -> Option { }) } -fn parse_queue_throttle(config: &mut Config) -> QueueThrottle { - // Parse throttle - let mut throttle = QueueThrottle { - sender: Vec::new(), - rcpt: Vec::new(), - host: Vec::new(), - outbound_concurrency: config - .property_or_default::("queue.threads.remote", "25") - .unwrap_or(25) - .max(1), - }; - - let all_throttles = parse_throttle( +fn parse_inbound_rate_limters(config: &mut Config) -> QueueRateLimiters { + let mut throttle = QueueRateLimiters::default(); + let all_throttles = parse_queue_rate_limiter( config, - "queue.throttle", + "queue.limiter.inbound", + &TokenMap::default().with_variables(SMTP_RCPT_TO_VARS), + THROTTLE_LISTENER + | THROTTLE_REMOTE_IP + | THROTTLE_LOCAL_IP + | THROTTLE_AUTH_AS + | THROTTLE_HELO_DOMAIN + | THROTTLE_RCPT + | THROTTLE_RCPT_DOMAIN + | THROTTLE_SENDER + | THROTTLE_SENDER_DOMAIN, + ); + for t in all_throttles { + if (t.keys & (THROTTLE_RCPT | THROTTLE_RCPT_DOMAIN)) != 0 + || t.expr.items().iter().any(|c| { + matches!( + c, + ExpressionItem::Variable(V_RECIPIENT | V_RECIPIENT_DOMAIN) + ) + }) + { + throttle.rcpt.push(t); + } else if (t.keys + & (THROTTLE_SENDER | THROTTLE_SENDER_DOMAIN | THROTTLE_HELO_DOMAIN | THROTTLE_AUTH_AS)) + != 0 + || t.expr.items().iter().any(|c| { + matches!( + c, + ExpressionItem::Variable( + V_SENDER | V_SENDER_DOMAIN | V_HELO_DOMAIN | V_AUTHENTICATED_AS + ) + ) + }) + { + throttle.sender.push(t); + } else { + throttle.remote.push(t); + } + } + + throttle +} + +fn parse_outbound_rate_limiters(config: &mut Config) -> QueueRateLimiters { + // Parse throttle + let mut throttle = QueueRateLimiters::default(); + + let all_throttles = parse_queue_rate_limiter( + config, + "queue.limiter.outbound", &TokenMap::default().with_variables(SMTP_QUEUE_HOST_VARS), THROTTLE_RCPT_DOMAIN | THROTTLE_SENDER @@ -410,7 +446,7 @@ fn parse_queue_throttle(config: &mut Config) -> QueueThrottle { .iter() .any(|c| matches!(c, ExpressionItem::Variable(V_MX | V_REMOTE_IP | V_LOCAL_IP))) { - throttle.host.push(t); + throttle.remote.push(t); } else if (t.keys & (THROTTLE_RCPT_DOMAIN)) != 0 || t.expr .items() @@ -481,7 +517,7 @@ fn parse_queue_quota_item(config: &mut Config, prefix: impl AsKey, id: &str) -> .map(|(k, v)| (k.to_string(), v.to_string())) .collect::>() { - match parse_throttle_key(&value) { + match parse_queue_rate_limiter_key(&value) { Ok(key) => { if (key & (THROTTLE_RCPT_DOMAIN diff --git a/crates/common/src/config/smtp/session.rs b/crates/common/src/config/smtp/session.rs index a0275009..52211cc8 100644 --- a/crates/common/src/config/smtp/session.rs +++ b/crates/common/src/config/smtp/session.rs @@ -24,7 +24,7 @@ use crate::{ expr::{if_block::IfBlock, tokenizer::TokenMap, *}, }; -use self::{resolver::Policy, throttle::parse_throttle}; +use self::resolver::Policy; use super::*; @@ -33,7 +33,6 @@ pub struct SessionConfig { pub timeout: IfBlock, pub duration: IfBlock, pub transfer_limit: IfBlock, - pub throttle: SessionThrottle, pub connect: Connect, pub ehlo: Ehlo, @@ -48,13 +47,6 @@ pub struct SessionConfig { pub hooks: Vec, } -#[derive(Default, Debug, Clone)] -pub struct SessionThrottle { - pub connect: Vec, - pub mail_from: Vec, - pub rcpt_to: Vec, -} - #[derive(Clone)] pub struct Connect { pub hostname: IfBlock, @@ -222,7 +214,6 @@ impl SessionConfig { .into_iter() .filter_map(|id| parse_hooks(config, &id, &has_rcpt_vars)) .collect(); - session.throttle = SessionThrottle::parse(config); session.mta_sts_policy = Policy::try_parse(config); for (value, key, token_map) in [ @@ -460,58 +451,6 @@ impl SessionConfig { } } -impl SessionThrottle { - pub fn parse(config: &mut Config) -> Self { - let mut throttle = SessionThrottle::default(); - let all_throttles = parse_throttle( - config, - "session.throttle", - &TokenMap::default().with_variables(SMTP_RCPT_TO_VARS), - THROTTLE_LISTENER - | THROTTLE_REMOTE_IP - | THROTTLE_LOCAL_IP - | THROTTLE_AUTH_AS - | THROTTLE_HELO_DOMAIN - | THROTTLE_RCPT - | THROTTLE_RCPT_DOMAIN - | THROTTLE_SENDER - | THROTTLE_SENDER_DOMAIN, - ); - for t in all_throttles { - if (t.keys & (THROTTLE_RCPT | THROTTLE_RCPT_DOMAIN)) != 0 - || t.expr.items().iter().any(|c| { - matches!( - c, - ExpressionItem::Variable(V_RECIPIENT | V_RECIPIENT_DOMAIN) - ) - }) - { - throttle.rcpt_to.push(t); - } else if (t.keys - & (THROTTLE_SENDER - | THROTTLE_SENDER_DOMAIN - | THROTTLE_HELO_DOMAIN - | THROTTLE_AUTH_AS)) - != 0 - || t.expr.items().iter().any(|c| { - matches!( - c, - ExpressionItem::Variable( - V_SENDER | V_SENDER_DOMAIN | V_HELO_DOMAIN | V_AUTHENTICATED_AS - ) - ) - }) - { - throttle.mail_from.push(t); - } else { - throttle.connect.push(t); - } - } - - throttle - } -} - fn parse_milter(config: &mut Config, id: &str, token_map: &TokenMap) -> Option { let hostname = config .value_require(("session.milter", id, "hostname"))? @@ -693,11 +632,6 @@ impl Default for SessionConfig { 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 { hostname: IfBlock::new::<()>( "server.connect.hostname", diff --git a/crates/common/src/config/smtp/throttle.rs b/crates/common/src/config/smtp/throttle.rs index e8e11e22..f76283e0 100644 --- a/crates/common/src/config/smtp/throttle.rs +++ b/crates/common/src/config/smtp/throttle.rs @@ -10,44 +10,44 @@ use crate::expr::{tokenizer::TokenMap, Expression}; use super::*; -pub fn parse_throttle( +pub fn parse_queue_rate_limiter( config: &mut Config, prefix: impl AsKey, token_map: &TokenMap, - available_throttle_keys: u16, -) -> Vec { + available_rate_limiter_keys: u16, +) -> Vec { let prefix_ = prefix.as_key(); - let mut throttles = Vec::new(); - for throttle_id in config + let mut rate_limiters = Vec::new(); + for rate_limiter_id in config .sub_keys(prefix, "") .map(|s| s.to_string()) .collect::>() { - let throttle_id = throttle_id.as_str(); - if let Some(throttle) = parse_throttle_item( + let rate_limiter_id = rate_limiter_id.as_str(); + if let Some(rate_limiter) = parse_queue_rate_limiter_item( config, - (&prefix_, throttle_id), - throttle_id, + (&prefix_, rate_limiter_id), + rate_limiter_id, token_map, - available_throttle_keys, + available_rate_limiter_keys, ) { - throttles.push(throttle); + rate_limiters.push(rate_limiter); } } - throttles + rate_limiters } -fn parse_throttle_item( +fn parse_queue_rate_limiter_item( config: &mut Config, prefix: impl AsKey, - throttle_id: &str, + rate_limiter_id: &str, token_map: &TokenMap, - available_throttle_keys: u16, -) -> Option { + available_rate_limiter_keys: u16, +) -> Option { let prefix = prefix.as_key(); - // Skip disabled throttles + // Skip disabled rate_limiters if !config .property::((prefix.as_str(), "enable")) .unwrap_or(true) @@ -61,12 +61,13 @@ fn parse_throttle_item( .map(|(k, v)| (k.to_string(), v.to_string())) .collect::>() { - match parse_throttle_key(&value) { + match parse_queue_rate_limiter_key(&value) { Ok(key) => { - if (key & available_throttle_keys) != 0 { + if (key & available_rate_limiter_keys) != 0 { keys |= key; } else { - let err = format!("Throttle key {value:?} is not available in this context"); + let err = + format!("Rate limiter key {value:?} is not available in this context"); config.new_build_error(key_, err); } } @@ -76,38 +77,18 @@ fn parse_throttle_item( } } - let throttle = Throttle { - id: throttle_id.to_string(), + Some(QueueRateLimiter { + id: rate_limiter_id.to_string(), expr: Expression::try_parse(config, (prefix.as_str(), "match"), token_map) .unwrap_or_default(), keys, - concurrency: config - .property::>((prefix.as_str(), "concurrency")) - .filter(|&v| v.as_ref().is_some_and(|v| *v > 0)) - .unwrap_or_default(), rate: config - .property::>((prefix.as_str(), "rate")) - .filter(|v| v.as_ref().is_some_and(|r| r.requests > 0)) - .unwrap_or_default(), - }; - - // Validate - if throttle.rate.is_none() && throttle.concurrency.is_none() { - config.new_parse_error( - prefix.as_str(), - concat!( - "Throttle needs to define a ", - "valid 'rate' and/or 'concurrency' property." - ) - .to_string(), - ); - None - } else { - Some(throttle) - } + .property_require::((prefix.as_str(), "rate")) + .filter(|r| r.requests > 0)?, + }) } -pub(crate) fn parse_throttle_key(value: &str) -> Result { +pub(crate) fn parse_queue_rate_limiter_key(value: &str) -> Result { match value { "rcpt" => Ok(THROTTLE_RCPT), "rcpt_domain" => Ok(THROTTLE_RCPT_DOMAIN), @@ -119,6 +100,6 @@ pub(crate) fn parse_throttle_key(value: &str) -> Result { "remote_ip" => Ok(THROTTLE_REMOTE_IP), "local_ip" => Ok(THROTTLE_LOCAL_IP), "helo_domain" => Ok(THROTTLE_HELO_DOMAIN), - _ => Err(format!("Invalid throttle key {value:?}")), + _ => Err(format!("Invalid THROTTLE key {value:?}")), } } diff --git a/crates/common/src/ipc.rs b/crates/common/src/ipc.rs index 7abb3ef8..06d95faf 100644 --- a/crates/common/src/ipc.rs +++ b/crates/common/src/ipc.rs @@ -17,12 +17,9 @@ use store::{BlobStore, InMemoryStore, Store}; use tokio::sync::mpsc; use utils::map::bitmap::Bitmap; -use crate::{ - config::smtp::{ - report::AggregateFrequency, - resolver::{Policy, Tlsa}, - }, - listener::limiter::ConcurrencyLimiter, +use crate::config::smtp::{ + report::AggregateFrequency, + resolver::{Policy, Tlsa}, }; pub enum HousekeeperEvent { @@ -108,22 +105,10 @@ pub enum QueueEvent { #[derive(Debug)] pub enum QueueEventStatus { Completed, - Locked { - until: u64, - }, - Limited { - limiters: Vec, - next_due: Option, - }, + Locked { until: u64 }, Deferred, } -#[derive(Debug, Clone, Copy)] -pub struct QueuedMessage { - pub due: u64, - pub queue_id: u64, -} - #[derive(Debug)] pub enum ReportingEvent { Dmarc(Box), diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index e72917e7..5d709b1a 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -14,7 +14,7 @@ use std::{ }, }; -use ahash::{AHashMap, AHashSet, RandomState}; +use ahash::{AHashMap, AHashSet}; use arc_swap::ArcSwap; use auth::{oauth::config::OAuthConfig, roles::RolePermissions, AccessToken}; use config::{ @@ -30,13 +30,10 @@ use config::{ storage::Storage, telemetry::Metrics, }; -use dashmap::DashMap; use imap_proto::protocol::list::Attribute; use ipc::{HousekeeperEvent, QueueEvent, ReportingEvent, StateEvent}; -use listener::{ - asn::AsnGeoLookupData, blocked::Security, limiter::ConcurrencyLimiter, tls::AcmeProviders, -}; +use listener::{asn::AsnGeoLookupData, blocked::Security, tls::AcmeProviders}; use mail_auth::{Txt, MX}; use manager::webadmin::{Resource, WebAdminManager}; @@ -125,15 +122,9 @@ pub struct Data { pub queue_status: AtomicBool, pub webadmin: WebAdminManager, + pub logos: Mutex>>>>, pub config_version: AtomicU8, - pub jmap_limiter: DashMap, RandomState>, - pub imap_limiter: DashMap, RandomState>, - - pub logos: Mutex>>>>, - - pub smtp_session_throttle: DashMap, - pub smtp_queue_throttle: DashMap, pub smtp_connectors: TlsConnectors, } @@ -245,11 +236,6 @@ pub struct Threads { pub modseq: Option, } -pub struct ConcurrencyLimiters { - pub concurrent_requests: ConcurrencyLimiter, - pub concurrent_uploads: ConcurrencyLimiter, -} - #[derive(Clone, Default)] pub struct Core { pub storage: Storage, @@ -382,12 +368,6 @@ impl BuildHasher for ThrottleKeyHasherBuilder { } } -impl ConcurrencyLimiters { - pub fn is_active(&self) -> bool { - self.concurrent_requests.is_active() || self.concurrent_uploads.is_active() - } -} - #[cfg(feature = "test_mode")] #[allow(clippy::derivable_impls)] impl Default for Server { diff --git a/crates/common/src/listener/limiter.rs b/crates/common/src/listener/limiter.rs index 2b2a8fa7..5c009071 100644 --- a/crates/common/src/listener/limiter.rs +++ b/crates/common/src/listener/limiter.rs @@ -4,22 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, - time::SystemTime, +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, }; -use utils::config::Rate; - -#[derive(Debug)] -pub struct RateLimiter { - next_refill: AtomicU64, - used_tokens: AtomicU64, -} - #[derive(Debug, Clone)] pub struct ConcurrencyLimiter { pub max_concurrent: u64, @@ -31,53 +20,18 @@ pub struct InFlight { concurrent: Arc, } +pub enum LimiterResult { + Allowed(InFlight), + Forbidden, + Disabled, +} + impl Drop for InFlight { fn drop(&mut self) { self.concurrent.fetch_sub(1, Ordering::Relaxed); } } -impl RateLimiter { - pub fn new(rate: &Rate) -> Self { - RateLimiter { - next_refill: (now() + rate.period.as_secs()).into(), - used_tokens: 0.into(), - } - } - - pub fn is_allowed(&self, rate: &Rate) -> bool { - // Check rate limit - if self.used_tokens.fetch_add(1, Ordering::Relaxed) < rate.requests { - true - } else { - let now = now(); - if self.next_refill.load(Ordering::Relaxed) <= now { - self.next_refill - .store(now + rate.period.as_secs(), Ordering::Relaxed); - self.used_tokens.store(1, Ordering::Relaxed); - true - } else { - false - } - } - } - - pub fn is_allowed_soft(&self, rate: &Rate) -> bool { - self.used_tokens.load(Ordering::Relaxed) < rate.requests - || self.next_refill.load(Ordering::Relaxed) <= now() - } - - pub fn secs_to_refill(&self) -> u64 { - self.next_refill - .load(Ordering::Relaxed) - .saturating_sub(now()) - } - - pub fn is_active(&self) -> bool { - self.next_refill.load(Ordering::Relaxed) > now() - } -} - impl ConcurrencyLimiter { pub fn new(max_concurrent: u64) -> Self { ConcurrencyLimiter { @@ -86,15 +40,15 @@ impl ConcurrencyLimiter { } } - pub fn is_allowed(&self) -> Option { + pub fn is_allowed(&self) -> LimiterResult { if self.concurrent.load(Ordering::Relaxed) < self.max_concurrent { // Return in-flight request self.concurrent.fetch_add(1, Ordering::Relaxed); - Some(InFlight { + LimiterResult::Allowed(InFlight { concurrent: self.concurrent.clone(), }) } else { - None + LimiterResult::Forbidden } } @@ -113,9 +67,12 @@ impl InFlight { } } -fn now() -> u64 { - SystemTime::UNIX_EPOCH - .elapsed() - .unwrap_or_default() - .as_secs() +impl From for Option { + fn from(result: LimiterResult) -> Self { + match result { + LimiterResult::Allowed(in_flight) => Some(in_flight), + LimiterResult::Forbidden => None, + LimiterResult::Disabled => Some(InFlight::default()), + } + } } diff --git a/crates/common/src/listener/listen.rs b/crates/common/src/listener/listen.rs index ca82582b..55ef0e8d 100644 --- a/crates/common/src/listener/listen.rs +++ b/crates/common/src/listener/listen.rs @@ -24,8 +24,8 @@ use crate::{ }; use super::{ - limiter::ConcurrencyLimiter, ServerInstance, SessionData, SessionManager, SessionStream, - TcpAcceptor, + limiter::{ConcurrencyLimiter, LimiterResult}, + ServerInstance, SessionData, SessionManager, SessionStream, TcpAcceptor, }; impl Listener { @@ -234,7 +234,7 @@ impl BuildSession for Arc { RemotePort = remote_port, ); None - } else if let Some(in_flight) = self.limiter.is_allowed() { + } else if let LimiterResult::Allowed(in_flight) = self.limiter.is_allowed() { // Enforce concurrency SessionData { stream, diff --git a/crates/common/src/manager/boot.rs b/crates/common/src/manager/boot.rs index cec049bd..f31b3100 100644 --- a/crates/common/src/manager/boot.rs +++ b/crates/common/src/manager/boot.rs @@ -292,16 +292,13 @@ impl BootManager { ("queue.quota.size.messages", "100000"), ("queue.quota.size.size", "10737418240"), ("queue.quota.size.enable", "true"), - ("queue.throttle.rcpt.key", "rcpt_domain"), - ("queue.throttle.rcpt.concurrency", "5"), - ("queue.throttle.rcpt.enable", "true"), - ("session.throttle.ip.key", "remote_ip"), - ("session.throttle.ip.concurrency", "5"), - ("session.throttle.ip.enable", "true"), - ("session.throttle.sender.key.0", "sender_domain"), - ("session.throttle.sender.key.1", "rcpt"), - ("session.throttle.sender.rate", "25/1h"), - ("session.throttle.sender.enable", "true"), + ("queue.limiter.inbound.ip.key", "remote_ip"), + ("queue.limiter.inbound.ip.rate", "5/1s"), + ("queue.limiter.inbound.ip.enable", "true"), + ("queue.limiter.inbound.sender.key.0", "sender_domain"), + ("queue.limiter.inbound.sender.key.1", "rcpt"), + ("queue.limiter.inbound.sender.rate", "25/1h"), + ("queue.limiter.inbound.sender.enable", "true"), ("report.analysis.addresses", "postmaster@*"), ] { insert_keys.push(ConfigKey::from(key)); diff --git a/crates/imap/Cargo.toml b/crates/imap/Cargo.toml index 377baf0e..378eb8ad 100644 --- a/crates/imap/Cargo.toml +++ b/crates/imap/Cargo.toml @@ -24,7 +24,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring", parking_lot = "0.12" ahash = { version = "0.8" } md5 = "0.7.0" -dashmap = "6.0" rand = "0.8.5" diff --git a/crates/imap/src/core/client.rs b/crates/imap/src/core/client.rs index e6733c39..e328bcda 100644 --- a/crates/imap/src/core/client.rs +++ b/crates/imap/src/core/client.rs @@ -7,8 +7,8 @@ use std::{iter::Peekable, sync::Arc, vec::IntoIter}; use common::{ - listener::{limiter::ConcurrencyLimiter, SessionResult, SessionStream}, - ConcurrencyLimiters, KV_RATE_LIMIT_IMAP, + listener::{SessionResult, SessionStream}, + KV_RATE_LIMIT_IMAP, }; use imap_proto::{ receiver::{self, Request}, @@ -419,29 +419,6 @@ impl Session { }, } } - - pub fn get_concurrency_limiter(&self, account_id: u32) -> Option> { - let rate = self.server.core.imap.rate_concurrent?; - self.server - .inner - .data - .imap_limiter - .get(&account_id) - .map(|limiter| limiter.clone()) - .unwrap_or_else(|| { - let limiter = Arc::new(ConcurrencyLimiters { - concurrent_requests: ConcurrencyLimiter::new(rate), - concurrent_uploads: ConcurrencyLimiter::new(rate), - }); - self.server - .inner - .data - .imap_limiter - .insert(account_id, limiter.clone()); - limiter - }) - .into() - } } impl State { diff --git a/crates/imap/src/op/authenticate.rs b/crates/imap/src/op/authenticate.rs index ac1f91e4..c1d15165 100644 --- a/crates/imap/src/op/authenticate.rs +++ b/crates/imap/src/op/authenticate.rs @@ -9,7 +9,7 @@ use common::{ sasl::{sasl_decode_challenge_oauth, sasl_decode_challenge_plain}, AuthRequest, }, - listener::SessionStream, + listener::{limiter::LimiterResult, SessionStream}, }; use directory::Permission; use imap_proto::{ @@ -105,17 +105,14 @@ impl Session { })?; // Enforce concurrency limits - let in_flight = match self - .get_concurrency_limiter(access_token.primary_id()) - .map(|limiter| limiter.concurrent_requests.is_allowed()) - { - Some(Some(limiter)) => Some(limiter), - None => None, - Some(None) => { + let in_flight = match access_token.is_imap_request_allowed() { + LimiterResult::Allowed(in_flight) => Some(in_flight), + LimiterResult::Forbidden => { return Err(trc::LimitEvent::ConcurrentRequest .into_err() - .id(tag.clone())); + .id(tag.clone())) } + LimiterResult::Disabled => None, }; // Create session diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index cdfbb6fd..b26d703a 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -42,7 +42,6 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls- tokio-tungstenite = "0.26" tungstenite = "0.26" chrono = "0.4" -dashmap = "6.0" rand = "0.8.5" pkcs8 = { version = "0.10.2", features = ["alloc", "std"] } lz4_flex = { version = "0.11", default-features = false } diff --git a/crates/jmap/src/auth/authenticate.rs b/crates/jmap/src/auth/authenticate.rs index fd268d21..58d6232f 100644 --- a/crates/jmap/src/auth/authenticate.rs +++ b/crates/jmap/src/auth/authenticate.rs @@ -24,7 +24,7 @@ pub trait Authenticator: Sync + Send { req: &HttpRequest, session: &HttpSessionData, allow_api_access: bool, - ) -> impl Future)>> + Send; + ) -> impl Future, Arc)>> + Send; } impl Authenticator for Server { @@ -33,7 +33,7 @@ impl Authenticator for Server { req: &HttpRequest, session: &HttpSessionData, allow_api_access: bool, - ) -> trc::Result<(InFlight, Arc)> { + ) -> trc::Result<(Option, Arc)> { if let Some((mechanism, token)) = req.authorization() { // Check if the credentials are cached if let Some(http_cache) = self.inner.cache.http_auth.get(token) { diff --git a/crates/jmap/src/auth/rate_limit.rs b/crates/jmap/src/auth/rate_limit.rs index c017a058..c49b49a1 100644 --- a/crates/jmap/src/auth/rate_limit.rs +++ b/crates/jmap/src/auth/rate_limit.rs @@ -4,12 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{net::IpAddr, sync::Arc}; +use std::net::IpAddr; use common::{ ip_to_bytes, - listener::limiter::{ConcurrencyLimiter, InFlight}, - ConcurrencyLimiters, Server, KV_RATE_LIMIT_HTTP_ANONYMOUS, KV_RATE_LIMIT_HTTP_AUTHENTICATED, + listener::limiter::{InFlight, LimiterResult}, + Server, KV_RATE_LIMIT_HTTP_ANONYMOUS, KV_RATE_LIMIT_HTTP_AUTHENTICATED, }; use directory::Permission; use trc::AddContext; @@ -18,47 +18,22 @@ use common::auth::AccessToken; use std::future::Future; pub trait RateLimiter: Sync + Send { - fn get_concurrency_limiter(&self, account_id: u32) -> Arc; fn is_http_authenticated_request_allowed( &self, access_token: &AccessToken, - ) -> impl Future> + Send; + ) -> impl Future>> + Send; fn is_http_anonymous_request_allowed( &self, addr: &IpAddr, ) -> impl Future> + Send; - fn is_upload_allowed(&self, access_token: &AccessToken) -> trc::Result; + fn is_upload_allowed(&self, access_token: &AccessToken) -> trc::Result>; } impl RateLimiter for Server { - fn get_concurrency_limiter(&self, account_id: u32) -> Arc { - self.inner - .data - .jmap_limiter - .get(&account_id) - .map(|limiter| limiter.clone()) - .unwrap_or_else(|| { - let limiter = Arc::new(ConcurrencyLimiters { - concurrent_requests: ConcurrencyLimiter::new( - self.core.jmap.request_max_concurrent, - ), - concurrent_uploads: ConcurrencyLimiter::new( - self.core.jmap.upload_max_concurrent, - ), - }); - self.inner - .data - .jmap_limiter - .insert(account_id, limiter.clone()); - limiter - }) - } - async fn is_http_authenticated_request_allowed( &self, access_token: &AccessToken, - ) -> trc::Result { - let limiter = self.get_concurrency_limiter(access_token.primary_id()); + ) -> trc::Result> { let is_rate_allowed = if let Some(rate) = &self.core.jmap.rate_authenticated { self.core .storage @@ -77,15 +52,19 @@ impl RateLimiter for Server { }; if is_rate_allowed { - if let Some(in_flight_request) = limiter.concurrent_requests.is_allowed() { - Ok(in_flight_request) - } else if access_token.has_permission(Permission::UnlimitedRequests) { - Ok(InFlight::default()) - } else { - Err(trc::LimitEvent::ConcurrentRequest.into_err()) + match access_token.is_http_request_allowed() { + LimiterResult::Allowed(in_flight) => Ok(Some(in_flight)), + LimiterResult::Forbidden => { + if access_token.has_permission(Permission::UnlimitedRequests) { + Ok(None) + } else { + Err(trc::LimitEvent::ConcurrentRequest.into_err()) + } + } + LimiterResult::Disabled => Ok(None), } } else if access_token.has_permission(Permission::UnlimitedRequests) { - Ok(InFlight::default()) + Ok(None) } else { Err(trc::LimitEvent::TooManyRequests.into_err()) } @@ -114,17 +93,17 @@ impl RateLimiter for Server { Ok(()) } - fn is_upload_allowed(&self, access_token: &AccessToken) -> trc::Result { - if let Some(in_flight_request) = self - .get_concurrency_limiter(access_token.primary_id()) - .concurrent_uploads - .is_allowed() - { - Ok(in_flight_request) - } else if access_token.has_permission(Permission::UnlimitedRequests) { - Ok(InFlight::default()) - } else { - Err(trc::LimitEvent::ConcurrentUpload.into_err()) + fn is_upload_allowed(&self, access_token: &AccessToken) -> trc::Result> { + match access_token.is_upload_allowed() { + LimiterResult::Allowed(in_flight) => Ok(Some(in_flight)), + LimiterResult::Forbidden => { + if access_token.has_permission(Permission::UnlimitedRequests) { + Ok(None) + } else { + Err(trc::LimitEvent::ConcurrentUpload.into_err()) + } + } + LimiterResult::Disabled => Ok(None), } } } diff --git a/crates/jmap/src/services/housekeeper.rs b/crates/jmap/src/services/housekeeper.rs index 29262f5e..590baac0 100644 --- a/crates/jmap/src/services/housekeeper.rs +++ b/crates/jmap/src/services/housekeeper.rs @@ -7,7 +7,7 @@ use std::{ collections::BinaryHeap, future::Future, - sync::{atomic::Ordering, Arc}, + sync::Arc, time::{Duration, Instant, SystemTime}, }; @@ -39,7 +39,6 @@ struct Action { #[derive(PartialEq, Eq, Debug)] enum ActionClass { - Session, Account, Store(usize), Acme(String), @@ -71,12 +70,6 @@ pub fn spawn_housekeeper(inner: Arc, mut rx: mpsc::Receiver, mut rx: mpsc::Receiver { - trc::event!( - Housekeeper(trc::HousekeeperEvent::Run), - Type = "purge_session" - ); - - let server = server.clone(); - queue.schedule( - Instant::now() - + server.core.jmap.session_purge_frequency.time_to_next(), - ActionClass::Session, - ); - - tokio::spawn(async move { - trc::event!(Purge(PurgeEvent::Started), Type = "session"); - server - .inner - .data - .jmap_limiter - .retain(|_, limiter| limiter.is_active()); - - for throttle in [ - &server.inner.data.smtp_session_throttle, - &server.inner.data.smtp_queue_throttle, - ] { - throttle.retain(|_, v| { - v.concurrent.load(Ordering::Relaxed) > 0 - }); - } - }); - } ActionClass::Store(idx) => { if let Some(schedule) = server.core.storage.purge_schedules.get(idx).cloned() diff --git a/crates/managesieve/src/op/authenticate.rs b/crates/managesieve/src/op/authenticate.rs index 9093b788..887e7ac3 100644 --- a/crates/managesieve/src/op/authenticate.rs +++ b/crates/managesieve/src/op/authenticate.rs @@ -9,8 +9,7 @@ use common::{ sasl::{sasl_decode_challenge_oauth, sasl_decode_challenge_plain}, AuthRequest, }, - listener::{limiter::ConcurrencyLimiter, SessionStream}, - ConcurrencyLimiters, + listener::{limiter::LimiterResult, SessionStream}, }; use directory::Permission; use imap_proto::{ @@ -18,7 +17,6 @@ use imap_proto::{ receiver::{self, Request}, }; use mail_parser::decoders::base64::base64_decode; -use std::sync::Arc; use crate::core::{Command, Session, State, StatusResponse}; @@ -104,15 +102,12 @@ impl Session { })?; // Enforce concurrency limits - let in_flight = match self - .get_concurrency_limiter(access_token.primary_id()) - .map(|limiter| limiter.concurrent_requests.is_allowed()) - { - Some(Some(limiter)) => Some(limiter), - None => None, - Some(None) => { + let in_flight = match access_token.is_imap_request_allowed() { + LimiterResult::Allowed(in_flight) => Some(in_flight), + LimiterResult::Forbidden => { return Err(trc::LimitEvent::ConcurrentRequest.into_err()); } + LimiterResult::Disabled => None, }; // Create session @@ -135,27 +130,4 @@ impl Session { Ok(StatusResponse::ok("Unauthenticate successful.").into_bytes()) } - - pub fn get_concurrency_limiter(&self, account_id: u32) -> Option> { - let rate = self.server.core.imap.rate_concurrent?; - self.server - .inner - .data - .imap_limiter - .get(&account_id) - .map(|limiter| limiter.clone()) - .unwrap_or_else(|| { - let limiter = Arc::new(ConcurrencyLimiters { - concurrent_requests: ConcurrencyLimiter::new(rate), - concurrent_uploads: ConcurrencyLimiter::new(rate), - }); - self.server - .inner - .data - .imap_limiter - .insert(account_id, limiter.clone()); - limiter - }) - .into() - } } diff --git a/crates/pop3/src/op/authenticate.rs b/crates/pop3/src/op/authenticate.rs index d600cca9..665247bc 100644 --- a/crates/pop3/src/op/authenticate.rs +++ b/crates/pop3/src/op/authenticate.rs @@ -9,13 +9,11 @@ use common::{ sasl::{sasl_decode_challenge_oauth, sasl_decode_challenge_plain}, AuthRequest, }, - listener::{limiter::ConcurrencyLimiter, SessionStream}, - ConcurrencyLimiters, + listener::{limiter::LimiterResult, SessionStream}, }; use directory::Permission; use mail_parser::decoders::base64::base64_decode; use mail_send::Credentials; -use std::sync::Arc; use crate::{ protocol::{request, Command, Mechanism}, @@ -103,15 +101,12 @@ impl Session { })?; // Enforce concurrency limits - let in_flight = match self - .get_concurrency_limiter(access_token.primary_id()) - .map(|limiter| limiter.concurrent_requests.is_allowed()) - { - Some(Some(limiter)) => Some(limiter), - None => None, - Some(None) => { + let in_flight = match access_token.is_imap_request_allowed() { + LimiterResult::Allowed(in_flight) => Some(in_flight), + LimiterResult::Forbidden => { return Err(trc::LimitEvent::ConcurrentRequest.into_err()); } + LimiterResult::Disabled => None, }; // Fetch mailbox @@ -125,27 +120,4 @@ impl Session { }; self.write_ok("Authentication successful").await } - - pub fn get_concurrency_limiter(&self, account_id: u32) -> Option> { - let rate = self.server.core.imap.rate_concurrent?; - self.server - .inner - .data - .imap_limiter - .get(&account_id) - .map(|limiter| limiter.clone()) - .unwrap_or_else(|| { - let limiter = Arc::new(ConcurrencyLimiters { - concurrent_requests: ConcurrencyLimiter::new(rate), - concurrent_uploads: ConcurrencyLimiter::new(rate), - }); - self.server - .inner - .data - .imap_limiter - .insert(account_id, limiter.clone()); - limiter - }) - .into() - } } diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index 15a6cc64..3deb9f57 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -43,7 +43,6 @@ md5 = "0.7.0" rayon = "1.5" parking_lot = "0.12" regex = "1.7.0" -dashmap = "6.0" blake3 = "1.3" lru-cache = "0.1.2" rand = "0.8.5" diff --git a/crates/smtp/src/core/mod.rs b/crates/smtp/src/core/mod.rs index c6e04436..138b6965 100644 --- a/crates/smtp/src/core/mod.rs +++ b/crates/smtp/src/core/mod.rs @@ -14,7 +14,7 @@ use std::{ use common::{ auth::AccessToken, config::smtp::auth::VerifyStrategy, - listener::{asn::AsnGeoLookupResult, limiter::InFlight, ServerInstance}, + listener::{asn::AsnGeoLookupResult, ServerInstance}, Inner, Server, }; use directory::Directory; @@ -62,7 +62,6 @@ pub struct Session { pub stream: T, pub data: SessionData, pub params: SessionParameters, - pub in_flight: Vec, } pub struct SessionData { @@ -247,7 +246,6 @@ impl Session { can_expn: false, can_vrfy: false, }, - in_flight: vec![], } } diff --git a/crates/smtp/src/core/throttle.rs b/crates/smtp/src/core/throttle.rs index 5dc27be3..2de1952f 100644 --- a/crates/smtp/src/core/throttle.rs +++ b/crates/smtp/src/core/throttle.rs @@ -5,12 +5,12 @@ */ use common::{ - config::smtp::{queue::QueueQuota, *}, + config::smtp::*, expr::{functions::ResolveVariable, *}, - listener::{limiter::ConcurrencyLimiter, SessionStream}, + listener::SessionStream, ThrottleKey, KV_RATE_LIMIT_HASH, }; -use dashmap::mapref::entry::Entry; +use queue::QueueQuota; use trc::SmtpEvent; use utils::config::Rate; @@ -71,7 +71,7 @@ impl NewKey for QueueQuota { } } -impl NewKey for Throttle { +impl NewKey for QueueRateLimiter { fn new_key(&self, e: &impl ResolveVariable) -> ThrottleKey { let mut hasher = blake3::Hasher::new(); @@ -129,13 +129,8 @@ impl NewKey for Throttle { if (self.keys & THROTTLE_LOCAL_IP) != 0 { hasher.update(e.resolve_variable(V_LOCAL_IP).to_string().as_bytes()); } - if let Some(rate_limit) = &self.rate { - hasher.update(&rate_limit.period.as_secs().to_ne_bytes()[..]); - hasher.update(&rate_limit.requests.to_ne_bytes()[..]); - } - if let Some(concurrency) = &self.concurrency { - hasher.update(&concurrency.to_ne_bytes()[..]); - } + hasher.update(&self.rate.period.as_secs().to_ne_bytes()[..]); + hasher.update(&self.rate.requests.to_ne_bytes()[..]); ThrottleKey { hash: hasher.finalize().into(), @@ -146,11 +141,11 @@ impl NewKey for Throttle { impl Session { pub async fn is_allowed(&mut self) -> bool { let throttles = if !self.data.rcpt_to.is_empty() { - &self.server.core.smtp.session.throttle.rcpt_to + &self.server.core.smtp.queue.inbound_limiters.rcpt } else if self.data.mail_from.is_some() { - &self.server.core.smtp.session.throttle.mail_from + &self.server.core.smtp.queue.inbound_limiters.sender } else { - &self.server.core.smtp.session.throttle.connect + &self.server.core.smtp.queue.inbound_limiters.remote }; for t in throttles { @@ -177,69 +172,34 @@ impl Session { // Build throttle key let key = t.new_key(self); - // Check concurrency - if let Some(concurrency) = &t.concurrency { - match self - .server - .inner - .data - .smtp_session_throttle - .entry(key.clone()) - { - Entry::Occupied(mut e) => { - let limiter = e.get_mut(); - if let Some(inflight) = limiter.is_allowed() { - self.in_flight.push(inflight); - } else { - trc::event!( - Smtp(SmtpEvent::ConcurrencyLimitExceeded), - SpanId = self.data.session_id, - Id = t.id.clone(), - Limit = limiter.max_concurrent - ); - return false; - } - } - Entry::Vacant(e) => { - let limiter = ConcurrencyLimiter::new(*concurrency); - if let Some(inflight) = limiter.is_allowed() { - self.in_flight.push(inflight); - } - e.insert(limiter); - } - } - } - // Check rate - if let Some(rate) = &t.rate { - match self - .server - .core - .storage - .lookup - .is_rate_allowed(KV_RATE_LIMIT_HASH, key.hash.as_slice(), rate, false) - .await - { - Ok(Some(_)) => { - trc::event!( - Smtp(SmtpEvent::RateLimitExceeded), - SpanId = self.data.session_id, - Id = t.id.clone(), - Limit = vec![ - trc::Value::from(rate.requests), - trc::Value::from(rate.period) - ], - ); + match self + .server + .core + .storage + .lookup + .is_rate_allowed(KV_RATE_LIMIT_HASH, key.hash.as_slice(), &t.rate, false) + .await + { + Ok(Some(_)) => { + trc::event!( + Smtp(SmtpEvent::RateLimitExceeded), + SpanId = self.data.session_id, + Id = t.id.clone(), + Limit = vec![ + trc::Value::from(t.rate.requests), + trc::Value::from(t.rate.period) + ], + ); - return false; - } - Err(err) => { - trc::error!(err - .span_id(self.data.session_id) - .caused_by(trc::location!())); - } - _ => (), + return false; } + Err(err) => { + trc::error!(err + .span_id(self.data.session_id) + .caused_by(trc::location!())); + } + _ => (), } } } diff --git a/crates/smtp/src/inbound/spawn.rs b/crates/smtp/src/inbound/spawn.rs index 6aff9b04..ee1b367d 100644 --- a/crates/smtp/src/inbound/spawn.rs +++ b/crates/smtp/src/inbound/spawn.rs @@ -23,6 +23,7 @@ impl SessionManager for SmtpSessionManager { async fn handle(self, session: listener::SessionData) { // Build server and create session let server = self.inner.build_server(); + let _in_flight = session.in_flight; let mut session = Session { data: SessionData::new( session.local_ip, @@ -37,7 +38,6 @@ impl SessionManager for SmtpSessionManager { instance: session.instance, state: State::default(), stream: session.stream, - in_flight: vec![session.in_flight], params: SessionParameters::default(), }; @@ -266,7 +266,6 @@ impl Session { data: self.data, instance: self.instance, server: self.server, - in_flight: self.in_flight, params: self.params, }) } diff --git a/crates/smtp/src/outbound/delivery.rs b/crates/smtp/src/outbound/delivery.rs index 762d6eaf..225473fa 100644 --- a/crates/smtp/src/outbound/delivery.rs +++ b/crates/smtp/src/outbound/delivery.rs @@ -39,13 +39,13 @@ use crate::{ }; use super::{lookup::ToNextHop, mta_sts, session::SessionParams, NextHop, TlsStrategy}; -use crate::queue::{throttle, DeliveryAttempt, Domain, Error, QueueEnvelope, Status}; +use crate::queue::{Domain, Error, QueueEnvelope, QueuedMessage, Status}; -impl DeliveryAttempt { +impl QueuedMessage { pub fn try_deliver(self, server: Server) { tokio::spawn(async move { // Lock queue event - let queue_id = self.event.queue_id; + let queue_id = self.queue_id; let status = if server.try_lock_event(queue_id).await { if let Some(mut message) = server.read_message(queue_id).await { // Generate span id @@ -98,8 +98,8 @@ impl DeliveryAttempt { let mut batch = BatchBuilder::new(); batch.clear(ValueClass::Queue(QueueClass::MessageEvent( store::write::QueueEvent { - due: self.event.due, - queue_id: self.event.queue_id, + due: self.due, + queue_id: self.queue_id, }, ))); @@ -138,7 +138,7 @@ impl DeliveryAttempt { }); } - async fn deliver_task(mut self, server: Server, mut message: Message) -> QueueEventStatus { + async fn deliver_task(self, server: Server, mut message: Message) -> QueueEventStatus { // Check that the message still has recipients to be delivered let has_pending_delivery = message.has_pending_delivery(); let span_id = message.span_id; @@ -152,7 +152,7 @@ impl DeliveryAttempt { if due > now() { // Save changes message - .save_changes(&server, self.event.due.into(), due.into()) + .save_changes(&server, self.due.into(), due.into()) .await; return QueueEventStatus::Deferred; } @@ -164,62 +164,36 @@ impl DeliveryAttempt { ); // All message recipients expired, do not re-queue. (DSN has been already sent) - message.remove(&server, self.event.due).await; + message.remove(&server, self.due).await; return QueueEventStatus::Completed; } // Throttle sender - for throttle in &server.core.smtp.queue.throttle.sender { - if let Err(err) = server - .is_allowed(throttle, &message, &mut self.in_flight, message.span_id) - .await - { - let event = match err { - throttle::Error::Concurrency { limiter } => { - // Save changes to disk - let next_due = message.next_event_after(now()); - message.save_changes(&server, None, None).await; + for throttle in &server.core.smtp.queue.outbound_limiters.sender { + if let Err(retry_at) = server.is_allowed(throttle, &message, message.span_id).await { + // Save changes to disk + let next_event = std::cmp::min( + retry_at, + message.next_event_after(now()).unwrap_or(u64::MAX), + ); - trc::event!( - Delivery(DeliveryEvent::ConcurrencyLimitExceeded), - Id = throttle.id.clone(), - SpanId = span_id, - ); + trc::event!( + Delivery(DeliveryEvent::RateLimitExceeded), + Id = throttle.id.clone(), + SpanId = span_id, + NextRetry = trc::Value::Timestamp(next_event) + ); - QueueEventStatus::Limited { - limiters: vec![limiter], - next_due, - } - } - throttle::Error::Rate { retry_at } => { - // Save changes to disk - let next_event = std::cmp::min( - retry_at, - message.next_event_after(now()).unwrap_or(u64::MAX), - ); + message + .save_changes(&server, self.due.into(), next_event.into()) + .await; - trc::event!( - Delivery(DeliveryEvent::RateLimitExceeded), - Id = throttle.id.clone(), - SpanId = span_id, - NextRetry = trc::Value::Timestamp(next_event) - ); - - message - .save_changes(&server, self.event.due.into(), next_event.into()) - .await; - - QueueEventStatus::Deferred - } - }; - - return event; + return QueueEventStatus::Deferred; } } let queue_config = &server.core.smtp.queue; - let mut on_hold = Vec::new(); let no_ip = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); let mut recipients = std::mem::take(&mut message.recipients); 'next_domain: for domain_idx in 0..message.domains.len() { @@ -242,10 +216,9 @@ impl DeliveryAttempt { let mut envelope = QueueEnvelope::new(&message, domain_idx); // Throttle recipient domain - let mut in_flight = Vec::new(); - for throttle in &queue_config.throttle.rcpt { - if let Err(err) = server - .is_allowed(throttle, &envelope, &mut in_flight, message.span_id) + for throttle in &queue_config.outbound_limiters.rcpt { + if let Err(retry_at) = server + .is_allowed(throttle, &envelope, message.span_id) .await { trc::event!( @@ -255,7 +228,7 @@ impl DeliveryAttempt { Domain = domain.domain.clone(), ); - message.domains[domain_idx].set_throttle_error(err, &mut on_hold); + message.domains[domain_idx].set_rate_limiter_error(retry_at); continue 'next_domain; } } @@ -868,11 +841,10 @@ impl DeliveryAttempt { envelope.local_ip = source_ip.unwrap_or(no_ip); // Throttle remote host - let mut in_flight_host = Vec::new(); envelope.remote_ip = remote_ip; - for throttle in &queue_config.throttle.host { - if let Err(err) = server - .is_allowed(throttle, &envelope, &mut in_flight_host, message.span_id) + for throttle in &queue_config.outbound_limiters.remote { + if let Err(retry_at) = server + .is_allowed(throttle, &envelope, message.span_id) .await { trc::event!( @@ -881,7 +853,7 @@ impl DeliveryAttempt { Id = throttle.id.clone(), RemoteIp = remote_ip, ); - message.domains[domain_idx].set_throttle_error(err, &mut on_hold); + message.domains[domain_idx].set_rate_limiter_error(retry_at); continue 'next_domain; } } @@ -1322,21 +1294,7 @@ impl DeliveryAttempt { server.send_dsn(&mut message).await; // Notify queue manager - if !on_hold.is_empty() { - // Save changes to disk - let next_due = message.next_event_after(now()); - message.save_changes(&server, None, None).await; - - trc::event!( - Delivery(DeliveryEvent::ConcurrencyLimitExceeded), - SpanId = span_id, - ); - - QueueEventStatus::Limited { - limiters: on_hold, - next_due, - } - } else if let Some(due) = message.next_event() { + if let Some(due) = message.next_event() { trc::event!( Queue(trc::QueueEvent::Rescheduled), SpanId = span_id, @@ -1347,7 +1305,7 @@ impl DeliveryAttempt { // Save changes to disk message - .save_changes(&server, self.event.due.into(), due.into()) + .save_changes(&server, self.due.into(), due.into()) .await; QueueEventStatus::Deferred @@ -1359,7 +1317,7 @@ impl DeliveryAttempt { ); // Delete message from queue - message.remove(&server, self.event.due).await; + message.remove(&server, self.due).await; QueueEventStatus::Completed } diff --git a/crates/smtp/src/outbound/mod.rs b/crates/smtp/src/outbound/mod.rs index 8e66ed24..fdaad1ee 100644 --- a/crates/smtp/src/outbound/mod.rs +++ b/crates/smtp/src/outbound/mod.rs @@ -6,17 +6,14 @@ use std::borrow::Cow; -use common::{ - config::{ - server::ServerProtocol, - smtp::queue::{RelayHost, RequireOptional}, - }, - ipc::QueuedMessage, +use common::config::{ + server::ServerProtocol, + smtp::queue::{RelayHost, RequireOptional}, }; use mail_send::Credentials; use smtp_proto::{Response, Severity}; -use crate::queue::{DeliveryAttempt, Error, ErrorDetails, HostResponse, Status}; +use crate::queue::{Error, ErrorDetails, HostResponse, Status}; pub mod client; pub mod dane; @@ -203,15 +200,6 @@ impl From for Status<(), Error> { } } -impl DeliveryAttempt { - pub fn new(event: QueuedMessage) -> Self { - DeliveryAttempt { - in_flight: Vec::new(), - event, - } - } -} - #[derive(Debug)] pub enum NextHop<'x> { Relay(&'x RelayHost), diff --git a/crates/smtp/src/queue/manager.rs b/crates/smtp/src/queue/manager.rs index e481f8cf..02f9b7aa 100644 --- a/crates/smtp/src/queue/manager.rs +++ b/crates/smtp/src/queue/manager.rs @@ -22,7 +22,7 @@ use tokio::sync::mpsc; use super::{ spool::{SmtpSpool, QUEUE_REFRESH}, - DeliveryAttempt, Message, QueueId, Status, + Message, QueueId, Status, }; pub struct Queue { @@ -96,13 +96,6 @@ impl Queue { self.on_hold.insert(queue_id, OnHold::Locked { until }); self.on_hold.len() > 1 || has_back_pressure } - QueueEventStatus::Limited { limiters, next_due } => { - self.on_hold.insert( - queue_id, - OnHold::ConcurrencyLimited { limiters, next_due }, - ); - !self.on_hold.is_empty() || has_back_pressure - } QueueEventStatus::Deferred => { self.on_hold.remove(&queue_id); true @@ -129,7 +122,7 @@ impl Queue { if refresh_queue || self.next_wake_up <= Instant::now() { // If the number of in-flight messages is greater than the maximum allowed, skip the queue let server = self.core.build_server(); - let max_in_flight = server.core.smtp.queue.throttle.outbound_concurrency; + let max_in_flight = server.core.smtp.queue.max_threads; has_back_pressure = in_flight_count >= max_in_flight; if has_back_pressure { self.next_wake_up = Instant::now() + Duration::from_secs(QUEUE_REFRESH); @@ -233,7 +226,7 @@ impl Queue { // Deliver message in_flight_count += 1; self.on_hold.insert(queue_event.queue_id, OnHold::InFlight); - DeliveryAttempt::new(*queue_event).try_deliver(server.clone()); + queue_event.try_deliver(server.clone()); } else { let due_in = queue_event.due - now; if due_in < next_wake_up { diff --git a/crates/smtp/src/queue/mod.rs b/crates/smtp/src/queue/mod.rs index c3aa1a5f..c1cb7c3a 100644 --- a/crates/smtp/src/queue/mod.rs +++ b/crates/smtp/src/queue/mod.rs @@ -10,11 +10,7 @@ use std::{ time::{Duration, Instant, SystemTime}, }; -use common::{ - expr::{self, functions::ResolveVariable, *}, - ipc::QueuedMessage, - listener::limiter::InFlight, -}; +use common::expr::{self, functions::ResolveVariable, *}; use serde::{Deserialize, Serialize}; use smtp_proto::Response; use store::write::now; @@ -34,6 +30,12 @@ pub struct Schedule { pub inner: T, } +#[derive(Debug, Clone, Copy)] +pub struct QueuedMessage { + pub due: u64, + pub queue_id: u64, +} + #[derive(Debug, Clone, Copy)] pub enum MessageSource { Authenticated, @@ -131,11 +133,6 @@ pub struct ErrorDetails { pub details: String, } -pub struct DeliveryAttempt { - pub in_flight: Vec, - pub event: QueuedMessage, -} - impl Ord for Schedule { fn cmp(&self, other: &Self) -> std::cmp::Ordering { other.due.cmp(&self.due) diff --git a/crates/smtp/src/queue/spool.rs b/crates/smtp/src/queue/spool.rs index 328f7289..7612aca0 100644 --- a/crates/smtp/src/queue/spool.rs +++ b/crates/smtp/src/queue/spool.rs @@ -5,7 +5,7 @@ */ use crate::queue::DomainPart; -use common::ipc::{QueueEvent, QueuedMessage}; +use common::ipc::QueueEvent; use common::{Server, KV_LOCK_QUEUE_MESSAGE}; use std::borrow::Cow; use std::future::Future; @@ -17,7 +17,8 @@ use trc::ServerEvent; use utils::BlobHash; use super::{ - Domain, Message, MessageSource, QueueEnvelope, QueueId, QuotaKey, Recipient, Schedule, Status, + Domain, Message, MessageSource, QueueEnvelope, QueueId, QueuedMessage, QuotaKey, Recipient, + Schedule, Status, }; pub const LOCK_EXPIRY: u64 = 300; @@ -387,13 +388,11 @@ impl Message { ) -> bool { debug_assert!(prev_event.is_some() == next_event.is_some()); - let mut batch = BatchBuilder::new(); - // Release quota for completed deliveries + let mut batch = BatchBuilder::new(); self.release_quota(&mut batch); // Update message queue - let mut batch = BatchBuilder::new(); if let (Some(prev_event), Some(next_event)) = (prev_event, next_event) { batch .clear(ValueClass::Queue(QueueClass::MessageEvent( diff --git a/crates/smtp/src/queue/throttle.rs b/crates/smtp/src/queue/throttle.rs index 7aef6253..51560dd0 100644 --- a/crates/smtp/src/queue/throttle.rs +++ b/crates/smtp/src/queue/throttle.rs @@ -7,42 +7,30 @@ use std::future::Future; use common::{ - config::smtp::Throttle, - expr::functions::ResolveVariable, - listener::limiter::{ConcurrencyLimiter, InFlight}, - Server, KV_RATE_LIMIT_HASH, + config::smtp::QueueRateLimiter, expr::functions::ResolveVariable, Server, KV_RATE_LIMIT_HASH, }; -use dashmap::mapref::entry::Entry; use store::write::now; use crate::core::throttle::NewKey; use super::{Domain, Status}; -#[derive(Debug)] -pub enum Error { - Concurrency { limiter: ConcurrencyLimiter }, - Rate { retry_at: u64 }, -} - pub trait IsAllowed: Sync + Send { fn is_allowed<'x>( &'x self, - throttle: &'x Throttle, + throttle: &'x QueueRateLimiter, envelope: &impl ResolveVariable, - in_flight: &mut Vec, session_id: u64, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } impl IsAllowed for Server { async fn is_allowed<'x>( &'x self, - throttle: &'x Throttle, + throttle: &'x QueueRateLimiter, envelope: &impl ResolveVariable, - in_flight: &mut Vec, session_id: u64, - ) -> Result<(), Error> { + ) -> Result<(), u64> { if throttle.expr.is_empty() || self .eval_expr(&throttle.expr, envelope, "throttle", session_id) @@ -51,63 +39,30 @@ impl IsAllowed for Server { { let key = throttle.new_key(envelope); - if let Some(rate) = &throttle.rate { - match self - .core - .storage - .lookup - .is_rate_allowed(KV_RATE_LIMIT_HASH, key.as_ref(), rate, false) - .await - { - Ok(Some(next_refill)) => { - trc::event!( - Queue(trc::QueueEvent::RateLimitExceeded), - SpanId = session_id, - Id = throttle.id.clone(), - Limit = vec![ - trc::Value::from(rate.requests), - trc::Value::from(rate.period) - ], - ); + match self + .core + .storage + .lookup + .is_rate_allowed(KV_RATE_LIMIT_HASH, key.as_ref(), &throttle.rate, false) + .await + { + Ok(Some(next_refill)) => { + trc::event!( + Queue(trc::QueueEvent::RateLimitExceeded), + SpanId = session_id, + Id = throttle.id.clone(), + Limit = vec![ + trc::Value::from(throttle.rate.requests), + trc::Value::from(throttle.rate.period) + ], + ); - return Err(Error::Rate { - retry_at: now() + next_refill, - }); - } - Err(err) => { - trc::error!(err.span_id(session_id).caused_by(trc::location!())); - } - _ => (), + return Err(now() + next_refill); } - } - - if let Some(concurrency) = &throttle.concurrency { - match self.inner.data.smtp_queue_throttle.entry(key) { - Entry::Occupied(mut e) => { - let limiter = e.get_mut(); - if let Some(inflight) = limiter.is_allowed() { - in_flight.push(inflight); - } else { - trc::event!( - Queue(trc::QueueEvent::ConcurrencyLimitExceeded), - SpanId = session_id, - Id = throttle.id.clone(), - Limit = limiter.max_concurrent, - ); - - return Err(Error::Concurrency { - limiter: limiter.clone(), - }); - } - } - Entry::Vacant(e) => { - let limiter = ConcurrencyLimiter::new(*concurrency); - if let Some(inflight) = limiter.is_allowed() { - in_flight.push(inflight); - } - e.insert(limiter); - } + Err(err) => { + trc::error!(err.span_id(session_id).caused_by(trc::location!())); } + _ => (), } } @@ -116,16 +71,8 @@ impl IsAllowed for Server { } impl Domain { - pub fn set_throttle_error(&mut self, err: Error, on_hold: &mut Vec) { - match err { - Error::Concurrency { limiter } => { - on_hold.push(limiter); - self.status = Status::TemporaryFailure(super::Error::ConcurrencyLimited); - } - Error::Rate { retry_at } => { - self.retry.due = retry_at; - self.status = Status::TemporaryFailure(super::Error::RateLimited); - } - } + pub fn set_rate_limiter_error(&mut self, retry_at: u64) { + self.retry.due = retry_at; + self.status = Status::TemporaryFailure(super::Error::RateLimited); } } diff --git a/resources/config/spamfilter.toml b/resources/config/spamfilter.toml deleted file mode 100644 index 11c559d7..00000000 --- a/resources/config/spamfilter.toml +++ /dev/null @@ -1,10508 +0,0 @@ -[version] -spam-filter = "1.2" - -[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') && !env.test"; - -# 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')"; - -# LLM model to use for spam classification -let "LLM_MODEL" "key_get('spam-config', 'llm-model')"; - -# LLM prompt to use for spam classification -let "LLM_PROMPT_TEXT" "key_get('spam-config', 'llm-prompt')"; - -# Whether to add an X-Spam-Llm-Result header -let "ADD_HEADER_LLM" "key_get('spam-config', 'add-llm-result')"; - - -#### 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_UNPARSABLE" "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, '= 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, '= 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(dedup(uri_part(tokenize(text_part, 'uri_strict'), 'host')))"; - 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(dedup(uri_part(tokenize(part.text, 'uri_strict'), 'host')))"; - 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.ENCRYPTED_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 llm.sieve #### - -if eval "LLM_MODEL && LLM_PROMPT_TEXT" { - let "llm_result" "trim(split_n(llm_prompt(LLM_MODEL, LLM_PROMPT_TEXT + '\n\nSubject: ' + subject_clean + '\n\n' + text_body, 0.5), ',', 3))"; - - if eval "eq_ignore_case(llm_result[0], 'Unsolicited')" { - if eval "eq_ignore_case(llm_result[1], 'High')" { - let "t.LLM_UNSOLICITED_HIGH" "1"; - } elsif eval "eq_ignore_case(llm_result[1], 'Medium')" { - let "t.LLM_UNSOLICITED_MEDIUM" "1"; - } else { - let "t.LLM_UNSOLICITED_LOW" "1"; - } - } elsif eval "eq_ignore_case(llm_result[0], 'Commercial')" { - if eval "eq_ignore_case(llm_result[1], 'High')" { - let "t.LLM_COMMERCIAL_HIGH" "1"; - } elsif eval "eq_ignore_case(llm_result[1], 'Medium')" { - let "t.LLM_COMMERCIAL_MEDIUM" "1"; - } else { - let "t.LLM_COMMERCIAL_LOW" "1"; - } - } elsif eval "eq_ignore_case(llm_result[0], 'Harmful')" { - if eval "eq_ignore_case(llm_result[1], 'High')" { - let "t.LLM_HARMFUL_HIGH" "1"; - } elsif eval "eq_ignore_case(llm_result[1], 'Medium')" { - let "t.LLM_HARMFUL_MEDIUM" "1"; - } else { - let "t.LLM_HARMFUL_LOW" "1"; - } - } elsif eval "eq_ignore_case(llm_result[0], 'Legitimate')" { - if eval "eq_ignore_case(llm_result[1], 'High')" { - let "t.LLM_LEGITIMATE_HIGH" "1"; - } elsif eval "eq_ignore_case(llm_result[1], 'Medium')" { - let "t.LLM_LEGITIMATE_MEDIUM" "1"; - } else { - let "t.LLM_LEGITIMATE_LOW" "1"; - } - } - - if eval "ADD_HEADER_LLM && count(llm_result) > 2" { - eval "add_header('X-Spam-Llm-Result', 'Category=' + llm_result[0] + '; Confidence=' + llm_result[1] + '; Explanation=' + llm_result[2])"; - } -} - - -#### 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 "!env.test && 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 "!env.test && 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') && !env.test"; - -# 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')"; - -# LLM model to use for spam classification -let "LLM_MODEL" "key_get('spam-config', 'llm-model')"; - -# LLM prompt to use for spam classification -let "LLM_PROMPT_TEXT" "key_get('spam-config', 'llm-prompt')"; - -# Whether to add an X-Spam-Llm-Result header -let "ADD_HEADER_LLM" "key_get('spam-config', 'add-llm-result')"; - - -#### 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') && !env.test"; - -# 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')"; - -# LLM model to use for spam classification -let "LLM_MODEL" "key_get('spam-config', 'llm-model')"; - -# LLM prompt to use for spam classification -let "LLM_PROMPT_TEXT" "key_get('spam-config', 'llm-prompt')"; - -# Whether to add an X-Spam-Llm-Result header -let "ADD_HEADER_LLM" "key_get('spam-config', 'add-llm-result')"; - - -#### 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; -} - -''' - -[sieve.trusted.scripts.train] -name = "Train Bayes Classifier" -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') && !env.test"; - -# 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')"; - -# LLM model to use for spam classification -let "LLM_MODEL" "key_get('spam-config', 'llm-model')"; - -# LLM prompt to use for spam classification -let "LLM_PROMPT_TEXT" "key_get('spam-config', 'llm-prompt')"; - -# Whether to add an X-Spam-Llm-Result header -let "ADD_HEADER_LLM" "key_get('spam-config', 'add-llm-result')"; - - -#### Script train.sieve #### - - - -# Obtain thread name and subject -let "contents" "thread_name(header.subject) + ' ' + body.to_text"; - -if eval "env.train == 'spam'" { - eval "bayes_train(SPAM_DB, contents, true)"; -} elsif eval "env.train == 'ham'" { - eval "bayes_train(SPAM_DB, contents, false)"; -} else { - reject "Missing variable 'train'"; -} - -''' - - -[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" = "", -"llm-model" = "", -"llm-prompt" = "You are an AI assistant specialized in analyzing email content to detect unsolicited, commercial, or harmful messages. Your task is to examine the provided email, including its subject line, and determine if it falls into any of these categories. Please follow these steps: - -- Carefully read the entire email content, including the subject line. -- Look for indicators of unsolicited messages, such as: - * Lack of prior relationship or consent - * Mass-mailing characteristics - * Vague or misleading sender information -- Identify commercial content by checking for: - * Promotional language - * Product or service offerings - * Call-to-action for purchases -- Detect potentially harmful content by searching for: - * Phishing attempts (requests for personal information, suspicious links) - * Malware indicators (suspicious attachments, urgent calls to action) - * Scams or fraudulent schemes -- Analyze the overall tone, intent, and legitimacy of the email. -- Determine the most appropriate single category for the email: Unsolicited, Commercial, Harmful, or Legitimate. -- Assess your confidence level in this determination: High, Medium, or Low. -- Provide a brief explanation for your determination. -- Format your response as follows, separated by commas: Category,Confidence,Explanation - * Example: Unsolicited,High,The email contains mass-mailing characteristics without any prior relationship context. - -Here's the email to analyze, please provide your analysis based on the above instructions, ensuring your response is in the specified comma-separated format:", -"add-llm-result" = true -} - -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" = "1.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_UNPARSABLE" = "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" = "0.5", -"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", -"LLM_UNSOLICITED_HIGH" = "3.0", -"LLM_UNSOLICITED_MEDIUM" = "2.0", -"LLM_UNSOLICITED_LOW" = "0.5", -"LLM_COMMERCIAL_HIGH" = "3.0", -"LLM_COMMERCIAL_MEDIUM" = "2.0", -"LLM_COMMERCIAL_LOW" = "0.5", -"LLM_HARMFUL_HIGH" = "3.0", -"LLM_HARMFUL_MEDIUM" = "2.0", -"LLM_HARMFUL_LOW" = "0.5", -"LLM_LEGITIMATE_HIGH" = "-3.0", -"LLM_LEGITIMATE_MEDIUM" = "-2.0", -"LLM_LEGITIMATE_LOW" = "-0.5"} - -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", -"docm" = "BAD", -"pptm" = "BAD", -"xlsm" = "BAD", -"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/tests/Cargo.toml b/tests/Cargo.toml index 7f5cca38..80d5aa4a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -58,7 +58,6 @@ hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } hyper-util = { version = "0.1.1", features = ["tokio"] } http-body-util = "0.1.0" base64 = "0.22" -dashmap = "6.0" ahash = { version = "0.8" } serial_test = "3.0.0" num_cpus = "1.15.0" diff --git a/tests/resources/acme/config.toml b/tests/resources/acme/config.toml index d5e114b3..bdc13c50 100644 --- a/tests/resources/acme/config.toml +++ b/tests/resources/acme/config.toml @@ -22,9 +22,6 @@ oauth.key = "0Wn7rO4UdmBoE8mp3cDcD9Qlpz3na74z7fGRoSuq8fVsGPelLl3KrHomBN8h2biA" queue.quota.size.enable = true queue.quota.size.messages = 100000 queue.quota.size.size = 10737418240 -queue.throttle.rcpt.concurrency = 5 -queue.throttle.rcpt.enable = true -queue.throttle.rcpt.key = "rcpt_domain" report.analysis.addresses = "postmaster@*" server.http.permissive-cors = true server.listener.http.bind = "[::]:5002" @@ -46,13 +43,6 @@ server.listener.submission.protocol = "smtp" server.listener.submissions.bind = "[::]:465" server.listener.submissions.protocol = "smtp" server.listener.submissions.tls.implicit = true -session.throttle.ip.concurrency = 5 -session.throttle.ip.enable = true -session.throttle.ip.key = "remote_ip" -session.throttle.sender.enable = true -session.throttle.sender.key.0 = "sender_domain" -session.throttle.sender.key.1 = "rcpt" -session.throttle.sender.rate = "25/1h" storage.blob = "rocksdb" storage.data = "rocksdb" storage.directory = "internal" diff --git a/tests/resources/smtp/antispam/rbl.test b/tests/resources/smtp/antispam/rbl.test index 89afc4b4..77f3aeb0 100644 --- a/tests/resources/smtp/antispam/rbl.test +++ b/tests/resources/smtp/antispam/rbl.test @@ -7,7 +7,7 @@ test remote_ip 20.11.0.2 -expect RBL_SENDERSCORE_REPUT_0 RBL_NIXSPAM RBL_SEM RBL_SPAMHAUS_SBL RBL_BARRACUDA RBL_BLOCKLISTDE RBL_VIRUSFREE_BOTNET RBL_SPAMCOP RCVD_IN_DNSWL_MED +expect RBL_SENDERSCORE_REPUT_0 RBL_SEM RBL_SPAMHAUS_SBL RBL_BARRACUDA RBL_BLOCKLISTDE RBL_VIRUSFREE_BOTNET RBL_SPAMCOP RCVD_IN_DNSWL_MED Subject: test diff --git a/tests/resources/smtp/config/throttle.toml b/tests/resources/smtp/config/throttle.toml index 5c8adf03..5082cbf4 100644 --- a/tests/resources/smtp/config/throttle.toml +++ b/tests/resources/smtp/config/throttle.toml @@ -1,12 +1,11 @@ [[throttle]] match = "remote_ip == '127.0.0.1'" key = ["remote_ip", "authenticated_as"] -concurrency = 100 rate = "50/30s" enable = true [[throttle]] key = "sender_domain" -concurrency = 10000 +rate = "50/30s" enable = true diff --git a/tests/src/directory/imap.rs b/tests/src/directory/imap.rs index 4e95c029..cf641d3c 100644 --- a/tests/src/directory/imap.rs +++ b/tests/src/directory/imap.rs @@ -122,7 +122,7 @@ pub fn spawn_mock_imap_server(max_concurrency: u64) -> watch::Sender { //println!("--- Accepted connection --- "); let acceptor = acceptor.clone(); let in_flight = limited.is_allowed(); - tokio::spawn(accept_imap(stream, acceptor, in_flight)); + tokio::spawn(accept_imap(stream, acceptor, in_flight.into())); } Err(err) => { panic!("Something went wrong: {err}" ); diff --git a/tests/src/directory/smtp.rs b/tests/src/directory/smtp.rs index 4e2711ab..14123cd2 100644 --- a/tests/src/directory/smtp.rs +++ b/tests/src/directory/smtp.rs @@ -228,7 +228,7 @@ pub fn spawn_mock_lmtp_server(max_concurrency: u64) -> watch::Sender { Ok((stream, _)) => { let acceptor = acceptor.clone(); let in_flight = limited.is_allowed(); - tokio::spawn(accept_smtp(stream, rx.clone(), acceptor, in_flight)); + tokio::spawn(accept_smtp(stream, rx.clone(), acceptor, in_flight.into())); } Err(err) => { panic!("Something went wrong: {err}" ); diff --git a/tests/src/jmap/auth_limits.rs b/tests/src/jmap/auth_limits.rs index 6640d443..dab06f6e 100644 --- a/tests/src/jmap/auth_limits.rs +++ b/tests/src/jmap/auth_limits.rs @@ -49,7 +49,6 @@ pub async fn test(params: &mut JMAPTest) { .to_string(); // Reset rate limiters - server.inner.data.jmap_limiter.clear(); params.webhook.clear(); // Incorrect passwords should be rejected with a 401 error diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 38dab09e..72160b9c 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -381,7 +381,7 @@ pub async fn jmap_tests() { email_query_changes::test(&mut params).await; email_copy::test(&mut params).await; thread_get::test(&mut params).await; - thread_merge::test(&mut params).await; + thread_merge::test(&mut params).await;*/ mailbox::test(&mut params).await; delivery::test(&mut params).await; auth_acl::test(&mut params).await; @@ -392,7 +392,7 @@ pub async fn jmap_tests() { sieve_script::test(&mut params).await; vacation_response::test(&mut params).await; email_submission::test(&mut params).await; - websocket::test(&mut params).await;*/ + websocket::test(&mut params).await; quota::test(&mut params).await; crypto::test(&mut params).await; blob::test(&mut params).await; diff --git a/tests/src/smtp/config.rs b/tests/src/smtp/config.rs index 23490c0e..cae1973e 100644 --- a/tests/src/smtp/config.rs +++ b/tests/src/smtp/config.rs @@ -9,11 +9,12 @@ use std::{fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration}; use common::{ config::{ server::{Listener, Listeners, ServerProtocol, TcpListener}, - smtp::{throttle::parse_throttle, *}, + smtp::*, }, expr::{functions::ResolveVariable, if_block::*, tokenizer::TokenMap, *}, Server, }; +use throttle::parse_queue_rate_limiter; use tokio::net::TcpSocket; use utils::config::{Config, Rate}; @@ -241,7 +242,7 @@ fn parse_throttles() { file.push("throttle.toml"); let mut config = Config::new(fs::read_to_string(file).unwrap()).unwrap(); - let throttle = parse_throttle( + let throttle = parse_queue_rate_limiter( &mut config, "throttle", &TokenMap::default().with_variables(&[ @@ -261,7 +262,7 @@ fn parse_throttles() { assert_eq!( throttle, vec![ - Throttle { + QueueRateLimiter { id: "0000".to_string(), expr: Expression { items: vec![ @@ -271,19 +272,19 @@ fn parse_throttles() { ] }, keys: THROTTLE_REMOTE_IP | THROTTLE_AUTH_AS, - concurrency: 100.into(), rate: Rate { requests: 50, period: Duration::from_secs(30) } - .into() }, - Throttle { + QueueRateLimiter { id: "0001".to_string(), expr: Expression::default(), keys: THROTTLE_SENDER_DOMAIN, - concurrency: 10000.into(), - rate: None + rate: Rate { + requests: 50, + period: Duration::from_secs(30) + } } ] ); diff --git a/tests/src/smtp/inbound/mail.rs b/tests/src/smtp/inbound/mail.rs index 9ff1f72d..b71b781a 100644 --- a/tests/src/smtp/inbound/mail.rs +++ b/tests/src/smtp/inbound/mail.rs @@ -59,7 +59,7 @@ is-allowed = "sender_domain != 'blocked.com'" size = [{if = "remote_ip = '10.0.0.2'", then = 2048}, {else = 1024}] -[[session.throttle]] +[[queue.limiter.inbound]] match = "remote_ip = '10.0.0.1'" key = 'sender' rate = '2/1s' diff --git a/tests/src/smtp/inbound/mod.rs b/tests/src/smtp/inbound/mod.rs index e8256ae1..4506eebf 100644 --- a/tests/src/smtp/inbound/mod.rs +++ b/tests/src/smtp/inbound/mod.rs @@ -7,7 +7,7 @@ use std::time::Duration; use common::{ - ipc::{DmarcEvent, QueueEvent, QueueEventStatus, QueuedMessage, ReportingEvent, TlsEvent}, + ipc::{DmarcEvent, QueueEvent, QueueEventStatus, ReportingEvent, TlsEvent}, Server, }; use store::{ @@ -16,7 +16,7 @@ use store::{ }; use tokio::sync::mpsc::error::TryRecvError; -use smtp::queue::{DeliveryAttempt, Message, QueueId}; +use smtp::queue::{Message, QueueId, QueuedMessage}; use super::{QueueReceiver, ReportReceiver}; @@ -131,17 +131,17 @@ impl QueueReceiver { message } - pub async fn expect_message_then_deliver(&mut self) -> DeliveryAttempt { + pub async fn expect_message_then_deliver(&mut self) -> QueuedMessage { let message = self.expect_message().await; self.delivery_attempt(message.queue_id).await } - pub async fn delivery_attempt(&mut self, queue_id: u64) -> DeliveryAttempt { - DeliveryAttempt::new(QueuedMessage { + pub async fn delivery_attempt(&mut self, queue_id: u64) -> QueuedMessage { + QueuedMessage { due: self.message_due(queue_id).await, queue_id, - }) + } } pub async fn read_queued_events(&self) -> Vec { @@ -302,7 +302,6 @@ pub trait TestQueueEvent { fn assert_refresh(self); fn assert_done(self); fn assert_refresh_or_done(self); - fn assert_on_hold(self); } impl TestQueueEvent for QueueEvent { @@ -336,16 +335,6 @@ impl TestQueueEvent for QueueEvent { e => panic!("Unexpected event: {e:?}"), } } - - fn assert_on_hold(self) { - match self { - QueueEvent::WorkerDone { - status: QueueEventStatus::Limited { .. }, - .. - } => (), - e => panic!("Unexpected event: {e:?}"), - } - } } pub trait TestReportingEvent { diff --git a/tests/src/smtp/inbound/rcpt.rs b/tests/src/smtp/inbound/rcpt.rs index 3a5da304..103c8f5e 100644 --- a/tests/src/smtp/inbound/rcpt.rs +++ b/tests/src/smtp/inbound/rcpt.rs @@ -74,7 +74,7 @@ wait = [{if = "remote_ip = '10.0.0.1'", then = '5ms'}, dsn = [{if = "remote_ip = '10.0.0.1'", then = false}, {else = true}] -[[session.throttle]] +[[queue.limiter.inbound]] match = "remote_ip = '10.0.0.1' && !is_empty(rcpt)" key = 'sender' rate = '2/1s' diff --git a/tests/src/smtp/inbound/throttle.rs b/tests/src/smtp/inbound/throttle.rs index 28cfdc9e..8536e523 100644 --- a/tests/src/smtp/inbound/throttle.rs +++ b/tests/src/smtp/inbound/throttle.rs @@ -23,19 +23,18 @@ fts = "rocksdb" type = "rocksdb" path = "{TMP}/data.db" -[[session.throttle]] +[[queue.limiter.inbound]] match = "remote_ip = '10.0.0.1'" key = 'remote_ip' -concurrency = 2 -rate = '3/1s' +rate = '2/1s' enable = true -[[session.throttle]] +[[queue.limiter.inbound]] key = 'sender' rate = '2/1s' enable = true -[[session.throttle]] +[[queue.limiter.inbound]] key = ['remote_ip', 'rcpt'] rate = '2/1s' enable = true @@ -52,24 +51,12 @@ async fn throttle_inbound() { let stores = Stores::parse_all(&mut config, false).await; let core = Core::parse(&mut config, stores, Default::default()).await; - // Test connection concurrency limit + // Test connection rate limit let mut session = Session::test(TestSMTP::from_core(core).server); session.data.remote_ip_str = "10.0.0.1".to_string(); - assert!( - session.is_allowed().await, - "Concurrency limiter too strict." - ); - assert!( - session.is_allowed().await, - "Concurrency limiter too strict." - ); - assert!(!session.is_allowed().await, "Concurrency limiter failed."); - - // Test connection rate limit - session.in_flight.clear(); // Manually reset concurrency limiter + assert!(session.is_allowed().await, "Rate limiter too strict."); assert!(session.is_allowed().await, "Rate limiter too strict."); assert!(!session.is_allowed().await, "Rate limiter failed."); - session.in_flight.clear(); tokio::time::sleep(Duration::from_millis(1100)).await; assert!( session.is_allowed().await, diff --git a/tests/src/smtp/outbound/lmtp.rs b/tests/src/smtp/outbound/lmtp.rs index b1e55e67..9482b838 100644 --- a/tests/src/smtp/outbound/lmtp.rs +++ b/tests/src/smtp/outbound/lmtp.rs @@ -12,7 +12,7 @@ use crate::smtp::{ DnsCache, TestSMTP, }; use common::{config::server::ServerProtocol, ipc::QueueEvent}; -use smtp::queue::{spool::SmtpSpool, DeliveryAttempt}; +use smtp::queue::spool::SmtpSpool; use store::write::now; const REMOTE: &str = " @@ -127,7 +127,7 @@ async fn lmtp_delivery() { message.clone().remove(&core, event.due).await; dsn.push(message); } else { - DeliveryAttempt::new(event).try_deliver(core.clone()); + event.try_deliver(core.clone()); tokio::time::sleep(Duration::from_millis(100)).await; } } diff --git a/tests/src/smtp/outbound/smtp.rs b/tests/src/smtp/outbound/smtp.rs index b22c3c6b..de97c9ed 100644 --- a/tests/src/smtp/outbound/smtp.rs +++ b/tests/src/smtp/outbound/smtp.rs @@ -15,7 +15,7 @@ use crate::smtp::{ session::{TestSession, VerifyResponse}, DnsCache, TestSMTP, }; -use smtp::queue::{spool::SmtpSpool, DeliveryAttempt}; +use smtp::queue::spool::SmtpSpool; const LOCAL: &str = r#" [session.rcpt] @@ -159,7 +159,7 @@ async fn smtp_delivery() { for (idx, domain) in message.domains.iter().enumerate() { domain_retries[idx] = domain.retry.inner; } - DeliveryAttempt::new(event).try_deliver(core.clone()); + event.try_deliver(core.clone()); tokio::time::sleep(Duration::from_millis(100)).await; } } diff --git a/tests/src/smtp/outbound/throttle.rs b/tests/src/smtp/outbound/throttle.rs index b2f52392..d5232738 100644 --- a/tests/src/smtp/outbound/throttle.rs +++ b/tests/src/smtp/outbound/throttle.rs @@ -26,37 +26,34 @@ retry = "1h" notify = "1h" expire = "1h" -[[queue.throttle]] +[[queue.limiter.outbound]] match = "sender_domain = 'foobar.org'" key = 'sender_domain' -concurrency = 1 enable = true -[[queue.throttle]] +[[queue.limiter.outbound]] match = "sender_domain = 'foobar.net'" key = 'sender_domain' rate = '1/30m' enable = true -[[queue.throttle]] +[[queue.limiter.outbound]] match = "rcpt_domain = 'example.org'" key = 'rcpt_domain' -concurrency = 1 enable = true -[[queue.throttle]] +[[queue.limiter.outbound]] match = "rcpt_domain = 'example.net'" key = 'rcpt_domain' rate = '1/40m' enable = true -[[queue.throttle]] +[[queue.limiter.outbound]] match = "mx = 'mx.test.org'" key = 'mx' -concurrency = 1 enable = true -[[queue.throttle]] +[[queue.limiter.outbound]] match = "mx = 'mx.test.net'" key = 'mx' rate = '1/50m' @@ -88,43 +85,30 @@ async fn throttle_outbound() { ); // Throttle sender - let mut in_flight = vec![]; - let throttle = &core.core.smtp.queue.throttle; + let throttle = &core.core.smtp.queue.outbound_limiters; for t in &throttle.sender { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 0, ""), - &mut in_flight, - 0, - ) - .await - .unwrap(); + core.is_allowed(t, &QueueEnvelope::test(&test_message, 0, ""), 0) + .await + .unwrap(); } - assert!(!in_flight.is_empty()); // Expect concurrency throttle for sender domain 'foobar.org' - local + /*local .queue_receiver .expect_message_then_deliver() .await .try_deliver(core.clone()); tokio::time::sleep(Duration::from_millis(100)).await; - local.queue_receiver.read_event().await.assert_on_hold(); - in_flight.clear(); + local.queue_receiver.read_event().await.assert_on_hold();*/ // Expect rate limit throttle for sender domain 'foobar.net' test_message.return_path_domain = "foobar.net".to_string(); for t in &throttle.sender { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 0, ""), - &mut in_flight, - 0, - ) - .await - .unwrap(); + core.is_allowed(t, &QueueEnvelope::test(&test_message, 0, ""), 0) + .await + .unwrap(); } - assert!(in_flight.is_empty()); + session .send_message("john@foobar.net", &["bill@test.org"], "test:no_dkim", "250") .await; @@ -148,17 +132,12 @@ async fn throttle_outbound() { status: Status::Scheduled, }); for t in &throttle.rcpt { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 0, ""), - &mut in_flight, - 0, - ) - .await - .unwrap(); + core.is_allowed(t, &QueueEnvelope::test(&test_message, 0, ""), 0) + .await + .unwrap(); } - assert!(!in_flight.is_empty()); - session + + /*session .send_message( "john@test.net", &["jane@example.org"], @@ -172,8 +151,7 @@ async fn throttle_outbound() { .await .try_deliver(core.clone()); tokio::time::sleep(Duration::from_millis(100)).await; - local.queue_receiver.read_event().await.assert_on_hold(); - in_flight.clear(); + local.queue_receiver.read_event().await.assert_on_hold();*/ // Expect rate limit throttle for recipient domain 'example.net' test_message.domains.push(Domain { @@ -184,16 +162,11 @@ async fn throttle_outbound() { status: Status::Scheduled, }); for t in &throttle.rcpt { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 1, ""), - &mut in_flight, - 0, - ) - .await - .unwrap(); + core.is_allowed(t, &QueueEnvelope::test(&test_message, 1, ""), 0) + .await + .unwrap(); } - assert!(in_flight.is_empty()); + session .send_message( "john@test.net", @@ -233,18 +206,13 @@ async fn throttle_outbound() { expires: 0, status: Status::Scheduled, }); - for t in &throttle.host { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 2, "mx.test.org"), - &mut in_flight, - 0, - ) - .await - .unwrap(); + for t in &throttle.remote { + core.is_allowed(t, &QueueEnvelope::test(&test_message, 2, "mx.test.org"), 0) + .await + .unwrap(); } - assert!(!in_flight.is_empty()); - session + + /*session .send_message("john@test.net", &["jane@test.org"], "test:no_dkim", "250") .await; local @@ -252,8 +220,7 @@ async fn throttle_outbound() { .expect_message_then_deliver() .await .try_deliver(core.clone()); - local.queue_receiver.read_event().await.assert_on_hold(); - in_flight.clear(); + local.queue_receiver.read_event().await.assert_on_hold();*/ // Expect rate limit throttle for mx 'mx.test.net' core.mx_add( @@ -269,17 +236,12 @@ async fn throttle_outbound() { vec!["127.0.0.1".parse().unwrap()], Instant::now() + Duration::from_secs(10), ); - for t in &throttle.host { - core.is_allowed( - t, - &QueueEnvelope::test(&test_message, 1, "mx.test.net"), - &mut in_flight, - 0, - ) - .await - .unwrap(); + for t in &throttle.remote { + core.is_allowed(t, &QueueEnvelope::test(&test_message, 1, "mx.test.net"), 0) + .await + .unwrap(); } - assert!(in_flight.is_empty()); + session .send_message("john@test.net", &["jane@test.net"], "test:no_dkim", "250") .await; diff --git a/tests/src/smtp/queue/retry.rs b/tests/src/smtp/queue/retry.rs index e392fed6..e7bbb7af 100644 --- a/tests/src/smtp/queue/retry.rs +++ b/tests/src/smtp/queue/retry.rs @@ -13,7 +13,7 @@ use crate::smtp::{ }; use ahash::AHashSet; use common::ipc::{QueueEvent, QueueEventStatus}; -use smtp::queue::{spool::SmtpSpool, DeliveryAttempt}; +use smtp::queue::spool::SmtpSpool; use store::write::now; const CONFIG: &str = r#" @@ -85,7 +85,7 @@ async fn queue_retry() { let attempt = qr.expect_message_then_deliver().await; let mut dsn = Vec::new(); let mut retries = Vec::new(); - in_fight.insert(attempt.event.queue_id); + in_fight.insert(attempt.queue_id); attempt.try_deliver(core.clone()); loop { @@ -122,7 +122,7 @@ async fn queue_retry() { } else { retries.push(event.due.saturating_sub(now)); in_fight.insert(event.queue_id); - DeliveryAttempt::new(event).try_deliver(core.clone()); + event.try_deliver(core.clone()); tokio::time::sleep(Duration::from_millis(100)).await; } } diff --git a/tests/src/smtp/session.rs b/tests/src/smtp/session.rs index 9f95156c..f301c07e 100644 --- a/tests/src/smtp/session.rs +++ b/tests/src/smtp/session.rs @@ -116,7 +116,6 @@ impl TestSession for Session { 0, ), params: SessionParameters::default(), - in_flight: vec![], hostname: "localhost".to_string(), } }