FairEmail/app/src/main/java/eu/faircode/email/DnsBlockList.java

396 lines
14 KiB
Java
Raw Normal View History

2021-06-19 08:00:38 +00:00
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 <http://www.gnu.org/licenses/>.
2024-01-01 07:50:49 +00:00
Copyright 2018-2024 by Marcel Bokhorst (M66B)
2021-06-19 08:00:38 +00:00
*/
2021-06-26 13:14:59 +00:00
import android.content.Context;
import android.content.SharedPreferences;
2021-07-01 07:19:59 +00:00
import android.net.Uri;
2021-06-19 08:00:38 +00:00
import android.text.TextUtils;
import androidx.core.net.MailTo;
2021-06-26 13:14:59 +00:00
import androidx.preference.PreferenceManager;
2021-06-19 08:00:38 +00:00
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
2021-06-19 15:23:35 +00:00
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
2021-06-19 08:00:38 +00:00
import java.util.Date;
import java.util.Hashtable;
2021-06-19 15:23:35 +00:00
import java.util.List;
import java.util.Locale;
2021-06-19 08:00:38 +00:00
import java.util.Map;
2021-07-18 19:05:00 +00:00
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
2021-06-19 08:00:38 +00:00
public class DnsBlockList {
2021-07-01 16:59:05 +00:00
private static final List<BlockList> BLOCK_LISTS = Collections.unmodifiableList(Arrays.asList(
2021-06-26 12:09:41 +00:00
// 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
2021-06-19 15:23:35 +00:00
"127.0.0.2", // SBL Spamhaus SBL Data
"127.0.0.3", // SBL Spamhaus SBL CSS Data
"127.0.0.4", // XBL CBL Data
2021-06-26 12:09:41 +00:00
"127.0.0.9", // SBL Spamhaus DROP/EDROP Data
2021-06-19 15:23:35 +00:00
//127.0.0.10 PBL ISP Maintained
//127.0.0.11 PBL Spamhaus Maintained
}),
2021-06-26 12:09:41 +00:00
// https://www.spamhaus.org/dbl/
2021-07-18 19:05:00 +00:00
new BlockList(true, "Spamhaus/DBL", "dbl.spamhaus.org", false, new String[]{
2021-06-26 12:09:41 +00:00
// 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
}),
2024-02-25 19:07:29 +00:00
new BlockList(false, "UCEPROTECT/Level 1", "dnsbl-1.uceprotect.net", true, new String[]{
// https://www.uceprotect.net/en/index.php?m=6&s=11
"127.0.0.2",
}),
new BlockList(false, "UCEPROTECT/Level 2", "dnsbl-2.uceprotect.net", true, new String[]{
// https://www.uceprotect.net/en/index.php?m=6&s=11
"127.0.0.2",
}),
new BlockList(false, "UCEPROTECT/Level 3", "dnsbl-3.uceprotect.net", true, new String[]{
// https://www.uceprotect.net/en/index.php?m=6&s=11
"127.0.0.2",
}),
2022-10-28 14:00:39 +00:00
new BlockList(false, "Spamcop", "bl.spamcop.net", true, new String[]{
2021-06-19 15:23:35 +00:00
// https://www.spamcop.net/fom-serve/cache/291.html
2021-06-26 12:09:41 +00:00
"127.0.0.2",
2021-06-26 19:10:14 +00:00
}),
2021-06-26 13:15:47 +00:00
2021-06-26 19:10:14 +00:00
new BlockList(false, "Barracuda", "b.barracudacentral.org", true, new String[]{
// https://www.barracudacentral.org/rbl/how-to-use
2021-06-29 11:02:47 +00:00
"127.0.0.2",
2021-08-08 11:11:32 +00:00
}),
new BlockList(BuildConfig.DEBUG, "NordSpam", "dbl.nordspam.com", false, new String[]{
// https://www.nordspam.com/
"127.0.0.2",
2021-06-26 19:10:14 +00:00
})
2021-06-19 15:23:35 +00:00
));
2021-06-19 08:00:38 +00:00
private static final long CACHE_EXPIRY_AFTER = 3600 * 1000L; // milliseconds
2021-06-19 15:23:35 +00:00
private static final Map<String, CacheEntry> cache = new Hashtable<>();
2021-06-19 08:00:38 +00:00
static void clearCache() {
Log.i("isJunk clear cache");
synchronized (cache) {
cache.clear();
}
}
2021-06-26 13:14:59 +00:00
static void setEnabled(Context context, BlockList blocklist, boolean enabled) {
Log.i("isJunk " + blocklist.name + "=" + enabled);
2021-06-26 13:14:59 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2021-07-01 05:15:39 +00:00
if (blocklist.enabled == null || blocklist.enabled == enabled)
2021-06-26 13:50:20 +00:00
prefs.edit().remove("blocklist." + blocklist.name).apply();
else
prefs.edit().putBoolean("blocklist." + blocklist.name, enabled).apply();
clearCache();
2021-06-26 13:14:59 +00:00
}
static boolean isEnabled(Context context, BlockList blocklist) {
2021-07-01 16:59:05 +00:00
if (blocklist.enabled == null)
return false;
2021-06-26 13:14:59 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2021-07-01 16:59:05 +00:00
return prefs.getBoolean("blocklist." + blocklist.name, blocklist.enabled);
2021-06-26 13:14:59 +00:00
}
2021-06-26 13:50:20 +00:00
static void reset(Context context) {
Log.i("isJunk reset");
2021-06-26 13:50:20 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
2021-07-01 05:15:39 +00:00
for (BlockList blocklist : BLOCK_LISTS)
2021-06-26 13:50:20 +00:00
editor.remove("blocklist." + blocklist.name);
editor.apply();
clearCache();
2021-06-26 13:50:20 +00:00
}
2021-07-01 16:59:05 +00:00
static List<BlockList> getListsAvailable() {
2021-07-01 05:15:39 +00:00
List<BlockList> result = new ArrayList<>();
for (BlockList blockList : BLOCK_LISTS)
if (blockList.enabled != null)
result.add(blockList);
return result;
}
2021-07-01 16:59:05 +00:00
static List<String> getNamesEnabled(Context context) {
2021-06-20 12:20:29 +00:00
List<String> names = new ArrayList<>();
2021-07-01 05:15:39 +00:00
for (BlockList blocklist : BLOCK_LISTS)
2021-06-26 13:14:59 +00:00
if (isEnabled(context, blocklist))
2021-06-26 12:09:41 +00:00
names.add(blocklist.name);
2021-06-20 12:20:29 +00:00
return names;
}
static Boolean isJunk(Context context, String[] received) {
if (received == null || received.length == 0)
return null;
2021-07-01 07:19:59 +00:00
String host = getFromHost(MimeUtility.unfold(received[received.length - 1]));
2021-07-18 19:05:00 +00:00
if (host == null)
return null;
boolean numeric = host.startsWith("[") && host.endsWith("]");
if (numeric)
host = host.substring(1, host.length() - 1);
return isJunk(context, host, true, BLOCK_LISTS);
}
static Boolean isJunk(Context context, List<Address> addresses) {
2022-03-03 14:44:42 +00:00
if (ContactInfo.getLookupUri(addresses) != null)
return false;
2021-08-07 10:40:24 +00:00
boolean hasDomain = false;
2021-07-18 19:05:00 +00:00
for (Address address : addresses) {
String email = ((InternetAddress) address).getAddress();
String domain = UriHelper.getEmailDomain(email);
if (domain == null)
continue;
2021-08-07 10:40:24 +00:00
hasDomain = true;
2021-07-18 19:05:00 +00:00
if (isJunk(context, domain, false, BLOCK_LISTS))
return true;
}
2022-03-03 14:44:42 +00:00
2021-08-07 10:40:24 +00:00
return (hasDomain ? false : null);
2021-06-19 08:00:38 +00:00
}
2021-08-08 10:57:55 +00:00
static Boolean isJunk(Context context, Uri uri) {
String domain = null;
if ("mailto".equalsIgnoreCase(uri.getScheme())) {
MailTo email = MailTo.parse(uri.toString());
2022-03-03 14:44:42 +00:00
String to = email.getTo();
if (TextUtils.isEmpty(to))
return null;
if (ContactInfo.getLookupUri(to) != null)
return false;
2021-08-08 10:57:55 +00:00
domain = UriHelper.getEmailDomain(email.getTo());
} else
domain = uri.getHost();
if (domain == null)
return null;
2021-08-07 11:51:51 +00:00
return isJunk(context, domain, false, BLOCK_LISTS);
}
2021-07-18 19:05:00 +00:00
private static boolean isJunk(Context context, String host, boolean numeric, List<BlockList> blocklists) {
2021-06-19 15:23:35 +00:00
synchronized (cache) {
CacheEntry entry = cache.get(host);
2021-06-19 15:23:35 +00:00
if (entry != null && !entry.isExpired())
return entry.isJunk();
}
2021-06-19 08:00:38 +00:00
boolean blocked = false;
2021-06-19 15:23:35 +00:00
for (BlockList blocklist : blocklists)
2021-07-18 19:05:00 +00:00
if (isEnabled(context, blocklist) &&
blocklist.numeric == numeric &&
2024-01-05 14:27:03 +00:00
isJunk(context, host, blocklist)) {
2021-06-19 15:23:35 +00:00
blocked = true;
break;
}
synchronized (cache) {
cache.put(host, new CacheEntry(blocked));
2021-06-19 15:23:35 +00:00
}
return blocked;
}
2024-01-05 14:27:03 +00:00
private static boolean isJunk(Context context, String host, BlockList blocklist) {
2021-06-19 08:00:38 +00:00
try {
2021-06-26 12:09:41 +00:00
if (blocklist.numeric) {
long start = new Date().getTime();
2024-01-05 14:27:03 +00:00
InetAddress[] addresses = DnsHelper.getAllByName(context, host);
2021-06-26 12:09:41 +00:00
long elapsed = new Date().getTime() - start;
Log.i("isJunk resolved=" + host + " elapse=" + elapsed + " ms");
for (InetAddress addr : addresses) {
if (addr.isLoopbackAddress() ||
addr.isLinkLocalAddress() ||
addr.isSiteLocalAddress() ||
addr.isMulticastAddress()) {
Log.i("isJunk local=" + addr);
continue;
}
2021-06-19 08:00:38 +00:00
try {
2021-06-26 12:09:41 +00:00
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('.');
2021-06-19 15:23:35 +00:00
}
}
2021-06-19 08:00:38 +00:00
2021-06-26 12:09:41 +00:00
lookup.append(blocklist.address);
2021-06-19 15:23:35 +00:00
2024-01-05 14:27:03 +00:00
if (isJunk(context, lookup.toString(), blocklist.responses))
2021-06-26 12:09:41 +00:00
return true;
} catch (Throwable ex) {
Log.w(ex);
}
}
2021-07-18 19:05:00 +00:00
} else {
2021-06-26 12:09:41 +00:00
long start = new Date().getTime();
String lookup = host + "." + blocklist.address;
2024-01-05 14:27:03 +00:00
boolean junk = isJunk(context, lookup, blocklist.responses);
2021-06-26 12:09:41 +00:00
long elapsed = new Date().getTime() - start;
Log.i("isJunk" + " " + lookup + "=" + junk + " elapsed=" + elapsed);
return junk;
}
2021-06-19 08:00:38 +00:00
} catch (Throwable ex) {
Log.w(ex);
}
2021-06-19 15:23:35 +00:00
return false;
2021-06-19 08:00:38 +00:00
}
2024-01-05 14:27:03 +00:00
private static boolean isJunk(Context context, String lookup, InetAddress[] responses) {
2021-06-26 12:09:41 +00:00
long start = new Date().getTime();
InetAddress result;
try {
// Possibly blocked
2024-01-05 14:27:03 +00:00
result = DnsHelper.getByName(context, lookup);
2021-06-26 12:09:41 +00:00
} catch (UnknownHostException ignored) {
// Not blocked
result = null;
}
long elapsed = new Date().getTime() - start;
2021-06-26 14:02:23 +00:00
boolean blocked = false;
2021-06-26 12:09:41 +00:00
if (result != null && responses.length > 0) {
for (InetAddress response : responses)
if (response.equals(result)) {
blocked = true;
break;
}
2021-06-26 14:02:23 +00:00
if (!blocked)
2021-06-26 12:09:41 +00:00
result = null;
}
2021-06-26 14:02:23 +00:00
Log.w("isJunk" +
" lookup=" + lookup +
" result=" + (result == null ? null : result.getHostAddress()) +
" blocked=" + blocked +
2021-06-26 12:09:41 +00:00
" elapsed=" + elapsed);
2021-06-26 14:02:23 +00:00
return blocked;
2021-06-26 12:09:41 +00:00
}
2021-07-01 07:19:59 +00:00
private static String getFromHost(String received) {
String[] words = received.split("\\s+");
for (int i = 0; i < words.length - 1; i++)
if ("from".equalsIgnoreCase(words[i])) {
String host = words[i + 1].toLowerCase(Locale.ROOT);
if (!TextUtils.isEmpty(host))
return host;
}
return null;
}
static void show(Context context, String received) {
String host = DnsBlockList.getFromHost(MimeUtility.unfold(received));
if (host == null)
return;
if (host.startsWith("[") && host.endsWith("]"))
host = host.substring(1, host.length() - 1);
Uri uri = Uri.parse(BuildConfig.MXTOOLBOX_URI)
.buildUpon()
.appendPath("/SuperTool.aspx")
.appendQueryParameter("action", "blacklist:" + host)
.appendQueryParameter("run", "toolpage")
.build();
Helper.view(context, uri, true);
}
2021-06-19 08:00:38 +00:00
private static class CacheEntry {
private final long time;
2021-06-19 15:23:35 +00:00
private final boolean blocked;
2021-06-19 08:00:38 +00:00
2021-06-19 15:23:35 +00:00
CacheEntry(boolean blocked) {
2021-06-19 08:00:38 +00:00
this.time = new Date().getTime();
2021-06-19 15:23:35 +00:00
this.blocked = blocked;
2021-06-19 08:00:38 +00:00
}
boolean isExpired() {
return (new Date().getTime() - this.time) > CACHE_EXPIRY_AFTER;
}
boolean isJunk() {
2021-06-19 15:23:35 +00:00
return blocked;
}
}
static class BlockList {
2021-06-26 13:14:59 +00:00
int id;
2021-07-01 05:15:39 +00:00
Boolean enabled;
2021-06-20 12:20:29 +00:00
String name;
2021-06-19 15:23:35 +00:00
String address;
2021-06-26 12:09:41 +00:00
boolean numeric;
2021-06-19 15:23:35 +00:00
InetAddress[] responses;
2021-06-26 13:14:59 +00:00
private static int nextid = 1;
2021-07-01 05:15:39 +00:00
BlockList(Boolean enabled, String name, String address, boolean numeric, String[] responses) {
2021-06-26 13:14:59 +00:00
this.id = nextid++;
2021-06-26 12:09:41 +00:00
this.enabled = enabled;
2021-06-20 12:20:29 +00:00
this.name = name;
2021-06-19 15:23:35 +00:00
this.address = address;
2021-06-26 12:09:41 +00:00
this.numeric = numeric;
2021-06-19 15:23:35 +00:00
List<InetAddress> 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]);
2022-05-18 08:49:35 +00:00
if (!numeric && BuildConfig.PLAY_STORE_RELEASE)
this.enabled = null;
2021-06-19 08:00:38 +00:00
}
}
}