1 /*
   2  * Copyright (c) 2002, 2016, 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 javax.management.remote.rmi;
  27 
  28 import java.io.IOException;
  29 import java.rmi.NoSuchObjectException;
  30 import java.rmi.Remote;
  31 import java.rmi.RemoteException;
  32 import java.rmi.server.RMIClientSocketFactory;
  33 import java.rmi.server.RMIServerSocketFactory;
  34 import java.rmi.server.UnicastRemoteObject;
  35 import java.rmi.server.RemoteObject;
  36 import java.util.Map;
  37 import java.util.Collections;
  38 import javax.security.auth.Subject;
  39 
  40 import com.sun.jmx.remote.internal.rmi.RMIExporter;
  41 import com.sun.jmx.remote.util.EnvHelp;
  42 import java.io.ObjectStreamClass;
  43 import java.lang.reflect.Method;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.List;
  47 import sun.reflect.misc.ReflectUtil;
  48 import sun.rmi.server.DeserializationChecker;
  49 import sun.rmi.server.UnicastServerRef;
  50 import sun.rmi.server.UnicastServerRef2;
  51 
  52 /**
  53  * <p>An {@link RMIServer} object that is exported through JRMP and that
  54  * creates client connections as RMI objects exported through JRMP.
  55  * User code does not usually reference this class directly.</p>
  56  *
  57  * @see RMIServerImpl
  58  *
  59  * @since 1.5
  60  */
  61 public class RMIJRMPServerImpl extends RMIServerImpl {
  62 
  63     private final ExportedWrapper exportedWrapper;
  64 
  65     /**
  66      * <p>Creates a new {@link RMIServer} object that will be exported
  67      * on the given port using the given socket factories.</p>
  68      *
  69      * @param port the port on which this object and the {@link
  70      * RMIConnectionImpl} objects it creates will be exported.  Can be
  71      * zero, to indicate any available port.
  72      *
  73      * @param csf the client socket factory for the created RMI
  74      * objects.  Can be null.
  75      *
  76      * @param ssf the server socket factory for the created RMI
  77      * objects.  Can be null.
  78      *
  79      * @param env the environment map.  Can be null.
  80      *
  81      * @exception IOException if the {@link RMIServer} object
  82      * cannot be created.
  83      *
  84      * @exception IllegalArgumentException if <code>port</code> is
  85      * negative.
  86      */
  87     public RMIJRMPServerImpl(int port,
  88                              RMIClientSocketFactory csf,
  89                              RMIServerSocketFactory ssf,
  90                              Map<String,?> env)
  91             throws IOException {
  92 
  93         super(env);
  94 
  95         if (port < 0)
  96             throw new IllegalArgumentException("Negative port: " + port);
  97 
  98         this.port = port;
  99         this.csf = csf;
 100         this.ssf = ssf;
 101         this.env = (env == null) ? Collections.<String, Object>emptyMap() : env;
 102 
 103         String[] credentialsTypes
 104                 = (String[]) this.env.get(RMIConnectorServer.CREDENTIAL_TYPES);
 105         List<String> types = null;
 106         if (credentialsTypes != null) {
 107             types = new ArrayList<>();
 108             for (String type : credentialsTypes) {
 109                 if (type == null) {
 110                     throw new IllegalArgumentException("A credential type is null.");
 111                 }
 112                 ReflectUtil.checkPackageAccess(type);
 113                 types.add(type);
 114             }
 115         }
 116         exportedWrapper = types != null ?
 117                 new ExportedWrapper(this, types) :
 118                 null;
 119     }
 120 
 121     protected void export() throws IOException {
 122         if (exportedWrapper != null) {
 123             export(exportedWrapper);
 124         } else {
 125             export(this);
 126         }
 127     }
 128 
 129     private void export(Remote obj) throws RemoteException {
 130         final RMIExporter exporter =
 131             (RMIExporter) env.get(RMIExporter.EXPORTER_ATTRIBUTE);
 132         final boolean daemon = EnvHelp.isServerDaemon(env);
 133 
 134         if (daemon && exporter != null) {
 135             throw new IllegalArgumentException("If "+EnvHelp.JMX_SERVER_DAEMON+
 136                     " is specified as true, "+RMIExporter.EXPORTER_ATTRIBUTE+
 137                     " cannot be used to specify an exporter!");
 138         }
 139 
 140         if (daemon) {
 141             if (csf == null && ssf == null) {
 142                 new UnicastServerRef(port).exportObject(obj, null, true);
 143             } else {
 144                 new UnicastServerRef2(port, csf, ssf).exportObject(obj, null, true);
 145             }
 146         } else if (exporter != null) {
 147             exporter.exportObject(obj, port, csf, ssf);
 148         } else {
 149             UnicastRemoteObject.exportObject(obj, port, csf, ssf);
 150         }
 151     }
 152 
 153     private void unexport(Remote obj, boolean force)
 154             throws NoSuchObjectException {
 155         RMIExporter exporter =
 156             (RMIExporter) env.get(RMIExporter.EXPORTER_ATTRIBUTE);
 157         if (exporter == null)
 158             UnicastRemoteObject.unexportObject(obj, force);
 159         else
 160             exporter.unexportObject(obj, force);
 161     }
 162 
 163     protected String getProtocol() {
 164         return "rmi";
 165     }
 166 
 167     /**
 168      * <p>Returns a serializable stub for this {@link RMIServer} object.</p>
 169      *
 170      * @return a serializable stub.
 171      *
 172      * @exception IOException if the stub cannot be obtained - e.g the
 173      *            RMIJRMPServerImpl has not been exported yet.
 174      */
 175     public Remote toStub() throws IOException {
 176         if (exportedWrapper != null) {
 177             return RemoteObject.toStub(exportedWrapper);
 178         } else {
 179             return RemoteObject.toStub(this);
 180         }
 181     }
 182 
 183     /**
 184      * <p>Creates a new client connection as an RMI object exported
 185      * through JRMP. The port and socket factories for the new
 186      * {@link RMIConnection} object are the ones supplied
 187      * to the <code>RMIJRMPServerImpl</code> constructor.</p>
 188      *
 189      * @param connectionId the ID of the new connection. Every
 190      * connection opened by this connector server will have a
 191      * different id.  The behavior is unspecified if this parameter is
 192      * null.
 193      *
 194      * @param subject the authenticated subject.  Can be null.
 195      *
 196      * @return the newly-created <code>RMIConnection</code>.
 197      *
 198      * @exception IOException if the new {@link RMIConnection}
 199      * object cannot be created or exported.
 200      */
 201     protected RMIConnection makeClient(String connectionId, Subject subject)
 202             throws IOException {
 203 
 204         if (connectionId == null)
 205             throw new NullPointerException("Null connectionId");
 206 
 207         RMIConnection client =
 208             new RMIConnectionImpl(this, connectionId, getDefaultClassLoader(),
 209                                   subject, env);
 210         export(client);
 211         return client;
 212     }
 213 
 214     protected void closeClient(RMIConnection client) throws IOException {
 215         unexport(client, true);
 216     }
 217 
 218     /**
 219      * <p>Called by {@link #close()} to close the connector server by
 220      * unexporting this object.  After returning from this method, the
 221      * connector server must not accept any new connections.</p>
 222      *
 223      * @exception IOException if the attempt to close the connector
 224      * server failed.
 225      */
 226     protected void closeServer() throws IOException {
 227         if (exportedWrapper != null) {
 228             unexport(exportedWrapper, true);
 229         } else {
 230             unexport(this, true);
 231         }
 232     }
 233 
 234     private final int port;
 235     private final RMIClientSocketFactory csf;
 236     private final RMIServerSocketFactory ssf;
 237     private final Map<String, ?> env;
 238 
 239     private static class ExportedWrapper implements RMIServer, DeserializationChecker {
 240         private final RMIServer impl;
 241         private final List<String> allowedTypes;
 242 
 243         private ExportedWrapper(RMIServer impl, List<String> credentialsTypes) {
 244             this.impl = impl;
 245             allowedTypes = credentialsTypes;
 246         }
 247 
 248         @Override
 249         public String getVersion() throws RemoteException {
 250             return impl.getVersion();
 251         }
 252 
 253         @Override
 254         public RMIConnection newClient(Object credentials) throws IOException {
 255             return impl.newClient(credentials);
 256         }
 257 
 258         @Override
 259         public void check(Method method, ObjectStreamClass descriptor,
 260                 int paramIndex, int callID) {
 261             String type = descriptor.getName();
 262             if (!allowedTypes.contains(type)) {
 263                 throw new ClassCastException("Unsupported type: " + type);
 264             }
 265         }
 266 
 267         @Override
 268         public void checkProxyClass(Method method, String[] ifaces,
 269                 int paramIndex, int callID) {
 270             if (ifaces != null && ifaces.length > 0) {
 271                 for (String iface : ifaces) {
 272                     if (!allowedTypes.contains(iface)) {
 273                         throw new ClassCastException("Unsupported type: " + iface);
 274                     }
 275                 }
 276             }
 277         }
 278     }
 279 }