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 + + + + + + + + + + + + + bcc + ignore + + + 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}