mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-01-30 03:37:48 +08:00
392 lines
9.5 KiB
TOML
392 lines
9.5 KiB
TOML
#############################################
|
|
# SMTP server configuration
|
|
#############################################
|
|
|
|
[server.listener."smtp"]
|
|
bind = ["[::]:25"]
|
|
greeting = "Stalwart SMTP at your service"
|
|
protocol = "smtp"
|
|
|
|
[server.listener."submission"]
|
|
bind = ["[::]:587"]
|
|
protocol = "smtp"
|
|
|
|
[server.listener."submissions"]
|
|
bind = ["[::]:465"]
|
|
protocol = "smtp"
|
|
tls.implicit = true
|
|
|
|
[server.listener."management"]
|
|
bind = ["127.0.0.1:8080"]
|
|
protocol = "http"
|
|
|
|
[session]
|
|
timeout = "5m"
|
|
transfer-limit = 262144000 # 250 MB
|
|
duration = "10m"
|
|
|
|
[session.connect]
|
|
#script = "connect.sieve"
|
|
|
|
[session.ehlo]
|
|
require = true
|
|
reject-non-fqdn = [ { if = "listener", eq = "smtp", then = true},
|
|
{ else = false } ]
|
|
#script = "ehlo"
|
|
|
|
[session.extensions]
|
|
pipelining = true
|
|
chunking = true
|
|
requiretls = true
|
|
no-soliciting = ""
|
|
dsn = [ { if = "authenticated-as", ne = "", then = true},
|
|
{ else = false } ]
|
|
expn = [ { if = "authenticated-as", ne = "", then = true},
|
|
{ else = false } ]
|
|
vrfy = [ { if = "authenticated-as", ne = "", then = true},
|
|
{ else = false } ]
|
|
future-release = [ { if = "authenticated-as", ne = "", then = "7d"},
|
|
{ else = false } ]
|
|
deliver-by = [ { if = "authenticated-as", ne = "", then = "15d"},
|
|
{ else = false } ]
|
|
mt-priority = [ { if = "authenticated-as", ne = "", then = "mixer"},
|
|
{ else = false } ]
|
|
|
|
[session.auth]
|
|
mechanisms = [ { if = "listener", ne = "smtp", then = ["plain", "login"]},
|
|
{ else = [] } ]
|
|
directory = [ { if = "listener", ne = "smtp", then = "__SMTP_DIRECTORY__" },
|
|
{ else = false } ]
|
|
require = [ { if = "listener", ne = "smtp", then = true},
|
|
{ else = false } ]
|
|
|
|
[session.auth.errors]
|
|
total = 3
|
|
wait = "5s"
|
|
|
|
[session.mail]
|
|
#script = "mail-from"
|
|
#rewrite = [ { all-of = [ { if = "listener", ne = "smtp" },
|
|
# { if = "rcpt", matches = "^([^.]+)@([^.]+)\.(.+)$"},
|
|
# ], then = "${1}@${3}" },
|
|
# { else = false } ]
|
|
|
|
[session.rcpt]
|
|
#script = "rcpt-to"
|
|
relay = [ { if = "authenticated-as", ne = "", then = true },
|
|
{ else = false } ]
|
|
#rewrite = [ { all-of = [ { if = "rcpt-domain", in-list = "__SMTP_DIRECTORY__/domains" },
|
|
# { if = "rcpt", matches = "^([^.]+)\.([^.]+)@(.+)$"},
|
|
# ], then = "${1}+${2}@${3}" },
|
|
# { else = false } ]
|
|
max-recipients = 25
|
|
directory = "__SMTP_DIRECTORY__"
|
|
|
|
[session.rcpt.errors]
|
|
total = 5
|
|
wait = "5s"
|
|
|
|
[session.data]
|
|
#script = "data"
|
|
|
|
#[session.data.milter."rspamd"]
|
|
#enable = [ { if = "listener", eq = "smtp", then = true },
|
|
# { else = false } ]
|
|
#hostname = "127.0.0.1"
|
|
#port = 11332
|
|
#tls = false
|
|
#allow-invalid-certs = false
|
|
|
|
#[session.data.milter."rspamd".timeout]
|
|
#connect = "30s"
|
|
#command = "30s"
|
|
#data = "60s"
|
|
|
|
#[session.data.milter."rspamd".options]
|
|
#tempfail-on-error = true
|
|
#max-response-size = 52428800 # 50mb
|
|
#version = 6
|
|
|
|
#[session.data.pipe."spam-assassin"]
|
|
#command = "spamc"
|
|
#arguments = []
|
|
#timeout = "10s"
|
|
|
|
[session.data.limits]
|
|
messages = 10
|
|
size = 104857600
|
|
received-headers = 50
|
|
|
|
[session.data.add-headers]
|
|
received = [ { if = "listener", eq = "smtp", then = true },
|
|
{ else = false } ]
|
|
received-spf = [ { if = "listener", eq = "smtp", then = true },
|
|
{ else = false } ]
|
|
auth-results = [ { if = "listener", eq = "smtp", then = true },
|
|
{ else = false } ]
|
|
message-id = [ { if = "listener", eq = "smtp", then = false },
|
|
{ else = true } ]
|
|
date = [ { if = "listener", eq = "smtp", then = false },
|
|
{ else = true } ]
|
|
return-path = false
|
|
|
|
[[session.throttle]]
|
|
#match = {if = "remote-ip", eq = "10.0.0.1"}
|
|
key = ["remote-ip"]
|
|
concurrency = 5
|
|
#rate = "5/1h"
|
|
|
|
[[session.throttle]]
|
|
key = ["sender-domain", "rcpt"]
|
|
rate = "25/1h"
|
|
|
|
[auth.dnsbl]
|
|
verify = [ { if = "listener", eq = "smtp", then = ["ip", "iprev", "ehlo", "return-path", "from"] },
|
|
{ else = [] } ]
|
|
|
|
[auth.dnsbl.lookup]
|
|
ip = ["zen.spamhaus.org", "bl.spamcop.net", "b.barracudacentral.org"]
|
|
domain = ["dbl.spamhaus.org"]
|
|
|
|
[auth.iprev]
|
|
verify = [ { if = "listener", eq = "smtp", then = "relaxed" },
|
|
{ else = "disable" } ]
|
|
|
|
[auth.dkim]
|
|
verify = "relaxed"
|
|
sign = [ { if = "listener", ne = "smtp", then = ["rsa"] },
|
|
{ else = [] } ]
|
|
|
|
[auth.spf.verify]
|
|
ehlo = [ { if = "listener", eq = "smtp", then = "relaxed" },
|
|
{ else = "disable" } ]
|
|
mail-from = [ { if = "listener", eq = "smtp", then = "relaxed" },
|
|
{ else = "disable" } ]
|
|
|
|
[auth.arc]
|
|
verify = "relaxed"
|
|
seal = ["rsa"]
|
|
|
|
[auth.dmarc]
|
|
verify = [ { if = "listener", eq = "smtp", then = "relaxed" },
|
|
{ else = "disable" } ]
|
|
|
|
[queue]
|
|
path = "__PATH__/queue"
|
|
hash = 64
|
|
|
|
[queue.schedule]
|
|
retry = ["2m", "5m", "10m", "15m", "30m", "1h", "2h"]
|
|
notify = ["1d", "3d"]
|
|
expire = "5d"
|
|
|
|
[queue.outbound]
|
|
#hostname = "__HOST__"
|
|
next-hop = [ { if = "rcpt-domain", in-list = "__SMTP_DIRECTORY__/domains", then = "__NEXT_HOP__" },
|
|
{ else = false } ]
|
|
ip-strategy = "ipv4-then-ipv6"
|
|
|
|
[queue.outbound.tls]
|
|
dane = "optional"
|
|
mta-sts = "optional"
|
|
starttls = "require"
|
|
|
|
#[queue.outbound.source-ip]
|
|
#v4 = ["10.0.0.10", "10.0.0.11"]
|
|
#v6 = ["a::b", "a::c"]
|
|
|
|
[queue.outbound.limits]
|
|
mx = 7
|
|
multihomed = 2
|
|
|
|
[queue.outbound.timeouts]
|
|
connect = "3m"
|
|
greeting = "3m"
|
|
tls = "2m"
|
|
ehlo = "3m"
|
|
mail-from = "3m"
|
|
rcpt-to = "3m"
|
|
data = "10m"
|
|
mta-sts = "2m"
|
|
|
|
[[queue.quota]]
|
|
#match = {if = "sender-domain", eq = "foobar.org"}
|
|
#key = ["rcpt"]
|
|
messages = 100000
|
|
size = 10737418240 # 10gb
|
|
|
|
[[queue.throttle]]
|
|
key = ["rcpt-domain"]
|
|
#rate = "100/1h"
|
|
concurrency = 5
|
|
|
|
[resolver]
|
|
type = "system"
|
|
#preserve-intermediates = true
|
|
concurrency = 2
|
|
timeout = "5s"
|
|
attempts = 2
|
|
try-tcp-on-error = true
|
|
|
|
[resolver.cache]
|
|
txt = 2048
|
|
mx = 1024
|
|
ipv4 = 1024
|
|
ipv6 = 1024
|
|
ptr = 1024
|
|
tlsa = 1024
|
|
mta-sts = 1024
|
|
|
|
[report]
|
|
path = "__PATH__/reports"
|
|
hash = 64
|
|
#submitter = "__HOST__"
|
|
|
|
[report.analysis]
|
|
addresses = ["dmarc@*", "abuse@*", "postmaster@*"]
|
|
forward = true
|
|
#store = "__PATH__/incoming"
|
|
|
|
[report.dsn]
|
|
from-name = "Mail Delivery Subsystem"
|
|
from-address = "MAILER-DAEMON@__DOMAIN__"
|
|
sign = ["rsa"]
|
|
|
|
[report.dkim]
|
|
from-name = "Report Subsystem"
|
|
from-address = "noreply-dkim@__DOMAIN__"
|
|
subject = "DKIM Authentication Failure Report"
|
|
sign = ["rsa"]
|
|
send = "1/1d"
|
|
|
|
[report.spf]
|
|
from-name = "Report Subsystem"
|
|
from-address = "noreply-spf@__DOMAIN__"
|
|
subject = "SPF Authentication Failure Report"
|
|
send = "1/1d"
|
|
sign = ["rsa"]
|
|
|
|
[report.dmarc]
|
|
from-name = "Report Subsystem"
|
|
from-address = "noreply-dmarc@__DOMAIN__"
|
|
subject = "DMARC Authentication Failure Report"
|
|
send = "1/1d"
|
|
sign = ["rsa"]
|
|
|
|
[report.dmarc.aggregate]
|
|
from-name = "DMARC Report"
|
|
from-address = "noreply-dmarc@__DOMAIN__"
|
|
org-name = "__DOMAIN__"
|
|
#contact-info = ""
|
|
send = "daily"
|
|
max-size = 26214400 # 25mb
|
|
sign = ["rsa"]
|
|
|
|
[report.tls.aggregate]
|
|
from-name = "TLS Report"
|
|
from-address = "noreply-tls@__DOMAIN__"
|
|
org-name = "__DOMAIN__"
|
|
#contact-info = ""
|
|
send = "daily"
|
|
max-size = 26214400 # 25 mb
|
|
sign = ["rsa"]
|
|
|
|
[signature."rsa"]
|
|
#public-key = "file://__PATH__/etc/dkim/__DOMAIN__.cert"
|
|
private-key = "file://__PATH__/etc/dkim/__DOMAIN__.key"
|
|
domain = "__DOMAIN__"
|
|
selector = "stalwart"
|
|
headers = ["From", "To", "Date", "Subject", "Message-ID"]
|
|
algorithm = "rsa-sha256"
|
|
canonicalization = "relaxed/relaxed"
|
|
#expire = "10d"
|
|
#third-party = ""
|
|
#third-party-algo = ""
|
|
#auid = ""
|
|
set-body-length = false
|
|
report = true
|
|
|
|
[remote."lmtp"]
|
|
address = "127.0.0.1"
|
|
port = 11200
|
|
protocol = "lmtp"
|
|
concurrency = 10
|
|
timeout = "1m"
|
|
|
|
[remote."lmtp".tls]
|
|
implicit = false
|
|
allow-invalid-certs = true
|
|
|
|
#[remote."lmtp".auth]
|
|
#username = ""
|
|
#secret = ""
|
|
|
|
[sieve]
|
|
from-name = "Automated Message"
|
|
from-addr = "no-reply@__DOMAIN__"
|
|
return-path = ""
|
|
#hostname = "__HOST__"
|
|
sign = ["rsa"]
|
|
use-directory = "__SMTP_DIRECTORY__"
|
|
|
|
[sieve.limits]
|
|
redirects = 3
|
|
out-messages = 5
|
|
received-headers = 50
|
|
cpu = 10000
|
|
nested-includes = 5
|
|
duplicate-expiry = "7d"
|
|
|
|
[sieve.scripts]
|
|
# Note: These scripts are included here for demonstration purposes.
|
|
# They should not be used in their current form.
|
|
connect = '''
|
|
require ["variables", "extlists", "reject"];
|
|
|
|
if string :list "${env.remote_ip}" "__SMTP_DIRECTORY__/blocked-ips" {
|
|
reject "Your IP '${env.remote_ip}' is not welcomed here.";
|
|
}
|
|
'''
|
|
ehlo = '''
|
|
require ["variables", "extlists", "reject"];
|
|
|
|
if string :list "${env.helo_domain}" "__SMTP_DIRECTORY__/blocked-domains" {
|
|
reject "551 5.1.1 Your domain '${env.helo_domain}' has been blacklisted.";
|
|
}
|
|
'''
|
|
mail = '''
|
|
require ["variables", "envelope", "reject"];
|
|
|
|
if envelope :localpart :is "from" "known_spammer" {
|
|
reject "We do not accept SPAM.";
|
|
}
|
|
'''
|
|
rcpt = '''
|
|
require ["variables", "vnd.stalwart.execute", "envelope", "reject"];
|
|
|
|
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
|
|
|
if not execute :query "SELECT EXISTS(SELECT 1 FROM greylist WHERE addr=? LIMIT 1)" ["${triplet}"] {
|
|
execute :query "INSERT INTO greylist (addr) VALUES (?)" ["${triplet}"];
|
|
reject "422 4.2.2 Greylisted, please try again in a few moments.";
|
|
}
|
|
'''
|
|
data = '''
|
|
require ["envelope", "variables", "replace", "mime", "foreverypart", "editheader", "extracttext"];
|
|
|
|
if envelope :domain :is "to" "foobar.net" {
|
|
set "counter" "a";
|
|
foreverypart {
|
|
if header :mime :contenttype "content-type" "text/html" {
|
|
extracttext :upper "text_content";
|
|
replace "${text_content}";
|
|
}
|
|
set :length "part_num" "${counter}";
|
|
addheader :last "X-Part-Number" "${part_num}";
|
|
set "counter" "${counter}a";
|
|
}
|
|
}
|
|
'''
|
|
|
|
[management]
|
|
directory = "__DIRECTORY__"
|