add trusted proxies

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

View File

@@ -50,6 +50,7 @@ env vars
<summary>other optional config parameters</summary>
* **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_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

View File

@@ -109,8 +109,25 @@ func (b *Bot) IsBanned(addr net.Addr) bool {
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
func (b *Bot) Ban(addr net.Addr) {
if b.IsTrusted(addr) {
return
}
b.log.Debug("attempting to ban %s", addr.String())
banlist := b.cfg.GetBanlist()
banlist.Add(addr)

View File

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

View File

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

View File

@@ -123,7 +123,7 @@ func initMatrix(cfg *config.Config) {
mxc = mxconfig.New(lp, cfglog)
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 {
// nolint // Fatal = panic, not os.Exit()
log.Fatal("cannot start matrix bot: %v", err)

View File

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

View File

@@ -14,6 +14,8 @@ type Config struct {
Domains []string
// Port for SMTP
Port string
// Proxies is list of trusted SMTP proxies
Proxies []string
// RoomID of the admin room
LogLevel string
// 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)
IsGreylisted(net.Addr) bool
IsBanned(net.Addr) bool
IsTrusted(net.Addr) bool
Ban(net.Addr)
GetMapping(string) (id.RoomID, bool)
GetIFOptions(id.RoomID) email.IncomingFilteringOptions

View File

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

View File

@@ -6,6 +6,7 @@ import (
"errors"
"io"
"net"
"strconv"
"github.com/emersion/go-msgauth/dkim"
"github.com/emersion/go-smtp"
@@ -26,9 +27,10 @@ type incomingSession struct {
getFilters func(id.RoomID) email.IncomingFilteringOptions
receiveEmail func(context.Context, *email.Email) error
greylisted func(net.Addr) bool
trusted func(net.Addr) bool
ban func(net.Addr)
domains []string
enforceDKIM bool
roomID id.RoomID
ctx context.Context
addr net.Addr
@@ -64,38 +66,69 @@ func (s *incomingSession) Rcpt(to string) error {
return ErrNoUser
}
roomID, ok := s.getRoomID(utils.Mailbox(to))
var ok bool
s.roomID, ok = s.getRoomID(utils.Mailbox(to))
if !ok {
s.log.Debug("mapping for %s not found", to)
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)
return nil
}
func (s *incomingSession) Data(r io.Reader) error {
if s.greylisted(s.addr) {
return &smtp.SMTPError{
Code: 451,
EnhancedCode: smtp.EnhancedCode{4, 5, 1},
Message: "You have been greylisted, try again a bit later.",
}
// getAddr gets real address of incoming email serder,
// including special case of trusted proxy
func (s *incomingSession) getAddr(envelope *enmime.Envelope) net.Addr {
if !s.trusted(s.addr) {
return s.addr
}
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)
if err != nil {
s.log.Error("cannot read DATA: %v", err)
return err
}
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)
if verr != nil {
s.log.Error("cannot verify DKIM: %v", verr)
@@ -107,12 +140,6 @@ func (s *incomingSession) Data(r io.Reader) error {
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)
@@ -125,6 +152,7 @@ func (s *incomingSession) Data(r io.Reader) error {
}
return nil
}
func (s *incomingSession) Reset() {}
func (s *incomingSession) Logout() error { return nil }

View File

@@ -1,6 +1,7 @@
package utils
import (
"net"
"strconv"
"strings"
@@ -22,6 +23,16 @@ func SetDomains(slice []string) {
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
func SanitizeDomain(domain string) string {
domain = strings.TrimSpace(domain)