001/* SocketPermission.java -- Class modeling permissions for socket operations
002   Copyright (C) 1998, 2000, 2001, 2002, 2004, 2006 Free Software
003   Foundation, Inc.
004
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039package java.net;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.io.IOException;
044import java.io.ObjectInputStream;
045import java.io.ObjectOutputStream;
046import java.io.Serializable;
047import java.security.Permission;
048import java.security.PermissionCollection;
049import 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 */
117public 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}