Skip to content

Commit

Permalink
Adding more blacklist rules and error checking
Browse files Browse the repository at this point in the history
  • Loading branch information
s32x committed Apr 22, 2018
1 parent 33740f3 commit 2012f75
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 57 deletions.
2 changes: 1 addition & 1 deletion glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions verifier/blacklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (
func (v *Verifier) Blacklisted() error {
var g errgroup.Group
g.Go(func() error { return v.dnsBlacklisted(blacklists) })
g.Go(func() error { return v.matchBlacklisted("[email protected]", "proofpoint") })
g.Go(func() error { return v.matchBlacklisted("[email protected]", "cloudmark") })
g.Go(func() error { return v.matchBlacklisted("[email protected]", "trend micro rbl") })
g.Go(func() error { return v.matchBlacklisted("[email protected]", "proofpoint") }) // Proofpoint
// g.Go(func() error { return v.matchBlacklisted("[email protected]", "outlook.com") }) // Outlook
g.Go(func() error { return v.matchBlacklisted("[email protected]", "cloudmark") }) // Cloudmark
g.Go(func() error { return v.matchBlacklisted("[email protected]", "trend micro rbl") }) // Trend Micro RBL+
return g.Wait()
}

Expand Down Expand Up @@ -55,7 +56,7 @@ func (v *Verifier) matchBlacklisted(email, selector string) error {
// Perform a lookup on the email
if _, err := v.Verify(email); err != nil {
// If the error confirms we are blocked with the selector
le := parseRCPTErr(err)
le := parseSMTPError(err)
if le != nil && le.Message == ErrBlocked &&
insContains(le.Details, selector) {
return errors.New("Blocked by " + selector)
Expand Down
3 changes: 3 additions & 0 deletions verifier/deliverabler.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ func shouldRetry(err error) bool {
return insContains(err.Error(),
"i/o timeout",
"broken pipe",
"exceeded the maximum number of connections",
"use of closed network connection",
"connection reset by peer",
"connection declined",
"connection refused",
"multiple regions",
"server busy",
"eof")
Expand Down
86 changes: 44 additions & 42 deletions verifier/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ const (
ErrUnexpectedResponse = "Unexpected response from deliverabler"

// Standard Errors
ErrNoStatusCode = "No status code"
ErrInvalidStatusCode = "Invalid status code"
ErrTimeout = "The connection to the mail server has timed out"
ErrNoSuchHost = "Mail server does not exist"
ErrServerUnavailable = "Mail server is unavailable"
Expand Down Expand Up @@ -46,52 +44,23 @@ func (e *LookupError) Error() string {
return fmt.Sprintf("%s : %s", e.Message, e.Details)
}

// parseSTDErr parses a standard error in order to return a
// more user friendly version of the error
func parseSTDErr(err error) *LookupError {
if err == nil {
return nil
}
errStr := err.Error()

// Return a more understandable error
switch {
case insContains(errStr,
"spamhaus",
"proofpoint",
"cloudmark",
"banned",
"blocked",
"denied"):
return newLookupError(ErrBlocked, errStr)
case insContains(errStr, "timeout"):
return newLookupError(ErrTimeout, errStr)
case insContains(errStr, "no such host"):
return newLookupError(ErrNoSuchHost, errStr)
case insContains(errStr, "unavailable"):
return newLookupError(ErrServerUnavailable, errStr)
default:
return newLookupError(errStr, errStr)
}
}

// parseRCPTErr receives an MX Servers RCPT response message
// parseSMTPError receives an MX Servers response message
// and generates the cooresponding MX error
func parseRCPTErr(err error) *LookupError {
func parseSMTPError(err error) *LookupError {
if err == nil {
return nil
}
errStr := err.Error()

// Verify the length of the error before reading nil indexes
if len(errStr) < 3 {
return newLookupError(ErrNoStatusCode, errStr)
return parseBasicErr(err)
}

// Strips out the status code string and converts to an integer for parsing
status, convErr := strconv.Atoi(string([]rune(errStr)[0:3]))
if convErr != nil {
return newLookupError(ErrInvalidStatusCode, errStr)
return parseBasicErr(err)
}

// If the status code is above 400 there was an error and we should return it
Expand All @@ -102,6 +71,8 @@ func parseRCPTErr(err error) *LookupError {
"undeliverable",
"does not exist",
"may not exist",
"user unknown",
"user not found",
"invalid address",
"recipient invalid",
"recipient rejected",
Expand All @@ -120,7 +91,9 @@ func parseRCPTErr(err error) *LookupError {
if insContains(errStr,
"full",
"space",
"over quota") {
"over quota",
"insufficient",
) {
return newLookupError(ErrFullInbox, errStr)
}
return newLookupError(ErrTooManyRCPT, errStr)
Expand All @@ -132,6 +105,7 @@ func parseRCPTErr(err error) *LookupError {
"proofpoint",
"cloudmark",
"banned",
"blacklisted",
"blocked",
"denied") {
return newLookupError(ErrBlocked, errStr)
Expand All @@ -146,20 +120,48 @@ func parseRCPTErr(err error) *LookupError {
case 554:
return newLookupError(ErrNotAllowed, errStr)
default:
return parseSTDErr(err)
return parseBasicErr(err)
}
}
return nil
}

// parseBasicErr parses a basic MX record response and returns
// a more understandable LookupError
func parseBasicErr(err error) *LookupError {
if err == nil {
return nil
}
errStr := err.Error()

// Return a more understandable error
switch {
case insContains(errStr,
"spamhaus",
"proofpoint",
"cloudmark",
"banned",
"blocked",
"denied"):
return newLookupError(ErrBlocked, errStr)
case insContains(errStr, "timeout"):
return newLookupError(ErrTimeout, errStr)
case insContains(errStr, "no such host"):
return newLookupError(ErrNoSuchHost, errStr)
case insContains(errStr, "unavailable"):
return newLookupError(ErrServerUnavailable, errStr)
default:
return newLookupError(errStr, errStr)
}
}

// insContains returns true if any of the substrings
// are found in the passed string. This method of checking
// contains is case insensitive
func insContains(str string, substr ...string) bool {
lowStr := strings.ToLower(str)
for _, sub := range substr {
lowSub := strings.ToLower(sub)
if strings.Contains(lowStr, lowSub) {
func insContains(str string, subStrs ...string) bool {
for _, subStr := range subStrs {
if strings.Contains(strings.ToLower(str),
strings.ToLower(subStr)) {
return true
}
}
Expand Down
4 changes: 2 additions & 2 deletions verifier/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (

func TestParse550RCPTError(t *testing.T) {
err := errors.New("550 This mailbox does not exist")
le := parseRCPTErr(err)
le := parseSMTPError(err)
assert.Equal(t, (*LookupError)(nil), le)
}

func TestParse550BlockedRCPTError(t *testing.T) {
err := errors.New("550 spamhaus")
le := parseRCPTErr(err)
le := parseSMTPError(err)
assert.Equal(t, ErrBlocked, le.Message)
assert.Equal(t, err.Error(), le.Details)
}
14 changes: 6 additions & 8 deletions verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,12 @@ func (v *Verifier) Verify(email string) (*Lookup, error) {
// Attempt to form an SMTP Connection
del, err := NewDeliverabler(address.Domain, v.hostname, v.sourceAddr)
if err != nil {
le := parseSTDErr(err)
if le != nil {
if le.Message == ErrNoSuchHost {
l.HostExists = false
return l, nil
}
le := parseSMTPError(err)
if le != nil && le.Message == ErrNoSuchHost {
l.HostExists = false
return l, nil
}
return nil, parseSTDErr(err)
return nil, err
}
l.HostExists = true
defer del.Close() // Defer close the SMTP connection
Expand All @@ -110,7 +108,7 @@ func (v *Verifier) Verify(email string) (*Lookup, error) {
// Perform the main address verification if not a catchall server
if !l.CatchAll {
if err := del.IsDeliverable(address.Address, 3); err != nil {
le := parseRCPTErr(err)
le := parseSMTPError(err)
if le != nil {
if le.Message == ErrFullInbox {
l.FullInbox = true // Set FullInbox and move on
Expand Down

0 comments on commit 2012f75

Please sign in to comment.