0001: /*
0002: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: /*
0027: * @(#)Config.java 1.38 07/07/18
0028: *
0029: * (C) Copyright IBM Corp. 1999 All Rights Reserved.
0030: * Copyright 1997 The Open Group Research Institute. All rights reserved.
0031: */
0032: package sun.security.krb5;
0033:
0034: import java.io.File;
0035: import java.io.FileInputStream;
0036: import java.util.Hashtable;
0037: import java.util.Vector;
0038: import java.util.ArrayList;
0039: import java.io.BufferedReader;
0040: import java.io.InputStreamReader;
0041: import java.io.IOException;
0042: import java.util.Enumeration;
0043: import java.util.List;
0044: import java.util.StringTokenizer;
0045: import java.net.InetAddress;
0046: import java.net.UnknownHostException;
0047: import sun.security.krb5.internal.crypto.EType;
0048: import sun.security.krb5.internal.ktab.*;
0049: import sun.security.krb5.internal.Krb5;
0050:
0051: /**
0052: * This class maintains key-value pairs of Kerberos configurable constants
0053: * from configuration file or from user specified system properties.
0054: */
0055:
0056: public class Config {
0057:
0058: /*
0059: * Only allow a single instance of Config.
0060: */
0061: private static Config singleton = null;
0062:
0063: /*
0064: * Hashtable used to store configuration infomation.
0065: */
0066: private Hashtable<String, Object> stanzaTable;
0067:
0068: private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
0069:
0070: // these are used for hexdecimal calculation.
0071: private static final int BASE16_0 = 1;
0072: private static final int BASE16_1 = 16;
0073: private static final int BASE16_2 = 16 * 16;
0074: private static final int BASE16_3 = 16 * 16 * 16;
0075: private String defaultRealm; // default kdc realm.
0076:
0077: // used for native interface
0078: private static native String getWindowsDirectory();
0079:
0080: /**
0081: * Gets an instance of Config class. One and only one instance (the
0082: * singleton) is returned.
0083: *
0084: * @exception KrbException if error occurs when constructing a Config
0085: * instance. Possible causes would be configuration file not
0086: * found, either of java.security.krb5.realm or java.security.krb5.kdc
0087: * not specified, error reading configuration file.
0088: */
0089: public static synchronized Config getInstance() throws KrbException {
0090: if (singleton == null) {
0091: singleton = new Config();
0092: }
0093: return singleton;
0094: }
0095:
0096: /**
0097: * Refresh and reload the Configuration. This could involve,
0098: * for example reading the Configuration file again or getting
0099: * the java.security.krb5.* system properties again.
0100: *
0101: * @exception KrbException if error occurs when constructing a Config
0102: * instance. Possible causes would be configuration file not
0103: * found, either of java.security.krb5.realm or java.security.krb5.kdc
0104: * not specified, error reading configuration file.
0105: */
0106:
0107: public static synchronized void refresh() throws KrbException {
0108: singleton = new Config();
0109: KeyTab.refresh();
0110: }
0111:
0112: /**
0113: * Private constructor - can not be instantiated externally.
0114: */
0115: private Config() throws KrbException {
0116: /*
0117: * If these two system properties are being specified by the user,
0118: * we ignore configuration file. If either one system property is
0119: * specified, we throw exception. If neither of them are specified,
0120: * we load the information from configuration file.
0121: */
0122: String kdchost = java.security.AccessController
0123: .doPrivileged(new sun.security.action.GetPropertyAction(
0124: "java.security.krb5.kdc"));
0125: defaultRealm = java.security.AccessController
0126: .doPrivileged(new sun.security.action.GetPropertyAction(
0127: "java.security.krb5.realm"));
0128: if ((kdchost == null && defaultRealm != null)
0129: || (defaultRealm == null && kdchost != null)) {
0130: throw new KrbException(
0131: "System property java.security.krb5.kdc and "
0132: + "java.security.krb5.realm both must be set or "
0133: + "neither must be set.");
0134: }
0135: if (kdchost != null) {
0136: /*
0137: * If configuration information is only specified by
0138: * properties java.security.krb5.kdc and
0139: * java.security.krb5.realm, we put both in the hashtable
0140: * under [libdefaults].
0141: */
0142: Hashtable<String, String> kdcs = new Hashtable<String, String>();
0143: kdcs.put("default_realm", defaultRealm);
0144: // The user can specify a list of kdc hosts separated by ":"
0145: kdchost = kdchost.replace(':', ' ');
0146: kdcs.put("kdc", kdchost);
0147: stanzaTable = new Hashtable<String, Object>();
0148: stanzaTable.put("libdefaults", kdcs);
0149: } else {
0150: // Read the Kerberos configuration file
0151: try {
0152: Vector<String> configFile;
0153: configFile = loadConfigFile();
0154: stanzaTable = parseStanzaTable(configFile);
0155: } catch (IOException ioe) {
0156: KrbException ke = new KrbException("Could not load "
0157: + "configuration file " + ioe.getMessage());
0158: ke.initCause(ioe);
0159: throw (ke);
0160: }
0161: }
0162: }
0163:
0164: /**
0165: * Gets the default int value for the specified name.
0166: * @param name the name.
0167: * @return the default Integer, null is returned if no such name and
0168: * value are found in configuration file, or error occurs when parsing
0169: * string to integer.
0170: */
0171: public int getDefaultIntValue(String name) {
0172: String result = null;
0173: int value = Integer.MIN_VALUE;
0174: result = getDefault(name);
0175: if (result != null) {
0176: try {
0177: value = parseIntValue(result);
0178: } catch (NumberFormatException e) {
0179: if (DEBUG) {
0180: System.out.println("Exception in getting value of "
0181: + name + " " + e.getMessage());
0182: System.out.println("Setting " + name
0183: + " to minimum value");
0184: }
0185: value = Integer.MIN_VALUE;
0186: }
0187: }
0188: return value;
0189: }
0190:
0191: /**
0192: * Gets the default int value for the specified name in the specified
0193: * section. <br>This method is quicker by using section name as the
0194: * search key.
0195: * @param name the name.
0196: * @param sectio the name string of the section.
0197: * @return the default Integer, null is returned if no such name and
0198: * value are found in configuration file, or error occurs when parsing
0199: * string to integer.
0200: */
0201: public int getDefaultIntValue(String name, String section) {
0202: String result = null;
0203: int value = Integer.MIN_VALUE;
0204: result = getDefault(name, section);
0205: if (result != null) {
0206: try {
0207: value = parseIntValue(result);
0208: } catch (NumberFormatException e) {
0209: if (DEBUG) {
0210: System.out.println("Exception in getting value of "
0211: + name + " in section " + section + " "
0212: + e.getMessage());
0213: System.out.println("Setting " + name
0214: + " to minimum value");
0215: }
0216: value = Integer.MIN_VALUE;
0217: }
0218: }
0219: return value;
0220: }
0221:
0222: /**
0223: * Gets the default string value for the specified name.
0224: * @param name the name.
0225: * @return the default value, null is returned if it cannot be found.
0226: */
0227: public String getDefault(String name) {
0228: if (stanzaTable == null) {
0229: return null;
0230: } else {
0231: return getDefault(name, stanzaTable);
0232: }
0233: }
0234:
0235: /**
0236: * This method does the real job to recursively search through the
0237: * stanzaTable.
0238: * @param k the key string.
0239: * @param t stanzaTable or sub hashtable within it.
0240: * @return the value found in config file, returns null if no value
0241: * matched with the key is found.
0242: */
0243: private String getDefault(String k, Hashtable t) {
0244: String result = null;
0245: String key;
0246: if (stanzaTable != null) {
0247: for (Enumeration e = t.keys(); e.hasMoreElements();) {
0248: key = (String) e.nextElement();
0249: Object ob = t.get(key);
0250: if (ob instanceof Hashtable) {
0251: result = getDefault(k, (Hashtable) ob);
0252: if (result != null) {
0253: return result;
0254: }
0255: } else if (key.equalsIgnoreCase(k)) {
0256: if (ob instanceof String) {
0257: return (String) (t.get(key));
0258: } else if (ob instanceof Vector) {
0259: result = "";
0260: int length = ((Vector) ob).size();
0261: for (int i = 0; i < length; i++) {
0262: if (i == length - 1) {
0263: result += (String) (((Vector) ob)
0264: .elementAt(i));
0265: } else {
0266: result += (String) (((Vector) ob)
0267: .elementAt(i))
0268: + " ";
0269: }
0270: }
0271: return result;
0272: }
0273: }
0274: }
0275: }
0276: return result;
0277: }
0278:
0279: /**
0280: * Gets the default string value for the specified name in the
0281: * specified section.
0282: * <br>This method is quicker by using the section name as the search key.
0283: * @param name the name.
0284: * @param section the name of the section.
0285: * @return the default value, null is returned if it cannot be found.
0286: */
0287: public String getDefault(String name, String section) {
0288: String stanzaName;
0289: String result = null;
0290: Hashtable subTable;
0291:
0292: /*
0293: * In the situation when kdc is specified by
0294: * java.security.krb5.kdc, we get the kdc from [libdefaults] in
0295: * hashtable.
0296: */
0297: if (name.equalsIgnoreCase("kdc")
0298: && (!section.equalsIgnoreCase("libdefaults"))
0299: && (java.security.AccessController
0300: .doPrivileged(new sun.security.action.GetPropertyAction(
0301: "java.security.krb5.kdc")) != null)) {
0302: result = getDefault("kdc", "libdefaults");
0303: return result;
0304: }
0305: if (stanzaTable != null) {
0306: for (Enumeration e = stanzaTable.keys(); e
0307: .hasMoreElements();) {
0308: stanzaName = (String) e.nextElement();
0309: subTable = (Hashtable) stanzaTable.get(stanzaName);
0310: if (stanzaName.equalsIgnoreCase(section)) {
0311: if (subTable.containsKey(name)) {
0312: return (String) (subTable.get(name));
0313: }
0314: } else if (subTable.containsKey(section)) {
0315: Object ob = subTable.get(section);
0316: if (ob instanceof Hashtable) {
0317: Hashtable temp = (Hashtable) ob;
0318: if (temp.containsKey(name)) {
0319: Object object = temp.get(name);
0320: if (object instanceof Vector) {
0321: result = "";
0322: int length = ((Vector) object).size();
0323: for (int i = 0; i < length; i++) {
0324: if (i == length - 1) {
0325: result += (String) (((Vector) object)
0326: .elementAt(i));
0327: } else {
0328: result += (String) (((Vector) object)
0329: .elementAt(i))
0330: + " ";
0331: }
0332: }
0333: } else {
0334: result = (String) object;
0335: }
0336: }
0337: }
0338: }
0339: }
0340: }
0341: return result;
0342: }
0343:
0344: /**
0345: * Gets the default boolean value for the specified name.
0346: * @param name the name.
0347: * @return the default boolean value, false is returned if it cannot be
0348: * found.
0349: */
0350: public boolean getDefaultBooleanValue(String name) {
0351: String val = null;
0352: if (stanzaTable == null) {
0353: val = null;
0354: } else {
0355: val = getDefault(name, stanzaTable);
0356: }
0357: if (val != null && val.equalsIgnoreCase("true")) {
0358: return true;
0359: } else {
0360: return false;
0361: }
0362: }
0363:
0364: /**
0365: * Gets the default boolean value for the specified name in the
0366: * specified section.
0367: * <br>This method is quicker by using the section name as the search key.
0368: * @param name the name.
0369: * @param section the name of the section.
0370: * @return the default boolean value, false is returned if it cannot be
0371: * found.
0372: */
0373: public boolean getDefaultBooleanValue(String name, String section) {
0374: String val = getDefault(name, section);
0375: if (val != null && val.equalsIgnoreCase("true")) {
0376: return true;
0377: } else {
0378: return false;
0379: }
0380: }
0381:
0382: /**
0383: * Parses a string to an integer. The convertible strings include the
0384: * string representations of positive integers, negative integers, and
0385: * hex decimal integers. Valid inputs are, e.g., -1234, +1234,
0386: * 0x40000.
0387: *
0388: * @param input the String to be converted to an Integer.
0389: * @return an numeric value represented by the string
0390: * @exception NumberFormationException if the String does not contain a
0391: * parsable integer.
0392: */
0393: private int parseIntValue(String input)
0394: throws NumberFormatException {
0395: int value = 0;
0396: if (input.startsWith("+")) {
0397: String temp = input.substring(1);
0398: return Integer.parseInt(temp);
0399: } else if (input.startsWith("0x")) {
0400: String temp = input.substring(2);
0401: char[] chars = temp.toCharArray();
0402: if (chars.length > 8) {
0403: throw new NumberFormatException();
0404: } else {
0405: for (int i = 0; i < chars.length; i++) {
0406: int index = chars.length - i - 1;
0407: switch (chars[i]) {
0408: case '0':
0409: value += 0;
0410: break;
0411: case '1':
0412: value += 1 * getBase(index);
0413: break;
0414: case '2':
0415: value += 2 * getBase(index);
0416: break;
0417: case '3':
0418: value += 3 * getBase(index);
0419: break;
0420: case '4':
0421: value += 4 * getBase(index);
0422: break;
0423: case '5':
0424: value += 5 * getBase(index);
0425: break;
0426: case '6':
0427: value += 6 * getBase(index);
0428: break;
0429: case '7':
0430: value += 7 * getBase(index);
0431: break;
0432: case '8':
0433: value += 8 * getBase(index);
0434: break;
0435: case '9':
0436: value += 9 * getBase(index);
0437: break;
0438: case 'a':
0439: case 'A':
0440: value += 10 * getBase(index);
0441: break;
0442: case 'b':
0443: case 'B':
0444: value += 11 * getBase(index);
0445: break;
0446: case 'c':
0447: case 'C':
0448: value += 12 * getBase(index);
0449: break;
0450: case 'd':
0451: case 'D':
0452: value += 13 * getBase(index);
0453: break;
0454: case 'e':
0455: case 'E':
0456: value += 14 * getBase(index);
0457: break;
0458: case 'f':
0459: case 'F':
0460: value += 15 * getBase(index);
0461: break;
0462: default:
0463: throw new NumberFormatException(
0464: "Invalid numerical format");
0465: }
0466: }
0467: }
0468: if (value < 0) {
0469: throw new NumberFormatException("Data overflow.");
0470: }
0471: } else {
0472: value = Integer.parseInt(input);
0473: }
0474: return value;
0475: }
0476:
0477: private int getBase(int i) {
0478: int result = 16;
0479: switch (i) {
0480: case 0:
0481: result = BASE16_0;
0482: break;
0483: case 1:
0484: result = BASE16_1;
0485: break;
0486: case 2:
0487: result = BASE16_2;
0488: break;
0489: case 3:
0490: result = BASE16_3;
0491: break;
0492: default:
0493: for (int j = 1; j < i; j++) {
0494: result *= 16;
0495: }
0496: }
0497: return result;
0498: }
0499:
0500: /**
0501: * Finds the matching value in the hashtable.
0502: */
0503: private String find(String key1, String key2) {
0504: String result;
0505: if ((stanzaTable != null)
0506: && ((result = (String) (((Hashtable) (stanzaTable
0507: .get(key1))).get(key2))) != null)) {
0508: return result;
0509: } else {
0510: return "";
0511: }
0512: }
0513:
0514: /**
0515: * Reads name/value pairs to the memory from the configuration
0516: * file. The default location of the configuration file is in java home
0517: * directory.
0518: *
0519: * Configuration file contains information about the default realm,
0520: * ticket parameters, location of the KDC and the admin server for
0521: * known realms, etc. The file is divided into sections. Each section
0522: * contains one or more name/value pairs with one pair per line. A
0523: * typical file would be:
0524: * [libdefaults]
0525: * default_realm = EXAMPLE.COM
0526: * default_tgs_enctypes = des-cbc-md5
0527: * default_tkt_enctypes = des-cbc-md5
0528: * [realms]
0529: * EXAMPLE.COM = {
0530: * kdc = kerberos.example.com
0531: * kdc = kerberos-1.example.com
0532: * admin_server = kerberos.example.com
0533: * }
0534: * SAMPLE_COM = {
0535: * kdc = orange.sample.com
0536: * admin_server = orange.sample.com
0537: * }
0538: * [domain_realm]
0539: * blue.sample.com = TEST.SAMPLE.COM
0540: * .backup.com = EXAMPLE.COM
0541: */
0542: private Vector<String> loadConfigFile() throws IOException {
0543: try {
0544: final String fileName = getFileName();
0545: if (!fileName.equals("")) {
0546: BufferedReader br = new BufferedReader(
0547: new InputStreamReader(
0548: java.security.AccessController
0549: .doPrivileged(new java.security.PrivilegedExceptionAction<FileInputStream>() {
0550: public FileInputStream run()
0551: throws IOException {
0552: return new FileInputStream(
0553: fileName);
0554: }
0555: })));
0556: String Line;
0557: Vector<String> v = new Vector<String>();
0558: String previous = null;
0559: while ((Line = br.readLine()) != null) {
0560: // ignore comments and blank line in the configuration file.
0561: // Comments start with #.
0562: if (!(Line.startsWith("#") || Line.trim().isEmpty())) {
0563: String current = Line.trim();
0564: // In practice, a subsection might look like:
0565: // EXAMPLE.COM =
0566: // {
0567: // kdc = kerberos.example.com
0568: // ...
0569: // }
0570: // Before parsed into stanza table, it needs to be
0571: // converted into formal style:
0572: // EXAMPLE.COM = {
0573: // kdc = kerberos.example.com
0574: // ...
0575: // }
0576: //
0577: // So, if a line is "{", adhere to the previous line.
0578: if (current.equals("{")) {
0579: if (previous == null) {
0580: throw new IOException(
0581: "Config file should not start with \"{\"");
0582: }
0583: previous += " " + current;
0584: } else {
0585: if (previous != null) {
0586: v.addElement(previous);
0587: }
0588: previous = current;
0589: }
0590: }
0591: }
0592: if (previous != null) {
0593: v.addElement(previous);
0594: }
0595:
0596: br.close();
0597: return v;
0598: }
0599: return null;
0600: } catch (java.security.PrivilegedActionException pe) {
0601: throw (IOException) pe.getException();
0602: }
0603: }
0604:
0605: /**
0606: * Parses stanza names and values from configuration file to
0607: * stanzaTable (Hashtable). Hashtable key would be stanza names,
0608: * (libdefaults, realms, domain_realms, etc), and the hashtable value
0609: * would be another hashtable which contains the key-value pairs under
0610: * a stanza name.
0611: */
0612: private Hashtable<String, Object> parseStanzaTable(Vector<String> v)
0613: throws KrbException {
0614: if (v == null) {
0615: throw new KrbException("I/O error while reading"
0616: + " configuration file.");
0617: }
0618: Hashtable<String, Object> table = new Hashtable<String, Object>();
0619: for (int i = 0; i < v.size(); i++) {
0620: String line = v.elementAt(i).trim();
0621: if (line.equalsIgnoreCase("[realms]")) {
0622: for (int count = i + 1; count < v.size() + 1; count++) {
0623: // find the next stanza name
0624: if ((count == v.size())
0625: || (v.elementAt(count).startsWith("["))) {
0626: Hashtable<String, Hashtable<String, Vector<String>>> temp = new Hashtable<String, Hashtable<String, Vector<String>>>();
0627: temp = parseRealmField(v, i + 1, count);
0628: table.put("realms", temp);
0629: i = count - 1;
0630: break;
0631: }
0632: }
0633: } else if (line.equalsIgnoreCase("[capaths]")) {
0634: for (int count = i + 1; count < v.size() + 1; count++) {
0635: // find the next stanza name
0636: if ((count == v.size())
0637: || (v.elementAt(count).startsWith("["))) {
0638: Hashtable<String, Hashtable<String, Vector<String>>> temp = new Hashtable<String, Hashtable<String, Vector<String>>>();
0639: temp = parseRealmField(v, i + 1, count);
0640: table.put("capaths", temp);
0641: i = count - 1;
0642: break;
0643: }
0644: }
0645: } else if (line.startsWith("[") && line.endsWith("]")) {
0646: String key = line.substring(1, line.length() - 1);
0647: for (int count = i + 1; count < v.size() + 1; count++) {
0648: // find the next stanza name
0649: if ((count == v.size())
0650: || (v.elementAt(count).startsWith("["))) {
0651: Hashtable<String, String> temp = parseField(v,
0652: i + 1, count);
0653: table.put(key, temp);
0654: i = count - 1;
0655: break;
0656: }
0657: }
0658: }
0659: }
0660: return table;
0661: }
0662:
0663: /**
0664: * Gets the default configuration file name. The file will be searched
0665: * in a list of possible loations in the following order:
0666: * 1. the location and file name defined by system property
0667: * "java.security.krb5.conf",
0668: * 2. at Java home lib\security directory with "krb5.conf" name,
0669: * 3. "krb5.ini" at Java home,
0670: * 4. at windows directory with the name of "krb5.ini" for Windows,
0671: * /etc/krb5/krb5.conf for Solaris, /etc/krb5.conf for Linux.
0672: */
0673: private String getFileName() {
0674: String name = java.security.AccessController
0675: .doPrivileged(new sun.security.action.GetPropertyAction(
0676: "java.security.krb5.conf"));
0677: if (name != null) {
0678: boolean temp = java.security.AccessController
0679: .doPrivileged(new FileExistsAction(name));
0680: if (temp)
0681: return name;
0682: } else {
0683: name = java.security.AccessController
0684: .doPrivileged(new sun.security.action.GetPropertyAction(
0685: "java.home"))
0686: + File.separator
0687: + "lib"
0688: + File.separator
0689: + "security" + File.separator + "krb5.conf";
0690: boolean temp = java.security.AccessController
0691: .doPrivileged(new FileExistsAction(name));
0692: if (temp) {
0693: return name;
0694: } else {
0695: String osname = java.security.AccessController
0696: .doPrivileged(new sun.security.action.GetPropertyAction(
0697: "os.name"));
0698: if (osname.startsWith("Windows")) {
0699: try {
0700: Credentials.ensureLoaded();
0701: } catch (Exception e) {
0702: // ignore exceptions
0703: }
0704: if (Credentials.alreadyLoaded) {
0705: if ((name = getWindowsDirectory()) == null) {
0706: name = "c:\\winnt\\krb5.ini";
0707: } else if (name.endsWith("\\")) {
0708: name += "krb5.ini";
0709: } else {
0710: name += "\\krb5.ini";
0711: }
0712: } else {
0713: name = "c:\\winnt\\krb5.ini";
0714: }
0715: } else if (osname.startsWith("SunOS")) {
0716: name = "/etc/krb5/krb5.conf";
0717: } else if (osname.startsWith("Linux")) {
0718: name = "/etc/krb5.conf";
0719: }
0720: }
0721: }
0722: if (DEBUG) {
0723: System.out.println("Config name: " + name);
0724: }
0725: return name;
0726: }
0727:
0728: /**
0729: * Parses key-value pairs under a stanza name.
0730: */
0731: private Hashtable<String, String> parseField(Vector<String> v,
0732: int start, int end) {
0733: Hashtable<String, String> table = new Hashtable<String, String>();
0734: String line;
0735: for (int i = start; i < end; i++) {
0736: line = v.elementAt(i);
0737: for (int j = 0; j < line.length(); j++) {
0738: if (line.charAt(j) == '=') {
0739: String key = (line.substring(0, j)).trim();
0740: String value = (line.substring(j + 1)).trim();
0741: table.put(key, value);
0742: break;
0743: }
0744: }
0745: }
0746: return table;
0747: }
0748:
0749: /**
0750: * Parses key-value pairs under [realms]. The key would be the realm
0751: * name, the value would be another hashtable which contains
0752: * information for the realm given within a pair of braces.
0753: */
0754: private Hashtable<String, Hashtable<String, Vector<String>>> parseRealmField(
0755: Vector<String> v, int start, int end) {
0756: Hashtable<String, Hashtable<String, Vector<String>>> table = new Hashtable<String, Hashtable<String, Vector<String>>>();
0757: String line;
0758: for (int i = start; i < end; i++) {
0759: line = v.elementAt(i).trim();
0760: if (line.endsWith("{")) {
0761: String key = "";
0762: for (int j = 0; j < line.length(); j++) {
0763: if (line.charAt(j) == '=') {
0764: key = line.substring(0, j).trim();
0765: // get the key
0766: break;
0767: }
0768: }
0769: for (int k = i + 1; k < end; k++) {
0770: boolean found = false;
0771: line = v.elementAt(k).trim();
0772: for (int l = 0; l < line.length(); l++) {
0773: if (line.charAt(l) == '}') {
0774: found = true;
0775: break;
0776: }
0777: }
0778: if (found == true) {
0779: Hashtable<String, Vector<String>> temp = parseRealmFieldEx(
0780: v, i + 1, k);
0781: table.put(key, temp);
0782: i = k;
0783: found = false;
0784: break;
0785: }
0786:
0787: }
0788: }
0789: }
0790: return table;
0791: }
0792:
0793: /**
0794: * Parses key-value pairs within each braces under [realms].
0795: */
0796: private Hashtable<String, Vector<String>> parseRealmFieldEx(
0797: Vector<String> v, int start, int end) {
0798: Hashtable<String, Vector<String>> table = new Hashtable<String, Vector<String>>();
0799: Vector<String> keyVector = new Vector<String>();
0800: Vector<String> nameVector = new Vector<String>();
0801: String line = "";
0802: String key;
0803: for (int i = start; i < end; i++) {
0804: line = v.elementAt(i);
0805: for (int j = 0; j < line.length(); j++) {
0806: if (line.charAt(j) == '=') {
0807: int index;
0808: key = line.substring(0, j - 1).trim();
0809: if (!exists(key, keyVector)) {
0810: keyVector.addElement(key);
0811: nameVector = new Vector<String>();
0812: } else {
0813: nameVector = table.get(key);
0814: }
0815: nameVector.addElement((line.substring(j + 1))
0816: .trim());
0817: table.put(key, nameVector);
0818: break;
0819: }
0820: }
0821: }
0822: return table;
0823: }
0824:
0825: /**
0826: * Compares the key with the known keys to see if it exists.
0827: */
0828: private boolean exists(String key, Vector v) {
0829: boolean exists = false;
0830: for (int i = 0; i < v.size(); i++) {
0831: if (((String) (v.elementAt(i))).equals(key)) {
0832: exists = true;
0833: }
0834: }
0835: return exists;
0836: }
0837:
0838: /**
0839: * For testing purpose. This method lists all information being parsed from
0840: * the configuration file to the hashtable.
0841: */
0842: public void listTable() {
0843: listTable(stanzaTable);
0844: }
0845:
0846: private void listTable(Hashtable table) {
0847: Vector v = new Vector();
0848: String key;
0849: if (stanzaTable != null) {
0850: for (Enumeration e = table.keys(); e.hasMoreElements();) {
0851: key = (String) e.nextElement();
0852: Object object = table.get(key);
0853: if (table == stanzaTable) {
0854: System.out.println("[" + key + "]");
0855: }
0856: if (object instanceof Hashtable) {
0857: if (table != stanzaTable)
0858: System.out.println("\t" + key + " = {");
0859: listTable((Hashtable) object);
0860: if (table != stanzaTable)
0861: System.out.println("\t}");
0862:
0863: } else if (object instanceof String) {
0864: System.out.println("\t" + key + " = "
0865: + (String) table.get(key));
0866: } else if (object instanceof Vector) {
0867: v = (Vector) object;
0868: for (int i = 0; i < v.size(); i++) {
0869: System.out.println("\t" + key + " = "
0870: + (String) v.elementAt(i));
0871: }
0872: }
0873: }
0874: } else {
0875: System.out.println("Configuration file not found.");
0876: }
0877: }
0878:
0879: /**
0880: * Returns the default encryption types.
0881: *
0882: */
0883: public int[] defaultEtype(String enctypes) {
0884: String default_enctypes;
0885: default_enctypes = getDefault(enctypes, "libdefaults");
0886: String delim = " ";
0887: StringTokenizer st;
0888: int[] etype;
0889: if (default_enctypes == null) {
0890: if (DEBUG) {
0891: System.out.println("Using builtin default etypes for "
0892: + enctypes);
0893: }
0894: etype = EType.getBuiltInDefaults();
0895: } else {
0896: for (int j = 0; j < default_enctypes.length(); j++) {
0897: if (default_enctypes.substring(j, j + 1).equals(",")) {
0898: // only two delimiters are allowed to use
0899: // according to Kerberos DCE doc.
0900: delim = ",";
0901: break;
0902: }
0903: }
0904: st = new StringTokenizer(default_enctypes, delim);
0905: int len = st.countTokens();
0906: ArrayList<Integer> ls = new ArrayList<Integer>(len);
0907: int type;
0908: for (int i = 0; i < len; i++) {
0909: type = getType(st.nextToken());
0910: if ((type != -1) && (EType.isSupported(type))) {
0911: ls.add(type);
0912: }
0913: }
0914: if (ls.size() == 0) {
0915: if (DEBUG) {
0916: System.out
0917: .println("no supported default etypes for "
0918: + enctypes);
0919: }
0920: return null;
0921: } else {
0922: etype = new int[ls.size()];
0923: for (int i = 0; i < etype.length; i++) {
0924: etype[i] = ls.get(i);
0925: }
0926: }
0927: }
0928:
0929: if (DEBUG) {
0930: System.out.print("default etypes for " + enctypes + ":");
0931: for (int i = 0; i < etype.length; i++) {
0932: System.out.print(" " + etype[i]);
0933: }
0934: System.out.println(".");
0935: }
0936: return etype;
0937: }
0938:
0939: /**
0940: * Get the etype and checksum value for the specified encryption and
0941: * checksum type.
0942: *
0943: */
0944: /*
0945: * This method converts the string representation of encryption type and
0946: * checksum type to int value that can be later used by EType and
0947: * Checksum classes.
0948: */
0949: public int getType(String input) {
0950: int result = -1;
0951: if (input == null) {
0952: return result;
0953: }
0954: if (input.startsWith("d") || (input.startsWith("D"))) {
0955: if (input.equalsIgnoreCase("des-cbc-crc")) {
0956: result = EncryptedData.ETYPE_DES_CBC_CRC;
0957: } else if (input.equalsIgnoreCase("des-cbc-md5")) {
0958: result = EncryptedData.ETYPE_DES_CBC_MD5;
0959: } else if (input.equalsIgnoreCase("des-mac")) {
0960: result = Checksum.CKSUMTYPE_DES_MAC;
0961: } else if (input.equalsIgnoreCase("des-mac-k")) {
0962: result = Checksum.CKSUMTYPE_DES_MAC_K;
0963: } else if (input.equalsIgnoreCase("des-cbc-md4")) {
0964: result = EncryptedData.ETYPE_DES_CBC_MD4;
0965: } else if (input.equalsIgnoreCase("des3-cbc-sha1")
0966: || input.equalsIgnoreCase("des3-hmac-sha1")
0967: || input.equalsIgnoreCase("des3-cbc-sha1-kd")
0968: || input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) {
0969: result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD;
0970: }
0971: } else if (input.startsWith("a") || (input.startsWith("A"))) {
0972: // AES
0973: if (input.equalsIgnoreCase("aes128-cts")
0974: || input
0975: .equalsIgnoreCase("aes128-cts-hmac-sha1-96")) {
0976: result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96;
0977: } else if (input.equalsIgnoreCase("aes256-cts")
0978: || input
0979: .equalsIgnoreCase("aes256-cts-hmac-sha1-96")) {
0980: result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96;
0981: // ARCFOUR-HMAC
0982: } else if (input.equalsIgnoreCase("arcfour-hmac")
0983: || input.equalsIgnoreCase("arcfour-hmac-md5")) {
0984: result = EncryptedData.ETYPE_ARCFOUR_HMAC;
0985: }
0986: // RC4-HMAC
0987: } else if (input.equalsIgnoreCase("rc4-hmac")) {
0988: result = EncryptedData.ETYPE_ARCFOUR_HMAC;
0989: } else if (input.equalsIgnoreCase("CRC32")) {
0990: result = Checksum.CKSUMTYPE_CRC32;
0991: } else if (input.startsWith("r") || (input.startsWith("R"))) {
0992: if (input.equalsIgnoreCase("rsa-md5")) {
0993: result = Checksum.CKSUMTYPE_RSA_MD5;
0994: } else if (input.equalsIgnoreCase("rsa-md5-des")) {
0995: result = Checksum.CKSUMTYPE_RSA_MD5_DES;
0996: }
0997: } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) {
0998: result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD;
0999: } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) {
1000: result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128;
1001: } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) {
1002: result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256;
1003: } else if (input.equalsIgnoreCase("hmac-md5-rc4")
1004: || input.equalsIgnoreCase("hmac-md5-arcfour")
1005: || input.equalsIgnoreCase("hmac-md5-enc")) {
1006: result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR;
1007: } else if (input.equalsIgnoreCase("NULL")) {
1008: result = EncryptedData.ETYPE_NULL;
1009: }
1010:
1011: return result;
1012: }
1013:
1014: /**
1015: * Resets the default kdc realm.
1016: * We do not need to synchronize these methods since assignments are atomic
1017: */
1018: public void resetDefaultRealm(String realm) {
1019: defaultRealm = realm;
1020: if (DEBUG) {
1021: System.out.println(">>> Config reset default kdc "
1022: + defaultRealm);
1023: }
1024:
1025: }
1026:
1027: /**
1028: * Check to use addresses in tickets
1029: * use addresses if "no_addresses" or "noaddresses" is set to false
1030: */
1031: public boolean useAddresses() {
1032: boolean useAddr = false;
1033: // use addresses if "no_addresses" is set to false
1034: String value = getDefault("no_addresses", "libdefaults");
1035: useAddr = (value != null && value.equalsIgnoreCase("false"));
1036: if (useAddr == false) {
1037: // use addresses if "noaddresses" is set to false
1038: value = getDefault("noaddresses", "libdefaults");
1039: useAddr = (value != null && value.equalsIgnoreCase("false"));
1040: }
1041: return useAddr;
1042: }
1043:
1044: /**
1045: * Check if need to use DNS to locate Kerberos services
1046: */
1047: public boolean useDNS(String name) {
1048: boolean value = getDefaultBooleanValue(name, "libdefaults");
1049: if (value == false) {
1050: value = getDefaultBooleanValue("dns_fallback",
1051: "libdefaults");
1052: }
1053: return value;
1054: }
1055:
1056: /**
1057: * Check if need to use DNS to locate the KDC
1058: */
1059: public boolean useDNS_KDC() {
1060: return useDNS("dns_lookup_kdc");
1061: }
1062:
1063: /*
1064: * Check if need to use DNS to locate the Realm
1065: */
1066: public boolean useDNS_Realm() {
1067: return useDNS("dns_lookup_realm");
1068: }
1069:
1070: /**
1071: * Gets default realm.
1072: */
1073: public String getDefaultRealm() throws KrbException {
1074: String realm = getDefault("default_realm", "libdefaults");
1075: if ((realm == null) && useDNS_Realm()) {
1076: // use DNS to locate Kerberos realm
1077: realm = getRealmFromDNS();
1078: }
1079: return realm;
1080: }
1081:
1082: /**
1083: * Returns a list of KDC's with each KDC separated by a space
1084: *
1085: * @param realm the realm for which the master KDC is desired
1086: * @return the list of KDCs
1087: */
1088: public String getKDCList(String realm) throws KrbException {
1089: if (realm == null) {
1090: realm = getDefaultRealm();
1091: }
1092: String kdcs = getDefault("kdc", realm);
1093: if ((kdcs == null) && useDNS_KDC()) {
1094: // use DNS to locate KDC
1095: kdcs = getKDCFromDNS(realm);
1096: }
1097: return kdcs;
1098: }
1099:
1100: /**
1101: * Locate Kerberos realm using DNS
1102: *
1103: * @return the Kerberos realm
1104: */
1105: private String getRealmFromDNS() throws KrbException {
1106: // use DNS to locate Kerberos realm
1107: String realm = null;
1108: String hostName = null;
1109: try {
1110: hostName = InetAddress.getLocalHost().getHostName();
1111: } catch (UnknownHostException e) {
1112: KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC,
1113: "Unable to locate Kerberos realm: "
1114: + e.getMessage());
1115: ke.initCause(e);
1116: throw (ke);
1117: }
1118: // get the domain realm mapping from the configuration
1119: String mapRealm = PrincipalName.mapHostToRealm(hostName);
1120: String[] records = null;
1121: String newRealm = mapRealm;
1122: while ((records == null) && (newRealm != null)) {
1123: // locate DNS TXT record
1124: records = KrbServiceLocator.getKerberosService(newRealm);
1125: newRealm = Realm.parseRealmComponent(newRealm);
1126: // if no DNS TXT records found, try again using sub-realm
1127: }
1128: if (records == null) {
1129: // no DNS TXT records
1130: throw new KrbException(Krb5.KRB_ERR_GENERIC,
1131: "Unable to locate Kerberos realm");
1132: }
1133: boolean found = false;
1134: for (int i = 0; i < records.length; i++) {
1135: if (records[i].equals(mapRealm)) {
1136: found = true;
1137: realm = records[i];
1138: }
1139: }
1140: if (found == false) {
1141: throw new KrbException(Krb5.KRB_ERR_GENERIC,
1142: "Unable to locate Kerberos realm");
1143: }
1144: return realm;
1145: }
1146:
1147: /**
1148: * Locate KDC using DNS
1149: *
1150: * @param realm the realm for which the master KDC is desired
1151: * @return the KDC
1152: */
1153: private String getKDCFromDNS(String realm) throws KrbException {
1154: // use DNS to locate KDC
1155: String kdcs = null;
1156: String[] srvs = null;
1157: // locate DNS SRV record using UDP
1158: srvs = KrbServiceLocator.getKerberosService(realm, "_udp.");
1159: if (srvs == null) {
1160: // locate DNS SRV record using TCP
1161: srvs = KrbServiceLocator.getKerberosService(realm, "_tcp.");
1162: }
1163: if (srvs == null) {
1164: // no DNS SRV records
1165: throw new KrbException(Krb5.KRB_ERR_GENERIC,
1166: "Unable to locate KDC for realm " + realm);
1167: }
1168: for (int i = 0; i < srvs.length; i++) {
1169: String value = srvs[i];
1170: for (int j = 0; j < srvs[i].length(); j++) {
1171: // filter the KDC name
1172: if (value.charAt(j) == ':') {
1173: kdcs = (value.substring(0, j)).trim();
1174: }
1175: }
1176: }
1177: return kdcs;
1178: }
1179:
1180: static class FileExistsAction implements
1181: java.security.PrivilegedAction<Boolean> {
1182:
1183: private String fileName;
1184:
1185: public FileExistsAction(String fileName) {
1186: this .fileName = fileName;
1187: }
1188:
1189: public Boolean run() {
1190: return new File(fileName).exists();
1191: }
1192: }
1193:
1194: }
|