This commit is contained in:
Aine
2022-11-16 14:23:42 +02:00
parent c1d33fe3cb
commit 86cda29729
9 changed files with 276 additions and 10 deletions

View File

@@ -122,6 +122,13 @@ If you want to change them - check available options in the help message (`!pm h
* **!pm mailboxes** - Show the list of all mailboxes
* **!pm delete** <mailbox> - Delete specific mailbox
---
* **!pm banlist** - Enable/disable banlist and show current values
* **!pm banlist:add** - Ban an IP
* **!pm banlist:remove** - Unban an IP
* **!pm banlist:reset** - Reset banlist
</details>

View File

@@ -2,6 +2,7 @@ package bot
import (
"context"
"net"
"regexp"
"strings"
@@ -71,6 +72,29 @@ func (b *Bot) allowSend(actorID id.UserID, targetRoomID id.RoomID) bool {
return !cfg.NoSend()
}
// IsBanned checks if address is banned
func (b *Bot) IsBanned(addr net.Addr) bool {
if !b.getBotSettings().BanlistEnabled() {
return false
}
return b.getBanlist().Has(addr)
}
// Ban an address
func (b *Bot) Ban(addr net.Addr) {
if !b.getBotSettings().BanlistEnabled() {
return
}
b.log.Debug("banning %s", addr.String())
banlist := b.getBanlist()
banlist.Add(addr)
err := b.setBanlist(banlist)
if err != nil {
b.log.Error("cannot update banlist with %s: %v", addr.String(), err)
}
}
// AllowAuth check if SMTP login (email) and password are valid
func (b *Bot) AllowAuth(email, password string) bool {
var suffix bool

View File

@@ -22,6 +22,10 @@ const (
commandQueueBatch = botOptionQueueBatch
commandQueueRetries = botOptionQueueRetries
commandDelete = "delete"
commandBanlist = "banlist"
commandBanlistAdd = "banlist:add"
commandBanlistRemove = "banlist:remove"
commandBanlistReset = "banlist:reset"
commandMailboxes = "mailboxes"
)
@@ -211,6 +215,27 @@ func (b *Bot) initCommands() commandList {
description: "Delete specific mailbox",
allowed: b.allowAdmin,
},
{allowed: b.allowAdmin}, // delimiter
{
key: commandBanlist,
description: "Enable/disable banlist and show current values",
allowed: b.allowAdmin,
},
{
key: commandBanlistAdd,
description: "Ban an IP",
allowed: b.allowAdmin,
},
{
key: commandBanlistRemove,
description: "Unban an IP",
allowed: b.allowAdmin,
},
{
key: commandBanlistReset,
description: "Reset banlist",
allowed: b.allowAdmin,
},
}
}
@@ -245,6 +270,14 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice
b.runCatchAll(ctx, commandSlice)
case commandDelete:
b.runDelete(ctx, commandSlice)
case commandBanlist:
b.runBanlist(ctx, commandSlice)
case commandBanlistAdd:
b.runBanlistAdd(ctx, commandSlice)
case commandBanlistRemove:
b.runBanlistRemove(ctx, commandSlice)
case commandBanlistReset:
b.runBanlistReset(ctx)
case commandMailboxes:
b.sendMailboxes(ctx)
default:

View File

@@ -3,6 +3,7 @@ package bot
import (
"context"
"fmt"
"net"
"sort"
"strings"
@@ -205,3 +206,104 @@ func (b *Bot) runCatchAll(ctx context.Context, commandSlice []string) {
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Catch-all is set to: `%s` (%s).", mailbox, utils.EmailsList(mailbox, "")))
}
func (b *Bot) runBanlist(ctx context.Context, commandSlice []string) {
evt := eventFromContext(ctx)
cfg := b.getBotSettings()
if len(commandSlice) < 2 {
banlist := b.getBanlist()
var msg strings.Builder
if len(banlist) > 0 {
msg.WriteString("Currently: `")
msg.WriteString(cfg.Get(botOptionBanlistEnabled))
msg.WriteString("` (`")
msg.WriteString(strings.Join(banlist.Slice(), " "))
msg.WriteString("`)\n\n")
}
if !cfg.BanlistEnabled() {
msg.WriteString("To enable banlist, send `")
msg.WriteString(b.prefix)
msg.WriteString(" banlist true`\n\n")
}
msg.WriteString("To ban somebody: `")
msg.WriteString(b.prefix)
msg.WriteString(" banlist:add IP1 IP2 IP3...`")
msg.WriteString("where each ip is IPv4 or IPv6\n")
b.SendNotice(ctx, evt.RoomID, msg.String())
return
}
value := utils.SanitizeBoolString(commandSlice[1])
cfg.Set(botOptionBanlistEnabled, value)
err := b.setBotSettings(cfg)
if err != nil {
b.Error(ctx, evt.RoomID, "cannot set bot config: %v", err)
}
b.SendNotice(ctx, evt.RoomID, "banlist has been updated")
}
func (b *Bot) runBanlistAdd(ctx context.Context, commandSlice []string) {
evt := eventFromContext(ctx)
if len(commandSlice) < 2 {
b.runBanlist(ctx, commandSlice)
return
}
banlist := b.getBanlist()
ips := commandSlice[1:]
for _, ip := range ips {
addr, err := net.ResolveIPAddr("ip", ip)
if err != nil {
b.Error(ctx, evt.RoomID, "cannot add %s to banlist: %v", ip, err)
return
}
banlist.Add(addr)
}
err := b.setBanlist(banlist)
if err != nil {
b.Error(ctx, evt.RoomID, "cannot set banlist: %v", err)
return
}
b.SendNotice(ctx, evt.RoomID, "banlist has been updated, kupo")
}
func (b *Bot) runBanlistRemove(ctx context.Context, commandSlice []string) {
evt := eventFromContext(ctx)
if len(commandSlice) < 2 {
b.runBanlist(ctx, commandSlice)
return
}
banlist := b.getBanlist()
ips := commandSlice[1:]
for _, ip := range ips {
addr, err := net.ResolveIPAddr("ip", ip)
if err != nil {
b.Error(ctx, evt.RoomID, "cannot remove %s from banlist: %v", ip, err)
return
}
banlist.Remove(addr)
}
err := b.setBanlist(banlist)
if err != nil {
b.Error(ctx, evt.RoomID, "cannot set banlist: %v", err)
return
}
b.SendNotice(ctx, evt.RoomID, "banlist has been updated, kupo")
}
func (b *Bot) runBanlistReset(ctx context.Context) {
evt := eventFromContext(ctx)
err := b.setBanlist(banList{})
if err != nil {
b.Error(ctx, evt.RoomID, "cannot set banlist: %v", err)
return
}
b.SendNotice(ctx, evt.RoomID, "banlist has been reset, kupo")
}

