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.4'; 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 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 = ''; 917 918 while (1) { 919 920 // 921 // grab the next DNS server 922 // 923 $ns = current($this->nameservers); 924 next($this->nameservers); 925 926 if ($ns === false) { 927 928 if (is_null($this->last_exception) == false) { 929 930 throw $this->last_exception; 931 } else { 932 933 throw new Net_DNS2_Exception( 934 'every name server provided has failed', 935 Net_DNS2_Lookups::E_NS_FAILED 936 ); 937 } 938 } 939 940 // 941 // if the use TCP flag (force TCP) is set, or the packet is bigger than our 942 // max allowed UDP size- which is either 512, or if this is DNSSEC request, 943 // then whatever the configured dnssec_payload_size is. 944 // 945 $max_udp_size = Net_DNS2_Lookups::DNS_MAX_UDP_SIZE; 946 if ($this->dnssec == true) 947 { 948 $max_udp_size = $this->dnssec_payload_size; 949 } 950 951 if ( ($use_tcp == true) || (strlen($data) > $max_udp_size) ) { 952 953 try 954 { 955 $response = $this->sendTCPRequest($ns, $data, ($request->question[0]->qtype == 'AXFR') ? true : false); 956 957 } catch(Net_DNS2_Exception $e) { 958 959 $this->last_exception = $e; 960 $this->last_exception_list[$ns] = $e; 961 962 continue; 963 } 964 965 // 966 // otherwise, send it using UDP 967 // 968 } else { 969 970 try 971 { 972 $response = $this->sendUDPRequest($ns, $data); 973 974 // 975 // check the packet header for a trucated bit; if it was truncated, 976 // then re-send the request as TCP. 977 // 978 if ($response->header->tc == 1) { 979 980 $response = $this->sendTCPRequest($ns, $data); 981 } 982 983 } catch(Net_DNS2_Exception $e) { 984 985 $this->last_exception = $e; 986 $this->last_exception_list[$ns] = $e; 987 988 continue; 989 } 990 } 991 992 // 993 // make sure header id's match between the request and response 994 // 995 if ($request->header->id != $response->header->id) { 996 997 $this->last_exception = new Net_DNS2_Exception( 998 999 'invalid header: the request and response id do not match.', 1000 Net_DNS2_Lookups::E_HEADER_INVALID, 1001 null, 1002 $request, 1003 $response 1004 ); 1005 1006 $this->last_exception_list[$ns] = $this->last_exception; 1007 continue; 1008 } 1009 1010 // 1011 // make sure the response is actually a response 1012 // 1013 // 0 = query, 1 = response 1014 // 1015 if ($response->header->qr != Net_DNS2_Lookups::QR_RESPONSE) { 1016 1017 $this->last_exception = new Net_DNS2_Exception( 1018 1019 'invalid header: the response provided is not a response packet.', 1020 Net_DNS2_Lookups::E_HEADER_INVALID, 1021 null, 1022 $request, 1023 $response 1024 ); 1025 1026 $this->last_exception_list[$ns] = $this->last_exception; 1027 continue; 1028 } 1029 1030 // 1031 // make sure the response code in the header is ok 1032 // 1033 if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) { 1034 1035 $this->last_exception = new Net_DNS2_Exception( 1036 1037 'DNS request failed: ' . 1038 Net_DNS2_Lookups::$result_code_messages[$response->header->rcode], 1039 $response->header->rcode, 1040 null, 1041 $request, 1042 $response 1043 ); 1044 1045 $this->last_exception_list[$ns] = $this->last_exception; 1046 continue; 1047 } 1048 1049 break; 1050 } 1051 1052 return $response; 1053 } 1054 1055 /** 1056 * cleans up a failed socket and throws the given exception 1057 * 1058 * @param string $_proto the protocol of the socket 1059 * @param string $_ns the name server to use for the request 1060 * @param string $_error the error message to throw at the end of the function 1061 * 1062 * @throws Net_DNS2_Exception 1063 * @access private 1064 * 1065 */ 1066 private function generateError($_proto, $_ns, $_error) 1067 { 1068 if (isset($this->sock[$_proto][$_ns]) == false) 1069 { 1070 throw new Net_DNS2_Exception('invalid socket referenced', Net_DNS2_Lookups::E_NS_INVALID_SOCKET); 1071 } 1072 1073 // 1074 // grab the last error message off the socket 1075 // 1076 $last_error = $this->sock[$_proto][$_ns]->last_error; 1077 1078 // 1079 // close it 1080 // 1081 $this->sock[$_proto][$_ns]->close(); 1082 1083 // 1084 // remove it from the socket cache 1085 // 1086 unset($this->sock[$_proto][$_ns]); 1087 1088 // 1089 // throw the error provided 1090 // 1091 throw new Net_DNS2_Exception($last_error, $_error); 1092 } 1093 1094 /** 1095 * sends a DNS request using TCP 1096 * 1097 * @param string $_ns the name server to use for the request 1098 * @param string $_data the raw DNS packet data 1099 * @param boolean $_axfr if this is a zone transfer request 1100 * 1101 * @return Net_DNS2_Packet_Response the reponse object 1102 * @throws Net_DNS2_Exception 1103 * @access private 1104 * 1105 */ 1106 private function sendTCPRequest($_ns, $_data, $_axfr = false) 1107 { 1108 // 1109 // grab the start time 1110 // 1111 $start_time = microtime(true); 1112 1113 // 1114 // see if we already have an open socket from a previous request; if so, try to use 1115 // that instead of opening a new one. 1116 // 1117 if ( (!isset($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns])) 1118 || (!($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] instanceof Net_DNS2_Socket)) 1119 ) { 1120 1121 // 1122 // if the socket library is available, then use that 1123 // 1124 if ($this->sockets_enabled === true) { 1125 1126 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Sockets( 1127 Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout 1128 ); 1129 1130 // 1131 // otherwise the streams library 1132 // 1133 } else { 1134 1135 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Streams( 1136 Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout 1137 ); 1138 } 1139 1140 // 1141 // if a local IP address / port is set, then add it 1142 // 1143 if (strlen($this->local_host) > 0) { 1144 1145 $this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->bindAddress( 1146 $this->local_host, $this->local_port 1147 ); 1148 } 1149 1150 // 1151 // open the socket 1152 // 1153 if ($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->open() === false) { 1154 1155 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1156 } 1157 } 1158 1159 // 1160 // write the data to the socket; if it fails, continue on 1161 // the while loop 1162 // 1163 if ($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->write($_data) === false) { 1164 1165 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1166 } 1167 1168 // 1169 // read the content, using select to wait for a response 1170 // 1171 $size = 0; 1172 $result = null; 1173 $response = null; 1174 1175 // 1176 // handle zone transfer requests differently than other requests. 1177 // 1178 if ($_axfr == true) { 1179 1180 $soa_count = 0; 1181 1182 while (1) { 1183 1184 // 1185 // read the data off the socket 1186 // 1187 $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); 1188 if ( ($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE) ) { 1189 1190 // 1191 // if we get an error, then keeping this socket around for a future request, could cause 1192 // an error- for example, https://github.com/mikepultz/netdns2/issues/61 1193 // 1194 // in this case, the connection was timing out, which once it did finally respond, left 1195 // data on the socket, which could be captured on a subsequent request. 1196 // 1197 // since there's no way to "reset" a socket, the only thing we can do it close it. 1198 // 1199 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1200 } 1201 1202 // 1203 // parse the first chunk as a packet 1204 // 1205 $chunk = new Net_DNS2_Packet_Response($result, $size); 1206 1207 // 1208 // if this is the first packet, then clone it directly, then 1209 // go through it to see if there are two SOA records 1210 // (indicating that it's the only packet) 1211 // 1212 if (is_null($response) == true) { 1213 1214 $response = clone $chunk; 1215 1216 // 1217 // look for a failed response; if the zone transfer 1218 // failed, then we don't need to do anything else at this 1219 // point, and we should just break out. 1220 // 1221 if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) { 1222 break; 1223 } 1224 1225 // 1226 // go through each answer 1227 // 1228 foreach ($response->answer as $index => $rr) { 1229 1230 // 1231 // count the SOA records 1232 // 1233 if ($rr->type == 'SOA') { 1234 $soa_count++; 1235 } 1236 } 1237 1238 // 1239 // if we have 2 or more SOA records, then we're done; 1240 // otherwise continue out so we read the rest of the 1241 // packets off the socket 1242 // 1243 if ($soa_count >= 2) { 1244 break; 1245 } else { 1246 continue; 1247 } 1248 1249 } else { 1250 1251 // 1252 // go through all these answers, and look for SOA records 1253 // 1254 foreach ($chunk->answer as $index => $rr) { 1255 1256 // 1257 // count the number of SOA records we find 1258 // 1259 if ($rr->type == 'SOA') { 1260 $soa_count++; 1261 } 1262 1263 // 1264 // add the records to a single response object 1265 // 1266 $response->answer[] = $rr; 1267 } 1268 1269 // 1270 // if we've found the second SOA record, we're done 1271 // 1272 if ($soa_count >= 2) { 1273 break; 1274 } 1275 } 1276 } 1277 1278 // 1279 // everything other than a AXFR 1280 // 1281 } else { 1282 1283 $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); 1284 if ( ($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE) ) { 1285 1286 $this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1287 } 1288 1289 // 1290 // create the packet object 1291 // 1292 $response = new Net_DNS2_Packet_Response($result, $size); 1293 } 1294 1295 // 1296 // store the query time 1297 // 1298 $response->response_time = microtime(true) - $start_time; 1299 1300 // 1301 // add the name server that the response came from to the response object, 1302 // and the socket type that was used. 1303 // 1304 $response->answer_from = $_ns; 1305 $response->answer_socket_type = Net_DNS2_Socket::SOCK_STREAM; 1306 1307 // 1308 // return the Net_DNS2_Packet_Response object 1309 // 1310 return $response; 1311 } 1312 1313 /** 1314 * sends a DNS request using UDP 1315 * 1316 * @param string $_ns the name server to use for the request 1317 * @param string $_data the raw DNS packet data 1318 * 1319 * @return Net_DNS2_Packet_Response the reponse object 1320 * @throws Net_DNS2_Exception 1321 * @access private 1322 * 1323 */ 1324 private function sendUDPRequest($_ns, $_data) 1325 { 1326 // 1327 // grab the start time 1328 // 1329 $start_time = microtime(true); 1330 1331 // 1332 // see if we already have an open socket from a previous request; if so, try to use 1333 // that instead of opening a new one. 1334 // 1335 if ( (!isset($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns])) 1336 || (!($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] instanceof Net_DNS2_Socket)) 1337 ) { 1338 1339 // 1340 // if the socket library is available, then use that 1341 // 1342 if ($this->sockets_enabled === true) { 1343 1344 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Sockets( 1345 Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout 1346 ); 1347 1348 // 1349 // otherwise the streams library 1350 // 1351 } else { 1352 1353 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Streams( 1354 Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout 1355 ); 1356 } 1357 1358 // 1359 // if a local IP address / port is set, then add it 1360 // 1361 if (strlen($this->local_host) > 0) { 1362 1363 $this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->bindAddress( 1364 $this->local_host, $this->local_port 1365 ); 1366 } 1367 1368 // 1369 // open the socket 1370 // 1371 if ($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->open() === false) { 1372 1373 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1374 } 1375 } 1376 1377 // 1378 // write the data to the socket 1379 // 1380 if ($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->write($_data) === false) { 1381 1382 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1383 } 1384 1385 // 1386 // read the content, using select to wait for a response 1387 // 1388 $size = 0; 1389 1390 $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); 1391 if (( $result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)) { 1392 1393 $this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED); 1394 } 1395 1396 // 1397 // create the packet object 1398 // 1399 $response = new Net_DNS2_Packet_Response($result, $size); 1400 1401 // 1402 // store the query time 1403 // 1404 $response->response_time = microtime(true) - $start_time; 1405 1406 // 1407 // add the name server that the response came from to the response object, 1408 // and the socket type that was used. 1409 // 1410 $response->answer_from = $_ns; 1411 $response->answer_socket_type = Net_DNS2_Socket::SOCK_DGRAM; 1412 1413 // 1414 // return the Net_DNS2_Packet_Response object 1415 // 1416 return $response; 1417 } 1418} 1419 1420/* 1421 * Local variables: 1422 * tab-width: 4 1423 * c-basic-offset: 4 1424 * c-hanging-comment-ender-p: nil 1425 * End: 1426 */ 1427?> 1428