Back to index

lightning-sunbird  0.9+nobinonly
nsDNSService2.cpp
Go to the documentation of this file.
00001 /* vim:set ts=4 sw=4 sts=4 et cin: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla.
00016  *
00017  * The Initial Developer of the Original Code is IBM Corporation.
00018  * Portions created by IBM Corporation are Copyright (C) 2003
00019  * IBM Corporation. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   IBM Corp.
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsDNSService2.h"
00039 #include "nsIDNSRecord.h"
00040 #include "nsIDNSListener.h"
00041 #include "nsICancelable.h"
00042 #include "nsIProxyObjectManager.h"
00043 #include "nsIPrefService.h"
00044 #include "nsIPrefBranch.h"
00045 #include "nsIPrefBranch2.h"
00046 #include "nsIServiceManager.h"
00047 #include "nsReadableUtils.h"
00048 #include "nsString.h"
00049 #include "nsAutoLock.h"
00050 #include "nsAutoPtr.h"
00051 #include "nsNetCID.h"
00052 #include "nsNetError.h"
00053 #include "prsystem.h"
00054 #include "prnetdb.h"
00055 #include "prmon.h"
00056 #include "prio.h"
00057 #include "plstr.h"
00058 
00059 static const char kPrefDnsCacheEntries[]    = "network.dnsCacheEntries";
00060 static const char kPrefDnsCacheExpiration[] = "network.dnsCacheExpiration";
00061 static const char kPrefEnableIDN[]          = "network.enableIDN";
00062 static const char kPrefIPv4OnlyDomains[]    = "network.dns.ipv4OnlyDomains";
00063 static const char kPrefDisableIPv6[]        = "network.dns.disableIPv6";
00064 
00065 //-----------------------------------------------------------------------------
00066 
00067 class nsDNSRecord : public nsIDNSRecord
00068 {
00069 public:
00070     NS_DECL_ISUPPORTS
00071     NS_DECL_NSIDNSRECORD
00072 
00073     nsDNSRecord(nsHostRecord *hostRecord)
00074         : mHostRecord(hostRecord)
00075         , mIter(nsnull)
00076         , mDone(PR_FALSE) {}
00077 
00078 private:
00079     virtual ~nsDNSRecord() {}
00080 
00081     nsRefPtr<nsHostRecord>  mHostRecord;
00082     void                   *mIter;
00083     PRBool                  mDone;
00084 };
00085 
00086 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRecord, nsIDNSRecord)
00087 
00088 NS_IMETHODIMP
00089 nsDNSRecord::GetCanonicalName(nsACString &result)
00090 {
00091     // this method should only be called if we have a CNAME
00092     NS_ENSURE_TRUE(mHostRecord->flags & nsHostResolver::RES_CANON_NAME,
00093                    NS_ERROR_NOT_AVAILABLE);
00094 
00095     // if the record is for an IP address literal, then the canonical
00096     // host name is the IP address literal.
00097     const char *cname;
00098     if (mHostRecord->addr_info)
00099         cname = PR_GetCanonNameFromAddrInfo(mHostRecord->addr_info);
00100     else
00101         cname = mHostRecord->host;
00102     result.Assign(cname);
00103     return NS_OK;
00104 }
00105 
00106 NS_IMETHODIMP
00107 nsDNSRecord::GetNextAddr(PRUint16 port, PRNetAddr *addr)
00108 {
00109     // not a programming error to poke the DNS record when it has no more
00110     // entries.  just fail without any debug warnings.  this enables consumers
00111     // to enumerate the DNS record without calling HasMore.
00112     if (mDone)
00113         return NS_ERROR_NOT_AVAILABLE;
00114 
00115     if (mHostRecord->addr_info) {
00116         mIter = PR_EnumerateAddrInfo(mIter, mHostRecord->addr_info, port, addr);
00117         if (!mIter)
00118             return NS_ERROR_NOT_AVAILABLE;
00119     }
00120     else {
00121         // This should never be null (but see bug 290190) :-(
00122         NS_ENSURE_STATE(mHostRecord->addr);
00123 
00124         mIter = nsnull; // no iterations
00125         memcpy(addr, mHostRecord->addr, sizeof(PRNetAddr));
00126         // set given port
00127         port = PR_htons(port);
00128         if (addr->raw.family == PR_AF_INET)
00129             addr->inet.port = port;
00130         else
00131             addr->ipv6.port = port;
00132     }
00133         
00134     mDone = !mIter;
00135     return NS_OK; 
00136 }
00137 
00138 NS_IMETHODIMP
00139 nsDNSRecord::GetNextAddrAsString(nsACString &result)
00140 {
00141     PRNetAddr addr;
00142     nsresult rv = GetNextAddr(0, &addr);
00143     if (NS_FAILED(rv)) return rv;
00144 
00145     char buf[64];
00146     if (PR_NetAddrToString(&addr, buf, sizeof(buf)) == PR_SUCCESS) {
00147         result.Assign(buf);
00148         return NS_OK;
00149     }
00150     NS_ERROR("PR_NetAddrToString failed unexpectedly");
00151     return NS_ERROR_FAILURE; // conversion failed for some reason
00152 }
00153 
00154 NS_IMETHODIMP
00155 nsDNSRecord::HasMore(PRBool *result)
00156 {
00157     if (mDone)
00158         *result = PR_FALSE;
00159     else {
00160         // unfortunately, NSPR does not provide a way for us to determine if
00161         // there is another address other than to simply get the next address.
00162         void *iterCopy = mIter;
00163         PRNetAddr addr;
00164         *result = NS_SUCCEEDED(GetNextAddr(0, &addr));
00165         mIter = iterCopy; // backup iterator
00166         mDone = PR_FALSE;
00167     }
00168     return NS_OK;
00169 }
00170 
00171 NS_IMETHODIMP
00172 nsDNSRecord::Rewind()
00173 {
00174     mIter = nsnull;
00175     mDone = PR_FALSE;
00176     return NS_OK;
00177 }
00178 
00179 //-----------------------------------------------------------------------------
00180 
00181 class nsDNSAsyncRequest : public nsResolveHostCallback
00182                         , public nsICancelable
00183 {
00184 public:
00185     NS_DECL_ISUPPORTS
00186     NS_DECL_NSICANCELABLE
00187 
00188     nsDNSAsyncRequest(nsHostResolver   *res,
00189                       const nsACString &host,
00190                       nsIDNSListener   *listener,
00191                       PRUint16          flags,
00192                       PRUint16          af)
00193         : mResolver(res)
00194         , mHost(host)
00195         , mListener(listener)
00196         , mFlags(flags)
00197         , mAF(af) {}
00198     ~nsDNSAsyncRequest() {}
00199 
00200     void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
00201 
00202     nsRefPtr<nsHostResolver> mResolver;
00203     nsCString                mHost; // hostname we're resolving
00204     nsCOMPtr<nsIDNSListener> mListener;
00205     PRUint16                 mFlags;
00206     PRUint16                 mAF;
00207 };
00208 
00209 void
00210 nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver,
00211                                     nsHostRecord   *hostRecord,
00212                                     nsresult        status)
00213 {
00214     // need to have an owning ref when we issue the callback to enable
00215     // the caller to be able to addref/release multiple times without
00216     // destroying the record prematurely.
00217     nsCOMPtr<nsIDNSRecord> rec;
00218     if (NS_SUCCEEDED(status)) {
00219         NS_ASSERTION(hostRecord, "no host record");
00220         rec = new nsDNSRecord(hostRecord);
00221         if (!rec)
00222             status = NS_ERROR_OUT_OF_MEMORY;
00223     }
00224 
00225     mListener->OnLookupComplete(this, rec, status);
00226     mListener = nsnull;
00227 
00228     // release the reference to ourselves that was added before we were
00229     // handed off to the host resolver.
00230     NS_RELEASE_THIS();
00231 }
00232 
00233 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest, nsICancelable)
00234 
00235 NS_IMETHODIMP
00236 nsDNSAsyncRequest::Cancel(nsresult reason)
00237 {
00238     NS_ENSURE_ARG(NS_FAILED(reason));
00239     mResolver->DetachCallback(mHost.get(), mFlags, mAF, this, reason);
00240     return NS_OK;
00241 }
00242 
00243 //-----------------------------------------------------------------------------
00244 
00245 class nsDNSSyncRequest : public nsResolveHostCallback
00246 {
00247 public:
00248     nsDNSSyncRequest(PRMonitor *mon)
00249         : mDone(PR_FALSE)
00250         , mStatus(NS_OK)
00251         , mMonitor(mon) {}
00252     virtual ~nsDNSSyncRequest() {}
00253 
00254     void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
00255 
00256     PRBool                 mDone;
00257     nsresult               mStatus;
00258     nsRefPtr<nsHostRecord> mHostRecord;
00259 
00260 private:
00261     PRMonitor             *mMonitor;
00262 };
00263 
00264 void
00265 nsDNSSyncRequest::OnLookupComplete(nsHostResolver *resolver,
00266                                    nsHostRecord   *hostRecord,
00267                                    nsresult        status)
00268 {
00269     // store results, and wake up nsDNSService::Resolve to process results.
00270     PR_EnterMonitor(mMonitor);
00271     mDone = PR_TRUE;
00272     mStatus = status;
00273     mHostRecord = hostRecord;
00274     PR_Notify(mMonitor);
00275     PR_ExitMonitor(mMonitor);
00276 }
00277 
00278 //-----------------------------------------------------------------------------
00279 
00280 nsDNSService::nsDNSService()
00281     : mLock(nsnull)
00282 {
00283 }
00284 
00285 nsDNSService::~nsDNSService()
00286 {
00287     if (mLock)
00288         PR_DestroyLock(mLock);
00289 }
00290 
00291 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService, nsIDNSService, nsPIDNSService,
00292                               nsIObserver)
00293 
00294 NS_IMETHODIMP
00295 nsDNSService::Init()
00296 {
00297     NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
00298 
00299     PRBool firstTime = (mLock == nsnull);
00300 
00301     // prefs
00302     PRUint32 maxCacheEntries  = 20;
00303     PRUint32 maxCacheLifetime = 1; // minutes
00304     PRBool   enableIDN        = PR_TRUE;
00305     PRBool   disableIPv6      = PR_FALSE;
00306     nsAdoptingCString ipv4OnlyDomains;
00307 
00308     // read prefs
00309     nsCOMPtr<nsIPrefBranch2> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00310     if (prefs) {
00311         PRInt32 val;
00312         if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheEntries, &val)))
00313             maxCacheEntries = (PRUint32) val;
00314         if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheExpiration, &val)))
00315             maxCacheLifetime = val / 60; // convert from seconds to minutes
00316 
00317         // ASSUMPTION: pref branch does not modify out params on failure
00318         prefs->GetBoolPref(kPrefEnableIDN, &enableIDN);
00319         prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6);
00320         prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains));
00321     }
00322 
00323     if (firstTime) {
00324         mLock = PR_NewLock();
00325         if (!mLock)
00326             return NS_ERROR_OUT_OF_MEMORY;
00327 
00328         // register as prefs observer
00329         prefs->AddObserver(kPrefDnsCacheEntries, this, PR_FALSE);
00330         prefs->AddObserver(kPrefDnsCacheExpiration, this, PR_FALSE);
00331         prefs->AddObserver(kPrefEnableIDN, this, PR_FALSE);
00332         prefs->AddObserver(kPrefIPv4OnlyDomains, this, PR_FALSE);
00333         prefs->AddObserver(kPrefDisableIPv6, this, PR_FALSE);
00334     }
00335 
00336     // we have to null out mIDN since we might be getting re-initialized
00337     // as a result of a pref change.
00338     nsCOMPtr<nsIIDNService> idn;
00339     if (enableIDN)
00340         idn = do_GetService(NS_IDNSERVICE_CONTRACTID);
00341 
00342     nsRefPtr<nsHostResolver> res;
00343     nsresult rv = nsHostResolver::Create(maxCacheEntries,
00344                                          maxCacheLifetime,
00345                                          getter_AddRefs(res));
00346     if (NS_SUCCEEDED(rv)) {
00347         // now, set all of our member variables while holding the lock
00348         nsAutoLock lock(mLock);
00349         mResolver = res;
00350         mIDN = idn;
00351         mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership
00352         mDisableIPv6 = disableIPv6;
00353     }
00354 
00355     return rv;
00356 }
00357 
00358 NS_IMETHODIMP
00359 nsDNSService::Shutdown()
00360 {
00361     nsRefPtr<nsHostResolver> res;
00362     {
00363         nsAutoLock lock(mLock);
00364         res = mResolver;
00365         mResolver = nsnull;
00366     }
00367     if (res)
00368         res->Shutdown();
00369     return NS_OK;
00370 }
00371 
00372 NS_IMETHODIMP
00373 nsDNSService::AsyncResolve(const nsACString &hostname,
00374                            PRUint32          flags,
00375                            nsIDNSListener   *listener,
00376                            nsIEventTarget   *eventTarget,
00377                            nsICancelable   **result)
00378 {
00379     // grab reference to global host resolver and IDN service.  beware
00380     // simultaneous shutdown!!
00381     nsRefPtr<nsHostResolver> res;
00382     nsCOMPtr<nsIIDNService> idn;
00383     {
00384         nsAutoLock lock(mLock);
00385         res = mResolver;
00386         idn = mIDN;
00387     }
00388     NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
00389 
00390     const nsACString *hostPtr = &hostname;
00391 
00392     nsresult rv;
00393     nsCAutoString hostACE;
00394     if (idn && !IsASCII(hostname)) {
00395         if (NS_SUCCEEDED(idn->ConvertUTF8toACE(hostname, hostACE)))
00396             hostPtr = &hostACE;
00397     }
00398 
00399     nsCOMPtr<nsIDNSListener> listenerProxy;
00400     nsCOMPtr<nsIEventQueue> eventQ = do_QueryInterface(eventTarget);
00401     // TODO(darin): make XPCOM proxies support any nsIEventTarget impl
00402     if (eventQ) {
00403         rv = NS_GetProxyForObject(eventQ,
00404                                   NS_GET_IID(nsIDNSListener),
00405                                   listener,
00406                                   PROXY_ASYNC | PROXY_ALWAYS,
00407                                   getter_AddRefs(listenerProxy));
00408         if (NS_FAILED(rv)) return rv;
00409         listener = listenerProxy;
00410     }
00411 
00412     PRUint16 af = GetAFForLookup(*hostPtr);
00413 
00414     nsDNSAsyncRequest *req =
00415             new nsDNSAsyncRequest(res, *hostPtr, listener, flags, af);
00416     if (!req)
00417         return NS_ERROR_OUT_OF_MEMORY;
00418     NS_ADDREF(*result = req);
00419 
00420     // addref for resolver; will be released when OnLookupComplete is called.
00421     NS_ADDREF(req);
00422     rv = res->ResolveHost(req->mHost.get(), flags, af, req);
00423     if (NS_FAILED(rv)) {
00424         NS_RELEASE(req);
00425         NS_RELEASE(*result);
00426     }
00427     return rv;
00428 }
00429 
00430 NS_IMETHODIMP
00431 nsDNSService::Resolve(const nsACString &hostname,
00432                       PRUint32          flags,
00433                       nsIDNSRecord    **result)
00434 {
00435     // grab reference to global host resolver and IDN service.  beware
00436     // simultaneous shutdown!!
00437     nsRefPtr<nsHostResolver> res;
00438     nsCOMPtr<nsIIDNService> idn;
00439     {
00440         nsAutoLock lock(mLock);
00441         res = mResolver;
00442         idn = mIDN;
00443     }
00444     NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
00445 
00446     const nsACString *hostPtr = &hostname;
00447 
00448     nsresult rv;
00449     nsCAutoString hostACE;
00450     if (idn && !IsASCII(hostname)) {
00451         if (NS_SUCCEEDED(idn->ConvertUTF8toACE(hostname, hostACE)))
00452             hostPtr = &hostACE;
00453     }
00454 
00455     //
00456     // sync resolve: since the host resolver only works asynchronously, we need
00457     // to use a mutex and a condvar to wait for the result.  however, since the
00458     // result may be in the resolvers cache, we might get called back recursively
00459     // on the same thread.  so, our mutex needs to be re-entrant.  inotherwords,
00460     // we need to use a monitor! ;-)
00461     //
00462     
00463     PRMonitor *mon = PR_NewMonitor();
00464     if (!mon)
00465         return NS_ERROR_OUT_OF_MEMORY;
00466 
00467     PR_EnterMonitor(mon);
00468     nsDNSSyncRequest syncReq(mon);
00469 
00470     PRUint16 af = GetAFForLookup(*hostPtr);
00471 
00472     rv = res->ResolveHost(PromiseFlatCString(*hostPtr).get(), flags, af, &syncReq);
00473     if (NS_SUCCEEDED(rv)) {
00474         // wait for result
00475         while (!syncReq.mDone)
00476             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
00477 
00478         if (NS_FAILED(syncReq.mStatus))
00479             rv = syncReq.mStatus;
00480         else {
00481             NS_ASSERTION(syncReq.mHostRecord, "no host record");
00482             nsDNSRecord *rec = new nsDNSRecord(syncReq.mHostRecord);
00483             if (!rec)
00484                 rv = NS_ERROR_OUT_OF_MEMORY;
00485             else
00486                 NS_ADDREF(*result = rec);
00487         }
00488     }
00489 
00490     PR_ExitMonitor(mon);
00491     PR_DestroyMonitor(mon);
00492     return rv;
00493 }
00494 
00495 NS_IMETHODIMP
00496 nsDNSService::GetMyHostName(nsACString &result)
00497 {
00498     char name[100];
00499     if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) {
00500         result = name;
00501         return NS_OK;
00502     }
00503     return NS_ERROR_FAILURE;
00504 }
00505 
00506 NS_IMETHODIMP
00507 nsDNSService::Observe(nsISupports *subject, const char *topic, const PRUnichar *data)
00508 {
00509     // we are only getting called if a preference has changed. 
00510     NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
00511         "unexpected observe call");
00512 
00513     //
00514     // Shutdown and this function are both only called on the UI thread, so we don't
00515     // have to worry about mResolver being cleared out from under us.
00516     //
00517     // NOTE Shutting down and reinitializing the service like this is obviously
00518     // suboptimal if Observe gets called several times in a row, but we don't
00519     // expect that to be the case.
00520     //
00521 
00522     if (mResolver) {
00523         Shutdown();
00524         Init();
00525     }
00526     return NS_OK;
00527 }
00528 
00529 PRUint16
00530 nsDNSService::GetAFForLookup(const nsACString &host)
00531 {
00532     if (mDisableIPv6)
00533         return PR_AF_INET;
00534 
00535     nsAutoLock lock(mLock);
00536 
00537     PRUint16 af = PR_AF_UNSPEC;
00538 
00539     if (!mIPv4OnlyDomains.IsEmpty()) {
00540         const char *domain, *domainEnd, *end;
00541         PRUint32 hostLen, domainLen;
00542 
00543         // see if host is in one of the IPv4-only domains
00544         domain = mIPv4OnlyDomains.BeginReading();
00545         domainEnd = mIPv4OnlyDomains.EndReading(); 
00546 
00547         nsACString::const_iterator hostStart;
00548         host.BeginReading(hostStart);
00549         hostLen = host.Length();
00550 
00551         do {
00552             // skip any whitespace
00553             while (*domain == ' ' || *domain == '\t')
00554                 ++domain;
00555 
00556             // find end of this domain in the string
00557             end = strchr(domain, ',');
00558             if (!end)
00559                 end = domainEnd;
00560 
00561             // to see if the hostname is in the domain, check if the domain
00562             // matches the end of the hostname.
00563             domainLen = end - domain;
00564             if (domainLen && hostLen >= domainLen) {
00565                 const char *hostTail = hostStart.get() + hostLen - domainLen;
00566                 if (PL_strncasecmp(domain, hostTail, domainLen) == 0) {
00567                     // now, make sure either that the hostname is a direct match or
00568                     // that the hostname begins with a dot.
00569                     if (hostLen == domainLen ||
00570                             *hostTail == '.' || *(hostTail - 1) == '.') {
00571                         af = PR_AF_INET;
00572                         break;
00573                     }
00574                 }
00575             }
00576 
00577             domain = end + 1;
00578         } while (*end);
00579     }
00580 
00581     return af;
00582 }