1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.net;
  27 
  28 import java.util.Enumeration;
  29 import java.util.Vector;
  30 import java.util.List;
  31 import java.util.ArrayList;
  32 import java.util.Collections;
  33 import java.util.StringJoiner;
  34 import java.util.StringTokenizer;
  35 import java.net.InetAddress;
  36 import java.security.Permission;
  37 import java.security.PermissionCollection;
  38 import java.security.PrivilegedAction;
  39 import java.security.AccessController;
  40 import java.security.Security;
  41 import java.io.Serializable;
  42 import java.io.ObjectStreamField;
  43 import java.io.ObjectOutputStream;
  44 import java.io.ObjectInputStream;
  45 import java.io.IOException;
  46 import sun.net.util.IPAddressUtil;
  47 import sun.net.RegisteredDomain;
  48 import sun.net.PortConfig;
  49 import sun.security.util.SecurityConstants;
  50 import sun.security.util.Debug;
  51 
  52 
  53 /**
  54  * This class represents access to a network via sockets.
  55  * A SocketPermission consists of a
  56  * host specification and a set of "actions" specifying ways to
  57  * connect to that host. The host is specified as
  58  * <pre>
  59  *    host = (hostname | IPv4address | iPv6reference) [:portrange]
  60  *    portrange = portnumber | -portnumber | portnumber-[portnumber]
  61  * </pre>
  62  * The host is expressed as a DNS name, as a numerical IP address,
  63  * or as "localhost" (for the local machine).
  64  * The wildcard "*" may be included once in a DNS name host
  65  * specification. If it is included, it must be in the leftmost
  66  * position, as in "*.sun.com".
  67  * <p>
  68  * The format of the IPv6reference should follow that specified in <a
  69  * href="http://www.ietf.org/rfc/rfc2732.txt"><i>RFC&nbsp;2732: Format
  70  * for Literal IPv6 Addresses in URLs</i></a>:
  71  * <pre>
  72  *    ipv6reference = "[" IPv6address "]"
  73  *</pre>
  74  * For example, you can construct a SocketPermission instance
  75  * as the following:
  76  * <pre>
  77  *    String hostAddress = inetaddress.getHostAddress();
  78  *    if (inetaddress instanceof Inet6Address) {
  79  *        sp = new SocketPermission("[" + hostAddress + "]:" + port, action);
  80  *    } else {
  81  *        sp = new SocketPermission(hostAddress + ":" + port, action);
  82  *    }
  83  * </pre>
  84  * or
  85  * <pre>
  86  *    String host = url.getHost();
  87  *    sp = new SocketPermission(host + ":" + port, action);
  88  * </pre>
  89  * <p>
  90  * The <A HREF="Inet6Address.html#lform">full uncompressed form</A> of
  91  * an IPv6 literal address is also valid.
  92  * <p>
  93  * The port or portrange is optional. A port specification of the
  94  * form "N-", where <i>N</i> is a port number, signifies all ports
  95  * numbered <i>N</i> and above, while a specification of the
  96  * form "-N" indicates all ports numbered <i>N</i> and below.
  97  * The special port value {@code 0} refers to the entire <i>ephemeral</i>
  98  * port range. This is a fixed range of ports a system may use to
  99  * allocate dynamic ports from. The actual range may be system dependent.
 100  * <p>
 101  * The possible ways to connect to the host are
 102  * <pre>
 103  * accept
 104  * connect
 105  * listen
 106  * resolve
 107  * </pre>
 108  * The "listen" action is only meaningful when used with "localhost" and
 109  * means the ability to bind to a specified port.
 110  * The "resolve" action is implied when any of the other actions are present.
 111  * The action "resolve" refers to host/ip name service lookups.
 112  * <P>
 113  * The actions string is converted to lowercase before processing.
 114  * <p>As an example of the creation and meaning of SocketPermissions,
 115  * note that if the following permission:
 116  *
 117  * <pre>
 118  *   p1 = new SocketPermission("puffin.eng.sun.com:7777", "connect,accept");
 119  * </pre>
 120  *
 121  * is granted to some code, it allows that code to connect to port 7777 on
 122  * {@code puffin.eng.sun.com}, and to accept connections on that port.
 123  *
 124  * <p>Similarly, if the following permission:
 125  *
 126  * <pre>
 127  *   p2 = new SocketPermission("localhost:1024-", "accept,connect,listen");
 128  * </pre>
 129  *
 130  * is granted to some code, it allows that code to
 131  * accept connections on, connect to, or listen on any port between
 132  * 1024 and 65535 on the local host.
 133  *
 134  * <p>Note: Granting code permission to accept or make connections to remote
 135  * hosts may be dangerous because malevolent code can then more easily
 136  * transfer and share confidential data among parties who may not
 137  * otherwise have access to the data.
 138  *
 139  * @see java.security.Permissions
 140  * @see SocketPermission
 141  *
 142  *
 143  * @author Marianne Mueller
 144  * @author Roland Schemers
 145  *
 146  * @serial exclude
 147  */
 148 
 149 public final class SocketPermission extends Permission
 150     implements java.io.Serializable
 151 {
 152     private static final long serialVersionUID = -7204263841984476862L;
 153 
 154     /**
 155      * Connect to host:port
 156      */
 157     private final static int CONNECT    = 0x1;
 158 
 159     /**
 160      * Listen on host:port
 161      */
 162     private final static int LISTEN     = 0x2;
 163 
 164     /**
 165      * Accept a connection from host:port
 166      */
 167     private final static int ACCEPT     = 0x4;
 168 
 169     /**
 170      * Resolve DNS queries
 171      */
 172     private final static int RESOLVE    = 0x8;
 173 
 174     /**
 175      * No actions
 176      */
 177     private final static int NONE               = 0x0;
 178 
 179     /**
 180      * All actions
 181      */
 182     private final static int ALL        = CONNECT|LISTEN|ACCEPT|RESOLVE;
 183 
 184     // various port constants
 185     private static final int PORT_MIN = 0;
 186     private static final int PORT_MAX = 65535;
 187     private static final int PRIV_PORT_MAX = 1023;
 188     private static final int DEF_EPH_LOW = 49152;
 189 
 190     // the actions mask
 191     private transient int mask;
 192 
 193     /**
 194      * the actions string.
 195      *
 196      * @serial
 197      */
 198 
 199     private String actions; // Left null as long as possible, then
 200                             // created and re-used in the getAction function.
 201 
 202     // hostname part as it is passed
 203     private transient String hostname;
 204 
 205     // the canonical name of the host
 206     // in the case of "*.foo.com", cname is ".foo.com".
 207 
 208     private transient String cname;
 209 
 210     // all the IP addresses of the host
 211     private transient InetAddress[] addresses;
 212 
 213     // true if the hostname is a wildcard (e.g. "*.sun.com")
 214     private transient boolean wildcard;
 215 
 216     // true if we were initialized with a single numeric IP address
 217     private transient boolean init_with_ip;
 218 
 219     // true if this SocketPermission represents an invalid/unknown host
 220     // used for implies when the delayed lookup has already failed
 221     private transient boolean invalid;
 222 
 223     // port range on host
 224     private transient int[] portrange;
 225 
 226     private transient boolean defaultDeny = false;
 227 
 228     // true if this SocketPermission represents a hostname
 229     // that failed our reverse mapping heuristic test
 230     private transient boolean untrusted;
 231     private transient boolean trusted;
 232 
 233     // true if the sun.net.trustNameService system property is set
 234     private static boolean trustNameService;
 235 
 236     private static Debug debug = null;
 237     private static boolean debugInit = false;
 238 
 239     // lazy initializer
 240     private static class EphemeralRange {
 241         static final int low = initEphemeralPorts("low", DEF_EPH_LOW);
 242             static final int high = initEphemeralPorts("high", PORT_MAX);
 243     };
 244 
 245     static {
 246         Boolean tmp = java.security.AccessController.doPrivileged(
 247                 new sun.security.action.GetBooleanAction("sun.net.trustNameService"));
 248         trustNameService = tmp.booleanValue();
 249     }
 250 
 251     private static synchronized Debug getDebug() {
 252         if (!debugInit) {
 253             debug = Debug.getInstance("access");
 254             debugInit = true;
 255         }
 256         return debug;
 257     }
 258 
 259     /**
 260      * Creates a new SocketPermission object with the specified actions.
 261      * The host is expressed as a DNS name, or as a numerical IP address.
 262      * Optionally, a port or a portrange may be supplied (separated
 263      * from the DNS name or IP address by a colon).
 264      * <p>
 265      * To specify the local machine, use "localhost" as the <i>host</i>.
 266      * Also note: An empty <i>host</i> String ("") is equivalent to "localhost".
 267      * <p>
 268      * The <i>actions</i> parameter contains a comma-separated list of the
 269      * actions granted for the specified host (and port(s)). Possible actions are
 270      * "connect", "listen", "accept", "resolve", or
 271      * any combination of those. "resolve" is automatically added
 272      * when any of the other three are specified.
 273      * <p>
 274      * Examples of SocketPermission instantiation are the following:
 275      * <pre>
 276      *    nr = new SocketPermission("www.catalog.com", "connect");
 277      *    nr = new SocketPermission("www.sun.com:80", "connect");
 278      *    nr = new SocketPermission("*.sun.com", "connect");
 279      *    nr = new SocketPermission("*.edu", "resolve");
 280      *    nr = new SocketPermission("204.160.241.0", "connect");
 281      *    nr = new SocketPermission("localhost:1024-65535", "listen");
 282      *    nr = new SocketPermission("204.160.241.0:1024-65535", "connect");
 283      * </pre>
 284      *
 285      * @param host the hostname or IPaddress of the computer, optionally
 286      * including a colon followed by a port or port range.
 287      * @param action the action string.
 288      */
 289     public SocketPermission(String host, String action) {
 290         super(getHost(host));
 291         // name initialized to getHost(host); NPE detected in getHost()
 292         init(getName(), getMask(action));
 293     }
 294 
 295 
 296     SocketPermission(String host, int mask) {
 297         super(getHost(host));
 298         // name initialized to getHost(host); NPE detected in getHost()
 299         init(getName(), mask);
 300     }
 301 
 302     private void setDeny() {
 303         defaultDeny = true;
 304     }
 305 
 306     private static String getHost(String host) {
 307         if (host.equals("")) {
 308             return "localhost";
 309         } else {
 310             /* IPv6 literal address used in this context should follow
 311              * the format specified in RFC 2732;
 312              * if not, we try to solve the unambiguous case
 313              */
 314             int ind;
 315             if (host.charAt(0) != '[') {
 316                 if ((ind = host.indexOf(':')) != host.lastIndexOf(':')) {
 317                     /* More than one ":", meaning IPv6 address is not
 318                      * in RFC 2732 format;
 319                      * We will rectify user errors for all unambiguious cases
 320                      */
 321                     StringTokenizer st = new StringTokenizer(host, ":");
 322                     int tokens = st.countTokens();
 323                     if (tokens == 9) {
 324                         // IPv6 address followed by port
 325                         ind = host.lastIndexOf(':');
 326                         host = "[" + host.substring(0, ind) + "]" +
 327                             host.substring(ind);
 328                     } else if (tokens == 8 && host.indexOf("::") == -1) {
 329                         // IPv6 address only, not followed by port
 330                         host = "[" + host + "]";
 331                     } else {
 332                         // could be ambiguous
 333                         throw new IllegalArgumentException("Ambiguous"+
 334                                                            " hostport part");
 335                     }
 336                 }
 337             }
 338             return host;
 339         }
 340     }
 341 
 342     private int[] parsePort(String port)
 343         throws Exception
 344     {
 345 
 346         if (port == null || port.equals("") || port.equals("*")) {
 347             return new int[] {PORT_MIN, PORT_MAX};
 348         }
 349 
 350         int dash = port.indexOf('-');
 351 
 352         if (dash == -1) {
 353             int p = Integer.parseInt(port);
 354             return new int[] {p, p};
 355         } else {
 356             String low = port.substring(0, dash);
 357             String high = port.substring(dash+1);
 358             int l,h;
 359 
 360             if (low.equals("")) {
 361                 l = PORT_MIN;
 362             } else {
 363                 l = Integer.parseInt(low);
 364             }
 365 
 366             if (high.equals("")) {
 367                 h = PORT_MAX;
 368             } else {
 369                 h = Integer.parseInt(high);
 370             }
 371             if (l < 0 || h < 0 || h<l)
 372                 throw new IllegalArgumentException("invalid port range");
 373 
 374             return new int[] {l, h};
 375         }
 376     }
 377 
 378     /**
 379      * Returns true if the permission has specified zero
 380      * as its value (or lower bound) signifying the ephemeral range
 381      */
 382     private boolean includesEphemerals() {
 383         return portrange[0] == 0;
 384     }
 385 
 386     /**
 387      * Initialize the SocketPermission object. We don't do any DNS lookups
 388      * as this point, instead we hold off until the implies method is
 389      * called.
 390      */
 391     private void init(String host, int mask) {
 392         // Set the integer mask that represents the actions
 393 
 394         if ((mask & ALL) != mask)
 395             throw new IllegalArgumentException("invalid actions mask");
 396 
 397         // always OR in RESOLVE if we allow any of the others
 398         this.mask = mask | RESOLVE;
 399 
 400         // Parse the host name.  A name has up to three components, the
 401         // hostname, a port number, or two numbers representing a port
 402         // range.   "www.sun.com:8080-9090" is a valid host name.
 403 
 404         // With IPv6 an address can be 2010:836B:4179::836B:4179
 405         // An IPv6 address needs to be enclose in []
 406         // For ex: [2010:836B:4179::836B:4179]:8080-9090
 407         // Refer to RFC 2732 for more information.
 408 
 409         int rb = 0 ;
 410         int start = 0, end = 0;
 411         int sep = -1;
 412         String hostport = host;
 413         if (host.charAt(0) == '[') {
 414             start = 1;
 415             rb = host.indexOf(']');
 416             if (rb != -1) {
 417                 host = host.substring(start, rb);
 418             } else {
 419                 throw new
 420                     IllegalArgumentException("invalid host/port: "+host);
 421             }
 422             sep = hostport.indexOf(':', rb+1);
 423         } else {
 424             start = 0;
 425             sep = host.indexOf(':', rb);
 426             end = sep;
 427             if (sep != -1) {
 428                 host = host.substring(start, end);
 429             }
 430         }
 431 
 432         if (sep != -1) {
 433             String port = hostport.substring(sep+1);
 434             try {
 435                 portrange = parsePort(port);
 436             } catch (Exception e) {
 437                 throw new
 438                     IllegalArgumentException("invalid port range: "+port);
 439             }
 440         } else {
 441             portrange = new int[] { PORT_MIN, PORT_MAX };
 442         }
 443 
 444         hostname = host;
 445 
 446         // is this a domain wildcard specification
 447         if (host.lastIndexOf('*') > 0) {
 448             throw new
 449                IllegalArgumentException("invalid host wildcard specification");
 450         } else if (host.startsWith("*")) {
 451             wildcard = true;
 452             if (host.equals("*")) {
 453                 cname = "";
 454             } else if (host.startsWith("*.")) {
 455                 cname = host.substring(1).toLowerCase();
 456             } else {
 457               throw new
 458                IllegalArgumentException("invalid host wildcard specification");
 459             }
 460             return;
 461         } else {
 462             if (host.length() > 0) {
 463                 // see if we are being initialized with an IP address.
 464                 char ch = host.charAt(0);
 465                 if (ch == ':' || Character.digit(ch, 16) != -1) {
 466                     byte ip[] = IPAddressUtil.textToNumericFormatV4(host);
 467                     if (ip == null) {
 468                         ip = IPAddressUtil.textToNumericFormatV6(host);
 469                     }
 470                     if (ip != null) {
 471                         try {
 472                             addresses =
 473                                 new InetAddress[]
 474                                 {InetAddress.getByAddress(ip) };
 475                             init_with_ip = true;
 476                         } catch (UnknownHostException uhe) {
 477                             // this shouldn't happen
 478                             invalid = true;
 479                         }
 480                     }
 481                 }
 482             }
 483         }
 484     }
 485 
 486     /**
 487      * Convert an action string to an integer actions mask.
 488      *
 489      * @param action the action string
 490      * @return the action mask
 491      */
 492     private static int getMask(String action) {
 493 
 494         if (action == null) {
 495             throw new NullPointerException("action can't be null");
 496         }
 497 
 498         if (action.equals("")) {
 499             throw new IllegalArgumentException("action can't be empty");
 500         }
 501 
 502         int mask = NONE;
 503 
 504         // Use object identity comparison against known-interned strings for
 505         // performance benefit (these values are used heavily within the JDK).
 506         if (action == SecurityConstants.SOCKET_RESOLVE_ACTION) {
 507             return RESOLVE;
 508         } else if (action == SecurityConstants.SOCKET_CONNECT_ACTION) {
 509             return CONNECT;
 510         } else if (action == SecurityConstants.SOCKET_LISTEN_ACTION) {
 511             return LISTEN;
 512         } else if (action == SecurityConstants.SOCKET_ACCEPT_ACTION) {
 513             return ACCEPT;
 514         } else if (action == SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION) {
 515             return CONNECT|ACCEPT;
 516         }
 517 
 518         char[] a = action.toCharArray();
 519 
 520         int i = a.length - 1;
 521         if (i < 0)
 522             return mask;
 523 
 524         while (i != -1) {
 525             char c;
 526 
 527             // skip whitespace
 528             while ((i!=-1) && ((c = a[i]) == ' ' ||
 529                                c == '\r' ||
 530                                c == '\n' ||
 531                                c == '\f' ||
 532                                c == '\t'))
 533                 i--;
 534 
 535             // check for the known strings
 536             int matchlen;
 537 
 538             if (i >= 6 && (a[i-6] == 'c' || a[i-6] == 'C') &&
 539                           (a[i-5] == 'o' || a[i-5] == 'O') &&
 540                           (a[i-4] == 'n' || a[i-4] == 'N') &&
 541                           (a[i-3] == 'n' || a[i-3] == 'N') &&
 542                           (a[i-2] == 'e' || a[i-2] == 'E') &&
 543                           (a[i-1] == 'c' || a[i-1] == 'C') &&
 544                           (a[i] == 't' || a[i] == 'T'))
 545             {
 546                 matchlen = 7;
 547                 mask |= CONNECT;
 548 
 549             } else if (i >= 6 && (a[i-6] == 'r' || a[i-6] == 'R') &&
 550                                  (a[i-5] == 'e' || a[i-5] == 'E') &&
 551                                  (a[i-4] == 's' || a[i-4] == 'S') &&
 552                                  (a[i-3] == 'o' || a[i-3] == 'O') &&
 553                                  (a[i-2] == 'l' || a[i-2] == 'L') &&
 554                                  (a[i-1] == 'v' || a[i-1] == 'V') &&
 555                                  (a[i] == 'e' || a[i] == 'E'))
 556             {
 557                 matchlen = 7;
 558                 mask |= RESOLVE;
 559 
 560             } else if (i >= 5 && (a[i-5] == 'l' || a[i-5] == 'L') &&
 561                                  (a[i-4] == 'i' || a[i-4] == 'I') &&
 562                                  (a[i-3] == 's' || a[i-3] == 'S') &&
 563                                  (a[i-2] == 't' || a[i-2] == 'T') &&
 564                                  (a[i-1] == 'e' || a[i-1] == 'E') &&
 565                                  (a[i] == 'n' || a[i] == 'N'))
 566             {
 567                 matchlen = 6;
 568                 mask |= LISTEN;
 569 
 570             } else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') &&
 571                                  (a[i-4] == 'c' || a[i-4] == 'C') &&
 572                                  (a[i-3] == 'c' || a[i-3] == 'C') &&
 573                                  (a[i-2] == 'e' || a[i-2] == 'E') &&
 574                                  (a[i-1] == 'p' || a[i-1] == 'P') &&
 575                                  (a[i] == 't' || a[i] == 'T'))
 576             {
 577                 matchlen = 6;
 578                 mask |= ACCEPT;
 579 
 580             } else {
 581                 // parse error
 582                 throw new IllegalArgumentException(
 583                         "invalid permission: " + action);
 584             }
 585 
 586             // make sure we didn't just match the tail of a word
 587             // like "ackbarfaccept".  Also, skip to the comma.
 588             boolean seencomma = false;
 589             while (i >= matchlen && !seencomma) {
 590                 switch(a[i-matchlen]) {
 591                 case ',':
 592                     seencomma = true;
 593                     break;
 594                 case ' ': case '\r': case '\n':
 595                 case '\f': case '\t':
 596                     break;
 597                 default:
 598                     throw new IllegalArgumentException(
 599                             "invalid permission: " + action);
 600                 }
 601                 i--;
 602             }
 603 
 604             // point i at the location of the comma minus one (or -1).
 605             i -= matchlen;
 606         }
 607 
 608         return mask;
 609     }
 610 
 611     private boolean isUntrusted()
 612         throws UnknownHostException
 613     {
 614         if (trusted) return false;
 615         if (invalid || untrusted) return true;
 616         try {
 617             if (!trustNameService && (defaultDeny ||
 618                 sun.net.www.URLConnection.isProxiedHost(hostname))) {
 619                 if (this.cname == null) {
 620                     this.getCanonName();
 621                 }
 622                 if (!match(cname, hostname)) {
 623                     // Last chance
 624                     if (!authorized(hostname, addresses[0].getAddress())) {
 625                         untrusted = true;
 626                         Debug debug = getDebug();
 627                         if (debug != null && Debug.isOn("failure")) {
 628                             debug.println("socket access restriction: proxied host " + "(" + addresses[0] + ")" + " does not match " + cname + " from reverse lookup");
 629                         }
 630                         return true;
 631                     }
 632                 }
 633                 trusted = true;
 634             }
 635         } catch (UnknownHostException uhe) {
 636             invalid = true;
 637             throw uhe;
 638         }
 639         return false;
 640     }
 641 
 642     /**
 643      * attempt to get the fully qualified domain name
 644      *
 645      */
 646     void getCanonName()
 647         throws UnknownHostException
 648     {
 649         if (cname != null || invalid || untrusted) return;
 650 
 651         // attempt to get the canonical name
 652 
 653         try {
 654             // first get the IP addresses if we don't have them yet
 655             // this is because we need the IP address to then get
 656             // FQDN.
 657             if (addresses == null) {
 658                 getIP();
 659             }
 660 
 661             // we have to do this check, otherwise we might not
 662             // get the fully qualified domain name
 663             if (init_with_ip) {
 664                 cname = addresses[0].getHostName(false).toLowerCase();
 665             } else {
 666              cname = InetAddress.getByName(addresses[0].getHostAddress()).
 667                                               getHostName(false).toLowerCase();
 668             }
 669         } catch (UnknownHostException uhe) {
 670             invalid = true;
 671             throw uhe;
 672         }
 673     }
 674 
 675     private transient String cdomain, hdomain;
 676 
 677     private boolean match(String cname, String hname) {
 678         String a = cname.toLowerCase();
 679         String b = hname.toLowerCase();
 680         if (a.startsWith(b)  &&
 681             ((a.length() == b.length()) || (a.charAt(b.length()) == '.')))
 682             return true;
 683         if (cdomain == null) {
 684             cdomain = RegisteredDomain.getRegisteredDomain(a);
 685         }
 686         if (hdomain == null) {
 687             hdomain = RegisteredDomain.getRegisteredDomain(b);
 688         }
 689 
 690         return cdomain.length() != 0 && hdomain.length() != 0
 691                         && cdomain.equals(hdomain);
 692     }
 693 
 694     private boolean authorized(String cname, byte[] addr) {
 695         if (addr.length == 4)
 696             return authorizedIPv4(cname, addr);
 697         else if (addr.length == 16)
 698             return authorizedIPv6(cname, addr);
 699         else
 700             return false;
 701     }
 702 
 703     private boolean authorizedIPv4(String cname, byte[] addr) {
 704         String authHost = "";
 705         InetAddress auth;
 706 
 707         try {
 708             authHost = "auth." +
 709                         (addr[3] & 0xff) + "." + (addr[2] & 0xff) + "." +
 710                         (addr[1] & 0xff) + "." + (addr[0] & 0xff) +
 711                         ".in-addr.arpa";
 712             // Following check seems unnecessary
 713             // auth = InetAddress.getAllByName0(authHost, false)[0];
 714             authHost = hostname + '.' + authHost;
 715             auth = InetAddress.getAllByName0(authHost, false)[0];
 716             if (auth.equals(InetAddress.getByAddress(addr))) {
 717                 return true;
 718             }
 719             Debug debug = getDebug();
 720             if (debug != null && Debug.isOn("failure")) {
 721                 debug.println("socket access restriction: IP address of " + auth + " != " + InetAddress.getByAddress(addr));
 722             }
 723         } catch (UnknownHostException uhe) {
 724             Debug debug = getDebug();
 725             if (debug != null && Debug.isOn("failure")) {
 726                 debug.println("socket access restriction: forward lookup failed for " + authHost);
 727             }
 728         }
 729         return false;
 730     }
 731 
 732     private boolean authorizedIPv6(String cname, byte[] addr) {
 733         String authHost = "";
 734         InetAddress auth;
 735 
 736         try {
 737             StringBuilder sb = new StringBuilder(39);
 738 
 739             for (int i = 15; i >= 0; i--) {
 740                 sb.append(Integer.toHexString(((addr[i]) & 0x0f)));
 741                 sb.append('.');
 742                 sb.append(Integer.toHexString(((addr[i] >> 4) & 0x0f)));
 743                 sb.append('.');
 744             }
 745             authHost = "auth." + sb.toString() + "IP6.ARPA";
 746             //auth = InetAddress.getAllByName0(authHost, false)[0];
 747             authHost = hostname + '.' + authHost;
 748             auth = InetAddress.getAllByName0(authHost, false)[0];
 749             if (auth.equals(InetAddress.getByAddress(addr)))
 750                 return true;
 751             Debug debug = getDebug();
 752             if (debug != null && Debug.isOn("failure")) {
 753                 debug.println("socket access restriction: IP address of " + auth + " != " + InetAddress.getByAddress(addr));
 754             }
 755         } catch (UnknownHostException uhe) {
 756             Debug debug = getDebug();
 757             if (debug != null && Debug.isOn("failure")) {
 758                 debug.println("socket access restriction: forward lookup failed for " + authHost);
 759             }
 760         }
 761         return false;
 762     }
 763 
 764 
 765     /**
 766      * get IP addresses. Sets invalid to true if we can't get them.
 767      *
 768      */
 769     void getIP()
 770         throws UnknownHostException
 771     {
 772         if (addresses != null || wildcard || invalid) return;
 773 
 774         try {
 775             // now get all the IP addresses
 776             String host;
 777             if (getName().charAt(0) == '[') {
 778                 // Literal IPv6 address
 779                 host = getName().substring(1, getName().indexOf(']'));
 780             } else {
 781                 int i = getName().indexOf(':');
 782                 if (i == -1)
 783                     host = getName();
 784                 else {
 785                     host = getName().substring(0,i);
 786                 }
 787             }
 788 
 789             addresses =
 790                 new InetAddress[] {InetAddress.getAllByName0(host, false)[0]};
 791 
 792         } catch (UnknownHostException uhe) {
 793             invalid = true;
 794             throw uhe;
 795         }  catch (IndexOutOfBoundsException iobe) {
 796             invalid = true;
 797             throw new UnknownHostException(getName());
 798         }
 799     }
 800 
 801     /**
 802      * Checks if this socket permission object "implies" the
 803      * specified permission.
 804      * <P>
 805      * More specifically, this method first ensures that all of the following
 806      * are true (and returns false if any of them are not):
 807      * <ul>
 808      * <li> <i>p</i> is an instanceof SocketPermission,
 809      * <li> <i>p</i>'s actions are a proper subset of this
 810      * object's actions, and
 811      * <li> <i>p</i>'s port range is included in this port range. Note:
 812      * port range is ignored when p only contains the action, 'resolve'.
 813      * </ul>
 814      *
 815      * Then {@code implies} checks each of the following, in order,
 816      * and for each returns true if the stated condition is true:
 817      * <ul>
 818      * <li> If this object was initialized with a single IP address and one of <i>p</i>'s
 819      * IP addresses is equal to this object's IP address.
 820      * <li>If this object is a wildcard domain (such as *.sun.com), and
 821      * <i>p</i>'s canonical name (the name without any preceding *)
 822      * ends with this object's canonical host name. For example, *.sun.com
 823      * implies *.eng.sun.com.
 824      * <li>If this object was not initialized with a single IP address, and one of this
 825      * object's IP addresses equals one of <i>p</i>'s IP addresses.
 826      * <li>If this canonical name equals <i>p</i>'s canonical name.
 827      * </ul>
 828      *
 829      * If none of the above are true, {@code implies} returns false.
 830      * @param p the permission to check against.
 831      *
 832      * @return true if the specified permission is implied by this object,
 833      * false if not.
 834      */
 835     public boolean implies(Permission p) {
 836         int i,j;
 837 
 838         if (!(p instanceof SocketPermission))
 839             return false;
 840 
 841         if (p == this)
 842             return true;
 843 
 844         SocketPermission that = (SocketPermission) p;
 845 
 846         return ((this.mask & that.mask) == that.mask) &&
 847                                         impliesIgnoreMask(that);
 848     }
 849 
 850     /**
 851      * Checks if the incoming Permission's action are a proper subset of
 852      * the this object's actions.
 853      * <P>
 854      * Check, in the following order:
 855      * <ul>
 856      * <li> Checks that "p" is an instanceof a SocketPermission
 857      * <li> Checks that "p"'s actions are a proper subset of the
 858      * current object's actions.
 859      * <li> Checks that "p"'s port range is included in this port range
 860      * <li> If this object was initialized with an IP address, checks that
 861      *      one of "p"'s IP addresses is equal to this object's IP address.
 862      * <li> If either object is a wildcard domain (i.e., "*.sun.com"),
 863      *      attempt to match based on the wildcard.
 864      * <li> If this object was not initialized with an IP address, attempt
 865      *      to find a match based on the IP addresses in both objects.
 866      * <li> Attempt to match on the canonical hostnames of both objects.
 867      * </ul>
 868      * @param that the incoming permission request
 869      *
 870      * @return true if "permission" is a proper subset of the current object,
 871      * false if not.
 872      */
 873     boolean impliesIgnoreMask(SocketPermission that) {
 874         int i,j;
 875 
 876         if ((that.mask & RESOLVE) != that.mask) {
 877 
 878             // check simple port range
 879             if ((that.portrange[0] < this.portrange[0]) ||
 880                     (that.portrange[1] > this.portrange[1])) {
 881 
 882                 // if either includes the ephemeral range, do full check
 883                 if (this.includesEphemerals() || that.includesEphemerals()) {
 884                     if (!inRange(this.portrange[0], this.portrange[1],
 885                                      that.portrange[0], that.portrange[1]))
 886                     {
 887                                 return false;
 888                     }
 889                 } else {
 890                     return false;
 891                 }
 892             }
 893         }
 894 
 895         // allow a "*" wildcard to always match anything
 896         if (this.wildcard && "".equals(this.cname))
 897             return true;
 898 
 899         // return if either one of these NetPerm objects are invalid...
 900         if (this.invalid || that.invalid) {
 901             return compareHostnames(that);
 902         }
 903 
 904         try {
 905             if (this.init_with_ip) { // we only check IP addresses
 906                 if (that.wildcard)
 907                     return false;
 908 
 909                 if (that.init_with_ip) {
 910                     return (this.addresses[0].equals(that.addresses[0]));
 911                 } else {
 912                     if (that.addresses == null) {
 913                         that.getIP();
 914                     }
 915                     for (i=0; i < that.addresses.length; i++) {
 916                         if (this.addresses[0].equals(that.addresses[i]))
 917                             return true;
 918                     }
 919                 }
 920                 // since "this" was initialized with an IP address, we
 921                 // don't check any other cases
 922                 return false;
 923             }
 924 
 925             // check and see if we have any wildcards...
 926             if (this.wildcard || that.wildcard) {
 927                 // if they are both wildcards, return true iff
 928                 // that's cname ends with this cname (i.e., *.sun.com
 929                 // implies *.eng.sun.com)
 930                 if (this.wildcard && that.wildcard)
 931                     return (that.cname.endsWith(this.cname));
 932 
 933                 // a non-wildcard can't imply a wildcard
 934                 if (that.wildcard)
 935                     return false;
 936 
 937                 // this is a wildcard, lets see if that's cname ends with
 938                 // it...
 939                 if (that.cname == null) {
 940                     that.getCanonName();
 941                 }
 942                 return (that.cname.endsWith(this.cname));
 943             }
 944 
 945             // comapare IP addresses
 946             if (this.addresses == null) {
 947                 this.getIP();
 948             }
 949 
 950             if (that.addresses == null) {
 951                 that.getIP();
 952             }
 953 
 954             if (!(that.init_with_ip && this.isUntrusted())) {
 955                 for (j = 0; j < this.addresses.length; j++) {
 956                     for (i=0; i < that.addresses.length; i++) {
 957                         if (this.addresses[j].equals(that.addresses[i]))
 958                             return true;
 959                     }
 960                 }
 961 
 962                 // XXX: if all else fails, compare hostnames?
 963                 // Do we really want this?
 964                 if (this.cname == null) {
 965                     this.getCanonName();
 966                 }
 967 
 968                 if (that.cname == null) {
 969                     that.getCanonName();
 970                 }
 971 
 972                 return (this.cname.equalsIgnoreCase(that.cname));
 973             }
 974 
 975         } catch (UnknownHostException uhe) {
 976             return compareHostnames(that);
 977         }
 978 
 979         // make sure the first thing that is done here is to return
 980         // false. If not, uncomment the return false in the above catch.
 981 
 982         return false;
 983     }
 984 
 985     private boolean compareHostnames(SocketPermission that) {
 986         // we see if the original names/IPs passed in were equal.
 987 
 988         String thisHost = hostname;
 989         String thatHost = that.hostname;
 990 
 991         if (thisHost == null) {
 992             return false;
 993         } else if (this.wildcard) {
 994             final int cnameLength = this.cname.length();
 995             return thatHost.regionMatches(true,
 996                                           (thatHost.length() - cnameLength),
 997                                           this.cname, 0, cnameLength);
 998         } else {
 999             return thisHost.equalsIgnoreCase(thatHost);
1000         }
1001     }
1002 
1003     /**
1004      * Checks two SocketPermission objects for equality.
1005      *
1006      * @param obj the object to test for equality with this object.
1007      *
1008      * @return true if <i>obj</i> is a SocketPermission, and has the
1009      *  same hostname, port range, and actions as this
1010      *  SocketPermission object. However, port range will be ignored
1011      *  in the comparison if <i>obj</i> only contains the action, 'resolve'.
1012      */
1013     public boolean equals(Object obj) {
1014         if (obj == this)
1015             return true;
1016 
1017         if (! (obj instanceof SocketPermission))
1018             return false;
1019 
1020         SocketPermission that = (SocketPermission) obj;
1021 
1022         //this is (overly?) complex!!!
1023 
1024         // check the mask first
1025         if (this.mask != that.mask) return false;
1026 
1027         if ((that.mask & RESOLVE) != that.mask) {
1028             // now check the port range...
1029             if ((this.portrange[0] != that.portrange[0]) ||
1030                 (this.portrange[1] != that.portrange[1])) {
1031                 return false;
1032             }
1033         }
1034 
1035         // short cut. This catches:
1036         //  "crypto" equal to "crypto", or
1037         // "1.2.3.4" equal to "1.2.3.4.", or
1038         //  "*.edu" equal to "*.edu", but it
1039         //  does not catch "crypto" equal to
1040         // "crypto.eng.sun.com".
1041 
1042         if (this.getName().equalsIgnoreCase(that.getName())) {
1043             return true;
1044         }
1045 
1046         // we now attempt to get the Canonical (FQDN) name and
1047         // compare that. If this fails, about all we can do is return
1048         // false.
1049 
1050         try {
1051             this.getCanonName();
1052             that.getCanonName();
1053         } catch (UnknownHostException uhe) {
1054             return false;
1055         }
1056 
1057         if (this.invalid || that.invalid)
1058             return false;
1059 
1060         if (this.cname != null) {
1061             return this.cname.equalsIgnoreCase(that.cname);
1062         }
1063 
1064         return false;
1065     }
1066 
1067     /**
1068      * Returns the hash code value for this object.
1069      *
1070      * @return a hash code value for this object.
1071      */
1072 
1073     public int hashCode() {
1074         /*
1075          * If this SocketPermission was initialized with an IP address
1076          * or a wildcard, use getName().hashCode(), otherwise use
1077          * the hashCode() of the host name returned from
1078          * java.net.InetAddress.getHostName method.
1079          */
1080 
1081         if (init_with_ip || wildcard) {
1082             return this.getName().hashCode();
1083         }
1084 
1085         try {
1086             getCanonName();
1087         } catch (UnknownHostException uhe) {
1088 
1089         }
1090 
1091         if (invalid || cname == null)
1092             return this.getName().hashCode();
1093         else
1094             return this.cname.hashCode();
1095     }
1096 
1097     /**
1098      * Return the current action mask.
1099      *
1100      * @return the actions mask.
1101      */
1102 
1103     int getMask() {
1104         return mask;
1105     }
1106 
1107     /**
1108      * Returns the "canonical string representation" of the actions in the
1109      * specified mask.
1110      * Always returns present actions in the following order:
1111      * connect, listen, accept, resolve.
1112      *
1113      * @param mask a specific integer action mask to translate into a string
1114      * @return the canonical string representation of the actions
1115      */
1116     private static String getActions(int mask) {
1117         StringJoiner sj = new StringJoiner(",");
1118         if ((mask & CONNECT) == CONNECT) {
1119             sj.add("connect");
1120         }
1121         if ((mask & LISTEN) == LISTEN) {
1122             sj.add("listen");
1123         }
1124         if ((mask & ACCEPT) == ACCEPT) {
1125             sj.add("accept");
1126         }
1127         if ((mask & RESOLVE) == RESOLVE) {
1128             sj.add("resolve");
1129         }
1130         return sj.toString();
1131     }
1132 
1133     /**
1134      * Returns the canonical string representation of the actions.
1135      * Always returns present actions in the following order:
1136      * connect, listen, accept, resolve.
1137      *
1138      * @return the canonical string representation of the actions.
1139      */
1140     public String getActions()
1141     {
1142         if (actions == null)
1143             actions = getActions(this.mask);
1144 
1145         return actions;
1146     }
1147 
1148     /**
1149      * Returns a new PermissionCollection object for storing SocketPermission
1150      * objects.
1151      * <p>
1152      * SocketPermission objects must be stored in a manner that allows them
1153      * to be inserted into the collection in any order, but that also enables the
1154      * PermissionCollection {@code implies}
1155      * method to be implemented in an efficient (and consistent) manner.
1156      *
1157      * @return a new PermissionCollection object suitable for storing SocketPermissions.
1158      */
1159 
1160     public PermissionCollection newPermissionCollection() {
1161         return new SocketPermissionCollection();
1162     }
1163 
1164     /**
1165      * WriteObject is called to save the state of the SocketPermission
1166      * to a stream. The actions are serialized, and the superclass
1167      * takes care of the name.
1168      */
1169     private synchronized void writeObject(java.io.ObjectOutputStream s)
1170         throws IOException
1171     {
1172         // Write out the actions. The superclass takes care of the name
1173         // call getActions to make sure actions field is initialized
1174         if (actions == null)
1175             getActions();
1176         s.defaultWriteObject();
1177     }
1178 
1179     /**
1180      * readObject is called to restore the state of the SocketPermission from
1181      * a stream.
1182      */
1183     private synchronized void readObject(java.io.ObjectInputStream s)
1184          throws IOException, ClassNotFoundException
1185     {
1186         // Read in the action, then initialize the rest
1187         s.defaultReadObject();
1188         init(getName(),getMask(actions));
1189     }
1190 
1191     /**
1192      * Check the system/security property for the ephemeral port range
1193      * for this system. The suffix is either "high" or "low"
1194      */
1195     private static int initEphemeralPorts(String suffix, int defval) {
1196         return AccessController.doPrivileged(
1197             new PrivilegedAction<Integer>(){
1198                 public Integer run() {
1199                     int val = Integer.getInteger(
1200                             "jdk.net.ephemeralPortRange."+suffix, -1
1201                     );
1202                     if (val != -1) {
1203                         return val;
1204                     } else {
1205                         return suffix.equals("low") ?
1206                             PortConfig.getLower() : PortConfig.getUpper();
1207                     }
1208                 }
1209             }
1210         );
1211     }
1212 
1213     /**
1214      * Check if the target range is within the policy range
1215      * together with the ephemeral range for this platform
1216      * (if policy includes ephemeral range)
1217      */
1218     private static boolean inRange(
1219         int policyLow, int policyHigh, int targetLow, int targetHigh
1220     )
1221     {
1222         final int ephemeralLow = EphemeralRange.low;
1223         final int ephemeralHigh = EphemeralRange.high;
1224 
1225         if (targetLow == 0) {
1226             // check policy includes ephemeral range
1227             if (!inRange(policyLow, policyHigh, ephemeralLow, ephemeralHigh)) {
1228                 return false;
1229             }
1230             if (targetHigh == 0) {
1231                 // nothing left to do
1232                 return true;
1233             }
1234             // continue check with first real port number
1235             targetLow = 1;
1236         }
1237 
1238         if (policyLow == 0 && policyHigh == 0) {
1239             // ephemeral range only
1240             return targetLow >= ephemeralLow && targetHigh <= ephemeralHigh;
1241         }
1242 
1243         if (policyLow != 0) {
1244             // simple check of policy only
1245             return targetLow >= policyLow && targetHigh <= policyHigh;
1246         }
1247 
1248         // policyLow == 0 which means possibly two ranges to check
1249 
1250         // first check if policy and ephem range overlap/contiguous
1251 
1252         if (policyHigh >= ephemeralLow - 1) {
1253             return targetHigh <= ephemeralHigh;
1254         }
1255 
1256         // policy and ephem range do not overlap
1257 
1258         // target range must lie entirely inside policy range or eph range
1259 
1260         return  (targetLow <= policyHigh && targetHigh <= policyHigh) ||
1261                 (targetLow >= ephemeralLow && targetHigh <= ephemeralHigh);
1262     }
1263     /*
1264     public String toString()
1265     {
1266         StringBuffer s = new StringBuffer(super.toString() + "\n" +
1267             "cname = " + cname + "\n" +
1268             "wildcard = " + wildcard + "\n" +
1269             "invalid = " + invalid + "\n" +
1270             "portrange = " + portrange[0] + "," + portrange[1] + "\n");
1271         if (addresses != null) for (int i=0; i<addresses.length; i++) {
1272             s.append( addresses[i].getHostAddress());
1273             s.append("\n");
1274         } else {
1275             s.append("(no addresses)\n");
1276         }
1277 
1278         return s.toString();
1279     }
1280 
1281     public static void main(String args[]) throws Exception {
1282         SocketPermission this_ = new SocketPermission(args[0], "connect");
1283         SocketPermission that_ = new SocketPermission(args[1], "connect");
1284         System.out.println("-----\n");
1285         System.out.println("this.implies(that) = " + this_.implies(that_));
1286         System.out.println("-----\n");
1287         System.out.println("this = "+this_);
1288         System.out.println("-----\n");
1289         System.out.println("that = "+that_);
1290         System.out.println("-----\n");
1291 
1292         SocketPermissionCollection nps = new SocketPermissionCollection();
1293         nps.add(this_);
1294         nps.add(new SocketPermission("www-leland.stanford.edu","connect"));
1295         nps.add(new SocketPermission("www-sun.com","connect"));
1296         System.out.println("nps.implies(that) = " + nps.implies(that_));
1297         System.out.println("-----\n");
1298     }
1299     */
1300 }
1301 
1302 /**
1303 
1304 if (init'd with IP, key is IP as string)
1305 if wildcard, its the wild card
1306 else its the cname?
1307 
1308  *
1309  * @see java.security.Permission
1310  * @see java.security.Permissions
1311  * @see java.security.PermissionCollection
1312  *
1313  *
1314  * @author Roland Schemers
1315  *
1316  * @serial include
1317  */
1318 
1319 final class SocketPermissionCollection extends PermissionCollection
1320     implements Serializable
1321 {
1322     // Not serialized; see serialization section at end of class
1323     private transient List<SocketPermission> perms;
1324 
1325     /**
1326      * Create an empty SocketPermissions object.
1327      *
1328      */
1329 
1330     public SocketPermissionCollection() {
1331         perms = new ArrayList<SocketPermission>();
1332     }
1333 
1334     /**
1335      * Adds a permission to the SocketPermissions. The key for the hash is
1336      * the name in the case of wildcards, or all the IP addresses.
1337      *
1338      * @param permission the Permission object to add.
1339      *
1340      * @exception IllegalArgumentException - if the permission is not a
1341      *                                       SocketPermission
1342      *
1343      * @exception SecurityException - if this SocketPermissionCollection object
1344      *                                has been marked readonly
1345      */
1346     public void add(Permission permission) {
1347         if (! (permission instanceof SocketPermission))
1348             throw new IllegalArgumentException("invalid permission: "+
1349                                                permission);
1350         if (isReadOnly())
1351             throw new SecurityException(
1352                 "attempt to add a Permission to a readonly PermissionCollection");
1353 
1354         // optimization to ensure perms most likely to be tested
1355         // show up early (4301064)
1356         synchronized (this) {
1357             perms.add(0, (SocketPermission)permission);
1358         }
1359     }
1360 
1361     /**
1362      * Check and see if this collection of permissions implies the permissions
1363      * expressed in "permission".
1364      *
1365      * @param permission the Permission object to compare
1366      *
1367      * @return true if "permission" is a proper subset of a permission in
1368      * the collection, false if not.
1369      */
1370 
1371     public boolean implies(Permission permission)
1372     {
1373         if (! (permission instanceof SocketPermission))
1374                 return false;
1375 
1376         SocketPermission np = (SocketPermission) permission;
1377 
1378         int desired = np.getMask();
1379         int effective = 0;
1380         int needed = desired;
1381 
1382         synchronized (this) {
1383             int len = perms.size();
1384             //System.out.println("implies "+np);
1385             for (int i = 0; i < len; i++) {
1386                 SocketPermission x = perms.get(i);
1387                 //System.out.println("  trying "+x);
1388                 if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(np)) {
1389                     effective |=  x.getMask();
1390                     if ((effective & desired) == desired)
1391                         return true;
1392                     needed = (desired ^ effective);
1393                 }
1394             }
1395         }
1396         return false;
1397     }
1398 
1399     /**
1400      * Returns an enumeration of all the SocketPermission objects in the
1401      * container.
1402      *
1403      * @return an enumeration of all the SocketPermission objects.
1404      */
1405 
1406     @SuppressWarnings("unchecked")
1407     public Enumeration<Permission> elements() {
1408         // Convert Iterator into Enumeration
1409         synchronized (this) {
1410             return Collections.enumeration((List<Permission>)(List)perms);
1411         }
1412     }
1413 
1414     private static final long serialVersionUID = 2787186408602843674L;
1415 
1416     // Need to maintain serialization interoperability with earlier releases,
1417     // which had the serializable field:
1418 
1419     //
1420     // The SocketPermissions for this set.
1421     // @serial
1422     //
1423     // private Vector permissions;
1424 
1425     /**
1426      * @serialField permissions java.util.Vector
1427      *     A list of the SocketPermissions for this set.
1428      */
1429     private static final ObjectStreamField[] serialPersistentFields = {
1430         new ObjectStreamField("permissions", Vector.class),
1431     };
1432 
1433     /**
1434      * @serialData "permissions" field (a Vector containing the SocketPermissions).
1435      */
1436     /*
1437      * Writes the contents of the perms field out as a Vector for
1438      * serialization compatibility with earlier releases.
1439      */
1440     private void writeObject(ObjectOutputStream out) throws IOException {
1441         // Don't call out.defaultWriteObject()
1442 
1443         // Write out Vector
1444         Vector<SocketPermission> permissions = new Vector<>(perms.size());
1445 
1446         synchronized (this) {
1447             permissions.addAll(perms);
1448         }
1449 
1450         ObjectOutputStream.PutField pfields = out.putFields();
1451         pfields.put("permissions", permissions);
1452         out.writeFields();
1453     }
1454 
1455     /*
1456      * Reads in a Vector of SocketPermissions and saves them in the perms field.
1457      */
1458     private void readObject(ObjectInputStream in)
1459         throws IOException, ClassNotFoundException
1460     {
1461         // Don't call in.defaultReadObject()
1462 
1463         // Read in serialized fields
1464         ObjectInputStream.GetField gfields = in.readFields();
1465 
1466         // Get the one we want
1467         @SuppressWarnings("unchecked")
1468         Vector<SocketPermission> permissions = (Vector<SocketPermission>)gfields.get("permissions", null);
1469         perms = new ArrayList<SocketPermission>(permissions.size());
1470         perms.addAll(permissions);
1471     }
1472 }