banlist
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
74
bot/settings_banlist.go
Normal 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))
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user