Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add function ValidateHostAndUser to validate host and user preventing SPAN and BOT #28

Merged
merged 2 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,42 @@

### 1. Format
```go
func main() {
err := checkmail.ValidateFormat("ç$€§/[email protected]")
if err != nil {
fmt.Println(err)
}
}
func main() {
err := checkmail.ValidateFormat("ç$€§/[email protected]")
if err != nil {
fmt.Println(err)
}
}
```
output: `invalid format`

### 2. Domain
```go
func main() {
err := checkmail.ValidateHost("[email protected]")
if err != nil {
fmt.Println(err)
}
}
func main() {
err := checkmail.ValidateHost("[email protected]")
if err != nil {
fmt.Println(err)
}
}
```
output: `unresolvable host`

### 3. User
### 3. Host and User

If host is valid, requires valid SMTP `serverHostName` (see to [online validator](https://mxtoolbox.com/SuperTool.aspx)) and `serverMailAddress` to reverse validation
for prevent SPAN and BOTS.

```go
func main() {
err := checkmail.ValidateHost("[email protected]")
if smtpErr, ok := err.(checkmail.SmtpError); ok && err != nil {
fmt.Printf("Code: %s, Msg: %s", smtpErr.Code(), smtpErr)
}
}
func main() {
var (
serverHostName = "smtp.myserver.com" // set your SMTP server here
serverMailAddress = "[email protected]" // set your valid mail address here
)
err := checkmail.ValidateHostAndUser(serverHostName, serverMailAddress, "[email protected]")
if smtpErr, ok := err.(checkmail.SmtpError); ok && err != nil {
fmt.Printf("Code: %s, Msg: %s", smtpErr.Code(), smtpErr)
}
}
```
output: `Code: 550, Msg: 550 5.1.1 The email account that you tried to reach does not exist.`

Expand Down
23 changes: 21 additions & 2 deletions checkmail.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,50 @@ var (
emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
)


func ValidateFormat(email string) error {
if !emailRegexp.MatchString(email) {
return ErrBadFormat
}
return nil
}

// ValidateHost validate mail host.
func ValidateHost(email string) error {
_, host := split(email)
mx, err := net.LookupMX(host)
if err != nil {
return ErrUnresolvableHost
}
client, err := DialTimeout(fmt.Sprintf("%s:%d", mx[0].Host, 25), forceDisconnectAfter)
if err != nil {
return NewSmtpError(err)
}
client.Close()
return nil
}

// ValidateHostAndUser validate mail host and user.
// If host is valid, requires valid SMTP [1] serverHostName and serverMailAddress to reverse validation
// for prevent SPAN and BOTS.
// [1] https://mxtoolbox.com/SuperTool.aspx
func ValidateHostAndUser(serverHostName, serverMailAddress, email string) error {
_, host := split(email)
mx, err := net.LookupMX(host)
if err != nil {
return ErrUnresolvableHost
}
client, err := DialTimeout(fmt.Sprintf("%s:%d", mx[0].Host, 25), forceDisconnectAfter)
if err != nil {
return NewSmtpError(err)
}
defer client.Close()

err = client.Hello("checkmail.me")
err = client.Hello(serverHostName)
if err != nil {
return NewSmtpError(err)
}
err = client.Mail("[email protected]")
err = client.Mail(serverMailAddress)
if err != nil {
return NewSmtpError(err)
}
Expand Down
37 changes: 33 additions & 4 deletions checkmail_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package checkmail_test
package checkmail

import (
"fmt"
"os"
"testing"

"github.com/badoux/checkmail"
)

var (
Expand Down Expand Up @@ -35,7 +36,27 @@ func TestValidateHost(t *testing.T) {
continue
}

err := checkmail.ValidateHost(s.mail)
err := ValidateHost(s.mail)
if err != nil && s.account == true {
t.Errorf(`"%s" => unexpected error: "%v"`, s.mail, err)
}
if err == nil && s.account == false {
t.Errorf(`"%s" => expected error`, s.mail)
}
}
}

func TestValidateHostAndUser(t *testing.T) {
var (
serverHostName = getenv(t, "self_hostname")
serverMailAddress = getenv(t, "self_mail")
)
for _, s := range samples {
if !s.format {
continue
}

err := ValidateHostAndUser(serverHostName, serverMailAddress, s.mail)
if err != nil && s.account == true {
t.Errorf(`"%s" => unexpected error: "%v"`, s.mail, err)
}
Expand All @@ -47,7 +68,7 @@ func TestValidateHost(t *testing.T) {

func TestValidateFormat(t *testing.T) {
for _, s := range samples {
err := checkmail.ValidateFormat(s.mail)
err := ValidateFormat(s.mail)
if err != nil && s.format == true {
t.Errorf(`"%s" => unexpected error: "%v"`, s.mail, err)
}
Expand All @@ -56,3 +77,11 @@ func TestValidateFormat(t *testing.T) {
}
}
}

func getenv(t *testing.T, name string) (value string) {
name = "test_checkmail_"+name
if value = os.Getenv(name); value =="" {
panic(fmt.Errorf("enviroment variable %q is not defined", name))
}
return
}