#!/usr/local/bin/perl # ############################################################# ### ### CGI掲示板(簡易版) SDnote-Jack Ver.1.0 ### ### (C) 2000 Sentora Dori ### http://www.lowpower.iis.u-tokyo.ac.jp/~hat ### ############################################################# # #################### 変数設定 #################### #----- 基本設定 ----- # 掲示板データファイルをおくディレクトリのパス $basedir = "./"; # 掲示板データファイル名 (要変更) $bbsfile = 'SDnote.txt'; # 管理人のURL (要変更) $adminurl = 'http://www.sentora.co.jp/'; # 管理人のE-mailアドレス (要変更) $adminmail = 'sentora@dori.co.jp'; # 管理人の名前 (要変更) $mastername = '道里千虎'; # 戻る先のURL (要変更) $ homeurl = '../../index.html'; # 掲示板のタイトル (要変更) $title = '道里千虎の掲示板'; # コメント $comment = ''; # 背景の色 $bgcolor = '#BFFFFF'; # 背景に埋め込む画像 $bgimage = ''; # 1ページに表示される記事の数 $pagesize = 20; # ファイルに記録しておく記事の数 #(記事がこれ以上になると古いものから削除される。0に設定すると削除なし。) $maxarticle = 100; # post: postメソッド get: getメソッド $method = 'post'; # 漢字コード(sjis or jis or euc) $kanjicode = 'sjis'; #----- 応用設定 ----- # 1:名前の入力必須, 0:必要なし $needname = 1; # 1:題名の入力必須, 0:必要なし $needsubject =1; # 1:E-mailアドレスの入力必須, 0:必要なし $needmail = 0; # 1:削除用のパスワードの入力必須, 0:必要なし $needpass = 0; # パスワードの最小の長さ $minpass = 4; # パスワードの最大の長さ $maxpass =12; # メッセージの最大の長さ $maxmessage = 3000; # 1:名前が入力されていない匿名の投稿者に特定の名前をつける, 0:つけない $attach_anonymous = 1; # 匿名の投稿者に対してつける名前 $anonymous = '匿名'; # 1:タグ入力を許す, 0:タグ入力をすべて禁止する $permittag = 1; # 注意書きの文字の色 $notecolor = '#FF0000'; # テーブルの枠線の太さ $tableborder = 0; # テーブルの背景色 $tablecolor = ''; # 記事欄に投稿者のE-mailアドレスを表示する $showmail = 1; # 記事欄に投稿者のURLを表示する $showurl = 1; # 投稿者のホスト名を表示する $showhostname = 0; # 投稿者のIPを表示する $showhostaddress = 0; # 管理者の名前を最下欄に表示する $showmastername =1; # 1:クッキーを使用する, 0:使用しない $usecookie = 1; # クッキーの名前 $cookiename = 'bbs'; # クッキーの有効期限 $cookiedays = 90; # 1: ロックを使用する 0:使用しない $uselock =1; # 漢字ライブラリのファイル名 $jcodelib ='./jcode.pl'; #----- ファイル情報 ----- #(ファイル名、サインを変更するときだけここも変更してください。) # このファイル名 $thisurl = 'SDnoteJ10.cgi'; # 掲示板データファイルであることを示すサイン $signature = 'bbs'; #################### 変数設定(ここまで) #################### $bbsfile = $basedir.$bbsfile; #----- 漢字コード設定 ----- require "$jcodelib"; if ($kanjicode eq 'sjis') { $contenttype = ""; # $contenttype = ""; } elsif ($kanjicode eq 'euc') { $contenttype = ""; # $contenttype = ""; } elsif ($kanjicode eq 'jis') { $contenttype = ""; } ##### format ##### #----- ユーザからの情報取得 ----- &init_form($kanjicode); $bakaddr = $ENV{'REMOTE_ADDR'}; $bakhost = $ENV{'REMOTE_HOST'}; ($bakaddr eq $bakhost) && ($hostname = gethostbyaddr(pack("C4", split(/\./, $bakaddr)), 2)); $hostaddress = $bakaddr; #----- cookie取得 ----- &init_cookie($cookiename); $cookie_name = $cookie{'bbs_name'}; $cookie_mail = $cookie{'bbs_mail'}; $cookie_pass = $cookie{'bbs_pass'}; $cookie_url = $cookie{'bbs_url'}; #----- 変数入力 ----- $bbs_name = $form{'bbs_name'}; $bbs_subject = $form{'bbs_subject'}; $bbs_mail = $form{'bbs_mail'}; $bbs_url = $form{'bbs_url'}; $bbs_pass = $form{'bbs_pass'}; $bbs_message = $form{'bbs_message'}; $bbs_command = $form{'bbs_command'}; $bbs_id = $form{'bbs_id'}; $bbs_hostname = $form{'bbs_hostname'}; $bbs_hostaddress = $form{'bbs_hostaddress'}; $bbs_masterpass = $form{'bbs_masterpass'}; $bbs_oldmasterpass = $form{'bbs_oldmasterpass'}; $bbs_newmasterpass = $form{'bbs_newmasterpass'}; $bbs_newmasterpass2 = $form{'bbs_newmasterpass2'}; $bbs_pagenumber = $form{'bbs_pagenumber'}; #----- タグ禁止 ----- $bbs_name = &norm_input($bbs_name); $bbs_subject = &norm_input($bbs_subject); $bbs_mail = &norm_input($bbs_mail); $bbs_url = &norm_input($bbs_url); $bbs_message = &norm_input($bbs_message); $bbs_id = &norm_input($bbs_id); ##### branch(入力されたコマンドによる分岐) ##### if ($bbs_command eq 'read') { &do_read; } elsif ($bbs_command eq 'write') { &do_write; } elsif ($bbs_command eq 'showdelete') { &show_delete; } elsif ($bbs_command eq 'delete') { &do_delete; } elsif ($bbs_command eq 'initmaster') { &do_initmaster; } elsif ($bbs_command eq 'showchangemaster') { &show_changemaster; } elsif ($bbs_command eq 'changemaster') { &do_changemaster; } else { &do_read; } exit(0); ###################### ##### subroutine ##### ###################### ###### do(コマンド実行) ##### #----- 記事を読む(単に表示する) ----- sub do_read { &show_article; } #----- 記事を書く(ファイルに書いて表示する) ----- sub do_write { ### a 入力情報の検証 ###; $error= ''; if ($needname == 1 && $bbs_name eq '') { $error .= '名前が入力されていません。'; } if ($needsubject == 1 && $bbs_subject eq '') { $error .= '題名が入力されていません。'; } if ($needmail == 1 && $bbs_mail eq '') { $error .= 'E-mailアドレスが入力されていません。'; } if ($needpass == 1 && $bbs_pass eq '') { $error .= 'パスワードが入力されていません。'; } if ($bbs_message eq '') { $error .= 'メッセージが入力されていません。'; } if ($error) { &print_error("$error"); } if ($bbs_pass ne '') { if (length($bbs_pass) < $minpass || length($bbs_pass) > $maxpass) { &print_error("$minpass 文字以上 $maxpass 文字以下のパスワードを入力してください。"); } } elsif ($needpass == 0) { $nopass = 1; } if ($bbs_name eq '' && $attach_anonymous == 1) { $bbs_name = $anonymous; } if ($maxlength >0 && length($bbs_message) > $maxmessage) { &print_error('メッセージが長すぎます。'); } ### b ファイルへ書き込み ### &open_file(BBS, "+<$bbsfile", "No bbs file"); &lock_file(BBS); @bbs = ; # 掲示板ファイルかどうかチェック ($sign, $masterpass) = splice(@bbs, 0, 2); chop($sign); chop($masterpass); if ($sign ne $signature) { &unlock_file(BBS); &close_file(BBS); &print_error("Not BBS file"); } # リロードかどうかチェックし連続書き込みを防止する ($print_id, $print_name, $print_mail, $print_url, $print_date, $print_pass, $print_subject, $print_message, $print_hostname, $print_hostaddress) = split(/,/, $bbs[0]); if ($print_name eq $bbs_name && $print_subject eq $bbs_subject && $print_message eq $bbs_message) { &unlock_file(BBS); &close_file(BBS); &show_article; exit(0); } # 書き込み準備 seek(BBS, 0, 0); print BBS "$sign\n"; print BBS "$masterpass\n"; $datestr = &get_date_string; if ($bbs_url eq 'http://') { $bbs_url = ''; } # 書き込み位置検出 $id = 1; for ($i = 0; $i < @bbs; $i++) { ($bakid) = split(/,/, $bbs[$i]); if ($bakid >= $id) { $id = $bakid + 1; } } # パスワード暗号化 if ($nopass ==1) { $encpass = ''; } else { $encpass = &encode_pass($bbs_pass); } $writeline = "$id,$bbs_name,$bbs_mail,$bbs_url,$datestr,$encpass,$bbs_subject,$bbs_message,$hostname,$hostaddress\n"; unshift(@bbs, $writeline); if ($maxarticle != 0) { splice (@bbs, $maxarticle); } # 書き込み print BBS @bbs; truncate(BBS, tell(BBS)); &unlock_file(BBS); &close_file(BBS); ### c 表示 ### &show_article; } #----- 記事を削除する ----- sub do_delete { ### a 記事削除 ### &open_file(BBS, "+<$bbsfile", "No bbs file"); &lock_file(BBS); @bbs = ; # 掲示板ファイルかどうかチェック ($sign, $masterpass) = splice(@bbs, 0, 2); chop($sign); chop($masterpass); if ($sign ne $signature) { &unlock_file(BBS); &close_file(BBS); &print_error("Not BBS file"); } # ファイルから削除する記事を探す $index = -1; for ($i = 0; $i < @bbs; $i++) { ($findid) = split(/,/, $bbs[$i]); if ($bbs_id == $findid) { $index = $i; last; } } if ($index < 0) { &unlock_file(BBS); &close_file(BBS); &print_error("No message"); } # パスワード認証 ($print_id, $print_name, $print_mail, $print_url, $print_date, $print_pass, $print_subject, $print_message, $print_hostname, $print_hostaddress) = split(/,/, $bbs[$index]); if ($print_pass ne crypt($bbs_pass, $print_pass) && $masterpass ne crypt($bbs_masterpass, $masterpass)) { &unlock_file(BBS); &close_file(BBS); &print_error("パスワードが正しくありません。"); } # 記事の削除 splice(@bbs, $index, 1); unshift(@bbs, ("$sign\n", "$masterpass\n")); seek(BBS, 0, 0); print BBS @bbs; truncate(BBS, tell(BBS)); &unlock_file(BBS); &close_file(BBS); undef @bbs; ### b 削除の表示 ### &show_article; } #----- マスターパスワードを初期化する ----- sub do_initmaster { # 入力されたパスワードのチェック &verify_master; # マスターパスワードの変更 &change_master; # 変更の表示 &show_article; } #----- マスターパスワードを変更する ----- sub do_changemaster { &verify_master; &change_master; &show_article; } # 入力されたパスワードのチェック # sub verify_master { if (length($bbs_newmasterpass) < $minpass || length($bbs_newmasterpass) > $maxpass) { &print_error("$minpass 文字以上 $maxpass 文字以下のパスワードを入力してください。"); } if ($bbs_newmasterpass ne $bbs_newmasterpass2) { &print_error("マスターパスワードと確認用の入力が違います。もう一度やり直してください。"); } } # パスワードの変更 # sub change_master { local($encpass) = &encode_pass($bbs_newmasterpass); local(@sign) = ("$signature\n", "$encpass\n"); &open_file(BBS, "+<$bbsfile", "No bbs file"); &lock_file(BBS); @bbs = ; ### パスワード初期化 ### if ($bbs_command eq 'initmaster') { @file_info = stat($bbsfile); if ($file_info[7] != 0) { &unlock_file(BBS); &close(BBS); &print_error("マスターパスワードはすでに設定されています。"); } seek(BBS, 0, 0); print BBS @sign; } else { ### パスワード変更 ### ($sign, $masterpass) = splice(@bbs, 0, 2); chop($sign); chop($masterpass); if ($sign ne $signature) { &unlock_file(BBS); &close_file(BBS); &print_error("Not BBS file"); } if ($masterpass ne crypt($bbs_oldmasterpass, $masterpass)) { &unlock_file(BBS); &close_file(BBS); &print_error("パスワードが正しくありません。"); } seek(BBS, 0, 0); print BBS @sign; print BBS @bbs; } } ##### show(HTML表示) ##### #----- 書き込み用フォームと記事を表示する ----- sub show_article { # open &open_file(BBS, "$bbsfile", "No BBS file"); @bbs = ; &close_file(BBS); @file_info = stat($bbsfile); if ($file_info[7] == 0) { &show_initmaster; return; } # check signiture ($sign, $masterpass) = splice(@bbs, 0, 2); chop($sign); chop($masterpass); $number_article = $#bbs + 1; if ($sign ne $signature) { &print_error("No BBS file"); } # クッキーデータの出力 print "Content-type: text/html\n"; if ($bbs_command eq 'write' && $usecookie) { undef %cookie; $cookie{'bbs_name'} = $bbs_name; $cookie{'bbs_mail'} = $bbs_mail; $cookie{'bbs_pass'} = $bbs_pass; $cookie{'bbs_url'} = $bbs_url; &print_cookie($cookiename, $cookiedays); $cookie_name = $bbs_name; $cookie_mail = $bbs_mail; $cookie_pass = $bbs_pass; $cookie_url = $bbs_url; } print "\n"; if ($cookie_url eq '') { $cookie_url = 'http://'; } # ヘッダ表示 print <<"END_SHOW_ARTICLE_HEAD"; $contenttype $title [戻る] $title $comment END_SHOW_ARTICLE_HEAD # 書き込み用フォーム表示 if ($needname) { $print_needname ='(必須)'; } else { $print_needname = ''; } if ($needsubject) { $print_needsubject ='(必須)'; } else { $print_needsubject = ''; } if ($needmail) { $print_needmail ='(必須)'; } else { $print_needmail = ''; } if ($needpass) { $print_needpass ='(必須)'; } else { $print_needpass = ''; } if ($permittag) { $print_note = '半角カナは使えません。タグを使用することができます。(<,>,"の記号のみ使えます。)'; } else { $print_note = '半角カナ・タグは使えません。'; } if ($maxarticle != 0) { $print_note .= "保存されるメッセージは$maxarticle個で、古い順に消えていきます。"; } $maxmessage_zenkaku = int($maxmessage / 2); print <<"END_SHOW_ARTICLE_INPUTFORM"; 名前 $print_needname 題名 $print_needsubject E-mail $print_needmail URL 削除用のパスワード $print_needpass $print_note メッセージの最大の長さは全角で$maxmessage_zenkaku文字です。 削除用パスワードを入れないと削除できません。パスワードは$minpass文字以上$maxpass文字以下で入力してください。 END_SHOW_ARTICLE_INPUTFORM # 記事表示 print "\n"; $article_start = $bbs_pagenumber * $pagesize; $article_end = $article_start + $pagesize; if ($article_end < $number_article) { $bbs_pagenumber++; $shownextpagebutton = 1; } if ($article_end > $number_article) { $article_end = $number_article; } for ($i = $article_start; $i<$article_end; $i++) { ($print_id, $print_name, $print_mail, $print_url, $print_date, $print_pass, $print_subject, $print_message, $print_hostname, $print_hostaddress) = split(/,/, $bbs[$i]); if ($showmail && $print_mail) { $print_nametag = "$print_name"; } else { $print_nametag = "$print_name"; } if ($showurl) { $print_urltag = "$print_url"; } else { $print_urltag = ""; } if ($showhostname) { $print_hostnametag = "$print_hostname"; } else { $print_hostnametag = ""; } if ($showhostaddress) { $print_hostaddresstag = "$print_hostaddress"; } else { $print_hostaddresstag = ""; } print <<"END_SHOW_ARTICLE_ARTICLE"; No.$print_id   $print_nametag   題名: $print_subject   $print_urltag   $print_date $print_hostnametag   $print_hostaddresstag $print_message END_SHOW_ARTICLE_ARTICLE } # フッター表示 if ($showmastername) { $print_mastername = "管理人: $mastername"; } else { $print_mastername = ''; } if ($shownextpagebutton) { print <<"END_SHOW_NEXTPAGEBUTTON"; END_SHOW_NEXTPAGEBUTTON } print <<"END_SHOW_ARTICLE_FOOT"; $print_mastername END_SHOW_ARTICLE_FOOT } #----- 削除用フォームを表示する ----- sub show_delete { # open &open_file(BBS, "$bbsfile", "No bbs file"); @bbs = ; &close_file(BBS); # check signiture ($sign, $masterpass) = splice(@bbs, 0, 2); chop($sign); chop($masterpass); if ($sign ne $signature) { &print_error("Not BBS file"); } # find message $index = -1; for ($i = 0; $i < @bbs; $i++) { ($findid) = split(/,/, $bbs[$i]); if ($bbs_id == $findid) { $index = $i; last; } } if ($index < 0) { &print_error("No message"); } # 削除しようとする記事と削除用フォームの表示 ($print_id, $print_name, $print_mail, $print_url, $print_date, $print_pass, $print_subject, $print_message) = split(/,/, $bbs[$index]); print "Content-type: text/html\n\n"; print <<"END_SHOW_DELETE"; $contenttype $title $title - 削除メッセージの確認 No.$print_id 題名$print_subject 名前$print_name 書き込み時間  $print_date $print_message パスワード マスターパスワード 削除をやめる END_SHOW_DELETE } #----- マスターパスワード初期化用フォームを表示する ----- sub show_initmaster { print "Content-type: text/html\n\n"; print <<"END_SHOW_INITMASTER"; $contenttype $title - initiate master password $title - マスターパスワードの初期設定 マスターパスワードを設定してください。 確認のために2箇所に入力してください。(カット&ペーストは避けてください。) メッセージの削除時に必要となりますので、忘れないようにしてください。 マスターパスワード 確認用の再入力 END_SHOW_INITMASTER } #----- マスターパスワード変更用フォームを表示する ----- sub show_changemaster { print "Content-type: text/html\n\n"; print <<"END_SHOW_CHANGEMASTER"; $contenttype $title - change master password $title - マスターパスワードの変更 マスターパスワードは管理者のみが変更できます。 現在のパスワードを入力し、 新しいパスワードは確認のために2箇所に入力してください。 (カット&ペーストは避けてください。) 現在のマスターパスワード 新しいマスターパスワード 確認用の再入力 パスワード変更をやめる END_SHOW_CHANGEMASTER } ##### サブ処理 ##### #----- パスワード暗号化 ----- sub encode_pass { local($sec, $min, $hour, $day, $mon, $year, $weekday) = localtime(time); local(@token) = ('0'..'9', 'A'..'Z', 'a'..'z'); local($pass) = @_; local($encpass, $salt1, $salt2); $salt1 = $token[(time | $$) % scalar(@token)]; $salt2 = $token[($sec + $min*60 + $hour*3600) % scalar(@token)]; $encpass = crypt($pass, "$salt1$salt2"); return $encpass; } #----- 時刻文字列の取得 ----- sub get_date_string { local(@week) = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); local($sec, $min, $hour, $day, $mon, $year, $weekday) = localtime(time); $year += 1900; $mon++; if ($hour < 10) { $hour = "0$hour"; } if ($min < 10) { $min = "0$min"; } if ($sec < 10) { $sec = "0$sec"; } $weekstr = $week[$weekday] ; return "$year-$mon-$day ($weekstr) $hour:$min"; } #----- クッキー用の有効期限時刻文字列の取得 ----- sub get_expiredate_string { local($days) =@_; local(@month) = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); local(@week) = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); local($sec, $min, $hour, $day, $mon, $year, $weekday) = gmtime(time + $days * 24*60*60); local($expiredate); $year += 1900; if ($hour < 10) { $hour = "0$hour"; } if ($min < 10) { $min = "0$min"; } if ($sec < 10) { $sec = "0$sec"; } $weekstr = $week[$weekday] ; $monstr = $month[$mon]; return "$weekstr, $day-$monstr-$year $hour:$min:$sec GMT"; } ##### 入出力処理 ###### #----- ブラウザへの入力データの取得 ----- sub init_form { local($query, @dataarray, $data, $property, $value, $charcode, $method); $charcode = $_[0]; $method = $ENV{'REQUEST_METHOD'}; $method =~ tr/A-Z/a-z/; if ($method eq 'post') { read(STDIN, $query, $ENV{'CONTENT_LENGTH'}); } else { $query = $ENV{'QUERY_STRING'}; } @dataarray = split(/&/, $query); foreach $data (@dataarray) { ($property, $value) = split(/=/, $data); $value =~ tr/+/ /; $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg; &jcode'convert(*value, $charcode); $form{$property} = $value; } } #----- 入力データからのタグ、メタ文字の消去 ----- sub norm_input { local($string) = @_; $string =~ s/&/&/g; $string =~ s/"/"/g if ($permittag != 1); $string =~ s/</g if ($permittag != 1); $string =~ s/>/>/g if ($permittag != 1); $string =~ s/,/,/g; $string =~ s/\r\n/\n/g; $string =~ s/\r/\n/g; $string =~ s/\n\n/ /g; $string =~ s/\n//g; return $string; } #----- クッキー情報の取得 ----- sub init_cookie { local($cookiename) = @_; local($name, $value, @pairs, $pair); @cookie_list = split(/;\s/, $ENV{'HTTP_COOKIE'}); foreach $cookie_list (@cookie_list) { ($cookie_name, $cookie_value) = split(/=/, $cookie_list); if ($cookie_name eq $cookiename) { $cookie_value =~ s/:/; /g; $cookie_value =~ s/_/=/g; @pairs = split(/;\s/, $cookie_value); foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $name =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg; $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg; $cookie{$name} = $value; } last; } } } #----- クッキー情報の出力 ----- sub print_cookie { local($cookiename, $days, $domain) = @_; local($cookiestr) = &make_cookie($cookiename); local($expiredate) = &get_expiredate_string($days); print "Set-Cookie: $cookiestr;"; print " expires=$expiredate;"; if ($domain) { print " domain=$domain;"; } print "\n"; } #----- クッキー情報の作成 ----- sub make_cookie { local($cookiename) = @_; local(@enccookie, @encstr); local($name, $value); local($encode) = '\%\+\;\,\=\&\_\:'; while (($name, $value) = each %cookie) { $name =~ s/([$encode])/'%'.unpack("H2", $1)/eg; $value =~ s/([$encode])/'%'.unpack("H2", $1)/eg; $name =~ s/\s/\+/g; $value =~ s/\s/\+/g; push(@enccookie, "${name}_${value}"); } $encstr = join(':', @enccookie); return "$cookiename=$encstr"; } ##### ファイル処理 ##### #----- ファイルオープン ----- sub open_file { local(*FILE, $name, $msg) = @_; if (!open(FILE, $name)) { &print_error("$msg"); } seek(FILE, 0, 0); } #----- ファイルクローズ ----- sub close_file { local(*FILE) = @_; close(FILE); } #----- ファイルロック ----- sub lock_file { local(*FILE) = @_; if ($uselock) { eval("flock(FILE, 2)"); if ($@) { &print_error("Cannot use flock"); } } } #----- ファイルアンロック ----- sub unlock_file { local(*FILE) = @_; if ($uselock) { eval("flock(FILE, 8)"); } } ##### エラー処理 ##### #----- エラー文を出力し終了 ----- sub print_error { local($msg) = @_; print "Content-type: text/html\n\n"; print <<"END_PRINT_ERROR"; $contenttype $msg $msg エラーです。再度試していただいてもエラーが出力される場合には$adminmailまでお知らせください。 戻る END_PRINT_ERROR exit(0); }