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