Skip to content

Commit

Permalink
Dialer.Dial() now automatically uses CRAM-MD5 when it's available
Browse files Browse the repository at this point in the history
Also deprecated NewPlainDialer() in favor of NewDialer().

Fixes #52
  • Loading branch information
alexcesaro committed Mar 6, 2016
1 parent 6ea1c86 commit 5ceb8e6
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 219 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ bypass the verification of the server's certificate chain and host name by using
)

func main() {
d := gomail.NewPlainDialer("smtp.example.com", 587, "user", "123456")
d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}

// Send emails using d.
Expand Down
52 changes: 17 additions & 35 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,33 @@ import (
"net/smtp"
)

// plainAuth is an smtp.Auth that implements the PLAIN authentication mechanism.
// It fallbacks to the LOGIN mechanism if it is the only mechanism advertised
// by the server.
type plainAuth struct {
// loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
type loginAuth struct {
username string
password string
host string
login bool
}

func (a *plainAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if server.Name != a.host {
return "", nil, errors.New("gomail: wrong host name")
}

var plain, login bool
for _, a := range server.Auth {
switch a {
case "PLAIN":
plain = true
case "LOGIN":
login = true
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if !server.TLS {
advertised := false
for _, mechanism := range server.Auth {
if mechanism == "LOGIN" {
advertised = true
break
}
}
if !advertised {
return "", nil, errors.New("gomail: unencrypted connection")
}
}

if !server.TLS && !plain && !login {
return "", nil, errors.New("gomail: unencrypted connection")
}

if !plain && login {
a.login = true
return "LOGIN", nil, nil
if server.Name != a.host {
return "", nil, errors.New("gomail: wrong host name")
}

return "PLAIN", []byte("\x00" + a.username + "\x00" + a.password), nil
return "LOGIN", nil, nil
}

func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if !a.login {
if more {
return nil, errors.New("gomail: unexpected server challenge")
}
return nil, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if !more {
return nil, nil
}
Expand Down
88 changes: 16 additions & 72 deletions auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,103 +11,51 @@ const (
testHost = "smtp.example.com"
)

var testAuth = &plainAuth{
username: testUser,
password: testPwd,
host: testHost,
}

type plainAuthTest struct {
type authTest struct {
auths []string
challenges []string
tls bool
wantProto string
wantData []string
wantError bool
}

func TestNoAdvertisement(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{},
challenges: []string{"Username:", "Password:"},
tls: false,
wantProto: "PLAIN",
wantError: true,
testLoginAuth(t, &authTest{
auths: []string{},
tls: false,
wantError: true,
})
}

func TestNoAdvertisementTLS(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
testLoginAuth(t, &authTest{
auths: []string{},
challenges: []string{"Username:", "Password:"},
tls: true,
wantProto: "PLAIN",
wantData: []string{"\x00" + testUser + "\x00" + testPwd},
})
}

func TestPlain(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{"PLAIN"},
challenges: []string{"Username:", "Password:"},
tls: false,
wantProto: "PLAIN",
wantData: []string{"\x00" + testUser + "\x00" + testPwd},
})
}

func TestPlainTLS(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{"PLAIN"},
challenges: []string{"Username:", "Password:"},
tls: true,
wantProto: "PLAIN",
wantData: []string{"\x00" + testUser + "\x00" + testPwd},
})
}

func TestPlainAndLogin(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{"PLAIN", "LOGIN"},
challenges: []string{"Username:", "Password:"},
tls: false,
wantProto: "PLAIN",
wantData: []string{"\x00" + testUser + "\x00" + testPwd},
})
}

func TestPlainAndLoginTLS(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{"PLAIN", "LOGIN"},
challenges: []string{"Username:", "Password:"},
tls: true,
wantProto: "PLAIN",
wantData: []string{"\x00" + testUser + "\x00" + testPwd},
wantData: []string{"", testUser, testPwd},
})
}

