diff --git a/manifest.yml b/manifest.yml
new file mode 100644
index 0000000..f4e211b
--- /dev/null
+++ b/manifest.yml
@@ -0,0 +1,16 @@
+---
+applications:
+- name: direct-james-server
+ health-check-type: none
+ instances: 1
+ buildpack: java_buildpack
+ path: target/direct-james-server-6.0-SNAPSHOT.jar
+ timeout: 120
+ env:
+ spring.cloud.config.label: master
+ JBP_CONFIG_DEBUG: '{enabled: true}'
+ JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}'
+ services:
+ - directhisp-config-server
+ - directhisp-eureka
+ - directhisp-rabbit
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..dc1d6fd
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,459 @@
+
+
+ 4.0.0
+ org.nhind
+ direct-james-server
+ 6.0-SNAPSHOT
+ jar
+ Direct Java RI James Spring Boot Application
+ irect Java RI James Spring Boot Application
+ http://api.nhindirect.org/x/www/api.nhindirect.org/java/site/config/direct-james-server/${project.version}
+
+ scm:git:https://github.com/DirectProject/direct-james-server.git
+ scm:git:https://github.com/DirectProject/direct-james-server.git
+
+
+ 3.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+
+
+ Greg Meyer
+ GM2552
+ gm2552@cerner.com
+
+ owner
+
+
+
+
+
+
+ New BSD License
+ http://nhindirect.org/BSDLicense
+
+
+
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.1.3.RELEASE
+ pom
+ import
+
+
+ io.pivotal.spring.cloud
+ spring-cloud-services-dependencies
+ 2.1.1.RELEASE
+ pom
+ import
+
+
+ org.springframework.cloud
+ spring-cloud-starter-parent
+ Greenwich.SR1
+ pom
+ import
+
+
+ io.dropwizard.metrics
+ metrics-core
+ 3.2.6
+
+
+ io.dropwizard.metrics
+ metrics-jvm
+ 3.2.6
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.cloud
+ spring-cloud-starter-config
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ com.google.inject
+ guice
+ 4.2.2
+
+
+ io.pivotal.spring.cloud
+ spring-cloud-services-starter-config-client
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream
+
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-rabbit
+
+
+ org.nhind
+ config-service-client
+ 6.0
+
+
+
+ org.nhind
+ gateway
+ 6.0.2
+
+
+ org.nhind
+ direct-common-audit
+
+
+ org.apache.james
+ *
+
+
+
+
+
+ org.apache.james
+ james-server-jpa-guice
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-common
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-imap
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-lmtp
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-managedsieve
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-smtp
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-pop
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-mailbox
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-activemq
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-activemq
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-es-resporter
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-mailbox-plugin-spamassassin
+ 3.2.0
+
+
+ org.apache.james
+ james-server-jpa-common-guice
+ 3.2.0
+
+
+ org.apache.james
+ metrics-dropwizard
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin-data
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin-mailbox
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin-mailqueue
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin-mailrepository
+ 3.2.0
+
+
+ org.apache.james
+ james-server-guice-webadmin-swagger
+ 3.2.0
+
+
+
+ com.h2database
+ h2
+ 1.4.194
+ runtime
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ runtime
+
+
+ org.apache.derby
+ derby
+ 10.14.2.0
+ runtime
+
+
+ mysql
+ mysql-connector-java
+ 5.1.47
+ runtime
+
+
+ org.postgresql
+ postgresql
+ 42.2.5
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.0.1
+
+
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ testCompile
+
+ compile
+
+
+
+ true
+ true
+ true
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+ com.atlassian.maven.plugins
+ maven-clover2-plugin
+ 3.0.2
+
+ 1.8
+ ${project.basedir}/../../licenses/clover.license
+
+
+
+ pre-site
+
+ instrument
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 2.9
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.0.1
+
+ UTF-8
+ UTF-8
+ true
+ true
+ true
+ 1.8
+ protected
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 1.2
+
+ Max
+
+
+
+ org.codehaus.mojo
+ taglist-maven-plugin
+
+
+ FIXME
+ TODO
+ WARN
+ @deprecated
+
+
+
+
+ com.atlassian.maven.plugins
+ maven-clover2-plugin
+ 3.0.2
+
+ ${project.basedir}/../../licenses/clover.license
+
+
+
+
+
+
+ nhind-site
+ NHIN Direct API publication site
+ sftp://api.nhindirect.org/x/www/api.nhindirect.org/java/site/direct-james-server/${project.version}
+
+
+ sonatype-snapshot
+ Sonatype OSS Maven SNAPSHOT Repository
+ https://oss.sonatype.org/content/repositories/snapshots/
+ false
+
+
+ sonatype-release
+ Sonatype OSS Maven Release Repositor
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+ false
+
+
+
+
diff --git a/src/main/java/org/nhindirect/james/server/authfilter/WebAdminBasicAuthFilter.java b/src/main/java/org/nhindirect/james/server/authfilter/WebAdminBasicAuthFilter.java
new file mode 100644
index 0000000..3131baf
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/authfilter/WebAdminBasicAuthFilter.java
@@ -0,0 +1,87 @@
+package org.nhindirect.james.server.authfilter;
+
+import static spark.Spark.halt;
+
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.james.webadmin.authentication.AuthenticationFilter;
+import org.eclipse.jetty.http.HttpStatus;
+
+import spark.Request;
+import spark.Response;
+
+public class WebAdminBasicAuthFilter implements AuthenticationFilter
+{
+ public static final String AUTHORIZATION_HEADER_NAME = "Authorization";
+ public static final String AUTHORIZATION_HEADER_PREFIX = "Basic ";
+ public static final String OPTIONS = "OPTIONS";
+
+ protected final String user;
+ protected final byte[] passHash;
+ protected final MessageDigest digest;
+
+ public WebAdminBasicAuthFilter(String user, String pass)
+ {
+ try
+ {
+ digest = MessageDigest.getInstance("SHA-256");
+
+ this.user = user;
+ this.passHash = digest.digest(pass.getBytes(Charset.defaultCharset()));
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Error creating webadmin password hash: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception
+ {
+ if (request.requestMethod() != OPTIONS)
+ {
+ Optional basicAuth = Optional.ofNullable(request.headers(AUTHORIZATION_HEADER_NAME))
+ .filter(value -> value.startsWith(AUTHORIZATION_HEADER_PREFIX))
+ .map(value -> value.substring(AUTHORIZATION_HEADER_PREFIX.length()));
+
+ checkHeaderPresent(basicAuth);
+
+ final String[] subjectAndSecret = new String(Base64.decodeBase64(basicAuth.get()), Charset.defaultCharset()).split(":");
+ final String user = subjectAndSecret[0];
+ final String pass = subjectAndSecret[1];
+
+ checkValidPass(pass);
+ checkIsAdmin(user);
+ }
+ }
+
+ private void checkHeaderPresent(Optional basicAuth)
+ {
+ if (!basicAuth.isPresent())
+ {
+ halt(HttpStatus.UNAUTHORIZED_401, "No Basic Auth header.");
+ }
+ }
+
+ private void checkIsAdmin(String user)
+ {
+ if (user.compareToIgnoreCase(this.user) != 0)
+ {
+ halt(HttpStatus.UNAUTHORIZED_401, "Non authorized user.");
+ }
+ }
+
+ private void checkValidPass(String pass)
+ {
+ final byte[] passDigest = digest.digest(pass.getBytes(Charset.defaultCharset()));
+
+ if (!Objects.deepEquals(passDigest, this.passHash))
+ {
+ halt(HttpStatus.UNAUTHORIZED_401, "Invalid login.");
+ }
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/boot/JamesServerApplication.java b/src/main/java/org/nhindirect/james/server/boot/JamesServerApplication.java
new file mode 100644
index 0000000..2b2294a
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/boot/JamesServerApplication.java
@@ -0,0 +1,29 @@
+package org.nhindirect.james.server.boot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan({"org.nhindirect.james.server.spring", "org.nhindirect.james.server.streams"})
+@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
+@EnableEurekaClient
+public class JamesServerApplication
+{
+ public static void main(String[] args)
+ {
+ SpringApplication springApplication =
+ new SpringApplicationBuilder()
+ .sources(JamesServerApplication.class)
+ .web(WebApplicationType.NONE)
+ .build();
+
+ springApplication.run(args);
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/mailets/MailUtils.java b/src/main/java/org/nhindirect/james/server/mailets/MailUtils.java
new file mode 100644
index 0000000..080c239
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/mailets/MailUtils.java
@@ -0,0 +1,127 @@
+package org.nhindirect.james.server.mailets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.Mail;
+import org.nhindirect.common.mail.SMTPMailMessage;
+import org.nhindirect.common.tx.TxDetailParser;
+import org.nhindirect.common.tx.impl.DefaultTxDetailParser;
+import org.nhindirect.common.tx.model.Tx;
+import org.nhindirect.gateway.smtp.dsn.DSNCreator;
+import org.nhindirect.gateway.util.MessageUtils;
+import org.nhindirect.james.server.spring.DSNCreatorFactory;
+import org.nhindirect.james.server.streams.SmtpGatewayMessageSource;
+import org.nhindirect.stagent.NHINDAddress;
+import org.nhindirect.stagent.NHINDAddressCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+public class MailUtils
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(MailUtils.class);
+
+ protected static TxDetailParser txParser = new DefaultTxDetailParser();
+
+ /**
+ * Converts an Apache James Mail message to the common SMTPMailMessage object;
+ * @param mail The Apache James smtp message
+ * @return An SMTPMailMessage message instance container information from the Apache James mail object;
+ */
+ public static SMTPMailMessage mailToSMTPMailMessage(Mail mail) throws MessagingException
+ {
+ if (mail == null)
+ return null;
+
+ List toAddrs = new ArrayList<>();
+ final InternetAddress fromAddr = (mail.getSender() == null) ? null : mail.getSender().toInternetAddress();
+ // uses the RCPT TO commands
+ final Collection recips = mail.getRecipients();
+ if (recips == null || recips.size() == 0)
+ {
+ // fall back to the mime message list of recipients
+ final Address[] recipsAddr = mail.getMessage().getAllRecipients();
+ for (Address addr : recipsAddr)
+ toAddrs.add((InternetAddress)addr);
+ }
+ else
+ {
+ toAddrs = recips.stream().
+ map(toAddr -> toAddr.toInternetAddress()).collect(Collectors.toList());
+
+ }
+
+ return new SMTPMailMessage(mail.getMessage(), toAddrs, fromAddr);
+ }
+
+ /**
+ * Creates a trackable monitoring object for a message.
+ * @param msg The message that is being processed
+ * @param sender The sender of the message
+ * @return A trackable Tx object.
+ */
+ protected static Tx getTxToTrack(MimeMessage msg, NHINDAddress sender, NHINDAddressCollection recipients)
+ {
+ return MessageUtils.getTxToTrack(msg, sender, recipients, txParser);
+ }
+
+ protected static void sendDSN(Tx tx, NHINDAddressCollection undeliveredRecipeints, boolean useSenderAsPostmaster)
+ {
+ try
+ {
+ DSNCreator dsnCreator = DSNCreatorFactory.getFailedDeliverDSNCreator();
+ if (dsnCreator != null)
+ {
+ final Collection msgs = dsnCreator.createDSNFailure(tx, undeliveredRecipeints, useSenderAsPostmaster);
+ if (msgs != null && msgs.size() > 0)
+ for (MimeMessage msg : msgs)
+ sendMessageToStream(msg);
+ }
+ }
+ catch (Throwable e)
+ {
+ // don't kill the process if this fails
+ LOGGER.error("Error sending DSN failure message.", e);
+ }
+ }
+
+ protected static void sendMessageToStream(MimeMessage msg) throws Exception
+ {
+ final ImmutableList recips = Arrays.asList(msg.getAllRecipients()).stream()
+ .map(Throwing.function(MailUtils::castToMailAddress).sneakyThrow())
+ .collect(Guavate.toImmutableList());
+
+ final Mail mail = MailImpl.builder()
+ .sender(castToMailAddress(msg.getFrom()[0]))
+ .recipients(recips)
+ .mimeMessage(msg).build();
+
+ final SmtpGatewayMessageSource messageSource = SmtpGatewayMessageSource.getMessageSourceInstance();
+ if (messageSource != null)
+ {
+ messageSource.forwardSMTPMessage(mailToSMTPMailMessage(mail));
+ }
+ }
+
+ public static MailAddress castToMailAddress(Address address) throws AddressException
+ {
+ Preconditions.checkArgument(address instanceof InternetAddress);
+ return new MailAddress((InternetAddress) address);
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/mailets/StreamsTimelyAndReliableLocalDelivery.java b/src/main/java/org/nhindirect/james/server/mailets/StreamsTimelyAndReliableLocalDelivery.java
new file mode 100644
index 0000000..2c25d5a
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/mailets/StreamsTimelyAndReliableLocalDelivery.java
@@ -0,0 +1,140 @@
+package org.nhindirect.james.server.mailets;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.transport.mailets.LocalDelivery;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.mailet.Mail;
+import org.apache.mailet.Mailet;
+import org.nhindirect.common.mail.SMTPMailMessage;
+import org.nhindirect.common.tx.TxUtil;
+import org.nhindirect.common.tx.model.Tx;
+import org.nhindirect.common.tx.model.TxMessageType;
+import org.nhindirect.gateway.smtp.NotificationProducer;
+import org.nhindirect.gateway.smtp.NotificationSettings;
+import org.nhindirect.gateway.smtp.ReliableDispatchedNotificationProducer;
+import org.nhindirect.gateway.util.MessageUtils;
+import org.nhindirect.stagent.NHINDAddress;
+import org.nhindirect.stagent.NHINDAddressCollection;
+import org.nhindirect.stagent.mail.Message;
+import org.nhindirect.stagent.mail.notifications.NotificationMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StreamsTimelyAndReliableLocalDelivery extends LocalDelivery//TimelyAndReliableLocalDelivery
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StreamsTimelyAndReliableLocalDelivery.class);
+
+ protected static final String DISPATCHED_MDN_DELAY = "DispatchedMDNDelay";
+
+ protected static StreamsTimelyAndReliableLocalDelivery mailetContextInstance;
+
+ protected NotificationProducer notificationProducer;
+
+ @Inject
+ public StreamsTimelyAndReliableLocalDelivery(UsersRepository usersRepository, MailboxManager mailboxManager,
+ MetricFactory metricFactory)
+ {
+ super(usersRepository, mailboxManager, metricFactory);
+ /*
+ * Set the static reference used by the Spring Cloud streams processor (i.e. the processLastMileMessage() method).
+ * Once this instance has been set, then notify any thread that is blocked.
+ */
+ synchronized(StreamsTimelyAndReliableLocalDelivery.class)
+ {
+ if (usersRepository != null)
+ {
+ mailetContextInstance = this;
+ StreamsTimelyAndReliableLocalDelivery.class.notifyAll();
+ }
+ }
+
+ notificationProducer = new ReliableDispatchedNotificationProducer(new NotificationSettings(true, "Local Direct Delivery Agent", "Your message was successfully dispatched."));
+ }
+
+ public static Mailet getStaticMailet()
+ {
+ synchronized(StreamsTimelyAndReliableLocalDelivery.class)
+ {
+ return mailetContextInstance;
+ }
+ }
+
+ @Override
+ public void service(Mail mail) throws MessagingException
+ {
+ LOGGER.debug("Calling timely and reliable service method.");
+
+ boolean deliverySuccessful = false;
+
+ final MimeMessage msg = mail.getMessage();
+ final boolean isReliableAndTimely = TxUtil.isReliableAndTimelyRequested(msg);
+
+ final SMTPMailMessage smtpMailMessage = MailUtils.mailToSMTPMailMessage(mail);
+
+ final NHINDAddressCollection recipients = MessageUtils.getMailRecipients(smtpMailMessage);
+
+ final NHINDAddress sender = MessageUtils.getMailSender(smtpMailMessage);
+
+
+ try
+ {
+ super.service(mail);
+ deliverySuccessful = true;
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Failed to deliver mail locally.", e);
+ }
+
+ final Tx txToTrack = MailUtils.getTxToTrack(msg, sender, recipients);
+
+ if (deliverySuccessful)
+ {
+ if (isReliableAndTimely && txToTrack.getMsgType() == TxMessageType.IMF)
+ {
+
+ // send back an MDN dispatched message
+ final Collection notifications =
+ notificationProducer.produce(new Message(msg), recipients.toInternetAddressCollection());
+ if (notifications != null && notifications.size() > 0)
+ {
+ LOGGER.debug("Sending MDN \"dispatched\" messages");
+ // create a message for each notification and put it on James "stack"
+ for (NotificationMessage message : notifications)
+ {
+ try
+ {
+ message.saveChanges();
+
+ MailUtils.sendMessageToStream(message);
+ }
+ ///CLOVER:OFF
+ catch (Throwable t)
+ {
+ // don't kill the process if this fails
+ LOGGER.error("Error sending MDN dispatched message.", t);
+ }
+ ///CLOVER:ON
+ }
+ }
+ }
+ }
+ else
+ {
+ // create a DSN message regarless if timely and reliable was requested
+ if (txToTrack != null && txToTrack.getMsgType() == TxMessageType.IMF)
+ MailUtils.sendDSN(txToTrack, recipients, false);
+ }
+
+ LOGGER.debug("Exiting timely and reliable service method.");
+ }
+
+}
diff --git a/src/main/java/org/nhindirect/james/server/mailets/ToStaAgentStream.java b/src/main/java/org/nhindirect/james/server/mailets/ToStaAgentStream.java
new file mode 100644
index 0000000..f235c79
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/mailets/ToStaAgentStream.java
@@ -0,0 +1,41 @@
+package org.nhindirect.james.server.mailets;
+
+import javax.mail.MessagingException;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+import org.nhindirect.common.mail.SMTPMailMessage;
+import org.nhindirect.james.server.streams.SmtpGatewayMessageSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ToStaAgentStream extends GenericMailet
+{
+ protected static final Logger LOGGER = LoggerFactory.getLogger(ToStaAgentStream.class);
+
+ public ToStaAgentStream()
+ {
+
+ }
+
+ @Override
+ public void service(Mail mail) throws MessagingException
+ {
+ LOGGER.info("Receiving message to deliver to STA. Message id: {}", mail.getMessage().getMessageID());
+
+ final SMTPMailMessage smtpMailMessage = MailUtils.mailToSMTPMailMessage(mail);
+
+ final SmtpGatewayMessageSource messageSource = SmtpGatewayMessageSource.getMessageSourceInstance();
+ if (messageSource != null)
+ {
+ messageSource.forwardSMTPMessage(smtpMailMessage);
+ mail.setState(Mail.GHOST);
+
+ LOGGER.info("Message sent to STA. Message id: {}", mail.getMessage().getMessageID());
+
+ return;
+ }
+
+ LOGGER.warn("Message STA source is not available to process message id: {}", mail.getMessage().getMessageID());
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/modules/DirectWebAdminServerModule.java b/src/main/java/org/nhindirect/james/server/modules/DirectWebAdminServerModule.java
new file mode 100644
index 0000000..9123d9f
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/modules/DirectWebAdminServerModule.java
@@ -0,0 +1,98 @@
+package org.nhindirect.james.server.modules;
+
+import static org.apache.james.webadmin.WebAdminConfiguration.DISABLED_CONFIGURATION;
+
+import java.io.FileNotFoundException;
+import java.util.Optional;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.james.modules.server.HealthCheckRoutesModule;
+import org.apache.james.modules.server.TaskRoutesModule;
+import org.apache.james.modules.server.WebAdminServerModule.WebAdminServerModuleConfigurationPerformer;
+import org.apache.james.utils.ConfigurationPerformer;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.utils.PropertiesProvider;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.FixedPortSupplier;
+import org.apache.james.webadmin.TlsConfiguration;
+import org.apache.james.webadmin.WebAdminConfiguration;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.authentication.AuthenticationFilter;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.utils.JsonTransformerModule;
+import org.nhindirect.james.server.authfilter.WebAdminBasicAuthFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+
+public class DirectWebAdminServerModule extends AbstractModule
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DirectWebAdminServerModule.class);
+ private static final boolean DEFAULT_DISABLED = true;
+ private static final String DEFAULT_NO_CORS_ORIGIN = null;
+ private static final boolean DEFAULT_CORS_DISABLED = false;
+ private static final String DEFAULT_NO_KEYSTORE = null;
+ private static final boolean DEFAULT_HTTPS_DISABLED = false;
+ private static final String DEFAULT_NO_PASSWORD = null;
+ private static final String DEFAULT_NO_TRUST_KEYSTORE = null;
+ private static final String DEFAULT_NO_TRUST_PASSWORD = null;
+
+ @Override
+ protected void configure()
+ {
+ install(new TaskRoutesModule());
+ install(new HealthCheckRoutesModule());
+
+ bind(JsonTransformer.class).in(Scopes.SINGLETON);
+ bind(WebAdminServer.class).in(Scopes.SINGLETON);
+
+ Multibinder.newSetBinder(binder(), ConfigurationPerformer.class).addBinding().to(WebAdminServerModuleConfigurationPerformer.class);
+ Multibinder.newSetBinder(binder(), GuiceProbe.class).addBinding().to(WebAdminGuiceProbe.class);
+ Multibinder.newSetBinder(binder(), JsonTransformerModule.class);
+ }
+
+ @Provides
+ public WebAdminConfiguration provideWebAdminConfiguration(PropertiesProvider propertiesProvider) throws Exception {
+ try {
+ Configuration configurationFile = propertiesProvider.getConfiguration("webadmin");
+ return WebAdminConfiguration.builder()
+ .enable(configurationFile.getBoolean("enabled", DEFAULT_DISABLED))
+ .port(new FixedPortSupplier(configurationFile.getInt("port", WebAdminServer.DEFAULT_PORT)))
+ .tls(readHttpsConfiguration(configurationFile))
+ .enableCORS(configurationFile.getBoolean("cors.enable", DEFAULT_CORS_DISABLED))
+ .urlCORSOrigin(configurationFile.getString("cors.origin", DEFAULT_NO_CORS_ORIGIN))
+ .host(configurationFile.getString("host", WebAdminConfiguration.DEFAULT_HOST))
+ .build();
+ } catch (FileNotFoundException e) {
+ LOGGER.info("No webadmin.properties file. Disabling WebAdmin interface.");
+ return DISABLED_CONFIGURATION;
+ }
+ }
+
+ @Provides
+ public AuthenticationFilter providesAuthenticationFilter(PropertiesProvider propertiesProvider) throws Exception
+ {
+ Configuration configurationFile = propertiesProvider.getConfiguration("webadmin");
+
+ return new WebAdminBasicAuthFilter(configurationFile.getString("username"), configurationFile.getString("password"));
+ }
+
+ private Optional readHttpsConfiguration(Configuration configurationFile) {
+ boolean enabled = configurationFile.getBoolean("https.enabled", DEFAULT_HTTPS_DISABLED);
+ if (enabled) {
+ return Optional.of(TlsConfiguration.builder()
+ .raw(configurationFile.getString("https.keystore", DEFAULT_NO_KEYSTORE),
+ configurationFile.getString("https.password", DEFAULT_NO_PASSWORD),
+ configurationFile.getString("https.trust.keystore", DEFAULT_NO_TRUST_KEYSTORE),
+ configurationFile.getString("https.trust.password", DEFAULT_NO_TRUST_PASSWORD))
+ .build());
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/src/main/java/org/nhindirect/james/server/spring/ConfigServiceClientConfig.java b/src/main/java/org/nhindirect/james/server/spring/ConfigServiceClientConfig.java
new file mode 100644
index 0000000..7caa268
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/spring/ConfigServiceClientConfig.java
@@ -0,0 +1,33 @@
+package org.nhindirect.james.server.spring;
+
+import org.nhind.config.rest.AddressService;
+import org.nhind.config.rest.DomainService;
+import org.nhind.config.rest.feign.AddressClient;
+import org.nhind.config.rest.feign.DomainClient;
+import org.nhind.config.rest.impl.DefaultAddressService;
+import org.nhind.config.rest.impl.DefaultDomainService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableFeignClients({"org.nhind.config.rest.feign"})
+public class ConfigServiceClientConfig
+{
+
+ @Bean
+ @ConditionalOnMissingBean
+ public DomainService domainService(DomainClient domainClient)
+ {
+ return new DefaultDomainService(domainClient);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public AddressService addressService(AddressClient addressClient)
+ {
+ return new DefaultAddressService(addressClient);
+ }
+
+}
diff --git a/src/main/java/org/nhindirect/james/server/spring/DSNCreatorFactory.java b/src/main/java/org/nhindirect/james/server/spring/DSNCreatorFactory.java
new file mode 100644
index 0000000..457edac
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/spring/DSNCreatorFactory.java
@@ -0,0 +1,27 @@
+package org.nhindirect.james.server.spring;
+
+import org.nhindirect.gateway.smtp.dsn.DSNCreator;
+import org.nhindirect.gateway.smtp.dsn.impl.FailedDeliveryDSNCreator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DSNCreatorFactory
+{
+ protected static DSNCreator dsnCreator;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public DSNCreator failedDeliveryDSNCreator()
+ {
+ dsnCreator = new FailedDeliveryDSNCreator(null);
+
+ return dsnCreator;
+ }
+
+ public static DSNCreator getFailedDeliverDSNCreator()
+ {
+ return dsnCreator;
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/spring/JamesServerConfig.java b/src/main/java/org/nhindirect/james/server/spring/JamesServerConfig.java
new file mode 100644
index 0000000..271bdc8
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/spring/JamesServerConfig.java
@@ -0,0 +1,391 @@
+package org.nhindirect.james.server.spring;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.Collection;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.modules.MailboxModule;
+import org.apache.james.modules.activemq.ActiveMQQueueModule;
+import org.apache.james.modules.data.JPADataModule;
+import org.apache.james.modules.data.SieveJPARepositoryModules;
+import org.apache.james.modules.mailbox.DefaultEventModule;
+import org.apache.james.modules.mailbox.JPAMailboxModule;
+import org.apache.james.modules.mailbox.LuceneSearchMailboxModule;
+import org.apache.james.modules.protocols.IMAPServerModule;
+import org.apache.james.modules.protocols.LMTPServerModule;
+import org.apache.james.modules.protocols.ManageSieveServerModule;
+import org.apache.james.modules.protocols.POP3ServerModule;
+import org.apache.james.modules.protocols.ProtocolHandlerModule;
+import org.apache.james.modules.protocols.SMTPServerModule;
+import org.apache.james.modules.server.DataRoutesModules;
+import org.apache.james.modules.server.DefaultProcessorsConfigurationProviderModule;
+import org.apache.james.modules.server.ElasticSearchMetricReporterModule;
+import org.apache.james.modules.server.JMXServerModule;
+import org.apache.james.modules.server.MailQueueRoutesModule;
+import org.apache.james.modules.server.MailRepositoriesRoutesModule;
+import org.apache.james.modules.server.MailboxRoutesModule;
+import org.apache.james.modules.server.NoJwtModule;
+import org.apache.james.modules.server.RawPostDequeueDecoratorModule;
+import org.apache.james.modules.server.ReIndexingModule;
+import org.apache.james.modules.server.SieveQuotaRoutesModule;
+import org.apache.james.modules.server.SwaggerRoutesModule;
+import org.apache.james.modules.spamassassin.SpamAssassinListenerModule;
+import org.nhind.config.rest.DomainService;
+import org.nhindirect.config.model.Domain;
+import org.nhindirect.james.server.modules.DirectWebAdminServerModule;
+import org.parboiled.common.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+
+@Configuration
+public class JamesServerConfig
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(JamesServerConfig.class);
+
+ public static final String DEFAULT_MAILET_CONFIG = "/properties/mailetcontainer.xml";
+ public static final String DEFAULT_IMAP_CONFIG = "/properties/imapserver.xml";
+ public static final String DEFAULT_POP3_CONFIG = "/properties/pop3server.xml";
+ public static final String DEFAULT_SMTP_CONFIG = "/properties/smtpserver.xml";
+ public static final String DEFAULT_KEYSTORE = "/properties/keystore";
+
+ /*
+ * Data base parameters
+ */
+ @Value("${spring.datasource.driver-class-name}")
+ protected String driverClassName;
+
+ @Value("${spring.datasource.url}")
+ protected String datasourceUrl;
+
+ @Value("${spring.datasource.username}")
+ protected String datasourceUserName;
+
+ @Value("${spring.datasource.password}")
+ protected String datasourcePassword;
+
+ @Value("${spring.datasource.adapter}")
+ protected String datasourceAdapter;
+
+ @Value("${spring.datasource.streaming}")
+ protected String datasourceStreaming;
+
+ /*
+ * Web admin parameters
+ */
+ @Value("${james.server.webadmin.enabled:true}")
+ protected String enableWebAdmin;
+
+ @Value("${james.server.webadmin.port:8080}")
+ protected String webAdminPort;
+
+ @Value("${james.server.webadmin.username}")
+ protected String webAdminUser;
+
+ @Value("${james.server.webadmin.password}")
+ protected String webAdminPassword;
+
+ @Value("${james.server.webadmin.https.tlsenabled:false}")
+ protected String webadminTlsEnabled;
+
+ @Value("${james.server.webadmin.https.keystore:}")
+ protected String webAdminKeystore;
+
+ @Value("${james.server.webadmin.https.keystorePassword:}")
+ protected String webAdminKeystorePassword;
+
+ @Value("${james.server.webadmin.https.trust.keystore:}")
+ protected String webAdminTrustKeystore;
+
+ @Value("${james.server.webadmin.https.trust.keystorePassword:}")
+ protected String webAdminTrustKeystorePassword;
+
+ /*
+ * IMAP Settings
+ */
+ @Value("${james.server.imap.bind:0.0.0.0}")
+ protected String imapBind;
+
+ @Value("${james.server.imap.port:1143}")
+ protected String imapPort;
+
+ @Value("${james.server.imap.sockettls:false}")
+ protected String imapSocketTLS;
+
+ @Value("${james.server.imap.starttls:true}")
+ protected String imapStartTLS;
+
+ @Value("${james.server.imap.keystore:}")
+ protected String imapKeyStore;
+
+ @Value("${james.server.imap.keystorePassword:1kingpuff}")
+ protected String imapKeyStorePassword;
+
+ /*
+ * POP3 Settings
+ */
+ @Value("${james.server.pop3.bind:0.0.0.0}")
+ protected String pop3Bind;
+
+ @Value("${james.server.pop3.port:1110}")
+ protected String pop3Port;
+
+ @Value("${james.server.pop3.sockettls:false}")
+ protected String pop3SocketTLS;
+
+ @Value("${james.server.pop3.starttls:true}")
+ protected String pop3StartTLS;
+
+ @Value("${james.server.pop3.keystore:}")
+ protected String pop3KeyStore;
+
+ @Value("${james.server.pop3.keystorePassword:1kingpuff}")
+ protected String pop3KeyStorePassword;
+
+ /*
+ * SMTP Settings
+ */
+ @Value("${james.server.smtp.bind:0.0.0.0}")
+ protected String smtpBind;
+
+ @Value("${james.server.smtp.port:1587}")
+ protected String smtpPort;
+
+ @Value("${james.server.smtp.sockettls:false}")
+ protected String smtpSocketTLS;
+
+ @Value("${james.server.smtp.starttls:true}")
+ protected String smtpStartTLS;
+
+ @Value("${james.server.smtp.keystore:}")
+ protected String smtpKeyStore;
+
+ @Value("${james.server.smtp.keystorePassword:1kingpuff}")
+ protected String smtpKeyStorePassword;
+
+ @Value("${james.server.smtp.autoAddresses:127.0.0.0/8}")
+ protected String smtpAuthAddresses;
+
+ /*
+ * Custom configuration files
+ */
+ @Value("${james.server.config.mailet.configFile:}")
+ protected String mailetConfigFile;
+
+ @Value("${james.server.config.imap.configFile:}")
+ protected String imapConfigFile;
+
+ @Value("${james.server.config.pop3.configFile:}")
+ protected String pop3ConfigFile;
+
+ @Value("${james.server.config.smtp.configFile:}")
+ protected String smtpConfigFile;
+
+ @Autowired
+ protected DomainService domService;
+
+ public static final Module PROTOCOLS;
+ public static final Module JPA_SERVER_MODULE;
+ public static final Module JPA_MODULE_AGGREGATE;
+ public static final Module WEBADMIN;
+
+ static
+ {
+
+ WEBADMIN = Modules.combine(
+ new DirectWebAdminServerModule(),
+ new DataRoutesModules(),
+ new MailboxRoutesModule(),
+ new MailQueueRoutesModule(),
+ new MailRepositoriesRoutesModule(),
+ new SwaggerRoutesModule(),
+ new SieveQuotaRoutesModule(),
+ new ReIndexingModule());
+
+ PROTOCOLS = Modules
+ .combine(new Module[]{new IMAPServerModule(), new LMTPServerModule(), new ManageSieveServerModule(),
+ new POP3ServerModule(), new ProtocolHandlerModule(), new SMTPServerModule(), WEBADMIN});
+
+ JPA_SERVER_MODULE = Modules.combine(new Module[]{new ActiveMQQueueModule(),
+ new DefaultProcessorsConfigurationProviderModule(), new ElasticSearchMetricReporterModule(),
+ new JPADataModule(), new JPAMailboxModule(), new MailboxModule(), new LuceneSearchMailboxModule(), new NoJwtModule(),
+ new RawPostDequeueDecoratorModule(), new SieveJPARepositoryModules(),
+ new DefaultEventModule(), new SpamAssassinListenerModule()});
+
+ JPA_MODULE_AGGREGATE = Modules.combine(new Module[]{JPA_SERVER_MODULE, PROTOCOLS});
+ }
+
+
+ @Bean
+ @ConditionalOnMissingBean
+ public GuiceJamesServer jamesServer() throws Exception
+ {
+ writeJPAConfig();
+
+ writeWebAdminConfig();
+
+ writeDomainListConfig();
+
+ writeMailetConfig();
+
+ writeIMAPConfig();
+
+ writePOP3Config();
+
+ writeSMTPConfig();
+
+ final org.apache.james.server.core.configuration.Configuration configuration =
+ org.apache.james.server.core.configuration.Configuration.builder().workingDirectory(".").build();
+
+ final GuiceJamesServer server = GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(new Module[]{JPA_MODULE_AGGREGATE, new JMXServerModule()});
+
+ server.start();
+
+ return server;
+ }
+
+ protected void writeJPAConfig() throws Exception
+ {
+ final File file = new File("conf/james-database.properties");
+
+ String dbPropString = IOUtils.resourceToString("/properties/james-database.properties", Charset.defaultCharset());
+ dbPropString = dbPropString.replace("${driver}", this.driverClassName);
+ dbPropString = dbPropString.replace("${url}", this.datasourceUrl);
+ dbPropString = dbPropString.replace("${username}", this.datasourceUserName);
+ dbPropString = dbPropString.replace("${password}", this.datasourcePassword);
+ dbPropString = dbPropString.replace("${dbadapter}", this.datasourceAdapter);
+
+ dbPropString = dbPropString.replace("${streaming}", this.datasourceStreaming);
+
+ FileUtils.writeAllText(dbPropString, file);
+ }
+
+ protected void writeWebAdminConfig() throws Exception
+ {
+ final File file = new File("conf/webadmin.properties");
+
+ String webAdminString = IOUtils.resourceToString("/properties/webadmin.properties", Charset.defaultCharset());
+ webAdminString = webAdminString.replace("${enabled}", this.enableWebAdmin);
+ webAdminString = webAdminString.replace("${port}", this.webAdminPort);
+ webAdminString = webAdminString.replace("${username}", this.webAdminUser);
+ webAdminString = webAdminString.replace("${password}", this.webAdminPassword);
+ webAdminString = webAdminString.replace("${httpsEnabled}", this.webadminTlsEnabled);
+ webAdminString = webAdminString.replace("${keystore}", this.webAdminKeystore);
+ webAdminString = webAdminString.replace("${keystorePassword}", this.webAdminKeystorePassword);
+ webAdminString = webAdminString.replace("${trustKeystore}", this.webAdminTrustKeystore);
+ webAdminString = webAdminString.replace("${trustPassword}", this.webAdminTrustKeystorePassword);
+
+
+ FileUtils.writeAllText(webAdminString, file);
+ }
+
+ protected void writeDomainListConfig() throws Exception
+ {
+ final File file = new File("conf/domainlist.xml");
+
+ String domainlistXML = IOUtils.resourceToString("/properties/domainlist.xml", Charset.defaultCharset());
+ final StringBuilder domListBuilder = new StringBuilder();
+
+ Collection domains = domService.searchDomains("", null);
+ if (domains.isEmpty())
+ {
+ LOGGER.warn("No domains defined. A default list will be injected by James.");
+ return;
+ }
+ for (Domain domain : domains)
+ {
+ domListBuilder.append("").append(domain.getDomainName()).append("\r\n");
+ }
+
+ domainlistXML = domainlistXML.replace("${domainnames}", domListBuilder.toString());
+ // just use the first in the list for the default damain
+ domainlistXML = domainlistXML.replace("${defaultdomain}", domains.iterator().next().getDomainName());
+
+ FileUtils.writeAllText(domainlistXML, file);
+ }
+
+ protected void writeMailetConfig() throws Exception
+ {
+ /*
+ * Mailet config
+ */
+ File writeFile = new File("conf/mailetcontainer.xml");
+ byte[] content = (StringUtils.isEmpty(mailetConfigFile)) ? IOUtils.resourceToByteArray(DEFAULT_MAILET_CONFIG) : FileUtils.readAllBytes(new File(mailetConfigFile));
+ FileUtils.writeAllBytes(content, writeFile);
+ }
+
+ protected void writeIMAPConfig() throws Exception
+ {
+ /*
+ * IMAP config
+ */
+ final File configFile = new File("conf/imapserver.xml");
+ String content = (StringUtils.isEmpty(imapConfigFile)) ? IOUtils.resourceToString(DEFAULT_IMAP_CONFIG, Charset.defaultCharset()) : FileUtils.readAllText(new File(imapConfigFile));
+
+ content = content.replace("${bind}", this.imapBind);
+ content = content.replace("${port}", this.imapPort);
+ content = content.replace("${socketTLS}", this.imapSocketTLS);
+ content = content.replace("${startTLS}", this.imapStartTLS);
+ content = content.replace("${keystorePassword}", this.imapKeyStorePassword);
+
+ FileUtils.writeAllText(content, configFile);
+
+ final File keystoreFile = new File("conf/keystore");
+ byte[] keyStoreContent = (StringUtils.isEmpty(imapKeyStore)) ? IOUtils.resourceToByteArray(DEFAULT_KEYSTORE) : FileUtils.readAllBytes(new File(imapKeyStore));
+ FileUtils.writeAllBytes(keyStoreContent, keystoreFile);
+ }
+
+ protected void writePOP3Config() throws Exception
+ {
+ /*
+ * POP3 config
+ */
+ final File configFile = new File("conf/pop3server.xml");
+ String content = (StringUtils.isEmpty(pop3ConfigFile)) ? IOUtils.resourceToString(DEFAULT_POP3_CONFIG, Charset.defaultCharset()) : FileUtils.readAllText(new File(pop3ConfigFile));
+
+ content = content.replace("${bind}", this.pop3Bind);
+ content = content.replace("${port}", this.pop3Port);
+ content = content.replace("${socketTLS}", this.pop3SocketTLS);
+ content = content.replace("${startTLS}", this.pop3StartTLS);
+ content = content.replace("${keystorePassword}", this.pop3KeyStorePassword);
+
+ FileUtils.writeAllText(content, configFile);
+
+ final File keystoreFile = new File("conf/keystore");
+ byte[] keyStoreContent = (StringUtils.isEmpty(pop3KeyStore)) ? IOUtils.resourceToByteArray(DEFAULT_KEYSTORE) : FileUtils.readAllBytes(new File(pop3KeyStore));
+ FileUtils.writeAllBytes(keyStoreContent, keystoreFile);
+ }
+
+ protected void writeSMTPConfig() throws Exception
+ {
+ /*
+ * SMTP config
+ */
+ final File configFile = new File("conf/smtpserver.xml");
+ String content = (StringUtils.isEmpty(smtpConfigFile)) ? IOUtils.resourceToString(DEFAULT_SMTP_CONFIG, Charset.defaultCharset()) : FileUtils.readAllText(new File(smtpConfigFile));
+
+ content = content.replace("${bind}", this.smtpBind);
+ content = content.replace("${port}", this.smtpPort);
+ content = content.replace("${socketTLS}", this.smtpSocketTLS);
+ content = content.replace("${startTLS}", this.smtpStartTLS);
+ content = content.replace("${keystorePassword}", this.smtpKeyStorePassword);
+ content = content.replace("${authAddresses}", this.smtpAuthAddresses);
+
+ FileUtils.writeAllText(content, configFile);
+
+ final File keystoreFile = new File("conf/keystore");
+ byte[] keyStoreContent = (StringUtils.isEmpty(smtpKeyStore)) ? IOUtils.resourceToByteArray(DEFAULT_KEYSTORE) : FileUtils.readAllBytes(new File(smtpKeyStore));
+ FileUtils.writeAllBytes(keyStoreContent, keystoreFile);
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/streams/STALastMileDeliveryInput.java b/src/main/java/org/nhindirect/james/server/streams/STALastMileDeliveryInput.java
new file mode 100644
index 0000000..4f02944
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/streams/STALastMileDeliveryInput.java
@@ -0,0 +1,12 @@
+package org.nhindirect.james.server.streams;
+
+import org.springframework.cloud.stream.annotation.Input;
+import org.springframework.messaging.SubscribableChannel;
+
+public interface STALastMileDeliveryInput
+{
+ public static final String STA_LAST_MILE_INPUT = "direct-sta-last-mile-input";
+
+ @Input(STA_LAST_MILE_INPUT)
+ SubscribableChannel staLastMileInput();
+}
diff --git a/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageOutput.java b/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageOutput.java
new file mode 100644
index 0000000..23a240c
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageOutput.java
@@ -0,0 +1,12 @@
+package org.nhindirect.james.server.streams;
+
+import org.springframework.cloud.stream.annotation.Output;
+import org.springframework.messaging.MessageChannel;
+
+public interface SmtpGatewayMessageOutput
+{
+ public static final String SMTP_GATEWAY_MESSAGE_OUTPUT = "direct-smtp-mq-gateway";
+
+ @Output(SMTP_GATEWAY_MESSAGE_OUTPUT)
+ MessageChannel txOutput();
+}
diff --git a/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageSource.java b/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageSource.java
new file mode 100644
index 0000000..0ab4751
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/streams/SmtpGatewayMessageSource.java
@@ -0,0 +1,36 @@
+package org.nhindirect.james.server.streams;
+
+
+import org.nhindirect.common.mail.SMTPMailMessage;
+import org.nhindirect.common.mail.streams.SMTPMailMessageConverter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.annotation.Output;
+import org.springframework.messaging.MessageChannel;
+
+@EnableBinding(SmtpGatewayMessageOutput.class)
+public class SmtpGatewayMessageSource
+{
+ protected static SmtpGatewayMessageSource messageSourceInstance;
+
+ @Autowired
+ @Qualifier(SmtpGatewayMessageOutput.SMTP_GATEWAY_MESSAGE_OUTPUT)
+ private MessageChannel smtpGatewayChannel;
+
+ public SmtpGatewayMessageSource()
+ {
+ messageSourceInstance = this;
+ }
+
+ @Output(SmtpGatewayMessageOutput.SMTP_GATEWAY_MESSAGE_OUTPUT)
+ public void forwardSMTPMessage(SMTPMailMessage msg)
+ {
+ this.smtpGatewayChannel.send(SMTPMailMessageConverter.toStreamMessage(msg));
+ }
+
+ public static SmtpGatewayMessageSource getMessageSourceInstance()
+ {
+ return messageSourceInstance;
+ }
+}
diff --git a/src/main/java/org/nhindirect/james/server/streams/sinks/STALastMileDeliverySink.java b/src/main/java/org/nhindirect/james/server/streams/sinks/STALastMileDeliverySink.java
new file mode 100644
index 0000000..795f968
--- /dev/null
+++ b/src/main/java/org/nhindirect/james/server/streams/sinks/STALastMileDeliverySink.java
@@ -0,0 +1,63 @@
+package org.nhindirect.james.server.streams.sinks;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.Mail;
+import org.nhindirect.common.mail.SMTPMailMessage;
+import org.nhindirect.common.mail.streams.SMTPMailMessageConverter;
+import org.nhindirect.james.server.mailets.MailUtils;
+import org.nhindirect.james.server.mailets.StreamsTimelyAndReliableLocalDelivery;
+import org.nhindirect.james.server.streams.STALastMileDeliveryInput;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.annotation.StreamListener;
+import org.springframework.messaging.Message;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+@EnableBinding(STALastMileDeliveryInput.class)
+public class STALastMileDeliverySink
+{
+ public STALastMileDeliverySink()
+ {
+
+ }
+
+ @StreamListener(target = STALastMileDeliveryInput.STA_LAST_MILE_INPUT)
+ public void processLastMileMessage(Message> streamMsg) throws MessagingException
+ {
+ /*
+ * This blocks the processing of messages until the mailet instance has been created
+ */
+ synchronized(StreamsTimelyAndReliableLocalDelivery.class)
+ {
+ if (StreamsTimelyAndReliableLocalDelivery.getStaticMailet() == null)
+ {
+ try
+ {
+ STALastMileDeliverySink.class.wait();
+ }
+ catch (InterruptedException e) {/* no-op */}
+ }
+ }
+
+ /*
+ * Create the MAIL message and send it on to the mailet.
+ */
+ final SMTPMailMessage smtpMessage = SMTPMailMessageConverter.fromStreamMessage(streamMsg);
+
+ final ImmutableList recips = smtpMessage.getRecipientAddresses().stream()
+ .map(Throwing.function(MailUtils::castToMailAddress).sneakyThrow())
+ .collect(Guavate.toImmutableList());
+
+ final Mail mail = MailImpl.builder()
+ .sender(new MailAddress(smtpMessage.getMailFrom()))
+ .recipients(recips)
+ .mimeMessage(smtpMessage.getMimeMessage()).build();
+
+ StreamsTimelyAndReliableLocalDelivery.getStaticMailet().service(mail);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..fed5c71
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,25 @@
+spring:
+ cloud:
+ stream:
+ bindings:
+ direct-smtp-mq-gateway:
+ destination: direct-smtp-mq-gateway
+
+ direct-sta-last-mile-input:
+ destination: direct-sta-last-mile-delivery
+ group: direct-sta-last-mile-delivery-group
+ consumer:
+ concurrency: 10
+ maxAttempts: 4
+ backOffInitialInterval: 15000
+ backOffMaxInterval: 60000
+
+direct:
+ webservices:
+ security:
+ basic:
+ user:
+ name: admin
+ password: d1r3ct;
+
+
diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..91981cf
--- /dev/null
+++ b/src/main/resources/bootstrap.yml
@@ -0,0 +1,42 @@
+spring:
+#Update these properties to your own data store if not using Derby
+ datasource:
+ driver-class-name: org.apache.derby.jdbc.EmbeddedDriver
+ url: jdbc:derby:./var/store/derby;create=true
+ username: app
+ password: app
+ adapter: DERBY
+ streaming: false
+#Fill out these properties for your own rabbit MQ instance
+# rabbitmq:
+# host:
+# port:
+# username:
+# password:
+# virtual-host:
+# ssl:
+# enabled:
+# validate-server-certificate: false
+# verify-hostname: false
+ application:
+ name: direct-james-server
+ main:
+ allow-bean-definition-overriding: true
+
+eureka:
+ client:
+ enabled: false
+
+#If not using Eureka, then set the config-service URL. This should be set to a empty value
+#if you are using Eureka
+direct:
+ config:
+ service:
+ url: http://localhost:8080/config-service
+
+#Default the web admin credentials
+james:
+ server:
+ webadmin:
+ username: admin
+ password: d1r3ct
\ No newline at end of file
diff --git a/src/main/resources/properties/domainlist.xml b/src/main/resources/properties/domainlist.xml
new file mode 100644
index 0000000..ea70b1f
--- /dev/null
+++ b/src/main/resources/properties/domainlist.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ${domainnames}
+
+ false
+ false
+ ${defaultdomain}
+
diff --git a/src/main/resources/properties/imapserver.xml b/src/main/resources/properties/imapserver.xml
new file mode 100644
index 0000000..d957954
--- /dev/null
+++ b/src/main/resources/properties/imapserver.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ imapserver
+
+
+
+
+
+
+ ${bind}:${port}
+
+ 200
+
+
+
+
+ file://conf/keystore
+ ${keystorePassword}
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
diff --git a/src/main/resources/properties/james-database.properties b/src/main/resources/properties/james-database.properties
new file mode 100644
index 0000000..b30c774
--- /dev/null
+++ b/src/main/resources/properties/james-database.properties
@@ -0,0 +1,11 @@
+
+database.driverClassName=${driver}
+database.url=${url}
+database.username=${username}
+database.password=${password}
+
+# Supported adapters are:
+# DB2, DERBY, H2, HSQL, INFORMIX, MYSQL, ORACLE, POSTGRESQL, SQL_SERVER, SYBASE
+vendorAdapter.database=${dbadapter}
+
+openjpa.streaming=${streaming}
diff --git a/src/main/resources/properties/keystore b/src/main/resources/properties/keystore
new file mode 100644
index 0000000..84758f8
Binary files /dev/null and b/src/main/resources/properties/keystore differ
diff --git a/src/main/resources/properties/mailetcontainer.out.xml b/src/main/resources/properties/mailetcontainer.out.xml
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/properties/mailetcontainer.xml b/src/main/resources/properties/mailetcontainer.xml
new file mode 100644
index 0000000..d966f86
--- /dev/null
+++ b/src/main/resources/properties/mailetcontainer.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+ postmaster@james.minet.net
+
+
+
+ 20
+
+
+
+
+
+
+
+ transport
+
+
+
+
+
+ mailetContainerErrors
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+
+
+
+
+ relay
+
+
+
+
+
+ outgoing
+ 5000, 100000, 500000
+ 25
+ 0
+ 10
+ true
+ bounces
+
+
+
+
+
+ mailetContainerLocalAddressError
+
+
+ none
+
+
+
+
+
+ mailetContainerRelayDenied
+
+
+ none
+
+
+
+
+
+ bounces
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/properties/pop3server.xml b/src/main/resources/properties/pop3server.xml
new file mode 100644
index 0000000..6b5d691
--- /dev/null
+++ b/src/main/resources/properties/pop3server.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ pop3server
+ ${bind}:${port}
+ 200
+
+
+ file://conf/keystore
+ ${keystorePassword}
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 1200
+ 0
+ 0
+
+
+
+
+
diff --git a/src/main/resources/properties/smtpserver.xml b/src/main/resources/properties/smtpserver.xml
new file mode 100644
index 0000000..17afbea
--- /dev/null
+++ b/src/main/resources/properties/smtpserver.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:1025
+ 200
+
+
+
+ 360
+ 0
+ 0
+ false
+ 127.0.0.0/8
+ true
+ 0
+ true
+ DirectProject SMTP Server
+
+
+
+
+
+
+ smtpserver-authenticated
+ ${bind}:${port}
+ 200
+
+ file://conf/keystore
+ ${keystorePassword}
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+
+ 360
+ 0
+ 0
+
+ true
+ ${authAddresses}
+
+ false
+ 0
+ true
+ DirectProject SMTP Server
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/properties/webadmin.properties b/src/main/resources/properties/webadmin.properties
new file mode 100644
index 0000000..0567c01
--- /dev/null
+++ b/src/main/resources/properties/webadmin.properties
@@ -0,0 +1,9 @@
+enabled=${enabled}
+port=${port}
+username=${username}
+password=${password}
+https.enabled=${httpsEnabled}
+https.keystore=${keystore}
+https.password=${keystorePassword}
+https.trust.keystore=${trustKeystore}
+https.trust.password=${trustPassword}