spammer counter の記事で書いた,403 にかかった IP を保存するスクリプトを改造した.
まず最初に注意だが, ErrorDocument 403 で php ファイルへ飛ばす場合,その php ファイルへのアクセスは可能とすること.
さもなくば, 403 の転送で無限ループが発生する.
具体的には,.htaccess へ下記を追記.hogehoge.phpの部分は,設置したphpファイル名に変更すること.
<Files hogehoge.php> allow from all </Files>
もともとこのスクリプトを設置した理由は,弾いている IP を subnet mask でまとめるため,
subnet 毎のアクセスを把握したかったためなのだが,
先の記事のログではアクセス順に並ぶため, subnet 毎の状況を一見して把握できなかった.
そこで,アクセスがある毎に, 1 回だけ bubble sort を走らせることにした.
bubble sort で実装し行毎処理を行なっているのは,
全てメモリに読み込んで sort するのは,spammer のアドレスが想像以上に多いので,
ライン数が増えて処理に時間がかかるようになり,ファイルロックに邪魔されて
spammer のアクセスを拾えなくなるのではないかという懸念のため.
実際には,File I/O の方がボトルネックになっている気がするので,
fwrite 一発で書き込んだ方が処理は早いかも知れない…
もっと行数が増えたら,測定してみよう.
で,まぁコードはこんな感じ.
-- 追記
ごめん,バグがあった.
ループ前の1回の処理でbreak文を消してなかったので,バグってた.
-- 追記2
fgetcsv で取得した末尾の空白文字を取り除かないと \n が入ってしまうらしいことに,
後から気付いた.
全部 int キャストすることで,回避.
<?php header('HTTP/1.1 403 Forbidden'); header('Content-Type: text/html; charset=iso-8859-1'); ?> <!DOCTYPE HTML PUBLIC '-//IETF//DTD HTML 2.0//EN'> <HTML><HEAD> <TITLE>403 Forbidden</TITLE> </HEAD><BODY> <H1>Forbidden</H1> <?php printf('You don't have permission to access %s\non this server.', htmlentities(strip_tags($_SERVER['REQUEST_URI']))); ?><P> <HR> <ADDRESS><?php $e = explode(' ',$_SERVER['SERVER_SOFTWARE']); printf('%s Server at %s Port %d',$e[0],$_ENV['SERVER_NAME'],$_ENV['SERVER_PORT']); ?></ADDRESS> </BODY></HTML> <?php // comparison 2 IP addresses function aIsLargerThanB($a,$b) { // a and b are arrays of 4 integers, e.g. 127.0.0.1 is expressed as // array(127,0,0,1) if ($a[0]==$b[0]) if ($a[1]==$b[1]) if ($a[2]==$b[2]) return $a[3] > $b[3]; else return $a[2] > $b[2]; else return $a[1] > $b[1]; else return $a[0] > $b[0]; } // parse IP address to array of 4 integers. function parseIpAddress($ip_addr){ $return = array(); foreach ( explode('.',$ip_addr) as $i) $return[] = (int)$i; return $return; } // count IP addr $addr = $_SERVER['REMOTE_ADDR']; $fobj = fopen('counter.dat','r'); if (flock($fobj, LOCK_EX)) { $oobj = fopen('counter.tmp','w'); $flag=false; // 1st processing $s=fgetcsv($fobj); if (count($s)==2 and $s[0] === $addr) { $s[1]=(int)$s[1]+1; $flag=true; } else { $s[1] = (int)$s[1];} $prev = array($s[0],$s[1]); // process loop while (!feof($fobj)) { $s=fgetcsv($fobj); $s[1] = rtrim($s[1]); if (count($s)<2) { fputcsv($oobj,$prev); break; } elseif (!$flag and $s[0] === $addr) { $s[1]=(int)$s[1]+1; $flag=true; } else { $s[1] = (int)$s[1];} $pips = parseIpAddress($prev[0]); $nips = parseIpAddress($s[0]); // bubble sort if (aIsLargerThanB($pips,$nips)) { fputcsv($oobj,$s); } else { fputcsv($oobj,$prev); $prev = array($s[0],$s[1]); } } fputcsv($oobj,$prev); if (!$flag) { fprintf($oobj,'%s,1\n',$addr);} flock($fobj,LOCK_UN); fclose($fobj); fclose($oobj); copy('counter.tmp','counter.dat'); } else { fclose($fobj); } ?>