<?php
###############################################################################
# DNS.php
#
# @author Anil Kumar <akumar@codepunch.com>
# @link   https://codepunch.com
#
############################################################################### 

namespace 	CodePunch\LU;
use 		CodePunch\Base\Util as UTIL;
use			CodePunch\Base\Text as TEXT;
use 		Exception;

###############################################################################

class DNS  {
	
	const	DNS_REC_ALL					= 255;
	const 	DNS_REC_A	 				= 1;
	const	DNS_REC_CNAME				= 2;
	const	DNS_REC_NS					= 4;
	const	DNS_REC_MX					= 8;
	const	DNS_REC_TXT					= 16;
	const	DNS_REC_BL					= 512;
	
	const 	DNS_TIMEOUT 				= 10;

	private $lookupManager 				= null;
	private $global_nameserver_ip 		= "";
	private $local_name_servers_exist	= false;
	
	###########################################################################
	
	public function __construct($lum=null) {
		$this->lookupManager = $lum;
		if($lum) {
			$auth = $this->lookupManager->getAuthentication();
			$setup = new \CodePunch\Config\Settings($auth);
			$db = $auth->getDatabase();
			$this->global_nameserver_ip = $setup->getOption("global_nameserver_ip", "");
			if(in_array("local_nameserver_ip", $db->getAllColumnNames($db->getDomaintableName())))
				$this->local_name_servers_exist = true;
		}
	}
	
	###############################################################################
	
	public function Root(&$ludata) 
	{
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		
		$did = $ludata['sid'];
		$domain = $db->getDomainName($did);
		$lookupcount = 0;
		
		$dataarray = array();
		$host = UTIL::idn_convert_to_host_name($domain, "");
		$iplong = -1;
		$ip = gethostbyname($host);
		if($ip != $host)
		{
			$dataarray['ip'] = $ip;
			$iplong = ip2long($ip);
			$dataarray = array_merge($dataarray, $this->do_ip_asn_lookup($ip));
		}
		else
			$dataarray['ip'] = "error";
		$dataarray['domain'] = $domain;
		$dataarray['rootdns_checked_at'] = date("Y-m-d H:i:s");
		$this->lookupManager->updateDomainTable(\CodePunch\LU\LookupManager::ROOT_DNS, $dataarray);
		$lookupcount++;
		
		$setup = new \CodePunch\Config\Settings($this->lookupManager->getAuthentication());
		$rootcheck = $setup->getBoolean('do_dns_record_lookup', true);
		$dnsbl_checks = $setup->getBoolean("dnsbl_checks_enabled", false);
		if($rootcheck) {
			$domaindata = array();
			$nsrecords = array();
			$index = 1;
			
			$nsip = "";
			$axfr = $this->get_axfr_status($domain, $nsip);
			$rectype = $dnsbl_checks ? (31+self::DNS_REC_BL) : 31;

			try {
				$ipv4 = $this->lookupManager->getAuthentication()->getProClass('IPV4');
				$data = $this->get_dns_records($domain, "", $nsip, $axfr, $rectype);
				$db->deleteFromTable($db->getSubdomainTableName(), "auto_added=? AND sid=? AND subdomain='@'", array(\CodePunch\LU\LookupManager::SD_AUTO_DNS_ROWS, $did));
				if(count($data)) {
					foreach($data as $da) {
						// TODO: Check duplicates before inserting.
						$db->insertIntoTable($db->getSubdomainTableName(), $da);
						
						// Insert prime row if it doesn't exist
						// The prime row is important because it will hold custom notes columns
						$mainrow = $db->getFromTable("hid", $db->getSubdomainTableName(), "sid=? AND subdomain=? AND record_type=? AND record_value=? AND auto_added=?", array($da['sid'], $da['subdomain'], $da['record_type'], $da['record_value'], \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS));
						if($mainrow === false || !is_array($mainrow) || count($mainrow) != 1) {
							$da['auto_added'] = \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS;
							$db->insertIntoTable($db->getSubdomainTableName(), $da);
						}
						if(strtolower($da['record_type']) == "mx") {
							$domaindata["mx".$index] = $da['ptr'];
							$domaindata["mxip".$index] = $da['ip'];
							$index++;
						}
						if(strtolower($da['record_type']) == "ns")
							$nsrecords[] = $da['record_value'];
						
						// Added May 2023
						if(!empty($ipv4) && (strtolower($da['record_type']) == "cname" || strtolower($da['record_type']) == "a")) {
							$ipv4->add($domain, $da, $dataarray);
						}
					}
					$lookupcount++;
				}

				$tld = strrchr($domain, ".");
				if($tld !== false && count($nsrecords)) {
					$tld = substr($tld, 1);
					$nscopy = $db->findOneOf($db->getTLDsTableName(), "tld", $tld, "dnscopyns");
					if($nscopy) {
						$idx = 1;
						foreach($nsrecords as $ns) {
							$domaindata["ns".$idx] = $ns;
							$idx++;
						}
					}
				}

				if(count($domaindata)) {
					$domaindata['sid'] = $did;
					$this->lookupManager->updateDomainTable(\CodePunch\LU\LookupManager::ROOT_DNS, $domaindata);
				}
			}
			catch(Exception $e) {
				// Error is already logged. We will not process this data.
				// Only timeout errors should get caught here.
			}
		}
		
		// TODO:  Save Data History
		
		$ludata['status'] = \CodePunch\LU\LookupManager::LUQ_COMPLETE;
		return $lookupcount;
	}
	
