mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
commit
30b20c5049
6 changed files with 129 additions and 7 deletions
|
@ -6,15 +6,12 @@ freebsd_task:
|
||||||
name: FreeBSD
|
name: FreeBSD
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
- name: FreeBSD 13.1
|
- name: FreeBSD 13.2
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-13-1
|
image_family: freebsd-13-2
|
||||||
- name: FreeBSD 12.4
|
- name: FreeBSD 12.4
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-12-4
|
image_family: freebsd-12-4
|
||||||
- name: FreeBSD 12.3
|
|
||||||
freebsd_instance:
|
|
||||||
image_family: freebsd-12-3
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TEST_ALLOW_SEND: 0
|
TEST_ALLOW_SEND: 0
|
||||||
|
|
7
auth.go
7
auth.go
|
@ -19,6 +19,10 @@ const (
|
||||||
|
|
||||||
// SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954
|
// SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954
|
||||||
SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5"
|
SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5"
|
||||||
|
|
||||||
|
// SMTPAuthXOAUTH2 is the "XOAUTH2" SASL authentication mechanism.
|
||||||
|
// https://developers.google.com/gmail/imap/xoauth2-protocol
|
||||||
|
SMTPAuthXOAUTH2 SMTPAuthType = "XOAUTH2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SMTP Auth related static errors
|
// SMTP Auth related static errors
|
||||||
|
@ -31,4 +35,7 @@ var (
|
||||||
|
|
||||||
// ErrCramMD5AuthNotSupported should be used if the target server does not support the "CRAM-MD5" schema
|
// ErrCramMD5AuthNotSupported should be used if the target server does not support the "CRAM-MD5" schema
|
||||||
ErrCramMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: CRAM-MD5")
|
ErrCramMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: CRAM-MD5")
|
||||||
|
|
||||||
|
// ErrXOauth2AuthNotSupported should be used if the target server does not support the "XOAUTH2" schema
|
||||||
|
ErrXOauth2AuthNotSupported = errors.New("server does not support SMTP AUTH type: XOAUTH2")
|
||||||
)
|
)
|
||||||
|
|
|
@ -658,6 +658,11 @@ func (c *Client) auth() error {
|
||||||
return ErrCramMD5AuthNotSupported
|
return ErrCramMD5AuthNotSupported
|
||||||
}
|
}
|
||||||
c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
|
c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
|
||||||
|
case SMTPAuthXOAUTH2:
|
||||||
|
if !strings.Contains(sat, string(SMTPAuthXOAUTH2)) {
|
||||||
|
return ErrXOauth2AuthNotSupported
|
||||||
|
}
|
||||||
|
c.sa = smtp.XOAuth2Auth(c.user, c.pass)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported SMTP AUTH type %q", c.satype)
|
return fmt.Errorf("unsupported SMTP AUTH type %q", c.satype)
|
||||||
}
|
}
|
||||||
|
|
24
smtp/auth_xoauth2.go
Normal file
24
smtp/auth_xoauth2.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2023 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package smtp
|
||||||
|
|
||||||
|
type xoauth2Auth struct {
|
||||||
|
username, token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func XOAuth2Auth(username, token string) Auth {
|
||||||
|
return &xoauth2Auth{username, token}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *xoauth2Auth) Start(_ *ServerInfo) (string, []byte, error) {
|
||||||
|
return "XOAUTH2", []byte("user=" + a.username + "\x01" + "auth=Bearer " + a.token + "\x01\x01"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *xoauth2Auth) Next(_ []byte, more bool) ([]byte, error) {
|
||||||
|
if more {
|
||||||
|
return []byte(""), nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -234,8 +234,10 @@ func (c *Client) Auth(a Auth) error {
|
||||||
resp, err = a.Next(msg, code == 334)
|
resp, err = a.Next(msg, code == 334)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// abort the AUTH
|
if mech != "XOAUTH2" {
|
||||||
|
// abort the AUTH. Not required for XOAUTH2
|
||||||
_, _, _ = c.cmd(501, "*")
|
_, _, _ = c.cmd(501, "*")
|
||||||
|
}
|
||||||
_ = c.Quit()
|
_ = c.Quit()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,13 @@ var authTests = []authTest{
|
||||||
[]string{"", "user 287eb355114cf5c471c26a875f1ca4ae"},
|
[]string{"", "user 287eb355114cf5c471c26a875f1ca4ae"},
|
||||||
[]bool{false, false},
|
[]bool{false, false},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
XOAuth2Auth("username", "token"),
|
||||||
|
[]string{""},
|
||||||
|
"XOAUTH2",
|
||||||
|
[]string{"user=username\x01auth=Bearer token\x01\x01", ""},
|
||||||
|
[]bool{false},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestAuth(t *testing.T) {
|
||||||
|
@ -193,6 +200,86 @@ func TestAuthLogin(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestXOAuthOK(t *testing.T) {
|
||||||
|
server := []string{
|
||||||
|
"220 Fake server ready ESMTP",
|
||||||
|
"250-fake.server",
|
||||||
|
"250-AUTH XOAUTH2",
|
||||||
|
"250 8BITMIME",
|
||||||
|
"235 2.7.0 Accepted",
|
||||||
|
}
|
||||||
|
var wrote strings.Builder
|
||||||
|
var fake faker
|
||||||
|
fake.ReadWriter = struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}{
|
||||||
|
strings.NewReader(strings.Join(server, "\r\n")),
|
||||||
|
&wrote,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(fake, "fake.host")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClient: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
auth := XOAuth2Auth("user", "token")
|
||||||
|
err = c.Auth(auth)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("XOAuth2 error: %v", err)
|
||||||
|
}
|
||||||
|
// the Next method returns a nil response. It must not be sent.
|
||||||
|
// The client request must end with the authentication.
|
||||||
|
if !strings.HasSuffix(wrote.String(), "AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=\r\n") {
|
||||||
|
t.Fatalf("got %q; want AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=\r\n", wrote.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXOAuth2Error(t *testing.T) {
|
||||||
|
serverResp := []string{
|
||||||
|
"220 Fake server ready ESMTP",
|
||||||
|
"250-fake.server",
|
||||||
|
"250-AUTH XOAUTH2",
|
||||||
|
"250 8BITMIME",
|
||||||
|
"334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==",
|
||||||
|
"535 5.7.8 Username and Password not accepted",
|
||||||
|
"221 2.0.0 closing connection",
|
||||||
|
}
|
||||||
|
var wrote strings.Builder
|
||||||
|
var fake faker
|
||||||
|
fake.ReadWriter = struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}{
|
||||||
|
strings.NewReader(strings.Join(serverResp, "\r\n")),
|
||||||
|
&wrote,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(fake, "fake.host")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClient: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
auth := XOAuth2Auth("user", "token")
|
||||||
|
err = c.Auth(auth)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected auth error, got nil")
|
||||||
|
}
|
||||||
|
client := strings.Split(wrote.String(), "\r\n")
|
||||||
|
if len(client) != 5 {
|
||||||
|
t.Fatalf("unexpected number of client requests got %d; want 5", len(client))
|
||||||
|
}
|
||||||
|
if client[1] != "AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=" {
|
||||||
|
t.Fatalf("got %q; want AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=", client[1])
|
||||||
|
}
|
||||||
|
// the Next method returns an empty response. It must be sent
|
||||||
|
if client[2] != "" {
|
||||||
|
t.Fatalf("got %q; want empty response", client[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Issue 17794: don't send a trailing space on AUTH command when there's no password.
|
// Issue 17794: don't send a trailing space on AUTH command when there's no password.
|
||||||
func TestClientAuthTrimSpace(t *testing.T) {
|
func TestClientAuthTrimSpace(t *testing.T) {
|
||||||
server := "220 hello world\r\n" +
|
server := "220 hello world\r\n" +
|
||||||
|
|
Loading…
Reference in a new issue