| /* |
| * Copyright 2000-2006 Sun Microsystems, Inc. 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. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.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; |
| } |
| } |
| } |