<?php
/*
Whois.php        PHP classes to conduct whois queries

Copyright (C)1999,2005 easyDNS Technologies Inc. & Mark Jeftovic

Maintained by David Saez

For the most recent version of this package visit:

http://www.phpwhois.org

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

require_once('whois.ip.lib.php');

class WhoisClient {
	
	// Recursion allowed ?
	var $gtld_recurse = false;

	// Default WHOIS port
	var $PORT = 43;

	// Maximum number of retries on connection failure
	var $RETRY = 0;

	// Time to wait between retries
	var $SLEEP = 2;

	// Read buffer size (0 == char by char)
	var $BUFFER = 1024;
	
	// Communications timeout
	var $STIMEOUT = 10;

	// List of servers and handlers (loaded from servers.whois)
	var $DATA = array(); 	
	
	// Array to contain all query variables
	var $Query = array(
		'tld' => '',
		'type' => 'domain',
		'query' => '',
		'status',
		'server'
		);

	// This release of the package
	var $CODE_VERSION = '4.2.2';
	
	// Full code and data version string (e.g. 'Whois2.php v3.01:16')
	var $VERSION;
	
	/*
	 * Constructor function
	 */
	function __construct () {
		// Load DATA array
		@require('whois.servers.php');		

		// Set version
		$this->VERSION = sprintf("phpWhois v%s-%s", $this->CODE_VERSION, $this->DATA_VERSION);
	}
		
	/*
	 * Perform lookup
	 */

	function GetRawData ($query) {
		
		$this->Query['query'] = $query;
		
		// clear error description
		if (isset($this->Query['errstr'])) unset($this->Query['errstr']);
		
		if (!isset($this->Query['server'])) {
			$this->Query['status'] = 'error';
			$this->Query['errstr'][] = 'No server specified';
			return(array());
			}

		// Check if protocol is http
		
		if (substr($this->Query['server'],0,7)=='http://' ||
			substr($this->Query['server'],0,8)=='https://')
			{
			$output = $this->httpQuery($this->Query['server']);
			
			if (!$output)
				{
				$this->Query['status'] = 'error';
				$this->Query['errstr'][] = 'Connect failed to: '.$this->Query['server'];
				return(array());
				}
				
			$this->Query['args'] = substr(strchr($this->Query['server'],'?'),1);
			$this->Query['server'] = strtok($this->Query['server'],'?');
			
			if (substr($this->Query['server'],0,7)=='http://')
				$this->Query['server_port'] = 80;
			else
				$this->Query['server_port'] = 483;
			}
		else
			{
			// Get args
			
			if (strpos($this->Query['server'],'?'))
				{
				$parts = explode('?',$this->Query['server']);
				$this->Query['server'] = trim($parts[0]);
				$query_args = trim($parts[1]);
				
				// replace substitution parameters			
				$query_args = str_replace('{query}', $query, $query_args);
				$query_args = str_replace('{version}', 'phpWhois'.$this->CODE_VERSION, $query_args);
				
				if (strpos($query_args,'{ip}')!==false)
					{
					$query_args = str_replace('{ip}', phpwhois_getclientip(), $query_args);
					}
					
				if (strpos($query_args,'{hname}')!==false)
					{
					$query_args = str_replace('{hname}', gethostbyaddr(phpwhois_getclientip()), $query_args);
					}
				}
			else
				{
				if (empty($this->Query['args']))
					$query_args = $query;
				else
					$query_args = $this->Query['args'];
				}

			$this->Query['args'] = $query_args;

			if (substr($this->Query['server'],0,9) == 'rwhois://')
				{
				$this->Query['server'] = substr($this->Query['server'],9);
				}

			if (substr($this->Query['server'],0,8) == 'whois://')
				{
				$this->Query['server'] = substr($this->Query['server'],8);
				}
			
			// Get port
			
			if (strpos($this->Query['server'],':'))
				{
				$parts = explode(':',$this->Query['server']);
				$this->Query['server'] = trim($parts[0]);
				$this->Query['server_port'] = trim($parts[1]);
				}
			else			
				$this->Query['server_port'] = $this->PORT;
				
			// Connect to whois server, or return if failed

			$ptr = $this->Connect();

			if($ptr < 0) {
				$this->Query['status'] = 'error';
				$this->Query['errstr'][] = 'Connect failed to: '.$this->Query['server'];
				return array();
				}

			stream_set_timeout($ptr,$this->STIMEOUT);
			stream_set_blocking($ptr,0);
			
			// Send query
			fputs($ptr, trim($query_args)."\r\n");
			
			// Prepare to receive result
			$raw = '';
			$start = time();
			$null = NULL;
			$r = array($ptr);

			while (!feof($ptr))
				{
				if (@stream_select($r,$null,$null,$this->STIMEOUT)!==false)
					{
					$raw .= fgets($ptr, $this->BUFFER);
					}

				if (time()-$start > $this->STIMEOUT)
					{
					$this->Query['status'] = 'error';
					$this->Query['errstr'][] = 'Timeout reading from '.$this->Query['server'];
					return array();
					}
				}

			if (array_key_exists($this->Query['server'],$this->NON_UTF8))
				{
				$raw = utf8_encode($raw);
				}

			$output = explode("\n", $raw);

			// Drop empty last line (if it's empty! - saleck)
			if (empty($output[count($output)-1]))
				unset($output[count($output)-1]);
			}
		
		return $output;	
	}

	/*
	 * Perform lookup. Returns an array. The 'rawdata' element contains an
	 * array of lines gathered from the whois query. If a top level domain
	 * handler class was found for the domain, other elements will have been
	 * populated too.
	 */

	function GetData ($query='', $deep_whois=true) {
	
		// If domain to query passed in, use it, otherwise use domain from initialisation
		$query = !empty($query) ? $query : $this->Query['query'];
				
		$output = $this->GetRawData($query);
						
		// Create result and set 'rawdata'
		$result = array( 'rawdata' => $output );		
		$result = $this->set_whois_info($result);

		// Return now on error
		if (empty($output)) return $result;
		
		// If we have a handler, post-process it with it
		if (isSet($this->Query['handler']))
			{
			// Keep server list
			$servers = $result['regyinfo']['servers'];
			unset($result['regyinfo']['servers']);
			
			// Process data
			$result = $this->Process($result,$deep_whois);
		
			// Add new servers to the server list
			if (isset($result['regyinfo']['servers']))
				$result['regyinfo']['servers'] = array_merge($servers,$result['regyinfo']['servers']);
			else
				$result['regyinfo']['servers'] = $servers;
			
			// Handler may forget to set rawdata
			if (!isset($result['rawdata']))
				$result['rawdata'] = $output;
			}

		// Type defaults to domain
		if (!isset($result['regyinfo']['type']))
			$result['regyinfo']['type'] = 'domain';	
		
		// Add error information if any
		if (isset($this->Query['errstr']))
			$result['errstr'] = $this->Query['errstr'];

		// Fix/add nameserver information
		if (method_exists($this,'FixResult') && $this->Query['tld'] != 'ip')
			$this->FixResult($result,$query);
			
		return($result);
	}
	
	/*
	*   Adds whois server query information to result
	*/
	
	function set_whois_info ($result)
		{
		$info = array(
					'server'=> $this->Query['server'],
					);

		if (!empty($this->Query['args']))
			$info['args'] = $this->Query['args'];
		else
			$info['args'] = $this->Query['query'];
		
		if (!empty($this->Query['server_port']))
			$info['port'] = $this->Query['server_port'];
		else
			$info['port'] = 43;
			
		if (isset($result['regyinfo']['whois']))
			unset($result['regyinfo']['whois']);
		
		if (isset($result['regyinfo']['rwhois']))
			unset($result['regyinfo']['rwhois']);
			
		$result['regyinfo']['servers'][] = $info;
		
		return $result;
		}

	/*
	*   Convert html output to plain text
	*/
	function httpQuery ($query) {
		
		//echo ini_get('allow_url_fopen');
		
		//if (ini_get('allow_url_fopen'))
			$lines = @file($this->Query['server']);		
			
		if (!$lines) return false;
		
		$output = '';
		$pre = '';

		foreach ($lines as $key => $val) {
			$val = trim($val);

			$pos=strpos(strtoupper($val),'<PRE>');
			if ($pos!==false) {
				$pre = "\n";
				$output.=substr($val,0,$pos)."\n";
				$val = substr($val,$pos+5);
				}
			$pos=strpos(strtoupper($val),'</PRE>');
			if ($pos!==false) {
				$pre = '';
				$output.=substr($val,0,$pos)."\n";
				$val = substr($val,$pos+6);
				}
			$output.=$val.$pre;
			}
			
		$search = array (
				'<BR>', '<P>', '</TITLE>',
				'</H1>', '</H2>', '</H3>',
				'<br>', '<p>', '</title>',
				'</h1>', '</h2>', '</h3>'  );

		$output = str_replace($search,"\n",$output);
		$output = str_replace('<TD',' <td',$output);
		$output = str_replace('<td',' <td',$output);
		$output = str_replace('<tr',"\n<tr",$output);
		$output = str_replace('<TR',"\n<tr",$output);
		$output = str_replace('&nbsp;',' ',$output);
		$output = strip_tags($output);		
		$output = explode("\n",$output);

		$rawdata = array();
		$null = 0;

		foreach ($ouput as $key => $val) {
			$val=trim($val);
			if ($val=='') {
				if (++$null>2) continue;
			}
			else $null=0;
			$rawdata[]=$val;
		}
		return $rawdata;
	}
	
	/*
	 * Open a socket to the whois server.
	 *
	 * Returns a socket connection pointer on success, or -1 on failure.
	 */
	function Connect ($server = '') {
	
		if ($server == '')
			$server = $this->Query['server'];
			
		// Fail if server not set
		if($server == '')
			return(-1);

		// Get rid of protocol and/or get port
		$port = $this->Query['server_port'];
		
		$pos = strpos($server,'://');
		
		if ($pos !== false)
			$server = substr($server, $pos+3);
			
		$pos = strpos($server,':');
		
		if ($pos !== false)
			{
			$port = substr($server,$pos+1);
			$server = substr($server,0,$pos);			
			}
			
		// Enter connection attempt loop
		$retry = 0;
		
		while($retry <= $this->RETRY) {
			// Set query status
			$this->Query['status'] = 'ready';

			// Connect to whois port
			$ptr = @fsockopen($server, $port, $errno, $errstr, $this->STIMEOUT);
			
			if($ptr > 0) {
				$this->Query['status'] = 'ok';
				return($ptr);
			}
			
			// Failed this attempt
			$this->Query['status'] = 'error';
			$this->Query['error'][] = $errstr;
			$retry++;

			// Sleep before retrying
			sleep($this->SLEEP);
		}
		
		// If we get this far, it hasn't worked
		return(-1);
	} 	
	
	/*
	 * Post-process result with handler class. On success, returns the result
	 * from the handler. On failure, returns passed result unaltered.
	 */
	function Process (&$result, $deep_whois=true) {

		$handler_name = str_replace('.','_',$this->Query['handler']);

		// If the handler has not already been included somehow, include it now
		$HANDLER_FLAG = sprintf("__%s_HANDLER__", strtoupper($handler_name));

		if (!defined($HANDLER_FLAG))
			include($this->Query['file']);

		// If the handler has still not been included, append to query errors list and return
		if (!defined($HANDLER_FLAG))
			{
			$this->Query['errstr'][] = "Can't find $handler_name handler: ".$this->Query['file'];
			return($result);
			}

		if (!$this->gtld_recurse && $this->Query['file'] == 'whois.gtld.php')
			return $result;

		// Pass result to handler
		$object = $handler_name.'_handler';
		
		$handler = new $object('');

		// If handler returned an error, append it to the query errors list
		if(isSet($handler->Query['errstr']))
			$this->Query['errstr'][] = $handler->Query['errstr'];

		$handler->deep_whois = $deep_whois;

		// Process
		$res = $handler->parse($result,$this->Query['query']);

		// Return the result
		return $res;
	}	
	
	/*
	 * Does more (deeper) whois ...
	 */
	 
	function DeepWhois ($query, $result) {
	
		if (!isset($result['regyinfo']['whois'])) return $result;
		
		$this->Query['server'] = $wserver = $result['regyinfo']['whois'];
		unset($result['regyinfo']['whois']);
		$subresult = $this->GetRawData($query);

		if (!empty($subresult))
			{
			$result = $this->set_whois_info($result);
			$result['rawdata'] = $subresult;
		
			if (isset($this->WHOIS_GTLD_HANDLER[$wserver]))
				$this->Query['handler'] = $this->WHOIS_GTLD_HANDLER[$wserver];
			else
				{
				$parts = explode('.',$wserver);
				$hname = strtolower($parts[1]);

				if (($fp = @fopen('whois.gtld.'.$hname.'.php', 'r', 1)) and fclose($fp))
					$this->Query['handler'] = $hname;
				}
				
			if (!empty($this->Query['handler']))
				{			
				$this->Query['file'] = sprintf('whois.gtld.%s.php', $this->Query['handler']);
				$regrinfo = $this->Process($subresult); //$result['rawdata']);
				$result['regrinfo'] = $this->merge_results($result['regrinfo'], $regrinfo);
				//$result['rawdata'] = $subresult;
				}
			}
				
		return $result;
	}
	
	/*
	 *  Merge results
	 */
	 
	function merge_results($a1, $a2) {

		reset($a2);
	
		foreach ($a2 as $key => $val)
			{
			if (isset($a1[$key]))
				{
				if (is_array($val))
					{
					if ($key != 'nserver')
						$a1[$key] = $this->merge_results($a1[$key], $val);
					}
				else
					{
					$val = trim($val);
					if ($val != '')
						$a1[$key] = $val;
					}
				}
			else
				$a1[$key] = $val;
			}
	
		return $a1;
	}
	
	function FixNameServer($nserver)
		{
		$dns = array();

		foreach($nserver as $val)
			{
			$val = str_replace( array('[',']','(',')'), '', trim($val));
			$val = str_replace("\t", ' ', $val);
			$parts = explode(' ', $val);
			$host = '';
			$ip = '';

			foreach($parts as $p)
				{
				if (substr($p,-1) == '.') $p = substr($p,0,-1);

				if ((ip2long($p) == - 1) or (ip2long($p) === false))
					{
					// Hostname ?
					if ($host == '' && preg_match('/^[\w\-]+(\.[\w\-]+)+$/',$p))
						{
						$host = $p;
						}
					}
				else
					// IP Address
					$ip = $p;
				}

			// Valid host name ?

			if ($host == '') continue;

			// Get ip address

			if ($ip == '')
				{
				$ip = gethostbyname($host);
				if ($ip == $host) $ip = '(DOES NOT EXIST)';
				}

			if (substr($host,-1,1) == '.') $host = substr($host,0,-1);
				
			$dns[strtolower($host)] = $ip;
			}

		return $dns;
		}
}
