Commit 45558406 authored by Hugo Leisink's avatar Hugo Leisink

Release 1.0

parent 10ffd041
monitor (1.0) stable; urgency=low
* Added script for sending daily reports.
* Support for Notify My Android notifications.
* Small improvements.
* Bugfix: wrong details per day for server statistics.
-- Hugo Leisink <hugo@leisink.net> Sun, 12 Oct 2014 09:50:07 +0200
monitor (0.8) stable; urgency=low
* Access restrictions per webserver added.
......
......@@ -36,7 +36,6 @@ The Hiawatha Monitor requires the following PHP modules:
Use the following PHP settings:
cgi.fix_pathinfo = 0 (when using FastCGI PHP), 1 (otherwise)
cgi.rfc2616_headers = 1
register_globals = Off
date.timezone = <your timezone>
......@@ -48,13 +47,14 @@ Change the database settings in 'settings/website.conf' and run the script 'data
CONFIGURE CRON DAEMON
======================
Use the cron daemon to fetch the information from the webservers:
Use the cron daemon to fetch the information from the webservers and to send the daily reports:
# Crontab
SHELL=/bin/sh
MAILTO=<account name of which you can read e-mail>
*/5 * * * * /path/to/monitor/website/database/fetch_webserver_logs
3 1 * * * /path/to/monitor/website/database/delete_old_logs
0 0 * * * /path/to/monitor/website/database/delete_old_logs
59 23 * * * /path/to/monitor/website/database/send_reports
USING THE HIAWATHA MONITOR
......
......@@ -44,6 +44,8 @@
}
private function show_user_form($user) {
global $notification_methods;
if (isset($user["roles"]) == false) {
$user["roles"] = array();
}
......@@ -80,6 +82,7 @@
}
$this->output->close_tag();
$user["daily_report"] = show_boolean($user["daily_report"]);
$this->output->record($user, "user");
$this->output->open_tag("roles");
......@@ -100,6 +103,12 @@
}
$this->output->close_tag();
$this->output->open_tag("notification");
foreach ($notification_methods as $method => $label) {
$this->output->add_tag("method", $method, array("label" => $label));
}
$this->output->close_tag();
$this->output->open_tag("webservers");
foreach ($webservers as $webserver) {
$checked = in_array($webserver["id"], $user["webservers"]);
......
......@@ -7,10 +7,6 @@
return;
}
if (($status = $this->model->get_webserver_status()) === false) {
return;
}
$webservers_offline = false;
foreach ($webservers as $webserver) {
......@@ -22,9 +18,10 @@
$webserver["address"] .= "/";
if ($webserver["active"]) {
if (isset($status[$webserver["id"]]) == false) {
$webserver["status"] = "unknown";
} else if (($webserver["status"] = $status[$webserver["id"]]) == "offline") {
if ($webserver["errors"] == 0) {
$webserver["status"] = "online";
} else {
$webserver["status"] = "offline";
$webservers_offline = true;
}
}
......@@ -36,7 +33,7 @@
}
if ($webservers_offline) {
$this->output->add_system_message("Warning, one or more webservers are offline!");
$this->output->add_system_message("Warning, one or more webservers are unavailable!");
}
/* Alerts
......
<?php
class events_controller extends controller {
public function execute() {
if (isset($_SERVER["hide_ss"]) == false) {
$_SERVER["hide_ss"] = true;
}
if (($_SERVER["REQUEST_METHOD"] == "POST") && ($_POST["submit_button"] == "hidess")) {
$_SERVER["hide_ss"] = is_true($_POST["hide_ss"]);
}
$filter = new filter($this->db, $this->output, $this->user);
$filter->to_output($this->model->table, false);
if (($count = $this->model->count_events($filter->webserver)) === false) {
if (($count = $this->model->count_events($filter->webserver, $_SERVER["hide_ss"])) === false) {
$this->output->add_tag("result", "Database error.");
return;
}
......@@ -14,15 +22,16 @@
$paging->reset();
}
if (($events = $this->model->get_events($paging->offset, $paging->size, $filter->webserver)) === false) {
if (($events = $this->model->get_events($paging->offset, $paging->size, $filter->webserver, $_SERVER["hide_ss"])) === false) {
$this->output->add_tag("result", "Database error.");
return;
}
$this->output->open_tag("events");
$this->output->open_tag("events", array("hide_ss" => show_boolean($_SERVER["hide_ss"])));
foreach ($events as $event) {
$event["timestamp"] = date("j F Y, H:i:s", $event["timestamp"]);
$event["event"] = $this->output->secure_string($event["event"], "_");
$this->output->record($event, "event");
}
......
<?php
class profile_controller extends controller {
private function show_profile_form($profile) {
global $notification_methods;
$this->output->add_javascript("md5.js");
$this->output->add_javascript("profile.js");
$this->output->open_tag("edit");
$this->output->add_tag("email", $profile["email"]);
$this->output->add_tag("prowl_key", $profile["prowl_key"]);
$this->output->add_tag("notification_key", $profile["notification_key"]);
$this->output->add_tag("notification_method", $profile["notification_method"]);
$this->output->add_tag("daily_report", show_boolean($profile["daily_report"]));
if ($this->user->status == USER_STATUS_CHANGEPWD) {
$this->output->add_tag("cancel", "Logout", array("page" => LOGOUT_MODULE));
}
$this->output->open_tag("notification");
foreach ($notification_methods as $method => $label) {
$this->output->add_tag("method", $method, array("label" => $label));
}
$this->output->close_tag();
/* Action log
*/
if (($actionlog = $this->model->last_account_logs()) !== false) {
......@@ -43,8 +53,10 @@
}
} else {
$user = array(
"email" => $this->user->email,
"prowl_key" => $this->user->prowl_key);
"email" => $this->user->email,
"notification_key" => $this->user->notification_key,
"notification_method" => $this->user->notification_method,
"daily_report" => $this->user->daily_report);
$this->show_profile_form($user);
}
}
......
#!/usr/bin/php
<?php
define("MAX_CACHE_SIZE", 10);
chdir(dirname($argv[0]));
require("../libraries/configuration.php");
require("../libraries/general.php");
require("../libraries/http.php");
require("../libraries/prowl.php");
require("../libraries/security.php");
/* Database functions
*/
......@@ -49,28 +46,54 @@
return $db->insert("events", $data) !== false;
}
function send_prowl_notification($db, $message) {
static $keys = null;
static $prowl = null;
function send_notification($db, $webserver_id, $message) {
$query = "select u.* from users u, webserver_user l ".
"where u.id=l.user_id and l.webserver_id=%d and notification_method!=%s";
if (($users = $db->execute($query, $webserver_id, "none")) == false) {
return;
}
if ($keys === null) {
$query = "select * from users where prowl_key!=%s";
if (($users = $db->execute($query, "")) == false) {
return;
}
$keys = array();
foreach ($users as $user) {
array_push($keys, $user["prowl_key"]);
$prowl_keys = array();
$nma_keys = array();
$email_keys = array();
foreach ($users as $user) {
switch ($user["notification_method"]) {
case "prowl":
array_push($prowl_keys, $user["notification_key"]);
break;
case "nma":
array_push($nma_keys, $user["notification_key"]);
break;
case "email":
array_push($email_keys, $user["notification_key"]);
break;
}
}
$prowl = new prowl("Hiawatha Monitor", $keys);
/* Prowl
*/
if (count($prowl_keys) > 0) {
$prowl = new prowl("Hiawatha Monitor", $prowl_keys);
$prowl->send_notification("Event log", $message);
}
if ($prowl === null) {
return;
/* Notify My Andriod
*/
if (count($nma_keys) > 0) {
$nma = new NMA("Hiawatha Monitor", $nma_keys);
$nma->send_notification("Event log", $message);
}
$prowl->send_notification("Event log", $message);
/* E-mail
*/
if (count($email_keys) > 0) {
$settings = new settings($db);
$email = new email("Hiawatha Monitor event log", $settings->webmaster_email);
$email->message($message);
foreach ($email_keys as $address) {
$email->send($address);
}
}
}
/* HTTP class extensions
......@@ -129,7 +152,7 @@
}
$message = sprintf("Webserver %s appears to be offline.", $webserver["name"]);
send_prowl_notification($db, $message);
send_notification($db, $webserver_id, $message);
continue;
}
......@@ -333,7 +356,7 @@
*/
case "event";
list(, $event, $timestamp) = $field;
log_event($db, $event, $webserver_id, $timestamp);
log_event($db, $event, $webserver_id, (int)$timestamp);
break;
/* Version
*/
......@@ -346,7 +369,7 @@
*/
default:
list($event, $timestamp) = $field;
log_event($db, $event, $webserver_id, $timestamp);
log_event($db, $event, $webserver_id, (int)$timestamp);
}
}
}
......
-- MySQL dump 10.13 Distrib 5.5.37, for debian-linux-gnu (x86_64)
-- MySQL dump 10.13 Distrib 5.5.38, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: hiawatha_monitor
-- ------------------------------------------------------
-- Server version 5.5.37-0ubuntu0.12.04.1
-- Server version 5.5.38-0ubuntu0.14.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
......@@ -338,7 +338,9 @@ CREATE TABLE `users` (
`status` tinyint(4) unsigned NOT NULL DEFAULT '0',
`fullname` varchar(50) NOT NULL DEFAULT '',
`email` varchar(50) NOT NULL DEFAULT '',
`prowl_key` varchar(50) NOT NULL,
`notification_key` varchar(50) NOT NULL,
`notification_method` enum('none','prowl','nma','email') NOT NULL,
`daily_report` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
......@@ -350,7 +352,7 @@ CREATE TABLE `users` (
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES (1,'admin','08b5411f848a2581a41672a759c87380',NULL,1,'Administrator','root@localhost','');
INSERT INTO `users` VALUES (1,'admin','08b5411f848a2581a41672a759c87380',NULL,1,'Administrator','root@localhost','','none',0);
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
......@@ -401,4 +403,4 @@ CREATE TABLE `webservers` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2014-06-01 8:23:08
-- Dump completed on 2014-10-07 10:00:37
#!/usr/bin/php
<?php
define("HISTORY_DAYS", 15);
define("ALERT_MEDIUM", 100);
define("ALERT_HIGH", 200);
chdir(dirname($argv[0]));
require("../libraries/configuration.php");
require("../libraries/security.php");
/* Dummy user class
*/
class dummy_user {
private $id = null;
public function __construct($user_id) {
$this->id = $user_id;
}
public function __get($key) {
switch ($key) {
case "id": return $this->id;
}
return null;
}
}
/* Model class
*/
class stats_model extends graph_model {
public function __construct($db, $user, $table, $columns, $hostnames) {
$this->table = $table;
$this->columns = $columns;
$this->hostnames = $hostnames;
parent::__construct($db, null, $user, null, null);
}
}
/* Connect to database
*/
$db = new MySQLi_connection(DB_HOSTNAME, DB_DATABASE, DB_USERNAME, DB_PASSWORD);
if ($db->connected == false) {
exit("Internal error: database not available.\n");
}
$settings = new settings($db);
/* Get information about webserver
*/
function get_information($db, $table, $columns, $user_id, $webserver_id, $hostnames) {
$user = new dummy_user($user_id);
$model = new stats_model($db, $user, $table, $columns, $hostnames);
$today = date("Y-m-d");
$begin = date("Y-m-d", strtotime("-".(HISTORY_DAYS - 1)." days"));
$end = date("Y-m-d", strtotime("tomorrow"));
if (($stats = $model->get_statistics($begin, $end, 0, $webserver_id)) === false) {
return false;
}
$result = array();
foreach ($columns as $column) {
$result[$column] = array("today" => 0, "previous" => 0);
}
foreach ($stats as $day => $stat) {
$type = ($day == $today) ? "today" : "previous";
foreach ($stat as $column => $value) {
$result[$column][$type] += $value;
}
}
foreach ($result as $i => $stat) {
$result[$i]["previous"] = round($stat["previous"] / (HISTORY_DAYS - 1));
$result[$i]["alert"] = "none";
if ($result[$i]["previous"] != 0) {
$percentage = round($stat["today"] / $result[$i]["previous"] * 100) - 100;
if ($percentage >= ALERT_HIGH) {
$result[$i]["alert"] = "high";
} else if ($percentage >= ALERT_MEDIUM) {
$result[$i]["alert"] = "medium";
}
$result[$i]["percentage"] = $percentage." %";
} else if ($result["today"] == 0) {
$result[$i]["percentage"] = "0 %";
} else {
$result[$i]["percentage"] = "&#8734;";
}
}
return $result;
}
/* Generate report for webserver
*/
function generate_report($db, $user, $webserver) {
static $reports = array();
if (isset($reports[$webserver["id"]])) {
return $reports[$webserver["id"]];
}
$stats = array(
"Request statistics" => array(
"table" => "host_statistics",
"hostnames" => true,
"details" => array(
"requests" => "Requests",
"bytes_sent" => "Bytes sent",
"result_forbidden" => "Forbidden",
"result_not_found" => "Not Found",
"result_internal_error" => "Internal Server Error")),
"Security statistics" => array(
"table" => "host_statistics",
"hostnames" => true,
"details" => array(
"bans" => "Bans",
"exploit_attempts" => "Exploit attempts")),
"CGI statistics" => array(
"table" => "cgi_statistics",
"hostnames" => true,
"details" => array(
"time_0_1" => "0 - 1 second",
"time_1_3" => "1 - 3 seconds",
"time_3_10" => "3 - 10 seconds",
"time_10_x" => "More than 10 seconds",
"cgi_errors" => "CGI errors")),
"Server statistics" => array(
"table" => "server_statistics",
"hostnames" => false,
"details" => array(
"connections" => "Connections",
"result_bad_request" => "Bad Requests")));
$report = array(
"content" => "<h2>".$webserver["name"]."</h2>\n",
"alerts" => array());
foreach ($stats as $label => $stat) {
$report["content"] .= "<h3>".$label."</h3>\n";
$report["content"] .= "<table class=\"stats\">\n";
$report["content"] .= "<tr><th>Type</th><th>Value</th><th>= &#916;% of</th><th>Average</th></tr>\n";
$columns = array_keys($stat["details"]);
if (($information = get_information($db, $stat["table"], $columns, $user["id"], $webserver["id"], $stat["hostnames"])) == false) {
continue;
}
foreach ($information as $column => $value) {
$today = graph_model::readable_number($value["today"]);
$previous = graph_model::readable_number($value["previous"]);
$report["content"] .= "<tr><td>".$stat["details"][$column].":</td>".
"<td class=\"alert_".$value["alert"]."\">".$today."</td>".
"<td>".$value["percentage"]."</td><td>".$previous."</td></tr>\n";
if ($value["alert"] != "none") {
array_push($report["alerts"], $webserver["name"]);
}
}
$report["content"] .= "</table>\n";
}
$query = "select event, UNIX_TIMESTAMP(timestamp) as timestamp from events ".
"where date(timestamp)=date(now()) and webserver_id=%d order by timestamp";
if (($events = $db->execute($query, $webserver["id"])) != false) {
$report["content"] .= "<h3>Events</h3>\n";
$report["content"] .= "<table class=\"events\">\n";
$report["content"] .= "<tr><th>Time</th><th>Event</th></tr>\n";
foreach ($events as $event) {
$report["content"] .= "<tr><td>".date("H:i:s", $event["timestamp"])."</td><td>".$event["event"]."</td></tr>\n";
}
$report["content"] .= "</table>\n";
}
$reports[$webserver["id"]] = $report;
return $report;
}
/* Select users which want to receive daily reports
*/
$query = "select * from users where daily_report=%d";
if (($users = $db->execute($query, YES)) == false) {
return;
}
/* Send reports per user
*/
if (($template = file_get_contents("../extra/report.html")) === false) {
return;
}
$query = "select * from webservers w, webserver_user l ".
"where w.id=l.webserver_id and l.user_id=%d";
foreach ($users as $user) {
if (($webservers = $db->execute($query, $user["id"])) == false) {
continue;
}
$report_content = "";
$report_alerts = array();
foreach ($webservers as $webserver) {
if (($report = generate_report($db, $user, $webserver)) == false) {
continue;
}
$report_content .= $report["content"];
$report_alerts = array_unique(array_merge($report_alerts, $report["alerts"]), SORT_STRING);
}
$count = count($report_alerts);
if ($count == 0) {
$report_alerts = "-";
} else {
if ($count >= 2) {
$last = " and ".array_pop($report_alerts);
} else {
$last = "";
}
$report_alerts = implode(", ", $report_alerts).$last;
}
$replace = array(
"ALERTS" => $report_alerts,
"CONTENT" => $report_content,
"HISTORY" => HISTORY_DAYS - 1,
"TIMESTAMP" => date("j F Y, H:i (O)"));
$email = new email("Daily Hiawatha Monitor report", $settings->webmaster_email);
$email->set_message_fields($replace);
$email->message($template);
$email->send($user["email"], $user["fullname"]);
unset($email);
}
?>
<html>
<head>
<style type="text/css">
body {
font-family:courier-sans;
background-color:#c0c0c0;
font-size:9pt;
padding:20px 0 20px 0;
}
div.container {
max-width:550px;
background-color:#ffffff;
box-shadow:15px 10px 5px #606060;
padding:30px 40px;
margin:0 auto;
}
p {
margin:20px 0 0 0;
}
h1 {
font-size:16pt;
color:#1010c0;
margin:0;
}
h2 {
font-size:14pt;
color:#6060ff;
padding-bottom:5px;
margin-bottom:0;
border-bottom:1px dashed #ff8000;
}
h3 {
font-size:12pt;
margin-bottom:0;
}
table {
font-size:inherit;
}
table th {
background-color:#c0c0ff;
padding:0 5px;
}
table th:first-child {
text-align:left;
}
table td {
background-color:#e0e0ff;
padding:0 5px;
}
table td.alert_medium {
color:#ff8000;
}
table td.alert_high {
color:#ff0000;
}
table.stats td:first-child {
width:200px;
}
table.stats td:nth-child(n+2) {
width:75px;
text-align:right;
}
table.events th:first-child {
width:75px;
}
table.events th:nth-child(2) {
width:375px;
}
</style>
</head>
<body>
<div class="container">
<h1>Daily Hiawatha Monitor report</h1>
<p>Alerts in this report for: [ALERTS]</p>
[CONTENT]
<p>The average is calculated over the last [HISTORY] days. This report was generated at [TIMESTAMP].</p>
</div>
</body>
</html>
......@@ -9,7 +9,7 @@
/* For internal usage. Only change if you know what you're doing!
*/
define("BANSHEE_VERSION", "3.0");
define("MONITOR_VERSION", "0.8");
define("MONITOR_VERSION", "1.0");
define("ADMIN_ROLE_ID", 1);
define("YES", 1);
define("NO", 0);
......@@ -50,6 +50,12 @@
$days_of_week = array("monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday");
$notification_methods = array(
"none" => "No notification",
"prowl" => "Prowl",
"nma" => "Notify My Android",
"email" => "E-mail");
/* Auto class loader
*
* INPUT: string class name
......
......@@ -13,9 +13,13 @@
protected $from = null;
protected $reply_to = null;
protected $subject = null;
protected $messages = array();
protected $text_message = null;
protected $html_message = null;
protected $attachments = array();
protected $images = array();
protected $sender_address = null;
protected $message_fields = array();
protected $field_format = "[%s]";
/* Constructor
*
......@@ -40,6 +44,13 @@
* ERROR: -
*/
public static function valid_address($email) {
$forbidden = array("mailinator.com");
list(, $domain) = explode("@", $email, 2);
if (in_array($domain, $forbidden)) {
return false;
}
return preg_match("/^[0-9A-Za-z]([-_.~]?[0-9A-Za-z])*@[0-9A-Za-z]([-.]?[0-9A-Za-z])*\\.[A-Za-z]{2,4}$/", $email) === 1;
}
......@@ -52,7 +63,7 @@
protected function make_address($address, $name) {
$address = strtolower($address);
if ($name === null) {
if ($name == null) {
return $address;
}
......@@ -147,22 +158,28 @@
* OUTPUT: -
* ERROR: -
*/
public function message($message, $content_type = null) {
public function message($message) {
$message = str_replace("\r\n", "\n", $message);
if ((substr($message, 0, 6) == "<body>") && (substr(rtrim($message), -7) == "</body>")) {
$message = "<html>\n".rtrim($message)."\n</html>";
}
/* Determine message mimetype
*/
if ($content_type === null) {
if ((substr($message, 0, 6) == "<html>") && (substr(rtrim($message), -7) == "</html>")) {
$content_type = "text/html";
} else {
$content_type = "text/plain";
if ((substr($message, 0, 6) == "<html>") && (substr(rtrim($message), -7) == "</html>")) {
$this->html_message = $message;