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