<?php
/*
	/usr/local/www/ifbwstats_daemon.php

	Contributed - 2010 - Zorac

	interface read code using netstat as identifed below from 
	/usr/local/www/ifstats.php
	Copyright (C) 2005-2006 Scott Ullrich (sullrich@gmail.com)
	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.

	command to run
	/usr/local/bin/php -q /usr/local/www/ifbwstats_daemon.php &

	command to interupt
	kill -INT pid
	kill -INT `cat /var/run/ifbwstats.lock`

	command to force interface read without quitting daemon
	kill -USR1 pid
	kill -USR1 `cat /var/run/ifbwstats.lock`
*/

//required for SIGINT and SIGUSR1 
declare(ticks = 1);

//allow php to run as a daemon and not time out
set_time_limit(0);

require_once("config.inc");
define( 'LOCK_FILE', "/var/run/ifbwstats.lock" );

function isLocked()
{
	# If lock file exists, check if stale.  If exists and is not stale, return TRUE
	# Else, create lock file and return FALSE.

	if( file_exists( LOCK_FILE ) )
	{
		# check if it's stale
		$lockingPID = trim( file_get_contents( LOCK_FILE ) );

		# Get all active PIDs.
		$pids = explode( "\n", trim( `ps | awk '{print $1}'` ) );

		# If PID is still active, return true
		if( in_array( $lockingPID, $pids ) )  
		{
			return true;
		}

		# Lock-file is stale, so kill it.  Then move on to re-creating it.
		unlink( LOCK_FILE );
	}
	file_put_contents( LOCK_FILE, getmypid() . "\n" );
	return false;
} 

function sig_handler($signo)
{
	global $logfile;
	switch ($signo) 
	{
	case SIGTERM:
		if (isset($logfile)) fwrite($logfile, date('Y-m-d H:i:s')." SIGTERM ".getmypid()." \n");
		break;
	case SIGINT:
		global $_MYDAEMON_SHOULD_STOP;
		$_MYDAEMON_SHOULD_STOP = true;
		if (isset($logfile)) fwrite($logfile, date('Y-m-d H:i:s')." SIGINT ".getmypid()." \n");
		break;
	case SIGUSR1:
		global $_MYDAEMON_SHOULD_QUERY;
		$_MYDAEMON_SHOULD_QUERY = true;
		if (isset($logfile)) fwrite($logfile, date('Y-m-d H:i:s')." SIGUSR1 ".getmypid()." \n");
		break;
	}
}

