<?php
/*
    # Copyright (C) 2014 Deciso B.V.
    #
    # All rights reserved.
    #
    # Redistribution and use in source and binary forms, with or without
    # modification, are permitted provided that the following conditions are met:
    #
    # 1. Redistributions of source code must retain the above copyright notice,
    #    this list of conditions and the following disclaimer.
    #
    # 2. Redistributions in binary form must reproduce the above copyright
    #    notice, this list of conditions and the following disclaimer in the
    #    documentation and/or other materials provided with the distribution.
    #—
    # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
    # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
    # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
    # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    # POSSIBILITY OF SUCH DAMAGE.

    --------------------------------------------------------------------------------------
    package : Captive portal
    function: main package for captive portal backend functionality

*/


namespace OPNsense\CaptivePortal;

use \OPNsense\Core;

/**
 * Class CPClient
 * // TODO: CARP interfaces are probably not handled correctly
 * @package CaptivePortal
 */
class CPClient {


    /**
     * config handle
     * @var Core_Config
     */
    private  $config = null;

    /**
     * ipfw rule object
     * @var \CaptivePortal\Rules
     */
    private $rules = null;

    /**
     * link to shell object
     * @var  Core\Shell
     */
    private $shell = null;

    /**
     * send message to syslog
     *
     * @param $status
     * @param $user
     * @param $mac
     * @param $ip
     * @param string $message
     */
    private function logportalauth($cpzonename, $user, $mac, $ip, $status, $message=""){

        $message = trim($message);
        $message = "Zone : {$cpzonename} {$status}: {$user}, {$mac}, {$ip}, {$message}";

        $logger = new \Phalcon\Logger\Adapter\Syslog("logportalauth", array(
            'option' => LOG_PID,
            'facility' => LOG_LOCAL4
        ));
        $logger->info($message);

    }

    /**
     * Request new pipeno
     * @return int
     */
    private function new_ipfw_pipeno(){
        // TODO: implement global pipe number assigment
        return 999;
    }

    /**
     * reset bandwidth, if the current bandwidth is unchanged, do nothing
     *
     * @param int $pipeno system pipeno
     * @param int $bw bandwidth in Kbit/s
     */
    private function reset_bandwidth($pipeno,$bw){
        //TODO : setup bandwidth for sessions ( check changed )
        //#pipe 2000 config bw 2000Kbit/s
        return;
    }

    /**
     * @param $cpzonename zone name
     * @param $sessionid session id
     */
    private function _disconnect($cpzonename,$sessionid){

        $zoneid = -1;
        foreach($this->config->object()->captiveportal->children() as $zone => $zoneobj){
            if ($zone == $cpzonename) $zoneid = $zoneobj->zoneid;
        }

        if ($zoneid == -1) return; // not a valid zone

        $db = new DB($cpzonename);
        $db_clients = $db->listClients(array("sessionid"=>$sessionid));

        $ipfw_tables = $this->rules->getAuthUsersTables($zoneid);
        if ( sizeof($db_clients) > 0 ){
            if ($db_clients[0]->ip != null ) {
                // only handle disconnect if we can find a client in our database
                $exec_commands[] = "/sbin/ipfw table " . $ipfw_tables["in"] . " delete " . $db_clients[0]->ip;
                $exec_commands[] = "/sbin/ipfw table " . $ipfw_tables["out"] . " delete " . $db_clients[0]->ip;
                $this->shell->exec($exec_commands, false, false);
                // TODO: cleanup dummynet pipes $db_clients[0]->pipeno_in/out
                // TODO: log removal ( was : captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");)
            }
            $db->remove_session($sessionid);
        }
    }

    /**
     * load accounting rules into ruleset, used for reinitialisation of the ruleset.
     * triggers add_accounting() for all active clients in all zones
     */
    private function loadAccounting()
    {
        foreach ($this->config->object()->captiveportal->children() as $cpzonename => $zone)
        {
            $db = new DB($cpzonename);
            foreach ($db->listClients(array()) as $client)
            {
                $this->add_accounting($zone->zoneid, $client->ip) ;
            }
            unset($db);
        }
    }

