ldap/xpcom/src/nsLDAPConnection.cpp
author Geoff Lankow <geoff@darktrojan.net>
Wed, 08 Apr 2020 10:52:17 +1200
changeset 38743 3f497398788daab69b15f64d49ea1d18cdafbccc
parent 38742 25b29578a1467e2ad16bc9348ef9709210c0294e
child 38992 e2f7a8209767d10cf9d1f8ffa05402704d0c60d1
permissions -rw-r--r--
Port bug 1625213 - Removal of onLookupByTypeComplete from nsIDNSListener. rs=bustage-fix

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsLDAPInternal.h"
#include "nsIServiceManager.h"
#include "nsString.h"
#include "nsIComponentManager.h"
#include "nsIDNSRecord.h"
#include "nsLDAPConnection.h"
#include "nsLDAPMessage.h"
#include "nsThreadUtils.h"
#include "nsIConsoleService.h"
#include "nsIDNSService.h"
#include "nsINetAddr.h"
#include "nsIRequestObserver.h"
#include "nsError.h"
#include "nsLDAPOperation.h"
#include "nsILDAPErrors.h"
#include "nsIClassInfoImpl.h"
#include "nsILDAPURL.h"
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "nsMemory.h"
#include "nsLDAPUtils.h"
#include "nsProxyRelease.h"
#include "mozilla/Attributes.h"
#include "nsNetUtil.h"

using namespace mozilla;

const char kDNSServiceContractId[] = "@mozilla.org/network/dns-service;1";

// constructor
//
nsLDAPConnection::nsLDAPConnection()
    : mConnectionHandle(nullptr),
      mPendingOperationsMutex("nsLDAPConnection.mPendingOperationsMutex"),
      mPendingOperations(10),
      mSSL(false),
      mVersion(nsILDAPConnection::VERSION3),
      mDNSRequest(nullptr) {}

// destructor
//
nsLDAPConnection::~nsLDAPConnection() {
  nsCOMPtr<nsIObserverService> obsServ =
      do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
  if (obsServ) obsServ->RemoveObserver(this, "profile-change-net-teardown");
  Close();
}

NS_IMPL_ADDREF(nsLDAPConnection)
NS_IMPL_RELEASE(nsLDAPConnection)
NS_IMPL_CLASSINFO(nsLDAPConnection, NULL, nsIClassInfo::THREADSAFE,
                  NS_LDAPCONNECTION_CID)

NS_INTERFACE_MAP_BEGIN(nsLDAPConnection)
  NS_INTERFACE_MAP_ENTRY(nsILDAPConnection)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsILDAPConnection)
  NS_IMPL_QUERY_CLASSINFO(nsLDAPConnection)
NS_INTERFACE_MAP_END
NS_IMPL_CI_INTERFACE_GETTER(nsLDAPConnection, nsILDAPConnection,
                            nsISupportsWeakReference, nsIDNSListener,
                            nsIObserver)

