--- loncom/interface/lonpreferences.pm 2001/12/19 17:17:46 1.2 +++ loncom/interface/lonpreferences.pm 2002/09/11 18:26:41 1.13 @@ -1,7 +1,7 @@ # The LearningOnline Network # Preferences # -# $Id: lonpreferences.pm,v 1.2 2001/12/19 17:17:46 albertel Exp $ +# $Id: lonpreferences.pm,v 1.13 2002/09/11 18:26:41 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -35,31 +35,478 @@ # # 3/1 Gerd Kortemeyer # +# 2/13/02 2/14 2/15 Matthew Hall +# +# This package uses the "londes.js" javascript code. +# +# TODOs that have to be completed: +# interface with lonnet to change the password + package Apache::lonpreferences; use strict; use Apache::Constants qw(:common); +use Apache::File; +use Crypt::DES; +use DynaLoader; # for Crypt::DES version +use Apache::loncommon(); + +# +# Write lonnet::passwd to do the call below. +# Use: +# my $answer=reply("encrypt:passwd:$udom:$uname:$upass",$tryserver); +# +################################################## +# password associated functions # +################################################## +sub des_keys { + # Make a new key for DES encryption. + # Each key has two parts which are returned seperately. + # Please note: Each key must be passed through the &hex function + # before it is output to the web browser. The hex versions cannot + # be used to decrypt. + my @hexstr=('0','1','2','3','4','5','6','7', + '8','9','a','b','c','d','e','f'); + my $lkey=''; + for (0..7) { + $lkey.=$hexstr[rand(15)]; + } + my $ukey=''; + for (0..7) { + $ukey.=$hexstr[rand(15)]; + } + return ($lkey,$ukey); +} + +sub des_decrypt { + my ($key,$cyphertext) = @_; + my $keybin=pack("H16",$key); + my $cypher; + if ($Crypt::DES::VERSION>=2.03) { + $cypher=new Crypt::DES $keybin; + } else { + $cypher=new DES $keybin; + } + my $plaintext= + $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16)))); + $plaintext.= + $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16)))); + $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) ); + return $plaintext; +} + +################################################################ +# Handler subroutines # +################################################################ + +################################################################ +# Anonymous Discussion Name Change Subroutines # +################################################################ +sub screennamechanger { + my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; + my %userenv = &Apache::lonnet::get('environment',['screenname']); + my $screenname=$userenv{'screenname'}; + my $bodytag=&Apache::loncommon::bodytag( + 'Change Your Anonymous Screen Name'); + $r->print(<<ENDSCREEN); +<html> +$bodytag + +<form name="server" action="/adm/preferences" method="post"> +<input type="hidden" name="action" value="verify_and_change_screenname" /> +New screenname: +<input type="text" size="20" value="$screenname" name="screenname" /> +<input type="submit" value="Change" /> +</form> +</body> +</html> +ENDSCREEN +} +sub verify_and_change_screenname { + my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; + my $newscreen = $ENV{'form.screenname'}; + $newscreen=~s/\W//g; + my $message=''; + if ($newscreen) { + &Apache::lonnet::put('environment',{'screenname' => $newscreen}); + &Apache::lonnet::appenv('environment.screenname' => $newscreen); + $message='Set new screenname to '.$newscreen; + } else { + &Apache::lonnet::del('environment',['screenname']); + &Apache::lonnet::delenv('environment\.screenname'); + $message='Reset screenname'; + } + my $bodytag=&Apache::loncommon::bodytag( + 'Change Your Anonymous Screen Name'); + $r->print(<<ENDVCSCREEN); +<html> +$bodytag +</p> +$message +</body></html> +ENDVCSCREEN +} + +################################################################ +# Message Forward # +################################################################ + +sub msgforwardchanger { + my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; + my %userenv = &Apache::lonnet::get('environment',['msgforward']); + my $msgforward=$userenv{'msgforward'}; + my $bodytag=&Apache::loncommon::bodytag( + 'Change Your Message Forwarding'); + $r->print(<<ENDMSG); +<html> +$bodytag + +<form name="server" action="/adm/preferences" method="post"> +<input type="hidden" name="action" value="verify_and_change_msgforward" /> +New Forwarding Address(es) (<tt>user:domain,user:domain,...</tt>): +<input type="text" size="40" value="$msgforward" name="msgforward" /> +<input type="submit" value="Change" /> +</form> +</body> +</html> +ENDMSG +} + +sub verify_and_change_msgforward { + my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; + my $newscreen = ''; + my $message=''; + foreach (split(/\,/,$ENV{'form.msgforward'})) { + my ($msuser,$msdomain)=split(/[\@\:]/,$_); + $msuser=~s/\W//g; + $msdomain=~s/\W//g; + if (($msuser) && ($msdomain)) { + if (&Apache::lonnet::homeserver($msuser,$msdomain) ne 'no_host') { + $newscreen.=$msuser.':'.$msdomain.','; + } else { + $message.='No such user: '.$msuser.':'.$msdomain.'<br>'; + } + } + } + $newscreen=~s/\,$//; + if ($newscreen) { + &Apache::lonnet::put('environment',{'msgforward' => $newscreen}); + &Apache::lonnet::appenv('environment.msgforward' => $newscreen); + $message.='Set new message forwarding to '.$newscreen; + } else { + &Apache::lonnet::del('environment',['msgforward']); + &Apache::lonnet::delenv('environment\.msgforward'); + $message.='Reset message forwarding'; + } + my $bodytag=&Apache::loncommon::bodytag( + 'Change Your Message Forwarding'); + $r->print(<<ENDVCMSG); +<html> +$bodytag +</p> +$message +</body></html> +ENDVCMSG +} + +###################################################### +# password handler subroutines # +###################################################### +sub passwordchanger { + # 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 $currentauth=&Apache::lonnet::queryauthenticate($user,$domain); + # Check for authentication types that allow changing of the password. + return if ($currentauth !~ /^(unix|internal):/); + # + # Generate keys + my ($lkey_cpass ,$ukey_cpass ) = &des_keys(); + my ($lkey_npass1,$ukey_npass1) = &des_keys(); + my ($lkey_npass2,$ukey_npass2) = &des_keys(); + # Store the keys in the log files + my $lonhost = $r->dir_config('lonHostID'); + my $logtoken=Apache::lonnet::reply('tmpput:' + .$ukey_cpass . $lkey_cpass .'&' + .$ukey_npass1 . $lkey_npass1.'&' + .$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); + # Output javascript to deal with passwords + # Output DES javascript + $r->print("<html><head>"); + { + my $include = $r->dir_config('lonIncludes'); + my $jsh=Apache::File->new($include."/londes.js"); + $r->print(<$jsh>); + } + my $bodytag=&Apache::loncommon::bodytag('Change Password','', + 'onLoad="init();"'); + $r->print(<<ENDFORM); +</head> +$bodytag + +<script language="JavaScript"> + + function send() { + uextkey=this.document.client.elements.ukey_cpass.value; + lextkey=this.document.client.elements.lkey_cpass.value; + initkeys(); + + this.document.server.elements.currentpass.value + =crypted(this.document.client.elements.currentpass.value); + + uextkey=this.document.client.elements.ukey_npass1.value; + lextkey=this.document.client.elements.lkey_npass1.value; + initkeys(); + this.document.server.elements.newpass_1.value + =crypted(this.document.client.elements.newpass_1.value); + + uextkey=this.document.client.elements.ukey_npass2.value; + lextkey=this.document.client.elements.lkey_npass2.value; + initkeys(); + this.document.server.elements.newpass_2.value + =crypted(this.document.client.elements.newpass_2.value); + + this.document.server.submit(); + } + +</script> +$errormessage + +<p> +<!-- We seperate the forms into 'server' and 'client' in order to + ensure that unencrypted passwords will not be sent out by a + crappy browser --> + +<form name="server" 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> + +<form name="client" > +<table> +<tr><td align="right"> Current password: </td> + <td><input type="password" name="currentpass" size="10"/> </td></tr> +<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> + <td><input type="password" name="newpass_2" size="10" /> </td></tr> +<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" /> +</form> +</p> +ENDFORM + # + return; +} + +sub verify_and_change_password { + my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; + my $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):/); + # + $r->print(<<ENDHEADER); +<html> +<head> +<title>LON-CAPA Preferences: Change password for $user</title> +</head> +ENDHEADER + # + my $currentpass = $ENV{'form.currentpass'}; + my $newpass1 = $ENV{'form.newpass_1'}; + my $newpass2 = $ENV{'form.newpass_2'}; + my $logtoken = $ENV{'form.logtoken'}; + # Check for empty data + unless (defined($currentpass) && + defined($newpass1) && + defined($newpass2) ){ + &passwordchanger($r,"<p>\n<font color='#ff0000'>ERROR</font>". + "Password data was blank.\n</p>"); + return; + } + # Get the keys + my $lonhost = $r->dir_config('lonHostID'); + 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 + $r->print(<<ENDERROR); +<p> +<font color="#ff0000">ERROR:</font> Unable to retrieve stored token for +password decryption. Please log out and try again. +</p> +ENDERROR + # Probably should log an error here + return; + } + my ($ckey,$n1key,$n2key)=split(/&/,$tmpinfo); + # + my $currentpass = &des_decrypt($ckey ,$currentpass); + my $newpass1 = &des_decrypt($n1key,$newpass1); + my $newpass2 = &des_decrypt($n2key,$newpass2); + # + if ($newpass1 ne $newpass2) { + &passwordchanger($r, + '<font color="#ff0000">ERROR:</font>'. + 'The new passwords you entered do not match. '. + 'Please try again.'); + return; + } + if (length($newpass1) < 7) { + &passwordchanger($r, + '<font color="#ff0000">ERROR:</font>'. + 'Passwords must be a minimum of 7 characters long. '. + 'Please try again.'); + return; + } + # + # Check for bad characters + my $badpassword = 0; + foreach (split(//,$newpass1)) { + $badpassword = 1 if ((ord($_)<32)||(ord($_)>126)); + } + if ($badpassword) { + # I can't figure out how to enter bad characters on my browser. + &passwordchanger($r,<<ENDERROR); +<font color="#ff0000">ERROR:</font> +The password you entered contained illegal characters.<br /> +Valid characters are: space and <br /> +<pre> +!"\#$%&\'()*+,-./0123456789:;<=>?\@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_\`abcdefghijklmnopqrstuvwxyz{|}~ +</pre> +ENDERROR + } + # + # Change the password (finally) + my $result = &Apache::lonnet::changepass + ($user,$domain,$currentpass,$newpass1,$homeserver); + # Inform the user the password has (not?) been changed + if ($result =~ /^ok$/) { + $r->print(<<"ENDTEXT"); +<h2>The password for $user was successfully changed</h2> +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> +Please make sure your old password was entered correctly. +ENDERROR + } + return; +} + +###################################################### +# other handler subroutines # +###################################################### + +################################################################ +# Main handler # +################################################################ sub handler { my $r = shift; + my $user = $ENV{'user.name'}; + my $domain = $ENV{'user.domain'}; $r->content_type('text/html'); + # Some pages contain DES keys and should not be cached. + &Apache::loncommon::no_cache($r); $r->send_http_header; return OK if $r->header_only; - -# --------------------------------------------------- Print login screen header - $r->print(<<ENDDOCUMENT); + # + if ($ENV{'form.action'} eq 'changepass') { + &passwordchanger($r); + } elsif ($ENV{'form.action'} eq 'verify_and_change_pass') { + &verify_and_change_password($r); + } elsif ($ENV{'form.action'} eq 'changescreenname') { + &screennamechanger($r); + } elsif ($ENV{'form.action'} eq 'verify_and_change_screenname') { + &verify_and_change_screenname($r); + } elsif ($ENV{'form.action'} eq 'changemsgforward') { + &msgforwardchanger($r); + } elsif ($ENV{'form.action'} eq 'verify_and_change_msgforward') { + &verify_and_change_msgforward($r); + } else { + $r->print(<<ENDHEADER); <html> <head> -<title>The LearningOnline Network with CAPA</title> +<title>LON-CAPA Preferences</title> </head> -<body bgcolor="#FFFFFF"> -<h1>Preferences</h1> -<img src="/adm/lonKaputt/lonconstruct.gif"> +ENDHEADER + $r->print(&Apache::loncommon::bodytag('Change Your Preferences')); + # Determine current authentication method + my $currentauth=&Apache::lonnet::queryauthenticate($user,$domain); + if ($currentauth =~ /^(unix|internal):/) { + $r->print(<<ENDPASSWORDFORM); +<form name="client" action="/adm/preferences" method="post"> +<input type="hidden" name="action" value="changepass"> +<input type="submit" value="Change password"> +</form> +ENDPASSWORDFORM + } +# Change screen name + $r->print(<<ENDSCREENNAMEFORM); +<form name="client" action="/adm/preferences" method="post"> +<input type="hidden" name="action" value="changescreenname"> +<input type="submit" value="Change anonymous discussion screen name"> +</form> +ENDSCREENNAMEFORM + $r->print(<<ENDMSGFORWARDFORM); +<form name="client" action="/adm/preferences" method="post"> +<input type="hidden" name="action" value="changemsgforward"> +<input type="submit" value="Change message forwarding address"> +</form> +ENDMSGFORWARDFORM +# The "about me" page + my $aboutmeaction= + '/adm/'.$ENV{'user.domain'}.'/'.$ENV{'user.name'}.'/aboutme'; + $r->print(<<ENDABOUTME); +<form name="client" action="$aboutmeaction" method="post"> +<input type="hidden" name="action" value="changescreenname"> +<input type="submit" value="Edit the 'About Me' Personal Information Screen"> +</form> +ENDABOUTME + # Other preference setting code should be added here + } + $r->print(<<ENDFOOTER); </body> </html> -ENDDOCUMENT +ENDFOOTER return OK; -} +} 1; __END__