001/**
002 *
003 * Copyright 2014-2020 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util.dns.minidns;
018
019import java.io.IOException;
020import java.net.InetAddress;
021import java.net.UnknownHostException;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025import java.util.Set;
026
027import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
028import org.jivesoftware.smack.initializer.SmackInitializer;
029import org.jivesoftware.smack.util.DNSUtil;
030import org.jivesoftware.smack.util.dns.DNSResolver;
031import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
032
033import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
034import org.minidns.dnsmessage.Question;
035import org.minidns.dnsname.DnsName;
036import org.minidns.dnssec.DnssecResultNotAuthenticException;
037import org.minidns.hla.DnssecResolverApi;
038import org.minidns.hla.ResolutionUnsuccessfulException;
039import org.minidns.hla.ResolverApi;
040import org.minidns.hla.ResolverResult;
041import org.minidns.hla.SrvResolverResult;
042import org.minidns.record.A;
043import org.minidns.record.AAAA;
044import org.minidns.record.SRV;
045
046
047/**
048 * This implementation uses the <a href="https://github.com/rtreffer/minidns/">MiniDNS</a> implementation for
049 * resolving DNS addresses.
050 */
051public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
052
053    private static final MiniDnsResolver INSTANCE = new MiniDnsResolver();
054
055    private static final ResolverApi DNSSEC_RESOLVER = DnssecResolverApi.INSTANCE;
056
057    private static final ResolverApi NON_DNSSEC_RESOLVER = ResolverApi.INSTANCE;
058
059    public static DNSResolver getInstance() {
060        return INSTANCE;
061    }
062
063    public MiniDnsResolver() {
064        super(true);
065    }
066
067    @Override
068    protected Set<SRV> lookupSrvRecords0(final DnsName name, List<RemoteConnectionEndpointLookupFailure> lookupFailures,
069                    DnssecMode dnssecMode) {
070        final ResolverApi resolver = getResolver(dnssecMode);
071
072        SrvResolverResult result;
073        try {
074            result = resolver.resolveSrv(name);
075        } catch (IOException e) {
076            RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
077                            name, e);
078            lookupFailures.add(failure);
079            return null;
080        }
081
082        ResolutionUnsuccessfulException resolutionUnsuccessfulException = result.getResolutionUnsuccessfulException();
083        if (resolutionUnsuccessfulException != null) {
084            RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
085                            name, resolutionUnsuccessfulException);
086            lookupFailures.add(failure);
087            return null;
088        }
089
090        if (shouldAbortIfNotAuthentic(name, dnssecMode, result, lookupFailures)) {
091            return null;
092        }
093
094        return result.getAnswers();
095    }
096
097    @Override
098    protected List<InetAddress> lookupHostAddress0(final DnsName name,
099                    List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
100        final ResolverApi resolver = getResolver(dnssecMode);
101
102        final ResolverResult<A> aResult;
103        final ResolverResult<AAAA> aaaaResult;
104
105        try {
106            aResult = resolver.resolve(name, A.class);
107            aaaaResult = resolver.resolve(name, AAAA.class);
108        } catch (IOException e) {
109            RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
110                            name, e);
111            lookupFailures.add(failure);
112            return null;
113        }
114
115        if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) {
116            // Both results where not successful.
117            RemoteConnectionEndpointLookupFailure failureA = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
118                            name, getExceptionFrom(aResult));
119            lookupFailures.add(failureA);
120            RemoteConnectionEndpointLookupFailure failureAaaa = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
121                            name, getExceptionFrom(aaaaResult));
122            lookupFailures.add(failureAaaa);
123            return null;
124        }
125
126        if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, lookupFailures)
127                        || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, lookupFailures)) {
128            return null;
129        }
130
131        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
132        Set<A> aResults;
133        if (aResult.wasSuccessful()) {
134            aResults = aResult.getAnswers();
135        }
136        else {
137            aResults = Collections.emptySet();
138        }
139
140        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
141        Set<AAAA> aaaaResults;
142        if (aaaaResult.wasSuccessful()) {
143            aaaaResults = aaaaResult.getAnswers();
144        }
145        else {
146            aaaaResults = Collections.emptySet();
147        }
148
149        List<InetAddress> inetAddresses = new ArrayList<>(aResults.size()
150                        + aaaaResults.size());
151
152        for (A a : aResults) {
153            InetAddress inetAddress;
154            try {
155                inetAddress = InetAddress.getByAddress(a.getIp());
156            }
157            catch (UnknownHostException e) {
158                continue;
159            }
160            inetAddresses.add(inetAddress);
161        }
162        for (AAAA aaaa : aaaaResults) {
163            InetAddress inetAddress;
164            try {
165                inetAddress = InetAddress.getByAddress(name.ace, aaaa.getIp());
166            }
167            catch (UnknownHostException e) {
168                continue;
169            }
170            inetAddresses.add(inetAddress);
171        }
172
173        return inetAddresses;
174    }
175
176    public static void setup() {
177        DNSUtil.setDNSResolver(getInstance());
178    }
179
180    @Override
181    public List<Exception> initialize() {
182        setup();
183        MiniDnsDane.setup();
184        return null;
185    }
186
187    private static ResolverApi getResolver(DnssecMode dnssecMode) {
188        if (dnssecMode == DnssecMode.disabled) {
189            return NON_DNSSEC_RESOLVER;
190        } else {
191            return DNSSEC_RESOLVER;
192        }
193    }
194
195    private static boolean shouldAbortIfNotAuthentic(DnsName name, DnssecMode dnssecMode,
196                    ResolverResult<?> result, List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
197        switch (dnssecMode) {
198        case needsDnssec:
199        case needsDnssecAndDane:
200            // Check if the result is authentic data, i.e. there a no reasons the result is unverified.
201            DnssecResultNotAuthenticException exception = result.getDnssecResultNotAuthenticException();
202            if (exception != null) {
203                RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
204                                name, exception);
205                lookupFailures.add(failure);
206                return true;
207            }
208            break;
209        case disabled:
210            break;
211        default:
212            throw new IllegalStateException("Unknown DnssecMode: " + dnssecMode);
213        }
214        return false;
215    }
216
217    private static ResolutionUnsuccessfulException getExceptionFrom(ResolverResult<?> result) {
218        Question question = result.getQuestion();
219        RESPONSE_CODE responseCode = result.getResponseCode();
220        ResolutionUnsuccessfulException resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
221        return resolutionUnsuccessfulException;
222    }
223}