Index: gnu/java/security/PolicyFile.java =================================================================== RCS file: gnu/java/security/PolicyFile.java diff -N gnu/java/security/PolicyFile.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/PolicyFile.java 3 Jun 2004 09:10:37 -0000 @@ -0,0 +1,665 @@ +/* PolicyFile.java -- policy file reader. + Copyright (C) 2004 Free Software Foundation, Inc. + +This program 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. + +This program 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 this program; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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.java.security; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StreamTokenizer; +import java.lang.reflect.Constructor; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.Principal; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.Security; +import java.security.UnresolvedPermission; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * An implementation of a address@hidden java.security.Policy} object whose + * permissions are specified by a policy file. + * + *

The approximate syntax of policy files is:

+ * + *
+ * policyFile ::= keystoreOrGrantEntries ;
+ *
+ * keystoreOrGrantEntries ::= keystoreOrGrantEntry |
+ *                            keystoreOrGrantEntries keystoreOrGrantEntry |
+ *                            EMPTY ;
+ *
+ * keystoreOrGrantEntry ::= keystoreEntry | grantEntry ;
+ *
+ * keystoreEntry ::= "keystore" keystoreUrl ';' |
+ *                   "keystore" keystoreUrl ',' keystoreAlgorithm ';' ;
+ *
+ * keystoreUrl ::= URL ;
+ * keystoreAlgorithm ::= STRING ;
+ *
+ * grantEntry ::= "grant" domainParameters '{' permissions '}' ';'
+ *
+ * domainParameters ::= domainParameter |
+ *                      domainParameter ',' domainParameters ;
+ *
+ * domainParameter ::= "signedBy" signerNames |
+ *                     "codeBase" codeBaseUrl |
+ *                     "principal" principalClassName principalName |
+ *                     "principal" principalName ;
+ *
+ * signerNames ::= quotedString ;
+ * codeBaseUrl ::= URL ;
+ * principalClassName ::= STRING ;
+ * principalName ::= quotedString ;
+ *
+ * quotedString ::= quoteChar STRING quoteChar ;
+ * quoteChar ::= '"' | '\'';
+ *
+ * permissions ::= permission | permissions permission ;
+ *
+ * permission ::= "permission" permissionClassName permissionTarget permissionAction |
+ *                "permission" permissionClassName permissionTarget |
+ *                "permission" permissionClassName;
+ * 
+ * + *

Comments are either form of Java comments. Keystore entries only + * affect subsequent grant entries, so if a grant entry preceeds a + * keystore entry, that grant entry is not affected by that keystore + * entry. Certian instances of ${property-name} will be + * replaced with System.getProperty("property-name") in + * quoted strings.

+ * + *

This class will load the following files when created or + * refreshed, in order:

+ * + *
    + *
  1. The file ${java.home}/lib/security/java.policy.
  2. + *
  3. All URLs specified by security properties + * "policy.file.n", for increasing n + * starting from 1. The sequence stops at the first undefined + * property, so you must set "policy.file.1" if you also + * set "policy.file.2", and so on.
  4. + *
  5. The URL specified by the property + * "java.security.policy".
  6. + *
+ * + * @author Casey Marshall (address@hidden) + * @see java.security.Policy + */ +public final class PolicyFile extends Policy +{ + + // Constants and fields. + // ------------------------------------------------------------------------- + + private static final boolean DEBUG = true; + private static void debug(String msg) + { + System.err.print(">> PolicyFile: "); + System.err.println(msg); + } + + private static void debug(Throwable t) + { + System.err.println(">> PolicyFile"); + t.printStackTrace(System.err); + } + + private static final String DEFAULT_POLICY = System.getProperty("java.home") + + System.getProperty("file.separator") + "lib" + + System.getProperty("file.separator") + "security" + + System.getProperty("file.separator") + "java.policy"; + + private final Map cs2pc; + + // Constructors. + // ------------------------------------------------------------------------- + + public PolicyFile() + { + cs2pc = new HashMap(); + refresh(); + } + + // Instance methods. + // ------------------------------------------------------------------------- + + public PermissionCollection getPermissions(CodeSource codeSource) + { + Permissions perms = new Permissions(); + for (Iterator it = cs2pc.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + CodeSource cs = (CodeSource) e.getKey(); + if (cs.implies(codeSource)) + { + if (DEBUG) debug(cs+" -> "+codeSource); + PermissionCollection pc = (PermissionCollection) e.getValue(); + for (Enumeration ee = pc.elements(); ee.hasMoreElements(); ) + { + perms.add((Permission) ee.nextElement()); + } + } + else + if (DEBUG) debug(cs+" !-> "+codeSource); + } + if (DEBUG) debug ("returning permissions " + perms + " for " + codeSource); + return perms; + } + + public void refresh() + { + cs2pc.clear(); + List policyFiles = new LinkedList(); + try + { + policyFiles.add(new File(DEFAULT_POLICY).toURL()); + if (DEBUG) debug ("defualt policy is " + DEFAULT_POLICY); + policyFiles.addAll((List) AccessController.doPrivileged( + new PrivilegedExceptionAction() + { + public Object run() throws Exception + { + LinkedList l = new LinkedList(); + for (int i = 1; ; i++) + { + String s = Security.getProperty("policy.file."+i); + if (DEBUG) debug("policy.file."+i+"="+s); + if (s == null) + break; + l.add(new URL(s)); + } + String s = System.getProperty("java.security.policy"); + if (DEBUG) debug("java.security.policy="+s); + if (s != null) + l.add(new URL(s)); + return l; + } + })); + } + catch (PrivilegedActionException pae) + { + if (DEBUG) debug(pae); + } + catch (MalformedURLException mue) + { + if (DEBUG) debug(mue); + } + for (Iterator it = policyFiles.iterator(); it.hasNext(); ) + { + try + { + URL url = (URL) it.next(); + parse(url); + } + catch (IOException ioe) + { + if (DEBUG) debug(ioe); + } + } + } + + public String toString() + { + return super.toString() + " [ " + cs2pc.toString() + " ]"; + } + + // Own methods. + // ------------------------------------------------------------------------- + + private static final int STATE_BEGIN = 0; + private static final int STATE_GRANT = 1; + private static final int STATE_PERMS = 2; + + /** + * Parse a policy file, incorporating the permission definitions + * described therein. + * + * @param url The URL of the policy file to read. + * @throws IOException if an I/O error occurs, or if the policy file + * cannot be parsed. + */ + private void parse(final URL url) throws IOException + { + if (DEBUG) debug ("reading policy file from " + url); + final StreamTokenizer in = new StreamTokenizer(new InputStreamReader(url.openStream())); + in.resetSyntax(); + in.slashSlashComments(true); + in.slashStarComments(true); + in.wordChars('A', 'Z'); + in.wordChars('a', 'z'); + in.wordChars('0', '9'); + in.wordChars('.', '.'); + in.wordChars('_', '_'); + in.wordChars('$', '$'); + in.whitespaceChars(' ', ' '); + in.whitespaceChars('\t', '\t'); + in.whitespaceChars('\f', '\f'); + in.whitespaceChars('\n', '\n'); + in.whitespaceChars('\r', '\r'); + in.quoteChar('\''); + in.quoteChar('"'); + + int tok; + int state = STATE_BEGIN; + List keystores = new LinkedList(); + URL currentBase = null; + List currentCerts = new LinkedList(); + Permissions currentPerms = new Permissions(); + while ((tok = in.nextToken()) != StreamTokenizer.TT_EOF) + { + switch (tok) + { + case '{': + if (state != STATE_GRANT) + error(url, in, "spurious '{'"); + state = STATE_PERMS; + tok = in.nextToken(); + break; + case '}': + if (state != STATE_PERMS) + error(url, in, "spurious '}'"); + state = STATE_BEGIN; + currentPerms.setReadOnly(); + Certificate[] c = null; + if (!currentCerts.isEmpty()) + c = (Certificate[]) currentCerts.toArray(new Certificate[currentCerts.size()]); + cs2pc.put(new CodeSource(currentBase, c), currentPerms); + currentCerts.clear(); + currentPerms = new Permissions(); + currentBase = null; + tok = in.nextToken(); + if (tok != ';') + in.pushBack(); + continue; + } + if (tok != StreamTokenizer.TT_WORD) + { + error(url, in, "expecting word token"); + } + + // keystore "" [',' ""] ';' + if (in.sval.equalsIgnoreCase("keystore")) + { + String alg = KeyStore.getDefaultType(); + tok = in.nextToken(); + if (tok != '"' && tok != '\'') + error(url, in, "expecting key store URL"); + String store = in.sval; + tok = in.nextToken(); + if (tok == ',') + { + tok = in.nextToken(); + if (tok != '"' && tok != '\'') + error(url, in, "expecting key store type"); + alg = in.sval; + tok = in.nextToken(); + } + if (tok != ';') + error(url, in, "expecting semicolon"); + try + { + KeyStore keystore = KeyStore.getInstance(alg); + keystore.load(new URL(url, store).openStream(), null); + keystores.add(keystore); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + } + else if (in.sval.equalsIgnoreCase("grant")) + { + if (state != STATE_BEGIN) + error(url, in, "extraneous grant keyword"); + state = STATE_GRANT; + } + else if (in.sval.equalsIgnoreCase("signedBy")) + { + if (state != STATE_GRANT && state != STATE_PERMS) + error(url, in, "spurious 'signedBy'"); + if (keystores.isEmpty()) + error(url, in, "'signedBy' with no keystores"); + tok = in.nextToken(); + if (tok != '"' && tok != '\'') + error(url, in, "expecting signedBy name"); + StringTokenizer st = new StringTokenizer(in.sval, ","); + while (st.hasMoreTokens()) + { + String alias = st.nextToken(); + for (Iterator it = keystores.iterator(); it.hasNext(); ) + { + KeyStore keystore = (KeyStore) it.next(); + try + { + if (keystore.isCertificateEntry(alias)) + currentCerts.add(keystore.getCertificate(alias)); + } + catch (KeyStoreException kse) + { + error(url, in, kse.toString()); + } + } + } + tok = in.nextToken(); + if (tok != ',') + { + if (state != STATE_GRANT) + error(url, in, "spurious ','"); + in.pushBack(); + } + } + else if (in.sval.equalsIgnoreCase("codeBase")) + { + if (state != STATE_GRANT) + error(url, in, "spurious 'codeBase'"); + tok = in.nextToken(); + if (tok != '"' && tok != '\'') + error(url, in, "expecting code base URL"); + String base = expand(in.sval); + if (File.separatorChar != '/') + base = base.replace(File.separatorChar, '/'); + try + { + currentBase = new URL(base); + } + catch (MalformedURLException mue) + { + error(url, in, mue.toString()); + } + tok = in.nextToken(); + if (tok != ',') + in.pushBack(); + } + else if (in.sval.equalsIgnoreCase("principal")) + { + if (state != STATE_GRANT) + error(url, in, "spurious 'principal'"); + tok = in.nextToken(); + if (tok == StreamTokenizer.TT_WORD) + { + tok = in.nextToken(); + if (tok != '"' && tok != '\'') + error(url, in, "expecting principal name"); + String name = in.sval; + Principal p = null; + try + { + Class pclass = Class.forName(in.sval); + Constructor c = + pclass.getConstructor(new Class[] { String.class }); + p = (Principal) c.newInstance(new Object[] { name }); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + for (Iterator it = keystores.iterator(); it.hasNext(); ) + { + KeyStore ks = (KeyStore) it.next(); + try + { + for (Enumeration e = ks.aliases(); e.hasMoreElements(); ) + { + String alias = (String) e.nextElement(); + if (ks.isCertificateEntry(alias)) + { + Certificate cert = ks.getCertificate(alias); + if (!(cert instanceof X509Certificate)) + continue; + if (p.equals(((X509Certificate) cert).getSubjectDN()) || + p.equals(((X509Certificate) cert).getSubjectX500Principal())) + currentCerts.add(cert); + } + } + } + catch (KeyStoreException kse) + { + error(url, in, kse.toString()); + } + } + } + else if (tok == '"' || tok == '\'') + { + String alias = in.sval; + for (Iterator it = keystores.iterator(); it.hasNext(); ) + { + KeyStore ks = (KeyStore) it.next(); + try + { + if (ks.isCertificateEntry(alias)) + currentCerts.add(ks.getCertificate(alias)); + } + catch (KeyStoreException kse) + { + error(url, in, kse.toString()); + } + } + } + else + error(url, in, "expecting principal"); + tok = in.nextToken(); + if (tok != ',') + in.pushBack(); + } + else if (in.sval.equalsIgnoreCase("permission")) + { + if (state != STATE_PERMS) + error(url, in, "spurious 'permission'"); + tok = in.nextToken(); + if (tok != StreamTokenizer.TT_WORD) + error(url, in, "expecting permission class name"); + String className = in.sval; + Class clazz = null; + try + { + clazz = Class.forName(className); + } + catch (ClassNotFoundException cnfe) + { + } + tok = in.nextToken(); + if (tok == ';') + { + if (clazz == null) + { + currentPerms.add(new UnresolvedPermission(className, + null, null, (Certificate[]) currentCerts.toArray(new Certificate[0]))); + continue; + } + try + { + currentPerms.add((Permission) clazz.newInstance()); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + continue; + } + if (tok != '"' && tok != '\'') + error(url, in, "expecting permission target"); + String target = expand(in.sval); + tok = in.nextToken(); + if (tok == ';') + { + if (clazz == null) + { + currentPerms.add(new UnresolvedPermission(className, + target, null, (Certificate[]) currentCerts.toArray(new Certificate[0]))); + continue; + } + try + { + Constructor c = + clazz.getConstructor(new Class[] { String.class }); + currentPerms.add((Permission) c.newInstance( + new Object[] { target })); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + continue; + } + if (tok != ',') + error(url, in, "expecting ','"); + tok = in.nextToken(); + if (tok == StreamTokenizer.TT_WORD) + { + if (!in.sval.equalsIgnoreCase("signedBy")) + error(url, in, "expecting 'signedBy'"); + try + { + Constructor c = + clazz.getConstructor(new Class[] { String.class }); + currentPerms.add((Permission) c.newInstance( + new Object[] { target })); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + in.pushBack(); + continue; + } + if (tok != '"' && tok != '\'') + error(url, in, "expecting permission action"); + String action = in.sval; + if (clazz == null) + { + currentPerms.add(new UnresolvedPermission(className, + target, action, (Certificate[]) currentCerts.toArray(new Certificate[0]))); + continue; + } + else + { + try + { + Constructor c = clazz.getConstructor( + new Class[] { String.class, String.class }); + currentPerms.add((Permission) c.newInstance( + new Object[] { target, action })); + } + catch (Exception x) + { + error(url, in, x.toString()); + } + } + tok = in.nextToken(); + if (tok != ';' && tok != ',') + error(url, in, "expecting ';' or ','"); + } + } + } + + /** + * Expand all instances of "${property-name}" into + * System.getProperty("property-name"). + */ + private static String expand(final String s) + { + final StringBuffer result = new StringBuffer(); + final StringBuffer prop = new StringBuffer(); + int state = 0; + for (int i = 0; i < s.length(); i++) + { + switch (state) + { + case 0: + if (s.charAt(i) == '$') + state = 1; + else + result.append(s.charAt(i)); + break; + case 1: + if (s.charAt(i) == '{') + state = 2; + else + { + state = 0; + result.append('$').append(s.charAt(i)); + } + break; + case 2: + if (s.charAt(i) == '}') + { + String p = prop.toString(); + if (p.equals("/")) + p = "file.separator"; + p = System.getProperty(p); + if (p == null) + p = ""; + result.append(p); + prop.setLength(0); + state = 0; + } + else + prop.append(s.charAt(i)); + break; + } + } + if (state != 0) + result.append('$').append('{').append(prop); + return result.toString(); + } + + /** + * I miss macros. + */ + private static void error(URL base, StreamTokenizer in, String msg) + throws IOException + { + throw new IOException(base+":"+in.lineno()+": "+msg); + } +} Index: gnu/java/security/Makefile.am =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/security/Makefile.am,v retrieving revision 1.6 diff -u -b -B -r1.6 Makefile.am --- gnu/java/security/Makefile.am 23 Apr 2003 23:02:38 -0000 1.6 +++ gnu/java/security/Makefile.am 3 Jun 2004 09:10:37 -0000 @@ -4,5 +4,6 @@ EXTRA_DIST = \ Engine.java \ -OID.java +OID.java \ +PolicyFile.java Index: java/security/IntersectingDomainCombiner.java =================================================================== RCS file: java/security/IntersectingDomainCombiner.java diff -N java/security/IntersectingDomainCombiner.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/security/IntersectingDomainCombiner.java 3 Jun 2004 09:10:37 -0000 @@ -0,0 +1,82 @@ +/* IntersectingDomainCombiner.java -- + Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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 java.security; + +import java.util.HashSet; + +/** + * A trivial implementation of address@hidden DomainCombiner} that produces the + * intersection of the supplied address@hidden ProtectionDomain} objects. + */ +final class IntersectingDomainCombiner implements DomainCombiner +{ + + // Contstant. + // ------------------------------------------------------------------------- + + static final IntersectingDomainCombiner SINGLETON = new IntersectingDomainCombiner(); + + // Constructor. + // ------------------------------------------------------------------------- + + private IntersectingDomainCombiner() + { + } + + // Methods. + // ------------------------------------------------------------------------- + + public ProtectionDomain[] combine (ProtectionDomain[] currentDomains, + ProtectionDomain[] assignedDomains) + { + HashSet newDomains = new HashSet (); + for (int i = 0; i < currentDomains.length; i++) + { + if (currentDomains[i] == null) + continue; + for (int j = 0; j < assignedDomains.length; j++) + { + if (currentDomains[i].equals (assignedDomains[j])) + newDomains.add (currentDomains[i]); + } + } + return (ProtectionDomain[]) + newDomains.toArray(new ProtectionDomain[newDomains.size()]); + } +} Index: java/security/Makefile.am =================================================================== RCS file: /cvsroot/classpath/classpath/java/security/Makefile.am,v retrieving revision 1.8 diff -u -b -B -r1.8 Makefile.am --- java/security/Makefile.am 6 May 2002 21:10:53 -0000 1.8 +++ java/security/Makefile.am 3 Jun 2004 09:10:37 -0000 @@ -26,6 +26,7 @@ Guard.java \ Identity.java \ IdentityScope.java \ +IntersectingDomainCombiner.java \ InvalidAlgorithmParameterException.java \ InvalidKeyException.java \ InvalidParameterException.java \ Index: java/security/AccessController.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/security/AccessController.java,v retrieving revision 1.7 diff -u -b -B -r1.7 AccessController.java --- java/security/AccessController.java 20 Apr 2004 19:18:15 -0000 1.7 +++ java/security/AccessController.java 3 Jun 2004 09:10:38 -0000 @@ -115,8 +115,16 @@ public static Object doPrivileged(PrivilegedAction action, AccessControlContext context) { + VMAccessController.pushContext (context, action.getClass()); + try + { return action.run(); } + finally + { + VMAccessController.popContext (action.getClass()); + } + } /** * Calls the run() method of the given action with as @@ -170,6 +178,7 @@ AccessControlContext context) throws PrivilegedActionException { + VMAccessController.pushContext (context, action.getClass()); try { @@ -179,19 +188,28 @@ { throw new PrivilegedActionException(e); } + finally + { + VMAccessController.popContext (action.getClass()); + } } /** * Returns the complete access control context of the current thread. - *

- * XXX - Should this include all the protection domains in the call chain - * or only the domains till the last doPrivileged() call? - *

- * XXX - needs native support. Currently returns an empty context. + * The returned object encompasses all address@hidden ProtectionDomain} objects + * for all classes in the current call stack, or the set of protection + * domains until the last call to address@hidden + * #doPrivileged(java.security.PrivilegedAction)}. + * + *

