#!/usr/local/bin/perl
;# 簡易BBS version 10.32
;#
;# This is Freeware.
;# (c)1996-2008 by CGI-RESCUE
;# Scripts Found at: http://www.rescue.ne.jp/
# [基本仕様]
#
# ※ 旧バージョンとのデータ互換はありません.
# ※ METHOD=POST専用です.
# ※ S-JIS設置専用です.
# ※ 西暦2000年対応.
# [基本構成] ( )内は設定する必要があるパーミッション値
#
# /public_html/(ホームページディレクトリ)
# |
# |-- /cgi-bin/(任意のディレクトリ)
# |
# |-- jcode.pl
# |-- minibbs.cgi (755)
# |
# |-- /data/ (777)
# |
# |-- data.cgi
# [履歴]
#
# v1.0 02/MAY/96 初版
# v8.90 11/AUG/98 v8.8にロック機能を付加
# v9.00 12/AUG/98 タグ処理を制限してセキュリティアップ
# v9.01 24/AUG/98 未入力処理(クッキー消去)時にリストされな不具合を修正
# v9.02 03/SEP/98 改行の扱いを3種類に変更
# v9.03 18/SEP/98 Eメール自動リンクの廃止
# v9.04 19/OCT/98 タブコードの取り扱い修正
# v9.05 26/OCT/98 記録データ制限の解除処理
# v9.06 22/NOV/98 エラー処理を改善
# v10.00 24/NOV/98 検索機能の追加および構造変更
# v10.01 02/DEC/98 クッキー機能の修正ほか
# v10.02 08/DEC/98 D系トラブルの修正
# v10.10 08/DEC/98 2重(連続)投稿防止処理
# v10.11 08/DEC/98 削除キー削除が出来なくなったバグの修正
# v10.12 15/DEC/98 パスワード強化
# v10.13 17/DEC/98 全角文字マッチの不具合の訂正
# v10.14 21/DEC/98 ファイルを閉じていない個所の修正,メールアドレス形式チェック方法の変更
# v10.15 14/FEB/99 記事最後案内の修正
# v10.20 19/FEB/99 新着記事のマーキング
# v10.21 20/FEB/99 通算日数計算のバグ修正
# v10.30 08/JUL/99 ロック処理のバグ修正,ロック処理方法の変更,暗号処理のベリファイ機能付加
# v10.31 04/SEP/99 暗号処理部分の修正
# v10.32 13/DEC/08 クロスサイト・スクリプティング脆弱性を修正(※1) , 削除キー保存機能の無効化 , メールアドレスのエンティティー化
# [データ形式]
#
# ※ 各記事は1件1行とし、各項目はタブ区切りとする.
# ※ 各項目の並びは次の通り.
#
# 識別番号 暗号パスワード 投稿者 Eメール ホスト名 投稿時刻 題名 記録タイプ リンクの有無 内容 2重投稿チェック用文字列
#----------------#
# 初期設定 #
#----------------#
#--- 必ずあなたの環境に合わせて書き替える項目 --------------------------------------------#
#◆掲示板の名前
# ''内に記述しますが、'を入れたい場合は '' を "" に替えてください.
# ただしその場合、文字によって化けが生じることがあります.
# 詳しくは当サイトのFAQを参照してください.
$title = '簡易BBS';
#◆このスクリプトをURLで設定
$reload = 'http://設置したURL/minibbs.cgi';
#◆画面の「終了」リンク先をURLで設定
$modoru = 'http://ホームページなどのURL/';
#--- 必要に応じて設定する項目 ------------------------------------------------------------#
#◆画面の色や背景の設定 (HTML書式)
$body = '
';
#◆見出の色
$midashi_color = '#ffeedd';
#◆記事題名の色
$subject_color = '#ff88aa';
#◆記事ヘッダ(名前や投稿日など)の色
$head_color = '#ffeedd';
#◆記事内容の色
$body_color = '#ffffff';
#◆記事(通常時)内容の文字サイズ(CSS設定)
$span_size = 'small';
#◆記事(図表モード時)内容の文字サイズ(CSS設定)
$pre_size = 'small';
#◆ホスト名の表示の可否 1:する 0:しない
$view_host = 1;
#◆2重(連続)投稿チェックの対象にする行数
$njmax = 10;
#◆画面内に記述する文字列等 (HTML書式)
# ''内に記述しますが、'を入れたい場合は '' を "" に替えてください.
# ただしその場合、文字によって化けが生じることがあります.
# 詳しくは当サイトのFAQを参照してください.
# 必要ない場合は '' 内に何も書きません.
#◆(見出)タイトルの下位置に表示する文字列
$msg_top = '
';
#◆(見出)投稿フォームの下位置に表示する文字列
$msg_mid = '
';
#◆(見出)最下部に表示する文字列
$msg_btm = '
※ すべてのボタンは1回だけ押してしばらくお待ちください.
※ [削除]ボックスをチェックして、投稿時に設定した削除キーを入力してボタンを押せば削除できます.
※ 検索文字列はスペースで区切ることで複数指定できます.
※ 複数指定時にはそれぞれの文字列に対して、AND(かつ) OR(または)を適用します.
※ 削除キー欄にマスターキー(管理者のみ)を入力すると任意の記事の削除が可能です.
';
#◆$reloadで設定した設置URL以外のフォームからの投稿を禁止する処置 する:1 しない:0
# 悪戯の防止用ですが、利用サーバやブラウザによっては正規投稿もできなくなる場合もあります.
$ref_axs = 0;
#◆1画面に表示する件数
$def = 10;
#◆新着マーク(New画像)を表示する日数
$update = 7;
#◆書き込み件数の最大登録数の設定です。この件数を超えると、古いものから削除されていきます.
# ページ処理機能が付きましたので、この件数を大きくしても一度に表示される記事数は限定されます.
# サーバの負担を考慮し、できるだけ500以下程度に設定しましょう。
$max = '300';
#◆日本語コード変換ライブラリ
# minibbs.cgiと同じ場所に設置する場合はこのままでよい.
require './jcode.pl';
#◆内容が書き込まれる記録ファイルの名前(パスの設定ではない!)
$file = 'data.cgi';
#◆データディレクトリのパスの設定(処理の都合上 / で閉じない)
$tmp_dir = './data';
#◆海外サーバ等で時差が生じる場合は修正
# +9時間する場合 = localtime(time + 9*60*60);
# −9時間する場合 = localtime(time - 9*60*60);
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
#◆クッキーの消化設定
# 最終書き込みから 30日後 30*24*60*60
# 1日後 24*60*60
# 10時間後 10*60*60
($secg,$ming,$hourg,$mdayg,$mong,$yearg,$wdayg,$ydayg,$isdstg) = gmtime(time + 30*24*60*60);
#------------------
$cmd = $ENV{'QUERY_STRING'};
if ($cmd eq 'copyright') { ©right; exit; }
elsif ($cmd eq 'new') { &new; exit; }
@wday_array = ('日','月','火','水','木','金','土');
$date_now = sprintf("%04d年%01d月%01d日(%s)%02d時%02d分",$year +1900,$mon +1,$mday,$wday_array[$wday],$hour,$min);
$date_num = sprintf("%04d%02d%02d%02d%02d%02d",$year +1900,$mon +1,$mday,$hour,$min,$sec);
$days[4] = $days[6] = $days[9] = $days[11] = 30;
$days[1] = $days[3] = $days[5] = $days[7] = $days[8] = $days[10] = $days[12] = 31;
$days_now = &Days($year +1900,$mon +1,$mday);
$ps = $$;
if ($ps eq '') { $ps = $date_num; }
$tmp_file = "$ps\.tmp";
read(STDIN,$buffer,$ENV{'CONTENT_LENGTH'});
@pairs = split(/&/,$buffer);
foreach $pair (@pairs) {
($name,$value) = split(/=/,$pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
&jcode'convert(*value,'sjis');
$value =~ s/\&/&/g;
$value =~ s/\"/"/g;
$value =~ s/</g;
$value =~ s/>/>/g;
$value =~ s/\t//g;
$value =~ s/\r\n/\r/g;
$value =~ s/\n/\r/g;
if ($name eq 'target') { push(@RM,$value); }
else { $FORM{$name} = $value; }
}
$cookies = $ENV{'HTTP_COOKIE'};
@pairs = split(/;/,$cookies);
foreach $pair (@pairs) {
($name,$value) = split(/=/,$pair,2);
$name =~ s/ //g;
$value =~ s/\"/"/g; # ※1
$value =~ s/</g;
$value =~ s/>/>/g;
$DUMMY{$name} = $value;
}
@pairs = split(/,/,$DUMMY{$reload});
foreach $pair (@pairs) {
($name,$value) = split(/:/,$pair,2);
$COOKIE{$name} = $value;
}
if (-z "$tmp_dir\/$file") { $first = 1; }
if ($FORM{'action'} eq 'setpwd') { &setpwd; &search; &html; exit; }
elsif ($first) { &password; exit; }
elsif ($FORM{'admin'} eq 'change') { &password; exit; }
elsif (@RM && $FORM{'action'} eq 'remove') { &remove; }
elsif ($FORM{'action'} eq 'regist') { ®ist; }
&search;
&html;
exit;
#------------------
sub search {
if ($FORM{'search'} ne '') {
$i = $FORM{'search'};
$i =~ s/ / /g;
$keys = $i;
&jcode'convert(*i,'euc');
$i =~ s/(\W)/\\$1/g;
$target = $i;
@keys = split(/\\\s+/,$target);
}
if (!open(READ,"$tmp_dir\/$file")) { &error('エラー','データが読み出せません.'); }
$master = ;
unless ($master =~ /^MiniBBSv10:(.+)$/) { &error('エラー','データの形式が違います.'); }
if ($FORM{'page'} eq '') { $page = 1; } else { $page = $FORM{'page'}; }
$page_control = $hit = $allhits = $all = 0;
if ($FORM{'action'} ne 'remove') { $start = (times)[0]; }
while () {
$string = $string_s = $_;
$page_control++;
if ($page > $page_control) { next; }
if ($FORM{'search'} ne '') {
if ($FORM{'page'} eq '') { $all++; }
&jcode'convert(*string,'euc');
if ($FORM{'mode'} eq 'or') {
$match = 1;
foreach $term (@keys) {
if ($string =~ /^([\x00-\x7F]|[\x8E\xA1-\xFE][\xA1-\xFE]|\x8F[\xA1-\xFE]{2})*$term/i) { $match = 0; }
}
}
else {
$match = 0;
foreach $term (@keys) {
if (!($string =~ /^([\x00-\x7F]|[\x8E\xA1-\xFE][\xA1-\xFE]|\x8F[\xA1-\xFE]{2})*$term/i)) { $match = 1; }
}
}
if ($match) { next; }
if ($FORM{'page'} ne '') {
$allhits = $FORM{'allhits'};
if ($hit == $def) { $next_num = $page_control; last; }
else { push(@PICKUP,$string_s); $hit++; }
}
else {
if ($end != 1 && $hit == $def) { $end = 1; $next_num = $page_control; $allhits++; }
elsif ($hit >= $def) { $allhits++; }
else { push(@PICKUP,$string_s); $hit++; $allhits++; }
}
}
else {
$hit++;
if ($hit > $def) { $next_num = $page_control; last; }
else { push(@PICKUP,$string_s); }
}
}
if ($FORM{'action'} ne 'remove') { $end = (times)[0]; }
$cpu = sprintf("%.3f",$end - $start);
close(READ);
}
sub html {
if (!@PICKUP && $page != 0) { $FORM{'page'} = $page - 1; &search; }
$nj = '';
@char = ('a'..'z','A'..'Z','0'..'9');
srand(time|$$);
foreach (0..9) {
{
local(@temp);
push(@temp,splice(@char,rand(@char),1)) while @char;
@char = @temp;
}
$nj = $char[($_)] . $nj;
}
print "Content-type: text/html\n\n";
print "$title\n";
print "\n";
print "\n";
print "\n";
print "$body\n";
print "$title
\n";
print "$msg_top\n";
print "
";
print "$msg_mid
\n";
print "[更新] [終了]\n";
print "
\n";
if ($FORM{'mode'} eq 'or') { $OR = 'checked'; $MODE = '(または)'; }
elsif ($FORM{'mode'} eq 'and' || $FORM{'mode'} eq '') { $AND = 'checked'; $MODE = '(かつ)'; }
unless ($keys =~ / /) { $MODE = ''; }
if ($next_num ne '') { $page_end = $page + $hit - 2; }
else { $page_end = $page + $hit - 1; }
if ($page_end <= 0) {
if ($FORM{'search'} ne '') { print "指定の文字列を含む記事はありません. (検索時間 $cpu CPU秒) "; }
else { print "記事はありません. "; }
}
elsif ($page <= $page_end && $FORM{'search'} eq '' && !$next_num) {
print "新着順 $page → 最後 ";
print "最大記録保持数 $max ";
}
else {
if ($FORM{'search'} ne '') {
if ($all == 0) { $all = $FORM{'all'}; }
print "《検索モード》 文字列 "$keys" $MODE ";
if ($FORM{'action'} ne 'remove') {
print "抽出数 $allhits ";
print "抽出位置 $page/総数$all〜 ";
print "検索時間 $cpu CPU秒 ";
}
else { print ""; }
}
else {
print "新着順 $page → $page_end ";
print "最大記録保持数 $max ";
print "";
}
}
print "(は$update日以内の記事)\n";
if ($page_end > 0) {
print "\n";
# このスクリプトの著作権表示(かならず表示してください)
print "
\n";
print "\n";
}
sub regist {
if ($ref_axs) {
$ref = $ENV{'HTTP_REFERER'};
$ref =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
if (!($ref =~ /$reload/)) { &error('利用不可',"次のページ以外からの投稿は受け付けられません.
→ $reload"); }
}
if (!open(READ,"$tmp_dir\/$file")) { &error('エラー','データが読み出せません.'); }
$master = ;
unless ($master =~ /^MiniBBSv10:(.+)$/) { &error('エラー','データの形式が違います.'); }
if ($FORM{'name'} eq '') { &error('入力ミス','投稿者を記入してください.'); }
if ($FORM{'name'} =~ /\;/) { &error('入力ミス','投稿者にセミコロンは使えません.'); }
if ($FORM{'name'} =~ /\,/) { &error('入力ミス','投稿者にカンマは使えません.'); }
if ($FORM{'email'} ne '' && !($FORM{'email'} =~ /\b[-\w.]+@[-\w.]+\.[-\w]+\b/)) { &error('入力ミス','Eメールの形式が間違っています.'); }
if ($FORM{'email'} =~ /\;/) { &error('入力ミス','Eメールにセミコロンは使えません.'); }
if ($FORM{'email'} =~ /\,/) { &error('入力ミス','Eメールにカンマは使えません.'); }
if ($FORM{'subject'} eq '') { &error('入力ミス','題名を記入してください.'); }
if ($FORM{'value'} eq '') { &error('入力ミス','内容を記入してください.'); }
if ($FORM{'pwd'} eq '' || length($FORM{'pwd'}) < 6) { &error('入力ミス','削除キー欄に6文字以上の半角文字でパスワードを指定してください.
これは記事を削除する時に利用するものです.'); }
&encode2($FORM{'pwd'});
$j = 0;
while () {
$j++;
if ($j > $njmax) { last; }
($i,$i,$i,$i,$i,$i,$i,$i,$i,$i,$nj) = split(/\t/);
if ($nj =~ /$FORM{'nj'}/) { &error('二重(連続)投稿の禁止',"OKボタンを2回以上押した可能\性がありますので、投稿されているかどうかご確認ください.
再投稿は更新してからご利用ください."); }
}
close(READ);
$y0="Sunday"; $y1="Monday"; $y2="Tuesday"; $y3="Wednesday"; $y4="Thursday"; $y5="Friday"; $y6="Saturday";
@youbi = ($y0,$y1,$y2,$y3,$y4,$y5,$y6);
$m0="Jan"; $m1="Feb"; $m2="Mar"; $m3="Apr"; $m4="May"; $m5="Jun";
$m6="Jul"; $m7="Aug"; $m8="Sep"; $m9="Oct"; $m10="Nov"; $m11="Dec";
@monthg = ($m0,$m1,$m2,$m3,$m4,$m5,$m6,$m7,$m8,$m9,$m10,$m11);
$date_gmt = sprintf("%s\, %02d\-%s\-%04d %02d:%02d:%02d GMT",$youbi[$wdayg],$mdayg,$monthg[$mong],$yearg +1900,$hourg,$ming,$secg);
if ($FORM{'cookie'} eq 'on') {
$cook="name\:$FORM{'name'}\,email\:$FORM{'email'}";
print "Set-Cookie: $reload=$cook; expires=$date_gmt\n";
$COOKIE{'name'} = $FORM{'name'};
$COOKIE{'email'} = $FORM{'email'};
}
else {
print "Set-Cookie: $reload=\n";
$COOKIE{'name'} = $COOKIE{'email'} = '';
}
$host = $ENV{'REMOTE_HOST'};
$addr = $ENV{'REMOTE_ADDR'};
if ($host eq '') { $host = $addr; }
if ($host eq $addr) { $host = gethostbyaddr(pack('C4',split(/\./,$host)),2) || $addr; }
&lock1;
if (!open(READ,"$tmp_dir\/$file")) { &error('エラー','データが読み出せません.'); }
$master = ;
if (!open(WRITE,"> $tmp_dir\/$tmp_file")) { &error('エラー','テンポラリーファイルが作成できません.'); }
print WRITE $master;
print WRITE "$date_num\t$pwd\t$FORM{'name'}\t$FORM{'email'}\t$host\t$date_now\t$FORM{'subject'}\t$FORM{'how'}\t$FORM{'link'}\t$FORM{'value'}\t$FORM{'nj'}\n";
$max_control = 0;
while () {
$max_control++;
print WRITE;
if ($max_control == $max - 1) { last; }
}
close(WRITE);
close(READ);
&lock2;
}
sub error {
print "Content-type: text/html\n\n";
print "$title\n";
print "\n";
print "$body\n";
print "$_[0]
\n";
print "$_[1]
\n";
print "ブラウザの[戻る]ボタンを押して前の画面に移動してください.\n";
print "