1<?PHP
2/**
3 * StopForumSpam2 Plugin - Helper Section
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     HokkaidoPerson <dosankomali@yahoo.co.jp>
7 */
8
9if(!defined('DOKU_INC')) die();
10
11
12class helper_plugin_stopforumspam2 extends DokuWiki_Plugin {
13
14    /**
15     * Return the raw result of the investigation
16     *
17     * @param string or array $ip - Do nothing about the IP address if empty
18     * @param string or array $email - Do nothing about the e-mail address if empty
19     * @param string or array $username - Do nothing about the user name if empty
20     * @param string $wildcards - Specify the wildcards if necessary.  Create the string of wildcards divided by "&".  DO NOT specify serialised formatting wildcards such as 'serial', or the function will not run properly.  e.g.: 'nobadusername'  e.g.: 'nobadip&nobademail&notorexit'
21     * @return array - The array contains the whole data returned (converted from the json data to the array).  See https://www.stopforumspam.com/usage
22     */
23    function rawdata($ip = null, $email = null, $username = null, $wildcards = null){
24        // All arguments are empty?  Completely nothing to do.  Return the empty array.
25        if ($ip == null and $email == null and $username == null) return array();
26
27        // The script below was adopted from https://www.stopforumspam.com/usage
28        // setup the URL
29        $data = array(
30            'ip' => $ip,
31            'username' => $username,
32            'email' => $email,
33        );
34
35        $data = http_build_query($data, '', '&', PHP_QUERY_RFC3986);
36        if ($wildcards != null) $data .= '&' . preg_replace('/[^a-z0-9&=]+/', '', $wildcards);
37
38        // init the request, set some info, send it and finally close it
39        $this->ch = curl_init();
40        if ($this->ch) {
41            curl_setopt ($this->ch, CURLOPT_URL, 'http://api.stopforumspam.org/api?json');
42            curl_setopt ($this->ch, CURLOPT_POST, 1);
43            curl_setopt ($this->ch, CURLOPT_POSTFIELDS, $data);
44            curl_setopt ($this->ch, CURLOPT_RETURNTRANSFER, true);
45
46            $result = curl_exec($this->ch);
47            curl_close($this->ch);
48        }
49        // End of adoption from https://www.stopforumspam.com/usage
50        return json_decode($result, true);
51    }
52
53    /**
54     * Look for the frequency score of given IP address, e-mail address, and(or) user name
55     *
56     * @param string $ip - Do nothing about the IP address if empty
57     * @param string $email - Do nothing about the e-mail address if empty
58     * @param string $username - Do nothing about the user name if empty
59     * @param boolean $returnvalue - If true, this function returns the frequency score itself.  If false, it checks whether or not the score is as large as or higher than $border.  This will be TRUE if one or more of the IP, e-mail, and user name is likely a spammer.
60     * @param value $border - The conf "freqBorder" will be used if empty or minus value.  If $returnvalue is TRUE, the $border will be no use.  Don't check if 0.
61     * @return boolean (if $returnvalue is false) or array (otherwise) - The boolean will be TRUE if the user is regarded as a spammer, or FALSE otherwise.  The array's components will be values with keys "ip", "email", and "username".  If the plugin fail to read the frequency, the value will be -1.
62     */
63    function freqcheck($ip = null, $email = null, $username = null, $returnvalue = FALSE, $border = null){
64        global $INFO;
65
66        // All arguments are empty?  Completely nothing to do.  Return FALSE.
67        if ($ip == null and $email == null and $username == null) return FALSE;
68
69        if ($border === null or $border < 0) $border = $this->getConf('freqBorder');
70        if ($border == 0 and $returnvalue == FALSE) return FALSE;
71        if ($this->whitelists and $returnvalue == FALSE) return FALSE;
72
73        // Get the data from the function "rawdata" above.
74        $resultarray = $this->rawdata($ip, $email, $username);
75
76        if (isset($resultarray['ip']['frequency'])) $ipfreq = $resultarray['ip']['frequency']; else $ipfreq = -1;
77        if (isset($resultarray['email']['frequency'])) $emailfreq = $resultarray['email']['frequency']; else $emailfreq = -1;
78        if (isset($resultarray['username']['frequency'])) $namefreq = $resultarray['username']['frequency']; else $namefreq = -1;
79        if (isset($resultarray['ip']['confidence'])) $ipconf = $resultarray['ip']['confidence']; else $ipconf = -1;
80        if (isset($resultarray['email']['confidence'])) $emailconf = $resultarray['email']['confidence']; else $emailconf = -1;
81        if (isset($resultarray['username']['confidence'])) $nameconf = $resultarray['username']['confidence']; else $nameconf = -1;
82
83        if ($returnvalue) return array('ip' => $ipfreq, 'email' => $emailfreq, 'username' => $namefreq);
84
85        if ($ipfreq >= $border or $emailfreq >= $border or $namefreq >= $border) {
86            $logfilename = $this->getConf('logPlace');
87            if ($logfilename == '') return TRUE;
88            if ($loghandle = fopen($logfilename, 'a')) {
89                $logcontent = "=== " . date('H:i:s M d, Y') . " - higher frequency score than the border ===\n";
90                if ($ip != '') $logcontent .= "IP: " . $ip .", frequency " . $ipfreq . ", confidence " . $ipconf . "\n";
91                if ($email != '') $logcontent .= "E-mail Address: " . $email .", frequency " . $emailfreq . ", confidence " . $emailconf . "\n";
92                if ($username != '') $logcontent .= "User Name: " . $username .", frequency " . $namefreq . ", confidence " . $nameconf . "\n";
93                $logcontent .= "It was accessing " . $INFO['id'] . "\n\n";
94                fwrite($loghandle, $logcontent);
95                fclose($loghandle);
96            }
97            return TRUE;
98        } else return FALSE;
99    }
100
101    /**
102     * Look for the confidence score of given IP address, e-mail address, and(or) user name
103     *
104     * @param string $ip - Do nothing about the IP address if empty
105     * @param string $email - Do nothing about the e-mail address if empty
106     * @param string $username - Do nothing about the user name if empty
107     * @param boolean $returnvalue - If true, this function returns the confidence score itself.  If false, it checks whether or not the score is as large as or higher than $border.  This will be TRUE if one or more of the IP, e-mail, and user name is likely a spammer.
108     * @param value $border - The conf "confidenceBorder" will be used if empty or minus value.  If $returnvalue is TRUE, the $border will be no use.  Don't check if 0.
109     * @return boolean (if $returnvalue is false) or array (otherwise) - The boolean will be TRUE if the user is regarded as a spammer, or FALSE otherwise.  The array's components will be values with keys "ip", "email", and "username".  If the plugin fail to read the confidence score, the value will be -1.
110     */
111    function confcheck($ip = null, $email = null, $username = null, $returnvalue = FALSE, $border = null){
112        global $INFO;
113
114        // All arguments are empty?  Completely nothing to do.  Return FALSE.
115        if ($ip == null and $email == null and $username == null) return FALSE;
116
117        if ($border === null or $border < 0) $border = $this->getConf('confidenceBorder');
118        if ($border == 0 and $returnvalue == FALSE) return FALSE;
119        if ($this->whitelists and $returnvalue == FALSE) return FALSE;
120
121        // Get the data from the function "rawdata" above.
122        $resultarray = $this->rawdata($ip, $email, $username);
123
124        if (isset($resultarray['ip']['confidence'])) $ipconf = $resultarray['ip']['confidence']; else $ipconf = -1;
125        if (isset($resultarray['email']['confidence'])) $emailconf = $resultarray['email']['confidence']; else $emailconf = -1;
126        if (isset($resultarray['username']['confidence'])) $nameconf = $resultarray['username']['confidence']; else $nameconf = -1;
127        if (isset($resultarray['ip']['frequency'])) $ipfreq = $resultarray['ip']['frequency']; else $ipfreq = -1;
128        if (isset($resultarray['email']['frequency'])) $emailfreq = $resultarray['email']['frequency']; else $emailfreq = -1;
129        if (isset($resultarray['username']['frequency'])) $namefreq = $resultarray['username']['frequency']; else $namefreq = -1;
130
131        if ($returnvalue) return array('ip' => $ipconf, 'email' => $emailconf, 'username' => $nameconf);
132
133        if ($ipconf >= $border or $emailconf >= $border or $nameconf >= $border) {
134            $logfilename = $this->getConf('logPlace');
135            if ($logfilename == '') return TRUE;
136            if ($loghandle = fopen($logfilename, 'a')) {
137                $logcontent = "=== " . date('H:i:s M d, Y') . " - higher confidence score than the border ===\n";
138                if ($ip != '') $logcontent .= "IP: " . $ip .", frequency " . $ipfreq . ", confidence " . $ipconf . "\n";
139                if ($email != '') $logcontent .= "E-mail Address: " . $email .", frequency " . $emailfreq . ", confidence " . $emailconf . "\n";
140                if ($username != '') $logcontent .= "User Name: " . $username .", frequency " . $namefreq . ", confidence " . $nameconf . "\n";
141                $logcontent .= "It was accessing " . $INFO['id'] . "\n\n";
142                fwrite($loghandle, $logcontent);
143                fclose($loghandle);
144            }
145            return TRUE;
146        } else return FALSE;
147    }
148
149    /**
150     * Quick check of the IP address
151     * Investigates about both the frequency score and the confidence score.
152     *
153     * @param string $ip - Remote IP address will be used if empty
154     * @param value $freqborder - The conf "freqBorder" will be used if empty or minus value.  Don't check if 0.
155     * @param value $confborder - The conf "confidenceBorder" will be used if empty or minus value.  Don't check if 0.
156     * @return boolean - TRUE if the function freqcheck, confcheck, or both is(are) TRUE, FALSE otherwise.
157     */
158    function quickipcheck($ip = null, $freqborder = null, $confborder = null){
159        if ($ip == '') $ip = $_SERVER['REMOTE_ADDR'];
160        $freqcheck = FALSE;
161        $confcheck = FALSE;
162
163        $freqcheck = $this->freqcheck($ip, null, null, FALSE, $freqborder);
164        $confcheck = $this->confcheck($ip, null, null, FALSE, $confborder);
165        if ($freqcheck or $confcheck) return TRUE; else return FALSE;
166    }
167
168    /**
169     * Report and add a spammer to the database of the forum
170     * The API key in configuration "reportAPI" will be automatically used.
171     *
172     * @param string $ip - Required
173     * @param string $email - Required
174     * @param string $username - Required
175     * @param string $evidence - Optional
176     * @return array ('succeeded' => [TRUE if succeeded, or FALSE if failed], 'message' => [a message that indicates whether or not the plugin successfully reported the user (If failed, it contains the error message (sometimes sent by the API).)])
177     */
178    function addToDatabase($ip = null, $email = null, $username = null, $evidence = null){
179        if ($ip == null or $email == null or $username == null) return array('succeeded' => FALSE, 'message' => $this->getLang('lackingArgs'));
180        $api = $this->getConf('reportAPI');
181        if ($api == '') return array('succeeded' => FALSE, 'message' => $this->getLang('lackingAPI'));
182
183        // The script below was adopted from https://www.stopforumspam.com/usage
184        $data = array(
185            'username' => $username,
186            'ip_addr' => $ip,
187            'evidence' => $evidence,
188            'email' => $email,
189            'api_key' => $api
190        );
191
192        $data = http_build_query($data, '', '&', PHP_QUERY_RFC3986);
193
194        $this->ch = curl_init();
195        if ($this->ch) {
196            curl_setopt ($this->ch, CURLOPT_URL, 'https://www.stopforumspam.com/add.php');
197            curl_setopt ($this->ch, CURLOPT_POST, 1);
198            curl_setopt ($this->ch, CURLOPT_POSTFIELDS, $data);
199            curl_setopt ($this->ch, CURLOPT_RETURNTRANSFER, true);
200            $result = curl_exec ($this->ch);
201            $detail = curl_getinfo($this->ch);
202            curl_close ($this->ch);
203
204            if ($detail['http_code'] == '200') return array('succeeded' => TRUE, 'message' => $this->getLang('submitted')); else return array('succeeded' => FALSE, 'message' => $this->getLang('errorHappened') . strip_tags($result));
205
206        } else return array('succeeded' => FALSE, 'message' => $this->getLang('curlError'));
207    }
208
209    /**
210     * Check about whitelists in the configuration
211     *
212     * @param string $ip
213     * @param string $email
214     * @param string $username
215     * @return boolean - TRUE if he is in the whitelist(s), FALSE otherwise
216     */
217    function whitelists ($ip = null, $email = null, $username = null) {
218            global $INFO;
219
220            // IPs
221            $exlist = str_replace(array("\r\n", "\r", "\n"), "\n", $this->getConf('ipWhitelist'));
222            $exlist = preg_quote($exlist, '/');
223            $exlist = str_replace('\*', '[0-9]+', $exlist);
224            $exlist = str_replace('\?', '[0-9]', $exlist);
225            $exlist = explode("\n", $exlist);
226
227            foreach ($exlist as $checking) {
228                if ($checking == '') continue;
229                $prefix = '/^' . $checking . '$/';
230                if (preg_match($prefix, $ip)) return TRUE;
231            }
232
233            // User names
234            $exlist = str_replace(array("\r\n", "\r", "\n"), "\n", $this->getConf('nameWhitelist'));
235            $exlist = preg_quote($exlist, '/');
236            $exlist = str_replace('\*', '.+', $exlist);
237            $exlist = str_replace('\?', '.', $exlist);
238            $exlist = str_replace('~', '[0-9]+', $exlist);
239            $exlist = str_replace('\!', '[0-9]', $exlist);
240            $exlist = explode("\n", $exlist);
241
242            foreach ($exlist as $checking) {
243                if ($checking == '') continue;
244                $prefix = '/^' . $checking . '$/';
245                if (preg_match($prefix, $username)) return TRUE;
246            }
247
248            // E-mail addresses
249            $exlist = str_replace(array("\r\n", "\r", "\n"), "\n", $this->getConf('emailWhitelist'));
250            $exlist = preg_quote($exlist, '/');
251            $exlist = str_replace('\*', '.+', $exlist);
252            $exlist = str_replace('\?', '.', $exlist);
253            $exlist = str_replace('~', '[0-9]+', $exlist);
254            $exlist = str_replace('\!', '[0-9]', $exlist);
255            $exlist = explode("\n", $exlist);
256
257            // IDN conversion
258            $emexp = explode('@', $email, 2);
259            $ascii = idn_to_ascii($emexp[1]);
260            $utf8 = idn_to_utf8($emexp[1]);
261            $ascii = $emexp[0] . $ascii;
262            $utf8 = $emexp[0] . $utf8;
263
264            foreach ($exlist as $checking) {
265                if ($checking == '') continue;
266                $prefix = '/^' . $checking . '$/';
267                if (preg_match($prefix, $ascii)) return TRUE;
268                if (preg_match($prefix, $utf8)) return TRUE;
269            }
270
271            // Skip logged-in users, managers and superusers?
272            if ($this->getConf('skipMgAndSp') == 'mg' && auth_ismanager()) return TRUE;
273            if ($this->getConf('skipMgAndSp') == 'sp' && auth_isadmin()) return TRUE;
274            if ($this->getConf('skipMgAndSp') == 'user' && $_SERVER['REMOTE_USER']) return TRUE;
275
276            // Check about a list of users and user groups
277            if(auth_isMember($this->getConf('userWhitelist'), $_SERVER['REMOTE_USER'], (array) $USERINFO['grps'])) return TRUE;
278
279            // Not in whitelists
280            return FALSE;
281    }
282}
283