Additionally, if a call was made to address@hidden + * #doPrivileged(java.security.PrivilegedAction,java.security.AccessControlContext)} + * that supplied an address@hidden AccessControlContext}, then that context + * will be intersected with the calculated one. + * + * @return The context. */ public static AccessControlContext getContext() { - // For now just return an new empty context - return new AccessControlContext(new ProtectionDomain[0]); + return VMAccessController.getContext(); } } Index: vm/reference/java/security/VMAccessController.java =================================================================== RCS file: vm/reference/java/security/VMAccessController.java diff -N vm/reference/java/security/VMAccessController.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ vm/reference/java/security/VMAccessController.java 3 Jun 2004 09:10:38 -0000 @@ -0,0 +1,236 @@ +/* VMAccessController.java -- VM-specific access controller methods. + Copyright (C) 2004 Free Software Foundation, Inc. + +This program 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. + +This program 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 this program; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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 java.security; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class VMAccessController +{ + + // Fields. + // ------------------------------------------------------------------------- + + /** + * A mapping between pairs (thread, classname) to access + * control contexts. The thread and classname are the thread + * and classname current as of the last call to doPrivileged with + * an AccessControlContext argument. + */ + private static final Map contexts = Collections.synchronizedMap(new HashMap()); + + private static final ThreadLocal inGetContext = new ThreadLocal(); + + private final static AccessControlContext DEFAULT_CONTEXT; + static + { + CodeSource source = new CodeSource(null, null); + Permissions permissions = new Permissions(); + permissions.add(new AllPermission()); + ProtectionDomain[] domain = new ProtectionDomain[] { + new ProtectionDomain(source, permissions) + }; + DEFAULT_CONTEXT = new AccessControlContext(domain); + } + + private static final boolean DEBUG = false; + private static void debug (String msg) + { + System.err.print (">>> VMAccessController: "); + System.err.println (msg); + } + + // Constructors. + // ------------------------------------------------------------------------- + + private VMAccessController() { } + + // Class methods. + // ------------------------------------------------------------------------- + + /** + * Relate a class (which should be an instance of address@hidden PrivilegedAction} + * with an access control context. This method is used by address@hidden + * AccessController#doPrivileged(java.security.PrivilegedAction,java.security.AccessControlContext)} + * to set up the context that will be returned by address@hidden #getContext()}. + * This method relates the class to the current thread, so contexts + * pushed from one thread will not be available to another. + * + * @param acc The access control context. + * @param clazz The class that implements address@hidden PrivilegedAction}. + */ + static void pushContext (AccessControlContext acc, Class clazz) + { + ArrayList pair = new ArrayList (2); + pair.add (Thread.currentThread()); + pair.add (clazz); + if (DEBUG) debug ("pushing " + pair); + contexts.put (pair, acc); + } + + /** + * Removes the relation of a class to an address@hidden AccessControlContext}. + * This method is used by address@hidden AccessController} when exiting from a + * call to address@hidden + * AccessController#doPrivileged(java.security.PrivilegedAction,java.security.AccessControlContext)}. + * + * @param clazz The class that implements address@hidden PrivilegedAction}. + */ + static void popContext (Class clazz) + { + ArrayList pair = new ArrayList (2); + pair.add (Thread.currentThread()); + pair.add (clazz); + if (DEBUG) debug ("popping " + pair); + contexts.remove (pair); + } + + /** + * Examine the method stack of the currently running thread, and create + * an address@hidden AccessControlContext} filled in with the appropriate address@hidden + * ProtectionDomain} objects given this stack. + * + * @return The context. + */ + static AccessControlContext getContext() + { + // If we are already in getContext, but called a method that needs + // a permission check, return the all-permissive context so methods + // called from here succeed. + // + // XXX is this necessary? We should verify if there are any calls in + // the stack below this method that require permission checks. + Boolean inCall = (Boolean) inGetContext.get(); + if (inCall != null && inCall.booleanValue()) + { + if (DEBUG) debug ("already in getContext"); + return DEFAULT_CONTEXT; + } + + Object[][] stack = getStack(); + Class[] classes = (Class[]) stack[0]; + String[] methods = (String[]) stack[1]; + + inGetContext.set (Boolean.TRUE); + + if (DEBUG) debug (">>> got trace of length " + classes.length); + + HashSet domains = new HashSet(); + HashSet seenDomains = new HashSet(); + AccessControlContext context = null; + + // We walk down the stack, adding each ProtectionDomain for each + // class in the call stack. If we reach a call to doPrivileged, + // we don't add any more stack frames. We skip the first three stack + // frames, since they comprise the calls to getStack, getContext, + // and AccessController.getContext. + for (int i = 3; i < classes.length; i++) + { + Class clazz = classes[i]; + String method = methods[i]; + + if (DEBUG) debug (">>> checking " + clazz + "." + method); + + if (DEBUG) debug (">>> loader = " + clazz.getClassLoader()); + + if (clazz.equals (AccessController.class) + && method.equals ("doPrivileged")) + { + // If there was a call to doPrivileged with a supplied context, + // return that context. + List pair = new ArrayList(2); + pair.add (Thread.currentThread()); + pair.add (classes[i-1]); + if (contexts.containsKey (pair)) + context = (AccessControlContext) contexts.get (pair); + break; + } + + ProtectionDomain domain = clazz.getProtectionDomain(); + + if (domain == null) + continue; + if (seenDomains.contains (domain)) + continue; + seenDomains.add (domain); + + // Create a static snapshot of this domain, which may change over time + // if the current policy changes. + domains.add (new ProtectionDomain (domain.getCodeSource(), + domain.getPermissions())); + } + + if (DEBUG) debug ("created domains: " + domains); + + ProtectionDomain[] result = (ProtectionDomain[]) + domains.toArray (new ProtectionDomain[domains.size()]); + + // Intersect the derived protection domain with the context supplied + // to doPrivileged. + if (context != null) + context = new AccessControlContext (result, context, + IntersectingDomainCombiner.SINGLETON); + // No context was supplied. Return the derived one. + else + context = new AccessControlContext (result); + + inGetContext.set (Boolean.FALSE); + return context; + } + + /** + * Returns a snapshot of the current call stack as a pair of arrays: + * the first an array of classes in the call stack, the second an array + * of strings containing the method names in the call stack. The two + * arrays match up, meaning that method i is declared in class + * i. The arrays are clean; it will only contain Java methods, + * and no element of the list should be null. + * + *

