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