#!/usr/bin/php
<?php
	chdir(dirname($argv[0]));
	require("../libraries/configuration.php");
	require("../libraries/general.php");
	require("../libraries/security.php");

	/* Database functions
	 */
	function get_version($db, $webserver_id) {
		static $cache = array();

		if (isset($cache[$webserver_id]) == false) {
			$query = "select version from webservers where id=%d";
			if (($result = $db->execute($query, $webserver_id)) == false) {
				return false;
			}

			list($version) = explode(",", $result[0]["version"], 2);
			list(, $version) = explode("v", $version);

			$cache[$webserver_id] = $version;
		}

		return $cache[$webserver_id];
	}

	function get_foreign_key($db, $table, $key, $value) {
		static $cache = array();

		$index = implode(":", array($table, $key, $value));
		if (isset($cache[$index])) {
			return $cache[$index];
		}

		$query = "select id from %S where %S=%s";
		if (($result = $db->execute($query, $table, $key, $value)) != false) {
			$cache[$index] = (int)$result[0]["id"];
		} else {
			if ($db->insert($table, array("id" => null, $key => $value)) == false) {
				return false;
			}
			$cache[$index] = $db->last_insert_id;
		}

		return $cache[$index];
	}

	function get_hostname_id($db, $value) {
		return get_foreign_key($db, "hostnames", "hostname", $value);
	}

	function log_event($db, $event, $webserver_id, $timestamp = null) {
		if ($timestamp == null) {
			$timestamp = time();
		}

		$data = array(
			"id"           => null,
			"timestamp"    => date("Y-m-d H:i:s", $timestamp),
			"webserver_id" => (int)$webserver_id,
			"event"        => $event);

		return $db->insert("events", $data) !== false;
	}

	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;
		}

		$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
		 */
		if (count($prowl_keys) > 0) {
			$prowl = new prowl("Hiawatha Monitor", $prowl_keys);
			$prowl->send_notification("Event log", $message);
		}

		/* Notify My Andriod
		 */
		if (count($nma_keys) > 0) {
			$nma = new NMA("Hiawatha Monitor", $nma_keys);
			$nma->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
	 */
	class HTTP_monitor extends HTTP {
		public function __call($method, $parameters) {
			$this->host = "monitor";
			return parent::__call($method, $parameters);
		}
	}

	class HTTPS_monitor extends HTTPS {
		public function __call($method, $parameters) {
			$this->host = "monitor";
			return parent::__call($method, $parameters);
		}
	}

	/* Main program
	 */
	error_reporting(E_ALL & ~E_NOTICE);

	$debug_mode = ($argv[1] == "-d");

	$db = new MySQLi_connection(DB_HOSTNAME, DB_DATABASE, DB_USERNAME, DB_PASSWORD);
	if ($db->connected == false) {
		exit("Internal error: database not available.\n");
	}

	if (($webservers = $db->execute("select * from webservers where active=%d", 1)) === false) {
		exit("Error retrieving webserver IP addresses.\n");
	}

	foreach ($webservers as $webserver) {
		if (isset($http)) {
			unset($http);
		}
		if (is_true($webserver["tls"]) == false) {
			$http = new HTTP_monitor($webserver["ip_address"], $webserver["port"]);;
		} else {
			$http = new HTTPS_monitor($webserver["ip_address"], $webserver["port"]);;
		}

		$webserver_id = (int)$webserver["id"];

		if ($debug_mode) {
			printf("Retrieving index from %s...\n", $webserver["name"]);
		}
		$result = $http->GET("/");
		if ($result["status"] != 200) {
			$db->update("webservers", $webserver_id, array("errors" => $webserver["errors"] + 1));
			$event = "Error while getting statistics logfile index from webserver";
			log_event($db, $event, $webserver_id);
			if ($debug_mode) {
				printf("%s\n", $event);
			}

			$message = sprintf("Webserver %s appears to be offline.", $webserver["name"]);
			send_notification($db, $webserver_id, $message);
			continue;
		}

		if ($webserver["errors"] != 0) {
			$db->update("webservers", $webserver_id, array("errors" => 0));
		}

		if ($result["headers"]["Content-Type"] != "text/xml") {
			continue;
		}

		$index = new DomDocument();
		if ($index->loadXML($result["body"]) == false) {
			continue;
		}

		$files = $index->getElementsByTagName("file");

		foreach ($files as $file) {
			if ($debug_mode) {
				printf("Downloading %s...\n", $file->nodeValue);
			}
			$result = $http->GET("/".$file->nodeValue);
			if ($result["status"] != 200) {
				$event = "Error while getting data file from webserver";
				log_event($db, $event, $webserver_id);
				if ($debug_mode) {
					printf("%s\n", $event);
				}
				continue;
			}
			if ($result["headers"]["Content-Type"] == "application/x-gzip") {
				if (($result["body"] = gzdecode($result["body"])) === false) {
					printf("Invalid gzipped data received from %s.\n", $webserver["name"]);
					continue;
				}
			}

			$loglines = explode("\n", chop($result["body"]));

			foreach ($loglines as $logline) {
				if ($debug_mode) {
					printf("Processing [%s]\n", $logline);
				}
				$field = explode("\t", chop($logline));

				switch ($field[0]) {
					/* Request (obsolete)
					 */
					case "request":
						break;
					/* CGI runtime
					 */
					case "cgi":
						list(, $timestamp_begin, $timestamp_end, $hostname, $time_0_1,
						       $time_1_3, $time_3_10, $time_10_x, $cgi_errors) = $field;

						if (($hostname_id = get_hostname_id($db, $hostname)) == false) {
							print "Error getting hostname id\n";
							continue;
						}

						$day = date("Y-m-d", (int)$timestamp_begin);
						if ($day === date("Y-m-d", (int)$timestamp_end)) {
							/* Check for existing log on same day
							 */
							$query = "select * from cgi_statistics where webserver_id=%d and hostname_id=%d and ".
									 "timestamp_begin>=%s and timestamp_end<=%s limit 1";
							$result = $db->execute($query, $webserver_id, $hostname_id, $day." 00:00:00", $day." 23:59:59");
						} else {
							/* Log overlaps midnight
							 */
							$result = false;
						}

						if ($result == false) {
							/* Insert new CGI record
							 */
							$db->insert("cgi_statistics", array(
								"id"                    => null,
								"timestamp_begin"       => date("Y-m-d H:i:s", (int)$timestamp_begin),
								"timestamp_end"         => date("Y-m-d H:i:s", (int)$timestamp_end),
								"webserver_id"          => (int)$webserver_id,
								"hostname_id"           => (int)$hostname_id,
								"time_0_1"              => (int)$time_0_1,
								"time_1_3"              => (int)$time_1_3,
								"time_3_10"             => (int)$time_3_10,
								"time_10_x"             => (int)$time_10_x,
								"cgi_errors"            => (int)$cgi_errors));
						} else {
							/* Update existing CGI record
							 */
							$current = $result[0];
							$data = array(
								"timestamp_end"         => date("Y-m-d H:i:s", (int)$timestamp_end),
								"time_0_1"              => (int)($current["time_0_1"] + $time_0_1),
								"time_1_3"              => (int)($current["time_1_3"] + $time_1_3),
								"time_3_10"             => (int)($current["time_3_10"] + $time_3_10),
								"time_10_x"             => (int)($current["time_10_x"] + $time_10_x),
								"cgi_errors"            => (int)($current["cgi_errors"] + $cgi_errors));
							$db->update("cgi_statistics", $current["id"], $data);
						}
						break;
					/* Server statistic
					 */
					case "server":
						list(, $timestamp_begin, $timestamp_end, $connections, $result_bad_request) = $field;

						if ($result_bad_request === NULL) {
							break;
						}

						$day = date("Y-m-d", (int)$timestamp_begin);
						if ($day === date("Y-m-d", (int)$timestamp_end)) {
							/* Check for existing log on same day
							 */
							$query = "select * from server_statistics where webserver_id=%d and ".
									 "timestamp_begin>=%s and timestamp_end<=%s limit 1";
							$result = $db->execute($query, $webserver_id, $day." 00:00:00", $day." 23:59:59");
						} else {
							/* Log overlaps midnight
							 */
							$result = false;
						}

						if ($result == false) {
							/* Insert new server record
							 */
							$db->insert("server_statistics", array(
								"id"                 => null,
								"timestamp_begin"    => date("Y-m-d H:i:s", (int)$timestamp_begin),
								"timestamp_end"      => date("Y-m-d H:i:s", (int)$timestamp_end),
								"webserver_id"       => (int)$webserver_id,
								"connections"        => (int)$connections,
								"result_bad_request" => (int)$result_bad_request));
						} else {
							/* Update existing server record
							 */
							$current = $result[0];
							$data = array(
								"timestamp_end"      => date("Y-m-d H:i:s", (int)$timestamp_end),
								"connections"        => (int)($current["connections"] + $connections),
								"result_bad_request" => (int)($current["result_bad_request"] + $result_bad_request));
							$db->update("server_statistics", $current["id"], $data);
						}
						break;
					/* Host statistic
					 */
					case "host":
						if (($webserver_version = get_version($db, $webserver_id)) == false) {
							$lower_than_9_13 = true;
						} else {
							list($webserver_version) = explode("-", $webserver_version);
							$lower_than_9_13 = version_compare($webserver_version, "9.13", "<");
						}

						if ($lower_than_9_13) {
							list(, $timestamp_begin, $timestamp_end, $hostname, $requests, $bytes_sent, $bans, $exploit_attempts,
								   $result_forbidden, $result_not_found, $result_internal_error) = $field;
							$failed_logins = 0;
						} else {
							list(, $timestamp_begin, $timestamp_end, $hostname, $requests, $bytes_sent, $bans, $exploit_attempts,
						   	    $failed_logins, $result_forbidden, $result_not_found, $result_internal_error) = $field;
						}

						if (($hostname_id = get_hostname_id($db, $hostname)) == false) {
							print "Error getting hostname id\n";
							continue;
						}

						$day = date("Y-m-d", (int)$timestamp_begin);
						if ($day === date("Y-m-d", (int)$timestamp_end)) {
							/* Check for existing log on same day
							 */
							$query = "select * from host_statistics where webserver_id=%d and hostname_id=%d and ".
									 "timestamp_begin>=%s and timestamp_end<=%s limit 1";
							$result = $db->execute($query, $webserver_id, $hostname_id, $day." 00:00:00", $day." 23:59:59");
						} else {
							/* Log overlaps midnight
							 */
							$result = false;
						}

						if ($result == false) {
							/* Insert new host record
							 */
							$db->insert("host_statistics", array(
								"id"                    => null,
								"timestamp_begin"       => date("Y-m-d H:i:s", (int)$timestamp_begin),
								"timestamp_end"         => date("Y-m-d H:i:s", (int)$timestamp_end),
								"webserver_id"          => (int)$webserver_id,
								"hostname_id"           => (int)$hostname_id,
								"requests"              => (int)$requests,
								"bytes_sent"            => (int)$bytes_sent,
								"bans"                  => (int)$bans,
								"exploit_attempts"      => (int)$exploit_attempts,
								"failed_logins"         => (int)$failed_logins,
								"result_forbidden"      => (int)$result_forbidden,
								"result_not_found"      => (int)$result_not_found,
								"result_internal_error" => (int)$result_internal_error));
						} else {
							/* Update existing host record
							 */
							$current = $result[0];
							$data = array(
								"timestamp_end"         => date("Y-m-d H:i:s", (int)$timestamp_end),
								"requests"              => (int)($current["requests"] + $requests),
								"bytes_sent"            => (int)($current["bytes_sent"] + $bytes_sent),
								"bans"                  => (int)($current["bans"] + $bans),
								"exploit_attempts"      => (int)($current["exploit_attempts"] + $exploit_attempts),
								"failed_logins"         => (int)($current["failed_logins"] + $failed_logins),
								"result_forbidden"      => (int)($current["result_forbidden"] + $result_forbidden),
								"result_not_found"      => (int)($current["result_not_found"] + $result_not_found),
								"result_internal_error" => (int)($current["result_internal_error"] + $result_internal_error));
							$db->update("host_statistics", $current["id"], $data);
						}
						break;
					/* Event
					 */
					case "event";
						list(, $event, $timestamp) = $field;
						log_event($db, $event, $webserver_id, (int)$timestamp);
						break;
					/* Version
					 */
					case "version":
						list(, $version) = $field;
						$data = array("version" => $version);
						$db->update("webservers", $webserver_id, $data);
						break;
					/* Rest
					 */
					default:
						list($event, $timestamp) = $field;
						log_event($db, $event, $webserver_id, (int)$timestamp);
				}
			}
		}
	}
?>
