001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.protocols.jmx;
028    import org.opends.messages.Message;
029    
030    import java.util.*;
031    
032    import javax.management.remote.JMXAuthenticator;
033    import javax.security.auth.Subject;
034    
035    import org.opends.server.api.plugin.PluginResult;
036    import org.opends.server.core.BindOperationBasis;
037    import org.opends.server.core.DirectoryServer;
038    import org.opends.server.core.PluginConfigManager;
039    import org.opends.messages.CoreMessages;
040    import org.opends.server.protocols.asn1.ASN1OctetString;
041    import org.opends.server.protocols.ldap.LDAPResultCode;
042    import org.opends.server.types.Control;
043    import org.opends.server.types.DisconnectReason;
044    import org.opends.server.types.Privilege;
045    import org.opends.server.types.ResultCode;
046    import org.opends.server.types.DN;
047    import org.opends.server.types.AuthenticationInfo;
048    import org.opends.server.types.LDAPException;
049    
050    import static org.opends.server.loggers.debug.DebugLogger.*;
051    import static org.opends.messages.ProtocolMessages.*;
052    
053    import org.opends.server.loggers.debug.DebugTracer;
054    import org.opends.server.types.DebugLogLevel;
055    
056    /**
057     * A <code>RMIAuthenticator</code> manages authentication for the secure
058     * RMI connectors. It receives authentication requests from clients as a
059     * SASL/PLAIN challenge and relies on a SASL server plus the local LDAP
060     * authentication accept or reject the user being connected.
061     */
062    public class RmiAuthenticator implements JMXAuthenticator
063    {
064      /**
065       * The tracer object for the debug logger.
066       */
067      private static final DebugTracer TRACER = getTracer();
068    
069    
070        /**
071         * The client authencation mode. <code>true</code> indicates that the
072         * client will be authenticated by its certificate (SSL protocol).
073         * <code>true</code> indicate , that we have to perform an lDAP
074         * authentication
075         */
076        private boolean needClientCertificate = false;
077    
078        /**
079         * Indicate if the we are in the finalized phase.
080         *
081         * @see JmxConnectionHandler
082         */
083        private boolean finalizedPhase = false;
084    
085      /**
086       * The JMX Client connection to be used to perform the bind (auth)
087       * call.
088       */
089      private JmxConnectionHandler jmxConnectionHandler;
090    
091      /**
092       * Constructs a <code>RmiAuthenticator</code>.
093       *
094       * @param jmxConnectionHandler
095       *        The jmxConnectionHandler associated to this RmiAuthenticator
096       */
097      public RmiAuthenticator(JmxConnectionHandler jmxConnectionHandler)
098      {
099        this.jmxConnectionHandler = jmxConnectionHandler;
100      }
101    
102      /**
103       * Set that we are in the finalized phase.
104       *
105       * @param finalizedPhase Set to true, it indicates that we are in
106       * the finalized phase that that we other connection should be accepted.
107       *
108       * @see JmxConnectionHandler
109       */
110      public synchronized void setFinalizedPhase(boolean finalizedPhase)
111      {
112        this.finalizedPhase = finalizedPhase;
113      }
114    
115      /**
116       * Authenticates a RMI client. The credentials received are composed of
117       * a SASL/PLAIN authentication id and a password.
118       *
119       * @param credentials
120       *            the SASL/PLAIN credentials to validate
121       * @return a <code>Subject</code> holding the principal(s)
122       *         authenticated
123       */
124      public Subject authenticate(Object credentials)
125      {
126        //
127        // If we are in the finalized phase, we should not accept
128        // new connection
129        if (finalizedPhase)
130        {
131          SecurityException se = new SecurityException();
132          throw se;
133        }
134    
135        //
136        // Credentials are null !!!
137        if (credentials == null)
138        {
139          SecurityException se = new SecurityException();
140          throw se;
141        }
142        Object c[] = (Object[]) credentials;
143        String authcID = (String) c[0];
144        String password = (String) c[1];
145    
146        //
147        // The authcID is used at forwarder level to identify the calling
148        // client
149        if (authcID == null)
150        {
151          if (debugEnabled())
152          {
153            TRACER.debugVerbose("User name is Null");
154          }
155          SecurityException se = new SecurityException();
156          throw se;
157        }
158        if (password == null)
159        {
160          if (debugEnabled())
161          {
162            TRACER.debugVerbose("User password is Null ");
163          }
164    
165          SecurityException se = new SecurityException();
166          throw se;
167        }
168    
169        if (debugEnabled())
170        {
171          TRACER.debugVerbose("UserName = %s", authcID);
172        }
173    
174        //
175        // Declare the client connection
176        JmxClientConnection jmxClientConnection;
177    
178        //
179        // Try to see if we have an Ldap Authentication
180        // Which should be the case in the current implementation
181        try
182        {
183          jmxClientConnection = bind(authcID, password);
184        }
185        catch (Exception e)
186        {
187          if (debugEnabled())
188          {
189            TRACER.debugCaught(DebugLogLevel.ERROR, e);
190          }
191          SecurityException se = new SecurityException(e.getMessage());
192          se.initCause(e);
193          throw se;
194        }
195    
196        //
197        // If we've gotten here, then the authentication was
198        // successful. We'll take the connection so
199        // invoke the post-connect plugins.
200        PluginConfigManager pluginManager = DirectoryServer
201            .getPluginConfigManager();
202        PluginResult.PostConnect pluginResult = pluginManager
203            .invokePostConnectPlugins(jmxClientConnection);
204        if (!pluginResult.continueProcessing())
205        {
206          jmxClientConnection.disconnect(pluginResult.getDisconnectReason(),
207              pluginResult.sendDisconnectNotification(),
208              pluginResult.getErrorMessage());
209    
210          if (debugEnabled())
211          {
212            TRACER.debugVerbose("Disconnect result from post connect plugins: " +
213                "%s: %s ", pluginResult.getDisconnectReason(),
214                pluginResult.getErrorMessage());
215          }
216    
217          SecurityException se = new SecurityException();
218          throw se;
219        }
220    
221        // initialize a subject
222        Subject s = new Subject();
223    
224        //
225        // Add the Principal. The current implementation doesn't use it
226    
227        s.getPrincipals().add(new OpendsJmxPrincipal(authcID));
228    
229        // add the connection client object
230        // this connection client is used at forwarder level to identify the
231        // calling client
232        s.getPrivateCredentials().add(new Credential(jmxClientConnection));
233    
234        return s;
235    
236      }
237    
238      /**
239       * Process bind operation.
240       *
241       * @param authcID
242       *            The LDAP user.
243       * @param password
244       *            The Ldap password associated to the user.
245       */
246      private JmxClientConnection bind(String authcID, String password)
247      {
248        ArrayList<Control> requestControls = new ArrayList<Control>();
249    
250        //
251        // We have a new client connection
252        DN bindDN;
253        try
254        {
255          bindDN = DN.decode(authcID);
256        }
257        catch (Exception e)
258        {
259          LDAPException ldapEx = new LDAPException(
260              LDAPResultCode.INVALID_CREDENTIALS,
261              CoreMessages.INFO_RESULT_INVALID_CREDENTIALS.get());
262          SecurityException se = new SecurityException();
263          se.initCause(ldapEx);
264          throw se;
265        }
266        ASN1OctetString bindPW;
267        if (password == null)
268        {
269          bindPW = null;
270        }
271        else
272        {
273          bindPW = new ASN1OctetString(password);
274        }
275    
276        AuthenticationInfo authInfo = new AuthenticationInfo();
277        JmxClientConnection jmxClientConnection = new JmxClientConnection(
278            jmxConnectionHandler, authInfo);
279    
280        BindOperationBasis bindOp = new BindOperationBasis(jmxClientConnection,
281            jmxClientConnection.nextOperationID(),
282            jmxClientConnection.nextMessageID(), requestControls,
283            jmxConnectionHandler.getRMIConnector().getProtocolVersion(),
284            new ASN1OctetString(authcID), bindPW);
285    
286        bindOp.run();
287        if (bindOp.getResultCode() == ResultCode.SUCCESS)
288        {
289          if (debugEnabled())
290          {
291            TRACER.debugVerbose("User is authenticated");
292          }
293    
294          authInfo = bindOp.getAuthenticationInfo();
295          jmxClientConnection.setAuthenticationInfo(authInfo);
296    
297          // Check JMX_READ privilege.
298          if (! jmxClientConnection.hasPrivilege(Privilege.JMX_READ, null))
299          {
300            Message message = ERR_JMX_INSUFFICIENT_PRIVILEGES.get();
301    
302            jmxClientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
303                false, message);
304    
305            throw new SecurityException(message.toString());
306          }
307          return jmxClientConnection;
308        }
309        else
310        {
311          //
312          // Set the initcause.
313          LDAPException ldapEx = new LDAPException(
314              LDAPResultCode.INVALID_CREDENTIALS,
315              CoreMessages.INFO_RESULT_INVALID_CREDENTIALS.get());
316          SecurityException se = new SecurityException("return code: "
317              + bindOp.getResultCode());
318          se.initCause(ldapEx);
319          throw se;
320        }
321      }
322    }