dns.cpp
/*
Copyright (c) 2005-2023 by Jakob Schröter <js@camaya.net>
This file is part of the gloox library. http://camaya.net/gloox
This software is distributed under a license. The full license
agreement can be found in the file LICENSE in this distribution.
This software may not be copied, modified, sold or distributed
other than expressed in the named license agreement.
This software is distributed without any warranty.
*/
#include "config.h"
#include "gloox.h"
#include "dns.h"
#include "util.h"
#ifndef _WIN32_WCE
# include <sys/types.h>
#endif
#include <stdio.h>
#include <string.h>
#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ )
# include <netinet/in.h>
# include <arpa/nameser.h>
# include <resolv.h>
# include <netdb.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <sys/un.h>
# include <unistd.h>
# include <errno.h>
#endif
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
# include <winsock2.h>
# include <ws2tcpip.h>
#elif defined( _WIN32_WCE )
# include <winsock2.h>
#endif
#ifdef HAVE_WINDNS_H
# include <windns.h>
#endif
#define SRV_COST (RRFIXEDSZ+0)
#define SRV_WEIGHT (RRFIXEDSZ+2)
#define SRV_PORT (RRFIXEDSZ+4)
#define SRV_SERVER (RRFIXEDSZ+6)
#define SRV_FIXEDSZ (RRFIXEDSZ+6)
#ifndef T_SRV
# define T_SRV 33
#endif
// mingw
#ifndef DNS_TYPE_SRV
# define DNS_TYPE_SRV 33
#endif
#ifndef NS_CMPRSFLGS
# define NS_CMPRSFLGS 0xc0
#endif
#ifndef C_IN
# define C_IN 1
#endif
#ifndef INVALID_SOCKET
# define INVALID_SOCKET -1
#endif
#define XMPP_PORT 5222
namespace gloox
{
#if defined( HAVE_RES_QUERYDOMAIN ) && defined( HAVE_DN_SKIPNAME ) && defined( HAVE_RES_QUERY )
DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
const std::string& domain, const LogSink& logInstance )
{
buffer srvbuf;
bool error = false;
const std::string dname = "_" + service + "._" + proto;
if( !domain.empty() )
srvbuf.len = res_querydomain( dname.c_str(), const_cast<char*>( domain.c_str() ),
C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
else
srvbuf.len = res_query( dname.c_str(), C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
if( srvbuf.len < 0 )
return defaultHostMap( domain, logInstance );
HEADER* hdr = reinterpret_cast<HEADER*>( srvbuf.buf );
unsigned char* here = srvbuf.buf + NS_HFIXEDSZ;
if( srvbuf.len < NS_HFIXEDSZ )
error = true;
if( hdr->rcode >= 1 && hdr->rcode <= 5 )
error = true;
if( ntohs( hdr->ancount ) == 0 )
error = true;
if( ntohs( hdr->ancount ) > NS_PACKETSZ )
error = true;
int cnt;
for( cnt = ntohs( hdr->qdcount ); cnt > 0; --cnt )
{
int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
here += strlen + NS_QFIXEDSZ;
}
unsigned char* srv[NS_PACKETSZ];
int srvnum = 0;
for( cnt = ntohs( hdr->ancount ); cnt > 0; --cnt )
{
int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
here += strlen;
srv[srvnum++] = here;
here += SRV_FIXEDSZ;
here += dn_skipname( here, srvbuf.buf + srvbuf.len );
}
if( error )
{
return defaultHostMap( domain, logInstance );
}
// (q)sort here
HostMap servers;
for( cnt = 0; cnt < srvnum; ++cnt )
{
char srvname[NS_MAXDNAME];
srvname[0] = '\0';
if( dn_expand( srvbuf.buf, srvbuf.buf + NS_PACKETSZ,
srv[cnt] + SRV_SERVER, srvname, NS_MAXDNAME ) < 0
|| !(*srvname) )
continue;
unsigned char* c = srv[cnt] + SRV_PORT;
servers.insert( std::make_pair( static_cast<char*>( srvname ), ntohs( c[1] << 8 | c[0] ) ) );
}
if( !servers.size() )
return defaultHostMap( domain, logInstance );
return servers;
}
#elif defined( _WIN32 ) && defined( HAVE_WINDNS_H ) && !defined( __MINGW32__ )
DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
const std::string& domain, const LogSink& logInstance )
{
const std::string dname = "_" + service + "._" + proto + "." + domain;
bool error = false;
DNS::HostMap servers;
DNS_RECORD* pRecord = NULL;
DNS_STATUS status = DnsQuery_UTF8( dname.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &pRecord, NULL );
if( status == ERROR_SUCCESS )
{
// NOTE: DnsQuery_UTF8 and DnsQuery_A really should have been defined with
// PDNS_RECORDA instead of PDNS_RECORD, since that's what it is (even with _UNICODE defined).
// We'll correct for that mistake with a cast.
DNS_RECORDA* pRec = (DNS_RECORDA*)pRecord;
do
{
if( pRec->wType == DNS_TYPE_SRV )
{
servers[pRec->Data.SRV.pNameTarget] = pRec->Data.SRV.wPort;
}
pRec = pRec->pNext;
}
while( pRec != NULL );
DnsRecordListFree( pRecord, DnsFreeRecordList );
}
else
{
logInstance.warn( LogAreaClassDns, "DnsQuery_UTF8() failed: " + util::int2string( status ) );
error = true;
}
if( error || !servers.size() )
{
servers = defaultHostMap( domain, logInstance );
}
return servers;
}
#else
DNS::HostMap DNS::resolve( const std::string& /*service*/, const std::string& /*proto*/,
const std::string& domain, const LogSink& logInstance )
{
logInstance.warn( LogAreaClassDns, "Notice: gloox does not support SRV "
"records on this platform. Using A records instead." );
return defaultHostMap( domain, logInstance );
}
#endif
DNS::HostMap DNS::defaultHostMap( const std::string& domain, const LogSink& logInstance )
{
HostMap server;
logInstance.warn( LogAreaClassDns, "Notice: no SRV record found for "
+ domain + ", using default port." );
if( !domain.empty() )
server[domain] = XMPP_PORT;
return server;
}
#ifdef HAVE_GETADDRINFO
void DNS::resolve( struct addrinfo** res, const std::string& service, const std::string& proto,
const std::string& domain, const LogSink& logInstance )
{
logInstance.dbg( LogAreaClassDns, "Resolving: _" + service + "._" + proto + "." + domain );
struct addrinfo hints;
if( proto == "tcp" )
hints.ai_socktype = SOCK_STREAM;
else if( proto == "udp" )
hints.ai_socktype = SOCK_DGRAM;
else
{
logInstance.err( LogAreaClassDns, "Unknown/Invalid protocol: " + proto );
}
memset( &hints, '\0', sizeof( hints ) );
hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
hints.ai_socktype = SOCK_STREAM;
int e = getaddrinfo( domain.c_str(), service.c_str(), &hints, res );
if( e )
logInstance.err( LogAreaClassDns, "getaddrinfo() failed" );
}
int DNS::connect( const std::string& host, const LogSink& logInstance )
{
struct addrinfo* results = 0;
resolve( &results, host, logInstance );
if( !results )
{
logInstance.err( LogAreaClassDns, "host not found: " + host );
return -ConnDnsError;
}
struct addrinfo* runp = results;
while( runp )
{
int fd = DNS::connect( runp, logInstance );
if( fd >= 0 )
{
freeaddrinfo( results );
return fd;
}
runp = runp->ai_next;
}
freeaddrinfo( results );
return -ConnConnectionRefused;
}
int DNS::connect( struct addrinfo* res, const LogSink& logInstance )
{
if( !res )
return -1;
int fd = getSocket( res->ai_family, res->ai_socktype, res->ai_protocol, logInstance );
if( fd < 0 )
return fd;
if( ::connect( fd, res->ai_addr, res->ai_addrlen ) == 0 )
{
char ip[NI_MAXHOST];
char port[NI_MAXSERV];
if( getnameinfo( res->ai_addr, res->ai_addrlen,
ip, sizeof( ip ),
port, sizeof( port ),
NI_NUMERICHOST | NI_NUMERICSERV ) )
{
//FIXME do we need to handle this? How? Can it actually happen at all?
// printf( "could not get numeric hostname");
}
if( res->ai_canonname )
logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( res->ai_canonname ).append( " (" ).append( ip ).append( "), port " ).append( port ) );
else
logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( ip ).append( ":" ).append( port ) );
return fd;
}
std::string message = "connect() failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
logInstance.dbg( LogAreaClassDns, message );
closeSocket( fd, logInstance );
return -ConnConnectionRefused;
}
#else
int DNS::connect( const std::string& host, const LogSink& logInstance )
{
HostMap hosts = resolve( host, logInstance );
if( hosts.size() == 0 )
return -ConnDnsError;
HostMap::const_iterator it = hosts.begin();
for( ; it != hosts.end(); ++it )
{
int fd = DNS::connect( (*it).first, (*it).second, logInstance );
if( fd >= 0 )
return fd;
}
return -ConnConnectionRefused;
}
#endif
int DNS::getSocket( const LogSink& logInstance )
{
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
WSADATA wsaData;
if( WSAStartup( MAKEWORD( 1, 1 ), &wsaData ) != 0 )
{
logInstance.dbg( LogAreaClassDns, "WSAStartup() failed. WSAGetLastError: "
+ util::int2string( ::WSAGetLastError() ) );
return -ConnDnsError;
}
#endif
int protocol = IPPROTO_TCP;
#if !defined( __APPLE__ ) // Sandboxing on Apple doesn't like you to use getprotobyname
struct protoent* prot;
if( ( prot = getprotobyname( "tcp" ) ) != 0 )
{
protocol = prot->p_proto;
}
else
{
std::string message = "getprotobyname( \"tcp\" ) failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() )
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
+ ". Falling back to IPPROTO_TCP: " + util::int2string( IPPROTO_TCP );
logInstance.dbg( LogAreaClassDns, message );
// Do not return an error. We'll fall back to IPPROTO_TCP.
}
#endif // !defined( __APPLE__ )
return getSocket( PF_INET, SOCK_STREAM, protocol, logInstance );
}
int DNS::getSocket( int af, int socktype, int proto, const LogSink& logInstance )
{
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
SOCKET fd;
#else
int fd;
#endif
if( ( fd = socket( af, socktype, proto ) ) == INVALID_SOCKET )
{
std::string message = "getSocket( "
+ util::int2string( af ) + ", "
+ util::int2string( socktype ) + ", "
+ util::int2string( proto )
+ " ) failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
logInstance.dbg( LogAreaClassDns, message );
cleanup( logInstance );
return -ConnConnectionRefused;
}
#ifdef HAVE_SETSOCKOPT
int timeout = 5000;
int reuseaddr = 1;
setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>( &timeout ), sizeof( timeout ) );
setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>( &reuseaddr ), sizeof( reuseaddr ) );
#endif
return static_cast<int>( fd );
}
#ifdef HAVE_GETADDRINFO
int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
{
struct addrinfo hints, *servinfo, *p;
int rv = 0;
int fd = 0;
memset( &hints, 0, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if( ( rv = getaddrinfo( host.c_str(), util::int2string( port ).c_str(), &hints, &servinfo ) ) != 0 )
{
logInstance.dbg( LogAreaClassDns, "getaddrinfo() failed for " + host + "." );
return -ConnDnsError;
}
for( p = servinfo; p != 0; p = p->ai_next )
{
if( ( fd = getSocket( p->ai_family, p->ai_socktype, p->ai_protocol, logInstance ) ) == -1 )
{
continue;
}
if( ::connect( fd, p->ai_addr, p->ai_addrlen ) == -1 )
{
closeSocket( fd, logInstance );
continue;
}
break;
}
if( p == 0 )
{
freeaddrinfo( servinfo );
std::string message = "Connection to " + host + ":" + util::int2string( port ) + " failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
logInstance.dbg( LogAreaClassDns, message );
return -ConnConnectionRefused;
}
freeaddrinfo( servinfo );
return fd;
}
#else // HAVE_GETADDRINFO
int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
{
int fd = getSocket( logInstance );
if( fd < 0 )
return fd;
struct hostent* h;
if( ( h = gethostbyname( host.c_str() ) ) == 0 )
{
logInstance.dbg( LogAreaClassDns, "gethostbyname() failed for " + host + "." );
cleanup( logInstance );
closeSocket( fd, logInstance );
return -ConnDnsError;
}
struct sockaddr_in target;
target.sin_family = AF_INET;
target.sin_port = htons( static_cast<unsigned short int>( port ) );
if( h->h_length != sizeof( struct in_addr ) )
{
logInstance.dbg( LogAreaClassDns, "gethostbyname() returned unexpected structure." );
cleanup( logInstance );
closeSocket( fd, logInstance );
return -ConnDnsError;
}
else
{
memcpy( &target.sin_addr, h->h_addr, sizeof( struct in_addr ) );
}
logInstance.dbg( LogAreaClassDns, "Connecting to " + host
+ " (" + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
memset( target.sin_zero, '\0', 8 );
if( ::connect( fd, reinterpret_cast<struct sockaddr *>( &target ), sizeof( struct sockaddr ) ) == 0 )
{
logInstance.dbg( LogAreaClassDns, "Connected to " + host + " ("
+ inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
return fd;
}
std::string message = "Connection to " + host + " ("
+ inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ") failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
logInstance.dbg( LogAreaClassDns, message );
closeSocket( fd, logInstance );
return -ConnConnectionRefused;
}
#endif // HAVE_GETADDRINFO
void DNS::closeSocket( int fd, const LogSink& logInstance )
{
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
int result = closesocket( fd );
#else
int result = close( fd );
#endif
if( result != 0 )
{
std::string message = "closeSocket() failed. "
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
"WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
#else
"errno: " + util::int2string( errno ) + ": " + strerror( errno );
#endif
logInstance.dbg( LogAreaClassDns, message );
}
}
void DNS::cleanup( const LogSink& logInstance )
{
#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
if( WSACleanup() != 0 )
{
logInstance.dbg( LogAreaClassDns, "WSACleanup() failed. WSAGetLastError: "
+ util::int2string( ::WSAGetLastError() ) );
}
#else
(void)logInstance;
#endif
}
}
gloox::DNS::connect
static int connect(const std::string &host, const LogSink &logInstance)
Definition dns.cpp:247
gloox::DNS::resolve
static HostMap resolve(const std::string &service, const std::string &proto, const std::string &domain, const LogSink &logInstance)
Definition dns.cpp:83
gloox::DNS::HostMap
std::map< std::string, int > HostMap
Definition dns.h:68
gloox::DNS::getSocket
static int getSocket(const LogSink &logInstance)
Definition dns.cpp:339
gloox::DNS::closeSocket
static void closeSocket(int fd, const LogSink &logInstance)
Definition dns.cpp:518
gloox::LogSink
An implementation of log sink and source.
Definition logsink.h:39
gloox::LogSink::warn
void warn(LogArea area, const std::string &message) const
Definition logsink.h:75
gloox::LogSink::dbg
void dbg(LogArea area, const std::string &message) const
Definition logsink.h:66
gloox::LogSink::err
void err(LogArea area, const std::string &message) const
Definition logsink.h:84
gloox
The namespace for the gloox library.
Definition adhoc.cpp:28
gloox::ConnConnectionRefused
@ ConnConnectionRefused
Definition gloox.h:698
gloox::ConnDnsError
@ ConnDnsError
Definition gloox.h:700
gloox::LogAreaClassDns
@ LogAreaClassDns
Definition gloox.h:1059