NS_IMETHODIMP
nsLDAPConnection::Init(nsILDAPURL *aUrl, const nsACString &aBindName,
                       nsILDAPMessageListener *aMessageListener,
                       nsISupports *aClosure, uint32_t aVersion) {
  NS_ENSURE_ARG_POINTER(aUrl);
  NS_ENSURE_ARG_POINTER(aMessageListener);

  nsresult rv;

  // Cache the STS thread we'll use to dispatch IO on.
  mSTS = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIObserverService> obsServ =
      do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  // We have to abort all LDAP pending operation before shutdown.
  obsServ->AddObserver(this, "profile-change-net-teardown", true);

  // Save various items that we'll use later
  mBindName.Assign(aBindName);
  mClosure = aClosure;
  mInitListener = aMessageListener;

  // Make sure we haven't called Init earlier, i.e. there's a DNS
  // request pending.
  NS_ASSERTION(!mDNSRequest,
               "nsLDAPConnection::Init() "
               "Connection was already initialized");

  // Check and save the version number
  if (aVersion != nsILDAPConnection::VERSION2 &&
      aVersion != nsILDAPConnection::VERSION3) {
    NS_ERROR("nsLDAPConnection::Init(): illegal version");
    return NS_ERROR_ILLEGAL_VALUE;
  }
  mVersion = aVersion;

  // Get the port number, SSL flag for use later, once the DNS server(s)
  // has resolved the host part.
  rv = aUrl->GetPort(&mPort);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t options;
  rv = aUrl->GetOptions(&options);
  NS_ENSURE_SUCCESS(rv, rv);

  mSSL = options & nsILDAPURL::OPT_SECURE;

  nsCOMPtr<nsIThread> curThread = do_GetCurrentThread();
  if (!curThread) {
    NS_ERROR("nsLDAPConnection::Init(): couldn't get current thread");
    return NS_ERROR_FAILURE;
  }

  // Do the pre-resolve of the hostname, using the DNS service. This
  // will also initialize the LDAP connection properly, once we have
  // the IPs resolved for the hostname. This includes creating the
  // new thread for this connection.
  //
  // XXX - What return codes can we expect from the DNS service?
  //
  nsCOMPtr<nsIDNSService> pDNSService(
      do_GetService(kDNSServiceContractId, &rv));

  if (NS_FAILED(rv)) {
    NS_ERROR(
        "nsLDAPConnection::Init(): couldn't create the DNS Service object");
    return NS_ERROR_FAILURE;
  }

  rv = aUrl->GetAsciiHost(mDNSHost);
  NS_ENSURE_SUCCESS(rv, rv);

  // if the caller has passed in a space-delimited set of hosts, as the
  // ldap c-sdk allows, strip off the trailing hosts for now.
  // Soon, we'd like to make multiple hosts work, but now make
  // at least the first one work.
  LdapCompressWhitespace(mDNSHost);

  int32_t spacePos = mDNSHost.FindChar(' ');
  // trim off trailing host(s)
  if (spacePos != kNotFound) mDNSHost.SetLength(spacePos);

  mozilla::OriginAttributes attrs;
  rv = pDNSService->AsyncResolveNative(mDNSHost, 0, this, curThread, attrs,
                                       getter_AddRefs(mDNSRequest));

  if (NS_FAILED(rv)) {
    switch (rv) {
      case NS_ERROR_OUT_OF_MEMORY:
      case NS_ERROR_UNKNOWN_HOST:
      case NS_ERROR_FAILURE:
      case NS_ERROR_OFFLINE:
        break;

      default:
        rv = NS_ERROR_UNEXPECTED;
    }
    mDNSHost.Truncate();
  }
  return rv;
}

// this might get exposed to clients, so we've broken it
// out of the destructor.
void nsLDAPConnection::Close() {
  int rc;
  MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug, ("unbinding"));

  if (mConnectionHandle) {
    // note that the ldap_unbind() call in the 5.0 version of the LDAP C SDK
    // appears to be exactly identical to ldap_unbind_s(), so it may in fact
    // still be synchronous
    //
    rc = ldap_unbind(mConnectionHandle);
    if (rc != LDAP_SUCCESS) {
      MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Warning,
              ("nsLDAPConnection::Close(): %s", ldap_err2string(rc)));
    }
    mConnectionHandle = nullptr;
  }

  MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug, ("unbound"));

  // Cancel the DNS lookup if needed, and also drop the reference to the
  // Init listener (if still there).
  //
  if (mDNSRequest) {
    mDNSRequest->Cancel(NS_ERROR_ABORT);
    mDNSRequest = nullptr;
  }
  mInitListener = nullptr;
}

