From 06d5ca4782f2afa68a3fe24a22e0ac5ad9d46fd2 Mon Sep 17 00:00:00 2001 From: Jesse Norell <jesse@kci.net> Date: Fri, 6 Aug 2021 16:42:41 -0600 Subject: [PATCH] add file_cleanup cronjob to catch abandoned rspamd config files --- .../classes/cron.d/600-file_cleanup.inc.php | 143 ++++++++++++++++++ server/lib/classes/cronjob.inc.php | 27 +++- 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 server/lib/classes/cron.d/600-file_cleanup.inc.php diff --git a/server/lib/classes/cron.d/600-file_cleanup.inc.php b/server/lib/classes/cron.d/600-file_cleanup.inc.php new file mode 100644 index 0000000000..b487a529b8 --- /dev/null +++ b/server/lib/classes/cron.d/600-file_cleanup.inc.php @@ -0,0 +1,143 @@ +<?php + +/* +Copyright (c) 2021, Jesse Norell +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of ISPConfig nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +class cronjob_file_cleanup extends cronjob { + + // job schedule + protected $_schedule = '* * * * *'; + protected $_run_at_new = true; + + public function onBeforeRun() { + global $app; + + /* currently we only cleanup rspamd config files, so bail if not needed */ + if (! is_dir("/etc/rspamd/local.d/users/")) { + return false; + } + + return parent::onBeforeRun(); + } + + public function onRunJob() { + global $app, $conf; + + $server_id = $conf['server_id']; + + /* rspamd config file cleanup */ + if (is_dir("/etc/rspamd/local.d/users/")) { + $mail_access = array(); + $sql = "SELECT access_id as id FROM mail_access WHERE active = 'y' AND server_id = ?"; + $records = $app->db->queryAllRecords($sql, $server_id); + if(is_array($records)) { + foreach($records as $rec){ + $mail_access[$rec['id']] = $rec['id']; + } + } + + $spamfilter_wblist = array(); + $sql = "SELECT wblist_id as id FROM spamfilter_wblist WHERE active = 'y' AND server_id = ?"; + $records = $app->db->queryAllRecords($sql, $server_id); + if(is_array($records)) { + foreach($records as $rec){ + $spamfilter_wblist[$rec['id']] = $rec['id']; + } + } + + $spamfilter_users = array(); + $sql = "SELECT id FROM spamfilter_users WHERE policy_id != 0 AND server_id = ?"; + $records = $app->db->queryAllRecords($sql, $server_id); + if(is_array($records)) { + foreach($records as $rec){ + $spamfilter_users[$rec['id']] = $rec['id']; + } + } + + $mail_user = array(); + $sql = "SELECT mailuser_id as id FROM mail_user WHERE postfix = 'y' AND server_id = ?"; + $records = $app->db->queryAllRecords($sql, $server_id); + if(is_array($records)) { + foreach($records as $rec){ + $mail_user[$rec['id']] = $rec['id']; + } + } + + $mail_forwarding = array(); + $sql = "SELECT forwarding_id as id FROM mail_forwarding WHERE active = 'y' AND server_id = ?"; + $records = $app->db->queryAllRecords($sql, $server_id); + if(is_array($records)) { + foreach($records as $rec){ + $mail_forwarding[$rec['id']] = $rec['id']; + } + } + + foreach (glob('/etc/rspamd/local.d/users/*.conf') as $file) { + if($handle = fopen($file, 'r')) { + if(($line = fgets($handle)) !== false) { + if(preg_match('/^((?:global|spamfilter)_wblist|ispc_(spamfilter_user|mail_user|mail_forwarding))[_-](\d+)\s/', $line, $matches)) { + switch($matches[1]) { + case 'global_wblist': + $remove = ! isset($mail_access[$matches[3]]); + break; + case 'spamfilter_wblist': + $remove = ! isset($spamfilter_wblist[$matches[3]]); + break; + case 'ispc_spamfilter_user': + $remove = ! isset($spamfilter_users[$matches[3]]); + break; + case 'ispc_mail_user': + $remove = ! isset($mail_user[$matches[3]]); + break; + case 'ispc_mail_forwarding': + $remove = ! isset($mail_forwarding[$matches[3]]); + break; + default: + $app->log("conf file has unhandled rule naming convention, ignoring: $file", LOGLEVEL_DEBUG); + $remove = false; + } + if($remove) { + $app->log("$matches[1] id $matches[3] not found, removing $file", LOGLEVEL_DEBUG); + unlink($file); + $this->restartServiceDelayed('rspamd', 'reload'); + } + } else { + $app->log("conf file has unknown rule naming convention, ignoring: $file", LOGLEVEL_DEBUG); + } + } + + fclose($handle); + } + } + } + + parent::onRunJob(); + } + +} + diff --git a/server/lib/classes/cronjob.inc.php b/server/lib/classes/cronjob.inc.php index 61d45749a8..6ea7559570 100644 --- a/server/lib/classes/cronjob.inc.php +++ b/server/lib/classes/cronjob.inc.php @@ -43,6 +43,9 @@ class cronjob { protected $_next_run = null; private $_running = false; + // services for delayed restart/reload + private $_delayed_restart_services = array(); + /** return schedule */ @@ -178,6 +181,12 @@ class cronjob { global $app, $conf; if($conf['log_priority'] <= LOGLEVEL_DEBUG) print "Called onAfterRun() for class " . get_class($this) . "\n"; + + if(is_array($this->_delayed_restart_services)) { + foreach ($this->_delayed_restart_services as $service => $mode) { + $this->restartService($service, $mode); + } + } } // child classes may NOT override this! @@ -188,6 +197,22 @@ class cronjob { $app->db->query("UPDATE `sys_cron` SET `running` = 0 WHERE `name` = ?", get_class($this)); } + // child classes may NOT override this! + protected function restartService($service, $mode='restart') { + global $app; + + $mode = ($mode == 'reload' ? 'reload' : 'restart'); + $app->system->exec_safe('service ? ?', $service, $mode); + } + + // child classes may NOT override this! + protected function restartServiceDelayed($service, $mode='restart') { + $mode = ($mode == 'reload' ? 'reload' : 'restart'); + + if (is_array($this->_delayed_restart_services)) { + $this->_delayed_restart_services[$service] = $mode; + } + } + } -?> -- GitLab