Merge branch 'feature/GHSA-54wq-72mp-cq7c' into develop

This commit is contained in:
Ralph Slooten
2026-01-18 11:53:11 +13:00
2 changed files with 62 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ import (
"io/fs"
"log"
"net"
"net/mail"
"os"
"regexp"
"strconv"
@@ -421,7 +422,7 @@ loop:
break
}
match := mailFromRE.FindStringSubmatch(args)
match := extractAndValidateAddress(mailFromRE, args)
if match == nil {
s.writef("501 5.5.4 Syntax error in parameters or arguments (invalid FROM parameter)")
} else {
@@ -477,7 +478,7 @@ loop:
break
}
match := rcptToRE.FindStringSubmatch(args)
match := extractAndValidateAddress(rcptToRE, args)
if match == nil {
s.writef("501 5.5.4 Syntax error in parameters or arguments (invalid TO parameter)")
} else {
@@ -1014,3 +1015,33 @@ func (s *session) handleAuthCramMD5() (bool, error) {
return authenticated, err
}
// Extract and validate email address from a regex match.
// This ensures that only RFC 5322 email addresses are accepted (if set).
func extractAndValidateAddress(re *regexp.Regexp, args string) []string {
match := re.FindStringSubmatch(args)
if match == nil || strings.Contains(match[1], " ") {
return nil
}
// first argument will be the email address, validate it if not empty
if match[1] != "" {
a, err := mail.ParseAddress(match[1])
if err != nil {
return nil
}
parts := strings.SplitN(a.Address, "@", 2)
if len(parts) != 2 {
return nil
}
// https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1
if len(parts[0]) > 64 || len(parts[1]) > 255 || len(a.Address) > 256 {
return nil
}
}
return match
}

View File

@@ -104,6 +104,20 @@ func TestCmdEHLO(t *testing.T) {
// See RFC 2821 section 4.1.4 for more detail.
cmdCode(t, conn, "MAIL FROM:<sender@example.com>", "250")
cmdCode(t, conn, "RCPT TO:<recipient@example.com>", "250")
// test invalid addresses & header injection
cmdCode(t, conn, "RCPT TO: <recipientrecipientrecipientrecipientrecipientrecipientrecipientrecipientrecipientrecipientrecipientrecipient@exampleexampleexampleexampleexampleexampleexample.com>", "501") // too long
cmdCode(t, conn, "RCPT TO: <recipientrecipientrecipientreciprecipientrecipientrecipientrecipt@exaample.com>", "501")
cmdCode(t, conn, "RCPT TO: <recipient@examplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <r@examplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexample.com>", "250") // valid
cmdCode(t, conn, "RCPT TO:<recipientexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <recipientexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <recipientexample.com>", "501")
cmdCode(t, conn, "RCPT TO:<recipient\rexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <recipient\rexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <recipient\rexample.com>", "501")
cmdCode(t, conn, "RCPT TO: <>", "501") // empty address not allowed here
cmdCode(t, conn, "EHLO host.example.com", "250")
cmdCode(t, conn, "DATA", "503")
@@ -145,6 +159,21 @@ func TestCmdMAIL(t *testing.T) {
// MAIL with seemingly valid but noncompliant FROM arg (double space after the colon) should return 501 syntax error
cmdCode(t, conn, "MAIL FROM: <sender@example.com>", "501")
// test invalid addresses & header injection
cmdCode(t, conn, "MAIL FROM: <sendersendersendersendersendersendersendersendersendersendersendersendersendersendersendersendersendersender@exampleexampleexampleexampleexampleexampleexample.com>", "501") // too long
cmdCode(t, conn, "MAIL FROM: <sendersendersendersendersendersendersendersendersendersendersender@exaample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <sender@examplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <s@examplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexamplexampleexampleexampleexampleexampleexampleexample.com>", "250") // valid
cmdCode(t, conn, "MAIL FROM:<sender\rexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <sender\rexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <sender\rexample.com>", "501")
cmdCode(t, conn, "MAIL FROM:<senderexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <senderexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: <senderexample.com>", "501")
cmdCode(t, conn, "MAIL FROM: < sender@example.com >", "501")
cmdCode(t, conn, "MAIL FROM: < sender@example.com>", "501")
cmdCode(t, conn, "MAIL FROM: <sender@example.com >", "501")
// MAIL with valid SIZE parameter should return 250 Ok
cmdCode(t, conn, "MAIL FROM:<sender@example.com> SIZE=1000", "250")