move dnsbl command to per-mailbox spamcheck:rbl; refactor smtp a bit
This commit is contained in:
parent
8246e2c074
commit
b30e013172
15 changed files with 116 additions and 137 deletions
|
|
@ -26,6 +26,7 @@ so you can use it to send emails from your apps and scripts as well.
|
||||||
- [x] SMTP verification
|
- [x] SMTP verification
|
||||||
- [x] DKIM verification
|
- [x] DKIM verification
|
||||||
- [x] SPF verification
|
- [x] SPF verification
|
||||||
|
- [x] RBL verification
|
||||||
- [x] MX verification
|
- [x] MX verification
|
||||||
- [x] Spamlist of emails (wildcards supported)
|
- [x] Spamlist of emails (wildcards supported)
|
||||||
- [x] Spamlist of hosts (per server only)
|
- [x] Spamlist of hosts (per server only)
|
||||||
|
|
@ -149,6 +150,7 @@ If you want to change them - check available options in the help message (`!pm h
|
||||||
|
|
||||||
* **`!pm spamcheck:mx`** - only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)
|
* **`!pm spamcheck:mx`** - only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)
|
||||||
* **`!pm spamcheck:spf`** - only accept email from senders which authorized to send it (those matching SPF records) (`true` - enable, `false` - disable)
|
* **`!pm spamcheck:spf`** - only accept email from senders which authorized to send it (those matching SPF records) (`true` - enable, `false` - disable)
|
||||||
|
* **`!pm spamcheck:rbl`** - reject incoming emails from hosts listed in DNS blocklists (`true` - enable, `false` - disable)
|
||||||
* **`!pm spamcheck:dkim`** - only accept correctly authorized emails (without DKIM signature at all or with valid DKIM signature) (`true` - enable, `false` - disable)
|
* **`!pm spamcheck:dkim`** - only accept correctly authorized emails (without DKIM signature at all or with valid DKIM signature) (`true` - enable, `false` - disable)
|
||||||
* **`!pm spamcheck:smtp`** - only accept email from servers which seem prepared to receive it (those listening on an SMTP port) (`true` - enable, `false` - disable)
|
* **`!pm spamcheck:smtp`** - only accept email from servers which seem prepared to receive it (those listening on an SMTP port) (`true` - enable, `false` - disable)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,28 +189,6 @@ func (b *Bot) BanAuth(ctx context.Context, addr net.Addr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ban an address listed in DNS Blacklists automatically
|
|
||||||
func (b *Bot) BanDNSBL(ctx context.Context, addr net.Addr) {
|
|
||||||
if !b.cfg.GetBot(ctx).BanlistEnabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !b.cfg.GetBot(ctx).BanlistDNSBL() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsTrusted(addr) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.log.Debug().Str("addr", addr.String()).Msg("attempting to automatically ban")
|
|
||||||
banlist := b.cfg.GetBanlist(ctx)
|
|
||||||
banlist.Add(addr)
|
|
||||||
err := b.cfg.SetBanlist(ctx, banlist)
|
|
||||||
if err != nil {
|
|
||||||
b.log.Error().Err(err).Str("addr", addr.String()).Msg("cannot update banlist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ban an address manually
|
// Ban an address manually
|
||||||
func (b *Bot) BanManually(ctx context.Context, addr net.Addr) {
|
func (b *Bot) BanManually(ctx context.Context, addr net.Addr) {
|
||||||
if !b.cfg.GetBot(ctx).BanlistEnabled() {
|
if !b.cfg.GetBot(ctx).BanlistEnabled() {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ const (
|
||||||
commandBanlistTotals = "banlist:totals"
|
commandBanlistTotals = "banlist:totals"
|
||||||
commandBanlistAuto = "banlist:auto"
|
commandBanlistAuto = "banlist:auto"
|
||||||
commandBanlistAuth = "banlist:auth"
|
commandBanlistAuth = "banlist:auth"
|
||||||
commandBanlistDNSBL = "banlist:dnsbl"
|
|
||||||
commandBanlistAdd = "banlist:add"
|
commandBanlistAdd = "banlist:add"
|
||||||
commandBanlistRemove = "banlist:remove"
|
commandBanlistRemove = "banlist:remove"
|
||||||
commandBanlistReset = "banlist:reset"
|
commandBanlistReset = "banlist:reset"
|
||||||
|
|
@ -256,6 +255,12 @@ func (b *Bot) initCommands() commandList {
|
||||||
sanitizer: utils.SanitizeBoolString,
|
sanitizer: utils.SanitizeBoolString,
|
||||||
allowed: b.allowOwner,
|
allowed: b.allowOwner,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: config.RoomSpamcheckRBL,
|
||||||
|
description: "reject incoming emails from hosts listed in DNS blocklists (`true` - enable, `false` - disable)",
|
||||||
|
sanitizer: utils.SanitizeBoolString,
|
||||||
|
allowed: b.allowOwner,
|
||||||
|
},
|
||||||
{allowed: b.allowOwner, description: "mailbox anti-spam"}, // delimiter
|
{allowed: b.allowOwner, description: "mailbox anti-spam"}, // delimiter
|
||||||
{
|
{
|
||||||
key: commandSpamlist,
|
key: commandSpamlist,
|
||||||
|
|
@ -337,11 +342,6 @@ func (b *Bot) initCommands() commandList {
|
||||||
description: "Enable/disable automatic banning of IP addresses when they try to auth with invalid credentials",
|
description: "Enable/disable automatic banning of IP addresses when they try to auth with invalid credentials",
|
||||||
allowed: b.allowAdmin,
|
allowed: b.allowAdmin,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: commandBanlistDNSBL,
|
|
||||||
description: "Enable/disable automatic banning of IP addresses when they are listed in DNS Blacklists",
|
|
||||||
allowed: b.allowAdmin,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: commandBanlistAuto,
|
key: commandBanlistAuto,
|
||||||
description: "Enable/disable automatic banning of IP addresses when they try to send invalid emails",
|
description: "Enable/disable automatic banning of IP addresses when they try to send invalid emails",
|
||||||
|
|
@ -439,8 +439,6 @@ func (b *Bot) handle(ctx context.Context) {
|
||||||
b.runBanlist(ctx, commandSlice)
|
b.runBanlist(ctx, commandSlice)
|
||||||
case commandBanlistAuth:
|
case commandBanlistAuth:
|
||||||
b.runBanlistAuth(ctx, commandSlice)
|
b.runBanlistAuth(ctx, commandSlice)
|
||||||
case commandBanlistDNSBL:
|
|
||||||
b.runBanlistDNSBL(ctx, commandSlice)
|
|
||||||
case commandBanlistAuto:
|
case commandBanlistAuto:
|
||||||
b.runBanlistAuto(ctx, commandSlice)
|
b.runBanlistAuto(ctx, commandSlice)
|
||||||
case commandBanlistTotals:
|
case commandBanlistTotals:
|
||||||
|
|
|
||||||
|
|
@ -380,33 +380,6 @@ func (b *Bot) runBanlistAuth(ctx context.Context, commandSlice []string) { //nol
|
||||||
b.lp.SendNotice(ctx, evt.RoomID, "auth banning has been updated", linkpearl.RelatesTo(evt.ID))
|
b.lp.SendNotice(ctx, evt.RoomID, "auth banning has been updated", linkpearl.RelatesTo(evt.ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) runBanlistDNSBL(ctx context.Context, commandSlice []string) { //nolint:dupl // not in that case
|
|
||||||
evt := eventFromContext(ctx)
|
|
||||||
cfg := b.cfg.GetBot(ctx)
|
|
||||||
if len(commandSlice) < 2 {
|
|
||||||
var msg strings.Builder
|
|
||||||
msg.WriteString("Currently: `")
|
|
||||||
msg.WriteString(cfg.Get(config.BotBanlistDNSBL))
|
|
||||||
msg.WriteString("`\n\n")
|
|
||||||
|
|
||||||
if !cfg.BanlistDNSBL() {
|
|
||||||
msg.WriteString("To enable automatic banning by using DNS Blacklists, send `")
|
|
||||||
msg.WriteString(b.prefix)
|
|
||||||
msg.WriteString(" banlist:dnsbl true` (banlist itself must be enabled!)\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.lp.SendNotice(ctx, evt.RoomID, msg.String(), linkpearl.RelatesTo(evt.ID))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value := utils.SanitizeBoolString(commandSlice[1])
|
|
||||||
cfg.Set(config.BotBanlistDNSBL, value)
|
|
||||||
err := b.cfg.SetBot(ctx, cfg)
|
|
||||||
if err != nil {
|
|
||||||
b.Error(ctx, "cannot set bot config: %v", err)
|
|
||||||
}
|
|
||||||
b.lp.SendNotice(ctx, evt.RoomID, "dns blacklists banning has been updated", linkpearl.RelatesTo(evt.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) runBanlistAuto(ctx context.Context, commandSlice []string) { //nolint:dupl // not in that case
|
func (b *Bot) runBanlistAuto(ctx context.Context, commandSlice []string) { //nolint:dupl // not in that case
|
||||||
evt := eventFromContext(ctx)
|
evt := eventFromContext(ctx)
|
||||||
cfg := b.cfg.GetBot(ctx)
|
cfg := b.cfg.GetBot(ctx)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ const (
|
||||||
BotBanlistEnabled = "banlist:enabled"
|
BotBanlistEnabled = "banlist:enabled"
|
||||||
BotBanlistAuto = "banlist:auto"
|
BotBanlistAuto = "banlist:auto"
|
||||||
BotBanlistAuth = "banlist:auth"
|
BotBanlistAuth = "banlist:auth"
|
||||||
BotBanlistDNSBL = "banlist:dnsbl"
|
|
||||||
BotGreylist = "greylist"
|
BotGreylist = "greylist"
|
||||||
BotMautrix015Migration = "mautrix015migration"
|
BotMautrix015Migration = "mautrix015migration"
|
||||||
)
|
)
|
||||||
|
|
@ -85,11 +84,6 @@ func (s Bot) BanlistAuth() bool {
|
||||||
return utils.Bool(s.Get(BotBanlistAuth))
|
return utils.Bool(s.Get(BotBanlistAuth))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BanlistDNSBL option
|
|
||||||
func (s Bot) BanlistDNSBL() bool {
|
|
||||||
return utils.Bool(s.Get(BotBanlistDNSBL))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greylist option (duration in minutes)
|
// Greylist option (duration in minutes)
|
||||||
func (s Bot) Greylist() int {
|
func (s Bot) Greylist() int {
|
||||||
return utils.Int(s.Get(BotGreylist))
|
return utils.Int(s.Get(BotGreylist))
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,11 @@ const (
|
||||||
RoomNoSubject = "nosubject"
|
RoomNoSubject = "nosubject"
|
||||||
RoomNoThreads = "nothreads"
|
RoomNoThreads = "nothreads"
|
||||||
|
|
||||||
|
RoomSpamcheckRBL = "spamcheck:rbl"
|
||||||
RoomSpamcheckDKIM = "spamcheck:dkim"
|
RoomSpamcheckDKIM = "spamcheck:dkim"
|
||||||
RoomSpamcheckMX = "spamcheck:mx"
|
|
||||||
RoomSpamcheckSMTP = "spamcheck:smtp"
|
RoomSpamcheckSMTP = "spamcheck:smtp"
|
||||||
RoomSpamcheckSPF = "spamcheck:spf"
|
RoomSpamcheckSPF = "spamcheck:spf"
|
||||||
|
RoomSpamcheckMX = "spamcheck:mx"
|
||||||
|
|
||||||
RoomSpamlist = "spamlist"
|
RoomSpamlist = "spamlist"
|
||||||
)
|
)
|
||||||
|
|
@ -147,6 +148,10 @@ func (s Room) NoInlines() bool {
|
||||||
return utils.Bool(s.Get(RoomNoInlines))
|
return utils.Bool(s.Get(RoomNoInlines))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Room) SpamcheckRBL() bool {
|
||||||
|
return utils.Bool(s.Get(RoomSpamcheckRBL))
|
||||||
|
}
|
||||||
|
|
||||||
func (s Room) SpamcheckDKIM() bool {
|
func (s Room) SpamcheckDKIM() bool {
|
||||||
return utils.Bool(s.Get(RoomSpamcheckDKIM))
|
return utils.Bool(s.Get(RoomSpamcheckDKIM))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ func initMatrix(cfg *config.Config) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cannot initialize SQL database")
|
log.Fatal().Err(err).Msg("cannot initialize SQL database")
|
||||||
}
|
}
|
||||||
|
if cfg.DB.Dialect == "sqlite" {
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
}
|
||||||
|
|
||||||
lp, err := linkpearl.New(&linkpearl.Config{
|
lp, err := linkpearl.New(&linkpearl.Config{
|
||||||
Homeserver: cfg.Homeserver,
|
Homeserver: cfg.Homeserver,
|
||||||
|
|
|
||||||
2
e2e/send
2
e2e/send
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
ssmtp -v aine@gelato.casa < $1
|
ssmtp -v test@localhost < $1
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ type IncomingFilteringOptions interface {
|
||||||
SpamcheckDKIM() bool
|
SpamcheckDKIM() bool
|
||||||
SpamcheckSMTP() bool
|
SpamcheckSMTP() bool
|
||||||
SpamcheckSPF() bool
|
SpamcheckSPF() bool
|
||||||
|
SpamcheckRBL() bool
|
||||||
SpamcheckMX() bool
|
SpamcheckMX() bool
|
||||||
Spamlist() []string
|
Spamlist() []string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ type DNSBLResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckDNSBLs checks if the given IP address is listed in any of the DNSBLs, and returns a decision, based on the results
|
// CheckDNSBLs checks if the given IP address is listed in any of the DNSBLs, and returns a decision, based on the results
|
||||||
func CheckDNSBLs(ctx context.Context, log *zerolog.Logger, addr net.Addr, optionalTimeout ...time.Duration) bool {
|
func CheckDNSBLs(ctx context.Context, log *zerolog.Logger, addr net.Addr, optionalTimeout ...time.Duration) (blocked bool, reasons []string) {
|
||||||
ttl := DNSBLTimeout
|
ttl := DNSBLTimeout
|
||||||
if len(optionalTimeout) > 0 {
|
if len(optionalTimeout) > 0 {
|
||||||
ttl = optionalTimeout[0]
|
ttl = optionalTimeout[0]
|
||||||
|
|
@ -77,6 +77,7 @@ func CheckDNSBLs(ctx context.Context, log *zerolog.Logger, addr net.Addr, option
|
||||||
}
|
}
|
||||||
if r.Listed {
|
if r.Listed {
|
||||||
listed++
|
listed++
|
||||||
|
reasons = append(reasons, r.Reasons)
|
||||||
listedRBLs = append(listedRBLs, r.RBL)
|
listedRBLs = append(listedRBLs, r.RBL)
|
||||||
} else {
|
} else {
|
||||||
unlisted++
|
unlisted++
|
||||||
|
|
@ -95,7 +96,7 @@ func CheckDNSBLs(ctx context.Context, log *zerolog.Logger, addr net.Addr, option
|
||||||
Bool("blocked", decision).
|
Bool("blocked", decision).
|
||||||
Msg("DNSBL results")
|
Msg("DNSBL results")
|
||||||
|
|
||||||
return decision
|
return decision, reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
// check checks if the given IP address is listed in any of the DNSBLs
|
// check checks if the given IP address is listed in any of the DNSBLs
|
||||||
|
|
|
||||||
65
smtp/errors.go
Normal file
65
smtp/errors.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package smtp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoUserCode SMTP code
|
||||||
|
NoUserCode = 550
|
||||||
|
// BannedCode SMTP code
|
||||||
|
BannedCode = 554
|
||||||
|
// GreylistCode SMTP code
|
||||||
|
GreylistCode = 451
|
||||||
|
// RBLCode SMTP code
|
||||||
|
RBLCode = 450
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// NoUserEnhancedCode enhanced SMTP code
|
||||||
|
NoUserEnhancedCode = smtp.EnhancedCode{5, 5, 0}
|
||||||
|
// BannedEnhancedCode enhanced SMTP code
|
||||||
|
BannedEnhancedCode = smtp.EnhancedCode{5, 5, 4}
|
||||||
|
// GreylistEnhancedCode is GraylistCode in enhanced code notation
|
||||||
|
GreylistEnhancedCode = smtp.EnhancedCode{4, 5, 1}
|
||||||
|
// RBLEnhancedCode is RBLCode in enhanced code notation
|
||||||
|
RBLEnhancedCode = smtp.EnhancedCode{4, 5, 0}
|
||||||
|
// ErrBanned returned to banned hosts
|
||||||
|
ErrBanned = &smtp.SMTPError{
|
||||||
|
Code: BannedCode,
|
||||||
|
EnhancedCode: BannedEnhancedCode,
|
||||||
|
Message: "please, don't bother me anymore, kupo.",
|
||||||
|
}
|
||||||
|
// ErrNoUser returned when no such mailbox found
|
||||||
|
ErrNoUser = &smtp.SMTPError{
|
||||||
|
Code: NoUserCode,
|
||||||
|
EnhancedCode: NoUserEnhancedCode,
|
||||||
|
Message: "no such user here, kupo.",
|
||||||
|
}
|
||||||
|
// ErrGreylisted returned when the host is graylisted
|
||||||
|
ErrGreylisted = &smtp.SMTPError{
|
||||||
|
Code: GreylistCode,
|
||||||
|
EnhancedCode: GreylistEnhancedCode,
|
||||||
|
Message: "You have been greylisted, try again a bit later.",
|
||||||
|
}
|
||||||
|
// ErrRBL returned when the host is blacklisted
|
||||||
|
ErrRBL = &smtp.SMTPError{
|
||||||
|
Code: RBLCode,
|
||||||
|
EnhancedCode: RBLEnhancedCode,
|
||||||
|
Message: "You are blacklisted, kupo.",
|
||||||
|
}
|
||||||
|
// ErrInvalidEmail for invalid emails :)
|
||||||
|
ErrInvalidEmail = errors.New("please, provide valid email address")
|
||||||
|
)
|
||||||
|
|
||||||
|
// extendErrRBL extends the RBL error with reasons returned by the DNSBLs
|
||||||
|
func extendErrRBL(reasons []string) *smtp.SMTPError {
|
||||||
|
return &smtp.SMTPError{
|
||||||
|
Code: RBLCode,
|
||||||
|
EnhancedCode: RBLEnhancedCode,
|
||||||
|
Message: "You are blacklisted, kupo. Details: " + strings.Join(reasons, "; "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,14 +17,12 @@ type Listener struct {
|
||||||
tlsMu sync.Mutex
|
tlsMu sync.Mutex
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
isBanned func(context.Context, net.Addr) bool
|
isBanned func(context.Context, net.Addr) bool
|
||||||
banDNSBL func(context.Context, net.Addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListener(
|
func NewListener(
|
||||||
port string,
|
port string,
|
||||||
tlsConfig *tls.Config,
|
tlsConfig *tls.Config,
|
||||||
isBanned func(context.Context, net.Addr) bool,
|
isBanned func(context.Context, net.Addr) bool,
|
||||||
banDNSBL func(context.Context, net.Addr),
|
|
||||||
log *zerolog.Logger,
|
log *zerolog.Logger,
|
||||||
) (*Listener, error) {
|
) (*Listener, error) {
|
||||||
actual, err := net.Listen("tcp", ":"+port)
|
actual, err := net.Listen("tcp", ":"+port)
|
||||||
|
|
@ -38,7 +36,6 @@ func NewListener(
|
||||||
tls: tlsConfig,
|
tls: tlsConfig,
|
||||||
listener: actual,
|
listener: actual,
|
||||||
isBanned: isBanned,
|
isBanned: isBanned,
|
||||||
banDNSBL: banDNSBL,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,15 +66,6 @@ func (l *Listener) Accept() (net.Conn, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msg("checking dns blacklists...")
|
|
||||||
if CheckDNSBLs(ctx, l.log, conn.RemoteAddr()) {
|
|
||||||
//nolint:gocritic // TODO
|
|
||||||
// conn.Close()
|
|
||||||
// l.banDNSBL(ctx, conn.RemoteAddr())
|
|
||||||
log.Info().Msg("should rejected connection (DNS Blacklist); but won't do it for now (for testing purposes)")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Msg("accepted connection")
|
log.Info().Msg("accepted connection")
|
||||||
|
|
||||||
if l.tls != nil {
|
if l.tls != nil {
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ type matrixbot interface {
|
||||||
IsTrusted(net.Addr) bool
|
IsTrusted(net.Addr) bool
|
||||||
BanAuto(context.Context, net.Addr)
|
BanAuto(context.Context, net.Addr)
|
||||||
BanAuth(context.Context, net.Addr)
|
BanAuth(context.Context, net.Addr)
|
||||||
BanDNSBL(context.Context, net.Addr)
|
|
||||||
GetMapping(context.Context, string) (id.RoomID, bool)
|
GetMapping(context.Context, string) (id.RoomID, bool)
|
||||||
GetIFOptions(context.Context, id.RoomID) email.IncomingFilteringOptions
|
GetIFOptions(context.Context, id.RoomID) email.IncomingFilteringOptions
|
||||||
IncomingEmail(context.Context, *email.Email) error
|
IncomingEmail(context.Context, *email.Email) error
|
||||||
|
|
@ -174,7 +173,7 @@ func (m *Manager) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) listen(port string, tlsConfig *tls.Config) {
|
func (m *Manager) listen(port string, tlsConfig *tls.Config) {
|
||||||
lwrapper, err := NewListener(port, tlsConfig, m.bot.IsBanned, m.bot.BanDNSBL, m.log)
|
lwrapper, err := NewListener(port, tlsConfig, m.bot.IsBanned, m.log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Error().Err(err).Str("port", port).Msg("cannot start listener")
|
m.log.Error().Err(err).Str("port", port).Msg("cannot start listener")
|
||||||
m.errs <- err
|
m.errs <- err
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,6 @@ import (
|
||||||
"gitlab.com/etke.cc/postmoogle/email"
|
"gitlab.com/etke.cc/postmoogle/email"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// NoUserCode SMTP code
|
|
||||||
NoUserCode = 550
|
|
||||||
// BannedCode SMTP code
|
|
||||||
BannedCode = 554
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NoUserEnhancedCode enhanced SMTP code
|
|
||||||
NoUserEnhancedCode = smtp.EnhancedCode{5, 5, 0}
|
|
||||||
// BannedEnhancedCode enhanced SMTP code
|
|
||||||
BannedEnhancedCode = smtp.EnhancedCode{5, 5, 4}
|
|
||||||
// ErrBanned returned to banned hosts
|
|
||||||
ErrBanned = &smtp.SMTPError{
|
|
||||||
Code: BannedCode,
|
|
||||||
EnhancedCode: BannedEnhancedCode,
|
|
||||||
Message: "please, don't bother me anymore, kupo.",
|
|
||||||
}
|
|
||||||
// ErrNoUser returned when no such mailbox found
|
|
||||||
ErrNoUser = &smtp.SMTPError{
|
|
||||||
Code: NoUserCode,
|
|
||||||
EnhancedCode: NoUserEnhancedCode,
|
|
||||||
Message: "no such user here, kupo.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type mailServer struct {
|
type mailServer struct {
|
||||||
bot matrixbot
|
bot matrixbot
|
||||||
log *zerolog.Logger
|
log *zerolog.Logger
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package smtp
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -22,22 +21,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// GraylistCode SMTP code
|
|
||||||
GraylistCode = 451
|
|
||||||
// Incoming is the direction of the email
|
// Incoming is the direction of the email
|
||||||
Incoming = "incoming"
|
Incoming = "incoming"
|
||||||
// Outgoing is the direction of the email
|
// Outgoing is the direction of the email
|
||||||
Outoing = "outgoing"
|
Outoing = "outgoing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ensure that session implements smtp.AuthSession
|
||||||
// ErrInvalidEmail for invalid emails :)
|
var _ smtp.AuthSession = (*session)(nil)
|
||||||
ErrInvalidEmail = errors.New("please, provide valid email address")
|
|
||||||
// GraylistEnhancedCode is GraylistCode in enhanced code notation
|
|
||||||
GraylistEnhancedCode = smtp.EnhancedCode{4, 5, 1}
|
|
||||||
// ensure that session implements smtp.AuthSession
|
|
||||||
_ smtp.AuthSession = (*session)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
log *zerolog.Logger
|
log *zerolog.Logger
|
||||||
|
|
@ -95,27 +86,38 @@ func (s *session) authPlain(_, username, password string) error {
|
||||||
|
|
||||||
func (s *session) Mail(from string, _ *smtp.MailOptions) error {
|
func (s *session) Mail(from string, _ *smtp.MailOptions) error {
|
||||||
if s.dir == Outoing {
|
if s.dir == Outoing {
|
||||||
if err := s.validateOutgoingMail(from); err != nil {
|
return s.validateOutgoingMail(from)
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !email.AddressValid(from) {
|
|
||||||
s.log.Debug().Str("from", from).Msg("address is invalid")
|
|
||||||
s.bot.BanAuto(s.ctx, s.conn.Conn().RemoteAddr())
|
|
||||||
return ErrBanned
|
|
||||||
}
|
|
||||||
s.from = email.Address(from)
|
|
||||||
s.log.Debug().Str("from", from).Msg("incoming mail")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// incoming mail
|
||||||
|
if !email.AddressValid(from) {
|
||||||
|
s.log.Debug().Str("from", from).Msg("address is invalid")
|
||||||
|
s.bot.BanAuto(s.ctx, s.conn.Conn().RemoteAddr())
|
||||||
|
return ErrBanned
|
||||||
|
}
|
||||||
|
s.from = email.Address(from)
|
||||||
|
s.log.Debug().Str("from", from).Msg("incoming mail")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) Rcpt(to string, _ *smtp.RcptOptions) error {
|
func (s *session) Rcpt(to string, _ *smtp.RcptOptions) error {
|
||||||
s.tos = append(s.tos, to)
|
s.tos = append(s.tos, to)
|
||||||
s.log.Debug().Str("to", to).Msg("mail")
|
s.log.Debug().Str("to", to).Msg("mail")
|
||||||
if s.dir != Outoing {
|
if s.dir == Outoing {
|
||||||
if err := s.validateIncomingRcpt(to); err != nil {
|
return nil
|
||||||
return err
|
}
|
||||||
|
if err := s.validateIncomingRcpt(to); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.bot.GetIFOptions(s.ctx, s.roomID).SpamcheckRBL() {
|
||||||
|
s.log.Info().Msg("checking dns blacklists...")
|
||||||
|
if listed, reasons := CheckDNSBLs(s.ctx, s.log, s.conn.Conn().RemoteAddr()); listed {
|
||||||
|
s.log.Info().Strs("reasons", reasons).Msg("rejected incoming email (DNS Blacklist)")
|
||||||
|
if len(reasons) > 0 {
|
||||||
|
return extendErrRBL(reasons)
|
||||||
|
}
|
||||||
|
return ErrRBL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,11 +175,7 @@ func (s *session) incomingData(r io.Reader) error {
|
||||||
return ErrBanned
|
return ErrBanned
|
||||||
}
|
}
|
||||||
if s.bot.IsGreylisted(s.ctx, addr) {
|
if s.bot.IsGreylisted(s.ctx, addr) {
|
||||||
return &smtp.SMTPError{
|
return ErrGreylisted
|
||||||
Code: GraylistCode,
|
|
||||||
EnhancedCode: GraylistEnhancedCode,
|
|
||||||
Message: "You have been greylisted, try again a bit later.",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if validations.SpamcheckDKIM() {
|
if validations.SpamcheckDKIM() {
|
||||||
results, verr := dkim.Verify(reader)
|
results, verr := dkim.Verify(reader)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue