Use safe defaults when settings are missing
This commit is contained in:
94
resources/config/build.py
Normal file
94
resources/config/build.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import os
|
||||
|
||||
# Define the scripts and their component files
|
||||
scripts = {
|
||||
"spam-filter": [
|
||||
"config.sieve",
|
||||
"prelude.sieve",
|
||||
"from.sieve",
|
||||
"recipient.sieve",
|
||||
"subject.sieve",
|
||||
"replyto.sieve",
|
||||
"date.sieve",
|
||||
"messageid.sieve",
|
||||
"received.sieve",
|
||||
"headers.sieve",
|
||||
"bounce.sieve",
|
||||
"html.sieve",
|
||||
"mime.sieve",
|
||||
"dmarc.sieve",
|
||||
"ip.sieve",
|
||||
"helo.sieve",
|
||||
"replies_in.sieve",
|
||||
"spamtrap.sieve",
|
||||
"bayes_classify.sieve",
|
||||
"url.sieve",
|
||||
"rbl.sieve",
|
||||
"pyzor.sieve",
|
||||
"composites.sieve",
|
||||
"scores.sieve",
|
||||
"reputation.sieve",
|
||||
"epilogue.sieve"
|
||||
],
|
||||
"track-replies": [
|
||||
"config.sieve",
|
||||
"replies_out.sieve"
|
||||
],
|
||||
"greylist": [
|
||||
"config.sieve",
|
||||
"greylist.sieve"
|
||||
]
|
||||
}
|
||||
script_names = {
|
||||
"spam-filter" : "Spam Filter",
|
||||
"track-replies" : "Track Replies",
|
||||
"greylist" : "Greylisting"
|
||||
}
|
||||
|
||||
maps = ["scores.map",
|
||||
"allow_dmarc.list",
|
||||
"allow_domains.list",
|
||||
"allow_spf_dkim.list",
|
||||
"domains_disposable.list",
|
||||
"domains_free.list",
|
||||
"mime_types.map",
|
||||
"url_redirectors.list"]
|
||||
|
||||
|
||||
def read_and_concatenate(files):
|
||||
content = ""
|
||||
for file in files:
|
||||
with open(os.path.join("./spamfilter/scripts", file), "r", encoding="utf-8") as f:
|
||||
content += "\n#### Script " + file + " ####\n\n"
|
||||
content += f.read() + "\n"
|
||||
return content
|
||||
|
||||
def read_file(file):
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
return f.read() + "\n"
|
||||
|
||||
def build_spam_filters(scripts):
|
||||
spam_filter = read_file("./spamfilter/settings.toml")
|
||||
for script_name, file_list in scripts.items():
|
||||
script_content = read_and_concatenate(file_list).replace("'''", "\\'\\'\\'")
|
||||
script_description = script_names[script_name]
|
||||
spam_filter += f"[sieve.trusted.scripts.{script_name}]\nname = \"{script_description}\"\ncontents = '''\n{script_content}'''\n\n"
|
||||
|
||||
spam_filter += "\n[lookup]\n"
|
||||
for map in maps :
|
||||
with open(os.path.join("./spamfilter/maps", map), "r", encoding="utf-8") as f:
|
||||
spam_filter += f.read() + "\n"
|
||||
|
||||
return spam_filter
|
||||
|
||||
def main():
|
||||
spam_filter = build_spam_filters(scripts)
|
||||
with open("spamfilter.toml", "w", encoding="utf-8") as toml_file:
|
||||
toml_file.write(spam_filter)
|
||||
config = read_file("./minimal.toml") + read_file("./security.toml") + spam_filter
|
||||
with open("config.toml", "w", encoding="utf-8") as toml_file:
|
||||
toml_file.write(config)
|
||||
print("Stalwart TOML configuration files have been generated.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,35 +0,0 @@
|
||||
#############################################
|
||||
# Cache configuration
|
||||
#############################################
|
||||
|
||||
[cache]
|
||||
capacity = 512
|
||||
shard = 32
|
||||
|
||||
[cache.session]
|
||||
ttl = "1h"
|
||||
|
||||
[cache.account]
|
||||
size = 2048
|
||||
|
||||
[cache.mailbox]
|
||||
size = 2048
|
||||
|
||||
[cache.thread]
|
||||
size = 2048
|
||||
|
||||
[cache.bayes]
|
||||
capacity = 8192
|
||||
|
||||
[cache.bayes.ttl]
|
||||
positive = "1h"
|
||||
negative = "1h"
|
||||
|
||||
[cache.resolver]
|
||||
txt = 2048
|
||||
mx = 1024
|
||||
ipv4 = 1024
|
||||
ipv6 = 1024
|
||||
ptr = 1024
|
||||
tlsa = 1024
|
||||
mta-sts = 1024
|
||||
@@ -1,37 +0,0 @@
|
||||
#############################################
|
||||
# Server configuration
|
||||
#############################################
|
||||
|
||||
[server]
|
||||
hostname = "%{HOST}%"
|
||||
max-connections = 8192
|
||||
|
||||
#[server.proxy]
|
||||
#trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8"]
|
||||
|
||||
[authentication]
|
||||
fail2ban = "100/1d"
|
||||
rate-limit = "10/1m"
|
||||
|
||||
[server.run-as]
|
||||
user = "stalwart-mail"
|
||||
group = "stalwart-mail"
|
||||
|
||||
[server.socket]
|
||||
nodelay = true
|
||||
reuse-addr = true
|
||||
#reuse-port = true
|
||||
backlog = 1024
|
||||
#ttl = 3600
|
||||
#send-buffer-size = 65535
|
||||
#recv-buffer-size = 65535
|
||||
#linger = 1
|
||||
#tos = 1
|
||||
|
||||
[global]
|
||||
#thread-pool = 8
|
||||
|
||||
[server.http]
|
||||
#headers = ["Access-Control-Allow-Origin: *",
|
||||
# "Access-Control-Allow-Methods: POST, GET, PATCH, PUT, DELETE, HEAD, OPTIONS",
|
||||
# "Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With"]
|
||||
@@ -1,73 +0,0 @@
|
||||
#############################################
|
||||
# Sieve untrusted runtime configuration
|
||||
#############################################
|
||||
|
||||
[sieve.untrusted]
|
||||
disable-capabilities = []
|
||||
notification-uris = ["mailto"]
|
||||
protected-headers = ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]
|
||||
|
||||
[sieve.untrusted.limits]
|
||||
name-length = 512
|
||||
max-scripts = 256
|
||||
script-size = 102400
|
||||
string-length = 4096
|
||||
variable-name-length = 32
|
||||
variable-size = 4096
|
||||
nested-blocks = 15
|
||||
nested-tests = 15
|
||||
nested-foreverypart = 3
|
||||
match-variables = 30
|
||||
local-variables = 128
|
||||
header-size = 1024
|
||||
includes = 3
|
||||
nested-includes = 3
|
||||
cpu = 5000
|
||||
redirects = 1
|
||||
received-headers = 10
|
||||
outgoing-messages = 3
|
||||
|
||||
[sieve.untrusted.vacation]
|
||||
default-subject = "Automated reply"
|
||||
subject-prefix = "Auto: "
|
||||
|
||||
[sieve.untrusted.default-expiry]
|
||||
vacation = "30d"
|
||||
duplicate = "7d"
|
||||
|
||||
#############################################
|
||||
# Sieve trusted runtime configuration
|
||||
#############################################
|
||||
|
||||
[sieve.trusted]
|
||||
from-name = "Automated Message"
|
||||
from-addr = "no-reply@%{DEFAULT_DOMAIN}%"
|
||||
return-path = ""
|
||||
#hostname = "%{HOST}%"
|
||||
no-capability-check = true
|
||||
sign = ["rsa"]
|
||||
|
||||
[sieve.trusted.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
cpu = 1048576
|
||||
nested-includes = 5
|
||||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.trusted.scripts]
|
||||
#connect = '''require ["variables", "extlists", "reject"];
|
||||
# if string :list "${env.remote_ip}" "default/blocked-ips" {
|
||||
# reject "Your IP '${env.remote_ip}' is not welcomed here.";
|
||||
# }'''
|
||||
|
||||
#ehlo = '''require ["variables", "extlists", "reject"];
|
||||
# if string :list "${env.helo_domain}" "default/blocked-domains" {
|
||||
# reject "551 5.1.1 Your domain '${env.helo_domain}' has been blacklisted.";
|
||||
# }'''
|
||||
|
||||
#mail = '''require ["variables", "envelope", "reject"];
|
||||
# if envelope :localpart :is "from" "known_spammer" {
|
||||
# reject "We do not accept SPAM.";
|
||||
# }'''
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#############################################
|
||||
# Storage configuration
|
||||
#############################################
|
||||
|
||||
[storage]
|
||||
data = "%{DEFAULT_STORE}%"
|
||||
fts = "%{DEFAULT_STORE}%"
|
||||
blob = "%{DEFAULT_STORE}%"
|
||||
lookup = "%{DEFAULT_STORE}%"
|
||||
directory = "%{DEFAULT_DIRECTORY}%"
|
||||
|
||||
[storage.encryption]
|
||||
enable = true
|
||||
append = false
|
||||
|
||||
[storage.full-text]
|
||||
default-language = "en"
|
||||
|
||||
[storage.cluster]
|
||||
node-id = 1
|
||||
@@ -1,30 +0,0 @@
|
||||
#############################################
|
||||
# TLS default configuration
|
||||
#############################################
|
||||
|
||||
[server.tls]
|
||||
enable = true
|
||||
implicit = false
|
||||
timeout = "1m"
|
||||
certificate = "default"
|
||||
#acme = "letsencrypt"
|
||||
#protocols = ["TLSv1.2", "TLSv1.3"]
|
||||
#ciphers = [ "TLS13_AES_256_GCM_SHA384", "TLS13_AES_128_GCM_SHA256",
|
||||
# "TLS13_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
# "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
# "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"]
|
||||
ignore-client-order = true
|
||||
|
||||
[acme."letsencrypt"]
|
||||
directory = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
#directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
contact = ["postmaster@%{DEFAULT_DOMAIN}%"]
|
||||
cache = "%{BASE_PATH}%/etc/acme"
|
||||
port = 443
|
||||
renew-before = "30d"
|
||||
|
||||
[certificate."default"]
|
||||
sni-subjects = []
|
||||
cert = "file://__CERT_PATH__"
|
||||
private-key = "file://__PK_PATH__"
|
||||
@@ -1,24 +0,0 @@
|
||||
#############################################
|
||||
# Tracing & logging configuration
|
||||
#############################################
|
||||
|
||||
[tracing."stdout"]
|
||||
method = "stdout"
|
||||
level = "trace"
|
||||
enable = false
|
||||
|
||||
[tracing."ot"]
|
||||
method = "open-telemetry"
|
||||
transport = "http"
|
||||
endpoint = "https://127.0.0.1/otel"
|
||||
headers = ["Authorization: <place_auth_here>"]
|
||||
level = "debug"
|
||||
enable = false
|
||||
|
||||
[tracing."log"]
|
||||
method = "log"
|
||||
path = "%{BASE_PATH}%/logs"
|
||||
prefix = "stalwart.log"
|
||||
rotate = "daily"
|
||||
level = "info"
|
||||
enable = true
|
||||
@@ -1,52 +0,0 @@
|
||||
#############################################
|
||||
# Stalwart Mail Server Configuration File
|
||||
#############################################
|
||||
|
||||
[macros]
|
||||
host = "__HOST__"
|
||||
default_domain = "__DOMAIN__"
|
||||
base_path = "__BASE_PATH__"
|
||||
default_directory = "__DIRECTORY__"
|
||||
default_store = "__STORE__"
|
||||
|
||||
[include]
|
||||
files = [ "%{BASE_PATH}%/etc/common/server.toml",
|
||||
"%{BASE_PATH}%/etc/common/tls.toml",
|
||||
"%{BASE_PATH}%/etc/common/store.toml",
|
||||
"%{BASE_PATH}%/etc/common/tracing.toml",
|
||||
"%{BASE_PATH}%/etc/common/sieve.toml",
|
||||
"%{BASE_PATH}%/etc/common/cache.toml",
|
||||
"%{BASE_PATH}%/etc/directory/imap.toml",
|
||||
"%{BASE_PATH}%/etc/directory/internal.toml",
|
||||
"%{BASE_PATH}%/etc/directory/ldap.toml",
|
||||
"%{BASE_PATH}%/etc/directory/lmtp.toml",
|
||||
"%{BASE_PATH}%/etc/directory/memory.toml",
|
||||
"%{BASE_PATH}%/etc/directory/sql.toml",
|
||||
"%{BASE_PATH}%/etc/store/elasticsearch.toml",
|
||||
"%{BASE_PATH}%/etc/store/fs.toml",
|
||||
"%{BASE_PATH}%/etc/store/foundationdb.toml",
|
||||
"%{BASE_PATH}%/etc/store/mysql.toml",
|
||||
"%{BASE_PATH}%/etc/store/postgresql.toml",
|
||||
"%{BASE_PATH}%/etc/store/redis.toml",
|
||||
"%{BASE_PATH}%/etc/store/rocksdb.toml",
|
||||
"%{BASE_PATH}%/etc/store/s3.toml",
|
||||
"%{BASE_PATH}%/etc/store/sqlite.toml",
|
||||
"%{BASE_PATH}%/etc/imap/listener.toml",
|
||||
"%{BASE_PATH}%/etc/imap/settings.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/auth.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/listener.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/oauth.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/protocol.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/push.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/ratelimit.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/websockets.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/auth.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/listener.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/milter.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/queue.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/remote.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/report.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/resolver.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/session.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/signature.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/spamfilter.toml" ]
|
||||
@@ -1,29 +0,0 @@
|
||||
#############################################
|
||||
# IMAP Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."imap"]
|
||||
type = "imap"
|
||||
host = "127.0.0.1"
|
||||
port = 993
|
||||
disable = true
|
||||
|
||||
[directory."imap".pool]
|
||||
max-connections = 10
|
||||
|
||||
[directory."imap".pool.timeout]
|
||||
create = "30s"
|
||||
wait = "30s"
|
||||
recycle = "30s"
|
||||
|
||||
[directory."imap".tls]
|
||||
enable = true
|
||||
allow-invalid-certs = true
|
||||
|
||||
[directory."imap".cache]
|
||||
entries = 500
|
||||
ttl = {positive = '1h', negative = '10m'}
|
||||
|
||||
[directory."imap".lookup]
|
||||
domains = ["%{DEFAULT_DOMAIN}%"]
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#############################################
|
||||
# Internal Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."internal"]
|
||||
type = "internal"
|
||||
store = "%{DEFAULT_STORE}%"
|
||||
disable = true
|
||||
|
||||
[directory."internal".options]
|
||||
catch-all = true
|
||||
#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
|
||||
# { else = false } ]
|
||||
subaddressing = true
|
||||
#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
|
||||
[directory."internal".cache]
|
||||
entries = 500
|
||||
ttl = {positive = '1h', negative = '10m'}
|
||||
@@ -1,60 +0,0 @@
|
||||
#############################################
|
||||
# LDAP Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."ldap"]
|
||||
type = "ldap"
|
||||
url = "ldap://localhost:389"
|
||||
base-dn = "dc=example,dc=org"
|
||||
timeout = "30s"
|
||||
disable = true
|
||||
|
||||
[directory."ldap".bind]
|
||||
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
|
||||
secret = "mysecret"
|
||||
|
||||
[directory."ldap".bind.auth]
|
||||
enable = false
|
||||
dn = "cn=?,ou=svcaccts,dc=example,dc=org"
|
||||
|
||||
[directory."ldap".tls]
|
||||
enable = false
|
||||
allow-invalid-certs = false
|
||||
|
||||
[directory."ldap".cache]
|
||||
entries = 500
|
||||
ttl = {positive = '1h', negative = '10m'}
|
||||
|
||||
[directory."ldap".options]
|
||||
catch-all = true
|
||||
#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
|
||||
# { else = false } ]
|
||||
subaddressing = true
|
||||
#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
|
||||
[directory."ldap".pool]
|
||||
max-connections = 10
|
||||
|
||||
[directory."ldap".pool.timeout]
|
||||
create = "30s"
|
||||
wait = "30s"
|
||||
recycle = "30s"
|
||||
|
||||
[directory."ldap".filter]
|
||||
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
|
||||
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(mailAlias=?)(mailList=?)))"
|
||||
verify = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*?*)(mailAlias=*?*)))"
|
||||
expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(mailList=?))"
|
||||
domains = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*@?)(mailAlias=*@?)))"
|
||||
|
||||
[directory."ldap".attributes]
|
||||
name = "uid"
|
||||
class = "objectClass"
|
||||
description = ["principalName", "description"]
|
||||
secret = "userPassword"
|
||||
groups = ["memberOf", "otherGroups"]
|
||||
email = "mail"
|
||||
email-alias = "mailAlias"
|
||||
quota = "diskQuota"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#############################################
|
||||
# LMTP Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."lmtp"]
|
||||
type = "lmtp"
|
||||
host = "127.0.0.1"
|
||||
port = 11200
|
||||
disable = true
|
||||
|
||||
[directory."lmtp".limits]
|
||||
auth-errors = 3
|
||||
rcpt = 5
|
||||
|
||||
[directory."lmtp".pool]
|
||||
max-connections = 10
|
||||
|
||||
[directory."lmtp".pool.timeout]
|
||||
create = "30s"
|
||||
wait = "30s"
|
||||
recycle = "30s"
|
||||
|
||||
[directory."lmtp".tls]
|
||||
enable = false
|
||||
allow-invalid-certs = true
|
||||
|
||||
[directory."lmtp".cache]
|
||||
entries = 500
|
||||
ttl = {positive = '1h', negative = '10m'}
|
||||
|
||||
[directory."lmtp".lookup]
|
||||
domains = ["%{DEFAULT_DOMAIN}%"]
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#############################################
|
||||
# In-Memory Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."memory"]
|
||||
type = "memory"
|
||||
disable = true
|
||||
|
||||
[directory."memory".options]
|
||||
catch-all = true
|
||||
#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
|
||||
# { else = false } ]
|
||||
subaddressing = true
|
||||
#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "admin"
|
||||
class = "admin"
|
||||
description = "Superuser"
|
||||
secret = "changeme"
|
||||
email = ["postmaster@%{DEFAULT_DOMAIN}%"]
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "john"
|
||||
class = "individual"
|
||||
description = "John Doe"
|
||||
secret = "12345"
|
||||
email = ["john@%{DEFAULT_DOMAIN}%", "jdoe@%{DEFAULT_DOMAIN}%", "john.doe@%{DEFAULT_DOMAIN}%"]
|
||||
email-list = ["info@%{DEFAULT_DOMAIN}%"]
|
||||
member-of = ["sales"]
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "jane"
|
||||
class = "individual"
|
||||
description = "Jane Doe"
|
||||
secret = "abcde"
|
||||
email = ["jane@%{DEFAULT_DOMAIN}%", "jane.doe@%{DEFAULT_DOMAIN}%"]
|
||||
email-list = ["info@%{DEFAULT_DOMAIN}%"]
|
||||
member-of = ["sales", "support"]
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "bill"
|
||||
class = "individual"
|
||||
description = "Bill Foobar"
|
||||
secret = "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe"
|
||||
quota = 50000000
|
||||
email = ["bill@%{DEFAULT_DOMAIN}%", "bill.foobar@%{DEFAULT_DOMAIN}%"]
|
||||
email-list = ["info@%{DEFAULT_DOMAIN}%"]
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "sales"
|
||||
class = "group"
|
||||
description = "Sales Team"
|
||||
|
||||
[[directory."memory".principals]]
|
||||
name = "support"
|
||||
class = "group"
|
||||
description = "Support Team"
|
||||
@@ -1,26 +0,0 @@
|
||||
#############################################
|
||||
# SQL Directory configuration
|
||||
#############################################
|
||||
|
||||
[directory."sql"]
|
||||
type = "sql"
|
||||
store = "__SQL_STORE__"
|
||||
disable = true
|
||||
|
||||
[directory."sql".options]
|
||||
catch-all = true
|
||||
#catch-all = [ { if = "matches('(.+)@(.+)$', address)", then = "'info@' + $2" },
|
||||
# { else = false } ]
|
||||
subaddressing = true
|
||||
#subaddressing = [ { if = "matches('^([^.]+)\\.([^.]+)@(.+)$', address)", then = "$2 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
|
||||
[directory."sql".cache]
|
||||
entries = 500
|
||||
ttl = {positive = '1h', negative = '10m'}
|
||||
|
||||
[directory."sql".columns]
|
||||
class = "type"
|
||||
secret = "secret"
|
||||
description = "description"
|
||||
quota = "quota"
|
||||
@@ -1,16 +0,0 @@
|
||||
#############################################
|
||||
# IMAP server listeners configuration
|
||||
#############################################
|
||||
|
||||
[server.listener."imap"]
|
||||
bind = ["[::]:143"]
|
||||
protocol = "imap"
|
||||
|
||||
[server.listener."imaptls"]
|
||||
bind = ["[::]:993"]
|
||||
protocol = "imap"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener."sieve"]
|
||||
bind = ["[::]:4190"]
|
||||
protocol = "managesieve"
|
||||
@@ -1,22 +0,0 @@
|
||||
#############################################
|
||||
# IMAP server settings
|
||||
#############################################
|
||||
|
||||
[imap.request]
|
||||
max-size = 52428800
|
||||
|
||||
[imap.auth]
|
||||
max-failures = 3
|
||||
allow-plain-text = false
|
||||
|
||||
[imap.folders.name]
|
||||
shared = "Shared Folders"
|
||||
|
||||
[imap.timeout]
|
||||
authenticated = "30m"
|
||||
anonymous = "1m"
|
||||
idle = "30m"
|
||||
|
||||
[imap.rate-limit]
|
||||
requests = "2000/1m"
|
||||
concurrent = 6
|
||||
@@ -1,6 +0,0 @@
|
||||
#############################################
|
||||
# JMAP authentication & session configuration
|
||||
#############################################
|
||||
|
||||
[jmap.session.purge]
|
||||
frequency = "15 * *"
|
||||
@@ -1,14 +0,0 @@
|
||||
#############################################
|
||||
# JMAP server listener configuration
|
||||
#############################################
|
||||
|
||||
[server.listener."jmap"]
|
||||
protocol = "jmap"
|
||||
bind = ["[::]:443"]
|
||||
url = "https://%{HOST}%"
|
||||
|
||||
[server.listener."jmap".tls]
|
||||
implicit = true
|
||||
|
||||
#bind = ["[::]:8080"]
|
||||
#url = "https://%{HOST}%:8080"
|
||||
@@ -1,16 +0,0 @@
|
||||
#############################################
|
||||
# JMAP OAuth server configuration
|
||||
#############################################
|
||||
|
||||
[oauth]
|
||||
key = "__OAUTH_KEY__"
|
||||
|
||||
[oauth.auth]
|
||||
max-attempts = 3
|
||||
|
||||
[oauth.expiry]
|
||||
user-code = "30m"
|
||||
auth-code = "10m"
|
||||
token = "1h"
|
||||
refresh-token = "30d"
|
||||
refresh-token-renew = "4d"
|
||||
@@ -1,43 +0,0 @@
|
||||
#############################################
|
||||
# JMAP protocol configuration
|
||||
#############################################
|
||||
|
||||
[jmap.protocol.get]
|
||||
max-objects = 500
|
||||
|
||||
[jmap.protocol.set]
|
||||
max-objects = 500
|
||||
|
||||
[jmap.protocol.request]
|
||||
max-concurrent = 4
|
||||
max-size = 10000000
|
||||
max-calls = 16
|
||||
|
||||
[jmap.protocol.query]
|
||||
max-results = 5000
|
||||
|
||||
[jmap.protocol.upload]
|
||||
max-size = 50000000
|
||||
max-concurrent = 4
|
||||
ttl = "1h"
|
||||
|
||||
[jmap.protocol.upload.quota]
|
||||
files = 1000
|
||||
size = 50000000
|
||||
|
||||
[jmap.protocol.changes]
|
||||
max-results = 5000
|
||||
|
||||
[jmap.mailbox]
|
||||
max-depth = 10
|
||||
max-name-length = 255
|
||||
|
||||
[jmap.email]
|
||||
max-attachment-size = 50000000
|
||||
max-size = 75000000
|
||||
|
||||
[jmap.email.parse]
|
||||
max-items = 10
|
||||
|
||||
[jmap.principal]
|
||||
allow-lookups = true
|
||||
@@ -1,21 +0,0 @@
|
||||
#############################################
|
||||
# JMAP Push & EventSource configuration
|
||||
#############################################
|
||||
|
||||
[jmap.push]
|
||||
max-total = 100
|
||||
throttle = "1ms"
|
||||
|
||||
[jmap.push.attempts]
|
||||
interval = "1m"
|
||||
max = 3
|
||||
|
||||
[jmap.push.retry]
|
||||
interval = "1s"
|
||||
|
||||
[jmap.push.timeout]
|
||||
request = "10s"
|
||||
verify = "1s"
|
||||
|
||||
[jmap.event-source]
|
||||
throttle = "1s"
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
#############################################
|
||||
# JMAP server rate limiter configuration
|
||||
#############################################
|
||||
|
||||
[jmap.rate-limit]
|
||||
account = "1000/1m"
|
||||
anonymous = "100/1m"
|
||||
use-forwarded = false
|
||||
@@ -1,8 +0,0 @@
|
||||
#############################################
|
||||
# JMAP WebSockets server configuration
|
||||
#############################################
|
||||
|
||||
[jmap.web-sockets]
|
||||
throttle = "1s"
|
||||
timeout = "10m"
|
||||
heartbeat = "1m"
|
||||
72
resources/config/minimal.toml
Normal file
72
resources/config/minimal.toml
Normal file
@@ -0,0 +1,72 @@
|
||||
#############################################
|
||||
# Stalwart Mail Server Configuration File
|
||||
#############################################
|
||||
|
||||
[server.listener."smtp"]
|
||||
bind = ["[::]:25"]
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener."submission"]
|
||||
bind = ["[::]:587"]
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener."submissions"]
|
||||
bind = ["[::]:465"]
|
||||
protocol = "smtp"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener."imap"]
|
||||
bind = ["[::]:143"]
|
||||
protocol = "imap"
|
||||
|
||||
[server.listener."imaptls"]
|
||||
bind = ["[::]:993"]
|
||||
protocol = "imap"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener."sieve"]
|
||||
bind = ["[::]:4190"]
|
||||
protocol = "managesieve"
|
||||
|
||||
[server.listener."https"]
|
||||
protocol = "http"
|
||||
bind = ["[::]:443"]
|
||||
tls.implicit = true
|
||||
|
||||
[storage]
|
||||
data = "rocksdb"
|
||||
fts = "rocksdb"
|
||||
blob = "rocksdb"
|
||||
lookup = "rocksdb"
|
||||
directory = "internal"
|
||||
|
||||
[store."rocksdb"]
|
||||
type = "rocksdb"
|
||||
path = "%{env:STALWART_PATH}%/data"
|
||||
compression = "lz4"
|
||||
|
||||
[directory."internal"]
|
||||
type = "internal"
|
||||
store = "rocksdb"
|
||||
|
||||
[lookup.default]
|
||||
domain = "%{env:DOMAIN}%"
|
||||
hostname = "%{env:HOSTNAME}%"
|
||||
|
||||
[oauth]
|
||||
key = "%{env:OAUTH_KEY}%"
|
||||
|
||||
[tracer."stdout"]
|
||||
type = "stdout"
|
||||
level = "info"
|
||||
ansi = false
|
||||
enable = true
|
||||
|
||||
#[server.run-as]
|
||||
#user = "stalwart-mail"
|
||||
#group = "stalwart-mail"
|
||||
|
||||
[server.http]
|
||||
headers = ["Access-Control-Allow-Origin: *",
|
||||
"Access-Control-Allow-Methods: POST, GET, PATCH, PUT, DELETE, HEAD, OPTIONS",
|
||||
"Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With"]
|
||||
19
resources/config/security.toml
Normal file
19
resources/config/security.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[[queue.quota]]
|
||||
messages = 100000
|
||||
size = 10737418240 # 10gb
|
||||
enable = true
|
||||
|
||||
[[queue.throttle]]
|
||||
key = ["rcpt_domain"]
|
||||
concurrency = 5
|
||||
enable = true
|
||||
|
||||
[[session.throttle]]
|
||||
key = ["remote_ip"]
|
||||
concurrency = 5
|
||||
enable = true
|
||||
|
||||
[[session.throttle]]
|
||||
key = ["sender_domain", "rcpt"]
|
||||
rate = "25/1h"
|
||||
enable = true
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
#############################################
|
||||
# SMTP DMARC, DKIM, SPF, ARC & IpRev
|
||||
#############################################
|
||||
|
||||
[auth.iprev]
|
||||
verify = [ { if = "listener = 'smtp'", then = "relaxed" },
|
||||
{ else = "disable" } ]
|
||||
|
||||
[auth.dkim]
|
||||
verify = "relaxed"
|
||||
sign = [ { if = "listener != 'smtp'", then = "['rsa']" },
|
||||
{ else = false } ]
|
||||
|
||||
[auth.spf.verify]
|
||||
ehlo = [ { if = "listener = 'smtp'", then = "relaxed" },
|
||||
{ else = "disable" } ]
|
||||
mail-from = [ { if = "listener = 'smtp'", then = "relaxed" },
|
||||
{ else = "disable" } ]
|
||||
|
||||
[auth.arc]
|
||||
verify = "relaxed"
|
||||
seal = "['rsa']"
|
||||
|
||||
[auth.dmarc]
|
||||
verify = [ { if = "listener = 'smtp'", then = "relaxed" },
|
||||
{ else = "disable" } ]
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#############################################
|
||||
# SMTP server listener configuration
|
||||
#############################################
|
||||
|
||||
[server.listener."smtp"]
|
||||
bind = ["[::]:25"]
|
||||
#greeting = "Stalwart SMTP at your service"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener."submission"]
|
||||
bind = ["[::]:587"]
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener."submissions"]
|
||||
bind = ["[::]:465"]
|
||||
protocol = "smtp"
|
||||
tls.implicit = true
|
||||
|
||||
#[server.listener."management"]
|
||||
#bind = ["127.0.0.1:8080"]
|
||||
#protocol = "http"
|
||||
@@ -1,26 +0,0 @@
|
||||
#############################################
|
||||
# SMTP inbound Milter configuration
|
||||
#############################################
|
||||
|
||||
#[session.data.milter."rspamd"]
|
||||
#enable = [ { if = "listener = 'smtp'", then = true },
|
||||
# { else = false } ]
|
||||
#hostname = "127.0.0.1"
|
||||
#port = 11332
|
||||
#tls = false
|
||||
#allow-invalid-certs = false
|
||||
|
||||
#[session.data.milter."rspamd".timeout]
|
||||
#connect = "30s"
|
||||
#command = "30s"
|
||||
#data = "60s"
|
||||
|
||||
#[session.data.milter."rspamd".options]
|
||||
#tempfail-on-error = true
|
||||
#max-response-size = 52428800 # 50mb
|
||||
#version = 6
|
||||
|
||||
#[session.data.pipe."spam-assassin"]
|
||||
#command = "spamc"
|
||||
#arguments = []
|
||||
#timeout = "10s"
|
||||
@@ -1,49 +0,0 @@
|
||||
#############################################
|
||||
# SMTP server queue configuration
|
||||
#############################################
|
||||
|
||||
[queue.schedule]
|
||||
retry = "[2m, 5m, 10m, 15m, 30m, 1h, 2h]"
|
||||
notify = "[1d, 3d]"
|
||||
expire = "5d"
|
||||
|
||||
[queue.outbound]
|
||||
#hostname = "%{HOST}%"
|
||||
next-hop = [ { if = "is_local_domain('%{DEFAULT_DIRECTORY}%', rcpt_domain)", then = "'local'" },
|
||||
{ else = false } ]
|
||||
ip-strategy = "ipv4_then_ipv6"
|
||||
|
||||
[queue.outbound.tls]
|
||||
dane = "optional"
|
||||
mta-sts = "optional"
|
||||
starttls = "require"
|
||||
allow-invalid-certs = false
|
||||
|
||||
#[queue.outbound.source-ip]
|
||||
#v4 = "['10.0.0.10', '10.0.0.11']"
|
||||
#v6 = "['a::b', 'a::c']"
|
||||
|
||||
[queue.outbound.limits]
|
||||
mx = 7
|
||||
multihomed = 2
|
||||
|
||||
[queue.outbound.timeouts]
|
||||
connect = "3m"
|
||||
greeting = "3m"
|
||||
tls = "2m"
|
||||
ehlo = "3m"
|
||||
mail-from = "3m"
|
||||
rcpt-to = "3m"
|
||||
data = "10m"
|
||||
mta-sts = "2m"
|
||||
|
||||
[[queue.quota]]
|
||||
#match = "sender_domain = 'foobar.org'"
|
||||
#key = ["rcpt"]
|
||||
messages = 100000
|
||||
size = 10737418240 # 10gb
|
||||
|
||||
[[queue.throttle]]
|
||||
key = ["rcpt_domain"]
|
||||
#rate = "100/1h"
|
||||
concurrency = 5
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# SMTP remote servers configuration
|
||||
#############################################
|
||||
|
||||
[remote."local"]
|
||||
address = "127.0.0.1"
|
||||
port = 11200
|
||||
protocol = "lmtp"
|
||||
concurrency = 10
|
||||
timeout = "1m"
|
||||
|
||||
[remote."local".tls]
|
||||
implicit = false
|
||||
allow-invalid-certs = true
|
||||
|
||||
#[remote."local".auth]
|
||||
#username = ""
|
||||
#secret = ""
|
||||
@@ -1,55 +0,0 @@
|
||||
#############################################
|
||||
# SMTP reporting configuration
|
||||
#############################################
|
||||
|
||||
[report]
|
||||
#submitter = "'%{HOST}%'"
|
||||
|
||||
[report.analysis]
|
||||
addresses = ["dmarc@*", "abuse@*", "postmaster@*"]
|
||||
forward = true
|
||||
store = "30d"
|
||||
|
||||
[report.dsn]
|
||||
from-name = "'Mail Delivery Subsystem'"
|
||||
from-address = "'MAILER-DAEMON@%{DEFAULT_DOMAIN}%'"
|
||||
sign = "['rsa']"
|
||||
|
||||
[report.dkim]
|
||||
from-name = "'Report Subsystem'"
|
||||
from-address = "'noreply-dkim@%{DEFAULT_DOMAIN}%'"
|
||||
subject = "'DKIM Authentication Failure Report'"
|
||||
sign = "['rsa']"
|
||||
send = "[1, 1d]"
|
||||
|
||||
[report.spf]
|
||||
from-name = "'Report Subsystem'"
|
||||
from-address = "'noreply-spf@%{DEFAULT_DOMAIN}%'"
|
||||
subject = "'SPF Authentication Failure Report'"
|
||||
send = "[1, 1d]"
|
||||
sign = "['rsa']"
|
||||
|
||||
[report.dmarc]
|
||||
from-name = "'Report Subsystem'"
|
||||
from-address = "'noreply-dmarc@%{DEFAULT_DOMAIN}%'"
|
||||
subject = "'DMARC Authentication Failure Report'"
|
||||
send = "[1, 1d]"
|
||||
sign = "['rsa']"
|
||||
|
||||
[report.dmarc.aggregate]
|
||||
from-name = "'DMARC Report'"
|
||||
from-address = "'noreply-dmarc@%{DEFAULT_DOMAIN}%'"
|
||||
org-name = "'%{DEFAULT_DOMAIN}%'"
|
||||
#contact-info = ""
|
||||
send = "daily"
|
||||
max-size = 26214400 # 25mb
|
||||
sign = "['rsa']"
|
||||
|
||||
[report.tls.aggregate]
|
||||
from-name = "'TLS Report'"
|
||||
from-address = "'noreply-tls@%{DEFAULT_DOMAIN}%'"
|
||||
org-name = "'%{DEFAULT_DOMAIN}%'"
|
||||
#contact-info = ""
|
||||
send = "daily"
|
||||
max-size = 26214400 # 25 mb
|
||||
sign = "['rsa']"
|
||||
@@ -1,13 +0,0 @@
|
||||
#############################################
|
||||
# SMTP server resolver configuration
|
||||
#############################################
|
||||
|
||||
[resolver]
|
||||
type = "system"
|
||||
#preserve-intermediates = true
|
||||
concurrency = 2
|
||||
timeout = "5s"
|
||||
attempts = 2
|
||||
try-tcp-on-error = true
|
||||
public-suffix = ["https://publicsuffix.org/list/public_suffix_list.dat",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/maps/suffix_list.dat.gz"]
|
||||
@@ -1,100 +0,0 @@
|
||||
#############################################
|
||||
# SMTP inbound session configuration
|
||||
#############################################
|
||||
|
||||
[session]
|
||||
timeout = "5m"
|
||||
transfer-limit = 262144000 # 250 MB
|
||||
duration = "10m"
|
||||
|
||||
[session.connect]
|
||||
#script = "'connect'"
|
||||
|
||||
[session.ehlo]
|
||||
require = true
|
||||
reject-non-fqdn = [ { if = "listener = 'smtp'", then = true},
|
||||
{ else = false } ]
|
||||
#script = "'ehlo'"
|
||||
|
||||
[session.extensions]
|
||||
pipelining = true
|
||||
chunking = true
|
||||
requiretls = true
|
||||
no-soliciting = ""
|
||||
dsn = [ { if = "!is_empty(authenticated_as)", then = true},
|
||||
{ else = false } ]
|
||||
expn = [ { if = "!is_empty(authenticated_as)", then = true},
|
||||
{ else = false } ]
|
||||
vrfy = [ { if = "!is_empty(authenticated_as)", then = true},
|
||||
{ else = false } ]
|
||||
future-release = [ { if = "!is_empty(authenticated_as)", then = "7d"},
|
||||
{ else = false } ]
|
||||
deliver-by = [ { if = "!is_empty(authenticated_as)", then = "15d"},
|
||||
{ else = false } ]
|
||||
mt-priority = [ { if = "!is_empty(authenticated_as)", then = "mixer"},
|
||||
{ else = false } ]
|
||||
|
||||
[session.auth]
|
||||
mechanisms = [ { if = "listener != 'smtp'", then = "[plain, login]"},
|
||||
{ else = false } ]
|
||||
directory = [ { if = "listener != 'smtp'", then = "'%{DEFAULT_DIRECTORY}%'" },
|
||||
{ else = false } ]
|
||||
require = [ { if = "listener != 'smtp'", then = true},
|
||||
{ else = false } ]
|
||||
allow-plain-text = false
|
||||
|
||||
[session.auth.errors]
|
||||
total = 3
|
||||
wait = "5s"
|
||||
|
||||
[session.mail]
|
||||
#script = "mail-from"
|
||||
#rewrite = [ { if = "listener != 'smtp' & matches('^([^.]+)@([^.]+)\\.(.+)$', rcpt)", then = "$1 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
|
||||
[session.rcpt]
|
||||
#script = "greylist"
|
||||
relay = [ { if = "!is_empty(authenticated_as)", then = true },
|
||||
{ else = false } ]
|
||||
#rewrite = [ { if = "is_local_domain('%{DEFAULT_DIRECTORY}%', rcpt_domain) & matches('^([^.]+)\\.([^.]+)@(.+)$', rcpt)", then = "$1 + '+' + $2 + '@' + $3" },
|
||||
# { else = false } ]
|
||||
max-recipients = 25
|
||||
directory = "'%{DEFAULT_DIRECTORY}%'"
|
||||
|
||||
[session.rcpt.errors]
|
||||
total = 5
|
||||
wait = "5s"
|
||||
|
||||
[session.data]
|
||||
script = [ { if = "is_empty(authenticated_as)", then = "'spam-filter'"},
|
||||
{ else = "'track-replies'" } ]
|
||||
|
||||
[session.data.limits]
|
||||
messages = 10
|
||||
size = 104857600
|
||||
received-headers = 50
|
||||
|
||||
[session.data.add-headers]
|
||||
received = [ { if = "listener = 'smtp'", then = true },
|
||||
{ else = false } ]
|
||||
received-spf = [ { if = "listener = 'smtp'", then = true },
|
||||
{ else = false } ]
|
||||
auth-results = [ { if = "listener = 'smtp'", then = true },
|
||||
{ else = false } ]
|
||||
message-id = [ { if = "listener = 'smtp'", then = false },
|
||||
{ else = true } ]
|
||||
date = [ { if = "listener = 'smtp'", then = false },
|
||||
{ else = true } ]
|
||||
return-path = false
|
||||
|
||||
[[session.throttle]]
|
||||
#match = "remote_ip = '10.0.0.1'"
|
||||
key = ["remote_ip"]
|
||||
concurrency = 5
|
||||
#rate = "5/1h"
|
||||
enable = true
|
||||
|
||||
[[session.throttle]]
|
||||
key = ["sender_domain", "rcpt"]
|
||||
rate = "25/1h"
|
||||
enable = true
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# SMTP DKIM & ARC signatures
|
||||
#############################################
|
||||
|
||||
[signature."rsa"]
|
||||
#public-key = "file://%{BASE_PATH}%/etc/dkim/%{DEFAULT_DOMAIN}%.cert"
|
||||
private-key = "file://%{BASE_PATH}%/etc/dkim/%{DEFAULT_DOMAIN}%.key"
|
||||
domain = "%{DEFAULT_DOMAIN}%"
|
||||
selector = "stalwart"
|
||||
headers = ["From", "To", "Date", "Subject", "Message-ID"]
|
||||
algorithm = "rsa-sha256"
|
||||
canonicalization = "relaxed/relaxed"
|
||||
#expire = "10d"
|
||||
#third-party = ""
|
||||
#third-party-algo = ""
|
||||
#auid = ""
|
||||
set-body-length = false
|
||||
report = true
|
||||
@@ -1,62 +0,0 @@
|
||||
#############################################
|
||||
# SMTP Spam & Phishing filter configuration
|
||||
#############################################
|
||||
|
||||
[spam.header]
|
||||
add-spam = true
|
||||
add-spam-result = true
|
||||
is-spam = "X-Spam-Status: Yes"
|
||||
|
||||
[spam.autolearn]
|
||||
enable = true
|
||||
balance = 0.9
|
||||
|
||||
[spam.autolearn.ham]
|
||||
replies = true
|
||||
threshold = -0.5
|
||||
|
||||
[spam.autolearn.spam]
|
||||
threshold = 6.0
|
||||
|
||||
[spam.threshold]
|
||||
spam = 5.0
|
||||
discard = 0
|
||||
reject = 0
|
||||
|
||||
[spam.data]
|
||||
directory = ""
|
||||
lookup = ""
|
||||
|
||||
[sieve.trusted.scripts]
|
||||
spam-filter = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/prelude.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/from.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/recipient.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/subject.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/replyto.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/date.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/messageid.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/received.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/headers.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/bounce.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/html.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/mime.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/dmarc.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/ip.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/helo.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/replies_in.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/spamtrap.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/bayes_classify.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/url.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/rbl.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/pyzor.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/composites.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/scores.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/reputation.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/epilogue.sieve"]
|
||||
|
||||
track-replies = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/replies_out.sieve"]
|
||||
|
||||
greylist = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/greylist.sieve"]
|
||||
10331
resources/config/spamfilter.toml
Normal file
10331
resources/config/spamfilter.toml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,6 @@ spam-dmarc = {"18f.gov",
|
||||
"adp.com",
|
||||
"advice.hmrc.gov.uk",
|
||||
"aerocivil.gov.co",
|
||||
"aerocivil.gov.co",
|
||||
"afreximbank.com",
|
||||
"agingstats.gov",
|
||||
"agro.ru",
|
||||
|
||||
@@ -150,7 +150,6 @@ spam-spdk = {"1cfresh.com",
|
||||
"paypal.ca",
|
||||
"paypal.cn",
|
||||
"paypal.com",
|
||||
"paypal.com",
|
||||
"paypal.co.uk",
|
||||
"paypal.de",
|
||||
"paypal.es",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
spam-trap = {}
|
||||
Binary file not shown.
@@ -1,35 +1,35 @@
|
||||
# Whether to add an X-Spam-Status header
|
||||
let "ADD_HEADER_SPAM" "%{cfg:spam.header.add-spam}%";
|
||||
let "ADD_HEADER_SPAM" "key_get('spam-config', 'add-spam')";
|
||||
|
||||
# Whether to add an X-Spam-Result header
|
||||
let "ADD_HEADER_SPAM_RESULT" "%{cfg:spam.header.add-spam-result}%";
|
||||
let "ADD_HEADER_SPAM_RESULT" "key_get('spam-config', 'add-spam-result')";
|
||||
|
||||
# Whether message replies from authenticated users should be learned as ham
|
||||
let "AUTOLEARN_REPLIES_HAM" "%{cfg:spam.autolearn.ham.replies}%";
|
||||
let "AUTOLEARN_REPLIES_HAM" "key_get('spam-config', 'learn-ham-replies')";
|
||||
|
||||
# Whether the bayes classifier should be trained automatically
|
||||
let "AUTOLEARN_ENABLE" "%{cfg:spam.autolearn.enable}%";
|
||||
let "AUTOLEARN_ENABLE" "key_get('spam-config', 'learn-enable')";
|
||||
|
||||
# When to learn ham (score >= threshold)
|
||||
let "AUTOLEARN_HAM_THRESHOLD" "%{cfg:spam.autolearn.ham.threshold}%";
|
||||
let "AUTOLEARN_HAM_THRESHOLD" "key_get('spam-config', 'learn-ham-threshold')";
|
||||
|
||||
# When to learn spam (score <= threshold)
|
||||
let "AUTOLEARN_SPAM_THRESHOLD" "%{cfg:spam.autolearn.spam.threshold}%";
|
||||
let "AUTOLEARN_SPAM_THRESHOLD" "key_get('spam-config', 'learn-spam-threshold')";
|
||||
|
||||
# Keep difference for spam/ham learns for at least this value
|
||||
let "AUTOLEARN_SPAM_HAM_BALANCE" "%{cfg:spam.autolearn.balance}%";
|
||||
let "AUTOLEARN_SPAM_HAM_BALANCE" "key_get('spam-config', 'learn-balance')";
|
||||
|
||||
# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
|
||||
let "SCORE_SPAM_THRESHOLD" "%{cfg:spam.threshold.spam}%";
|
||||
let "SCORE_SPAM_THRESHOLD" "key_get('spam-config', 'threshold-spam')";
|
||||
|
||||
# Discard messages with a score above this threshold
|
||||
let "SCORE_DISCARD_THRESHOLD" "%{cfg:spam.threshold.discard}%";
|
||||
let "SCORE_DISCARD_THRESHOLD" "key_get('spam-config', 'threshold-discard')";
|
||||
|
||||
# Reject messages with a score above this threshold
|
||||
let "SCORE_REJECT_THRESHOLD" "%{cfg:spam.threshold.reject}%";
|
||||
let "SCORE_REJECT_THRESHOLD" "key_get('spam-config', 'threshold-reject')";
|
||||
|
||||
# Directory name to use for local domain lookups (leave empty for default)
|
||||
let "DOMAIN_DIRECTORY" "%{cfg:spam.data.directory}%";
|
||||
let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
|
||||
|
||||
# Store to use for Bayes tokens and ids (leave empty for default)
|
||||
let "SPAM_DB" "%{cfg:spam.data.lookup}%";
|
||||
let "SPAM_DB" "key_get('spam-config', 'lookup')";
|
||||
|
||||
20
resources/config/spamfilter/settings.toml
Normal file
20
resources/config/spamfilter/settings.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[spam.header]
|
||||
is-spam = "X-Spam-Status: Yes"
|
||||
|
||||
[lookup.spam-config]
|
||||
add-spam = true
|
||||
add-spam-result = true
|
||||
learn-enable = true
|
||||
learn-balance = "0.9"
|
||||
learn-ham-replies = true
|
||||
learn-ham-threshold = "-0.5"
|
||||
learn-spam-threshold = "6.0"
|
||||
threshold-spam = "5.0"
|
||||
threshold-discard = "0.0"
|
||||
threshold-reject = "0.0"
|
||||
directory = ""
|
||||
lookup = ""
|
||||
|
||||
[session.data]
|
||||
script = [ { if = "is_empty(authenticated_as)", then = "'spam-filter'"},
|
||||
{ else = "'track-replies'" } ]
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# ElasticSearch FTS Store configuration
|
||||
#############################################
|
||||
|
||||
[store."elasticsearch"]
|
||||
type = "elasticsearch"
|
||||
url = "https://localhost:9200"
|
||||
user = "elastic"
|
||||
password = "myelasticpassword"
|
||||
#cloud-id = "my-cloud-id"
|
||||
disable = true
|
||||
|
||||
[store."elasticsearch".tls]
|
||||
allow-invalid-certs = true
|
||||
|
||||
[store."elasticsearch".index]
|
||||
shards = 3
|
||||
replicas = 0
|
||||
@@ -1,20 +0,0 @@
|
||||
#############################################
|
||||
# FoundationDB Store configuration
|
||||
#############################################
|
||||
|
||||
[store."foundationdb"]
|
||||
type = "foundationdb"
|
||||
#cluster-file = "/etc/foundationdb/fdb.cluster"
|
||||
disable = true
|
||||
|
||||
#[store."foundationdb".transaction]
|
||||
#timeout = "5s"
|
||||
#retry-limit = 10
|
||||
#max-retry-delay = "1s"
|
||||
|
||||
#[store."foundationdb".ids]
|
||||
#machine = "stalwart"
|
||||
#data-center = "my-datacenter"
|
||||
|
||||
[store."foundationdb".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,12 +0,0 @@
|
||||
#############################################
|
||||
# File System Blob Store configuration
|
||||
#############################################
|
||||
|
||||
[store."fs"]
|
||||
type = "fs"
|
||||
path = "%{BASE_PATH}%/data/blobs"
|
||||
depth = 2
|
||||
disable = true
|
||||
|
||||
[store."fs".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,37 +0,0 @@
|
||||
#############################################
|
||||
# MySQL Store configuration
|
||||
#############################################
|
||||
|
||||
[store."mysql"]
|
||||
type = "mysql"
|
||||
host = "localhost"
|
||||
port = 3307
|
||||
database = "stalwart"
|
||||
user = "root"
|
||||
password = "password"
|
||||
disable = true
|
||||
#max-allowed-packet = 1073741824
|
||||
timeout = "15s"
|
||||
|
||||
#[store."mysql".pool]
|
||||
#max-connections = 10
|
||||
#min-connections = 5
|
||||
|
||||
#[store."mysql".init]
|
||||
#execute = [
|
||||
# "CREATE TABLE IF NOT EXISTS accounts (name VARCHAR(32) PRIMARY KEY, secret VARCHAR(1024), description VARCHAR(1024), type VARCHAR(32) NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
|
||||
# "CREATE TABLE IF NOT EXISTS group_members (name VARCHAR(32) NOT NULL, member_of VARCHAR(32) NOT NULL, PRIMARY KEY (name, member_of))",
|
||||
# "CREATE TABLE IF NOT EXISTS emails (name VARCHAR(32) NOT NULL, address VARCHAR(128) NOT NULL, type VARCHAR(32), PRIMARY KEY (name, address))"
|
||||
#]
|
||||
|
||||
[store."mysql".query]
|
||||
name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
|
||||
members = "SELECT member_of FROM group_members WHERE name = ?"
|
||||
recipients = "SELECT name FROM emails WHERE address = ? ORDER BY name ASC"
|
||||
emails = "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
|
||||
verify = "SELECT address FROM emails WHERE address LIKE CONCAT('%', ?, '%') AND type = 'primary' ORDER BY address LIMIT 5"
|
||||
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
|
||||
domains = "SELECT 1 FROM emails WHERE address LIKE CONCAT('%@', ?) LIMIT 1"
|
||||
|
||||
[store."mysql".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,39 +0,0 @@
|
||||
#############################################
|
||||
# PostgreSQL Store configuration
|
||||
#############################################
|
||||
|
||||
[store."postgresql"]
|
||||
type = "postgresql"
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
database = "stalwart"
|
||||
user = "postgres"
|
||||
password = "mysecretpassword"
|
||||
timeout = "15s"
|
||||
disable = true
|
||||
|
||||
[store."postgresql".tls]
|
||||
enable = false
|
||||
allow-invalid-certs = false
|
||||
|
||||
#[store."postgresql".pool]
|
||||
#max-connections = 10
|
||||
|
||||
#[store."postgresql".init]
|
||||
#execute = [
|
||||
# "CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
|
||||
# "CREATE TABLE IF NOT EXISTS group_members (name TEXT NOT NULL, member_of TEXT NOT NULL, PRIMARY KEY (name, member_of))",
|
||||
# "CREATE TABLE IF NOT EXISTS emails (name TEXT NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (name, address))"
|
||||
#]
|
||||
|
||||
[store."postgresql".query]
|
||||
name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = $1 AND active = true"
|
||||
members = "SELECT member_of FROM group_members WHERE name = $1"
|
||||
recipients = "SELECT name FROM emails WHERE address = $1 ORDER BY name ASC"
|
||||
emails = "SELECT address FROM emails WHERE name = $1 AND type != 'list' ORDER BY type DESC, address ASC"
|
||||
verify = "SELECT address FROM emails WHERE address LIKE '%' || $1 || '%' AND type = 'primary' ORDER BY address LIMIT 5"
|
||||
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = $1 AND l.type = 'list' ORDER BY p.address LIMIT 50"
|
||||
domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || $1 LIMIT 1"
|
||||
|
||||
[store."postgresql".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# Redis Lookup Store configuration
|
||||
#############################################
|
||||
|
||||
[store."redis"]
|
||||
type = "redis"
|
||||
redis-type = "single"
|
||||
urls = ["redis://127.0.0.1"]
|
||||
user = "my_username"
|
||||
password = "secretpassword"
|
||||
timeout = "10s"
|
||||
#read-from-replicas = false
|
||||
disable = true
|
||||
|
||||
#[store."redis".retry]
|
||||
#total = 3
|
||||
#max-wait = "1s"
|
||||
#min-wait = "500ms"
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# RocksDB Store configuration
|
||||
#############################################
|
||||
|
||||
[store."rocksdb"]
|
||||
type = "rocksdb"
|
||||
path = "%{BASE_PATH}%/data"
|
||||
disable = true
|
||||
|
||||
[store."rocksdb".settings]
|
||||
min-blob-size = 16834
|
||||
write-buffer-size = 134217728
|
||||
|
||||
#[store."rocksdb".pool]
|
||||
#workers = 10
|
||||
|
||||
[store."rocksdb".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,18 +0,0 @@
|
||||
#############################################
|
||||
# S3/MinIO Blob Store configuration
|
||||
#############################################
|
||||
|
||||
[store."s3"]
|
||||
type = "s3"
|
||||
bucket = "stalwart"
|
||||
region = "eu-central-1"
|
||||
access-key = "minioadmin"
|
||||
secret-key = "minioadmin"
|
||||
#endpoint = ""
|
||||
#security-token = ""
|
||||
#profile = ""
|
||||
timeout = "30s"
|
||||
disable = true
|
||||
|
||||
[store."s3".purge]
|
||||
frequency = "0 3 *"
|
||||
@@ -1,31 +0,0 @@
|
||||
#############################################
|
||||
# SQLite Store configuration
|
||||
#############################################
|
||||
|
||||
[store."sqlite"]
|
||||
type = "sqlite"
|
||||
path = "%{BASE_PATH}%/data/index.sqlite3"
|
||||
disable = true
|
||||
|
||||
#[store."sqlite".pool]
|
||||
#max-connections = 10
|
||||
#workers = 10
|
||||
|
||||
#[store."sqlite".init]
|
||||
#execute = [
|
||||
# "CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
|
||||
# "CREATE TABLE IF NOT EXISTS group_members (name TEXT NOT NULL, member_of TEXT NOT NULL, PRIMARY KEY (name, member_of))",
|
||||
# "CREATE TABLE IF NOT EXISTS emails (name TEXT NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (name, address))"
|
||||
#]
|
||||
|
||||
[store."sqlite".query]
|
||||
name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
|
||||
members = "SELECT member_of FROM group_members WHERE name = ?"
|
||||
recipients = "SELECT name FROM emails WHERE address = ?"
|
||||
emails = "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
|
||||
verify = "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type = 'primary' ORDER BY address LIMIT 5"
|
||||
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
|
||||
domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || ? LIMIT 1"
|
||||
|
||||
[store."sqlite".purge]
|
||||
frequency = "0 3 *"
|
||||
Reference in New Issue
Block a user