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