gloox 1.0.24
dns.cpp
1/*
2 Copyright (c) 2005-2019 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
79namespace 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 {
264 freeaddrinfo( results );
265 return fd;
266 }
267
268 runp = runp->ai_next;
269 }
270
271 freeaddrinfo( results );
272
273 return -ConnConnectionRefused;
274 }
275
276 int DNS::connect( struct addrinfo* res, const LogSink& logInstance )
277 {
278 if( !res )
279 return -1;
280
281 int fd = getSocket( res->ai_family, res->ai_socktype, res->ai_protocol, logInstance );
282 if( fd < 0 )
283 return fd;
284
285 if( ::connect( fd, res->ai_addr, res->ai_addrlen ) == 0 )
286 {
287 char ip[NI_MAXHOST];
288 char port[NI_MAXSERV];
289
290 if( getnameinfo( res->ai_addr, res->ai_addrlen,
291 ip, sizeof( ip ),
292 port, sizeof( port ),
293 NI_NUMERICHOST | NI_NUMERICSERV ) )
294 {
295 //FIXME do we need to handle this? How? Can it actually happen at all?
296// printf( "could not get numeric hostname");
297 }
298
299 if( res->ai_canonname )
300 logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( res->ai_canonname ).append( " (" ).append( ip ).append( "), port " ).append( port ) );
301 else
302 logInstance.dbg( LogAreaClassDns, std::string( "Connecting to " ).append( ip ).append( ":" ).append( port ) );
303
304 return fd;
305 }
306
307 std::string message = "connect() failed. "
308#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
309 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
310#else
311 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
312#endif
313 logInstance.dbg( LogAreaClassDns, message );
314
315 closeSocket( fd, logInstance );
316 return -ConnConnectionRefused;
317 }
318
319#else
320
321 int DNS::connect( const std::string& host, const LogSink& logInstance )
322 {
323 HostMap hosts = resolve( host, logInstance );
324 if( hosts.size() == 0 )
325 return -ConnDnsError;
326
327 HostMap::const_iterator it = hosts.begin();
328 for( ; it != hosts.end(); ++it )
329 {
330 int fd = DNS::connect( (*it).first, (*it).second, logInstance );
331 if( fd >= 0 )
332 return fd;
333 }
334
335 return -ConnConnectionRefused;
336 }
337#endif
338
339 int DNS::getSocket( const LogSink& logInstance )
340 {
341#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
342 WSADATA wsaData;
343 if( WSAStartup( MAKEWORD( 1, 1 ), &wsaData ) != 0 )
344 {
345 logInstance.dbg( LogAreaClassDns, "WSAStartup() failed. WSAGetLastError: "
346 + util::int2string( ::WSAGetLastError() ) );
347 return -ConnDnsError;
348 }
349#endif
350
351 int protocol = IPPROTO_TCP;
352#if !defined( __APPLE__ ) // Sandboxing on Apple doesn't like you to use getprotobyname
353 struct protoent* prot;
354 if( ( prot = getprotobyname( "tcp" ) ) != 0 )
355 {
356 protocol = prot->p_proto;
357 }
358 else
359 {
360 std::string message = "getprotobyname( \"tcp\" ) failed. "
361#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
362 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() )
363#else
364 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
365#endif
366 + ". Falling back to IPPROTO_TCP: " + util::int2string( IPPROTO_TCP );
367 logInstance.dbg( LogAreaClassDns, message );
368
369 // Do not return an error. We'll fall back to IPPROTO_TCP.
370 }
371#endif // !defined( __APPLE__ )
372
373 return getSocket( PF_INET, SOCK_STREAM, protocol, logInstance );
374 }
375
376 int DNS::getSocket( int af, int socktype, int proto, const LogSink& logInstance )
377 {
378#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
379 SOCKET fd;
380#else
381 int fd;
382#endif
383 if( ( fd = socket( af, socktype, proto ) ) == INVALID_SOCKET )
384 {
385 std::string message = "getSocket( "
386 + util::int2string( af ) + ", "
387 + util::int2string( socktype ) + ", "
388 + util::int2string( proto )
389 + " ) failed. "
390#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
391 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
392#else
393 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
394#endif
395 logInstance.dbg( LogAreaClassDns, message );
396
397 cleanup( logInstance );
398 return -ConnConnectionRefused;
399 }
400
401#ifdef HAVE_SETSOCKOPT
402 int timeout = 5000;
403 int reuseaddr = 1;
404 setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>( &timeout ), sizeof( timeout ) );
405 setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>( &reuseaddr ), sizeof( reuseaddr ) );
406#endif
407
408 return static_cast<int>( fd );
409 }
410
411#ifdef HAVE_GETADDRINFO
412 int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
413 {
414 struct addrinfo hints, *servinfo, *p;
415 int rv = 0;
416 int fd = 0;
417
418 memset( &hints, 0, sizeof( hints ) );
419 hints.ai_family = AF_UNSPEC;
420 hints.ai_socktype = SOCK_STREAM;
421
422 if( ( rv = getaddrinfo( host.c_str(), util::int2string( port ).c_str(), &hints, &servinfo ) ) != 0 )
423 {
424 logInstance.dbg( LogAreaClassDns, "getaddrinfo() failed for " + host + "." );
425 return -ConnDnsError;
426 }
427
428 for( p = servinfo; p != 0; p = p->ai_next )
429 {
430 if( ( fd = getSocket( p->ai_family, p->ai_socktype, p->ai_protocol, logInstance ) ) == -1 )
431 {
432 continue;
433 }
434
435 if( ::connect( fd, p->ai_addr, p->ai_addrlen ) == -1 )
436 {
437 closeSocket( fd, logInstance );
438 continue;
439 }
440
441 break;
442 }
443
444 if( p == 0 )
445 {
446 freeaddrinfo( servinfo );
447 std::string message = "Connection to " + host + ":" + util::int2string( port ) + " failed. "
448#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
449 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
450#else
451 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
452#endif
453 logInstance.dbg( LogAreaClassDns, message );
454 return -ConnConnectionRefused;
455 }
456
457 freeaddrinfo( servinfo );
458 return fd;
459 }
460
461#else // HAVE_GETADDRINFO
462 int DNS::connect( const std::string& host, int port, const LogSink& logInstance )
463 {
464 int fd = getSocket( logInstance );
465 if( fd < 0 )
466 return fd;
467
468 struct hostent* h;
469 if( ( h = gethostbyname( host.c_str() ) ) == 0 )
470 {
471 logInstance.dbg( LogAreaClassDns, "gethostbyname() failed for " + host + "." );
472 cleanup( logInstance );
473 closeSocket( fd, logInstance );
474 return -ConnDnsError;
475 }
476
477 struct sockaddr_in target;
478 target.sin_family = AF_INET;
479 target.sin_port = htons( static_cast<unsigned short int>( port ) );
480
481 if( h->h_length != sizeof( struct in_addr ) )
482 {
483 logInstance.dbg( LogAreaClassDns, "gethostbyname() returned unexpected structure." );
484 cleanup( logInstance );
485 closeSocket( fd, logInstance );
486 return -ConnDnsError;
487 }
488 else
489 {
490 memcpy( &target.sin_addr, h->h_addr, sizeof( struct in_addr ) );
491 }
492
493 logInstance.dbg( LogAreaClassDns, "Connecting to " + host
494 + " (" + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
495
496 memset( target.sin_zero, '\0', 8 );
497 if( ::connect( fd, reinterpret_cast<struct sockaddr *>( &target ), sizeof( struct sockaddr ) ) == 0 )
498 {
499 logInstance.dbg( LogAreaClassDns, "Connected to " + host + " ("
500 + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" );
501 return fd;
502 }
503
504 std::string message = "Connection to " + host + " ("
505 + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ") failed. "
506#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
507 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
508#else
509 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
510#endif
511 logInstance.dbg( LogAreaClassDns, message );
512
513 closeSocket( fd, logInstance );
514 return -ConnConnectionRefused;
515 }
516#endif // HAVE_GETADDRINFO
517
518 void DNS::closeSocket( int fd, const LogSink& logInstance )
519 {
520#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
521 int result = closesocket( fd );
522#else
523 int result = close( fd );
524#endif
525
526 if( result != 0 )
527 {
528 std::string message = "closeSocket() failed. "
529#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
530 "WSAGetLastError: " + util::int2string( ::WSAGetLastError() );
531#else
532 "errno: " + util::int2string( errno ) + ": " + strerror( errno );
533#endif
534 logInstance.dbg( LogAreaClassDns, message );
535 }
536 }
537
538 void DNS::cleanup( const LogSink& logInstance )
539 {
540#if defined( _WIN32 ) && !defined( __SYMBIAN32__ )
541 if( WSACleanup() != 0 )
542 {
543 logInstance.dbg( LogAreaClassDns, "WSACleanup() failed. WSAGetLastError: "
544 + util::int2string( ::WSAGetLastError() ) );
545 }
546#else
547 (void)logInstance;
548#endif
549 }
550
551}
static int connect(const std::string &host, const LogSink &logInstance)
Definition: dns.cpp:247
static HostMap resolve(const std::string &service, const std::string &proto, const std::string &domain, const LogSink &logInstance)
Definition: dns.cpp:83
std::map< std::string, int > HostMap
Definition: dns.h:68
static int getSocket(const LogSink &logInstance)
Definition: dns.cpp:339
static void closeSocket(int fd, const LogSink &logInstance)
Definition: dns.cpp:518
An implementation of log sink and source.
Definition: logsink.h:39
void warn(LogArea area, const std::string &message) const
Definition: logsink.h:75
void dbg(LogArea area, const std::string &message) const
Definition: logsink.h:66
void err(LogArea area, const std::string &message) const
Definition: logsink.h:84
The namespace for the gloox library.
Definition: adhoc.cpp:28
@ ConnConnectionRefused
Definition: gloox.h:698
@ ConnDnsError
Definition: gloox.h:700
@ LogAreaClassDns
Definition: gloox.h:1059