3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-24 21:09:30 +01:00
* Fix #1997 (allow the use of an external file for the email blacklist)
* Change config key names for blacklist (compatibility break)
* Accept globs rather than regexes for blacklist by default
* Blacklist comparison is now case-insensitive
This commit is contained in:
Shivaram Lingamneni 2023-09-11 22:06:55 -07:00 committed by GitHub
parent 6b386ce2ac
commit 2013beb7c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 20 deletions

View File

@ -418,8 +418,13 @@ accounts:
# username: "admin" # username: "admin"
# password: "hunter2" # password: "hunter2"
# implicit-tls: false # TLS from the first byte, typically on port 465 # implicit-tls: false # TLS from the first byte, typically on port 465
blacklist-regexes: # addresses that are not accepted for registration:
# - ".*@mailinator.com" address-blacklist:
# - "*@mailinator.com"
address-blacklist-syntax: "glob" # change to "regex" for regular expressions
# file of newline-delimited address blacklist entries in the above syntax;
# supersedes address-blacklist if set:
# address-blacklist-file: "/path/to/address-blacklist-file"
timeout: 60s timeout: 60s
# email-based password reset: # email-based password reset:
password-reset: password-reset:

View File

@ -4,10 +4,13 @@
package email package email
import ( import (
"bufio"
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"os"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -23,6 +26,38 @@ var (
ErrNoMXRecord = errors.New("Couldn't resolve MX record") ErrNoMXRecord = errors.New("Couldn't resolve MX record")
) )
type BlacklistSyntax uint
const (
BlacklistSyntaxGlob BlacklistSyntax = iota
BlacklistSyntaxRegexp
)
func blacklistSyntaxFromString(status string) (BlacklistSyntax, error) {
switch strings.ToLower(status) {
case "glob", "":
return BlacklistSyntaxGlob, nil
case "re", "regex", "regexp":
return BlacklistSyntaxRegexp, nil
default:
return BlacklistSyntaxRegexp, fmt.Errorf("Unknown blacklist syntax type `%s`", status)
}
}
func (bs *BlacklistSyntax) UnmarshalYAML(unmarshal func(interface{}) error) error {
var orig string
var err error
if err = unmarshal(&orig); err != nil {
return err
}
if result, err := blacklistSyntaxFromString(orig); err == nil {
*bs = result
return nil
} else {
return err
}
}
type MTAConfig struct { type MTAConfig struct {
Server string Server string
Port int Port int
@ -35,24 +70,64 @@ type MailtoConfig struct {
// legacy config format assumed the use of an MTA/smarthost, // legacy config format assumed the use of an MTA/smarthost,
// so server, port, etc. appear directly at top level // so server, port, etc. appear directly at top level
// XXX: see https://github.com/go-yaml/yaml/issues/63 // XXX: see https://github.com/go-yaml/yaml/issues/63
MTAConfig `yaml:",inline"` MTAConfig `yaml:",inline"`
Enabled bool Enabled bool
Sender string Sender string
HeloDomain string `yaml:"helo-domain"` HeloDomain string `yaml:"helo-domain"`
RequireTLS bool `yaml:"require-tls"` RequireTLS bool `yaml:"require-tls"`
VerifyMessageSubject string `yaml:"verify-message-subject"` VerifyMessageSubject string `yaml:"verify-message-subject"`
DKIM DKIMConfig DKIM DKIMConfig
MTAReal MTAConfig `yaml:"mta"` MTAReal MTAConfig `yaml:"mta"`
BlacklistRegexes []string `yaml:"blacklist-regexes"` AddressBlacklist []string `yaml:"address-blacklist"`
blacklistRegexes []*regexp.Regexp AddressBlacklistSyntax BlacklistSyntax `yaml:"address-blacklist-syntax"`
Timeout time.Duration AddressBlacklistFile string `yaml:"address-blacklist-file"`
PasswordReset struct { blacklistRegexes []*regexp.Regexp
Timeout time.Duration
PasswordReset struct {
Enabled bool Enabled bool
Cooldown custime.Duration Cooldown custime.Duration
Timeout custime.Duration Timeout custime.Duration
} `yaml:"password-reset"` } `yaml:"password-reset"`
} }
func (config *MailtoConfig) compileBlacklistEntry(source string) (re *regexp.Regexp, err error) {
if config.AddressBlacklistSyntax == BlacklistSyntaxGlob {
return utils.CompileGlob(source, false)
} else {
return regexp.Compile(fmt.Sprintf("^%s$", source))
}
}
func (config *MailtoConfig) processBlacklistFile(filename string) (result []*regexp.Regexp, err error) {
f, err := os.Open(filename)
if err != nil {
return
}
defer f.Close()
reader := bufio.NewReader(f)
lineNo := 0
for {
line, err := reader.ReadString('\n')
lineNo++
line = strings.TrimSpace(line)
if line != "" && line[0] != '#' {
if compiled, compileErr := config.compileBlacklistEntry(line); compileErr == nil {
result = append(result, compiled)
} else {
return result, fmt.Errorf("Failed to compile line %d of blacklist-regex-file `%s`: %w", lineNo, line, compileErr)
}
}
switch err {
case io.EOF:
return result, nil
case nil:
continue
default:
return result, err
}
}
}
func (config *MailtoConfig) Postprocess(heloDomain string) (err error) { func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
if config.Sender == "" { if config.Sender == "" {
return errors.New("Invalid mailto sender address") return errors.New("Invalid mailto sender address")
@ -68,12 +143,20 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
config.HeloDomain = heloDomain config.HeloDomain = heloDomain
} }
for _, reg := range config.BlacklistRegexes { if config.AddressBlacklistFile != "" {
compiled, err := regexp.Compile(fmt.Sprintf("^%s$", reg)) config.blacklistRegexes, err = config.processBlacklistFile(config.AddressBlacklistFile)
if err != nil { if err != nil {
return err return err
} }
config.blacklistRegexes = append(config.blacklistRegexes, compiled) } else if len(config.AddressBlacklist) != 0 {
config.blacklistRegexes = make([]*regexp.Regexp, 0, len(config.AddressBlacklist))
for _, reg := range config.AddressBlacklist {
compiled, err := config.compileBlacklistEntry(reg)
if err != nil {
return err
}
config.blacklistRegexes = append(config.blacklistRegexes, compiled)
}
} }
if config.MTAConfig.Server != "" { if config.MTAConfig.Server != "" {
@ -118,8 +201,9 @@ func ComposeMail(config MailtoConfig, recipient, subject string) (message bytes.
} }
func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) { func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
recipientLower := strings.ToLower(recipient)
for _, reg := range config.blacklistRegexes { for _, reg := range config.blacklistRegexes {
if reg.MatchString(recipient) { if reg.MatchString(recipientLower) {
return ErrBlacklistedAddress return ErrBlacklistedAddress
} }
} }

View File

@ -391,8 +391,13 @@ accounts:
# username: "admin" # username: "admin"
# password: "hunter2" # password: "hunter2"
# implicit-tls: false # TLS from the first byte, typically on port 465 # implicit-tls: false # TLS from the first byte, typically on port 465
blacklist-regexes: # addresses that are not accepted for registration:
# - ".*@mailinator.com" address-blacklist:
# - "*@mailinator.com"
address-blacklist-syntax: "glob" # change to "regex" for regular expressions
# file of newline-delimited address blacklist entries in the above syntax;
# supersedes address-blacklist if set:
# address-blacklist-file: "/path/to/address-blacklist-file"
timeout: 60s timeout: 60s
# email-based password reset: # email-based password reset:
password-reset: password-reset: