This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new c83e97ad73 JAMES-3775 Implement a RSpamD client
c83e97ad73 is described below

commit c83e97ad733b439b0512f56e09c319fe54cd9b22
Author: Quan Tran <[email protected]>
AuthorDate: Wed Jul 20 13:01:11 2022 +0700

    JAMES-3775 Implement a RSpamD client
---
 third-party/rspamd/pom.xml                         |  12 +
 ...CombinedHeaderAndContentInputStreamHelper.java} |  45 ++-
 .../rspamd/client/RSpamDClientConfiguration.java}  |  44 ++-
 .../james/rspamd/client/RSpamDHttpClient.java      | 134 +++++++++
 .../exception/RSpamDUnexpectedException.java}      |  34 +--
 .../rspamd/exception/UnauthorizedException.java}   |  33 +--
 .../apache/james/rspamd/model/AnalysisResult.java  | 151 ++++++++++
 .../java/org/apache/james/rspamd/DockerRSpamD.java |   6 +-
 .../apache/james/rspamd/DockerRSpamDExtension.java |   7 +
 .../james/rspamd/DockerRSpamDExtensionTest.java    |  70 +++++
 .../james/rspamd/client/RSpamDHttpClientTest.java  | 167 +++++++++++
 .../model/AnalysisResultDeserializationTest.java   |  98 +++++++
 .../rspamd/src/test/resources/mail/ham/ham1.eml    |  73 +++++
 .../rspamd/src/test/resources/mail/spam/spam8.eml  | 321 +++++++++++++++++++++
 .../src/test/resources/rspamd-config/actions.conf  |  29 ++
 15 files changed, 1119 insertions(+), 105 deletions(-)

diff --git a/third-party/rspamd/pom.xml b/third-party/rspamd/pom.xml
index b19487faa5..75f9635d44 100644
--- a/third-party/rspamd/pom.xml
+++ b/third-party/rspamd/pom.xml
@@ -33,6 +33,10 @@
     <description>RSpamD Java client (HTTP) and testing utilities</description>
 
     <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-mailet-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>
@@ -66,6 +70,14 @@
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jdk8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.testcontainers</groupId>
             <artifactId>testcontainers</artifactId>
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/CombinedHeaderAndContentInputStreamHelper.java
similarity index 52%
copy from 
third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
copy to 
third-party/rspamd/src/main/java/org/apache/james/rspamd/client/CombinedHeaderAndContentInputStreamHelper.java
index 3a8d606099..ee8929ed42 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/CombinedHeaderAndContentInputStreamHelper.java
@@ -17,35 +17,28 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.rspamd;
+package org.apache.james.rspamd.client;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
 
-import org.apache.james.util.Port;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.eclipse.jetty.http.HttpStatus;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
 
