001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.net;
019:
020: import java.io.IOException;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023: import java.io.Serializable;
024: import java.security.Permission;
025: import java.security.PermissionCollection;
026:
027: import org.apache.harmony.luni.util.Inet6Util;
028: import org.apache.harmony.luni.util.Msg;
029:
030: /**
031: * SocketPermissions represent permission to access resources via sockets. The
032: * name of the permission should be either the (possibly wildcarded (eg.
033: * *.company.com)) DNS style name of the of the host for which access is being
034: * requested, or its IP address in standard nn.nn.nn.nn ("dot") notation. The
035: * action list can be made up of any of the following:
036: * <dl>
037: * <dt>connect</dt>
038: * <dd>requests permission to connect to the host</dd>
039: * <dt>listen</dt>
040: * <dd>requests permission to listen for connections from the host</dd>
041: * <dt>accept</dt>
042: * <dd>requests permission to accept connections from the host</dd>
043: * <dt>resolve</dt>
044: * <dd>requests permission to resolve the host name</dd>
045: * </dl>
046: * Note that "resolve" is implied when any (or none) of the others are present.
047: * <p>
048: * Access to a particular port can be requested by appending a colon and a
049: * single digit to the name (eg. "*.company.com:7000"). A range of port numbers
050: * can also be specified, by appending a pattern of the form <low>-<high> where
051: * <low> and <high> are valid port numbers. If either <low> or <high> is omitted
052: * it is equivalent to entering the lowest or highest possible value
053: * respectively. For example:
054: *
055: * <pre>
056: * SocketPermission("www.company.com:7000-", "connect", "accept")
057: * </pre>
058: *
059: * represents permission to connect to and accept connections from
060: * www.company.com on ports in the range 7000 to 65535.
061: */
062: public final class SocketPermission extends Permission implements
063: Serializable {
064:
065: private static final long serialVersionUID = -7204263841984476862L;
066:
067: // Bit masks for each of the possible actions
068: static final int SP_CONNECT = 1;
069:
070: static final int SP_LISTEN = 2;
071:
072: static final int SP_ACCEPT = 4;
073:
074: static final int SP_RESOLVE = 8;
075:
076: // list of actions permitted for socket permission in order, indexed by mask
077: // value
078: @SuppressWarnings("nls")
079: private static final String[] actionNames = { "", "connect",
080: "listen", "", "accept", "", "", "", "resolve" };
081:
082: // If a wildcard is present store the information
083: private transient boolean isPartialWild;
084:
085: private transient boolean isWild;
086:
087: // The highest port number
088: private static final int HIGHEST_PORT = 65535;
089:
090: // The lowest port number
091: private static final int LOWEST_PORT = 0;
092:
093: transient String hostName; // Host name as returned by InetAddress
094:
095: transient String ipString; // IP address as returned by InetAddress
096:
097: transient boolean resolved; // IP address has been resolved
098:
099: // the port range;
100: transient int portMin = LOWEST_PORT;
101:
102: transient int portMax = HIGHEST_PORT;
103:
104: private String actions; // List of all actions allowed by this permission
105:
106: transient int actionsMask = SP_RESOLVE;
107:
108: /**
109: * Constructs an instance of this class. The host name can be a DNS name, an
110: * individual hostname, an ip address or the empty string which implies
111: * localhost. The port or port range is optional.
112: * <p>
113: * The action list is a comma-seperated list which can consist of "connect",
114: * "listen", "accept", and "resolve". They are case-insensitive and can be
115: * put together in any order. "resolve" is always implied.
116: *
117: * @param host
118: * java.lang.String the host name
119: * @param action
120: * java.lang.String the action string
121: */
122: public SocketPermission(String host, String action) {
123: super (host.equals("") ? "localhost" : host); //$NON-NLS-1$ //$NON-NLS-2$
124: hostName = getHostString(host);
125: if (action == null) {
126: throw new NullPointerException();
127: }
128: if (action.equals("")) { //$NON-NLS-1$
129: throw new IllegalArgumentException();
130: }
131:
132: setActions(action);
133: actions = toCanonicalActionString(action);
134: // Use host since we are only checking for port presence
135: parsePort(host);
136: }
137:
138: /**
139: * Compares the argument to the receiver, and answers true if they represent
140: * the equal objects using a class specific comparison.
141: * <p>
142: *
143: * @param o
144: * the object to compare with this object
145: * @return <code>true</code> if the object is the same as this object
146: * <code>false</code> if it is different from this object
147: * @see #hashCode
148: */
149: @Override
150: public boolean equals(Object o) {
151: if (this == o) {
152: return true;
153: }
154: if (o == null || this .getClass() != o.getClass()) {
155: return false;
156: }
157: SocketPermission sp = (SocketPermission) o;
158: if (!hostName.equals(sp.hostName)) {
159: if (getIPString() == null
160: || !ipString.equals(sp.getIPString())) {
161: return false;
162: }
163: }
164: if (this .actionsMask != SP_RESOLVE) {
165: if (this .portMin != sp.portMin) {
166: return false;
167: }
168: if (this .portMax != sp.portMax) {
169: return false;
170: }
171: }
172: return this .actionsMask == sp.actionsMask;
173: }
174:
175: /**
176: * Answers an integer hash code for the receiver. Any two objects which
177: * answer <code>true</code> when passed to <code>.equals</code> must
178: * answer the same value for this method.
179: *
180: * @return int the receiver's hash.
181: *
182: * @see #equals
183: */
184: @Override
185: public int hashCode() {
186: return hostName.hashCode() ^ actionsMask ^ portMin ^ portMax;
187: }
188:
189: /**
190: * Answers the canonical action list of this SocketPermission in the order:
191: * connect, listen, accept, resolve.
192: *
193: * @return java.lang.String the canonical action list
194: */
195: @Override
196: public String getActions() {
197: return actions;
198: }
199:
200: /**
201: * Stores the actions for this permission as a bit field
202: *
203: * @param actions
204: * java.lang.String the action list
205: */
206: private void setActions(String actions)
207: throws IllegalArgumentException {
208: if (actions.equals("")) { //$NON-NLS-1$
209: return;
210: }
211: boolean parsing = true;
212: String action;
213: StringBuffer sb = new StringBuffer();
214: int pos = 0, length = actions.length();
215: while (parsing) {
216: char c;
217: sb.setLength(0);
218: while (pos < length && (c = actions.charAt(pos++)) != ',') {
219: sb.append(c);
220: }
221: if (pos == length) {
222: parsing = false;
223: }
224: action = sb.toString().trim().toLowerCase();
225: if (action.equals(actionNames[SP_CONNECT])) {
226: actionsMask |= SP_CONNECT;
227: } else if (action.equals(actionNames[SP_LISTEN])) {
228: actionsMask |= SP_LISTEN;
229: } else if (action.equals(actionNames[SP_ACCEPT])) {
230: actionsMask |= SP_ACCEPT;
231: } else if (action.equals(actionNames[SP_RESOLVE])) {
232: } else {
233: throw new IllegalArgumentException(Msg.getString(
234: "K0048", //$NON-NLS-1$
235: action));
236: }
237: }
238: }
239:
240: /**
241: * Check the permission to see if the actions requested by the argument
242: * permission are permissable. All argument permission actions, host and
243: * port must be implied by this permission in order to return true. This
244: * permission may imply additional actions etc. not present in the argument
245: * permission.
246: *
247: * @return boolean true if this permission implies <code>p</code>, and
248: * false otherwise
249: * @param p
250: * java.security.Permission the other socket permission
251: */
252: @Override
253: public boolean implies(Permission p) {
254: SocketPermission sp;
255: try {
256: sp = (SocketPermission) p;
257: } catch (ClassCastException e) {
258: return false;
259: }
260:
261: // tests if the action list of p is the subset of the one of the
262: // receiver
263: if (sp == null
264: || (actionsMask & sp.actionsMask) != sp.actionsMask) {
265: return false;
266: }
267:
268: // only check the port range if the action string of the current object
269: // is not "resolve"
270: if (!p.getActions().equals("resolve")) { //$NON-NLS-1$
271: if ((sp.portMin < this .portMin)
272: || (sp.portMax > this .portMax)) {
273: return false;
274: }
275: }
276:
277: // Verify the host is valid
278: return checkHost(sp);
279: }
280:
281: /**
282: * Answers a PermissionCollection for storing SocketPermission objects.
283: *
284: * @return java.security.PermissionCollection a permission collection
285: */
286: @Override
287: public PermissionCollection newPermissionCollection() {
288: return new SocketPermissionCollection();
289: }
290:
291: /**
292: * Parses the port string into the lower and higher bound of the port range.
293: *
294: */
295: private void parsePort(String hostString)
296: throws IllegalArgumentException {
297: int negidx = -1;
298: int len = -1;
299: int lastIdx = hostString.lastIndexOf(':');
300: int idx = hostString.indexOf(':');
301: int endOfIPv6Addr = hostString.lastIndexOf(']');
302: if ((endOfIPv6Addr == -1) && (idx != lastIdx)) {
303: // there are no square braces, but there are more than one ':' which
304: // implies an IPv6 address with no port, or an illegal argument
305: // check for valid IPv6 address
306: if (Inet6Util.isValidIP6Address(hostString)) {
307: return;
308: }
309: // throw an invalid argument exception
310: throw new IllegalArgumentException(Msg.getString("K004a")); //$NON-NLS-1$
311: }
312: // if there is a colon and it occurs after the ']' then there is a port
313: // to be parsed
314: if ((lastIdx > -1) && (lastIdx > endOfIPv6Addr)) {
315: try {
316: len = hostString.length();
317: // if hostString ends with ":*", such as "localhost:*"
318: // the port range should be 0-65535
319: if (hostString.endsWith(":*")) { //$NON-NLS-1$
320: portMin = 0;
321: portMax = 65535;
322: return;
323: }
324: // look for a '-' after the colon
325: negidx = hostString.indexOf('-', lastIdx);
326: if (negidx == lastIdx + 1) {
327: portMax = Integer.parseInt(hostString.substring(
328: lastIdx + 2, len));
329: } else {
330: // A port range was provided
331: if (negidx != -1 && (negidx != len - 1)) {
332: portMin = Integer.parseInt(hostString
333: .substring(lastIdx + 1, negidx));
334: portMax = Integer.parseInt(hostString
335: .substring(negidx + 1, len));
336: } else {
337: if (negidx == -1) {
338: portMin = Integer.parseInt(hostString
339: .substring(lastIdx + 1, len));
340: portMax = portMin;
341: } else {
342: portMin = Integer.parseInt(hostString
343: .substring(lastIdx + 1, negidx));
344: }
345: }
346: }
347: if (portMax < portMin) {
348: throw new IllegalArgumentException(Msg
349: .getString("K0049")); //$NON-NLS-1$
350: }
351:
352: } catch (NumberFormatException e) {
353: throw new IllegalArgumentException(Msg
354: .getString("K004a")); //$NON-NLS-1$
355: }
356: }
357: }
358:
359: /**
360: * Creates a canonical action list.
361: *
362: * @param action
363: * java.lang.String
364: *
365: * @return java.lang.String
366: */
367: private String toCanonicalActionString(String action) {
368: if (action == null
369: || action.equals("") || actionsMask == SP_RESOLVE) { //$NON-NLS-1$
370: return actionNames[SP_RESOLVE]; // If none specified return the
371: }
372: // implied action resolve
373: StringBuffer sb = new StringBuffer();
374: if ((actionsMask & SP_CONNECT) == SP_CONNECT) {
375: sb.append(',');
376: sb.append(actionNames[SP_CONNECT]);
377: }
378: if ((actionsMask & SP_LISTEN) == SP_LISTEN) {
379: sb.append(',');
380: sb.append(actionNames[SP_LISTEN]);
381: }
382: if ((actionsMask & SP_ACCEPT) == SP_ACCEPT) {
383: sb.append(',');
384: sb.append(actionNames[SP_ACCEPT]);
385: }
386: sb.append(',');
387: sb.append(actionNames[SP_RESOLVE]);// Resolve is always implied
388: // Don't copy the first ','.
389: return actions = sb.substring(1, sb.length());
390: }
391:
392: private String getIPString() {
393: if (!resolved) {
394: try {
395: ipString = InetAddress.getHostNameInternal(hostName);
396: } catch (UnknownHostException e) {
397: }
398: resolved = true;
399: }
400: return ipString;
401: }
402:
403: private String getHostString(String host)
404: throws IllegalArgumentException {
405: int idx = -1;
406: idx = host.indexOf(':');
407: isPartialWild = (host.length() > 0 && host.charAt(0) == '*');
408: if (isPartialWild) {
409: resolved = true;
410: isWild = (host.length() == 1);
411: if (isWild) {
412: return host;
413: }
414: if (idx > -1) {
415: host = host.substring(0, idx);
416: }
417: return host.toLowerCase();
418: }
419:
420: int lastIdx = host.lastIndexOf(':');
421: if ((idx > -1) && (idx == lastIdx)) {
422: host = host.substring(0, idx);
423: } else {
424: // likely host is or contains an IPv6 address
425: if (lastIdx != -1) {
426: if (Inet6Util.isValidIP6Address(host)) {
427: return host.toLowerCase();
428: } else if (Inet6Util.isValidIP6Address(host.substring(
429: 0, lastIdx))) {
430: host = host.substring(0, lastIdx);
431: } else {
432: throw new IllegalArgumentException(Msg
433: .getString("K004a")); //$NON-NLS-1$
434: }
435: }
436: }
437: return host.toLowerCase();
438: }
439:
440: /*
441: * Determines whether or not this permission could refer to the same host as
442: * sp
443: */
444: boolean checkHost(SocketPermission sp) {
445: if (isPartialWild) {
446: if (isWild) {
447: return true; // Match on any host
448: }
449: int length = hostName.length() - 1;
450: return sp.hostName.regionMatches(sp.hostName.length()
451: - length, hostName, 1, length);
452: }
453: // The ipString may not be the same, some hosts resolve to
454: // multiple ips
455: return (getIPString() != null && ipString.equals(sp
456: .getIPString()))
457: || hostName.equals(sp.hostName);
458: }
459:
460: private void writeObject(ObjectOutputStream stream)
461: throws IOException {
462: stream.defaultWriteObject();
463: }
464:
465: private void readObject(ObjectInputStream stream)
466: throws IOException, ClassNotFoundException {
467: stream.defaultReadObject();
468: // Initialize locals
469: isPartialWild = false;
470: isWild = false;
471: portMin = LOWEST_PORT;
472: portMax = HIGHEST_PORT;
473: actionsMask = SP_RESOLVE;
474: hostName = getHostString(getName());
475: parsePort(getName());
476: setActions(actions);
477: }
478: }
|