001/**
002 *
003 * Copyright 2014-2017 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.LinkedList;
025import java.util.List;
026import java.util.Set;
027
028import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
029import org.jivesoftware.smack.initializer.SmackInitializer;
030import org.jivesoftware.smack.util.DNSUtil;
031import org.jivesoftware.smack.util.dns.DNSResolver;
032import org.jivesoftware.smack.util.dns.HostAddress;
033import org.jivesoftware.smack.util.dns.SRVRecord;
034
035import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
036import org.minidns.dnsmessage.Question;
037import org.minidns.dnsname.DnsName;
038import org.minidns.hla.DnssecResolverApi;
039import org.minidns.hla.ResolutionUnsuccessfulException;
040import org.minidns.hla.ResolverApi;
041import org.minidns.hla.ResolverResult;
042import org.minidns.hla.SrvResolverResult;
043import org.minidns.record.A;
044import org.minidns.record.AAAA;
045import org.minidns.record.SRV;
046
047
048/**
049 * This implementation uses the <a href="https://github.com/rtreffer/minidns/">MiniDNS</a> implementation for
050 * resolving DNS addresses.
051 */
052public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
053
054    private static final MiniDnsResolver INSTANCE = new MiniDnsResolver();
055
056    private static final ResolverApi DNSSEC_RESOLVER = DnssecResolverApi.INSTANCE;
057
058    private static final ResolverApi NON_DNSSEC_RESOLVER = ResolverApi.INSTANCE;
059
060    public static DNSResolver getInstance() {
061        return INSTANCE;
062    }
063
064    public MiniDnsResolver() {
065        super(true);
066    }
067
068    @Override
069    protected List<SRVRecord> lookupSRVRecords0(final DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
070        final ResolverApi resolver = getResolver(dnssecMode);
071
072        SrvResolverResult result;
073        try {
074            result = resolver.resolveSrv(name);
075        } catch (IOException e) {
076            failedAddresses.add(new HostAddress(name, e));
077            return null;
078        }
079
080        ResolutionUnsuccessfulException resolutionUnsuccessfulException = result.getResolutionUnsuccessfulException();
081        if (resolutionUnsuccessfulException != null) {
082            failedAddresses.add(new HostAddress(name, resolutionUnsuccessfulException));
083            return null;
084        }
085
086        if (shouldAbortIfNotAuthentic(name, dnssecMode, result, failedAddresses)) {
087            return null;
088        }
089
090        List<SRVRecord> res = new LinkedList<>();
091        for (SRV srv : result.getAnswers()) {
092            DnsName hostname = srv.target;
093            List<InetAddress> hostAddresses = lookupHostAddress0(hostname, failedAddresses, dnssecMode);
094            if (shouldContinue(name, hostname, hostAddresses)) {
095                continue;
096            }
097
098            SRVRecord srvRecord = new SRVRecord(hostname, srv.port, srv.priority, srv.weight, hostAddresses);
099            res.add(srvRecord);
100        }
101
102        return res;
103    }
104
105    @Override
106    protected List<InetAddress> lookupHostAddress0(final DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
107        final ResolverApi resolver = getResolver(dnssecMode);
108
109        final ResolverResult<A> aResult;
110        final ResolverResult<AAAA> aaaaResult;
111
112        try {
113            aResult = resolver.resolve(name, A.class);
114            aaaaResult = resolver.resolve(name, AAAA.class);
115        } catch (IOException e) {
116            failedAddresses.add(new HostAddress(name, e));
117            return null;
118        }
119
120        if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) {
121            // Both results where not successful.
122            failedAddresses.add(new HostAddress(name, getExceptionFrom(aResult)));
123            failedAddresses.add(new HostAddress(name, getExceptionFrom(aaaaResult)));
124            return null;
125        }
126
127        if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, failedAddresses)
128                        || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, failedAddresses)) {
129            return null;
130        }
131
132        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
133        Set<A> aResults;
134        if (aResult.wasSuccessful()) {
135            aResults = aResult.getAnswers();
136        }
137        else {
138            aResults = Collections.emptySet();
139        }
140
141        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
142        Set<AAAA> aaaaResults;
143        if (aaaaResult.wasSuccessful()) {
144            aaaaResults = aaaaResult.getAnswers();
145        }
146        else {
147            aaaaResults = Collections.emptySet();
148        }
149
150        List<InetAddress> inetAddresses = new ArrayList<>(aResults.size()
151                        + aaaaResults.size());
152
153        for (A a : aResults) {
154            InetAddress inetAddress;
155            try {
156                inetAddress = InetAddress.getByAddress(a.getIp());
157            }
158            catch (UnknownHostException e) {
159                continue;
160            }
161            inetAddresses.add(inetAddress);
162        }
163        for (AAAA aaaa : aaaaResults) {
164            InetAddress inetAddress;
165            try {
166                inetAddress = InetAddress.getByAddress(name.ace, aaaa.getIp());
167            }
168            catch (UnknownHostException e) {
169                continue;
170            }
171            inetAddresses.add(inetAddress);
172        }
173
174        return inetAddresses;
175    }
176
177    public static void setup() {
178        DNSUtil.setDNSResolver(getInstance());
179    }
180
181    @Override
182    public List<Exception> initialize() {
183        setup();
184        MiniDnsDane.setup();
185        return null;
186    }
187
188    private static ResolverApi getResolver(DnssecMode dnssecMode) {
189        if (dnssecMode == DnssecMode.disabled) {
190            return NON_DNSSEC_RESOLVER;
191        } else {
192            return DNSSEC_RESOLVER;
193        }
194    }
195
196    private static boolean shouldAbortIfNotAuthentic(DnsName name, DnssecMode dnssecMode,
197                    ResolverResult<?> result, List<HostAddress> failedAddresses) {
198        switch (dnssecMode) {
199        case needsDnssec:
200        case needsDnssecAndDane:
201            // Check if the result is authentic data, i.e. there a no reasons the result is unverified.
202            // TODO: Use ResolverResult.getDnssecResultNotAuthenticException() of newer MiniDNS versions.
203            if (!result.isAuthenticData()) {
204                Exception exception = new Exception("DNSSEC verification failed: " + result.getUnverifiedReasons().iterator().next().getReasonString());
205                failedAddresses.add(new HostAddress(name, exception));
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}