/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the mozilla.org LDAP XPCOM SDK. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Dan Mosedale (original author) * Leif Hedstrom * Kipp Hickman * Warren Harris * Dan Matejka * David Bienvenu * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsLDAPInternal.h" #include "nsIServiceManager.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsIComponentManager.h" #include "nsLDAPConnection.h" #include "nsLDAPMessage.h" #include "nsThreadUtils.h" #include "nsIConsoleService.h" #include "nsIDNSService.h" #include "nsIDNSRecord.h" #include "nsIRequestObserver.h" #include "nsIProxyObjectManager.h" #include "nsNetError.h" #include "nsLDAPOperation.h" #include "nsILDAPErrors.h" #include "nsIClassInfoImpl.h" const char kConsoleServiceContractId[] = "@mozilla.org/consoleservice;1"; const char kDNSServiceContractId[] = "@mozilla.org/network/dns-service;1"; // constructor // nsLDAPConnection::nsLDAPConnection() : mConnectionHandle(0), mPendingOperations(0), mRunnable(0), mSSL(PR_FALSE), mVersion(nsILDAPConnection::VERSION3), mDNSRequest(0) { } // destructor // nsLDAPConnection::~nsLDAPConnection() { Close(); // Release the reference to the runnable object. // NS_IF_RELEASE(mRunnable); } // We need our own Release() here, so that we can lock around the delete. // This is needed to avoid a race condition with the weak reference to us, // which is used in nsLDAPConnectionLoop. A problem could occur if the // nsLDAPConnection gets destroyed while do_QueryReferent() is called, // since converting to the strong reference isn't MT safe. // NS_IMPL_THREADSAFE_ADDREF(nsLDAPConnection) NS_INTERFACE_MAP_BEGIN(nsLDAPConnection) NS_INTERFACE_MAP_ENTRY(nsILDAPConnection) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIDNSListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsILDAPConnection) NS_IMPL_QUERY_CLASSINFO(nsLDAPConnection) NS_INTERFACE_MAP_END_THREADSAFE NS_IMPL_CI_INTERFACE_GETTER3(nsLDAPConnection, nsILDAPConnection, nsISupportsWeakReference, nsIDNSListener) nsrefcnt nsLDAPConnection::Release(void) { nsrefcnt count; NS_PRECONDITION(0 != mRefCnt, "dup release"); count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); NS_LOG_RELEASE(this, count, "nsLDAPConnection"); if (0 == count) { // As commented by danm: In the object's destructor, if by some // convoluted, indirect means it happens to run into some code // that temporarily references it (addref/release), then if the // refcount had been left at 0 the unexpected release would // attempt to reenter the object's destructor. // mRefCnt = 1; /* stabilize */ // If we have a mRunnable object, we need to make sure to lock it's // mLock before we try to DELETE. This is to avoid a race condition. // We also make sure to keep a strong reference to the runnable // object, to make sure it doesn't get GCed from underneath us, // while we are still holding a lock for instance. // if (mRunnable && mRunnable->mLock) { nsLDAPConnectionLoop *runnable = mRunnable; NS_ADDREF(runnable); PR_Lock(runnable->mLock); NS_DELETEXPCOM(this); PR_Unlock(runnable->mLock); NS_RELEASE(runnable); } else { NS_DELETEXPCOM(this); } return 0; } return count; } NS_IMETHODIMP nsLDAPConnection::Init(const char *aHost, PRInt32 aPort, PRBool aSSL, const nsACString& aBindName, nsILDAPMessageListener *aMessageListener, nsISupports *aClosure, PRUint32 aVersion) { nsCOMPtr selfProxy; nsresult rv; if ( !aHost || !aMessageListener) { return NS_ERROR_ILLEGAL_VALUE; } // 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\n"); mBindName.Assign(aBindName); mClosure = aClosure; // Save the port number, SSL flag, and protocol version for later // use, once the DNS server(s) has resolved the host part. // mPort = aPort; mSSL = aSSL; if (aVersion != nsILDAPConnection::VERSION2 && aVersion != nsILDAPConnection::VERSION3) { NS_ERROR("nsLDAPConnection::Init(): illegal version"); return NS_ERROR_ILLEGAL_VALUE; } mVersion = aVersion; // Save the Init listener reference, we need it when the async // DNS resolver has finished. // mInitListener = aMessageListener; // allocate a hashtable to keep track of pending operations. // 10 buckets seems like a reasonable size, and we do want it to // be threadsafe // mPendingOperations = new nsSupportsHashtable(10, PR_TRUE); if ( !mPendingOperations) { NS_ERROR("failure initializing mPendingOperations hashtable"); return NS_ERROR_FAILURE; } nsCOMPtr 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 pDNSService(do_GetService(kDNSServiceContractId, &rv)); if (NS_FAILED(rv)) { NS_ERROR("nsLDAPConnection::Init(): couldn't " "create the DNS Service object"); return NS_ERROR_FAILURE; } mDNSHost = aHost; // 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. mDNSHost.CompressWhitespace(PR_TRUE, PR_TRUE); PRInt32 spacePos = mDNSHost.FindChar(' '); // trim off trailing host(s) if (spacePos != kNotFound) mDNSHost.Truncate(spacePos); rv = pDNSService->AsyncResolve(mDNSHost, 0, this, curThread, 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; PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("unbinding\n")); 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); #ifdef PR_LOGGING if (rc != LDAP_SUCCESS) { PR_LOG(gLDAPLogModule, PR_LOG_WARNING, ("nsLDAPConnection::Close(): %s\n", ldap_err2string(rc))); } #endif mConnectionHandle = nsnull; } PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("unbound\n")); if (mPendingOperations) { delete mPendingOperations; mPendingOperations = nsnull; } // 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 = 0; } mInitListener = 0; } 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, PRInt32 *_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(PRUnichar **_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 = UTF8ToNewUnicode(nsDependentCString(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(nsILDAPOperation *aOperation) { PRInt32 msgID; if (!aOperation) { return NS_ERROR_ILLEGAL_VALUE; } // find the message id // aOperation->GetMessageID(&msgID); // turn it into an nsVoidKey. note that this is another spot that // assumes that sizeof(void*) >= sizeof(PRInt32). // // XXXdmose should really create an nsPRInt32Key. // nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgID)); if (!key) { return NS_ERROR_OUT_OF_MEMORY; } // actually add it to the queue. if Put indicates that an item in // the hashtable was actually overwritten, something is really wrong. // if (mPendingOperations->Put(key, aOperation)) { NS_ERROR("nsLDAPConnection::AddPendingOperation() " "mPendingOperations->Put() overwrote an item. msgId " "is supposed to be unique\n"); delete key; return NS_ERROR_UNEXPECTED; } PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("pending operation added; total pending operations now = %d\n", mPendingOperations->Count())); delete key; return NS_OK; } /** * 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(nsILDAPOperation *aOperation) { nsresult rv; PRInt32 msgID; NS_ENSURE_TRUE(mPendingOperations, NS_OK); NS_ENSURE_ARG_POINTER(aOperation); // find the message id // rv = aOperation->GetMessageID(&msgID); NS_ENSURE_SUCCESS(rv, rv); // turn it into an nsVoidKey. note that this is another spot that // assumes that sizeof(void*) >= sizeof(PRInt32). // // XXXdmose should really create an nsPRInt32Key. // nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgID)); if (!key) { return NS_ERROR_OUT_OF_MEMORY; } // remove the operation if it's still there. // if (!mPendingOperations->Remove(key)) { PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("nsLDAPConnection::RemovePendingOperation(): could not remove " "operation; perhaps it already completed.")); } else { PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("nsLDAPConnection::RemovePendingOperation(): operation " "removed; total pending operations now = %d\n", mPendingOperations->Count())); } delete key; return NS_OK; } nsresult nsLDAPConnection::InvokeMessageCallback(LDAPMessage *aMsgHandle, nsILDAPMessage *aMsg, PRBool aRemoveOpFromConnQ) { PRInt32 msgId; nsresult rv; nsCOMPtr operation; nsCOMPtr listener; #if defined(DEBUG) // We only want this being logged for debug builds so as not to affect performance too much. PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("InvokeMessageCallback entered\n")); #endif // get the message id corresponding to this operation // msgId = ldap_msgid(aMsgHandle); if (msgId == -1) { NS_ERROR("nsLDAPConnection::GetCallbackByMessage(): " "ldap_msgid() failed\n"); return NS_ERROR_FAILURE; } // get this in key form. note that using nsVoidKey in this way assumes // that sizeof(void *) >= sizeof PRInt32 // nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgId)); if (!key) return NS_ERROR_OUT_OF_MEMORY; // find the operation in question operation = getter_AddRefs(NS_STATIC_CAST(nsILDAPOperation *, mPendingOperations->Get(key))); if (!operation) { PR_LOG(gLDAPLogModule, PR_LOG_WARNING, ("Warning: InvokeMessageCallback(): couldn't find " "nsILDAPOperation corresponding to this message id\n")); delete key; // this may well be ok, since it could just mean that the operation // was aborted while some number of messages were already in transit. // return NS_OK; } // Make sure the mOperation member is set to this operation before // we call the callback. // NS_STATIC_CAST(nsLDAPMessage *, aMsg)->mOperation = operation; // get the message listener object (this may be a proxy for a // callback which should happen on another thread) // rv = operation->GetMessageListener(getter_AddRefs(listener)); if (NS_FAILED(rv)) { NS_ERROR("nsLDAPConnection::InvokeMessageCallback(): probable " "memory corruption: GetMessageListener() returned error"); delete key; return NS_ERROR_UNEXPECTED; } // invoke the callback // if (listener) { listener->OnLDAPMessage(aMsg); } // if requested (ie the operation is done), remove the operation // from the connection queue. // if (aRemoveOpFromConnQ) { nsCOMPtr operation = getter_AddRefs(NS_STATIC_CAST(nsLDAPOperation *, mPendingOperations->Get(key))); // try to break cycles if (operation) operation->Clear(); rv = mPendingOperations->Remove(key); if (NS_FAILED(rv)) { NS_ERROR("nsLDAPConnection::InvokeMessageCallback: unable to " "remove operation from the connection queue\n"); delete key; return NS_ERROR_UNEXPECTED; } PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("pending operation removed; total pending operations now =" " %d\n", mPendingOperations->Count())); } delete key; return NS_OK; } // constructor // nsLDAPConnectionLoop::nsLDAPConnectionLoop() : mWeakConn(0), mLock(0) { } // destructor // nsLDAPConnectionLoop::~nsLDAPConnectionLoop() { // Delete the lock object if (mLock) PR_DestroyLock(mLock); } NS_IMPL_THREADSAFE_ISUPPORTS1(nsLDAPConnectionLoop, nsIRunnable) NS_IMETHODIMP nsLDAPConnectionLoop::Init() { if (!mLock) { mLock = PR_NewLock(); if (!mLock) { NS_ERROR("nsLDAPConnectionLoop::Init: out of memory "); return NS_ERROR_OUT_OF_MEMORY; } } return NS_OK; } // typedef PRBool // (*PR_CALLBACK nsHashtableEnumFunc) // (nsHashKey *aKey, void *aData, void* aClosure); PRBool PR_CALLBACK CheckLDAPOperationResult(nsHashKey *aKey, void *aData, void* aClosure) { int lderrno; nsresult rv; PRInt32 returnCode; LDAPMessage *msgHandle; nsCOMPtr msg; PRBool operationFinished = PR_TRUE; struct timeval timeout = { 0, 0 }; PRIntervalTime sleepTime = PR_MillisecondsToInterval(40); // we need to access some of the connection loop's objects // nsLDAPConnectionLoop *loop = NS_STATIC_CAST(nsLDAPConnectionLoop *, aClosure); // get the console service so we can log messages // nsCOMPtr consoleSvc = do_GetService(kConsoleServiceContractId, &rv); if (NS_FAILED(rv)) { NS_ERROR("CheckLDAPOperationResult() couldn't get console service"); return NS_ERROR_FAILURE; } returnCode = ldap_result(loop->mRawConn->mConnectionHandle, aKey->HashCode(), LDAP_MSG_ONE, &timeout, &msgHandle); // if we didn't error or timeout, create an nsILDAPMessage // switch (returnCode) { case 0: // timeout // the connection may not exist yet. sleep for a while // to avoid a problem where the LDAP connection/thread isn't // ready quite yet, and we want to avoid a very busy loop. // PR_Sleep(sleepTime); return PR_TRUE; case -1: // something went wrong lderrno = ldap_get_lderrno(loop->mRawConn->mConnectionHandle, 0, 0); // Sleep briefly, to avoid a very busy loop again. // PR_Sleep(sleepTime); switch (lderrno) { case LDAP_SERVER_DOWN: // We might want to shutdown the thread here, but it has // implications to the user of the nsLDAPConnection, so // for now we just ignore it. It's up to the owner of // the nsLDAPConnection to detect the error, and then // create a new connection. // PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("CheckLDAPOperationResult(): ldap_result returned" " LDAP_SERVER_DOWN")); break; case LDAP_DECODING_ERROR: consoleSvc->LogStringMessage( NS_LITERAL_STRING("LDAP: WARNING: decoding error; possible corrupt data received").get()); NS_WARNING("CheckLDAPOperationResult(): ldaperrno = " "LDAP_DECODING_ERROR after ldap_result()"); break; case LDAP_NO_MEMORY: NS_ERROR("CheckLDAPOperationResult(): Couldn't allocate memory" " while getting async operation result"); // punt and hope things work out better next time around break; case LDAP_PARAM_ERROR: // I think it's possible to hit a race condition where we're // continuing to poll for a result after the C SDK connection // has removed the operation because the connection has gone // dead. In theory we should fix this. Practically, it's // unclear to me whether it matters. // NS_WARNING("CheckLDAPOperationResult(): ldap_result returned" " LDAP_PARAM_ERROR"); break; default: NS_ERROR("CheckLDAPOperationResult(): lderrno set to " "unexpected value after ldap_result() " "call in nsLDAPConnection::Run()"); PR_LOG(gLDAPLogModule, PR_LOG_ERROR, ("lderrno = 0x%x", lderrno)); break; } break; case LDAP_RES_SEARCH_ENTRY: case LDAP_RES_SEARCH_REFERENCE: // XXX what should we do with LDAP_RES_SEARCH_EXTENDED? // not done yet, so we shouldn't remove the op from the conn q operationFinished = PR_FALSE; // fall through to default case default: // initialize the message and call the callback // we want nsLDAPMessage specifically, not a compatible, since // we're sharing native objects used by the LDAP C SDK // nsLDAPMessage *rawMsg; NS_NEWXPCOM(rawMsg, nsLDAPMessage); if (!rawMsg) { NS_ERROR("CheckLDAPOperationResult(): couldn't allocate memory" " for new LDAP message; search entry dropped"); // punt and hope things work out better next time around break; } // initialize the message, using a protected method not available // through nsILDAPMessage (which is why we need the raw pointer) // rv = rawMsg->Init(loop->mRawConn, msgHandle); switch (rv) { case NS_OK: { PRInt32 errorCode; rawMsg->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 && loop->mRawConn->mVersion == nsILDAPConnection::VERSION3) { nsCAutoString password; loop->mRawConn->mVersion = nsILDAPConnection::VERSION2; ldap_set_option(loop->mRawConn->mConnectionHandle, LDAP_OPT_PROTOCOL_VERSION, &loop->mRawConn->mVersion); nsCOMPtr operation = NS_STATIC_CAST(nsILDAPOperation *, NS_STATIC_CAST(nsISupports *, aData)); // we pass in an empty password to tell the operation that // it should use the cached password. // rv = operation->SimpleBind(password); if (NS_SUCCEEDED(rv)) { operationFinished = PR_FALSE; // we don't want to notify callers that we're done... return PR_TRUE; } } } break; case NS_ERROR_LDAP_DECODING_ERROR: consoleSvc->LogStringMessage( NS_LITERAL_STRING("LDAP: WARNING: decoding error; possible corrupt data received").get()); NS_WARNING("CheckLDAPOperationResult(): ldaperrno = " "LDAP_DECODING_ERROR after ldap_result()"); return PR_TRUE; case NS_ERROR_OUT_OF_MEMORY: // punt and hope things work out better next time around return PR_TRUE; case NS_ERROR_ILLEGAL_VALUE: case NS_ERROR_UNEXPECTED: default: // shouldn't happen; internal error // NS_ERROR("CheckLDAPOperationResult(): nsLDAPMessage::Init() " "returned unexpected value."); // punt and hope things work out better next time around return PR_TRUE; } // now let the scoping mechanisms provided by nsCOMPtr manage // the reference for us. // msg = rawMsg; // invoke the callback on the nsILDAPOperation corresponding to // this message // rv = loop->mRawConn->InvokeMessageCallback(msgHandle, msg, operationFinished); if (NS_FAILED(rv)) { NS_ERROR("CheckLDAPOperationResult(): error invoking message" " callback"); // punt and hope things work out better next time around return PR_TRUE; } break; } return PR_TRUE; } // for nsIRunnable. this thread spins in ldap_result() awaiting the next // message. once one arrives, it dispatches it to the nsILDAPMessageListener // on the main thread. // // XXX do all returns from this function need to do thread cleanup? // NS_IMETHODIMP nsLDAPConnectionLoop::Run(void) { PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("nsLDAPConnection::Run() entered\n")); // wait for results // while(1) { // Exit this thread if we no longer have an nsLDAPConnection // associated with it. We also aquire a lock here, to make sure // to avoid a possible race condition when the nsLDAPConnection // is destructed during the call to do_QueryReferent() (since that // function isn't MT safe). // nsresult rv; PR_Lock(mLock); nsCOMPtr strongConn = do_QueryReferent(mWeakConn, &rv); PR_Unlock(mLock); if (NS_FAILED(rv)) { mWeakConn = 0; return NS_OK; } // we use a raw connection because we need to call non-interface // methods mRawConn = NS_STATIC_CAST(nsLDAPConnection *, NS_STATIC_CAST(nsILDAPConnection *, strongConn.get())); // XXX deal with timeouts better // NS_ASSERTION(mRawConn->mConnectionHandle, "nsLDAPConnection::Run(): " "no connection created.\n"); // We can't enumerate over mPendingOperations itself, because the // callback needs to modify mPendingOperations. So we clone it first, // and enumerate over the clone. It kinda sucks that we need to do // this everytime we poll, but the hashtable will pretty much always // be small. // // only clone if the number of pending operations is non-zero // otherwise, put the LDAP connection thread to sleep (briefly) // until there is pending operations.. if (mRawConn->mPendingOperations->Count()) { nsHashtable *hashtableCopy = mRawConn->mPendingOperations->Clone(); if (hashtableCopy) { hashtableCopy->Enumerate(CheckLDAPOperationResult, this); delete hashtableCopy; } else { // punt and hope it works next time around NS_ERROR("nsLDAPConnectionLoop::Run() error cloning hashtable"); } } else { PR_Sleep(PR_MillisecondsToInterval(40)); } } // This will never happen, but here just in case. // return NS_OK; } NS_IMETHODIMP nsLDAPConnection::OnLookupComplete(nsICancelable *aRequest, nsIDNSRecord *aRecord, nsresult aStatus) { nsresult rv = NS_OK; if (aRecord) { // Build mResolvedIP list // mResolvedIP.Truncate(); PRInt32 index = 0; char addrbuf[64]; PRNetAddr addr; while (NS_SUCCEEDED(aRecord->GetNextAddr(0, &addr))) { // We can only use v4 addresses // PRBool v4mapped = PR_FALSE; if (addr.raw.family == PR_AF_INET6) v4mapped = PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped); if (addr.raw.family == PR_AF_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. // PR_NetAddrToString(&addr, addrbuf, sizeof(addrbuf)); if ((addrbuf[0] == ':') && (strlen(addrbuf) > 7)) mResolvedIP.Append(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.\n"); 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 ? 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 { #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"); } #ifdef MOZ_PSM // 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"); } } #endif } // Create a new runnable object, and increment the refcnt. The // thread will also hold a strong ref to the runnable, but we need // to make sure it doesn't get destructed until we are done with // all locking etc. in nsLDAPConnection::Release(). // mRunnable = new nsLDAPConnectionLoop(); NS_IF_ADDREF(mRunnable); if (!mRunnable || NS_FAILED(mRunnable->Init())) { rv = NS_ERROR_OUT_OF_MEMORY; } else { // Here we keep a weak reference in the runnable object to the // nsLDAPConnection ("this"). This avoids the problem where a // connection can't get destructed because of the new thread // keeping a strong reference to it. It also helps us know when // we need to exit the new thread: when we can't convert the weak // reference to a strong ref, we know that the nsLDAPConnection // object is gone, and we need to stop the thread running. // nsCOMPtr conn = NS_STATIC_CAST(nsILDAPConnection *, this); mRunnable->mWeakConn = do_GetWeakReference(conn); // kick off a thread for result listening and marshalling // rv = NS_NewThread(getter_AddRefs(mThread), mRunnable); if (NS_FAILED(rv)) { rv = NS_ERROR_NOT_AVAILABLE; } // XXX(darin): We need to shutdown this thread at some point. // Otherwise, it will stick around until shutdown. } } // Drop the DNS request object, we no longer need it, and set the flag // indicating that DNS has finished. // mDNSRequest = 0; mDNSHost.Truncate(); // Call the listener, and then we can release our reference to it. // mInitListener->OnLDAPInit(this, rv); mInitListener = 0; return rv; }