	###############################################################################
	
	private function get_axfr_status($domain, &$nsip)
	{
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$axfr = false;
		$nsip = $db->findOneOf($db->getDomaintableName(), "domain", $domain, "axfr_ip");
		if($nsip == null || $nsip == "" || $nsip === false)
			$nsip = $this->get_ns_ip_for_domain($domain);
		else 
			$axfr = true;
		// set axfr_status to 0 for this domain
		$sid  = $db->getDomainID($domain);
		$db->updateTable($db->getDomainTableName(), array('axfr_status'=>0), "sid=?", array($sid));
		return $axfr;
	}
	
	###############################################################################
	
	public function parse_dns_records($result, $rtype)
	{
		if($rtype != "" && !is_array($rtype))
			$rtype = array($rtype);
		
		if(isset($result->answer)) {
			$rmx = array();
			foreach($result->answer as $a) {
				$ra = array();
				if(isset($a->name))
					$ra['host'] = $a->name;
				if(isset($a->class))
					$ra['class'] = $a->class;
				if(isset($a->ttl))
					$ra['ttl'] = $a->ttl;
				if(isset($a->type))
					$ra['type'] = $a->type;
				if(isset($a->preference))
					$ra['pri'] = $a->preference;
				if(isset($a->exchange))
					$ra['target'] = $a->exchange;
				if(isset($a->address)) {
					if($rtype == "AAAA")
						$ra['ipv6'] = $a->address;
					else
						$ra['ip'] = $a->address;
				}
				if(isset($a->nsdname))
					$ra['target'] = $a->nsdname;
				if(isset($a->cname))
					$ra['target'] = $a->cname;
				if(isset($a->text)) {
					$ra['entries'] = $a->text;
					if(is_array($ra['entries']) && isset($ra['entries'][0]))
						$ra['txt'] = $ra['entries'][0];
				}
				if($rtype == "")
					$rmx[] = $ra;
				else if(UTIL::in_array_casei($ra['type'], $rtype))
					$rmx[strtolower($ra['type'])][] = $ra;
			}
			return $rmx;
		}
		return false;
	}
	
	###############################################################################
	
	public function getAXFR($domain, $axfrip, $hostname)
	{
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$sid  = $db->getDomainID($domain);
		// axfr_status for this domain is assumed to be reset to 0 already.
		
		if(!class_exists('Net_DNS2_Resolver')) {
			$ipath = stream_resolve_include_path('Net/DNS2.php');
			if($ipath !== false && file_exists($ipath))
				require_once('Net/DNS2.php');
			else {
				$logger = new \CodePunch\Base\CPLogger();
				$error = "Unable to load Net/DNS2. Please install php-net-dns2 if you want to use custom name servers for DNS lookups.";
				$logger->error($error);
			}
		}
		if(class_exists('Net_DNS2_Resolver')) {
			try {
				$nsips = is_array($axfrip) ? $axfrip : array($axfrip);
				$resolver = new \Net_DNS2_Resolver(array('nameservers' => $nsips, 'timeout' => self::DNS_TIMEOUT));
				$result = $resolver->query($domain, "AXFR");
				$rmx = $this->parse_dns_records($result, array("A","MX", "CNAME", "NS", "TXT"));
				// AXFR returns all types of data.
				// We should separate the A, CNAME, NS, MX and TXT records.
				UTIL::debug_cli_print("AXFR");
				UTIL::debug_cli_print($rmx);
				UTIL::debug_cli_print("$domain : $hostname");
				$nrr = array();
				$rkreqd = array('a', 'cname', 'ns', 'mx', 'txt');
				foreach($rmx as $rt=>$rv) {
					$r2 = array();
					foreach($rv as $r) {
						if($domain == $hostname && $r['host'] == $domain)
							$r2[] = $r;
						else if($domain != $hostname && $r['host'] == $hostname) {
							$r2[] = $r;
						}							
					}
					$nrr[$rt] = $r2;
				}
				foreach($rkreqd as $rk) {
					if(!isset($nrr[$rk]))
						$nrr[$rk] = array();
				}
				//UTIL::debug_cli_print($nrr);
				// remove all entries that contain a subdomain and return the rest.
				return $nrr;
			}
			catch(Exception $e) {
				$logger = new \CodePunch\Base\CPLogger();
				$logger->error("AXFR: $domain: $axfrip: " . $e->getMessage() . " A normal DNS query will be attempted now.");
				UTIL::debug_cli_print("AXFR: $domain: $axfrip: " . $e->getMessage());
			}
		}
		
		// Do the old fashioned way if AXFR fails.
		$nsip = $this->get_ns_ip_for_domain($domain);
		$rmx = array();
		$rmx['a'] = $this->getDNSRecord($hostname, DNS_A, $nsip);
		$rmx['cname'] = $this->getDNSRecord($hostname, DNS_CNAME, $nsip);
		$rmx['ns'] = $this->getDNSRecord($hostname, DNS_NS, $nsip);
		$rmx['mx'] = $this->getDNSRecord($hostname, DNS_MX, $nsip);
		$rmx['txt']  = $this->getDNSRecord($hostname, DNS_TXT, $nsip);
		return $rmx;
	}
	
