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}