6 changed files with 486 additions and 0 deletions
@ -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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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