	###############################################################################
	
	public function getDNSRecord($hostname, $recordtype, $nameserverip="")
	{
		$dnsr = false;
		if($nameserverip != "") {
			if(!class_exists('Net_DNS2_Resolver')) {
				$ipath = stream_resolve_include_path('Net/DNS2.php');
				if($ipath !== false && file_exists($ipath))
					require_once('Net/DNS2.php');
				else {
					$logger = new \CodePunch\Base\CPLogger();
					$error = "Unable to load Net/DNS2. Please install php-net-dns2 if you want to use custom name servers for DNS lookups.";
					$logger->error($error);
				}
			}
			if(class_exists('Net_DNS2_Resolver')) {
				try {
					$resolver = new \Net_DNS2_Resolver(array('nameservers' => array($nameserverip), 'timeout' => self::DNS_TIMEOUT));
					$rtype = "";
					switch($recordtype) {
						case DNS_A:		$rtype = "A"; 		break;
						case DNS_NS: 	$rtype = "NS"; 		break;
						case DNS_TXT: 	$rtype = "TXT"; 	break;
						case DNS_MX: 	$rtype = "MX"; 		break;
						case DNS_PTR: 	$rtype = "PTR"; 	break;
						case DNS_AAAA: 	$rtype = "AAAA"; 	break;
						case DNS_CNAME: $rtype = "CNAME"; 	break;
					}
					if($rtype != "") {
						$result = $resolver->query($hostname, $rtype);
						if(isset($result->answer)) {
							$rmx = array();
							foreach($result->answer as $a) {
								$ra = array();
								if(isset($a->name))
									$ra['host'] = $a->name;
								if(isset($a->class))
									$ra['class'] = $a->class;
								if(isset($a->ttl))
									$ra['ttl'] = $a->ttl;
								if(isset($a->type))
									$ra['type'] = $a->type;
								if(isset($a->preference))
									$ra['pri'] = $a->preference;
								if(isset($a->exchange))
									$ra['target'] = $a->exchange;
								if(isset($a->address)) {
									if($rtype == "AAAA")
										$ra['ipv6'] = $a->address;
									else
										$ra['ip'] = $a->address;
								}
								if(isset($a->nsdname))
									$ra['target'] = $a->nsdname;
								if(isset($a->cname))
									$ra['target'] = $a->cname;
								if(isset($a->text)) {
									$ra['entries'] = $a->text;
									if(is_array($ra['entries']) && isset($ra['entries'][0]))
										$ra['txt'] = $ra['entries'][0];
								}
								if(strtolower($ra['type']) == strtolower($rtype))
									$rmx[] = $ra;
							}
							return $rmx;
						}
					}
				}
				catch(Exception $e) {
					$logger = new \CodePunch\Base\CPLogger();
					$logger->error("[$hostname] [$recordtype] "  . $e->getMessage());
					// We will throw if there is a timeout error, otherwise an error
					// will be thrown even when records are legitimately deleted.
					if(stripos($e->getMessage(), "timeout on read select") !== false) {
						UTIL::debug_cli_print("[$hostname] [$recordtype] "  . $e->getMessage());
						$db = $this->lookupManager->getAuthentication()->getDatabase();
						\CodePunch\DB\Audit::add($db, \CodePunch\DB\Audit::DNS_CHECK_TIMEOUT, "[$hostname] [$recordtype] "  . $e->getMessage());
						throw($e);
					}
				}
			}
		}
		else 
			$dnsr = @dns_get_record($hostname, $recordtype);
		return ($dnsr === false ? array() : $dnsr);
	}
	
	###############################################################################
	
	public function get_ns_ip_for_domain($domain)
	{
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$nsip = "";
		$gnsip = $this->global_nameserver_ip;
		if($this->local_name_servers_exist)
			$lnsip = $db->findOneOf($db->getDomaintableName(), "domain", $domain, "local_nameserver_ip");
		else
			$lnsip = "";
		if($lnsip != "" && filter_var($lnsip, FILTER_VALIDATE_IP))
			$nsip = $lnsip;
		if($gnsip != "" && $nsip == "" && filter_var($gnsip, FILTER_VALIDATE_IP))
			$nsip = $gnsip;
		return $nsip;
	}
	
	###############################################################################

