#┌───────────────────────────────── #│ ログインモジュール v1.5 #│ login.pl - 2022/01/30 #│ copyright (c) kentweb, 1997-2022 #│ https://www.kent-web.com/ #└───────────────────────────────── # [ 必要ファイル構成 ] # lib/CGI/Session.pm # lib/CGI/Session/*.* # lib/Digest/SHA/PurePerl.pm # data/pass.dat # data/pwd/ # data/ses/ #----------------------------------------------------------- # アクセス認証 #----------------------------------------------------------- sub auth_login { $in{id} =~ s/\W//g; $in{pw} =~ s/\W//g; # ログイン if ($in{login} && $in{id} ne '' && $in{pw} ne '') { if (-e "$cf{datadir}/pwd/$in{id}.lock") { my $mtime = (stat("$cf{datadir}/pwd/$in{id}.lock"))[9]; # 自動解除 if ($cf{lock_days} > 0 && (time - $mtime > $cf{lock_days} * 3600)) { unlink("$cf{datadir}/pwd/$in{id}.lock"); # ログインを全ブロック } else { auth_err("このログインIDは現在使用できません"); } } my ($flg,$crypt); open(IN,"$cf{datadir}/pass.dat") or auth_err("open err: pass.dat"); while(<IN>) { chomp; my ($id,$pw) = split(/\t/); if ($in{id} eq $id) { $flg++; $crypt = $pw; last; } } close(IN); if (!$flg) { auth_err("認証できません"); } elsif (!decrypt($crypt,$in{pw})) { if (!-e "$cf{datadir}/pwd/$in{id}.dat") { open(DAT,"> $cf{datadir}/pwd/$in{id}.dat"); close(DAT); } open(DAT,"+< $cf{datadir}/pwd/$in{id}.dat"); eval "flock(DAT,2);"; my $kai = <DAT> + 1; seek(DAT,0,0); print DAT $kai; close(DAT); if ($kai >= $cf{max_failpass}) { rename("$cf{datadir}/pwd/$in{id}.dat","$cf{datadir}/pwd/$in{id}.lock"); auth_err("一定回数以上間違ったログインをしたためロックしました"); } auth_err("認証できません"); } # ロックファイルを開放しておく unlink("$cf{datadir}/pwd/$in{id}.dat") if (-e "$cf{datadir}/pwd/$in{id}.dat"); # 新規セッション発行 my $ses = new CGI::Session(undef,undef,{Directory => "$cf{datadir}/ses"}) or die CGI::Session->errstr; # 有効時間は1時間 $ses->expire(3600); # セッションID my $sid = $ses->id(); $in{sid} = $sid; # 掃除 ses_clean(); # 入室画面 } elsif ($in{sid} eq '') { enter_form(); # 認証 } else { # セッション認識 my $ses = CGI::Session->load(undef,$in{sid},{Directory => "$cf{datadir}/ses"}); # ログアウト if ($in{logoff}) { $ses->delete(); # 入室画面に戻る if ($ENV{PERLXS} eq "PerlIS") { print "HTTP/1.0 302 Temporary Redirection\r\n"; print "Content-type: text/html\n"; } print "Location: $cf{admin_cgi}\n\n"; exit; } # 期限切れ if ( $ses->is_expired or $ses->is_empty ) { my $data = qq|<p>[<a href="$cf{admin_cgi}">ログインする</a>]</p>|; auth_err("タイムアウトです。再度ログインしてください。$data"); } } } #----------------------------------------------------------- # 入室画面 #----------------------------------------------------------- sub enter_form { header("入室画面"); print <<EOM; <div id="body"> <form action="$cf{admin_cgi}" method="post"> <fieldset id="login"> <legend> Log in </legend> <p> username<br> <input type="text" name="id" class="auth"> </p><p> password<br> <input type="password" name="pw" class="auth"> </p> <div class="ta-r"> <input type="submit" name="login" value="Log in"> </div> </fieldset> </form> <script>self.document.forms[0].id.focus();</script> </body> </div> </html> EOM exit; } #----------------------------------------------------------- # パスワード管理 #----------------------------------------------------------- sub pass_mgr { # 追加 if ($in{add} && $in{id} ne '' && $in{pw} ne '') { if ($in{id} =~ /\W/ or $in{pw} =~ /\W/) { error("ID又はパスワードに英数字・アンダーバー以外が含まれています"); } # 暗号化 my $crypt = encrypt($in{pw}); my ($flg,@log); open(DAT,"+< $cf{datadir}/pass.dat"); while(<DAT>) { my ($id,$pw) = split(/\t/); # ID重複 if ($in{id} eq $id) { $flg++; last; } push(@log,$_); } if ($flg) { close(DAT); error("このIDはすでに作成されてます"); } push(@log,"$in{id}\t$crypt\n"); seek(DAT,0,0); print DAT @log; truncate(DAT,tell(DAT)); close(DAT); } my ($chg,$del); for ( keys %in ) { if (/^chg:(\w+)/) { $chg = $1; last; } elsif (/^del:(\w+)/) { $del = $1; last; } } # パス変更 if ($chg) { if ($in{"pwd:$chg"} eq '') { error("パスワードが未入力です"); } elsif ($in{"pwd:$chg"} =~ /\W/) { error("パスワードに英数字・アンダーバー以外が含まれています"); } # 暗号化 my $crypt = encrypt($in{"pwd:$chg"}); my @log; open(DAT,"+< $cf{datadir}/pass.dat"); while(<DAT>) { my ($id,$pw) = split(/\t/); if ($chg eq $id) { $_ = "$id\t$crypt\n"; } push(@log,$_); } seek(DAT,0,0); print DAT @log; truncate(DAT,tell(DAT)); close(DAT); pwd_msg("パスワードを変更しました"); # 削除 } elsif ($del) { my @log; open(DAT,"+< $cf{datadir}/pass.dat"); while(<DAT>) { my ($id,$pw) = split(/\t/); next if ($del eq $id); push(@log,$_); } seek(DAT,0,0); print DAT @log; truncate(DAT,tell(DAT)); close(DAT); } # 画面表示 header("パスワード管理"); print <<EOM; <div id="body"> <div class="back-btn"> <form action="$cf{admin_cgi}" method="post"> <input type="hidden" name="sid" value="$in{sid}"> <input type="submit" value="< メニュー"> </form> </div> <div class="ttl">■ 管理パスワード管理</div> <form action="$cf{admin_cgi}" method="post" name="selform"> <input type="hidden" name="sid" value="$in{sid}"> <input type="hidden" name="pass_mgr" value="1"> <p> ・ 新規作成・削除・変更を行います。<br> ・ ID及びパスワードで使用できる文字は「英数字及びアンダーバー」のみ。 </p> <table class="form-tbl"> <tr> <th class="ta-c"></th> <th class="ta-c">ログインID</th> <th class="ta-c">パスワード</th> </tr><tr> <td>[新規]</td> <td><input type="text" name="id" size="20"></td> <td> <input type="text" name="pw" size="20"> <input type="submit" name="add" value="追加"> </td> </tr> EOM open(IN,"$cf{datadir}/pass.dat"); while(<IN>) { my ($id,$pw) = split(/\t/); print qq|<tr><td><input type="submit" name="del:$id" value="削除" onclick="return confirm('よろしいですか?');"></td>|; print qq|<td class="ta-c"><b>$id</b></td>|; print qq|<td><input type="text" name="pwd:$id" size="20">\n|; print qq|<input type="submit" name="chg:$id" value="変更"></td></tr>\n|; } close(IN); print <<EOM; </table> </form> </div> </body> </html> EOM exit; } #----------------------------------------------------------- # メッセージ #----------------------------------------------------------- sub pwd_msg { my $msg = shift; header($msg); print <<EOM; <div id="body"> <div id="msg-box"> <p>$msg</p> <p> <form action="$cf{admin_cgi}" method="post"> <input type="hidden" name="pass_mgr" value="1"> <input type="hidden" name="sid" value="$in{sid}"> <input type="submit" value="最初の画面へ戻る"> </form> </p> </div> </div> </body> </html> EOM exit; } #----------------------------------------------------------- # 認証エラー #----------------------------------------------------------- sub auth_err { my $msg = shift; header("認証エラー"); print <<EOM; <div id="body"> <div id="err-box"> <h4>ERROR!</h4> <p id="err-msg">$msg</p> <p><input type="button" value="入力画面へ戻る" onclick="window.open('$cf{admin_cgi}','_self')"></p> </div> </div> </body> </html> EOM exit; } #----------------------------------------------------------- # セッションディレクトリ掃除 #----------------------------------------------------------- sub ses_clean { my $time = time; opendir(DIR,"$cf{datadir}/ses"); while( my $dir = readdir(DIR) ) { next if ($dir eq '.'); next if ($dir eq '..'); next if ($dir eq 'index.html'); # ファイル時間取得 my $mtime = (stat("$cf{datadir}/ses/$dir"))[9]; # 1時間以上経過しているファイルは削除 unlink("$cf{datadir}/ses/$dir") if ($time - $mtime > 3600); } closedir(DIR); } #----------------------------------------------------------- # Digest::SHA256 暗号 #----------------------------------------------------------- sub encrypt { my $plain = shift; my @str = ('a' .. 'z', 'A' .. 'Z', 0 .. 9); my $salt; for (1 .. 8) { $salt .= $str[int(rand(@str))]; } # 変換 return $salt . sha256_base64($salt . $plain); } #----------------------------------------------------------- # Digest::SHA256 照合 #----------------------------------------------------------- sub decrypt { my ($crypt,$plain) = @_; # 照合 my $salt = substr($crypt,0,8); return $crypt eq ($salt . sha256_base64($salt . $plain)) ? 1 : 0; } 1;