NS_IMETHODIMP
nsLDAPConnection::Observe(nsISupports *aSubject, const char *aTopic,
                          const char16_t *aData) {
  if (!strcmp(aTopic, "profile-change-net-teardown")) {
    // Abort all ldap requests.
    /* We cannot use enumerate function to abort operations because
     * nsILDAPOperation::AbandonExt() is modifying list of operations
     * and this leads to starvation.
     * We have to do a copy of pending operations.
     */
    nsTArray<nsILDAPOperation *> pending_operations;
    {
      MutexAutoLock lock(mPendingOperationsMutex);
      for (auto iter = mPendingOperations.Iter(); !iter.Done(); iter.Next()) {
        pending_operations.AppendElement(iter.UserData());
      }
    }
    for (uint32_t i = 0; i < pending_operations.Length(); i++) {
      pending_operations[i]->AbandonExt();
    }
    Close();
  } else {
    MOZ_ASSERT_UNREACHABLE("unexpected topic");
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPConnection::GetClosure(nsISupports **_retval) {
  if (!_retval) {
    return NS_ERROR_ILLEGAL_VALUE;
  }
  NS_IF_ADDREF(*_retval = mClosure);
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPConnection::SetClosure(nsISupports *aClosure) {
  mClosure = aClosure;
  return NS_OK;
}

// who we're binding as
//
// readonly attribute AUTF8String bindName
//
NS_IMETHODIMP
nsLDAPConnection::GetBindName(nsACString &_retval) {
  _retval.Assign(mBindName);
  return NS_OK;
}

// wrapper for ldap_get_lderrno
// XXX should copy before returning
//
NS_IMETHODIMP
nsLDAPConnection::GetLdErrno(nsACString &matched, nsACString &errString,
                             int32_t *_retval) {
  char *match, *err;

  NS_ENSURE_ARG_POINTER(_retval);

  *_retval = ldap_get_lderrno(mConnectionHandle, &match, &err);
  matched.Assign(match);
  errString.Assign(err);
  return NS_OK;
}

// return the error string corresponding to GetLdErrno.
//
// XXX - deal with optional params
// XXX - how does ldap_perror know to look at the global errno?
//
NS_IMETHODIMP
nsLDAPConnection::GetErrorString(char16_t **_retval) {
  NS_ENSURE_ARG_POINTER(_retval);

  // get the error string
  //
  char *rv = ldap_err2string(ldap_get_lderrno(mConnectionHandle, 0, 0));
  if (!rv) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // make a copy using the XPCOM shared allocator
  //
  *_retval = ToNewUnicode(NS_ConvertUTF8toUTF16(rv));
  if (!*_retval) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

/**
 * Add an nsILDAPOperation to the list of operations pending on
 * this connection.  This is also mainly intended for use by the
 * nsLDAPOperation code.
 */
nsresult nsLDAPConnection::AddPendingOperation(uint32_t aOperationID,
                                               nsILDAPOperation *aOperation) {
  MOZ_ASSERT(aOperation != nullptr);

  nsIRunnable *runnable =
      new nsLDAPConnectionRunnable(aOperationID, aOperation, this);
  {
    MutexAutoLock lock(mPendingOperationsMutex);
    mPendingOperations.Put((uint32_t)aOperationID, aOperation);
    MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug,
            ("pending operation added; total pending operations now = %d",
             mPendingOperations.Count()));
  }

  nsresult rv;
  rv = mSTS->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
  if (rv != NS_OK) {
    RemovePendingOperation(aOperationID);
    // Tell the server we want the operation cancelled, and also
    // ditch any responses we've already received but not collected.
    (void)ldap_abandon_ext(mConnectionHandle, aOperationID, 0, 0);
    // At this point we should be letting the listener know that there's
    // an error, but listener doesn't have a suitable callback.
    // See Bug 1592449.
    // For now, just log it and leave it at that.
    MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Error,
            ("nsLDAPConnection::AddPendingOperation() failed, rv=%" PRIx32,
             static_cast<uint32_t>(rv)));
  }
  return rv;
}

nsresult nsLDAPConnection::StartOp(nsIRunnable *aOp) {
  return mSTS->Dispatch(aOp, nsIEventTarget::DISPATCH_NORMAL);
}

/**
 * Remove an nsILDAPOperation from the list of operations pending on this
 * connection.  Mainly intended for use by the nsLDAPOperation code.
 *
 * @param aOperation    operation to add
 * @exception NS_ERROR_INVALID_POINTER  aOperation was NULL
 * @exception NS_ERROR_OUT_OF_MEMORY    out of memory
 * @exception NS_ERROR_FAILURE      could not delete the operation
 *
 * void removePendingOperation(in nsILDAPOperation aOperation);
 */
nsresult nsLDAPConnection::RemovePendingOperation(uint32_t aOperationID) {
  NS_ENSURE_TRUE(aOperationID > 0, NS_ERROR_UNEXPECTED);

  MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug,
          ("nsLDAPConnection::RemovePendingOperation(): operation removed"));

  {
    MutexAutoLock lock(mPendingOperationsMutex);
    mPendingOperations.Remove(aOperationID);
    MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug,
            ("nsLDAPConnection::RemovePendingOperation(): operation "
             "removed; total pending operations now = %d",
             mPendingOperations.Count()));
  }

  return NS_OK;
}

