2019-12-14 10:45:22 +08:00
|
|
|
<?php
|
|
|
|
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
|
|
|
|
|
|
|
/**
|
2019-12-14 19:27:05 +08:00
|
|
|
* DNS Library for handling lookups and updates.
|
2019-12-14 10:45:22 +08:00
|
|
|
*
|
|
|
|
* PHP Version 5
|
|
|
|
*
|
|
|
|
* Copyright (c) 2010, Mike Pultz <mike@mikepultz.com>.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in
|
|
|
|
* the documentation and/or other materials provided with the
|
|
|
|
* distribution.
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* * Neither the name of Mike Pultz nor the names of his contributors
|
|
|
|
* may be used to endorse or promote products derived from this
|
2019-12-14 10:45:22 +08:00
|
|
|
* software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* @category Networking
|
|
|
|
* @package Net_DNS2
|
|
|
|
* @author Mike Pultz <mike@mikepultz.com>
|
|
|
|
* @copyright 2010 Mike Pultz <mike@mikepultz.com>
|
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
|
|
|
* @version SVN: $Id$
|
|
|
|
* @link http://pear.php.net/package/Net_DNS2
|
|
|
|
* @since File available since Release 0.6.0
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* register the auto-load function
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
spl_autoload_register('Net_DNS2::autoload');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is the base class for the Net_DNS2_Resolver and Net_DNS2_Updater
|
|
|
|
* classes.
|
|
|
|
*
|
|
|
|
* @category Networking
|
|
|
|
* @package Net_DNS2
|
|
|
|
* @author Mike Pultz <mike@mikepultz.com>
|
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
|
|
|
* @link http://pear.php.net/package/Net_DNS2
|
|
|
|
* @see Net_DNS2_Resolver, Net_DNS2_Updater
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
class Net_DNS2 {
|
2019-12-14 10:45:22 +08:00
|
|
|
/*
|
|
|
|
* the current version of this library
|
|
|
|
*/
|
|
|
|
const VERSION = '1.4.4';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the default path to a resolv.conf file
|
|
|
|
*/
|
|
|
|
const RESOLV_CONF = '/etc/resolv.conf';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* override options from the resolv.conf file
|
|
|
|
*
|
|
|
|
* if this is set, then certain values from the resolv.conf file will override
|
|
|
|
* local settings. This is disabled by default to remain backwards compatible.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $use_resolv_options = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* use TCP only (true/false)
|
|
|
|
*/
|
|
|
|
public $use_tcp = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DNS Port to use (53)
|
|
|
|
*/
|
|
|
|
public $dns_port = 53;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the ip/port for use as a local socket
|
|
|
|
*/
|
|
|
|
public $local_host = '';
|
|
|
|
public $local_port = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* timeout value for socket connections
|
|
|
|
*/
|
|
|
|
public $timeout = 5;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* randomize the name servers list
|
|
|
|
*/
|
|
|
|
public $ns_random = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* default domains
|
|
|
|
*/
|
|
|
|
public $domain = '';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* domain search list - not actually used right now
|
|
|
|
*/
|
|
|
|
public $search_list = array();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* enable cache; either "shared", "file" or "none"
|
|
|
|
*/
|
|
|
|
public $cache_type = 'none';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* file name to use for shared memory segment or file cache
|
|
|
|
*/
|
|
|
|
public $cache_file = '/tmp/net_dns2.cache';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the max size of the cache file (in bytes)
|
|
|
|
*/
|
|
|
|
public $cache_size = 50000;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the method to use for storing cache data; either "serialize" or "json"
|
|
|
|
*
|
|
|
|
* json is faster, but can't remember the class names (everything comes back
|
|
|
|
* as a "stdClass Object"; all the data is the same though. serialize is
|
|
|
|
* slower, but will have all the class info.
|
|
|
|
*
|
|
|
|
* defaults to 'serialize'
|
|
|
|
*/
|
|
|
|
public $cache_serializer = 'serialize';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* by default, according to RFC 1034
|
|
|
|
*
|
|
|
|
* CNAME RRs cause special action in DNS software. When a name server
|
|
|
|
* fails to find a desired RR in the resource set associated with the
|
|
|
|
* domain name, it checks to see if the resource set consists of a CNAME
|
|
|
|
* record with a matching class. If so, the name server includes the CNAME
|
|
|
|
* record in the response and restarts the query at the domain name
|
|
|
|
* specified in the data field of the CNAME record.
|
|
|
|
*
|
|
|
|
* this can cause "unexpected" behavious, since i'm sure *most* people
|
|
|
|
* don't know DNS does this; there may be cases where Net_DNS2 returns a
|
|
|
|
* positive response, even though the hostname the user looked up did not
|
|
|
|
* actually exist.
|
|
|
|
*
|
|
|
|
* strict_query_mode means that if the hostname that was looked up isn't
|
|
|
|
* actually in the answer section of the response, Net_DNS2 will return an
|
|
|
|
* empty answer section, instead of an answer section that could contain
|
|
|
|
* CNAME records.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $strict_query_mode = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if we should set the recursion desired bit to 1 or 0.
|
|
|
|
*
|
|
|
|
* by default this is set to true, we want the DNS server to perform a recursive
|
|
|
|
* request. If set to false, the RD bit will be set to 0, and the server will
|
|
|
|
* not perform recursion on the request.
|
|
|
|
*/
|
|
|
|
public $recurse = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* request DNSSEC values, by setting the DO flag to 1; this actually makes
|
|
|
|
* the resolver add a OPT RR to the additional section, and sets the DO flag
|
|
|
|
* in this RR to 1
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $dnssec = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set the DNSSEC AD (Authentic Data) bit on/off; the AD bit on the request
|
|
|
|
* side was previously undefined, and resolvers we instructed to always clear
|
|
|
|
* the AD bit when sending a request.
|
|
|
|
*
|
|
|
|
* RFC6840 section 5.7 defines setting the AD bit in the query as a signal to
|
|
|
|
* the server that it wants the value of the AD bit, without needed to request
|
|
|
|
* all the DNSSEC data via the DO bit.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $dnssec_ad_flag = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set the DNSSEC CD (Checking Disabled) bit on/off; turning this off, means
|
|
|
|
* that the DNS resolver will perform it's own signature validation- so the DNS
|
|
|
|
* servers simply pass through all the details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $dnssec_cd_flag = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the EDNS(0) UDP payload size to use when making DNSSEC requests
|
|
|
|
* see RFC 4035 section 4.1 - EDNS Support.
|
|
|
|
*
|
|
|
|
* there is some different ideas on the suggest size to supprt; but it seems to
|
|
|
|
* be "at least 1220 bytes, but SHOULD support 4000 bytes.
|
|
|
|
*
|
|
|
|
* we'll just support 4000
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public $dnssec_payload_size = 4000;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the last exeception that was generated
|
|
|
|
*/
|
|
|
|
public $last_exception = null;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the list of exceptions by name server
|
|
|
|
*/
|
|
|
|
public $last_exception_list = array();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* name server list
|
|
|
|
*/
|
|
|
|
public $nameservers = array();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* local sockets
|
|
|
|
*/
|
|
|
|
protected $sock = array(Net_DNS2_Socket::SOCK_DGRAM => array(), Net_DNS2_Socket::SOCK_STREAM => array());
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if the socket extension is loaded
|
|
|
|
*/
|
|
|
|
protected $sockets_enabled = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the TSIG or SIG RR object for authentication
|
|
|
|
*/
|
|
|
|
protected $auth_signature = null;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the shared memory segment id for the local cache
|
|
|
|
*/
|
|
|
|
protected $cache = null;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* internal setting for enabling cache
|
|
|
|
*/
|
|
|
|
protected $use_cache = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor - base constructor for the Resolver and Updater
|
|
|
|
*
|
|
|
|
* @param mixed $options array of options or null for none
|
|
|
|
*
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public function __construct(array $options = null){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// check for the sockets extension; we no longer support the sockets library under
|
|
|
|
// windows- there have been too many errors related to sockets under windows-
|
|
|
|
// specifically inconsistent socket defines between versions of windows-
|
|
|
|
//
|
|
|
|
// and since I can't seem to find a way to get the actual windows version, it
|
|
|
|
// doesn't seem fixable in the code.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((extension_loaded('sockets') == true) && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sockets_enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// load any options that were provided
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(!empty($options)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
foreach($options as $key => $value){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if($key == 'nameservers'){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->setServers($value);
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->$key = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if we're set to use the local shared memory cache, then
|
|
|
|
// make sure it's been initialized
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
switch($this->cache_type){
|
|
|
|
case 'shared':
|
|
|
|
if(extension_loaded('shmop')){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->cache = new Net_DNS2_Cache_Shm;
|
|
|
|
$this->use_cache = true;
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'shmop library is not available for cache',
|
|
|
|
Net_DNS2_Lookups::E_CACHE_SHM_UNAVAIL
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'file':
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->cache = new Net_DNS2_Cache_File;
|
|
|
|
$this->use_cache = true;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
break;
|
|
|
|
case 'none':
|
|
|
|
$this->use_cache = false;
|
|
|
|
break;
|
|
|
|
default:
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'un-supported cache type: ' . $this->cache_type,
|
|
|
|
Net_DNS2_Lookups::E_CACHE_UNSUPPORTED
|
|
|
|
);
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* autoload call-back function; used to auto-load classes
|
|
|
|
*
|
|
|
|
* @param string $name the name of the class
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
static public function autoload($name){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// only auto-load our classes
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(strncmp($name, 'Net_DNS2', 8) == 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
include dirname(__DIR__) . '/' . str_replace('_', '/', $name) . '.php';
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sets the name servers to be used
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param mixed $nameservers either an array of name servers, or a file name
|
2019-12-14 10:45:22 +08:00
|
|
|
* to parse, assuming it's in the resolv.conf format
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public function setServers($nameservers){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// if it's an array, then use it directly
|
|
|
|
//
|
|
|
|
// otherwise, see if it's a path to a resolv.conf file and if so, load it
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(is_array($nameservers)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->nameservers = $nameservers;
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// temporary list of name servers; do it this way rather than just
|
|
|
|
// resetting the local nameservers value, just incase an exception
|
|
|
|
// is thrown here; this way we might avoid ending up with an empty
|
|
|
|
// namservers list.
|
|
|
|
//
|
|
|
|
$ns = array();
|
|
|
|
|
|
|
|
//
|
|
|
|
// check to see if the file is readable
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(is_readable($nameservers) === true){
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
$data = file_get_contents($nameservers);
|
2019-12-14 19:27:05 +08:00
|
|
|
if($data === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'failed to read contents of file: ' . $nameservers,
|
|
|
|
Net_DNS2_Lookups::E_NS_INVALID_FILE
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$lines = explode("\n", $data);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
foreach($lines as $line){
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
$line = trim($line);
|
|
|
|
|
|
|
|
//
|
|
|
|
// ignore empty lines, and lines that are commented out
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((strlen($line) == 0)
|
|
|
|
|| ($line[0] == '#')
|
2019-12-14 10:45:22 +08:00
|
|
|
|| ($line[0] == ';')
|
2019-12-14 19:27:05 +08:00
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// ignore lines with no spaces in them.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(strpos($line, ' ') === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($key, $value) = preg_split('/\s+/', $line, 2);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
$key = trim(strtolower($key));
|
|
|
|
$value = trim(strtolower($value));
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
switch($key){
|
|
|
|
case 'nameserver':
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// nameserver can be a IPv4 or IPv6 address
|
|
|
|
//
|
|
|
|
if((self::isIPv4($value) == true)
|
|
|
|
|| (self::isIPv6($value) == true)
|
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
$ns[] = $value;
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'invalid nameserver entry: ' . $value,
|
|
|
|
Net_DNS2_Lookups::E_NS_INVALID_ENTRY
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
case 'domain':
|
|
|
|
$this->domain = $value;
|
|
|
|
break;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
case 'search':
|
|
|
|
$this->search_list = preg_split('/\s+/', $value);
|
|
|
|
break;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
case 'options':
|
|
|
|
$this->parseOptions($value);
|
|
|
|
break;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
default:
|
|
|
|
;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if we don't have a domain, but we have a search list, then
|
|
|
|
// take the first entry on the search list as the domain
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((strlen($this->domain) == 0)
|
|
|
|
&& (count($this->search_list) > 0)
|
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
$this->domain = $this->search_list[0];
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'resolver file file provided is not readable: ' . $nameservers,
|
|
|
|
Net_DNS2_Lookups::E_NS_INVALID_FILE
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// store the name servers locally
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(count($ns) > 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
$this->nameservers = $ns;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// remove any duplicates; not sure if we should bother with this- if people
|
|
|
|
// put duplicate name servers, who I am to stop them?
|
|
|
|
//
|
|
|
|
$this->nameservers = array_unique($this->nameservers);
|
|
|
|
|
|
|
|
//
|
|
|
|
// check the name servers
|
|
|
|
//
|
|
|
|
$this->checkServers();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parses the options line from a resolv.conf file; we don't support all the options
|
|
|
|
* yet, and using them is optional.
|
|
|
|
*
|
|
|
|
* @param string $value is the options string from the resolv.conf file.
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
* @access private
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
private function parseOptions($value){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// if overrides are disabled (the default), or the options list is empty for some
|
|
|
|
// reason, then we don't need to do any of this work.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($this->use_resolv_options == false) || (strlen($value) == 0)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$options = preg_split('/\s+/', strtolower($value));
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
foreach($options as $option){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// override the timeout value from the resolv.conf file.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((strncmp($option, 'timeout', 7) == 0) && (strpos($option, ':') !== false)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
list($key, $val) = explode(':', $option);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($val > 0) && ($val <= 30)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->timeout = $val;
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// the rotate option just enabled the ns_random option
|
|
|
|
//
|
|
|
|
}else if(strncmp($option, 'rotate', 6) == 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->ns_random = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2019-12-14 19:27:05 +08:00
|
|
|
}
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* checks the list of name servers to make sure they're set
|
|
|
|
*
|
|
|
|
* @param mixed $default a path to a resolv.conf file or an array of servers.
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access protected
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
protected function checkServers($default = null){
|
|
|
|
if(empty($this->nameservers)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if(isset($default)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->setServers($default);
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
throw new Net_DNS2_Exception(
|
2019-12-14 19:27:05 +08:00
|
|
|
'empty name servers list; you must provide a list of name ' .
|
2019-12-14 10:45:22 +08:00
|
|
|
'servers, or the path to a resolv.conf file.',
|
|
|
|
Net_DNS2_Lookups::E_NS_INVALID_ENTRY
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* adds a TSIG RR object for authentication
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param string $keyname the key name to use for the TSIG RR
|
2019-12-14 10:45:22 +08:00
|
|
|
* @param string $signature the key to sign the request.
|
|
|
|
* @param string $algorithm the algorithm to use
|
2019-12-14 19:27:05 +08:00
|
|
|
*
|
2019-12-14 10:45:22 +08:00
|
|
|
* @return boolean
|
|
|
|
* @access public
|
|
|
|
* @since function available since release 1.1.0
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function signTSIG(
|
|
|
|
$keyname, $signature = '', $algorithm = Net_DNS2_RR_TSIG::HMAC_MD5
|
2019-12-14 19:27:05 +08:00
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// if the TSIG was pre-created and passed in, then we can just used
|
|
|
|
// it as provided.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($keyname instanceof Net_DNS2_RR_TSIG){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->auth_signature = $keyname;
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// otherwise create the TSIG RR, but don't add it just yet; TSIG needs
|
|
|
|
// to be added as the last additional entry- so we'll add it just
|
|
|
|
// before we send.
|
|
|
|
//
|
|
|
|
$this->auth_signature = Net_DNS2_RR::fromString(
|
|
|
|
strtolower(trim($keyname)) .
|
2019-12-14 19:27:05 +08:00
|
|
|
' TSIG ' . $signature
|
2019-12-14 10:45:22 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
//
|
|
|
|
// set the algorithm to use
|
|
|
|
//
|
|
|
|
$this->auth_signature->algorithm = $algorithm;
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* adds a SIG RR object for authentication
|
|
|
|
*
|
|
|
|
* @param string $filename the name of a file to load the signature from.
|
2019-12-14 19:27:05 +08:00
|
|
|
*
|
2019-12-14 10:45:22 +08:00
|
|
|
* @return boolean
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access public
|
|
|
|
* @since function available since release 1.1.0
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public function signSIG0($filename){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// check for OpenSSL
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(extension_loaded('openssl') === false){
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'the OpenSSL extension is required to use SIG(0).',
|
|
|
|
Net_DNS2_Lookups::E_OPENSSL_UNAVAIL
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if the SIG was pre-created, then use it as-is
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($filename instanceof Net_DNS2_RR_SIG){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->auth_signature = $filename;
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// otherwise, it's filename which needs to be parsed and processed.
|
|
|
|
//
|
|
|
|
$private = new Net_DNS2_PrivateKey($filename);
|
|
|
|
|
|
|
|
//
|
|
|
|
// create a new Net_DNS2_RR_SIG object
|
|
|
|
//
|
|
|
|
$this->auth_signature = new Net_DNS2_RR_SIG();
|
|
|
|
|
|
|
|
//
|
|
|
|
// reset some values
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->auth_signature->name = $private->signname;
|
|
|
|
$this->auth_signature->ttl = 0;
|
|
|
|
$this->auth_signature->class = 'ANY';
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// these values are pulled from the private key
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->auth_signature->algorithm = $private->algorithm;
|
|
|
|
$this->auth_signature->keytag = $private->keytag;
|
|
|
|
$this->auth_signature->signname = $private->signname;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// these values are hard-coded for SIG0
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->auth_signature->typecovered = 'SIG0';
|
|
|
|
$this->auth_signature->labels = 0;
|
|
|
|
$this->auth_signature->origttl = 0;
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// generate the dates
|
|
|
|
//
|
|
|
|
$t = time();
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->auth_signature->sigincep = gmdate('YmdHis', $t);
|
|
|
|
$this->auth_signature->sigexp = gmdate('YmdHis', $t + 500);
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// store the private key in the SIG object for later.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
$this->auth_signature->private_key = $private;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// only RSA algorithms are supported for SIG(0)
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
switch($this->auth_signature->algorithm){
|
|
|
|
case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:
|
|
|
|
case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:
|
|
|
|
case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256:
|
|
|
|
case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512:
|
|
|
|
case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSA:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'only asymmetric algorithms work with SIG(0)!',
|
|
|
|
Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
|
|
|
|
);
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* a simple function to determine if the RR type is cacheable
|
|
|
|
*
|
|
|
|
* @param stream $_type the RR type string
|
|
|
|
*
|
|
|
|
* @return bool returns true/false if the RR type if cachable
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public function cacheable($_type){
|
|
|
|
switch($_type){
|
|
|
|
case 'AXFR':
|
|
|
|
case 'OPT':
|
|
|
|
return false;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
return true;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* PHP doesn't support unsigned integers, but many of the RR's return
|
|
|
|
* unsigned values (like SOA), so there is the possibility that the
|
2019-12-14 19:27:05 +08:00
|
|
|
* value will overrun on 32bit systems, and you'll end up with a
|
2019-12-14 10:45:22 +08:00
|
|
|
* negative value.
|
|
|
|
*
|
|
|
|
* 64bit systems are not affected, as their PHP_IN_MAX value should
|
|
|
|
* be 64bit (ie 9223372036854775807)
|
|
|
|
*
|
|
|
|
* This function returns a negative integer value, as a string, with
|
|
|
|
* the correct unsigned value.
|
|
|
|
*
|
|
|
|
* @param string $_int the unsigned integer value to check
|
|
|
|
*
|
|
|
|
* @return string returns the unsigned value as a string.
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public static function expandUint32($_int){
|
|
|
|
if(($_int < 0) && (PHP_INT_MAX == 2147483647)){
|
2019-12-14 10:45:22 +08:00
|
|
|
return sprintf('%u', $_int);
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
return $_int;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns true/false if the given address is a valid IPv4 address
|
|
|
|
*
|
|
|
|
* @param string $_address the IPv4 address to check
|
|
|
|
*
|
|
|
|
* @return boolean returns true/false if the address is IPv4 address
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public static function isIPv4($_address){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// use filter_var() if it's available; it's faster than preg
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(extension_loaded('filter') == true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if(filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) == false){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// do the main check here;
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(inet_pton($_address) === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// then make sure we're not a IPv6 address
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
/**
|
|
|
|
* returns true/false if the given address is a valid IPv6 address
|
|
|
|
*
|
|
|
|
* @param string $_address the IPv6 address to check
|
|
|
|
*
|
|
|
|
* @return boolean returns true/false if the address is IPv6 address
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public static function isIPv6($_address){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// use filter_var() if it's available; it's faster than preg
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(extension_loaded('filter') == true){
|
|
|
|
if(filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) == false){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// do the main check here
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(inet_pton($_address) === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// then make sure it doesn't match a IPv4 address
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 1){
|
2019-12-14 10:45:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* formats the given IPv6 address as a fully expanded IPv6 address
|
|
|
|
*
|
|
|
|
* @param string $_address the IPv6 address to expand
|
|
|
|
*
|
|
|
|
* @return string the fully expanded IPv6 address
|
|
|
|
* @access public
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
public static function expandIPv6($_address){
|
2019-12-14 10:45:22 +08:00
|
|
|
$hex = unpack('H*hex', inet_pton($_address));
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
return substr(preg_replace('/([A-f0-9]{4})/', "$1:", $hex['hex']), 0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sends a standard Net_DNS2_Packet_Request packet
|
|
|
|
*
|
|
|
|
* @param Net_DNS2_Packet $request a Net_DNS2_Packet_Request object
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param boolean $use_tcp true/false if the function should
|
2019-12-14 10:45:22 +08:00
|
|
|
* use TCP for the request
|
|
|
|
*
|
|
|
|
* @return mixed returns a Net_DNS2_Packet_Response object, or false on error
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access protected
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
protected function sendPacket(Net_DNS2_Packet $request, $use_tcp){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// get the data from the packet
|
|
|
|
//
|
|
|
|
$data = $request->get();
|
2019-12-14 19:27:05 +08:00
|
|
|
if(strlen($data) < Net_DNS2_Lookups::DNS_HEADER_SIZE){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'invalid or empty packet for sending!',
|
|
|
|
Net_DNS2_Lookups::E_PACKET_INVALID,
|
|
|
|
null,
|
|
|
|
$request
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
reset($this->nameservers);
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// randomize the name server list if it's asked for
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->ns_random == true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
shuffle($this->nameservers);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// loop so we can handle server errors
|
|
|
|
//
|
|
|
|
$response = null;
|
|
|
|
$ns = '';
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
while(1){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// grab the next DNS server
|
|
|
|
//
|
|
|
|
$ns = current($this->nameservers);
|
|
|
|
next($this->nameservers);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if($ns === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if(is_null($this->last_exception) == false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
throw $this->last_exception;
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
throw new Net_DNS2_Exception(
|
|
|
|
'every name server provided has failed',
|
|
|
|
Net_DNS2_Lookups::E_NS_FAILED
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if the use TCP flag (force TCP) is set, or the packet is bigger than our
|
|
|
|
// max allowed UDP size- which is either 512, or if this is DNSSEC request,
|
|
|
|
// then whatever the configured dnssec_payload_size is.
|
|
|
|
//
|
|
|
|
$max_udp_size = Net_DNS2_Lookups::DNS_MAX_UDP_SIZE;
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->dnssec == true){
|
2019-12-14 10:45:22 +08:00
|
|
|
$max_udp_size = $this->dnssec_payload_size;
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($use_tcp == true) || (strlen($data) > $max_udp_size)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
try{
|
2019-12-14 10:45:22 +08:00
|
|
|
$response = $this->sendTCPRequest($ns, $data, ($request->question[0]->qtype == 'AXFR') ? true : false);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}catch(Net_DNS2_Exception $e){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->last_exception = $e;
|
|
|
|
$this->last_exception_list[$ns] = $e;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// otherwise, send it using UDP
|
|
|
|
//
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
try{
|
2019-12-14 10:45:22 +08:00
|
|
|
$response = $this->sendUDPRequest($ns, $data);
|
|
|
|
|
|
|
|
//
|
|
|
|
// check the packet header for a trucated bit; if it was truncated,
|
|
|
|
// then re-send the request as TCP.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($response->header->tc == 1){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$response = $this->sendTCPRequest($ns, $data);
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}catch(Net_DNS2_Exception $e){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->last_exception = $e;
|
|
|
|
$this->last_exception_list[$ns] = $e;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// make sure header id's match between the request and response
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($request->header->id != $response->header->id){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->last_exception = new Net_DNS2_Exception(
|
|
|
|
|
|
|
|
'invalid header: the request and response id do not match.',
|
|
|
|
Net_DNS2_Lookups::E_HEADER_INVALID,
|
|
|
|
null,
|
|
|
|
$request,
|
|
|
|
$response
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->last_exception_list[$ns] = $this->last_exception;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// make sure the response is actually a response
|
|
|
|
//
|
|
|
|
// 0 = query, 1 = response
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($response->header->qr != Net_DNS2_Lookups::QR_RESPONSE){
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
$this->last_exception = new Net_DNS2_Exception(
|
|
|
|
|
|
|
|
'invalid header: the response provided is not a response packet.',
|
|
|
|
Net_DNS2_Lookups::E_HEADER_INVALID,
|
|
|
|
null,
|
|
|
|
$request,
|
|
|
|
$response
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->last_exception_list[$ns] = $this->last_exception;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// make sure the response code in the header is ok
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR){
|
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
$this->last_exception = new Net_DNS2_Exception(
|
2019-12-14 19:27:05 +08:00
|
|
|
|
|
|
|
'DNS request failed: ' .
|
2019-12-14 10:45:22 +08:00
|
|
|
Net_DNS2_Lookups::$result_code_messages[$response->header->rcode],
|
|
|
|
$response->header->rcode,
|
|
|
|
null,
|
|
|
|
$request,
|
|
|
|
$response
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->last_exception_list[$ns] = $this->last_exception;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* cleans up a failed socket and throws the given exception
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param string $_proto the protocol of the socket
|
|
|
|
* @param string $_ns the name server to use for the request
|
|
|
|
* @param string $_error the error message to throw at the end of the function
|
2019-12-14 10:45:22 +08:00
|
|
|
*
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access private
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
private function generateError($_proto, $_ns, $_error){
|
|
|
|
if(isset($this->sock[$_proto][$_ns]) == false){
|
2019-12-14 10:45:22 +08:00
|
|
|
throw new Net_DNS2_Exception('invalid socket referenced', Net_DNS2_Lookups::E_NS_INVALID_SOCKET);
|
|
|
|
}
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// grab the last error message off the socket
|
|
|
|
//
|
|
|
|
$last_error = $this->sock[$_proto][$_ns]->last_error;
|
2019-12-14 19:27:05 +08:00
|
|
|
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// close it
|
|
|
|
//
|
|
|
|
$this->sock[$_proto][$_ns]->close();
|
|
|
|
|
|
|
|
//
|
|
|
|
// remove it from the socket cache
|
|
|
|
//
|
|
|
|
unset($this->sock[$_proto][$_ns]);
|
|
|
|
|
|
|
|
//
|
|
|
|
// throw the error provided
|
|
|
|
//
|
|
|
|
throw new Net_DNS2_Exception($last_error, $_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sends a DNS request using TCP
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param string $_ns the name server to use for the request
|
|
|
|
* @param string $_data the raw DNS packet data
|
2019-12-14 10:45:22 +08:00
|
|
|
* @param boolean $_axfr if this is a zone transfer request
|
|
|
|
*
|
|
|
|
* @return Net_DNS2_Packet_Response the reponse object
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access private
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
private function sendTCPRequest($_ns, $_data, $_axfr = false){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// grab the start time
|
|
|
|
//
|
|
|
|
$start_time = microtime(true);
|
|
|
|
|
|
|
|
//
|
|
|
|
// see if we already have an open socket from a previous request; if so, try to use
|
|
|
|
// that instead of opening a new one.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((!isset($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]))
|
2019-12-14 10:45:22 +08:00
|
|
|
|| (!($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] instanceof Net_DNS2_Socket))
|
2019-12-14 19:27:05 +08:00
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// if the socket library is available, then use that
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sockets_enabled === true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Sockets(
|
|
|
|
Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout
|
|
|
|
);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// otherwise the streams library
|
|
|
|
//
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns] = new Net_DNS2_Socket_Streams(
|
|
|
|
Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if a local IP address / port is set, then add it
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(strlen($this->local_host) > 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->bindAddress(
|
|
|
|
$this->local_host, $this->local_port
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// open the socket
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->open() === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// write the data to the socket; if it fails, continue on
|
|
|
|
// the while loop
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sock[Net_DNS2_Socket::SOCK_STREAM][$_ns]->write($_data) === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// read the content, using select to wait for a response
|
|
|
|
//
|
|
|
|
$size = 0;
|
|
|
|
$result = null;
|
|
|
|
$response = null;
|
|
|
|
|
|
|
|
//
|
|
|
|
// handle zone transfer requests differently than other requests.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($_axfr == true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$soa_count = 0;
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
while(1){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// read the data off the socket
|
|
|
|
//
|
|
|
|
$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);
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// if we get an error, then keeping this socket around for a future request, could cause
|
|
|
|
// an error- for example, https://github.com/mikepultz/netdns2/issues/61
|
|
|
|
//
|
|
|
|
// in this case, the connection was timing out, which once it did finally respond, left
|
|
|
|
// data on the socket, which could be captured on a subsequent request.
|
|
|
|
//
|
|
|
|
// since there's no way to "reset" a socket, the only thing we can do it close it.
|
|
|
|
//
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// parse the first chunk as a packet
|
|
|
|
//
|
|
|
|
$chunk = new Net_DNS2_Packet_Response($result, $size);
|
|
|
|
|
|
|
|
//
|
|
|
|
// if this is the first packet, then clone it directly, then
|
|
|
|
// go through it to see if there are two SOA records
|
|
|
|
// (indicating that it's the only packet)
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(is_null($response) == true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$response = clone $chunk;
|
|
|
|
|
|
|
|
//
|
|
|
|
// look for a failed response; if the zone transfer
|
|
|
|
// failed, then we don't need to do anything else at this
|
|
|
|
// point, and we should just break out.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR){
|
2019-12-14 10:45:22 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// go through each answer
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
foreach($response->answer as $index => $rr){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// count the SOA records
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($rr->type == 'SOA'){
|
2019-12-14 10:45:22 +08:00
|
|
|
$soa_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if we have 2 or more SOA records, then we're done;
|
|
|
|
// otherwise continue out so we read the rest of the
|
|
|
|
// packets off the socket
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($soa_count >= 2){
|
2019-12-14 10:45:22 +08:00
|
|
|
break;
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// go through all these answers, and look for SOA records
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
foreach($chunk->answer as $index => $rr){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// count the number of SOA records we find
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($rr->type == 'SOA'){
|
|
|
|
$soa_count++;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// add the records to a single response object
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
$response->answer[] = $rr;
|
2019-12-14 10:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if we've found the second SOA record, we're done
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($soa_count >= 2){
|
2019-12-14 10:45:22 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// everything other than a AXFR
|
|
|
|
//
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$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);
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_STREAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// create the packet object
|
|
|
|
//
|
|
|
|
$response = new Net_DNS2_Packet_Response($result, $size);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// store the query time
|
|
|
|
//
|
|
|
|
$response->response_time = microtime(true) - $start_time;
|
|
|
|
|
|
|
|
//
|
|
|
|
// add the name server that the response came from to the response object,
|
|
|
|
// and the socket type that was used.
|
|
|
|
//
|
|
|
|
$response->answer_from = $_ns;
|
|
|
|
$response->answer_socket_type = Net_DNS2_Socket::SOCK_STREAM;
|
|
|
|
|
|
|
|
//
|
|
|
|
// return the Net_DNS2_Packet_Response object
|
|
|
|
//
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sends a DNS request using UDP
|
|
|
|
*
|
2019-12-14 19:27:05 +08:00
|
|
|
* @param string $_ns the name server to use for the request
|
|
|
|
* @param string $_data the raw DNS packet data
|
2019-12-14 10:45:22 +08:00
|
|
|
*
|
|
|
|
* @return Net_DNS2_Packet_Response the reponse object
|
|
|
|
* @throws Net_DNS2_Exception
|
|
|
|
* @access private
|
|
|
|
*
|
|
|
|
*/
|
2019-12-14 19:27:05 +08:00
|
|
|
private function sendUDPRequest($_ns, $_data){
|
2019-12-14 10:45:22 +08:00
|
|
|
//
|
|
|
|
// grab the start time
|
|
|
|
//
|
|
|
|
$start_time = microtime(true);
|
|
|
|
|
|
|
|
//
|
|
|
|
// see if we already have an open socket from a previous request; if so, try to use
|
|
|
|
// that instead of opening a new one.
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if((!isset($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]))
|
2019-12-14 10:45:22 +08:00
|
|
|
|| (!($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] instanceof Net_DNS2_Socket))
|
2019-12-14 19:27:05 +08:00
|
|
|
){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// if the socket library is available, then use that
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sockets_enabled === true){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Sockets(
|
|
|
|
Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout
|
|
|
|
);
|
|
|
|
|
2019-12-14 19:27:05 +08:00
|
|
|
//
|
|
|
|
// otherwise the streams library
|
|
|
|
//
|
|
|
|
}else{
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns] = new Net_DNS2_Socket_Streams(
|
|
|
|
Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// if a local IP address / port is set, then add it
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if(strlen($this->local_host) > 0){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->bindAddress(
|
|
|
|
$this->local_host, $this->local_port
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// open the socket
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->open() === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// write the data to the socket
|
|
|
|
//
|
2019-12-14 19:27:05 +08:00
|
|
|
if($this->sock[Net_DNS2_Socket::SOCK_DGRAM][$_ns]->write($_data) === false){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// read the content, using select to wait for a response
|
|
|
|
//
|
|
|
|
$size = 0;
|
|
|
|
|
|
|
|
$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);
|
2019-12-14 19:27:05 +08:00
|
|
|
if(($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)){
|
2019-12-14 10:45:22 +08:00
|
|
|
|
|
|
|
$this->generateError(Net_DNS2_Socket::SOCK_DGRAM, $_ns, Net_DNS2_Lookups::E_NS_SOCKET_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// create the packet object
|
|
|
|
//
|
|
|
|
$response = new Net_DNS2_Packet_Response($result, $size);
|
|
|
|
|
|
|
|
//
|
|
|
|
// store the query time
|
|
|
|
//
|
|
|
|
$response->response_time = microtime(true) - $start_time;
|
|
|
|
|
|
|
|
//
|
|
|
|
// add the name server that the response came from to the response object,
|
|
|
|
// and the socket type that was used.
|
|
|
|
//
|
|
|
|
$response->answer_from = $_ns;
|
|
|
|
$response->answer_socket_type = Net_DNS2_Socket::SOCK_DGRAM;
|
|
|
|
|
|
|
|
//
|
|
|
|
// return the Net_DNS2_Packet_Response object
|
|
|
|
//
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Local variables:
|
|
|
|
* tab-width: 4
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* c-hanging-comment-ender-p: nil
|
|
|
|
* End:
|
|
|
|
*/
|
|
|
|
?>
|