From 853657964d933a9188de6077b051a10c71949f7f Mon Sep 17 00:00:00 2001
From: Daniel Haag <daniel.haag@uibk.ac.at>
Date: Thu, 11 Oct 2018 08:18:23 +0000
Subject: [PATCH] OPENOLAT-211: new configuration mailFromDomain allowing mail
 from header handling depending on the sender and recipient mail address
 domains

---
 .../java/org/olat/core/util/WebappHelper.java |  5 ++
 .../core/util/_spring/utilCorecontext.xml     |  1 +
 .../util/mail/manager/MailManagerImpl.java    | 44 +++++++++++++-----
 .../resources/serviceconfig/olat.properties   |  4 ++
 .../util/mail/manager/MailManagerTest.java    | 46 +++++++++++++++++++
 5 files changed, 89 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/olat/core/util/WebappHelper.java b/src/main/java/org/olat/core/util/WebappHelper.java
index 5bef57b97b2..1277bf2d5f1 100644
--- a/src/main/java/org/olat/core/util/WebappHelper.java
+++ b/src/main/java/org/olat/core/util/WebappHelper.java
@@ -426,6 +426,7 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 	 *	key="smtpPwd"
 	 *	key="mailSupport"
 	 *  key="mailReplyTo" - default from email address (reply-to)
+	 *  key="mailFromDomain" - own domain of our smtp server where it is allowed to use foreign addresses
 	 *  key="mailFrom" - real from email address
 	 *  key="mailFromName" - plain text name for from address
 	 * @param string
@@ -435,6 +436,10 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 		return WebappHelper.mailConfig.get(key);
 	}
 
+	public static String setMailConfig(String key, String value) {
+		return WebappHelper.mailConfig.put(key, value);
+	}
+
 	public static boolean isMailHostAuthenticationEnabled() {
 		return StringHelper.containsNonWhitespace(getMailConfig("smtpUser"));
 	}
diff --git a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml
index 32f47349095..8fe93bcad17 100644
--- a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml
+++ b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml
@@ -31,6 +31,7 @@
 				<entry key="sslEnabled" value="${smtp.sslEnabled}"/>
 				<entry key="sslCheckCertificate" value="${smtp.sslCheckCertificate}"/>
 				<entry key="smtpStarttls" value="${smtp.starttls}"/>
+				<entry key="mailFromDomain" value="${fromdomain}"/>
 				<entry key="mailFrom" value="${fromemail}"/>
 				<entry key="mailFromName" value="${fromname}"/>
 				<entry key="smtpFrom" value="${smtp.from}"/>
diff --git a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
index f330f6932e9..35ac5d4c823 100644
--- a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
+++ b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
@@ -30,6 +30,7 @@ import java.io.OutputStream;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -1587,6 +1588,21 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 		return new InternetAddress(fromPlainAddress);
 	}
 
+	private boolean hasExternalFromAndRecipient(MimeMessage msg) throws MessagingException {
+		String fromDomain = WebappHelper.getMailConfig("mailFromDomain");
+		if (fromDomain == null || fromDomain.isEmpty()) {
+			// if no mailFromDomain property is set every address is considered external
+			return true;
+		}
+		return containsExternalAddress(msg.getFrom(), fromDomain)
+				&& containsExternalAddress(msg.getAllRecipients(), fromDomain);
+	}
+
+	private boolean containsExternalAddress(Address[] addressArray, String fromDomain) {
+		return !(Arrays.stream(addressArray).map(r -> ((InternetAddress) r).getAddress())
+				.filter(x -> x.contains("@")).allMatch(x -> x.endsWith(fromDomain)));
+	}
+
 	@Override
 	public MimeMessage createMimeMessage(Address from, Address[] tos, Address[] ccs, Address[] bccs, String subject, String body,
 			List<File> attachments, MailerResult result) {
@@ -1606,6 +1622,23 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 				msg.addRecipients(RecipientType.BCC, bccs);
 			}
 
+			String platformFrom = WebappHelper.getMailConfig("mailFrom");
+			String platformName = WebappHelper.getMailConfig("mailFromName");
+			Address viewablePlatformFrom = createAddressWithName(platformFrom, platformName);
+			// in case the sender and one of the recipients has an external mail address domain we set
+			// the from header to the admin address to prevent rejected or messages detected as spam.
+			msg.setFrom(from);
+			// from has to be set for this check to work
+			if (hasExternalFromAndRecipient(msg)) {
+				msg.setFrom(viewablePlatformFrom);
+			} else {
+				// sender header contains the agent responsible for the actual transmission
+				// if it is not equal to the from header.
+				if (!from.equals(viewablePlatformFrom)) {
+					msg.setSender(viewablePlatformFrom);
+				}
+			}
+
 			if (attachments != null && !attachments.isEmpty()) {
 				// with attachment use multipart message
 				Multipart multipart = new MimeMultipart("mixed");
@@ -1692,17 +1725,6 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 			} catch (MessagingException e) {
 				log.error("", e);
 			}
