From fd566a25b351ba5ea331e00ba708d870d3de8e67 Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Tue, 14 Jul 2020 14:53:54 +0300 Subject: [PATCH 1/3] server: Move to using Backend.Features() instead of Enable* flags --- backend.go | 12 ++++++++++++ conn.go | 13 +++++++------ server.go | 20 ++++++++------------ server_test.go | 11 ++++++++--- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/backend.go b/backend.go index e2af9af..7a7ab82 100644 --- a/backend.go +++ b/backend.go @@ -21,6 +21,18 @@ type Backend interface { AnonymousLogin(state *ConnectionState) (Session, error) } +type Feature int32 + +const ( + FeatureNoSMTPUTF8 Feature = 2 << iota + FeatureNoBINARYMIME + FeatureNoREQUIRETLS +) + +type FeatureBackend interface { + Features() Feature +} + type BodyType string const ( diff --git a/conn.go b/conn.go index 462e38e..570e199 100644 --- a/conn.go +++ b/conn.go @@ -255,13 +255,14 @@ func (c *Conn) handleGreet(enhanced bool, arg string) { caps = append(caps, authCap) } - if c.server.EnableSMTPUTF8 { + feat := c.server.backendFeatures() + if feat&FeatureNoSMTPUTF8 == 0 { caps = append(caps, "SMTPUTF8") } - if _, isTLS := c.TLSConnectionState(); isTLS && c.server.EnableREQUIRETLS { + if _, isTLS := c.TLSConnectionState(); isTLS && feat&FeatureNoREQUIRETLS == 0 { caps = append(caps, "REQUIRETLS") } - if c.server.EnableBINARYMIME { + if feat&FeatureNoBINARYMIME == 0 { caps = append(caps, "BINARYMIME") } if c.server.MaxMessageBytes > 0 { @@ -346,13 +347,13 @@ func (c *Conn) handleMail(arg string) { opts.Size = int(size) case "SMTPUTF8": - if !c.server.EnableSMTPUTF8 { + if c.server.backendFeatures()&FeatureNoSMTPUTF8 != 0 { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "SMTPUTF8 is not implemented") return } opts.UTF8 = true case "REQUIRETLS": - if !c.server.EnableREQUIRETLS { + if c.server.backendFeatures()&FeatureNoREQUIRETLS != 0 { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "REQUIRETLS is not implemented") return } @@ -360,7 +361,7 @@ func (c *Conn) handleMail(arg string) { case "BODY": switch value { case "BINARYMIME": - if !c.server.EnableBINARYMIME { + if c.server.backendFeatures()&FeatureNoBINARYMIME != 0 { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "BINARYMIME is not implemented") return } diff --git a/server.go b/server.go index 52506b5..ee92934 100644 --- a/server.go +++ b/server.go @@ -45,18 +45,6 @@ type Server struct { ReadTimeout time.Duration WriteTimeout time.Duration - // Advertise SMTPUTF8 (RFC 6531) capability. - // Should be used only if backend supports it. - EnableSMTPUTF8 bool - - // Advertise REQUIRETLS (RFC 8689) capability. - // Should be used only if backend supports it. - EnableREQUIRETLS bool - - // Advertise BINARYMIME (RFC 3030) capability. - // Should be used only if backend supports it. - EnableBINARYMIME bool - // If set, the AUTH command will not be advertised and authentication // attempts will be rejected. This setting overrides AllowInsecureAuth. AuthDisabled bool @@ -262,3 +250,11 @@ func (s *Server) ForEachConn(f func(*Conn)) { f(conn) } } + +func (s *Server) backendFeatures() Feature { + fb, ok := s.Backend.(FeatureBackend) + if !ok { + return 0 + } + return fb.Features() +} diff --git a/server_test.go b/server_test.go index 48ad654..d466853 100644 --- a/server_test.go +++ b/server_test.go @@ -40,10 +40,16 @@ type backend struct { // Read N bytes of message before returning dataErr. dataErrOffset int64 + features smtp.Feature + panicOnMail bool userErr error } +func (be *backend) Features() smtp.Feature { + return be.features +} + func (be *backend) Login(_ *smtp.ConnectionState, username, password string) (smtp.Session, error) { if be.userErr != nil { return &session{}, be.userErr @@ -339,7 +345,6 @@ func TestServerPanicRecover(t *testing.T) { func TestServerSMTPUTF8(t *testing.T) { _, s, c, scanner := testServerAuthenticated(t) - s.EnableSMTPUTF8 = true defer s.Close() defer c.Close() @@ -353,9 +358,10 @@ func TestServerSMTPUTF8(t *testing.T) { } func TestServerSMTPUTF8_Disabled(t *testing.T) { - _, s, c, scanner := testServerAuthenticated(t) + be, s, c, scanner := testServerAuthenticated(t) defer s.Close() defer c.Close() + be.features = smtp.FeatureNoSMTPUTF8 io.WriteString(c, "MAIL FROM: SMTPUTF8\r\n") scanner.Scan() @@ -1050,7 +1056,6 @@ func TestServer_Chunking_Binarymime(t *testing.T) { be, s, c, scanner := testServerAuthenticated(t) defer s.Close() defer c.Close() - s.EnableBINARYMIME = true io.WriteString(c, "MAIL FROM: BODY=BINARYMIME\r\n") scanner.Scan() From 392f97560363aff8cd7f6bf6b2df1b5f669637ef Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Sun, 17 Jan 2021 17:50:32 +0300 Subject: [PATCH 2/3] server: Invert Feature* flags Make all extensions opt-in rather than opt-out to make future additions easier and more consistent. Unrelated string(rune(char)) change is to make Go 1.15.6 'go test' happy. --- backend.go | 10 +++++++--- conn.go | 14 +++++++------- server_test.go | 7 ++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/backend.go b/backend.go index 7a7ab82..411b92f 100644 --- a/backend.go +++ b/backend.go @@ -24,11 +24,15 @@ type Backend interface { type Feature int32 const ( - FeatureNoSMTPUTF8 Feature = 2 << iota - FeatureNoBINARYMIME - FeatureNoREQUIRETLS + FeatureSMTPUTF8 Feature = 2 << iota + FeatureBINARYMIME + FeatureREQUIRETLS ) +func (f Feature) Contains(feat Feature) bool { + return f&feat != 0 +} + type FeatureBackend interface { Features() Feature } diff --git a/conn.go b/conn.go index 570e199..2b30f22 100644 --- a/conn.go +++ b/conn.go @@ -256,13 +256,13 @@ func (c *Conn) handleGreet(enhanced bool, arg string) { caps = append(caps, authCap) } feat := c.server.backendFeatures() - if feat&FeatureNoSMTPUTF8 == 0 { + if feat.Contains(FeatureSMTPUTF8) { caps = append(caps, "SMTPUTF8") } - if _, isTLS := c.TLSConnectionState(); isTLS && feat&FeatureNoREQUIRETLS == 0 { + if _, isTLS := c.TLSConnectionState(); isTLS && feat.Contains(FeatureREQUIRETLS) { caps = append(caps, "REQUIRETLS") } - if feat&FeatureNoBINARYMIME == 0 { + if feat.Contains(FeatureBINARYMIME) { caps = append(caps, "BINARYMIME") } if c.server.MaxMessageBytes > 0 { @@ -347,13 +347,13 @@ func (c *Conn) handleMail(arg string) { opts.Size = int(size) case "SMTPUTF8": - if c.server.backendFeatures()&FeatureNoSMTPUTF8 != 0 { + if !c.server.backendFeatures().Contains(FeatureSMTPUTF8) { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "SMTPUTF8 is not implemented") return } opts.UTF8 = true case "REQUIRETLS": - if c.server.backendFeatures()&FeatureNoREQUIRETLS != 0 { + if !c.server.backendFeatures().Contains(FeatureREQUIRETLS) { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "REQUIRETLS is not implemented") return } @@ -361,7 +361,7 @@ func (c *Conn) handleMail(arg string) { case "BODY": switch value { case "BINARYMIME": - if c.server.backendFeatures()&FeatureNoBINARYMIME != 0 { + if !c.server.backendFeatures().Contains(FeatureBINARYMIME) { c.WriteResponse(504, EnhancedCode{5, 5, 4}, "BINARYMIME is not implemented") return } @@ -431,7 +431,7 @@ func decodeXtext(val string) (string, error) { return "" } - return string(char) + return string(rune(char)) }) if replaceErr != nil { return "", replaceErr diff --git a/server_test.go b/server_test.go index d466853..50a6639 100644 --- a/server_test.go +++ b/server_test.go @@ -344,9 +344,10 @@ func TestServerPanicRecover(t *testing.T) { } func TestServerSMTPUTF8(t *testing.T) { - _, s, c, scanner := testServerAuthenticated(t) + be, s, c, scanner := testServerAuthenticated(t) defer s.Close() defer c.Close() + be.features = smtp.FeatureSMTPUTF8 io.WriteString(c, "MAIL FROM: SMTPUTF8\r\n") scanner.Scan() @@ -358,10 +359,9 @@ func TestServerSMTPUTF8(t *testing.T) { } func TestServerSMTPUTF8_Disabled(t *testing.T) { - be, s, c, scanner := testServerAuthenticated(t) + _, s, c, scanner := testServerAuthenticated(t) defer s.Close() defer c.Close() - be.features = smtp.FeatureNoSMTPUTF8 io.WriteString(c, "MAIL FROM: SMTPUTF8\r\n") scanner.Scan() @@ -1056,6 +1056,7 @@ func TestServer_Chunking_Binarymime(t *testing.T) { be, s, c, scanner := testServerAuthenticated(t) defer s.Close() defer c.Close() + be.features = smtp.FeatureBINARYMIME io.WriteString(c, "MAIL FROM: BODY=BINARYMIME\r\n") scanner.Scan() From 266cfb0cfffa38422db24249d10e0d12d79ee2af Mon Sep 17 00:00:00 2001 From: "fox.cpp" Date: Fri, 7 May 2021 11:33:56 +0300 Subject: [PATCH 3/3] Address feedback --- backend.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend.go b/backend.go index 411b92f..98a087b 100644 --- a/backend.go +++ b/backend.go @@ -21,11 +21,14 @@ type Backend interface { AnonymousLogin(state *ConnectionState) (Session, error) } -type Feature int32 +type Feature uint32 const ( + // SMTPUTF8 (RFC 6531) extension. FeatureSMTPUTF8 Feature = 2 << iota + // BINARYMIME (RFC 3030) extension. CHUNKING extension is always supported. FeatureBINARYMIME + // REQUIRETLS (RFC 8689) extension. FeatureREQUIRETLS ) @@ -34,6 +37,7 @@ func (f Feature) Contains(feat Feature) bool { } type FeatureBackend interface { + Backend Features() Feature }