From 021666d6adec8edcb2d1bbf4549f59ebccfdfa1a Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 11 Oct 2024 11:21:56 +0200 Subject: [PATCH] #332: Add default SMTP authentication type to NewClient This commit fixes a regression introduced in v0.5.0. We now set the default SMTPAuthType to NOAUTH in NewClient. Enhanced documentation and added test cases for different SMTP authentication scenarios. --- auth.go | 2 +- client.go | 27 ++++++++++++------ client_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/auth.go b/auth.go index e175a12..a088274 100644 --- a/auth.go +++ b/auth.go @@ -47,7 +47,7 @@ const ( // SMTPAuthNoAuth is equivalent to performing no authentication at all. It is a convenience // option and should not be used. Instead, for mail servers that do no support/require // authentication, the Client should not be passed the WithSMTPAuth option at all. - SMTPAuthNoAuth SMTPAuthType = "" + SMTPAuthNoAuth SMTPAuthType = "NOAUTH" // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616. // diff --git a/client.go b/client.go index 45fd764..af34933 100644 --- a/client.go +++ b/client.go @@ -256,11 +256,12 @@ var ( // - An error if any critical default values are missing or options fail to apply. func NewClient(host string, opts ...Option) (*Client, error) { c := &Client{ - connTimeout: DefaultTimeout, - host: host, - port: DefaultPort, - tlsconfig: &tls.Config{ServerName: host, MinVersion: DefaultTLSMinVersion}, - tlspolicy: DefaultTLSPolicy, + smtpAuthType: SMTPAuthNoAuth, + connTimeout: DefaultTimeout, + host: host, + port: DefaultPort, + tlsconfig: &tls.Config{ServerName: host, MinVersion: DefaultTLSMinVersion}, + tlspolicy: DefaultTLSPolicy, } // Set default HELO/EHLO hostname @@ -1028,9 +1029,16 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e // determines the supported authentication methods, and applies the appropriate authentication // type. An error is returned if authentication fails. // -// This method first verifies the connection to the SMTP server. If no custom authentication -// mechanism is provided, it checks which authentication methods are supported by the server. -// Based on the configured SMTPAuthType, it sets up the appropriate authentication mechanism. +// By default NewClient sets the SMTP authentication type to SMTPAuthNoAuth, meaning, that no +// SMTP authentication will be performed. If the user makes use of SetSMTPAuth or initialzes the +// client with WithSMTPAuth, the SMTP authentication type will be set in the Client, forcing +// this method to determine if the server supports the selected authentication method and +// assigning the corresponding smtp.Auth function to it. +// +// If the user set a custom SMTP authentication function using SetSMTPAuthCustom or +// WithSMTPAuthCustom, we will not perform any detection and assignment logic and will trust +// the user with their provided smtp.Auth function. +// // Finally, it attempts to authenticate the client using the selected method. // // Returns: @@ -1040,7 +1048,8 @@ func (c *Client) auth() error { if err := c.checkConn(); err != nil { return fmt.Errorf("failed to authenticate: %w", err) } - if c.smtpAuth == nil && c.smtpAuthType != SMTPAuthCustom { + + if c.smtpAuth == nil && c.smtpAuthType != SMTPAuthNoAuth { hasSMTPAuth, smtpAuthType := c.smtpClient.Extension("AUTH") if !hasSMTPAuth { return fmt.Errorf("server does not support SMTP AUTH") diff --git a/client_test.go b/client_test.go index c767e75..7e34e97 100644 --- a/client_test.go +++ b/client_test.go @@ -1162,6 +1162,81 @@ func TestClient_Send_withBrokenRecipient(t *testing.T) { } } +func TestClient_DialWithContext_switchAuth(t *testing.T) { + if os.Getenv("TEST_ALLOW_SEND") == "" { + t.Skipf("TEST_ALLOW_SEND is not set. Skipping mail sending test") + } + + // We start with no auth explicitly set + client, err := NewClient( + os.Getenv("TEST_HOST"), + WithTLSPortPolicy(TLSMandatory), + ) + defer func() { + _ = client.Close() + }() + if err != nil { + t.Errorf("failed to create client: %s", err) + return + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to sending server: %s", err) + } + if err = client.Close(); err != nil { + t.Errorf("failed to close client connection: %s", err) + } + + // We switch to LOGIN auth, which the server supports + client.SetSMTPAuth(SMTPAuthLogin) + client.SetUsername(os.Getenv("TEST_USER")) + client.SetPassword(os.Getenv("TEST_PASS")) + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to sending server: %s", err) + } + if err = client.Close(); err != nil { + t.Errorf("failed to close client connection: %s", err) + } + + // We switch to CRAM-MD5, which the server does not support - error expected + client.SetSMTPAuth(SMTPAuthCramMD5) + if err = client.DialWithContext(context.Background()); err == nil { + t.Errorf("expected error when dialing with unsupported auth mechanism, got nil") + return + } + if !errors.Is(err, ErrCramMD5AuthNotSupported) { + t.Errorf("expected dial error: %s, but got: %s", ErrCramMD5AuthNotSupported, err) + } + + // We switch to CUSTOM by providing PLAIN auth as function - the server supports this + client.SetSMTPAuthCustom(smtp.PlainAuth("", os.Getenv("TEST_USER"), os.Getenv("TEST_PASS"), + os.Getenv("TEST_HOST"))) + if client.smtpAuthType != SMTPAuthCustom { + t.Errorf("expected auth type to be Custom, got: %s", client.smtpAuthType) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to sending server: %s", err) + } + if err = client.Close(); err != nil { + t.Errorf("failed to close client connection: %s", err) + } + + // We switch back to explicit no authenticaiton + client.SetSMTPAuth(SMTPAuthNoAuth) + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to sending server: %s", err) + } + if err = client.Close(); err != nil { + t.Errorf("failed to close client connection: %s", err) + } + + // Finally we set an empty string as SMTPAuthType and expect and error. This way we can + // verify that we do not accidentaly skip authentication with an empty string SMTPAuthType + client.SetSMTPAuth("") + if err = client.DialWithContext(context.Background()); err == nil { + t.Errorf("expected error when dialing with empty auth mechanism, got nil") + } +} + // TestClient_auth tests the Dial(), Send() and Close() method of Client with broken settings func TestClient_auth(t *testing.T) { tests := []struct {