/**
   * Test entry.
   *
   * @throws Exception If the test failed unexpectedly.
   */
  @Test
  public void testEntryToAndFromDatabase() throws Exception {
    ensureServerIsUpAndRunning();

    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);

    try (final LDIFReader reader =
        new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) {
      Entry entryBefore, entryAfter;
      while ((entryBefore = reader.readEntry(false)) != null) {
        ByteString bytes =
            ID2Entry.entryToDatabase(entryBefore, new DataConfig(false, false, null));

        entryAfter =
            ID2Entry.entryFromDatabase(bytes, DirectoryServer.getDefaultCompressedSchema());

        // check DN and number of attributes
        assertEquals(entryBefore.getAttributes().size(), entryAfter.getAttributes().size());

        assertEquals(entryBefore.getName(), entryAfter.getName());

        // check the object classes were not changed
        for (String ocBefore : entryBefore.getObjectClasses().values()) {
          ObjectClass objectClass = DirectoryServer.getObjectClass(ocBefore.toLowerCase());
          if (objectClass == null) {
            objectClass = DirectoryServer.getDefaultObjectClass(ocBefore);
          }
          String ocAfter = entryAfter.getObjectClasses().get(objectClass);

          assertEquals(ocBefore, ocAfter);
        }

        // check the user attributes were not changed
        for (AttributeType attrType : entryBefore.getUserAttributes().keySet()) {
          List<Attribute> listBefore = entryBefore.getAttribute(attrType);
          List<Attribute> listAfter = entryAfter.getAttribute(attrType);
          assertThat(listBefore).hasSameSizeAs(listAfter);

          for (Attribute attrBefore : listBefore) {
            boolean found = false;

            for (Attribute attrAfter : listAfter) {
              if (attrAfter.optionsEqual(attrBefore.getOptions())) {
                // Found the corresponding attribute
                assertEquals(attrBefore, attrAfter);
                found = true;
              }
            }

            assertTrue(found);
          }
        }
      }
    }
  }
  /**
   * Encodes this entry using the V3 encoding.
   *
   * @param buffer The buffer to encode into.
   * @throws DirectoryException If a problem occurs while attempting to encode the entry.
   */
  private void encodeV1(Entry entry, ByteStringBuilder buffer) throws DirectoryException {
    // The version number will be one byte.
    buffer.appendByte(0x01);

    // TODO: Can we encode the DN directly into buffer?
    byte[] dnBytes = getBytes(entry.getName().toString());
    buffer.appendBERLength(dnBytes.length);
    buffer.appendBytes(dnBytes);

    // Encode number of OCs and 0 terminated names.
    int i = 1;
    ByteStringBuilder bsb = new ByteStringBuilder();
    for (String ocName : entry.getObjectClasses().values()) {
      bsb.appendUtf8(ocName);
      if (i < entry.getObjectClasses().values().size()) {
        bsb.appendByte(0x00);
      }
      i++;
    }
    buffer.appendBERLength(bsb.length());
    buffer.appendBytes(bsb);

    // Encode the user attributes in the appropriate manner.
    encodeV1Attributes(buffer, entry.getUserAttributes());

    // The operational attributes will be encoded in the same way as
    // the user attributes.
    encodeV1Attributes(buffer, entry.getOperationalAttributes());
  }
  /**
   * Validates a number of password policy state constraints for the user. This will be called
   * before the offered credentials are checked.
   *
   * @param userEntry The entry for the user that is authenticating.
   * @param saslHandler The SASL mechanism handler if this is a SASL bind, or {@code null} for a
   *     simple bind.
   * @throws DirectoryException If a problem occurs that should cause the bind to fail.
   */
  protected void checkUnverifiedPasswordPolicyState(
      Entry userEntry, SASLMechanismHandler<?> saslHandler) throws DirectoryException {
    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();

    boolean isSASLBind = saslHandler != null;

    // If the password policy is configured to track authentication failures or
    // keep the last login time and the associated backend is disabled, then we
    // may need to reject the bind immediately.
    if ((policy.getStateUpdateFailurePolicy()
            == PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE)
        && ((policy.getLockoutFailureCount() > 0)
            || ((policy.getLastLoginTimeAttribute() != null)
                && (policy.getLastLoginTimeFormat() != null)))
        && ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED)
            || (backend.getWritabilityMode() == WritabilityMode.DISABLED))) {
      // This policy isn't applicable to root users, so if it's a root
      // user then ignore it.
      if (!DirectoryServer.isRootDN(userEntry.getName())) {
        throw new DirectoryException(
            ResultCode.INVALID_CREDENTIALS,
            ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(userEntry.getName()));
      }
    }

    // Check to see if the authentication must be done in a secure
    // manner.  If so, then the client connection must be secure.
    if (policy.isRequireSecureAuthentication() && !clientConnection.isSecure()) {
      if (isSASLBind) {
        if (!saslHandler.isSecure(saslMechanism)) {
          throw new DirectoryException(
              ResultCode.INVALID_CREDENTIALS,
              ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(saslMechanism, userEntry.getName()));
        }
      } else {
        throw new DirectoryException(
            ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get());
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public VirtualStaticGroup newInstance(ServerContext serverContext, Entry groupEntry)
      throws DirectoryException {
    ifNull(groupEntry);

    // Get the target group DN attribute from the entry, if there is one.
    DN targetDN = null;
    AttributeType targetType = DirectoryServer.getAttributeType(ATTR_TARGET_GROUP_DN, true);
    List<Attribute> attrList = groupEntry.getAttribute(targetType);
    if (attrList != null) {
      for (Attribute a : attrList) {
        for (ByteString v : a) {
          if (targetDN != null) {
            LocalizableMessage message =
                ERR_VIRTUAL_STATIC_GROUP_MULTIPLE_TARGETS.get(groupEntry.getName());
            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message);
          }

          try {
            targetDN = DN.decode(v);
          } catch (DirectoryException de) {
            logger.traceException(de);

            LocalizableMessage message =
                ERR_VIRTUAL_STATIC_GROUP_CANNOT_DECODE_TARGET.get(
                    v, groupEntry.getName(), de.getMessageObject());
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de);
          }
        }
      }
    }

    if (targetDN == null) {
      LocalizableMessage message = ERR_VIRTUAL_STATIC_GROUP_NO_TARGET.get(groupEntry.getName());
      throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message);
    }

    return new VirtualStaticGroup(groupEntry.getName(), targetDN);
  }
 private Integer getIntegerUserAttribute(
     Entry userEntry,
     String attributeTypeName,
     Arg1<Object> nonUniqueAttributeMessage,
     Arg2<Object, Object> cannotProcessAttributeMessage) {
   AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(attributeTypeName);
   List<Attribute> attrList = userEntry.getAttribute(attrType);
   if (attrList != null && attrList.size() == 1) {
     Attribute a = attrList.get(0);
     if (a.size() == 1) {
       ByteString v = a.iterator().next();
       try {
         return Integer.valueOf(v.toString());
       } catch (Exception e) {
         logger.traceException(e);
         logger.error(cannotProcessAttributeMessage.get(v, userEntry.getName()));
       }
     } else if (a.size() > 1) {
       logger.error(nonUniqueAttributeMessage.get(userEntry.getName()));
     }
   }
   return null;
 }
  /**
   * Encodes this entry using the V3 encoding.
   *
   * @param buffer The buffer to encode into.
   * @throws DirectoryException If a problem occurs while attempting to encode the entry.
   */
  private void encodeV2(Entry entry, ByteStringBuilder buffer, EntryEncodeConfig config)
      throws DirectoryException {
    // The version number will be one byte.
    buffer.appendByte(0x02);

    // Get the encoded respresentation of the config.
    config.encode(buffer);

    // If we should include the DN, then it will be encoded as a
    // one-to-five byte length followed by the UTF-8 byte
    // representation.
    if (!config.excludeDN()) {
      // TODO: Can we encode the DN directly into buffer?
      byte[] dnBytes = getBytes(entry.getName().toString());
      buffer.appendBERLength(dnBytes.length);
      buffer.appendBytes(dnBytes);
    }

    // Encode the object classes in the appropriate manner.
    if (config.compressObjectClassSets()) {
      config.getCompressedSchema().encodeObjectClasses(buffer, entry.getObjectClasses());
    } else {
      // Encode number of OCs and 0 terminated names.
      int i = 1;
      ByteStringBuilder bsb = new ByteStringBuilder();
      for (String ocName : entry.getObjectClasses().values()) {
        bsb.appendUtf8(ocName);
        if (i < entry.getObjectClasses().values().size()) {
          bsb.appendByte(0x00);
        }
        i++;
      }
      buffer.appendBERLength(bsb.length());
      buffer.appendBytes(bsb);
    }

    // Encode the user attributes in the appropriate manner.
    encodeV2Attributes(buffer, entry.getUserAttributes(), config);

    // The operational attributes will be encoded in the same way as
    // the user attributes.
    encodeV2Attributes(buffer, entry.getOperationalAttributes(), config);
  }
  /**
   * Tests the entry encoding and decoding process the version 1 encoding.
   *
   * @throws Exception If the test failed unexpectedly.
   */
  @Test(dataProvider = "encodeConfigs")
  public void testEntryToAndFromDatabaseV3(EntryEncodeConfig config) throws Exception {
    ensureServerIsUpAndRunning();

    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);

    try (final LDIFReader reader =
        new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) {
      Entry entryBefore, entryAfterV3;
      while ((entryBefore = reader.readEntry(false)) != null) {
        ByteStringBuilder bsb = new ByteStringBuilder();
        entryBefore.encode(bsb, config);
        entryAfterV3 = Entry.decode(bsb.asReader());
        if (config.excludeDN()) {
          entryAfterV3.setDN(entryBefore.getName());
        }
        assertEquals(entryBefore, entryAfterV3);
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public void processSASLBind(BindOperation bindOperation) {
    ExternalSASLMechanismHandlerCfg config = currentConfig;
    AttributeType certificateAttributeType = this.certificateAttributeType;
    CertificateValidationPolicy validationPolicy = this.validationPolicy;

    // Get the client connection used for the bind request, and get the
    // security manager for that connection.  If either are null, then fail.
    ClientConnection clientConnection = bindOperation.getClientConnection();
    if (clientConnection == null) {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CONNECTION.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }

    if (!(clientConnection instanceof LDAPClientConnection)) {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      LocalizableMessage message = ERR_SASLEXTERNAL_NOT_LDAP_CLIENT_INSTANCE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    LDAPClientConnection lc = (LDAPClientConnection) clientConnection;
    Certificate[] clientCertChain = lc.getClientCertificateChain();
    if ((clientCertChain == null) || (clientCertChain.length == 0)) {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CERT.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }

    // Get the certificate mapper to use to map the certificate to a user entry.
    DN certificateMapperDN = config.getCertificateMapperDN();
    CertificateMapper<?> certificateMapper =
        DirectoryServer.getCertificateMapper(certificateMapperDN);

    // Use the Directory Server certificate mapper to map the client certificate
    // chain to a single user DN.
    Entry userEntry;
    try {
      userEntry = certificateMapper.mapCertificateToUser(clientCertChain);
    } catch (DirectoryException de) {
      logger.traceException(de);

      bindOperation.setResponseData(de);
      return;
    }

    // If the user DN is null, then we couldn't establish a mapping and
    // therefore the authentication failed.
    if (userEntry == null) {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

      LocalizableMessage message = ERR_SASLEXTERNAL_NO_MAPPING.get();
      bindOperation.setAuthFailureReason(message);
      return;
    } else {
      bindOperation.setSASLAuthUserEntry(userEntry);
    }

    // Get the userCertificate attribute from the user's entry for use in the
    // validation process.
    List<Attribute> certAttrList = userEntry.getAttribute(certificateAttributeType);
    switch (validationPolicy) {
      case ALWAYS:
        if (certAttrList == null) {
          if (validationPolicy == CertificateValidationPolicy.ALWAYS) {
            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

            LocalizableMessage message = ERR_SASLEXTERNAL_NO_CERT_IN_ENTRY.get(userEntry.getName());
            bindOperation.setAuthFailureReason(message);
            return;
          }
        } else {
          try {
            ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded());
            if (!find(certAttrList, certBytes)) {
              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

              LocalizableMessage message =
                  ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName());
              bindOperation.setAuthFailureReason(message);
              return;
            }
          } catch (Exception e) {
            logger.traceException(e);

            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

            LocalizableMessage message =
                ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get(
                    userEntry.getName(), getExceptionMessage(e));
            bindOperation.setAuthFailureReason(message);
            return;
          }
        }
        break;

      case IFPRESENT:
        if (certAttrList != null) {
          try {
            ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded());
            if (!find(certAttrList, certBytes)) {
              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

              LocalizableMessage message =
                  ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName());
              bindOperation.setAuthFailureReason(message);
              return;
            }
          } catch (Exception e) {
            logger.traceException(e);

            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);

            LocalizableMessage message =
                ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get(
                    userEntry.getName(), getExceptionMessage(e));
            bindOperation.setAuthFailureReason(message);
            return;
          }
        }
    }

    AuthenticationInfo authInfo =
        new AuthenticationInfo(
            userEntry, SASL_MECHANISM_EXTERNAL, DirectoryServer.isRootDN(userEntry.getName()));
    bindOperation.setAuthenticationInfo(authInfo);
    bindOperation.setResultCode(ResultCode.SUCCESS);
  }
  /** {@inheritDoc} */
  @Override
  public LDIFImportResult importLDIF(
      LDIFImportConfig importConfig, RootContainer rootContainer, ServerContext serverContext)
      throws DirectoryException {
    try {
      ScheduledThreadPoolExecutor timerService = new ScheduledThreadPoolExecutor(1);
      try {
        final LDIFReader reader;
        try {
          reader = new LDIFReader(importConfig);
        } catch (Exception e) {
          LocalizableMessage m =
              ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_READER.get(stackTraceToSingleLineString(e));
          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, e);
        }

        long importCount = 0;
        final long startTime = System.currentTimeMillis();
        timerService.scheduleAtFixedRate(
            new ImportProgress(reader),
            IMPORT_PROGRESS_INTERVAL,
            IMPORT_PROGRESS_INTERVAL,
            TimeUnit.MILLISECONDS);
        while (true) {
          final Entry entry;
          try {
            entry = reader.readEntry();
            if (entry == null) {
              break;
            }
          } catch (LDIFException le) {
            if (!le.canContinueReading()) {
              LocalizableMessage m =
                  ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(stackTraceToSingleLineString(le));
              throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, le);
            }
            continue;
          }

          final DN dn = entry.getName();
          final EntryContainer ec = rootContainer.getEntryContainer(dn);
          if (ec == null) {
            final LocalizableMessage m = ERR_LDIF_SKIP.get(dn);
            logger.error(m);
            reader.rejectLastEntry(m);
            continue;
          }

          try {
            ec.addEntry(entry, null);
            importCount++;
          } catch (DirectoryException e) {
            switch (e.getResultCode().asEnum()) {
              case ENTRY_ALREADY_EXISTS:
                if (importConfig.replaceExistingEntries()) {
                  final Entry oldEntry = ec.getEntry(entry.getName());
                  ec.replaceEntry(oldEntry, entry, null);
                } else {
                  reader.rejectLastEntry(WARN_IMPORT_ENTRY_EXISTS.get());
                }
                break;
              case NO_SUCH_OBJECT:
                reader.rejectLastEntry(ERR_IMPORT_PARENT_NOT_FOUND.get(dn.parent()));
                break;
              default:
                // Not sure why it failed.
                reader.rejectLastEntry(e.getMessageObject());
                break;
            }
          }
        }
        final long finishTime = System.currentTimeMillis();

        waitForShutdown(timerService);

        final long importTime = finishTime - startTime;
        float rate = 0;
        if (importTime > 0) {
          rate = 1000f * reader.getEntriesRead() / importTime;
        }
        logger.info(
            NOTE_IMPORT_FINAL_STATUS,
            reader.getEntriesRead(),
            importCount,
            reader.getEntriesIgnored(),
            reader.getEntriesRejected(),
            0,
            importTime / 1000,
            rate);
        return new LDIFImportResult(
            reader.getEntriesRead(), reader.getEntriesRejected(), reader.getEntriesIgnored());
      } finally {
        rootContainer.close();

        // if not already stopped, then stop it
        waitForShutdown(timerService);
      }
    } catch (DirectoryException e) {
      logger.traceException(e);
      throw e;
    } catch (OpenDsException e) {
      logger.traceException(e);
      throw new DirectoryException(getServerErrorResultCode(), e.getMessageObject());
    } catch (Exception e) {
      logger.traceException(e);
      throw new DirectoryException(
          getServerErrorResultCode(), LocalizableMessage.raw(e.getMessage()));
    }
  }
  /**
   * Performs the processing necessary for a SASL bind operation.
   *
   * @return {@code true} if processing should continue for the operation, or {@code false} if not.
   * @throws DirectoryException If a problem occurs that should cause the bind operation to fail.
   */
  private boolean processSASLBind() throws DirectoryException {
    // 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) {
      throw new DirectoryException(
          ResultCode.AUTH_METHOD_NOT_SUPPORTED,
          ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(saslMechanism));
    }

    // Check to see if the client has sufficient permission to perform the bind.
    // NYI

    // Invoke pre-operation plugins.
    if (!invokePreOpPlugins()) {
      return false;
    }

    // Actually process the SASL bind.
    saslHandler.processSASLBind(this);

    // If the server is operating in lockdown mode, then we will need to
    // ensure that the authentication was successful and performed as a
    // root user to continue.
    Entry saslAuthUserEntry = getSASLAuthUserEntry();
    if (DirectoryServer.lockdownMode()) {
      ResultCode resultCode = getResultCode();
      if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS
          && (resultCode != ResultCode.SUCCESS
              || saslAuthUserEntry == null
              || !ClientConnection.hasPrivilege(saslAuthUserEntry, BYPASS_LOCKDOWN))) {
        throw new DirectoryException(
            ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
      }
    }

    // Create the password policy state object.
    if (saslAuthUserEntry != null) {
      setUserEntryDN(saslAuthUserEntry.getName());

      // FIXME -- Need to have a way to enable debugging.
      authPolicyState = AuthenticationPolicyState.forUser(saslAuthUserEntry, false);
      if (authPolicyState.isPasswordPolicy()) {
        // Account is managed locally: perform password policy checks that can
        // be completed before we have checked authentication was successful.
        checkUnverifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);
      }
    }

    // Determine whether the authentication was successful and perform
    // any remaining password policy processing accordingly.
    ResultCode resultCode = getResultCode();
    if (resultCode == ResultCode.SUCCESS) {
      if (authPolicyState != null && authPolicyState.isPasswordPolicy()) {
        checkVerifiedPasswordPolicyState(saslAuthUserEntry, saslHandler);

        PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;

        if (saslHandler.isPasswordBased(saslMechanism) && pwPolicyState.mustChangePassword()) {
          mustChangePassword = true;
        }

        if (isFirstWarning) {
          pwPolicyState.setWarnedTime();

          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
          LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(secondsToTimeString(numSeconds));

          pwPolicyState.generateAccountStatusNotification(
              AccountStatusNotificationType.PASSWORD_EXPIRING,
              saslAuthUserEntry,
              m,
              AccountStatusNotification.createProperties(
                  pwPolicyState, false, numSeconds, null, null));
        }

        if (isGraceLogin) {
          pwPolicyState.updateGraceLoginTimes();
        }

        pwPolicyState.setLastLoginTime();
      }

      // Set appropriate resource limits for the user (note that SASL ANONYMOUS
      // does not have a user).
      if (saslAuthUserEntry != null) {
        setResourceLimits(saslAuthUserEntry);
      }
    } else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) {
      // FIXME -- Is any special processing needed here?
      return false;
    } else {
      if (authPolicyState != null && authPolicyState.isPasswordPolicy()) {
        PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;

        if (saslHandler.isPasswordBased(saslMechanism)
            && pwPolicyState.getAuthenticationPolicy().getLockoutFailureCount() > 0) {
          generateAccountStatusNotificationForLockedBindAccount(saslAuthUserEntry, pwPolicyState);
        }
      }
    }

    return true;
  }
  /**
   * Performs the processing necessary for a simple bind operation.
   *
   * @return {@code true} if processing should continue for the operation, or {@code false} if not.
   * @throws DirectoryException If a problem occurs that should cause the bind operation to fail.
   */
  protected boolean processSimpleBind() throws DirectoryException {
    // See if this is an anonymous bind.  If so, then determine whether
    // to allow it.
    ByteString simplePassword = getSimplePassword();
    if (simplePassword == null || simplePassword.length() == 0) {
      return processAnonymousSimpleBind();
    }

    // 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;
    }

    Entry userEntry;
    try {
      userEntry = backend.getEntry(bindDN);
    } catch (DirectoryException de) {
      logger.traceException(de);

      userEntry = null;

      if (de.getResultCode() == ResultCode.REFERRAL) {
        // Re-throw referral exceptions - these should be passed back
        // to the client.
        throw de;
      } else {
        // Replace other exceptions in case they expose any sensitive
        // information.
        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, de.getMessageObject());
      }
    }

    if (userEntry == null) {
      throw new DirectoryException(
          ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_UNKNOWN_USER.get());
    } else {
      setUserEntryDN(userEntry.getName());
    }

    // Check to see if the user has a password. If not, then fail.
    // FIXME -- We need to have a way to enable/disable debugging.
    authPolicyState = AuthenticationPolicyState.forUser(userEntry, false);
    if (authPolicyState.isPasswordPolicy()) {
      // Account is managed locally.
      PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
      PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();

      AttributeType pwType = policy.getPasswordAttribute();
      List<Attribute> pwAttr = userEntry.getAttribute(pwType);
      if (pwAttr == null || pwAttr.isEmpty()) {
        throw new DirectoryException(
            ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_NO_PASSWORD.get());
      }

      // Perform a number of password policy state checks for the
      // non-authenticated user.
      checkUnverifiedPasswordPolicyState(userEntry, null);

      // Invoke pre-operation plugins.
      if (!invokePreOpPlugins()) {
        return false;
      }

      // Determine whether the provided password matches any of the stored
      // passwords for the user.
      if (pwPolicyState.passwordMatches(simplePassword)) {
        setResultCode(ResultCode.SUCCESS);

        checkVerifiedPasswordPolicyState(userEntry, null);

        if (DirectoryServer.lockdownMode()
            && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) {
          throw new DirectoryException(
              ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
        }
        setAuthenticationInfo(
            new AuthenticationInfo(
                userEntry, getBindDN(), DirectoryServer.isRootDN(userEntry.getName())));

        // Set resource limits for the authenticated user.
        setResourceLimits(userEntry);

        // Perform any remaining processing for a successful simple
        // authentication.
        pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
        pwPolicyState.clearFailureLockout();

        if (isFirstWarning) {
          pwPolicyState.setWarnedTime();

          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
          LocalizableMessage m = WARN_BIND_PASSWORD_EXPIRING.get(secondsToTimeString(numSeconds));

          pwPolicyState.generateAccountStatusNotification(
              AccountStatusNotificationType.PASSWORD_EXPIRING,
              userEntry,
              m,
              AccountStatusNotification.createProperties(
                  pwPolicyState, false, numSeconds, null, null));
        }

        if (isGraceLogin) {
          pwPolicyState.updateGraceLoginTimes();
        }

        pwPolicyState.setLastLoginTime();
      } else {
        setResultCode(ResultCode.INVALID_CREDENTIALS);
        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());

        if (policy.getLockoutFailureCount() > 0) {
          generateAccountStatusNotificationForLockedBindAccount(userEntry, pwPolicyState);
        }
      }
    } else {
      // Check to see if the user is administratively disabled or locked.
      if (authPolicyState.isDisabled()) {
        throw new DirectoryException(
            ResultCode.INVALID_CREDENTIALS, ERR_BIND_OPERATION_ACCOUNT_DISABLED.get());
      }

      // Invoke pre-operation plugins.
      if (!invokePreOpPlugins()) {
        return false;
      }

      if (authPolicyState.passwordMatches(simplePassword)) {
        setResultCode(ResultCode.SUCCESS);

        if (DirectoryServer.lockdownMode()
            && !ClientConnection.hasPrivilege(userEntry, BYPASS_LOCKDOWN)) {
          throw new DirectoryException(
              ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
        }
        setAuthenticationInfo(
            new AuthenticationInfo(
                userEntry, getBindDN(), DirectoryServer.isRootDN(userEntry.getName())));

        // Set resource limits for the authenticated user.
        setResourceLimits(userEntry);
      } else {
        setResultCode(ResultCode.INVALID_CREDENTIALS);
        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
      }
    }

    return true;
  }