== == Created: Wednesday, July 2nd 2008 == == Modified: Tuesday, July 22nd 2008 == == 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 == = 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 == = Added User Agent "MOSMC-PHP" when checking sites. = Minor bug fix related to services checking. Did not affect functionality. == 1.1.5 == = Fixed PHP error. Might be major bug if run in cron. \*** Changelog ***/ /*** To-Do ***\ = Nothing right now, but I'm sure I can find new features I can add very soon ;) \*** To-Do ***/ /*** Comments ***\ = MOSMC is pronounced as "moss-mick" = I admit I could've used some OOP, or at least functions, but currently it's more scalable and configurable. Feel free to improve upon it, but please let me know what you've done with it. :) \*** Comments ***/ /*** Bugs ***\ = Site Monitor, when testing site that it cannot connect to, displays HTTP 200 (OK) text in GUI and Alerts. Oddly enough, it triggers the GUI + Alert mechanism. \*** Bugs ***/ /*** Notes & Usage ***\ == General == = Status codes 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. = == 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 failure = == 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 failure with the details of the failing component (SMS formatting isn't applied with &report=1) = Use monitor.php?cron=1&type=report&pass=yourpass if you want to send email with full report regardless of status = To send an e-mail, you need 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 = == How MOSMC-PHP Tests == = Services: Checks first if services are listening, then checks if it accepts internal connections, then checks if it accepts external connections. = Sites: Checks HTTP header response. = == Disclaimers == = All user input that is accepted by this script is only used for loose comparisons. No input is directly used in any PHP function, command, or printed. This should make the script relatively secure. = Script 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). \*** Notes & Usage ***/ /*** Init ***/ //For security purposes. You never know your own server configuration sometimes ;) //This script (as most others) would become a gaping security hole in your server if register_globals were enabled. ini_set('register_globals', 0); $version = '1.1.5'; /*** Init ***/ /*** Configuration ***/ //External IP of server to test $ip = '123.45.67.89'; //If testing a box with a dynamic IP linked to a DNS record, comment the line above and uncomment line below //$ip = gethostbyname('sub.domain.tld'); //Name of your server $serverName = 'ServerName'; //E-mail to send Reports to $emailRecipient['report'] = 'reports@domain.tld'; //E-mail to send Alerts to $emailRecipient['alert'] = 'alerts@domain.tld'; //E-mail to send Reports from $emailSender['report'] = 'mosmc-reports@domain.tld'; //E-mail to send Alerts from $emailSender['alert'] = 'mosmc-alerts@domain.tld'; //Subject of Report e-mail $emailSubject['report'] = 'ServerName Report'; //Subject of Alert e-mail $emailSubject['alert'] = 'ServerName Alert'; //E-mail headers for Reports $emailHeaders['report'] = 'From: "MOSMC-PHP" <'.$emailSender['report'].">\n"; //E-mail headers for Alerts $emailHeaders['alert'] = 'From: "MOSMC-PHP" <'.$emailSender['alert'].">\n"; //Set minimum threshold level to send email. Values: 0 = Warn; 1 = Max/Alert $emailThreshold = 0; //Password for sending e-mails. //Use in order to prevent others from triggering e-mails. Leaving it blank is not recommended. $emailPassword = 'yourpass'; //What should be grep'd from 'netstat -l' $service[0] = 'www'; $service[1] = 'ftp'; $service[2] = 'mysql'; $service[3] = 'ssh'; //Human readable service name $serviceName[0] = 'httpd'; $serviceName[1] = 'proFTPd'; $serviceName[2] = 'MySQL'; $serviceName[3] = 'SSH'; //Type of connection //type 0 == 'Local service', type 1 == 'Public service' $serviceConnType[0] = 1; $serviceConnType[1] = 1; $serviceConnType[2] = 0; $serviceConnType[3] = 1; //Port connection is running on $servicePort[0] = 80; $servicePort[1] = 21; $servicePort[2] = 3306; $servicePort[3] = 22; //Statistics Warning and Maximum thresholds //Load average $loadWarn = '2.00'; $loadMax = '4.00'; //RAM $memWarn = 236; $memMax = 256; //Storage (File System) $fsWarn = 8*1024; //8GB (2.0 GB free) $fsMax = 9.5*1024; //9.5GB (0.5 GB free) //Human readable website name $siteName[0] = 'Google'; $siteName[1] = 'YouTube'; //URL of websites to check (You may use alternate ports. Ex: http://domain.tld:8080) $siteURL[0] = 'http://www.google.com'; $siteURL[1] = 'http://youtube.com'; /*** Configuration ***/ /*** Init ***/ //Count number of services to test $serviceCount = count($serviceName); //Count number of services to test $siteCount = count($siteURL); //Check if running cron job and get configuration options if so if(isset($argv)) { $cron = 1; $type = strtolower($argv[1]); $password = $argv[2]; } elseif(isset($_GET['cron'])) { $cron = $_GET['cron']; $type = strtolower($_GET['type']); $password = $_GET['pass']; } /*** Init ***/ /*** Populate Data Variables ***/ /*** Services Status Checks ***/ //Check if ports are open for each service for($i=0;$i<$serviceCount;$i++) { $listenStatus[$i] = -1; $intStatus[$i] = -1; $extStatus[$i] = -1; //Check if services are listening $serviceListen = exec("netstat -l -n | grep $servicePort[$i] | wc -l"); //If listening, win if($serviceListen > 0) { $listenStatus[$i] = 0; //If not listening, fail } else { $listenStatus[$i] = 1; } //Check internal connection if service is listening for connections if($listenStatus[$i] == 0) { $fp = @fsockopen('localhost', $servicePort[$i], $errno, $errstr, 1); //If internal connection is accepted, win if($fp) { $intStatus[$i] = 0; //If internal connection is rejected, fail } else { $intStatus[$i] = 1; } } //Check external connection if internal connection is available if($intStatus[$i] == 0 && $listenStatus[$i] == 0) { $fp = @fsockopen($ip, $servicePort[$i], $errno, $errstr, 1); //If external connection is accepted, and is supposed to be accepted, win if($fp && $serviceConnType[$i] == 1) { $extStatus[$i] = 0; //If external connection is rejected, and not supposed to be accepted, win } elseif(!$fp && $serviceConnType[$i] == 0) { $extStatus[$i] = 1; //If external connection is rejected, and is supposed to be accepted, fail } elseif(!$fp && $serviceConnType[$i] == 1) { $extStatus[$i] = 2; //If external connection is accepted, but not supposed to be, fail + alert } elseif($fp && $serviceConnType[$i] == 0) { $extStatus[$i] = 3; } } } /*** Services Status Checks ***/ /*** Grab System Stats ***/ //Grab Uptime + Load Averages $uptimeRaw = @exec('uptime'); //Extract load averages preg_match("/averages?: ([0-9\.]+),[\s]+([0-9\.]+),[\s]+([0-9\.]+)/", $uptimeRaw, $loads); //Extract uptime $uptime = explode(' up ', $uptimeRaw); $uptime = explode(',', $uptime[1]); $uptime = $uptime[0].', '.$uptime[1]; //Grab number of users logged in $users = shell_exec('who | wc -l'); //Grab RAM usage $usedMem = trim(shell_exec('free -m | grep "buffers/cache" | awk \'{print $3}\'')); $totalMem = shell_exec('free -m | grep Mem | awk \'{print $2}\''); //Grab HDD File System usage $fsFree = round(shell_exec('df | grep sda1 | awk \'{print $4}\'')/1024, 2); $fsUsed = round(shell_exec('df | grep sda1 | awk \'{print $3}\'')/1024, 2); $fsTotal = round(shell_exec('df | grep sda1 | awk \'{print $2}\'')/1024, 2); //Grab number of processes $procNumber = shell_exec('ps aux | wc -l'); $procNumber = $procNumber--; //Grab number of connections $tcpConn = trim(shell_exec('netstat -t | grep tcp | wc -l')); $udpConn = trim(shell_exec('netstat -u | grep udp | wc -l')); /*** Grab System Stats ***/ /*** Get Sites' Status ***/ //Check if sites are returning HTTP status 200 (OK) or hTTP status 302 (Found) for($i=0;$i<$siteCount;$i++) { $siteStatus[$i] = -1; //Grab HTTP Status $rawResponse = @shell_exec("curl $siteURL[$i] -I -s -A 'MOSMC-PHP' | grep HTTP | awk '{print $2 $3}'"); $response[$i][0] = substr($rawResponse, 0, 3); $response[$i][1] = trim(substr($rawResponse, 3)); //If headers return HTTP status 200 (OK) or hTTP status 302 (Found), win if($response[$i][0] == 200 || $response[$i][0] == 302) { $siteStatus[$i] = 0; //If headers return HTTP status other than 200 (OK) or hTTP status 302 (Found), fail } else { $siteStatus[$i] = 1; } } /*** Get Sites' Status ***/ /*** Testing Values ***/ //Various service status scenarios //1-Service is not listening, probably because it's down /*$listenStatus[0] = 1; $intStatus[0] = -1; $extStatus[0] = -1;*/ //2-Service is listening, but rejecting all connections /*$listenStatus[0] = 0; $intStatus[0] = 1; $extStatus[0] = -1;*/ //3-Service is listening, and rejecting external connections, but supposed to accept them /*$listenStatus[0] = 0; $intStatus[0] = 0; $extStatus[0] = 2;*/ //4-Service is listening, and accepting external connections, but supposed to reject them /*$listenStatus[0] = 0; $intStatus[0] = 0; $extStatus[0] = 3;*/ //$loads[1] = '4.00'; //$usedMem = 350; //$fsUsed = 10*1024; /*$siteStatus[0] = 1; $response[0][0] = 403; $response[0][1] = 'Forbidden';*/ /*** Testing Values ***/ /*** Populate Data Variables ***/ /*** Print All Data ***/ //If this is a cron job, don't print data if(!isset($cron)) { ?> <?php echo $serverName; ?> Monitoring Console

