Browse Source

Merge pull request #1741 from slingamn/greylisting.3

user visible email errors, email timeouts
tags/v2.8.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
907f82a27e
No account linked to committer's email address
8 changed files with 72 additions and 17 deletions
  1. 1
    0
      default.yaml
  2. 25
    1
      irc/accounts.go
  3. 10
    3
      irc/email/email.go
  4. 0
    1
      irc/errors.go
  5. 10
    6
      irc/handlers.go
  6. 1
    1
      irc/nickserv.go
  7. 24
    5
      irc/smtp/smtp.go
  8. 1
    0
      traditional.yaml

+ 1
- 0
default.yaml View File

@@ -413,6 +413,7 @@ accounts:
413 413
             #     password: "hunter2"
414 414
             blacklist-regexes:
415 415
             #    - ".*@mailinator.com"
416
+            timeout: 60s
416 417
 
417 418
     # throttle account login attempts (to prevent either password guessing, or DoS
418 419
     # attacks on the server aimed at forcing repeated expensive bcrypt computations)

+ 25
- 1
irc/accounts.go View File

@@ -15,6 +15,8 @@ import (
15 15
 	"time"
16 16
 	"unicode"
17 17
 
18
+	"github.com/ergochat/irc-go/ircutils"
19
+
18 20
 	"github.com/ergochat/ergo/irc/connection_limits"
19 21
 	"github.com/ergochat/ergo/irc/email"
20 22
 	"github.com/ergochat/ergo/irc/migrations"
@@ -460,7 +462,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
460 462
 	code, err := am.dispatchCallback(client, account, callbackNamespace, callbackValue)
461 463
 	if err != nil {
462 464
 		am.Unregister(casefoldedAccount, true)
463
-		return errCallbackFailed
465
+		return &registrationCallbackError{underlying: err}
464 466
 	} else {
465 467
 		return am.server.store.Update(func(tx *buntdb.Tx) error {
466 468
 			_, _, err = tx.Set(verificationCodeKey, code, setOptions)
@@ -469,6 +471,28 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
469 471
 	}
470 472
 }
471 473
 
474
+type registrationCallbackError struct {
475
+	underlying error
476
+}
477
+
478
+func (r *registrationCallbackError) Error() string {
479
+	return `Account verification could not be sent`
480
+}
481
+
482
+func registrationCallbackErrorText(config *Config, client *Client, err error) string {
483
+	if callbackErr, ok := err.(*registrationCallbackError); ok {
484
+		// only expose a user-visible error if we are doing direct sending
485
+		if config.Accounts.Registration.EmailVerification.DirectSendingEnabled() {
486
+			errorText := ircutils.SanitizeText(callbackErr.underlying.Error(), 350)
487
+			return fmt.Sprintf(client.t("Could not dispatch registration e-mail: %s"), errorText)
488
+		} else {
489
+			return client.t("Could not dispatch registration e-mail")
490
+		}
491
+	} else {
492
+		return ""
493
+	}
494
+}
495
+
472 496
 // validatePassphrase checks whether a passphrase is allowed by our rules
473 497
 func validatePassphrase(passphrase string) error {
474 498
 	// sanity check the length

+ 10
- 3
irc/email/email.go View File

@@ -9,13 +9,14 @@ import (
9 9
 	"net"
10 10
 	"regexp"
11 11
 	"strings"
12
+	"time"
12 13
 
13 14
 	"github.com/ergochat/ergo/irc/smtp"
14 15
 )
15 16
 
16 17
 var (
17 18
 	ErrBlacklistedAddress = errors.New("Email address is blacklisted")
18
-	ErrInvalidAddress     = errors.New("Email address is blacklisted")
19
+	ErrInvalidAddress     = errors.New("Email address is invalid")
19 20
 	ErrNoMXRecord         = errors.New("Couldn't resolve MX record")
20 21
 )
21 22
 
@@ -40,6 +41,7 @@ type MailtoConfig struct {
40 41
 	MTAReal              MTAConfig `yaml:"mta"`
41 42
 	BlacklistRegexes     []string  `yaml:"blacklist-regexes"`
42 43
 	blacklistRegexes     []*regexp.Regexp
44
+	Timeout              time.Duration
43 45
 }
44 46
 
45 47
 func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
@@ -73,6 +75,11 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
73 75
 	return config.DKIM.Postprocess()
74 76
 }
75 77
 
78
+// are we sending email directly, as opposed to deferring to an MTA?
79
+func (config *MailtoConfig) DirectSendingEnabled() bool {
80
+	return config.MTAReal.Server == ""
81
+}
82
+
76 83
 // get the preferred MX record hostname, "" on error
77 84
 func lookupMX(domain string) (server string) {
78 85
 	var minPref uint16
@@ -104,7 +111,7 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
104 111
 
105 112
 	var addr string
106 113
 	var auth smtp.Auth
107
-	if config.MTAReal.Server != "" {
114
+	if !config.DirectSendingEnabled() {
108 115
 		addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port)
109 116
 		if config.MTAReal.Username != "" && config.MTAReal.Password != "" {
110 117
 			auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server)
@@ -121,5 +128,5 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
121 128
 		addr = fmt.Sprintf("%s:smtp", mx)
122 129
 	}
123 130
 
124
-	return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS)
131
+	return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS, config.Timeout)
125 132
 }