	public function get_dns_records($domain, $subdomain="", $nsip="", $axfr=false, $rectype=31)
	{
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$sid = $db->getDomainID($domain);
		if($sid === false || $sid == 0)
			return false;
		
		$hostname = UTIL::idn_convert_to_host_name($domain, $subdomain);

		if($axfr && $nsip != "" && $subdomain == "") {
			$rdata = $this->getAXFR($domain, $nsip, $hostname);
			// Grab everything when it is AXFR
			$ra = UTIL::get_from_array($rdata['a'], array());
			$rcn = UTIL::get_from_array($rdata['cname'], array());
			$rns = UTIL::get_from_array($rdata['ns'], array());
			$rmx = UTIL::get_from_array($rdata['mx'], array());
			$rtxt = UTIL::get_from_array($rdata['txt'], array());
		}
		else if($axfr && $nsip != "" && $subdomain != "") {
			$rdata = $this->getAXFR($domain, $nsip, $hostname);
			// Grab everything when it is AXFR
			$ra = ($rectype&self::DNS_REC_A) ?  UTIL::get_from_array($rdata['a'], array()) : array();
			$rcn = ($rectype&self::DNS_REC_CNAME) ? UTIL::get_from_array($rdata['cname'], array()) : array();
			$rns = ($rectype&self::DNS_REC_NS) ? UTIL::get_from_array($rdata['ns'], array()) : array();
			$rmx = ($rectype&self::DNS_REC_MX) ? UTIL::get_from_array($rdata['mx'], array()) : array();
			$rtxt = ($rectype&self::DNS_REC_TXT) ? UTIL::get_from_array($rdata['txt'], array()) : array();
		}
		else {
			// Do selective lookup if not AXFR
			$ra = ($rectype&self::DNS_REC_A) ?  $this->getDNSRecord($hostname, DNS_A, $nsip) : array();
			$rcn = ($rectype&self::DNS_REC_CNAME) ? $this->getDNSRecord($hostname, DNS_CNAME, $nsip) : array();
			$rns = ($rectype&self::DNS_REC_NS) ? $this->getDNSRecord($hostname, DNS_NS, $nsip) : array();
			$rmx = ($rectype&self::DNS_REC_MX) ? $this->getDNSRecord($hostname, DNS_MX, $nsip) : array();
			$rtxt = ($rectype&self::DNS_REC_TXT) ? $this->getDNSRecord($hostname, DNS_TXT, $nsip) : array();
			$ar = array();
			foreach($ra as $r) {
				if($r['host'] == $hostname) 
					$ar[] = $r;
			}
			$ra = $ar;
		}
		
		$disableAlerts = $db->findOneOf($db->getDomainTableName(), "sid", $sid, "disable_alerts");
		if($disableAlerts !== false && intval($disableAlerts) == 1)
			UTIL::debug_cli_print("Ignoring Alerts for $domain");
		else {
			$difflog = array();
			if($rectype&self::DNS_REC_NS)
				$difflog = array_merge($difflog, $this->lookupManager->monitorDNSData($sid, $domain, $subdomain, $rns, "NS"));
			if($rectype&self::DNS_REC_A)
				$difflog = array_merge($difflog, $this->lookupManager->monitorDNSData($sid, $domain, $subdomain, $ra, "A"));
			if($rectype&self::DNS_REC_CNAME)
				$difflog = array_merge($difflog, $this->lookupManager->monitorDNSData($sid, $domain, $subdomain, $rcn, "CNAME"));
			if($rectype&self::DNS_REC_MX)
				$difflog = array_merge($difflog, $this->lookupManager->monitorDNSData($sid, $domain, $subdomain, $rmx, "MX"));
			if($rectype&self::DNS_REC_TXT)
				$difflog = array_merge($difflog, $this->lookupManager->monitorDNSData($sid, $domain, $subdomain, $rtxt, "TXT"));
			if(count($difflog)) {
				$this->lookupManager->emailDNSAlerts($difflog);
			}
		}
		
		$r = $ra;
		$r = array_merge($r, $rcn);
		$r = array_merge($r, $rns);
		$r = array_merge($r, $rmx);
		$r = array_merge($r, $rtxt);
		
		$data = array();
		if(is_array($r)) {
			$index = 0;
			foreach($r as $record) {
				$data_array = Array();
				$type = "";
				$target = "";
				$ttl = "";
				$pri = "";
				$ip = "";
				$host = "";
				if(isset($record['type']))
					$type = $record['type'];
				if(isset($record['ttl']))
					$ttl = $record['ttl'];
				if(isset($record['pri']))
					$pri = $record['pri'];
				if(isset($record['target']))
					$target = $record['target'];
				if(isset($record['ip']))
					$ip = $record['ip'];
				if(isset($record['host']))
					$host = $record['host'];
				if(isset($record['txt'])) {
					$txtval = $record['txt'];
				}
				if($type == "NS" || $type == "MX" || $type == "CNAME" || $type == "A" || $type == "TXT") {
					$data_array['auto_added'] = \CodePunch\LU\LookupManager::SD_AUTO_DNS_ROWS;
					$data_array['record_type'] = $type;
					$data_array['ttl'] = $ttl;
					if($type == "NS" || $type == "MX") {
						if($type == "MX") 
							$data_array['record_value'] = $pri . " " . $target;
						else 
							$data_array['record_value'] = $target;
						$ipdata = $this->get_ip_info($target);
						$data_array['ip'] = UTIL::get_from_array($ipdata['ip'], "");
						$data_array['ptr'] = UTIL::get_from_array($ipdata['ptr'], "");
						if($type == "MX" && ($rectype&self::DNS_REC_BL)) {
							$listed = self::checkIPAddressInDNSBL($data_array['ip']);
							if(count($listed))
								$data_array['dnsbl_lookup_results'] = implode(", ", $listed);
							else
								$data_array['dnsbl_lookup_results'] = "-";
						}
					}
					else if($type == "A") {
						$data_array['ip'] = $ip;
						$data_array['record_value'] = $ip;
						$revptr = gethostbyaddr($ip);
						if($revptr != $ip)
							$data_array['ptr'] = $revptr;
						if($rectype&self::DNS_REC_BL) {
							$listed = self::checkIPAddressInDNSBL($data_array['ip']);
							if(count($listed))
								$data_array['dnsbl_lookup_results'] = implode(", ", $listed);
							else
								$data_array['dnsbl_lookup_results'] = "-";
						}
					}
					else if($type == "CNAME") {
						$data_array['record_value'] = $target;
						$ipc = gethostbyname($target);
						if($ipc != $target)
						{
							$data_array['ip'] = $ipc;
							$revptr = gethostbyaddr($ipc);
							if($revptr != $ip)
								$data_array['ptr'] = $revptr;
						}
					}
					else if($type == "TXT")
						$data_array['record_value'] = $txtval;
						
					$sd = $subdomain;
					if($axfr && $nsip != "" && $subdomain == "")
						$subdomain = trim(str_ireplace($domain, "", $host), " .");
					
					if($subdomain == "" || $subdomain == "@")
						$data_array['subdomain'] = "@";
					else
						$data_array['subdomain'] = $subdomain;
					$data_array['added_on'] = date("Y-m-d H:i:s");
					$data_array['sid'] = $sid;
					
					// 2020-16-05, Check for manually added data in DB
					$aamode = \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS;
					$mainrow = $db->getFromTable("*", $db->getSubdomainTableName(), "sid=? AND subdomain=? AND record_type=? AND record_value=? AND auto_added=?", array($sid, $data_array['subdomain'], $data_array['record_type'], $data_array['record_value'], $aamode));
					if($mainrow !== false && is_array($mainrow) && count($mainrow) == 1) {
						$mainrow = $db->fetchRow($mainrow, 0);
						$editable = $db->getEditableSubdomainColumnNames();
						foreach($mainrow as $cpkey => $cpval) {
							$cpkey = strtolower($cpkey);
							if(in_array($cpkey, $editable) && $cpval != "") {
								$data_array[$cpkey] = $cpval;
							}
							$data_array['manual_edited_at'] = $mainrow['manual_edited_at'];
							$data_array['edited'] = '1';
						}
						//UTIL::debug_cli_print("Manually edited DNS Row");
						//UTIL::debug_cli_print($mainrow);
					}
					// 2020-16-05

					$subdomain = $sd;
				}	
				$data[] = $data_array;
				unset($data_array);
			}
		}
		return $data;
	}
	
