1 | <?php |
---|
2 | /** |
---|
3 | * @brief antispam, a plugin for Dotclear 2 |
---|
4 | * |
---|
5 | * @package Dotclear |
---|
6 | * @subpackage Plugins |
---|
7 | * |
---|
8 | * @copyright Olivier Meunier & Association Dotclear |
---|
9 | * @copyright GPL-2.0-only |
---|
10 | */ |
---|
11 | |
---|
12 | if (!defined('DC_RC_PATH')) {return;} |
---|
13 | |
---|
14 | class dcFilterIP extends dcSpamFilter |
---|
15 | { |
---|
16 | public $name = 'IP Filter'; |
---|
17 | public $has_gui = true; |
---|
18 | public $help = 'ip-filter'; |
---|
19 | |
---|
20 | private $con; |
---|
21 | private $table; |
---|
22 | |
---|
23 | public function __construct($core) |
---|
24 | { |
---|
25 | parent::__construct($core); |
---|
26 | $this->con = &$core->con; |
---|
27 | $this->table = $core->prefix . 'spamrule'; |
---|
28 | } |
---|
29 | |
---|
30 | protected function setInfo() |
---|
31 | { |
---|
32 | $this->description = __('IP Blacklist / Whitelist Filter'); |
---|
33 | } |
---|
34 | |
---|
35 | public function getStatusMessage($status, $comment_id) |
---|
36 | { |
---|
37 | return sprintf(__('Filtered by %1$s with rule %2$s.'), $this->guiLink(), $status); |
---|
38 | } |
---|
39 | |
---|
40 | public function isSpam($type, $author, $email, $site, $ip, $content, $post_id, &$status) |
---|
41 | { |
---|
42 | if (!$ip) { |
---|
43 | return; |
---|
44 | } |
---|
45 | |
---|
46 | # White list check |
---|
47 | if ($this->checkIP($ip, 'white') !== false) { |
---|
48 | return false; |
---|
49 | } |
---|
50 | |
---|
51 | # Black list check |
---|
52 | if (($s = $this->checkIP($ip, 'black')) !== false) { |
---|
53 | $status = $s; |
---|
54 | return true; |
---|
55 | } |
---|
56 | } |
---|
57 | |
---|
58 | public function gui($url) |
---|
59 | { |
---|
60 | global $default_tab; |
---|
61 | $core = &$this->core; |
---|
62 | |
---|
63 | # Set current type and tab |
---|
64 | $ip_type = 'black'; |
---|
65 | if (!empty($_REQUEST['ip_type']) && $_REQUEST['ip_type'] == 'white') { |
---|
66 | $ip_type = 'white'; |
---|
67 | } |
---|
68 | $default_tab = 'tab_' . $ip_type; |
---|
69 | |
---|
70 | # Add IP to list |
---|
71 | if (!empty($_POST['addip'])) { |
---|
72 | try |
---|
73 | { |
---|
74 | $global = !empty($_POST['globalip']) && $core->auth->isSuperAdmin(); |
---|
75 | |
---|
76 | $this->addIP($ip_type, $_POST['addip'], $global); |
---|
77 | dcPage::addSuccessNotice(__('IP address has been successfully added.')); |
---|
78 | http::redirect($url . '&ip_type=' . $ip_type); |
---|
79 | } catch (Exception $e) { |
---|
80 | $core->error->add($e->getMessage()); |
---|
81 | } |
---|
82 | } |
---|
83 | |
---|
84 | # Remove IP from list |
---|
85 | if (!empty($_POST['delip']) && is_array($_POST['delip'])) { |
---|
86 | try { |
---|
87 | $this->removeRule($_POST['delip']); |
---|
88 | dcPage::addSuccessNotice(__('IP addresses have been successfully removed.')); |
---|
89 | http::redirect($url . '&ip_type=' . $ip_type); |
---|
90 | } catch (Exception $e) { |
---|
91 | $core->error->add($e->getMessage()); |
---|
92 | } |
---|
93 | } |
---|
94 | |
---|
95 | /* DISPLAY |
---|
96 | ---------------------------------------------- */ |
---|
97 | $res = dcPage::notices(); |
---|
98 | |
---|
99 | $res .= |
---|
100 | $this->displayForms($url, 'black', __('Blacklist')) . |
---|
101 | $this->displayForms($url, 'white', __('Whitelist')); |
---|
102 | |
---|
103 | return $res; |
---|
104 | } |
---|
105 | |
---|
106 | private function displayForms($url, $type, $title) |
---|
107 | { |
---|
108 | $core = &$this->core; |
---|
109 | |
---|
110 | $res = |
---|
111 | '<div class="multi-part" id="tab_' . $type . '" title="' . $title . '">' . |
---|
112 | |
---|
113 | '<form action="' . html::escapeURL($url) . '" method="post" class="fieldset">' . |
---|
114 | |
---|
115 | '<p>' . |
---|
116 | form::hidden(['ip_type'], $type) . |
---|
117 | '<label class="classic" for="addip_' . $type . '">' . __('Add an IP address: ') . '</label> ' . |
---|
118 | form::field(['addip', 'addip_' . $type], 18, 255); |
---|
119 | if ($core->auth->isSuperAdmin()) { |
---|
120 | $res .= '<label class="classic" for="globalip_' . $type . '">' . form::checkbox(['globalip', 'globalip_' . $type], 1) . ' ' . |
---|
121 | __('Global IP (used for all blogs)') . '</label> '; |
---|
122 | } |
---|
123 | |
---|
124 | $res .= |
---|
125 | $core->formNonce() . |
---|
126 | '</p>' . |
---|
127 | '<p><input type="submit" value="' . __('Add') . '"/></p>' . |
---|
128 | '</form>'; |
---|
129 | |
---|
130 | $rs = $this->getRules($type); |
---|
131 | |
---|
132 | if ($rs->isEmpty()) { |
---|
133 | $res .= '<p><strong>' . __('No IP address in list.') . '</strong></p>'; |
---|
134 | } else { |
---|
135 | $res .= |
---|
136 | '<form action="' . html::escapeURL($url) . '" method="post">' . |
---|
137 | '<h3>' . __('IP list') . '</h3>' . |
---|
138 | '<div class="antispam">'; |
---|
139 | |
---|
140 | $res_global = ''; |
---|
141 | $res_local = ''; |
---|
142 | while ($rs->fetch()) { |
---|
143 | $bits = explode(':', $rs->rule_content); |
---|
144 | $pattern = $bits[0]; |
---|
145 | $ip = $bits[1]; |
---|
146 | $bitmask = $bits[2]; |
---|
147 | |
---|
148 | $disabled_ip = false; |
---|
149 | $p_style = ''; |
---|
150 | if (!$rs->blog_id) { |
---|
151 | $disabled_ip = !$core->auth->isSuperAdmin(); |
---|
152 | $p_style .= ' global'; |
---|
153 | } |
---|
154 | |
---|
155 | $item = |
---|
156 | '<p class="' . $p_style . '"><label class="classic" for="' . $type . '-ip-' . $rs->rule_id . '">' . |
---|
157 | form::checkbox(['delip[]', $type . '-ip-' . $rs->rule_id], $rs->rule_id, |
---|
158 | [ |
---|
159 | 'disabled' => $disabled_ip |
---|
160 | ] |
---|
161 | ) . ' ' . |
---|
162 | html::escapeHTML($pattern) . |
---|
163 | '</label></p>'; |
---|
164 | |
---|
165 | if ($rs->blog_id) { |
---|
166 | // local list |
---|
167 | if ($res_local == '') { |
---|
168 | $res_local = '<h4>' . __('Local IPs (used only for this blog)') . '</h4>'; |
---|
169 | } |
---|
170 | $res_local .= $item; |
---|
171 | } else { |
---|
172 | // global list |
---|
173 | if ($res_global == '') { |
---|
174 | $res_global = '<h4>' . __('Global IPs (used for all blogs)') . '</h4>'; |
---|
175 | } |
---|
176 | $res_global .= $item; |
---|
177 | } |
---|
178 | } |
---|
179 | $res .= $res_local . $res_global; |
---|
180 | |
---|
181 | $res .= |
---|
182 | '</div>' . |
---|
183 | '<p><input class="submit delete" type="submit" value="' . __('Delete') . '"/>' . |
---|
184 | $core->formNonce() . |
---|
185 | form::hidden(['ip_type'], $type) . |
---|
186 | '</p>' . |
---|
187 | '</form>'; |
---|
188 | } |
---|
189 | |
---|
190 | $res .= '</div>'; |
---|
191 | |
---|
192 | return $res; |
---|
193 | } |
---|
194 | |
---|
195 | private function ipmask($pattern, &$ip, &$mask) |
---|
196 | { |
---|
197 | $bits = explode('/', $pattern); |
---|
198 | |
---|
199 | # Set IP |
---|
200 | $bits[0] .= str_repeat(".0", 3 - substr_count($bits[0], ".")); |
---|
201 | $ip = ip2long($bits[0]); |
---|
202 | |
---|
203 | if (!$ip || $ip == -1) { |
---|
204 | throw new Exception('Invalid IP address'); |
---|
205 | } |
---|
206 | |
---|
207 | # Set mask |
---|
208 | if (!isset($bits[1])) { |
---|
209 | $mask = -1; |
---|
210 | } elseif (strpos($bits[1], '.')) { |
---|
211 | $mask = ip2long($bits[1]); |
---|
212 | if (!$mask) { |
---|
213 | $mask = -1; |
---|
214 | } |
---|
215 | } else { |
---|
216 | $mask = ~((1 << (32 - $bits[1])) - 1); |
---|
217 | } |
---|
218 | } |
---|
219 | |
---|
220 | public function addIP($type, $pattern, $global) |
---|
221 | { |
---|
222 | $this->ipmask($pattern, $ip, $mask); |
---|
223 | $pattern = long2ip($ip) . ($mask != -1 ? '/' . long2ip($mask) : ''); |
---|
224 | $content = $pattern . ':' . $ip . ':' . $mask; |
---|
225 | |
---|
226 | $old = $this->getRuleCIDR($type, $global, $ip, $mask); |
---|
227 | $cur = $this->con->openCursor($this->table); |
---|
228 | |
---|
229 | if ($old->isEmpty()) { |
---|
230 | $id = $this->con->select('SELECT MAX(rule_id) FROM ' . $this->table)->f(0) + 1; |
---|
231 | |
---|
232 | $cur->rule_id = $id; |
---|
233 | $cur->rule_type = (string) $type; |
---|
234 | $cur->rule_content = (string) $content; |
---|
235 | |
---|
236 | if ($global && $this->core->auth->isSuperAdmin()) { |
---|
237 | $cur->blog_id = null; |
---|
238 | } else { |
---|
239 | $cur->blog_id = $this->core->blog->id; |
---|
240 | } |
---|
241 | |
---|
242 | $cur->insert(); |
---|
243 | } else { |
---|
244 | $cur->rule_type = (string) $type; |
---|
245 | $cur->rule_content = (string) $content; |
---|
246 | $cur->update('WHERE rule_id = ' . (integer) $old->rule_id); |
---|
247 | } |
---|
248 | } |
---|
249 | |
---|
250 | private function getRules($type = 'all') |
---|
251 | { |
---|
252 | $strReq = |
---|
253 | 'SELECT rule_id, rule_type, blog_id, rule_content ' . |
---|
254 | 'FROM ' . $this->table . ' ' . |
---|
255 | "WHERE rule_type = '" . $this->con->escape($type) . "' " . |
---|
256 | "AND (blog_id = '" . $this->core->blog->id . "' OR blog_id IS NULL) " . |
---|
257 | 'ORDER BY blog_id ASC, rule_content ASC '; |
---|
258 | |
---|
259 | return $this->con->select($strReq); |
---|
260 | } |
---|
261 | |
---|
262 | private function getRuleCIDR($type, $global, $ip, $mask) |
---|
263 | { |
---|
264 | $strReq = |
---|
265 | 'SELECT * FROM ' . $this->table . ' ' . |
---|
266 | "WHERE rule_type = '" . $this->con->escape($type) . "' " . |
---|
267 | "AND rule_content LIKE '%:" . (integer) $ip . ":" . (integer) $mask . "' " . |
---|
268 | 'AND blog_id ' . ($global ? 'IS NULL ' : "= '" . $this->core->blog->id . "' "); |
---|
269 | |
---|
270 | return $this->con->select($strReq); |
---|
271 | } |
---|
272 | |
---|
273 | private function checkIP($cip, $type) |
---|
274 | { |
---|
275 | $core = &$this->core; |
---|
276 | |
---|
277 | $strReq = |
---|
278 | 'SELECT DISTINCT(rule_content) ' . |
---|
279 | 'FROM ' . $this->table . ' ' . |
---|
280 | "WHERE rule_type = '" . $this->con->escape($type) . "' " . |
---|
281 | "AND (blog_id = '" . $this->core->blog->id . "' OR blog_id IS NULL) " . |
---|
282 | 'ORDER BY rule_content ASC '; |
---|
283 | |
---|
284 | $rs = $this->con->select($strReq); |
---|
285 | while ($rs->fetch()) { |
---|
286 | list($pattern, $ip, $mask) = explode(':', $rs->rule_content); |
---|
287 | if ((ip2long($cip) & (integer) $mask) == ((integer) $ip & (integer) $mask)) { |
---|
288 | return $pattern; |
---|
289 | } |
---|
290 | } |
---|
291 | return false; |
---|
292 | } |
---|
293 | |
---|
294 | private function removeRule($ids) |
---|
295 | { |
---|
296 | $strReq = 'DELETE FROM ' . $this->table . ' '; |
---|
297 | |
---|
298 | if (is_array($ids)) { |
---|
299 | foreach ($ids as $i => $v) { |
---|
300 | $ids[$i] = (integer) $v; |
---|
301 | } |
---|
302 | $strReq .= 'WHERE rule_id IN (' . implode(',', $ids) . ') '; |
---|
303 | } else { |
---|
304 | $ids = (integer) $ids; |
---|
305 | $strReq .= 'WHERE rule_id = ' . $ids . ' '; |
---|
306 | } |
---|
307 | |
---|
308 | if (!$this->core->auth->isSuperAdmin()) { |
---|
309 | $strReq .= "AND blog_id = '" . $this->core->blog->id . "' "; |
---|
310 | } |
---|
311 | |
---|
312 | $this->con->execute($strReq); |
---|
313 | } |
---|
314 | } |
---|