This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 381e20feb0deb813297904cf4ac979f0214ee033 Author: Quan Tran <[email protected]> AuthorDate: Thu Aug 13 09:45:51 2026 +0700 JAMES-4210 Add tests to cover ManageSieve SASL behaviors Test coverage includes: - PLAIN initial-response and continuation authentication; - invalid credentials, delegation, disabled mechanisms, and unknown mechanisms; - custom multi-step exchanges returning final server data; - literal initial responses split across network frames; - exchange cleanup after oversized responses and client disconnection. --- .../james/managesieveserver/AuthenticateTest.java | 269 ++++++++++++++++++--- .../james/managesieveserver/ManageSieveClient.java | 22 +- 2 files changed, 257 insertions(+), 34 deletions(-) diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java index 1f59044735..19e3596faf 100644 --- a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java +++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java @@ -19,17 +19,106 @@ package org.apache.james.managesieveserver; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; - -import org.assertj.core.api.Assertions; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.configuration2.HierarchicalConfiguration; +import org.apache.commons.configuration2.tree.ImmutableNode; +import org.apache.james.core.Username; +import org.apache.james.protocols.api.sasl.SaslAuthenticator; +import org.apache.james.protocols.api.sasl.SaslExchange; +import org.apache.james.protocols.api.sasl.SaslIdentity; +import org.apache.james.protocols.api.sasl.SaslInitialRequest; +import org.apache.james.protocols.api.sasl.SaslMechanism; +import org.apache.james.protocols.api.sasl.SaslStep; +import org.apache.james.server.core.configuration.FileConfigurationProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import com.google.common.collect.ImmutableList; + class AuthenticateTest { + private static final SaslIdentity IDENTITY = new SaslIdentity(Username.of("authenticated"), Username.of("authorized")); + + private static byte[] bytes(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + + private record CustomMechanism(RecordingExchange exchange) implements SaslMechanism { + @Override + public String name() { + return "CUSTOM"; + } + + @Override + public SaslExchange start(SaslInitialRequest request, SaslAuthenticator authenticator) { + return exchange; + } + } + + private static class InitialResponseMechanism implements SaslMechanism { + private byte[] initialResponse; + private int closeCount; + + @Override + public String name() { + return "INITIAL"; + } + + @Override + public SaslExchange start(SaslInitialRequest request, SaslAuthenticator authenticator) { + initialResponse = request.initialResponse().orElseThrow(); + return new SaslExchange() { + @Override + public SaslStep firstStep() { + return new SaslStep.Success(IDENTITY, Optional.empty()); + } + + @Override + public SaslStep onResponse(byte[] clientResponse) { + throw new IllegalStateException("Initial response mechanism does not expect a continuation"); + } + + @Override + public void close() { + closeCount++; + } + }; + } + } + + private static class RecordingExchange implements SaslExchange { + private final AtomicInteger closeCount = new AtomicInteger(); + private final CountDownLatch closed = new CountDownLatch(1); + private byte[] clientResponse; + + @Override + public SaslStep firstStep() { + return new SaslStep.Challenge(Optional.of(bytes("challenge"))); + } + + @Override + public SaslStep onResponse(byte[] clientResponse) { + this.clientResponse = clientResponse; + return new SaslStep.Success(IDENTITY, Optional.of(bytes("server-data"))); + } + + @Override + public void close() { + closeCount.incrementAndGet(); + closed.countDown(); + } + } + private ManageSieveClient client; private final ManageSieveServerTestSystem testSystem; @@ -60,7 +149,7 @@ class AuthenticateTest { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD + "wrong"; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); } @Test @@ -68,7 +157,18 @@ class AuthenticateTest { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "not-existing" + "\0" + "pwd"; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + } + + @Test + void plainLoginShouldRejectDelegation() throws IOException { + String initialClientResponse = "other-user\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; + this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); + + ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); + + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.explanation()).contains("authentication failed"); } @Test @@ -76,7 +176,7 @@ class AuthenticateTest { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0"; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); } // The SASL PLAIN standard (https://datatracker.ietf.org/doc/html/rfc4616) defines the following message: @@ -87,7 +187,7 @@ class AuthenticateTest { String initialClientResponse = ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } // The SASL PLAIN standard defines UTF8NUL as separator. To stay compatible with older versions of James, @@ -97,7 +197,7 @@ class AuthenticateTest { String initialClientResponse = " " + ManageSieveServerTestSystem.USERNAME.asString() + " " + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + initialClientResponse + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } // This tests the combination of both lenient behaviors above. @@ -106,7 +206,7 @@ class AuthenticateTest { String initialClientResponse = ManageSieveServerTestSystem.USERNAME.asString() + " " + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + initialClientResponse + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } @Test @@ -114,7 +214,7 @@ class AuthenticateTest { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE PLAIN \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); } @Test @@ -122,33 +222,65 @@ class AuthenticateTest { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE \"PLAIN\" " + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + } + + @Test + void shouldRejectUnknownSaslMechanism() throws IOException { + this.client.sendCommand("AUTHENTICATE {8+}"); + this.client.sendCommand("BAD\"\r\nOK"); + + ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); + + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.explanation()).contains("Unknown SASL mechanism"); + assertThat(authenticationResponse.responseLines()).isEmpty(); + } + + @Test + void shouldReportDisabledPlainAsUnknownSaslMechanismInsteadOfRequiringEncryption() throws Exception { + HierarchicalConfiguration<ImmutableNode> configuration = FileConfigurationProvider.getConfig( + ClassLoader.getSystemResourceAsStream("managesieveserver.xml")); + configuration.addProperty("auth.plainAuthEnabled", false); + client.disconnect(); + testSystem.manageSieveServer.destroy(); + testSystem.setUp(configuration); + client = new ManageSieveClient(); + client.connect(testSystem.getBindedIP(), testSystem.getBindedPort()); + client.readResponse(); + + client.sendCommand("AUTHENTICATE \"PLAIN\""); + ManageSieveClient.ServerResponse authenticationResponse = client.readResponse(); + + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.responseCode()).isEmpty(); + assertThat(authenticationResponse.explanation()).contains("Unknown SASL mechanism"); } @Test void plainLoginWithContinuationShouldSucceed() throws IOException { this.client.sendCommand("AUTHENTICATE \"PLAIN\""); - ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse(); - Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION); - Assertions.assertThat(continuationResponse.explanation().get()).isEqualTo(""); + ManageSieveClient.ServerResponse continuationResponse = this.client.readSaslChallenge(); + assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION); + assertThat(continuationResponse.explanation().get()).isEqualTo(""); String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand(Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } @Test void plainLoginWithContinuationCanBeAborted() throws IOException { this.client.sendCommand("AUTHENTICATE \"PLAIN\""); - ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse(); - Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION); - Assertions.assertThat(continuationResponse.explanation().get()).isEqualTo(""); + ManageSieveClient.ServerResponse continuationResponse = this.client.readSaslChallenge(); + assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION); + assertThat(continuationResponse.explanation().get()).isEqualTo(""); this.client.sendCommand("*"); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); - Assertions.assertThat(authenticationResponse.explanation()).get().isEqualTo("Authentication failed with: authentication aborted by client"); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(authenticationResponse.explanation()).get().isEqualTo("Authentication failed with: authentication aborted by client"); } @Test @@ -158,19 +290,19 @@ class AuthenticateTest { this.client.sendCommand(command); ManageSieveClient.ServerResponse firstAuthenticationResponse = this.client.readResponse(); - Assertions.assertThat(firstAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(firstAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); this.client.sendCommand(command); ManageSieveClient.ServerResponse secondAuthenticationResponse = this.client.readResponse(); - Assertions.assertThat(secondAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); - Assertions.assertThat(secondAuthenticationResponse.explanation()).get().isEqualTo("already authenticated"); + assertThat(secondAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(secondAuthenticationResponse.explanation()).get().isEqualTo("already authenticated"); } @Test void unauthenticateInUnauthenticatedStateShouldFail() throws IOException { this.client.sendCommand("UNAUTHENTICATE"); ManageSieveClient.ServerResponse response = this.client.readResponse(); - Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); } @Test @@ -179,28 +311,28 @@ class AuthenticateTest { this.client.sendCommand("UNAUTHENTICATE"); ManageSieveClient.ServerResponse response = this.client.readResponse(); - Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } @Test void authenticatedStateUnlocksNewCommands() throws IOException { this.client.sendCommand("LISTSCRIPTS"); ManageSieveClient.ServerResponse unauthenticatedResponse = this.client.readResponse(); - Assertions.assertThat(unauthenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(unauthenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); this.authenticatePlain(); this.client.sendCommand("LISTSCRIPTS"); ManageSieveClient.ServerResponse authenticatedResponse = this.client.readResponse(); - Assertions.assertThat(authenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); this.client.sendCommand("UNAUTHENTICATE"); ManageSieveClient.ServerResponse response = this.client.readResponse(); - Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); this.client.sendCommand("LISTSCRIPTS"); ManageSieveClient.ServerResponse loggedOutResponse = this.client.readResponse(); - Assertions.assertThat(loggedOutResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(loggedOutResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); } // The server actually disconnects but isConnected still returns True. @@ -211,8 +343,8 @@ class AuthenticateTest { void logoutShouldWorkInUnauthenticatedState() throws IOException, InterruptedException { this.client.sendCommand("LOGOUT"); ManageSieveClient.ServerResponse response = this.client.readResponse(); - Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); - Assertions.assertThat(this.client.isConnected()).isFalse(); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(this.client.isConnected()).isFalse(); } // The server actually disconnects but isConnected still returns True. @@ -225,14 +357,85 @@ class AuthenticateTest { this.client.sendCommand("LOGOUT"); ManageSieveClient.ServerResponse response = this.client.readResponse(); - Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); - Assertions.assertThat(this.client.isConnected()).isFalse(); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(this.client.isConnected()).isFalse(); + } + + @Test + void shouldDriveCustomMultiStepMechanismAndReturnFinalServerData() throws Exception { + RecordingExchange exchange = new RecordingExchange(); + useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange))); + + client.sendCommand("AUTHENTICATE \"CUSTOM\""); + assertThat(client.readSaslChallenge().explanation()).contains("Y2hhbGxlbmdl"); + + client.sendCommand("\"cmVzcG9uc2U=\""); + assertThat(client.readSaslSuccessData()).containsExactly(bytes("server-data")); + + client.sendCommand("CAPABILITY"); + assertThat(client.readResponse().responseLines()).contains("\"OWNER\" \"authorized\""); + assertThat(exchange.clientResponse).containsExactly(bytes("response")); + assertThat(exchange.closeCount).hasValue(1); + } + + @Test + void shouldAcceptLiteralInitialResponseAcrossNetworkFrames() throws Exception { + InitialResponseMechanism mechanism = new InitialResponseMechanism(); + useSaslMechanisms(ImmutableList.of(mechanism)); + String encodedResponse = "cmVzcG9uc2U="; + + client.sendCommand("AUTHENTICATE \"INITIAL\" {" + encodedResponse.length() + "+}"); + client.sendCommand(encodedResponse); + + assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(mechanism.initialResponse).containsExactly(bytes("response")); + assertThat(mechanism.closeCount).isEqualTo(1); + } + + @Test + void shouldCloseActiveExchangeWhenClientResponseExceedsMaximumLineLength() throws Exception { + RecordingExchange exchange = new RecordingExchange(); + useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange))); + client.sendCommand("AUTHENTICATE \"CUSTOM\""); + client.readSaslChallenge(); + + client.sendCommand("A".repeat(9000)); + + ManageSieveClient.ServerResponse response = client.readResponse(); + assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO); + assertThat(response.explanation()).contains("Maximum command line length exceeded"); + assertThat(exchange.closeCount).hasValue(1); + + client.sendCommand("CAPABILITY"); + assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + } + + @Test + void shouldCloseActiveExchangeWhenClientDisconnects() throws Exception { + RecordingExchange exchange = new RecordingExchange(); + useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange))); + client.sendCommand("AUTHENTICATE \"CUSTOM\""); + client.readSaslChallenge(); + + client.disconnect(); + + assertThat(exchange.closed.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(exchange.closeCount).hasValue(1); + } + + private void useSaslMechanisms(ImmutableList<SaslMechanism> saslMechanisms) throws Exception { + client.disconnect(); + testSystem.manageSieveServer.destroy(); + testSystem.setUp(saslMechanisms); + client = new ManageSieveClient(); + client.connect(testSystem.getBindedIP(), testSystem.getBindedPort()); + client.readResponse(); } void authenticatePlain() throws IOException { String initialClientResponse = "\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD; this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\""); ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse(); - Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); } } diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java index 0f1e7cb9e1..4a2a101b4d 100644 --- a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java +++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java @@ -26,6 +26,7 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Optional; import org.apache.commons.lang3.EnumUtils; @@ -93,7 +94,7 @@ public class ManageSieveClient extends SocketClient { response = new ServerResponse(responseType, responseCode, explanation, lines); } else if (tokens[0].equals("+")) { Optional<String> explanation = Optional.of(tokens[1].substring(1, tokens[1].length() - 1)); - response = new ServerResponse(ResponseType.CONTINUATION, Optional.empty(), explanation, new ArrayList<String>()); + response = new ServerResponse(ResponseType.CONTINUATION, Optional.empty(), explanation, new ArrayList<>()); } else { lines.addLast(line); } @@ -101,6 +102,25 @@ public class ManageSieveClient extends SocketClient { return response; } + public ServerResponse readSaslChallenge() throws IOException { + String line = this.reader.readLine(); + if (line.startsWith("\"") && line.endsWith("\"")) { + return new ServerResponse(ResponseType.CONTINUATION, Optional.empty(), + Optional.of(line.substring(1, line.length() - 1)), new ArrayList<>()); + } + throw new IOException("Expected a ManageSieve SASL challenge but received: " + line); + } + + public byte[] readSaslSuccessData() throws IOException { + String line = this.reader.readLine(); + String prefix = "OK (SASL \""; + String suffix = "\")"; + if (line.startsWith(prefix) && line.endsWith(suffix)) { + return Base64.getDecoder().decode(line.substring(prefix.length(), line.length() - suffix.length())); + } + throw new IOException("Expected a ManageSieve SASL success response but received: " + line); + } + public void sendCommand(String command) throws IOException { this.writer.write(command + "\r\n"); this.writer.flush(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
