[prev in list] [next in list] [prev in thread] [next in thread]
List: james-dev
Subject: [01/13] james-project git commit: JAMES-2262 Add unit testing for SpamAssassin
From: btellier () apache ! org
Date: 2017-12-25 4:26:45
Message-ID: 51ad6c1827de4d8392e05105ec578d6a () git ! apache ! org
[Download RAW message or body]
Repository: james-project
Updated Branches:
refs/heads/master e0ad36d00 -> dfd3f420b
JAMES-2262 Add unit testing for SpamAssassin
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4d9d568c
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4d9d568c
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4d9d568c
Branch: refs/heads/master
Commit: 4d9d568c2ea640740ee2c0f88c87156e4aa5d5ff
Parents: e0ad36d
Author: quynhn <qnguyen@linagora.com>
Authored: Thu Dec 14 16:53:53 2017 +0700
Committer: benwa <btellier@linagora.com>
Committed: Mon Dec 25 11:24:08 2017 +0700
----------------------------------------------------------------------
.../transport/mailets/SpamAssassinTest.java | 167 ++++++++++++++
server/mailet/mailets/pom.xml | 13 ++
.../james/transport/mailets/SpamAssassin.java | 47 ++--
.../transport/mailets/SpamAssassinTest.java | 215 +++++++++++++++++++
4 files changed, 425 insertions(+), 17 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/james-project/blob/4d9d568c/server/mailet/integ \
ration-testing/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java
----------------------------------------------------------------------
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java \
b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java
new file mode 100644
index 0000000..34c9a36
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.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.transport.mailets;
+
+import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.jmap.mailet.VacationMailet;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.transport.mailets.amqp.AmqpRule;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.transport.matchers.RecipientIsLocal;
+import org.apache.james.transport.matchers.SMTPAuthSuccessful;
+import org.apache.james.util.streams.ContainerNames;
+import org.apache.james.util.streams.SwarmGenericContainer;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.IMAPMessageReader;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.MimeMessageBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import com.jayway.awaitility.Awaitility;
+import com.jayway.awaitility.Duration;
+
+public class ContactExtractorTest {
+
+ public static final String JAMES_ORG = "james.org";
+ public static final String SENDER = "sender@" + JAMES_ORG;
+ public static final String TO = "to@" + JAMES_ORG;
+ public static final String TO2 = "to2@" + JAMES_ORG;
+ public static final String CC = "cc@" + JAMES_ORG;
+ public static final String CC2 = "cc2@" + JAMES_ORG;
+ public static final String BCC = "bcc@" + JAMES_ORG;
+ public static final String BCC2 = "bcc2@" + JAMES_ORG;
+ public static final String PASSWORD = "secret";
+ public static final String EXCHANGE = "collector:email";
+ public static final String ROUTING_KEY = "";
+
+ public SwarmGenericContainer rabbit = new \
SwarmGenericContainer(ContainerNames.RABBITMQ); + public AmqpRule amqpRule = new \
AmqpRule(rabbit, EXCHANGE, ROUTING_KEY); + public TemporaryFolder folder = new \
TemporaryFolder(); +
+ @Rule
+ public RuleChain chain = \
RuleChain.outerRule(rabbit).around(amqpRule).around(folder); +
+ private TemporaryJamesServer jamesServer;
+
+ @Before
+ public void setup() throws Exception {
+ String attribute = "ExtractedContacts";
+ MailetContainer mailets = MailetContainer
+ .builder()
+ .threads(5)
+ .postmaster(SENDER)
+ .addProcessor(CommonProcessors.root())
+ .addProcessor(CommonProcessors.error())
+ .addProcessor(
+ ProcessorConfiguration.builder()
+ .state("transport")
+ .addMailet(MailetConfiguration.builder()
+ .matcher(SMTPAuthSuccessful.class)
+ .mailet(ContactExtractor.class)
+ .addProperty(ContactExtractor.Configuration.ATTRIBUTE, \
attribute) + .build())
+ .addMailet(MailetConfiguration.builder()
+ .matcher(All.class)
+ .mailet(AmqpForwardAttribute.class)
+ .addProperty(AmqpForwardAttribute.URI_PARAMETER_NAME, \
amqpRule.getAmqpUri()) + \
.addProperty(AmqpForwardAttribute.EXCHANGE_PARAMETER_NAME, EXCHANGE) + \
.addProperty(AmqpForwardAttribute.ATTRIBUTE_PARAMETER_NAME, attribute) + \
.build()) + .addMailet(MailetConfiguration.builder()
+ .matcher(All.class)
+ .mailet(RemoveMimeHeader.class)
+ .addProperty("name", "bcc")
+ .build())
+ .addMailet(MailetConfiguration.builder()
+ .matcher(RecipientIsLocal.class)
+ .mailet(VacationMailet.class)
+ .build())
+ .addMailet(MailetConfiguration.builder()
+ .matcher(RecipientIsLocal.class)
+ .mailet(LocalDelivery.class)
+ .build())
+ .build())
+ .build();
+ jamesServer = TemporaryJamesServer.builder().build(folder, mailets);
+ DataProbeImpl probe = jamesServer.getProbe(DataProbeImpl.class);
+ probe.addDomain(JAMES_ORG);
+ probe.addUser(SENDER, PASSWORD);
+ probe.addUser(TO, PASSWORD);
+ probe.addUser(TO2, PASSWORD);
+ probe.addUser(CC, PASSWORD);
+ probe.addUser(CC2, PASSWORD);
+ probe.addUser(BCC, PASSWORD);
+ probe.addUser(BCC2, PASSWORD);
+ }
+
+ @After
+ public void tearDown() {
+ jamesServer.shutdown();
+ }
+
+ @Test
+ public void recipientsShouldBePublishedToAmqpWhenSendingEmail() throws Exception \
{ + MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+ .setSender(SENDER)
+ .addToRecipient(TO, "John To2 <" + TO2 + ">")
+ .addCcRecipient(CC, "John Cc2 <" + CC2 + ">")
+ .addBccRecipient(BCC, "John Bcc2 <" + BCC2 + ">")
+ .setSubject("Contact collection Rocks")
+ .setText("This is my email")
+ .build();
+ FakeMail mail = FakeMail.builder()
+ .mimeMessage(message)
+ .sender(new MailAddress(SENDER))
+ .recipients(new MailAddress(TO), new MailAddress(TO2), new \
MailAddress(CC), new MailAddress(CC2), new MailAddress(BCC), new MailAddress(BCC2)) + \
.build(); + try (SMTPMessageSender messageSender = \
SMTPMessageSender.authentication("localhost", 1025, JAMES_ORG, SENDER, PASSWORD); + \
IMAPMessageReader imap = new IMAPMessageReader("localhost", 1143)) { +
+ messageSender.sendMessage(mail);
+ Awaitility.await().pollDelay(Duration.FIVE_HUNDRED_MILLISECONDS)
+ .atMost(Duration.ONE_MINUTE)
+ .until(() -> imap.userReceivedMessage(TO, PASSWORD));
+
+ Optional<String> actual = amqpRule.readContent();
+ assertThat(actual).isNotEmpty();
+ assertThatJson(actual.get()).isEqualTo("{"
+ + "\"userEmail\" : \"sender@james.org\", "
+ + "\"emails\" : [ \"to@james.org\", \"John To2 \
<to2@james.org>\", \"cc@james.org\", \"John Cc2 <cc2@james.org>\", \"bcc@james.org\", \
\"John Bcc2 <bcc2@james.org>\" ]" + + "}");
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/james-project/blob/4d9d568c/server/mailet/mailets/pom.xml
----------------------------------------------------------------------
diff --git a/server/mailet/mailets/pom.xml b/server/mailet/mailets/pom.xml
index 9f81107..a8f409c 100644
--- a/server/mailet/mailets/pom.xml
+++ b/server/mailet/mailets/pom.xml
@@ -135,6 +135,19 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>james-server-protocols-library</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>james-server-protocols-smtp</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>james-server-queue-api</artifactId>
</dependency>
<dependency>
http://git-wip-us.apache.org/repos/asf/james-project/blob/4d9d568c/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SpamAssassin.java
----------------------------------------------------------------------
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SpamAssassin.java \
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SpamAssassin.java
index 970bcf1..1b3de01 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SpamAssassin.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SpamAssassin.java
@@ -19,6 +19,8 @@
package org.apache.james.transport.mailets;
+import java.util.Optional;
+
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
@@ -27,9 +29,13 @@ import org.apache.james.util.scanner.SpamAssassinInvoker;
import org.apache.mailet.Experimental;
import org.apache.mailet.Mail;
import org.apache.mailet.base.GenericMailet;
+import org.apache.mailet.base.MailetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+
/**
* Sends the message through daemonized SpamAssassin (spamd), visit <a
* href="http://spamassassin.apache.org/">spamassassin.apache.org/</a> for info
@@ -57,29 +63,26 @@ import org.slf4j.LoggerFactory;
@Experimental
public class SpamAssassin extends GenericMailet {
private static final Logger LOGGER = \
LoggerFactory.getLogger(ManageSieveMailet.class); + public static final String \
SPAMD_HOST = "spamdHost"; + public static final String SPAMD_PORT = "spamdPort";
+ public static final String DEFAULT_HOST = "127.0.0.1";
+ public static final int DEFAULT_PORT = 783;
+ public static final int MAX_AVAILABLE_PORT = 65535;
- String spamdHost;
- int spamdPort;
+ private String spamdHost;
+ private int spamdPort;
/**
* @see org.apache.mailet.base.GenericMailet#init()
*/
public void init() throws MessagingException {
- spamdHost = getInitParameter("spamdHost");
- if (spamdHost == null || spamdHost.equals("")) {
- spamdHost = "127.0.0.1";
- }
+ spamdHost = Optional.ofNullable(getInitParameter(SPAMD_HOST))
+ .filter(s -> !Strings.isNullOrEmpty(s))
+ .orElse(DEFAULT_HOST);
- String port = getInitParameter("spamdPort");
- if (port == null || port.equals("")) {
- spamdPort = 783;
- } else {
-
- try {
- spamdPort = Integer.parseInt(getInitParameter("spamdPort"));
- } catch (NumberFormatException e) {
- throw new MessagingException("Please configure a valid port. Not \
valid: " + spamdPort);
- }
+ spamdPort = \
MailetUtil.getInitParameterAsStrictlyPositiveInteger(getInitParameter(SPAMD_PORT), \
DEFAULT_PORT); + if (spamdPort > MAX_AVAILABLE_PORT) {
+ throw new MessagingException("Please configure a valid port. Not valid: \
" + spamdPort); }
}
@@ -90,7 +93,7 @@ public class SpamAssassin extends GenericMailet {
try {
MimeMessage message = mail.getMessage();
- // Invoke spamassian connection and scan the message
+ // Invoke SpamAssassin connection and scan the message
SpamAssassinInvoker sa = new SpamAssassinInvoker(spamdHost, spamdPort);
sa.scanMail(message);
@@ -112,4 +115,14 @@ public class SpamAssassin extends GenericMailet {
public String getMailetInfo() {
return "Checks message against SpamAssassin";
}
+
+ @VisibleForTesting
+ String getSpamdHost() {
+ return spamdHost;
+ }
+
+ @VisibleForTesting
+ int getSpamdPort() {
+ return spamdPort;
+ }
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/4d9d568c/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java
----------------------------------------------------------------------
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java \
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java
new file mode 100644
index 0000000..e49d981
--- /dev/null
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SpamAssassinTest.java
@@ -0,0 +1,215 @@
+/****************************************************************
+ * 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.transport.mailets;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.protocols.lib.PortUtil;
+import org.apache.james.smtpserver.mock.util.MockSpamd;
+import org.apache.james.util.scanner.SpamAssassinInvoker;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.apache.mailet.base.test.MimeMessageBuilder;
+import org.junit.Test;
+
+public class SpamAssassinTest {
+ private SpamAssassin mailet = new SpamAssassin();
+
+ @Test
+ public void initShouldSetDefaultSpamdHostWhenNone() throws Exception {
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .build());
+
+ assertThat(mailet.getSpamdHost()).isEqualTo(SpamAssassin.DEFAULT_HOST);
+ }
+
+ @Test
+ public void initShouldSetDefaultSpamdPortWhenNone() throws Exception {
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .build());
+
+ assertThat(mailet.getSpamdPort()).isEqualTo(SpamAssassin.DEFAULT_PORT);
+ }
+
+ @Test
+ public void initShouldSetSpamdHostWhenPresent() throws Exception {
+ String spamdHost = "any.host";
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_HOST, spamdHost)
+ .build());
+
+ assertThat(mailet.getSpamdHost()).isEqualTo(spamdHost);
+ }
+
+ @Test
+ public void getSpamHostShouldReturnDefaultValueWhenEmpty() throws Exception {
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_HOST, "")
+ .build());
+
+ assertThat(mailet.getSpamdHost()).isEqualTo(SpamAssassin.DEFAULT_HOST);
+ }
+
+ @Test
+ public void initShouldSetDefaultSpamdPortWhenDefault() throws Exception {
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .build());
+
+ assertThat(mailet.getSpamdPort()).isEqualTo(SpamAssassin.DEFAULT_PORT);
+ }
+
+ @Test
+ public void initShouldThrowWhenSpamdPortIsNotNumber() throws Exception {
+ assertThatThrownBy(() -> mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_PORT, "noNumber")
+ .build())).isInstanceOf(MessagingException.class);
+ }
+
+ @Test
+ public void initShouldThrowWhenSpamdPortIsNegative() throws Exception {
+ assertThatThrownBy(() -> mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_PORT, "-1")
+ .build())).isInstanceOf(MessagingException.class);
+ }
+
+ @Test
+ public void initShouldThrowWhenSpamdPortIsZero() throws Exception {
+ assertThatThrownBy(() -> mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_PORT, "0")
+ .build())).isInstanceOf(MessagingException.class);
+ }
+
+ @Test
+ public void initShouldThrowWhenSpamdPortTooBig() throws Exception {
+ assertThatThrownBy(() -> mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_PORT,
+ String.valueOf(SpamAssassin.MAX_AVAILABLE_PORT + 1))
+ .build())).isInstanceOf(MessagingException.class);
+ }
+
+ @Test
+ public void initShouldSetSpamPortWhenPresent() throws Exception {
+ int spamPort = 1000;
+ mailet.init(FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_PORT, String.valueOf(spamPort))
+ .build());
+
+ assertThat(mailet.getSpamdPort()).isEqualTo(spamPort);
+ }
+
+ @Test
+ public void serviceShouldWriteSpamAttributeOnMail() throws Exception {
+ int port = PortUtil.getNonPrivilegedPort();
+ new Thread(new MockSpamd(port)).start();
+
+ FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_HOST, "localhost")
+ .setProperty(SpamAssassin.SPAMD_PORT, String.valueOf(port))
+ .build();
+ mailet.init(mailetConfiguration);
+
+ Mail mail = FakeMail.builder()
+ .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+ .addToRecipient("user1@exemple.com")
+ .addFrom("sender@exemple.com")
+ .setSubject("testing")
+ .setText("Please!")
+ .build())
+ .build();
+
+ mailet.service(mail);
+
+ assertThat(mail.getAttributeNames())
+ .containsOnly(SpamAssassinInvoker.FLAG_MAIL_ATTRIBUTE_NAME, \
SpamAssassinInvoker.STATUS_MAIL_ATTRIBUTE_NAME); + }
+
+ @Test
+ public void serviceShouldWriteMessageAsNotSpamWhenNotSpam() throws Exception {
+ int port = PortUtil.getNonPrivilegedPort();
+ new Thread(new MockSpamd(port)).start();
+
+ FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_HOST, "localhost")
+ .setProperty(SpamAssassin.SPAMD_PORT, String.valueOf(port))
+ .build();
+ mailet.init(mailetConfiguration);
+
+ Mail mail = FakeMail.builder()
+ .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+ .addToRecipient("user1@exemple.com")
+ .addFrom("sender@exemple.com")
+ .setSubject("testing")
+ .setText("Please!")
+ .build())
+ .build();
+
+ mailet.service(mail);
+
+ assertThat(mail.getAttribute(SpamAssassinInvoker.FLAG_MAIL_ATTRIBUTE_NAME)).isEqualTo("NO");
+ }
+
+ @Test
+ public void serviceShouldWriteMessageAsSpamWhenSpam() throws Exception {
+ int port = PortUtil.getNonPrivilegedPort();
+ new Thread(new MockSpamd(port)).start();
+
+ FakeMailetConfig mailetConfiguration = FakeMailetConfig.builder()
+ .mailetName("SpamAssassin")
+ .setProperty(SpamAssassin.SPAMD_HOST, "localhost")
+ .setProperty(SpamAssassin.SPAMD_PORT, String.valueOf(port))
+ .build();
+ mailet.init(mailetConfiguration);
+
+ Mail mail = FakeMail.builder()
+ .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+ .addToRecipient("user1@exemple.com")
+ .addFrom("sender@exemple.com")
+ .setSubject(MockSpamd.GTUBE + " testing")
+ .setText("Please!")
+ .build())
+ .build();
+
+ mailet.service(mail);
+
+ assertThat(mail.getAttribute(SpamAssassinInvoker.FLAG_MAIL_ATTRIBUTE_NAME)).isEqualTo("YES");
+ }
+
+ @Test
+ public void getMailetInfoShouldReturnSpamAssasinMailetInformation() throws \
Exception { + assertThat(mailet.getMailetInfo()).isEqualTo("Checks message \
against SpamAssassin"); + }
+
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic