001 package sysModel;
002
003 /* PolicyFile.java -- policy file reader.
004 Copyright (C) 2003 Casey Marshall <rsdio@metastatic.org>
005
006 This program is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 This program is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with this program; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
019 02111-1307 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 // package gnu.java.security;
040
041 import java.io.File;
042 import java.io.IOException;
043 import java.io.InputStreamReader;
044 import java.io.StreamTokenizer;
045 import java.lang.reflect.Constructor;
046 import java.net.MalformedURLException;
047 import java.net.URL;
048 import java.security.*;
049 import java.security.cert.Certificate;
050 import java.security.cert.X509Certificate;
051 import java.util.*;
052
053 /**
054 * An implementation of a {@link Policy} object whose permissions are specified by a <em>policy
055 * file</em>.
056 * <p/>
057 * <p>The approximate syntax of policy files is:</p>
058 * <p/>
059 * <pre>
060 * policyFile ::= keystoreOrGrantEntries ;
061 * <p/>
062 * keystoreOrGrantEntries ::= keystoreOrGrantEntry |
063 * keystoreOrGrantEntries keystoreOrGrantEntry |
064 * EMPTY ;
065 * <p/>
066 * keystoreOrGrantEntry ::= keystoreEntry | grantEntry ;
067 * <p/>
068 * keystoreEntry ::= "keystore" keystoreUrl ';' |
069 * "keystore" keystoreUrl ',' keystoreAlgorithm ';' ;
070 * <p/>
071 * keystoreUrl ::= URL ;
072 * keystoreAlgorithm ::= STRING ;
073 * <p/>
074 * grantEntry ::= "grant" domainParameters '{' permissions '}' ';'
075 * <p/>
076 * domainParameters ::= domainParameter |
077 * domainParameter ',' domainParameters ;
078 * <p/>
079 * domainParameter ::= "signedBy" signerNames |
080 * "codeBase" codeBaseUrl |
081 * "principal" principalClassName principalName |
082 * "principal" principalName ;
083 * <p/>
084 * signerNames ::= quotedString ;
085 * codeBaseUrl ::= URL ;
086 * principalClassName ::= STRING ;
087 * principalName ::= quotedString ;
088 * <p/>
089 * quotedString ::= quoteChar STRING quoteChar ;
090 * quoteChar ::= '"' | '\'';
091 * <p/>
092 * permissions ::= permission | permissions permission ;
093 * <p/>
094 * permission ::= "permission" permissionClassName permissionTarget permissionAction |
095 * "permission" permissionClassName permissionTarget |
096 * "permission" permissionClassName;
097 * </pre>
098 * <p/>
099 * <p>Comments are either form of Java comments. Keystore entries only affect subsequent grant entries, so if a grant
100 * entry preceeds a keystore entry, that grant entry is not affected by that keystore entry. Certian instances of
101 * <code>${property-name}</code> will be replaced with <code>System.getProperty("property-name")</code> in quoted
102 * strings.</p>
103 * <p/>
104 * <p>This class will load the following files when created or refreshed, in order:</p>
105 * <p/>
106 * <ol> <li>The file <code>${java.home}/lib/security/java.policy</code>.</li> <li>All URLs specified by security
107 * properties <code>"policy.file.<i>n</i>"</code>, for increasing <i>n</i> starting from 1. The sequence stops at the
108 * first undefined property, so you must set <code>"policy.file.1"</code> if you also set <code>"policy.file.2"</code>,
109 * and so on.</li> <li>The URL specified by the property <code>"java.security.policy"</code>.</li> </ol>
110 *
111 * @author Casey Marshall (rsdio@metastatic.org)
112 * @see Policy
113 */
114 public final class PolicyFile extends Policy {
115
116 // Constants and fields.
117 // -------------------------------------------------------------------------
118
119 private static final boolean DEBUG = false; // = true;
120
121 private static void debug(String msg) {
122 if (DEBUG) {
123 System.err.print(">> PolicyFile message: ");
124 System.err.println(msg);
125 }
126 }
127
128 private static void debug(Throwable t) {
129 if (DEBUG) {
130 System.err.println(">> PolicyFile throws: ");
131 t.printStackTrace(System.err);
132 }
133 }
134
135 private static final String DEFAULT_POLICY = System.getProperty("java.home")
136 + System.getProperty("file.separator") + "lib"
137 + System.getProperty("file.separator") + "security"
138 + System.getProperty("file.separator") + "java.policy";
139
140 private final Map<CodeSource,Permissions> cs2pc;
141
142 // Constructors.
143 // -------------------------------------------------------------------------
144
145 public PolicyFile() {
146 cs2pc = new HashMap<CodeSource,Permissions>();
147 refresh();
148 }
149
150 // Instance methods.
151 // -------------------------------------------------------------------------
152
153 public PermissionCollection getPermissions(CodeSource codeSource) {
154 Permissions perms = new Permissions();
155 for(Iterator it = cs2pc.entrySet().iterator(); it.hasNext();) {
156 Map.Entry e = (Map.Entry)it.next();
157 CodeSource cs = (CodeSource)e.getKey();
158 if (cs.implies(codeSource)) {
159 debug(cs + " -> " + codeSource);
160 PermissionCollection pc = (PermissionCollection)e.getValue();
161 for(Enumeration ee = pc.elements(); ee.hasMoreElements();) {
162 perms.add((Permission)ee.nextElement());
163 }
164 }
165 else {
166 debug(cs + " !-> " + codeSource);
167 }
168 }
169 perms.setReadOnly();
170 return perms;
171 }
172
173 public void refresh() {
174 cs2pc.clear();
175 List<URL> policyFiles = new LinkedList<URL>();
176 try {
177 policyFiles.add(new File(DEFAULT_POLICY).toURL());
178 policyFiles.addAll(AccessController.doPrivileged(new PrivilegedExceptionAction<List<URL>>() {
179 public List<URL> run() throws Exception {
180 LinkedList<URL> l = new LinkedList<URL>();
181 for(int i = 1; ; i++) {
182 String s = Security.getProperty("policy.file." + i);
183 debug("policy.file." + i + '=' + s);
184 if (null == s) {
185 break;
186 }
187 l.add(new URL(s));
188 }
189 String s = System.getProperty("java.security.policy");
190 debug("java.security.policy=" + s);
191 if (null != s) {
192 l.add(new URL(s));
193 }
194 return l;
195 }
196 }));
197 }
198 catch(PrivilegedActionException pae) {
199 debug(pae);
200 }
201 catch(MalformedURLException mue) {
202 debug(mue);
203 }
204 for(Iterator it = policyFiles.iterator(); it.hasNext();) {
205 try {
206 URL url = (URL)it.next();
207 parse(url);
208 }
209 catch(IOException ioe) {
210 debug(ioe);
211 }
212 }
213 }
214
215 public String toString() {
216 return super.toString() + " [ " + cs2pc.toString() + " ]";
217 }
218
219 // Own methods.
220 // -------------------------------------------------------------------------
221
222 private static final int STATE_BEGIN = 0;
223 private static final int STATE_GRANT = 1;
224 private static final int STATE_PERMS = 2;
225
226 /**
227 * Parse a policy file, incorporating the permission definitions described therein.
228 *
229 * @param url The URL of the policy file to read.
230 *
231 * @throws IOException if an I/O error occurs, or if the policy file cannot be parsed.
232 */
233 private void parse(URL url) throws IOException {
234 StreamTokenizer in = new StreamTokenizer(new InputStreamReader(url.openStream()));
235 in.resetSyntax();
236 in.slashSlashComments(true);
237 in.slashStarComments(true);
238 in.wordChars('A', 'Z');
239 in.wordChars('a', 'z');
240 in.wordChars('0', '9');
241 in.wordChars('.', '.');
242 in.wordChars('_', '_');
243 in.wordChars('$', '$');
244 in.whitespaceChars(' ', ' ');
245 in.whitespaceChars('\t', '\t');
246 in.whitespaceChars('\f', '\f');
247 in.whitespaceChars('\n', '\n');
248 in.whitespaceChars('\r', '\r');
249 in.quoteChar('\'');
250 in.quoteChar('"');
251
252 int tok;
253 int state = STATE_BEGIN;
254 List<KeyStore> keystores = new LinkedList<KeyStore>();
255 URL currentBase = null;
256 List<Certificate> currentCerts = new LinkedList<Certificate>();
257 Permissions currentPerms = new Permissions();
258 while(StreamTokenizer.TT_EOF != (tok = in.nextToken())) {
259 switch(tok) {
260 case '{':
261 if (STATE_GRANT != state) {
262 error(url, in, "spurious '{'");
263 }
264 state = STATE_PERMS;
265 tok = in.nextToken();
266 break;
267 case '}':
268 if (STATE_PERMS != state) {
269 error(url, in, "spurious '}'");
270 }
271 state = STATE_BEGIN;
272 currentPerms.setReadOnly();
273 Certificate[] c = null;
274 if (!currentCerts.isEmpty()) {
275 c = (Certificate[])currentCerts.toArray(new Certificate[currentCerts.size()]);
276 }
277 cs2pc.put(new CodeSource(currentBase, c), currentPerms);
278 currentCerts.clear();
279 currentPerms = new Permissions();
280 currentBase = null;
281 tok = in.nextToken();
282 if (';' != tok) {
283 in.pushBack();
284 }
285 continue;
286 }
287 if (StreamTokenizer.TT_WORD != tok) {
288 error(url, in, "expecting word token");
289 }
290
291 // keystore "<keystore-path>" [',' "<keystore-type>"] ';'
292 if ("keystore".equalsIgnoreCase(in.sval)) {
293 String alg = KeyStore.getDefaultType();
294 tok = in.nextToken();
295 if ('"' != tok && '\'' != tok) {
296 error(url, in, "expecting key store URL");
297 }
298 String store = in.sval;
299 tok = in.nextToken();
300 if (',' == tok) {
301 tok = in.nextToken();
302 if ('"' != tok && '\'' != tok) {
303 error(url, in, "expecting key store type");
304 }
305 alg = in.sval;
306 tok = in.nextToken();
307 }
308 if (';' != tok) {
309 error(url, in, "expecting semicolon");
310 }
311 try {
312 KeyStore keystore = KeyStore.getInstance(alg);
313 keystore.load(new URL(url, store).openStream(), null);
314 keystores.add(keystore);
315 }
316 catch(Exception x) {
317 error(url, in, x.toString());
318 }
319 }
320 else if ("grant".equalsIgnoreCase(in.sval)) {
321 if (STATE_BEGIN != state) {
322 error(url, in, "extraneous grant keyword");
323 }
324 state = STATE_GRANT;
325 }
326 else if ("signedBy".equalsIgnoreCase(in.sval)) {
327 if (STATE_GRANT != state && STATE_PERMS != state) {
328 error(url, in, "spurious 'signedBy'");
329 }
330 if (keystores.isEmpty()) {
331 error(url, in, "'signedBy' with no keystores");
332 }
333 tok = in.nextToken();
334 if ('"' != tok && '\'' != tok) {
335 error(url, in, "expecting signedBy name");
336 }
337 StringTokenizer st = new StringTokenizer(in.sval, ",");
338 while(st.hasMoreTokens()) {
339 String alias = st.nextToken();
340 for(Iterator it = keystores.iterator(); it.hasNext();) {
341 KeyStore keystore = (KeyStore)it.next();
342 try {
343 if (keystore.isCertificateEntry(alias)) {
344 currentCerts.add(keystore.getCertificate(alias));
345 }
346 }
347 catch(KeyStoreException kse) {
348 error(url, in, kse.toString());
349 }
350 }
351 }
352 tok = in.nextToken();
353 if (',' != tok) {
354 if (STATE_GRANT != state) {
355 error(url, in, "spurious ','");
356 }
357 in.pushBack();
358 }
359 }
360 else if ("codeBase".equalsIgnoreCase(in.sval)) {
361 if (STATE_GRANT != state) {
362 error(url, in, "spurious 'codeBase'");
363 }
364 tok = in.nextToken();
365 if ('"' != tok && '\'' != tok) {
366 error(url, in, "expecting code base URL");
367 }
368 String base = expand(in.sval);
369 if ('/' != File.separatorChar) {
370 base = base.replace(File.separatorChar, '/');
371 }
372 try {
373 currentBase = new URL(base);
374 }
375 catch(MalformedURLException mue) {
376 error(url, in, mue.toString());
377 }
378 tok = in.nextToken();
379 if (',' != tok) {
380 in.pushBack();
381 }
382 }
383 else if ("principal".equalsIgnoreCase(in.sval)) {
384 if (STATE_GRANT != state) {
385 error(url, in, "spurious 'principal'");
386 }
387 tok = in.nextToken();
388 if (StreamTokenizer.TT_WORD == tok) {
389 tok = in.nextToken();
390 if ('"' != tok && '\'' != tok) {
391 error(url, in, "expecting principal name");
392 }
393 String name = in.sval;
394 Principal p = null;
395 try {
396 Class pclass = Class.forName(in.sval);
397 Constructor c =
398 pclass.getConstructor(new Class[]{String.class});
399 p = (Principal)c.newInstance(new Object[]{name});
400 }
401 catch(Exception x) {
402 error(url, in, x.toString());
403 }
404 for(Iterator it = keystores.iterator(); it.hasNext();) {
405 KeyStore ks = (KeyStore)it.next();
406 try {
407 for(Enumeration e = ks.aliases(); e.hasMoreElements();) {
408 String alias = (String)e.nextElement();
409 if (ks.isCertificateEntry(alias)) {
410 Certificate cert = ks.getCertificate(alias);
411 if (!(cert instanceof X509Certificate)) {
412 continue;
413 }
414 if (p.equals(((X509Certificate)cert).getSubjectDN()) ||
415 p.equals(((X509Certificate)cert).getSubjectX500Principal())) {
416 currentCerts.add(cert);
417 }
418 }
419 }
420 }
421 catch(KeyStoreException kse) {
422 error(url, in, kse.toString());
423 }
424 }
425 }
426 else if ('"' == tok || '\'' == tok) {
427 String alias = in.sval;
428 for(Iterator it = keystores.iterator(); it.hasNext();) {
429 KeyStore ks = (KeyStore)it.next();
430 try {
431 if (ks.isCertificateEntry(alias)) {
432 currentCerts.add(ks.getCertificate(alias));
433 }
434 }
435 catch(KeyStoreException kse) {
436 error(url, in, kse.toString());
437 }
438 }
439 }
440 else {
441 error(url, in, "expecting principal");
442 }
443 tok = in.nextToken();
444 if (',' != tok) {
445 in.pushBack();
446 }
447 }
448 else if ("permission".equalsIgnoreCase(in.sval)) {
449 if (STATE_PERMS != state) {
450 error(url, in, "spurious 'permission'");
451 }
452 tok = in.nextToken();
453 if (StreamTokenizer.TT_WORD != tok) {
454 error(url, in, "expecting permission class name");
455 }
456 String className = in.sval;
457 Class clazz = null;
458 try {
459 clazz = Class.forName(className);
460 }
461 catch(ClassNotFoundException cnfe) {
462 }
463 tok = in.nextToken();
464 if (';' == tok) {
465 if (null == clazz) {
466 currentPerms.add(new UnresolvedPermission(className,
467 null, null, (Certificate[])currentCerts.toArray(new Certificate[0])));
468 continue;
469 }
470 try {
471 currentPerms.add((Permission)clazz.newInstance());
472 }
473 catch(Exception x) {
474 error(url, in, x.toString());
475 }
476 continue;
477 }
478 if ('"' != tok && '\'' != tok) {
479 error(url, in, "expecting permission target");
480 }
481 String target = expand(in.sval);
482 tok = in.nextToken();
483 if (';' == tok) {
484 if (null == clazz) {
485 currentPerms.add(new UnresolvedPermission(className,
486 target, null, (Certificate[])currentCerts.toArray(new Certificate[0])));
487 continue;
488 }
489 try {
490 Constructor c =
491 clazz.getConstructor(new Class[]{String.class});
492 currentPerms.add((Permission)c.newInstance(new Object[]{target}));
493 }
494 catch(Exception x) {
495 error(url, in, x.toString());
496 }
497 continue;
498 }
499 if (',' != tok) {
500 error(url, in, "expecting ','");
501 }
502 tok = in.nextToken();
503 if (StreamTokenizer.TT_WORD == tok) {
504 if (!"signedBy".equalsIgnoreCase(in.sval)) {
505 error(url, in, "expecting 'signedBy'");
506 }
507 try {
508 Constructor c =
509 clazz.getConstructor(new Class[]{String.class});
510 currentPerms.add((Permission)c.newInstance(new Object[]{target}));
511 }
512 catch(Exception x) {
513 error(url, in, x.toString());
514 }
515 in.pushBack();
516 continue;
517 }
518 if ('"' != tok && '\'' != tok) {
519 error(url, in, "expecting permission action");
520 }
521 String action = in.sval;
522 if (null == clazz) {
523 currentPerms.add(new UnresolvedPermission(className,
524 target, action, (Certificate[])currentCerts.toArray(new Certificate[0])));
525 continue;
526 }
527 else {
528 try {
529 Constructor c = clazz.getConstructor(new Class[]{String.class, String.class});
530 currentPerms.add((Permission)c.newInstance(new Object[]{target, action}));
531 }
532 catch(Exception x) {
533 error(url, in, x.toString());
534 }
535 }
536 tok = in.nextToken();
537 if (';' != tok && ',' != tok) {
538 error(url, in, "expecting ';' or ','");
539 }
540 }
541 }
542 }
543
544 /**
545 * Expand all instances of <code>"${property-name}"</code> into <code>System.getProperty("property-name")</code>.
546 */
547 private static String expand(String s) {
548 StringBuffer result = new StringBuffer();
549 StringBuffer prop = new StringBuffer();
550 int state = 0;
551 for(int i = 0; i < s.length(); i++) {
552 switch(state) {
553 case 0:
554 if ('$' == s.charAt(i)) {
555 state = 1;
556 }
557 else {
558 result.append(s.charAt(i));
559 }
560 break;
561 case 1:
562 if ('{' == s.charAt(i)) {
563 state = 2;
564 }
565 else {
566 state = 0;
567 result.append('$').append(s.charAt(i));
568 }
569 break;
570 case 2:
571 if ('}' == s.charAt(i)) {
572 String p = prop.toString();
573 if ("/".equals(p)) {
574 p = "file.separator";
575 }
576 p = System.getProperty(p);
577 if (null == p) {
578 p = "";
579 }
580 result.append(p);
581 prop.setLength(0);
582 state = 0;
583 }
584 else {
585 prop.append(s.charAt(i));
586 }
587 break;
588 }
589 }
590 if (0 != state) {
591 result.append('$').append('{').append(prop);
592 }
593 return result.toString();
594 }
595
596 /**
597 * I miss macros.
598 */
599 private static void error(URL base, StreamTokenizer in, String msg)
600 throws IOException {
601 throw new IOException(base + ":" + in.lineno() + ": " + msg);
602 }
603 }