#!/home/local/usr/inet/bin/perl5
#==================================================================
$g_name = "TwnLounge Ver2.07";
#
# Copyright (C) 1997-1999 とほほ http://wakusei.cplaza.ne.jp/twn/
#
# 法人/個人/私用/商用を問わず、使用・改造・再配布可能です。
#==================================================================
# 履歴
# 1999/05/23 2.00 大改造版
# 1999/05/23 2.01 空行が空行として表示されないバグを修正
# 1999/05/30 2.02 utimeコマンドにパスワードチェックを追加
# 1999/05/30 2.03 メッセージ末尾の改行を削除するように修正
# 1999/05/30 2.04 「>」に加えて「>」も引用と見なすようにした
# 1999/05/30 2.05 タイトルも検索対象になるように修正
# 1999/06/06 2.06 ロックファイルのパーミッションを755から0755に修正
# 1999/06/06 2.06 引用文の色を変更
# 1999/10/03 2.07 誤ってロックファイルを消してしまうことがあるバグを修正
# 漢字コード変換ライブラリ
require "jcode.pl";
# 日本時刻のための環境変数
$ENV{'TZ'} = "JST-9";
# グローバル変数
$g_dir = "wwwlng";
@g_wdays = ( "日", "月", "火", "水", "木", "金", "土" );
$g_return_url = "http://www.kcn.ne.jp/~blue/index.html";
$g_default_number = 50;
$g_lockdir = "../lock";
$g_lockfile = "$g_lockdir/wwwlng.loc";
$g_sendmail = '/usr/lib/sendmail'; # メール送信コマンド
$g_show_questioner = 0;
$g_passwd = "1234";
$g_search_log = "search.txt";
#
# メインルーチン
#
{
# カレントディレクトリの移動
chdir($g_dir);
# フォームデータを読み込む
if (!&readParam()) {
&showErrorPage("データの読み込みに失敗しました。");
exit(0);
}
# それぞれのページ表示にジャンプ
if ($#ARGV == -1) {
&showTopPage($g_default_number); # トップ
} elsif ($ARGV[0] eq "dir") {
&showDirPage($ARGV[1]); # トピック一覧
} elsif ($ARGV[0] eq "list") {
&showTopicListByDays($ARGV[1]); # トピック一覧
} elsif ($ARGV[0] eq "print") {
&showTopicPage($ARGV[1]); # トピック表示
} elsif ($ARGV[0] eq "showall") {
&showLatelyPage($ARGV[1]); # 全文表示
} elsif ($ARGV[0] eq "new") {
&showNewTopicPage(); # 新規トピック入力
} elsif ($ARGV[0] eq "search") {
&showSearchPage(); # 検索
} elsif ($ARGV[0] eq "test") {
&showTestPage(); # 自己診断
} elsif ($ARGV[0] eq "new2") {
&appendTopic(); # トピック追加
} elsif ($ARGV[0] eq "append") {
&appendMessage($ARGV[1], 1); # 発言追加
} elsif ($ARGV[0] eq "delete") {
shift(@ARGV);
&deleteTopic(@ARGV);
} elsif ($ARGV[0] eq "utime") {
shift(@ARGV);
&utimeTopic(@ARGV); # ファイルの更新時刻を合わせる
} else {
&showErrorPage("URLの指定が誤っています。");
}
exit(0);
}
#
# トップページ
#
sub showTopPage #($num)
{
local($num) = @_;
local($dir, @dirs, $file, @files);
&printHeader("ラウンジ(最近更新されたトピック)");
if ($g_return_url) {
print "[戻る]\n";
}
print "[使用上の注意]\n";
print "[新規質問]\n";
print "[過去のフォルダ一覧]\n";
print "
\n";
if (0) {
print "\n";
}
#---------
print "Lounge: Created by
\n";
print "[とほほ]\n";
# print "[blue]\n";
#--------
print "
これまでのまとめは「Q&A集」にあります。\n";
print "
\n";
#--------
print "[H.P]\n";
print "[CGI]\n";
print "[PHOTO]\n";
print "[LINK]\n";
print "[BBS]\n";
print "[Travel BBS]\n";
print "[落書き帳]\n";
#------------
#print "
\n";
#print "現在、このラウンジは試験運用中です。";
#print "試験運用中に書き込まれたデータはすべて消去しますので、";
#print "ご協力くださる方は、テストデータを書き込み、試験のご協力を";
#print "お願いいたします。\n";
#print "
\n";
# 最近更新されたトピックの一覧を得る
opendir(DIR, ".");
foreach $dir (grep { !/^\./ && -d "$_" } readdir(DIR)) {
push(@dirs, $dir);
opendir(DIR2, $dir) || next;
foreach $file (grep { !/^\./ && -f "$dir/$_" } readdir(DIR2)) {
push(@files, (stat("$dir/$file"))[9] . " $dir/$file");
}
closedir(DIR2);
}
closedir(DIR);
@dirs = sort(@dirs);
@files = sort { $b <=> $a } @files;
splice(@files, $num);
# 最近更新されたトピック一覧
print "\n";
&printTopicList(@files);
# フォルダ一覧
if ($#dirs >= 0) {
print "
\n";
print "
\n";
print "- 過去のフォルダ一覧\n";
print "
- \n";
foreach $dir (@dirs) {
print "[$dir]\n";
}
print "\n";
print "
\n";
}
# フッタ
&printFooter();
}
#
# トピック一覧
#
sub showDirPage #($dir)
{
local($dir) = @_;
local($file, @files);
if ($dir =~ /(^\/|\.\.)/) {
&showErrorPage("他のフォルダを参照することはできません。");
exit(0);
}
&printHeader("ラウンジ(フォルダ$dirのトピック一覧)");
print "[戻る]\n";
print "
\n";
# ファイルの日付とファイル名の一覧を得る
opendir(DIR, $dir);
foreach $file (grep { !/^\./ } readdir(DIR)) {
push(@files, (stat("$dir/$file"))[9] . " $dir/$file");
}
closedir(DIR);
# フォルダのファイル一覧
&printTopicList(@files);
# フッタを表示する
print "\n";
&printFooter();
}
#
# トピック一覧ページ
#
sub showTopicListByDays #($days)
{
local($days) = @_;
local($limittime, $mtime);
local($dir, @dirs, $file, @files);
&printHeader("ラウンジ(過去$days日分一覧表\示)");
print "[戻る]\n";
print "
\n";
# 過去n日分のトピック一覧を得る
$limittime = time() - ($days * 3600 * 24);
opendir(DIR, ".");
foreach $dir (grep { !/^\./ && -d "$_" } readdir(DIR)) {
push(@dirs, $dir);
opendir(DIR2, $dir) || next;
foreach $file (grep { !/^\./ && -f "$dir/$_" } readdir(DIR2)) {
$mtime = (stat("$dir/$file"))[9];
if ($mtime >= $limittime) {
push(@files, "$mtime $dir/$file");
}
}
closedir(DIR2);
}
closedir(DIR);
@files = sort { $b <=> $a } @files;
# トピックの中身を表示する
&printTopicList(@files);
&printFooter();
}
#
# トピック表示ページ
#
sub showTopicPage #($file)
{
local($file) = @_;
if ($file =~ /(^\/|\.\.)/) {
&showErrorPage("他のフォルダを参照することはできません。");
exit(0);
}
open(IN, $file);
$subject = ;
$subject =~ s/^Subject: //;
$subject =~ s/[\r\n]+$//;
&printHeader($subject);
print "[戻る]\n";
&printTopicContent(*IN);
close(IN);
print "
\n";
print "追加発言\n";
print "\n";
&printFooter();
}
#
# 全文表示ページ
#
sub showLatelyPage #($days)
{
local($days) = @_;
local($limittime, $mtime);
local($dir, @dirs, $file, @files);
&printHeader("ラウンジ(全文表\示)");
print "[戻る]\n";
# 過去n日分のトピック一覧を得る
$limittime = time() - ($days * 3600 * 24);
opendir(DIR, ".");
foreach $dir (grep { !/^\./ && -d "$_" } readdir(DIR)) {
push(@dirs, $dir);
opendir(DIR2, $dir) || next;
foreach $file (grep { !/^\./ && -f "$dir/$_" } readdir(DIR2)) {
$mtime = (stat("$dir/$file"))[9];
if ($mtime >= $limittime) {
push(@files, "$mtime $dir/$file");
}
}
closedir(DIR2);
}
closedir(DIR);
@files = sort { $b <=> $a } @files;
# トピックの中身を表示する
foreach $file (@files) {
$file = (split(/ /, $file))[1];
open(IN, $file);
$subject = ;
$subject =~ s/^Subject: //;
$subject =~ s/[\r\n]+$//;
$hflag = 0;
print "
\n";
print "■ $subject
\n";
&printTopicContent(*IN);
print "
\n";
print "[この(↑)トピックに追加発言]\n";
close(IN);
}
&printFooter();
}
#
# 新規トピック入力ページ
#
sub showNewTopicPage
{
&printHeader("ラウンジ(新規質問)");
print "[戻る]\n";
print "
\n";
print "\n";
&printFooter();
}
#
# 検索ページ
#
sub showSearchPage
{
# 出力時にバッファリングしないようにする
$| = 1;
&printHeader("ラウンジ(検索)");
print "[戻る]\n";
print "
\n";
# キーワードが無ければ終了
if (!$FORM{'WORD'}) {
goto done;
}
# 複数のワードを分割する
@words = split(/ +/, $FORM{'WORD'});
# メタ文字を無効化する
for ($i = 0; $i <= $#words; $i++) {
$words[$i] =~ s/([\+\*\.\?\^\$\[\-\]\|\(\)\\])/\\$1/g;
}
# 検索キーワードをロギングする
if (-f $g_search_log) {
open(OUT, ">> $g_search_log");
for ($i = 0; $i <= $#words; $i++) {
print OUT "$words[$i]\n";
}
close(OUT);
}
# ディレクトリの一覧を得る
opendir(DIR, ".");
@dirs = grep { !/^\./ && (-d "$_") } readdir(DIR);
closedir(DIR);
print "\n";
foreach $dir (@dirs) {
opendir(DIR, $dir);
@files = grep { -f "$dir/$_" } readdir(DIR);
foreach $file (@files) {
&printSearchResult("$dir/$file", @words);
}
closedir(DIR);
}
print "
\n";
done:
&printFooter();
}
#
# 自己診断ページ
#
sub showTestPage
{
&printHeader("ラウンジ(自己診断)");
print "[戻る]\n";
print "
\n";
print "OK. CGIは正常に動作しています。
\n";
if (!-x $g_sendmail) {
print "NG. $g_sendmailがありません。
\n";
}
if (!-d $g_lockdir) {
print "NG. $g_lockdirがありません。
\n";
}
if (-d $g_lockfile) {
print "NG. $g_lockfileが残っています。削除してください。
\n";
}
&printFooter();
}
#
# エラーページ
#
sub showErrorPage #($errmsg)
{
local($errmsg) = @_;
&printHeader("ラウンジ(エラー)");
print "[戻る]\n";
print "
\n";
print $errmsg;
&printFooter();
}
#
# トピック追加
#
sub appendTopic
{
# 入力パラメータのチェック
if ($FORM{'SUBJECT'} =~ /^[ \r\n]*$/) {
&showErrorPage("タイトルが入力されていません。");
exit(0);
}
if ($FORM{'NAME'} =~ /^[ \r\n]*$/) {
&showErrorPage("名前が入力されていません。");
exit(0);
}
if ($FORM{'MESSAGE'} =~ /^[ \r\n]*$/) {
&showErrorPage("内容が入力されていません。");
exit(0);
}
# ロックをかける
if (!&fileLock()) {
&showErrorPage("ファイルのロックに失敗しました。");
exit(0);
}
# 使用されていないファイル名を探す
if (!($file = &determineFileName())) {
&showErrorPage("新規質問の書き込みに失敗しました。");
goto done;
}
# サブジェクトを書き込む
if (!open(OUT, "> $file")) {
&showErrorPage("新規質問の書き込みに失敗しました。");
goto done;
}
print OUT "Subject: $FORM{'SUBJECT'}\n";
if (($FORM{'REPLY'} eq "on") && ($FORM{'EMAIL'} =~ /.*@.*/)) {
print OUT "Reply-To: $FORM{'EMAIL'}\n";
}
close(OUT);
# メッセージを追加する
&appendMessage($file, 0);
done:
&fileUnlock();
}
#
# 発言追加
#
sub appendMessage #($file, $lockflag)
{
local($file, $lockflag) = @_;
if ($file =~ /(^\/|\.\.)/) {
&showErrorPage("他のフォルダを参照することはできません。");
exit(0);
}
# 入力パラメータのチェック
if ($FORM{'NAME'} =~ /^[ \r\n]*$/) {
&showErrorPage("名前が入力されていません。");
exit(0);
}
if ($FORM{'MESSAGE'} =~ /^[ \r\n]*$/) {
&showErrorPage("内容が入力されていません。");
exit(0);
}
# ロックをかける
if ($lockflag) {
if (!&fileLock()) {
&showErrorPage("ファイルのロックに失敗しました。");
exit(0);
}
}
# トピックに発言を追加する
$msg = "========================================\n";
$msg .= "From: $FORM{'NAME'}\n";
if ($FORM{'EMAIL'} ne "") {
$FORM{'EMAIL'} =~ s/'//g;
$msg .= "E-Mail: $FORM{'EMAIL'}\n";
}
if ($FORM{'HPAGE'} ne "") {
$FORM{'HPAGE'} =~ s/'//g;
$msg .= "HomePage: $FORM{'HPAGE'}\n";
}
($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime();
$date = sprintf("%04d/%02d/%02d(%s) %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday, $g_wdays[$wday], $hour, $min, $sec);
$msg .= "Date: $date\n";
if ($FORM{'SOLVED'} eq "on") {
$msg .= "Solved: $FORM{'SOLVED'}\n";
}
$msg .= "\n";
$msg .= "$FORM{'MESSAGE'}\n";
open(OUT, ">> $file");
$old = select(OUT); $| = 1; select($old);
print OUT $msg;
close(OUT);
# 質問者にメールで知らせる
$mailto = "";
open(IN, "$file");
while () {
if (/^Reply-To:/) {
chop($_);
$mailto = $_;
$mailto =~ s/^Reply-To: //;
}
if (/^\t/) {
last;
}
}
if ($mailto ne "") {
seek(IN, 0, 0);
$msg = "To: $mailto\n";
$msg .= "Subject: TwnLounge Report\n";
$msg .= "\n";
while () {
$_ =~ s/[\r\n]*$//;
$_ =~ s/^\.$/. /;
$_ =~ s/<//g;
$_ =~ s/&/&/g;
$msg .= "$_\n";
}
&jcode'convert(*msg, "jis");
open(OUT, "| $g_sendmail $mailto");
print OUT $msg;
close(OUT);
}
close(IN);
# ロックをはずす
if ($lockflag) {
&fileUnlock();
}
done:
# ページを表示する
print "Location: $ENV{'SCRIPT_NAME'}?print+$file\n\n";
# &showTopicPage($file);
return(1);
}
#
# 新規ファイル名を決定する
#
sub determineFileName
{
local($sec, $min, $hour, $mday, $mon, $year);
local($dir, @files, $file, $num);
# ディレクトリ名(YYYYMM)を得る
($sec, $min, $hour, $mday, $mon, $year) = localtime();
$dir = sprintf("%04d%02d", $year + 1900, $mon + 1);
# ディレクトリが無ければ作成する
if (!-d $dir) {
mkdir($dir, 0755);
}
# 最新のファイル番号を得る
$num = 0;
opendir(DIR, $dir);
foreach $file (readdir(DIR)) {
if ($file =~ /^[0-9]{4}([0-9]{4}).txt$/) {
$num = ($num < $1) ? $1 : $num;
}
}
closedir(DIR);
$num++;
# ファイル名(YYYYMM/YYMMNNNN.txt)を返す
return(sprintf("$dir/%02d%02d%04d.txt", $year % 100, $mon + 1, $num));
}
#
# トピックを削除する
#
sub deleteTopic #($passwd, $file)
{
local($passwd, $file) = @_;
# パスワードをチェックする
if (($g_passwd eq "") || ($passwd ne $g_passwd)) {
&showErrorPage("パスワードが違います。");
exit(0);
}
if ($file =~ /(^\/|\.\.)/) {
&showErrorPage("他のフォルダのファイルを削除することはできません。");
exit(0);
}
if (!unlink($file)) {
&showErrorPage("$fileの削除に失敗しました。");
exit(0);
}
&printHeader("ラウンジ(削除)");
print "[戻る]\n";
print "
\n";
print "$file の削除が完了しました。\n";
&printFooter();
}
#
# ファイルの更新時刻を合わせる
#
sub utimeTopic #($passwd, @dirs)
{
local($passwd, @dirs) = @_;
local($dir);
# パスワードをチェックする
if (($g_passwd eq "") || ($passwd ne $g_passwd)) {
&showErrorPage("パスワードが違います。");
exit(0);
}
foreach $dir (@dirs) {
if ($dir =~ /(^\/|\.\.)/) {
&showErrorPage("他のフォルダを参照することはできません。");
exit(0);
}
}
&printHeader("ラウンジ(管理者用:ファイルの時刻調整)");
print "[戻る]\n";
print "
\n";
if ($dirs[0] eq "all") {
opendir(DIR, ".");
@dirs = grep { !/^\./ && (-d "$_") } readdir(DIR);
closedir(DIR);
}
foreach $dir (@dirs) {
opendir(DIR, $dir);
foreach $file (grep { !/^\./ && (-f "$dir/$_") } readdir(DIR)) {
$file = "$dir/$file";
$date = "";
open(IN, $file);
while () {
if (/^========================================[\r\n]+$/) {
$hflag = 1;
}
if (/^[\r\n]+$/) {
$hflag = 0;
}
if ($hflag == 0) {
next;
}
if (/^Date: /) {
$date = $_;
}
}
close(IN);
if ($date =~ /(\d\d\d\d)\/(\d\d)\/(\d\d).*(\d\d):(\d\d):(\d\d)/) {
$mtime = mktime($1 - 1900, $2 - 1, $3, $4, $5, $6, -9);
if (utime($mtime, $mtime, $file)) {
print "$file $date OK.
\n";
} else {
print "$file $date NG.
\n";
}
#$mtime = sprintf("%02d%02d%02d%02d%02d.%02d",
# $2, $3, $4, $5, $1 - 1900, $6);
#print("/bin/touch -t $mtime $file
\n");
#print system("/bin/touch -t $mtime $file");
#print "
\n";
}
}
}
&printFooter();
}
#
# ヘッダを書き出す
#
sub printHeader #($title)
{
local($title) = @_;
print "Content-type: text/html\n";
print "\n";
print "\n";
print "\n";
print "$title\n";
print "\n";
print "\n";
print "$title
\n";
}
#
# フッタを書き出す
#
sub printFooter
{
print "
\n";
print "\n";
print "$g_name\n";
print "
\n";
print "\n";
print "\n";
}
#
# トピック一覧を書き出す
#
sub printTopicList #(@files)
{
local(@files) = @_;
local($file, $hflag, $count, $questioner, $solved, $from, $date, $subject);
local($line);
print "\n";
print "\n";
}
#
# トピック内容を書き出す
#
sub printTopicContent #(IN)
{
local(*IN) = @_;
while () {
s/[\r\n]+//;
if (/^Reply-To: /) {
next;
} elsif ($hflag && /^From: /) {
s/^From: //;
print "
\n";
print "$_\n";
} elsif ($hflag && /^E-Mail: /) {
s/^E-Mail: //;
print "[E-Mail]\n";
} elsif ($hflag && /^HomePage: /) {
s/^HomePage: //;
print "[HomePage]\n";
} elsif ($hflag && /^Date: /) {
s/^Date: //;
print "$_
\n";
} elsif ($hflag && /^Solved: /) {
print "[[解決]]
\n";
} elsif (/^========================================[\r\n]*$/) {
$hflag = 1;
} elsif ($hflag && ($_ eq "")) {
$hflag = 0;
} else {
s/(http:\/\/[a-zA-Z0-9\.\/\-+#_?~&%=^\@:;]+)/$1<\/A>/g;
s/^((>|>).*$)/$1<\/FONT>/;
print "$_
\n";
}
}
}
#
# 検索結果を書き出す
#
sub printSearchResult #($file)
{
local($file, @words) = @_;
local($subject, $wcount, $word);
local(%wordflag, %wordtext);
$title = "";
$wcount = 0;
open(IN, $file);
$subject = ;
$subject =~ s/^Subject: //;
$subject =~ s/[\r\n]+$//;
seek(IN, 0, 0);
loop: while () {
foreach $word (@words) {
if ($wordflag{$word} == 1) { next; }
if (!/$word/) { next; }
s/[\r\n]+$//;
$wordtext{$word} = $_;
$wordflag{$word} = 1;
if (($FORM{'ANDOR'} eq "or") || (++$wcount == $#words + 1)) {
if ($subject eq "") { $subject = $file; }
print "\n";
print "
$subject\n";
foreach $word (@words) {
print "$wordtext{$word}\n";
}
last loop;
}
}
}
close(IN);
}
#
# フォームデータを読みこむ
#
sub readParam
{
local($query_string, @a, $x, $name, $value);
if ($ENV{'REQUEST_METHOD'} ne "POST") {
return(1);
}
if (!read(STDIN, $query_string, $ENV{'CONTENT_LENGTH'})) {
return(undef);
}
@a = split(/&/, $query_string);
foreach $x (@a) {
($name, $value) = split(/=/, $x);
$value =~ tr/+/ /;
$value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg;
&jcode'convert(*value, "euc");
&jcode'h2z_euc(*value);
$value =~ s/&/&/g;
$value =~ s/</g;
$value =~ s/>/>/g;
$value =~ s/\r\n/\n/g;
$value =~ s/\s+$//;
&jcode'convert(*value, "sjis");
$FORM{$name} = $value;
}
return(1);
}
#
# ファイルをロックする
#
sub fileLock
{
for ($i = 1; $i <= 6; $i++) {
if (mkdir($g_lockfile, 0755)) {
$SIG{'PIPE'} = "sigexit";
$SIG{'INT'} = "sigexit";
$SIG{'HUP'} = "sigexit";
$SIG{'QUIT'} = "sigexit";
$SIG{'TERM'} = "sigexit";
last;
} elsif ($i < 6) {
sleep(2);
} else {
return(undef);
}
}
return(1);
}
#
# ファイルのロックを解除する
#
sub fileUnlock
{
for ($i = 1; $i <= 4; $i++) {
if (rmdir($g_lockfile) == 0) {
last;
} elsif ($i < 4) {
sleep(1);
} else {
return(undef);
}
}
return(1);
}
#
# プログラム中断時の処理
#
sub sigexit
{
if (-d $g_lockfile) {
rmdir($g_lockfile);
}
exit(0);
}
#
# 年/月/日 時:分:秒から、UNIX時間(1970/1/1からの秒数)を求める
#
sub mktime #($year, $mon, $mday, $hour, $min, $sec, $tz)
{
local($year, $mon, $mday, $hour, $min, $sec, $tz) = @_;
local(@mdays, $xtime);
@mdays = ( 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
$uru = int(($year - 72) / 4);
if (((($year + 1900) % 4) == 0) && ($mon < 2)) { $uru--; }
$xtime = $year - 70;
$xtime = ($xtime * 365) + $mdays[$mon] + $mday + $uru;
$xtime = ($xtime * 24) + $hour + $tz;
$xtime = ($xtime * 60) + $min;
$xtime = ($xtime * 60) + $sec;
return($xtime);
}