diff options
-rw-r--r-- | .htaccess | 3 | ||||
-rw-r--r-- | config.php.example | 2 | ||||
-rw-r--r-- | execute.php | 99 | ||||
-rw-r--r-- | includes/style.css | 63 | ||||
-rw-r--r-- | includes/utils.js | 41 | ||||
-rw-r--r-- | index.php | 111 | ||||
-rw-r--r-- | router.php | 139 |
7 files changed, 458 insertions, 0 deletions
diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..149477a --- /dev/null +++ b/.htaccess @@ -0,0 +1,3 @@ +<Files config.php> + Require all denied +</Files> diff --git a/config.php.example b/config.php.example index 7666cb3..7355e92 100644 --- a/config.php.example +++ b/config.php.example @@ -6,6 +6,8 @@ $config['contact']['mail'] = 'support@example.com'; // Frontpage configuration +// CSS to use +$config['frontpage']['css'] = 'includes/style.css'; // Title of the page $config['frontpage']['title'] = 'Looking Glass'; // Logo to display diff --git a/execute.php b/execute.php new file mode 100644 index 0000000..e3774f0 --- /dev/null +++ b/execute.php @@ -0,0 +1,99 @@ +<?php + +require_once 'config.php'; +require_once 'router.php'; +require_once 'utils.php'; + +if (isset($_POST['query']) && !empty($_POST['query']) && + isset($_POST['routers']) && !empty($_POST['routers']) && + isset($_POST['parameters']) && !empty($_POST['parameters'])) { + $query = htmlspecialchars($_POST['query']); + $hostname = htmlspecialchars($_POST['routers']); + $parameters = htmlspecialchars($_POST['parameters']); + $valid_request = false; + + switch ($query) { + case 'bgp': + if (match_ipv4($parameters) || match_ipv6($parameters)) { + $valid_request = true; + } else { + $error = 'The parameter is not an IPv4/IPv6 address.'; + } + break; + + case 'as-path-regex': + if (match_aspath_regex($parameters)) { + $valid_request = true; + } else { + $error = 'The parameter is not an AS-Path regular expression.'; + } + break; + + case 'as': + if (match_as($parameters)) { + $valid_request = true; + } else { + $error = 'The parameter is not an AS number.'; + } + break; + + case 'ping': + case 'traceroute': + if (match_ipv4($parameters) || match_ipv6($parameters) || + match_fqdn($parameters)) { + $valid_request = true; + } else { + $error = 'The parameter is not an IPv4/IPv6 address or a FQDN.'; + } + break; + + default: + $error = 'Unknown request: '.$query; + break; + } + + if (!$valid_request && isset($error)) { + // Unknown query or invalid parameters + echo $error; + } else { + // Do the processing + // Router connection, command execution, disconnection + $router = new Router($hostname, $_SERVER['REMOTE_ADDR']); + $router->connect(); + $data = $router->send_command($query, $parameters); + $router->disconnect(); + + // Process the output line by line + foreach (preg_split("/((\r?\n)|(\r\n?))/", $data) as $line) { + // Get rid of empty lines + if (empty($line)) { + continue; + } + + $valid = true; + + foreach ($config['filters'] as $filter) { + // Line has been marked as invalid + if (!$valid) { + break; + } + + // Filter line based on the configuration + if (preg_match($filter, $line) === 1) { + $valid = false; + break; + } + } + + // The line is valid, print it + if ($valid) { + $return .= $line."\n"; + } + } + + // Display the result of the command + echo $return; + } +} + +// End of execute.php diff --git a/includes/style.css b/includes/style.css new file mode 100644 index 0000000..caf7a34 --- /dev/null +++ b/includes/style.css @@ -0,0 +1,63 @@ +body { + font-size: 1em; + background-color: #FFFFFF; +} +.header_bar { + color: #000000; + text-align: center; + margin-bottom: 1em; +} +.footer_bar { + color: #000000; + font-size: 1.2em; + text-align: center; + margin-top: 1em; + width: 50%; + margin-left: auto; + margin-right: auto; +} +.logo { + text-align: center; + padding: 1%; +} +.content { + color: #000000; + font-size: 1.1em; + text-align: center; + width: 50%; + display: block; + margin-left: auto; + margin-right: auto; +} +.confirm { + width: 50%; + margin-left: auto; + margin-right: auto; +} +.loading { + margin-top: 1em; + width: 50%; + margin-left: auto; + margin-right: auto; +} +.reset { + width: 25%; + margin-left: auto; + margin-right: auto; +} +pre { + color: #FFFFFF; + background-color: #000000; + text-decoration: none; + width: 70%; + margin-left: auto; + margin-right: auto; +} +a { + color: #000000; + text-decoration: none; +} +a:hover { + color: #000000; + text-decoration: none; +} diff --git a/includes/utils.js b/includes/utils.js new file mode 100644 index 0000000..3bcde51 --- /dev/null +++ b/includes/utils.js @@ -0,0 +1,41 @@ +$(function() { + // hide the optional parameters field + $('.result').hide(); + $('.loading').hide(); + + // show and hide loading bar + $(document).ajaxStart(function() { + $('.loading').show(); + }); + $(document).ajaxStop(function() { + $('.loading').hide(); + }); + + // validate the parameters field + $('#input-params').on('input', function() { + var cmd = $('#query').val(); + }); + + // reset the view to the default one + $('#backhome').click(function() { + $('.content').slideDown(); + $('.result').slideUp(); + }); + + // send an ajax request that will get the info on the router + $('form').on('submit', function(e) { + e.preventDefault(); + + $.ajax({ + type: 'post', + url: 'execute.php', + data: $('form').serialize() + }).done(function(response, state, xhr) { + $('#output').text(response); + $('.content').slideUp(); + $('.result').slideDown(); + }).fail(function(xhr, state, error) { + alert('The following error occured: ' + state, error); + }); + }); +}); diff --git a/index.php b/index.php new file mode 100644 index 0000000..76774d0 --- /dev/null +++ b/index.php @@ -0,0 +1,111 @@ +<?php require_once 'config.php'; ?> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="keywords" content="Looking Glass, LG, BGP, prefix-list, AS-path, ASN, traceroute, ping, IPv4, IPv6, Cisco, Juniper, Internet" /> + <meta name="description" content="<?php echo $config['frontpage']['title']; ?>" /> + <title><?php echo $config['frontpage']['title']; ?></title> + <link href="bootstrap-3.1.1/css/bootstrap.min.css" rel="stylesheet" /> + <link href="bootstrap-3.1.1/css/bootstrap-theme.min.css" rel="stylesheet" /> + <link href="<?php echo $config['frontpage']['css']; ?>" rel="stylesheet" /> +</head> + +<body> + <div class="header_bar"> + <h1><?php echo $config['frontpage']['title']; ?></h1><br /> + <?php + if (isset($config['frontpage']['image'])) { + echo '<img src="'.$config['frontpage']['image'].'" alt="logo" />'; + } + ?> + </div> + + <div class="content" id="command_options"> + <form role="form" action="execute.php" method="post"> + <div class="form-group"> + <label for="routers">Router to use</label> + <select size="5" class="form-control" name="routers"> + <?php + $first = true; + foreach (array_keys($config['routers']) as $router) { + if ($first) { + $first = false; + echo '<option value="'.$router.'" selected="selected">'. + $config['routers'][$router]['desc'].'</option>'; + } else { + echo '<option value="'.$router.'">'. + $config['routers'][$router]['desc'].'</option>'; + } + } + ?> + </select> + </div> + + <div class="form-group"> + <label for="query">Command to issue</label> + <select size="5" class="form-control" name="query" id="query"> + <option value="bgp" selected="selected">show route IP_ADDRESS</option> + <option value="as-path-regex">show route as-path-regex AS_PATH_REGEX</option> + <option value="as">show route AS</option> + <option value="ping">ping IP_ADDRESS</option> + <option value="traceroute">traceroute IP_ADDRESS</option> + </select> + </div> + + <div class="form-group"> + <label for="parameters">Parameters</label> + <input class="form-control" name="parameters" id="input-params" /> + </div> + + <div class="confirm btn-group btn-group-justified"> + <div class="btn-group"> + <button class="btn btn-primary" id="send" type="submit">Enter</button> + </div> + <div class="btn-group"> + <button class="btn btn-danger" id="clear" type="reset">Reset</button> + </div> + </div> + </form> + </div> + + <div class="loading"> + <div class="progress progress-striped active"> + <div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> + </div> + </div> + </div> + + <div class="result"> + <pre class="pre-scrollable" id="output"></pre> + <div class="reset"> + <button class="btn btn-danger btn-block" id="backhome">Reset</button> + </div> + </div> + + <div class="footer_bar"> + <p class="text-center"> + <?php + if (isset($config['frontpage']['disclaimer']) && + !empty($config['frontpage']['disclaimer'])) { + echo 'Your IP address: '.$_SERVER['REMOTE_ADDR'].'<br />'; + echo $config['frontpage']['disclaimer']; + echo '<br /><br />'; + } + + if (isset($config['contact']) && !empty($config['contact'])) { + echo 'Contact: '; + echo '<a href="mail:'.$config['contact']['mail'].'">'.$config['contact']['name'].'</a>'; + } + ?> + </p> + </div> + + <!-- jquery / bootstrap / custom functions --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> + <script src="bootstrap-3.1.1/js/bootstrap.min.js"></script> + <script src="includes/utils.js"></script> + </script> +</body> +</html> diff --git a/router.php b/router.php new file mode 100644 index 0000000..c1fb3c4 --- /dev/null +++ b/router.php @@ -0,0 +1,139 @@ +<?php + +require_once 'utils.php'; + +class Router { + private $id; + private $host; + private $type; + private $auth; + private $connection; + private $requester; + + public function __construct($id, $requester) { + include 'config.php'; + + $this->id = $id; + $this->host = $config['routers'][$id]['host']; + $this->type = $config['routers'][$id]['type']; + $this->auth = $config['routers'][$id]['auth']; + $this->requester = $requester; + } + + private function log_command($command) { + include 'config.php'; + + file_put_contents($config['misc']['logs'], $command, + FILE_APPEND | LOCK_EX); + } + + public function connect() { + include 'config.php'; + + switch ($this->auth) { + case 'ssh-password': + $this->connection = ssh2_connect($this->host, 22); + if (!$this->connection) { + throw new Exception('Cannot connect to router'); + } + + $user = $config['routers'][$this->id]['user']; + $pass = $config['routers'][$this->id]['pass']; + + if (!ssh2_auth_password($this->connection, $user, $pass)) { + throw new Exception('Bad login/password.'); + } + + break; + + default: + echo 'Unknown protocol for connection.'; + } + } + + public function execute_command($command) { + if ($this->connection == null) { + $this->connect(); + } + + if (!($stream = ssh2_exec($this->connection, $command))) { + throw new Exception('SSH command failed'); + } + + stream_set_blocking($stream, true); + + $data = ''; + while ($buf = fread($stream, 4096)) { + $data .= $buf; + } + fclose($stream); + + return $data; + } + + public function send_command($command, $parameters) { + switch ($command) { + case 'bgp': + if (($parameters != null) && (strlen($parameters) > 0)) { + $complete_command = 'show route '.$parameters.' | no-more'; + } else { + return 'An IP address (and only one) is required as destination.'; + } + break; + + case 'as-path-regex': + if (($parameters != null) && (strlen($parameters) > 0)) { + $complete_command = 'show route aspath-regex '.$parameters.' | no-more'; + } else { + return 'An AS-Path regex is required like ".*XXXX YYYY.*".'; + } + break; + + case 'as': + if (($parameters != null) && (strlen($parameters) > 0)) { + $complete_command = 'show route aspath-regex .*'.$parameters.'.* | no-more'; + } else { + return 'An AS number is required like XXXX.'; + } + break; + + case 'ping': + if (($parameters != null) && (strlen($parameters) > 0)) { + $complete_command = 'ping count 10 '.$parameters.' rapid'; + } else { + return 'An IP address (and only one) is required to ping a host.'; + } + break; + + case 'traceroute': + if (($parameters != null) && (strlen($parameters) > 0)) { + if (match_ipv4($parameters)) { + $complete_command = 'traceroute '.$parameters.' as-number-lookup'; + } else { + $complete_command = 'traceroute '.$parameters; + } + } else { + return 'An IP address is required to traceroute a host.'; + } + break; + + default: + return 'Command not supported.'; + } + + $this->log_command('['.date("Y-m-d H:i:s").'] [client: '. + $this->requester.'] '.$this->host.'> '.$complete_command."\n"); + return $this->execute_command($complete_command); + } + + public function disconnect() { + $this->execute_command('exit'); + $this->connection = null; + } + + public function __destruct() { + $this->disconnect(); + } +} + +// End of router.php |