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 }