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