Frames | No Frames |
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: }