$CGI::Auth::Basic::VERSION
=
'1.24'
;
use
constant
CRYP_CHARS
=>
q{.}
,
q{,}
,
q{/}
, 0..9,
q{A}
..
q{Z}
,
q{a}
..
q{z}
;
our
$RE
=
qr{\A\w\./}
xms;
our
$FATAL_HEADER
;
our
%ERROR
= (
INVALID_OPTION
=>
'Options must be in "param => value" format!'
,
CGI_OBJECT
=>
'I need a CGI object to run!!!'
,
FILE_READ
=>
'Error opening pasword file: '
,
NO_PASSWORD
=>
'No password specified (or password file can not be found)!'
,
UPDATE_PFILE
=>
'Your password file is empty and your current setting does not allow this code to update the file! Please update your password file.'
,
ILLEGAL_PASSWORD
=>
'Illegal password! Not accepted. Go back and enter a new one'
,
FILE_WRITE
=>
'Error opening password file for update: '
,
UNKNOWN_METHOD
=>
'There is no method called "<b>%s</b>". Check your coding.'
,
EMPTY_FORM_PFIELD
=>
q{You didn't set any password (password file is empty)!}
,
WRONG_PASSWORD
=>
'<p>Wrong password!</p>'
,
INVALID_COOKIE
=>
'Your cookie info includes invalid data and it has been deleted by the program.'
,
);
sub
new {
my
(
$class
,
@args
) =
@_
;
my
$self
= {};
bless
$self
,
$class
;
$self
->_fatal(
$ERROR
{INVALID_OPTION})
if
@args
% 2;
$self
->_set_options(
@args
);
$self
->_init;
return
$self
;
}
sub
_set_options {
my
(
$self
,
%o
) =
@_
;
if
(
$o
{cgi_object} eq
'AUTOLOAD_CGI'
) {
$o
{cgi_object} = CGI->new;
}
else
{
if
( !
$o
{ihacloaiwtui} ) {
$self
->_fatal(
$ERROR
{CGI_OBJECT})
if
ref
$o
{cgi_object} ne
'CGI'
;
}
}
my
$password
;
if
(
$o
{file} and -e
$o
{file} and not -d
$o
{file}) {
$self
->{password_file_path} =
$o
{file};
$password
=
sub
{
$self
->_pfile_content};
}
else
{
$password
=
$o
{password};
}
$self
->_fatal(
$ERROR
{NO_PASSWORD})
if
!
$password
;
$self
->{password} =
$password
;
$self
->{cgi} =
$o
{cgi_object};
$self
->{program} =
$self
->{cgi}->url || EMPTY_STRING;
$self
->{cookie_id} =
$o
{cookie_id} ||
'authpass'
;
$self
->{http_charset} =
$o
{http_charset} ||
'ISO-8859-1'
;
$self
->{logoff_param} =
$o
{logoff_param} ||
'logoff'
;
$self
->{changep_param} =
$o
{changep_param} ||
'changepass'
;
$self
->{cookie_timeout} =
$o
{cookie_timeout} || EMPTY_STRING;
$self
->{setup_pfile} =
$o
{setup_pfile} || 0;
$self
->{chmod_value} =
$o
{chmod_value} || CHMOD_VALUE;
$self
->{use_flock} =
$o
{use_flock} || 1;
$self
->{hidden} =
$o
{hidden} || [];
return
;
}
sub
exit_code {
my
$self
=
shift
;
my
$code
=
shift
||
return
;
$self
->{EXIT_PROGRAM} =
$code
if
ref
$code
eq
'CODE'
;
return
;
}
sub
_init {
my
$self
=
shift
;
if
( !
ref
$self
->{hidden} eq
'ARRAY'
) {
$self
->_fatal(
'hidden parameter must be an arrayref!'
);
}
my
$hidden
;
my
@hidden_q
;
foreach
(@{
$self
->{hidden} }) {
next
if
$_
->[0] eq
$self
->{cookie_id};
next
if
$_
->[0] eq
$self
->{cookie_id} .
'_new'
;
$hidden
.=
qq~<input type="hidden" name="$_->[0]" value="$_->[1]">\n~
;
push
@hidden_q
,
join
q{=}
,
$_
->[0],
$_
->[1];
}
$self
->{hidden_q} =
@hidden_q
?
join
(
q{&}
,
@hidden_q
) : EMPTY_STRING;
$self
->{hidden} =
$hidden
|| EMPTY_STRING;
$self
->{logged_in} = 0;
$self
->{EXIT_PROGRAM} =
sub
{CORE::
exit
()};
$self
->{_TEMPLATE_TITLE} = {
title_login_form
=>
'Login'
,
title_cookie_error
=>
'Your invalid cookie has been deleted by the program'
,
title_login_success
=>
'You are now logged-in'
,
title_logged_off
=>
'You are now logged-off'
,
title_change_pass_form
=>
'Change password'
,
title_password_created
=>
'Password created'
,
title_password_changed
=>
'Password changed successfully'
,
title_error
=>
'Error'
,
};
$self
->{_TEMPLATE_TITLE_USER} = {};
$self
->{_TEMPLATE_NAMES} = [
qw(
login_form
screen
logoff_link
change_pass_form
)
];
$self
->{
$_
} = EMPTY_STRING
foreach
qw(
page_form_error
page_logoff_link
page_content
page_title
)
;
return
;
}
sub
_setup_password {
my
$self
=
shift
;
$self
->_fatal(
$ERROR
{UPDATE_PFILE})
unless
$self
->{setup_pfile};
if
( !
$self
->{cgi}->param(
'change_password'
) ) {
return
$self
->_screen(
content
=>
$self
->_change_pass_form(
$ERROR
{EMPTY_FORM_PFIELD}),
title
=>
$self
->_get_title(
'change_pass_form'
),
);
}
my
$password
=
$self
->{cgi}->param(
$self
->{cookie_id}.
'_new'
);
$self
->_check_password(
$password
);
$self
->_update_pfile(
$password
);
return
$self
->_screen(
content
=>
$self
->_get_title(
'password_created'
),
title
=>
$self
->_get_title(
'password_created'
),
cookie
=>
$self
->_empty_cookie,
forward
=> 1,
);
}
sub
_check_password {
my
$self
=
shift
;
my
$password
=
shift
;
my
$not_ok
= !
$password
||
$password
=~ /\s/xms ||
length
(
$password
) < MIN_PASSWORD_LENGTH ||
length
(
$password
) > MAX_PASSWORD_LENGTH ||
$password
=~
$RE
;
$self
->_error(
$ERROR
{ILLEGAL_PASSWORD} )
if
$not_ok
;
return
;
}
sub
_update_pfile {
my
$self
=
shift
;
my
$password
=
shift
;
my
$PASSWORD
= IO::File->new;
$PASSWORD
->
open
(
$self
->{password_file_path},
'>'
) or
$self
->_fatal(
$ERROR
{FILE_WRITE}.
" $!"
);
flock
$PASSWORD
, Fcntl::LOCK_EX()
if
$self
->{use_flock};
my
$pok
=
print
{
$PASSWORD
}
$self
->_encode(
$password
);
flock
$PASSWORD
, Fcntl::LOCK_UN()
if
$self
->{use_flock};
$PASSWORD
->
close
;
return
chmod
$self
->{chmod_value},
$self
->{password_file_path};
}
sub
_pfile_content {
my
$self
=
shift
;
my
$PASSWORD
= IO::File->new;
$PASSWORD
->
open
(
$self
->{password_file_path}) or
$self
->_fatal(
$ERROR
{FILE_READ}.
" $!"
);
my
$flat
=
do
{
local
$/;
my
$rv
= <
$PASSWORD
>;
$rv
};
chomp
$flat
;
$PASSWORD
->
close
;
$flat
=~ s{\s}{}xmsg;
return
$flat
;
}
sub
check_user {
my
$self
=
shift
;
$self
->_check_user_real;
if
(
$self
->{cgi}->param(
$self
->{changep_param}) ) {
if
( !
$self
->{cgi}->param(
'change_password'
) ) {
$self
->_screen(
content
=>
$self
->_change_pass_form,
title
=>
$self
->_get_title(
'change_pass_form'
)
);
}
my
$password
=
$self
->{cgi}->param(
$self
->{cookie_id}.
'_new'
);
$self
->_check_password(
$password
);
$self
->_update_pfile(
$password
);
$self
->_screen(
content
=>
$self
->_get_title(
'password_changed'
),
title
=>
$self
->_get_title(
'password_changed'
),
cookie
=>
$self
->_empty_cookie,
forward
=> 1);
}
return
;
}
sub
_check_user_real {
my
$self
=
shift
;
my
$pass_param
;
if
(
ref
(
$self
->{password}) eq
'CODE'
) {
$self
->{password} =
$self
->{password}->() ||
$self
->_setup_password;
}
if
(
$self
->{cgi}->param(
$self
->{logoff_param})) {
$self
->_screen(
content
=>
$self
->_get_title(
'logged_off'
),
title
=>
$self
->_get_title(
'logged_off'
),
cookie
=>
$self
->_empty_cookie,
forward
=> 1,
);
}
if
(
$pass_param
=
$self
->{cgi}->param(
$self
->{cookie_id})){
if
(
$pass_param
!~
$RE
&&
$self
->_match_pass(
$pass_param
) ) {
$self
->{logged_in} = 1;
$self
->_screen(
content
=>
$self
->_get_title(
'login_success'
),
title
=>
$self
->_get_title(
'login_success'
),
forward
=> 1,
cookie
=>
$self
->{cgi}->cookie(
-name
=>
$self
->{cookie_id},
-value
=>
$self
->{password},
-expires
=>
$self
->{cookie_timeout},
),
);
}
else
{
$self
->_screen(
content
=>
$self
->_login_form(
$ERROR
{WRONG_PASSWORD}),
title
=>
$self
->_get_title(
'login_form'
),
);
}
}
elsif
(
$pass_param
=
$self
->{cgi}->cookie(
$self
->{cookie_id})) {
if
(
$pass_param
!~
$RE
&&
$pass_param
eq
$self
->{password} ) {
$self
->{logged_in} = 1;
return
1;
}
else
{
$self
->_screen(
content
=>
$ERROR
{INVALID_COOKIE},
title
=>
$self
->_get_title(
'cookie_error'
),
cookie
=>
$self
->_empty_cookie,
forward
=> 1,
);
}
}
else
{
$self
->_screen(
content
=>
$self
->_login_form,
title
=>
$self
->_get_title(
'login_form'
),
);
}
return
;
}
sub
_compile_template {
my
$self
=
shift
;
my
$key
=
shift
;
my
$code
=
$self
->{
'template_'
.
$key
};
return
if
!
$code
;
$code
=~ s{<\?(?:\s+|)(\w+)(?:\s+|)\?>}
{
my
$param
=
lc
$1;
if
(
$param
!~ m{\W}xms &&
exists
$self
->{
$param
} ) {
$self
->{
$param
};
}
}xmsge;
return
$code
;
}
sub
_get_title {
my
$self
=
shift
;
my
$key
=
shift
or
return
;
return
$self
->{_TEMPLATE_TITLE_USER}{
'title_'
.
$key
}
||
$self
->{_TEMPLATE_TITLE}{
'title_'
.
$key
};
}
sub
set_template {
my
(
$self
,
@args
) =
@_
;
$self
->_fatal(
$ERROR
{INVALID_OPTION})
if
@args
% 2;
my
%o
=
@args
;
if
(
$o
{delete_all}) {
foreach
my
$key
(
keys
%{
$self
}) {
delete
$self
->{
$key
}
if
$key
=~ m{ \A template_ }xms;
}
$self
->{_TEMPLATE_TITLE_USER} = {};
}
else
{
foreach
my
$key
(@{
$self
->{_TEMPLATE_NAMES} }) {
$self
->{
'template_'
.
$key
} =
$o
{
$key
}
if
exists
$o
{
$key
};
}
}
return
1;
}
sub
set_title {
my
(
$self
,
@args
) =
@_
;
$self
->_fatal(
$ERROR
{INVALID_OPTION})
if
@args
% 2;
my
%o
=
@args
;
foreach
(
keys
%o
) {
next
if
!
$self
->{_TEMPLATE_TITLE}{
'title_'
.
$_
};
$self
->{_TEMPLATE_TITLE_USER}{
'title_'
.
$_
} =
$o
{
$_
};
}
return
;
}
sub
_login_form {
my
(
$self
,
@args
) =
@_
;
$self
->{page_form_error} =
shift
@args
if
@args
;
return
$self
->_compile_template(
'login_form'
) ||
<<"TEMPLATE";
<span class="error">$self->{page_form_error}</span>
<form action = "$self->{program}"
method = "post"
>
<table border = "0"
cellpadding = "0"
cellspacing = "0"
>
<tr>
<td class="darktable">
<table border = "0"
cellpadding = "4"
cellspacing = "1"
>
<tr>
<td class = "titletable"
colspan = "3"
>
You need to login to use this function
</td>
</tr>
<tr>
<td class="lighttable">
Enter <i>the</i> password to run this program:
</td>
<td class="lighttable">
<input type = "password"
name = "$self->{cookie_id}"
/>
</td>
<td class = "lighttable"
align = "right"
>
<input type = "submit"
name = "submit"
value = "Login"
/>
$self->{hidden}
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
TEMPLATE
}
sub
_change_pass_form {
my
(
$self
,
@args
) =
@_
;
$self
->{page_form_error} =
shift
@args
if
@args
;
return
$self
->_compile_template(
'change_pass_form'
) ||
<<"PASS_FORM";
qq~
<span class="error">$self->{page_form_error}</span>
<form action = "$self->{program}"
method = "post"
>
<table border = "0"
cellpadding = "0"
cellspacing = "0"
>
<tr>
<td class="darktable">
<table border = "0"
cellpadding = "4"
cellspacing = "1"
>
<tr>
<td class = "titletable"
colspan = "3"
>
Enter a password between 3 and 32 characters
and no spaces allowed!
</td>
</tr>
<tr>
<td class="lighttable">
Enter your new password:
</td>
<td class="lighttable">
<input type = "password"
name = "$self->{cookie_id}_new"
/>
</td>
<td class="lighttable"
align="right"
>
<input type = "submit"
name = "submit"
value = "Change Password"
/>
<input type = "hidden"
name = "change_password"
value = "ok"
/>
</td>
<input type = "hidden"
name = "$self->{changep_param}"
value = "1"
/>
</td>
$self->{hidden}
</tr>
</table>
</td>
</tr>
</table>
</form>
PASS_FORM
}
sub
logoff_link {
my
$self
=
shift
;
my
$query
=
$self
->{hidden_q} ?
q{&}
.
$self
->{hidden_q} : EMPTY_STRING;
if
(
$self
->{logged_in} ) {
return
$self
->_compile_template(
'logoff_link'
) ||
<<"TEMPLATE";
<span class="small">
[
<a href="$self->{program}?$self->{logoff_param}=1$query">Log-off</a>
-
<a href="$self->{program}?$self->{changep_param}=1$query">Change password</a>
]
</span>
TEMPLATE
}
return
EMPTY_STRING;
}
sub
_error {
my
$self
=
shift
;
my
$error
=
shift
;
return
$self
->_screen(
content
=> qq~<span class=
"error"
>
$error
</span>~,
title
=>
$self
->_get_title(
'error'
),
);
}
sub
_screen {
my
(
$self
,
@args
) =
@_
;
my
%p
=
@args
% 2 ? () :
@args
;
my
@cookie
=
$p
{cookie} ? (
-cookie
=>
$p
{cookie}) : ();
my
$refresh_url
;
if
(
$self
->{hidden_q} ) {
$refresh_url
=
"$self->{program}?$self->{hidden_q}"
;
}
else
{
my
@qs
;
foreach
my
$p
(
$self
->{cgi}->param ) {
next
if
$p
eq
$self
->{logoff_param}
||
$p
eq
$self
->{changep_param}
||
$p
eq
$self
->{cookie_id}
||
$p
eq
$self
->{cookie_id} .
'_new'
;
push
@qs
,
$p
.
q{=}
.
$self
->{cgi}->param(
$p
);
}
my
$url
=
$self
->{program};
if
(
@qs
) {
$url
=~ s{\?}{}xmsg;
$url
.=
q{?}
.
join
q{&}
,
@qs
;
}
$refresh_url
=
$url
;
}
$self
->{page_logoff_link} =
$self
->logoff_link;
$self
->{page_content} =
$p
{content};
$self
->{page_title} =
$p
{title};
$self
->{page_refresh} =
$p
{forward}
?
qq~<meta http-equiv="refresh" content="0; url=$refresh_url">~
: EMPTY_STRING
;
$self
->{page_inline_refresh} =
$p
{forward}
?
qq~<a href="$refresh_url">»</a>~
: EMPTY_STRING
;
my
$out
=
$self
->_compile_template(
'screen'
) ||
<<"MAIN_TEMPLATE";
<html>
<head>
$self->{page_refresh}
<title>$self->{page_title}</title>
<style>
body {font-family: Verdana, sans; font-size: 10pt}
td {font-family: Verdana, sans; font-size: 10pt}
.darktable { background: black; }
.lighttable { background: white; }
.titletable { background: #dedede; }
.error { color = red; font-weight: bold}
.small { font-size: 8pt}
</style>
</head>
<body>
$self->{'page_logoff_link'}
$self->{'page_content'}
$self->{'page_inline_refresh'}
</body>
</html>
MAIN_TEMPLATE
my
$header
=
$self
->{cgi}->header(
-charset
=>
$self
->{http_charset},
@cookie
);
my
$pok
=
print
$header
.
$out
;
return
$self
->_exit_program;
}
sub
fatal_header {
my
(
$self
,
@args
) =
@_
;
$FATAL_HEADER
=
shift
@args
if
@args
;
return
$FATAL_HEADER
||
qq~Content-Type: text/html; charset=ISO-8859-1\n\n~
;
}
sub
_fatal {
my
$self
=
shift
;
my
$error
=
shift
|| EMPTY_STRING;
my
@rep
=
caller
0;
my
@caller
=
caller
1;
$rep
[1] =~ s{.*[\\/]}{}xms;
$caller
[1] =~ s{.*[\\/]}{}xms;
my
$class
=
ref
$self
;
my
$version
=
$self
->VERSION;
my
$fatal
=
$self
->fatal_header;
$fatal
.=
<<"FATAL";
<html>
<head>
<title>Flawless Victory</title>
<style>
body {font-family: Verdana, sans; font-size: 11pt}
.error { color : red }
.finfo { color : gray}
</style>
</head>
<body>
<h1>$class $version - Fatal Error</h1>
<span class="error">$error</span>
<br>
<br>
<span class="finfo">Program terminated at <b>$caller[1]</b>
(package <b>$caller[0]</b>) line <b>$caller[2]</b>.
<br>
Error occurred in <b>$rep[0]</b> line <b>$rep[2]</b>.
</span>
</body>
</html>
FATAL
my
$pok
=
print
$fatal
;
return
$self
->_exit_program;
}
sub
_match_pass {
my
$self
=
shift
;
my
$form
=
shift
;
return
crypt
(
$form
,
substr
$self
->{password}, 0, 2 ) eq
$self
->{password};
}
sub
_encode {
my
$self
=
shift
;
my
$plain
=
shift
;
my
$salt
=
join
EMPTY_STRING, (CRYP_CHARS)[
rand
RANDOM_NUM,
rand
RANDOM_NUM ];
return
crypt
$plain
,
$salt
;
}
sub
_empty_cookie {
my
$self
=
shift
;
return
$self
->{cgi}->cookie(
-name
=>
$self
->{cookie_id},
-value
=> EMPTY_STRING,
-expires
=>
'-10y'
,
)
}
sub
_exit_program {
my
$exit
=
shift
->{EXIT_PROGRAM};
return
$exit
?
$exit
->() :
exit
;
}
1;