-import io.restassured.specification.RequestSpecification;
-
-public class DockerRSpamDExtensionTest {
-    @RegisterExtension
-    static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
-
-    @Test
-    void dockerRSpamDExtensionShouldWork() {
-        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
-
-        String response = rspamdApi
-            .get("ping")
-        .then()
-            .statusCode(HttpStatus.OK_200)
-            .extract()
-            .body()
-            .asString()
-            .trim();
+public class CombinedHeaderAndContentInputStreamHelper {
+    public static InputStream mergeHeaderAndContentInputStream(InputStream 
headerInputStream, InputStream contentInputStream) {
+        return new SequenceInputStream(new 
SequenceInputStream(headerInputStream, new 
ByteArrayInputStream("\r\n".getBytes())), contentInputStream);
+    }
 
-        assertThat(response).isEqualTo("pong");
+    public static InputStream getInputStreamOfMessageHeaders(MimeMessage 
message) throws MessagingException {
+        Enumeration<String> heads = message.getAllHeaderLines();
+        StringBuilder headBuffer = new StringBuilder();
+        while (heads.hasMoreElements()) {
+            headBuffer.append(heads.nextElement()).append("\n");
+        }
+        return new 
ByteArrayInputStream(headBuffer.toString().getBytes(StandardCharsets.UTF_8));
     }
 }
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDClientConfiguration.java
similarity index 56%
copy from 
third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
copy to 
third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDClientConfiguration.java
index 3a8d606099..2141e0baff 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDClientConfiguration.java
@@ -17,35 +17,33 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.rspamd;
+package org.apache.james.rspamd.client;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import java.net.URL;
+import java.util.Optional;
 
-import org.apache.james.util.Port;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.eclipse.jetty.http.HttpStatus;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
+public class RSpamDClientConfiguration {
+    public static final Integer DEFAULT_TIMEOUT_IN_SECONDS = 10;
 
-import io.restassured.specification.RequestSpecification;
+    private final URL url;
+    private final String password;
+    private final Optional<Integer> timeout;
 
-public class DockerRSpamDExtensionTest {
-    @RegisterExtension
-    static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
+    public RSpamDClientConfiguration(URL url, String password, 
Optional<Integer> timeout) {
+        this.url = url;
+        this.password = password;
+        this.timeout = timeout;
+    }
 
-    @Test
-    void dockerRSpamDExtensionShouldWork() {
-        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+    public URL getUrl() {
+        return url;
+    }
 
-        String response = rspamdApi
-            .get("ping")
-        .then()
-            .statusCode(HttpStatus.OK_200)
-            .extract()
-            .body()
-            .asString()
-            .trim();
+    public String getPassword() {
+        return password;
+    }
 
-        assertThat(response).isEqualTo("pong");
+    public Optional<Integer> getTimeout() {
+        return timeout;
     }
 }
diff --git 
a/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDHttpClient.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDHttpClient.java
new file mode 100644
index 0000000000..36c2d268da
--- /dev/null
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/client/RSpamDHttpClient.java
@@ -0,0 +1,134 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.rspamd.client;
+
+import static 
org.apache.james.rspamd.client.CombinedHeaderAndContentInputStreamHelper.getInputStreamOfMessageHeaders;
+import static 
org.apache.james.rspamd.client.CombinedHeaderAndContentInputStreamHelper.mergeHeaderAndContentInputStream;
+import static 
org.apache.james.rspamd.client.RSpamDClientConfiguration.DEFAULT_TIMEOUT_IN_SECONDS;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.rspamd.exception.RSpamDUnexpectedException;
+import org.apache.james.rspamd.exception.UnauthorizedException;
+import org.apache.james.rspamd.model.AnalysisResult;
+import org.apache.james.util.ReactorUtils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.github.fge.lambdas.Throwing;
+
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.netty.ByteBufMono;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.http.client.HttpClientResponse;
+
+public class RSpamDHttpClient {
+    public static final String CHECK_V2_ENDPOINT = "/checkV2";
+    public static final String LEARN_SPAM_ENDPOINT = "/learnspam";
+    public static final String LEARN_HAM_ENDPOINT = "/learnham";
+    private static final int OK = 200;
+    private static final int FORBIDDEN = 403;
+    private static final int BUFFER_SIZE = 16384;
+
+    private final HttpClient httpClient;
+    private final ObjectMapper objectMapper;
+
+    public RSpamDHttpClient(RSpamDClientConfiguration configuration) {
+        httpClient = buildReactorNettyHttpClient(configuration);
+        this.objectMapper = new ObjectMapper().registerModule(new 
Jdk8Module());
+    }
+
+    public Mono<AnalysisResult> checkV2(MimeMessage mimeMessage) throws 
MessagingException, IOException {
+        return Flux.just(getInputStreamOfMessageHeaders(mimeMessage), 
mimeMessage.getInputStream())
+            
.reduce(CombinedHeaderAndContentInputStreamHelper::mergeHeaderAndContentInputStream)
+            .flatMap(inputStream -> httpClient.post()
+                .uri(CHECK_V2_ENDPOINT)
+                .send(ReactorUtils.toChunks(inputStream, BUFFER_SIZE)
+                    .map(Unpooled::wrappedBuffer))
+                .responseSingle(this::checkMailHttpResponseHandler))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER);
+    }
+
+    public Mono<Void> reportAsSpam(InputStream headerInputStream, InputStream 
contentInputStream) {
+        return reportMail(headerInputStream, contentInputStream, 
LEARN_SPAM_ENDPOINT);
+    }
+
+    public Mono<Void> reportAsHam(InputStream headerInputStream, InputStream 
contentInputStream) {
+        return reportMail(headerInputStream, contentInputStream, 
LEARN_HAM_ENDPOINT);
+    }
+
+    private HttpClient buildReactorNettyHttpClient(RSpamDClientConfiguration 
configuration) {
+        return HttpClient.create()
+            .disableRetry(true)
+            
.responseTimeout(Duration.ofSeconds(configuration.getTimeout().orElse(DEFAULT_TIMEOUT_IN_SECONDS)))
+            .baseUrl(configuration.getUrl().toString())
+            .headers(headers -> headers.add("Password", 
configuration.getPassword()));
+    }
+
+    private Mono<Void> reportMail(InputStream headerInputStream, InputStream 
contentInputStream, String endpoint) {
+        return httpClient.post()
+            .uri(endpoint)
+            
.send(ReactorUtils.toChunks(mergeHeaderAndContentInputStream(headerInputStream, 
contentInputStream), BUFFER_SIZE)
+                .map(Unpooled::wrappedBuffer)
+                .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER))
+            .responseSingle(this::reportMailHttpResponseHandler);
+    }
+
+    private Mono<AnalysisResult> 
checkMailHttpResponseHandler(HttpClientResponse httpClientResponse, ByteBufMono 
byteBufMono) {
+        switch (httpClientResponse.status().code()) {
+            case OK:
+                return byteBufMono.asString(StandardCharsets.UTF_8)
+                    .map(Throwing.function(this::convertToAnalysisResult));
+            case FORBIDDEN:
+                return byteBufMono.asString(StandardCharsets.UTF_8)
+                    .flatMap(responseBody -> Mono.error(() -> new 
UnauthorizedException(responseBody)));
+            default:
+                return byteBufMono.asString(StandardCharsets.UTF_8)
+                    .flatMap(responseBody -> Mono.error(() -> new 
RSpamDUnexpectedException(responseBody)));
+        }
+    }
+
+    private Mono<Void> reportMailHttpResponseHandler(HttpClientResponse 
httpClientResponse, ByteBufMono byteBufMono) {
+        switch (httpClientResponse.status().code()) {
+            case OK:
+                return Mono.empty();
+            case FORBIDDEN:
+                return byteBufMono.asString(StandardCharsets.UTF_8)
+                    .flatMap(responseBody -> Mono.error(() -> new 
UnauthorizedException(responseBody)));
+            default:
+                return byteBufMono.asString(StandardCharsets.UTF_8)
+                    .flatMap(responseBody -> Mono.error(() -> new 
RSpamDUnexpectedException(responseBody)));
+        }
+    }
+
+    private AnalysisResult convertToAnalysisResult(String json) throws 
JsonProcessingException {
+        return objectMapper.readValue(json, AnalysisResult.class);
+    }
+
+}
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/RSpamDUnexpectedException.java
similarity index 56%
copy from 
third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
copy to 
third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/RSpamDUnexpectedException.java
index 3a8d606099..33ba848448 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/RSpamDUnexpectedException.java
@@ -17,35 +17,13 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.rspamd;
+package org.apache.james.rspamd.exception;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.apache.james.util.Port;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.eclipse.jetty.http.HttpStatus;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import io.restassured.specification.RequestSpecification;
-
-public class DockerRSpamDExtensionTest {
-    @RegisterExtension
-    static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
-
-    @Test
-    void dockerRSpamDExtensionShouldWork() {
-        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
-
-        String response = rspamdApi
-            .get("ping")
-        .then()
-            .statusCode(HttpStatus.OK_200)
-            .extract()
-            .body()
-            .asString()
-            .trim();
+public class RSpamDUnexpectedException extends RuntimeException {
+    public RSpamDUnexpectedException() {
+    }
 
-        assertThat(response).isEqualTo("pong");
+    public RSpamDUnexpectedException(String message) {
+        super(message);
     }
 }
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/UnauthorizedException.java
similarity index 56%
copy from 
third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
copy to 
third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/UnauthorizedException.java
index 3a8d606099..67687227fe 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/exception/UnauthorizedException.java
@@ -17,35 +17,14 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.rspamd;
+package org.apache.james.rspamd.exception;
 
-import static org.assertj.core.api.Assertions.assertThat;
+public class UnauthorizedException extends RuntimeException {
+    public UnauthorizedException() {
 
-import org.apache.james.util.Port;
-import org.apache.james.webadmin.WebAdminUtils;
-import org.eclipse.jetty.http.HttpStatus;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-import io.restassured.specification.RequestSpecification;
-
-public class DockerRSpamDExtensionTest {
-    @RegisterExtension
-    static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
-
-    @Test
-    void dockerRSpamDExtensionShouldWork() {
-        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
-
-        String response = rspamdApi
-            .get("ping")
-        .then()
-            .statusCode(HttpStatus.OK_200)
-            .extract()
-            .body()
-            .asString()
-            .trim();
+    }
 
-        assertThat(response).isEqualTo("pong");
+    public UnauthorizedException(String message) {
+        super(message);
     }
 }
diff --git 
a/third-party/rspamd/src/main/java/org/apache/james/rspamd/model/AnalysisResult.java
 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/model/AnalysisResult.java
new file mode 100644
index 0000000000..23fb960a06
--- /dev/null
+++ 
b/third-party/rspamd/src/main/java/org/apache/james/rspamd/model/AnalysisResult.java
@@ -0,0 +1,151 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.rspamd.model;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AnalysisResult {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private Action action;
+        private float score;
+        private float requiredScore;
+        private Optional<String> desiredRewriteSubject;
+
+        public Builder() {
+            desiredRewriteSubject = Optional.empty();
+        }
+
+        public Builder action(Action action) {
+            this.action = action;
+            return this;
+        }
+
+        public Builder score(float score) {
+            this.score = score;
+            return this;
+        }
+
+        public Builder requiredScore(float requiredScore) {
+            this.requiredScore = requiredScore;
+            return this;
+        }
+
+        public Builder desiredRewriteSubject(String desiredRewriteSubject) {
+            this.desiredRewriteSubject = Optional.of(desiredRewriteSubject);
+            return this;
+        }
+
+        public AnalysisResult build() {
+            Preconditions.checkNotNull(action);
+
+            return new AnalysisResult(action, score, requiredScore, 
desiredRewriteSubject);
+        }
+    }
+
+    public enum Action {
+        @JsonProperty("no action") NO_ACTION("no action"), // message is 
likely ham
+        @JsonProperty("greylist") GREY_LIST("greylist"), // message should be 
grey listed
+        @JsonProperty("add header") ADD_HEADER("add header"), // message is 
suspicious and should be marked as spam
+        @JsonProperty("rewrite subject") REWRITE_SUBJECT("rewrite subject"), 
// message is suspicious and should have subject rewritten
+        @JsonProperty("soft reject") SOFT_REJECT("soft reject"), // message 
should be temporary rejected (for example, due to rate limit exhausting)
+        @JsonProperty("reject") REJECT("reject"); // message should be 
rejected as spam
+
+        private final String description;
+
+        Action(String description) {
+            this.description = description;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+    }
+
+    private final Action action;
+    private final float score;
+    private final float requiredScore;
+    private final Optional<String> desiredRewriteSubject;
+
+    public AnalysisResult(@JsonProperty("action") Action action,
+                          @JsonProperty("score") float score,
+                          @JsonProperty("required_score") float requiredScore,
+                          @JsonProperty("subject") Optional<String> 
desiredRewriteSubject) {
+        this.action = action;
+        this.score = score;
+        this.requiredScore = requiredScore;
+        this.desiredRewriteSubject = desiredRewriteSubject;
+    }
+
+    public Action getAction() {
+        return action;
+    }
+
+    public float getScore() {
+        return score;
+    }
+
+    public float getRequiredScore() {
+        return requiredScore;
+    }
+
+    public Optional<String> getDesiredRewriteSubject() {
+        return desiredRewriteSubject;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof AnalysisResult) {
+            AnalysisResult that = (AnalysisResult) o;
+
+            return Objects.equals(this.score, that.score)
+                && Objects.equals(this.requiredScore, that.requiredScore)
+                && Objects.equals(this.action, that.action)
+                && Objects.equals(this.desiredRewriteSubject, 
that.desiredRewriteSubject);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(action, score, requiredScore, 
desiredRewriteSubject);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("action", action)
+            .add("score", score)
+            .add("requiredScore", requiredScore)
+            .add("desiredRewriteSubject", desiredRewriteSubject)
+            .toString();
+    }
+}
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamD.java 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamD.java
index dcff4dde8d..5f7d95a191 100644
--- a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamD.java
+++ b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamD.java
@@ -23,15 +23,17 @@ import org.apache.james.rate.limiter.DockerRedis;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.Network;
 import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
 
 public class DockerRSpamD {
+    public static final String PASSWORD = "admin";
     private static final DockerImageName DEFAULT_IMAGE_NAME = 
DockerImageName.parse("a16bitsysop/rspamd");
     private static final String DEFAULT_TAG = "3.2-r2-alpine3.16.0-r0";
     private static final int DEFAULT_PORT = 11334;
 
     private final DockerRedis dockerRedis;
     private final GenericContainer<?> container;
-    private Network network;
+    private final Network network;
 
     public DockerRSpamD() {
         this.network = Network.newNetwork();
@@ -43,6 +45,8 @@ public class DockerRSpamD {
         return new GenericContainer<>(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG))
             .withExposedPorts(DEFAULT_PORT)
             .withEnv("REDIS", "redis")
+            .withEnv("PASSWORD", PASSWORD)
+            
.withCopyFileToContainer(MountableFile.forClasspathResource("rspamd-config/actions.conf"),
 "/etc/rspamd/")
             .withNetwork(network);
     }
 
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java
 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java
index 20f95f8d1a..0dc22cccd8 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java
+++ 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java
@@ -19,6 +19,9 @@
 
 package org.apache.james.rspamd;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+
 import org.apache.james.GuiceModuleTestExtension;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.ParameterContext;
@@ -55,4 +58,8 @@ public class DockerRSpamDExtension implements 
GuiceModuleTestExtension {
     public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext extensionContext) throws ParameterResolutionException {
         return dockerRSpamD();
     }
+
+    public URL getBaseUrl() throws MalformedURLException {
+        return new URL("http://127.0.0.1:"; + dockerRSpamD().getPort());
+    }
 }
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
index 3a8d606099..a98cfd2743 100644
--- 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
+++ 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtensionTest.java
@@ -19,14 +19,19 @@
 
 package org.apache.james.rspamd;
 
+import static org.apache.james.rspamd.DockerRSpamD.PASSWORD;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.core.Is.is;
 
 import org.apache.james.util.Port;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.eclipse.jetty.http.HttpStatus;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
+import io.restassured.http.Header;
 import io.restassured.specification.RequestSpecification;
 
 public class DockerRSpamDExtensionTest {
@@ -48,4 +53,69 @@ public class DockerRSpamDExtensionTest {
 
         assertThat(response).isEqualTo("pong");
     }
+
+    @Test
+    void checkSpamEmailWithExactPasswordHeaderShouldWork() {
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream("mail/spam/spam8.eml"))
+            .post("checkv2")
+        .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("action", is("reject"));
+    }
+
+    @Test
+    void checkHamEmailWithExactPasswordHeaderShouldWork() {
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream("mail/ham/ham1.eml"))
+            .post("checkv2")
+        .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("action", is("no action"));
+    }
+
+    @Test
+    void learnSpamEmailWithExactPasswordHeaderShouldWork() {
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream("mail/spam/spam8.eml"))
+        .post("learnspam")
+            .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("success", is(true));
+    }
+
+    @Test
+    void learnHamEmailWithExactPasswordHeaderShouldWork() {
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream("mail/ham/ham1.eml"))
+            .post("learnham")
+        .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("success", is(true));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"checkv2", "learnspam", "learnham"})
+    void endpointsWithWrongPasswordHeaderShouldReturnUnauthorized(String 
endpoint) {
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+
+        rspamdApi
+            .header(new Header("Password", "wrongPassword"))
+            .body("dummy")
+            .post(endpoint)
+        .then()
+            .statusCode(HttpStatus.FORBIDDEN_403)
+            .body("error", is("Unauthorized"));
+    }
 }
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/client/RSpamDHttpClientTest.java
 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/client/RSpamDHttpClientTest.java
new file mode 100644
index 0000000000..a77902ec9d
--- /dev/null
+++ 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/client/RSpamDHttpClientTest.java
@@ -0,0 +1,167 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.rspamd.client;
+
+import static org.apache.james.rspamd.DockerRSpamD.PASSWORD;
+import static 
org.apache.james.rspamd.client.CombinedHeaderAndContentInputStreamHelper.getInputStreamOfMessageHeaders;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.core.Is.is;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.rspamd.DockerRSpamDExtension;
+import org.apache.james.rspamd.exception.UnauthorizedException;
+import org.apache.james.rspamd.model.AnalysisResult;
+import org.apache.james.util.MimeMessageUtil;
+import org.apache.james.util.Port;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.restassured.http.Header;
+import io.restassured.specification.RequestSpecification;
+
+public class RSpamDHttpClientTest {
+    private final static String SPAM_MESSAGE_PATH = "mail/spam/spam8.eml";
+    private final static String HAM_MESSAGE_PATH = "mail/ham/ham1.eml";
+
+    @RegisterExtension
+    static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
+
+    private MimeMessage spamMessage;
+    private MimeMessage hamMessage;
+
+    @BeforeEach
+    void setup() throws MessagingException {
+        spamMessage = 
MimeMessageUtil.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream(SPAM_MESSAGE_PATH));
+        hamMessage = 
MimeMessageUtil.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream(HAM_MESSAGE_PATH));
+    }
+
+    @Test
+    void checkMailWithWrongPasswordShouldThrowUnauthorizedExceptionException() 
throws IOException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), "wrongPassword", 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        assertThatThrownBy(() -> client.checkV2(spamMessage).block())
+            .hasMessage("{\"error\":\"Unauthorized\"}")
+            .isInstanceOf(UnauthorizedException.class);
+    }
+
+    @Test
+    void learnSpamWithWrongPasswordShouldThrowUnauthorizedExceptionException() 
throws IOException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), "wrongPassword", 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        assertThatThrownBy(() -> reportAsSpam(client, spamMessage))
+            .hasMessage("{\"error\":\"Unauthorized\"}")
+            .isInstanceOf(UnauthorizedException.class);
+    }
+
+    @Test
+    void learnHamWithWrongPasswordShouldThrowUnauthorizedExceptionException() 
throws IOException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), "wrongPassword", 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        assertThatThrownBy(() -> reportAsHam(client, hamMessage))
+            .hasMessage("{\"error\":\"Unauthorized\"}")
+            .isInstanceOf(UnauthorizedException.class);
+    }
+
+    @Test
+    void 
checkSpamMailUsingRSpamDClientWithExactPasswordShouldReturnAnalysisResultAsSameAsUsingRawClient()
 throws IOException, MessagingException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), PASSWORD, 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        AnalysisResult analysisResult = client.checkV2(spamMessage).block();
