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