add trusted proxies
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user