class nsOnLDAPMessageRunnable : public Runnable {
 public:
  nsOnLDAPMessageRunnable(nsLDAPMessage *aMsg, bool aClear)
      : Runnable("nsOnLDAPMessageRunnable"), m_msg(aMsg), m_clear(aClear) {}
  NS_DECL_NSIRUNNABLE
 private:
  RefPtr<nsLDAPMessage> m_msg;
  bool m_clear;
};

NS_IMETHODIMP nsOnLDAPMessageRunnable::Run() {
  // get the message listener object.
  nsLDAPOperation *nsoperation =
      static_cast<nsLDAPOperation *>(m_msg->mOperation.get());
  nsCOMPtr<nsILDAPMessageListener> listener;
  nsresult rv = nsoperation->GetMessageListener(getter_AddRefs(listener));

  if (m_clear) {
    // try to break cycles
    nsoperation->Clear();
  }

  if (!listener) {
    NS_ERROR(
        "nsLDAPConnection::InvokeMessageCallback(): probable "
        "memory corruption: GetMessageListener() returned nullptr");
    return rv;
  }

  return listener->OnLDAPMessage(m_msg);
}

nsresult nsLDAPConnection::InvokeMessageCallback(LDAPMessage *aMsgHandle,
                                                 nsILDAPMessage *aMsg,
                                                 int32_t aOperation,
                                                 bool aRemoveOpFromConnQ) {
#if defined(DEBUG)
  // We only want this being logged for debug builds so as not to affect
  // performance too much.
  MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug,
          ("InvokeMessageCallback entered"));
#endif

  // Get the operation.
  nsCOMPtr<nsILDAPOperation> operation;
  {
    MutexAutoLock lock(mPendingOperationsMutex);
    mPendingOperations.Get((uint32_t)aOperation, getter_AddRefs(operation));
  }

  NS_ENSURE_TRUE(operation, NS_ERROR_NULL_POINTER);

  nsLDAPMessage *msg = static_cast<nsLDAPMessage *>(aMsg);
  msg->mOperation = operation;

  // proxy the listener callback to the ui thread.
  RefPtr<nsOnLDAPMessageRunnable> runnable =
      new nsOnLDAPMessageRunnable(msg, aRemoveOpFromConnQ);
  // invoke the callback
  NS_DispatchToMainThread(runnable);

  // if requested (ie the operation is done), remove the operation
  // from the connection queue.
  if (aRemoveOpFromConnQ) {
    MutexAutoLock lock(mPendingOperationsMutex);
    mPendingOperations.Remove(aOperation);

    MOZ_LOG(gLDAPLogModule, mozilla::LogLevel::Debug,
            ("pending operation removed; total pending operations now = %d",
             mPendingOperations.Count()));
  }

  return NS_OK;
}

