1#!/usr/local/bin/php
2<?php
3	chdir(dirname($argv[0]));
4	require("../libraries/banshee.php");
5	require("../libraries/security.php");
6	require("../libraries/settings.php");
7
8	/* Dummy user class
9	 */
10	class dummy_user {
11		private $id = null;
12
13		public function __construct($user_id) {
14			$this->id = $user_id;
15		}
16
17		public function __get($key) {
18			switch ($key) {
19				case "id": return $this->id;
20			}
21
22			return null;
23		}
24	}
25
26	/* Model class
27	 */
28	class stats_model extends graph_model {
29		public function __construct($db, $user, $table, $columns, $hostnames) {
30			$this->table = $table;
31			$this->columns = $columns;
32			$this->hostnames = $hostnames;
33
34			parent::__construct($db, null, $user, null, null);
35		}
36	}
37
38	/* Filter class
39	 */
40	class filter {
41		private $webserver_id = null;
42
43		public function __construct($webserver_id) {
44			$this->webserver_id = $webserver_id;
45		}
46
47		public function __get($key) {
48			switch ($key) {
49				case "webserver": return $this->webserver_id;
50				case "hostname" : return 0;
51				case "hour_from": return 0;
52				case "hour_to"  : return 23;
53			}
54
55			return null;
56		}
57	}
58
59	/* Calculate median
60	 */
61	function get_median($data, $key) {
62		$values = array();
63
64		foreach ($data as $item) {
65			array_push($values, $item[$key]);
66		}
67		sort($values);
68
69		$count = count($values);
70		if ($count & 1 == 1) {
71			$center = ($count + 1) / 2;
72			$median = $values[$center];
73		} else {
74			$center = $count / 2;
75			$median = ($values[$center] + $values[$center + 1]) / 2;
76		}
77
78		return $median;
79	}
80
81	/* Main program
82	 */
83	error_reporting(E_ALL & ~E_NOTICE);
84
85	/* Connect to database
86	 */
87	$db = new MySQLi_connection(DB_HOSTNAME, DB_DATABASE, DB_USERNAME, DB_PASSWORD);
88	if ($db->connected == false) {
89		exit("Internal error: database not available.\n");
90	}
91
92	$settings = new settings($db);
93
94	/* Get information about webserver
95	 */
96	function get_information($db, $settings, $table, $columns, $user_id, $webserver_id, $hostnames) {
97		$user = new dummy_user($user_id);
98		$model = new stats_model($db, $user, $table, $columns, $hostnames);
99		$filter = new filter($webserver_id);
100
101		$begin = date("Y-m-d", strtotime("-".($settings->report_history_days - 1)." days"));
102		$today = date("Y-m-d");
103
104		if (($stats = $model->get_statistics($begin, $today, $filter)) === false) {
105			return false;
106		}
107
108		$result = array();
109		foreach ($columns as $column) {
110			$result[$column] = array("today" => 0, "previous" => 0);
111		}
112
113		foreach ($stats as $day => $stat) {
114			$type = ($day == $today) ? "today" : "previous";
115			foreach ($stat as $column => $value) {
116				$result[$column][$type] += $value;
117			}
118		}
119
120		foreach ($result as $idx => $stat) {
121			if ($settings->report_use_median) {
122				$result[$idx]["previous"] = get_median($stats, $idx);
123			} else {
124				$result[$idx]["previous"] = round($stat["previous"] / ($settings->report_history_days - 1));
125			}
126
127			if ($result[$idx]["previous"] != 0) {
128				$change = round(($stat["today"] - $result[$idx]["previous"]) / $result[$idx]["previous"], 2);
129				$result[$idx]["change"] = $change." x";
130			} else if ($result["today"] == 0) {
131				$change = $stat["today"];
132				$result[$idx]["change"] = $change." x";
133			} else {
134				$change = 0;
135				$result[$idx]["change"] = "&#8734;";
136			}
137
138			if ($change >= $settings->report_alert_high) {
139				$result[$idx]["alert"] = "high";
140			} else if ($change >= $settings->report_alert_medium) {
141				$result[$idx]["alert"] = "medium";
142			} else {
143				$result[$idx]["alert"] = "none";
144			}
145		}
146
147		return $result;
148	}
149
150	/* Generate report for webserver
151	 */
152	function generate_report($db, $settings, $user, $webserver) {
153		static $reports = array();
154
155		if (isset($reports[$webserver["id"]])) {
156			return $reports[$webserver["id"]];
157		}
158
159		$stats = array(
160			"Request statistics" => array(
161				"table"     => "host_statistics",
162				"hostnames" => true,
163				"details"   => array(
164					"requests"              => "Requests",
165					"bytes_sent"            => "Bytes sent",
166					"result_forbidden"      => "Forbidden",
167					"result_not_found"      => "Not Found",
168					"result_internal_error"	=> "Internal Server Error")),
169			"Security statistics" => array(
170				"table"     => "host_statistics",
171				"hostnames" => true,
172				"details"   => array(
173					"exploit_attempts"      => "Exploit attempts",
174					"failed_logins"         => "Failed logins",
175					"bans"                  => "Bans")),
176			"CGI statistics" => array(
177				"table"     => "cgi_statistics",
178				"hostnames" => true,
179				"details"   => array(
180					"cgi_errors"            => "CGI errors",
181					"time_0_1"              => "0 - 1 second",
182					"time_1_3"              => "1 - 3 seconds",
183					"time_3_10"             => "3 - 10 seconds",
184					"time_10_x"             => "More than 10 seconds")),
185			"Server statistics" => array(
186				"table"     => "server_statistics",
187				"hostnames" => false,
188				"details"   => array(
189					"connections"           => "Connections",
190					"result_bad_request"    => "Bad Requests")));
191
192		$report = array(
193			"content" => "<h2>".$webserver["name"]."</h2>\n",
194			"alerts"  => array());
195		$type = $settings->report_use_median ? "Median" : "Average";
196
197		$add_count = 0;
198		foreach ($stats as $label => $stat) {
199			$header = "<h3>".$label."</h3>\n".
200				"<table class=\"stats\">\n".
201				"<thead>\n<tr><th>Type</th><th>Value</th><th>= &#916; of</th><th>".$type."</th></tr>\n</thead>\n<tbody>\n";
202			$header_set = false;
203
204			$columns = array_keys($stat["details"]);
205			if (($information = get_information($db, $settings, $stat["table"], $columns, $user["id"], $webserver["id"], $stat["hostnames"])) == false) {
206				continue;
207			}
208
209			foreach ($information as $column => $value) {
210				if ($value["change"] < $settings->report_alert_medium) {
211					if ($settings->report_skip_normal) {
212						continue;
213					}
214				}
215
216				$add_count++;
217
218				if ($header_set == false) {
219					$report["content"] .= $header;
220					$header_set = true;
221				}
222
223				$today = graph_model::readable_number($value["today"]);
224				$previous = graph_model::readable_number($value["previous"]);
225				$report["content"] .= "<tr><td>".$stat["details"][$column].":</td>".
226					"<td class=\"alert_".$value["alert"]."\">".$today."</td>".
227					"<td>".$value["change"]."</td><td>".$previous."</td></tr>\n";
228				if ($value["alert"] != "none") {
229					array_push($report["alerts"], $webserver["name"]);
230				}
231			}
232
233			if ($header_set) {
234				$report["content"] .= "</tbody>\n</table>\n";
235			}
236		}
237
238		$query = "select event, UNIX_TIMESTAMP(timestamp) as timestamp from events ".
239		         "where date(timestamp)=date(now()) and webserver_id=%d order by timestamp";
240		if (($events = $db->execute($query, $webserver["id"])) != false) {
241			$report["content"] .= "<h3>Events</h3>\n";
242			$report["content"] .= "<table class=\"events\">\n";
243			$report["content"] .= "<thead>\n<tr><th>Time</th><th>Event</th></tr>\n</thead>\n<tbody>\n";
244
245			foreach ($events as $event) {
246				$report["content"] .= "<tr><td>".date("H:i:s", $event["timestamp"])."</td><td>".htmlentities($event["event"])."</td></tr>\n";
247			}
248
249			$report["content"] .= "</tbody>\n</table>\n";
250
251			$add_count++;
252		}
253
254		if ($add_count == 0) {
255			$report["content"] .= "<p>Nothing to report.</p>\n";
256		}
257
258		$reports[$webserver["id"]] = $report;
259
260		return $report;
261	}
262
263	/* Main program
264	 */
265	error_reporting(E_ALL & ~E_NOTICE);
266
267	/* Select users which want to receive daily reports
268	 */
269	if (count($argv) > 1) {
270		$args = array_slice($argv, 1);
271		$filter = array_fill(0, count($args), "%s");
272		$query = "select * from users where username in (".implode(", ", $filter).")";
273	} else {
274		$query = "select * from users where daily_report=%d";
275		$args = array(YES);
276	}
277
278	if (($users = $db->execute($query, $args)) == false) {
279		return;
280	}
281
282	/* Send reports per user
283	 */
284	if (($template = file_get_contents("../extra/report.html")) === false) {
285		return;
286	}
287
288	$query = "select w.* from webservers w, webserver_user l ".
289	         "where w.id=l.webserver_id and l.user_id=%d order by w.name";
290	foreach ($users as $user) {
291		if (($webservers = $db->execute($query, $user["id"])) == false) {
292			continue;
293		}
294
295		$report_content = "";
296		$report_alerts = array();
297		foreach ($webservers as $webserver) {
298			if (($report = generate_report($db, $settings, $user, $webserver)) === false) {
299				continue;
300			}
301			$report_content .= $report["content"];
302			$report_alerts = array_unique(array_merge($report_alerts, $report["alerts"]), SORT_STRING);
303		}
304
305		$count = count($report_alerts);
306		if ($count == 0) {
307			$report_alerts = "none";
308		} else {
309			if ($count >= 2) {
310				$last = " and ".array_pop($report_alerts);
311			} else {
312				$last = "";
313			}
314			$report_alerts = implode(", ", $report_alerts).$last;
315		}
316
317		$replace = array(
318			"ALERTS"    => $report_alerts,
319			"CONTENT"   => $report_content,
320			"TYPE"      => $settings->report_use_median ? "median" : "average",
321			"HISTORY"   => $settings->report_history_days - 1,
322			"TIMESTAMP" => date("j F Y, H:i (O)"));
323
324		$email = new email("Daily Hiawatha Monitor report", $settings->webmaster_email);
325		$email->set_message_fields($replace);
326		$email->message($template);
327		$email->send($user["email"], $user["fullname"]);
328		unset($email);
329	}
330?>
331