Repository: emersion/go-smtp
Pull request #168 (2021-11-26 15:33:37)

Remove hostname argument from the NewSession method
Client's hostname is already available inside the `ConnectionState` struct:

https://github.com/emersion/go-smtp/blob/30169acc42e795e5d35ce901c8387950b103dfd9/conn.go#L238-L246

https://github.com/emersion/go-smtp/blob/30169acc42e795e5d35ce901c8387950b103dfd9/conn.go#L206-L213

emersion: LGTM

Patch:
diff --git a/README.md b/README.md
index c6e0791..6936f78 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ import (
 // The Backend implements SMTP server methods.
 type Backend struct{}
 
-func (bkd *Backend) NewSession(_ smtp.ConnectionState, _ string) (smtp.Session, error) {
+func (bkd *Backend) NewSession(_ smtp.ConnectionState) (smtp.Session, error) {
 	return &Session{}, nil
 }
 
diff --git a/backend.go b/backend.go
index 9885d8e..c65c41c 100644
--- a/backend.go
+++ b/backend.go
@@ -5,20 +5,21 @@ import (
 )
 
 var (
-	ErrAuthRequired    = &SMTPError{
-		Code: 502,
+	ErrAuthRequired = &SMTPError{
+		Code:         502,
 		EnhancedCode: EnhancedCode{5, 7, 0},
-		Message: "Please authenticate first",
+		Message:      "Please authenticate first",
 	}
-	ErrAuthUnsupported    = &SMTPError{
-		Code: 502,
+	ErrAuthUnsupported = &SMTPError{
+		Code:         502,
 		EnhancedCode: EnhancedCode{5, 7, 0},
-		Message: "Authentication not supported",
-	})
+		Message:      "Authentication not supported",
+	}
+)
 
 // A SMTP server backend.
 type Backend interface {
-	NewSession(c ConnectionState, hostname string) (Session, error)
+	NewSession(c ConnectionState) (Session, error)
 }
 
 type BodyType string
@@ -76,6 +77,8 @@ type Session interface {
 	// Add recipient for currently processed message.
 	Rcpt(to string) error
 	// Set currently processed message contents and send it.
+	// 
+	// r must be consumed before Data returns.
 	Data(r io.Reader) error
 }
 
diff --git a/backendutil/transform.go b/backendutil/transform.go
index aae0ab8..6bbe485 100755
--- a/backendutil/transform.go
+++ b/backendutil/transform.go
@@ -15,8 +15,8 @@ type TransformBackend struct {
 	TransformData func(r io.Reader) (io.Reader, error)
 }
 
-func (be *TransformBackend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
-	sess, err := be.Backend.NewSession(c, hostname)
+func (be *TransformBackend) NewSession(c smtp.ConnectionState) (smtp.Session, error) {
+	sess, err := be.Backend.NewSession(c)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/smtp-debug-server/main.go b/cmd/smtp-debug-server/main.go
index 38d0a90..2606bd5 100644
--- a/cmd/smtp-debug-server/main.go
+++ b/cmd/smtp-debug-server/main.go
@@ -17,7 +17,7 @@ func init() {
 
 type backend struct{}
 
-func (bkd *backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
+func (bkd *backend) NewSession(c smtp.ConnectionState) (smtp.Session, error) {
 	return &session{}, nil
 }
 
diff --git a/conn.go b/conn.go
index 9797efb..cc0d93b 100644
--- a/conn.go
+++ b/conn.go
@@ -243,7 +243,7 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
 	}
 	c.helo = domain
 
-	sess, err := c.server.Backend.NewSession(c.State(), domain)
+	sess, err := c.server.Backend.NewSession(c.State())
 	if err != nil {
 		if smtpErr, ok := err.(*SMTPError); ok {
 			c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
@@ -614,6 +614,7 @@ func (c *Conn) handleStartTLS() {
 		session.Logout()
 		c.SetSession(nil)
 	}
+	c.helo = ""
 	c.didAuth = false
 	c.reset()
 }
diff --git a/lengthlimit_reader.go b/lengthlimit_reader.go
index 695bc76..1513e56 100644
--- a/lengthlimit_reader.go
+++ b/lengthlimit_reader.go
@@ -5,7 +5,7 @@ import (
 	"io"
 )
 
-var ErrTooLongLine = errors.New("smtp: too longer line in input stream")
+var ErrTooLongLine = errors.New("smtp: too long a line in input stream")
 
 // lineLimitReader reads from the underlying Reader but restricts
 // line length of lines in input stream to a certain length.
diff --git a/server.go b/server.go
index d61020c..ee9effd 100644
--- a/server.go
+++ b/server.go
@@ -109,6 +109,8 @@ func (s *Server) Serve(l net.Listener) error {
 	s.listeners = append(s.listeners, l)
 	s.locker.Unlock()
 
+	var tempDelay time.Duration // how long to sleep on accept failure
+
 	for {
 		c, err := l.Accept()
 		if err != nil {
@@ -117,11 +119,28 @@ func (s *Server) Serve(l net.Listener) error {
 				// we called Close()
 				return nil
 			default:
-				return err
 			}
+			if ne, ok := err.(net.Error); ok && ne.Temporary() {
+				if tempDelay == 0 {
+					tempDelay = 5 * time.Millisecond
+				} else {
+					tempDelay *= 2
+				}
+				if max := 1 * time.Second; tempDelay > max {
+					tempDelay = max
+				}
+				s.ErrorLog.Printf("accept error: %s; retrying in %s", err, tempDelay)
+				time.Sleep(tempDelay)
+				continue
+			}
+			return err
 		}
-
-		go s.handleConn(newConn(c, s))
+		go func() {
+			err := s.handleConn(newConn(c, s))
+			if err != nil {
+				s.ErrorLog.Printf("handler error: %s", err)
+			}
+		}()
 	}
 }
 


Test patch:
diff --git a/backendutil/transform_test.go b/backendutil/transform_test.go
index 471bfc1..1800bb1 100755
--- a/backendutil/transform_test.go
+++ b/backendutil/transform_test.go
@@ -29,7 +29,7 @@ type backend struct {
 	userErr error
 }
 
-func (be *backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
+func (be *backend) NewSession(c smtp.ConnectionState) (smtp.Session, error) {
 	return &session{backend: be, anonymous: true}, nil
 }
 
diff --git a/example_test.go b/example_test.go
index 89d09f8..08aee82 100644
--- a/example_test.go
+++ b/example_test.go
@@ -93,7 +93,7 @@ func ExampleSendMail() {
 type Backend struct{}
 
 // NewSession is called after client greeting (EHLO, HELO).
-func (bkd *Backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
+func (bkd *Backend) NewSession(c smtp.ConnectionState) (smtp.Session, error) {
 	return &Session{}, nil
 }
 
diff --git a/server_test.go b/server_test.go
index 48cdb12..57c1c50 100644
--- a/server_test.go
+++ b/server_test.go
@@ -2,6 +2,7 @@ package smtp_test
 
 import (
 	"bufio"
+	"bytes"
 	"errors"
 	"io"
 	"io/ioutil"
@@ -44,7 +45,7 @@ type backend struct {
 	userErr     error
 }
 
-func (be *backend) NewSession(_ smtp.ConnectionState, _ string) (smtp.Session, error) {
+func (be *backend) NewSession(_ smtp.ConnectionState) (smtp.Session, error) {
 	if be.implementLMTPData {
 		return &lmtpSession{&session{backend: be, anonymous: true}}, nil
 	}
@@ -146,6 +147,57 @@ func (s *session) LMTPData(r io.Reader, collector smtp.StatusCollector) error {
 	return nil
 }
 
+type failingListener struct {
+	c      chan error
+	closed bool
+}
+
+func newFailingListener() *failingListener {
+	return &failingListener{c: make(chan error)}
+}
+
+func (l *failingListener) Send(err error) {
+	if !l.closed {
+		l.c <- err
+	}
+}
+
+func (l *failingListener) Accept() (net.Conn, error) {
+	return nil, <-l.c
+}
+
+func (l *failingListener) Close() error {
+	if !l.closed {
+		close(l.c)
+		l.closed = true
+	}
+	return nil
+}
+
+func (l *failingListener) Addr() net.Addr {
+	return &net.TCPAddr{
+		IP:   net.ParseIP("127.0.0.1"),
+		Port: 12345,
+	}
+}
+
+type mockError struct {
+	msg       string
+	temporary bool
+}
+
+func newMockError(msg string, temporary bool) *mockError {
+	return &mockError{
+		msg:       msg,
+		temporary: temporary,
+	}
+}
+
+func (m *mockError) Error() string   { return m.msg }
+func (m *mockError) String() string  { return m.msg }
+func (m *mockError) Timeout() bool   { return false }
+func (m *mockError) Temporary() bool { return m.temporary }
+
 type serverConfigureFunc func(*smtp.Server)
 
 var (
@@ -226,6 +278,37 @@ func testServerEhlo(t *testing.T, fn ...serverConfigureFunc) (be *backend, s *sm
 	return
 }
 
+func TestServerAcceptErrorHandling(t *testing.T) {
+	errorLog := bytes.NewBuffer(nil)
+	be := new(backend)
+	s := smtp.NewServer(be)
+	s.Domain = "localhost"
+	s.AllowInsecureAuth = true
+	s.ErrorLog = log.New(errorLog, "", 0)
+
+	l := newFailingListener()
+	var serveError error
+	go func() {
+		serveError = s.Serve(l)
+		l.Close()
+	}()
+
+	temporaryError := newMockError("temporary mock error", true)
+	l.Send(temporaryError)
+	permanentError := newMockError("permanent mock error", false)
+	l.Send(permanentError)
+	s.Close()
+
+	if serveError == nil {
+		t.Fatal("Serve had exited without an expected error")
+	} else if serveError != permanentError {
+		t.Fatal("Unexpected error:", serveError)
+	}
+	if !strings.Contains(errorLog.String(), temporaryError.String()) {
+		t.Fatal("Missing temporary error in log output:", errorLog.String())
+	}
+}
+
 func TestServer_helo(t *testing.T) {
 	_, s, c, scanner := testServerGreeted(t)
 	defer s.Close()