1package eu.siacs.conversations.utils;
2
3import android.content.Context;
4import android.support.annotation.NonNull;
5import android.util.Log;
6
7import java.io.IOException;
8import java.net.InetAddress;
9import java.util.ArrayList;
10import java.util.Collections;
11import java.util.List;
12
13import de.measite.minidns.DNSClient;
14import de.measite.minidns.DNSName;
15import de.measite.minidns.Question;
16import de.measite.minidns.Record;
17import de.measite.minidns.dnssec.DNSSECValidationFailedException;
18import de.measite.minidns.hla.DnssecResolverApi;
19import de.measite.minidns.hla.ResolverApi;
20import de.measite.minidns.hla.ResolverResult;
21import de.measite.minidns.record.A;
22import de.measite.minidns.record.AAAA;
23import de.measite.minidns.record.Data;
24import de.measite.minidns.record.InternetAddressRR;
25import de.measite.minidns.record.SRV;
26import eu.siacs.conversations.Config;
27
28public class Resolver {
29
30 private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
31 private static final String STARTTLS_SERICE = "_xmpp-client";
32
33
34
35
36 public static void registerLookupMechanism(Context context) {
37 DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(context));
38 }
39
40 public static List<Result> resolve(String domain) {
41 List<Result> results = new ArrayList<>();
42 try {
43 results.addAll(resolveSrv(domain,true));
44 } catch (Throwable t) {
45 Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": "+t.getMessage());
46 }
47 try {
48 results.addAll(resolveSrv(domain,false));
49 } catch (Throwable t) {
50 Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": "+t.getMessage());
51 }
52 if (results.size() == 0) {
53 results.add(Result.createDefault(domain));
54 }
55 Collections.sort(results);
56 return results;
57 }
58
59 private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
60 Question question = new Question((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE)+"._tcp."+domain,Record.TYPE.SRV);
61 ResolverResult<Data> result;
62 try {
63 result = DnssecResolverApi.INSTANCE.resolve(question);
64 } catch (DNSSECValidationFailedException e) {
65 Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving SRV record with DNSSEC. Trying DNS instead "+e.getMessage());
66 result = ResolverApi.INSTANCE.resolve(question);
67 }
68 List<Result> results = new ArrayList<>();
69 for(Data record : result.getAnswersOrEmptySet()) {
70 if (record instanceof SRV) {
71 SRV srvRecord = (SRV) record;
72 boolean added = results.addAll(resolveIp(srvRecord,A.class,result.isAuthenticData(),directTls));
73 added |= results.addAll(resolveIp(srvRecord,AAAA.class,result.isAuthenticData(),directTls));
74 if (!added) {
75 Result resolverResult = Result.fromRecord(srvRecord, directTls);
76 resolverResult.authenticated = resolverResult.isAuthenticated();
77 results.add(resolverResult);
78 }
79 }
80 }
81 return results;
82 }
83
84 private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
85 List<Result> list = new ArrayList<>();
86 try {
87 ResolverResult<D> results;
88 try {
89 results = DnssecResolverApi.INSTANCE.resolve(srv.name, type);
90 } catch (DNSSECValidationFailedException e) {
91 Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving "+type.getSimpleName()+" with DNSSEC. Trying DNS instead "+e.getMessage());
92 results = ResolverApi.INSTANCE.resolve(srv.name,type);
93 }
94 for (D record : results.getAnswersOrEmptySet()) {
95 Result resolverResult = Result.fromRecord(srv, directTls);
96 resolverResult.authenticated = results.isAuthenticData() && authenticated;
97 resolverResult.ip = record.getInetAddress();
98 list.add(resolverResult);
99 }
100 } catch (Throwable t) {
101 Log.d(Config.LOGTAG,Resolver.class.getSimpleName()+": error resolving "+type.getSimpleName()+" "+t.getMessage());
102 }
103 return list;
104 }
105
106 public static class Result implements Comparable<Result> {
107 private InetAddress ip;
108 private DNSName hostname;
109 private int port = 5222;
110 private boolean directTls = false;
111 private boolean authenticated =false;
112 private int priority;
113
114 public InetAddress getIp() {
115 return ip;
116 }
117
118 public int getPort() {
119 return port;
120 }
121
122 public DNSName getHostname() {
123 return hostname;
124 }
125
126 public boolean isDirectTls() {
127 return directTls;
128 }
129
130 public boolean isAuthenticated() {
131 return authenticated;
132 }
133
134 @Override
135 public String toString() {
136 return "Result{" +
137 "ip='" + ip + '\'' +
138 ", hostame='" + hostname.toString() + '\'' +
139 ", port=" + port +
140 ", directTls=" + directTls +
141 ", authenticated=" + authenticated +
142 ", priority=" + priority +
143 '}';
144 }
145
146 @Override
147 public int compareTo(@NonNull Result result) {
148 if (result.priority == priority) {
149 if (directTls == result.directTls) {
150 return 0;
151 } else {
152 return directTls ? 1 : -1;
153 }
154 } else {
155 return priority - result.priority;
156 }
157 }
158
159 public static Result fromRecord(SRV srv, boolean directTls) {
160 Result result = new Result();
161 result.port = srv.port;
162 result.hostname = srv.name;
163 result.directTls = directTls;
164 result.priority = srv.priority;
165 return result;
166 }
167
168 public static Result createDefault(String domain) {
169 Result result = new Result();
170 result.port = 5222;
171 result.hostname = DNSName.from(domain);
172 return result;
173 }
174 }
175
176}