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