[LON-CAPA-cvs] cvs: loncom / lond /interface lonpreferences.pm resetpw.pm /lonnet/perl lonnet.pm

raeburn lon-capa-cvs@mail.lon-capa.org
Mon, 23 Oct 2006 21:22:52 -0000


This is a MIME encoded message

--raeburn1161638572
Content-Type: text/plain

raeburn		Mon Oct 23 17:22:52 2006 EDT

  Added files:                 
    /loncom/interface	resetpw.pm 

  Modified files:              
    /loncom	lond 
    /loncom/lonnet/perl	lonnet.pm 
    /loncom/interface	lonpreferences.pm 
  Log:
  Allow internally authenticated users who have forgotten their password access to set a new one based on a token sent to an e-mail account associated with their LON-CAPA account. Future mods: strengthen challenge by asking for student ID? Use less deterministic token ID from lond::tmpput() - MD5 hash?
  
  
--raeburn1161638572
Content-Type: text/plain
Content-Disposition: attachment; filename="raeburn-20061023172252.txt"

Index: loncom/lond
diff -u loncom/lond:1.345 loncom/lond:1.346
--- loncom/lond:1.345	Mon Oct 16 15:18:11 2006
+++ loncom/lond	Mon Oct 23 17:22:38 2006
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.345 2006/10/16 19:18:11 raeburn Exp $
+# $Id: lond,v 1.346 2006/10/23 21:22:38 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -59,7 +59,7 @@
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.345 $'; #' stupid emacs
+my $VERSION='$Revision: 1.346 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -1574,17 +1574,24 @@
     #  uname - Username.
     #  upass - Current password.
     #  npass - New password.
+    #  context - Context in which this was called 
+    #            (preferences or reset_by_email).
    
-    my ($udom,$uname,$upass,$npass)=split(/:/,$tail);
+    my ($udom,$uname,$upass,$npass,$context)=split(/:/,$tail);
 
     $upass=&unescape($upass);
     $npass=&unescape($npass);
     &Debug("Trying to change password for $uname");
 
     # First require that the user can be authenticated with their
-    # old password:
-
-    my $validated = &validate_user($udom, $uname, $upass);
+    # old password unless context was 'reset_by_email':
+    
+    my $validated;
+    if ($context eq 'reset_by_email') {
+        $validated = 1;
+    } else {
+        $validated = &validate_user($udom, $uname, $upass);
+    }
     if($validated) {
 	my $realpasswd  = &get_auth_type($udom, $uname); # Defined since authd.
 	
@@ -1603,7 +1610,7 @@
 			 ."to change password");
 		&Failure( $client, "non_authorized\n",$userinput);
 	    }