    /**
     *
     * @param $zoneid
     * @param $ip
     */
    public function add_accounting($zoneid,$ip){
        // TODO: check processing speed, this might need some improvement
        // check if our ip is already in the list and collect first free rule number to place it there if necessary
        $shell_output=array();
        $this->shell->exec("/sbin/ipfw show",false,false,$shell_output);
        $prev_id = 0;
        $new_id = null;
        foreach($shell_output as $line){
            // only trigger on counter rules and last item in the list
            if ( strpos($line," count " ) !== false || strpos($line,"65535 " )!== false  ) {
                if ( strpos($line," ".$ip." " ) !== false ) {
                    // already in table... exit
                    return;
                }

                $this_line_id = (int) (explode(" ",$line)[0]) ;
                if ( $this_line_id  > 30000 and ($this_line_id -1) > $prev_id and $new_id == null) {
                    // new id found
                    if ( $this_line_id == 65535 ) $new_id = $prev_id+1;
                    else $new_id = $this_line_id-1;
                }

                $prev_id =  $this_line_id;
            }
        }

        if ( $new_id != null ) {
            $exec_commands = array(
                "/sbin/ipfw add " . $new_id . " set " . $zoneid . " count ip from " . $ip . " to any ",
                "/sbin/ipfw add " . $new_id . " set " . $zoneid . " count ip from  any to " . $ip,
            );

            // execute all ipfw actions
            $this->shell->exec($exec_commands, false, false);
        }
    }

    /**
     * list (ipfw) accounting information
     * @return array (key = hosts ip)
     */
    public function list_accounting($ipaddr=null){
        $filter_cmd = "";
        $result = array();
        $shell_output = array();
        if ( $ipaddr != null ) $filter_cmd =" | /usr/bin/grep ' " . $ipaddr ." '" ;

        if ( $this->shell->exec("/sbin/ipfw -aT list ".$filter_cmd,false,false,$shell_output) == 0 ){
            foreach( $shell_output as $line) {
                if (strpos($line, ' count ip from') !== false) {
                    $parts = preg_split('/\s+/', $line);
                    if (count($parts) > 8 && $parts[7] != 'any' and strlen($parts[7]) > 5) {
                        $result[$parts[7]] = array(
                            "rulenum" => $parts[0],
                            "last_accessed" => (int)$parts[3],
                            "idle_time" => time() - (int)$parts[3],
                            "out_packets" => (int)$parts[1],
                            "in_packets" => (int)$parts[2]
                        );
                    }
                }
            }
        }

        return $result;

    }

    /**
     * reset traffic counters
     *
     * @param null $rulenum
     */
    public function zero_counters($rulenum=null){
        if ( $rulenum != null and is_numeric($rulenum) ){
            $this->shell->exec("/sbin/ipfw zero " . $rulenum );
        }
        elseif ( $rulenum == null ){
            $this->shell->exec("/sbin/ipfw zero " );
        }

    }

    /**
     * Constructor
     */
    function __construct() {
        // Request handle to configuration
        $this->config = Core\Config::getInstance();
        // generate new ruleset
        $this->rules = new Rules();
        // keep a link to the shell object
        $this->shell = new Core\Shell();
    }

    /**
     * Reconfigure zones ( generate and load ruleset )
     */
    public function reconfigure(){
        $ruleset_filename = \Phalcon\DI\FactoryDefault::getDefault()->get('config')->globals->temp_path."/ipfw.rules";
        $this->rules->generate($ruleset_filename);

        // load ruleset
        $this->shell->exec("/sbin/ipfw -f ".$ruleset_filename);

        // update tables
        $this->update();

        // after reinit all accounting rules are vanished, reapply them for active sessions
        $this->loadAccounting();
    }

    /**
     * update zone(s) with new configuration data
     * @param string $zone
     */
    public function update($zone=null){
        $this->refresh_allowed_ips($zone);
        $this->refresh_allowed_mac($zone);
    }

