reSIProcate/rutil  9694
AresDns.cxx
Go to the documentation of this file.
00001 #ifdef HAVE_CONFIG_H
00002 #include "config.h"
00003 #endif
00004 
00005 #if !defined(WIN32)
00006 #include <sys/types.h>
00007 #endif
00008 #include <time.h>
00009 
00010 #include "rutil/dns/AresDns.hxx"
00011 #include "rutil/GenericIPAddress.hxx"
00012 
00013 #include "AresCompat.hxx"
00014 #if !defined(USE_CARES)
00015 #include "ares_private.h"
00016 #endif
00017 
00018 #include "rutil/Logger.hxx"
00019 #include "rutil/DnsUtil.hxx"
00020 #include "rutil/WinLeakCheck.hxx"
00021 #include "rutil/FdPoll.hxx"
00022 
00023 #if !defined(WIN32)
00024 #if !defined(__CYGWIN__)
00025 #include <arpa/nameser.h>
00026 #endif
00027 #endif
00028 
00029 using namespace resip;
00030 
00031 #define RESIPROCATE_SUBSYSTEM resip::Subsystem::DNS
00032 
00033 /**********************************************************************
00034  *
00035  *              class AresDnsPollItem
00036  *
00037  * This is callback class used for epoll-based systems.
00038  *
00039  **********************************************************************/
00040 
00041 #ifndef USE_CARES
00042 namespace resip
00043 {
00044 
00045 class AresDnsPollItem : public FdPollItemBase
00046 {
00047   public:
00048    AresDnsPollItem(FdPollGrp *grp, int fd, AresDns& aresObj,
00049      ares_channel chan, int server_idx)
00050      : FdPollItemBase(grp, fd, FPEM_Read), mAres(aresObj),
00051        mChannel(chan), mFd(fd), mServerIdx(server_idx)
00052    {
00053    }
00054 
00055    virtual void processPollEvent(FdPollEventMask mask);
00056    void resetPollGrp(FdPollGrp *grp)
00057    {
00058       mPollGrp->delPollItem(mPollHandle);
00059       mPollGrp = grp;
00060       mPollHandle = mPollGrp->addPollItem(mFd, FPEM_Read, this);
00061    }
00062 
00063    AresDns&     mAres;
00064    ares_channel mChannel;
00065    int mFd;
00066    int mServerIdx;
00067 
00068    static void socket_poll_cb(void *cb_data,
00069                               ares_channel channel, int server_idx,
00070                               int fd, ares_poll_action_t act);
00071 };
00072 
00073 };
00074 
00075 void
00076 AresDnsPollItem::processPollEvent(FdPollEventMask mask)
00077 {
00078    assert( (mask&(FPEM_Read|FPEM_Write))!= 0 );
00079 
00080    time_t nowSecs;
00081    time(&nowSecs);      
00082 
00083    ares_process_poll(mChannel, mServerIdx,
00084      (mask&FPEM_Read)?(int)mPollSocket:-1, (mask&FPEM_Write)?(int)mPollSocket:-1,
00085      nowSecs);
00086 }
00087 
00093 void
00094 AresDnsPollItem::socket_poll_cb(void *cb_data,
00095         ares_channel channel, int server_idx,
00096         int fd, ares_poll_action_t act)
00097 {
00098    AresDns *ares = static_cast<AresDns*>(cb_data);
00099    //assert( ares );
00100    FdPollGrp *grp = ares->mPollGrp;
00101    //assert( grp );
00102    AresDnsPollItem *olditem = ares->mPollItems.at(server_idx);
00103    if ( olditem )
00104    {
00105       assert( olditem->mChannel==channel );
00106       assert( olditem->mServerIdx==server_idx );
00107    }
00108    switch ( act )
00109    {
00110    case ARES_POLLACTION_OPEN:
00111       assert( olditem==NULL );
00112       assert( fd!=INVALID_SOCKET );
00113       ares->mPollItems[server_idx] = new AresDnsPollItem( grp, fd, *ares, channel, server_idx);
00114       break;
00115    case ARES_POLLACTION_CLOSE:
00116       assert( olditem );
00117       ares->mPollItems[server_idx] = NULL;
00118       delete olditem;   // destructor removes from poll
00119       break;
00120    case ARES_POLLACTION_WRITEON:
00121       assert( olditem );
00122       grp->modPollItem(olditem->mPollHandle, FPEM_Read|FPEM_Write);
00123       break;
00124    case ARES_POLLACTION_WRITEOFF:
00125       assert( olditem );
00126       grp->modPollItem(olditem->mPollHandle, FPEM_Read);
00127       break;
00128    default:
00129       assert( 0 );
00130    }
00131 }
00132 
00133 #endif
00134 
00135 /**********************************************************************
00136  *
00137  *              class AresDns
00138  *
00139  **********************************************************************/
00140 
00141 volatile bool AresDns::mHostFileLookupOnlyMode = false;
00142 
00143 void
00144 AresDns::setPollGrp(FdPollGrp *grp)
00145 {
00146 #ifdef USE_CARES
00147    if(mPollGrp)
00148    {
00149       mPollGrp->unregisterFdSetIOObserver(*this);
00150    }
00151    mPollGrp=grp;
00152    if(mPollGrp)
00153    {
00154       mPollGrp->registerFdSetIOObserver(*this);
00155    }
00156 #else
00157    for(std::vector<AresDnsPollItem*>::iterator i=mPollItems.begin();
00158          i!=mPollItems.end(); ++i)
00159    {
00160       if(*i)
00161       {
00162          (*i)->resetPollGrp(grp);
00163       }
00164    }
00165    mPollGrp = grp;
00166 #endif
00167 }
00168 
00169 int
00170 AresDns::init(const std::vector<GenericIPAddress>& additionalNameservers,
00171               AfterSocketCreationFuncPtr socketfunc,
00172               int timeout,
00173               int tries,
00174               unsigned int features)
00175 {
00176    mAdditionalNameservers = additionalNameservers;
00177    mFeatures = features;
00178 
00179    int ret = internalInit(additionalNameservers,
00180                           socketfunc,
00181                           features,
00182                           &mChannel,
00183                           timeout,
00184                           tries);
00185 
00186    if (ret != Success)
00187       return ret;
00188 
00189 #ifdef WIN32
00190       // For windows OSs it is uncommon to run a local DNS server.  Therefor if there
00191       // are no defined DNS servers in windows networking and ARES just returned the
00192       // loopback address (ie. default localhost server / named)
00193       // then put resip DNS resolution into hostfile lookup only mode
00194       if(mChannel->nservers == 1 &&
00195          mChannel->servers[0].default_localhost_server)
00196       {
00197          // enable hostfile only lookup mode
00198          mHostFileLookupOnlyMode = true;
00199       }
00200       else
00201       {
00202          // disable hostfile only lookup mode
00203          mHostFileLookupOnlyMode = false;
00204       }
00205 #endif
00206 
00207    return Success;
00208 }
00209 
00210 int
00211 AresDns::internalInit(const std::vector<GenericIPAddress>& additionalNameservers,
00212                       AfterSocketCreationFuncPtr socketfunc,
00213                       unsigned int features,
00214                       ares_channeldata** channel,
00215                       int timeout,
00216                       int tries
00217 )
00218 {
00219    if(*channel)
00220    {
00221 #if defined(USE_ARES)
00222       ares_destroy_suppress_callbacks(*channel);
00223 #elif defined(USE_CARES)
00224       // Callbacks will be supressed by looking for the ARES_EDESTRUCTION
00225       // sentinel status
00226       ares_destroy(*channel);
00227 #endif
00228       *channel = 0;
00229    }
00230 
00231 #if defined(USE_ARES)
00232 
00233 #ifdef USE_IPV6
00234    int requiredCap = ARES_CAP_IPV6;
00235 #else
00236    int requiredCap = 0;
00237 #endif
00238 
00239    // Only the contrib/ares has this function
00240    int cap = ares_capabilities(requiredCap);
00241    if (cap != requiredCap)
00242    {
00243       ErrLog (<< "Build mismatch (ipv4/ipv6) problem in ares library"); // !dcm!
00244       return BuildMismatch;
00245    }
00246 #endif
00247 
00248    int status;
00249    ares_options opt;
00250    int optmask = 0;
00251 
00252    memset(&opt, '\0', sizeof(opt));
00253 
00254 #if defined(USE_ARES)
00255    // TODO: What is this and how does it map to c-ares?
00256    if ((features & ExternalDns::TryServersOfNextNetworkUponRcode3))
00257    {
00258       optmask |= ARES_OPT_FLAGS;
00259       opt.flags |= ARES_FLAG_TRY_NEXT_SERVER_ON_RCODE3;
00260    }
00261 #endif
00262 
00263 #if defined(USE_CARES)
00264    // In c-ares, we can actually set the timeout and retries via the API
00265    if (timeout > 0)
00266    {
00267       opt.timeout = timeout;
00268       optmask |= ARES_OPT_TIMEOUT;
00269    }
00270 
00271    if (tries > 0)
00272    {
00273       opt.tries = tries;
00274       optmask |= ARES_OPT_TRIES;
00275    }
00276 #endif
00277 
00278    if (additionalNameservers.empty())
00279    {
00280 #if defined(USE_ARES)
00281       status = ares_init_options_with_socket_function(channel, &opt, optmask, socketfunc);
00282 #elif defined(USE_CARES)
00283       // TODO: Does the socket function matter?
00284       status = ares_init_options(channel, &opt, optmask);
00285 #endif
00286    }
00287    else
00288    {
00289       optmask |= ARES_OPT_SERVERS;
00290       opt.nservers = (int)additionalNameservers.size();
00291 
00292 #if defined(USE_IPV6) && defined(USE_ARES)
00293       // With contrib/ares, you can configure IPv6 addresses for the
00294       // nameservers themselves.
00295       opt.servers = new multiFamilyAddr[additionalNameservers.size()];
00296       for (size_t i =0; i < additionalNameservers.size(); i++)
00297       {
00298          if (additionalNameservers[i].isVersion4())
00299          {
00300             opt.servers[i].family = AF_INET;
00301             opt.servers[i].addr = additionalNameservers[i].v4Address.sin_addr;
00302          }
00303          else
00304          {
00305             opt.servers[i].family = AF_INET6;
00306             opt.servers[i].addr6 = additionalNameservers[i].v6Address.sin6_addr;
00307          }
00308       }
00309 #else
00310       // If we're only supporting IPv4 or we are using c-ares, we can't
00311       // support additional nameservers that are IPv6 right now.
00312       opt.servers = new in_addr[additionalNameservers.size()];
00313       for (size_t i =0; i < additionalNameservers.size(); i++)
00314       {
00315          if(additionalNameservers[i].isVersion4())
00316          {
00317             opt.servers[i] = additionalNameservers[i].v4Address.sin_addr;
00318          }
00319          else
00320          {
00321 #if defined(USE_CARES)
00322            WarningLog (<< "Ignoring non-IPv4 additional name server (not yet supported with c-ares)");
00323 #elif defined(USE_ARES)
00324            WarningLog (<< "Ignoring non-IPv4 additional name server (IPv6 support was not enabled)");
00325 #endif
00326          }
00327       }
00328 #endif
00329 
00330 #if defined(USE_ARES)
00331       status = ares_init_options_with_socket_function(channel, &opt, optmask, socketfunc);
00332 #elif defined(USE_CARES)
00333       // TODO: Does the socket function matter?
00334       status = ares_init_options(channel, &opt, optmask);
00335 #endif
00336 
00337       delete [] opt.servers;
00338       opt.servers = 0;
00339    }
00340    if (status != ARES_SUCCESS)
00341    {
00342       ErrLog (<< "Failed to initialize DNS library (status=" << status << ")");
00343       return status;
00344    }
00345    else
00346    {
00347 
00348 #if defined(USE_ARES)
00349 
00350       InfoLog(<< "DNS initialization: found  " << (*channel)->nservers << " name servers");
00351       for (int i = 0; i < (*channel)->nservers; ++i)
00352       {
00353 #ifdef USE_IPV6
00354          if((*channel)->servers[i].family == AF_INET6) 
00355          {
00356             InfoLog(<< " name server: " << DnsUtil::inet_ntop((*channel)->servers[i].addr6));
00357          } 
00358          else
00359 #endif
00360          {
00361             InfoLog(<< " name server: " << DnsUtil::inet_ntop((*channel)->servers[i].addr));
00362          }
00363       }
00364 
00365       // In ares, we must manipulate these directly
00366       if (timeout > 0)
00367       {
00368          mChannel->timeout = timeout;
00369       }
00370 
00371       if (tries > 0)
00372       {
00373          mChannel->tries = tries;
00374       }
00375 
00376 #ifndef USE_CARES
00377       if ( mPollGrp )
00378       {
00379          // expand vector to hold {nservers} and init to NULL
00380          mPollItems.insert( mPollItems.end(), (*channel)->nservers, (AresDnsPollItem*)0);
00381          // tell ares to let us know when things change
00382          ares_process_set_poll_cb(mChannel, AresDnsPollItem::socket_poll_cb, this);
00383       }
00384 #endif
00385 
00386 #elif defined(USE_CARES)
00387       {
00388          // Log which version of c-ares we're using
00389          InfoLog(<< "DNS initialization: using c-ares v"
00390                  << ::ares_version(NULL));
00391 
00392          // Ask for the current configuration so we can print the servers found
00393          struct ares_options options;
00394          std::memset(&options, 0, sizeof(options));
00395          int ignored;
00396          if(ares_save_options(*channel, &options, &ignored) == ARES_SUCCESS)
00397          {
00398             InfoLog(<< "DNS initialization: found "
00399                     << options.nservers << " name servers");
00400 
00401             // Log them all
00402             for (int i = 0; i < options.nservers; ++i)
00403             {
00404                InfoLog(<< " name server: "
00405                        << DnsUtil::inet_ntop(options.servers[i]));
00406             }
00407             ares_destroy_options(&options);
00408          }
00409       }
00410 #endif
00411 
00412       return Success;
00413    }
00414 }
00415 
00416 bool AresDns::checkDnsChange()
00417 {
00418    // We must return 'true' if there are changes in the list of DNS servers
00419    struct ares_channeldata* channel = 0;
00420    bool bRet = false;
00421    int result = internalInit(mAdditionalNameservers, 0, mFeatures, &channel);
00422    if(result != Success || channel == 0)
00423    {
00424       // It has changed because it failed, I suppose
00425       InfoLog(<< " DNS server list changed");
00426       return true;
00427    }
00428 
00429 #if defined(USE_ARES)
00430    {
00431       // Compare the two lists.  Are they different sizes?
00432       if(mChannel->nservers != channel->nservers)
00433       {
00434          // Yes, so they're different
00435          bRet = true;
00436       }
00437       else
00438       {
00439          // Compare them one-by-one
00440          for (int i = 0; i < mChannel->nservers; ++i)
00441          {
00442             if (mChannel->servers[i].addr.s_addr
00443                 != channel->servers[i].addr.s_addr)
00444             {
00445                bRet = true;
00446                break;
00447             }
00448          }
00449       }
00450 
00451       // Destroy the secondary configuration we read
00452       ares_destroy_suppress_callbacks(channel);
00453    }
00454 #elif defined(USE_CARES)
00455    {
00456       // Get the options, including the server list, from the old and the
00457       // current (i.e. just read) configuration.
00458       struct ares_options old;
00459       struct ares_options updated;
00460       std::memset(&old, 0, sizeof(old));
00461       std::memset(&updated, 0, sizeof(updated));
00462       int ignored;
00463 
00464       // Can we get the configuration?
00465       if(ares_save_options(mChannel, &old, &ignored) != ARES_SUCCESS
00466          || ares_save_options(channel, &updated, &ignored) != ARES_SUCCESS)
00467       {
00468          // It failed, so call it different
00469          bRet = true;
00470       }
00471       else
00472       {
00473          // Compare the two lists.  Are they different sizes?
00474          if(old.nservers != updated.nservers)
00475          {
00476             // Yes, so they're different
00477             bRet = true;
00478          }
00479          else
00480          {
00481             // Compare them one-by-one
00482             for (int i = 0; i < old.nservers; ++i)
00483             {
00484                if (old.servers[i].s_addr != updated.servers[i].s_addr)
00485                {
00486                   bRet = true;
00487                   break;
00488                }
00489             }
00490          }
00491 
00492          // Free any ares_options contents we have created.
00493          ares_destroy_options(&old);
00494          ares_destroy_options(&updated);
00495       }
00496 
00497       // Destroy the secondary configuration we read
00498       ares_destroy(channel);
00499    }
00500 #endif
00501 
00502    // Report on the results
00503    if(!bRet)
00504    {
00505       InfoLog(<< " No changes in DNS server list");
00506    }
00507    else
00508    {
00509       InfoLog(<< " DNS server list changed");
00510    }
00511 
00512    return bRet;
00513 }
00514 
00515 AresDns::~AresDns()
00516 {
00517 #if defined(USE_ARES)
00518    ares_destroy_suppress_callbacks(mChannel);
00519 #elif defined(USE_CARES)
00520    ares_destroy(mChannel);
00521 #endif
00522 }
00523 
00524 bool AresDns::hostFileLookup(const char* target, in_addr &addr)
00525 {
00526    assert(target);
00527 
00528    hostent *hostdata = 0;
00529 
00530    // Look this up
00531    int status =
00532 #if defined(USE_ARES)
00533      hostfile_lookup(target, &hostdata)
00534 #elif defined(USE_CARES)
00535      ares_gethostbyname_file(mChannel, target, AF_INET, &hostdata)
00536 #endif
00537      ;
00538 
00539    if (status != ARES_SUCCESS)
00540    {
00541       DebugLog(<< "hostFileLookup failed for " << target);
00542       return false;
00543    }
00544    sockaddr_in saddr;
00545    memset(&saddr,0,sizeof(saddr));  /* Initialize sockaddr fields. */
00546    saddr.sin_family = AF_INET;
00547    memcpy((char *)&(saddr.sin_addr.s_addr),(char *)hostdata->h_addr_list[0], (size_t)hostdata->h_length);
00548    addr = saddr.sin_addr;
00549 #if defined(USE_ARES)
00550    // for resip-ares, the hostdata (and its contents) is dynamically allocated
00551    ares_free_hostent(hostdata);
00552 #endif
00553 
00554    DebugLog(<< "hostFileLookup succeeded for " << target);
00555    return true;
00556 }
00557 
00558 ExternalDnsHandler*
00559 AresDns::getHandler(void* arg)
00560 {
00561    Payload* p = reinterpret_cast<Payload*>(arg);
00562    ExternalDnsHandler *thisp = reinterpret_cast<ExternalDnsHandler*>(p->first);
00563    return thisp;
00564 }
00565 
00566 ExternalDnsRawResult
00567 AresDns::makeRawResult(void *arg, int status, unsigned char *abuf, int alen)
00568 {
00569    Payload* p = reinterpret_cast<Payload*>(arg);
00570    void* userArg = reinterpret_cast<void*>(p->second);
00571 
00572    if (status != ARES_SUCCESS)
00573    {
00574       return ExternalDnsRawResult(status, abuf, alen, userArg);
00575    }
00576    else
00577    {
00578       return ExternalDnsRawResult(abuf, alen, userArg);
00579    }
00580 }
00581 
00582 unsigned int
00583 AresDns::getTimeTillNextProcessMS()
00584 {
00585    struct timeval tv;
00586    ares_timeout(mChannel, NULL, &tv);
00587    return tv.tv_sec*1000 + tv.tv_usec / 1000;
00588 }
00589 
00590 void
00591 AresDns::buildFdSet(fd_set& read, fd_set& write, int& size)
00592 {
00593    int newsize = ares_fds(mChannel, &read, &write);
00594    if ( newsize > size )
00595    {
00596       size = newsize;
00597    }
00598 }
00599 
00600 void
00601 AresDns::processTimers()
00602 {
00603 #ifdef USE_CARES
00604    return;
00605 #else
00606    assert( mPollGrp!=0 );
00607    time_t timeSecs;
00608    time(&timeSecs);
00609    ares_process_poll(mChannel, /*server*/-1, /*rd*/-1, /*wr*/-1, timeSecs);
00610 #endif
00611 }
00612 
00613 void 
00614 AresDns::process(FdSet& fdset)
00615 {
00616    process(fdset.read, fdset.write);
00617 }
00618 
00619 void 
00620 AresDns::buildFdSet(FdSet& fdset)
00621 {
00622    buildFdSet(fdset.read, fdset.write, fdset.size);
00623 }
00624 
00625 void
00626 AresDns::process(fd_set& read, fd_set& write)
00627 {
00628    ares_process(mChannel, &read, &write);
00629 }
00630 
00631 char*
00632 AresDns::errorMessage(long errorCode)
00633 {
00634    const char* aresMsg = ares_strerror(errorCode);
00635 
00636    size_t len = strlen(aresMsg);
00637    char* errorString = new char[len+1];
00638 
00639    strncpy(errorString, aresMsg, len);
00640    errorString[len] = '\0';
00641    return errorString;
00642 }
00643 
00644 void
00645 AresDns::lookup(const char* target, unsigned short type, ExternalDnsHandler* handler, void* userData)
00646 {
00647    ares_query(mChannel, target, C_IN, type,
00648 #if defined(USE_ARES)
00649               resip_AresDns_aresCallback,
00650 #elif defined(USE_CARES)
00651               resip_AresDns_caresCallback,
00652 #endif
00653               new Payload(handler, userData));
00654 }
00655 
00656 void
00657 resip_AresDns_aresCallback(void *arg, int status, unsigned char *abuf, int alen)
00658 {
00659 #if defined(USE_CARES)
00660    // If this is destruction, skip it.  We do this here for completeness.
00661    if(status == ARES_EDESTRUCTION)
00662    {
00663       return;
00664    }
00665 #endif
00666 
00667    resip::AresDns::getHandler(arg)->handleDnsRaw(resip::AresDns::makeRawResult(arg, status, abuf, alen));
00668    resip::AresDns::Payload* p = reinterpret_cast<resip::AresDns::Payload*>(arg);
00669    delete p;
00670 }
00671 
00672 void
00673 resip_AresDns_caresCallback(void *arg, int status, int timeouts,
00674                        unsigned char *abuf, int alen)
00675 {
00676    // Simply ignore the timeouts argument
00677    return ::resip_AresDns_aresCallback(arg, status, abuf, alen);
00678 }
00679 
00680 /* ====================================================================
00681  * The Vovida Software License, Version 1.0
00682  *
00683  * Copyright (c) 2000-2005 Vovida Networks, Inc.  All rights reserved.
00684  *
00685  * Redistribution and use in source and binary forms, with or without
00686  * modification, are permitted provided that the following conditions
00687  * are met:
00688  *
00689  * 1. Redistributions of source code must retain the above copyright
00690  *    notice, this list of conditions and the following disclaimer.
00691  *
00692  * 2. Redistributions in binary form must reproduce the above copyright
00693  *    notice, this list of conditions and the following disclaimer in
00694  *    the documentation and/or other materials provided with the
00695  *    distribution.
00696  *
00697  * 3. The names "VOCAL", "Vovida Open Communication Application Library",
00698  *    and "Vovida Open Communication Application Library (VOCAL)" must
00699  *    not be used to endorse or promote products derived from this
00700  *    software without prior written permission. For written
00701  *    permission, please contact vocal@vovida.org.
00702  *
00703  * 4. Products derived from this software may not be called "VOCAL", nor
00704  *    may "VOCAL" appear in their name, without prior written
00705  *    permission of Vovida Networks, Inc.
00706  *
00707  * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
00708  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00709  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
00710  * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
00711  * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
00712  * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
00713  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00714  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00715  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
00716  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00717  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
00718  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00719  * DAMAGE.
00720  *
00721  * ====================================================================
00722  *
00723  * This software consists of voluntary contributions made by Vovida
00724  * Networks, Inc. and many individuals on behalf of Vovida Networks,
00725  * Inc.  For more information on Vovida Networks, Inc., please see
00726  * <http://www.vovida.org/>.
00727  *
00728  * vi: shiftwidth=3 expandtab:
00729  */