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