+ 0
- 1
irc/errors.go View File

@@ -34,7 +34,6 @@ var (
34 34
 	errAccountUpdateFailed            = errors.New(`Error while updating your account information`)
35 35
 	errAccountMustHoldNick            = errors.New(`You must hold that nickname in order to register it`)
36 36
 	errAuthzidAuthcidMismatch         = errors.New(`authcid and authzid must be the same`)
37
-	errCallbackFailed                 = errors.New("Account verification could not be sent")
38 37
 	errCertfpAlreadyExists            = errors.New(`An account already exists for your certificate fingerprint`)
39 38
 	errChannelNotOwnedByAccount       = errors.New("Channel not owned by the specified account")
40 39
 	errChannelTransferNotOffered      = errors.New(`You weren't offered ownership of that channel`)

+ 10
- 6
irc/handlers.go View File

@@ -63,14 +63,16 @@ func parseCallback(spec string, config *Config) (callbackNamespace string, callb
63 63
 	return
64 64
 }
65 65
 
66
-func registrationErrorToMessage(err error) (message string) {
66
+func registrationErrorToMessage(config *Config, client *Client, err error) (message string) {
67
+	if emailError := registrationCallbackErrorText(config, client, err); emailError != "" {
68
+		return emailError
69
+	}
70
+
67 71
 	switch err {
68 72
 	case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered, errAccountAlreadyLoggedIn, errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled, errAccountBadPassphrase:
69 73
 		message = err.Error()
70 74
 	case errLimitExceeded:
71 75
 		message = `There have been too many registration attempts recently; try again later`
72
-	case errCallbackFailed:
73
-		message = `Could not dispatch verification email`
74 76
 	default:
75 77
 		// default response: let's be risk-averse about displaying internal errors
76 78
 		// to the clients, especially for something as sensitive as accounts
@@ -2557,10 +2559,12 @@ func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res
2557 2559
 		rb.Add(nil, server.name, "FAIL", "REGISTER", "USERNAME_EXISTS", accountName, client.t("Username is already registered or otherwise unavailable"))
2558 2560
 	case errAccountBadPassphrase:
2559 2561
 		rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_PASSWORD", accountName, client.t("Password was invalid"))
2560
-	case errCallbackFailed:
2561
-		rb.Add(nil, server.name, "FAIL", "REGISTER", "UNACCEPTABLE_EMAIL", accountName, client.t("Could not dispatch verification e-mail"))
2562 2562
 	default:
2563
-		rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", accountName, client.t("Could not register"))
2563
+		if emailError := registrationCallbackErrorText(config, client, err); emailError != "" {
2564
+			rb.Add(nil, server.name, "FAIL", "REGISTER", "UNACCEPTABLE_EMAIL", accountName, emailError)
2565
+		} else {
2566
+			rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", accountName, client.t("Could not register"))
2567
+		}
2564 2568
 	}
2565 2569
 	return
2566 2570
 }

+ 1
- 1
irc/nickserv.go View File

@@ -900,7 +900,7 @@ func nsRegisterHandler(service *ircService, server *Server, client *Client, comm
900 900
 		}
901 901
 	} else {
902 902
 		// details could not be stored and relevant numerics have been dispatched, abort
903
-		message := registrationErrorToMessage(err)
903
+		message := registrationErrorToMessage(config, client, err)
904 904
 		service.Notice(rb, client.t(message))
905 905
 	}
906 906
 }

