<?php
setlocale(LC_ALL, 'nld_nld');
/*
 * (c) 2012 - Jan Reilink - jan@saotn.nl
 * 
 * PHP class for quering IP adresses agains the Project Honey Pot and 
 * Stop Forum Spam databases.
 * 
 * - An Access Key is mandatory!
 * http://www.projecthoneypot.org/create_account.php
 * - Project Honey Pot appreciates a donation
 * http://www.projecthoneypot.org/donate.php
 * API information
 * http://www.projecthoneypot.org/httpbl_api.php
 * 
 * About Project Honey Pot (PHP)
 * http://www.projecthoneypot.org/about_us.php
 * 
 * The same goes for Stop Forum Spam (SFS)
 * http://www.stopforumspam.com/usage
 * http://www.stopforumspam.com/donate
 * http://www.stopforumspam.com/keys
 * 
 * 
 * Files used:
 * database\config.ini : with configuration settings
 * database\bl_log.txt : to log blocked requests
 * database\blacklist.txt : the blacklist with which ISAPI Rewrite (or
 *   mod_rewrite) RewiteMap directive works
 * 
 * www\httpbl.class.php : the PHP class which does all the magic
 * www\web.config : IIS 7.0 / 7.5 configuration file for use with IIS 
 *   IP Address and Domain Restrictions. Needs to be writable
 * www\.htaccess : configuration file for IIS ISAPI Rewrite or Apache 
 *   mod_rewrite. Needs to be writable
 * 
 * Example usage:
 * <?php
 * require_once("httpbl.class.php");
 * $mybl = new httpBL();
 * if($mybl->_retrieve_IP_address_status_SFP(
 *   $mybl->_retrieve_remote_IP_address()))
 * {
 *		$output = "<html><head><title>Unauthorised access</title></head>";
 *		$output .= "<body><h1>Unauthorised access</h1><h2>Access from"; 
 * 		$output .= "your IP address to this website is prohibited!</h2>";
 * 		$output .= "<p><span>Contact the webmaster if you believe this";
 * 		$output .= " is an error.</span></p></body></html>";
 * 		header("HTTP/1.0 403 Forbidden");
 * 		die($output);
 * 	}
 * ?>
 */

