#!/usr/bin/perl
# ↑この行をプロバイダの環境にあわせて変更してください。
# (上はBIGLOBE 2*サーバー用です)
# #! の前には空行もスペース文字も入れないようにしてください。
# perl ver.5 only
#==================================================================
# 名称: whcount.cgi Ver3.07 rm.5.1
# 原作者: とほほさん 改修: Hassy
# 原作入手先: http://tohoho.wakusei.ne.jp/
# 連絡先: hassym@pal.tok2.com
# 取り扱い: フリーソフト。利用/改造/再配布可能。確認メール不要。
#
# このスクリプトはとほほさんの保証対象外となっていますので
# ご質問がある時はhassyの方へお願いします
#
#==================================================================
#==================================================================
# 使いかた:
#==================================================================
# (書式1) whcount.cgi?test
# CGIが使用できるかテストを行う。
#
# (書式2) whcount.cgi?text
# カウントアップを行い、カウンタをテキストで表示する。
#
# (書式3) whcount.cgi?gif
# カウントアップを行い、カウンタをGIFで表示する。
#
# (書式4) whcount.cgi?hide+xxx.gif
# カウントアップを行い、xxx.gifを表示する。
#
# (書式5) whcount.cgi?flash (Flashからの呼び出しのみ)
# カウントアップを行い、カウンタ値をFlashに渡す。
#==================================================================
# 履歴:
#==================================================================
# 日付 Ver
# 2000.08.10 rm.1 Flash対応とカウンターイベントの組み込み
# 2000.08.15 rm.2 カウンター値喪失時の対処追加
# 2000.10.19 rm.3 アクセスログをサーバーに残す設定を追加
# 2000.12.05 rm.4 次の切り番表示機能と音の再生機能追加
# 2000.12.10 rm.4.1 CGIの高速化
# 2001. 2. 8 rm.4.2 WAKWAKへの対応と細かなバグフィックス
# 2001. 4.19 rm.4.3 NIFTYへの対応
# 2002. 5.25 rm.5.0 SSI表示時の切り番表示切替機能追加
# (上手く作動するのか不安?)
# 及びTripodへの対応
# 2002. 8. 1 rm.5.1 ホスト名取得部のバグ修正
#
#==================================================================
# カスタマイズ:
#==================================================================
#
# SSIのテキストモードで使用する場合は「$mode = "text";」としてください。
#
$mode = "";
#
# 表示桁数を例えば5桁に指定する場合は「$figure = 5;」のように指定する。
# 自動調整するには「$figure = 0;」と指定する。
#
$figure = 5;
#
# ファイルロック機能をオンにする場合
# サーバーがflock関数をサポートしている時は「$do_file_lock = 1;」とする。
# 〃 サポートしていない時は「$do_file_lock = 2;」とする。
#
# ファイルロック機能をオフにする場合は「$do_file_lock = 0;」とする。
#
$do_file_lock = 1;
#
# 同アドレスチェック機能をオンにする場合は「$do_address_check = 1;」とする。
# 同アドレスチェック機能をオフにする場合は「$do_address_check = 0;」とする。
#
$do_address_check = 1;
#
# レポート機能を使う場合は「$mail_to = 'abc@xxx.yyy.zzz';」のように自分の
# メールアドレスを設定する。また、sendmailが使用可能であることを確認
# しておく。別の場所にある場合は、$sendmail も適切に変更する。
# 詳細メールの場合は「$account_detail = 1;」とし
# アクセス件数のみをメールする場合は「$account_detail = 0;」とする。
#
# 但し、アクセス件数のみのメールの場合でもカウンターイベントが起こった
# 時のログは送られてきます
#
# レポート機能を使用しない設定の時は
# 自動的にサーバーにログを残す設定になります
#
$mail_to = '';
$sendmail = '/usr/sbin/sendmail';
$account_detail = 0;
#
# [サーバーにログを残す設定のみ有効]
# サーバーにログを残す日数を設定してください
# 「$left_day = 0;」とするとログを自動削除しません
#
$left_day = 7;
#
#[サーバーにログを残す設定のみ有効]
#ログファイルのパーミッションを設定してください
#
$pm = "0666";
#
# ホスト名の取得をしない場合は
# 「$get_hostname = 0;」として下さい。
# (ホスト名の検索が出来ないサーバを使用している場合は
# 取得をしない設定にして下さい)
#
$get_hostname = 1;
#
# このアドレスにマッチするサイトからの FROM は表示しない。
#
$my_url = '';
#
# カウンター名
#
$count_name = "whcount";
#
# [引数にtext指定時のみ有効]
# 切り番取得時に表示されるメッセージを
# 「$ssi_msg = <<"HTML";」と「HTML」の間に記入してください。
# その時に上記の行は変更しないで下さい。
# (「_cnt_」の部分がカウント値に置き換わります)
#
# 「$ssi_msg = "";」の1行のみとすると表示は変わりません。
#
$ssi_msg = <<"HTML";
conglaturation!!
あなたは見事_cnt_をGETしました!!
welcome myBBS!
HTML
#
# [引数にgif指定時のみ有効]
# 今日及び昨日のカウンター値用のgifファイルが在るディレクトリ名
# (積算値と同一にする場合は「$img_dir = "";」 とする)
#
$img_dir = "small";
#
# [引数にgif指定時のみ有効]
# カウンターイベントが起きた際のカウンター値の前に表示
# されるgif画像を”〜”内に入力して下さい
# 表示したくない場合は「$topgif = "";」とします
#
$topgif = "./top.gif";
#
# [引数にgif指定時のみ有効]
# カウンターイベントが起きた際のカウンター値の後ろに表示
# されるgif画像を”〜”内に入力して下さい
# 表示したくない場合は「$lastgif = "";」とします
#
$lastgif = "./last.gif";
#
# [引数にsound指定時のみ有効]
# 通常時に流れる音楽ファイル
#
$normal_sound = "./none.mid";
#
# [引数にsound指定時のみ有効]
# イベント時に流れる音楽ファイル
#
$event_sound = "./event.mid";
#==================================================================
# 処理部:
#==================================================================
#
# 関連するファイルを洗い出しておく
# このCGIスクリプトのファイル名の拡張子を変更したものになる。
#
$file_count = "./$count_name.cnt";
$file_date = "./$count_name.dat";
$file_access = "./$count_name.acc";
$file_event = "./$count_name.evt";
$file_evtlog = "./$count_name/eventlog.log";
$file_lock_m = "./lock/$count_name.mlo";
$file_lock_f = "./lock/$count_name.flo";
@in = (@ARGV) ? @ARGV : split(/\+/, $ENV{'QUERY_STRING'});
#
# CGIが使用できるかテストを行う。
#
if ($in[0] eq "test") {
print <<"HTML";
Content-type: text/html
Test
OK. CGIスクリプトは正常に呼び出されました
HTML
if ($mail_to ne "") {
unless (-f $sendmail) {
print "
NG. $sendmail が存在しません。\n";
}
}
if (-d $file_lock_m) {
print "
NG. $file_lock_m が残っています。\n";
}
if (! -r $file_count) {
print "
NG. $file_count が存在しません。\n";
} elsif (! -w $file_count) {
print "
NG. $file_count が書き込み可能\ではありません。\n";
}
if (! -r $file_date) {
print "
NG. $file_date が存在しません。\n";
} elsif (! -w $file_date) {
print "
NG. $file_date が書き込み可能\ではありません。\n";
}
if (! -r $file_access) {
print "
NG. $file_access が存在しません。\n";
} elsif (! -w $file_access) {
print "
NG. $file_access が書き込み可能\ではありません。\n";
}
if (! -r $file_event) {
print "
NG. $file_event が存在しません。\n";
} elsif (! -w $file_event) {
print "
NG. $file_event が書き込み可能\ではありません。\n";
}
if (! -r $file_evtlog) {
print "
NG. $file_evtlog が存在しません。\n";
} elsif (! -w $file_evtlog) {
print "
NG. $file_evtlog が書き込み可能\ではありません。\n";
}
if (! -r $file_lock_f) {
print "
NG. $file_lock_f が存在しません。\n";
}
print "\n\n";
exit(0);
}
#
# 引数を解釈する
#
$tm = time();
$ref_url = "";
foreach $i (0 .. $#in) {
if (($in[$i] eq "text") or ($in[$i] eq "gif")) {
$mode = $in[$i];
$cnt_mode = $in[++$i] if (($in[$i+1] eq "today")
or ($in[$i+1] eq "yesterday")
or ($in[$i+1] eq "next"));
} elsif ($in[$i] eq "hide") {
$mode = "hide";
$giffile = $in[++$i];
($giffile =~ /\.gif$/i) or die "Bad giffile in $giffile";
($giffile !~ /[<>|&]/) or die "Bad giffile in $giffile";
} elsif ($in[$i] eq "name") {
$count_name = $in[++$i];
($count_name =~ /^[a-z\d]+$/i) or die "Bad cntname in $count_name";
$file_count = "./$count_name.cnt";
$file_date = "./$count_name.dat";
$file_access = "./$count_name.acc";
$file_event = "./$count_name.evt";
$file_evtlog = "./$count_name/eventlog.log";
} elsif ($in[$i] eq "ref") {
$ref_url = $in[++$i];
} elsif ($in[$i] eq "flash") {
$mode = "flash";
$cnt_mode = "";
}elsif ($in[$i] eq "sound") {
$mode = "sound";
$cnt_mode = "";
}
}
sleep(3) if ($cnt_mode or ($mode eq "sound"));
#
# flock関数でロック権を得る
#
if ($do_file_lock == 1) {
if (open(LOCK, "$file_lock_f")) {
eval { flock(LOCK, 2); };
$do_file_lock = $@ ? 2 : 1;
} else { $do_file_lock = 2; }
}
#
# ディレクトリ作成でロック権を得る
#
if ($do_file_lock == 2) {
foreach $i (1 .. 10) {
if (mkdir("$file_lock_m", 0755)) {
# ロック成功。次の処理へ。
last;
} elsif ($i == 1) {
# 10分以上古いロックファイルは削除する。
($lock_time) = (stat($file_lock_m))[9];
rmdir($file_lock_m) if ($lock_time < ($tm - 600));
} elsif ($i < 10) {
# ロック失敗。1秒待って再トライ。
sleep(1);
} else {
# 何度やってもロック失敗。あきらめる。
die "filelock failed";
}
}
#
# 途中で終了してもロックファイルが残らないようにする
#
sub sigexit { rmdir($file_lock_m); exit(0); }
$SIG{'PIPE'} = $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} =
"sigexit";
}
#
# カウンターファイルからカウンター値を読み出す
#
if (open(CNT, "$file_count")) {
($wh_cnt, $today_cnt, $yesterday_cnt) = split(/,/, );
close(CNT);
} else { $wh_cnt = -1; }
$today_cnt ||= 0;
$yesterday_cnt ||= 0;
if (! $cnt_mode and ($mode ne "sound")) {
#
# 環境変数TZを日本時間に設定する
#
$ENV{'TZ'} = "JST-9";
#
# 日付を得る
#
($sec, $min, $hour, $mday, $mon, $year) = localtime($tm);
$date_now = sprintf("%04d/%02d/%02d", 1900 + $year, ++$mon, $mday);
$time_now = sprintf("%02d:%02d:%02d", $hour, $min, $sec);
($tmp1, $tmp2, $tmp3, $last_mday, $last_mon) =
localtime($tm - ($left_day*86400)), $last_mon++ if ($left_day);
#
# 日付ファイルから最終アクセス日付を読み出す
#
if (open(DATE, "$file_date")) {
$date_log = ;
close(DATE);
} else { $date_log = ""; }
#
# カウンター値の整合チェック
#
if (($wh_cnt <= 0) and $date_log) {
#
# ログファイルからカウンター値を拾い出す
#
if (open(LOG, "$file_access")) {
while ($i = ) {
($wh_cnt = $i) =~ tr/0-9//cd if ($i =~ /^COUNT/);
}
close(LOG);
#
# カウンタをカウンタファイルに書き戻す
#
if (open(CNT, "> $file_count")) {
print(CNT $wh_cnt, ',', $today_cnt, ',', $yesterday_cnt);
close(CNT);
}
} else { $wh_cnt = -1; }
}
#
# 日付が異なる、つまり、今日初めてのアクセスであれば
#
if ($date_log and ($date_log ne $date_now)) {
#
# メールアドレスが設定してある時は、アクセスログをメールで送信する
#
if ($mail_to ne "") {
$msg = "To: $mail_to\nFrom: $count_name\n";
$msg .= "Subject: ACCESS $date_log $today_cnt\n\n";
if ($account_detail and open(IN, "$file_access")) {
$msg = join("", $msg,);
close(IN);
} elsif (! $account_detail and open(IN, "$file_access")) {
$msg .= "TodayAccess = $today_cnt\n\n";
while ($i = ) {
$msg = join("", $msg,$i)
if (($i =~ /HIT!$/) .. ($i eq "\n"));
}
close(IN);
} else { $msg .= "logfile not found\n"; }
if (-x $sendmail) {
open(MAIL, "| $sendmail $mail_to");
print(MAIL "$msg");
close(MAIL);
}
#
# メールアドレスを設定していない時は、過去ログファイルを作成
#
} else {
($tmp1, $log_mon, $log_mday) = split(/\//, $date_log);
$log_mon = int($log_mon);
$log_mday = int($log_mday);
$file_acclog = "./$count_name/acl$log_mon-$log_mday.log";
if ($left_day and opendir(DIR, "$count_name")) {
@logfile_name = grep(/^acl.+\.log$/, readdir(DIR));
closedir(DIR);
foreach $i (@logfile_name) {
($log_mon, $log_mday) = ($i =~ /(\d+)-(\d+)/g);
if ($last_mon > $log_mon) {
push(@del_name, join('/', ".",$count_name,$i))
unless (($mon < $last_mon) and ($mon >= $log_mon));
} elsif (($last_mon == $log_mon) and
($last_mday > $log_mday)) {
push(@del_name, join('/', ".",$count_name,$i));
}
}
foreach $i (@del_name) {
if ($i =~ /^([-\/\w.]+)$/) {
$i = $1;
} else { die "Bad path in $i"; }
}
unlink(@del_name);
}
if ($file_acclog =~ /^([-\/\w.]+)$/) {
$file_acclog = $1;
} else { die "Bad path in $file_acclog"; }
if (open(IN, "$file_access") and open(YLOG, "> $file_acclog")) {
print(YLOG );
close(YLOG);
close(IN);
chmod(oct($pm), "$file_acclog");
} else { die "Logfile can't be made"; }
}
#
# アクセスログを初期化する
#
close(LOG) if (open(LOG, "> $file_access"));
#
# 今日の日付を日付ログファイルに書き出す
#
print(DATE "$date_now"), close(DATE) if (open(DATE, "> $file_date"));
#
# 昨日のカウンター値を更新する
#
$yesterday_cnt = $today_cnt;
$today_cnt = 0;
} elsif (! $date_log) {
print(DATE "$date_now"), close(DATE) if (open(DATE, "> $file_date"));
}
#
# すでに同アドレスからのアクセスがあればカウントアップしない
#
$count_up = 1;
if ($do_address_check and open(LOG, "$file_access")) {
while ($i = ) {
$count_up = ($i ne "ADDR = [ $ENV{'REMOTE_ADDR'} ]\n") or last;
}
close(LOG);
}
#
# カウントアップ処理
#
if (($wh_cnt >= 0) and $count_up) {
#
# カウンタをひとつインクリメントする
#
$wh_cnt++;
$today_cnt++;
#
#カウンター値をイベント値と比較する
#
&compare_event;
#
# %7E や \~ などを処理する
#
$referer = ($ENV{'HTTP_REFERER'} or "none") if ($mode eq "text");
$ref_url =~ tr/\\//d;
$ref_url =~ s|http%3A//|http://|;
#
# アクセスログを記録する
#
$addr = $ENV{'REMOTE_ADDR'};
if ($get_hostname) {
$host = ($ENV{'REMOTE_HOST'} eq $addr) ? "" : $ENV{'REMOTE_HOST'};
$host ||= gethostbyaddr(pack('C4',split(/\./, $addr)),2) || $addr;
} else { $host = $addr; }
if (open(LOG, ">> $file_access")) {
$count_msg = "COUNT = [ $wh_cnt ]";
$count_msg .= $eventkey ? "HIT!\n" : "\n";
print(LOG "$count_msg");
print(LOG "TIME = [ $time_now ]\n");
print(LOG "ADDR = [ $addr ]\n");
print(LOG "HOST = [ $host ]\n") if ($host ne $addr);
print(LOG "AGENT = [ $ENV{'HTTP_USER_AGENT'} ]\n");
print(LOG "FROM = [ $ref_url ]\n")
if ($ref_url and (! $my_url or ($ref_url !~ /$my_url/o)));
print(LOG "REFER = [ $referer ]\n") if ($mode eq "text");
print(LOG "\n");
close(LOG);
}
if ($eventkey and open(ELOG, ">> $file_evtlog")) {
print(ELOG "COUNT = [ $wh_cnt ]\n");
print(ELOG "DAY = [ $date_now ]\n");
print(ELOG "TIME = [ $time_now ]\n");
print(ELOG "ADDR = [ $addr ]\n");
print(ELOG "HOST = [ $host ]\n") if ($host ne $addr);
print(ELOG "AGENT = [ $ENV{'HTTP_USER_AGENT'} ]\n");
print(ELOG "FROM = [ $ref_url ]\n")
if ($ref_url and (! $my_url or ($ref_url !~ /$my_url/o)));
print(ELOG "REFER = [ $referer ]\n") if ($mode eq "text");
print(ELOG "\n");
close(ELOG);
}
#
# カウンタをカウンタファイルに書き戻す
#
print(CNT $wh_cnt, ',', $today_cnt, ',', $yesterday_cnt), close(CNT)
if (open(CNT, "> $file_count"));
}
}
#
# 次のイベント値を求める
#
if (($cnt_mode eq "next") or ($mode eq "flash")) {
if (open(EVT, "$file_event")) {
while($i = ) {
chomp($i);
$next_event = ($wh_cnt < $i) ? $i : 0 and last;
}
close(EVT);
} else { $next_event = 0; }
unless (($next_event > 0) and ($next_event < 1000)) {
$cnt_length = length($wh_cnt);
if ($cnt_length < 4) {
$next_event = 1000;
} else {
$regular_event1 = substr($wh_cnt, 0, 1);
$regular_event2 = $regular_event1 x $cnt_length;
$regular_event1++;
$regular_event1 *= 10 ** ($cnt_length - 1);
$next_event ||= $regular_event1;
$next_event = $regular_event1
if (($wh_cnt < $regular_event1) and ($regular_event1 < $next_event));
$next_event = $regular_event2
if (($wh_cnt < $regular_event2) and ($regular_event2 < $next_event));
}
}
}
#
# イベント音楽の選択
#
if ($mode eq "sound") {
$count_up = 1;
if ($do_address_check and open(LOG, "$file_access")) {
while ($i = ) {
($cnt_tmp = $i) =~ tr/0-9//cd if ($i =~ /^COUNT/);
$count_up = 0, last
if (($i eq "ADDR = [ $ENV{'REMOTE_ADDR'} ]\n")
and ($cnt_tmp != $wh_cnt));
}
close(LOG);
}
&compare_event;
$sound_locate = ($eventkey and $count_up) ? $event_sound : $normal_sound;
}
#
# CGIスクリプトの結果としてカウンターを書き出す
#
$wh_cnt = 0 if ($wh_cnt == -1);
{
$cnt_mode eq "today" and $cnt_tmp = $today_cnt, last;
$cnt_mode eq "yesterday" and $cnt_tmp = $yesterday_cnt, last;
$cnt_mode eq "next" and $cnt_tmp = $next_event, last;
$cnt_tmp = $wh_cnt;
}
$cntstr = (($figure > 0) and !$cnt_mode) ?
sprintf(sprintf("%%0%dld", $figure), $cnt_tmp) : sprintf("%ld", $cnt_tmp);
if ($mode eq "text") {
if ($eventkey and $ssi_msg) {
$ssi_msg =~ s/_cnt_/$cntstr/g;
$cntstr = $ssi_msg;
}
print "Content-type: text/html\n\n$cntstr\n";
} elsif ($mode eq "gif") {
print "Content-type: image/gif\n\n";
foreach $i (split(//, $cntstr)) {
push(@files,
($cnt_mode and $img_dir) ? "./$img_dir/$i.gif" : "./big/$i.gif");
}
if ($eventkey and ! $cnt_mode) {
unshift(@files, "$topgif") if ($topgif);
push(@files, "$lastgif") if ($lastgif);
}
require "./gifcat.pl";
binmode(STDOUT);
print &gifcat::gifcat(@files);
} elsif ($mode eq "hide") {
print "Content-type: image/gif\n\n";
$size = -s $giffile;
open(IMG, "./$giffile");
binmode(IMG);
binmode(STDOUT);
read(IMG, $buf, $size);
print $buf;
close(IMG);
} elsif ($mode eq "flash") {
print "Content-type: application/x-www-form-urlencoded\n\n";
print "count=$wh_cnt&today_count=$today_cnt&";
print "last_count=$yesterday_cnt&eventkey=$eventkey&nextevent=$next_event";
} elsif ($mode eq "sound") {
print "Location: $sound_locate\n\n";
}
#
# ロック権を開放する
#
close(LOCK) if ($do_file_lock == 1);
rmdir($file_lock_m) if ($do_file_lock == 2);
#
# イベント値比較ルーチン
#
sub compare_event {
$eventkey = 0;
if ($wh_cnt > 999) {
$eventkey = (substr($wh_cnt, 1) == 0);
unless ($eventkey) {
$eventkey = 1;
@cnts = split(//, $wh_cnt);
foreach $i (@cnts) { $eventkey = ($cnts[0] == $i) or last; }
}
}
if (! $eventkey and open(EVT, "$file_event")) {
while($i = ) {
chomp($i);
$eventkey = ($wh_cnt == $i) and last;
}
close(EVT);
}
}