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