-	} elsif ($howpwd eq 'unix') {
+	} elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') {
 	    my $result = &change_unix_password($uname, $npass);
 	    &logthis("Result of password change for $uname: ".
 		     $result);
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.798 loncom/lonnet/perl/lonnet.pm:1.799
--- loncom/lonnet/perl/lonnet.pm:1.798	Thu Oct 19 20:34:45 2006
+++ loncom/lonnet/perl/lonnet.pm	Mon Oct 23 17:22:44 2006
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.798 2006/10/20 00:34:45 raeburn Exp $
+# $Id: lonnet.pm,v 1.799 2006/10/23 21:22:44 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -536,10 +536,10 @@
 # --------------------------------------------- Try to change a user's password
 
 sub changepass {
-    my ($uname,$udom,$currentpass,$newpass,$server)=@_;
+    my ($uname,$udom,$currentpass,$newpass,$server,$context)=@_;
     $currentpass = &escape($currentpass);
     $newpass     = &escape($newpass);
-    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass",
+    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context",
 		       $server);
     if (! $answer) {
 	&logthis("No reply on password change request to $server ".
Index: loncom/interface/lonpreferences.pm
diff -u loncom/interface/lonpreferences.pm:1.93 loncom/interface/lonpreferences.pm:1.94
--- loncom/interface/lonpreferences.pm:1.93	Mon Jun 26 14:56:50 2006
+++ loncom/interface/lonpreferences.pm	Mon Oct 23 17:22:51 2006
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Preferences
 #
-# $Id: lonpreferences.pm,v 1.93 2006/06/26 18:56:50 albertel Exp $
+# $Id: lonpreferences.pm,v 1.94 2006/10/23 21:22:51 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -718,14 +718,41 @@
 #            password handler subroutines            #
 ######################################################
 sub passwordchanger {
+    my ($r,$errormessage,$caller,$mailtoken) = @_;
     # This function is a bit of a mess....
     # Passwords are encrypted using londes.js (DES encryption)
-    my $r = shift;
-    my $errormessage = shift;
     $errormessage = ($errormessage || '');
-    my $user       = $env{'user.name'};
-    my $domain     = $env{'user.domain'};
-    my $homeserver = $env{'user.home'};
+    my ($user,$domain,$currentpass,$defdom);
+    if ((!defined($caller)) || ($caller eq 'preferences')) {
+        $user = $env{'user.name'};
+        $domain = $env{'user.domain'};
+        if (!defined($caller)) {
+            $caller = 'preferences';
+        }
+    } elsif ($caller eq 'reset_by_email') {
+            $defdom = $r->dir_config('lonDefDomain');
+            my %data = &Apache::lonnet::tmpget($mailtoken);
+            if (keys(%data) == 0) {
+                $r->print(&mt('Sorry, the URL you provided to complete the reset of your password was invalid.  Either the token included in the URL has been deleted or the URL you provided was invalid. Please submit a <a href="/adm/resetpw">new request</a> for a password reset, and follow the link to the new URL included in the e-mail that will be sent to you, to allow you to enter a new password.'));
+                return;
+            }
+            if (defined($data{time})) {
+                if (time - $data{'time'} < 7200) {
+                    $user = $data{'username'};
+                    $domain = $data{'domain'};
+                    $currentpass = $data{'temppasswd'};
+                } else {
+                    $r->print(&mt('Sorry, the token generated when you requested a password reset has expired.').'<br />');
+                    return;
+                }
+            } else {
+                $r->print(&mt('Sorry, the URL generated when you requested reset of your password contained incomplete information.').'<br />');
+                return;
+            }
+   } else {
+        $r->print(&mt('Page requested in unexpected context').'<br />');
+        return;
+    }
     my $currentauth=&Apache::lonnet::queryauthenticate($user,$domain);
     # Check for authentication types that allow changing of the password.
     return if ($currentauth !~ /^(unix|internal):/);
@@ -742,12 +769,13 @@
 				       .$ukey_npass2 . $lkey_npass2,
 				       $lonhost);
     # Hexify the keys for output as javascript variables
-    $ukey_cpass = hex($ukey_cpass);
-    $lkey_cpass = hex($lkey_cpass);
-    $ukey_npass1= hex($ukey_npass1);
-    $lkey_npass1= hex($lkey_npass1);
-    $ukey_npass2= hex($ukey_npass2);
-    $lkey_npass2= hex($lkey_npass2);
+    my %hexkey;
+    $hexkey{'ukey_cpass'}  = hex($ukey_cpass);
+    $hexkey{'lkey_cpass'}  = hex($lkey_cpass);
+    $hexkey{'ukey_npass1'} = hex($ukey_npass1);
+    $hexkey{'lkey_npass1'} = hex($lkey_npass1);
+    $hexkey{'ukey_npass2'} = hex($ukey_npass2);
+    $hexkey{'lkey_npass2'} = hex($lkey_npass2);
     # Output javascript to deal with passwords
     # Output DES javascript
     {
@@ -755,7 +783,25 @@
 	my $jsh=Apache::File->new($include."/londes.js");
 	$r->print(<$jsh>);
     }
+    $r->print(&jscript_send($caller));
     $r->print(<<ENDFORM);
+$errormessage
+
+<p>
+<!-- We separate the forms into 'server' and 'client' in order to
+     ensure that unencrypted passwords will not be sent out by a
+     crappy browser -->
+ENDFORM
+    $r->print(&server_form($logtoken,$caller,$mailtoken));
+    $r->print(&client_form($caller,\%hexkey,$currentpass,$defdom));
+
+    #
+    return;
+}
+
+sub jscript_send {
+    my ($caller) = @_;
+    my $output = qq|
 <script language="JavaScript">
 
     function send() {
@@ -777,30 +823,51 @@
         initkeys();
         this.document.pserver.elements.newpass_2.value
             =crypted(this.document.client.elements.newpass_2.value);
-
+|;
+    if ($caller eq 'reset_by_email') {
+        $output .= qq|
+        this.document.pserver.elements.uname.value =
+                   this.document.client.elements.uname.value;
+        this.document.pserver.elements.udom.value =
+                   this.document.client.elements.udom.options[this.document.client.elements.udom.selectedIndex].value;
+|;
+    }
+    $ output .= qq|
         this.document.pserver.submit();
     }
-
 </script>
-$errormessage
-
-<p>
-<!-- We separate the forms into 'server' and 'client' in order to
-     ensure that unencrypted passwords will not be sent out by a
-     crappy browser -->
-
-<form name="pserver" action="/adm/preferences" method="post">
-<input type="hidden" name="logtoken"    value="$logtoken" />
-<input type="hidden" name="action"      value="verify_and_change_pass" />
-<input type="hidden" name="currentpass" value="" />
-<input type="hidden" name="newpass_1"   value="" />
-<input type="hidden" name="newpass_2"   value="" />
-</form>
+|;
+}
 
+sub client_form {
+    my ($caller,$hexkey,$currentpass,$defdom) = @_;
+    my $output = qq|
 <form name="client" >
 <table>
+|;
+    if ($caller eq 'reset_by_email') {
+        $output .= qq|
+<tr><td align="right"> E-mail address:                        </td>
+    <td><input type="text" name="email" size="15" /> </td></tr>
+<tr><td align="right"> Username:                        </td>
+    <td>
+     <input type="text" name="uname" size="10" />
+     <input type="hidden" name="currentpass" value="$currentpass" />
+    </td></tr>
+<tr><td align="right"> Domain:                               </td>
+    <td>
+|;
+        $output .= &Apache::loncommon::select_dom_form($defdom,'udom').'
+   </td>
+</tr>
+';
+    } else {
+        $output .= qq|
 <tr><td align="right"> Current password:                      </td>
     <td><input type="password" name="currentpass" size="10"/> </td></tr>
+|;
+    }
+    $output .= <<"ENDFORM";
 <tr><td align="right"> New password:                          </td>
     <td><input type="password" name="newpass_1" size="10"  /> </td></tr>
 <tr><td align="right"> Confirm password:                      </td>
@@ -808,27 +875,83 @@
 <tr><td colspan="2" align="center">
     <input type="button" value="Change Password" onClick="send();">
 </table>
-<input type="hidden" name="ukey_cpass"  value="$ukey_cpass" />
-<input type="hidden" name="lkey_cpass"  value="$lkey_cpass" />
-<input type="hidden" name="ukey_npass1" value="$ukey_npass1" />
-<input type="hidden" name="lkey_npass1" value="$lkey_npass1" />
-<input type="hidden" name="ukey_npass2" value="$ukey_npass2" />
-<input type="hidden" name="lkey_npass2" value="$lkey_npass2" />
+<input type="hidden" name="ukey_cpass"  value="$hexkey->{'ukey_cpass'}" />
+<input type="hidden" name="lkey_cpass"  value="$hexkey->{'lkey_cpass'}" />
+<input type="hidden" name="ukey_npass1" value="$hexkey->{'ukey_npass1'}" />
+<input type="hidden" name="lkey_npass1" value="$hexkey->{'lkey_npass1'}" />
+<input type="hidden" name="ukey_npass2" value="$hexkey->{'ukey_npass2'}" />
+<input type="hidden" name="lkey_npass2" value="$hexkey->{'lkey_npass2'}" />
 </form>
 </p>
 ENDFORM
-    #
-    return;
+    return $output;
+}
+
+sub server_form {
+    my ($logtoken,$caller,$mailtoken) = @_;
+    my $action = '/adm/preferences';
+    if ($caller eq 'reset_by_email') {
+        $action = '/adm/resetpw';
+    }
+    my $output = qq|
+<form name="pserver" action="$action" method="post">
+<input type="hidden" name="logtoken"    value="$logtoken" />
+<input type="hidden" name="currentpass" value="" />
+<input type="hidden" name="newpass_1"   value="" />
+<input type="hidden" name="newpass_2"   value="" />
+    |;
+    if ($caller eq 'reset_by_email') {
+        $output .=  qq|
+<input type="hidden" name="token"   value="$mailtoken" />
+<input type="hidden" name="uname"   value="" />
+<input type="hidden" name="udom"   value="" />
+
+|;
+    }
+    $output .= qq|
+<input type="hidden" name="action" value="verify_and_change_pass" />
+</form>
+|;
+    return $output;
 }
 
 sub verify_and_change_password {
-    my $r = shift;
-    my $user       = $env{'user.name'};
-    my $domain     = $env{'user.domain'};
-    my $homeserver = $env{'user.home'};
+    my ($r,$caller,$mailtoken) = @_;
+    my ($user,$domain,$homeserver);
+    if ($caller eq 'reset_by_email') {
+        $user       = $env{'form.uname'};
+        $domain     = $env{'form.udom'};
+        if ($user ne '' && $domain ne '') {
+            $homeserver = &Apache::lonnet::homeserver($user,$domain);
+            if ($homeserver eq 'no_host') {
+        &passwordchanger($r,"<p>\n<font color='#ff0000'>ERROR</font>".
+                         "Invalid username and/or domain .\n</p>",
+                         $caller,$mailtoken);
+                return 1;
+            }
+        } else {
+            &passwordchanger($r,"<p>\n<font color='#ff0000'>ERROR</font>".
+                             "Username and Domain were blank.\n</p>",
+                             $caller,$mailtoken);
+            return 1;
+        }
+    } else {
+        $user       = $env{'user.name'};
+        $domain     = $env{'user.domain'};
+        $homeserver = $env{'user.home'};
+    }
     my $currentauth=&Apache::lonnet::queryauthenticate($user,$domain);
     # Check for authentication types that allow changing of the password.
-    return if ($currentauth !~ /^(unix|internal):/);
+    if ($currentauth !~ /^(unix|internal):/) {
+        if ($caller eq 'reset_by_email') {
+            &passwordchanger($r,"<p>\n<font color='#ff0000'>ERROR</font>".
+                             "Authentication type for this user can not be changed by this mechanism..\n</p>",
+                              $caller,$mailtoken);
+            return 1;
+        } else {
+            return;
+        }
+    }
     #
     my $currentpass = $env{'form.currentpass'}; 
     my $newpass1    = $env{'form.newpass_1'}; 
@@ -839,7 +962,7 @@
 	    defined($newpass1)    && 
 	    defined($newpass2)    ){
 	&passwordchanger($r,"<p>\n<font color='#ff0000'>ERROR</font>".
-			 "Password data was blank.\n</p>");
+			 "One or more password fields were blank.\n</p>",$caller,$mailtoken);
 	return;
     }
     # Get the keys
