#!/usr/bin/php -q
<?php
mb_internal_encoding("UTF-8");
set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);
$config = array();
$__server_listening = true;
$thread_list=array();
error_reporting(-1);
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
ini_set('log_errors', true);
ini_set('error_log', '/var/log/nids.log');
date_default_timezone_set("Europe/Paris");
//-----------------------------------------------------------------------------
class Thread { //class permettant de créer différentes thread
    const FUNCTION_NOT_CALLABLE     = 10;
    const COULD_NOT_FORK            = 15;
    private $errors = array(
        Thread::FUNCTION_NOT_CALLABLE   => 'ERROR : No valid function given to THREAD function...',
        Thread::COULD_NOT_FORK          => 'ERROR : Fork process failed...',
    );
    protected $runnable;
    private $pid;
	//--------------------------------------------
    public static function available() {
        $required_functions = array(
            'pcntl_fork',
        );
        
        foreach( $required_functions as $function ) {
            if ( !function_exists( $function ) ) {
                return false;
            }
        }
        
        return true;
    }
	//-------------------------------------------------
    public function __construct( $_runnable = null ) {
    	if( $_runnable !== null ) {
        	$this->setRunnable( $_runnable );
    	}
    }
    //-------------------------------------------------
    public function setRunnable( $_runnable ) {
        if( self::runnableOk( $_runnable ) ) {
            $this->runnable = $_runnable;
        }
        else {
            throw new Exception( $this->getError( Thread::FUNCTION_NOT_CALLABLE ), Thread::FUNCTION_NOT_CALLABLE );
        }
    }
	//-------------------------------------------------
    public function getRunnable() {
        return $this->runnable;
    }
	//--------------------------------------------------
    public static function runnableOk( $_runnable ) {
        return ( function_exists( $_runnable ) && is_callable( $_runnable ) );
    }
	//--------------------------------------------------
    public function getPid() {
        return $this->pid;
    }
	//---------------------------------------------------
    public function isAlive() {
        $pid = pcntl_waitpid( $this->pid, $status, WNOHANG );
        return ( $pid === 0 );
        
    }
	//---------------------------------------------------------
    public function start() {
        $pid = @ pcntl_fork();
        if( $pid == -1 ) {
            throw new Exception( $this->getError( Thread::COULD_NOT_FORK ), Thread::COULD_NOT_FORK );
        }
        if( $pid ) {
            $this->pid = $pid;
        }
        else {
            pcntl_signal( SIGTERM, array( $this, 'signalHandler' ) );
            $arguments = func_get_args();
            if ( !empty( $arguments ) ) {
                call_user_func_array( $this->runnable, $arguments );
            }
            else {
                call_user_func( $this->runnable );
            }
            
            exit( 0 );
        }
    }
	//------------------------------------------------------------------------
    public function stop( $_signal = SIGTERM, $_wait = false ) {
        if( $this->isAlive() ) {
            posix_kill( $this->pid, $_signal );
            if( $_wait ) {
                pcntl_waitpid( $this->pid, $status = 0 );
            }
        }
    }
	//------------------------------------------------------------------------
    public function kill( $_signal = SIGKILL, $_wait = false ) {
        posix_kill( $this->pid, $_signal );
    }
    //------------------------------------------------------------------------
    public function getError( $_code ) {
        if ( isset( $this->errors[$_code] ) ) {
            return $this->errors[$_code];
        }
        else {
            return "error " . $_code;
        }
    }
	//--------------------------------------------------------------------------
    protected function signalHandler( $_signal ) {
        switch( $_signal ) {
            case SIGTERM:
                echo "SIGTERM via TREAD";
				exit( 0 );
            break;
        }
    }
	//---------------------------------------------------------------------------
}
//-----------------------------------------------------------------------------
function get_option ($option) { // obtenir la valeur d'une option depuis mysql
	global $config;
	global $connbdd;
	if ($result = exec_mysql("SELECT value FROM options WHERE name='".$option."'")) {
		$val = mysqli_fetch_array($result);
		return $val['value'];
	}
	else {
		return -2;
	}
}
//------------------------------------------------------------------------------
function readconfig_master() { // lit de fichier de configuration
	global $config;
	global $connbdd;
	$reader = new XMLReader(); 
	$reader->open("/etc/nids/nids.conf"); 
	while ($reader->read()) { 
		switch ($reader->nodeType)  { 
			case (XMLREADER::ELEMENT): 
				if ($reader->name == "security_key") { $reader->read(); $config['config_security_key'] = trim( $reader->value ); break; }
				if ($reader->name == "master_ip") { $reader->read(); $config['master_ip'] = trim( $reader->value ); break; }
				if ($reader->name == "master_port") { $reader->read(); $config['master_port'] = trim( $reader->value ); break; }
				if ($reader->name == "config_mysql_host") { $reader->read(); $config['config_mysql_host'] = trim( $reader->value ); break; }
				if ($reader->name == "config_mysql_user") { $reader->read(); $config['config_mysql_user'] = trim( $reader->value ); break; }
				if ($reader->name == "config_mysql_password") { $reader->read(); $config['config_mysql_password'] = trim( $reader->value ); break; }
				if ($reader->name == "config_mysql_db") { $reader->read(); $config['config_mysql_db'] = trim( $reader->value ); break; }
				if ($reader->name == "theme") { $reader->read(); $config['theme'] = trim( $reader->value ); break; }
				if ($reader->name == "debug") { $reader->read(); $config['debug'] = trim( $reader->value ); break; }
		}
	}
}
//-----------------------------------------------------------------------------
function GenerationCle($Texte,$CleDEncryptage) { // génère la cle de cryptage pour la comunication des daemons
	global $config;
	global $connbdd;
  $CleDEncryptage = md5($CleDEncryptage); 
  $Compteur=0; 
  $VariableTemp = ""; 
  for ($Ctr=0;$Ctr<strlen($Texte);$Ctr++) 
    { 
    if ($Compteur==strlen($CleDEncryptage))
      $Compteur=0; 
    $VariableTemp.= substr($Texte,$Ctr,1) ^ substr($CleDEncryptage,$Compteur,1); 
    $Compteur++; 
    } 
  return $VariableTemp; 
  }