function interface_query($if)
{
	global $config;
	global $g;
	
	//set data files for appropriate interface
	$wandatalastfile = '/tmp/ifbwstats-'.$if.'.last';
	$wandataallfile = '/tmp/ifbwstats-'.$if.'.data';
	$wandatabackupfile = '/cf/conf/ifbwstats-'.$if.'.data';
	
	//assume max is 4GB because of the 32bit counter wrap
	$maxbytesin = 4294967296;
	$maxbytesout = 4294967296;

	//create (or clear if already used)  variables
	$wandatacurrent = array();
	$wandatalast = array();
	$wandataall = array();

	//----------start modified code insert from ifstats.php----------
	$ifinfo = array();

	$ifinfo['hwif'] = $config['interfaces'][$if]['if'];
	if(!$ifinfo['hwif'])
	$ifinfo['hwif'] = $if;

	$ifinfo['if'] = $ifinfo['hwif'];	

	/* run netstat to determine link info */
	$linkinfo = "";
	unset($linkinfo);
	exec("/usr/bin/netstat -I " . $ifinfo['hwif'] . " -nWb -f link", $linkinfo);
	$linkinfo = preg_split("/\s+/", $linkinfo[1]);
	if (preg_match("/\*$/", $linkinfo[0])) {
		$ifinfo['status'] = "down";
	} else {
		$ifinfo['status'] = "up";
	}

	if(preg_match("/^enc|^tun/i", $ifinfo['if'])) {
		$ifinfo['inbytes'] = $linkinfo[5];
		$ifinfo['outbytes'] = $linkinfo[8];
	} else {
		$ifinfo['inbytes'] = $linkinfo[6];
		$ifinfo['outbytes'] = $linkinfo[9];
	}
	//----------end modified code insert from ifstats.php----------

	//check for errors
	if ((file_exists($wandatalastfile)) && ($ifinfo['inbytes'] == 0) && ($ifinfo['outbytes'] == 0)) 
	{
		$ifinfo['status'] = "down";
	}

	if (is_NaN($ifinfo['inbytes']) || is_NaN($outinfo['inbytes'])) 
	{
		$ifinfo['status'] = "down";
	}

	if ($ifinfo['status'] == "up")
	{
		$wandatacurrent[0] = $ifinfo['inbytes'];
		$wandatacurrent[1] = $ifinfo['outbytes'];

		if (file_exists($wandatalastfile))
		{
			//read last read file
			$wandatalast = explode("|", file_get_contents($wandatalastfile));
		}
		else 
		{
			$wandatalast = $wandatacurrent;
		}
		
		if (!is_numeric($wandatalast[0]) || is_NaN($wandatalast[1]))
		{
			$wandatalast = $wandatacurrent;
		}

		$fp = fopen($wandatalastfile,"w") or die("Error Reading File");
		fwrite($fp, $wandatacurrent[0].'|'.$wandatacurrent[1]);
		fclose($fp);

		//account for 4gig counter reset
		if ($wandatacurrent[0] < $wandatalast[0]) $inbytes = ((4294967296 - $wandatalast[0]) + $wandatacurrent[0]);
		else $inbytes = $wandatacurrent[0] - $wandatalast[0];
		if ($wandatacurrent[1] < $wandatalast[1]) $outbytes = ((4294967296 - $wandatalast[1]) + $wandatacurrent[1]);
		else $outbytes = $wandatacurrent[1] - $wandatalast[1];

		//check to make sure inbytes and outbytes are possible, if not, 0 both and erase last data as it may be corrupt
		if (($inbytes < 0) || ($inbytes > $maxbytesin) || ($outbytes < 0) || ($outbytes > $maxbytesout))
		{
			$inbytes = 0;
			$outbytes = 0;
			if (file_exists($wandatalastfile)) unlink ($wandatalastfile);
		}

		$foundfile = 'null';
		if (file_exists($wandataallfile)) $foundfile = $wandataallfile;
		else
		{
			if (file_exists($wandatabackupfile)) $foundfile = $wandatabackupfile;
		}

		//if no file is found, create new data, else read file and add to existing data
		if ($foundfile == 'null')
		{
			$wanwritedata = date("Y-m-d").'|'.$inbytes.'|'.$outbytes;
		}
		else
		{
			//read data file
			$wandataall = explode("\n", file_get_contents($foundfile));
			$n = count($wandataall);

			//if last line of data date matchs current date, add to totals, else add new line
			$dataset = explode("|", $wandataall[$n-1]);
			if ($dataset[0] == date("Y-m-d"))
			{
				$dataset[1]=$dataset[1]+$inbytes;
				$dataset[2]=$dataset[2]+$outbytes;
				$wandataall[$n-1]=$dataset[0].'|'.$dataset[1].'|'.$dataset[2];
			}
			else 
			{
				$wandataall[$n] = date("Y-m-d").'|'.$inbytes.'|'.$outbytes;
			}

			//number of data entries (days)
			$n = count($wandataall);

			//if more than three years worth of data, trim data to 4 years (1460 days)
			$start = 0;
			if ($n > 1460) $start = $n - 1460;

			//generate file data to write
			for ($i=$start ; $i < ($n-1) ; $i++ ) $wanwritedata = $wanwritedata.$wandataall[$i]."\n";
			$wanwritedata = $wanwritedata.$wandataall[$n-1];
		}

		//write data file
		$fp = fopen($wandataallfile,"w") or die("Error Reading File");
		fwrite($fp, $wanwritedata);
		fclose($fp);
	}
	else 
	{
		if (file_exists($wandatalastfile)) unlink ($wandatalastfile);
	}
}

pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');
pcntl_signal(SIGUSR1, 'sig_handler');