XXX note: this interface (VMAccessController) would possibly be + * cleaner if we had a method similar to this, but returned an array + * of java.lang.reflect.Method objects. Then, instead of having this + * much logic in this class, we put everything in AccessController, + * and simply have this single getStack method for a VM to implement. + */ + private static native Object[][] getStack(); +} Index: vm/reference/java/security/Makefile.am =================================================================== RCS file: vm/reference/java/security/Makefile.am diff -N vm/reference/java/security/Makefile.am --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ vm/reference/java/security/Makefile.am 3 Jun 2004 09:10:38 -0000 @@ -0,0 +1,5 @@ +# used by automake to generate Makefile.in + + +EXTRA_DIST = \ +VMAccessController.java Index: vm/reference/java/Makefile.am =================================================================== RCS file: /cvsroot/classpath/classpath/vm/reference/java/Makefile.am,v retrieving revision 1.2 diff -u -b -B -r1.2 Makefile.am --- vm/reference/java/Makefile.am 17 Jan 2003 16:45:28 -0000 1.2 +++ vm/reference/java/Makefile.am 3 Jun 2004 09:10:38 -0000 @@ -1,3 +1,3 @@ # used by automake to generate Makefile.in -SUBDIRS = lang io +SUBDIRS = lang io security Index: configure.ac =================================================================== RCS file: /cvsroot/classpath/classpath/configure.ac,v retrieving revision 1.29 diff -u -b -B -r1.29 configure.ac --- configure.ac 28 May 2004 10:25:30 -0000 1.29 +++ configure.ac 3 Jun 2004 09:10:38 -0000 @@ -447,6 +447,7 @@ vm/reference/java/lang/Makefile vm/reference/java/lang/reflect/Makefile vm/reference/java/io/Makefile +vm/reference/java/security/Makefile lib/Makefile lib/gen-classlist.sh]) AC_CONFIG_COMMANDS([gen-classlist],[chmod 755 lib/gen-classlist.sh])