1 /*
2  * Copyright (c) 2002, 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 javax.management.remote.rmi;
27 
28 
29 import com.sun.jmx.remote.security.MBeanServerFileAccessController;
30 import com.sun.jmx.remote.util.ClassLogger;
31 import com.sun.jmx.remote.util.EnvHelp;
32 
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.ObjectInputFilter;
36 import java.io.ObjectOutputStream;
37 import java.net.MalformedURLException;
38 import java.rmi.server.RMIClientSocketFactory;
39 import java.rmi.server.RMIServerSocketFactory;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Hashtable;
44 import java.util.Map;
45 import java.util.Set;
46 
47 import javax.management.InstanceNotFoundException;
48 import javax.management.MBeanServer;
49 import javax.management.remote.JMXAuthenticator;
50 
51 import javax.management.remote.JMXConnectionNotification;
52 import javax.management.remote.JMXConnector;
53 import javax.management.remote.JMXConnectorServer;
54 import javax.management.remote.JMXServiceURL;
55 import javax.management.remote.MBeanServerForwarder;
56 
57 import javax.naming.InitialContext;
58 import javax.naming.NamingException;
59 
60 /**
61  * <p>A JMX API connector server that creates RMI-based connections
62  * from remote clients.  Usually, such connector servers are made
63  * using {@link javax.management.remote.JMXConnectorServerFactory
64  * JMXConnectorServerFactory}.  However, specialized applications can
65  * use this class directly, for example with an {@link RMIServerImpl}
66  * object.</p>
67  *
68  * @since 1.5
69  */
70 public class RMIConnectorServer extends JMXConnectorServer {
71     /**
72      * <p>Name of the attribute that specifies whether the {@link
73      * RMIServer} stub that represents an RMI connector server should
74      * override an existing stub at the same address.  The value
75      * associated with this attribute, if any, should be a string that
76      * is equal, ignoring case, to <code>"true"</code> or
77      * <code>"false"</code>.  The default value is false.</p>
78      */
79     public static final String JNDI_REBIND_ATTRIBUTE =
80         "jmx.remote.jndi.rebind";
81 
82     /**
83      * <p>Name of the attribute that specifies the {@link
84      * RMIClientSocketFactory} for the RMI objects created in
85      * conjunction with this connector. The value associated with this
86      * attribute must be of type <code>RMIClientSocketFactory</code> and can
87      * only be specified in the <code>Map</code> argument supplied when
88      * creating a connector server.</p>
89      */
90     public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE =
91         "jmx.remote.rmi.client.socket.factory";
92 
93     /**
94      * <p>Name of the attribute that specifies the {@link
95      * RMIServerSocketFactory} for the RMI objects created in
96      * conjunction with this connector. The value associated with this
97      * attribute must be of type <code>RMIServerSocketFactory</code> and can
98      * only be specified in the <code>Map</code> argument supplied when
99      * creating a connector server.</p>
100      */
101     public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE =
102         "jmx.remote.rmi.server.socket.factory";
103 
104     /**
105      * Name of the attribute that specifies a list of class names acceptable
106      * as parameters to the {@link RMIServer#newClient(java.lang.Object) RMIServer.newClient()}
107      * remote method call.
108      * <p>
109      * This list of classes should correspond to the transitive closure of the
110      * credentials class (or classes) used by the installed {@linkplain JMXAuthenticator}
111      * associated with the {@linkplain RMIServer} implementation.
112      * <p>
113      * If the attribute is not set, or is null, then any class is
114      * deemed acceptable.
115      *
116      * @deprecated Use {@link #CREDENTIALS_FILTER_PATTERN} with a
117      * {@linkplain java.io.ObjectInputFilter.Config#createFilter
118      * filter pattern} string instead.
119      */
120     @Deprecated(since="10", forRemoval=true)
121     public static final String CREDENTIAL_TYPES =
122             "jmx.remote.rmi.server.credential.types";
123 
124     /**
125     * Name of the attribute that specifies an
126     * {@link ObjectInputFilter} pattern string to filter classes acceptable
127     * for {@link RMIServer#newClient(java.lang.Object) RMIServer.newClient()}
128     * remote method call.
129     * <p>
130     * The filter pattern must be in same format as used in
131     * {@link java.io.ObjectInputFilter.Config#createFilter}
132     * <p>
133     * This list of classes allowed by filter should correspond to the
134     * transitive closure of the credentials class (or classes) used by the
135     * installed {@linkplain JMXAuthenticator} associated with the
136     * {@linkplain RMIServer} implementation.
137     * If the attribute is not set then any class is deemed acceptable.
138     * @see ObjectInputFilter
139     */
140     public static final String CREDENTIALS_FILTER_PATTERN =
141         "jmx.remote.rmi.server.credentials.filter.pattern";
142 
143     /**
144      * This attribute defines a pattern from which to create a
145      * {@link java.io.ObjectInputFilter} that will be used when deserializing
146      * objects sent to the {@code JMXConnectorServer} by any client.
147      * <p>
148      * The filter will be called for any class found in the serialized
149      * stream sent to server by client, including all JMX defined classes
150      * (such as {@link javax.management.ObjectName}), all method parameters,
151      * and, if present in the stream, all classes transitively referred by
152      * the serial form of any deserialized object.
153      * The pattern must be in same format as used in
154      * {@link java.io.ObjectInputFilter.Config#createFilter}.
155      * It may define a white list of permitted classes, a black list of
156      * rejected classes, a maximum depth for the deserialized objects,
157      * etc.
158      * <p>
159      * To be functional, the filter should allow at least all the
160      * concrete types in the transitive closure of all objects that
161      * might get serialized when serializing all JMX classes referred
162      * as parameters in the {@link
163      * javax.management.remote.rmi.RMIConnection} interface,
164      * plus all classes that a {@link javax.management.remote.rmi.RMIConnector client}
165      * might need to transmit wrapped in {@linkplain java.rmi.MarshalledObject
166      * marshalled objects} in order to interoperate with the MBeans registered
167      * in the {@code MBeanServer}. That would potentially include all the
168      * concrete {@linkplain javax.management.openmbean  JMX OpenTypes} and the
169      * classes they use in their serial form.
170      * <p>
171      * Care must be taken when defining such a filter, as defining
172      * a white list too restrictive or a too wide a black list may
173      * prevent legitimate clients from interoperating with the
174      * {@code JMXConnectorServer}.
175      */
176     public static final String SERIAL_FILTER_PATTERN =
177        "jmx.remote.rmi.server.serial.filter.pattern";
178 
179     /**
180      * <p>Makes an <code>RMIConnectorServer</code>.
181      * This is equivalent to calling {@link #RMIConnectorServer(
182      * JMXServiceURL,Map,RMIServerImpl,MBeanServer)
183      * RMIConnectorServer(directoryURL,environment,null,null)}</p>
184      *
185      * @param url the URL defining how to create the connector server.
186      * Cannot be null.
187      *
188      * @param environment attributes governing the creation and
189      * storing of the RMI object.  Can be null, which is equivalent to
190      * an empty Map.
191      *
192      * @exception IllegalArgumentException if <code>url</code> is null.
193      *
194      * @exception MalformedURLException if <code>url</code> does not
195      * conform to the syntax for an RMI connector, or if its protocol
196      * is not recognized by this implementation. Only "rmi" is valid when
197      * this constructor is used.
198      *
199      * @exception IOException if the connector server cannot be created
200      * for some reason or if it is inevitable that its {@link #start()
201      * start} method will fail.
202      */
RMIConnectorServer(JMXServiceURL url, Map<String,?> environment)203     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment)
204             throws IOException {
205         this(url, environment, (MBeanServer) null);
206     }
207 
208     /**
209      * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
210      * server.
211      * This is equivalent to calling {@link #RMIConnectorServer(
212      * JMXServiceURL,Map,RMIServerImpl,MBeanServer)
213      * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p>
214      *
215      * @param url the URL defining how to create the connector server.
216      * Cannot be null.
217      *
218      * @param environment attributes governing the creation and
219      * storing of the RMI object.  Can be null, which is equivalent to
220      * an empty Map.
221      *
222      * @param mbeanServer the MBean server to which the new connector
223      * server is attached, or null if it will be attached by being
224      * registered as an MBean in the MBean server.
225      *
226      * @exception IllegalArgumentException if <code>url</code> is null.
227      *
228      * @exception MalformedURLException if <code>url</code> does not
229      * conform to the syntax for an RMI connector, or if its protocol
230      * is not recognized by this implementation. Only "rmi" is valid
231      * when this constructor is used.
232      *
233      * @exception IOException if the connector server cannot be created
234      * for some reason or if it is inevitable that its {@link #start()
235      * start} method will fail.
236      */
RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, MBeanServer mbeanServer)237     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
238                               MBeanServer mbeanServer)
239             throws IOException {
240         this(url, environment, (RMIServerImpl) null, mbeanServer);
241     }
242 
243     /**
244      * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
245      * server.</p>
246      *
247      * @param url the URL defining how to create the connector server.
248      * Cannot be null.
249      *
250      * @param environment attributes governing the creation and
251      * storing of the RMI object.  Can be null, which is equivalent to
252      * an empty Map.
253      *
254      * @param rmiServerImpl An implementation of the RMIServer interface,
255      *  consistent with the protocol type specified in <var>url</var>.
256      *  If this parameter is non null, the protocol type specified by
257      *  <var>url</var> is not constrained, and is assumed to be valid.
258      *  Otherwise, only "rmi" will be recognized.
259      *
260      * @param mbeanServer the MBean server to which the new connector
261      * server is attached, or null if it will be attached by being
262      * registered as an MBean in the MBean server.
263      *
264      * @exception IllegalArgumentException if <code>url</code> is null.
265      *
266      * @exception MalformedURLException if <code>url</code> does not
267      * conform to the syntax for an RMI connector, or if its protocol
268      * is not recognized by this implementation. Only "rmi" is recognized
269      * when <var>rmiServerImpl</var> is null.
270      *
271      * @exception IOException if the connector server cannot be created
272      * for some reason or if it is inevitable that its {@link #start()
273      * start} method will fail.
274      *
275      * @see #start
276      */
RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, RMIServerImpl rmiServerImpl, MBeanServer mbeanServer)277     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
278                               RMIServerImpl rmiServerImpl,
279                               MBeanServer mbeanServer)
280             throws IOException {
281         super(mbeanServer);
282 
283         if (url == null) throw new
284             IllegalArgumentException("Null JMXServiceURL");
285         if (rmiServerImpl == null) {
286             final String prt = url.getProtocol();
287             if (prt == null || !(prt.equals("rmi"))) {
288                 final String msg = "Invalid protocol type: " + prt;
289                 throw new MalformedURLException(msg);
290             }
291             final String urlPath = url.getURLPath();
292             if (!urlPath.isEmpty()
293                 && !urlPath.equals("/")
294                 && !urlPath.startsWith("/jndi/")) {
295                 final String msg = "URL path must be empty or start with " +
296                     "/jndi/";
297                 throw new MalformedURLException(msg);
298             }
299         }
300 
301         if (environment == null)
302             this.attributes = Collections.emptyMap();
303         else {
304             EnvHelp.checkAttributes(environment);
305             this.attributes = Collections.unmodifiableMap(environment);
306         }
307 
308         this.address = url;
309         this.rmiServerImpl = rmiServerImpl;
310     }
311 
312     /**
313      * <p>Returns a client stub for this connector server.  A client
314      * stub is a serializable object whose {@link
315      * JMXConnector#connect(Map) connect} method can be used to make
316      * one new connection to this connector server.</p>
317      *
318      * @param env client connection parameters of the same sort that
319      * could be provided to {@link JMXConnector#connect(Map)
320      * JMXConnector.connect(Map)}.  Can be null, which is equivalent
321      * to an empty map.
322      *
323      * @return a client stub that can be used to make a new connection
324      * to this connector server.
325      *
326      * @exception UnsupportedOperationException if this connector
327      * server does not support the generation of client stubs.
328      *
329      * @exception IllegalStateException if the JMXConnectorServer is
330      * not started (see {@link #isActive()}).
331      *
332      * @exception IOException if a communications problem means that a
333      * stub cannot be created.
334      **/
toJMXConnector(Map<String,?> env)335     public JMXConnector toJMXConnector(Map<String,?> env) throws IOException {
336         // The serialized for of rmiServerImpl is automatically
337         // a RMI server stub.
338         if (!isActive()) throw new
339             IllegalStateException("Connector is not active");
340 
341         // Merge maps
342         Map<String, Object> usemap = new HashMap<String, Object>(
343                 (this.attributes==null)?Collections.<String, Object>emptyMap():
344                     this.attributes);
345 
346         if (env != null) {
347             EnvHelp.checkAttributes(env);
348             usemap.putAll(env);
349         }
350 
351         usemap = EnvHelp.filterAttributes(usemap);
352 
353         final RMIServer stub=(RMIServer)rmiServerImpl.toStub();
354 
355         return new RMIConnector(stub, usemap);
356     }
357 
358     /**
359      * <p>Activates the connector server, that is starts listening for
360      * client connections.  Calling this method when the connector
361      * server is already active has no effect.  Calling this method
362      * when the connector server has been stopped will generate an
363      * <code>IOException</code>.</p>
364      *
365      * <p>The behavior of this method when called for the first time
366      * depends on the parameters that were supplied at construction,
367      * as described below.</p>
368      *
369      * <p>First, an object of a subclass of {@link RMIServerImpl} is
370      * required, to export the connector server through RMI:</p>
371      *
372      * <ul>
373      *
374      * <li>If an <code>RMIServerImpl</code> was supplied to the
375      * constructor, it is used.
376      *
377      * <li>Otherwise, if the <code>JMXServiceURL</code>
378      * was null, or its protocol part was <code>rmi</code>, an object
379      * of type {@link RMIJRMPServerImpl} is created.
380      *
381      * <li>Otherwise, the implementation can create an
382      * implementation-specific {@link RMIServerImpl} or it can throw
383      * {@link MalformedURLException}.
384      *
385      * </ul>
386      *
387      * <p>If the given address includes a JNDI directory URL as
388      * specified in the package documentation for {@link
389      * javax.management.remote.rmi}, then this
390      * <code>RMIConnectorServer</code> will bootstrap by binding the
391      * <code>RMIServerImpl</code> to the given address.</p>
392      *
393      * <p>If the URL path part of the <code>JMXServiceURL</code> was
394      * empty or a single slash (<code>/</code>), then the RMI object
395      * will not be bound to a directory.  Instead, a reference to it
396      * will be encoded in the URL path of the RMIConnectorServer
397      * address (returned by {@link #getAddress()}).  The encodings for
398      * <code>rmi</code> are described in the package documentation for
399      * {@link javax.management.remote.rmi}.</p>
400      *
401      * <p>The behavior when the URL path is neither empty nor a JNDI
402      * directory URL, or when the protocol is not <code>rmi</code>,
403      * is implementation defined, and may include throwing
404      * {@link MalformedURLException} when the connector server is created
405      * or when it is started.</p>
406      *
407      * @exception IllegalStateException if the connector server has
408      * not been attached to an MBean server.
409      * @exception IOException if the connector server cannot be
410      * started.
411      */
start()412     public synchronized void start() throws IOException {
413         final boolean tracing = logger.traceOn();
414 
415         if (state == STARTED) {
416             if (tracing) logger.trace("start", "already started");
417             return;
418         } else if (state == STOPPED) {
419             if (tracing) logger.trace("start", "already stopped");
420             throw new IOException("The server has been stopped.");
421         }
422 
423         if (getMBeanServer() == null)
424             throw new IllegalStateException("This connector server is not " +
425                                             "attached to an MBean server");
426 
427         // Check the internal access file property to see
428         // if an MBeanServerForwarder is to be provided
429         //
430         if (attributes != null) {
431             // Check if access file property is specified
432             //
433             String accessFile =
434                 (String) attributes.get("jmx.remote.x.access.file");
435             if (accessFile != null) {
436                 // Access file property specified, create an instance
437                 // of the MBeanServerFileAccessController class
438                 //
439                 MBeanServerForwarder mbsf;
440                 try {
441                     mbsf = new MBeanServerFileAccessController(accessFile);
442                 } catch (IOException e) {
443                     throw EnvHelp.initCause(
444                         new IllegalArgumentException(e.getMessage()), e);
445                 }
446                 // Set the MBeanServerForwarder
447                 //
448                 setMBeanServerForwarder(mbsf);
449             }
450         }
451 
452         try {
453             if (tracing) logger.trace("start", "setting default class loader");
454             defaultClassLoader = EnvHelp.resolveServerClassLoader(
455                     attributes, getMBeanServer());
456         } catch (InstanceNotFoundException infc) {
457             IllegalArgumentException x = new
458                 IllegalArgumentException("ClassLoader not found: "+infc);
459             throw EnvHelp.initCause(x,infc);
460         }
461 
462         if (tracing) logger.trace("start", "setting RMIServer object");
463         final RMIServerImpl rmiServer;
464 
465         if (rmiServerImpl != null)
466             rmiServer = rmiServerImpl;
467         else
468             rmiServer = newServer();
469 
470         rmiServer.setMBeanServer(getMBeanServer());
471         rmiServer.setDefaultClassLoader(defaultClassLoader);
472         rmiServer.setRMIConnectorServer(this);
473         rmiServer.export();
474 
475         try {
476             if (tracing) logger.trace("start", "getting RMIServer object to export");
477             final RMIServer objref = objectToBind(rmiServer, attributes);
478 
479             if (address != null && address.getURLPath().startsWith("/jndi/")) {
480                 final String jndiUrl = address.getURLPath().substring(6);
481 
482                 if (tracing)
483                     logger.trace("start", "Using external directory: " + jndiUrl);
484 
485                 String stringBoolean = (String) attributes.get(JNDI_REBIND_ATTRIBUTE);
486                 final boolean rebind = EnvHelp.computeBooleanFromString( stringBoolean );
487 
488                 if (tracing)
489                     logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);
490 
491                 try {
492                     if (tracing) logger.trace("start", "binding to " + jndiUrl);
493 
494                     final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes);
495 
496                     bind(jndiUrl, usemap, objref, rebind);
497 
498                     boundJndiUrl = jndiUrl;
499                 } catch (NamingException e) {
500                     // fit e in the nested exception if we are on 1.4
501                     throw newIOException("Cannot bind to URL ["+jndiUrl+"]: "
502                                          + e, e);
503                 }
504             } else {
505                 // if jndiURL is null, we must encode the stub into the URL.
506                 if (tracing) logger.trace("start", "Encoding URL");
507 
508                 encodeStubInAddress(objref, attributes);
509 
510                 if (tracing) logger.trace("start", "Encoded URL: " + this.address);
511             }
512         } catch (Exception e) {
513             try {
514                 rmiServer.close();
515             } catch (Exception x) {
516                 // OK: we are already throwing another exception
517             }
518             if (e instanceof RuntimeException)
519                 throw (RuntimeException) e;
520             else if (e instanceof IOException)
521                 throw (IOException) e;
522             else
523                 throw newIOException("Got unexpected exception while " +
524                                      "starting the connector server: "
525                                      + e, e);
526         }
527 
528         rmiServerImpl = rmiServer;
529 
530         synchronized(openedServers) {
531             openedServers.add(this);
532         }
533 
534         state = STARTED;
535 
536         if (tracing) {
537             logger.trace("start", "Connector Server Address = " + address);
538             logger.trace("start", "started.");
539         }
540     }
541 
542     /**
543      * <p>Deactivates the connector server, that is, stops listening for
544      * client connections.  Calling this method will also close all
545      * client connections that were made by this server.  After this
546      * method returns, whether normally or with an exception, the
547      * connector server will not create any new client
548      * connections.</p>
549      *
550      * <p>Once a connector server has been stopped, it cannot be started
551      * again.</p>
552      *
553      * <p>Calling this method when the connector server has already
554      * been stopped has no effect.  Calling this method when the
555      * connector server has not yet been started will disable the
556      * connector server object permanently.</p>
557      *
558      * <p>If closing a client connection produces an exception, that
559      * exception is not thrown from this method.  A {@link
560      * JMXConnectionNotification} is emitted from this MBean with the
561      * connection ID of the connection that could not be closed.</p>
562      *
563      * <p>Closing a connector server is a potentially slow operation.
564      * For example, if a client machine with an open connection has
565      * crashed, the close operation might have to wait for a network
566      * protocol timeout.  Callers that do not want to block in a close
567      * operation should do it in a separate thread.</p>
568      *
569      * <p>This method calls the method {@link RMIServerImpl#close()
570      * close} on the connector server's <code>RMIServerImpl</code>
571      * object.</p>
572      *
573      * <p>If the <code>RMIServerImpl</code> was bound to a JNDI
574      * directory by the {@link #start() start} method, it is unbound
575      * from the directory by this method.</p>
576      *
577      * @exception IOException if the server cannot be closed cleanly,
578      * or if the <code>RMIServerImpl</code> cannot be unbound from the
579      * directory.  When this exception is thrown, the server has
580      * already attempted to close all client connections, if
581      * appropriate; to call {@link RMIServerImpl#close()}; and to
582      * unbind the <code>RMIServerImpl</code> from its directory, if
583      * appropriate.  All client connections are closed except possibly
584      * those that generated exceptions when the server attempted to
585      * close them.
586      */
stop()587     public void stop() throws IOException {
588         final boolean tracing = logger.traceOn();
589 
590         synchronized (this) {
591             if (state == STOPPED) {
592                 if (tracing) logger.trace("stop","already stopped.");
593                 return;
594             } else if (state == CREATED) {
595                 if (tracing) logger.trace("stop","not started yet.");
596             }
597 
598             if (tracing) logger.trace("stop", "stopping.");
599             state = STOPPED;
600         }
601 
602         synchronized(openedServers) {
603             openedServers.remove(this);
604         }
605 
606         IOException exception = null;
607 
608         // rmiServerImpl can be null if stop() called without start()
609         if (rmiServerImpl != null) {
610             try {
611                 if (tracing) logger.trace("stop", "closing RMI server.");
612                 rmiServerImpl.close();
613             } catch (IOException e) {
614                 if (tracing) logger.trace("stop", "failed to close RMI server: " + e);
615                 if (logger.debugOn()) logger.debug("stop",e);
616                 exception = e;
617             }
618         }
619 
620         if (boundJndiUrl != null) {
621             try {
622                 if (tracing)
623                     logger.trace("stop",
624                           "unbind from external directory: " + boundJndiUrl);
625 
626                 final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes);
627 
628                 InitialContext ctx =
629                     new InitialContext(usemap);
630 
631                 ctx.unbind(boundJndiUrl);
632 
633                 ctx.close();
634             } catch (NamingException e) {
635                 if (tracing) logger.trace("stop", "failed to unbind RMI server: "+e);
636                 if (logger.debugOn()) logger.debug("stop",e);
637                 // fit e in as the nested exception if we are on 1.4
638                 if (exception == null)
639                     exception = newIOException("Cannot bind to URL: " + e, e);
640             }
641         }
642 
643         if (exception != null) throw exception;
644 
645         if (tracing) logger.trace("stop", "stopped");
646     }
647 
isActive()648     public synchronized boolean isActive() {
649         return (state == STARTED);
650     }
651 
getAddress()652     public JMXServiceURL getAddress() {
653         if (!isActive())
654             return null;
655         return address;
656     }
657 
getAttributes()658     public Map<String,?> getAttributes() {
659         Map<String, ?> map = EnvHelp.filterAttributes(attributes);
660         return Collections.unmodifiableMap(map);
661     }
662 
663     @Override
664     public synchronized
setMBeanServerForwarder(MBeanServerForwarder mbsf)665         void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
666         super.setMBeanServerForwarder(mbsf);
667         if (rmiServerImpl != null)
668             rmiServerImpl.setMBeanServer(getMBeanServer());
669     }
670 
671     /* We repeat the definitions of connection{Opened,Closed,Failed}
672        here so that they are accessible to other classes in this package
673        even though they have protected access.  */
674 
675     @Override
connectionOpened(String connectionId, String message, Object userData)676     protected void connectionOpened(String connectionId, String message,
677                                     Object userData) {
678         super.connectionOpened(connectionId, message, userData);
679     }
680 
681     @Override
connectionClosed(String connectionId, String message, Object userData)682     protected void connectionClosed(String connectionId, String message,
683                                     Object userData) {
684         super.connectionClosed(connectionId, message, userData);
685     }
686 
687     @Override
connectionFailed(String connectionId, String message, Object userData)688     protected void connectionFailed(String connectionId, String message,
689                                     Object userData) {
690         super.connectionFailed(connectionId, message, userData);
691     }
692 
693     /**
694      * Bind a stub to a registry.
695      * @param jndiUrl URL of the stub in the registry, extracted
696      *        from the <code>JMXServiceURL</code>.
697      * @param attributes A Hashtable containing environment parameters,
698      *        built from the Map specified at this object creation.
699      * @param rmiServer The object to bind in the registry
700      * @param rebind true if the object must be rebound.
701      **/
bind(String jndiUrl, Hashtable<?, ?> attributes, RMIServer rmiServer, boolean rebind)702     void bind(String jndiUrl, Hashtable<?, ?> attributes,
703               RMIServer rmiServer, boolean rebind)
704         throws NamingException, MalformedURLException {
705         // if jndiURL is not null, we nust bind the stub to a
706         // directory.
707         InitialContext ctx =
708             new InitialContext(attributes);
709 
710         if (rebind)
711             ctx.rebind(jndiUrl, rmiServer);
712         else
713             ctx.bind(jndiUrl, rmiServer);
714         ctx.close();
715     }
716 
717     /**
718      * Creates a new RMIServerImpl.
719      **/
newServer()720     RMIServerImpl newServer() throws IOException {
721         final int port;
722         if (address == null)
723             port = 0;
724         else
725             port = address.getPort();
726 
727         return newJRMPServer(attributes, port);
728     }
729 
730     /**
731      * Encode a stub into the JMXServiceURL.
732      * @param rmiServer The stub object to encode in the URL
733      * @param attributes A Map containing environment parameters,
734      *        built from the Map specified at this object creation.
735      **/
encodeStubInAddress( RMIServer rmiServer, Map<String, ?> attributes)736     private void encodeStubInAddress(
737             RMIServer rmiServer, Map<String, ?> attributes)
738             throws IOException {
739 
740         final String protocol, host;
741         final int port;
742 
743         if (address == null) {
744             protocol = "rmi";
745             host = null; // will default to local host name
746             port = 0;
747         } else {
748             protocol = address.getProtocol();
749             host = (address.getHost().isEmpty()) ? null : address.getHost();
750             port = address.getPort();
751         }
752 
753         final String urlPath = encodeStub(rmiServer, attributes);
754 
755         address = new JMXServiceURL(protocol, host, port, urlPath);
756     }
757 
758     /**
759      * Returns the IOR of the given rmiServer.
760      **/
encodeStub( RMIServer rmiServer, Map<String, ?> env)761     static String encodeStub(
762             RMIServer rmiServer, Map<String, ?> env) throws IOException {
763         return "/stub/" + encodeJRMPStub(rmiServer, env);
764     }
765 
encodeJRMPStub( RMIServer rmiServer, Map<String, ?> env)766     static String encodeJRMPStub(
767             RMIServer rmiServer, Map<String, ?> env)
768             throws IOException {
769         ByteArrayOutputStream bout = new ByteArrayOutputStream();
770         ObjectOutputStream oout = new ObjectOutputStream(bout);
771         oout.writeObject(rmiServer);
772         oout.close();
773         byte[] bytes = bout.toByteArray();
774         return byteArrayToBase64(bytes);
775     }
776 
777     /**
778      * Object that we will bind to the registry.
779      * This object is a stub connected to our RMIServerImpl.
780      **/
objectToBind( RMIServerImpl rmiServer, Map<String, ?> env)781     private static RMIServer objectToBind(
782             RMIServerImpl rmiServer, Map<String, ?> env)
783         throws IOException {
784         return (RMIServer)rmiServer.toStub();
785     }
786 
newJRMPServer(Map<String, ?> env, int port)787     private static RMIServerImpl newJRMPServer(Map<String, ?> env, int port)
788             throws IOException {
789         RMIClientSocketFactory csf = (RMIClientSocketFactory)
790             env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
791         RMIServerSocketFactory ssf = (RMIServerSocketFactory)
792             env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
793         return new RMIJRMPServerImpl(port, csf, ssf, env);
794     }
795 
byteArrayToBase64(byte[] a)796     private static String byteArrayToBase64(byte[] a) {
797         int aLen = a.length;
798         int numFullGroups = aLen/3;
799         int numBytesInPartialGroup = aLen - 3*numFullGroups;
800         int resultLen = 4*((aLen + 2)/3);
801         final StringBuilder result = new StringBuilder(resultLen);
802 
803         // Translate all full groups from byte array elements to Base64
804         int inCursor = 0;
805         for (int i=0; i<numFullGroups; i++) {
806             int byte0 = a[inCursor++] & 0xff;
807             int byte1 = a[inCursor++] & 0xff;
808             int byte2 = a[inCursor++] & 0xff;
809             result.append(intToAlpha[byte0 >> 2]);
810             result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
811             result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
812             result.append(intToAlpha[byte2 & 0x3f]);
813         }
814 
815         // Translate partial group if present
816         if (numBytesInPartialGroup != 0) {
817             int byte0 = a[inCursor++] & 0xff;
818             result.append(intToAlpha[byte0 >> 2]);
819             if (numBytesInPartialGroup == 1) {
820                 result.append(intToAlpha[(byte0 << 4) & 0x3f]);
821                 result.append("==");
822             } else {
823                 // assert numBytesInPartialGroup == 2;
824                 int byte1 = a[inCursor++] & 0xff;
825                 result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
826                 result.append(intToAlpha[(byte1 << 2)&0x3f]);
827                 result.append('=');
828             }
829         }
830         // assert inCursor == a.length;
831         // assert result.length() == resultLen;
832         return result.toString();
833     }
834 
835     /**
836      * This array is a lookup table that translates 6-bit positive integer
837      * index values into their "Base64 Alphabet" equivalents as specified
838      * in Table 1 of RFC 2045.
839      */
840     private static final char intToAlpha[] = {
841         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
842         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
843         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
844         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
845         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
846     };
847 
848     /**
849      * Construct a new IOException with a nested exception.
850      * The nested exception is set only if JDK {@literal >= 1.4}
851      */
newIOException(String message, Throwable cause)852     private static IOException newIOException(String message,
853                                               Throwable cause) {
854         final IOException x = new IOException(message);
855         return EnvHelp.initCause(x,cause);
856     }
857 
858 
859     // Private variables
860     // -----------------
861 
862     private static ClassLogger logger =
863         new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer");
864 
865     private JMXServiceURL address;
866     private RMIServerImpl rmiServerImpl;
867     private final Map<String, ?> attributes;
868     private ClassLoader defaultClassLoader = null;
869 
870     private String boundJndiUrl;
871 
872     // state
873     private static final int CREATED = 0;
874     private static final int STARTED = 1;
875     private static final int STOPPED = 2;
876 
877     private int state = CREATED;
878     private final static Set<RMIConnectorServer> openedServers =
879             new HashSet<RMIConnectorServer>();
880 }
881