# Please don't modify this file as your changes might be overwritten with
# the next update.
#
# You can modify 'local.d/mx_check.conf' to add and merge
# parameters defined inside this section
#
# You can modify 'override.d/mx_check.conf' to strictly override all
# parameters defined inside this section
#
# See https://rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories
# for details
#
# Module documentation can be found at  https://rspamd.com/doc/modules/mx_check.html

# Disabled by default. Set `enabled = true` in local.d/mx_check.conf and
# define redis servers.

mx_check {
  enabled = false;

  # TCP probe timeouts. read_timeout only used with verify_greeting = true.
  connect_timeout = 2.0;
  read_timeout = 5.0;

  port = 25;

  # When true, probe reads SMTP banner and validates 3-digit reply code.
  # send_quit issues QUIT after the banner on success.
  verify_greeting = false;
  send_quit = false;

  # Cache TTLs (seconds). expire_dns = 0 disables d:/m: caching.
  # Read timeout (tr) rides `expire`: TCP connected, listener alive --
  # almost always a long greeting delay (postscreen / tarpit), not dead.
  expire = 86400;          # i: good verdict + error:<code> + read timeout (1d)
  expire_dns = 1800;       # d:/m: DNS results (30m)
  expire_novalid = 14400;  # i: hard failures (4h)
  expire_timeout = 7200;   # i: connect timeout (2h)

  reject_null_mx = false;
  reject_null_mx_message = "Domain published RFC 7505 Null MX";

  # Never force-reject authenticated / locally-originated traffic.
  reject_authorized = false;
  reject_local = false;

  # Greylist advice for recoverable failures.
  greylist_invalid = true;
  greylist_none = true;
  greylist_broken = true;
  greylist_refused = true;
  greylist_null = true;
  greylist_timeout_connect = true;
  # Postfix postscreen delays the banner ~6s; raise read_timeout to 7-10s
  # before disabling this.
  greylist_timeout_read = true;

  # Never greylist authenticated / locally-originated traffic.
  greylist_authorized = false;
  greylist_local = false;

  # Opt back into checking authenticated / local traffic (ESP use case).
  check_authorized = false;
  check_local = false;

  # Source domains. One probe + one symbol per unique domain, prefixed
  # per symbol_prefix_* (highest-priority source wins: from > reply_to > mime_from).
  check_from = true;
  check_mime_from = true;
  check_reply_to = true;

  # Address-family controls. probe_ipv6 stays false until outbound v6:25
  # is verified end-to-end. prefer_ipv6 only takes effect with both on.
  probe_ipv4 = true;
  probe_ipv6 = false;
  prefer_ipv6 = true;

  # Cap on MX targets (step 2) and A/AAAA fan-out (step 3). Smaller =
  # fewer Redis lookups on cache miss. Probe still only dials ips[1].
  max_mx_a_records = 3;

  # Per-source symbol prefixes. Envelope-from is unprefixed.
  symbol_prefix_from = "";
  symbol_prefix_mime_from = "MIME_FROM_";
  symbol_prefix_reply_to = "REPLYTO_";

  # MX_INVALID fires only when TCP accepts but the listener doesn't speak
  # SMTP. Every other failure has its own symbol below.
  symbol_bad_mx = "MX_INVALID";
  symbol_good_mx = "MX_GOOD";
  symbol_white_mx = "MX_WHITE";

  # MX-RR path outcomes.
  symbol_mx_refused = "MX_REFUSED";
  symbol_mx_timeout_connect = "MX_TIMEOUT_CONNECT";
  symbol_mx_timeout_read = "MX_TIMEOUT_READ";
  symbol_mx_error = "MX_ERROR";
  symbol_mx_none = "MX_NONE";
  symbol_mx_null = "MX_NULL";
  symbol_mx_broken = "MX_BROKEN";

  # A-fallback path (RFC 5321 §5.1: no MX, A used as implicit MX).
  # Stronger evidence than MX-RR equivalents -- parked-domain shape.
  symbol_mx_a_good = "MX_A_GOOD";
  symbol_mx_a_refused = "MX_A_REFUSED";
  symbol_mx_a_timeout_connect = "MX_A_TIMEOUT_CONNECT";
  symbol_mx_a_timeout_read = "MX_A_TIMEOUT_READ";
  symbol_mx_a_error = "MX_A_ERROR";
  symbol_mx_a_invalid = "MX_A_INVALID";

  # Our-side DNS path failure (SERVFAIL/REFUSED/timeout). Sender not at fault.
  symbol_mx_dns_fail = "MX_DNS_FAIL";

  # IP-class symbols. _ONLY = every IP in class, no probe run.
  # _MIX = some in class, public subset probed.
  symbol_mx_local_only = "MX_LOCAL_ONLY";    # RFC1918 / CGNAT / ULA
  symbol_mx_local_mix = "MX_LOCAL_MIX";
  symbol_mx_bogon_only = "MX_BOGON_ONLY";    # loopback, TEST-NET, multicast, etc.
  symbol_mx_bogon_mix = "MX_BOGON_MIX";

  symbol_mx_skip = "MX_SKIP";                  # exclude_ips dropped every routable IP
  symbol_mx_bad = "MX_BAD";                    # bad_mxs hit: punishment short-circuit
  symbol_mx_ip_bad = "MX_IP_BAD";              # bad_ips hit: punishment short-circuit
  symbol_mx_inflight = "MX_INFLIGHT";          # another worker holds the i-layer lock
  symbol_mx_redis_error = "MX_REDIS_ERROR";    # Redis failed during lock claim; probe skipped

  # Per-layer trust/skip maps. exclude_domains/exclude_mxs are trust hits
  # (-> MX_WHITE). exclude_ips drops IPs from the probe set; full match
  # fires MX_SKIP.
  # exclude_domains = "/etc/rspamd/local.d/maps.d/mx_check_exclude_domains.inc";
  # exclude_mxs = "/etc/rspamd/local.d/maps.d/mx_check_mx_whitelist.inc";
  # exclude_ips = "/etc/rspamd/local.d/maps.d/mx_check_ip_skip.inc";

  # Punishment maps. Hit -> short-circuit with MX_BAD / MX_IP_BAD. bad_mxs
  # is glob on MX hostnames; bad_ips is radix on resolved MX IPs. No probing
  # or further symbol emission past the match.
  # bad_mxs = "/etc/rspamd/local.d/maps.d/mx_check_bad_mxs.inc";
  # bad_ips = "/etc/rspamd/local.d/maps.d/mx_check_bad_ips.inc";

  # Cache layers live under <key_prefix>:d:<domain>, :m:<mx>, :i:<ip>.
  key_prefix = "rmx";

  .include(try=true,priority=5) "${DBDIR}/dynamic/mx_check.conf"
  .include(try=true,priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/mx_check.conf"
  .include(try=true,priority=10) "$LOCAL_CONFDIR/override.d/mx_check.conf"
}
