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