+ 24
- 5
irc/smtp/smtp.go View File

@@ -24,6 +24,11 @@ import (
24 24
 	"net"
25 25
 	"net/textproto"
26 26
 	"strings"
27
+	"time"
28
+)
29
+
30
+var (
31
+	ErrTimedOut = errors.New("Timed out")
27 32
 )
28 33
 
29 34
 // A Client represents a client connection to an SMTP server.
@@ -48,11 +53,25 @@ type Client struct {
48 53
 
49 54
 // Dial returns a new Client connected to an SMTP server at addr.
50 55
 // The addr must include a port, as in "mail.example.com:smtp".
51
-func Dial(addr string) (*Client, error) {
52
-	conn, err := net.Dial("tcp", addr)
56
+func Dial(addr string, timeout time.Duration) (*Client, error) {
57
+	var conn net.Conn
58
+	var err error
59
+	start := time.Now()
60
+	if timeout == 0 {
61
+		conn, err = net.Dial("tcp", addr)
62
+	} else {
63
+		conn, err = net.DialTimeout("tcp", addr, timeout)
64
+	}
53 65
 	if err != nil {
54 66
 		return nil, err
55 67
 	}
68
+	if timeout != 0 {
69
+		remaining := timeout - time.Since(start)
70
+		if remaining <= 0 {
71
+			return nil, ErrTimedOut
72
+		}
73
+		conn.SetDeadline(time.Now().Add(remaining))
74
+	}
56 75
 	host, _, _ := net.SplitHostPort(addr)
57 76
 	return NewClient(conn, host)
58 77
 }
@@ -316,8 +335,8 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests
316 335
 // attachments (see the mime/multipart package), or other mail
317 336
 // functionality. Higher-level packages exist outside of the standard
318 337
 // library.
319
-// XXX: modified in Oragono to add `requireTLS` and `heloDomain` arguments
320
-func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS bool) error {
338
+// XXX: modified in Ergo to add `requireTLS`, `heloDomain`, and `timeout` arguments
339
+func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS bool, timeout time.Duration) error {
321 340
 	if err := validateLine(from); err != nil {
322 341
 		return err
323 342
 	}
@@ -326,7 +345,7 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string,
326 345
 			return err
327 346
 		}
328 347
 	}
329
-	c, err := Dial(addr)
348
+	c, err := Dial(addr, timeout)
330 349
 	if err != nil {
331 350
 		return err
332 351
 	}

+ 1
- 0
traditional.yaml View File

@@ -386,6 +386,7 @@ accounts:
386 386
             #     password: "hunter2"
387 387
             blacklist-regexes:
388 388
             #    - ".*@mailinator.com"
389
+            timeout: 60s
389 390
 
390 391
     # throttle account login attempts (to prevent either password guessing, or DoS
391 392
     # attacks on the server aimed at forcing repeated expensive bcrypt computations)

Loading…
Cancel
Save