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