001/* 002 * (C) Copyright 2010-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Robert Browning - initial implementation 018 * Nuxeo - code review and integration 019 */ 020package org.nuxeo.ecm.directory.ldap.dns; 021 022import java.time.Duration; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Properties; 029 030import javax.naming.NamingException; 031import javax.naming.directory.Attribute; 032import javax.naming.directory.Attributes; 033import javax.naming.directory.DirContext; 034import javax.naming.directory.InitialDirContext; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.runtime.api.Framework; 039 040/** 041 * Utility class to perform DNS lookups for services. 042 */ 043public class DNSServiceResolverImpl implements DNSServiceResolver { 044 045 public static final Log log = LogFactory.getLog(DNSServiceResolverImpl.class); 046 047 protected static DNSServiceResolver instance; 048 049 protected static final String SRV_RECORD = "SRV"; 050 051 /** 052 * Create a cache to hold the at most 100 recent DNS lookups for a period of 10 minutes. 053 */ 054 protected Map<String, List<DNSServiceEntry>> cache = new HashMap<>(); 055 056 protected long lastCacheUpdate = System.currentTimeMillis(); 057 058 protected final long maxDelay; 059 060 protected final DirContext context; 061 062 public static synchronized DNSServiceResolver getInstance() { 063 if (instance == null) { 064 instance = new DNSServiceResolverImpl(); 065 } 066 return instance; 067 } 068 069 protected DNSServiceResolverImpl() { 070 /* 071 * The expiry of the cache in minutes 072 */ 073 int cacheExpiry = 10; 074 try { 075 cacheExpiry = Integer.parseInt(Framework.getProperty(DNS_CACHE_EXPIRY, "10")); 076 } catch (NumberFormatException e) { 077 log.warn("invalid value for property: " + DNS_CACHE_EXPIRY 078 + ", falling back to default value of 10 minutes"); 079 } 080 maxDelay = Duration.ofMinutes(cacheExpiry).toMillis(); 081 082 Properties env = new Properties(); 083 env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); 084 try { 085 context = new InitialDirContext(env); 086 } catch (NamingException e) { 087 throw new RuntimeException(e); 088 } 089 } 090 091 /** 092 * Returns the host name and port that a server providing the specified service can be reached at. A DNS lookup for 093 * a SRV record in the form "_service.example.com" is attempted. 094 * <p> 095 * As an example, a lookup for "example.com" for the service _gc._tcp may return "dc01.example.com:3268". 096 * 097 * @param service the service. 098 * @param domain the domain. 099 * @return a List of DNSServiceEntrys, which encompasses the hostname and port that the server can be reached at for 100 * the specified domain. 101 * @throws NamingException if the DNS server is unreachable 102 */ 103 protected List<DNSServiceEntry> resolveDnsServiceRecord(final String service, final String domain) 104 throws NamingException { 105 List<DNSServiceEntry> addresses = new ArrayList<>(); 106 107 if (context == null) { 108 return addresses; 109 } 110 111 final String key = service + "." + domain; 112 /* 113 * Return item from cache if it exists. 114 */ 115 if (System.currentTimeMillis() - lastCacheUpdate > maxDelay) { 116 cache.clear(); 117 } 118 if (cache.containsKey(key)) { 119 List<DNSServiceEntry> cachedAddresses = cache.get(key); 120 if (cachedAddresses != null) { 121 return cachedAddresses; 122 } 123 } 124 125 Attributes dnsLookup = context.getAttributes(service + "." + domain, new String[] { SRV_RECORD }); 126 127 Attribute attribute = dnsLookup.get(SRV_RECORD); 128 for (int i = 0; i < attribute.size(); i++) { 129 /* 130 * Get the current resource record 131 */ 132 String entry = (String) attribute.get(i); 133 134 String[] records = entry.split(" "); 135 String host = records[records.length - 1]; 136 int port = Integer.parseInt(records[records.length - 2]); 137 int weight = Integer.parseInt(records[records.length - 3]); 138 int priority = Integer.parseInt(records[records.length - 4]); 139 140 /* 141 * possible to get TTL? 142 */ 143 144 /* 145 * Host entries in DNS should end with a "." 146 */ 147 if (host.endsWith(".")) { 148 host = host.substring(0, host.length() - 1); 149 } 150 151 addresses.add(new DNSServiceEntry(host, port, priority, weight)); 152 } 153 154 /* 155 * Sort the addresses by DNS priority and weight settings 156 */ 157 Collections.sort(addresses); 158 159 /* 160 * Add item to cache. 161 */ 162 if (cache.size() > 100) { 163 cache.clear(); 164 } 165 cache.put(key, addresses); 166 lastCacheUpdate = System.currentTimeMillis(); 167 return addresses; 168 } 169 170 @Override 171 public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain) throws NamingException { 172 return resolveDnsServiceRecord(LDAP_SERVICE_PREFIX, domain); 173 } 174 175 @Override 176 public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain, final String prefix) 177 throws NamingException { 178 return resolveDnsServiceRecord(prefix, domain); 179 } 180 181}