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 }