2006-09-05 Gary Benson <gbenson@redhat.com>
[gcc.git] / libjava / classpath / java / net / SocketPermission.java
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(processHostport(hostport));
168
169 setHostPort(getName());
170 setActions(actions);
171 }
172
173 /**
174 * There are two cases in which hostport needs rewriting before
175 * being passed to the superclass constructor. If hostport is an
176 * empty string then it is substituted with "localhost". And if
177 * the host part of hostport is a literal IPv6 address in the full
178 * uncompressed form not enclosed with "[" and "]" then we enclose
179 * it with them.
180 */
181 private static String processHostport(String hostport)
182 {
183 if (hostport.length() == 0)
184 return "localhost";
185
186 if (hostport.charAt(0) == '[')
187 return hostport;
188
189 int colons = 0, last_colon = 0;
190 for (int i = 0; i < hostport.length(); i++)
191 {
192 if (hostport.charAt(i) == ':')
193 {
194 if (i - last_colon == 1)
195 throw new IllegalArgumentException("Ambiguous hostport part");
196 colons++;
197 last_colon = i;
198 }
199 }
200
201 switch (colons)
202 {
203 case 0:
204 case 1:
205 // a hostname or IPv4 address
206 return hostport;
207
208 case 7:
209 // an IPv6 address with no ports
210 return "[" + hostport + "]";
211
212 case 8:
213 // an IPv6 address with ports
214 return "[" + hostport.substring(0, last_colon) + "]"
215 + hostport.substring(last_colon);
216
217 default:
218 throw new IllegalArgumentException("Ambiguous hostport part");
219 }
220 }
221
222 /**
223 * Parse the hostport argument to the constructor.
224 */
225 private void setHostPort(String hostport)
226 {
227 // Split into host and ports
228 String ports;
229 if (hostport.charAt(0) == '[')
230 {
231 // host is a bracketed IPv6 address
232 int end = hostport.indexOf("]");
233 if (end == -1)
234 throw new IllegalArgumentException("Unmatched '['");
235 host = hostport.substring(1, end);
236
237 if (end == hostport.length() - 1)
238 ports = "";
239 else if (hostport.charAt(end + 1) == ':')
240 ports = hostport.substring(end + 2);
241 else
242 throw new IllegalArgumentException("Bad character after ']'");
243 }
244 else
245 {
246 // host is a hostname or IPv4 address
247 int sep = hostport.indexOf(":");
248 if (sep == -1)
249 {
250 host = hostport;
251 ports = "";
252 }
253 else
254 {
255 host = hostport.substring(0, sep);
256 ports = hostport.substring(sep + 1);
257 }
258 }
259
260 // Parse and validate the ports
261 if (ports.length() == 0)
262 {
263 minport = MIN_PORT;
264 maxport = MAX_PORT;
265 }
266 else
267 {
268 int sep = ports.indexOf("-");
269 if (sep == -1)
270 {
271 // a single port
272 minport = maxport = Integer.parseInt(ports);
273 }
274 else
275 {
276 if (ports.indexOf("-", sep + 1) != -1)
277 throw new IllegalArgumentException("Unexpected '-'");
278
279 if (sep == 0)
280 {
281 // an upper bound
282 minport = MIN_PORT;
283 maxport = Integer.parseInt(ports.substring(1));
284 }
285 else if (sep == ports.length() - 1)
286 {
287 // a lower bound
288 minport =
289 Integer.parseInt(ports.substring(0, ports.length() - 1));
290 maxport = MAX_PORT;
291 }
292 else
293 {
294 // a range with two bounds
295 minport = Integer.parseInt(ports.substring(0, sep));
296 maxport = Integer.parseInt(ports.substring(sep + 1));
297 }
298 }
299 }
300 }
301
302 /**
303 * Parse the actions argument to the constructor.
304 */
305 private void setActions(String actionstring)
306 {
307 actionmask = 0;
308
309 boolean resolve_needed = false;
310 boolean resolve_present = false;
311
312 StringTokenizer t = new StringTokenizer(actionstring, ",");
313 while (t.hasMoreTokens())
314 {
315 String action = t.nextToken();
316 action = action.trim().toLowerCase();
317 setAction(action);
318
319 if (action.equals("resolve"))
320 resolve_present = true;
321 else
322 resolve_needed = true;
323 }
324
325 if (resolve_needed && !resolve_present)
326 setAction("resolve");
327 }
328
329 /**
330 * Parse one element of the actions argument to the constructor.
331 */
332 private void setAction(String action)
333 {
334 for (int i = 0; i < ACTIONS.length; i++)
335 {
336 if (action.equals(ACTIONS[i]))
337 {
338 actionmask |= 1 << i;
339 return;
340 }
341 }
342 throw new IllegalArgumentException("Unknown action " + action);
343 }
344
345 /**
346 * Tests this object for equality against another. This will be true if
347 * and only if the passed object is an instance of
348 * <code>SocketPermission</code> and both its hostname/port combination
349 * and permissions string are identical.
350 *
351 * @param obj The object to test against for equality
352 *
353 * @return <code>true</code> if object is equal to this object,
354 * <code>false</code> otherwise.
355 */
356 public boolean equals(Object obj)
357 {
358 SocketPermission p;
359
360 if (obj instanceof SocketPermission)
361 p = (SocketPermission) obj;
362 else
363 return false;
364
365 return p.actionmask == actionmask &&
366 p.minport == minport &&
367 p.maxport == maxport &&
368 p.host.equals(host);
369 }
370
371 /**
372 * Returns a hash code value for this object. Overrides the
373 * <code>Permission.hashCode()</code>.
374 *
375 * @return A hash code
376 */
377 public int hashCode()
378 {
379 return actionmask + minport + maxport + host.hashCode();
380 }
381
382 /**
383 * Returns the list of permission actions in this object in canonical
384 * order. The canonical order is "connect,listen,accept,resolve"
385 *
386 * @return The permitted action string.
387 */
388 public String getActions()
389 {
390 StringBuffer sb = new StringBuffer("");
391
392 for (int i = 0; i < ACTIONS.length; i++)
393 {
394 if ((actionmask & (1 << i)) != 0)
395 {
396 if (sb.length() != 0)
397 sb.append(",");
398 sb.append(ACTIONS[i]);
399 }
400 }
401
402 return sb.toString();
403 }
404
405 /**
406 * Returns a new <code>PermissionCollection</code> object that can hold
407 * <code>SocketPermission</code>'s.
408 *
409 * @return A new <code>PermissionCollection</code>.
410 */
411 public PermissionCollection newPermissionCollection()
412 {
413 // FIXME: Implement
414
415 return null;
416 }
417
418 /**
419 * Returns true if the permission object passed it is implied by the
420 * this permission. This will be true if:
421 *
422 * <ul>
423 * <li>The argument is of type <code>SocketPermission</code></li>
424 * <li>The actions list of the argument are in this object's actions</li>
425 * <li>The port range of the argument is within this objects port range</li>
426 * <li>The hostname is equal to or a subset of this objects hostname</li>
427 * </ul>
428 *
429 * <p>The argument's hostname will be a subset of this object's hostname if:</p>
430 *
431 * <ul>
432 * <li>The argument's hostname or IP address is equal to this object's.</li>
433 * <li>The argument's canonical hostname is equal to this object's.</li>
434 * <li>The argument's canonical name matches this domains hostname with
435 * wildcards</li>
436 * </ul>
437 *
438 * @param perm The <code>Permission</code> to check against
439 *
440 * @return <code>true</code> if the <code>Permission</code> is implied by
441 * this object, <code>false</code> otherwise.
442 */
443 public boolean implies(Permission perm)
444 {
445 SocketPermission p;
446
447 // First make sure we are the right object type
448 if (perm instanceof SocketPermission)
449 p = (SocketPermission) perm;
450 else
451 return false;
452
453 // Next check the actions
454 if ((p.actionmask & actionmask) != p.actionmask)
455 return false;
456
457 // Then check the ports
458 if ((p.minport < minport) || (p.maxport > maxport))
459 return false;
460
461 // Finally check the hosts
462 if (host.equals(p.host))
463 return true;
464
465 // Try the canonical names
466 String ourcanonical = null;
467 String theircanonical = null;
468 try
469 {
470 ourcanonical = InetAddress.getByName(host).getHostName();
471 theircanonical = InetAddress.getByName(p.host).getHostName();
472 }
473 catch (UnknownHostException e)
474 {
475 // Who didn't resolve? Just assume current address is canonical enough
476 // Is this ok to do?
477 if (ourcanonical == null)
478 ourcanonical = host;
479 if (theircanonical == null)
480 theircanonical = p.host;
481 }
482
483 if (ourcanonical.equals(theircanonical))
484 return true;
485
486 // Well, last chance. Try for a wildcard
487 if (host.indexOf("*.") != -1)
488 {
489 String wild_domain =
490 host.substring(host.indexOf("*" + 1));
491 if (theircanonical.endsWith(wild_domain))
492 return true;
493 }
494
495 // Didn't make it
496 return false;
497 }
498
499 /**
500 * Deserializes a <code>SocketPermission</code> object from
501 * an input stream.
502 *
503 * @param input the input stream.
504 * @throws IOException if an I/O error occurs in the stream.
505 * @throws ClassNotFoundException if the class of the
506 * serialized object could not be found.
507 */
508 private void readObject(ObjectInputStream input)
509 throws IOException, ClassNotFoundException
510 {
511 input.defaultReadObject();
512 setHostPort(getName());
513 setActions(actions);
514 }
515
516 /**
517 * Serializes a <code>SocketPermission</code> object to an
518 * output stream.
519 *
520 * @param output the output stream.
521 * @throws IOException if an I/O error occurs in the stream.
522 */
523 private void writeObject(ObjectOutputStream output)
524 throws IOException
525 {
526 actions = getActions();
527 output.defaultWriteObject();
528 }
529 }