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 de.measite.minidns.DNSMessage.RESPONSE_CODE;
036import de.measite.minidns.Question;
037import de.measite.minidns.hla.DnssecResolverApi;
038import de.measite.minidns.hla.ResolutionUnsuccessfulException;
039import de.measite.minidns.hla.ResolverApi;
040import de.measite.minidns.hla.ResolverResult;
041import de.measite.minidns.record.A;
042import de.measite.minidns.record.AAAA;
043import de.measite.minidns.record.SRV;
044
045
046/**
047 * This implementation uses the <a href="https://github.com/rtreffer/minidns/">MiniDNS</a> implementation for
048 * resolving DNS addresses.
049 */
050public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
051
052    private static final MiniDnsResolver INSTANCE = new MiniDnsResolver();
053
054    private static final ResolverApi DNSSEC_RESOLVER = DnssecResolverApi.INSTANCE;
055
056    private static final ResolverApi NON_DNSSEC_RESOLVER = ResolverApi.INSTANCE;
057
058    public static DNSResolver getInstance() {
059        return INSTANCE;
060    }
061
062    public MiniDnsResolver() {
063        super(true);
064    }
065
066    @Override
067    protected List<SRVRecord> lookupSRVRecords0(final String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
068        final ResolverApi resolver = getResolver(dnssecMode);
069
070        ResolverResult<SRV> result;
071        try {
072            result = resolver.resolve(name, SRV.class);
073        } catch (IOException e) {
074            failedAddresses.add(new HostAddress(name, e));
075            return null;
076        }
077
078        // TODO: Use ResolverResult.getResolutionUnsuccessfulException() found in newer MiniDNS versions.
079        if (!result.wasSuccessful()) {
080            ResolutionUnsuccessfulException resolutionUnsuccessfulException = getExceptionFrom(result);
081            failedAddresses.add(new HostAddress(name, resolutionUnsuccessfulException));
082            return null;
083        }
084
085        if (shouldAbortIfNotAuthentic(name, dnssecMode, result, failedAddresses)) {
086            return null;
087        }
088
089        List<SRVRecord> res = new LinkedList<SRVRecord>();
090        for (SRV srv : result.getAnswers()) {
091            String hostname = srv.name.ace;
092            List<InetAddress> hostAddresses = lookupHostAddress0(hostname, failedAddresses, dnssecMode);
093            if (hostAddresses == null) {
094                continue;
095            }
096
097            SRVRecord srvRecord = new SRVRecord(hostname, srv.port, srv.priority, srv.weight, hostAddresses);
098            res.add(srvRecord);
099        }
100
101        return res;
102    }
103
104    @Override
105    protected List<InetAddress> lookupHostAddress0(final String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
106        final ResolverApi resolver = getResolver(dnssecMode);
107
108        final ResolverResult<A> aResult;
109        final ResolverResult<AAAA> aaaaResult;
110
111        try {
112            aResult = resolver.resolve(name, A.class);
113            aaaaResult = resolver.resolve(name, AAAA.class);
114        } catch (IOException e) {
115            failedAddresses.add(new HostAddress(name, e));
116            return null;
117        }
118
119        if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) {
120            // Both results where not successful.
121            failedAddresses.add(new HostAddress(name, getExceptionFrom(aResult)));
122            failedAddresses.add(new HostAddress(name, getExceptionFrom(aaaaResult)));
123            return null;
124        }
125
126        if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, failedAddresses)
127                        || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, failedAddresses)) {
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, 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(String name, DnssecMode dnssecMode,
196                    ResolverResult<?> result, List<HostAddress> failedAddresses) {
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            // TODO: Use ResolverResult.getDnssecResultNotAuthenticException() of newer MiniDNS versions.
202            if (!result.isAuthenticData()) {
203                Exception exception = new Exception("DNSSEC verification failed: " + result.getUnverifiedReasons().iterator().next().getReasonString());
204                failedAddresses.add(new HostAddress(name, exception));
205                return true;
206            }
207            break;
208        case disabled:
209            break;
210        default:
211            throw new IllegalStateException("Unknown DnssecMode: " + dnssecMode);
212        }
213        return false;
214    }
215
216    private static ResolutionUnsuccessfulException getExceptionFrom(ResolverResult<?> result) {
217        Question question = result.getQuestion();
218        RESPONSE_CODE responseCode = result.getResponseCode();
219        ResolutionUnsuccessfulException resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
220        return resolutionUnsuccessfulException;
221    }
222}