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 // Use object identity comparison against known-interned strings for 250 // performance benefit (these values are used heavily within the JDK). 251 if (actions == SecurityConstants.PROPERTY_READ_ACTION) { 252 return READ; 253 } if (actions == SecurityConstants.PROPERTY_WRITE_ACTION) { 254 return WRITE; 255 } else if (actions == SecurityConstants.PROPERTY_RW_ACTION) { 256 return READ|WRITE; 257 } 258 259 char[] a = actions.toCharArray(); 260 261 int i = a.length - 1; 262 if (i < 0) 263 return mask; 264 265 while (i != -1) { 266 char c; 267 268 // skip whitespace 269 while ((i!=-1) && ((c = a[i]) == ' ' || 270 c == '\r' || 271 c == '\n' || 272 c == '\f' || 273 c == '\t')) 274 i--; 275 276 // check for the known strings 277 int matchlen; 278 279 if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') && 280 (a[i-2] == 'e' || a[i-2] == 'E') && 281 (a[i-1] == 'a' || a[i-1] == 'A') && 282 (a[i] == 'd' || a[i] == 'D')) 283 { 284 matchlen = 4; 285 mask |= READ; 286 287 } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') && 288 (a[i-3] == 'r' || a[i-3] == 'R') && 289 (a[i-2] == 'i' || a[i-2] == 'I') && 290 (a[i-1] == 't' || a[i-1] == 'T') && 291 (a[i] == 'e' || a[i] == 'E')) 292 { 293 matchlen = 5; 294 mask |= WRITE; 295 296 } else { 297 // parse error 298 throw new IllegalArgumentException( 299 "invalid permission: " + actions); 300 } 301 302 // make sure we didn't just match the tail of a word 303 // like "ackbarfaccept". Also, skip to the comma. 304 boolean seencomma = false; 305 while (i >= matchlen && !seencomma) { 306 switch(a[i-matchlen]) { 307 case ',': 308 seencomma = true; 309 break; 310 case ' ': case '\r': case '\n': 311 case '\f': case '\t': 312 break; 313 default: 314 throw new IllegalArgumentException( 315 "invalid permission: " + actions); 316 } 317 i--; 318 } 319 320 // point i at the location of the comma minus one (or -1). 321 i -= matchlen; 322 } 323 324 return mask; 325 } 326 327 328 /** 329 * Return the canonical string representation of the actions. 330 * Always returns present actions in the following order: 331 * read, write. 332 * 333 * @return the canonical string representation of the actions. 334 */ 335 static String getActions(int mask) 336 { 337 StringBuilder sb = new StringBuilder(); 338 boolean comma = false; 339 340 if ((mask & READ) == READ) { 341 comma = true; 342 sb.append("read"); 343 } 344 345 if ((mask & WRITE) == WRITE) { 346 if (comma) sb.append(','); 347 else comma = true; 348 sb.append("write"); 349 } 350 return sb.toString(); 351 } 352 353 /** 354 * Returns the "canonical string representation" of the actions. 355 * That is, this method always returns present actions in the following order: 356 * read, write. For example, if this PropertyPermission object 357 * allows both write and read actions, a call to <code>getActions</code> 358 * will return the string "read,write". 359 * 360 * @return the canonical string representation of the actions. 361 */ 362 public String getActions() 363 { 364 if (actions == null) 365 actions = getActions(this.mask); 366 367 return actions; 368 } 369 370 /** 371 * Return the current action mask. 372 * Used by the PropertyPermissionCollection 373 * 374 * @return the actions mask. 375 */ 376 377 int getMask() { 378 return mask; 379 } 380 381 /** 382 * Returns a new PermissionCollection object for storing 383 * PropertyPermission objects. 384 * <p> 385 * 386 * @return a new PermissionCollection object suitable for storing 387 * PropertyPermissions. 388 */ 389 390 public PermissionCollection newPermissionCollection() { 391 return new PropertyPermissionCollection(); 392 } 393 394 395 private static final long serialVersionUID = 885438825399942851L; 396 397 /** 398 * WriteObject is called to save the state of the PropertyPermission 399 * to a stream. The actions are serialized, and the superclass 400 * takes care of the name. 401 */ 402 private synchronized void writeObject(java.io.ObjectOutputStream s) 403 throws IOException 404 { 405 // Write out the actions. The superclass takes care of the name 406 // call getActions to make sure actions field is initialized 407 if (actions == null) 408 getActions(); 409 s.defaultWriteObject(); 410 } 411 412 /** 413 * readObject is called to restore the state of the PropertyPermission from 414 * a stream. 415 */ 416 private synchronized void readObject(java.io.ObjectInputStream s) 417 throws IOException, ClassNotFoundException 418 { 419 // Read in the action, then initialize the rest 420 s.defaultReadObject(); 421 init(getMask(actions)); 422 } 423 } 424 425 /** 426 * A PropertyPermissionCollection stores a set of PropertyPermission 427 * permissions. 428 * 429 * @see java.security.Permission 430 * @see java.security.Permissions 431 * @see java.security.PermissionCollection 432 * 433 * 434 * @author Roland Schemers 435 * 436 * @serial include 437 */ 438 final class PropertyPermissionCollection extends PermissionCollection 439 implements Serializable 440 { 441 442 /** 443 * Key is property name; value is PropertyPermission. 444 * Not serialized; see serialization section at end of class. 445 */ 446 private transient Map<String, PropertyPermission> perms; 447 448 /** 449 * Boolean saying if "*" is in the collection. 450 * 451 * @see #serialPersistentFields 452 */ 453 // No sync access; OK for this to be stale. 454 private boolean all_allowed; 455 456 /** 457 * Create an empty PropertyPermissions object. 458 * 459 */ 460 461 public PropertyPermissionCollection() { 462 perms = new HashMap<>(32); // Capacity for default policy 463 all_allowed = false; 464 } 465 466 /** 467 * Adds a permission to the PropertyPermissions. The key for the hash is 468 * the name. 469 * 470 * @param permission the Permission object to add. 471 * 472 * @exception IllegalArgumentException - if the permission is not a 473 * PropertyPermission 474 * 475 * @exception SecurityException - if this PropertyPermissionCollection 476 * object has been marked readonly 477 */ 478 479 public void add(Permission permission) 480 { 481 if (! (permission instanceof PropertyPermission)) 482 throw new IllegalArgumentException("invalid permission: "+ 483 permission); 484 if (isReadOnly()) 485 throw new SecurityException( 486 "attempt to add a Permission to a readonly PermissionCollection"); 487 488 PropertyPermission pp = (PropertyPermission) permission; 489 String propName = pp.getName(); 490 491 synchronized (this) { 492 PropertyPermission existing = perms.get(propName); 493 494 if (existing != null) { 495 int oldMask = existing.getMask(); 496 int newMask = pp.getMask(); 497 if (oldMask != newMask) { 498 int effective = oldMask | newMask; 499 String actions = PropertyPermission.getActions(effective); 500 perms.put(propName, new PropertyPermission(propName, actions)); 501 } 502 } else { 503 perms.put(propName, pp); 504 } 505 } 506 507 if (!all_allowed) { 508 if (propName.equals("*")) 509 all_allowed = true; 510 } 511 } 512 513 /** 514 * Check and see if this set of permissions implies the permissions 515 * expressed in "permission". 516 * 517 * @param p the Permission object to compare 518 * 519 * @return true if "permission" is a proper subset of a permission in 520 * the set, false if not. 521 */ 522 523 public boolean implies(Permission permission) 524 { 525 if (! (permission instanceof PropertyPermission)) 526 return false; 527 528 PropertyPermission pp = (PropertyPermission) permission; 529 PropertyPermission x; 530 531 int desired = pp.getMask(); 532 int effective = 0; 533 534 // short circuit if the "*" Permission was added 535 if (all_allowed) { 536 synchronized (this) { 537 x = perms.get("*"); 538 } 539 if (x != null) { 540 effective |= x.getMask(); 541 if ((effective & desired) == desired) 542 return true; 543 } 544 } 545 546 // strategy: 547 // Check for full match first. Then work our way up the 548 // name looking for matches on a.b.* 549 550 String name = pp.getName(); 551 //System.out.println("check "+name); 552 553 synchronized (this) { 554 x = perms.get(name); 555 } 556 557 if (x != null) { 558 // we have a direct hit! 559 effective |= x.getMask(); 560 if ((effective & desired) == desired) 561 return true; 562 } 563 564 // work our way up the tree... 565 int last, offset; 566 567 offset = name.length()-1; 568 569 while ((last = name.lastIndexOf(".", offset)) != -1) { 570 571 name = name.substring(0, last+1) + "*"; 572 //System.out.println("check "+name); 573 synchronized (this) { 574 x = perms.get(name); 575 } 576 577 if (x != null) { 578 effective |= x.getMask(); 579 if ((effective & desired) == desired) 580 return true; 581 } 582 offset = last -1; 583 } 584 585 // we don't have to check for "*" as it was already checked 586 // at the top (all_allowed), so we just return false 587 return false; 588 } 589 590 /** 591 * Returns an enumeration of all the PropertyPermission objects in the 592 * container. 593 * 594 * @return an enumeration of all the PropertyPermission objects. 595 */ 596 @SuppressWarnings("unchecked") 597 public Enumeration<Permission> elements() { 598 // Convert Iterator of Map values into an Enumeration 599 synchronized (this) { 600 /** 601 * Casting to rawtype since Enumeration<PropertyPermission> 602 * cannot be directly cast to Enumeration<Permission> 603 */ 604 return (Enumeration)Collections.enumeration(perms.values()); 605 } 606 } 607 608 private static final long serialVersionUID = 7015263904581634791L; 609 610 // Need to maintain serialization interoperability with earlier releases, 611 // which had the serializable field: 612 // 613 // Table of permissions. 614 // 615 // @serial 616 // 617 // private Hashtable permissions; 618 /** 619 * @serialField permissions java.util.Hashtable 620 * A table of the PropertyPermissions. 621 * @serialField all_allowed boolean 622 * boolean saying if "*" is in the collection. 623 */ 624 private static final ObjectStreamField[] serialPersistentFields = { 625 new ObjectStreamField("permissions", Hashtable.class), 626 new ObjectStreamField("all_allowed", Boolean.TYPE), 627 }; 628 629 /** 630 * @serialData Default fields. 631 */ 632 /* 633 * Writes the contents of the perms field out as a Hashtable for 634 * serialization compatibility with earlier releases. all_allowed 635 * unchanged. 636 */ 637 private void writeObject(ObjectOutputStream out) throws IOException { 638 // Don't call out.defaultWriteObject() 639 640 // Copy perms into a Hashtable 641 Hashtable<String, Permission> permissions = 642 new Hashtable<>(perms.size()*2); 643 synchronized (this) { 644 permissions.putAll(perms); 645 } 646 647 // Write out serializable fields 648 ObjectOutputStream.PutField pfields = out.putFields(); 649 pfields.put("all_allowed", all_allowed); 650 pfields.put("permissions", permissions); 651 out.writeFields(); 652 } 653 654 /* 655 * Reads in a Hashtable of PropertyPermissions and saves them in the 656 * perms field. Reads in all_allowed. 657 */ 658 private void readObject(ObjectInputStream in) throws IOException, 659 ClassNotFoundException { 660 // Don't call defaultReadObject() 661 662 // Read in serialized fields 663 ObjectInputStream.GetField gfields = in.readFields(); 664 665 // Get all_allowed 666 all_allowed = gfields.get("all_allowed", false); 667 668 // Get permissions 669 @SuppressWarnings("unchecked") 670 Hashtable<String, PropertyPermission> permissions = 671 (Hashtable<String, PropertyPermission>)gfields.get("permissions", null); 672 perms = new HashMap<>(permissions.size()*2); 673 perms.putAll(permissions); 674 } 675 }