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 }