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        ResolutionUnsuccessfulException resolutionUnsuccessfulException = result.getResolutionUnsuccessfulException();
079        if (resolutionUnsuccessfulException != null) {
080            failedAddresses.add(new HostAddress(name, resolutionUnsuccessfulException));
081            return null;
082        }
083
084        if (shouldAbortIfNotAuthentic(name, dnssecMode, result, failedAddresses)) {
085            return null;
086        }
087
088        List<SRVRecord> res = new LinkedList<>();
089        for (SRV srv : result.getAnswers()) {
090            String hostname = srv.name.ace;
091            List<InetAddress> hostAddresses = lookupHostAddress0(hostname, failedAddresses, dnssecMode);
092            if (shouldContinue(name, hostname, hostAddresses)) {
093                continue;
094            }
095
096            SRVRecord srvRecord = new SRVRecord(hostname, srv.port, srv.priority, srv.weight, hostAddresses);
097            res.add(srvRecord);
098        }
099
100        return res;
101    }
102
103    @Override
104    protected List<InetAddress> lookupHostAddress0(final String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
105        final ResolverApi resolver = getResolver(dnssecMode);
106
107        final ResolverResult<A> aResult;
108        final ResolverResult<AAAA> aaaaResult;
109
110        try {
111            aResult = resolver.resolve(name, A.class);
112            aaaaResult = resolver.resolve(name, AAAA.class);
113        } catch (IOException e) {
114            failedAddresses.add(new HostAddress(name, e));
115            return null;
116        }
117
118        if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) {
119            // Both results where not successful.
120            failedAddresses.add(new HostAddress(name, getExceptionFrom(aResult)));
121            failedAddresses.add(new HostAddress(name, getExceptionFrom(aaaaResult)));
122            return null;
123        }
124
125        if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, failedAddresses)
126                        || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, failedAddresses)) {
127            return null;
128        }
129
130        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
131        Set<A> aResults;
132        if (aResult.wasSuccessful()) {
133            aResults = aResult.getAnswers();
134        }
135        else {
136            aResults = Collections.emptySet();
137        }
138
139        // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS.
140        Set<AAAA> aaaaResults;
141        if (aaaaResult.wasSuccessful()) {
142            aaaaResults = aaaaResult.getAnswers();
143        }
144        else {
145            aaaaResults = Collections.emptySet();
146        }
147
148        List<InetAddress> inetAddresses = new ArrayList<>(aResults.size()
149                        + aaaaResults.size());
150
151        for (A a : aResults) {
152            InetAddress inetAddress;
153            try {
154                inetAddress = InetAddress.getByAddress(a.getIp());
155            }
156            catch (UnknownHostException e) {
157                continue;
158            }
159            inetAddresses.add(inetAddress);
160        }
161        for (AAAA aaaa : aaaaResults) {
162            InetAddress inetAddress;
163            try {
164                inetAddress = InetAddress.getByAddress(name, aaaa.getIp());
165            }
166            catch (UnknownHostException e) {
167                continue;
168            }
169            inetAddresses.add(inetAddress);
170        }
171
172        return inetAddresses;
173    }
174
175    public static void setup() {
176        DNSUtil.setDNSResolver(getInstance());
177    }
178
179    @Override
180    public List<Exception> initialize() {
181        setup();
182        MiniDnsDane.setup();
183        return null;
184    }
185
186    private static ResolverApi getResolver(DnssecMode dnssecMode) {
187        if (dnssecMode == DnssecMode.disabled) {
188            return NON_DNSSEC_RESOLVER;
189        } else {
190            return DNSSEC_RESOLVER;
191        }
192    }
193
194    private static boolean shouldAbortIfNotAuthentic(String name, DnssecMode dnssecMode,
195                    ResolverResult<?> result, List<HostAddress> failedAddresses) {
196        switch (dnssecMode) {
197        case needsDnssec:
198        case needsDnssecAndDane:
199            // Check if the result is authentic data, i.e. there a no reasons the result is unverified.
200            // TODO: Use ResolverResult.getDnssecResultNotAuthenticException() of newer MiniDNS versions.
201            if (!result.isAuthenticData()) {
202                Exception exception = new Exception("DNSSEC verification failed: " + result.getUnverifiedReasons().iterator().next().getReasonString());
203                failedAddresses.add(new HostAddress(name, exception));
204                return true;
205            }
206            break;
207        case disabled:
208            break;
209        default:
210            throw new IllegalStateException("Unknown DnssecMode: " + dnssecMode);
211        }
212        return false;
213    }
214
215    private static ResolutionUnsuccessfulException getExceptionFrom(ResolverResult<?> result) {
216        Question question = result.getQuestion();
217        RESPONSE_CODE responseCode = result.getResponseCode();
218        ResolutionUnsuccessfulException resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
219        return resolutionUnsuccessfulException;
220    }
221}