@@ -847,10 +970,14 @@
     my $tmpinfo = Apache::lonnet::reply('tmpget:'.$logtoken,$lonhost);
     if (($tmpinfo=~/^error/) || ($tmpinfo eq 'con_lost')) {
         # I do not a have a better idea about how to handle this
+        my $tryagain_text = &mt('Please log out and try again.');
+        if ($caller eq 'reset_by_email') {
+            $tryagain_text = &mt('Please try again later.');
+        }
 	$r->print(<<ENDERROR);
 <p>
 <font color="#ff0000">ERROR:</font> Unable to retrieve stored token for
-password decryption.  Please log out and try again.
+password decryption.  $tryagain_text
 </p>
 ENDERROR
         # Probably should log an error here
@@ -861,19 +988,29 @@
     $currentpass = &des_decrypt($ckey ,$currentpass);
     $newpass1    = &des_decrypt($n1key,$newpass1);
     $newpass2    = &des_decrypt($n2key,$newpass2);
-    # 
+    #
+    if ($caller eq 'reset_by_email') {
+        my %data = &Apache::lonnet::tmpget($mailtoken);
+        if ($currentpass ne $data{'temppasswd'}) {
+            &passwordchanger($r,
+                         '<font color="#ff0000">ERROR:</font>'.
+                         'Could not verify current authentication.  '.
+                         'Please try again.',$caller,$mailtoken);
+            return 1;
+        }
+    } 
     if ($newpass1 ne $newpass2) {
 	&passwordchanger($r,
 			 '<font color="#ff0000">ERROR:</font>'.
 			 'The new passwords you entered do not match.  '.
-			 'Please try again.');
+			 'Please try again.',$caller,$mailtoken);
 	return 1;
     }
     if (length($newpass1) < 7) {
 	&passwordchanger($r,
 			 '<font color="#ff0000">ERROR:</font>'.
 			 'Passwords must be a minimum of 7 characters long.  '.
-			 'Please try again.');
+			 'Please try again.',$caller,$mailtoken);
 	return 1;
     }
     #
