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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.protocols.jmx; 028 029import static org.opends.messages.ProtocolMessages.*; 030 031import java.util.ArrayList; 032 033import javax.management.remote.JMXAuthenticator; 034import javax.security.auth.Subject; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.ldap.ByteString; 039import org.forgerock.opendj.ldap.ResultCode; 040import org.opends.messages.CoreMessages; 041import org.opends.server.api.plugin.PluginResult; 042import org.opends.server.core.BindOperationBasis; 043import org.opends.server.core.DirectoryServer; 044import org.opends.server.core.PluginConfigManager; 045import org.opends.server.protocols.ldap.LDAPResultCode; 046import org.opends.server.types.AuthenticationInfo; 047import org.opends.server.types.Control; 048import org.opends.server.types.DN; 049import org.opends.server.types.DisconnectReason; 050import org.opends.server.types.LDAPException; 051import org.opends.server.types.Privilege; 052 053/** 054 * A <code>RMIAuthenticator</code> manages authentication for the secure 055 * RMI connectors. It receives authentication requests from clients as a 056 * SASL/PLAIN challenge and relies on a SASL server plus the local LDAP 057 * authentication accept or reject the user being connected. 058 */ 059public class RmiAuthenticator implements JMXAuthenticator 060{ 061 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 062 063 /** 064 * Indicate if the we are in the finalized phase. 065 * 066 * @see JmxConnectionHandler 067 */ 068 private boolean finalizedPhase; 069 070 /** The JMX Client connection to be used to perform the bind (auth) call. */ 071 private JmxConnectionHandler jmxConnectionHandler; 072 073 /** 074 * Constructs a <code>RmiAuthenticator</code>. 075 * 076 * @param jmxConnectionHandler 077 * The jmxConnectionHandler associated to this RmiAuthenticator 078 */ 079 public RmiAuthenticator(JmxConnectionHandler jmxConnectionHandler) 080 { 081 this.jmxConnectionHandler = jmxConnectionHandler; 082 } 083 084 /** 085 * Set that we are in the finalized phase. 086 * 087 * @param finalizedPhase Set to true, it indicates that we are in 088 * the finalized phase that that we other connection should be accepted. 089 * 090 * @see JmxConnectionHandler 091 */ 092 public synchronized void setFinalizedPhase(boolean finalizedPhase) 093 { 094 this.finalizedPhase = finalizedPhase; 095 } 096 097 /** 098 * Authenticates a RMI client. The credentials received are composed of 099 * a SASL/PLAIN authentication id and a password. 100 * 101 * @param credentials 102 * the SASL/PLAIN credentials to validate 103 * @return a <code>Subject</code> holding the principal(s) 104 * authenticated 105 */ 106 @Override 107 public Subject authenticate(Object credentials) 108 { 109 // If we are in the finalized phase, we should not accept new connection 110 if (finalizedPhase 111 || credentials == null) 112 { 113 throw new SecurityException(); 114 } 115 Object c[] = (Object[]) credentials; 116 String authcID = (String) c[0]; 117 String password = (String) c[1]; 118 119 // The authcID is used at forwarder level to identify the calling client 120 if (authcID == null) 121 { 122 logger.trace("User name is Null"); 123 throw new SecurityException(); 124 } 125 if (password == null) 126 { 127 logger.trace("User password is Null "); 128 throw new SecurityException(); 129 } 130 131 logger.trace("UserName = %s", authcID); 132 133 // Try to see if we have an Ldap Authentication 134 // Which should be the case in the current implementation 135 JmxClientConnection jmxClientConnection; 136 try 137 { 138 jmxClientConnection = bind(authcID, password); 139 } 140 catch (Exception e) 141 { 142 logger.traceException(e); 143 SecurityException se = new SecurityException(e.getMessage()); 144 throw se; 145 } 146 147 // If we've gotten here, then the authentication was successful. 148 // We'll take the connection so invoke the post-connect plugins. 149 PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); 150 PluginResult.PostConnect pluginResult = pluginManager.invokePostConnectPlugins(jmxClientConnection); 151 if (!pluginResult.continueProcessing()) 152 { 153 jmxClientConnection.disconnect(pluginResult.getDisconnectReason(), 154 pluginResult.sendDisconnectNotification(), 155 pluginResult.getErrorMessage()); 156 157 if (logger.isTraceEnabled()) 158 { 159 logger.trace("Disconnect result from post connect plugins: " + 160 "%s: %s ", pluginResult.getDisconnectReason(), 161 pluginResult.getErrorMessage()); 162 } 163 164 throw new SecurityException(); 165 } 166 167 // initialize a subject 168 Subject s = new Subject(); 169 170 // Add the Principal. The current implementation doesn't use it 171 s.getPrincipals().add(new OpendsJmxPrincipal(authcID)); 172 173 // add the connection client object 174 // this connection client is used at forwarder level to identify the calling client 175 s.getPrivateCredentials().add(new Credential(jmxClientConnection)); 176 177 return s; 178 } 179 180 /** 181 * Process bind operation. 182 * 183 * @param authcID 184 * The LDAP user. 185 * @param password 186 * The Ldap password associated to the user. 187 */ 188 private JmxClientConnection bind(String authcID, String password) 189 { 190 try 191 { 192 DN.valueOf(authcID); 193 } 194 catch (Exception e) 195 { 196 LDAPException ldapEx = new LDAPException( 197 LDAPResultCode.INVALID_CREDENTIALS, 198 CoreMessages.INFO_RESULT_INVALID_CREDENTIALS.get()); 199 throw new SecurityException(ldapEx); 200 } 201 202 ArrayList<Control> requestControls = new ArrayList<>(); 203 ByteString bindPW = password != null ? ByteString.valueOfUtf8(password) : null; 204 205 AuthenticationInfo authInfo = new AuthenticationInfo(); 206 JmxClientConnection jmxClientConnection = new JmxClientConnection( 207 jmxConnectionHandler, authInfo); 208 209 BindOperationBasis bindOp = new BindOperationBasis(jmxClientConnection, 210 jmxClientConnection.nextOperationID(), 211 jmxClientConnection.nextMessageID(), requestControls, 212 jmxConnectionHandler.getRMIConnector().getProtocolVersion(), 213 ByteString.valueOfUtf8(authcID), bindPW); 214 215 bindOp.run(); 216 if (bindOp.getResultCode() == ResultCode.SUCCESS) 217 { 218 logger.trace("User is authenticated"); 219 220 authInfo = bindOp.getAuthenticationInfo(); 221 jmxClientConnection.setAuthenticationInfo(authInfo); 222 223 // Check JMX_READ privilege. 224 if (! jmxClientConnection.hasPrivilege(Privilege.JMX_READ, null)) 225 { 226 LocalizableMessage message = ERR_JMX_INSUFFICIENT_PRIVILEGES.get(); 227 228 jmxClientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED, 229 false, message); 230 231 throw new SecurityException(message.toString()); 232 } 233 return jmxClientConnection; 234 } 235 else 236 { 237 // Set the initcause. 238 LDAPException ldapEx = new LDAPException( 239 LDAPResultCode.INVALID_CREDENTIALS, 240 CoreMessages.INFO_RESULT_INVALID_CREDENTIALS.get()); 241 SecurityException se = new SecurityException("return code: " + bindOp.getResultCode()); 242 se.initCause(ldapEx); 243 throw se; 244 } 245 } 246}