gloox  1.0.22
dns.cpp
1 /*
2  Copyright (c) 2005-2017 by Jakob Schröter <js@camaya.net>
3  This file is part of the gloox library. http://camaya.net/gloox
4 
5  This software is distributed under a license. The full license
6  agreement can be found in the file LICENSE in this distribution.
7  This software may not be copied, modified, sold or distributed
8  other than expressed in the named license agreement.
9 
10  This software is distributed without any warranty.
11 */
12 
13 
14 #include "config.h"
15 
16 #include "gloox.h"
17 #include "dns.h"
18 #include "util.h"
19 
20 #ifndef _WIN32_WCE
21 # include <sys/types.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 
27 #if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ )
28 # include <netinet/in.h>
29 # include <arpa/nameser.h>
30 # include <resolv.h>
31 # include <netdb.h>
32 # include <arpa/inet.h>
33 # include <sys/socket.h>
34 # include <sys/un.h>
35 # include <unistd.h>
36 # include <errno.h>
37 #endif
38 
39 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
40 # include <winsock2.h>
41 # include <ws2tcpip.h>
42 #elif defined( _WIN32_WCE )
43 # include <winsock2.h>
44 #endif
45 
46 #ifdef HAVE_WINDNS_H
47 # include <windns.h>
48 #endif
49 
50 #define SRV_COST (RRFIXEDSZ+0)
51 #define SRV_WEIGHT (RRFIXEDSZ+2)
52 #define SRV_PORT (RRFIXEDSZ+4)
53 #define SRV_SERVER (RRFIXEDSZ+6)
54 #define SRV_FIXEDSZ (RRFIXEDSZ+6)
55 
56 #ifndef T_SRV
57 # define T_SRV 33
58 #endif
59 
60 // mingw
61 #ifndef DNS_TYPE_SRV
62 # define DNS_TYPE_SRV 33
63 #endif
64 
65 #ifndef NS_CMPRSFLGS
66 # define NS_CMPRSFLGS 0xc0
67 #endif
68 
69 #ifndef C_IN
70 # define C_IN 1
71 #endif
72 
73 #ifndef INVALID_SOCKET
74 # define INVALID_SOCKET -1
75 #endif
76 
77 #define XMPP_PORT 5222
78 
79 namespace gloox
80 {
81 
82 #if defined( HAVE_RES_QUERYDOMAIN ) && defined( HAVE_DN_SKIPNAME ) && defined( HAVE_RES_QUERY )
83  DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
84  const std::string& domain, const LogSink& logInstance )
85  {
86  buffer srvbuf;
87  bool error = false;
88 
89  const std::string dname = "_" + service + "._" + proto;
90 
91  if( !domain.empty() )
92  srvbuf.len = res_querydomain( dname.c_str(), const_cast<char*>( domain.c_str() ),
93  C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
94  else
95  srvbuf.len = res_query( dname.c_str(), C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ );
96 
97  if( srvbuf.len < 0 )
98  return defaultHostMap( domain, logInstance );
99 
100  HEADER* hdr = reinterpret_cast<HEADER*>( srvbuf.buf );
101  unsigned char* here = srvbuf.buf + NS_HFIXEDSZ;
102 
103  if( srvbuf.len < NS_HFIXEDSZ )
104  error = true;
105 
106  if( hdr->rcode >= 1 && hdr->rcode <= 5 )
107  error = true;
108 
109  if( ntohs( hdr->ancount ) == 0 )
110  error = true;
111 
112  if( ntohs( hdr->ancount ) > NS_PACKETSZ )
113  error = true;
114 
115  int cnt;
116  for( cnt = ntohs( hdr->qdcount ); cnt > 0; --cnt )
117  {
118  int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
119  here += strlen + NS_QFIXEDSZ;
120  }
121 
122  unsigned char* srv[NS_PACKETSZ];
123  int srvnum = 0;
124  for( cnt = ntohs( hdr->ancount ); cnt > 0; --cnt )
125  {
126  int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len );
127  here += strlen;
128  srv[srvnum++] = here;
129  here += SRV_FIXEDSZ;
130  here += dn_skipname( here, srvbuf.buf + srvbuf.len );
131  }
132 
133  if( error )
134  {
135  return defaultHostMap( domain, logInstance );
136  }
137 
138  // (q)sort here
139 
140  HostMap servers;
141  for( cnt = 0; cnt < srvnum; ++cnt )
142  {
143  char srvname[NS_MAXDNAME];
144  srvname[0] = '\0';
145 
146  if( dn_expand( srvbuf.buf, srvbuf.buf + NS_PACKETSZ,
147  srv[cnt] + SRV_SERVER, srvname, NS_MAXDNAME ) < 0
148  || !(*srvname) )
149  continue;
150 
151  unsigned char* c = srv[cnt] + SRV_PORT;
152  servers.insert( std::make_pair( static_cast<char*>( srvname ), ntohs( c[1] << 8 | c[0] ) ) );
153  }
154 
155  if( !servers.size() )
156  return defaultHostMap( domain, logInstance );
157 
158  return servers;
159  }
160 
161 #elif defined( _WIN32 ) && defined( HAVE_WINDNS_H ) && !defined( __MINGW32__ )
162  DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto,
163  const std::string& domain, const LogSink& logInstance )
164  {
165  const std::string dname = "_" + service + "._" + proto + "." + domain;
166  bool error = false;
167 
168  DNS::HostMap servers;
169  DNS_RECORD* pRecord = NULL;
170  DNS_STATUS status = DnsQuery_UTF8( dname.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &pRecord, NULL );
171  if( status == ERROR_SUCCESS )
172  {
173  // NOTE: DnsQuery_UTF8 and DnsQuery_A really should have been defined with
174  // PDNS_RECORDA instead of PDNS_RECORD, since that's what it is (even with _UNICODE defined).
175  // We'll correct for that mistake with a cast.
176  DNS_RECORDA* pRec = (DNS_RECORDA*)pRecord;
177  do
178  {
179  if( pRec->wType == DNS_TYPE_SRV )
180  {
181  servers[pRec->Data.SRV.pNameTarget] = pRec->Data.SRV.wPort;
182  }
183  pRec = pRec->pNext;
184  }
185  while( pRec != NULL );
186  DnsRecordListFree( pRecord, DnsFreeRecordList );
187  }
188  else
189  {
190  logInstance.warn( LogAreaClassDns, "DnsQuery_UTF8() failed: " + util::int2string( status ) );
191  error = true;
192  }
193 
194  if( error || !servers.size() )
195  {
196  servers = defaultHostMap( domain, logInstance );
197  }
198 
199  return servers;
200  }
201 
202 #else
203  DNS::HostMap DNS::resolve( const std::string& /*service*/, const std::string& /*proto*/,
204  const std::string& domain, const LogSink& logInstance )
205  {
206  logInstance.warn( LogAreaClassDns, "Notice: gloox does not support SRV "
207  "records on this platform. Using A records instead." );
208  return defaultHostMap( domain, logInstance );
209  }
210 #endif
211 
212  DNS::HostMap DNS::defaultHostMap( const std::string& domain, const LogSink& logInstance )
213  {
214  HostMap server;
215 
216  logInstance.warn( LogAreaClassDns, "Notice: no SRV record found for "
217  + domain + ", using default port." );
218 
219  if( !domain.empty() )
220  server[domain] = XMPP_PORT;
221 
222  return server;
223  }
224 
225 #ifdef HAVE_GETADDRINFO
226  void DNS::resolve( struct addrinfo** res, const std::string& service, const std::string& proto,
227  const std::string& domain, const LogSink& logInstance )
228  {
229  logInstance.dbg( LogAreaClassDns, "Resolving: _" + service + "._" + proto + "." + domain );
230  struct addrinfo hints;
231  if( proto == "tcp" )
232  hints.ai_socktype = SOCK_STREAM;
233  else if( proto == "udp" )
234  hints.ai_socktype = SOCK_DGRAM;
235  else
236  {
237  logInstance.err( LogAreaClassDns, "Unknown/Invalid protocol: " + proto );
238  }
239  memset( &hints, '\0', sizeof( hints ) );
240  hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
241  hints.ai_socktype = SOCK_STREAM;
242  int e = getaddrinfo( domain.c_str(), service.c_str(), &hints, res );
243  if( e )
244  logInstance.err( LogAreaClassDns, "getaddrinfo() failed" );
245  }
246 
247  int DNS::connect( const std::string& host, const LogSink& logInstance )
248  {
249  struct addrinfo* results = 0;
250 
251  resolve( &results, host, logInstance );
252  if( !results )
253  {
254  logInstance.err( LogAreaClassDns, "host not found: " + host );
255  return -ConnDnsError;
256  }
257 
258  struct addrinfo* runp = results;
259  while( runp )
260  {
261  int fd = DNS::connect( runp, logInstance );
262  if( fd >= 0 )
263  return fd;
264 
265  runp = runp->ai_next;
266  }
267 
268  freeaddrinfo( results );
269 
270  return -ConnConnectionRefused;
271  }
272 
273  int DNS::connect( struct addrinfo* res, const LogSink& logInstance )
274  {
275  if( !res )
276  return -1;
277 
278  int fd = getSocket( res->ai_family, res->ai_socktype, res->ai_protocol, logInstance );
279  if( fd < 0 )
280  return fd;
281 
282  if( ::connect( fd, res->ai_addr, res->ai_addrlen ) == 0 )
283  {
284  char ip[NI_MAXHOST];
285  char port[NI_MAXSERV];
286 
287  if( getnameinfo( res->ai_addr, res->ai_addrlen,
288  ip, sizeof( ip ),
289  port, sizeof( port ),
290  NI_NUMERICHOST | NI_NUMERICSERV ) )
291  {
292  //FIXME do we need to handle this? How? Can it actually happen at all?
293 // printf( "could not get numeric hostname");
294  }
295 
296  if( res->ai_canonname )
297  logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( res->ai_canonname ).append( " (" ).append( ip ).append( "), port " ).append( port ) );
298  else
299  logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( ip ).append( ":" ).append( port ) );
300 
301  return fd;
302  }
303 
304  std::string message = "connect() failed. "
305 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
306  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
307 #else
308  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
309 #endif
310  logInstance.dbg( LogAreaClassDns, message );
311 
312  closeSocket( fd, logInstance );
313  return -ConnConnectionRefused;
314  }
315 
316 #else
317 
318  int DNS::connect( const std::string& host, const LogSink& logInstance )
319  {
320  HostMap hosts = resolve( host, logInstance );
321  if( hosts.size() == 0 )
322  return -ConnDnsError;
323 
324  HostMap::const_iterator it = hosts.begin();
325  for( ; it != hosts.end(); ++it )
326  {
327  int fd = DNS::connect( (*it).first, (*it).second, logInstance );
328  if( fd >= 0 )
329  return fd;
330  }
331 
332  return -ConnConnectionRefused;
333  }
334 #endif
335 
336  int DNS::getSocket( const LogSink& logInstance )
337  {
338 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
339  WSADATA wsaData;
340  if( WSAStartup( MAKEWORD( 1, 1 ), &wsaData ) != 0 )
341  {
342  logInstance.dbg( LogAreaClassDns, "WSAStartup() failed. WSAGetLastError: "
343  + util::int2string( ::WSAGetLastError() ) );
344  return -ConnDnsError;
345  }
346 #endif
347 
348  int protocol = IPPROTO_TCP;
349 #if !defined( __APPLE__ ) // Sandboxing on Apple doesn't like you to use getprotobyname
350  struct protoent* prot;
351  if( ( prot = getprotobyname( "tcp" ) ) != 0 )
352  {
353  protocol = prot->p_proto;
354  }
355  else
356  {
357  std::string message = "getprotobyname( \"tcp\" ) failed. "
358 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
359  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() )
360 #else
361  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
362 #endif
363  + ". Falling back to IPPROTO_TCP: " + util::int2string( IPPROTO_TCP );
364  logInstance.dbg( LogAreaClassDns, message );
365 
366  // Do not return an error. We'll fall back to IPPROTO_TCP.
367  }
368 #endif // !defined( __APPLE__ )
369 
370  return getSocket( PF_INET, SOCK_STREAM, protocol, logInstance );
371  }
372 
373  int DNS::getSocket( int af, int socktype, int proto, const LogSink& logInstance )
374  {
375 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
376  SOCKET fd;
377 #else
378  int fd;
379 #endif
380  if( ( fd = socket( af, socktype, proto ) ) == INVALID_SOCKET )
381  {
382  std::string message = "getSocket( "
383  + util::int2string( af ) + ", "
384  + util::int2string( socktype ) + ", "
385  + util::int2string( proto )
386  + " ) failed. "
387 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
388  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
389 #else
390  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
391 #endif
392  logInstance.dbg( LogAreaClassDns, message );
393 
394  cleanup( logInstance );
395  return -ConnConnectionRefused;
396  }
397 
398 #ifdef HAVE_SETSOCKOPT
399  int timeout = 5000;
400  int reuseaddr = 1;
401  setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>( &timeout ), sizeof( timeout ) );
402  setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>( &reuseaddr ), sizeof( reuseaddr ) );
403 #endif
404 
405  return static_cast<int>( fd );
406  }
407 
408 #ifdef HAVE_GETADDRINFO
409  int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
410  {
411  struct addrinfo hints, *servinfo, *p;
412  int rv = 0;
413  int fd = 0;
414 
415  memset( &hints, 0, sizeof( hints ) );
416  hints.ai_family = AF_UNSPEC;
417  hints.ai_socktype = SOCK_STREAM;
418 
419  if( ( rv = getaddrinfo( host.c_str(), util::int2string( port ).c_str(), &hints, &servinfo ) ) != 0 )
420  {
421  logInstance.dbg( LogAreaClassDns, "getaddrinfo() failed for " + host + "." );
422  return -ConnDnsError;
423  }
424 
425  for( p = servinfo; p != 0; p = p->ai_next )
426  {
427  if( ( fd = getSocket( p->ai_family, p->ai_socktype, p->ai_protocol, logInstance ) ) == -1 )
428  {
429  continue;
430  }
431 
432  if( ::connect( fd, p->ai_addr, p->ai_addrlen ) == -1 )
433  {
434  closeSocket( fd, logInstance );
435  continue;
436  }
437 
438  break;
439  }
440 
441  if( p == 0 )
442  {
443  freeaddrinfo( servinfo );
444  std::string message = "Connection to " + host + ":" + util::int2string( port ) + " failed. "
445 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
446  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
447 #else
448  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
449 #endif
450  logInstance.dbg( LogAreaClassDns, message );
451  return -ConnConnectionRefused;
452  }
453 
454  freeaddrinfo( servinfo );
455  return fd;
456  }
457 
458 #else // HAVE_GETADDRINFO
459  int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
460  {
461  int fd = getSocket( logInstance );
462  if( fd < 0 )
463  return fd;
464 
465  struct hostent* h;
466  if( ( h = gethostbyname( host.c_str() ) ) == 0 )
467  {
468  logInstance.dbg( LogAreaClassDns, "gethostbyname() failed for " + host + "." );
469  cleanup( logInstance );
470  closeSocket( fd, logInstance );
471  return -ConnDnsError;
472  }
473 
474  struct sockaddr_in target;
475  target.sin_family = AF_INET;
476  target.sin_port = htons( static_cast<unsigned short int>( port ) );
477 
478  if( h->h_length != sizeof( struct in_addr ) )
479  {
480  logInstance.dbg( LogAreaClassDns, "gethostbyname() returned unexpected structure." );
481  cleanup( logInstance );
482  closeSocket( fd, logInstance );
483  return -ConnDnsError;
484  }
485  else
486  {
487  memcpy( &target.sin_addr, h->h_addr, sizeof( struct in_addr ) );
488  }
489 
490  logInstance.dbg( LogAreaClassDns, "Connecting to " + host
491  + " (" + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
492 
493  memset( target.sin_zero, '\0', 8 );
494  if( ::connect( fd, reinterpret_cast<struct sockaddr *>( &target ), sizeof( struct sockaddr ) ) == 0 )
495  {
496  logInstance.dbg( LogAreaClassDns, "Connected to " + host + " ("
497  + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
498  return fd;
499  }
500 
501  std::string message = "Connection to " + host + " ("
502  + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ") failed. "
503 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
504  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
505 #else
506  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
507 #endif
508  logInstance.dbg( LogAreaClassDns, message );
509 
510  closeSocket( fd, logInstance );
511  return -ConnConnectionRefused;
512  }
513 #endif // HAVE_GETADDRINFO
514 
515  void DNS::closeSocket( int fd, const LogSink& logInstance )
516  {
517 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
518  int result = closesocket( fd );
519 #else
520  int result = close( fd );
521 #endif
522 
523  if( result != 0 )
524  {
525  std::string message = "closeSocket() failed. "
526 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
527  "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
528 #else
529  "errno: " + util::int2string( errno ) + ": " + strerror( errno );
530 #endif
531  logInstance.dbg( LogAreaClassDns, message );
532  }
533  }
534 
535  void DNS::cleanup( const LogSink& logInstance )
536  {
537 #if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
538  if( WSACleanup() != 0 )
539  {
540  logInstance.dbg( LogAreaClassDns, "WSACleanup() failed. WSAGetLastError: "
541  + util::int2string( ::WSAGetLastError() ) );
542  }
543 #else
544  (void)logInstance;
545 #endif
546  }
547 
548 }
gloox::DNS::HostMap
std::map< std::string, int > HostMap
Definition: dns.h:68
gloox::LogSink::warn
void warn(LogArea area, const std::string &message) const
Definition: logsink.h:75
gloox::DNS::resolve
static HostMap resolve(const std::string &service, const std::string &proto, const std::string &domain, const LogSink &logInstance)
Definition: dns.cpp:203
gloox::DNS::closeSocket
static void closeSocket(int fd, const LogSink &logInstance)
Definition: dns.cpp:515
gloox::ConnDnsError
@ ConnDnsError
Definition: gloox.h:700
gloox::LogSink
An implementation of log sink and source.
Definition: logsink.h:38
gloox::LogAreaClassDns
@ LogAreaClassDns
Definition: gloox.h:1059
gloox
The namespace for the gloox library.
Definition: adhoc.cpp:27
gloox::DNS::getSocket
static int getSocket(const LogSink &logInstance)
Definition: dns.cpp:336
gloox::LogSink::dbg
void dbg(LogArea area, const std::string &message) const
Definition: logsink.h:66
gloox::ConnConnectionRefused
@ ConnConnectionRefused
Definition: gloox.h:698
gloox::DNS::connect
static int connect(const std::string &host, const LogSink &logInstance)
Definition: dns.cpp:318