# HG changeset patch # User Guido Berhoerster # Date 1612908062 -3600 # Node ID 66b46b3d73be056a7bf94335570294b16d7f061d # Parent b790df0733d4490aa2939c070d7c7e41fe82ae8d Handle capabilities sent by the server after negotiating a SASL security layer diff -r b790df0733d4 -r 66b46b3d73be auth.go --- a/auth.go Tue Feb 09 21:28:13 2021 +0100 +++ b/auth.go Tue Feb 09 23:01:02 2021 +0100 @@ -58,6 +58,8 @@ // an error is returned SASL authentication will be aborted and an // AuthenticationError will be returned to the caller. Next(challenge []byte, more bool) (response []byte, err error) + // Returns true if a SASL security layer was negotiated. + SASLSecurityLayer() bool } var ( @@ -114,6 +116,10 @@ return nil, nil } +func (a *plainAuth) SASLSecurityLayer() bool { + return false +} + // PlainAuth provides an Auth implementation of SASL PLAIN authentication as // specified in RFC 4616 using the provided authorization identity, username // and password. If the identity is an empty string the server will derive an diff -r b790df0733d4 -r 66b46b3d73be managesieve.go --- a/managesieve.go Tue Feb 09 21:28:13 2021 +0100 +++ b/managesieve.go Tue Feb 09 23:01:02 2021 +0100 @@ -395,10 +395,24 @@ } } - // capabilities are no longer valid after succesful authentication - r, err = c.cmd("CAPABILITY") - if err != nil { - return err + if a.SASLSecurityLayer() { + // server sends capabilities response + r, err := c.p.readReply() + if err != nil { + return err + } + if r.resp == responseNo { + return &ServerError{r.code, r.msg} + } else if r.resp == responseBye { + return &ConnClosedError{r.code, r.msg} + } + } else { + // capabilities are no longer valid after succesful + // authentication + r, err = c.cmd("CAPABILITY") + if err != nil { + return err + } } c.capa, err = parseCapabilities(r) return err diff -r b790df0733d4 -r 66b46b3d73be managesieve_test.go --- a/managesieve_test.go Tue Feb 09 21:28:13 2021 +0100 +++ b/managesieve_test.go Tue Feb 09 23:01:02 2021 +0100 @@ -791,6 +791,45 @@ } } +// capabilities sent by the server after negotiating a secure SASL-layer +var authCapabilitiesResponse string = `"IMPLEMENTATION" "Example" +"SASL" "PLAIN TEST" +"SIEVE" "fileinto vacation" +"StARTTLS" +"VERSION" "1.0" +OK +"cXV1eCxnYXJwbHk=" +"d2FsZG8sZnJlZA==" +OK (SASL "eHl6enkgdGh1ZA==") +"IMPLEMENTATION" "Example" +"SASL" "PLAIN TEST" +"SIEVE" "fileinto vacation" +"StARTTLS" +"VERSION" "1.0" +OK +"default" +OK +` + +func TestAuthCapabilitiesResponse(t *testing.T) { + conn := newFakeConn(authCapabilitiesResponse) + c, err := managesieve.NewClient(conn, "localhost") + if err != nil { + t.Fatalf("failed to create client: %s", err) + } + + auth := &testAuth{} + if err = c.Authenticate(auth); err != nil { + t.Fatalf("failed to authenticate: %s", err) + } + + if scripts, _, err := c.ListScripts(); err != nil { + t.Fatalf("failed to list scripts after authentication: %s", err) + } else if len(scripts) != 1 { + t.Fatalf("expected list of scripts but got none") + } +} + // authentication failure due to invalid credentials func TestPlainAuthDenied(t *testing.T) { conn := newFakeConn(minimalServer + @@ -886,6 +925,10 @@ return resp, nil } +func (a *testAuth) SASLSecurityLayer() bool { + return true +} + // authentication using a custom authentication method func TestCustomAuth(t *testing.T) { conn := newFakeConn(customAuthServer) @@ -975,6 +1018,7 @@ if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } + } // response to aborted SASL authentication is not NO