1 /* 2 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * 28 * (C) Copyright IBM Corp. 1999 All Rights Reserved. 29 * Copyright 1997 The Open Group Research Institute. All rights reserved. 30 */ 31 package sun.security.krb5; 32 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.util.Hashtable; 36 import java.util.Vector; 37 import java.util.ArrayList; 38 import java.io.BufferedReader; 39 import java.io.InputStreamReader; 40 import java.io.IOException; 41 import java.util.Enumeration; 42 import java.util.StringTokenizer; 43 import java.net.InetAddress; 44 import java.net.UnknownHostException; 45 import java.util.List; 46 import sun.net.dns.ResolverConfiguration; 47 import sun.security.krb5.internal.crypto.EType; 48 import sun.security.krb5.internal.ktab.*; 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 infomation. 65 */ 66 private Hashtable<String,Object> stanzaTable; 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. 105 * 106 * @exception KrbException if error occurs when constructing a Config 107 * instance. Possible causes would be either of java.security.krb5.realm or 108 * java.security.krb5.kdc not specified, error reading configuration file. 109 */ 110 111 public static synchronized void refresh() throws KrbException { 112 singleton = new Config(); 113 KdcComm.initStatic(); 114 } 115 116 117 /** 118 * Private constructor - can not be instantiated externally. 119 */ 120 private Config() throws KrbException { 121 /* 122 * If either one system property is specified, we throw exception. 123 */ 124 String tmp = 125 java.security.AccessController.doPrivileged( 126 new sun.security.action.GetPropertyAction 127 ("java.security.krb5.kdc")); 128 if (tmp != null) { 129 // The user can specify a list of kdc hosts separated by ":" 130 defaultKDC = tmp.replace(':', ' '); 131 } else { 132 defaultKDC = null; 133 } 134 defaultRealm = 135 java.security.AccessController.doPrivileged( 136 new sun.security.action.GetPropertyAction 137 ("java.security.krb5.realm")); 138 if ((defaultKDC == null && defaultRealm != null) || 139 (defaultRealm == null && defaultKDC != null)) { 140 throw new KrbException 141 ("System property java.security.krb5.kdc and " + 142 "java.security.krb5.realm both must be set or " + 143 "neither must be set."); 144 } 145 146 // Always read the Kerberos configuration file 147 try { 148 Vector<String> configFile; 149 configFile = loadConfigFile(); 150 stanzaTable = parseStanzaTable(configFile); 151 } catch (IOException ioe) { 152 // No krb5.conf, no problem. We'll use DNS or system property etc. 153 } 154 } 155 156 /** 157 * Gets the default int value for the specified name. 158 * @param name the name. 159 * @return the default Integer, null is returned if no such name and 160 * value are found in configuration file, or error occurs when parsing 161 * string to integer. 162 */ 163 public int getDefaultIntValue(String name) { 164 String result = null; 165 int value = Integer.MIN_VALUE; 166 result = getDefault(name); 167 if (result != null) { 168 try { 169 value = parseIntValue(result); 170 } catch (NumberFormatException e) { 171 if (DEBUG) { 172 System.out.println("Exception in getting value of " + 173 name + " " + 174 e.getMessage()); 175 System.out.println("Setting " + name + 176 " to minimum value"); 177 } 178 value = Integer.MIN_VALUE; 179 } 180 } 181 return value; 182 } 183 184 /** 185 * Gets the default int value for the specified name in the specified 186 * section. <br>This method is quicker by using section name as the 187 * search key. 188 * @param name the name. 189 * @param sectio the name string of the section. 190 * @return the default Integer, null is returned if no such name and 191 * value are found in configuration file, or error occurs when parsing 192 * string to integer. 193 */ 194 public int getDefaultIntValue(String name, String section) { 195 String result = null; 196 int value = Integer.MIN_VALUE; 197 result = getDefault(name, section); 198 if (result != null) { 199 try { 200 value = parseIntValue(result); 201 } catch (NumberFormatException e) { 202 if (DEBUG) { 203 System.out.println("Exception in getting value of " + 204 name +" in section " + 205 section + " " + e.getMessage()); 206 System.out.println("Setting " + name + 207 " to minimum value"); 208 } 209 value = Integer.MIN_VALUE; 210 } 211 } 212 return value; 213 } 214 215 /** 216 * Gets the default string value for the specified name. 217 * @param name the name. 218 * @return the default value, null is returned if it cannot be found. 219 */ 220 public String getDefault(String name) { 221 if (stanzaTable == null) { 222 return null; 223 } else { 224 return getDefault(name, stanzaTable); 225 } 226 } 227 228 /** 229 * This method does the real job to recursively search through the 230 * stanzaTable. 231 * @param k the key string. 232 * @param t stanzaTable or sub hashtable within it. 233 * @return the value found in config file, returns null if no value 234 * matched with the key is found. 235 */ 236 private String getDefault(String k, Hashtable t) { 237 String result = null; 238 String key; 239 if (stanzaTable != null) { 240 for (Enumeration e = t.keys(); e.hasMoreElements(); ) { 241 key = (String)e.nextElement(); 242 Object ob = t.get(key); 243 if (ob instanceof Hashtable) { 244 result = getDefault(k, (Hashtable)ob); 245 if (result != null) { 246 return result; 247 } 248 } else if (key.equalsIgnoreCase(k)) { 249 if (ob instanceof String) { 250 return (String)(t.get(key)); 251 } else if (ob instanceof Vector) { 252 result = ""; 253 int length = ((Vector)ob).size(); 254 for (int i = 0; i < length; i++) { 255 if (i == length -1) { 256 result += 257 (String)(((Vector)ob).elementAt(i)); 258 } else { 259 result += 260 (String)(((Vector)ob).elementAt(i)) + " "; 261 } 262 } 263 return result; 264 } 265 } 266 } 267 } 268 return result; 269 } 270 271 /** 272 * Gets the default string value for the specified name in the 273 * specified section. 274 * <br>This method is quicker by using the section name as the search key. 275 * @param name the name. 276 * @param section the name of the section. 277 * @return the default value, null is returned if it cannot be found. 278 */ 279 public String getDefault(String name, String section) { 280 String stanzaName; 281 String result = null; 282 Hashtable subTable; 283 284 if (stanzaTable != null) { 285 for (Enumeration e = stanzaTable.keys(); e.hasMoreElements(); ) { 286 stanzaName = (String)e.nextElement(); 287 subTable = (Hashtable)stanzaTable.get(stanzaName); 288 if (stanzaName.equalsIgnoreCase(section)) { 289 if (subTable.containsKey(name)) { 290 return (String)(subTable.get(name)); 291 } 292 } else if (subTable.containsKey(section)) { 293 Object ob = subTable.get(section); 294 if (ob instanceof Hashtable) { 295 Hashtable temp = (Hashtable)ob; 296 if (temp.containsKey(name)) { 297 Object object = temp.get(name); 298 if (object instanceof Vector) { 299 result = ""; 300 int length = ((Vector)object).size(); 301 for (int i = 0; i < length; i++) { 302 if (i == length - 1) { 303 result += 304 (String)(((Vector)object).elementAt(i)); 305 } else { 306 result += 307 (String)(((Vector)object).elementAt(i)) 308 + " "; 309 } 310 } 311 } else { 312 result = (String)object; 313 } 314 } 315 } 316 } 317 } 318 } 319 return result; 320 } 321 322 /** 323 * Gets the default boolean value for the specified name. 324 * @param name the name. 325 * @return the default boolean value, false is returned if it cannot be 326 * found. 327 */ 328 public boolean getDefaultBooleanValue(String name) { 329 String val = null; 330 if (stanzaTable == null) { 331 val = null; 332 } else { 333 val = getDefault(name, stanzaTable); 334 } 335 if (val != null && val.equalsIgnoreCase("true")) { 336 return true; 337 } else { 338 return false; 339 } 340 } 341 342 /** 343 * Gets the default boolean value for the specified name in the 344 * specified section. 345 * <br>This method is quicker by using the section name as the search key. 346 * @param name the name. 347 * @param section the name of the section. 348 * @return the default boolean value, false is returned if it cannot be 349 * found. 350 */ 351 public boolean getDefaultBooleanValue(String name, String section) { 352 String val = getDefault(name, section); 353 if (val != null && val.equalsIgnoreCase("true")) { 354 return true; 355 } else { 356 return false; 357 } 358 } 359 360 /** 361 * Parses a string to an integer. The convertible strings include the 362 * string representations of positive integers, negative integers, and 363 * hex decimal integers. Valid inputs are, e.g., -1234, +1234, 364 * 0x40000. 365 * 366 * @param input the String to be converted to an Integer. 367 * @return an numeric value represented by the string 368 * @exception NumberFormationException if the String does not contain a 369 * parsable integer. 370 */ 371 private int parseIntValue(String input) throws NumberFormatException { 372 int value = 0; 373 if (input.startsWith("+")) { 374 String temp = input.substring(1); 375 return Integer.parseInt(temp); 376 } else if (input.startsWith("0x")) { 377 String temp = input.substring(2); 378 char[] chars = temp.toCharArray(); 379 if (chars.length > 8) { 380 throw new NumberFormatException(); 381 } else { 382 for (int i = 0; i < chars.length; i++) { 383 int index = chars.length - i - 1; 384 switch (chars[i]) { 385 case '0': 386 value += 0; 387 break; 388 case '1': 389 value += 1 * getBase(index); 390 break; 391 case '2': 392 value += 2 * getBase(index); 393 break; 394 case '3': 395 value += 3 * getBase(index); 396 break; 397 case '4': 398 value += 4 * getBase(index); 399 break; 400 case '5': 401 value += 5 * getBase(index); 402 break; 403 case '6': 404 value += 6 * getBase(index); 405 break; 406 case '7': 407 value += 7 * getBase(index); 408 break; 409 case '8': 410 value += 8 * getBase(index); 411 break; 412 case '9': 413 value += 9 * getBase(index); 414 break; 415 case 'a': 416 case 'A': 417 value += 10 * getBase(index); 418 break; 419 case 'b': 420 case 'B': 421 value += 11 * getBase(index); 422 break; 423 case 'c': 424 case 'C': 425 value += 12 * getBase(index); 426 break; 427 case 'd': 428 case 'D': 429 value += 13 * getBase(index); 430 break; 431 case 'e': 432 case 'E': 433 value += 14 * getBase(index); 434 break; 435 case 'f': 436 case 'F': 437 value += 15 * getBase(index); 438 break; 439 default: 440 throw new NumberFormatException("Invalid numerical format"); 441 } 442 } 443 } 444 if (value < 0) { 445 throw new NumberFormatException("Data overflow."); 446 } 447 } else { 448 value = Integer.parseInt(input); 449 } 450 return value; 451 } 452 453 private int getBase(int i) { 454 int result = 16; 455 switch (i) { 456 case 0: 457 result = BASE16_0; 458 break; 459 case 1: 460 result = BASE16_1; 461 break; 462 case 2: 463 result = BASE16_2; 464 break; 465 case 3: 466 result = BASE16_3; 467 break; 468 default: 469 for (int j = 1; j < i; j++) { 470 result *= 16; 471 } 472 } 473 return result; 474 } 475 476 /** 477 * Finds the matching value in the hashtable. 478 */ 479 private String find(String key1, String key2) { 480 String result; 481 if ((stanzaTable != null) && 482 ((result = (String) 483 (((Hashtable)(stanzaTable.get(key1))).get(key2))) != null)) { 484 return result; 485 } else { 486 return ""; 487 } 488 } 489 490 /** 491 * Reads name/value pairs to the memory from the configuration 492 * file. The default location of the configuration file is in java home 493 * directory. 494 * 495 * Configuration file contains information about the default realm, 496 * ticket parameters, location of the KDC and the admin server for 497 * known realms, etc. The file is divided into sections. Each section 498 * contains one or more name/value pairs with one pair per line. A 499 * typical file would be: 500 * [libdefaults] 501 * default_realm = EXAMPLE.COM 502 * default_tgs_enctypes = des-cbc-md5 503 * default_tkt_enctypes = des-cbc-md5 504 * [realms] 505 * EXAMPLE.COM = { 506 * kdc = kerberos.example.com 507 * kdc = kerberos-1.example.com 508 * admin_server = kerberos.example.com 509 * } 510 * SAMPLE_COM = { 511 * kdc = orange.sample.com 512 * admin_server = orange.sample.com 513 * } 514 * [domain_realm] 515 * blue.sample.com = TEST.SAMPLE.COM 516 * .backup.com = EXAMPLE.COM 517 */ 518 private Vector<String> loadConfigFile() throws IOException { 519 try { 520 final String fileName = getFileName(); 521 if (!fileName.equals("")) { 522 BufferedReader br = new BufferedReader(new InputStreamReader( 523 java.security.AccessController.doPrivileged( 524 new java.security.PrivilegedExceptionAction<FileInputStream> () { 525 public FileInputStream run() throws IOException { 526 return new FileInputStream(fileName); 527 } 528 }))); 529 String Line; 530 Vector<String> v = new Vector<>(); 531 String previous = null; 532 while ((Line = br.readLine()) != null) { 533 // ignore comments and blank line in the configuration file. 534 // Comments start with #. 535 if (!(Line.startsWith("#") || Line.trim().isEmpty())) { 536 String current = Line.trim(); 537 // In practice, a subsection might look like: 538 // EXAMPLE.COM = 539 // { 540 // kdc = kerberos.example.com 541 // ... 542 // } 543 // Before parsed into stanza table, it needs to be 544 // converted into formal style: 545 // EXAMPLE.COM = { 546 // kdc = kerberos.example.com 547 // ... 548 // } 549 // 550 // So, if a line is "{", adhere to the previous line. 551 if (current.equals("{")) { 552 if (previous == null) { 553 throw new IOException( 554 "Config file should not start with \"{\""); 555 } 556 previous += " " + current; 557 } else { 558 if (previous != null) { 559 v.addElement(previous); 560 } 561 previous = current; 562 } 563 } 564 } 565 if (previous != null) { 566 v.addElement(previous); 567 } 568 569 br.close(); 570 return v; 571 } 572 return null; 573 } catch (java.security.PrivilegedActionException pe) { 574 throw (IOException)pe.getException(); 575 } 576 } 577 578 579 /** 580 * Parses stanza names and values from configuration file to 581 * stanzaTable (Hashtable). Hashtable key would be stanza names, 582 * (libdefaults, realms, domain_realms, etc), and the hashtable value 583 * would be another hashtable which contains the key-value pairs under 584 * a stanza name. 585 */ 586 private Hashtable<String,Object> parseStanzaTable(Vector<String> v) throws KrbException { 587 if (v == null) { 588 throw new KrbException("I/O error while reading" + 589 " configuration file."); 590 } 591 Hashtable<String,Object> table = new Hashtable<>(); 592 for (int i = 0; i < v.size(); i++) { 593 String line = v.elementAt(i).trim(); 594 if (line.equalsIgnoreCase("[realms]")) { 595 for (int count = i + 1; count < v.size() + 1; count++) { 596 // find the next stanza name 597 if ((count == v.size()) || 598 (v.elementAt(count).startsWith("["))) { 599 Hashtable<String,Hashtable<String,Vector<String>>> temp = 600 new Hashtable<>(); 601 temp = parseRealmField(v, i + 1, count); 602 table.put("realms", temp); 603 i = count - 1; 604 break; 605 } 606 } 607 } else if (line.equalsIgnoreCase("[capaths]")) { 608 for (int count = i + 1; count < v.size() + 1; count++) { 609 // find the next stanza name 610 if ((count == v.size()) || 611 (v.elementAt(count).startsWith("["))) { 612 Hashtable<String,Hashtable<String,Vector<String>>> temp = 613 new Hashtable<>(); 614 temp = parseRealmField(v, i + 1, count); 615 table.put("capaths", temp); 616 i = count - 1; 617 break; 618 } 619 } 620 } else if (line.startsWith("[") && line.endsWith("]")) { 621 String key = line.substring(1, line.length() - 1); 622 for (int count = i + 1; count < v.size() + 1; count++) { 623 // find the next stanza name 624 if ((count == v.size()) || 625 (v.elementAt(count).startsWith("["))) { 626 Hashtable<String,String> temp = 627 parseField(v, i + 1, count); 628 table.put(key, temp); 629 i = count - 1; 630 break; 631 } 632 } 633 } 634 } 635 return table; 636 } 637 638 /** 639 * Gets the default configuration file name. This method will never 640 * return null. 641 * 642 * If the system property "java.security.krb5.conf" is defined, we'll 643 * use its value, no matter if the file exists or not. Otherwise, 644 * the file will be searched in a list of possible loations in the 645 * following order: 646 * 647 * 1. at Java home lib\security directory with "krb5.conf" name, 648 * 2. at windows directory with the name of "krb5.ini" for Windows, 649 * /etc/krb5/krb5.conf for Solaris, /etc/krb5.conf otherwise. 650 * 651 * Note: When the Terminal Service is started in Windows (from 2003), 652 * there are two kinds of Windows directories: A system one (say, 653 * C:\Windows), and a user-private one (say, C:\Users\Me\Windows). 654 * We will first look for krb5.ini in the user-private one. If not 655 * found, try the system one instead. 656 */ 657 private String getFileName() { 658 String name = 659 java.security.AccessController.doPrivileged( 660 new sun.security.action. 661 GetPropertyAction("java.security.krb5.conf")); 662 if (name == null) { 663 name = java.security.AccessController.doPrivileged( 664 new sun.security.action. 665 GetPropertyAction("java.home")) + File.separator + 666 "lib" + File.separator + "security" + 667 File.separator + "krb5.conf"; 668 if (!fileExists(name)) { 669 name = null; 670 String osname = 671 java.security.AccessController.doPrivileged( 672 new sun.security.action.GetPropertyAction("os.name")); 673 if (osname.startsWith("Windows")) { 674 try { 675 Credentials.ensureLoaded(); 676 } catch (Exception e) { 677 // ignore exceptions 678 } 679 if (Credentials.alreadyLoaded) { 680 String path = getWindowsDirectory(false); 681 if (path != null) { 682 if (path.endsWith("\\")) { 683 path = path + "krb5.ini"; 684 } else { 685 path = path + "\\krb5.ini"; 686 } 687 if (fileExists(path)) { 688 name = path; 689 } 690 } 691 if (name == null) { 692 path = getWindowsDirectory(true); 693 if (path != null) { 694 if (path.endsWith("\\")) { 695 path = path + "krb5.ini"; 696 } else { 697 path = path + "\\krb5.ini"; 698 } 699 name = path; 700 } 701 } 702 } 703 if (name == null) { 704 name = "c:\\winnt\\krb5.ini"; 705 } 706 } else if (osname.startsWith("SunOS")) { 707 name = "/etc/krb5/krb5.conf"; 708 } else { 709 name = "/etc/krb5.conf"; 710 } 711 } 712 } 713 if (DEBUG) { 714 System.out.println("Config name: " + name); 715 } 716 return name; 717 } 718 719 private static String trimmed(String s) { 720 s = s.trim(); 721 if (s.charAt(0) == '"' && s.charAt(s.length()-1) == '"' || 722 s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\'') { 723 s = s.substring(1, s.length()-1).trim(); 724 } 725 return s; 726 } 727 /** 728 * Parses key-value pairs under a stanza name. 729 */ 730 private Hashtable<String,String> parseField(Vector<String> v, int start, int end) { 731 Hashtable<String,String> table = new Hashtable<>(); 732 String line; 733 for (int i = start; i < end; i++) { 734 line = v.elementAt(i); 735 for (int j = 0; j < line.length(); j++) { 736 if (line.charAt(j) == '=') { 737 String key = (line.substring(0, j)).trim(); 738 String value = trimmed(line.substring(j + 1)); 739 table.put(key, value); 740 break; 741 } 742 } 743 } 744 return table; 745 } 746 747 /** 748 * Parses key-value pairs under [realms]. The key would be the realm 749 * name, the value would be another hashtable which contains 750 * information for the realm given within a pair of braces. 751 */ 752 private Hashtable<String,Hashtable<String,Vector<String>>> parseRealmField(Vector<String> v, int start, int end) { 753 Hashtable<String,Hashtable<String,Vector<String>>> table = new Hashtable<>(); 754 String line; 755 for (int i = start; i < end; i++) { 756 line = v.elementAt(i).trim(); 757 if (line.endsWith("{")) { 758 String key = ""; 759 for (int j = 0; j < line.length(); j++) { 760 if (line.charAt(j) == '=') { 761 key = line.substring(0, j).trim(); 762 // get the key 763 break; 764 } 765 } 766 for (int k = i + 1; k < end; k++) { 767 boolean found = false; 768 line = v.elementAt(k).trim(); 769 for (int l = 0; l < line.length(); l++) { 770 if (line.charAt(l) == '}') { 771 found = true; 772 break; 773 } 774 } 775 if (found == true) { 776 Hashtable<String,Vector<String>> temp = parseRealmFieldEx(v, i + 1, k); 777 table.put(key, temp); 778 i = k; 779 found = false; 780 break; 781 } 782 783 } 784 } 785 } 786 return table; 787 } 788 789 /** 790 * Parses key-value pairs within each braces under [realms]. 791 */ 792 private Hashtable<String,Vector<String>> parseRealmFieldEx(Vector<String> v, int start, int end) { 793 Hashtable<String,Vector<String>> table = new Hashtable<>(); 794 Vector<String> keyVector = new Vector<>(); 795 Vector<String> nameVector = new Vector<>(); 796 String line = ""; 797 String key; 798 for (int i = start; i < end; i++) { 799 line = v.elementAt(i); 800 for (int j = 0; j < line.length(); j++) { 801 if (line.charAt(j) == '=') { 802 int index; 803 key = line.substring(0, j).trim(); 804 if (! exists(key, keyVector)) { 805 keyVector.addElement(key); 806 nameVector = new Vector<String> (); 807 } else { 808 nameVector = table.get(key); 809 } 810 nameVector.addElement(trimmed(line.substring(j + 1))); 811 table.put(key, nameVector); 812 break; 813 } 814 } 815 } 816 return table; 817 } 818 819 /** 820 * Compares the key with the known keys to see if it exists. 821 */ 822 private boolean exists(String key, Vector v) { 823 boolean exists = false; 824 for (int i = 0; i < v.size(); i++) { 825 if (((String)(v.elementAt(i))).equals(key)) { 826 exists = true; 827 } 828 } 829 return exists; 830 } 831 832 /** 833 * For testing purpose. This method lists all information being parsed from 834 * the configuration file to the hashtable. 835 */ 836 public void listTable() { 837 listTable(stanzaTable); 838 } 839 840 private void listTable(Hashtable table) { 841 Vector v = new Vector(); 842 String key; 843 if (stanzaTable != null) { 844 for (Enumeration e = table.keys(); e.hasMoreElements(); ) { 845 key = (String)e.nextElement(); 846 Object object = table.get(key); 847 if (table == stanzaTable) { 848 System.out.println("[" + key + "]"); 849 } 850 if (object instanceof Hashtable) { 851 if (table != stanzaTable) 852 System.out.println("\t" + key + " = {"); 853 listTable((Hashtable)object); 854 if (table != stanzaTable) 855 System.out.println("\t}"); 856 857 } else if (object instanceof String) { 858 System.out.println("\t" + key + " = " + 859 (String)table.get(key)); 860 } else if (object instanceof Vector) { 861 v = (Vector)object; 862 for (int i = 0; i < v.size(); i++) { 863 System.out.println("\t" + key + " = " + 864 (String)v.elementAt(i)); 865 } 866 } 867 } 868 } else { 869 System.out.println("Configuration file not found."); 870 } 871 } 872 873 /** 874 * Returns the default encryption types. 875 * 876 */ 877 public int[] defaultEtype(String enctypes) { 878 String default_enctypes; 879 default_enctypes = getDefault(enctypes, "libdefaults"); 880 String delim = " "; 881 StringTokenizer st; 882 int[] etype; 883 if (default_enctypes == null) { 884 if (DEBUG) { 885 System.out.println("Using builtin default etypes for " + 886 enctypes); 887 } 888 etype = EType.getBuiltInDefaults(); 889 } else { 890 for (int j = 0; j < default_enctypes.length(); j++) { 891 if (default_enctypes.substring(j, j + 1).equals(",")) { 892 // only two delimiters are allowed to use 893 // according to Kerberos DCE doc. 894 delim = ","; 895 break; 896 } 897 } 898 st = new StringTokenizer(default_enctypes, delim); 899 int len = st.countTokens(); 900 ArrayList<Integer> ls = new ArrayList<>(len); 901 int type; 902 for (int i = 0; i < len; i++) { 903 type = getType(st.nextToken()); 904 if ((type != -1) && 905 (EType.isSupported(type))) { 906 ls.add(type); 907 } 908 } 909 if (ls.size() == 0) { 910 if (DEBUG) { 911 System.out.println( 912 "no supported default etypes for " + enctypes); 913 } 914 return null; 915 } else { 916 etype = new int[ls.size()]; 917 for (int i = 0; i < etype.length; i++) { 918 etype[i] = ls.get(i); 919 } 920 } 921 } 922 923 if (DEBUG) { 924 System.out.print("default etypes for " + enctypes + ":"); 925 for (int i = 0; i < etype.length; i++) { 926 System.out.print(" " + etype[i]); 927 } 928 System.out.println("."); 929 } 930 return etype; 931 } 932 933 934 /** 935 * Get the etype and checksum value for the specified encryption and 936 * checksum type. 937 * 938 */ 939 /* 940 * This method converts the string representation of encryption type and 941 * checksum type to int value that can be later used by EType and 942 * Checksum classes. 943 */ 944 public int getType(String input) { 945 int result = -1; 946 if (input == null) { 947 return result; 948 } 949 if (input.startsWith("d") || (input.startsWith("D"))) { 950 if (input.equalsIgnoreCase("des-cbc-crc")) { 951 result = EncryptedData.ETYPE_DES_CBC_CRC; 952 } else if (input.equalsIgnoreCase("des-cbc-md5")) { 953 result = EncryptedData.ETYPE_DES_CBC_MD5; 954 } else if (input.equalsIgnoreCase("des-mac")) { 955 result = Checksum.CKSUMTYPE_DES_MAC; 956 } else if (input.equalsIgnoreCase("des-mac-k")) { 957 result = Checksum.CKSUMTYPE_DES_MAC_K; 958 } else if (input.equalsIgnoreCase("des-cbc-md4")) { 959 result = EncryptedData.ETYPE_DES_CBC_MD4; 960 } else if (input.equalsIgnoreCase("des3-cbc-sha1") || 961 input.equalsIgnoreCase("des3-hmac-sha1") || 962 input.equalsIgnoreCase("des3-cbc-sha1-kd") || 963 input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) { 964 result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD; 965 } 966 } else if (input.startsWith("a") || (input.startsWith("A"))) { 967 // AES 968 if (input.equalsIgnoreCase("aes128-cts") || 969 input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) { 970 result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96; 971 } else if (input.equalsIgnoreCase("aes256-cts") || 972 input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) { 973 result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96; 974 // ARCFOUR-HMAC 975 } else if (input.equalsIgnoreCase("arcfour-hmac") || 976 input.equalsIgnoreCase("arcfour-hmac-md5")) { 977 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 978 } 979 // RC4-HMAC 980 } else if (input.equalsIgnoreCase("rc4-hmac")) { 981 result = EncryptedData.ETYPE_ARCFOUR_HMAC; 982 } else if (input.equalsIgnoreCase("CRC32")) { 983 result = Checksum.CKSUMTYPE_CRC32; 984 } else if (input.startsWith("r") || (input.startsWith("R"))) { 985 if (input.equalsIgnoreCase("rsa-md5")) { 986 result = Checksum.CKSUMTYPE_RSA_MD5; 987 } else if (input.equalsIgnoreCase("rsa-md5-des")) { 988 result = Checksum.CKSUMTYPE_RSA_MD5_DES; 989 } 990 } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) { 991 result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD; 992 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) { 993 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128; 994 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) { 995 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256; 996 } else if (input.equalsIgnoreCase("hmac-md5-rc4") || 997 input.equalsIgnoreCase("hmac-md5-arcfour") || 998 input.equalsIgnoreCase("hmac-md5-enc")) { 999 result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR; 1000 } else if (input.equalsIgnoreCase("NULL")) { 1001 result = EncryptedData.ETYPE_NULL; 1002 } 1003 1004 return result; 1005 } 1006 1007 /** 1008 * Resets the default kdc realm. 1009 * We do not need to synchronize these methods since assignments are atomic 1010 * 1011 * This method was useless. Kept here in case some class still calls it. 1012 */ 1013 public void resetDefaultRealm(String realm) { 1014 if (DEBUG) { 1015 System.out.println(">>> Config try resetting default kdc " + realm); 1016 } 1017 } 1018 1019 /** 1020 * Check to use addresses in tickets 1021 * use addresses if "no_addresses" or "noaddresses" is set to false 1022 */ 1023 public boolean useAddresses() { 1024 boolean useAddr = false; 1025 // use addresses if "no_addresses" is set to false 1026 String value = getDefault("no_addresses", "libdefaults"); 1027 useAddr = (value != null && value.equalsIgnoreCase("false")); 1028 if (useAddr == false) { 1029 // use addresses if "noaddresses" is set to false 1030 value = getDefault("noaddresses", "libdefaults"); 1031 useAddr = (value != null && value.equalsIgnoreCase("false")); 1032 } 1033 return useAddr; 1034 } 1035 1036 /** 1037 * Check if need to use DNS to locate Kerberos services 1038 */ 1039 public boolean useDNS(String name) { 1040 String value = getDefault(name, "libdefaults"); 1041 if (value == null) { 1042 value = getDefault("dns_fallback", "libdefaults"); 1043 if ("false".equalsIgnoreCase(value)) { 1044 return false; 1045 } else { 1046 return true; 1047 } 1048 } else { 1049 return value.equalsIgnoreCase("true"); 1050 } 1051 } 1052 1053 /** 1054 * Check if need to use DNS to locate the KDC 1055 */ 1056 public boolean useDNS_KDC() { 1057 return useDNS("dns_lookup_kdc"); 1058 } 1059 1060 /* 1061 * Check if need to use DNS to locate the Realm 1062 */ 1063 public boolean useDNS_Realm() { 1064 return useDNS("dns_lookup_realm"); 1065 } 1066 1067 /** 1068 * Gets default realm. 1069 * @throws KrbException where no realm can be located 1070 * @return the default realm, always non null 1071 */ 1072 public String getDefaultRealm() throws KrbException { 1073 if (defaultRealm != null) { 1074 return defaultRealm; 1075 } 1076 Exception cause = null; 1077 String realm = getDefault("default_realm", "libdefaults"); 1078 if ((realm == null) && useDNS_Realm()) { 1079 // use DNS to locate Kerberos realm 1080 try { 1081 realm = getRealmFromDNS(); 1082 } catch (KrbException ke) { 1083 cause = ke; 1084 } 1085 } 1086 if (realm == null) { 1087 realm = java.security.AccessController.doPrivileged( 1088 new java.security.PrivilegedAction<String>() { 1089 @Override 1090 public String run() { 1091 String osname = System.getProperty("os.name"); 1092 if (osname.startsWith("Windows")) { 1093 return System.getenv("USERDNSDOMAIN"); 1094 } 1095 return null; 1096 } 1097 }); 1098 } 1099 if (realm == null) { 1100 KrbException ke = new KrbException("Cannot locate default realm"); 1101 if (cause != null) { 1102 ke.initCause(cause); 1103 } 1104 throw ke; 1105 } 1106 return realm; 1107 } 1108 1109 /** 1110 * Returns a list of KDC's with each KDC separated by a space 1111 * 1112 * @param realm the realm for which the KDC list is desired 1113 * @throws KrbException if there's no way to find KDC for the realm 1114 * @return the list of KDCs separated by a space, always non null 1115 */ 1116 public String getKDCList(String realm) throws KrbException { 1117 if (realm == null) { 1118 realm = getDefaultRealm(); 1119 } 1120 if (realm.equalsIgnoreCase(defaultRealm)) { 1121 return defaultKDC; 1122 } 1123 Exception cause = null; 1124 String kdcs = getDefault("kdc", realm); 1125 if ((kdcs == null) && useDNS_KDC()) { 1126 // use DNS to locate KDC 1127 try { 1128 kdcs = getKDCFromDNS(realm); 1129 } catch (KrbException ke) { 1130 cause = ke; 1131 } 1132 } 1133 if (kdcs == null) { 1134 kdcs = java.security.AccessController.doPrivileged( 1135 new java.security.PrivilegedAction<String>() { 1136 @Override 1137 public String run() { 1138 String osname = System.getProperty("os.name"); 1139 if (osname.startsWith("Windows")) { 1140 String logonServer = System.getenv("LOGONSERVER"); 1141 if (logonServer != null 1142 && logonServer.startsWith("\\\\")) { 1143 logonServer = logonServer.substring(2); 1144 } 1145 return logonServer; 1146 } 1147 return null; 1148 } 1149 }); 1150 } 1151 if (kdcs == null) { 1152 if (defaultKDC != null) { 1153 return defaultKDC; 1154 } 1155 KrbException ke = new KrbException("Cannot locate KDC"); 1156 if (cause != null) { 1157 ke.initCause(cause); 1158 } 1159 throw ke; 1160 } 1161 return kdcs; 1162 } 1163 1164 /** 1165 * Locate Kerberos realm using DNS 1166 * 1167 * @return the Kerberos realm 1168 */ 1169 private String getRealmFromDNS() throws KrbException { 1170 // use DNS to locate Kerberos realm 1171 String realm = null; 1172 String hostName = null; 1173 try { 1174 hostName = InetAddress.getLocalHost().getCanonicalHostName(); 1175 } catch (UnknownHostException e) { 1176 KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC, 1177 "Unable to locate Kerberos realm: " + e.getMessage()); 1178 ke.initCause(e); 1179 throw (ke); 1180 } 1181 // get the domain realm mapping from the configuration 1182 String mapRealm = PrincipalName.mapHostToRealm(hostName); 1183 if (mapRealm == null) { 1184 // No match. Try search and/or domain in /etc/resolv.conf 1185 List<String> srchlist = ResolverConfiguration.open().searchlist(); 1186 for (String domain: srchlist) { 1187 realm = checkRealm(domain); 1188 if (realm != null) { 1189 break; 1190 } 1191 } 1192 } else { 1193 realm = checkRealm(mapRealm); 1194 } 1195 if (realm == null) { 1196 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1197 "Unable to locate Kerberos realm"); 1198 } 1199 return realm; 1200 } 1201 1202 /** 1203 * Check if the provided realm is the correct realm 1204 * @return the realm if correct, or null otherwise 1205 */ 1206 private static String checkRealm(String mapRealm) { 1207 if (DEBUG) { 1208 System.out.println("getRealmFromDNS: trying " + mapRealm); 1209 } 1210 String[] records = null; 1211 String newRealm = mapRealm; 1212 while ((records == null) && (newRealm != null)) { 1213 // locate DNS TXT record 1214 records = KrbServiceLocator.getKerberosService(newRealm); 1215 newRealm = Realm.parseRealmComponent(newRealm); 1216 // if no DNS TXT records found, try again using sub-realm 1217 } 1218 if (records != null) { 1219 for (int i = 0; i < records.length; i++) { 1220 if (records[i].equalsIgnoreCase(mapRealm)) { 1221 return records[i]; 1222 } 1223 } 1224 } 1225 return null; 1226 } 1227 1228 /** 1229 * Locate KDC using DNS 1230 * 1231 * @param realm the realm for which the master KDC is desired 1232 * @return the KDC 1233 */ 1234 private String getKDCFromDNS(String realm) throws KrbException { 1235 // use DNS to locate KDC 1236 String kdcs = null; 1237 String[] srvs = null; 1238 // locate DNS SRV record using UDP 1239 if (DEBUG) { 1240 System.out.println("getKDCFromDNS using UDP"); 1241 } 1242 srvs = KrbServiceLocator.getKerberosService(realm, "_udp"); 1243 if (srvs == null) { 1244 // locate DNS SRV record using TCP 1245 if (DEBUG) { 1246 System.out.println("getKDCFromDNS using UDP"); 1247 } 1248 srvs = KrbServiceLocator.getKerberosService(realm, "_tcp"); 1249 } 1250 if (srvs == null) { 1251 // no DNS SRV records 1252 throw new KrbException(Krb5.KRB_ERR_GENERIC, 1253 "Unable to locate KDC for realm " + realm); 1254 } 1255 for (int i = 0; i < srvs.length; i++) { 1256 String value = srvs[i]; 1257 for (int j = 0; j < srvs[i].length(); j++) { 1258 // filter the KDC name 1259 if (value.charAt(j) == ':') { 1260 kdcs = (value.substring(0, j)).trim(); 1261 } 1262 } 1263 } 1264 return kdcs; 1265 } 1266 1267 private boolean fileExists(String name) { 1268 return java.security.AccessController.doPrivileged( 1269 new FileExistsAction(name)); 1270 } 1271 1272 static class FileExistsAction 1273 implements java.security.PrivilegedAction<Boolean> { 1274 1275 private String fileName; 1276 1277 public FileExistsAction(String fileName) { 1278 this.fileName = fileName; 1279 } 1280 1281 public Boolean run() { 1282 return new File(fileName).exists(); 1283 } 1284 } 1285 1286 @Override 1287 public String toString() { 1288 StringBuffer sb = new StringBuffer(); 1289 toStringIndented("", stanzaTable, sb); 1290 return sb.toString(); 1291 } 1292 private static void toStringIndented(String prefix, Object obj, 1293 StringBuffer sb) { 1294 if (obj instanceof String) { 1295 sb.append(prefix); 1296 sb.append(obj); 1297 sb.append('\n'); 1298 } else if (obj instanceof Hashtable) { 1299 Hashtable tab = (Hashtable)obj; 1300 for (Object o: tab.keySet()) { 1301 sb.append(prefix); 1302 sb.append(o); 1303 sb.append(" = {\n"); 1304 toStringIndented(prefix + " ", tab.get(o), sb); 1305 sb.append(prefix + "}\n"); 1306 } 1307 } else if (obj instanceof Vector) { 1308 Vector v = (Vector)obj; 1309 for (Object o: v.toArray()) { 1310 toStringIndented(prefix + " ", o, sb); 1311 } 1312 } 1313 } 1314 }