//----------------------------------------------------------------------------  
function Crypte($Texte) { // crypte les infos de communications des daemons avec la clé
	global $config;
	global $connbdd;
  $Cle = $config['config_security_key'];
  srand((double)microtime()*1000000); 
  $CleDEncryptage = md5(rand(0,32000) ); 
  $Compteur=0; 
  $VariableTemp = ""; 
  for ($Ctr=0;$Ctr<strlen($Texte);$Ctr++) 
    { 
    if ($Compteur==strlen($CleDEncryptage)) 
      $Compteur=0; 
    $VariableTemp.= substr($CleDEncryptage,$Compteur,1).(substr($Texte,$Ctr,1) ^ substr($CleDEncryptage,$Compteur,1) ); 
    $Compteur++;
    } 
  return base64_encode(GenerationCle($VariableTemp,$Cle) );
  }
//----------------------------------------------------------------------------
function Decrypte($Texte) { // decrypte les infos de communications des daemons avec la clé
	global $config;
	global $connbdd;
  $Cle = $config['config_security_key'];
  $Texte = GenerationCle(base64_decode($Texte),$Cle);
  $VariableTemp = ""; 
  for ($Ctr=0;$Ctr<strlen($Texte);$Ctr++) 
    { 
    $md5 = substr($Texte,$Ctr,1); 
    $Ctr++; 
    $VariableTemp.= (substr($Texte,$Ctr,1) ^ $md5); 
    } 
  return $VariableTemp; 
  }