global $config;
global $g;
global $_MYDAEMON_SHOULD_STOP;
global $_MYDAEMON_SHOULD_QUERY;

//logging -> yes or no
$logging = $config['installedpackages']['ifbwstats']['config'][0]['logging'];

if ($logging == 'yes')
{
	global $logfile;
	$logfile = fopen("/tmp/ifbwstats-daemon.log","a") or die("Error Reading File");
	$parentpid = getmypid();
	fwrite($logfile, date('Y-m-d H:i:s')." Startup - Parent PID is: ".$parentpid."\n");
}

if ($g['platform'] == 'cdrom') 
{
	fwrite($logfile, date('Y-m-d H:i:s')." Daemon will not run on CD Rom platform, exiting... \n");
	fclose ($logfile);
	exit ();
}
if (isLocked()) 
{
	if ($logging == 'yes') fwrite($logfile, date('Y-m-d H:i:s')." Daemon is already running, exiting second instance (".getmypid().") \n");
	fclose ($logfile);
	exit ();
}

// endless loop
while (1) 
{
	if (isset($_MYDAEMON_SHOULD_STOP) AND $_MYDAEMON_SHOULD_STOP) break;
	$pidA = pcntl_fork();
	if($pidA) 
	{
		// parent process runs here
		// wait until the child has finished processing then end the script
		pcntl_waitpid($pid, $status, WUNTRACED);
		//calc seconds to midnight
		$sleeptime = (mktime(23, 59, 55, date ('m, d, Y'))) - time();
		//use whichever is less, seconds to midnight or interval, running script just prior to midnight insure accurate daily reporting as standard timing interval could be as great at 50min
		if (($sleeptime > $config['installedpackages']['ifbwstats']['config'][0]['intervalrun']) || ($sleeptime < 5)) $sleeptime = $config['installedpackages']['ifbwstats']['config'][0]['intervalrun'];
		if ($logging == 'yes') fwrite($logfile, date('Y-m-d H:i:s')." Parent (".$parentpid.") Sleep for: ".$sleeptime."\n");
		for ($i=0; $i<$sleeptime; $i++)
		{
			if ((isset($_MYDAEMON_SHOULD_QUERY) AND $_MYDAEMON_SHOULD_QUERY) || (isset($_MYDAEMON_SHOULD_STOP) AND $_MYDAEMON_SHOULD_STOP))
			{
				$_MYDAEMON_SHOULD_QUERY = false;
				break;
			}
			else sleep (1);
		}
	}
	else 
	{
		//child process runs here
		if ($logging == 'yes') fwrite($logfile, date('Y-m-d H:i:s')." Child Process ". getmypid(). " Reading Interfaces... (Parent: ".$parentpid.")\n");
		if ($config['installedpackages']['ifbwstats']['config'][0]['ifmon'] != 'all') interface_query($config['installedpackages']['ifbwstats']['config'][0]['ifmon']);
		else
		{
			foreach ($config[interfaces] as $if => $value)
			{
				interface_query($if);
			}
		}
		exit (0);
	}
}

//run query one last time
if ($logging == 'yes') fwrite($logfile, date('Y-m-d H:i:s')." Parent Process ". getmypid(). " Reading Interfaces One Last Time... \n");
if ($config['installedpackages']['ifbwstats']['config'][0]['ifmon'] != 'all') interface_query($config['installedpackages']['ifbwstats']['config'][0]['ifmon']);
else
{
	foreach ($config[interfaces] as $if => $value)
	{
		interface_query($if);
	}
}
// backup data files to conf dir on exit
if ($g['platform'] != 'pfSense') exec ('/etc/rc.conf_mount_rw');
exec('cp /tmp/ifbwstats-*.data /cf/conf/');
if ($g['platform'] != 'pfSense') exec ('/etc/rc.conf_mount_ro');

if ($logging == 'yes') 
{
	fwrite($logfile, date('Y-m-d H:i:s')." Shutdown Parent ".$parentpid." \n");
	fclose ($logfile);
}

if( file_exists( LOCK_FILE ) ) unlink( LOCK_FILE );

exit (0);

?>