func TestLogin(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
auths: []string{"LOGIN"},
testLoginAuth(t, &authTest{
auths: []string{"PLAIN", "LOGIN"},
challenges: []string{"Username:", "Password:"},
tls: false,
wantProto: "LOGIN",
wantData: []string{"", testUser, testPwd},
})
}

func TestLoginTLS(t *testing.T) {
testPlainAuth(t, &plainAuthTest{
testLoginAuth(t, &authTest{
auths: []string{"LOGIN"},
challenges: []string{"Username:", "Password:"},
tls: true,
wantProto: "LOGIN",
wantData: []string{"", testUser, testPwd},
})
}

func testPlainAuth(t *testing.T, test *plainAuthTest) {
auth := &plainAuth{
func testLoginAuth(t *testing.T, test *authTest) {
auth := &loginAuth{
username: testUser,
password: testPwd,
host: testHost,
Expand All @@ -119,13 +67,13 @@ func testPlainAuth(t *testing.T, test *plainAuthTest) {
}
proto, toServer, err := auth.Start(server)
if err != nil && !test.wantError {
t.Fatalf("plainAuth.Start(): %v", err)
t.Fatalf("loginAuth.Start(): %v", err)
}
if err != nil && test.wantError {
return
}
if proto != test.wantProto {
t.Errorf("invalid protocol, got %q, want %q", proto, test.wantProto)
if proto != "LOGIN" {
t.Errorf("invalid protocol, got %q, want LOGIN", proto)
}

i := 0
Expand All @@ -134,10 +82,6 @@ func testPlainAuth(t *testing.T, test *plainAuthTest) {
t.Errorf("Invalid response, got %q, want %q", got, test.wantData[i])
}

if proto == "PLAIN" {
return
}

for _, challenge := range test.challenges {
i++
if i >= len(test.wantData) {
Expand All @@ -146,7 +90,7 @@ func testPlainAuth(t *testing.T, test *plainAuthTest) {

toServer, err = auth.Next([]byte(challenge), true)
if err != nil {
t.Fatalf("plainAuth.Auth(): %v", err)
t.Fatalf("loginAuth.Auth(): %v", err)
}
got = string(toServer)
if got != test.wantData[i] {
Expand Down
25 changes: 3 additions & 22 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"html/template"
"io"
"log"
"net/smtp"
"time"

"gopkg.in/gomail.v2"
Expand All @@ -20,7 +19,7 @@ func Example() {
m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
m.Attach("/home/Alex/lolcat.jpg")

d := gomail.NewPlainDialer("smtp.example.com", 587, "user", "123456")
d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")

// Send the email to Bob, Cora and Dan.
if err := d.DialAndSend(m); err != nil {
Expand All @@ -33,7 +32,7 @@ func Example_daemon() {
ch := make(chan *gomail.Message)

go func() {
d := gomail.NewPlainDialer("smtp.example.com", 587, "user", "123456")
d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")

var s gomail.SendCloser
var err error
Expand Down Expand Up @@ -80,7 +79,7 @@ func Example_newsletter() {
Address string
}

d := gomail.NewPlainDialer("smtp.example.com", 587, "user", "123456")
d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
s, err := d.Dial()
if err != nil {
panic(err)
Expand Down Expand Up @@ -114,24 +113,6 @@ func Example_noAuth() {
}
}

// Send an email using the CRAM-MD5 authentication mechanism.
func Example_cRAMMD5() {
m := gomail.NewMessage()
m.SetHeader("From", "from@example.com")
m.SetHeader("To", "to@example.com")
m.SetHeader("Subject", "Hello!")
m.SetBody("text/plain", "Hello!")

d := gomail.Dialer{
Host: "localhost",
Port: 587,
Auth: smtp.CRAMMD5Auth("username", "secret"),
}
if err := d.DialAndSend(m); err != nil {
panic(err)
}
}

// Send an email using an API or postfix.
func Example_noSMTP() {
m := gomail.NewMessage()
Expand Down
Loading

0 comments on commit 5ceb8e6

Please sign in to comment.