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