Source for java.net.SocketPermission

   1: /* SocketPermission.java -- Class modeling permissions for socket operations
   2:    Copyright (C) 1998, 2000, 2001, 2002, 2004, 2006 Free Software
   3:    Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: package java.net;
  40: 
  41: import java.io.IOException;
  42: import java.io.ObjectInputStream;
  43: import java.io.ObjectOutputStream;
  44: import java.io.Serializable;
  45: import java.security.Permission;
  46: import java.security.PermissionCollection;
  47: import java.util.StringTokenizer;
  48: 
  49: 
  50: /**
  51:  * This class models a specific set of permssions for connecting to a
  52:  * host.  There are two elements to this, the host/port combination and
  53:  * the permission list.
  54:  * <p>
  55:  * The host/port combination is specified as followed
  56:  * <p>
  57:  * <pre>
  58:  * hostname[:[-]port[-[port]]]
  59:  * </pre>
  60:  * <p>
  61:  * The hostname portion can be either a hostname or IP address.  If it is
  62:  * a hostname, a wildcard is allowed in hostnames.  This wildcard is a "*"
  63:  * and matches one or more characters.  Only one "*" may appear in the
  64:  * host and it must be the leftmost character.  For example,
  65:  * "*.urbanophile.com" matches all hosts in the "urbanophile.com" domain.
  66:  * <p>
  67:  * The port portion can be either a single value, or a range of values
  68:  * treated as inclusive.  The first or the last port value in the range
  69:  * can be omitted in which case either the minimum or maximum legal
  70:  * value for a port (respectively) is used by default.  Here are some
  71:  * examples:
  72:  * <p><ul>
  73:  * <li>8080 - Represents port 8080 only</li>
  74:  * <li>2000-3000 - Represents ports 2000 through 3000 inclusive</li>
  75:  * <li>-4000 - Represents ports 0 through 4000 inclusive</li>
  76:  * <li>1024- - Represents ports 1024 through 65535 inclusive</li>
  77:  * </ul><p>
  78:  * The permission list is a comma separated list of individual permissions.
  79:  * These individual permissions are:
  80:  * <p>
  81:  * <pre>
  82:  * accept
  83:  * connect
  84:  * listen
  85:  * resolve
  86:  * </pre>
  87:  * <p>
  88:  * The "listen" permission is only relevant if the host is localhost.  If
  89:  * any permission at all is specified, then resolve permission is implied to
  90:  * exist.
  91:  * <p>
  92:  * Here are a variety of examples of how to create SocketPermission's
  93:  * <p><pre>
  94:  * SocketPermission("www.urbanophile.com", "connect");
  95:  *   Can connect to any port on www.urbanophile.com
  96:  * SocketPermission("www.urbanophile.com:80", "connect,accept");
  97:  *   Can connect to or accept connections from www.urbanophile.com on port 80
  98:  * SocketPermission("localhost:1024-", "listen,accept,connect");
  99:  *   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) or IP address (IPv4 or IPv6).
 121:    */
 122:   private transient String host;
 123: 
 124:   /**
 125:    * A range of ports.
 126:    */
 127:   private transient int minport;
 128:   private transient int maxport;
 129: 
 130:   /**
 131:    * Values used for minimum and maximum ports when one or both bounds
 132:    * are omitted.  This class is essentially independent of the
 133:    * networking code it describes, so we do not limit ports to the
 134:    * usual network limits of 1 and 65535.
 135:    */
 136:   private static final int MIN_PORT = 0;
 137:   private static final int MAX_PORT = Integer.MAX_VALUE;
 138: 
 139:   /**
 140:    * The actions for which we have permission.  This field is present
 141:    * to make the serialized form correct and should not be used by
 142:    * anything other than writeObject: everything else should use
 143:    * actionmask.
 144:    */
 145:   private String actions;
 146: 
 147:   /**
 148:    * A bitmask representing the actions for which we have permission.
 149:    */
 150:   private transient int actionmask;
 151: 
 152:   /**
 153:    * The available actions, in the canonical order required for getActions().
 154:    */
 155:   private static final String[] ACTIONS = new String[] {
 156:     "connect", "listen", "accept", "resolve"};
 157: 
 158:   /**
 159:    * Initializes a new instance of <code>SocketPermission</code> with the
 160:    * specified host/port combination and actions string.
 161:    *
 162:    * @param hostport The hostname/port number combination
 163:    * @param actions The actions string
 164:    */
 165:   public SocketPermission(String hostport, String actions)
 166:   {
 167:     super(maybeBracketIPv6Address(hostport));
 168: 
 169:     setHostPort(getName());
 170:     setActions(actions);
 171:   }
 172: 
 173:   /**
 174:    * IPv6 addresses in the hostport must either be enclosed by
 175:    * "[" and "]" or be specified in the full uncompressed form.
 176:    * In the latter case proprietary JVMs will quote the address
 177:    * with "[" and "]", so we do to.
 178:    */
 179:   private static String maybeBracketIPv6Address(String hostport)
 180:   {
 181:     if (hostport.length() == 0 || hostport.charAt(0) == '[')
 182:       return hostport;
 183: 
 184:     int colons = 0, last_colon = 0;
 185:     for (int i = 0; i < hostport.length(); i++)
 186:       {
 187:     if (hostport.charAt(i) == ':')
 188:       {
 189:         if (i - last_colon == 1)
 190:           throw new IllegalArgumentException("Ambiguous hostport part");
 191:         colons++;
 192:         last_colon = i;
 193:       }
 194:       }
 195: 
 196:     switch (colons)
 197:       {
 198:       case 0:
 199:       case 1:
 200:     // a hostname or IPv4 address
 201:     return hostport;
 202:     
 203:       case 7:
 204:     // an IPv6 address with no ports
 205:     return "[" + hostport + "]";
 206: 
 207:       case 8:
 208:     // an IPv6 address with ports
 209:     return "[" + hostport.substring(0, last_colon) + "]"
 210:       + hostport.substring(last_colon);
 211: 
 212:       default:
 213:     throw new IllegalArgumentException("Ambiguous hostport part");
 214:       }
 215:   }
 216:   
 217:   /**
 218:    * Parse the hostport argument to the constructor.
 219:    */
 220:   private void setHostPort(String hostport)
 221:   {
 222:     // Split into host and ports
 223:     String ports;
 224:     if (hostport.length() == 0)
 225:       {
 226:     host = ports = "";
 227:       }
 228:     else if (hostport.charAt(0) == '[')
 229:       {
 230:     // host is a bracketed IPv6 address
 231:     int end = hostport.indexOf("]");
 232:     if (end == -1)
 233:       throw new IllegalArgumentException("Unmatched '['");
 234:     host = hostport.substring(1, end);
 235: 
 236:     if (end == hostport.length() - 1)
 237:       ports = "";
 238:     else if (hostport.charAt(end + 1) == ':')
 239:       ports = hostport.substring(end + 2);
 240:     else
 241:       throw new IllegalArgumentException("Bad character after ']'");
 242:       }
 243:     else
 244:       {
 245:     // host is a hostname or IPv4 address
 246:     int sep = hostport.indexOf(":");
 247:     if (sep == -1)
 248:       {
 249:         host = hostport;
 250:         ports = "";
 251:       }
 252:     else
 253:       {
 254:         host = hostport.substring(0, sep);
 255:         ports = hostport.substring(sep + 1);
 256:       }
 257:       }
 258:     if (ports.indexOf(":") != -1)
 259:       throw new IllegalArgumentException("Unexpected ':'");
 260: 
 261:     // Parse and validate the ports
 262:     if (ports.length() == 0)
 263:       {
 264:     minport = MIN_PORT;
 265:     maxport = MAX_PORT;
 266:       }
 267:     else
 268:       {
 269:     int sep = ports.indexOf("-");
 270:     if (sep == -1)
 271:       {
 272:         // a single port
 273:         minport = maxport = Integer.parseInt(ports);
 274:       }
 275:     else
 276:       {
 277:         if (ports.indexOf("-", sep + 1) != -1)
 278:           throw new IllegalArgumentException("Unexpected '-'");
 279: 
 280:         if (sep == 0)
 281:           {
 282:         // an upper bound
 283:         minport = MIN_PORT;
 284:         maxport = Integer.parseInt(ports.substring(1));
 285:           }
 286:         else if (sep == ports.length() - 1)
 287:           {
 288:         // a lower bound
 289:         minport =
 290:           Integer.parseInt(ports.substring(0, ports.length() - 1));
 291:         maxport = MAX_PORT;
 292:           }
 293:         else
 294:           {
 295:         // a range with two bounds
 296:         minport = Integer.parseInt(ports.substring(0, sep));
 297:         maxport = Integer.parseInt(ports.substring(sep + 1));
 298:           }
 299:       }
 300:       }
 301:   }
 302:   
 303:   /**
 304:    * Parse the actions argument to the constructor.
 305:    */
 306:   private void setActions(String actionstring)
 307:   {
 308:     actionmask = 0;
 309: 
 310:     boolean resolve_needed = false;
 311:     boolean resolve_present = false;
 312:     
 313:     StringTokenizer t = new StringTokenizer(actionstring, ",");
 314:     while (t.hasMoreTokens())
 315:       {
 316:     String action = t.nextToken();
 317:     action = action.trim().toLowerCase();
 318:     setAction(action);
 319: 
 320:     if (action.equals("resolve"))
 321:       resolve_present = true;
 322:     else
 323:       resolve_needed = true;
 324:       }
 325: 
 326:     if (resolve_needed && !resolve_present)
 327:       setAction("resolve");
 328:   }
 329: 
 330:   /**
 331:    * Parse one element of the actions argument to the constructor.
 332:    */
 333:   private void setAction(String action)
 334:   {
 335:     for (int i = 0; i < ACTIONS.length; i++)
 336:       {
 337:     if (action.equals(ACTIONS[i]))
 338:       {
 339:         actionmask |= 1 << i;
 340:         return;
 341:       }
 342:       }
 343:     throw new IllegalArgumentException("Unknown action " + action);
 344:   }
 345: 
 346:   /**
 347:    * Tests this object for equality against another.  This will be true if
 348:    * and only if the passed object is an instance of
 349:    * <code>SocketPermission</code> and both its hostname/port combination
 350:    * and permissions string are identical.
 351:    *
 352:    * @param obj The object to test against for equality
 353:    *
 354:    * @return <code>true</code> if object is equal to this object,
 355:    *         <code>false</code> otherwise.
 356:    */
 357:   public boolean equals(Object obj)
 358:   {
 359:     SocketPermission p;
 360: 
 361:     if (obj instanceof SocketPermission)
 362:       p = (SocketPermission) obj;
 363:     else
 364:       return false;
 365: 
 366:     return p.actionmask == actionmask &&
 367:       p.minport == minport &&
 368:       p.maxport == maxport &&
 369:       p.host.equals(host);
 370:   }
 371: 
 372:   /**
 373:    * Returns a hash code value for this object.  Overrides the
 374:    * <code>Permission.hashCode()</code>.
 375:    *
 376:    * @return A hash code
 377:    */
 378:   public int hashCode()
 379:   {
 380:     return actionmask + minport + maxport + host.hashCode();
 381:   }
 382: 
 383:   /**
 384:    * Returns the list of permission actions in this object in canonical
 385:    * order.  The canonical order is "connect,listen,accept,resolve"
 386:    *
 387:    * @return The permitted action string.
 388:    */
 389:   public String getActions()
 390:   {
 391:     StringBuffer sb = new StringBuffer("");
 392: 
 393:     for (int i = 0; i < ACTIONS.length; i++)
 394:       {
 395:     if ((actionmask & (1 << i)) != 0)
 396:       {
 397:         if (sb.length() != 0)
 398:           sb.append(",");
 399:         sb.append(ACTIONS[i]);
 400:       }
 401:       }
 402: 
 403:     return sb.toString();
 404:   }
 405: 
 406:   /**
 407:    * Returns a new <code>PermissionCollection</code> object that can hold
 408:    * <code>SocketPermission</code>'s.
 409:    *
 410:    * @return A new <code>PermissionCollection</code>.
 411:    */
 412:   public PermissionCollection newPermissionCollection()
 413:   {
 414:     // FIXME: Implement
 415: 
 416:     return null;
 417:   }
 418: 
 419:   /**
 420:    * Returns true if the permission object passed it is implied by the
 421:    * this permission.  This will be true if:
 422:    * 
 423:    * <ul>
 424:    * <li>The argument is of type <code>SocketPermission</code></li>
 425:    * <li>The actions list of the argument are in this object's actions</li>
 426:    * <li>The port range of the argument is within this objects port range</li>
 427:    * <li>The hostname is equal to or a subset of this objects hostname</li>
 428:    * </ul>
 429:    *
 430:    * <p>The argument's hostname will be a subset of this object's hostname if:</p>
 431:    * 
 432:    * <ul>
 433:    * <li>The argument's hostname or IP address is equal to this object's.</li>
 434:    * <li>The argument's canonical hostname is equal to this object's.</li>
 435:    * <li>The argument's canonical name matches this domains hostname with
 436:    * wildcards</li>
 437:    * </ul>
 438:    *
 439:    * @param perm The <code>Permission</code> to check against
 440:    *
 441:    * @return <code>true</code> if the <code>Permission</code> is implied by
 442:    * this object, <code>false</code> otherwise.
 443:    */
 444:   public boolean implies(Permission perm)
 445:   {
 446:     SocketPermission p;
 447: 
 448:     // First make sure we are the right object type
 449:     if (perm instanceof SocketPermission)
 450:       p = (SocketPermission) perm;
 451:     else
 452:       return false;
 453: 
 454:     // Next check the actions
 455:     if ((p.actionmask & actionmask) != p.actionmask)
 456:     return false;
 457: 
 458:     // Then check the ports
 459:     if ((p.minport < minport) || (p.maxport > maxport))
 460:       return false;
 461: 
 462:     // Finally check the hosts
 463:     if (host.equals(p.host))
 464:       return true;
 465: 
 466:     // Try the canonical names
 467:     String ourcanonical = null;
 468:     String theircanonical = null;
 469:     try
 470:       {
 471:     ourcanonical = InetAddress.getByName(host).getHostName();
 472:     theircanonical = InetAddress.getByName(p.host).getHostName();
 473:       }
 474:     catch (UnknownHostException e)
 475:       {
 476:     // Who didn't resolve?  Just assume current address is canonical enough
 477:     // Is this ok to do?
 478:     if (ourcanonical == null)
 479:       ourcanonical = host;
 480:     if (theircanonical == null)
 481:       theircanonical = p.host;
 482:       }
 483: 
 484:     if (ourcanonical.equals(theircanonical))
 485:       return true;
 486: 
 487:     // Well, last chance.  Try for a wildcard
 488:     if (host.indexOf("*.") != -1)
 489:       {
 490:     String wild_domain =
 491:       host.substring(host.indexOf("*" + 1));
 492:     if (theircanonical.endsWith(wild_domain))
 493:       return true;
 494:       }
 495: 
 496:     // Didn't make it
 497:     return false;
 498:   }
 499: 
 500:   /**
 501:    * Deserializes a <code>SocketPermission</code> object from
 502:    * an input stream.
 503:    *
 504:    * @param input the input stream.
 505:    * @throws IOException if an I/O error occurs in the stream.
 506:    * @throws ClassNotFoundException if the class of the
 507:    *         serialized object could not be found.
 508:    */
 509:   private void readObject(ObjectInputStream input)
 510:     throws IOException, ClassNotFoundException
 511:   {
 512:     input.defaultReadObject();
 513:     setHostPort(getName());
 514:     setActions(actions);
 515:   }
 516: 
 517:   /**
 518:    * Serializes a <code>SocketPermission</code> object to an
 519:    * output stream.
 520:    *
 521:    * @param output the output stream.
 522:    * @throws IOException if an I/O error occurs in the stream.
 523:    */
 524:   private void writeObject(ObjectOutputStream output)
 525:     throws IOException
 526:   {
 527:     actions = getActions();
 528:     output.defaultWriteObject();
 529:   }
 530: }