summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.htaccess3
-rw-r--r--config.php.example2
-rw-r--r--execute.php99
-rw-r--r--includes/style.css63
-rw-r--r--includes/utils.js41
-rw-r--r--index.php111
-rw-r--r--router.php139
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:&nbsp;';
+ 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