Back to index

lightning-sunbird  0.9+nobinonly
nsLDAPConnection.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the mozilla.org LDAP XPCOM SDK.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2000
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Dan Mosedale <dmose@mozilla.org> (original author)
00025  *   Leif Hedstrom <leif@netscape.com>
00026  *   Kipp Hickman <kipp@netscape.com>
00027  *   Warren Harris <warren@netscape.com>
00028  *   Dan Matejka <danm@netscape.com>
00029  *   David Bienvenu <bienvenu@mozilla.org>
00030  *
00031  * Alternatively, the contents of this file may be used under the terms of
00032  * either the GNU General Public License Version 2 or later (the "GPL"), or
00033  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00034  * in which case the provisions of the GPL or the LGPL are applicable instead
00035  * of those above. If you wish to allow use of your version of this file only
00036  * under the terms of either the GPL or the LGPL, and not to allow others to
00037  * use your version of this file under the terms of the MPL, indicate your
00038  * decision by deleting the provisions above and replace them with the notice
00039  * and other provisions required by the GPL or the LGPL. If you do not delete
00040  * the provisions above, a recipient may use your version of this file under
00041  * the terms of any one of the MPL, the GPL or the LGPL.
00042  *
00043  * ***** END LICENSE BLOCK ***** */
00044 
00045 #include "nsLDAPInternal.h"
00046 #include "nsIServiceManager.h"
00047 #include "nsString.h"
00048 #include "nsReadableUtils.h"
00049 #include "nsIComponentManager.h"
00050 #include "nsLDAPConnection.h"
00051 #include "nsLDAPMessage.h"
00052 #include "nsIEventQueueService.h"
00053 #include "nsIConsoleService.h"
00054 #include "nsIDNSService.h"
00055 #include "nsIDNSRecord.h"
00056 #include "nsIRequestObserver.h"
00057 #include "nsIProxyObjectManager.h"
00058 #include "nsEventQueueUtils.h"
00059 #include "nsNetError.h"
00060 #include "nsLDAPOperation.h"
00061 
00062 const char kConsoleServiceContractId[] = "@mozilla.org/consoleservice;1";
00063 const char kDNSServiceContractId[] = "@mozilla.org/network/dns-service;1";
00064 
00065 // constructor
00066 //
00067 nsLDAPConnection::nsLDAPConnection()
00068     : mConnectionHandle(0),
00069       mPendingOperations(0),
00070       mRunnable(0),
00071       mSSL(PR_FALSE),
00072       mVersion(nsILDAPConnection::VERSION3),
00073       mDNSRequest(0)
00074 {
00075 }
00076 
00077 // destructor
00078 //
00079 nsLDAPConnection::~nsLDAPConnection()
00080 {
00081   Close();
00082   // Release the reference to the runnable object.
00083   //
00084   NS_IF_RELEASE(mRunnable);
00085 }
00086 
00087 // We need our own Release() here, so that we can lock around the delete.
00088 // This is needed to avoid a race condition with the weak reference to us,
00089 // which is used in nsLDAPConnectionLoop. A problem could occur if the
00090 // nsLDAPConnection gets destroyed while do_QueryReferent() is called,
00091 // since converting to the strong reference isn't MT safe.
00092 //
00093 NS_IMPL_THREADSAFE_ADDREF(nsLDAPConnection)
00094 NS_INTERFACE_MAP_BEGIN(nsLDAPConnection)
00095   NS_INTERFACE_MAP_ENTRY(nsILDAPConnection)
00096   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
00097   NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
00098   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsILDAPConnection)
00099   NS_IMPL_QUERY_CLASSINFO(nsLDAPConnection)
00100 NS_INTERFACE_MAP_END_THREADSAFE
00101 NS_IMPL_CI_INTERFACE_GETTER3(nsLDAPConnection, nsILDAPConnection, 
00102                              nsISupportsWeakReference, nsIDNSListener)
00103 
00104 nsrefcnt
00105 nsLDAPConnection::Release(void)
00106 {
00107     nsrefcnt count;
00108 
00109     NS_PRECONDITION(0 != mRefCnt, "dup release");
00110     count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);
00111     NS_LOG_RELEASE(this, count, "nsLDAPConnection");
00112     if (0 == count) {
00113         // As commented by danm: In the object's destructor, if by some
00114         // convoluted, indirect means it happens to run into some code
00115         // that temporarily references it (addref/release), then if the
00116         // refcount had been left at 0 the unexpected release would
00117         // attempt to reenter the object's destructor.
00118         //
00119         mRefCnt = 1; /* stabilize */
00120 
00121         // If we have a mRunnable object, we need to make sure to lock it's
00122         // mLock before we try to DELETE. This is to avoid a race condition.
00123         // We also make sure to keep a strong reference to the runnable
00124         // object, to make sure it doesn't get GCed from underneath us,
00125         // while we are still holding a lock for instance.
00126         //
00127         if (mRunnable && mRunnable->mLock) {
00128             nsLDAPConnectionLoop *runnable  = mRunnable;
00129 
00130             NS_ADDREF(runnable);
00131             PR_Lock(runnable->mLock);
00132             NS_DELETEXPCOM(this);
00133             PR_Unlock(runnable->mLock);
00134             NS_RELEASE(runnable);
00135         } else {
00136             NS_DELETEXPCOM(this);
00137         }
00138 
00139         return 0;
00140     }
00141     return count;
00142 }
00143 
00144 
00145 NS_IMETHODIMP
00146 nsLDAPConnection::Init(const char *aHost, PRInt32 aPort, PRBool aSSL,
00147                        const nsACString& aBindName, 
00148                        nsILDAPMessageListener *aMessageListener,
00149                        nsISupports *aClosure, PRUint32 aVersion)
00150 {
00151     nsCOMPtr<nsIDNSListener> selfProxy;
00152     nsresult rv;
00153 
00154     if ( !aHost || !aMessageListener) {
00155         return NS_ERROR_ILLEGAL_VALUE;
00156     }
00157 
00158     // Make sure we haven't called Init earlier, i.e. there's a DNS
00159     // request pending.
00160     //
00161     NS_ASSERTION(!mDNSRequest, "nsLDAPConnection::Init() "
00162                  "Connection was already initialized\n");
00163 
00164     mBindName.Assign(aBindName);
00165 
00166     mClosure = aClosure;
00167 
00168     // Save the port number, SSL flag, and protocol version for later
00169     // use, once the DNS server(s) has resolved the host part.
00170     //
00171     mPort = aPort;
00172     mSSL = aSSL;
00173     if (aVersion != nsILDAPConnection::VERSION2 && 
00174         aVersion != nsILDAPConnection::VERSION3) {
00175         NS_ERROR("nsLDAPConnection::Init(): illegal version");
00176         return NS_ERROR_ILLEGAL_VALUE;
00177     }
00178     mVersion = aVersion;
00179 
00180     // Save the Init listener reference, we need it when the async
00181     // DNS resolver has finished.
00182     //
00183     mInitListener = aMessageListener;
00184 
00185     // allocate a hashtable to keep track of pending operations.
00186     // 10 buckets seems like a reasonable size, and we do want it to 
00187     // be threadsafe
00188     //
00189     mPendingOperations = new nsSupportsHashtable(10, PR_TRUE);
00190     if ( !mPendingOperations) {
00191         NS_ERROR("failure initializing mPendingOperations hashtable");
00192         return NS_ERROR_FAILURE;
00193     }
00194 
00195     nsCOMPtr<nsIEventQueue> curEventQ;
00196     rv = NS_GetCurrentEventQ(getter_AddRefs(curEventQ));
00197     if (NS_FAILED(rv)) {
00198         NS_ERROR("nsLDAPConnection::Init(): couldn't "
00199                  "get current event queue");
00200         return NS_ERROR_FAILURE;
00201     }
00202     // Do the pre-resolve of the hostname, using the DNS service. This
00203     // will also initialize the LDAP connection properly, once we have
00204     // the IPs resolved for the hostname. This includes creating the
00205     // new thread for this connection.
00206     //
00207     // XXX - What return codes can we expect from the DNS service?
00208     //
00209     nsCOMPtr<nsIDNSService>
00210         pDNSService(do_GetService(kDNSServiceContractId, &rv));
00211 
00212     if (NS_FAILED(rv)) {
00213         NS_ERROR("nsLDAPConnection::Init(): couldn't "
00214                  "create the DNS Service object");
00215 
00216         return NS_ERROR_FAILURE;
00217     }
00218     mDNSHost = aHost;
00219 
00220     // if the caller has passed in a space-delimited set of hosts, as the 
00221     // ldap c-sdk allows, strip off the trailing hosts for now.
00222     // Soon, we'd like to make multiple hosts work, but now make
00223     // at least the first one work.
00224     mDNSHost.CompressWhitespace(PR_TRUE, PR_TRUE);
00225 
00226     PRInt32 spacePos = mDNSHost.FindChar(' ');
00227     // trim off trailing host(s)
00228     if (spacePos != kNotFound)
00229       mDNSHost.Truncate(spacePos);
00230 
00231     rv = pDNSService->AsyncResolve(mDNSHost, 0, this, curEventQ, 
00232                                    getter_AddRefs(mDNSRequest));
00233 
00234     if (NS_FAILED(rv)) {
00235         switch (rv) {
00236         case NS_ERROR_OUT_OF_MEMORY:
00237         case NS_ERROR_UNKNOWN_HOST:
00238         case NS_ERROR_FAILURE:
00239         case NS_ERROR_OFFLINE:
00240             break;
00241 
00242         default:
00243             rv = NS_ERROR_UNEXPECTED;
00244         }
00245         mDNSHost.Truncate();
00246     }
00247     return rv;
00248 }
00249 
00250 // this might get exposed to clients, so we've broken it
00251 // out of the destructor.
00252 void
00253 nsLDAPConnection::Close()
00254 {
00255   int rc;
00256 
00257   PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("unbinding\n"));
00258 
00259   if (mConnectionHandle) {
00260       // note that the ldap_unbind() call in the 5.0 version of the LDAP C SDK
00261       // appears to be exactly identical to ldap_unbind_s(), so it may in fact
00262       // still be synchronous
00263       //
00264       rc = ldap_unbind(mConnectionHandle);
00265 #ifdef PR_LOGGING
00266       if (rc != LDAP_SUCCESS) {
00267           PR_LOG(gLDAPLogModule, PR_LOG_WARNING, 
00268                  ("nsLDAPConnection::Close(): %s\n", 
00269                   ldap_err2string(rc)));
00270       }
00271 #endif
00272       mConnectionHandle = nsnull;
00273   }
00274 
00275   PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("unbound\n"));
00276 
00277   if (mPendingOperations) {
00278       delete mPendingOperations;
00279       mPendingOperations = nsnull;
00280   }
00281 
00282   // Cancel the DNS lookup if needed, and also drop the reference to the
00283   // Init listener (if still there).
00284   //
00285   if (mDNSRequest) {
00286       mDNSRequest->Cancel(NS_ERROR_ABORT);
00287       mDNSRequest = 0;
00288   }
00289   mInitListener = 0;
00290 
00291 }
00292 
00293 NS_IMETHODIMP
00294 nsLDAPConnection::GetClosure(nsISupports **_retval)
00295 {
00296     if (!_retval) {
00297         return NS_ERROR_ILLEGAL_VALUE;
00298     }
00299     NS_IF_ADDREF(*_retval = mClosure);
00300     return NS_OK;
00301 }
00302 
00303 NS_IMETHODIMP
00304 nsLDAPConnection::SetClosure(nsISupports *aClosure)
00305 {
00306     mClosure = aClosure;
00307     return NS_OK;
00308 }
00309 
00310 // who we're binding as
00311 //
00312 // readonly attribute AUTF8String bindName
00313 //
00314 NS_IMETHODIMP
00315 nsLDAPConnection::GetBindName(nsACString& _retval)
00316 {
00317     _retval.Assign(mBindName);
00318     return NS_OK;
00319 }
00320 
00321 // wrapper for ldap_get_lderrno
00322 // XXX should copy before returning
00323 //
00324 NS_IMETHODIMP
00325 nsLDAPConnection::GetLdErrno(nsACString& matched, nsACString& errString, 
00326                              PRInt32 *_retval)
00327 {
00328     char *match, *err;
00329 
00330     NS_ENSURE_ARG_POINTER(_retval);
00331 
00332     *_retval = ldap_get_lderrno(mConnectionHandle, &match, &err);
00333     matched.Assign(match);
00334     errString.Assign(err);
00335     return NS_OK;
00336 }
00337 
00338 // return the error string corresponding to GetLdErrno.
00339 //
00340 // XXX - deal with optional params
00341 // XXX - how does ldap_perror know to look at the global errno?
00342 //
00343 NS_IMETHODIMP
00344 nsLDAPConnection::GetErrorString(PRUnichar **_retval)
00345 {
00346     NS_ENSURE_ARG_POINTER(_retval);
00347 
00348     // get the error string
00349     //
00350     char *rv = ldap_err2string(ldap_get_lderrno(mConnectionHandle, 0, 0));
00351     if (!rv) {
00352         return NS_ERROR_OUT_OF_MEMORY;
00353     }
00354 
00355     // make a copy using the XPCOM shared allocator
00356     //
00357     *_retval = UTF8ToNewUnicode(nsDependentCString(rv));
00358     if (!*_retval) {
00359         return NS_ERROR_OUT_OF_MEMORY;
00360     }
00361     return NS_OK;
00362 }
00363 
00369 nsresult
00370 nsLDAPConnection::AddPendingOperation(nsILDAPOperation *aOperation)
00371 {
00372     PRInt32 msgID;
00373 
00374     if (!aOperation) {
00375         return NS_ERROR_ILLEGAL_VALUE;
00376     }
00377 
00378     // find the message id
00379     //
00380     aOperation->GetMessageID(&msgID);
00381 
00382     // turn it into an nsVoidKey.  note that this is another spot that
00383     // assumes that sizeof(void*) >= sizeof(PRInt32).  
00384     //
00385     // XXXdmose  should really create an nsPRInt32Key.
00386     //
00387     nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgID));
00388     if (!key) {
00389         return NS_ERROR_OUT_OF_MEMORY;
00390     }
00391 
00392     // actually add it to the queue.  if Put indicates that an item in 
00393     // the hashtable was actually overwritten, something is really wrong.
00394     //
00395     if (mPendingOperations->Put(key, aOperation)) {
00396         NS_ERROR("nsLDAPConnection::AddPendingOperation() "
00397                  "mPendingOperations->Put() overwrote an item.  msgId "
00398                  "is supposed to be unique\n");
00399         delete key;
00400         return NS_ERROR_UNEXPECTED;
00401     }
00402 
00403     PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00404            ("pending operation added; total pending operations now = %d\n", 
00405             mPendingOperations->Count()));
00406 
00407     delete key;
00408     return NS_OK;
00409 }
00410 
00422 nsresult
00423 nsLDAPConnection::RemovePendingOperation(nsILDAPOperation *aOperation)
00424 {
00425     nsresult rv;
00426     PRInt32 msgID;
00427 
00428     NS_ENSURE_TRUE(mPendingOperations, NS_OK);
00429     NS_ENSURE_ARG_POINTER(aOperation);
00430 
00431     // find the message id
00432     //
00433     rv = aOperation->GetMessageID(&msgID);
00434     NS_ENSURE_SUCCESS(rv, rv);
00435 
00436     // turn it into an nsVoidKey.  note that this is another spot that
00437     // assumes that sizeof(void*) >= sizeof(PRInt32).  
00438     //
00439     // XXXdmose  should really create an nsPRInt32Key.
00440     //
00441     nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgID));
00442     if (!key) {
00443         return NS_ERROR_OUT_OF_MEMORY;
00444     }
00445 
00446     // remove the operation if it's still there.  
00447     //
00448     if (!mPendingOperations->Remove(key)) {
00449 
00450         PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00451                ("nsLDAPConnection::RemovePendingOperation(): could not remove "
00452                 "operation; perhaps it already completed."));
00453     } else {
00454 
00455         PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00456                ("nsLDAPConnection::RemovePendingOperation(): operation "
00457                 "removed; total pending operations now = %d\n", 
00458                 mPendingOperations->Count()));
00459     }
00460 
00461     delete key;
00462     return NS_OK;
00463 }
00464 
00465 nsresult
00466 nsLDAPConnection::InvokeMessageCallback(LDAPMessage *aMsgHandle, 
00467                                         nsILDAPMessage *aMsg,
00468                                         PRBool aRemoveOpFromConnQ)
00469 {
00470     PRInt32 msgId;
00471     nsresult rv;
00472     nsCOMPtr<nsILDAPOperation> operation;
00473     nsCOMPtr<nsILDAPMessageListener> listener;
00474 
00475 #if defined(DEBUG)
00476     // We only want this being logged for debug builds so as not to affect performance too much.
00477     PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, ("InvokeMessageCallback entered\n"));
00478 #endif
00479 
00480     // get the message id corresponding to this operation
00481     //
00482     msgId = ldap_msgid(aMsgHandle);
00483     if (msgId == -1) {
00484         NS_ERROR("nsLDAPConnection::GetCallbackByMessage(): "
00485                  "ldap_msgid() failed\n");
00486         return NS_ERROR_FAILURE;
00487     }
00488 
00489     // get this in key form.  note that using nsVoidKey in this way assumes
00490     // that sizeof(void *) >= sizeof PRInt32
00491     //
00492     nsVoidKey *key = new nsVoidKey(NS_REINTERPRET_CAST(void *, msgId));
00493     if (!key)
00494         return NS_ERROR_OUT_OF_MEMORY;
00495 
00496     // find the operation in question
00497     operation = getter_AddRefs(NS_STATIC_CAST(nsILDAPOperation *, mPendingOperations->Get(key)));
00498     if (!operation) {
00499 
00500         PR_LOG(gLDAPLogModule, PR_LOG_WARNING, 
00501                ("Warning: InvokeMessageCallback(): couldn't find "
00502                 "nsILDAPOperation corresponding to this message id\n"));
00503         delete key;
00504 
00505         // this may well be ok, since it could just mean that the operation
00506         // was aborted while some number of messages were already in transit.
00507         //
00508         return NS_OK;
00509     }
00510 
00511 
00512     // Make sure the mOperation member is set to this operation before
00513     // we call the callback.
00514     //
00515     NS_STATIC_CAST(nsLDAPMessage *, aMsg)->mOperation = operation;
00516 
00517     // get the message listener object (this may be a proxy for a
00518     // callback which should happen on another thread)
00519     //
00520     rv = operation->GetMessageListener(getter_AddRefs(listener));
00521     if (NS_FAILED(rv)) {
00522         NS_ERROR("nsLDAPConnection::InvokeMessageCallback(): probable "
00523                  "memory corruption: GetMessageListener() returned error");
00524         delete key;
00525         return NS_ERROR_UNEXPECTED;
00526     }
00527 
00528     // invoke the callback 
00529     //
00530     if (listener) {
00531       listener->OnLDAPMessage(aMsg);
00532     }
00533     // if requested (ie the operation is done), remove the operation
00534     // from the connection queue.
00535     //
00536     if (aRemoveOpFromConnQ) {
00537         nsCOMPtr <nsLDAPOperation> operation = 
00538           getter_AddRefs(NS_STATIC_CAST(nsLDAPOperation *,
00539                                         mPendingOperations->Get(key)));
00540         // try to break cycles
00541         if (operation)
00542           operation->Clear();
00543         rv = mPendingOperations->Remove(key);
00544         if (NS_FAILED(rv)) {
00545             NS_ERROR("nsLDAPConnection::InvokeMessageCallback: unable to "
00546                      "remove operation from the connection queue\n");
00547             delete key;
00548             return NS_ERROR_UNEXPECTED;
00549         }
00550 
00551         PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00552                ("pending operation removed; total pending operations now ="
00553                 " %d\n", mPendingOperations->Count()));
00554     }
00555 
00556     delete key;
00557     return NS_OK;
00558 }
00559 
00560 // constructor
00561 //
00562 nsLDAPConnectionLoop::nsLDAPConnectionLoop()
00563     : mWeakConn(0),
00564       mLock(0)
00565 {
00566 }
00567 
00568 // destructor
00569 //
00570 nsLDAPConnectionLoop::~nsLDAPConnectionLoop()
00571 {
00572     // Delete the lock object
00573     if (mLock)
00574         PR_DestroyLock(mLock);
00575 }
00576 
00577 NS_IMPL_THREADSAFE_ISUPPORTS1(nsLDAPConnectionLoop, nsIRunnable)
00578 
00579 NS_IMETHODIMP
00580 nsLDAPConnectionLoop::Init()
00581 {
00582     if (!mLock) {
00583         mLock = PR_NewLock();
00584         if (!mLock) {
00585             NS_ERROR("nsLDAPConnectionLoop::Init: out of memory ");
00586             return NS_ERROR_OUT_OF_MEMORY;
00587         }
00588     }
00589 
00590     return NS_OK;
00591 }
00592 
00593 // typedef PRBool
00594 // (*PR_CALLBACK nsHashtableEnumFunc)
00595 //      (nsHashKey *aKey, void *aData, void* aClosure);
00596 PRBool PR_CALLBACK
00597 CheckLDAPOperationResult(nsHashKey *aKey, void *aData, void* aClosure)
00598 {
00599     int lderrno;
00600     nsresult rv;
00601     PRInt32 returnCode;
00602     LDAPMessage *msgHandle;
00603     nsCOMPtr<nsILDAPMessage> msg;
00604     PRBool operationFinished = PR_TRUE;
00605     struct timeval timeout = { 1, 0 }; 
00606     PRIntervalTime sleepTime = PR_MillisecondsToInterval(40);
00607 
00608     // we need to access some of the connection loop's objects
00609     //
00610     nsLDAPConnectionLoop *loop = 
00611         NS_STATIC_CAST(nsLDAPConnectionLoop *, aClosure);
00612 
00613     // get the console service so we can log messages
00614     //
00615     nsCOMPtr<nsIConsoleService> consoleSvc = 
00616         do_GetService(kConsoleServiceContractId, &rv);
00617     if (NS_FAILED(rv)) {
00618         NS_ERROR("CheckLDAPOperationResult() couldn't get console service");
00619         return NS_ERROR_FAILURE;
00620     }
00621 
00622     returnCode = ldap_result(loop->mRawConn->mConnectionHandle,
00623                              aKey->HashCode(), LDAP_MSG_ONE,
00624                                  &timeout, &msgHandle);
00625 
00626         // if we didn't error or timeout, create an nsILDAPMessage
00627         //      
00628         switch (returnCode) {
00629 
00630         case 0: // timeout
00631 
00632             // the connection may not exist yet.  sleep for a while
00633             // to avoid a problem where the LDAP connection/thread isn't 
00634             // ready quite yet, and we want to avoid a very busy loop.
00635             //
00636             PR_Sleep(sleepTime);
00637             return PR_TRUE;
00638 
00639         case -1: // something went wrong 
00640 
00641         lderrno = ldap_get_lderrno(loop->mRawConn->mConnectionHandle, 0, 0);
00642 
00643             // Sleep briefly, to avoid a very busy loop again.
00644             //
00645             PR_Sleep(sleepTime);
00646 
00647             switch (lderrno) {
00648 
00649             case LDAP_SERVER_DOWN:
00650                 // We might want to shutdown the thread here, but it has
00651                 // implications to the user of the nsLDAPConnection, so
00652                 // for now we just ignore it. It's up to the owner of
00653                 // the nsLDAPConnection to detect the error, and then
00654                 // create a new connection.
00655                 //
00656                 PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00657                        ("CheckLDAPOperationResult(): ldap_result returned" 
00658                         " LDAP_SERVER_DOWN"));
00659                 break;
00660 
00661             case LDAP_DECODING_ERROR:
00662                 consoleSvc->LogStringMessage(
00663                     NS_LITERAL_STRING("LDAP: WARNING: decoding error; possible corrupt data received").get());
00664                 NS_WARNING("CheckLDAPOperationResult(): ldaperrno = "
00665                            "LDAP_DECODING_ERROR after ldap_result()");
00666                 break;
00667 
00668             case LDAP_NO_MEMORY:
00669                 NS_ERROR("CheckLDAPOperationResult(): Couldn't allocate memory"
00670                          " while getting async operation result");
00671                 // punt and hope things work out better next time around
00672                 break;
00673 
00674             case LDAP_PARAM_ERROR:
00675                 // I think it's possible to hit a race condition where we're
00676                 // continuing to poll for a result after the C SDK connection
00677                 // has removed the operation because the connection has gone
00678                 // dead.  In theory we should fix this.  Practically, it's
00679                 // unclear to me whether it matters.
00680                 //
00681                 NS_WARNING("CheckLDAPOperationResult(): ldap_result returned"
00682                            " LDAP_PARAM_ERROR");
00683                 break;
00684 
00685             default:
00686                 NS_ERROR("CheckLDAPOperationResult(): lderrno set to "
00687                            "unexpected value after ldap_result() "
00688                            "call in nsLDAPConnection::Run()");
00689                 PR_LOG(gLDAPLogModule, PR_LOG_ERROR, 
00690                        ("lderrno = 0x%x", lderrno));
00691                 break;
00692             }
00693             break;
00694 
00695         case LDAP_RES_SEARCH_ENTRY:
00696         case LDAP_RES_SEARCH_REFERENCE:
00697             // XXX what should we do with LDAP_RES_SEARCH_EXTENDED?
00698 
00699             // not done yet, so we shouldn't remove the op from the conn q
00700             operationFinished = PR_FALSE;
00701 
00702             // fall through to default case
00703 
00704         default: // initialize the message and call the callback
00705 
00706             // we want nsLDAPMessage specifically, not a compatible, since
00707             // we're sharing native objects used by the LDAP C SDK
00708             //
00709             nsLDAPMessage *rawMsg;
00710             NS_NEWXPCOM(rawMsg, nsLDAPMessage);
00711             if (!rawMsg) {
00712             NS_ERROR("CheckLDAPOperationResult(): couldn't allocate memory"
00713                      " for new LDAP message; search entry dropped");
00714                 // punt and hope things work out better next time around
00715                 break;
00716             }
00717 
00718             // initialize the message, using a protected method not available
00719             // through nsILDAPMessage (which is why we need the raw pointer)
00720             //
00721             rv = rawMsg->Init(loop->mRawConn, msgHandle);
00722 
00723             switch (rv) {
00724 
00725             case NS_OK: {
00726                 PRInt32 errorCode;
00727                 rawMsg->GetErrorCode(&errorCode);
00728                 // maybe a version error, e.g., using v3 on a v2 server.
00729                 // if we're using v3, try v2.
00730                 //
00731                 if (errorCode == LDAP_PROTOCOL_ERROR && 
00732                    loop->mRawConn->mVersion == nsILDAPConnection::VERSION3) {
00733                     nsCAutoString password;
00734                     loop->mRawConn->mVersion = nsILDAPConnection::VERSION2;
00735                     ldap_set_option(loop->mRawConn->mConnectionHandle,
00736                           LDAP_OPT_PROTOCOL_VERSION, &loop->mRawConn->mVersion);
00737                     nsCOMPtr <nsILDAPOperation> operation = 
00738                       NS_STATIC_CAST(nsILDAPOperation *, 
00739                           NS_STATIC_CAST(nsISupports *, aData));
00740                     // we pass in an empty password to tell the operation that 
00741                     // it should use the cached password.
00742                     //
00743                     rv = operation->SimpleBind(password);
00744                     if (NS_SUCCEEDED(rv)) {
00745                         operationFinished = PR_FALSE;
00746                         // we don't want to notify callers that we're done...
00747                         return PR_TRUE;
00748                     }
00749                 }
00750             }
00751             break;
00752 
00753             case NS_ERROR_LDAP_DECODING_ERROR:
00754                 consoleSvc->LogStringMessage(
00755                     NS_LITERAL_STRING("LDAP: WARNING: decoding error; possible corrupt data received").get());
00756             NS_WARNING("CheckLDAPOperationResult(): ldaperrno = "
00757                            "LDAP_DECODING_ERROR after ldap_result()");
00758             return PR_TRUE;
00759 
00760             case NS_ERROR_OUT_OF_MEMORY:
00761                 // punt and hope things work out better next time around
00762             return PR_TRUE;
00763 
00764             case NS_ERROR_ILLEGAL_VALUE:
00765             case NS_ERROR_UNEXPECTED:
00766             default:
00767                 // shouldn't happen; internal error
00768                 //
00769             NS_ERROR("CheckLDAPOperationResult(): nsLDAPMessage::Init() "
00770                            "returned unexpected value.");
00771 
00772                 // punt and hope things work out better next time around
00773             return PR_TRUE;
00774             }
00775 
00776             // now let the scoping mechanisms provided by nsCOMPtr manage
00777             // the reference for us.
00778             //
00779             msg = rawMsg;
00780 
00781             // invoke the callback on the nsILDAPOperation corresponding to 
00782             // this message
00783             //
00784         rv = loop->mRawConn->InvokeMessageCallback(msgHandle, msg, 
00785                                                     operationFinished);
00786             if (NS_FAILED(rv)) {
00787             NS_ERROR("CheckLDAPOperationResult(): error invoking message"
00788                      " callback");
00789                 // punt and hope things work out better next time around
00790             return PR_TRUE;
00791             }
00792 
00793             break;
00794         }       
00795 
00796     return PR_TRUE;
00797 }
00798 
00799 // for nsIRunnable.  this thread spins in ldap_result() awaiting the next
00800 // message.  once one arrives, it dispatches it to the nsILDAPMessageListener 
00801 // on the main thread.
00802 //
00803 // XXX do all returns from this function need to do thread cleanup?
00804 //
00805 NS_IMETHODIMP
00806 nsLDAPConnectionLoop::Run(void)
00807 {
00808     PR_LOG(gLDAPLogModule, PR_LOG_DEBUG, 
00809            ("nsLDAPConnection::Run() entered\n"));
00810 
00811     // wait for results
00812     //
00813     while(1) {
00814 
00815         // Exit this thread if we no longer have an nsLDAPConnection
00816         // associated with it. We also aquire a lock here, to make sure
00817         // to avoid a possible race condition when the nsLDAPConnection
00818         // is destructed during the call to do_QueryReferent() (since that
00819         // function isn't MT safe).
00820         //
00821         nsresult rv;
00822 
00823         PR_Lock(mLock);
00824         nsCOMPtr<nsILDAPConnection> strongConn = 
00825             do_QueryReferent(mWeakConn, &rv);
00826         PR_Unlock(mLock);
00827 
00828         if (NS_FAILED(rv)) {
00829             mWeakConn = 0;
00830             return NS_OK;
00831         }
00832         // we use a raw connection because we need to call non-interface
00833         // methods
00834         mRawConn = NS_STATIC_CAST(nsLDAPConnection *, 
00835                                   NS_STATIC_CAST(nsILDAPConnection *, 
00836                                                  strongConn.get()));
00837 
00838         // XXX deal with timeouts better
00839         //
00840         NS_ASSERTION(mRawConn->mConnectionHandle, "nsLDAPConnection::Run(): "
00841                      "no connection created.\n");
00842 
00843         // We can't enumerate over mPendingOperations itself, because the
00844         // callback needs to modify mPendingOperations.  So we clone it first,
00845         // and enumerate over the clone.  It kinda sucks that we need to do
00846         // this everytime we poll, but the hashtable will pretty much always
00847         // be small.
00848         //
00849         // only clone if the number of pending operations is non-zero
00850         // otherwise, put the LDAP connection thread to sleep (briefly)
00851         // until there is pending operations..
00852         if (mRawConn->mPendingOperations->Count()) {
00853           nsHashtable *hashtableCopy = mRawConn->mPendingOperations->Clone();
00854           if (hashtableCopy) {
00855             hashtableCopy->Enumerate(CheckLDAPOperationResult, this);
00856             delete hashtableCopy;
00857           } else {
00858             // punt and hope it works next time around
00859             NS_ERROR("nsLDAPConnectionLoop::Run() error cloning hashtable");
00860           }
00861         }
00862         else {
00863           PR_Sleep(PR_MillisecondsToInterval(40));
00864         }
00865     }
00866 
00867     // This will never happen, but here just in case.
00868     //
00869     return NS_OK;
00870 }
00871 
00872 NS_IMETHODIMP
00873 nsLDAPConnection::OnLookupComplete(nsICancelable *aRequest,
00874                                    nsIDNSRecord  *aRecord,
00875                                    nsresult       aStatus)
00876 {    
00877     nsresult rv = NS_OK;
00878 
00879     if (aRecord) {
00880         // Build mResolvedIP list
00881         //
00882         mResolvedIP.Truncate();
00883 
00884         PRInt32 index = 0;
00885         char addrbuf[64];
00886         PRNetAddr addr;
00887 
00888         while (NS_SUCCEEDED(aRecord->GetNextAddr(0, &addr))) {
00889             // We can only use v4 addresses
00890             //
00891             PRBool v4mapped = PR_FALSE;
00892             if (addr.raw.family == PR_AF_INET6)
00893                 v4mapped = PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped);
00894             if (addr.raw.family == PR_AF_INET || v4mapped) {
00895                 // If there are more IPs in the list, we separate them with
00896                 // a space, as supported/used by the LDAP C-SDK.
00897                 //
00898                 if (index++)
00899                     mResolvedIP.Append(' ');
00900 
00901                 // Convert the IPv4 address to a string, and append it to our
00902                 // list of IPs.  Strip leading '::FFFF:' (the IPv4-mapped-IPv6 
00903                 // indicator) if present.
00904                 //
00905                 PR_NetAddrToString(&addr, addrbuf, sizeof(addrbuf));
00906                 if ((addrbuf[0] == ':') && (strlen(addrbuf) > 7))
00907                     mResolvedIP.Append(addrbuf+7);
00908                 else
00909                     mResolvedIP.Append(addrbuf);
00910             }
00911         }
00912     }
00913 
00914     if (NS_FAILED(aStatus)) {
00915         // The DNS service failed, lets pass something reasonable
00916         // back to the listener.
00917         //
00918         switch (aStatus) {
00919         case NS_ERROR_OUT_OF_MEMORY:
00920         case NS_ERROR_UNKNOWN_HOST:
00921         case NS_ERROR_FAILURE:
00922         case NS_ERROR_OFFLINE:
00923             rv = aStatus;
00924             break;
00925 
00926         default:
00927             rv = NS_ERROR_UNEXPECTED;
00928             break;
00929         }
00930     } else if (!mResolvedIP.Length()) {
00931         // We have no host resolved, that is very bad, and should most
00932         // likely have been caught earlier.
00933         //
00934         NS_ERROR("nsLDAPConnection::OnStopLookup(): the resolved IP "
00935                  "string is empty.\n");
00936         
00937         rv = NS_ERROR_UNKNOWN_HOST;
00938     } else {
00939         // We've got the IP(s) for the hostname, now lets setup the
00940         // LDAP connection using this information. Note that if the
00941         // LDAP server returns a referral, the C-SDK will perform a
00942         // new, synchronous DNS lookup, which might hang (but hopefully
00943         // if we've come this far, DNS is working properly).
00944         //
00945         mConnectionHandle = ldap_init(mResolvedIP.get(),
00946                                       mPort == -1 ? LDAP_PORT : mPort);
00947         // Check that we got a proper connection, and if so, setup the
00948         // threading functions for this connection.
00949         //
00950         if ( !mConnectionHandle ) {
00951             rv = NS_ERROR_FAILURE;  // LDAP C SDK API gives no useful error
00952         } else {
00953 #if defined(DEBUG_dmose) || defined(DEBUG_bienvenu)
00954             const int lDebug = 0;
00955             ldap_set_option(mConnectionHandle, LDAP_OPT_DEBUG_LEVEL, &lDebug);
00956 #endif
00957 
00958             // the C SDK currently defaults to v2.  if we're to use v3, 
00959             // tell it so.
00960             //
00961             int version;
00962             switch (mVersion) {
00963             case 2:
00964                 break;
00965             case 3:
00966                 version = LDAP_VERSION3;
00967                 ldap_set_option(mConnectionHandle, LDAP_OPT_PROTOCOL_VERSION, 
00968                                 &version);
00969               break;
00970             default:
00971                 NS_ERROR("nsLDAPConnection::OnLookupComplete(): mVersion"
00972                          " invalid");
00973             }
00974 
00975 #ifdef MOZ_PSM
00976             // This code sets up the current connection to use PSM for SSL
00977             // functionality.  Making this use libssldap instead for
00978             // non-browser user shouldn't be hard.
00979 
00980             extern nsresult nsLDAPInstallSSL(LDAP *ld, const char *aHostName);
00981 
00982             if (mSSL) {
00983                 if (ldap_set_option(mConnectionHandle, LDAP_OPT_SSL,
00984                                     LDAP_OPT_ON) != LDAP_SUCCESS ) {
00985                     NS_ERROR("nsLDAPConnection::OnStopLookup(): Error"
00986                              " configuring connection to use SSL");
00987                     rv = NS_ERROR_UNEXPECTED;
00988                 }
00989 
00990                 rv = nsLDAPInstallSSL(mConnectionHandle, mDNSHost.get());
00991                 if (NS_FAILED(rv)) {
00992                     NS_ERROR("nsLDAPConnection::OnStopLookup(): Error"
00993                              " installing secure LDAP routines for"
00994                              " connection");
00995                 }
00996             }
00997 #endif
00998         }
00999 
01000         // Create a new runnable object, and increment the refcnt. The
01001         // thread will also hold a strong ref to the runnable, but we need
01002         // to make sure it doesn't get destructed until we are done with
01003         // all locking etc. in nsLDAPConnection::Release().
01004         //
01005         mRunnable = new nsLDAPConnectionLoop();
01006         NS_ADDREF(mRunnable);
01007         rv = mRunnable->Init();
01008         if (NS_FAILED(rv)) {
01009             rv = NS_ERROR_OUT_OF_MEMORY;
01010         } else {
01011             // Here we keep a weak reference in the runnable object to the
01012             // nsLDAPConnection ("this"). This avoids the problem where a
01013             // connection can't get destructed because of the new thread
01014             // keeping a strong reference to it. It also helps us know when
01015             // we need to exit the new thread: when we can't convert the weak
01016             // reference to a strong ref, we know that the nsLDAPConnection
01017             // object is gone, and we need to stop the thread running.
01018             //
01019             nsCOMPtr<nsILDAPConnection> conn =
01020                 NS_STATIC_CAST(nsILDAPConnection *, this);
01021 
01022             mRunnable->mWeakConn = do_GetWeakReference(conn);
01023 
01024             // kick off a thread for result listening and marshalling
01025             // XXXdmose - should this be JOINABLE?
01026             //
01027             rv = NS_NewThread(getter_AddRefs(mThread), mRunnable, 0,
01028                           PR_UNJOINABLE_THREAD);
01029             if (NS_FAILED(rv)) {
01030                 rv = NS_ERROR_NOT_AVAILABLE;
01031             }
01032         }
01033     }
01034 
01035     // Drop the DNS request object, we no longer need it, and set the flag
01036     // indicating that DNS has finished.
01037     //
01038     mDNSRequest = 0;
01039     mDNSHost.Truncate();
01040 
01041     // Call the listener, and then we can release our reference to it.
01042     //
01043     mInitListener->OnLDAPInit(this, rv);
01044     mInitListener = 0;
01045 
01046     return rv;
01047 }