/*
* (C) Copyright 2010-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Robert Browning - initial implementation
* Nuxeo - code review and integration
*/
package org.nuxeo.ecm.directory.ldap.dns;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.runtime.api.Framework;
/**
* Utility class to perform DNS lookups for services.
*/
public class DNSServiceResolverImpl implements DNSServiceResolver {
public static final Log log = LogFactory.getLog(DNSServiceResolverImpl.class);
protected static DNSServiceResolver instance;
protected static final String SRV_RECORD = "SRV";
/**
* Create a cache to hold the at most 100 recent DNS lookups for a period of 10 minutes.
*/
protected Map<String, List<DNSServiceEntry>> cache = new HashMap<>();
protected long lastCacheUpdate = System.currentTimeMillis();
protected final long maxDelay;
protected static DirContext context;
public static synchronized DNSServiceResolver getInstance() {
if (instance == null) {
instance = new DNSServiceResolverImpl();
}
return instance;
}
protected DNSServiceResolverImpl() {
/*
* The expiry of the cache in minutes
*/
int cacheExpiry = 10;
try {
cacheExpiry = Integer.parseInt(Framework.getProperty(DNS_CACHE_EXPIRY, "10"));
} catch (NumberFormatException e) {
log.warn("invalid value for property: " + DNS_CACHE_EXPIRY
+ ", falling back to default value of 10 minutes");
}
maxDelay = 1000 * 60 * cacheExpiry;
Properties env = new Properties();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
try {
context = new InitialDirContext(env);
} catch (NamingException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the host name and port that a server providing the specified service can be reached at. A DNS lookup for
* a SRV record in the form "_service.example.com" is attempted.
* <p>
* As an example, a lookup for "example.com" for the service _gc._tcp may return "dc01.example.com:3268".
*
* @param service the service.
* @param domain the domain.
* @return a List of DNSServiceEntrys, which encompasses the hostname and port that the server can be reached at for
* the specified domain.
* @throws NamingException if the DNS server is unreachable
*/
protected List<DNSServiceEntry> resolveDnsServiceRecord(final String service, final String domain)
throws NamingException {
List<DNSServiceEntry> addresses = new ArrayList<>();
if (context == null) {
return addresses;
}
final String key = service + "." + domain;
/*
* Return item from cache if it exists.
*/
if (System.currentTimeMillis() - lastCacheUpdate > maxDelay) {
cache.clear();
}
if (cache.containsKey(key)) {
List<DNSServiceEntry> cachedAddresses = cache.get(key);
if (cachedAddresses != null) {
return cachedAddresses;
}
}
Attributes dnsLookup = context.getAttributes(service + "." + domain, new String[] { SRV_RECORD });
Attribute attribute = dnsLookup.get(SRV_RECORD);
for (int i = 0; i < attribute.size(); i++) {
/*
* Get the current resource record
*/
String entry = (String) attribute.get(i);
String[] records = entry.split(" ");
String host = records[records.length - 1];
int port = Integer.parseInt(records[records.length - 2]);
int weight = Integer.parseInt(records[records.length - 3]);
int priority = Integer.parseInt(records[records.length - 4]);
/*
* possible to get TTL?
*/
/*
* Host entries in DNS should end with a "."
*/
if (host.endsWith(".")) {
host = host.substring(0, host.length() - 1);
}
addresses.add(new DNSServiceEntry(host, port, priority, weight));
}
/*
* Sort the addresses by DNS priority and weight settings
*/
Collections.sort(addresses);
/*
* Add item to cache.
*/
if (cache.size() > 100) {
cache.clear();
}
cache.put(key, addresses);
lastCacheUpdate = System.currentTimeMillis();
return addresses;
}
@Override
public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain) throws NamingException {
return resolveDnsServiceRecord(LDAP_SERVICE_PREFIX, domain);
}
@Override
public List<DNSServiceEntry> resolveLDAPDomainServers(final String domain, final String prefix)
throws NamingException {
return resolveDnsServiceRecord(prefix, domain);
}
}