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}