diff --git a/build.gradle b/build.gradle index e16a28846c0d67edf3902549672da02b42692592..bcc19b7c957f6d22aa7f0b44cd499f470014d331 100644 --- a/build.gradle +++ b/build.gradle @@ -73,7 +73,7 @@ dependencies { //zxing stopped supporting Java 7 so we have to stick with 3.3.3 //https://github.com/zxing/zxing/issues/1170 implementation 'com.google.zxing:core:3.3.3' - implementation 'de.measite.minidns:minidns-hla:0.2.4' + implementation 'org.minidns:minidns-hla:1.0.4' implementation 'me.leolin:ShortcutBadger:1.1.22@aar' implementation 'org.whispersystems:signal-protocol-android:2.6.2' implementation "com.wefika:flowlayout:0.4.1" diff --git a/src/cheogram/res/drawable/shield.xml b/src/cheogram/res/drawable/shield.xml new file mode 100644 index 0000000000000000000000000000000000000000..83ae5149cd98be79ffd5c717bdbd8e51c5e7858b --- /dev/null +++ b/src/cheogram/res/drawable/shield.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/cheogram/res/drawable/shield_question.xml b/src/cheogram/res/drawable/shield_question.xml new file mode 100644 index 0000000000000000000000000000000000000000..017b47736878871a6c64743606260ff4fa5fa1e1 --- /dev/null +++ b/src/cheogram/res/drawable/shield_question.xml @@ -0,0 +1,11 @@ + + + diff --git a/src/main/java/de/gultsch/minidns/AndroidDNSClient.java b/src/main/java/de/gultsch/minidns/AndroidDNSClient.java deleted file mode 100644 index 2c3731871c1f7ab7cf2fb5eaa869867dc31fd93c..0000000000000000000000000000000000000000 --- a/src/main/java/de/gultsch/minidns/AndroidDNSClient.java +++ /dev/null @@ -1,221 +0,0 @@ -package de.gultsch.minidns; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.LinkProperties; -import android.net.Network; -import android.os.Build; -import android.util.Log; - -import androidx.collection.LruCache; - -import com.google.common.base.Objects; -import com.google.common.base.Strings; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; - -import de.measite.minidns.AbstractDNSClient; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Record; -import de.measite.minidns.record.Data; - -import eu.siacs.conversations.Config; - -import java.io.IOException; -import java.net.InetAddress; -import java.time.Duration; -import java.util.Collections; -import java.util.List; - -public class AndroidDNSClient extends AbstractDNSClient { - - private static final long DNS_MAX_TTL = 86_400L; - - private static final LruCache QUERY_CACHE = - new LruCache<>(1024); - private final Context context; - private final NetworkDataSource networkDataSource = new NetworkDataSource(); - private boolean askForDnssec = false; - - public AndroidDNSClient(final Context context) { - super(); - this.setDataSource(networkDataSource); - this.context = context; - } - - private static String getPrivateDnsServerName(final LinkProperties linkProperties) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - return linkProperties.getPrivateDnsServerName(); - } else { - return null; - } - } - - private static boolean isPrivateDnsActive(final LinkProperties linkProperties) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - return linkProperties.isPrivateDnsActive(); - } else { - return false; - } - } - - @Override - protected DNSMessage.Builder newQuestion(final DNSMessage.Builder message) { - message.setRecursionDesired(true); - message.getEdnsBuilder() - .setUdpPayloadSize(networkDataSource.getUdpPayloadSize()) - .setDnssecOk(askForDnssec); - return message; - } - - @Override - protected DNSMessage query(final DNSMessage.Builder queryBuilder) throws IOException { - final DNSMessage question = newQuestion(queryBuilder).build(); - for (final DNSServer dnsServer : getDNSServers()) { - final QuestionServerTuple cacheKey = new QuestionServerTuple(dnsServer, question); - final DNSMessage cachedResponse = queryCache(cacheKey); - if (cachedResponse != null) { - return cachedResponse; - } - final DNSMessage response = this.networkDataSource.query(question, dnsServer); - if (response == null) { - continue; - } - switch (response.responseCode) { - case NO_ERROR: - case NX_DOMAIN: - break; - default: - continue; - } - cacheQuery(cacheKey, response); - return response; - } - return null; - } - - public boolean isAskForDnssec() { - return askForDnssec; - } - - public void setAskForDnssec(boolean askForDnssec) { - this.askForDnssec = askForDnssec; - } - - private List getDNSServers() { - final ImmutableList.Builder dnsServerBuilder = new ImmutableList.Builder<>(); - final ConnectivityManager connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - final Network[] networks = getActiveNetworks(connectivityManager); - for (final Network network : networks) { - final LinkProperties linkProperties = connectivityManager.getLinkProperties(network); - if (linkProperties == null) { - continue; - } - final String privateDnsServerName = getPrivateDnsServerName(linkProperties); - if (Strings.isNullOrEmpty(privateDnsServerName)) { - final boolean isPrivateDns = isPrivateDnsActive(linkProperties); - for (final InetAddress dnsServer : linkProperties.getDnsServers()) { - if (isPrivateDns) { - dnsServerBuilder.add(new DNSServer(dnsServer, Transport.TLS)); - } else { - dnsServerBuilder.add(new DNSServer(dnsServer)); - } - } - } else { - dnsServerBuilder.add(new DNSServer(privateDnsServerName, Transport.TLS)); - } - } - return dnsServerBuilder.build(); - } - - private Network[] getActiveNetworks(final ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return new Network[0]; - } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - final Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork != null) { - return new Network[] {activeNetwork}; - } - } - return connectivityManager.getAllNetworks(); - } - - private DNSMessage queryCache(final QuestionServerTuple key) { - final DNSMessage cachedResponse; - synchronized (QUERY_CACHE) { - cachedResponse = QUERY_CACHE.get(key); - if (cachedResponse == null) { - return null; - } - final long expiresIn = expiresIn(cachedResponse); - if (expiresIn < 0) { - QUERY_CACHE.remove(key); - return null; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Log.d( - Config.LOGTAG, - "DNS query came from cache. expires in " + Duration.ofMillis(expiresIn)); - } - } - return cachedResponse; - } - - private void cacheQuery(final QuestionServerTuple key, final DNSMessage response) { - if (response.receiveTimestamp <= 0) { - return; - } - synchronized (QUERY_CACHE) { - QUERY_CACHE.put(key, response); - } - } - - private static long ttl(final DNSMessage dnsMessage) { - final List> answerSection = dnsMessage.answerSection; - if (answerSection == null || answerSection.isEmpty()) { - final List> authoritySection = dnsMessage.authoritySection; - if (authoritySection == null || authoritySection.isEmpty()) { - return 0; - } else { - return Collections.min(Collections2.transform(authoritySection, d -> d.ttl)); - } - - } else { - return Collections.min(Collections2.transform(answerSection, d -> d.ttl)); - } - } - - private static long expiresAt(final DNSMessage dnsMessage) { - return dnsMessage.receiveTimestamp + (Math.min(DNS_MAX_TTL, ttl(dnsMessage)) * 1000L); - } - - private static long expiresIn(final DNSMessage dnsMessage) { - return expiresAt(dnsMessage) - System.currentTimeMillis(); - } - - private static class QuestionServerTuple { - private final DNSServer dnsServer; - private final DNSMessage question; - - private QuestionServerTuple(final DNSServer dnsServer, final DNSMessage question) { - this.dnsServer = dnsServer; - this.question = question.asNormalizedVersion(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - QuestionServerTuple that = (QuestionServerTuple) o; - return Objects.equal(dnsServer, that.dnsServer) - && Objects.equal(question, that.question); - } - - @Override - public int hashCode() { - return Objects.hashCode(dnsServer, question); - } - } -} diff --git a/src/main/java/de/gultsch/minidns/DNSServer.java b/src/main/java/de/gultsch/minidns/DNSServer.java deleted file mode 100644 index 7486ec2c60c6a6601220b37bf19efbfc96f9e888..0000000000000000000000000000000000000000 --- a/src/main/java/de/gultsch/minidns/DNSServer.java +++ /dev/null @@ -1,104 +0,0 @@ -package de.gultsch.minidns; - -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.collect.Iterables; - -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.annotation.Nonnull; - -public final class DNSServer { - - public final InetAddress inetAddress; - public final String hostname; - public final int port; - public final List transports; - - public DNSServer(InetAddress inetAddress, Integer port, Transport transport) { - this.inetAddress = inetAddress; - this.port = port == null ? 0 : port; - this.transports = Collections.singletonList(transport); - this.hostname = null; - } - - public DNSServer(final String hostname, final Integer port, final Transport transport) { - Preconditions.checkArgument( - Arrays.asList(Transport.HTTPS, Transport.TLS).contains(transport), - "hostname validation only works with TLS based transports"); - this.hostname = hostname; - this.port = port == null ? 0 : port; - this.transports = Collections.singletonList(transport); - this.inetAddress = null; - } - - public DNSServer(final String hostname, final Transport transport) { - this(hostname, Transport.DEFAULT_PORTS.get(transport), transport); - } - - public DNSServer(InetAddress inetAddress, Transport transport) { - this(inetAddress, Transport.DEFAULT_PORTS.get(transport), transport); - } - - public DNSServer(final InetAddress inetAddress) { - this(inetAddress, 53, Arrays.asList(Transport.UDP, Transport.TCP)); - } - - public DNSServer(final InetAddress inetAddress, int port, List transports) { - this(inetAddress, null, port, transports); - } - - private DNSServer( - final InetAddress inetAddress, - final String hostname, - final int port, - final List transports) { - this.inetAddress = inetAddress; - this.hostname = hostname; - this.port = port; - this.transports = transports; - } - - public Transport uniqueTransport() { - return Iterables.getOnlyElement(this.transports); - } - - public DNSServer asUniqueTransport(final Transport transport) { - Preconditions.checkArgument( - this.transports.contains(transport), - "This DNS server does not have transport ", - transport); - return new DNSServer(inetAddress, hostname, port, Collections.singletonList(transport)); - } - - @Override - @Nonnull - public String toString() { - return MoreObjects.toStringHelper(this) - .add("inetAddress", inetAddress) - .add("hostname", hostname) - .add("port", port) - .add("transports", transports) - .toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DNSServer dnsServer = (DNSServer) o; - return port == dnsServer.port - && Objects.equal(inetAddress, dnsServer.inetAddress) - && Objects.equal(hostname, dnsServer.hostname) - && Objects.equal(transports, dnsServer.transports); - } - - @Override - public int hashCode() { - return Objects.hashCode(inetAddress, hostname, port, transports); - } -} diff --git a/src/main/java/de/gultsch/minidns/DNSSocket.java b/src/main/java/de/gultsch/minidns/DNSSocket.java deleted file mode 100644 index a3403115a73c097e231626436453f59ac3b7a0bc..0000000000000000000000000000000000000000 --- a/src/main/java/de/gultsch/minidns/DNSSocket.java +++ /dev/null @@ -1,199 +0,0 @@ -package de.gultsch.minidns; - -import android.util.Log; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; - -import de.measite.minidns.DNSMessage; - -import eu.siacs.conversations.Config; - -import org.conscrypt.OkHostnameVerifier; - -import java.io.Closeable; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -final class DNSSocket implements Closeable { - - public static final int QUERY_TIMEOUT = 5_000; - - private final Semaphore semaphore = new Semaphore(1); - private final Map> inFlightQueries = new HashMap<>(); - private final Socket socket; - private final DataInputStream dataInputStream; - private final DataOutputStream dataOutputStream; - - private DNSSocket( - final Socket socket, - final DataInputStream dataInputStream, - final DataOutputStream dataOutputStream) { - this.socket = socket; - this.dataInputStream = dataInputStream; - this.dataOutputStream = dataOutputStream; - new Thread(this::readDNSMessages).start(); - } - - private void readDNSMessages() { - try { - while (socket.isConnected()) { - final DNSMessage response = readDNSMessage(); - final SettableFuture future; - synchronized (inFlightQueries) { - future = inFlightQueries.remove(response.id); - } - if (future != null) { - future.set(response); - } else { - Log.e(Config.LOGTAG, "no in flight query found for response id " + response.id); - } - } - evictInFlightQueries(new EOFException()); - } catch (final IOException e) { - evictInFlightQueries(e); - } - } - - private void evictInFlightQueries(final Exception e) { - synchronized (inFlightQueries) { - final Iterator>> iterator = - inFlightQueries.entrySet().iterator(); - while (iterator.hasNext()) { - final Map.Entry> entry = iterator.next(); - entry.getValue().setException(e); - iterator.remove(); - } - } - } - - private static DNSSocket of(final Socket socket) throws IOException { - final DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); - final DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); - return new DNSSocket(socket, dataInputStream, dataOutputStream); - } - - public static DNSSocket connect(final DNSServer dnsServer) throws IOException { - switch (dnsServer.uniqueTransport()) { - case TCP: - return connectTcpSocket(dnsServer); - case TLS: - return connectTlsSocket(dnsServer); - default: - throw new IllegalStateException("This is not a socket based transport"); - } - } - - private static DNSSocket connectTcpSocket(final DNSServer dnsServer) throws IOException { - Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TCP); - final SocketAddress socketAddress = - new InetSocketAddress(dnsServer.inetAddress, dnsServer.port); - final Socket socket = new Socket(); - socket.connect(socketAddress, QUERY_TIMEOUT / 2); - socket.setSoTimeout(QUERY_TIMEOUT); - return DNSSocket.of(socket); - } - - private static DNSSocket connectTlsSocket(final DNSServer dnsServer) throws IOException { - Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TLS); - final SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(); - if (Strings.isNullOrEmpty(dnsServer.hostname)) { - final SocketAddress socketAddress = - new InetSocketAddress(dnsServer.inetAddress, dnsServer.port); - sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2); - sslSocket.setSoTimeout(QUERY_TIMEOUT); - sslSocket.startHandshake(); - } else { - final SocketAddress socketAddress = new InetSocketAddress(dnsServer.hostname, dnsServer.port); - sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2); - sslSocket.setSoTimeout(QUERY_TIMEOUT); - sslSocket.startHandshake(); - final SSLSession session = sslSocket.getSession(); - final Certificate[] peerCertificates = session.getPeerCertificates(); - if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate)) { - throw new IOException("Peer did not provide X509 certificates"); - } - final X509Certificate certificate = (X509Certificate) peerCertificates[0]; - if (!OkHostnameVerifier.strictInstance().verify(dnsServer.hostname, certificate)) { - throw new SSLPeerUnverifiedException("Peer did not provide valid certificates"); - } - } - return DNSSocket.of(sslSocket); - } - - public DNSMessage query(final DNSMessage query) throws IOException, InterruptedException { - try { - return queryAsync(query).get(QUERY_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (final ExecutionException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException) { - throw (IOException) cause; - } else { - throw new IOException(e); - } - } catch (final TimeoutException e) { - throw new IOException(e); - } - } - - public ListenableFuture queryAsync(final DNSMessage query) - throws InterruptedException, IOException { - final SettableFuture responseFuture = SettableFuture.create(); - synchronized (this.inFlightQueries) { - this.inFlightQueries.put(query.id, responseFuture); - } - this.semaphore.acquire(); - try { - query.writeTo(this.dataOutputStream); - this.dataOutputStream.flush(); - } finally { - this.semaphore.release(); - } - return responseFuture; - } - - private DNSMessage readDNSMessage() throws IOException { - final int length = this.dataInputStream.readUnsignedShort(); - byte[] data = new byte[length]; - int read = 0; - while (read < length) { - read += this.dataInputStream.read(data, read, length - read); - } - return new DNSMessage(data); - } - - @Override - public void close() throws IOException { - this.socket.close(); - } - - public void closeQuietly() { - try { - this.socket.close(); - } catch (final IOException ignored) { - - } - } -} diff --git a/src/main/java/de/gultsch/minidns/NetworkDataSource.java b/src/main/java/de/gultsch/minidns/NetworkDataSource.java deleted file mode 100644 index 93909891d54a37b7a7c0b5570cb3d2fb7f8f885e..0000000000000000000000000000000000000000 --- a/src/main/java/de/gultsch/minidns/NetworkDataSource.java +++ /dev/null @@ -1,160 +0,0 @@ -package de.gultsch.minidns; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.cache.RemovalListener; -import com.google.common.collect.ImmutableList; - -import de.measite.minidns.DNSMessage; -import de.measite.minidns.MiniDNSException; -import de.measite.minidns.source.DNSDataSource; -import de.measite.minidns.util.MultipleIoException; - -import eu.siacs.conversations.Config; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -public class NetworkDataSource extends DNSDataSource { - - private static final LoadingCache socketCache = - CacheBuilder.newBuilder() - .removalListener( - (RemovalListener) - notification -> { - final DNSServer dnsServer = notification.getKey(); - final DNSSocket dnsSocket = notification.getValue(); - if (dnsSocket == null) { - return; - } - Log.d(Config.LOGTAG, "closing connection to " + dnsServer); - dnsSocket.closeQuietly(); - }) - .expireAfterAccess(5, TimeUnit.MINUTES) - .build( - new CacheLoader() { - @Override - @NonNull - public DNSSocket load(@NonNull final DNSServer dnsServer) - throws Exception { - Log.d(Config.LOGTAG, "establishing connection to " + dnsServer); - return DNSSocket.connect(dnsServer); - } - }); - - private static List transportsForPort(final int port) { - final ImmutableList.Builder transportBuilder = new ImmutableList.Builder<>(); - for (final Map.Entry entry : Transport.DEFAULT_PORTS.entrySet()) { - if (entry.getValue().equals(port)) { - transportBuilder.add(entry.getKey()); - } - } - return transportBuilder.build(); - } - - @Override - public DNSMessage query(final DNSMessage message, final InetAddress address, final int port) - throws IOException { - final List transports = transportsForPort(port); - Log.w( - Config.LOGTAG, - "using legacy DataSource interface. guessing transports " - + transports - + " from port"); - if (transports.isEmpty()) { - throw new IOException(String.format("No transports found for port %d", port)); - } - return query(message, new DNSServer(address, port, transports)); - } - - public DNSMessage query(final DNSMessage message, final DNSServer dnsServer) - throws IOException { - Log.d(Config.LOGTAG, "using " + dnsServer); - final List ioExceptions = new ArrayList<>(); - for (final Transport transport : dnsServer.transports) { - try { - final DNSMessage response = - queryWithUniqueTransport(message, dnsServer.asUniqueTransport(transport)); - if (response != null && !response.truncated) { - return response; - } - } catch (final IOException e) { - ioExceptions.add(e); - } catch (final InterruptedException e) { - throw new IOException(e); - } - } - MultipleIoException.throwIfRequired(ioExceptions); - return null; - } - - private DNSMessage queryWithUniqueTransport(final DNSMessage message, final DNSServer dnsServer) - throws IOException, InterruptedException { - final Transport transport = dnsServer.uniqueTransport(); - switch (transport) { - case UDP: - return queryUdp(message, dnsServer.inetAddress, dnsServer.port); - case TCP: - case TLS: - return queryDnsSocket(message, dnsServer); - default: - throw new IOException( - String.format("Transport %s has not been implemented", transport)); - } - } - - protected DNSMessage queryUdp( - final DNSMessage message, final InetAddress address, final int port) - throws IOException { - final DatagramPacket request = message.asDatagram(address, port); - final byte[] buffer = new byte[udpPayloadSize]; - try (final DatagramSocket socket = new DatagramSocket()) { - socket.setSoTimeout(timeout); - socket.send(request); - final DatagramPacket response = new DatagramPacket(buffer, buffer.length); - socket.receive(response); - DNSMessage dnsMessage = new DNSMessage(response.getData()); - if (dnsMessage.id != message.id) { - throw new MiniDNSException.IdMismatch(message, dnsMessage); - } - return dnsMessage; - } - } - - protected DNSMessage queryDnsSocket(final DNSMessage message, final DNSServer dnsServer) - throws IOException, InterruptedException { - final DNSSocket cachedDnsSocket = socketCache.getIfPresent(dnsServer); - if (cachedDnsSocket != null) { - try { - return cachedDnsSocket.query(message); - } catch (final IOException e) { - Log.d( - Config.LOGTAG, - "IOException occurred at cached socket. invalidating and falling through to new socket creation"); - socketCache.invalidate(dnsServer); - } - } - try { - return socketCache.get(dnsServer).query(message); - } catch (final ExecutionException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException) { - throw (IOException) cause; - } else { - throw new IOException(cause); - } - } - } -} diff --git a/src/main/java/de/gultsch/minidns/Transport.java b/src/main/java/de/gultsch/minidns/Transport.java deleted file mode 100644 index 3aabfacaa57177e6c3d1d6f5250f8303a3120063..0000000000000000000000000000000000000000 --- a/src/main/java/de/gultsch/minidns/Transport.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.gultsch.minidns; - -import com.google.common.collect.ImmutableMap; - -import java.util.Map; - -public enum Transport { - UDP, - TCP, - TLS, - HTTPS; - - public static final Map DEFAULT_PORTS; - - static { - final ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put(Transport.UDP, 53); - builder.put(Transport.TCP, 53); - builder.put(Transport.TLS, 853); - builder.put(Transport.HTTPS, 443); - DEFAULT_PORTS = builder.build(); - } -} diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index dab84da05ead6515da48abcd1391b401d15ece9d..99054c2fdf5154ab5f0e7bbd9637ae87ef8fce4d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1265,6 +1265,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { this.binding.otherDeviceKeysCard.setVisibility(View.GONE); } + this.binding.verificationBox.setVisibility(View.VISIBLE); + if (mAccount.getXmppConnection() != null && mAccount.getXmppConnection().resolverAuthenticated()) { + this.binding.verificationMessage.setText("DNSSEC Verified"); + this.binding.verificationIndicator.setImageResource(R.drawable.shield); + } else { + this.binding.verificationMessage.setText("Not DNSSEC Verified"); + this.binding.verificationIndicator.setImageResource(R.drawable.shield_question); + } } else { final TextInputLayout errorLayout; if (this.mAccount.errorStatus()) { @@ -1287,6 +1295,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat removeErrorsOnAllBut(errorLayout); this.binding.stats.setVisibility(View.GONE); this.binding.otherDeviceKeysCard.setVisibility(View.GONE); + this.binding.verificationBox.setVisibility(View.GONE); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 21d1e9f0f116c962a7acdb11f580f08b0f959ba5..fe16d7aba5b8f1eaea840a7b3ac8d6cc0bf8de1d 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -69,6 +69,11 @@ public class AccountAdapter extends ArrayAdapter { viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError)); break; } + if (account.getXmppConnection() != null && account.getXmppConnection().resolverAuthenticated()) { + viewHolder.binding.verificationIndicator.setImageResource(R.drawable.shield); + } else { + viewHolder.binding.verificationIndicator.setImageResource(R.drawable.shield_question); + } final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener(null); viewHolder.binding.tglAccountStatus.setChecked(!isDisabled); diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java b/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java index d8c46b08f7ae2b1b3b71bd2abd0a58752fb58719..8f31f2eecc39e7fb7badfdda6d1b9607e07afb48 100644 --- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java +++ b/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java @@ -19,17 +19,20 @@ import java.net.InetAddress; import java.util.HashSet; import java.util.logging.Level; -import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; -import de.measite.minidns.dnsserverlookup.AndroidUsingReflection; -import de.measite.minidns.dnsserverlookup.DNSServerLookupMechanism; -import de.measite.minidns.util.PlatformDetection; +import java.util.ArrayList; +import java.util.List; + +import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism; +import org.minidns.dnsserverlookup.AndroidUsingReflection; +import org.minidns.dnsserverlookup.DnsServerLookupMechanism; +import org.minidns.util.PlatformDetection; /** * Try to retrieve the list of DNS server by executing getprop. */ -public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism { +public class AndroidUsingExecLowPriority extends AbstractDnsServerLookupMechanism { - public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority(); + public static final DnsServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority(); public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1; private AndroidUsingExecLowPriority() { @@ -37,7 +40,7 @@ public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanis } @Override - public String[] getDnsServerAddresses() { + public List getDnsServerAddresses() { try { Process process = Runtime.getRuntime().exec("getprop"); InputStream inputStream = process.getInputStream(); @@ -76,7 +79,7 @@ public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanis } } if (server.size() > 0) { - return server.toArray(new String[server.size()]); + return new ArrayList<>(server); } } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e); diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java b/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java index 658e7abcd7e8d1046fc51b02a3944e48aacf265f..e3bd5e7af59a1728aa04625e2274ea0ada0c3dbb 100644 --- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java +++ b/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java @@ -14,10 +14,10 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.List; -import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; -import de.measite.minidns.dnsserverlookup.AndroidUsingExec; +import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism; +import org.minidns.dnsserverlookup.AndroidUsingExec; -public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism { +public class AndroidUsingLinkProperties extends AbstractDnsServerLookupMechanism { private final Context context; @@ -33,11 +33,11 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism @Override @TargetApi(21) - public String[] getDnsServerAddresses() { + public List getDnsServerAddresses() { final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); final Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); if (networks == null) { - return new String[0]; + return new ArrayList<>(); } final Network activeNetwork = getActiveNetwork(connectivityManager); final List servers = new ArrayList<>(); @@ -58,7 +58,7 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers())); } } - return servers.toArray(new String[0]); + return servers; } @TargetApi(23) diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 915209413e51b77b85c7c8f9f2492087dce0f747..4193f84e3079917b688dfd38b39c0d663bf9a6c2 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -20,26 +20,26 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import de.gultsch.minidns.AndroidDNSClient; -import de.measite.minidns.AbstractDNSClient; -import de.measite.minidns.DNSCache; -import de.measite.minidns.DNSClient; -import de.measite.minidns.DNSName; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.cache.LRUCache; -import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException; -import de.measite.minidns.dnsserverlookup.AndroidUsingExec; -import de.measite.minidns.hla.DnssecResolverApi; -import de.measite.minidns.hla.ResolverApi; -import de.measite.minidns.hla.ResolverResult; -import de.measite.minidns.iterative.ReliableDNSClient; -import de.measite.minidns.record.A; -import de.measite.minidns.record.AAAA; -import de.measite.minidns.record.CNAME; -import de.measite.minidns.record.Data; -import de.measite.minidns.record.InternetAddressRR; -import de.measite.minidns.record.SRV; +//import de.gultsch.minidns.AndroidDNSClient; +import org.minidns.AbstractDnsClient; +import org.minidns.DnsCache; +import org.minidns.DnsClient; +import org.minidns.dnsname.DnsName; +import org.minidns.dnsmessage.Question; +import org.minidns.record.Record; +import org.minidns.cache.LruCache; +import org.minidns.dnssec.DnssecResultNotAuthenticException; +import org.minidns.dnsserverlookup.AndroidUsingExec; +import org.minidns.hla.DnssecResolverApi; +import org.minidns.hla.ResolverApi; +import org.minidns.hla.ResolverResult; +import org.minidns.iterative.ReliableDnsClient; +import org.minidns.record.A; +import org.minidns.record.AAAA; +import org.minidns.record.CNAME; +import org.minidns.record.Data; +import org.minidns.record.InternetAddressRR; +import org.minidns.record.SRV; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.services.XmppConnectionService; @@ -57,34 +57,18 @@ public class Resolver { public static void init(XmppConnectionService service) { Resolver.SERVICE = service; - DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE); - DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE); - DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service)); - final AbstractDNSClient client = ResolverApi.INSTANCE.getClient(); - if (client instanceof ReliableDNSClient) { - disableHardcodedDnsServers((ReliableDNSClient) client); - } - } - - private static void disableHardcodedDnsServers(ReliableDNSClient reliableDNSClient) { - try { - final Field dnsClientField = ReliableDNSClient.class.getDeclaredField("dnsClient"); - dnsClientField.setAccessible(true); - final DNSClient dnsClient = (DNSClient) dnsClientField.get(reliableDNSClient); - if (dnsClient != null) { - dnsClient.getDataSource().setTimeout(3000); - } - final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers"); - useHardcodedDnsServers.setAccessible(true); - useHardcodedDnsServers.setBoolean(dnsClient, false); - } catch (NoSuchFieldException | IllegalAccessException e) { - Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); + DnsClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE); + DnsClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE); + DnsClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service)); + final AbstractDnsClient client = ResolverApi.INSTANCE.getClient(); + if (client instanceof ReliableDnsClient) { + ((ReliableDnsClient) client).setUseHardcodedDnsServers(false); } } public static List fromHardCoded(final String hostname, final int port) { final Result result = new Result(); - result.hostname = DNSName.from(hostname); + result.hostname = DnsName.from(hostname); result.port = port; result.directTls = useDirectTls(port); result.authenticated = true; @@ -92,12 +76,12 @@ public class Resolver { } public static void checkDomain(final Jid jid) { - DNSName.from(jid.getDomain()); + DnsName.from(jid.getDomain()); } public static boolean invalidHostname(final String hostname) { try { - DNSName.from(hostname); + DnsName.from(hostname); return false; } catch (IllegalArgumentException e) { return true; @@ -105,11 +89,11 @@ public class Resolver { } public static void clearCache() { - final AbstractDNSClient client = ResolverApi.INSTANCE.getClient(); - final DNSCache dnsCache = client.getCache(); - if (dnsCache instanceof LRUCache) { + final AbstractDnsClient client = ResolverApi.INSTANCE.getClient(); + final DnsCache dnsCache = client.getCache(); + if (dnsCache instanceof LruCache) { Log.d(Config.LOGTAG,"clearing DNS cache"); - ((LRUCache) dnsCache).clear(); + ((LruCache) dnsCache).clear(); } } @@ -151,7 +135,7 @@ public class Resolver { } }); threads[2] = new Thread(() -> { - List list = resolveNoSrvRecords(DNSName.from(domain), true); + List list = resolveNoSrvRecords(DnsName.from(domain), true); synchronized (fallbackResults) { fallbackResults.addAll(list); } @@ -193,6 +177,7 @@ public class Resolver { Result result = new Result(); result.ip = InetAddress.getByName(domain); result.port = DEFAULT_PORT_XMPP; + result.authenticated = true; return Collections.singletonList(result); } catch (UnknownHostException e) { return Collections.emptyList(); @@ -200,7 +185,7 @@ public class Resolver { } private static List resolveSrv(String domain, final boolean directTls) throws IOException { - DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); + DnsName dnsName = DnsName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); ResolverResult result = resolveWithFallback(dnsName, SRV.class); final List results = new ArrayList<>(); final List threads = new ArrayList<>(); @@ -243,10 +228,10 @@ public class Resolver { private static List resolveIp(SRV srv, Class type, boolean authenticated, boolean directTls) { List list = new ArrayList<>(); try { - ResolverResult results = resolveWithFallback(srv.name, type, authenticated); + ResolverResult results = resolveWithFallback(srv.name, type); for (D record : results.getAnswersOrEmptySet()) { Result resolverResult = Result.fromRecord(srv, directTls); - resolverResult.authenticated = results.isAuthenticData() && authenticated; //TODO technically it doesn’t matter if the IP was authenticated + resolverResult.authenticated = results.isAuthenticData() && authenticated; resolverResult.ip = record.getInetAddress(); list.add(resolverResult); } @@ -256,18 +241,29 @@ public class Resolver { return list; } - private static List resolveNoSrvRecords(DNSName dnsName, boolean withCnames) { + private static List resolveNoSrvRecords(DnsName dnsName, boolean withCnames) { List results = new ArrayList<>(); try { - for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) { - results.add(Result.createDefault(dnsName, a.getInetAddress())); + ResolverResult aResult = resolveWithFallback(dnsName, A.class); + Log.d("WUTr", "" + aResult.isAuthenticData() + " " + aResult.getAnswersOrEmptySet()); + for (A a : aResult.getAnswersOrEmptySet()) { + Result r = Result.createDefault(dnsName, a.getInetAddress()); + r.authenticated = aResult.isAuthenticData(); + results.add(r); } - for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) { - results.add(Result.createDefault(dnsName, aaaa.getInetAddress())); + ResolverResult aaaaResult = resolveWithFallback(dnsName, AAAA.class); + for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) { + Result r = Result.createDefault(dnsName, aaaa.getInetAddress()); + r.authenticated = aaaaResult.isAuthenticData(); + results.add(r); } if (results.size() == 0 && withCnames) { - for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) { - results.addAll(resolveNoSrvRecords(cname.name, false)); + ResolverResult cnameResult = resolveWithFallback(dnsName, CNAME.class); + for (CNAME cname : cnameResult.getAnswersOrEmptySet()) { + for (Result r : resolveNoSrvRecords(cname.name, false)) { + r.authenticated = r.authenticated && cnameResult.isAuthenticData(); + results.add(r); + } } } } catch (final Throwable throwable) { @@ -279,21 +275,10 @@ public class Resolver { return results; } - private static ResolverResult resolveWithFallback(DNSName dnsName, Class type) throws IOException { - return resolveWithFallback(dnsName, type, validateHostname()); - } - - private static ResolverResult resolveWithFallback(DNSName dnsName, Class type, boolean validateHostname) throws IOException { + private static ResolverResult resolveWithFallback(DnsName dnsName, Class type) throws IOException { final Question question = new Question(dnsName, Record.TYPE.getType(type)); - if (!validateHostname) { - final AndroidDNSClient androidDNSClient = new AndroidDNSClient(SERVICE); - final ResolverApi resolverApi = new ResolverApi(androidDNSClient); - return resolverApi.resolve(question); - } try { - return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question); - } catch (DNSSECResultNotAuthenticException e) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e); + return DnssecResolverApi.INSTANCE.resolve(question); } catch (IOException e) { throw e; } catch (Throwable throwable) { @@ -302,10 +287,6 @@ public class Resolver { return ResolverApi.INSTANCE.resolve(question); } - private static boolean validateHostname() { - return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname); - } - public static class Result implements Comparable { public static final String DOMAIN = "domain"; public static final String IP = "ip"; @@ -315,7 +296,7 @@ public class Resolver { public static final String DIRECT_TLS = "directTls"; public static final String AUTHENTICATED = "authenticated"; private InetAddress ip; - private DNSName hostname; + private DnsName hostname; private int port = DEFAULT_PORT_XMPP; private boolean directTls = false; private boolean authenticated = false; @@ -330,7 +311,7 @@ public class Resolver { return result; } - static Result createDefault(DNSName hostname, InetAddress ip) { + static Result createDefault(DnsName hostname, InetAddress ip) { Result result = new Result(); result.port = DEFAULT_PORT_XMPP; result.hostname = hostname; @@ -338,7 +319,7 @@ public class Resolver { return result; } - static Result createDefault(DNSName hostname) { + static Result createDefault(DnsName hostname) { return createDefault(hostname, null); } @@ -350,7 +331,7 @@ public class Resolver { result.ip = null; } final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); - result.hostname = hostname == null ? null : DNSName.from(hostname); + result.hostname = hostname == null ? null : DnsName.from(hostname); result.port = cursor.getInt(cursor.getColumnIndex(PORT)); result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; @@ -392,7 +373,7 @@ public class Resolver { return port; } - public DNSName getHostname() { + public DnsName getHostname() { return hostname; } @@ -482,7 +463,7 @@ public class Resolver { return null; } try { - result.hostname = DNSName.from(hostPart.trim()); + result.hostname = DnsName.from(hostPart.trim()); } catch (final Exception e) { return null; } @@ -499,7 +480,7 @@ public class Resolver { result.ip = inetAddress; } else { try { - result.hostname = DNSName.from(hostname); + result.hostname = DnsName.from(hostname); } catch (final Exception e) { return null; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 4a4f9ccce822217b385f84afaab0d0185a497bad..4b8cec64cef1b812119033c9854ecbafa449d721 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -225,6 +225,12 @@ public class XmppConnection implements Runnable { } } + + public boolean resolverAuthenticated() { + if (currentResolverResult == null) return false; + return currentResolverResult.isAuthenticated(); + } + private void changeStatus(final Account.State nextStatus) { synchronized (this) { if (Thread.currentThread().isInterrupted()) { diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index bbc09b028e75ad1f6ec02f287f4e7110e271ff92..98a897f66dca1278b3ae79fa1161d31f1a0ba600 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -43,12 +43,29 @@ android:singleLine="true" android:textAppearance="@style/TextAppearance.Conversations.Subhead" /> - + android:layout_centerVertical="true" + android:orientation="horizontal"> + + + + + > + + + + + + + + + + -