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