#11: implement MTA-STS, TLS-RPT & Autoconfig HTTP services

This commit is contained in:
Lucas Rayan committed 2026-07-25 10:18:03 -03:00
1 parent c0042ef207
commit 314eb67dda
20 files changed
+1177 -31

No files matched your search

@@ -0,0 +1,79 @@
package valueobject
import (
"fmt"
)
// BuildThunderbirdAutoconfigXML generates Mozilla Thunderbird config-v1.1 XML payload.
func BuildThunderbirdAutoconfigXML(domainName, mailHost string) string {
if mailHost == "" {
mailHost = fmt.Sprintf("mail.%s", domainName)
}
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="%s">
<domain>%s</domain>
<displayName>LambdaMail - %s</displayName>
<displayShortName>%s</displayShortName>
<incomingServer type="imap">
<hostname>%s</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%%EMAILADDRESS%%</username>
</incomingServer>
<incomingServer type="pop3">
<hostname>%s</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%%EMAILADDRESS%%</username>
</incomingServer>
<outgoingServer type="smtp">
<hostname>%s</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%%EMAILADDRESS%%</username>
</outgoingServer>
</emailProvider>
</clientConfig>`, domainName, domainName, domainName, domainName, mailHost, mailHost, mailHost)
}
// BuildOutlookAutodiscoverXML generates Microsoft POX Autodiscover response XML.
func BuildOutlookAutodiscoverXML(domainName, mailHost, emailAddress string) string {
if mailHost == "" {
mailHost = fmt.Sprintf("mail.%s", domainName)
}
return fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<User>
<DisplayName>%s</DisplayName>
</User>
<Account>
<AccountType>email</AccountType>
<Action>settings</Action>
<Protocol>
<Type>IMAP</Type>
<Server>%s</Server>
<Port>993</Port>
<SSL>on</SSL>
<AuthRequired>on</AuthRequired>
<LoginName>%s</LoginName>
</Protocol>
<Protocol>
<Type>SMTP</Type>
<Server>%s</Server>
<Port>587</Port>
<SSL>off</SSL>
<Encryption>TLS</Encryption>
<AuthRequired>on</AuthRequired>
<LoginName>%s</LoginName>
</Protocol>
</Account>
</Response>
</Autodiscover>`, emailAddress, mailHost, emailAddress, mailHost, emailAddress)
}
@@ -0,0 +1,26 @@
package valueobject
import (
"strings"
"testing"
)
func TestBuildThunderbirdAutoconfigXML(t *testing.T) {
xml := BuildThunderbirdAutoconfigXML("example.test", "mail.example.test")
if !strings.Contains(xml, "<hostname>mail.example.test</hostname>") {
t.Errorf("missing mail host: %s", xml)
}
if !strings.Contains(xml, "<port>993</port>") {
t.Errorf("missing port 993: %s", xml)
}
}
func TestBuildOutlookAutodiscoverXML(t *testing.T) {
xml := BuildOutlookAutodiscoverXML("example.test", "mail.example.test", "user@example.test")
if !strings.Contains(xml, "<Server>mail.example.test</Server>") {
t.Errorf("missing Server: %s", xml)
}
if !strings.Contains(xml, "<LoginName>user@example.test</LoginName>") {
t.Errorf("missing LoginName: %s", xml)
}
}
@@ -0,0 +1,44 @@
package valueobject
import (
"fmt"
"strings"
)
type MtaStsMode string
const (
MtaStsModeTesting MtaStsMode = "testing"
MtaStsModeEnforce MtaStsMode = "enforce"
MtaStsModeNone MtaStsMode = "none"
)
type MtaStsPolicy struct {
Version string
Mode MtaStsMode
MX []string
MaxAge int
}
func NewMtaStsPolicy(mode MtaStsMode, mx []string, maxAgeSeconds int) MtaStsPolicy {
if maxAgeSeconds <= 0 {
maxAgeSeconds = 604800 // default 7 days
}
return MtaStsPolicy{
Version: "STSv1",
Mode: mode,
MX: mx,
MaxAge: maxAgeSeconds,
}
}
func (p MtaStsPolicy) Format() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("version: %s\r\n", p.Version))
sb.WriteString(fmt.Sprintf("mode: %s\r\n", p.Mode))
for _, mxHost := range p.MX {
sb.WriteString(fmt.Sprintf("mx: %s\r\n", mxHost))
}
sb.WriteString(fmt.Sprintf("max_age: %d\r\n", p.MaxAge))
return sb.String()
}
@@ -0,0 +1,24 @@
package valueobject
import (
"strings"
"testing"
)
func TestMtaStsPolicy_Format(t *testing.T) {
policy := NewMtaStsPolicy(MtaStsModeEnforce, []string{"mail.example.test"}, 604800)
formatted := policy.Format()
if !strings.Contains(formatted, "version: STSv1") {
t.Errorf("missing version: %s", formatted)
}
if !strings.Contains(formatted, "mode: enforce") {
t.Errorf("missing mode: %s", formatted)
}
if !strings.Contains(formatted, "mx: mail.example.test") {
t.Errorf("missing mx: %s", formatted)
}
if !strings.Contains(formatted, "max_age: 604800") {
t.Errorf("missing max_age: %s", formatted)
}
}