    /**
     * refresh allowed ip's for defined zone ( null for all zones )
     * @param string $zone
     */
    public function refresh_allowed_ips($cpzone=null){

        $handled_addresses = array();
        foreach( $this->config->object()->captiveportal->children()  as $cpzonename => $zone ) {
            // search requested zone (id)
            if ( $cpzonename == $cpzone || $zone->zoneid == $cpzone || $cpzone == null ) {
                $db = new DB($cpzonename);
                $db_iplist = $db->listFixedIPs();

                // calculate table numbers for this zone
                $ipfw_tables = $this->rules->getAuthIPTables($zone->zoneid );

                foreach ($zone->children() as $tagname => $tagcontent) {
                    $ip = $tagcontent->ip->__toString();
                    if ($tagname == 'allowedip') {
                        $handled_addresses[$ip] = array();
                        $handled_addresses[$ip]["bw_up"] = $tagcontent->bw_up->__toString() ;
                        $handled_addresses[$ip]["bw_down"] = $tagcontent->bw_down->__toString() ;

                        if ( !array_key_exists($ip,$db_iplist) ){
                            // only insert new values
                            $pipeno_in = $this->new_ipfw_pipeno() ;
                            $pipeno_out = $this->new_ipfw_pipeno() ;

                            $exec_commands = array(
                                # insert new ip address
                                "/sbin/ipfw table ". $ipfw_tables["in"]  ." add " . $ip . "/" . $tagcontent->sn->__toString() . " " . $pipeno_in,
                                "/sbin/ipfw table ". $ipfw_tables["out"] ." add " . $ip . "/" . $tagcontent->sn->__toString() . " " . $pipeno_out,
                            );

                            // execute all ipfw actions
                            $this->shell->exec($exec_commands, false,false);
                            // update administration
                            $db->upsertFixedIP($ip,$pipeno_in,$pipeno_out);
                            // save bandwidth data
                            $handled_addresses[$ip]["pipeno_in"] = $pipeno_in ;
                            $handled_addresses[$ip]["pipeno_out"] =  $pipeno_out ;
                        }else{
                            //
                            $handled_addresses[$ip]["pipeno_in"] = $db_iplist[$ip]->pipeno_in ;
                            $handled_addresses[$ip]["pipeno_out"] = $db_iplist[$ip]->pipeno_out ;
                        }
                    }

                }


                // Cleanup deleted addresses
                foreach($db_iplist as $ip => $record){
                    if (!array_key_exists($ip,$handled_addresses)){
                        $exec_commands = array(
                            # insert new ip address
                            "/sbin/ipfw table ". $ipfw_tables["in"]  ." del " . $ip . "/" . $tagcontent->sn->__toString() ,
                            "/sbin/ipfw table ". $ipfw_tables["out"] ." del " . $ip . "/" . $tagcontent->sn->__toString() ,
                        );

                        // execute all ipfw actions
                        $this->shell->exec($exec_commands, false,false);
                        // TODO : cleanup $record->pipeno_in, $record->pipeno_out ;
                        $db->dropFixedIP($ip);
                    }
                }

                // reset bandwidth,
                foreach($handled_addresses as $mac => $record){
                    if (array_key_exists("pipeno_in",$record) ){
                        $this->reset_bandwidth($record["pipeno_in"],$record["bw_down"]);
                        $this->reset_bandwidth($record["pipeno_out"],$record["bw_up"]);
                    }
                }

                unset($db);
            }
        }

    }

