001    /* SocketPermission.java -- Class modeling permissions for socket operations
002       Copyright (C) 1998, 2000, 2001, 2002, 2004, 2006 Free Software
003       Foundation, Inc.
004    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011    
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    package java.net;
040    
041    import java.io.IOException;
042    import java.io.ObjectInputStream;
043    import java.io.ObjectOutputStream;
044    import java.io.Serializable;
045    import java.security.Permission;
046    import java.security.PermissionCollection;
047    import java.util.StringTokenizer;
048    
049    
050    /**
051     * This class models a specific set of permssions for connecting to a
052     * host.  There are two elements to this, the host/port combination and
053     * the permission list.
054     * <p>
055     * The host/port combination is specified as followed
056     * <p>
057     * <pre>
058     * hostname[:[-]port[-[port]]]
059     * </pre>
060     * <p>
061     * The hostname portion can be either a hostname or IP address.  If it is
062     * a hostname, a wildcard is allowed in hostnames.  This wildcard is a "*"
063     * and matches one or more characters.  Only one "*" may appear in the
064     * host and it must be the leftmost character.  For example,
065     * "*.urbanophile.com" matches all hosts in the "urbanophile.com" domain.
066     * <p>
067     * The port portion can be either a single value, or a range of values
068     * treated as inclusive.  The first or the last port value in the range
069     * can be omitted in which case either the minimum or maximum legal
070     * value for a port (respectively) is used by default.  Here are some
071     * examples:
072     * <p><ul>
073     * <li>8080 - Represents port 8080 only</li>
074     * <li>2000-3000 - Represents ports 2000 through 3000 inclusive</li>
075     * <li>-4000 - Represents ports 0 through 4000 inclusive</li>
076     * <li>1024- - Represents ports 1024 through 65535 inclusive</li>
077     * </ul><p>
078     * The permission list is a comma separated list of individual permissions.
079     * These individual permissions are:
080     * <p>
081     * <pre>
082     * accept
083     * connect
084     * listen
085     * resolve
086     * </pre>
087     * <p>
088     * The "listen" permission is only relevant if the host is localhost.  If
089     * any permission at all is specified, then resolve permission is implied to
090     * exist.
091     * <p>
092     * Here are a variety of examples of how to create SocketPermission's
093     * <p><pre>
094     * SocketPermission("www.urbanophile.com", "connect");
095     *   Can connect to any port on www.urbanophile.com
096     * SocketPermission("www.urbanophile.com:80", "connect,accept");
097     *   Can connect to or accept connections from www.urbanophile.com on port 80
098     * SocketPermission("localhost:1024-", "listen,accept,connect");
099     *   Can connect to, accept from, an listen on any local port number 1024
100     *   and up.
101     * SocketPermission("*.edu", "connect");
102     *   Can connect to any host in the edu domain
103     * SocketPermission("197.197.20.1", "accept");
104     *   Can accept connections from 197.197.20.1
105     * </pre><p>
106     *
107     * This class also supports IPv6 addresses.  These should be specified
108     * in either RFC 2732 format or in full uncompressed form.
109     *
110     * @since 1.2
111     *
112     * @author Written by Aaron M. Renn (arenn@urbanophile.com)
113     * @author Extensively modified by Gary Benson (gbenson@redhat.com)
114     */
115    public final class SocketPermission extends Permission implements Serializable
116    {
117      static final long serialVersionUID = -7204263841984476862L;
118    
119      /**
120       * A hostname (possibly wildcarded).  Will be set if and only if
121       * this object was initialized with a hostname.
122       */
123      private transient String hostname = null;
124    
125      /**
126       * An IP address (IPv4 or IPv6).  Will be set if and only if this
127       * object was initialized with a single literal IP address.
128       */  
129      private transient InetAddress address = null;
130      
131      /**
132       * A range of ports.
133       */
134      private transient int minport;
135      private transient int maxport;
136    
137      /**
138       * Values used for minimum and maximum ports when one or both bounds
139       * are omitted.  This class is essentially independent of the
140       * networking code it describes, so we do not limit ports to the
141       * usual network limits of 1 and 65535.
142       */
143      private static final int MIN_PORT = 0;
144      private static final int MAX_PORT = Integer.MAX_VALUE;
145    
146      /**
147       * The actions for which we have permission.  This field is present
148       * to make the serialized form correct and should not be used by
149       * anything other than writeObject: everything else should use
150       * actionmask.
151       */
152      private String actions;
153    
154      /**
155       * A bitmask representing the actions for which we have permission.
156       */
157      private transient int actionmask;
158    
159      /**
160       * The available actions, in the canonical order required for getActions().
161       */
162      private static final String[] ACTIONS = new String[] {
163        "connect", "listen", "accept", "resolve"};
164    
165      /**
166       * Initializes a new instance of <code>SocketPermission</code> with the
167       * specified host/port combination and actions string.
168       *
169       * @param hostport The hostname/port number combination
170       * @param actions The actions string
171       */
172      public SocketPermission(String hostport, String actions)
173      {
174        super(processHostport(hostport));
175    
176        setHostPort(getName());
177        setActions(actions);
178      }
179    
180      /**
181       * There are two cases in which hostport needs rewriting before
182       * being passed to the superclass constructor.  If hostport is an
183       * empty string then it is substituted with "localhost".  And if
184       * the host part of hostport is a literal IPv6 address in the full
185       * uncompressed form not enclosed with "[" and "]" then we enclose
186       * it with them.
187       */
188      private static String processHostport(String hostport)
189      {
190        if (hostport.length() == 0)
191          return "localhost";
192    
193        if (hostport.charAt(0) == '[')
194          return hostport;
195    
196        int colons = 0;
197        boolean colon_allowed = true;
198        for (int i = 0; i < hostport.length(); i++)
199          {
200            if (hostport.charAt(i) == ':')
201              {
202                if (!colon_allowed)
203                  throw new IllegalArgumentException("Ambiguous hostport part");
204                colons++;
205                colon_allowed = false;
206              }
207            else
208              colon_allowed = true;
209          }
210    
211        switch (colons)
212          {
213          case 0:
214          case 1:
215            // a hostname or IPv4 address
216            return hostport;
217            
218          case 7:
219            // an IPv6 address with no ports
220            return "[" + hostport + "]";
221    
222          case 8:
223            // an IPv6 address with ports
224            int last_colon = hostport.lastIndexOf(':');
225            return "[" + hostport.substring(0, last_colon) + "]"
226              + hostport.substring(last_colon);
227    
228          default:
229            throw new IllegalArgumentException("Ambiguous hostport part");
230          }
231      }
232      
233      /**
234       * Parse the hostport argument to the constructor.
235       */
236      private void setHostPort(String hostport)
237      {
238        // Split into host and ports
239        String host, ports;
240        if (hostport.charAt(0) == '[')
241          {
242            // host is a bracketed IPv6 address
243            int end = hostport.indexOf("]");
244            if (end == -1)
245              throw new IllegalArgumentException("Unmatched '['");
246            host = hostport.substring(1, end);
247    
248            address = InetAddress.getByLiteral(host);
249            if (address == null)
250              throw new IllegalArgumentException("Bad IPv6 address");
251    
252            if (end == hostport.length() - 1)
253              ports = "";
254            else if (hostport.charAt(end + 1) == ':')
255              ports = hostport.substring(end + 2);
256            else
257              throw new IllegalArgumentException("Bad character after ']'");
258          }
259        else
260          {
261            // host is a hostname or IPv4 address
262            int sep = hostport.indexOf(":");
263            if (sep == -1)
264              {
265                host = hostport;
266                ports = "";
267              }
268            else
269              {
270                host = hostport.substring(0, sep);
271                ports = hostport.substring(sep + 1);
272              }
273    
274            address = InetAddress.getByLiteral(host);
275            if (address == null)
276              {
277                if (host.lastIndexOf('*') > 0)
278                  throw new IllegalArgumentException("Bad hostname");
279    
280                hostname = host;
281              }
282          }
283    
284        // Parse and validate the ports
285        if (ports.length() == 0)
286          {
287            minport = MIN_PORT;
288            maxport = MAX_PORT;
289          }
290        else
291          {
292            int sep = ports.indexOf("-");
293            if (sep == -1)
294              {
295                // a single port
296                minport = maxport = Integer.parseInt(ports);
297              }
298            else
299              {
300                if (ports.indexOf("-", sep + 1) != -1)
301                  throw new IllegalArgumentException("Unexpected '-'");
302    
303                if (sep == 0)
304                  {
305                    // an upper bound
306                    minport = MIN_PORT;
307                    maxport = Integer.parseInt(ports.substring(1));
308                  }
309                else if (sep == ports.length() - 1)
310                  {
311                    // a lower bound
312                    minport =
313                      Integer.parseInt(ports.substring(0, ports.length() - 1));
314                    maxport = MAX_PORT;
315                  }
316                else
317                  {
318                    // a range with two bounds
319                    minport = Integer.parseInt(ports.substring(0, sep));
320                    maxport = Integer.parseInt(ports.substring(sep + 1));
321                  }
322              }
323          }
324      }
325      
326      /**
327       * Parse the actions argument to the constructor.
328       */
329      private void setActions(String actionstring)
330      {
331        actionmask = 0;
332    
333        boolean resolve_needed = false;
334        boolean resolve_present = false;
335        
336        StringTokenizer t = new StringTokenizer(actionstring, ",");
337        while (t.hasMoreTokens())
338          {
339            String action = t.nextToken();
340            action = action.trim().toLowerCase();
341            setAction(action);
342    
343            if (action.equals("resolve"))
344              resolve_present = true;
345            else
346              resolve_needed = true;
347          }
348    
349        if (resolve_needed && !resolve_present)
350          setAction("resolve");
351      }
352    
353      /**
354       * Parse one element of the actions argument to the constructor.
355       */
356      private void setAction(String action)
357      {
358        for (int i = 0; i < ACTIONS.length; i++)
359          {
360            if (action.equals(ACTIONS[i]))
361              {
362                actionmask |= 1 << i;
363                return;
364              }
365          }
366        throw new IllegalArgumentException("Unknown action " + action);
367      }
368    
369      /**
370       * Tests this object for equality against another.  This will be true if
371       * and only if the passed object is an instance of
372       * <code>SocketPermission</code> and both its hostname/port combination
373       * and permissions string are identical.
374       *
375       * @param obj The object to test against for equality
376       *
377       * @return <code>true</code> if object is equal to this object,
378       *         <code>false</code> otherwise.
379       */
380      public boolean equals(Object obj)
381      {
382        SocketPermission p;
383    
384        if (obj instanceof SocketPermission)
385          p = (SocketPermission) obj;
386        else
387          return false;
388    
389        if (p.actionmask != actionmask ||
390            p.minport != minport ||
391            p.maxport != maxport)
392          return false;
393    
394        if (address != null)
395          {
396            if (p.address == null)
397              return false;
398            else
399              return p.address.equals(address);
400          }
401        else
402          {
403            if (p.hostname == null)
404              return false;
405            else
406              return p.hostname.equals(hostname);
407          }
408      }
409    
410      /**
411       * Returns a hash code value for this object.  Overrides the
412       * <code>Permission.hashCode()</code>.
413       *
414       * @return A hash code
415       */
416      public int hashCode()
417      {
418        int code = actionmask + minport + maxport;
419        if (address != null)
420          code += address.hashCode();
421        else
422          code += hostname.hashCode();
423        return code;
424      }
425    
426      /**
427       * Returns the list of permission actions in this object in canonical
428       * order.  The canonical order is "connect,listen,accept,resolve"
429       *
430       * @return The permitted action string.
431       */
432      public String getActions()
433      {
434        StringBuffer sb = new StringBuffer("");
435    
436        for (int i = 0; i < ACTIONS.length; i++)
437          {
438            if ((actionmask & (1 << i)) != 0)
439              {
440                if (sb.length() != 0)
441                  sb.append(",");
442                sb.append(ACTIONS[i]);
443              }
444          }
445    
446        return sb.toString();
447      }
448    
449      /**
450       * Returns a new <code>PermissionCollection</code> object that can hold
451       * <code>SocketPermission</code>'s.
452       *
453       * @return A new <code>PermissionCollection</code>.
454       */
455      public PermissionCollection newPermissionCollection()
456      {
457        // FIXME: Implement
458    
459        return null;
460      }
461    
462      /**
463       * Returns an array of all IP addresses represented by this object.
464       */
465      private InetAddress[] getAddresses()
466      {
467        if (address != null)
468          return new InetAddress[] {address};
469    
470        try
471          {
472            return InetAddress.getAllByName(hostname);
473          }
474        catch (UnknownHostException e)
475          {
476            return new InetAddress[0];
477          }
478      }
479    
480      /**
481       * Returns the canonical hostname represented by this object,
482       * or null if this object represents a wildcarded domain.
483       */
484      private String getCanonicalHostName()
485      {
486        if (address != null)
487          return address.internalGetCanonicalHostName();
488        if (hostname.charAt(0) == '*')
489          return null;
490        try
491          {
492            return InetAddress.getByName(hostname).internalGetCanonicalHostName();
493          }
494        catch (UnknownHostException e)
495          {
496            return null;
497          }
498      }
499      
500      /**
501       * Returns true if the permission object passed it is implied by the
502       * this permission.  This will be true if:
503       * 
504       * <ul>
505       * <li>The argument is of type <code>SocketPermission</code></li>
506       * <li>The actions list of the argument are in this object's actions</li>
507       * <li>The port range of the argument is within this objects port range</li>
508       * <li>The hostname is equal to or a subset of this objects hostname</li>
509       * </ul>
510       *
511       * <p>The argument's hostname will be a subset of this object's hostname if:</p>
512       * 
513       * <ul>
514       * <li>The argument's hostname or IP address is equal to this object's.</li>
515       * <li>The argument's canonical hostname is equal to this object's.</li>
516       * <li>The argument's canonical name matches this domains hostname with
517       * wildcards</li>
518       * </ul>
519       *
520       * @param perm The <code>Permission</code> to check against
521       *
522       * @return <code>true</code> if the <code>Permission</code> is implied by
523       * this object, <code>false</code> otherwise.
524       */
525      public boolean implies(Permission perm)
526      {
527        SocketPermission p;
528    
529        // First make sure we are the right object type
530        if (perm instanceof SocketPermission)
531          p = (SocketPermission) perm;
532        else
533          return false;
534    
535        // If p was initialised with an empty hostname then we do not
536        // imply it. This is not part of the spec, but it seems necessary.
537        if (p.hostname != null && p.hostname.length() == 0)
538          return false;
539        
540        // Next check the actions
541        if ((p.actionmask & actionmask) != p.actionmask)
542            return false;
543    
544        // Then check the ports
545        if ((p.minport < minport) || (p.maxport > maxport))
546          return false;
547    
548        // Finally check the hosts
549        String p_canon = null;
550    
551        // Return true if this object was initialized with a single
552        // IP address which one of p's IP addresses is equal to.
553        if (address != null)
554          {
555            InetAddress[] addrs = p.getAddresses();
556            for (int i = 0; i < addrs.length; i++)
557              {
558                if (address.equals(addrs[i]))
559                  return true;
560              }
561          }
562    
563        // Return true if this object is a wildcarded domain that
564        // p's canonical name matches.
565        if (hostname != null && hostname.charAt(0) == '*')
566          {
567            p_canon = p.getCanonicalHostName();
568            if (p_canon != null && p_canon.endsWith(hostname.substring(1)))
569              return true;
570            
571          }
572    
573        // Return true if this one of this object's IP addresses
574        // is equal to one of p's.
575        if (address == null)
576          {
577            InetAddress[] addrs = p.getAddresses();
578            InetAddress[] p_addrs = p.getAddresses();
579    
580            for (int i = 0; i < addrs.length; i++)
581              {
582                for (int j = 0; j < p_addrs.length; j++)
583                  {
584                    if (addrs[i].equals(p_addrs[j]))
585                      return true;
586                  }
587              }
588          }
589    
590        // Return true if this object's canonical name equals p's.
591        String canon = getCanonicalHostName();
592        if (canon != null)
593          {
594            if (p_canon == null)
595              p_canon = p.getCanonicalHostName();
596            if (p_canon != null && canon.equals(p_canon))
597              return true;
598          }
599    
600        // Didn't make it
601        return false;
602      }
603    
604      /**
605       * Deserializes a <code>SocketPermission</code> object from
606       * an input stream.
607       *
608       * @param input the input stream.
609       * @throws IOException if an I/O error occurs in the stream.
610       * @throws ClassNotFoundException if the class of the
611       *         serialized object could not be found.
612       */
613      private void readObject(ObjectInputStream input)
614        throws IOException, ClassNotFoundException
615      {
616        input.defaultReadObject();
617        setHostPort(getName());
618        setActions(actions);
619      }
620    
621      /**
622       * Serializes a <code>SocketPermission</code> object to an
623       * output stream.
624       *
625       * @param output the output stream.
626       * @throws IOException if an I/O error occurs in the stream.
627       */
628      private void writeObject(ObjectOutputStream output)
629        throws IOException
630      {
631        actions = getActions();
632        output.defaultWriteObject();
633      }
634    }