Monitoring Console

'; echo 'Listening ('.$servicePort[$i].')'; break; case 1: echo ''; echo 'Accepting'; break; case 1: echo ''; echo 'Accepting as configured'; break; case 1: echo '
Service Port Local Connection External Connection
'; echo 'Not listening ('.$servicePort[$i].')'; break; default: echo ''; echo 'N/A'; break; } ?> '; echo 'Rejecting'; break; default: echo ''; echo 'N/A'; break; } ?> '; echo 'Rejecting as configured'; break; case 2: echo ''; echo 'Rejecting; Misconfigured'; break; case 3: echo ''; echo 'Accepting; Misconfigured'; break; default: echo ''; echo 'N/A'; break; } ?>

$loads[1] && $loads[1] < $loadMax) { echo ' $usedMem && $usedMem < $memMax) { echo ' $fsUsed && $fsUsed < $fsMax) { echo '
Stat Type Current Value Warn Threshold Alert Threshold
Load Averages '; echo $loads[1].' '.$loads[2].' '.$loads[3]; } elseif($loadWarn <= $loads[1] && $loads[1] < $loadMax) { echo ''; echo ''.$loads[1].' '.$loads[2].' '.$loads[3].''; } else { echo ''; echo ''.$loads[1].' '.$loads[2].' '.$loads[3].''; } ?> (1 min avg) (1 min avg)
Used RAM '; echo $usedMem; } elseif($memWarn <= $usedMem && $usedMem < $memMax) { echo ''; echo ''.$usedMem.''; } else { echo ''; echo ''.$usedMem.''; } ?> MB MB = 1024) ? ($memMax/1024).' GB' : $memMax.' MB'; ?>
Used Storage '; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; } elseif($fsWarn <= $fsUsed && $fsUsed < $fsMax) { echo ''; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; echo ''; } else { echo ''; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; echo ''; } ?> = 1024) ? round($fsWarn/1024, 2).' GB' : $fsWarn.' MB'; ?> = 1024) ? round($fsMax/1024, 2).' GB' : $fsMax.' MB'; ?>

Stat Type Current Value Stat Type Current Value
Processes Users Logged In
Uptime TCP/UDP Conns

'; echo $response[$i][0].' ('.$response[$i][1].')'; break; case 1: echo '
Site HTTP Status URL
'; echo $response[$i][0].' ('.$response[$i][1].')'; break; default: echo ''; echo 'N/A'; break; } ?> '.$siteURL[$i].'' : $siteURL[$i]; ?>
Creative Commons License
MOSMC-PHP v | Alesandro Ortiz
The source of this script is available at ThePHPJedi.com
$loads[1] && $loads[1] < $loadMax) { echo 'Up, '; echo $loads[1].' '.$loads[2].' '.$loads[3]; } elseif($loadWarn <= $loads[1] && $loads[1] < $loadMax) { echo 'Warning, '; echo $loads[1].' '.$loads[2].' '.$loads[3]; } else { echo 'Down, '; echo $loads[1].' '.$loads[2].' '.$loads[3]; } echo "\n".'RAM: '; if($memWarn > $usedMem && $usedMem < $memMax) { echo 'Up, '; echo $usedMem.' MB'; } elseif($memWarn <= $usedMem && $usedMem < $memMax) { echo 'Warning, '; echo $usedMem.' MB'; } else { echo 'Down, '; echo $usedMem.' MB'; } echo "\n".'Storage: '; if($fsWarn > $fsUsed && $fsUsed < $fsMax) { echo 'Up, '; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; } elseif($fsWarn <= $fsUsed && $fsUsed < $fsMax) { echo 'Warning, '; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; } else { echo 'Down, '; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).' GB' : $fsUsed.' MB'; } echo "\n".'Processes: '.$procNumber; echo "\n".'Uptime: '.$uptime; echo "\n".'Users Logged In: '.$users; echo "\n".'TCP Connections: '.$tcpConn; echo "\n".'UDP Connections: '.$udpConn; echo "\n".'=Sites='; for($i=0;$i<$siteCount;$i++) { echo "\n".$siteName[$i].': '; switch ($siteStatus[$i]) { case 0: echo 'Up, '; echo $response[$i][0].' ('.$response[$i][1].')'; break; case 1: echo 'Down, '; echo $response[$i][0].' ('.$response[$i][1].')'; break; default: echo 'Warning, '; echo 'N/A'; break; } } $content = ob_get_clean(); //Send email with report if($content) { //Check password if($emailPassword == $password) { mail($emailRecipient['report'],$emailSubject['report'],$content,$emailHeaders['report']); } } //If you want to send a report only on errors and warnings... } elseif($type == 'failure') { ob_start(); for($i=0;$i<$serviceCount;$i++) { echo ($listenStatus[$i] > 0 || $intStatus[$i] > 0 || $extStatus[$i] > 1) ? $serviceName[$i] : null; echo ($listenStatus[$i] > 0) ? '|Port:' : null; switch ($listenStatus[$i]) { case 0: break; case 1: echo 'Down,'; echo 'Not listening ('.$servicePort[$i].')'; break; default: echo 'Warning,'; echo 'N/A'; break; } echo ($intStatus[$i] > 0) ? '|Local Conn:' : null; switch ($intStatus[$i]) { case 0: break; case 1: echo 'Down,'; echo 'Rejecting'; break; default: echo 'Warning,'; echo 'N/A'; break; } echo ($extStatus[$i] > 1) ? '|Ext Conn:' : null; switch ($extStatus[$i]) { case 0: break; case 1: break; case 2: echo 'Down,'; echo 'Rejecting; Misconfigured'; echo "\n"; break; case 3: echo 'Down,'; echo 'Accepting; Misconfigured'; echo "\n"; break; default: echo 'Warning,'; echo 'N/A'; echo "\n"; break; } } echo ($loadWarn < $loads[1]) ? 'Loads:' : null; if($loadWarn < $loads[1] && $loads[1] < $loadMax) { echo 'Warning,'; echo $loads[1].' '.$loads[2].' '.$loads[3]; } elseif($loadMax <= $loads[1]) { echo 'Down,'; echo $loads[1].' '.$loads[2].' '.$loads[3]; } echo ($memWarn < $usedMem) ? '|RAM:' : null; if($memWarn < $usedMem && $usedMem < $memMax) { echo 'Warning,'; echo $usedMem; } elseif($memMax <= $usedMem) { echo 'Down,'; echo $usedMem; } echo ($memWarn < $usedMem) ? 'MB' : null; echo ($fsWarn < $fsUsed) ? '|FS:' : null; if($fsWarn < $fsUsed && $fsUsed < $fsMax) { echo 'Warning,'; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).'GB' : $fsUsed.'MB'; } elseif($fsMax <= $fsUsed) { echo 'Down,'; echo ($fsUsed >= 1024) ? round($fsUsed/1024, 2).'GB' : $fsUsed.'MB'; } for($i=0;$i<$siteCount;$i++) { echo ($siteStatus[$i] > 0) ? '|'.$siteName[$i].':' : null; switch ($siteStatus[$i]) { case 0: break; case 1: echo 'Down,'; echo $response[$i][0].'('.$response[$i][1].')'; break; default: echo 'Warning,'; echo 'N/A'; break; } } $content = ob_get_clean(); //Send email if any failure is detected if($content) { //Check password if($emailPassword == $password) { $searchType = strtolower($content); //If threshold has been set to 'Alert/Max', send only when down if($emailThreshold) { if(fnmatch('*down*', $searchType)) mail($emailRecipient['alert'],$emailSubject['alert'],$content,$emailHeaders['alert']); //If threshold has been set to 'Warn', send on warn or down } else { if(fnmatch('*warn*', $searchType) || fnmatch('*down*', $searchType)) mail($emailRecipient['alert'],$emailSubject['alert'],$content,$emailHeaders['alert']); } } } } } ?>