add trusted proxies

This commit is contained in:
Aine
2022-11-27 00:30:50 +02:00
parent 8d6c4aeafe
commit fcd6110790
11 changed files with 95 additions and 37 deletions

View File

@@ -50,6 +50,7 @@ env vars
<summary>other optional config parameters</summary> <summary>other optional config parameters</summary>
* **POSTMOOGLE_PORT** - SMTP port to listen for new emails * **POSTMOOGLE_PORT** - SMTP port to listen for new emails
* **POSTMOOGLE_PROXIES** - space separated list of IP addresses considered as trusted proxies, thus never banned
* **POSTMOOGLE_TLS_PORT** - secure SMTP port to listen for new emails. Requires valid cert and key as well * **POSTMOOGLE_TLS_PORT** - secure SMTP port to listen for new emails. Requires valid cert and key as well
* **POSTMOOGLE_TLS_CERT** - space separated list of paths to the SSL certificates (chain) of your domains, note that position in the cert list must match the position of the cert's key in the key list * **POSTMOOGLE_TLS_CERT** - space separated list of paths to the SSL certificates (chain) of your domains, note that position in the cert list must match the position of the cert's key in the key list
* **POSTMOOGLE_TLS_KEY** - space separated list of paths to the SSL certificates' private keys of your domains, note that position on the key list must match the position of cert in the cert list * **POSTMOOGLE_TLS_KEY** - space separated list of paths to the SSL certificates' private keys of your domains, note that position on the key list must match the position of cert in the cert list

View File