    /**
     * To be able to grant access to physical pc's, we need to do some administration.
     * Our captive portal database keeps a list of every used address and last know mac address
     *
     * @param String $zone zone name or number
     */
    public function refresh_allowed_mac($cpzone=null){

        // read ARP table
        $arp= new ARP();
        $arp_maclist = $arp->getMACs();

        // keep a list of handled addresses, so we can cleanup the rest and keep track of needed bandwidth restrictions
        $handled_mac_addresses = array();
        foreach( $this->config->object()->captiveportal->children()  as $cpzonename => $zone ) {
            if ( $cpzonename == $cpzone || $zone->zoneid == $cpzone || $cpzone == null ) {
                // open administrative database for this zone
                $db = new DB($cpzonename);
                $db_maclist = $db->listPassthruMacs();
                $ipfw_tables = $this->rules->getAuthMACTables($zone->zoneid);

                foreach ($zone->children() as $tagname => $tagcontent) {
                    $mac = trim(strtolower($tagcontent->mac));
                    if ($tagname == 'passthrumac') {
                        // only accept valid macaddresses
                        if (preg_match('/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/', $mac)) {
                            if ($tagcontent->action == "pass") {
                                $handled_mac_addresses[$mac] = array("action"=>"skipped" );
                                $handled_mac_addresses[$mac]["bw_up"] = $tagcontent->bw_up ;
                                $handled_mac_addresses[$mac]["bw_down"] = $tagcontent->bw_down ;

                                // only handle addresses we know of
                                if (array_key_exists($mac, $arp_maclist)) {
                                    // if the address is already in our database, check if it has changed
                                    if ( array_key_exists($mac,$db_maclist) ) {
                                        // save pipe numbers for bandwidth restriction
                                        $handled_mac_addresses[$mac]["pipeno_in"] = $db_maclist[$mac]->pipeno_in ;
                                        $handled_mac_addresses[$mac]["pipeno_out"] = $db_maclist[$mac]->pipeno_out ;

                                        if ($db_maclist[$mac]->ip !=  $arp_maclist[$mac]['ip'] ) {
                                            // handle changed ip,
                                            $handled_mac_addresses[$mac]["action"] = "changed ip";
                                            $exec_commands = array(
                                                # delete old ip address
                                                "/sbin/ipfw table ". $ipfw_tables["in"] ." delete ". $db_maclist[$mac]->ip,
                                                "/sbin/ipfw table ". $ipfw_tables["out"] ." delete ". $db_maclist[$mac]->ip,
                                                # insert new ip address
                                                "/sbin/ipfw table ". $ipfw_tables["in"]  ." add " . $arp_maclist[$mac]['ip']. " " . $db_maclist[$mac]->pipeno_in,
                                                "/sbin/ipfw table ". $ipfw_tables["out"] ." add " . $arp_maclist[$mac]['ip']. " " . $db_maclist[$mac]->pipeno_out,
                                            );

                                            // execute all ipfw actions
                                            $this->shell->exec($exec_commands, false,false);
                                            // update administration
                                            $db->upsertPassthruMAC($tagcontent->mac,$arp_maclist[$mac]['ip'],$db_maclist[$mac]->pipeno_in,$db_maclist[$mac]->pipeno_out); // new ip according to arp table
                                        }
                                    }
                                    else {
                                        // new host, not seen it yet
                                        $handled_mac_addresses[$mac]["action"] = "new";
                                        $pipeno_in = $this->new_ipfw_pipeno() ;
                                        $pipeno_out = $this->new_ipfw_pipeno() ;

                                        // execute all ipfw actions
                                        $exec_commands = array(
                                            # insert new ip address
                                            "/sbin/ipfw table ". $ipfw_tables["in"]  ." add " . $arp_maclist[$mac]['ip']. " " . $pipeno_in,
                                            "/sbin/ipfw table ". $ipfw_tables["out"] ." add " . $arp_maclist[$mac]['ip']. " " . $pipeno_out,
                                        );
                                        $this->shell->exec($exec_commands, false,false);

                                        $db->upsertPassthruMAC($tagcontent->mac,$arp_maclist[$mac]['ip'],$pipeno_in,$pipeno_out);
                                        // save pipe numbers for bandwidth restriction
                                        $handled_mac_addresses[$mac]["pipeno_in"] = $pipeno_in ;
                                        $handled_mac_addresses[$mac]["pipeno_out"] =  $pipeno_out ;
                                    }
                                }
                            }
                        }
                    }
                }

                //
                // cleanup old addresses
                //
                foreach($db_maclist as $mac => $record){
                    if ( !array_key_exists($mac,$handled_mac_addresses) ){
                        # delete old ip address, execute all actions
                        $exec_commands = array(
                            "/sbin/ipfw table ". $ipfw_tables["in"] ." delete ". $db_maclist[$mac]->ip,
                            "/sbin/ipfw table ". $ipfw_tables["out"] ." delete ". $db_maclist[$mac]->ip,
                        );
                        $this->shell->exec($exec_commands, false,false);
                        // TODO : cleanup $record->pipeno_in, $record->pipeno_out ;
                        $db->dropPassthruMAC($mac);
                    }
                }

                // reset bandwidth
                foreach($handled_mac_addresses as $mac => $record){
                    if (array_key_exists("pipeno_in",$record) ){
                        $this->reset_bandwidth($record["pipeno_in"],$record["bw_down"]);
                        $this->reset_bandwidth($record["pipeno_out"],$record["bw_up"]);
                    }
                }

                unset($db);

            }
        }

    }

