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 the default encryption types. 784 * 785 */ 786 public int[] defaultEtype(String enctypes) { 787 String default_enctypes; 788 default_enctypes = get("libdefaults", enctypes); 789 String delim = " "; 790 StringTokenizer st; 791 int[] etype; 792 if (default_enctypes == null) { 793 if (DEBUG) { 794 System.out.println("Using builtin default etypes for " + 795 enctypes); 796 } 797 etype = EType.getBuiltInDefaults(); 798 } else { 799 for (int j = 0; j < default_enctypes.length(); j++) { 800 if (default_enctypes.substring(j, j + 1).equals(",")) { 801 // only two delimiters are allowed to use 802 // according to Kerberos DCE doc. 803 delim = ","; 804 break; 805 } 806 } 807 st = new StringTokenizer(default_enctypes, delim); 808 int len = st.countTokens(); 809 ArrayList<Integer> ls = new ArrayList<>(len); 810 int type; 811 for (int i = 0; i < len; i++) { 812 type = Config.getType(st.nextToken()); 813 if ((type != -1) && 814 (EType.isSupported(type))) { 815 ls.add(type); 816 } 817 } 818 if (ls.isEmpty()) { 819 if (DEBUG) { 820 System.out.println( 821 "no supported default etypes for " + enctypes); 822 } 823 return null; 824 } else { 825 etype = new int[ls.size()]; 826 for (int i = 0; i < etype.length; i++) { 827 etype[i] = ls.get(i); 828 } 829 } 830 } 831 832 if (DEBUG) { 833 System.out.print("default etypes for " + enctypes + ":"); 834 for (int i = 0; i < etype.length; i++) { 835 System.out.print(" " + etype[i]); 836 } 837 System.out.println("."); 838 } 839 return etype; 840 } 841 842 843 /** 844 * Get the etype and checksum value for the specified encryption and 845 * checksum type. 846 * 847 */ 848 /* 849 * This method converts the string representation of encryption type and 850 * checksum type to int value that can be later used by EType and 851 * Checksum classes. 852 */ 853 public static int getType(String input) { 854 int result = -1; 855 if (input == null) { 856 return result; 857 } 858 if (input.startsWith("d") || (input.startsWith("D"))) { 859 if (input.equalsIgnoreCase("des-cbc-crc")) { 860 result = EncryptedData.ETYPE_DES_CBC_CRC; 861 } else if (input.equalsIgnoreCase("des-cbc-md5")) { 862 result = EncryptedData.ETYPE_DES_CBC_MD5; 863 } else if (input.equalsIgnoreCase("des-mac")) { 864 result = Checksum.CKSUMTYPE_DES_MAC; 865 } else if (input.equalsIgnoreCase("des-mac-k")) { 866 result = Checksum.CKSUMTYPE_DES_MAC_K; 867 } else if (input.equalsIgnoreCase("des-cbc-md4")) { 868 result = EncryptedData.ETYPE_DES_CBC_MD4; 869 } else if (input.equalsIgnoreCase("des3-cbc-sha1") || 870 input.equalsIgnoreCase("des3-hmac-sha1") || 871 input.equalsIgnoreCase("des3-cbc-sha1-kd") || 872 input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) { 873 result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD; 874 } 875 } else if (input.startsWith("a") || (input.startsWith("A"))) { 876 // AES 877 if (input.equalsIgnoreCase("aes128-cts") || 878 input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) { 879 result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96; 880 } else if (input.equalsIgnoreCase("aes256-cts") || 881 input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) { 882 result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96; 883 // ARCFOUR-HMAC 884 } else if (input.equalsIgnoreCase("arcfour-hmac") || 885 input.equalsIgnoreCase("arcfour-hmac-md5")) { 886 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 887 } 888 // RC4-HMAC 889 } else if (input.equalsIgnoreCase("rc4-hmac")) { 890 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 891 } else if (input.equalsIgnoreCase("CRC32")) { 892 result = Checksum.CKSUMTYPE_CRC32; 893 } else if (input.startsWith("r") || (input.startsWith("R"))) { 894 if (input.equalsIgnoreCase("rsa-md5")) { 895 result = Checksum.CKSUMTYPE_RSA_MD5; 896 } else if (input.equalsIgnoreCase("rsa-md5-des")) { 897 result = Checksum.CKSUMTYPE_RSA_MD5_DES; 898 } 899 } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) { 900 result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD; 901 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) { 902 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128; 903 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) { 904 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256; 905 } else if (input.equalsIgnoreCase("hmac-md5-rc4") || 906 input.equalsIgnoreCase("hmac-md5-arcfour") || 907 input.equalsIgnoreCase("hmac-md5-enc")) { 908 result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR; 909 } else if (input.equalsIgnoreCase("NULL")) { 910 result = EncryptedData.ETYPE_NULL; 911 } 912 913 return result; 914 } 915 916 /** 917 * Resets the default kdc realm. 918 * We do not need to synchronize these methods since assignments are atomic 919 * 920 * This method was useless. Kept here in case some class still calls it. 921 */ 922 public void resetDefaultRealm(String realm) { 923 if (DEBUG) { 924 System.out.println(">>> Config try resetting default kdc " + realm); 925 } 926 } 927 928 /** 929 * Check to use addresses in tickets 930 * use addresses if "no_addresses" or "noaddresses" is set to false 931 */ 932 public boolean useAddresses() { 933 boolean useAddr = false; 934 // use addresses if "no_addresses" is set to false 935 String value = get("libdefaults", "no_addresses"); 936 useAddr = (value != null && value.equalsIgnoreCase("false")); 937 if (useAddr == false) { 938 // use addresses if "noaddresses" is set to false 939 value = get("libdefaults", "noaddresses"); 940 useAddr = (value != null && value.equalsIgnoreCase("false")); 941 } 942 return useAddr; 943 } 944 945 /** 946 * Check if need to use DNS to locate Kerberos services 947 */ 948 private boolean useDNS(String name) { 949 String value = get("libdefaults", name); 950 if (value == null) { 951 value = get("libdefaults", "dns_fallback"); 952 if ("false".equalsIgnoreCase(value)) { 953 return false; 954 } else { 955 return true; 956 } 957 } else { 958 return value.equalsIgnoreCase("true"); 959 } 960 } 961 962 /** 963 * Check if need to use DNS to locate the KDC 964 */ 965 private boolean useDNS_KDC() { 966 return useDNS("dns_lookup_kdc"); 967 } 968 969 /* 970 * Check if need to use DNS to locate the Realm 971 */ 972 private boolean useDNS_Realm() { 973 return useDNS("dns_lookup_realm"); 974 } 975 976 /** 977 * Gets default realm. 978 * @throws KrbException where no realm can be located 979 * @return the default realm, always non null 980 */ 981 public String getDefaultRealm() throws KrbException { 982 if (defaultRealm != null) { 983 return defaultRealm; 984 } 985 Exception cause = null; 986 String realm = get("libdefaults", "default_realm"); 987 if ((realm == null) && useDNS_Realm()) { 988 // use DNS to locate Kerberos realm 989 try { 990 realm = getRealmFromDNS(); 991 } catch (KrbException ke) { 992 cause = ke; 993 } 994 } 995 if (realm == null) { 996 realm = java.security.AccessController.doPrivileged( 997 new java.security.PrivilegedAction<String>() { 998 @Override 999 public String run() { 1000 String osname = System.getProperty("os.name"); 1001 if (osname.startsWith("Windows")) { 1002 return System.getenv("USERDNSDOMAIN"); 1003 } 1004 return null; 1005 } 1006 }); 1007 } 1008 if (realm == null) { 1009 KrbException ke = new KrbException("Cannot locate default realm"); 1010 if (cause != null) { 1011 ke.initCause(cause); 1012 } 1013 throw ke; 1014 } 1015 return realm; 1016 } 1017 1018 /** 1019 * Returns a list of KDC's with each KDC separated by a space 1020 * 1021 * @param realm the realm for which the KDC list is desired 1022 * @throws KrbException if there's no way to find KDC for the realm 1023 * @return the list of KDCs separated by a space, always non null 1024 */ 1025 public String getKDCList(String realm) throws KrbException { 1026 if (realm == null) { 1027 realm = getDefaultRealm(); 1028 } 1029 if (realm.equalsIgnoreCase(defaultRealm)) { 1030 return defaultKDC; 1031 } 1032 Exception cause = null; 1033 String kdcs = getAll("realms", realm, "kdc"); 1034 if ((kdcs == null) && useDNS_KDC()) { 1035 // use DNS to locate KDC 1036 try { 1037 kdcs = getKDCFromDNS(realm); 1038 } catch (KrbException ke) { 1039 cause = ke; 1040 } 1041 } 1042 if (kdcs == null) { 1043 kdcs = java.security.AccessController.doPrivileged( 1044 new java.security.PrivilegedAction<String>() { 1045 @Override 1046 public String run() { 1047 String osname = System.getProperty("os.name"); 1048 if (osname.startsWith("Windows")) { 1049 String logonServer = System.getenv("LOGONSERVER"); 1050 if (logonServer != null 1051 && logonServer.startsWith("\\\\")) { 1052 logonServer = logonServer.substring(2); 1053 } 1054 return logonServer; 1055 } 1056 return null; 1057 } 1058 }); 1059 } 1060 if (kdcs == null) { 1061 if (defaultKDC != null) { 1062 return defaultKDC; 1063 } 1064 KrbException ke = new KrbException("Cannot locate KDC"); 1065 if (cause != null) { 1066 ke.initCause(cause); 1067 } 1068 throw ke; 1069 } 1070 return kdcs; 1071 } 1072 1073 /** 1074 * Locate Kerberos realm using DNS 1075 * 1076 * @return the Kerberos realm 1077 */ 1078 private String getRealmFromDNS() throws KrbException { 1079 // use DNS to locate Kerberos realm 1080 String realm = null; 1081 String hostName = null; 1082 try { 1083 hostName = InetAddress.getLocalHost().getCanonicalHostName(); 1084 } catch (UnknownHostException e) { 1085 KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC, 1086 "Unable to locate Kerberos realm: " + e.getMessage()); 1087 ke.initCause(e); 1088 throw (ke); 1089 } 1090 // get the domain realm mapping from the configuration 1091 String mapRealm = PrincipalName.mapHostToRealm(hostName); 1092 if (mapRealm == null) { 1093 // No match. Try search and/or domain in /etc/resolv.conf 1094 List<String> srchlist = ResolverConfiguration.open().searchlist(); 1095 for (String domain: srchlist) { 1096 realm = checkRealm(domain); 1097 if (realm != null) { 1098 break; 1099 } 1100 } 1101 } else { 1102 realm = checkRealm(mapRealm); 1103 } 1104 if (realm == null) { 1105 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1106 "Unable to locate Kerberos realm"); 1107 } 1108 return realm; 1109 } 1110 1111 /** 1112 * Check if the provided realm is the correct realm 1113 * @return the realm if correct, or null otherwise 1114 */ 1115 private static String checkRealm(String mapRealm) { 1116 if (DEBUG) { 1117 System.out.println("getRealmFromDNS: trying " + mapRealm); 1118 } 1119 String[] records = null; 1120 String newRealm = mapRealm; 1121 while ((records == null) && (newRealm != null)) { 1122 // locate DNS TXT record 1123 records = KrbServiceLocator.getKerberosService(newRealm); 1124 newRealm = Realm.parseRealmComponent(newRealm); 1125 // if no DNS TXT records found, try again using sub-realm 1126 } 1127 if (records != null) { 1128 for (int i = 0; i < records.length; i++) { 1129 if (records[i].equalsIgnoreCase(mapRealm)) { 1130 return records[i]; 1131 } 1132 } 1133 } 1134 return null; 1135 } 1136 1137 /** 1138 * Locate KDC using DNS 1139 * 1140 * @param realm the realm for which the master KDC is desired 1141 * @return the KDC 1142 */ 1143 private String getKDCFromDNS(String realm) throws KrbException { 1144 // use DNS to locate KDC 1145 String kdcs = ""; 1146 String[] srvs = null; 1147 // locate DNS SRV record using UDP 1148 if (DEBUG) { 1149 System.out.println("getKDCFromDNS using UDP"); 1150 } 1151 srvs = KrbServiceLocator.getKerberosService(realm, "_udp"); 1152 if (srvs == null) { 1153 // locate DNS SRV record using TCP 1154 if (DEBUG) { 1155 System.out.println("getKDCFromDNS using TCP"); 1156 } 1157 srvs = KrbServiceLocator.getKerberosService(realm, "_tcp"); 1158 } 1159 if (srvs == null) { 1160 // no DNS SRV records 1161 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1162 "Unable to locate KDC for realm " + realm); 1163 } 1164 if (srvs.length == 0) { 1165 return null; 1166 } 1167 for (int i = 0; i < srvs.length; i++) { 1168 kdcs += srvs[i].trim() + " "; 1169 } 1170 kdcs = kdcs.trim(); 1171 if (kdcs.equals("")) { 1172 return null; 1173 } 1174 return kdcs; 1175 } 1176 1177 private boolean fileExists(String name) { 1178 return java.security.AccessController.doPrivileged( 1179 new FileExistsAction(name)); 1180 } 1181 1182 static class FileExistsAction 1183 implements java.security.PrivilegedAction<Boolean> { 1184 1185 private String fileName; 1186 1187 public FileExistsAction(String fileName) { 1188 this.fileName = fileName; 1189 } 1190 1191 public Boolean run() { 1192 return new File(fileName).exists(); 1193 } 1194 } 1195 1196 // Shows the content of the Config object for debug purpose. 1197 // 1198 // { 1199 // libdefaults = { 1200 // default_realm = R 1201 // } 1202 // realms = { 1203 // R = { 1204 // kdc = [k1,k2] 1205 // } 1206 // } 1207 // } 1208 1209 @Override 1210 public String toString() { 1211 StringBuffer sb = new StringBuffer(); 1212 toStringInternal("", stanzaTable, sb); 1213 return sb.toString(); 1214 } 1215 private static void toStringInternal(String prefix, Object obj, 1216 StringBuffer sb) { 1217 if (obj instanceof String) { 1218 // A string value, just print it 1219 sb.append(obj).append('\n'); 1220 } else if (obj instanceof Hashtable) { 1221 // A table, start a new sub-section... 1222 Hashtable<?, ?> tab = (Hashtable<?, ?>)obj; 1223 sb.append("{\n"); 1224 for (Object o: tab.keySet()) { 1225 // ...indent, print "key = ", and 1226 sb.append(prefix).append(" ").append(o).append(" = "); 1227 // ...go recursively into value 1228 toStringInternal(prefix + " ", tab.get(o), sb); 1229 } 1230 sb.append(prefix).append("}\n"); 1231 } else if (obj instanceof Vector) { 1232 // A vector of strings, print them inside [ and ] 1233 Vector<?> v = (Vector<?>)obj; 1234 sb.append("["); 1235 boolean first = true; 1236 for (Object o: v.toArray()) { 1237 if (!first) sb.append(","); 1238 sb.append(o); 1239 first = false; 1240 } 1241 sb.append("]\n"); 1242 } 1243 } 1244 }