Git Product home page Git Product logo

Comments (10)

33KK avatar 33KK commented on May 23, 2024 2

Being able to impersonate any user on the server doesn't seem like a good/secure default to me at all, especially considering that this is an all-in-one solution.

from mail-server.

33KK avatar 33KK commented on May 23, 2024 2

This seems to work for now:

require ["vnd.stalwart.plugins", "variables", "envelope", "reject", "environment"];

query :use "postgresql" :set ["address_owner"] "SELECT name FROM addresses WHERE address = $1" ["${envelope.from}"];

if not environment :is "authenticated_as" "${address_owner}" {
	reject "Unauthorized sender";
	stop;
}

I don't like the SQL query duplication from directory.postgresql.query.recipients though, no idea how to avoid it, sieve isn't intuitive at all.

It would really make much more sense to have this built-in, at the very minimum I'd expect to be informed that anyone can send from any address in the docs, because this is NOT obvious nor expected to anyone who isn't an email nerd and can be dangerous

from mail-server.

33KK avatar 33KK commented on May 23, 2024 1

Being able to impersonate any user on the server doesn't seem like a good/secure default to me at all

This is how email has worked for the last 40 years. I also use this daily.

Should we also go back to http, get rid of SPF, DKIM and everything else, because that's how it worked 40 years ago? This really doesn't make sense to me, there's a clear security issue here.

from mail-server.

Avamander avatar Avamander commented on May 23, 2024 1

Yes, this is an awful default. Even if it's not malicious, it can just be a deliverability issue.

Allowing forging a sender should require explicit configuration.

from mail-server.

mdecimus avatar mdecimus commented on May 23, 2024

Please attach your configuration file (removing any sensitive info).

from mail-server.

33KK avatar 33KK commented on May 23, 2024

It's mostly just whatever was the default

#############################################
# Server configuration
#############################################

[server]
hostname = "uwu.kkx.one"
max-connections = 8192

#[server.run-as]
#user = "stalwart-mail"
#group = "stalwart-mail"

[server.tls]
enable = true
implicit = false
timeout = "1m"
certificate = "default"
#sni = [{subject = "", certificate = ""}]
#protocols = ["TLSv1.2", TLSv1.3"]
#ciphers = []
ignore-client-order = true

[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]
shared-map = {shard = 32, capacity = 10}
#thread-pool = 8

[global.tracing]
method = "stdout"
level = "trace"

#[global.tracing]
#method = "open-telemetry"
#transport = "http"
#endpoint = "https://127.0.0.1/otel"
#headers = ["Authorization: <place_auth_here>"]
#level = "debug"

#[global.tracing]
#method = "log"
#path = "/opt/stalwart-mail/logs"
#prefix = "stalwart.log"
#rotate = "daily"
#level = "info"

[certificate."default"]
cert = "file:///certs/pepega.club.crt"
private-key = "file:///certs/pepega.club.key"

#############################################
# Directory configuration
#############################################

[directory."postgres"]
type = "sql"
address = "postgresql://stalwart:[email protected]:5432/stalwart?sslmode=disable"

[directory."postgres".options]
catch-all = true
#catch-all = { map = "(.+)@(.+)$", to = "info@${2}" }
subaddressing = true
#subaddressing = { map = "^([^.]+)\.([^.]+)@(.+)$", to = "${2}@${3}" }
superuser-group = "superusers"

[directory."postgres".pool]
max-connections = 10
min-connections = 0
#idle-timeout = "10m"

[directory."postgres".cache]
entries = 500
ttl = {positive = '1h', negative = '10m'}

