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