//------------------------------------------------------------------------------------
function myexec($commande) {
	global $config;
	global $connbdd;
	exec ($commande, $result, $return);
	if ($return > 0) logme ("EXEC : ".$commande." -> ".$return);
	return $result;
}
//------------------------------------------------------------------------------------
function logme ($data) {
	file_put_contents("/var/log/nids.log", date("Y-m-d H:i:s ").$data."\n", FILE_APPEND);
}
//------------------------------------------------------------
function connect_mysql () {
    global $config;
	global $connbdd;
	$connbdd = mysqli_connect ($config['config_mysql_host'], $config['config_mysql_user'], $config['config_mysql_password'], $config['config_mysql_db']);
	if (mysqli_connect_errno())
	{
		logme("Failed to connect to MySQL: " . mysqli_connect_error());
		return FALSE;
	}
    return TRUE;
}
//-----------------------------------------------------------
function verif_mysql () {
    global $config;
	global $connbdd;
	if (!isset($connbdd)) connect_mysql();
	if (!($result = @mysqli_query($connbdd, "SET NAMES 'utf8'"))) {
		logme("Verif MySQL ERROR try to reconnect : " . mysqli_error($connbdd));
		connect_mysql();
		if (!($result = @mysqli_query($connbdd, "SET NAMES 'utf8'"))) {
			logme("Failed to connect to MySQL: " . mysqli_connect_error());
			return FALSE;
		}
		else {
			return TRUE;
		}
	}
    return TRUE;
}
//------------------------------------------------------------
function become_daemon() { 

	$pid = pcntl_fork(); 
	if ($pid == -1) { 
		exit(); 
    }
	elseif ($pid) { 
		exit(); 
    }
	else { 
		posix_setsid(); 
		chdir('/'); 
		umask(0); 
		return posix_getpid(); 
    } 
}
//-----------------------------------------------------------
function sig_handler($sig) { 
	global $thread_task;
	global $proc;
    switch($sig) { 
        case SIGTERM:
			logme ("MAIN : Shutdown via SIGTERM...");
			foreach ($thread_task AS $key=>$value) {
				$value->stop();
			}
			sleep (1);
			logme ("MAIN : Shutdown Complete turn OFF...");
			exit(0);
		break;
        case SIGINT:
			logme ("MAIN : Shutdown via SIGKILL...");
			foreach ($thread_task AS $key=>$value) {
				$value->stop();
			}
			sleep (1);
			logme ("MAIN : Shutdown Complete turn OFF...");
			exit(0);
        break; 		
    } 
}
//-----------------------------------------------------------
function sig_handler_thread($sig) { 
	global $proc;
    switch($sig) { 
        case SIGTERM:
			logme ("MAIN : thread received SIGTERM");
			if (is_resource($proc)) {
				proc_terminate($proc, 15);
			}
			exit(0);
		break;
        case SIGINT:
			logme ("MAIN : thread received SIGINT");
			if (is_resource($proc)) {
				proc_terminate($proc, 15);
			}
			exit(0);
        break; 
    } 
}
//--------------------------------------------------------------------
function thread_conntrack() {
	global $proc;
	global $config;
	pcntl_signal(SIGTERM, 'sig_handler_thread'); 
	pcntl_signal(SIGINT, 'sig_handler_thread');
	pcntl_signal(SIGCHLD, SIG_IGN);
	logme("CONNTRACK : start ...");
	$descriptorspec = array(
       0 => array("pipe", "r"),   // stdin
       1 => array("pipe", "w"),  // stdout
       2 => array("pipe", "w")   // stderr
	);
	$ip_vip_list=get_ip_vip_array();
	$ip_vip_list_last_update=time();
	$conntrack_rules=get_conntrack_rules();
	$conntrack_rules_last_update=time();
	if ($proc = proc_open("exec conntrack -E -e NEW", $descriptorspec, $pipes, null, null)) {
		while ($proc) {
			$proc_status = proc_get_status ( $proc );
			if (!$proc_status['running']) break;
			$read   = array($pipes[1]);
			$write  = NULL;
			$except = NULL;
			if (@stream_select($read, $write, $except, 0, 100000) > 0) { 
				$line = fgets($pipes[1]);
				preg_match("/^\s+\[NEW\]\s(?P<proto>[A-Za-z]+)\s+.*src\=(?P<sip>[0-9\.]+)\sdst\=(?P<dip>[0-9\.]+)\ssport\=(?P<sport>[0-9]+)\sdport\=(?P<dport>[0-9\.]+)\s\[.*$/", $line, $match);
				if ((isset($match['proto'])) AND (isset($match['sip'])) AND (isset($match['dip'])) AND (isset($match['dport'])) AND (isset($match['sport'])) AND ($match['sip']!="127.0.0.1")) {
					$match['proto'] = strtoupper($match['proto']);
					$is_match_score=0;
					foreach ($conntrack_rules[$match['proto']] AS $k=>$v) {
						$t=explode("-", $v['dport']);
						if (isset($t[1])) { //si multiport
							if (($match['dport'] >= $t[0]) AND ($match['dport'] <= $t[1]) AND (($v['dip']=="") OR ($v['dip']==$match['dip']))) {
								$is_match_score=$is_match_score+$v['score'];
							}
						}
						else { //si monoport
							if ($match['dport']==$t[0] AND (($v['dip']=="") OR ($v['dip']==$match['dip']))) {
								$is_match_score=$is_match_score+$v['score'];
							}
						}
					}
					if ($is_match_score > 0) { // si score ajouter score
						if (ip_is_vip($match['sip'])) { // si VIP
							$is_match_score=0;
						}
						else { // si pas VIP
							add_score_to_ip($match['sip'], $is_match_score);
							logme("CONNTRACK : ".$match['sip']." try to connect to ".$match['proto']." ".$match['dport']." : add score ".$is_match_score);
						}
						exec_mysql("INSERT INTO conntrack_log SET proto='".$match['proto']."', sip='".$match['sip']."', dip='".$match['dip']."', dport='".$match['dport']."', addscore='".$is_match_score."'");
					}
				}
				if (($ip_vip_list_last_update+10) < time()) { //refresh cache
					$ip_vip_list=get_ip_vip_array();
					$ip_vip_list_last_update=time();
				}
				if (($conntrack_rules_last_update+10) < time()) { //refresh cache
					$conntrack_rules=get_conntrack_rules();
					$conntrack_rules_last_update=time();
				}
			}
		}
	}
	logme("CONNTRACK : conntrack is stopped !");
	proc_terminate($proc, 15);
	sleep (1);
}
//--------------------------------------------------------------------
function get_conntrack_rules() {
	global $config;
	global $connbdd;
	$conntrack_rules=array();
	$conntrack_rules['UDP']=array();
	$conntrack_rules['TCP']=array();
	$result=exec_mysql("SELECT * FROM conntrack_rules");
	while ($val = mysqli_fetch_array($result)) {
		if ($val['proto']=="TCP-UDP") {
			$conntrack_rules['TCP'][$val['id']]=array();
			$conntrack_rules['TCP'][$val['id']]['dport']=$val['dport'];
			$conntrack_rules['TCP'][$val['id']]['score']=$val['score'];
			$conntrack_rules['TCP'][$val['id']]['dip']=$val['dip'];
			$conntrack_rules['UDP'][$val['id']]=array();
			$conntrack_rules['UDP'][$val['id']]['dport']=$val['dport'];
			$conntrack_rules['UDP'][$val['id']]['score']=$val['score'];
			$conntrack_rules['UDP'][$val['id']]['dip']=$val['dip'];
		}
		else {
			$conntrack_rules[$val['proto']][$val['id']]=array();
			$conntrack_rules[$val['proto']][$val['id']]['dport']=$val['dport'];
			$conntrack_rules[$val['proto']][$val['id']]['score']=$val['score'];
			$conntrack_rules[$val['proto']][$val['id']]['dip']=$val['dip'];
		}
	}
	return $conntrack_rules;
}
//--------------------------------------------------------------------
function read_suricata_alert_log() {
	global $proc;
	global $config;
	pcntl_signal(SIGTERM, 'sig_handler_thread'); 
	pcntl_signal(SIGINT, 'sig_handler_thread');
	pcntl_signal(SIGCHLD, SIG_IGN);
	logme("ALERT_LOG : read suricata alert log is started ...");
	$descriptorspec = array(
       0 => array("pipe", "r"),   // stdin
       1 => array("pipe", "w"),  // stdout
       2 => array("pipe", "w")   // stderr
	);
	$ip_vip_list=get_ip_vip_array();
	$ip_vip_list_last_update=time();
	if ($proc = proc_open("exec tail -f /var/log/suricata/fast.log -n 0", $descriptorspec, $pipes, null, null)) {
		while ($proc) {
			$proc_status = proc_get_status ( $proc );
			if (!$proc_status['running']) break;
			$read   = array($pipes[1]);
			$write  = NULL;
			$except = NULL;
			if (@stream_select($read, $write, $except, 0, 100000) > 0) { 
				$line = fgets($pipes[1]);
				preg_match("/^(?P<datetime>(\d{2})\/(\d{2})\/(\d{4})-(\d{2}):(\d{2}):(\d{2}))\.(\d{6})\s+\[.*\]\s\[(?P<gid>\d+)\:(?P<sid>\d+)\:(?P<rev>\d+)\].*(\[Priority:\s)(?P<prio>\d+)\]\s(\{)(?P<proto>[A-Za-z]+)(\})\s(?P<sip>[0-9\.]+)\:(?P<sport>\d+)\s\-\>\s(?P<dip>[0-9\.]+)\:(?P<dport>\d+)$/", $line, $match);
				if (isset($match['sip']) AND ($match['sip']!="127.0.0.1")) {
					$log_date = date ("Y-m-d H:i:s", strtotime(str_replace("-", " ", $match['datetime'])));					
					exec_mysql("INSERT INTO suricata_log SET date='".$log_date."', gid='".$match['gid']."', sid='".$match['sid']."', rev='".$match['rev']."', prio='".$match['prio']."', proto='".$match['proto']."', sip='".$match['sip']."', sport='".$match['sport']."', dip='".$match['dip']."', dport='".$match['dport']."', action='alert'");
					$result=exec_mysql("SELECT score FROM rules WHERE sid='".$match['sid']."' LIMIT 1");
					if (!ip_is_vip($match['sip'])) {
						if ($result) {
							$val = mysqli_fetch_array($result);
							if (isset($val['score']) AND $val['score'] > 0) {
								add_score_to_ip($match['sip'], $val['score']);
							}
						}
					}
				}
				if (($ip_vip_list_last_update+10) < time()) { //refresh cache
					$ip_vip_list=get_ip_vip_array();
					$ip_vip_list_last_update=time();
				}
			}
		}
	}
	logme("ALERT_LOG : read suricata alert log is stopped !");
	proc_terminate($proc, 15);
	sleep (1);
}
//--------------------------------------------------------------------
function add_score_to_ip($ip, $score) {
	global $config;
	if (isset($ip) AND isset($score) AND (!ip_is_vip($ip))) {
		exec_mysql("INSERT INTO ip SET score=score+".$score.", ip='".$ip."' ON DUPLICATE KEY UPDATE score=score+".$score);
		logme("SCORE : ".$ip." +".$score);
		$result=exec_mysql("SELECT score FROM ip WHERE ip='".$ip."' LIMIT 1;");
		$val = mysqli_fetch_array($result);
		if (isset($val['score']) AND $val['score'] > 998) {
			ban_ip($ip, "score become so high");
		}
	}
}
//----------------------------------------------------------------------
function ip_is_vip($ip) {
	global $config;
	$timer=apc_fetch("ip_vip_list_timer");
	if ($timer+30 > time()) { //verifie si le cache est a jour
		$ip_vip_list=json_decode(apc_fetch("ip_vip_list"));
	}
	else { //mise a jour du cache
		$ip_vip_list=get_ip_vip_array();
		apc_store("ip_vip_list",json_encode($ip_vip_list));
		$timer=time();
		apc_store("ip_vip_list_timer",$timer);
	}
	$seek=FALSE;
	foreach ($ip_vip_list AS $k=>$v) {
		if ($ip==$v) $seek=TRUE;
	}
	if ($seek) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}
