package eu.faircode.email; /* This file is part of FairEmail. FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FairEmail. If not, see . Copyright 2018-2021 by Marcel Bokhorst (M66B) */ import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import androidx.preference.PreferenceManager; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Map; public class DnsBlockList { static final List BLOCKLISTS = Collections.unmodifiableList(Arrays.asList( // https://www.spamhaus.org/zen/ new BlockList(true, "Spamhaus/zen", "zen.spamhaus.org", true, new String[]{ // https://www.spamhaus.org/faq/section/DNSBL%20Usage#200 "127.0.0.2", // SBL Spamhaus SBL Data "127.0.0.3", // SBL Spamhaus SBL CSS Data "127.0.0.4", // XBL CBL Data "127.0.0.9", // SBL Spamhaus DROP/EDROP Data //127.0.0.10 PBL ISP Maintained //127.0.0.11 PBL Spamhaus Maintained }), // https://www.spamhaus.org/dbl/ new BlockList(true, "Spamhaus/DBL", "dbl.spamhaus.org", false, new String[]{ // https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291 "127.0.1.2", // spam domain "127.0.1.4", // phish domain "127.0.1.5", // malware domain "127.0.1.6", // botnet C&C domain "127.0.1.102", // abused legit spam "127.0.1.103", // abused spammed redirector domain "127.0.1.104", // abused legit phish "127.0.1.105", // abused legit malware "127.0.1.106", // abused legit botnet C&C }), new BlockList(true, "Spamcop", "bl.spamcop.net", true, new String[]{ // https://www.spamcop.net/fom-serve/cache/291.html "127.0.0.2", }), new BlockList(false, "Barracuda", "b.barracudacentral.org", true, new String[]{ // https://www.barracudacentral.org/rbl/how-to-use }) )); private static final long CACHE_EXPIRY_AFTER = 3600 * 1000L; // milliseconds private static final Map cache = new Hashtable<>(); static void setEnabled(Context context, BlockList blocklist, boolean enabled) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putBoolean("blocklist." + blocklist.name, enabled).apply(); synchronized (cache) { cache.clear(); } } static boolean isEnabled(Context context, BlockList blocklist) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getBoolean("blocklist." + blocklist.name, blocklist.enabled); } static List getNames(Context context) { List names = new ArrayList<>(); for (BlockList blocklist : BLOCKLISTS) if (isEnabled(context, blocklist)) names.add(blocklist.name); return names; } static boolean isJunk(Context context, String email) { if (TextUtils.isEmpty(email)) return false; int at = email.indexOf('@'); if (at < 0) return false; return isJunk(context, email.substring(at + 1), BLOCKLISTS); } private static boolean isJunk(Context context, String domain, List blocklists) { synchronized (cache) { CacheEntry entry = cache.get(domain); if (entry != null && !entry.isExpired()) return entry.isJunk(); } boolean blocked = false; for (BlockList blocklist : blocklists) if (isEnabled(context, blocklist) && isJunk(domain, blocklist)) { blocked = true; break; } synchronized (cache) { cache.put(domain, new CacheEntry(blocked)); } return blocked; } private static boolean isJunk(String domain, BlockList blocklist) { try { if (blocklist.numeric) { long start = new Date().getTime(); InetAddress[] addresses = InetAddress.getAllByName(domain); long elapsed = new Date().getTime() - start; Log.i("isJunk resolved=" + domain + " elapse=" + elapsed + " ms"); for (InetAddress addr : addresses) try { StringBuilder lookup = new StringBuilder(); if (addr instanceof Inet4Address) { byte[] a = addr.getAddress(); for (int i = 3; i >= 0; i--) lookup.append(a[i] & 0xff).append('.'); } else if (addr instanceof Inet6Address) { byte[] a = addr.getAddress(); for (int i = 15; i >= 0; i--) { int b = a[i] & 0xff; lookup.append(String.format("%01x", b & 0xf)).append('.'); lookup.append(String.format("%01x", b >> 4)).append('.'); } } lookup.append(blocklist.address); if (isJunk(lookup.toString(), blocklist.responses)) return true; } catch (Throwable ex) { Log.w(ex); } } else { long start = new Date().getTime(); String lookup = domain + "." + blocklist.address; boolean junk = isJunk(lookup, blocklist.responses); long elapsed = new Date().getTime() - start; Log.i("isJunk" + " " + lookup + "=" + junk + " elapsed=" + elapsed); return junk; } } catch (Throwable ex) { Log.w(ex); } return false; } private static boolean isJunk(String lookup, InetAddress[] responses) { long start = new Date().getTime(); InetAddress result; try { // Possibly blocked result = InetAddress.getByName(lookup); } catch (UnknownHostException ignored) { // Not blocked result = null; } long elapsed = new Date().getTime() - start; if (result != null && responses.length > 0) { boolean blocked = false; for (InetAddress response : responses) if (response.equals(result)) { blocked = true; break; } if (!blocked) { Log.w("isJunk" + " lookup=" + lookup + " result=" + result + " elapsed=" + elapsed); result = null; } } Log.i("isJunk" + " " + lookup + "=" + (result == null ? "false" : result) + " elapsed=" + elapsed); return (result != null); } private static class CacheEntry { private final long time; private final boolean blocked; CacheEntry(boolean blocked) { this.time = new Date().getTime(); this.blocked = blocked; } boolean isExpired() { return (new Date().getTime() - this.time) > CACHE_EXPIRY_AFTER; } boolean isJunk() { return blocked; } } static class BlockList { int id; boolean enabled; String name; String address; boolean numeric; InetAddress[] responses; private static int nextid = 1; BlockList(boolean enabled, String name, String address, boolean numeric, String[] responses) { this.id = nextid++; this.enabled = enabled; this.name = name; this.address = address; this.numeric = numeric; List r = new ArrayList<>(); for (String response : responses) try { r.add(InetAddress.getByName(response)); } catch (UnknownHostException ex) { Log.e(ex); } this.responses = r.toArray(new InetAddress[0]); } } }