diff --git a/app/src/main/java/eu/faircode/email/ConnectionHelper.java b/app/src/main/java/eu/faircode/email/ConnectionHelper.java index 0e29edfa88..9066b2d9fb 100644 --- a/app/src/main/java/eu/faircode/email/ConnectionHelper.java +++ b/app/src/main/java/eu/faircode/email/ConnectionHelper.java @@ -41,6 +41,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.security.cert.Certificate; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; @@ -501,6 +502,20 @@ public class ConnectionHelper { return addr; } + static boolean isLocalIPAddress(String host) { + boolean numeric = ConnectionHelper.jni_is_numeric_address(host); + if (!numeric) + return false; + + try { + InetAddress addr = ConnectionHelper.from6to4(InetAddress.getByName(host)); + return (addr.isSiteLocalAddress() || addr.isLinkLocalAddress()); + } catch (UnknownHostException ex) { + Log.e(ex); + return false; + } + } + static List getCommonNames(Context context, String domain, int port, int timeout) throws IOException { List result = new ArrayList<>(); InetSocketAddress address = new InetSocketAddress(domain, port); diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index a0ce1e7bd4..13ba7099b4 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -2393,8 +2393,6 @@ public class HtmlHelper { try { // https://datatracker.ietf.org/doc/html/rfc2821#section-4.4 - final List words = Collections.unmodifiableList(Arrays.asList( - "from", "by", "via", "with", "id", "for")); final DateFormat DTF = Helper.getDateTimeInstance(context, DateFormat.SHORT, DateFormat.MEDIUM); ByteArrayInputStream bis = new ByteArrayInputStream(headers.getBytes()); @@ -2456,7 +2454,7 @@ public class HtmlHelper { s = ssb.length(); ssb.append(w[j]); - if (!p && words.contains(w[j].toLowerCase(Locale.ROOT))) + if (!p && MessageHelper.RECEIVED_WORDS.contains(w[j].toLowerCase(Locale.ROOT))) ssb.setSpan(new ForegroundColorSpan(textColorLink), s, ssb.length(), 0); if (w[j].endsWith(")")) diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index e1687c4e76..234f998b77 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -66,9 +66,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.IDN; -import java.net.InetAddress; import java.net.URLDecoder; -import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; @@ -135,6 +133,10 @@ public class MessageHelper { static final String HEADER_CORRELATION_ID = "X-Correlation-ID"; static final int MAX_SUBJECT_AGE = 48; // hours + static final List RECEIVED_WORDS = Collections.unmodifiableList(Arrays.asList( + "from", "by", "via", "with", "id", "for" + )); + private static final int MAX_HEADER_LENGTH = 998; private static final int MAX_MESSAGE_SIZE = 10 * 1024 * 1024; // bytes private static final long ATTACHMENT_PROGRESS_UPDATE = 1500L; // milliseconds @@ -1917,6 +1919,12 @@ public class MessageHelper { } Boolean getTLS() throws MessagingException { + Boolean tls = _getTLS(); + Log.i("--- TLS=" + tls); + return tls; + } + + Boolean _getTLS() throws MessagingException { // https://datatracker.ietf.org/doc/html/rfc2821#section-4.4 // Time-stamp-line = "Received:" FWS Stamp @@ -1943,66 +1951,94 @@ public class MessageHelper { if (received == null || received.length == 0) return null; - final List words = Collections.unmodifiableList(Arrays.asList( - "from", "by", "via", "with", "id", "for")); - // First header is last added header + Log.i("======="); for (String r : received) { String header = MimeUtility.unfold(r); + Log.i("--- header=" + header); - int s, e; - do { - s = header.indexOf('('); - e = header.indexOf(')', s); - if (s > 0 && e > 0) - header = header.substring(0, s) + header.substring((e + 1)); - } while (s > 0 && e > 0); - + // Strip date int semi = header.lastIndexOf(';'); if (semi > 0) header = header.substring(0, semi); - header = header.replaceAll("\\s+", " "); + if (header.contains("TLS")) + continue; - Log.i("--- header=" + header); + // (qmail nnn invoked by uid nnn); 1 Jan 2022 00:00:00 -0000 + if (header.contains("qmail") && header.contains("invoked by uid")) + continue; + + // Normalize white space + header = header.replaceAll("\\s+", " "); + // Get key/values String[] parts = header.split(" "); - boolean hasFrom = false; - boolean hasWith = false; - for (int j = 0; j < parts.length - 1; j++) { - Log.i("--- part " + j + " " + parts[j] + "=" + parts[j + 1]); - if ("from".equalsIgnoreCase(parts[j])) { - hasFrom = true; - String from = parts[j + 1].toLowerCase(Locale.ROOT); - boolean numeric = ConnectionHelper.jni_is_numeric_address(from); - Log.i("--- numeric=" + numeric); - if (numeric) - try { - InetAddress addr = InetAddress.getByName(from); - if (addr.isSiteLocalAddress()) - break; - Log.i("--- remote"); - } catch (UnknownHostException ex) { - Log.e(ex); - } - } else if ("with".equalsIgnoreCase(parts[j])) { - hasWith = true; - if (hasFrom) { - String with = parts[j + 1].toUpperCase(Locale.ROOT); - Log.i("--- MTP=" + with.contains("MTP") + " MTPS=" + with.contains("MTPS")); - // https://www.iana.org/assignments/mail-parameters/mail-parameters.txt - if (!with.contains("MTP")) - return null; - if (!with.contains("MTPS")) - return false; - } - break; + Map kv = new HashMap<>(); + String key = null; + for (int p = 0; p < parts.length; p++) { + String k = parts[p].toLowerCase(Locale.ROOT); + if (RECEIVED_WORDS.contains(k)) { + key = k; + if (!kv.containsKey(key)) + kv.put(key, new StringBuilder()); + } else if (key != null) { + StringBuilder sb = kv.get(key); + if (sb.length() > 0) + sb.append(' '); + sb.append(parts[p]); } - if (words.contains(parts[j])) - j++; } - if (hasFrom && !hasWith) + + // Dump + for (String k : kv.keySet()) + Log.i("--- " + k + "=" + kv.get(k)); + + // Check if 'by' local address + if (kv.containsKey("by")) { + String by = kv.get("by").toString(); + if (by.contains("localhost") || by.contains("[127.0.0.1]") || by.contains("[::1]")) + continue; + int b = by.indexOf(' '); + String host = (b < 0 ? by : by.substring(0, b)); + if (ConnectionHelper.isLocalIPAddress(host)) + continue; + } + + // Check if 'from' local address + if (kv.containsKey("from")) { + String from = kv.get("from").toString(); + if (from.contains("localhost") || from.contains("[127.0.0.1]") || from.contains("[::1]")) + continue; + int f = from.indexOf(' '); + String host = (f < 0 ? from : from.substring(0, f)); + if (ConnectionHelper.isLocalIPAddress(host)) + continue; + } + + // Check Microsoft front end transport (proxy) + // https://social.technet.microsoft.com/wiki/contents/articles/50370.exchange-2016-what-is-the-front-end-transport-service-on-the-mailbox-role.aspx + if (kv.containsKey("via")) { + String via = kv.get("via").toString(); + if ("Frontend Transport".equals(via)) + continue; + } + + // Check protocol + if (!kv.containsKey("with")) return null; + + // https://www.iana.org/assignments/mail-parameters/mail-parameters.txt + String with = kv.get("with").toString(); + int w = with.indexOf(' '); + String protocol = (w < 0 ? with : with.substring(0, w)).toLowerCase(Locale.ROOT); + + if ("mapi".equals(protocol)) // https://en.wikipedia.org/wiki/MAPI + continue; + if (!protocol.contains("mtp")) + return null; + if (!protocol.contains("mtps")) + return false; } return true;