new commands: spam:list, spam:add, spam:remove, spam:reset; updated readme with the full list of commands; rearranged sections in help
This commit is contained in:
101
README.md
101
README.md
@@ -97,55 +97,88 @@ If you want to change them - check available options in the help message (`!pm h
|
|||||||
<details>
|
<details>
|
||||||
<summary>Full list of available commands</summary>
|
<summary>Full list of available commands</summary>
|
||||||
|
|
||||||
* **!pm help** - Show help message
|
> The following section is visible to all allowed users
|
||||||
* **!pm stop** - Disable bridge for the room and clear all configuration
|
|
||||||
|
* **`!pm help`** - Show this help message
|
||||||
|
* **`!pm stop`** - Disable bridge for the room and clear all configuration
|
||||||
|
* **`!pm send`** - Send email
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* **!pm mailbox** - Get or set mailbox of the room
|
#### mailbox ownership
|
||||||
* **!pm domain** - Get or set default domain of the room
|
|
||||||
* **!pm owner** - Get or set owner of the room
|
> The following section is visible to the mailbox owners only
|
||||||
* **!pm password** - Get or set SMTP password of the room's mailbox
|
|
||||||
|
* **`!pm mailbox`** - Get or set mailbox of the room
|
||||||
|
* **`!pm domain`** - Get or set default domain of the room
|
||||||
|
* **`!pm owner`** - Get or set owner of the room
|
||||||
|
* **`!pm password`** - Get or set SMTP password of the room's mailbox
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* **!pm nosend** - Get or set `nosend` of the room (`true` - disable email sending; `false` - enable email sending)
|
#### mailbox options
|
||||||
* **!pm noreplies** - Get or set `noreplies` of the room (`true` - ignore matrix replies; `false` - parse matrix replies)
|
|
||||||
* **!pm nosender** - Get or set `nosender` of the room (`true` - hide email sender; `false` - show email sender)
|
> The following section is visible to the mailbox owners only
|
||||||
* **!pm norecipient** - Get or set `norecipient` of the room (`true` - hide recipient; `false` - show recipient)
|
|
||||||
* **!pm nocc** - Get or set `nocc` of the room (`true` - hide CC; `false` - show CC)
|
* **`!pm nosend`** - Get or set `nosend` of the room (`true` - disable email sending; `false` - enable email sending)
|
||||||
* **!pm nosubject** - Get or set `nosubject` of the room (`true` - hide email subject; `false` - show email subject)
|
* **`!pm noreplies`** - Get or set `noreplies` of the room (`true` - ignore matrix replies; `false` - parse matrix replies)
|
||||||
* **!pm nohtml** - Get or set `nohtml` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)
|
* **`!pm nosender`** - Get or set `nosender` of the room (`true` - hide email sender; `false` - show email sender)
|
||||||
* **!pm nothreads** - Get or set `nothreads` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)
|
* **`!pm norecipient`** - Get or set `norecipient` of the room (`true` - hide recipient; `false` - show recipient)
|
||||||
* **!pm nofiles** - Get or set `nofiles` of the room (`true` - ignore email attachments; `false` - upload email attachments)
|
* **`!pm nocc`** - Get or set `nocc` of the room (`true` - hide CC; `false` - show CC)
|
||||||
* **!pm noinlines** - Get or set `noinlines` of the room (`true` - ignore inline attachments; `false` - upload inline attachments)
|
* **`!pm nosubject`** - Get or set `nosubject` of the room (`true` - hide email subject; `false` - show email subject)
|
||||||
|
* **`!pm nohtml`** - Get or set `nohtml` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)
|
||||||
|
* **`!pm nothreads`** - Get or set `nothreads` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)
|
||||||
|
* **`!pm nofiles`** - Get or set `nofiles` of the room (`true` - ignore email attachments; `false` - upload email attachments)
|
||||||
|
* **`!pm noinlines`** - Get or set `noinlines` of the room (`true` - ignore inline attachments; `false` - upload inline attachments)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* **!pm spamcheck:mx** - only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)
|
#### mailbox security checks
|
||||||
* **!pm spamcheck:spf** - only accept email from senders which authorized to send it (those matching SPF records) (`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)
|
> The following section is visible to the mailbox owners only
|
||||||
* **!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 spamlist** - Get or set `spamlist` of the room (comma-separated list), eg: `spammer@example.com,*@spammer.org,noreply@*`
|
* **`!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: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 adminroom** - Get or set admin room
|
#### mailbox anti-spam
|
||||||
* **!pm dkim** - Get DKIM signature
|
|
||||||
* **!pm catch-all** - Configure catch-all mailbox
|
> The following section is visible to the mailbox owners only
|
||||||
* **!pm queue:batch** - max amount of emails to process on each queue check
|
|
||||||
* **!pm queue:retries** - max amount of tries per email in queue before removal
|
* **`!pm spam:list`** - Show comma-separated spamlist of the room, eg: `spammer@example.com,*@spammer.org,spam@*`
|
||||||
* **!pm users** - Get or set allowed users patterns
|
* **`!pm spam:add`** - Mark an email address (or pattern) as spam
|
||||||
* **!pm mailboxes** - Show the list of all mailboxes
|
* **`!pm spam:remove`** - Unmark an email address (or pattern) as spam
|
||||||
* **!pm delete** <mailbox> - Delete specific mailbox
|
* **`!pm spam:reset`** - Reset spamlist
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* **!pm greylist** - Set automatic greylisting duration in minutes (0 - disabled)
|
#### server options
|
||||||
* **!pm banlist** - Enable/disable banlist and show current values
|
|
||||||
* **!pm banlist:add** - Ban an IP
|
> The following section is visible to the bridge admins only
|
||||||
* **!pm banlist:remove** - Unban an IP
|
|
||||||
* **!pm banlist:reset** - Reset banlist
|
* **`!pm adminroom`** - Get or set admin room
|
||||||
|
* **`!pm users`** - Get or set allowed users
|
||||||
|
* **`!pm dkim`** - Get DKIM signature
|
||||||
|
* **`!pm catch-all`** - Get or set catch-all mailbox
|
||||||
|
* **`!pm queue:batch`** - max amount of emails to process on each queue check
|
||||||
|
* **`!pm queue:retries`** - max amount of tries per email in queue before removal
|
||||||
|
* **`!pm mailboxes`** - Show the list of all mailboxes
|
||||||
|
* **`!pm delete`** - Delete specific mailbox
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### server antispam
|
||||||
|
|
||||||
|
> The following section is visible to the bridge admins only
|
||||||
|
|
||||||
|
* **`!pm greylist`** - Set automatic greylisting duration in minutes (0 - disabled)
|
||||||
|
* **`!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>
|
||||||
|
|
||||||
|
|||||||
@@ -16,20 +16,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
commandHelp = "help"
|
commandHelp = "help"
|
||||||
commandStop = "stop"
|
commandStop = "stop"
|
||||||
commandSend = "send"
|
commandSend = "send"
|
||||||
commandDKIM = "dkim"
|
commandDKIM = "dkim"
|
||||||
commandCatchAll = config.BotCatchAll
|
commandCatchAll = config.BotCatchAll
|
||||||
commandUsers = config.BotUsers
|
commandUsers = config.BotUsers
|
||||||
commandQueueBatch = config.BotQueueBatch
|
commandQueueBatch = config.BotQueueBatch
|
||||||
commandQueueRetries = config.BotQueueRetries
|
commandQueueRetries = config.BotQueueRetries
|
||||||
commandDelete = "delete"
|
commandSpamlist = "spam:list"
|
||||||
commandBanlist = "banlist"
|
commandSpamlistAdd = "spam:add"
|
||||||
commandBanlistAdd = "banlist:add"
|
commandSpamlistRemove = "spam:remove"
|
||||||
commandBanlistRemove = "banlist:remove"
|
commandSpamlistReset = "spam:reset"
|
||||||
commandBanlistReset = "banlist:reset"
|
commandDelete = "delete"
|
||||||
commandMailboxes = "mailboxes"
|
commandBanlist = "banlist"
|
||||||
|
commandBanlistAdd = "banlist:add"
|
||||||
|
commandBanlistRemove = "banlist:remove"
|
||||||
|
commandBanlistReset = "banlist:reset"
|
||||||
|
commandMailboxes = "mailboxes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -185,7 +189,7 @@ func (b *Bot) initCommands() commandList {
|
|||||||
sanitizer: utils.SanitizeBoolString,
|
sanitizer: utils.SanitizeBoolString,
|
||||||
allowed: b.allowOwner,
|
allowed: b.allowOwner,
|
||||||
},
|
},
|
||||||
{allowed: b.allowOwner, description: "mailbox antispam"}, // delimiter
|
{allowed: b.allowOwner, description: "mailbox security checks"}, // delimiter
|
||||||
{
|
{
|
||||||
key: config.RoomSpamcheckMX,
|
key: config.RoomSpamcheckMX,
|
||||||
description: "only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)",
|
description: "only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)",
|
||||||
@@ -210,14 +214,27 @@ func (b *Bot) initCommands() commandList {
|
|||||||
sanitizer: utils.SanitizeBoolString,
|
sanitizer: utils.SanitizeBoolString,
|
||||||
allowed: b.allowOwner,
|
allowed: b.allowOwner,
|
||||||
},
|
},
|
||||||
|
{allowed: b.allowOwner, description: "mailbox anti-spam"}, // delimiter
|
||||||
{
|
{
|
||||||
key: config.RoomSpamlist,
|
key: commandSpamlist,
|
||||||
description: fmt.Sprintf(
|
description: "Show comma-separated spamlist of the room, eg: `spammer@example.com,*@spammer.org,spam@*`",
|
||||||
"Get or set `%s` of the room (comma-separated list), eg: `spammer@example.com,*@spammer.org,spam@*`",
|
sanitizer: utils.SanitizeStringSlice,
|
||||||
config.RoomSpamlist,
|
allowed: b.allowOwner,
|
||||||
),
|
},
|
||||||
sanitizer: utils.SanitizeStringSlice,
|
{
|
||||||
allowed: b.allowOwner,
|
key: commandSpamlistAdd,
|
||||||
|
description: "Mark an email address (or pattern) as spam",
|
||||||
|
allowed: b.allowOwner,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: commandSpamlistRemove,
|
||||||
|
description: "Unmark an email address (or pattern) as spam",
|
||||||
|
allowed: b.allowOwner,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: commandSpamlistReset,
|
||||||
|
description: "Reset spamlist",
|
||||||
|
allowed: b.allowOwner,
|
||||||
},
|
},
|
||||||
{allowed: b.allowAdmin, description: "server options"}, // delimiter
|
{allowed: b.allowAdmin, description: "server options"}, // delimiter
|
||||||
{
|
{
|
||||||
@@ -340,6 +357,12 @@ func (b *Bot) handle(ctx context.Context) {
|
|||||||
b.runSend(ctx)
|
b.runSend(ctx)
|
||||||
case commandDKIM:
|
case commandDKIM:
|
||||||
b.runDKIM(ctx, commandSlice)
|
b.runDKIM(ctx, commandSlice)
|
||||||
|
case commandSpamlistAdd:
|
||||||
|
b.runSpamlistAdd(ctx, commandSlice)
|
||||||
|
case commandSpamlistRemove:
|
||||||
|
b.runSpamlistRemove(ctx, commandSlice)
|
||||||
|
case commandSpamlistReset:
|
||||||
|
b.runSpamlistReset(ctx)
|
||||||
case config.BotAdminRoom:
|
case config.BotAdminRoom:
|
||||||
b.runAdminRoom(ctx, commandSlice)
|
b.runAdminRoom(ctx, commandSlice)
|
||||||
case commandUsers:
|
case commandUsers:
|
||||||
@@ -427,7 +450,12 @@ func (b *Bot) sendHelp(ctx context.Context) {
|
|||||||
msg.WriteString(" ")
|
msg.WriteString(" ")
|
||||||
msg.WriteString(cmd.key)
|
msg.WriteString(cmd.key)
|
||||||
msg.WriteString("`**")
|
msg.WriteString("`**")
|
||||||
value := cfg.Get(cmd.key)
|
|
||||||
|
name := cmd.key
|
||||||
|
if name == commandSpamlist {
|
||||||
|
name = config.RoomSpamlist
|
||||||
|
}
|
||||||
|
value := cfg.Get(name)
|
||||||
if cmd.sanitizer != nil {
|
if cmd.sanitizer != nil {
|
||||||
switch value != "" {
|
switch value != "" {
|
||||||
case false:
|
case false:
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package bot
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/raja/argon2pw"
|
"github.com/raja/argon2pw"
|
||||||
|
|
||||||
@@ -52,6 +54,10 @@ func (b *Bot) getOption(ctx context.Context, name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == commandSpamlist {
|
||||||
|
name = config.RoomSpamlist
|
||||||
|
}
|
||||||
|
|
||||||
value := cfg.Get(name)
|
value := cfg.Get(name)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
msg := fmt.Sprintf("`%s` is not set, kupo.\n"+
|
msg := fmt.Sprintf("`%s` is not set, kupo.\n"+
|
||||||
@@ -138,3 +144,100 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
|
|||||||
}
|
}
|
||||||
b.SendNotice(ctx, evt.RoomID, msg)
|
b.SendNotice(ctx, evt.RoomID, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bot) runSpamlistAdd(ctx context.Context, commandSlice []string) {
|
||||||
|
evt := eventFromContext(ctx)
|
||||||
|
if len(commandSlice) < 2 {
|
||||||
|
b.getOption(ctx, config.RoomSpamlist)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roomCfg, err := b.cfg.GetRoom(evt.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot get room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spamlist := utils.StringSlice(roomCfg[config.RoomSpamlist])
|
||||||
|
for _, newItem := range commandSlice[1:] {
|
||||||
|
newItem = strings.TrimSpace(newItem)
|
||||||
|
if slices.Contains(spamlist, newItem) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
spamlist = append(spamlist, newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
roomCfg.Set(config.RoomSpamlist, utils.SliceString(spamlist))
|
||||||
|
err = b.cfg.SetRoom(evt.RoomID, roomCfg)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot store room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SendNotice(ctx, evt.RoomID, "spamlist has been updated, kupo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) runSpamlistRemove(ctx context.Context, commandSlice []string) {
|
||||||
|
evt := eventFromContext(ctx)
|
||||||
|
if len(commandSlice) < 2 {
|
||||||
|
b.getOption(ctx, config.RoomSpamlist)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roomCfg, err := b.cfg.GetRoom(evt.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot get room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toRemove := map[int]struct{}{}
|
||||||
|
spamlist := utils.StringSlice(roomCfg[config.RoomSpamlist])
|
||||||
|
for _, item := range commandSlice[1:] {
|
||||||
|
item = strings.TrimSpace(item)
|
||||||
|
idx := slices.Index(spamlist, item)
|
||||||
|
if idx < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toRemove[idx] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(toRemove) == 0 {
|
||||||
|
b.SendNotice(ctx, evt.RoomID, "nothing new, kupo.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedSpamlist := []string{}
|
||||||
|
for i, item := range spamlist {
|
||||||
|
if _, ok := toRemove[i]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
updatedSpamlist = append(updatedSpamlist, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
roomCfg.Set(config.RoomSpamlist, utils.SliceString(updatedSpamlist))
|
||||||
|
err = b.cfg.SetRoom(evt.RoomID, roomCfg)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot store room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SendNotice(ctx, evt.RoomID, "spamlist has been updated, kupo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) runSpamlistReset(ctx context.Context) {
|
||||||
|
evt := eventFromContext(ctx)
|
||||||
|
roomCfg, err := b.cfg.GetRoom(evt.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot get room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spamlist := utils.StringSlice(roomCfg[config.RoomSpamlist])
|
||||||
|
if len(spamlist) == 0 {
|
||||||
|
b.SendNotice(ctx, evt.RoomID, "spamlist is empty, kupo.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roomCfg.Set(config.RoomSpamlist, "")
|
||||||
|
err = b.cfg.SetRoom(evt.RoomID, roomCfg)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot store room settings: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SendNotice(ctx, evt.RoomID, "spamlist has been reset, kupo.")
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ func (s Room) MigrateSpamlistSettings() {
|
|||||||
for item := range uniq {
|
for item := range uniq {
|
||||||
spamlist = append(spamlist, item)
|
spamlist = append(spamlist, item)
|
||||||
}
|
}
|
||||||
s.Set(RoomSpamlist, strings.Join(spamlist, ","))
|
s.Set(RoomSpamlist, utils.SliceString(spamlist))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentOptions converts room display settings to content options
|
// ContentOptions converts room display settings to content options
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -96,6 +97,20 @@ func SanitizeIntString(str string) string {
|
|||||||
return strconv.Itoa(Int(str))
|
return strconv.Itoa(Int(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SliceString converts slice into comma-separated string
|
||||||
|
func SliceString(strs []string) string {
|
||||||
|
res := []string{}
|
||||||
|
for _, str := range strs {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
if str == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res = append(res, str)
|
||||||
|
}
|
||||||
|
sort.Strings(res)
|
||||||
|
return strings.Join(res, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// StringSlice converts comma-separated string to slice
|
// StringSlice converts comma-separated string to slice
|
||||||
func StringSlice(str string) []string {
|
func StringSlice(str string) []string {
|
||||||
if str == "" {
|
if str == "" {
|
||||||
@@ -107,19 +122,15 @@ func StringSlice(str string) []string {
|
|||||||
return []string{str}
|
return []string{str}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Split(str, ",")
|
parts := strings.Split(str, ",")
|
||||||
}
|
|
||||||
|
|
||||||
// SanitizeBoolString converts string to slice and back to string
|
|
||||||
func SanitizeStringSlice(str string) string {
|
|
||||||
parts := StringSlice(str)
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
parts[i] = strings.TrimSpace(part)
|
parts[i] = strings.TrimSpace(part)
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(parts, ",")
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeBoolString converts string to slice and back to string
|
||||||
|
func SanitizeStringSlice(str string) string {
|
||||||
|
return SliceString(StringSlice(str))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user