From 99e509ea3a2d6b9c4df5b025438cfddb198a6e35 Mon Sep 17 00:00:00 2001 From: Aine Date: Sat, 8 Oct 2022 00:11:48 +0300 Subject: [PATCH] Email validations --- README.md | 8 ++++++++ bot/command.go | 4 ++-- bot/email.go | 11 +++++++++++ bot/settings_room.go | 6 +++--- go.mod | 3 ++- go.sum | 6 ++++-- smtp/msasession.go | 25 +++++++++++++++++++++++-- smtp/mta.go | 1 + utils/email.go | 9 +++++++++ 9 files changed, 63 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 903153c..e1839a8 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,14 @@ If you want to change them - check available options in the help message (`!pm h --- +* **!pm security:mx** - Enforce sender email MX check (`true` - enforce, `false` - disable) +* **!pm security:smtp** - Enforce sender email SMTP check (`true` - enforce, `false` - disable) +* **!pm spam:emails** - Get or set `spam:emails` of the room (comma-separated list) +* **!pm spam:hosts** - Get or set `spam:hosts` of the room (comma-separated list) +* **!pm spam:localparts** - Get or set `spam:localparts` of the room (comma-separated list) + +--- + * **!pm dkim** - Get DKIM signature * **!pm catch-all** - Configure catch-all mailbox * **!pm users** - Get or set allowed users patterns diff --git a/bot/command.go b/bot/command.go index df202d4..4b54662 100644 --- a/bot/command.go +++ b/bot/command.go @@ -145,8 +145,8 @@ func (b *Bot) initCommands() commandList { }, {allowed: b.allowOwner}, // delimiter { - key: roomOptionSecurityEmail, - description: "Enforce sender email address validation (`true` - enforce, `false` - disable)", + key: roomOptionSecurityMX, + description: "Enforce sender email MX check (`true` - enforce, `false` - disable)", sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, diff --git a/bot/email.go b/bot/email.go index b946795..d69da1d 100644 --- a/bot/email.go +++ b/bot/email.go @@ -59,6 +59,17 @@ func (b *Bot) GetMapping(mailbox string) (id.RoomID, bool) { return roomID, ok } +// GetOptions returns room settings +func (b *Bot) GetOptions(roomID id.RoomID) utils.ValidationOptions { + cfg, err := b.getRoomSettings(roomID) + if err != nil { + b.log.Error("cannot retrieve room settings: %v", err) + return roomSettings{} + } + + return cfg +} + // Send email to matrix room func (b *Bot) Send2Matrix(ctx context.Context, email *utils.Email, incoming bool) error { roomID, ok := b.GetMapping(email.Mailbox(incoming)) diff --git a/bot/settings_room.go b/bot/settings_room.go index 57d57b9..69a8bdc 100644 --- a/bot/settings_room.go +++ b/bot/settings_room.go @@ -24,7 +24,7 @@ const ( roomOptionNoFiles = "nofiles" roomOptionPassword = "password" roomOptionSecuritySMTP = "security:smtp" - roomOptionSecurityEmail = "security:email" + roomOptionSecurityMX = "security:mx" roomOptionSpamEmails = "spam:emails" roomOptionSpamHosts = "spam:hosts" roomOptionSpamLocalparts = "spam:localparts" @@ -86,8 +86,8 @@ func (s roomSettings) SecuritySMTP() bool { return utils.Bool(s.Get(roomOptionSecuritySMTP)) } -func (s roomSettings) SecurityEmail() bool { - return utils.Bool(s.Get(roomOptionSecurityEmail)) +func (s roomSettings) SecurityMX() bool { + return utils.Bool(s.Get(roomOptionSecurityMX)) } func (s roomSettings) SpamEmails() []string { diff --git a/go.mod b/go.mod index c844f19..1c0a75a 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,9 @@ require ( gitlab.com/etke.cc/go/mxidwc v1.0.0 gitlab.com/etke.cc/go/secgen v1.1.1 gitlab.com/etke.cc/go/trysmtp v1.0.0 + gitlab.com/etke.cc/go/validator v1.0.1 gitlab.com/etke.cc/linkpearl v0.0.0-20221002171411-bb783f7e50f0 - golang.org/x/net v0.0.0-20221002022538-bcab6841153b + golang.org/x/net v0.0.0-20221004154528-8021a29435af maunium.net/go/mautrix v0.12.1 ) diff --git a/go.sum b/go.sum index 60bf3c6..af7000d 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ gitlab.com/etke.cc/go/secgen v1.1.1 h1:RmKOki725HIhWJHzPtAc9X4YvBneczndchpMgoDkE gitlab.com/etke.cc/go/secgen v1.1.1/go.mod h1:3pJqRGeWApzx7qXjABqz2o2SMCNpKSZao/gXVdasqE8= gitlab.com/etke.cc/go/trysmtp v1.0.0 h1:f/7gSmzohKniVeLSLevI+ZsySYcPUGkT9cRlOTwjOr8= gitlab.com/etke.cc/go/trysmtp v1.0.0/go.mod h1:KqRuIB2IPElEEbAxXmFyKtm7S5YiuEb4lxwWthccqyE= +gitlab.com/etke.cc/go/validator v1.0.1 h1:xp1tAzgCu9A1pga8rFUo7hODaEcCR1nkkodw96+dYuA= +gitlab.com/etke.cc/go/validator v1.0.1/go.mod h1:3vdssRG4LwgdTr9IHz9MjGSEO+3/FO9hXPGMuSeweJ8= gitlab.com/etke.cc/linkpearl v0.0.0-20221002171411-bb783f7e50f0 h1:B5YV62XKsLb9sCu9jW4Pnc5HDNRzdR1FswtRBMw1sR0= gitlab.com/etke.cc/linkpearl v0.0.0-20221002171411-bb783f7e50f0/go.mod h1:hjn0SVswej+Jo3+MycLm+lTsAVFy047Df+adX6MoXoE= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -105,8 +107,8 @@ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= +golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/smtp/msasession.go b/smtp/msasession.go index 93b293f..1c4df92 100644 --- a/smtp/msasession.go +++ b/smtp/msasession.go @@ -9,6 +9,7 @@ import ( "github.com/getsentry/sentry-go" "github.com/jhillyerd/enmime" "gitlab.com/etke.cc/go/logger" + "gitlab.com/etke.cc/go/validator" "gitlab.com/etke.cc/postmoogle/utils" ) @@ -43,6 +44,7 @@ func (s *msasession) Mail(from string, opts smtp.MailOptions) error { func (s *msasession) Rcpt(to string) error { sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) + s.to = to if s.incoming { if utils.Hostname(to) != s.domain { @@ -50,14 +52,18 @@ func (s *msasession) Rcpt(to string) error { return smtp.ErrAuthRequired } - _, ok := s.bot.GetMapping(utils.Mailbox(to)) + roomID, ok := s.bot.GetMapping(utils.Mailbox(to)) if !ok { s.log.Debug("mapping for %s not found", to) return smtp.ErrAuthRequired } + + validations := s.bot.GetOptions(roomID) + if !s.validate(validations) { + return smtp.ErrAuthRequired + } } - s.to = to s.log.Debug("mail to %s", to) return nil } @@ -75,6 +81,21 @@ func (s *msasession) parseAttachments(parts []*enmime.Part) []*utils.File { return files } +func (s *msasession) validate(options utils.ValidationOptions) bool { + spam := validator.Spam{ + Emails: options.SpamEmails(), + Hosts: options.SpamHosts(), + Localparts: options.SpamLocalparts(), + } + enforce := validator.Enforce{ + MX: options.SecurityMX(), + SMTP: options.SecuritySMTP(), + } + v := validator.New(spam, enforce, s.to, s.log) + + return v.Email(s.from) +} + func (s *msasession) Data(r io.Reader) error { parser := enmime.NewParser() eml, err := parser.ReadEnvelope(r) diff --git a/smtp/mta.go b/smtp/mta.go index a8647cb..f095d91 100644 --- a/smtp/mta.go +++ b/smtp/mta.go @@ -16,6 +16,7 @@ import ( type Bot interface { AllowAuth(string, string) bool GetMapping(string) (id.RoomID, bool) + GetOptions(id.RoomID) utils.ValidationOptions Send2Matrix(ctx context.Context, email *utils.Email, incoming bool) error SetMTA(mta utils.MTA) } diff --git a/utils/email.go b/utils/email.go index b415929..6f7e279 100644 --- a/utils/email.go +++ b/utils/email.go @@ -19,6 +19,15 @@ type MTA interface { Send(from, to, data string) error } +// ValidationOptions for incoming mail +type ValidationOptions interface { + SecuritySMTP() bool + SecurityMX() bool + SpamEmails() []string + SpamHosts() []string + SpamLocalparts() []string +} + // Email object type Email struct { Date string