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; 027import java.util.logging.Level; 028 029import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode; 030import org.jivesoftware.smack.initializer.SmackInitializer; 031import org.jivesoftware.smack.util.DNSUtil; 032import org.jivesoftware.smack.util.dns.DNSResolver; 033import org.jivesoftware.smack.util.dns.HostAddress; 034import org.jivesoftware.smack.util.dns.SRVRecord; 035 036import de.measite.minidns.DNSMessage.RESPONSE_CODE; 037import de.measite.minidns.Question; 038import de.measite.minidns.hla.DnssecResolverApi; 039import de.measite.minidns.hla.ResolutionUnsuccessfulException; 040import de.measite.minidns.hla.ResolverApi; 041import de.measite.minidns.hla.ResolverResult; 042import de.measite.minidns.record.A; 043import de.measite.minidns.record.AAAA; 044import de.measite.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 List<SRVRecord> lookupSRVRecords0(final String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) { 069 final ResolverApi resolver = getResolver(dnssecMode); 070 071 ResolverResult<SRV> result; 072 try { 073 result = resolver.resolve(name, SRV.class); 074 } catch (IOException e) { 075 failedAddresses.add(new HostAddress(name, e)); 076 return null; 077 } 078 079 // TODO: Use ResolverResult.getResolutionUnsuccessfulException() found in newer MiniDNS versions. 080 if (!result.wasSuccessful()) { 081 ResolutionUnsuccessfulException resolutionUnsuccessfulException = getExceptionFrom(result); 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<SRVRecord>(); 091 for (SRV srv : result.getAnswers()) { 092 String hostname = srv.name.ace; 093 List<InetAddress> hostAddresses = lookupHostAddress0(hostname, failedAddresses, dnssecMode); 094 if (hostAddresses == null || hostAddresses.isEmpty()) { 095 // If hostAddresses is not null but empty, then the DNS resolution was successful but the domain did not 096 // have any A or AAAA resource records. 097 if (hostAddresses.isEmpty()) { 098 LOGGER.log(Level.INFO, "The DNS name " + name + ", points to a hostname (" + hostname 099 + ") which has neither A or AAAA resource records. This is an indication of a broken DNS setup."); 100 } 101 continue; 102 } 103 104 SRVRecord srvRecord = new SRVRecord(hostname, srv.port, srv.priority, srv.weight, hostAddresses); 105 res.add(srvRecord); 106 } 107 108 return res; 109 } 110 111 @Override 112 protected List<InetAddress> lookupHostAddress0(final String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) { 113 final ResolverApi resolver = getResolver(dnssecMode); 114 115 final ResolverResult<A> aResult; 116 final ResolverResult<AAAA> aaaaResult; 117 118 try { 119 aResult = resolver.resolve(name, A.class); 120 aaaaResult = resolver.resolve(name, AAAA.class); 121 } catch (IOException e) { 122 failedAddresses.add(new HostAddress(name, e)); 123 return null; 124 } 125 126 if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) { 127 // Both results where not successful. 128 failedAddresses.add(new HostAddress(name, getExceptionFrom(aResult))); 129 failedAddresses.add(new HostAddress(name, getExceptionFrom(aaaaResult))); 130 return null; 131 } 132 133 if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, failedAddresses) 134 || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, failedAddresses)) { 135 return null; 136 } 137 138 // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS. 139 Set<A> aResults; 140 if (aResult.wasSuccessful()) { 141 aResults = aResult.getAnswers(); 142 } 143 else { 144 aResults = Collections.emptySet(); 145 } 146 147 // TODO: Use ResolverResult.getAnswersOrEmptySet() once we updated MiniDNS. 148 Set<AAAA> aaaaResults; 149 if (aaaaResult.wasSuccessful()) { 150 aaaaResults = aaaaResult.getAnswers(); 151 } 152 else { 153 aaaaResults = Collections.emptySet(); 154 } 155 156 List<InetAddress> inetAddresses = new ArrayList<>(aResults.size() 157 + aaaaResults.size()); 158 159 for (A a : aResults) { 160 InetAddress inetAddress; 161 try { 162 inetAddress = InetAddress.getByAddress(a.getIp()); 163 } 164 catch (UnknownHostException e) { 165 continue; 166 } 167 inetAddresses.add(inetAddress); 168 } 169 for (AAAA aaaa : aaaaResults) { 170 InetAddress inetAddress; 171 try { 172 inetAddress = InetAddress.getByAddress(name, aaaa.getIp()); 173 } 174 catch (UnknownHostException e) { 175 continue; 176 } 177 inetAddresses.add(inetAddress); 178 } 179 180 return inetAddresses; 181 } 182 183 public static void setup() { 184 DNSUtil.setDNSResolver(getInstance()); 185 } 186 187 @Override 188 public List<Exception> initialize() { 189 setup(); 190 MiniDnsDane.setup(); 191 return null; 192 } 193 194 private static ResolverApi getResolver(DnssecMode dnssecMode) { 195 if (dnssecMode == DnssecMode.disabled) { 196 return NON_DNSSEC_RESOLVER; 197 } else { 198 return DNSSEC_RESOLVER; 199 } 200 } 201 202 private static boolean shouldAbortIfNotAuthentic(String name, DnssecMode dnssecMode, 203 ResolverResult<?> result, List<HostAddress> failedAddresses) { 204 switch (dnssecMode) { 205 case needsDnssec: 206 case needsDnssecAndDane: 207 // Check if the result is authentic data, i.e. there a no reasons the result is unverified. 208 // TODO: Use ResolverResult.getDnssecResultNotAuthenticException() of newer MiniDNS versions. 209 if (!result.isAuthenticData()) { 210 Exception exception = new Exception("DNSSEC verification failed: " + result.getUnverifiedReasons().iterator().next().getReasonString()); 211 failedAddresses.add(new HostAddress(name, exception)); 212 return true; 213 } 214 break; 215 case disabled: 216 break; 217 default: 218 throw new IllegalStateException("Unknown DnssecMode: " + dnssecMode); 219 } 220 return false; 221 } 222 223 private static ResolutionUnsuccessfulException getExceptionFrom(ResolverResult<?> result) { 224 Question question = result.getQuestion(); 225 RESPONSE_CODE responseCode = result.getResponseCode(); 226 ResolutionUnsuccessfulException resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode); 227 return resolutionUnsuccessfulException; 228 } 229}