	###############################################################################
	
	public function get_ip_info($hostname)
	{
		$hostname = UTIL::idn_convert_to_host_name($hostname, "");
		$ip = gethostbyname($hostname);
		$data = array('ip'=>'', 'ptr'=>'');
		if($ip != $hostname) {
			$data['ip'] = $ip;
			$revptr = gethostbyaddr($ip);
			if($revptr != $ip)
				$data['ptr'] = $revptr;
		}
		return $data;
	}
	
	###############################################################################

	public function do_ip_asn_lookup($ip)
	{
		return UTIL::ip_asn_lookup($ip);
	}
	
	###############################################################################
	
	public function SubDomain(&$ludata) 
	{
		$lookupcount = 0;
		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$sid = $ludata['sid'];
		$domain = $db->getDomainName($sid);
		$axfrstatus = $db->findOneOf($db->getDomainTableName(), "sid", $sid, "axfr_status");
		if($axfrstatus !== false && $axfrstatus == 1) {
			// We have AXFR data, don't do subdomain checks.
			$logger = new \CodePunch\Base\CPLogger();
			$logger->info("Ignoring subdomain lookups for $domain because we have AXFR data");
			UTIL::debug_cli_print("Ignoring subdomain lookups for $domain because we have AXFR data");
		}
		else {
			$data = $this->do_subdomain_lookup($ludata['sid']);
			if(count($data)) {
				$lookupcount++;
			}
		}
		$dataarray = array();
		$dataarray['domain'] = $domain;
		if($dataarray['domain'] != false && $dataarray['domain'] != "") {
			$dataarray['subdomains_checked_at'] = date("Y-m-d H:i:s");
			$this->lookupManager->updateDomainTable(\CodePunch\LU\LookupManager::SUB_DOMAINS, $dataarray);
		}
		$ludata['status'] = \CodePunch\LU\LookupManager::LUQ_COMPLETE;
		return $lookupcount;
	}
	
	###############################################################################