NS_IMETHODIMP
nsLDAPConnection::OnLookupComplete(nsICancelable *aRequest,
                                   nsIDNSRecord *aRecord, nsresult aStatus) {
  nsresult rv = NS_OK;

  if (aRecord) {
    // Build mResolvedIP list
    //
    mResolvedIP.Truncate();

    int32_t index = 0;
    nsCString addrbuf;
    nsCOMPtr<nsINetAddr> addr;

    while (
        NS_SUCCEEDED(aRecord->GetScriptableNextAddr(0, getter_AddRefs(addr)))) {
      // We can only use v4 addresses
      //
      uint16_t family = 0;
      bool v4mapped = false;
      addr->GetFamily(&family);
      addr->GetIsV4Mapped(&v4mapped);
      if (family == nsINetAddr::FAMILY_INET || v4mapped) {
        // If there are more IPs in the list, we separate them with
        // a space, as supported/used by the LDAP C-SDK.
        //
        if (index++) mResolvedIP.Append(' ');

        // Convert the IPv4 address to a string, and append it to our
        // list of IPs.  Strip leading '::FFFF:' (the IPv4-mapped-IPv6
        // indicator) if present.
        //
        addr->GetAddress(addrbuf);
        if (addrbuf[0] == ':' && addrbuf.Length() > 7)
          mResolvedIP.Append(Substring(addrbuf, 7));
        else
          mResolvedIP.Append(addrbuf);
      }
    }
  }

  if (NS_FAILED(aStatus)) {
    // The DNS service failed, lets pass something reasonable
    // back to the listener.
    //
    switch (aStatus) {
      case NS_ERROR_OUT_OF_MEMORY:
      case NS_ERROR_UNKNOWN_HOST:
      case NS_ERROR_FAILURE:
      case NS_ERROR_OFFLINE:
        rv = aStatus;
        break;

      default:
        rv = NS_ERROR_UNEXPECTED;
        break;
    }
  } else if (!mResolvedIP.Length()) {
    // We have no host resolved, that is very bad, and should most
    // likely have been caught earlier.
    //
    NS_ERROR(
        "nsLDAPConnection::OnStopLookup(): the resolved IP "
        "string is empty.");

    rv = NS_ERROR_UNKNOWN_HOST;
  } else {
    // We've got the IP(s) for the hostname, now lets setup the
    // LDAP connection using this information. Note that if the
    // LDAP server returns a referral, the C-SDK will perform a
    // new, synchronous DNS lookup, which might hang (but hopefully
    // if we've come this far, DNS is working properly).
    //
    mConnectionHandle =
        ldap_init(mResolvedIP.get(),
                  mPort == -1 ? (mSSL ? LDAPS_PORT : LDAP_PORT) : mPort);
    // Check that we got a proper connection, and if so, setup the
    // threading functions for this connection.
    //
    if (!mConnectionHandle) {
      rv = NS_ERROR_FAILURE;  // LDAP C SDK API gives no useful error
    } else {
      ldap_set_option(mConnectionHandle, LDAP_OPT_ASYNC_CONNECT, LDAP_OPT_ON);
#if defined(DEBUG_dmose) || defined(DEBUG_bienvenu)
      const int lDebug = 0;
      ldap_set_option(mConnectionHandle, LDAP_OPT_DEBUG_LEVEL, &lDebug);
#endif

      // the C SDK currently defaults to v2.  if we're to use v3,
      // tell it so.
      //
      int version;
      switch (mVersion) {
        case 2:
          break;
        case 3:
          version = LDAP_VERSION3;
          ldap_set_option(mConnectionHandle, LDAP_OPT_PROTOCOL_VERSION,
                          &version);
          break;
        default:
          NS_ERROR(
              "nsLDAPConnection::OnLookupComplete(): mVersion"
              " invalid");
      }

      // This code sets up the current connection to use PSM for SSL
      // functionality.  Making this use libssldap instead for
      // non-browser user shouldn't be hard.

      extern nsresult nsLDAPInstallSSL(LDAP * ld, const char *aHostName);

      if (mSSL) {
        if (ldap_set_option(mConnectionHandle, LDAP_OPT_SSL, LDAP_OPT_ON) !=
            LDAP_SUCCESS) {
          NS_ERROR(
              "nsLDAPConnection::OnStopLookup(): Error"
              " configuring connection to use SSL");
          rv = NS_ERROR_UNEXPECTED;
        }

        rv = nsLDAPInstallSSL(mConnectionHandle, mDNSHost.get());
        if (NS_FAILED(rv)) {
          NS_ERROR(
              "nsLDAPConnection::OnStopLookup(): Error"
              " installing secure LDAP routines for"
              " connection");
        }
      }
    }
  }

  // Drop the DNS request object, we no longer need it, and set the flag
  // indicating that DNS has finished.
  //
  mDNSRequest = nullptr;
  mDNSHost.Truncate();

  // Call the listener, and then we can release our reference to it.
  //
  mInitListener->OnLDAPInit(this, rv);
  mInitListener = nullptr;

  return rv;
}

