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