--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/provider/ConfigFile.java Mon Aug 12 09:03:51 2013 -0400
@@ -0,0 +1,669 @@
+/*
+ * Copyright (c) 2000, 2013, 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 sun.security.provider;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.Security;
+import java.security.URIParameter;
+import java.text.MessageFormat;
+import java.util.*;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.ConfigurationSpi;
+import sun.security.util.Debug;
+import sun.security.util.PropertyExpander;
+import sun.security.util.ResourcesMgr;
+
+/**
+ * This class represents a default implementation for
+ * {@code javax.security.auth.login.Configuration}.
+ *
+ * <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}
+ * object is:
+ *
+ * <ol>
+ * <li>
+ * Loop through the security properties,
+ * <i>login.config.url.1</i>, <i>login.config.url.2</i>, ...,
+ * <i>login.config.url.X</i>.
+ * Each property value specifies a {@code URL} pointing to a
+ * login configuration file to be loaded. Read in and load
+ * each configuration.
+ *
+ * <li>
+ * The {@code java.lang.System} property
+ * <i>java.security.auth.login.config</i>
+ * may also be set to a {@code 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,
+ * <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} class.
+ *
+ * @see javax.security.auth.login.LoginContext
+ * @see java.security.Security security properties
+ */
+public final class ConfigFile extends Configuration {
+
+ private final Spi spi;
+
+ public ConfigFile() {
+ spi = new Spi();
+ }
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
+ return spi.engineGetAppConfigurationEntry(appName);
+ }
+
+ @Override
+ public synchronized void refresh() {
+ spi.engineRefresh();
+ }
+
+ public final static class Spi extends ConfigurationSpi {
+
+ private URL url;
+ private boolean expandProp = true;
+ private Map<String, List<AppConfigurationEntry>> configuration;
+ private int linenum;
+ private StreamTokenizer st;
+ private int lookahead;
+
+ private static Debug debugConfig = Debug.getInstance("configfile");
+ private static Debug debugParser = Debug.getInstance("configparser");
+
+ /**
+ * Creates a new {@code ConfigurationSpi} object.
+ *
+ * @throws SecurityException if the {@code ConfigurationSpi} can not be
+ * initialized
+ */
+ public Spi() {
+ try {
+ init();
+ } catch (IOException ioe) {
+ throw new SecurityException(ioe);
+ }
+ }
+
+ /**
+ * Creates a new {@code ConfigurationSpi} object from the specified
+ * {@code URI}.
+ *
+ * @param uri the {@code URI}
+ * @throws SecurityException if the {@code ConfigurationSpi} can not be
+ * initialized
+ * @throws NullPointerException if {@code uri} is null
+ */
+ public Spi(URI uri) {
+ // only load config from the specified URI
+ try {
+ url = uri.toURL();
+ init();
+ } catch (IOException ioe) {
+ throw new SecurityException(ioe);
+ }
+ }
+
+ public Spi(final Configuration.Parameters params) throws IOException {
+
+ // call in a doPrivileged
+ //
+ // we have already passed the Configuration.getInstance
+ // security check. also this class is not freely accessible
+ // (it is in the "sun" package).
+
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+ public Void run() throws IOException {
+ if (params == null) {
+ init();
+ } else {
+ if (!(params instanceof URIParameter)) {
+ throw new IllegalArgumentException
+ ("Unrecognized parameter: " + params);
+ }
+ URIParameter uriParam = (URIParameter)params;
+ url = uriParam.getURI().toURL();
+ init();
+ }
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ throw (IOException)pae.getException();
+ }
+
+ // if init() throws some other RuntimeException,
+ // let it percolate up naturally.
+ }
+
+ /**
+ * Read and initialize the entire login Configuration from the
+ * configured URL.
+ *
+ * @throws IOException if the Configuration can not be initialized
+ * @throws SecurityException if the caller does not have permission
+ * to initialize the Configuration
+ */
+ private void init() throws IOException {
+
+ boolean initialized = false;
+
+ // For policy.expandProperties, check if either a security or system
+ // property is set to false (old code erroneously checked the system
+ // prop so we must check both to preserve compatibility).
+ String expand = Security.getProperty("policy.expandProperties");
+ if (expand == null) {
+ expand = System.getProperty("policy.expandProperties");
+ }
+ if ("false".equals(expand)) {
+ expandProp = false;
+ }
+
+ // new configuration
+ Map<String, List<AppConfigurationEntry>> 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 = 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) {
+ throw ioException("Unable.to.properly.expand.config",
+ extra_config);
+ }
+
+ URL configURL = null;
+ try {
+ configURL = new URL(extra_config);
+ } catch (MalformedURLException mue) {
+ File configFile = new File(extra_config);
+ if (configFile.exists()) {
+ configURL = configFile.toURI().toURL();
+ } else {
+ throw ioException(
+ "extra.config.No.such.file.or.directory.",
+ extra_config);
+ }
+ }
+
+ 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 = 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) {
+ throw ioException("Unable.to.properly.expand.config",
+ config_url);
+ }
+ 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,
+ Map<String, List<AppConfigurationEntry>> newConfig)
+ throws IOException {
+
+ try (InputStreamReader 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"));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ public AppConfigurationEntry[] engineGetAppConfigurationEntry
+ (String applicationName) {
+
+ List<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.
+ *
+ * @throws SecurityException if the caller does not have permission
+ * to refresh the Configuration.
+ */
+ @Override
+ public synchronized void engineRefresh() {
+
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(
+ new AuthPermission("refreshLoginConfiguration"));
+ }
+
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ try {
+ init();
+ } catch (IOException ioe) {
+ throw new SecurityException(ioe.getLocalizedMessage(),
+ ioe);
+ }
+ return null;
+ }
+ });
+ }
+
+ private void readConfig(Reader reader,
+ Map<String, List<AppConfigurationEntry>> newConfig)
+ throws IOException {
+
+ linenum = 1;
+
+ if (!(reader instanceof BufferedReader)) {
+ reader = new BufferedReader(reader);
+ }
+
+ st = new StreamTokenizer(reader);
+ st.quoteChar('"');
+ st.wordChars('$', '$');
+ 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(
+ Map<String, List<AppConfigurationEntry>> newConfig)
+ throws IOException {
+
+ List<AppConfigurationEntry> configEntries = new LinkedList<>();
+
+ // application name
+ String 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
+ String moduleClass = match("module class name");
+
+ // controlFlag (required, optional, etc)
+ LoginModuleControlFlag controlFlag;
+ String sflag = match("controlFlag").toUpperCase();
+ switch (sflag) {
+ case "REQUIRED":
+ controlFlag = LoginModuleControlFlag.REQUIRED;
+ break;
+ case "REQUISITE":
+ controlFlag = LoginModuleControlFlag.REQUISITE;
+ break;
+ case "SUFFICIENT":
+ controlFlag = LoginModuleControlFlag.SUFFICIENT;
+ break;
+ case "OPTIONAL":
+ controlFlag = LoginModuleControlFlag.OPTIONAL;
+ break;
+ default:
+ throw ioException(
+ "Configuration.Error.Invalid.control.flag.flag",
+ sflag);
+ }
+
+ // get the args
+ Map<String, String> options = new HashMap<>();
+ while (peek(";") == false) {
+ String key = match("option key");
+ match("=");
+ try {
+ options.put(key, expand(match("option value")));
+ } catch (PropertyExpander.ExpandException peee) {
+ throw new IOException(peee.getLocalizedMessage());
+ }
+ }
+
+ lookahead = nextToken();
+
+ // create the new element
+ if (debugParser != null) {
+ debugParser.println("\t\t" + moduleClass + ", " + sflag);
+ for (String key : options.keySet()) {
+ debugParser.println("\t\t\t" + key +
+ "=" + options.get(key));
+ }
+ }
+ configEntries.add(new AppConfigurationEntry(moduleClass,
+ controlFlag,
+ options));
+ }
+
+ match("}");
+ match(";");
+
+ // add this configuration entry
+ if (newConfig.containsKey(appName)) {
+ throw ioException(
+ "Configuration.Error.Can.not.specify.multiple.entries.for.appName",
+ appName);
+ }
+ newConfig.put(appName, configEntries);
+ }
+
+ private String match(String expect) throws IOException {
+
+ String value = null;
+
+ switch(lookahead) {
+ case StreamTokenizer.TT_EOF:
+ throw ioException(
+ "Configuration.Error.expected.expect.read.end.of.file.",
+ expect);
+
+ 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 {
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.found.value.",
+ new Integer(linenum), expect, st.sval);
+ }
+ break;
+
+ case '{':
+ if (expect.equalsIgnoreCase("{")) {
+ lookahead = nextToken();
+ } else {
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.",
+ new Integer(linenum), expect, st.sval);
+ }
+ break;
+
+ case ';':
+ if (expect.equalsIgnoreCase(";")) {
+ lookahead = nextToken();
+ } else {
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.",
+ new Integer(linenum), expect, st.sval);
+ }
+ break;
+
+ case '}':
+ if (expect.equalsIgnoreCase("}")) {
+ lookahead = nextToken();
+ } else {
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.",
+ new Integer(linenum), expect, st.sval);
+ }
+ break;
+
+ case '=':
+ if (expect.equalsIgnoreCase("=")) {
+ lookahead = nextToken();
+ } else {
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.",
+ new Integer(linenum), expect, st.sval);
+ }
+ break;
+
+ default:
+ throw ioException(
+ "Configuration.Error.Line.line.expected.expect.found.value.",
+ new Integer(linenum), expect, st.sval);
+ }
+ return value;
+ }
+
+ private boolean peek(String expect) {
+ switch (lookahead) {
+ case ',':
+ return expect.equalsIgnoreCase(",");
+ case ';':
+ return expect.equalsIgnoreCase(";");
+ case '{':
+ return expect.equalsIgnoreCase("{");
+ case '}':
+ return expect.equalsIgnoreCase("}");
+ default:
+ return false;
+ }
+ }
+
+ private int nextToken() throws IOException {
+ int tok;
+ while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) {
+ linenum++;
+ }
+ return tok;
+ }
+
+ 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 (value.isEmpty()) {
+ return value;
+ }
+
+ if (!expandProp) {
+ return value;
+ }
+ String s = PropertyExpander.expand(value);
+ if (s == null || s.length() == 0) {
+ throw ioException(
+ "Configuration.Error.Line.line.system.property.value.expanded.to.empty.value",
+ new Integer(linenum), value);
+ }
+ return s;
+ }
+
+ private IOException ioException(String resourceKey, Object... args) {
+ MessageFormat form = new MessageFormat(ResourcesMgr.getString
+ (resourceKey, "sun.security.util.AuthResources"));
+ return new IOException(form.format(args));
+ }
+ }
+}