PHP Classes

File: hostblock.php

Recommend this page to a friend!
  Classes of Rolands Kusins   PHP Block Host   hostblock.php   Download  
File: hostblock.php
Role: Application script
Content type: text/plain
Description: Application
Class: PHP Block Host
Parse logs and block suspicious hosts
Author: By
Last change: Was using h shortopt for both help and ssh log parsing. No idea what was I smoking... Anyhow, changed h to e shortopt for SSHd log file parsing.
Now multiple SSH refused format patterns can be configured also updated patterns in default configuration.
New command line function "--remove", allows to manually remove IP address from data file.
Date: 9 years ago
Size: 21,934 bytes
 

Contents

Class file image Download
#!/usr/bin/php -q <?php /** * HostBlock v.0.1 * * Simple utility that parses log files and updates access files to deny access * to suspicious hosts. * * @author Rolands Kusiņš * @license GPL */ // Allow execution only from console if(php_sapi_name() !== "cli"){ echo "This script can be run only from console!"; exit(1); } // Define allowed command line arguments $shortopts = "hslctaep:y:r:d"; $longopts = array( 'help',// usage 'statistics',// statistics 'list',// ip list (with count and/or ip) 'count',// show activity count with list 'time',// show last activity time with list 'parse-apache-log',// parse Apache access log file 'parse-ssh-log',// parse SSHd log file 'path:',// path to log file for manual parsing 'year:',// year for SSHd log parsing - this log file has time without year, to parse older log files this option was introduced 'test',// run parsing as test - do not update IP data 'remove:',// remove IP address from data file 'daemon',// run script as daemon ); if(isset($argv)){ // Init some variables $showUsage = false; // Parse command line arguments $opts = getopt($shortopts,$longopts); // Include needed classes, config, initialize needed variables and objects if(isset($opts['statistics']) || isset($opts['s']) || isset($opts['list']) || isset($opts['l']) || isset($opts['parse-apache-log']) || isset($opts['a']) || isset($opts['parse-ssh-log']) || isset($opts['h']) || isset($opts['remove'])|| isset($opts['r']) || isset($opts['daemon']) || isset($opts['d'])){ include_once "hostblock/dist-cfg.php"; include_once "hostblock/Log.php"; $log = new Log(); $log->logDirectory = LOGDIR_PATH; include_once "hostblock/Stats.php"; include_once "hostblock/ApacheAccessLogParser.php"; include_once "hostblock/AccessUpdate.php"; include_once "hostblock/SshdLogParser.php"; $config = parse_ini_file(CONFIG_PATH); if(isset($config['datetimeformat'])) $log->dateTimeFormat = $config['datetimeformat']; // Suspicious entry match count if(!isset($config['suspiciousentrymatchcount'])) $config['suspiciousentrymatchcount'] = 10; else $config['suspiciousentrymatchcount'] = (int)$config['suspiciousentrymatchcount']; // How long must a IP be kept in blacklist if(!isset($config['blacklisttime'])) $config['blacklisttime'] = 0; else $config['blacklisttime'] = (int)$config['blacklisttime']; // Whitelist if(!isset($config['whitelist'])) $config['whitelist'] = null; // Permanent blacklist if(!isset($config['blacklist'])) $config['blacklist'] = null; // Timezone if(!isset($config['timezone'])) $config['timezone'] = "UTC"; date_default_timezone_set($config['timezone']); // Init stats object $stats = new Stats(); $stats->suspiciousIpsPath = WORKDIR_PATH."/suspicious_ips"; $stats->log = $log; $stats->suspiciousEntryMatchCount = $config['suspiciousentrymatchcount']; $stats->blacklistTime = $config['blacklisttime']; $stats->permanentBlacklistFile = $config['blacklist']; $stats->permanentWhitelistFile = $config['whitelist']; if(isset($config['datetimeformat'])) $stats->dateTimeFormat = $config['datetimeformat']; $stats->loadBlacklist(); } if(isset($opts['statistics']) || isset($opts['s'])){ // Output statistics $log->write("Preparing statistics..."); // Get data from file $stats->load(); // Calculate $stats->calculate(); // Output data (formatted for console) $stats->output(); $log->write("Statistics calculated"); exit(0); } elseif(isset($opts['list']) || isset($opts['l'])){ // Output blacklisted IP addresses $log->write("Preparing list of blacklisted IP addresses..."); $count = false; $time = false; if(isset($opts['count']) || isset($opts['c'])) $count = true; if(isset($opts['time']) || isset($opts['t'])) $time = true; // Get data from file $stats->load(); // Output data (each IP in new line) $stats->outputBlacklist($count, $time); $log->write("Blacklist returned"); exit(0); } elseif(isset($opts['parse-apache-log']) || isset($opts['a'])){ if(isset($opts['path']) || isset($opts['p'])){ // Parse Apache access log file $path = null; if(isset($opts['path'])) $path = $opts['path']; if(isset($opts['p'])) $path = $opts['p']; if(!file_exists($path)){ echo "Log file doesn't exist!\n"; $log->write("Log file doesn't exist!","error"); exit(1); } if(!isset($config['apacheaccesspaterns'])){ $config['apacheaccesspaterns'] = array(); } // Init Apache access log file parser $apacheAccessLogParser = new ApacheAccessLogParser(); $apacheAccessLogParser->log = $log; $apacheAccessLogParser->suspiciousPatterns = $config['apacheaccesspaterns']; // Load info about suspicious IPs $ipInfo = array(); $data = @file_get_contents(WORKDIR_PATH."/suspicious_ips"); if($data != false){ $ipInfo = unserialize($data); $log->write("Suspicious IP data loaded!"); } $stats->ipInfo = $ipInfo; echo "Suspicious IP addresses before processing: ".count($stats->ipInfo)."\n"; echo "Blacklisted IP addresses before processing: ".$stats->getBlacklistedIpCount()."\n"; // Parse file $apacheAccessLogFile['path'] = $path; $apacheAccessLogFile['offset'] = 0; $updateHostData = false; $updateOffsets = false; $matchCount = $apacheAccessLogParser->parseFile($apacheAccessLogFile, $ipInfo, $updateHostData, $updateOffsets); // Update IP data $stats->ipInfo = $ipInfo; if(!isset($opts['test'])){ $data = serialize($ipInfo); @file_put_contents(WORKDIR_PATH."/suspicious_ips", $data); } echo "Pattern match count: ".$matchCount."\n"; echo "Suspicious IP addresses after processing: ".count($stats->ipInfo)."\n"; echo "Blacklisted IP addresses after processing: ".$stats->getBlacklistedIpCount()."\n"; $log->write("Apache access log file parsing finished."); if(!isset($opts['test'])){ echo "IP address data updated! Please wait for daemon to reload IP data and update access files if needed.\n"; $log->write("IP address data updated! Please wait for daemon to reload IP data and update access files if needed."); } exit(0); } else{ echo "Path to log file not provided!\n"; $showUsage = true; } } elseif(isset($opts['parse-ssh-log']) || isset($opts['e'])){ if(isset($opts['path']) || isset($opts['p'])){ // Parse SSHd log file $path = null; if(isset($opts['path'])) $path = $opts['path']; if(isset($opts['p'])) $path = $opts['p']; if(!file_exists($path)){ echo "Log file doesn't exist!\n"; $log->write("Log file doesn't exist!","error"); exit(1); } $year = date("Y"); if(isset($opts['year'])) $year = (int)$opts['year']; if(isset($opts['y'])) $year = (int)$opts['y']; // Init SSHd log file parser $sshdLogParser = new SshdLogParser(); $sshdLogParser->log = $log; if(isset($config['sshformats']) && count($config['sshformats']) > 0){ $sshdLogParser->formats = $config['sshformats']; } if(isset($config['sshrefusedformats']) && count($config['sshrefusedformats']) > 0){ $sshdLogParser->refusedFormats = $config['sshrefusedformats']; } /* if(isset($config['sshrefusedformat']) && !empty($config['sshrefusedformat'])){ $sshdLogParser->refusedFormat = $config['sshrefusedformat']; } */ $sshdLogFile = array(); $sshdLogFile['path'] = $path; $sshdLogFile['offset'] = 0; // Info about suspicious IPs $ipInfo = array(); $data = @file_get_contents(WORKDIR_PATH."/suspicious_ips"); if($data != false){ $ipInfo = unserialize($data); $log->write("Suspicious IP address data loaded!"); } $stats->ipInfo = $ipInfo; echo "Suspicious IP addresses before parsing: ".count($stats->ipInfo)."\n"; echo "Blacklisted IP addresses before parsing: ".$stats->getBlacklistedIpCount()."\n"; echo "Total refused SSH authorization count before parsing: ".$stats->getTotalRefusedConnectCount()."\n"; // Check for entries in SSHd log file $updateHostData = false; $updateOffsets = false; $matchCount = $sshdLogParser->parseFile($sshdLogFile, $ipInfo, $updateHostData, $updateOffsets, $year); // Update IP data $stats->ipInfo = $ipInfo; if(!isset($opts['test'])){ $data = serialize($ipInfo); @file_put_contents(WORKDIR_PATH."/suspicious_ips", $data); } echo "Pattern match count: ".$matchCount."\n"; echo "Suspicious IP addresses after parsing: ".count($stats->ipInfo)."\n"; echo "Blacklisted IP addresses after parsing: ".$stats->getBlacklistedIpCount()."\n"; echo "Total refused SSH authorization count after parsing: ".$stats->getTotalRefusedConnectCount()."\n"; $log->write("SSHd log file parsing finished."); if(!isset($opts['test'])){ echo "IP address data updated! Please wait for daemon to reload IP data and update access files if needed.\n"; $log->write("IP address data updated! Please wait for daemon to reload IP data and update access files if needed."); } exit(0); } else{ echo "Path to log file not provided!\n"; $showUsage = true; } } elseif(isset($opts['remove']) || isset($opts['r'])){ // Remove IP address from data file $ipToRemove = null; if(isset($opts['remove'])) $ipToRemove = $opts['remove']; if(isset($opts['r'])) $ipToRemove = $opts['r']; // Load data $stats->load(); if(count($stats->ipInfo) > 0){ if(isset($stats->ipInfo[$ipToRemove])){ // Show some stats if IP address found echo "Removing IP address from data file.\n"; echo "Suspicious activity count: ".$stats->ipInfo[$ipToRemove]['count']."\n"; echo "Refused SSH authorization count: "; if(isset($stats->ipInfo[$ipToRemove]['refused'])) echo $stats->ipInfo[$ipToRemove]['refused']."\n"; else echo "0\n"; echo "Last activity: ".date($stats->dateTimeFormat, $stats->ipInfo[$ipToRemove]['lastactivity'])."\n"; // Unset information about this IP address unset($stats->ipInfo[$ipToRemove]); // Update data file $data = serialize($stats->ipInfo); @file_put_contents(WORKDIR_PATH."/suspicious_ips", $data); echo "IP address data updated! Please wait for daemon to reload IP data and update access files if needed.\n"; $log->write("IP address data updated! Please wait for daemon to reload IP data and update access files if needed."); } else{ echo "IP address not found!\n"; $log->write("Trying to remove unknown IP address from data file! IP address: ".$ipToRemove,"error"); exit(1); } } else{ echo "No data!\n"; exit(1); } exit(0); } elseif(isset($opts['daemon']) || isset($opts['d'])){ // Start as daemon process $log->write("Starting daemon process..."); // Check if process is already running if(file_exists(PID_PATH)){ echo "Another instance of hostblock is already running!\n"; $log->write("Another instance of hostblock is already running!","error"); exit(1); } // Fork currently running process $pid = pcntl_fork(); if($pid == -1){// Fork failed echo "Failed to fork process!\n"; $log->write("Failed to fork process!","error"); exit(1); } elseif($pid){// We are parent (pid>0) // Write PID to file $f = @fopen(PID_PATH,"w"); if($f){ @fwrite($f,$pid); @fclose($f); } // Fork succeeded, exit exit(0); } else{// We are children (pid==0) // Variable for main loop, will exit loop when false $running = true; // tick required for signal handler declare(ticks = 1); // Define signal handler function signal_handler($signalNumber){ global $running; switch($signalNumber){ case SIGTERM: // Handle shutdown $running = false; break; } } // Install signal handler $log->write("Registering signal handler..."); pcntl_signal(SIGTERM,"signal_handler"); // Log file parse interval if(!isset($config['logparseinterval'])) $config['logparseinterval'] = 60; else $config['logparseinterval'] = (int)$config['logparseinterval']; // Access file update interval if(!isset($config['blacklistupdateinterval'])) $config['blacklistupdateinterval'] = 60; else $config['blacklistupdateinterval'] = (int)$config['blacklistupdateinterval']; // Get stored log file offsets $data = @file_get_contents(WORKDIR_PATH."/offsets"); if($data != false){ $offsets = unserialize($data); $log->write("Log file offset data loaded!"); } // Apache access log file configuration $apacheAccessLogFiles = array(); if(!isset($config['apacheaccesslogs'])){ $config['apacheaccesslogs'] = array(); } if(!isset($config['apacheaccesslogformats'])){ $config['apacheaccesslogformats'] = array(); } if(count($config['apacheaccesslogs']) > 0){ foreach($config['apacheaccesslogs'] as $k => $v){ $offset = 0; if(isset($offsets) && isset($offsets[$v])){ $offset = $offsets[$v]; } $format = "%h %l %u %t \"%r\" %s %b"; if(isset($config['apacheaccesslogformats'][$k]) && !empty($config['apacheaccesslogformats'][$k])){ $format = $config['apacheaccesslogformats'][$k]; } $apacheAccessLogFiles[] = array( 'path' => $v, 'offset' => $offset, 'format' => $format, ); } } if(!isset($config['apacheaccesspaterns'])){ $config['apacheaccesspaterns'] = array(); } if(!isset($config['htaccessfiles'])){ $config['htaccessfiles'] = array(); } // SSHd log file config $sshdLogFile = array(); if(isset($config['sshlog'])){ $sshdLogFile['path'] = $config['sshlog']; $sshdLogFile['offset'] = 0; if(isset($offsets) && isset($offsets[$sshdLogFile['path']])) $sshdLogFile['offset'] = $offsets[$sshdLogFile['path']]; } // Init Apache access log file parser $apacheAccessLogParser = new ApacheAccessLogParser(); $apacheAccessLogParser->log = $log; $apacheAccessLogParser->suspiciousPatterns = $config['apacheaccesspaterns']; // Init Apache access file updater $accessUpdater = new AccessUpdate(); $accessUpdater->log = $log; // Init SSHd log file parser $sshdLogParser = new SshdLogParser(); $sshdLogParser->log = $log; if(isset($config['sshformats']) && count($config['sshformats']) > 0){ $sshdLogParser->formats = $config['sshformats']; } if(isset($config['sshrefusedformats']) && count($config['sshrefusedformats']) > 0){ $sshdLogParser->refusedFormats = $config['sshrefusedformats']; } /* if(isset($config['sshrefusedformat']) && !empty($config['sshrefusedformat'])){ $sshdLogParser->refusedFormat = $config['sshrefusedformat']; } */ // Info about suspicious IPs $ipInfo = array(); $data = @file_get_contents(WORKDIR_PATH."/suspicious_ips"); if($data != false){ $ipInfo = unserialize($data); $log->write("Suspicious IP address data loaded!"); } $stats->ipInfo = $ipInfo; // Main loop $lastParseTime = time()-$config['logparseinterval']; $lastUpdateTime = time()-$config['blacklistupdateinterval']; $updateHostData = false; $updateOffsets = false; $newMatchCount = 0; $updateAccessFiles = false; $blacklistedIpCount = $stats->getBlacklistedIpCount(); $lastFileCheckTime = time(); if(file_exists(WORKDIR_PATH."/suspicious_ips")) $ipInfoMTime = filemtime(WORKDIR_PATH."/suspicious_ips"); if(!is_null($config['blacklist'])) $blacklistMTime = filemtime($config['blacklist']); if(!is_null($config['whitelist'])) $whitelistMTime = filemtime($config['whitelist']); while($running){ // Check each 60 seconds if data files are updated and reload if needed if(time() - $lastFileCheckTime >= 60){ // Suspicious IP data if(file_exists(WORKDIR_PATH."/suspicious_ips")){ $ipInfoMTimeNew = filemtime(WORKDIR_PATH."/suspicious_ips"); if($ipInfoMTime != $ipInfoMTimeNew){ $log->write("Suspicious IP address data has been changed, reloading data for deamon!"); $data = @file_get_contents(WORKDIR_PATH."/suspicious_ips"); if($data != false){ $ipInfo = unserialize($data); $log->write("Suspicious IP data loaded!"); } $stats->ipInfo = $ipInfo; if($blacklistedIpCount != $stats->getBlacklistedIpCount()){ $updateAccessFiles = true; $blacklistedIpCount = $stats->getBlacklistedIpCount(); } $ipInfoMTime = $ipInfoMTimeNew; } } // Blacklist/whitelist $reloadStatsBlacklist = false; if(!is_null($config['blacklist'])){ $blacklistMTimeNew = filemtime($config['blacklist']); if($blacklistMTime != $blacklistMTimeNew){ $reloadStatsBlacklist = true; $blacklistMTime = $blacklistMTimeNew; } } if(!is_null($config['whitelist'])){ $whitelistMTimeNew = filemtime($config['whitelist']); if($whitelistMTime != $whitelistMTimeNew){ $reloadStatsBlacklist = true; $whitelistMTime = $whitelistMTimeNew; } } if($reloadStatsBlacklist){ $log->write("Whitelist/blacklist has been changed, reloading data for deamon!"); $stats->loadBlacklist(); $reloadStatsBlacklist = false; $updateAccessFiles = true; $blacklistedIpCount = $stats->getBlacklistedIpCount(); } $lastFileCheckTime = time(); } // If it is time to check log files for new entries if(time() - $lastParseTime >= $config['logparseinterval']){ // Loop through all defined apache log files if(count($apacheAccessLogFiles) > 0){ foreach($apacheAccessLogFiles as &$apacheAccessLogFile){ // Check for new entries in file $newMatchCount += $apacheAccessLogParser->parseFile($apacheAccessLogFile, $ipInfo, $updateHostData, $updateOffsets); } } // Check for new entries in SSHd log file if(isset($sshdLogFile['path'])){ $newMatchCount += $sshdLogParser->parseFile($sshdLogFile, $ipInfo, $updateHostData, $updateOffsets); } // Update host data if($updateHostData == true){ $data = serialize($ipInfo); @file_put_contents(WORKDIR_PATH."/suspicious_ips", $data); $ipInfoMTime = filemtime(WORKDIR_PATH."/suspicious_ips"); $updateHostData = false; // Check if blacklisted IP count differs // Here might be a bug if we have +1 because of activity and -1 because of time in a same time $stats->ipInfo = $ipInfo; if($blacklistedIpCount != $stats->getBlacklistedIpCount()){ $updateAccessFiles = true; $blacklistedIpCount = $stats->getBlacklistedIpCount(); } $log->write("Suspicious IP address data updated!"); } // Update offsets if($updateOffsets == true){ $offsets = array(); foreach($apacheAccessLogFiles as &$apacheAccessLogFile){ $offsets[$apacheAccessLogFile['path']] = $apacheAccessLogFile['offset']; } if(isset($sshdLogFile['path'])){ $offsets[$sshdLogFile['path']] = $sshdLogFile['offset']; } $offsets = serialize($offsets); @file_put_contents(WORKDIR_PATH."/offsets", $offsets); $updateOffsets = false; } // Info in log file if we have new pattern matches if($newMatchCount > 0){ $log->write("Pattern match count: ".$newMatchCount); $newMatchCount = 0; } // Update last parse time $lastParseTime = time(); } // If it is time to check if update to blacklist is needed if(time() - $lastUpdateTime >= $config['blacklistupdateinterval']){ if($updateAccessFiles == true){ // Get white&black lists $stats->loadBlacklist(); // Get blacklisted IPs $blacklistedIps = $stats->getBlacklistedIps(); // If we need to update .htaccess files if(count($config['htaccessfiles']) > 0){ foreach($config['htaccessfiles'] as &$apacheAccessFile){ $accessUpdater->updateApacheAccessFile($apacheAccessFile, $blacklistedIps); } $log->write("Apache access files updated!"); $updateAccessFiles = false; } // If we need to update hosts.deny files if(isset($config['hostsdenyfile']) && !empty($config['hostsdenyfile'])){ $accessUpdater->updateHostsDenyFile($config['hostsdenyfile'], $blacklistedIps); $log->write("hosts.deny file updated!"); $updateAccessFiles = false; } } // Update last update time $lastUpdateTime = time(); } // Sleep half a second before next iteration usleep(500000); } $log->write("Total suspicious IP addresses: ".count($ipInfo)); $log->write("Shutdown"); exit(0); } } else{ $showUsage = true; } if($showUsage){ echo "HostBlock v.0.1\n\n"; echo "Usage:\n"; echo "hostblock [-h | --help] [-s | --statistics] [-l | --list [-c | --count] [-t | --time]] [-a -p<path> | --parse-apache-access-log --path=<path>] [-e -p<path> -y<year> | --parse-ssh-log --path=<path> --year=<year>] [-r<ip_address> | --remove=<ip_address>] [-d | --daemon]\n"; echo ' --help - show this help information --statistics - show statistics --list - show list of blacklisted IP addresses --list --count - show list of blacklisted IP addresses with suspicious activity count --list --time - show list of blacklisted IP addresses with last suspicious activity time --list --count --time - show list of blacklisted IP addresses with suspicious activity count and last suspicious activity time --parse-apache-log --path=<path> - parse Apache access log file --parse-ssh-log --path=<path> --year=<year> - parse SSHd log file --remove=<ip_address> - remove IP address from data file --daemon - run as daemon '; } } ?>