-		} else {
-			try {
-				if (msg.getReplyTo().length > 0) {
-					Address a = msg.getReplyTo()[0];
-					SMTPMessage smtpMsg = new SMTPMessage(msg);
-					smtpMsg.setEnvelopeFrom(((InternetAddress)a).getAddress());
-					msg = smtpMsg;
-				}
-			} catch (MessagingException e) {
-				log.error("", e);
-			}
 		}
 
 		try{
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index 7b2ca6dfd32..7aea976a329 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -126,6 +126,10 @@ smtp.starttls=false
 smtp.timeout=8000
 # smtp.from will override the mail envelope, leave it empty to set it to the first reply-to address
 smtp.from=
+# local mail domain where the return address is allowed to be set to the sender accounts email address.
+# if the sender or all the recipients are in this domain the senders mailaddress otherwise the fromemail (see below) will be in the from header.
+# leave empty to always use the fromemail as return address in the from header
+fromdomain=
 # system mails will be sent from this address (from local domain with valid reverse dns):
 fromemail=no-reply@your.domain
 # the plain text name of the from mail address usually displayed by the email client
diff --git a/src/test/java/org/olat/core/util/mail/manager/MailManagerTest.java b/src/test/java/org/olat/core/util/mail/manager/MailManagerTest.java
index 4d5b8eae9ff..843be9a8a8c 100644
--- a/src/test/java/org/olat/core/util/mail/manager/MailManagerTest.java
+++ b/src/test/java/org/olat/core/util/mail/manager/MailManagerTest.java
@@ -29,6 +29,11 @@ import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -37,6 +42,7 @@ import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.WebappHelper;
 import org.olat.core.util.mail.ContactList;
 import org.olat.core.util.mail.MailBundle;
 import org.olat.core.util.mail.MailManager;
@@ -269,6 +275,46 @@ public class MailManagerTest extends OlatTestCase {
 		assertEquals("Thread(s) did not finish", NUM_OF_THREADS, statusList.size());
 	}
 
+	@Test
+	public void testFromHeaderOverride() throws MessagingException {
+		WebappHelper.setMailConfig("mailFromDomain","internal.com");
+		
+		Address fromx = new InternetAddress("External Sender <test1@external.com>");
+		Address fromi = new InternetAddress("Internal Sender <test1@internal.com>");
+
+		Address gr1 = new InternetAddress("group1:;");
+		Address gr2 = new InternetAddress("group2:;");
+
+		Address tox1 = new InternetAddress("External Recipient 1 <test1@external.com>");
+		Address tox2 = new InternetAddress("External Recipient 2 <test1@external.com>");
+		Address toi1 = new InternetAddress("Internal Recipient 1 <test1@internal.com>");
+		Address toi2 = new InternetAddress("Internal Recipient 2 <test1@internal.com>");
+
+		Address mailFrom = new InternetAddress(WebappHelper.getMailConfig("mailFrom"));
+		
+		Address[] groupRecipients = {gr1, gr2};
+		Address[] mixedRecipients = {tox1, tox2, toi1, toi2};
+		Address[] internalRecipients = {toi1, toi2};
+
+		MailerResult result1 = new MailerResult();
+		MimeMessage msg1 = mailManager.createMimeMessage(fromx, groupRecipients, groupRecipients, mixedRecipients, "Testsubject", "Testbody", null, result1);
+		Assert.assertTrue("From header is set to admin address for external maildomain in from and recipients",
+				msg1.getFrom()[0].equals(mailFrom));
+		Assert.assertNotNull(result1);
+
+		MailerResult result2 = new MailerResult();
+		MimeMessage msg2 = mailManager.createMimeMessage(fromi, groupRecipients, groupRecipients, mixedRecipients, "Testsubject", "Testbody", null, result2);
+		Assert.assertTrue("From header is set to real user address for internal maildomain in from and external recipients",
+				msg2.getFrom()[0].equals(fromi));
+		Assert.assertNotNull(result2);
+
+		MailerResult result3 = new MailerResult();
+		MimeMessage msg3 = mailManager.createMimeMessage(fromx, groupRecipients, groupRecipients, internalRecipients, "Testsubject", "Testbody", null, result3);
+		Assert.assertTrue("From header is set to real user address for external maildomain in from and only internal recipients",
+				msg3.getFrom()[0].equals(fromx));
+		Assert.assertNotNull(result3);
+	}
+
 	private class SubscribeThread extends Thread {
 		
 		private final List<Identity> ids;
-- 
GitLab