==
== Created: Wednesday, July 2nd 2008 ==
== Modified: Monday, January 12th 2009 ==
== License: Creative Commons Attribution-Noncommercial 3.0 United States ==
== License URL: http://creativecommons.org/licenses/by-nc/3.0/us/ ==
\***** File Info *****/
/*** Changelog ***\
== 1.1.0 ==
= Public release
=
== 1.1.1 & 1.1.2 ==
= Display fix: Fixed minor bugs regarding display of information.
=
== 1.1.3 ==
= New Feature: Different e-mail subject, sender, and recipient for Reports and Alerts.
= Bug fix: Site monitor would show incorrect information in most cases where more than one site was monitored.
= Display fix: E-mail report would show 'MB' in RAM information in a new line.
=
== 1.1.4 ==
= New Feature: Added User Agent "MOSMC-PHP" when checking sites.
= Bux fix: Minor. Related to services checking. Did not affect functionality.
=
== 1.1.5 (07/22/2008) ==
= Bug fix: PHP error. Might be major bug if running through cron.
=
== 2.0.0 (01/11/2009) ==
= Update: Re-wrote MOSMC in OOP.
= Update: Re-worded some of the comments to make clearer what MOSMC is doing.
= Update: Documentation is now more specific, and has been re-worded to make it clearer.
= New Feature: Added configuration option: hard drive partition to use for hard drive usage stats.
= New Feature: Integrated HTTP authentication.
= New Feature: Notification when new MOSMC release is available (optional; See configuration options).
= New Feature: Test variables simulating various scenarios have been included. Search for "Testing Variables" in the source. Note: Email and Browser output testing variables are separated.
= Bug fix: PHP mail() would not set the Return-Path header properly, often required for SPF. Now fixed, using sender email as return path address.
= Bug fix: Service listening check now returns proper results. In limited cases, it would show a service as listening when it wasn't.
= Bug fix: Only first word of HTTP status description would be shown.
= Bug fix: Now handles DNS errors and cURL errors properly, and when the server hangs connections (an alert is triggered).
= Bug fix: When testing sites, a 30 second timeout is used. Previously, MOSMC would seem to hang if a site was not responding.
= Security fix: Vulnerability when register_globals is enabled in PHP configuration.
= Display fix: Alert SMS output has updated formatting for improved readability.
= Misc fix: Cleaned up HTML output formatting.
= Misc fix: Cleaned up PHP source code formatting.
=
== 2.0.1 (01/12/2008) ==
= Update: Modified about a dozen areas of PHP source code to improve performance, and removed some unnecessary code.
= Update: Added updating instructions in documentation.
= New Feature: Configuration option ($this->web->auth) allows disabling HTTP authentication on Web console (Not recommended if others can access MOSMC).
= Bug fix: In certain software configurations, MOSMC's Web console would fail to run properly. (Direct cause unknown; Work-around added.)
= Display/Security fix: If register_globals is enabled, a warning is shown.
\*** Changelog ***/
/*** Comments ***\
= MOSMC is pronounced as "moss-em-see".
= Feel free to improve upon it, but please let me know what you've done with it. :)
\*** Comments ***/
/*** Notes & Usage ***\
== How To Configure Initially ==
= Go to the area delimited as "Configuration" (the Config class), and insert/modify values as necessary.
= For configuration options with arrays (multiple values), such as services and sites, increase the array key (inside the square brackets) by one for each new service/site configured.
= Then proceed to use MOSMC via a Web browser, by placing it in a PHP-enabled Web server directory.
= For full utilization of MOSMC, it is recommended you set up cron jobs, as detailed later in this documentation. This enables alerts on component failure, as well as periodic reports.
=
== How to Update ==
= Just copy the configuration from your old MOSMC file to your new MOSMC file. Wasn't that simple?
= Check the changelog for new configuration options, and make sure they are set.
= Note: v1.x.x configuration is *not* compatible with v2.x.x configuration.
=
== General ==
= Status codes are in ascending order of severity. Higher number, higher severity.
= Site monitor links to URL only in case of failure. Otherwise, it doesn't link to the URL being tested.
= 'Component' refers to any item that is monitored by MOSMC (such as a specific website or service).
=
== Cron Jobs // Overview ==
= Cron jobs can be run to either notify you on failure of a component or to send reports periodically.
= There are two types of cron jobs: Those that trigger alerts only on failure (type = failure), and those who send a full report of all monitored components, regardless of their status (type = report)
= Additionally, you can set up a cron job to check for updates periodically (optional, of course).
=
== Setting Up Local Cron Jobs ==
= Use command 'php /path/to/monitor.php type password'
= Example: 'php /path/to/monitor.php failure yourpass' if you wanted to send emails only on component failure
=
== Setting Up Remote Cron Jobs or On-Demand Reporting ==
= Use monitor.php?cron=1&type=failure&pass=yourpass if you want to send an email formatted for SMS on component failure with the details of the failing component
= Use monitor.php?cron=1&type=report&pass=yourpass if you want to send email with full report regardless of status (Not optimized for SMS)
= To send an e-mail, you must to append the password you've set in the configuration to the URL. This prevents others from triggering e-mails. Example: monitor.php?cron=1&type=failure&pass=yourpass
= Note: HTTP authentication (username and password) is not required when using web cron jobs.
=
== Checking for Updates Automatically ==
= This is optional, but recommended. There are two methods to check for updates:
= Add a cron job, either locally ('php /path/to/monitor.php update password'), or remotely (monitor.php?cron=1&type=update&pass=yourpass)
= It is recommended that you set your cron job to check once a week, preferably Monday or Tuesday (updates will probably be published on these days)
= Note: An e-mail will be sent if an update is available each time you run the cron job.
= Note: The Web console may check for updates each time you access it, depending on your configuration.
= Note: MOSMC checks updates using cURL from the terminal, when using cron jobs. It checks using an AJAX request when using the Web console.
= Note: No information is deliberately sent or stored, other than your current version number. Standard information recorded by Apache logs is saved, but will *NEVER* be used or analyzed whatsoever.
=
== How MOSMC-PHP Tests Components ==
= Services: Checks first if services are listening through netstat, then checks if it accepts internal connections, and then checks if it accepts external connections (last two using fsockopen() ).
= Sites: Checks HTTP header response using cURL (from the terminal, not the PHP library).
=
== Disclaimers ==
=
****====IMPORTANT====****
= MOSMC outputs certain information that can make it easier to target your system for vulnerabilities, such as ports where services are running, and the types of connections accepted by services.
= It is highly recommended that your MOSMC script is *ALWAYS* in a location you only know, and that is not easily guessable.
= It is highly recommended that your MOSMC script *ALWAYS* be protected by a strong username and password combination.
= It is highly recommended that you have register_globals *DISABLED* in your PHP configuration (although MOSMC makes sure only required input is accepted).
****====IMPORTANT====****
=
= MOSMC has only been tested using PHP 5.2 on Debian 4.0 (Etch). No guarantee of functionality or reliability is given (although I've tested it extensively, and it seems very reliable).
= MOSMC will probably *not* work in PHP 4, as it uses PHP 5 OOP and functions only supported by PHP 5.
\*** Notes & Usage ***/
class Config {
protected $server, $email, $web, $service, $load, $ram, $fs, $site;
public function __construct()
{
/*** Configuration ***/
//External IP of server to test
$this->server->ip = '1234.56.78.90';
//If using MOSMC in a server with a dynamic IP linked to a DNS record, comment the line above and uncomment line below
//$this->server->ip = gethostbyname('sub.domain.tld');
//Name of your server
$this->server->name = 'Server Name';
//E-mail to send Reports to
$this->email->recipient->report = 'reports@yourdomain.tld';
//E-mail to send Alerts to
$this->email->recipient->alert = 'alerts@yourdomain.tld';
//E-mail to send Update notifications to (optional)
$this->email->recipient->update = 'update@yourdomain.tld';
//E-mail to send Reports from
$this->email->sender->report = 'mosmc-report@yourdomain.tld';
//E-mail to send Alerts from
$this->email->sender->alert = 'mosmc-alert@yourdomain.tld';
//E-mail to send Update notifcations from (optional)
$this->email->sender->update = 'mosmc-update@yourdomain.tld';
//Subject of Report e-mail (Recommended: Default)
$this->email->subject->report = $this->server->name.' Report';
//Subject of Alert e-mail (Recommended: Default)
$this->email->subject->alert = $this->server->name.' Alert';
//Subject of Update notification e-mail (Recommended: Default)
$this->email->subject->update = 'MOSMC Update needed at '.$this->server->name;
//E-mail headers for Reports (Recommended: Default)
$this->email->header->report = 'From: "MOSMC" <'.$this->email->sender->report.">\r\n";
//E-mail headers for Alerts (Recommended: Default)
$this->email->header->alert = 'From: "MOSMC" <'.$this->email->sender->alert.">\r\n";
//E-mail headers for Update notifications (Recommended: Default)
$this->email->header->update = 'From: "MOSMC" <'.$this->email->sender->update.">\r\n";
//Set minimum threshold level to send email. Values: 0 = Send on Warning and Max/Alert (recommended); 1 = Send only on Max/Alert
$this->email->threshold = 0;
//Username used for viewing the Web console.
$this->web->username = 'yourUsername';
//Password for viewing the Web console, and sending e-mails.
$this->web->password = 'yourVerySecurePassword';
//Set if you want to check for updates automatically when visiting the Web console. 1 = yes (default), 0 = no
$this->web->update = 1;
//Set if you want to use HTTP authentication on Web console. 1 = enabled (default), 0 = disabled (NOT recommended)
$this->web->auth = 1;
//Human readable service name
$this->service[0]->name = 'httpd';
$this->service[1]->name = 'MySQL';
//Type of connection accepted by service. Type 0 = 'Local service' (only internal connections), type 1 = 'Public service' (internal + external connections)
$this->service[0]->type = 1;
$this->service[1]->type = 0;
//Port service is running on
$this->service[0]->port = 80;
$this->service[1]->port = 3306;
//Statistics Warning and Maximum/Alert thresholds
//Load average
$this->load->warn = '4.00';
$this->load->max = '8.00';
//RAM
$this->ram->warn = 460;
$this->ram->max = 512;
//Storage (File System)
$this->fs->partition = 'sda1'; //As listed in 'df' after /dev/
$this->fs->warn = 8*1024; //8GB (2.0 GB free)
$this->fs->max = 10*1024; //10GB (0.0 GB free)
//Human readable website name
$this->site[0]->name = 'Google';
$this->site[1]->name = 'Slashdot';
//URL of websites to check (You may use alternate ports. Ex: http://domain.tld:8080)
$this->site[0]->url = 'http://www.google.com';
$this->site[1]->url = 'http://slashdot.org';
/*** Configuration ***/
}
}
class Monitor extends Config {
protected $version, $get, $services, $sites;
private $global, $key;
public function __construct()
{
$this->filterInput();
$this->getArguments();
parent::__construct();
$this->version = '2.0.1';
$this->populateData();
}
private function filterInput()
{
//Lets only grab the input we need
foreach(array($_GET, $_SERVER) as $global)
{
foreach($global as $key => $value)
{
if($key == 'argv')
{
$this->get->$key = $value;
}
elseif($key == 'PHP_AUTH_USER')
{
$this->get->http->user = $value;
}
elseif($key == 'PHP_AUTH_PW')
{
$this->get->http->pass = $value;
}
elseif($key == 'cron' || $key == 'type' || $key == 'pass')
{
$this->get->$key = $value;
}
}
}
}
private function getArguments()
{
//Check if running cron job. If so, get configuration options from CLI.
if(isset($this->get->argv[1]))
{
$this->get->cron = 1;
$this->get->type = strtolower($this->get->argv[1]);
$this->get->pass = $this->get->argv[2];
}
}
private function populateData()
{
/*** Populate Data Variables ***/
$this->services->count = count($this->service);
$this->sites->count = count($this->site);
$this->checkServices();
$this->grabStats();
$this->checkSites();
/*** Populate Data Variables ***/
}
private function checkServices()
{
//Check if ports are open for each service
for($i=0; $i < $this->services->count; $i++)
{
$this->service[$i]->status->listen = -1;
$this->service[$i]->status->internal = -1;
$this->service[$i]->status->external = -1;
//Check if services are listening
$listenCheck = shell_exec('netstat -l -n | grep ":'.$this->service[$i]->port.' " | wc -l');
//If listening, win
if($listenCheck > 0)
{
$this->service[$i]->status->listen = 0;
}
//If not listening, fail
else
{
$this->service[$i]->status->listen = 1;
}
//Check internal connection if service is listening for connections
if($this->service[$i]->status->listen == 0)
{
$fp = @fsockopen('localhost', $this->service[$i]->port, $errno, $errstr, 1);
//If internal connection is accepted, win
if($fp)
{
$this->service[$i]->status->internal = 0;
}
//If internal connection is rejected, fail
else
{
$this->service[$i]->status->internal = 1;
}
}
//Check external connection if internal connection is available
if($this->service[$i]->status->internal == 0 && $this->service[$i]->status->listen == 0)
{
$fp = @fsockopen($this->server->ip, $this->service[$i]->port, $errno, $errstr, 1);
//If external connection is accepted, and is supposed to be accepted, win
if($fp && $this->service[$i]->type == 1)
{
$this->service[$i]->status->external = 0;
}
//If external connection is rejected, and not supposed to be accepted, win
elseif(!$fp && $this->service[$i]->type == 0)
{
$this->service[$i]->status->external = 1;
}
//If external connection is rejected, and is supposed to be accepted, fail
elseif(!$fp && $this->service[$i]->type == 1)
{
$this->service[$i]->status->external = 2;
}
//If external connection is accepted, but not supposed to be, fail + alert
elseif($fp && $this->service[$i]->type == 0)
{
$this->service[$i]->status->external = 3;
}
}
}
}
private function grabStats()
{
//Grab Uptime + Load Averages
$this->uptime = shell_exec('uptime');
//Extract load averages
preg_match("/averages?: ([0-9\.]+),[\s]+([0-9\.]+),[\s]+([0-9\.]+)/", $this->uptime, $this->loads);
$this->load->status = -1;
if($this->load->warn > $this->loads[1] && $this->loads[1] < $this->load->max)
{
$this->load->status = 0;
}
elseif($this->load->warn <= $this->loads[1] && $this->loads[1] < $this->load->max)
{
$this->load->status = 1;
}
else
{
$this->load->status = 2;
}
//Extract uptime
$this->uptime = explode(' up ', $this->uptime);
$this->uptime = explode(',', $this->uptime[1]);
$this->uptime = $this->uptime[0].', '.trim($this->uptime[1]);
//Grab number of users logged in
$this->users = trim(shell_exec('who | wc -l'));
//Grab RAM usage
$this->ram->used = trim(shell_exec('free -m | grep "buffers/cache" | awk \'{print $3}\''));
$this->ram->status = -1;
if($this->ram->warn > $this->ram->used && $this->ram->used < $this->ram->max)
{
$this->ram->status = 0;
}
elseif($this->ram->warn <= $this->ram->used && $this->ram->used < $this->ram->max)
{
$this->ram->status = 1;
}
else
{
$this->ram->status = 2;
}
//Grab HDD File System usage
$this->fs->used = round(shell_exec('df | grep '.$this->fs->partition.' | awk \'{print $3}\'')/1024, 2);
$this->fs->status = -1;
if($this->fs->warn > $this->fs->used && $this->fs->used < $this->fs->max)
{
$this->fs->status = 0;
}
elseif($this->fs->warn <= $this->fs->used && $this->fs->used < $this->fs->max)
{
$this->fs->status = 1;
}
else
{
$this->fs->status = 2;
}
//Grab number of processes
$this->processes = trim(shell_exec('ps aux | wc -l'));
$this->processes = $this->processes--;
//Grab number of connections
$this->conns->tcp = trim(shell_exec('netstat -t | grep tcp | wc -l'));
$this->conns->udp = trim(shell_exec('netstat -u | grep udp | wc -l'));
}
private function checkSites()
{
//Check if sites are returning HTTP status 200 (OK) or HTTP status 302 (Found)
for($i=0; $i < $this->sites->count; $i++)
{
$this->site[$i]->status = -1;
//Grab HTTP Status. The whitespace in the awk command is there as a workaround to a small bug when parsing data w/ awk.
$this->site[$i]->http->response = trim(shell_exec('curl '.$this->site[$i]->url." -I -s -m 30 -A 'MOSMC-PHP' | grep HTTP | ".'awk \'{print " "$2" "$3" "$4" "$5" "$6}\''));
$this->site[$i]->http->status = substr($this->site[$i]->http->response, 0, 3);
$this->site[$i]->http->desc = trim(substr($this->site[$i]->http->response, 3));
//If headers return HTTP status 200 (OK) or HTTP status 302 (Found), win
if($this->site[$i]->http->status == 200 || $this->site[$i]->http->status == 302)
{
$this->site[$i]->status = 0;
}
//If DNS cannot be resolved, cURL isn't installed, or server hangs connections, fail
elseif($this->site[$i]->http->response == '')
{
$this->site[$i]->status = 1;
$this->site[$i]->http->desc = 'DNS/cURL error or server hangs conn.';
}
//If headers return HTTP status other than 200 (OK) or HTTP status 302 (Found), fail
else
{
$this->site[$i]->status = 1;
}
}
}
}
class BrowserOutput extends Monitor {
public function __construct()
{
parent::__construct();
if($this->get->http->user == $this->web->username && $this->get->http->pass == $this->web->password)
{
//Do nothing
}
elseif($this->web->auth)
{
header('WWW-Authenticate: Basic realm="MOSMC"');
exit('Not authorized.');
}
/*** Testing Variables ***/
//These testing variables only affect browser output, not email notifications.
//Various service status scenarios
//1-Service is not listening, probably because it's down
/*$this->service[0]->status->listen = 1;
$this->service[0]->status->internal = -1;
$this->service[0]->status->external = -1;*/
//2-Service is listening, but rejecting all connections
/*$this->service[0]->status->listen = 0;
$this->service[0]->status->internal = 1;
$this->service[0]->status->external = -1;*/
//3-Service is listening, and rejecting external connections, but supposed to accept them
/*$this->service[0]->status->listen = 0;
$this->service[0]->status->internal = 0;
$this->service[0]->status->external = 2;*/
//4-Service is listening, and accepting external connections, but supposed to reject them
/*$this->service[0]->status->listen = 0;
$this->service[0]->status->internal = 0;
$this->service[0]->status->external = 3;*/
//Various system stats failure scenarios
//Trigger warning
/*$this->load->status = 1;
$this->ram->status = 1;
$this->fs->status = 1;*/
//Trigger alert
/*$this->load->status = 2;
$this->ram->status = 2;
$this->fs->status = 2;*/
//Site failure scenario
/*$this->site[0]->status = 1;
$this->site[0]->http->status = 500;
$this->site[0]->http->desc = 'Internal Server Error';*/
/*** Testing Variables ***/
}
public function update()
{
//Send a request to my update checking script
$return = shell_exec('curl http://update.thephpjedi.com/mosmc/?version='.$this->version.' -s -m 30 -A "MOSMC Updater"');
//If we get a response, display it
if($return != '')
{
echo $return;
}
else
{
echo 'updates=>Error while checking for newer version.|';
}
}
public function render()
{
?>
server->name; ?> Monitoring Console
server->name; ?> Monitoring Console
server->ip; ?>
Warning!
PHP configuration option "register_globals" is enabled!
This is a serious security risk. It is HIGHLY recommended you disable it.
}
?>
$this->services();
$this->stats();
$this->sites();
?>
}
private function services()
{
?>
Service
Port
Local Connection
External Connection
service as $service)
{
?>
name; ?>
status->listen)
{
case 0:
echo '
';
echo 'Listening ('.$service->port.')';
break;
case 1:
echo '