1 /* 2 * Copyright (c) 1997, 2003, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import java.io.Serializable; 29 import java.io.IOException; 30 import java.security.*; 31 import java.util.Map; 32 import java.util.HashMap; 33 import java.util.Enumeration; 34 import java.util.Hashtable; 35 import java.util.Collections; 36 import java.io.ObjectStreamField; 37 import java.io.ObjectOutputStream; 38 import java.io.ObjectInputStream; 39 import java.io.IOException; 40 import sun.security.util.SecurityConstants; 41 42 /** 43 * This class is for property permissions. 44 * 45 * <P> 46 * The name is the name of the property ("java.home", 47 * "os.name", etc). The naming 48 * convention follows the hierarchical property naming convention. 49 * Also, an asterisk 50 * may appear at the end of the name, following a ".", or by itself, to 51 * signify a wildcard match. For example: "java.*" or "*" is valid, 52 * "*java" or "a*b" is not valid. 53 * <P> 54 * <P> 55 * The actions to be granted are passed to the constructor in a string containing 56 * a list of one or more comma-separated keywords. The possible keywords are 57 * "read" and "write". Their meaning is defined as follows: 58 * <P> 59 * <DL> 60 * <DT> read 61 * <DD> read permission. Allows <code>System.getProperty</code> to 62 * be called. 63 * <DT> write 64 * <DD> write permission. Allows <code>System.setProperty</code> to 65 * be called. 66 * </DL> 67 * <P> 68 * The actions string is converted to lowercase before processing. 69 * <P> 70 * Care should be taken before granting code permission to access 71 * certain system properties. For example, granting permission to 72 * access the "java.home" system property gives potentially malevolent 73 * code sensitive information about the system environment (the Java 74 * installation directory). Also, granting permission to access 75 * the "user.name" and "user.home" system properties gives potentially 76 * malevolent code sensitive information about the user environment 77 * (the user's account name and home directory). 78 * 79 * @see java.security.BasicPermission 80 * @see java.security.Permission 81 * @see java.security.Permissions 82 * @see java.security.PermissionCollection 83 * @see java.lang.SecurityManager 84 * 85 * 86 * @author Roland Schemers 87 * @since 1.2 88 * 89 * @serial exclude 90 */ 91 92 public final class PropertyPermission extends BasicPermission { 93 94 /** 95 * Read action. 96 */ 97 private final static int READ = 0x1; 98 99 /** 100 * Write action. 101 */ 102 private final static int WRITE = 0x2; 103 /** 104 * All actions (read,write); 105 */ 106 private final static int ALL = READ|WRITE; 107 /** 108 * No actions. 109 */ 110 private final static int NONE = 0x0; 111 112 /** 113 * The actions mask. 114 * 115 */ 116 private transient int mask; 117 118 /** 119 * The actions string. 120 * 121 * @serial 122 */ 123 private String actions; // Left null as long as possible, then 124 // created and re-used in the getAction function. 125 126 /** 127 * initialize a PropertyPermission object. Common to all constructors. 128 * Also called during de-serialization. 129 * 130 * @param mask the actions mask to use. 131 * 132 */ 133 134 private void init(int mask) 135 { 136 137 if ((mask & ALL) != mask) 138 throw new IllegalArgumentException("invalid actions mask"); 139 140 if (mask == NONE) 141 throw new IllegalArgumentException("invalid actions mask"); 142 143 if (getName() == null) 144 throw new NullPointerException("name can't be null"); 145 146 this.mask = mask; 147 } 148 149 /** 150 * Creates a new PropertyPermission object with the specified name. 151 * The name is the name of the system property, and 152 * <i>actions</i> contains a comma-separated list of the 153 * desired actions granted on the property. Possible actions are 154 * "read" and "write". 155 * 156 * @param name the name of the PropertyPermission. 157 * @param actions the actions string. 158 * 159 * @throws NullPointerException if <code>name</code> is <code>null</code>. 160 * @throws IllegalArgumentException if <code>name</code> is empty or if 161 * <code>actions</code> is invalid. 162 */ 163 164 public PropertyPermission(String name, String actions) 165 { 166 super(name,actions); 167 init(getMask(actions)); 168 } 169 170 /** 171 * Checks if this PropertyPermission object "implies" the specified 172 * permission. 173 * <P> 174 * More specifically, this method returns true if:<p> 175 * <ul> 176 * <li> <i>p</i> is an instanceof PropertyPermission,<p> 177 * <li> <i>p</i>'s actions are a subset of this 178 * object's actions, and <p> 179 * <li> <i>p</i>'s name is implied by this object's 180 * name. For example, "java.*" implies "java.home". 181 * </ul> 182 * @param p the permission to check against. 183 * 184 * @return true if the specified permission is implied by this object, 185 * false if not. 186 */ 187 public boolean implies(Permission p) { 188 if (!(p instanceof PropertyPermission)) 189 return false; 190 191 PropertyPermission that = (PropertyPermission) p; 192 193 // we get the effective mask. i.e., the "and" of this and that. 194 // They must be equal to that.mask for implies to return true. 195 196 return ((this.mask & that.mask) == that.mask) && super.implies(that); 197 } 198 199 200 /** 201 * Checks two PropertyPermission objects for equality. Checks that <i>obj</i> is 202 * a PropertyPermission, and has the same name and actions as this object. 203 * <P> 204 * @param obj the object we are testing for equality with this object. 205 * @return true if obj is a PropertyPermission, and has the same name and 206 * actions as this PropertyPermission object. 207 */ 208 public boolean equals(Object obj) { 209 if (obj == this) 210 return true; 211 212 if (! (obj instanceof PropertyPermission)) 213 return false; 214 215 PropertyPermission that = (PropertyPermission) obj; 216 217 return (this.mask == that.mask) && 218 (this.getName().equals(that.getName())); 219 } 220 221 /** 222 * Returns the hash code value for this object. 223 * The hash code used is the hash code of this permissions name, that is, 224 * <code>getName().hashCode()</code>, where <code>getName</code> is 225 * from the Permission superclass. 226 * 227 * @return a hash code value for this object. 228 */ 229 230 public int hashCode() { 231 return this.getName().hashCode(); 232 } 233 234 235 /** 236 * Converts an actions String to an actions mask. 237 * 238 * @param action the action string. 239 * @return the actions mask. 240 */ 241 private static int getMask(String actions) { 242 243 int mask = NONE; 244 245 if (actions == null) { 246 return mask; 247 } 248 249 // Check against use of constants (used heavily within the JDK) 250 if (actions == SecurityConstants.PROPERTY_READ_ACTION) { 251 return READ; 252 } if (actions == SecurityConstants.PROPERTY_WRITE_ACTION) { 253 return WRITE; 254 } else if (actions == SecurityConstants.PROPERTY_RW_ACTION) { 255 return READ|WRITE; 256 } 257 258 char[] a = actions.toCharArray(); 259 260 int i = a.length - 1; 261 if (i < 0) 262 return mask; 263 264 while (i != -1) { 265 char c; 266 267 // skip whitespace 268 while ((i!=-1) && ((c = a[i]) == ' ' || 269 c == '\r' || 270 c == '\n' || 271 c == '\f' || 272 c == '\t')) 273 i--; 274 275 // check for the known strings 276 int matchlen; 277 278 if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') && 279 (a[i-2] == 'e' || a[i-2] == 'E') && 280 (a[i-1] == 'a' || a[i-1] == 'A') && 281 (a[i] == 'd' || a[i] == 'D')) 282 { 283 matchlen = 4; 284 mask |= READ; 285 286 } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') && 287 (a[i-3] == 'r' || a[i-3] == 'R') && 288 (a[i-2] == 'i' || a[i-2] == 'I') && 289 (a[i-1] == 't' || a[i-1] == 'T') && 290 (a[i] == 'e' || a[i] == 'E')) 291 { 292 matchlen = 5; 293 mask |= WRITE; 294 295 } else { 296 // parse error 297 throw new IllegalArgumentException( 298 "invalid permission: " + actions); 299 } 300 301 // make sure we didn't just match the tail of a word 302 // like "ackbarfaccept". Also, skip to the comma. 303 boolean seencomma = false; 304 while (i >= matchlen && !seencomma) { 305 switch(a[i-matchlen]) { 306 case ',': 307 seencomma = true; 308 /*FALLTHROUGH*/ 309 case ' ': case '\r': case '\n': 310 case '\f': case '\t': 311 break; 312 default: 313 throw new IllegalArgumentException( 314 "invalid permission: " + actions); 315 } 316 i--; 317 } 318 319 // point i at the location of the comma minus one (or -1). 320 i -= matchlen; 321 } 322 323 return mask; 324 } 325 326 327 /** 328 * Return the canonical string representation of the actions. 329 * Always returns present actions in the following order: 330 * read, write. 331 * 332 * @return the canonical string representation of the actions. 333 */ 334 static String getActions(int mask) 335 { 336 StringBuilder sb = new StringBuilder(); 337 boolean comma = false; 338 339 if ((mask & READ) == READ) { 340 comma = true; 341 sb.append("read"); 342 } 343 344 if ((mask & WRITE) == WRITE) { 345 if (comma) sb.append(','); 346 else comma = true; 347 sb.append("write"); 348 } 349 return sb.toString(); 350 } 351 352 /** 353 * Returns the "canonical string representation" of the actions. 354 * That is, this method always returns present actions in the following order: 355 * read, write. For example, if this PropertyPermission object 356 * allows both write and read actions, a call to <code>getActions</code> 357 * will return the string "read,write". 358 * 359 * @return the canonical string representation of the actions. 360 */ 361 public String getActions() 362 { 363 if (actions == null) 364 actions = getActions(this.mask); 365 366 return actions; 367 } 368 369 /** 370 * Return the current action mask. 371 * Used by the PropertyPermissionCollection 372 * 373 * @return the actions mask. 374 */ 375 376 int getMask() { 377 return mask; 378 } 379 380 /** 381 * Returns a new PermissionCollection object for storing 382 * PropertyPermission objects. 383 * <p> 384 * 385 * @return a new PermissionCollection object suitable for storing 386 * PropertyPermissions. 387 */ 388 389 public PermissionCollection newPermissionCollection() { 390 return new PropertyPermissionCollection(); 391 } 392 393 394 private static final long serialVersionUID = 885438825399942851L; 395 396 /** 397 * WriteObject is called to save the state of the PropertyPermission 398 * to a stream. The actions are serialized, and the superclass 399 * takes care of the name. 400 */ 401 private synchronized void writeObject(java.io.ObjectOutputStream s) 402 throws IOException 403 { 404 // Write out the actions. The superclass takes care of the name 405 // call getActions to make sure actions field is initialized 406 if (actions == null) 407 getActions(); 408 s.defaultWriteObject(); 409 } 410 411 /** 412 * readObject is called to restore the state of the PropertyPermission from 413 * a stream. 414 */ 415 private synchronized void readObject(java.io.ObjectInputStream s) 416 throws IOException, ClassNotFoundException 417 { 418 // Read in the action, then initialize the rest 419 s.defaultReadObject(); 420 init(getMask(actions)); 421 } 422 } 423 424 /** 425 * A PropertyPermissionCollection stores a set of PropertyPermission 426 * permissions. 427 * 428 * @see java.security.Permission 429 * @see java.security.Permissions 430 * @see java.security.PermissionCollection 431 * 432 * 433 * @author Roland Schemers 434 * 435 * @serial include 436 */ 437 final class PropertyPermissionCollection extends PermissionCollection 438 implements Serializable 439 { 440 441 /** 442 * Key is property name; value is PropertyPermission. 443 * Not serialized; see serialization section at end of class. 444 */ 445 private transient Map perms; 446 447 /** 448 * Boolean saying if "*" is in the collection. 449 * 450 * @see #serialPersistentFields 451 */ 452 // No sync access; OK for this to be stale. 453 private boolean all_allowed; 454 455 /** 456 * Create an empty PropertyPermissions object. 457 * 458 */ 459 460 public PropertyPermissionCollection() { 461 perms = new HashMap(32); // Capacity for default policy 462 all_allowed = false; 463 } 464 465 /** 466 * Adds a permission to the PropertyPermissions. The key for the hash is 467 * the name. 468 * 469 * @param permission the Permission object to add. 470 * 471 * @exception IllegalArgumentException - if the permission is not a 472 * PropertyPermission 473 * 474 * @exception SecurityException - if this PropertyPermissionCollection 475 * object has been marked readonly 476 */ 477 478 public void add(Permission permission) 479 { 480 if (! (permission instanceof PropertyPermission)) 481 throw new IllegalArgumentException("invalid permission: "+ 482 permission); 483 if (isReadOnly()) 484 throw new SecurityException( 485 "attempt to add a Permission to a readonly PermissionCollection"); 486 487 PropertyPermission pp = (PropertyPermission) permission; 488 String propName = pp.getName(); 489 490 synchronized (this) { 491 PropertyPermission existing = (PropertyPermission) perms.get(propName); 492 493 if (existing != null) { 494 int oldMask = existing.getMask(); 495 int newMask = pp.getMask(); 496 if (oldMask != newMask) { 497 int effective = oldMask | newMask; 498 String actions = PropertyPermission.getActions(effective); 499 perms.put(propName, new PropertyPermission(propName, actions)); 500 } 501 } else { 502 perms.put(propName, permission); 503 } 504 } 505 506 if (!all_allowed) { 507 if (propName.equals("*")) 508 all_allowed = true; 509 } 510 } 511 512 /** 513 * Check and see if this set of permissions implies the permissions 514 * expressed in "permission". 515 * 516 * @param p the Permission object to compare 517 * 518 * @return true if "permission" is a proper subset of a permission in 519 * the set, false if not. 520 */ 521 522 public boolean implies(Permission permission) 523 { 524 if (! (permission instanceof PropertyPermission)) 525 return false; 526 527 PropertyPermission pp = (PropertyPermission) permission; 528 PropertyPermission x; 529 530 int desired = pp.getMask(); 531 int effective = 0; 532 533 // short circuit if the "*" Permission was added 534 if (all_allowed) { 535 synchronized (this) { 536 x = (PropertyPermission) perms.get("*"); 537 } 538 if (x != null) { 539 effective |= x.getMask(); 540 if ((effective & desired) == desired) 541 return true; 542 } 543 } 544 545 // strategy: 546 // Check for full match first. Then work our way up the 547 // name looking for matches on a.b.* 548 549 String name = pp.getName(); 550 //System.out.println("check "+name); 551 552 synchronized (this) { 553 x = (PropertyPermission) perms.get(name); 554 } 555 556 if (x != null) { 557 // we have a direct hit! 558 effective |= x.getMask(); 559 if ((effective & desired) == desired) 560 return true; 561 } 562 563 // work our way up the tree... 564 int last, offset; 565 566 offset = name.length()-1; 567 568 while ((last = name.lastIndexOf(".", offset)) != -1) { 569 570 name = name.substring(0, last+1) + "*"; 571 //System.out.println("check "+name); 572 synchronized (this) { 573 x = (PropertyPermission) perms.get(name); 574 } 575 576 if (x != null) { 577 effective |= x.getMask(); 578 if ((effective & desired) == desired) 579 return true; 580 } 581 offset = last -1; 582 } 583 584 // we don't have to check for "*" as it was already checked 585 // at the top (all_allowed), so we just return false 586 return false; 587 } 588 589 /** 590 * Returns an enumeration of all the PropertyPermission objects in the 591 * container. 592 * 593 * @return an enumeration of all the PropertyPermission objects. 594 */ 595 596 public Enumeration elements() { 597 // Convert Iterator of Map values into an Enumeration 598 synchronized (this) { 599 return Collections.enumeration(perms.values()); 600 } 601 } 602 603 private static final long serialVersionUID = 7015263904581634791L; 604 605 // Need to maintain serialization interoperability with earlier releases, 606 // which had the serializable field: 607 // 608 // Table of permissions. 609 // 610 // @serial 611 // 612 // private Hashtable permissions; 613 /** 614 * @serialField permissions java.util.Hashtable 615 * A table of the PropertyPermissions. 616 * @serialField all_allowed boolean 617 * boolean saying if "*" is in the collection. 618 */ 619 private static final ObjectStreamField[] serialPersistentFields = { 620 new ObjectStreamField("permissions", Hashtable.class), 621 new ObjectStreamField("all_allowed", Boolean.TYPE), 622 }; 623 624 /** 625 * @serialData Default fields. 626 */ 627 /* 628 * Writes the contents of the perms field out as a Hashtable for 629 * serialization compatibility with earlier releases. all_allowed 630 * unchanged. 631 */ 632 private void writeObject(ObjectOutputStream out) throws IOException { 633 // Don't call out.defaultWriteObject() 634 635 // Copy perms into a Hashtable 636 Hashtable permissions = new Hashtable(perms.size()*2); 637 synchronized (this) { 638 permissions.putAll(perms); 639 } 640 641 // Write out serializable fields 642 ObjectOutputStream.PutField pfields = out.putFields(); 643 pfields.put("all_allowed", all_allowed); 644 pfields.put("permissions", permissions); 645 out.writeFields(); 646 } 647 648 /* 649 * Reads in a Hashtable of PropertyPermissions and saves them in the 650 * perms field. Reads in all_allowed. 651 */ 652 private void readObject(ObjectInputStream in) throws IOException, 653 ClassNotFoundException { 654 // Don't call defaultReadObject() 655 656 // Read in serialized fields 657 ObjectInputStream.GetField gfields = in.readFields(); 658 659 // Get all_allowed 660 all_allowed = gfields.get("all_allowed", false); 661 662 // Get permissions 663 Hashtable permissions = (Hashtable)gfields.get("permissions", null); 664 perms = new HashMap(permissions.size()*2); 665 perms.putAll(permissions); 666 } 667 }