/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.core; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.locks.Lock; import org.opends.server.api.ClientConnection; import org.opends.server.api.SASLMechanismHandler; import org.opends.server.api.plugin.PostOperationPluginResult; import org.opends.server.api.plugin.PreOperationPluginResult; import org.opends.server.api.plugin.PreParsePluginResult; import org.opends.server.controls.AuthorizationIdentityResponseControl; import org.opends.server.controls.PasswordExpiredControl; import org.opends.server.controls.PasswordExpiringControl; import org.opends.server.controls.PasswordPolicyErrorType; import org.opends.server.controls.PasswordPolicyResponseControl; import org.opends.server.controls.PasswordPolicyWarningType; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.types.AccountStatusNotificationType; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.AuthenticationInfo; import org.opends.server.types.AuthenticationType; import org.opends.server.types.ByteString; import org.opends.server.types.Control; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.types.ResultCode; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.core.CoreConstants.*; import static org.opends.server.extensions.ExtensionsConstants.*; import static org.opends.server.loggers.Access.*; import static org.opends.server.loggers.Debug.*; import static org.opends.server.loggers.Error.*; import static org.opends.server.messages.CoreMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines an operation that may be used to authenticate a user to * the Directory Server. Note that for security restrictions, response messages * that may be returned to the client must be carefully cleaned to ensure that * they do not provide a malicious client with information that may be useful in * an attack. This does impact the debugability of the server, but that can * be addressed by calling the setAuthFailureReason method, which * can provide a reason for a failure in a form that will not be returned to the * client but may be written to a log file. */ public class BindOperation extends Operation { /** * The fully-qualified name of this class for debugging purposes. */ private static final String CLASS_NAME = "org.opends.server.core.BindOperation"; // The credentials used for SASL authentication. private ASN1OctetString saslCredentials; // The server SASL credentials provided to the client in the response. private ASN1OctetString serverSASLCredentials; // The authentication type used for this bind operation. private AuthenticationType authType; // Indicates whether the warning notification that should be sent to the user // would be the first warning. private boolean isFirstWarning; // Indicates whether the authentication should use a grace login if it is // successful. private boolean isGraceLogin; // Indicates whether the user's password must be changed before any other // operations will be allowed. private boolean mustChangePassword; // Indicates whether the client included the password policy control in the // bind request. private boolean pwPolicyControlRequested; // The raw, unprocessed bind DN as contained in the client request. private ByteString rawBindDN; // The password used for simple authentication. private ByteString simplePassword; // The bind DN used for this bind operation. private DN bindDN; // The DN of the user entry that is attempting to authenticate. private DN userEntryDN; // The DN of the user as whom a SASL authentication was attempted (regardless // of whether the authentication was successful) for the purpose of updating // password policy state information. private Entry saslAuthUserEntry; // The unique ID associated with the failure reason message. private int authFailureID; // The password policy warning value that should be included in the response // control. private int pwPolicyWarningValue; // The set of response controls for this bind operation. private List responseControls; // The time that processing started on this operation. private long processingStartTime; // The time that processing ended on this operation. private long processingStopTime; // The password policy error type that should be included in the response // control PasswordPolicyErrorType pwPolicyErrorType; // The password policy warning type that should be included in the response // control PasswordPolicyWarningType pwPolicyWarningType; // The password policy state information for this bind operation. private PasswordPolicyState pwPolicyState; // A message explaining the reason for the authentication failure. private String authFailureReason; // The SASL mechanism used for SASL authentication. private String saslMechanism; /** * Creates a new simple bind operation with the provided information. * * @param clientConnection The client connection with which this operation * is associated. * @param operationID The operation ID for this operation. * @param messageID The message ID of the request with which this * operation is associated. * @param requestControls The set of controls included in the request. * @param rawBindDN The raw, unprocessed bind DN as provided in the * request from the client. * @param simplePassword The password to use for the simple * authentication. */ public BindOperation(ClientConnection clientConnection, long operationID, int messageID, List requestControls, ByteString rawBindDN, ByteString simplePassword) { super(clientConnection, operationID, messageID, requestControls); assert debugConstructor(CLASS_NAME, new String[] { String.valueOf(clientConnection), String.valueOf(messageID), String.valueOf(requestControls), String.valueOf(rawBindDN), String.valueOf(simplePassword) }); this.authType = AuthenticationType.SIMPLE; this.rawBindDN = rawBindDN; this.simplePassword = simplePassword; this.saslMechanism = null; this.saslCredentials = null; bindDN = null; userEntryDN = null; responseControls = new ArrayList(0); authFailureID = 0; authFailureReason = null; saslAuthUserEntry = null; isFirstWarning = false; isGraceLogin = false; mustChangePassword = false; pwPolicyControlRequested = false; pwPolicyErrorType = null; pwPolicyWarningType = null; pwPolicyWarningValue = -1; } /** * Creates a new SASL bind operation with the provided information. * * @param clientConnection The client connection with which this operation * is associated. * @param operationID The operation ID for this operation. * @param messageID The message ID of the request with which this * operation is associated. * @param requestControls The set of controls included in the request. * @param rawBindDN The raw, unprocessed bind DN as provided in the * request from the client. * @param saslMechanism The SASL mechanism included in the request. * @param saslCredentials The optional SASL credentials included in the * request. */ public BindOperation(ClientConnection clientConnection, long operationID, int messageID, List requestControls, ByteString rawBindDN, String saslMechanism, ASN1OctetString saslCredentials) { super(clientConnection, operationID, messageID, requestControls); assert debugConstructor(CLASS_NAME, new String[] { String.valueOf(clientConnection), String.valueOf(operationID), String.valueOf(messageID), String.valueOf(requestControls), String.valueOf(rawBindDN), String.valueOf(saslMechanism), String.valueOf(saslCredentials) }); this.authType = AuthenticationType.SASL; this.rawBindDN = rawBindDN; this.saslMechanism = saslMechanism; this.saslCredentials = saslCredentials; this.simplePassword = null; bindDN = null; userEntryDN = null; responseControls = new ArrayList(0); authFailureID = 0; authFailureReason = null; saslAuthUserEntry = null; } /** * Creates a new simple bind operation with the provided information. * * @param clientConnection The client connection with which this operation * is associated. * @param operationID The operation ID for this operation. * @param messageID The message ID of the request with which this * operation is associated. * @param requestControls The set of controls included in the request. * @param bindDN The bind DN for this bind operation. * @param simplePassword The password to use for the simple * authentication. */ public BindOperation(ClientConnection clientConnection, long operationID, int messageID, List requestControls, DN bindDN, ByteString simplePassword) { super(clientConnection, operationID, messageID, requestControls); assert debugConstructor(CLASS_NAME, new String[] { String.valueOf(clientConnection), String.valueOf(messageID), String.valueOf(requestControls), String.valueOf(bindDN), String.valueOf(simplePassword) }); this.authType = AuthenticationType.SIMPLE; this.bindDN = bindDN; this.simplePassword = simplePassword; this.saslMechanism = null; this.saslCredentials = null; if (bindDN == null) { rawBindDN = new ASN1OctetString(); } else { rawBindDN = new ASN1OctetString(bindDN.toString()); } responseControls = new ArrayList(0); authFailureID = 0; authFailureReason = null; saslAuthUserEntry = null; isFirstWarning = false; isGraceLogin = false; mustChangePassword = false; pwPolicyControlRequested = false; pwPolicyErrorType = null; pwPolicyWarningType = null; pwPolicyWarningValue = -1; userEntryDN = null; } /** * Creates a new SASL bind operation with the provided information. * * @param clientConnection The client connection with which this operation * is associated. * @param operationID The operation ID for this operation. * @param messageID The message ID of the request with which this * operation is associated. * @param requestControls The set of controls included in the request. * @param bindDN The bind DN for this bind operation. * @param saslMechanism The SASL mechanism included in the request. * @param saslCredentials The optional SASL credentials included in the * request. */ public BindOperation(ClientConnection clientConnection, long operationID, int messageID, List requestControls, DN bindDN, String saslMechanism, ASN1OctetString saslCredentials) { super(clientConnection, operationID, messageID, requestControls); assert debugConstructor(CLASS_NAME, new String[] { String.valueOf(clientConnection), String.valueOf(operationID), String.valueOf(messageID), String.valueOf(requestControls), String.valueOf(bindDN), String.valueOf(saslMechanism), String.valueOf(saslCredentials) }); this.authType = AuthenticationType.SASL; this.bindDN = bindDN; this.saslMechanism = saslMechanism; this.saslCredentials = saslCredentials; this.simplePassword = null; if (bindDN == null) { rawBindDN = new ASN1OctetString(); } else { rawBindDN = new ASN1OctetString(bindDN.toString()); } responseControls = new ArrayList(0); authFailureID = 0; authFailureReason = null; saslAuthUserEntry = null; userEntryDN = null; } /** * Retrieves the authentication type for this bind operation. * * @return The authentication type for this bind operation. */ public AuthenticationType getAuthenticationType() { assert debugEnter(CLASS_NAME, "getAuthenticationType"); return authType; } /** * Specifies the authentication type for this bind operation. * * @param authType The authentication type for this bind operation. */ public void setAuthenticationType(AuthenticationType authType) { assert debugEnter(CLASS_NAME, "setAuthenticationType", String.valueOf(authType)); this.authType = authType; } /** * Retrieves the raw, unprocessed bind DN for this bind operation as contained * in the client request. The value may not actually contain a valid DN, as * no validation will have been performed. * * @return The raw, unprocessed bind DN for this bind operation as contained * in the client request. */ public ByteString getRawBindDN() { assert debugEnter(CLASS_NAME, "getRawBindDN"); return rawBindDN; } /** * Specifies the raw, unprocessed bind DN for this bind operation. This * should only be called by pre-parse plugins; all other code that wishes to * alter the bind DN should use the getBindDN and * setBindDN methods. * * @param rawBindDN The raw, unprocessed bind DN for this bind operation. */ public void setRawBindDN(ByteString rawBindDN) { assert debugEnter(CLASS_NAME, "setRawBindDN", String.valueOf(rawBindDN)); this.rawBindDN = rawBindDN; bindDN = null; } /** * Retrieves the bind DN for this bind operation. This method should not be * called by pre-parse plugins, as the raw value will not have been processed * by that time. Instead, pre-parse plugins should call the * getRawBindDN method. * * @return The bind DN for this bind operation, or null if the * raw DN has not yet been processed. */ public DN getBindDN() { assert debugEnter(CLASS_NAME, "getBindDN"); return bindDN; } /** * Specifies the bind DN for this bind operation. This method should not be * called by pre-parse plugins, which should use setRawBindDN * instead. * * @param bindDN The bind DN for this bind operation. */ public void setBindDN(DN bindDN) { assert debugEnter(CLASS_NAME, "setBindDN", String.valueOf(bindDN)); this.bindDN = bindDN; } /** * Retrieves the simple authentication password for this bind operation. * * @return The simple authentication password for this bind operation. */ public ByteString getSimplePassword() { assert debugEnter(CLASS_NAME, "getSimplePassword"); return simplePassword; } /** * Specifies the simple authentication password for this bind operation. * * @param simplePassword The simple authentication password for this bind * operation. */ public void setSimplePassword(ByteString simplePassword) { assert debugEnter(CLASS_NAME, "setSimplePassword", String.valueOf(simplePassword)); this.simplePassword = simplePassword; } /** * Retrieves the SASL mechanism for this bind operation. * * @return The SASL mechanism for this bind operation. */ public String getSASLMechanism() { assert debugEnter(CLASS_NAME, "getSASLMechanism"); return saslMechanism; } /** * Specifies the SASL mechanism for this bind operation. * * @param saslMechanism The SASL mechanism for this bind operation. */ public void setSASLMechanism(String saslMechanism) { assert debugEnter(CLASS_NAME, "setSASLMechanism", String.valueOf(saslMechanism)); this.saslMechanism = saslMechanism; } /** * Retrieves the SASL credentials for this bind operation. * * @return The SASL credentials for this bind operation. */ public ASN1OctetString getSASLCredentials() { assert debugEnter(CLASS_NAME, "getSASLCredentials"); return saslCredentials; } /** * Specifies the SASL credentials for this bind operation. * * @param saslCredentials The SASL credentials for this bind operation. */ public void setSASLCredentials(ASN1OctetString saslCredentials) { assert debugEnter(CLASS_NAME, "setSASLCredentials", String.valueOf(saslCredentials)); this.saslCredentials = saslCredentials; } /** * Retrieves the set of server SASL credentials to include in the bind * response. * * @return The set of server SASL credentials to include in the bind * response, or null if there are none. */ public ASN1OctetString getServerSASLCredentials() { assert debugEnter(CLASS_NAME, "getServerSASLCredentials"); return serverSASLCredentials; } /** * Specifies the set of server SASL credentials to include in the bind * response. * * @param serverSASLCredentials The set of server SASL credentials to * include in the bind response. */ public void setServerSASLCredentials(ASN1OctetString serverSASLCredentials) { assert debugEnter(CLASS_NAME, "setServerSASLCredentials", String.valueOf(serverSASLCredentials)); this.serverSASLCredentials = serverSASLCredentials; } /** * Retrieves the user entry associated with the SASL authentication attempt. * This should be set by any SASL mechanism in which the processing was able * to get far enough to make this determination, regardless of whether the * authentication was ultimately successful. * * @return The user entry associated with the SASL authentication attempt, or * null if it was not a SASL authentication or the SASL * processing was not able to map the request to a user. */ public Entry getSASLAuthUserEntry() { assert debugEnter(CLASS_NAME, "getSASLAuthUserEntry"); return saslAuthUserEntry; } /** * Specifies the user entry associated with the SASL authentication attempt. * This should be set by any SASL mechanism in which the processing was able * to get far enough to make this determination, regardless of whether the * authentication was ultimately successful. * * @param saslAuthUserEntry The user entry associated with the SASL * authentication attempt. */ public void setSASLAuthUserEntry(Entry saslAuthUserEntry) { assert debugEnter(CLASS_NAME, "setSASLAuthUserEntry", String.valueOf(saslAuthUserEntry)); this.saslAuthUserEntry = saslAuthUserEntry; } /** * Retrieves a human-readable message providing the reason that the * authentication failed, if available. * * @return A human-readable message providing the reason that the * authentication failed, or null if none is available. */ public String getAuthFailureReason() { assert debugEnter(CLASS_NAME, "getAuthFailureReason"); return authFailureReason; } /** * Retrieves the unique identifier for the authentication failure reason, if * available. * * @return The unique identifier for the authentication failure reason, or * zero if none is available. */ public int getAuthFailureID() { assert debugEnter(CLASS_NAME, "getAuthFailureID"); return authFailureID; } /** * Specifies the reason that the authentication failed. * * @param id The unique identifier for the authentication failure * reason. * @param reason A human-readable message providing the reason that the * authentication failed. */ public void setAuthFailureReason(int id, String reason) { assert debugEnter(CLASS_NAME, "setAuthFailureReason", String.valueOf(id), String.valueOf(reason)); if (id < 0) { authFailureID = 0; } else { authFailureID = id; } authFailureReason = reason; } /** * Retrieves the user entry DN for this bind operation. It will only be * available if the bind processing has proceeded far enough to identify the * user attempting to authenticate or if the user DN could not be determined. * * @return The user entry DN for this bind operation, or null if * the bind processing has not progressed far enough to identify the * user or if the user DN could not be determined. */ public DN getUserEntryDN() { assert debugEnter(CLASS_NAME, "getUserEntryDN"); return userEntryDN; } /** * Retrieves the time that processing started for this operation. * * @return The time that processing started for this operation. */ public long getProcessingStartTime() { assert debugEnter(CLASS_NAME, "getProcessingStartTime"); return processingStartTime; } /** * Retrieves the time that processing stopped for this operation. This will * actually hold a time immediately before the response was sent to the * client. * * @return The time that processing stopped for this operation. */ public long getProcessingStopTime() { assert debugEnter(CLASS_NAME, "getProcessingStopTime"); return processingStopTime; } /** * Retrieves the length of time in milliseconds that the server spent * processing this operation. This should not be called until after the * server has sent the response to the client. * * @return The length of time in milliseconds that the server spent * processing this operation. */ public long getProcessingTime() { assert debugEnter(CLASS_NAME, "getProcessingTime"); return (processingStopTime - processingStartTime); } /** * Retrieves the operation type for this operation. * * @return The operation type for this operation. */ public OperationType getOperationType() { // Note that no debugging will be done in this method because it is a likely // candidate for being called by the logging subsystem. return OperationType.BIND; } /** * Retrieves a standard set of elements that should be logged in requests for * this type of operation. Each element in the array will itself be a * two-element array in which the first element is the name of the field and * the second is a string representation of the value, or null if * there is no value for that field. * * @return A standard set of elements that should be logged in requests for * this type of operation. */ public String[][] getRequestLogElements() { // Note that no debugging will be done in this method because it is a likely // candidate for being called by the logging subsystem. if (authType == AuthenticationType.SASL) { return new String[][] { new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) }, new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() }, new String[] { LOG_ELEMENT_SASL_MECHANISM, saslMechanism } }; } else { return new String[][] { new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) }, new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() } }; } } /** * Retrieves a standard set of elements that should be logged in responses for * this type of operation. Each element in the array will itself be a * two-element array in which the first element is the name of the field and * the second is a string representation of the value, or null if * there is no value for that field. * * @return A standard set of elements that should be logged in responses for * this type of operation. */ public String[][] getResponseLogElements() { // Note that no debugging will be done in this method because it is a likely // candidate for being called by the logging subsystem. String resultCode = String.valueOf(getResultCode().getIntValue()); String errorMessage; StringBuilder errorMessageBuffer = getErrorMessage(); if (errorMessageBuffer == null) { errorMessage = null; } else { errorMessage = errorMessageBuffer.toString(); } String matchedDNStr; DN matchedDN = getMatchedDN(); if (matchedDN == null) { matchedDNStr = null; } else { matchedDNStr = matchedDN.toString(); } String referrals; List referralURLs = getReferralURLs(); if ((referralURLs == null) || referralURLs.isEmpty()) { referrals = null; } else { StringBuilder buffer = new StringBuilder(); Iterator iterator = referralURLs.iterator(); buffer.append(iterator.next()); while (iterator.hasNext()) { buffer.append(", "); buffer.append(iterator.next()); } referrals = buffer.toString(); } String processingTime = String.valueOf(processingStopTime - processingStartTime); return new String[][] { new String[] { LOG_ELEMENT_RESULT_CODE, resultCode }, new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage }, new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr }, new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals }, new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime } }; } /** * Retrieves the set of controls to include in the response to the client. * Note that the contents of this list should not be altered after * post-operation plugins have been called. * * @return The set of controls to include in the response to the client. */ public List getResponseControls() { assert debugEnter(CLASS_NAME, "getResponseControls"); return responseControls; } /** * Performs the work of actually processing this operation. This should * include all processing for the operation, including invoking plugins, * logging messages, performing access control, managing synchronization, and * any other work that might need to be done in the course of processing. */ public void run() { assert debugEnter(CLASS_NAME, "run"); // Start the processing timer and initially set the result to indicate that // the result is unknown. processingStartTime = System.currentTimeMillis(); setResultCode(ResultCode.UNDEFINED); boolean returnAuthzID = false; int sizeLimit = DirectoryServer.getSizeLimit(); int timeLimit = DirectoryServer.getTimeLimit(); int lookthroughLimit = DirectoryServer.getLookthroughLimit(); // Set a flag to indicate that a bind operation is in progress. This should // ensure that no new operations will be accepted for this client until the // bind is complete. clientConnection.setBindInProgress(true); // Wipe out any existing authentication for the client connection and create // a placeholder that will be used if the bind is successful. clientConnection.setUnauthenticated(); AuthenticationInfo authInfo = null; // Abandon any operations that may be in progress for the client. String cancelReason = getMessage(MSGID_CANCELED_BY_BIND_REQUEST); CancelRequest cancelRequest = new CancelRequest(true, cancelReason); clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID()); // Get the plugin config manager that will be used for invoking plugins. PluginConfigManager pluginConfigManager = DirectoryServer.getPluginConfigManager(); boolean skipPostOperation = false; // Create a labeled block of code that we can break out of if a problem is // detected. bindProcessing: { // Invoke the pre-parse bind plugins. PreParsePluginResult preParseResult = pluginConfigManager.invokePreParseBindPlugins(this); if (preParseResult.connectionTerminated()) { // There's no point in continuing with anything. Log the request and // result and return. setResultCode(ResultCode.CANCELED); int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT; appendErrorMessage(getMessage(msgID)); processingStopTime = System.currentTimeMillis(); logBindRequest(this); logBindResponse(this); return; } else if (preParseResult.sendResponseImmediately()) { skipPostOperation = true; logBindRequest(this); break bindProcessing; } // Log the bind request message. logBindRequest(this); // Process the bind DN to convert it from the raw form as provided by the // client into the form required for the rest of the bind processing. try { if (bindDN == null) { bindDN = DN.decode(rawBindDN); } } catch (DirectoryException de) { assert debugException(CLASS_NAME, "run", de); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(de.getErrorMessageID(), de.getErrorMessage()); break bindProcessing; } // Check to see if the client has permission to perform the // bind. // FIXME: for now assume that this will check all permission // pertinent to the operation. This includes any controls // specified. if (AccessControlConfigManager.getInstance() .getAccessControlHandler().isAllowed(this) == false) { setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); int msgID = MSGID_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS; appendErrorMessage(getMessage(msgID, String.valueOf(bindDN))); skipPostOperation = true; break bindProcessing; } // Check to see if there are any controls in the request. If so, then see // if there is any special processing required. List requestControls = getRequestControls(); if ((requestControls != null) && (! requestControls.isEmpty())) { for (int i=0; i < requestControls.size(); i++) { Control c = requestControls.get(i); String oid = c.getOID(); if (oid.equals(OID_AUTHZID_REQUEST)) { returnAuthzID = true; } else if (oid.equals(OID_PASSWORD_POLICY_CONTROL)) { pwPolicyControlRequested = true; } // NYI -- Add support for additional controls. else if (c.isCritical()) { setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); int msgID = MSGID_BIND_UNSUPPORTED_CRITICAL_CONTROL; appendErrorMessage(getMessage(msgID, String.valueOf(oid))); break bindProcessing; } } } // Check to see if this is a simple bind or a SASL bind and process // accordingly. switch (authType) { case SIMPLE: // See if this is an anonymous bind. If so, then determine whether // to allow it. if ((simplePassword == null) || (simplePassword.value().length == 0)) { // If there is a bind DN, then wee whether that is acceptable. if (DirectoryServer.bindWithDNRequiresPassword() && ((bindDN != null) && (! bindDN.isNullDN()))) { setResultCode(ResultCode.INVALID_CREDENTIALS); int msgID = MSGID_BIND_DN_BUT_NO_PASSWORD; String message = getMessage(msgID); setAuthFailureReason(msgID, message); break bindProcessing; } setResultCode(ResultCode.SUCCESS); authInfo = new AuthenticationInfo(); break bindProcessing; } // See if the bind DN is actually one of the alternate root DNs // defined in the server. If so, then replace it with the actual DN // for that user. DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN); if (actualRootDN != null) { bindDN = actualRootDN; } // Get the user entry based on the bind DN. If it does not exist, // then fail. Lock userLock = null; for (int i=0; i < 3; i++) { userLock = LockManager.lockRead(bindDN); if (userLock != null) { break; } } if (userLock == null) { int msgID = MSGID_BIND_OPERATION_CANNOT_LOCK_USER; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(DirectoryServer.getServerErrorResultCode()); setAuthFailureReason(msgID, message); break bindProcessing; } try { Entry userEntry; try { userEntry = DirectoryServer.getEntry(bindDN); } catch (DirectoryException de) { assert debugException(CLASS_NAME, "run", de); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(de.getErrorMessageID(), de.getErrorMessage()); userEntry = null; break bindProcessing; } if (userEntry == null) { int msgID = MSGID_BIND_OPERATION_UNKNOWN_USER; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); break bindProcessing; } else { userEntryDN = userEntry.getDN(); } // Check to see if the user has a password. If not, then fail. // FIXME -- We need to have a way to enable/disable debugging. pwPolicyState = new PasswordPolicyState(userEntry, false, false); AttributeType pwType = pwPolicyState.getPasswordAttribute(); List pwAttr = userEntry.getAttribute(pwType); if ((pwAttr == null) || (pwAttr.isEmpty())) { int msgID = MSGID_BIND_OPERATION_NO_PASSWORD; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); break bindProcessing; } // Check to see if the authentication must be done in a secure // manner. If so, then the client connection must be secure. if (pwPolicyState.requireSecureAuthentication() && (! clientConnection.isSecure())) { int msgID = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); break bindProcessing; } // Check to see if the user is administratively disabled or locked. if (pwPolicyState.isDisabled()) { int msgID = MSGID_BIND_OPERATION_ACCOUNT_DISABLED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); break bindProcessing; } else if (pwPolicyState.isAccountExpired()) { int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID, message); break bindProcessing; } else if (pwPolicyState.lockedDueToFailures()) { int msgID = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED; String message = getMessage(msgID, String.valueOf(bindDN)); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); break bindProcessing; } else if (pwPolicyState.lockedDueToMaximumResetAge()) { int msgID = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED; String message = getMessage(msgID, String.valueOf(bindDN)); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN, msgID, message); break bindProcessing; } else if (pwPolicyState.lockedDueToIdleInterval()) { int msgID = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED; String message = getMessage(msgID, String.valueOf(bindDN)); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN, msgID, message); break bindProcessing; } // Determine whether the password is expired, or whether the user // should be warned about an upcoming expiration. if (pwPolicyState.isPasswordExpired()) { if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; } int maxGraceLogins = pwPolicyState.getMaxAllowedGraceLogins(); if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin()) { List graceLoginTimes = pwPolicyState.getGraceLoginTimes(); if ((graceLoginTimes == null) || (graceLoginTimes.size() < maxGraceLogins)) { isGraceLogin = true; mustChangePassword = true; if (pwPolicyWarningType == null) { pwPolicyWarningType = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; pwPolicyWarningValue = maxGraceLogins - (graceLoginTimes.size() + 1); } } else { int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN, msgID, message); break bindProcessing; } } else { int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN, msgID, message); break bindProcessing; } } else if (pwPolicyState.shouldWarn()) { int numSeconds = pwPolicyState.getSecondsUntilExpiration(); String timeToExpiration = secondsToTimeString(numSeconds); int msgID = MSGID_BIND_PASSWORD_EXPIRING; String message = getMessage(msgID, timeToExpiration); appendErrorMessage(message); if (pwPolicyWarningType == null) { pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; pwPolicyWarningValue = numSeconds; } isFirstWarning = pwPolicyState.isFirstWarning(); } // Check to see if the user's password has been reset. if (pwPolicyState.mustChangePassword()) { mustChangePassword = true; if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; } } // Invoke the pre-operation bind plugins. PreOperationPluginResult preOpResult = pluginConfigManager.invokePreOperationBindPlugins(this); if (preOpResult.connectionTerminated()) { // There's no point in continuing with anything. Log the result // and return. setResultCode(ResultCode.CANCELED); int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT; appendErrorMessage(getMessage(msgID)); processingStopTime = System.currentTimeMillis(); logBindResponse(this); return; } else if (preOpResult.sendResponseImmediately()) { skipPostOperation = true; break bindProcessing; } // Determine whether the provided password matches any of the stored // passwords for the user. if (pwPolicyState.passwordMatches(simplePassword)) { setResultCode(ResultCode.SUCCESS); boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN()); authInfo = new AuthenticationInfo(userEntry.getDN(), simplePassword, isRoot); // See if the user's entry contains a custom size limit. AttributeType attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT, true); List attrList = userEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS; String message = getMessage(msgID, String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { sizeLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT; String message = getMessage(msgID, v.getStringValue(), String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } // See if the user's entry contains a custom time limit. attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT, true); attrList = userEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS; String message = getMessage(msgID, String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { timeLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT; String message = getMessage(msgID, v.getStringValue(), String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } // See if the user's entry contains a custom lookthrough limit. attrType = DirectoryServer.getAttributeType( OP_ATTR_USER_LOOKTHROUGH_LIMIT, true); attrList = userEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS; String message = getMessage(msgID, String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { lookthroughLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT; String message = getMessage(msgID, v.getStringValue(), String.valueOf(userEntry.getDN())); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } pwPolicyState.handleDeprecatedStorageSchemes(simplePassword); pwPolicyState.clearAuthFailureTimes(); if (isFirstWarning) { pwPolicyState.setWarnedTime(); int numSeconds = pwPolicyState.getSecondsUntilExpiration(); String timeToExpiration = secondsToTimeString(numSeconds); int msgID = MSGID_BIND_PASSWORD_EXPIRING; String message = getMessage(msgID, timeToExpiration); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN, msgID, message); } if (isGraceLogin) { pwPolicyState.updateGraceLoginTimes(); } pwPolicyState.setLastLoginTime(); } else { int msgID = MSGID_BIND_OPERATION_WRONG_PASSWORD; String message = getMessage(msgID); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); int maxAllowedFailures = pwPolicyState.getMaxAllowedFailures(); if (maxAllowedFailures > 0) { pwPolicyState.updateAuthFailureTimes(); if (pwPolicyState.getAuthFailureTimes().size() >= maxAllowedFailures) { pwPolicyState.lockDueToFailures(); AccountStatusNotificationType notificationType; int lockoutDuration = pwPolicyState.getLockoutDuration(); if (lockoutDuration > 0) { notificationType = AccountStatusNotificationType. ACCOUNT_TEMPORARILY_LOCKED; msgID = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED; message = getMessage(msgID, secondsToTimeString(lockoutDuration)); } else { notificationType = AccountStatusNotificationType. ACCOUNT_PERMANENTLY_LOCKED; msgID = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED; message = getMessage(msgID); } pwPolicyState.generateAccountStatusNotification( notificationType, userEntryDN, msgID, message); } } } } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION; String message = getMessage(msgID, stackTraceToSingleLineString(e)); setResultCode(DirectoryServer.getServerErrorResultCode()); setAuthFailureReason(msgID, message); break bindProcessing; } finally { // No matter what, make sure to unlock the user's entry. LockManager.unlock(bindDN, userLock); } break; case SASL: // Get the appropriate authentication handler for this request based // on the SASL mechanism. If there is none, then fail. SASLMechanismHandler saslHandler = DirectoryServer.getSASLMechanismHandler(saslMechanism); if (saslHandler == null) { setResultCode(ResultCode.AUTH_METHOD_NOT_SUPPORTED); int msgID = MSGID_BIND_OPERATION_UNKNOWN_SASL_MECHANISM; String message = getMessage(msgID, saslMechanism); appendErrorMessage(message); setAuthFailureReason(msgID, message); break bindProcessing; } // Check to see if the client has sufficient permission to perform the // bind. // NYI // Invoke the pre-operation bind plugins. PreOperationPluginResult preOpResult = pluginConfigManager.invokePreOperationBindPlugins(this); if (preOpResult.connectionTerminated()) { // There's no point in continuing with anything. Log the result // and return. setResultCode(ResultCode.CANCELED); int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT; appendErrorMessage(getMessage(msgID)); processingStopTime = System.currentTimeMillis(); logBindResponse(this); return; } else if (preOpResult.sendResponseImmediately()) { skipPostOperation = true; break bindProcessing; } // Actually process the SASL bind. saslHandler.processSASLBind(this); // Create the password policy state object. String userDNString; if (saslAuthUserEntry == null) { pwPolicyState = null; userDNString = null; } else { try { // FIXME -- Need to have a way to enable debugging. pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false, false); userEntryDN = saslAuthUserEntry.getDN(); userDNString = String.valueOf(userEntryDN); } catch (DirectoryException de) { assert debugException(CLASS_NAME, "run", de); setResponseData(de); break bindProcessing; } } // Perform password policy checks that will need to be completed // regardless of whether the authentication was successful. if (pwPolicyState != null) { if (pwPolicyState.isDisabled()) { setResultCode(ResultCode.INVALID_CREDENTIALS); int msgID = MSGID_BIND_OPERATION_ACCOUNT_DISABLED; appendErrorMessage(getMessage(msgID, userDNString)); break bindProcessing; } else if (pwPolicyState.isAccountExpired()) { setResultCode(ResultCode.INVALID_CREDENTIALS); int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED; String message = getMessage(msgID, userDNString); appendErrorMessage(message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID, message); break bindProcessing; } if (pwPolicyState.requireSecureAuthentication() && (! clientConnection.isSecure()) && (! saslHandler.isSecure(saslMechanism))) { setResultCode(ResultCode.INVALID_CREDENTIALS); int msgID = MSGID_BIND_OPERATION_INSECURE_SASL_BIND; appendErrorMessage(getMessage(msgID, saslMechanism, userDNString)); break bindProcessing; } if (pwPolicyState.lockedDueToFailures()) { setResultCode(ResultCode.INVALID_CREDENTIALS); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } int msgID = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED; appendErrorMessage(getMessage(msgID, userDNString)); break bindProcessing; } if (pwPolicyState.lockedDueToIdleInterval()) { setResultCode(ResultCode.INVALID_CREDENTIALS); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } int msgID = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED; String message = getMessage(msgID, userDNString); appendErrorMessage(message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN, msgID, message); break bindProcessing; } if (saslHandler.isPasswordBased(saslMechanism)) { if (pwPolicyState.lockedDueToMaximumResetAge()) { setResultCode(ResultCode.INVALID_CREDENTIALS); if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; } int msgID = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED; String message = getMessage(msgID, userDNString); appendErrorMessage(message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN, msgID, message); break bindProcessing; } if (pwPolicyState.isPasswordExpired()) { if (pwPolicyErrorType == null) { pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; } int maxGraceLogins = pwPolicyState.getMaxAllowedGraceLogins(); if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin()) { List graceLoginTimes = pwPolicyState.getGraceLoginTimes(); if ((graceLoginTimes == null) || (graceLoginTimes.size() < maxGraceLogins)) { isGraceLogin = true; mustChangePassword = true; if (pwPolicyWarningType == null) { pwPolicyWarningType = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; pwPolicyWarningValue = maxGraceLogins - (graceLoginTimes.size() + 1); } } else { int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN, msgID, message); break bindProcessing; } } else { int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED; String message = getMessage(msgID, String.valueOf(bindDN)); setResultCode(ResultCode.INVALID_CREDENTIALS); setAuthFailureReason(msgID, message); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN, msgID, message); break bindProcessing; } } else if (pwPolicyState.shouldWarn()) { int numSeconds = pwPolicyState.getSecondsUntilExpiration(); String timeToExpiration = secondsToTimeString(numSeconds); int msgID = MSGID_BIND_PASSWORD_EXPIRING; String message = getMessage(msgID, timeToExpiration); appendErrorMessage(message); if (pwPolicyWarningType == null) { pwPolicyWarningType = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; pwPolicyWarningValue = numSeconds; } isFirstWarning = pwPolicyState.isFirstWarning(); } } } // Determine whether the authentication was successful and perform // any remaining password policy processing accordingly. Also check // for a custom size/time limit. ResultCode resultCode = getResultCode(); if (resultCode == ResultCode.SUCCESS) { if (pwPolicyState != null) { if (saslHandler.isPasswordBased(saslMechanism) && pwPolicyState.mustChangePassword()) { mustChangePassword = true; } if (isFirstWarning) { pwPolicyState.setWarnedTime(); int numSeconds = pwPolicyState.getSecondsUntilExpiration(); String timeToExpiration = secondsToTimeString(numSeconds); int msgID = MSGID_BIND_PASSWORD_EXPIRING; String message = getMessage(msgID, timeToExpiration); pwPolicyState.generateAccountStatusNotification( AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN, msgID, message); } if (isGraceLogin) { pwPolicyState.updateGraceLoginTimes(); } pwPolicyState.setLastLoginTime(); // See if the user's entry contains a custom size limit. AttributeType attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT, true); List attrList = saslAuthUserEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS; String message = getMessage(msgID, userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { sizeLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT; String message = getMessage(msgID, v.getStringValue(), userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } // See if the user's entry contains a custom time limit. attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT, true); attrList = saslAuthUserEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS; String message = getMessage(msgID, userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { timeLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT; String message = getMessage(msgID, v.getStringValue(), userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } // See if the user's entry contains a custom lookthrough limit. attrType = DirectoryServer.getAttributeType( OP_ATTR_USER_LOOKTHROUGH_LIMIT, true); attrList = saslAuthUserEntry.getAttribute(attrType); if ((attrList != null) && (attrList.size() == 1)) { Attribute a = attrList.get(0); LinkedHashSet values = a.getValues(); Iterator iterator = values.iterator(); if (iterator.hasNext()) { AttributeValue v = iterator.next(); if (iterator.hasNext()) { int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS; String message = getMessage(msgID, userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } else { try { lookthroughLimit = Integer.parseInt(v.getStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "run", e); int msgID = MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT; String message = getMessage(msgID, v.getStringValue(), userDNString); logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } } } } else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) { // FIXME -- Is any special processing needed here? } else { if (pwPolicyState != null) { if (saslHandler.isPasswordBased(saslMechanism)) { int maxAllowedFailures = pwPolicyState.getMaxAllowedFailures(); if (maxAllowedFailures > 0) { pwPolicyState.updateAuthFailureTimes(); if (pwPolicyState.getAuthFailureTimes().size() >= maxAllowedFailures) { pwPolicyState.lockDueToFailures(); AccountStatusNotificationType notificationType; int msgID; String message; int lockoutDuration = pwPolicyState.getLockoutDuration(); if (lockoutDuration > 0) { notificationType = AccountStatusNotificationType. ACCOUNT_TEMPORARILY_LOCKED; msgID = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED; message = getMessage(msgID, secondsToTimeString(lockoutDuration)); } else { notificationType = AccountStatusNotificationType. ACCOUNT_PERMANENTLY_LOCKED; msgID = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED; message = getMessage(msgID); } pwPolicyState.generateAccountStatusNotification( notificationType, userEntryDN, msgID, message); } } } } } break; default: // Send a protocol error response to the client and disconnect. // NYI return; } } // Update the user's account with any password policy changes that may be // required. try { if (pwPolicyState != null) { pwPolicyState.updateUserEntry(); } } catch (DirectoryException de) { assert debugException(CLASS_NAME, "run", de); setResponseData(de); } // Invoke the post-operation bind plugins. if (! skipPostOperation) { PostOperationPluginResult postOpResult = pluginConfigManager.invokePostOperationBindPlugins(this); if (postOpResult.connectionTerminated()) { // There's no point in continuing with anything. Log the result // and return. setResultCode(ResultCode.CANCELED); int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT; appendErrorMessage(getMessage(msgID)); processingStopTime = System.currentTimeMillis(); logBindResponse(this); return; } } // Update the authentication information for the user. if ((getResultCode() == ResultCode.SUCCESS) && (authInfo != null)) { clientConnection.setAuthenticationInfo(authInfo); clientConnection.setSizeLimit(sizeLimit); clientConnection.setTimeLimit(timeLimit); clientConnection.setLookthroughLimit(lookthroughLimit); clientConnection.setMustChangePassword(mustChangePassword); if (returnAuthzID) { responseControls.add(new AuthorizationIdentityResponseControl( authInfo.getAuthorizationDN())); } } // See if we need to send a password policy control to the client. If so, // then add it to the response. if (getResultCode() == ResultCode.SUCCESS) { if (pwPolicyControlRequested) { PasswordPolicyResponseControl pwpControl = new PasswordPolicyResponseControl(pwPolicyWarningType, pwPolicyWarningValue, pwPolicyErrorType); responseControls.add(pwpControl); } else { if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) { responseControls.add(new PasswordExpiredControl()); } else if (pwPolicyWarningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) { responseControls.add(new PasswordExpiringControl( pwPolicyWarningValue)); } } } else { if (pwPolicyControlRequested) { PasswordPolicyResponseControl pwpControl = new PasswordPolicyResponseControl(pwPolicyWarningType, pwPolicyWarningValue, pwPolicyErrorType); responseControls.add(pwpControl); } else { if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) { responseControls.add(new PasswordExpiredControl()); } } } // Unset the "bind in progress" flag to allow other operations to be // processed. // FIXME -- Make sure this also gets unset at every possible point at which // the bind could fail and this method could return early. clientConnection.setBindInProgress(false); // Stop the processing timer. processingStopTime = System.currentTimeMillis(); // Send the bind response to the client. clientConnection.sendResponse(this); // Log the bind response. logBindResponse(this); // Invoke the post-response bind plugins. pluginConfigManager.invokePostResponseBindPlugins(this); } /** * Attempts to cancel this operation before processing has completed. Note * that a bind operation may not be canceled, so this should never do * anything. * * @param cancelRequest Information about the way in which the operation * should be canceled. * * @return A code providing information on the result of the cancellation. */ public CancelResult cancel(CancelRequest cancelRequest) { assert debugEnter(CLASS_NAME, "cancel", String.valueOf(cancelRequest)); cancelRequest.addResponseMessage(getMessage(MSGID_CANNOT_CANCEL_BIND)); return CancelResult.CANNOT_CANCEL; } /** * Retrieves the cancel request that has been issued for this operation, if * there is one. Note that a bind operation may not be canceled, so this will * always return null. * * @return The cancel request that has been issued for this operation, or * null if there has not been any request to cancel. */ public CancelRequest getCancelRequest() { assert debugEnter(CLASS_NAME, "getCancelRequest"); return null; } /** * Appends a string representation of this operation to the provided buffer. * * @param buffer The buffer into which a string representation of this * operation should be appended. */ public void toString(StringBuilder buffer) { assert debugEnter(CLASS_NAME, "toString", "java.lang.StringBuilder"); buffer.append("BindOperation(connID="); buffer.append(clientConnection.getConnectionID()); buffer.append(", opID="); buffer.append(operationID); buffer.append(", dn="); buffer.append(rawBindDN); buffer.append(", authType="); buffer.append(authType); buffer.append(")"); } }