	function do_subdomain_lookup($sid)
	{
		$setup = new \CodePunch\Config\Settings($this->lookupManager->getAuthentication());
		$dnsbl_checks = $setup->getBoolean("dnsbl_checks_enabled", false);

		$db = $this->lookupManager->getAuthentication()->getDatabase();
		$domain = $db->getDomainName($sid);
		$nsip = $this->get_ns_ip_for_domain($domain);
		$data = array();
		$ipv4 = $this->lookupManager->getAuthentication()->getProClass('IPV4');
		if($sid !== false && $sid > 0) {
			$sdinfo = $db->getFromTable("hid,subdomain,auto_added", $db->getSubdomainTableName(), "sid = ? AND subdomain != '@' AND (auto_added = ? OR auto_added=?)", array($sid,\CodePunch\LU\LookupManager::SD_USER_ROWS, \CodePunch\LU\LookupManager::SD_TXT_ROWS));
			if($sdinfo !== false && is_array($sdinfo)) {
				foreach($sdinfo as $sdi) {
					$sdi = array_change_key_case($sdi, CASE_LOWER);
					$rectype = ($sdi['auto_added'] == \CodePunch\LU\LookupManager::SD_TXT_ROWS) ? self::DNS_REC_TXT : (self::DNS_REC_A + self::DNS_REC_CNAME);
					if($dnsbl_checks)
						$rectype += self::DNS_REC_BL;
					if($sdi['auto_added'] == \CodePunch\LU\LookupManager::SD_TXT_ROWS)
						$ec = "record_type = 'TXT'";
					else
						$ec = "(record_type = 'A' OR record_type = 'CNAME')";

					try {
						$axfr = $this->get_axfr_status($domain, $nsip);
						$dataarray = $this->get_dns_records($domain, $sdi['subdomain'], $nsip, $axfr, $rectype);
						UTIL::debug_cli_print("Subdomain DNS: " . $domain . ":" . $sdi['subdomain'] . ":" . $rectype);
						UTIL::debug_cli_print($dataarray);
						$db->deleteFromTable($db->getSubdomainTableName(), "auto_added=? AND sid=? AND subdomain=? AND $ec", array(\CodePunch\LU\LookupManager::SD_AUTO_DNS_ROWS, $sid, $sdi['subdomain']));
						$data[] = $dataarray;
						foreach($dataarray as $da) {
							// TODO: Check duplicates before inserting.
							$db->insertIntoTable($db->getSubdomainTableName(), $da);
							
							// Insert prime row if it doesn't exist
							// The prime row is important because it will hold custom notes columns
							$mainrow = $db->getFromTable("hid", $db->getSubdomainTableName(), "sid=? AND subdomain=? AND record_type=? AND record_value=? AND auto_added=?", array($da['sid'], $da['subdomain'], $da['record_type'], $da['record_value'], \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS));
							if($mainrow === false || !is_array($mainrow) || count($mainrow) != 1) {
								$da['auto_added'] = \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS;
								$db->insertIntoTable($db->getSubdomainTableName(), $da);
							}
							// Added May 2023
							if(!empty($ipv4) && (strtolower($da['record_type']) == "cname" || strtolower($da['record_type']) == "a")) {
								//UTIL::debug_cli_print("Got DNS Data");
								//UTIL::debug_cli_print($da);
								$ipdata = array();
								$ipdata['ipv4'] = ip2long($da['ip']);
								$ipdata['ip'] = $da['ip'];
								if(isset($da['ptr']))
									$ipdata['ptr'] = $da['ptr'];
								$ipdata['hostname'] = $da['subdomain'] . "." . $domain;
								$ipdata['record_type'] = $da['record_type'];
								$ipdata['record_value'] = $da['record_value'];
								//$ipdata['sid'] = $sid;
								$ipv4->addData($ipdata);
							}
						}
					}
					catch(Exception $e) {
						// Error is already logged. We will not process this data.
						// Only timeout errors should get caught here.
					}
				}
			}
		}
		return $data;
	}
	
	###############################################################################
	
