/* * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.security.auth.login; import javax.security.auth.AuthPermission; import javax.security.auth.login.AppConfigurationEntry; import java.io.*; import java.util.*; import java.net.URI; import java.net.URL; import java.net.MalformedURLException; import java.text.MessageFormat; import sun.security.util.Debug; import sun.security.util.ResourcesMgr; import sun.security.util.PropertyExpander; /** * This class represents a default implementation for * <code>javax.security.auth.login.Configuration</code>. * * <p> This object stores the runtime login configuration representation, * and is the amalgamation of multiple static login * configurations that resides in files. * The algorithm for locating the login configuration file(s) and reading their * information into this <code>Configuration</code> object is: * * <ol> * <li> * Loop through the <code>java.security.Security</code> properties, * <i>login.config.url.1</i>, <i>login.config.url.2</i>, ..., * <i>login.config.url.X</i>. These properties are set * in the Java security properties file, which is located in the file named * <JAVA_HOME>/lib/security/java.security. * <JAVA_HOME> refers to the value of the java.home system property, * and specifies the directory where the JRE is installed. * Each property value specifies a <code>URL</code> pointing to a * login configuration file to be loaded. Read in and load * each configuration. * * <li> * The <code>java.lang.System</code> property * <i>java.security.auth.login.config</i> * may also be set to a <code>URL</code> pointing to another * login configuration file * (which is the case when a user uses the -D switch at runtime). * If this property is defined, and its use is allowed by the * security property file (the Security property, * <i>policy.allowSystemProperty</i> is set to <i>true</i>), * also load that login configuration. * * <li> * If the <i>java.security.auth.login.config</i> property is defined using * "==" (rather than "="), then ignore all other specified * login configurations and only load this configuration. * * <li> * If no system or security properties were set, try to read from the file, * ${user.home}/.java.login.config, where ${user.home} is the value * represented by the "user.home" System property. * </ol> * * <p> The configuration syntax supported by this implementation * is exactly that syntax specified in the * <code>javax.security.auth.login.Configuration</code> class. * * @see javax.security.auth.login.LoginContext */ public class ConfigFile extends javax.security.auth.login.Configuration { private StreamTokenizer st; private int lookahead; private int linenum; private HashMap<String, LinkedList<AppConfigurationEntry>> configuration; private boolean expandProp = true; private URL url; private static Debug debugConfig = Debug.getInstance("configfile"); private static Debug debugParser = Debug.getInstance("configparser"); /** * Create a new <code>Configuration</code> object. */ public ConfigFile() { try { init(url); } catch (IOException ioe) { throw (SecurityException) new SecurityException(ioe.getMessage()).initCause(ioe); } } /** * Create a new <code>Configuration</code> object from the specified URI. * * @param uri Create a new Configuration object from this URI. */ public ConfigFile(URI uri) { // only load config from the specified URI try { url = uri.toURL(); init(url); } catch (MalformedURLException mue) { throw (SecurityException) new SecurityException(mue.getMessage()).initCause(mue); } catch (IOException ioe) { throw (SecurityException) new SecurityException(ioe.getMessage()).initCause(ioe); } } /** * Read and initialize the entire login Configuration. * * <p> * * @exception IOException if the Configuration can not be initialized. <p> * @exception SecurityException if the caller does not have permission * to initialize the Configuration. */ private void init(URL url) throws IOException { boolean initialized = false; FileReader fr = null; String sep = File.separator; if ("false".equals(System.getProperty("policy.expandProperties"))) { expandProp = false; } // new configuration HashMap<String, LinkedList<AppConfigurationEntry>> newConfig = new HashMap<String, LinkedList<AppConfigurationEntry>>(); if (url != null) { /** * If the caller specified a URI via Configuration.getInstance, * we only read from that URI */ if (debugConfig != null) { debugConfig.println("reading " + url); } init(url, newConfig); configuration = newConfig; return; } /** * Caller did not specify URI via Configuration.getInstance. * Read from URLs listed in the java.security properties file. */ String allowSys = java.security.Security.getProperty ("policy.allowSystemProperty"); if ("true".equalsIgnoreCase(allowSys)) { String extra_config = System.getProperty ("java.security.auth.login.config"); if (extra_config != null) { boolean overrideAll = false; if (extra_config.startsWith("=")) { overrideAll = true; extra_config = extra_config.substring(1); } try { extra_config = PropertyExpander.expand(extra_config); } catch (PropertyExpander.ExpandException peee) { MessageFormat form = new MessageFormat (ResourcesMgr.getString ("Unable to properly expand config", "sun.security.util.AuthResources")); Object[] source = {extra_config}; throw new IOException(form.format(source)); } URL configURL = null; try { configURL = new URL(extra_config); } catch (java.net.MalformedURLException mue) { File configFile = new File(extra_config); if (configFile.exists()) { configURL = configFile.toURI().toURL(); } else { MessageFormat form = new MessageFormat (ResourcesMgr.getString ("extra_config (No such file or directory)", "sun.security.util.AuthResources")); Object[] source = {extra_config}; throw new IOException(form.format(source)); } } if (debugConfig != null) { debugConfig.println("reading "+configURL); } init(configURL, newConfig); initialized = true; if (overrideAll) { if (debugConfig != null) { debugConfig.println("overriding other policies!"); } configuration = newConfig; return; } } } int n = 1; String config_url; while ((config_url = java.security.Security.getProperty ("login.config.url."+n)) != null) { try { config_url = PropertyExpander.expand (config_url).replace(File.separatorChar, '/'); if (debugConfig != null) { debugConfig.println("\tReading config: " + config_url); } init(new URL(config_url), newConfig); initialized = true; } catch (PropertyExpander.ExpandException peee) { MessageFormat form = new MessageFormat (ResourcesMgr.getString ("Unable to properly expand config", "sun.security.util.AuthResources")); Object[] source = {config_url}; throw new IOException(form.format(source)); } n++; } if (initialized == false && n == 1 && config_url == null) { // get the config from the user's home directory if (debugConfig != null) { debugConfig.println("\tReading Policy " + "from ~/.java.login.config"); } config_url = System.getProperty("user.home"); String userConfigFile = config_url + File.separatorChar + ".java.login.config"; // No longer throws an exception when there's no config file // at all. Returns an empty Configuration instead. if (new File(userConfigFile).exists()) { init(new File(userConfigFile).toURI().toURL(), newConfig); } } configuration = newConfig; } private void init(URL config, HashMap<String, LinkedList<AppConfigurationEntry>> newConfig) throws IOException { InputStreamReader isr = null; try { isr = new InputStreamReader(getInputStream(config), "UTF-8"); readConfig(isr, newConfig); } catch (FileNotFoundException fnfe) { if (debugConfig != null) { debugConfig.println(fnfe.toString()); } throw new IOException(ResourcesMgr.getString ("Configuration Error:\n\tNo such file or directory", "sun.security.util.AuthResources")); } finally { if (isr != null) { isr.close(); } } } /** * Retrieve an entry from the Configuration using an application name * as an index. * * <p> * * @param applicationName the name used to index the Configuration. * @return an array of AppConfigurationEntries which correspond to * the stacked configuration of LoginModules for this * application, or null if this application has no configured * LoginModules. */ public AppConfigurationEntry[] getAppConfigurationEntry (String applicationName) { LinkedList<AppConfigurationEntry> list = null; synchronized (configuration) { list = configuration.get(applicationName); } if (list == null || list.size() == 0) return null; AppConfigurationEntry[] entries = new AppConfigurationEntry[list.size()]; Iterator<AppConfigurationEntry> iterator = list.iterator(); for (int i = 0; iterator.hasNext(); i++) { AppConfigurationEntry e = iterator.next(); entries[i] = new AppConfigurationEntry(e.getLoginModuleName(), e.getControlFlag(), e.getOptions()); } return entries; } /** * Refresh and reload the Configuration by re-reading all of the * login configurations. * * <p> * * @exception SecurityException if the caller does not have permission * to refresh the Configuration. */ public synchronized void refresh() { java.lang.SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(new AuthPermission("refreshLoginConfiguration")); java.security.AccessController.doPrivileged (new java.security.PrivilegedAction<Void>() { public Void run() { try { init(url); } catch (java.io.IOException ioe) { throw (SecurityException) new SecurityException (ioe.getLocalizedMessage()).initCause(ioe); } return null; } }); } private void readConfig(Reader reader, HashMap<String, LinkedList<AppConfigurationEntry>> newConfig) throws IOException { int linenum = 1; if (!(reader instanceof BufferedReader)) reader = new BufferedReader(reader); st = new StreamTokenizer(reader); st.quoteChar('"'); st.wordChars('$', '$'); st.wordChars('_', '_'); st.wordChars('-', '-'); st.lowerCaseMode(false); st.slashSlashComments(true); st.slashStarComments(true); st.eolIsSignificant(true); lookahead = nextToken(); while (lookahead != StreamTokenizer.TT_EOF) { parseLoginEntry(newConfig); } } private void parseLoginEntry( HashMap<String, LinkedList<AppConfigurationEntry>> newConfig) throws IOException { String appName; String moduleClass; String sflag; AppConfigurationEntry.LoginModuleControlFlag controlFlag; LinkedList<AppConfigurationEntry> configEntries = new LinkedList<AppConfigurationEntry>(); // application name appName = st.sval; lookahead = nextToken(); if (debugParser != null) { debugParser.println("\tReading next config entry: " + appName); } match("{"); // get the modules while (peek("}") == false) { // get the module class name moduleClass = match("module class name"); // controlFlag (required, optional, etc) sflag = match("controlFlag"); if (sflag.equalsIgnoreCase("REQUIRED")) controlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; else if (sflag.equalsIgnoreCase("REQUISITE")) controlFlag = AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; else if (sflag.equalsIgnoreCase("SUFFICIENT")) controlFlag = AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; else if (sflag.equalsIgnoreCase("OPTIONAL")) controlFlag = AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tInvalid control flag, flag", "sun.security.util.AuthResources")); Object[] source = {sflag}; throw new IOException(form.format(source)); } // get the args HashMap<String, String> options = new HashMap<String, String>(); String key; String value; while (peek(";") == false) { key = match("option key"); match("="); try { value = expand(match("option value")); } catch (PropertyExpander.ExpandException peee) { throw new IOException(peee.getLocalizedMessage()); } options.put(key, value); } lookahead = nextToken(); // create the new element if (debugParser != null) { debugParser.println("\t\t" + moduleClass + ", " + sflag); java.util.Iterator<String> i = options.keySet().iterator(); while (i.hasNext()) { key = i.next(); debugParser.println("\t\t\t" + key + "=" + options.get(key)); } } AppConfigurationEntry entry = new AppConfigurationEntry (moduleClass, controlFlag, options); configEntries.add(entry); } match("}"); match(";"); // add this configuration entry if (newConfig.containsKey(appName)) { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\t" + "Can not specify multiple entries for appName", "sun.security.util.AuthResources")); Object[] source = {appName}; throw new IOException(form.format(source)); } newConfig.put(appName, configEntries); } private String match(String expect) throws IOException { String value = null; switch(lookahead) { case StreamTokenizer.TT_EOF: MessageFormat form1 = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\texpected [expect], " + "read [end of file]", "sun.security.util.AuthResources")); Object[] source1 = {expect}; throw new IOException(form1.format(source1)); case '"': case StreamTokenizer.TT_WORD: if (expect.equalsIgnoreCase("module class name") || expect.equalsIgnoreCase("controlFlag") || expect.equalsIgnoreCase("option key") || expect.equalsIgnoreCase("option value")) { value = st.sval; lookahead = nextToken(); } else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: " + "expected [expect], found [value]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } break; case '{': if (expect.equalsIgnoreCase("{")) { lookahead = nextToken(); } else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: expected [expect]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } break; case ';': if (expect.equalsIgnoreCase(";")) { lookahead = nextToken(); } else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: expected [expect]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } break; case '}': if (expect.equalsIgnoreCase("}")) { lookahead = nextToken(); } else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: expected [expect]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } break; case '=': if (expect.equalsIgnoreCase("=")) { lookahead = nextToken(); } else { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: expected [expect]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } break; default: MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: " + "expected [expect], found [value]", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), expect, st.sval}; throw new IOException(form.format(source)); } return value; } private boolean peek(String expect) { boolean found = false; switch (lookahead) { case ',': if (expect.equalsIgnoreCase(",")) found = true; break; case ';': if (expect.equalsIgnoreCase(";")) found = true; break; case '{': if (expect.equalsIgnoreCase("{")) found = true; break; case '}': if (expect.equalsIgnoreCase("}")) found = true; break; default: } return found; } private int nextToken() throws IOException { int tok; while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) { linenum++; } return tok; } /* * Fast path reading from file urls in order to avoid calling * FileURLConnection.connect() which can be quite slow the first time * it is called. We really should clean up FileURLConnection so that * this is not a problem but in the meantime this fix helps reduce * start up time noticeably for the new launcher. -- DAC */ private InputStream getInputStream(URL url) throws IOException { if ("file".equals(url.getProtocol())) { String path = url.getFile().replace('/', File.separatorChar); return new FileInputStream(path); } else { return url.openStream(); } } private String expand(String value) throws PropertyExpander.ExpandException, IOException { if ("".equals(value)) { return value; } if (expandProp) { String s = PropertyExpander.expand(value); if (s == null || s.length() == 0) { MessageFormat form = new MessageFormat(ResourcesMgr.getString ("Configuration Error:\n\tLine line: " + "system property [value] expanded to empty value", "sun.security.util.AuthResources")); Object[] source = {new Integer(linenum), value}; throw new IOException(form.format(source)); } return s; } else { return value; } } }