#!/usr/local/bin/php * @copyright 2006 Cal Evans * @license GPL 2.0 * @package EximDenyManager * @access public * @version 2.0 * * @todo all output needs to be buffered into a log object and the main calling program can output it. (The object should not output.) * @todo main should return a value to use in the die. I'm thinking a 0 = no lines added or removed, -1 there was a problem, 1 a change was made. This way the shell can determine whether to restart the firewall when used with a firewall. * @todo rewrite main to be more OOP. Right now it feels real procedureal. */ error_reporting(E_NONE); $o= &new EximDenyManager($argv); $o->main(); $o=null; die(0); /** * * EximDenyManager * * This is the main class of this project. It is the controller object that * manages the other objects. * * @author Cal Evans * @copyright 2006 Cal Evans * @license GPL 2.0 * @package EximDenyManager * @access public * @version 1.0 * */ class EximDenyManager { public $seconds_to_keep; protected $verbose_flag; protected $exim_deny_manager; protected $fw_deny_manager; protected $exim_deny_file_name; protected $fw_deny_file_name; protected $move_to_fw; protected $remove_from_edf; protected $remove_from_fw; protected $help_flag; function __construct($arguments=array()) { $this->seconds_to_keep = 86400; $this->exim_deny_file_name = '/var/tmp/exim_deny'; $this->fw_deny_file_name = '/etc/apf/deny_hosts.rules'; $this->count_exim_flag = false; $this->count_fw_flag = false; $this->verbose_flag = false; $this->move_to_fw = false; $this->remove_from_edf = false; $this->remove_from_fw = false; $this->help_flag = false; $this->exim_deny_manager = null; $this->fw_deny_manager = null; $this->process_arguments($arguments); } // function Exim_Filter() function main() { // Help is exclusive. if ($this->help_flag) { $this->display_help(); return; } // if ($this->help_flag) if ($this->verbose_flag) $this->display_options(); // Do we need an EDFManager? if (!empty($this->exim_deny_file_name) and ($this->count_exim_flag or $this->move_to_fw or $this->remove_from_edf)) { $this->exim_deny_manager = new EDMEximDenyFileReader($this->exim_deny_file_name); } // if ($this->count_exim_flag or // Do we need a EDMDenyHostsFileReader if (!empty($this->fw_deny_file_name) and ($this->count_fw_flag or $this->move_to_fw or $this->remove_from_fw)) { $this->fw_deny_manager = new EDMDenyHostsFileReader($this->fw_deny_file_name); } // if ($this->count_fw_flag or /* * Now for the actions */ if ($this->count_exim_flag or $this->count_fw_flag) { /* * Counts are exclusive. no matter what else has been asked, they * will do nothing else. */ $exim_count = 0; $fw_count = 0; $this->count_ips($exim_count, $fw_count); if ($this->verbose_flag) { echo "EDF : ".number_format($exim_count,0)."\n"; echo "FDF : ".number_format($fw_count,0)."\n"; echo "Total count : "; } // if ($this->verbose_flag) echo number_format($exim_count+$fw_count,0)."\n"; return; } // if ($this->count_exim_flag or $this->count_fw_flag) if ($this->remove_from_edf) { /* * A remove from EDF precludes a move to FW. */ $this->exim_deny_manager->filter_ips($this->seconds_to_keep); if ($this->verbose_flag) echo "Removed From EDF :".number_format(count($this->exim_deny_manager->filtered_lines),0)."\n"; } else if ($this->move_to_fw) { $this->exim_deny_manager->filter_ips(0); if ($this->verbose_flag) echo "Removed From EDF :".number_format(count($this->exim_deny_manager->filtered_lines),0)."\n"; $count = $this->fw_deny_manager->add_ips($this->exim_deny_manager->filtered_lines); if ($this->verbose_flag) echo "Added to FDF :".number_format($count,0)."\n"; } // if ($this->move_to_fw) /* * This is a kludge and needs to be re-worked. I should not have to make * this decision here. However, I can't make it at write either because * there are valid reasons why output_lines would be empty but * original_lines would not be. This needs to be fixed in the next * version. */ if ($this->remove_from_fw) { $this->fw_deny_manager->filter_ips($this->seconds_to_keep); } else { $this->fw_deny_manager->output_lines = $this->fw_deny_manager->original_lines; } if (is_object($this->exim_deny_manager)) $this->exim_deny_manager->write_file($this->verbose_flag); if (is_object($this->fw_deny_manager)) $this->fw_deny_manager->write_file($this->verbose_flag); return; } // function main() protected function display_options() { $this->display_output_header(); echo "------------------------------------------------------------\n"; echo "Seconds to keep an entry..:".$this->seconds_to_keep."\n"; echo "Exim deny file name.......:".$this->exim_deny_file_name."\n"; echo "Firewall deny file name...:".$this->fw_deny_file_name."\n"; echo "Count IP in EDF...........:".($this->count_exim_flag?"TRUE":"FALSE")."\n"; echo "Count IP in FDF...........:".($this->count_fw_flag?"TRUE":"FALSE")."\n"; echo "Verbose mode..............:".($this->verbose_flag?"TRUE":"FALSE")."\n"; echo "Move IP addresses to FDF..:".($this->move_to_fw?"TRUE":"FALSE")."\n"; echo "Remove old IP from EDF....:".($this->remove_from_edf?"TRUE":"FALSE")."\n"; echo "Remove old IP from FDF....:".($this->remove_from_fw?"TRUE":"FALSE")."\n"; echo "------------------------------------------------------------\n"; } // protected function display_options() protected function display_output_header() { echo "\nexim_deny_manager.php Version 2.0 - 02/01/2006\n"; echo "Manage the IP addresses of Dictionary attackers.\n"; echo " Cal Evans \n"; echo " http://www.calevans.com/\n"; return; } // protected function display_output_header() protected function display_help() { $this->display_output_header(); echo "usage : exim_deny_manager [-v] [-c -ce -cf] [-m -r -re -rf] [-s xxx] [-edf /path/to/file] [-fdf /path/to/file]\n\n"; echo " -v Verbose mode.\n"; echo " \n"; echo " -c Count all IP addresses in both files.\n"; echo " -ce Count IP addresses in Exim Deny File.\n"; echo " -cf Count IP in Firewall Deny File.\n"; echo " \n"; echo " -m Move all IP addresses from EDF to FDF.\n"; echo " -r Remove all old IPs from both files.\n"; echo " -re Remove old IP from EDF.\n"; echo " -rf Remove old IP from FDF.\n"; echo " \n"; echo " -s Seconds to keep an entry.\n"; echo " \n"; echo " -edf Exim deny file name.\n"; echo " -fdf Firewall deny file name.\n"; echo " \n"; echo " --help This screen.\n"; return; } // protected function display_help() protected function process_arguments($arguments) { if (count($arguments)==0) return; /* * Process arguments if available. */ for ($lcvA=0;$lcvAcount_exim_flag = true; break; case "-cf": // count fw IP addresses *+ $this->count_fw_flag = true; break; case "-c": // count All IPs blocked by exim_deny *+ $this->count_exim_flag = true; $this->count_fw_flag = true; break; case "-m": // Move all IP address older than x seconds from exim_deny to fw/deny_hosts.rules *+ $this->move_to_fw = true; break; case "-r": // remove all IP address older than x seconds from both files. $this->remove_from_edf = true; $this->remove_from_fw = true; break; case "-re": // remove all IP address from exim_deny older than x seconds. *+ $this->remove_from_edf = true; break; case "-rf": // remove all IP address from fw/deny_hosts.rules older than x seconds. * $this->remove_from_fw = true; break; /* * OPTIONS */ case "-edf": // the next argument is the complete path to the exim_deny file. default is /etc/exim_deny $this->exim_deny_file_name = $arguments[++$lcvA]; break; case "-fdf": // the next argument is the complete path to the fw/deny_hosts.rule. default is /etc/fw/deny_hosts.rules $this->fw_deny_file_name = $arguments[++$lcvA]; break; case "-s": // The next argument is the number of seconds to keep any IP address. $this->seconds_to_keep = $arguments[++$lcvA]; break; case "-v": $this->verbose_flag = true; break; case "--help": $this->help_flag = true; break; } // switch ($arguments[$lcvA]) } // for ($lcvA=0;$lcvAexim_deny_manager)) $exim_count = $this->exim_deny_manager->count_ips(); if (is_object($this->fw_deny_manager)) $firewall_count = $this->fw_deny_manager->count_ips(); return; } // protected function count_ips() } // class EximDenyManager /** * * EDMFileReader * * This is the base class for all the file handlers. Subclass this for each * different file type you need to process. * * @author Cal Evans * @copyright 2006 Cal Evans * @license GPL 2.0 * @package EximDenyManager * @access public * @version 1.0 * */ class EDMFileReader { // the file name of the file to read. protected $file_name; // lines read in from the file. public $original_lines; // lines that were removed form original lines public $filtered_lines; // Lines that are going back out to the file. public $output_lines; // the final output sent to the file. public $final_output; public function __construct($file_name = '') { $this->file_name = $file_name; $this->original_lines = array(); $this->filtered_lines = array(); $this->output_lines = array(); $this->final_output = array(); if (strlen($this->file_name)>0) $this->read_file(); } // public function __construct($file_name = '') public function set_filename($value='') { $this->file_name = $value; return; } // public function set_filename($value='') public function get_filename() { return $this->file_name; } // public function get_filename() public function read_file() { $this->original_lines = file($this->file_name); return count($this->original_lines)>0; } // protected function read_file() public function write_file($verbose) { $this->prepare_output(); $file_handle = fopen($this->file_name,'w'); if (!$file_handle) return false; foreach($this->final_output as $line) { fwrite($file_handle,$line."\n"); } // foreach($this->output_lines as $line) fClose($file_handle); if ($verbose) echo "Wrote ".count($this->output_lines)." to ".$this->file_name."\n"; $this->final_output = array(); return; } // protected function write_file() public function filter_ips($seconds_to_keep=86400) { $ips = array(); $break = mktime()-$seconds_to_keep; $this->filtered_lines = array(); $this->output_lines = array(); for($lcvA=0;$lcvAoriginal_lines);$lcvA++) { /* * we simply skip unqualified lines. We are not qualified to * judge them. */ if (!$this->qualify($this->original_lines[$lcvA])) { $this->output_lines[] = $this->original_lines[$lcvA]; continue; } // if (!$this->qualify($this->original_lines[$lcvA])) if ($this->original_lines[$lcvA]['time']<$break) { $this->filtered_lines[] = $this->original_lines[$lcvA]; continue; } // if ($this->original_lines[$lcvA]['time']<$break) /* * This is simply a dupe filter. I won't add them into filtered * because chances are they are already there. */ if (in_array($this->original_lines[$lcvA]['ip'], $ips)) { continue; } // if (in_array($this->original_lines[$lcvA]['ip'], $ips)) $ips[] = $this->original_lines[$lcvA]['ip']; $this->output_lines[] = $this->original_lines[$lcvA]; } // for($lcvA=0;$lcvAoriginal_lines); } // public function count_ips() public function add_ips($lines_to_add=array()) { for($lcvA=0;$lcvAoutput_lines[] = $lines_to_add[$lcvA]; } // for($lcvA=0;$lcvAfinal_output)>0) { $this->write_file(); } //if (count($this->output_lines)>0) return; } // public function __destroy() protected function prepare_output() { for($lcvA=0;$lcvAoutput_lines);$lcvA++) { $this->final_output[] = "# ".mktime()."--".date('m/d/Y h:i:s',$this->output_lines[$lcvA]['time']); if (!empty($this->output_lines[$lcvA]['source'])) $this->final_output[] = $this->output_lines[$lcvA]['source']; $this->final_output[] = $this->output_lines[$lcvA]['ip']; $this->final_output[] = " "; } // for($lcvA=0;$lcvAoutput_lines);$lcvA++) return; } // public function prepare_output() } // class EDMFileReader /** * * EDMEximDenyFileReader * * This subclass handles the file format for the exim_deny file * Format for exim_deny: * # date/time of block * ip address * optional blank line * * @author Cal Evans * @copyright 2006 Cal Evans * @license GPL 2.0 * @package EximDenyManager * @access public * @version 1.0 * */ class EDMEximDenyFileReader extends EDMFileReader{ public function read_file() { parent::read_file(); $work = $this->original_lines; $this->original_lines = array(); for($lcvA=0;$lcvAoriginal_lines[] = array( 'time'=>strtotime(substr(trim(strtr($work[$lcvA],"\n\r\t\0"," ")),1)), 'ip'=>trim(strtr($work[++$lcvA],"\n\r\t\0"," ")), 'source'=>''); } // if (substr($work[$lcvA],0,1)=="#") } // for($lcvA=0;$lcvAoriginal_lines)>0; } // protected function read_file() } // class EDMFileReader /** * * EDMDenyHostsFileReader * * This subclass handles the file format for the apf/deny_hosts.rules file * Format for deny_hosts.rules: * # added 111.222.333.444 on 1137999600--01/23/06 01:00:00 * # {source} * 111.222.333.444 * * @author Cal Evans * @copyright 2006 Cal Evans * @license GPL 2.0 * @package EximDenyManager * @access public * @version 1.0 * */ class EDMDenyHostsFileReader extends EDMFileReader{ public function read_file() { parent::read_file(); $work = $this->original_lines; $this->original_lines = array(); for($lcvA=0;$lcvAoriginal_lines[] = array('time'=>$time,'ip'=>$ip,'source'=>$source); } // if (substr($work[$lcvA],0,1)=="#") } // for($lcvA=0;$lcvAlines);$lcvA++) return count($this->original_lines)>0; } // protected function read_file() protected function qualify($this_line=array()) { return strpos($this_line['source'],'{edf}')?true:false; } // protected function qualify($this_line) public function add_ips($lines_to_add=array()) { for($lcvA=0;$lcvAoriginal_lines[] = $lines_to_add[$lcvA]; } // for($lcvA=0;$lcvA