/ modes / report.php
report.php
  1  <?php
  2  
  3  	function report_get_style($board) {
  4  		$styles = array(
  5  					'Yotsuba' => STATIC_SERVER.'css/yotsuba.css',
  6  					'Yotsuba B' => STATIC_SERVER.'css/yotsublue.css',
  7  					'Futaba' => STATIC_SERVER.'css/futaba.css',
  8  					'Burichan' => STATIC_SERVER.'css/burichan.css',
  9  					);
 10  		$board = mysql_real_escape_string($board);
 11  		$query = mysql_global_call("SELECT domain FROM boardlist where dir='$board'");
 12  		list($domain) = mysql_fetch_row($query);
 13  		if(DEFAULT_BURICHAN == 1)
 14  			$styletitle = ($_COOKIE['ws_style']?$_COOKIE['ws_style']:'Yotsuba B');
 15  		elseif($domain == 'may')
 16  			$styletitle = 'not4chan';
 17  		else
 18  			$styletitle = ($_COOKIE['nws_style']?$_COOKIE['nws_style']:'Yotsuba');
 19  		return $styles[$styletitle];
 20  	}
 21  
 22  function log_cleared_reporter($long_ip, $pwd, $pass_id, $cat_id, $weight) {
 23      $sql = <<<SQL
 24  INSERT INTO report_clear_log(long_ip, pwd, pass_id, category, weight)
 25  VALUES(%d, '%s', '%s', %d, %F)
 26  SQL;
 27      
 28    return !!mysql_global_call($sql, $long_ip, $pwd, $pass_id, $cat_id, $weight);
 29  }
 30  
 31  function report_can_bypass_captcha($ip, $userpwd, $post) {
 32    if (!$userpwd || !$post) {
 33      return false;
 34    }
 35    
 36    if ($userpwd->ipLifetime() < 604800) { // 7 days
 37      return false;
 38    }
 39    
 40    if (!$post['fsize']) { // only posts with images
 41      return false;
 42    }
 43    
 44    $allowance = 3;
 45    
 46    $long_ip = ip2long($ip);
 47    
 48    if (!$long_ip) {
 49      return false;
 50    }
 51    
 52    // Allow $allowance no-captcha reports for every hour of inactivity
 53    $sql = <<<SQL
 54  SELECT COUNT(*) as cnt FROM user_actions WHERE ip = $long_ip
 55  AND action = 'report'
 56  AND time > DATE_SUB(NOW(), INTERVAL 1 HOUR)
 57  SQL;
 58  
 59    $res = mysql_global_call($sql);
 60    
 61    if (!$res) {
 62      return false;
 63    }
 64    
 65    $row = mysql_fetch_row($res);
 66    
 67    if (!$row || $row[0] >= $allowance) {
 68      return false;
 69    }
 70    
 71    // Don't allow ips with 1 cleared reports in the past 72 hours
 72    $sql = <<<SQL
 73  SELECT COUNT(*) FROM report_clear_log
 74  WHERE long_ip = $long_ip AND created_on > DATE_SUB(NOW(), INTERVAL 72 HOUR)
 75  SQL;
 76    
 77    $res = mysql_global_call($sql);
 78    
 79    if (!$res) {
 80      return false;
 81    }
 82    
 83    $count = (int)mysql_fetch_row($res)[0];
 84    
 85    if ($count >= 1) {
 86      return false;
 87    }
 88    
 89    // Don't allow ips with recent warn/ban history
 90    $sql = <<<SQL
 91  SELECT no FROM banned_users
 92  WHERE host = '%s'
 93  AND now > DATE_SUB(NOW(), INTERVAL 30 DAY)
 94  LIMIT 1
 95  SQL;
 96    
 97    $res = mysql_global_call($sql, $ip);
 98    
 99    if (!$res) {
100      return false;
101    }
102    
103    if (mysql_num_rows($res) > 0) {
104      return false;
105    }
106    
107    return true;
108  }
109    
110    function report_check_ip($board, $no, $check_ban = false, $is_illegal = false) {
111      global $captcha_bypass, $passid;
112      
113      $board = mysql_real_escape_string($board);
114      
115      $no = mysql_real_escape_string($no);
116      
117      $ip = ip2long($_SERVER['REMOTE_ADDR']);
118      
119      $pass_sql = false;
120      
121      $pwd_sql = false;
122      
123      // Check if already reported
124      // by IP
125      $rep_clauses = array("ip = '$ip'");
126      
127      // by 4chan pass
128      if ($captcha_bypass && $passid) {
129        $pass_sql = mysql_real_escape_string($passid);
130        $rep_clauses[] = "4pass_id = '$pass_sql'";
131      }
132      
133      // by password
134      $userpwd = UserPwd::getSession();
135      
136      if ($userpwd && $userpwd->getPwd()) {
137        $pwd_sql = mysql_real_escape_string($userpwd->getPwd());
138        $rep_clauses[] = "pwd = '$pwd_sql'";
139      }
140      
141      $rep_clauses_sql = implode(' OR ', $rep_clauses);
142      
143      $res = mysql_global_call("SELECT no FROM reports WHERE ($rep_clauses_sql) AND board = '$board' AND no = '$no'");
144      
145      if ($res && mysql_num_rows($res) > 0) {
146        fancydie('You have already reported this post.');
147      }
148      
149      // Check cooldown
150      $res = mysql_global_call("SELECT no FROM reports WHERE ($rep_clauses_sql) AND ts > DATE_SUB(NOW(), INTERVAL 15 SECOND) LIMIT 1");
151      
152      if ($res && mysql_num_rows($res) > 0) {
153        fancydie('You have to wait a while before reporting another post.');
154      }
155      
156      // Check hourly limits
157      $res = mysql_global_call("SELECT COUNT(*) FROM reports WHERE ($rep_clauses_sql) AND ts > DATE_SUB(NOW(), INTERVAL 1 HOUR) LIMIT 1");
158      
159      if ($res && mysql_fetch_row($res)[0] >= RENZOKU_REP_HOURLY) {
160        fancydie('You have to wait a while before reporting another post.');
161      }
162      
163      // Check daily limits
164      $res = mysql_global_call("SELECT COUNT(*) FROM reports WHERE ($rep_clauses_sql) AND ts > DATE_SUB(NOW(), INTERVAL 24 HOUR) LIMIT 1");
165      
166      if ($res && mysql_fetch_row($res)[0] >= RENZOKU_REP_DAILY) {
167        fancydie('You have to wait a while before reporting another post.');
168      }
169      
170      // Check if banned
171      if ($check_ban) {
172        $ip_sql = mysql_real_escape_string($_SERVER['REMOTE_ADDR']);
173        
174        // by ip
175        $ban_clauses = array("host = '$ip_sql'");
176        
177        // by 4chan pass
178        if ($pass_sql) {
179          $ban_clauses[] = "4pass_id = '$pass_sql'";
180        }
181        
182        // by password
183        if ($pwd_sql) {
184          $ban_clauses[] = "password = '$pwd_sql'";
185        }
186        
187        $ban_clauses_sql = implode(' OR ', $ban_clauses);
188        
189        $res = mysql_global_call("SELECT COUNT(*) FROM banned_users WHERE ($ban_clauses_sql) AND active = 1 AND (global = 1 OR board = '$board')");
190        
191        if ($res && mysql_fetch_row($res)[0] > 0) {
192          fancydie('You can\'t report posts because you are <a href="https://www.' .
193            L::d(BOARD_DIR) .
194            '/banned" target="_blank">banned</a>.');
195        }
196        
197        if ($captcha_bypass !== true) {
198          $longip = ip2long($_SERVER['REMOTE_ADDR']);
199          
200          if (isset($_SERVER['HTTP_X_GEO_ASN'])) {
201            $asn = (int)$_SERVER['HTTP_X_GEO_ASN'];
202          }
203          else {
204            $_asninfo = GeoIP2::get_asn($_SERVER['REMOTE_ADDR']);
205            
206            if ($_asninfo) {
207              $asn = (int)$_asninfo['asn'];
208            }
209            else {
210              $asn = 0;
211            }
212          }
213          
214          if (isIPRangeBannedReport($longip, $asn, BOARD_DIR, $userpwd)) {
215            fancydie('Reporting from this IP range has been blocked due to abuse. [<a href="//www.' .
216              L::d(BOARD_DIR) .
217              '/faq#blocked" target="_blank">More Info</a>]<br>4chan Pass users can bypass this block. [<a href="https://www.4chan.org/pass" target="_blank">Learn More</a>]');
218          }
219        }
220      }
221    }
222  
223  	function report_increment_counter() {
224  		return; // broken lol
225  		$count = @file_get_contents('reports/report.count');
226  		if(!$count) $count = 0;
227  		$count++;
228  		file_put_contents('reports/report.count',$count);
229  	}
230  
231  	function report_post_exists($no) {
232  		$query=mysql_board_call("SELECT COUNT(*) FROM `".SQLLOG."` WHERE no='$no'");
233  		return mysql_result($query,0,0);
234  	}
235  
236  	function report_is_capcoded_post( $no )
237  	{
238  		$query = mysql_board_call( "SELECT COUNT(*) FROM `%s` WHERE capcode != 'none' AND no=%d", SQLLOG, $no );
239  		return mysql_result( $query, 0, 0 );
240  	}
241  
242  	function report_check_autodelete($board,$no) {
243  		$query = mysql_global_do("SELECT COUNT(*) FROM reports WHERE board='$board' AND no='$no'");
244  		$count = mysql_result($query,0,0);
245  
246  		if(defined('REPORTS_AUTODELETE') && $count >= REPORTS_AUTODELETE) {
247  			report_do_autodelete($board,$no,1);
248  			return;
249  		}
250  
251  		$query = mysql_global_do("SELECT COUNT(*) FROM reports WHERE cat='2' AND board='$board' AND no='$no'");
252  		$count = mysql_result($query,0,0);
253  		if(defined('REPORTS_AUTODELETE_ILLEGAL') && $count >= REPORTS_AUTODELETE_ILLEGAL) {
254  			report_do_autodelete($board,$no,2);
255  			return;
256  		}
257  	}
258  	function report_do_autodelete($board,$no,$cat) {
259  		$query = mysql_board_call("SELECT * FROM `".SQLLOG."` WHERE no='$no'");
260  		$row = mysql_fetch_assoc($query);
261  		if(!$row) return;
262  		$auser = 'Auto-del';
263  		$adfsize=($row['fsize']>0)?1:0;
264  		$adname=str_replace('</span> <span class="postertrip">!','#',$row['name']);
265  		$imgonly = 0;
266  		$row['sub'] = mysql_escape_string($row['sub']);
267  		$row['com'] = mysql_escape_string($row['com']);
268  		$row['filename'] = mysql_escape_string($row['filename']);
269  		mysql_global_do("INSERT INTO ".SQLLOGDEL." (imgonly,postno,board,name,sub,com,img,filename,admin) values('$imgonly','$no','".SQLLOG."','$adname','{$row['sub']}','{$row['com']}','$adfsize','{$row['filename']}','$auser')");
270  		delete_post($no, '', 0, 1, 1);
271  	}
272  	function report_log_action($board,$no) {
273  		mysql_global_call("insert into user_actions (ip,board,action,postno,time) values (%d,'%s','report',%d,now())", ip2long($_SERVER["REMOTE_ADDR"]), $board, $no);
274  	}
275  
276  	function report_post_sticky($no) {
277  		$query=mysql_board_call("SELECT sticky FROM `".SQLLOG."` WHERE no='$no'");
278  		return mysql_result($query,0,0);
279  	}
280  
281  function report_check_post($board, $post_id) {
282    $sql = "SELECT * FROM `%s` WHERE no = %d";
283    
284    $res = mysql_board_call($sql, $board, $post_id);
285    
286    if (!$res) {
287      fancydie(S_POST_DEAD);
288    }
289    
290    $post = mysql_fetch_assoc($res);
291    
292    if (!$post) {
293      fancydie(S_POST_DEAD);
294    }
295    
296    if ($post['sticky']) {
297      fancydie(S_CANNOTREPORTSTICKY);
298    }
299    
300    if ($post['capcode'] !== 'none') {
301      fancydie(S_CANNOTREPORT);
302    }
303    
304    return $post;
305  }
306  
307  function get_report_categories($board, $post_id, $is_worksafe) {
308    $query = "SELECT * FROM report_categories ORDER BY board ASC";
309    
310    $res = mysql_global_call($query);
311    
312    if (!$res) {
313      return false;
314    }
315    
316    $query = "SELECT resto, fsize, filedeleted FROM `%s` WHERE no = %d";
317    
318    $res2 = mysql_board_call($query, $board, $post_id);
319    
320    if (!$res2) {
321      return false;
322    }
323    
324    $post = mysql_fetch_assoc($res2);
325    
326    if (!$post) {
327      return false;
328    }
329    
330    $is_op = !$post['resto'];
331    $has_image = $post['fsize'] && !$post['filedeleted'];
332    
333    // ID of the category which will be used for the Illegal radio button
334    $illegal_cat_id = 31;
335    
336    // Rule violations + one illegal category
337    $data = array('rule' => null, 'illegal' => null);
338    
339    // Sorting, board specific categories go on top
340    $data_rule_top = array();
341    $data_rule_bottom = array();
342    
343    $match_board = ',' . $board . ',';
344    
345    while ($cat = mysql_fetch_assoc($res)) {
346      if ($cat['id'] == $illegal_cat_id) {
347        $data['illegal'] = $cat;
348        continue;
349      }
350      
351      if ($cat['board'] !== '') {
352        if ($cat['board'] === '_ws_') {
353          if (!$is_worksafe) {
354            continue;
355          }
356        }
357        else if ($cat['board'] === '_nws_') {
358          if ($is_worksafe) {
359            continue;
360          }
361        }
362        else if ($cat['board'] !== $board) {
363          continue;
364        }
365      }
366      
367      if ($cat['op_only'] && !$is_op) {
368        continue;
369      }
370      
371      if ($cat['reply_only'] && $is_op) {
372        continue;
373      }
374      
375      if ($cat['image_only'] && !$has_image) {
376        continue;
377      }
378      
379      if ($cat['exclude_boards'] && strpos(",{$cat['exclude_boards']},", $match_board) !== false) {
380        continue;
381      }
382      
383      if ($cat['board']) {
384        $data_rule_top[$cat['id']] = $cat;
385      }
386      else {
387        $data_rule_bottom[$cat['id']] = $cat;
388      }
389    }
390    
391    $data['rule'] = $data_rule_top + $data_rule_bottom;
392    
393    return $data;
394  }
395  
396  /**
397   * Checks if the report should have a different priority
398   * based on the number of cleared reports in the past X days and ban history.
399   */
400  function is_report_filtered($filter_thres, $ip, $long_ip, $pass_id = null, $pwd = null) {
401    if ($filter_thres < 1) {
402      return false;
403    }
404    
405    // only count reports made in the past X days
406    $cleared_days_lim = 2;
407    // number of cleared reports for the IP to be considered 'abusive'
408    $cleared_count_lim = (int)$filter_thres;
409    // only count bans made in the past X days
410    $ban_days_lim = 30;
411    // number of bans/warnings for the IP to be considered 'abusive'
412    $ban_count_lim = 3;
413    
414    $rep_abuse_tpl = 190; // ban template for report abusing
415    
416    $long_ip = (int)$long_ip;
417    
418    $ban_clauses = array();
419    $rep_clauses = array();
420    
421    // 4chan Pass
422    if ($pass_id) {
423      $pass_id_sql = mysql_real_escape_string($pass_id);
424      $ban_clauses[] = "4pass_id = '$pass_id_sql'";
425      $rep_clauses[] = "pass_id = '$pass_id_sql'";
426      
427      $pwd_and_ban = "4pass_id != '$pass_id_sql'";
428      $pwd_and_rep = "pass_id != '$pass_id_sql'";
429    }
430    // IP
431    else {
432      $ip_sql = mysql_real_escape_string($ip);
433      $ban_clauses[] = "host = '$ip_sql'";
434      $rep_clauses[] = "long_ip = $long_ip";
435      
436      $pwd_and_ban = "host != '$ip_sql'";
437      $pwd_and_rep = "long_ip != $long_ip";
438    }
439  
440    // Password
441    if ($pwd) {
442      $pwd_sql = mysql_real_escape_string($pwd);
443      $ban_clauses[] = "password = '$pwd_sql' AND $pwd_and_ban";
444      $rep_clauses[] = "pwd = '$pwd_sql' AND $pwd_and_rep";
445    }
446    
447    // ---
448    // Check cleared reports
449    // ---
450    $clear_count = 0;
451    
452    foreach ($rep_clauses as $clause) {
453      $query = <<<SQL
454  SELECT COUNT(*) FROM report_clear_log
455  WHERE $clause AND created_on > DATE_SUB(NOW(), INTERVAL $cleared_days_lim DAY)
456  SQL;
457      
458      $res = mysql_global_call($query);
459      
460      if (!$res) {
461        return false;
462      }
463      
464      $clear_count += (int)mysql_fetch_row($res)[0];
465      
466      if ($clear_count >= $cleared_count_lim) {
467        return true;
468      }
469    }
470    
471    // ---
472    // Check ban history
473    // ---
474    $ban_count = 0;
475    
476    foreach ($ban_clauses as $clause) {
477      $query = <<<SQL
478  SELECT COUNT(*) FROM banned_users
479  WHERE active = 0 AND $clause AND template_id = $rep_abuse_tpl
480  AND now > DATE_SUB(NOW(), INTERVAL $ban_days_lim DAY)
481  SQL;
482      
483      $res = mysql_global_call($query);
484      
485      if (!$res) {
486        return false;
487      }
488      
489      $ban_count += (int)mysql_fetch_row($res)[0];
490      
491      if ($ban_count >= $ban_count_lim) {
492        return true;
493      }
494    }
495    
496    return false;
497  }
498  
499  function report_get_rel_sub($board, $thread_id) {
500    if (!$board || !$thread_id) {
501      return '';
502    }
503    
504    $thread_id = (int)$thread_id;
505    
506    $query = "SELECT sub FROM `%s` WHERE no = $thread_id";
507    
508    $res = mysql_board_call($query, $board);
509    
510    if (!$res || mysql_num_rows($res) !== 1) {
511      return '';
512    }
513    
514    return mysql_fetch_row($res)[0];
515  }
516  
517  function report_submit($board, $no, $cat_id) {
518    global $log, $passid;
519    
520    $board = mysql_real_escape_string($board);
521    $no = (int)$no;
522    $long_ip = ip2long($_SERVER['REMOTE_ADDR']);
523    
524    // check if the category is valid
525    $cats = get_report_categories($board, $no, DEFAULT_BURICHAN == 1);
526    
527    if ($cats['illegal']['id'] == $cat_id) {
528      $old_cat = 2; // todo: remove later
529      $old_field = 'num_illegal'; // todo: remove later
530      $rep_cat = $cats['illegal'];
531    }
532    else if (isset($cats['rule'][$cat_id])) {
533      $old_cat = 1;
534      $old_field = 'num_rule';
535      $rep_cat = $cats['rule'][$cat_id];
536    }
537    else {
538      fancydie('Invalid category selected.');
539    }
540    
541    if (!$no) {
542      fancydie(S_POST_DEAD);
543    }
544    
545    log_cache(0, $no, 2);
546    
547    if ($log[$no]['archived']) {
548      $extra = array('archived' => 1);
549    }
550    else {
551      $extra = array();
552    }
553    
554    $resto = (int)$log[$no]['resto'];
555    
556    $post_data = generate_post_json($log[$no], $log[$no]['resto'] ? $log[$no]['resto'] : $no, $extra);
557    
558    if ($log[$no]['resto']) {
559      $rel_sub = report_get_rel_sub($board, $log[$no]['resto']);
560      
561      if ($rel_sub !== '') {
562        $post_data['rel_sub'] = $rel_sub;
563      }
564    }
565    
566    $json = json_encode($post_data);
567    
568    $weight = $rep_cat['weight'];
569    
570    $is_staff = has_level('janitor');
571    
572    $req_sig = spam_filter_get_req_sig();
573    
574    $userpwd = UserPwd::getSession();
575    
576    if ($userpwd) {
577      $pwd = $userpwd->getPwd();
578      $is_new_pwd = $userpwd->isNew();
579      $is_known_pwd = $userpwd->isUserKnownOrVerified(60);
580    }
581    else {
582      $pwd = null;
583      $is_new_pwd = true;
584      $is_known_pwd = false;
585    }
586    
587    if (!$is_staff) {
588      $ignore_reason = 0;
589      
590      $_threat_score = spam_filter_get_threat_score(null, true, false);
591      
592      if (!$is_known_pwd) {
593        $ignore_reason = 1;
594      }
595      else if ($_threat_score >= 0.4) {
596        $ignore_reason = 2;
597      }
598      else if ($rep_cat['filtered']) {
599        if (is_report_filtered($rep_cat['filtered'], $_SERVER['REMOTE_ADDR'], $long_ip, $passid, $is_new_pwd ? null : $pwd)) {
600          $ignore_reason = 3;
601        }
602      }
603    }
604    
605    if ($ignore_reason > 0) {
606      $weight = 0.5;
607      if ($ignore_reason == 2) {
608        $_bot_headers = spam_filter_format_http_headers($log[$no]['com'], '', '', $_threat_score, $req_sig);
609        log_spam_filter_trigger('ignore_report_score', BOARD_DIR, $no, $_SERVER['REMOTE_ADDR'], $ignore_reason, $_bot_headers);
610      }
611    }
612    
613    // Check if the post was already reported and cleared
614    $is_cleared = 0;
615    $cleared_by = '';
616    
617    $query = "SELECT cleared_by FROM reports WHERE board = '$board' AND no = $no AND cleared = 1 LIMIT 1";
618    
619    $res = mysql_global_call($query);
620    
621    if ($res) {
622      $row = mysql_fetch_row($res);
623      
624      if ($row) {
625        $is_cleared = 1;
626        $cleared_by = $row[0];
627        log_cleared_reporter($long_ip, $pwd, $passid, $rep_cat['id'], $weight);
628      }
629    }
630    
631    $is_ws = DEFAULT_BURICHAN == 1 ? 1 : 0;
632    
633    $query = <<<SQL
634  INSERT IGNORE INTO reports
635  SET ip = %d, pwd = '%s', 4pass_id = '%s', req_sig = '%s', board = '%s', no = %d, resto = %d,
636  cat = %d, weight = %F, report_category = %d, ws = $is_ws, post_ip = %d, post_json = '%s',
637  cleared = $is_cleared, cleared_by = '%s'
638  SQL;
639    
640    $res = mysql_global_call($query,
641      $long_ip, $pwd, $passid, $req_sig, $board, $no, $resto,
642      $old_cat, $weight, $rep_cat['id'], ip2long($log[$no]['host']), $json, $cleared_by
643    );
644    
645    if (!$res) {
646      fancydie('There was an error submitting your report. Please try again.');
647    }
648    
649    $query = <<<SQL
650  INSERT INTO `reports_for_posts` (`board`, `postid`, `threadid`, `$old_field`, `max_cat`)
651  VALUES ('$board', $no, $resto, 1, $old_cat)
652  ON DUPLICATE KEY UPDATE $old_field = $old_field + 1, max_cat = IF(num_illegal >= num_rule, 2, 1)
653  SQL;
654    
655    $res = mysql_global_call($query);
656    
657    report_log_action($board, $no);
658    
659    if ($userpwd) {
660      $userpwd->updateReportActivity();
661      $userpwd->setCookie('.' . MAIN_DOMAIN);
662    }
663    
664    fancydie('Report submitted! This window will close in 3 seconds...', 1);
665  }