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