From a33270a24ef9757aab7e53adec97d35c88f0fffc Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 7 Jul 2021 09:24:47 +0200 Subject: [PATCH] Scan/starttls --- .../java/eu/faircode/email/EmailProvider.java | 133 +++++++++++++++--- 1 file changed, 112 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/EmailProvider.java b/app/src/main/java/eu/faircode/email/EmailProvider.java index 3c92db485e..0bb3b482d2 100644 --- a/app/src/main/java/eu/faircode/email/EmailProvider.java +++ b/app/src/main/java/eu/faircode/email/EmailProvider.java @@ -28,10 +28,13 @@ import android.text.TextUtils; import androidx.annotation.NonNull; +import com.sun.mail.util.LineInputStream; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; +import java.io.BufferedInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; @@ -39,6 +42,8 @@ import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; import java.net.URL; import java.net.UnknownHostException; import java.security.cert.Certificate; @@ -55,6 +60,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import javax.net.SocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -238,6 +244,16 @@ public class EmailProvider implements Parcelable { if (PROPRIETARY.contains(domain)) throw new IllegalArgumentException(context.getString(R.string.title_no_standard)); + if (BuildConfig.DEBUG && false) + try { + // Scan ports + Log.i("Provider from template domain=" + domain); + return fromTemplate(context, domain, discover); + } catch (Throwable ex) { + Log.w(ex); + throw new UnknownHostException(context.getString(R.string.title_setup_no_settings, domain)); + } + List providers = loadProfiles(context); for (EmailProvider provider : providers) if (provider.domain != null) @@ -794,32 +810,39 @@ public class EmailProvider implements Parcelable { for (InetAddress iaddr : InetAddress.getAllByName(host)) { InetSocketAddress address = new InetSocketAddress(iaddr, Server.this.port); - SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); - try (SSLSocket socket = (SSLSocket) factory.createSocket()) { + SocketFactory factory = (starttls + ? SocketFactory.getDefault() + : SSLSocketFactory.getDefault()); + try (Socket socket = factory.createSocket()) { EntityLog.log(context, "Connecting to " + address); socket.connect(address, SCAN_TIMEOUT); EntityLog.log(context, "Connected " + address); - if (!starttls) - try { - socket.setSoTimeout(SCAN_TIMEOUT); - socket.startHandshake(); - Certificate[] certs = socket.getSession().getPeerCertificates(); - for (Certificate cert : certs) - if (cert instanceof X509Certificate) { - List names = ConnectionHelper.getDnsNames((X509Certificate) cert); - if (ConnectionHelper.matches(host, names)) { - EntityLog.log(context, "Trusted " + address); - return true; - } + socket.setSoTimeout(SCAN_TIMEOUT); + + try (SSLSocket sslSocket = starttls + ? starttls(socket, context) + : (SSLSocket) socket) { + sslSocket.startHandshake(); + Certificate[] certs = sslSocket.getSession().getPeerCertificates(); + for (Certificate cert : certs) + if (cert instanceof X509Certificate) { + List names = ConnectionHelper.getDnsNames((X509Certificate) cert); + EntityLog.log(context, "Certificate " + address + + " " + TextUtils.join(",", names)); + if (ConnectionHelper.matches(host, names)) { + EntityLog.log(context, "Trusted " + address); + return true; } - EntityLog.log(context, "Untrusted " + address); - return null; - } catch (Throwable ex) { - // Typical: - // javax.net.ssl.SSLException: Unable to parse TLS packet header - EntityLog.log(context, "Handshake " + address + ": " + Log.formatThrowable(ex)); - } + } + + EntityLog.log(context, "Untrusted " + address); + return null; + } catch (Throwable ex) { + // Typical: + // javax.net.ssl.SSLException: Unable to parse TLS packet header + EntityLog.log(context, "Handshake " + address + ": " + Log.formatThrowable(ex)); + } EntityLog.log(context, "Reachable " + address); return true; @@ -848,6 +871,74 @@ public class EmailProvider implements Parcelable { }); } + private SSLSocket starttls(Socket socket, Context context) throws IOException { + String response; + String command; + boolean has = false; + + LineInputStream lis = + new LineInputStream( + new BufferedInputStream( + socket.getInputStream())); + + if (port == 587) { + do { + response = lis.readLine(); + if (response != null) + EntityLog.log(context, socket.getRemoteSocketAddress() + " <" + response); + } while (response != null && !response.startsWith("220 ")); + + command = "EHLO " + EmailService.getDefaultEhlo() + "\n"; + EntityLog.log(context, socket.getRemoteSocketAddress() + " >" + command); + socket.getOutputStream().write(command.getBytes()); + + do { + response = lis.readLine(); + if (response != null) { + EntityLog.log(context, socket.getRemoteSocketAddress() + " <" + response); + if (response.contains("STARTTLS")) + has = true; + } + } while (response != null && + response.length() >= 4 && response.charAt(3) == '-'); + + if (has) { + command = "STARTTLS\n"; + EntityLog.log(context, socket.getRemoteSocketAddress() + " >" + command); + socket.getOutputStream().write(command.getBytes()); + } + } else if (port == 143) { + do { + response = lis.readLine(); + if (response != null) { + EntityLog.log(context, socket.getRemoteSocketAddress() + " <" + response); + if (response.contains("STARTTLS")) + has = true; + } + } while (response != null && + !response.startsWith("* OK")); + + if (has) { + command = "A001 STARTTLS\n"; + EntityLog.log(context, socket.getRemoteSocketAddress() + " >" + command); + socket.getOutputStream().write(command.getBytes()); + } + } + + if (has) { + do { + response = lis.readLine(); + if (response != null) + EntityLog.log(context, socket.getRemoteSocketAddress() + " <" + response); + } while (response != null && + !(response.startsWith("A001 OK") || response.startsWith("220 "))); + + SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + return (SSLSocket) sslFactory.createSocket(socket, host, port, false); + } else + throw new SocketException("No STARTTLS"); + } + @NonNull @Override public String toString() {