1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * DNS Library for handling lookups and updates. 6 * 7 * PHP Version 5 8 * 9 * Copyright (c) 2010, Mike Pultz <mike@mikepultz.com>. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 16 * * Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * * Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in 21 * the documentation and/or other materials provided with the 22 * distribution. 23 * 24 * * Neither the name of Mike Pultz nor the names of his contributors 25 * may be used to endorse or promote products derived from this 26 * software without specific prior written permission. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 31 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 32 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 34 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 35 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 36 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC 37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 38 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 39 * POSSIBILITY OF SUCH DAMAGE. 40 * 41 * @category Networking 42 * @package Net_DNS2 43 * @author Mike Pultz <mike@mikepultz.com> 44 * @copyright 2010 Mike Pultz <mike@mikepultz.com> 45 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 46 * @version SVN: $Id$ 47 * @link http://pear.php.net/package/Net_DNS2 48 * @since File available since Release 0.6.0 49 * 50 */ 51 52/* 53 * register the auto-load function 54 * 55 */ 56spl_autoload_register('Net_DNS2::autoload'); 57 58/** 59 * This is the base class for the Net_DNS2_Resolver and Net_DNS2_Updater 60 * classes. 61 * 62 * @category Networking 63 * @package Net_DNS2 64 * @author Mike Pultz <mike@mikepultz.com> 65 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 66 * @link http://pear.php.net/package/Net_DNS2 67 * @see Net_DNS2_Resolver, Net_DNS2_Updater 68 * 69 */ 70class Net_DNS2 71{ 72 /* 73 * the current version of this library 74 */ 75 const VERSION = '1.4.3'; 76 77 /* 78 * the default path to a resolv.conf file 79 */ 80 const RESOLV_CONF = '/etc/resolv.conf'; 81 82 /* 83 * override options from the resolv.conf file 84 * 85 * if this is set, then certain values from the resolv.conf file will override 86 * local settings. This is disabled by default to remain backwards compatible. 87 * 88 */ 89 public $use_resolv_options = false; 90 91 /* 92 * use TCP only (true/false) 93 */ 94 public $use_tcp = false; 95 96 /* 97 * DNS Port to use (53) 98 */ 99 public $dns_port = 53; 100 101 /* 102 * the ip/port for use as a local socket 103 */ 104 public $local_host = ''; 105 public $local_port = 0; 106 107 /* 108 * timeout value for socket connections 109 */ 110 public $timeout = 5; 111 112 /* 113 * randomize the name servers list 114 */ 115 public $ns_random = false; 116 117 /* 118 * default domains 119 */ 120 public $domain = ''; 121 122 /* 123 * domain search list - not actually used right now 124 */ 125 public $search_list = array(); 126 127 /* 128 * enable cache; either "shared", "file" or "none" 129 */ 130 public $cache_type = 'none'; 131 132 /* 133 * file name to use for shared memory segment or file cache 134 */ 135 public $cache_file = '/tmp/net_dns2.cache'; 136 137 /* 138 * the max size of the cache file (in bytes) 139 */ 140 public $cache_size = 50000; 141 142 /* 143 * the method to use for storing cache data; either "serialize" or "json" 144 * 145 * json is faster, but can't remember the class names (everything comes back 146 * as a "stdClass Object"; all the data is the same though. serialize is 147 * slower, but will have all the class info. 148 * 149 * defaults to 'serialize' 150 */ 151 public $cache_serializer = 'serialize'; 152 153 /* 154 * by default, according to RFC 1034 155 * 156 * CNAME RRs cause special action in DNS software. When a name server 157 * fails to find a desired RR in the resource set associated with the 158 * domain name, it checks to see if the resource set consists of a CNAME 159 * record with a matching class. If so, the name server includes the CNAME 160 * record in the response and restarts the query at the domain name 161 * specified in the data field of the CNAME record. 162 * 163 * this can cause "unexpected" behavious, since i'm sure *most* people 164 * don't know DNS does this; there may be cases where Net_DNS2 returns a 165 * positive response, even though the hostname the user looked up did not 166 * actually exist. 167 * 168 * strict_query_mode means that if the hostname that was looked up isn't 169 * actually in the answer section of the response, Net_DNS2 will return an 170 * empty answer section, instead of an answer section that could contain 171 * CNAME records. 172 * 173 */ 174 public $strict_query_mode = false; 175 176 /* 177 * if we should set the recursion desired bit to 1 or 0. 178 * 179 * by default this is set to true, we want the DNS server to perform a recursive 180 * request. If set to false, the RD bit will be set to 0, and the server will 181 * not perform recursion on the request. 182 */ 183 public $recurse = true; 184 185 /* 186 * request DNSSEC values, by setting the DO flag to 1; this actually makes 187 * the resolver add a OPT RR to the additional section, and sets the DO flag 188 * in this RR to 1 189 * 190 */ 191 public $dnssec = false; 192 193 /* 194 * set the DNSSEC AD (Authentic Data) bit on/off; the AD bit on the request 195 * side was previously undefined, and resolvers we instructed to always clear 196 * the AD bit when sending a request. 197 * 198 * RFC6840 section 5.7 defines setting the AD bit in the query as a signal to 199 * the server that it wants the value of the AD bit, without needed to request 200 * all the DNSSEC data via the DO bit. 201 * 202 */ 203 public $dnssec_ad_flag = false; 204 205 /* 206 * set the DNSSEC CD (Checking Disabled) bit on/off; turning this off, means 207 * that the DNS resolver will perform it's own signature validation- so the DNS 208 * servers simply pass through all the details. 209 * 210 */ 211 public $dnssec_cd_flag = false; 212 213 /* 214 * the EDNS(0) UDP payload size to use when making DNSSEC requests 215 * see RFC 4035 section 4.1 - EDNS Support. 216 * 217 * there is some different ideas on the suggest size to supprt; but it seems to 218 * be "at least 1220 bytes, but SHOULD support 4000 bytes. 219 * 220 * we'll just support 4000 221 * 222 */ 223 public $dnssec_payload_size = 4000; 224 225 /* 226 * the last exeception that was generated 227 */ 228 public $last_exception = null; 229 230 /* 231 * the list of exceptions by name server 232 */ 233 public $last_exception_list = array(); 234 235 /* 236 * name server list 237 */ 238 public $nameservers = array(); 239 240 /* 241 * local sockets 242 */ 243 protected $sock = array(Net_DNS2_Socket::SOCK_DGRAM => array(), Net_DNS2_Socket::SOCK_STREAM => array()); 244 245 /* 246 * if the socket extension is loaded 247 */ 248 protected $sockets_enabled = false; 249 250 /* 251 * the TSIG or SIG RR object for authentication 252 */ 253 protected $auth_signature = null; 254 255 /* 256 * the shared memory segment id for the local cache 257 */ 258 protected $cache = null; 259 260 /* 261 * internal setting for enabling cache 262 */ 263 protected $use_cache = false; 264 265 /** 266 * Constructor - base constructor for the Resolver and Updater 267 * 268 * @param mixed $options array of options or null for none 269 * 270 * @throws Net_DNS2_Exception 271 * @access public 272 * 273 */ 274 public function __construct(array $options = null) 275 { 276 // 277 // check for the sockets extension; we no longer support the sockets library under 278 // windows- there have been too many errors related to sockets under windows- 279 // specifically inconsistent socket defines between versions of windows- 280 // 281 // and since I can't seem to find a way to get the actual windows version, it 282 // doesn't seem fixable in the code. 283 // 284 if ( (extension_loaded('sockets') == true) && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') ) { 285 286 $this->sockets_enabled = true; 287 } 288 289 // 290 // load any options that were provided 291 // 292 if (!empty($options)) { 293 294 foreach ($options as $key => $value) { 295 296 if ($key == 'nameservers') { 297 298 $this->setServers($value); 299 } else { 300 301 $this->$key = $value; 302 } 303 } 304 } 305 306 // 307 // if we're set to use the local shared memory cache, then 308 // make sure it's been initialized 309 // 310 switch($this->cache_type) { 311 case 'shared': 312 if (extension_loaded('shmop')) { 313 314 $this->cache = new Net_DNS2_Cache_Shm; 315 $this->use_cache = true; 316 } else { 317 318 throw new Net_DNS2_Exception( 319 'shmop library is not available for cache', 320 Net_DNS2_Lookups::E_CACHE_SHM_UNAVAIL 321 ); 322 } 323 break; 324 case 'file': 325 326 $this->cache = new Net_DNS2_Cache_File; 327 $this->use_cache = true; 328 329 break; 330 case 'none': 331 $this->use_cache = false; 332 break; 333 default: 334 335 throw new Net_DNS2_Exception( 336 'un-supported cache type: ' . $this->cache_type, 337 Net_DNS2_Lookups::E_CACHE_UNSUPPORTED 338 ); 339 } 340 } 341 342 /** 343 * autoload call-back function; used to auto-load classes 344 * 345 * @param string $name the name of the class 346 * 347 * @return void 348 * @access public 349 * 350 */ 351 static public function autoload($name) 352 { 353 // 354 // only auto-load our classes 355 // 356 if (strncmp($name, 'Net_DNS2', 8) == 0) { 357 358 include dirname(__FILE__) . '/../' . str_replace('_', '/', $name) . '.php'; 359 } 360 361 return; 362 } 363 364 /** 365 * sets the name servers to be used 366 * 367 * @param mixed $nameservers either an array of name servers, or a file name 368 * to parse, assuming it's in the resolv.conf format 369 * 370 * @return boolean 371 * @throws Net_DNS2_Exception 372 * @access public 373 * 374 */ 375 public function setServers($nameservers) 376 { 377 // 378 // if it's an array, then use it directly 379 // 380 // otherwise, see if it's a path to a resolv.conf file and if so, load it 381 // 382 if (is_array($nameservers)) { 383 384 $this->nameservers = $nameservers; 385 386 } else { 387 388 // 389 // temporary list of name servers; do it this way rather than just 390 // resetting the local nameservers value, just incase an exception 391 // is thrown here; this way we might avoid ending up with an empty 392 // namservers list. 393 // 394 $ns = array(); 395 396 // 397 // check to see if the file is readable 398 // 399 if (is_readable($nameservers) === true) { 400 401 $data = file_get_contents($nameservers); 402 if ($data === false) { 403 throw new Net_DNS2_Exception( 404 'failed to read contents of file: ' . $nameservers, 405 Net_DNS2_Lookups::E_NS_INVALID_FILE 406 ); 407 } 408 409 $lines = explode("\n", $data); 410 411 foreach ($lines as $line) { 412 413 $line = trim($line); 414 415 // 416 // ignore empty lines, and lines that are commented out 417 // 418 if ( (strlen($line) == 0) 419 || ($line[0] == '#') 420 || ($line[0] == ';') 421 ) { 422 continue; 423 } 424 425 // 426 // ignore lines with no spaces in them. 427 // 428 if (strpos($line, ' ') === false) { 429 continue; 430 } 431 432 list($key, $value) = preg_split('/\s+/', $line, 2); 433 434 $key = trim(strtolower($key)); 435 $value = trim(strtolower($value)); 436 437 switch($key) { 438 case 'nameserver': 439 440 // 441 // nameserver can be a IPv4 or IPv6 address 442 // 443 if ( (self::isIPv4($value) == true) 444 || (self::isIPv6($value) == true) 445 ) { 446 447 $ns[] = $value; 448 } else { 449 450 throw new Net_DNS2_Exception( 451 'invalid nameserver entry: ' . $value, 452 Net_DNS2_Lookups::E_NS_INVALID_ENTRY 453 ); 454 } 455 break; 456 457 case 'domain': 458 $this->domain = $value; 459 break; 460 461 case 'search': 462 $this->search_list = preg_split('/\s+/', $value); 463 break; 464 465 case 'options': 466 $this->parseOptions($value); 467 break; 468 469 default: 470 ; 471 } 472 } 473 474 // 475 // if we don't have a domain, but we have a search list, then 476 // take the first entry on the search list as the domain 477 // 478 if ( (strlen($this->domain) == 0) 479 && (count($this->search_list) > 0) 480 ) { 481 $this->domain = $this->search_list[0]; 482 } 483 484 } else { 485 throw new Net_DNS2_Exception( 486 'resolver file file provided is not readable: ' . $nameservers, 487 Net_DNS2_Lookups::E_NS_INVALID_FILE 488 ); 489 } 490 491 // 492 // store the name servers locally 493 // 494 if (count($ns) > 0) { 495 $this->nameservers = $ns; 496 } 497 } 498 499 // 500 // remove any duplicates; not sure if we should bother with this- if people 501 // put duplicate name servers, who I am to stop them? 502 // 503 $this->nameservers = array_unique($this->nameservers); 504 505 // 506 // check the name servers 507 // 508 $this->checkServers(); 509 510 return true; 511 } 512 513 /** 514 * parses the options line from a resolv.conf file; we don't support all the options 515 * yet, and using them is optional. 516 * 517 * @param string $value is the options string from the resolv.conf file. 518 * 519 * @return boolean 520 * @access private 521 * 522 */ 523 private function parseOptions($value) 524 { 525 // 526 // if overrides are disabled (the default), or the options list is empty for some 527 // reason, then we don't need to do any of this work. 528 // 529 if ( ($this->use_resolv_options == false) || (strlen($value) == 0) ) { 530 531 return true; 532 } 533 534 $options = preg_split('/\s+/', strtolower($value)); 535 536 foreach ($options as $option) { 537 538 // 539 // override the timeout value from the resolv.conf file. 540 // 541 if ( (strncmp($option, 'timeout', 7) == 0) && (strpos($option, ':') !== false) ) { 542 543 list($key, $val) = explode(':', $option); 544 545 if ( ($val > 0) && ($val <= 30) ) { 546 547 $this->timeout = $val; 548 } 549 550 // 551 // the rotate option just enabled the ns_random option 552 // 553 } else if (strncmp($option, 'rotate', 6) == 0) { 554 555 $this->ns_random = true; 556 } 557 } 558 559 return true; 560 } 561 562 /** 563 * checks the list of name servers to make sure they're set 564 * 565 * @param mixed $default a path to a resolv.conf file or an array of servers. 566 * 567 * @return boolean 568 * @throws Net_DNS2_Exception 569 * @access protected 570 * 571 */ 572 protected function checkServers($default = null) 573 { 574 if (empty($this->nameservers)) { 575 576 if (isset($default)) { 577 578 $this->setServers($default); 579 } else { 580 581 throw new Net_DNS2_Exception( 582 'empty name servers list; you must provide a list of name '. 583 'servers, or the path to a resolv.conf file.', 584 Net_DNS2_Lookups::E_NS_INVALID_ENTRY 585 ); 586 } 587 } 588 589 return true; 590 } 591 592 /** 593 * adds a TSIG RR object for authentication 594 * 595 * @param string $keyname the key name to use for the TSIG RR 596 * @param string $signature the key to sign the request. 597 * @param string $algorithm the algorithm to use 598 * 599 * @return boolean 600 * @access public 601 * @since function available since release 1.1.0 602 * 603 */ 604 public function signTSIG( 605 $keyname, $signature = '', $algorithm = Net_DNS2_RR_TSIG::HMAC_MD5 606 ) { 607 // 608 // if the TSIG was pre-created and passed in, then we can just used 609 // it as provided. 610 // 611 if ($keyname instanceof Net_DNS2_RR_TSIG) { 612 613 $this->auth_signature = $keyname; 614 615 } else { 616 617 // 618 // otherwise create the TSIG RR, but don't add it just yet; TSIG needs 619 // to be added as the last additional entry- so we'll add it just 620 // before we send. 621 // 622 $this->auth_signature = Net_DNS2_RR::fromString( 623 strtolower(trim($keyname)) . 624 ' TSIG '. $signature 625 ); 626 627 // 628 // set the algorithm to use 629 // 630 $this->auth_signature->algorithm = $algorithm; 631 } 632 633 return true; 634 } 635 636 /** 637 * adds a SIG RR object for authentication 638 * 639 * @param string $filename the name of a file to load the signature from. 640 * 641 * @return boolean 642 * @throws Net_DNS2_Exception 643 * @access public 644 * @since function available since release 1.1.0 645 * 646 */ 647 public function signSIG0($filename) 648 { 649 // 650 // check for OpenSSL 651 // 652 if (extension_loaded('openssl') === false) { 653 654 throw new Net_DNS2_Exception( 655 'the OpenSSL extension is required to use SIG(0).', 656 Net_DNS2_Lookups::E_OPENSSL_UNAVAIL 657 ); 658 } 659 660 // 661 // if the SIG was pre-created, then use it as-is 662 // 663 if ($filename instanceof Net_DNS2_RR_SIG) { 664 665 $this->auth_signature = $filename; 666 667 } else { 668 669 // 670 // otherwise, it's filename which needs to be parsed and processed. 671 // 672 $private = new Net_DNS2_PrivateKey($filename); 673 674 // 675 // create a new Net_DNS2_RR_SIG object 676 // 677 $this->auth_signature = new Net_DNS2_RR_SIG(); 678 679 // 680 // reset some values 681 // 682 $this->auth_signature->name = $private->signname; 683 $this->auth_signature->ttl = 0; 684 $this->auth_signature->class = 'ANY'; 685 686 // 687 // these values are pulled from the private key 688 // 689 $this->auth_signature->algorithm = $private->algorithm; 690 $this->auth_signature->keytag = $private->keytag; 691 $this->auth_signature->signname = $private->signname; 692 693 // 694 // these values are hard-coded for SIG0 695 // 696 $this->auth_signature->typecovered = 'SIG0'; 697 $this->auth_signature->labels = 0; 698 $this->auth_signature->origttl = 0; 699 700 // 701 // generate the dates 702 // 703 $t = time(); 704 705 $this->auth_signature->sigincep = gmdate('YmdHis', $t); 706 $this->auth_signature->sigexp = gmdate('YmdHis', $t + 500); 707 708 // 709 // store the private key in the SIG object for later. 710 // 711 $this->auth_signature->private_key = $private; 712 } 713 714 // 715 // only RSA algorithms are supported for SIG(0) 716 // 717 switch($this->auth_signature->algorithm) { 718 case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5: 719 case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1: 720 case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256: 721 case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512: 722 case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSA: 723 break; 724 default: 725 throw new Net_DNS2_Exception( 726 'only asymmetric algorithms work with SIG(0)!', 727 Net_DNS2_Lookups::E_OPENSSL_INV_ALGO 728 ); 729 } 730 731 return true; 732 } 733 734 /** 735 * a simple function to determine if the RR type is cacheable 736 * 737 * @param stream $_type the RR type string 738 * 739 * @return bool returns true/false if the RR type if cachable 740 * @access public 741 * 742 */ 743 public function cacheable($_type) 744 { 745 switch($_type) { 746 case 'AXFR': 747 case 'OPT': 748 return false; 749 } 750 751 return true; 752 } 753 754 /** 755 * PHP doesn't support unsigned integers, but many of the RR's return 756 * unsigned values (like SOA), so there is the possibility that the 757 * value will overrun on 32bit systems, and you'll end up with a 758 * negative value. 759 * 760 * 64bit systems are not affected, as their PHP_IN_MAX value should 761 * be 64bit (ie 9223372036854775807) 762 * 763 * This function returns a negative integer value, as a string, with 764 * the correct unsigned value. 765 * 766 * @param string $_int the unsigned integer value to check 767 * 768 * @return string returns the unsigned value as a string. 769 * @access public 770 * 771 */ 772 public static function expandUint32($_int) 773 { 774 if ( ($_int < 0) && (PHP_INT_MAX == 2147483647) ) { 775 return sprintf('%u', $_int); 776 } else { 777 return $_int; 778 } 779 } 780 781 /** 782 * returns true/false if the given address is a valid IPv4 address 783 * 784 * @param string $_address the IPv4 address to check 785 * 786 * @return boolean returns true/false if the address is IPv4 address 787 * @access public 788 * 789 */ 790 public static function isIPv4($_address) 791 { 792 // 793 // use filter_var() if it's available; it's faster than preg 794 // 795 if (extension_loaded('filter') == true) { 796 797 if (filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) == false) { 798 return false; 799 } 800 } else { 801 802 // 803 // do the main check here; 804 // 805 if (inet_pton($_address) === false) { 806 return false; 807 } 808 809 // 810 // then make sure we're not a IPv6 address 811 // 812 if (preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 0) { 813 return false; 814 } 815 } 816 817 return true; 818 } 819 820 /** 821 * returns true/false if the given address is a valid IPv6 address 822 * 823 * @param string $_address the IPv6 address to check 824 * 825 * @return boolean returns true/false if the address is IPv6 address 826 * @access public 827 * 828 */ 829 public static function isIPv6($_address) 830 { 831 // 832 // use filter_var() if it's available; it's faster than preg 833 // 834 if (extension_loaded('filter') == true) { 835 if (filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) == false) { 836 return false; 837 } 838 } else { 839 840 // 841 // do the main check here 842 // 843 if (inet_pton($_address) === false) { 844 return false; 845 } 846 847 // 848 // then make sure it doesn't match a IPv4 address 849 // 850 if (preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 1) { 851 return false; 852 } 853 } 854 855 return true; 856 } 857 858 /** 859 * formats the given IPv6 address as a fully expanded IPv6 address 860 * 861 * @param string $_address the IPv6 address to expand 862 * 863 * @return string the fully expanded IPv6 address 864 * @access public 865 * 866 */ 867 public static function expandIPv6($_address) 868 { 869 $hex = unpack('H*hex', inet_pton($_address)); 870 871 return substr(preg_replace('/([A-f0-9]{4})/', "$1:", $hex['hex']), 0, -1); 872 } 873 874 /** 875 * sends a standard Net_DNS2_Packet_Request packet 876 * 877 * @param Net_DNS2_Packet $request a Net_DNS2_Packet_Request object 878 * @param boolean $use_tcp true/false if the function should 879 * use TCP for the request 880 * 881 * @return mixed returns a Net_DNS2_Packet_Response object, or false on error 882 * @throws Net_DNS2_Exception 883 * @access protected 884 * 885 */ 886 protected function sendPacket(Net_DNS2_Packet $request, $use_tcp) 887 { 888 // 889 // get the data from the packet 890 // 891 $data = $request->get(); 892 if (strlen($data) < Net_DNS2_Lookups::DNS_HEADER_SIZE) { 893 894 throw new Net_DNS2_Exception( 895 'invalid or empty packet for sending!', 896 Net_DNS2_Lookups::E_PACKET_INVALID, 897 null, 898 $request 899 ); 900 } 901 902 reset($this->nameservers); 903 904 // 905 // randomize the name server list if it's asked for 906 // 907 if ($this->ns_random == true) { 908 909 shuffle($this->nameservers); 910 } 911 912 // 913 // loop so we can handle server errors 914 // 915 $response = null; 916 $ns = false; 917 // php72 compatibility - Reset pointer to start of array 918 reset($this->nameservers); 919 920 while (1) { 921 922 // 923 // grab the next DNS server 924 // 925 // $ns = each($this->nameservers); 926 927 // php72 compatibility - grab the next DNS server 928 $ns = current($this->nameservers); 929 next($this->nameservers); 930 931 if ($ns === false) { 932 933 if (is_null($this->last_exception) == false) { 934 935 throw $this->last_exception; 936 } else { 937 938 throw new Net_DNS2_Exception( 939 'every name server provided has failed', 940 Net_DNS2_Lookups::E_NS_FAILED 941 ); 942 } 943 } 944 945 // php72 compatibility - each() returns array(0=>key, 1=>value), current() returns value. 946 // $ns = $ns[1]; 947 948 // 949 // if the use TCP flag (force TCP) is set, or the packet is bigger than our 950 // max allowed UDP size- which is either 512, or if this is DNSSEC request, 951 // then whatever the configured dnssec_payload_size is. 952 // 953 $max_udp_size = Net_DNS2_Lookups::DNS_MAX_UDP_SIZE; 954 if ($this->dnssec == true) 955 { 956 $max_udp_size = $this->dnssec_payload_size; 957 } 958 959 if ( ($use_tcp == true) || (strlen($data) > $max_udp_size) ) { 960 961 try 962 { 963 $response = $this->sendTCPRequest($ns, $data, ($request->question[0]->qtype == 'AXFR') ? true : false); 964 965 } catch(Net_DNS2_Exception $e) { 966 967 $this->last_exception = $e; 968 $this->last_exception_list[$ns] = $e; 969 970 continue; 971 } 972 973 // 974 // otherwise, send it using UDP 975 // 976 } else { 977 978 try 979 { 980 $response = $this->sendUDPRequest($ns, $data); 981 982 // 983 // check the packet header for a trucated bit; if it was truncated, 984 // then re-send the request as TCP. 985 // 986 if ($response->header->tc == 1) { 987 988 $response = $this->sendTCPRequest($ns, $data); 989 } 990 991 } catch(Net_DNS2_Exception $e) { 992 993 $this->last_exception = $e; 994 $this->last_exception_list[$ns] = $e; 995 996 continue; 997 } 998 } 999 1000 // 1001 // make sure header id's match between the request and response 1002 // 1003 if ($request->header->id != $response->header->id) { 1004 1005 $this->last_exception = new Net_DNS2_Exception( 1006 1007 'invalid header: the request and response id do not match.', 1008 Net_DNS2_Lookups::E_HEADER_INVALID, 1009 null, 1010 $request, 1011 $response 1012 ); 1013 1014 $this->last_exception_list[$ns] = $this->last_exception; 1015 continue; 1016 } 1017 1018 // 1019 // make sure the response is actually a response 1020 // 1021 // 0 = query, 1 = response 1022 // 1023 if ($response->header->qr != Net_DNS2_Lookups::QR_RESPONSE) { 1024 1025 $this->last_exception = new Net_DNS2_Exception( 1026 1027 'invalid header: the response provided is not a response packet.', 1028 Net_DNS2_Lookups::E_HEADER_INVALID, 1029 null, 1030 $request, 1031 $response 1032 ); 1033 1034 $this->last_exception_list[$ns] = $this->last_exception; 1035 continue; 1036 } 1037 1038 // 1039 // make sure the response code in the header is ok 1040 // 1041 if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) { 1042 1043 $this->last_exception = new Net_DNS2_Exception( 1044 1045 'DNS request failed: ' . 1046 Net_DNS2_Lookups::$result_code_messages[$response->header->rcode], 1047 $response->header->rcode, 1048 null, 1049 $request, 1050 $response 1051 ); 1052 1053 $this->last_exception_list[$ns] = $this->last_exception; 1054 continue; 1055 } 1056 1057 break; 1058 } 1059 1060 return $response; 1061 } 1062 1063 /** 1064 * cleans up a failed socket and throws the given exception 1065 * 1066 * @param string $_proto the protocol of the socket 1067 * @param string $_ns the name server to use for the request 1068 * @param string $_error the error message to throw at the end of the function 1069 * 1070 * @throws Net_DNS2_Exception 1071 * @access private 1072 * 1073 */ 1074 private function generateError($_proto, $_ns, $_error) 1075 { 1076 if (isset($this->sock[$_proto][$_ns]) == false) 1077 { 1078 throw new Net_DNS2_Exception('invalid socket referenced', Net_DNS2_Lookups::E_NS_INVALID_SOCKET); 1079 } 1080 1081 // 1082 // grab the last error message off the socket 1083 // 1084 $last_error = $this->sock[$_proto][$_ns]->last_error; 1085 1086 // 1087 // close it 1088 // 1089 $this->sock[$_proto][$_ns]->close(); 1090 1091 // 1092 // remove it from the socket cache 1093 // 1094 unset($this->sock[$_proto][$_ns]); 1095 1096 // 1097 // throw the error provided 1098 // 1099 throw new Net_DNS2_Exception($last_error, $_error); 1100 } 1101 1102 /** 1103 * sends a DNS request using TCP 1104 * 1105 * @param string $_ns the name server to use for the request 1106 * @param string $_data the raw DNS packet data 1107 * @param boolean $_axfr if this is a zone transfer request 1108 * 1109 * @return Net_DNS2_Packet_Response the reponse object 1110 * @throws Net_DNS2_Exception 1111 * @access private 1112 * 1113 */ 1114 private function sendTCPRequest($_ns, $_data, $_axfr = false) 1115 { 1116 // 1117 // grab the start time 1118 // 1119 $start_time = microtime(true); 1120 1121 // 1122 // see if we already have an open socket from a previous request; if so, try to use 1123 // that instead of opening a new one. 1124 // 1125 if ( (!isset($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns])) 1126 || (!($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] instanceof Net_DNS2_Socket)) 1127 ) { 1128 1129 // 1130 // if the socket library is available, then use that 1131 // 1132 if ($this->sockets_enabled === true) { 1133 1134 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Sockets( 1135 Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout 1136 ); 1137 1138 // 1139 // otherwise the streams library 1140 // 1141 } else { 1142 1143 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Streams( 1144 Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout 1145 ); 1146 } 1147 1148 // 1149 // if a local IP address / port is set, then add it 1150 // 1151 if (strlen($this->local_host) > 0) { 1152 1153 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->bindAddress( 1154 $this->local_host, $this->local_port 1155 ); 1156 } 1157 1158 // 1159 // open the socket 1160 // 1161 if ($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->open() === false) { 1162 1163 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1164 } 1165 } 1166 1167 // 1168 // write the data to the socket; if it fails, continue on 1169 // the while loop 1170 // 1171 if ($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->write($_data) === false) { 1172 1173 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1174 } 1175 1176 // 1177 // read the content, using select to wait for a response 1178 // 1179 $size = 0; 1180 $result = null; 1181 $response = null; 1182 1183 // 1184 // handle zone transfer requests differently than other requests. 1185 // 1186 if ($_axfr == true) { 1187 1188 $soa_count = 0; 1189 1190 while (1) { 1191 1192 // 1193 // read the data off the socket 1194 // 1195 $result = $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->read($size, ($this->dnssec == true) ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE); 1196 if ( ($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE) ) { 1197 1198 // 1199 // if we get an error, then keeping this socket around for a future request, could cause 1200 // an error- for example, https://github.com/mikepultz/netdns2/issues/61 1201 // 1202 // in this case, the connection was timing out, which once it did finally respond, left 1203 // data on the socket, which could be captured on a subsequent request. 1204 // 1205 // since there's no way to "reset" a socket, the only thing we can do it close it. 1206 // 1207 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1208 } 1209 1210 // 1211 // parse the first chunk as a packet 1212 // 1213 $chunk = new Net_DNS2_Packet_Response($result, $size); 1214 1215 // 1216 // if this is the first packet, then clone it directly, then 1217 // go through it to see if there are two SOA records 1218 // (indicating that it's the only packet) 1219 // 1220 if (is_null($response) == true) { 1221 1222 $response = clone $chunk; 1223 1224 // 1225 // look for a failed response; if the zone transfer 1226 // failed, then we don't need to do anything else at this 1227 // point, and we should just break out. 1228 // 1229 if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) { 1230 break; 1231 } 1232 1233 // 1234 // go through each answer 1235 // 1236 foreach ($response->answer as $index => $rr) { 1237 1238 // 1239 // count the SOA records 1240 // 1241 if ($rr->type == 'SOA') { 1242 $soa_count++; 1243 } 1244 } 1245 1246 // 1247 // if we have 2 or more SOA records, then we're done; 1248 // otherwise continue out so we read the rest of the 1249 // packets off the socket 1250 // 1251 if ($soa_count >= 2) { 1252 break; 1253 } else { 1254 continue; 1255 } 1256 1257 } else { 1258 1259 // 1260 // go through all these answers, and look for SOA records 1261 // 1262 foreach ($chunk->answer as $index => $rr) { 1263 1264 // 1265 // count the number of SOA records we find 1266 // 1267 if ($rr->type == 'SOA') { 1268 $soa_count++; 1269 } 1270 1271 // 1272 // add the records to a single response object 1273 // 1274 $response->answer[] = $rr; 1275 } 1276 1277 // 1278 // if we've found the second SOA record, we're done 1279 // 1280 if ($soa_count >= 2) { 1281 break; 1282 } 1283 } 1284 } 1285 1286 // 1287 // everything other than a AXFR 1288 // 1289 } else { 1290 1291 $result = $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->read($size, ($this->dnssec == true) ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE); 1292 if ( ($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE) ) { 1293 1294 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1295 } 1296 1297 // 1298 // create the packet object 1299 // 1300 $response = new Net_DNS2_Packet_Response($result, $size); 1301 } 1302 1303 // 1304 // store the query time 1305 // 1306 $response->response_time = microtime(true) - $start_time; 1307 1308 // 1309 // add the name server that the response came from to the response object, 1310 // and the socket type that was used. 1311 // 1312 $response->answer_from = $_ns; 1313 $response->answer_socket_type = Net_DNS2_Socket::SOCK_STREAM; 1314 1315 // 1316 // return the Net_DNS2_Packet_Response object 1317 // 1318 return $response; 1319 } 1320 1321 /** 1322 * sends a DNS request using UDP 1323 * 1324 * @param string $_ns the name server to use for the request 1325 * @param string $_data the raw DNS packet data 1326 * 1327 * @return Net_DNS2_Packet_Response the reponse object 1328 * @throws Net_DNS2_Exception 1329 * @access private 1330 * 1331 */ 1332 private function sendUDPRequest($_ns, $_data) 1333 { 1334 // 1335 // grab the start time 1336 // 1337 $start_time = microtime(true); 1338 1339 // 1340 // see if we already have an open socket from a previous request; if so, try to use 1341 // that instead of opening a new one. 1342 // 1343 if ( (!isset($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns])) 1344 || (!($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] instanceof Net_DNS2_Socket)) 1345 ) { 1346 1347 // 1348 // if the socket library is available, then use that 1349 // 1350 if ($this->sockets_enabled === true) { 1351 1352 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Sockets( 1353 Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout 1354 ); 1355 1356 // 1357 // otherwise the streams library 1358 // 1359 } else { 1360 1361 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Streams( 1362 Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout 1363 ); 1364 } 1365 1366 // 1367 // if a local IP address / port is set, then add it 1368 // 1369 if (strlen($this->local_host) > 0) { 1370 1371 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->bindAddress( 1372 $this->local_host, $this->local_port 1373 ); 1374 } 1375 1376 // 1377 // open the socket 1378 // 1379 if ($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->open() === false) { 1380 1381 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1382 } 1383 } 1384 1385 // 1386 // write the data to the socket 1387 // 1388 if ($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->write($_data) === false) { 1389 1390 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1391 } 1392 1393 // 1394 // read the content, using select to wait for a response 1395 // 1396 $size = 0; 1397 1398 $result = $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->read($size, ($this->dnssec == true) ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE); 1399 if (( $result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)) { 1400 1401 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1402 } 1403 1404 // 1405 // create the packet object 1406 // 1407 $response = new Net_DNS2_Packet_Response($result, $size); 1408 1409 // 1410 // store the query time 1411 // 1412 $response->response_time = microtime(true) - $start_time; 1413 1414 // 1415 // add the name server that the response came from to the response object, 1416 // and the socket type that was used. 1417 // 1418 $response->answer_from = $_ns; 1419 $response->answer_socket_type = Net_DNS2_Socket::SOCK_DGRAM; 1420 1421 // 1422 // return the Net_DNS2_Packet_Response object 1423 // 1424 return $response; 1425 } 1426} 1427 1428/* 1429 * Local variables: 1430 * tab-width: 4 1431 * c-basic-offset: 4 1432 * c-hanging-comment-ender-p: nil 1433 * End: 1434 */ 1435?> 1436