Repository: emersion/go-smtp
Pull request #221 (2023-04-05 09:35:02)

Add ErrAuthFailed
Convenience definition for backends.

Defined in https://www.rfc-editor.org/rfc/rfc4954#section-6


Patch:
diff --git a/README.md b/README.md
index 6992498..51f4baf 100644
--- a/README.md
+++ b/README.md
@@ -107,7 +107,7 @@ type Session struct{}
 
 func (s *Session) AuthPlain(username, password string) error {
 	if username != "username" || password != "password" {
-		return errors.New("Invalid username or password")
+		return smtp.ErrAuthFailed
 	}
 	return nil
 }
diff --git a/backend.go b/backend.go
index 59cea3a..d61f3f6 100644
--- a/backend.go
+++ b/backend.go
@@ -5,6 +5,11 @@ import (
 )
 
 var (
+	ErrAuthFailed = &SMTPError{
+		Code:         535,
+		EnhancedCode: EnhancedCode{5, 7, 8},
+		Message:      "Authentication failed",
+	}
 	ErrAuthRequired = &SMTPError{
 		Code:         502,
 		EnhancedCode: EnhancedCode{5, 7, 0},
diff --git a/conn.go b/conn.go
index 72a67d8..339f40b 100644
--- a/conn.go
+++ b/conn.go
@@ -281,7 +281,7 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
 // READY state -> waiting for MAIL
 func (c *Conn) handleMail(arg string) {
 	if c.helo == "" {
-		c.writeResponse(502, EnhancedCode{2, 5, 1}, "Please introduce yourself first.")
+		c.writeResponse(502, EnhancedCode{5, 5, 1}, "Please introduce yourself first.")
 		return
 	}
 	if c.bdatPipe != nil {
@@ -467,7 +467,7 @@ func (c *Conn) handleRcpt(arg string) {
 	recipient := strings.Trim(arg[3:], "<> ")
 
 	if c.server.MaxRecipients > 0 && len(c.recipients) >= c.server.MaxRecipients {
-		c.writeResponse(552, EnhancedCode{5, 5, 3}, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
+		c.writeResponse(452, EnhancedCode{4, 5, 3}, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
 		return
 	}
 
@@ -626,7 +626,7 @@ func (c *Conn) handleData(arg string) {
 	}
 
 	// We have recipients, go to accept data
-	c.writeResponse(354, EnhancedCode{2, 0, 0}, "Go ahead. End your data with <CR><LF>.<CR><LF>")
+	c.writeResponse(354, NoEnhancedCode, "Go ahead. End your data with <CR><LF>.<CR><LF>")
 
 	defer c.reset()
 
@@ -924,7 +924,11 @@ func (c *Conn) Reject() {
 }
 
 func (c *Conn) greet() {
-	c.writeResponse(220, NoEnhancedCode, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain))
+	protocol := "ESMTP"
+	if c.server.LMTP {
+		protocol = "LMTP"
+	}
+	c.writeResponse(220, NoEnhancedCode, fmt.Sprintf("%v %s Service Ready", c.server.Domain, protocol))
 }
 
 func (c *Conn) writeResponse(code int, enhCode EnhancedCode, text ...string) {
diff --git a/server.go b/server.go
index 3e6c3e3..341ac09 100644
--- a/server.go
+++ b/server.go
@@ -201,11 +201,11 @@ func (s *Server) handleConn(c *Conn) error {
 			}
 
 			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
-				c.writeResponse(221, EnhancedCode{2, 4, 2}, "Idle timeout, bye bye")
+				c.writeResponse(421, EnhancedCode{4, 4, 2}, "Idle timeout, bye bye")
 				return nil
 			}
 
-			c.writeResponse(221, EnhancedCode{2, 4, 0}, "Connection error, sorry")
+			c.writeResponse(421, EnhancedCode{4, 4, 0}, "Connection error, sorry")
 			return err
 		}
 	}


Test patch:
diff --git a/lmtp_server_test.go b/lmtp_server_test.go
index 9cd98c2..980b3a3 100644
--- a/lmtp_server_test.go
+++ b/lmtp_server_test.go
@@ -4,12 +4,24 @@ import (
 	"bufio"
 	"errors"
 	"io"
+	"net"
 	"strings"
 	"testing"
 
 	"github.com/emersion/go-smtp"
 )
 
+func testServerGreetedLMTP(t *testing.T, fn ...serverConfigureFunc) (be *backend, s *smtp.Server, c net.Conn, scanner *bufio.Scanner) {
+	be, s, c, scanner = testServer(t, fn...)
+
+	scanner.Scan()
+	if scanner.Text() != "220 localhost LMTP Service Ready" {
+		t.Fatal("Invalid greeting:", scanner.Text())
+	}
+
+	return
+}
+
 func sendDeliveryCmdsLMTP(t *testing.T, scanner *bufio.Scanner, c io.Writer) {
 	sendLHLO(t, scanner, c)
 
@@ -43,7 +55,7 @@ func sendLHLO(t *testing.T, scanner *bufio.Scanner, c io.Writer) {
 }
 
 func TestServer_LMTP(t *testing.T) {
-	be, s, c, scanner := testServerGreeted(t, func(s *smtp.Server) {
+	be, s, c, scanner := testServerGreetedLMTP(t, func(s *smtp.Server) {
 		s.LMTP = true
 		be := s.Backend.(*backend)
 		be.implementLMTPData = true
@@ -80,7 +92,7 @@ func TestServer_LMTP_Early(t *testing.T) {
 
 	lmtpStatusSync := make(chan struct{})
 
-	be, s, c, scanner := testServerGreeted(t, func(s *smtp.Server) {
+	be, s, c, scanner := testServerGreetedLMTP(t, func(s *smtp.Server) {
 		s.LMTP = true
 		be := s.Backend.(*backend)
 		be.implementLMTPData = true
@@ -124,7 +136,7 @@ func TestServer_LMTP_Expand(t *testing.T) {
 	// correctly expands results if backend doesn't
 	// implement LMTPSession.
 
-	be, s, c, scanner := testServerGreeted(t, func(s *smtp.Server) {
+	be, s, c, scanner := testServerGreetedLMTP(t, func(s *smtp.Server) {
 		s.LMTP = true
 	})
 	defer s.Close()
@@ -147,7 +159,7 @@ func TestServer_LMTP_Expand(t *testing.T) {
 }
 
 func TestServer_LMTP_DuplicatedRcpt(t *testing.T) {
-	be, s, c, scanner := testServerGreeted(t, func(s *smtp.Server) {
+	be, s, c, scanner := testServerGreetedLMTP(t, func(s *smtp.Server) {
 		s.LMTP = true
 		be := s.Backend.(*backend)
 		be.implementLMTPData = true