/**
   * A utility method which may be used by implementations in order to obtain the value of the
   * specified attribute from the provided entry as a time in generalized time format.
   *
   * @param entry The entry whose attribute is to be parsed as a boolean.
   * @param attributeType The attribute type whose value should be parsed as a generalized time
   *     value.
   * @return The requested time, or -1 if it could not be determined.
   * @throws DirectoryException If a problem occurs while attempting to decode the value as a
   *     generalized time.
   */
  protected static final long getGeneralizedTime(
      final Entry entry, final AttributeType attributeType) throws DirectoryException {
    long timeValue = -1;

    final List<Attribute> attrList = entry.getAttribute(attributeType);
    if (attrList != null) {
      for (final Attribute a : attrList) {
        if (a.isEmpty()) {
          continue;
        }

        final AttributeValue v = a.iterator().next();
        try {
          timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(v.getNormalizedValue());
        } catch (final Exception e) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);

            TRACER.debugWarning(
                "Unable to decode value %s for attribute %s " + "in user entry %s: %s",
                v.getValue().toString(),
                attributeType.getNameOrOID(),
                entry.getDN().toString(),
                stackTraceToSingleLineString(e));
          }

          final Message message =
              ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.get(
                  v.getValue().toString(),
                  attributeType.getNameOrOID(),
                  entry.getDN().toString(),
                  String.valueOf(e));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e);
        }
        break;
      }
    }

    if (timeValue == -1) {
      if (debugEnabled()) {
        TRACER.debugInfo(
            "Returning -1 because attribute %s does not " + "exist in user entry %s",
            attributeType.getNameOrOID(), entry.getDN().toString());
      }
    }
    // FIXME: else to be consistent...

    return timeValue;
  }
  /**
   * 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());
      }
    }
  }
  /**
   * A utility method which may be used by implementations in order to obtain the value of the
   * specified attribute from the provided entry as a boolean.
   *
   * @param entry The entry whose attribute is to be parsed as a boolean.
   * @param attributeType The attribute type whose value should be parsed as a boolean.
   * @return The attribute's value represented as a ConditionResult value, or
   *     ConditionResult.UNDEFINED if the specified attribute does not exist in the entry.
   * @throws DirectoryException If the value cannot be decoded as a boolean.
   */
  protected static final ConditionResult getBoolean(
      final Entry entry, final AttributeType attributeType) throws DirectoryException {
    final List<Attribute> attrList = entry.getAttribute(attributeType);
    if (attrList != null) {
      for (final Attribute a : attrList) {
        if (a.isEmpty()) {
          continue;
        }

        final String valueString = toLowerCase(a.iterator().next().getValue().toString());

        if (valueString.equals("true")
            || valueString.equals("yes")
            || valueString.equals("on")
            || valueString.equals("1")) {
          if (debugEnabled()) {
            TRACER.debugInfo(
                "Attribute %s resolves to true for user entry " + "%s",
                attributeType.getNameOrOID(), entry.getDN().toString());
          }

          return ConditionResult.TRUE;
        }

        if (valueString.equals("false")
            || valueString.equals("no")
            || valueString.equals("off")
            || valueString.equals("0")) {
          if (debugEnabled()) {
            TRACER.debugInfo(
                "Attribute %s resolves to false for user " + "entry %s",
                attributeType.getNameOrOID(), entry.getDN().toString());
          }

          return ConditionResult.FALSE;
        }

        if (debugEnabled()) {
          TRACER.debugError(
              "Unable to resolve value %s for attribute %s " + "in user entry %s as a Boolean.",
              valueString, attributeType.getNameOrOID(), entry.getDN().toString());
        }

        final Message message =
            ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN.get(
                valueString, attributeType.getNameOrOID(), entry.getDN().toString());
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
    }

    if (debugEnabled()) {
      TRACER.debugInfo(
          "Returning %s because attribute %s does not exist " + "in user entry %s",
          ConditionResult.UNDEFINED.toString(),
          attributeType.getNameOrOID(),
          entry.getDN().toString());
    }

    return ConditionResult.UNDEFINED;
  }
  /**
   * Returns {@code true} if this authentication policy state is associated with a user whose
   * account has been administratively disabled.
   *
   * <p>The default implementation is use the value of the "ds-pwp-account-disable" attribute in the
   * user's entry.
   *
   * @return {@code true} if this authentication policy state is associated with a user whose
   *     account has been administratively disabled.
   */
  public boolean isDisabled() {
    final AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
    try {
      isDisabled = getBoolean(userEntry, type);
    } catch (final Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      isDisabled = ConditionResult.TRUE;
      if (debugEnabled()) {
        TRACER.debugWarning(
            "User %s is considered administratively "
                + "disabled because an error occurred while "
                + "attempting to make the determination: %s.",
            userEntry.getDN().toString(), stackTraceToSingleLineString(e));
      }

      return true;
    }

    if (isDisabled == ConditionResult.UNDEFINED) {
      isDisabled = ConditionResult.FALSE;
      if (debugEnabled()) {
        TRACER.debugInfo(
            "User %s is not administratively disabled since "
                + "the attribute \"%s\" is not present in the entry.",
            userEntry.getDN().toString(), OP_ATTR_ACCOUNT_DISABLED);
      }
      return false;
    }

    if (debugEnabled()) {
      TRACER.debugInfo(
          "User %s %s administratively disabled.",
          userEntry.getDN().toString(), ((isDisabled == ConditionResult.TRUE) ? " is" : " is not"));
    }

    return isDisabled == ConditionResult.TRUE;
  }
 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;
 }
 private DN getDN(Entry e) {
   return e != null ? e.getDN() : DN.nullDN();
 }
  /**
   * 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;
  }