diff --git a/app/src/main/java/eu/faircode/email/ConnectionHelper.java b/app/src/main/java/eu/faircode/email/ConnectionHelper.java index 80294a4eb0..9c21757f2b 100644 --- a/app/src/main/java/eu/faircode/email/ConnectionHelper.java +++ b/app/src/main/java/eu/faircode/email/ConnectionHelper.java @@ -73,6 +73,7 @@ import javax.mail.MessagingException; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -504,6 +505,17 @@ public class ConnectionHelper { return false; } + static boolean isUnsupportedProtocol(Throwable ex) { + while (ex != null) { + if (ex instanceof SSLHandshakeException && + ex.getMessage() != null && + ex.getMessage().contains("UNSUPPORTED_PROTOCOL")) + return true; + ex = ex.getCause(); + } + return false; + } + static boolean isAborted(Throwable ex) { while (ex != null) { String msg = ex.getMessage(); diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index e10854e51b..3cd00c5701 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -742,80 +742,99 @@ public class EmailService implements AutoCloseable { ((ErrnoException) ex.getCause().getCause()).errno == OsConstants.EACCES) throw new SecurityException("EACCES Please check 'Restrict data usage' in the Android app settings", ex); - boolean ioError = false; - Throwable ce = ex; - while (ce != null) { - if (factory != null && - (ce instanceof CertificateException || - ce instanceof CertPathValidatorException)) - throw new UntrustedException(ex, factory.certificate); - if (ce instanceof IOException) - ioError = true; - ce = ce.getCause(); - } - - if (ioError) { - EntityLog.log(context, EntityLog.Type.Network, "Connect ex=" + - ex.getClass().getName() + ":" + - ex + "\n" + android.util.Log.getStackTraceString(ex)); + if (!ssl_harden && ConnectionHelper.isUnsupportedProtocol(ex)) { + EntityLog.log(context, EntityLog.Type.Network, "Unsuported protocol"); try { - // Some devices resolve IPv6 addresses while not having IPv6 connectivity - InetAddress[] iaddrs = DnsHelper.getAllByName(context, host, dnssec); - int ip4 = (main instanceof Inet4Address ? 1 : 0); - int ip6 = (main instanceof Inet6Address ? 1 : 0); - - boolean[] has46 = ConnectionHelper.has46(context); - - boolean prefer_ip4 = prefs.getBoolean("prefer_ip4", true); - boolean prefer_ip6 = !prefer_ip4 && prefs.getBoolean("prefer_ip6", false); - - EntityLog.log(context, EntityLog.Type.Network, "Address main=" + main + - " count=" + iaddrs.length + - " ip4=" + ip4 + " max4=" + MAX_IPV4 + " has4=" + has46[0] + " pref4=" + prefer_ip4 + - " ip6=" + ip6 + " max6=" + MAX_IPV6 + " has6=" + has46[1] + " pref6=" + prefer_ip6); - - if (prefer_ip4 || prefer_ip6) - Arrays.sort(iaddrs, new Comparator() { - @Override - public int compare(InetAddress a1, InetAddress a2) { - int s = Boolean.compare(a1 instanceof Inet4Address, a2 instanceof Inet4Address); - if (prefer_ip4) - s = -s; - return s; - } - }); - - for (InetAddress iaddr : iaddrs) { - EntityLog.log(context, EntityLog.Type.Network, "Address resolved=" + iaddr); - - if (iaddr.equals(main)) - continue; + this.insecure = true; + factory = new SSLSocketFactoryService(context, + host, port, true, false, + false, false, false, false, + false, + true, true, + factory.key, factory.chain, factory.trustedFingerprint); + properties.put("mail." + protocol + ".ssl.socketFactory", factory); + _connect(main, port, require_id, user, factory); + return; + } catch (GeneralSecurityException ex1) { + Log.e(ex1); + } + return; + } else { + boolean ioError = false; + Throwable ce = ex; + while (ce != null) { + if (factory != null && + (ce instanceof CertificateException || + ce instanceof CertPathValidatorException)) + throw new UntrustedException(ex, factory.certificate); + if (ce instanceof IOException) + ioError = true; + ce = ce.getCause(); + } - if (iaddr instanceof Inet4Address) { - if (!has46[0] || ip4 >= MAX_IPV4) + if (ioError) { + EntityLog.log(context, EntityLog.Type.Network, "Connect ex=" + + ex.getClass().getName() + ":" + + ex + "\n" + android.util.Log.getStackTraceString(ex)); + try { + // Some devices resolve IPv6 addresses while not having IPv6 connectivity + InetAddress[] iaddrs = DnsHelper.getAllByName(context, host, dnssec); + int ip4 = (main instanceof Inet4Address ? 1 : 0); + int ip6 = (main instanceof Inet6Address ? 1 : 0); + + boolean[] has46 = ConnectionHelper.has46(context); + + boolean prefer_ip4 = prefs.getBoolean("prefer_ip4", true); + boolean prefer_ip6 = !prefer_ip4 && prefs.getBoolean("prefer_ip6", false); + + EntityLog.log(context, EntityLog.Type.Network, "Address main=" + main + + " count=" + iaddrs.length + + " ip4=" + ip4 + " max4=" + MAX_IPV4 + " has4=" + has46[0] + " pref4=" + prefer_ip4 + + " ip6=" + ip6 + " max6=" + MAX_IPV6 + " has6=" + has46[1] + " pref6=" + prefer_ip6); + + if (prefer_ip4 || prefer_ip6) + Arrays.sort(iaddrs, new Comparator() { + @Override + public int compare(InetAddress a1, InetAddress a2) { + int s = Boolean.compare(a1 instanceof Inet4Address, a2 instanceof Inet4Address); + if (prefer_ip4) + s = -s; + return s; + } + }); + + for (InetAddress iaddr : iaddrs) { + EntityLog.log(context, EntityLog.Type.Network, "Address resolved=" + iaddr); + + if (iaddr.equals(main)) continue; - ip4++; - } - if (iaddr instanceof Inet6Address) { - if (!has46[1] || ip6 >= MAX_IPV6) - continue; - ip6++; - } + if (iaddr instanceof Inet4Address) { + if (!has46[0] || ip4 >= MAX_IPV4) + continue; + ip4++; + } + + if (iaddr instanceof Inet6Address) { + if (!has46[1] || ip6 >= MAX_IPV6) + continue; + ip6++; + } - try { - EntityLog.log(context, EntityLog.Type.Network, "Falling back to " + iaddr); - _connect(iaddr, port, require_id, user, factory); - return; - } catch (MessagingException ex1) { - ex = ex1; - EntityLog.log(context, EntityLog.Type.Network, "Fallback ex=" + - ex1.getClass().getName() + ":" + - ex1 + " " + android.util.Log.getStackTraceString(ex1)); + try { + EntityLog.log(context, EntityLog.Type.Network, "Falling back to " + iaddr); + _connect(iaddr, port, require_id, user, factory); + return; + } catch (MessagingException ex1) { + ex = ex1; + EntityLog.log(context, EntityLog.Type.Network, "Fallback ex=" + + ex1.getClass().getName() + ":" + + ex1 + " " + android.util.Log.getStackTraceString(ex1)); + } } + } catch (IOException ex1) { + throw new MessagingException(ex1.getMessage(), ex1); } - } catch (IOException ex1) { - throw new MessagingException(ex1.getMessage(), ex1); } } @@ -1111,6 +1130,8 @@ public class EmailService implements AutoCloseable { private boolean secure; private boolean ssl_harden; private boolean ssl_harden_strict; + private PrivateKey key; + private X509Certificate[] chain; private String trustedFingerprint; private SSLSocketFactory factory; private X509Certificate certificate; @@ -1125,6 +1146,8 @@ public class EmailService implements AutoCloseable { this.secure = !insecure; this.ssl_harden = ssl_harden; this.ssl_harden_strict = ssl_harden_strict; + this.key = key; + this.chain = chain; this.trustedFingerprint = fingerprint; TrustManager[] tms = SSLHelper.getTrustManagers(