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]