1 /*
   2  * Copyright (c) 1996, 2008, 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 package sun.rmi.transport.proxy;
  26 
  27 import java.io.*;
  28 import java.net.*;
  29 import java.security.*;
  30 import java.util.*;
  31 import java.rmi.server.LogStream;
  32 import java.rmi.server.RMISocketFactory;
  33 import sun.rmi.runtime.Log;
  34 import sun.rmi.runtime.NewThreadAction;
  35 import sun.security.action.GetBooleanAction;
  36 import sun.security.action.GetLongAction;
  37 
  38 /**
  39  * RMIMasterSocketFactory attempts to create a socket connection to the
  40  * specified host using successively less efficient mechanisms
  41  * until one succeeds.  If the host is successfully connected to,
  42  * the factory for the successful mechanism is stored in an internal
  43  * hash table keyed by the host name, so that future attempts to
  44  * connect to the same host will automatically use the same
  45  * mechanism.
  46  */
  47 public class RMIMasterSocketFactory extends RMISocketFactory {
  48 
  49     /** "proxy" package log level */
  50     static int logLevel = LogStream.parseLevel(getLogLevel());
  51 
  52     private static String getLogLevel() {
  53         return java.security.AccessController.doPrivileged(
  54             new sun.security.action.GetPropertyAction("sun.rmi.transport.proxy.logLevel"));
  55     }
  56 
  57     /* proxy package log */
  58     static final Log proxyLog =
  59         Log.getLog("sun.rmi.transport.tcp.proxy",
  60                    "transport", RMIMasterSocketFactory.logLevel);
  61 
  62     /** timeout for attemping direct socket connections */
  63     private static long connectTimeout = getConnectTimeout();
  64 
  65     private static long getConnectTimeout() {
  66         return java.security.AccessController.doPrivileged(
  67                 new GetLongAction("sun.rmi.transport.proxy.connectTimeout",
  68                               15000)).longValue(); // default: 15 seconds
  69     }
  70 
  71     /** whether to fallback to HTTP on general connect failures */
  72     private static final boolean eagerHttpFallback =
  73         java.security.AccessController.doPrivileged(new GetBooleanAction(
  74             "sun.rmi.transport.proxy.eagerHttpFallback")).booleanValue();
  75 
  76     /** table of hosts successfully connected to and the factory used */
  77     private Hashtable<String, RMISocketFactory> successTable = 
  78         new Hashtable<>();
  79 
  80     /** maximum number of hosts to remember successful connection to */
  81     private static final int MaxRememberedHosts = 64;
  82 
  83     /** list of the hosts in successTable in initial connection order */
  84     private Vector<String> hostList = new Vector<>(MaxRememberedHosts);
  85 
  86     /** default factory for initial use for direct socket connection */
  87     protected RMISocketFactory initialFactory = new RMIDirectSocketFactory();
  88 
  89     /** ordered list of factories to try as alternate connection
  90       * mechanisms if a direct socket connections fails */
  91     protected Vector<RMISocketFactory> altFactoryList;
  92 
  93     /**
  94      * Create a RMIMasterSocketFactory object.  Establish order of
  95      * connection mechanisms to attempt on createSocket, if a direct
  96      * socket connection fails.
  97      */
  98     public RMIMasterSocketFactory() {
  99         altFactoryList = new Vector<RMISocketFactory>(2);
 100         boolean setFactories = false;
 101 
 102         try {
 103             String proxyHost;
 104             proxyHost = java.security.AccessController.doPrivileged(
 105                 new sun.security.action.GetPropertyAction("http.proxyHost"));
 106 
 107             if (proxyHost == null)
 108                 proxyHost = java.security.AccessController.doPrivileged(
 109                     new sun.security.action.GetPropertyAction("proxyHost"));
 110 
 111             Boolean tmp = java.security.AccessController.doPrivileged(
 112                 new sun.security.action.GetBooleanAction("java.rmi.server.disableHttp"));
 113 
 114             if (!tmp.booleanValue() &&
 115                 (proxyHost != null && proxyHost.length() > 0)) {
 116                 setFactories = true;
 117             }
 118         } catch (Exception e) {
 119             // unable to obtain the properties, so assume default behavior.
 120             setFactories = true;
 121         }
 122 
 123         if (setFactories) {
 124             altFactoryList.addElement(new RMIHttpToPortSocketFactory());
 125             altFactoryList.addElement(new RMIHttpToCGISocketFactory());
 126         }
 127     }
 128 
 129     /**
 130      * Create a new client socket.  If we remember connecting to this host
 131      * successfully before, then use the same factory again.  Otherwise,
 132      * try using a direct socket connection and then the alternate factories
 133      * in the order specified in altFactoryList.
 134      */
 135     public Socket createSocket(String host, int port)
 136         throws IOException
 137     {
 138         if (proxyLog.isLoggable(Log.BRIEF)) {
 139             proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port);
 140         }
 141 
 142         /*
 143          * If we don't have any alternate factories to consult, short circuit
 144          * the fallback procedure and delegate to the initial factory.
 145          */
 146         if (altFactoryList.size() == 0) {
 147             return initialFactory.createSocket(host, port);
 148         }
 149 
 150         RMISocketFactory factory;
 151 
 152         /*
 153          * If we remember successfully connecting to this host before,
 154          * use the same factory.
 155          */
 156         factory = successTable.get(host);
 157         if (factory != null) {
 158             if (proxyLog.isLoggable(Log.BRIEF)) {
 159                 proxyLog.log(Log.BRIEF,
 160                     "previously successful factory found: " + factory);
 161             }
 162             return factory.createSocket(host, port);
 163         }
 164 
 165         /*
 166          * Next, try a direct socket connection.  Open socket in another
 167          * thread and only wait for specified timeout, in case the socket
 168          * would otherwise spend minutes trying an unreachable host.
 169          */
 170         Socket initialSocket = null;
 171         Socket fallbackSocket = null;
 172         final AsyncConnector connector =
 173             new AsyncConnector(initialFactory, host, port,
 174                 AccessController.getContext());
 175                 // connection must be attempted with
 176                 // this thread's access control context
 177         IOException initialFailure = null;
 178 
 179         try {
 180             synchronized (connector) {
 181 
 182                 Thread t = java.security.AccessController.doPrivileged(
 183                     new NewThreadAction(connector, "AsyncConnector", true));
 184                 t.start();
 185 
 186                 try {
 187                     long now = System.currentTimeMillis();
 188                     long deadline = now + connectTimeout;
 189                     do {
 190                         connector.wait(deadline - now);
 191                         initialSocket = checkConnector(connector);
 192                         if (initialSocket != null)
 193                             break;
 194                         now = System.currentTimeMillis();
 195                     } while (now < deadline);
 196                 } catch (InterruptedException e) {
 197                     throw new InterruptedIOException(
 198                         "interrupted while waiting for connector");
 199                 }
 200             }
 201 
 202             // assume no route to host (for now) if no connection yet
 203             if (initialSocket == null)
 204                 throw new NoRouteToHostException(
 205                     "connect timed out: " + host);
 206 
 207             proxyLog.log(Log.BRIEF, "direct socket connection successful");
 208 
 209             return initialSocket;
 210 
 211         } catch (UnknownHostException | NoRouteToHostException e) {
 212             initialFailure = e;
 213         } catch (SocketException e) {
 214             if (eagerHttpFallback) {
 215                 initialFailure = e;
 216             } else {
 217                 throw e;
 218             }
 219         } finally {
 220             if (initialFailure != null) {
 221 
 222                 if (proxyLog.isLoggable(Log.BRIEF)) {
 223                     proxyLog.log(Log.BRIEF,
 224                         "direct socket connection failed: ", initialFailure);
 225                 }
 226 
 227                 // Finally, try any alternate connection mechanisms.
 228                 for (int i = 0; i < altFactoryList.size(); ++ i) {
 229                     factory = altFactoryList.elementAt(i);
 230                     try {
 231                         if (proxyLog.isLoggable(Log.BRIEF)) {
 232                             proxyLog.log(Log.BRIEF,
 233                                 "trying with factory: " + factory);
 234                         }
 235                         // For HTTP connections, the output (POST request) must
 236                         // be sent before we verify a successful connection.
 237                         // So, sacrifice a socket for the sake of testing...
 238                         // The following sequence should verify a successful
 239                         // HTTP connection if no IOException is thrown.
 240                         try (Socket testSocket = 
 241                                 factory.createSocket(host, port)) {
 242                             InputStream in = testSocket.getInputStream();
 243                             int b = in.read(); // probably -1 for EOF...
 244                         }
 245                     } catch (IOException ex) {
 246                         if (proxyLog.isLoggable(Log.BRIEF)) {
 247                             proxyLog.log(Log.BRIEF, "factory failed: ", ex);
 248                         }
 249 
 250                         continue;
 251                     }
 252                     proxyLog.log(Log.BRIEF, "factory succeeded");
 253 
 254                     // factory succeeded, open new socket for caller's use
 255                     try {
 256                         fallbackSocket = factory.createSocket(host, port);
 257                     } catch (IOException ex) {  // if it fails 2nd time,
 258                     }                           // just give up
 259                     break;
 260                 }
 261             }
 262         }
 263 
 264         synchronized (successTable) {
 265             try {
 266                 // check once again to see if direct connection succeeded
 267                 synchronized (connector) {
 268                     initialSocket = checkConnector(connector);
 269                 }
 270                 if (initialSocket != null) {
 271                     // if we had made another one as well, clean it up...
 272                     if (fallbackSocket != null)
 273                         fallbackSocket.close();
 274                     return initialSocket;
 275                 }
 276                 // if connector ever does get socket, it won't be used
 277                 connector.notUsed();
 278             } catch (UnknownHostException | NoRouteToHostException e) {
 279                 initialFailure = e;
 280             } catch (SocketException e) {
 281                 if (eagerHttpFallback) {
 282                     initialFailure = e;
 283                 } else {
 284                     throw e;
 285                 }
 286             }
 287             // if we had found an alternate mechanism, go and use it
 288             if (fallbackSocket != null) {
 289                 // remember this successful host/factory pair
 290                 rememberFactory(host, factory);
 291                 return fallbackSocket;
 292             }
 293             throw initialFailure;
 294         }
 295     }
 296 
 297     /**
 298      * Remember a successful factory for connecting to host.
 299      * Currently, excess hosts are removed from the remembered list
 300      * using a Least Recently Created strategy.
 301      */
 302     void rememberFactory(String host, RMISocketFactory factory) {
 303         synchronized (successTable) {
 304             while (hostList.size() >= MaxRememberedHosts) {
 305                 successTable.remove(hostList.elementAt(0));
 306                 hostList.removeElementAt(0);
 307             }
 308             hostList.addElement(host);
 309             successTable.put(host, factory);
 310         }
 311     }
 312 
 313     /**
 314      * Check if an AsyncConnector succeeded.  If not, return socket
 315      * given to fall back to.
 316      */
 317     Socket checkConnector(AsyncConnector connector)
 318         throws IOException
 319     {
 320         Exception e = connector.getException();
 321         if (e != null) {
 322             e.fillInStackTrace();
 323             /*
 324              * The AsyncConnector implementation guaranteed that the exception
 325              * will be either an IOException or a RuntimeException, and we can
 326              * only throw one of those, so convince that compiler that it must
 327              * be one of those.
 328              */
 329             if (e instanceof IOException) {
 330                 throw (IOException) e;
 331             } else if (e instanceof RuntimeException) {
 332                 throw (RuntimeException) e;
 333             } else {
 334                 throw new Error("internal error: " +
 335                     "unexpected checked exception: " + e.toString());
 336             }
 337         }
 338         return connector.getSocket();
 339     }
 340 
 341     /**
 342      * Create a new server socket.
 343      */
 344     public ServerSocket createServerSocket(int port) throws IOException {
 345         //return new HttpAwareServerSocket(port);
 346         return initialFactory.createServerSocket(port);
 347     }
 348 
 349 
 350     /**
 351      * AsyncConnector is used by RMIMasterSocketFactory to attempt socket
 352      * connections on a separate thread.  This allows RMIMasterSocketFactory
 353      * to control how long it will wait for the connection to succeed.
 354      */
 355     private class AsyncConnector implements Runnable {
 356 
 357         /** what factory to use to attempt connection */
 358         private RMISocketFactory factory;
 359 
 360         /** the host to connect to */
 361         private String host;
 362 
 363         /** the port to connect to */
 364         private int port;
 365 
 366         /** access control context to attempt connection within */
 367         private AccessControlContext acc;
 368 
 369         /** exception that occurred during connection, if any */
 370         private Exception exception = null;
 371 
 372         /** the connected socket, if successful */
 373         private Socket socket = null;
 374 
 375         /** socket should be closed after created, if ever */
 376         private boolean cleanUp = false;
 377 
 378         /**
 379          * Create a new asynchronous connector object.
 380          */
 381         AsyncConnector(RMISocketFactory factory, String host, int port,
 382                        AccessControlContext acc)
 383         {
 384             this.factory = factory;
 385             this.host    = host;
 386             this.port    = port;
 387             this.acc     = acc;
 388             SecurityManager security = System.getSecurityManager();
 389             if (security != null) {
 390                 security.checkConnect(host, port);
 391             }
 392         }
 393 
 394         /**
 395          * Attempt socket connection in separate thread.  If successful,
 396          * notify master waiting,
 397          */
 398         public void run() {
 399             try {
 400                 /*
 401                  * Using the privileges of the thread that wants to make the
 402                  * connection is tempting, but it will fail with applets with
 403                  * the current applet security manager because the applet
 404                  * network connection policy is not captured in the permission
 405                  * framework of the access control context we have.
 406                  *
 407                  * java.security.AccessController.beginPrivileged(acc);
 408                  */
 409                 try {
 410                     Socket temp = factory.createSocket(host, port);
 411                     synchronized (this) {
 412                         socket = temp;
 413                         notify();
 414                     }
 415                     rememberFactory(host, factory);
 416                     synchronized (this) {
 417                         if (cleanUp)
 418                           try {
 419                               socket.close();
 420                           } catch (IOException e) {
 421                           }
 422                     }
 423                 } catch (Exception e) {
 424                     /*
 425                      * Note that the only exceptions which could actually have
 426                      * occurred here are IOException or RuntimeException.
 427                      */
 428                     synchronized (this) {
 429                         exception = e;
 430                         notify();
 431                     }
 432                 }
 433             } finally {
 434                 /*
 435                  * See above comments for matching beginPrivileged() call that
 436                  * is also commented out.
 437                  *
 438                  * java.security.AccessController.endPrivileged();
 439                  */
 440             }
 441         }
 442 
 443         /**
 444          * Get exception that occurred during connection attempt, if any.
 445          * In the current implementation, this is guaranteed to be either
 446          * an IOException or a RuntimeException.
 447          */
 448         private synchronized Exception getException() {
 449             return exception;
 450         }
 451 
 452         /**
 453          * Get successful socket, if any.
 454          */
 455         private synchronized Socket getSocket() {
 456             return socket;
 457         }
 458 
 459         /**
 460          * Note that this connector's socket, if ever successfully created,
 461          * will not be used, so it should be cleaned up quickly
 462          */
 463         synchronized void notUsed() {
 464             if (socket != null) {
 465                 try {
 466                     socket.close();
 467                 } catch (IOException e) {
 468                 }
 469             }
 470             cleanUp = true;
 471         }
 472     }
 473 }