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