From d3cdcd255c23f9bdee2a13f212eed0583af58906 Mon Sep 17 00:00:00 2001
From: "thorben.betten" <thorben.betten@open-xchange.com>
Date: Thu, 6 Oct 2022 16:05:30 +0200
Subject: [PATCH] MWB-1862: Check probed hosts against configured
 black-list/white-list for mail accounts prior to connect attempt

(cherry picked from commit 1b9786ae00a3e8458cd95bbe77bffb3fb1472211)
---
 .../META-INF/MANIFEST.MF                      |  1 +
 .../mail/autoconfig/sources/Guess.java        | 52 ++++++++++---------
 .../mail/autoconfig/tools/MailValidator.java  | 10 +++-
 com.openexchange.server/META-INF/MANIFEST.MF  |  1 +
 .../src/com/sun/mail/util/SocketFetcher.java  | 41 +++++++++++++--
 5 files changed, 75 insertions(+), 30 deletions(-)

diff --git a/com.openexchange.mail.autoconfig.impl/META-INF/MANIFEST.MF b/com.openexchange.mail.autoconfig.impl/META-INF/MANIFEST.MF
index d88434c147d3..ab841d4aa0d6 100644
--- a/com.openexchange.mail.autoconfig.impl/META-INF/MANIFEST.MF
+++ b/com.openexchange.mail.autoconfig.impl/META-INF/MANIFEST.MF
@@ -33,6 +33,7 @@ Import-Package: com.google.common.annotations,
  com.openexchange.mail.mime,
  com.openexchange.mail.utils,
  com.openexchange.mailaccount,
+ com.openexchange.mailaccount.utils,
  com.openexchange.net,
  com.openexchange.net.ssl,
  com.openexchange.net.ssl.config,
diff --git a/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/sources/Guess.java b/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/sources/Guess.java
index 428179310ae3..bd557181d127 100644
--- a/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/sources/Guess.java
+++ b/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/sources/Guess.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import com.openexchange.config.cascade.ComposedConfigProperty;
 import com.openexchange.config.cascade.ConfigView;
 import com.openexchange.config.cascade.ConfigViewFactory;
