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