	public function scanForSubdomains($names, $days=0, $hours=0, $domain="", $maxdomains=0, $maxsubdomains=250)
	{
		$responce = array('status' => 'notok', 'error'=>'', 'found'=>array(), 'added'=>0);
		$fp = UTIL::get_lock_file("sdscan");
		if(!$fp) {
			$responce['error'] = "Unable to get lock file for subdomain scan.\n";
			UTIL::print("\t" . $responce['error'] . "\n");
			return $responce;
		}
		if(!is_array($names)) {
			if(!strcasecmp($names, "auto")) {
				$names = ['!auto!'];
			}
			else if(is_file($names) && $names != "") {
				$contents = file_get_contents($names);
				if($contents !== false) {
					$contents = str_replace("\r", "\n", $contents);
					$names = array_filter(explode("\n", $contents));
				}
			}
		}
		
		if(is_array($names) && count($names)) {
			$sql = "axfr_status != 1";
			$params = array();
			$orderby = "";
			$sortorder = "asc";
			$limit = ($maxdomains <= 0 ? null : $maxdomains);
			if($days > 0 || $hours > 0) {
				$dc = date("Y-m-d H:i:s", time()-($days*24*3600)-($hours*3600));
				$sql .= " AND (added_on > ?)";
				$params[] = $dc;
				$orderby = "added_on";
				$sortorder = "desc";
				$scanmsg = "Scanning the domains added in the last $days days, $hours hours";
			}
			else
				$scanmsg = "Scanning";
			if($names[0] == '!auto!') {
				$sql .= " AND (subdomains_autofound_at IS NULL OR subdomains_autofound_at < ?)";
				$sixmonthsago = date("Y-m-d H:i:s", time() - (6*30*24*3600));
				$params[] = $sixmonthsago;
			}
			$db = $this->lookupManager->getAuthentication()->getDatabase();
			if($domain != "") {
				if(UTIL::starts_with($domain, "[") && UTIL::ends_with($domain, "]")) {
					$sql .= " AND (" . $db->customExpandWhere($domain) . ")";
					$scanmsg .= " $domain";
				}
				else if(strtolower($domain) != "all") {
					$params[] = $domain;
					$sql .= " AND domain=?";
					$scanmsg .= " domain = $domain";
				}
			}
			UTIL::print("$scanmsg.\n");
			$rows = $db->getFromTable("sid,domain", $db->getDomainTableName() . " d", $sql, $params, $orderby, $sortorder, $limit);
			if($rows !== false) {
				$count = count($rows);
				$index = 0;
				foreach($rows as $di) {
					$di = array_change_key_case($di, CASE_LOWER);
					$domain = $di['domain'];
					$sid = $di['sid'];
					$index++;	
					if(UTIL::is_cli() && UTIL::is_request_key_set("debug")) {
						$perc = intval(($index/$count)*100);
						UTIL::print("\t($perc%)\t$index/$count] $domain");		
					}
					$nc = 0;
					$nsip = $this->get_ns_ip_for_domain($domain);
					$althostname = UTIL::idn_convert_to_host_name($domain, "wvrty7bnGwe56");
					$altip = gethostbyname($althostname);
					if($altip != $althostname && $names[0] != "!auto!") {
						UTIL::cli_print("\t\tIgnoring $domain because of wildcard $altip for *.$domain");
						continue;
					}
					if($names[0] != "!auto!") {
						foreach($names as $selector) {
							$hostname = UTIL::idn_convert_to_host_name($domain, $selector);
							if($db->isSubdomain($sid, $selector) === false) {
								try {
									$dnsinfo = $this->getDNSRecord($hostname, DNS_CNAME, $nsip);
									if(!count($dnsinfo))
										$dnsinfo = $this->getDNSRecord($hostname, DNS_A, $nsip);
									if(count($dnsinfo)) {
										$dnsdata = $dnsinfo[0];
										if(isset($dnsdata['ip']) || (isset($dnsdata['type']) && $dnsdata['type'] == 'CNAME')) {
											$responce['added'] += $db->addSubdomain($sid, $selector);
											UTIL::cli_print("\t\t*** " . $responce['added'] . " $hostname [" . $dnsdata['type'] . "] ***");
											$responce['found'][] = $dnsdata;
										}
									}
								}
								catch(Exception $e) {
									// Error is already logged.
									// Only timeout errors should get caught here.
								}
								$nc++;
								if(($nc%100) == 0) {
									$perc = intval(($nc/count($names))*100);
									UTIL::print("\t\t($perc%)\t$nc/count($names)] $hostname");		
								}
							}
						}
					}
					else {
						$ascii_domain = UTIL::idn_convert($domain);
						$subdomains = UTIL::getKnownSubdomains($domain);
						UTIL::cli_print($domain);
						if(count($subdomains))
							UTIL::cli_print($subdomains);
						foreach($subdomains as $hn) {
							$selector = str_replace($domain, "", $hn);
							$hostname = UTIL::idn_convert_to_host_name($hn, "");
							if(UTIL::ends_with($hostname, $ascii_domain) && $db->isSubdomain($sid, $selector) === false) {
								try {
									$dnsinfo = $this->getDNSRecord($hostname, DNS_CNAME, $nsip);
									if(!count($dnsinfo))
										$dnsinfo = $this->getDNSRecord($hostname, DNS_A, $nsip);
									if(count($dnsinfo)) {
										$dnsdata = $dnsinfo[0];
										if(isset($dnsdata['ip']) || (isset($dnsdata['type']) && $dnsdata['type'] == 'CNAME')) {
											$responce['added'] += $db->addSubdomain($sid, $selector);
											UTIL::cli_print("\t\t*** " . $responce['added'] . " $hostname [" . $dnsdata['type'] . "] ***");
											$responce['found'][] = $dnsdata;
										}
									}
								}
								catch(Exception $e) {
									// Error is already logged.
									// Only timeout errors should get caught here.
								}
								if($maxsubdomains && $responce['added'] >= $maxsubdomains)
									break;
								$nc++;
							}
						}
						
						// Let us check for common DKIM records too.
						$this->scanForTXTRecords("auto", false, 0, 0, $domain);
						
						$dud = ['sid'=>$sid, 'subdomains_autofound_at'=>date("Y-m-d H:i:s")];
						$db->updateDomainTable($dud);
					}
				}
			}
		}
		else {
			$responce['error'] = "Missing selector List";
		}
		flock($fp, LOCK_UN);
		fclose($fp);
		return $responce;
	}
	
	###############################################################################
	
