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