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