--- loncom/lonnet/perl/lonnet.pm	2002/02/14 20:44:26	1.201
+++ loncom/lonnet/perl/lonnet.pm	2002/05/06 13:46:41	1.211
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.201 2002/02/14 20:44:26 albertel Exp $
+# $Id: lonnet.pm,v 1.211 2002/05/06 13:46:41 www Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -84,7 +84,7 @@ qw(%perlvar %hostname %homecache %hostip
 use IO::Socket;
 use GDBM_File;
 use Apache::Constants qw(:common :http);
-use HTML::TokeParser;
+use HTML::LCParser;
 use Fcntl qw(:flock);
 my $readit;
 
@@ -137,8 +137,24 @@ sub subreply {
 
 sub reply {
     my ($cmd,$server)=@_;
+    unless (defined($hostname{$server})) { return 'no_such_host'; }
     my $answer=subreply($cmd,$server);
-    if ($answer eq 'con_lost') { $answer=subreply($cmd,$server); }
+    if ($answer eq 'con_lost') {
+       sleep 5; 
+       $answer=subreply($cmd,$server);
+       if ($answer eq 'con_lost') {
+	   &logthis("Second attempt con_lost on $server");
+           my $peerfile="$perlvar{'lonSockDir'}/$server";
+           my $client=IO::Socket::UNIX->new(Peer    =>"$peerfile",
+                                            Type    => SOCK_STREAM,
+                                            Timeout => 10)
+                      or return "con_lost";
+           &logthis("Killing socket");
+           print $client "close_connection_exit\n";
+           sleep 5;
+           $answer=subreply($cmd,$server);       
+       }   
+    }
     if (($answer=~/^refused/) || ($answer=~/^rejected/)) {
        &logthis("<font color=blue>WARNING:".
                 " $cmd to $server returned $answer</font>");
@@ -348,6 +364,41 @@ sub spareserver {
     return $spareserver;
 }
 
+# --------------------------------------------- Try to change a user's password
+
+sub changepass {
+    my ($uname,$udom,$currentpass,$newpass,$server)=@_;
+    $currentpass = &escape($currentpass);
+    $newpass     = &escape($newpass);
+    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass",
+		       $server);
+    if (! $answer) {
+	&logthis("No reply on password change request to $server ".
+		 "by $uname in domain $udom.");
+    } elsif ($answer =~ "^ok") {
+        &logthis("$uname in $udom successfully changed their password ".
+		 "on $server.");
+    } elsif ($answer =~ "^pwchange_failure") {
+	&logthis("$uname in $udom was unable to change their password ".
+		 "on $server.  The action was blocked by either lcpasswd ".
+		 "or pwchange");
+    } elsif ($answer =~ "^non_authorized") {
+        &logthis("$uname in $udom did not get their password correct when ".
+		 "attempting to change it on $server.");
+    } elsif ($answer =~ "^auth_mode_error") {
+        &logthis("$uname in $udom attempted to change their password despite ".
+		 "not being locally or internally authenticated on $server.");
+    } elsif ($answer =~ "^unknown_user") {
+        &logthis("$uname in $udom attempted to change their password ".
+		 "on $server but were unable to because $server is not ".
+		 "their home server.");
+    } elsif ($answer =~ "^refused") {
+	&logthis("$server refused to change $uname in $udom password because ".
+		 "it was sent an unencrypted request to change the password.");
+    }
+    return $answer;
+}
+
 # ----------------------- Try to determine user's current authentication scheme
 
 sub queryauthenticate {
@@ -665,6 +716,7 @@ sub flushcourselogs {
     &logthis('Flushing course log buffers');
     foreach (keys %courselogs) {
         my $crsid=$_;
+	&logthis(":$crsid:$coursehombuf{$crsid}");
         if (&reply('log:'.$coursedombuf{$crsid}.':'.
 		          &escape($courselogs{$crsid}),
 		          $coursehombuf{$crsid}) eq 'ok') {
@@ -862,10 +914,55 @@ sub devalidate {
     }
 }
 
+sub arrayref2str {
+  my ($arrayref) = @_;
+  my $result='_ARRAY_REF__';
+  foreach my $elem (@$arrayref) {
+    if (ref($elem) eq 'ARRAY') {
+      $result.=&escape(&arrayref2str($elem)).'&';
+    } elsif (ref($elem) eq 'HASH') {
+      $result.=&escape(&hashref2str($elem)).'&';
+    } elsif (ref($elem)) {
+      &logthis("Got a ref of ".(ref($elem))." skipping.");
+    } else {
+      $result.=&escape($elem).'&';
+    }
+  }
+  $result=~s/\&$//;
+  return $result;
+}
+
 sub hash2str {
-  my (%hash)=@_;
-  my $result='';
-  foreach (keys %hash) { $result.=escape($_).'='.escape($hash{$_}).'&'; }
+  my (%hash) = @_;
+  my $result=&hashref2str(\%hash);
+  $result=~s/^_HASH_REF__//;
+  return $result;
+}
+
+sub hashref2str {
+  my ($hashref)=@_;
+  my $result='_HASH_REF__';
+  foreach (keys(%$hashref)) {
+    if (ref($_) eq 'ARRAY') {
+      $result.=&escape(&arrayref2str($_)).'=';
+    } elsif (ref($_) eq 'HASH') {
+      $result.=&escape(&hashref2str($_)).'=';
+    } elsif (ref($_)) {
+      &logthis("Got a ref of ".(ref($_))." skipping.");
+    } else {
+      $result.=&escape($_).'=';
+    }
+
+    if (ref($$hashref{$_}) eq 'ARRAY') {
+      $result.=&escape(&arrayref2str($$hashref{$_})).'&';
+    } elsif (ref($$hashref{$_}) eq 'HASH') {
+      $result.=&escape(&hashref2str($$hashref{$_})).'&';
+    } elsif (ref($$hashref{$_})) {
+      &logthis("Got a ref of ".(ref($$hashref{$_}))." skipping.");
+    } else {
+      $result.=&escape($$hashref{$_}).'&';
+    }
+  }
   $result=~s/\&$//;
   return $result;
 }
@@ -875,9 +972,39 @@ sub str2hash {
   my %returnhash;
   foreach (split(/\&/,$string)) {
     my ($name,$value)=split(/\=/,$_);
-    $returnhash{&unescape($name)}=&unescape($value);
+    $name=&unescape($name);
+    $value=&unescape($value);
+    if ($value =~ /^_HASH_REF__/) {
+      $value =~ s/^_HASH_REF__//;
+      my %hash=&str2hash($value);
+      $value=\%hash;
+    } elsif ($value =~ /^_ARRAY_REF__/) {
+      $value =~ s/^_ARRAY_REF__//;
+      my @array=&str2array($value);
+      $value=\@array;
+    }
+    $returnhash{$name}=$value;
   }
-  return %returnhash;
+  return (%returnhash);
+}
+
+sub str2array {
+  my ($string) = @_;
+  my @returnarray;
+  foreach my $value (split(/\&/,$string)) {
+    $value=&unescape($value);
+    if ($value =~ /^_HASH_REF__/) {
+      $value =~ s/^_HASH_REF__//;
+      my %hash=&str2hash($value);
+      $value=\%hash;
+    } elsif ($value =~ /^_ARRAY_REF__/) {
+      $value =~ s/^_ARRAY_REF__//;
+      my @array=&str2array($value);
+      $value=\@array;
+    }
+    push(@returnarray,$value);
+  }
+  return (@returnarray);
 }
 
 # -------------------------------------------------------------------Temp Store
@@ -1733,23 +1860,28 @@ sub modifyuserauth {
 
 # --------------------------------------------------------------- Modify a user
 
-
 sub modifyuser {
-    my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,
-        $forceid)=@_;
+    my ($udom,    $uname, $uid,
+        $umode,   $upass, $first,
+        $middle,  $last,  $gene,
+        $forceid, $desiredhome)=@_;
     $udom=~s/\W//g;
     $uname=~s/\W//g;
     &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '.
              $umode.', '.$first.', '.$middle.', '.
-	     $last.', '.$gene.'(forceid: '.$forceid.') by '.
-             $ENV{'user.name'}.' at '.$ENV{'user.domain'});  
+	     $last.', '.$gene.'(forceid: '.$forceid.')'.
+             (defined($desiredhome) ? ' desiredhome = '.$desiredhome :
+                                     ' desiredhome not specified'). 
+             ' by '.$ENV{'user.name'}.' at '.$ENV{'user.domain'});
     my $uhome=&homeserver($uname,$udom);
 # ----------------------------------------------------------------- Create User
     if (($uhome eq 'no_host') && ($umode) && ($upass)) {
         my $unhome='';
-	if ($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $udom) {
+        if (defined($desiredhome) && $hostdom{$desiredhome} eq $udom) { 
+            $unhome = $desiredhome;
+	} elsif($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $udom) {
 	    $unhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'};
-        } else {
+        } else { # load balancing routine for determining $unhome
             my $tryserver;
             my $loadm=10000000;
             foreach $tryserver (keys %libserv) {
@@ -1763,7 +1895,8 @@ sub modifyuser {
 	    }
         }
         if (($unhome eq '') || ($unhome eq 'no_host')) {
-	    return 'error: find home';
+	    return 'error: unable to find a home server for '.$uname.
+                   ' in domain '.$udom;
         }
         my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':'.$umode.':'.
                          &escape($upass),$unhome);
@@ -1774,7 +1907,7 @@ sub modifyuser {
         if (($uhome eq '') || ($uhome eq 'no_host') || ($uhome ne $unhome)) {
 	    return 'error: verify home';
         }
-    }
+    }   # End of creation of new user
 # ---------------------------------------------------------------------- Add ID
     if ($uid) {
        $uid=~tr/A-Z/a-z/;
@@ -1792,6 +1925,7 @@ sub modifyuser {
     my %names=&get('environment',
 		   ['firstname','middlename','lastname','generation'],
 		   $udom,$uname);
+    if ($names{'firstname'} =~ m/^error:.*/) { %names=(); }
     if ($first)  { $names{'firstname'}  = $first; }
     if ($middle) { $names{'middlename'} = $middle; }
     if ($last)   { $names{'lastname'}   = $last; }
@@ -1809,14 +1943,15 @@ sub modifyuser {
 
 sub modifystudent {
     my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec,
-        $end,$start,$forceid)=@_;
+        $end,$start,$forceid,$desiredhome)=@_;
     my $cid='';
     unless ($cid=$ENV{'request.course.id'}) {
 	return 'not_in_class';
     }
 # --------------------------------------------------------------- Make the user
     my $reply=&modifyuser
-	($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$forceid);
+	($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$forceid,
+         $desiredhome);
     unless ($reply eq 'ok') { return $reply; }
     my $uhome=&homeserver($uname,$udom);
     if (($uhome eq '') || ($uhome eq 'no_host')) { 
@@ -2255,7 +2390,7 @@ sub metadata {
         my %metathesekeys=();
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }
 	my $metastring=&getfile($perlvar{'lonDocRoot'}.'/res/'.$filename);
-        my $parser=HTML::TokeParser->new(\$metastring);
+        my $parser=HTML::LCParser->new(\$metastring);
         my $token;
         undef %metathesekeys;
         while ($token=$parser->get_token) {
@@ -2344,7 +2479,7 @@ sub metadata {
 		  $metacache{$uri.':'.$unikey.'.'.$_}=$token->[2]->{$_};
               }
               unless (
-                 $metacache{$uri.':'.$unikey}=$parser->get_text('/'.$entry)
+                 $metacache{$uri.':'.$unikey}=&HTML::Entities::decode($parser->get_text('/'.$entry))
 		      ) { $metacache{$uri.':'.$unikey}=
 			      $metacache{$uri.':'.$unikey.'.default'};
 		      }
@@ -2382,12 +2517,23 @@ sub symblist {
     return 'error';
 }
 
+# --------------------------------------------------------------- Clean-up symb
+
+sub symbclean {
+    my $symb=shift;
+# remove version from map
+    $symb=~s/\.(\d+)\.(\w+)\_\_\_/\.$2\_\_\_/;
+# remove version from URL
+    $symb=~s/\.(\d+)\.(\w+)$/\.$2/;
+    return $symb;
+}
+
 # ------------------------------------------------------ Return symb list entry
 
 sub symbread {
     my $thisfn=shift;
     unless ($thisfn) {
-        if ($ENV{'request.symb'}) { return $ENV{'request.symb'}; }
+        if ($ENV{'request.symb'}) { return &symbclean($ENV{'request.symb'}); }
 	$thisfn=$ENV{'request.filename'};
     }
     $thisfn=declutter($thisfn);
@@ -2446,7 +2592,7 @@ sub symbread {
            } 
         }
         if ($syval) {
-           return $syval.'___'.$thisfn; 
+           return &symbclean($syval.'___'.$thisfn); 
         }
     }
     &appenv('request.ambiguous' => $thisfn);
@@ -2585,6 +2731,7 @@ sub unescape {
 # ================================================================ Main Program
 
 sub goodbye {
+   &logthis("Starting Shut down");
    &flushcourselogs();
    &logthis("Shutting down");
 }
@@ -2847,12 +2994,30 @@ devalidate($symb) : devalidate spreadshe
 =item *
 
 hash2str(%hash) : convert a hash into a string complete with escaping and '='
-and '&' separators
+and '&' separators, supports elements that are arrayrefs and hashrefs
+
+=item *
+
+hashref2str($hashref) : convert a hashref into a string complete with
+escaping and '=' and '&' separators, supports elements that are
+arrayrefs and hashrefs
+
+=item *
+
+arrayref2str($arrayref) : convert an arrayref into a string complete
+with escaping and '&' separators, supports elements that are arrayrefs
+and hashrefs
+
+=item *
+
+str2hash($string) : convert string to hash using unescaping and
+splitting on '=' and '&', supports elements that are arrayrefs and
+hashrefs
 
 =item *
 
-str2hash($string) : convert string to hash using unescaping and splitting on
-'=' and '&'
+str2array($string) : convert string to hash using unescaping and
+splitting on '&', supports elements that are arrayrefs and hashrefs
 
 =item *