You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tqt3/src/network/qdns.cpp

2695 lines
65 KiB

/****************************************************************************
**
** Implementation of TQDns class.
**
** Created : 991122
**
** Copyright (C) 1999-2008 Trolltech ASA. All rights reserved.
**
** This file is part of the network module of the TQt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free TQt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.TQPL
** included in the packaging of this file. Licensees holding valid TQt
** Commercial licenses may use this file in accordance with the TQt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#include "qplatformdefs.h"
// POSIX Large File Support redefines open -> open64
#if defined(open)
# undef open
#endif
// POSIX Large File Support redefines truncate -> truncate64
#if defined(truncate)
# undef truncate
#endif
// Solaris redefines connect -> __xnet_connect with _XOPEN_SOURCE_EXTENDED.
#if defined(connect)
# undef connect
#endif
// UnixWare 7 redefines socket -> _socket
#if defined(socket)
# undef socket
#endif
#include "ntqdns.h"
#ifndef QT_NO_DNS
#include "ntqdatetime.h"
#include "ntqdict.h"
#include "ntqptrlist.h"
#include "ntqstring.h"
#include "ntqtimer.h"
#include "ntqapplication.h"
#include "ntqptrvector.h"
#include "ntqstrlist.h"
#include "ntqptrdict.h"
#include "ntqfile.h"
#include "ntqtextstream.h"
#include "ntqsocketdevice.h"
#include "ntqcleanuphandler.h"
#include <limits.h>
#ifdef Q_OS_MAC
#include "../3rdparty/dlcompat/dlfcn.h"
#endif
//#define TQDNS_DEBUG
static TQ_UINT16 id; // ### seeded started by now()
static TQDateTime * originOfTime = 0;
static TQCleanupHandler<TQDateTime> qdns_cleanup_time;
static TQ_UINT32 now()
{
if ( originOfTime )
return originOfTime->secsTo( TQDateTime::currentDateTime() );
originOfTime = new TQDateTime( TQDateTime::currentDateTime() );
::id = originOfTime->time().msec() * 60 + originOfTime->time().second();
qdns_cleanup_time.add( &originOfTime );
return 0;
}
#if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3)))
#define Q_MODERN_RES_API
#else
#endif
static TQPtrList<TQHostAddress> * ns = 0;
static TQStrList * domains = 0;
static bool ipv6support = FALSE;
#if defined(Q_MODERN_RES_API)
#else
static int qdns_res_init()
{
#ifdef Q_OS_MAC
typedef int (*PtrRes_init)();
static PtrRes_init ptrRes_init = 0;
if (!ptrRes_init)
ptrRes_init = (PtrRes_init)DL_PREFIX(dlsym)(RTLD_NEXT, "res_init");
if (ptrRes_init)
return (*ptrRes_init)();
else
return -1;
#elif defined(Q_OS_UNIX)
return res_init();
#else
return 0; // not called at all on Windows.
#endif
}
#endif
class TQDnsPrivate {
public:
TQDnsPrivate() : queryTimer( 0 ), noNames(FALSE)
{
#if defined(Q_DNS_SYNCHRONOUS)
#if defined(Q_OS_UNIX)
noEventLoop = tqApp==0 || tqApp->loopLevel()==0;
#else
noEventLoop = FALSE;
#endif
#endif
}
~TQDnsPrivate()
{
delete queryTimer;
}
private:
TQTimer * queryTimer;
bool noNames;
#if defined(Q_DNS_SYNCHRONOUS)
bool noEventLoop;
#endif
friend class TQDns;
friend class TQDnsAnswer;
};
class TQDnsRR;
class TQDnsDomain;
// TQDnsRR is the class used to store a single RR. TQDnsRR can store
// all of the supported RR types. a TQDnsRR is always cached.
// TQDnsRR is mostly constructed from the outside. a but hacky, but
// permissible since the entire class is internal.
class TQDnsRR {
public:
TQDnsRR( const TQString & label );
~TQDnsRR();
public:
TQDnsDomain * domain;
TQDns::RecordType t;
bool nxdomain;
bool current;
TQ_UINT32 expireTime;
TQ_UINT32 deleteTime;
// somewhat space-wasting per-type data
// a / aaaa
TQHostAddress address;
// cname / mx / srv / ptr
TQString target;
// mx / srv
TQ_UINT16 priority;
// srv
TQ_UINT16 weight;
TQ_UINT16 port;
// txt
TQString text; // could be overloaded into target...
private:
};
class TQDnsDomain {
public:
TQDnsDomain( const TQString & label );
~TQDnsDomain();
static void add( const TQString & label, TQDnsRR * );
static TQPtrList<TQDnsRR> * cached( const TQDns * );
void take( TQDnsRR * );
void sweep( TQ_UINT32 thisSweep );
bool isEmpty() const { return rrs == 0 || rrs->isEmpty(); }
TQString name() const { return l; }
public:
TQString l;
TQPtrList<TQDnsRR> * rrs;
};
class TQDnsQuery: public TQTimer { // this inheritance is a very evil hack
public:
TQDnsQuery():
id( 0 ), t( TQDns::None ), step(0), started(0),
dns( new TQPtrDict<void>(17) ) {}
~TQDnsQuery() { delete dns; }
TQ_UINT16 id;
TQDns::RecordType t;
TQString l;
uint step;
TQ_UINT32 started;
TQPtrDict<void> * dns;
};
class TQDnsAnswer {
public:
TQDnsAnswer( TQDnsQuery * );
TQDnsAnswer( const TQByteArray &, TQDnsQuery * );
~TQDnsAnswer();
void parse();
void notify();
bool ok;
private:
TQDnsQuery * query;
TQ_UINT8 * answer;
int size;
int pp;
TQPtrList<TQDnsRR> * rrs;
// convenience
int next;
int ttl;
TQString label;
TQDnsRR * rr;
TQString readString(bool multipleLabels = TRUE);
void parseA();
void parseAaaa();
void parseMx();
void parseSrv();
void parseCname();
void parsePtr();
void parseTxt();
void parseNs();
};
TQDnsRR::TQDnsRR( const TQString & label )
: domain( 0 ), t( TQDns::None ),
nxdomain( FALSE ), current( FALSE ),
expireTime( 0 ), deleteTime( 0 ),
priority( 0 ), weight( 0 ), port( 0 )
{
TQDnsDomain::add( label, this );
}
// not supposed to be deleted except by TQDnsDomain
TQDnsRR::~TQDnsRR()
{
// nothing is necessary
}
// this one just sticks in a NXDomain
TQDnsAnswer::TQDnsAnswer( TQDnsQuery * query_ )
{
ok = TRUE;
answer = 0;
size = 0;
query = query_;
pp = 0;
rrs = new TQPtrList<TQDnsRR>;
rrs->setAutoDelete( FALSE );
next = size;
ttl = 0;
label = TQString::null;
rr = 0;
TQDnsRR * newrr = new TQDnsRR( query->l );
newrr->t = query->t;
newrr->deleteTime = query->started + 10;
newrr->expireTime = query->started + 10;
newrr->nxdomain = TRUE;
newrr->current = TRUE;
rrs->append( newrr );
}
TQDnsAnswer::TQDnsAnswer( const TQByteArray& answer_,
TQDnsQuery * query_ )
{
ok = TRUE;
answer = (TQ_UINT8 *)(answer_.data());
size = (int)answer_.size();
query = query_;
pp = 0;
rrs = new TQPtrList<TQDnsRR>;
rrs->setAutoDelete( FALSE );
next = size;
ttl = 0;
label = TQString::null;
rr = 0;
}
TQDnsAnswer::~TQDnsAnswer()
{
if ( !ok && rrs ) {
TQPtrListIterator<TQDnsRR> it( *rrs );
TQDnsRR * tmprr;
while( (tmprr=it.current()) != 0 ) {
++it;
tmprr->t = TQDns::None; // will be deleted soonish
}
}
delete rrs;
}
TQString TQDnsAnswer::readString(bool multipleLabels)
{
int p = pp;
TQString r = TQString::null;
TQ_UINT8 b;
for( ;; ) {
b = 128;
// Read one character
if ( p >= 0 && p < size )
b = answer[p];
switch( b >> 6 ) {
case 0:
// b is less than 64
p++;
// Detect end of data
if ( b == 0 ) {
if ( p > pp )
pp = p;
return r.isNull() ? TQString( "." ) : r;
}
// Read a label of size 'b' characters
if ( !r.isNull() )
r += '.';
while( b-- > 0 ) {
r += TQChar( answer[p] );
p++;
}
// Return immediately if we were only supposed to read one
// label.
if (!multipleLabels)
return r;
break;
default:
// Ignore unrecognized control character, or p was out of
// range.
goto not_ok;
case 3:
// Use the next character to determine the relative offset
// to jump to before continuing the packet parsing.
int q = ( (answer[p] & 0x3f) << 8 ) + answer[p+1];
if ( q >= pp || q >= p )
goto not_ok;
if ( p >= pp )
pp = p + 2;
p = q;
}
}
not_ok:
ok = FALSE;
return TQString::null;
}
void TQDnsAnswer::parseA()
{
if ( next != pp + 4 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %d bytes long IN A for %s",
next - pp, label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->t = TQDns::A;
rr->address = TQHostAddress( ( answer[pp+0] << 24 ) +
( answer[pp+1] << 16 ) +
( answer[pp+2] << 8 ) +
( answer[pp+3] ) );
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN A %s (ttl %d)", label.ascii(),
rr->address.toString().ascii(), ttl );
#endif
}
void TQDnsAnswer::parseAaaa()
{
if ( next != pp + 16 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %d bytes long IN Aaaa for %s",
next - pp, label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->t = TQDns::Aaaa;
rr->address = TQHostAddress( answer+pp );
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN Aaaa %s (ttl %d)", label.ascii(),
rr->address.toString().ascii(), ttl );
#endif
}
void TQDnsAnswer::parseMx()
{
if ( next < pp + 2 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %d bytes long IN MX for %s",
next - pp, label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->priority = (answer[pp] << 8) + answer[pp+1];
pp += 2;
rr->target = readString().lower();
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad string in MX for %s", label.ascii() );
#endif
return;
}
rr->t = TQDns::Mx;
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN MX %d %s (ttl %d)", label.ascii(),
rr->priority, rr->target.ascii(), ttl );
#endif
}
void TQDnsAnswer::parseSrv()
{
if ( next < pp + 6 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %d bytes long IN SRV for %s",
next - pp, label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->priority = (answer[pp] << 8) + answer[pp+1];
rr->weight = (answer[pp+2] << 8) + answer[pp+3];
rr->port = (answer[pp+4] << 8) + answer[pp+5];
pp += 6;
rr->target = readString().lower();
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad string in SRV for %s", label.ascii() );
#endif
return;
}
rr->t = TQDns::Srv;
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN SRV %d %d %d %s (ttl %d)", label.ascii(),
rr->priority, rr->weight, rr->port, rr->target.ascii(), ttl );
#endif
}
void TQDnsAnswer::parseCname()
{
TQString target = readString().lower();
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad cname for for %s", label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->t = TQDns::Cname;
rr->target = target;
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN CNAME %s (ttl %d)", label.ascii(),
rr->target.ascii(), ttl );
#endif
}
void TQDnsAnswer::parseNs()
{
TQString target = readString().lower();
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad cname for for %s", label.ascii() );
#endif
return;
}
// parse, but ignore
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN NS %s (ttl %d)", label.ascii(),
target.ascii(), ttl );
#endif
}
void TQDnsAnswer::parsePtr()
{
TQString target = readString().lower();
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad PTR for for %s", label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->t = TQDns::Ptr;
rr->target = target;
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN PTR %s (ttl %d)", label.ascii(),
rr->target.ascii(), ttl );
#endif
}
void TQDnsAnswer::parseTxt()
{
TQString text = readString(FALSE);
if ( !ok ) {
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw bad TXT for for %s", label.ascii() );
#endif
return;
}
rr = new TQDnsRR( label );
rr->t = TQDns::Txt;
rr->text = text;
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns: saw %s IN TXT \"%s\" (ttl %d)", label.ascii(),
rr->text.ascii(), ttl );
#endif
}
void TQDnsAnswer::parse()
{
// okay, do the work...
if ( (answer[2] & 0x78) != 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: answer to wrong query type (%d)", answer[1] );
#endif
ok = FALSE;
return;
}
// AA
bool aa = (answer[2] & 4) != 0;
// TC
if ( (answer[2] & 2) != 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: truncated answer; pressing on" );
#endif
}
// RD
bool rd = (answer[2] & 1) != 0;
// we don't test RA
// we don't test the MBZ fields
if ( (answer[3] & 0x0f) == 3 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: saw NXDomain for %s", query->l.ascii() );
#endif
// NXDomain. cache that for one minute.
rr = new TQDnsRR( query->l );
rr->t = query->t;
rr->deleteTime = query->started + 60;
rr->expireTime = query->started + 60;
rr->nxdomain = TRUE;
rr->current = TRUE;
rrs->append( rr );
return;
}
if ( (answer[3] & 0x0f) != 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: error code %d", answer[3] & 0x0f );
#endif
ok = FALSE;
return;
}
int qdcount = ( answer[4] << 8 ) + answer[5];
int ancount = ( answer[6] << 8 ) + answer[7];
int nscount = ( answer[8] << 8 ) + answer[9];
int adcount = (answer[10] << 8 ) +answer[11];
pp = 12;
// read query
while( qdcount > 0 && pp < size ) {
// should I compare the string against query->l?
(void)readString();
if ( !ok )
return;
pp += 4;
qdcount--;
}
// answers and stuff
int rrno = 0;
// if we parse the answer completely, but there are no answers,
// ignore the entire thing.
int answers = 0;
while( ( rrno < ancount ||
( ok && answers >0 && rrno < ancount + nscount + adcount ) ) &&
pp < size ) {
label = readString().lower();
if ( !ok )
return;
int rdlength = 0;
if ( pp + 10 <= size )
rdlength = ( answer[pp+8] << 8 ) + answer[pp+9];
if ( pp + 10 + rdlength > size ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: ran out of stuff to parse (%d+%d>%d (%d)",
pp, rdlength, size, rrno < ancount );
#endif
// if we're still in the AN section, we should go back and
// at least down the TTLs. probably best to invalidate
// the results.
// the rrs list is good for this
ok = ( rrno < ancount );
return;
}
uint type, clas;
type = ( answer[pp+0] << 8 ) + answer[pp+1];
clas = ( answer[pp+2] << 8 ) + answer[pp+3];
ttl = ( answer[pp+4] << 24 ) + ( answer[pp+5] << 16 ) +
( answer[pp+6] << 8 ) + answer[pp+7];
pp = pp + 10;
if ( clas != 1 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: class %d (not internet) for %s",
clas, label.isNull() ? "." : label.ascii() );
#endif
} else {
next = pp + rdlength;
rr = 0;
switch( type ) {
case 1:
parseA();
break;
case 28:
parseAaaa();
break;
case 15:
parseMx();
break;
case 33:
parseSrv();
break;
case 5:
parseCname();
break;
case 12:
parsePtr();
break;
case 16:
parseTxt();
break;
case 2:
parseNs();
break;
default:
// something we don't know
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: type %d for %s", type,
label.isNull() ? "." : label.ascii() );
#endif
break;
}
if ( rr ) {
rr->deleteTime = 0;
if ( ttl > 0 )
rr->expireTime = query->started + ttl;
else
rr->expireTime = query->started + 20;
if ( rrno < ancount ) {
answers++;
rr->deleteTime = rr->expireTime;
}
rr->current = TRUE;
rrs->append( rr );
}
}
if ( !ok )
return;
pp = next;
next = size;
rrno++;
}
if ( answers == 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: answer contained no answers" );
#endif
ok = ( aa && rd );
}
// now go through the list and mark all the As that are referenced
// by something we care about. we want to cache such As.
rrs->first();
TQDict<void> used( 17 );
used.setAutoDelete( FALSE );
while( (rr=rrs->current()) != 0 ) {
rrs->next();
if ( rr->target.length() && rr->deleteTime > 0 && rr->current )
used.insert( rr->target, (void*)42 );
if ( ( rr->t == TQDns::A || rr->t == TQDns::Aaaa ) &&
used.find( rr->domain->name() ) != 0 )
rr->deleteTime = rr->expireTime;
}
// next, for each RR, delete any older RRs that are equal to it
rrs->first();
while( (rr=rrs->current()) != 0 ) {
rrs->next();
if ( rr && rr->domain && rr->domain->rrs ) {
TQPtrList<TQDnsRR> * drrs = rr->domain->rrs;
drrs->first();
TQDnsRR * older;
while( (older=drrs->current()) != 0 ) {
if ( older != rr &&
older->t == rr->t &&
older->nxdomain == rr->nxdomain &&
older->address == rr->address &&
older->target == rr->target &&
older->priority == rr->priority &&
older->weight == rr->weight &&
older->port == rr->port &&
older->text == rr->text ) {
// well, it's equal, but it's not the same. so we kill it,
// but use its expiry time.
#if defined(TQDNS_DEBUG)
tqDebug( "killing off old %d for %s, expire was %d",
older->t, older->domain->name().latin1(),
rr->expireTime );
#endif
older->t = TQDns::None;
rr->expireTime = TQMAX( older->expireTime, rr->expireTime );
rr->deleteTime = TQMAX( older->deleteTime, rr->deleteTime );
older->deleteTime = 0;
#if defined(TQDNS_DEBUG)
tqDebug( " adjusted expire is %d", rr->expireTime );
#endif
}
drrs->next();
}
}
}
#if defined(TQDNS_DEBUG)
//tqDebug( "DNS Manager: ()" );
#endif
}
class TQDnsUgleHack: public TQDns {
public:
void ugle( bool emitAnyway=FALSE );
};
void TQDnsAnswer::notify()
{
if ( !rrs || !ok || !query || !query->dns )
return;
TQPtrDict<void> notified;
notified.setAutoDelete( FALSE );
TQPtrDictIterator<void> it( *query->dns );
TQDns * dns;
it.toFirst();
while( (dns=(TQDns*)(it.current())) != 0 ) {
++it;
if ( notified.find( (void*)dns ) == 0 ) {
notified.insert( (void*)dns, (void*)42 );
if ( rrs->count() == 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: found no answers!" );
#endif
dns->d->noNames = TRUE;
((TQDnsUgleHack*)dns)->ugle( TRUE );
} else {
TQStringList n = dns->qualifiedNames();
if ( query && n.contains(query->l) )
((TQDnsUgleHack*)dns)->ugle();
#if defined(TQDNS_DEBUG)
else
tqDebug( "DNS Manager: DNS thing %s not notified for %s",
dns->label().ascii(), query->l.ascii() );
#endif
}
}
}
}
//
//
// TQDnsManager
//
//
class TQDnsManager: public TQDnsSocket {
private:
public: // just to silence the moronic g++.
TQDnsManager();
~TQDnsManager();
public:
static TQDnsManager * manager();
TQDnsDomain * domain( const TQString & );
void transmitQuery( TQDnsQuery * );
void transmitQuery( int );
// reimplementation of the slots
void cleanCache();
void retransmit();
void answer();
public:
TQPtrVector<TQDnsQuery> queries;
TQDict<TQDnsDomain> cache;
TQSocketDevice * ipv4Socket;
#if !defined (QT_NO_IPV6)
TQSocketDevice * ipv6Socket;
#endif
};
static TQDnsManager * globalManager = 0;
static void cleanupDns()
{
delete globalManager;
globalManager = 0;
}
TQDnsManager * TQDnsManager::manager()
{
if ( !globalManager ) {
tqAddPostRoutine(cleanupDns);
new TQDnsManager();
}
return globalManager;
}
void TQDnsUgleHack::ugle( bool emitAnyway)
{
if ( emitAnyway || !isWorking() ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: status change for %s (type %d)",
label().ascii(), recordType() );
#endif
emit resultsReady();
}
}
TQDnsManager::TQDnsManager()
: TQDnsSocket( tqApp, "Internal DNS manager" ),
queries( TQPtrVector<TQDnsQuery>( 0 ) ),
cache( TQDict<TQDnsDomain>( 83, FALSE ) ),
ipv4Socket( new TQSocketDevice( TQSocketDevice::Datagram, TQSocketDevice::IPv4, 0 ) )
#if !defined (QT_NO_IPV6)
, ipv6Socket( new TQSocketDevice( TQSocketDevice::Datagram, TQSocketDevice::IPv6, 0 ) )
#endif
{
cache.setAutoDelete( TRUE );
globalManager = this;
TQTimer * sweepTimer = new TQTimer( this );
sweepTimer->start( 1000 * 60 * 3 );
connect( sweepTimer, SIGNAL(timeout()),
this, SLOT(cleanCache()) );
TQSocketNotifier * rn4 = new TQSocketNotifier( ipv4Socket->socket(),
TQSocketNotifier::Read,
this, "dns IPv4 socket watcher" );
ipv4Socket->setAddressReusable( FALSE );
ipv4Socket->setBlocking( FALSE );
connect( rn4, SIGNAL(activated(int)), SLOT(answer()) );
#if !defined (QT_NO_IPV6)
// Don't connect the IPv6 socket notifier if the host does not
// support IPv6.
if ( ipv6Socket->socket() != -1 ) {
TQSocketNotifier * rn6 = new TQSocketNotifier( ipv6Socket->socket(),
TQSocketNotifier::Read,
this, "dns IPv6 socket watcher" );
ipv6support = TRUE;
ipv6Socket->setAddressReusable( FALSE );
ipv6Socket->setBlocking( FALSE );
connect( rn6, SIGNAL(activated(int)), SLOT(answer()) );
}
#endif
if ( !ns )
TQDns::doResInit();
// O(n*n) stuff here. but for 3 and 6, O(n*n) with a low k should
// be perfect. the point is to eliminate any duplicates that
// might be hidden in the lists.
TQPtrList<TQHostAddress> * ns = new TQPtrList<TQHostAddress>;
::ns->first();
TQHostAddress * h;
while( (h=::ns->current()) != 0 ) {
ns->first();
while( ns->current() != 0 && !(*ns->current() == *h) )
ns->next();
if ( !ns->current() ) {
ns->append( new TQHostAddress(*h) );
#if defined(TQDNS_DEBUG)
tqDebug( "using name server %s", h->toString().latin1() );
} else {
tqDebug( "skipping address %s", h->toString().latin1() );
#endif
}
::ns->next();
}
delete ::ns;
::ns = ns;
::ns->setAutoDelete( TRUE );
TQStrList * domains = new TQStrList( TRUE );
::domains->first();
const char * s;
while( (s=::domains->current()) != 0 ) {
domains->first();
while( domains->current() != 0 && qstrcmp( domains->current(), s ) )
domains->next();
if ( !domains->current() ) {
domains->append( s );
#if defined(TQDNS_DEBUG)
tqDebug( "searching domain %s", s );
} else {
tqDebug( "skipping domain %s", s );
#endif
}
::domains->next();
}
delete ::domains;
::domains = domains;
::domains->setAutoDelete( TRUE );
}
TQDnsManager::~TQDnsManager()
{
if ( globalManager )
globalManager = 0;
queries.setAutoDelete( TRUE );
cache.setAutoDelete( TRUE );
delete ipv4Socket;
#if !defined (QT_NO_IPV6)
delete ipv6Socket;
#endif
}
static TQ_UINT32 lastSweep = 0;
void TQDnsManager::cleanCache()
{
bool again = FALSE;
TQDictIterator<TQDnsDomain> it( cache );
TQDnsDomain * d;
TQ_UINT32 thisSweep = now();
#if defined(TQDNS_DEBUG)
tqDebug( "TQDnsManager::cleanCache(: Called, time is %u, last was %u",
thisSweep, lastSweep );
#endif
while( (d=it.current()) != 0 ) {
++it;
d->sweep( thisSweep ); // after this, d may be empty
if ( !again )
again = !d->isEmpty();
}
if ( !again )
delete this;
lastSweep = thisSweep;
}
void TQDnsManager::retransmit()
{
const TQObject * o = sender();
if ( o == 0 || globalManager == 0 || this != globalManager )
return;
uint q = 0;
while( q < queries.size() && queries[q] != o )
q++;
if ( q < queries.size() )
transmitQuery( q );
}
void TQDnsManager::answer()
{
TQByteArray a( 16383 ); // large enough for anything, one suspects
int r;
#if defined (QT_NO_IPV6)
r = ipv4Socket->readBlock(a.data(), a.size());
#else
if (((TQSocketNotifier *)sender())->socket() == ipv4Socket->socket())
r = ipv4Socket->readBlock(a.data(), a.size());
else
r = ipv6Socket->readBlock(a.data(), a.size());
#endif
#if defined(TQDNS_DEBUG)
#if !defined (QT_NO_IPV6)
tqDebug("DNS Manager: answer arrived: %d bytes from %s:%d", r,
useIpv4Socket ? ipv4Socket->peerAddress().toString().ascii()
: ipv6Socket->peerAddress().toString().ascii(),
useIpv4Socket ? ipv4Socket->peerPort() : ipv6Socket->peerPort() );
#else
tqDebug("DNS Manager: answer arrived: %d bytes from %s:%d", r,
ipv4Socket->peerAddress().toString().ascii(), ipv4Socket->peerPort());;
#endif
#endif
if ( r < 12 )
return;
// maybe we should check that the answer comes from port 53 on one
// of our name servers...
a.resize( r );
TQ_UINT16 aid = (((TQ_UINT8)a[0]) << 8) + ((TQ_UINT8)a[1]);
uint i = 0;
while( i < queries.size() &&
!( queries[i] && queries[i]->id == aid ) )
i++;
if ( i == queries.size() ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: bad id (0x%04x) %d", aid, i );
#endif
return;
}
// at this point queries[i] is whatever we asked for.
if ( ( (TQ_UINT8)(a[2]) & 0x80 ) == 0 ) {
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: received a query" );
#endif
return;
}
TQDnsQuery * q = queries[i];
TQDnsAnswer answer( a, q );
answer.parse();
if ( answer.ok ) {
queries.take( i );
answer.notify();
delete q;
}
}
void TQDnsManager::transmitQuery( TQDnsQuery * query_ )
{
if ( !query_ )
return;
uint i = 0;
while( i < queries.size() && queries[i] != 0 )
i++;
if ( i == queries.size() )
queries.resize( i+1 );
queries.insert( i, query_ );
transmitQuery( i );
}
void TQDnsManager::transmitQuery( int i )
{
if ( i < 0 || i >= (int)queries.size() )
return;
TQDnsQuery * q = queries[i];
if ( q && q->step > 8 ) {
// okay, we've run out of retransmissions. we fake an NXDomain
// with a very short life time...
TQDnsAnswer answer( q );
answer.notify();
if (globalManager == 0)
return;
// and then get rid of the query
queries.take( i );
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: giving up on query 0x%04x", q->id );
#endif
delete q;
TQTimer::singleShot( 0, TQDnsManager::manager(), SLOT(cleanCache()) );
// and don't process anything more
return;
}
if ( ( q && !q->dns ) || q->dns->isEmpty() )
// noone currently wants the answer, so there's no point in
// retransmitting the query. we keep it, though. an answer may
// arrive for an earlier query transmission, and if it does we
// may benefit from caching the result.
return;
TQByteArray p( 12 + q->l.length() + 2 + 4 );
if ( p.size() > 500 )
return; // way over the limit, so don't even try
// header
// id
p[0] = (q->id & 0xff00) >> 8;
p[1] = q->id & 0x00ff;
p[2] = 1; // recursion desired, rest is 0
p[3] = 0; // all is 0
// one query
p[4] = 0;
p[5] = 1;
// no answers, name servers or additional data
p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0;
// the name is composed of several components. each needs to be
// written by itself... so we write...
// oh, and we assume that there's no funky characters in there.
int pp = 12;
uint lp = 0;
while( lp < q->l.length() ) {
int le = q->l.find( '.', lp );
if ( le < 0 )
le = q->l.length();
TQString component = q->l.mid( lp, le-lp );
p[pp++] = component.length();
int cp;
for( cp=0; cp < (int)component.length(); cp++ )
p[pp++] = component[cp].latin1();
lp = le + 1;
}
// final null
p[pp++] = 0;
// query type
p[pp++] = 0;
switch( q->t ) {
case TQDns::A:
p[pp++] = 1;
break;
case TQDns::Aaaa:
p[pp++] = 28;
break;
case TQDns::Mx:
p[pp++] = 15;
break;
case TQDns::Srv:
p[pp++] = 33;
break;
case TQDns::Cname:
p[pp++] = 5;
break;
case TQDns::Ptr:
p[pp++] = 12;
break;
case TQDns::Txt:
p[pp++] = 16;
break;
default:
p[pp++] = (char)255; // any
break;
}
// query class (always internet)
p[pp++] = 0;
p[pp++] = 1;
// if we have no name servers, we should regenerate ns in case
// name servers have recently been defined (like on windows,
// plugging/unplugging the network cable will change the name
// server entries)
if ( !ns || ns->isEmpty() )
TQDns::doResInit();
if ( !ns || ns->isEmpty() ) {
// we don't find any name servers. We fake an NXDomain
// with a very short life time...
TQDnsAnswer answer( q );
answer.notify();
// and then get rid of the query
queries.take( i );
#if defined(TQDNS_DEBUG)
tqDebug( "DNS Manager: no DNS server found on query 0x%04x", q->id );
#endif
delete q;
TQTimer::singleShot( 1000*10, TQDnsManager::manager(), SLOT(cleanCache()) );
// and don't process anything more
return;
}
TQHostAddress receiver = *ns->at( q->step % ns->count() );
if (receiver.isIPv4Address())
ipv4Socket->writeBlock( p.data(), pp, receiver, 53 );
#if !defined (QT_NO_IPV6)
else
ipv6Socket->writeBlock( p.data(), pp, receiver, 53 );
#endif
#if defined(TQDNS_DEBUG)
tqDebug( "issuing query 0x%04x (%d) about %s type %d to %s",
q->id, q->step, q->l.ascii(), q->t,
ns->at( q->step % ns->count() )->toString().ascii() );
#endif
if ( ns->count() > 1 && q->step == 0 && queries.count() == 1 ) {
// if it's the first time, and we don't have any other
// outstanding queries, send nonrecursive queries to the other
// name servers too.
p[2] = 0;
TQHostAddress * server;
while( (server=ns->next()) != 0 ) {
if (server->isIPv4Address())
ipv4Socket->writeBlock( p.data(), pp, *server, 53 );
#if !defined (QT_NO_IPV6)
else
ipv6Socket->writeBlock( p.data(), pp, *server, 53 );
#endif
#if defined(TQDNS_DEBUG)
tqDebug( "copying query to %s", server->toString().ascii() );
#endif
}
}
q->step++;
// some testing indicates that normal dns queries take up to 0.6
// seconds. the graph becomes steep around that point, and the
// number of errors rises... so it seems good to retry at that
// point.
q->start( q->step < ns->count() ? 800 : 1500, TRUE );
}
TQDnsDomain * TQDnsManager::domain( const TQString & label )
{
TQDnsDomain * d = cache.find( label );
if ( !d ) {
d = new TQDnsDomain( label );
cache.insert( label, d );
}
return d;
}
//
//
// the TQDnsDomain class looks after and coordinates queries for TQDnsRRs for
// each domain, and the cached TQDnsRRs. (A domain, in DNS terminology, is
// a node in the DNS. "no", "trolltech.com" and "lupinella.troll.no" are
// all domains.)
//
//
// this is ONLY to be called by TQDnsManager::domain(). noone else.
TQDnsDomain::TQDnsDomain( const TQString & label )
{
l = label;
rrs = 0;
}
TQDnsDomain::~TQDnsDomain()
{
delete rrs;
rrs = 0;
}
void TQDnsDomain::add( const TQString & label, TQDnsRR * rr )
{
TQDnsDomain * d = TQDnsManager::manager()->domain( label );
if ( !d->rrs ) {
d->rrs = new TQPtrList<TQDnsRR>;
d->rrs->setAutoDelete( TRUE );
}
d->rrs->append( rr );
rr->domain = d;
}
TQPtrList<TQDnsRR> * TQDnsDomain::cached( const TQDns * r )
{
TQPtrList<TQDnsRR> * l = new TQPtrList<TQDnsRR>;
// test at first if you have to start a query at all
if ( r->recordType() == TQDns::A ) {
if ( r->label().lower() == "localhost" ) {
// undocumented hack. ipv4-specific. also, may be a memory
// leak? not sure. would be better to do this in doResInit(),
// anyway.
TQDnsRR *rrTmp = new TQDnsRR( r->label() );
rrTmp->t = TQDns::A;
rrTmp->address = TQHostAddress( 0x7f000001 );
rrTmp->current = TRUE;
l->append( rrTmp );
return l;
}
TQHostAddress tmp;
if ( tmp.setAddress( r->label() ) ) {
TQDnsRR *rrTmp = new TQDnsRR( r->label() );
if ( tmp.isIPv4Address() ) {
rrTmp->t = TQDns::A;
rrTmp->address = tmp;
rrTmp->current = TRUE;
l->append( rrTmp );
} else {
rrTmp->nxdomain = TRUE;
}
return l;
}
}
if ( r->recordType() == TQDns::Aaaa ) {
TQHostAddress tmp;
if ( tmp.setAddress(r->label()) ) {
TQDnsRR *rrTmp = new TQDnsRR( r->label() );
if ( tmp.isIPv6Address() ) {
rrTmp->t = TQDns::Aaaa;
rrTmp->address = tmp;
rrTmp->current = TRUE;
l->append( rrTmp );
} else {
rrTmp->nxdomain = TRUE;
}
return l;
}
}
// if you reach this point, you have to do the query
TQDnsManager * m = TQDnsManager::manager();
TQStringList n = r->qualifiedNames();
TQValueListIterator<TQString> it = n.begin();
TQValueListIterator<TQString> end = n.end();
bool nxdomain;
int cnamecount = 0;
while( it != end ) {
TQString s = *it++;
nxdomain = FALSE;
#if defined(TQDNS_DEBUG)
tqDebug( "looking at cache for %s (%s %d)",
s.ascii(), r->label().ascii(), r->recordType() );
#endif
TQDnsDomain * d = m->domain( s );
#if defined(TQDNS_DEBUG)
tqDebug( " - found %d RRs", d && d->rrs ? d->rrs->count() : 0 );
#endif
if ( d->rrs )
d->rrs->first();
TQDnsRR * rr;
bool answer = FALSE;
while( d->rrs && (rr=d->rrs->current()) != 0 ) {
if ( rr->t == TQDns::Cname && r->recordType() != TQDns::Cname &&
!rr->nxdomain && cnamecount < 16 ) {
// cname. if the code is ugly, that may just
// possibly be because the concept is.
#if defined(TQDNS_DEBUG)
tqDebug( "found cname from %s to %s",
r->label().ascii(), rr->target.ascii() );
#endif
s = rr->target;
d = m->domain( s );
if ( d->rrs )
d->rrs->first();
it = end;
// we've elegantly moved over to whatever the cname
// pointed to. well, not elegantly. let's remember
// that we've done something, anyway, so we can't be
// fooled into an infinte loop as well.
cnamecount++;
} else {
if ( rr->t == r->recordType() ) {
if ( rr->nxdomain )
nxdomain = TRUE;
else
answer = TRUE;
l->append( rr );
if ( rr->deleteTime <= lastSweep ) {
// we're returning something that'll be
// deleted soon. we assume that if the client
// wanted it twice, it'll want it again, so we
// ask the name server again right now.
TQDnsQuery * query = new TQDnsQuery;
query->started = now();
query->id = ++::id;
query->t = rr->t;
query->l = rr->domain->name();
// note that here, we don't bother about
// notification. but we do bother about
// timeouts: we make sure to use high timeouts
// and few tramsissions.
query->step = ns->count();
TQObject::connect( query, SIGNAL(timeout()),
TQDnsManager::manager(),
SLOT(retransmit()) );
TQDnsManager::manager()->transmitQuery( query );
}
}
d->rrs->next();
}
}
// if we found a positive result, return tquickly
if ( answer && l->count() ) {
#if defined(TQDNS_DEBUG)
tqDebug( "found %d records for %s",
l->count(), r->label().ascii() );
l->first();
while( l->current() ) {
tqDebug( " type %d target %s address %s",
l->current()->t,
l->current()->target.latin1(),
l->current()->address.toString().latin1() );
l->next();
}
#endif
l->first();
return l;
}
#if defined(TQDNS_DEBUG)
if ( nxdomain )
tqDebug( "found NXDomain %s", s.ascii() );
#endif
if ( !nxdomain ) {
// if we didn't, and not a negative result either, perhaps
// we need to transmit a query.
uint q = 0;
while ( q < m->queries.size() &&
( m->queries[q] == 0 ||
m->queries[q]->t != r->recordType() ||
m->queries[q]->l != s ) )
q++;
// we haven't done it before, so maybe we should. but
// wait - if it's an unqualified name, only ask when all
// the other alternatives are exhausted.
if ( q == m->queries.size() && ( s.find( '.' ) >= 0 ||
l->count() >= n.count()-1 ) ) {
TQDnsQuery * query = new TQDnsQuery;
query->started = now();
query->id = ++::id;
query->t = r->recordType();
query->l = s;
query->dns->replace( (void*)r, (void*)r );
TQObject::connect( query, SIGNAL(timeout()),
TQDnsManager::manager(), SLOT(retransmit()) );
TQDnsManager::manager()->transmitQuery( query );
} else if ( q < m->queries.size() ) {
// if we've found an earlier query for the same
// domain/type, subscribe to its answer
m->queries[q]->dns->replace( (void*)r, (void*)r );
}
}
}
l->first();
return l;
}
void TQDnsDomain::sweep( TQ_UINT32 thisSweep )
{
if ( !rrs )
return;
TQDnsRR * rr;
rrs->first();
while( (rr=rrs->current()) != 0 ) {
if ( !rr->deleteTime )
rr->deleteTime = thisSweep; // will hit next time around
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::sweep: %s type %d expires %u %u - %s / %s",
rr->domain->name().latin1(), rr->t,
rr->expireTime, rr->deleteTime,
rr->target.latin1(), rr->address.toString().latin1());
#endif
if ( rr->current == FALSE ||
rr->t == TQDns::None ||
rr->deleteTime <= thisSweep ||
rr->expireTime <= thisSweep )
rrs->remove();
else
rrs->next();
}
if ( rrs->isEmpty() ) {
delete rrs;
rrs = 0;
}
}
// the itsy-bitsy little socket class I don't really need except for
// so I can subclass and reimplement the slots.
TQDnsSocket::TQDnsSocket( TQObject * parent, const char * name )
: TQObject( parent, name )
{
// nothing
}
TQDnsSocket::~TQDnsSocket()
{
// nothing
}
void TQDnsSocket::cleanCache()
{
// nothing
}
void TQDnsSocket::retransmit()
{
// nothing
}
void TQDnsSocket::answer()
{
// nothing
}
/*!
\class TQDns ntqdns.h
\brief The TQDns class provides asynchronous DNS lookups.
\if defined(commercial)
It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>.
\endif
\module network
\ingroup io
Both Windows and Unix provide synchronous DNS lookups; Windows
provides some asynchronous support too. At the time of writing
neither operating system provides asynchronous support for
anything other than hostname-to-address mapping.
TQDns rectifies this shortcoming, by providing asynchronous caching
lookups for the record types that we expect modern GUI
applications to need in the near future.
The class is \e not straightforward to use (although it is much
simpler than the native APIs); TQSocket provides much easier to use
TCP connection facilities. The aim of TQDns is to provide a correct
and small API to the DNS and nothing more. (We use "correctness"
to mean that the DNS information is correctly cached, and
correctly timed out.)
The API comprises a constructor, functions to set the DNS node
(the domain in DNS terminology) and record type (setLabel() and
setRecordType()), the corresponding get functions, an isWorking()
function to determine whether TQDns is working or reading, a
resultsReady() signal and query functions for the result.
There is one query function for each RecordType, namely
addresses(), mailServers(), servers(), hostNames() and texts().
There are also two generic query functions: canonicalName()
returns the name you'll presumably end up using (the exact meaning
of this depends on the record type) and qualifiedNames() returns a
list of the fully qualified names label() maps to.
\sa TQSocket
*/
/*!
Constructs a DNS query object with invalid settings for both the
label and the search type.
*/
TQDns::TQDns()
{
d = new TQDnsPrivate;
t = None;
}
/*!
Constructs a DNS query object that will return record type \a rr
information about \a label.
The DNS lookup is started the next time the application enters the
event loop. When the result is found the signal resultsReady() is
emitted.
\a rr defaults to \c A, IPv4 addresses.
*/
TQDns::TQDns( const TQString & label, RecordType rr )
{
d = new TQDnsPrivate;
t = rr;
setLabel( label );
setStartQueryTimer(); // start query the next time we enter event loop
}
/*!
Constructs a DNS query object that will return record type \a rr
information about host address \a address. The label is set to the
IN-ADDR.ARPA domain name. This is useful in combination with the
\c Ptr record type (e.g. if you want to look up a hostname for a
given address).
The DNS lookup is started the next time the application enters the
event loop. When the result is found the signal resultsReady() is
emitted.
\a rr defaults to \c Ptr, that maps addresses to hostnames.
*/
TQDns::TQDns( const TQHostAddress & address, RecordType rr )
{
d = new TQDnsPrivate;
t = rr;
setLabel( address );
setStartQueryTimer(); // start query the next time we enter event loop
}
/*!
Destroys the DNS query object and frees its allocated resources.
*/
TQDns::~TQDns()
{
if ( globalManager ) {
uint q = 0;
TQDnsManager * m = globalManager;
while( q < m->queries.size() ) {
TQDnsQuery * query=m->queries[q];
if ( query && query->dns )
(void)query->dns->take( (void*) this );
q++;
}
}
delete d;
d = 0;
}
/*!
Sets this DNS query object to query for information about \a
label.
This does not change the recordType(), but its isWorking() status
will probably change as a result.
The DNS lookup is started the next time the application enters the
event loop. When the result is found the signal resultsReady() is
emitted.
*/
void TQDns::setLabel( const TQString & label )
{
l = label;
d->noNames = FALSE;
// construct a list of qualified names
n.clear();
if ( l.length() > 1 && l[(int)l.length()-1] == '.' ) {
n.append( l.left( l.length()-1 ).lower() );
} else {
int i = l.length();
int dots = 0;
const int maxDots = 2;
while( i && dots < maxDots ) {
if ( l[--i] == '.' )
dots++;
}
if ( dots < maxDots ) {
(void)TQDnsManager::manager(); // create a TQDnsManager, if it is not already there
TQStrListIterator it( *domains );
const char * dom;
while( (dom=it.current()) != 0 ) {
++it;
n.append( l.lower() + "." + dom );
}
}
n.append( l.lower() );
}
#if defined(Q_DNS_SYNCHRONOUS)
if ( d->noEventLoop ) {
doSynchronousLookup();
} else {
setStartQueryTimer(); // start query the next time we enter event loop
}
#else
setStartQueryTimer(); // start query the next time we enter event loop
#endif
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::setLabel: %d address(es) for %s", n.count(), l.ascii() );
int i = 0;
for( i = 0; i < (int)n.count(); i++ )
tqDebug( "TQDns::setLabel: %d: %s", i, n[i].ascii() );
#endif
}
/*!
\overload
Sets this DNS query object to query for information about the host
address \a address. The label is set to the IN-ADDR.ARPA domain
name. This is useful in combination with the \c Ptr record type
(e.g. if you want to look up a hostname for a given address).
*/
void TQDns::setLabel( const TQHostAddress & address )
{
setLabel( toInAddrArpaDomain( address ) );
}
/*!
\fn TQStringList TQDns::qualifiedNames() const
Returns a list of the fully qualified names label() maps to.
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQStringList list = myDns.qualifiedNames();
TQStringList::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
/*!
\fn TQString TQDns::label() const
Returns the domain name for which this object returns information.
\sa setLabel()
*/
/*!
\enum TQDns::RecordType
This enum type defines the record types TQDns can handle. The DNS
provides many more; these are the ones we've judged to be in
current use, useful for GUI programs and important enough to
support right away:
\value None No information. This exists only so that TQDns can
have a default.
\value A IPv4 addresses. By far the most common type.
\value Aaaa IPv6 addresses. So far mostly unused.
\value Mx Mail eXchanger names. Used for mail delivery.
\value Srv SeRVer names. Generic record type for finding
servers. So far mostly unused.
\value Cname Canonical names. Maps from nicknames to the true
name (the canonical name) for a host.
\value Ptr name PoinTeRs. Maps from IPv4 or IPv6 addresses to hostnames.
\value Txt arbitrary TeXT for domains.
We expect that some support for the
\link http://www.dns.net/dnsrd/rfc/rfc2535.html RFC-2535 \endlink
extensions will be added in future versions.
*/
/*!
Sets this object to query for record type \a rr records.
The DNS lookup is started the next time the application enters the
event loop. When the result is found the signal resultsReady() is
emitted.
\sa RecordType
*/
void TQDns::setRecordType( RecordType rr )
{
t = rr;
d->noNames = FALSE;
setStartQueryTimer(); // start query the next time we enter event loop
}
/*!
\internal
Private slot for starting the query.
*/
void TQDns::startQuery()
{
// isWorking() starts the query (if necessary)
if ( !isWorking() )
emit resultsReady();
}
/*!
The three functions TQDns::TQDns(TQString, RecordType),
TQDns::setLabel() and TQDns::setRecordType() may start a DNS lookup.
This function handles setting up the single shot timer.
*/
void TQDns::setStartQueryTimer()
{
#if defined(Q_DNS_SYNCHRONOUS)
if ( !d->queryTimer && !d->noEventLoop )
#else
if ( !d->queryTimer )
#endif
{
// start the query the next time we enter event loop
d->queryTimer = new TQTimer( this );
connect( d->queryTimer, SIGNAL(timeout()),
this, SLOT(startQuery()) );
d->queryTimer->start( 0, TRUE );
}
}
/*
Transforms the host address \a address to the IN-ADDR.ARPA domain
name. Returns something indeterminate if you're sloppy or
naughty. This function has an IPv4-specific name, but works for
IPv6 too.
*/
TQString TQDns::toInAddrArpaDomain( const TQHostAddress &address )
{
TQString s;
if ( address.isNull() ) {
// if the address isn't valid, neither of the other two make
// cases make sense. better to just return.
} else if ( address.isIp4Addr() ) {
TQ_UINT32 i = address.ip4Addr();
s.sprintf( "%d.%d.%d.%d.IN-ADDR.ARPA",
i & 0xff, (i >> 8) & 0xff, (i>>16) & 0xff, (i>>24) & 0xff );
} else {
// RFC 3152. (1886 is deprecated, and clients no longer need to
// support it, in practice).
Q_IPV6ADDR i = address.toIPv6Address();
s = "ip6.arpa";
uint b = 0;
while( b < 16 ) {
s = TQString::number( i.c[b]%16, 16 ) + "." +
TQString::number( i.c[b]/16, 16 ) + "." + s;
b++;
}
}
return s;
}
/*!
\fn TQDns::RecordType TQDns::recordType() const
Returns the record type of this DNS query object.
\sa setRecordType() RecordType
*/
/*!
\fn void TQDns::resultsReady()
This signal is emitted when results are available for one of the
qualifiedNames().
*/
/*!
Returns TRUE if TQDns is doing a lookup for this object (i.e. if it
does not already have the necessary information); otherwise
returns FALSE.
TQDns emits the resultsReady() signal when the status changes to FALSE.
*/
bool TQDns::isWorking() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::isWorking (%s, %d)", l.ascii(), t );
#endif
if ( t == None )
return FALSE;
#if defined(Q_DNS_SYNCHRONOUS)
if ( d->noEventLoop )
return TRUE;
#endif
TQPtrList<TQDnsRR> * ll = TQDnsDomain::cached( this );
TQ_LONG queries = n.count();
while( ll->current() != 0 ) {
if ( ll->current()->nxdomain ) {
queries--;
} else {
delete ll;
return FALSE;
}
ll->next();
}
delete ll;
if ( queries <= 0 )
return FALSE;
if ( d->noNames )
return FALSE;
return TRUE;
}
/*!
Returns a list of the addresses for this name if this TQDns object
has a recordType() of \c TQDns::A or \c TQDns::Aaaa and the answer
is available; otherwise returns an empty list.
As a special case, if label() is a valid numeric IP address, this
function returns that address.
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQValueList<TQHostAddress> list = myDns.addresses();
TQValueList<TQHostAddress>::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
TQValueList<TQHostAddress> TQDns::addresses() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::addresses (%s)", l.ascii() );
#endif
TQValueList<TQHostAddress> result;
if ( t != A && t != Aaaa )
return result;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this );
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain )
result.append( rr->address );
cached->next();
}
delete cached;
return result;
}
/*!
\class TQDns::MailServer
\brief The TQDns::MailServer class is described in TQDns::mailServers().
\if defined(commercial)
It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>.
\endif
\ingroup io
\internal
*/
/*!
Returns a list of mail servers if the record type is \c Mx. The
class \c TQDns::MailServer contains the following public variables:
\list
\i TQString TQDns::MailServer::name
\i TQ_UINT16 TQDns::MailServer::priority
\endlist
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQValueList<TQDns::MailServer> list = myDns.mailServers();
TQValueList<TQDns::MailServer>::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
TQValueList<TQDns::MailServer> TQDns::mailServers() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::mailServers (%s)", l.ascii() );
#endif
TQValueList<TQDns::MailServer> result;
if ( t != Mx )
return result;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this );
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain ) {
MailServer ms( rr->target, rr->priority );
result.append( ms );
}
cached->next();
}
delete cached;
return result;
}
/*!
\class TQDns::Server
\brief The TQDns::Server class is described in TQDns::servers().
\if defined(commercial)
It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>.
\endif
\ingroup io
\internal
*/
/*!
Returns a list of servers if the record type is \c Srv. The class
\c TQDns::Server contains the following public variables:
\list
\i TQString TQDns::Server::name
\i TQ_UINT16 TQDns::Server::priority
\i TQ_UINT16 TQDns::Server::weight
\i TQ_UINT16 TQDns::Server::port
\endlist
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQValueList<TQDns::Server> list = myDns.servers();
TQValueList<TQDns::Server>::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
TQValueList<TQDns::Server> TQDns::servers() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::servers (%s)", l.ascii() );
#endif
TQValueList<TQDns::Server> result;
if ( t != Srv )
return result;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this );
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain ) {
Server s( rr->target, rr->priority, rr->weight, rr->port );
result.append( s );
}
cached->next();
}
delete cached;
return result;
}
/*!
Returns a list of host names if the record type is \c Ptr.
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQStringList list = myDns.hostNames();
TQStringList::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
TQStringList TQDns::hostNames() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::hostNames (%s)", l.ascii() );
#endif
TQStringList result;
if ( t != Ptr )
return result;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this );
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain ) {
TQString str( rr->target );
result.append( str );
}
cached->next();
}
delete cached;
return result;
}
/*!
Returns a list of texts if the record type is \c Txt.
Note that if you want to iterate over the list, you should iterate
over a copy, e.g.
\code
TQStringList list = myDns.texts();
TQStringList::Iterator it = list.begin();
while( it != list.end() ) {
myProcessing( *it );
++it;
}
\endcode
*/
TQStringList TQDns::texts() const
{
#if defined(TQDNS_DEBUG)
tqDebug( "TQDns::texts (%s)", l.ascii() );
#endif
TQStringList result;
if ( t != Txt )
return result;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this );
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain ) {
TQString str( rr->text );
result.append( str );
}
cached->next();
}
delete cached;
return result;
}
/*!
Returns the canonical name for this DNS node. (This works
regardless of what recordType() is set to.)
If the canonical name isn't known, this function returns a null
string.
The canonical name of a DNS node is its full name, or the full
name of the target of its CNAME. For example, if l.trolltech.com
is a CNAME to lillian.troll.no, and the search path for TQDns is
"trolltech.com", then the canonical name for all of "lillian",
"l", "lillian.troll.no." and "l.trolltech.com" is
"lillian.troll.no.".
*/
TQString TQDns::canonicalName() const
{
// the cname should work regardless of the recordType(), so set the record
// type temporarily to cname when you look at the cache
TQDns *that = (TQDns*) this; // mutable function
RecordType oldType = t;
that->t = Cname;
TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( that );
that->t = oldType;
TQDnsRR * rr;
while( (rr=cached->current()) != 0 ) {
if ( rr->current && !rr->nxdomain && rr->domain ) {
delete cached;
return rr->target;
}
cached->next();
}
delete cached;
return TQString::null;
}
#if defined(Q_DNS_SYNCHRONOUS)
/*! \reimp
*/
void TQDns::connectNotify( const char *signal )
{
if ( d->noEventLoop && qstrcmp(signal,SIGNAL(resultsReady()) )==0 ) {
doSynchronousLookup();
}
}
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_CYGWIN)
#if defined(Q_DNS_SYNCHRONOUS)
void TQDns::doSynchronousLookup()
{
// ### not implemented yet
}
#endif
// the following typedefs are needed for GetNetworkParams() API call
#ifndef IP_TYPES_INCLUDED
#define MAX_HOSTNAME_LEN 128
#define MAX_DOMAIN_NAME_LEN 128
#define MAX_SCOPE_ID_LEN 256
typedef struct {
char String[4 * 4];
} IP_ADDRESS_STRING, *PIP_ADDRESS_STRING, IP_MASK_STRING, *PIP_MASK_STRING;
typedef struct _IP_ADDR_STRING {
struct _IP_ADDR_STRING* Next;
IP_ADDRESS_STRING IpAddress;
IP_MASK_STRING IpMask;
DWORD Context;
} IP_ADDR_STRING, *PIP_ADDR_STRING;
typedef struct {
char HostName[MAX_HOSTNAME_LEN + 4] ;
char DomainName[MAX_DOMAIN_NAME_LEN + 4];
PIP_ADDR_STRING CurrentDnsServer;
IP_ADDR_STRING DnsServerList;
UINT NodeType;
char ScopeId[MAX_SCOPE_ID_LEN + 4];
UINT EnableRouting;
UINT EnableProxy;
UINT EnableDns;
} FIXED_INFO, *PFIXED_INFO;
#endif
typedef DWORD (WINAPI *GNP)( PFIXED_INFO, PULONG );
// ### FIXME: this code is duplicated in qfiledialog.cpp
static TQString getWindowsRegString( HKEY key, const TQString &subKey )
{
TQString s;
QT_WA( {
char buf[1024];
DWORD bsz = sizeof(buf);
int r = RegQueryValueEx( key, (TCHAR*)subKey.ucs2(), 0, 0, (LPBYTE)buf, &bsz );
if ( r == ERROR_SUCCESS ) {
s = TQString::fromUcs2( (unsigned short *)buf );
} else if ( r == ERROR_MORE_DATA ) {
char *ptr = new char[bsz+1];
r = RegQueryValueEx( key, (TCHAR*)subKey.ucs2(), 0, 0, (LPBYTE)ptr, &bsz );
if ( r == ERROR_SUCCESS )
s = ptr;
delete [] ptr;
}
} , {
char buf[512];
DWORD bsz = sizeof(buf);
int r = RegQueryValueExA( key, subKey.local8Bit(), 0, 0, (LPBYTE)buf, &bsz );
if ( r == ERROR_SUCCESS ) {
s = buf;
} else if ( r == ERROR_MORE_DATA ) {
char *ptr = new char[bsz+1];
r = RegQueryValueExA( key, subKey.local8Bit(), 0, 0, (LPBYTE)ptr, &bsz );
if ( r == ERROR_SUCCESS )
s = ptr;
delete [] ptr;
}
} );
return s;
}
static bool getDnsParamsFromRegistry( const TQString &path,
TQString *domainName, TQString *nameServer, TQString *searchList )
{
HKEY k;
int r;
QT_WA( {
r = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
(TCHAR*)path.ucs2(),
0, KEY_READ, &k );
} , {
r = RegOpenKeyExA( HKEY_LOCAL_MACHINE,
path,
0, KEY_READ, &k );
} );
if ( r == ERROR_SUCCESS ) {
*domainName = getWindowsRegString( k, "DhcpDomain" );
if ( domainName->isEmpty() )
*domainName = getWindowsRegString( k, "Domain" );
*nameServer = getWindowsRegString( k, "DhcpNameServer" );
if ( nameServer->isEmpty() )
*nameServer = getWindowsRegString( k, "NameServer" );
*searchList = getWindowsRegString( k, "SearchList" );
}
RegCloseKey( k );
return r == ERROR_SUCCESS;
}
void TQDns::doResInit()
{
char separator = 0;
if ( ns )
delete ns;
ns = new TQPtrList<TQHostAddress>;
ns->setAutoDelete( TRUE );
domains = new TQStrList( TRUE );
domains->setAutoDelete( TRUE );
TQString domainName, nameServer, searchList;
bool gotNetworkParams = FALSE;
// try the API call GetNetworkParams() first and use registry lookup only
// as a fallback
#ifdef Q_OS_TEMP
HINSTANCE hinstLib = LoadLibraryW( L"iphlpapi" );
#else
HINSTANCE hinstLib = LoadLibraryA( "iphlpapi" );
#endif
if ( hinstLib != 0 ) {
#ifdef Q_OS_TEMP
GNP getNetworkParams = (GNP) GetProcAddressW( hinstLib, L"GetNetworkParams" );
#else
GNP getNetworkParams = (GNP) GetProcAddress( hinstLib, "GetNetworkParams" );
#endif
if ( getNetworkParams != 0 ) {
ULONG l = 0;
DWORD res;
res = getNetworkParams( 0, &l );
if ( res == ERROR_BUFFER_OVERFLOW ) {
FIXED_INFO *finfo = (FIXED_INFO*)new char[l];
res = getNetworkParams( finfo, &l );
if ( res == ERROR_SUCCESS ) {
domainName = finfo->DomainName;
nameServer = "";
IP_ADDR_STRING *dnsServer = &finfo->DnsServerList;
while ( dnsServer != 0 ) {
nameServer += dnsServer->IpAddress.String;
dnsServer = dnsServer->Next;
if ( dnsServer != 0 )
nameServer += " ";
}
searchList = "";
separator = ' ';
gotNetworkParams = TRUE;
}
delete[] finfo;
}
}
FreeLibrary( hinstLib );
}
if ( !gotNetworkParams ) {
if ( getDnsParamsFromRegistry(
TQString( "System\\CurrentControlSet\\Services\\Tcpip\\Parameters" ),
&domainName, &nameServer, &searchList )) {
// for NT
separator = ' ';
} else if ( getDnsParamsFromRegistry(
TQString( "System\\CurrentControlSet\\Services\\VxD\\MSTCP" ),
&domainName, &nameServer, &searchList )) {
// for 95/98
separator = ',';
} else {
// Could not access the TCP/IP parameters
domainName = "";
nameServer = "127.0.0.1";
searchList = "";
separator = ' ';
}
}
nameServer = nameServer.simplifyWhiteSpace();
int first, last;
if ( !nameServer.isEmpty() ) {
first = 0;
do {
last = nameServer.find( separator, first );
if ( last < 0 )
last = nameServer.length();
TQDns tmp( nameServer.mid( first, last-first ), TQDns::A );
TQValueList<TQHostAddress> address = tmp.addresses();
TQ_LONG i = address.count();
while( i )
ns->append( new TQHostAddress(address[--i]) );
first = last+1;
} while( first < (int)nameServer.length() );
}
searchList = searchList + " " + domainName;
searchList = searchList.simplifyWhiteSpace().lower();
first = 0;
do {
last = searchList.find( separator, first );
if ( last < 0 )
last = searchList.length();
domains->append( tqstrdup( searchList.mid( first, last-first ) ) );
first = last+1;
} while( first < (int)searchList.length() );
}
#elif defined(Q_OS_UNIX)
#if defined(Q_DNS_SYNCHRONOUS)
void TQDns::doSynchronousLookup()
{
if ( t!=None && !l.isEmpty() ) {
TQValueListIterator<TQString> it = n.begin();
TQValueListIterator<TQString> end = n.end();
int type;
switch( t ) {
case TQDns::A:
type = 1;
break;
case TQDns::Aaaa:
type = 28;
break;
case TQDns::Mx:
type = 15;
break;
case TQDns::Srv:
type = 33;
break;
case TQDns::Cname:
type = 5;
break;
case TQDns::Ptr:
type = 12;
break;
case TQDns::Txt:
type = 16;
break;
default:
type = (char)255; // any
break;
}
while( it != end ) {
TQString s = *it;
it++;
TQByteArray ba( 512 );
int len = res_search( s.latin1(), 1, type, (uchar*)ba.data(), ba.size() );
if ( len > 0 ) {
ba.resize( len );
TQDnsQuery * query = new TQDnsQuery;
query->started = now();
query->id = ++::id;
query->t = t;
query->l = s;
TQDnsAnswer a( ba, query );
a.parse();
} else if ( len == -1 ) {
// res_search error
}
}
emit resultsReady();
}
}
#endif
void TQDns::doResInit()
{
if ( ns )
return;
ns = new TQPtrList<TQHostAddress>;
ns->setAutoDelete( TRUE );
domains = new TQStrList( TRUE );
domains->setAutoDelete( TRUE );
// read resolv.conf manually.
TQFile resolvConf("/etc/resolv.conf");
if (resolvConf.open(IO_ReadOnly)) {
TQTextStream stream( &resolvConf );
TQString line;
while ( !stream.atEnd() ) {
line = stream.readLine();
TQStringList list = TQStringList::split( " ", line );
if ( line.startsWith( "#" ) || list.count() < 2 )
continue;
const TQString type = list[0].lower();
if ( type == "nameserver" ) {
TQHostAddress *address = new TQHostAddress();
if ( address->setAddress( TQString(list[1]) ) ) {
// only add ipv6 addresses from resolv.conf if
// this host supports ipv6.
if ( address->isIPv4Address() || ipv6support )
ns->append( address );
else
delete address;
} else {
delete address;
}
} else if ( type == "search" ) {
TQStringList srch = TQStringList::split( " ", list[1] );
for ( TQStringList::Iterator i = srch.begin(); i != srch.end(); ++i )
domains->append( (*i).lower() );
} else if ( type == "domain" ) {
domains->append( list[1].lower() );
}
}
}
if (ns->isEmpty()) {
#if defined(Q_MODERN_RES_API)
struct __res_state res;
res_ninit( &res );
int i;
// find the name servers to use
for( i=0; i < MAXNS && i < res.nscount; i++ )
ns->append( new TQHostAddress( ntohl( res.nsaddr_list[i].sin_addr.s_addr ) ) );
# if defined(MAXDFLSRCH)
for( i=0; i < MAXDFLSRCH; i++ ) {
if ( res.dnsrch[i] && *(res.dnsrch[i]) )
domains->append( TQString::fromLatin1( res.dnsrch[i] ).lower() );
else
break;
}
# endif
if ( *res.defdname )
domains->append( TQString::fromLatin1( res.defdname ).lower() );
#else
qdns_res_init();
int i;
// find the name servers to use
for( i=0; i < MAXNS && i < _res.nscount; i++ )
ns->append( new TQHostAddress( ntohl( _res.nsaddr_list[i].sin_addr.s_addr ) ) );
# if defined(MAXDFLSRCH)
for( i=0; i < MAXDFLSRCH; i++ ) {
if ( _res.dnsrch[i] && *(_res.dnsrch[i]) )
domains->append( TQString::fromLatin1( _res.dnsrch[i] ).lower() );
else
break;
}
# endif
if ( *_res.defdname )
domains->append( TQString::fromLatin1( _res.defdname ).lower() );
#endif
// the code above adds "0.0.0.0" as a name server at the slightest
// hint of trouble. so remove those again.
ns->first();
while( ns->current() ) {
if ( ns->current()->isNull() )
delete ns->take();
else
ns->next();
}
}
TQFile hosts( TQString::fromLatin1( "/etc/hosts" ) );
if ( hosts.open( IO_ReadOnly ) ) {
// read the /etc/hosts file, creating long-life A and PTR RRs
// for the things we find.
TQTextStream i( &hosts );
TQString line;
while( !i.atEnd() ) {
line = i.readLine().simplifyWhiteSpace().lower();
uint n = 0;
while( n < line.length() && line[(int)n] != '#' )
n++;
line.truncate( n );
n = 0;
while( n < line.length() && !line[(int)n].isSpace() )
n++;
TQString ip = line.left( n );
TQHostAddress a;
a.setAddress( ip );
if ( ( a.isIPv4Address() || a.isIPv6Address() ) && !a.isNull() ) {
bool first = TRUE;
line = line.mid( n+1 );
n = 0;
while( n < line.length() && !line[(int)n].isSpace() )
n++;
TQString hostname = line.left( n );
// ### in case of bad syntax, hostname is invalid. do we care?
if ( n ) {
TQDnsRR * rr = new TQDnsRR( hostname );
if ( a.isIPv4Address() )
rr->t = TQDns::A;
else
rr->t = TQDns::Aaaa;
rr->address = a;
rr->deleteTime = UINT_MAX;
rr->expireTime = UINT_MAX;
rr->current = TRUE;
if ( first ) {
first = FALSE;
TQDnsRR * ptr = new TQDnsRR( TQDns::toInAddrArpaDomain( a ) );
ptr->t = TQDns::Ptr;
ptr->target = hostname;
ptr->deleteTime = UINT_MAX;
ptr->expireTime = UINT_MAX;
ptr->current = TRUE;
}
}
}
}
}
}
#endif
#endif // QT_NO_DNS