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