	public function scanForTXTRecords($names, $append_dk = false, $days = 0, $hours=0, $domain="")
	{
		$responce = array('status' => 'notok', 'error'=>'', 'found'=>array(), 'added'=>0);
		if(!is_array($names)) {
			if($names == 'auto') {
				$names = [];
				$cnames = ['google','mail','default','dkim'];
				foreach($cnames as $cn) {
					if($append_dk) 
						$names[] = $cn;
					else
						$names[] = $cn . "._domainkey";
				}
			}
			else if(is_file($names) && $names != "") {
				$contents = file_get_contents($names);
				if($contents !== false) {
					$contents = str_replace("\r", "\n", $contents);
					$names = array_filter(explode("\n", $contents));
				}
			}
		}
		
		if(is_array($names) && count($names)) {
			foreach($names as &$name) {
				if($append_dk) 
					$name .= "._domainkey";
			}
			
			$sql = "axfr_status != 1";
			$params = array();
			$orderby = "";
			$sortorder = "asc";
			if($days > 0 || $hours > 0) {
				$dc = date("Y-m-d H:i:s", time()-($days*24*3600)-($hours*3600));
				$sql .= " AND (added_on > ?)";
				$params[] = $dc;
				$orderby = "added_on";
				$sortorder = "desc";
				$scanmsg = "Scanning the domains added in the last $days days, $hours hours";
			}
			else
				$scanmsg = "Scanning";
			$db = $this->lookupManager->getAuthentication()->getDatabase();
			if($domain != "") {
				if(UTIL::starts_with($domain, "[") && UTIL::ends_with($domain, "]")) {
					$sql .= " AND (" . $db->customExpandWhere($domain) . ")";
					$scanmsg .= " $domain";
				}
				else if(strtolower($domain) != "all") {
					$params[] = $domain;
					$sql .= " AND domain=?";
					$scanmsg .= " domain = $domain";
				}
			}
			UTIL::print("\t$scanmsg.\n");
			$rows = $db->getFromTable("sid,domain", $db->getDomainTableName() . " d", $sql, $params, $orderby, $sortorder);
			if($rows !== false) {
				$count = count($rows);
				$index = 0;
				foreach($rows as $di) {
					$di = array_change_key_case($di, CASE_LOWER);
					$domain = $di['domain'];
					$sid = $di['sid'];
					$index++;
					if(UTIL::is_cli() && UTIL::is_request_key_set("debug")) {
						$perc = intval(($index/$count)*100);
						UTIL::print("\t($perc%)\t$index/$count] $domain");		
					}
					$nsip = $this->get_ns_ip_for_domain($domain);
					$althostname = UTIL::idn_convert_to_host_name($domain, "wvrty7bnGwe56");
					$altip = gethostbyname($althostname);
					if($altip != $althostname) {
						UTIL::cli_print("\t\tIgnoring $domain because of wildcard $altip for *.$domain");
						continue;
					}
					foreach($names as $selector) {
						$hostname = UTIL::idn_convert_to_host_name($domain, $selector);
						if($db->isTXTName($sid, $selector) === false) {
							try {
								$rdata = $this->getDNSRecord($hostname, DNS_TXT, $nsip);
								if(is_array($rdata) && count($rdata)) {
									//UTIL::cli_print($rdata);
									foreach($rdata as $r) {
										if(isset($r['type'])) {
											$responce['added'] += $db->addTXTName($sid, $selector);
											UTIL::cli_print("\t\t*** " . $responce['added'] . " $hostname ***");
											$responce['found'][] = $hostname;
											break;
										}
									}
								}
							}
							catch(Exception $e) {
								// Error is already logged.
								// Only timeout errors should get caught here.
							}
						}
					}				
				}
			}
		}
		else {
			$responce['error'] = "Missing selector List";
		}
		return $responce;
	}
	
	###############################################################################

	public static function checkIPAddressInDNSBL($ip, $dnsbl_lookup_array=null)
	{
		if($dnsbl_lookup_array == null) {
			$dnsbl_lookup_array = array(
				"all.s5h.net", "b.barracudacentral.org", "bl.spamcop.net", "blacklist.woody.ch", "bogons.cymru.com",
				"cbl.abuseat.org", "cdl.anti-spam.org.cn", "combined.abuse.ch", "db.wpbl.info", "dnsbl-1.uceprotect.net",
				"dnsbl-2.uceprotect.net", "dnsbl-3.uceprotect.net", "dnsbl.anticaptcha.net", "dnsbl.cyberlogic.net",
				"dnsbl.dronebl.org", "dnsbl.inps.de", "dnsbl.sorbs.net", "drone.abuse.ch",
				"duinv.aupads.org", "dul.dnsbl.sorbs.net", "dyna.spamrats.com", "dynip.rothen.com",
				"exitnodes.tor.dnsbl.sectoor.de", "http.dnsbl.sorbs.net", "ips.backscatterer.org",
				"ix.dnsbl.manitu.net", "korea.services.net", "misc.dnsbl.sorbs.net", "noptr.spamrats.com",
				"orvedb.aupads.org", "pbl.spamhaus.org", "proxy.bl.gweep.ca", "psbl.surriel.com",
				"relays.bl.gweep.ca", "relays.nether.net", "sbl.spamhaus.org", "short.rbl.jp",
				"singular.ttk.pte.hu", "smtp.dnsbl.sorbs.net", "socks.dnsbl.sorbs.net", "spam.abuse.ch",
				"spam.dnsbl.anonmails.de", "spam.dnsbl.sorbs.net", "spam.spamrats.com", "spambot.bls.digibase.ca",
				"spamrbl.imp.ch", "spamsources.fabel.dk", "ubl.lashback.com", "ubl.unsubscore.com", "virus.rbl.jp",
				"web.dnsbl.sorbs.net", "wormrbl.imp.ch", "xbl.spamhaus.org", "z.mailspike.net", "zen.spamhaus.org",
				"zombie.dnsbl.sorbs.net", "bl.nszones.com",
				//"dnsbl.spfbl.net",
				);
		}
		$listed = array();
		UTIL::debug_cli_print("DSBL: $ip");
		if ($ip) {
			$reverse_ip = implode(".", array_reverse(explode(".", $ip)));
			foreach ($dnsbl_lookup_array as $host) {
				if (checkdnsrr($reverse_ip . "." . $host . ".", "A")) {
					$listed[] = $host;
				}
			}
		}
		UTIL::debug_cli_print($listed);
		return $listed;
	}
}

###############################################################################
