1 # Copyright (C) 2004 Fletcher T. Penney <fletcher@freeshell.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the
15 # Free Software Foundation, Inc.
16 # 59 Temple Place, Suite 330
17 # Boston, MA 02111-1307 USA
19 $ModulesDescription .= '<p><a href="http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/login.pl">login.pl</a>, see <a href="http://www.oddmuse.org/cgi-bin/oddmuse/Login_Module">Login Module</a></p>';
21 #use vars qw($RegistrationForm $MinimumPasswordLength $RegistrationsMustBeApproved $LoginForm $PasswordFile $PendingPasswordFile $RequireLoginToEdit $ConfirmEmailAddress $ConfirmEmailAddress $UncomfirmedPasswordFile $EmailSenderAddress $EmailCommand $NotifyPendingRegistrations $EmailConfirmationMessage $ResetPasswordMessage $RegistrationForm $LogoutForm $ResetForm $ChangePassForm $RequireCamelUserName);
23 my $EncryptedPassword = "";
25 push(@MyAdminCode, \
&LoginAdminRule
);
27 $EmailRegExp = '[\w\.\-]+@([\w\-]+\.)+[\w]+';
28 $UsernameRegExp = '([A-Z][a-z]+){2,}';
29 $RequireCamelUserName = 0 unless defined $RequireCamelUserName;
31 $RequireLoginToEdit = 1 unless defined $RequireLoginToEdit;
32 $MinimumPasswordLength = 6 unless defined $MinimumPasswordLength;
33 $PasswordFile = "$DataDir/passwords" unless defined $PasswordFile;
35 $RegistrationsMustBeApproved = 1 unless defined $RegistrationsMustBeApproved;
36 $PendingPasswordFile = "$DataDir/pending" unless defined $PendingPasswordFile;
38 $ConfirmEmailAddress = 1 unless defined $ConfirmEmailAddress;
39 $UncomfirmedPasswordFile = "$DataDir/uncomfirmed" unless defined $UncomfirmedPasswordFile;
41 $EmailSenderAddress = "fletcher\@freeshell.org" unless defined $EmailSenderAddress;
42 $EmailCommand = "/usr/sbin/sendmail -oi -t" unless defined $EmailCommand;
44 $NotifyPendingRegistrations = "fletcher\@mercury.local" unless defined $NotifyPendingRegistrations;
46 $EmailConfirmationMessage = qq!From
: $EmailSenderAddress
47 Subject
: $SiteName Registration Confirmation
49 This email address was used to create an account at
$SiteName. If you did
not register at this site
, you
do not need to
do anything
.
51 Otherwise
, in order to confirm your account
, follow the
link below
.
55 ! unless defined $EmailConfirmationMessage;
57 $ResetPasswordMessage = qq!From
: $EmailSenderAddress
58 Subject
: $SiteName Password Reset
60 We received a request to
reset your password on
our website
. Your password has been
reset (see below
). You may
log in and change to a password of your choice
.
64 ! unless defined $ResetPasswordMessage;
66 $PasswordFileToUse = $RegistrationsMustBeApproved
67 ?
$PendingPasswordFile : $PasswordFile;
69 $PasswordFileToUse = $ConfirmEmailAddress
70 ?
$UncomfirmedPasswordFile : $PasswordFileToUse;
72 $RegistrationForm = <<'EOT' unless defined $RegistrationForm;
73 <p>Your Username should be a CamelCase form of your real name, e.g. JohnDoe.</p>
75 <p>Your password must be at least 6 characters long.</p>
77 <p>Your email address must be real, as a confirmation email will be sent to you. Your email address will not be shared with anyone else, or used for any other purpose.</p>
80 <input type="hidden" name="action" value="process_registration" />
82 <tr><td class="label">
84 </td><td class="input">
85 <input type="text" name="username" value="%username%" />
87 <tr><td class="label">
89 </td><td class="input">
90 <input type="password" name="pwd1" value="" />
92 <tr><td class="label">
94 </td><td class="input">
95 <input type="password" name="pwd2" value="" />
97 <tr><td class="label">
99 </td><td class="input">
100 <input type="text" name="email" value="%email%" />
102 <tr><td colspan="2" class="button">
103 <input type="submit" value="Register" />
109 $LoginForm = <<'EOT' unless defined $LoginForm;
111 <input type="hidden" name="action" value="process_login" />
113 <tr><td class="label">
115 </td><td class="input">
116 <input type="text" name="username" value="%username%" />
118 <tr><td class="label">
120 </td><td class="input">
121 <input type="password" name="pwd" value="" />
123 <tr><td colspan="2" class="button">
124 <input type="submit" value="Login" />
130 $LogoutForm = <<'EOT' unless defined $LogoutForm;
132 <input type="hidden" name="action" value="process_logout" />
133 <input type="hidden" name="pwd" value="" />
135 <tr><td colspan="2" class="button">
136 <input type="submit" value="Logout" />
142 $ResetForm = <<'EOT' unless defined $ResetForm;
143 <p>Submit your username in order to reset your password.</p>
144 <p>A temporary password will be mailed to you.</p>
146 <input type="hidden" name="action" value="reset_password" />
147 <input type="hidden" name="pwd" value="" />
149 <tr><td class="label">
151 </td><td class="input">
152 <input type="text" name="username" value="%username%" />
154 <tr><td colspan="2" class="button">
155 <input type="submit" value="Reset" />
161 $ChangePassForm = <<'EOT' unless defined $ChangePassForm;
163 <input type="hidden" name="action" value="change_password" />
165 <tr><td class="label">
167 </td><td class="input">
168 <input type="text" name="username" value="%username%" />
170 <tr><td class="label">
172 </td><td class="input">
173 <input type="password" name="oldpwd" value="" />
175 <tr><td class="label">
177 </td><td class="input">
178 <input type="password" name="pwd1" value="" />
180 <tr><td class="label">
182 </td><td class="input">
183 <input type="password" name="pwd2" value="" />
185 <tr><td colspan="2" class="button">
186 <input type="submit" value="Submit" />
192 $Action{register
} = \
&DoRegister
;
196 print GetHeader
('', Ts
('Register for %s', $SiteName), '');
197 print '<div class="content">';
198 $RegistrationForm =~ s/\%([a-z]+)\%/GetParam($1)/ige;
199 $RegistrationForm =~ s
/\$([a-z]+)\$/$q->span({-class=>'param'}, GetParam
($1))
200 . $q->input({-type
=>'hidden', -name
=>$1, -value
=>GetParam
($1)})/ge;
201 print $RegistrationForm;
207 $Action{process_registration
} = \
&DoProcessRegistration
;
209 sub DoProcessRegistration
{
211 my $username = GetParam
('username', '');
212 my $pwd1 = GetParam
('pwd1', '');
213 my $pwd2 = GetParam
('pwd2', '');
214 my $email = GetParam
('email', '');
216 if ($RequireCamelUserName) {
217 ReportError
(T
('Please choose a username of the form "FirstLast" using your real name.'))
218 unless ($username =~ /$UsernameRegExp/);
220 ReportError
(T
('The passwords do not match.'))
221 unless ($pwd1 eq $pwd2);
222 ReportError
(Ts
('The password must be at least %s characters.', $MinimumPasswordLength))
223 unless (length($pwd1) > ($MinimumPasswordLength-1));
224 ReportError
(T
('That email address is invalid.'))
225 unless ($email =~ /$EmailRegExp/);
226 ReportError
(Ts
('The username %s has already been registered.',$username))
227 if (UserExists
($username));
229 print GetHeader
('', Ts
('Register for %s', $SiteName), '');
231 if ($RegistrationsMustBeApproved) {
232 if (AddUser
($username,$pwd1,$email,$PasswordFileToUse)) {
233 print Ts
('Your registration for %s has been submitted.', $SiteName);
235 print T
('Please allow time for the webmaster to approve your request.');
237 if ($ConfirmEmailAddress) {
238 print Ts
('An email has been sent to "%s" with further instructions.', $email);
241 SendNotification
($username);
244 ReportError
(T
('There was an error saving your registration.'));
247 if (AddUser
($username, $pwd1, $email,$PasswordFileToUse)) {
248 print Ts
('An account was created for %s.',$username);
250 if ($ConfirmEmailAddress) {
251 print Ts
('An email has been sent to "%s" with further instructions.', $email);
255 ReportError
(T
('There was an error saving your registration.'));
259 SendConfirmationEmail
($username,$email) if ($ConfirmEmailAddress);
264 $Action{login
} = \
&DoLogin
;
268 print GetHeader
('', Ts
('Login to %s', $SiteName), '');
269 print '<div class="content">';
270 $LoginForm =~ s/\%([a-z]+)\%/GetParam($1)/ge;
271 $LoginForm =~ s
/\$([a-z]+)\$/$q->span({-class=>'param'}, GetParam
($1))
272 . $q->input({-type
=>'hidden', -name
=>$1, -value
=>GetParam
($1)})/ge;
278 $Action{process_login
} = \
&DoProcessLogin
;
282 my $username = GetParam
('username', '');
283 my $pwd = GetParam
('pwd', '');
284 my $email = GetParam
('email', '');
286 ReportError
(T
('Username and/or password are incorrect.'))
287 unless (AuthenticateUser
($username,$pwd));
290 print GetHeader
('', Ts
('Register for %s', $SiteName), '');
291 print '<div class="content">';
292 print Ts
('Logged in as %s.', $username);
297 $Action{logout
} = \
&DoLogout
;
301 print GetHeader
('', Ts
('Logout of %s', $SiteName), '');
302 print '<div class="content">';
303 print '<p>' . Ts
('Logout of %s?',$SiteName) . '</p>';
304 $LogoutForm =~ s/\%([a-z]+)\%/GetParam($1)/ge;
305 $LogoutForm =~ s
/\$([a-z]+)\$/$q->span({-class=>'param'}, GetParam
($1))
306 . $q->input({-type
=>'hidden', -name
=>$1, -value
=>GetParam
($1)})/ge;
312 $Action{process_logout
} = \
&DoProcessLogout
;
314 sub DoProcessLogout
{
316 SetParam
('username','');
317 unlink($IndexFile); # I shouldn't have to do this...
318 print GetHeader
('', Ts
('Logged out of %s', $SiteName), '');
319 print '<div class="content">';
320 print T
('You are now logged out.');
326 my $username = shift;
327 if (open (PASSWD
, $PasswordFile)) {
329 if ($_ =~ /^$username:/) {
336 if ($RegistrationsMustBeApproved) {
337 if (open (PASSWD
, $PendingPasswordFile)) {
339 if ($_ =~ /^$username:/) {
347 if ($ConfirmEmailAddress) {
348 if (open (PASSWD
, $UncomfirmedPasswordFile)) {
350 if ($_ =~ /^$username:/) {
362 my ($username, $pwd, $email, $FileToUse) = @_;
364 my @salts = (a
..z
,A
..Z
,0..9,'.','/');
365 my $salt=$salts[rand @salts];
366 $salt.=$salts[rand @salts];
367 my $encrypted = crypt($pwd,$salt);
368 $EncryptedPassword = $encrypted;
374 if (open (PASSWD
, $FileToUse)) {
376 if ($_ =~ /^(.*):(.*):(.*)$/) {
384 $passwords{$username} = $encrypted;
385 $emails{$username} = $email;
387 open (PASSWD
, ">$FileToUse");
388 foreach $key ( sort keys(%passwords)) {
389 print PASSWD
"$key:$passwords{$key}:$emails{$key}\n";
397 *OldUserCanEdit
= *UserCanEdit
;
398 *UserCanEdit
= *LoginUserCanEdit
;
400 sub LoginUserCanEdit
{
401 my ($id, $editing) = @_;
403 my $user = GetParam
('username', '');
404 my $pwd = GetParam
('pwd', '');
406 if ($RequireLoginToEdit) {
407 if ($user and $pwd) {
408 # If not logged in, return 0. Otherwise, let Oddmuse d$
409 return 0 unless AuthenticateUser
($user, $pwd);
410 return OldUserCanEdit
($id, $editing);
414 return OldUserCanEdit
($id, $editing);
417 sub AuthenticateUser
{
418 my ($username, $password) = @_;
421 if (open(PASSWD
, $PasswordFile)) {
422 while ($line = <PASSWD
>) {
423 if ($line =~ /^$username:(.*):(.*)/) {
424 if (crypt($password,$1) eq $1) {
436 ($id, $menuref, *restref
) = @_;
438 push(@
$menuref, ScriptLink
('action=register', T
('Register a new account'), 'register'));
439 push(@
$menuref, ScriptLink
('action=login', T
('Login'), 'login'));
440 push(@
$menuref, ScriptLink
('action=logout', T
('Logout'), 'logout'));
441 push(@
$menuref, ScriptLink
('action=whoami', T
('Who am I?'), 'whoami'));
442 push(@
$menuref, ScriptLink
('action=reset', T
('Forgot your password?'), 'reset'));
443 push(@
$menuref, ScriptLink
('action=change', T
('Change your password'), 'change'));
446 push(@
$menuref, ScriptLink
('action=approve_pending', T
('Approve pending registrations'), 'approve'));
450 sub SendConfirmationEmail
{
451 my ($username, $email) = @_;
452 my $key = $EncryptedPassword;
453 my @salts = (a
..z
,A
..Z
,0..9,'.','/');
454 my $salt=$salts[rand @salts];
455 $salt.=$salts[rand @salts];
456 my $encrypted = crypt($key,$salt);
458 $confirmationLink = "$FullUrl?action=confirm_registration;account=$username;key=$encrypted;";
460 open (MAIL
, "| $EmailCommand");
461 print MAIL
"To: $email\n$EmailConfirmationMessage\n\nClick on the following link to confirm:\n\n$confirmationLink\n\n";
466 $Action{confirm_registration
} = \
&DoConfirmRegistration
;
468 sub DoConfirmRegistration
{
470 my $account = GetParam
('account', '');
471 my $key = GetParam
('key', '');
473 if ( ConfirmUser
($account,$key)) {
474 print GetHeader
('', Ts
('Confirm Registration for %s', $SiteName), '');
476 print Ts
('%s, your registration has been approved. You can now use your password to login and edit this wiki.',$account);
481 ReportError
(Ts
('Confirmation failed. Please email %s for help.', $EmailSenderAddress));
487 my ($username, $key) = @_;
488 my $FileToUse = $RegistrationsMustBeApproved
489 ?
$PendingPasswordFile : $PasswordFileToUse;
491 if (open(PASSWD
, $UncomfirmedPasswordFile)) {
493 if ($_ =~ /^$username:(.*):(.*)/) {
494 if (crypt($1,$key) eq $key) {
495 AddUser
($username,$1,$2,$FileToUse);
497 RemoveUser
($username,$UncomfirmedPasswordFile);
498 if ($RegistrationsMustBeApproved) {
499 SendNotification
($username);
511 my ($username, $FileToUse) = @_;
517 if (open (PASSWD
, $FileToUse)) {
519 if ($_ =~ /^(.*):(.*):(.*)$/) {
520 next if ($1 eq $username);
528 open (PASSWD
, ">$FileToUse");
529 foreach $key ( sort keys(%passwords)) {
530 print PASSWD
"$key:$passwords{$key}:$emails{$key}\n";
537 $Action{whoami
} = \
&DoWhoAmI
;
540 print GetHeader
('', T
('Who Am I?'), '');
541 my $user = GetParam
('username', '');
542 my $pwd = GetParam
('pwd', '');
544 if (AuthenticateUser
($user, $pwd)) {
545 print Ts
('You are logged in as %s.',GetParam
('username', ''));
547 print T
('You are not logged in.');
553 $Action{reset_password
} = \
&DoResetPassword
;
555 sub DoResetPassword
{
557 my $username = GetParam
('username', '');
559 if (UserExists
($username)) {
560 my ($newpass, $newhash) = newpass
();
562 my $email = ChangePassword
($username,$newhash);
565 print GetHeader
('', T
('Reset Password'), '');
566 print Ts
('The password for %s was reset. It has been emailed to the address on file.',$username);
568 SendResetEmail
($email,$newpass);
570 ReportError
(Ts
('There was an error resetting the password for %s.',$username));
573 ReportError
(Ts
('The username "%s" does not exist.',$username));
578 # Create a random password
580 my @salts = (a
..z
,A
..Z
,0..9,'.','/');
581 my $salt=$salts[rand @salts];
582 $salt.=$salts[rand @salts];
584 my $password = $salts[rand @salts];
586 for ( $i=0; $i < 7; $i++) {
587 $password .= $salts[rand @salts];
590 my $hash = crypt($password, $salt);
592 return ($password, $hash);
596 my ($user, $hash) = @_;
602 if (open (PASSWD
, $PasswordFile)) {
604 if ($_ =~ /^(.*):(.*):(.*)$/) {
612 $passwords{$user} = $hash;
614 open (PASSWD
, ">$PasswordFile");
615 foreach $key ( sort keys(%passwords)) {
616 print PASSWD
"$key:$passwords{$key}:$emails{$key}\n";
620 return $emails{$user};
623 $Action{reset} = \
&DoReset
;
627 print GetHeader
('', Ts
('Reset Password for %s', $SiteName), '');
628 print '<div class="content">';
629 print '<p>' . T
('Reset Password?') . '</p>';
630 $ResetForm =~ s/\%([a-z]+)\%/GetParam($1)/ge;
631 $ResetForm =~ s
/\$([a-z]+)\$/$q->span({-class=>'param'}, GetParam
($1))
632 . $q->input({-type
=>'hidden', -name
=>$1, -value
=>GetParam
($1)})/ge;
639 my ($email, $newpass) = @_;
641 open (MAIL
, "| $EmailCommand");
642 print MAIL
"To: $email\n$EmailConfirmationMessage\n\nYour new temporary password:\n\n$newpass\n\n";
648 $Action{change
} = \
&DoChangePassword
;
650 sub DoChangePassword
{
652 print GetHeader
('', Ts
('Change Password for %s', $SiteName), '');
653 print '<div class="content">';
654 print '<p>' . T
('Change Password?') . '</p>';
655 $ChangePassForm =~ s/\%([a-z]+)\%/GetParam($1)/ge;
656 $ChangePassForm =~ s
/\$([a-z]+)\$/$q->span({-class=>'param'}, GetParam
($1))
657 . $q->input({-type
=>'hidden', -name
=>$1, -value
=>GetParam
($1)})/ge;
658 print $ChangePassForm;
663 $Action{change_password
} = \
&DoProcessChangePassword
;
665 sub DoProcessChangePassword
{
667 my $username = GetParam
('username', '');
668 my $pwd1 = GetParam
('pwd1', '');
669 my $pwd2 = GetParam
('pwd2', '');
670 my $oldpwd = GetParam
('oldpwd', '');
672 ReportError
(T
('Your current password is incorrect.')) if
673 (! AuthenticateUser
($username,$oldpwd));
675 ReportError
(T
('The passwords do not match.'))
676 unless ($pwd1 eq $pwd2);
677 ReportError
(Ts
('The password must be at least %s characters.', $MinimumPasswordLength))
678 unless (length($pwd1) > ($MinimumPasswordLength-1));
680 print GetHeader
('', Ts
('Register for %s', $SiteName), '');
682 my @salts = (a
..z
,A
..Z
,0..9,'.','/');
683 my $salt=$salts[rand @salts];
684 $salt.=$salts[rand @salts];
685 my $encrypted = crypt($pwd1,$salt);
687 ChangePassword
($username,$encrypted);
689 print T
('Your password has been changed.');
693 sub SendNotification
{
696 open (MAIL
, "| $EmailCommand");
697 print MAIL
"To: $NotifyPendingRegistrations\nFrom: $EmailSenderAddress\nSubject: New User at $SiteName\n\nYou have a new pending registration at $SiteName:\n\n$NewUser\n\n";
702 $Action{approve_pending
} = \
&DoApprovePending
;
704 sub DoApprovePending
{
708 my $ToBeApproved = GetParam
('user','');
710 UserIsAdminOrError
();
712 print GetHeader
('', Ts
('Approve Pending Registrations for %s', $SiteName), '');
715 if (ApproveUser
($ToBeApproved)) {
716 print Ts
('%s has been approved.',$ToBeApproved);
718 print Ts
('There was an error approving %s.',$ToBeApproved);
722 if (open(PASSWD
, $PendingPasswordFile)) {
724 if ($_ =~ /^(.*):(.*):(.*)$/) {
725 print Tss
('<li>%1 - %2</li>',ScriptLink
("action=approve_pending;user=$1;",$1),"$3");
733 print T
('There are no pending registrations.');
744 if (open(PASSWD
, $PendingPasswordFile)) {
746 if ($_ =~ /^$username:(.*):(.*)/) {
747 AddUser
($username,$1,$2,$PasswordFile);
749 RemoveUser
($username,$PendingPasswordFile);