Class httpBL
{
	// CHANGE THE PATH ON THE LINE BELOW!
	public $configfile = "/path/to/your/config.ini";
	public $ini_array;
	
	public function _read_config_file()
	{
		return $this->ini_array = parse_ini_file($this->configfile, true);
	}

	public function _check_required_params()
	{
		$this->_read_config_file();
		
		if(
			($this->ini_array["PHPaccesskey"] != "") &&
			($this->ini_array["SFSaccesskey"] != "") &&
			($this->ini_array["blfileloc"] != "")
		  )
		{
			return TRUE;
		}
		else
		{
			trigger_error("not all required variables are filled out.");
			exit;
		}
	}
	
	public function _retrieve_remote_IP_address()
	{
		$ip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? 
		  $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']);
		return $ip;
	}

	/**
	 * Only for public IP v4 address space
	 * http://www.php.net/manual/en/filter.filters.validate.php
	 **/
	public function validate_IP_address($ip)
	{
		if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE) !== FALSE)
		{
			// IPV4 === TRUE
			return TRUE;
			exit;
		}
		else
		{
			return FALSE;
			exit;
		}
	}

	/**
	 * Note that the IP address being queried should be sent in the reversed 
	 * octet format. In other words, "127.1.1.7" should become "7.1.1.127" 
	 * for all DNS queries. For more detailed information, please see the 
	 * http:BL API (http://www.projecthoneypot.org/httpbl_api.php).
	 **/
	public function _reverse_octet_format($ip)
	{
		// return preg_replace("/(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})$/",'${4}.${3}.${2}.${1}',$ip);
		return implode(".", array_reverse(explode ('.', $ip)));
	}

	/**
	 * Look up IP address using DNS to retrieve its status with
	 * Project Honey Pot (PHP)
	 * We can also validate IP addresses against different blacklists:
	 *   dns_get_record($revip.".cbl.abuseat.org", DNS_A); or
	 *   zen.spamhaus.org, but that's beyond the scope of this Class
	 **/
	public function _retrieve_IP_address_status_PHP($ip)
	{
		if($this->_check_required_params() == TRUE)
		{
			if(!$this->validate_IP_address($ip))
			{
				//trigger_error("not a valid IP address");
				//exit;
				return FALSE;
			}
			if(!$this->check_is_current_listed($ip,'webconfig') && (!$this->check_is_current_listed($ip,'htaccess')))
			{
				$lookup = $this->ini_array["PHPaccesskey"] .".".implode(".", array_reverse(explode (".", $ip))) .".dnsbl.httpbl.org";
				$result = explode(".", gethostbyname($lookup));
				if(!empty($result) && ($result["0"] == "127"))
				{
					// minDayinBl (2) and minThreatLevel (5)
					if(($result["1"] >= $this->ini_array["minDayinBl"]) &&
					  ($result["2"] >= $this->ini_array["minThreatLevel"]))
					{
						$this->save_positive_result($ip);
						$this->save_any_listing_file($ip);
						return TRUE;
					}
				}
			}
			else
			{
				$this->save_any_listing_file($ip);
				return TRUE;
			}
		}
		else
		{
			trigger_error("not all required variables are filled out.");
			exit;
		}
	}

	/**
	 * function to retrieve the IP status from Stop Forum Spam
	 * $ip : IP address in *normal* octet format - (variable)
	 * result is either: listed in Stop Forum Spam and now added to our 
	 * own blacklist, or already listed in our own little blacklist
	 **/
	public function _retrieve_IP_address_status_SFP($ip)
	{
		if($this->_check_required_params() == 1)
		{
			if(!$this->validate_IP_address($ip))
			{
				//trigger_error("not a valid IP address");
				return FALSE;
				//exit;
			}
			if(!$this->check_is_current_listed($ip,'webconfig') && (!$this->check_is_current_listed($ip,'htaccess')))
			{
				if($this->http_GET($ip, 'serial') == TRUE)
				{
					$this->save_positive_result($ip);
					$this->save_any_listing_file($ip);
					return TRUE;
				}
			}
			else
			{
				$this->save_positive_result($ip);
				$this->save_any_listing_file($ip);
				return TRUE;
			}
		}
		else
		{
			trigger_error("not all required variables are filled out.");
			exit;
		}
	}
	
	/**
	 * $url		: HTTP URL to Stop Forum Spam - (fixed)
	 * $ip		: IP address in *normal* octet format - (variable)
	 * $format	: serialized (serialize() or JSON) - (variable, 
	 * 			  serialize() is used as default)
	 **/
	public function http_GET($ip,$format='serial')
	{
		$ch = curl_init();
		$url = "http://www.stopforumspam.com/api?ip=".$ip."&f=".$format."";
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_HEADER, 0);
		$output = curl_exec($ch);
		curl_close($ch);
		$result = unserialize($output);
		if($result["ip"]["appears"] == 1)
		{
			return TRUE;
		}
	}
	
	/**
	 * function to save a positive listing result in our own blacklist
	 * $ip : IP address in *normal* octet format - (variable)
	 * 
	 * ISAPI Rewrite version 3 format uses .htaccess, in combination with 
	 * a blacklist.txt file (RewriteMap):
	 * 
	 * 108.59.10.145 -
	 * 
	 * More information:
	 * http://www.saotn.nl/isapi_rewrite-als-web-application-firewall-waf-1/
	 * http://helicontech.blogspot.com/2009/02/isapirewrite-faq.html
	 * 
	 * Should also work for Apache's mod_rewrite RewriteMap
	 * http://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#rewritemap
	 * http://httpd.apache.org/docs/2.4/rewrite/access.html#host-deny
	 **/
	public function save_positive_result($ip)
	{
		if(file_exists($this->ini_array["blfileloc"]) &&
		  is_writable($this->ini_array["blfileloc"]))
		{
			if($this->check_is_current_listed($ip, 'htaccess') != TRUE)
			{
				$f = fopen($this->ini_array["blfileloc"],"ab");
				fwrite($f, $ip ." -\r\n");
				fclose($f);
				clearstatcache();
			}
		}
		else
		{
			trigger_error($this->ini_array["blfileloc"] ." not writable!");
		}

		if(file_exists($this->ini_array["webconfigFile"]) &&
		  is_writable($this->ini_array["webconfigFile"]))
		{
			if(!$this->check_is_current_listed($ip, 'webconfig'))
			{
				$this->_save_web_dot_config_positive_result($ip);
			}
		}
		else
		{
			trigger_error($this->ini_array["webconfigFile"] ." not writable!");
		}
	}
	
	/**
	 * function to see if it is already listed
	 * $ip : IP address in *normal* octet format - (variable)
	 * result is either TRUE (listed in own blacklist) or FALSE (not listed)
	 **/
	public function check_is_current_listed($ip, $file='')
	{
		if($file == '' || $file == 'htaccess')
		{
			if(file_exists($this->ini_array["blfileloc"]))
			{
				$f = file_get_contents($this->ini_array["blfileloc"]);
				if(strstr($f, $ip ." -") != FALSE)
				{
					return TRUE;
				}
			}
		}
		if($file == 'webconfig')
		{
			if(($this->ini_array["use_webconfigFile"] == "1") && (file_exists($this->ini_array["webconfigFile"])))
			{
				$f = file_get_contents($this->ini_array["webconfigFile"]);
				$sstring = "ipAddress=\"".$ip."\" allowed=\"false\"";
				//var_dump(strstr($f,$sstring)); die();
				if(strstr($f,$sstring) == TRUE)
				{
					return TRUE;
				}
			}
		}
	}

	/**
	 * saves any hit of blocked IP addresses to a log file
	 **/
	public function save_any_listing_file($ip)
	{
		if(file_exists($this->ini_array["bllogfileloc"]) &&
		  is_writable($this->ini_array["bllogfileloc"]))
		{
			$f = fopen($this->ini_array["bllogfileloc"],"ab");
			fwrite($f, date("Y.m.d.G:i") ." - " . $ip ."\r\n");
			fclose($f);
			clearstatcache();
		}
		else
		{
			trigger_error("could not find logfile "
			  .$this->ini_array["bllogfileloc"]);
		}
	}

	/**
	 * IIS 7 / 7.5 IP Address and Domain Restrictions format
	 * uses web.config for configuration (to do)
	 * 
	 * <system.webServer>
	 * 	<security>
	 * 		<ipSecurity>
	 * 			<add ipAddress="aa.bbb.ccc.dd" allowed="false" />
	 * 		</ipSecurity>
	 * 	</security>
	 * </system.webServer>
	 **/
	public function _save_web_dot_config_positive_result($ip)
	{
		$formatxml = PHP_EOL;
		$formatxml .= "				<add ipAddress=\"".$ip."\" allowed=\"false\" />";

		$doc = new DOMDocument();
		if($doc->load($this->ini_array["webconfigFile"]) === false )
		{
			return false;
		}
		$xpath = new DOMXPath($doc);
		$iprestrictions_nodes = $xpath->query('/configuration/system.webServer/security/ipSecurity[starts-with(@add,\'ipAddress\')]');
		if($iprestrictions_nodes->length > 0)
		{
			return true;
		}

		$xmlnodes = $xpath->query('/configuration/system.webServer/security/ipSecurity');
		if ($xmlnodes->length > 0)
		{
			$ipsecurity_node = $xmlnodes->item(0);
		}
		else
		{
			$ipsecurity_node = $doc->createElement('ipSecurity');
		
			$xmlnodes = $xpath->query('/configuration/system.webServer/security');
			if($xmlnodes->length > 0)
			{
				$security_node = $xmlnodes->item(0);
				$security_node->appendChild($ipsecurity_node);
			}
			else
			{
				$security_node = $doc->createElement('security');
				$security_node->appendChild($ipsecurity_node);
				
				$xmlnodes = $xpath->query('/configuration/system.webServer');
				if($xmlnodes->length > 0)
				{
					$system_webServer_node = $xmlnodes->item(0);
					$system_webServer_node->appendChild($security_node);
				}
				else
				{
					$system_webServer_node = $doc->createElement('system.webServer');
					$system_webServer_node->appendChild($security_node);

					$xmlnodes = $xpath->query('/configuration');
					if ($xmlnodes->length > 0)
					{
						$config_node = $xmlnodes->item(0);
						$config_node->appendChild($system_webServer_node);
					}
					else
					{
						$config_node = $doc->createElement('configuration');
						$doc->appendChild($config_node);
						$config_node->appendChild($system_webServer_node);
					}
				}
			}
		}

		$rule_fragment = $doc->createDocumentFragment();
		$rule_fragment->appendXML($formatxml);
		$ipsecurity_node->appendChild($rule_fragment);

		$doc->encoding = "UTF-8";
		$doc->formatOutput = true;
		$this->saveDomDocument($doc, $this->ini_array["webconfigFile"]);

		return true;
	}

	function saveDomDocument($doc, $filename)
	{
		$config = $doc->saveXML();
		$config = preg_replace("/([^\r])\n/", "$1\r\n", $config);
		$fp = fopen($filename, 'wb');
		fwrite($fp, $config);
		fclose($fp);
	}
}
?>