    /**
     * unlock host for captiveportal use
     *
     * @param string $cpzonename
     * @param string $clientip
     * @param string $clientmac
     * @param string $username
     * @param string $password
     * @param string $attributes
     * @param string $radiusctx
     */
    public function portal_allow($cpzonename,$clientip,$clientmac,$username,$password = null,$bw_up=null,$bw_down=null, $radiusctx = null,$session_timeout=null,$idle_timeout=null,$session_terminate_time=null,$interim_interval=null){
        // defines
        $exec_commands = array() ;
        $db = new DB($cpzonename);
        $arp= new ARP();

        // find zoneid for this named zone
        $zoneid = -1;
        foreach($this->config->object()->captiveportal->children() as $zone => $zoneobj){
            if ($zone == $cpzonename) $zoneid = $zoneobj->zoneid;
        }

        if ($zoneid == -1) return; // not a valid zone, bailout


        // grap needed data to generate our rules
        $ipfw_tables = $this->rules->getAuthUsersTables($zoneid);
        $cp_table = $db->listClients(array("mac"=>$clientmac,"ip"=>$clientip),"or");
        if ( sizeof($cp_table) > 0 && ($cp_table[0]->ip == $clientip && $cp_table[0]->mac == $clientmac ) ){
            // nothing (important) changed here... move on
            return $cp_table[0]->sessionid;
        } elseif ( sizeof($cp_table) > 0) {
            // something changed...
            // prevent additional sessions to popup, one MAC should have only one active session, remove the rest (if any)
            $cnt = 0;
            $remove_sessions = array();
            foreach($cp_table as $record){
                if ( $cnt >0) $remove_sessions[] = $record->sessionid;
                else $current_session = $record;
                $cnt++;
                // prepare removal for all ip addresses belonging to this host
                $exec_commands[] = "/sbin/ipfw table ". $ipfw_tables["in"] ." delete ". $record->ip;
                $exec_commands[] = "/sbin/ipfw table ". $ipfw_tables["out"] ." delete ". $record->ip;
                // TODO: if for some strange reason there is more than one session, we are failing to drop the pipes
                $exec_commands[] = "/usr/sbin/arp -d ".trim($record->ip); // drop static arp entry (prevent MAC change)
            }
            if (sizeof($remove_sessions)){
                $db->remove_session($remove_sessions);
            }

            // collect pipe numbers for  dummynet
            $pipeno_in = $current_session->pipeno_in;
            $pipeno_out = $current_session->pipeno_out;

            $db->update_session($current_session->sessionid,array("ip"=>$clientip,"mac"=>$clientmac));

            // preserve session for response
            $sessionid = $current_session->sessionid;
        } else
        {
            // new session, allocate new dummynet pipes and generate a unique id
            $pipeno_in = $this->new_ipfw_pipeno();
            $pipeno_out = $this->new_ipfw_pipeno();

            // construct session data
            $session_data=Array();
            $session_data["ip"]=$clientip;
            $session_data["mac"]=$clientmac;
            $session_data["pipeno_in"] = $pipeno_in;
            $session_data["pipeno_out"] = $pipeno_out;
            $session_data["username"]=\SQLite3::escapeString($username);
            $session_data["bpassword"] =base64_encode($password);
            $session_data["session_timeout"] = $session_timeout;
            $session_data["idle_timeout"] = $idle_timeout;
            $session_data["session_terminate_time"] = $session_terminate_time;
            $session_data["interim_interval"] = $interim_interval;
            $session_data["radiusctx"] = $radiusctx;
            $session_data["allow_time"] = time(); // allow time is actual starting time of this session
            $sessionid = uniqid() ;

            $db->insert_session($sessionid, $session_data );

        }

        // add commands for access tables, and execute all collected
        $exec_commands[] = "/sbin/ipfw table ". $ipfw_tables["in"] ." add ". $clientip . " ".$pipeno_in;
        $exec_commands[] = "/sbin/ipfw table ". $ipfw_tables["out"] ." add ". $clientip . " ".$pipeno_out;
        $this->shell->exec($exec_commands, false,false);

        // lock the user/ip to it's MAC address using arp
        $arp->setStatic($clientip,$clientmac);

        // add accounting rule
        $this->add_accounting($zoneid,$clientip);

        // set bandwidth restrictions
        $this->reset_bandwidth($pipeno_in,$bw_up);
        $this->reset_bandwidth($pipeno_in,$bw_down);

        // log
        $this->logportalauth($cpzonename, $username, $clientmac, $clientip, $status="LOGIN");

        // cleanup
        unset($db);

        return $sessionid;
    }

