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);
}
?>