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

330 lines
13 KiB
Java
Raw Normal View History

2020-04-15 18:08:17 +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)
2020-04-15 18:08:17 +00:00
*/
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DnsResolver;
2020-04-15 18:08:17 +00:00
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.os.Build;
2023-02-13 13:20:06 +00:00
import android.text.TextUtils;
2020-04-15 18:08:17 +00:00
import androidx.annotation.NonNull;
2022-04-25 19:31:20 +00:00
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
2020-04-15 18:08:17 +00:00
import org.xbill.DNS.Lookup;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.Message;
2022-10-12 09:59:26 +00:00
import org.xbill.DNS.NSRecord;
2020-04-15 18:08:17 +00:00
import org.xbill.DNS.Record;
import org.xbill.DNS.SOARecord;
2020-04-15 19:25:44 +00:00
import org.xbill.DNS.SRVRecord;
2020-04-15 18:08:17 +00:00
import org.xbill.DNS.SimpleResolver;
2021-07-13 08:37:02 +00:00
import org.xbill.DNS.TXTRecord;
2020-04-15 18:08:17 +00:00
import org.xbill.DNS.Type;
import java.io.IOException;
2020-04-15 18:08:17 +00:00
import java.net.InetAddress;
import java.net.UnknownHostException;
2020-04-16 16:09:55 +00:00
import java.util.ArrayList;
2020-04-15 18:08:17 +00:00
import java.util.List;
2023-11-14 06:36:46 +00:00
import java.util.Locale;
2020-09-16 18:57:13 +00:00
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
2020-04-15 18:08:17 +00:00
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
2020-04-16 15:39:29 +00:00
public class DnsHelper {
2020-04-15 18:08:17 +00:00
// https://dns.watch/
private static final String DEFAULT_DNS = "84.200.69.80";
2020-07-29 12:08:56 +00:00
private static final int CHECK_TIMEOUT = 5; // seconds
2020-07-21 07:34:56 +00:00
private static final int LOOKUP_TIMEOUT = 15; // seconds
2020-04-15 18:08:17 +00:00
2020-04-16 16:28:12 +00:00
static void checkMx(Context context, Address[] addresses) throws UnknownHostException {
if (addresses == null)
return;
for (Address address : addresses) {
String email = ((InternetAddress) address).getAddress();
2021-07-02 07:51:56 +00:00
String domain = UriHelper.getEmailDomain(email);
if (domain == null)
2020-04-16 16:28:12 +00:00
continue;
2021-12-02 19:26:19 +00:00
lookup(context, domain, "mx", CHECK_TIMEOUT);
2020-04-16 16:28:12 +00:00
}
2020-04-15 18:08:17 +00:00
}
@NonNull
2020-04-16 16:09:55 +00:00
static DnsRecord[] lookup(Context context, String name, String type) throws UnknownHostException {
2021-12-02 19:26:19 +00:00
return lookup(context, name, type, LOOKUP_TIMEOUT);
}
@NonNull
static DnsRecord[] lookup(Context context, String name, String type, int timeout) throws UnknownHostException {
2023-11-14 06:36:46 +00:00
String filter = null;
int colon = type.indexOf(':');
if (colon > 0) {
filter = type.substring(colon + 1);
type = type.substring(0, colon);
}
2020-04-16 16:09:55 +00:00
int rtype;
switch (type) {
2022-10-12 09:59:26 +00:00
case "ns":
rtype = Type.NS;
break;
2020-04-16 16:09:55 +00:00
case "mx":
rtype = Type.MX;
break;
case "soa":
rtype = Type.SOA;
break;
2020-04-16 16:09:55 +00:00
case "srv":
rtype = Type.SRV;
break;
2021-07-13 08:37:02 +00:00
case "txt":
rtype = Type.TXT;
break;
2022-04-25 19:31:20 +00:00
case "a":
rtype = Type.A;
break;
case "aaaa":
rtype = Type.AAAA;
break;
2020-04-16 16:09:55 +00:00
default:
throw new IllegalArgumentException(type);
}
2020-04-15 18:08:17 +00:00
2020-04-16 16:09:55 +00:00
try {
SimpleResolver resolver = new SimpleResolver(getDnsServer(context)) {
private IOException ex;
private Message result;
@Override
public Message send(Message query) throws IOException {
2020-09-16 18:57:13 +00:00
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
return super.send(query);
else {
Log.i("Using Android DNS resolver");
Semaphore sem = new Semaphore(0);
DnsResolver resolver = DnsResolver.getInstance();
//OPTRecord optRecord = new OPTRecord(4096, 0, 0, Flags.DO, null);
//query.addRecord(optRecord, Section.ADDITIONAL);
//query.getHeader().setFlag(Flags.AD);
2020-09-07 15:09:13 +00:00
Log.i("DNS query=" + query.toString());
resolver.rawQuery(
null,
query.toWire(),
DnsResolver.FLAG_EMPTY,
2020-09-16 18:57:13 +00:00
new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
},
null,
new DnsResolver.Callback<byte[]>() {
@Override
public void onAnswer(@NonNull byte[] answer, int rcode) {
try {
if (rcode == 0)
result = new Message(answer);
else
2020-09-16 18:57:13 +00:00
ex = new IOException("rcode=" + rcode);
} catch (Throwable e) {
ex = new IOException(e.getMessage());
2020-09-16 18:57:13 +00:00
} finally {
sem.release();
}
}
@Override
public void onError(@NonNull DnsResolver.DnsException e) {
2020-09-16 18:57:13 +00:00
try {
2022-07-27 06:03:49 +00:00
ex = new IOException(e.getMessage(), e);
2020-09-16 18:57:13 +00:00
} finally {
sem.release();
}
}
});
try {
2021-12-02 19:26:19 +00:00
if (!sem.tryAcquire(timeout, TimeUnit.SECONDS))
2020-09-16 18:57:13 +00:00
ex = new IOException("timeout");
} catch (InterruptedException e) {
ex = new IOException("interrupted");
}
2020-09-07 15:09:13 +00:00
if (ex == null) {
//ConnectivityManager cm = getSystemService(context, ConnectivityManager.class);
//Network active = (cm == null ? null : cm.getActiveNetwork());
//LinkProperties props = (active == null ? null : cm.getLinkProperties(active));
//Log.i("DNS private=" + (props == null ? null : props.isPrivateDnsActive()));
2020-09-07 15:09:13 +00:00
Log.i("DNS answer=" + result.toString() + " flags=" + result.getHeader().printFlags());
return result;
2020-09-16 18:57:13 +00:00
} else {
2022-07-27 06:03:49 +00:00
Log.i(ex);
throw ex;
2020-09-16 18:57:13 +00:00
}
}
}
};
resolver.setTimeout(timeout);
2020-07-16 21:28:32 +00:00
Lookup lookup = new Lookup(name, rtype);
2020-04-16 16:09:55 +00:00
lookup.setResolver(resolver);
Log.i("Lookup name=" + name + " @" + resolver.getAddress() + " type=" + rtype);
Record[] records = lookup.run();
2020-04-18 17:58:39 +00:00
if (lookup.getResult() == Lookup.HOST_NOT_FOUND ||
lookup.getResult() == Lookup.TYPE_NOT_FOUND)
2020-04-16 16:09:55 +00:00
throw new UnknownHostException(name);
2020-04-18 17:58:39 +00:00
else if (lookup.getResult() != Lookup.SUCCESSFUL)
2021-07-17 06:16:49 +00:00
Log.i("DNS error=" + lookup.getErrorString());
2020-04-15 18:08:17 +00:00
2020-04-16 16:09:55 +00:00
List<DnsRecord> result = new ArrayList<>();
2020-04-18 17:58:39 +00:00
if (records != null)
for (Record record : records) {
Log.i("Found record=" + record);
2022-10-12 09:59:26 +00:00
if (record instanceof NSRecord) {
NSRecord ns = (NSRecord) record;
result.add(new DnsRecord(ns.getTarget().toString(true)));
2022-10-12 09:59:26 +00:00
} else if (record instanceof MXRecord) {
2020-04-18 17:58:39 +00:00
MXRecord mx = (MXRecord) record;
result.add(new DnsRecord(mx.getTarget().toString(true)));
} else if (record instanceof SOARecord) {
SOARecord soa = (SOARecord) record;
result.add(new DnsRecord(soa.getHost().toString(true)));
2020-04-18 17:58:39 +00:00
} else if (record instanceof SRVRecord) {
SRVRecord srv = (SRVRecord) record;
result.add(new DnsRecord(srv.getTarget().toString(true), srv.getPort(), srv.getPriority(), srv.getWeight()));
2021-07-13 08:37:02 +00:00
} else if (record instanceof TXTRecord) {
TXTRecord txt = (TXTRecord) record;
2023-02-13 13:20:06 +00:00
for (Object content : txt.getStrings()) {
String text = content.toString();
2023-11-14 06:36:46 +00:00
if (filter != null &&
(TextUtils.isEmpty(text) || !text.toLowerCase(Locale.ROOT).startsWith(filter)))
continue;
2023-02-13 13:20:06 +00:00
int i = 0;
int slash = text.indexOf('\\', i);
while (slash >= 0 && slash + 4 < text.length()) {
String digits = text.substring(slash + 1, slash + 4);
if (TextUtils.isDigitsOnly(digits)) {
int k = Integer.parseInt(digits);
text = text.substring(0, slash) + (char) k + text.substring(slash + 4);
} else
i += 4;
slash = text.indexOf('\\', i);
}
2022-03-23 07:27:41 +00:00
if (result.size() > 0)
2023-04-05 05:31:14 +00:00
result.get(0).response += text;
2022-03-23 07:27:41 +00:00
else
result.add(new DnsRecord(text, 0));
2023-02-13 13:20:06 +00:00
}
2022-04-25 19:31:20 +00:00
} else if (record instanceof ARecord) {
ARecord a = (ARecord) record;
result.add(new DnsRecord(a.getAddress().getHostAddress()));
2022-04-25 19:31:20 +00:00
} else if (record instanceof AAAARecord) {
AAAARecord aaaa = (AAAARecord) record;
result.add(new DnsRecord(aaaa.getAddress().getHostAddress()));
2020-04-18 17:58:39 +00:00
} else
throw new IllegalArgumentException(record.getClass().getName());
}
2020-04-15 18:08:17 +00:00
for (DnsRecord record : result)
record.query = name;
2020-04-16 16:09:55 +00:00
return result.toArray(new DnsRecord[0]);
2023-10-11 16:04:02 +00:00
} catch (Throwable ex) {
// TextParseException
// Lookup static ctor: RuntimeException("Failed to initialize resolver")
2021-12-02 19:26:19 +00:00
Log.e(ex);
return new DnsRecord[0];
2020-04-16 16:09:55 +00:00
}
2020-04-15 18:08:17 +00:00
}
private static String getDnsServer(Context context) {
2022-04-13 20:27:33 +00:00
ConnectivityManager cm = Helper.getSystemService(context, ConnectivityManager.class);
2020-04-15 18:08:17 +00:00
if (cm == null)
return DEFAULT_DNS;
LinkProperties props = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
for (Network network : cm.getAllNetworks()) {
NetworkInfo ni = cm.getNetworkInfo(network);
if (ni != null && ni.isConnected()) {
props = cm.getLinkProperties(network);
Log.i("Old props=" + props);
break;
}
}
else {
Network active = cm.getActiveNetwork();
if (active == null)
return DEFAULT_DNS;
props = cm.getLinkProperties(active);
Log.i("New props=" + props);
}
if (props == null)
return DEFAULT_DNS;
List<InetAddress> dns = props.getDnsServers();
if (dns.size() == 0)
return DEFAULT_DNS;
else
return dns.get(0).getHostAddress();
}
2020-04-15 19:25:44 +00:00
2020-04-16 16:09:55 +00:00
static class DnsRecord {
String query;
2023-04-05 05:31:14 +00:00
String response;
2020-04-16 16:09:55 +00:00
Integer port;
Integer priority;
Integer weight;
2020-04-16 16:09:55 +00:00
DnsRecord(String response) {
2023-04-05 05:31:14 +00:00
this.response = response;
2020-04-16 16:09:55 +00:00
}
DnsRecord(String response, int port) {
2023-04-05 05:31:14 +00:00
this.response = response;
2020-04-16 16:09:55 +00:00
this.port = port;
}
DnsRecord(String response, int port, int priority, int weight) {
2023-04-05 05:31:14 +00:00
this.response = response;
this.port = port;
this.priority = priority;
this.weight = weight;
}
@NonNull
@Override
public String toString() {
return query + "=" + response + ":" + port + " " + priority + "/" + weight;
}
2020-04-16 16:09:55 +00:00
}
2020-04-15 18:08:17 +00:00
}