    /**
     * disconnect a session or a list of sessions depending on the parameter
     * @param string $cpzonename zone name or id
     * @param $sessionid
     */
    public function disconnect($cpzonename,$sessionid){
        if ( is_array($sessionid)){
            foreach($sessionid as $sessid ){
                $this->_disconnect($cpzonename,$sessid);
            }
        }
        else{
            $this->_disconnect($cpzonename,$sessionid);
        }
    }

    /**
     * flush zone (null flushes all zones)
     * @param null $zone
     */
    function flush($zone=null){
        if ( $zone == null ) {
            $shell = new Core\Shell();
            $shell->exec("/sbin/ipfw -f table all flush");
        }
        else{
            // find zoneid for this named zone
            if (preg_match("/^[0-9]{1,2}$/", trim($zone)) ) {
                $zoneid = $zone;
            }else {
                $zoneid = -1;
                foreach ($this->config->object()->captiveportal->children() as $zonenm => $zoneobj) {
                    if ($zonenm == $zone) $zoneid = $zoneobj->zoneid;
                }
            }

            if ( $zoneid != -1 ){
                $exec_commands= array(
                    "/sbin/ipfw -f table ".$this->rules->getAuthUsersTables($zoneid)["in"]." flush",
                    "/sbin/ipfw -f table ".$this->rules->getAuthUsersTables($zoneid)["out"]." flush",
                    "/sbin/ipfw -f table ".$this->rules->getAuthIPTables($zoneid)["in"]." flush",
                    "/sbin/ipfw -f table ".$this->rules->getAuthIPTables($zoneid)["out"]." flush",
                    "/sbin/ipfw -f table ".$this->rules->getAuthMACTables($zoneid)["in"]." flush",
                    "/sbin/ipfw -f table ".$this->rules->getAuthMACTables($zoneid)["out"]." flush",
                    "/sbin/ipfw delete set ".$zoneid,
                );
                $this->shell->exec($exec_commands, false,false);
            }
        }
    }

    /**
     * cleanup portal sessions
     */
    function portal_cleanup_sessions($cpzone=null){
        $acc_list = $this->list_accounting();
        foreach($this->config->object()->captiveportal->children() as $cpzonename => $zoneobj){
            if ( $cpzone == null || $cpzone == $cpzonename ) {
                $db = new DB($cpzonename);

                $clients = $db->listClients(array(), null, null);

                foreach ($clients as $client) {
                    $idle_time = 0;
                    if (array_key_exists($client->ip, $acc_list)) {
                        $idle_time = $acc_list[$client->ip];
                    }

                    // if session timeout is reached, disconnect
                    if (is_numeric($client->session_timeout) && $client->session_timeout > 0 ) {
                        if (((time() - $client->allow_time) / 60) > $client->session_timeout) {
                            $this->disconnect($cpzonename, $client->sessionid);
                            $this->logportalauth($cpzonename, $client->username, $client->mac, $client->ip, $status="SESSION TIMEOUT");
                            continue;
                        }
                    }

                    // disconnect session if idle timeout is reached
                    if (is_numeric($client->idle_timeout) && $client->idle_timeout > 0  && $idle_time > 0) {
                        if ($idle_time > $client->idle_timeout) {
                            $this->disconnect($cpzonename, $client->sessionid);
                            $this->logportalauth($cpzonename, $client->username, $client->mac, $client->ip, $status="IDLE TIMEOUT");
                            continue;
                        }
                    }

                    // disconnect on session terminate time
                    if ( is_numeric($client->session_terminate_time) && $client->session_terminate_time > 0 && $client->session_terminate_time < time()) {
                        $this->disconnect($cpzonename, $client->sessionid);
                        $this->logportalauth($cpzonename, $client->username, $client->mac, $client->ip, $status="TERMINATE TIME REACHED");
                        continue;
                    }
                }

                unset($db);
            }
        }

        unset ($acc_list);

    }


}