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 gnu.java.lang.CPStringBuilder; 42: 43: import java.io.IOException; 44: import java.io.ObjectInputStream; 45: import java.io.ObjectOutputStream; 46: import java.io.Serializable; 47: import java.security.Permission; 48: import java.security.PermissionCollection; 49: import java.util.StringTokenizer; 50: 51: 52: /** 53: * This class models a specific set of permssions for connecting to a 54: * host. There are two elements to this, the host/port combination and 55: * the permission list. 56: * <p> 57: * The host/port combination is specified as followed 58: * <p> 59: * <pre> 60: * hostname[:[-]port[-[port]]] 61: * </pre> 62: * <p> 63: * The hostname portion can be either a hostname or IP address. If it is 64: * a hostname, a wildcard is allowed in hostnames. This wildcard is a "*" 65: * and matches one or more characters. Only one "*" may appear in the 66: * host and it must be the leftmost character. For example, 67: * "*.urbanophile.com" matches all hosts in the "urbanophile.com" domain. 68: * <p> 69: * The port portion can be either a single value, or a range of values 70: * treated as inclusive. The first or the last port value in the range 71: * can be omitted in which case either the minimum or maximum legal 72: * value for a port (respectively) is used by default. Here are some 73: * examples: 74: * <p><ul> 75: * <li>8080 - Represents port 8080 only</li> 76: * <li>2000-3000 - Represents ports 2000 through 3000 inclusive</li> 77: * <li>-4000 - Represents ports 0 through 4000 inclusive</li> 78: * <li>1024- - Represents ports 1024 through 65535 inclusive</li> 79: * </ul><p> 80: * The permission list is a comma separated list of individual permissions. 81: * These individual permissions are: 82: * <p> 83: * <pre> 84: * accept 85: * connect 86: * listen 87: * resolve 88: * </pre> 89: * <p> 90: * The "listen" permission is only relevant if the host is localhost. If 91: * any permission at all is specified, then resolve permission is implied to 92: * exist. 93: * <p> 94: * Here are a variety of examples of how to create SocketPermission's 95: * <p><pre> 96: * SocketPermission("www.urbanophile.com", "connect"); 97: * Can connect to any port on www.urbanophile.com 98: * SocketPermission("www.urbanophile.com:80", "connect,accept"); 99: * 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: }