[directory."postgres".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 addresses WHERE address = $1"
emails = "SELECT address FROM addresses WHERE name = $1 AND type != 'list' ORDER BY type DESC, address ASC"
verify = "SELECT address FROM addresses WHERE address LIKE '%' || $1 || '%' AND type = 'primary' ORDER BY address LIMIT 5"
expand = "SELECT p.address FROM addresses AS p JOIN addresses 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 addresses WHERE address LIKE '%@' || $1 LIMIT 1"

[directory."postgres".columns]
name = "name"
description = "description"
secret = "secret"
email = "address"
quota = "quota"
type = "type"

#############################################
# JMAP server configuration
#############################################

[server.listener."jmap"]
bind = ["[::]:8080"]
url = "https://uwu.kkx.one:8080"
protocol = "jmap"

[store.db]
path = "/opt/stalwart-mail/data/index.sqlite3"

[store.db.pool]
max-connections = 10
#workers = 8

[store.db.cache]
size = 1000

[store.blob]
type = "local"

[store.blob.local]
path = "/opt/stalwart-mail/data/blobs"

[jmap]
directory = "postgres"

[jmap.http]
#headers = ["Access-Control-Allow-Origin: *", 
#           "Access-Control-Allow-Methods: POST, GET, HEAD, OPTIONS", 
#           "Access-Control-Allow-Headers: *"]

[jmap.encryption]
enable = true
append = false

[jmap.session.cache]
ttl = "1h"
size = 100

[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.rate-limit]
account = "1000/1m"
authentication = "10/1m"
anonymous = "100/1m"
use-forwarded = false

[jmap.rate-limit.cache]
size = 1024

[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

[jmap.sieve]
disable-capabilities = []
notification-uris = ["mailto"]
protected-headers = ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]

[jmap.sieve.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

[jmap.sieve.vacation]
default-subject = "Automated reply"
subject-prefix = "Auto: "

[jmap.sieve.default-expiry]
vacation = "30d"
duplicate = "7d"

[jmap.event-source]
throttle = "1s"

[jmap.web-sockets]
throttle = "1s"
timeout = "10m"
heartbeat = "1m"

[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.fts]
default-language = "en"

[oauth]
key = "key"

[oauth.auth]
max-attempts = 3

[oauth.expiry]
user-code = "30m"
auth-code = "10m"
token = "1h"
refresh-token = "30d"
refresh-token-renew = "4d"

[oauth.cache]
size = 128

[jmap.purge.schedule]
db = "0 3 *"
blobs = "30 3 *"
sessions = "15 * *"

#############################################
# IMAP server 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"
tls.implicit = true

[imap.request]
max-size = 52428800

[imap.auth]
max-failures = 3
allow-plain-text = false

[imap.folders.name]
shared = "Shared Folders"
all = "All Mail"

[imap.timeout]
authenticated = "30m"
anonymous = "1m"
idle = "30m"

[imap.rate-limit]
requests = "2000/1m"
concurrent = 4

#############################################
# 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 = ["0.0.0.0: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 = "postgres" }, 
           { 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 = "sql/domains" },
#                         { if = "rcpt", matches = "^([^.]+)\.([^.]+)@(.+)$"}, 
#                       ], then = "${1}+${2}@${3}" }, 
#            { else = false } ]
max-recipients = 25
directory = "postgres"

[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 = "/opt/stalwart-mail/queue"
hash = 64

[queue.schedule]
retry = ["2m", "5m", "10m", "15m", "30m", "1h", "2h"]
notify = ["1d", "3d"]
expire = "5d"

[queue.outbound]
#hostname = "uwu.kkx.one"
next-hop = [ { if = "rcpt-domain", in-list = "postgres/domains", then = "local" }, 
             { 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 = "/opt/stalwart-mail/reports"
hash = 64
#submitter = "uwu.kkx.one"

[report.analysis]
addresses = ["dmarc@*", "abuse@*", "postmaster@*"]
forward = true
#store = "/opt/stalwart-mail/incoming"

[report.dsn]
from-name = "Mail Delivery Subsystem"
from-address = "[email protected]"
sign = ["rsa"]

[report.dkim]
from-name = "Report Subsystem"
from-address = "[email protected]"
subject = "DKIM Authentication Failure Report"
sign = ["rsa"]
send = "1/1d"

[report.spf]
from-name = "Report Subsystem"
from-address = "[email protected]"
subject = "SPF Authentication Failure Report"
send = "1/1d"
sign = ["rsa"]

[report.dmarc]
from-name = "Report Subsystem"
from-address = "[email protected]"
subject = "DMARC Authentication Failure Report"
send = "1/1d"
sign = ["rsa"]

[report.dmarc.aggregate]
from-name = "DMARC Report"
from-address = "[email protected]"
org-name = "kkx.one"
#contact-info = ""
send = "daily"
max-size = 26214400 # 25mb
sign = ["rsa"]

[report.tls.aggregate]
from-name = "TLS Report"
from-address = "[email protected]"
org-name = "kkx.one"
#contact-info = ""
send = "daily"
max-size = 26214400 # 25 mb
sign = ["rsa"]

[signature."rsa"]
#public-key = "file:///opt/stalwart-mail/etc/dkim/kkx.one.cert"
private-key = "file:///opt/stalwart-mail/etc/dkim/kkx.one.key"
domain = "kkx.one"
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 = "[email protected]"
return-path = ""
#hostname = "uwu.kkx.one"
sign = ["rsa"]
use-directory = "postgres"

[sieve.limits]
redirects = 3
out-messages = 5
received-headers = 50
cpu = 10000
nested-includes = 5
duplicate-expiry = "7d"

[sieve.scripts]

[management]
directory = "postgres"

from mail-server.

mdecimus avatar mdecimus commented on May 23, 2024

This is not a bug.

SMTP servers in general do not validate whether a MAIL FROM address exists or not. However,

  • For messages received from the internet, you can perform IPREV, DNSBL, SPF and DMARC checks to make sure the remote domain is not a spammer.
  • For authenticated senders, you can create a Sieve scripts that verifies that the envelope.from variable equals to the env.authenticated_as variable. Alternatively you could do a directory lookup from the Sieve script to make sure that envelope.from is from a local domain and is a valid account.

from mail-server.

daviessm avatar daviessm commented on May 23, 2024

Being able to impersonate any user on the server doesn't seem like a good/secure default to me at all

This is how email has worked for the last 40 years. I also use this daily.

from mail-server.

daviessm avatar daviessm commented on May 23, 2024

How do you propose to check that the address specified in the MAIL FROM command is able to be used by the (presumably authenticated) person sending the mail? What happens if the user isn't authenticated?

from mail-server.

33KK avatar 33KK commented on May 23, 2024

How do you propose to check that the address specified in the MAIL FROM command is able to be used by the (presumably authenticated) person sending the mail? What happens if the user isn't authenticated?

Just like relay config option works? If authenticated as some user only then check if that user has this address assigned?

EDIT: Something like this

[session.mail]
directory = "postgresql" # already needs to be configured for session.rcpt
validate_from = [ { if = "authenticated-as", ne = "", then = true }, { else = false } ]

from mail-server.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.