Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/Config.java
41159 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26*27* (C) Copyright IBM Corp. 1999 All Rights Reserved.28* Copyright 1997 The Open Group Research Institute. All rights reserved.29*/30package sun.security.krb5;3132import java.io.*;33import java.nio.file.DirectoryStream;34import java.nio.file.Files;35import java.nio.file.Paths;36import java.nio.file.Path;37import java.security.PrivilegedAction;38import java.util.*;39import java.net.InetAddress;40import java.net.UnknownHostException;41import java.security.AccessController;42import java.security.PrivilegedExceptionAction;43import java.util.regex.Matcher;44import java.util.regex.Pattern;4546import sun.net.dns.ResolverConfiguration;47import sun.security.action.GetPropertyAction;48import sun.security.krb5.internal.crypto.EType;49import sun.security.krb5.internal.Krb5;50import sun.security.util.SecurityProperties;5152/**53* This class maintains key-value pairs of Kerberos configurable constants54* from configuration file or from user specified system properties.55*/5657public class Config {5859/**60* {@systemProperty sun.security.krb5.disableReferrals} property61* indicating whether or not cross-realm referrals (RFC 6806) are62* enabled.63*/64public static final boolean DISABLE_REFERRALS;6566/**67* {@systemProperty sun.security.krb5.maxReferrals} property68* indicating the maximum number of cross-realm referral69* hops allowed.70*/71public static final int MAX_REFERRALS;7273static {74String disableReferralsProp =75SecurityProperties.privilegedGetOverridable(76"sun.security.krb5.disableReferrals");77if (disableReferralsProp != null) {78DISABLE_REFERRALS = "true".equalsIgnoreCase(disableReferralsProp);79} else {80DISABLE_REFERRALS = false;81}8283int maxReferralsValue = 5;84String maxReferralsProp =85SecurityProperties.privilegedGetOverridable(86"sun.security.krb5.maxReferrals");87try {88maxReferralsValue = Integer.parseInt(maxReferralsProp);89} catch (NumberFormatException e) {90}91MAX_REFERRALS = maxReferralsValue;92}9394/*95* Only allow a single instance of Config.96*/97private static Config singleton = null;9899/*100* Hashtable used to store configuration information.101*/102private Hashtable<String,Object> stanzaTable = new Hashtable<>();103104private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;105106// these are used for hexdecimal calculation.107private static final int BASE16_0 = 1;108private static final int BASE16_1 = 16;109private static final int BASE16_2 = 16 * 16;110private static final int BASE16_3 = 16 * 16 * 16;111112/**113* Specified by system properties. Must be both null or non-null.114*/115private final String defaultRealm;116private final String defaultKDC;117118// used for native interface119private static native String getWindowsDirectory(boolean isSystem);120121122/**123* Gets an instance of Config class. One and only one instance (the124* singleton) is returned.125*126* @exception KrbException if error occurs when constructing a Config127* instance. Possible causes would be either of java.security.krb5.realm or128* java.security.krb5.kdc not specified, error reading configuration file.129*/130public static synchronized Config getInstance() throws KrbException {131if (singleton == null) {132singleton = new Config();133}134return singleton;135}136137/**138* Refresh and reload the Configuration. This could involve,139* for example reading the Configuration file again or getting140* the java.security.krb5.* system properties again. This method141* also tries its best to update static fields in other classes142* that depend on the configuration.143*144* @exception KrbException if error occurs when constructing a Config145* instance. Possible causes would be either of java.security.krb5.realm or146* java.security.krb5.kdc not specified, error reading configuration file.147*/148149public static void refresh() throws KrbException {150synchronized (Config.class) {151singleton = new Config();152}153KdcComm.initStatic();154EType.initStatic();155Checksum.initStatic();156KrbAsReqBuilder.ReferralsState.initStatic();157}158159160private static boolean isMacosLionOrBetter() {161// split the "10.x.y" version number162String osname = GetPropertyAction.privilegedGetProperty("os.name");163if (!osname.contains("OS X")) {164return false;165}166167String osVersion = GetPropertyAction.privilegedGetProperty("os.version");168String[] fragments = osVersion.split("\\.");169if (fragments.length < 2) return false;170171// check if Mac OS X 10.7(.y) or higher172try {173int majorVers = Integer.parseInt(fragments[0]);174int minorVers = Integer.parseInt(fragments[1]);175if (majorVers > 10) return true;176if (majorVers == 10 && minorVers >= 7) return true;177} catch (NumberFormatException e) {178// were not integers179}180181return false;182}183184/**185* Private constructor - can not be instantiated externally.186*/187private Config() throws KrbException {188/*189* If either one system property is specified, we throw exception.190*/191String tmp = GetPropertyAction192.privilegedGetProperty("java.security.krb5.kdc");193if (tmp != null) {194// The user can specify a list of kdc hosts separated by ":"195defaultKDC = tmp.replace(':', ' ');196} else {197defaultKDC = null;198}199defaultRealm = GetPropertyAction200.privilegedGetProperty("java.security.krb5.realm");201if ((defaultKDC == null && defaultRealm != null) ||202(defaultRealm == null && defaultKDC != null)) {203throw new KrbException204("System property java.security.krb5.kdc and " +205"java.security.krb5.realm both must be set or " +206"neither must be set.");207}208209// Always read the Kerberos configuration file210try {211List<String> configFile;212String fileName = getJavaFileName();213if (fileName != null) {214configFile = loadConfigFile(fileName);215stanzaTable = parseStanzaTable(configFile);216if (DEBUG) {217System.out.println("Loaded from Java config");218}219} else {220boolean found = false;221if (isMacosLionOrBetter()) {222try {223stanzaTable = SCDynamicStoreConfig.getConfig();224if (DEBUG) {225System.out.println("Loaded from SCDynamicStoreConfig");226}227found = true;228} catch (IOException ioe) {229// OK. Will go on with file230}231}232if (!found) {233fileName = getNativeFileName();234configFile = loadConfigFile(fileName);235stanzaTable = parseStanzaTable(configFile);236if (DEBUG) {237System.out.println("Loaded from native config");238}239}240}241} catch (IOException ioe) {242if (DEBUG) {243System.out.println("Exception thrown in loading config:");244ioe.printStackTrace(System.out);245}246throw new KrbException("krb5.conf loading failed");247}248}249250/**251* Gets the last-defined string value for the specified keys.252* @param keys the keys, as an array from section name, sub-section names253* (if any), to value name.254* @return the value. When there are multiple values for the same key,255* returns the first one. {@code null} is returned if not all the keys are256* defined. For example, {@code get("libdefaults", "forwardable")} will257* return null if "forwardable" is not defined in [libdefaults], and258* {@code get("realms", "R", "kdc")} will return null if "R" is not259* defined in [realms] or "kdc" is not defined for "R".260* @throws IllegalArgumentException if any of the keys is illegal, either261* because a key not the last one is not a (sub)section name or the last262* key is still a section name. For example, {@code get("libdefaults")}263* throws this exception because [libdefaults] is a section name instead of264* a value name, and {@code get("libdefaults", "forwardable", "tail")}265* also throws this exception because "forwardable" is already a value name266* and has no sub-key at all (given "forwardable" is defined, otherwise,267* this method has no knowledge if it's a value name or a section name),268*/269public String get(String... keys) {270Vector<String> v = getString0(keys);271if (v == null) return null;272return v.firstElement();273}274275/**276* Gets the boolean value for the specified keys. Returns TRUE if the277* string value is "yes", or "true", FALSE if "no", or "false", or null278* if otherwise or not defined. The comparision is case-insensitive.279*280* @param keys the keys, see {@link #get(String...)}281* @return the boolean value, or null if there is no value defined or the282* value does not look like a boolean value.283* @throws IllegalArgumentException see {@link #get(String...)}284*/285public Boolean getBooleanObject(String... keys) {286String s = get(keys);287if (s == null) {288return null;289}290switch (s.toLowerCase(Locale.US)) {291case "yes": case "true":292return Boolean.TRUE;293case "no": case "false":294return Boolean.FALSE;295default:296return null;297}298}299300/**301* Gets all values (at least one) for the specified keys separated by302* a whitespace, or null if there is no such keys.303* The values can either be provided on a single line, or on multiple lines304* using the same key. When provided on a single line, the value can be305* comma or space separated.306* @throws IllegalArgumentException if any of the keys is illegal307* (See {@link #get})308*/309public String getAll(String... keys) {310Vector<String> v = getString0(keys);311if (v == null) return null;312StringBuilder sb = new StringBuilder();313boolean first = true;314for (String s: v) {315s = s.replaceAll("[\\s,]+", " ");316if (first) {317sb.append(s);318first = false;319} else {320sb.append(' ').append(s);321}322}323return sb.toString();324}325326/**327* Returns true if keys exists, can be final string(s) or a sub-section328* @throws IllegalArgumentException if any of the keys is illegal329* (See {@link #get})330*/331public boolean exists(String... keys) {332return get0(keys) != null;333}334335// Returns final string value(s) for given keys.336@SuppressWarnings("unchecked")337private Vector<String> getString0(String... keys) {338try {339return (Vector<String>)get0(keys);340} catch (ClassCastException cce) {341throw new IllegalArgumentException(cce);342}343}344345// Internal method. Returns the value for keys, which can be a sub-section346// (as a Hashtable) or final string value(s) (as a Vector). This is the347// only method (except for toString) that reads stanzaTable directly.348@SuppressWarnings("unchecked")349private Object get0(String... keys) {350Object current = stanzaTable;351try {352for (String key: keys) {353current = ((Hashtable<String,Object>)current).get(key);354if (current == null) return null;355}356return current;357} catch (ClassCastException cce) {358throw new IllegalArgumentException(cce);359}360}361362/**363* Translates a duration value into seconds.364*365* The format can be one of "h:m[:s]", "NdNhNmNs", and "N". See366* http://web.mit.edu/kerberos/krb5-devel/doc/basic/date_format.html#duration367* for definitions.368*369* @param s the string duration370* @return time in seconds371* @throws KrbException if format is illegal372*/373public static int duration(String s) throws KrbException {374375if (s.isEmpty()) {376throw new KrbException("Duration cannot be empty");377}378379// N380if (s.matches("\\d+")) {381return Integer.parseInt(s);382}383384// h:m[:s]385Matcher m = Pattern.compile("(\\d+):(\\d+)(:(\\d+))?").matcher(s);386if (m.matches()) {387int hr = Integer.parseInt(m.group(1));388int min = Integer.parseInt(m.group(2));389if (min >= 60) {390throw new KrbException("Illegal duration format " + s);391}392int result = hr * 3600 + min * 60;393if (m.group(4) != null) {394int sec = Integer.parseInt(m.group(4));395if (sec >= 60) {396throw new KrbException("Illegal duration format " + s);397}398result += sec;399}400return result;401}402403// NdNhNmNs404// 120m allowed. Maybe 1h120m is not good, but still allowed405m = Pattern.compile(406"((\\d+)d)?\\s*((\\d+)h)?\\s*((\\d+)m)?\\s*((\\d+)s)?",407Pattern.CASE_INSENSITIVE).matcher(s);408if (m.matches()) {409int result = 0;410if (m.group(2) != null) {411result += 86400 * Integer.parseInt(m.group(2));412}413if (m.group(4) != null) {414result += 3600 * Integer.parseInt(m.group(4));415}416if (m.group(6) != null) {417result += 60 * Integer.parseInt(m.group(6));418}419if (m.group(8) != null) {420result += Integer.parseInt(m.group(8));421}422return result;423}424425throw new KrbException("Illegal duration format " + s);426}427428/**429* Gets the int value for the specified keys.430* @param keys the keys431* @return the int value, Integer.MIN_VALUE is returned if it cannot be432* found or the value is not a legal integer.433* @throws IllegalArgumentException if any of the keys is illegal434* @see #get(java.lang.String[])435*/436public int getIntValue(String... keys) {437String result = get(keys);438int value = Integer.MIN_VALUE;439if (result != null) {440try {441value = parseIntValue(result);442} catch (NumberFormatException e) {443if (DEBUG) {444System.out.println("Exception in getting value of " +445Arrays.toString(keys) + ": " +446e.getMessage());447System.out.println("Setting " + Arrays.toString(keys) +448" to minimum value");449}450value = Integer.MIN_VALUE;451}452}453return value;454}455456/**457* Parses a string to an integer. The convertible strings include the458* string representations of positive integers, negative integers, and459* hex decimal integers. Valid inputs are, e.g., -1234, +1234,460* 0x40000.461*462* @param input the String to be converted to an Integer.463* @return an numeric value represented by the string464* @exception NumberFormatException if the String does not contain a465* parsable integer.466*/467private int parseIntValue(String input) throws NumberFormatException {468int value = 0;469if (input.startsWith("+")) {470String temp = input.substring(1);471return Integer.parseInt(temp);472} else if (input.startsWith("0x")) {473String temp = input.substring(2);474char[] chars = temp.toCharArray();475if (chars.length > 8) {476throw new NumberFormatException();477} else {478for (int i = 0; i < chars.length; i++) {479int index = chars.length - i - 1;480switch (chars[i]) {481case '0':482value += 0;483break;484case '1':485value += 1 * getBase(index);486break;487case '2':488value += 2 * getBase(index);489break;490case '3':491value += 3 * getBase(index);492break;493case '4':494value += 4 * getBase(index);495break;496case '5':497value += 5 * getBase(index);498break;499case '6':500value += 6 * getBase(index);501break;502case '7':503value += 7 * getBase(index);504break;505case '8':506value += 8 * getBase(index);507break;508case '9':509value += 9 * getBase(index);510break;511case 'a':512case 'A':513value += 10 * getBase(index);514break;515case 'b':516case 'B':517value += 11 * getBase(index);518break;519case 'c':520case 'C':521value += 12 * getBase(index);522break;523case 'd':524case 'D':525value += 13 * getBase(index);526break;527case 'e':528case 'E':529value += 14 * getBase(index);530break;531case 'f':532case 'F':533value += 15 * getBase(index);534break;535default:536throw new NumberFormatException("Invalid numerical format");537}538}539}540if (value < 0) {541throw new NumberFormatException("Data overflow.");542}543} else {544value = Integer.parseInt(input);545}546return value;547}548549private int getBase(int i) {550int result = 16;551switch (i) {552case 0:553result = BASE16_0;554break;555case 1:556result = BASE16_1;557break;558case 2:559result = BASE16_2;560break;561case 3:562result = BASE16_3;563break;564default:565for (int j = 1; j < i; j++) {566result *= 16;567}568}569return result;570}571572/**573* Reads the lines of the configuration file. All include and includedir574* directives are resolved by calling this method recursively.575*576* @param file the krb5.conf file, must be absolute577* @param content the lines. Comment and empty lines are removed,578* all lines trimmed, include and includedir579* directives resolved, unknown directives ignored580* @param dups a set of Paths to check for possible infinite loop581* @throws IOException if there is an I/O error582*/583private static Void readConfigFileLines(584Path file, List<String> content, Set<Path> dups)585throws IOException {586587if (DEBUG) {588System.out.println("Loading krb5 profile at " + file);589}590if (!file.isAbsolute()) {591throw new IOException("Profile path not absolute");592}593594if (!dups.add(file)) {595throw new IOException("Profile path included more than once");596}597598List<String> lines = Files.readAllLines(file);599600boolean inDirectives = true;601for (String line: lines) {602line = line.trim();603if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) {604continue;605}606if (inDirectives) {607if (line.charAt(0) == '[') {608inDirectives = false;609content.add(line);610} else if (line.startsWith("includedir ")) {611Path dir = Paths.get(612line.substring("includedir ".length()).trim());613try (DirectoryStream<Path> files =614Files.newDirectoryStream(dir)) {615for (Path p: files) {616if (Files.isDirectory(p)) continue;617String name = p.getFileName().toString();618if (name.matches("[a-zA-Z0-9_-]+") ||619(!name.startsWith(".") &&620name.endsWith(".conf"))) {621// if dir is absolute, so is p622readConfigFileLines(p, content, dups);623}624}625}626} else if (line.startsWith("include ")) {627readConfigFileLines(628Paths.get(line.substring("include ".length()).trim()),629content, dups);630} else {631// Unsupported directives632if (DEBUG) {633System.out.println("Unknown directive: " + line);634}635}636} else {637content.add(line);638}639}640return null;641}642643/**644* Reads the configuration file and return normalized lines.645* If the original file is:646*647* [realms]648* EXAMPLE.COM =649* {650* kdc = kerberos.example.com651* ...652* }653* ...654*655* The result will be (no indentations):656*657* {658* realms = {659* EXAMPLE.COM = {660* kdc = kerberos.example.com661* ...662* }663* }664* ...665* }666*667* @param fileName the configuration file668* @return normalized lines669*/670@SuppressWarnings("removal")671private List<String> loadConfigFile(final String fileName)672throws IOException, KrbException {673674List<String> result = new ArrayList<>();675List<String> raw = new ArrayList<>();676Set<Path> dupsCheck = new HashSet<>();677678try {679Path fullp = AccessController.doPrivileged((PrivilegedAction<Path>)680() -> Paths.get(fileName).toAbsolutePath(),681null,682new PropertyPermission("user.dir", "read"));683AccessController.doPrivileged(684new PrivilegedExceptionAction<Void>() {685@Override686public Void run() throws IOException {687Path path = Paths.get(fileName);688if (!Files.exists(path)) {689// This is OK. There are other ways to get690// Kerberos 5 settings691return null;692} else {693return readConfigFileLines(694fullp, raw, dupsCheck);695}696}697},698null,699// include/includedir can go anywhere700new FilePermission("<<ALL FILES>>", "read"));701} catch (java.security.PrivilegedActionException pe) {702throw (IOException)pe.getException();703}704String previous = null;705for (String line: raw) {706if (line.startsWith("[")) {707if (!line.endsWith("]")) {708throw new KrbException("Illegal config content:"709+ line);710}711if (previous != null) {712result.add(previous);713result.add("}");714}715String title = line.substring(7161, line.length()-1).trim();717if (title.isEmpty()) {718throw new KrbException("Illegal config content:"719+ line);720}721previous = title + " = {";722} else if (line.startsWith("{")) {723if (previous == null) {724throw new KrbException(725"Config file should not start with \"{\"");726}727previous += " {";728if (line.length() > 1) {729// { and content on the same line730result.add(previous);731previous = line.substring(1).trim();732}733} else {734if (previous == null) {735// This won't happen, because before a section736// all directives have been resolved737throw new KrbException(738"Config file must starts with a section");739}740result.add(previous);741previous = line;742}743}744if (previous != null) {745result.add(previous);746result.add("}");747}748return result;749}750751/**752* Parses the input lines to a hashtable. The key would be section names753* (libdefaults, realms, domain_realms, etc), and the value would be754* another hashtable which contains the key-value pairs inside the section.755* The value of this sub-hashtable can be another hashtable containing756* another sub-sub-section or a non-empty vector of strings for final values757* (even if there is only one value defined).758* <p>759* For top-level sections with duplicates names, their contents are merged.760* For sub-sections the former overwrites the latter. For final values,761* they are stored in a vector in their appearing order. Please note these762* values must appear in the same sub-section. Otherwise, the sub-section763* appears first should have already overridden the others.764* <p>765* As a corner case, if the same name is used as both a section name and a766* value name, the first appearance decides the type. That is to say, if the767* first one is for a section, all latter appearances are ignored. If it's768* a value, latter appearances as sections are ignored, but those as values769* are added to the vector.770* <p>771* The behavior described above is compatible to other krb5 implementations772* but it's not decumented publicly anywhere. the best practice is not to773* assume any kind of override functionality and only specify values for774* a particular key in one place.775*776* @param v the normalized input as return by loadConfigFile777* @throws KrbException if there is a file format error778*/779@SuppressWarnings("unchecked")780private Hashtable<String,Object> parseStanzaTable(List<String> v)781throws KrbException {782Hashtable<String,Object> current = stanzaTable;783for (String line: v) {784// There are only 3 kinds of lines785// 1. a = b786// 2. a = {787// 3. }788if (line.equals("}")) {789// Go back to parent, see below790current = (Hashtable<String,Object>)current.remove(" PARENT ");791if (current == null) {792throw new KrbException("Unmatched close brace");793}794} else {795int pos = line.indexOf('=');796if (pos < 0) {797throw new KrbException("Illegal config content:" + line);798}799String key = line.substring(0, pos).trim();800String value = unquote(line.substring(pos + 1));801if (value.equals("{")) {802Hashtable<String,Object> subTable;803if (current == stanzaTable) {804key = key.toLowerCase(Locale.US);805}806// When there are dup names for sections807if (current.containsKey(key)) {808if (current == stanzaTable) { // top-level, merge809// The value at top-level must be another Hashtable810subTable = (Hashtable<String,Object>)current.get(key);811} else { // otherwise, ignored812// read and ignore it (do not put into current)813subTable = new Hashtable<>();814}815} else {816subTable = new Hashtable<>();817current.put(key, subTable);818}819// A special entry for its parent. Put whitespaces around,820// so will never be confused with a normal key821subTable.put(" PARENT ", current);822current = subTable;823} else {824Vector<String> values;825if (current.containsKey(key)) {826Object obj = current.get(key);827if (obj instanceof Vector) {828// String values are merged829values = (Vector<String>)obj;830values.add(value);831} else {832// If a key shows as section first and then a value,833// ignore the value.834}835} else {836values = new Vector<String>();837values.add(value);838current.put(key, values);839}840}841}842}843if (current != stanzaTable) {844throw new KrbException("Not closed");845}846return current;847}848849/**850* Gets the default Java configuration file name.851*852* If the system property "java.security.krb5.conf" is defined, we'll853* use its value, no matter if the file exists or not. Otherwise, we854* will look at $JAVA_HOME/conf/security directory with "krb5.conf" name,855* and return it if the file exists.856*857* The method returns null if it cannot find a Java config file.858*/859private String getJavaFileName() {860String name = GetPropertyAction861.privilegedGetProperty("java.security.krb5.conf");862if (name == null) {863name = GetPropertyAction.privilegedGetProperty("java.home")864+ File.separator + "conf" + File.separator + "security"865+ File.separator + "krb5.conf";866if (!fileExists(name)) {867name = null;868}869}870if (DEBUG) {871System.out.println("Java config name: " + name);872}873return name;874}875876/**877* Gets the default native configuration file name.878*879* Depending on the OS type, the method returns the default native880* kerberos config file name, which is at windows directory with881* the name of "krb5.ini" for Windows, /etc/krb5/krb5.conf for Solaris,882* /etc/krb5.conf otherwise. Mac OSX X has a different file name.883*884* Note: When the Terminal Service is started in Windows (from 2003),885* there are two kinds of Windows directories: A system one (say,886* C:\Windows), and a user-private one (say, C:\Users\Me\Windows).887* We will first look for krb5.ini in the user-private one. If not888* found, try the system one instead.889*890* This method will always return a non-null non-empty file name,891* even if that file does not exist.892*/893private String getNativeFileName() {894String name = null;895String osname = GetPropertyAction.privilegedGetProperty("os.name");896if (osname.startsWith("Windows")) {897try {898Credentials.ensureLoaded();899} catch (Exception e) {900// ignore exceptions901}902if (Credentials.alreadyLoaded) {903String path = getWindowsDirectory(false);904if (path != null) {905if (path.endsWith("\\")) {906path = path + "krb5.ini";907} else {908path = path + "\\krb5.ini";909}910if (fileExists(path)) {911name = path;912}913}914if (name == null) {915path = getWindowsDirectory(true);916if (path != null) {917if (path.endsWith("\\")) {918path = path + "krb5.ini";919} else {920path = path + "\\krb5.ini";921}922name = path;923}924}925}926if (name == null) {927name = "c:\\winnt\\krb5.ini";928}929} else if (osname.contains("OS X")) {930name = findMacosConfigFile();931} else {932name = "/etc/krb5.conf";933}934if (DEBUG) {935System.out.println("Native config name: " + name);936}937return name;938}939940private String findMacosConfigFile() {941String userHome = GetPropertyAction.privilegedGetProperty("user.home");942final String PREF_FILE = "/Library/Preferences/edu.mit.Kerberos";943String userPrefs = userHome + PREF_FILE;944945if (fileExists(userPrefs)) {946return userPrefs;947}948949if (fileExists(PREF_FILE)) {950return PREF_FILE;951}952953return "/etc/krb5.conf";954}955956private static String unquote(String s) {957s = s.trim();958if (s.length() >= 2 &&959((s.charAt(0) == '"' && s.charAt(s.length()-1) == '"') ||960(s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\''))) {961s = s.substring(1, s.length()-1).trim();962}963return s;964}965966/**967* For testing purpose. This method lists all information being parsed from968* the configuration file to the hashtable.969*/970public void listTable() {971System.out.println(this);972}973974/**975* Returns all etypes specified in krb5.conf for the given configName,976* or all the builtin defaults. This result is always non-empty.977* If no etypes are found, an exception is thrown.978*/979public int[] defaultEtype(String configName) throws KrbException {980String default_enctypes;981default_enctypes = get("libdefaults", configName);982if (default_enctypes == null && !configName.equals("permitted_enctypes")) {983default_enctypes = get("libdefaults", "permitted_enctypes");984}985int[] etype;986if (default_enctypes == null) {987if (DEBUG) {988System.out.println("Using builtin default etypes for " +989configName);990}991etype = EType.getBuiltInDefaults();992} else {993String delim = " ";994StringTokenizer st;995for (int j = 0; j < default_enctypes.length(); j++) {996if (default_enctypes.substring(j, j + 1).equals(",")) {997// only two delimiters are allowed to use998// according to Kerberos DCE doc.999delim = ",";1000break;1001}1002}1003st = new StringTokenizer(default_enctypes, delim);1004int len = st.countTokens();1005ArrayList<Integer> ls = new ArrayList<>(len);1006int type;1007for (int i = 0; i < len; i++) {1008type = Config.getType(st.nextToken());1009if (type != -1 && EType.isSupported(type)) {1010ls.add(type);1011}1012}1013if (ls.isEmpty()) {1014throw new KrbException("no supported default etypes for "1015+ configName);1016} else {1017etype = new int[ls.size()];1018for (int i = 0; i < etype.length; i++) {1019etype[i] = ls.get(i);1020}1021}1022}10231024if (DEBUG) {1025System.out.print("default etypes for " + configName + ":");1026for (int i = 0; i < etype.length; i++) {1027System.out.print(" " + etype[i]);1028}1029System.out.println(".");1030}1031return etype;1032}103310341035/**1036* Get the etype and checksum value for the specified encryption and1037* checksum type.1038*1039*/1040/*1041* This method converts the string representation of encryption type and1042* checksum type to int value that can be later used by EType and1043* Checksum classes.1044*/1045public static int getType(String input) {1046int result = -1;1047if (input == null) {1048return result;1049}1050if (input.startsWith("d") || (input.startsWith("D"))) {1051if (input.equalsIgnoreCase("des-cbc-crc")) {1052result = EncryptedData.ETYPE_DES_CBC_CRC;1053} else if (input.equalsIgnoreCase("des-cbc-md5")) {1054result = EncryptedData.ETYPE_DES_CBC_MD5;1055} else if (input.equalsIgnoreCase("des-mac")) {1056result = Checksum.CKSUMTYPE_DES_MAC;1057} else if (input.equalsIgnoreCase("des-mac-k")) {1058result = Checksum.CKSUMTYPE_DES_MAC_K;1059} else if (input.equalsIgnoreCase("des-cbc-md4")) {1060result = EncryptedData.ETYPE_DES_CBC_MD4;1061} else if (input.equalsIgnoreCase("des3-cbc-sha1") ||1062input.equalsIgnoreCase("des3-hmac-sha1") ||1063input.equalsIgnoreCase("des3-cbc-sha1-kd") ||1064input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) {1065result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD;1066}1067} else if (input.startsWith("a") || (input.startsWith("A"))) {1068// AES1069if (input.equalsIgnoreCase("aes128-cts") ||1070input.equalsIgnoreCase("aes128-sha1") ||1071input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) {1072result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96;1073} else if (input.equalsIgnoreCase("aes256-cts") ||1074input.equalsIgnoreCase("aes256-sha1") ||1075input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) {1076result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96;1077} else if (input.equalsIgnoreCase("aes128-sha2") ||1078input.equalsIgnoreCase("aes128-cts-hmac-sha256-128")) {1079result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128;1080} else if (input.equalsIgnoreCase("aes256-sha2") ||1081input.equalsIgnoreCase("aes256-cts-hmac-sha384-192")) {1082result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192;1083// ARCFOUR-HMAC1084} else if (input.equalsIgnoreCase("arcfour-hmac") ||1085input.equalsIgnoreCase("arcfour-hmac-md5")) {1086result = EncryptedData.ETYPE_ARCFOUR_HMAC;1087}1088// RC4-HMAC1089} else if (input.equalsIgnoreCase("rc4-hmac")) {1090result = EncryptedData.ETYPE_ARCFOUR_HMAC;1091} else if (input.equalsIgnoreCase("CRC32")) {1092result = Checksum.CKSUMTYPE_CRC32;1093} else if (input.startsWith("r") || (input.startsWith("R"))) {1094if (input.equalsIgnoreCase("rsa-md5")) {1095result = Checksum.CKSUMTYPE_RSA_MD5;1096} else if (input.equalsIgnoreCase("rsa-md5-des")) {1097result = Checksum.CKSUMTYPE_RSA_MD5_DES;1098}1099} else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) {1100result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD;1101} else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) {1102result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128;1103} else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) {1104result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256;1105} else if (input.equalsIgnoreCase("hmac-sha256-128-aes128")) {1106result = Checksum.CKSUMTYPE_HMAC_SHA256_128_AES128;1107} else if (input.equalsIgnoreCase("hmac-sha384-192-aes256")) {1108result = Checksum.CKSUMTYPE_HMAC_SHA384_192_AES256;1109} else if (input.equalsIgnoreCase("hmac-md5-rc4") ||1110input.equalsIgnoreCase("hmac-md5-arcfour") ||1111input.equalsIgnoreCase("hmac-md5-enc")) {1112result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR;1113} else if (input.equalsIgnoreCase("NULL")) {1114result = EncryptedData.ETYPE_NULL;1115}11161117return result;1118}11191120/**1121* Resets the default kdc realm.1122* We do not need to synchronize these methods since assignments are atomic1123*1124* This method was useless. Kept here in case some class still calls it.1125*/1126public void resetDefaultRealm(String realm) {1127if (DEBUG) {1128System.out.println(">>> Config try resetting default kdc " + realm);1129}1130}11311132/**1133* Check to use addresses in tickets1134* use addresses if "no_addresses" or "noaddresses" is set to false1135*/1136public boolean useAddresses() {1137return getBooleanObject("libdefaults", "no_addresses") == Boolean.FALSE ||1138getBooleanObject("libdefaults", "noaddresses") == Boolean.FALSE;1139}11401141/**1142* Check if need to use DNS to locate Kerberos services for name. If not1143* defined, check dns_fallback, whose default value is true.1144*/1145private boolean useDNS(String name, boolean defaultValue) {1146Boolean value = getBooleanObject("libdefaults", name);1147if (value != null) {1148return value.booleanValue();1149}1150value = getBooleanObject("libdefaults", "dns_fallback");1151if (value != null) {1152return value.booleanValue();1153}1154return defaultValue;1155}11561157/**1158* Check if need to use DNS to locate the KDC1159*/1160private boolean useDNS_KDC() {1161return useDNS("dns_lookup_kdc", true);1162}11631164/*1165* Check if need to use DNS to locate the Realm1166*/1167private boolean useDNS_Realm() {1168return useDNS("dns_lookup_realm", false);1169}11701171/**1172* Gets default realm.1173* @throws KrbException where no realm can be located1174* @return the default realm, always non null1175*/1176@SuppressWarnings("removal")1177public String getDefaultRealm() throws KrbException {1178if (defaultRealm != null) {1179return defaultRealm;1180}1181Exception cause = null;1182String realm = get("libdefaults", "default_realm");1183if ((realm == null) && useDNS_Realm()) {1184// use DNS to locate Kerberos realm1185try {1186realm = getRealmFromDNS();1187} catch (KrbException ke) {1188cause = ke;1189}1190}1191if (realm == null) {1192realm = java.security.AccessController.doPrivileged(1193new java.security.PrivilegedAction<String>() {1194@Override1195public String run() {1196String osname = System.getProperty("os.name");1197if (osname.startsWith("Windows")) {1198return System.getenv("USERDNSDOMAIN");1199}1200return null;1201}1202});1203}1204if (realm == null) {1205KrbException ke = new KrbException("Cannot locate default realm");1206if (cause != null) {1207ke.initCause(cause);1208}1209throw ke;1210}1211return realm;1212}12131214/**1215* Returns a list of KDC's with each KDC separated by a space1216*1217* @param realm the realm for which the KDC list is desired1218* @throws KrbException if there's no way to find KDC for the realm1219* @return the list of KDCs separated by a space, always non null1220*/1221@SuppressWarnings("removal")1222public String getKDCList(String realm) throws KrbException {1223if (realm == null) {1224realm = getDefaultRealm();1225}1226if (realm.equalsIgnoreCase(defaultRealm)) {1227return defaultKDC;1228}1229Exception cause = null;1230String kdcs = getAll("realms", realm, "kdc");1231if ((kdcs == null) && useDNS_KDC()) {1232// use DNS to locate KDC1233try {1234kdcs = getKDCFromDNS(realm);1235} catch (KrbException ke) {1236cause = ke;1237}1238}1239if (kdcs == null) {1240kdcs = java.security.AccessController.doPrivileged(1241new java.security.PrivilegedAction<String>() {1242@Override1243public String run() {1244String osname = System.getProperty("os.name");1245if (osname.startsWith("Windows")) {1246String logonServer = System.getenv("LOGONSERVER");1247if (logonServer != null1248&& logonServer.startsWith("\\\\")) {1249logonServer = logonServer.substring(2);1250}1251return logonServer;1252}1253return null;1254}1255});1256}1257if (kdcs == null) {1258if (defaultKDC != null) {1259return defaultKDC;1260}1261KrbException ke = new KrbException("Cannot locate KDC");1262if (cause != null) {1263ke.initCause(cause);1264}1265throw ke;1266}1267return kdcs;1268}12691270/**1271* Locate Kerberos realm using DNS1272*1273* @return the Kerberos realm1274*/1275private String getRealmFromDNS() throws KrbException {1276// use DNS to locate Kerberos realm1277String realm = null;1278String hostName = null;1279try {1280hostName = InetAddress.getLocalHost().getCanonicalHostName();1281} catch (UnknownHostException e) {1282KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC,1283"Unable to locate Kerberos realm: " + e.getMessage());1284ke.initCause(e);1285throw (ke);1286}1287// get the domain realm mapping from the configuration1288String mapRealm = PrincipalName.mapHostToRealm(hostName);1289if (mapRealm == null) {1290// No match. Try search and/or domain in /etc/resolv.conf1291List<String> srchlist = ResolverConfiguration.open().searchlist();1292for (String domain: srchlist) {1293realm = checkRealm(domain);1294if (realm != null) {1295break;1296}1297}1298} else {1299realm = checkRealm(mapRealm);1300}1301if (realm == null) {1302throw new KrbException(Krb5.KRB_ERR_GENERIC,1303"Unable to locate Kerberos realm");1304}1305return realm;1306}13071308/**1309* Check if the provided realm is the correct realm1310* @return the realm if correct, or null otherwise1311*/1312private static String checkRealm(String mapRealm) {1313if (DEBUG) {1314System.out.println("getRealmFromDNS: trying " + mapRealm);1315}1316String[] records = null;1317String newRealm = mapRealm;1318while ((records == null) && (newRealm != null)) {1319// locate DNS TXT record1320records = KrbServiceLocator.getKerberosService(newRealm);1321newRealm = Realm.parseRealmComponent(newRealm);1322// if no DNS TXT records found, try again using sub-realm1323}1324if (records != null) {1325for (int i = 0; i < records.length; i++) {1326if (records[i].equalsIgnoreCase(mapRealm)) {1327return records[i];1328}1329}1330}1331return null;1332}13331334/**1335* Locate KDC using DNS1336*1337* @param realm the realm for which the primary KDC is desired1338* @return the KDC1339*/1340private String getKDCFromDNS(String realm) throws KrbException {1341// use DNS to locate KDC1342String kdcs = "";1343String[] srvs = null;1344// locate DNS SRV record using UDP1345if (DEBUG) {1346System.out.println("getKDCFromDNS using UDP");1347}1348srvs = KrbServiceLocator.getKerberosService(realm, "_udp");1349if (srvs == null) {1350// locate DNS SRV record using TCP1351if (DEBUG) {1352System.out.println("getKDCFromDNS using TCP");1353}1354srvs = KrbServiceLocator.getKerberosService(realm, "_tcp");1355}1356if (srvs == null) {1357// no DNS SRV records1358throw new KrbException(Krb5.KRB_ERR_GENERIC,1359"Unable to locate KDC for realm " + realm);1360}1361if (srvs.length == 0) {1362return null;1363}1364for (int i = 0; i < srvs.length; i++) {1365kdcs += srvs[i].trim() + " ";1366}1367kdcs = kdcs.trim();1368if (kdcs.equals("")) {1369return null;1370}1371return kdcs;1372}13731374@SuppressWarnings("removal")1375private boolean fileExists(String name) {1376return java.security.AccessController.doPrivileged(1377new FileExistsAction(name));1378}13791380static class FileExistsAction1381implements java.security.PrivilegedAction<Boolean> {13821383private String fileName;13841385public FileExistsAction(String fileName) {1386this.fileName = fileName;1387}13881389public Boolean run() {1390return new File(fileName).exists();1391}1392}13931394// Shows the content of the Config object for debug purpose.1395//1396// {1397// libdefaults = {1398// default_realm = R1399// }1400// realms = {1401// R = {1402// kdc = [k1,k2]1403// }1404// }1405// }14061407@Override1408public String toString() {1409StringBuffer sb = new StringBuffer();1410toStringInternal("", stanzaTable, sb);1411return sb.toString();1412}1413private static void toStringInternal(String prefix, Object obj,1414StringBuffer sb) {1415if (obj instanceof String) {1416// A string value, just print it1417sb.append(obj).append('\n');1418} else if (obj instanceof Hashtable) {1419// A table, start a new sub-section...1420Hashtable<?, ?> tab = (Hashtable<?, ?>)obj;1421sb.append("{\n");1422for (Object o: tab.keySet()) {1423// ...indent, print "key = ", and1424sb.append(prefix).append(" ").append(o).append(" = ");1425// ...go recursively into value1426toStringInternal(prefix + " ", tab.get(o), sb);1427}1428sb.append(prefix).append("}\n");1429} else if (obj instanceof Vector) {1430// A vector of strings, print them inside [ and ]1431Vector<?> v = (Vector<?>)obj;1432sb.append("[");1433boolean first = true;1434for (Object o: v.toArray()) {1435if (!first) sb.append(",");1436sb.append(o);1437first = false;1438}1439sb.append("]\n");1440}1441}1442}144314441445