+        
assertThat(analysisResult.getAction()).isEqualTo(AnalysisResult.Action.REJECT);
+
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream(SPAM_MESSAGE_PATH))
+            .post("checkv2")
+        .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("action", is(analysisResult.getAction().getDescription()))
+            .body("required_score", is(analysisResult.getRequiredScore()))
+            .body("subject", is(nullValue()));
+    }
+
+    @Test
+    void 
checkHamMailUsingRSpamDClientWithExactPasswordShouldReturnAnalysisResultAsSameAsUsingRawClient()
 throws IOException, MessagingException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), PASSWORD, 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        AnalysisResult analysisResult = client.checkV2(hamMessage).block();
+        assertThat(analysisResult).isEqualTo(AnalysisResult.builder()
+            .action(AnalysisResult.Action.NO_ACTION)
+            .score(0.99F)
+            .requiredScore(14.0F)
+            .build());
+
+        RequestSpecification rspamdApi = 
WebAdminUtils.spec(Port.of(rSpamDExtension.dockerRSpamD().getPort()));
+        rspamdApi
+            .header(new Header("Password", PASSWORD))
+            .body(ClassLoader.getSystemResourceAsStream(HAM_MESSAGE_PATH))
+            .post("checkv2")
+        .then()
+            .statusCode(HttpStatus.OK_200)
+            .body("action", is(analysisResult.getAction().getDescription()))
+            .body("score", is(analysisResult.getScore()))
+            .body("required_score", is(analysisResult.getRequiredScore()))
+            .body("subject", is(nullValue()));
+    }
+
+    @Test
+    void learnSpamMailUsingRSpamDClientWithExactPasswordShouldWork() throws 
IOException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), PASSWORD, 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        assertThatCode(() -> 
client.reportAsSpam(getInputStreamOfMessageHeaders(spamMessage), 
spamMessage.getInputStream()).block())
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    void learnHamMailUsingRSpamDClientWithExactPasswordShouldWork() throws 
IOException {
+        RSpamDClientConfiguration configuration = new 
RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), PASSWORD, 
Optional.empty());
+        RSpamDHttpClient client = new RSpamDHttpClient(configuration);
+
+        assertThatCode(() -> 
client.reportAsHam(getInputStreamOfMessageHeaders(hamMessage), 
hamMessage.getInputStream()).block())
+            .doesNotThrowAnyException();
+    }
+
+    private void reportAsSpam(RSpamDHttpClient client, MimeMessage 
mimeMessage) throws MessagingException, IOException {
+        client.reportAsSpam(getInputStreamOfMessageHeaders(mimeMessage), 
mimeMessage.getInputStream()).block();
+    }
+
+    private void reportAsHam(RSpamDHttpClient client, MimeMessage mimeMessage) 
throws MessagingException, IOException {
+        client.reportAsHam(getInputStreamOfMessageHeaders(mimeMessage), 
mimeMessage.getInputStream()).block();
+    }
+
+}
diff --git 
a/third-party/rspamd/src/test/java/org/apache/james/rspamd/model/AnalysisResultDeserializationTest.java
 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/model/AnalysisResultDeserializationTest.java
