commit bfecd0b4ee84fee37cc4e8cbfdc87e4f9bf557d5
Author: Leah (ctucx) <leah@ctu.cx>
Date: Wed, 20 Jan 2021 18:00:38 +0100
Author: Leah (ctucx) <leah@ctu.cx>
Date: Wed, 20 Jan 2021 18:00:38 +0100
init
5 files changed, 490 insertions(+), 0 deletions(-)
A
|
162
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
90
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/index.php b/index.php @@ -0,0 +1,85 @@ +<?php +error_reporting(E_ALL); +ini_set('display_errors', true); +define('PATH', __DIR__); + +require PATH.'/lib/Template.php'; +require PATH.'/lib/Router.php'; +require PATH.'/lib/functions.php'; + +$tpl = new Template(PATH.'/templates/', []); + +Router::add('GET', '/', function() { + header("Location: /overview"); +}); + +Router::add('GET', '/overview', function() { + global $tpl; + + $leases = parseDhcpLeases('/var/lib/misc/dnsmasq.leases'); + + foreach ($leases['ipv4'] as $lease4) { + $lease6 = NULL; + + if($lease4['hostname'] !== NULL) { + $id = array_search($lease4['hostname'], array_column($leases['ipv6'], 'hostname')); + + if (!is_bool($id) && isset($leases['ipv6'][$id])) { + $lease6 = $leases['ipv6'][$id]; + } + } + + $duration = duration($lease4['expiryTimestamp'], time()); + + $tpl->blockAssign('leases', [ + 'EXPIRY_TIME' => $duration['days'].'d '.$duration['hours'].'h '.$duration['minutes'].'m', + 'MAC_ADDRESS' => $lease4['macAddress'], + 'IP4_ADDRESS' => $lease4['address'], + 'IP6_ADDRESS' => $lease6 !== NULL ? $lease6['address'] : '-', + 'HOSTNAME' => $lease4['hostname'] ? $lease4['hostname'] : '-', + 'CLIENT_ID' => $lease4['dhcpClientId'] ? $lease4['dhcpClientId'] : '-', + 'HAS_HTTP' => isPortOpen($lease4['address'], 80), + ]); + } + + foreach ($leases['ipv6'] as $lease6) { + if($lease6['hostname'] !== NULL) { + $id = array_search($lease6['hostname'], array_column($leases['ipv4'], 'hostname')); + + if (!is_bool($id)) { + continue; + } + } + + $duration = duration($lease6['expiryTimestamp'], time()); + + $tpl->blockAssign('leases', [ + 'EXPIRY_TIME' => $duration['days'].'d '.$duration['hours'].'h '.$duration['minutes'].'m', + 'MAC_ADDRESS' => '-', + 'IP4_ADDRESS' => '-', + 'IP6_ADDRESS' => $lease6['address'], + 'HOSTNAME' => $lease6['hostname'] ? $lease6['hostname'] : '-', + 'CLIENT_ID' => $lease6['dhcpClientId'] ? $lease6['dhcpClientId'] : '-', + 'HAS_HTTP' => isPortOpen($lease4['address'], 80), + ]); + } + + $tpl->render('overview', [ + 'PAGE' => 'DHCP Overview', + ]); +}); + +Router::add('GET', '/overview/json', function() { + header('Content-Type: application/json'); + echo json_encode(parseDhcpLeases('/var/lib/misc/dnsmasq.leases')); +}); + +Router::pathNotFound(function() { + header("Loctaion: /"); +}); + +Router::methodNotAllowed(function() { + header("Loctaion: /"); +}); + +Router::run('/');
diff --git a/lib/Router.php b/lib/Router.php @@ -0,0 +1,104 @@ +<?php + +class Router { + + private static $routes = []; + private static $pathNotFound = null; + private static $methodNotAllowed = null; + + public static function add($method, $expression, $function){ + array_push(self::$routes, [ + 'expression' => $expression, + 'function' => $function, + 'method' => $method + ]); + } + + public static function pathNotFound($function){ + self::$pathNotFound = $function; + } + + public static function methodNotAllowed($function){ + self::$methodNotAllowed = $function; + } + + public static function run($basepath = '/'){ + + // Parse current url + $parsed_url = parse_url($_SERVER['REQUEST_URI']);//Parse Uri + + if(isset($parsed_url['path'])){ + $path = $parsed_url['path']; + }else{ + $path = '/'; + } + + // Get current request method + $method = $_SERVER['REQUEST_METHOD']; + + $path_match_found = false; + + $route_match_found = false; + + foreach(self::$routes as $route){ + + // If the method matches check the path + + // Add basepath to matching string + if($basepath!=''&&$basepath!='/'){ + $route['expression'] = '('.$basepath.')'.$route['expression']; + } + + // Add 'find string start' automatically + $route['expression'] = '^'.$route['expression']; + + // Add 'find string end' automatically + $route['expression'] = $route['expression'].'$'; + + // echo $route['expression'].'<br/>'; + + // Check path match + if(preg_match('#'.$route['expression'].'#',$path,$matches)){ + + $path_match_found = true; + + // Check method match + if(strtolower($method) == strtolower($route['method'])){ + + array_shift($matches);// Always remove first element. This contains the whole string + + if($basepath!=''&&$basepath!='/'){ + array_shift($matches);// Remove basepath + } + + call_user_func_array($route['function'], $matches); + + $route_match_found = true; + + // Do not check other routes + break; + } + } + } + + // No matching route was found + if(!$route_match_found){ + + // But a matching path exists + if($path_match_found){ + header("HTTP/1.0 405 Method Not Allowed"); + if(self::$methodNotAllowed){ + call_user_func_array(self::$methodNotAllowed, Array($path,$method)); + } + }else{ + header("HTTP/1.0 404 Not Found"); + if(self::$pathNotFound){ + call_user_func_array(self::$pathNotFound, Array($path)); + } + } + + } + + } + +}+ \ No newline at end of file
diff --git a/lib/Template.php b/lib/Template.php @@ -0,0 +1,161 @@ +<?php + +class Template { + public $vars = []; + public $blocks = []; + private $pagevars = []; + private $tpl_path = NULL; + private $cache_path = NULL; + + public function __construct ($tpl_path, array $pagevars) { + if(!file_exists($tpl_path)){ + throw new Exception('Error templates folder not found.'); + } else { + $this->tpl_path = $tpl_path; + } + + $this->pagevars = $pagevars; + } + + public function assign ($vars, $value = null) { + if (is_array($vars)) { + $this->vars = array_merge($this->vars, $vars); + } else if ($value !== null) { + $this->vars[$vars] = $value; + } + } + + public function blockAssign ($name, $array) { + $this->blocks[$name][] = (array)$array; + } + + private function compileVars ($var) { + $newvar = $this->compileVar($var[1]); + return "<?php echo isset(" . $newvar . ") ? " . $newvar . " : '{" . $var[1] . "}' ?>"; + } + + private function compileVar ($var) { + if (strpos($var, '.') === false) { + $var = '$this->vars[\'' . $var . '\']'; + } else { + $vars = explode('.', $var); + if (!isset($this->blocks[$vars[0]]) && isset($this->vars[$var[0]]) && gettype($this->vars[$var[0]]) == 'array') { + $var = '$this->vars[\'' . $vars[0] . '\'][\'' . $vars[1] . '\']'; + } else { + $var = preg_replace("#(.*)\.(.*)#", "\$_$1['$2']", $var); + } + } + return $var; + } + + private function compileTags ($match) { + switch ($match[1]) { + case 'INCLUDE': + return "<?php echo \$this->compile('" . $match[2] . "'); ?>"; + break; + + case 'INCLUDEPHP': + return "<?php echo include(" . PATH . $match[2] . "'); ?>"; + break; + + case 'IF': + return $this->compileIf($match[2], false); + break; + + case 'ELSEIF': + return $this->compileIf($match[2], true); + break; + + case 'ELSE': + return "<?php } else { ?>"; + break; + + case 'ENDIF': + return "<?php } ?>"; + break; + + case 'BEGIN': + return "<?php if (isset(\$this->blocks['" . $match[2] . "'])) { foreach (\$this->blocks['" . $match[2] . "'] as \$_" . $match[2] . ") { ?>"; + break; + + case 'BEGINELSE': + return "<?php } } else { { ?>"; + break; + + case 'END': + return "<?php } } ?>"; + break; + } + } + + private function compileIf ($code, $elseif) { + $ex = explode(' ', trim($code)); + $code = ''; + + foreach ($ex as $value) { + $chars = strtolower($value); + + switch ($chars) { + case 'and': + case '&&': + case 'or': + case '||': + case '==': + case '!=': + case '!==': + case '>': + case '<': + case '>=': + case '<=': + case '0': + case is_numeric($value): + $code .= $value; + break; + + case 'not': + $code .= '!'; + break; + + default: + if (preg_match('/^[A-Za-z0-9_\-\.]+$/i', $value)) { + $var = $this->compileVar($value); + $code .= "(isset(" . $var . ") ? " . $var . " : '')"; + } else { + $code .= '\'' . preg_replace("#(\\\\|\'|\")#", '', $value) . '\''; + } + break; + } + $code .= ' '; + } + + return '<?php ' . (($elseif) ? '} else ' : '') . 'if (' . trim($code) . ") { ?>"; + } + + private function compile ($file) { + $abs_file = $this->tpl_path.'/'.$file; + + $tpl = file_get_contents($abs_file); + $tpl = preg_replace("#<\?(.*)\?>#", '', $tpl); + $tpl = preg_replace_callback("#<!-- ([A-Z]+) (.*)? ?-->#U", array($this, 'compileTags'), $tpl); + $tpl = preg_replace_callback("#{([A-Za-z0-9_\-.]+)}#U", array($this, 'compileVars'), $tpl); + + if (eval(' ?>'.$tpl.'<?php ') === false) { + $this->error(); + } + } + + public function error () { + exit('Fehler im Template!'); + } + + public function render ($file, $data = NULL) { + $this->assign($this->pagevars); + + if ($data !== NULL) { + $this->assign($data); + } + + $this->compile($file.'.tpl'); + exit(); + } +}+ \ No newline at end of file
diff --git a/lib/functions.php b/lib/functions.php @@ -0,0 +1,90 @@ +<?php + +function parseDhcpLeases ($file) { + $fileHandler = new SplFileObject($file); + $leases = []; + $mode = 'ipv4'; + + while(!$fileHandler->eof()) { + $rawLine = trim($fileHandler->fgets()); + + if ($rawLine == '') continue; + + $rawEntry = explode(' ', $rawLine); + + if ($rawEntry[0] == 'duid') { + $leases['duid'] = $rawEntry[1]; + $mode = 'ipv6'; + continue; + } + + if ($mode !== 'ipv6') { + $leases['ipv4'][] = [ + 'expiryTimestamp' => $rawEntry[0], + 'macAddress' => $rawEntry[1], + 'address' => $rawEntry[2], + 'hostname' => ($rawEntry[3] !== '*' ? $rawEntry[3] : NULL), + 'dhcpClientId' => ($rawEntry[4] !== '*' ? $rawEntry[4] : NULL) + ]; + } else { + $leases['ipv6'][] = [ + 'expiryTimestamp' => $rawEntry[0], + 'iaid' => $rawEntry[1], + 'address' => $rawEntry[2], + 'hostname' => ($rawEntry[3] !== '*' ? $rawEntry[3] : NULL), + 'dhcpClientId' => ($rawEntry[4] !== '*' ? $rawEntry[4] : NULL) + ]; + } + } + + $fileHandler = NULL; + + return $leases; +} + +function walk($array, $key, $option) { + if( !is_array( $array)) { + return false; + } + foreach ($array as $k => $v) { + if($k == $key && is_array($v) && isset($v[$option])){ + return $v[$option]; + } + $data = walk($v, $key, $option); + if($data != false){ + return $data; + } + } + + return false; +} + +function duration($date1, $date2) { + $diff = abs($date2 - $date1); + + $years = floor($diff / (365*60*60*24)); + $months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24)); + $days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24) / (60*60*24)); + $hours = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24) / (60*60)); + $minutes = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24 - $hours*60*60) / 60); + $seconds = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24 - $hours*60*60 - $minutes*60)); + + return [ + 'years' => $years, + 'months' => $months, + 'days' => $days, + 'hours' => $hours, + 'minutes' => $minutes, + 'seconds' => $seconds, + ]; +} + +function isPortOpen($ip, $port) { + $fp = @fsockopen($ip, $port, $errno, $errstr, 0.1); + if (!$fp) { + return false; + } else { + fclose($fp); + return true; + } +}
diff --git a/templates/overview.tpl b/templates/overview.tpl @@ -0,0 +1,48 @@ +<html> + <head> + <title>{PAGE}</title> + <!-- Google Fonts --> + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> + <!-- CSS Reset --> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css"> + <!-- Milligram CSS --> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css"> + </head> + <body> + <main class="wrapper"> + <div class="container"> + <div class="row"> + <div class="column column-80 column-offset-10"> + <h2>DHCP Leases</h2> + <table> + <thead> + <tr> + <th>Hostname</th> + <th>IPv4-Adresse</th> + <th>IPv6-Adresse</th> + <th>MAC-Adresse</th> + <th>Gültig bis</th> + </tr> + </thead> + <tbody> + <!-- BEGIN leases --> + <tr> + <!-- IF leases.HAS_HTTP != true --> + <td><a href="http://{leases.HOSTNAME}">{leases.HOSTNAME}</a></td> + <!-- ELSE --> + <td>{leases.HOSTNAME}</td> + <!-- ENDIF --> + <td>{leases.IP4_ADDRESS}</td> + <td>{leases.IP6_ADDRESS}</td> + <td>{leases.MAC_ADDRESS}</td> + <td>{leases.EXPIRY_TIME}</td> + </tr> + <!-- END leases --> + </tbody> + </table> + </div> + </div> + </div> + </main> + </body> +</html>