hello there, this patch contains an implementation of the JAAS Login Module Configuration provider.
cheers; rsn
Index: ChangeLog
===================================================================
RCS file: /cvsroot/classpath/classpath/ChangeLog,v
retrieving revision 1.6064
diff -u -r1.6064 ChangeLog
--- ChangeLog 14 Jan 2006 11:14:43 -0000 1.6064
+++ ChangeLog 14 Jan 2006 20:03:38 -0000
@@ -1,3 +1,16 @@
+2006-01-15 Raif S. Naffah <[EMAIL PROTECTED]>
+
+ * gnu/javax/security/auth/login/ConfigFileTokenizer.java: New class.
+ * gnu/javax/security/auth/login/ConfigFileParser.java: New class.
+ * gnu/javax/security/auth/login/GnuConfiguration.java: New class.
+ * javax/security/auth/login/AppConfigurationEntry.java: Updated
+ copyright year.
+ (toString): Added method implementation.
+ (LoginModuleControlFlag.toString): Removed class name from result.
+ * javax/security/auth/login/Configuration.java: Updated copyright year.
+ Added documentation.
+ (getConfig(): replaced calls to NullConfiguration with GnuConfiguration.
+
2006-01-14 Wolfgang Baer <[EMAIL PROTECTED]>
Fixes bug #25387
Index: ConfigFileParser.java
===================================================================
RCS file: ConfigFileParser.java
diff -N ConfigFileParser.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ConfigFileParser.java 14 Jan 2006 20:01:19 -0000
@@ -0,0 +1,339 @@
+/* ConfigurationParser.java -- JAAS Login Configuration default syntax parser
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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 for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.security.auth.login;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.login.AppConfigurationEntry;
+
+/**
+ * A parser that knows how to interpret JAAS Login Module Configuration files
+ * written in the <i>default syntax</i> which is interpreted as adhering to
+ * the following grammar:
+ *
+ * <pre>
+ * CONFIG ::= APP_OR_OTHER_ENTRY+
+ * APP_OR_OTHER_ENTRY ::= APP_NAME_OR_OTHER JAAS_CONFIG_BLOCK
+ * APP_NAME_OR_OTHER ::= APP_NAME
+ * | 'other'
+ * JAAS_CONFIG_BLOCK ::= '{' (LOGIN_MODULE_ENTRY ';')+ '}' ';'
+ * LOGIN_MODULE_ENTRY ::= MODULE_CLASS FLAG MODULE_OPTION* ';'
+ * FLAG ::= 'required'
+ * | 'requisite'
+ * | 'sufficient'
+ * | 'optional'
+ * MODULE_OPTION ::= PARAM_NAME '=' PARAM_VALUE
+ *
+ * APP_NAME ::= JAVA_IDENTIFIER
+ * MODULE_CLASS ::= JAVA_IDENTIFIER ('.' JAVA_IDENTIFIER)*
+ * PARAM_NAME ::= STRING
+ * PARAM_VALUE ::= '"' STRING '"' | ''' STRING '''
+ * </pre>
+ *
+ * <p>This parser handles UTF-8 entities when used as APP_NAME and PARAM_VALUE.
+ * It also checks for the use of Java identifiers used in MODULE_CLASS, thus
+ * minimizing the risks of having [EMAIL PROTECTED] java.lang.ClassCastException}s thrown
+ * at runtime due to syntactically invalid names.</p>
+ *
+ * <p>In the above context, a JAVA_IDENTIFIER is a sequence of tokens,
+ * separated by the character '.'. Each of these tokens obeys the following:</p>
+ *
+ * <ol>
+ * <li>its first character yields <code>true</code> when used as an input to
+ * the [EMAIL PROTECTED] java.lang.Character#isJavaIdentifierStart(char)}, and</li>
+ * <li>all remaining characters, yield <code>true</code> when used as an
+ * input to [EMAIL PROTECTED] java.lang.Character#isJavaIdentifierPart(char)}.</li>
+ * </ol>
+ */
+public final class ConfigFileParser
+{
+ // Constants and fields
+ // --------------------------------------------------------------------------
+
+ private static final boolean DEBUG = false;
+ private static final void debug(String s) {if (DEBUG) System.out.println(s);};
+
+ private ConfigFileTokenizer cft;
+ private Map map = new HashMap();
+
+ // Constructor(s)
+ // --------------------------------------------------------------------------
+
+ // default 0-arguments constructor
+
+ // Class methods
+ // --------------------------------------------------------------------------
+
+ // Instance methods
+ // --------------------------------------------------------------------------
+
+ /**
+ * Returns the parser result as a [EMAIL PROTECTED] Map} where the keys are application
+ * names, and the entries are [EMAIL PROTECTED] List}s of [EMAIL PROTECTED] AppConfigurationEntry}
+ * entries, one for each login module entry, in the order they were
+ * encountered, for that application name in the just parsed configuration
+ * file.
+ */
+ public Map getLoginModulesMap()
+ {
+ return map;
+ }
+
+ /**
+ * Parses the [EMAIL PROTECTED] Reader}'s contents assuming it is in the <i>default
+ * syntax</i>.
+ * @param r the [EMAIL PROTECTED] Reader} whose contents are assumed to be a JAAS Login
+ * Configuration Module file written in the <i>default syntax</i>.
+ * @throws IOException if an exception occurs while parsing the input.
+ */
+ public void parse(Reader r) throws IOException
+ {
+ initParser(r);
+
+ while (parseAppOrOtherEntry())
+ ; // do nothing
+ }
+
+ private void initParser(Reader r) throws IOException
+ {
+ map.clear();
+
+ cft = new ConfigFileTokenizer(r);
+ }
+
+ /**
+ * @return <code>true</code> if an APP_OR_OTHER_ENTRY was correctly parsed.
+ * Returns <code>false</code> otherwise.
+ * @throws IOException if an exception occurs while parsing the input.
+ */
+ private boolean parseAppOrOtherEntry() throws IOException
+ {
+ int c = cft.nextToken();
+ if (c == ConfigFileTokenizer.TT_EOF)
+ return false;
+
+ if (c != ConfigFileTokenizer.TT_WORD)
+ {
+ cft.pushBack();
+ return false;
+ }
+
+ String appName = cft.sval;
+ debug("DEBUG: APP_NAME_OR_OTHER: " + appName);
+ if (cft.nextToken() != '{')
+ abort("Missing '{' after APP_NAME_OR_OTHER");
+
+ List lmis = new ArrayList();
+ while (parseACE(lmis))
+ ; // do nothing
+
+ c = cft.nextToken();
+ if (c != '}')
+ abort("Was expecting '}' but found " + (char) c);
+
+ c = cft.nextToken();
+ if (c != ';')
+ abort("Was expecting ';' but found " + (char) c);
+
+ List listOfACEs = (List) map.get(appName);
+ if (listOfACEs == null)
+ {
+ listOfACEs = new ArrayList();
+ map.put(appName, listOfACEs);
+ }
+ listOfACEs.addAll(lmis);
+ return !appName.equalsIgnoreCase("other");
+ }
+
+ /**
+ * @return <code>true</code> if a LOGIN_MODULE_ENTRY was correctly parsed.
+ * Returns <code>false</code> otherwise.
+ * @throws IOException if an exception occurs while parsing the input.
+ */
+ private boolean parseACE(List listOfACEs) throws IOException
+ {
+ int c = cft.nextToken();
+ if (c != ConfigFileTokenizer.TT_WORD)
+ {
+ cft.pushBack();
+ return false;
+ }
+
+ String clazz = validateClassName(cft.sval);
+ debug("DEBUG: MODULE_CLASS: " + clazz);
+
+ if (cft.nextToken() != ConfigFileTokenizer.TT_WORD)
+ abort("Was expecting FLAG but found none");
+
+ String flag = cft.sval;
+ debug("DEBUG: FLAG: " + flag);
+ AppConfigurationEntry.LoginModuleControlFlag f = null;
+ if (flag.equalsIgnoreCase("required"))
+ f = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED;
+ else if (flag.equalsIgnoreCase("requisite"))
+ f = AppConfigurationEntry.LoginModuleControlFlag.REQUISITE;
+ else if (flag.equalsIgnoreCase("sufficient"))
+ f = AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT;
+ else if (flag.equalsIgnoreCase("optional"))
+ f = AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL;
+ else
+ abort("Unknown Flag: " + flag);
+
+ Map options = new HashMap();
+ String paramName, paramValue;
+ c = cft.nextToken();
+ while (c != ';')
+ {
+ if (c != ConfigFileTokenizer.TT_WORD)
+ abort("Was expecting PARAM_NAME but got '" + ((char) c) + "'");
+
+ paramName = cft.sval;
+ debug("DEBUG: PARAM_NAME: " + paramName);
+ if (cft.nextToken() != '=')
+ abort("Missing '=' after PARAM_NAME");
+
+ c = cft.nextToken();
+ if (c != '"' && c != '\'')
+ abort("Was expecting PARAM_VALUE, as a quoted string, but got none");
+
+ paramValue = expandParamValue(cft.sval);
+ debug("DEBUG: PARAM_VALUE: " + paramValue);
+ options.put(paramName, paramValue);
+
+ c = cft.nextToken();
+ }
+
+ AppConfigurationEntry ace = new AppConfigurationEntry(clazz, f, options);
+ debug("DEBUG: LOGIN_MODULE_ENTRY: " + ace);
+ listOfACEs.add(ace);
+ return true;
+ }
+
+ private void abort(String m) throws IOException
+ {
+ debug("ERROR: " + m);
+ debug("DEBUG: Map (so far): " + String.valueOf(map));
+ throw new IOException(m);
+ }
+
+ private String validateClassName(String cn) throws IOException
+ {
+ if (cn.startsWith(".") || cn.endsWith("."))
+ abort("MODULE_CLASS MUST NOT start or end with a '.'");
+
+ String[] tokens = cn.split(".");
+ for (int i = 0; i < tokens.length; i++)
+ {
+ String t = tokens[i];
+ if (Character.isJavaIdentifierStart(cn.toCharArray()[0]))
+ abort("");
+
+ // we dont check the rest of the characters for isJavaIdentifierPart()
+ // because that's what the tokenizer does.
+ }
+
+ return cn;
+ }
+
+ /**
+ * The documentation of the [EMAIL PROTECTED] javax.security.auth.login.Configuration}
+ * states that: <i>"...If a String in the form, ${system.property}, occurs in
+ * the value, it will be expanded to the value of the system property."</i>.
+ * This method ensures this is the case. If such a string can not be expanded
+ * then it is left AS IS, assuming the LoginModule knows what to do with it.
+ *
+ * <p><b>IMPORTANT</b>: This implementation DOES NOT handle embedded ${}
+ * constructs.
+ *
+ * @param s the raw parameter value, incl. eventually strings of the form
+ * <code>${system.property}</code>.
+ * @return the input string with every occurence of
+ * <code>${system.property}</code> replaced with the value of the
+ * corresponding System property at the time of this method invocation. If
+ * the string is not a known System property name, then the complete sequence
+ * (incl. the ${} characters are passed AS IS.
+ */
+ private String expandParamValue(String s)
+ {
+ String result = s;
+ try
+ {
+ int searchNdx = 0;
+ // FIXME: this logic barfs on the first non-expandable system property
+ // although others after it may be expanded correctly.
+ while (searchNdx < result.length())
+ {
+ int i = s.indexOf("${", searchNdx);
+ if (i == -1)
+ break;
+
+ int j = s.indexOf("}", i + 2);
+ if (j == -1)
+ {
+ debug(" WARN: Found a ${ prefix with no } suffix. Ignore");
+ break;
+ }
+
+ String sysPropName = s.substring(i + 2, j);
+ debug("DEBUG: Found a reference to System property: " + sysPropName);
+ String sysPropValue = System.getProperty(sysPropName);
+ debug("DEBUG: Resolved " + sysPropName + " to '" + sysPropValue + "'");
+
+ if (sysPropValue != null)
+ {
+ result = s.substring(0, i) + sysPropValue + s.substring(j + 1);
+ searchNdx = i + sysPropValue.length();
+ }
+ else
+ searchNdx = j + 1;
+ }
+ }
+ catch (Exception x)
+ {
+ debug("ERROR: Exception while expanding " + s + ". Ignore: " + x);
+ }
+
+ return result;
+ }
+}
Index: Configuration.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/security/auth/login/Configuration.java,v
retrieving revision 1.4
diff -u -r1.4 Configuration.java
--- Configuration.java 9 Sep 2005 12:17:30 -0000 1.4
+++ Configuration.java 14 Jan 2006 20:03:16 -0000
@@ -1,5 +1,5 @@
/* Configuration.java
- Copyright (C) 2004 Free Software Foundation, Inc.
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -38,15 +38,140 @@
package javax.security.auth.login;
+import gnu.javax.security.auth.login.GnuConfiguration;
+
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Security;
import javax.security.auth.AuthPermission;
+/**
+ * This is an abstract class for representing the configuration of
+ * [EMAIL PROTECTED] javax.security.auth.spi.LoginModule}s under an application. The
+ * <code>Configuration</code> specifies which LoginModules should be used for a
+ * particular application, and in what order the LoginModules should be invoked.
+ * This abstract class, in GNU Classpath, is subclassed by
+ * [EMAIL PROTECTED] gnu.javax.security.auth.login.GnuConfiguration} to provide an
+ * implementation which reads and loads the actual Configuration.
+ *
+ * <p>A login configuration contains the following information. Note that this
+ * example only represents the <i>default syntax</i> for the Configuration.
+ * Subclass implementations of this class may implement alternative syntaxes and
+ * may retrieve the Configuration from any source such as files, databases, or
+ * servers.</p>
+ *
+ * <pre>
+ * Name {
+ * ModuleClass Flag ModuleOptions;
+ * ModuleClass Flag ModuleOptions;
+ * ModuleClass Flag ModuleOptions;
+ * };
+ * Name {
+ * ModuleClass Flag ModuleOptions;
+ * ModuleClass Flag ModuleOptions;
+ * };
+ * other {
+ * ModuleClass Flag ModuleOptions;
+ * ModuleClass Flag ModuleOptions;
+ * };
+ * </pre>
+ *
+ * <p>Each entry in the Configuration is indexed via an application name,
+ * <i>Name</i>, and contains a list of LoginModules configured for that
+ * application. Each LoginModule is specified via its fully qualified class
+ * name. Authentication proceeds down the module list in the exact order
+ * specified. If an application does not have specific entry, it defaults to the
+ * specific entry for <i>"other"</i>.</p>
+ *
+ * <p>The <i>Flag</i> value controls the overall behavior as authentication
+ * proceeds down the stack. The following represents a description of the valid
+ * values for <i>Flag</i> and their respective semantics:</p>
+ *
+ * <pre>
+ * 1) Required - The LoginModule is required to succeed.
+ * If it succeeds or fails, authentication still continues
+ * to proceed down the LoginModule list.
+ *
+ * 2) Requisite - The LoginModule is required to succeed.
+ * If it succeeds, authentication continues down the
+ * LoginModule list. If it fails,
+ * control immediately returns to the application
+ * (authentication does not proceed down the
+ * LoginModule list).
+ *
+ * 3) Sufficient - The LoginModule is not required to
+ * succeed. If it does succeed, control immediately
+ * returns to the application (authentication does not
+ * proceed down the LoginModule list).
+ * If it fails, authentication continues down the
+ * LoginModule list.
+ *
+ * 4) Optional - The LoginModule is not required to
+ * succeed. If it succeeds or fails,
+ * authentication still continues to proceed down the
+ * LoginModule list.
+ * </pre>
+ *
+ * <p>The overall authentication succeeds only if all <i>Required</i> and
+ * <i>Requisite</i> LoginModules succeed. If a <i>Sufficient</i> LoginModule is
+ * configured and succeeds, then only the <i>Required</i> and <i>Requisite</i>
+ * LoginModules prior to that <i>Sufficient</i> LoginModule need to have
+ * succeeded for the overall authentication to succeed. If no <i>Required</i> or
+ * <i>Requisite</i> LoginModules are configured for an application, then at
+ * least one <i>Sufficient</i> or <i>Optional</i> LoginModule must succeed.</p>
+ *
+ * <p><i>ModuleOptions</i> is a space separated list of LoginModule-specific
+ * values which are passed directly to the underlying LoginModules. Options are
+ * defined by the LoginModule itself, and control the behavior within it. For
+ * example, a LoginModule may define options to support debugging/testing
+ * capabilities. The correct way to specify options in the Configuration is by
+ * using the following key-value pairing: debug="true". The key and value should
+ * be separated by an 'equals' symbol, and the value should be surrounded by
+ * double quotes. If a String in the form, ${system.property}, occurs in the
+ * value, it will be expanded to the value of the system property. Note that
+ * there is no limit to the number of options a LoginModule may define.</p>
+ *
+ * <p>The following represents an example Configuration entry based on the
+ * syntax above:</p>
+ *
+ * <pre>
+ * Login {
+ * com.sun.security.auth.module.UnixLoginModule required;
+ * com.sun.security.auth.module.Krb5LoginModule optional
+ * useTicketCache="true"
+ * ticketCache="${user.home}${/}tickets";
+ * };
+ * </pre>
+ *
+ * <p>This Configuration specifies that an application named, "Login", requires
+ * users to first authenticate to the
+ * <code>com.sun.security.auth.module.UnixLoginModule</code>, which is required
+ * to succeed. Even if the <code>UnixLoginModule</code> authentication fails,
+ * the <code>com.sun.security.auth.module.Krb5LoginModule</code> still gets
+ * invoked. This helps hide the source of failure. Since the
+ * <code>Krb5LoginModule</code> is <i>Optional</i>, the overall authentication
+ * succeeds only if the <code>UnixLoginModule</code> (<i>Required</i>) succeeds.
+ * </p>
+ *
+ * <p>Also note that the LoginModule-specific options, <i>useTicketCache="true"</i>
+ * and <i>ticketCache=${user.home}${/}tickets"</i>, are passed to the
+ * <code>Krb5LoginModule</code>. These options instruct the
+ * <code>Krb5LoginModule</code> to use the ticket cache at the specified
+ * location. The system properties, <code>user.home</code> and /
+ * (<code>file.separator</code>), are expanded to their respective values.</p>
+ *
+ * <p>The default Configuration implementation can be changed by setting the
+ * value of the "<i>login.configuration.provider</i>" security property (in the
+ * Java security properties file) to the fully qualified name of the desired
+ * Configuration implementation class. The Java security properties file is
+ * located in the file named <JAVA_HOME>/lib/security/java.security, where
+ * <JAVA_HOME> refers to the directory where the JDK was installed.</p>
+ *
+ * @see LoginContext
+ */
public abstract class Configuration
{
-
// Fields.
// -------------------------------------------------------------------------
@@ -81,8 +206,29 @@
// Abstract methods.
// -------------------------------------------------------------------------
+ /**
+ * Retrieves the [EMAIL PROTECTED] AppConfigurationEntry} instances (as an array) for
+ * the designated <i>Application</i> name.
+ *
+ * @param applicationName usually the name of an <i>Application</i> used by a
+ * <code>Configuration</code> to index the entries.
+ * @return an array of [EMAIL PROTECTED] AppConfigurationEntry} instances for the
+ * specified <i>Application</i> name. Returns <code>null</code> if there are
+ * no entries for the specified <code>applicationName</code>.
+ */
public abstract AppConfigurationEntry[] getAppConfigurationEntry (String applicationName);
+ /**
+ * Refreshes and reloads the <code>Configuration</code>.
+ *
+ * <p>This method causes this <code>Configuration</code> object to
+ * refresh/reload its contents in an implementation-dependent manner. For
+ * example, if this <code>Configuration</code> object stores its entries in a
+ * file, calling [EMAIL PROTECTED] #refresh()} may cause the file to be re-read.</p>
+ *
+ * @throws SecurityException if the caller does not have permission to refresh
+ * its <code>Configuration</code>.
+ */
public abstract void refresh();
// Package-private methods.
@@ -108,11 +254,11 @@
if (conf != null)
config = (Configuration) Class.forName (conf).newInstance();
else
- config = new NullConfiguration();
+ config = new GnuConfiguration();
}
catch (Exception x)
{
- config = new NullConfiguration();
+ config = new GnuConfiguration();
}
}
return config;
Index: AppConfigurationEntry.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/security/auth/login/AppConfigurationEntry.java,v
retrieving revision 1.2
diff -u -r1.2 AppConfigurationEntry.java
--- AppConfigurationEntry.java 2 Jul 2005 20:32:46 -0000 1.2
+++ AppConfigurationEntry.java 14 Jan 2006 20:03:00 -0000
@@ -1,5 +1,5 @@
/* AppConfigurationEntry.java
- Copyright (C) 2004 Free Software Foundation, Inc.
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -44,7 +44,6 @@
public class AppConfigurationEntry
{
-
// Fields.
// -------------------------------------------------------------------------
@@ -61,13 +60,16 @@
{
if (loginModuleName == null || loginModuleName.length() == 0)
throw new IllegalArgumentException ("module name cannot be null nor empty");
+
if (LoginModuleControlFlag.OPTIONAL != controlFlag &&
LoginModuleControlFlag.REQUIRED != controlFlag &&
LoginModuleControlFlag.REQUISITE != controlFlag &&
LoginModuleControlFlag.SUFFICIENT != controlFlag)
throw new IllegalArgumentException ("invalid controlFlag");
+
if (options == null)
throw new IllegalArgumentException ("options cannot be null");
+
this.loginModuleName = loginModuleName;
this.controlFlag = controlFlag;
this.options = Collections.unmodifiableMap (new HashMap (options));
@@ -91,7 +93,17 @@
return options;
}
-// Inner class.
+ // Object methods ----------------------------------------------------------
+
+ public String toString()
+ {
+
+ return loginModuleName + "\t"
+ + String.valueOf(controlFlag) + "\t"
+ + String.valueOf(options);
+ }
+
+ // Inner class.
// -------------------------------------------------------------------------
public static class LoginModuleControlFlag
@@ -117,19 +129,15 @@
public String toString()
{
- StringBuffer buf = new StringBuffer (LoginModuleControlFlag.class.getName());
- buf.append ('.');
- if (this == OPTIONAL)
- buf.append ("OPTIONAL");
- else if (this == REQUIRED)
- buf.append ("REQUIRED");
- else if (this == REQUISITE)
- buf.append ("REQUISITE");
- else if (this == SUFFICIENT)
- buf.append ("SUFFICIENT");
- else
- buf.append ("HARVEY_THE_RABBIT");
- return buf.toString();
+ if (this == LoginModuleControlFlag.REQUIRED)
+ return "REQUIRED";
+ if (this == LoginModuleControlFlag.REQUISITE)
+ return "REQUISITE";
+ if (this == LoginModuleControlFlag.SUFFICIENT)
+ return "SUFFICIENT";
+ if (this == LoginModuleControlFlag.OPTIONAL)
+ return "OPTIONAL";
+ return "???";
}
}
}
Index: GnuConfiguration.java
===================================================================
RCS file: GnuConfiguration.java
diff -N GnuConfiguration.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ GnuConfiguration.java 14 Jan 2006 20:02:41 -0000
@@ -0,0 +1,452 @@
+/* GnuConfiguration.java -- GNU Classpath implementation of JAAS Configuration
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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 for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.security.auth.login;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Security;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.AuthPermission;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+/**
+ * An implementation of the [EMAIL PROTECTED] Configuration} class which interprets JAAS
+ * Login Configuration files written in the <i>default</i> syntax described in
+ * the publicly available documentation of that class. A more formal definition
+ * of this syntax is as follows:
+ *
+ * <pre>
+ * CONFIG ::= APP_OR_OTHER_ENTRY+
+ * APP_OR_OTHER_ENTRY ::= APP_NAME_OR_OTHER JAAS_CONFIG_BLOCK
+ * APP_NAME_OR_OTHER ::= APP_NAME
+ * | 'other'
+ * JAAS_CONFIG_BLOCK ::= '{' (LOGIN_MODULE_ENTRY ';')+ '}' ';'
+ * LOGIN_MODULE_ENTRY ::= MODULE_CLASS FLAG MODULE_OPTION* ';'
+ * FLAG ::= 'required'
+ * | 'requisite'
+ * | 'sufficient'
+ * | 'optional'
+ * MODULE_OPTION ::= PARAM_NAME '=' PARAM_VALUE
+ *
+ * APP_NAME ::= JAVA_IDENTIFIER
+ * MODULE_CLASS ::= JAVA_IDENTIFIER ('.' JAVA_IDENTIFIER)*
+ * PARAM_NAME ::= STRING
+ * PARAM_VALUE ::= '"' STRING '"' | ''' STRING '''
+ * </pre>
+ *
+ * <p>This implementation will specifically attempt to process one or more
+ * Login Configuration files in the following locations, and when found parse
+ * them and merge their contents. The locations, and the order in which they are
+ * investigated, follows:</p>
+ *
+ * <ol>
+ * <li>If the following Security properties:
+ * <i>java.security.auth.login.config.url.<b>N</b></i>, where <i><b>N</b></i>
+ * is a digit, from <code>1</code> to an arbitrary number, are defined, then
+ * the value of each of those properties will be considered as a JAAS Login
+ * Configuration file written in the default syntax. This implementation will
+ * attempt parsing all such files.
+ *
+ * <p>It is worth noting the following:
+ * <ul>
+ * <li>The GNU Classpath security file, named <i>classpath.security</i>,
+ * where all Security properties are encoded, is usually located in
+ * <i>/usr/local/classpath/lib/security</i> folder.</li>
+ *
+ * <li>The numbers used in the properties
+ * <i>java.security.auth.login.config.url.<b>N</b></i> MUST be sequential,
+ * with no breaks in-between.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>If at least one of the designated Configuration files was found, and
+ * was parsed correctly, then no other location will be inspected.</p></li>
+ *
+ * <li>If the System property named <i>java.security.auth.login.config</i>
+ * is not null or empty, its contents are then interpreted as a URL to a
+ * JAAS Login Configuration file written in the default syntax.
+ *
+ * <p>If this System property is defined, and the file it refers to was
+ * parsed correctly, then no other location will be inspected.</p></li>
+ *
+ * <li>If a file named <i>.java.login.config</i> or <i>java.login.config</i>
+ * (in that order) is found in the location referenced by the value of the
+ * System property <i>user.home</i>, then that file is parsed as a JAAS Login
+ * Configuration written in the default syntax.</li>
+ *
+ * <li>If none of the above resulted in a correctly parsed JAAS Login
+ * Configuration file, then this implementation will install a <i>Null
+ * Configuration</i> which basically does not recognize any Application.</li>
+ * </ol>
+ */
+public final class GnuConfiguration extends Configuration
+{
+ // Constants and fields
+ // --------------------------------------------------------------------------
+
+ private static final boolean DEBUG = true;
+ private static final void debug(String s) {if (DEBUG) System.out.println(s);};
+
+ /**
+ * The internal map of login modules keyed by application name. Each entry in
+ * this map is a [EMAIL PROTECTED] List} of [EMAIL PROTECTED] AppConfigurationEntry}s for that
+ * application name.
+ */
+ private Map loginModulesMap;
+ /** Our reference to our default syntax parser. */
+ private ConfigFileParser cp;
+
+ // Constructor(s)
+ // --------------------------------------------------------------------------
+
+ /** Trivial 0-arguments Constructor. */
+ public GnuConfiguration()
+ {
+ super();
+
+ loginModulesMap = new HashMap();
+ cp = new ConfigFileParser();
+ init();
+ }
+
+ // Class methods
+ // --------------------------------------------------------------------------
+
+ // Instance methods
+ // --------------------------------------------------------------------------
+
+ // Configuration abstract methods implementation ----------------------------
+
+ /* (non-Javadoc)
+ * @see javax.security.auth.login.Configuration#getAppConfigurationEntry(java.lang.String)
+ */
+ public AppConfigurationEntry[] getAppConfigurationEntry(String appName)
+ {
+ if (appName == null)
+ return null;
+
+ appName = appName.trim();
+ if (appName.length() == 0)
+ return null;
+
+ List loginModules = (List) loginModulesMap.get(appName);
+ if (loginModules == null || loginModules.size() == 0)
+ return null;
+
+ debug("DEBUG: " + appName + " -> " + loginModules.size() + " entry(ies)");
+ return (AppConfigurationEntry[]) loginModules.toArray(new AppConfigurationEntry[0]);
+ }
+
+ /**
+ * Refreshes and reloads this <code>Configuration</code>.
+ *
+ * <p>This method causes this <code>Configuration</code> object to refresh /
+ * reload its contents following the locations and logic described above in
+ * the class documentation section.</p>
+ *
+ * @throws SecurityException if the caller does not have an
+ * [EMAIL PROTECTED] AuthPermission} for the action named
+ * <code>refreshLoginConfiguration</code>.
+ * @see [EMAIL PROTECTED] AuthPermission}
+ */
+ public void refresh()
+ {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AuthPermission("refreshLoginConfiguration"));
+
+ loginModulesMap.clear();
+ init();
+ }
+
+ // helper methods -----------------------------------------------------------
+
+ /**
+ * Attempts to find and parse JAAS Login Configuration file(s) written in
+ * the default syntax. The locations searched are as descibed in the class
+ * documentation.
+ */
+ private void init()
+ {
+ if (processSecurityProperties())
+ debug(" INFO: Using login configuration defined by Security property(ies)");
+ else if (processSystemProperty())
+ debug(" INFO: Using login configuration defined by System property");
+ else if (processUserHome())
+ debug(" INFO: Using login configuration defined in ${user.home}");
+ else
+ debug(" WARN: No login configuration file found");
+ }
+
+ /**
+ * Attempts to locate and parse one or more JAAS Login Configuration files
+ * defined as the values of the Security properties
+ * <i>java.security.auth.login.config.url.N</i>.
+ *
+ * @return <code>true</code> if it succeeds, and <code>false</code>
+ * otherwsie.
+ */
+ private boolean processSecurityProperties()
+ {
+ boolean result = false;
+ int counter = 0;
+ String s;
+ while (true)
+ {
+ counter++;
+ s = Security.getProperty("java.security.auth.login.config.url."
+ + counter);
+ if (s == null)
+ break;
+
+ s = s.trim();
+ if (s.length() != 0)
+ {
+ debug(" INFO: java.security.auth.login.config.url." + counter
+ + " = " + s);
+ try
+ {
+ parseConfig(getInputStreamFromURL(s));
+ result = true;
+ }
+ catch (Throwable t)
+ {
+ debug(" WARN: Exception while handling Security property at #"
+ + counter + ". Continue: " + t);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Attempts to open a designated string as a well-formed [EMAIL PROTECTED] URL}. If a
+ * [EMAIL PROTECTED] MalformedURLException} occurs, this method then tries to open that
+ * string as a [EMAIL PROTECTED] File} (with the same name). If it succeeds, an
+ * [EMAIL PROTECTED] InputStream} is constructed and returned.
+ *
+ * @param s
+ * the designated name of either a [EMAIL PROTECTED] URL} or a [EMAIL PROTECTED] File}
+ * assumed to contain a JAAS Login Configuration in the default
+ * syntax.
+ * @return an [EMAIL PROTECTED] InputStream} around the data source.
+ * @throws IOException
+ * if an exception occurs during the operation.
+ */
+ private InputStream getInputStreamFromURL(String s) throws IOException
+ {
+ InputStream result = null;
+ try
+ {
+ URL url = new URL(s);
+ result = url.openStream();
+ }
+ catch (MalformedURLException x)
+ {
+ debug(" WARN: Failed opening as URL: " + s + ". Will try as File");
+ result = new FileInputStream(s);
+ }
+ return result;
+ }
+
+ /**
+ * Attempts to locate and parse a JAAS Login Configuration file defined as the
+ * value of the System property <i>java.security.auth.login.config</i>.
+ *
+ * @return <code>true</code> if it succeeds, and <code>false</code>
+ * otherwsie.
+ */
+ private boolean processSystemProperty()
+ {
+ boolean result = false;
+ try
+ {
+ String s = System.getProperty("java.security.auth.login.config");
+ if (s != null)
+ {
+ s = s.trim();
+ if (s.length() != 0)
+ {
+ debug(" INFO: java.security.auth.login.config = " + s);
+ parseConfig(getInputStreamFromURL(s));
+ result = true;
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ debug(" WARN: Exception while handling System property. Continue: " + t);
+ }
+ return result;
+ }
+
+ /**
+ * Attempts to locate and parse a JAAS Login Configuration file named either
+ * as <i>.java.login.config</i> or <i>java.login.config</i> (without the
+ * leading dot) in the folder referenced by the System property
+ * <code>user.home</code>.
+ *
+ * @return <code>true</code> if it succeeds, and <code>false</code>
+ * otherwsie.
+ */
+ private boolean processUserHome()
+ {
+ boolean result = false;
+ try
+ {
+ File userHome = getUserHome();
+ if (userHome == null)
+ return result;
+
+ File jaasFile;
+ jaasFile = getConfigFromUserHome(userHome, ".java.login.config");
+ if (jaasFile == null)
+ jaasFile = getConfigFromUserHome(userHome, "java.login.config");
+
+ if (jaasFile == null)
+ {
+ debug(" INFO: Login Configuration file, in " + userHome
+ + ", does not exist or is inaccessible");
+ return result;
+ }
+
+ FileInputStream fis = new FileInputStream(jaasFile);
+ parseConfig(fis);
+ result = true;
+ }
+ catch (Throwable t)
+ {
+ debug(" WARN: Exception while handling ${user.home}: " + t);
+ }
+ return result;
+ }
+
+ private void parseConfig(InputStream configStream) throws IOException
+ {
+ cp.parse(new InputStreamReader(configStream, "UTF-8"));
+ Map loginModulesMap = cp.getLoginModulesMap();
+ mergeLoginModules(loginModulesMap);
+ }
+
+ private void mergeLoginModules(Map otherLoginModules)
+ {
+ if (otherLoginModules == null || otherLoginModules.size() < 1)
+ return;
+
+ for (Iterator it = otherLoginModules.keySet().iterator(); it.hasNext();)
+ {
+ String appName = (String) it.next();
+ List thatListOfACEs = (List) otherLoginModules.get(appName);
+ if (thatListOfACEs == null || thatListOfACEs.size() < 1)
+ continue;
+
+ List thisListsOfACEs = (List) loginModulesMap.get(appName);
+ if (thisListsOfACEs == null)
+ loginModulesMap.put(appName, thatListOfACEs);
+ else
+ thisListsOfACEs.addAll(thatListOfACEs);
+ }
+ }
+
+ private File getUserHome()
+ {
+ String uh = System.getProperty("user.home");
+ if (uh == null || uh.trim().length() == 0)
+ {
+ debug(" INFO: User home path is not set or is empty");
+ return null;
+ }
+
+ uh = uh.trim();
+ File result = new File(uh);
+ if (!result.exists())
+ {
+ debug("ERROR: User home '" + uh + "' does not exist");
+ return null;
+ }
+
+ if (!result.isDirectory())
+ {
+ debug("ERROR: User home '" + uh + "' is not a directory");
+ return null;
+ }
+
+ if (!result.canRead())
+ {
+ debug("ERROR: User home '" + uh + "' is not readable");
+ return null;
+ }
+
+ return result;
+ }
+
+ private File getConfigFromUserHome(File userHome, String fileName)
+ {
+ File result = new File(userHome, fileName);
+ if (!result.exists())
+ {
+ debug(" WARN: File '" + fileName + "' does not exist in user's home");
+ return null;
+ }
+
+ if (!result.isFile())
+ {
+ debug("ERROR: File '" + fileName + "' in user's home is not a file");
+ return null;
+ }
+
+ if (!result.canRead())
+ {
+ debug("ERROR: File '" + fileName + "' in user's home is not readable");
+ return null;
+ }
+
+ return result;
+ }
+}
Index: ConfigFileTokenizer.java
===================================================================
RCS file: ConfigFileTokenizer.java
diff -N ConfigFileTokenizer.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ConfigFileTokenizer.java 14 Jan 2006 20:02:25 -0000
@@ -0,0 +1,243 @@
+/* ConfigFileTokenizer.java -- JAAS Login Configuration default syntax tokenizer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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 for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.security.auth.login;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A UTF-8 friendly, JAAS Login Module Configuration file tokenizer written in
+ * the deault syntax. This class emulates, to a certain extent, the behavior of
+ * a [EMAIL PROTECTED] java.io.SrteamTokenizer} instance <code>st</code>, when set as
+ * follows:
+ *
+ * <pre>
+ * st.resetSyntax();
+ * st.lowerCaseMode(false);
+ * st.slashSlashComments(true);
+ * st.slashStarComments(true);
+ * st.eolIsSignificant(false);
+ * st.wordChars('_', '_');
+ * st.wordChars('$', '$');
+ * st.wordChars('A', 'Z');
+ * st.wordChars('a', 'z');
+ * st.wordChars('0', '9');
+ * st.wordChars('.', '.');
+ * st.whitespaceChars(' ', ' ');
+ * st.whitespaceChars('\t', '\t');
+ * st.whitespaceChars('\f', '\f');
+ * st.whitespaceChars('\r', '\r');
+ * st.whitespaceChars('\n', '\n');
+ * st.quoteChar('"');
+ * st.quoteChar('\'');
+ * </pre>
+ *
+ * <p>The most important (negative) difference with a
+ * [EMAIL PROTECTED] java.io.StreamTokenizer} is that this tokenizer does not properly
+ * handle C++ and Java // style comments in the middle of the line. It only
+ * ignores them if/when found at the start of the line.</p>
+ */
+public class ConfigFileTokenizer
+{
+ // Constants and fields
+ // --------------------------------------------------------------------------
+
+ private static final boolean DEBUG = false;
+ private static final void debug(String s) {if (DEBUG) System.out.println(s);};
+
+ /** A constant indicating that the end of the stream has been read. */
+ public static final int TT_EOF = -1;
+ /** A constant indicating that a word token has been read. */
+ public static final int TT_WORD = -3;
+ /** A constant indicating that no tokens have been read yet. */
+ private static final int TT_NONE = -4;
+
+ public String sval;
+ public int ttype;
+
+ private BufferedReader br;
+ boolean initialised;
+ private StringBuffer sb;
+ private int sbNdx;
+
+ // Constructor(s)
+ // --------------------------------------------------------------------------
+
+ /** Trivial constructor. */
+ ConfigFileTokenizer(Reader r)
+ {
+ super();
+
+ br = r instanceof BufferedReader ? (BufferedReader) r : new BufferedReader(r);
+ initialised = false;
+ }
+
+ // Class methods
+ // --------------------------------------------------------------------------
+
+ // Instance methods
+ // --------------------------------------------------------------------------
+
+ public int nextToken() throws IOException
+ {
+ if (!initialised)
+ init();
+
+ if (sbNdx >= sb.length())
+ return TT_EOF;
+
+ skipWhitespace();
+
+ if (sbNdx >= sb.length())
+ return TT_EOF;
+
+ int endNdx;
+ if (Character.isJavaIdentifierPart(sb.charAt(sbNdx)))
+ {
+ endNdx = sbNdx + 1;
+ while (Character.isJavaIdentifierPart(sb.charAt(endNdx))
+ || sb.charAt(endNdx) == '.')
+ endNdx++;
+
+ ttype = TT_WORD;
+ sval = sb.substring(sbNdx, endNdx);
+ sbNdx = endNdx;
+ return ttype;
+ }
+
+ int c = sb.charAt(sbNdx);
+ if (c == '{' || c == '}' || c == ';' || c == '=')
+ {
+ ttype = c;
+ sbNdx++;
+ return ttype;
+ }
+
+ if (c == '"' || c == '\'')
+ {
+ ttype = c;
+ String quote = sb.substring(sbNdx, sbNdx + 1);
+ int i = sbNdx + 1;
+ while (true)
+ {
+ // find a candidate
+ endNdx = sb.indexOf(quote, i);
+ if (endNdx == -1)
+ abort("Missing closing quote: " + quote);
+
+ // found one; is it escaped?
+ if (sb.charAt(endNdx - 1) != '\\')
+ break;
+
+ i++;
+ continue;
+ }
+
+ endNdx++;
+ sval = sb.substring(sbNdx, endNdx);
+ sbNdx = endNdx;
+ return ttype;
+ }
+
+ abort("Unknown character: " + sb.charAt(sbNdx));
+ return Integer.MIN_VALUE;
+ }
+
+ public void pushBack()
+ {
+ sbNdx -= ttype != TT_WORD ? 1 : sval.length();
+ }
+
+ private void init() throws IOException
+ {
+ sb = new StringBuffer();
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ line = line.trim();
+ if (line.length() == 0)
+ continue;
+
+ if (line.startsWith("#") || line.startsWith("//"))
+ continue;
+
+ sb.append(line).append(" ");
+ }
+
+ sbNdx = 0;
+ sval = null;
+ ttype = TT_NONE;
+
+ initialised = true;
+ }
+
+ private void skipWhitespace() throws IOException
+ {
+ int endNdx;
+ while (sbNdx < sb.length())
+ if (Character.isWhitespace(sb.charAt(sbNdx)))
+ {
+ sbNdx++;
+ while (sbNdx < sb.length() && Character.isWhitespace(sb.charAt(sbNdx)))
+ sbNdx++;
+
+ continue;
+ }
+ else if (sb.charAt(sbNdx) == '/' && sb.charAt(sbNdx + 1) == '*')
+ {
+ endNdx = sb.indexOf("*/", sbNdx + 2);
+ if (endNdx == -1)
+ abort("Missing closing */ sequence");
+
+ sbNdx = endNdx + 2;
+ continue;
+ }
+ else
+ break;
+ }
+
+ private void abort(String m) throws IOException
+ {
+ debug("ERROR: " + m);
+ debug("DEBUG: sb: " + sb);
+ debug("DEBUG: sbNdx: " + sbNdx);
+ throw new IOException(m);
+ }
+}
pgpPVYlSklIiv.pgp
Description: PGP signature
_______________________________________________ Classpath-patches mailing list [email protected] http://lists.gnu.org/mailman/listinfo/classpath-patches
