1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-10-29 15:02:30 +00:00

Reject invalid characters in domains

Domains in addresses had very lax validation, which allowed them to
include characters like "/".

That could be exploited to trigger a daemon panic: a MAIL FROM
"x@evil/foo" could be accepted and, on failed delivery, a DSN queued.
Attempting to deliver the DSN would cause an MTA-STS cache lookup, which
would panic because the domain includes a "/" and crash the server.
Because the DSN would be queued, the server would crash again on
restarts, and only self-recover after give_up_send_after (20h by
default).

To fix this, we now explicitly validate the domain when normalizing it,
make MTA-STS cache not panic on invalid domains, and also make the SMTP
courier fail permanently on invalid domains.

Thanks to John JEAN <john@wargan.com> for finding and reporting this
problem. The detailed report can be found in
https://johnjean.io/advisories/chasquid-unauthenticated-remote-dos/.
This commit is contained in:
Alberto Bertogli
2026-10-08 23:54:15 +01:00
parent d2260180c6
commit 61792e3bb4
14 changed files with 255 additions and 35 deletions
+9 -1
View File
@@ -23,11 +23,19 @@ var (
id = flag.String("id", "mid1234", "Message ID")
from = flag.String("from", "from", "Mail from")
rcpt = flag.String("rcpt", "rcpt", "Rcpt to")
rcptType = flag.String("rcpt_type", "EMAIL",
"Recipient type (EMAIL, PIPE, FORWARD)")
)
func main() {
flag.Parse()
rType, ok := queue.Recipient_Type_value[*rcptType]
if !ok {
fmt.Printf("unknown recipient type %q\n", *rcptType)
os.Exit(1)
}
data, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Printf("error reading data: %v\n", err)
@@ -42,7 +50,7 @@ func main() {
Rcpt: []*queue.Recipient{
{
Address: *rcpt,
Type: queue.Recipient_EMAIL,
Type: queue.Recipient_Type(rType),
Status: queue.Recipient_PENDING,
},
},
+40 -3
View File
@@ -5,21 +5,58 @@ set -e
init
# Don't hang forever if queue items are not processed as expected.
timeout 1m
# Start with an empty queue and mail log, so leftovers from previous runs
# don't interfere.
rm -rf .data/queue .logs/mail_log
# Add an item to the queue before starting chasquid.
go run addtoqueue.go --queue_dir=.data/queue \
--from someone@testserver \
--rcpt someone@testserver \
< content
# Add items with invalid domains, which older versions could have queued.
# They should fail delivery, but not crash the daemon.
# A DSN to an invalid domain.
go run addtoqueue.go --queue_dir=.data/queue --id=invalid1 \
--from "<>" \
--rcpt "x@evil/foo" \
< content
# A message from an invalid domain, which fails delivery and so generates a
# DSN to the invalid domain.
go run addtoqueue.go --queue_dir=.data/queue --id=invalid2 \
--from "x@evil/foo" \
--rcpt_type PIPE --rcpt "false" \
< content
generate_certs_for testserver
mkdir -p .logs
chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config &
wait_until_ready 1025
CHASQUID_PID=$!
# Check that the item in the queue was delivered.
wait_for_file .mail/someone@testserver
# Wait for all the queue items to be processed (or chasquid to exit), and
# check that chasquid is still running.
function queue_empty_or_exited() {
! kill -0 $CHASQUID_PID 2>/dev/null || \
! compgen -G '.data/queue/m:*' > /dev/null
}
wait_until queue_empty_or_exited
if ! kill -0 $CHASQUID_PID 2>/dev/null; then
fail "chasquid exited unexpectedly"
fi
# Check that the valid item in the queue was delivered.
mail_diff content .mail/someone@testserver
# Check that the DSNs to the invalid domain failed as expected (one queued
# directly, one generated from the failed delivery).
if [ "$(grep -c 'from=<> to=x@evil/foo failed (permanent)' \
.logs/mail_log)" != 2 ]; then
fail "did not find the expected DSN failures in the mail log"
fi
success
+14 -1
View File
@@ -21,7 +21,20 @@ c -> MAIL FROM:<pepe>
c <~ 501
c -> MAIL FROM:<a@xn--->
c <- 501 5.1.8 Malformed sender domain (IDNA conversion failed)
c <- 501 5.1.8 Malformed sender domain
# Reconnect to avoid getting rejected due to too many errors.
c close
c tcp_connect localhost:1025
c <~ 220
c -> HELO localhost
c <~ 250
c -> MAIL FROM:<a@b/c>
c <- 501 5.1.8 Malformed sender domain
c -> MAIL FROM:<a@[1.2.3.4]>
c <- 501 5.1.8 Malformed sender domain
# Reconnect to avoid getting rejected due to too many errors.
c close
+16 -1
View File
@@ -26,7 +26,22 @@ c -> RCPT TO:<pepe>
c <~ 501
c -> RCPT TO:<a@xn--->
c <- 501 5.1.2 Malformed destination domain (IDNA conversion failed)
c <- 501 5.1.2 Malformed destination domain
# Reconnect to avoid getting rejected due to too many errors.
c close
c tcp_connect localhost:1025
c <~ 220
c -> HELO localhost
c <~ 250
c -> MAIL FROM:<test@testy.com>
c <~ 250
c -> RCPT TO:<a@b/c>
c <- 501 5.1.2 Malformed destination domain
c -> RCPT TO:<a@[1.2.3.4]>
c <- 501 5.1.2 Malformed destination domain
# Reconnect to avoid getting rejected due to too many errors.
c close