74
bot/settings_banlist.go Normal file
View File

@@ -0,0 +1,74 @@
package bot
import (
"net"
"time"
"gitlab.com/etke.cc/postmoogle/utils"
)
// account data key
const acBanlistKey = "cc.etke.postmoogle.banlist"
type banList map[string]string
// Slice returns slice of banlist items
func (b banList) Slice() []string {
slice := make([]string, 0, len(b))
for item := range b {
slice = append(slice, item)
}
return slice
}
func (b banList) getKey(addr net.Addr) string {
key := addr.String()
host, _, _ := net.SplitHostPort(key) //nolint:errcheck // either way it's ok
if host != "" {
key = host
}
return key
}
// Has addr in banlist
func (b banList) Has(addr net.Addr) bool {
_, ok := b[b.getKey(addr)]
return ok
}
// Add an addr to banlist
func (b banList) Add(addr net.Addr) {
key := b.getKey(addr)
if _, ok := b[key]; ok {
return
}
b[key] = time.Now().UTC().Format(time.RFC1123Z)
}
// Remove an addr from banlist
func (b banList) Remove(addr net.Addr) {
key := b.getKey(addr)
if _, ok := b[key]; !ok {
return
}
delete(b, key)
}
func (b *Bot) getBanlist() banList {
config, err := b.lp.GetAccountData(acBanlistKey)
if err != nil {
b.log.Error("cannot get banlist: %v", utils.UnwrapError(err))
}
if config == nil {
config = map[string]string{}
}
return config
}
func (b *Bot) setBanlist(cfg banList) error {
return utils.UnwrapError(b.lp.SetAccountData(acBanlistKey, cfg))
}

