1/* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.security.krb5; 27 28import sun.security.krb5.internal.Krb5; 29 30import java.security.AccessController; 31import java.security.PrivilegedActionException; 32import java.security.PrivilegedExceptionAction; 33import java.util.Arrays; 34import java.util.Hashtable; 35import java.util.Random; 36import java.util.StringTokenizer; 37 38import javax.naming.*; 39import javax.naming.directory.*; 40import javax.naming.spi.NamingManager; 41 42/** 43 * This class discovers the location of Kerberos services by querying DNS, 44 * as defined in RFC 4120. 45 * 46 * @author Seema Malkani 47 * @since 1.7 48 */ 49 50class KrbServiceLocator { 51 52 private static final String SRV_RR = "SRV"; 53 private static final String[] SRV_RR_ATTR = new String[] {SRV_RR}; 54 55 private static final String SRV_TXT = "TXT"; 56 private static final String[] SRV_TXT_ATTR = new String[] {SRV_TXT}; 57 58 private static final Random random = new Random(); 59 60 private static final boolean DEBUG = Krb5.DEBUG; 61 62 private KrbServiceLocator() { 63 } 64 65 /** 66 * Locates the KERBEROS service for a given domain. 67 * Queries DNS for a list of KERBEROS Service Text Records (TXT) for a 68 * given domain name. 69 * Information on the mapping of DNS hostnames and domain names 70 * to Kerberos realms is stored using DNS TXT records 71 * 72 * @param realmName A string realm name. 73 * @return An ordered list of hostports for the Kerberos service or null if 74 * the service has not been located. 75 */ 76 static String[] getKerberosService(String realmName) { 77 78 // search realm in SRV TXT records 79 String dnsUrl = "dns:///_kerberos." + realmName; 80 String[] records = null; 81 try { 82 // Create the DNS context using NamingManager rather than using 83 // the initial context constructor. This avoids having the initial 84 // context constructor call itself (when processing the URL 85 // argument in the getAttributes call). 86 Context ctx = NamingManager.getURLContext("dns", new Hashtable<>(0)); 87 if (!(ctx instanceof DirContext)) { 88 return null; // cannot create a DNS context 89 } 90 Attributes attrs = null; 91 try { 92 // both connect and accept are needed since DNS is thru UDP 93 attrs = AccessController.doPrivileged( 94 (PrivilegedExceptionAction<Attributes>) 95 () -> ((DirContext)ctx).getAttributes( 96 dnsUrl, SRV_TXT_ATTR), 97 null, 98 new java.net.SocketPermission("*", "connect,accept")); 99 } catch (PrivilegedActionException e) { 100 throw (NamingException)e.getCause(); 101 } 102 Attribute attr; 103 104 if (attrs != null && ((attr = attrs.get(SRV_TXT)) != null)) { 105 int numValues = attr.size(); 106 int numRecords = 0; 107 String[] txtRecords = new String[numValues]; 108 109 // gather the text records 110 int i = 0; 111 int j = 0; 112 while (i < numValues) { 113 try { 114 txtRecords[j] = (String)attr.get(i); 115 j++; 116 } catch (Exception e) { 117 // ignore bad value 118 } 119 i++; 120 } 121 numRecords = j; 122 123 // trim 124 if (numRecords < numValues) { 125 String[] trimmed = new String[numRecords]; 126 System.arraycopy(txtRecords, 0, trimmed, 0, numRecords); 127 records = trimmed; 128 } else { 129 records = txtRecords; 130 } 131 } 132 } catch (NamingException e) { 133 // ignore 134 } 135 return records; 136 } 137 138 /** 139 * Locates the KERBEROS service for a given domain. 140 * Queries DNS for a list of KERBEROS Service Location Records (SRV) for a 141 * given domain name. 142 * 143 * @param realmName A string realm name. 144 * @param protocol the protocol string, can be "_udp" or "_tcp" 145 * @return An ordered list of hostports for the Kerberos service or null if 146 * the service has not been located. 147 */ 148 static String[] getKerberosService(String realmName, String protocol) { 149 150 String dnsUrl = "dns:///_kerberos." + protocol + "." + realmName; 151 String[] hostports = null; 152 153 try { 154 // Create the DNS context using NamingManager rather than using 155 // the initial context constructor. This avoids having the initial 156 // context constructor call itself (when processing the URL 157 // argument in the getAttributes call). 158 Context ctx = NamingManager.getURLContext("dns", new Hashtable<>(0)); 159 if (!(ctx instanceof DirContext)) { 160 return null; // cannot create a DNS context 161 } 162 163 Attributes attrs = null; 164 try { 165 // both connect and accept are needed since DNS is thru UDP 166 attrs = AccessController.doPrivileged( 167 (PrivilegedExceptionAction<Attributes>) 168 () -> ((DirContext)ctx).getAttributes( 169 dnsUrl, SRV_RR_ATTR), 170 null, 171 new java.net.SocketPermission("*", "connect,accept")); 172 } catch (PrivilegedActionException e) { 173 throw (NamingException)e.getCause(); 174 } 175 176 Attribute attr; 177 178 if (attrs != null && ((attr = attrs.get(SRV_RR)) != null)) { 179 int numValues = attr.size(); 180 int numRecords = 0; 181 SrvRecord[] srvRecords = new SrvRecord[numValues]; 182 183 // create the service records 184 int i = 0; 185 int j = 0; 186 while (i < numValues) { 187 try { 188 srvRecords[j] = new SrvRecord((String) attr.get(i)); 189 j++; 190 } catch (Exception e) { 191 // ignore bad value 192 } 193 i++; 194 } 195 numRecords = j; 196 197 // trim 198 if (numRecords < numValues) { 199 SrvRecord[] trimmed = new SrvRecord[numRecords]; 200 System.arraycopy(srvRecords, 0, trimmed, 0, numRecords); 201 srvRecords = trimmed; 202 } 203 204 // Sort the service records in ascending order of their 205 // priority value. For records with equal priority, move 206 // those with weight 0 to the top of the list. 207 if (numRecords > 1) { 208 Arrays.sort(srvRecords); 209 } 210 211 // extract the host and port number from each service record 212 hostports = extractHostports(srvRecords); 213 } 214 } catch (NamingException e) { 215 // e.printStackTrace(); 216 // ignore 217 } 218 return hostports; 219 } 220 221 /** 222 * Extract hosts and port numbers from a list of SRV records. 223 * An array of hostports is returned or null if none were found. 224 */ 225 private static String[] extractHostports(SrvRecord[] srvRecords) { 226 String[] hostports = null; 227 228 int head = 0; 229 int tail = 0; 230 int sublistLength = 0; 231 int k = 0; 232 for (int i = 0; i < srvRecords.length; i++) { 233 if (hostports == null) { 234 hostports = new String[srvRecords.length]; 235 } 236 // find the head and tail of the list of records having the same 237 // priority value. 238 head = i; 239 while (i < srvRecords.length - 1 && 240 srvRecords[i].priority == srvRecords[i + 1].priority) { 241 i++; 242 } 243 tail = i; 244 245 // select hostports from the sublist 246 sublistLength = (tail - head) + 1; 247 for (int j = 0; j < sublistLength; j++) { 248 hostports[k++] = selectHostport(srvRecords, head, tail); 249 } 250 } 251 return hostports; 252 } 253 254 /* 255 * Randomly select a service record in the range [head, tail] and return 256 * its hostport value. Follows the algorithm in RFC 2782. 257 */ 258 private static String selectHostport(SrvRecord[] srvRecords, int head, 259 int tail) { 260 if (head == tail) { 261 return srvRecords[head].hostport; 262 } 263 264 // compute the running sum for records between head and tail 265 int sum = 0; 266 for (int i = head; i <= tail; i++) { 267 if (srvRecords[i] != null) { 268 sum += srvRecords[i].weight; 269 srvRecords[i].sum = sum; 270 } 271 } 272 String hostport = null; 273 274 // If all records have zero weight, select first available one; 275 // otherwise, randomly select a record according to its weight 276 int target = (sum == 0 ? 0 : random.nextInt(sum + 1)); 277 for (int i = head; i <= tail; i++) { 278 if (srvRecords[i] != null && srvRecords[i].sum >= target) { 279 hostport = srvRecords[i].hostport; 280 srvRecords[i] = null; // make this record unavailable 281 break; 282 } 283 } 284 return hostport; 285 } 286 287/** 288 * This class holds a DNS service (SRV) record. 289 * See http://www.ietf.org/rfc/rfc2782.txt 290 */ 291 292static class SrvRecord implements Comparable<SrvRecord> { 293 294 int priority; 295 int weight; 296 int sum; 297 String hostport; 298 299 /** 300 * Creates a service record object from a string record. 301 * DNS supplies the string record in the following format: 302 * <pre> 303 * <Priority> " " <Weight> " " <Port> " " <Host> 304 * </pre> 305 */ 306 SrvRecord(String srvRecord) throws Exception { 307 StringTokenizer tokenizer = new StringTokenizer(srvRecord, " "); 308 String port; 309 310 if (tokenizer.countTokens() == 4) { 311 priority = Integer.parseInt(tokenizer.nextToken()); 312 weight = Integer.parseInt(tokenizer.nextToken()); 313 port = tokenizer.nextToken(); 314 hostport = tokenizer.nextToken() + ":" + port; 315 } else { 316 throw new IllegalArgumentException(); 317 } 318 } 319 320 /* 321 * Sort records in ascending order of priority value. For records with 322 * equal priority move those with weight 0 to the top of the list. 323 */ 324 public int compareTo(SrvRecord that) { 325 if (priority > that.priority) { 326 return 1; // this > that 327 } else if (priority < that.priority) { 328 return -1; // this < that 329 } else if (weight == 0 && that.weight != 0) { 330 return -1; // this < that 331 } else if (weight != 0 && that.weight == 0) { 332 return 1; // this > that 333 } else { 334 return 0; // this == that 335 } 336 } 337} 338} 339