Browse Source

fix #1997 (#2088)

* 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
tags/v2.12.0-rc1
Shivaram Lingamneni 8 months ago
parent
commit
2013beb7c8
No account linked to committer's email address
3 changed files with 114 additions and 20 deletions
  1. 7
    2
      default.yaml
  2. 100
    16
      irc/email/email.go
  3. 7
    2
      traditional.yaml

+ 7
- 2
default.yaml View File

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

+ 100
- 16
irc/email/email.go View File

@@ -4,10 +4,13 @@
4 4
 package email
5 5
 
6 6
 import (
7
+	"bufio"
7 8
 	"bytes"
8 9
 	"errors"
9 10
 	"fmt"
11
+	"io"
10 12
 	"net"
13
+	"os"
11 14
 	"regexp"
12 15
 	"strings"
13 16
 	"time"
@@ -23,6 +26,38 @@ var (
23 26
 	ErrNoMXRecord         = errors.New("Couldn't resolve MX record")
24 27
 )
25 28
 
29
+type BlacklistSyntax uint
30
+
31
+const (
32
+	BlacklistSyntaxGlob BlacklistSyntax = iota
33
+	BlacklistSyntaxRegexp
34
+)
35
+
36
+func blacklistSyntaxFromString(status string) (BlacklistSyntax, error) {
37
+	switch strings.ToLower(status) {
38
+	case "glob", "":
39
+		return BlacklistSyntaxGlob, nil
40
+	case "re", "regex", "regexp":
41
+		return BlacklistSyntaxRegexp, nil
42
+	default:
43
+		return BlacklistSyntaxRegexp, fmt.Errorf("Unknown blacklist syntax type `%s`", status)
44
+	}
45
+}
46
+
47
+func (bs *BlacklistSyntax) UnmarshalYAML(unmarshal func(interface{}) error) error {
48
+	var orig string
49
+	var err error
50
+	if err = unmarshal(&orig); err != nil {
51
+		return err
52
+	}
53
+	if result, err := blacklistSyntaxFromString(orig); err == nil {
54
+		*bs = result
55
+		return nil
56
+	} else {
57
+		return err
58
+	}
59
+}
60
+
26 61
 type MTAConfig struct {
27 62
 	Server      string
28 63
 	Port        int
@@ -35,24 +70,64 @@ type MailtoConfig struct {
35 70
 	// legacy config format assumed the use of an MTA/smarthost,
36 71
 	// so server, port, etc. appear directly at top level
37 72
 	// XXX: see https://github.com/go-yaml/yaml/issues/63
38
-	MTAConfig            `yaml:",inline"`
39
-	Enabled              bool
40
-	Sender               string
41
-	HeloDomain           string `yaml:"helo-domain"`
42
-	RequireTLS           bool   `yaml:"require-tls"`
43
-	VerifyMessageSubject string `yaml:"verify-message-subject"`
44
-	DKIM                 DKIMConfig
45
-	MTAReal              MTAConfig `yaml:"mta"`
46
-	BlacklistRegexes     []string  `yaml:"blacklist-regexes"`
47
-	blacklistRegexes     []*regexp.Regexp
48
-	Timeout              time.Duration
49
-	PasswordReset        struct {
73
+	MTAConfig              `yaml:",inline"`
74
+	Enabled                bool
75
+	Sender                 string
76
+	HeloDomain             string `yaml:"helo-domain"`
77
+	RequireTLS             bool   `yaml:"require-tls"`
78
+	VerifyMessageSubject   string `yaml:"verify-message-subject"`
79
+	DKIM                   DKIMConfig
80
+	MTAReal                MTAConfig       `yaml:"mta"`
81
+	AddressBlacklist       []string        `yaml:"address-blacklist"`
82
+	AddressBlacklistSyntax BlacklistSyntax `yaml:"address-blacklist-syntax"`
83
+	AddressBlacklistFile   string          `yaml:"address-blacklist-file"`
84
+	blacklistRegexes       []*regexp.Regexp
85
+	Timeout                time.Duration
86
+	PasswordReset          struct {
50 87
 		Enabled  bool
51 88
 		Cooldown custime.Duration
52 89
 		Timeout  custime.Duration
53 90
 	} `yaml:"password-reset"`
54 91
 }
55 92
 
93
+func (config *MailtoConfig) compileBlacklistEntry(source string) (re *regexp.Regexp, err error) {
94
+	if config.AddressBlacklistSyntax == BlacklistSyntaxGlob {
95
+		return utils.CompileGlob(source, false)
96
+	} else {
97
+		return regexp.Compile(fmt.Sprintf("^%s$", source))
98
+	}
99
+}
100
+
101
+func (config *MailtoConfig) processBlacklistFile(filename string) (result []*regexp.Regexp, err error) {
102
+	f, err := os.Open(filename)
103
+	if err != nil {
104
+		return
105
+	}
106
+	defer f.Close()
107
+	reader := bufio.NewReader(f)
108
+	lineNo := 0
109
+	for {
110
+		line, err := reader.ReadString('\n')
111
+		lineNo++
112
+		line = strings.TrimSpace(line)
113
+		if line != "" && line[0] != '#' {
114
+			if compiled, compileErr := config.compileBlacklistEntry(line); compileErr == nil {
115
+				result = append(result, compiled)
116
+			} else {
117
+				return result, fmt.Errorf("Failed to compile line %d of blacklist-regex-file `%s`: %w", lineNo, line, compileErr)
118
+			}
119
+		}
120
+		switch err {
121
+		case io.EOF:
122
+			return result, nil
123
+		case nil:
124
+			continue
125
+		default:
126
+			return result, err
127
+		}
128
+	}
129
+}
130
+
56 131
 func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
57 132
 	if config.Sender == "" {
58 133
 		return errors.New("Invalid mailto sender address")
@@ -68,12 +143,20 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
68 143
 		config.HeloDomain = heloDomain
69 144
 	}
70 145
 
71
-	for _, reg := range config.BlacklistRegexes {
72
-		compiled, err := regexp.Compile(fmt.Sprintf("^%s$", reg))
146
+	if config.AddressBlacklistFile != "" {
147
+		config.blacklistRegexes, err = config.processBlacklistFile(config.AddressBlacklistFile)
73 148
 		if err != nil {
74 149
 			return err
75 150
 		}
76
-		config.blacklistRegexes = append(config.blacklistRegexes, compiled)
151
+	} else if len(config.AddressBlacklist) != 0 {
152
+		config.blacklistRegexes = make([]*regexp.Regexp, 0, len(config.AddressBlacklist))
153
+		for _, reg := range config.AddressBlacklist {
154
+			compiled, err := config.compileBlacklistEntry(reg)
155
+			if err != nil {
156
+				return err
157
+			}
158
+			config.blacklistRegexes = append(config.blacklistRegexes, compiled)
159
+		}
77 160
 	}
78 161
 
79 162
 	if config.MTAConfig.Server != "" {
@@ -118,8 +201,9 @@ func ComposeMail(config MailtoConfig, recipient, subject string) (message bytes.
118 201
 }
119 202
 
120 203
 func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
204
+	recipientLower := strings.ToLower(recipient)
121 205
 	for _, reg := range config.blacklistRegexes {
122
-		if reg.MatchString(recipient) {
206
+		if reg.MatchString(recipientLower) {
123 207
 			return ErrBlacklistedAddress
124 208
 		}
125 209
 	}

+ 7
- 2
traditional.yaml View File

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

Loading…
Cancel
Save