new file mode 100644
index 0000000000..4e8067398b
--- /dev/null
+++ 
b/third-party/rspamd/src/test/java/org/apache/james/rspamd/model/AnalysisResultDeserializationTest.java
@@ -0,0 +1,98 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.rspamd.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+public class AnalysisResultDeserializationTest {
+    @Test
+    void shouldBeDeserializedWellWhenEmptyDesiredRewriteSubject() throws 
JsonProcessingException {
+        String json = "{\n" +
+            "  \"is_skipped\": false,\n" +
+            "  \"score\": 5.2,\n" +
+            "  \"required_score\": 7,\n" +
+            "  \"action\": \"add header\",\n" +
+            "  \"symbols\": {\n" +
+            "    \"DATE_IN_PAST\": {\n" +
+            "      \"name\": \"DATE_IN_PAST\",\n" +
+            "      \"score\": 0.1\n" +
+            "    }\n" +
+            "  },\n" +
+            "  \"urls\": [\n" +
+            "    \"www.example.com\"\n" +
+            "  ],\n" +
+            "  \"emails\": [\n" +
+            "    \"[email protected]\"\n" +
+            "  ],\n" +
+            "  " +
+            "\"message-id\": 
\"4E699308EFABE14EB3F18A1BB025456988527794@example\"\n" +
+            "}";
+
+        ObjectMapper objectMapper = new ObjectMapper().registerModule(new 
Jdk8Module());
+        AnalysisResult analysisResult = objectMapper.readValue(json, 
AnalysisResult.class);
+
+        assertThat(analysisResult).isEqualTo(AnalysisResult.builder()
+            .action(AnalysisResult.Action.ADD_HEADER)
+            .score(5.2F)
+            .requiredScore(7.0F)
+            .build());
+    }
+
+    @Test
+    void shouldBeDeserializedWellWhenDesiredRewriteSubject() throws 
JsonProcessingException {
+        String json = "{\n" +
+            "  \"is_skipped\": false,\n" +
+            "  \"score\": 5.2,\n" +
+            "  \"required_score\": 7,\n" +
+            "  \"action\": \"rewrite subject\",\n" +
+            "  \"symbols\": {\n" +
+            "    \"DATE_IN_PAST\": {\n" +
+            "      \"name\": \"DATE_IN_PAST\",\n" +
+            "      \"score\": 0.1\n" +
+            "    }\n" +
+            "  },\n" +
+            "  \"urls\": [\n" +
+            "    \"www.example.com\"\n" +
+            "  ],\n" +
+            "  \"emails\": [\n" +
+            "    \"[email protected]\"\n" +
+            "  ],\n" +
+            "  \"message-id\": 
\"4E699308EFABE14EB3F18A1BB025456988527794@example\",\n" +
+            "  \"subject\": \"A rewritten ham subject" +
+            "\"\n" +
+            "}";
+
+        ObjectMapper objectMapper = new ObjectMapper().registerModule(new 
Jdk8Module());
+        AnalysisResult analysisResult = objectMapper.readValue(json, 
AnalysisResult.class);
+
+        assertThat(analysisResult).isEqualTo(AnalysisResult.builder()
+            .action(AnalysisResult.Action.REWRITE_SUBJECT)
+            .score(5.2F)
+            .requiredScore(7.0F)
+            .desiredRewriteSubject("A rewritten ham subject")
+            .build());
+    }
+}
diff --git a/third-party/rspamd/src/test/resources/mail/ham/ham1.eml 
b/third-party/rspamd/src/test/resources/mail/ham/ham1.eml
new file mode 100644
index 0000000000..390fc550a7
--- /dev/null
+++ b/third-party/rspamd/src/test/resources/mail/ham/ham1.eml
@@ -0,0 +1,73 @@
+From [email protected]  Thu Aug 22 12:46:39 2002
+Return-Path: <[email protected]>
+Delivered-To: [email protected]
+Received: from localhost (localhost [127.0.0.1])
+       by phobos.labs.netnoteinc.com (Postfix) with ESMTP id BE12E43C34
+       for <zzzz@localhost>; Thu, 22 Aug 2002 07:46:38 -0400 (EDT)
+Received: from phobos [127.0.0.1]
+       by localhost with IMAP (fetchmail-5.9.0)
+       for zzzz@localhost (single-drop); Thu, 22 Aug 2002 12:46:38 +0100 (IST)
+Received: from n20.grp.scd.yahoo.com (n20.grp.scd.yahoo.com
+    [66.218.66.76]) by dogma.slashnull.org (8.11.6/8.11.6) with SMTP id
+    g7MBkTZ05087 for <[email protected]>; Thu, 22 Aug 2002 12:46:29 
+0100
+X-Egroups-Return: 
sentto-2242572-52726-1030016790-zzzz=spamassassin.taint....@returns.groups.yahoo.com
+Received: from [66.218.67.196] by n20.grp.scd.yahoo.com with NNFMP;
+    22 Aug 2002 11:46:30 -0000
+X-Sender: [email protected]
+X-Apparently-To: [email protected]
+Received: (EGP: mail-8_1_0_1); 22 Aug 2002 11:46:29 -0000
+Received: (qmail 11764 invoked from network); 22 Aug 2002 11:46:29 -0000
+Received: from unknown (66.218.66.217) by m3.grp.scd.yahoo.com with QMQP;
+    22 Aug 2002 11:46:29 -0000
+Received: from unknown (HELO mailgateway.cursor-system.com) (62.189.7.27)
+    by mta2.grp.scd.yahoo.com with SMTP; 22 Aug 2002 11:46:29 -0000
+Received: from exchange1.cps.local (unverified) by
+    mailgateway.cursor-system.com (Content Technologies SMTPRS 4.2.10) with
+    ESMTP id <[email protected]> for
+    <[email protected]>; Thu, 22 Aug 2002 13:14:10 +0100
+Received: by exchange1.cps.local with Internet Mail Service (5.5.2653.19)
+    id <PXX6AT23>; Thu, 22 Aug 2002 12:46:27 +0100
+Message-Id: <[email protected]>
+To: "'[email protected]'" <[email protected]>
+X-Mailer: Internet Mail Service (5.5.2653.19)
+X-Egroups-From: Steve Burt <[email protected]>
+From: Steve Burt <[email protected]>
+X-Yahoo-Profile: pyruse
+MIME-Version: 1.0
+Mailing-List: list [email protected]; contact
+    [email protected]
+Delivered-To: mailing list [email protected]
+Precedence: bulk
+List-Unsubscribe: <mailto:[email protected]>
+Date: Thu, 22 Aug 2002 12:46:18 +0100
+Subject: [zzzzteana] RE: Alexander
+Reply-To: [email protected]
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+
+Martin A posted:
+Tassos Papadopoulos, the Greek sculptor behind the plan, judged that the
+ limestone of Mount Kerdylio, 70 miles east of Salonika and not far from the
+ Mount Athos monastic community, was ideal for the patriotic sculpture.
+
+ As well as Alexander's granite features, 240 ft high and 170 ft wide, a
+ museum, a restored amphitheatre and car park for admiring crowds are
+planned
+---------------------
+So is this mountain limestone or granite?
+If it's limestone, it'll weather pretty fast.
+
+------------------------ Yahoo! Groups Sponsor ---------------------~-->
+4 DVDs Free +s&p Join Now
+http://us.click.yahoo.com/pt6YBB/NXiEAA/mG3HAA/7gSolB/TM
+---------------------------------------------------------------------~->
+
+To unsubscribe from this group, send an email to:
[email protected]
+
+
+
+Your use of Yahoo! Groups is subject to http://docs.yahoo.com/info/terms/
+
+
+
diff --git a/third-party/rspamd/src/test/resources/mail/spam/spam8.eml 
b/third-party/rspamd/src/test/resources/mail/spam/spam8.eml
new file mode 100644
index 0000000000..addf4638cb
--- /dev/null
+++ b/third-party/rspamd/src/test/resources/mail/spam/spam8.eml
@@ -0,0 +1,321 @@
+From [email protected]  Sun Jul 15 04:56:31 2001
+Return-Path: <[email protected]>
+Delivered-To: [email protected]
+Received: from cccp.co.kr (unknown [211.218.149.105]) by
+    mail.netnoteinc.com (Postfix) with SMTP id 789D51140BA for
+    <[email protected]>; Sun, 15 Jul 2001 03:56:28 +0000 (Eire)
+Received: from imail.ru [210.14.5.95] by cccp.co.kr running [nMail 1.04
+    (Windows NT/2000) SMTP Server]; Wed, 11 Jul 2001 02:41:35 +0900
+Message-Id: <[email protected]>
+To: <[email protected]>
+From: [email protected]
+Subject: FW:
+Date: Fri, 02 Jan 1998 04:30:44 -0400
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+X-Priority: 3
+X-Msmail-Priority: Normal
+X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
+
+
+<HTML>
+<BODY bgColor=3D#C0C0C0>
+
+<FONT face=3D"Arial">
+<FONT size=3D4>
+<FONT color=3D"#800040"> Thank you for your interest!</FONT>
+<FONT size=3D3>
+<FONT color=3D"#800040">  </FONT>
+<FONT color=3D"#000000">                         <BR>
+<BR>
+Judgment Courses offers an extensive Audio training<BR>
+course in </FONT>
+<FONT color=3D"#0000A0"> "How to Collect Money Judgments"</FONT>
+<FONT color=3D"#000000">  .         </FONT>
+<FONT size=3D2>
+<FONT color=3D"#000000"> S6</FONT>
+<FONT size=3D3>
+<FONT color=3D"#000000"> <BR>
+<BR>
+If you are like many people, you are not even sure what a<BR>
+Money Judgment is and </FONT>
+<FONT color=3D"#0000A0"> why processing Money Judgments<BR>
+can earn you very substantial income</FONT>
+<FONT color=3D"#000000">  .<BR>
+<BR>
+If you ever sue a company or a person and you win then you<BR>
+will have a Money Judgment against them.<BR>
+<BR>
+You are happy you won but you will soon find out the<BR>
+shocking fact: "Its now up to you to collect on the<BR>
+Judgment". The court does not require the loser to pay you.<BR>
+The court will not even help you. You must trace the loser<BR>
+down, find their assets, their employment, bank accounts,<BR>
+real estate, stocks and bonds, etc.<BR>
+<BR>
+Very few people know how to find these assets or what to do<BR>
+when they are found. The result is that millions of<BR>
+Judgments are just sitting in files and being forgotten.<BR>
+<BR>
+"In 79% of the cases the winner of a Judgment never sees a<BR>
+dime."<BR>
+<BR>
+The non-payment of judicial debt has grown to epidemic<BR>
+proportions. Right now in the United States there is<BR>
+between </FONT>
+<FONT color=3D"#0000A0"> 200 and 300 billion dollars of uncollected Money<=
+BR>
+Judgment debt</FONT>
+<FONT color=3D"#000000">  . For every Judgment that is paid, 5 more<BR>
+Judgments take its place.<BR>
+<BR>
+We identified this massive market 8 years ago and have<BR>
+actively pursued Judicial Judgments since. We invented this<BR>
+business. We have perfected it into a well proven and solid<BR>
+profession in which only a select few will be trained in the<BR>
+techniques necessary to succeed.<BR>
+<BR>
+With our first hand experience we have built a course which<BR>
+teaches you how to start your business in this new unknown<BR>
+and exciting field of processing Money Judgments.<BR>
+<BR>
+By following the steps laid out in our course and with<BR>
+reasonable effort you can become very successful in the<BR>
+processing of Money Judgments.<BR>
+<BR>
+The income potential is substantial in this profession. </FONT>
+<FONT color=3D"#0000A0"> We<BR>
+have associates who have taken our course and are now<BR>
+working full time making $96,000.00 to over $200,000.00 per<BR>
+year. Part time associates are earning between $24,000.00<BR>
+and $100,000.00 per year </FONT>
+<FONT color=3D"#000000"> . Some choose to operate out of<BR>
+their home and work by themselves. Others build a sizable<BR>
+organization of 15 to 25 people in attractive business<BR>
+offices.<BR>
+<BR>
+Today our company and our associates have over 126<BR>
+million dollars in Money Judgments that we are currently<BR>
+processing. Of this 126 million, 25 million is in the form<BR>
+of joint ventures between our firm and our associates.<BR>
+Joint ventures are where we make our money. We only break<BR>
+even when our course is purchased. We make a 12% margin on<BR>
+the reports we supply to our associates. Our reporting<BR>
+capability is so extensive that government agencies, police<BR>
+officers, attorneys, credit agencies etc., all come to us<BR>
+for reports.<BR>
+<BR>
+<BR>
+Many of our associates already have real estate liens in<BR>
+force of between 5 million to over 15 million dollars.<BR>
+Legally this means that when the properties are sold or<BR>
+refinanced our associate must be paid off. The norm is 10%<BR>
+interest compounded annually on unpaid Money Judgments.<BR>
+Annual interest on 5 million at 10% translates to<BR>
+$500,000.00 annually in interest income, not counting the<BR>
+payment of the principal.<BR>
+<BR>
+Our associates earn half of this amount or $250,000.00 per<BR>
+year. This is just for interest, not counting principle<BR>
+and not counting the compounding of the interest which can<BR>
+add substantial additional income. Typically companies are<BR>
+sold for 10 times earnings. Just based on simple interest<BR>
+an associate with 5 million in real estate liens could sell<BR>
+their business for approximately 2.5 million dollars.<BR>
+<BR>
+</FONT>
+<FONT color=3D"#0000A0"> 92% of all of our associates work out of their ho=
+me; 43%<BR>
+are women and 36% are part time</FONT>
+<FONT color=3D"#000000">  .<BR>
+<BR>
+One of the benefits of working in this field is that you are<BR>
+not under any kind of time frame. If you decide to take off<BR>
+for a month on vacation then go. The Judgments you are<BR>
+working on will be there when you return. The Judgments<BR>
+are still in force, they do not disappear.<BR>
+<BR>
+The way we train you is non-confrontational. You use your<BR>
+computer and telephone to do most of the processing. You<BR>
+never confront the debtor. The debtor doesn't know who you<BR>
+are. You are not a collection agency.<BR>
+<BR>
+Simply stated the steps to successful Money Processing<BR>
+are as follows:<BR>
+<BR>
+Mail our recommended letter to companies and individuals<BR>
+with Money Judgments. (We train you how to find out who<BR>
+to write to)<BR>
+<BR>
+8% to 11% of the firms and people you write will call you<BR>
+and ask for your help. They call you, you don't call them<BR>
+unless you want to.<BR>
+<BR>
+You send them an agreement (supplied in the course) to<BR>
+sign which splits every dollar you collect 50% to you and<BR>
+50% to them. This applies no matter if the judgment is for<BR>
+$2,000.00 or $2,000,000.00.<BR>
+<BR>
+You then go on-line to our computers to find the debtor<BR>
+and their assets. We offer over 120 powerful reports to<BR>
+assist you. They range from credit reports from all three<BR>
+credit bureaus, to bank account locates, employment<BR>
+locates, skip traces and locating stocks and bonds, etc.<BR>
+The prices of our reports are very low. Typically 1/2 to<BR>
+1/3 of what other firms charge. For example we charge<BR>
+$6.00 for an individuals credit report when some other<BR>
+companies charge $25.00.<BR>
+<BR>
+Once you find the debtor and their assets you file<BR>
+garnishments and liens on the assets you have located.<BR>
+(Standard fill in the blanks forms are included in the<BR>
+course)<BR>
+<BR>
+When you receive the assets you keep 50% and send 50% to<BR>
+the original Judgment holder.<BR>
+<BR>
+Once the Judgment is fully paid you mail a Satisfaction of<BR>
+Judgment to the court. (Included in the course)<BR>
+<BR>
+Quote's from several of our students:<BR>
+<BR>
+Thomas in area code 516 writes us: "I just wanted to drop<BR>
+you a short note thanking you for your excellent course. </FONT>
+<FONT color=3D"#0000A0"> My<BR>
+first week, part time, will net me 3,700.00 dollars</FONT>
+<FONT color=3D"#000000">  . Your<BR>
+professionalism in both the manual and the video opened<BR>
+doors for me in the future. There's no stopping me now.<BR>
+Recently Thomas states he has over $8,500,000 worth of<BR>
+judgments he is working on.<BR>
+<BR>
+After only having this course for four months, Larry S. in<BR>
+area code 314 stated to us: " </FONT>
+<FONT color=3D"#0000A0"> I am now making $2,000.00 per<BR>
+week </FONT>
+<FONT color=3D"#000000"> and expect this to grow to twice this amount with=
+in the<BR>
+next year. I am having a ball. I have over $250,000 in<BR>
+judgments I am collecting on now."<BR>
+<BR>
+After having our course for 7 months Larry S. in 314 stated<BR>
+" </FONT>
+<FONT color=3D"#0000A0"> I am now making $12,000.00</FONT>
+<FONT color=3D"#000000">  per month and have approximately<BR>
+$500,000.00 in judgments I am collecting on. Looks like I<BR>
+will have to hire someone to help out"<BR>
+<BR>
+Marshal in area code 407 states to us "I feel bad, you only<BR>
+charged me $259.00 for this course and it is a goldmine. I<BR>
+have added 3 full time people to help me after only having<BR>
+your course for 5 months"<BR>
+<BR>
+>From the above information and actual results you can see<BR>
+why we can state the following:<BR>
+<BR>
+With our course you can own your own successful business.<BR>
+A business which earns you substantial income now and one<BR>
+which could be sold in 3-5 years, paying you enough to<BR>
+retire on and travel the world. A business which is<BR>
+extremely interesting to be in. A Business in which every<BR>
+day is new and exciting.<BR>
+<BR>
+None of your days will be hum-drum. Your brain is<BR>
+Challenged. A business, which protects you from Corporate<BR>
+Downsizing. A business which you can start part time from<BR>
+your home and later, if you so desire, you can work in full<BR>
+time. A business, which is your ticket to freedom from<BR>
+others telling you what to do. A business, which lets you<BR>
+control your own destiny. Our training has made this happen<BR>
+for many others already. Make it happen for you!<BR>
+<BR>
+If the above sounds interesting to you then its time for you<BR>
+to talk to a real live human being, no cost or obligation<BR>
+on your part.<BR>
+<BR>
+</FONT>
+<FONT color=3D"#800040"> Please call us at 1_406_652_0194 </FONT>
+<FONT color=3D"#000000"> .<BR>
+<BR>
+We have </FONT>
+<FONT color=3D"#800040"> Customer Support staff available to you from 8:00=
+am to<BR>
+9:00pm (Mountain Time) 7 days a week</FONT>
+<FONT color=3D"#000000">  . If you call this number<BR>
+you can talk to one of our experienced Customer Support personnel.<BR>
+They can answer any questions you may have - with no obligation.<BR>
+Sometimes we run special pricing on our courses and combinations<BR>
+of courses. When you call our Customer Support line they can let<BR>
+you know of any specials we may be running. If you like what you<BR>
+read and hear about our courses, then the Customer Support person<BR>
+can work with you to place your order. We are very low key. We<BR>
+merely give you the facts and you can then decide if you want to<BR>
+work with us or not.<BR>
+<BR>
+Thank you for your time and interest.<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+</FONT>
+<FONT face=3D"Courier New">
+<FONT size=3D2>
+<FONT color=3D"#000000"> .................................................=
+....................<BR>
+T h i s message i s produced a n d sent out by:<BR>
+Universal S.y.s.t.e.m.s.<BR>
+To be dropped form our mailing list please email us at <BR>
[email protected] call us toll free at 1=3D888=3D605=3D2485<BR>
+and  g i v e us y o u r email a d d r e s s  or w r i t e  us a t:<BR>
+*Central*DB*Processing, PO: Box:1200, O r a n j e s t a d, Aruba<BR>
+.....................................................................<BR>
+</FONT>
+<FONT face=3D"Arial">
+<FONT size=3D3>
+<FONT color=3D"#000000"> <BR>
+<BR>
+</FONT>
+<FONT size=3D3>
+<FONT color=3D"#000000"> <BR>
+<BR>
+</FONT>
+<FONT size=3D3>
+<FONT color=3D"#000000"> <BR>
+<BR>
+<BR>
+<BR>
+<BR>
+</FONT>
+<FONT size=3D2>
+<FONT color=3D"#000000"> <BR>
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''=
+'''''''''''<BR>
+</FONT>
+<FONT size=3D3>
+<FONT color=3D"#000000"> <BR>
+</FONT></FONT></FONT></FONT></FONT></FONT></FONT></FONT></FONT></FONT></FO=
+NT></FONT></FONT></BODY></HTML>
+
+
+
+
diff --git a/third-party/rspamd/src/test/resources/rspamd-config/actions.conf 
b/third-party/rspamd/src/test/resources/rspamd-config/actions.conf
new file mode 100644
index 0000000000..1eb9709077
--- /dev/null
+++ b/third-party/rspamd/src/test/resources/rspamd-config/actions.conf
@@ -0,0 +1,29 @@
+# Actions settings
+# Please don't modify this file as your changes might be overwritten with
+# the next update.
+#
+# You can modify 'local.d/actions.conf' to add and merge
+# parameters defined inside this section
+#
+# You can modify 'override.d/actions.conf' to strictly override all
+# parameters defined inside this section
+#
+# See 
https://rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories
+# for details
+#
+# See also https://rspamd.com/doc/faq.html#what-are-rspamd-actions for actions 
definition
+
+actions {
+    reject = 14; # Reject when reaching this score
+    add_header = 6; # Add header when reaching this score
+    greylist = 4; # Apply greylisting when reaching this score (will emit 
`soft reject action`)
+
+    #unknown_weight = 1.0; # Enable if need to set score for all symbols 
implicitly
+    # Each new symbol is added multiplied by gf^N, where N is the number of 
spammy symbols
+    #grow_factor = 1.1;
+    # Set rewrite subject to this value (%s is replaced by the original 
subject)
+    #subject = "***SPAM*** %s"
+
+    .include(try=true; priority=1; duplicate=merge) 
"$LOCAL_CONFDIR/local.d/actions.conf"
+    .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/actions.conf"
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to