//--------------------------------------------------------------------
function ban_ip($ip, $reason="unknow") {
	//$t=explode(".", $ip);
	//$f=$t[0];
	//if (system("/sbin/iptables -t raw -C ip".$f." -s ".$ip." -j DROP 2>&1") == "iptables: Bad rule (does a matching rule exist in that chain?).") {
	//	system("/sbin/iptables -t raw -I ip".$f." -s ".$ip." -j DROP 2>&1");
	//}
	system ("ipset add blacklist ".$ip." 2>&1");
	$t=json_decode(apc_fetch("ip_state_kill"));
	if (!is_array($t)) $t=array();
	array_push($t, $ip);
	apc_store("ip_state_kill", json_encode($t));
}
//--------------------------------------------------------------------
function unban_ip($ip) {
	global $config;
	global $connbdd;
	exec_mysql("UPDATE ip SET score='0' WHERE ip='".$ip."'");
	system("ipset del blacklist ".$ip." 2>&1");
}
//-----------------------------------------------------------------------------
function server_loop() { //boucle d'attente de nouveaux clients
    GLOBAL $__server_listening;
	GLOBAL $thread_list;
	GLOBAL $config;
	global $connbdd;
	connect_mysql();
	
	pcntl_signal(SIGTERM, 'sig_handler_thread'); 
	pcntl_signal(SIGINT, 'sig_handler_thread');
	pcntl_signal(SIGCHLD, SIG_IGN);
    
	if(($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) 
    { 
        logme ("LISTEN : failed to create socket: ".socket_strerror($sock)); 
        exit(); 
    } 
	socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
    if(($ret = socket_bind($sock, $config['master_ip'], $config['master_port'])) < 0) 
    { 
        logme ("LISTEN : failed to bind socket: ".socket_strerror($ret)); 
        exit(); 
    } 

    if( ( $ret = socket_listen( $sock, 0 ) ) < 0 ) 
    { 
        logme ("LISTEN : failed to listen to socket: ".socket_strerror($ret)); 
        exit(); 
    } 

    
    
    logme ("LISTEN : waiting for clients to connect"); 

    while ($__server_listening) 
    { 
		//---------------Gestion des Threads par le processus maitre---------------------		
		$oldErrorLevel = error_reporting(0);
		error_reporting( $oldErrorLevel & ~E_WARNING );
		foreach ($thread_list AS $child_key=>$child_pid) {
			if ((pcntl_getpriority($child_pid)) === FALSE) {
				unset ($thread_list[$child_key]);
			}
		}
		error_reporting( $oldErrorLevel);
		//--------------------------------------------------------------------------------
		$write = NULL;
		$except = NULL;
		$tv_sec = 1;
		$sock_tab = array(1 => $sock);
		$ready = @socket_select($sock_tab, $write, $except, $tv_sec);
		if ($ready > 0) {
		$connection = @socket_accept($sock_tab[1]); 
        if ($connection === false) 
        { 
			continue;
        }elseif ($connection > 0) 
        { 
			handle_client($sock_tab[1], $connection); 
        }else 
        { 
            logme ("LISTEN : error: ".socket_strerror($connection)); 
            die; 
        }
		}
    } 
}
//---------------------------------------------------------------------------------------------------
function handle_client($ssock, $csock) { //creation d'une thread pour un nouveau client connecté
    GLOBAL $__server_listening; 
	GLOBAL $thread_list;
	global $config;
	global $connbdd;
	
    $pid = pcntl_fork(); 
    if ($pid == -1) 
    { 
        /* fork failed */ 
        logme ("MASTER SERVER : fork failure! : DIE"); 
        die; 
    }elseif ($pid == 0) 
    { 
        /* child process */
        $__server_listening = false; 
        socket_close($ssock); 
        interact($csock); 
        socket_close($csock);
    }else 
    { 
		array_push($thread_list, $pid);
		socket_close($csock); 
    } 
}
//---------------------------------------------------------------------------------------------------
function interact($socket) { //Gestion des commandes des clients
	global $config;
	global $connbdd;
	connect_mysql();
	$count_timeout=0;
	socket_getpeername($socket, $ip);
	socket_write($socket, Crypte("welcome to NIDS\n"));
	while ($count_timeout < 10) 
    { 
		$write = NULL;
		$except = NULL;
		$tv_sec = 1;
		$sock_tab[1]=$socket;
		$ready = socket_select($sock_tab, $write, $except, $tv_sec);
		$data = @socket_read($socket, 4096, PHP_NORMAL_READ);
		$data = trim($data);
					$data = Decrypte($data);

					if(!empty($data))
						{
						logme($data);
						$command = explode (" ", $data);
						$count_timeout=0;		
							switch ($command[0]) {
							case "reload_ids":
								generate_sid_msg_map();
								suricata_reload_rules();
								socket_write($socket, Crypte("0")."\n");
							break;
							case "unban_ip":
								if (isset($command[1])) {
									unban_ip($command[1]);
								}
								socket_write($socket, Crypte("0")."\n");
							break;
							case "score_add":
								if (isset($command[1])) {
									$ip=$command[1];
									if (isset($command[2])) {
										$score=$command[2];
									}
									else $score=999;
									if (isset($command[3])) $jail=$command[3]; else $jail="";
									exec_mysql("INSERT INTO fail2ban_log SET ip='".$ip."', score='".$score."', jail='".$jail."'");
									add_score_to_ip($ip, $score);
								}
								socket_write($socket, Crypte("0")."\n");
							break;
							default:
								socket_write($socket, Crypte("NOK")."\n");
								logme ("FROM ".$ip." : " . $data . " : Unkonw command Closing connection");
								exit();
							break;
							}
						}
						else {
							$count_timeout++;
						}
	}
exit(0);
}
//----------------------------------------------------------------------------------------------------
function load_firewall() {
	global $config;
	global $connbdd;
	logme ("FIREWALL : configuring iptables...");
	myexec("echo '1' > /proc/sys/net/bridge/bridge-nf-call-iptables");
	//myexec ("iptables -t filter -m physdev --physdev-is-bridged --physdev-in eth2 --physdev-out eth1 -I FORWARD -j ACCEPT");
	myexec ("ebtables -F");
	myexec ("ebtables -t broute -F");
	myexec ("ebtables -I FORWARD -p IPv6 -j DROP"); // bloc ipv6 forward traffic
	myexec ("ebtables -t broute -A BROUTING -p 802_1Q -j DROP"); // bloc VLAN tagged trafic on Bridge forward
	myexec ("iptables -t raw -F");
	myexec ("iptables -t raw -X");
	myexec ("iptables -t raw -N conntrack_ignore");
	myexec ("iptables -t raw -A conntrack_ignore -j RETURN");
	myexec ("iptables -t raw -N firewall");
	myexec ("iptables -t raw -A firewall -j RETURN");
	//myexec ("ipset destroy blacklist");
	//myexec ("ipset destroy network_blacklist");
	myexec ("ipset create blacklist hash:ip hashsize 4096 2>&1");
	myexec ("ipset create network_blacklist hash:net hashsize 4096 2>&1");
	myexec ("iptables -t raw -I PREROUTING -m set --match-set blacklist src -j DROP");
	myexec ("iptables -t raw -I PREROUTING -m set --match-set network_blacklist src -j DROP");
	//for ($a = 1;$a <= 254; $a++) {
	//	iptables_create_chain("ip".($a), "raw");
	//	if (system("/sbin/iptables -t raw -C PREROUTING -s ".$a.".0.0.0/8 -j ip".$a." 2>&1") == "iptables: No chain/target/match by that name.") {
	//		system("/sbin/iptables -t raw -I PREROUTING -s ".$a.".0.0.0/8 -j ip".$a);
	//	}
	//}
	myexec ("iptables -t raw -I PREROUTING -j firewall");
	myexec ("iptables -t raw -A PREROUTING -j conntrack_ignore");
	myexec ("iptables -t raw -A PREROUTING ! -s 127.0.0.1 -j NFQUEUE --queue-bypass");
	logme ("FIREWALL : iptables is config");
	load_firewall_rules();
	load_conntrack_ignore_rules();
	load_ipset_blacklist();
	load_ipset_network_blacklist();
}
//--------------------------------------------------------------------------------------
function load_conntrack_ignore_rules() {
	global $config;
	logme ("FIREWALL : Load CONNTRACK ignore rules...");
	$result = exec_mysql("SELECT * FROM conntrack_ignore");
	while ($val = mysqli_fetch_array($result)) {
		$rule="iptables -t raw -I conntrack_ignore ";
		if (($val['proto']=="tcp") OR ($val['proto']=="udp")) {
			$rule=$rule."-p ".$val['proto']." ";
			if ($val['dport']!="") {
				$rule=$rule."--dport ".$val['dport']." ";
			}
		}
		if ($val['dip']!="") {
			$rule=$rule."-d ".$val['dip']." ";
		}
		$rule=$rule."-j NOTRACK";
		myexec ($rule);
	}
	logme ("FIREWALL : Load CONNTRACK ignore rules done !");
}
//--------------------------------------------------------------------------------------
function load_firewall_rules() {
	global $config;
	logme ("FIREWALL : Load FIRERWAL rules...");
	$result = exec_mysql("SELECT * FROM firewall");
	while ($val = mysqli_fetch_array($result)) {
		$rule="iptables -t raw -I firewall ";
		if (($val['proto']=="tcp") OR ($val['proto']=="udp")) {
			$rule=$rule."-p ".$val['proto']." ";
			if ($val['dport']!="") {
				$rule=$rule."--dport ".$val['dport']." ";
			}
		}
		if ($val['dip']!="") {
			$rule=$rule."-d ".$val['dip']." ";
		}
		$rule=$rule."-j DROP";
		myexec ($rule);
	}
	logme ("FIREWALL : Load FIRERWAL rules is done !");
}
//--------------------------------------------------------------------------------------
function load_ipset_network_blacklist() {
	global $config;
	logme ("FIREWALL : Load NETWORK Blacklist");
	$net_already_drop=get_array_ipset_network_blacklist();
	$mstart=time();
	$n=0;
	$result = exec_mysql("SELECT * FROM network_drop");
	while ($val = mysqli_fetch_array($result)) {
		if (!isset($net_already_drop[$val['network_cdir']])) {
			ban_network_cdir($val['network_cdir']);
			$n++;
		}
	}
	$mstop=time();
	$m=$mstop-$mstart;
	logme ("FIREWALL : NETWORK Blacklist Loaded ".$n." CDIR NETWORK in ".$m." seconds ...");
}
//--------------------------------------------------------------------------------------
function get_array_ipset_network_blacklist() {
	global $config;
	$n=0;
	$ip=array();
	$r=myexec("ipset list network_blacklist");
	foreach ($r AS $k=>$v) {
		$l = rtrim ( $v , "\n" );
		$n++;
		$ip[$l]="";
	}
	logme("READ network blacklist : ".$n." cdir network already blacklist");
	return $ip;
}
//--------------------------------------------------------------------------------------
function ban_network_cdir($net) {
	myexec("ipset add network_blacklist ".$net);
}
//--------------------------------------------------------------------------------------
function load_ipset_blacklist() {
	global $config;
	logme ("FIREWALL : Load IP Blacklist");
	$ip_already_drop=get_array_ipset_blacklist();
	$mstart=time();
	$n=0;
	$result = exec_mysql("SELECT ip FROM ip WHERE score > 998");
	while ($val = mysqli_fetch_array($result)) {
		if (!isset($ip_already_drop[$val['ip']])) {
			ban_ip($val['ip']);
			$n++;
		}
	}
	$mstop=time();
	$m=$mstop-$mstart;
	logme ("FIREWALL : IP Blacklist Loaded ".$n." IP in ".$m." seconds ...");
}
//--------------------------------------------------------------------------------------
function iptables_create_chain($name, $table) {
	system("/sbin/iptables -t ".$table." -N ".$name." 2>&1");
	if (system("/sbin/iptables -t ".$table." -C ".$name." -j RETURN 2>&1") == "iptables: Bad rule (does a matching rule exist in that chain?).") {
		system("/sbin/iptables -t ".$table." -A ".$name." -j RETURN 2>&1");
	}
}
//-----------------------------------------------------------------------------------
function exec_mysql($query) {
	global $config;
	global $connbdd;
	if (!is_resource($connbdd)) {
		connect_mysql();
	}
	if (!($result = @mysqli_query($connbdd, $query))) {
		logme("EXEC_MYSQL ERROR (".$query.") : " . @mysqli_error($connbdd));
		if (verif_mysql()) {
			if (!($result = @mysqli_query($connbdd, $query))) {
				logme("EXEC_MYSQL TWICE ERROR (".$query.") : " . @mysqli_error($connbdd));
			}
			else {
				return $result;
			}
		}
		else {
			logme("MYSQL VERIF ERROR : connexion impossible");
			return FALSE;
		}
	}
	else {
		return $result;
	}
}
//-------------------------------------------------------------------
function get_option_from_mysql($name) {
	global $config;
	global $connbdd;
	$query = "SELECT value FROM options WHERE name='".$name."'";
	$result = exec_mysql($query);
	$val = mysqli_fetch_array($result);
	return $val['value'];
}
//-------------------------------------------------------------------
function suricata_reload_rules() {
	global $config;
	global $connbdd;
	$r = suricata_status();
	if ( $r > 0) {
		logme("SURICATA : reload rules ...");
		passthru("kill -USR2 ".$r);
		return TRUE;
	}
	return FALSE;
}
//-------------------------------------------------------------------
function disable_net_offload() {
	global $config;
	passthru("ethtool --offload eth0 tx off tso off sg off ufo off gso off gro off 2>&1");	
}
//-------------------------------------------------------------------
function suricata_stop() {
	global $config;
	$timeout=10;
	$c=0;
	while ($c < $timeout) {
		$c++;
		$r = suricata_status();
		if ($r > 0) {
			logme("SURICATA : is running go stopping ...");
			passthru("pkill -SIGTERM -f -x \"suricata -c /etc/suricata/suricata.yaml -q 0 -D\"");
			sleep(1);
		}
		else {
			logme("SURICATA : is stopped");
			return TRUE;
		}
	}
	return FALSE;
}
//-------------------------------------------------------------------
function suricata_start() {
	global $config;
	if (suricata_stop()) {
		logme("SURICATA : Try to start it ...");
		passthru("suricata -c /etc/suricata/suricata.yaml -q 0 -D");
		sleep(1);
		$r = suricata_status();
		if ($r > 0) {
			logme("SURICATA : started pid ".$r);
			return TRUE;
		}
		else {
			logme("SURICATA : start failed !!!");
			return FALSE;
		}
	}
	else {
		logme("SURICATA : is not stopped so he can t start !");
		return FALSE;
	}
}
//--------------------------------------------------------------------
function suricata_status() {
	global $config;
	$r=system("pgrep -f -x \"suricata -c /etc/suricata/suricata.yaml -q 0 -D\"");
	if (isset($r) AND $r > 0) {
		return $r;
	}
	return 0;
}
//--------------------------------------------------------------------
function get_ip_vip_array() {
	global $config;
	global $connbdd;
	$ip_vip_array=array();
	$result = exec_mysql("SELECT ip FROM ip_vip");
	while ($val = mysqli_fetch_array($result)) {
		array_push($ip_vip_array, $val['ip']);
	}
	return $ip_vip_array;
}
//-------------------------------------------------------------------
function update_rules() {
	global $config;
	if (load_spooler_file_ruleset() > 0) {
		generate_sid_msg_map();
		suricata_reload_rules();
	}
}
//-------------------------------------------------------------------
function thread_auto_update_rules() {
	global $config;
	$last_update=0;
	sleep(60); //delay thread at start
	while (TRUE) {
		if ($last_update + 3600 < time()) {
			logme("START UPDATE RULES ....");
			update_rules();
			logme("UPDATE RULES DONE !");
			$last_update=time();
		}
		sleep(60);
	}
}
//-------------------------------------------------------------------
function load_to_mysql_rules_file($filename) {
	global $proc;
	global $config;
	global $connbdd;
	$start_time=time();
	if (!isset($filename)) return 0;
	connect_mysql ();
	$nrules=0;
	$handle = @fopen($filename, "r");
	if ($handle) {
		while (($line = fgets($handle, 4096)) !== false) {
			preg_match("/.*\(msg\:\"(?P<msg>[^\"]*)\"\;.*\sclasstype\:(?P<classtype>[^\;]*)\;.*sid\:(?P<sid>.*)\;\srev\:(?P<rev>.*)\;\).*/", $line, $match);
			if (isset($match['msg']) AND isset($match['sid']) AND isset($match['rev'])) {
				if (!isset($match['classtype'])) $classtype="";
				else $classtype=$match['classtype'];
				$line=ltrim ($line,"#");
				$line=ltrim ($line," ");
				$line = mysqli_real_escape_string($connbdd, htmlspecialchars($line));
				$msg = mysqli_real_escape_string($connbdd, htmlspecialchars($match['msg']));
				exec_mysql("INSERT INTO rules SET sid='".$match['sid']."', rev='".$match['rev']."', msg='".$msg."', classtype='".$classtype."', rule='".$line."', action='alert' ON DUPLICATE KEY UPDATE rev='".$match['rev']."', msg='".$msg."', classtype='".$classtype."', rule='".$line."', action='alert'");
				$nrules++;
			}
		}
		if (!feof($handle)) {
			echo "Erreur: fgets() a échoué\n";
		}
		fclose($handle);
    }
	myexec("rm ".$filename);
	$stop_time=time();
	$timelaps = $stop_time - $start_time;
	return $nrules;
}
//-------------------------------------------------------------------
function get_array_ipset_blacklist() {
	global $config;
	$n=0;
	$ip=array();
	$r=myexec("ipset list blacklist");
	foreach ($r AS $k=>$v) {
		$l = rtrim ( $v , "\n" );
		$n++;
		$ip[$l]="";
	}
	logme("READ ip blacklist : ".$n." ip already blacklist");
	return $ip;
}
//-------------------------------------------------------------------
function load_spooler_file_ruleset() {
	global $config;
	proc_nice(19);
	$nrules=0;
	$start_time=time();
	$r = array(); 
	$cdir = scandir("/etc/suricata/spooler"); 
	foreach ($cdir as $k => $v) {
		if (!(stripos($v, ".rules") === FALSE)) {
			array_push($r, "/etc/suricata/spooler/".$v);
		}
	}
	foreach ($r AS $k=>$v) {
		$nrules = $nrules + load_to_mysql_rules_file($v);
	}
	$stop_time=time();
	$timelaps = $stop_time - $start_time;
	return $nrules;
}
//-------------------------------------------------------------------
function generate_sid_msg_map() {
	global $config;
	proc_nice(19);
	connect_mysql ();
	logme("CREATE CONFIG FILE...");
	$r=exec_mysql("SELECT * FROM rules");
	$first=0;
	while ($val = mysqli_fetch_array($r)) {
		if ($val['disable']==0) {
			if ($first==0) {
				file_put_contents("/etc/suricata/rules/sid-msg.map", $val['sid']." || ".$val['msg']."\n");
				file_put_contents("/etc/suricata/rules/all.rules", htmlspecialchars_decode($val['rule']));
				$first=1;
			}
			else {
				file_put_contents("/etc/suricata/rules/sid-msg.map", $val['sid']." || ".$val['msg']."\n", FILE_APPEND);
				file_put_contents("/etc/suricata/rules/all.rules", htmlspecialchars_decode($val['rule']), FILE_APPEND);
			}
		}
	}
	logme("CREATE CONFIG FILE is DONE !");
}
//-------------------------------------------------------------------
function get_mysql_network_blacklist() {
	global $config;
	global $connbdd;
	$cdir=array();
	$result = exec_mysql("SELECT network_cdir FROM network_drop");
	while ($val = mysqli_fetch_array($result)) {
		$cdir[$val['network_cdir']]="";
	}
	return $cdir;
}
//-------------------------------------------------------------------
function update_squidblacklist() {
	global $config;
	global $connbdd;
	connect_mysql ();
	logme("Start update squidblacklist.com for cdir network blocklist ...");
	$content=file_get_contents("http://www.squidblacklist.org/downloads/drop.malicious.rsc");
	$content = explode("\n", $content);
	$cdir=get_mysql_network_blacklist();
	foreach ($content AS $k=>$v) {
		preg_match("/^.*\saddress=(?P<net>[0-9\.\/]*)\s.*$/", $v, $match);
		if (isset($match['net'])) {
			if (!isset($cdir[$match['net']])) {
				exec_mysql("INSERT INTO network_drop SET network_cdir='".$match['net']."', origin='squidblacklist'");
			}
		}
	}
}
//-------------------------------------------------------------------
function thread_auto_update_blocklist() {
	global $config;
	$last_update=0;
	sleep(60); //delay thread at start
	while (TRUE) {
		if ($last_update + 14400 < time()) {
			logme("START UPDATE BLOCKLIST ....");
			update_squidblacklist();
			logme("UPDATE BLOCKLISt DONE !");
			load_ipset_network_blacklist();
			$last_update=time();
		}
		sleep(60);
	}
}
//-------------------------------------------------------------------
function thread_kill_state() {
	global $config;
	$j=array();
	while (TRUE) {
		$t=json_decode(apc_fetch("ip_state_kill"));
		apc_store("ip_state_kill", json_encode($j));
			foreach($t AS $k=>$v) {
				myexec("conntrack -D -s ".$v." 2>&1");
				myexec("conntrack -D -d ".$v." 2>&1");
				logme("KILL STATE of ".$v);
			}
		sleep(1);
	}
}
//----------------------Créer un daemon via fork---------------------
logme("MAIN : start ...");
logme("SURICATA : status ".suricata_status());
if( ! Thread::available() ) {
	logme("MAIN : Threads not supported ... GO DIE");
    die( 'Threads not supported' );
}
if (!file_exists ( "/var/run/suricata" )) {
	system("mkdir /var/run/suricata");
}
$__parent_pid = become_daemon();
//--------------------readconfig------------------------------------
logme("MAIN : Read config ...");
apc_store("test_apc", "1");
if (!apc_exists("test_apc")) {
	logme("APC NOK");
	exit(0);
}
apc_store("ip_state_kill", json_encode(array()));
system("sysctl -w net.bridge.bridge-nf-call-iptables=1");
system("modprobe nf_conntrack_ipv4");
readconfig_master();
load_firewall();
generate_sid_msg_map();
suricata_start();
disable_net_offload();
//----lancement des thread------------------------------------------
$a=0;
$thread_task_arg = array();
$a++;$thread_task[$a] = new Thread( 'read_suricata_alert_log' );$thread_task[$a]->start();
$a++;$thread_task[$a] = new Thread( 'thread_auto_update_rules' );$thread_task[$a]->start();
$a++;$thread_task[$a] = new Thread( 'server_loop' );$thread_task[$a]->start();
$a++;$thread_task[$a] = new Thread( 'thread_conntrack' );$thread_task[$a]->start();
$a++;$thread_task[$a] = new Thread( 'thread_auto_update_blocklist' );$thread_task[$a]->start();
$a++;$thread_task[$a] = new Thread( 'thread_kill_state' );$thread_task[$a]->start();
//--------Gestion des signaux systèmes------------------------
pcntl_signal(SIGTERM, 'sig_handler'); 
pcntl_signal(SIGINT, 'sig_handler');
pcntl_signal(SIGCHLD, SIG_IGN);
//---------------------Debut du programme watchdog------------------------------
logme ("WATCHDOG : WatchDog activated ... ".count($thread_task)." threads started");
while (TRUE) {
	sleep (10);
	foreach ($thread_task AS $key => $value) {
		if (!$thread_task[$key]->isAlive()) {
			logme ("WATCHDOG : Task ".$key." Crash, reload task");
			if (isset($thread_task_arg[$key])) {
				$thread_task[$key]->start($thread_task_arg[$key]);
			}
			else {
				$thread_task[$key]->start();
			}
		}
	}
}
?>