@@ -884,7 +1021,7 @@
     }
     if ($badpassword) {
 	# I can't figure out how to enter bad characters on my browser.
-	&passwordchanger($r,<<ENDERROR);
+	my $errormessage = <<"ENDERROR";
 <font color="#ff0000">ERROR:</font>
 The password you entered contained illegal characters.<br />
 Valid characters are: space and <br />
@@ -893,20 +1030,22 @@
 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_\`abcdefghijklmnopqrstuvwxyz{|}~
 </pre>
 ENDERROR
+        &passwordchanger($r,$errormessage,$caller,$mailtoken);
+        return 1;
     }
     # 
     # Change the password (finally)
     my $result = &Apache::lonnet::changepass
-	($user,$domain,$currentpass,$newpass1,$homeserver);
+	($user,$domain,$currentpass,$newpass1,$homeserver,$caller);
     # Inform the user the password has (not?) been changed
     if ($result =~ /^ok$/) {
 	$r->print(<<"ENDTEXT");
-<h2>The password for $user was successfully changed</h2>
+<h3>The password for $user was successfully changed</h3>
 ENDTEXT
     } else {
 	# error error: run in circles, scream and shout
         $r->print(<<ENDERROR);
-<h2><font color="#ff0000">The password for $user was not changed</font></h2>
+<h3><font color="#ff0000">The password for $user was not changed</font></h3>
 Please make sure your old password was entered correctly.
 ENDERROR
         return 1;

Index: loncom/interface/resetpw.pm
+++ loncom/interface/resetpw.pm
# The LearningOnline Network
# Allow access to password changing via a token sent to user's e-mail. 
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
#
package Apache::resetpw;

use strict;
use Apache::Constants qw(:common);
use Apache::lonacc;
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonlocal;
use lib '/home/httpd/lib/perl/';
use LONCAPA;

sub handler {
    my $r = shift;
    &Apache::loncommon::content_type($r,'text/html');
    $r->send_http_header;
    if ($r->header_only) {
        return OK;
    }
    my $start_page =
        &Apache::loncommon::start_page('Reset password','',
                                           {
                                             'no_inline_link'   => 1,});
    $r->print($start_page);
    my $contact_name = &mt('LON-CAPA helpdesk');
    my $contact_email =  $r->dir_config('lonSupportEMail');
    my $server = $r->dir_config('lonHostID');
    my $defdom = $r->dir_config('lonDefDomain');
    &Apache::lonacc::get_posted_cgi($r);
    &Apache::lonlocal::get_language_handle($r);
    &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['token']);
    
    my @emailtypes = ('permanentemail','critnotification','notification');
    my $uname = $env{'form.uname'};
    my $udom = $env{'form.udom'};
    $uname =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
    my $token = $env{'form.token'};
    $r->print(&mt('<h3>Reset forgotten LON-CAPA password</h3>'));
    my $output;
    if ($token) {
        $output = &reset_passwd($r,$token,$contact_name,$contact_email);
    } elsif ($uname && $udom) {
        my $domdesc = $Apache::lonnet::domaindescription{$udom};
        my $authtype = &Apache::lonnet::queryauthenticate($uname,$udom);
        if ($authtype =~ /^internal/) {
            my %userinfo = 
            &Apache::lonnet::get('environment',\@emailtypes,$udom,$uname);
            my $email = '';
            my $emailtarget;
            foreach my $type (@emailtypes) {
                $email = $userinfo{$type};
                if ($email =~ /[^\@]+\@[^\@]+/) {
                    $emailtarget = $type; 
                    last;
                }
            }
            if ($email =~ /[^\@]+\@[^\@]+/) {
                $output = &send_token($uname,$udom,$email,$server,$domdesc,
                                      $contact_name,$contact_email);
            } else {
                $output = &invalid_state('missing',$domdesc,
                                         $contact_name,$contact_email);
            }
        } elsif ($authtype =~ /^(krb|unix|local)/) { 
            $output = &invalid_state('authentication',$domdesc,
                                     $contact_name,$contact_email);
        } else {
            $output = &invalid_state('invalid',$domdesc,
                                     $contact_name,$contact_email);
        }
    } else {
        $output = &get_uname($defdom);
    }
    $r->print($output);
    $r->print(&Apache::loncommon::end_page());
    return OK;
}

sub get_uname {
    my ($defdom) = @_;
    my %lt = &Apache::lonlocal::texthash(
                                         unam => 'username',
                                         udom => 'domain',
                                         proc => 'Proceed');

    my $msg = &mt('If you use the same account for other campus services besides LON-CAPA, (e.g., e-mail, course registration, etc.), a separate centrally managed mechanism likely exists to reset a password.  However, if your account is used for just LON-CAPA access you will probably be able to reset a password from this page.');
    $msg .= '<br /><br />'.&mt('Three conditions must be met:<ul><li>An e-mail address must have previously been associated with your LON-CAPA username.</li><li>You must be able to access e-mail sent to that address.</li><li>Your account must be of a type for which LON-CAPA can reset a password.</ul>');
    $msg .= qq|
<form name="forgotpw" method="post">
<table>
<tr><td>
<tr><td align="left">LON-CAPA $lt{'unam'}:                      </td>
    <td><input type="text" name="uname" size="10"  /> </td></tr>
<tr><td align="left">LON-CAPA $lt{'udom'}:                      </td>
    <td>|;
    $msg .= &Apache::loncommon::select_dom_form($defdom,'udom');
    $msg .= qq|</td></tr>
<tr><td colspan="2" align="left"><br />
    <input type="button" value="$lt{'proc'}" onClick="document.forgotpw.submit()"></td></tr>
</table>
|;
    return $msg;
}

sub send_token {
    my ($uname,$udom,$email,$server,$domdesc,$contact_name,
        $contact_email) = @_;
#    my $token = MD5->hexhash(MD5->hexhash(time.{}.rand().$$));
    my $msg = &mt('Thank you for your request to reset the password for your
        LON-CAPA account.').'<br /><br />';

    my $now = time;
    my $temppasswd = &create_passwd();
    my %info = ('ip'  => $ENV{'REMOTE_ADDR'},
              'time'     => $now,
              'domain'   => $udom,
              'username' => $uname,
              'email'    => $email,
              'temppasswd' => $temppasswd);

    my $token = &Apache::lonnet::tmpput(\%info,$server);
    if ($token !~ /^error/) {
        my $esc_token = $token;
        $esc_token =~ s/(\W)/"%".unpack('H2',$1)/eg;
        my $mailmsg = "A request was submitted on ".localtime(time)." for a reset of the ".
             "password for your LON-CAPA account.".
             "To complete this process please open a web browser and enter the following ".
             "URL in the address/location box: ".&Apache::lonnet::absolute_url()."/adm/resetpw?token=$esc_token";
        my $result = &send_mail($domdesc,$email,$mailmsg,$contact_name,
                                $contact_email);
        if ($result eq 'ok') {
            $msg .= &mt("An e-mail message sent to <b>$email</b> includes the web address for the link you should use to complete the reset process.<br /><br />The link included in the message will be valid for the next <b>two</b> hours.");
        } else {
            $msg .= ("An error occurred when sending e-mail to $email with information about the reset process. Please contact the $contact_name ($contact_email) for assistance.");
        }
    } else {
        $msg .= &mt("An error occurred creating a token required for the password reset process. Please contact the $contact_name ($contact_email) for assistance.");
    }
    return $msg;
}

sub send_mail {
    my ($domdesc,$email,$mailmsg,$contact_name,$contact_email) = @_;
    my $outcome;
    my $requestmail = "To: $email\n".
                      "From: $contact_name <$contact_email>\n".
                      "Subject: ".&mt('Your LON-CAPA account')."\n".
                      "\n\n".$mailmsg."\n\n".
                      &mt('[_1] LON-CAPA support team',$domdesc)."\n".
                      "$contact_email\n";
    if (open(MAIL, "|/usr/lib/sendmail -oi -t -odb")) {
        print MAIL $requestmail;
        close(MAIL);
        $outcome = 'ok';
    } else {
        $outcome = 'fail';
    }
    return $outcome;
}

sub invalid_state {
    my ($error,$domdesc,$contact_name,$contact_email) = @_;
    my $msg;
    if ($error eq 'invalid') {
        $msg = &mt('The username you provided was not verified as a valid username in the LON-CAPA system for the [_1] domain.',$domdesc).&mt(' Please <a href="javascript:history.go(-1)"><u>go back</u></a> and try again.');
    } else {
        if ($error eq 'missing') {
            $msg = &mt('A valid e-mail address was not located in the LON-CAPA system for the username and domain you provided.');
        } elsif ($error eq 'authentication') {
            $msg = &mt('The username you provided uses an authentication type which can not be reset directly via LON-CAPA.');
        }
        if ($contact_email ne '') {
            my $escuri = &HTML::Entities::encode('/adm/resetpw','&<>"');
            $msg .= &mt(' You may wish to contact the <a href="/adm/helpdesk?origurl=[_1]">LON-CAPA helpdesk</a> for the [_2] domain.',$escuri,$domdesc);
        } else {
            $msg .= &mt(' You may wish to send an e-mail to the server administrator: [_1] for the [_2] domain.',Apache::lonnet::perlvar{'AdminEmail'},$domdesc);
        }
    }
    return $msg;
}

sub reset_passwd {
    my ($r,$token,$contact_name,$contact_email) = @_;
    my $msg;
    my %data = &Apache::lonnet::tmpget($token);
    my $now = time;
    if (keys(%data) == 0) {
        $msg = &mt('Sorry, the URL you provided to complete the reset of your password was invalid.  Either the token included in the URL has been deleted or the URL you provided was invalid. Please submit a <a href="/adm/resetpw">new request</a> for a password reset, and follow the link to the new URL included in the e-mail that will be sent to you, to allow you to enter a new password.');
        return $msg;
    }
    if (($data{'time'} =~ /^\d+$/) && 
        ($data{'username'} ne '') && 
        ($data{'domain'} ne '') && 
        ($data{'email'}  =~ /^[^\@]+\@[^\@]+$/) && 
        ($data{'temppasswd'} =~/^\w+$/)) {
        my $reqtime = localtime($data{'time'});
        if ($now - $data{'time'} < 7200) {
            if ($env{'form.action'} eq 'verify_and_change_pass') {
                my $change_failed = 
  &Apache::lonpreferences::verify_and_change_password($r,'reset_by_email',$token);
                if (!$change_failed) {
                    my $delete = &Apache::lonnet::tmpdel($token);
                    my $now = localtime(time);
                    my $domdesc = 
                        $Apache::lonnet::domaindescription{$data{'domain'}};
                    my $mailmsg = &mt('The password for your LON-CAPA account in the [_1] domain was changed [_2] from IP address: [_3].  If you did not perform this change or authorize it, please contact the [_4] ([_5]).',$domdesc,$now,$ENV{'REMOTE_ADDR'},$contact_name,$contact_email)."\n";
                    my $result = &send_mail($domdesc,$data{'email'},$mailmsg,
                                            $contact_name,$contact_email);
                    if ($result eq 'ok') {
                        $msg .= &mt('An e-mail confirming setting of the password for your LON-CAPA account has been sent to [_1].',$data{'email'});
                    } else {
                        $msg .= &mt('An error occurred when sending e-mail to [_1] confirming setting of your new password.',$data{'email'});
                    }
                    $msg .= '<br /<br />'.&mt('<a href="/adm/login">Go to the login page</a>.');
                } else {
                    $msg .= &mt('A problem occurred when attempting to reset the password for your account.  Please contact the [_1] - (<a href="mailto:[_2]">[_2]</a>) for assistance.',$contact_name,$contact_email);
                }
            } else {
                $r->print(&mt('The token included in an email sent to you [_1] has been verified, so you may now proceed to reset the password for your LON-CAPA account.',$reqtime).'<br /><br />');
                $r->print(&mt('Please enter the username and domain of the LON-CAPA account, and the associated e-mail address, for which you are setting a password. The new password must contain at least 7 characters.').' '.&mt('Your new password will be sent to the LON-CAPA server in an encrypted form.').'<br />');
                &Apache::lonpreferences::passwordchanger($r,'','reset_by_email',$token);
            }
        } else {
            $msg = &mt('Sorry, the token generated when you requested a password reset has expired. Please submit a <a href="/adm/resetpw">new request</a>, and follow the link to the web page included in the new e-mail that will be sent to you, to allow you to enter a new password.');
        }
    } else {
        $msg .= &mt('Sorry, the URL generated when you requested reset of your password contained incomplete information. Please submit a <a href="/adm/resetpw">new request</a> for a password reset, and use the new URL that will be sent to your e-mail account to complete the process.');
    }
    return $msg;
}

sub create_passwd {
    my $passwd = '';
    my @letts = ("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z");
    for (my $i=0; $i<8; $i++) {
        my $lettnum = int (rand 2);
        my $item = '';
        if ($lettnum) {
            $item = $letts[int( rand(26) )];
            my $uppercase = int(rand 2);
            if ($uppercase) {
                $item =~ tr/a-z/A-Z/;
            }
        } else {
            $item = int( rand(10) );
        }
        $passwd .= $item;
    }
    return ($passwd);
}

1;

--raeburn1161638572--