Skip to content

Commit 06d5ca4

Browse files
committed
add file_cleanup cronjob to catch abandoned rspamd config files
1 parent 9a33517 commit 06d5ca4

File tree

2 files changed

+169
-1
lines changed

2 files changed

+169
-1
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
/*
4+
Copyright (c) 2021, Jesse Norell
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification,
8+
are permitted provided that the following conditions are met:
9+
10+
* Redistributions of source code must retain the above copyright notice,
11+
this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
* Neither the name of ISPConfig nor the names of its contributors
16+
may be used to endorse or promote products derived from this software without
17+
specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22+
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
class cronjob_file_cleanup extends cronjob {
32+
33+
// job schedule
34+
protected $_schedule = '* * * * *';
35+
protected $_run_at_new = true;
36+
37+
public function onBeforeRun() {
38+
global $app;
39+
40+
/* currently we only cleanup rspamd config files, so bail if not needed */
41+
if (! is_dir("/etc/rspamd/local.d/users/")) {
42+
return false;
43+
}
44+
45+
return parent::onBeforeRun();
46+
}
47+
48+
public function onRunJob() {
49+
global $app, $conf;
50+
51+
$server_id = $conf['server_id'];
52+
53+
/* rspamd config file cleanup */
54+
if (is_dir("/etc/rspamd/local.d/users/")) {
55+
$mail_access = array();
56+
$sql = "SELECT access_id as id FROM mail_access WHERE active = 'y' AND server_id = ?";
57+
$records = $app->db->queryAllRecords($sql, $server_id);
58+
if(is_array($records)) {
59+
foreach($records as $rec){
60+
$mail_access[$rec['id']] = $rec['id'];
61+
}
62+
}
63+
64+
$spamfilter_wblist = array();
65+
$sql = "SELECT wblist_id as id FROM spamfilter_wblist WHERE active = 'y' AND server_id = ?";
66+
$records = $app->db->queryAllRecords($sql, $server_id);
67+
if(is_array($records)) {
68+
foreach($records as $rec){
69+
$spamfilter_wblist[$rec['id']] = $rec['id'];
70+
}
71+
}
72+
73+
$spamfilter_users = array();
74+
$sql = "SELECT id FROM spamfilter_users WHERE policy_id != 0 AND server_id = ?";
75+
$records = $app->db->queryAllRecords($sql, $server_id);
76+
if(is_array($records)) {
77+
foreach($records as $rec){
78+
$spamfilter_users[$rec['id']] = $rec['id'];
79+
}
80+
}
81+
82+
$mail_user = array();
83+
$sql = "SELECT mailuser_id as id FROM mail_user WHERE postfix = 'y' AND server_id = ?";
84+
$records = $app->db->queryAllRecords($sql, $server_id);
85+
if(is_array($records)) {
86+
foreach($records as $rec){
87+
$mail_user[$rec['id']] = $rec['id'];
88+
}
89+
}
90+
91+
$mail_forwarding = array();
92+
$sql = "SELECT forwarding_id as id FROM mail_forwarding WHERE active = 'y' AND server_id = ?";
93+
$records = $app->db->queryAllRecords($sql, $server_id);
94+
if(is_array($records)) {
95+
foreach($records as $rec){
96+
$mail_forwarding[$rec['id']] = $rec['id'];
97+
}
98+
}
99+
100+
foreach (glob('/etc/rspamd/local.d/users/*.conf') as $file) {
101+
if($handle = fopen($file, 'r')) {
102+
if(($line = fgets($handle)) !== false) {
103+
if(preg_match('/^((?:global|spamfilter)_wblist|ispc_(spamfilter_user|mail_user|mail_forwarding))[_-](\d+)\s/', $line, $matches)) {
104+
switch($matches[1]) {
105+
case 'global_wblist':
106+
$remove = ! isset($mail_access[$matches[3]]);
107+
break;
108+
case 'spamfilter_wblist':
109+
$remove = ! isset($spamfilter_wblist[$matches[3]]);
110+
break;
111+
case 'ispc_spamfilter_user':
112+
$remove = ! isset($spamfilter_users[$matches[3]]);
113+
break;
114+
case 'ispc_mail_user':
115+
$remove = ! isset($mail_user[$matches[3]]);
116+
break;
117+
case 'ispc_mail_forwarding':
118+
$remove = ! isset($mail_forwarding[$matches[3]]);
119+
break;
120+
default:
121+
$app->log("conf file has unhandled rule naming convention, ignoring: $file", LOGLEVEL_DEBUG);
122+
$remove = false;
123+
}
124+
if($remove) {
125+
$app->log("$matches[1] id $matches[3] not found, removing $file", LOGLEVEL_DEBUG);
126+
unlink($file);
127+
$this->restartServiceDelayed('rspamd', 'reload');
128+
}
129+
} else {
130+
$app->log("conf file has unknown rule naming convention, ignoring: $file", LOGLEVEL_DEBUG);
131+
}
132+
}
133+
134+
fclose($handle);
135+
}
136+
}
137+
}
138+
139+
parent::onRunJob();
140+
}
141+
142+
}
143+

server/lib/classes/cronjob.inc.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class cronjob {
4343
protected $_next_run = null;
4444
private $_running = false;
4545

46+
// services for delayed restart/reload
47+
private $_delayed_restart_services = array();
48+
4649
/** return schedule */
4750

4851

@@ -178,6 +181,12 @@ protected function onAfterRun() {
178181
global $app, $conf;
179182

180183
if($conf['log_priority'] <= LOGLEVEL_DEBUG) print "Called onAfterRun() for class " . get_class($this) . "\n";
184+
185+
if(is_array($this->_delayed_restart_services)) {
186+
foreach ($this->_delayed_restart_services as $service => $mode) {
187+
$this->restartService($service, $mode);
188+
}
189+
}
181190
}
182191

183192
// child classes may NOT override this!
@@ -188,6 +197,22 @@ protected function onCompleted() {
188197
$app->db->query("UPDATE `sys_cron` SET `running` = 0 WHERE `name` = ?", get_class($this));
189198
}
190199

200+
// child classes may NOT override this!
201+
protected function restartService($service, $mode='restart') {
202+
global $app;
203+
204+
$mode = ($mode == 'reload' ? 'reload' : 'restart');
205+
$app->system->exec_safe('service ? ?', $service, $mode);
206+
}
207+
208+
// child classes may NOT override this!
209+
protected function restartServiceDelayed($service, $mode='restart') {
210+
$mode = ($mode == 'reload' ? 'reload' : 'restart');
211+
212+
if (is_array($this->_delayed_restart_services)) {
213+
$this->_delayed_restart_services[$service] = $mode;
214+
}
215+
}
216+
191217
}
192218

193-
?>

0 commit comments

Comments
 (0)