6 changed files with 486 additions and 0 deletions
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
|
||||
/** |
||||
* This will be thrown, if no entry matches the specified DNS query |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
*/ |
||||
public class DnsEntryNotFoundException extends DnsLookupException { |
||||
|
||||
private static final long serialVersionUID = -947232730426775162L; |
||||
|
||||
public DnsEntryNotFoundException(String msg) { |
||||
super(msg); |
||||
} |
||||
|
||||
public DnsEntryNotFoundException(String msg, Throwable cause) { |
||||
super(msg, cause); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* This will be thrown for unknown DNS errors |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
*/ |
||||
public class DnsLookupException extends DataAccessException { |
||||
|
||||
private static final long serialVersionUID = -7538424279394361310L; |
||||
|
||||
public DnsLookupException(String msg, Throwable cause) { |
||||
super(msg, cause); |
||||
} |
||||
|
||||
public DnsLookupException(String msg) { |
||||
super(msg); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
/** |
||||
* Helper class for DNS operations |
||||
* |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
*/ |
||||
public interface DnsResolver { |
||||
|
||||
/** |
||||
* Resolves the IP Address (A record) to the specified host name. |
||||
* Throws DnsEntryNotFoundException if there is no record. |
||||
* @param hostname The hostname for which you need the IP Address |
||||
* @return IP Address as a String |
||||
* @throws DnsEntryNotFoundException No record found |
||||
* @throws DnsLookupException Unknown DNS error |
||||
*/ |
||||
public String resolveIpAddress(String hostname) throws DnsEntryNotFoundException, DnsLookupException; |
||||
|
||||
/** |
||||
* <p>Resolves the host name for the specified service in the specified domain</p> |
||||
* <p>For example, if you need the host name for an LDAP server running in the |
||||
* domain springsource.com, you would call <b>resolveServiceEntry("ldap", "springsource.com")</b>.</p> |
||||
* |
||||
* <p>The DNS server needs to provide the service records for this, in the example above, it |
||||
* would look like this: |
||||
* |
||||
* <pre>_ldap._tcp.springsource.com IN SRV 10 0 88 ldap.springsource.com.</pre> |
||||
* |
||||
* The method will return the record with highest priority (which means the lowest number in the DNS record) |
||||
* and if there are more than one records with the same priority, it will return the one with the highest weight. |
||||
* You will find more informatione about DNS service records at <a href="http://en.wikipedia.org/wiki/SRV_record">Wikipedia</a>.</p> |
||||
* |
||||
* @param serviceType The service type you are searching for, e.g. ldap, kerberos, ... |
||||
* @param domain The domain, in which you are searching for the service |
||||
* @return The hostname of the service |
||||
* @throws DnsEntryNotFoundException No record found |
||||
* @throws DnsLookupException Unknown DNS error |
||||
*/ |
||||
public String resolveServiceEntry(String serviceType, String domain) throws DnsEntryNotFoundException, DnsLookupException; |
||||
|
||||
/** |
||||
* Resolves the host name for the specified service and then the IP Address for this host in one call. |
||||
* |
||||
* @param serviceType The service type you are searching for, e.g. ldap, kerberos, ... |
||||
* @param domain The domain, in which you are searching for the service |
||||
* @return IP Address of the service |
||||
* @throws DnsEntryNotFoundException No record found |
||||
* @throws DnsLookupException Unknown DNS error |
||||
* @see #resolveServiceEntry(String, String) |
||||
* @see #resolveIpAddress(String) |
||||
*/ |
||||
public String resolveServiceIpAddress(String serviceType, String domain) throws DnsEntryNotFoundException, DnsLookupException; |
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
|
||||
import javax.naming.directory.DirContext; |
||||
import javax.naming.directory.InitialDirContext; |
||||
|
||||
/** |
||||
* This is used in JndiDnsResolver to get an InitialDirContext for DNS queries. |
||||
* |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
* @see InitialDirContext |
||||
* @see DirContext |
||||
* @see JndiDnsResolver |
||||
*/ |
||||
public interface InitialContextFactory { |
||||
|
||||
|
||||
/** |
||||
* Must return a DirContext which can be used for DNS queries |
||||
* @return JNDI DirContext |
||||
*/ |
||||
public DirContext getCtx(); |
||||
|
||||
} |
||||
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
import java.util.Hashtable; |
||||
|
||||
import javax.naming.Context; |
||||
import javax.naming.NameNotFoundException; |
||||
import javax.naming.NamingEnumeration; |
||||
import javax.naming.NamingException; |
||||
import javax.naming.directory.Attribute; |
||||
import javax.naming.directory.Attributes; |
||||
import javax.naming.directory.DirContext; |
||||
import javax.naming.directory.InitialDirContext; |
||||
|
||||
|
||||
/** |
||||
* Implementation of DnsResolver which uses JNDI for the DNS queries. |
||||
* |
||||
* Uses an <b>InitialContextFactory</b> to get the JNDI DirContext. The default implementation |
||||
* will just create a new Context with the context factory <b>com.sun.jndi.dns.DnsContextFactory</b> |
||||
* |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
* @see DnsResolver |
||||
* @see InitialContextFactory |
||||
*/ |
||||
public class JndiDnsResolver implements DnsResolver { |
||||
|
||||
private InitialContextFactory ctxFactory = new DefaultInitialContextFactory(); |
||||
|
||||
/** |
||||
* Allows to inject an own JNDI context factory. |
||||
* |
||||
* @param ctxFactory factory to use, when a DirContext is needed |
||||
* @see InitialDirContext |
||||
* @see DirContext |
||||
*/ |
||||
public void setCtxFactory(InitialContextFactory ctxFactory) { |
||||
this.ctxFactory = ctxFactory; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.security.remoting.dns.DnsResolver#resolveIpAddress(java.lang.String) |
||||
*/ |
||||
public String resolveIpAddress(String hostname) { |
||||
return resolveIpAddress(hostname, ctxFactory.getCtx()); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.security.remoting.dns.DnsResolver#resolveServiceEntry(java.lang.String, java.lang.String) |
||||
*/ |
||||
public String resolveServiceEntry(String serviceType, String domain) { |
||||
return resolveServiceEntry(serviceType, domain, ctxFactory.getCtx()); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.security.remoting.dns.DnsResolver#resolveServiceIpAddress(java.lang.String, java.lang.String) |
||||
*/ |
||||
public String resolveServiceIpAddress(String serviceType, String domain) { |
||||
DirContext ctx = ctxFactory.getCtx(); |
||||
String hostname = resolveServiceEntry(serviceType, domain, ctx); |
||||
return resolveIpAddress(hostname, ctx); |
||||
} |
||||
|
||||
|
||||
|
||||
// This method is needed, so that we can use only one DirContext for
|
||||
// resolveServiceIpAddress().
|
||||
private String resolveIpAddress(String hostname, DirContext ctx) { |
||||
try { |
||||
Attribute dnsRecord = lookup(hostname, ctx, "A"); |
||||
// There should be only one A record, therefore it is save to return
|
||||
// only the first.
|
||||
return dnsRecord.get().toString(); |
||||
} catch (NamingException e) { |
||||
throw new DnsLookupException("DNS lookup failed for: "+ hostname, e); |
||||
} |
||||
|
||||
} |
||||
|
||||
// This method is needed, so that we can use only one DirContext for
|
||||
// resolveServiceIpAddress().
|
||||
private String resolveServiceEntry(String serviceType, String domain, DirContext ctx) { |
||||
String result = null; |
||||
try { |
||||
String query = new StringBuilder("_").append(serviceType).append("._tcp.").append(domain).toString(); |
||||
Attribute dnsRecord = lookup(query, ctx, "SRV"); |
||||
// There are maybe more records defined, we will return the one
|
||||
// with the highest priority (lowest number) and the highest weight
|
||||
// (highest number)
|
||||
int highestPriority = -1; |
||||
int highestWeight = -1; |
||||
|
||||
for (NamingEnumeration<?> recordEnum = dnsRecord.getAll(); recordEnum.hasMoreElements();) { |
||||
String[] record = recordEnum.next().toString().split(" "); |
||||
if (record.length != 4) { |
||||
throw new DnsLookupException("Wrong service record for query " + query + ": [" + record + "]"); |
||||
} |
||||
int priority = Integer.parseInt(record[0]); |
||||
int weight = Integer.parseInt(record[1]); |
||||
// we have a new highest Priority, so forget also the highest weight
|
||||
if (priority < highestPriority || highestPriority == -1) { |
||||
highestPriority = priority; |
||||
highestWeight = weight; |
||||
result = record[3].trim(); |
||||
} |
||||
// same priority, but higher weight
|
||||
if (priority == highestPriority && weight > highestWeight) { |
||||
highestWeight = weight; |
||||
result = record[3].trim(); |
||||
} |
||||
} |
||||
} catch (NamingException e) { |
||||
throw new DnsLookupException("DNS lookup failed for service " + serviceType + " at " + domain, e); |
||||
} |
||||
|
||||
// remove the "." at the end
|
||||
if (result.endsWith(".")) { |
||||
result = result.substring(0, result.length() - 1); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private Attribute lookup(String query, DirContext ictx, String recordType) { |
||||
try { |
||||
Attributes dnsResult = ictx.getAttributes(query, new String[] { recordType }); |
||||
Attribute dnsRecord = dnsResult.get(recordType); |
||||
return dnsRecord; |
||||
} catch (NamingException e) { |
||||
if (e instanceof NameNotFoundException) { |
||||
throw new DnsEntryNotFoundException("DNS entry not found for:" + query, e); |
||||
} |
||||
throw new DnsLookupException("DNS lookup failed for: " + query, e); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
private static class DefaultInitialContextFactory implements InitialContextFactory { |
||||
|
||||
public DirContext getCtx() { |
||||
Hashtable<String, String> env = new Hashtable<String, String>(); |
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); |
||||
env.put(Context.PROVIDER_URL, "dns:"); // This is needed for IBM JDK/JRE
|
||||
InitialDirContext ictx; |
||||
try { |
||||
ictx = new InitialDirContext(env); |
||||
} catch (NamingException e) { |
||||
throw new DnsLookupException("Cannot create InitialDirContext for DNS lookup", e); |
||||
} |
||||
return ictx; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
/* |
||||
* Copyright 2009 the original author or authors. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package org.springframework.security.remoting.dns; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import javax.naming.NameNotFoundException; |
||||
import javax.naming.NamingException; |
||||
import javax.naming.directory.Attributes; |
||||
import javax.naming.directory.BasicAttribute; |
||||
import javax.naming.directory.BasicAttributes; |
||||
import javax.naming.directory.DirContext; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* |
||||
* @author Mike Wiesner |
||||
* @since 3.0 |
||||
* @version $Id$ |
||||
*/ |
||||
public class JndiDnsResolverTest { |
||||
|
||||
private JndiDnsResolver dnsResolver; |
||||
private InitialContextFactory contextFactory; |
||||
private DirContext context; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
contextFactory = mock(InitialContextFactory.class); |
||||
context = mock(DirContext.class); |
||||
dnsResolver = new JndiDnsResolver(); |
||||
dnsResolver.setCtxFactory(contextFactory); |
||||
when(contextFactory.getCtx()).thenReturn(context); |
||||
} |
||||
|
||||
@Test |
||||
public void testResolveIpAddress() throws Exception { |
||||
Attributes records = new BasicAttributes("A","63.246.7.80"); |
||||
|
||||
when(context.getAttributes("www.springsource.com", new String[] {"A"})).thenReturn(records); |
||||
|
||||
String ipAddress = dnsResolver.resolveIpAddress("www.springsource.com"); |
||||
assertEquals("63.246.7.80", ipAddress); |
||||
} |
||||
|
||||
@Test(expected=DnsEntryNotFoundException.class) |
||||
public void testResolveIpAddressNotExisting() throws Exception { |
||||
when(context.getAttributes(any(String.class), any(String[].class))).thenThrow(new NameNotFoundException("not found")); |
||||
|
||||
dnsResolver.resolveIpAddress("notexisting.ansdansdugiuzgguzgioansdiandwq.foo"); |
||||
} |
||||
|
||||
@Test |
||||
public void testResolveServiceEntry() throws Exception { |
||||
BasicAttributes records = createSrvRecords(); |
||||
|
||||
when(context.getAttributes("_ldap._tcp.springsource.com", new String[] {"SRV"})).thenReturn(records); |
||||
|
||||
String hostname = dnsResolver.resolveServiceEntry("ldap", "springsource.com"); |
||||
assertEquals("kdc.springsource.com", hostname); |
||||
} |
||||
|
||||
|
||||
@Test(expected=DnsEntryNotFoundException.class) |
||||
public void testResolveServiceEntryNotExisting() throws Exception { |
||||
when(context.getAttributes(any(String.class), any(String[].class))).thenThrow(new NameNotFoundException("not found")); |
||||
|
||||
dnsResolver.resolveServiceEntry("wrong", "secpod.de"); |
||||
} |
||||
|
||||
@Test |
||||
public void testResolveServiceIpAddress() throws Exception { |
||||
BasicAttributes srvRecords = createSrvRecords(); |
||||
BasicAttributes aRecords = new BasicAttributes("A", "63.246.7.80"); |
||||
when(context.getAttributes("_ldap._tcp.springsource.com", new String[] {"SRV"})).thenReturn(srvRecords); |
||||
when(context.getAttributes("kdc.springsource.com", new String[] {"A"})).thenReturn(aRecords); |
||||
|
||||
String ipAddress = dnsResolver.resolveServiceIpAddress("ldap", "springsource.com"); |
||||
assertEquals("63.246.7.80", ipAddress); |
||||
} |
||||
|
||||
@Test(expected=DnsLookupException.class) |
||||
public void testUnknowError() throws Exception { |
||||
when(context.getAttributes(any(String.class), any(String[].class))).thenThrow(new NamingException("error")); |
||||
dnsResolver.resolveIpAddress(""); |
||||
} |
||||
|
||||
|
||||
|
||||
private BasicAttributes createSrvRecords() { |
||||
BasicAttributes records = new BasicAttributes(); |
||||
BasicAttribute record = new BasicAttribute("SRV"); |
||||
// the structure of the service records is:
|
||||
// priority weight port hostname
|
||||
// for more information: http://en.wikipedia.org/wiki/SRV_record
|
||||
record.add("20 80 389 kdc3.springsource.com."); |
||||
record.add("10 70 389 kdc.springsource.com."); |
||||
record.add("20 20 389 kdc4.springsource.com."); |
||||
record.add("10 30 389 kdc2.springsource.com"); |
||||
records.put(record); |
||||
return records; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue