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 }