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 }