View File

@@ -17,6 +17,7 @@ const (
botOptionDKIMPrivateKey = "dkim.pem"
botOptionQueueBatch = "queue:batch"
botOptionQueueRetries = "queue:retries"
botOptionBanlistEnabled = "banlist:enabled"
)
type botSettings map[string]string
@@ -50,6 +51,11 @@ func (s botSettings) CatchAll() string {
return s.Get(botOptionCatchAll)
}
// BanlistEnabled option
func (s botSettings) BanlistEnabled() bool {
return utils.Bool(s.Get(botOptionBanlistEnabled))
}
// DKIMSignature (DNS TXT record)
func (s botSettings) DKIMSignature() string {
return s.Get(botOptionDKIMSignature)

View File

@@ -40,6 +40,8 @@ type Manager struct {
type matrixbot interface {
AllowAuth(string, string) bool
IsBanned(net.Addr) bool
Ban(net.Addr)
GetMapping(string) (id.RoomID, bool)
GetIFOptions(id.RoomID) utils.IncomingFilteringOptions
IncomingEmail(context.Context, *utils.Email) error

View File

@@ -23,11 +23,17 @@ type mailServer struct {
// Login used for outgoing mail submissions only (when you use postmoogle as smtp server in your scripts)
func (m *mailServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
m.log.Debug("Login state=%+v username=%+v", state, username)
if m.bot.IsBanned(state.RemoteAddr) {
return nil, errors.New("please, don't bother me anymore")
}
if !utils.AddressValid(username) {
m.bot.Ban(state.RemoteAddr)
return nil, errors.New("please, provide an email address")
}
if !m.bot.AllowAuth(username, password) {
m.bot.Ban(state.RemoteAddr)
return nil, errors.New("email or password is invalid")
}
@@ -44,6 +50,10 @@ func (m *mailServer) Login(state *smtp.ConnectionState, username, password strin
// AnonymousLogin used for incoming mail submissions only
func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
m.log.Debug("AnonymousLogin state=%+v", state)
if m.bot.IsBanned(state.RemoteAddr) {
return nil, errors.New("please, don't bother me anymore")
}
return &incomingSession{
ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()),
getRoomID: m.bot.GetMapping,
@@ -51,6 +61,7 @@ func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session,
receiveEmail: m.ReceiveEmail,
log: m.log,
domains: m.domains,
addr: state.RemoteAddr,
}, nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"net"
"github.com/emersion/go-smtp"
"github.com/getsentry/sentry-go"
@@ -21,9 +22,11 @@ type incomingSession struct {
getRoomID func(string) (id.RoomID, bool)
getFilters func(id.RoomID) utils.IncomingFilteringOptions
receiveEmail func(context.Context, *utils.Email) error
ban func(net.Addr)
domains []string
ctx context.Context
addr net.Addr
to string
from string
}
@@ -31,6 +34,7 @@ type incomingSession struct {
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
if !utils.AddressValid(from) {
s.ban(s.addr)
return errors.New("please, provide email address")
}
s.from = from
@@ -50,17 +54,20 @@ func (s *incomingSession) Rcpt(to string) error {
}
if !domainok {
s.log.Debug("wrong domain of %s", to)
s.ban(s.addr)
return smtp.ErrAuthRequired
}
roomID, ok := s.getRoomID(utils.Mailbox(to))
if !ok {
s.log.Debug("mapping for %s not found", to)
s.ban(s.addr)
return smtp.ErrAuthRequired
}
validations := s.getFilters(roomID)
if !validateEmail(s.from, s.to, s.log, validations) {
s.ban(s.addr)
return smtp.ErrAuthRequired
}