-
Notifications
You must be signed in to change notification settings - Fork 431
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added DNS Fallback for REALM resolution
- Loading branch information
1 parent
8fd821b
commit d6de090
Showing
5 changed files
with
290 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.microsoft.sqlserver.jdbc.dns; | ||
|
||
import java.util.Set; | ||
|
||
import javax.naming.NameNotFoundException; | ||
import javax.naming.NamingException; | ||
|
||
public final class DNSKerberosLocator { | ||
|
||
private DNSKerberosLocator() {} | ||
|
||
/** | ||
* Tells whether a realm is valid. | ||
* | ||
* @param realmName the realm to test | ||
* @return true if realm is valid, false otherwise | ||
* @throws NamingException if DNS failed, so realm existence cannot be determined | ||
*/ | ||
public static boolean isRealmValid(String realmName) throws NamingException { | ||
if (realmName == null || realmName.length() < 2) { | ||
return false; | ||
} | ||
if (realmName.startsWith(".")) { | ||
realmName = realmName.substring(1); | ||
} | ||
try { | ||
Set<DNSRecordSRV> records = DNSUtilities.findSrvRecords("_kerberos._udp." + realmName); | ||
return !records.isEmpty(); | ||
} catch (NameNotFoundException wrongDomainException) { | ||
return false; | ||
} | ||
} | ||
} |
142 changes: 142 additions & 0 deletions
142
src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package com.microsoft.sqlserver.jdbc.dns; | ||
|
||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Describe an DNS SRV Record. | ||
*/ | ||
public class DNSRecordSRV implements Comparable<DNSRecordSRV> { | ||
|
||
private static final Pattern PATTERN = Pattern.compile("^([0-9]+) ([0-9]+) ([0-9]+) (.+)$"); | ||
|
||
private final int priority; | ||
|
||
/** | ||
* Parse a DNS SRC Record from a DNS String record. | ||
* | ||
* @param record | ||
* the record to parse | ||
* @return a not null DNS Record | ||
* @throws IllegalArgumentException | ||
* if record is not correct and cannot be parsed | ||
*/ | ||
public static DNSRecordSRV parseFromDNSRecord(String record) throws IllegalArgumentException { | ||
Matcher m = PATTERN.matcher(record); | ||
if (!m.matches()) { | ||
throw new IllegalArgumentException("record '" + record + "' cannot be matched as a valid DNS SRV Record"); | ||
} | ||
try { | ||
int priority = Integer.parseInt(m.group(1)); | ||
int weight = Integer.parseInt(m.group(2)); | ||
int port = Integer.parseInt(m.group(3)); | ||
String serverName = m.group(4); | ||
// Avoid issues with Kerberos SPN when fully qualified records ends with '.' | ||
if (serverName.endsWith(".")) { | ||
serverName = serverName.substring(0, serverName.length() - 1); | ||
} | ||
return new DNSRecordSRV(priority, weight, port, serverName); | ||
} catch (IllegalArgumentException err) { | ||
throw err; | ||
} catch (Exception err) { | ||
throw new IllegalArgumentException("Failed to parse DNS SRV record '" + record + "'", err); | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("DNS.SRV[pri=%d w=%d port=%d h='%s']", priority, weight, port, serverName); | ||
} | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param priority | ||
* is lowest | ||
* @param weight | ||
* 1 at minimum | ||
* @param port | ||
* the port of service | ||
* @param serverName | ||
* the host | ||
* @throws IllegalArgumentException | ||
* if priority < 0 or weight <= 1 | ||
*/ | ||
public DNSRecordSRV(int priority, int weight, int port, String serverName) throws IllegalArgumentException { | ||
if (priority < 0) { | ||
throw new IllegalArgumentException("priority must be >= 0, but was: " + priority); | ||
} | ||
this.priority = priority; | ||
if (weight < 0) { | ||
// Weight == 0 is OK to disable load balancing, but not below | ||
throw new IllegalArgumentException("weight must be >= 0, but was: " + weight); | ||
} | ||
this.weight = weight; | ||
if (port < 0 || port > 65535) { | ||
throw new IllegalArgumentException("port must be between 0 and 65535, but was: " + port); | ||
} | ||
this.port = port; | ||
if (serverName == null || serverName.trim().isEmpty()) { | ||
throw new IllegalArgumentException("hostname is not supposed to be null or empty in a SRV Record"); | ||
} | ||
this.serverName = serverName; | ||
} | ||
|
||
private final int weight; | ||
private final int port; | ||
private final String serverName; | ||
|
||
@Override | ||
public int hashCode() { | ||
return serverName.hashCode(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (other == this) { | ||
return true; | ||
} | ||
if (!(other instanceof DNSRecordSRV)) { | ||
return false; | ||
} | ||
|
||
DNSRecordSRV r = (DNSRecordSRV) other; | ||
return port == r.port && weight == r.weight && priority == r.priority && serverName.equals(r.serverName); | ||
} | ||
|
||
@Override | ||
public int compareTo(DNSRecordSRV o) { | ||
if (o == null) { | ||
return 1; | ||
} | ||
int p = Integer.compare(priority, o.priority); | ||
if (p != 0) { | ||
return p; | ||
} | ||
p = Integer.compare(weight, o.weight); | ||
if (p != 0) { | ||
return p; | ||
} | ||
p = Integer.compare(port, o.port); | ||
if (p != 0) { | ||
return p; | ||
} | ||
return serverName.compareTo(o.serverName); | ||
} | ||
|
||
public int getPriority() { | ||
return priority; | ||
} | ||
|
||
public int getWeight() { | ||
return weight; | ||
} | ||
|
||
public int getPort() { | ||
return port; | ||
} | ||
|
||
public String getServerName() { | ||
return serverName; | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package com.microsoft.sqlserver.jdbc.dns; | ||
|
||
import java.util.Hashtable; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
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; | ||
|
||
public class DNSUtilities { | ||
|
||
private final static Logger LOG = Logger.getLogger(DNSUtilities.class.getName()); | ||
|
||
private static final Level DNS_ERR_LOG_LEVEL = Level.FINE; | ||
|
||
/** | ||
* Find all SRV Record using DNS. | ||
* You can then use {@link DNSRecordsSRVCollection#getBestRecord()} to find | ||
* the best candidate (for instance for Round-Robin calls) | ||
* | ||
* @param dnsSrvRecordToFind | ||
* the DNS record, for instance: _ldap._tcp.dc._msdcs.DOMAIN.COM | ||
* to find all LDAP servers in DOMAIN.COM | ||
* @return the collection of records with facilities to find the best | ||
* candidate | ||
* @throws NamingException | ||
* if DNS is not available | ||
*/ | ||
public static Set<DNSRecordSRV> findSrvRecords(final String dnsSrvRecordToFind) throws NamingException { | ||
Hashtable<Object, Object> env = new Hashtable<Object, Object>(); | ||
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); | ||
env.put("java.naming.provider.url", "dns:"); | ||
DirContext ctx = new InitialDirContext(env); | ||
Attributes attrs = ctx.getAttributes(dnsSrvRecordToFind, new String[] { "SRV" }); | ||
NamingEnumeration<? extends Attribute> allServers = attrs.getAll(); | ||
TreeSet<DNSRecordSRV> records = new TreeSet<DNSRecordSRV>(); | ||
while (allServers.hasMoreElements()) { | ||
Attribute a = allServers.nextElement(); | ||
NamingEnumeration<?> srvRecord = a.getAll(); | ||
while (srvRecord.hasMore()) { | ||
final String record = String.valueOf(srvRecord.nextElement()); | ||
try { | ||
DNSRecordSRV rec = DNSRecordSRV.parseFromDNSRecord(record); | ||
if (rec != null) { | ||
records.add(rec); | ||
} | ||
} catch (IllegalArgumentException errorParsingRecord) { | ||
if (LOG.isLoggable(DNS_ERR_LOG_LEVEL)) { | ||
LOG.log(DNS_ERR_LOG_LEVEL, String.format("Failed to parse SRV DNS Record: '%s'", record), | ||
errorParsingRecord); | ||
} | ||
} | ||
} | ||
} | ||
return records; | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.microsoft.sqlserver.jdbc.dns; | ||
|
||
import javax.naming.NamingException; | ||
|
||
public class DNSRealmsTest { | ||
|
||
public static void main(String... args) { | ||
if (args.length < 1) { | ||
System.err.println("USAGE: list of domains to test for kerberos realms"); | ||
} | ||
for (String realmName : args) { | ||
try { | ||
System.out.print(DNSKerberosLocator.isRealmValid(realmName) ? "[ VALID ] " : "[INVALID] "); | ||
} catch (NamingException err) { | ||
System.err.print("[ FAILED] : " + err.getClass().getName() + ":" + err.getMessage()); | ||
} | ||
System.out.println(realmName); | ||
} | ||
} | ||
|
||
} |