@@ -109,8 +109,25 @@ func (b *Bot) IsBanned(addr net.Addr) bool {
return b.cfg.GetBanlist().Has(addr) return b.cfg.GetBanlist().Has(addr)
} }
// IsTrusted checks if address is a trusted (proxy)
func (b *Bot) IsTrusted(addr net.Addr) bool {
ip := utils.AddrIP(addr)
for _, proxy := range b.proxies {
if ip == proxy {
b.log.Debug("address %s is trusted", ip)
return true
}
}
b.log.Debug("address %s is NOT trusted", ip)
return false
}
// Ban an address // Ban an address
func (b *Bot) Ban(addr net.Addr) { func (b *Bot) Ban(addr net.Addr) {
if b.IsTrusted(addr) {
return
}
b.log.Debug("attempting to ban %s", addr.String()) b.log.Debug("attempting to ban %s", addr.String())
banlist := b.cfg.GetBanlist() banlist := b.cfg.GetBanlist()
banlist.Add(addr) banlist.Add(addr)

View File

@@ -34,6 +34,7 @@ type Bot struct {
adminRooms []id.RoomID adminRooms []id.RoomID
commands commandList commands commandList
rooms sync.Map rooms sync.Map
proxies []string
sendmail func(string, string, string) error sendmail func(string, string, string) error
cfg *config.Manager cfg *config.Manager
log *logger.Logger log *logger.Logger
@@ -49,6 +50,7 @@ func New(
lp *linkpearl.Linkpearl, lp *linkpearl.Linkpearl,
log *logger.Logger, log *logger.Logger,
cfg *config.Manager, cfg *config.Manager,
proxies []string,
prefix string, prefix string,
domains []string, domains []string,
admins []string, admins []string,
@@ -59,6 +61,7 @@ func New(
prefix: prefix, prefix: prefix,
rooms: sync.Map{}, rooms: sync.Map{},
adminRooms: []id.RoomID{}, adminRooms: []id.RoomID{},
proxies: proxies,
mbxc: mbxc, mbxc: mbxc,
cfg: cfg, cfg: cfg,
log: log, log: log,

View File

@@ -4,6 +4,8 @@ import (
"net" "net"
"sort" "sort"
"time" "time"
"gitlab.com/etke.cc/postmoogle/utils"
) )
// account data keys // account data keys
@@ -26,24 +28,15 @@ func (l List) Slice() []string {
return slice return slice
} }
func (l List) 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 ban- or greylist // Has addr in ban- or greylist
func (l List) Has(addr net.Addr) bool { func (l List) Has(addr net.Addr) bool {
_, ok := l[l.getKey(addr)] _, ok := l[utils.AddrIP(addr)]
return ok return ok
} }
// Get when addr was added in ban- or greylist // Get when addr was added in ban- or greylist
func (l List) Get(addr net.Addr) (time.Time, bool) { func (l List) Get(addr net.Addr) (time.Time, bool) {
from := l[l.getKey(addr)] from := l[utils.AddrIP(addr)]
if from == "" { if from == "" {
return time.Time{}, false return time.Time{}, false
} }
@@ -57,7 +50,7 @@ func (l List) Get(addr net.Addr) (time.Time, bool) {
// Add an addr to ban- or greylist // Add an addr to ban- or greylist
func (l List) Add(addr net.Addr) { func (l List) Add(addr net.Addr) {
key := l.getKey(addr) key := utils.AddrIP(addr)
if _, ok := l[key]; ok { if _, ok := l[key]; ok {
return return
} }
@@ -67,7 +60,7 @@ func (l List) Add(addr net.Addr) {
// Remove an addr from ban- or greylist // Remove an addr from ban- or greylist
func (l List) Remove(addr net.Addr) { func (l List) Remove(addr net.Addr) {
key := l.getKey(addr) key := utils.AddrIP(addr)
if _, ok := l[key]; !ok { if _, ok := l[key]; !ok {
return return
} }

View File

@@ -123,7 +123,7 @@ func initMatrix(cfg *config.Config) {
mxc = mxconfig.New(lp, cfglog) mxc = mxconfig.New(lp, cfglog)
q = queue.New(lp, mxc, qlog) q = queue.New(lp, mxc, qlog)
mxb, err = bot.New(q, lp, mxlog, mxc, cfg.Prefix, cfg.Domains, cfg.Admins, bot.MBXConfig(cfg.Mailboxes)) mxb, err = bot.New(q, lp, mxlog, mxc, cfg.Proxies, cfg.Prefix, cfg.Domains, cfg.Admins, bot.MBXConfig(cfg.Mailboxes))
if err != nil { if err != nil {
// nolint // Fatal = panic, not os.Exit() // nolint // Fatal = panic, not os.Exit()
log.Fatal("cannot start matrix bot: %v", err) log.Fatal("cannot start matrix bot: %v", err)

View File

@@ -19,6 +19,7 @@ func New() *Config {
Prefix: env.String("prefix", defaultConfig.Prefix), Prefix: env.String("prefix", defaultConfig.Prefix),
Domains: migrateDomains("domain", "domains"), Domains: migrateDomains("domain", "domains"),
Port: env.String("port", defaultConfig.Port), Port: env.String("port", defaultConfig.Port),
Proxies: env.Slice("proxies"),
NoEncryption: env.Bool("noencryption"), NoEncryption: env.Bool("noencryption"),
DataSecret: env.String("data.secret", defaultConfig.DataSecret), DataSecret: env.String("data.secret", defaultConfig.DataSecret),
MaxSize: env.Int("maxsize", defaultConfig.MaxSize), MaxSize: env.Int("maxsize", defaultConfig.MaxSize),

View File

@@ -14,6 +14,8 @@ type Config struct {
Domains []string Domains []string
// Port for SMTP // Port for SMTP
Port string Port string
// Proxies is list of trusted SMTP proxies
Proxies []string
// RoomID of the admin room // RoomID of the admin room
LogLevel string LogLevel string
// DataSecret is account data secret key (password) to encrypt all account data values // DataSecret is account data secret key (password) to encrypt all account data values

View File

@@ -43,6 +43,7 @@ type matrixbot interface {
AllowAuth(string, string) (id.RoomID, bool) AllowAuth(string, string) (id.RoomID, bool)
IsGreylisted(net.Addr) bool IsGreylisted(net.Addr) bool
IsBanned(net.Addr) bool IsBanned(net.Addr) bool
IsTrusted(net.Addr) bool
Ban(net.Addr) Ban(net.Addr)
GetMapping(string) (id.RoomID, bool) GetMapping(string) (id.RoomID, bool)
GetIFOptions(id.RoomID) email.IncomingFilteringOptions GetIFOptions(id.RoomID) email.IncomingFilteringOptions

View File

@@ -81,6 +81,7 @@ func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session,
receiveEmail: m.ReceiveEmail, receiveEmail: m.ReceiveEmail,
ban: m.bot.Ban, ban: m.bot.Ban,
greylisted: m.bot.IsGreylisted, greylisted: m.bot.IsGreylisted,
trusted: m.bot.IsTrusted,
log: m.log, log: m.log,
domains: m.domains, domains: m.domains,
addr: state.RemoteAddr, addr: state.RemoteAddr,

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"strconv"
"github.com/emersion/go-msgauth/dkim" "github.com/emersion/go-msgauth/dkim"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
@@ -26,9 +27,10 @@ type incomingSession struct {
getFilters func(id.RoomID) email.IncomingFilteringOptions getFilters func(id.RoomID) email.IncomingFilteringOptions
receiveEmail func(context.Context, *email.Email) error receiveEmail func(context.Context, *email.Email) error
greylisted func(net.Addr) bool greylisted func(net.Addr) bool
trusted func(net.Addr) bool
ban func(net.Addr) ban func(net.Addr)
domains []string domains []string
enforceDKIM bool roomID id.RoomID
ctx context.Context ctx context.Context
addr net.Addr addr net.Addr
@@ -64,38 +66,69 @@ func (s *incomingSession) Rcpt(to string) error {
return ErrNoUser return ErrNoUser
} }
roomID, ok := s.getRoomID(utils.Mailbox(to)) var ok bool
s.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)
return ErrNoUser return ErrNoUser
} }
validations := s.getFilters(roomID)
s.enforceDKIM = validations.SpamcheckDKIM()
if !validateIncoming(s.from, to, s.addr, s.log, validations) {
s.ban(s.addr)
return ErrBanned
}
s.log.Debug("mail to %s", to) s.log.Debug("mail to %s", to)
return nil return nil
} }
func (s *incomingSession) Data(r io.Reader) error { // getAddr gets real address of incoming email serder,
if s.greylisted(s.addr) { // including special case of trusted proxy
return &smtp.SMTPError{ func (s *incomingSession) getAddr(envelope *enmime.Envelope) net.Addr {
Code: 451, if !s.trusted(s.addr) {
EnhancedCode: smtp.EnhancedCode{4, 5, 1}, return s.addr
Message: "You have been greylisted, try again a bit later.",
}
} }
addrHeader := envelope.GetHeader("X-Real-Addr")
if addrHeader == "" {
return s.addr
}
host, portString, _ := net.SplitHostPort(addrHeader) //nolint:errcheck
if host == "" {
return s.addr
}
var port int
port, _ = strconv.Atoi(portString) //nolint:errcheck
realAddr := &net.TCPAddr{IP: net.ParseIP(host), Port: port}
s.log.Info("real address: %s", realAddr.String())
return realAddr
}
func (s *incomingSession) Data(r io.Reader) error {
data, err := io.ReadAll(r) data, err := io.ReadAll(r)
if err != nil { if err != nil {
s.log.Error("cannot read DATA: %v", err) s.log.Error("cannot read DATA: %v", err)
return err return err
} }
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
if s.enforceDKIM { parser := enmime.NewParser()
envelope, err := parser.ReadEnvelope(reader)
if err != nil {
return err
}
addr := s.getAddr(envelope)
reader.Seek(0, io.SeekStart) //nolint:errcheck
validations := s.getFilters(s.roomID)
if !validateIncoming(s.from, s.tos[0], addr, s.log, validations) {
s.ban(addr)
return ErrBanned
}
if s.greylisted(addr) {
return &smtp.SMTPError{
Code: 451,
EnhancedCode: smtp.EnhancedCode{4, 5, 1},
Message: "You have been greylisted, try again a bit later.",
}
}
if validations.SpamcheckDKIM() {
results, verr := dkim.Verify(reader) results, verr := dkim.Verify(reader)
if verr != nil { if verr != nil {
s.log.Error("cannot verify DKIM: %v", verr) s.log.Error("cannot verify DKIM: %v", verr)
@@ -107,12 +140,6 @@ func (s *incomingSession) Data(r io.Reader) error {
return result.Err return result.Err
} }
} }
reader.Seek(0, io.SeekStart) //nolint:errcheck
}
parser := enmime.NewParser()
envelope, err := parser.ReadEnvelope(reader)
if err != nil {
return err
} }
eml := email.FromEnvelope(s.tos[0], envelope) eml := email.FromEnvelope(s.tos[0], envelope)
@@ -125,6 +152,7 @@ func (s *incomingSession) Data(r io.Reader) error {
} }
return nil return nil
} }
func (s *incomingSession) Reset() {} func (s *incomingSession) Reset() {}
func (s *incomingSession) Logout() error { return nil } func (s *incomingSession) Logout() error { return nil }

View File

@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"net"
"strconv" "strconv"
"strings" "strings"
@@ -22,6 +23,16 @@ func SetDomains(slice []string) {
domains = slice domains = slice
} }
// AddrIP returns IP from a network address
func AddrIP(addr net.Addr) string {
key := addr.String()
host, _, _ := net.SplitHostPort(key) //nolint:errcheck // either way it's ok
if host != "" {
key = host
}
return key
}
// SanitizeDomain checks that input domain is available for use // SanitizeDomain checks that input domain is available for use
func SanitizeDomain(domain string) string { func SanitizeDomain(domain string) string {
domain = strings.TrimSpace(domain) domain = strings.TrimSpace(domain)