diff --git a/README.md b/README.md index 3ee282c..70f7acf 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,16 @@ bypass the verification of the server's certificate chain and host name by using Note, however, that this is insecure and should not be used in production. +### 504 5.7.4 Unrecognized authentication type + +If you get this error, you should try using the LOGIN authentication mechanism: + + addr := "smtp.example.com:587" + auth := gomail.LoginAuth("username", "password", "smtp.example.com") + mailer := gomail.NewCustomMailer(addr, auth) + +See [issue #16](https://github.com/go-gomail/gomail/issues/16). + ## Contact diff --git a/login.go b/login.go new file mode 100644 index 0000000..ee4b3b4 --- /dev/null +++ b/login.go @@ -0,0 +1,54 @@ +package gomail + +import ( + "errors" + "fmt" + "net/smtp" + "strings" +) + +type loginAuth struct { + username string + password string + host string +} + +// LoginAuth returns an Auth that implements the LOGIN authentication mechanism. +func LoginAuth(username, password, host string) smtp.Auth { + return &loginAuth{username, password, host} +} + +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.Name != a.host { + return "", nil, errors.New("gomail: wrong host name") + } + return "LOGIN", nil, nil +} + +func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { + if !more { + return nil, nil + } + + command := strings.ToLower(strings.TrimSuffix(string(fromServer), ":")) + switch command { + case "username": + return []byte(fmt.Sprintf("%s", a.username)), nil + case "password": + return []byte(fmt.Sprintf("%s", a.password)), nil + default: + return nil, fmt.Errorf("gomail: unexpected server challenge: %s", command) + } +} diff --git a/login_test.go b/login_test.go new file mode 100644 index 0000000..64e1762 --- /dev/null +++ b/login_test.go @@ -0,0 +1,66 @@ +package gomail + +import ( + "net/smtp" + "testing" +) + +type output struct { + proto string + data []string + err error +} + +const ( + testUser = "user" + testPwd = "pwd" +) + +func TestPlainAuth(t *testing.T) { + tests := []struct { + serverProtos []string + serverChallenges []string + proto string + data []string + }{ + { + serverProtos: []string{"LOGIN"}, + serverChallenges: []string{"Username:", "Password:"}, + proto: "LOGIN", + data: []string{"", testUser, testPwd}, + }, + } + + for _, test := range tests { + auth := LoginAuth(testUser, testPwd, testHost) + server := &smtp.ServerInfo{ + Name: testHost, + TLS: true, + Auth: test.serverProtos, + } + proto, toServer, err := auth.Start(server) + if err != nil { + t.Fatalf("Start error: %v", err) + } + if proto != test.proto { + t.Errorf("Invalid protocol, got %q, want %q", proto, test.proto) + } + + i := 0 + got := string(toServer) + if got != test.data[i] { + t.Errorf("Invalid response, got %q, want %q", got, test.data[i]) + } + for _, challenge := range test.serverChallenges { + toServer, err = auth.Next([]byte(challenge), true) + if err != nil { + t.Fatalf("Auth error: %v", err) + } + i++ + got = string(toServer) + if got != test.data[i] { + t.Errorf("Invalid response, got %q, want %q", got, test.data[i]) + } + } + } +}