gloox  1.0
dns.cpp
00001 /*
00002   Copyright (c) 2005-2009 by Jakob Schroeter <js@camaya.net>
00003   This file is part of the gloox library. http://camaya.net/gloox
00004 
00005   This software is distributed under a license. The full license
00006   agreement can be found in the file LICENSE in this distribution.
00007   This software may not be copied, modified, sold or distributed
00008   other than expressed in the named license agreement.
00009 
00010   This software is distributed without any warranty.
00011 */
00012 
00013 
00014 #include "config.h"
00015 
00016 #include "gloox.h"
00017 #include "dns.h"
00018 #include "util.h"
00019 
00020 #ifndef _WIN32_WCE
00021 # include <sys/types.h>
00022 #endif
00023 
00024 #include <stdio.h>
00025 
00026 #if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ )
00027 # include <netinet/in.h>
00028 # include <arpa/nameser.h>
00029 # include <resolv.h>
00030 # include <netdb.h>
00031 # include <arpa/inet.h>
00032 # include <sys/socket.h>
00033 # include <sys/un.h>
00034 # include <unistd.h>
00035 # include <errno.h>
00036 #endif
00037 
00038 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00039 # include <winsock.h>
00040 #elif defined( _WIN32_WCE )
00041 # include <winsock2.h>
00042 #endif
00043 
00044 #ifdef HAVE_WINDNS_H
00045 # include <windns.h>
00046 #endif
00047 
00048 #define SRV_COST    (RRFIXEDSZ+0)
00049 #define SRV_WEIGHT  (RRFIXEDSZ+2)
00050 #define SRV_PORT    (RRFIXEDSZ+4)
00051 #define SRV_SERVER  (RRFIXEDSZ+6)
00052 #define SRV_FIXEDSZ (RRFIXEDSZ+6)
00053 
00054 #ifndef T_SRV
00055 # define T_SRV 33
00056 #endif
00057 
00058 // mingw
00059 #ifndef DNS_TYPE_SRV
00060 # define DNS_TYPE_SRV 33
00061 #endif
00062 
00063 #ifndef NS_CMPRSFLGS
00064 # define NS_CMPRSFLGS 0xc0
00065 #endif
00066 
00067 #ifndef C_IN
00068 # define C_IN 1
00069 #endif
00070 
00071 #ifndef INVALID_SOCKET
00072 # define INVALID_SOCKET -1
00073 #endif
00074 
00075 #define XMPP_PORT 5222
00076 
00077 namespace gloox
00078 {
00079 
00080 #if defined( HAVE_RES_QUERYDOMAIN ) && defined( HAVE_DN_SKIPNAME ) && defined( HAVE_RES_QUERY )
00081   DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
00082                              const std::string& domain, const LogSink& logInstance )
00083   {
00084     buffer srvbuf;
00085     bool error = false;
00086 
00087     const std::string dname = "_" +  service + "._" + proto;
00088 
00089     if( !domain.empty() )
00090       srvbuf.len = res_querydomain( dname.c_str(), const_cast<char*>( domain.c_str() ),
00091                                     C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
00092     else
00093       srvbuf.len = res_query( dname.c_str(), C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
00094 
00095     if( srvbuf.len < 0 )
00096       return defaultHostMap( domain, logInstance );
00097 
00098     HEADER* hdr = (HEADER*)srvbuf.buf;
00099     unsigned char* here = srvbuf.buf + NS_HFIXEDSZ;
00100 
00101     if( ( hdr->tc ) || ( srvbuf.len < NS_HFIXEDSZ ) )
00102       error = true;
00103 
00104     if( hdr->rcode >= 1 && hdr->rcode <= 5 )
00105       error = true;
00106 
00107     if( ntohs( hdr->ancount ) == 0 )
00108       error = true;
00109 
00110     if( ntohs( hdr->ancount ) > NS_PACKETSZ )
00111       error = true;
00112 
00113     int cnt;
00114     for( cnt = ntohs( hdr->qdcount ); cnt > 0; --cnt )
00115     {
00116       int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
00117       here += strlen + NS_QFIXEDSZ;
00118     }
00119 
00120     unsigned char* srv[NS_PACKETSZ];
00121     int srvnum = 0;
00122     for( cnt = ntohs( hdr->ancount ); cnt > 0; --cnt )
00123     {
00124       int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
00125       here += strlen;
00126       srv[srvnum++] = here;
00127       here += SRV_FIXEDSZ;
00128       here += dn_skipname( here, srvbuf.buf + srvbuf.len );
00129     }
00130 
00131     if( error )
00132     {
00133       return defaultHostMap( domain, logInstance );
00134     }
00135 
00136     // (q)sort here
00137 
00138     HostMap servers;
00139     for( cnt = 0; cnt < srvnum; ++cnt )
00140     {
00141       char srvname[NS_MAXDNAME];
00142       srvname[0] = '\0';
00143 
00144       if( dn_expand( srvbuf.buf, srvbuf.buf + NS_PACKETSZ,
00145                      srv[cnt] + SRV_SERVER, srvname, NS_MAXDNAME ) < 0
00146           || !(*srvname) )
00147         continue;
00148 
00149       unsigned char* c = srv[cnt] + SRV_PORT;
00150       servers.insert( std::make_pair( (char*)srvname, ntohs( c[1] << 8 | c[0] ) ) );
00151     }
00152 
00153     if( !servers.size() )
00154       return defaultHostMap( domain, logInstance );
00155 
00156     return servers;
00157   }
00158 
00159 #elif defined( _WIN32 ) && defined( HAVE_WINDNS_H )
00160   DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
00161                              const std::string& domain, const LogSink& logInstance )
00162   {
00163     const std::string dname = "_" +  service + "._" + proto + "." + domain;
00164     bool error = false;
00165 
00166     DNS::HostMap servers;
00167     DNS_RECORD* pRecord = NULL;
00168     DNS_STATUS status = DnsQuery_UTF8( dname.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &pRecord, NULL );
00169     if( status == ERROR_SUCCESS )
00170     {
00171       DNS_RECORD* pRec = pRecord;
00172       do
00173       {
00174         if( pRec->wType == DNS_TYPE_SRV )
00175         {
00176           servers[pRec->Data.SRV.pNameTarget] = pRec->Data.SRV.wPort;
00177         }
00178         pRec = pRec->pNext;
00179       }
00180       while( pRec != NULL );
00181       DnsRecordListFree( pRecord, DnsFreeRecordList );
00182     }
00183     else
00184     {
00185       logInstance.warn( LogAreaClassDns, "DnsQuery_UTF8() failed: " + util::int2string( status ) );
00186       error = true;
00187     }
00188 
00189     if( error || !servers.size() )
00190     {
00191       servers = defaultHostMap( domain, logInstance );
00192     }
00193 
00194     return servers;
00195   }
00196 
00197 #else
00198   DNS::HostMap DNS::resolve( const std::string& /*service*/, const std::string& /*proto*/,
00199                              const std::string& domain, const LogSink& logInstance )
00200   {
00201     logInstance.warn( LogAreaClassDns, "Notice: gloox does not support SRV "
00202                         "records on this platform. Using A records instead." );
00203     return defaultHostMap( domain, logInstance );
00204   }
00205 #endif
00206 
00207   DNS::HostMap DNS::defaultHostMap( const std::string& domain, const LogSink& logInstance )
00208   {
00209     HostMap server;
00210 
00211     logInstance.warn( LogAreaClassDns, "Notice: no SRV record found for "
00212                                           + domain + ", using default port." );
00213 
00214     if( !domain.empty() )
00215       server[domain] = XMPP_PORT;
00216 
00217     return server;
00218   }
00219 
00220 #ifdef HAVE_GETADDRINFO
00221   void DNS::resolve( struct addrinfo** res, const std::string& service, const std::string& proto,
00222                      const std::string& domain, const LogSink& logInstance )
00223   {
00224     logInstance.dbg( LogAreaClassDns, "Resolving: _" +  service + "._" + proto + "." + domain );
00225     struct addrinfo hints;
00226     if( proto == "tcp" )
00227       hints.ai_socktype = SOCK_STREAM;
00228     else if( proto == "udp" )
00229       hints.ai_socktype = SOCK_DGRAM;
00230     else
00231     {
00232       logInstance.err( LogAreaClassDns, "Unknown/Invalid protocol: " + proto );
00233     }
00234     memset( &hints, '\0', sizeof( hints ) );
00235     hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
00236     hints.ai_socktype = SOCK_STREAM;
00237     int e = getaddrinfo( domain.c_str(), service.c_str(), &hints, res );
00238     if( e )
00239       logInstance.err( LogAreaClassDns, "getaddrinfo() failed" );
00240   }
00241 
00242   int DNS::connect( const std::string& host, const LogSink& logInstance )
00243   {
00244     struct addrinfo* results = 0;
00245 
00246     resolve( &results, host, logInstance );
00247     if( !results )
00248     {
00249       logInstance.err( LogAreaClassDns, "host not found: " + host );
00250       return -ConnDnsError;
00251     }
00252 
00253     struct addrinfo* runp = results;
00254     while( runp )
00255     {
00256       int fd = DNS::connect( runp, logInstance );
00257       if( fd >= 0 )
00258         return fd;
00259 
00260       runp = runp->ai_next;
00261     }
00262 
00263     freeaddrinfo( results );
00264 
00265     return -ConnConnectionRefused;
00266   }
00267 
00268   int DNS::connect( struct addrinfo* res, const LogSink& logInstance )
00269   {
00270     if( !res )
00271       return -1;
00272 
00273     int fd = getSocket( res->ai_family, res->ai_socktype, res->ai_protocol, logInstance );
00274     if( fd < 0 )
00275       return fd;
00276 
00277     if( ::connect( fd, res->ai_addr, res->ai_addrlen ) == 0 )
00278     {
00279       char ip[NI_MAXHOST];
00280       char port[NI_MAXSERV];
00281 
00282       if( getnameinfo( res->ai_addr, sizeof( sockaddr ),
00283                        ip, sizeof( ip ),
00284                        port, sizeof( port ),
00285                        NI_NUMERICHOST | NI_NUMERICSERV ) )
00286       {
00287         //FIXME do we need to handle this? How? Can it actually happen at all?
00288 //         printf( "could not get numeric hostname");
00289       }
00290 
00291       if( res->ai_canonname )
00292         logInstance.dbg( LogAreaClassDns, "Connecting to " + std::string( res->ai_canonname )
00293                                           + " (" + ip + "), port " + port );
00294       else
00295         logInstance.dbg( LogAreaClassDns, "Connecting to " + ip + ":" + port );
00296 
00297       return fd;
00298     }
00299 
00300     std::string message = "connect() failed. "
00301 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00302         "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
00303 #else
00304         "errno: " + util::int2string( errno );
00305 #endif
00306     logInstance.dbg( LogAreaClassDns, message );
00307 
00308     closeSocket( fd, logInstance );
00309     return -ConnConnectionRefused;
00310   }
00311 
00312 #else
00313 
00314   int DNS::connect( const std::string& host, const LogSink& logInstance )
00315   {
00316     HostMap hosts = resolve( host, logInstance );
00317     if( hosts.size() == 0 )
00318       return -ConnDnsError;
00319 
00320     HostMap::const_iterator it = hosts.begin();
00321     for( ; it != hosts.end(); ++it )
00322     {
00323       int fd = DNS::connect( (*it).first, (*it).second, logInstance );
00324       if( fd >= 0 )
00325         return fd;
00326     }
00327 
00328     return -ConnConnectionRefused;
00329   }
00330 #endif
00331 
00332   int DNS::getSocket( const LogSink& logInstance )
00333   {
00334 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00335     WSADATA wsaData;
00336     if( WSAStartup( MAKEWORD( 1, 1 ), &wsaData ) != 0 )
00337     {
00338       logInstance.dbg( LogAreaClassDns, "WSAStartup() failed. WSAGetLastError: "
00339                                         + util::int2string( ::WSAGetLastError() ) );
00340       return -ConnDnsError;
00341     }
00342 #endif
00343 
00344     int protocol = IPPROTO_TCP;
00345     struct protoent* prot;
00346     if( ( prot = getprotobyname( "tcp" ) ) != 0 )
00347     {
00348       protocol = prot->p_proto;
00349     }
00350     else
00351     {
00352       std::string message = "getprotobyname( \"tcp\" ) failed. "
00353 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00354           "WSAGetLastError: " + util::int2string( ::WSAGetLastError() )
00355 #else
00356           "errno: " + util::int2string( errno );
00357 #endif
00358           + ". Falling back to IPPROTO_TCP: " + util::int2string( IPPROTO_TCP );
00359       logInstance.dbg( LogAreaClassDns, message );
00360 
00361       // Do not return an error. We'll fall back to IPPROTO_TCP.
00362     }
00363 
00364     return getSocket( PF_INET, SOCK_STREAM, protocol, logInstance );
00365   }
00366 
00367   int DNS::getSocket( int af, int socktype, int proto, const LogSink& logInstance )
00368   {
00369 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00370     SOCKET fd;
00371 #else
00372     int fd;
00373 #endif
00374     if( ( fd = socket( af, socktype, proto ) ) == INVALID_SOCKET )
00375     {
00376       std::string message = "getSocket( "
00377           + util::int2string( af ) + ", "
00378           + util::int2string( socktype ) + ", "
00379           + util::int2string( proto )
00380           + " ) failed. "
00381 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00382           "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
00383 #else
00384           "errno: " + util::int2string( errno );
00385 #endif
00386       logInstance.dbg( LogAreaClassDns, message );
00387 
00388       cleanup( logInstance );
00389       return -ConnConnectionRefused;
00390     }
00391 
00392 #ifdef HAVE_SETSOCKOPT
00393     int timeout = 5000;
00394     setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof( timeout ) );
00395     setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (char*)&timeout, sizeof( timeout ) );
00396 #endif
00397 
00398     return (int)fd;
00399   }
00400 
00401   int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
00402   {
00403     int fd = getSocket( logInstance );
00404     if( fd < 0 )
00405       return fd;
00406 
00407     struct hostent* h;
00408     if( ( h = gethostbyname( host.c_str() ) ) == 0 )
00409     {
00410       logInstance.dbg( LogAreaClassDns, "gethostbyname() failed for " + host + "." );
00411       cleanup( logInstance );
00412       return -ConnDnsError;
00413     }
00414 
00415     struct sockaddr_in target;
00416     target.sin_family = AF_INET;
00417     target.sin_port = htons( static_cast<unsigned short int>( port ) );
00418 
00419     if( h->h_length != sizeof( struct in_addr ) )
00420     {
00421       logInstance.dbg( LogAreaClassDns, "gethostbyname() returned unexpected structure." );
00422       cleanup( logInstance );
00423       return -ConnDnsError;
00424     }
00425     else
00426     {
00427       memcpy( &target.sin_addr, h->h_addr, sizeof( struct in_addr ) );
00428     }
00429 
00430     logInstance.dbg( LogAreaClassDns, "Connecting to " + host
00431         + " (" + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
00432 
00433     memset( target.sin_zero, '\0', 8 );
00434     if( ::connect( fd, (struct sockaddr *)&target, sizeof( struct sockaddr ) ) == 0 )
00435     {
00436       logInstance.dbg( LogAreaClassDns, "Connected to " + host + " ("
00437           + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
00438       return fd;
00439     }
00440 
00441     std::string message = "Connection to " + host + " ("
00442         + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ") failed. "
00443 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00444         "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
00445 #else
00446         "errno: " + util::int2string( errno );
00447 #endif
00448     logInstance.dbg( LogAreaClassDns, message );
00449 
00450     closeSocket( fd, logInstance );
00451     return -ConnConnectionRefused;
00452   }
00453 
00454   void DNS::closeSocket( int fd, const LogSink& logInstance )
00455   {
00456 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00457     int result = closesocket( fd );
00458 #else
00459     int result = close( fd );
00460 #endif
00461 
00462     if( result != 0 )
00463     {
00464       std::string message = "closeSocket() failed. "
00465 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00466           "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
00467 #else
00468           "errno: " + util::int2string( errno );
00469 #endif
00470       logInstance.dbg( LogAreaClassDns, message );
00471     }
00472   }
00473 
00474   void DNS::cleanup( const LogSink& logInstance )
00475   {
00476 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
00477     if( WSACleanup() != 0 )
00478     {
00479       logInstance.dbg( LogAreaClassDns, "WSACleanup() failed. WSAGetLastError: "
00480           + util::int2string( ::WSAGetLastError() ) );
00481     }
00482 #else
00483     (void)logInstance;
00484 #endif
00485   }
00486 
00487 }