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}