/* * Copyright (c) 2000, 2011, 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 * javax.security.auth.login.Configuration. * *

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 Configuration object is: * *

    *
  1. * Loop through the java.security.Security properties, * login.config.url.1, login.config.url.2, ..., * login.config.url.X. 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 URL pointing to a * login configuration file to be loaded. Read in and load * each configuration. * *
  2. * The java.lang.System property * java.security.auth.login.config * may also be set to a URL 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, * policy.allowSystemProperty is set to true), * also load that login configuration. * *
  3. * If the java.security.auth.login.config property is defined using * "==" (rather than "="), then ignore all other specified * login configurations and only load this configuration. * *
  4. * 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. *
* *

The configuration syntax supported by this implementation * is exactly that syntax specified in the * javax.security.auth.login.Configuration 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> 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 Configuration object. */ public ConfigFile() { try { init(url); } catch (IOException ioe) { throw (SecurityException) new SecurityException(ioe.getMessage()).initCause(ioe); } } /** * Create a new Configuration 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. * *

* * @exception IOException if the Configuration can not be initialized.

* @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> newConfig = new HashMap<>(); 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> 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.No.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. * *

* * @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 list = null; synchronized (configuration) { list = configuration.get(applicationName); } if (list == null || list.size() == 0) return null; AppConfigurationEntry[] entries = new AppConfigurationEntry[list.size()]; Iterator 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. * *

* * @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() { 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> 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> newConfig) throws IOException { String appName; String moduleClass; String sflag; AppConfigurationEntry.LoginModuleControlFlag controlFlag; LinkedList configEntries = new LinkedList<>(); // 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.Invalid.control.flag.flag", "sun.security.util.AuthResources")); Object[] source = {sflag}; throw new IOException(form.format(source)); } // get the args HashMap options = new HashMap<>(); 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 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.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.expected.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.Line.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.Line.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.Line.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.Line.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.Line.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.Line.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".equalsIgnoreCase(url.getProtocol())) { // Compatibility notes: // // Code changed from // String path = url.getFile().replace('/', File.separatorChar); // return new FileInputStream(path); // // The original implementation would search for "/tmp/a%20b" // when url is "file:///tmp/a%20b". This is incorrect. The // current codes fix this bug and searches for "/tmp/a b". // For compatibility reasons, when the file "/tmp/a b" does // not exist, the file named "/tmp/a%20b" will be tried. // // This also means that if both file exists, the behavior of // this method is changed, and the current codes choose the // correct one. try { return url.openStream(); } catch (Exception e) { String file = url.getPath(); if (url.getHost().length() > 0) { // For Windows UNC file = "//" + url.getHost() + file; } if (debugConfig != null) { debugConfig.println("cannot read " + url + ", try " + file); } return new FileInputStream(file); } } 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.Line.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; } } }