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 mailboxes** - Show the list of all mailboxes
|
||||||
* **!pm delete** <mailbox> - Delete specific mailbox
|
* **!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>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -71,6 +72,29 @@ func (b *Bot) allowSend(actorID id.UserID, targetRoomID id.RoomID) bool {
|
|||||||
return !cfg.NoSend()
|
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
|
// AllowAuth check if SMTP login (email) and password are valid
|
||||||
func (b *Bot) AllowAuth(email, password string) bool {
|
func (b *Bot) AllowAuth(email, password string) bool {
|
||||||
var suffix bool
|
var suffix bool
|
||||||
|
|||||||
@@ -13,16 +13,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
commandHelp = "help"
|
commandHelp = "help"
|
||||||
commandStop = "stop"
|
commandStop = "stop"
|
||||||
commandSend = "send"
|
commandSend = "send"
|
||||||
commandDKIM = "dkim"
|
commandDKIM = "dkim"
|
||||||
commandCatchAll = botOptionCatchAll
|
commandCatchAll = botOptionCatchAll
|
||||||
commandUsers = botOptionUsers
|
commandUsers = botOptionUsers
|
||||||
commandQueueBatch = botOptionQueueBatch
|
commandQueueBatch = botOptionQueueBatch
|
||||||
commandQueueRetries = botOptionQueueRetries
|
commandQueueRetries = botOptionQueueRetries
|
||||||
commandDelete = "delete"
|
commandDelete = "delete"
|
||||||
commandMailboxes = "mailboxes"
|
commandBanlist = "banlist"
|
||||||
|
commandBanlistAdd = "banlist:add"
|
||||||
|
commandBanlistRemove = "banlist:remove"
|
||||||
|
commandBanlistReset = "banlist:reset"
|
||||||
|
commandMailboxes = "mailboxes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -211,6 +215,27 @@ func (b *Bot) initCommands() commandList {
|
|||||||
description: "Delete specific mailbox",
|
description: "Delete specific mailbox",
|
||||||
allowed: b.allowAdmin,
|
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)
|
b.runCatchAll(ctx, commandSlice)
|
||||||
case commandDelete:
|
case commandDelete:
|
||||||
b.runDelete(ctx, commandSlice)
|
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:
|
case commandMailboxes:
|
||||||
b.sendMailboxes(ctx)
|
b.sendMailboxes(ctx)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package bot
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"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, "")))
|
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"
|
botOptionDKIMPrivateKey = "dkim.pem"
|
||||||
botOptionQueueBatch = "queue:batch"
|
botOptionQueueBatch = "queue:batch"
|
||||||
botOptionQueueRetries = "queue:retries"
|
botOptionQueueRetries = "queue:retries"
|
||||||
|
botOptionBanlistEnabled = "banlist:enabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
type botSettings map[string]string
|
type botSettings map[string]string
|
||||||
@@ -50,6 +51,11 @@ func (s botSettings) CatchAll() string {
|
|||||||
return s.Get(botOptionCatchAll)
|
return s.Get(botOptionCatchAll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BanlistEnabled option
|
||||||
|
func (s botSettings) BanlistEnabled() bool {
|
||||||
|
return utils.Bool(s.Get(botOptionBanlistEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
// DKIMSignature (DNS TXT record)
|
// DKIMSignature (DNS TXT record)
|
||||||
func (s botSettings) DKIMSignature() string {
|
func (s botSettings) DKIMSignature() string {
|
||||||
return s.Get(botOptionDKIMSignature)
|
return s.Get(botOptionDKIMSignature)
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ type Manager struct {
|
|||||||
|
|
||||||
type matrixbot interface {
|
type matrixbot interface {
|
||||||
AllowAuth(string, string) bool
|
AllowAuth(string, string) bool
|
||||||
|
IsBanned(net.Addr) bool
|
||||||
|
Ban(net.Addr)
|
||||||
GetMapping(string) (id.RoomID, bool)
|
GetMapping(string) (id.RoomID, bool)
|
||||||
GetIFOptions(id.RoomID) utils.IncomingFilteringOptions
|
GetIFOptions(id.RoomID) utils.IncomingFilteringOptions
|
||||||
IncomingEmail(context.Context, *utils.Email) error
|
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)
|
// 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) {
|
func (m *mailServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
||||||
m.log.Debug("Login state=%+v username=%+v", state, username)
|
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) {
|
if !utils.AddressValid(username) {
|
||||||
|
m.bot.Ban(state.RemoteAddr)
|
||||||
return nil, errors.New("please, provide an email address")
|
return nil, errors.New("please, provide an email address")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.bot.AllowAuth(username, password) {
|
if !m.bot.AllowAuth(username, password) {
|
||||||
|
m.bot.Ban(state.RemoteAddr)
|
||||||
return nil, errors.New("email or password is invalid")
|
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
|
// AnonymousLogin used for incoming mail submissions only
|
||||||
func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
||||||
m.log.Debug("AnonymousLogin state=%+v", state)
|
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{
|
return &incomingSession{
|
||||||
ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()),
|
ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()),
|
||||||
getRoomID: m.bot.GetMapping,
|
getRoomID: m.bot.GetMapping,
|
||||||
@@ -51,6 +61,7 @@ func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session,
|
|||||||
receiveEmail: m.ReceiveEmail,
|
receiveEmail: m.ReceiveEmail,
|
||||||
log: m.log,
|
log: m.log,
|
||||||
domains: m.domains,
|
domains: m.domains,
|
||||||
|
addr: state.RemoteAddr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
@@ -21,9 +22,11 @@ type incomingSession struct {
|
|||||||
getRoomID func(string) (id.RoomID, bool)
|
getRoomID func(string) (id.RoomID, bool)
|
||||||
getFilters func(id.RoomID) utils.IncomingFilteringOptions
|
getFilters func(id.RoomID) utils.IncomingFilteringOptions
|
||||||
receiveEmail func(context.Context, *utils.Email) error
|
receiveEmail func(context.Context, *utils.Email) error
|
||||||
|
ban func(net.Addr)
|
||||||
domains []string
|
domains []string
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
addr net.Addr
|
||||||
to string
|
to string
|
||||||
from string
|
from string
|
||||||
}
|
}
|
||||||
@@ -31,6 +34,7 @@ type incomingSession struct {
|
|||||||
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
|
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
|
||||||
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
||||||
if !utils.AddressValid(from) {
|
if !utils.AddressValid(from) {
|
||||||
|
s.ban(s.addr)
|
||||||
return errors.New("please, provide email address")
|
return errors.New("please, provide email address")
|
||||||
}
|
}
|
||||||
s.from = from
|
s.from = from
|
||||||
@@ -50,17 +54,20 @@ func (s *incomingSession) Rcpt(to string) error {
|
|||||||
}
|
}
|
||||||
if !domainok {
|
if !domainok {
|
||||||
s.log.Debug("wrong domain of %s", to)
|
s.log.Debug("wrong domain of %s", to)
|
||||||
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return smtp.ErrAuthRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
roomID, ok := s.getRoomID(utils.Mailbox(to))
|
roomID, ok := s.getRoomID(utils.Mailbox(to))
|
||||||
if !ok {
|
if !ok {
|
||||||
s.log.Debug("mapping for %s not found", to)
|
s.log.Debug("mapping for %s not found", to)
|
||||||
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return smtp.ErrAuthRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
validations := s.getFilters(roomID)
|
validations := s.getFilters(roomID)
|
||||||
if !validateEmail(s.from, s.to, s.log, validations) {
|
if !validateEmail(s.from, s.to, s.log, validations) {
|
||||||
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return smtp.ErrAuthRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user