| /* PolicyFile.java -- policy file reader | |
| Copyright (C) 2004, 2005, 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.java.security; | |
| import gnu.classpath.debug.Component; | |
| import gnu.classpath.debug.SystemLogger; | |
| import gnu.java.security.action.GetPropertyAction; | |
| 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; | |
| import java.util.logging.Logger; | |
| /** | |
| * An implementation of a {@link java.security.Policy} object whose | |
| * permissions are specified by a <em>policy file</em>. | |
| * | |
| * <p>The approximate syntax of policy files is:</p> | |
| * | |
| * <pre> | |
| * 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; | |
| * </pre> | |
| * | |
| * <p>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 <code>${property-name}</code> will be | |
| * replaced with <code>System.getProperty("property-name")</code> in | |
| * quoted strings.</p> | |
| * | |
| * <p>This class will load the following files when created or | |
| * refreshed, in order:</p> | |
| * | |
| * <ol> | |
| * <li>The file <code>${java.home}/lib/security/java.policy</code>.</li> | |
| * <li>All URLs specified by security properties | |
| * <code>"policy.file.<i>n</i>"</code>, for increasing <i>n</i> | |
| * starting from 1. The sequence stops at the first undefined | |
| * property, so you must set <code>"policy.file.1"</code> if you also | |
| * set <code>"policy.file.2"</code>, and so on.</li> | |
| * <li>The URL specified by the property | |
| * <code>"java.security.policy"</code>.</li> | |
| * </ol> | |
| * | |
| * @author Casey Marshall (csm@gnu.org) | |
| * @see java.security.Policy | |
| */ | |
| public final class PolicyFile extends Policy | |
| { | |
| // Constants and fields. | |
| // ------------------------------------------------------------------------- | |
| protected static final Logger logger = SystemLogger.SYSTEM; | |
| // Added to cut redundant AccessController.doPrivileged calls | |
| private static GetPropertyAction prop = new GetPropertyAction("file.seperator"); | |
| private static final String fs = (String) AccessController.doPrivileged(prop); | |
| private static final String DEFAULT_POLICY = | |
| (String) AccessController.doPrivileged(prop.setParameters("java.home")) | |
| + fs + "lib" + fs + "security" + fs + "java.policy"; | |
| private static final String DEFAULT_USER_POLICY = | |
| (String) AccessController.doPrivileged(prop.setParameters("user.home")) + | |
| fs + ".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)) | |
| { | |
| logger.log (Component.POLICY, "{0} -> {1}", new Object[] | |
| { cs, codeSource }); | |
| PermissionCollection pc = (PermissionCollection) e.getValue(); | |
| for (Enumeration ee = pc.elements(); ee.hasMoreElements(); ) | |
| { | |
| perms.add((Permission) ee.nextElement()); | |
| } | |
| } | |
| else | |
| logger.log (Component.POLICY, "{0} !-> {1}", new Object[] | |
| { cs, codeSource }); | |
| } | |
| logger.log (Component.POLICY, "returning permissions {0} for {1}", | |
| new Object[] { perms, codeSource }); | |
| return perms; | |
| } | |
| public void refresh() | |
| { | |
| cs2pc.clear(); | |
| final List policyFiles = new LinkedList(); | |
| try | |
| { | |
| policyFiles.add (new File (DEFAULT_POLICY).toURL()); | |
| policyFiles.add (new File (DEFAULT_USER_POLICY).toURL ()); | |
| AccessController.doPrivileged( | |
| new PrivilegedExceptionAction() | |
| { | |
| public Object run() throws Exception | |
| { | |
| String allow = Security.getProperty ("policy.allowSystemProperty"); | |
| if (allow == null || Boolean.getBoolean (allow)) | |
| { | |
| String s = System.getProperty ("java.security.policy"); | |
| logger.log (Component.POLICY, "java.security.policy={0}", s); | |
| if (s != null) | |
| { | |
| boolean only = s.startsWith ("="); | |
| if (only) | |
| s = s.substring (1); | |
| policyFiles.clear (); | |
| policyFiles.add (new URL (s)); | |
| if (only) | |
| return null; | |
| } | |
| } | |
| for (int i = 1; ; i++) | |
| { | |
| String pname = "policy.url." + i; | |
| String s = Security.getProperty (pname); | |
| logger.log (Component.POLICY, "{0}={1}", new Object [] | |
| { pname, s }); | |
| if (s == null) | |
| break; | |
| policyFiles.add (new URL (s)); | |
| } | |
| return null; | |
| } | |
| }); | |
| } | |
| catch (PrivilegedActionException pae) | |
| { | |
| logger.log (Component.POLICY, "reading policy properties", pae); | |
| } | |
| catch (MalformedURLException mue) | |
| { | |
| logger.log (Component.POLICY, "setting default policies", mue); | |
| } | |
| logger.log (Component.POLICY, "building policy from URLs {0}", | |
| policyFiles); | |
| for (Iterator it = policyFiles.iterator(); it.hasNext(); ) | |
| { | |
| try | |
| { | |
| URL url = (URL) it.next(); | |
| parse(url); | |
| } | |
| catch (IOException ioe) | |
| { | |
| logger.log (Component.POLICY, "reading policy", 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 | |
| { | |
| logger.log (Component.POLICY, "reading policy file from {0}", 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 "<keystore-path>" [',' "<keystore-type>"] ';' | |
| 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[currentCerts.size()]))); | |
| 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[currentCerts.size()]))); | |
| 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[currentCerts.size()]))); | |
| 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 <code>"${property-name}"</code> into | |
| * <code>System.getProperty("property-name")</code>. | |
| */ | |
| 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); | |
| } | |
| } |