PageRenderTime 392ms CodeModel.GetById 91ms app.highlight 232ms RepoModel.GetById 59ms app.codeStats 1ms

/htdocs/includes/net/dns2.php

https://bitbucket.org/speedealing/speedealing
PHP | 1100 lines | 433 code | 175 blank | 492 comment | 124 complexity | fb9db7aa7f7aded37f13a487b676af00 MD5 | raw file
   1<?php
   2
   3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
   4
   5/**
   6 * DNS Library for handling lookups and updates. 
   7 *
   8 * PHP Version 5
   9 *
  10 * Copyright (c) 2010, Mike Pultz <mike@mikepultz.com>.
  11 * All rights reserved.
  12 *
  13 * Redistribution and use in source and binary forms, with or without
  14 * modification, are permitted provided that the following conditions
  15 * are met:
  16 *
  17 *   * Redistributions of source code must retain the above copyright
  18 *     notice, this list of conditions and the following disclaimer.
  19 *
  20 *   * Redistributions in binary form must reproduce the above copyright
  21 *     notice, this list of conditions and the following disclaimer in
  22 *     the documentation and/or other materials provided with the
  23 *     distribution.
  24 *
  25 *   * Neither the name of Mike Pultz nor the names of his contributors 
  26 *     may be used to endorse or promote products derived from this 
  27 *     software without specific prior written permission.
  28 *
  29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  32 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  33 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  35 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  36 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  37 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
  38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  39 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  40 * POSSIBILITY OF SUCH DAMAGE.
  41 *
  42 * @category  Networking
  43 * @package   Net_DNS2
  44 * @author    Mike Pultz <mike@mikepultz.com>
  45 * @copyright 2010 Mike Pultz <mike@mikepultz.com>
  46 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
  47 * @version   SVN: $Id: DNS2.php 180 2012-11-30 00:46:05Z mike.pultz $
  48 * @link      http://pear.php.net/package/Net_DNS2
  49 * @since     File available since Release 0.6.0
  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	 * the current version of this library
  73	 */
  74
  75	const VERSION = '1.2.5';
  76
  77	/*
  78	 * the default path to a resolv.conf file
  79	 */
  80	const RESOLV_CONF = '/etc/resolv.conf';
  81
  82	/*
  83	 * use TCP only (true/false)
  84	 */
  85
  86	public $use_tcp = false;
  87
  88	/*
  89	 * DNS Port to use (53)
  90	 */
  91	public $dns_port = 53;
  92
  93	/*
  94	 * the ip/port for use as a local socket
  95	 */
  96	public $local_host = '';
  97	public $local_port = 0;
  98
  99	/*
 100	 * timeout value for socket connections
 101	 */
 102	public $timeout = 5;
 103
 104	/*
 105	 * randomize the name servers list
 106	 */
 107	public $ns_random = false;
 108
 109	/*
 110	 * default domains
 111	 */
 112	public $domain = '';
 113
 114	/*
 115	 * domain search list - not actually used right now
 116	 */
 117	public $search_list = array();
 118
 119	/*
 120	 * enable cache; either "shared", "file" or "none"
 121	 */
 122	public $cache_type = 'none';
 123
 124	/*
 125	 * file name to use for shared memory segment or file cache
 126	 */
 127	public $cache_file = '/tmp/net_dns2.cache';
 128
 129	/*
 130	 * the max size of the cache file (in bytes)
 131	 */
 132	public $cache_size = 10000;
 133
 134	/*
 135	 * the method to use for storing cache data; either "serialize" or "json"
 136	 *
 137	 * json is faster, but can't remember the class names (everything comes back 
 138	 * as a "stdClass Object"; all the data is the same though. serialize is 
 139	 * slower, but will have all the class info.
 140	 *
 141	 * defaults to 'serialize'
 142	 */
 143	public $cache_serializer = 'serialize';
 144
 145	/*
 146	 * by default, according to RFC 1034
 147	 *
 148	 * CNAME RRs cause special action in DNS software.  When a name server
 149	 * fails to find a desired RR in the resource set associated with the
 150	 * domain name, it checks to see if the resource set consists of a CNAME
 151	 * record with a matching class.  If so, the name server includes the CNAME
 152	 * record in the response and restarts the query at the domain name
 153	 * specified in the data field of the CNAME record.
 154	 *
 155	 * this can cause "unexpected" behavious, since i'm sure *most* people
 156	 * don't know DNS does this; there may be cases where Net_DNS2 returns a
 157	 * positive response, even though the hostname the user looked up did not
 158	 * actually exist.
 159	 *
 160	 * strict_query_mode means that if the hostname that was looked up isn't
 161	 * actually in the answer section of the response, Net_DNS2 will return an 
 162	 * empty answer section, instead of an answer section that could contain 
 163	 * CNAME records.
 164	 *
 165	 */
 166	public $strict_query_mode = false;
 167
 168	/*
 169	 * if we should set the recursion desired bit to 1 or 0.
 170	 *
 171	 * by default this is set to true, we want the DNS server to perform a recursive
 172	 * request. If set to false, the RD bit will be set to 0, and the server will not 
 173	 * perform recursion on the request.
 174	 */
 175	public $recurse = true;
 176
 177	/*
 178	 * local sockets
 179	 */
 180	protected $sock = array('udp' => array(), 'tcp' => array());
 181
 182	/*
 183	 * name server list
 184	 */
 185	protected $nameservers = array();
 186
 187	/*
 188	 * if the socket extension is loaded
 189	 */
 190	protected $sockets_enabled = false;
 191
 192	/*
 193	 * the TSIG or SIG RR object for authentication
 194	 */
 195	protected $auth_signature = null;
 196
 197	/*
 198	 * the shared memory segment id for the local cache
 199	 */
 200	protected $cache = null;
 201
 202	/*
 203	 * internal setting for enabling cache
 204	 */
 205	protected $use_cache = false;
 206
 207	/*
 208	 * the last erro message returned by the sockets class
 209	 */
 210	private $_last_socket_error = '';
 211
 212	/**
 213	 * Constructor - base constructor for the Resolver and Updater
 214	 *
 215	 * @param mixed $options array of options or null for none
 216	 *
 217	 * @throws Net_DNS2_Exception
 218	 * @access public
 219	 *
 220	 */
 221	public function __construct(array $options = null) {
 222		//
 223		// check for the sockets extension
 224		//
 225        $this->sockets_enabled = extension_loaded('sockets');
 226
 227		//
 228		// load any options that were provided
 229		//
 230        if (!empty($options)) {
 231
 232			foreach ($options as $key => $value) {
 233
 234				if ($key == 'nameservers') {
 235
 236					$this->setServers($value);
 237				} else {
 238
 239					$this->$key = $value;
 240				}
 241			}
 242		}
 243
 244		//
 245		// if we're set to use the local shared memory cache, then
 246		// make sure it's been initialized
 247		//
 248        switch ($this->cache_type) {
 249			case 'shared':
 250				if (extension_loaded('shmop')) {
 251
 252					$this->cache = new Net_DNS2_Cache_Shm;
 253					$this->use_cache = true;
 254				} else {
 255
 256					throw new Net_DNS2_Exception(
 257					'shmop library is not available for cache', Net_DNS2_Lookups::E_CACHE_SHM_UNAVAIL
 258					);
 259				}
 260				break;
 261			case 'file':
 262
 263				$this->cache = new Net_DNS2_Cache_File;
 264				$this->use_cache = true;
 265
 266				break;
 267			case 'none':
 268				$this->use_cache = false;
 269				break;
 270			default:
 271
 272				throw new Net_DNS2_Exception(
 273				'un-supported cache type: ' . $this->cache_type, Net_DNS2_Lookups::E_CACHE_UNSUPPORTED
 274				);
 275		}
 276	}
 277
 278	/**
 279	 * autoload call-back function; used to auto-load classes
 280	 *
 281	 * @param string $name the name of the class
 282	 *
 283	 * @return void
 284	 * @access public
 285	 *
 286	 */
 287	static public function autoload($name) {
 288		//
 289		// only auto-load our classes
 290		//
 291        if (strncmp($name, 'Net_DNS2', 8) == 0) {
 292
 293			include_once DOL_DOCUMENT_ROOT . '/includes/' . str_replace('_', '/', strtolower($name)) . '.php';
 294		}
 295
 296		return;
 297	}
 298
 299	/**
 300	 * sets the name servers to be used
 301	 *
 302	 * @param mixed $nameservers either an array of name servers, or a file name 
 303	 *                           to parse, assuming it's in the resolv.conf format
 304	 *
 305	 * @return boolean
 306	 * @throws Net_DNS2_Exception
 307	 * @access public
 308	 *
 309	 */
 310	public function setServers($nameservers) {
 311		//
 312		// if it's an array, then use it directly
 313		//
 314        // otherwise, see if it's a path to a resolv.conf file and if so, load it
 315		//
 316        if (is_array($nameservers)) {
 317
 318			$this->nameservers = $nameservers;
 319		} else {
 320
 321			//
 322			// check to see if the file is readable
 323			//
 324            if (is_readable($nameservers) === true) {
 325
 326				$data = file_get_contents($nameservers);
 327				if ($data === false) {
 328					throw new Net_DNS2_Exception(
 329					'failed to read contents of file: ' . $nameservers, Net_DNS2_Lookups::E_NS_INVALID_FILE
 330					);
 331				}
 332
 333				$lines = explode("\n", $data);
 334
 335				foreach ($lines as $line) {
 336
 337					$line = trim($line);
 338
 339					//
 340					// ignore empty lines, and lines that are commented out
 341					//
 342                    if ((strlen($line) == 0) || ($line[0] == '#') || ($line[0] == ';')
 343					) {
 344						continue;
 345					}
 346
 347					list($key, $value) = preg_split('/\s+/', $line, 2);
 348
 349					$key = trim(strtolower($key));
 350					$value = trim(strtolower($value));
 351
 352					switch ($key) {
 353						case 'nameserver':
 354
 355							//
 356							// nameserver can be a IPv4 or IPv6 address
 357							//
 358                        if ((self::isIPv4($value) == true) || (self::isIPv6($value) == true)
 359							) {
 360
 361								$this->nameservers[] = $value;
 362							} else {
 363
 364								throw new Net_DNS2_Exception(
 365								'invalid nameserver entry: ' . $value, Net_DNS2_Lookups::E_NS_INVALID_ENTRY
 366								);
 367							}
 368							break;
 369
 370						case 'domain':
 371							$this->domain = $value;
 372							break;
 373
 374						case 'search':
 375							$this->search_list = preg_split('/\s+/', $value);
 376							break;
 377
 378						default:
 379							;
 380					}
 381				}
 382
 383				//
 384				// if we don't have a domain, but we have a search list, then
 385				// take the first entry on the search list as the domain
 386				//
 387                if ((strlen($this->domain) == 0) && (count($this->search_list) > 0)
 388				) {
 389					$this->domain = $this->search_list[0];
 390				}
 391			} else {
 392				throw new Net_DNS2_Exception(
 393				'resolver file file provided is not readable: ' . $nameservers, Net_DNS2_Lookups::E_NS_INVALID_FILE
 394				);
 395			}
 396		}
 397
 398		//
 399		// check the name servers
 400		//
 401        $this->checkServers();
 402
 403		return true;
 404	}
 405
 406	/**
 407	 * checks the list of name servers to make sure they're set
 408	 *
 409	 * @param mixed $default a path to a resolv.conf file or an array of servers.
 410	 *
 411	 * @return boolean
 412	 * @throws Net_DNS2_Exception
 413	 * @access protected
 414	 *
 415	 */
 416	protected function checkServers($default = null) {
 417		if (empty($this->nameservers)) {
 418
 419			if (isset($default)) {
 420
 421				$this->setServers($default);
 422			} else {
 423
 424				throw new Net_DNS2_Exception(
 425				'empty name servers list; you must provide a list of name ' .
 426				'servers, or the path to a resolv.conf file.', Net_DNS2_Lookups::E_NS_INVALID_ENTRY
 427				);
 428			}
 429		}
 430
 431		return true;
 432	}
 433
 434	/**
 435	 * adds a TSIG RR object for authentication
 436	 *
 437	 * @param string $keyname   the key name to use for the TSIG RR
 438	 * @param string $signature the key to sign the request.
 439	 * 
 440	 * @return boolean
 441	 * @access public
 442	 * @since  function available since release 1.1.0
 443	 *
 444	 */
 445	public function signTSIG($keyname, $signature = '') {
 446		//
 447		// if the TSIG was pre-created and passed in, then we can just used 
 448		// it as provided.
 449		//
 450        if ($keyname instanceof Net_DNS2_RR_TSIG) {
 451
 452			$this->auth_signature = $keyname;
 453		} else {
 454
 455			//
 456			// otherwise create the TSIG RR, but don't add it just yet; TSIG needs 
 457			// to be added as the last additional entry- so we'll add it just 
 458			// before we send.
 459			//
 460            $this->auth_signature = Net_DNS2_RR::fromString(
 461							strtolower(trim($keyname)) .
 462							' TSIG ' . $signature
 463			);
 464		}
 465
 466		return true;
 467	}
 468
 469	/**
 470	 * adds a SIG RR object for authentication
 471	 *
 472	 * @param string $filename the name of a file to load the signature from.
 473	 * 
 474	 * @return boolean
 475	 * @throws Net_DNS2_Exception
 476	 * @access public
 477	 * @since  function available since release 1.1.0
 478	 *
 479	 */
 480	public function signSIG0($filename) {
 481		//
 482		// check for OpenSSL
 483		//
 484        if (extension_loaded('openssl') === false) {
 485
 486			throw new Net_DNS2_Exception(
 487			'the OpenSSL extension is required to use SIG(0).', Net_DNS2_Lookups::E_OPENSSL_UNAVAIL
 488			);
 489		}
 490
 491		//
 492		// if the SIG was pre-created, then use it as-is
 493		//
 494        if ($filename instanceof Net_DNS2_RR_SIG) {
 495
 496			$this->auth_signature = $filename;
 497		} else {
 498
 499			//
 500			// otherwise, it's filename which needs to be parsed and processed.
 501			//
 502            $private = new Net_DNS2_PrivateKey($filename);
 503
 504			//
 505			// create a new Net_DNS2_RR_SIG object
 506			//
 507            $this->auth_signature = new Net_DNS2_RR_SIG();
 508
 509			//
 510			// reset some values
 511			//
 512            $this->auth_signature->name = $private->signname;
 513			$this->auth_signature->ttl = 0;
 514			$this->auth_signature->class = 'ANY';
 515
 516			//
 517			// these values are pulled from the private key
 518			//
 519            $this->auth_signature->algorithm = $private->algorithm;
 520			$this->auth_signature->keytag = $private->keytag;
 521			$this->auth_signature->signname = $private->signname;
 522
 523			//
 524			// these values are hard-coded for SIG0
 525			//
 526            $this->auth_signature->typecovered = 'SIG0';
 527			$this->auth_signature->labels = 0;
 528			$this->auth_signature->origttl = 0;
 529
 530			//
 531			// generate the dates
 532			//
 533            $t = time();
 534
 535			$this->auth_signature->sigincep = gmdate('YmdHis', $t);
 536			$this->auth_signature->sigexp = gmdate('YmdHis', $t + 500);
 537
 538			//
 539			// store the private key in the SIG object for later.
 540			//
 541            $this->auth_signature->private_key = $private;
 542		}
 543
 544		//
 545		// only RSAMD5 and RSASHA1 are supported for SIG(0)
 546		//
 547        switch ($this->auth_signature->algorithm) {
 548			case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:
 549			case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:
 550				break;
 551			default:
 552				throw new Net_DNS2_Exception(
 553				'only asymmetric algorithms work with SIG(0)!', Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
 554				);
 555		}
 556
 557		return true;
 558	}
 559
 560	/**
 561	 * PHP doesn't support unsigned integers, but many of the RR's return
 562	 * unsigned values (like SOA), so there is the possibility that the
 563	 * value will overrun on 32bit systems, and you'll end up with a 
 564	 * negative value.
 565	 *
 566	 * 64bit systems are not affected, as their PHP_IN_MAX value should
 567	 * be 64bit (ie 9223372036854775807)
 568	 *
 569	 * This function returns a negative integer value, as a string, with
 570	 * the correct unsigned value.
 571	 *
 572	 * @param string $_int the unsigned integer value to check
 573	 *
 574	 * @return string returns the unsigned value as a string.
 575	 * @access public
 576	 *
 577	 */
 578	public static function expandUint32($_int) {
 579		if (($_int < 0) && (PHP_INT_MAX == 2147483647)) {
 580			return sprintf('%u', $_int);
 581		} else {
 582			return $_int;
 583		}
 584	}
 585
 586	/**
 587	 * returns true/false if the given address is a valid IPv4 address
 588	 *
 589	 * @param string $_address the IPv4 address to check
 590	 *
 591	 * @return boolean returns true/false if the address is IPv4 address
 592	 * @access public
 593	 *
 594	 */
 595	public static function isIPv4($_address) {
 596		//
 597		// use filter_var() if it's available; it's faster than preg
 598		//
 599        if (extension_loaded('filter') == true) {
 600
 601			if (filter_var(
 602							$_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4
 603					) == false) {
 604				return false;
 605			}
 606		} else {
 607
 608			//
 609			// do the main check here;
 610			//
 611            if (inet_pton($_address) === false) {
 612				return false;
 613			}
 614
 615			//
 616			// then make sure we're not a IPv6 address
 617			//
 618            if (preg_match(
 619							'/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address
 620					) == 0) {
 621				return false;
 622			}
 623		}
 624
 625		return true;
 626	}
 627
 628	/**
 629	 * returns true/false if the given address is a valid IPv6 address
 630	 *
 631	 * @param string $_address the IPv6 address to check
 632	 *
 633	 * @return boolean returns true/false if the address is IPv6 address
 634	 * @access public
 635	 *
 636	 */
 637	public static function isIPv6($_address) {
 638		//
 639		// use filter_var() if it's available; it's faster than preg
 640		//
 641        if (extension_loaded('filter') == true) {
 642			if (filter_var(
 643							$_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6
 644					) == false) {
 645				return false;
 646			}
 647		} else {
 648
 649			//
 650			// do the main check here
 651			//
 652            if (inet_pton($_address) === false) {
 653				return false;
 654			}
 655
 656			//
 657			// then make sure it doesn't match a IPv4 address
 658			//
 659            if (preg_match(
 660							'/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address
 661					) == 1) {
 662				return false;
 663			}
 664		}
 665
 666		return true;
 667	}
 668
 669	/**
 670	 * formats the given IPv6 address as a fully expanded IPv6 address
 671	 *
 672	 * @param string $_address the IPv6 address to expand
 673	 *
 674	 * @return string the fully expanded IPv6 address
 675	 * @access public
 676	 *
 677	 */
 678	public static function expandIPv6($_address) {
 679		if (strpos($_address, '::') !== false) {
 680
 681			$part = explode('::', $_address);
 682			$part[0] = explode(':', $part[0]);
 683			$part[1] = explode(':', $part[1]);
 684
 685			$missing = array();
 686
 687			$x = (8 - (count($part[0]) + count($part[1])));
 688			for ($i = 0; $i < $x; $i++) {
 689
 690				array_push($missing, '0000');
 691			}
 692
 693			$missing = array_merge($part[0], $missing);
 694			$part = array_merge($missing, $part[1]);
 695		} else {
 696
 697			$part = explode(':', $_address);
 698		}
 699
 700		foreach ($part as &$p) {
 701			while (strlen($p) < 4) {
 702				$p = '0' . $p;
 703			}
 704		}
 705
 706		unset($p);
 707
 708		$result = implode(':', $part);
 709
 710		if (strlen($result) == 39) {
 711			return $result;
 712		} else {
 713			return false;
 714		}
 715	}
 716
 717	/**
 718	 * sends a standard Net_DNS2_Packet_Request packet
 719	 *
 720	 * @param Net_DNS2_Packet $request a Net_DNS2_Packet_Request object
 721	 * @param boolean         $use_tcp true/false if the function should
 722	 *                                 use TCP for the request
 723	 *
 724	 * @return mixed returns a Net_DNS2_Packet_Response object, or false on error
 725	 * @throws Net_DNS2_Exception
 726	 * @access protected
 727	 *
 728	 */
 729	protected function sendPacket(Net_DNS2_Packet $request, $use_tcp) {
 730		//
 731		// get the data from the packet
 732		//
 733        $data = $request->get();
 734		if (strlen($data) < Net_DNS2_Lookups::DNS_HEADER_SIZE) {
 735
 736			throw new Net_DNS2_Exception(
 737			'invalid or empty packet for sending!', Net_DNS2_Lookups::E_PACKET_INVALID
 738			);
 739		}
 740
 741		reset($this->nameservers);
 742
 743		//
 744		// randomize the name server list if it's asked for
 745		//
 746        if ($this->ns_random == true) {
 747
 748			shuffle($this->nameservers);
 749		}
 750
 751		//
 752		// loop so we can handle server errors
 753		//
 754        $response = null;
 755		$ns = '';
 756		$socket_type = null;
 757		$tcp_fallback = false;
 758
 759		while (1) {
 760
 761			//
 762			// grab the next DNS server
 763			//
 764            if ($tcp_fallback == false) {
 765
 766				$ns = each($this->nameservers);
 767				if ($ns === false) {
 768
 769					throw new Net_DNS2_Exception(
 770					'every name server provided has failed: ' .
 771					$this->_last_socket_error, Net_DNS2_Lookups::E_NS_FAILED
 772					);
 773				}
 774
 775				$ns = $ns[1];
 776			}
 777
 778			//
 779			// if the use TCP flag (force TCP) is set, or the packet is bigger 
 780			// than 512 bytes, use TCP for sending the packet
 781			//
 782            if (($use_tcp == true) || (strlen($data) > Net_DNS2_Lookups::DNS_MAX_UDP_SIZE) || ($tcp_fallback == true)
 783			) {
 784				$tcp_fallback = false;
 785				$socket_type = Net_DNS2_Socket::SOCK_STREAM;
 786
 787				//
 788				// create the socket object
 789				//
 790                if ((!isset($this->sock['tcp'][$ns])) || (!($this->sock['tcp'][$ns] instanceof Net_DNS2_Socket))
 791				) {
 792					if ($this->sockets_enabled === true) {
 793
 794						$this->sock['tcp'][$ns] = new Net_DNS2_Socket_Sockets(
 795								Net_DNS2_Socket::SOCK_STREAM, $ns, $this->dns_port, $this->timeout
 796						);
 797					} else {
 798
 799						$this->sock['tcp'][$ns] = new Net_DNS2_Socket_Streams(
 800								Net_DNS2_Socket::SOCK_STREAM, $ns, $this->dns_port, $this->timeout
 801						);
 802					}
 803				}
 804
 805				//
 806				// if a local IP address / port is set, then add it
 807				//
 808                if (strlen($this->local_host) > 0) {
 809
 810					$this->sock['tcp'][$ns]->bindAddress(
 811							$this->local_host, $this->local_port
 812					);
 813				}
 814
 815				//
 816				// open it; if it fails, continue in the while loop
 817				//
 818                if ($this->sock['tcp'][$ns]->open() === false) {
 819
 820					$this->_last_socket_error = $this->sock['tcp'][$ns]->last_error;
 821					continue;
 822				}
 823
 824				//
 825				// write the data to the socket; if it fails, continue on 
 826				// the while loop
 827				//
 828                if ($this->sock['tcp'][$ns]->write($data) === false) {
 829
 830					$this->_last_socket_error = $this->sock['tcp'][$ns]->last_error;
 831					continue;
 832				}
 833
 834				//
 835				// read the content, using select to wait for a response
 836				//
 837                $size = 0;
 838				$result = null;
 839
 840				//
 841				// handle zone transfer requests differently than other requests.
 842				//
 843                if ($request->question[0]->qtype == 'AXFR') {
 844
 845					$soa_count = 0;
 846
 847					while (1) {
 848
 849						//
 850						// read the data off the socket
 851						//
 852                        $result = $this->sock['tcp'][$ns]->read($size);
 853						if (($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)
 854						) {
 855							$this->_last_socket_error = $this->sock['tcp'][$ns]->last_error;
 856							break;
 857						}
 858
 859						//
 860						// parse the first chunk as a packet
 861						//
 862                        $chunk = new Net_DNS2_Packet_Response($result, $size);
 863
 864						//
 865						// if this is the first packet, then clone it directly, then
 866						// go through it to see if there are two SOA records 
 867						// (indicating that it's the only packet)
 868						//
 869                        if (is_null($response) == true) {
 870
 871							$response = clone $chunk;
 872
 873							//
 874							// look for a failed response; if the zone transfer
 875							// failed, then we don't need to do anything else at this
 876							// point, and we should just break out.
 877							//
 878                            if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) {
 879								break;
 880							}
 881
 882							//
 883							// go through each answer
 884							//
 885                            foreach ($response->answer as $index => $rr) {
 886
 887								//
 888								// count the SOA records
 889								//
 890                                if ($rr->type == 'SOA') {
 891									$soa_count++;
 892								}
 893							}
 894
 895							//
 896							// if we have 2 or more SOA records, then we're done; 
 897							// otherwise continue out so we read the rest of the 
 898							// packets off the socket
 899							//
 900                            if ($soa_count >= 2) {
 901								break;
 902							} else {
 903								continue;
 904							}
 905						} else {
 906
 907							//
 908							// go through all these answers, and look for SOA records
 909							//
 910                            foreach ($chunk->answer as $index => $rr) {
 911
 912								//
 913								// count the number of SOA records we find
 914								//
 915                                if ($rr->type == 'SOA') {
 916									$soa_count++;
 917								}
 918
 919								//
 920								// add the records to a single response object
 921								//
 922                                $response->answer[] = $rr;
 923							}
 924
 925							//
 926							// if we've found the second SOA record, we're done
 927							//
 928                            if ($soa_count >= 2) {
 929								break;
 930							}
 931						}
 932					}
 933				} else {
 934
 935					$result = $this->sock['tcp'][$ns]->read($size);
 936					if (($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)
 937					) {
 938						$this->_last_socket_error = $this->sock['tcp'][$ns]->last_error;
 939						continue;
 940					}
 941
 942					//
 943					// create the packet object
 944					//
 945                    $response = new Net_DNS2_Packet_Response($result, $size);
 946				}
 947
 948				break;
 949			} else {
 950
 951				$socket_type = Net_DNS2_Socket::SOCK_DGRAM;
 952
 953				//
 954				// create the socket object
 955				//
 956                if ((!isset($this->sock['udp'][$ns])) || (!($this->sock['udp'][$ns] instanceof Net_DNS2_Socket))
 957				) {
 958					if ($this->sockets_enabled === true) {
 959
 960						$this->sock['udp'][$ns] = new Net_DNS2_Socket_Sockets(
 961								Net_DNS2_Socket::SOCK_DGRAM, $ns, $this->dns_port, $this->timeout
 962						);
 963					} else {
 964
 965						$this->sock['udp'][$ns] = new Net_DNS2_Socket_Streams(
 966								Net_DNS2_Socket::SOCK_DGRAM, $ns, $this->dns_port, $this->timeout
 967						);
 968					}
 969				}
 970
 971				//
 972				// if a local IP address / port is set, then add it
 973				//
 974                if (strlen($this->local_host) > 0) {
 975
 976					$this->sock['udp'][$ns]->bindAddress(
 977							$this->local_host, $this->local_port
 978					);
 979				}
 980
 981				//
 982				// open it
 983				//
 984                if ($this->sock['udp'][$ns]->open() === false) {
 985
 986					$this->_last_socket_error = $this->sock['udp'][$ns]->last_error;
 987					continue;
 988				}
 989
 990				//
 991				// write the data to the socket
 992				//
 993                if ($this->sock['udp'][$ns]->write($data) === false) {
 994
 995					$this->_last_socket_error = $this->sock['udp'][$ns]->last_error;
 996					continue;
 997				}
 998
 999				//
1000				// read the content, using select to wait for a response
1001				//
1002                $size = 0;
1003
1004				$result = $this->sock['udp'][$ns]->read($size);
1005				if (( $result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)
1006				) {
1007					$this->_last_socket_error = $this->sock['udp'][$ns]->last_error;
1008					continue;
1009				}
1010
1011				//
1012				// create the packet object
1013				//
1014                $response = new Net_DNS2_Packet_Response($result, $size);
1015
1016				if (is_null($response)) {
1017
1018					throw new Net_DNS2_Exception(
1019					'empty response object', Net_DNS2_Lookups::E_NS_FAILED
1020					);
1021				}
1022
1023				//
1024				// check the packet header for a trucated bit; if it was truncated,
1025				// then re-send the request as TCP.
1026				//
1027                if ($response->header->tc == 1) {
1028
1029					$tcp_fallback = true;
1030					continue;
1031				}
1032
1033				break;
1034			}
1035		}
1036
1037		//
1038		// if $response is null, then we didn't even try once; which shouldn't
1039		// actually ever happen
1040		//
1041        if (is_null($response)) {
1042
1043			throw new Net_DNS2_Exception(
1044			'empty response object', Net_DNS2_Lookups::E_NS_FAILED
1045			);
1046		}
1047
1048		//
1049		// add the name server that the response came from to the response object,
1050		// and the socket type that was used.
1051		//
1052        $response->answer_from = $ns;
1053		$response->answer_socket_type = $socket_type;
1054
1055		//
1056		// make sure header id's match between the request and response
1057		//
1058        if ($request->header->id != $response->header->id) {
1059
1060			throw new Net_DNS2_Exception(
1061			'invalid header: the request and response id do not match.', Net_DNS2_Lookups::E_HEADER_INVALID
1062			);
1063		}
1064
1065		//
1066		// make sure the response is actually a response
1067		// 
1068		// 0 = query, 1 = response
1069		//
1070        if ($response->header->qr != Net_DNS2_Lookups::QR_RESPONSE) {
1071
1072			throw new Net_DNS2_Exception(
1073			'invalid header: the response provided is not a response packet.', Net_DNS2_Lookups::E_HEADER_INVALID
1074			);
1075		}
1076
1077		//
1078		// make sure the response code in the header is ok
1079		//
1080        if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) {
1081
1082			throw new Net_DNS2_Exception(
1083			'DNS request failed: ' .
1084			Net_DNS2_Lookups::$result_code_messages[$response->header->rcode], $response->header->rcode
1085			);
1086		}
1087
1088		return $response;
1089	}
1090
1091}
1092
1093/*
1094 * Local variables:
1095 * tab-width: 4
1096 * c-basic-offset: 4
1097 * c-hanging-comment-ender-p: nil
1098 * End:
1099 */
1100?>