changeset 12:66b46b3d73be default tip

Handle capabilities sent by the server after negotiating a SASL security layer
author Guido Berhoerster <guido+managesieve@berhoerster.name>
date Tue, 09 Feb 2021 23:01:02 +0100
parents b790df0733d4
children
files auth.go managesieve.go managesieve_test.go
diffstat 3 files changed, 68 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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