nsLDAPConnectionRunnable::nsLDAPConnectionRunnable(
    int32_t aOperationID, nsILDAPOperation *aOperation,
    nsLDAPConnection *aConnection)
    : mOperationID(aOperationID), mConnection(aConnection) {}

nsLDAPConnectionRunnable::~nsLDAPConnectionRunnable() {
  if (mConnection) {
    NS_ReleaseOnMainThread("nsLDAPConnectionRunnable::mConnection",
                           mConnection.forget());
  }
}

NS_IMPL_ISUPPORTS(nsLDAPConnectionRunnable, nsIRunnable)

NS_IMETHODIMP nsLDAPConnectionRunnable::Run() {
  if (!mOperationID) {
    NS_ERROR("mOperationID is null");
    return NS_ERROR_NULL_POINTER;
  }

  LDAPMessage *msgHandle;
  bool operationFinished = true;
  RefPtr<nsLDAPMessage> msg;

  struct timeval timeout = {0, 0};

  nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
  int32_t returnCode = ldap_result(mConnection->mConnectionHandle, mOperationID,
                                   LDAP_MSG_ONE, &timeout, &msgHandle);
  switch (returnCode) {
    // timeout
    case 0:
      // XXX do we need a timer?
      return thread->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
    case -1:
      NS_ERROR("We don't know what went wrong with the ldap operation");
      return NS_ERROR_FAILURE;

    case LDAP_RES_SEARCH_ENTRY:
    case LDAP_RES_SEARCH_REFERENCE:
      // XXX what should we do with LDAP_RES_SEARCH_EXTENDED
      operationFinished = false;
      [[fallthrough]];
    default: {
      msg = new nsLDAPMessage;
      if (!msg) return NS_ERROR_NULL_POINTER;

      // initialize the message, using a protected method not available
      // through nsILDAPMessage (which is why we need the raw pointer)
      nsresult rv = msg->Init(mConnection, msgHandle);

      switch (rv) {
        case NS_OK: {
          int32_t errorCode;
          msg->GetErrorCode(&errorCode);

          // maybe a version error, e.g., using v3 on a v2 server.
          // if we're using v3, try v2.
          if (errorCode == LDAP_PROTOCOL_ERROR &&
              mConnection->mVersion == nsILDAPConnection::VERSION3) {
            nsAutoCString password;
            mConnection->mVersion = nsILDAPConnection::VERSION2;
            ldap_set_option(mConnection->mConnectionHandle,
                            LDAP_OPT_PROTOCOL_VERSION, &mConnection->mVersion);

            if (NS_SUCCEEDED(rv)) {
              // We don't want to notify callers that we are done, so
              // redispatch the runnable.
              // XXX do we need a timer?
              rv = thread->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
              NS_ENSURE_SUCCESS(rv, rv);
              return NS_OK;
            }
          }
          // If we're midway through a SASL Bind, we need to continue
          // without letting our caller know what we're up to!
          //
          if (errorCode == LDAP_SASL_BIND_IN_PROGRESS) {
            struct berval *creds;
            ldap_parse_sasl_bind_result(mConnection->mConnectionHandle,
                                        msgHandle, &creds, 0);

            nsCOMPtr<nsILDAPOperation> operation;
            {
              MutexAutoLock lock(mConnection->mPendingOperationsMutex);
              mConnection->mPendingOperations.Get((uint32_t)mOperationID,
                                                  getter_AddRefs(operation));
            }

            NS_ENSURE_TRUE(operation, NS_ERROR_NULL_POINTER);

            nsresult rv = operation->SaslStep(creds->bv_val, creds->bv_len);
            if (NS_SUCCEEDED(rv)) return NS_OK;
          }
          break;
        }
          // Error code handling in here
        default:
          return NS_OK;
      }

      // invoke the callback on the nsILDAPOperation corresponding to
      // this message
      rv = mConnection->InvokeMessageCallback(msgHandle, msg, mOperationID,
                                              operationFinished);
      if (NS_FAILED(rv)) {
        NS_ERROR(
            "CheckLDAPOperationResult(): error invoking message"
            " callback");
        // punt and hope things work out better next time around
        return NS_OK;
      }

      if (!operationFinished) {
        // XXX do we need a timer?
        rv = thread->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      break;
    }
  }
  return NS_OK;
}