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.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028 029import javax.naming.NamingException; 030import javax.naming.directory.Attribute; 031import javax.naming.directory.Attributes; 032import javax.naming.directory.DirContext; 033import javax.naming.directory.InitialDirContext; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.nuxeo.runtime.api.Framework; 038 039/** 040 * Utility class to perform DNS lookups for services. 041 */ 042public class DNSServiceResolverImpl implements DNSServiceResolver { 043 044 public static final Log log = LogFactory.getLog(DNSServiceResolverImpl.class); 045 046 protected static DNSServiceResolver instance; 047 048 protected static final String SRV_RECORD = "SRV"; 049 050 /** 051 * Create a cache to hold the at most 100 recent DNS lookups for a period of 10 minutes. 052 */ 053 protected Map<String, List<DNSServiceEntry>> cache = new HashMap<>(); 054 055 protected long lastCacheUpdate = System.currentTimeMillis(); 056 057 protected final long maxDelay; 058 059 protected static DirContext context; 060 061 public static synchronized DNSServiceResolver getInstance() { 062 if (instance == null) { 063 instance = new DNSServiceResolverImpl(); 064 } 065 return instance; 066 } 067 068 protected DNSServiceResolverImpl() { 069 /* 070 * The expiry of the cache in minutes 071 */ 072 int cacheExpiry = 10; 073 try { 074 cacheExpiry = Integer.parseInt(Framework.getProperty(DNS_CACHE_EXPIRY, "10")); 075 } catch (NumberFormatException e) { 076 log.warn("invalid value for property: " + DNS_CACHE_EXPIRY 077 + ", falling back to default value of 10 minutes"); 078 } 079 maxDelay = 1000 * 60 * cacheExpiry; 080 081 Properties env = new Properties(); 082 env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); 083 try { 084 context = new InitialDirContext(env); 085 } catch (NamingException e) { 086 throw new RuntimeException(e); 087 } 088 } 089 090 /** 091 * Returns the host name and port that a server providing the specified service can be reached at. A DNS lookup for 092 * a SRV record in the form "_service.example.com" is attempted. 093 * <p> 094 * As an example, a lookup for "example.com" for the service _gc._tcp may return "dc01.example.com:3268". 095 * 096 * @param service the service. 097 * @param domain the domain. 098 * @return a List of DNSServiceEntrys, which encompasses the hostname and port that the server can be reached at for 099 * the specified domain. 100 * @throws NamingException if the DNS server is unreachable 101 */ 102 protected List<DNSServiceEntry> resolveDnsServiceRecord(final String service, final String domain) 103 throws NamingException { 104 List<DNSServiceEntry> addresses = new ArrayList<>(); 105 106 if (context == null) { 107 return addresses; 108 } 109 110 final String key = service + "." + domain; 111 /* 112 * Return item from cache if it exists. 113 */ 114 if (System.currentTimeMillis() - lastCacheUpdate > maxDelay) { 115 cache.clear(); 116 } 117 if (cache.containsKey(key)) { 118 List<DNSServiceEntry> cachedAddresses = cache.get(key); 119 if (cachedAddresses != null) { 120 return cachedAddresses; 121 } 122 } 123 124 Attributes dnsLookup = context.getAttributes(service + "." + domain, new String[] { SRV_RECORD }); 125 126 Attribute attribute = dnsLookup.get(SRV_RECORD); 127 for (int i = 0; i < attribute.size(); i++) { 128 /* 129 * Get the current resource record 130 */ 131 String entry = (String) attribute.get(i); 132 133 String[] records = entry.split(" "); 134 String host = records[records.length - 1]; 135 int port = Integer.parseInt(records[records.length - 2]); 136 int weight = Integer.parseInt(records[records.length - 3]); 137 int priority = Integer.parseInt(records[records.length - 4]); 138 139 /* 140 * possible to get TTL? 141 */ 142 143 /* 144 * Host entries in DNS should end with a "." 145 */ 146 if (host.endsWith(".")) { 147 host = host.substring(0, host.length() - 1); 148 } 149 150 addresses.add(new DNSServiceEntry(host, port, priority, weight)); 151 } 152 153 /* 154 * Sort the addresses by DNS priority and weight settings 155 */ 156 Collections.sort(addresses); 157 158 /* 159 * Add item to cache. 160 */ 161 if (cache.size() > 100) { 162 cache.clear(); 163 } 164 cache.put(key, addresses); 165 lastCacheUpdate = System.currentTimeMillis(); 166 return addresses; 167 } 168 169 @Override 170 public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain) throws NamingException { 171 return resolveDnsServiceRecord(LDAP_SERVICE_PREFIX, domain); 172 } 173 174 @Override 175 public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain, final String prefix) 176 throws NamingException { 177 return resolveDnsServiceRecord(prefix, domain); 178 } 179 180}