1 /* 2 * Copyright (c) 2000, 2012, 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 26 /* 27 * 28 * (C) Copyright IBM Corp. 1999 All Rights Reserved. 29 * Copyright 1997 The Open Group Research Institute. All rights reserved. 30 */ 31 package sun.security.krb5; 32 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.util.Hashtable; 36 import java.util.Vector; 37 import java.util.ArrayList; 38 import java.io.BufferedReader; 39 import java.io.InputStreamReader; 40 import java.io.IOException; 41 import java.util.StringTokenizer; 42 import java.net.InetAddress; 43 import java.net.UnknownHostException; 44 import java.security.AccessController; 45 import java.security.PrivilegedExceptionAction; 46 import java.util.Arrays; 47 import java.util.List; 48 import java.util.Locale; 49 import sun.net.dns.ResolverConfiguration; 50 import sun.security.krb5.internal.crypto.EType; 51 import sun.security.krb5.internal.Krb5; 52 53 /** 54 * This class maintains key-value pairs of Kerberos configurable constants 55 * from configuration file or from user specified system properties. 56 */ 57 58 public class Config { 59 60 /* 61 * Only allow a single instance of Config. 62 */ 63 private static Config singleton = null; 64 65 /* 66 * Hashtable used to store configuration infomation. 67 */ 68 private Hashtable<String,Object> stanzaTable = new Hashtable<>(); 69 70 private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG; 71 72 // these are used for hexdecimal calculation. 73 private static final int BASE16_0 = 1; 74 private static final int BASE16_1 = 16; 75 private static final int BASE16_2 = 16 * 16; 76 private static final int BASE16_3 = 16 * 16 * 16; 77 78 /** 79 * Specified by system properties. Must be both null or non-null. 80 */ 81 private final String defaultRealm; 82 private final String defaultKDC; 83 84 // used for native interface 85 private static native String getWindowsDirectory(boolean isSystem); 86 87 88 /** 89 * Gets an instance of Config class. One and only one instance (the 90 * singleton) is returned. 91 * 92 * @exception KrbException if error occurs when constructing a Config 93 * instance. Possible causes would be either of java.security.krb5.realm or 94 * java.security.krb5.kdc not specified, error reading configuration file. 95 */ 96 public static synchronized Config getInstance() throws KrbException { 97 if (singleton == null) { 98 singleton = new Config(); 99 } 100 return singleton; 101 } 102 103 /** 104 * Refresh and reload the Configuration. This could involve, 105 * for example reading the Configuration file again or getting 106 * the java.security.krb5.* system properties again. This method 107 * also tries its best to update static fields in other classes 108 * that depend on the configuration. 109 * 110 * @exception KrbException if error occurs when constructing a Config 111 * instance. Possible causes would be either of java.security.krb5.realm or 112 * java.security.krb5.kdc not specified, error reading configuration file. 113 */ 114 115 public static synchronized void refresh() throws KrbException { 116 singleton = new Config(); 117 KdcComm.initStatic(); 118 EType.initStatic(); 119 Checksum.initStatic(); 120 } 121 122 123 private static boolean isMacosLionOrBetter() { 124 // split the "10.x.y" version number 125 String osname = getProperty("os.name"); 126 if (!osname.contains("OS X")) { 127 return false; 128 } 129 130 String osVersion = getProperty("os.version"); 131 String[] fragments = osVersion.split("\\."); 132 133 // sanity check the "10." part of the version 134 if (!fragments[0].equals("10")) return false; 135 if (fragments.length < 2) return false; 136 137 // check if Mac OS X 10.7(.y) 138 try { 139 int minorVers = Integer.parseInt(fragments[1]); 140 if (minorVers >= 7) return true; 141 } catch (NumberFormatException e) { 142 // was not an integer 143 } 144 145 return false; 146 } 147 148 /** 149 * Private constructor - can not be instantiated externally. 150 */ 151 private Config() throws KrbException { 152 /* 153 * If either one system property is specified, we throw exception. 154 */ 155 String tmp = getProperty("java.security.krb5.kdc"); 156 if (tmp != null) { 157 // The user can specify a list of kdc hosts separated by ":" 158 defaultKDC = tmp.replace(':', ' '); 159 } else { 160 defaultKDC = null; 161 } 162 defaultRealm = getProperty("java.security.krb5.realm"); 163 if ((defaultKDC == null && defaultRealm != null) || 164 (defaultRealm == null && defaultKDC != null)) { 165 throw new KrbException 166 ("System property java.security.krb5.kdc and " + 167 "java.security.krb5.realm both must be set or " + 168 "neither must be set."); 169 } 170 171 // Always read the Kerberos configuration file 172 try { 173 List<String> configFile; 174 String fileName = getJavaFileName(); 175 if (fileName != null) { 176 configFile = loadConfigFile(fileName); 177 stanzaTable = parseStanzaTable(configFile); 178 if (DEBUG) { 179 System.out.println("Loaded from Java config"); 180 } 181 } else { 182 boolean found = false; 183 if (isMacosLionOrBetter()) { 184 try { 185 stanzaTable = SCDynamicStoreConfig.getConfig(); 186 if (DEBUG) { 187 System.out.println("Loaded from SCDynamicStoreConfig"); 188 } 189 found = true; 190 } catch (IOException ioe) { 191 // OK. Will go on with file 192 } 193 } 194 if (!found) { 195 fileName = getNativeFileName(); 196 configFile = loadConfigFile(fileName); 197 stanzaTable = parseStanzaTable(configFile); 198 if (DEBUG) { 199 System.out.println("Loaded from native config"); 200 } 201 } 202 } 203 } catch (IOException ioe) { 204 // I/O error, mostly like krb5.conf missing. 205 // No problem. We'll use DNS or system property etc. 206 } 207 } 208 209 /** 210 * Gets the last-defined string value for the specified keys. 211 * @param keys the keys, as an array from section name, sub-section names 212 * (if any), to value name. 213 * @return the value. When there are multiple values for the same key, 214 * returns the last one. {@code null} is returned if not all the keys are 215 * defined. For example, {@code get("libdefaults", "forwardable")} will 216 * return null if "forwardable" is not defined in [libdefaults], and 217 * {@code get("realms", "R", "kdc")} will return null if "R" is not 218 * defined in [realms] or "kdc" is not defined for "R". 219 * @throws IllegalArgumentException if any of the keys is illegal, either 220 * because a key not the last one is not a (sub)section name or the last 221 * key is still a section name. For example, {@code get("libdefaults")} 222 * throws this exception because [libdefaults] is a section name instead of 223 * a value name, and {@code get("libdefaults", "forwardable", "tail")} 224 * also throws this exception because "forwardable" is already a value name 225 * and has no sub-key at all (given "forwardable" is defined, otherwise, 226 * this method has no knowledge if it's a value name or a section name), 227 */ 228 public String get(String... keys) { 229 Vector<String> v = getString0(keys); 230 if (v == null) return null; 231 return v.lastElement(); 232 } 233 234 /** 235 * Gets all values for the specified keys. 236 * @see #get(java.lang.String[]) 237 */ 238 public String getAll(String... keys) { 239 Vector<String> v = getString0(keys); 240 if (v == null) return null; 241 StringBuilder sb = new StringBuilder(); 242 boolean first = true; 243 for (String s: v) { 244 if (first) { 245 sb.append(s); 246 first = false; 247 } else { 248 sb.append(' ').append(s); 249 } 250 } 251 return sb.toString(); 252 } 253 254 /** 255 * Returns if keys exists, can be either final string(s) or sub-stanza 256 * @throws IllegalArgumentException if any of the keys is illegal 257 * @see get 258 */ 259 public boolean exists(String... keys) { 260 return get0(keys) != null; 261 } 262 263 // Returns final string value(s) for given keys. 264 @SuppressWarnings("unchecked") 265 private Vector<String> getString0(String... keys) { 266 try { 267 return (Vector<String>)get0(keys); 268 } catch (ClassCastException cce) { 269 throw new IllegalArgumentException(cce); 270 } 271 } 272 273 // Internal method. Returns the value for keys, which can be a sub-stanza 274 // or final string value(s). 275 // The only method (except for toString) that reads stanzaTable directly. 276 @SuppressWarnings("unchecked") 277 private Object get0(String... keys) { 278 Object current = stanzaTable; 279 try { 280 for (String key: keys) { 281 current = ((Hashtable<String,Object>)current).get(key); 282 if (current == null) return null; 283 } 284 return current; 285 } catch (ClassCastException cce) { 286 throw new IllegalArgumentException(cce); 287 } 288 } 289 290 /** 291 * Gets the int value for the specified keys. 292 * @param keys the keys 293 * @return the int value, Integer.MIN_VALUE is returned if it cannot be 294 * found or the value is not a legal integer. 295 * @throw IllegalArgumentException if any of the keys is illegal 296 * @see #get(java.lang.String[]) 297 */ 298 public int getIntValue(String... keys) { 299 String result = get(keys); 300 int value = Integer.MIN_VALUE; 301 if (result != null) { 302 try { 303 value = parseIntValue(result); 304 } catch (NumberFormatException e) { 305 if (DEBUG) { 306 System.out.println("Exception in getting value of " + 307 Arrays.toString(keys) + " " + 308 e.getMessage()); 309 System.out.println("Setting " + Arrays.toString(keys) + 310 " to minimum value"); 311 } 312 value = Integer.MIN_VALUE; 313 } 314 } 315 return value; 316 } 317 318 /** 319 * Gets the boolean value for the specified keys. 320 * @param keys the keys 321 * @return the boolean value, false is returned if it cannot be 322 * found or the value is not "true" (case insensitive). 323 * @throw IllegalArgumentException if any of the keys is illegal 324 * @see #get(java.lang.String[]) 325 */ 326 public boolean getBooleanValue(String... keys) { 327 String val = get(keys); 328 if (val != null && val.equalsIgnoreCase("true")) { 329 return true; 330 } else { 331 return false; 332 } 333 } 334 335 /** 336 * Parses a string to an integer. The convertible strings include the 337 * string representations of positive integers, negative integers, and 338 * hex decimal integers. Valid inputs are, e.g., -1234, +1234, 339 * 0x40000. 340 * 341 * @param input the String to be converted to an Integer. 342 * @return an numeric value represented by the string 343 * @exception NumberFormationException if the String does not contain a 344 * parsable integer. 345 */ 346 private int parseIntValue(String input) throws NumberFormatException { 347 int value = 0; 348 if (input.startsWith("+")) { 349 String temp = input.substring(1); 350 return Integer.parseInt(temp); 351 } else if (input.startsWith("0x")) { 352 String temp = input.substring(2); 353 char[] chars = temp.toCharArray(); 354 if (chars.length > 8) { 355 throw new NumberFormatException(); 356 } else { 357 for (int i = 0; i < chars.length; i++) { 358 int index = chars.length - i - 1; 359 switch (chars[i]) { 360 case '0': 361 value += 0; 362 break; 363 case '1': 364 value += 1 * getBase(index); 365 break; 366 case '2': 367 value += 2 * getBase(index); 368 break; 369 case '3': 370 value += 3 * getBase(index); 371 break; 372 case '4': 373 value += 4 * getBase(index); 374 break; 375 case '5': 376 value += 5 * getBase(index); 377 break; 378 case '6': 379 value += 6 * getBase(index); 380 break; 381 case '7': 382 value += 7 * getBase(index); 383 break; 384 case '8': 385 value += 8 * getBase(index); 386 break; 387 case '9': 388 value += 9 * getBase(index); 389 break; 390 case 'a': 391 case 'A': 392 value += 10 * getBase(index); 393 break; 394 case 'b': 395 case 'B': 396 value += 11 * getBase(index); 397 break; 398 case 'c': 399 case 'C': 400 value += 12 * getBase(index); 401 break; 402 case 'd': 403 case 'D': 404 value += 13 * getBase(index); 405 break; 406 case 'e': 407 case 'E': 408 value += 14 * getBase(index); 409 break; 410 case 'f': 411 case 'F': 412 value += 15 * getBase(index); 413 break; 414 default: 415 throw new NumberFormatException("Invalid numerical format"); 416 } 417 } 418 } 419 if (value < 0) { 420 throw new NumberFormatException("Data overflow."); 421 } 422 } else { 423 value = Integer.parseInt(input); 424 } 425 return value; 426 } 427 428 private int getBase(int i) { 429 int result = 16; 430 switch (i) { 431 case 0: 432 result = BASE16_0; 433 break; 434 case 1: 435 result = BASE16_1; 436 break; 437 case 2: 438 result = BASE16_2; 439 break; 440 case 3: 441 result = BASE16_3; 442 break; 443 default: 444 for (int j = 1; j < i; j++) { 445 result *= 16; 446 } 447 } 448 return result; 449 } 450 451 /** 452 * Reads lines to the memory from the configuration file. 453 * 454 * Configuration file contains information about the default realm, 455 * ticket parameters, location of the KDC and the admin server for 456 * known realms, etc. The file is divided into sections. Each section 457 * contains one or more name/value pairs with one pair per line. A 458 * typical file would be: 459 * <pre> 460 * [libdefaults] 461 * default_realm = EXAMPLE.COM 462 * default_tgs_enctypes = des-cbc-md5 463 * default_tkt_enctypes = des-cbc-md5 464 * [realms] 465 * EXAMPLE.COM = { 466 * kdc = kerberos.example.com 467 * kdc = kerberos-1.example.com 468 * admin_server = kerberos.example.com 469 * } 470 * SAMPLE_COM = { 471 * kdc = orange.sample.com 472 * admin_server = orange.sample.com 473 * } 474 * [domain_realm] 475 * blue.sample.com = TEST.SAMPLE.COM 476 * .backup.com = EXAMPLE.COM 477 * </pre> 478 * @return an ordered list of strings representing the config file after 479 * some initial processing, including:<ol> 480 * <li> Comment lines and empty lines are removed 481 * <li> "{" not at the end of a line is appended to the previous line 482 * <li> The content of a section is also placed between "{" and "}". 483 * <li> Lines are trimmed</ol> 484 * @throws IOException if there is an I/O error 485 * @throws KrbException if there is a file format error 486 */ 487 private List<String> loadConfigFile(final String fileName) 488 throws IOException, KrbException { 489 try { 490 List<String> v = new ArrayList<>(); 491 try (BufferedReader br = new BufferedReader(new InputStreamReader( 492 AccessController.doPrivileged( 493 new PrivilegedExceptionAction<FileInputStream> () { 494 public FileInputStream run() throws IOException { 495 return new FileInputStream(fileName); 496 } 497 })))) { 498 String line; 499 String previous = null; 500 while ((line = br.readLine()) != null) { 501 line = line.trim(); 502 if (line.startsWith("#") || line.isEmpty()) { 503 // ignore comments and blank line 504 // Comments start with #. 505 continue; 506 } 507 // In practice, a subsection might look like: 508 // [realms] 509 // EXAMPLE.COM = 510 // { 511 // kdc = kerberos.example.com 512 // ... 513 // } 514 // Before parsed into stanza table, it needs to be 515 // converted into a canonicalized style (no indent): 516 // realms = { 517 // EXAMPLE.COM = { 518 // kdc = kerberos.example.com 519 // ... 520 // } 521 // } 522 // 523 if (line.startsWith("[")) { 524 if (!line.endsWith("]")) { 525 throw new KrbException("Illegal config content:" 526 + line); 527 } 528 if (previous != null) { 529 v.add(previous); 530 v.add("}"); 531 } 532 String title = line.substring( 533 1, line.length()-1).trim(); 534 if (title.isEmpty()) { 535 throw new KrbException("Illegal config content:" 536 + line); 537 } 538 previous = title + " = {"; 539 } else if (line.startsWith("{")) { 540 if (previous == null) { 541 throw new KrbException( 542 "Config file should not start with \"{\""); 543 } 544 previous += " {"; 545 if (line.length() > 1) { 546 // { and content on the same line 547 v.add(previous); 548 previous = line.substring(1).trim(); 549 } 550 } else { 551 if (previous == null) { 552 throw new KrbException( 553 "Config file must starts with a section"); 554 } 555 v.add(previous); 556 previous = line; 557 } 558 } 559 if (previous != null) { 560 v.add(previous); 561 v.add("}"); 562 } 563 } 564 return v; 565 } catch (java.security.PrivilegedActionException pe) { 566 throw (IOException)pe.getException(); 567 } 568 } 569 570 /** 571 * Parses stanza names and values from configuration file to 572 * stanzaTable (Hashtable). Hashtable key would be stanza names, 573 * (libdefaults, realms, domain_realms, etc), and the hashtable value 574 * would be another hashtable which contains the key-value pairs under 575 * a stanza name. The value of this sub-hashtable can be another hashtable 576 * containing another sub-sub-section or a vector of strings for 577 * final values (even if there is only one value defined). 578 * <p> 579 * For duplicates section names, the latter overwrites the former. For 580 * duplicate value names, the values are in a vector in its appearing order. 581 * </ol> 582 * Please note that this behavior is Java traditional. and it is 583 * not the same as the MIT krb5 behavior, where:<ol> 584 * <li>Duplicated root sections will be merged 585 * <li>For duplicated sub-sections, the former overwrites the latter 586 * <li>Duplicate keys for values are always saved in a vector 587 * </ol> 588 * @param v the strings in the file, never null, might be empty 589 * @throws KrbException if there is a file format error 590 */ 591 @SuppressWarnings("unchecked") 592 private Hashtable<String,Object> parseStanzaTable(List<String> v) 593 throws KrbException { 594 Hashtable<String,Object> current = stanzaTable; 595 for (String line: v) { 596 // There are 3 kinds of lines 597 // 1. a = b 598 // 2. a = { 599 // 3. } 600 if (line.equals("}")) { 601 // Go back to parent, see below 602 current = (Hashtable<String,Object>)current.remove(" PARENT "); 603 if (current == null) { 604 throw new KrbException("Unmatched close brace"); 605 } 606 } else { 607 int pos = line.indexOf('='); 608 if (pos < 0) { 609 throw new KrbException("Illegal config content:" + line); 610 } 611 String key = line.substring(0, pos).trim(); 612 String value = trimmed(line.substring(pos+1)); 613 if (value.equals("{")) { 614 Hashtable<String,Object> subTable; 615 if (current == stanzaTable) { 616 key = key.toLowerCase(Locale.US); 617 } 618 subTable = new Hashtable<>(); 619 current.put(key, subTable); 620 // A special entry for its parent. Put whitespaces around, 621 // so will never be confused with a normal key 622 subTable.put(" PARENT ", current); 623 current = subTable; 624 } else { 625 Vector<String> values; 626 if (current.containsKey(key)) { 627 Object obj = current.get(key); 628 // If a key first shows as a section and then a value, 629 // this is illegal. However, we haven't really forbid 630 // first value then section, which the final result 631 // is a section. 632 if (!(obj instanceof Vector)) { 633 throw new KrbException("Key " + key 634 + "used for both value and section"); 635 } 636 values = (Vector<String>)current.get(key); 637 } else { 638 values = new Vector<String>(); 639 current.put(key, values); 640 } 641 values.add(value); 642 } 643 } 644 } 645 if (current != stanzaTable) { 646 throw new KrbException("Not closed"); 647 } 648 return current; 649 } 650 651 /** 652 * Gets the default Java configuration file name. 653 * 654 * If the system property "java.security.krb5.conf" is defined, we'll 655 * use its value, no matter if the file exists or not. Otherwise, we 656 * will look at $JAVA_HOME/lib/security directory with "krb5.conf" name, 657 * and return it if the file exists. 658 * 659 * The method returns null if it cannot find a Java config file. 660 */ 661 private String getJavaFileName() { 662 String name = getProperty("java.security.krb5.conf"); 663 if (name == null) { 664 name = getProperty("java.home") + File.separator + 665 "lib" + File.separator + "security" + 666 File.separator + "krb5.conf"; 667 if (!fileExists(name)) { 668 name = null; 669 } 670 } 671 if (DEBUG) { 672 System.out.println("Java config name: " + name); 673 } 674 return name; 675 } 676 677 /** 678 * Gets the default native configuration file name. 679 * 680 * Depending on the OS type, the method returns the default native 681 * kerberos config file name, which is at windows directory with 682 * the name of "krb5.ini" for Windows, /etc/krb5/krb5.conf for Solaris, 683 * /etc/krb5.conf otherwise. Mac OSX X has a different file name. 684 * 685 * Note: When the Terminal Service is started in Windows (from 2003), 686 * there are two kinds of Windows directories: A system one (say, 687 * C:\Windows), and a user-private one (say, C:\Users\Me\Windows). 688 * We will first look for krb5.ini in the user-private one. If not 689 * found, try the system one instead. 690 * 691 * This method will always return a non-null non-empty file name, 692 * even if that file does not exist. 693 */ 694 private String getNativeFileName() { 695 String name = null; 696 String osname = getProperty("os.name"); 697 if (osname.startsWith("Windows")) { 698 try { 699 Credentials.ensureLoaded(); 700 } catch (Exception e) { 701 // ignore exceptions 702 } 703 if (Credentials.alreadyLoaded) { 704 String path = getWindowsDirectory(false); 705 if (path != null) { 706 if (path.endsWith("\\")) { 707 path = path + "krb5.ini"; 708 } else { 709 path = path + "\\krb5.ini"; 710 } 711 if (fileExists(path)) { 712 name = path; 713 } 714 } 715 if (name == null) { 716 path = getWindowsDirectory(true); 717 if (path != null) { 718 if (path.endsWith("\\")) { 719 path = path + "krb5.ini"; 720 } else { 721 path = path + "\\krb5.ini"; 722 } 723 name = path; 724 } 725 } 726 } 727 if (name == null) { 728 name = "c:\\winnt\\krb5.ini"; 729 } 730 } else if (osname.startsWith("SunOS")) { 731 name = "/etc/krb5/krb5.conf"; 732 } else if (osname.contains("OS X")) { 733 name = findMacosConfigFile(); 734 } else { 735 name = "/etc/krb5.conf"; 736 } 737 if (DEBUG) { 738 System.out.println("Native config name: " + name); 739 } 740 return name; 741 } 742 743 private static String getProperty(String property) { 744 return java.security.AccessController.doPrivileged( 745 new sun.security.action.GetPropertyAction(property)); 746 } 747 748 private String findMacosConfigFile() { 749 String userHome = getProperty("user.home"); 750 final String PREF_FILE = "/Library/Preferences/edu.mit.Kerberos"; 751 String userPrefs = userHome + PREF_FILE; 752 753 if (fileExists(userPrefs)) { 754 return userPrefs; 755 } 756 757 if (fileExists(PREF_FILE)) { 758 return PREF_FILE; 759 } 760 761 return "/etc/krb5.conf"; 762 } 763 764 private static String trimmed(String s) { 765 s = s.trim(); 766 if (s.isEmpty()) return s; 767 if (s.charAt(0) == '"' && s.charAt(s.length()-1) == '"' || 768 s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\'') { 769 s = s.substring(1, s.length()-1).trim(); 770 } 771 return s; 772 } 773 774 /** 775 * For testing purpose. This method lists all information being parsed from 776 * the configuration file to the hashtable. 777 */ 778 public void listTable() { 779 System.out.println(this); 780 } 781 782 /** 783 * Returns all etypes specified in krb5.conf for the given configName, 784 * or all the builtin defaults. This result is always non-empty. 785 * If no etypes are found, an exception is thrown. 786 */ 787 public int[] defaultEtype(String configName) throws KrbException { 788 String default_enctypes; 789 default_enctypes = get("libdefaults", configName); 790 int[] etype; 791 if (default_enctypes == null) { 792 if (DEBUG) { 793 System.out.println("Using builtin default etypes for " + 794 configName); 795 } 796 etype = EType.getBuiltInDefaults(); 797 } else { 798 String delim = " "; 799 StringTokenizer st; 800 for (int j = 0; j < default_enctypes.length(); j++) { 801 if (default_enctypes.substring(j, j + 1).equals(",")) { 802 // only two delimiters are allowed to use 803 // according to Kerberos DCE doc. 804 delim = ","; 805 break; 806 } 807 } 808 st = new StringTokenizer(default_enctypes, delim); 809 int len = st.countTokens(); 810 ArrayList<Integer> ls = new ArrayList<>(len); 811 int type; 812 for (int i = 0; i < len; i++) { 813 type = Config.getType(st.nextToken()); 814 if (type != -1 && EType.isSupported(type)) { 815 ls.add(type); 816 } 817 } 818 if (ls.isEmpty()) { 819 throw new KrbException("no supported default etypes for " 820 + configName); 821 } else { 822 etype = new int[ls.size()]; 823 for (int i = 0; i < etype.length; i++) { 824 etype[i] = ls.get(i); 825 } 826 } 827 } 828 829 if (DEBUG) { 830 System.out.print("default etypes for " + configName + ":"); 831 for (int i = 0; i < etype.length; i++) { 832 System.out.print(" " + etype[i]); 833 } 834 System.out.println("."); 835 } 836 return etype; 837 } 838 839 840 /** 841 * Get the etype and checksum value for the specified encryption and 842 * checksum type. 843 * 844 */ 845 /* 846 * This method converts the string representation of encryption type and 847 * checksum type to int value that can be later used by EType and 848 * Checksum classes. 849 */ 850 public static int getType(String input) { 851 int result = -1; 852 if (input == null) { 853 return result; 854 } 855 if (input.startsWith("d") || (input.startsWith("D"))) { 856 if (input.equalsIgnoreCase("des-cbc-crc")) { 857 result = EncryptedData.ETYPE_DES_CBC_CRC; 858 } else if (input.equalsIgnoreCase("des-cbc-md5")) { 859 result = EncryptedData.ETYPE_DES_CBC_MD5; 860 } else if (input.equalsIgnoreCase("des-mac")) { 861 result = Checksum.CKSUMTYPE_DES_MAC; 862 } else if (input.equalsIgnoreCase("des-mac-k")) { 863 result = Checksum.CKSUMTYPE_DES_MAC_K; 864 } else if (input.equalsIgnoreCase("des-cbc-md4")) { 865 result = EncryptedData.ETYPE_DES_CBC_MD4; 866 } else if (input.equalsIgnoreCase("des3-cbc-sha1") || 867 input.equalsIgnoreCase("des3-hmac-sha1") || 868 input.equalsIgnoreCase("des3-cbc-sha1-kd") || 869 input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) { 870 result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD; 871 } 872 } else if (input.startsWith("a") || (input.startsWith("A"))) { 873 // AES 874 if (input.equalsIgnoreCase("aes128-cts") || 875 input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) { 876 result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96; 877 } else if (input.equalsIgnoreCase("aes256-cts") || 878 input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) { 879 result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96; 880 // ARCFOUR-HMAC 881 } else if (input.equalsIgnoreCase("arcfour-hmac") || 882 input.equalsIgnoreCase("arcfour-hmac-md5")) { 883 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 884 } 885 // RC4-HMAC 886 } else if (input.equalsIgnoreCase("rc4-hmac")) { 887 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 888 } else if (input.equalsIgnoreCase("CRC32")) { 889 result = Checksum.CKSUMTYPE_CRC32; 890 } else if (input.startsWith("r") || (input.startsWith("R"))) { 891 if (input.equalsIgnoreCase("rsa-md5")) { 892 result = Checksum.CKSUMTYPE_RSA_MD5; 893 } else if (input.equalsIgnoreCase("rsa-md5-des")) { 894 result = Checksum.CKSUMTYPE_RSA_MD5_DES; 895 } 896 } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) { 897 result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD; 898 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) { 899 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128; 900 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) { 901 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256; 902 } else if (input.equalsIgnoreCase("hmac-md5-rc4") || 903 input.equalsIgnoreCase("hmac-md5-arcfour") || 904 input.equalsIgnoreCase("hmac-md5-enc")) { 905 result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR; 906 } else if (input.equalsIgnoreCase("NULL")) { 907 result = EncryptedData.ETYPE_NULL; 908 } 909 910 return result; 911 } 912 913 /** 914 * Resets the default kdc realm. 915 * We do not need to synchronize these methods since assignments are atomic 916 * 917 * This method was useless. Kept here in case some class still calls it. 918 */ 919 public void resetDefaultRealm(String realm) { 920 if (DEBUG) { 921 System.out.println(">>> Config try resetting default kdc " + realm); 922 } 923 } 924 925 /** 926 * Check to use addresses in tickets 927 * use addresses if "no_addresses" or "noaddresses" is set to false 928 */ 929 public boolean useAddresses() { 930 boolean useAddr = false; 931 // use addresses if "no_addresses" is set to false 932 String value = get("libdefaults", "no_addresses"); 933 useAddr = (value != null && value.equalsIgnoreCase("false")); 934 if (useAddr == false) { 935 // use addresses if "noaddresses" is set to false 936 value = get("libdefaults", "noaddresses"); 937 useAddr = (value != null && value.equalsIgnoreCase("false")); 938 } 939 return useAddr; 940 } 941 942 /** 943 * Check if need to use DNS to locate Kerberos services 944 */ 945 private boolean useDNS(String name) { 946 String value = get("libdefaults", name); 947 if (value == null) { 948 value = get("libdefaults", "dns_fallback"); 949 if ("false".equalsIgnoreCase(value)) { 950 return false; 951 } else { 952 return true; 953 } 954 } else { 955 return value.equalsIgnoreCase("true"); 956 } 957 } 958 959 /** 960 * Check if need to use DNS to locate the KDC 961 */ 962 private boolean useDNS_KDC() { 963 return useDNS("dns_lookup_kdc"); 964 } 965 966 /* 967 * Check if need to use DNS to locate the Realm 968 */ 969 private boolean useDNS_Realm() { 970 return useDNS("dns_lookup_realm"); 971 } 972 973 /** 974 * Gets default realm. 975 * @throws KrbException where no realm can be located 976 * @return the default realm, always non null 977 */ 978 public String getDefaultRealm() throws KrbException { 979 if (defaultRealm != null) { 980 return defaultRealm; 981 } 982 Exception cause = null; 983 String realm = get("libdefaults", "default_realm"); 984 if ((realm == null) && useDNS_Realm()) { 985 // use DNS to locate Kerberos realm 986 try { 987 realm = getRealmFromDNS(); 988 } catch (KrbException ke) { 989 cause = ke; 990 } 991 } 992 if (realm == null) { 993 realm = java.security.AccessController.doPrivileged( 994 new java.security.PrivilegedAction<String>() { 995 @Override 996 public String run() { 997 String osname = System.getProperty("os.name"); 998 if (osname.startsWith("Windows")) { 999 return System.getenv("USERDNSDOMAIN"); 1000 } 1001 return null; 1002 } 1003 }); 1004 } 1005 if (realm == null) { 1006 KrbException ke = new KrbException("Cannot locate default realm"); 1007 if (cause != null) { 1008 ke.initCause(cause); 1009 } 1010 throw ke; 1011 } 1012 return realm; 1013 } 1014 1015 /** 1016 * Returns a list of KDC's with each KDC separated by a space 1017 * 1018 * @param realm the realm for which the KDC list is desired 1019 * @throws KrbException if there's no way to find KDC for the realm 1020 * @return the list of KDCs separated by a space, always non null 1021 */ 1022 public String getKDCList(String realm) throws KrbException { 1023 if (realm == null) { 1024 realm = getDefaultRealm(); 1025 } 1026 if (realm.equalsIgnoreCase(defaultRealm)) { 1027 return defaultKDC; 1028 } 1029 Exception cause = null; 1030 String kdcs = getAll("realms", realm, "kdc"); 1031 if ((kdcs == null) && useDNS_KDC()) { 1032 // use DNS to locate KDC 1033 try { 1034 kdcs = getKDCFromDNS(realm); 1035 } catch (KrbException ke) { 1036 cause = ke; 1037 } 1038 } 1039 if (kdcs == null) { 1040 kdcs = java.security.AccessController.doPrivileged( 1041 new java.security.PrivilegedAction<String>() { 1042 @Override 1043 public String run() { 1044 String osname = System.getProperty("os.name"); 1045 if (osname.startsWith("Windows")) { 1046 String logonServer = System.getenv("LOGONSERVER"); 1047 if (logonServer != null 1048 && logonServer.startsWith("\\\\")) { 1049 logonServer = logonServer.substring(2); 1050 } 1051 return logonServer; 1052 } 1053 return null; 1054 } 1055 }); 1056 } 1057 if (kdcs == null) { 1058 if (defaultKDC != null) { 1059 return defaultKDC; 1060 } 1061 KrbException ke = new KrbException("Cannot locate KDC"); 1062 if (cause != null) { 1063 ke.initCause(cause); 1064 } 1065 throw ke; 1066 } 1067 return kdcs; 1068 } 1069 1070 /** 1071 * Locate Kerberos realm using DNS 1072 * 1073 * @return the Kerberos realm 1074 */ 1075 private String getRealmFromDNS() throws KrbException { 1076 // use DNS to locate Kerberos realm 1077 String realm = null; 1078 String hostName = null; 1079 try { 1080 hostName = InetAddress.getLocalHost().getCanonicalHostName(); 1081 } catch (UnknownHostException e) { 1082 KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC, 1083 "Unable to locate Kerberos realm: " + e.getMessage()); 1084 ke.initCause(e); 1085 throw (ke); 1086 } 1087 // get the domain realm mapping from the configuration 1088 String mapRealm = PrincipalName.mapHostToRealm(hostName); 1089 if (mapRealm == null) { 1090 // No match. Try search and/or domain in /etc/resolv.conf 1091 List<String> srchlist = ResolverConfiguration.open().searchlist(); 1092 for (String domain: srchlist) { 1093 realm = checkRealm(domain); 1094 if (realm != null) { 1095 break; 1096 } 1097 } 1098 } else { 1099 realm = checkRealm(mapRealm); 1100 } 1101 if (realm == null) { 1102 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1103 "Unable to locate Kerberos realm"); 1104 } 1105 return realm; 1106 } 1107 1108 /** 1109 * Check if the provided realm is the correct realm 1110 * @return the realm if correct, or null otherwise 1111 */ 1112 private static String checkRealm(String mapRealm) { 1113 if (DEBUG) { 1114 System.out.println("getRealmFromDNS: trying " + mapRealm); 1115 } 1116 String[] records = null; 1117 String newRealm = mapRealm; 1118 while ((records == null) && (newRealm != null)) { 1119 // locate DNS TXT record 1120 records = KrbServiceLocator.getKerberosService(newRealm); 1121 newRealm = Realm.parseRealmComponent(newRealm); 1122 // if no DNS TXT records found, try again using sub-realm 1123 } 1124 if (records != null) { 1125 for (int i = 0; i < records.length; i++) { 1126 if (records[i].equalsIgnoreCase(mapRealm)) { 1127 return records[i]; 1128 } 1129 } 1130 } 1131 return null; 1132 } 1133 1134 /** 1135 * Locate KDC using DNS 1136 * 1137 * @param realm the realm for which the master KDC is desired 1138 * @return the KDC 1139 */ 1140 private String getKDCFromDNS(String realm) throws KrbException { 1141 // use DNS to locate KDC 1142 String kdcs = ""; 1143 String[] srvs = null; 1144 // locate DNS SRV record using UDP 1145 if (DEBUG) { 1146 System.out.println("getKDCFromDNS using UDP"); 1147 } 1148 srvs = KrbServiceLocator.getKerberosService(realm, "_udp"); 1149 if (srvs == null) { 1150 // locate DNS SRV record using TCP 1151 if (DEBUG) { 1152 System.out.println("getKDCFromDNS using TCP"); 1153 } 1154 srvs = KrbServiceLocator.getKerberosService(realm, "_tcp"); 1155 } 1156 if (srvs == null) { 1157 // no DNS SRV records 1158 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1159 "Unable to locate KDC for realm " + realm); 1160 } 1161 if (srvs.length == 0) { 1162 return null; 1163 } 1164 for (int i = 0; i < srvs.length; i++) { 1165 kdcs += srvs[i].trim() + " "; 1166 } 1167 kdcs = kdcs.trim(); 1168 if (kdcs.equals("")) { 1169 return null; 1170 } 1171 return kdcs; 1172 } 1173 1174 private boolean fileExists(String name) { 1175 return java.security.AccessController.doPrivileged( 1176 new FileExistsAction(name)); 1177 } 1178 1179 static class FileExistsAction 1180 implements java.security.PrivilegedAction<Boolean> { 1181 1182 private String fileName; 1183 1184 public FileExistsAction(String fileName) { 1185 this.fileName = fileName; 1186 } 1187 1188 public Boolean run() { 1189 return new File(fileName).exists(); 1190 } 1191 } 1192 1193 // Shows the content of the Config object for debug purpose. 1194 // 1195 // { 1196 // libdefaults = { 1197 // default_realm = R 1198 // } 1199 // realms = { 1200 // R = { 1201 // kdc = [k1,k2] 1202 // } 1203 // } 1204 // } 1205 1206 @Override 1207 public String toString() { 1208 StringBuffer sb = new StringBuffer(); 1209 toStringInternal("", stanzaTable, sb); 1210 return sb.toString(); 1211 } 1212 private static void toStringInternal(String prefix, Object obj, 1213 StringBuffer sb) { 1214 if (obj instanceof String) { 1215 // A string value, just print it 1216 sb.append(obj).append('\n'); 1217 } else if (obj instanceof Hashtable) { 1218 // A table, start a new sub-section... 1219 Hashtable<?, ?> tab = (Hashtable<?, ?>)obj; 1220 sb.append("{\n"); 1221 for (Object o: tab.keySet()) { 1222 // ...indent, print "key = ", and 1223 sb.append(prefix).append(" ").append(o).append(" = "); 1224 // ...go recursively into value 1225 toStringInternal(prefix + " ", tab.get(o), sb); 1226 } 1227 sb.append(prefix).append("}\n"); 1228 } else if (obj instanceof Vector) { 1229 // A vector of strings, print them inside [ and ] 1230 Vector<?> v = (Vector<?>)obj; 1231 sb.append("["); 1232 boolean first = true; 1233 for (Object o: v.toArray()) { 1234 if (!first) sb.append(","); 1235 sb.append(o); 1236 first = false; 1237 } 1238 sb.append("]\n"); 1239 } 1240 } 1241 }