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 
79 namespace gloox
80 {
81 
82 #if defined( HAVE_RES_QUERYDOMAIN ) && defined( HAVE_DN_SKIPNAME ) && defined( HAVE_RES_QUERY )
resolve(const std::string & service,const std::string & proto,const std::string & domain,const LogSink & logInstance)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 
defaultHostMap(const std::string & domain,const LogSink & logInstance)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
resolve(struct addrinfo ** res,const std::string & service,const std::string & proto,const std::string & domain,const LogSink & logInstance)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 
connect(const std::string & host,const LogSink & logInstance)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 
connect(struct addrinfo * res,const LogSink & logInstance)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 
connect(const std::string & host,const LogSink & logInstance)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 
getSocket(const LogSink & logInstance)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 
getSocket(int af,int socktype,int proto,const LogSink & logInstance)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
connect(const std::string & host,int port,const LogSink & logInstance)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
connect(const std::string & host,int port,const LogSink & logInstance)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 
closeSocket(int fd,const LogSink & logInstance)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 
cleanup(const LogSink & logInstance)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 }
552