@@ -45,13 +46,13 @@ import com.openexchange.tools.net.URIDefaults;
 public class Guess extends AbstractConfigSource {
 
     static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(Guess.class);
-    
+
     /** The property name for context identifier. Value is <code>java.lang.Integer</code> */
     public static final String PROP_GENERAL_CONTEXT_ID = "general.context";
-    
+
     /** The property name for user identifier. Value is <code>java.lang.Integer</code> */
     public static final String PROP_GENERAL_USER_ID = "general.user";
-    
+
     /** The property name for flag whether SMTP supports authentication. Value is <code>java.lang.Boolean</code> */
     public static final String PROP_SMTP_AUTH_SUPPORTED = "smtp.auth-supported";
 
@@ -113,13 +114,15 @@ public class Guess extends AbstractConfigSource {
     }
 
     private boolean fillProtocol(Protocol protocol, String emailLocalPart, String emailDomain, String password, DefaultAutoconfig config, Map<String, Object> properties, boolean forceSecure) {
-        ConnectSettings connectSettings = guessHost(protocol, emailDomain);
-        if (connectSettings == null) {
+        Optional<ConnectSettings> optConnectSettings = guessHost(protocol, emailDomain);
+        if (optConnectSettings.isPresent() == false) {
             return false;
         }
 
-        String login = guessLogin(protocol, connectSettings.host, connectSettings.port, connectSettings.secure, forceSecure, emailLocalPart, emailDomain, password, properties);
-        if (login == null) {
+        ConnectSettings connectSettings = optConnectSettings.get();
+
+        Optional<String> optLogin = guessLogin(protocol, connectSettings.host, connectSettings.port, connectSettings.secure, forceSecure, emailLocalPart, emailDomain, password, properties);
+        if (optLogin.isPresent() == false) {
             return false;
         }
 
@@ -129,19 +132,19 @@ public class Guess extends AbstractConfigSource {
             config.setTransportSecure(connectSettings.secure);
             config.setTransportServer(connectSettings.host);
             config.setTransportStartTls(forceSecure); // Take over since end-point has been checked with proper STARTTLS setting if we reach this point
-            config.setUsername(login);
+            config.setUsername(optLogin.get());
         } else {
             config.setMailPort(connectSettings.port);
             config.setMailProtocol(protocol.getProtocol());
             config.setMailSecure(connectSettings.secure);
             config.setMailServer(connectSettings.host);
             config.setMailStartTls(forceSecure); // Take over since end-point has been checked with proper STARTTLS setting if we reach this point
-            config.setUsername(login);
+            config.setUsername(optLogin.get());
         }
         return true;
     }
 
-    private String guessLogin(Protocol protocol, String host, int port, boolean secure, boolean requireTls, String emailLocalPart, String emailDomain, String password, Map<String, Object> properties) {
+    private Optional<String> guessLogin(Protocol protocol, String host, int port, boolean secure, boolean requireTls, String emailLocalPart, String emailDomain, String password, Map<String, Object> properties) {
         List<String> logins = Arrays.asList(emailLocalPart, emailLocalPart+"@"+emailDomain);
         ConnectMode connectMode = ConnectMode.connectModeFor(secure, requireTls);
 
@@ -149,34 +152,33 @@ public class Guess extends AbstractConfigSource {
             switch (protocol) {
                 case IMAP:
                     if (MailValidator.validateImap(host, port, connectMode, login, password, properties)) {
-                        return login;
+                        Optional.ofNullable(login);
                     }
                     break;
                 case POP3:
                     if (MailValidator.validatePop3(host, port, connectMode, login, password, properties)) {
-                        return login;
+                        Optional.ofNullable(login);
                     }
                     break;
                 case SMTP:
                     if (MailValidator.validateSmtp(host, port, connectMode, login, password, properties)) {
-                        return login;
+                        return Optional.ofNullable(login);
                     }
                     break;
             }
         }
 
-        return null;
+        return Optional.empty();
     }
 
     private static final List<String> IMAP_PREFIXES = Arrays.asList("", "imap.", "mail.");
     private static final List<String> SMTP_PREFIXES = Arrays.asList("", "smtp.", "mail.");
     private static final List<String> POP3_PREFIXES = Arrays.asList("", "pop3.", "mail.");
 
-    private ConnectSettings guessHost(Protocol protocol, String emailDomain) {
-        URIDefaults uriDefaults = null;
-        List<String> prefixes = null;
+    private Optional<ConnectSettings> guessHost(Protocol protocol, String emailDomain) {
+        URIDefaults uriDefaults;
         int altPort = 0;
-
+        List<String> prefixes;
         switch (protocol) {
             case IMAP:
                 prefixes = IMAP_PREFIXES;
@@ -192,34 +194,34 @@ public class Guess extends AbstractConfigSource {
                 uriDefaults = URIDefaults.SMTP;
                 break;
             default:
-                return null;
+                return Optional.empty();
         }
 
         for (String prefix : prefixes) {
-            String host = prefix + emailDomain;
+            String host = prefix.length() > 0 ? prefix + emailDomain : emailDomain;
 
             // Try SSL connect using default SSL port
             if (tryConnect(protocol, host, uriDefaults.getSSLPort(), true)) {
-                return new ConnectSettings(host, true, uriDefaults.getSSLPort());
+                return Optional.of(new ConnectSettings(host, true, uriDefaults.getSSLPort()));
             }
 
             // Try SSL connect using default port
             if (tryConnect(protocol, host, uriDefaults.getPort(), true)) {
-                return new ConnectSettings(host, true, uriDefaults.getPort());
+                return Optional.of(new ConnectSettings(host, true, uriDefaults.getPort()));
             }
 
             // Try plain connect using alternative port
             if (altPort > 0 && tryConnect(protocol, host, altPort, false)) {
-                return new ConnectSettings(host, false, altPort);
+                return Optional.of(new ConnectSettings(host, false, altPort));
             }
 
             // Try plain connect using default port
             if (tryConnect(protocol, host, uriDefaults.getPort(), false)) {
-                return new ConnectSettings(host, false, uriDefaults.getPort());
+                return Optional.of(new ConnectSettings(host, false, uriDefaults.getPort()));
             }
         }
 
-        return null;
+        return Optional.empty();
     }
 
     private boolean tryConnect(Protocol protocol, String emailDomain, int port, boolean secure) {
diff --git a/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/tools/MailValidator.java b/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/tools/MailValidator.java
index 0cafeaf80192..bf67f6a45718 100644
--- a/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/tools/MailValidator.java
+++ b/com.openexchange.mail.autoconfig.impl/src/com/openexchange/mail/autoconfig/tools/MailValidator.java
@@ -43,6 +43,7 @@ import com.openexchange.java.Streams;
 import com.openexchange.java.Strings;
 import com.openexchange.mail.mime.MimeDefaultSession;
 import com.openexchange.mail.utils.NetUtils;
+import com.openexchange.mailaccount.utils.MailAccountUtils;
 import com.openexchange.net.ssl.SSLSocketFactoryProvider;
 import com.openexchange.net.ssl.config.SSLConfigurationService;
 import com.sun.mail.smtp.SMTPTransport;
@@ -326,8 +327,15 @@ public class MailValidator {
     private static boolean tryConnect(String host, int port, boolean secure, String closePhrase, String name) {
         Socket s = null;
         try {
+            // Check if black-listed
+            if (MailAccountUtils.isDenied(host, port)) {
+                return false;
+            }
+
             // Establish socket connection
-            s = SocketFetcher.getSocket(host, port, createProps(name, port, secure), "mail." + name, false);
+            Properties props = createProps(name, port, secure);
+            props.put("mail." + name + ".denyInternalAddress", "true");
+            s = SocketFetcher.getSocket(host, port, props, "mail." + name, false);
             InputStream in = s.getInputStream();
             OutputStream out = s.getOutputStream();
             if (null == in || null == out) {
diff --git a/com.openexchange.server/META-INF/MANIFEST.MF b/com.openexchange.server/META-INF/MANIFEST.MF
index 88136627db1b..30dcae44b014 100644
--- a/com.openexchange.server/META-INF/MANIFEST.MF
+++ b/com.openexchange.server/META-INF/MANIFEST.MF
@@ -683,6 +683,7 @@ Export-Package: com.openexchange.ajax,
  com.openexchange.mailaccount.json,
  com.openexchange.mailaccount.json.parser,
  com.openexchange.mailaccount.json.writer,
+ com.openexchange.mailaccount.utils,
  com.openexchange.multiple,
  com.openexchange.passwordchange,
  com.openexchange.policy.retry,
diff --git a/javax.mail/src/com/sun/mail/util/SocketFetcher.java b/javax.mail/src/com/sun/mail/util/SocketFetcher.java
index 51cc0e67df62..4fbcbd5e3511 100644
--- a/javax.mail/src/com/sun/mail/util/SocketFetcher.java
+++ b/javax.mail/src/com/sun/mail/util/SocketFetcher.java
@@ -176,24 +176,57 @@ public class SocketFetcher {
     private static Socket getSocket(String host, int port, Properties props,
         String prefix, boolean useSSL, boolean retryOnNoRouteToHost) throws IOException {
         try {
+            boolean denyInternalAddress = PropUtil.getBooleanProperty(props, prefix + ".denyInternalAddress", false);
+
             InetAddress[] addresses = InetAddress.getAllByName(host);
             if (addresses.length == 1 || !PropUtil.getBooleanProperty(props, prefix + ".multiAddress.enabled", false)) {
+                if (denyInternalAddress && com.openexchange.java.InetAddresses.isInternalAddress(addresses[0])) {
+                    throw new IOException("Denied to connect against internal address: " + host);
+                }
                 return getSocket(addresses[0], host, port, props, prefix, useSSL);
             }
-    
+
+            if (denyInternalAddress) {
+                java.util.List<InetAddress> validAddresses = null;
+                for (int i = 0; i < addresses.length; i++) {
+                    boolean denied = com.openexchange.java.InetAddresses.isInternalAddress(addresses[i]);
+                    if (validAddresses == null) {
+                        if (denied) {
+                            validAddresses = new java.util.ArrayList<>(addresses.length);
+                            if (i > 0) {
+                                // Add previous addresses to valid ones
+                                for (int k = 0; k < i; k++) {
+                                    validAddresses.add(addresses[k]);
+                                }
+                            }
+                        }
+                    } else {
+                        if (denied == false) {
+                            validAddresses.add(addresses[i]);
+                        }
+                    }
+                }
+                if (validAddresses != null) {
+                    if (validAddresses.isEmpty()) {
+                        throw new IOException("Denied to connect against internal address: " + host);
+                    }
+                    addresses = validAddresses.toArray(new InetAddress[validAddresses.size()]);
+                }
+            }
+
             AddressSelector selector = AddressSelector.getSelectorFor(host, addresses, props, prefix);
             return new FailoverSocket(selector, host, port, props, prefix, useSSL);
         } catch (java.net.UnknownHostException e) {
             throw e;
         } catch (IOException e) {
-            if (retryOnNoRouteToHost == false || isNoRouteToHostException(e) == false) {                
+            if (retryOnNoRouteToHost == false || isNoRouteToHostException(e) == false) {
                 throw e;
             }
-            
+
             if (clearDnsCache() == false) {
                 throw e;
             }
-            
+
             return getSocket(host, port, props, prefix, useSSL, false);
         }
     }
-- 
GitLab