/**
   * Tests the use of the StartTLS extended operation to communicate with the server in conjunction
   * with no authentication and using blind trust.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test
  public void testStartTLSNoAuthTrustAll() throws Exception {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      String[] argSearch = {
        "--noPropertiesFile",
        "-h",
        "127.0.0.1",
        "-p",
        String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D",
        "cn=directory manager",
        "-w",
        "password",
        "-q",
        "-X",
        "-b",
        "",
        "-s",
        "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(argSearch, false, null, System.err), 0);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * Tests whether the who am I? extended operation with an unauthenticated connection fails with
   * new setting of "ds-cfg-reject-unauthenticated-requests".
   *
   * @throws UnsupportedEncodingException If an unexpected problem occurs.
   * @throws IOException If an unexpected problem occurs.
   * @throws ClientException If an unexpected problem occurs.
   */
  @Test
  public void testUnauthWAINewCfg()
      throws UnsupportedEncodingException, IOException, ClientException {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
      LDAPReader reader = new LDAPReader(s);
      LDAPWriter writer = new LDAPWriter(s);
      AtomicInteger nextMessageID = new AtomicInteger(1);
      LDAPAuthenticationHandler authHandler =
          new LDAPAuthenticationHandler(reader, writer, "localhost", nextMessageID);
      ByteString authzID = null;
      try {
        authzID = authHandler.requestAuthorizationIdentity();
      } catch (LDAPException e) {
        assertNull(authzID);
      } finally {
        LDAPMessage unbindMessage =
            new LDAPMessage(nextMessageID.getAndIncrement(), new UnbindRequestProtocolOp());
        writer.writeMessage(unbindMessage);
        s.close();
      }
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * Tests whether the Who Am I? extended operation with an internal authenticated connection
   * succeeds with new setting of "ds-cfg-reject-unauthenticated-requests".
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test
  public void testAuthWAINewCfg() throws Exception {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      InternalClientConnection conn = InternalClientConnection.getRootConnection();
      ExtendedOperation extOp = conn.processExtendedOperation(OID_WHO_AM_I_REQUEST, null);
      assertEquals(extOp.getResultCode(), ResultCode.SUCCESS);
      assertNotNull(extOp.getResponseValue());
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * Tests whether the Who Am I? extended operation with an internal authenticated connection
   * succeeds with default setting of "ds-cfg-reject-unauthenticated-requests".
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testAuthWAIDefCfg() throws Exception {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
    LDAPReader reader = new LDAPReader(s);
    LDAPWriter writer = new LDAPWriter(s);

    AtomicInteger nextMessageID = new AtomicInteger(1);
    LDAPAuthenticationHandler authHandler =
        new LDAPAuthenticationHandler(reader, writer, "localhost", nextMessageID);
    authHandler.doSimpleBind(
        3,
        ByteString.valueOf("cn=Directory Manager"),
        ByteString.valueOf("password"),
        new ArrayList<Control>(),
        new ArrayList<Control>());
    ByteString authzID = authHandler.requestAuthorizationIdentity();
    assertNotNull(authzID);

    LDAPMessage unbindMessage =
        new LDAPMessage(nextMessageID.getAndIncrement(), new UnbindRequestProtocolOp());
    writer.writeMessage(unbindMessage);
    s.close();
  }
  /**
   * Process this search operation against a local backend.
   *
   * @param wfe The local backend work-flow element.
   * @throws CanceledOperationException if this operation should be cancelled
   */
  public void processLocalSearch(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException {
    this.backend = wfe.getBackend();
    this.clientConnection = getClientConnection();

    // Check for a request to cancel this operation.
    checkIfCanceled(false);

    try {
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processSearch(executePostOpPlugins);

      // Check for a request to cancel this operation.
      checkIfCanceled(false);

      // Invoke the post-operation search plugins.
      if (executePostOpPlugins.value) {
        PluginResult.PostOperation postOpResult =
            DirectoryServer.getPluginConfigManager().invokePostOperationSearchPlugins(this);
        if (!postOpResult.continueProcessing()) {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
        }
      }
    } finally {
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
  }
  /**
   * Tests whether an Unauthenticated BIND request will be allowed with the default configuration
   * settings for "ds-cfg-reject-unauthenticated-requests".
   */
  @Test()
  public void testUnauthBindDefCfg() {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    BindOperation bindOperation = conn.processSimpleBind(DN.nullDN(), null);
    assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the whether the unauthenticated ADD,MODIFY,COMPARE,MODRDN and DELETE requests fail with
   * the new configuration settings.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test
  public void testOtherOpsUnauthNewCfg() throws Exception {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      assertFalse(performAddOperation(false) == 0);

      assertFalse(performModifyOperation(false) == 0);

      assertFalse(performCompareOperation(false) == 0);

      assertFalse(performModRdnOperation(false) == 0);

      assertFalse(performDeleteOperation(false) == 0);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * Tests whether authenticated and unauthenticated BIND requests will be allowed with the new
   * configuration settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test
  public void testBindNewCfg() {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
      ByteString user = ByteString.valueOf("cn=Directory Manager");
      ByteString password = ByteString.valueOf("password");
      // Unauthenticated BIND request.
      BindOperation bindOperation = conn.processSimpleBind(DN.nullDN(), null);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
      // Authenticated BIND request.
      bindOperation = conn.processSimpleBind(user, password);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * Tests whether an authenticated BIND request will be allowed with the default configuration
   * settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test()
  public void testAuthBindDefCfg() {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    ByteString user = ByteString.valueOf("cn=Directory Manager");
    ByteString password = ByteString.valueOf("password");
    BindOperation bindOperation = conn.processSimpleBind(user, password);
    assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests whether both authenticated and unauthenticated SEARCH requests will be allowed with the
   * new configuration settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test
  public void testSearchNewCfg() {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      String[] args = {
        "--noPropertiesFile",
        "-h",
        "127.0.0.1",
        "-p",
        String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-b",
        "",
        "-s",
        "base",
        "(objectClass=*)"
      };

      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);

      String[] authArgs = {
        "--noPropertiesFile",
        "-h",
        "127.0.0.1",
        "-p",
        String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D",
        "cn=Directory Manager",
        "-w",
        "password",
        "-b",
        "",
        "-s",
        "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(authArgs, false, null, System.err), 0);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
  /**
   * 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());
      }
    }
  }
  /**
   * Performs the processing necessary for an anonymous simple bind.
   *
   * @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 processAnonymousSimpleBind() throws DirectoryException {
    // If the server is in lockdown mode, then fail.
    if (DirectoryServer.lockdownMode()) {
      throw new DirectoryException(
          ResultCode.INVALID_CREDENTIALS, ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
    }

    // If there is a bind DN, then see whether that is acceptable.
    if (DirectoryServer.bindWithDNRequiresPassword() && bindDN != null && !bindDN.isRootDN()) {
      throw new DirectoryException(
          ResultCode.UNWILLING_TO_PERFORM, ERR_BIND_DN_BUT_NO_PASSWORD.get());
    }

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

    setResultCode(ResultCode.SUCCESS);
    setAuthenticationInfo(new AuthenticationInfo());
    return true;
  }
  /**
   * Tests the whether the authenticated ADD,MODIFY,COMPARE,MODRDN and DELETE requests succeed with
   * the default configuration settings.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testOtherOpsAuthDefCfg() throws Exception {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    assertEquals(performAddOperation(true), 0);

    assertEquals(performModifyOperation(true), 0);

    assertEquals(performCompareOperation(true), 0);

    assertEquals(performModRdnOperation(true), 0);

    assertEquals(performDeleteOperation(true), 0);
  }
  /**
   * Tests whether an unauthenticated SEARCH request will be allowed with the default configuration
   * settings for "ds-cfg-reject-unauthenticated-requests".
   */
  @Test()
  public void testUnauthSearchDefCfg() {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    String[] args = {
      "--noPropertiesFile",
      "-h",
      "127.0.0.1",
      "-p",
      String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-b",
      "",
      "-s",
      "base",
      "(objectClass=*)"
    };

    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
 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;
 }
  /**
   * 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 final void run() {
    setResultCode(ResultCode.UNDEFINED);

    // Start the processing timer.
    setProcessingStartTime();

    // Log the abandon request message.
    logAbandonRequest(this);

    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager = DirectoryServer.getPluginConfigManager();

    // Create a labeled block of code that we can break out of if a problem is
    // detected.
    abandonProcessing:
    {
      // Invoke the pre-parse abandon plugins.
      PluginResult.PreParse preParseResult = pluginConfigManager.invokePreParseAbandonPlugins(this);
      if (!preParseResult.continueProcessing()) {
        setResultCode(preParseResult.getResultCode());
        appendErrorMessage(preParseResult.getErrorMessage());
        setMatchedDN(preParseResult.getMatchedDN());
        setReferralURLs(preParseResult.getReferralURLs());
        break abandonProcessing;
      }

      // Actually perform the abandon operation.  Make sure to set the result
      // code to reflect whether the abandon was successful and an error message
      // if it was not.  Even though there is no response, the result should
      // still be logged.
      //
      // Even though it is technically illegal to send a response for
      // operations that have been abandoned, it may be a good idea to do so
      // to ensure that the requestor isn't left hanging.  This will be a
      // configurable option in the server.
      boolean notifyRequestor = DirectoryServer.notifyAbandonedOperations();

      Message cancelReason = INFO_CANCELED_BY_ABANDON_REQUEST.get(messageID);

      CancelRequest _cancelRequest = new CancelRequest(notifyRequestor, cancelReason);

      CancelResult result = clientConnection.cancelOperation(idToAbandon, _cancelRequest);

      setResultCode(result.getResultCode());
      appendErrorMessage(result.getResponseMessage());

      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationAbandonPlugins(this);
      if (!postOpResult.continueProcessing()) {
        setResultCode(preParseResult.getResultCode());
        appendErrorMessage(preParseResult.getErrorMessage());
        setMatchedDN(preParseResult.getMatchedDN());
        setReferralURLs(preParseResult.getReferralURLs());
        break abandonProcessing;
      }
    }

    // Stop the processing timer.
    setProcessingStopTime();

    // Log the result of the abandon operation.
    logAbandonResult(this);
  }
  /**
   * Handles any controls contained in the request.
   *
   * @throws DirectoryException If there is a problem with any of the request controls.
   */
  private void handleRequestControls() throws DirectoryException {
    LocalBackendWorkflowElement.removeAllDisallowedControls(baseDN, this);

    List<Control> requestControls = getRequestControls();
    if (requestControls != null && !requestControls.isEmpty()) {
      for (Control c : requestControls) {
        String oid = c.getOID();

        if (OID_LDAP_ASSERTION.equals(oid)) {
          LDAPAssertionRequestControl assertControl =
              getRequestControl(LDAPAssertionRequestControl.DECODER);

          SearchFilter assertionFilter;
          try {
            assertionFilter = assertControl.getSearchFilter();
          } catch (DirectoryException de) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            throw new DirectoryException(
                de.getResultCode(),
                ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(de.getMessageObject()),
                de);
          }

          Entry entry;
          try {
            entry = DirectoryServer.getEntry(baseDN);
          } catch (DirectoryException de) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            throw new DirectoryException(
                de.getResultCode(),
                ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(de.getMessageObject()));
          }

          if (entry == null) {
            throw new DirectoryException(
                ResultCode.NO_SUCH_OBJECT, ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
          }

          // Check if the current user has permission to make
          // this determination.
          if (!AccessControlConfigManager.getInstance()
              .getAccessControlHandler()
              .isAllowed(this, entry, assertionFilter)) {
            throw new DirectoryException(
                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
          }

          try {
            if (!assertionFilter.matchesEntry(entry)) {
              throw new DirectoryException(
                  ResultCode.ASSERTION_FAILED, ERR_SEARCH_ASSERTION_FAILED.get());
            }
          } catch (DirectoryException de) {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED) {
              throw de;
            }

            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            throw new DirectoryException(
                de.getResultCode(),
                ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(de.getMessageObject()),
                de);
          }
        } else if (OID_PROXIED_AUTH_V1.equals(oid)) {
          // Log usage of legacy proxy authz V1 control.
          addAdditionalLogItem(
              AdditionalLogItem.keyOnly(getClass(), "obsoleteProxiedAuthzV1Control"));

          // The requester must have the PROXIED_AUTH privilege in order to be
          // able to use this control.
          if (!clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) {
            throw new DirectoryException(
                ResultCode.AUTHORIZATION_DENIED, ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }

          ProxiedAuthV1Control proxyControl = getRequestControl(ProxiedAuthV1Control.DECODER);

          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          setProxiedAuthorizationDN(getDN(authorizationEntry));
        } else if (OID_PROXIED_AUTH_V2.equals(oid)) {
          // The requester must have the PROXIED_AUTH privilege in order to be
          // able to use this control.
          if (!clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) {
            throw new DirectoryException(
                ResultCode.AUTHORIZATION_DENIED, ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }

          ProxiedAuthV2Control proxyControl = getRequestControl(ProxiedAuthV2Control.DECODER);

          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          setProxiedAuthorizationDN(getDN(authorizationEntry));
        } else if (OID_PERSISTENT_SEARCH.equals(oid)) {
          final PersistentSearchControl ctrl = getRequestControl(PersistentSearchControl.DECODER);

          persistentSearch =
              new PersistentSearch(
                  this, ctrl.getChangeTypes(), ctrl.getChangesOnly(), ctrl.getReturnECs());
        } else if (OID_LDAP_SUBENTRIES.equals(oid)) {
          SubentriesControl subentriesControl = getRequestControl(SubentriesControl.DECODER);
          setReturnSubentriesOnly(subentriesControl.getVisibility());
        } else if (OID_LDUP_SUBENTRIES.equals(oid)) {
          // Support for legacy draft-ietf-ldup-subentry.
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(), "obsoleteSubentryControl"));

          setReturnSubentriesOnly(true);
        } else if (OID_MATCHED_VALUES.equals(oid)) {
          MatchedValuesControl matchedValuesControl =
              getRequestControl(MatchedValuesControl.DECODER);
          setMatchedValuesControl(matchedValuesControl);
        } else if (OID_ACCOUNT_USABLE_CONTROL.equals(oid)) {
          setIncludeUsableControl(true);
        } else if (OID_REAL_ATTRS_ONLY.equals(oid)) {
          setRealAttributesOnly(true);
        } else if (OID_VIRTUAL_ATTRS_ONLY.equals(oid)) {
          setVirtualAttributesOnly(true);
        } else if (OID_GET_EFFECTIVE_RIGHTS.equals(oid)
            && DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS)) {
          // Do nothing here and let AciHandler deal with it.
        }
        // NYI -- Add support for additional controls.

        else if (c.isCritical() && !backendSupportsControl(oid)) {
          throw new DirectoryException(
              ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
              ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
        }
      }
    }
  }
  private void processSearch(BooleanHolder executePostOpPlugins) throws CanceledOperationException {
    // Process the search base and filter to convert them from their raw forms
    // as provided by the client to the forms required for the rest of the
    // search processing.
    baseDN = getBaseDN();
    filter = getFilter();

    if (baseDN == null || filter == null) {
      return;
    }

    // Check to see if there are any controls in the request. If so, then
    // see if there is any special processing required.
    try {
      handleRequestControls();
    } catch (DirectoryException de) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }

      setResponseData(de);
      return;
    }

    // Check to see if the client has permission to perform the
    // search.

    // FIXME: for now assume that this will check all permission
    // pertinent to the operation. This includes proxy authorization
    // and any other controls specified.
    try {
      if (!AccessControlConfigManager.getInstance().getAccessControlHandler().isAllowed(this)) {
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String.valueOf(baseDN)));
        return;
      }
    } catch (DirectoryException e) {
      setResultCode(e.getResultCode());
      appendErrorMessage(e.getMessageObject());
      return;
    }

    // Check for a request to cancel this operation.
    checkIfCanceled(false);

    // Invoke the pre-operation search plugins.
    executePostOpPlugins.value = true;
    PluginResult.PreOperation preOpResult =
        DirectoryServer.getPluginConfigManager().invokePreOperationSearchPlugins(this);
    if (!preOpResult.continueProcessing()) {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return;
    }

    // Check for a request to cancel this operation.
    checkIfCanceled(false);

    // Get the backend that should hold the search base. If there is none,
    // then fail.
    if (backend == null) {
      setResultCode(ResultCode.NO_SUCH_OBJECT);
      appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(String.valueOf(baseDN)));
      return;
    }

    // We'll set the result code to "success". If a problem occurs, then it
    // will be overwritten.
    setResultCode(ResultCode.SUCCESS);

    try {
      // If there's a persistent search, then register it with the server.
      boolean processSearchNow = true;
      if (persistentSearch != null) {
        // If we're only interested in changes, then we do not actually want
        // to process the search now.
        processSearchNow = !persistentSearch.isChangesOnly();

        // The Core server maintains the count of concurrent persistent searches
        // so that all the backends (Remote and Local) are aware of it. Verify
        // with the core if we have already reached the threshold.
        if (!DirectoryServer.allowNewPersistentSearch()) {
          setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
          appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
          return;
        }
        backend.registerPersistentSearch(persistentSearch);
        persistentSearch.enable();
      }

      if (processSearchNow) {
        // Process the search in the backend and all its subordinates.
        backend.search(this);
      }
    } catch (DirectoryException de) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
      }

      setResponseData(de);

      if (persistentSearch != null) {
        persistentSearch.cancel();
        setSendResponse(true);
      }

      return;
    } catch (CanceledOperationException coe) {
      if (persistentSearch != null) {
        persistentSearch.cancel();
        setSendResponse(true);
      }

      throw coe;
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      setResultCode(DirectoryServer.getServerErrorResultCode());
      appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(getExceptionMessage(e)));

      if (persistentSearch != null) {
        persistentSearch.cancel();
        setSendResponse(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;
  }
  /**
   * 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;
  }
  /**
   * Process this bind operation in a local backend.
   *
   * @param wfe The local backend work-flow element.
   */
  public void processLocalBind(LocalBackendWorkflowElement wfe) {
    this.backend = wfe.getBackend();

    // Initialize a number of variables for use during the bind processing.
    clientConnection = getClientConnection();
    returnAuthzID = false;
    executePostOpPlugins = false;
    sizeLimit = DirectoryServer.getSizeLimit();
    timeLimit = DirectoryServer.getTimeLimit();
    lookthroughLimit = DirectoryServer.getLookthroughLimit();
    idleTimeLimit = DirectoryServer.getIdleTimeLimit();
    bindDN = getBindDN();
    saslMechanism = getSASLMechanism();
    authPolicyState = null;
    pwPolicyErrorType = null;
    pwPolicyControlRequested = false;
    isGraceLogin = false;
    isFirstWarning = false;
    mustChangePassword = false;
    pwPolicyWarningType = null;
    pwPolicyWarningValue = -1;
    pluginConfigManager = DirectoryServer.getPluginConfigManager();

    processBind();

    // Update the user's account with any password policy changes that may be
    // required.
    try {
      if (authPolicyState != null) {
        authPolicyState.finalizeStateAfterBind();
      }
    } catch (DirectoryException de) {
      logger.traceException(de);

      setResponseData(de);
    }

    // Invoke the post-operation bind plugins.
    if (executePostOpPlugins) {
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationBindPlugins(this);
      if (!postOpResult.continueProcessing()) {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
      }
    }

    // Update the authentication information for the user.
    AuthenticationInfo authInfo = getAuthenticationInfo();
    if (getResultCode() == ResultCode.SUCCESS && authInfo != null) {
      clientConnection.setAuthenticationInfo(authInfo);
      clientConnection.setSizeLimit(sizeLimit);
      clientConnection.setTimeLimit(timeLimit);
      clientConnection.setIdleTimeLimit(idleTimeLimit);
      clientConnection.setLookthroughLimit(lookthroughLimit);
      clientConnection.setMustChangePassword(mustChangePassword);

      if (returnAuthzID) {
        addResponseControl(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);
        addResponseControl(pwpControl);
      } else {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
          addResponseControl(new PasswordExpiredControl());
        } else if (pwPolicyWarningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
          addResponseControl(new PasswordExpiringControl(pwPolicyWarningValue));
        } else if (mustChangePassword) {
          addResponseControl(new PasswordExpiredControl());
        }
      }
    } else {
      if (pwPolicyControlRequested) {
        PasswordPolicyResponseControl pwpControl =
            new PasswordPolicyResponseControl(
                pwPolicyWarningType, pwPolicyWarningValue, pwPolicyErrorType);
        addResponseControl(pwpControl);
      } else {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
          addResponseControl(new PasswordExpiredControl());
        }
      }
    }
  }